Commit b5606001 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into fix/allow-deposit-only-legacy-batches

parents 32bd79ec 8a308fc1
---
'@eth-optimism/contracts-bedrock': patch
---
Reduce the time that the system dictator deploy scripts wait before checking the chain state.
---
'@eth-optimism/sdk': patch
---
Have SDK automatically create Standard and ETH bridges when L1StandardBridge is provided.
---
'@eth-optimism/atst': minor
---
Update readAttestations and prepareWriteAttestation to handle keys longer than 32 bytes
---
'@eth-optimism/atst': minor
---
Remove broken allowFailures as option
---
'@eth-optimism/atst': minor
---
Move react api to @eth-optimism/atst/react so react isn't required to run the core sdk
---
'@eth-optimism/contracts-bedrock': patch
---
Added a contsructor to the System Dictator
---
'@eth-optimism/sdk': patch
---
Update migrated withdrawal gaslimit calculation
---
'@eth-optimism/atst': minor
---
Fix main and module in atst package.json
---
'@eth-optimism/batch-submitter-service': patch
---
fix flag name for MaxStateRootElements in batch-submitter
fix log package for proposer
---
'@eth-optimism/atst': patch
---
Fixed bug with atst not defaulting to currently connected chain
---
'@eth-optimism/atst': minor
---
Deprecate parseAttestationBytes and createRawKey in favor for createKey, createValue
---
'@eth-optimism/fault-detector': patch
---
Fixes a bug that would cause the fault detector to error out if no outputs had been proposed yet.
---
'@eth-optimism/chain-mon': patch
'@eth-optimism/data-transport-layer': patch
'@eth-optimism/fault-detector': patch
'@eth-optimism/message-relayer': patch
'@eth-optimism/replica-healthcheck': patch
---
Empty patch release to re-release packages that failed to be released by a bug in the release process.
...@@ -66,9 +66,37 @@ jobs: ...@@ -66,9 +66,37 @@ jobs:
- checkout - checkout
- check-changed: - check-changed:
patterns: op-bindings,op-chain-ops,packages/ patterns: op-bindings,op-chain-ops,packages/
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- run: - run:
name: Install dependencies name: Install dependencies
command: yarn && git diff --exit-code command: yarn install && git diff --exit-code
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-v2-{{ checksum "yarn.lock" }}
paths:
- "node_modules"
- "packages/actor-tests/node_modules"
- "packages/atst/node_modules"
- "packages/balance-monitor/node_modules"
- "packages/chain-mon/node_modules"
- "packages/common-ts/node_modules"
- "packages/contracts/node_modules"
- "packages/contracts-bedrock/node_modules"
- "packages/contracts-governance/node_modules"
- "packages/contracts-periphery/node_modules"
- "packages/core-utils/node_modules"
- "packages/data-transport-layer/node_modules"
- "packages/drippie-mon/node_modules"
- "packages/fault-detector/node_modules"
- "packages/hardhat-deploy-config/node_modules"
- "packages/integration-tests-bedrock/node_modules"
- "packages/message-relayer/node_modules"
- "packages/migration-data/node_modules"
- "packages/replica-healthcheck/node_modules"
- "packages/sdk/node_modules"
- run: - run:
name: print forge version name: print forge version
command: forge --version command: forge --version
...@@ -78,8 +106,6 @@ jobs: ...@@ -78,8 +106,6 @@ jobs:
- persist_to_workspace: - persist_to_workspace:
root: "." root: "."
paths: paths:
- "node_modules"
- "packages/*/node_modules"
- "packages/*/dist" - "packages/*/dist"
- "packages/*/artifacts" - "packages/*/artifacts"
- "packages/contracts/src/contract-artifacts.ts" - "packages/contracts/src/contract-artifacts.ts"
...@@ -267,18 +293,12 @@ jobs: ...@@ -267,18 +293,12 @@ jobs:
steps: steps:
- checkout - checkout
- attach_workspace: { at: "." } - attach_workspace: { at: "." }
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- check-changed: - check-changed:
patterns: contracts-bedrock,hardhat-deploy-config patterns: contracts-bedrock,hardhat-deploy-config
- run:
name: lint
command: yarn lint:check
working_directory: packages/contracts-bedrock
- run:
name: slither
command: |
slither --version
yarn slither || exit 0
working_directory: packages/contracts-bedrock
- run: - run:
name: print forge version name: print forge version
command: forge --version command: forge --version
...@@ -295,6 +315,29 @@ jobs: ...@@ -295,6 +315,29 @@ jobs:
command: codecov --verbose --clean --flags contracts-bedrock-tests command: codecov --verbose --clean --flags contracts-bedrock-tests
environment: environment:
FOUNDRY_PROFILE: ci FOUNDRY_PROFILE: ci
contracts-bedrock-checks:
docker:
- image: ethereumoptimism/ci-builder:latest
steps:
- checkout
- attach_workspace: { at: "." }
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- check-changed:
patterns: contracts-bedrock,hardhat-deploy-config
- run:
name: lint
command: yarn lint:check
working_directory: packages/contracts-bedrock
- run:
name: slither
command: |
slither --version
yarn slither || exit 0
working_directory: packages/contracts-bedrock
- run: - run:
name: gas snapshot name: gas snapshot
command: | command: |
...@@ -303,10 +346,6 @@ jobs: ...@@ -303,10 +346,6 @@ jobs:
environment: environment:
FOUNDRY_PROFILE: ci FOUNDRY_PROFILE: ci
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run:
name: validate spacers
command: yarn validate-spacers
working_directory: packages/contracts-bedrock
- run: - run:
name: storage snapshot name: storage snapshot
command: yarn storage-snapshot && git diff --exit-code .storage-layout command: yarn storage-snapshot && git diff --exit-code .storage-layout
...@@ -316,6 +355,23 @@ jobs: ...@@ -316,6 +355,23 @@ jobs:
command: yarn autogen:invariant-docs && git diff --exit-code ./invariant-docs/*.md command: yarn autogen:invariant-docs && git diff --exit-code ./invariant-docs/*.md
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
contracts-bedrock-validate-spaces:
docker:
- image: ethereumoptimism/ci-builder:latest
steps:
- checkout
- attach_workspace: { at: "." }
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- check-changed:
patterns: contracts-bedrock,hardhat-deploy-config
- run:
name: validate spacers
command: yarn validate-spacers
working_directory: packages/contracts-bedrock
bedrock-echidna-build: bedrock-echidna-build:
docker: docker:
- image: ethereumoptimism/ci-builder:latest - image: ethereumoptimism/ci-builder:latest
...@@ -349,6 +405,10 @@ jobs: ...@@ -349,6 +405,10 @@ jobs:
steps: steps:
- checkout - checkout
- attach_workspace: { at: "." } - attach_workspace: { at: "." }
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- check-changed: - check-changed:
patterns: contracts-bedrock,contracts patterns: contracts-bedrock,contracts
- run: - run:
...@@ -364,6 +424,10 @@ jobs: ...@@ -364,6 +424,10 @@ jobs:
steps: steps:
- checkout - checkout
- attach_workspace: { at: "." } - attach_workspace: { at: "." }
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- check-changed: - check-changed:
patterns: contracts-bedrock,op-bindings patterns: contracts-bedrock,op-bindings
- run: - run:
...@@ -389,6 +453,10 @@ jobs: ...@@ -389,6 +453,10 @@ jobs:
steps: steps:
- checkout - checkout
- attach_workspace: { at: "." } - attach_workspace: { at: "." }
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- check-changed: - check-changed:
patterns: <<parameters.package_name>>,<<parameters.dependencies>> patterns: <<parameters.package_name>>,<<parameters.dependencies>>
- run: - run:
...@@ -442,10 +510,18 @@ jobs: ...@@ -442,10 +510,18 @@ jobs:
steps: steps:
- checkout - checkout
- attach_workspace: { at: "." } - attach_workspace: { at: "." }
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-v2-{{ checksum "yarn.lock" }}
- check-changed: - check-changed:
patterns: packages patterns: packages
# Note: The below needs to be manually configured whenever we # Note: The below needs to be manually configured whenever we
# add a new package to CI. # add a new package to CI.
- run:
name: Check common-ts
command: npx depcheck
working_directory: packages/common-ts
- run: - run:
name: Check contracts name: Check contracts
command: npx depcheck command: npx depcheck
...@@ -470,10 +546,6 @@ jobs: ...@@ -470,10 +546,6 @@ jobs:
name: Check integration-tests name: Check integration-tests
command: npx depcheck command: npx depcheck
working_directory: integration-tests working_directory: integration-tests
- run:
name: Check two-step-monitor
command: npx depcheck
working_directory: packages/two-step-monitor
go-lint: go-lint:
parameters: parameters:
...@@ -538,7 +610,7 @@ jobs: ...@@ -538,7 +610,7 @@ jobs:
command: | command: |
# Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional # Note: We don't use circle CI test splits because we need to split by test name, not by package. There is an additional
# constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building. # constraint that gotestsum does not currently (nor likely will) accept files from different pacakges when building.
OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=false OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \ OP_TESTLOG_DISABLE_COLOR=true OP_E2E_DISABLE_PARALLEL=true OP_E2E_USE_HTTP=<<parameters.use_http>> gotestsum \
--format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \ --format=standard-verbose --junitfile=/tmp/test-results/<<parameters.module>>_http_<<parameters.use_http>>.xml \
-- -timeout=20m ./... -- -timeout=20m ./...
working_directory: <<parameters.module>> working_directory: <<parameters.module>>
...@@ -893,6 +965,12 @@ workflows: ...@@ -893,6 +965,12 @@ workflows:
- contracts-bedrock-tests: - contracts-bedrock-tests:
requires: requires:
- yarn-monorepo - yarn-monorepo
- contracts-bedrock-checks:
requires:
- yarn-monorepo
- contracts-bedrock-validate-spaces:
requires:
- yarn-monorepo
- op-bindings-build: - op-bindings-build:
requires: requires:
- yarn-monorepo - yarn-monorepo
......
...@@ -66,6 +66,7 @@ You'll need the following: ...@@ -66,6 +66,7 @@ You'll need the following:
* [Yarn](https://classic.yarnpkg.com/en/docs/install) * [Yarn](https://classic.yarnpkg.com/en/docs/install)
* [Docker](https://docs.docker.com/get-docker/) * [Docker](https://docs.docker.com/get-docker/)
* [Docker Compose](https://docs.docker.com/compose/install/) * [Docker Compose](https://docs.docker.com/compose/install/)
* [Go](https://go.dev/dl/)
* [Foundry](https://getfoundry.sh) * [Foundry](https://getfoundry.sh)
### Setup ### Setup
......
(The MIT License) (The MIT License)
Copyright 2020-2022 Optimism Copyright 2020-2023 Optimism
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
......
...@@ -24,7 +24,7 @@ If you want to build Optimism, check out the [Protocol Specs](./specs/). ...@@ -24,7 +24,7 @@ If you want to build Optimism, check out the [Protocol Specs](./specs/).
## Community ## Community
General discussion happens most frequently on the [Optimism discord](https://discord.optimism.io). General discussion happens most frequently on the [Optimism discord](https://discord-gateway.optimism.io).
Governance discussion can also be found on the [Optimism Governance Forum](https://gov.optimism.io/). Governance discussion can also be found on the [Optimism Governance Forum](https://gov.optimism.io/).
## Contributing ## Contributing
...@@ -138,7 +138,7 @@ When merging commits to the `develop` branch you MUST include a changeset file i ...@@ -138,7 +138,7 @@ When merging commits to the `develop` branch you MUST include a changeset file i
To add a changeset, run the command `yarn changeset` in the root of this monorepo. To add a changeset, run the command `yarn changeset` in the root of this monorepo.
You will be presented with a small prompt to select the packages to be released, the scope of the release (major, minor, or patch), and the reason for the release. You will be presented with a small prompt to select the packages to be released, the scope of the release (major, minor, or patch), and the reason for the release.
Comments with in changeset files will be automatically included in the changelog of the package. Comments within changeset files will be automatically included in the changelog of the package.
### Triggering Releases ### Triggering Releases
......
...@@ -209,7 +209,7 @@ func NewConfig(ctx *cli.Context) (Config, error) { ...@@ -209,7 +209,7 @@ func NewConfig(ctx *cli.Context) (Config, error) {
MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeFlag.Name), MaxL1TxSize: ctx.GlobalUint64(flags.MaxL1TxSizeFlag.Name),
MaxPlaintextBatchSize: ctx.GlobalUint64(flags.MaxPlaintextBatchSizeFlag.Name), MaxPlaintextBatchSize: ctx.GlobalUint64(flags.MaxPlaintextBatchSizeFlag.Name),
MinStateRootElements: ctx.GlobalUint64(flags.MinStateRootElementsFlag.Name), MinStateRootElements: ctx.GlobalUint64(flags.MinStateRootElementsFlag.Name),
MaxStateRootElements: ctx.GlobalUint64(flags.MinStateRootElementsFlag.Name), MaxStateRootElements: ctx.GlobalUint64(flags.MaxStateRootElementsFlag.Name),
MaxBatchSubmissionTime: ctx.GlobalDuration(flags.MaxBatchSubmissionTimeFlag.Name), MaxBatchSubmissionTime: ctx.GlobalDuration(flags.MaxBatchSubmissionTimeFlag.Name),
PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name), PollInterval: ctx.GlobalDuration(flags.PollIntervalFlag.Name),
NumConfirmations: ctx.GlobalUint64(flags.NumConfirmationsFlag.Name), NumConfirmations: ctx.GlobalUint64(flags.NumConfirmationsFlag.Name),
......
...@@ -13,13 +13,13 @@ import ( ...@@ -13,13 +13,13 @@ import (
"github.com/ethereum-optimism/optimism/bss-core/metrics" "github.com/ethereum-optimism/optimism/bss-core/metrics"
"github.com/ethereum-optimism/optimism/bss-core/txmgr" "github.com/ethereum-optimism/optimism/bss-core/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient" l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum-optimism/optimism/l2geth/log"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
) )
// stateRootSize is the size in bytes of a state root. // stateRootSize is the size in bytes of a state root.
......
# The OP Stack Docs
[![Discord](https://img.shields.io/discord/667044843901681675.svg?color=768AD4&label=discord&logo=https%3A%2F%2Fdiscordapp.com%2Fassets%2F8c9701b98ad4372b58f13fd9f65f966e.svg)](https://discord-gateway.optimism.io)
[![Twitter Follow](https://img.shields.io/twitter/follow/optimismPBC.svg?label=optimismPBC&style=social)](https://twitter.com/optimismPBC)
The OP Stack is an open, collectively maintained development stack for blockchain ecosystems.
This repository contains the source code for the [OP Stack Docs](https://stack.optimism.io).
## Development
### Serving docs locally
```sh
yarn dev
```
Then navigate to [http://localhost:8080](http://localhost:8080).
If that link doesn't work, double check the output of `yarn dev`.
You might already be serving something on port 8080 and the site may be on another port (e.g., 8081).
### Building docs for production
```sh
yarn build
```
You probably don't need to run this command, but now you know.
### Editing docs
Edit the markdown directly in [src/docs](./src/docs).
### Adding new docs
Add your markdown files to [src/docs](./src/docs).
You will also have to update [src/.vuepress/config.js](./src/.vuepress/config.js) if you want these docs to show up in the sidebar.
### Updating the theme
We currently use an ejected version of [vuepress-theme-hope](https://vuepress-theme-hope.github.io/).
Since the version we use was ejected from the original theme, you'll see a bunch of compiled JavaScript files instead of the original TypeScript files.
There's not much we can do about that right now, so you'll just need to make do and edit the raw JS if you need to make theme adjustments.
We're planning to move away from VuePress relatively soon anyway so we won't be fixing this.
{
"name": "@eth-optimism/op-stack-docs",
"version": "0.0.2",
"description": "The OP Stack Docs",
"main": "index.js",
"scripts": {
"dev": "vuepress dev src",
"build": "vuepress build src"
},
"license": "MIT",
"devDependencies": {
"@vuepress/plugin-medium-zoom": "^1.8.2",
"@vuepress/plugin-pwa": "^1.9.7",
"vuepress": "^1.8.2",
"vuepress-plugin-plausible-analytics": "^0.2.1",
"vuepress-theme-hope": "^1.22.0"
},
"dependencies": {
"check-md": "^1.0.2",
"dayjs": "^1.11.7"
}
}
const { description } = require('../../package')
const path = require('path')
module.exports = {
title: 'OP Stack Docs',
description: description,
head: [
['link', { rel: 'manifest', href: '/manifest.json' }],
['meta', { name: 'theme-color', content: '#3eaf7c' }],
['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
['meta', { property: 'og:image', content: 'https://stack.optimism.io/assets/logos/twitter-logo.png' }],
['meta', { name: 'twitter:image', content: 'https://stack.optimism.io/assets/logos/twitter-logo.png' }],
['meta', { name: 'twitter:title', content: 'OP Stack Docs' }],
['meta', { property: 'og:title', content: 'OP Stack Docs' }],
['meta', { name: 'twitter:card', content: 'summary' } ],
['link', { rel: "icon", type: "image/png", sizes: "32x32", href: "/assets/logos/favicon.png"}],
],
theme: path.resolve(__dirname, './theme'),
themeConfig: {
"twitter:card": "summary",
contributor: false,
hostname: 'https://stack.optimism.io',
logo: '/assets/logos/logo.png',
docsDir: 'src',
docsRepo: 'https://github.com/ethereum-optimism/opstack-docs',
docsBranch: 'main',
lastUpdated: false,
darkmode: 'disable',
themeColor: false,
blog: false,
iconPrefix: 'far fa-',
pageInfo: false,
pwa: {
cacheHTML: false,
},
activeHash: {
offset: -200,
},
algolia: {
appId: 'O9WKE9RMCV',
apiKey: '00cf17cba30b374d08d7f7afead974be',
indexName: 'optimism'
},
nav: [
{
text: 'Home',
link: 'https://www.optimism.io/'
},
{
text: 'OP Stack Docs',
link: '/'
},
{
text: 'Optimism Docs',
link: 'https://community.optimism.io/'
},
{
text: 'Governance',
link: 'https://community.optimism.io/docs/governance/'
},
{
text: 'Community',
items: [
{
icon: 'discord',
iconPrefix: 'fab fa-',
iconClass: 'color-discord',
text: 'Discord',
link: 'https://discord.optimism.io',
},
{
icon: 'github',
iconPrefix: 'fab fa-',
iconClass: 'color-github',
text: 'GitHub',
link: 'https://github.com/ethereum-optimism/optimism',
},
{
icon: 'twitter',
iconPrefix: 'fab fa-',
iconClass: 'color-twitter',
text: 'Twitter',
link: 'https://twitter.com/optimismFND',
},
{
icon: 'twitch',
iconPrefix: 'fab fa-',
iconClass: 'color-twitch',
text: 'Twitch',
link: 'https://www.twitch.tv/optimismpbc'
},
{
icon: 'medium',
iconPrefix: 'fab fa-',
iconClass: 'color-medium',
text: 'Blog',
link: 'https://optimismpbc.medium.com/'
},
{
icon: 'computer-classic',
iconClass: 'color-ecosystem',
text: 'Ecosystem',
link: 'https://www.optimism.io/apps/all',
},
{
icon: 'globe',
iconClass: 'color-optimism',
text: 'optimism.io',
link: 'https://www.optimism.io/',
}
]
}
],
searchPlaceholder: 'Search the docs',
sidebar: [
{
title: "OP Stack",
collapsable: false,
children: [
'/',
[
'/docs/understand/design-principles.md',
'Design Principles'
],
'/docs/understand/landscape.md',
'/docs/understand/explainer.md'
]
},
{
title: "Releases",
collapsable: false,
children: [
'/docs/releases/',
{
title: "Bedrock",
collapsable: true,
children: [
'/docs/releases/bedrock/',
'/docs/releases/bedrock/explainer.md',
'/docs/releases/bedrock/differences.md'
]
}
]
},
{
title: "Building OP Stack Rollups",
collapsable: false,
children: [
'/docs/build/getting-started.md',
'/docs/build/conf.md',
'/docs/build/explorer.md',
'/docs/build/sdk.md',
{
title: "OP Stack Hacks",
collapsable: true,
children: [
'/docs/build/hacks.md',
'/docs/build/featured.md',
'/docs/build/data-avail.md',
'/docs/build/derivation.md',
'/docs/build/execution.md',
'/docs/build/settlement.md',
{
title: "Sample Hacks",
children: [
"/docs/build/tutorials/add-attr.md",
"/docs/build/tutorials/new-precomp.md",
"/docs/build/tutorials/predeploys.md"
]
} // End of tutorials
],
}, // End of OP Stack hacks
],
}, // End of Building OP Stack Rollups
{
title: "Contributing",
collapsable: false,
children: [
'/docs/contribute.md',
]
},
{
title: "Security",
collapsable: false,
children: [
'/docs/security/faq.md',
'/docs/security/policy.md',
]
},
], // end of sidebar
plugins: [
"@vuepress/pwa",
[
'@vuepress/plugin-medium-zoom',
{
selector: ':not(a) > img'
}
],
"plausible-analytics"
]
}
}
// module.exports.themeConfig.sidebar["/docs/useful-tools/"] = module.exports.themeConfig.sidebar["/docs/developers/"]
import event from '@vuepress/plugin-pwa/lib/event'
export default ({ router }) => {
registerAutoReload();
router.addRoutes([
{ path: '/docs/', redirect: '/' },
])
}
// When new content is detected by the app, this will automatically
// refresh the page, so that users do not need to manually click
// the refresh button. For more details see:
// https://linear.app/optimism/issue/FE-1003/investigate-archive-issue-on-docs
const registerAutoReload = () => {
event.$on('sw-updated', e => e.skipWaiting().then(() => {
location.reload(true);
}))
}
{
"name": "OP Docs",
"short_name": "OP Docs",
"description": "The official OP Docs",
"icons": [
{
"src": "/assets/logos/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/assets/logos/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "/assets/logos/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/assets/logos/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/assets/logos/icon-1020x1020.png",
"sizes": "any",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ff0420"
}
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,400;0,600;0,700;1,600;1,700&display=swap');
@import 'https://pro.fontawesome.com/releases/v5.15.4/css/all.css';
main, body, html {
font-family: 'Open Sans', sans-serif;
}
p {
font-size: 16px;
line-height: 24px;
}
aside.sidebar {
background-color: #F1F4F9;
border-right: none;
}
p.sidebar-heading {
color: #323A43 !important;
font-family: 'Open Sans', sans-serif;
font-weight: 600 !important;
font-size: 14px !important;
line-height: 24px !important;
min-height: 36px;
margin-left: 20px;
padding: 8px 16px !important;
width: calc(100% - 60px) !important;
border-radius: 8px;
}
a.sidebar-link {
font-family: 'Open Sans', sans-serif;
font-size: 14px !important;
line-height: 24px !important;
min-height: 36px;
margin-top: 3px;
margin-left: 20px;
padding: 8px 16px !important;
width: calc(100% - 60px) !important;
border-radius: 8px;
}
section.sidebar-group a.sidebar-link,
section.sidebar-group p.sidebar-heading.clickable {
margin-left: 32px;
width: calc(100% - 60px) !important;
}
.sidebar-links:not(.sidebar-group-items) > li > a.sidebar-link {
font-weight: 600 !important;
color: #323A43 !important;
}
.sidebar-links:not(.sidebar-group-items) > li > a.sidebar-link.active {
border-left-color: #F1F4F9 !important;
background-color: #FFDBDF !important;
color: #FF0420 !important;
}
a.sidebar-link.active {
border-left-color: #F1F4F9 !important;
background-color: #FFDBDF !important;
color: #FF0420 !important;
}
h1 {
font-size: 50px;
}
h2 {
font-size: 40px;
}
h3 {
font-size: 28px;
}
h4 {
font-size: 20px;
}
h1 {
font-family: 'Rubik', sans-serif;
font-weight: 700;
font-style: italic;
border-bottom: none;
color: #202327 !important;
}
h2, h3, h4 {
font-family: 'Rubik', sans-serif;
border-bottom: none;
font-weight: 400;
}
#search-form {
@media (min-width $MQNormal) {
margin-left: 2rem;
}
}
.search-box {
@media (min-width $MQNormal) {
order: 1;
margin-right: 0;
margin-left: 1rem;
.suggestions {
left: auto !important;
right: 0 !important;
}
}
input {
border-color: #CBD5E0 !important;
border-radius: 100px !important;
background-color: #FFFFFF !important;
}
}
header.navbar {
border-bottom: none;
box-shadow: 0px 6px 8px -6px rgba(20, 23, 26, 0.06), 0px 8px 16px -6px rgba(20, 23, 26, 0.04);
--bgcolor-blur: hsla(0,0%,100%,0.9);
}
span.site-name {
display: none !important;
}
a.nav-link,
div.nav-item span.title {
font-weight: 600;
}
a.nav-link:not(.router-link-active),
div.nav-item span.title {
color: #68778D;
}
.theme-default-content:not(.custom) > p {
text-align: inherit !important;
}
.sidebar {
box-shadow: none !important;
}
div.hero-info {
color: white;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
}
div.hero-info .description {
display: none;
}
div.hero-info #main-title {
color: white !important;
}
header.hero > img {
border-radius: 16px;
max-height: none !important;
}
header.hero {
margin-top: 20px;
position: relative;
justify-content: space-between !important;
}
.home .features {
border-top: none !important;
justify-content: space-between !important;
margin: 0 !important;
padding-top: 0.5rem !important;
}
.home .features h2 {
font-family: 'Open Sans', sans-serif;
font-style: normal;
font-size: 16px !important;
font-weight: 600 !important;
color: #202327 !important;
}
.home .features p {
font-family: 'Open Sans', sans-serif;
font-size: 14px !important;
color: #68778D !important;
}
.home .features .icon-container {
height: 44px;
width: 44px;
background-color: #FFF0F1;
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
color: #FF0420;
}
.home .features .feature {
background-color: #FFFFFF !important;
box-shadow: 0px 6px 8px -6px rgba(20, 23, 26, 0.12), 0px 8px 16px -6px rgba(20, 23, 26, 0.08);
border-radius: 16px !important;
margin: 0 !important;
margin-bottom: 2rem !important;
padding: 1.5rem !important;
}
.features-header {
margin-top: 30px;
margin-bottom: 0px;
}
div.theme-container:not(.has-sidebar) {
background-color: #F1F4F9;
}
.anchor-header {
color: #202327;
font-weight: 600;
font-size: 14px;
font-height: 20px;
margin-bottom: 10px;
}
.anchor-support {
margin-top: 20px;
}
.anchor-support-links i {
width: 20px;
text-align: center;
margin-right: 5px;
}
.anchor-support-links a {
color: #68778D;
}
.anchor-support-links a div {
height: 30px;
font-size: 14px;
}
.anchor-support-links a:hover {
color: #FF0420;
}
#anchor {
width: 15rem !important;
}
#anchor .anchor {
line-height: 27.2px !important;
}
#anchor .anchor div {
color: #68778D !important;
}
#anchor .anchor.active div {
font-weight: 600 !important;
}
.theme-default-content code {
top: 1px;
line-height: 22.4px !important;
vertical-align: middle !important;
}
.nav-dropdown svg.icon.outbound {
display: none;
}
.nav-dropdown .dropdown-item i {
width: 20px;
}
.color-discord {
color: #5865F2;
}
.color-github {
color: #121212;
}
.color-twitter {
color: #1DA1F2;
}
.color-twitch {
color: #6441A5;
}
.color-medium {
color: #000000;
}
.color-optimism {
color: #FF0420;
}
.color-ecosystem {
color: #ea94db;
}
$textColor = #000000
$accentColor = #f01a37
$backgroundColor = #272934
$lightBackgroundColor = #f3f3f3
$sidebarWidth = 320px
import Vue from "vue";
export default Vue.extend({
name: "AlgoliaSearchDropdown",
props: {
options: { type: Object, required: true },
},
data: () => ({
placeholder: "",
}),
watch: {
$lang(newValue) {
this.update(this.options, newValue);
},
options(newValue) {
this.update(newValue, this.$lang);
},
},
mounted() {
this.initialize(this.options, this.$lang);
this.placeholder =
this.$site.themeConfig.searchPlaceholder || "";
},
methods: {
initialize(userOptions, lang) {
void Promise.all([
import(
/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.js"),
import(
/* webpackChunkName: "docsearch" */ "docsearch.js/dist/cdn/docsearch.min.css"),
]).then(([docsearch]) => {
// eslint-disable-next-line
docsearch.default(Object.assign(Object.assign({}, userOptions), { inputSelector: "#algolia-search-input",
// #697 Make docsearch work well at i18n mode.
algoliaOptions: {
facetFilters: [`lang:${lang}`].concat(
// eslint-disable-next-line
userOptions.facetFilters || []),
}, handleSelected: (_input, _event, suggestion) => {
const { pathname, hash } = new URL(suggestion.url);
const routepath = pathname.replace(this.$site.base, "/");
if (this.$router.getRoutes().some((route) => route.path === routepath))
void this.$router.push(`${routepath}${decodeURIComponent(hash)}`);
else
window.open(suggestion.url);
} }));
});
},
update(options, lang) {
this.$el.innerHTML =
'<input id="algolia-search-input" class="search-query">';
this.initialize(options, lang);
},
},
});
//# sourceMappingURL=Dropdown.js.map
\ No newline at end of file
<template>
<form
id="search-form"
class="algolia-search-wrapper search-box"
role="search"
>
<label class="sr-only" for="algolia-search-input">Algolia search</label>
<input
id="algolia-search-input"
class="search-query"
:placeholder="placeholder"
/>
</form>
</template>
<script src="./Dropdown" />
<style lang="stylus">
.algolia-search-wrapper
& > span
vertical-align middle
.algolia-autocomplete
line-height normal
.ds-dropdown-menu
min-width 515px !important
margin 6px 0 0
padding 4px
border 1px solid var(--light-grey)
border-radius 4px
background var(--bgcolor)
font-size 16px
text-align left
@media (max-width $MQMobile)
min-width calc(100vw - 4rem) !important
max-width calc(100vw - 4rem) !important
&:before
border-color var(--light-grey)
[class*=ds-dataset-]
padding 0
border none
background var(--bgcolor)
.ds-suggestions
margin-top 0
.ds-suggestion
border-bottom 1px solid var(--border-color)
.algolia-docsearch-suggestion--highlight
color var(--accent-color)
.algolia-docsearch-suggestion
padding 0
border-color var(--border-color)
background var(--bgcolor)
color var(--text-color)
.algolia-docsearch-suggestion--category-header
padding 5px 10px
margin-top 0
background var(--accent-color)
color var(--white)
font-weight 600
.algolia-docsearch-suggestion--highlight
background rgba(255, 255, 255, 0.6)
.algolia-docsearch-suggestion--wrapper
padding 0
@media (max-width $MQMobile)
padding 5px 7px 5px 5px !important
.algolia-docsearch-suggestion--title
margin-bottom 0
color var(--text-color)
font-weight 600
.algolia-docsearch-suggestion--subcategory-column
vertical-align top
padding 5px 7px 5px 5px
border-color var(--border-color)
background var(--bgcolor)
color var(--text-color)
@media (min-width $MQMobile)
display table-cell
float none
width 150px
min-width 150px
@media (max-width $MQMobile)
padding 0 !important
background white !important
&:after
display none
.algolia-docsearch-suggestion--subcategory-column-text
color #555
&:after
@media (max-width $MQMobile)
display inline-block
vertical-align middle
content ' > '
width 5px
margin -3px 3px 0
font-size 10px
line-height 14.4px
.algolia-docsearch-suggestion--content
@media (min-width $MQMobile)
display table-cell
float none
vertical-align top
width 100%
.algolia-docsearch-footer
border-color var(--border-color)
.ds-cursor .algolia-docsearch-suggestion--content
background var(--grey3)
color var(--text-color)
</style>
import { createElement } from "preact";
import Vue from "vue";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: docsearch type issue
import docsearch from "@docsearch/js";
export default Vue.extend({
name: "AlgoliaSearchFull",
props: {
options: { type: Object, required: true },
},
watch: {
$lang(newValue) {
this.update(this.options, newValue);
},
options(newValue) {
this.update(newValue, this.$lang);
},
},
mounted() {
this.initialize(this.options, this.$lang);
},
methods: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
initialize(userOptions, _lang) {
// eslint-disable-next-line
docsearch(Object.assign(Object.assign({ container: "#docsearch", placeholder: this.$site.themeConfig.searchPlaceholder || "" }, userOptions), { searchParameters: userOptions.searchParameters || {},
// transform full url to route path
transformItems: (items) => items.map((item) => (Object.assign(Object.assign({}, item), {
// the `item.url` is full url with protocol and hostname
// so we have to transform it to vue-router path
url: this.resolveRoutePathFromUrl(item.url) }))),
// render the hit component with custom `onClick` handler
hitComponent: ({ hit, children }) => createElement("a", {
href: hit.url,
onClick: (event) => {
// We rely on the native link scrolling when user is
// already on the right anchor because Vue Router doesn’t
// support duplicated history entries.
if (this.$route.fullPath === hit.url)
return;
const fullPath = `${window.location.origin}${hit.url}`;
const { pathname: hitPathname } = new URL(fullPath);
// If the hits goes to another page, we prevent the native link behavior
// to leverage the Vue Router loading feature.
if (this.$route.path !== hitPathname)
event.preventDefault();
if (this.$router
.getRoutes()
.some((route) => route.path.replace(/index\.html$/, "") === hitPathname))
void this.$router.push(hit.url);
else
window.open(fullPath);
},
}, children), navigator: {
navigate: ({ itemUrl }) => {
const fullPath = `${window.location.origin}${itemUrl}`;
const { pathname: hitPathname } = new URL(fullPath);
// Vue Router doesn’t handle same-page navigation so we use
// the native browser location API for anchor navigation.
if (this.$route.path === hitPathname)
window.location.assign(fullPath);
else if (this.$router
.getRoutes()
.some((route) => route.path === hitPathname))
void this.$router.push(itemUrl);
else
window.open(fullPath);
},
navigateNewTab({ itemUrl }) {
window.open(itemUrl);
},
navigateNewWindow({ itemUrl }) {
window.open(itemUrl);
},
} }));
},
resolveRoutePathFromUrl(absoluteUrl) {
const { pathname, hash } = new URL(absoluteUrl);
return `${pathname.replace(this.$site.base, "/")}${hash}`;
},
update(options, lang) {
this.$el.innerHTML = '<div id="docsearch"></div>';
this.initialize(options, lang);
},
},
});
//# sourceMappingURL=Full.js.map
\ No newline at end of file
import Vue from "vue";
import { isActive } from "@theme/utils/path";
const renderLink = (h, { text, link, level }) => h("RouterLink", {
props: {
to: link,
activeClass: "",
exactActiveClass: "",
},
class: {
"anchor-link": true,
[level ? `heading${level}` : ""]: level,
},
}, [h("div", {}, [text])]);
const renderChildren = (h, { children, route }) => h("ul", { class: "anchor-list" }, children.map((child) => {
const active = isActive(route, `${route.path}#${child.slug}`);
return h("li", { class: { anchor: true, active } }, [
renderLink(h, {
text: child.title,
link: `${route.path}#${child.slug}`,
level: child.level,
}),
]);
}));
export default Vue.extend({
name: "Anchor",
functional: true,
props: {
items: {
type: Array,
default: () => [],
},
},
render(h, { props, parent: { $page, $route } }) {
return h("div", { attrs: { class: "anchor-place-holder" } }, [
h("aside", { attrs: { id: "anchor" } }, [
($page.headers && $page.headers.length)
? h("div", { class: "anchor-header" }, [
"On this page"
])
: null,
h("div", { class: "anchor-wrapper" }, [
props.items.length
? renderChildren(h, {
children: props.items,
route: $route,
})
: $page.headers
? renderChildren(h, {
children: $page.headers,
route: $route,
})
: null,
]),
($page.headers && $page.headers.length)
? h("div", [
h("div", { class: "anchor-header anchor-support" }, [
"Support"
]),
h("div", { class: "anchor-support-links" }, [
h("a", { attrs: { href: "https://discord.optimism.io", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "fab fa-discord" } }),
" Discord community "
])
]),
h("a", { attrs: { href: "https://forms.monday.com/forms/055862bfb7f4091be3db2567288296f8?r=use1", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "far fa-comment-dots" } }),
" Join the Superchain "
])
]),
h("a", { attrs: { href: "https://github.com/ethereum-optimism/optimism/issues", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "fab fa-github" } }),
" Make an issue on GitHub"
])
]),
h("a", { attrs: { href: "https://github.com/ethereum-optimism/optimism/contribute", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "far fa-hands-helping" } }),
" Contribute to Optimism"
])
]),
])
])
: null
]),
]);
},
});
//# sourceMappingURL=Anchor.js.map
<script src="./Anchor" />
<style lang="stylus">
$headings = 2 3 4 5 6
.anchor-place-holder
position sticky
top: ($navbarHeight + 2rem)
max-width $contentWidth
margin 0 auto
padding 0 2.5rem
z-index 99
@media (max-width $MQNarrow)
padding 0 1.5rem
& + .theme-default-content:not(.custom)
padding-top 0
#anchor
display none
position absolute
left calc(100% + 0.5rem)
min-width 10rem
max-width 15rem
max-height 85vh
overflow-y scroll
@media (min-width $MQWide)
.has-anchor &
display block
&::-webkit-scrollbar-track-piece
background transparent
&::-webkit-scrollbar
width 3px
&::-webkit-scrollbar-thumb:vertical
background #ddd
.theme-dark &
background #333
.anchor-wrapper
position relative
padding-left 8px
&::before
content ' '
position absolute
top 0
left 0px
bottom 0
width 2px
background var(--border-color)
z-index -1
> .anchor-list
margin 0
.anchor-list
padding-left 0
.anchor
position relative
box-sizing border-box
padding 1px 8px
list-style none
line-height 1.5
&::before
content ' '
position absolute
z-index 2
top 0
bottom 0
left -8px
width 2px
background transparent
&:hover
.anchor-link
color var(--accent-color)
&.active
.anchor-link
color var(--accent-color)
&::before
background var(--accent-color)
.anchor-link
display inline-block
vertical-align middle
position relative
max-width 100%
color var(--light-grey)
> div
text-overflow ellipsis
white-space nowrap
overflow hidden
for $heading in $headings
&.heading{$heading}
padding-left ($heading * 8 - 16) px
font-size: (16 - $heading)px
</style>
import Vue from "vue";
export default Vue.extend({
name: "Clipboard",
props: {
html: { type: String, default: "" },
lang: { type: String, default: "en-US" },
},
data: () => ({
location: "",
}),
computed: {
copyright() {
const { author } = this.$themeConfig;
const content = {
"zh-CN": `${this.html}\n-----\n${author ? `著作权归${author}所有。\n` : ""}链接: ${this.location}`,
"en-US": `${this.html}\n-----\n${author ? `Copyright by ${author}.\n` : ""}Link: ${this.location}`,
"vi-VN": `${this.html}\n-----\n${author ? `bản quyền bởi ${author}.\n` : ""}Liên kết: ${this.location}`,
};
return content[this.lang];
},
},
created() {
if (typeof window !== "undefined")
this.location = window.location.toString();
},
});
//# sourceMappingURL=Clipboard.js.map
\ No newline at end of file
<template>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="copyright" />
</template>
<script src="./Clipboard" />
import Vue from "vue";
import { getSidebarItems } from "@theme/utils/sidebar";
import Navbar from "@theme/components/Navbar/Navbar.vue";
import Sidebar from "@theme/components/Sidebar/Sidebar.vue";
import throttle from "lodash.throttle";
export default Vue.extend({
name: "Common",
components: {
Navbar,
Sidebar,
},
props: {
navbar: { type: Boolean, default: true },
sidebar: { type: Boolean, default: true },
},
data: () => ({
isSidebarOpen: false,
hideNavbar: false,
touchStart: {
clientX: 0,
clientY: 0,
},
}),
computed: {
enableNavbar() {
if (this.navbar === false)
return false;
const { frontmatter } = this.$page;
if (frontmatter.navbar === false || this.$themeConfig.navbar === false)
return false;
return Boolean(this.$title ||
this.$themeConfig.logo ||
this.$themeConfig.repo ||
this.$themeConfig.nav ||
this.$themeLocaleConfig.nav);
},
enableSidebar() {
if (this.sidebar === false)
return false;
return (!this.$frontmatter.home &&
this.$frontmatter.sidebar !== false &&
this.sidebarItems.length !== 0);
},
sidebarItems() {
if (this.sidebar === false)
return [];
return getSidebarItems(this.$page, this.$site, this.$localePath);
},
pageClasses() {
const userPageClass = this.$page.frontmatter.pageClass;
return [
{
"has-navbar": this.enableNavbar,
"has-sidebar": this.enableSidebar,
"has-anchor": this.enableAnchor,
"hide-navbar": this.hideNavbar,
"sidebar-open": this.isSidebarOpen,
},
userPageClass,
];
},
headers() {
return this.getHeader(this.sidebarItems);
},
enableAnchor() {
return (this.$frontmatter.anchorDisplay ||
(this.$themeConfig.anchorDisplay !== false &&
this.$frontmatter.anchorDisplay !== false));
},
},
mounted() {
let lastDistance = 0;
this.$router.afterEach(() => {
this.isSidebarOpen = false;
});
window.addEventListener("scroll", throttle(() => {
const distance = this.getScrollTop();
// scroll down
if (lastDistance < distance && distance > 58) {
if (!this.isSidebarOpen)
this.hideNavbar = true;
// scroll up
}
else
this.hideNavbar = false;
lastDistance = distance;
}, 300));
},
methods: {
/** Get scroll distance */
getScrollTop() {
return (window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop ||
0);
},
toggleSidebar(to) {
this.isSidebarOpen = typeof to === "boolean" ? to : !this.isSidebarOpen;
this.$emit("toggle-sidebar", this.isSidebarOpen);
},
// Side swipe
onTouchStart(event) {
this.touchStart = {
clientX: event.changedTouches[0].clientX,
clientY: event.changedTouches[0].clientY,
};
},
onTouchEnd(event) {
const dx = event.changedTouches[0].clientX - this.touchStart.clientX;
const dy = event.changedTouches[0].clientY - this.touchStart.clientY;
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40)
if (dx > 0 && this.touchStart.clientX <= 80)
this.toggleSidebar(true);
else
this.toggleSidebar(false);
},
getHeader(items) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.type === "group") {
const matching = this.getHeader(item.children);
if (matching.length !== 0)
return matching;
}
else if (item.type === "page" &&
item.headers &&
item.path === this.$route.path)
return item.headers;
}
return [];
},
},
});
//# sourceMappingURL=Common.js.map
<template>
<div
class="theme-container"
:class="pageClasses"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<!-- Content -->
<template>
<Navbar v-if="enableNavbar" @toggle-sidebar="toggleSidebar">
<template #start>
<slot name="navbar-start" />
</template>
<template #center>
<slot name="navbar-center" />
</template>
<template #end>
<slot name="navbar-end" />
</template>
</Navbar>
<div class="sidebar-mask" @click="toggleSidebar(false)" />
<Sidebar :items="sidebarItems" @toggle-sidebar="toggleSidebar">
<template #top>
<slot name="sidebar-top" />
</template>
<template #center>
<slot name="sidebar-center" />
</template>
<template #bottom>
<slot name="sidebar-bottom" />
</template>
</Sidebar>
<slot :sidebar-items="sidebarItems" :headers="headers" />
</template>
</div>
</template>
<script src="./Common" />
<style lang="stylus">
.theme-container
min-height 100vh
.sidebar-mask
position fixed
z-index 9
top 0
left 0
width 100vw
height 100vh
display none
.theme-container.sidebar-open &
display block
</style>
import Vue from "vue";
import MyTransition from "@theme/components/MyTransition.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { navigate } from "@theme/utils/navigate";
export default Vue.extend({
name: "Home",
components: { MyTransition, NavLink },
computed: {
actionLinks() {
const { action } = this.$frontmatter;
if (Array.isArray(action))
return action;
return [action];
},
},
methods: {
navigate(link) {
navigate(link, this.$router, this.$route);
},
},
});
//# sourceMappingURL=Home.js.map
\ No newline at end of file
<template>
<main
:aria-labelledby="$frontmatter.heroText !== null ? 'main-title' : null"
class="home"
>
<header class="hero">
<MyTransition>
<img
v-if="$frontmatter.heroImage"
key="light"
:class="{ light: Boolean($frontmatter.darkHeroImage) }"
:src="$withBase($frontmatter.heroImage)"
:alt="$frontmatter.heroAlt || 'HomeLogo'"
/>
</MyTransition>
<MyTransition>
<img
v-if="$frontmatter.darkHeroImage"
key="dark"
class="dark"
:src="$withBase($frontmatter.darkHeroImage)"
:alt="$frontmatter.heroAlt || 'HomeLogo'"
/>
</MyTransition>
<div class="hero-info">
<MyTransition :delay="0.04">
<h1
v-if="$frontmatter.heroText !== false"
id="main-title"
v-text="$frontmatter.heroText || $title || 'Hello'"
/>
</MyTransition>
<MyTransition :delay="0.08">
<p
class="description"
v-text="
$frontmatter.tagline ||
$description ||
'Welcome to your VuePress site'
"
/>
</MyTransition>
<MyTransition :delay="0.12">
<p v-if="$frontmatter.action" class="action">
<NavLink
v-for="action in actionLinks"
:key="action.text"
:item="action"
class="action-button"
:class="action.type || ''"
/>
</p>
</MyTransition>
</div>
</header>
<MyTransition :delay="0.16">
<div>
<h2 class="features-header">Resources</h2>
<div
v-if="$frontmatter.features && $frontmatter.features.length"
class="features"
>
<template v-for="(feature, index) in $frontmatter.features">
<div
v-if="feature.link"
:key="index"
class="feature link"
:class="`feature${index % 9}`"
tabindex="0"
role="navigation"
@click="navigate(feature.link)"
>
<div class="icon-container">
<i :class="`far fa-${feature.icon}`"></i>
</div>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</div>
<div
v-else
:key="index"
class="feature"
:class="`feature${index % 9}`"
>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</div>
</template>
</div>
</div>
</MyTransition>
<MyTransition :delay="0.24">
<Content class="theme-default-content custom" />
</MyTransition>
</main>
</template>
<script src="./Home" />
<style lang="stylus">
.home
display block
max-width $homePageWidth
min-height 100vh - $navbarHeight
padding $navbarHeight 2rem 0
margin 0px auto
overflow-x hidden
@media (max-width $MQNarrow)
min-height 100vh - $navbarMobileHeight
padding-top $navbarMobileHeight
@media (max-width $MQMobileNarrow)
padding-left 1.5rem
padding-right 1.5rem
.hero
text-align center
@media (min-width $MQNarrow)
display flex
justify-content space-evenly
align-items center
text-align left
img
display block
max-width 100%
max-height 320px
margin 0
@media (max-width $MQNarrow)
max-height 280px
margin 3rem auto 1.5rem
@media (max-width $MQMobile)
max-height 240px
margin 2rem auto 1.2rem
@media (max-width $MQMobileNarrow)
max-height 210px
margin 1.5rem auto 1rem
.theme-light &
&.light
display block
&.dark
display none
.theme-dark &
&.light
display none
&.dark
display block
h1
font-size 3rem
@media (max-width $MQMobile)
font-size 2.5rem
@media (max-width $MQMobileNarrow)
font-size 2rem
h1, .description, .action
margin 1.8rem auto
@media (max-width $MQMobile)
margin 1.5rem auto
@media (max-width $MQMobileNarrow)
margin 1.2rem auto
.description
max-width 35rem
color var(--text-color-l40)
font-size 1.6rem
line-height 1.3
@media (max-width $MQMobile)
font-size 1.4rem
@media (max-width $MQMobileNarrow)
font-size 1.2rem
.action-button
display inline-block
margin 0.6rem 0.8rem
padding 1rem 1.5rem
border 2px solid var(--accent-color)
border-radius 2rem
color var(--accent-color)
font-size 1.2rem
transition background 0.1s ease
overflow hidden
@media (max-width $MQMobile)
padding 0.8rem 1.2rem
font-size 1.1rem
@media (max-width $MQMobileNarrow)
font-size 1rem
&:hover
color var(--white)
background-color var(--accent-color)
&.primary
color var(--white)
background-color var(--accent-color)
&:hover
border-color var(--accent-color-l10)
background-color var(--accent-color-l10)
.theme-dark &
&:hover
border-color var(--accent-color-d10)
background-color var(--accent-color-d10)
.features
display flex
flex-wrap wrap
justify-content center
align-items stretch
align-content stretch
margin 0 -2rem
padding 1.2rem 0
border-top 1px solid var(--border-color)
@media (max-width $MQMobileNarrow)
margin 0 -1.5rem
.feature
display flex
flex-direction column
justify-content center
flex-basis calc(33% - 4rem)
margin 0.5rem
padding 0 1.5rem
border-radius 0.5rem
transition transform 0.3s, box-shadow 0.3s
overflow hidden
@media (max-width $MQNarrow)
flex-basis calc(50% - 4rem)
@media (max-width $MQMobile)
font-size 0.95rem
@media (max-width $MQMobileNarrow)
flex-basis calc(100%)
font-size 0.9rem
margin 0.5rem 0
border-radius 0
&.link
cursor pointer
&:hover
box-shadow 0 2px 12px 0 var(--card-shadow-color)
h2
margin-bottom 0.25rem
border-bottom none
color var(--text-color-l10)
font-size 1.25rem
font-weight 500
@media (max-width $MQMobileNarrow)
font-size 1.2rem
p
margin-top 0
color var(--text-color-l25)
{$contentClass}
padding-bottom 1.5rem
@require '~@mr-hope/vuepress-shared/styles/colors.styl'
for $color, $index in $colors
.home .features .feature{$index}
&, .theme-light &
background lighten($color, 90%)
.theme-dark &
background darken($color, 75%)
</style>
import Vue from "vue";
export default Vue.extend({
name: "MyTransition",
props: {
delay: { type: Number, default: 0 },
duration: { type: Number, default: 0.25 },
disable: { type: Boolean, default: false },
},
methods: {
setStyle(items) {
if (this.disable) {
return;
}
items.style.transition = `transform ${this.duration}s ease-in-out ${this.delay}s, opacity ${this.duration}s ease-in-out ${this.delay}s`;
items.style.transform = "translateY(-20px)";
items.style.opacity = "0";
},
unsetStyle(items) {
if (this.disable) {
return;
}
items.style.transform = "translateY(0)";
items.style.opacity = "1";
},
},
});
//# sourceMappingURL=MyTransition.js.map
\ No newline at end of file
<template>
<transition
name="drop"
appear
@appear="setStyle"
@after-appear="unsetStyle"
@enter="setStyle"
@after-enter="unsetStyle"
@before-leave="setStyle"
>
<slot />
</transition>
</template>
<script src="./MyTransition" />
<style lang="stylus">
.drop-enter, .drop-leave-to
opacity 0
transform translateY(-20px)
</style>
import Vue from "vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
export default Vue.extend({
name: "DropdownLink",
components: { NavLink },
props: {
item: { type: Object, required: true },
},
data: () => ({
open: false,
}),
computed: {
dropdownAriaLabel() {
return this.item.ariaLabel || this.item.text;
},
iconPrefix() {
const { iconPrefix } = this.$themeConfig;
return iconPrefix === "" ? "" : iconPrefix || "icon-";
},
},
watch: {
$route() {
this.open = false;
},
},
methods: {
setOpen(value) {
this.open = value;
},
handleDropdown(event) {
const isTriggerByTab = event.detail === 0;
if (isTriggerByTab)
this.setOpen(!this.open);
},
isLastItemOfArray(item, array) {
if (Array.isArray(array))
return item === array[array.length - 1];
return false;
},
},
});
//# sourceMappingURL=DropdownLink.js.map
\ No newline at end of file
<template>
<div class="dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="dropdownAriaLabel"
@click="handleDropdown"
>
<slot name="title">
<span class="title">
<i v-if="item.icon" :class="`iconfont ${iconPrefix}${item.icon}`" />
{{ item.text }}
</span>
</slot>
<span class="arrow" />
</button>
<ul class="nav-dropdown">
<li
v-for="(child, index) in item.items"
:key="child.link || index"
class="dropdown-item"
>
<template v-if="child.type === 'links'">
<h4 class="dropdown-subtitle">
<NavLink
v-if="child.link"
:item="child"
@focusout="
isLastItemOfArray(child, item.children) &&
child.children.length === 0 &&
setOpen(false)
"
/>
<span v-else>{{ child.text }}</span>
</h4>
<ul class="dropdown-subitem-wrapper">
<li
v-for="grandchild in child.items"
:key="grandchild.link"
class="dropdown-subitem"
>
<NavLink
:item="grandchild"
@focusout="
isLastItemOfArray(grandchild, child.items) &&
isLastItemOfArray(child, item.items) &&
setOpen(false)
"
/>
</li>
</ul>
</template>
<NavLink
v-else
:item="child"
@focusout="isLastItemOfArray(child, item.items) && setOpen(false)"
/>
</li>
</ul>
</div>
</template>
<script src="./DropdownLink" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/arrow'
@require '~@mr-hope/vuepress-shared/styles/reset'
.dropdown-wrapper
height 1.8rem
cursor pointer
&:not(:hover)
.arrow
transform rotate(-180deg)
&:hover, &.open
.nav-dropdown
z-index 2
transform scale(1)
visibility visible
opacity 1
.dropdown-title
button()
cursor inherit
padding inherit
color var(--dark-grey)
font-family inherit
font-size 0.9rem
font-weight 500
line-height 1.4rem
&::after
border-left 5px solid var(--accent-color)
&:hover
border-color transparent
.arrow
arrow()
font-size 1.2em
.nav-dropdown
box-sizing border-box
position absolute
top 100%
right 0
max-height 100vh - $navbarHeight
margin 0
padding 0.6rem 0
border 1px solid var(--grey14)
border-radius 0.25rem
background var(--bgcolor)
box-shadow 2px 2px 10px var(--card-shadow-color)
text-align left
white-space nowrap
overflow-y auto
transform scale(0.8)
opacity 0
visibility hidden
transition all 0.18s ease-out
.dropdown-item
color inherit
line-height 1.7rem
h4
margin 0
padding 0.75rem 1rem 0.25rem 0.75rem
border-top 1px solid var(--grey14)
color var(--dark-grey)
font-size 0.9rem
.nav-link
padding 0
&:before
display none
&:first-child h4
padding-top 0
border-top 0
.nav-link
display block
position relative
margin-bottom 0
padding 0 1.5rem 0 1.25rem
border-bottom none
color var(--dark-grey)
font-weight 400
line-height 1.7rem
&:hover
color var(--accent-color)
&.active
color var(--accent-color)
&::before
content ''
position absolute
top calc(50% - 3px)
left 9px
width 0
height 0
border-top 3px solid transparent
border-left 5px solid var(--accent-color)
border-bottom 3px solid transparent
.dropdown-subitem-wrapper
padding 0
list-style none
.dropdown-subitem
font-size 0.9em
</style>
import Vue from "vue";
import DropdownLink from "@theme/components/Navbar/DropdownLink.vue";
import I18nIcon from "@theme/icons/I18nIcon.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { getNavLinkItem } from "@theme/utils/navbar";
export default Vue.extend({
name: "LanguageDropdown",
components: { NavLink, DropdownLink },
computed: {
dropdown() {
const { locales } = this.$site;
if (locales && Object.keys(locales).length > 1) {
const currentLink = this.$page.path;
const { routes } = this.$router.options;
const themeLocales = this.$themeConfig.locales || {};
const languageDropdown = {
text: this.$themeLocaleConfig.selectText || "Languages",
ariaLabel: this.$themeLocaleConfig.ariaLabel || "Select language",
items: Object.keys(locales).map((path) => {
const locale = locales[path];
const text = (themeLocales[path] && themeLocales[path].label) ||
locale.lang ||
"Unknown Language";
let link;
// Stay on the current page
if (locale.lang === this.$lang)
link = currentLink;
else {
// Try to stay on the same page
link = currentLink.replace(this.$localeConfig.path, path);
// Fallback to homepage
if (!(routes || []).some((route) => route.path === link))
link = path;
}
return { text, link };
}),
};
return getNavLinkItem(languageDropdown);
}
return false;
},
},
render(h) {
return this.dropdown
? h("div", { class: "nav-links" }, [
h("div", { class: "nav-item" }, [
h(DropdownLink, { props: { item: this.dropdown } }, [
h(I18nIcon, {
slot: "title",
style: {
width: "1rem",
height: "1rem",
verticalAlign: "middle",
marginLeft: "1rem",
},
}),
]),
]),
])
: null;
},
});
//# sourceMappingURL=LanguageDropdown.js.map
\ No newline at end of file
import Vue from "vue";
import { ensureExt, isExternal, isMailto, isTel } from "@theme/utils/path";
export default Vue.extend({
name: "NavLink",
props: {
item: { type: Object, required: true },
},
computed: {
link() {
return ensureExt(this.item.link);
},
iconPrefix() {
const { iconPrefix } = this.$themeConfig;
return iconPrefix === "" ? "" : iconPrefix || "icon-";
},
active() {
// link is home path
if ((this.$site.locales &&
Object.keys(this.$site.locales).some((rootLink) => rootLink === this.link)) ||
this.link === "/")
// exact match
return this.$route.path === this.link;
// inclusive match
return this.$route.path.startsWith(this.link);
},
isNonHttpURI() {
return isMailto(this.link) || isTel(this.link);
},
isBlankTarget() {
return this.target === "_blank";
},
isInternal() {
return !isExternal(this.link) && !this.isBlankTarget;
},
target() {
if (this.isNonHttpURI)
return null;
if (this.item.target)
return this.item.target;
return isExternal(this.link) ? "_blank" : "";
},
rel() {
if (this.isNonHttpURI)
return null;
if (this.item.rel === false)
return null;
if (this.item.rel)
return this.item.rel;
return this.isBlankTarget ? "noopener noreferrer" : null;
},
},
methods: {
focusoutAction() {
// eslint-disable-next-line vue/require-explicit-emits
this.$emit("focusout");
},
},
});
//# sourceMappingURL=NavLink.js.map
\ No newline at end of file
<template>
<!-- eslint-disable vue/no-deprecated-v-on-native-modifier -->
<RouterLink
v-if="isInternal"
class="nav-link"
:class="{ active }"
:to="link"
@focusout.native="focusoutAction"
>
<i v-if="item.icon" :class="`iconfont ${item.iconPrefix || iconPrefix}${item.icon} ${item.iconClass}`" />
{{ item.text }}
</RouterLink>
<a
v-else
class="nav-link external"
:href="link"
:target="target"
:rel="rel"
@focusout="focusoutAction"
>
<i v-if="item.icon" :class="`iconfont ${item.iconPrefix || iconPrefix}${item.icon} ${item.iconClass}`" />
{{ item.text }}
<OutboundLink v-if="isBlankTarget" />
</a>
</template>
<script src="./NavLink" />
<style lang="stylus">
.nav-link
line-height 1.4rem
.navbar &
color var(--dark-grey)
&.active
color var(--accent-color)
.sidebar &
color var(--text-color)
&:hover, &.active
color var(--accent-color)
</style>
import Vue from "vue";
import DropdownLink from "@theme/components/Navbar/DropdownLink.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { getNavLinkItem } from "@theme/utils/navbar";
export default Vue.extend({
name: "NavLinks",
components: {
DropdownLink,
NavLink,
},
computed: {
navLinks() {
const navbar = this.$themeLocaleConfig.nav || this.$themeConfig.nav || [];
return navbar.map((link) => getNavLinkItem(link));
},
},
});
//# sourceMappingURL=NavLinks.js.map
\ No newline at end of file
<template>
<nav class="nav-links">
<!-- user links -->
<div v-for="item in navLinks" :key="item.link" class="nav-item">
<DropdownLink v-if="item.type === 'links'" :item="item" />
<NavLink v-else :item="item" />
</div>
</nav>
</template>
<script src="./NavLinks" />
<style lang="stylus">
.nav-links
display inline-block
.nav-item
position relative
display inline-block
line-height 2rem
margin-left 1.5rem
&:first-child
margin-left 0
> .nav-link
color var(--dark-grey)
&::after
position absolute
content ' '
left 50%
right 50%
bottom 0px
height 2px
background var(--accent-color-l10)
border-radius 1px
visibility hidden
transition left 0.2s ease-in-out, right 0.2s ease-in-out
&.active
color var(--accent-color)
&:hover, &.active
&::after
left 0
right 0
visibility visible
</style>
import Vue from "vue";
import AlgoliaSearchBox from "@AlgoliaSearchBox";
import LanguageDropdown from "@theme/components/Navbar/LanguageDropdown";
import NavLinks from "@theme/components/Navbar/NavLinks.vue";
import RepoLink from "@theme/components/Navbar/RepoLink.vue";
import SearchBox from "@SearchBox";
import SidebarButton from "@theme/components/Navbar/SidebarButton.vue";
import ThemeColor from "@ThemeColor";
let handler;
const css = (el, property) => {
// NOTE: Known bug, will return 'auto' if style value is 'auto'
const window = el.ownerDocument.defaultView;
// `null` means not to return pseudo styles
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return window.getComputedStyle(el, null)[property];
};
export default Vue.extend({
name: "Navbar",
components: {
AlgoliaSearchBox,
LanguageDropdown,
NavLinks,
RepoLink,
SearchBox,
SidebarButton,
ThemeColor,
},
data: () => ({
linksWrapMaxWidth: 0,
isMobile: false,
}),
computed: {
siteBrandTitle() {
return this.$site.title;
},
canHideSiteBrandTitle() {
return (Boolean(this.siteBrandTitle) &&
this.$themeConfig.hideSiteTitleonMobile !== false);
},
siteBrandLogo() {
const { logo } = this.$themeConfig;
return logo ? this.$withBase(logo) : "";
},
siteBrandDarkLogo() {
const { darkLogo } = this.$themeConfig;
return darkLogo ? this.$withBase(darkLogo) : "";
},
algoliaConfig() {
return (this.$themeLocaleConfig.algolia || this.$themeConfig.algolia || false);
},
isAlgoliaSearch() {
return Boolean(this.algoliaConfig &&
this.algoliaConfig.apiKey &&
this.algoliaConfig.indexName);
},
canHide() {
const autoHide = this.$themeConfig.navAutoHide;
return autoHide !== "none" && (autoHide === "always" || this.isMobile);
},
},
mounted() {
// Refer to config.styl
const MOBILE_DESKTOP_BREAKPOINT = 719;
const NAVBAR_HORIZONTAL_PADDING = parseInt(css(this.$el, "paddingLeft")) +
parseInt(css(this.$el, "paddingRight"));
handler = () => {
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
this.isMobile = true;
this.linksWrapMaxWidth = 0;
}
else {
this.isMobile = false;
this.linksWrapMaxWidth =
this.$el.offsetWidth -
NAVBAR_HORIZONTAL_PADDING -
((this.$refs.siteInfo &&
this.$refs.siteInfo.$el &&
this.$refs.siteInfo.$el.offsetWidth) ||
0);
}
};
handler();
window.addEventListener("resize", handler);
window.addEventListener("orientationchange", handler);
},
// eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle
beforeDestroy() {
window.removeEventListener("resize", handler);
window.removeEventListener("orientationchange", handler);
},
});
//# sourceMappingURL=Navbar.js.map
\ No newline at end of file
<template>
<header class="navbar" :class="{ 'can-hide': canHide }">
<slot name="start" />
<SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />
<RouterLink ref="siteInfo" :to="$localePath" class="home-link">
<img
v-if="siteBrandLogo"
class="logo"
:class="{ light: Boolean(siteBrandDarkLogo) }"
:src="siteBrandLogo"
:alt="siteBrandTitle"
/>
<img
v-if="siteBrandDarkLogo"
class="logo dark"
:src="siteBrandDarkLogo"
:alt="siteBrandTitle"
/>
<span
v-if="siteBrandTitle"
class="site-name"
:class="{ 'can-hide': canHideSiteBrandTitle }"
>{{ siteBrandTitle }}</span
>
</RouterLink>
<slot name="center" />
<div
:style="
linksWrapMaxWidth ? { 'max-width': `${linksWrapMaxWidth}px` } : {}
"
class="links"
>
<ThemeColor />
<AlgoliaSearchBox v-if="isAlgoliaSearch" :options="algoliaConfig" />
<SearchBox
v-else-if="
$themeConfig.search !== false && $page.frontmatter.search !== false
"
/>
<NavLinks class="can-hide" />
<LanguageDropdown />
<RepoLink class="can-hide" />
<slot name="end" />
</div>
</header>
</template>
<script src="./Navbar" />
<style lang="stylus">
.navbar
position fixed
z-index 200
top 0
left 0
right 0
height $navbarHeight
padding $navbarVerticalPadding $navbarHorizontalPadding
background var(--bgcolor-blur)
box-sizing border-box
box-shadow 0 2px 8px var(--card-shadow-color)
backdrop-filter saturate(200%) blur(20px)
line-height: $navbarHeight - $navbarVerticalPadding * 2
transition transform 0.3s ease-in-out
@media (max-width $MQMedium)
height $navbarMobileHeight
padding $navbarMobileVerticalPadding $navbarMobileHorizontalPadding
padding-left: $navbarMobileHorizontalPadding + 2.4rem
line-height: $navbarMobileHeight - $navbarMobileVerticalPadding * 2
.hide-navbar &.can-hide
transform translateY(-100%)
a, span, img
display inline-block
.logo
min-width: 200px
height: $navbarHeight - $navbarVerticalPadding * 2
margin-right 0.8rem
vertical-align top
@media (max-width $MQMedium)
min-width: $navbarMobileHeight - $navbarMobileVerticalPadding * 2
height: $navbarMobileHeight - $navbarMobileVerticalPadding * 2
.theme-light &
&.light
display inline-block
&.dark
display none
.theme-dark &
&.light
display none
&.dark
display inline-block
.can-hide
@media (max-width $MQMedium)
display none
.site-name
font-size 1.5rem
color var(--text-color)
position relative
@media (max-width $MQMedium)
width calc(100vw - 9.4rem)
overflow hidden
white-space nowrap
text-overflow ellipsis
.links
position absolute
top $navbarVerticalPadding
right $navbarHorizontalPadding
display flex
box-sizing border-box
padding-left 1.5rem
font-size 0.9rem
white-space nowrap
@media (max-width $MQMedium)
padding-left 0
top $navbarMobileVerticalPadding
right $navbarMobileHorizontalPadding
</style>
import Vue from "vue";
export default Vue.extend({
name: "RepoLink",
computed: {
repoLink() {
const { repo } = this.$themeConfig;
if (repo)
return /^https?:/u.test(repo) ? repo : `https://github.com/${repo}`;
return "";
},
repoLabel() {
if (!this.repoLink)
return "";
if (this.$themeConfig.repoLabel)
return this.$themeConfig.repoLabel;
const [repoHost] = /^https?:\/\/[^/]+/u.exec(this.repoLink) || [""];
const platforms = ["GitHub", "GitLab", "Bitbucket"];
for (let index = 0; index < platforms.length; index++) {
const platform = platforms[index];
if (new RegExp(platform, "iu").test(repoHost))
return platform;
}
return "Source";
},
},
});
//# sourceMappingURL=RepoLink.js.map
\ No newline at end of file
<template>
<!-- repo link -->
<a
v-if="repoLink && $themeConfig.repoDisplay !== false"
class="repo-link"
rel="noopener noreferrer"
:href="repoLink"
target="_blank"
>
{{ repoLabel }}
<OutboundLink />
</a>
</template>
<script src="./RepoLink" />
<style lang="stylus">
.repo-link
.navbar &
color var(--dark-grey)
margin-left 1rem
.sidebar-nav-links &
display block
padding 0.5rem 0 0.5rem 1.5rem
font-size 1.1em
line-height 1.25rem
</style>
<template>
<button
class="sidebar-button"
title="Sidebar Button"
@click="$emit('toggle-sidebar')"
>
<span class="icon" />
</button>
</template>
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/reset'
.sidebar-button
button()
display none
box-sizing content-box
position absolute
top calc(50% - 1.075rem)
left 1rem
width 1.25rem
height 1.25rem
padding 0.45rem
font unset
vertical-align middle
transition transform 0.2s ease-in-out
&::before
content ' '
margin-top 0.125em
&::after
content ' '
margin-bottom 0.125em
.icon
margin 0.2em 0
&::before, &::after, .icon
display block
width 100%
height 0.2em
transition transform 0.2s ease-in-out
border-radius 0.05em
background var(--text-color)
.sidebar-open .sidebar-button
&::before
transform translateY(0.4em) rotate(135deg)
.icon
transform scale(0)
&::after
transform translateY(-0.4em) rotate(-135deg)
@media (max-width $MQMedium)
.sidebar-button
display block
</style>
import Vue from "vue";
import Anchor from "@theme/components/Anchor.vue";
import Comment from "@Comment";
import MyTransition from "@theme/components/MyTransition.vue";
import PageInfo from "@mr-hope/vuepress-plugin-comment/lib/client/PageInfo.vue";
import PageMeta from "@theme/components/PageMeta.vue";
import PageNav from "@theme/components/PageNav.vue";
export default Vue.extend({
name: "Page",
components: {
Anchor,
Comment,
MyTransition,
PageInfo,
PageMeta,
PageNav,
},
props: {
sidebarItems: {
type: Array,
default: () => [],
},
headers: {
type: Array,
default: () => [],
},
},
});
//# sourceMappingURL=Page.js.map
<template>
<main class="page">
<BreadCrumb :key="$route.path" />
<slot name="top" />
<PageInfo :key="$route.path" />
<template>
<MyTransition :delay="0.12" :disable="true">
<Anchor :key="$route.path" />
</MyTransition>
<slot name="content-top" />
<MyTransition :delay="0.08" :disable="true">
<Content :key="$route.path" class="theme-default-content" />
</MyTransition>
<slot name="content-bottom" />
<MyTransition :delay="0.12" :disable="true">
<PageMeta :key="$route.path" />
</MyTransition>
<MyTransition :delay="0.14" :disable="true">
<PageNav :key="$route.path" v-bind="{ sidebarItems }" />
</MyTransition>
<MyTransition :delay="0.16" :disable="true">
<Comment :key="$route.path" />
</MyTransition>
</template>
<slot name="bottom" />
<!-- Google tag (gtag.js) -->
<!-- put here because the plugin didn't work -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-22HX148PZF">
</script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-9KLVB8X0ME');
</script>
</main>
</template>
<script src="./Page" />
<style lang="stylus">
.page
display block
box-sizing border-box
min-height 100vh
padding-left $sidebarWidth
padding-bottom 2rem
background var(--bgcolor)
@media (max-width $MQMobile)
min-height 100vh
// narrow desktop / iPad
@media (max-width $MQNarrow)
padding-left $mobileSidebarWidth
// wide mobile
@media (max-width $MQMobile)
padding-left 0
@media (min-width $MQMobile)
.theme-container:not(.has-sidebar) &
padding-left 0
@media (min-width $MQWide)
.has-anchor &:not(.blog)
padding-right 16rem
</style>
import Vue from "vue";
import EditIcon from "@theme/icons/EditIcon.vue";
import { endingSlashRE, outboundRE } from "@theme/utils/path";
export default Vue.extend({
name: "PageMeta",
components: { EditIcon },
computed: {
i18n() {
return (this.$themeLocaleConfig.meta || {
contributor: "Contributors",
editLink: "Edit this page",
updateTime: "Last Updated",
});
},
contributors() {
return this.$page.frontmatter.contributor === false ||
(this.$themeConfig.contributor === false &&
!this.$page.frontmatter.contributor)
? []
: this.$page.contributors || [];
},
contributorsText() {
return this.i18n.contributor;
},
updateTime() {
return this.$page.frontmatter.contributor === false ||
(this.$themeConfig.updateTime === false &&
!this.$page.frontmatter.updateTime)
? ""
: this.$page.updateTime || "";
},
updateTimeText() {
return this.i18n.updateTime;
},
editLink() {
const showEditLink = this.$page.frontmatter.editLink ||
(this.$themeConfig.editLinks !== false &&
this.$page.frontmatter.editLink !== false);
const { repo, docsRepo } = this.$site.themeConfig;
if (showEditLink && (repo || docsRepo) && this.$page.relativePath)
return this.createEditLink();
return false;
},
editLinkText() {
return this.i18n.editLink;
},
},
methods: {
createEditLink() {
const { repo = "", docsRepo = repo, docsDir = "", docsBranch = "main", } = this.$themeConfig;
const bitbucket = /bitbucket.org/u;
if (bitbucket.test(docsRepo))
return `${docsRepo.replace(endingSlashRE, "")}/src/${docsBranch}/${docsDir ? `${docsDir.replace(endingSlashRE, "")}/` : ""}${this.$page.relativePath}?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`;
const gitlab = /gitlab.com/u;
if (gitlab.test(docsRepo))
return `${docsRepo.replace(endingSlashRE, "")}/-/edit/${docsBranch}/${docsDir ? `${docsDir.replace(endingSlashRE, "")}/` : ""}${this.$page.relativePath}`;
const base = outboundRE.test(docsRepo)
? docsRepo
: `https://github.com/${docsRepo}`;
return `${base.replace(endingSlashRE, "")}/edit/${docsBranch}/${docsDir ? `${docsDir.replace(endingSlashRE, "")}/` : ""}${this.$page.relativePath}`;
},
},
});
//# sourceMappingURL=PageMeta.js.map
\ No newline at end of file
<template>
<footer class="page-meta">
<div class="footer-box">
<div class="footer-box-area">
<span class="footer-section-header">About this page</span>
<ul>
<li>Updated: {{ updateTime.slice(0, updateTime.length - 6) }}</li>
</ul>
</div>
<div class="footer-box-area">
<span class="footer-section-header">Contribute</span>
<ul>
<li>
<a :href="editLink" target="_blank" rel="noopener noreferrer">
<i class="far fa-pencil"></i> Edit this page
</a>
</li>
<li>
<a href="https://github.com/ethereum-optimism/optimism/contribute" target="_blank" rel="noopener noreferrer">
<i class="far fa-hands-helping"></i> Contribute to Optimism
</a>
</li>
</ul>
</div>
<div class="footer-box-area">
<span class="footer-section-header">Still need help?</span>
<ul>
<li>
<a href="https://discord.optimism.io" target="_blank" rel="noopener noreferrer">
<i class="fab fa-discord"></i> Discord community
</a>
</li>
<li>
<a href="https://wkf.ms/3XTdpLl" target="_blank" rel="noopener noreferrer">
<i class="far fa-comment-dots"></i> Get support for going live
</a>
</li>
</ul>
</div>
</div>
</footer>
</template>
<script src="./PageMeta" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/wrapper'
.page-meta
@extend $wrapper
padding-top 12px
padding-bottom 12px
font-family Arial, Helvetica, sans-serif
overflow auto
.meta-item
.label
font-weight 500
color var(--text-color-l25)
.info
font-weight 400
color var(--dark-grey)
.edit-link
display inline-block
font-size 14px
.icon
position relative
bottom -0.125em
width 1em
height 1em
color var(--accent-color)
@media (max-width $MQMobile)
margin-bottom 8px
a
color var(--accent-color-l10)
.update-time
float right
font-size 14px
@media (max-width $MQMobile)
float none
font-size 13px
text-align left
.contributors
font-size 14px
text-align right
@media (max-width $MQMobile)
font-size 13px
text-align left
.footer-box
display flex
flex-direction row
justify-content space-between
background-color #F1F4F9
padding 32px
padding-left: 40px;
padding-right: 40px;
border-radius 16px
@media (max-width $MQNarrow)
flex-direction column
.footer-box-area
@media (max-width $MQNarrow)
margin-bottom 32px
span.footer-section-header
font-family 'Open Sans', sans-serif
font-weight 600
font-size 14px
line-height 20px
ul
list-style-type none
padding-left 0
font-size 14px
line-height 20px
margin-top 10px
margin-bottom 0px
color #68778D
li
margin-top 15px
margin-bottom 5px
a
color #68778D
font-family 'Open Sans', sans-serif
&:hover
color #FF0420
i
font-size 14px
width 20px
margin-right 3px
text-align center
</style>
import Vue from "vue";
import NextIcon from "@theme/icons/NextIcon.vue";
import PrevIcon from "@theme/icons/PrevIcon.vue";
import { resolvePath } from "@theme/utils/path";
import { resolvePageforSidebar } from "@theme/utils/sidebar";
const getSidebarItems = (items, result) => {
for (const item of items)
if (item.type === "group")
getSidebarItems((item.children || []), result);
else
result.push(item);
};
const find = (page, items, offset) => {
const result = [];
getSidebarItems(items, result);
for (let i = 0; i < result.length; i++) {
const cur = result[i];
if (cur.type === "page" && cur.path === decodeURIComponent(page.path))
return result[i + offset];
}
return false;
};
const resolvePageLink = (linkType, { themeConfig, page, route, site, sidebarItems }) => {
const themeLinkConfig =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
themeConfig[`${linkType}Links`];
const pageLinkConfig = page.frontmatter[linkType];
if (themeLinkConfig === false || pageLinkConfig === false)
return false;
if (typeof pageLinkConfig === "string")
return resolvePageforSidebar(site.pages, resolvePath(pageLinkConfig, route.path));
return find(page, sidebarItems, linkType === "prev" ? -1 : 1);
};
export default Vue.extend({
name: "PageNav",
components: { NextIcon, PrevIcon },
props: {
sidebarItems: {
type: Array,
default: () => [],
},
},
computed: {
prev() {
return resolvePageLink("prev", {
sidebarItems: this.sidebarItems,
themeConfig: this.$themeConfig,
page: this.$page,
route: this.$route,
site: this.$site,
});
},
next() {
return resolvePageLink("next", {
sidebarItems: this.sidebarItems,
themeConfig: this.$themeConfig,
page: this.$page,
route: this.$route,
site: this.$site,
});
},
},
});
//# sourceMappingURL=PageNav.js.map
\ No newline at end of file
<template>
<div v-if="prev || next" class="page-nav">
<p class="inner">
<span v-if="prev" class="prev">
<a
v-if="prev.type === 'external'"
class="prev"
:href="prev.path"
target="_blank"
rel="noopener noreferrer"
>
<PrevIcon />
{{ prev.title || prev.path }}
<OutboundLink />
</a>
<RouterLink v-else class="prev" :to="prev.path">
<PrevIcon />
{{ prev.title || prev.path }}
</RouterLink>
</span>
<span v-if="next" class="next">
<a
v-if="next.type === 'external'"
:href="next.path"
target="_blank"
rel="noopener noreferrer"
>
{{ next.title || next.path }}
<OutboundLink />
<NextIcon />
</a>
<RouterLink v-else :to="next.path">
{{ next.title || next.path }}
<NextIcon />
</RouterLink>
</span>
</p>
</div>
</template>
<script src="./PageNav" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/wrapper'
.page-nav
@extend $wrapper
padding-top 12px
padding-bottom 0
font-family Arial, Helvetica, sans-serif
.inner
min-height 32px
margin-top 0
border-top 1px solid var(--border-color)
padding-top 16px
overflow auto // clear float
.prev .icon, .next .icon
position relative
top 0.125em
width 1em
height 1em
color var(--accent-color)
.next
float right
</style>
import Vue from "vue";
export default Vue.extend({
name: "DropdownTransition",
methods: {
setHeight(items) {
// explicitly set height so that it can be transitioned
items.style.height = `${items.scrollHeight}px`;
},
unsetHeight(items) {
items.style.height = "";
},
},
});
//# sourceMappingURL=DropdownTransition.js.map
\ No newline at end of file
<template>
<transition
name="dropdown"
@enter="setHeight"
@after-enter="unsetHeight"
@before-leave="setHeight"
>
<slot />
</transition>
</template>
<script src="./DropdownTransition" />
<style lang="stylus">
.dropdown-enter, .dropdown-leave-to
height 0 !important
</style>
import Vue from "vue";
import SidebarNavLinks from "@theme/components/Sidebar/SidebarNavLinks.vue";
import SidebarLinks from "@theme/components/Sidebar/SidebarLinks.vue";
export default Vue.extend({
name: "Sidebar",
components: {
SidebarLinks,
SidebarNavLinks,
},
props: {
items: { type: Array, required: true },
},
computed: {
blogConfig() {
return this.$themeConfig.blog || {};
},
sidebarDisplay() {
return this.blogConfig.sidebarDisplay || "none";
},
},
});
//# sourceMappingURL=Sidebar.js.map
<template>
<aside class="sidebar">
<slot name="top" />
<SidebarNavLinks />
<slot name="center" />
<SidebarLinks :depth="0" :items="items" />
<slot name="bottom" />
</aside>
</template>
<script src="./Sidebar" />
<style lang="stylus">
.sidebar
position fixed
z-index 150
top $navbarHeight
left 0
bottom 0
box-sizing border-box
width $sidebarWidth
margin 0
background var(--bgcolor-blur)
box-shadow 2px 0 4px var(--card-shadow-color)
backdrop-filter saturate(200%) blur(20px)
font-size 16px
overflow-y auto
@media (max-width $MQMedium)
top $navbarMobileHeight
.theme-container.hide-navbar &
top 0
.theme-container:not(.has-navbar) &
top 0
a
display inline-block
color var(--text-color)
.blogger-info.mobile
display none
.blogger-info.mobile + hr
display none
& > .sidebar-links
padding 1.5rem 0
& > li
& > a.sidebar-link
font-size 1.1em
line-height 1.7
&:not(:first-child)
margin-top 0.75rem
// narrow desktop / iPad
@media (max-width $MQNarrow)
width $mobileSidebarWidth
font-size 15px
@media (min-width $MQMedium)
.theme-container:not(.has-sidebar) &
display none
// wide mobile
@media (max-width $MQMedium)
transform translateX(-100%)
transition transform 0.2s ease
box-shadow none
.theme-container.sidebar-open &
transform translateX(0)
box-shadow 2px 0 8px var(--card-shadow-color)
.theme-container:not(.has-navbar) &
top 0
.blogger-info.mobile
display block
.blogger-info.mobile + hr
display block
margin-top 16px
& > .sidebar-links
padding 1rem 0
</style>
import Vue from "vue";
import DropdownTransition from "@theme/components/Sidebar/DropdownTransition.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
export default Vue.extend({
name: "SidebarDropdownLink",
components: { NavLink, DropdownTransition },
props: {
item: { type: Object, required: true },
},
data: () => ({
open: false,
}),
computed: {
dropdownAriaLabel() {
return this.item.ariaLabel || this.item.text;
},
iconPrefix() {
const { iconPrefix } = this.$themeConfig;
return iconPrefix === "" ? "" : iconPrefix || "icon-";
},
},
watch: {
$route() {
this.open = false;
},
},
methods: {
setOpen(value) {
this.open = value;
},
isLastItemOfArray(item, array) {
if (Array.isArray(array))
return item === array[array.length - 1];
return false;
},
},
});
//# sourceMappingURL=SidebarDropdownLink.js.map
\ No newline at end of file
<template>
<div class="mobile-dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="dropdownAriaLabel"
@click="setOpen(!open)"
>
<span class="title">
<i v-if="item.icon" :class="`iconfont ${iconPrefix}${item.icon}`" />
{{ item.text }}
</span>
<span class="arrow" :class="open ? 'down' : 'right'" />
</button>
<DropdownTransition>
<ul v-show="open" class="nav-dropdown">
<li
v-for="(child, index) in item.items"
:key="child.link || index"
class="dropdown-item"
>
<h4 v-if="child.type === 'links'" class="dropdown-subtitle">
<NavLink
v-if="child.link"
:item="child"
@focusout="
isLastItemOfArray(child, item.children) &&
child.children.length === 0 &&
setOpen(false)
"
/>
<span v-else>{{ child.text }}</span>
</h4>
<ul v-if="child.type === 'links'" class="dropdown-subitem-wrapper">
<li
v-for="grandchild in child.items"
:key="grandchild.link"
class="dropdown-subitem"
>
<NavLink
:item="grandchild"
@focusout="
isLastItemOfArray(grandchild, child.items) &&
isLastItemOfArray(child, item.items) &&
setOpen(false)
"
/>
</li>
</ul>
<NavLink
v-else
:item="child"
@focusout="isLastItemOfArray(child, item.items) && setOpen(false)"
/>
</li>
</ul>
</DropdownTransition>
</div>
</template>
<script src="./SidebarDropdownLink" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/arrow'
@require '~@mr-hope/vuepress-shared/styles/reset'
.mobile-dropdown-wrapper
cursor pointer
.dropdown-title
button()
cursor inherit
padding inherit
color var(--text-color)
font-family inherit
font-size inherit
line-height 1.4rem
&:hover
color var(--accent-color)
.arrow
arrow()
.nav-dropdown
margin-top 0.25rem
transition height 0.1s ease-out
overflow hidden
.dropdown-item
color inherit
line-height 1.7rem
h4
margin 0
padding-left 1.25rem
font-size 15px
line-height 1.7
.nav-link
padding 0
&:before
display none
.nav-link
display block
position relative
margin-bottom 0
padding 0 1.5rem 0 1.25rem
border-bottom none
font-weight 400
line-height 1.7rem
&:hover
color var(--accent-color)
&.active
color var(--accent-color)
&::before
content ''
position absolute
top calc(50% - 3px)
left 9px
width 0
height 0
border-top 3px solid transparent
border-left 5px solid var(--accent-color)
border-bottom 3px solid transparent
& > .nav-link
font-size 15px
line-height 2rem
.dropdown-subitem-wrapper
padding 0
list-style none
.dropdown-subitem
font-size 0.9em
padding-left 0.5rem
</style>
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import Vue from "vue";
import DropdownTransition from "@theme/components/Sidebar/DropdownTransition.vue";
import { isActive } from "@theme/utils/path";
export default Vue.extend({
name: "SidebarGroup",
components: { DropdownTransition },
props: {
item: {
type: Object,
required: true,
},
open: { type: Boolean },
depth: { type: Number, required: true },
},
beforeCreate() {
// eslint-disable-next-line
this.$options.components.SidebarLinks =
// eslint-disable-next-line @typescript-eslint/no-var-requires
require("@theme/components/Sidebar/SidebarLinks.vue").default;
},
methods: {
getIcon(icon) {
const { iconPrefix } = this.$themeConfig;
return this.$themeConfig.sidebarIcon !== false && icon
? `${iconPrefix === "" ? "" : iconPrefix || "icon-"}${icon}`
: "";
},
isActive,
},
});
//# sourceMappingURL=SidebarGroup.js.map
\ No newline at end of file
<template>
<section
:class="[
{
collapsable: item.collapsable,
'is-sub-group': depth !== 0,
},
`depth-${depth}`,
]"
class="sidebar-group"
>
<RouterLink
v-if="item.path"
:class="{
open,
active: isActive($route, item.path),
}"
class="sidebar-heading clickable"
:to="item.path"
@click="$emit('toggle')"
>
<i v-if="item.icon" :class="`iconfont ${getIcon(item.icon)}`" />
<span class="title">{{ item.title }}</span>
<span
v-if="item.collapsable"
:class="open ? 'down' : 'right'"
class="arrow"
/>
</RouterLink>
<p
v-else
:class="{ clickable: item.collapsable, open }"
class="sidebar-heading"
@click="$emit('toggle')"
>
<i v-if="item.icon" :class="`iconfont ${getIcon(item.icon)}`" />
<span class="title">{{ item.title }}</span>
<span
v-if="item.collapsable"
:class="open ? 'down' : 'right'"
class="arrow"
/>
</p>
<DropdownTransition>
<SidebarLinks
v-if="open || !item.collapsable"
class="sidebar-group-items"
:depth="depth + 1"
:items="item.children"
/>
</DropdownTransition>
</section>
</template>
<script src="./SidebarGroup" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/arrow'
.sidebar-group
.sidebar-group
padding-left 0.5em
&:not(.collapsable)
.sidebar-heading:not(.clickable)
color inherit
cursor auto
// refine styles of nested sidebar groups
&.is-sub-group
padding-left 0
& > .sidebar-heading
padding-left 1.75rem
font-weight normal
line-height 1.5
&:not(.clickable)
opacity 0.8
& > .sidebar-group-items
padding-left 1rem
& > li > .sidebar-link
border-left none
font-size 0.95em
&.depth-2
& > .sidebar-heading
border-left none
.sidebar-heading
display flex
box-sizing border-box
width 100%
margin 0
padding 0.35rem 1rem 0.35rem 1.25rem
border-left 0.25rem solid transparent
border-top-right-radius 0.25rem
border-bottom-right-radius 0.25rem
color var(--text-color)
font-size 1.1em
cursor pointer
transition color 0.15s ease
user-select none
&.open
color inherit
&.clickable
&:hover
background-color var(--bgcolor-light)
&.active
border-left-color var(--accent-color)
color var(--accent-color)
font-weight 500
.iconfont
margin-right 0.25em
.title
flex 1
.arrow
arrow()
font-size 1.5em
float right
.sidebar-group-items
font-size 0.95em
transition height 0.1s ease-out
overflow hidden
</style>
import Vue from "vue";
import { hashRE, isActive } from "@theme/utils/path";
import { groupSidebarHeaders } from "@theme/utils/sidebar";
const renderIcon = (h, icon) => icon
? h("i", {
class: ["iconfont", icon],
})
: null;
const renderLink = (h, { icon = "", text, link, level, active }) => h("RouterLink", {
props: {
to: link,
activeClass: "",
exactActiveClass: "",
},
class: {
active,
"sidebar-link": true,
[level ? `heading${level}` : ""]: level && level !== 2,
},
}, [renderIcon(h, icon), text]);
const renderExternalLink = (h, { path, title = path }) => h("a", {
attrs: {
href: path,
target: "_blank",
rel: "noopener noreferrer",
},
class: { "sidebar-link": true },
}, [title, h("OutboundLink")]);
const renderChildren = (h, { children, path, route, maxDepth, depth = 1 }) => {
if (!children || depth > maxDepth)
return null;
return h("ul", { class: "sidebar-sub-headers" }, children.map((child) => {
const active = isActive(route, `${path}#${child.slug}`);
return h("li", { class: "sidebar-sub-header" }, [
renderLink(h, {
text: child.title,
link: `${path}#${child.slug}`,
level: child.level,
active,
}),
renderChildren(h, {
children: child.children || false,
path,
route,
maxDepth,
depth: depth + 1,
}),
]);
}));
};
export default Vue.extend({
name: "SidebarLink",
functional: true,
props: {
item: {
type: Object,
required: true,
},
},
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
render(h, { parent: { $page, $route, $themeConfig, $themeLocaleConfig }, props }) {
const { item } = props;
// if the item can not be resolved
if (item.type === "error")
return null;
// external link
if (item.type === "external")
return renderExternalLink(h, item);
/*
* Use custom active class matching logic
* Due to edge case of paths ending with / + hash
*/
const selfActive = isActive($route, item.path);
/** whether the item is active */
const active =
// if the item is a heading, then one of the children needs to be active
item.type === "header"
? selfActive ||
(item.children || []).some((child) => isActive($route, `${item.basePath}#${child.slug}`))
: selfActive;
const pageMaxDepth = $page.frontmatter.sidebarDepth;
const localesMaxDepth = $themeLocaleConfig.sidebarDepth;
const themeMaxDepth = $themeConfig.sidebarDepth;
const maxDepth = typeof pageMaxDepth === "number"
? pageMaxDepth
: typeof localesMaxDepth === "number"
? localesMaxDepth
: typeof themeMaxDepth === "number"
? themeMaxDepth
: 2;
// the item is a heading
if (item.type === "header")
return [
renderLink(h, {
text: item.title || item.path,
link: item.path,
level: item.level,
active,
}),
renderChildren(h, {
children: item.children || false,
path: item.basePath,
route: $route,
maxDepth,
}),
];
const displayAllHeaders = $themeLocaleConfig.displayAllHeaders ||
$themeConfig.displayAllHeaders;
const link = renderLink(h, {
icon: $themeConfig.sidebarIcon !== false && item.frontmatter.icon
? `${$themeConfig.iconPrefix === ""
? ""
: $themeConfig.iconPrefix || "icon-"}${item.frontmatter.icon}`
: "",
text: item.title || item.path,
link: item.path,
active,
});
if ((active || displayAllHeaders) &&
item.headers &&
!hashRE.test(item.path)) {
const children = groupSidebarHeaders(item.headers);
return [
link,
renderChildren(h, {
children,
path: item.path,
route: $route,
maxDepth,
}),
];
}
return link;
},
});
//# sourceMappingURL=SidebarLink.js.map
\ No newline at end of file
<script src="./SidebarLink" />
<style lang="stylus">
$headings = 3 4 5 6
.sidebar-links
ul
padding 0
margin 0
list-style-type none
&.sidebar-sub-headers
padding-left 1rem
font-size 0.95em
.has-anchor &
@media (min-width $MQWide)
display none
a.sidebar-link
display inline-block
box-sizing border-box
width 100%
padding 0.35rem 1rem 0.35rem 1.25rem
border-left 0.2rem solid transparent
color var(--text-color)
font-size 1em
line-height 1.5
font-weight 400
for $heading in $headings
&.heading{$heading}
padding-left ($heading - 0.5) * 0.5rem !important
.iconfont
margin-right 0.25em
&:hover
background-color var(--bgcolor-light)
&.active
border-left-color var(--accent-color)
background-color var(--accent-color-a15)
color var(--accent-color)
font-weight 500
.iconfont
color var(--accent-color)
.sidebar-group &
padding-left 1.75rem
.sidebar-sub-headers &
padding-top 0.25rem
padding-bottom 0.25rem
border-left none
&.active
background-color transparent
font-weight 500
</style>
import Vue from "vue";
import SidebarGroup from "@theme/components/Sidebar/SidebarGroup.vue";
import SidebarLink from "@theme/components/Sidebar/SidebarLink.vue";
import { isActive } from "@theme/utils/path";
const descendantIsActive = (route, item) => {
if (item.type === "group")
return item.children.some((child) => {
if (child.type === "group")
return descendantIsActive(route, child);
return child.type === "page" && isActive(route, child.path);
});
return false;
};
const resolveOpenGroupIndex = (route, items) => {
for (let i = 0; i < items.length; i++)
if (descendantIsActive(route, items[i]))
return i;
return -1;
};
export default Vue.extend({
name: "SidebarLinks",
components: { SidebarGroup, SidebarLink },
props: {
items: {
type: Array,
required: true,
},
depth: { type: Number, required: true },
},
data: () => ({
openGroupIndex: 0,
}),
watch: {
$route() {
this.refreshIndex();
},
},
created() {
this.refreshIndex();
},
methods: {
refreshIndex() {
const index = resolveOpenGroupIndex(this.$route, this.items);
if (index > -1)
this.openGroupIndex = index;
},
toggleGroup(index) {
this.openGroupIndex = index === this.openGroupIndex ? -1 : index;
},
isActive(page) {
return isActive(this.$route, page.regularPath);
},
},
});
//# sourceMappingURL=SidebarLinks.js.map
\ No newline at end of file
<template>
<ul v-if="items.length" class="sidebar-links">
<li v-for="(item, index) in items" :key="index">
<SidebarGroup
v-if="item.type === 'group'"
:item="item"
:open="index === openGroupIndex"
:depth="depth"
@toggle="toggleGroup(index)"
/>
<SidebarLink v-else :item="item" />
</li>
</ul>
</template>
<script src="./SidebarLinks" />
import Vue from "vue";
import RepoLink from "@theme/components/Navbar/RepoLink.vue";
import SidebarDropdownLink from "@theme/components/Sidebar/SidebarDropdownLink.vue";
import NavLink from "@theme/components/Navbar/NavLink.vue";
import { getNavLinkItem } from "@theme/utils/navbar";
export default Vue.extend({
name: "SidebarNavLinks",
components: {
RepoLink,
SidebarDropdownLink,
NavLink,
},
computed: {
navLinks() {
const navbar = this.$themeLocaleConfig.nav || this.$themeConfig.nav || [];
return navbar.map((link) => getNavLinkItem(link));
},
},
});
//# sourceMappingURL=SidebarNavLinks.js.map
\ No newline at end of file
<template>
<nav class="sidebar-nav-links">
<!-- user links -->
<div v-for="item in navLinks" :key="item.link" class="nav-item">
<SidebarDropdownLink v-if="item.type === 'links'" :item="item" />
<NavLink v-else :item="item" />
</div>
<RepoLink />
</nav>
</template>
<script src="./SidebarNavLinks" />
<style lang="stylus">
.sidebar-nav-links
display none
padding 0.5rem 0 0.75rem 0
border-bottom 1px solid var(--border-color)
@media (max-width $MQMedium)
display block
ul
padding 0
margin 0.25rem 0 0
list-style-type none
.nav-item
position relative
display block
padding 0.5rem 0 0.5rem 1.5rem
font-size 1.1em
line-height 1.25rem
&:first-child
margin-left 0
</style>
import Vue from "vue";
import AutoIcon from "@theme/icons/AutoIcon.vue";
import DarkIcon from "@theme/icons/DarkIcon.vue";
import LightIcon from "@theme/icons/LightIcon.vue";
import { changeClass } from "@theme/utils/dom";
export default Vue.extend({
name: "DarkmodeSwitch",
components: { AutoIcon, DarkIcon, LightIcon },
data: () => ({
darkmode: "auto",
}),
computed: {
darkmodeConfig() {
return this.$themeConfig.darkmode || "auto-switch";
},
},
mounted() {
this.darkmode =
localStorage.getItem("darkmode") ||
"auto";
if (this.darkmodeConfig === "auto-switch")
if (this.darkmode === "auto")
this.setDarkmode("auto");
else
this.setDarkmode(this.darkmode);
else if (this.darkmodeConfig === "auto")
this.setDarkmode("auto");
else if (this.darkmodeConfig === "switch")
this.setDarkmode(this.darkmode);
// disabled
else
this.setDarkmode("off");
},
methods: {
setDarkmode(status) {
if (status === "on")
this.toggleDarkmode(true);
else if (status === "off")
this.toggleDarkmode(false);
else {
const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
const isLightMode = window.matchMedia("(prefers-color-scheme: light)").matches;
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (event) => {
if (event.matches)
this.toggleDarkmode(true);
});
window
.matchMedia("(prefers-color-scheme: light)")
.addEventListener("change", (event) => {
if (event.matches)
this.toggleDarkmode(false);
});
if (isDarkMode)
this.toggleDarkmode(true);
else if (isLightMode)
this.toggleDarkmode(false);
else {
const timeHour = new Date().getHours();
this.toggleDarkmode(timeHour < 6 || timeHour >= 18);
}
}
this.darkmode = status;
localStorage.setItem("darkmode", status);
},
toggleDarkmode(isDarkmode) {
const classes = document.body.classList;
if (isDarkmode)
changeClass(classes, ["theme-dark"], ["theme-light"]);
else
changeClass(classes, ["theme-light"], ["theme-dark"]);
},
},
});
//# sourceMappingURL=DarkmodeSwitch.js.map
\ No newline at end of file
import Vue from "vue";
import ClickOutside from "@theme/utils/click-outside";
import ThemeOptions from "@theme/components/Theme/ThemeOptions.vue";
export default Vue.extend({
name: "ThemeColor",
directives: { "click-outside": ClickOutside },
components: { ThemeOptions },
data: () => ({
showMenu: false,
}),
methods: {
clickOutside() {
this.showMenu = false;
},
},
});
//# sourceMappingURL=ThemeColor.js.map
\ No newline at end of file
<template>
<button
v-click-outside="clickOutside"
class="color-button"
:class="{ select: showMenu }"
tabindex="-1"
aria-hidden="true"
@click="showMenu = !showMenu"
>
<svg
class="skin-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M224 800c0 9.6 3.2 44.8 6.4 54.4 6.4 48-48 76.8-48 76.8s80 41.6 147.2 0 134.4-134.4
38.4-195.2c-22.4-12.8-41.6-19.2-57.6-19.2C259.2 716.8 227.2 761.6 224 800zM560 675.2l-32
51.2c-51.2 51.2-83.2 32-83.2 32 25.6 67.2 0 112-12.8 128 25.6 6.4 51.2 9.6 80 9.6 54.4 0
102.4-9.6 150.4-32l0 0c3.2 0 3.2-3.2 3.2-3.2 22.4-16 12.8-35.2
6.4-44.8-9.6-12.8-12.8-25.6-12.8-41.6 0-54.4 60.8-99.2 137.6-99.2 6.4 0 12.8 0 22.4
0 12.8 0 38.4 9.6 48-25.6 0-3.2 0-3.2 3.2-6.4 0-3.2 3.2-6.4 3.2-6.4 6.4-16 6.4-16 6.4-19.2
9.6-35.2 16-73.6 16-115.2 0-105.6-41.6-198.4-108.8-268.8C704 396.8 560 675.2 560 675.2zM224
419.2c0-28.8 22.4-51.2 51.2-51.2 28.8 0 51.2 22.4 51.2 51.2 0 28.8-22.4 51.2-51.2 51.2C246.4
470.4 224 448 224 419.2zM320 284.8c0-22.4 19.2-41.6 41.6-41.6 22.4 0 41.6 19.2 41.6 41.6 0
22.4-19.2 41.6-41.6 41.6C339.2 326.4 320 307.2 320 284.8zM457.6 208c0-12.8 12.8-25.6 25.6-25.6
12.8 0 25.6 12.8 25.6 25.6 0 12.8-12.8 25.6-25.6 25.6C470.4 233.6 457.6 220.8 457.6 208zM128
505.6C128 592 153.6 672 201.6 736c28.8-60.8 112-60.8 124.8-60.8-16-51.2 16-99.2
16-99.2l316.8-422.4c-48-19.2-99.2-32-150.4-32C297.6 118.4 128 291.2 128 505.6zM764.8
86.4c-22.4 19.2-390.4 518.4-390.4 518.4-22.4 28.8-12.8 76.8 22.4 99.2l9.6 6.4c35.2 22.4
80 12.8 99.2-25.6 0 0 6.4-12.8 9.6-19.2 54.4-105.6 275.2-524.8 288-553.6
6.4-19.2-3.2-32-19.2-32C777.6 76.8 771.2 80 764.8 86.4z"
/>
</svg>
<transition mode="out-in" name="menu-transition">
<div v-show="showMenu" class="color-picker-menu">
<ThemeOptions />
</div>
</transition>
</button>
</template>
<script src="./ThemeColor" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/reset'
.color-button
button()
position relative
width 2.25rem
height 2.25rem
margin 0 0.25rem
padding 0.5rem
outline none
color #aaa
flex-shrink 0
&:hover, &.select
color var(--accent-color)
&.select:hover
color #aaa
.skin-icon
width 100%
height 100%
fill currentcolor
.color-picker-menu
position absolute
top: $navbarHeight - $navbarVerticalPadding
left 50%
min-width 100px
margin 0
padding 0.5em 0.75em
background var(--bgcolor)
box-shadow 2px 2px 10px var(--card-shadow-color)
color var(--dark-grey)
border-radius 0.25em
transform translateX(-50%)
z-index 250
@media (max-width $MQMobile)
top: $navbarMobileHeight - $navbarMobileVerticalPadding
transform translateX(-80%)
&::before
content ''
position absolute
top -7px
left 50%
border-style solid
border-color transparent transparent var(--bgcolor)
border-width 0 7px 7px
transform translateX(-50%)
@media (max-width $MQMobile)
left 80%
&.menu-transition-enter-active, &.menu-transition-leave-active
transition all 0.25s ease-in-out
&.menu-transition-enter, &.menu-transition-leave-to
top 30px
opacity 0
ul
list-style-type none
margin 0
padding 0
@media (max-width $MQMobile)
.color-picker
.color-picker-menu
left calc(50% - 35px)
&::before
left calc(50% + 35px)
</style>
import Vue from "vue";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
import DarkmodeSwitch from "@theme/components/Theme/DarkmodeSwitch.vue";
const defaultColorPicker = {
red: "#e74c3c",
blue: "#3498db",
green: "#3eaf7c",
orange: "#f39c12",
purple: "#8e44ad",
};
export default Vue.extend({
name: "ThemeOptions",
components: { DarkmodeSwitch },
data: () => ({
themeColor: {},
isDarkmode: false,
}),
computed: {
text() {
return (this.$themeLocaleConfig.themeColor || getDefaultLocale().themeColor);
},
themeColorEnabled() {
return this.$themeConfig.themeColor !== false;
},
switchEnabled() {
return (this.$themeConfig.darkmode !== "disable" &&
this.$themeConfig.darkmode !== "auto");
},
},
mounted() {
const theme = localStorage.getItem("theme");
this.themeColor = {
list: this.$themeConfig.themeColor
? Object.keys(this.$themeConfig.themeColor)
: Object.keys(defaultColorPicker),
picker: this.$themeConfig.themeColor || defaultColorPicker,
};
if (theme)
this.setTheme(theme);
},
methods: {
setTheme(theme) {
const classes = document.body.classList;
const themes = this.themeColor.list.map((colorTheme) => `theme-${colorTheme}`);
if (!theme) {
localStorage.removeItem("theme");
classes.remove(...themes);
return;
}
classes.remove(...themes.filter((themeclass) => themeclass !== `theme-${theme}`));
classes.add(`theme-${theme}`);
localStorage.setItem("theme", theme);
},
},
});
//# sourceMappingURL=ThemeOptions.js.map
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment