Commit 45b10b9d authored by Ori Pomerantz's avatar Ori Pomerantz

feat(docs/op-stack): Initial version

Basically a copy paste of the current site
parent ae9f35d9
# The Optimism Community Hub
[![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)
Optimism is a Layer 2 platform for Ethereum.
Optimism is, in a nutshell, an application inside of Ethereum that executes transactions more efficiently than Ethereum itself. It's based on the concept of the [Optimistic Rollup](https://research.paradigm.xyz/rollups), a construction that allows us to "optimistically" publish transaction results without actually executing those transactions on Ethereum (most of the time). Optimism makes transactions cheaper, faster, and smarter.
Please note that this repository is undergoing rapid development.
------
This is the source for the [community hub](https://community.optimism.io/).
# Usage
## Serve Locally
```shell
yarn dev
```
Then navigate to 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 port 8081.
## Build for Production
```shell
yarn build
```
You probably don't need to run this command, but now you know.
{
"name": "optimism-hub",
"version": "0.0.2",
"description": "The Optimism Community Hub",
"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"
}
}
const { description } = require('../../package')
const path = require('path')
module.exports = {
title: 'OPStack 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' }],
['link', { rel: "icon", type: "image/png", sizes: "32x32", href: "/assets/logos/favicon.png"}],
],
// cache: false,
theme: path.resolve(__dirname, './theme'),
themeConfig: {
contributor: false,
hostname: 'https://community.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: '8LQU4WGQXA',
apiKey: '2c1a86142192f96dab9a5066ad0c1d50',
indexName: 'optimism'
},
nav: [
/* When you update here, don't forget to update the tiles
in src/README.md */
{
text: 'How Optimism Works',
link: '/docs/protocol/',
},
{
text: 'Support',
link: '/docs/biz/'
},
{
text: 'Security',
link: '/docs/security-model/',
},
{
text: 'Dev Docs',
link: '/docs/developers/',
},
{
text: "Governance",
link: "/docs/governance/"
},
{
text: 'Contribute',
link: '/docs/contribute/',
},
{
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: {
'/docs/governance/': [
[
'/docs/governance/',
'What is the Optimism Collective?'
],
[
'https://www.optimism.io/vision',
'The Optimistic Vision'
],
{
title: "OP Holders",
children: [
'/docs/governance/howto-delegate.md',
'/docs/governance/economics.md',
'/docs/governance/allocations.md',
'/docs/governance/airdrop-1.md'
],
collapsable: true,
},
{
title: "Delegates",
children: [
'/docs/governance/delegate.md',
'/docs/governance/existing-delegate.md',
'/docs/governance/delegate-info.md',
],
collapsable: true,
},
{
title: "Proposal Submitters",
children: [
'/docs/governance/proposals.md',
[
'https://gov.optimism.io/tags/c/proposals/38/passed',
'Passed Proposals'
]
],
collapsable: true,
},
{
title: "Token House Governance",
children: [
'/docs/governance/token-house.md',
'/docs/governance/token-house-history.md',
'/docs/governance/gov-fund.md',
[
"https://docs.google.com/spreadsheets/d/1Ul8iMTsOFUKUmqz6MK0zpgt8Ki8tFtoWKGlwXj-Op34",
"Governance Fund Tracker"
],
[
'https://gov.optimism.io/t/working-constitution-of-the-optimism-collective/55',
"Working Constitution"
],
[
'https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md',
'Operating Manual'
]
],
collapsable: true,
},
{
title: "Citizen House Governance",
children: [
'/docs/governance/citizens-house.md',
'/docs/governance/citizenship.md',
'/docs/governance/retropgf-2.md',
],
collapsable: true
},
[
'https://calendar.google.com/calendar/u/0?cid=Y19mbm10Z3VoNm5vbzZxZ2JuaTJncGVyaWQ0a0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t',
'Governance Calendar'
],
'/docs/governance/attestation-station.md'
],
'/docs/security-model/': [
'/docs/security-model/optimism-security-model.md',
'/docs/security-model/bounties.md',
[
'https://medium.com/ethereum-optimism/our-pragmatic-path-to-decentralization-cb5805ca43c1',
'Decentralizing Optimism'
]
],
'/docs/protocol/': [
{
title: 'How Optimism Works',
children: [
'/docs/protocol/1-design-philosophy.md',
'/docs/protocol/2-rollup-protocol.md',
],
collapsable: false,
},
{
title: 'Protocol Specs',
children: [
'/docs/protocol/protocol-2.0.md',
'/docs/protocol/compressed-ctc.md'
],
collapsable: false,
}
],
'/docs/developers/': [
'/docs/developers/releases.md',
{
title: 'Building on Optimism',
children: [
'/docs/developers/build/using-tools.md',
'/docs/developers/build/transaction-fees.md',
'/docs/developers/build/system-contracts.md',
'/docs/developers/build/dev-node.md',
'/docs/developers/build/run-a-node.md',
'/docs/developers/build/differences.md',
'/docs/developers/build/json-rpc.md',
'/docs/developers/build/testing-dapps.md',
'/docs/developers/build/cheap-dapp.md'
],
},
{
title: 'Bridging L1 and L2',
children: [
'/docs/developers/bridge/basics.md',
'/docs/developers/bridge/standard-bridge.md',
'/docs/developers/bridge/messaging.md',
'/docs/developers/bridge/comm-strategies.md'
],
collapsable: true,
},
'/docs/developers/known-issues.md',
{
title: "Useful Tools",
children: [
'/docs/useful-tools/networks.md',
'/docs/useful-tools/debugging.md',
'/docs/useful-tools/faucets.md',
'/docs/useful-tools/monitoring.md',
'/docs/useful-tools/explorers.md',
'/docs/useful-tools/providers.md',
'/docs/useful-tools/oracles.md',
'/docs/useful-tools/meta-tx.md',
['https://www.optimism.io/apps/tools', 'Third Party Tools'],
],
},
{
title: "SDK",
children: [
'/docs/sdk/js-client.md',
[
'https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/docs',
'Optimism Contracts'
],
'/docs/sdk/alchemy-sdk.md'
]
},
'/docs/developers/media.md',
{
title: "OP Stack: Bedrock",
children: [
'/docs/developers/bedrock/explainer.md',
'/docs/developers/bedrock/differences.md',
'/docs/developers/bedrock/public-testnets.md',
'/docs/developers/bedrock/node-operator-guide.md',
'/docs/developers/bedrock/upgrade-guide.md',
'/docs/developers/bedrock/metrics.md'
]
}
],
}
},
plugins: [
"@vuepress/pwa",
[
'@vuepress/plugin-medium-zoom',
{
// When an image is inside a link, it means we don't to expand it
// when clicked
selector: ':not(a) > img'
}
],
"plausible-analytics"
]
}
module.exports.themeConfig.sidebar["/docs/useful-tools/"] = module.exports.themeConfig.sidebar["/docs/developers/"]
export default ({ router }) => {
router.addRoutes([
{ path: '/docs/', redirect: '/' },
])
}
{
"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;
font-size: 14px !important;
line-height: 24px !important;
min-height: 36px;
margin-left: 32px;
padding: 8px 16px !important;
width: calc(100% - 64px) !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-left: 32px;
padding: 8px 16px !important;
width: calc(100% - 64px) !important;
border-radius: 8px;
}
section.sidebar-group a.sidebar-link {
margin-left: 44px;
width: calc(100% - 64px) !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";
import type { AlgoliaOption } from "@mr-hope/vuepress-types";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
placeholder: string;
}, {
initialize(userOptions: AlgoliaOption, lang: string): void;
update(options: AlgoliaOption, lang: string): void;
}, unknown, {
options: AlgoliaOption;
}>;
export default _default;
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
{"version":3,"file":"Dropdown.js","sourceRoot":"","sources":["Dropdown.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAKtB,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,uBAAuB;IAE7B,KAAK,EAAE;QACL,OAAO,EAAE,EAAE,IAAI,EAAE,MAAiC,EAAE,QAAQ,EAAE,IAAI,EAAE;KACrE;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,WAAW,EAAE,EAAE;KAChB,CAAC;IAEF,KAAK,EAAE;QACL,KAAK,CAAC,QAAgB;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACtC,CAAC;QAED,OAAO,CAAC,QAAuB;YAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,CAAC;KACF;IAED,OAAO;QACL,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,WAAW;YACb,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,iBAA4B,IAAI,EAAE,CAAC;IAC/D,CAAC;IAED,OAAO,EAAE;QACP,UAAU,CAAC,WAA0B,EAAE,IAAY;YACjD,KAAK,OAAO,CAAC,GAAG,CAAC;gBACf,MAAM;gBACJ,mCAAmC,CAAC,wCAAwC,CAC7E;gBACD,MAAM;gBACJ,mCAAmC,CAAC,yCAAyC,CAC9E;aACF,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE;gBACtB,2BAA2B;gBAC1B,SAAiB,CAAC,OAAO,iCACrB,WAAW,KACd,aAAa,EAAE,uBAAuB;oBACtC,8CAA8C;oBAC9C,cAAc,EAAE;wBACd,YAAY,EAAE,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM;wBACnC,2BAA2B;wBACzB,WAAmB,CAAC,YAAyB,IAAI,EAAE,CACtD;qBACF,EACD,cAAc,EAAE,CACd,MAAwB,EACxB,MAAa,EACb,UAA2B,EAC3B,EAAE;wBACF,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;wBACnD,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;wBAEzD,IACE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;4BAElE,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,SAAS,GAAG,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;4BAC/D,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;oBACnC,CAAC,IACD,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QAED,MAAM,CAAC,OAAsB,EAAE,IAAY;YACzC,IAAI,CAAC,GAAG,CAAC,SAAS;gBAChB,wDAAwD,CAAC;YAC3D,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACjC,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 Vue from "vue";
import type { AlgoliaOption } from "@mr-hope/vuepress-types";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
initialize(userOptions: AlgoliaOption, _lang: string): void;
resolveRoutePathFromUrl(absoluteUrl: string): string;
update(options: AlgoliaOption, lang: string): void;
}, unknown, {
options: AlgoliaOption;
}>;
export default _default;
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
{"version":3,"file":"Full.js","sourceRoot":"","sources":["Full.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
<template>
<div id="docsearch" />
</template>
<script src="./Full" />
<style lang="stylus">
@keyframes fade-in
0%
opacity 0
to
opacity 1
body
--docsearch-spacing 12px
--docsearch-icon-stroke-width 1.4
--docsearch-muted-color #969faf
--docsearch-container-background rgba(101, 108, 133, 0.8)
--docsearch-modal-width 560px
--docsearch-modal-height 600px
--docsearch-modal-shadow inset 1px 1px 0 0 hsla(0, 0%, 100%, 0.5), 0 3px 8px 0 #555a64
--docsearch-searchbox-height 56px
--docsearch-searchbox-background #ebedf0
--docsearch-searchbox-focus-background #efeef4
--docsearch-searchbox-shadow inset 0 0 0 2px var(--accent-color)
--docsearch-hit-height 56px
--docsearch-hit-color var(--dark-grey)
--docsearch-hit-shadow 0 1px 3px 0 #d4d9e1
--docsearch-key-gradient linear-gradient(-225deg, #d5dbe4, #f8f8f8)
--docsearch-key-shadow inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff, 0 1px 2px 1px rgba(30, 35, 90, 0.4)
--docsearch-footer-height 44px
--docsearch-footer-shadow 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgba(69, 98, 155, 0.12)
@media (max-width $MQMobile)
--docsearch-searchbox-height 44px
--docsearch-spacing 8px
--docsearch-footer-height 40px
body.theme-dark
--docsearch-container-background rgba(9, 10, 17, 0.8)
--docsearch-modal-shadow inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309
--docsearch-searchbox-background #090a11
--docsearch-searchbox-focus-background lighten($darkBgColor, 10%)
--docsearch-hit-shadow none
--docsearch-key-gradient linear-gradient(-26.5deg, #565862, #31353b)
--docsearch-key-shadow inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, 0.3)
--docsearch-footer-shadow inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2)
--docsearch-muted-color #7f8497
.DocSearch-Button
display inline-flex
justify-content space-between
align-items center
height 36px
margin 0 1rem 0 0.25rem
padding 0.5rem
background var(--docsearch-searchbox-background)
border-radius 40px
color var(--docsearch-muted-color)
font-weight 500
user-select none
outline none
@media (max-width $MQMobile)
margin-right 0
&:active, &:focus, &:hover
background var(--docsearch-searchbox-focus-background)
box-shadow var(--docsearch-searchbox-shadow)
color var(--dark-grey)
&:hover .DocSearch-Search-Icon
@media (max-width $MQMobile)
color var(--accent-color)
.DocSearch-Button-Container
.navbar &
align-items center
display flex
.DocSearch-Search-Icon
width 1rem
height 1rem
margin 0.1rem
color #aaa
stroke-width 3
position relative
.DocSearch-Search-Icon
stroke-width 1.6
.DocSearch-Button-Placeholder
padding 0 12px 0 6px
font-size 1rem
.DocSearch-Button-Keys
.navbar &
display flex
.DocSearch-Button-Key
position relative
top -1px
width 20px
height 18px
margin-right 0.4em
padding-bottom 2px
border-radius 3px
background var(--docsearch-key-gradient)
box-shadow var(--docsearch-key-shadow)
color var(--docsearch-muted-color)
display flex
justify-content center
align-items center
.navbar &
display flex
.navbar
@media (max-width $MQNarrow)
.DocSearch-Button-Key, .DocSearch-Button-KeySeparator, .DocSearch-Button-Placeholder
display none
.DocSearch--active
overflow hidden !important
.DocSearch-Container
position fixed
top 0
left 0
z-index 200
box-sizing border-box
width 100vw
height 100vh
background-color var(--docsearch-container-background)
*
box-sizing border-box
a
text-decoration none
.DocSearch-Link
margin 0
padding 0
border 0
background none
color var(--accent-color)
font inherit
appearance none
cursor pointer
.DocSearch-Modal
position relative
max-width var(--docsearch-modal-width)
margin 60px auto auto
border-radius 6px
background var(--bgcolor)
box-shadow var(--docsearch-modal-shadow)
flex-direction column
@media (max-width $MQNarrow)
width 100%
max-width 100%
height 100vh
margin 0
border-radius 0
box-shadow none
.DocSearch-SearchBar
display flex
padding var(--docsearch-spacing) var(--docsearch-spacing) 0
.DocSearch-Form
align-items center
background var(--docsearch-searchbox-focus-background)
border-radius 8px
display flex
height var(--docsearch-searchbox-height)
padding 0 var(--docsearch-spacing)
position relative
width 100%
.DocSearch-Input
width 80%
height 100%
padding 0 0 0 8px
border 0
background transparent
color var(--text-color)
font inherit
font-size 1.2rem
appearance none
outline none
flex 1
&::placeholder
color var(--docsearch-muted-color)
opacity 1
&::-webkit-search-cancel-button, &::-webkit-search-decoration, &::-webkit-search-results-button, &::-webkit-search-results-decoration
display none
@media (max-width $MQMobile)
font-size 1rem
.DocSearch-LoadingIndicator, .DocSearch-MagnifierLabel, .DocSearch-Reset
margin 0
padding 0
.DocSearch-MagnifierLabel, .DocSearch-Reset
align-items center
color var(--accent-color)
display flex
justify-content center
.DocSearch-Container--Stalled .DocSearch-MagnifierLabel, .DocSearch-LoadingIndicator
display none
.DocSearch-Container--Stalled .DocSearch-LoadingIndicator
align-items center
color var(--accent-color)
display flex
justify-content center
.DocSearch-Reset
animation fade-in 0.1s ease-in forwards
appearance none
background none
border 0
border-radius 50%
color var(--docsearch-icon-color)
cursor pointer
padding 2px
right 0
stroke-width var(--docsearch-icon-stroke-width)
@media screen and (prefers-reduced-motion reduce)
animation none
appearance none
background none
border 0
border-radius 50%
color var(--docsearch-icon-color)
cursor pointer
right 0
stroke-width var(--docsearch-icon-stroke-width)
&[hidden]
display none
&:focus
outline none
&:hover
color var(--accent-color)
.DocSearch-LoadingIndicator svg, .DocSearch-MagnifierLabel svg
height 24px
width 24px
.DocSearch-Cancel
display none
margin-left var(--docsearch-spacing)
padding 0
border 0
background none
color var(--accent-color)
font-family Arial, Helvetica, sans-serif
font-size 1em
font-weight 500
white-space nowrap
user-select none
appearance none
cursor pointer
flex none
outline none
overflow hidden
@media (max-width $MQNarrow)
display inline-block
.DocSearch-Dropdown
max-height calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))
min-height var(--docsearch-spacing)
overflow-y auto
overflow-y overlay
padding 0 var(--docsearch-spacing)
@media (max-width $MQNarrow)
height 100%
max-height unset
& ul
list-style none
margin 0
padding 0
.DocSearch-Label
font-size 0.75em
line-height 1.6em
color var(--docsearch-muted-color)
.DocSearch-Help
color var(--docsearch-muted-color)
font-size 0.9em
margin 0
user-select none
.DocSearch-Title
font-size 1.2em
.DocSearch-Logo
a
display flex
svg
margin-left 8px
color var(--accent-color)
.DocSearch-Hits
&:last-of-type
margin-bottom 24px
mark
background none
color var(--accent-color)
.DocSearch-HitsFooter
margin-bottom var(--docsearch-spacing)
padding var(--docsearch-spacing)
color var(--docsearch-muted-color)
font-size 0.85em
display flex
justify-content center
a
border-bottom 1px solid
color inherit
.DocSearch-Hit
position relative
padding-bottom 4px
border-radius 4px
display flex
a
width 100%
padding-left var(--docsearch-spacing)
border-radius 4px
background var(--bgcolor)
box-shadow var(--docsearch-hit-shadow)
display block
&[aria-selected='true'] a
background-color var(--accent-color)
&[aria-selected='true'] mark
text-decoration underline
.DocSearch-Hit--deleting
opacity 0
transition all 0.25s linear
@media screen and (prefers-reduced-motion reduce)
transition none
.DocSearch-Hit--favoriting
transform scale(0)
transform-origin top center
transition all 0.25s linear
transition-delay 0.25s
@media screen and (prefers-reduced-motion reduce)
transition none
.DocSearch-Hit-source
position sticky
top 0
z-index 10
margin 0 -4px
padding 8px 4px 0
background var(--bgcolor-light)
color var(--accent-color)
font-size 0.85em
font-weight 600
line-height 32px
.DocSearch-Hit-Tree
width 24px
height var(--docsearch-hit-height)
color var(--docsearch-muted-color)
opacity 0.5
stroke-width var(--docsearch-icon-stroke-width)
@media (max-width $MQNarrow)
display none
.DocSearch-Hit-Container
height var(--docsearch-hit-height)
padding 0 var(--docsearch-spacing) 0 0
color var(--docsearch-hit-color)
display flex
flex-direction row
align-items center
.DocSearch-Hit-icon
width 20px
height 20px
color var(--docsearch-muted-color)
stroke-width var(--docsearch-icon-stroke-width)
.DocSearch-Hit-action
width 22px
height 22px
color var(--docsearch-muted-color)
stroke-width var(--docsearch-icon-stroke-width)
display flex
align-items center
svg
display block
width 18px
height 18px
& + &
margin-left 6px
.DocSearch-Hit-action-button
padding 2px
border 0
border-radius 50%
background none
color inherit
cursor pointer
appearance none
&:focus, &:hover
background rgba(0, 0, 0, 0.2)
transition background-color 0.1s ease-in
@media screen and (prefers-reduced-motion reduce)
background rgba(0, 0, 0, 0.2)
transition none
path
fill #fff
svg.DocSearch-Hit-Select-Icon
display none
.DocSearch-Hit[aria-selected='true'] .DocSearch-Hit-Select-Icon
display block
.DocSearch-Hit-content-wrapper
width 80%
position relative
margin 0 8px
font-weight 500
line-height 1.2em
text-overflow ellipsis
white-space nowrap
display flex
flex 1 1 auto
flex-direction column
justify-content center
overflow-x hidden
.DocSearch-Hit-title
font-size 0.9em
.DocSearch-Hit-path
color var(--docsearch-muted-color)
font-size 0.75em
.DocSearch-Hit[aria-selected='true']
.DocSearch-Hit-action, .DocSearch-Hit-icon, .DocSearch-Hit-path, .DocSearch-Hit-text, .DocSearch-Hit-title, .DocSearch-Hit-Tree, mark
color var(--bgcolor) !important
.DocSearch-ErrorScreen, .DocSearch-NoResults, .DocSearch-StartScreen
width 80%
margin 0 auto
padding 36px 0
font-size 0.9em
text-align center
.DocSearch-Screen-Icon
padding-bottom 12px
color var(--docsearch-muted-color)
.DocSearch-NoResults-Prefill-List
display inline-block
padding-bottom 24px
text-align left
ul
display inline-block
padding 8px 0 0
li
list-style-position inside
list-style-type '» '
.DocSearch-Prefill
appearance none
background none
border 0
border-radius 1em
color var(--accent-color)
cursor pointer
display inline-block
font-size 1em
font-weight 700
padding 0
&:focus, &:hover
outline none
text-decoration underline
.DocSearch-Footer
position relative
z-index 300
width 100%
height var(--docsearch-footer-height)
padding 0 var(--docsearch-spacing)
border-radius 0 0 8px 8px
box-shadow var(--docsearch-footer-shadow)
background var(--bgcolor)
display flex
flex-direction row-reverse
flex-shrink 0
align-items center
justify-content space-between
user-select none
@media (max-width $MQNarrow)
position absolute
bottom 0
border-radius 0
.DocSearch-Commands
margin 0
padding 0
color var(--docsearch-muted-color)
display flex
list-style none
@media (max-width $MQNarrow)
display none
li
display flex
align-items center
&:not(:last-of-type)
margin-right 0.8em
.DocSearch-Commands-Key
width 20px
height 18px
margin-right 0.4em
padding-bottom 2px
border-radius 2px
background var(--docsearch-key-gradient)
box-shadow var(--docsearch-key-shadow)
display flex
justify-content center
align-items center
</style>
import Vue from "vue";
import { SidebarHeader } from "@theme/utils/sidebar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {}, {}, {}, {
items: SidebarHeader[];
}>;
export default _default;
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://optimismpbc.typeform.com/get-in-touch", target: "_blank" } }, [
h("div", [
h("i", { attrs: { class: "far fa-comment-dots" } }),
" Get support for going live"
])
]),
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
\ No newline at end of file
{"version":3,"file":"Anchor.js","sourceRoot":"","sources":["Anchor.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAW7C,MAAM,UAAU,GAAG,CACjB,CAAgB,EAChB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAc,EAC1B,EAAE,CACT,CAAC,CACC,YAAY,EACZ;IACE,KAAK,EAAE;QACL,EAAE,EAAE,IAAI;QACR,WAAW,EAAE,EAAE;QACf,gBAAgB,EAAE,EAAE;KACrB;IACD,KAAK,EAAE;QACL,aAAa,EAAE,IAAI;QACnB,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK;KACxC;CACF,EACD,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CACvB,CAAC;AAOJ,MAAM,cAAc,GAAG,CACrB,CAAgB,EAChB,EAAE,QAAQ,EAAE,KAAK,EAAyB,EACnC,EAAE,CACT,CAAC,CACC,IAAI,EACJ,EAAE,KAAK,EAAE,aAAa,EAAE,EACxB,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAoB,EAAE,EAAE;IACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAE9D,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;QAClD,UAAU,CAAC,CAAC,EAAE;YACZ,IAAI,EAAE,KAAK,CAAC,KAAK;YACjB,IAAI,EAAE,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE;YACnC,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB,CAAC;KACH,CAAC,CAAC;AACL,CAAC,CAAC,CACH,CAAC;AAEJ,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,QAAQ;IAEd,UAAU,EAAE,IAAI;IAEhB,KAAK,EAAE;QACL,KAAK,EAAE;YACL,IAAI,EAAE,KAAkC;YACxC,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE;SAClB;KACF;IAED,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAC5C,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,EAAE,EAAE;YAC3D,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;gBACtC,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE;oBACpC,KAAK,CAAC,KAAK,CAAC,MAAM;wBAChB,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE;4BAChB,QAAQ,EAAE,KAAK,CAAC,KAAK;4BACrB,KAAK,EAAE,MAAM;yBACd,CAAC;wBACJ,CAAC,CAAC,KAAK,CAAC,OAAO;4BACf,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE;gCAChB,QAAQ,EAAE,KAAK,CAAC,OAAO;gCACvB,KAAK,EAAE,MAAM;6BACd,CAAC;4BACJ,CAAC,CAAC,IAAI;iBACT,CAAC;aACH,CAAC;SACH,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
\ No newline at end of file
<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";
import type { PageComputed } from "@mr-hope/vuepress-types";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
author: string;
time: string;
tags: string[];
readingTimeContent: string;
readingTime: string;
authorText: string;
timeText: string;
tagText: string;
readingTimeText: string;
}, {
article: PageComputed;
}>;
export default _default;
import Vue from "vue";
import { capitalize } from "@mr-hope/vuepress-shared";
import AuthorIcon from "@mr-hope/vuepress-plugin-comment/lib/client/icons/AuthorIcon.vue";
import CalendarIcon from "@mr-hope/vuepress-plugin-comment/lib/client/icons/CalendarIcon.vue";
import CategoryInfo from "@mr-hope/vuepress-plugin-comment/lib/client/CategoryInfo.vue";
import TagInfo from "@mr-hope/vuepress-plugin-comment/lib/client/TagInfo.vue";
import TimerIcon from "@mr-hope/vuepress-plugin-comment/lib/client/icons/TimerIcon.vue";
export default Vue.extend({
name: "ArticleInfo",
components: {
AuthorIcon,
CalendarIcon,
CategoryInfo,
TagInfo,
TimerIcon,
},
props: {
article: { type: Object, required: true },
},
computed: {
author() {
return (this.article.frontmatter.author ||
(this.$themeConfig.author && this.article.frontmatter.author !== false
? this.$themeConfig.author
: ""));
},
time() {
const { date, time = date } = this.article.frontmatter;
if (typeof time === "string") {
if (time.indexOf("T") !== -1) {
const [dateString, temp] = time.split("T");
const [times] = temp.split(".");
return `${dateString} ${times === "00:00:00" ? "" : times}`;
}
return time;
}
return this.article.createTime || "";
},
tags() {
const { tag, tags = tag } = this.article.frontmatter;
if (typeof tags === "string")
return [capitalize(tags)];
if (Array.isArray(tags))
return tags.map((item) => capitalize(item));
return [];
},
readingTimeContent() {
return `PT${Math.max(Math.round(this.$page.readingTime.minutes), 1)}M`;
},
readingTime() {
const { minute, time } = READING_TIME_I18N[this.$localePath || "/"];
return this.article.readingTime.minutes < 1
? minute
: time.replace("$time", Math.round(this.article.readingTime.minutes).toString());
},
authorText() {
return PAGE_INFO_I18N[this.$localePath || "/"].author;
},
timeText() {
return PAGE_INFO_I18N[this.$localePath || "/"].time;
},
tagText() {
return PAGE_INFO_I18N[this.$localePath || "/"].tag;
},
readingTimeText() {
return PAGE_INFO_I18N[this.$localePath || "/"].readingTime;
},
},
});
//# sourceMappingURL=ArticleInfo.js.map
\ No newline at end of file
{"version":3,"file":"ArticleInfo.js","sourceRoot":"","sources":["ArticleInfo.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
<template>
<div v-if="author || time" class="article-info">
<!-- Author -->
<span v-if="author" :aria-label="authorText" data-balloon-pos="down">
<AuthorIcon />
<span property="author" v-text="author" />
</span>
<!-- Writing Date -->
<span
v-if="time"
class="time"
:aria-label="timeText"
data-balloon-pos="down"
>
<CalendarIcon />
<span property="datePublished" v-text="time" />
</span>
<CategoryInfo
v-if="article.frontmatter.category"
:category="article.frontmatter.category"
/>
<TagInfo v-if="tags.length !== 0" :tags="tags" />
<!-- Reading time -->
<span
v-if="readingTime"
class="read-time-info"
:aria-label="readingTimeText"
data-balloon-pos="down"
>
<TimerIcon />
<span v-text="readingTime" />
<meta property="timeRequired" :content="readingTimeContent" />
</span>
</div>
</template>
<script src="./ArticleInfo" />
<style lang="stylus">
$articleInfoTextSize ?= 14px
.article-info
color var(--dark-grey)
font-size $articleInfoTextSize
font-family Arial, Helvetica, sans-serif
& > span
display inline-block
margin-right 0.5em
line-height 1.8
@media (max-width $MQMobileNarrow)
margin-right 0.3em
font-size 0.86rem
&::after
--balloon-font-size 8px
padding 0.3em 0.6em !important
svg
position relative
bottom -0.125em
.tags-wrapper
display inline-block
.icon
width 1em
height 1em
</style>
import Vue from "vue";
import type { PageComputed } from "@mr-hope/vuepress-types";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
isEncrypted: boolean;
excerpt: string;
}, {
article: PageComputed;
}>;
export default _default;
import Vue from "vue";
import ArticleInfo from "@theme/components/Blog/ArticleInfo.vue";
import LockIcon from "@theme/icons/LockIcon.vue";
import PresentationIcon from "@theme/icons/PresentationIcon.vue";
import StickyIcon from "@theme/icons/StickyIcon.vue";
import { getPathMatchedKeys } from "@theme/utils/encrypt";
export default Vue.extend({
name: "ArticleItem",
components: { ArticleInfo, LockIcon, StickyIcon, PresentationIcon },
props: {
article: { type: Object, required: true },
},
computed: {
isEncrypted() {
return (getPathMatchedKeys(this.$themeConfig.encrypt, this.article.path)
.length !== 0 || Boolean(this.article.frontmatter.password));
},
excerpt() {
if (this.article.excerpt)
return this.article.excerpt;
return (this.article.frontmatter.description ||
this.article.frontmatter.summary ||
"");
},
},
});
//# sourceMappingURL=ArticleItem.js.map
\ No newline at end of file
{"version":3,"file":"ArticleItem.js","sourceRoot":"","sources":["ArticleItem.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,WAAW,MAAM,wCAAwC,CAAC;AACjE,OAAO,QAAQ,MAAM,2BAA2B,CAAC;AACjD,OAAO,gBAAgB,MAAM,mCAAmC,CAAC;AACjE,OAAO,UAAU,MAAM,6BAA6B,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAK1D,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,aAAa;IAEnB,UAAU,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,gBAAgB,EAAE;IAEnE,KAAK,EAAE;QACL,OAAO,EAAE,EAAE,IAAI,EAAE,MAAgC,EAAE,QAAQ,EAAE,IAAI,EAAE;KACpE;IAED,QAAQ,EAAE;QACR,WAAW;YACT,OAAO,CACL,kBAAkB,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;iBAC7D,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAC9D,CAAC;QACJ,CAAC;QAED,OAAO;YACL,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC;YAEtD,OAAO,CACL,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,WAAW;gBACpC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO;gBAChC,EAAE,CACH,CAAC;QACJ,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<article class="article" vocab="https://schema.org/" typeof="Article">
<StickyIcon v-if="article.frontmatter.sticky" />
<header class="title" @click="$router.push(article.path)">
<LockIcon v-if="isEncrypted" />
<PresentationIcon v-if="article.frontmatter.layout === 'Slide'" />
<span property="headline">{{ article.title }}</span>
<meta
v-if="article.frontmatter.image"
property="image"
:content="$withBase(article.frontmatter.image)"
/>
</header>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="excerpt" class="excerpt" v-html="excerpt" />
<hr class="hr" />
<ArticleInfo :article="article" />
</article>
</template>
<script src="./ArticleItem" />
<style lang="stylus">
.article
position relative
box-sizing border-box
width 100%
margin 0 auto 20px
padding 16px 20px
background var(--bgcolor)
border-radius 6px
text-align left
box-shadow 0 1px 3px 0 var(--card-shadow-color)
@media (max-width $MQMobileNarrow)
border-radius 0
&:last-child
margin-bottom 0
&:hover
box-shadow 0 2px 6px 0 var(--card-shadow-color)
.sticky-icon
position absolute
top 0
right 0
width 40px
height 40px
fill var(--accent-color)
.sticky-text
fill var(--white)
.title
display inline-block
position relative
font-size 1.28rem
line-height 36px
&::after
content ''
position absolute
width 100%
height 2px
bottom 0
left 0
background var(--accent-color)
visibility hidden
transform scaleX(0)
transition transform 0.3s ease-in-out
&:hover
cursor pointer
&::after
visibility visible
transform scaleX(1)
.lock-icon, .presentation-icon
position relative
bottom -0.125em
display inline-block
vertical-align baseline
width 20px
height 20px
color var(--accent-color)
.excerpt
overflow hidden
line-height 1.7
h1
display none
& + p
margin-top 0.5em
p
&:first-child
margin-top 0.5em
&:last-child
margin-bottom 0.5em
// code block fix
pre
line-height 1.4
padding 1.25rem 1.5rem
margin 0.85rem 0
// line number fix
.line-numbers-mode
pre
padding-left ($lineNumbersWrapperWidth + 1) rem
// hide code demo
.code-demo-wrapper
display none
</style>
import Vue from "vue";
import type { BlogOptions } from "@theme/types";
import type { PageComputed } from "@mr-hope/vuepress-types";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
currentPage: number;
articleList: PageComputed[];
}, {
getArticleList(): PageComputed[];
}, {
blogConfig: BlogOptions;
articlePerPage: number;
filter: ((page: PageComputed) => boolean) | undefined;
$articles: PageComputed[];
articles: PageComputed[];
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import ArticleItem from "@theme/components/Blog/ArticleItem.vue";
import EmptyIcon from "@theme/icons/EmptyIcon.vue";
import MyTransition from "@theme/components/MyTransition.vue";
import { filterArticle, sortArticle } from "@theme/utils/article";
import { getPathMatchedKeys } from "@theme/utils/encrypt";
export default Vue.extend({
name: "ArticleList",
components: { ArticleItem, EmptyIcon, MyTransition },
data: () => ({
currentPage: 1,
articleList: [],
}),
computed: {
blogConfig() {
return this.$themeConfig.blog || {};
},
articlePerPage() {
return this.blogConfig.perPage || 10;
},
filter() {
const { path } = this.$route;
return path.includes("/article")
? (page) => page.frontmatter.layout !== "Slide"
: path.includes("/star")
? (page) => Boolean(page.frontmatter.star || page.frontmatter.sticky)
: path.includes("/encrypt")
? (page) => getPathMatchedKeys(this.$themeConfig.encrypt, page.path).length !==
0 || Boolean(page.frontmatter.password)
: path.includes("/slide")
? (page) => page.frontmatter.layout === "Slide"
: undefined;
},
$articles() {
// filter then sort
return sortArticle(filterArticle(this.$site.pages, this.filter), "sticky");
},
/** Articles in this page */
articles() {
return this.articleList.slice((this.currentPage - 1) * this.articlePerPage, this.currentPage * this.articlePerPage);
},
},
watch: {
// update article list when route is changed
$route(to, from) {
if (to.path !== from.path) {
this.articleList = this.getArticleList();
// reset page to 1
this.currentPage = 1;
}
},
currentPage() {
// list top border distance
const distance = document.querySelector("#article-list").getBoundingClientRect().top + window.scrollY;
setTimeout(() => {
window.scrollTo(0, distance);
}, 100);
},
},
mounted() {
this.articleList = this.getArticleList();
},
methods: {
getArticleList() {
try {
return this.$pagination
? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.$pagination._matchedPages
: this.$articles;
}
catch (err) {
return this.$articles;
}
},
},
});
//# sourceMappingURL=ArticleList.js.map
\ No newline at end of file
{"version":3,"file":"ArticleList.js","sourceRoot":"","sources":["ArticleList.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
<template>
<div id="article-list" class="article-wrapper">
<EmptyIcon v-if="!articles.length" class="empty" />
<MyTransition
v-for="(article, index) in articles"
:key="article.path"
:delay="index * 0.04"
>
<ArticleItem :article="article" />
</MyTransition>
<Pagination
v-model="currentPage"
:per-page="articlePerPage"
:total="articleList.length"
/>
</div>
</template>
<script src="./ArticleList" />
<style lang="stylus">
.article-wrapper
margin-top -0.5rem - $navbarHeight
padding-top: $navbarHeight + 0.5rem
text-align center
@media (max-width $MQMobile)
margin-top -0.5rem - $navbarMobileHeight
padding-top: $navbarMobileHeight + 0.5rem
.empty
max-width 560px
margin 0 auto
text-align center
</style>
import Vue from "vue";
interface ArticleTypeItem {
text: string;
path: string;
}
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
navigate(path: string): void;
}, {
types: ArticleTypeItem[];
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
import { navigate } from "@theme/utils/navigate";
export default Vue.extend({
name: "ArticleType",
computed: {
types() {
const blogI18n = this.$themeLocaleConfig.blog || getDefaultLocale().blog;
return [
{ text: blogI18n.allText, path: "/article/" },
{ text: blogI18n.star, path: "/star/" },
{ text: blogI18n.slides, path: "/slide/" },
{ text: blogI18n.encrypt, path: "/encrypt/" },
];
},
},
methods: {
navigate(path) {
navigate(path, this.$router, this.$route);
},
},
});
//# sourceMappingURL=ArticleType.js.map
\ No newline at end of file
{"version":3,"file":"ArticleType.js","sourceRoot":"","sources":["ArticleType.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOjD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,aAAa;IAEnB,QAAQ,EAAE;QACR,KAAK;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC,IAAI,CAAC;YAEzE,OAAO;gBACL,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE;gBAC7C,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACvC,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;gBAC1C,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE;aAC9C,CAAC;QACJ,CAAC;KACF;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,IAAY;YACnB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<ul class="article-type-wrapper">
<li
v-for="type in types"
:key="type.text"
class="article-type"
:class="{ active: type.path === $route.path }"
role="navigation"
@click="navigate(type.path)"
>
<span>{{ type.text }}</span>
</li>
</ul>
</template>
<script src="./ArticleType" />
<style lang="stylus">
.article-type-wrapper
position relative
padding-left 0
font-size 18px
font-family Arial, Helvetica, sans-serif
font-weight 600
display flex
justify-content center
align-items center
list-style none
z-index 2
@media (max-width $MQMobileNarrow)
font-size 16px
.article-type
position relative
vertical-align middle
margin 0.3em 0.8em
line-height 1.2
cursor pointer
&::after
position absolute
content ' '
left 50%
right 50%
bottom -6px
height 2px
background var(--accent-color)
border-radius 1px
visibility hidden
transition left 0.2s ease-in-out, right 0.2s ease-in-out
span
transition all 0.3s ease-in-out
&.active
position relative
span
display inline-block
color var(--accent-color)
transform scale(1.1, 1.1)
&:hover, &.active
&::after
left calc(50% - 8px)
right calc(50% - 8px)
visibility visible
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
defaultHeroImage: string;
}, unknown, {
heroImageStyle: Record<string, string>;
bgImageStyle: Record<string, string>;
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import MyTransition from "@theme/components/MyTransition.vue";
import defaultHeroImage from "@theme/assets/hero.jpg";
export default Vue.extend({
name: "BlogHero",
components: { MyTransition },
data: () => ({ defaultHeroImage }),
computed: {
heroImageStyle() {
const defaultStyle = {
maxHeight: "180px",
margin: this.$frontmatter.showTitle === false
? "6rem auto 1.5rem"
: "1rem auto",
};
return Object.assign(Object.assign({}, defaultStyle), this.$frontmatter.heroImageStyle);
},
bgImageStyle() {
const defaultBgImageStyle = {
height: "350px",
textAlign: "center",
overflow: "hidden",
};
const { bgImageStyle = {} } = this.$frontmatter;
return Object.assign(Object.assign({}, defaultBgImageStyle), bgImageStyle);
},
},
});
//# sourceMappingURL=BlogHero.js.map
\ No newline at end of file
{"version":3,"file":"BlogHero.js","sourceRoot":"","sources":["BlogHero.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,gBAAgB,MAAM,wBAAwB,CAAC;AAEtD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE,EAAE,YAAY,EAAE;IAE5B,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAElC,QAAQ,EAAE;QACR,cAAc;YACZ,MAAM,YAAY,GAAG;gBACnB,SAAS,EAAE,OAAO;gBAClB,MAAM,EACJ,IAAI,CAAC,YAAY,CAAC,SAAS,KAAK,KAAK;oBACnC,CAAC,CAAC,kBAAkB;oBACpB,CAAC,CAAC,WAAW;aAClB,CAAC;YAEF,uCACK,YAAY,GACX,IAAI,CAAC,YAAY,CAAC,cAAyC,EAC/D;QACJ,CAAC;QAED,YAAY;YACV,MAAM,mBAAmB,GAA2B;gBAClD,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,QAAQ;gBACnB,QAAQ,EAAE,QAAQ;aACnB,CAAC;YACF,MAAM,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAEhD,uCACK,mBAAmB,GAClB,YAAuC,EAC3C;QACJ,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div
v-if="$frontmatter.hero !== false"
class="blog-hero"
:class="{ full: $frontmatter.heroFullScreen }"
:style="{ ...bgImageStyle }"
>
<div
class="mask"
:style="{
background: `url(${
$frontmatter.bgImage
? $withBase($frontmatter.bgImage)
: defaultHeroImage
}) center/cover no-repeat`,
}"
/>
<MyTransition :delay="0.04">
<img
v-if="$frontmatter.heroImage"
class="hero-logo"
:style="heroImageStyle || {}"
:src="$withBase($frontmatter.heroImage)"
alt="hero"
/>
</MyTransition>
<MyTransition :delay="0.08">
<h1 v-if="$frontmatter.showTitle !== false">
{{ $frontmatter.heroText || $title || "Hope" }}
</h1>
</MyTransition>
<MyTransition :delay="0.12">
<p v-if="$description" class="description" v-text="$description" />
</MyTransition>
</div>
</template>
<script src="./BlogHero" />
<style lang="stylus">
.blog-hero
position relative
color #eee
margin-bottom 16px
height 450px
display flex
flex-direction column
justify-content center
@media (max-width $MQMobile)
height 350px
margin 0 -1.5rem 16px
@media (max-width $MQMobileNarrow)
margin 0 0 16px
&.full
height 'calc(100vh - %s)' % $navbarHeight !important
@media (max-width $MQMobile)
height 'calc(100vh - %s)' % $navbarMobileHeight !important
.mask
background-position-y top !important
.mask
position absolute
top 0
bottom 0
left 0
right 0
&:after
display block
content ' '
background var(--light-grey)
position absolute
top 0
bottom 0
left 0
right 0
z-index 1
opacity 0.2
& > :not(.mask)
position relative
z-index 2
h1
margin 0.5rem auto
font-size 36px
@media (max-width $MQNarrow)
font-size 30px
@media (max-width $MQMobile)
font-size 36px
@media (max-width $MQMobileNarrow)
font-size 30px
.hero-logo + h1
margin 0 auto
.description
margin 1.2rem auto 0
font-size 20px
@media (max-width $MQNarrow)
font-size 18px
@media (max-width $MQMobile)
font-size 20px
@media (max-width $MQMobileNarrow)
font-size 18px
</style>
import Vue from "vue";
/**
* 项目配置
*
* Project Configuration
*/
export interface ProjectOptions {
/**
* 项目类型
*
* Type of project
*/
type: "article" | "book" | "link" | "project";
/**
* 项目名称
*
* Project name
*/
name: string;
/**
* 项目描述
*
* Project desription
*/
desc?: string;
/**
* 项目封面,应为绝对路径
*
* Cover for the project, must be an absolute path
*/
cover?: string;
/**
* 项目链接
*
* Link of the project
*/
link: string;
}
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, unknown, Record<never, any>>;
export default _default;
import Vue from "vue";
import ArticleList from "@theme/components/Blog/ArticleList.vue";
import BlogHero from "@theme/components/Blog/BlogHero.vue";
import BlogInfo from "@BlogInfo";
import MyTransition from "@theme/components/MyTransition.vue";
import ProjectList from "@theme/components/Blog/ProjectList.vue";
export default Vue.extend({
name: "BlogHome",
components: {
ArticleList,
BlogHero,
BlogInfo,
MyTransition,
ProjectList,
},
});
//# sourceMappingURL=BlogHome.js.map
\ No newline at end of file
{"version":3,"file":"BlogHome.js","sourceRoot":"","sources":["BlogHome.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,WAAW,MAAM,wCAAwC,CAAC;AACjE,OAAO,QAAQ,MAAM,qCAAqC,CAAC;AAC3D,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,WAAW,MAAM,wCAAwC,CAAC;AAwCjE,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE;QACV,WAAW;QACX,QAAQ;QACR,QAAQ;QACR,YAAY;QACZ,WAAW;KACZ;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="page blog">
<BlogHero />
<div class="blog-page-wrapper">
<main class="blog-home">
<MyTransition :delay="0.16">
<ProjectList />
</MyTransition>
<MyTransition :delay="0.24">
<ArticleList :key="$route.path" />
</MyTransition>
</main>
<MyTransition :delay="0.16">
<BlogInfo />
</MyTransition>
</div>
<MyTransition :delay="0.28">
<Content :key="$route.path" class="theme-default-content" custom />
</MyTransition>
</div>
</template>
<script src="./BlogHome" />
<style lang="stylus">
.page.blog
box-sizing content-box
min-height 100vh
padding-top $navbarHeight
padding-bottom 2rem
margin 0px auto
background var(--bgcolor-light)
@media (max-width $MQMobile)
padding $navbarMobileHeight 1.5rem 2rem
@media (max-width $MQMobileNarrow)
padding-left 0
padding-right 0
.blog-page-wrapper
display flex
justify-content center
align-items flex-start
margin 0 auto
@media (min-width $MQMobile)
padding 0 1rem
@media (min-width $MQNarrow)
padding 0 2rem
@media (min-width $MQWide)
padding 0
.blog-home
max-width 780px
overflow hidden
flex 1
@media (min-width $MQMobile)
margin 0 15px
.theme-default-content:empty
padding 0
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, unknown, Record<never, any>>;
export default _default;
import Vue from "vue";
import BlogInfoList from "@theme/components/Blog/BlogInfoList.vue";
import BloggerInfo from "@theme/components/Blog/BloggerInfo.vue";
import MyTransition from "@theme/components/MyTransition.vue";
export default Vue.extend({
name: "BlogInfo",
components: { BlogInfoList, BloggerInfo, MyTransition },
});
//# sourceMappingURL=BlogInfo.js.map
\ No newline at end of file
{"version":3,"file":"BlogInfo.js","sourceRoot":"","sources":["BlogInfo.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,YAAY,MAAM,yCAAyC,CAAC;AACnE,OAAO,WAAW,MAAM,wCAAwC,CAAC;AACjE,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAE9D,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE;CACxD,CAAC,CAAC"}
\ No newline at end of file
<template>
<aside class="blog-info-wrapper">
<MyTransition>
<BloggerInfo />
</MyTransition>
<MyTransition :delay="0.04">
<BlogInfoList />
</MyTransition>
</aside>
</template>
<script src="./BlogInfo" />
<style lang="stylus">
.blog-info-wrapper
.sidebar &
.blogger-info
display none
.page &
position sticky
box-sizing border-box
top ($navbarHeight + 1rem)
flex 0 0 300px
height auto
margin-bottom 12px
transition all 0.3s
@media (max-width $MQMobile)
display none
.blogger-info
margin-bottom 16px
padding 8px 0
border-radius 8px
box-shadow 0 1px 3px 0 var(--card-shadow-color)
&:hover
box-shadow 0 2px 6px 0 var(--card-shadow-color)
</style>
import ArticleIcon from "@theme/icons/ArticleIcon.vue";
declare const _default: import("vue/types/vue").ExtendedVue<{
$starArticles: import("@mr-hope/vuepress-types").PageComputed[];
} & Record<never, any> & ArticleIcon, {
active: string;
}, {
setActive(name: string): void;
navigate(path: string): void;
}, {
i18n: {
article: string;
articleList: string;
category: string;
tag: string;
timeline: string;
timelineText: string;
allText: string;
intro: string;
star: string;
slides: string;
encrypt: string;
};
articleNumber: number;
}, Record<never, any>>;
export default _default;
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
import ArticleIcon from "@theme/icons/ArticleIcon.vue";
import CategoryIcon from "@mr-hope/vuepress-plugin-comment/lib/client/icons/CategoryIcon.vue";
import TagIcon from "@mr-hope/vuepress-plugin-comment/lib/client/icons/TagIcon.vue";
import TimeIcon from "@mr-hope/vuepress-plugin-comment/lib/client/icons/TimeIcon.vue";
import ArticleList from "@theme/components/Blog/ArticleList.vue";
import CategoryList from "@theme/components/Blog/CategoryList.vue";
import MyTransition from "@theme/components/MyTransition.vue";
import TagList from "@theme/components/Blog/TagList.vue";
import Timeline from "@theme/components/Blog/Timeline.vue";
import TimelineList from "@theme/components/Blog/TimelineList.vue";
import { filterArticle } from "@theme/utils/article";
import { starMixin } from "@theme/mixins/star";
export default starMixin.extend({
name: "BlogInfo",
components: {
ArticleIcon,
ArticleList,
CategoryIcon,
CategoryList,
MyTransition,
TagIcon,
TagList,
TimeIcon,
Timeline,
TimelineList,
},
data: () => ({
active: "category",
}),
computed: {
i18n() {
return this.$themeLocaleConfig.blog || getDefaultLocale().blog;
},
articleNumber() {
return filterArticle(this.$site.pages).length;
},
},
methods: {
setActive(name) {
this.active = name;
},
navigate(path) {
if (this.$route.path !== path)
void this.$router.push(path);
},
},
});
//# sourceMappingURL=BlogInfoList.js.map
\ No newline at end of file
{"version":3,"file":"BlogInfoList.js","sourceRoot":"","sources":["BlogInfoList.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,WAAW,MAAM,8BAA8B,CAAC;AACvD,OAAO,YAAY,MAAM,oEAAoE,CAAC;AAC9F,OAAO,OAAO,MAAM,+DAA+D,CAAC;AACpF,OAAO,QAAQ,MAAM,gEAAgE,CAAC;AACtF,OAAO,WAAW,MAAM,wCAAwC,CAAC;AACjE,OAAO,YAAY,MAAM,yCAAyC,CAAC;AACnE,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,OAAO,MAAM,oCAAoC,CAAC;AACzD,OAAO,QAAQ,MAAM,qCAAqC,CAAC;AAC3D,OAAO,YAAY,MAAM,yCAAyC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAI/C,eAAe,SAAS,CAAC,MAAM,CAAC;IAC9B,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE;QACV,WAAW;QACX,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,YAAY;QACZ,OAAO;QACP,OAAO;QACP,QAAQ;QACR,QAAQ;QACR,YAAY;KACb;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,MAAM,EAAE,UAAU;KACnB,CAAC;IAEF,QAAQ,EAAE;QACR,IAAI;YACF,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC,IAAI,CAAC;QACjE,CAAC;QAED,aAAa;YACX,OAAO,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QAChD,CAAC;KACF;IAED,OAAO,EAAE;QACP,SAAS,CAAC,IAAY;YACpB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QAED,QAAQ,CAAC,IAAY;YACnB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI;gBAAE,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9D,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="blog-info-list">
<div class="switch-wrapper">
<button class="switch-button" @click="setActive('article')">
<div
class="icon-wapper"
:class="{ active: active === 'article' }"
:aria-label="i18n.article"
data-balloon-pos="up"
>
<ArticleIcon />
</div>
</button>
<button class="switch-button" @click="setActive('category')">
<div
class="icon-wapper"
:class="{ active: active === 'category' }"
:aria-label="i18n.category"
data-balloon-pos="up"
>
<CategoryIcon />
</div>
</button>
<button class="switch-button" @click="setActive('tag')">
<div
class="icon-wapper"
:class="{ active: active === 'tag' }"
:aria-label="i18n.tag"
data-balloon-pos="up"
>
<TagIcon />
</div>
</button>
<button class="switch-button" @click="setActive('timeline')">
<div
class="icon-wapper"
:class="{ active: active === 'timeline' }"
:aria-label="i18n.timeline"
data-balloon-pos="up"
>
<TimeIcon />
</div>
</button>
</div>
<!-- Article -->
<MyTransition v-if="active === 'article'">
<div class="sticky-article-wrapper">
<div class="title" @click="navigate('/article/')">
<ArticleIcon />
<span class="num">{{ articleNumber }}</span>
{{ i18n.article }}
</div>
<hr />
<ul class="sticky-article-list">
<MyTransition
v-for="(article, index) in $starArticles"
:key="article.path"
:delay="(index + 1) * 0.08"
>
<li
class="sticky-article"
@click="navigate(article.path)"
v-text="article.title"
/>
</MyTransition>
</ul>
</div>
</MyTransition>
<!-- Category -->
<MyTransition v-if="active === 'category'">
<div class="category-wrapper">
<div
v-if="$category.list.length !== 0"
class="title"
@click="navigate('/category/')"
>
<CategoryIcon />
<span class="num">{{ $category.list.length }}</span>
{{ i18n.category }}
</div>
<hr />
<MyTransition :delay="0.04">
<CategoryList />
</MyTransition>
</div>
</MyTransition>
<!-- Tags -->
<MyTransition v-if="active === 'tag'">
<div class="tag-wrapper">
<div
v-if="$tag.list.length !== 0"
class="title"
@click="navigate('/tag/')"
>
<TagIcon />
<span class="num">{{ $tag.list.length }}</span>
{{ i18n.tag }}
</div>
<hr />
<MyTransition :delay="0.04">
<TagList />
</MyTransition>
</div>
</MyTransition>
<!-- Timeline -->
<MyTransition v-if="active === 'timeline'">
<TimelineList />
</MyTransition>
</div>
</template>
<script src="./BlogInfoList" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/reset'
.blog-info-list
margin 8px auto
padding 8px 16px
.page &
background var(--bgcolor)
border-radius 6px
box-shadow 0 1px 3px 0 var(--card-shadow-color)
&:hover
box-shadow 0 2px 6px 0 var(--card-shadow-color)
.switch-wrapper
display flex
justify-content center
margin-bottom 8px
.switch-button
button()
width 44px
height 44px
margin 0 8px
padding 4px
color var(--grey3)
&:focus
outline none
.icon-wapper
width 20px
height 20px
padding 8px
border-radius 50%
background rgba(127, 127, 127, 0.15)
.theme-dark &
background rgba(255, 255, 255, 0.15)
&:hover
cursor pointer
&.active
.theme-light &
background var(--accent-color-l10)
.theme-dark &
background var(--accent-color-d10)
.icon
width 100%
height 100%
.sticky-article-wrapper, .category-wrapper, .tag-wrapper
padding 8px 0
.title
cursor pointer
.icon
position relative
bottom -0.125rem
width 16px
height 16px
margin 0 6px
.num
position relative
margin 0 2px
font-size 22px
.sticky-article-wrapper
.sticky-article-list
margin 8px auto
.sticky-article
padding 12px 8px 4px
border-bottom 1px dashed var(--grey14)
&:hover
cursor pointer
color var(--accent-color)
.category-wrapper
.category-list-wrapper
margin 8px auto
.tag-wrapper
.tag-list-wrapper
margin 8px auto
.page &
.timeline-list-wrapper
.content
max-height 60vh
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
showArticles: boolean;
componentName: string;
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import ArticleList from "@theme/components/Blog/ArticleList.vue";
import ArticleType from "@theme/components/Blog/ArticleType.vue";
import BlogInfo from "@BlogInfo";
import CategoryList from "@theme/components/Blog/CategoryList.vue";
import MyTransition from "@theme/components/MyTransition.vue";
import TagList from "@theme/components/Blog/TagList.vue";
import Timeline from "@theme/components/Blog/Timeline.vue";
import TimelineList from "@theme/components/Blog/TimelineList.vue";
export default Vue.extend({
name: "BlogPage",
components: {
ArticleList,
ArticleType,
BlogInfo,
CategoryList,
MyTransition,
TagList,
Timeline,
TimelineList,
},
computed: {
showArticles() {
const { path } = this.$route;
return !path.includes("/timeline");
},
componentName() {
const pathName = this.$route.path.split("/")[1];
if (["category", "tag"].includes(pathName))
return `${pathName}List`;
else if (pathName === "timeline")
return pathName;
return "articleType";
},
},
});
//# sourceMappingURL=BlogPage.js.map
\ No newline at end of file
{"version":3,"file":"BlogPage.js","sourceRoot":"","sources":["BlogPage.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,WAAW,MAAM,wCAAwC,CAAC;AACjE,OAAO,WAAW,MAAM,wCAAwC,CAAC;AACjE,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,YAAY,MAAM,yCAAyC,CAAC;AACnE,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,OAAO,MAAM,oCAAoC,CAAC;AACzD,OAAO,QAAQ,MAAM,qCAAqC,CAAC;AAC3D,OAAO,YAAY,MAAM,yCAAyC,CAAC;AAEnE,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE;QACV,WAAW;QACX,WAAW;QACX,QAAQ;QACR,YAAY;QACZ,YAAY;QACZ,OAAO;QACP,QAAQ;QACR,YAAY;KACb;IAED,QAAQ,EAAE;QACR,YAAY;YACV,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;YAE7B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACrC,CAAC;QAED,aAAa;YACX,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAEhD,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,OAAO,GAAG,QAAQ,MAAM,CAAC;iBAChE,IAAI,QAAQ,KAAK,UAAU;gBAAE,OAAO,QAAQ,CAAC;YAElD,OAAO,aAAa,CAAC;QACvB,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<main class="blog-page">
<MyTransition>
<component :is="componentName" v-if="componentName" />
</MyTransition>
<MyTransition :delay="0.24">
<ArticleList v-if="showArticles" :key="$route.path" />
</MyTransition>
</main>
</template>
<script src="./BlogPage" />
<style lang="stylus">
.blog-page
max-width 780px
flex 1
@media (min-width $MQMobile)
margin 0 15px
.article-title
font-size 1.8rem
margin 10px 15px
</style>
import MediaLinks from "@theme/components/MediaLinks.vue";
import type { BlogOptions } from "@theme/types";
declare const _default: import("vue/types/vue").ExtendedVue<{
$timelineItems: import("@mr-hope/vuepress-types").PageComputed[];
$timeline: import("@theme/mixins/timeline").TimelineItem[];
} & Record<never, any> & MediaLinks, unknown, {
navigate(url: string): void;
jumpIntro(): void;
}, {
blogConfig: BlogOptions;
bloggerName: string;
bloggerAvatar: string;
hasIntro: boolean;
hintAttr: string;
i18n: {
article: string;
articleList: string;
category: string;
tag: string;
timeline: string;
timelineText: string;
allText: string;
intro: string;
star: string;
slides: string;
encrypt: string;
};
articleNumber: number;
}, Record<never, any>>;
export default _default;
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
import MediaLinks from "@theme/components/MediaLinks.vue";
import { timelineMixin } from "@theme/mixins/timeline";
import { filterArticle } from "@theme/utils/article";
import { navigate } from "@theme/utils/navigate";
export default timelineMixin.extend({
name: "BloggerInfo",
components: { MediaLinks },
computed: {
blogConfig() {
return this.$themeConfig.blog || {};
},
bloggerName() {
return (this.blogConfig.name ||
this.$themeConfig.author ||
this.$site.title ||
"");
},
bloggerAvatar() {
return this.blogConfig.avatar || this.$themeConfig.logo || "";
},
hasIntro() {
return Boolean(this.blogConfig.intro);
},
hintAttr() {
return this.hasIntro ? "aria-label" : "";
},
i18n() {
return this.$themeLocaleConfig.blog || getDefaultLocale().blog;
},
articleNumber() {
return filterArticle(this.$site.pages).length;
},
},
methods: {
navigate(url) {
navigate(url, this.$router, this.$route);
},
jumpIntro() {
if (this.hasIntro)
navigate(this.blogConfig.intro, this.$router, this.$route);
},
},
});
//# sourceMappingURL=BloggerInfo.js.map
\ No newline at end of file
{"version":3,"file":"BloggerInfo.js","sourceRoot":"","sources":["BloggerInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,UAAU,MAAM,kCAAkC,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAKjD,eAAe,aAAa,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,aAAa;IAEnB,UAAU,EAAE,EAAE,UAAU,EAAE;IAE1B,QAAQ,EAAE;QACR,UAAU;YACR,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,EAAE,CAAC;QACtC,CAAC;QAED,WAAW;YACT,OAAO,CACL,IAAI,CAAC,UAAU,CAAC,IAAI;gBACpB,IAAI,CAAC,YAAY,CAAC,MAAM;gBACxB,IAAI,CAAC,KAAK,CAAC,KAAK;gBAChB,EAAE,CACH,CAAC;QACJ,CAAC;QAED,aAAa;YACX,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,EAAE,CAAC;QAChE,CAAC;QAED,QAAQ;YACN,OAAO,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;QAED,QAAQ;YACN,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,CAAC;QAED,IAAI;YACF,OAAO,IAAI,CAAC,kBAAkB,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC,IAAI,CAAC;QACjE,CAAC;QAED,aAAa;YACX,OAAO,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QAChD,CAAC;KACF;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,GAAW;YAClB,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3C,CAAC;QAED,SAAS;YACP,IAAI,IAAI,CAAC,QAAQ;gBACf,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,KAAe,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACzE,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="blogger-info" vocab="https://schema.org/" typeof="Person">
<div
class="blogger"
:class="{ hasIntro }"
:[hintAttr]="hasIntro ? i18n.intro : ''"
:data-balloon-pos="hasIntro ? 'down' : ''"
role="navigation"
@click="jumpIntro"
>
<img
v-if="bloggerAvatar"
class="avatar"
:class="{ round: blogConfig.roundAvatar !== false }"
property="image"
alt="Blogger Avatar"
:src="$withBase(bloggerAvatar)"
/>
<div
v-if="bloggerName"
class="name"
property="name"
v-text="bloggerName"
/>
<meta
v-if="hasIntro"
property="url"
:content="$withBase(blogConfig.intro)"
/>
</div>
<div class="num-wrapper">
<div @click="navigate('/article/')">
<div class="num">{{ articleNumber }}</div>
<div>{{ i18n.article }}</div>
</div>
<div @click="navigate('/category/')">
<div class="num">{{ $category.list.length }}</div>
<div>{{ i18n.category }}</div>
</div>
<div @click="navigate('/tag/')">
<div class="num">{{ $tag.list.length }}</div>
<div>{{ i18n.tag }}</div>
</div>
<div @click="navigate('/timeline/')">
<div class="num">{{ $timelineItems.length }}</div>
<div>{{ i18n.timeline }}</div>
</div>
</div>
<MediaLinks />
</div>
</template>
<script src="./BloggerInfo" />
<style lang="stylus">
.blogger-info
.page &
background var(--bgcolor)
.blogger
padding 8px 0
text-align center
&.hasIntro
cursor pointer
.avatar
width 128px
height 128px
margin 0 auto
&.round
border-radius 50%
.name
margin 16px auto
font-size 22px
.num-wrapper
display flex
margin 0 auto 16px
width 80%
> div
width 25%
text-align center
font-size 13px
cursor pointer
&:hover
color var(--accent-color)
.num
position relative
margin-bottom 8px
font-weight 600
font-size 20px
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
capitalize: (word: string) => string;
clickCategory(path: string): void;
}, unknown, Record<never, any>>;
export default _default;
import Vue from "vue";
import { capitalize } from "@mr-hope/vuepress-shared";
import { navigate } from "@theme/utils/navigate";
export default Vue.extend({
name: "CategoryList",
methods: {
capitalize,
clickCategory(path) {
navigate(path, this.$router, this.$route);
},
},
});
//# sourceMappingURL=CategoryList.js.map
\ No newline at end of file
{"version":3,"file":"CategoryList.js","sourceRoot":"","sources":["CategoryList.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc;IAEpB,OAAO,EAAE;QACP,UAAU;QAEV,aAAa,CAAC,IAAY;YACxB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<ul class="category-list-wrapper">
<li
v-for="(category, index) in $category.list"
:key="category.path"
class="category"
:class="{
active: category.path === $route.path,
[`category${index % 9}`]: true,
}"
@click="clickCategory(category.path)"
>
{{ capitalize(category.name) }}
<span class="category-num">{{ category.pages.length }}</span>
</li>
</ul>
</template>
<script src="./CategoryList" />
<style lang="stylus">
$categoryListTextSize ?= 14px
.category-list-wrapper
position relative
z-index 2
padding-left 0
font-size $categoryListTextSize
font-family Arial, Helvetica, sans-serif
list-style none
.category
display inline-block
vertical-align middle
margin 0.3rem 0.6rem 0.8rem
padding 0.4rem 0.8rem
border-radius 0.25rem
box-shadow 0 1px 4px 0 var(--card-shadow-color)
color var(--dark-grey)
cursor pointer
overflow hidden
transition background-color 0.3s, color 0.3s
@media (max-width $MQMobileNarrow)
font-size 0.9rem
.category-num
display inline-block
min-width 1rem
height 1.2rem
margin-left 0.2em
padding 0 0.1rem
border-radius 0.6rem
color var(--white)
font-family sans-serif
font-size 0.7rem
line-height 1.2rem
text-align center
@require '~@mr-hope/vuepress-shared/styles/colors.styl'
for $color, $index in $colors
.category-list-wrapper .category{$index}
&, .theme-light &
background lighten($color, 90%)
&:hover
background lighten($color, 75%)
&.active
background var(--accent-color)
color var(--white)
.category-num
color var(--accent-color)
background var(--bgcolor-light)
.theme-dark &
background darken($color, 75%)
&:hover
background darken($color, 60%)
&.active
background var(--accent-color-d10)
color var(--white)
.category-num
background $color
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
navigate(link: string): void;
}, unknown, Record<never, any>>;
export default _default;
import Vue from "vue";
import ArticleIcon from "@theme/icons/ArticleIcon.vue";
import BookIcon from "@theme/icons/BookIcon.vue";
import LinkIcon from "@theme/icons/LinkIcon.vue";
import ProjectIcon from "@theme/icons/ProjectIcon.vue";
import { navigate } from "@theme/utils/navigate";
export default Vue.extend({
name: "ProjectList",
components: { ArticleIcon, BookIcon, LinkIcon, ProjectIcon },
methods: {
navigate(link) {
navigate(link, this.$router, this.$route);
},
},
});
//# sourceMappingURL=ProjectList.js.map
\ No newline at end of file
{"version":3,"file":"ProjectList.js","sourceRoot":"","sources":["ProjectList.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,WAAW,MAAM,8BAA8B,CAAC;AACvD,OAAO,QAAQ,MAAM,2BAA2B,CAAC;AACjD,OAAO,QAAQ,MAAM,2BAA2B,CAAC;AACjD,OAAO,WAAW,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,aAAa;IAEnB,UAAU,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE;IAE5D,OAAO,EAAE;QACP,QAAQ,CAAC,IAAY;YACnB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="project-list">
<div
v-for="(project, index) in $frontmatter.project || []"
:key="project.name"
class="project"
:class="`project${index % 9}`"
@click="navigate(project.link)"
>
<div
v-if="project.cover"
class="cover"
:style="`background: url(${$withBase(
project.cover
)}) center/cover no-repeat;`"
/>
<component :is="`${project.type}-icon`" />
<div class="name">{{ project.name }}</div>
<div class="desc">{{ project.desc }}</div>
</div>
</div>
</template>
<script src="./ProjectList" />
<style lang="stylus">
.project-list
position relative
display flex
justify-content flex-start
align-content stretch
align-items stretch
flex-wrap wrap
font-family sans-serif
margin-bottom 12px
z-index 2
.project
position relative
width calc(50% - 40px)
background-color var(--grey14)
border-radius 8px
margin 6px 8px
padding 12px
transition background-color 0.3s, transform 0.3s
@media (min-width $MQNarrow)
width calc(33% - 40px)
@media (min-width $MQWide)
width calc(25% - 40px)
&:hover
cursor pointer
transform scale(0.98, 0.98)
.cover
content ''
opacity 0.5
top 0
left 0
bottom 0
right 0
position absolute
z-index 1
.icon
position relative
z-index 2
float right
width 20px
height 20px
.name
position relative
z-index 2
color var(--grey3)
font-size 16px
font-weight 500
.desc
position relative
z-index 2
margin 6px 0
color var(--dark-grey)
font-size 13px
@require '~@mr-hope/vuepress-shared/styles/colors.styl'
for $color, $index in $colors
.project-list .project{$index}
&, .theme-light &
background lighten($color, 90%)
&:hover
background lighten($color, 75%)
.theme-dark &
background darken($color, 75%)
&:hover
background darken($color, 60%)
</style>
import Vue from "vue";
interface TagOption {
name: string;
path: string;
}
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
isActive(name: string): boolean;
clickTag(path: string): void;
}, {
tagList: TagOption[];
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
import { navigate } from "@theme/utils/navigate";
export default Vue.extend({
name: "TagList",
computed: {
tagList() {
return [
{
name:
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.$themeLocaleConfig.blog.allText ||
getDefaultLocale().blog.allText,
path: "/tag/",
},
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
...this.$tag.list,
];
},
},
methods: {
isActive(name) {
return (name ===
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
((this.$currentTag && this.$currentTag.key) ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.$themeLocaleConfig.blog.allText ||
getDefaultLocale().blog.allText));
},
clickTag(path) {
navigate(path, this.$router, this.$route);
},
},
});
//# sourceMappingURL=TagList.js.map
\ No newline at end of file
{"version":3,"file":"TagList.js","sourceRoot":"","sources":["TagList.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOjD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,SAAS;IAEf,QAAQ,EAAE;QACR,OAAO;YACL,OAAO;gBACL;oBACE,IAAI;oBACF,oEAAoE;oBACpE,IAAI,CAAC,kBAAkB,CAAC,IAAK,CAAC,OAAO;wBACrC,gBAAgB,EAAE,CAAC,IAAI,CAAC,OAAO;oBACjC,IAAI,EAAE,OAAO;iBACd;gBACD,sEAAsE;gBACtE,GAAI,IAAI,CAAC,IAAI,CAAC,IAAoB;aACnC,CAAC;QACJ,CAAC;KACF;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,IAAY;YACnB,OAAO,CACL,IAAI;gBACJ,sEAAsE;gBACtE,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC;oBACzC,oEAAoE;oBACpE,IAAI,CAAC,kBAAkB,CAAC,IAAK,CAAC,OAAO;oBACrC,gBAAgB,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CACnC,CAAC;QACJ,CAAC;QAED,QAAQ,CAAC,IAAY;YACnB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<ul class="tag-list-wrapper">
<li
v-for="(tag, index) in tagList"
:key="tag.path"
class="tag"
:class="{ active: isActive(tag.name), [`tag${index % 9}`]: true }"
@click="clickTag(tag.path)"
>
<div class="tag-name">{{ tag.name }}</div>
</li>
</ul>
</template>
<script src="./TagList" />
<style lang="stylus">
.tag-list-wrapper
position relative
z-index 2
padding-left 0
font-family Arial, Helvetica, sans-serif
list-style none
display flex
flex-wrap wrap
justify-content space-evenly
.tag
display inline-block
position relative
vertical-align middle
min-width 24px
margin 4px 6px
padding 3px 8px
border-radius 8px
box-shadow 0 1px 6px 0 var(--box-shadow-color)
color var(--white)
font-size 12px
text-align center
overflow hidden
cursor pointer
transition background-color 0.3s, transform 0.3s
&:hover
cursor pointer
&.active
transform scale(1.1, 1.1)
@require '~@mr-hope/vuepress-shared/styles/colors.styl'
for $color, $index in $colors
.tag-list-wrapper .tag{$index}
.theme-light &, &
background lighten($color, 10%)
&:hover, &.active
background darken($color, 5%)
.theme-dark &
background darken($color, 5%)
&:hover, &.active
background lighten($color, 10%)
</style>
import Anchor from "@theme/components/Anchor.vue";
import type { SidebarHeader } from "@theme/utils/groupHeader";
declare const _default: import("vue/types/vue").ExtendedVue<{
$timelineItems: import("@mr-hope/vuepress-types").PageComputed[];
$timeline: import("@theme/mixins/timeline").TimelineItem[];
} & Record<never, any> & Anchor, unknown, {
navigate(url: string): void;
}, {
hint: string;
anchorConfig: SidebarHeader[];
}, Record<never, any>>;
export default _default;
import Anchor from "@theme/components/Anchor.vue";
import MyTransition from "@theme/components/MyTransition.vue";
import { timelineMixin } from "@theme/mixins/timeline";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
export default timelineMixin.extend({
name: "Timeline",
components: { Anchor, MyTransition },
computed: {
hint() {
return ((this.$themeConfig.blog && this.$themeConfig.blog.timeline) ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.$themeLocaleConfig.blog.timelineText ||
getDefaultLocale().blog.timelineText);
},
anchorConfig() {
return this.$timeline.map((item) => ({
title: item.year.toString(),
level: 2,
slug: item.year.toString(),
}));
},
},
methods: {
navigate(url) {
void this.$router.push(url);
},
},
});
//# sourceMappingURL=Timeline.js.map
\ No newline at end of file
{"version":3,"file":"Timeline.js","sourceRoot":"","sources":["Timeline.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,8BAA8B,CAAC;AAClD,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAI5D,eAAe,aAAa,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE;IAEpC,QAAQ,EAAE;QACR,IAAI;YACF,OAAO,CACL,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAC3D,oEAAoE;gBACpE,IAAI,CAAC,kBAAkB,CAAC,IAAK,CAAC,YAAY;gBAC1C,gBAAgB,EAAE,CAAC,IAAI,CAAC,YAAY,CACrC,CAAC;QACJ,CAAC;QAED,YAAY;YACV,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACnC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAC3B,KAAK,EAAE,CAAC;gBACR,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;aAC3B,CAAC,CAAC,CAAC;QACN,CAAC;KACF;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,GAAW;YAClB,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="timeline-wrapper">
<ul class="timeline-content">
<MyTransition>
<li class="desc">{{ hint }}</li>
</MyTransition>
<Anchor :items="anchorConfig" />
<MyTransition
v-for="(item, index) in $timeline"
:key="index"
:delay="0.08 * (index + 1)"
>
<li>
<h3 :id="item.year" class="year">
<span>{{ item.year }}</span>
</h3>
<ul class="year-wrapper">
<li
v-for="(article, articleIndex) in item.articles"
:key="articleIndex"
>
<span class="date">{{ article.frontmatter.parsedDate }}</span>
<span class="title" @click="navigate(article.path)">
{{ article.title }}
</span>
</li>
</ul>
</li>
</MyTransition>
</ul>
</div>
</template>
<script src="./Timeline" />
<style lang="stylus">
.timeline-wrapper
max-width 740px
margin 0 auto
padding 40px 0
--dot-color #fff
--dot-bar-color #eaecef
--dot-border-color #ddd
.theme-dark &
--dot-color #444
--dot-bar-color #333
--dot-border-color #555
#anchor
left unset
right 0
min-width 0
.anchor-wrapper
position relative
z-index 10
.timeline-content
box-sizing border-box
position relative
padding-left 76px
list-style none
&::after
content ' '
position absolute
top 14px
left 64px
z-index -1
width 4px
height calc(100% - 38px)
margin-left -2px
background var(--dot-bar-color)
.desc
position relative
color var(--text-color)
font-size 18px
@media (min-width $MQNormal)
font-size 20px
&:before
content ' '
position absolute
z-index 2
left -12px
top 50%
width 8px
height 8px
margin-left -6px
margin-top -6px
background var(--dot-color)
border 2px solid var(--dot-border-color)
border-radius 50%
.year
margin-top 0.5rem - $navbarHeight
margin-bottom 0.5rem
padding-top: ($navbarHeight + 3rem)
color var(--text-color)
font-size 26px
font-weight 700
span
position relative
&:before
content ' '
position absolute
z-index 2
left -12px
top 50%
width 8px
height 8px
margin-left -6px
margin-top -6px
background var(--dot-color)
border 2px solid var(--dot-border-color)
border-radius 50%
.year-wrapper
padding-left 0 !important
li
position relative
display flex
padding 30px 0 10px
border-bottom 1px dashed var(--border-color)
list-style none
&:hover
cursor pointer
.date
font-size 16px
transition font-size 0.3s ease-out
&::before
background-color var(--bgcolor)
border-color var(--accent-color)
.title
color var(--accent-color)
font-size 18px
transition font-size 0.3s ease-out
.date
position absolute
right calc(100% + 24px)
text-align right
width 40px
font-size 14px
line-height 30px
&::before
content ' '
position absolute
z-index 2
right -16px
top 50%
width 6px
height 6px
margin-left -6px
margin-top -6px
background var(--dot-color)
border 2px solid var(--dot-border-color)
border-radius 50%
.title
position relative
font-size 16px
line-height 30px
@media (max-width $MQMobile)
.timeline-wrapper
margin 0 1.2rem
</style>
D
import MyTransition from "@theme/components/MyTransition.vue";
declare const _default: import("vue/types/vue").ExtendedVue<{
$timelineItems: import("@mr-hope/vuepress-types").PageComputed[];
$timeline: import("@theme/mixins/timeline").TimelineItem[];
} & Record<never, any> & MyTransition, unknown, {
navigate(url: string): void;
}, {
hint: string;
}, Record<never, any>>;
export default _default;
import MyTransition from "@theme/components/MyTransition.vue";
import TimeIcon from "@mr-hope/vuepress-plugin-comment/lib/client/icons/TimeIcon.vue";
import { timelineMixin } from "@theme/mixins/timeline";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
export default timelineMixin.extend({
name: "TimelineList",
components: { MyTransition, TimeIcon },
computed: {
hint() {
return (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.$themeLocaleConfig.blog.timeline ||
getDefaultLocale().blog.timeline);
},
},
methods: {
navigate(url) {
void this.$router.push(url);
},
},
});
//# sourceMappingURL=TimelineList.js.map
\ No newline at end of file
{"version":3,"file":"TimelineList.js","sourceRoot":"","sources":["TimelineList.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,QAAQ,MAAM,gEAAgE,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,eAAe,aAAa,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,cAAc;IAEpB,UAAU,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE;IAEtC,QAAQ,EAAE;QACR,IAAI;YACF,OAAO;YACL,oEAAoE;YACpE,IAAI,CAAC,kBAAkB,CAAC,IAAK,CAAC,QAAQ;gBACtC,gBAAgB,EAAE,CAAC,IAAI,CAAC,QAAQ,CACjC,CAAC;QACJ,CAAC;KACF;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,GAAW;YAClB,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="timeline-list-wrapper">
<div class="title" @click="navigate('/timeline/')">
<TimeIcon />
<span class="num">{{ $timelineItems.length }}</span>
{{ hint }}
</div>
<hr />
<div class="content">
<ul class="timeline-list">
<MyTransition
v-for="(item, index) in $timeline"
:key="index"
:delay="0.08 * (index + 1)"
>
<li>
<h3 class="year">{{ item.year }}</h3>
<ul class="year-wrapper">
<li
v-for="(article, articleIndex) in item.articles"
:key="articleIndex"
>
<span class="date">{{ article.frontmatter.parsedDate }}</span>
<span class="timeline-title" @click="navigate(article.path)">
{{ article.title }}
</span>
</li>
</ul>
</li>
</MyTransition>
</ul>
</div>
</div>
</template>
<script src="./TimelineList" />
<style lang="stylus">
.timeline-list-wrapper
padding 8px 0
--dot-color #fff
--dot-bar-color #eaecef
--dot-border-color #ddd
.theme-dark &
--dot-color #444
--dot-bar-color #333
--dot-border-color #555
.title
cursor pointer
.icon
position relative
bottom -0.125rem
width 16px
height 16px
margin 0 6px
.num
position relative
margin 0 2px
font-size 22px
.content
overflow-y scroll
max-height 80vh
&::-webkit-scrollbar-track-piece
background transparent
.timeline-list
position relative
margin 0 8px
box-sizing border-box
list-style none
&::after
content ' '
position absolute
top 14px
left 0
z-index -1
margin-left -2px
width 4px
height calc(100% - 14px)
background var(--dot-bar-color)
.year
position relative
margin 20px 0 0px
color var(--text-color)
font-size 20px
font-weight 700
&:before
content ' '
position absolute
z-index 2
left -20px
top 50%
margin-left -4px
margin-top -4px
width 8px
height 8px
background var(--dot-color)
border 1px solid var(--dot-border-color)
border-radius 50%
.year-wrapper
padding-left 0 !important
li
position relative
display flex
padding 12px 0 4px
list-style none
border-bottom 1px dashed var(--border-color)
&:hover
.date
color var(--accent-color)
&::before
background var(--accent-color)
border-color var(--dot-color)
.title
color var(--accent-color)
.date
width 36px
line-height 32px
display inline-block
vertical-align bottom
font-size 12px
&::before
content ' '
position absolute
left -19px
top 24px
width 6px
height 6px
margin-left -4px
background var(--dot-color)
border-radius 50%
border 1px solid var(--dot-border-color)
z-index 2
.timeline-title
line-height 32px
font-size 14px
cursor pointer
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
location: string;
}, unknown, {
copyright: string;
}, {
html: string;
lang: string;
}>;
export default _default;
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
{"version":3,"file":"Clipboard.js","sourceRoot":"","sources":["Clipboard.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,WAAW;IAEjB,KAAK,EAAE;QACL,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;QACnC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE;KACzC;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,EAAE;KACb,CAAC;IAEF,QAAQ,EAAE;QACR,SAAS;YACP,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YACrC,MAAM,OAAO,GAA2B;gBACtC,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,YACnB,MAAM,CAAC,CAAC,CAAC,OAAO,MAAM,OAAO,CAAC,CAAC,CAAC,EAClC,OAAO,IAAI,CAAC,QAAQ,EAAE;gBACtB,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,YACnB,MAAM,CAAC,CAAC,CAAC,gBAAgB,MAAM,KAAK,CAAC,CAAC,CAAC,EACzC,SAAS,IAAI,CAAC,QAAQ,EAAE;gBACxB,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,YACnB,MAAM,CAAC,CAAC,CAAC,iBAAiB,MAAM,KAAK,CAAC,CAAC,CAAC,EAC1C,aAAa,IAAI,CAAC,QAAQ,EAAE;aAC7B,CAAC;YAEF,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;KACF;IAED,OAAO;QACL,IAAI,OAAO,MAAM,KAAK,WAAW;YAC/B,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC/C,CAAC;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="copyright" />
</template>
<script src="./Clipboard" />
import Navbar from "@theme/components/Navbar/Navbar.vue";
import type { SidebarItem, SidebarHeader } from "@theme/utils/sidebar";
declare const _default: import("vue/types/vue").ExtendedVue<{
globalEncryptPassword: string;
} & {
checkGlobalPassword(globalPassword: string): void;
} & {
isGlobalEncrypted: boolean;
} & Record<never, any> & {
encryptOptions: import("../types").EncryptOptions;
} & Navbar, {
isSidebarOpen: boolean;
hideNavbar: boolean;
touchStart: {
clientX: number;
clientY: number;
};
}, {
/** Get scroll distance */
getScrollTop(): number;
toggleSidebar(to: boolean): void;
onTouchStart(event: TouchEvent): void;
onTouchEnd(event: TouchEvent): void;
getHeader(items: SidebarItem[]): SidebarHeader[];
}, {
enableNavbar: boolean;
enableSidebar: boolean;
sidebarItems: SidebarItem[];
pageClasses: unknown;
headers: SidebarHeader[];
enableAnchor: boolean;
}, {
navbar: boolean;
sidebar: boolean;
}>;
export default _default;
import { getSidebarItems } from "@theme/utils/sidebar";
import { globalEncryptMixin } from "@theme/mixins/globalEncrypt";
import Navbar from "@theme/components/Navbar/Navbar.vue";
import PageFooter from "@theme/components/PageFooter.vue";
import Password from "@theme/components/Password.vue";
import Sidebar from "@theme/components/Sidebar/Sidebar.vue";
import throttle from "lodash.throttle";
export default globalEncryptMixin.extend({
name: "Common",
components: {
Navbar,
PageFooter,
Password,
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
\ No newline at end of file
{"version":3,"file":"Common.js","sourceRoot":"","sources":["Common.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
<template>
<div
class="theme-container"
:class="pageClasses"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<Password v-if="isGlobalEncrypted" @password-verify="checkGlobalPassword" />
<!-- Content -->
<template v-else>
<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" />
<PageFooter :key="$route.path" />
</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";
interface ActionConfig {
text: string;
link: string;
}
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
navigate(link: string): void;
}, {
actionLinks: ActionConfig[];
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"Home.js","sourceRoot":"","sources":["Home.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,OAAO,MAAM,sCAAsC,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAOjD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM;IAEZ,UAAU,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE;IAErC,QAAQ,EAAE;QACR,WAAW;YACT,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YACrC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,OAAO,MAAwB,CAAC;YAC3D,OAAO,CAAC,MAAM,CAAmB,CAAC;QACpC,CAAC;KACF;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,IAAY;YACnB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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";
import type { BlogMedia } from "@theme/types";
interface MediaLink {
icon: string;
url: string;
}
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
mediaLink: false | Partial<Record<BlogMedia, string>>;
links: MediaLink[];
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import Baidu from "@theme/icons/media/Baidu.vue";
import Bitbucket from "@theme/icons/media/Bitbucket.vue";
import Dingding from "@theme/icons/media/Dingding.vue";
import Discord from "@theme/icons/media/Discord.vue";
import Dribbble from "@theme/icons/media/Dribbble.vue";
import Email from "@theme/icons/media/Email.vue";
import Evernote from "@theme/icons/media/Evernote.vue";
import Facebook from "@theme/icons/media/Facebook.vue";
import Flipboard from "@theme/icons/media/Flipboard.vue";
import Gitee from "@theme/icons/media/Gitee.vue";
import Github from "@theme/icons/media/Github.vue";
import Gitlab from "@theme/icons/media/Gitlab.vue";
import Gmail from "@theme/icons/media/Gmail.vue";
import Instagram from "@theme/icons/media/Instagram.vue";
import Lines from "@theme/icons/media/Lines.vue";
import Linkedin from "@theme/icons/media/Linkedin.vue";
import Pinterest from "@theme/icons/media/Pinterest.vue";
import Pocket from "@theme/icons/media/Pocket.vue";
import QQ from "@theme/icons/media/QQ.vue";
import Qzone from "@theme/icons/media/Qzone.vue";
import Reddit from "@theme/icons/media/Reddit.vue";
import Rss from "@theme/icons/media/Rss.vue";
import Steam from "@theme/icons/media/Steam.vue";
import Twitter from "@theme/icons/media/Twitter.vue";
import Wechat from "@theme/icons/media/Wechat.vue";
import Weibo from "@theme/icons/media/Weibo.vue";
import Whatsapp from "@theme/icons/media/Whatsapp.vue";
import Youtube from "@theme/icons/media/Youtube.vue";
import Zhihu from "@theme/icons/media/Zhihu.vue";
const medias = [
"Baidu",
"Bitbucket",
"Dingding",
"Discord",
"Dribbble",
"Email",
"Evernote",
"Facebook",
"Flipboard",
"Gitee",
"Github",
"Gitlab",
"Gmail",
"Instagram",
"Lines",
"Linkedin",
"Pinterest",
"Pocket",
"QQ",
"Qzone",
"Reddit",
"Rss",
"Steam",
"Twitter",
"Wechat",
"Weibo",
"Whatsapp",
"Youtube",
"Zhihu",
];
export default Vue.extend({
name: "MediaLinks",
components: {
Baidu,
Bitbucket,
Dingding,
Discord,
Dribbble,
Email,
Evernote,
Facebook,
Flipboard,
Gitee,
Github,
Gitlab,
Gmail,
Instagram,
Lines,
Linkedin,
Pinterest,
Pocket,
QQ,
Qzone,
Reddit,
Rss,
Steam,
Twitter,
Wechat,
Weibo,
Whatsapp,
Youtube,
Zhihu,
},
computed: {
mediaLink() {
const { medialink } = this.$frontmatter;
return medialink === false
? false
: typeof medialink === "object"
? medialink
: this.$themeConfig.blog
? this.$themeConfig.blog.links || false
: false;
},
links() {
if (this.mediaLink) {
const links = [];
for (const media in this.mediaLink)
if (medias.includes(media))
links.push({
icon: media,
url: this.mediaLink[media],
});
return links;
}
return [];
},
},
});
//# sourceMappingURL=MediaLinks.js.map
\ No newline at end of file
{"version":3,"file":"MediaLinks.js","sourceRoot":"","sources":["MediaLinks.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
<template>
<div v-if="mediaLink" class="media-links-wrapper">
<a
v-for="link in links"
:key="link.name"
class="media-link"
:href="link.url"
rel="noopener noreferrer"
target="_blank"
:aria-label="link.icon"
data-balloon-pos="up"
>
<span class="sr-only" v-text="link.icon" />
<Component :is="link.icon" />
</a>
</div>
</template>
<script src="./MediaLinks" />
<style lang="stylus">
.media-links-wrapper
display flex
justify-content center
flex-wrap wrap
margin 8px auto
.media-link
width 26px
height 26px
margin 4px
transform scale(1, 1)
transition transform 0.18s ease-out 0.18s
&:hover
cursor pointer
transform scale(1.2, 1.2)
&::after
--balloon-font-size 8px
padding 0.3em 0.6em
.icon
width 100%
height 100%
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
setStyle(items: HTMLElement): void;
unsetStyle(items: HTMLElement): void;
}, unknown, {
delay: number;
duration: number;
}>;
export default _default;
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
{"version":3,"file":"MyTransition.js","sourceRoot":"","sources":["MyTransition.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc;IAEpB,KAAK,EAAE;QACL,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;QACnC,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE;KAC1C;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,KAAkB;YACzB,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,aAAa,IAAI,CAAC,QAAQ,iBAAiB,IAAI,CAAC,KAAK,cAAc,IAAI,CAAC,QAAQ,iBAAiB,IAAI,CAAC,KAAK,GAAG,CAAC;YACxI,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,mBAAmB,CAAC;YAC5C,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;QAC5B,CAAC;QAED,UAAU,CAAC,KAAkB;YAC3B,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,eAAe,CAAC;YACxC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;QAC5B,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 type { NavBarConfigItem } from "@theme/utils/navbar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
open: boolean;
}, {
setOpen(value: boolean): void;
handleDropdown(event: MouseEvent): void;
isLastItemOfArray(item: NavBarConfigItem, array: NavBarConfigItem[]): boolean;
}, {
dropdownAriaLabel: string;
iconPrefix: string;
}, {
item: NavBarConfigItem;
}>;
export default _default;
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
{"version":3,"file":"DropdownLink.js","sourceRoot":"","sources":["DropdownLink.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,OAAO,MAAM,sCAAsC,CAAC;AAK3D,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc;IAEpB,UAAU,EAAE,EAAE,OAAO,EAAE;IAEvB,KAAK,EAAE;QACL,IAAI,EAAE,EAAE,IAAI,EAAE,MAAoC,EAAE,QAAQ,EAAE,IAAI,EAAE;KACrE;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,KAAK;KACZ,CAAC;IAEF,QAAQ,EAAE;QACR,iBAAiB;YACf,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAC/C,CAAC;QAED,UAAU;YACR,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAEzC,OAAO,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;QACxD,CAAC;KACF;IAED,KAAK,EAAE;QACL,MAAM;YACJ,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QACpB,CAAC;KACF;IAED,OAAO,EAAE;QACP,OAAO,CAAC,KAAc;YACpB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QACpB,CAAC;QAED,cAAc,CAAC,KAAiB;YAC9B,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;YAC1C,IAAI,cAAc;gBAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,CAAC;QAED,iBAAiB,CACf,IAAsB,EACtB,KAAyB;YAEzB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAElE,OAAO,KAAK,CAAC;QACf,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 type { NavBarConfigItem as ResovledNavbarConfigItem } from "@theme/utils/navbar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
dropdown: false | ResovledNavbarConfigItem;
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"LanguageDropdown.js","sourceRoot":"","sources":["LanguageDropdown.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
import Vue from "vue";
import type { NavBarConfigItem } from "@theme/utils/navbar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
focusoutAction(): void;
}, {
link: string;
iconPrefix: string;
active: boolean;
isNonHttpURI: boolean;
isBlankTarget: boolean;
isInternal: boolean;
target: string | null;
rel: string | null;
}, {
item: NavBarConfigItem;
}>;
export default _default;
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
{"version":3,"file":"NavLink.js","sourceRoot":"","sources":["NavLink.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAK3E,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,SAAS;IAEf,KAAK,EAAE;QACL,IAAI,EAAE,EAAE,IAAI,EAAE,MAAoC,EAAE,QAAQ,EAAE,IAAI,EAAE;KACrE;IAED,QAAQ,EAAE;QACR,IAAI;YACF,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAc,CAAC,CAAC;QAC7C,CAAC;QAED,UAAU;YACR,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAEzC,OAAO,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;QACxD,CAAC;QAED,MAAM;YACJ,oBAAoB;YACpB,IACE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO;gBACjB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAClC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC,IAAI,CACrC,CAAC;gBACJ,IAAI,CAAC,IAAI,KAAK,GAAG;gBAEjB,cAAc;gBACd,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;YAExC,kBAAkB;YAClB,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC;QAED,YAAY;YACV,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,CAAC;QAED,aAAa;YACX,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC;QAClC,CAAC;QAED,UAAU;YACR,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;QACvD,CAAC;QAED,MAAM;YACJ,IAAI,IAAI,CAAC,YAAY;gBAAE,OAAO,IAAI,CAAC;YAEnC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;YAE9C,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,CAAC;QAED,GAAG;YACD,IAAI,IAAI,CAAC,YAAY;gBAAE,OAAO,IAAI,CAAC;YACnC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,KAAK;gBAAE,OAAO,IAAI,CAAC;YACzC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;YAExC,OAAO,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3D,CAAC;KACF;IAED,OAAO,EAAE;QACP,cAAc;YACZ,sDAAsD;YACtD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACzB,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 type { NavBarConfigItem as ResovledNavbarConfigItem } from "@theme/utils/navbar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
navLinks: ResovledNavbarConfigItem[];
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"NavLinks.js","sourceRoot":"","sources":["NavLinks.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,YAAY,MAAM,2CAA2C,CAAC;AACrE,OAAO,OAAO,MAAM,sCAAsC,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE;QACV,YAAY;QACZ,OAAO;KACR;IAED,QAAQ,EAAE;QACR,QAAQ;YACN,MAAM,MAAM,GACV,IAAI,CAAC,kBAAkB,CAAC,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,IAAI,EAAE,CAAC;YAE7D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 type { AlgoliaOption } from "@mr-hope/vuepress-types";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
linksWrapMaxWidth: number;
isMobile: boolean;
}, unknown, {
siteBrandTitle: string;
canHideSiteBrandTitle: boolean;
siteBrandLogo: string;
siteBrandDarkLogo: string;
algoliaConfig: false | AlgoliaOption;
isAlgoliaSearch: boolean;
canHide: boolean;
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"Navbar.js","sourceRoot":"","sources":["Navbar.ts"],"names":[],"mappings":"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"}
\ 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";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
repoLink: string;
repoLabel: string;
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"RepoLink.js","sourceRoot":"","sources":["RepoLink.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,QAAQ,EAAE;QACR,QAAQ;YACN,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAEnC,IAAI,IAAI;gBACN,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,sBAAsB,IAAI,EAAE,CAAC;YAEtE,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,SAAS;YACP,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAAE,OAAO,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,YAAY,CAAC,SAAS;gBAAE,OAAO,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC;YAEpE,MAAM,CAAC,QAAQ,CAAC,GAAG,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpE,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;YAEpD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;gBACrD,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;gBAElC,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAAE,OAAO,QAAQ,CAAC;aAChE;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 Anchor from "@theme/components/Anchor.vue";
import type { PageHeader } from "@mr-hope/vuepress-types";
import type { SidebarItem } from "@theme/utils/sidebar";
declare const _default: import("vue/types/vue").ExtendedVue<{
encryptPasswordConfig: Record<string, string>;
} & {
checkPathPassword(password: string): void;
} & {
pathEncryptMatchKeys: string[];
isPathEncrypted: boolean;
} & Record<never, any> & {
encryptOptions: import("../types").EncryptOptions;
} & Anchor, {
password: string;
}, unknown, {
pagePassword: string;
pageDescrypted: boolean;
}, {
sidebarItems: SidebarItem[];
headers: PageHeader[];
}>;
export default _default;
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";
import Password from "@theme/components/Password.vue";
import { pathEncryptMixin } from "@theme/mixins/pathEncrypt";
export default pathEncryptMixin.extend({
name: "Page",
components: {
Anchor,
Comment,
MyTransition,
PageInfo,
PageMeta,
PageNav,
Password,
},
props: {
sidebarItems: {
type: Array,
default: () => [],
},
headers: {
type: Array,
default: () => [],
},
},
data: () => ({
password: "",
}),
computed: {
pagePassword() {
const { password } = this.$frontmatter;
return typeof password === "number"
? password.toString()
: typeof password === "string"
? password
: "";
},
pageDescrypted() {
return this.password === this.pagePassword;
},
},
});
//# sourceMappingURL=Page.js.map
\ No newline at end of file
{"version":3,"file":"Page.js","sourceRoot":"","sources":["Page.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,8BAA8B,CAAC;AAClD,OAAO,OAAO,MAAM,UAAU,CAAC;AAC/B,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,QAAQ,MAAM,0DAA0D,CAAC;AAChF,OAAO,QAAQ,MAAM,gCAAgC,CAAC;AACtD,OAAO,OAAO,MAAM,+BAA+B,CAAC;AACpD,OAAO,QAAQ,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAM7D,eAAe,gBAAgB,CAAC,MAAM,CAAC;IACrC,IAAI,EAAE,MAAM;IAEZ,UAAU,EAAE;QACV,MAAM;QACN,OAAO;QACP,YAAY;QACZ,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,QAAQ;KACT;IAED,KAAK,EAAE;QACL,YAAY,EAAE;YACZ,IAAI,EAAE,KAAgC;YACtC,OAAO,EAAE,GAAkB,EAAE,CAAC,EAAE;SACjC;QACD,OAAO,EAAE;YACP,IAAI,EAAE,KAA+B;YACrC,OAAO,EAAE,GAAiB,EAAE,CAAC,EAAE;SAChC;KACF;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,EAAE;KACb,CAAC;IAEF,QAAQ,EAAE;QACR,YAAY;YACV,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAEvC,OAAO,OAAO,QAAQ,KAAK,QAAQ;gBACjC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE;gBACrB,CAAC,CAAC,OAAO,QAAQ,KAAK,QAAQ;oBAC9B,CAAC,CAAC,QAAQ;oBACV,CAAC,CAAC,EAAE,CAAC;QACT,CAAC;QAED,cAAc;YACZ,OAAO,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY,CAAC;QAC7C,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<main class="page">
<BreadCrumb :key="$route.path" />
<slot name="top" />
<PageInfo :key="$route.path" />
<MyTransition v-if="pagePassword && !pageDescrypted" :delay="0.08" :disable="true">
<Password
:key="$route.path"
:page="true"
@password-verify="password = $event"
/>
</MyTransition>
<MyTransition v-else-if="isPathEncrypted" :delay="0.08" :disable="true">
<Password
:key="$route.path"
:page="true"
@password-verify="checkPathPassword"
/>
</MyTransition>
<template v-else>
<MyTransition :delay="0.12" :disable="true">
<Anchor :key="$route.path" />
</MyTransition>
<slot v-if="!pagePassword || pageDescrypted" name="content-top" />
<MyTransition v-show="!pagePassword || pageDescrypted" :delay="0.08" :disable="true">
<Content :key="$route.path" class="theme-default-content" />
</MyTransition>
<slot v-if="!pagePassword || pageDescrypted" 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-9KLVB8X0ME">
</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 type { HopeFooterConfig } from "../types";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
footerConfig: HopeFooterConfig;
enable: boolean;
footerContent: string | false;
copyright: string | false;
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import MediaLinks from "@theme/components/MediaLinks.vue";
export default Vue.extend({
name: "PageFooter",
components: { MediaLinks },
computed: {
footerConfig() {
return this.$themeLocaleConfig.footer || this.$themeConfig.footer || {};
},
enable() {
const { copyrightText, footer, medialink } = this.$page.frontmatter;
return (footer !== false &&
Boolean(copyrightText || footer || medialink || this.footerConfig.display));
},
footerContent() {
const { footer } = this.$page.frontmatter;
return footer === false
? false
: typeof footer === "string"
? footer
: this.footerConfig.content || "";
},
copyright() {
return this.$frontmatter.copyrightText === false
? false
: this.$frontmatter.copyrightText ||
(this.footerConfig.copyright === false
? false
: this.footerConfig.copyright ||
(this.$themeConfig.author
? `Copyright © ${new Date().getFullYear()} ${this.$themeConfig.author}`
: ""));
},
},
});
//# sourceMappingURL=PageFooter.js.map
\ No newline at end of file
{"version":3,"file":"PageFooter.js","sourceRoot":"","sources":["PageFooter.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,UAAU,MAAM,kCAAkC,CAAC;AAI1D,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,YAAY;IAElB,UAAU,EAAE,EAAE,UAAU,EAAE;IAE1B,QAAQ,EAAE;QACR,YAAY;YACV,OAAO,IAAI,CAAC,kBAAkB,CAAC,MAAM,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,IAAI,EAAE,CAAC;QAC1E,CAAC;QAED,MAAM;YACJ,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;YAEpE,OAAO,CACL,MAAM,KAAK,KAAK;gBAChB,OAAO,CACL,aAAa,IAAI,MAAM,IAAI,SAAS,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,CAClE,CACF,CAAC;QACJ,CAAC;QAED,aAAa;YACX,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;YAE1C,OAAO,MAAM,KAAK,KAAK;gBACrB,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,OAAO,MAAM,KAAK,QAAQ;oBAC5B,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,IAAI,EAAE,CAAC;QACtC,CAAC;QAED,SAAS;YACP,OAAO,IAAI,CAAC,YAAY,CAAC,aAAa,KAAK,KAAK;gBAC9C,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,aAAa;oBAC7B,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS,KAAK,KAAK;wBACpC,CAAC,CAAC,KAAK;wBACP,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,SAAS;4BAC3B,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM;gCACvB,CAAC,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,IACrC,IAAI,CAAC,YAAY,CAAC,MACpB,EAAE;gCACJ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrB,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<footer v-if="enable" class="footer-wrapper">
<MediaLinks v-if="!($frontmatter.home && $frontmatter.blog)" />
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="footerContent" class="footer" v-html="footerContent" />
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="copyright" class="copyright" v-html="copyright" />
</footer>
</template>
<script src="./PageFooter" />
<style lang="stylus">
.footer-wrapper
display flex
flex-wrap wrap
justify-content space-evenly
align-items center
padding 12px 30px
border-top 1px solid var(--border-color)
background var(--bgcolor)
color var(--dark-color, #666)
text-align center
@media (min-width $MQMobile)
.has-sidebar &
padding-left $sidebarWidth
border-left 30px solid transparent
& > div
@media (max-width $MQMobileNarrow)
width 100%
.media-links-wrapper
margin 0
.footer
margin 8px 16px
font-size 14px
.copyright
margin 6px 0
font-size 13px
.page:not(.not-found) + .footer-wrapper
margin-top -2rem
</style>
import Vue from "vue";
import type { GitContributor } from "@mr-hope/vuepress-plugin-git";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
createEditLink(): string;
}, {
i18n: {
contributor: string;
editLink: string;
updateTime: string;
};
contributors: GitContributor[];
contributorsText: string;
updateTime: string;
updateTimeText: string;
editLink: string | false;
editLinkText: string;
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"PageMeta.js","sourceRoot":"","sources":["PageMeta.ts"],"names":[],"mappings":"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"}
\ 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://optimismpbc.typeform.com/get-in-touch" 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 type { SidebarItem } from "@theme/utils/sidebar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
prev: false | SidebarItem;
next: false | SidebarItem;
}, {
sidebarItems: SidebarItem[];
}>;
export default _default;
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
{"version":3,"file":"PageNav.js","sourceRoot":"","sources":["PageNav.ts"],"names":[],"mappings":"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"}
\ 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";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
password: string;
hasTried: boolean;
}, {
verify(): void;
}, {
isMainPage: boolean;
encrypt: {
title: string;
errorHint: string;
};
}, {
page: boolean;
}>;
export default _default;
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
import Vue from "vue";
export default Vue.extend({
name: "Password",
props: {
page: { type: Boolean, default: false },
},
data: () => ({
password: "",
hasTried: false,
}),
computed: {
isMainPage() {
return this.$frontmatter.home === true;
},
encrypt() {
return this.$themeLocaleConfig.encrypt || getDefaultLocale().encrypt;
}
},
methods: {
verify() {
this.hasTried = false;
// eslint-disable-next-line vue/require-explicit-emits
this.$emit("password-verify", this.password);
void Vue.nextTick().then(() => {
this.hasTried = true;
});
},
},
});
//# sourceMappingURL=Password.js.map
\ No newline at end of file
{"version":3,"file":"Password.js","sourceRoot":"","sources":["Password.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,GAAG,MAAM,KAAK,CAAC;AAItB,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,KAAK,EAAE;QACL,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE;KACxC;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,KAAK;KAChB,CAAC;IAEF,QAAQ,EAAE;QACR,UAAU;YACR,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,IAAI,CAAC;QACzC,CAAC;QAED,OAAO;YACL,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,IAAI,gBAAgB,EAAE,CAAC,OAAO,CAAC;QACvE,CAAC;KACF;IAED,OAAO,EAAE;QACP,MAAM;YACJ,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,sDAAsD;YACtD,IAAI,CAAC,KAAK,CAAC,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAE7C,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC5B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACvB,CAAC,CAAC,CAAC;QACL,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="password" :class="{ expand: page || isMainPage }">
<div class="hint" :class="{ hasTried }">
{{ hasTried ? encrypt.errorHint : encrypt.title }}
</div>
<div class="input">
<input v-model="password" type="password" @keypress.enter="verify" />
<button @click="verify">OK</button>
</div>
</div>
</template>
<script src="./Password" />
<style lang="stylus">
.password
background var(--bg-color)
height 90vh - $navbarHeight
margin-top $navbarHeight
text-align center
padding-left $sidebarWidth
display flex
flex-direction column
justify-content center
align-items center
@media (max-width $MQNarrow)
height 90vh - $navbarMobileHeight
margin-top $navbarMobileHeight
padding-left $mobileSidebarWidth
@media (max-width $MQMobile)
padding-left 0
&.expand
padding-left 0 !important
margin-top 0
height 400px
.hint
margin-bottom 20px
font-family Arial, Helvetica, sans-serif
font-weight 600
font-size 22px
line-height 2
&.hasTried
color red
animation-name shake
animation-duration 0.5s
animation-timing-function ease-out
animation-fill-mode both
.input
width 80%
max-width 600px
display flex
justify-content center
input
flex 1
width calc(100% - 60px)
padding-left 20px
color var(--black) !important
background var(--bgcolor) !important
border 2px solid var(--accent-color)
border-radius 22px 0 0 22px
font-size 20px
letter-spacing 0.5em
line-height 2
outline none
button
width 70px
padding-right 10px
background var(--accent-color)
color var(--bgcolor)
border-width 0
border-radius 0 22px 22px 0
font-size 20px
line-height 2
outline none
&:hover
background lighten($accentColor, 15%)
@keyframes shake
0%, 100%
transform translateX(0)
10%
transform translateX(-9px)
20%
transform translateX(8px)
30%
transform translateX(-7px)
40%
transform translateX(6px)
50%
transform translateX(-5px)
60%
transform translateX(4px)
70%
transform translateX(-3px)
80%
transform translateX(2px)
90%
transform translateX(-1px)
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
setHeight(items: HTMLElement): void;
unsetHeight(items: HTMLElement): void;
}, unknown, Record<never, any>>;
export default _default;
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
{"version":3,"file":"DropdownTransition.js","sourceRoot":"","sources":["DropdownTransition.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,oBAAoB;IAE1B,OAAO,EAAE;QACP,SAAS,CAAC,KAAkB;YAC1B,uDAAuD;YACvD,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,IAAI,CAAC;QACjD,CAAC;QAED,WAAW,CAAC,KAAkB;YAC5B,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;QAC1B,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 type { BlogOptions } from "@theme/types";
import type { SidebarItem } from "@theme/utils/sidebar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
blogConfig: BlogOptions;
sidebarDisplay: "always" | "mobile" | "none";
}, {
items: SidebarItem[];
}>;
export default _default;
import Vue from "vue";
import BlogInfo from "@BlogInfo";
import BloggerInfo from "@BloggerInfo";
import SidebarNavLinks from "@theme/components/Sidebar/SidebarNavLinks.vue";
import SidebarLinks from "@theme/components/Sidebar/SidebarLinks.vue";
export default Vue.extend({
name: "Sidebar",
components: {
BlogInfo,
BloggerInfo,
SidebarLinks,
SidebarNavLinks,
},
props: {
items: { type: Array, required: true },
},
computed: {
blogConfig() {
return this.$themeConfig.blog || {};
},
sidebarDisplay() {
return this.blogConfig.sidebarDisplay || "none";
},
},
});
//# sourceMappingURL=Sidebar.js.map
\ No newline at end of file
{"version":3,"file":"Sidebar.js","sourceRoot":"","sources":["Sidebar.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,eAAe,MAAM,+CAA+C,CAAC;AAC5E,OAAO,YAAY,MAAM,4CAA4C,CAAC;AAMtE,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,SAAS;IAEf,UAAU,EAAE;QACV,QAAQ;QACR,WAAW;QACX,YAAY;QACZ,eAAe;KAChB;IAED,KAAK,EAAE;QACL,KAAK,EAAE,EAAE,IAAI,EAAE,KAAgC,EAAE,QAAQ,EAAE,IAAI,EAAE;KAClE;IAED,QAAQ,EAAE;QACR,UAAU;YACR,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,EAAE,CAAC;QACtC,CAAC;QAED,cAAc;YACZ,OAAO,IAAI,CAAC,UAAU,CAAC,cAAc,IAAI,MAAM,CAAC;QAClD,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<aside class="sidebar">
<template v-if="sidebarDisplay !== 'none'">
<BloggerInfo :class="{ mobile: sidebarDisplay === 'mobile' }" />
<hr />
</template>
<slot name="top" />
<SidebarNavLinks />
<slot name="center" />
<SidebarLinks :depth="0" :items="items" />
<slot name="bottom" />
<BlogInfo v-if="$frontmatter.blog && $themeConfig.blog !== false" />
</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 type { NavBarConfigItem } from "@theme/utils/navbar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
open: boolean;
}, {
setOpen(value: boolean): void;
isLastItemOfArray(item: NavBarConfigItem, array: NavBarConfigItem[]): boolean;
}, {
dropdownAriaLabel: string;
iconPrefix: string;
}, {
item: NavBarConfigItem;
}>;
export default _default;
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
{"version":3,"file":"SidebarDropdownLink.js","sourceRoot":"","sources":["SidebarDropdownLink.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,kBAAkB,MAAM,kDAAkD,CAAC;AAClF,OAAO,OAAO,MAAM,sCAAsC,CAAC;AAK3D,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,qBAAqB;IAE3B,UAAU,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE;IAE3C,KAAK,EAAE;QACL,IAAI,EAAE,EAAE,IAAI,EAAE,MAAoC,EAAE,QAAQ,EAAE,IAAI,EAAE;KACrE;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,IAAI,EAAE,KAAK;KACZ,CAAC;IAEF,QAAQ,EAAE;QACR,iBAAiB;YACf,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAC/C,CAAC;QAED,UAAU;YACR,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAEzC,OAAO,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;QACxD,CAAC;KACF;IAED,KAAK,EAAE;QACL,MAAM;YACJ,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QACpB,CAAC;KACF;IAED,OAAO,EAAE;QACP,OAAO,CAAC,KAAc;YACpB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QACpB,CAAC;QAED,iBAAiB,CACf,IAAsB,EACtB,KAAyB;YAEzB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAElE,OAAO,KAAK,CAAC;QACf,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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>
import Vue from "vue";
import type { SidebarAutoItem, SidebarGroupItem } from "@theme/utils/sidebar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
getIcon(icon: string | undefined): string;
isActive: (route: import("vue-router").Route, path: string) => boolean;
}, unknown, {
item: SidebarAutoItem | SidebarGroupItem;
open: boolean;
depth: number;
}>;
export default _default;
/* 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
{"version":3,"file":"SidebarGroup.js","sourceRoot":"","sources":["SidebarGroup.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,kBAAkB,MAAM,kDAAkD,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAK7C,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc;IAEpB,UAAU,EAAE,EAAE,kBAAkB,EAAE;IAElC,KAAK,EAAE;QACL,IAAI,EAAE;YACJ,IAAI,EAAE,MAAsD;YAC5D,QAAQ,EAAE,IAAI;SACf;QACD,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;QACvB,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;KACxC;IAED,YAAY;QACV,2BAA2B;QAC3B,IAAI,CAAC,QAAQ,CAAC,UAAW,CAAC,YAAY;YACpC,8DAA8D;YAC9D,OAAO,CAAC,4CAA4C,CAAC,CAAC,OAAO,CAAC;IAClE,CAAC;IAED,OAAO,EAAE;QACP,OAAO,CAAC,IAAwB;YAC9B,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;YAEzC,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,KAAK,KAAK,IAAI,IAAI;gBACpD,CAAC,CAAC,GAAG,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,OAAO,GAAG,IAAI,EAAE;gBAC5D,CAAC,CAAC,EAAE,CAAC;QACT,CAAC;QAED,QAAQ;KACT;CACF,CAAC,CAAC"}
\ 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";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, unknown, Record<never, any>>;
export default _default;
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
{"version":3,"file":"SidebarLink.js","sourceRoot":"","sources":["SidebarLink.ts"],"names":[],"mappings":"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"}
\ 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 type { PageComputed } from "@mr-hope/vuepress-types";
import type { SidebarItem } from "@theme/utils/sidebar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
openGroupIndex: number;
}, {
refreshIndex(): void;
toggleGroup(index: number): void;
isActive(page: PageComputed): boolean;
}, unknown, {
items: SidebarItem[];
depth: number;
}>;
export default _default;
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
{"version":3,"file":"SidebarLinks.js","sourceRoot":"","sources":["SidebarLinks.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,YAAY,MAAM,4CAA4C,CAAC;AACtE,OAAO,WAAW,MAAM,2CAA2C,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAO7C,MAAM,kBAAkB,GAAG,CAAC,KAAY,EAAE,IAAiB,EAAW,EAAE;IACtE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QACvB,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAsC,EAAE,EAAE;YACnE,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAEpE,OAAO,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IAEL,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,KAAY,EAAE,KAAoB,EAAU,EAAE;IAC3E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QACnC,IAAI,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IAEpD,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC,CAAC;AAEF,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc;IAEpB,UAAU,EAAE,EAAE,YAAY,EAAE,WAAW,EAAE;IAEzC,KAAK,EAAE;QACL,KAAK,EAAE;YACL,IAAI,EAAE,KAAgC;YACtC,QAAQ,EAAE,IAAI;SACf;QACD,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;KACxC;IAED,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,cAAc,EAAE,CAAC;KAClB,CAAC;IAEF,KAAK,EAAE;QACL,MAAM;YACJ,IAAI,CAAC,YAAY,EAAE,CAAC;QACtB,CAAC;KACF;IAED,OAAO;QACL,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED,OAAO,EAAE;QACP,YAAY;YACV,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YAE7D,IAAI,KAAK,GAAG,CAAC,CAAC;gBAAE,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;QAC9C,CAAC;QAED,WAAW,CAAC,KAAa;YACvB,IAAI,CAAC,cAAc,GAAG,KAAK,KAAK,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACnE,CAAC;QAED,QAAQ,CAAC,IAAkB;YACzB,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACjD,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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 type { NavBarConfigItem as ResovledNavbarConfigItem } from "@theme/utils/navbar";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
navLinks: ResovledNavbarConfigItem[];
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"SidebarNavLinks.js","sourceRoot":"","sources":["SidebarNavLinks.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,QAAQ,MAAM,uCAAuC,CAAC;AAC7D,OAAO,mBAAmB,MAAM,mDAAmD,CAAC;AACpF,OAAO,OAAO,MAAM,sCAAsC,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,iBAAiB;IAEvB,UAAU,EAAE;QACV,QAAQ;QACR,mBAAmB;QACnB,OAAO;KACR;IAED,QAAQ,EAAE;QACR,QAAQ;YACN,MAAM,MAAM,GACV,IAAI,CAAC,kBAAkB,CAAC,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,IAAI,EAAE,CAAC;YAE7D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
darkmode: "auto" | "off" | "on";
}, {
setDarkmode(status: "on" | "off" | "auto"): void;
toggleDarkmode(isDarkmode: boolean): void;
}, {
darkmodeConfig: "auto" | "auto-switch" | "switch" | "disable";
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"DarkmodeSwitch.js","sourceRoot":"","sources":["DarkmodeSwitch.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
<template>
<div class="darkmode-switch">
<template v-if="darkmodeConfig === 'auto-switch'">
<div
class="item day"
:class="{ active: darkmode === 'off' }"
@click="setDarkmode('off')"
>
<LightIcon />
</div>
<div
class="item auto"
:class="{ active: darkmode === 'auto' }"
@click="setDarkmode('auto')"
>
<AutoIcon />
</div>
<div
class="item night"
:class="{ active: darkmode === 'on' }"
@click="setDarkmode('on')"
>
<DarkIcon />
</div>
</template>
<div v-else-if="darkmodeConfig === 'switch'" class="switch">
<input
id="switch"
class="switch-input"
type="checkbox"
:checked="darkmode !== 'on'"
@click="setDarkmode(darkmode === 'on' ? 'off' : 'on')"
/>
<label class="label" for="switch">
<span class="label-content" />
</label>
</div>
</div>
</template>
<script src="./DarkmodeSwitch" />
<style lang="stylus">
@keyframes starry_star
50%
background rgba(255, 255, 255, 0.1)
box-shadow #fff 7.5px -0.75px 0 0, #fff 3px 2.5px 0 -0.25px, rgba(255, 255, 255, 0.1) 9.5px 4.5px 0 0.25px, #fff 8px 8.5px 0 0, rgba(255, 255, 255, 0.1) 5px 6px 0 -0.375px, #fff 1.25px 9.5px 0 0.25px
@keyframes bounceIn
0%
opacity 0
transform scale(0.3)
50%
opacity 100
transform scale(1.1)
55%
transform scale(1.1)
75%
transform scale(0.9)
100%
opacity 100
transform scale(1)
.darkmode-switch
display flex
height 22px
&:hover
cursor pointer
.item
padding 2px
border 1px solid var(--accent-color)
border-left none
line-height 1
&:first-child
border-left 1px solid var(--accent-color)
&.day
border-radius 4px 0 0 4px
&.night
border-radius 0 4px 4px 0
.icon
width 16px
height 16px
color var(--accent-color)
&.active
background var(--accent-color)
&:hover
cursor default
.icon
color var(--white)
.switch
display block
text-align center
user-select none
.label
display block
position relative
width 31.25px
height 17.5px
margin 0 auto
border-radius 17.5px
border 1px solid #1c1c1c
background #3c4145
font-size 1.4em
transition all 250ms ease-in
&:hover
cursor pointer
&:before
content ''
display block
position absolute
top 0.5px
left 1px
width 14px
height 14px
border 1.25px solid #e3e3c7
border-radius 50%
background #fff
transition all 250ms ease-in
&:after
content ''
display block
position absolute
top 62%
left 9.75px
z-index 10
width 2.8px
height 2.8px
opacity 0
background #fff
border-radius 50%
box-shadow #fff 0 0, #fff 0.75px 0, #fff 1.5px 0, #fff 2.25px 0, #fff 2.75px 0, #fff 3.5px 0, #fff 4px 0, #fff 5.25px -0.25px 0 0.25px, #fff 4px -1.75px 0 -0.5px, #fff 1.75px -1.75px 0 0.25px, #d3d3d3 0 0 0 1px, #d3d3d3 1.5px 0 0 1px, #d3d3d3 2.75px 0 0 1px, #d3d3d3 4px 0 0 1px, #d3d3d3 5.25px -0.25px 0 1.25px, #d3d3d3 4px -1.75px 0 0.25px, #d3d3d3 1.75px -1.75px 0 1.25px
transition opacity 100ms ease-in
.label-content
display block
position absolute
top 2.25px
left 52.5%
z-index 20
width 1px
height 1px
border-radius 50%
background #fff
box-shadow rgba(255, 255, 255, 0.1) 7.5px -0.75px 0 0, rgba(255, 255, 255, 0.1) 3px 2.5px 0 -0.25px, #fff 9.5px 4.5px 0 0.25px, rgba(255, 255, 255, 0.1) 8px 8.5px 0 0, #fff 5px 6px 0 0.375px, rgba(255, 255, 255, 0.1) 1.25px 9.5px 0 0.25px
animation starry_star 5s ease-in-out infinite
transition all 250ms ease-in
&:before
content ''
display block
position absolute
top -0.5px
left -6.25px
width 4.5px
height 4.5px
background #fff
border-radius 50%
border 1.25px solid #e3e3c7
box-shadow #e3e3c7 -7px 0 0 -0.75px, #e3e3c7 -2px 6px 0 -0.5px
transform-origin -1.5px 130%
transition all 250ms ease-in
.switch-input
display none
transition all 250ms ease-in
&:checked + .label
background #9ee3fb
border 1px solid #86c3d7
&:before
left 13.75px
background #ffdf6d
border 1.25px solid #e1c348
&:after
opacity 100
animation bounceIn 0.6s ease-in-out 0.1s
animation-fill-mode backwards
& > .label-content
opacity 0
box-shadow rgba(255, 255, 255, 0.1) 7.5px -0.75px 0 -1px, rgba(255, 255, 255, 0.1) 3px 2.5px 0 -1.25px, #fff 9.5px 4.5px 0 -0.75px, rgba(255, 255, 255, 0.1) 8px 8.5px 0 -1px, #fff 5px 6px 0 -1.375px, rgba(255, 255, 255, 0.1) 1.25px 9.5px 0 -0.75px
animation none
&:before
left 6.25px
transform rotate(70deg)
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
showMenu: boolean;
}, {
clickOutside(): void;
}, unknown, Record<never, any>>;
export default _default;
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
{"version":3,"file":"ThemeColor.js","sourceRoot":"","sources":["ThemeColor.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,YAAY,MAAM,4BAA4B,CAAC;AACtD,OAAO,YAAY,MAAM,0CAA0C,CAAC;AAEpE,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,YAAY;IAElB,UAAU,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;IAE7C,UAAU,EAAE,EAAE,YAAY,EAAE;IAE5B,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,KAAK;KAChB,CAAC;IAEF,OAAO,EAAE;QACP,YAAY;YACV,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;KACF;CACF,CAAC,CAAC"}
\ 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";
interface ThemeColor {
/** Color list */
list: string[];
/** Color picker */
picker: Record<string, string>;
}
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
themeColor: ThemeColor;
isDarkmode: boolean;
}, {
setTheme(theme?: string | undefined): void;
}, {
text: {
themeColor: string;
themeMode: string;
};
themeColorEnabled: boolean;
switchEnabled: boolean;
}, Record<never, any>>;
export default _default;
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
{"version":3,"file":"ThemeOptions.js","sourceRoot":"","sources":["ThemeOptions.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,cAAc,MAAM,4CAA4C,CAAC;AAIxE,MAAM,kBAAkB,GAA2B;IACjD,GAAG,EAAE,SAAS;IACd,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,MAAM,EAAE,SAAS;IACjB,MAAM,EAAE,SAAS;CAClB,CAAC;AASF,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,cAAc;IAEpB,UAAU,EAAE,EAAE,cAAc,EAAE;IAE9B,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,UAAU,EAAE,EAAgB;QAE5B,UAAU,EAAE,KAAK;KAClB,CAAC;IAEF,QAAQ,EAAE;QACR,IAAI;YACF,OAAO,CACL,IAAI,CAAC,kBAAkB,CAAC,UAAU,IAAI,gBAAgB,EAAE,CAAC,UAAU,CACpE,CAAC;QACJ,CAAC;QAED,iBAAiB;YACf,OAAO,IAAI,CAAC,YAAY,CAAC,UAAU,KAAK,KAAK,CAAC;QAChD,CAAC;QAED,aAAa;YACX,OAAO,CACL,IAAI,CAAC,YAAY,CAAC,QAAQ,KAAK,SAAS;gBACxC,IAAI,CAAC,YAAY,CAAC,QAAQ,KAAK,MAAM,CACtC,CAAC;QACJ,CAAC;KACF;IAED,OAAO;QACL,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAE5C,IAAI,CAAC,UAAU,GAAG;YAChB,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU;gBAChC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;gBAC3C,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC;YACnC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU,IAAI,kBAAkB;SAC3D,CAAC;QAEF,IAAI,KAAK;YAAE,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,EAAE;QACP,QAAQ,CAAC,KAAc;YACrB,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC;YACxC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CACrC,CAAC,UAAU,EAAE,EAAE,CAAC,SAAS,UAAU,EAAE,CACtC,CAAC;YAEF,IAAI,CAAC,KAAK,EAAE;gBACV,YAAY,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;gBACjC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;gBAE1B,OAAO;aACR;YAED,OAAO,CAAC,MAAM,CACZ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,KAAK,SAAS,KAAK,EAAE,CAAC,CAClE,CAAC;YAEF,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;YAC9B,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="theme-options">
<ul v-if="themeColorEnabled" class="themecolor-select">
<label for="themecolor-select" v-text="`${text.themeColor}:`" />
<li>
<span class="default-theme" @click="setTheme()" />
</li>
<li v-for="color in themeColor.list" :key="color">
<span
:style="{ background: themeColor.picker[color] }"
@click="setTheme(color)"
/>
</li>
</ul>
<div v-if="switchEnabled" class="darkmode-toggle">
<label class="desc" for="darkmode-toggle" v-text="`${text.themeMode}:`" />
<DarkmodeSwitch />
<ScreenFull />
</div>
</div>
</template>
<script src="./ThemeOptions" />
<style lang="stylus">
.theme-options
font-size 14px
.themecolor-select
display flex
justify-content space-around
label
padding-right 8px
li
&:first-child
margin-right 8px
span
vertical-align middle
width 15px
height 15px
margin 0 2px
border-radius 2px
&.default-theme
background $accentColor // must be fixed to the original accent-color
.darkmode-toggle
display flex
align-items center
margin-top 8px
.desc
padding-right 8px
line-height 1.5
.full-screen, .cancel-full-screen
margin-left 0.5em
</style>
<template>
<svg class="icon article-icon" viewBox="0 0 1024 1024">
<path
d="M853.333 938.667H170.667A42.667 42.667 0 0 1 128 896V128a42.667 42.667 0 0 1 42.667-42.667h682.666A42.667 42.667 0 0 1 896 128v768a42.667 42.667 0 0 1-42.667 42.667zm-42.666-85.334V170.667H213.333v682.666h597.334zM298.667 256h170.666v170.667H298.667V256zm0 256h426.666v85.333H298.667V512zm0 170.667h426.666V768H298.667v-85.333zm256-384h170.666V384H554.667v-85.333z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon auto-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M460.864 539.072H564.8L510.592 376l-49.728 163.072zM872 362.368V149.504H659.648L510.528 0l-149.12 149.504H149.12v212.928L0 511.872l149.12 149.504v212.928h212.352l149.12 149.504 149.12-149.504h212.352V661.376l149.12-149.504L872 362.368zM614.464 693.12l-31.616-90.624H438.272l-31.616 90.624h-85.888l144.576-407.68h90.368l144.576 407.68h-85.824zm0 0"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon book-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M256 853.333h426.667A85.333 85.333 0 0 0 768 768V256a85.333 85.333 0 0 0-85.333-85.333H469.333a42.667 42.667 0 0 1 0-85.334h213.334A170.667 170.667 0 0 1 853.333 256v512a170.667 170.667 0 0 1-170.666 170.667H213.333A42.667 42.667 0 0 1 170.667 896V128a42.667 42.667 0 0 1 42.666-42.667h128A42.667 42.667 0 0 1 384 128v304.256l61.653-41.088a42.667 42.667 0 0 1 47.36 0l61.654 41.045V256A42.667 42.667 0 0 1 640 256v256a42.667 42.667 0 0 1-66.347 35.499l-104.32-69.547-104.32 69.547A42.667 42.667 0 0 1 298.667 512V170.667H256v682.666z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon dark-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M935.539 630.402c-11.43-11.432-28.674-14.739-43.531-8.354-46.734 20.103-96.363 30.297-147.508 30.297-99.59 0-193.221-38.784-263.64-109.203-108.637-108.637-139.61-270.022-78.908-411.148a39.497 39.497 0 0 0-51.886-51.887c-52.637 22.64-100.017 54.81-140.826 95.616-85.346 85.346-132.346 198.821-132.346 319.52 0 120.7 47.001 234.172 132.347 319.519S408.063 947.11 528.76 947.11c120.7 0 234.172-47.003 319.52-132.351 40.809-40.81 72.978-88.19 95.616-140.826a39.497 39.497 0 0 0-8.356-43.532z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon edit-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M117.953 696.992 64.306 959.696l265.931-49.336 450.204-452.505-212.284-213.376-450.204 452.513zm496.384-296.326L219.039 797.993l-46.108-46.34L568.233 354.33l46.104 46.335zm345.357-122.99-114.45 115.04-212.288-213.377 114.45-115.035 212.288 213.371zm0 0"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="empty-icon"
viewBox="0 0 1024 1024"
>
<defs>
<linearGradient
id="linear-gradient"
x1="512.342"
y1="2266.13"
x2="512.342"
y2="666.063"
gradientUnits="userSpaceOnUse"
>
<stop offset=".919" stop-color="#e6e6e6" stop-opacity="0" />
<stop offset="1" stop-color="#e6e6e6" />
</linearGradient>
<linearGradient
id="linear-gradient-2"
x1="528.912"
y1="774"
x2="388.088"
y2="612"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ccc" />
<stop offset="1" stop-color="#e6e6e6" stop-opacity="0" />
</linearGradient>
<linearGradient
id="linear-gradient-3"
x1="213.219"
y1="721.704"
x2="251.313"
y2="683.61"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#d7d7d7" />
<stop offset=".485" stop-color="#fafafa" />
<stop offset="1" stop-color="#fafafa" />
</linearGradient>
<linearGradient
id="linear-gradient-4"
x1="724.813"
y1="821.718"
x2="768.656"
y2="777.876"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ccc" />
<stop offset="1" stop-color="#fafafa" />
</linearGradient>
<linearGradient
id="linear-gradient-5"
x1="513.493"
y1="714.594"
x2="471.007"
y2="544.188"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#999" />
<stop offset="1" stop-color="#ccc" />
</linearGradient>
<linearGradient
id="linear-gradient-6"
x1="440.156"
y1="564.031"
x2="508.594"
y2="495.594"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ccc" />
<stop offset="1" stop-color="#f0f0f0" />
</linearGradient>
<linearGradient
id="linear-gradient-7"
x1="660.988"
y1="754.156"
x2="608.637"
y2="544.188"
xlink:href="#linear-gradient-5"
/>
<linearGradient
id="linear-gradient-8"
x1="479.188"
y1="774.219"
x2="649.782"
y2="603.625"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#b3b3b3" />
<stop offset="1" stop-color="#e6e6e6" />
</linearGradient>
<linearGradient
id="linear-gradient-9"
x1="447.121"
y1="774.219"
x2="394.661"
y2="563.813"
xlink:href="#linear-gradient-5"
/>
<linearGradient
id="linear-gradient-10"
x1="494"
y1="597"
x2="628"
y2="463"
xlink:href="#linear-gradient-6"
/>
<linearGradient
id="linear-gradient-11"
x1="610.485"
y1="604.938"
x2="697.298"
y2="518.125"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ccc" />
<stop offset="1" stop-color="#fff" />
</linearGradient>
<linearGradient
id="linear-gradient-12"
x1="457.438"
y1="619.25"
x2="353.469"
y2="619.25"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#e6e6e6" stop-opacity="0" />
<stop offset="1" stop-color="#e6e6e6" />
</linearGradient>
<linearGradient
id="linear-gradient-14"
x1="542.734"
y1="674.25"
x2="615.672"
y2="601.313"
xlink:href="#linear-gradient-6"
/>
<linearGradient
id="linear-gradient-15"
x1="627.933"
y1="358.938"
x2="685.192"
y2="422.531"
gradientUnits="userSpaceOnUse"
>
<stop offset=".4" stop-color="#e6e6e6" stop-opacity=".4" />
<stop offset=".443" stop-color="#fff" />
<stop offset=".6" stop-color="#ccc" />
</linearGradient>
<linearGradient
id="linear-gradient-16"
x1="618.547"
y1="422.531"
x2="681.547"
y2="359.531"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#e6e6e6" />
<stop offset=".761" stop-color="#fff" />
<stop offset="1" stop-color="#f0f0f0" />
</linearGradient>
<linearGradient
id="linear-gradient-17"
x1="625"
y1="441.5"
x2="697"
y2="369.5"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ccc" />
<stop offset=".761" stop-color="#fff" />
<stop offset="1" stop-color="#f0f0f0" />
</linearGradient>
<linearGradient
id="linear-gradient-18"
x1="627.681"
y1="361.438"
x2="692.257"
y2="433.156"
xlink:href="#linear-gradient-15"
/>
<linearGradient
id="linear-gradient-19"
x1="561.414"
y1="735.438"
x2="573.149"
y2="688.375"
xlink:href="#linear-gradient-11"
/>
<linearGradient
id="linear-gradient-20"
x1="405"
y1="485.875"
x2="440"
y2="450.875"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ccc" />
<stop offset="1" stop-color="#fff" stop-opacity=".702" />
</linearGradient>
<linearGradient
id="linear-gradient-21"
x1="404.61"
y1="486.906"
x2="441.86"
y2="449.656"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#ccc" />
<stop offset=".495" stop-color="#ccc" stop-opacity=".702" />
<stop offset=".498" stop-color="#ccc" />
<stop offset="1" stop-color="#fff" stop-opacity=".302" />
</linearGradient>
<radialGradient
id="radial-gradient"
cx="329.297"
cy="647.578"
r="8.172"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stop-color="#fafafa" />
<stop offset="1.2" stop-color="#e6e6e6" />
</radialGradient>
<radialGradient
id="radial-gradient-2"
cx="802.297"
cy="673.578"
r="8.172"
xlink:href="#radial-gradient"
/>
<radialGradient
id="radial-gradient-3"
cx="774.844"
cy="642.75"
r="5.531"
xlink:href="#radial-gradient"
/>
<style>
.cls-17,
.cls-19,
.cls-27,
.cls-28,
.cls-29,
.cls-3,
.cls-30,
.cls-6 {
fill-rule: evenodd;
}
.cls-3 {
fill: #b3b3b3;
}
.cls-6 {
fill: #ccc;
}
.cls-17 {
fill: url(#linear-gradient-12);
}
.cls-19 {
fill: #fff;
}
.cls-27 {
fill: #f8cfad;
}
.cls-28 {
fill: #141a33;
}
.cls-29 {
fill: #f0c5a8;
}
.cls-30 {
fill: #232c57;
}
</style>
</defs>
<g>
<path
d="M512.33 666.07c441.828 0 800 358.18 800 800.03s-358.172 800.02-800 800.02-800-358.18-800-800.02 358.17-800.03 800-800.03z"
style="fill: url(#linear-gradient); fill-rule: evenodd"
/>
<path
d="m272 694 242-82 131 119-188 43z"
style="fill: url(#linear-gradient-2); fill-rule: evenodd"
/>
<path
class="cls-3"
d="M232.391 723.534a2.4 2.4 0 0 1 2.4 2.4v17.725a2.4 2.4 0 0 1-4.8 0v-17.725a2.4 2.4 0 0 1 2.4-2.4z"
/>
<path
d="M232.255 676.559c10.33 0 17.067 15.408 18.7 28.493 1.619 12.942-2.372 23.694-18.7 23.694-16.878 0-20.213-10.733-18.7-23.694 1.633-14.061 8.37-28.493 18.7-28.493z"
style="fill: url(#linear-gradient-3); fill-rule: evenodd"
/>
<path
class="cls-3"
d="M745.853 826h.938a2.4 2.4 0 0 1 2.4 2.4v22.238a2.4 2.4 0 0 1-2.4 2.4h-.938a2.4 2.4 0 0 1-2.4-2.4V828.4a2.4 2.4 0 0 1 2.4-2.4z"
/>
<path
d="M746.727 830.3c-19.438 0-23.278-9.326-21.541-20.59a34.467 34.467 0 0 1 3.289-10.369 16.628 16.628 0 0 1 0-9.112c2.889-12.327 12.059-20.911 18.356-20.911 6.56 0 15.468 9.1 18.356 20.911a14.589 14.589 0 0 1-.335 9.217 34.36 34.36 0 0 1 3.419 10.264c1.861 11.243-2.735 20.59-21.544 20.59z"
style="fill: url(#linear-gradient-4); fill-rule: evenodd"
/>
<path
class="cls-6"
d="M328.841 654.562a6.571 6.571 0 0 0-5.2-5.027q-4.107-.952-.034-2.045a6.571 6.571 0 0 0 5.027-5.2q.952-4.109 2.045-.035a6.569 6.569 0 0 0 5.2 5.027q4.109.954.035 2.045a6.569 6.569 0 0 0-5.027 5.2q-.955 4.108-2.046.035z"
/>
<path
d="M328.383 653.73a6.567 6.567 0 0 0-5.2-5.027q-4.109-.954-.035-2.045a6.568 6.568 0 0 0 5.027-5.2q.954-4.107 2.046-.034a6.568 6.568 0 0 0 5.2 5.027q4.107.952.035 2.045a6.568 6.568 0 0 0-5.027 5.2q-.954 4.104-2.046.034z"
style="fill: url(#radial-gradient); fill-rule: evenodd"
/>
<path
class="cls-6"
d="M801.841 680.562a6.571 6.571 0 0 0-5.2-5.027q-4.107-.952-.034-2.045a6.571 6.571 0 0 0 5.027-5.2q.952-4.109 2.045-.035a6.569 6.569 0 0 0 5.2 5.027q4.108.954.035 2.045a6.569 6.569 0 0 0-5.027 5.2q-.955 4.108-2.046.035z"
/>
<path
d="M801.383 679.73a6.567 6.567 0 0 0-5.2-5.027q-4.108-.954-.035-2.045a6.568 6.568 0 0 0 5.027-5.2q.954-4.107 2.046-.034a6.568 6.568 0 0 0 5.2 5.027q4.107.952.035 2.045a6.568 6.568 0 0 0-5.027 5.2q-.954 4.104-2.046.034z"
style="fill: url(#radial-gradient-2); fill-rule: evenodd"
/>
<path
d="M774.21 646.9a4.446 4.446 0 0 0-3.517-3.4q-2.778-.643-.023-1.383a4.443 4.443 0 0 0 3.4-3.517q.645-2.778 1.383-.023a4.443 4.443 0 0 0 3.517 3.4q2.778.645.023 1.383a4.446 4.446 0 0 0-3.4 3.517q-.645 2.78-1.383.023z"
style="fill: url(#radial-gradient-3); fill-rule: evenodd"
/>
<path
d="m385.6 714.6.158-150.658L598.9 544.174l-.158 150.658z"
style="fill: url(#linear-gradient-5); fill-rule: evenodd"
/>
<path
d="m385.474 564.031 214.763-19.383-36.171-49.067-215.559 17.634z"
style="fill: url(#linear-gradient-6); fill-rule: evenodd"
/>
<path
d="m598.744 694.832.156-150.658 71.975 59.319-.158 150.658z"
style="fill: url(#linear-gradient-7); fill-rule: evenodd"
/>
<path
d="m457.064 774.209.158-150.658 214.691-19.914-.158 150.663z"
style="fill: url(#linear-gradient-8); fill-rule: evenodd"
/>
<path
d="m384.566 714.459.158-150.659 72.5 59.75-.158 150.658z"
style="fill: url(#linear-gradient-9); fill-rule: evenodd"
/>
<path
d="M494 640s75.357-58.4 42-83-38.887 1.663-37 14 53.847 12.465 54-26c.2-49.979 75-125 75-125"
style="
fill: none;
stroke-width: 3px;
stroke-dasharray: 12 6;
stroke: url(#linear-gradient-10);
fill-rule: evenodd;
"
/>
<path
d="m670.275 604.939-72.041-59.9 38.476-26.909 72.86 58.159z"
style="fill: url(#linear-gradient-11); fill-rule: evenodd"
/>
<path
class="cls-17"
d="m425.5 674.383-72.042-59.9 31.109-50.347 72.86 58.16z"
/>
<path
class="cls-17"
d="m425.5 674.383-72.042-59.9 31.109-50.347 72.86 58.16z"
/>
<path
d="m487.918 674.235 214.482-22.57-31.1-50.346-215.309 20.833z"
style="fill: url(#linear-gradient-14); fill-rule: evenodd"
/>
<path class="cls-19" d="m697.363 358.927-69.58 62.511-12.035 1.082z" />
<path
d="m697.363 358.927-69.58 62.511-12.035 1.082z"
style="fill: url(#linear-gradient-15); fill-rule: evenodd"
/>
<path
d="M615.748 422.52 604 413l92.089-53.46"
style="fill: url(#linear-gradient-16); fill-rule: evenodd"
/>
<path
d="m625 432 12 18 60-89"
style="fill: url(#linear-gradient-17); fill-rule: evenodd"
/>
<path class="cls-19" d="m626.98 421.335-2.471 11.828 70.918-71.735" />
<path
d="m626.98 421.335-2.471 11.828 70.918-71.735"
style="fill: url(#linear-gradient-18); fill-rule: evenodd"
/>
<path
d="m494.814 735.44 21.293-2.1v-6.613l-13.4 1.319v-6.965l10.977-1.08v-6.613l-10.977 1.08v-6.084l12.917-1.27v-6.525l-20.808 2.047v32.8zM521 732.863l7.054-.694v-11.241a106.361 106.361 0 0 0-1.014-11.274l.176-.017 2.645 7.586 4.453 11.553 4.32-.425 4.408-12.424 2.733-8.116.177-.018a111.811 111.811 0 0 0-1.014 11.474v11.241l7.185-.707V697l-8.552.841-5.025 14.646c-.618 1.956-1.147 4.08-1.808 6.173l-.22.022c-.617-1.968-1.146-3.987-1.808-5.818l-5.2-13.639-8.508.837v32.8zm37.213-3.661 7.891-.776v-10.889l3.835-.377c6.922-.681 12.961-4.714 12.961-12.517 0-8.111-5.951-10.082-13.181-9.371l-11.504 1.128v32.8zm7.891-17.881v-9.478l3.218-.316c3.792-.373 5.908.565 5.908 3.871 0 3.218-1.852 5.208-5.687 5.585zM594 725.682l7.891-.777v-26.274l8.905-.876v-6.524l-25.657 2.524v6.524l8.861-.871v26.274zm27.991-2.754 7.847-.772v-11.594l9.919-22.18-8.244.811-2.733 7.542c-.925 2.56-1.807 4.939-2.733 7.587l-.176.018c-.926-2.466-1.764-4.676-2.645-7.058l-2.734-7-8.375.824 9.874 20.233v11.594z"
style="fill: url(#linear-gradient-19); fill-rule: evenodd"
/>
<path
class="cls-6"
d="M408.938 457.309a17.5 17.5 0 0 0 21.374 26.725 17.5 17.5 0 1 1-16.306-30.955 17.442 17.442 0 0 0-5.068 4.23z"
/>
<circle
cx="422.5"
cy="468.375"
r="17.5"
style="fill: url(#linear-gradient-20)"
/>
<path
class="cls-6"
d="M391.76 451.5c-2.358 4.419 9.827 15.52 27.215 24.8 15.131 8.071 29.212 12.1 34.166 10.093-4.191 2.772-18.943-1.24-34.86-9.73-17.388-9.275-29.573-20.376-27.215-24.8a2.96 2.96 0 0 1 1.585-1.3 2.606 2.606 0 0 0-.891.937z"
/>
<path
d="M418.975 476.29c-17.388-9.275-29.573-20.376-27.215-24.8s18.363-.484 35.751 8.791 29.572 20.376 27.215 24.8-18.364.483-35.751-8.791zm31.634 5.732c1.824-3.42-8.789-12.642-23.7-20.6s-28.486-11.633-30.31-8.213 8.789 12.642 23.7 20.6 28.486 11.633 30.31 8.213zm-36.645-29.008-2.775 1.452.032 1.751 28.637 14.183.266-4.559z"
style="fill: url(#linear-gradient-21); fill-rule: evenodd"
/>
</g>
<g class="people">
<path
class="cls-27"
d="m612.131 676.5 1.362 3.532 3.255-2.324-1.361-3.532z"
/>
<path
class="cls-27"
d="m629.131 665.5 1.362 3.532 3.255-2.324-1.361-3.532z"
/>
<path
class="cls-28"
d="m617.764 678.184-3.162-.078a11.028 11.028 0 0 0-1.034 3.454c-.258 2.006-1.177 5-.449 5.367 1.5 2.659 4.118-.215 4.118-.215s2.187-2.848 1.925-5.265c-.106-.973-1.181-1.869-1.398-3.263z"
/>
<path
class="cls-28"
d="m633.781 665.855 3.019.945a11.008 11.008 0 0 1-.137 3.6c-.4 1.981-.179 4.166-.986 4.277-2.283 2.03-3.827-1.533-3.827-1.533s-1.473-2.456-.444-4.659c.412-.88 1.718-1.385 2.375-2.63z"
/>
<path
class="cls-29"
d="M599.935 592.534s10.293 9.761 11.95 7.564 3.536-3.463-6.758-13.65z"
/>
<path
class="cls-27"
d="M611.3 596.361c1.674-1.105 11.5 7.048 14.5 11.774s-12.705-4.36-14.632-6.776-1.54-3.893.132-4.998z"
/>
<path
class="cls-27"
d="M621.815 607.988s1.809 2.549 2.433 1.756 2.475-1.064 2.449-1.138.1-.819 1.288-2.331-3.8-3.632-5.81-.494a2.556 2.556 0 0 0-.36 2.207z"
/>
<path
class="cls-30"
d="M598 617s14.968-5.618 17 7a150.235 150.235 0 0 1 2 22s12.666 11.836 16 19c0 0-4.753-1.629-4 2 0 0-18.132-14.647-19-19s-9.148-18.716-12-31z"
/>
<path
d="M589 622s14.968-5.618 17 7a150.235 150.235 0 0 1 2 22s4.666 17.836 8 25c0 0-4.753-1.629-4 2 0 0-10.132-20.647-11-25s-9.148-18.716-12-31z"
style="fill: #292966; fill-rule: evenodd"
/>
<path
class="cls-29"
d="M585.626 597.7s-10.292 9.761-11.95 7.563-3.536-3.463 6.758-13.65z"
/>
<path
class="cls-27"
d="M574.259 601.529c-1.675-1.105-11.5 7.049-14.5 11.774s12.7-4.36 14.631-6.775 1.543-3.894-.131-4.999z"
/>
<path
class="cls-29"
d="M591.715 577.752s-.606 1.681 1.48 3.716-3.615 5.307-4.645 2.85-.48-2.716-.48-2.716z"
/>
<path
class="cls-27"
d="M583.527 574.123c-.839 1.043.491 3.873 1.453 5.449s2.749 2.833 3.364 2.428 4.088-2.657 4-4-.228-3.4-.228-3.4 2.562-1.641 2.154-2.916-2.916-.154-2.916-.154a15.853 15.853 0 0 0-.227-2.224c-.189-.929-6.887-1.445-7.827 2.6s.558 1.805.227 2.217z"
/>
<path
class="cls-30"
d="M584.227 567.758c2.1-.885 7.2-3.684 10.125.318s.842 4.385.989 5.294-1.894 5.69-1.341 6.63-3.865.8-4.657-1.179-2.844-.539-2.227-1.224-1.3-4.456-2.916-2.154a9.252 9.252 0 0 0 .309-1.38c-.115.192.259-3.257-.673-1.32s-2.1 1.037-3.069.762-1.8-1.118-1.071-1.689c.023-.016 2.436-3.172 4.531-4.058z"
/>
<path
d="M589 585c-2.584-.47-10.055.362-13 13 0 0 1.9 3.349 5 4s6 21 6 21 24.016 11.06 27-3c-.07-13.826-8-21-8-21s5.829-3.2 5-6-8.016-10.153-11-10-6 0-6 0-2.416 2.47-5 2z"
style="fill: #f6bb07; fill-rule: evenodd"
/>
<path
class="cls-27"
d="M563.284 612.581s-.986 2.965-1.814 2.389-2.678-.3-2.675-.374-.333-.755-1.912-1.854 2.577-4.583 5.414-2.167a2.551 2.551 0 0 1 .987 2.006z"
/>
</g>
</svg>
</template>
<style lang="stylus">
.empty-icon
.theme-dark &
g.people
opacity 0.8
g:not(.people)
filter invert(80%)
</style>
<template>
<svg
class="icon i18n-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M639.981 344.075c14.805 44.45 34.542 79.023 69.084 113.596 29.603-29.634 49.34-69.146 64.145-113.596H639.981zM314.33 591.024h128.29l-64.145-172.865-64.145 172.865z"
fill="currentColor"
/>
<path
d="M807.746 116.882H215.643c-54.274 0-98.681 44.45-98.681 98.78v592.677c0 54.329 44.407 98.78 98.68 98.78h592.104c54.273 0 98.681-44.451 98.681-98.78V215.66c0-54.329-39.475-98.78-98.68-98.78zM565.971 754.01c-9.866 9.878-19.738 9.878-29.603 9.878-4.94 0-14.805 0-19.738-4.939-4.939-4.939-9.872 0-9.872-4.939s-4.932-9.878-9.865-19.756c-4.94-9.878-4.94-14.817-9.872-24.695L467.29 655.23H294.592l-19.737 54.33c-9.866 19.755-14.805 34.572-19.738 44.45-4.939 9.878-14.804 9.878-29.603 9.878-9.871 0-19.737-4.939-29.609-9.878-9.865-9.878-14.798-14.817-14.798-24.695 0-4.939 0-9.878 4.933-19.756 4.939-9.878 4.939-14.817 9.865-24.695l108.553-276.583c4.939-9.878 4.939-19.756 9.872-29.633 4.932-9.878 9.865-19.756 14.798-24.695 4.939-4.94 9.872-14.817 19.737-19.756 9.872-4.94 19.738-4.94 29.61-4.94 9.865 0 19.73 0 29.603 4.94 9.865 4.939 14.804 9.878 19.737 19.756 4.933 4.939 9.866 14.817 14.798 24.695 4.94 9.877 9.872 19.755 14.805 34.572l108.553 271.644c9.865 19.756 14.804 34.573 14.804 44.451-4.939 4.94-9.872 14.817-14.804 24.695zm271.378-192.62c-54.273-19.756-93.748-44.451-128.29-74.085-34.536 34.573-78.943 59.268-133.223 74.085l-14.798-24.695c54.273-14.817 98.68-34.573 133.223-69.146-34.542-34.573-64.145-79.024-74.017-128.413h-49.34V319.38h133.228c-9.877-14.817-19.743-34.573-29.609-49.39l14.799-4.94c9.871 14.818 24.676 34.574 34.542 54.33h123.35v24.695h-49.34c-14.798 49.39-39.468 93.84-69.077 123.474 34.541 29.634 74.01 54.329 128.29 69.146l-19.738 24.695z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon light-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M512 256a42.667 42.667 0 0 0 42.667-42.667V128a42.667 42.667 0 0 0-85.334 0v85.333A42.667 42.667 0 0 0 512 256zm384 213.333h-85.333a42.667 42.667 0 0 0 0 85.334H896a42.667 42.667 0 0 0 0-85.334zM256 512a42.667 42.667 0 0 0-42.667-42.667H128a42.667 42.667 0 0 0 0 85.334h85.333A42.667 42.667 0 0 0 256 512zm9.387-298.667a42.667 42.667 0 0 0-59.307 62.72l61.44 59.307a42.667 42.667 0 0 0 31.147 11.947 42.667 42.667 0 0 0 30.72-13.227 42.667 42.667 0 0 0 0-60.16zm459.946 133.974a42.667 42.667 0 0 0 29.44-11.947l61.44-59.307a42.667 42.667 0 0 0-57.6-62.72l-61.44 60.587a42.667 42.667 0 0 0 0 60.16 42.667 42.667 0 0 0 28.16 13.227zM512 768a42.667 42.667 0 0 0-42.667 42.667V896a42.667 42.667 0 0 0 85.334 0v-85.333A42.667 42.667 0 0 0 512 768zm244.48-79.36a42.667 42.667 0 0 0-59.307 61.44l61.44 60.587a42.667 42.667 0 0 0 29.44 11.946 42.667 42.667 0 0 0 30.72-12.8 42.667 42.667 0 0 0 0-60.586zm-488.96 0-61.44 59.307a42.667 42.667 0 0 0 0 60.586 42.667 42.667 0 0 0 30.72 12.8 42.667 42.667 0 0 0 28.587-10.666l61.44-59.307a42.667 42.667 0 0 0-59.307-61.44zM512 341.333A170.667 170.667 0 1 0 682.667 512 170.667 170.667 0 0 0 512 341.333z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon link-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M460.8 584.533c17.067 17.067 17.067 42.667 0 59.734-17.067 17.066-42.667 17.066-59.733 0-85.334-85.334-85.334-217.6 0-302.934L554.667 192C640 110.933 776.533 110.933 857.6 196.267c81.067 81.066 81.067 213.333 0 294.4l-68.267 64c0-34.134-4.266-68.267-17.066-102.4l21.333-21.334c51.2-46.933 55.467-128 4.267-179.2s-128-55.466-179.2-4.266c-4.267 0-4.267 4.266-4.267 4.266L465.067 401.067c-51.2 51.2-51.2 132.266-4.267 183.466m123.733-183.466C601.6 384 627.2 384 644.267 401.067c85.333 85.333 85.333 217.6 0 302.933l-153.6 149.333C405.333 934.4 268.8 934.4 187.733 849.067c-81.066-81.067-81.066-213.334 0-294.4l68.267-64c0 34.133 4.267 72.533 17.067 102.4L251.733 614.4C204.8 665.6 204.8 746.667 256 793.6c51.2 46.933 123.733 46.933 174.933 0l149.334-149.333c51.2-51.2 51.2-128 0-179.2-12.8-17.067-17.067-46.934 4.266-64z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon lock-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M787.168 952.268H236.832c-30.395 0-55.033-24.638-55.033-55.033V429.45c0-30.395 24.638-55.034 55.033-55.034h82.55V264.35c0-106.38 86.238-192.618 192.618-192.618S704.618 157.97 704.618 264.35v110.066h82.55c30.395 0 55.033 24.639 55.033 55.034v467.785c0 30.395-24.639 55.033-55.033 55.033zM484.483 672.046v115.122h55.034V672.046c31.99-11.373 55.033-41.605 55.033-77.496 0-45.592-36.958-82.55-82.55-82.55s-82.55 36.958-82.55 82.55c0 35.89 23.042 66.123 55.033 77.496zM622.067 264.35c0-60.788-49.28-110.067-110.067-110.067s-110.067 49.28-110.067 110.067v110.066h220.135V264.35z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon next-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M906.772 512c0 4.772-2.211 9.267-5.99 12.175L524.257 813.66a15.37 15.37 0 0 1-18.616.092 15.368 15.368 0 0 1-5.038-17.91l75.714-191.672h-443.73c-8.488 0-15.36-6.881-15.36-15.36v-153.6c0-8.489 6.872-15.36 15.36-15.36h443.73l-75.714-191.682a15.358 15.358 0 0 1 5.048-17.91c5.51-4.158 13.128-4.137 18.606.092l376.525 289.485a15.323 15.323 0 0 1 5.99 12.165z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
class="page-404-icon"
viewBox="0 0 178 130"
>
<defs>
<linearGradient
id="b"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#e9e9e9" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="c"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#dcdcdc" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="d"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#f1f1f1" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="e"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#dedede" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="f"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#e8e8e8" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="g"
x1=".213"
y1="1.265"
x2=".846"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#fff" />
<stop offset="1" stop-color="#f5f5f5" />
</linearGradient>
<linearGradient
id="h"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#c5c5c5" />
<stop offset="1" stop-color="#fff" stop-opacity="0" />
</linearGradient>
<linearGradient
id="i"
x1=".5"
x2=".5"
y2="1"
gradientUnits="objectBoundingBox"
>
<stop offset="0" stop-color="#acacac" />
<stop offset="1" stop-color="#f2f2f2" stop-opacity=".388" />
</linearGradient>
<clipPath id="a">
<path transform="translate(744 1111)" fill="none" d="M0 0h178v130H0z" />
</clipPath>
</defs>
<g transform="translate(-744 -1111)" clip-path="url(#a)">
<g>
<path
d="M0 10.795 36.6 0v93.779L0 104.574z"
transform="translate(772.466 1122.142)"
fill="url(#b)"
/>
<path
d="M-8.492 10.642-26.361-.469v93.78l17.868 11.111z"
transform="translate(780.958 1122.293)"
fill="url(#c)"
/>
<path
d="M-8.5 5.55 28.106-5.3 10.228-16.437l-36.6 10.845z"
transform="translate(780.963 1127.438)"
fill="url(#d)"
/>
<path
d="M0 10.539 35.741 0v91.56L0 102.1z"
transform="translate(870.158 1123.617)"
fill="url(#d)"
/>
<path
d="M-8.913 10.38-26.361-.469v91.562l17.448 10.848z"
transform="translate(879.071 1123.775)"
fill="url(#e)"
/>
<path
d="m-8.918 5.032 35.741-10.59L9.366-16.437-26.375-5.848z"
transform="translate(879.076 1129.175)"
fill="url(#d)"
/>
<path
d="M0 9.137 30.839 0v79.381L0 88.519z"
transform="translate(799.678 1151.579)"
fill="url(#f)"
/>
<path
d="m-11.306 8.936-15.054-9.4v79.377l15.054 9.4z"
transform="translate(810.985 1151.78)"
fill="url(#c)"
/>
<path
d="M-11.313 2.087 19.526-7.05 4.464-16.437-26.375-7.3z"
transform="translate(810.991 1158.63)"
fill="url(#g)"
/>
<path
d="M178 53H0a51.361 51.361 0 0 1 10.453-20.952 74.532 74.532 0 0 1 19.742-16.811A103.3 103.3 0 0 1 57.089 4.058a127.515 127.515 0 0 1 63.823 0 103.3 103.3 0 0 1 26.894 11.179 74.532 74.532 0 0 1 19.741 16.811A51.363 51.363 0 0 1 178 53z"
transform="translate(744 1187.549)"
fill="url(#h)"
/>
<path d="m814.529 1199.586-1.272 1.212h2.3l1.2-1.212z" fill="#cbcbcb" />
<path
d="m816.725 1194.909-1.272 1.212h2.3l1.263-1.212z"
fill="#cbcbcb"
/>
<path d="m863.284 1199.585-1.272 1.212h2.3l1.2-1.212z" fill="#cbcbcb" />
<path d="m865.519 1194.9-1.272 1.212h2.3l1.263-1.212z" fill="#cbcbcb" />
<path
d="m799.527 1191.21 10.182-21.97h4.381l-9.931 21.719h14.876v3.941h-19.508zm13.081-9.493h4.152v17.859h-4.152zm20.728 18.151q-4.256 0-6.457-2.274a8.74 8.74 0 0 1-2.2-6.343v-13.791a8.708 8.708 0 0 1 2.21-6.353q2.212-2.264 6.447-2.264 4.256 0 6.457 2.253a8.726 8.726 0 0 1 2.2 6.363v13.792a8.708 8.708 0 0 1-2.21 6.349q-2.211 2.268-6.447 2.268zm0-4.048a4.29 4.29 0 0 0 3.328-1.178 4.862 4.862 0 0 0 1.074-3.39v-13.792a4.893 4.893 0 0 0-1.064-3.39 4.285 4.285 0 0 0-3.338-1.179 4.285 4.285 0 0 0-3.338 1.179 4.893 4.893 0 0 0-1.064 3.39v13.791a4.862 4.862 0 0 0 1.075 3.391 4.29 4.29 0 0 0 3.327 1.178zm14.928-4.61 10.181-21.97h4.381l-9.931 21.719h14.876v3.941h-19.507zm13.081-9.493h4.152v17.859h-4.152z"
fill="#c6c6c6"
/>
<path
d="m798.306 1192.431 10.182-21.97h4.381l-9.931 21.719h14.876v3.941h-19.508zm13.081-9.493h4.152v17.859h-4.152zm20.728 18.151q-4.256 0-6.457-2.274a8.74 8.74 0 0 1-2.2-6.343v-13.791a8.708 8.708 0 0 1 2.21-6.353q2.212-2.264 6.447-2.264 4.256 0 6.457 2.253a8.726 8.726 0 0 1 2.2 6.363v13.792a8.708 8.708 0 0 1-2.21 6.349q-2.211 2.268-6.447 2.268zm0-4.048a4.29 4.29 0 0 0 3.328-1.178 4.862 4.862 0 0 0 1.074-3.39v-13.792a4.893 4.893 0 0 0-1.064-3.39 4.285 4.285 0 0 0-3.338-1.179 4.285 4.285 0 0 0-3.338 1.179 4.893 4.893 0 0 0-1.064 3.39v13.791a4.862 4.862 0 0 0 1.075 3.391 4.29 4.29 0 0 0 3.327 1.178zm14.928-4.61 10.181-21.97h4.381l-9.931 21.719h14.876v3.941h-19.507zm13.081-9.493h4.152v17.859h-4.152z"
fill="#b2b2b2"
/>
<path
d="m-27.694-19.435 10.182 14.517h4.381l-9.931-14.352h14.876v-2.606h-19.508zm13.081 6.273h4.152v-11.8h-4.152zM6.115-25.156q-4.256 0-6.457 1.5a4.8 4.8 0 0 0-2.2 4.191v9.113a4.784 4.784 0 0 0 2.212 4.2 11.511 11.511 0 0 0 6.447 1.5q4.256 0 6.457-1.489a4.786 4.786 0 0 0 2.2-4.2v-9.113a4.784 4.784 0 0 0-2.212-4.2 11.511 11.511 0 0 0-6.447-1.502zm0 2.675a5.705 5.705 0 0 1 3.328.779 2.6 2.6 0 0 1 1.074 2.24v9.113a2.607 2.607 0 0 1-1.064 2.24 5.7 5.7 0 0 1-3.338.779 5.7 5.7 0 0 1-3.338-.779 2.607 2.607 0 0 1-1.064-2.24v-9.113A2.6 2.6 0 0 1 2.788-21.7a5.705 5.705 0 0 1 3.327-.782zm14.927 3.047L31.224-4.918h4.381l-9.931-14.351H40.55v-2.606H21.043zm13.081 6.273h4.152v-11.8h-4.151z"
transform="translate(826 1226.245)"
opacity=".32"
fill="url(#i)"
/>
<g fill="#e6e6e6">
<path d="m858.428 1169.23-1.2 1.259h4.388l1.178-1.259z" />
<path
d="m802.944 1192.187 1.288-1.375h7.143v1.375zm8.415-9.25 1.273-1.234h4.15l-1.235 1.234zm-2.855-12.469 1.198-1.259h4.367l-1.178 1.259z"
/>
<path d="m861.362 1181.678-1.27 1.3h4.188l1.236-1.3z" />
<path d="m865.519 1190.9-1.27 1.3h2.3l1.162-1.3z" />
<path d="m852.838 1190.791-1.207 1.508h8.447v-1.508z" />
</g>
</g>
</g>
</svg>
</template>
<style lang="stylus">
.page-404-icon
.theme-dark &
filter invert(70%)
</style>
<template>
<svg
class="icon presentation-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M896 170.667v426.666a85.333 85.333 0 0 1-85.333 85.334h-256v61.184l192.597 115.584-43.861 73.13-148.736-89.173v95.275h-85.334v-95.318l-148.736 89.216-43.861-73.13 192.597-115.627v-61.141h-256A85.333 85.333 0 0 1 128 597.333V170.667H85.333V85.333h853.334v85.334H896zm-682.667 0v426.666h597.334V170.667H213.333zM426.667 512h-85.334V341.333h85.334V512zm128 0h-85.334V256h85.334v256zm128 0h-85.334V384h85.334v128z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon prev-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M906.783 588.79c-.02 8.499-6.882 15.36-15.38 15.37l-443.7-.01 75.704 191.682c2.52 6.42.482 13.763-5.038 17.91-5.52 4.168-13.138 4.147-18.616-.092L123.228 524.175a15.362 15.362 0 0 1-6-12.165c0-4.782 2.222-9.277 6-12.185L499.753 210.35a15.388 15.388 0 0 1 9.38-3.195c3.236 0 6.502 1.034 9.236 3.103 5.52 4.147 7.578 11.49 5.038 17.91L447.683 419.84l443.72-.01c8.498.01 15.36 6.881 15.36 15.36l.02 153.6z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M987.456 425.152H864V295.296a36.48 36.48 0 0 0-36.544-36.544h-360l-134.08-128.256A9.344 9.344 0 0 0 327.04 128H36.48A36.48 36.48 0 0 0 0 164.544v676.608a36.48 36.48 0 0 0 36.544 36.544h797.76a36.672 36.672 0 0 0 33.92-22.848L1021.44 475.52a36.48 36.48 0 0 0-33.92-50.304zM82.304 210.304h215.424l136.64 130.752h347.328v84.096H198.848A36.672 36.672 0 0 0 164.928 448L82.304 652.8V210.304zM808.32 795.456H108.544l118.08-292.608h699.904L808.32 795.52z"
fill="currentColor"
/>
</svg>
</template>
<template>
<svg
class="icon sticky-icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M989.922 444.341 580.15 34.909H119.411l870.511 870.597V444.34z" />
<path
class="sticky-text"
d="m621.368 219.398-43.833-43.77-126.663 126.841-32.827-32.78L544.71 142.846l-43.735-43.674 26.739-26.775L648.11 192.621l-26.741 26.776zm-38.762 65.278c24.03-24.065 50.614-36.637 79.751-37.716 29.135-1.077 55.24 9.904 78.314 32.945 21.95 21.919 32.324 46.87 31.121 74.852s-13.258 53.441-36.167 76.383c-23.901 23.935-50.255 36.407-79.057 37.416-28.807 1.013-54.482-9.74-77.025-32.252-22.016-21.985-32.69-47.068-32.015-75.244.673-28.18 12.366-53.639 35.078-76.384zm36.196 32.578c-14.921 14.943-23.517 30.756-25.783 47.439-2.27 16.684 2.88 31.298 15.441 43.842 12.826 12.807 27.348 18.234 43.567 16.271 16.217-1.96 31.986-10.608 47.303-25.948 15.977-15.998 25.133-32.11 27.467-48.332 2.334-16.221-2.813-30.637-15.442-43.247-12.827-12.81-27.679-18.133-44.558-15.973-16.879 2.158-32.878 10.809-47.995 25.948zm161.326 207.05-53.477 53.554-32.727-32.681L847.325 391.56l52.859 52.784c38.214 38.16 41.146 73.44 8.797 105.834-15.713 15.737-34.076 22.586-55.087 20.552-21.012-2.032-39.98-11.898-56.905-29.591l-16.861-16.834zm74.572-74.676-49.517 49.586 14.182 14.162c19.24 19.211 37.21 20.455 53.914 3.728 16.305-16.33 14.941-34.002-4.1-53.016l-14.479-14.46z"
/>
</svg>
</template>
<template>
<svg
class="icon icon-baidu"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#1D2FE3" />
<path
d="M239.022 704.978c.098-4.865-.314-9.772.162-14.591 5.178-52.464 197.571-253.377 249.641-259.233 42.996-4.833 75.768 16.545 99.824 49.144 37.893 51.351 82.81 95.455 131.292 136.237 52.903 44.503 56.525 99.801 32.6 158.592-23.425 57.56-75.34 69.833-127.771 58.804-84.971-17.874-168.158-13.744-253.37-4.536-86.35 9.333-133.788-39.4-132.378-124.417zM352.464 412.86c-3.58 50.707-17.93 96.128-75.9 98.12-58.053 1.995-80.093-41.432-79.275-91.71.81-49.705 13.416-104.053 76.851-102.136 53.84 1.625 74.74 45.8 78.324 95.726zm386.053 142.168c-68.494-1.735-84.188-43.331-82.635-93.812 1.46-47.519 10.082-97.628 73.299-96.65 61.395.95 81.6 43.207 81.553 98.668-.047 53.156-19.818 89.398-72.217 91.794zm-45.235-278.345c-10.464 42.665-24.513 91.761-85.919 94.502-52.74 2.354-71.705-34.482-72.805-81.242-1.233-52.42 48.08-112.965 87.582-110.373 33.943 2.226 71.146 49.541 71.142 97.113zm-195.147-14.097c-7.005 46.274-13.63 100.025-71.562 101.351-57.077 1.306-73.567-47.922-73.638-97.109-.068-48.054 12.128-99.024 69.345-101.426 59.45-2.493 67.11 51.093 75.855 97.184z"
fill="#fff"
/>
<path
d="M479.52 663.165c.006 12.194 1.498 24.61-.284 36.537-4.707 31.503 18.862 78.749-45.326 77.534-54.226-1.027-103.338-3.31-113.231-73.536-7.164-50.852 7.78-85.674 57.687-102.668 17.67-6.016 39.618 5.058 54.096-14.548 10.84-14.679-2.901-54.592 33.418-41.47 24.075 8.7 11.477 38.922 13.278 59.652 1.68 19.366.359 38.99.363 58.5zm175.45 41.902c4.291 39.657 5.093 78.047-64.709 73.503-60.097-3.912-95.56-20.794-86.293-85.624 4.287-29.991-21.148-83.238 22.19-84.867 42.71-1.606 13.57 50.41 20.825 77.622 5.276 19.794-3.984 46.774 29.753 48.193 41.337 1.738 28.383-30.022 31.099-51.604 1.209-9.61-.85-19.65.528-29.215 2.516-17.474-8.928-44.716 19.554-47.191 36.044-3.133 24.155 28.376 26.678 47.523 1.896 14.387.375 29.225.375 51.66z"
fill="#1D2FE3"
/>
<path
d="M435.669 685.038c-2.255 24.07 5.605 53.68-33.623 52.136-34.594-1.362-35.274-31.818-38.513-53.078-4.028-26.448 11.38-48.18 40.785-50.023 40.967-2.564 27.097 30.764 31.35 50.965z"
fill="#fff"
/>
</svg>
</template>
<template>
<svg
class="icon icon-bitbucket"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#205081" />
<path
d="M512 191.32v.01-.01c-152.76 0-277.805 41.114-277.805 92.166 0 13.443 33.353 206.247 46.577 282.708 5.932 34.285 94.508 84.563 231.126 84.563l.102-.407v.407c137.484 0 225.26-50.278 231.192-84.578 13.23-76.457 46.592-269.255 46.592-282.698.005-51.047-125.024-92.165-277.784-92.165zm0 397.493c-48.771 0-88.31-39.545-88.31-88.31 0-48.772 39.539-88.306 88.31-88.306s88.31 39.534 88.31 88.31c0 48.766-39.539 88.306-88.31 88.306zm-.05-276.842c-98.256-.153-177.885-17.232-177.855-38.14.036-20.912 79.72-37.731 177.976-37.568 98.256.153 177.884 17.22 177.849 38.139-.026 20.908-79.705 37.716-177.966 37.564z"
fill="#fff"
/>
<path
d="M711.668 642.814c-4.227 0-7.608 2.994-7.608 2.994S635.65 699.987 512 699.987s-192.06-54.18-192.06-54.18-3.386-2.988-7.608-2.988c-5.04 0-9.827 3.391-9.827 10.871 0 .79.076 1.579.224 2.353 10.617 56.826 18.382 97.206 19.736 103.347 9.268 41.805 91.045 73.411 189.525 73.411h.01c98.49 0 180.267-31.606 189.535-73.411 1.364-6.136 9.114-46.49 19.736-103.317.143-.779.224-1.578.224-2.368 0-7.485-4.786-10.881-9.827-10.881zM467.659 500.477a44.255 44.255 0 1 0 88.51 0 44.255 44.255 0 1 0-88.51 0z"
fill="#fff"
/>
</svg>
</template>
<template>
<svg
class="icon icon-dingding"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#1C9DF7" />
<path
d="M760.551 442.095c0 5.178-5.178 12.945-7.767 20.713-23.302 49.192-82.85 144.988-82.85 144.988l-18.124 31.069h85.44L576.727 853.758l36.247-144.988h-67.316l23.302-95.796c-18.124 5.178-41.426 10.356-67.316 18.124 0 0-36.247 20.712-100.974-38.837 0 0-44.015-38.836-18.124-49.192 10.356-5.178 54.37-10.356 88.029-12.945 44.014-5.179 72.494-10.357 72.494-10.357s-139.81 2.59-173.468-2.589c-33.658-5.178-75.083-59.549-82.85-108.741 0 0-12.946-25.89 28.48-12.945 44.013 12.945 222.66 49.192 222.66 49.192s-235.606-72.494-251.14-90.618c-15.535-18.123-46.604-95.796-41.426-144.988 0 0 2.59-12.945 12.945-7.767 0 0 173.469 80.261 292.566 121.686 119.098 41.426 222.66 64.727 209.715 119.098z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-discord"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#2DAAE1" />
<path
d="m422.935 240.147 5.851 8.052c-113.039 34.253-165.649 84.585-165.649 84.585s13.645-8.052 37.024-18.123c66.28-30.215 118.865-38.267 142.244-40.286 3.91 0 7.819-1.994 9.76-1.994 38.966-6.058 81.816-6.058 126.658-2.02 58.462 8.053 122.749 24.157 187.061 62.423 0 0-48.7-48.338-155.862-82.566l7.767-10.071h1.968c13.308.233 91.757 4.246 173.416 68.481 0 0 89.634 169.144 89.634 376.555 1.942-2.02-50.668 92.637-189.003 96.65 0 0-23.405-28.194-40.933-52.35 81.84-24.157 113.013-76.534 113.013-76.534a313.796 313.796 0 0 1-72.106 38.267c-31.172 14.11-60.403 22.162-89.633 28.22-60.404 12.066-114.955 8.027-161.74 0a636.81 636.81 0 0 1-91.576-28.22c-13.644-6.033-29.23-12.065-44.817-22.137-1.941-2.02-3.883-2.02-5.85-4.039-1.943 0-1.943-2.02-1.943-2.02-11.702-6.032-17.528-10.07-17.528-10.07s29.23 52.376 109.104 76.532c-19.47 24.157-40.907 54.371-40.907 54.371-138.36-4.039-190.97-98.67-190.97-98.67 0-207.41 89.633-376.555 89.633-376.555 89.634-70.5 175.384-68.481 175.384-68.481zm213.961 233.017c-35.315 0-64.727 34.512-64.727 77.672s29.412 77.672 64.727 77.672 64.727-34.512 64.727-77.672-29.412-77.672-64.727-77.672zm-233.016 0c-35.315 0-64.727 34.512-64.727 77.672s29.412 77.672 64.727 77.672 64.726-34.512 64.726-77.672-29.411-77.672-64.726-77.672z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-dribbble"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#DC4373" />
<path
d="M716.668 302.752c-13.332 5.154-28.61 3.676-35.726-1.068-86.196-48.27-179.286-62.06-265.482-37.926C292.074 300.212 241.674 437.028 242 564.746c22.622 42.136 52.558 105.234 61.854 153.104l298.24 298.246c210.532-37.376 376.584-203.408 413.986-413.928L716.668 302.752z"
fill="#C13366"
/>
<path
d="M511.994 219.308c-161.592 0-293.058 131.31-293.058 292.696s131.466 292.684 293.058 292.684c161.598 0 293.07-131.292 293.07-292.684 0-161.386-131.472-292.696-293.07-292.696zm192.842 138.148c33.43 41.432 53.744 93.756 54.682 150.772-11.122-2.282-58.23-11.068-114.526-11.068-18.164 0-37.282.91-56.448 3.278a822.854 822.854 0 0 0-4.938-11.812c-4.972-11.692-10.326-23.29-15.874-34.72 86.642-35.696 128.436-85.238 137.104-96.45zm-192.842-92.33c62.446 0 119.528 23.2 163.126 61.412-6.902 9.416-44.332 56.202-127.876 87.768-38.67-70.688-80.996-129.22-91.07-142.812a247.13 247.13 0 0 1 55.82-6.368zm-106.352 23.99c8.536 11.758 50.928 70.918 90.592 141.036-106.448 27.982-200.976 29.806-223.79 29.806h-2.424c16.39-75.534 67.424-138.298 135.622-170.842zM264.402 512.39c0-2.038.034-4.076.1-6.102 1.48.018 3.666.018 6.5.018 30.726 0 137.382-2.538 247.288-35.154a828.402 828.402 0 0 1 18.928 39.526 232.24 232.24 0 0 0-8.234 2.482C405.236 553.126 337.216 658.936 326.75 676.232c-38.778-43.696-62.348-101.058-62.348-163.842zm247.592 247.246c-56.786 0-109.192-19.232-151.01-51.48 7.074-13.868 58.412-106.3 194.026-153.5a1.806 1.806 0 0 1 .272-.09c34.006 88.53 48.408 162.834 52.358 185.862-29.432 12.374-61.752 19.208-95.646 19.208zm140.236-43.584c-3.32-19.052-16.66-88.688-47.452-173.618 17.27-2.686 34.072-3.748 49.828-3.748 51.748 0 92.23 11.33 101.634 14.204-11.182 67.444-49.794 125.808-104.01 163.162z"
fill="#F0F1F1"
/>
<path
d="M511.994 219.308c-.382 0-.758.028-1.142.028v45.804c.38 0 .758-.018 1.142-.018 62.446 0 119.528 23.2 163.126 61.412-6.902 9.416-44.332 56.202-127.876 87.768-12.14-22.194-24.642-43.188-36.392-61.968V473.29c2.476-.706 4.954-1.41 7.434-2.144a828.402 828.402 0 0 1 18.928 39.526c-2.776.796-5.514 1.592-8.234 2.478a323.314 323.314 0 0 0-18.128 6.52v53.228a418.976 418.976 0 0 1 44.428-18.332c34.006 88.53 48.408 162.834 52.358 185.862-29.428 12.374-61.746 19.208-95.646 19.208-.382 0-.758-.024-1.142-.024v45.05c.382 0 .758.028 1.142.028 161.598 0 293.07-131.292 293.07-292.684.002-161.388-131.47-292.698-293.068-292.698zm71.614 269.316c-4.972-11.692-10.326-23.29-15.874-34.72 86.64-35.696 128.434-85.236 137.102-96.45 33.43 41.432 53.748 93.756 54.682 150.77-11.122-2.282-58.23-11.068-114.522-11.068-18.164 0-37.282.914-56.448 3.278-1.624-3.932-3.24-7.852-4.94-11.81zm68.622 227.428c-3.32-19.052-16.66-88.688-47.452-173.618 17.27-2.686 34.072-3.748 49.828-3.748 51.748 0 92.23 11.33 101.634 14.204-11.182 67.444-49.794 125.808-104.01 163.162z"
fill="#D1D1D1"
/>
</svg>
</template>
<template>
<svg
class="icon icon-email"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0 512a512 512 0 101024 0A512 512 0 100 512z" fill="#1384FF" />
<path
d="M299.372 313.572H722.93c28.945 0 52.61 21.845 52.975 48.787L511.333 500.35 246.76 362.481c.182-27.003 23.666-48.97 52.611-48.97zm-52.671 101.702l-.243 244.121c0 27.186 23.848 49.395 52.914 49.395H722.93c29.127 0 52.975-22.21 52.975-49.395V415.152L517.522 546.71a13.957 13.957 0 01-12.682 0L246.7 415.274z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-evernote"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#36D613" />
<path d="M347.671 193.259v87.51h-87.51z" fill="#595757" />
<path
d="M788.047 323.645s3.65-77.491-73.84-103.02c0 0-89.479-12.867-151.41-11.91 0 0-8.595-53.23-103.33-53.23 0 0-89.556-1.244-89.892 70.526v61.671s2.848 14.991-27.833 14.991h-81.581s-34.28 5.282-34.28 72.934c0 0 3.133 120.082 41.322 200.24 0 0 9.398 34.667 58.228 46.577 0 0 95.822 25.477 123.991 21.722 0 0 58.228 22.137 62.008-111.874 0 0 3.755-19.935 6.266 11.392 0 0-1.89 68.948 57.607 72.702 0 0 45.723 12.557 73.892 10.045 0 0 37.568 2.15 37.568 64.158 0 0 13.152 71.665-34.435 71.665h-65.763s-18.149 4.428-18.149-21.877c0 0-4.997-21.878 26.305-21.878h15.534v-43.756h-43.082s-66.332-6.317-66.332 50.047v75.135s9.347 49.866 66.332 49.866h121.273s48.441.44 76.61-90.359c0-.078 48.52-182.323 22.991-435.767zM625.272 486.523c0-21.877 18.02-51.16 39.432-51.16s36.48 36.118 36.48 58.022c-28.79-7.897-45.827-9.606-75.912-6.862z"
fill="#595757"
/>
</svg>
</template>
<template>
<svg
class="icon icon-facebook"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#3C599B" />
<path
d="M372.568 413.895h59.898V355.68c0-25.67.647-65.257 19.294-89.774 19.642-25.965 46.605-43.613 92.983-43.613 75.565 0 107.384 10.778 107.384 10.778l-14.971 88.74s-24.967-7.217-48.254-7.217c-23.302 0-44.16 8.35-44.16 31.635v67.666h95.526l-6.67 86.678h-88.855V801.69H432.466V500.574h-59.898v-86.68z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-flipboard"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#E12828" />
<path d="M293.58 292.18h160.343v481.003H293.58V292.18z" fill="#FFF" />
<path d="M453.922 292.18h320.662v160.343H453.922V292.18z" fill="#FCE9E9" />
<path
d="M453.922 452.523h160.343v160.343H453.922V452.523z"
fill="#F6BEBE"
/>
</svg>
</template>
<template>
<svg
class="icon icon-gitee"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#C71D23" />
<path
d="M772.953 454.723H480.17v.006a25.46 25.46 0 0 0-25.46 25.453l-.025 63.649a25.46 25.46 0 0 0 25.46 25.466l178.242-.007a25.46 25.46 0 0 1 25.459 25.46v12.73c0 42.18-34.198 76.378-76.378 76.378H365.583a25.46 25.46 0 0 1-25.46-25.46V416.533h-.006c0-42.18 34.192-76.378 76.378-76.378h356.388v-.013a25.46 25.46 0 0 0 25.46-25.446l.057-63.65h.013a25.46 25.46 0 0 0-25.46-25.471l-356.432.012c-105.453 0-190.946 85.493-190.946 190.946v356.433a25.46 25.46 0 0 0 25.46 25.46H626.56c94.913 0 171.852-76.94 171.852-171.852V480.182a25.46 25.46 0 0 0-25.46-25.46z"
fill="#fff"
/>
</svg>
</template>
<template>
<svg
class="icon icon-github"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#171515" />
<path
d="M509.423 146.442c-200.317 0-362.756 162.42-362.756 362.8 0 160.266 103.936 296.24 248.109 344.217 18.139 3.327 24.76-7.872 24.76-17.486 0-8.613-.313-31.427-.49-61.702-100.912 21.923-122.205-48.63-122.205-48.63-16.495-41.91-40.28-53.067-40.28-53.067-32.937-22.51 2.492-22.053 2.492-22.053 36.407 2.566 55.568 37.386 55.568 37.386 32.362 55.438 84.907 39.43 105.58 30.143 3.296-23.444 12.667-39.43 23.032-48.498-80.557-9.156-165.246-40.28-165.246-179.297 0-39.604 14.135-71.988 37.342-97.348-3.731-9.178-16.18-46.063 3.556-96.009 0 0 30.46-9.754 99.76 37.19 28.937-8.048 59.97-12.071 90.823-12.211 30.807.14 61.843 4.165 90.822 12.21 69.26-46.944 99.663-37.189 99.663-37.189 19.792 49.946 7.34 86.831 3.61 96.01 23.25 25.359 37.29 57.742 37.29 97.347 0 139.366-84.82 170.033-165.637 179.013 13.026 11.2 24.628 33.342 24.628 67.182 0 48.498-.445 87.627-.445 99.521 0 9.702 6.535 20.988 24.945 17.444 144.03-48.067 247.881-183.95 247.881-344.175 0-200.378-162.442-362.798-362.802-362.798z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-gitlab"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#E8F0FF" />
<path d="m512 848.182 134.473-413.8H377.527L512 848.182z" fill="#E24329" />
<path d="m512 848.182-134.473-413.8h-188.36L512 848.182z" fill="#FC6D26" />
<path
d="m189.167 434.382-40.787 125.82a27.8 27.8 0 0 0 10.135 31.147L512 848.182l-322.833-413.8z"
fill="#FCA326"
/>
<path
d="M189.167 434.382h188.36l-80.832-249.17c-4.202-12.854-22.247-12.854-26.45 0l-81.078 249.17z"
fill="#E24329"
/>
<path d="m512 848.182 134.473-413.8h188.36L512 848.182z" fill="#FC6D26" />
<path
d="m834.833 434.382 40.787 125.82a27.8 27.8 0 0 1-10.135 31.147L512 848.182l322.833-413.8z"
fill="#FCA326"
/>
<path
d="M834.833 434.382h-188.36l81.079-249.17c4.202-12.854 22.247-12.854 26.45 0l80.831 249.17z"
fill="#E24329"
/>
</svg>
</template>
<template>
<svg
class="icon icon-gmail"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#DB4437" />
<path d="M277.48 285.567h465.767v441.362H277.48V285.567z" fill="#E67C73" />
<path
d="M282.543 285.567h-10.645c-25.962 0-47.122 21.808-47.122 48.705v343.952c0 26.897 21.08 48.705 47.122 48.705h24.976V407.954l213.49 169.95 213.489-169.95V726.93h24.975c26.04 0 47.123-21.809 47.123-48.705V334.272c0-26.897-21.134-48.705-47.123-48.705h-10.644L510.364 480.44 282.542 285.567z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-instagram"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#181818" />
<path
d="M512 348.16c-88.222 0-163.84 71.417-163.84 163.84 0 88.222 71.417 163.84 163.84 163.84 88.222 0 163.84-71.417 163.84-163.84 0-88.222-75.618-163.84-163.84-163.84zm0 268.866c-58.814 0-105.026-46.212-105.026-105.026S453.186 406.974 512 406.974 617.026 453.186 617.026 512 570.814 617.026 512 617.026zM680.041 306.15c-21.005 0-37.81 16.804-37.81 37.809s16.805 37.81 37.81 37.81 37.81-16.805 37.81-37.81-16.805-37.81-37.81-37.81z"
fill="#FFF"
/>
<path
d="M659.036 196.923h-16.804c-50.413-4.2-210.051-4.2-260.464 0-96.623-4.2-180.644 71.418-184.845 168.041v16.804c-4.2 50.413-4.2 210.051 0 260.464-4.2 96.623 71.418 180.644 168.041 184.845h16.804c50.413 4.2 210.051 4.2 260.464 0 96.623 4.2 180.644-71.418 184.845-168.041V381.768c4.2-96.623-71.418-180.644-168.041-184.845zM759.86 696.845c-12.604 29.407-33.609 50.412-58.815 58.814-121.83 16.805-247.86 16.805-373.891 0-29.407-12.603-50.412-33.608-58.814-58.814-12.604-63.015-16.805-126.03-12.604-184.845-4.2-63.015 0-126.03 12.604-184.845 12.603-29.407 33.608-50.412 58.814-58.814 121.83-16.805 247.86-16.805 373.891 0 29.407 12.603 50.412 33.608 58.815 58.814 12.603 63.015 16.804 126.03 12.603 184.845 4.2 63.015 0 126.03-12.603 184.845z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-line"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#00C300" />
<path
d="M861.44 469.76C861.44 313.28 704 186.08 512 186.08s-349.44 127.2-349.44 283.68C162.56 608 286.88 727.52 454.88 752c11.52 2.4 26.88 7.68 30.72 17.28a71.04 71.04 0 0 1 0 31.68l-5.28 29.76c0 8.64-7.2 34.56 30.24 18.72a1104 1104 0 0 0 274.56-202.56A251.52 251.52 0 0 0 860 472.16zM375.2 562.88h-69.12a17.76 17.76 0 0 1-18.24-18.24v-139.2a17.76 17.76 0 0 1 18.24-18.24 18.24 18.24 0 0 1 18.24 18.24v120.48h50.88a18.72 18.72 0 0 1 18.24 18.72 18.24 18.24 0 0 1-18.24 18.24zm72-18.24a18.24 18.24 0 1 1-36.48 0v-139.2a18.24 18.24 0 0 1 36.48 0zm167.04 0a18.24 18.24 0 0 1-12.48 17.28H596a18.24 18.24 0 0 1-14.4-7.2l-69.6-96v85.92a18.24 18.24 0 1 1-36.48 0v-139.2A18.24 18.24 0 0 1 488 388.16h5.76a18.24 18.24 0 0 1 14.4 7.2l71.52 96v-85.92a18.24 18.24 0 1 1 36.48 0zm112.32-87.84a18.24 18.24 0 0 1 18.24 18.24 17.76 17.76 0 0 1-18.24 18.24h-50.88v32.64h50.88a18.72 18.72 0 0 1 18.24 18.72 18.24 18.24 0 0 1-18.24 18.24H656a18.24 18.24 0 0 1-18.24-18.24v-139.2A18.24 18.24 0 0 1 656 387.2h69.12a18.24 18.24 0 0 1 18.24 18.24 18.24 18.24 0 0 1-18.24 18.72h-49.44v32.64zm0 0"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-linkedin"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="240"
height="240"
>
<circle cx="512" cy="512" r="512" fill="#4376B1" />
<path
d="M838.301 555.94v225.157h-130.54V571.03c0-52.746-18.847-88.766-66.112-88.766-36.069 0-57.496 24.25-66.959 47.732-3.436 8.391-4.322 20.045-4.322 31.814v219.277h-130.55s1.752-355.784 0-392.613h130.56v55.637c-.263.438-.633.867-.867 1.285h.866v-1.285c17.349-26.694 48.287-64.856 117.651-64.856 85.884 0 150.273 56.114 150.273 176.685zm-535.05-356.72c-44.655 0-73.87 29.314-73.87 67.826 0 37.695 28.368 67.855 72.157 67.855h.847c45.532 0 73.842-30.16 73.842-67.855-.866-38.512-28.31-67.825-72.975-67.825zM237.14 781.098h130.5V388.474h-130.5v392.623z"
fill="#F1F2F2"
/>
</svg>
</template>
<template>
<svg
class="icon icon-pinterest"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M512 1023.147c282.773 0 512-228.288 512-509.888 0-281.622-229.227-509.91-512-509.91S0 231.637 0 513.26c0 281.6 229.227 509.888 512 509.888z"
fill="#FFF"
/>
<path
d="M512 3.35C229.248 3.35 0 231.658 0 513.258c0 216.128 134.848 400.789 325.312 475.05-4.63-40.277-8.427-102.378 1.685-146.453 9.28-39.872 59.84-253.483 59.84-253.483s-15.168-30.634-15.168-75.541c0-70.933 41.302-123.797 92.715-123.797 43.819 0 64.896 32.725 64.896 71.765 0 43.627-27.819 109.099-42.56 169.963-12.224 50.773 25.707 92.33 75.84 92.33 91.03 0 160.981-95.68 160.981-233.344 0-122.133-88.064-207.317-214.058-207.317-145.814 0-231.36 108.693-231.36 221.163 0 43.648 16.853 90.645 37.93 116.245a15.19 15.19 0 0 1 3.371 14.699c-3.797 15.936-12.65 50.773-14.336 57.92-2.09 9.216-7.573 11.328-17.28 6.698-64.043-29.781-104.085-122.538-104.085-197.653 0-160.747 117.162-308.459 338.389-308.459 177.408 0 315.627 125.888 315.627 294.614 0 175.829-111.254 317.269-265.472 317.269-51.84 0-100.715-26.859-117.163-58.752l-32.021 121.28c-11.371 44.48-42.56 99.883-63.638 133.867A516.01 516.01 0 0 0 511.168 1024c282.752 0 512-228.31 512-509.91C1024 231.66 794.752 3.35 512 3.35z"
fill="#CA242D"
/>
</svg>
</template>
<template>
<svg
class="icon icon-pocket"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#EE4056" />
<path
d="M716.52 309.066c12.549 0 23.172 4.394 31.87 13.182 8.697 8.788 13.023 19.48 13.023 32.006v150.4c0 33.975-6.568 66.41-19.705 97.307-13.138 30.918-30.76 57.487-52.89 79.685-22.106 22.197-48.562 39.864-79.367 52.888-30.804 13.024-63.081 19.547-96.876 19.547a246.897 246.897 0 0 1-97.215-19.547c-30.805-13.046-57.306-30.668-79.504-52.888-22.198-22.198-39.865-48.767-53.003-79.663a246.311 246.311 0 0 1-19.728-97.33V354.255c0-12.321 4.44-22.945 13.319-31.847a43.489 43.489 0 0 1 31.87-13.341H716.52zM512.574 617.339c9.06 0 16.989-3.216 23.738-9.581l117.103-112.415a32.622 32.622 0 0 0 10.691-24.62c0-9.469-3.33-17.533-9.966-24.191a32.958 32.958 0 0 0-24.237-10.012c-9.06 0-16.988 3.171-23.737 9.56l-93.547 89.808-93.614-89.809a33.185 33.185 0 0 0-23.443-9.559c-9.468 0-17.532 3.33-24.19 9.967-6.66 6.682-9.967 14.722-9.967 24.236 0 9.83 3.443 18.03 10.419 24.599l117.33 112.413c6.342 6.342 14.179 9.56 23.466 9.56l-.046.044z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-qq"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#5EAADE" />
<path
d="M729.46 627.3c-3.157-39.628-24.045-83.747-32.624-105.91l-22.084-57.047c-.702-23.73 6.312-78.322-30.511-146.61s-110.82-74.446-124.497-75.147c-13.677-.701-99.248-1.403-141.331 72.945-42.084 74.347-30.745 148.812-30.745 148.812l-23.523 57.478c-.001.002-10.962 26.223-20.43 58.135-9.469 31.914-18.938 82.064-9.469 92.234 9.47 10.17 43.837-46.643 46.993-51.903 0 0 2.456 27.18 8.943 41.383l.81 1.776.33.723.38.826.3.652.444.96.203.436a281.465 281.465 0 0 0 1.917 4.025l.189.386c.231.473.468.953.711 1.442l.146.292c6.886 13.807 18.61 33.823 37.443 50.42l.018.016-1.184.387c-10.667 3.516-31.694 11.21-40.625 19.82-1.717 1.655-2.987 3.344-3.65 5.045-5.376 13.794 4.208 15.43 20.575 16.366 16.366.934 94.923 3.04 132.564-2.221.407-.056.787-.114 1.17-.171 2.711.094 5.324.142 7.83.16l.151.002c.836.005 1.663.008 2.475.008.496 0 1.015-.002 1.542-.006l.21-.001a222.593 222.593 0 0 0 5.462-.107c.26.038.508.076.778.114 37.642 5.26 116.198 3.156 132.564 2.22 16.366-.934 25.951-2.571 20.574-16.365-4.302-11.037-34.175-21.62-45.956-25.413a141.388 141.388 0 0 0 7.958-7.645l.237-.245a142.494 142.494 0 0 0 2.53-2.702c42.435-46.643 38.928-76.101 40.682-92.935 0 0 35.775 51.553 43.488 53.306 7.713 1.754 10.169-6.31 7.012-45.94z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-qzone"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#0985DD" />
<path
d="M722.38 595.24c22.486-4.056 11.345-12.424 2.156-11.346-16.685 1.72-40.43 1.925-66.562 1.284l3.029 17.79a656.641 656.641 0 0 0 61.402-7.702l-.025-.026zm68.95-174.915a5.287 5.287 0 0 0-4.493-3.645L598.42 389.29l-84.326-170.628c-1.925-3.594-7.958-3.594-9.857 0L419.885 389.29l-188.417 27.39a5.338 5.338 0 0 0-4.466 3.645 5.493 5.493 0 0 0 1.488 5.57l136.36 132.92-32.088 187.519a5.263 5.263 0 0 0 2.13 5.39c1.695 1.284 3.851 1.463 5.776.385l168.651-88.407 168.524 88.638 2.567.642 3.209-1.079c1.72-1.283 2.566-3.208 2.13-5.34l-24.591-143.648c-27.21 2.156-54.37 3.183-76.42 3.183-77.267 0-135.075-3.645-135.948-3.645a16.48 16.48 0 0 1-14.785-11.757 16.247 16.247 0 0 1 5.981-17.764l155.431-113.05c-99.959-7.906-183.873-6.418-184.721-6.418-13.502.642-25.67-3.645.642-14.375 4.518-1.694 109.2-23.72 230.362-7.445 6.673.847 12.013 5.75 13.733 12.194a16.61 16.61 0 0 1-6.263 17.302L497.204 571.598c27.826 5.802 100.37 12.014 160.745 13.502l-4.519-26.312 136.308-132.97a5.338 5.338 0 0 0 1.54-5.544l.051.051z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-reddit"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#EB5528" />
<path
d="M617.199 680.55c5.666 5.974 5.666 11.742 0 17.34-21.845 23.143-56.9 34.714-105.199 34.714s-83.354-11.571-105.199-34.714c-5.666-5.598-5.666-11.366 0-17.34a10.445 10.445 0 0 1 7.919-3.379c3.174 0 5.803 1.127 7.919 3.38 16.93 18.295 46.728 27.408 89.361 27.408 42.325 0 72.09-9.113 89.361-27.409a10.445 10.445 0 0 1 7.919-3.379c3.174 0 5.803 1.127 7.919 3.38zm-176.06-136.635c9.182 9.694 13.756 21.47 13.756 35.294 0 13.79-4.574 25.565-13.756 35.26a44.134 44.134 0 0 1-33.28 14.54c-13.073 0-24.234-4.847-33.587-14.54a49.015 49.015 0 0 1-13.995-35.26c0-14.2 4.642-26.147 13.995-35.84 9.353-9.728 20.514-14.575 33.587-14.575 13.04 0 24.132 5.051 33.28 15.12zm222.584 35.294c0 13.79-4.642 25.565-13.995 35.26a44.954 44.954 0 0 1-33.587 14.54c-13.04 0-24.132-4.847-33.28-14.54a49.493 49.493 0 0 1-13.756-35.26c0-13.824 4.574-25.669 13.756-35.567 9.148-9.9 20.24-14.848 33.28-14.848 13.073 0 24.234 4.847 33.587 14.575 9.353 9.693 13.995 21.64 13.995 35.84zM796.433 512c0-18.295-6.144-33.963-18.5-47.036a59.494 59.494 0 0 0-44.92-19.592c-17.647 0-32.768 6.724-45.465 20.138-45.841-33.587-100.66-51.507-164.455-53.725l33.314-158.482 105.746 25.19c0 13.825 4.573 25.6 13.755 35.295 9.148 9.694 20.241 14.54 33.314 14.54 13.04 0 24.235-4.915 33.553-14.813 9.353-9.899 13.995-21.743 13.995-35.567s-4.642-25.669-13.995-35.567a44.578 44.578 0 0 0-33.553-14.814c-19.046 0-33.143 9.318-42.325 27.99L550.06 228.112c-6.69-1.877-11.094 1.126-13.21 8.977l-36.488 174.695c-63.454 2.594-117.897 20.718-163.363 54.272a59.187 59.187 0 0 0-46.011-20.685c-17.613 0-32.598 6.52-44.92 19.592a66.082 66.082 0 0 0-18.5 47.036c0 13.073 3.243 25.02 9.762 35.84 6.52 10.82 15.258 19.046 26.18 24.644a152.303 152.303 0 0 0-3.174 31.335c0 53.009 24.678 98.372 74.035 136.09 49.323 37.682 108.715 56.524 178.176 56.524 69.769 0 129.365-18.842 178.688-56.525 49.357-37.717 74.001-83.08 74.001-136.09 0-11.946-1.229-22.561-3.686-31.914 10.581-5.598 19.046-13.722 25.395-24.337 6.315-10.65 9.49-22.528 9.49-35.567z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-rss"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#FD9B00" />
<path
d="M687.981 740.39c0-225.92-183.617-409.777-409.21-409.777v-97.205c279.353 0 506.617 227.506 506.617 506.98H687.98zm-74.841 0h-97.538c0-63.567-24.688-123.245-69.43-167.993-44.762-44.856-104.24-69.556-167.54-69.556v-97.176c184.44 0 334.508 150.046 334.508 334.725zM346.038 605.166c37.35 0 67.514 30.357 67.514 67.39 0 37.146-30.163 67.177-67.514 67.177-37.219 0-67.458-30.03-67.458-67.176 0-37.034 30.24-67.391 67.458-67.391z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-steam"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cy="512" cx="512" fill="#fff" r="488" />
<path
d="M1008 512c0 274-222.4 496-496.8 496-227.6 0-419.2-152.6-478-360.8l190.4 78.6c12.8 64.2 69.8 112.8 137.8 112.8 78.4 0 143.8-64.8 140.4-147l169-120.4c104.2 2.6 191.6-81.8 191.6-187 0-103.2-84-187-187.4-187s-187.4 84-187.4 187v2.4L369.2 558c-31-1.8-61.4 6.8-87 24.2L16 472.2C36.4 216.8 250.2 16 511.2 16 785.6 16 1008 238 1008 512zM327.4 768.6l-61-25.2a105.58 105.58 0 0 0 54.4 51.6c53.8 22.4 115.6-3.2 138-56.8 10.8-26 11-54.6.2-80.6-10.8-26-31-46.4-57-57.2-25.8-10.8-53.4-10.4-77.8-1.2l63 26c39.6 16.4 58.4 61.8 41.8 101.4-16.6 39.8-62 58.4-101.6 42zM675 508.8c-68.8 0-124.8-56-124.8-124.6s56-124.6 124.8-124.6 124.8 56 124.8 124.6S744 508.8 675 508.8zm.2-31.2c51.8 0 93.8-42 93.8-93.6 0-51.8-42-93.6-93.8-93.6s-93.8 42-93.8 93.6c.2 51.6 42.2 93.6 93.8 93.6z"
fill="#13227a"
/>
</svg>
</template>
<template>
<svg
class="icon icon-twitter"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#5EAADE" />
<path
d="M749.737 364.631c-17.594 7.805-36.513 13.088-56.371 15.459 20.269-12.148 35.836-31.387 43.156-54.312A196.233 196.233 0 0 1 674.2 349.6c-17.894-19.083-43.406-30.997-71.636-30.997-54.2 0-98.137 43.944-98.137 98.157 0 7.695.861 15.19 2.544 22.373-81.57-4.092-153.876-43.174-202.284-102.558-8.443 14.498-13.285 31.356-13.285 49.348 0 34.05 17.326 64.096 43.656 81.697a97.69 97.69 0 0 1-44.447-12.277c-.01.41-.01.82-.01 1.24 0 47.558 33.822 87.23 78.72 96.249a98.285 98.285 0 0 1-25.852 3.448 97.491 97.491 0 0 1-18.465-1.768c12.483 39.002 48.725 67.38 91.672 68.17-33.582 26.334-75.897 42.024-121.884 42.024-7.924 0-15.736-.46-23.408-1.37 43.434 27.844 95.014 44.104 150.443 44.104 180.505 0 279.221-149.576 279.221-279.294 0-4.263-.09-8.494-.278-12.708 19.178-13.835 35.813-31.115 48.967-50.807z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-wechat"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#1AC88E" />
<path
d="M827.551 578.742a176.583 176.583 0 0 0-185.685-158.379 172.942 172.942 0 0 0-186.3 158.379 172.942 172.942 0 0 0 185.686 158.379 282.169 282.169 0 0 0 65.536-10.923l60.689 32.768-16.384-54.613a166.275 166.275 0 0 0 76.458-125.611zm-245.76-27.307a21.845 21.845 0 1 1 0-43.69 24.872 24.872 0 0 1 27.307 21.845 24.872 24.872 0 0 1-27.921 21.845h.614zm121.356 0a21.845 21.845 0 1 1 0-43.69 24.872 24.872 0 0 1 27.306 21.845 24.872 24.872 0 0 1-28.512 21.845h1.206z"
fill="#FFF"
/>
<path
d="M623.662 400.953h21.23A222.709 222.709 0 0 0 419.772 245.6a208.145 208.145 0 0 0-223.323 189.94 182.044 182.044 0 0 0 89.201 150.483l-22.436 67.356 78.279-39.435a389.575 389.575 0 0 0 78.279 10.923h20.616a163.226 163.226 0 0 1-6.667-46.718 182.044 182.044 0 0 1 189.94-177.197zm-121.379-60.69a27.921 27.921 0 1 1 0 55.843 31.562 31.562 0 0 1-33.36-27.921 31.562 31.562 0 0 1 34.59-27.921h-1.23zM346.34 396.107a31.562 31.562 0 0 1-33.383-27.921 31.562 31.562 0 0 1 33.383-27.921 27.921 27.921 0 1 1 0 55.842z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-weibo"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#E6162D" />
<path
d="M745.314 454.802c9.652 0 17.869-7.258 19.239-16.728a8.39 8.39 0 0 0 .261-2.12C779.445 302.233 657.02 325.25 657.02 325.25c-10.869 0-19.567 8.94-19.567 20.089 0 10.97 8.698 19.907 19.567 19.907 87.95-19.732 68.54 69.649 68.54 69.649-.004 11.06 8.842 19.908 19.754 19.908z"
fill="#fff"
/>
<path
d="M731.054 221.409c-42.342-10.077-85.862-1.393-98.055.981-.938.09-1.829.994-2.697 1.17-.415.088-.673.532-.673.532-12.045 3.457-20.828 14.77-20.828 28.14 0 15.932 12.694 29.034 28.564 29.034 0 0 15.39-2.097 25.846-6.252 10.364-4.246 98.012-3.16 141.576 71.17 23.734 54.247 10.428 90.553 8.778 96.387 0 0-5.653 14.095-5.653 27.973 0 16.024 12.694 26.083 28.433 26.083 13.169 0 24.211-1.821 27.452-24.447h.172c46.768-158.386-57.183-232.81-132.915-250.771zm-44.083 282.78c-28.28-5.579-14.519-21.062-14.519-21.062s27.67-46.38-5.482-80.099c-41.104-41.761-140.966 5.314-140.966 5.314-38.144 12.032-28.02-5.49-22.629-35.31 0-35.13-11.844-94.596-113.445-59.47-101.49 35.309-188.654 159.03-188.654 159.03-60.603 82.207-52.56 145.747-52.56 145.747 15.128 140.268 161.749 178.772 275.782 187.89 119.967 9.564 281.905-42.045 330.988-148.064 49.105-106.193-40.126-148.22-68.515-153.975zM433.387 766.675c-119.124 5.658-215.394-55.053-215.394-135.851 0-80.887 96.27-145.748 215.394-151.328 119.162-5.58 215.634 44.333 215.634 125.052.002 80.79-96.475 156.626-215.634 162.127z"
fill="#fff"
/>
<path
d="M409.603 532.773c-119.77 14.249-105.943 128.31-105.943 128.31s-1.22 36.117 32.126 54.513c70.084 38.593 142.248 15.224 178.723-32.634 36.474-47.888 15.086-164.346-104.906-150.189zM379.39 692.856c-22.343 2.665-40.385-10.437-40.385-29.463 0-18.94 16.02-38.768 38.387-41.143 25.694-2.485 42.431 12.56 42.431 31.588-.003 18.936-18.128 36.449-40.433 39.018zm70.626-61.146c-7.59 5.754-16.893 4.958-20.892-1.948-4.175-6.726-2.607-17.52 5.046-23.19 8.863-6.714 18.105-4.779 22.106 1.958 4.02 6.893 1.153 17.246-6.26 23.18z"
fill="#fff"
/>
</svg>
</template>
<template>
<svg
class="icon icon-whatsapp"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#31B84C" />
<path
d="m192.021 832 45.227-164.33a315.413 315.413 0 0 1-42.539-158.529C194.731 334.251 337.707 192 513.344 192c84.587-.213 165.76 33.28 225.387 93.013A314.453 314.453 0 0 1 832 509.376c-.085 174.848-143.04 317.141-318.656 317.141h-.15a319.61 319.61 0 0 1-152.277-38.613L192 832h.021zm241.686-455.467c-6.443-15.445-13.014-13.354-17.92-13.61-4.63-.214-9.942-.256-15.254-.256a29.227 29.227 0 0 0-21.226 9.898c-7.296 7.958-27.84 27.136-27.84 66.134s28.501 76.672 32.49 81.962c3.968 5.291 56.15 85.334 136 119.638 19.008 8.17 33.814 13.056 45.398 16.704 19.072 6.037 36.437 5.184 50.133 3.157 15.296-2.283 47.125-19.2 53.76-37.675 6.613-18.56 6.613-34.389 4.65-37.717-1.983-3.264-7.295-5.27-15.274-9.237-7.957-3.947-47.125-23.126-54.4-25.771-7.296-2.667-12.587-3.968-17.92 3.947-5.312 7.936-20.565 25.792-25.195 31.061-4.65 5.312-9.301 5.973-17.258 2.005-7.979-3.968-33.622-12.33-64-39.338-23.68-20.992-39.68-46.955-44.331-54.912-4.65-7.915-.47-12.203 3.52-16.15 3.563-3.541 7.936-9.258 11.904-13.866 3.99-4.651 5.333-7.958 7.979-13.227 2.645-5.29 1.322-9.92-.64-13.888-2.006-3.968-17.92-42.987-24.555-58.859h-.021z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-youtube"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#DD1829" />
<path
d="M800.305 372.2c-12.805-42.429-22.873-65.942-65.303-71.064 0 0-113.644-5.761-226.64-5.761-111.716 0-222.797 5.761-222.797 5.761-44.992 5.122-55.7 29.915-67.223 71.065 0 0-11.524 65.527-11.524 131.886 0 68.066 11.524 137.008 11.524 137.008 8.963 39.87 27.354 65.943 67.223 71.065 0 0 123.292 7.682 240.724 7.682 106.78 0 208.714-7.682 208.714-7.682 39.87-7.682 53.78-28.635 65.303-71.065 0 0 11.523-63.022 11.523-128.045 0-69.288-11.524-140.85-11.524-140.85zM448.82 619.97V393.33l174.781 113.32L448.82 619.97z"
fill="#FFF"
/>
</svg>
</template>
<template>
<svg
class="icon icon-zhihu"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="512" cy="512" r="512" fill="#006CE2" />
<path
d="M513.65 491.261H411.551c1.615-16.154 5.815-60.095 5.815-84.973 0-24.88-.323-60.742-.323-60.742h102.744V329.39c0-21.647-9.37-31.34-17.124-31.34h-178.67s5.169-17.77 10.015-36.186c4.846-18.417 15.832-44.264 15.832-44.264-63.003 4.2-67.958 50.941-81.743 92.729-13.787 41.785-24.556 62.356-44.586 107.912 27.786 0 55.249-13.57 66.879-32.309 11.631-18.74 16.908-40.71 16.908-40.71h62.035v59.019c0 21.107-3.878 87.45-3.878 87.45H254.742c-19.386 0-29.724 48.894-29.724 48.894h133.76c-8.4 75.82-26.493 106.191-51.91 152.716-25.418 46.525-92.728 99.406-92.728 99.406 41.033 11.63 86.589-3.555 105.974-21.972 19.386-18.417 35.863-49.756 47.817-72.838 11.954-23.081 21.972-65.124 21.972-65.124L498.462 766.86s4.846-24.233 6.461-39.418c1.616-15.186-.755-26.385-4.63-35.433-3.878-9.046-15.509-21.54-31.018-39.634-15.507-18.094-48.034-52.879-48.034-52.879s-15.832 11.63-28.108 21.001c9.046-21.97 16.262-79.695 16.262-79.695h122.343v-20.249c.003-17.66-7.319-29.29-18.089-29.29zm287.337-200.747h-234.35a4.308 4.308 0 0 0-4.309 4.308v435.099a4.308 4.308 0 0 0 4.308 4.308h40.226l14.7 50.402 81.096-50.402h98.328a4.308 4.308 0 0 0 4.308-4.308v-435.1a4.308 4.308 0 0 0-4.308-4.308zM755.97 684.47h-52.343l-61.548 39.095-10.823-39.095h-18.738V338.116H755.97v346.355z"
fill="#FFF"
/>
</svg>
</template>
import type { Context, PluginOptionAPI } from "@mr-hope/vuepress-types";
import type { HopeNavBarConfig, HopeSideBarConfig, HopeThemeConfig, ResolvedHopeThemeConfig } from "./types";
declare const themeAPI: {
(themeConfig: ResolvedHopeThemeConfig, ctx: Context): PluginOptionAPI;
config: (config: import("./types").HopeVuePressConfig) => import("./types").ResolvedHopeVuePressConfig;
themeConfig(themeConfig: HopeThemeConfig): HopeThemeConfig;
navbarConfig(navbarConfig: HopeNavBarConfig): HopeNavBarConfig;
sidebarConfig(sidebarConfig: HopeSideBarConfig): HopeSideBarConfig;
};
export = themeAPI;
"use strict";
const alias_1 = require("./node/alias");
const config_1 = require("./node/config");
const eject_1 = require("./node/eject");
const plugins_1 = require("./node/plugins");
const blogAddtionalPages = [
{
path: "/article/",
frontmatter: { layout: "Blog" },
},
{
path: "/star/",
frontmatter: { layout: "Blog" },
},
{
path: "/encrypt/",
frontmatter: { layout: "Blog" },
},
{
path: "/slide/",
frontmatter: { layout: "Blog" },
},
{
path: "/timeline/",
frontmatter: { layout: "Blog" },
},
];
// Theme API.
const themeAPI = (themeConfig, ctx) => ({
alias: (0, alias_1.getAlias)(themeConfig, ctx),
plugins: (0, plugins_1.getPluginConfig)(themeConfig),
additionalPages: themeConfig.blog === false ? [] : blogAddtionalPages,
extendCli: (cli) => {
cli
.command("eject-hope [targetDir]", "copy vuepress-theme-hope into .vuepress/theme for customization.")
.option("--debug", "eject in debug mode")
.action((dir) => {
void (0, eject_1.eject)(dir || ".");
});
},
});
themeAPI.config = config_1.config;
// helper functions
themeAPI.themeConfig = (themeConfig) => themeConfig;
themeAPI.navbarConfig = (navbarConfig) => navbarConfig;
themeAPI.sidebarConfig = (sidebarConfig) => sidebarConfig;
module.exports = themeAPI;
//# sourceMappingURL=index.js.map
\ No newline at end of file
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AACA,wCAAwC;AACxC,0CAAuC;AACvC,wCAAqC;AACrC,4CAAiD;AAUjD,MAAM,kBAAkB,GAAG;IACzB;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;KAChC;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;KAChC;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;KAChC;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;KAChC;IACD;QACE,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;KAChC;CACF,CAAC;AAEF,aAAa;AACb,MAAM,QAAQ,GAAG,CACf,WAAoC,EACpC,GAAY,EACK,EAAE,CAAC,CAAC;IACrB,KAAK,EAAE,IAAA,gBAAQ,EAAC,WAAW,EAAE,GAAG,CAAC;IAEjC,OAAO,EAAE,IAAA,yBAAe,EAAC,WAAW,CAAC;IAErC,eAAe,EAAE,WAAW,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kBAAkB;IAErE,SAAS,EAAE,CAAC,GAAQ,EAAQ,EAAE;QAC5B,GAAG;aACA,OAAO,CACN,wBAAwB,EACxB,kEAAkE,CACnE;aACA,MAAM,CAAC,SAAS,EAAE,qBAAqB,CAAC;aACxC,MAAM,CAAC,CAAC,GAAW,EAAE,EAAE;YACtB,KAAK,IAAA,aAAK,EAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACP,CAAC;CACF,CAAC,CAAC;AAEH,QAAQ,CAAC,MAAM,GAAG,eAAM,CAAC;AAEzB,mBAAmB;AACnB,QAAQ,CAAC,WAAW,GAAG,CAAC,WAA4B,EAAmB,EAAE,CACvE,WAAW,CAAC;AACd,QAAQ,CAAC,YAAY,GAAG,CAAC,YAA8B,EAAoB,EAAE,CAC3E,YAAY,CAAC;AACf,QAAQ,CAAC,aAAa,GAAG,CACvB,aAAgC,EACb,EAAE,CAAC,aAAa,CAAC;AAEtC,iBAAS,QAAQ,CAAC"}
\ No newline at end of file
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, {
back(): void;
}, {
i18n: {
hint: string[];
home: string;
back: string;
};
msg: string;
}, Record<never, any>>;
export default _default;
import Vue from "vue";
import Common from "@theme/components/Common.vue";
import Page404Icon from "@theme/icons/Page404Icon.vue";
import { getDefaultLocale } from "@mr-hope/vuepress-shared";
export default Vue.extend({
name: "NotFound",
components: {
Common,
Page404Icon,
},
computed: {
i18n() {
return this.$themeLocaleConfig.error404 || getDefaultLocale().error404;
},
msg() {
return this.i18n.hint[Math.floor(Math.random() * this.i18n.hint.length)];
},
},
methods: {
back() {
window.history.go(-1);
},
},
});
//# sourceMappingURL=404.js.map
\ No newline at end of file
{"version":3,"file":"404.js","sourceRoot":"","sources":["404.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,MAAM,MAAM,8BAA8B,CAAC;AAClD,OAAO,WAAW,MAAM,8BAA8B,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAI5D,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU;IAEhB,UAAU,EAAE;QACV,MAAM;QACN,WAAW;KACZ;IAED,QAAQ,EAAE;QACR,IAAI;YACF,OAAO,IAAI,CAAC,kBAAkB,CAAC,QAAQ,IAAI,gBAAgB,EAAE,CAAC,QAAQ,CAAC;QACzE,CAAC;QAED,GAAG;YACD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC3E,CAAC;KACF;IAED,OAAO,EAAE;QACP,IAAI;YACF,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<Common :sidebar="false">
<main class="page not-found">
<Page404Icon />
<blockquote v-text="msg" />
<button class="action-button" @click="back">{{ i18n.back }}</button>
<RouterLink class="action-button" to="/">{{ i18n.home }}</RouterLink>
</main>
</Common>
</template>
<script src="./404" />
<style lang="stylus">
.page.not-found
display block
max-width $homePageWidth
margin 0px auto
padding ($navbarHeight + 1rem) 2rem 2rem 2rem !important
text-align center
.page-404-icon
width 50%
margin 0 auto
.action-button
display inline-block
box-sizing border-box
margin 0 0.25rem
padding 0.5rem 1rem
border-width 0
border-bottom 1px solid var(--accent-color-d10)
border-radius 0.25rem
background var(--accent-color)
color var(--white)
font-size 1rem
outline none
transition background 0.1s ease
&:hover
cursor pointer
background var(--accent-color-l10)
</style>
import BlogInfo from "@BlogInfo";
declare const _default: import("vue/types/vue").ExtendedVue<Record<never, any> & {
globalEncryptPassword: string;
} & {
checkGlobalPassword(globalPassword: string): void;
} & {
isGlobalEncrypted: boolean;
} & {
encryptOptions: import("../types").EncryptOptions;
} & BlogInfo, unknown, unknown, unknown, Record<never, any>>;
export default _default;
import BlogInfo from "@BlogInfo";
import BlogPage from "@BlogPage";
import Common from "@theme/components/Common.vue";
import MyTransition from "@theme/components/MyTransition.vue";
import { globalEncryptMixin } from "@theme/mixins/globalEncrypt";
import { pathEncryptMixin } from "@theme/mixins/pathEncrypt";
import Password from "@theme/components/Password.vue";
export default globalEncryptMixin.extend(pathEncryptMixin).extend({
components: {
BlogInfo,
BlogPage,
Common,
MyTransition,
Password,
},
});
//# sourceMappingURL=Blog.js.map
\ No newline at end of file
{"version":3,"file":"Blog.js","sourceRoot":"","sources":["Blog.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,8BAA8B,CAAC;AAClD,OAAO,YAAY,MAAM,oCAAoC,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,QAAQ,MAAM,gCAAgC,CAAC;AAEtD,eAAe,kBAAkB,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC;IAChE,UAAU,EAAE;QACV,QAAQ;QACR,QAAQ;QACR,MAAM;QACN,YAAY;QACZ,QAAQ;KACT;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<Common :sidebar="false">
<template #sidebar-bottom>
<BlogInfo />
</template>
<Password v-if="isGlobalEncrypted" @password-verify="checkGlobalPassword" />
<Password
v-else-if="isPathEncrypted"
@password-verify="checkPathPassword"
/>
<main v-else class="page blog">
<div class="blog-page-wrapper">
<BlogPage />
<MyTransition :delay="0.16">
<BlogInfo />
</MyTransition>
</div>
</main>
</Common>
</template>
<script src="./Blog" />
<style lang="stylus">
.page.blog
box-sizing border-box
min-height 100vh
margin 0px auto
padding-top $navbarHeight
padding-bottom 2rem
background var(--bgcolor-light)
display flex
flex-direction column
justify-content space-between
@media (max-width $MQMobile)
padding $navbarMobileHeight 1.5rem 2rem
@media (max-width $MQMobileNarrow)
padding-left 0
padding-right 0
.blog-page-wrapper
box-sizing border-box
width 100%
margin 0 auto
display flex
justify-content center
align-items flex-start
@media (min-width $MQMobile)
padding 0 1rem
@media (min-width $MQNarrow)
padding 0 2rem
@media (min-width $MQWide)
padding 0
</style>
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, unknown, Record<never, any>>;
export default _default;
import Vue from "vue";
import BlogInfo from "@BlogInfo";
import BlogHome from "@BlogHome";
import ContentBottom from "@ContentBottom";
import ContentTop from "@ContentTop";
import NavbarStart from "@NavbarStart";
import NavbarCenter from "@NavbarCenter";
import NavbarEnd from "@NavbarEnd";
import PageBottom from "@PageBottom";
import PageTop from "@PageTop";
import SidebarBottom from "@SidebarBottom";
import SidebarCenter from "@SidebarCenter";
import SidebarTop from "@SidebarTop";
import Common from "@theme/components/Common.vue";
import Home from "@theme/components/Home.vue";
import Page from "@theme/components/Page.vue";
export default Vue.extend({
name: "Layout",
components: {
BlogInfo,
BlogHome,
Common,
ContentBottom,
ContentTop,
Home,
NavbarCenter,
NavbarEnd,
NavbarStart,
Page,
PageBottom,
PageTop,
SidebarBottom,
SidebarCenter,
SidebarTop,
},
});
//# sourceMappingURL=Layout.js.map
\ No newline at end of file
{"version":3,"file":"Layout.js","sourceRoot":"","sources":["Layout.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAC3C,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,OAAO,MAAM,UAAU,CAAC;AAC/B,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAC3C,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAC3C,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,MAAM,MAAM,8BAA8B,CAAC;AAClD,OAAO,IAAI,MAAM,4BAA4B,CAAC;AAC9C,OAAO,IAAI,MAAM,4BAA4B,CAAC;AAE9C,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,QAAQ;IAEd,UAAU,EAAE;QACV,QAAQ;QACR,QAAQ;QACR,MAAM;QACN,aAAa;QACb,UAAU;QACV,IAAI;QACJ,YAAY;QACZ,SAAS;QACT,WAAW;QACX,IAAI;QACJ,UAAU;QACV,OAAO;QACP,aAAa;QACb,aAAa;QACb,UAAU;KACX;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<Common :sidebar="$frontmatter.blog !== true">
<template #navbar-start>
<slot name="navbar-start">
<NavbarStart />
<Content slot-key="navbar-start" />
</slot>
</template>
<template #navbar-center>
<slot name="navbar-center">
<NavbarCenter />
<Content slot-key="navbar-center" />
</slot>
</template>
<template #navbar-end>
<slot name="navbar-end">
<NavbarEnd />
<Content slot-key="navbar-end" />
</slot>
</template>
<template #sidebar-top>
<slot name="sidebar-top">
<SidebarTop />
<Content slot-key="sidebar-top" />
</slot>
</template>
<template #sidebar-center>
<slot name="sidebar-center">
<SidebarCenter />
<Content slot-key="sidebar-center" />
</slot>
</template>
<template #sidebar-bottom>
<slot name="sidebar-bottom">
<SidebarBottom />
<Content slot-key="sidebar-bottom" />
</slot>
</template>
<template #default="slotProps">
<BlogHome v-if="$frontmatter.blog && $themeConfig.blog !== false" />
<Home v-else-if="$frontmatter.home" />
<Page
v-else
:headers="slotProps.headers"
:sidebar-items="slotProps.sidebarItems"
>
<template #top>
<slot name="page-top">
<PageTop />
<Content slot-key="page-top" />
</slot>
</template>
<template #content-top>
<slot name="content-top">
<ContentTop />
<Content slot-key="content-top" />
</slot>
</template>
<template #content-bottom>
<slot name="content-bottom">
<ContentBottom />
<Content slot-key="content-bottom" />
</slot>
</template>
<template #bottom>
<slot name="page-bottom">
<PageBottom />
<Content slot-key="page-bottom" />
</slot>
</template>
</Page>
</template>
</Common>
</template>
<script src="./Layout" />
import Vue from "vue";
declare const _default: import("vue/types/vue").ExtendedVue<Vue, {
showMenu: boolean;
}, {
toggle(): void;
back(): void;
home(): void;
clickOutside(): void;
}, unknown, Record<never, any>>;
export default _default;
import Vue from "vue";
import ClickOutside from "@theme/utils/click-outside";
import ThemeColor from "@theme/components/Theme/ThemeColor.vue";
export default Vue.extend({
name: "Slide",
components: { ThemeColor },
directives: { "click-outside": ClickOutside },
data: () => ({
showMenu: false,
}),
// eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle
destroyed() {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
document.querySelector("html").classList.remove("reveal-full-page");
document.body.classList.remove("reveal-viewport");
document.body.style.removeProperty("--slide-width");
document.body.style.removeProperty("--slide-height");
},
methods: {
toggle() {
this.showMenu = !this.showMenu;
},
back() {
window.history.go(-1);
this.showMenu = false;
},
home() {
void this.$router.push("/");
this.showMenu = false;
},
clickOutside() {
this.showMenu = false;
},
},
});
//# sourceMappingURL=Slide.js.map
\ No newline at end of file
{"version":3,"file":"Slide.js","sourceRoot":"","sources":["Slide.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,YAAY,MAAM,4BAA4B,CAAC;AACtD,OAAO,UAAU,MAAM,wCAAwC,CAAC;AAEhE,eAAe,GAAG,CAAC,MAAM,CAAC;IACxB,IAAI,EAAE,OAAO;IAEb,UAAU,EAAE,EAAE,UAAU,EAAE;IAE1B,UAAU,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;IAE7C,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,KAAK;KAChB,CAAC;IAEF,iEAAiE;IACjE,SAAS;QACP,oEAAoE;QACpE,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAE,CAAC,SAAS,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACrE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAClD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;QACpD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,EAAE;QACP,MAAM;YACJ,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;QACjC,CAAC;QAED,IAAI;YACF,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;QAED,IAAI;YACF,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;QAED,YAAY;YACV,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACxB,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
<template>
<div class="presentation">
<ThemeColor v-show="false" />
<Content :key="$route.path" class="presentation-content" />
<div
v-click-outside="clickOutside"
class="menu"
:class="{ active: showMenu }"
>
<button class="menu-button" @click="toggle">
<span class="icon" />
</button>
<button class="back-button" @click="back">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M1014.749 449.156v125.688H260.626l345.64 345.64-89.239 89.237L19.307 512l497.72-497.721 89.238 89.238-345.64 345.64h754.124z"
/>
</svg>
</button>
<button class="home-button" @click="home">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path
d="M780.106 420.978L506.994 147.866 233.882 420.978h.045v455.11H780.06v-455.11h.046zm90.977 90.976V876.09a91.022 91.022 0 01-91.023 91.022H233.927a91.022 91.022 0 01-91.022-91.022V511.954l-67.22 67.175-64.307-64.307 431.309-431.31c35.498-35.498 93.115-35.498 128.614 0l431.309 431.31-64.307 64.307L871.083 512z"
/>
</svg>
</button>
</div>
</div>
</template>
<script src="./Slide" />
<style lang="stylus">
@require '~@mr-hope/vuepress-shared/styles/reset'
.presentation
.md-presentation
min-width 100vw
min-height 100vh
.menu-button, .back-button, .home-button
button()
box-sizing content-box
position fixed
bottom 2rem
width 1rem
height 1rem
padding 0.5rem
border-radius 50%
background #bbb
color var(--white)
outline none
&:hover
background var(--accent-color)
.theme-dark &
background #666
&:hover
background var(--accent-color)
.menu-button
left 2rem
transition transform 0.2s ease-in-out
vertical-align middle
z-index 50
&::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(--white)
.active .menu-button
&::before
transform translateY(0.4em) rotate(135deg)
.icon
transform scale(0)
&::after
transform translateY(-0.4em) rotate(-135deg)
.back-button
left 2rem
opacity 0
transition left 0.2s ease-out, opacity 0.2s ease-out
z-index 49
fill var(--white)
.active .back-button
left 4.5rem
opacity 1
.home-button
left 2rem
opacity 0
transition left 0.2s ease-out, opacity 0.2s ease-out
z-index 48
fill var(--white)
.active .home-button
left 7rem
opacity 1
</style>
import Vue from "vue";
import type { EncryptOptions } from "../types";
export declare const encryptBaseMixin: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
encryptOptions: EncryptOptions;
}, Record<never, any>>;
import Vue from "vue";
export const encryptBaseMixin = Vue.extend({
computed: {
encryptOptions() {
return this.$themeConfig.encrypt || {};
},
},
});
//# sourceMappingURL=encrypt.js.map
\ No newline at end of file
{"version":3,"file":"encrypt.js","sourceRoot":"","sources":["encrypt.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAItB,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC;IACzC,QAAQ,EAAE;QACR,cAAc;YACZ,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,IAAI,EAAE,CAAC;QACzC,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
import type { EncryptOptions } from "../types";
export declare const globalEncryptMixin: import("vue/types/vue").ExtendedVue<{
encryptOptions: EncryptOptions;
} & Record<never, any> & import("vue").default, {
globalEncryptPassword: string;
}, {
checkGlobalPassword(globalPassword: string): void;
}, {
isGlobalEncrypted: boolean;
}, Record<never, any>>;
import { compareSync } from "bcryptjs";
import { encryptBaseMixin } from "@theme/mixins/encrypt";
export const globalEncryptMixin = encryptBaseMixin.extend({
data: () => ({
globalEncryptPassword: "",
}),
computed: {
isGlobalEncrypted() {
if (this.encryptOptions.status === "global" &&
this.encryptOptions.global) {
const { global } = this.encryptOptions;
const globalPasswords = typeof global === "string" ? [global] : global;
// none of the password matches
return !globalPasswords.some((globalPassword) => compareSync(this.globalEncryptPassword, globalPassword));
}
return false;
},
},
mounted() {
const globalPassword = localStorage.getItem("globalPassword");
if (globalPassword)
this.globalEncryptPassword = globalPassword;
},
methods: {
checkGlobalPassword(globalPassword) {
const { global } = this.encryptOptions;
const globalPasswords = typeof global === "string" ? [global] : global;
if (
// some of the password matches
globalPasswords.some((password) => compareSync(globalPassword, password))) {
this.globalEncryptPassword = globalPassword;
localStorage.setItem("globalPassword", globalPassword);
}
},
},
});
//# sourceMappingURL=globalEncrypt.js.map
\ No newline at end of file
{"version":3,"file":"globalEncrypt.js","sourceRoot":"","sources":["globalEncrypt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,MAAM,CAAC;IACxD,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,qBAAqB,EAAE,EAAE;KAC1B,CAAC;IAEF,QAAQ,EAAE;QACR,iBAAiB;YACf,IACE,IAAI,CAAC,cAAc,CAAC,MAAM,KAAK,QAAQ;gBACvC,IAAI,CAAC,cAAc,CAAC,MAAM,EAC1B;gBACA,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,cAAc,CAAC;gBACvC,MAAM,eAAe,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBAEvE,+BAA+B;gBAC/B,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,EAAE,CAC9C,WAAW,CAAC,IAAI,CAAC,qBAAqB,EAAE,cAAc,CAAC,CACxD,CAAC;aACH;YAED,OAAO,KAAK,CAAC;QACf,CAAC;KACF;IAED,OAAO;QACL,MAAM,cAAc,GAAG,YAAY,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAE9D,IAAI,cAAc;YAAE,IAAI,CAAC,qBAAqB,GAAG,cAAc,CAAC;IAClE,CAAC;IAED,OAAO,EAAE;QACP,mBAAmB,CAAC,cAAsB;YACxC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,cAA0C,CAAC;YACnE,MAAM,eAAe,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAEvE;YACE,+BAA+B;YAC/B,eAAe,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAChC,WAAW,CAAC,cAAc,EAAE,QAAQ,CAAC,CACtC,EACD;gBACA,IAAI,CAAC,qBAAqB,GAAG,cAAc,CAAC;gBAC5C,YAAY,CAAC,OAAO,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;aACxD;QACH,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
import type { EncryptOptions } from "../types";
export declare const pathEncryptMixin: import("vue/types/vue").ExtendedVue<{
encryptOptions: EncryptOptions;
} & Record<never, any> & import("vue").default, {
encryptPasswordConfig: Record<string, string>;
}, {
checkPathPassword(password: string): void;
}, {
pathEncryptMatchKeys: string[];
isPathEncrypted: boolean;
}, Record<never, any>>;
import { compareSync } from "bcryptjs";
import { encryptBaseMixin } from "@theme/mixins/encrypt";
import { getPathMatchedKeys } from "@theme/utils/encrypt";
export const pathEncryptMixin = encryptBaseMixin.extend({
data: () => ({
encryptPasswordConfig: {},
}),
computed: {
pathEncryptMatchKeys() {
return getPathMatchedKeys(this.encryptOptions, this.$route.path);
},
isPathEncrypted() {
if (this.pathEncryptMatchKeys.length === 0)
return false;
const { config } = this.encryptOptions;
// none of the password matches
return this.pathEncryptMatchKeys.every((key) => {
const keyConfig = config[key];
const hitPasswords = typeof keyConfig === "string" ? [keyConfig] : keyConfig;
return (!this.encryptPasswordConfig[key] ||
hitPasswords.every((encryptPassword) => !compareSync(this.encryptPasswordConfig[key], encryptPassword)));
});
},
},
mounted() {
const passwordConfig = localStorage.getItem("encryptConfig");
if (passwordConfig)
this.encryptPasswordConfig = JSON.parse(passwordConfig);
},
methods: {
checkPathPassword(password) {
const { config } = this.$themeConfig.encrypt;
for (const hitKey of this.pathEncryptMatchKeys) {
const hitPassword = config[hitKey];
const hitPasswordList = typeof hitPassword === "string" ? [hitPassword] : hitPassword;
// some of the password matches
if (hitPasswordList.filter((encryptPassword) => compareSync(password, encryptPassword))) {
this.$set(this.encryptPasswordConfig, hitKey, password);
localStorage.setItem("encryptConfig", JSON.stringify(this.encryptPasswordConfig));
break;
}
}
},
},
});
//# sourceMappingURL=pathEncrypt.js.map
\ No newline at end of file
{"version":3,"file":"pathEncrypt.js","sourceRoot":"","sources":["pathEncrypt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAI1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,MAAM,CAAC;IACtD,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QACX,qBAAqB,EAAE,EAA4B;KACpD,CAAC;IAEF,QAAQ,EAAE;QACR,oBAAoB;YAClB,OAAO,kBAAkB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnE,CAAC;QAED,eAAe;YACb,IAAI,IAAI,CAAC,oBAAoB,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YAEzD,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,cAA0C,CAAC;YAEnE,+BAA+B;YAC/B,OAAO,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC9B,MAAM,YAAY,GAChB,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBAE1D,OAAO,CACL,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC;oBAChC,YAAY,CAAC,KAAK,CAChB,CAAC,eAAe,EAAE,EAAE,CAClB,CAAC,WAAW,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC,CACjE,CACF,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;KACF;IAED,OAAO;QACL,MAAM,cAAc,GAAG,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAE7D,IAAI,cAAc;YAChB,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAGrD,CAAC;IACN,CAAC;IAED,OAAO,EAAE;QACP,iBAAiB,CAAC,QAAgB;YAChC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,OAAmC,CAAC;YAEzE,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,oBAAoB,EAAE;gBAC9C,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;gBACnC,MAAM,eAAe,GACnB,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;gBAEhE,+BAA+B;gBAC/B,IACE,eAAe,CAAC,MAAM,CAAC,CAAC,eAAe,EAAE,EAAE,CACzC,WAAW,CAAC,QAAQ,EAAE,eAAe,CAAC,CACvC,EACD;oBACA,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;oBACxD,YAAY,CAAC,OAAO,CAClB,eAAe,EACf,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAC3C,CAAC;oBAEF,MAAM;iBACP;aACF;QACH,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
import Vue from "vue";
import type { PageComputed } from "@mr-hope/vuepress-types";
export declare const starMixin: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
$starArticles: PageComputed[];
}, Record<never, any>>;
import Vue from "vue";
import { filterArticle, sortArticle } from "@theme/utils/article";
export const starMixin = Vue.extend({
computed: {
$starArticles() {
const { pages } = this.$site;
// filter before sort
return sortArticle(filterArticle(pages, (page) => Boolean(page.frontmatter.star)), "star");
},
},
});
//# sourceMappingURL=star.js.map
\ No newline at end of file
{"version":3,"file":"star.js","sourceRoot":"","sources":["star.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIlE,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC;IAClC,QAAQ,EAAE;QACR,aAAa;YACX,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAE7B,qBAAqB;YACrB,OAAO,WAAW,CAChB,aAAa,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EAC9D,MAAM,CACP,CAAC;QACJ,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
import Vue from "vue";
import type { PageComputed } from "@mr-hope/vuepress-types";
export interface TimelineItem {
year: number;
articles: PageComputed[];
}
export declare const timelineMixin: import("vue/types/vue").ExtendedVue<Vue, unknown, unknown, {
$timelineItems: PageComputed[];
$timeline: TimelineItem[];
}, Record<never, any>>;
import Vue from "vue";
import { filterArticle, getDate, sortArticle } from "@theme/utils/article";
export const timelineMixin = Vue.extend({
computed: {
$timelineItems() {
const { pages } = this.$site;
// filter before sort
return sortArticle(filterArticle(pages, (page) => Boolean(page.frontmatter.time ||
page.frontmatter.date ||
page.createTimeStamp) && page.frontmatter.timeline !== false));
},
/** Timeline list */
$timeline() {
const timelineItems = [];
// filter before sort
this.$timelineItems.forEach((article) => {
const { frontmatter: { date, time = date }, createTimeStamp, } = article;
const [year, month, day] = getDate((time || createTimeStamp));
if (year && month && day) {
if (!timelineItems[0] || timelineItems[0].year !== year)
timelineItems.unshift({ year, articles: [] });
article.frontmatter.parsedDate = `${month}/${day}`;
timelineItems[0].articles.push(article);
}
});
return timelineItems.reverse();
},
},
});
//# sourceMappingURL=timeline.js.map
\ No newline at end of file
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["timeline.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAS3E,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC;IACtC,QAAQ,EAAE;QACR,cAAc;YACZ,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;YAE7B,qBAAqB;YACrB,OAAO,WAAW,CAChB,aAAa,CACX,KAAK,EACL,CAAC,IAAI,EAAE,EAAE,CACP,OAAO,CACL,IAAI,CAAC,WAAW,CAAC,IAAI;gBACnB,IAAI,CAAC,WAAW,CAAC,IAAI;gBACrB,IAAI,CAAC,eAAe,CACvB,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,KAAK,KAAK,CAC3C,CACF,CAAC;QACJ,CAAC;QAED,oBAAoB;QACpB,SAAS;YACP,MAAM,aAAa,GAAmB,EAAE,CAAC;YAEzC,qBAAqB;YACrB,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBACtC,MAAM,EACJ,WAAW,EAAE,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,EAClC,eAAe,GAChB,GAAG,OAAO,CAAC;gBACZ,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,OAAO,CAChC,CAAC,IAAI,IAAI,eAAe,CAA2B,CACpD,CAAC;gBAEF,IAAI,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE;oBACxB,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI;wBACrD,aAAa,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;oBAEhD,OAAO,CAAC,WAAW,CAAC,UAAU,GAAG,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;oBACnD,aAAa,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;iBACzC;YACH,CAAC,CAAC,CAAC;YAEH,OAAO,aAAa,CAAC,OAAO,EAAE,CAAC;QACjC,CAAC;KACF;CACF,CAAC,CAAC"}
\ No newline at end of file
import type { Context } from "@mr-hope/vuepress-types";
import type { ResolvedHopeThemeConfig } from "../types";
export declare const getAlias: (themeConfig: ResolvedHopeThemeConfig, ctx: Context) => Record<string, string>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAlias = void 0;
const path_1 = require("path");
const getAlias = (themeConfig, ctx) => {
const { siteConfig } = ctx;
// Resolve algolia
const isAlgoliaSearch = Boolean(themeConfig.algolia) ||
Object.keys((siteConfig.locales && themeConfig.locales) || {}).some((base) => themeConfig.locales[base].algolia);
const blogEnabled = themeConfig.blog !== false;
const commentEnabled = themeConfig.comment &&
themeConfig.comment.type &&
themeConfig.comment.type !== "disable";
const themeColorEnabled = !(themeConfig.themeColor === false && themeConfig.darkmode === "disable");
const { custom = {} } = themeConfig;
const noopModule = "@mr-hope/vuepress-shared/lib/esm/noopModule";
return {
"@AlgoliaSearchBox": isAlgoliaSearch
? themeConfig.algoliaType === "full"
? (0, path_1.resolve)(__dirname, "../components/AlgoliaSearch/Full.vue")
: (0, path_1.resolve)(__dirname, "../components/AlgoliaSearch/Dropdown.vue")
: noopModule,
"@BlogInfo": blogEnabled
? (0, path_1.resolve)(__dirname, "../components/Blog/BlogInfo.vue")
: noopModule,
"@BloggerInfo": blogEnabled
? (0, path_1.resolve)(__dirname, "../components/Blog/BloggerInfo.vue")
: noopModule,
"@BlogHome": blogEnabled
? (0, path_1.resolve)(__dirname, "../components/Blog/BlogHome.vue")
: noopModule,
"@BlogPage": blogEnabled
? (0, path_1.resolve)(__dirname, "../components/Blog/BlogPage.vue")
: noopModule,
"@ContentTop": custom.contentTop
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.contentTop)
: noopModule,
"@ContentBottom": custom.contentBottom
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.contentBottom)
: noopModule,
"@PageTop": custom.pageTop
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.pageTop)
: noopModule,
"@PageBottom": custom.pageBottom
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.pageBottom)
: noopModule,
"@Comment": commentEnabled
? "@mr-hope/vuepress-plugin-comment/lib/client/Comment.vue"
: noopModule,
"@NavbarStart": custom.navbarStart
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.navbarStart)
: noopModule,
"@NavbarCenter": custom.navbarCenter
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.navbarCenter)
: noopModule,
"@NavbarEnd": custom.navbarEnd
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.navbarEnd)
: noopModule,
"@ThemeColor": themeColorEnabled
? (0, path_1.resolve)(__dirname, "../components/Theme/ThemeColor.vue")
: noopModule,
"@SidebarTop": custom.sidebarTop
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.sidebarTop)
: noopModule,
"@SidebarCenter": custom.sidebarCenter
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.sidebarCenter)
: noopModule,
"@SidebarBottom": custom.sidebarBottom
? (0, path_1.resolve)(ctx.sourceDir, ".vuepress", custom.sidebarBottom)
: noopModule,
};
};
exports.getAlias = getAlias;
//# sourceMappingURL=alias.js.map
\ No newline at end of file
{"version":3,"file":"alias.js","sourceRoot":"","sources":["alias.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAKxB,MAAM,QAAQ,GAAG,CACtB,WAAoC,EACpC,GAAY,EACY,EAAE;IAC1B,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC;IAC3B,kBAAkB;IAClB,MAAM,eAAe,GACnB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC;QAC5B,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,OAAO,IAAI,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CACjE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAC5C,CAAC;IAEJ,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,KAAK,KAAK,CAAC;IAC/C,MAAM,cAAc,GAClB,WAAW,CAAC,OAAO;QACnB,WAAW,CAAC,OAAO,CAAC,IAAI;QACxB,WAAW,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC;IAEzC,MAAM,iBAAiB,GAAG,CAAC,CACzB,WAAW,CAAC,UAAU,KAAK,KAAK,IAAI,WAAW,CAAC,QAAQ,KAAK,SAAS,CACvE,CAAC;IACF,MAAM,EAAE,MAAM,GAAG,EAAE,EAAE,GAAG,WAAW,CAAC;IAEpC,MAAM,UAAU,GAAG,6CAA6C,CAAC;IAEjE,OAAO;QACL,mBAAmB,EAAE,eAAe;YAClC,CAAC,CAAC,WAAW,CAAC,WAAW,KAAK,MAAM;gBAClC,CAAC,CAAC,IAAA,cAAO,EAAC,SAAS,EAAE,sCAAsC,CAAC;gBAC5D,CAAC,CAAC,IAAA,cAAO,EAAC,SAAS,EAAE,0CAA0C,CAAC;YAClE,CAAC,CAAC,UAAU;QACd,WAAW,EAAE,WAAW;YACtB,CAAC,CAAC,IAAA,cAAO,EAAC,SAAS,EAAE,iCAAiC,CAAC;YACvD,CAAC,CAAC,UAAU;QACd,cAAc,EAAE,WAAW;YACzB,CAAC,CAAC,IAAA,cAAO,EAAC,SAAS,EAAE,oCAAoC,CAAC;YAC1D,CAAC,CAAC,UAAU;QACd,WAAW,EAAE,WAAW;YACtB,CAAC,CAAC,IAAA,cAAO,EAAC,SAAS,EAAE,iCAAiC,CAAC;YACvD,CAAC,CAAC,UAAU;QACd,WAAW,EAAE,WAAW;YACtB,CAAC,CAAC,IAAA,cAAO,EAAC,SAAS,EAAE,iCAAiC,CAAC;YACvD,CAAC,CAAC,UAAU;QACd,aAAa,EAAE,MAAM,CAAC,UAAU;YAC9B,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;YACxD,CAAC,CAAC,UAAU;QACd,gBAAgB,EAAE,MAAM,CAAC,aAAa;YACpC,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC;YAC3D,CAAC,CAAC,UAAU;QACd,UAAU,EAAE,MAAM,CAAC,OAAO;YACxB,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC;YACrD,CAAC,CAAC,UAAU;QACd,aAAa,EAAE,MAAM,CAAC,UAAU;YAC9B,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;YACxD,CAAC,CAAC,UAAU;QACd,UAAU,EAAE,cAAc;YACxB,CAAC,CAAC,yDAAyD;YAC3D,CAAC,CAAC,UAAU;QACd,cAAc,EAAE,MAAM,CAAC,WAAW;YAChC,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC;YACzD,CAAC,CAAC,UAAU;QACd,eAAe,EAAE,MAAM,CAAC,YAAY;YAClC,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC;YAC1D,CAAC,CAAC,UAAU;QACd,YAAY,EAAE,MAAM,CAAC,SAAS;YAC5B,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC;YACvD,CAAC,CAAC,UAAU;QACd,aAAa,EAAE,iBAAiB;YAC9B,CAAC,CAAC,IAAA,cAAO,EAAC,SAAS,EAAE,oCAAoC,CAAC;YAC1D,CAAC,CAAC,UAAU;QACd,aAAa,EAAE,MAAM,CAAC,UAAU;YAC9B,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC;YACxD,CAAC,CAAC,UAAU;QACd,gBAAgB,EAAE,MAAM,CAAC,aAAa;YACpC,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC;YAC3D,CAAC,CAAC,UAAU;QACd,gBAAgB,EAAE,MAAM,CAAC,aAAa;YACpC,CAAC,CAAC,IAAA,cAAO,EAAC,GAAG,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,aAAa,CAAC;YAC3D,CAAC,CAAC,UAAU;KACf,CAAC;AACJ,CAAC,CAAC;AAhFW,QAAA,QAAQ,YAgFnB"}
\ No newline at end of file
import type { Plugin } from "@mr-hope/vuepress-types";
import type { ChunkRenameOptions } from "../types";
export declare const chunkRenamePlugin: Plugin<ChunkRenameOptions>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.chunkRenamePlugin = void 0;
const chunkRenamePlugin = ({ pageChunkName = ({ title = "", key }) => {
const chunkTitle = (title || "").replace(/[.&*?#\\/:"<>| ]/gu, "");
return chunkTitle ? `page-${chunkTitle}` : `page-${key.slice(1)}`;
}, layoutChunkName = (layout) => `layout-${layout.componentName}`, }, context) => {
// override internal plugins
const plugins = [];
if (pageChunkName) {
plugins.push({
name: "@vuepress/internal-page-components",
extendPageData(page) {
page._chunkName = pageChunkName(page);
},
clientDynamicModules() {
const content = `export default {\n${context.pages
.filter(({ _filePath }) => _filePath)
.map((page) => {
const key = JSON.stringify(page.key);
const filePath = JSON.stringify(page._filePath);
const comment = page._chunkName
? `/* webpackChunkName: ${JSON.stringify(page._chunkName)} */`
: "";
return ` ${key}: () => import(${comment}${filePath})`;
})
.join(",\n")} \n}`;
return {
dirname: "internal",
name: "page-components.js",
content,
};
},
});
}
if (layoutChunkName) {
const { layoutComponentMap } = context.themeAPI;
for (const key in layoutComponentMap) {
const component = layoutComponentMap[key];
component._chunkName = layoutChunkName(component);
}
plugins.push({
name: "@vuepress/internal-layout-components",
clientDynamicModules() {
const { layoutComponentMap } = context.themeAPI;
const content = `export default {\n${Object.keys(layoutComponentMap)
.map((name) => {
const component = layoutComponentMap[name];
const key = JSON.stringify(name);
const filePath = JSON.stringify(component.path);
const comment = component._chunkName
? `/* webpackChunkName: ${JSON.stringify(component._chunkName)} */`
: "";
return ` ${key}: () => import(${comment}${filePath})`;
})
.join(",\n")} \n}`;
return {
dirname: "internal",
name: "layout-components.js",
content,
};
},
});
}
return {
name: "chunk-rename",
plugins,
};
};
exports.chunkRenamePlugin = chunkRenamePlugin;
//# sourceMappingURL=chunk-rename.js.map
\ No newline at end of file
{"version":3,"file":"chunk-rename.js","sourceRoot":"","sources":["chunk-rename.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
import type { Plugin } from "@mr-hope/vuepress-types";
import type { CleanUrlOptions } from "../types";
export declare const cleanUrlPlugin: Plugin<CleanUrlOptions>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cleanUrlPlugin = void 0;
const cleanUrlPlugin = ({ normalSuffix = "", indexSuffix = "/", notFoundPath = "/404.html", }) => ({
name: "clean-url",
extendPageData(page) {
const { regularPath, frontmatter = {} } = page;
if (!frontmatter.permalink) {
if (regularPath === "/404.html")
// path for 404 page
page.path = notFoundPath;
else if (regularPath.endsWith(".html"))
// normal path
// e.g. foo/bar.md -> foo/bar.html
page.path = `${regularPath.slice(0, -5)}${normalSuffix}`;
else if (regularPath.endsWith("/"))
// index path
// e.g. foo/index.md -> foo/
page.path = `${regularPath.slice(0, -1)}${indexSuffix}`;
}
},
});
exports.cleanUrlPlugin = cleanUrlPlugin;
//# sourceMappingURL=clean-url.js.map
\ No newline at end of file
{"version":3,"file":"clean-url.js","sourceRoot":"","sources":["clean-url.ts"],"names":[],"mappings":";;;AAGO,MAAM,cAAc,GAA4B,CAAC,EACtD,YAAY,GAAG,EAAE,EACjB,WAAW,GAAG,GAAG,EACjB,YAAY,GAAG,WAAW,GAC3B,EAAE,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,WAAW;IAEjB,cAAc,CAAC,IAAI;QACjB,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC;QAE/C,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE;YAC1B,IAAI,WAAW,KAAK,WAAW;gBAC7B,oBAAoB;gBACpB,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;iBACtB,IAAI,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC;gBACpC,cAAc;gBACd,kCAAkC;gBAClC,IAAI,CAAC,IAAI,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,YAAY,EAAE,CAAC;iBACtD,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAChC,aAAa;gBACb,4BAA4B;gBAC5B,IAAI,CAAC,IAAI,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,WAAW,EAAE,CAAC;SAC3D;IACH,CAAC;CACF,CAAC,CAAC;AAxBU,QAAA,cAAc,kBAwBxB"}
\ No newline at end of file
import type { HopeVuePressConfig, ResolvedHopeVuePressConfig } from "../types";
export declare const config: (config: HopeVuePressConfig) => ResolvedHopeVuePressConfig;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.config = void 0;
const vuepress_shared_1 = require("@mr-hope/vuepress-shared");
const locales_1 = require("./locales");
const themeConfig_1 = require("./themeConfig");
const defaultConfig = {
base: process.env.VuePress_BASE || "/",
temp: "./node_modules/.temp",
theme: "hope",
themeConfig: { locales: {} },
evergreen: true,
};
const getRootLang = (config) => {
var _a, _b;
// infer from siteLocale
const siteLocales = config.locales;
if ((siteLocales === null || siteLocales === void 0 ? void 0 : siteLocales["/"]) && (0, vuepress_shared_1.checkLang)((_a = siteLocales["/"]) === null || _a === void 0 ? void 0 : _a.lang))
return siteLocales["/"].lang;
// infer from themeLocale
const themeLocales = config.locales;
if ((themeLocales === null || themeLocales === void 0 ? void 0 : themeLocales["/"]) && (0, vuepress_shared_1.checkLang)((_b = themeLocales["/"]) === null || _b === void 0 ? void 0 : _b.lang))
return themeLocales["/"].lang;
(0, vuepress_shared_1.showLangError)("root");
return "en-US";
};
const config = (config) => {
// merge default config
(0, vuepress_shared_1.deepAssignReverse)(defaultConfig, config);
const resolvedConfig = config;
const rootLang = getRootLang(resolvedConfig);
(0, themeConfig_1.resolveThemeConfig)(resolvedConfig.themeConfig, rootLang);
(0, locales_1.resolveLocales)(resolvedConfig, rootLang);
return resolvedConfig;
};
exports.config = config;
//# sourceMappingURL=config.js.map
\ No newline at end of file
{"version":3,"file":"config.js","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":";;;AAAA,8DAIkC;AAClC,uCAA2C;AAC3C,+CAAmD;AAKnD,MAAM,aAAa,GAAG;IACpB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,GAAG;IAEtC,IAAI,EAAE,sBAAsB;IAE5B,KAAK,EAAE,MAAM;IAEb,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAE5B,SAAS,EAAE,IAAI;CAChB,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,MAAkC,EAAY,EAAE;;IACnE,wBAAwB;IACxB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC;IAEnC,IAAI,CAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAG,GAAG,CAAC,KAAI,IAAA,2BAAS,EAAC,MAAA,WAAW,CAAC,GAAG,CAAC,0CAAE,IAAI,CAAC;QACzD,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,IAAgB,CAAC;IAE3C,yBAAyB;IACzB,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC;IAEpC,IAAI,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,GAAG,CAAC,KAAI,IAAA,2BAAS,EAAC,MAAA,YAAY,CAAC,GAAG,CAAC,0CAAE,IAAI,CAAC;QAC3D,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,IAAgB,CAAC;IAE5C,IAAA,+BAAa,EAAC,MAAM,CAAC,CAAC;IAEtB,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEK,MAAM,MAAM,GAAG,CACpB,MAA0B,EACE,EAAE;IAC9B,uBAAuB;IACvB,IAAA,mCAAiB,EAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAEzC,MAAM,cAAc,GAAG,MAAoC,CAAC;IAC5D,MAAM,QAAQ,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;IAE7C,IAAA,gCAAkB,EAAC,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IACzD,IAAA,wBAAc,EAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IAEzC,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAbW,QAAA,MAAM,UAajB"}
\ No newline at end of file
export declare const eject: (dir: string) => Promise<void>;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.eject = void 0;
const chalk_1 = require("chalk");
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
// #region exclude-files
const EXCLUDED_FILES = [
"__tests__",
".npmignore",
"LICENSE",
"package.json",
"node_modules",
"README.md",
"readme.md",
];
// #endregion exclude-files
const eject = async (dir) => {
try {
const sourceDir = (0, path_1.resolve)(__dirname, "../");
const targetDir = (0, path_1.resolve)(process.cwd(), dir, ".vuepress/theme");
await (0, fs_extra_1.copy)(sourceDir, targetDir, {
filter: (src) => {
return !EXCLUDED_FILES.includes((0, path_1.relative)(sourceDir, src));
},
});
console.log(`Copied vuepress-theme-hope into ${(0, chalk_1.cyan)(targetDir)}.\n`);
}
catch (err) {
console.error((0, chalk_1.red)(err.stack || ""));
process.exitCode = 1;
}
};
exports.eject = eject;
//# sourceMappingURL=eject.js.map
\ No newline at end of file
{"version":3,"file":"eject.js","sourceRoot":"","sources":["eject.ts"],"names":[],"mappings":";;;AAAA,iCAAkC;AAClC,uCAAgC;AAChC,+BAAyC;AAEzC,wBAAwB;AACxB,MAAM,cAAc,GAAG;IACrB,WAAW;IACX,YAAY;IACZ,SAAS;IACT,cAAc;IACd,cAAc;IACd,WAAW;IACX,WAAW;CACZ,CAAC;AACF,2BAA2B;AAEpB,MAAM,KAAK,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;IACxD,IAAI;QACF,MAAM,SAAS,GAAG,IAAA,cAAO,EAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,SAAS,GAAG,IAAA,cAAO,EAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAEjE,MAAM,IAAA,eAAI,EAAC,SAAS,EAAE,SAAS,EAAE;YAC/B,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;gBACd,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAA,eAAQ,EAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;YAC5D,CAAC;SACF,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,mCAAmC,IAAA,YAAI,EAAC,SAAS,CAAC,KAAK,CAAC,CAAC;KACtE;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,KAAK,CAAC,IAAA,WAAG,EAAE,GAAa,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;KACtB;AACH,CAAC,CAAC;AAhBW,QAAA,KAAK,SAgBhB"}
\ No newline at end of file
import type { EncryptOptions } from "../types";
export declare const resolveEncrypt: (encrypt: EncryptOptions) => void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveEncrypt = void 0;
const bcryptjs_1 = require("bcryptjs");
const resolveEncrypt = (encrypt) => {
// handle global password
if (encrypt.global)
if (typeof encrypt.global === "string")
encrypt.global = (0, bcryptjs_1.hashSync)(encrypt.global, 10);
else if (Array.isArray(encrypt.global))
encrypt.global = encrypt.global.map((globalPassword) => {
if (typeof globalPassword === "string")
return (0, bcryptjs_1.hashSync)(globalPassword, 10);
throw new Error(`[vuepress-theme-hope]: You config "themeConfig.encrypt.global", but your config is invalid.
All password MUST be string. But we found one’s type is ${typeof globalPassword}. Please fix it!`);
});
else
throw new Error(`[vuepress-theme-hope]: You are asking for global encryption but you provide invalid "global" config.
Please check "global" in your "themeConfig.encrypt" config. It can be string or string[], but you are providing ${typeof encrypt.global}. Please fix it!`);
const passwordConfig = encrypt.config || {};
Object.keys(passwordConfig).forEach((key) => {
const password = passwordConfig[key];
if (typeof password === "string")
passwordConfig[key] = (0, bcryptjs_1.hashSync)(password, 10);
else if (Array.isArray(password))
passwordConfig[key] = password.map((configPassword) => {
if (typeof configPassword === "string")
return (0, bcryptjs_1.hashSync)(configPassword, 10);
throw new Error(`[vuepress-theme-hope]: You config "themeConfig.encrypt.config", but your config is invalid.
Key ${key}’s value MUST be string or string[]. But it’s type is ${typeof configPassword}. Please fix it!`);
});
else
throw new Error(`[vuepress-theme-hope]: You config "themeConfig.encrypt.config", but your config is invalid.
The value of key ${key} MUST be string or string[]. But not it’s ${typeof password}. Please fix it!`);
});
};
exports.resolveEncrypt = resolveEncrypt;
//# sourceMappingURL=encrypt.js.map
\ No newline at end of file
{"version":3,"file":"encrypt.js","sourceRoot":"","sources":["encrypt.ts"],"names":[],"mappings":";;;AAAA,uCAAoC;AAI7B,MAAM,cAAc,GAAG,CAAC,OAAuB,EAAQ,EAAE;IAC9D,yBAAyB;IACzB,IAAI,OAAO,CAAC,MAAM;QAChB,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ;YACpC,OAAO,CAAC,MAAM,GAAG,IAAA,mBAAQ,EAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;aAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;YACpC,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,EAAE;gBACrD,IAAI,OAAO,cAAc,KAAK,QAAQ;oBACpC,OAAO,IAAA,mBAAQ,EAAC,cAAc,EAAE,EAAE,CAAC,CAAC;gBAEtC,MAAM,IAAI,KAAK,CACb;;oEAE0D,OAAO,cAAc,kBAAkB,CAClG,CAAC;YACJ,CAAC,CAAC,CAAC;;YAEH,MAAM,IAAI,KAAK,CACb;;kHAE0G,OAAO,OAAO,CAAC,MAAM,kBAAkB,CAClJ,CAAC;IAEN,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IAE5C,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1C,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAErC,IAAI,OAAO,QAAQ,KAAK,QAAQ;YAC9B,cAAc,CAAC,GAAG,CAAC,GAAG,IAAA,mBAAQ,EAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;aAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;YAC9B,cAAc,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,EAAE;gBACpD,IAAI,OAAO,cAAc,KAAK,QAAQ;oBACpC,OAAO,IAAA,mBAAQ,EAAC,cAAc,EAAE,EAAE,CAAC,CAAC;gBAEtC,MAAM,IAAI,KAAK,CAAC;;MAElB,GAAG,yDAAyD,OAAO,cAAc,kBAAkB,CAAC,CAAC;YACrG,CAAC,CAAC,CAAC;;YAEH,MAAM,IAAI,KAAK,CACb;;mBAEW,GAAG,6CAA6C,OAAO,QAAQ,kBAAkB,CAC7F,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AA9CW,QAAA,cAAc,kBA8CzB"}
\ No newline at end of file
import type { HopeLang } from "@mr-hope/vuepress-shared";
import type { ResolvedHopeVuePressConfig } from "../types";
export declare const resolveLocales: (config: ResolvedHopeVuePressConfig, rootLang: HopeLang) => void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveLocales = void 0;
const vuepress_shared_1 = require("@mr-hope/vuepress-shared");
const resolveLocales = (config, rootLang) => {
// ensure locales config
if (!config.locales)
config.locales = {};
const { locales } = config;
// set locate for base
locales["/"] = Object.assign({ lang: rootLang }, (locales["/"] || {}));
// handle other languages
Object.keys(config.themeConfig.locales).forEach((path) => {
if (path === "/")
return;
locales[path] = Object.assign({ lang: (0, vuepress_shared_1.path2Lang)(path) }, (locales[path] || {}));
});
};
exports.resolveLocales = resolveLocales;
//# sourceMappingURL=locales.js.map
\ No newline at end of file
{"version":3,"file":"locales.js","sourceRoot":"","sources":["locales.ts"],"names":[],"mappings":";;;AAAA,8DAAqD;AAK9C,MAAM,cAAc,GAAG,CAC5B,MAAkC,EAClC,QAAkB,EACZ,EAAE;IACR,wBAAwB;IACxB,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;IAEzC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAE3B,sBAAsB;IACtB,OAAO,CAAC,GAAG,CAAC,mBACV,IAAI,EAAE,QAAQ,IACX,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CACxB,CAAC;IAEF,yBAAyB;IACzB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACvD,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO;QAEzB,OAAO,CAAC,IAAI,CAAC,mBAAK,IAAI,EAAE,IAAA,2BAAS,EAAC,IAAI,CAAC,IAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAE,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AArBW,QAAA,cAAc,kBAqBzB"}
\ No newline at end of file
import type { PluginConfig } from "@mr-hope/vuepress-types";
import type { ResolvedHopeThemeConfig } from "../types";
export declare const getPluginConfig: (themeConfig: ResolvedHopeThemeConfig) => PluginConfig[];
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPluginConfig = void 0;
const path_1 = require("path");
const clean_url_1 = require("./clean-url");
const chunk_rename_1 = require("./chunk-rename");
const getPluginConfig = (themeConfig) => {
// set author for comment plugin
if (themeConfig.comment && themeConfig.author)
themeConfig.comment.author = themeConfig.author;
return [
["@mr-hope/comment", themeConfig.comment || true],
["@mr-hope/components"],
["@mr-hope/feed", themeConfig.feed],
["@mr-hope/git", themeConfig.git],
["@mr-hope/pwa", themeConfig.pwa],
["@mr-hope/seo", themeConfig.seo],
["@mr-hope/sitemap", themeConfig.sitemap],
[
"@mr-hope/smooth-scroll",
themeConfig.smoothScroll === false
? false
: typeof themeConfig.smoothScroll === "number"
? { delay: themeConfig.smoothScroll }
: themeConfig.smoothScroll || { delay: 500 },
],
[
"@vuepress/blog",
themeConfig.blog === false
? false
: {
frontmatters: [
{
id: "tag",
keys: ["tag", "tags"],
path: "/tag/",
layout: "Blog",
scopeLayout: "Blog",
},
{
id: "category",
keys: ["category", "categories"],
path: "/category/",
layout: "Blog",
scopeLayout: "Blog",
},
],
},
],
["@vuepress/last-updated", false],
"@vuepress/nprogress",
[
"@vuepress/search",
{
searchMaxSuggestions: themeConfig.searchMaxSuggestions || 10,
},
],
["active-hash", themeConfig.activeHash],
["add-this", typeof themeConfig.addThis === "string"],
[
"copyright",
typeof themeConfig.copyright === "object"
? Object.assign({ minLength: 100, disable: themeConfig.copyright.status === "local", clipboardComponent: (0, path_1.resolve)(__dirname, "../components/Clipboard.vue") }, themeConfig.copyright) : false,
],
["md-enhance", themeConfig.mdEnhance || {}],
["@mr-hope/copy-code", themeConfig.copyCode],
["photo-swipe", themeConfig.photoSwipe],
[
"typescript",
themeConfig.typescript
? {
tsLoaderOptions: typeof themeConfig.typescript === "object"
? themeConfig.typescript
: {},
}
: false,
],
[
clean_url_1.cleanUrlPlugin,
themeConfig.cleanUrl === false
? false
: themeConfig.cleanUrl || { normalSuffix: "/" },
],
[
chunk_rename_1.chunkRenamePlugin,
themeConfig.chunkRename === false ? false : themeConfig.chunkRename,
],
];
};
exports.getPluginConfig = getPluginConfig;
//# sourceMappingURL=plugins.js.map
\ No newline at end of file
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["plugins.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAE/B,2CAA6C;AAC7C,iDAAmD;AAK5C,MAAM,eAAe,GAAG,CAC7B,WAAoC,EACpB,EAAE;IAClB,gCAAgC;IAChC,IAAI,WAAW,CAAC,OAAO,IAAI,WAAW,CAAC,MAAM;QAC3C,WAAW,CAAC,OAAO,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAElD,OAAO;QACL,CAAC,kBAAkB,EAAE,WAAW,CAAC,OAAO,IAAI,IAAI,CAAC;QAEjD,CAAC,qBAAqB,CAAC;QAEvB,CAAC,eAAe,EAAE,WAAW,CAAC,IAAI,CAAC;QAEnC,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG,CAAC;QAEjC,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG,CAAC;QAEjC,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG,CAAC;QAEjC,CAAC,kBAAkB,EAAE,WAAW,CAAC,OAAO,CAAC;QAEzC;YACE,wBAAwB;YACxB,WAAW,CAAC,YAAY,KAAK,KAAK;gBAChC,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,OAAO,WAAW,CAAC,YAAY,KAAK,QAAQ;oBAC9C,CAAC,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,YAAY,EAAE;oBACrC,CAAC,CAAC,WAAW,CAAC,YAAY,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;SAC/C;QAED;YACE,gBAAgB;YAChB,WAAW,CAAC,IAAI,KAAK,KAAK;gBACxB,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC;oBACE,YAAY,EAAE;wBACZ;4BACE,EAAE,EAAE,KAAK;4BACT,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC;4BACrB,IAAI,EAAE,OAAO;4BACb,MAAM,EAAE,MAAM;4BACd,WAAW,EAAE,MAAM;yBACpB;wBACD;4BACE,EAAE,EAAE,UAAU;4BACd,IAAI,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC;4BAChC,IAAI,EAAE,YAAY;4BAClB,MAAM,EAAE,MAAM;4BACd,WAAW,EAAE,MAAM;yBACpB;qBACF;iBACF;SACN;QACD,CAAC,wBAAwB,EAAE,KAAK,CAAC;QAEjC,qBAAqB;QAErB;YACE,kBAAkB;YAClB;gBACE,oBAAoB,EAAE,WAAW,CAAC,oBAAoB,IAAI,EAAE;aAC7D;SACF;QAED,CAAC,aAAa,EAAE,WAAW,CAAC,UAAU,CAAC;QAEvC,CAAC,UAAU,EAAE,OAAO,WAAW,CAAC,OAAO,KAAK,QAAQ,CAAC;QAErD;YACE,WAAW;YACX,OAAO,WAAW,CAAC,SAAS,KAAK,QAAQ;gBACvC,CAAC,iBACG,SAAS,EAAE,GAAG,EACd,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,MAAM,KAAK,OAAO,EACjD,kBAAkB,EAAE,IAAA,cAAO,EACzB,SAAS,EACT,6BAA6B,CAC9B,IACE,WAAW,CAAC,SAAS,EAE5B,CAAC,CAAC,KAAK;SACV;QAED,CAAC,YAAY,EAAE,WAAW,CAAC,SAAS,IAAI,EAAE,CAAC;QAE3C,CAAC,oBAAoB,EAAE,WAAW,CAAC,QAAQ,CAAC;QAE5C,CAAC,aAAa,EAAE,WAAW,CAAC,UAAU,CAAC;QAEvC;YACE,YAAY;YACZ,WAAW,CAAC,UAAU;gBACpB,CAAC,CAAC;oBACE,eAAe,EACb,OAAO,WAAW,CAAC,UAAU,KAAK,QAAQ;wBACxC,CAAC,CAAC,WAAW,CAAC,UAAU;wBACxB,CAAC,CAAC,EAAE;iBACT;gBACH,CAAC,CAAC,KAAK;SACV;QAED;YACE,0BAAc;YACd,WAAW,CAAC,QAAQ,KAAK,KAAK;gBAC5B,CAAC,CAAC,KAAK;gBACP,CAAC,CAAC,WAAW,CAAC,QAAQ,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE;SAClD;QAED;YACE,gCAAiB;YACjB,WAAW,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,WAAW;SACpE;KACF,CAAC;AACJ,CAAC,CAAC;AAlHW,QAAA,eAAe,mBAkH1B"}
\ No newline at end of file
import type { HopeLang } from "@mr-hope/vuepress-shared";
import type { ResolvedHopeThemeConfig } from "../types";
export declare const resolveThemeConfig: (themeConfig: ResolvedHopeThemeConfig, rootLang: HopeLang) => void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveThemeConfig = void 0;
const vuepress_shared_1 = require("@mr-hope/vuepress-shared");
const encrypt_1 = require("./encrypt");
const setThemeLocales = (themeConfig, rootLang) => {
const rootLangPath = (0, vuepress_shared_1.lang2Path)(rootLang);
// set locate for base
themeConfig.locales["/"] = Object.assign(Object.assign(Object.assign({}, (0, vuepress_shared_1.getLocale)(rootLang)), (themeConfig.locales[rootLangPath] || {})), (themeConfig.locales["/"] || {}));
// handle other languages
Object.keys(themeConfig.locales).forEach((path) => {
if (path === "/")
return;
const lang = (0, vuepress_shared_1.path2Lang)(path);
themeConfig.locales[path] = Object.assign(Object.assign({}, (0, vuepress_shared_1.getLocale)(lang)), themeConfig.locales[path]);
});
};
const resolveThemeConfig = (themeConfig, rootLang) => {
setThemeLocales(themeConfig, rootLang);
if (themeConfig.encrypt)
(0, encrypt_1.resolveEncrypt)(themeConfig.encrypt);
};
exports.resolveThemeConfig = resolveThemeConfig;
//# sourceMappingURL=themeConfig.js.map
\ No newline at end of file
{"version":3,"file":"themeConfig.js","sourceRoot":"","sources":["themeConfig.ts"],"names":[],"mappings":";;;AAAA,8DAA2E;AAC3E,uCAA2C;AAK3C,MAAM,eAAe,GAAG,CACtB,WAAoC,EACpC,QAAgB,EACV,EAAE;IACR,MAAM,YAAY,GAAG,IAAA,2BAAS,EAAC,QAAQ,CAAC,CAAC;IAEzC,sBAAsB;IACtB,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,iDACnB,IAAA,2BAAS,EAAC,QAAQ,CAAC,GACnB,CAAC,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,GACzC,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CACpC,CAAC;IAEF,yBAAyB;IACzB,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAChD,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO;QAEzB,MAAM,IAAI,GAAG,IAAA,2BAAS,EAAC,IAAI,CAAC,CAAC;QAE7B,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,mCACpB,IAAA,2BAAS,EAAC,IAAI,CAAC,GACf,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAC7B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEK,MAAM,kBAAkB,GAAG,CAChC,WAAoC,EACpC,QAAkB,EACZ,EAAE;IACR,eAAe,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;IAEvC,IAAI,WAAW,CAAC,OAAO;QAAE,IAAA,wBAAc,EAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAC/D,CAAC,CAAC;AAPW,QAAA,kBAAkB,sBAO7B"}
\ No newline at end of file
{$contentClass}
code
color lighten($textColor, 20%)
padding 0.25rem 0.5rem
margin 0
font-size 0.85em
background-color rgba(27,31,35,0.05)
border-radius 3px
.token
&.deleted
color #EC5975
&.inserted
color $accentColor
{$contentClass}
pre, pre[class*="language-"]
line-height 1.4
padding 1.25rem 1.5rem
margin 0.85rem 0
background-color $codeBgColor
border-radius 6px
overflow auto
code
color #fff
padding 0
background-color transparent
border-radius 0
div[class*="language-"]
position relative
background-color $codeBgColor
border-radius 6px
.highlight-lines
user-select none
padding-top 1.3rem
position absolute
top 0
left 0
width 100%
line-height 1.4
.highlighted
background-color rgba(0, 0, 0, 66%)
pre, pre[class*="language-"]
background transparent
position relative
z-index 1
&::before
position absolute
z-index 3
top 0.8em
right 1em
font-size 0.75rem
color rgba(255, 255, 255, 0.4)
&:not(.line-numbers-mode)
.line-numbers-wrapper
display none
&.line-numbers-mode
.highlight-lines .highlighted
position relative
&:before
content ' '
position absolute
z-index 3
left 0
top 0
display block
width $lineNumbersWrapperWidth
height 100%
background-color rgba(0, 0, 0, 66%)
pre
padding-left $lineNumbersWrapperWidth + 1 rem
vertical-align middle
.line-numbers-wrapper
position absolute
top 0
width $lineNumbersWrapperWidth
text-align center
color rgba(255, 255, 255, 0.3)
padding 1.25rem 0
line-height 1.4
br
user-select none
.line-number
position relative
z-index 4
user-select none
font-size 0.85em
&::after
content ''
position absolute
z-index 2
top 0
left 0
width $lineNumbersWrapperWidth
height 100%
border-radius 6px 0 0 6px
border-right 1px solid rgba(0, 0, 0, 66%)
background-color $codeBgColor
for lang in $codeLang
div{'[class~="language-' + lang + '"]'}
&:before
content ('' + lang)
div[class~="language-javascript"]
&:before
content "js"
div[class~="language-typescript"]
&:before
content "ts"
div[class~="language-markup"]
&:before
content "html"
div[class~="language-markdown"]
&:before
content "md"
div[class~="language-json"]:before
content "json"
div[class~="language-ruby"]:before
content "rb"
div[class~="language-python"]:before
content "py"
div[class~="language-bash"]:before
content "sh"
div[class~="language-php"]:before
content "php"
@import '~prismjs/themes/prism-tomorrow.css'
@font-face {
font-family: "Crimson";
src: url(data:font/truetype;charset=utf-8;base64,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)
format("truetype");
font-weight: normal;
font-style: normal;
}
@require './normalize'
@require './prefer-color-scheme-config'
@require './theme'
@require './plugins/index'
@require './theme-color'
@require '~balloon-css/balloon.min.css'
@require '~@theme/styles/fonts/crimson.css'
@require '~@mr-hope/vuepress-shared/styles/wrapper'
html, body
padding 0
margin 0
background var(--bgcolor)
body
font-family Georgia Pro, Crimson, Georgia, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', STHeiti, 'Microsoft YaHei', SimSun, sans-serif
-webkit-font-smoothing antialiased
-moz-osx-font-smoothing grayscale
font-display optional
font-size 16px
color var(--text-color)
min-height 100vh
-webkit-tap-highlight-color transparent
{$contentClass}:not(.custom)
@extend $wrapper
> *:first-child
margin-top $navbarHeight
a:hover
text-decoration underline
// unknow container
p.demo
padding 1rem 1.5rem
border 1px solid #ddd
border-radius 4px
img
max-width 100%
{$contentClass}.custom
padding 0
margin 0
img
max-width 100%
a
font-weight 500
color var(--accent-color)
text-decoration none
overflow-wrap break-word
p a code
font-weight 400
color var(--accent-color)
kbd
background #eee
border solid 0.15rem #ddd
border-bottom solid 0.25rem #ddd
border-radius 0.15rem
padding 0 0.15em
blockquote
font-size 1rem
color var(--light-grey)
border-left 0.2rem solid #dfe2e5
margin 1rem 0
padding 0.25rem 0 0.25rem 1rem
& > p
margin 0
ul, ol
padding-left 1.2em
strong
font-weight 600
h1, h2, h3, h4, h5, h6
font-weight 500
line-height 1.25
{$contentClass}:not(.custom) > &
margin-top 0.5rem - $navbarHeight
padding-top: ($navbarHeight + 1rem)
margin-bottom 0.5rem
&:first-child
margin-top -3rem
margin-bottom 1rem
+ p, + pre, + .custom-block
margin-top 2rem
&:hover .header-anchor
opacity 1
p
{$contentClass}:not(.custom) > &, {$contentClass}:not(.custom) > ul &, {$contentClass}:not(.custom) > ol &
text-align justify
word-break break-word
hyphens auto
overflow-wrap break-word
@media (max-width $MQMobileNarrow)
text-align left
h1
font-size 2rem
h2
font-size 1.65rem
padding-bottom 0.3rem
border-bottom 1px solid var(--border-color)
h3
font-size 1.35rem
a.header-anchor
font-size 0.85em
float left
margin-left -0.87em
padding-right 0.23em
margin-top 0.125em
opacity 0
transition opacity 0.2s
&:hover
text-decoration none
code, kbd, .line-number
font-family source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace
p, ul, ol
line-height 1.7
hr
border 0
border-top 1px solid var(--border-color)
table
border-collapse collapse
margin 1rem 0
display block
overflow-x auto
tr
border-top 1px solid #dfe2e5
&:nth-child(2n)
background #f6f8fa
th, td
border 1px solid var(--grey14)
padding 0.6em 1em
.theme-dark
tr:nth-child(2n)
background #252322
th, td
border 1px solid var(--grey12)
/* basic color */
$accentColor ?= #3eaf7c
$textColor ?= #242424
$darkTextColor ?= #9e9e9e
$bgColor ?= #fff
$darkBgColor ?= #1e1e1e
$bgColorLight ?= #f8f8f8
$darkBgColorLight ?= #272727
$bgColorBlur ?= rgba(255, 255, 255, 0.9)
$darkBgColorBlur ?= rgba(30, 30, 30, 0.9)
$borderColor ?= #eaecef
$darkBorderColor ?= #302d28
$codeBgColor ?= #282c34
$darkCodeBgColor ?= #282c34
$arrowBgColor ?= #ccc
$darkArrowBgColor ?= #333
/* colors provided by theme */
$boxShadowColor ?= #f0f1f2
$darkBoxShadowColor ?= #0f0e0d
$cardShadowColor ?= rgba(0, 0, 0, 0.15)
$darkCardShadowColor ?= rgba(0, 0, 0, 0.3)
/* * theme-color */
$colorPicker ?= {
red: #e74c3c,
blue: #3498db,
green: #3eaf7c,
orange: #f39c12,
purple: #8e44ad
}
/* badge color */
$badgeTipColor ?= #42b983
$badgeWarningColor ?= darken(#ffe564, 35%)
$badgeErrorColor ?= #DA5961
/* layout */
$navbarHeight ?= 3.6rem
$navbarMobileHeight ?= 3.25rem
$navbarHorizontalPadding ?= 1.5rem
$navbarMobileHorizontalPadding ?= 1rem
$navbarVerticalPadding ?= 0.7rem
$navbarMobileVerticalPadding ?= 0.5rem
$sidebarWidth ?= 18rem
$mobileSidebarWidth ?= $sidebarWidth
$contentWidth ?= 820px
$homePageWidth ?= 960px
/* responsive breakpoints */
$MQWide ?= 1440px // wide screen
$MQMedium ?= 1350px // medium screen
$MQNormal ?= 1280px // desktop
$MQNarrow ?= 959px // narrow desktop / iPad
$MQMobile ?= 719px // wide mobile
$MQMobileNarrow ?= 419px // narrow mobile
/* code block */
$lineNumbersWrapperWidth ?= 2.5rem
$codeLang ?= js ts html md vue css sass scss less stylus go java c sh yaml py docker dockerfile makefile
/* content class */
$contentClass ?= '.theme-default-content'
@require './nprogress'
@require './search'
for $themeColorName, $themeColor in $colorPicker
.theme-{$themeColorName}
#nprogress
.bar
background $themeColor
.peg
box-shadow 0 0 10px $themeColor, 0 0 5px $themeColor
.spinner-icon
border-color $themeColor
.navbar
.search-box
height calc(2rem + 4px)
margin-left 0.25rem
input
margin-top 1px
margin-bottom 1px
border-color transparent
border-radius 0.25em
@media (max-width $MQMobile)
left 0
background-color transparent
@media (min-width $MQNarrow)
background-color #efeef4
&:focus
width 15rem
&:focus
background-color var(--bgcolor)
border-color var(--accent-color)
.theme-dark &
color var(--text-color)
background-color transparent
@media (min-width $MQNarrow)
background-color lighten($darkBgColor, 10%) !important
border-color var(--border-color)
&:focus
background-color lighten($darkBgColor, 10%) !important
.theme-dark &
.suggestion
a
color darken($darkTextColor, 35%)
&.focused
background #0c0b0a
a
color var(--accent-color)
border-color var(--accent-color)
.suggestions
border-color var(--border-color)
background var(--white)
#docsearch button
background-color transparent
border-color transparent
border-radius 0.25em
.mobile &
left 0
background-color transparent
@media (min-width $MQNarrow)
background-color #efeef4
&:focus
background-color var(--bgcolor)
border-color var(--accent-color)
.theme-dark &
color var(--text-color)
@media (min-width $MQNarrow)
background-color lighten($darkBgColor, 10%) !important
border-color var(--border-color)
&:focus
background-color lighten($darkBgColor, 10%) !important
body
--text-color $textColor
--bgcolor $bgColor
--bgcolor-light $bgColorLight
--border-color $borderColor
--code-bgcolor $codeBgColor
--arrow-bgcolor $arrowBgColor
--box-shadow-color $boxShadowColor
--card-shadow-color $cardShadowColor
--text-color-l10 lighten($textColor, 10%)
--text-color-l20 lighten($textColor, 20%)
--text-color-l25 lighten($textColor, 25%)
--text-color-l40 lighten($textColor, 40%)
--black #000
--dark-grey #666
--light-grey #999
--white #fff
--grey3 #333
--grey12 #bbb
--grey14 #eee
body.theme-light
--text-color $textColor
--bgcolor $bgColor
--bgcolor-light $bgColorLight
--bgcolor-blur $bgColorBlur
--border-color $borderColor
--code-bgcolor $codeBgColor
--arrow-bgcolor $arrowBgColor
--box-shadow-color $boxShadowColor
--card-shadow-color $cardShadowColor
--text-color-l10 lighten($textColor, 10%)
--text-color-l20 lighten($textColor, 20%)
--text-color-l25 lighten($textColor, 25%)
--text-color-l40 lighten($textColor, 40%)
--black #000
--dark-grey #666
--light-grey #999
--white #fff
--grey3 #333
--grey12 #bbb
--grey14 #eee
body.theme-dark
--text-color $darkTextColor
--bgcolor $darkBgColor
--bgcolor-light $darkBgColorLight
--bgcolor-blur $darkBgColorBlur
--border-color $darkBorderColor
--code-bgcolor $darkCodeBgColor
--arrow-bgcolor $darkArrowBgColor
--box-shadow-color $darkBoxShadowColor
--card-shadow-color $darkCardShadowColor
--text-color-l10 lighten($darkTextColor, 10%)
--text-color-l20 lighten($darkTextColor, 20%)
--text-color-l25 lighten($darkTextColor, 25%)
--text-color-l40 lighten($darkTextColor, 40%)
--black #fff
--dark-grey #999
--light-grey #666
--white #000
--grey3 #ccc
--grey12 #333
--grey14 #111
body
--accent-color $accentColor
--accent-color-l10 lighten($accentColor, 10%)
--accent-color-d10 darken($accentColor, 10%)
--accent-color-a15 alpha($accentColor, 15%)
theme-color($themeColor, $colorName)
body.theme-{$colorName}
--accent-color $themeColor
--accent-color-l10 lighten($themeColor, 10%)
--accent-color-d10 darken($themeColor, 10%)
--accent-color-a15 alpha($themeColor, 15%)
for key, value in $colorPicker
theme-color(value, key)
@require './code/index'
.theme-container:not(.has-navbar)
{$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6
margin-top 1.5rem
padding-top 0
// narrow mobile
@media (max-width $MQMobileNarrow)
h1
font-size 1.7rem
h2
font-size 1.5rem
h3
font-size 1.3rem
{$contentClass}
div[class*='language-']
margin 0.85rem -1.5rem
border-radius 0
.iconfont
font-weight normal
[aria-label][data-balloon-pos]
cursor help
declare module "*.jpg" {
const path: string;
export default path;
}
declare module "docsearch.js/dist/cdn/docsearch.min.js" {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const docsearch: any;
export default docsearch;
}
declare module "@AlgoliaSearchBox" {
import vue from "vue";
export default vue;
}
declare module "@BlogHome" {
import vue from "vue";
export default vue;
}
declare module "@BlogInfo" {
import vue from "vue";
export default vue;
}
declare module "@BloggerInfo" {
import vue from "vue";
export default vue;
}
declare module "@BlogPage" {
import vue from "vue";
export default vue;
}
declare module "@Comment" {
import vue from "vue";
export default vue;
}
declare module "@ContentTop" {
import vue from "vue";
export default vue;
}
declare module "@ContentBottom" {
import vue from "vue";
export default vue;
}
declare module "@NavbarStart" {
import vue from "vue";
export default vue;
}
declare module "@NavbarCenter" {
import vue from "vue";
export default vue;
}
declare module "@NavbarEnd" {
import vue from "vue";
export default vue;
}
declare module "@PageTop" {
import vue from "vue";
export default vue;
}
declare module "@PageInfo" {
import vue from "vue";
export default vue;
}
declare module "@PageBottom" {
import vue from "vue";
export default vue;
}
declare module "@Pagination" {
import vue from "vue";
export default vue;
}
declare module "@SearchBox" {
import vue from "vue";
export default vue;
}
declare module "@ThemeColor" {
import vue from "vue";
export default vue;
}
declare module "@SearchBox" {
import vue from "vue";
export default vue;
}
declare module "@SidebarTop" {
import vue from "vue";
export default vue;
}
declare module "@SidebarCenter" {
import vue from "vue";
export default vue;
}
declare module "@SidebarBottom" {
import vue from "vue";
export default vue;
}
/* eslint-disable @typescript-eslint/no-explicit-any */
import { HopeThemeLocaleConfigItem } from "@mr-hope/vuepress-shared";
import {
BlogMedia,
HopeThemeConfig,
HopeNavBarConfig,
HopeSideBarConfig,
HopeFooterConfig,
} from "./theme";
import { PageInfotype } from "@mr-hope/vuepress-plugin-comment";
import { FeedFrontmatterOption } from "@mr-hope/vuepress-plugin-feed";
import { AlgoliaOption } from "@mr-hope/vuepress-types";
declare module "vue/types/vue" {
export interface Vue {
$category: any;
$tag: any;
$currentTag: any;
$currentCategory: any;
$pagination: any;
}
}
declare module "@mr-hope/vuepress-types" {
interface PageFrontmatter {
icon?: string;
author?: string | false;
original?: boolean;
/**
* @deprecated
*/
date?: Date | string;
time?: Date | string;
category?: string;
tag?: string[];
/**
* @deprecated
*/
tags?: string[];
summary?: string;
sticky?: boolean | number;
star?: boolean | number;
article?: boolean;
timeline?: boolean;
password?: string | number;
image?: string;
copyright?: {
minLength?: number;
noCopy?: boolean;
noSelect?: boolean;
};
feed?: FeedFrontmatterOption;
pageInfo?: PageInfotype[] | false;
visitor?: boolean;
breadcrumb?: boolean;
breadcrumbIcon?: boolean;
navbar?: boolean;
sidebar?: "auto" | boolean;
sidebarDepth?: number;
comment?: boolean;
editLink?: boolean;
contributor?: boolean;
updateTime?: boolean;
prev?: string | false;
next?: string | false;
footer?: string | boolean;
copyrightText?: string | false;
mediaLink?: BlogMedia;
search?: boolean;
backToTop?: boolean;
anchorDisplay?: boolean;
}
interface I18nConfig extends Partial<HopeThemeLocaleConfigItem> {
/** 导航栏链接 */
nav?: HopeNavBarConfig;
/** 侧边栏配置 */
sidebar?: HopeSideBarConfig;
/** 当前语言的 algolia 设置 */
algolia?: AlgoliaOption;
/** 页脚设置 */
footer?: HopeFooterConfig;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ThemeConfig extends HopeThemeConfig {}
interface Page {
_chunkName?: string;
}
interface ResolvedComponent {
_chunkName?: string;
}
}
import {
HopeNavBarConfig,
HopeSideBarConfig,
HopeThemeConfig,
HopeVuePressConfig,
ResolvedHopeVuePressConfig,
} from "./theme";
import "./declare";
import "./extend";
export * from "./theme";
export const config: (config: HopeVuePressConfig) => ResolvedHopeVuePressConfig;
export const themeConfig: (themeConfig: HopeThemeConfig) => HopeThemeConfig;
export const navbarConfig: (navbarConfig: HopeNavBarConfig) => HopeNavBarConfig;
export const sidebarConfig: (
sidebarConfig: HopeSideBarConfig
) => HopeSideBarConfig;
export interface HopeThemeAppearanceConfig {
/**
* Algolia 搜索类型
*
* Algolia Search Type
*
* @default 'dropdown'
*/
algoliaType?: "dropdown" | "full";
/**
* 图标前缀
*
* Prefix of icon class
*
* @default 'icon-'
*/
iconPrefix?: string;
/**
* 是否在移动视图下隐藏站点名称
*
* Whether hide site title on mobile
*
* @default true
*/
hideSiteTitleonMobile?: boolean;
/**
* 是否在导航栏显示仓库链接
*
* Whether display repo link in navbar
*
* @default true
*/
repoDisplay?: boolean;
/**
* 是否显示 ”全屏“ 按钮
*
* Whether show fullscreen button in navbar
*
* @default true
*/
fullscreen?: boolean;
/**
* 是否在侧边栏显示图标
*
* Whether show icons in the sidebar
*
* @default true
*/
sidebarIcon?: boolean;
/**
* 侧边栏嵌套的标题深度
*
* Nested headings depth in sidebar
*
* @default 2
*/
sidebarDepth?: number;
/**
* 是否在路径导航显示图标
*
* Whether display icon in breadcrumb
*
* @default true
*/
breadcrumbIcon?: boolean;
/**
* 是否显示当前页面贡献者
*
* Whether show contributors in each page
*
* @default true
*/
contributor?: boolean;
/**
* 显示编辑本页链接
*
* Whether show edit link on each page
*
* @default true
*/
editLinks?: boolean;
/**
* 显示更新时间
*
* Whether show update time on each page
*
* @default true
*/
updateTime?: boolean;
/**
* 是否显示返回顶部按钮
*
* 如果设置为数字,则该数字为触发临界值 (默认临界值为 300px)
*
* Wether display backto top button
*
* If it’s set with a number, then it will be the threshold
*
* @default true
*/
backToTop?: boolean | number;
}
import {
NavBarConfigItem,
SideBarConfigItemObject,
} from "@mr-hope/vuepress-types";
/** vuepress-theme-hope 导航栏配置项 */
export interface HopeNavBarConfigItem extends NavBarConfigItem {
/** 导航栏对应项的图标 */
icon?: string;
/** 导航栏的路径前缀 */
prefix?: string;
/** 导航栏下拉列表子项 */
items?: HopeNavBarConfigItem[];
}
/** vuepress-theme-hope 导航栏配置 */
export type HopeNavBarConfig = HopeNavBarConfigItem[] | false;
/** vuepress-theme-hope 侧边栏配置对象 */
export interface HopeSideBarConfigItemObject extends SideBarConfigItemObject {
/** 分组的图标 */
icon?: string;
/** 当前分组的路径前缀 */
prefix?: string;
/** 当前侧边栏的子项 */
children: HopeSideBarConfigItem[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[props: string]: any;
}
/** vuepress-theme-hope 侧边栏配置项 */
export type HopeSideBarConfigItem =
| string
| string[]
| HopeSideBarConfigItemObject;
/** vuepress-theme-hope 侧边栏配置 */
export type HopeSideBarConfig =
| HopeSideBarConfigItem[]
| Record<string, HopeSideBarConfigItem[]>
| "auto"
| false;
/**
* 合法的媒体
*
* media you can choose
*/
type BlogMedia =
| "Baidu"
| "Bitbucket"
| "Dingding"
| "Discord"
| "Dribbble"
| "Email"
| "Evernote"
| "Facebook"
| "Flipboard"
| "Gitee"
| "Github"
| "Gitlab"
| "Gmail"
| "Instagram"
| "Lines"
| "Linkedin"
| "Pinterest"
| "Pocket"
| "QQ"
| "Qzone"
| "Reddit"
| "Rss"
| "Steam"
| "Twitter"
| "Wechat"
| "Weibo"
| "Whatsapp"
| "Youtube"
| "Zhihu";
/**
* 博客选项
*
* Blog configuration
*/
export type BlogOptions = {
/**
* 博主名称
*
* Name of the Blogger, default is author
*/
name?: string;
/**
* 博主头像,应为绝对路径
*
* Blogger avator, must be an absolute path
*/
avatar?: string;
/**
* 博主的个人介绍地址
*
* Intro page about blogger
*/
intro?: string;
/**
* 媒体链接配置
*
* Media links configuration
*
* E.g.
*
* ```js
* {
* QQ: "http://wpa.qq.com/msgrd?v=3&uin=1178522294&site=qq&menu=yes",
* Qzone: "https://1178522294.qzone.qq.com/",
* Gmail: "mailto:zhangbowang1998@gmail.com",
* Zhihu: "https://www.zhihu.com/people/mister-hope",
* Steam: "https://steamcommunity.com/id/Mr-Hope/",
* Weibo: "https://weibo.com/misterhope",
* }
* ```
*/
links?: Partial<Record<BlogMedia, string>>;
/**
* 是否剪裁头像为圆形形状
*
* Whether cliping the avatar with round shape
*
* @default true
*/
roundAvatar?: boolean;
/**
* 是否在侧边栏展示博主信息
*
* Whether to display blogger info in sidebar
*
* @default 'none'
*/
sidebarDisplay?: "mobile" | "none" | "always";
/**
* 时间轴自定义文字
*
* Custom text for timeline
*
* @default 'Yesterday once more'
*/
timeline?: string;
/**
* 每页的文章数量
*
* Article number per page
*
* @default 10
*/
perPage?: number;
};
/**
* 加密选项
*
* Encrypt Options
*/
export interface EncryptOptions {
/**
* 功能状态
*
* - `'global'` 意味着全局启用
* - `'local'` 意味着全局禁用,可在页面内启用
*
* Feature Status
*
* - `'global'` means enabled globally
* - `'local'` means disabled globally and can be enabled in pages
*
* @default 'local'
*/
status?: "global" | "local";
/**
* 最高权限密码
*
* Global passwords, which has the highest authority
*/
global?: string | string[];
/**
* 加密配置
*
* ```json
* {
* // 这会加密整个 guide 目录,并且两个密码都是可用的
* "/guide/": ["1234", "5678"],
* // 这只会加密 config/page.html
* "/config/page.html": "1234"
* }
* ```
*
* Encrypt Configuration
*
* E.g.:
*
* ```json
* {
* // This will encrypt the entire guide directory and both passwords will be available
* "/guide/": ["1234", "5678"],
* // this will only encrypt config/page.html
* "/config/page.html": "1234"
* }
* ```
*/
config?: Record<string, string | string[]>;
}
/** 自定义布局配置 */
export interface CustomOptions {
/** 页面顶部插槽 */
pageTop?: string;
/** 文章内容顶部插槽 */
contentTop?: string;
/** 文章内容底部插槽 */
contentBottom?: string;
/** 页面底部插槽 */
pageBottom?: string;
/** 导航栏起始插槽 */
navbarStart?: string;
/** 导航栏中部插槽 */
navbarCenter?: string;
/** 导航栏结束插槽 */
navbarEnd?: string;
/** 侧边栏顶部插槽 */
sidebarTop?: string;
/** 侧边栏中部插槽 */
sidebarCenter?: string;
/** 侧边栏底部插槽 */
sidebarBottom?: string;
}
export interface HopeFeatureConfig {
/**
* 深色模式支持选项:
*
* - `'auto-switch'`: "关闭 | 自动 | 打开" 的三段式开关 (默认)
* - `'switch'`: "关闭 | 打开" 的切换式开关
* - `'auto'`: 自动根据用户设备主题或当前时间决定是否应用深色模式
* - `'disable'`: 禁用深色模式
*
* Dark mode support options:
*
* - `'auto-switch'`: "off | automatic | on" three-stage switch (Default)
* - `'switch'`: "Close | Open" toggle switch
* - `'auto'`: Automatically decide whether to apply dark mode based on user device’s color-scheme or current time
* - `'disable'`: disable dark mode
*
* @default 'auto-switch'
*/
darkmode?: "auto-switch" | "auto" | "switch" | "disable";
/**
* 主题色选项配置。
*
* Theme color configuration.
*
* E.g.:
* ```js
* {
* blue: '#2196f3',
* red: '#f26d6d',
* green: '#3eaf7c',
* orange: '#fb9b5f'
* }
* ```
*
* @default { blue: '#2196f3', red: '#f26d6d', green: '#3eaf7c', orange: '#fb9b5f' }
*/
themeColor?: Record<string, string> | false;
/**
* 博客设置
*
* Blog configuration
*/
blog?: BlogOptions | false;
/**
* 加密设置
*
* Encrypt Configuration
*/
encrypt?: EncryptOptions;
/**
* 自定义组件设置
*/
custom?: CustomOptions;
/**
* 是否启用平滑滚动
*
* Enable smooth scrolling feature
*
* @default true
*/
smoothScroll?: boolean;
/**
* 每分钟的阅读字数
*
* Reading speed of word per minute
*
* @default 300
*/
wordPerminute?: number;
}
import { HopeThemeConfig, ResolvedHopeThemeConfig } from "./theme";
import { SiteConfig } from "@mr-hope/vuepress-types";
export * from "./appearance";
export * from "./extends";
export * from "./feature";
export * from "./layout";
export * from "./locale";
export * from "./plugin";
export * from "./theme";
/** vuepress-theme-hope 项目配置 */
export interface HopeVuePressConfig extends SiteConfig {
/** 自定义主题的配置 */
themeConfig: HopeThemeConfig;
}
/** 处理过的 vuepress-theme-hope 项目配置 */
export interface ResolvedHopeVuePressConfig extends HopeVuePressConfig {
/** 使用的自定义主题 */
theme: "hope";
/** 自定义主题的配置 */
themeConfig: ResolvedHopeThemeConfig;
}
import { PageInfotype } from "@mr-hope/vuepress-plugin-comment";
import { HopeNavBarConfig, HopeSideBarConfig } from "./extends";
/**
* 页脚配置
*
* Footer Settings
*/
export interface HopeFooterConfig {
/**
* 页脚的默认内容,可输入 HTMLString
*
* The default content for the footer, can accept HTMLString.
*/
content?: string;
/**
* 默认的版权信息,设置为 `false` 来默认禁用它
*
* The default copyright info, set it to `false` to disable it by default.
*/
copyright?: string | false;
/**
* 是否默认显示页脚
*
* Whether to display footer by default
*
* @default false
*/
display?: boolean;
}
export interface HopeLayoutConfig {
/**
* 导航栏配置
*
* Navbar configuration
*/
nav?: HopeNavBarConfig;
/**
* 是否禁用导航栏
*
* Whether disable navbar
*
* @default false
*/
navbar?: boolean;
/**
* 是否在向下滚动时自动隐藏导航栏
*
* Whether to hide navbar when scrolling down
*
* @default 'mobile'
*/
navAutoHide?: "always" | "mobile" | "none";
/**
* 侧边栏配置
*
* Sidebar configuration
*/
sidebar?: HopeSideBarConfig;
/**
* 是否在桌面模式显示锚点标题
*
* Whether display anchor in desktop mode
*
* @default true
*/
anchorDisplay?: boolean;
/**
* 是否全局启用路径导航
*
* Whether enable breadcrumb globally
*
* @default true
*/
breadcrumb?: boolean;
/**
* 页面信息
*
* Article information
*
* Avaliable Options:
*
* - `'author'`: Author
* - `'time'`: Writing Date
* - `'category'`: Category
* - `'tag'`: Tags
* - `'reading-time'`: Expect reading time
* - `'word'`: Word number for the article
* - `'visitor'`: Visitor Number
*
* @default ['author', 'visitor', 'time', 'category', 'tag', 'reading-time']
*/
pageInfo?: PageInfotype[] | false;
/**
* 页脚配置
*
* Footer Configuration
*/
footer?: HopeFooterConfig;
}
import {
HopeNavBarConfig,
HopeSideBarConfig,
HopeThemeLocaleConfigItem,
} from "@mr-hope/vuepress-shared";
import { AlgoliaOption } from "@mr-hope/vuepress-types";
import { HopeFooterConfig } from "./layout";
/** vuepress-theme-hope 多语言配置 */
export interface HopeLangLocalesConfig
extends Partial<HopeThemeLocaleConfigItem> {
/** 当前语言下的标题 */
title?: string;
/** 当前语言下的描述 */
description?: string;
/** 导航栏链接 */
nav?: HopeNavBarConfig;
/** 侧边栏配置 */
sidebar?: HopeSideBarConfig;
/** 当前语言的 algolia 设置 */
algolia?: AlgoliaOption;
/** 页脚设置 */
footer?: HopeFooterConfig;
}
import { ActiveHashOptions } from "vuepress-plugin-active-hash";
import { CommentOptions } from "@mr-hope/vuepress-plugin-comment";
import { CopyCodeOptions } from "@mr-hope/vuepress-plugin-copy-code";
import { FeedOptions } from "@mr-hope/vuepress-plugin-feed";
import { GitOptions } from "@mr-hope/vuepress-plugin-git";
import { MarkdownEnhanceOptions } from "vuepress-plugin-md-enhance";
import { PWAOptions } from "@mr-hope/vuepress-plugin-pwa";
import { PhotoSwipeOptions } from "vuepress-plugin-photo-swipe";
import { SeoOptions } from "@mr-hope/vuepress-plugin-seo";
import { SitemapOptions } from "@mr-hope/vuepress-plugin-sitemap";
import { SmoothScrollOptions } from "@mr-hope/vuepress-plugin-smooth-scroll";
import type { Page, ResolvedComponent } from "@mr-hope/vuepress-types";
/**
* 重命名块选项
*
* Options for renaming chunks
*/
export interface ChunkRenameOptions {
/**
* 页面块重命名选项。 默认情况下,所有页面块都将以页面标题命名。
*
* Page Chunk Rename Option. By default, all page chunks will be named with page title.
*/
pageChunkName: ((page: Page) => string) | false;
/**
* 布局块重命名选项。 默认情况下,所有布局块都将通过其组件名称来命名。
*
* Layout Chunk Rename Option. By default, all the layout chunks will be named by their component name.
*/
layoutChunkName: ((layout: ResolvedComponent) => string) | false;
}
/**
* Options for cleaning url suffix
*/
export interface CleanUrlOptions {
/**
* 普通页面后缀。此默认行为将为 `/a/b.md` 生成 `/a/b`。
*
* Nornal Page suffix. This default behavior will generate `a/b.md` with `/a/b`.
*
* @default ''
*/
normalSuffix: string;
/**
* `index.md`,`readme.md` 和 `README.md` 的页面后缀。此默认行为将为 `a/readme.md` 生成 `/a/`。
*
* Page suffix for `index.md`, `readme.md` and `README.md`. This default behavior will generate `a/readme.md` with `/a/`.
*
* @default '/'
*/
indexSuffix: string;
/**
* 未找到页面的链接
*
* Link for not found pages
*
* @default './404.html'
*/
notFoundPath: string;
}
/**
* 版权设置
*
* Copyright Settings
*/
export interface HopeCopyrightConfig {
/**
* 功能状态
*
* - `'global'` 意味着全局启用
* - `'local'` 意味着全局禁用,可在页面内启用
*
* Feature Status
*
* - `'global'` means enabled globally
* - `'local'` means disabled globally and can be enabled in pages
*
* @default 'global'
*/
status?: "global" | "local";
/**
* 触发版权信息或禁止复制动作的最少字符数
*
* The minimum text length that triggers the clipboard component or the noCopy effect
*/
minLength?: number;
/**
* 是否禁止复制
*
* Whether to prohibit copying.
*/
noCopy?: boolean;
/**
* 是否禁止选中文字
*
* Whether to prohibit selecting.
*/
noSelect?: boolean;
}
interface HopeThemePluginConfig {
/**
* AddThis 的公共 ID
* @see http://vuepress-theme-hope.github.io/add-this/zh/config/
*
* pubid for addthis
* @see http://vuepress-theme-hope.github.io/add-this/config/
*/
addThis?: string;
activeHash?: ActiveHashOptions | false;
/**
* 评论插件配置
* @see http://vuepress-theme-hope.github.io/comment/zh/config/
*
* Comment plugin options
* @see http://vuepress-theme-hope.github.io/comment/config/
*/
comment?: CommentOptions;
/**
* chunk 重命名
*
* @see https://vuepress-theme-hope.github.io/zh/config/theme/plugin/#chunkrename
*
* Chunk Rename
* @see https://vuepress-theme-hope.github.io/config/theme/plugin/#chunkrename
*/
chunkRename?: ChunkRenameOptions | false;
/**
* 清理插件配置
* @see https://vuepress-theme-hope.github.io/zh/config/theme/plugin/#cleanurl
*
* Clean Url Config
* @see https://vuepress-theme-hope.github.io/config/theme/plugin/#cleanurl
*/
cleanUrl?: CleanUrlOptions | false;
/**
* 代码复制插件配置
* @see http://vuepress-theme-hope.github.io/copy-code/zh/config/
*
* code copy plugin options
* @see http://vuepress-theme-hope.github.io/copy-code/config/
*/
copyCode?: CopyCodeOptions | false;
/**
* 版权设置
*
* Copyright plugin options
*/
copyright?: HopeCopyrightConfig;
/**
* Feed 插件配置
* @see http://vuepress-theme-hope.github.io/feed/zh/config/
*
* Feed plugin options
* @see http://vuepress-theme-hope.github.io/feed/config/
*/
feed?: FeedOptions | false;
/**
* Git 插件配置
* @see http://vuepress-theme-hope.github.io/git/zh/
*
* Git plugin options
* @see http://vuepress-theme-hope.github.io/git/
*/
git?: GitOptions | false;
/**
* Markdown 增强插件配置
* @see http://vuepress-theme-hope.github.io/md-enhance/zh/config/
*
* Markdown enhance plugin options
* @see http://vuepress-theme-hope.github.io/md-enhance/config/
*/
mdEnhance?: MarkdownEnhanceOptions | false;
/**
* PWA 插件配置
* @see http://vuepress-theme-hope.github.io/pwa/zh/config/
*
* PWA plugin options
* @see http://vuepress-theme-hope.github.io/pwa/config/
*/
pwa?: PWAOptions | false;
/**
* 图片预览插件配置
* @see http://vuepress-theme-hope.github.io/photo-swipe/zh/config/
*
* Photo Swipe plugin options
* @see http://vuepress-theme-hope.github.io/photo-swipe/config/
*/
photoSwipe?: PhotoSwipeOptions | false;
/**
* SEO 插件配置
* @see http://vuepress-theme-hope.github.io/seo/zh/config/
*
* SEO plugin options
* @see http://vuepress-theme-hope.github.io/seo/config/
*/
seo?: SeoOptions | false;
/**
* Sitemap 插件配置
* @see http://vuepress-theme-hope.github.io/sitemap/zh/config/
*
* Sitemap plugin options
* @see http://vuepress-theme-hope.github.io/sitemap/config/
*/
sitemap?: SitemapOptions | false;
smoothScrollOptions?: SmoothScrollOptions | number | false;
/**
* ts-loader 选项
*
* Options which will passed to ts-loader
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
typescript?: Record<string, any> | boolean;
}
import { AlgoliaOption, I18nConfig } from "@mr-hope/vuepress-types";
import { HopeThemeAppearanceConfig } from "./appearance";
import { HopeFeatureConfig } from "./feature";
import { HopeLayoutConfig } from "./layout";
import { HopeLangLocalesConfig } from "./locale";
import { HopeThemePluginConfig } from "./plugin";
/** vuepress-theme-hope 主题配置 */
export interface HopeThemeConfig
extends HopeThemeAppearanceConfig,
HopeFeatureConfig,
HopeLayoutConfig,
HopeThemePluginConfig {
/** 导航栏 Logo,应为绝对路径 */
logo?: string;
/**
* 暗黑模式下 logo
*
* Logo Image under darkmode
*/
darkLogo?: string;
/** 显示所有页面的标题链接 */
displayAllHeaders?: boolean;
/** 是否启用默认的搜索框 */
search?: boolean;
/** 搜索框占位符 */
searchPlaceholder?: string;
/** 默认搜索框显示的搜索结果数量 */
searchMaxSuggestions?: number;
/** Algolia 搜索配置 */
algolia?: AlgoliaOption;
/** 所有页面的 下一篇 链接 */
nextLinks?: boolean;
/** 所有页面的 上一篇 链接 */
prevLinks?: boolean;
/** 项目仓库地址 */
repo?: string;
/** 仓库标签文字 */
repoLabel?: string;
/** 文档所属仓库 */
docsRepo?: string;
/** 文档所属文件夹 */
docsDir?: string;
/** 文档所属分支 */
docsBranch?: string;
/**
* 多语言配置
*
* i18n config
*/
locales?: Record<string, I18nConfig & HopeLangLocalesConfig>;
/** 站点地址 */
hostname?: string;
/**
* 文章显示的默认作者
*
* The default author of the article
*/
author?: string;
}
/** 处理后的 vuepress-theme-hope 主题配置 */
export interface ResolvedHopeThemeConfig extends HopeThemeConfig {
/** 侧边栏深度 */
sidebarDepth: number;
/** 图标 FontClass 前缀 */
iconPrefix: string;
/** 多语言配置 */
locales: Record<string, I18nConfig & HopeLangLocalesConfig>;
/** 页脚配置 */
footer: HopeFooterConfig;
/** 显示编辑本页链接 */
editLinks: boolean;
}
import type { PageComputed } from "@mr-hope/vuepress-types";
export declare const getDate: (date: string | number | Date) => (number | undefined)[];
export declare const compareDate: (dataA: Date | number | string | undefined, dataB: Date | number | string | undefined) => number;
export declare const filterArticle: (pages: PageComputed[], filterFunc?: ((page: PageComputed) => boolean) | undefined) => PageComputed[];
export declare const sortArticle: (pages: PageComputed[], compareKey?: "sticky" | "star" | undefined) => PageComputed[];
export declare const generatePagination: (pages: PageComputed[], perPage?: number) => PageComputed[][];
import * as dayjs from "dayjs";
export const getDate = (date) => {
const time = dayjs(date instanceof Date || typeof date === "number" ? date : date.trim());
if (time.isValid()) {
const year = time.year();
const month = time.month() + 1;
const date = time.date();
const hour = time.hour();
const minute = time.minute();
const second = time.second();
const millisecond = time.millisecond();
if ((hour === 8 || hour === 0) &&
minute === 0 &&
second === 0 &&
millisecond === 0)
return [year, month, date, undefined, undefined, undefined];
return [year, month, date, hour, minute, second];
}
const pattern = /(?:(\d+)[/-](\d+)[/-](\d+))?\s*(?:(\d+):(\d+)(?::(\d+))?)?/u;
const [, year, month, day, hour, minute, second] = pattern.exec(date.trim()) || [];
const getNumber = (a) => typeof a === "undefined" ? undefined : Number(a);
const getYear = (yearNumber) => yearNumber && yearNumber < 100 ? yearNumber + 2000 : yearNumber;
const getSecond = (secondNumber) => hour && minute && !second ? 0 : secondNumber;
return [
getYear(getNumber(year)),
getNumber(month),
getNumber(day),
getNumber(hour),
getNumber(minute),
getSecond(getNumber(second)),
];
};
export const compareDate = (dataA, dataB) => {
if (!dataA)
return 1;
if (!dataB)
return -1;
const compare = (a, b) => {
if (a.length === 0)
return 0;
if (typeof b[0] === "undefined")
return typeof a[0] === "undefined" || a[0] === 0 ? 0 : -1;
if (typeof a[0] === "undefined")
return b[0] === 0 ? 0 : 1;
if (b[0] - a[0] === 0) {
a.shift();
b.shift();
return compare(a, b);
}
return b[0] - a[0];
};
return compare(getDate(dataA), getDate(dataB));
};
export const filterArticle = (pages, filterFunc) => pages.filter((page) => {
const { frontmatter: { article, blogpage, home }, title, } = page;
return (typeof title !== "undefined" &&
blogpage !== true &&
home !== true &&
article !== false &&
(!filterFunc || filterFunc(page)));
});
export const sortArticle = (pages, compareKey) => pages.slice(0).sort((prev, next) => {
if (compareKey) {
const prevKey = prev.frontmatter[compareKey];
const nextKey = next.frontmatter[compareKey];
if (prevKey && nextKey && prevKey !== nextKey)
return Number(nextKey) - Number(prevKey);
if (prevKey && !nextKey)
return -1;
if (!prevKey && nextKey)
return 1;
}
const prevTime = prev.frontmatter.time || prev.frontmatter.date || prev.createTimeStamp;
const nextTime = next.frontmatter.time || next.frontmatter.date || next.createTimeStamp;
return compareDate(prevTime, nextTime);
});
export const generatePagination = (pages, perPage = 10) => {
const result = [];
let index = 0;
while (index < pages.length) {
const paginationPage = [];
for (let i = 0; i < perPage; i++)
if (index < pages.length) {
paginationPage.push(pages[index]);
index += 1;
}
result.push(paginationPage);
}
return result;
};
//# sourceMappingURL=article.js.map
\ No newline at end of file
{"version":3,"file":"article.js","sourceRoot":"","sources":["article.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
import type { DirectiveOptions, VNode } from "vue";
import type { DirectiveBinding } from "vue/types/options";
declare type Event = TouchEvent | MouseEvent;
interface PopupHtmlElement extends HTMLElement {
$vueClickOutside?: {
callback: (event: Event) => void;
handler: (event: Event) => void;
};
}
declare type PopupDirectiveFunction = (el: PopupHtmlElement, binding: DirectiveBinding, vnode: VNode, oldVnode: VNode) => void;
export declare const bind: PopupDirectiveFunction;
export declare const update: PopupDirectiveFunction;
export declare const unbind: PopupDirectiveFunction;
declare const _default: DirectiveOptions;
export default _default;
const validate = (binding) => {
if (typeof binding.value !== "function") {
console.warn("[Vue-click-outside:] provided expression", binding.expression, "is not a function.");
return false;
}
return true;
};
const isPopup = (popupItem, elements) => {
if (!popupItem || !elements)
return false;
for (let i = 0, len = elements.length; i < len; i++)
try {
if (popupItem.contains(elements[i]))
return true;
if (elements[i].contains(popupItem))
return false;
}
catch (err) {
return false;
}
return false;
};
const isServer = (vNode) => typeof vNode.componentInstance !== "undefined" &&
vNode.componentInstance.$isServer;
export const bind = (el, binding, vNode) => {
if (!validate(binding))
return;
// Define Handler and cache it on the element
const handler = (event) => {
if (!vNode.context)
return;
// Some components may have related popup item, on which we shall prevent the click outside event handler.
// eslint-disable-next-line
const elements =
// eslint-disable-next-line
event.path ||
(event.composedPath ? event.composedPath() : []);
if (elements && elements.length > 0)
elements.unshift(event.target);
if (el.contains(event.target) ||
// eslint-disable-next-line
isPopup(vNode.context.popupItem, elements))
return;
if (el.$vueClickOutside)
el.$vueClickOutside.callback(event);
};
// Add Event Listeners
el.$vueClickOutside = {
handler,
callback: binding.value,
};
const clickHandler = "ontouchstart" in document.documentElement ? "touchstart" : "click";
if (!isServer(vNode))
document.addEventListener(clickHandler, handler);
};
export const update = (el, binding) => {
if (validate(binding) && el.$vueClickOutside)
el.$vueClickOutside.callback = binding.value;
};
export const unbind = (el, _binding, vNode) => {
// Remove Event Listeners
const clickHandler = "ontouchstart" in document.documentElement ? "touchstart" : "click";
if (!isServer(vNode) && el.$vueClickOutside)
document.removeEventListener(clickHandler, el.$vueClickOutside.handler);
delete el.$vueClickOutside;
};
export default {
bind,
update,
unbind,
};
//# sourceMappingURL=click-outside.js.map
\ No newline at end of file
{"version":3,"file":"click-outside.js","sourceRoot":"","sources":["click-outside.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
export default class Color {
type: "hex" | "rgb";
red: number;
green: number;
blue: number;
alpha: number;
constructor(type: "hex" | "rgb", red: number, green: number, blue: number, alpha?: number);
static fromHex(color: string): Color;
static fromRGB(color: string): Color;
static getColor(colorString: string): Color;
toString(): string;
adjust(item: "red" | "green" | "blue" | "alpha", amount: number): void;
darken(amount: number): Color;
lighten(amount: number): Color;
}
export default class Color {
constructor(type, red, green, blue, alpha = 1) {
this.type = type;
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
}
static fromHex(color) {
const parseHex = (colorString) => parseInt(colorString, 16);
const parseAlpha = (colorString, total) => Math.round((parseHex(colorString) * 100) / total) / 100;
if (color.length === 4)
return new Color("hex", parseHex(color[1]) * 17, parseHex(color[2]) * 17, parseHex(color[3]) * 17);
if (color.length === 5)
return new Color("hex", parseHex(color[1]) * 17, parseHex(color[2]) * 17, parseHex(color[3]) * 17, parseAlpha(color[4], 15));
if (color.length === 7)
return new Color("hex", parseHex(color.substring(1, 3)), parseHex(color.substring(3, 5)), parseHex(color.substring(5, 7)));
return new Color("hex", parseHex(color.substring(1, 3)), parseHex(color.substring(3, 5)), parseHex(color.substring(5, 7)), parseAlpha(color.substring(7, 9), 255));
}
// From RGB or RGBA
static fromRGB(color) {
// eslint-disable-next-line @typescript-eslint/naming-convention
const RGBAPattern = /rgba\((.+)?,(.+)?,(.+)?,(.+)?\)/u;
// eslint-disable-next-line @typescript-eslint/naming-convention
const RGBPattern = /rgb\((.+)?,(.+)?,(.+)?\)/u;
const fromRGB = (colorString) => colorString.includes("%")
? (Number(colorString.trim().substring(0, colorString.trim().length - 1)) /
100) *
256 -
1
: Number(colorString.trim());
const rgbaResult = RGBAPattern.exec(color);
if (rgbaResult)
return new Color("rgb", fromRGB(rgbaResult[1]), fromRGB(rgbaResult[2]), fromRGB(rgbaResult[3]), Number(rgbaResult[4] || 1));
const rgbResult = RGBPattern.exec(color);
if (rgbResult)
return new Color("rgb", fromRGB(rgbResult[1]), fromRGB(rgbResult[2]), fromRGB(rgbResult[3]));
throw new Error(`Can not handle color: ${color}`);
}
static getColor(colorString) {
if (colorString.startsWith("#"))
return this.fromHex(colorString);
return this.fromRGB(colorString);
}
toString() {
if (this.type === "hex" && this.alpha === 1) {
const toHex = (color) => color < 10
? color.toString()
: color === 10
? "a"
: color === 11
? "b"
: color === 12
? "c"
: color === 13
? "d"
: color === 14
? "e"
: "f";
if (this.red % 17 === 0 && this.green % 17 === 0 && this.blue % 17 === 0)
return `#${toHex(this.red / 17)}${toHex(this.green / 17)}${toHex(this.blue / 17)}`;
const getHex = (color) => toHex((color - (color % 16)) / 16) + toHex(color % 16);
return `#${getHex(this.red)}${getHex(this.green)}${getHex(this.blue)}`;
}
return this.alpha === 1
? `rgb(${this.red},${this.green},${this.blue})`
: `rgba(${this.red},${this.green},${this.blue},${this.alpha})`;
}
adjust(item, amount) {
const result = Math.round(this[item] * amount);
if (item === "alpha")
this.alpha = result < 0 ? 0 : result > 1 ? 1 : result;
else
this[item] = result < 0 ? 0 : result > 255 ? 255 : result;
}
darken(amount) {
this.adjust("red", 1 - amount);
this.adjust("green", 1 - amount);
this.adjust("blue", 1 - amount);
return this;
}
lighten(amount) {
this.adjust("red", 1 + amount);
this.adjust("green", 1 + amount);
this.adjust("blue", 1 + amount);
return this;
}
}
//# sourceMappingURL=color.js.map
\ No newline at end of file
{"version":3,"file":"color.js","sourceRoot":"","sources":["color.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
/**
* Change DOM classes
*
* @param domClass DOM classlist
* @param insert class to insert
* @param remove class to remove
*/
export declare const changeClass: (domClass: DOMTokenList, insert: string[], remove: string[]) => void;
/**
* Change DOM classes
*
* @param domClass DOM classlist
* @param insert class to insert
* @param remove class to remove
*/
export const changeClass = (domClass, insert, remove) => {
const oldClasses = [];
domClass.remove(...remove);
domClass.forEach((classname) => {
oldClasses.push(classname);
});
domClass.value = "";
domClass.add(...insert, ...oldClasses);
};
//# sourceMappingURL=dom.js.map
\ No newline at end of file
{"version":3,"file":"dom.js","sourceRoot":"","sources":["dom.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,QAAsB,EACtB,MAAgB,EAChB,MAAgB,EACV,EAAE;IACR,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,EAAE;QAC7B,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IACH,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAC;IACpB,QAAQ,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC;AACzC,CAAC,CAAC"}
\ No newline at end of file
import type { EncryptOptions } from "../types";
export declare const getPathMatchedKeys: (encryptOptions: EncryptOptions | undefined, path: string) => string[];
export declare const getPathEncryptStatus: (encryptOptions: EncryptOptions | undefined, passwordConfig: Record<string, string>, path: string) => boolean;
export const getPathMatchedKeys = (encryptOptions, path) => encryptOptions && typeof encryptOptions.config === "object"
? Object.keys(encryptOptions.config)
.filter((key) => path.startsWith(key))
.sort((a, b) => b.length - a.length)
: [];
export const getPathEncryptStatus = (encryptOptions, passwordConfig, path) => {
const hitKeys = getPathMatchedKeys(encryptOptions, path);
if (hitKeys.length !== 0) {
const { config } = encryptOptions;
return !hitKeys.some((key) => {
const keyConfig = config[key];
const hitPasswords = typeof keyConfig === "string" ? [keyConfig] : keyConfig;
return hitPasswords.some((password) => passwordConfig[key] === password);
});
}
return false;
};
//# sourceMappingURL=encrypt.js.map
\ No newline at end of file
{"version":3,"file":"encrypt.js","sourceRoot":"","sources":["encrypt.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,cAA0C,EAC1C,IAAY,EACF,EAAE,CACZ,cAAc,IAAI,OAAO,cAAc,CAAC,MAAM,KAAK,QAAQ;IACzD,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC;SAC/B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;SACrC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,CAAC,CAAC,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,cAA0C,EAC1C,cAAsC,EACtC,IAAY,EACH,EAAE;IACX,MAAM,OAAO,GAAG,kBAAkB,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAEzD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE;QACxB,MAAM,EAAE,MAAM,EAAE,GAAG,cAA0C,CAAC;QAE9D,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE;YAC3B,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,YAAY,GAChB,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAE1D,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC;QAC3E,CAAC,CAAC,CAAC;KACJ;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
\ No newline at end of file
import type { PageHeader } from "@mr-hope/vuepress-types";
export interface SidebarHeader extends PageHeader {
children?: PageHeader[];
}
/** Group lower level headings under h2 children */
export declare const groupHeaders: (headers: PageHeader[]) => SidebarHeader[];
/** Group lower level headings under h2 children */
export const groupHeaders = (headers) => {
const headerscopy = headers.map((header) => (Object.assign({}, header)));
let lastH2;
// group other headings under h2 headings
headerscopy.forEach((header) => {
if (header.level === 2)
lastH2 = header;
else if (lastH2) {
if (!lastH2.children)
lastH2.children = [];
lastH2.children.push(header);
}
});
// filter only h2 headings
return headerscopy.filter((header) => header.level === 2);
};
//# sourceMappingURL=groupHeader.js.map
\ No newline at end of file
{"version":3,"file":"groupHeader.js","sourceRoot":"","sources":["groupHeader.ts"],"names":[],"mappings":"AAMA,mDAAmD;AACnD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAqB,EAAmB,EAAE;IACrE,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,mBAAM,MAAM,EAAG,CAAC,CAAC;IAC7D,IAAI,MAAqB,CAAC;IAE1B,yCAAyC;IACzC,WAAW,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;QAC7B,IAAI,MAAM,CAAC,KAAK,KAAK,CAAC;YAAE,MAAM,GAAG,MAAM,CAAC;aACnC,IAAI,MAAM,EAAE;YACf,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAAE,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAC;YAC3C,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SAC9B;IACH,CAAC,CAAC,CAAC;IAEH,0BAA0B;IAC1B,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC"}
\ No newline at end of file
import type { HopeNavBarConfigItem } from "../types";
export interface NavBarConfigItem extends HopeNavBarConfigItem {
type: "link" | "links";
items: NavBarConfigItem[];
}
export declare const getNavLinkItem: (navbarLink: HopeNavBarConfigItem, beforeprefix?: string) => NavBarConfigItem;
export const getNavLinkItem = (navbarLink, beforeprefix = "") => {
var _a;
const prefix = beforeprefix + (navbarLink.prefix || "");
const navbarItem = Object.assign({}, navbarLink);
if (prefix) {
if (navbarItem.link !== undefined)
navbarItem.link = prefix + navbarItem.link;
delete navbarItem.prefix;
}
if ((_a = navbarItem.items) === null || _a === void 0 ? void 0 : _a.length)
Object.assign(navbarItem, {
type: "links",
items: navbarItem.items.map((item) => getNavLinkItem(item, prefix)),
});
else
navbarItem.type = "link";
return navbarItem;
};
//# sourceMappingURL=navbar.js.map
\ No newline at end of file
{"version":3,"file":"navbar.js","sourceRoot":"","sources":["navbar.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,UAAgC,EAChC,YAAY,GAAG,EAAE,EACC,EAAE;;IACpB,MAAM,MAAM,GAAG,YAAY,GAAG,CAAC,UAAU,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAExD,MAAM,UAAU,qBACX,UAAU,CACd,CAAC;IAEF,IAAI,MAAM,EAAE;QACV,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS;YAC/B,UAAU,CAAC,IAAI,GAAG,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC;QAC7C,OAAO,UAAU,CAAC,MAAM,CAAC;KAC1B;IAED,IAAI,MAAA,UAAU,CAAC,KAAK,0CAAE,MAAM;QAC1B,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE;YACxB,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;SACpE,CAAC,CAAC;;QACA,UAAU,CAAC,IAAI,GAAG,MAAM,CAAC;IAE9B,OAAO,UAA8B,CAAC;AACxC,CAAC,CAAC"}
\ No newline at end of file
import type VueRouter from "vue-router";
import type { Route } from "vue-router";
/**
* @param url navigate link
* @param router router
* @param route current route
*/
export declare const navigate: (url: string, router: VueRouter, route: Route) => void;
/**
* @param url navigate link
* @param router router
* @param route current route
*/
export const navigate = (url, router, route) => {
if (url)
if (url.startsWith("/")) {
// Inner absolute path
if (route.path !== url)
void router.push(url);
}
else if (url.startsWith("http://") ||
url.startsWith("https://") ||
url.startsWith("mailto:")) {
// Outter url
if (window)
window.open(url);
}
else {
// Inner relative path
const base = route.path.slice(0, route.path.lastIndexOf("/"));
void router.push(`${base}/${encodeURI(url)}`);
}
};
//# sourceMappingURL=navigate.js.map
\ No newline at end of file
{"version":3,"file":"navigate.js","sourceRoot":"","sources":["navigate.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CACtB,GAAW,EACX,MAAiB,EACjB,KAAY,EACN,EAAE;IACR,IAAI,GAAG;QACL,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;YACvB,sBAAsB;YACtB,IAAI,KAAK,CAAC,IAAI,KAAK,GAAG;gBAAE,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SAC/C;aAAM,IACL,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC;YACzB,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC;YAC1B,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EACzB;YACA,aAAa;YACb,IAAI,MAAM;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SAC9B;aAAM;YACL,sBAAsB;YACtB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YAE9D,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;SAC/C;AACL,CAAC,CAAC"}
\ No newline at end of file
import type { Route } from "vue-router";
export declare const hashRE: RegExp;
export declare const extRE: RegExp;
export declare const endingSlashRE: RegExp;
export declare const outboundRE: RegExp;
/** Remove hash and ext in a link */
export declare const normalize: (path: string) => string;
export declare const getHash: (path: string) => string | void;
/** Judge whether a path is external */
export declare const isExternal: (path: string) => boolean;
/** Judge whether a path is `mailto:` link */
export declare const isMailto: (path: string) => boolean;
/** Judge whether a path is `tel:` link */
export declare const isTel: (path: string) => boolean;
export declare const ensureExt: (path: string) => string;
export declare const ensureEndingSlash: (path: string) => string;
/** Judge whether a route match a link */
export declare const isActive: (route: Route, path: string) => boolean;
/**
* @param path links being resolved
* @param base deploy base
* @param append whether append directly
*/
export declare const resolvePath: (path: string, base: string, append?: boolean | undefined) => string;
export const hashRE = /#.*$/u;
export const extRE = /\.(md|html)$/u;
export const endingSlashRE = /\/$/u;
export const outboundRE = /^[a-z]+:/iu;
/** Remove hash and ext in a link */
export const normalize = (path) => decodeURI(path).replace(hashRE, "").replace(extRE, "");
export const getHash = (path) => {
const match = hashRE.exec(path);
if (match)
return match[0];
return "";
};
/** Judge whether a path is external */
export const isExternal = (path) => outboundRE.test(path);
/** Judge whether a path is `mailto:` link */
export const isMailto = (path) => path.startsWith("mailto:");
/** Judge whether a path is `tel:` link */
export const isTel = (path) => path.startsWith("tel:");
export const ensureExt = (path) => {
// do not resolve external links
if (isExternal(path))
return path;
const hashMatch = hashRE.exec(path);
const hash = hashMatch ? hashMatch[0] : "";
const normalized = normalize(path);
// do not resolve links ending with `/`
if (normalized.endsWith("/"))
return path;
// add `.html` ext
return `${normalized}.html${hash}`;
};
export const ensureEndingSlash = (path) => /(\.html|\/)$/u.test(path) ? path : `${path}/`;
/** Judge whether a route match a link */
export const isActive = (route, path) => {
const routeHash = decodeURIComponent(route.hash);
const linkHash = getHash(path);
// compare the hash only if the link has a hash
if (linkHash && routeHash !== linkHash)
return false;
const routePath = normalize(route.path);
const pagePath = normalize(path);
return routePath === pagePath;
};
/**
* @param path links being resolved
* @param base deploy base
* @param append whether append directly
*/
export const resolvePath = (path, base, append) => {
// do not resolve external links
if (isExternal(path))
return path;
const firstChar = path.charAt(0);
// do not resolve absolute links
if (firstChar === "/")
return path;
// if link is hash or query string, add with base
if (firstChar === "?" || firstChar === "#")
return `${base}${path}`;
// base links stack
const stack = base.split("/");
/*
* remove trailing segment if:
* - not appending
* - appending to trailing slash (last segment is empty)
*/
if (!append || !stack[stack.length - 1])
stack.pop();
// resolve relative path
const segments = path.replace(/^\//u, "").split("/");
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (segment === "..")
stack.pop();
else if (segment !== ".")
stack.push(segment);
}
// ensure leading slash
if (stack[0] !== "")
stack.unshift("");
return stack.join("/");
};
//# sourceMappingURL=path.js.map
\ No newline at end of file
{"version":3,"file":"path.js","sourceRoot":"","sources":["path.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
import type { PageComputed, SiteData } from "@mr-hope/vuepress-types";
import type { SidebarHeader } from "./groupHeader";
export type { SidebarHeader } from "./groupHeader";
export interface SidebarHeaderItem extends SidebarHeader {
type: "header";
basePath: string;
path: string;
}
export interface SidebarAutoItem {
type: "group";
/** Group title */
title: string;
/** Page Icon */
icon?: string;
/** Titles in page */
children: SidebarHeaderItem[];
collapsable: false;
path: "";
}
export declare const groupSidebarHeaders: (headers: import("@mr-hope/vuepress-types").PageHeader[]) => SidebarHeader[];
export interface SidebarExternalItem {
title?: string;
icon?: string;
type: "external";
path: string;
}
export interface SidebarPageItem extends PageComputed {
type: "page";
icon?: string;
path: string;
}
export interface SidebarGroupItem {
type: "group";
title: string;
/** @default true */
collapsable?: boolean;
/** @default 1 */
sidebarDepth?: number;
icon?: string;
prefix?: string;
children: SidebarItem[];
[props: string]: unknown;
}
export interface SidebarErrorItem {
type: "error";
path: string;
}
/** sidebarConfig merged with pageObject */
export declare const resolvePageforSidebar: (pages: PageComputed[], path: string) => SidebarPageItem | SidebarExternalItem | SidebarErrorItem;
export declare type SidebarItem = SidebarAutoItem | SidebarErrorItem | SidebarExternalItem | SidebarGroupItem | SidebarPageItem;
export declare const getSidebarItems: (page: PageComputed, site: SiteData, localePath: string) => SidebarItem[];
import { groupHeaders } from "./groupHeader";
import { ensureEndingSlash, ensureExt, isExternal, normalize, resolvePath, } from "./path";
export const groupSidebarHeaders = groupHeaders;
const resolveSidebarHeaders = (page) => {
const headers = page.headers ? groupSidebarHeaders(page.headers) : [];
return [
{
type: "group",
collapsable: false,
title: page.title,
icon: page.frontmatter.icon,
path: "",
children: headers.map((header) => (Object.assign(Object.assign({}, header), { type: "header", basePath: page.path, path: `${page.path}#${header.slug}`, children: header.children }))),
},
];
};
const findMatchingSidebarConfig = (regularPath, config) => {
// return directly as array-type config is the moest simple config
if (Array.isArray(config))
return {
base: "/",
config,
};
// find matching config
for (const base in config)
if (ensureEndingSlash(regularPath).startsWith(encodeURI(base)))
return {
base,
config: config[base],
};
console.warn(`${regularPath} do not have valid sidebar config`);
return false;
};
/** sidebarConfig merged with pageObject */
export const resolvePageforSidebar = (pages, path) => {
// if it is external link
if (isExternal(path))
return {
type: "external",
path,
};
const realPath = normalize(path);
// find matches in all pages
for (const page of pages)
if (normalize(page.regularPath) === realPath)
// return sidebarConfig merged with pageObject
return Object.assign(Object.assign({}, page), { type: "page", path: ensureExt(page.path) });
console.error(`Sidebar: "${realPath}" has no matching page`);
return { type: "error", path: realPath };
};
const resolve = (prefix, path, base) => resolvePath(`${prefix}${path}`, base);
/**
* @param sidebarConfigItem config item being resolved
* @param pages pages Object
* @param base sidebar base
*/
const resolveSidebarItem = (sidebarConfigItem, pages, base, prefix = "") => {
// resolve and return directly
if (typeof sidebarConfigItem === "string")
return resolvePageforSidebar(pages, resolve(prefix, sidebarConfigItem, base));
// custom title with format `['path', 'customTitle']`
if (Array.isArray(sidebarConfigItem))
return Object.assign(resolvePageforSidebar(pages, resolve(prefix, sidebarConfigItem[0], base)), { title: sidebarConfigItem[1] });
const children = sidebarConfigItem.children || [];
// item do not have children
if (children.length === 0 && sidebarConfigItem.path)
// cover title
return Object.assign(resolvePageforSidebar(pages, resolve(prefix, sidebarConfigItem.path, base)), { title: sidebarConfigItem.title });
// resolve children recursively then return
return Object.assign(Object.assign({}, sidebarConfigItem), { type: "group", path: sidebarConfigItem.path
? resolve(prefix, sidebarConfigItem.path, base)
: "", children: children.map((child) => resolveSidebarItem(child, pages, base, `${prefix}${sidebarConfigItem.prefix || ""}`)), collapsable: sidebarConfigItem.collapsable !== false });
};
export const getSidebarItems = (page, site, localePath) => {
const { themeConfig, pages } = site;
const localeConfig = localePath && themeConfig.locales
? themeConfig.locales[localePath] || themeConfig
: themeConfig;
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar;
// auto generate sidebar through headings
if (page.frontmatter.sidebar === "auto" || sidebarConfig === "auto")
return resolveSidebarHeaders(page);
// sidebar is disabled
if (!sidebarConfig)
return [];
const result = findMatchingSidebarConfig(page.regularPath, sidebarConfig);
return result
? result.config.map((item) => resolveSidebarItem(item, pages, result.base))
: [];
};
//# sourceMappingURL=sidebar.js.map
\ No newline at end of file
{"version":3,"file":"sidebar.js","sourceRoot":"","sources":["sidebar.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
---
home: true
icon: home
title: Home
heroImage: /assets/logos/header.png
heroText: The Optimism Docs
features:
- title: Quick Start
icon: book
details: How-to guides and tutorials for users and developers.
link: /docs/guides/
- title: How Optimism works
icon: info-square
details: General explanation of what makes Optimism tick.
link: /docs/protocol/2-rollup-protocol/
- title: Optimism's Security Model
icon: shield
details: Understand the safety and liveness properties of Optimism.
link: /docs/security-model/
- title: Protocol specs
icon: ruler-triangle
details: More detailed information about how Optimism works under the hood.
link: /docs/protocol/
- title: Developer docs
icon: code
details: Resources for building a decentralized application on Optimism.
link: /docs/developers/
- title: Tools for developers
icon: hammer
details: Useful third-party tools for easier development and deployment.
link: /docs/useful-tools/
- title: Contribute
icon: hands-helping
details: Check out the Optimism community and join the conversation.
link: /docs/contribute/
---
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>0DJ9YTQ4C2SFAXJP</RequestId><HostId>4x2Oq0BDEyorLSFPel+zWDD4qgXLdcb+4QFY7rjVI6kP7vPU13LsrxId7/OcDOV4tWgFveDbOsV8YFQ6sgTJnQ==</HostId></Error>
\ No newline at end of file
---
title: How do I get project support (marketing / integrations / etc.)?
lang: en-US
---
Welcome! We're stoked that you're launching on Optimism.
If you have not filled out the [get started form](https://oplabs.typeform.com/get-in-touch?typeform-source=community.optimism.io) please do so prior to reading this.
We are excited for your deployment onto Optimism!
You will be welcomed by [the following metrics](https://dune.com/optimismfnd/Optimism) 📈📈 (up and to the right!), [a budding community](https://discord-gateway.optimism.io/) 🫂🫂, and some great exclusive [Telegram Channels](https://t.me/+Cb7q0a1YqItkZTRk) ⚙️⚙️ for builders.
The OPLabs team gets a flurry of inbound messages and calls every day and we reply to them as efficiently as possible, but we are only human!
Please be patient if you are in need of direct support.
We cannot always provide direct help, but we strive to help everyone succeed on Optimism through the various resources we provide to projects.
## Developer Support 🙋💻
Steps to take if you would like developer support immediately, ranked in order of response times.
1. See our [Developer Documentation](../developers/), [Tutorials](https://github.com/ethereum-optimism/optimism-tutorial), and [Help Center](https://help.optimism.io)
1. Head over to [**#dev-support**](https://discord.com/channels/667044843901681675/887914409207414785) on discord for the fastest help
1. Join [this TG group](https://t.me/+Cb7q0a1YqItkZTRk) and ask for help there
1. Last attempt: [Fill out this form](https://oplabs.typeform.com/get-in-touch?typeform-source=community.optimism.io)
## Marketing Requests 🦸🦸
When your project is deployed on Optimism, you can be added to [our ecosystem page](https://www.optimism.io/apps/all).
Simply [fill out this form](https://oplabs.typeform.com/op-marketing) to be included. Inclusion is at our discretion.
We add new projects live on the Optimism mainnet [here](https://www.optimism.io/apps/all) once a week.
### Promoting Values before anything else
We love to promote values-aligned projects as part of our broader content program.
### Tweeting
We do the occasional retweet but the bar for these is kept quite high in order to keep our Twitter feed as valuable/high signal as possible.
Other than RT's, we may like or reply to a tweet or include it in our weekly wrap up thread.
You're welcome to submit your tweet for amplification but please keep in mind if and how we interact with it is at our sole discretion.
### OPRadio
We also host a weekly Twitter Space in an interview/podcast format called OP Radio. You're welcome to express interest in being featured on OP Radio, but keep in mind that we maintain the final say on whether it makes sense or not.
## Governance Grants
Grants are mainly handled by [The Optimism Collective](https://app.optimism.io/announcement), our open governance system/digital society. If you are interested in applying, we first recommend the following:
- Check that your application falls within the possible Governance Grant categories in the [OPerating manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md#valid-proposal-types).
- Deploy to Optimism (we are [EVM Equivalent](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), not just EVM compatible so it is very easy to deploy).
Check out our [getting started tutorial](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/getting-started) to see how to deploy. We also have [tons of more in-depth tutorials](https://github.com/ethereum-optimism/optimism-tutorial) for bridging and other L2 specific functionality.
- Try gaining organic traction:
- Traction can be gained through our community.
- Head over to [our discord](https://discord-gateway.optimism.io/) and check out [#showandtell](https://discord.com/channels/667044843901681675/819333988719263774) or [#demoday](https://discord.com/channels/667044843901681675/960493820674465883).
Demo Day is an AMA about your product to our community. Apply in Discord.
- Seek synergies – look at other teams already deployed on Optimism that could work with your product and reach out to them.
- Once you have done the above, check out our [governance grant process](https://gov.optimism.io/t/grant-proposal-template/3233).
- To get reviews in real time from governance delegates go to discord and post about your project in [#temp-check](https://discord.com/channels/667044843901681675/1011238484373159956).
## Other Helpful Links
Curious about our Optimistic Origins?
Listen to [this OPRadio Episode](https://twitter.com/optimismFND/status/1585015947626426368) with one of our founders, [@JingleJam](https://twitter.com/jinglejamOP).
What do the next 100 years have in store for crypto? [@Karl_dot_tech](https://twitter.com/karl_dot_tech) shares his vision in [this chat with Bankless](https://www.youtube.com/watch?v=60F2wtjZO5M).
Want to see what we are doing with Retroactive Public Goods? [See here](../governance/retropgf-2.md)
Changes to the protocol coming in the near term include multi-client architecture, and the world’s next generation fault proof.
Check out [Bedrock](https://dev.optimism.io/introducing-optimism-bedrock/) and [Cannon](https://medium.com/ethereum-optimism/cannon-cannon-cannon-introducing-cannon-4ce0d9245a03).
Want to see the first ever step forward into a modular rollup architecture? We are making it easy for anyone to deploy their own app-specific rollup via the [OP Stack](https://optimism.mirror.xyz/fLk5UGjZDiXFuvQh6R_HscMQuuY9ABYNF7PI76-qJYs).
**Fun fact:** A team has already launched [a Voxel Game on the OP Stack](https://dev.optimism.io/opcraft-autonomous-world/)!
Interested in exploring more?
[Fill out this form](https://docs.google.com/forms/d/e/1FAIpQLSciIONqVoMI6ivlrvp2EAdjrWqmZVGIpEaBbhaVZ17ZOf8DqQ/viewform?usp=sharing).
**Last note:** [We’re hiring](https://boards.greenhouse.io/oplabs)!
Join our community!
Have fun!
Enjoy life!
Don’t worry so much!
Brush your teeth!
Eat apples!
Eat bacon!
Research the origins of public relations!
Give back to your community!
Tweet things you don’t actually believe!
Download free music using your library card!
Don’t download a car!
Don’t listen to anyone except for your parents, and even then, with a grain of salt!
## WELCOME TO THE COLLECTIVE 🔴✨
---
title: Contribute to Optimism
lang: en-US
---
🎈 Thanks for your help improving the project! We are so happy to have you!
There are plenty of ways to contribute, in particular we appreciate support in the following areas:
## Code contributions
The Optimism codebase is maintained in a monorepo at [https://github.com/ethereum-optimism/optimism](https://github.com/ethereum-optimism/optimism). It's a [collection of packages](https://github.com/ethereum-optimism/optimism#directory-structure) all requiring different skills to maintain and evolve ranging from NodeJS and TypeScript, Solidity and EVM, Go and Geth to Docker and Kubernetes. The following are good entry points into using your coding skills to help us build Optimism:
- Reporting issues. For security issues see [Security policy](https://github.com/ethereum-optimism/.github/blob/master/SECURITY.md).
- Participate in the [Bug Bouty programme](https://immunefi.com/bounty/optimism/).
- Fixing and responding to existing issues. You can start off with those tagged ["good first issue"](https://github.com/ethereum-optimism/optimism/contribute) which are meant as introductory issues for external contributors.
- Work on open [bounties on Gitcoin](https://gitcoin.co/ethereum-optimism).
::: tip
Following the guidelines on [Contributing](https://github.com/ethereum-optimism/optimism/blob/master/CONTRIBUTING.md) and [Code of Conduct](https://github.com/ethereum-optimism/.github/blob/master/CODE_OF_CONDUCT.md) in all your interactions with the project will ensure your contributions are processed by the team.
:::
## Community contributions
- Pick an idea from the [project ideas list](https://github.com/ethereum-optimism/optimism-project-ideas) to start building
- Improve the [community site](https://community.optimism.io/) [documentation](https://github.com/ethereum-optimism/community-hub) or [tutorials](https://github.com/ethereum-optimism/optimism-tutorial).
- Become an "Optimizer" and answer questions in the [Optimism Discord](https://discord-gateway.optimism.io).
---
title: Developer docs
lang: en-US
---
Welcome to the Optimism developer docs!
Whether you're just looking to [deploy a basic contract](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/getting-started) or you're ready to [build a cross-chain app](./bridge/messaging.md), you'll be able to find everything you need to start building on Optimism within this section.
If you're looking for third-party tools that make building on Optimism easier, check out the [Tools for Developers](../useful-tools) section.
## Where should I start?
### Just getting started with Optimism?
If you're brand new to Optimism, we recommend checking out the [guide to deploying a basic contract](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/getting-started).
It'll get you familiar with the core steps required to get a contract deployed to the network.
Luckily, Optimism is [EVM equivalent](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), so it's 100% the same as deploying a contract to Ethereum.
If you're a bit more familiar with Optimism and Ethereum, you can try walking through one of the various [tutorials](https://github.com/ethereum-optimism/optimism-tutorial) put together by the Optimism community.
They'll help you get a headstart when building your first Optimistic project.
### Ready to deploy a contract?
If you're looking to deploy your contracts to the Optimism mainnet or the Optimism Goerli testnet, take a look at our page on [using your favorite tools](./build/using-tools.md).
It contains sample configuration files for deploying your contracts from common frameworks like Hardhat, Truffle, and Brownie.
You might also want to check out our guides for [running a local development environment](./build/dev-node.md) or [running your own Optimism node](./build/run-a-node.md).
These guides are designed to help you feel totally confident in your Optimism deployment.
### Want to explore the cross-chain frontier?
We've got detailed guides for that.
If you want to bridge a token from Ethereum to Optimism (or vice versa!), you should learn more about our [Standard Token Bridge](./bridge/standard-bridge.md).
The Standard Token Bridge makes the process of moving tokens between chains as easy as possible.
If you're looking for something more advanced, we recommend reading through our page on [sending data between L1 and L2](./bridge/messaging.md).
Contracts on one chain can trigger contract functions on the other chain, it's pretty cool!
We even dogfood the same infrastructure and use it under the hood of the Standard Token Bridge.
## Still don't know where to look?
If you can't find the content you're looking for you've got a few options to get extra help.
We recommend first searching through this documentation (search bar at the top right).
If you've already done this and come up short, you can try [asking us a question in Discord](https://discord-gateway.optimism.io), [checking the Help Center](https://help.optimism.io/hc/en-us), or [making an issue on GitHub](https://github.com/ethereum-optimism/community-hub/issues).
explainer.md
\ No newline at end of file
differences.md
\ No newline at end of file
---
title: Bedrock Differences
lang: en-US
---
Bedrock is the next major release of the Optimism network, planned for the first quarter of 2023 (subject to approval by Optimism governance).
It will further reduce [the differences between Optimism and L1 Ethereum](https://op-geth.optimism.io/).
Here are the major changes:
## Guide by persona
If you want to jump directly to the parts relevant to your job role, here are the parts we think will be most useful
<details>
<summary>Wallet developer</summary>
As a wallet developer you are most likely to interact with the JSON RPC, and your users want to know how much their transactions are going to cost.
Timing may also be relevant.
- [EIP-1559](#eip-1559)
- [JSON-RPC](#json-rpc)
- [Block Production](#block-production)
</details>
<details>
<summary>Dapp frontend developer</summary>
As an application developer you are probably interested in the fact Bedrock has a mempool and the changes in transaction fees.
You might also be interested in changes in the RPC interface and block timing.
- [EIP-1559](#eip-1559)
- [Mempool](#mempool)
- [JSON-RPC](#json-rpc)
- [Block Production](#block-production)
</details>
<details>
<summary>Dapp backend (protocol) developer</summary>
As an application developer you are probably interested in the fact Bedrock has a mempool and the changes in transaction fees.
You might also be interested in changes in the RPC interface and block timing.
- [EIP-1559](#eip-1559)
- [Mempool](#mempool)
- [JSON-RPC](#json-rpc)
- [Block Production](#block-production)
</details>
<details>
<summary>Infrastructure provider (or anybody else running a node)</summary>
To run a node you need to understand the executables required to run it.
You might also be interested in the existence of the mempool and the changes in block timing, fess, and the JSON RPC.
- [Mempool](#mempool)
- [Block Production](#block-production)
- [Historical Data](#historical-data)
- [JSON-RPC](#json-rpc)
- [Chain Reorganizations](#chain-reorganizations)
[See here for a more detailed guide](./node-operator-guide.md) on how to run a Bedrock node.
</details>
<details>
<summary>Bridge developer</summary>
As a bridge developer you are likely most interested in deposits into Optimism and withdrawals back into Ethereum L1.
- [Two-Phase Withdrawals](#two-phase-withdrawals)
- [Deposits](#deposits-from-ethereum-to-optimism)
</details>
## The EVM
### Block Production
:::warning Block Time Subject to Change
Currently blocks are produced every two seconds.
However, that value may change in the future.
:::
Unlike the legacy network which mines a block for every incoming transaction, the Bedrock network will produce new blocks every two seconds. This introduces the following changes to the EVM:
- `TIMESTAMP` will return the timestamp of the block. It will update every two seconds.
- `BLOCKNUMBER` will return an actual block number. It will update every two seconds. The one-to-one mapping between blocks and transactions will no longer apply.
The Bedrock upgrade also introduces the concept of _system transactions_. System transactions are created by the `op-node`, and are used to execute deposits and update the L2's view of L1. They have the following attributes:
- Every block will contain at least one system transaction called the [L1 attributes deposited transaction](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#l1-attributes-deposited-transaction). It will always be the first transaction in the block.
- Some blocks will contain one or more [user-deposited transactions](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#user-deposited-transactions).
- All system transactions have an [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718)-compatible transaction type of `0x7E`.
- All system transactions are unsigned, and set their `v`, `r`, and `s` fields to `null`.
For more information about these transactions, see the [deposited transactions specification](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md) on GitHub.
:::warning Known Issue
Some Ethereum client libraries, such as Web3j, cannot parse the `null` signature fields described above. To work around this issue, you will need to manually filter out the system transactions before passing them to the library.
:::
### EIP-1559
As part of the Bedrock upgrade, we have upgraded the network to support [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559). In EIP-1559 the cost of a unit of gas is composed of two components:
- **Base fee**: This fee is the same for all transactions in a block. It varies between blocks based on the difference between the actual size of the blocks (which depends on the demand for block space) and the target block size. When the block uses more gas than the target block size the base fee goes up to discourage demand. When the block uses less gas than the target block size the base fee goes down to encourage demand.
- **Priority fee**: This fee is specified in the transaction itself and varies between transactions. Block proposers are expected to select the transactions that offer them the highest priority fees first.
There are some differences between Ethereum and Optimism in this regard:
- ETH is not burned. Burning ETH on L2 would only lock it in the bridge forever.
- The EIP 1559 parameters have different values. Once those values are finalized they will be posted here.
The L1 security fee, which is the majority of the transaction cost, uses the same mechanism as before the upgrade. However, the transactions to L1 are going to be submitted on a [non-contract address](#the-transaction-trail). Between that and improved compression, the L1 security fee should be reduced by about 20%.
From an application development perspective, EIP-1559 introduces the following changes:
- The `BASEFEE` opcode is now supported. The `BASEFEE` opcodes returns the base fee of the current block.
- The `eth_maxPriorityFeePerGas` and `eth_feeHistory` RPC methods are now supported. `eth_maxPriorityFeePerGas` returns a fee per gas that is an estimate of how much you can pay as a priority fee, or 'tip', to get a transaction included in the current block. `eth_feeHistory` returns a collection of historical gas information from which you can decide what to submit as your `maxFeePerGas` and/or `maxPriorityFeePerGas`.
### Removed Opcodes
One of the design goals of the Bedrock upgrade is to be maximally EVM equivalent. To reduce differences between Optimism's EVM and vanilla Ethereum's EVM, we have removed the `L1BLOCKNUMBER` opcode.
### ETH Balances
The previous version of the system used an ERC20 contract called `OVM_ETH` to represent ETH balances. These balances will be migrated into the Bedrock network's state as part of the migration. To preserve backwards compatibility, the methods on the `OVM_ETH` contract will continue to work. Note, however, that the `totalSupply()` method will return zero.
## Chain Reorganizations
Unlike the legacy network, Bedrock nodes _always_ derive blocks from L1. This means that if L1 experiences a reorg, L2 will reorganize itself to match the state of L1. Blocks that have not been submitted to L1 yet are gossipped via a peer-to-peer network, and can be reorganized if the data on L1 does not match the data gossiped over P2P.
Bedrock adopts the same vocabulary as the Beacon Chain to describe block finality. Blocks can be in one of the following states:
- `unsafe`, meaning that the block has been received via gossip but has not yet been submitted to L1. Unsafe blocks can be reorged if L1 reorgs, or the sequencer reorgs.
- `safe`, meaning that the block has been submitted to L1. Unsafe blocks can be reorged if L1 reorgs.
- `finalized`, meaning that the block has reached sufficient depth to be considered final. Finalized blocks cannot be reorged.
The current `safe`, `unsafe`, and `finalized` blocks can be queried via [JSON-RPC](#json-rpc).
## Historical Data
Bedrock nodes can serve pre-Bedrock block bodies, transactions, and receipts out of the box. However, you will need to run a Legacy Geth instance to serve historical execution traces. See the [Node Operator Guide](./node-operator-guide.md) for more information about how to do this.
Note that the following legacy fields have been removed from pre-Bedrock JSON-RPC transaction responses:
- `queueOrigin`
- `l1TxOrigin`
- `l1BlockNumber`
- `l1Timestamp`
- `index`
- `queueIndex`
- `rawTransaction`
If your application needs these fields, query Legacy Geth instead of `op-geth`.
## JSON-RPC
Bedrock supports all of the standard JSON-RPC methods exposed by go-ethereum. In order to reduce differences between Bedrock and vanilla Ethereum, some legacy methods have been removed. These methods and their replacements are:
- `eth_getBlockRange`: Use `eth_getBlockByNumber` in a batch request instead.
- `rollup_getInfo`: None of the information returned by this method exists on Bedrock, so there is no replacement for this method.
- `rollup_gasPrices`: Use `eth_gasPrice` instead. The L1 and L2 fees are combined, and returned as a single value.
To query `op-node`'s sync status, use the `optimism_syncStatus` RPC method. This method will return the current `safe`, `unsafe`, and `finalized` as seen by the `op-node`.
## Mempool
Since the legacy network creates a block for every transaction, it processes new transactions in a first-come-first-serve manner. Bedrock, however, creates blocks on a fixed interval and therefore needs a mempool to store pending transactions until they are included in a block. To minimize MEV, Bedrock's mempool is private. To submit transactions, you will need to configure `op-geth` to forward transactions to the sequencer. This may change in the future.
The sequencer processes transactions in the mempool in order of their base and priority fees.
## Two-Phase Withdrawals
On the legacy network, funds are withdrawn as follows:
1. Users send a withdrawal message.
2. Users wait seven days for the withdrawal to be finalized.
3. Users post a withdrawal proof on L1, and claim their funds.
However, this introduces a security risk. If the withdrawal proof can be successfully forged, an attacker could withdraw funds from the bridge that they are not authorized to. This kind of attack has been exploited before - a fraudulent proof led to the loss of [$100M on Binance Smart Chain](https://www.nansen.ai/research/bnb-chains-cross-chain-bridge-exploit-explained).
To eliminate this risk, Bedrock requires users to post their withdrawal proofs upfront. Users must wait for a valid output root to be proposed so that the withdrawal proof can be validated on-chain. Once the seven-day waiting period has expired, users can then finalize the withdrawal and claim their funds. The updated withdrawal flow is as follows:
1. Users send a withdrawal message.
2. Users wait for a state output to be proposed on-chain. This can take up to an hour.
3. Users call `proveWithdrawalTransaction()` on the `OptimismPortal` to post and validate their proof.
4. Users wait for the withdrawal to be finalized. This takes seven days on mainnet.
5. Users call `finalizeWithdrawal()` on the `OptimismPortal` to claim their funds.
Visualized, this flow looks like this:
<div style="text-align: center">
![](../../../assets/docs/bedrock/two-phase-withdrawals.png)
</div>
By posting the proof upfront, it gives on-chain monitoring tools enough time to detect a fraudulent withdrawal proof and attempt corrective action.
Regular users can do this monitoring too. For example, an exechange could halt withdrawals in the event of a fraudulent proof.
Since this change fundamentally changes the way withdrawals are handled, it is **not** backwards-compatible with the old network. If you are performing withdrawals outside our standard bridge interface, you will need to update your software. The easiest way to to do this is to use our [TypeScript SDK](https://github.com/ethereum-optimism/optimism/tree/develop/packages/sdk), which includes two-phase withdrawals support out of the box.
For more information on two-phase withdrawals, see the withdrawals specification on [GitHub](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md).
## Deposit Replayability
:::warning Breaking Change
The changes described in this section are **not** backwards-compatible with the old network. Please read this section carefully.
:::
On the legacy network, deposits are replayable. This is no longer the case in Bedrock. Additionally, as part of the migration, all deposits on the legacy network that have not been replayed will no longer be replayable. **This means that if you have not replayed your deposit, you must do so before the migration.** This affects fewer than 100 transactions of the nearly 40 million on mainnet.
## Contracts
### L1 contracts
#### L2OutputOracle
[The `L2OutputOracle` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/L2OutputOracle.sol) contains the state root of the Optimism blockchain.
Once fault proofs are activated, it will be the one that receives the result of the fault proof process.
This is the contract that replaces the old State Commitment Chain.
#### OptimismPortal
[The `OptimismPortal` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol) provides [the low-level API for communications between layers](#deposits-from-ethereum-to-optimism). Unless you are trying to send L2 transactions via L1 to bypass the sequencer, we strongly recommend sending messages between L1 and L2 via the L1CrossDomainMessenger and L2CrossDomainMessenger.
#### Existing interface
These contracts provide the same interface as existed pre-Bedrock so dapps don’t have to be modified to run on Bedrock.
- [L1CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/L1CrossDomainMessenger.sol):
The `L1CrossDomainMessenger` contract is used for sending messages between Ethereum and Optimism. Those messages may or may not have assets attached to them.
- [L1StandardBridge](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol):
The `L1StandardBridge` contract uses `L1CrossDomainMessenger` to transfer ETH and ERC-20 tokens between Ethereum and Optimism.
### L2 contracts (predeploys)
#### L1Block
[The `L1Block` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1Block.sol) sits at address `0x4200000000000000000000000000000000000015`.
You can use [the getter functions](https://docs.soliditylang.org/en/v0.8.12/contracts.html#getter-functions) to get these parameters:
- `number`: The latest L1 block number known to L2 (the `L1BlockNumber` contract is still supported to avoid breaking existing applications)
- `timestamp`: The timestamp of the latest L1 block
- `basefee`: The base fee of the latest L1 block
- `hash`: The hash of the latest L1 block
- `sequenceNumber`: The number of the L2 block within the epoch (the epoch changes when there is a new L1 block)
Currently the L1 information is delayed by ten block confirmations (~2.5 minutes) to minimize the impact of reorgs.
This value may be reduced in the future.
#### SequencerFeeVault
[The `SequencerFeeVault` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/SequencerFeeVault.sol) handles funding the sequencer on L1 using the ETH base fee on L2.
The fees are calculated using [EIP 1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md), the same mechanism that Ethereum uses (but with different parameter values).
#### L2ToL1MessagePasser
[The `L2ToL1MessagePasser` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L2ToL1MessagePasser.sol) is used internally by `L2CrossDomainMessenger` to initiate withdrawals.
#### Existing interface
These contracts provide the same interface as existed pre-Bedrock so dapps don’t have to be modified to run on Bedrock.
- [L1BlockNumber](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1BlockNumber.sol):
The `L1BlockNumber` contract provides the number of the latest L1 block.
In Bedrock it is simply a proxy to [`L1Block`](#l1block).
- [L2CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L2CrossDomainMessenger.sol):
The `L2CrossDomainMessenger` contract is used to send messages from Optimism to Ethereum.
- [L2StandardBridge](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L2StandardBridge.sol):
The `L2StandardBridge` contract is used to "attach" assets (ETH and ERC-20 tokens) to messages that are then sent by `L2CrossDomainMessenger`.
- [WETH9](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/WETH9.sol):
[The WETH9 contract](https://weth.io/) is an ERC-20 token that wraps around ETH to provide extra functionality, such as approvals.
#### Historical contracts
These are contracts that are no longer relevant, but are kept as part of the state in case there is a call in any dapp that uses them.
- [DeployerWhitelist](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/DeployerWhitelist.sol):
The `DeployerWhitelist` contract used to manage the whitelist before [Optimism moved out of beta](https://twitter.com/optimismFND/status/1471571415774023682).
- [OVM_ETH](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/OVM_ETH.sol):
The `OVM_ETH` contract used to manage users ETH balances prior to Bedrock.
## Communication between layers
In Optimism terminology "deposit" refers to any message going from the Ethereum blockchain to Optimism, whether it has any assets attached or not.
Similarly, "withdrawal" refers to any message going from Optimism to Ethereum.
[See here for the messenger specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/messengers.md) and [here for the bridge specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/bridges.md).
<!--
### Gas cost changes
The gas costs for communication between layers are going to change, they will probably get lower.
More information will be posted here once we have more exact information after we profile a test network.
-->
<!-- TODO get the figures and put them here -->
### Deposits (from Ethereum to Optimism)
To create a deposit we recommend that you use the pre-Bedrock contracts [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol) and [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/L1CrossDomainMessenger.sol).
[`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/OptimismPortal.sol) also has low-level deposit functionality.
With the OptimismPortal’s `depositTransaction` function you can do from L1 anything you can do by contacting L2 directly: send transactions, send payments, create contracts, etc.
This provides an uncensorable alternative in case the sequencer is down.
Even though the sequencer is down, verifiers (nodes that synchronize the Optimism state from L1) are still going to receive such transactions and modify the state accordingly.
When the sequencer is back up it has to process the transactions in the same order to have a valid state.
We recommend adding a 50% buffer to whatever is returned by `estimateGas` to ensure that your deposit will not run out of gas (Note: the Optimism SDK already does this under the hood). For example, if you wish to send a deposit of ETH, you might call `eth_estimateGas` and see that the deposit is expected to consume `100,000 gas`. You should then multiply that estimated about by `1.5` and then send your transaction with a `gasLimit` of at least `150,000 gas`.
:::tip
In order to prevent the Optimism network from being DOSed via forced L1 to L2 transactions that bypass the Sequencer, a fee adjustment schedule to all L1→L2 transactions that closely mimics EIP1559 is included with Bedrock. Like in the current network, deposit fees are paid by burning some amount of L1 gas proportional to your deposit's L2 gas limit. Unfortunately, this means that you may have cases where you estimate how much gas an L1→L2 deposit will cost, and deposit fees increase by the time your transaction gets included in a block and executed, causing your deposit to run out of gas and revert. This is why we recommend adding a 50% buffer to your `gasLimit` to ensure your deposit will not run out of gas.
:::
Deposits that come from contracts still use [address aliasing](../build/differences.md#address-aliasing).
Deposits will also be faster, probably about 2.5 minutes or less, rather than the 10-20 minutes they take now.
<!--
Deposits are implemented using [a new transaction type](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#the-deposited-transaction-type), 0x7E.
-->
[You can read the full deposit specifications here](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#the-deposited-transaction-type).
[You can read the full withdrawal specifications here](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md)
## Behind the scenes
This section discusses some of the changes in Optimism internals.
### The transaction trail
There is no longer a CTC (cannonical transaction chain) contract. Instead, L2 blocks are saved to the Ethereum blockchain using a non-contract address to minimize the L1 gas expenses. Please see the [Public Testnets](./public-testnets.md) page for more information on where to find batch submission addresses.
[The block and transaction format is also different](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md#l2-chain-derivation).
---
title: Bedrock Explainer
lang: en-US
meta:
- name: og:image
content: https://dev.optimism.io/content/images/size/w2000/2022/12/bedrock-BLUE.jpg
---
![Bedrock](https://dev.optimism.io/content/images/size/w2000/2022/12/bedrock-BLUE.jpg)
Bedrock is the name of the first ever official release of the OP Stack, which is a set of free and open-source modular components that work together to power Optimism.
- To understand what is in the Bedrock release, keep reading.
- To develop on Optimism Mainnet, which will upgrade its infrastructure to the Bedrock release, read the docs.
- To contribute to the OP Stack, see the contribution guidelines on the ethereum-optimism monorepo.
## Summary of Improvements
Bedrock improves on its predecessor by reducing transaction fees using optimized batch [compression](#optimized-data-compression) and Ethereum as a data availability layer; shortening delays of including L1 transactions in rollups by handling L1 re-orgs more gracefully; enabling modular proof systems through code re-use; and improving node performance by removing technical debt.
### Lower fees
In addition, Bedrock implements an optimized data [compression](#optimized-data-compression) strategy to minimize data costs. We are currently benchmarking the impact of this change, but we expect it to reduce fees significantly.
Bedrock also removes all gas costs associated with EVM execution when submitting data to L1. This reduces fees by an additional 10% over the previous version of the protocol.
### Shorter deposit times
Bedrock introduces support for L1 re-orgs in the node software, which significantly reduces the amount of time users need to wait for deposits. Earlier versions of the protocol could take up to 10 minutes to confirm deposits. With Bedrock, we expect deposits to confirm within 3 minutes.
### Improved proof modularity
Bedrock abstracts the proof system from the OP Stack so that a rollup may use either a fault proof or validity proof (e.g., a zk-SNARK) to prove correct execution of inputs on the rollup. This abstraction enables systems like [Cannon](https://github.com/ethereum-optimism/cannon) to be used to prove faults in the system.
### Improved node performance
The node software performance has been significantly improved by enabling execution of several transactions in a single rollup "block" as opposed to the prior "one transaction per block" model in the previous version. This allows the cost of merkle trie updates to be amortized across multiple transactions. At current transaction volume, this reduces state growth by approximately 15GB/year.
Node performance is further improved by removing technical debt from the previous version of the protocol. This includes removing the need for a separate "data transport layer" node to index L1, and updating the node software to efficiently query for transaction data from L1.
### Improved Ethereum equivalence
Bedrock was designed from the ground up to be as close to Ethereum as possible. Multiple deviations from Ethereum in the previous version of the protocol have been removed, including:
1. The one-transaction-per-block model.
2. Custom opcodes to get L1 block information.
3. Separate L1/L2 fee fields in the JSON-RPC API.
4. A custom ERC20 representation of ETH balances.
Bedrock also adds support for EIP-1559, chain re-orgs, and other Ethereum features present on L1.
## Design Principles
Bedrock was built to be modular & upgradeable, to reuse existing code from Ethereum, and to be as close to 100% Ethereum-equivalent as possible.
### Modularity
Bedrock makes it easy to swap out different components in the OP Stack and add new capabilities by using well-defined interfaces and versioning schemes. This allows for a flexible architecture that can adapt to future developments in the Ethereum ecosystem.
Examples:
- Separation of [rollup node](#rollup-node) and execution client
- Modular fault proof design
### Code re-use
Bedrock uses existing Ethereum architecture and infrastructure as much as possible. This approach enables the OP Stack to inherit security and "lindy" benefits from the battle-tested codebases used in production on Ethereum Mainnet. You'll find examples of this throughout the design including:
Examples:
- [Minimally modified execution clients](https://op-geth.optimism.io/)
- EVM contracts instead of precompiled client code
### Ethereum equivalence
Bedrock is designed to have maximum compatibility with the existing Ethereum developer experience. A few exceptions exist due to fundamental differences between an L1 and a rollup: an altered fee model, faster block time (2s vs 12s), and a special transaction type for including L1 [deposit](#deposits) transactions.
Examples:
- Fault proof designed to prove faults of minimally modified Ethereum [execution client](#execution-client)
- Code re-use of Ethereum [execution client](#execution-client) for use by nodes in the L2 network and sequencers
## Protocol
Rollups are derived from a data availability source (generally an L1 blockchain like Ethereum). In their most common configuration, rollup protocols derive a **"canonical L2 chain"** from two primary sources of information:
1. Transaction data posted by Sequencers to the L1 and;
2. [Deposit](#deposits) transactions posted by accounts and contracts to a bridge contract on L1.
The following are the fundamental components of the protocol:
* Deposits are _writes_ to the canonical L2 chain by directly interacting with smart contracts on the L1.
* Withdrawals are _writes_ to the canonical L2 chain that implicitly trigger interactions with contracts and accounts on the L1.
* Batches are _writes_ of data corresponding to batches on the rollup.
* Block derivation is how _reads_ of data on the L1 are interpreted to understand the canonical L2 chain.
* Proof systems define _finality_ of posted output roots on the L1 such that they may be _executed_ upon (e.g., to execute withdrawals).
### Deposits
A **deposit** is a transaction on L1 that is to be included in the rollup. [Deposits](#deposits) are _guaranteed_ by definition to be included in the [canonical L2 chain](#protocol) as a means of preventing censorship or control of the L2.
#### Arbitrary message passing from L1
A **deposited transaction** is the transaction on the rollup that is made as a part of a [deposit](#deposits). With Bedrock, [deposits](#deposits) are fully generalized Ethereum transactions. For example, an account or contract on Ethereum can “deposit” a contract creation.
Bedrock defines a **deposit contract** that is available on the L1: it is a smart contract that L1 accounts and contracts can interact with to write to the L2. [Deposited transactions](#arbitrary-message-passing-from-l1) on the L2 are derived from the values in the event(s) emitted by this [deposit](#deposits) contract, which include expected parameters such as from, to, and data.
For full details, see the [deposit contract](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#deposit-contract) section of the protocol specifications.
#### Purchasing guaranteed L2 gas on L1
Bedrock also specifies a gas burn mechanism and a fee market for [deposits](#deposits). The gas that [deposited transactions](#arbitrary-message-passing-from-l1) spend on an L2 is bought on L1 via a gas burn. This gas is purchased on a fee market and there is a hard cap on the amount of gas provided to all [deposits](#deposits) in a single L1 block. This mechanism is used to prevent denial of service attacks that could occur by writing transactions to L2 from L1 that are extremely gas-intensive on L2, but cheap on L1.
The gas provided to [deposited transactions](#arbitrary-message-passing-from-l1) is sometimes called "guaranteed gas." Guaranteed gas is unique in that it is paid for by burning gas on L1 and is therefore not refundable.The total amount of L1 gas that must be burned per unit of guaranteed L2 gas requested depends on the price of L2 gas reported by a EIP-1559 style fee mechanism. Furthermore, users receive a dynamic gas stipend based on the amount of L1 gas spent to compute updates to the fee mechanism.
For a deeper explanation, read the [deposits section](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#deposits) of the protocol specifications.
### Withdrawals
**Withdrawals** are cross-domain transactions that are initiated on L2 and finalized by a transaction executed on L1. Notably, withdrawals may be used by an L2 account to call an L1 contract, or to transfer ETH from an L2 account to an L1 account.
Withdrawals are initiated on L2 via a call to the **Message Passer** predeploy contract, which records the important properties of the message in its storage. Withdrawals are finalized on L1 via a call to the [OptimismPortal](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md#the-optimism-portal-contract) contract, which proves the inclusion of this withdrawal message. In this way, withdrawals are different from [deposits](#deposits). Instead of relying on [block derivation](#block-derivation), withdrawal transactions must use smart contracts on L1 for finalization.
#### Two-step withdrawals
Withdrawal proof validation bugs have been the root cause of many of the biggest bridge hacks of the last few years. The Bedrock release introduces an additional step in the withdrawals’ process of prior versions meant to provide an extra layer of defense against these types of bugs. In the two-step withdrawal process, a Merkle proof corresponding to the withdrawal must be submitted 7 days before the withdrawal can be finalized.. This new safety mechanism gives monitoring tools a full 7 days to find and detect invalid withdrawal proofs . If the [withdrawal](#withdrawals) proof is found to be invalid, a contract fix can be deployed before funds are lost. This dramatically reduces the risk of a bridge compromise.
For full details, see the [withdrawals](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md) section of the protocol specification.
### Batches
In Bedrock, a wire format is defined for messaging between the L1 and L2 (i.e., for L2 deriving blocks from L1 and for L2 to write transactions to the L1). This wire format is designed to minimize costs and software complexity for writing to the L1.
#### Optimized data compression
To optimize data compression, lists of L2 transactions called **sequencer batches** are organized into groups of objects called **channels**, each of which have a maximum size that is defined in a [configurable parameter](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#channel-format) that will initially be set to ~9.5Mb. These [channels](#optimized-data-compression) are expected to be compressed using a compression function and submitted to the L1.
#### Parallelized batch submission
To parallelize messages from the sequencers that are submitting [compressed](#optimized-data-compression) [channel](#optimized-data-compression) data to the L1, [channels](#optimized-data-compression) are further broken down into **channel frames**, which are chunks of [compressed](#optimized-data-compression) [channel](#optimized-data-compression) data that can fit inside of a single L1 transaction. Given [channel frames](#parallelized-batch-submission) are mutually independent and the ordering is known, the Ethereum transactions sent by the sequencer to the L1 can be sent in parallel which minimizes sequencer software complexity and allows for filling up all available space for data on the L1.
#### Minimized usage of Ethereum gas
Bedrock removes all execution gas used by the L1 system from submitting [channel](#optimized-data-compression) data to the L1 in transactions called **batcher transactions**. All validation logic that was previously happening on smart contracts on the L1 is moved into the [block derivation](#block-derivation) logic. Instead, [batcher transactions](#minimized-usage-of-ethereum-gas) are sent to a single EOA on Ethereum referred to as the **batch inbox address**.
Batches are still subject to validity checks (i.e. they have to be encoded correctly), and so are individual transactions within the batch (e.g. signatures have to be valid). Invalid [batches](#optimized-data-compression) and invalid individual transactions within an otherwise valid batch are considered to be discarded and irrelevant to the system.
> Note: Ethereum will soon upgrade to include [EIP-4844](https://eip4844.com/), which introduces a separate fee market for writing data and an increased cap of the amount of data the Ethereum protocol is willing to store. This change is expected to further decrease the costs associated with posting data to an L1.
For a deeper explanation, read [the wire format specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#overview).
### Block Derivation
In Bedrock, the protocol is designed to guarantee that the timing of [deposits](#deposits) on the L1 is respected with regards to the [block derivation](#block-derivation) of the [canonical L2 chain](#protocol). Doing so is a _pure function_ of data written to the L1 by sequencers, [deposits](#deposits), and L1 block attributes. To accomplish this, the protocol defines strategies for guaranteeing inclusion of deposits, handling L1 and L2 timestamps, and processing sequencing windows in a pipeline to ensure correct ordering.
#### Guaranteed inclusion of deposits
A goal of the [block derivation](#block-derivation) protocol is to define it such that there must be an L2 block every "L2 block time" number of seconds, and that the timestamp of L2 blocks stays in sync with the timestamps of L1 (i.e., to ensure [deposits](#deposits) are included in a logical temporal order).
In Bedrock, the concept of a **sequencing epoch** is introduced: it is a range of L2 blocks derived from a range of L1 blocks. Each [epoch](#guaranteed-inclusion-of-deposits) is identified by an **epoch number**, which is equal to the block number of the first L1 block in the sequencing window. Epochs can vary in size, subject to some constraints.
The batch derivation pipeline treats the timestamps of the L1 blocks associated with [epoch number](#guaranteed-inclusion-of-deposits) as the anchor point for determining the order of transactions on the L2. The protocol guarantees that the first L2 block of an [epoch](#guaranteed-inclusion-of-deposits) never falls behind the timestamp of the L1 block matching the [epoch](#guaranteed-inclusion-of-deposits). The first blocks of an epoch _must_ contain deposits on L1 in order to guarantee that deposits will be processed.
Note that the target configuration for the block time on L2 in the Bedrock release is 2 seconds.
> **Note** that the target configuration for the block time on L2 in the Bedrock release is 2 seconds.
#### Handling L1 and L2 timestamps
Bedrock attempts to address the problem of reconciling the timestamps on L2 with timestamps on L1 present in [deposited transactions](#arbitrary-message-passing-from-l1). It does this by allowing a short window of time for sequencing to liberally apply timestamps on L2 transactions between [epochs](#guaranteed-inclusion-of-deposits).
A **sequencing window** is a sequence of L1 blocks from which an [epoch](#guaranteed-inclusion-of-deposits) can be derived. A [sequencing window](#handling-l1-and-l2-timestamps) whose first L1 block has the number `N` contains [batcher transactions](#minimized-usage-of-ethereum-gas) for [epoch](#guaranteed-inclusion-of-deposits) `N`.
The [sequencing window](#handling-l1-and-l2-timestamps) contains blocks `[N, N + SWS)` where `SWS` is the **sequencer window size**: a fixed rollup-level configuration parameter. This parameter must be at least 2. Increasing it provides more opportunity for sequencers to order L2 transactions with respect to [deposits](#deposits), and lowering it introduces stricter windows of time for sequencers to submit batcher transactions. It is a tradeoff between creating MEV opportunity and increasing software complexity.
A protocol constant called **max sequencer drift** governs the maximum timestamp a block can have within its epoch. Having this drift allows the sequencer to maintain liveness in case of temporary problems connecting to L1. Each L2 block’s timestamp fits within the following range:
```
l1_timestamp <= l2_block.timestamp <= max(l1_timestamp + max_sequencer_drift, l1_timestamp + l2_block_time)
```
#### Block derivation pipeline
The [canonical L2 chain](#protocol) can be processed from scratch by starting with the L2 genesis state, setting the L2 chain inception as the first epoch, and then processing all sequencing windows in order to determine the correct ordering of [sequencer batches](#optimized-data-compression) and [deposits](#deposits) according to the following simplified pipeline:
| **Stage** | **Notes** |
| --- | --- |
| Read from L1 | Epochs are defined by L1 blocks. Contained within an L2 block is data pertaining to [batcher transactions](#minimized-usage-of-ethereum-gas) or [deposits](#deposits) which must be included in the [canonical L2 chain](#protocol) |
| Buffer and decode into [channels](#optimized-data-compression) | The data from L1 blocks contains unordered [channel frames](#parallelized-batch-submission), which must all be collected before reconstructing them into channels. |
| Decompress [channels](#optimized-data-compression) into [batches](#optimized-data-compression) | Since [channels](#optimized-data-compression) are [compressed](#optimized-data-compression) to minimize data fee costs on the L1, they must be decompressed. |
| Queue [batches](#optimized-data-compression) into sequential order | With the latest information from L1, [batches](#optimized-data-compression) can be validated and processed sequentially. There are some nuances to what the correct ordering is in relation to [epochs](#guaranteed-inclusion-of-deposits) and timestamps from L2, see the full specification [here](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#batch-queue). |
| Interpret as L2 blocks | At this point, the correct ordering of [batches](#optimized-data-compression) can be determined.<br><br>Following this, the [execution client](#execution-client) can interpret them into L2 blocks. For implementation details pertaining to [execution clients](#execution-client), see the [engine queue](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#engine-queue) section of the protocol specifications. |
### Fault Proofs
After a sequencer processes one or more L2 blocks, the outputs computed from executing transactions in those blocks will need to be written with L1 for trustless execution of L2-to-L1 messaging, such as [withdrawals](#withdrawals).
In Bedrock, outputs are hashed in a tree-structured form which minimizes the cost of proving any piece of data captured by the outputs. Proposers periodically submit **output roots** that are Merkle roots of the entire [canonical L2 chain](#protocol) to the L1.
Future upgrades of the OP Stack should include a specification for a variation of a fault proof with bonding included to create incentives for proposers to propose correct output roots.
For full details, read the [L2 Output Root Proposals section](https://github.com/ethereum-optimism/optimism/blob/develop/specs/proposals.md#l2-output-root-proposals-specification) of the protocol specifications.
## Implementation
With Bedrock, the OP Stack leans heavily into the technical separation of concerns specified by Ethereum by mirroring the separation between the Ethereum execution layer and consensus layer. Bedrock introduces separation of execution client and rollup node in this same way.
### Execution Client
An **execution client** is the system that sequencers and other kinds of node operators run to determine the state of the [canonical L2 chain](#protocol). It also performs other functions such as processing inbound transactions and communicating them peer-to-peer, and handling the state of the system to process queries against it.
With Bedrock, the OP Stack is designed to reuse [Ethereum’s own execution client specifications](https://github.com/ethereum/execution-specs) and its many implementations. In this release, Bedrock has demonstrated an extremely limited modification of go-ethereum, the most popular Ethereum client written in Go, to a [diff of less than 2000 lines of code](https://op-geth.optimism.io/).
There are two fundamental reasons for having any diff at all: handling deposited transactions, and charging transaction fees.
#### Handling deposited transactions
To represent [deposited transactions](#arbitrary-message-passing-from-l1) in the rollup, there is an additional transaction type introduced. The [execution client](#execution-client) implements this [new transaction type](https://github.com/ethereum-optimism/optimism/blob/develop/specs/%5Bdeposits%5D(#deposits).md%23the-deposited-transaction-type) according to the [EIP-2718 typed transactions](https://eips.ethereum.org/EIPS/eip-2718) standard.
#### Charging transaction fees
Rollups also fundamentally have two kinds of fees associated with transactions:
**Sequencer fees**
The cost of operating a sequencer is computed using the same gas table as Ethereum and with the same [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) algorithm. These fees go to the protocol for operating sequencers and fluctuate based on the congestion of the network.
**Data availability fees**
Data availability costs are associated with writing [batcher transactions](#minimized-usage-of-ethereum-gas) to the L1. These fees are intended to cover the cost that sequencers need to pay to submit [batcher transactions](#minimized-usage-of-ethereum-gas) to the L1.
In Bedrock, the data availability portion of the fee is determined based on information in a system contract on the rollup called a [GasPriceOracle](https://github.com/ethereum-optimism/optimism/blob/develop/specs/predeploys.md#gaspriceoracle). This contract is updated during [block derivation](#block-derivation) from the gas pricing information retrieved from the L1 block attributes that get inserted at the beginning of every [epoch](#guaranteed-inclusion-of-deposits).
Bedrock specifies that both of these fees are added up into a single `gasPrice` field when using the JSON-RPC.
### Rollup Node
Unlike Ethereum, Bedrock does not have proof-of-stake consensus. Instead, the consensus of the [canonical L2 chain](#protocol) is defined by [block derivation](#block-derivation). An [execution client](#execution-client) of the OP Stack communicates to a new component that implements [block derivation](#block-derivation) called a **rollup node**. This node communicates to the [execution client](#execution-client) using the exact same [Engine API](https://github.com/ethereum/execution-apis/tree/main/src/engine) that Ethereum uses.
The [rollup node](#rollup-node) is a stateless component responsible for deriving the state of the system by reading data and [deposits](#deposits) on the L1. In Bedrock, a [rollup node](#rollup-node) can either be used to sequence incoming transactions from users or other [rollup nodes](#rollup-node) or to verify confirmed transactions posted on the L1 by singularly relying on the L1.
The multiple uses of a rollup node are outlined below.
#### Verifying the canonical L2 chain
The simplest mode of running a [rollup node](#rollup-node) is to only follow the [canonical L2 chain](#protocol). In this mode, the [rollup node](#rollup-node) has no peers and is strictly used to read data from the L1 and to interpret it according to [block derivation](#block-derivation) protocol rules.
One purpose of this kind of node is to verify that any output roots shared by other nodes or posted on the L1 are correct according to protocol definition. Additionally, proposers intending to submit output roots to the L1 themselves can generate the output roots they need using the [optimism_outputAtBlock](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md#l2-output-rpc-method) of the node which returns a 32-byte hash corresponding to the L2 output root.
For this purpose, nodes should only need to follow the finalized head. The term ["finalized"](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/#finality) refers to the Ethereum proof-of-stake consensus (i.e. canonical and practically irreversible) — the finalized L2 head is the head of the [canonical L2 chain](#protocol) that is derived only from finalized L1 blocks.
#### Participating in the L2 network
The most common way to use a [rollup node](#rollup-node) is to participate in a network of other [rollup nodes](#rollup-node) tracking the progression and state of an L2. In this mode, a [rollup node](#rollup-node) is both reading the data and [deposits](#deposits) it observes from the L1 and interpreting it as blocks and accepting inbound transactions from users and peers in a network of other [rollup nodes](#rollup-node).
Nodes participating in the network may make use of the safe and unsafe heads of the L2 they're syncing.
- The **safe L2 head** represents the rollup that can be constructed where every block up to and including the head can be fully derived from the reference L1 chain, before L1 has necessarily finalized (i.e., a re-org may occur on L1 still).
- The **unsafe L2 head** includes [unsafe blocks](https://github.com/ethereum-optimism/optimism/blob/develop/specs/glossary.md#unsafe-l2-block) that have not yet been derived from L1. These blocks either come from operating the [rollup node](#rollup-node) as a sequencer or from [unsafe sync](https://github.com/ethereum-optimism/optimism/blob/develop/specs/glossary.md#unsafe-sync) with the sequencer. This is also known as the "latest" head. The safe L2 head is always chosen over the unsafe L2 head in cases of disagreements. When disagreements occur, the unsafe portion of the chain will reorg.
For most purposes, nodes in the L2 network will refer to the unsafe L2 head for end-user applications.
#### Sequencing transactions
The third way to use a [rollup node](#rollup-node) is to sequence transactions. In this mode, a [rollup node](#rollup-node) will _create_ new blocks on top of the unsafe L2 head. Currently, there is only one sequencer per OP Stack network.
The sequencer is also responsible for posting batches to L1 for other nodes in the network to sync from.
### Batcher
The role of a sequencer is to produce [batches](#batches). To do this, a sequencer can run [rollup nodes](#rollup-node) and have separate processes which perform [batching](#batches) by reading from a trusted [rollup node](#rollup-node) they run. This warrants an additional component of the OP Stack called a [batcher](https://github.com/ethereum-optimism/optimism/blob/develop/specs/glossary.md#batcher) that reads transaction data from a [rollup node](#rollup-node) and interprets it into [batcher transactions](#minimized-usage-of-ethereum-gas) to be written to the L1. The batcher component is responsible for reading the unsafe L2 head of a [rollup node](#rollup-node) run by a sequencer, creating batcher transactions, and writing them to the L1.
### Standard Bridge Contracts
Bedrock also includes a pair of bridge contracts used for the most common kinds of [deposits](#deposits) called the [standard bridges](https://github.com/ethereum-optimism/optimism/blob/develop/specs/bridges.md#standard-bridges). These contracts wrap the [deposit](#deposits) and [withdrawal](#withdrawals) contracts to provide simple interfaces for [depositing](#deposits) and [withdrawing](#withdrawals) ETH and ERC-20 tokens.
These bridges are designed to involve a native token on one side of the bridge, and a wrapped token on the other side that can manage minting and burning. Bridging a native token involves locking the native token in a contract and then minting an equivalent amount of mintable token on the other side of the bridge.
For full details, see the [standard bridge](https://github.com/ethereum-optimism/optimism/blob/develop/specs/bridges.md#standard-bridges) section of the protocol specifications.
### Cannon
Although fault proof construction and verification is implemented in the [Cannon](https://github.com/ethereum-optimism/cannon) project, the fault proof game specification and integration of an output root challenger into the rollup node are part of later specification milestones.
## Further Reading
### Protocol Specification
The protocol specification defines the technical details of the OP Stack. It is the most up-to-date source of truth for the inner workings of the protocol. The protocol specification is located in the ethereum-optimism [monorepo](https://github.com/ethereum-optimism/optimism/blob/develop/specs/README.md).
### Bedrock Differences
For a deep dive into the differences between Bedrock and previous versions of the protocol, see the [How is Bedrock Different?](./how-is-bedrock-different.md) page.
---
title: Metrics
lang: en-US
---
The Bedrock `op-node` exposes a variety of metrics to help observe the health of the system and debug issues. Metrics are formatted for use with Prometheus, and exposed via a metrics endpoint. The default metrics endpoint is `http://localhost:7300/metrics`.
To enable metrics, pass the `--metrics.enabled` flag to the `op-node`. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively.
## Important Metrics
To monitor the health of your node, you should monitor the following metrics:
- `op_node_default_refs_number`: This metric represents the `op-node`'s current L1/L2 reference block number for different sync types. If it stops increasing, it means that the node is not syncing. If it goes backwards, it means your node is reorging.
- `op_node_default_peer_count`: This metric represents how many peers the `op-node` is connected to. Without peers, the `op-node` cannot sync unsafe blocks and your node will lag behind the sequencer as it will fall back to syncing purely from L1.
- `op_node_default_rpc_client_request_duration_seconds`: This metrics measures the latency of RPC requests initiated by the `op-node`. This metric is important when debugging sync performance, as it will reveal which specific RPC calls are slowing down sync. This metric exposes one timeseries per RPC method. The most important RPC methods to monitor are:
- `engine_forkChoiceUpdatedV1`, `engine_getPayloadV1`, and `engine_newPayloadV1`: These methods are used to execute blocks on `op-geth`. If these methods are slow, it means that sync time is bottlenecked by either `op-geth` itself or your connection to it.
- `eth_getBlockByHash`, `eth_getTransactionReceipt`, and `eth_getBlockByNumber`: These methods are used by the `op-node` to fetch transaction data from L1. If these methods are slow, it means that sync time is bottlenecked by your L1 RPC.
## Available Metrics
A complete list of available metrics is below:
| METRIC | DESCRIPTION | LABELS | TYPE |
|-----------------------------------------------------|--------------------------------------------------------------------------------------------------|--------------|-----------|
| op_node_default_info | Pseudo-metric tracking version and config info | version | gauge |
| op_node_default_up | 1 if the op node has finished starting up | | gauge |
| op_node_default_rpc_server_requests_total | Total requests to the RPC server | method | counter |
| op_node_default_rpc_server_request_duration_seconds | Histogram of RPC server request durations | method | histogram |
| op_node_default_rpc_client_requests_total | Total RPC requests initiated by the opnode's RPC client | method | counter |
| op_node_default_rpc_client_request_duration_seconds | Histogram of RPC client request durations | method | histogram |
| op_node_default_rpc_client_responses_total | Total RPC request responses received by the opnode's RPC client | method,error | counter |
| op_node_default_l1_source_cache_size | L1 Source cache cache size | type | gauge |
| op_node_default_l1_source_cache_get | L1 Source cache lookups, hitting or not | type,hit | counter |
| op_node_default_l1_source_cache_add | L1 Source cache additions, evicting previous values or not | type,evicted | counter |
| op_node_default_l2_source_cache_size | L2 Source cache cache size | type | gauge |
| op_node_default_l2_source_cache_get | L2 Source cache lookups, hitting or not | type,hit | counter |
| op_node_default_l2_source_cache_add | L2 Source cache additions, evicting previous values or not | type,evicted | counter |
| op_node_default_derivation_idle | 1 if the derivation pipeline is idle | | gauge |
| op_node_default_pipeline_resets_total | Count of derivation pipeline resets events | | counter |
| op_node_default_last_pipeline_resets_unix | Timestamp of last derivation pipeline resets event | | gauge |
| op_node_default_unsafe_payloads_total | Count of unsafe payloads events | | counter |
| op_node_default_last_unsafe_payloads_unix | Timestamp of last unsafe payloads event | | gauge |
| op_node_default_derivation_errors_total | Count of derivation errors events | | counter |
| op_node_default_last_derivation_errors_unix | Timestamp of last derivation errors event | | gauge |
| op_node_default_sequencing_errors_total | Count of sequencing errors events | | counter |
| op_node_default_last_sequencing_errors_unix | Timestamp of last sequencing errors event | | gauge |
| op_node_default_publishing_errors_total | Count of p2p publishing errors events | | counter |
| op_node_default_last_publishing_errors_unix | Timestamp of last p2p publishing errors event | | gauge |
| op_node_default_unsafe_payloads_buffer_len | Number of buffered L2 unsafe payloads | | gauge |
| op_node_default_unsafe_payloads_buffer_mem_size | Total estimated memory size of buffered L2 unsafe payloads | | gauge |
| op_node_default_refs_number | Gauge representing the different L1/L2 reference block numbers | layer,type | gauge |
| op_node_default_refs_time | Gauge representing the different L1/L2 reference block timestamps | layer,type | gauge |
| op_node_default_refs_hash | Gauge representing the different L1/L2 reference block hashes truncated to float values | layer,type | gauge |
| op_node_default_refs_seqnr | Gauge representing the different L2 reference sequence numbers | type | gauge |
| op_node_default_refs_latency | Gauge representing the different L1/L2 reference block timestamps minus current time, in seconds | layer,type | gauge |
| op_node_default_l1_reorg_depth | Histogram of L1 Reorg Depths | | histogram |
| op_node_default_transactions_sequenced_total | Count of total transactions sequenced | | gauge |
| op_node_default_p2p_peer_count | Count of currently connected p2p peers | | gauge |
| op_node_default_p2p_stream_count | Count of currently connected p2p streams | | gauge |
| op_node_default_p2p_gossip_events_total | Count of gossip events by type | type | counter |
| op_node_default_p2p_bandwidth_bytes_total | P2P bandwidth by direction | direction | gauge |
| op_node_default_sequencer_building_diff_seconds | Histogram of Sequencer building time, minus block time | | histogram |
| op_node_default_sequencer_building_diff_total | Number of sequencer block building jobs | | counter |
| op_node_default_sequencer_sealing_seconds | Histogram of Sequencer block sealing time | | histogram |
| op_node_default_sequencer_sealing_total | Number of sequencer block sealing jobs | | counter |
---
title: Node Operator Guide
lang: en-US
---
::: warning This guide is for bedrock
This guide is for the *bedrock* upgrade, which is coming in Q1, 2023, subject to approval by Optimism governance.
Do not attempt to use this in production prior to that upgrade. Keep an eye on these docs or [our official Twitter](https://twitter.com/OPLabsPBC) for announcements.
:::
This document provides an overview of how to deploy a Bedrock node. To learn more about how Bedrock itself works and its motivations, please see [the specs on GitHub](https://github.com/ethereum-optimism/optimism/tree/develop/specs).
This document is designed to be a general overview of how to deploy a Bedrock node.
## Deployment Overview
A Bedrock deployment consists of two core components:
1. The Rollup Node, which is responsible for deriving blocks from L1 and passing them to the Execution engine. It also participates in a peer-to-peer network that synchronizes unsubmitted blocks as the sequencer creates them. We refer to this component as `op-node`.
2. The Execution Engine, which is responsible for executing the blocks it receives from the rollup node and storing state. It also exposes standard JSON-RPC methods to query blockchain data and submit transactions to the network. We refer to this component as `op-geth`, since our Execution Engine is [a minimal fork](https://op-geth.optimism.io/) of `go-ethereum`.
The Rollup Node and Execution Engine communicate with each other over JSON-RPC via the Engine API. This is similar to how regular Ethereum networks are deployed. The Rollup Node functions as Optimism's consensus client, and the Execution Engine as its execution client.
For Goerli and mainnet, you may also need to run a third component called Legacy Geth. Legacy Geth is used to serve execution traces for transactions prior to the Bedrock upgrade, which we refer to as "historical transactions". When the Rollup Node encounters an RPC call that requires historical execution traces, it will forward the request to Legacy Geth. Note, however, that unlike our previous networks requests for historical _data_ will be served by the Execution Engine directly. This distinction will be clarified later on in this document.
The architecture of a typical Bedrock deployment looks like this:
<div style="text-align: center">
<img width="400" src="../../../assets/docs/bedrock/bedrock-deployment.png">
</div>
## System Requirements
We recommend the following minimum system requirements to run Bedrock:
- `op-node`: Minimum 2CPUs, 4GB RAM. No storage is necessary.
- `op-geth`: Minimum 4 CPUs, 8GB RAM. At least 32GB of storage is required for Goerli. At least 600GB of storage is required for mainnet. Storage must be SSD. Requirements are significantly higher for archive nodes.
## Getting the Software
`op-geth` and `op-node` are available as Docker images. Consult the [documentation](./public-testnets.md) for the network you are participating in to get the correct image tag.
Legacy Geth uses version `0.5.29` of our old `l2geth` image.
You can also compile `op-geth` and `op-node` from source.
## Node Configuration
:::warning
Always run `op-node` and `op-geth` in a one-to-one configuration. Don't run multiple `op-geth` instances behind one `op-node`, or vice versa.
:::
To configure your node, you will need to do the following:
1. Configure `op-node` to point to the correct L1, `op-geth`, and L2 network.
2. Initialize `op-geth` with the correct network parameters.
3. Configure `op-geth` to properly communicate with the Rollup Node.
4. Optionally, configure Legacy Geth.
### Configuring op-geth
:::tip
Even though the Docker image for the Execution Engine is called `op-geth`, the actually binary is still called `geth` in order to minimize differences between `op-geth` and `go-ethereum`.
:::
We'll start with `op-geth`'s configuration because it is more complex. As mentioned before, `op-geth` is a minimal fork of `go-ethereum`. As a result, it stores its state in a database that requires initialization. Initialization is done one of two ways, depending on which network you're deploying:
1. **With a Genesis File:** This is used for testnets or internal deployments that are not migrated from a legacy network. In this case, you'll download the genesis file and initialize the data directory via `geth init`.
2. **With a Data Directory:** This is used for networks that are migrated from a legacy network. In this case, you'll download a preconfigured data directory and extract it. No further initialization is necessary in this case, because the data directory contains the network's genesis information.
Regardless of how `op-geth` is initialized, you'll need to ensure that you have sufficient disk space available to store the network's data. As of this writing, the mainnet data directory is ~100GB for a full node and ~1TB for an archival node. The Goerli data directory is ~6GB for a full node.
Instructions for each initialization method are below.
#### Initialization via Genesis File
`op-geth` uses JSON files to encode a network's genesis information. For networks that are initialized in this way, you'll receive a URL to the genesis JSON. You'll need to download the genesis JSON, then run the following command to initialize the data directory:
```bash
curl -o <path to genesis JSON> -sL <URL to genesis JSON>
geth init \
--datadir="<your data directory>" \
"<path to genesis JSON>"
```
#### Initialization via Data Directory
To initialize `op-geth` with a preconfigured data directory, simply download and extract the data directory to a place of your choosing. The data directory is exported as a tar file. An example command to do this is below:
```bash
curl -o <path to data directory> -sL <URL to data directory>
tar -xvf <path to data directory>
```
#### Configuration
Once `op-geth` is initialized, it can be configured via CLI flags. `op-geth` accepts all the [standard `go-ethereum` flags](https://geth.ethereum.org/docs/interface/command-line-options) as well as a few extra flags that are specific to Optimism. These flags are:
- `--rollup.historicalrpc`: Enables the historical RPC endpoint. This endpoint is used to fetch historical execution data from Legacy Geth. This flag is only necessary for upgraded networks.
- `--rollup.sequencerhttp`: HTTP endpoint of the sequencer. `op-geth` will route `eth_sendRawTransaction` calls to this URL. Bedrock does not currently have a public mempool, so this is required if you want your node to support transaction submission. Consult the documentation for the network you are participating in to get the correct URL.
- `--rollup.disabletxpoolgossip`: Disables transaction pool gossiping. While not required, it's useful to set this to `true` since transaction pool gossip is currently unsupported.
To communicate with `op-node` and enable the Engine API, you'll also need to generate a JWT secret file and enable Geth's authenticated RPC endpoint.
To generate the JWT secret, run the following:
```bash
openssl rand -hex 32 > jwt.txt
```
Then, specify the following flags:
- `--authrpc.addr`: Sets the address `op-geth`'s authenticated RPC should listen on.
- `--authrpc.port`: Sets the port `op-geth`'s authenticated RPC should listen on. The default value is `8551`.
- `--authrpc.jwtsecret`: Sets the path to a JWT secret file you generated above.
- `--syncmode=full`: This prevents Geth from attempting to snap sync. Snap sync is currently unsupported, but will be enabled shortly after the mainnet upgrade.
- `--maxpeers=0`: This prevents Geth from peering with other Geth nodes. Execution-layer peering is currently unsupported, but will be added as part of enabling snap sync.
- `--nodiscover`: This disables Geth's peer discovery mechanism. Execution-layer discovery is currently unsupported, but will be added as part of enabling snap sync.
#### Optional op-geth Configuration
You may also want to specify the following flags based on your configuration:
- `--authrpc.vhosts`: Whitelists which hosts (as defined in the `Host` header) are allowed to access the authenticated RPC endpoint. This is useful if you're running `op-geth` on containerized infrastructure. The default value is `localhost`.
- `--http.vhosts`: Whitelists which hosts (as defined in the `Host` header) are allowed to access the unauthenticated RPC endpoint. This is useful if you're running `op-geth` on containerized infrastructure. The default value is `localhost`.
- `--http`, `--http.addr`, and `--http.port`: Enables the unauthenticated RPC endpoint, configures its address, and configures its port. You'll almost certainly want to specify these, since they will enable Geth's JSON-RPC endpoint.
- `--ws`, `--ws.addr`, and `--ws.port`: Enables the WebSocket API.
- `--verbosity`: Configures Geth's log level. This is a number between 0 and 5, with 5 being the most verbose. Defaults to 3.
#### Working Base Configuration
A valid command that runs `op-geth` on our `beta-1` network and enables RPC over HTTP and WebSockets looks like:
```bash
geth \
--ws \
--ws.port=8546 \
--ws.addr=localhost \
--ws.origins="*" \
--http \
--http.port=8545 \
--http.addr=localhost \
--http.vhosts="*" \
--http.corsdomain="*" \
--authrpc.addr=localhost \
--authrpc.jwtsecret=/var/secrets/jwt.txt \
--authrpc.port=8551 \
--authrpc.vhosts="*" \
--datadir=/data \
--verbosity=3 \
--rollup.disabletxpoolgossip=true \
--rollup.sequencerhttp=https://bedrock-beta-1-sequencer.optimism.io \
--nodiscover \
--syncmode=full \
--maxpeers=0
```
Consult [Geth's documentation](https://geth.ethereum.org/docs/) for more information on customizing `op-geth`'s behavior.
### Configuring op-node
`op-node` is a standalone, statically linked binary. It stores no state, and requires no initialization. It consumes configuration parameters either via the command line or environment variables. For some networks, the Rollup Node also requires a configuration file (called `rollup.json` or the "rollup config") that configures network-specific genesis parameters. For official networks like Goerli and mainnet, the genesis config is hardcoded in the `op-node` software and can be specified via a `--network` flag.
A minimal valid configuration for a rollup node on our `beta-1` testnet looks like this:
```bash
op-node --l1=<goerli RPC url> \
--l2=<op-geth authenticated RPC url> \
--network=beta-1
--rpc.addr=127.0.0.1 \
--rpc.port=9545 \
--l2.jwt-secret=<path to JWT secret>
```
You can manually specify a path to a rollup config with the `--rollup.config` flag. This is used for testnets or internal deployments that are not migrated from a legacy network.
Each of the above flags can also be defined via an environment variable. Run `op-node --help` to see a list of all available flags and environment variables.
#### Configuring Peer-to-Peer Networking
Unlike the previous system, the `op-node` participates in a peer-to-peer network. This network is used to distribute blocks that have not been submitted to L1 yet. The `op-node` will automatically discover and connect to peers using a hardcoded set of bootnodes. You can also manually specify peers to connect to via the `--p2p.static` flag.
For best results, run `op-node` with a static IP address that is accessible from the public Internet. For Kubernetes deployments, this can be achieved by configuring a dedicated `Ingress` with an external IP, and using the `--p2p.advertise.ip` flag to specify the IP address of the load balancer when advertising IP addresses to peers.
The default port for the peer-to-peer network is `9003`. You will need to open this port on your firewall to receive unsubmitted blocks. For your node to be discoverable, this port must be accessible via both TCP and UDP protocols.
### Legacy Geth
If you are running a node for an upgraded network like Goerli or mainnet, you will also need to run Legacy Geth in order to serve historical execution traces. Fundamentally, Legacy Geth is our old `l2geth` binary running against a preconfigured data directory. To configure Legacy Geth, follow the instructions above for using a preconfigured data directory, then execute the following command:
:::danger
It is imperative that you specify the `USING_OVM=true` environment variable in the command below. Failing to specify this will cause `l2geth` to return invalid execution traces, or panic at startup.
:::
```bash
USING_OVM=true \
ETH1_SYNC_SERVICE_ENABLE=false \
RPC_API=eth,rollup,net,web3,debug \
RPC_ADDR=0.0.0.0 \
RPC_CORS_DOMAIN=* \
RPC_ENABLE=true \
RPC_PORT=8545 \
RPC_VHOSTS=* \
geth --datadir <path to data directory>
```
This command is the minimum required to run Legacy Geth and expose a functioning RPC endpoint. As before, `l2geth` takes all standard `go-ethereum` flags so you can customize the configuration as needed.
As mentioned above, don't forget to specify `--rollup.historicalrpc` on `op-geth` to properly route requests for historical execution to Legacy Geth.
Since Legacy Geth is read-only, it is safe to run multiple Legacy Geth nodes behind a load balancer.
#### Historical Execution vs. Historical Data Routing
Only requests for historical execution will be routed to Legacy Geth.
Everything else will be served by `op-geth` directly.
The term _historical execution_ refers to RPC methods that need to execute transactions prior to bedrock (not just read data from the database):
- `eth_call`
- `eth_estimateGas`
- `debug_traceBlockByNumber`
- `debug_traceBlockByHash`
- `debug_traceCall`
- `debug_traceTransaction`
If you do not need these RPC methods for historical data, then you do not need to run Legacy Geth at all.
## Troubleshooting
### 401 Unauthorized: Signature Invalid
If you see a log that looks like this in `op-node`:
```
WARN [12-13|15:53:20.263] Derivation process temporary error attempts=80 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch current L2 forkchoice state: failed to find the finalized L2 block: failed to determine L2BlockRef of finalized, could not get payload: 401 Unauthorized: signature is invalid
```
It means that the `op-node` is unable to authenticate with `op-geth`'s authenticated RPC using the JWT secret. To fix:
1. Check that the JWT secret is correct in both services.
2. Check that `op-geth`'s authenticated RPC is enabled, and that the URL is correct.
### 403 Forbidden: Invalid Host Specified
If you see a log that looks like this in `op-node`:
```
{"err":"403 Forbidden: invalid host specified\n","lvl":"eror","msg":"error getting latest header","t":"2022-12-13T22:29:18.932833159Z"}
```
It means that you have not whitelisted `op-node`'s host with `op-geth`. To fix:
1. Make sure that the `--authrpc.vhosts` parameter in `op-geth` is either set to the correct host, or `*`.
2. Check that `op-geth`'s authenticated RPC is enabled, and that the URL is correct.
### Failed to Load P2P Config
If you see a log that looks like this in `op-node`:
```
CRIT [12-13|13:46:21.386] Application failed message="failed to load p2p config: failed to load p2p discovery options: failed to open discovery db: mkdir /p2p: permission denied"
```
It means that the `op-node` does not have write access to the P2P discovery or peerstore directories. To fix:
1. Make sure that the `op-node` has write access to the P2P directory. By default, this is `/p2p`.
2. Set the P2P directory to somewhere the `op-node` can access via the `--p2p.discovery.path` and `--p2p.peerstore.path` parameters.
3. Set the discovery path to `memory` to disable persistence via the `--p2p.discovery.path` and `--p2p.peerstore.path` parameters.
### Wrong Chain
If you see a log that looks like this in `op-node`:
```
{"attempts":183,"err":"stage 0 failed resetting: temp: failed to find the L2 Heads to start from: wrong chain L1: genesis: 0x4104895a540d87127ff11eef0d51d8f63ce00a6fc211db751a45a4b3a61a9c83:8106656, got 0x12e2c18a3ac50f74d3dd3c0ed7cb751cc924c2985de3dfed44080e683954f1dd:8106656","lvl":"warn","msg":"Derivation process temporary error","t":"2022-12-13T23:31:37.855253213Z"}
```
It means that the `op-node` is pointing to the wrong chain. To fix:
1. Verify that the `op-node`'s L1 URL is pointing to the correct L1 for the given network.
2. Verify that the `op-node`'s rollup config/`--network` parameter is set to the correct network.
3. Verify that the `op-node`'s L2 URL is pointing to the correct instance of `op-geth`, and that `op-geth` is properly initialized for the given network.
## Further Reading
Now that you've got your node up and running, check out the [Upgrade Guide](./upgrade-guide.md) for what to expect when we upgrade our official testnet and mainnet.
\ No newline at end of file
---
title: Public Testnets
lang: en-US
---
## Goerli
The Optimism Goerli testnet was migrated to Bedrock on January 12, 2023.
<table width="100%">
<tbody>
<tr>
<td colspan="2"><strong>Chain Parameters</strong></td>
</tr>
<tr>
<td>L1 Chain</td>
<td>Goerli</td>
</tr>
<tr>
<td>L1 Chain ID</td>
<td>5</td>
</tr>
<tr>
<td>L2 Chain ID</td>
<td><code>420</code></td>
</tr>
<tr>
<td>Rollup Config</td>
<td>This network does not require a rollup config. Specify <code>--network=goerli</code> on the command line
when official images are released.
</td>
</tr>
<tr>
<td>Bedrock Data Directory</td>
<td><a href="https://storage.googleapis.com/oplabs-goerli-data/goerli-bedrock.tar">https://storage.googleapis.com/oplabs-goerli-data/goerli-bedrock.tar</a>
</td>
</tr>
<tr>
<td>Legacy Geth Data Directory</td>
<td><a href="https://storage.googleapis.com/oplabs-goerli-data/goerli-legacy-archival.tar">https://storage.googleapis.com/oplabs-goerli-data/goerli-legacy-archival.tar</a>
</td>
</tr>
<tr>
<td>Block Explorer</td>
<td>
<a href="https://goerli-optimism.etherscan.io">https://goerli-optimism.etherscan.io</a>
</td>
</tr>
<tr>
<td>Public RPC Endpoint</td>
<td>
<a href="https://goerli.optimism.io">https://goerli.optimism.io</a>
</td>
</tr>
<tr>
<td>Sequencer Endpoint</td>
<td>
<a href="https://goerli-sequencer.optimism.io">https://goerli-sequencer.optimism.io</a>
</td>
</tr>
<tr>
<td>Withdrawal Period</td>
<td>2 Seconds</td>
</tr>
<tr>
<td colspan="2"><strong>Software Images</strong></td>
</tr>
<tr>
<td>op-node</td>
<td><code>us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v0.10.9</code></td>
</tr>
<tr>
<td>op-geth</td>
<td><code>ethereumoptimism/op-geth:v1.10.26-166f27c</code></td>
</tr>
<tr>
<td>Legacy Geth</td>
<td><code>ethereumoptimism/l2geth:0.5.29</code></td>
</tr>
<tr>
<td colspan="2"><strong>Contract Addresses</strong></td>
</tr>
<tr>
<td><a href="https://goerli.etherscan.io/address/0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60">SystemConfigProxy</a>
</td>
<td><code>0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60</code></td>
</tr>
<tr>
<td><a href="https://goerli.etherscan.io/address/0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0">L2OutputOracleProxy</a>
</td>
<td><code>0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0</code></td>
</tr>
<tr>
<td><a href="https://goerli.etherscan.io/address/0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383">OptimismPortalProxy</a>
</td>
<td><code>0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383</code></td>
</tr>
<tr>
<td><a href="https://goerli.etherscan.io/address/0x883dcF8B05364083D849D8bD226bC8Cb4c42F9C5">OptimismMintableERC20FactoryProxy</a>
</td>
<td><code>0x883dcF8B05364083D849D8bD226bC8Cb4c42F9C5</code></td>
</tr>
<tr>
<td><a href="https://goerli.etherscan.io/address/0x1f0613A44c9a8ECE7B3A2e0CdBdF0F5B47A50971">SystemDictatorProxy</a>
</td>
<td><code>0x1f0613A44c9a8ECE7B3A2e0CdBdF0F5B47A50971</code></td>
</tr>
<tr>
<td>
<a href="https://goerli.etherscan.io/address/0xa6f73589243a6A7a9023b1Fa0651b1d89c177111">Lib_AddressManager</a>
</td>
<td>
<code>0xa6f73589243a6A7a9023b1Fa0651b1d89c177111</code>
</td>
</tr>
<tr>
<td>
<a href="https://goerli.etherscan.io/address/0x5086d1eEF304eb5284A0f6720f79403b4e9bE294">Proxy__OVM_L1CrossDomainMessenger</a>
</td>
<td>
<code>0x5086d1eEF304eb5284A0f6720f79403b4e9bE294</code>
</td>
</tr>
<tr>
<td>
<a href="https://goerli.etherscan.io/address/0x636Af16bf2f682dD3109e60102b8E1A089FedAa8">Proxy__OVM_L1StandardBridge</a>
</td>
<td>
<code>0x636Af16bf2f682dD3109e60102b8E1A089FedAa8</code>
</td>
</tr>
</tbody>
</table>
\ No newline at end of file
---
title: Upgrade Guide
lang: en-US
---
::: warning This guide is for bedrock
This guide is for the *bedrock* upgrade, which is coming in Q1, 2023, subject to approval by Optimism governance.
Do not attempt to use this in production prior to that upgrade. Keep an eye on these docs or [our official Twitter](https://twitter.com/OPLabsPBC) for announcements.
:::
This document provides an overview of how the Bedrock upgrade will be executed and how it will impact node operators and dApp developers.
## Upgrade Overview
Unlike previous upgrades, the Bedrock upgrade will not be a "regenesis" event where historical transaction data is lost and the chain resets at block zero. Instead, the Bedrock upgrade will resemble a hard fork where the new Bedrock chain will be a continuation of the old one. This ensures that the upgrade is as seamless as possible.
The upgrade will proceed as follows on upgrade day:
1. Deposits and withdrawals on the legacy network will be paused.
2. Transactions on the legacy sequencer will no longer be accepted.
3. The smart contracts on L1 will be upgraded and an irregular state transition on L2 will be performed.
4. The Bedrock sequencer will start up.
5. Deposits and withdrawals will be re-enabled.
6. The contract addresses, binaries, and data directories required to interact with the new system will be distributed.
Backwards compatibility is one of the upgrade's key design goals.
Potential incompatibilities and their workarounds are highlighted in the sections below.
## Optimism Mainnet Upgrade
The Bedrock upgrade to the Optimism Mainnet Network is yet to be scheduled.
We plan to announce an official date and time in February 2023 at least 3 weeks in advance of the upgrade, subject to approval by Optimism governance.
## For Node Operators
:::tip Prerequisites
This section assumes that you have read and understood our [Node Operator Guide](./node-operator-guide.md). Please read that first if you haven't already.
:::
From a node operator perspective, the old system will be completely _replaced_ on upgrade day. This means that rather than upgrading legacy infrastructure, node operators will be standing up entirely new infrastructure to run the Bedrock network.
On upgrade day, we will provide node operators with the following information:
1. The correct `op-node` and `op-geth` images and binaries to use.
2. A URL to an upgraded data directory containing the genesis state for the new system.
3. A URL to a legacy data directory containing data for Legacy Geth.
4. A set of bootnodes to use as part of the peer-to-peer network.
We will embed the rollup config into the `op-node` itself. Then, on upgrade day, you will need to:
1. Initialize `op-geth`'s data directory using the upgraded genesis state from the provided URL. See the [Initialization via Data Directory](./node-operator-guide.md#initialization-via-data-directory) section of the Node Operator Guide for more information.
2. Specify the `op-node`'s network via the `--network` flag or `OP_NODE_NETWORK` environment variable. Its value will be `goerli` for the Goerli upgrade, or `mainnet` for the mainnet upgrade.
3. Initialize Legacy Geth's data directory using the legacy genesis state from the provided URL. See the [Initialization via Data Directory](./node-operator-guide.md#initialization-via-data-directory) and [Legacy Geth](./node-operator-guide.md#legacy-geth) sections of the Node Operator Guide for more information.
4. Set the `op-geth` `--rollup.historicalrpc` parameter to point to Legacy Geth's RPC endpoint.
5. Start `op-geth` and `op-node` as usual.
The best way to prepare for the upgrade is to participate in one of our public testnets. Please see the [public testnets](./public-testnets.md) page for how to connect to our testnet.
## For dApp and Wallet Developers
On upgrade day, deposits and withdrawals will be paused, along with ingress on the sequencer. This means that all transactions on Optimism will be halted for the duration of the upgrade.
Once the upgrade is complete, everything should be identical to how it was before the upgrade. All balances, contract addresses, transaction data, block data, and historical execution traces will be preserved. The new network is EVM-equivalent, so all existing Ethereum tooling will continue to work with the new system. Differences are described in [How is Bedrock Different?](./how-is-bedrock-different.md).
## FAQs
### When is the upgrade taking place?
The Goerli upgrade is tentatively scheduled for January 2023. The mainnet upgrade is tentatively scheduled for February 2023. The Goerli upgrade will be live for at least a month before the mainnet upgrade.
### Is this a hard fork, or a new network?
This is a hard fork. The network will retain the same chain ID, transaction history, and state. The first block of the new network will be the last block of the new network + 1.
### How long will the upgrade take?
We expect the upgrade to take less than 4 hours.
### How can I best prepare for the upgrade?
The best way to prepare for the upgrade is to participate in one of our public testnets. Please see the [Beta Testnet](https://www.notion.so/External-Optimism-Bedrock-Beta-Testnet-454a37e469af4658b89a9d766334e331) page for how to connect to our current testnet.
### Why is Legacy Geth necessary?
The upgraded data directory used to initialize `op-geth` contains the current state of the network as well as all historical block, transaction, and receipt data. However, providing historical execution would require bundling the legacy system's EVM implementation with `op-geth`. In an effort to keep our diff between `op-geth` and upstream `go-ethereum` small, we instead route requests for historical execution traces to Legacy Geth which already contains the correct execution engine.
You only need to run Legacy Geth if you need historical execution traces.
### What version of upstream Geth is op-geth based off of?
`op-geth` is currently based off of the `1.10.x` version of `go-ethereum`.
`op-geth` is periodically updated include the latest upstream changes.
### How can I see the difference between upstream Geth and op-geth?
A single-commit diff is maintained in the `op-geth` repository. See [here](https://github.com/ethereum-optimism/op-geth/compare/master...optimism) for the comparison.
### Will transaction tracing for post-Bedrock data be faster?
Yes. `op-geth` uses the latest transaction tracers from upstream, which have much better performance than tracers legacy `l2geth` uses.
\ No newline at end of file
---
title: Bridging basics
lang: en-US
---
Although Optimism is an L2 (and therefore fundamentally connected to Ethereum), it's also a separate blockchain system.
App developers commonly need to move data and assets between Optimism and Ethereum.
We call the process of moving data and assets between the two networks "bridging".
## Sending tokens
For the most common usecase, moving tokens around, we've created the [Standard Token Bridge](./standard-bridge.md).
The Standard Token Bridge is a simple smart contract with all the functionality you need to move tokens between Optimism and Ethereum.
It also allows you to easily create L2 representations of existing tokens on Ethereum.
## Sending data
If the Standard Token Bridge doesn't fully cover your usecase, you can also [send arbitrary data between L1 and L2](./messaging.md).
You can use this functionality to have a contract on Ethereum trigger a contract function on Optimism and vice versa.
We've made this process as easy as possible by giving developers a simple API for triggering a cross-chain function call.
We even [use this API under the hood](https://github.com/ethereum-optimism/optimism/blob/a21cec6d3d00c9d7ed100c0257d4b966b034620f/packages/contracts/contracts/L1/messaging/L1StandardBridge.sol#L202) inside the Standard Token Bridge.
---
title: Communication Strategies
lang: en-US
---
Dapps' inter-layer communication strategies are based on trade-offs between several parameters:
- Speed
- Cost
- Trust assumptions
An issue related to both speed and decentralization is the L2 state.
This state is vulnerable to fault challenges until the fault challenge period (currently one week) passes.
If you want to do something that relies on the L2 state prior to that point, you should [run a replica](../build/run-a-node.md) yourself to make sure the state you use is correct.
## Fully centralized
If your dapp has a centralized always on server, the easiest solution is to just have two providers, one connected to Ethereum (L1) and the other to Optimism (L2).
| Parameter | Evaluation |
| - | - |
| Speed | Fastest
| Cost | Cheapest
| Trust assumption | Centralized
### Using the client (please don't)
The client (typically a browser with a wallet) can also connect to both Ethereum and Optimism, but it isn't a great mechanism for inter-layer communication.
You know what the code in the server is, because you wrote it.
You know what the code in the client is *supposed to be*, but it is possible for users to run a different client that pretends to be the legitimate one.
The only time that you can trust the client for inter-layer communication is when it is in the best interest of the user running the client not to lie.
And even then, you shouldn't because a hacker can cause a user to run malware.
## Fully decentralized
If you want to keep the same decentralization promises as Optimism and Ethereum, you can [rely on our messaging infrastructure](messaging.md).
You are already trusting Optimism to run the chain, and the messaging infrastructure goes through the same development process.
### Messages from L1 to L2
If you want L1 code to either tell L2 code to do something, or update L2 code with some information, you just need to [issue a single L1 transaction](messaging.md#for-ethereum-l1-to-optimism-l2-transactions).
| Parameter | Evaluation |
| - | - |
| Speed | ~15 minutes
| Cost | Cheapish (requires an L1 transaction)
| Trust assumption | Same as using Optimism
### Messages from L2 to L1
Sending messages from L2 to L1 is [a lot harder](messaging.md#for-optimism-l2-to-ethereum-l1-transactions).
It requires two transactions:
1. An initiating transaction on L2, which is pretty cheap.
1. Once the fault challenge period passes, a claiming transaction on L1, which includes [a merkle proof](https://medium.com/crypto-0-nite/merkle-proofs-explained-6dd429623dc5).
This transaction is expensive because merkle proof verification is expensive.
| Parameter | Evaluation |
| - | - |
| Speed | >7 days
| Cost | Expensive
| Trust Assumption | Almost as good as using Optimism, however someone needs to initiate the claim transaction on L1
## Incentivized communication
You can also use incentives, for example using a mechanism such as [UMA's](../../useful-tools/oracles/#universal-market-access-uma).
This is similar to the way optimistic rollups work - honest relays get paid, dishonest ones get slashed.
However, this mechanism is only truly decentralized if there are enough relayers to make sure there will always be an honest one.
Otherwise, it's similar to centralized communications, just with a few extra relayers that can take over.
\ No newline at end of file
---
title: Sending data between L1 and L2
lang: en-US
---
Apps on Optimism can be made to interact with apps on Ethereum via a process called "bridging".
In a nutshell, **contracts on Optimism can trigger contract functions on Ethereum, and vice versa**.
With just a little bit of elbow grease, you too can create contracts that bridge the gap between Layer 1 and Layer 2!
::: tip
[See here for a step by step tutorial](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-comm)
:::
<details>
<summary><b>Understanding contract calls</b></summary>
To understand the process of creating bridges between contracts on Layer 1 and Layer 2, you should first have a basic understanding of the way contracts on *Ethereum* communicate with one another.
If you're a smart contract developer, you might be familiar with stuff like this:
```solidity
contract MyContract {
function doSomething(uint256 myFunctionParam) public {
// ... some sort of code goes here
}
}
contract MyOtherContract {
function doTheThing(address myContractAddress, uint256 myFunctionParam) public {
MyContract(myContractAddress).doSomething(myFunctionParam);
}
}
```
Here, `MyOtherContract.doTheThing` triggers a "call" to `MyContract.doSomething`.
A "call" is defined by a few key input parameters, mainly a `target address` and some `calldata`.
In this specific example, the `target address` is going to be the address of our instance of `MyContract`.
The `calldata`, on the other hand, depends on the function we're trying to call.
Solidity uses an encoding scheme called [Contract ABI](https://docs.soliditylang.org/en/v0.8.4/abi-spec.html) to both [select which function to call](https://docs.soliditylang.org/en/v0.8.4/abi-spec.html#function-selector) and to [encode function input arguments](https://docs.soliditylang.org/en/v0.8.4/abi-spec.html#argument-encoding).
Solidity gives us some useful tools to perform this same encoding manually.
For the sake of learning, let's take a look at how we can duplicate the same code with a manual encoding:
```solidity
contract MyContract {
function doSomething(uint256 myFunctionParam) public {
// ... some sort of code goes here
}
}
contract MyOtherContract {
function doTheThing(address myContractAddress, uint256 myFunctionParam) public {
myContractAddress.call(
abi.encodeWithSignature(
"doSomething(uint256)",
myFunctionParam
)
);
}
}
```
Here we're using the [low-level "call" function](https://docs.soliditylang.org/en/v0.8.4/units-and-global-variables.html#members-of-address-types) and one of the [ABI encoding functions built into Solidity](https://docs.soliditylang.org/en/v0.8.4/units-and-global-variables.html#abi-encoding-and-decoding-functions).
Although these two code snippets look a bit different, they're actually functionally identical.
</details>
## Communication basics between layers
At a high level, this process is pretty similar to the same process for two contracts on Ethereum (with a few caveats).
**Communication between L1 and L2 is enabled by two special smart contracts called the "messengers"**.
Each layer has its own messenger contract which serves to abstract away some lower-level communication details, a lot like how HTTP libraries abstract away physical network connections.
We won't get into *too* much detail about these contracts here — the only thing you really need to know about is the `sendMessage` function attached to each messenger:
```solidity
function sendMessage(
address _target,
bytes memory _message,
uint32 _gasLimit
) public;
```
It's the same as that `call` function used for contract messaging within L1 Ethereums.
We have an extra `_gasLimit` field here, but `call` has that too.
This is basically equivalent to:
```solidity
address(_target).call{gas: _gasLimit}(_message);
```
Except, of course, that we're calling a contract on a completely different network.
We're glossing over a lot of the technical details that make this whole thing work under the hood.
Point is, it works.
Want to call a contract on Optimism from a contract on Ethereum?
It's dead simple:
```solidity
// Pretend this is on L2
contract MyOptimisticContract {
function doSomething(uint256 myFunctionParam) public {
// ... some sort of code goes here
}
}
// And pretend this is on L1
contract MyOtherContract {
function doTheThing(address myOptimisticContractAddress, uint256 myFunctionParam) public {
ovmL1CrossDomainMessenger.sendMessage(
myOptimisticContractAddress,
abi.encodeWithSignature(
"doSomething(uint256)",
myFunctionParam
),
1000000 // use whatever gas limit you want
)
}
}
```
::: tip Using the messenger contracts
Our messenger contracts, the [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1CrossDomainMessenger.sol) and [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/L2CrossDomainMessenger.sol), always come pre-deployed to each of our networks.
You can find the exact addresses of these contracts on our various deployments [inside of the Optimism monorepo](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments).
:::
## Communication speed
Unlike calls between contracts on the same blockchain, calls between Ethereum and Optimism are *not* instantaneous.
The exact speed of a cross-chain transaction depends on the direction in which the transaction is sent.
### For Ethereum (L1) to Optimism (L2) transactions
Transactions sent from L1 to L2 take up to approximately 15 minutes on mainnet and 5 minutes on the Optimism Goerli testnet to reach the target L2 contract.
This is because L2 nodes will wait for a certain number of block confirmations on Ethereum before executing an L1 to L2 transaction.
### For Optimism (L2) to Ethereum (L1) transactions
L2 to L1 transactions have to wait two periods:
1. The time until the state root is written to L1.
You can estimate this time by looking at how often transactions happen to the State Commitment Chain (on both [mainnet](https://etherscan.io/address/0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19) and [goerli](https://goerli.etherscan.io/address/0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0)).
As of the Bedrock update, it is necessary to provide a Merkle proof of the message on L1 after the state root is written.
The fault challenge period starts *after* that proof transaction becomes part of the L1 chain.
1. The [fault challenge period](#understanding-the-challenge-period), which is a few seconds on goerli and seven days on mainnet.
This waiting period is a core part of the security mechanism designed to keep funds on Optimism secure and cannot be circumvented.
After this waiting period, any user can "finalize" the transaction by triggering a second transaction on Ethereum that sends the message to the target L1 contract.
## Accessing `msg.sender`
Contracts frequently make use of `msg.sender` to make decisions based on the calling account.
For example, many contracts will use the [Ownable](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) pattern to selectively restrict access to certain functions.
Because messages are essentially shuttled between L1 and L2 by the messenger contracts, **the `msg.sender` you'll see when receiving one of these messages will be the messenger contract** corresponding to the layer you're on.
In order to get around this, we added a `xDomainMessageSender` function to each messenger:
```solidity
function xDomainMessageSender() public returns (address);
```
If your contract has been called by one of the messenger contracts, you can use this function to see who's *actually* sending this message.
Here's how you might implement an `onlyOwner` modifier on L2:
```solidity
modifier onlyOwner() {
require(
msg.sender == address(ovmL2CrossDomainMessenger)
&& ovmL2CrossDomainMessenger.xDomainMessageSender() == owner
);
_;
}
```
## Fees for sending data between L1 and L2
### For L1 ⇒ L2 transactions
The majority of the cost of an L1 to L2 transaction comes from sending a transaction on Ethereum.
You send a transaction to the [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1CrossDomainMessenger.sol)
contract, which then sends a call to the [`CanonicalTransactionChain`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/rollup/CanonicalTransactionChain.sol).
This cost is ultimately determined by gas prices on Ethereum when you're sending the cross-chain transaction.
An L1 to L2 message is expected to trigger contract execution on L2, and that contract execution costs gas.
The first 1.92 million gas on L2 is free.
The vast majority of L1 to L2 calls spend less than the 1.92 million, so nothing further is required.
If you think that your call might spend more than that on L2, you can specify a higher gas limit.
However, to prevent denial of service attacks, we have to impose a cost on gas limits higher than 1.92 million.
This cost is one unit of L1 gas for every 32 units of L2 gas requested beyond the free amount.
For example, if you specify a 2.0 million gas limit in the call to `L1CrossDomainMessenger`, it will be processed this way:
| Amount | Action |
| ------ | ------- |
| free gas: 1.92 million | Nothing, this gas is provided on L2 for free |
| excess gas required: 80,000 | 2,500 gas is spent on the L1 portion of the gas fee and in return 80,000 extra gas is provided to the L2 transaction. This is inline with the 1:32 ratio of gas. |
This gas burn happens on L1 when the L1 contract calls `L1CrossDomainMessenger`.
This is before the message has been sent to the L2, and as such there is no way to know how much L2 gas will actually be used.
Therefore, the amount burned is based *only* on the gas limit specified in the L1 call.
For example, if the call above with a gas limit of two million only takes ten thousand gas on L2, the 2,500 gas on L1 is still burned.
There is no refund.
The parameters in the explanation above were 1.92 million and 32 at the time of writing, but they may change in the future.
To see the present values, [go to Etherscan](https://etherscan.io/address/0x5E4e65926BA27467555EB562121fac00D24E9dD2#readContract) and expand `enqueueL2GasPrepaid` for the free L2 gas amount and `l2GasDiscountDivisor` for the exchange rate at which L1 gas is burned for additional L2 gas.
### Fees for L2 ⇒ L1 transactions
Each message from L2 to L1 requires three transactions:
1. An L2 transaction that *initiates* the transaction, which is priced the same as any other transaction made on Optimism.
1. An L1 transaction that *proves* the transaction.
This transaction can only be submitted after the state root is submitted to L1.
This transaction is expensive because it includes verifying a [Merkle trie](https://eth.wiki/fundamentals/patricia-tree) inclusion proof.
1. An L1 transaction that *finalizes* the transaction.
This transaction can only be submitted after the transaction challenge period (7 days on mainnet) has passed.
The total cost of an L2 to L1 transaction is therefore the combined cost of the L2 initialization transaction and the two L1 transactions.
The L1 proof and finalization transactions are typically significantly more expensive than the L2 initialization transaction.
## Understanding the challenge period
One of the most important things to understand about L1 ⇔ L2 interaction is that **messages sent from Layer 2 to Layer 1 cannot be relayed for at least one week**.
This means that any messages you send from Layer 2 will only be received on Layer 1 after this one week period has elapsed.
We call this period of time the "challenge period" because it is the time during which a transaction can be challenged with a [fault proof](../../protocol/2-rollup-protocol.md#fault-proofs).
Optimistic Rollups are "optimistic" because they're based around the idea of publishing the *result* of a transaction to Ethereum without actually executing the transaction on Ethereum.
In the "optimistic" case, this transaction result is correct and we can completely avoid the need to perform complicated (and expensive) logic on Ethereum.
Cheap transactions, yay!
However, we still need some way to prevent incorrect transaction results from being published in place of correct ones.
Here's where the "fault proof" comes into play.
Whenever a transaction result is published, it's considered "pending" for a period of time known as the challenge period.
During this period of time, anyone may re-execute the transaction *on Ethereum* in an attempt to demonstrate that the published result was incorrect.
If someone is able prove that a transaction result is faulty, then the result is scrubbed from existence and anyone can publish another result in its place (hopefully the correct one this time, financial punishments make faulty results *very* costly for their publishers).
Once the window for a given transaction result has fully passed without a challenge the result can be considered fully valid (or else someone would've challenged it).
Anyway, the point here is that **you don't want to be making decisions about Layer 2 transaction results from inside a smart contract on Layer 1 until this challenge period has elapsed**.
Otherwise you might be making decisions based on an invalid transaction result.
As a result, L2 ⇒ L1 messages sent using the standard messenger contracts cannot be relayed until they've waited out the full challenge period.
::: tip On the length of the challenge period
We've set the challenge period to be exactly seven days on the Optimism mainnet.
We believe this is a reasonable balance between security and usability, with an emphasis on increased security to start.
We're open to changing the length of the window as long as we feel this can be done without significantly reducing the security of the system.
If you're strongly opinionated about this, we recommend [opening an issue on GitHub](https://github.com/ethereum-optimism/optimism/issues) explaining your position.
We *will* hear you out!
:::
---
title: Using the Standard Bridge
lang: en-US
---
Certain interactions, like transferring ETH and ERC20 tokens between the two networks, are common enough that we've built the "Standard Bridge" to make moving these assets between L1 and L2 as easy as possible.
The standard bridge functionality provides a method for an ERC20 token to be deposited and locked on L1 in exchange of the same amount of an equivalent token on L2. This process is known as "bridging a token", e.g. depositing 100 USDC on L1 in exchange for 100 USDC on L2 and also the reverse - withdrawing 100 USDC on L2 in exchange for the same amount on L1. In addition to bridging tokens the standard bridge is also used for ETH.
The Standard Bridge is composed of two main contracts the [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L1/messaging/L1StandardBridge.sol) (for Layer 1) and the [`L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L2/messaging/L2StandardBridge.sol) (for Layer 2).
Here we'll go over the basics of using this bridge to move ERC20 assets between Layer 1 and Layer 2.
::: tip
[See here for a step by step tutorial](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-bridge-erc20)
:::
## Deposits
::: warning NOTICE
We're working hard to get more smart contract wallet software deployed and tested on Optimism.
However, as a safety measure, **we currently block smart contract wallets from calling the `depositETH` and `depositERC20` functions**.
If you want to deposit using a smart contract wallet and you know what you're doing, you can use the `depositETHTo` and `depositERC20To` functions instead.
:::
### Depositing ERC20s
ERC20 deposits into L2 can be triggered via the `depositERC20` and `depositERC20To` functions on the [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1StandardBridge.sol).
You **must** approve the Standard Token Bridge to use the amount of tokens that you want to deposit or the deposit will fail.
::: danger Use the standard bridge contract only with standard bridge tokens
The standard bridge can only be used with tokens that have a properly configured ERC-20 version on Optimism.
If you send any other type of token to the standard bridge directly (not using the user interface or the API), it gets stuck there and you lose that value.
Note that if you use the [Optimism bridge UI](https://app.optimism.io/bridge), or the [Optimism SDK](../../sdk/js-client.md) it automatically chooses the correct bridge contract, so this problem does not happen.
There are two ways to check if a token can use the standard bridge:
1. Look in [the token list](https://static.optimism.io/optimism.tokenlist.json).
If a token can use the standard bridge, then the `"chainId": 10` entry will have the standard L2 bridge address, `0x4200..0010`. For example, this entry shows that on the main Optimism network `0xBTC` can use the standard bridge.
```json
{
"chainId": 10,
"address": "0xe0BB0D3DE8c10976511e5030cA403dBf4c25165B",
"name": "0xBitcoin",
"symbol": "0xBTC",
"decimals": 8,
"logoURI": "https://ethereum-optimism.github.io/data/0xBTC/logo.png",
"extensions": {
"optimismBridgeAddress": "0x4200000000000000000000000000000000000010"
}
},
```
In the token exists in the token list but does not use the standard bridge, the `extensions.optimismBridgeAddress` value is different. For example, this entry shows that on the main Optimism network `DAI` uses a different bridge:
```json
{
"chainId": 10,
"address": "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
"name": "Dai stable coin",
"symbol": "DAI",
"decimals": 18,
"logoURI": "https://ethereum-optimism.github.io/data/DAI/logo.svg",
"extensions": {
"optimismBridgeAddress": "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65"
}
},
```
1. You can "ask" the L2 token contract by calling it.
ERC-20 tokens that can use the standard bridge not only if they:
- Have an `l2Bridge` method
- That method returns `0x4200...0010`.
For example, [this link](https://explorer.optimism.io/address/0xe0bb0d3de8c10976511e5030ca403dbf4c25165b#readContract) can be used to see that `0xBTC` uses the standard bridge.
Note that you cannot query the L1 token contract the same way.
L2 contracts know the identity of their L1 counterpart, but L1 contracts only need to implement the standard ERC-20 methods.
:::
### Depositing ETH
ETH deposits into L2 can be triggered via the `depositETH` and `depositETHTo` functions on the [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1StandardBridge.sol).
ETH deposits can alternatively be triggered by sending ETH directly to the `L1StandardBridge`.
Once your deposit is detected and finalized on Optimism, your account will be funded with the corresponding amount of ETH on L2.
## Withdrawals
### Withdrawing ERC20s
ERC20 withdrawals can be triggered via the `withdraw` or `withdrawTo` functions on the [`L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/L2StandardBridge.sol).
If you'd like to see this contracts in action, you should check out the [L1 ⇔ L2 deposit-and-withdraw example](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-bridge-erc20).
### Withdrawing ETH
Unlike on L1, we do not have a separate function on L2 for withdrawing ETH.
Instead, you can use the `withdraw` or `withdrawTo` functions on the [`L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/messaging/L2StandardBridge.sol) and use the address `0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000` as the L2 token address.
## Adding an ERC20 token to the Standard Bridge
::: tip
To add your token to the standard bridge, see the guide [Adding an ERC20 token to the Standard Bridge](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/standard-bridge-standard-token).
:::
## The Optimism token list
The Standard bridge allows a one-to-many mapping between L1 and L2 tokens, meaning that there can be many Optimism implementations of an L1 token.
However there is always a one-to-one mapping between L1 and L2 tokens in the [Optimism token list](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/optimism.tokenlist.json).
The token list is used as the source of truth for the [Optimism Gateway](https://gateway.optimism.io) which is the main portal for moving assets between Layer 1 and Layer 2.
If you want to have your token added to the token list, you must make a pull request against the [Optimism token list repository](https://github.com/ethereum-optimism/ethereum-optimism.github.io#adding-a-token-to-the-list).
You'll need the addresses for both the L1 and L2 tokens, as well as a logo for the token.
If you're looking for an example to follow, take a look at [this simple pull request that adds a token to the token list](https://github.com/ethereum-optimism/ethereum-optimism.github.io/pull/43/files).
---
title: Making Optimism Dapps Even Cheaper
lang: en-US
---
The cost of using a decentralized application in Optimism is much lower than the cost of the equivalent application on L1 Ethereum.
[See here](https://l2fees.info/) for the current values.
However, with proper optimization, we can make our decentralized applications even cheaper.
Here are some strategies.
## Background
This is a basic introduction into some of the concepts you need to understand to fully optimise your contracts in the Optimism L2 environment.
### What are the transaction fees?
The cost of an L2 transaction on Optimism is composed of two components:
- L2 execution fee, which is proportional to the gas actually used in processing the transaction.
Normally the cost of L2 gas is 0.001 gwei, but this may increase when the system is extremely congested.
Do not hardcode this value.
- L1 data fee, which is proportional to:
- The gas cost of writing the transaction's data to L1 (roughly equal to the transaction's length)
- The cost of gas on L1.
The cost of gas on L1 can be extremely volatile.
To view the current gas costs as a user, [see here](https://public-grafana.optimism.io/). To retrieve them programatically, [see here](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/sdk-estimate-gas).
For a more in depth look at how transaction fees are calculated see our [fee documentation](transaction-fees.md).
### Optimization tradeoffs
In almost all cases, the L1 data fee is the vast majority of the transaction's cost.
The L2 execution fee is, comparatively speaking, negligible.
This means that the optimization tradeoffs are very different in Optimism than they are in Ethereum.
Transaction call data is *expensive*.
The cost of writing a byte to L1 is approximately 16 gas.
At a cost of 45 gwei per L1 gas unit, writing one byte to L1 on Optimism costs 720 gwei, or 720,000 units of L2 gas (at the non-congested price of 0.001 gwei per L2 gas unit).
In comparison, on-chain processing and storage are cheap.
The worst case for writing to storage (previously uninitialized storage) is a cost of [22100 L2 gas per EVM word, which contains 32 bytes of data](https://www.evm.codes/#55), which averages out to less than 700 L2 gas / byte.
At a cost of 45 gwei per L1 gas unit, this means it is cheaper to write a whole kilobyte to storage, rather than add one byte to the transaction call data.
## Modify the [ABI (application binary interface)](https://docs.soliditylang.org/en/latest/abi-spec.html)
[The standard ABI](https://docs.soliditylang.org/en/latest/abi-spec.html) was designed with L1 tradeoffs in mind.
It uses four byte function selectors and pads values to a 32 byte size.
Neither is optimal when using Optimism.
It is much more efficient to [create a shorter ABI with just the required bytes, and decode it onchain](https://ethereum.org/en/developers/tutorials/short-abi/).
All of your [`view`](https://docs.soliditylang.org/en/latest/contracts.html#view-functions) and [`pure`](https://docs.soliditylang.org/en/latest/contracts.html#pure-functions) functions can use the standard ABI at no cost.
## Use smaller values when possible
Your modified ABI is not going to pad values, so the less bytes you use the better.
For example, it is standard to use `uint256` for amounts.
This means that the highest number we can represent is 2<sup>256</sup>-1, or about 1.2*10<sup>77</sup>.
When storing ETH balances, for example, using `uint256` is overkill as there are only [120 million ETH](https://ycharts.com/indicators/ethereum_supply). Thus, we can safely store ETH balances in `uint88` which is just eleven bytes.
Go through your contracts and identify any values that will never reach 32 bytes and reduce them to logical sizes. You can do this same process for ints, bytes and [other Solidity data types](https://docs.soliditylang.org/en/develop/types.html#types).
---
title: Running a local development environment
lang: en-US
---
## What is this?
A development environment is a local installation of the entire Optimism system.
Our default development environment includes both L1 and L2 development nodes.
Running the Optimism environment locally is a great way to test your code and see how your contracts will behave on Optimism before you graduate to a testnet deployment (and eventually a mainnet deployment).
Alternatively, you can get a hosted development node from [Alchemy](https://www.alchemy.com/optimism) or [any of these providers](../../useful-tools/providers.md).
## Do I need this?
We generally recommend using the local development environment if your application falls into one of the following categories:
1. **You're building contracts on both Optimism and Ethereum that need to interact with one another.** The local development environment is a great way to quickly test interactions between L1 and L2. The Optimism testnet and mainnet environments both have a communication delay between L1 and L2 that can make testing slow during the early stages of development.
2. **You're building an application that might be subject to one of the few [differences between Ethereum and Optimism](./differences.md).** Although Optimism is [EVM equivalent](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), it's not exactly the same as Ethereum. If you're building an application that might be subject to one of these differences, you should use the local development environment to double check that everything is running as expected. You might otherwise have unexpected issues when you move to testnet. We strongly recommend reviewing these differences carefully to see if you might fall into this category.
However, not everyone will need to use the local development environment.
Optimism is [EVM equivalent](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), which means that Optimism looks almost exactly like Ethereum under the hood.
If you don't fall into one of the above categories, you can probably get away with simply relying on existing testing tools like Truffle or Hardhat.
If you don't know whether or not you should be using the development environment, feel free to hop into the [Optimism discord](https://discord-gateway.optimism.io).
Someone nice will help you out!
## What does it include?
Everything you need to test your Optimistic application:
1. An L1 (Ethereum) node available at [http://localhost:9545](http://localhost:9545).
1. An L2 (Optimism) node available at [http://localhost:8545](http://localhost:8545).
1. All of the Optimism contracts and services that make L1 ⇔ L2 communication possible.
1. Accounts with lots of ETH on both L1 and L2.
## Prerequisites
You'll need to have the following installed:
1. [Docker](https://www.docker.com/). these directions were verified with version 20.10.17
To compile the software on your own you also need:
1. [Node.js](https://nodejs.org/en/), version 12 or later
1. [Classic Yarn](https://classic.yarnpkg.com/lang/en/)
## Setting up the environment
We use [Docker](https://www.docker.com) to run our development environment.
On a Linux system you can get the appropriate versions using these steps:
1. Install Docker.
If you prefer not to use the convenience script shown below, there are other installation methods.
```sh
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
```
1. Configure Docker permissions.
Note that these permissions do not take effect until you log in again, so you need to open a new command line window.
```sh
sudo usermod -a -G docker `whoami`
```
::: tip There is no need to install docker-compose anymore
It is now available on Docker itself as `docker compose`
:::
## Getting the software
You can set up your development environment either by downloading the required software from [Docker Hub](https://hub.docker.com/u/ethereumoptimism) or by building the software from the [source code](https://github.com/ethereum-optimism/optimism).
Downloading images from Docker Hub is easier and more reliable and is the recommended solution.
### Downloading from Docker Hub
1. Clone and enter the [Optimism monorepo](https://github.com/ethereum-optimism/optimism):
```sh
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
```
2. Move into the `ops` directory:
```sh
cd ops
```
3. Download the images:
```sh
docker compose pull
```
4. Wait for the download to complete. This can take a while.
Depending on your machine, this startup process may take some time and it can be unclear when the system is fully ready.
## Accessing the environment
The local development environment consists of both an L1 node and an L2 node.
You can interact with these nodes at the following ports:
- L1 (Ethereum) node: [http://localhost:9545](http://localhost:9545)
- L2 (Optimism) node: [http://localhost:8545](http://localhost:8545)
## Getting ETH for transactions
::: warning WARNING
The private keys for the accounts used within the local development environment are **PUBLICLY KNOWN**.
Any funds sent to these accounts on a live network (Ethereum, Optimism, or any other public network) **WILL BE LOST**.
:::
All accounts that are funded by default within [Hardhat](https://hardhat.org) are funded with 5000 ETH on both L1 and L2.
These accounts are derived from the following mnemonic:
```
test test test test test test test test test test test junk
```
Here's the full list of accounts and their corresponding private keys:
```
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
Account #2: 0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc
Private Key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
Account #3: 0x90f79bf6eb2c4f870365e785982e1f101e93b906
Private Key: 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
Account #4: 0x15d34aaf54267db7d7c367839aaf71a00a2c6a65
Private Key: 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a
Account #5: 0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc
Private Key: 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba
Account #6: 0x976ea74026e726554db657fa54763abd0c3a0aa9
Private Key: 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
Account #7: 0x14dc79964da2c08b23698b3d3cc7ca32193d9955
Private Key: 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
Account #8: 0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f
Private Key: 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97
Account #9: 0xa0ee7a142d267c1f36714e4a8f75612f20a79720
Private Key: 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
Account #10: 0xbcd4042de499d14e55001ccbb24a551f3b954096
Private Key: 0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897
Account #11: 0x71be63f3384f5fb98995898a86b02fb2426c5788
Private Key: 0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82
Account #12: 0xfabb0ac9d68b0b445fb7357272ff202c5651694a
Private Key: 0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1
Account #13: 0x1cbd3b2770909d4e10f157cabc84c7264073c9ec
Private Key: 0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd
Account #14: 0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097
Private Key: 0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa
Account #15: 0xcd3b766ccdd6ae721141f452c550ca635964ce71
Private Key: 0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61
Account #16: 0x2546bcd3c84621e976d8185a91a922ae77ecec30
Private Key: 0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0
Account #17: 0xbda5747bfd65f08deb54cb465eb87d40e51b197e
Private Key: 0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd
Account #18: 0xdd2fd4581271e230360230f9337d5c0430bf44c0
Private Key: 0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0
Account #19: 0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199
Private Key: 0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e
```
## Accessing logs
The logs produced by the L1 and L2 nodes can sometimes be useful for debugging particularly hairy bugs.
Logs will appear in the terminal you used to start the development environment, but they often scroll too quickly to be of much use.
If you'd like to look at the logs for a specific container, you'll first need to know the name of the container you want to inspect.
Run the following command to get the name of all running containers:
```sh
docker ps -a --format '{{.Image}}\t\t\t{{.Names}}' | grep ethereumoptimism
```
The output includes two columns.
The first is the name of the **image**, and the second the name of the **container** based on that image.
Each container is essentially an instance of a particular image and you're looking for the name of the *container* that you want to inspect.
| Image | Container |
| - | - |
| ethereumoptimism/l2geth:latest | ops_verifier_1
| ethereumoptimism/l2geth:latest | ops_replica_1
| ethereumoptimism/data-transport-layer:latest | ops_dtl_1
| ethereumoptimism/batch-submitter-service:latest | ops_batch_submitter_1
| ethereumoptimism/l2geth:latest | ops_l2geth_1
| ethereumoptimism/deployer:latest | ops_deployer_1
| ethereumoptimism/hardhat:latest | ops_l1_chain_1
You can then dump the logs for a given container as follows:
```sh
docker logs <container name>
```
For example, to see the logs produced by the L1 node:
```sh
docker logs ops_l1_chain_1
```
If you'd like to follow these logs as they're being generated, run:
```sh
docker logs --follow <name of container>
```
## Getting Optimism system contract addresses
If you want to [interact with Optimism system contracts](./system-contracts.md), you'll need to know the addresses of the contracts that are deployed on the network.
### Getting L2 contract addresses
L2 contracts are always deployed to the same addresses on every Optimism network.
You can simply look at [the L2 contract addresses for the mainnet Optimism network](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments/mainnet#layer-2-contracts) and you'll have the addresses for your local environment.
### Getting L1 contract addresses
Optimism's L1 contracts are deployed to different addresses on different networks.
However, the addresses for your local environment will always be the same, even if you reset the environment.
You can get the addresses for your environment with the following command:
```sh
curl http://localhost:8080/addresses.json
```
You should get back a JSON object that contains a mapping of contract names to contract addresses.
These addresses should not change, even if you restart your environment.
---
title: Differences between Ethereum and Optimism
lang: en-US
---
It's important to note that there are various minor discrepancies between the behavior of Optimism and Ethereum.
You should be aware of these descrepancies when building apps on top of Optimism.
## Opcode Differences
### Modified Opcodes
| Opcode | Solidity equivalent | Behavior |
| - | - | - |
| `COINBASE` | `block.coinbase` | Value is set by the sequencer. Currently returns the `OVM_SequencerFeeVault` address (`0x420...011`). |
| `DIFFICULTY` | `block.difficulty` | Always returns zero. [You can use an oracle for randomness](../../useful-tools/oracles.md#verifiable-randomness-function-vrf). |
| `BASEFEE` | `block.basefee` | Currently unsupported. |
| `ORIGIN` | `tx.origin` | If the transaction is an L1 ⇒ L2 transaction, then `tx.origin` is set to the [aliased address](#address-aliasing) of the address that triggered the L1 ⇒ L2 transaction. Otherwise, this opcode behaves normally. |
### Added Opcodes
| Opcode | Behavior |
| - | - |
| `L1BLOCKNUMBER` | Returns the block number of the last L1 block known by the L2 system. Typically this block number will lag by up to 15 minutes behind the actual latest L1 block number. See section on [Block Numbers and Timestamps](#block-numbers-and-timestamps) for more information. |
## Block Numbers and Timestamps
### Block production is not constant
On Ethereum, the `NUMBER` opcode (`block.number` in Solidity) corresponds to the current Ethereum block number.
Similarly, in Optimism, `block.number` corresponds to the current L2 block number.
However, as of the OVM 2.0 release of Optimism (Nov. 2021), **each transaction on L2 is placed in a separate block and blocks are NOT produced at a constant rate.**
This is important because it means that `block.number` is currently NOT a reliable source of timing information.
If you want access to the current time, you should use `block.timestamp` (the `TIMESTAMP` opcode) instead.
### Timestamps
The `TIMESTAMP` opcode (`block.timestamp` in Solidity) uses the timestamp of the transaction itself. It gets updated every fifteen seconds.
### Accessing the latest L1 block number
::: warning NOTICE
The hex value that corresponds to the `L1BLOCKNUMBER` opcode (`0x4B`) may be changed in the future (pending further discussion).
**We strongly discourage direct use of this opcode within your contracts.**
Instead, if you want to access the latest L1 block number, please use the `OVM_L1BlockNumber` contract as described below.
:::
The block number of the latest L1 block seen by the L2 system can be accessed via the `L1BLOCKNUMBER` opcode.
Solidity doesn't make it easy to use non-standard opcodes, so we've created a simple contract located at [`0x4200000000000000000000000000000000000013`](https://explorer.optimism.io/address/0x4200000000000000000000000000000000000013) that will allow you to trigger this opcode.
You can use this contract as follows:
```solidity
import { iOVM_L1BlockNumber } from "@eth-optimism/contracts/L2/predeploys/iOVM_L1BlockNumber.sol";
import { Lib_PredeployAddresses } from "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol";
contract MyContract {
function myFunction() public {
// ... your code here ...
uint256 l1BlockNumber = iOVM_L1BlockNumber(
Lib_PredeployAddresses.L1_BLOCK_NUMBER // located at 0x4200000000000000000000000000000000000013
).getL1BlockNumber();
// ... your code here ...
}
}
```
## Using ETH in Contracts
As of the OVM 2.0 update (Nov. 2021), **the process of using ETH on L2 is identical to the process of using ETH in Ethereum.**
Please note that ETH was previously accessible as an ERC20 token, but this feature has been removed as part of OVM 2.0.
For tooling developers and infrastructure providers, please note that ETH is still represented internally as an ERC20 token at the address [`0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000`](https://explorer.optimism.io/address/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000).
As a result, user balances will always be zero inside the state trie and the user's actual balance will be stored in the aforementioned token's storage.
**It is NOT possible to call this contract directly, it will throw an error.**
## Address Aliasing
Because of the behavior of the `CREATE` opcode, it is possible for a user to create a contract on L1 and on L2 that share the same address but have different bytecode.
This can break trust assumptions, because one contract may be trusted and another be untrusted (see below).
To prevent this problem the behavior of the `ORIGIN` and `CALLER` opcodes (`tx.origin` and `msg.sender`) differs slightly between L1 and L2.
The value of `tx.origin` is determined as follows:
| Call source | `tx.origin` |
| ---------------------------------- | ------------------------------------------ |
| L2 user (Externally Owned Account) | The user's address (same as in Ethereum) |
| L1 user (Externally Owned Account) | The user's address (same as in Ethereum) |
| L1 contract (using `CanonicalTransactionChain.enqueue`) | `L1_contract_address + 0x1111000000000000000000000000000000001111` |
The value of `msg.sender` at the top-level (the very first contract being called) is always equal to `tx.origin`.
Therefore, if the value of `tx.origin` is affected by the rules defined above, the top-level value of `msg.sender` will also be impacted.
Note that in general, [`tx.origin` should *not* be used for authorization](https://docs.soliditylang.org/en/latest/security-considerations.html#tx-origin).
However, that is a separate issue from address aliasing because address aliasing also affects `msg.sender`.
### Why is address aliasing an issue?
The problem with two identical source addresses (the L1 contract and the L2 contract) is that we extend trust based on the address.
It is possible that we will want to trust one of the contracts, but not the other.
1. Helena Hacker forks [Uniswap](https://uniswap.org/) to create her own exchange (on L2), called Hackswap.
**Note:** There are actually multiple contracts in Uniswap, so this explanation is a bit simplified.
[See here if you want additional details](https://ethereum.org/en/developers/tutorials/uniswap-v2-annotated-code/).
1. Helena Hacker provides Hackswap with liquidity that appears to provide profitable arbitrage opportunities.
For example, she can make it so that you can spend 1 [DAI](https://www.coindesk.com/price/dai/)to buy 1.1 [USDT](https://www.coindesk.com/price/tether/).
Both of those coins are supposed to be worth exactly $1.
1. Nimrod Naive knows that if something looks too good to be true it probably is.
However, he checks the Hackswap contract's bytecode and verifies it is 100% identical to Uniswap.
He decides this means the contract can be trusted to behave exactly as Uniswap does.
1. Nimrod approves an allowance of 1000 DAI for the Hackswap contract.
Nimrod expects to call the swap function on Hackswap and receive back nearly 1100 USDT.
1. Before Nimrod's swap transaction is sent to the blockchain, Helena Hacker sends a transaction from an L1 contract with the same address as Hackswap.
This transaction transfers 1000 DAI from Nimrod's address to Helena Hacker's address.
If this transaction were to come from the same address as Hackswap on L2, it would be able to transfer the 1000 DAI because of the allowance Nimrod *had* to give Hackswap in the previous step to swap tokens.
Nimrod, despite his naivete, is protected because Optimism modified the transaction's `tx.origin` (which is also the initial `msg.sender`).
That transaction comes from a *different* address, one that does not have the allowance.
**Note:** It is simple to create two different contracts on the same address in different chains.
But it is nearly impossible to create two that are different by a specified amount, so Helena Hacker can't do that.
## Network specifications
### JSON-RPC differences
Optimism uses the same [JSON-RPC API](https://eth.wiki/json-rpc/API) as Ethereum.
Some additional Optimism specific methods have been introduced.
See the full list of [custom JSON-RPC methods](./json-rpc.md) for more information.
### Pre-EIP-155 support
[Pre-EIP-155](https://eips.ethereum.org/EIPS/eip-155) transactions do not have a chain ID, which means a transaction on one Ethereum blockchain can be replayed on others.
This is a security risk, because transactions that are legitimate on one chain could be a security risk on another.
For example, you might agree to send me 1 ETH on Goerli (chain ID 5) to help me test my contracts.
If you submit the transaction as a pre-EIP-155 transaction, then I can wait until your address's nonce on mainnet (chain ID 1) is the same as the one you had when you submitted the Goerli transaction and send the transaction to mainnet.
Mainnet would then interpret it as a legitimate transaction and transfer a *real* ETH from your account to mine (assuming your balance is high enough, of course)
Starting in November 2022, pre-EIP-155 transactions are no longer supported on Optimism using the public endpoint or through Alchemy.
::: warning Pre-EIP-155 transactions are dangerous
It is highly recommended not to use pre-eip-155 transaction.
But if you absolutely must use them, [Infura](../../useful-tools/providers.md#infura) and [QuickNode](../../useful-tools/providers.md#quicknode) still support them.
Just be careful.
:::
# Bedrock
In [the Bedrock version](../bedrock/how-is-bedrock-different.md) there are even less differences between Optimism and L1 Ethereum.
## Opcode Differences
| Opcode | Solidity equivalent | Behavior |
| - | - | - |
| `COINBASE` | `block.coinbase` | Undefined |
| `DIFFICULTY` | `block.difficulty` | Random value. As this value is set by the sequencer, it is not as reliably random as the L1 equivalent. [You can use an oracle for randomness](../../useful-tools/oracles.md#verifiable-randomness-function-vrf). |
| `NUMBER` | `block.number` | L2 block number
| `TIMESTAMP` | `block.timestamp` | Timestamp of the L2 block
| `ORIGIN` | `tx.origin` | If the transaction is an L1 ⇒ L2 transaction, then `tx.origin` is set to the [aliased address](#address-aliasing) of the address that triggered the L1 ⇒ L2 transaction. Otherwise, this opcode behaves normally. |
| `CALLER` | `msg.sender` | If the transaction is an L1 ⇒ L2 transaction, and this is the initial call (rather than an internal transaction from one contract to another), the same [address aliasing](#address-aliasing) behavior applies.
::: tip `tx.origin == msg.sender`
On L1 Ethereum `tx.origin` is equal to `msg.sender` only when the smart contract was called directly from an externally owned account (EOA).
However, on Optimism `tx.origin` is the origin *on Optimism*.
It could be an EOA.
However, in the case of messages from L1, it is possible for a message from a smart contract on L1 to appear on L2 with `tx.origin == msg.origin`.
This is unlikely to make a significant difference, because an L1 smart contract cannot directly manipulate the L2 state.
However, there could be edge cases we did not think about where this matters.
:::
### Accessing L1 information
If you need the equivalent information from the latest L1 block, you can get it from [the `L1Block` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1Block.sol).
This contract is a predeploy at address [`0x4200000000000000000000000000000000000015`](https://goerli-optimism.etherscan.io/address/0x4200000000000000000000000000000000000015).
You can use [the getter functions](https://docs.soliditylang.org/en/v0.8.12/contracts.html#getter-functions) to get these parameters:
- `number`: The latest L1 block number known to L2
- `timestamp`: The timestamp of the latest L1 block
- `basefee`: The base fee of the latest L1 block
- `hash`: The hash of the latest L1 block
- `sequenceNumber`: The number of the L2 block within the epoch (the epoch changes when there is a new L1 block)
### Address Aliasing
<details>
Because of the behavior of the `CREATE` opcode, it is possible for a user to create a contract on L1 and on L2 that share the same address but have different bytecode.
This can break trust assumptions, because one contract may be trusted and another be untrusted (see below).
To prevent this problem the behavior of the `ORIGIN` and `CALLER` opcodes (`tx.origin` and `msg.sender`) differs slightly between L1 and L2.
The value of `tx.origin` is determined as follows:
| Call source | `tx.origin` |
| ---------------------------------- | ------------------------------------------ |
| L2 user (Externally Owned Account) | The user's address (same as in Ethereum) |
| L1 user (Externally Owned Account) | The user's address (same as in Ethereum) |
| L1 contract (using `CanonicalTransactionChain.enqueue`) | `L1_contract_address + 0x1111000000000000000000000000000000001111` |
The value of `msg.sender` at the top-level (the very first contract being called) is always equal to `tx.origin`.
Therefore, if the value of `tx.origin` is affected by the rules defined above, the top-level value of `msg.sender` will also be impacted.
Note that in general, [`tx.origin` should *not* be used for authorization](https://docs.soliditylang.org/en/latest/security-considerations.html#tx-origin).
However, that is a separate issue from address aliasing because address aliasing also affects `msg.sender`.
#### Why is address aliasing an issue?
The problem with two identical source addresses (the L1 contract and the L2 contract) is that we extend trust based on the address.
It is possible that we will want to trust one of the contracts, but not the other.
1. Helena Hacker forks [Uniswap](https://uniswap.org/) to create her own exchange (on L2), called Hackswap.
**Note:** There are actually multiple contracts in Uniswap, so this explanation is a bit simplified.
[See here if you want additional details](https://ethereum.org/en/developers/tutorials/uniswap-v2-annotated-code/).
1. Helena Hacker provides Hackswap with liquidity that appears to allow for profitable arbitrage opportunities.
For example, she can make it so that you can spend 1 [DAI](https://www.coindesk.com/price/dai/)to buy 1.1 [USDT](https://www.coindesk.com/price/tether/).
Both of those coins are supposed to be worth exactly $1.
1. Nimrod Naive knows that if something looks too good to be true it probably is.
However, he checks the Hackswap contract's bytecode and verifies it is 100% identical to Uniswap.
He decides this means the contract can be trusted to behave exactly as Uniswap does.
1. Nimrod approves an allowance of 1000 DAI for the Hackswap contract.
Nimrod expects to call the swap function on Hackswap and receive back nearly 1100 USDT.
1. Before Nimrod's swap transaction is sent to the blockchain, Helena Hacker sends a transaction from an L1 contract with the same address as Hackswap.
This transaction transfers 1000 DAI from Nimrod's address to Helena Hacker's address.
If this transaction were to come from the same address as Hackswap on L2, it would be able to transfer the 1000 DAI because of the allowance Nimrod *had* to give Hackswap in the previous step to swap tokens.
Nimrod, despite his naivete, is protected because Optimism modified the transaction's `tx.origin` (which is also the initial `msg.sender`).
That transaction comes from a *different* address, one that does not have the allowance.
**Note:** It is simple to create two different contracts on the same address in different chains.
But it is nearly impossible to create two that are different by a specified amount, so Helena Hacker can't do that.
</details>
## Blocks
There are several differences in the way blocks are produced between L1 Ethereum and Optimism Bedrock.
| Parameter | L1 Ethereum | Optimism Bedrock |
| - | - | - |
| Time between blocks | 12 seconds(1) | 2 seconds |
| Block target size | 15,000,000 gas | to be determined |
| Block maximum size | 30,000,000 gas | to be determined |
(1) This is the ideal.
If any blocks are missed it could be an integer multiple such as 24 seconds, 36 seconds, etc.
**Note:** The L1 Ethereum parameter values are taken from [ethereum.org](https://ethereum.org/en/developers/docs/blocks/#block-time). The Optimism Bedrock values are taken from [the Optimism specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/guaranteed-gas-market.md#limiting-guaranteed-gas).
## Network specifications
### JSON-RPC differences
Optimism uses the same [JSON-RPC API](https://eth.wiki/json-rpc/API) as Ethereum.
Some additional Optimism specific methods have been introduced.
See the full list of [custom JSON-RPC methods](./json-rpc.md) for more information.
### Pre-EIP-155 support
[Pre-EIP-155](https://eips.ethereum.org/EIPS/eip-155) transactions do not have a chain ID, which means a transaction on one Ethereum blockchain can be replayed on others.
This is a security risk.
Starting in November 2022, pre-EIP-155 transactions are no longer supported on Optimism.
## Transaction costs
[Transaction costs on Optimism](./transaction-fees.md) include an [L2 execution fee](./transaction-fees.md#the-l2-execution-fee) and an [L1 data fee](./transaction-fees.md#the-l1-data-fee).
## Contract addresses
The addresses in which various infrastructure contracts are installed are different between L1 Ethereum and Optimism.
For example, [WETH9](https://github.com/gnosis/canonical-weth/blob/master/contracts/WETH9.sol) is installed on L1 Ethereum on [address `0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2`](https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2).
On Optimism the same contract is installed on [address `0x4200000000000000000000000000000000000006`](https://explorer.optimism.io/address/0x4200000000000000000000000000000000000006).
---
title: JSON-RPC API
lang: en-US
---
<details>
<summary><b>Pre-bedrock (current version)</b></summary>
Optimism shares the same [JSON-RPC API](https://eth.wiki/json-rpc/API) as Ethereum.
Some custom methods have been introduced to simplify certain Optimism specific interactions.
## Custom JSON-RPC Methods
### `eth_getBlockRange`
Like `eth_getBlockByNumber` but accepts a range of block numbers instead of just a single block.
**Parameters**
1. `QUANTITY|TAG` - integer of the starting block number for the range, or the string `"earliest"``"latest"` or `"pending"`, as in the [default block parameter](https://eth.wiki/json-rpc/API#the-default-block-parameter).
2. `QUANTITY|TAG` - integer of the ending block number for the range, or the string `"earliest"``"latest"` or `"pending"`, as in the [default block parameter](https://eth.wiki/json-rpc/API#the-default-block-parameter).
3. `BOOLEAN` - If `true` it returns the full transaction objects, if `false` only the hashes of the transactions.
**Returns**
An array of `block` objects.
See [`eth_getBlockByHash`](https://eth.wiki/json-rpc/API#eth_getblockbyhash) for the structure of a `block` object.
**Example**
```json
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockRange","params":["0x1", "0x2", false],"id":1}' <node url>
// Result
{
"jsonrpc":"2.0",
"id":1,
"result":[
{
"difficulty":"0x2",
"extraData":"0xd98301090a846765746889676f312e31352e3133856c696e75780000000000009c3827892825f0825a7e329b6913b84c9e4f89168350aff0939e0e6609629f2e7f07f2aeb62acbf4b16a739cab68866f4880ea406583a4b28a59d4f55dc2314e00",
"gasLimit":"0xe4e1c0",
"gasUsed":"0x3183d",
"hash":"0xbee7192e575af30420cae0c7776304ac196077ee72b048970549e4f08e875453",
"logsBloom":"0x00000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000100000000000000200000000002000000000000001000000000000000000004000000000000000000000000000040000400000100400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000001000000000000000000000100000000000000000000000000000000000000000000000000000000000000088000000080000000000010000000000000000000000000000800008000120000000000000000000000000000000002000",
"miner":"0x0000000000000000000000000000000000000000",
"mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce":"0x0000000000000000",
"number":"0x1",
"parentHash":"0x7ca38a1916c42007829c55e69d3e9a73265554b586a499015373241b8a3fa48b",
"receiptsRoot":"0xf4c97b1186b690ad3318f907c0cdaf46f4598f27f711a5609064b2690a767287",
"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size":"0x30c",
"stateRoot":"0xd3ac40854cd2ac17d8effeae6065cea990b04be714f7061544973feeb2f1c95f",
"timestamp":"0x618d8837",
"totalDifficulty":"0x3",
"transactions":["0x5e77a04531c7c107af1882d76cbff9486d0a9aa53701c30888509d4f5f2b003a"],
"transactionsRoot":"0x19f5efd0d94386e72fcb3f296f1cb2936d017c37487982f76f09c591129f561f",
"uncles":[]
},
{
"difficulty":"0x2",
"extraData":"0xd98301090a846765746889676f312e31352e3133856c696e757800000000000064a82cb66c7810b9619e7f14ab65c769a828b1616974987c530684eb3870b65e5b2400c1b61c6d340beef8c8e99127ac0de50e479d21f0833a5e2910fe64b41801",
"gasLimit":"0xe4e1c0",
"gasUsed":"0x1c60d",
"hash":"0x45fd6ce41bb8ebb2bccdaa92dd1619e287704cb07722039901a7eba63dea1d13",
"logsBloom":"0x00080000000200000000000000000008000000000000000000000100008000000000000000000000000000000000000000000000000000000000400000000000100000000000000000000000020000000000000000000000000000000000004000000000000000000000000000000000400000000400000000000000100000000000000000000000000000020000000000000000000000000000000000000000100000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000008400000000000000000010000000000000000020000000020000000000000000000000000000000000000000000002000",
"miner":"0x0000000000000000000000000000000000000000",
"mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce":"0x0000000000000000",
"number":"0x2",
"parentHash":"0xbee7192e575af30420cae0c7776304ac196077ee72b048970549e4f08e875453",
"receiptsRoot":"0x2057c8fb79c0f294062c1436aa56741134dc46d228a4f874929f8b791a7007a4",
"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size":"0x30c",
"stateRoot":"0x87026f3a614318ae24bcef6bc8f7564479afbbbe2b1fb189bc133a5de5a2b0f8",
"timestamp":"0x618d8837",
"totalDifficulty":"0x5",
"transactions":["0xaf6ed8a6864d44989adc47c84f6fe0aeb1819817505c42cde6cbbcd5e14dd317"],
"transactionsRoot":"0xa39c4d0d2397f8fcb1683ba833d4ab935cd2f4c5ca6f56a7d9a45b9904ea1c69",
"uncles":[]
}
]
}
```
---
### `rollup_getInfo`
Returns useful L2-specific information about the current node.
**Parameters**
None
**Returns**
`Object`
- `mode`: `STRING` - `"sequencer"` or `"verifier"` depending on the node's mode of operation
- `syncing`: `BOOLEAN` - `true` if the node is currently syncing, `false` otherwise
- `ethContext`: `OBJECT`
- `blockNumber`: `QUANTITY` - Block number of the latest known L1 block
- `timestamp`: `QUANTITY` - Timestamp of the latest known L1 block
- `rollupContext`: `OBJECT`
- `queueIndex`: `QUANTITY` - Index within the CTC of the last L1 to L2 message ingested
- `index`: `QUANTITY` - Index of the last L2 tx processed
- `verifiedIndex`: `QUANTITY` - Index of the last tx that was ingested from a batch that was posted to L1
**Example**
```json
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"rollup_getInfo","params":[],"id":1}' <node url>
// Result
{
"jsonrpc":"2.0",
"id":1,
"result":{
"mode":"verifier",
"syncing":false,
"ethContext":{
"blockNumber":13679735,
"timestamp":1637791660
},
"rollupContext":{
"index":430948,
"queueIndex":12481,
"verifiedIndex":0
}
}
}
```
---
### `rollup_gasPrices`
Returns the L1 and L2 gas prices that are being used by the Sequencer to calculate fees.
**Parameters**
None
**Returns**
`Object`
- `l1GasPrice`: `QUANTITY` - L1 gas price in wei that the Sequencer will use to estimate the L1 portion of fees (calldata costs).
- `l2GasPrice`: `QUANTITY` - L2 gas price in wei that the Sequencer will use to estimate the L2 portion of fees (execution costs).
**Example**
```json
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"rollup_gasPrices","params":[],"id":1}' <node url>
// Result
{
"jsonrpc":"2.0",
"id":1,
"result":{
"l1GasPrice":"0x237aa50984",
"l2GasPrice":"0xf4240"
}
}
```
---
## Unsupported JSON-RPC methods
### `eth_getAccounts`
This method is used to retrieve a list of addresses owned by a user.
Optimism nodes do not expose internal wallets for security reasons and therefore block the `eth_getAccounts` method.
You should use external wallet software as an alternative.
### `eth_sendTransaction`
Optimism nodes also block the `eth_sendTransaction` method for the same reasons as `eth_getAccounts`.
You should use external wallet software as an alternative.
Please note that this is not the same as the `eth_sendRawTransaction` method, which accepts a signed transaction as an input.
`eth_sendRawTransaction` _is_ supported by Optimism.
</details>
<details>
<summary><b>Bedrock (coming Q1 2023)</b></summary>
There are several bedrock components with an RPC API:
## Rollup node (op-node)
[*Rollup node*](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md) refers to the component in the protocol specifications.
The Optimism implementation is called *op-node*.
The `op-node` component implements several RPC methods:
### `optimism_outputAtBlock`
Get the output root at a specific block.
This method is documented in [the specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md#output-method-api).
```sh
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"optimism_outputAtBlock","params":["latest"],"id":1}' \
http://localhost:9545
```
Sample output:
```json
{
"jsonrpc":"2.0",
"id":1,
"result":[
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0xabe711e34c1387c8c56d0def8ce77e454d6a0bfd26cef2396626202238442421"
]
}
```
### `optimism_syncStatus`
Get the synchronization status.
```sh
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' \
http://localhost:9545
```
Sample output:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"current_l1": {
"hash": "0x5adcfcbd1c2fcf9e06bfdaa8414a4586f84e11f487396abca940299eb0ed2da5",
"number": 7569281,
"parentHash": "0xfd022ca8a8c4e0f3bfd67081c18551840ea0717cc01d9a94601e1e41e92616d3",
"timestamp": 1662862860
},
"head_l1": {
"hash": "0x5c12fde5ea79aefe4b52c0c8cc0e0eb33a2ccb423cb3cd9c9132e18ad42e89b6",
"number": 8042823,
"parentHash": "0x74818f8ecaa932431bf9523e929dcfa11ab382c752529d8271a24810884a2551",
"timestamp": 1669735356
},
"safe_l1": {
"hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": 0,
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": 0
},
"finalized_l1": {
"hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"number": 0,
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": 0
},
"unsafe_l2": {
"hash": "0x1cad05886ec0e2cda728674e00eadcbb9245ff34c0bfd86c866673a615c1c43a",
"number": 1752,
"parentHash": "0x0115dbbd26aaf9563d7e3cad65bad41926d94b2643ccb080f71e394c2c3d62a3",
"timestamp": 1662861300,
"l1origin": {
"hash": "0x43fe1601041056e9a2a5dabaa20715518ae0058abf67a69f5ebdd53b1f6ff02f",
"number": 7569162
},
"sequenceNumber": 0
},
"safe_l2": {
"hash": "0x1cad05886ec0e2cda728674e00eadcbb9245ff34c0bfd86c866673a615c1c43a",
"number": 1752,
"parentHash": "0x0115dbbd26aaf9563d7e3cad65bad41926d94b2643ccb080f71e394c2c3d62a3",
"timestamp": 1662861300,
"l1origin": {
"hash": "0x43fe1601041056e9a2a5dabaa20715518ae0058abf67a69f5ebdd53b1f6ff02f",
"number": 7569162
},
"sequenceNumber": 0
},
"finalized_l2": {
"hash": "0x6758307d692d4f2f6650acd3762674749a0c1cc2530b9b481845d0f8ee1bd456",
"number": 0,
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp": 1662857796,
"l1origin": {
"hash": "0xb0bbb79a00fb8485185b1bedfac386812d662e1cddba77b67a26e1ed9ba8f0ec",
"number": 7568910
},
"sequenceNumber": 0
}
}
}
```
### `optimism_rollupConfig`
Get the rollup configuration parameters.
```sh
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"optimism_rollupConfig","params":[],"id":1}' \
http://localhost:9545
```
Sample output:
```json
{
"jsonrpc":"2.0",
"id":1,
"result":{
"genesis":{
"l1":{
"hash":"0xb0bbb79a00fb8485185b1bedfac386812d662e1cddba77b67a26e1ed9ba8f0ec",
"number":7568910
},
"l2":{
"hash":"0x6758307d692d4f2f6650acd3762674749a0c1cc2530b9b481845d0f8ee1bd456",
"number":0
},
"l2_time":1662857796
},
"block_time":2,
"max_sequencer_drift":120,
"seq_window_size":120,
"channel_timeout":30,
"l1_chain_id":5,
"l2_chain_id":28528,
"p2p_sequencer_address":"0x59dc8e68a80833cc8a9592d532fed42374c8b5dc",
"fee_recipient_address":"0xdffc6a1c238ff9504b055ad7efeee0148f2d62bd",
"batch_inbox_address":"0xfeb2acb903f95fb5f5497157c0727a7d16e3fd16",
"batch_sender_address":"0x4ff79526ea1d492a3db2aa210d7318ff13f2012c",
"deposit_contract_address":"0xa581ca3353db73115c4625ffc7adf5db379434a8"
}
}
```
### `optimism_version`
Get the software version.
```sh
curl -X POST -H "Content-Type: application/json" \
'--data '{"jsonrpc":"2.0","method":"optimism_version","params":[],"id":1}' \
http://localhost:9545
```
Sample output:
```json
{
"jsonrpc":"2.0",
"id":1,
"result":"v0.0.0-"
}
```
### Peer to peer synchronization
Optionally, the rollup node can provide [peer to peer synchronization](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node-p2p.md) to provide pending L2 blocks to other rollup nodes.
## Execution engine (op-geth)
[*Execution engine*](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md) refers to the component in the protocol specifications.
The Optimism implementation is called *op-geth*.
The execution engine's RPC interface is identical to [the upstream Geth RPC interface](https://geth.ethereum.org/docs/rpc/server). This includes the ability to provide [snap sync](https://github.com/ethereum/devp2p/blob/master/caps/snap.md) functionality to other execution engines.
The responses are nearly identical too, except we also include the L1 gas usage and price information.
## Daisy chain
The daisy chain is a proxy that distributes requests either to the execution engine (if related to post-Bedrock blocks), or the legacy geth (if related to blocks prior to bedrock).
It accepts [the interface used by L1 execution engines](https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/execution-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=false&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false).
## Legacy geth
The legacy geth provides information about the blockchain prior to Bedrock.
It implements the read-only methods of [the interface used by L1 execution engines](https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/execution-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=false&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false).
It does not implement `eth_sendTransaction` and `eth_sendRawTransaction`, because they don't make sense in a read-only copy.
</details>
\ No newline at end of file
---
title: Running a testnet or mainnet node
lang: en-US
---
If you're looking to build an app on Optimism you'll need access to an Optimism node. You have two options - use a hosted node from providers like Alchemy or run your own.
## Hosted node providers
You can get a free, hosted one from [any of these providers](../../useful-tools/providers.md) to get up and building quickly. Of them, [Alchemy](https://www.alchemy.com/optimism) is our preferred node provider, and is used to power our [public endpoint](../../useful-tools/networks.md).
However, you might be interested in running your very own Optimism node.
Here we'll go over the process of running a testnet or mainnet Optimism node for yourself.
## Upgrades
If you run a node you need to subscribe to [an update feed](../releases.md) (either [the mailing list](https://groups.google.com/a/optimism.io/g/optimism-announce) or [the RSS feed](https://changelog.optimism.io/feed.xml)) to know when to upgrade.
Otherwise, your node will eventually stop working.
## Configuration choices
### Hardware requirements
Replicas need to store the transaction history of Optimism and to run Geth.
They need to be relatively powerful machines (real or virtual).
We recommend at least 16 GB RAM, and an SSD drive with at least 100 GB free.
### Source of synchronization
<details>
<summary><b>Pre-Bedrock (current version)</b></summary>
Prior to Bedrock you choose one of two configurations.
- **Replicas** replicate from L2 (Optimism).
Replicas gives you the most up to date information, at the cost of having to trust Optimism's updates.
- **Verifiers** replicate from L1 (Ethereum).
Verifiers read and execute transactions from the cannonical block chain.
As a result, the only way for them to have inaccurate information is an [Ethereum reorg](https://www.paradigm.xyz/2021/07/ethereum-reorgs-after-the-merge#post-merge-ethereum-with-proof-of-stake), an extremely rare event.
</details>
<details>
<summary><b>Bedrock (coming late 2022)</b></summary>
In Bedrock the [op-geth](https://community.optimism.io/docs/developers/bedrock-temp/infra/#bedrock-geth) typically synchronizes from other Optimism nodes (https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md#happy-path-sync), meaning L2, but it can [synchronize from L1](https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md#worst-case-sync) if necessary.
To synchronize only from L1, you edit the [op-node configuration](https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md) to set `OP_NODE_P2P_DISABLE` to `true`.
When you use RPC to get block information (https://github.com/ethereum-optimism/optimism/blob/develop/specs/rollup-node.md#l2-output-rpc-method), you can specify one of four options for `blockNumber`:
- an actual block number
- **pending**: Latest L2 block
- **latest**: Latest block written to L1
- **finalized**: Latest block fully finalized on L1 (a process that takes 12 minutes with Proof of Stake)
</details>
## Docker configuration
The recommended method to create a replica is to use [Docker](https://www.docker.com/) and the [Docker images we provide](https://hub.docker.com/u/ethereumoptimism).
They include all the configuration settings.
This is the recommended method because it is what we for our own systems.
As such, the docker images go through a lot more tests than any other configuration.
### Configuring and running the node
Follow [these instructions](https://github.com/smartcontracts/simple-optimism-node) to build and run the node.
## Non-docker configuration
Here are the instructions if you want to build you own replica without relying on our images.
These instructions were generated with a [GCP e2-standard-4](https://cloud.google.com/compute/docs/general-purpose-machines#e2-standard) virtual machine running [Debian 10](https://www.debian.org/News/2021/2021100902) with a 100 GB SSD drive.
They should work on different operating systems with minor changes, but there are no guarantees.
Note that these directions are for a replica of the main network.
You need to modify some of them if you want to create a replica of the test network.
**Note:** This is *not* the recommended configuration.
While we did QA on these instructions and they work, the QA that the docker images undergo is much more extensive.
### Install packages
1. These packages are all required either to compile the software or to run it.
We need `libusb-1.0` because geth requires it to check for hardware wallets.
```sh
sudo apt install -y git make wget gcc pkg-config libusb-1.0 jq
```
1. Install [the node.js package](https://nodejs.org/).
These instructions were written using the 12.x version.
1. Install [yarn](https://classic.yarnpkg.com/):
```sh
sudo npm install -g yarn
```
1. Install [the Go programming language](https://go.dev/doc/install).
These instructions were written using Go version 1.17.6
<!--
```sh
curl -sL https://deb.nodesource.com/setup_12.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt install -y nodejs
```
```sh
wget https://go.dev/dl/go1.17.6.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.17.6.linux-amd64.tar.gz
cp /etc/profile /tmp
echo "export PATH=$PATH:/usr/local/go/bin" >> /tmp/profile
sudo mv /tmp/profile /etc
. /etc/profile
. ~/.profile
```
--->
### The Data Transport Layer (DTL)
This TypeScript program reads data from an Optimism endpoint and passes it over to the local instance of l2geth ([geth](https://geth.ethereum.org/) with minor changes for layer 2 support).
1. Download [the source code](https://github.com/ethereum-optimism/optimism).
Then, compile the DTL:
```sh
git clone -b master https://github.com/ethereum-optimism/optimism.git
cd optimism
yarn
yarn build
cd ~/optimism/packages/data-transport-layer
cp .env.example .env
```
1. Edit `.env` to specify your own configuration.
Modify these parameters:
| Parameter | Value |
| --------- | ----- |
| DATA_TRANSPORT_LAYER__NODE_ENV | production |
| DATA_TRANSPORT_LAYER__ETH_NETWORK_NAME | mainnet |
| DATA_TRANSPORT_LAYER__ADDRESS_MANAGER | 0xdE1FCfB0851916CA5101820A69b13a4E276bd81F
| DATA_TRANSPORT_LAYER__SERVER_HOSTNAME | localhost
| DATA_TRANSPORT_LAYER__SERVER_PORT | 7878
| DATA_TRANSPORT_LAYER__SYNC_FROM_L1 | false |
| DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT | Get an endpoint from [a service provider](https://ethereum.org/en/developers/docs/nodes-and-clients/nodes-as-a-service/) unless you run a node yourself |
| DATA_TRANSPORT_LAYER__SYNC_FROM_L2 | true |
| DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT | [See here](../../useful-tools/networks/) |
| DATA_TRANSPORT_LAYER__L2_CHAIN_ID | 10 (for a mainnet replica) |
These directions are written with the assumption that you sync from L2, which is faster.
If you prefer, you can syncronize from L1, which is more secure but slower.
To use L1, keep the value of `DATA_TRANSPORT_LAYER__SYNC_FROM_L1` as `true` and
`DATA_TRANSPORT_LAYER__SYNC_FROM_L2` as `false`. Also, add this line:
```
DATA_TRANSPORT_LAYER__L1_START_HEIGHT=13596466
```
1. Start the DTL (as a daemon, logging to `~/dtl.log`):
```sh
nohup yarn start > ~/dtl.log &
```
Note that you cannot just close the window if you want DTL to continue running, you have to exit the shell gracefully.
1. To verify the DTL is running correctly you can run a command.
- If synchronizing from L2:
```sh
curl -s http://localhost:7878/eth/syncing?backend=l2 | jq .currentTransactionIndex
```
- If synchronizing from L1:
```sh
curl -s http://localhost:7878/eth/syncing?backend=l1 | jq .currentTransactionIndex
```
It gives you the current transaction index, which should increase with time.
1. For debugging purposes, it is sometimes useful to get a transaction's information from the DTL:
```
curl -s http://localhost:7878/transaction/index/<transaction number>?backend=l2 | jq .transaction
```
Note that the transaction indexes are one below the number on etherscan, so for example
```
curl -s http://localhost:7878/transaction/index/31337?backend=l2 | jq .transaction
```
Corresponds to [Etherscan transaction 31338](https://explorer.optimism.io/tx/31338).
The DTL now needs to download the entire transaction history since regenesis, a process that takes hours.
While it is running, we can get started on the client software.
### The Optimism client software
The client software, called l2geth, is a minimally modified version of [`geth`](https://geth.ethereum.org/).
Because `geth` supports hardware wallets you might get USB errors. If you do, ignore them.
These directions use `~/gethData` as the data directory.
You can replace it with you own directory as long as you are consistent.
1. To compile l2geth, run:
```sh
cd ~/optimism/l2geth
make geth
```
1. Download and verify the genesis state, the state of the Optimism blockchain during the final regenesis, 11 November 2021.
```sh
wget -O /tmp/genesis.json https://storage.googleapis.com/optimism/mainnet/genesis-berlin.json
sha256sum /tmp/genesis.json
```
The output of the `sha256sum` command should be:
```
0x106b0a3247ca54714381b1109e82cc6b7e32fd79ae56fbcc2e7b1541122f84ea /tmp/genesis.json
```
1. Create a file called `env.sh` (in whatever directory is convenient) with this content:
```sh
export CHAIN_ID=10
export DATADIR=~/gethData
export NETWORK_ID=10
export NO_DISCOVER=true
export NO_USB=true
export GASPRICE=0
export GCMODE=archive
export BLOCK_SIGNER_ADDRESS=0x00000398232E2064F896018496b4b44b3D62751F
export BLOCK_SIGNER_PRIVATE_KEY=6587ae678cf4fc9a33000cdbf9f35226b71dcc6a4684a31203241f9bcfd55d27
export ETH1_CTC_DEPLOYMENT_HEIGHT=13596466
export ETH1_SYNC_SERVICE_ENABLE=true
export ROLLUP_ADDRESS_MANAGER_OWNER_ADDRESS=0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A
export ROLLUP_CLIENT_HTTP=http://localhost:7878
export ROLLUP_DISABLE_TRANSFERS=false
export ROLLUP_ENABLE_L2_GAS_POLLING=false
export ROLLUP_GAS_PRICE_ORACLE_OWNER_ADDRESS=0x648E3e8101BFaB7bf5997Bd007Fb473786019159
export ROLLUP_MAX_CALLDATA_SIZE=40000
export ROLLUP_POLL_INTERVAL_FLAG=1s
export ROLLUP_SYNC_SERVICE_ENABLE=true
export ROLLUP_TIMESTAMP_REFRESH=5m
export ROLLUP_VERIFIER_ENABLE=true
export RPC_ADDR=0.0.0.0
export RPC_API=eth,rollup,net,web3,debug
export RPC_CORS_DOMAIN=*
export RPC_ENABLE=true
export RPC_PORT=8545
export RPC_VHOSTS=*
export TARGET_GAS_LIMIT=15000000
export USING_OVM=true
export WS_ADDR=0.0.0.0
export WS_API=eth,rollup,net,web3,debug
export WS_ORIGINS=*
export WS=true
export ROLLUP_BACKEND=l2
```
**Note**: If synchronizing from L1, replace the last line with
```sh
export ROLLUP_BACKEND=l1
```
1. Run the new file.
This syntax (dot, space, and then the name of the script) runs the script in the context of the current shell, rather than in a new shell.
The reason for doing this is that we want to modify the current shell's environment variables, not start a new shell, set up the environment in it, and then exit.
```sh
. env.sh
```
1. Initialize l2geth with the genesis state.
This process takes about nine minutes on my system.
```sh
mkdir ~/gethData
./build/bin/geth init --datadir=$DATADIR /tmp/genesis.json --nousb
```
1. Create the geth account.
The private key needs to be the one specified in the configuration, otherwise the consensus algorithm fails and the node does not synchronize.
```sh
touch $DATADIR/password
echo $BLOCK_SIGNER_PRIVATE_KEY > $DATADIR/block-signer-key
./build/bin/geth account import --datadir=$DATADIR --password $DATADIR/password $DATADIR/block-signer-key
```
1. Start geth (logging to `~/geth.log`).
```sh
nohup build/bin/geth \
--datadir=$DATADIR \
--password=$DATADIR/password \
--allow-insecure-unlock \
--unlock=$BLOCK_SIGNER_ADDRESS \
--mine \
--miner.etherbase=$BLOCK_SIGNER_ADDRESS > ~/geth.log &
```
It is possible that `geth` won't listen to IPC or the TCP port (8545) until it finishes the initial synchronization.
1. To check if l2geth is running correctly, open another command line window and run these commands:
```sh
cd ~/optimism/l2geth
build/bin/geth attach --datadir=~/gethData
eth.blockNumber
```
Wait a few seconds and then look at the blocknumber again and exit:
```sh
eth.blockNumber
exit
```
If l2geth is synchronizing, the second block number is higher than the first.
1. Wait a few hours until the entire history is downloaded by dtl and then propagated to l2geth.
If you have any problems, [contact us on our Discord](https://discord-gateway.optimism.io/).
<!--
latest.sh:
#! /bin/sh
echo dtl:
curl -s http://localhost:7878/eth/syncing?backend=l2 | jq .currentTransactionIndex
echo
echo l2geth:
tail -1 ~/geth.log | awk '{print $5}'
-->
---
title: Interacting with Optimism contracts
lang: en-US
---
Optimism is composed, in part, of a series of smart contracts on both L1 (Ethereum) and L2 (Optimism).
You may want to interact with these contracts for any number of reasons, including:
- Sending messages between L1 and L2
- Sending tokens between L1 and L2
- Querying information about the current [L1 data fee](./transaction-fees.md#the-l1-data-fee)
- And lots more!
On this page we'll show you how to work with these contracts directly from other contracts and how to work with them from the client side.
## Finding contract addresses
You'll need to find the address of the particular contract that you want to interact with before you can actually interact with it.
Check out the [Networks and Connection Details page](../../useful-tools/networks.md) for links to the contract addresses for each network.
You can also find the addresses for all networks in the [deployments folder](https://github.com/ethereum-optimism/optimism/tree/master/packages/contracts/deployments) of the [`contracts` package](https://github.com/ethereum-optimism/optimism/tree/master/packages/contracts).
## Interacting from another contract
All you need to interact with the Optimism system contracts from another contract is an address and an interface.
You can follow [the instructions above](#finding-contract-addresses) to find the address of the contract you want to interact with.
Now you simply need to import the appropriate contracts.
### Installing via NPM or Yarn
We export a package [`@eth-optimism/contracts`](https://www.npmjs.com/package/@eth-optimism/contracts?activeTab=readme) that makes it easy to use the Optimism contracts within NPM or Yarn based projects.
Install the package as follows:
```
npm install @eth-optimism/contracts
```
### Importing contracts
Simply import the desired contract or interface from the `@eth-optimism/contracts` package:
```solidity
import { SomeOptimismContract } from "@eth-optimism/contracts/path/to/SomeOptimismContract.sol";
```
Please note that `path/to/SomeOptimismContract` is the path to the contract [within this folder](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/contracts).
For example, if you wanted to import the [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L1/messaging/L1CrossDomainMessenger.sol) contract, you would use the following import:
```solidity
import { L1CrossDomainMessenger } from "@eth-optimism/contracts/L1/messaging/L1CrossDomainMessenger.sol";
```
### Getting L2 contract addresses
Addresses of system contracts on the L2 side of the network are the same on every network.
We provide these addresses as constants within the [`Lib_PredeployAddresses`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/constants/Lib_PredeployAddresses.sol) contract.
## Interacting from the client side
Just like when interacting from another contract, we've created a few packages that make it easy to interact with the Optimism system contracts from the client side.
### Installing via NPM or Yarn
You can use the [`@eth-optimism/contracts`](https://www.npmjs.com/package/@eth-optimism/contracts?activeTab=readme) package to interact with the Optimism system contracts from a JavaScript or TypeScript based project.
Install the package as follows:
```
npm install @eth-optimism/contracts
```
### Getting contract artifacts, interfaces, and ABIs
You can get the compiler artifact, bytecode, and ABI for any Optimism contract as follows:
```ts
import { getContractDefinition } from '@eth-optimism/contracts'
const artifact = getContractDefinition('SomeOptimismContract')
const abi = artifact.abi
const bytecode = artifact.bytecode
const deployedBytecode = artifact.deployedBytecode
```
Similarly, you can also get [ethers Interface objects](https://docs.ethers.io/v5/api/utils/abi/interface/) for any contract:
```ts
import { getContractInterface } from '@eth-optimism/contracts'
const iface = getContractInterface('SomeOptimismContract')
```
### Getting L2 contract addresses
You can get the address of any L2 contract as follows:
```ts
import { predeploys } from '@eth-optimism/contracts'
const address = predeploys.CONTRACT_NAME_GOES_HERE
```
---
title: Testing Decentralized Applications with Optimism
lang: en-US
---
For the most part running applications on Optimism is identical to running them on Ethereum, so the testing is identical too.
In this article you learn the best practices for Optimism testing where there are differences.
## Unit tests and single layer integration tests
The vast majority of tests do not involve any Optimism-specific features.
In those cases, while you *could* test everything on Optimism, that would normally be inefficient.
Most Ethereum development stacks include features that make testing easier, which normal Ethereum clients, such as geth (and our modified version, l2geth) don't support.
Therefore, it is a good idea to run the majority of tests, which do not rely on Optimism-specific features, in the development stack.
It is a lot faster.
Ideally you would want to be able to run some tests on Optimism (either a [local development environment](dev-node.md) or [the test network](../../useful-tools/networks.md#optimism-goerli).
This would be a much slower process, but it would let you identify cases where [the equivalence between Optimism and Ethereum breaks down](differences.md) (or the equivalence between Ethereum itself and the development stack, for that matter).
## Multilayer integration tests
Some dapps need Optimism-specific features that aren't available as part of the development stack.
For example, if your decentralized application relies on [inter-domain communication](../bridge/messaging.md), the effort of developing a stub to let you debug it in a development stack is probably greater than the hassle of having the automated test go to [a local development environment](dev-node.md) each time.
## Integration with other products
In many cases a decentralized application requires the services of other contracts.
For example, [Perpetual v. 2](https://v2docs.perp.fi/benefits-of-v2) cannot function without [Uniswap v. 3](https://uniswap.org/blog/uniswap-v3).
If that is the case you can use [mainnet forking](https://hardhat.org/hardhat-network/guides/mainnet-forking.html).
It works with Optimism with the exception of transactions that use the `L1BlockNumber` opcode directly.
Alternatively, you can connect to our [test network](../../useful-tools/networks.md#optimism-goerli) if those contracts are also deployed there (in many cases they are).
---
title: Transaction fees on L2
lang: en-US
---
## Understanding the basics
Transaction fees on Optimism work a lot like fees on Ethereum.
However, Layer 2 introduces some new paradigms that means it can never be exactly like Ethereum.
Luckily, Optimism's [EVM equivalence](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) makes these differences easy to understand and even easier to handle within your app.
Let's take a look at the two sources of cost in a transaction on Optimism: the L2 execution fee and the L1 data/security fee.
### The L2 execution fee
Just like on Ethereum, transactions on Optimism have to pay **gas** for the amount of computation and storage that they use.
Every L2 transaction will pay some **execution fee**, equal to the amount of gas used by the transaction multiplied by the gas price attached to the transaction.
This is exactly how fees work on Ethereum with the added bonus that gas prices on Optimism are seriously low.
Here's the (simple) math:
```
l2_execution_fee = transaction_gas_price * l2_gas_used
```
The amount of L2 gas used depends on the particular transaction that you're trying to send.
Thanks to [EVM equivalence](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), transactions typically use approximately the same amount of gas on Optimism as they do on Ethereum.
Gas prices fluctuate with time and congestion, but you can always check the current estimated L2 gas price on the [public Optimism dashboard](https://public-grafana.optimism.io/d/9hkhMxn7z/public-dashboard?orgId=1&refresh=5m).
### The L1 data fee
Optimism differs from Ethereum because all transactions on Optimism are also published to Ethereum.
This step is crucial to the security properties of Optimism because it means that all of the data you need to sync an Optimism node is always publicly available on Ethereum.
It's what makes Optimism an L2.
Users on Optimism have to pay for the cost of submitting their transactions to Ethereum.
We call this the **L1 data fee**, and it's the primary discrepancy between Optimism (and other L2s) and Ethereum.
Because the cost of gas is so expensive on Ethereum, the L1 data fee typically dominates the total cost of a transaction on Optimism.
This fee is based on four factors:
1. The current gas price on Ethereum.
2. The gas cost to publish the transaction to Ethereum. This scales roughly with the size of the transaction (in bytes).
3. A fixed overhead cost denominated in gas. This is currently set to 2100.
4. A dynamic overhead cost which scales the L1 fee paid by a fixed number. This is currently set to 1.0.
Here's the math:
```
l1_data_fee = l1_gas_price * (tx_data_gas + fixed_overhead) * dynamic_overhead
```
Where `tx_data_gas` is:
```
tx_data_gas = count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16
```
You can read the parameter values from the [gas oracle contract](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#readContract).
::: warning NOTE
Ethereum has limited support for adding custom transaction types.
As a result, unlike the L2 execution fee, **users are not able to set limits for the L1 data fee that they may be charged**.
The L1 gas price used to charge the data fee is automatically updated when new data is received from Ethereum.
**Spikes in Ethereum gas prices may result in users paying a higher or lower than estimated L1 data fee, by up to 25%.**
[See here for a detailed explanation why the difference is capped at 25%](https://help.optimism.io/hc/en-us/articles/4416677738907-What-happens-if-the-L1-gas-price-spikes-while-a-transaction-is-in-process).
:::
## Stuff to keep in mind
### Sending transactions
The process of sending a transaction on Optimism is identical to the process of sending a transaction on Ethereum.
When sending a transaction, you should provide a gas price greater than or equal to the current L2 gas price.
Like on Ethereum, you can query this gas price with the `eth_gasPrice` RPC method.
Similarly, you should set your transaction gas limit in the same way that you would set your transaction gas limit on Ethereum (e.g. via `eth_estimateGas`).
### Responding to gas price updates
Gas prices on L2 default to 0.001 Gwei but can increase dynamically if the network is congested.
When this happens, the lowest fee that the network will accept increases.
Unlike Ethereum, Optimism currently does not have a mempool to hold transactions with too low a fee.
Instead, Optimism nodes will reject the transaction with the message `Fee too low`.
You may need to handle this case explicitly and retry the transaction with a new gas price when this happens.
### Displaying fees to users
Many Ethereum applications display estimated fees to users by multiplying the gas price by the gas limit.
However, as discussed earlier, users on Optimism are charged both an L2 execution fee and an L1 data fee.
As a result, you should display the sum of both of these fees to give users the most accurate estimate of the total cost of a transaction.
[See here for a code sample using the JavaScript SDK](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/sdk-estimate-gas)
#### Estimating the L2 execution fee
You can estimate the L2 execution fee by multiplying the gas price by the gas limit, just like on Ethereum.
#### Estimating the L1 data fee
You can use the SDK [(see here)](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/sdk-estimate-gas).
Alternatively, you can estimate the L1 data fee using the `GasPriceOracle` predeployed smart contract located at [`0x420000000000000000000000000000000000000F`](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F).
[The `GasPriceOracle` contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol) is located at the same address on every Optimism network (mainnet and testnet).
To do so, call `GasPriceOracle.getL1Fee(<unsigned RLP encoded transaction>)`.
#### Estimating the total fee
You can estimate the total fee by combining your estimates for the L2 execution fee and L1 data fee.
### Sending max ETH
Sending the maximum amount of ETH that a user has in their wallet is a relatively common use case.
When doing this, you will need to subtract the estimated L2 execution fee and the estimated L1 data fee from the amount of ETH you want the user to send.
Use the logic described above for estimating the total fee.
## Common RPC Errors
### Insufficient funds
- Error code: `-32000`
- Error message: `invalid transaction: insufficient funds for l1Fee + l2Fee + value`
You'll get this error when attempting to send a transaction and you don't have enough ETH to pay for the value of the transaction, the L2 execution fee, and the L1 data fee.
You might get this error when attempting to send max ETH if you aren't properly accounting for both the L2 execution fee and the L1 data fee.
### Gas price too low
- Error code: `-32000`
- Error message: `gas price too low: X wei, use at least tx.gasPrice = Y wei`
This is a custom RPC error that Optimism returns when a transaction is rejected because the gas price is too low.
See the section on [Responding to gas price updates](#responding-to-gas-price-updates) for more information.
### Gas price too high
- Error code: `-32000`
- Error message: `gas price too high: X wei, use at most tx.gasPrice = Y wei`
This is a custom RPC error that Optimism returns when a transaction is rejected because the gas price is too high.
We include this as a safety measure to prevent users from accidentally sending a transaction with an extremely high L2 gas price.
See the section on [Responding to gas price updates](#responding-to-gas-price-updates) for more information.
---
title: Using your favorite tools
lang: en-US
---
How do you use your favorite tools for building contracts when you're working with Optimism?
Just use them!
<!--
TODO: We need a page on EVM equivalence.
-->
Optimism isn't just EVM compatible, it's EVM *equivalent*.
All the Ethereum tooling you know and love should work on Optimism without lots of fiddling.
Hardhat? Brownie? Truffle?
Yep, yep, and yep.
If you find a tool that *doesn't* seem to work with Optimism out of the box, try [opening an issue over on GitHub](https://github.com/ethereum-optimism/optimism/issues).
Someone working on Optimism will probably help fix the issue pretty quickly.
## Hardhat
Configuring Hardhat to deploy your contracts to Optimism is a breeze.
Simply add the following to your Hardhat config file:
```js
module.exports = {
networks: {
...
// for mainnet
'optimism': {
url: "https://mainnet.optimism.io",
accounts: [privateKey1, privateKey2, ...]
},
// for testnet
'optimism-goerli': {
url: "https://goerli.optimism.io",
accounts: [privateKey1, privateKey2, ...]
},
// for the local dev environment
'optimism-local': {
url: "http://localhost:8545",
accounts: [privateKey1, privateKey2, ...]
},
},
...
}
```
## Foundry
[Foundry](https://www.paradigm.xyz/2021/12/introducing-the-foundry-ethereum-development-toolbox) is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Foundry supports Optimism out of the box!
Just provide an Optimism RPC:
```sh
forge create ... --rpc-url=https://goerli.optimism.io/
```
Additionally, you can use [forge-optimism](https://github.com/tarrencev/forge-optimism) to simulate the optimism context and simplify testing.
## Truffle
Configuring Truffle is easy too:
```js
const HDWalletProvider = require('@truffle/hdwallet-provider')
...
module.exports = {
networks: {
...
// for mainnet
'optimism': {
provider: () => {
return new HDWalletProvider(YOUR_MAINNET_MNEMONIC_GOES_HERE, 'https://mainnet.optimism.io')
}
network_id: "10"
},
// for testnet
'optimism-goerli': {
provider: () => {
return new HDWalletProvider(YOUR_GOERLI_MNEMONIC_GOES_HERE, 'https://goerli.optimism.io')
}
network_id: "420"
},
// for the local dev environment
'optimism-local': {
provider: () => {
return new HDWalletProvider('test test test test test test test test test test test junk', 'http://localhost:8545')
}
network_id: "420"
}
},
...
}
```
## Brownie
[Brownie](https://github.com/eth-brownie/brownie) is a Python-based development and testing framework for EVM smart contracts. Brownie supports Optimism out of the box!
## Waffle
Starting from [Waffle](https://github.com/TrueFiEng/Waffle) v4.x.x you can use Waffle chai matchers to test your smart contracts on Optimism. We recommend following [this guide](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/getting-started)(scroll down to `Waffle` section) to get to know [Waffle](https://github.com/TrueFiEng/Waffle).
If you want to add [Waffle](https://github.com/TrueFiEng/Waffle) to an already existing project, you can install it with (replace `npm install` with the package manager's you're using relevant command):
```bash
npm install --save-dev ethereum-waffle@alpha @ethereum-waffle/optimism@alpha
```
## Other tools
Got a favorite tool that works well with Optimism?
Want it displayed on this page?
[Make a pull request over on the docs repository](https://github.com/ethereum-optimism/community-hub/pulls)!
---
title: Known Issues (none)
lang: en-US
---
**None at present**
---
title: Developer Videos
lang: en-US
---
### Getting started with Optimism
In this video you learn how to use Optimism for your own development:
- [Deploy a contract on Optimism (Foundry) (1:24)](https://youtu.be/_Y6CwsYgqwI?t=84)
- [Interact with an Optimism contract (Foundry) (2:20)](https://youtu.be/_Y6CwsYgqwI?t=140)
- [Use the Etherscan block explorer with Optimism (2:53)](https://youtu.be/_Y6CwsYgqwI?t=173)
- [Getting started docs, with other development stacks (4:45)](https://youtu.be/_Y6CwsYgqwI?t=285)
- [Getting test ETH (6:00)](https://youtu.be/_Y6CwsYgqwI?t=360)
- [The Optimism Bridge (7:25)](https://youtu.be/_Y6CwsYgqwI?t=445)
- [Inter-layer communication, between Ethereum and Optimism (8:48)](https://youtu.be/_Y6CwsYgqwI?t=528)
- [Gas cost calculation (10:20)](https://youtu.be/_Y6CwsYgqwI?t=620)
<iframe src="https://www.youtube.com/embed/_Y6CwsYgqwI" title="Getting Started with Optimism" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## ETHOnline 2022 | Optimism Summit
September 22nd, 2022
### Modular OP Stack - Karl Floersch
This talk covers the OP Stack chain software, and how the Bedrock release will enable deployments of op-chains which serve new use cases, are future proof, and will all one day fit together to form the Superchain.
<iframe src="https://www.youtube.com/embed/jlKPjiDu_KM?list=RDCMUCfF9ZO8Ug4xk_AJd4aeT5HA" title="Modular OP Stack - Karl Floersch" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Introduction to Architecture - Joshua Gutow
This talks covers bedrock’s architecture. It explains how redesigning the rollup architecture in bedrock reduces fees & increases implementation simplicity. It also covers the various components of the system & how they interact.
<iframe src="https://www.youtube.com/embed/fkoTMchOFPI?list=RDCMUCfF9ZO8Ug4xk_AJd4aeT5HA" title="Introduction to Architecture - Joshua Gutow" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Modular sequencing - Norswap Laurent
In this video you learn the design space around two design choices related to sequencing:
- *who* sequences (decentralization)
- *how* they sequence (MEV!)
<iframe src="https://www.youtube.com/embed/aKxS16TG2jk?list=PLXzKMXK2aHh4vbe7GLQfOnL-QJ1O5EqMv" title="Modular sequencing - Norswap Laurent" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Pluggable data availability - Protolambda
In this talk protolambda explains how the next Optimism upgrade improves the rollup with modular design, and how this can be utilized to quickly adopt future scaling improvements like EIP-4844 (a.k.a. proto-danksharding).
OP Labs is contributing to L1 research and development to increase the data availability used for securing all L2s.
<iframe src="https://www.youtube.com/embed/-xWpalvZeEU?list=PLXzKMXK2aHh4vbe7GLQfOnL-QJ1O5EqMv" title="Pluggable data availability - Protolambda" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Panel: State of 4844
The end game for data availability on Ethereum - Full Sharding - and how EIP-4844 gets us closer to that goal. This video discusses:
- Where we are with the implementation of EIP-4844 and what else was needed to be done before CFI.
- How the community could help us ship EIP-4844.
- Data Availability and the concept of a Data Availability Layer; analogous to the CL and EL. This concept helps reason about how data, i.e. the blobs, fits in Ethereum.
<iframe src="https://www.youtube.com/embed/uxYkGdCFsjI?list=PLXzKMXK2aHh4vbe7GLQfOnL-QJ1O5EqMv" title="Panel: State of 4844" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Intro to Quix - Mark Dawson
The NFT ecosystem on Optimism is an exciting, growing space.
Mark Dawson is the co-founder of Quix, the largest NFT marketplace on Optimism.
He discusses early NFT communities on Optimism, the upcoming NFT bridge, composable NFTs, and how to get started in the OP NFT ecosystem.
<iframe src="https://www.youtube.com/embed/Z8L8vNDgJjY?list=PLXzKMXK2aHh4vbe7GLQfOnL-QJ1O5EqMv" title="Intro to Quix - Mark Dawson" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
---
title: Releases
lang: en-US
---
## Our release cycle
Optimism releases new software every Tuesday and Thursday at 7PM UTC.
Software is released on Goerli first, followed by mainnet a week later.
## Up to date information
* [Changelog](https://changelog.optimism.io/)
* [Release announcement mailing list](https://groups.google.com/a/optimism.io/g/optimism-announce)
* [RSS feed](https://changelog.optimism.io/feed.xml)
If you [run your own replica](build/run-a-node.md), it is important that you subscribe either to the release announcement mailing list or the RSS feed to know when you need to upgrade.
---
title: What is the Optimism Collective?
lang: en-US
---
The [Optimism Collective](https://app.optimism.io/announcement) is a band of companies, communities, and citizens working together to reward public goods and build a sustainable future for Ethereum.
Together we will dispel the myth that public goods cannot be profitable.
Public goods (including public goods for Optimism and Ethereum) often go underfunded when incentives aren't properly aligned, forcing many to make trade-offs between earning a profit and building for the common good.
The Optimism Collective is bound by a mutually beneficial pact, [a vision](https://www.optimism.io/vision) that can be summed up with the equation **Impact = Profit**.
### Impact = Profit
The Optimism Collective will deploy a new model for rewarding those who create or sustain public goods - [retroactive public goods funding](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c), to support projects and communities building for the common good.
Retroactive public goods funding is advantageous because it is relatively easy to agree on what *is* useful, and see who built it.
It is a lot harder to identify in advance what *will be* useful, and which team will do the best job of building it.
The possibility of an eventual exit payment can also encourage investors to fund initial public good development efforts until the usefulness is obvious, in the same way that the possibility of an eventual IPO or buy out encourages investors to fund startups today.
### How is this going to be governed?
The Optimism Collective takes [an experimental and agile approach to governance](https://optimism.mirror.xyz/r888e4B5iiNQi-3_mO26ixgv-plQ099XWgqEOv9iWKA) relentlessly iterating towards a system which stands the test of time.
Initially, the Collective's model of digital democratic governance will consist of two houses: the *Token House* and the *Citizens' House*.
## Token House
Governance of the Optimism Collective began with the launch of the OP token and the [Token House](token-house.md).
OP was distributed to hundreds of thousands of addresses that engaged in positive-sum, community-oriented behavior with [Airdrop #1](airdrop-1.md).
You can read more about the [distribution criteria here](airdrop-1.md).
As Token House members, OP holders are responsible for submitting, deliberating, and voting on various types of governance proposals.
In carrying out these functions, OP holders may either [vote directly, or delegate their OP voting power to an eligible third party](delegate.md).
The Token House votes on these proposal types:
- [Governance fund](gov-fund.md) grants
- Protocol upgrades
- Inflation adjustment
- Director removal
- Treasury appropriations
- Rights protection
[Read here for more details](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md#valid-proposal-types).
## Citizens' House
The Citizens' House is a large-scale experiment in [non-plutocratic governance](https://vitalik.ca/general/2021/08/16/voting3.html) and retroactive funding of public goods.
The Citizens' House is responsible for [retroactive public goods funding (RPGF)](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c).
For more information about the Citizens' House, read [the Overview](./citizens-house.md).
For details about RetroPGF 2, visit [the RetroPGF 2 program details](./retropgf-2.md).
For plans around identity and governance, see [the Citizenship page](./citizenship.md).
**What is described here is an initial experiment**.
The specifics of this system will evolve as the Collective grows.
---
title: Airdrop 1
lang: en-US
---
## Background
The Optimism Foundation’s Airdrop #1 rewards those who have been instrumental as early adopters and active users of projects in the Optimism ecosystem.
To celebrate our Ethereum roots, The Optimism Foundation also wants to welcome active L1 participants who can help scale Ethereum’s innovations, culture, and values to Layer 2.
In total, **248,699** addresses are eligible to claim OP in this *initial* airdrop.
Our airdrop has six sets of criteria.
Two for Optimism users that target early adopters and active project users, and four for L1 Ethereum, which target active contribution, positive-sum behaviors, and active participation.
Each set is distinct, meaning that an address can be eligible for multiple and allocated a sum of tokens accordingly.
A snapshot of addresses was taken on 03-25-2022 0:00 UTC.
The allocations and criteria break down as follows:
## Airdrop #1 Allocations
| Name | # of Addresses Eligible | OP Allocated per Address |
| -------- | ----------------------: | -----------------------: |
| Optimism Users | 92,157 | 776.86
| Repeat Optimism Users* | 19,174 | 1,692.49
| DAO Voters | 84,015 | 271.83
| Multisig Signers | 19,542 | 1,190.26
| Gitcoin Donors (on L1) | 23,925 | 555.92
| Users Priced Out of Ethereum | 74,272 | 409.42
\* *Note that Repeat Optimism Users Receive 776.86 + 1,692.49 = 2,469.35 OP*
| Overlap Bonuses | # of Addresses Eligible | OP Allocated per Address
| - | -: | -:
| 4 Categories & OP User | 2,707 | 4,180.54
| 5 Categories & OP User | 627 | 13,330.16
| 6 Categories & OP User | 45 | 27,534.98
The amount of OP tokens that an address receives is cumulative, meaning that the sum of OP tokens allocated for each matching criteria set is the amount that an address is eligible to claim (overlap bonuses are not cumulative).
### Optimism Early Adopters
The Foundation sought to identify Optimism users who had actively used applications on Optimism as a part of their crypto experience.
#### Optimism Users
This group selects for addresses that have used Optimism, including both early adopters and newer users, but narrows down to a group that has used Optimism multiple times.
*Criterion: Address bridged to Optimism from L1 during the early phases of mainnet (prior to Jun 23, 2021), or used Optimism for more than 1 day (at least 24 hours between their first and last transaction) and made a transaction using an app (after to Jun 23, 2021).*
- *These rules are only based on usage on Optimism. It does not matter which bridge, exchange, fiat on-ramp, etc you used in order to come to Optimism.*
#### Repeat Optimism Users
This tier selects for the most active Optimism users, who repeatedly come back to use applications in the Optimism ecosystem.
*Criterion: Address is an ‘Optimism User’ and made at least 1 transaction with an Optimism application across four or more distinct weeks. This selects for the top 20% of ‘Optimism Users’.*
- *"Distinct weeks" are counted based on the time of an addresses' first transaction (i.e. days 1 - 7 are considered Week 1, not the calendar week). This makes sure that there is no skew by which day of the week an address joined Optimism.*
### Active Ethereum Participants
These rules aim to target behaviors that match Ethereum and Optimism’s values of active contribution, positive-sum behavior, and scaling decentralized applications to the world.
#### DAO Voters
The Foundation believes that active and engaged governance is crucial to scaling decentralized systems.
Addresses who match this criterion have actively chosen to actively contribute by participating in governance.
*Criterion: Address has either voted on or authored at least one proposal on-chain, or at least two on Snapshot (off-chain).*
- *We filtered to “active DAOs” who have had at least 5 proposals with at least 5 votes.*
- *On-Chain governance contracts included: Governor Alpha and Bravo, Aave, Curve, Maker, Aragon, DAOHaus, DAOStack, and forks.*
- *Since Snapshot votes were off-chain, the Foundation recognized that these were more susceptible to spam/farming behavior, often driven by voters with nominal voting power.
To mitigate this, we filtered Snapshot votes to voters who made up the top 99.9% of total voting power in each DAO (i.e. >= 0.1% of voting power was made up by the sum of all smaller voters).*
#### Multi-Sig Signers
Multi-sig signers are entrusted with larger pools of capital or control over key protocol functions.
They are often the present (and future) DAO leaders and builders.
*Criterion: Address is a current signer on a Multi-Sig which has executed at least 10 transactions all-time (this cohort includes 95% of all multisig transactions).*
- *Multisig Wallets Include: Gnosis Safe v0.1.0-1.3.0, MultiSigWithDailyLimit, MultiSigWalletWithTimeLock, and addresses in Etherscan’s ‘Multisig’ label which had a function to get owner addresses.*
#### Gitcoin Donors (on L1)
Gitcoin donors have chosen to behave in positive-sum ways by funding public goods.
These addresses may also align with Optimism’s goal to build sustainable funding source for public goods through [retroactive funding (RetroPGF](https://medium.com/ethereum-optimism/retropgf-experiment-1-1-million-dollars-for-public-goods-f7e455cbdca)).
*Criterion: Address has made an on-chain donation through Gitcoin on L1. This includes any donation, regardless of if it was during a matching round.*
- *For the time period between rounds 1 - 5, we included addresses who interacted with Gitcoin contracts, sent legacy `ExecuteSubscription` calls, or appeared in [Gitcoin’s donor API](https://docs.gitcoin.co/mk_rest_api/).*
- *For the period between rounds 6 - 13 (present), we included donor addresses in the Gitcoin ‘BulkCheckout’ contract transaction logs.*
#### Users Priced Out of Ethereum
Active users of dapps on Ethereum are critical to ecosystem growth. Many of these addresses have started bridging to other chains due to high fees, and we want to help retain them in the Ethereum ecosystem while rewarding their curiosity and exploration. Optimism’s airdrop is also calibrated to reward loyalty to Ethereum, so users who have abandoned Ethereum entirely would not receive an airdrop.
*Criterion: Address bridged to another chain, but still made an app transaction on Ethereum in each month after they bridged, and transacted at an average rate of at least 2 per week since then (top 60% of matching addresses).*
- *Bridges included top L1s by TVL: Terra, BSC, Fantom, Avalanche, Solana, Polygon; and general-purpose L2s: Arbtirum, Optimism, Metis, Boba.*
- *To ensure that we gave a long enough period to sample activity, addresses had to have bridged away from Ethereum at least 90 days before the snapshot.*
### Overlap Bonus
Early Optimism users who also match multiple Ethereum criteria may be most likely to become important participants in the Optimism ecosystem, so these addresses were rewarded with an extra overlap bonus.
*Criterion: Address matches an ‘Optimism Early Adopter’ criterion, and matches at least 4 criteria sets in total (including Optimism criteria).*
- *The overlap bonus increases as more criteria are matched (i.e. the bonus for 5 criteria is greater than the bonus for 4 criteria)*
### Global Filtering Criteria
We believe that it’s best for the community for our airdrop list narrows as well as possible to real users and honest actors.
So we applied some basic filters to various criteria:
- **Address Activity:** Addresses needed to have used Ethereum for more than 1 day (24 hours between their first and last transaction) in order to qualify for ‘Active Ethereum Participants’ criteria.
This applied to all Ethereum criteria except for multisig signers, since it’s common for signers to have “signing-only” addresses.
- **Sybil Farmers:** We identified a few patterns of likely sybil attackers, who often created tens, hundreds, or more duplicate addresses.
We required stronger activity criteria for these addresses, which helped filter out these attackers, but also preserved many real users.
It’s impossible to catch everything, but removing as many sybillors as we can help get more OP in the hands of true positive-sum participants.
- **Snapshot Bots and Spam:** We used [ENS’ bot-catching proposal](https://snapshot.org/#/ens.eth/proposal/QmfLwPbo5UwnyvkXo7emsSMDMFCr8UtJ76n87v9Rf7gEuH) to filter out addresses that spam Snapshot votes. Snapshot spaces that appeared to be either compromised or falsified were also excluded.
- **Exchanges and On-Ramps:** We filtered our known centralized exchange and fiat on-ramp addresses.
- **Exploiters:** Known exploit addresses were filtered out.
- **Recency Filter:** Addresses needed to have made a transaction after Jan 1, 2019.
For multisig signers, only the multisig needed to have executed a transaction after Jan 1, 2019.
We chose this cutoff date to narrow in on active addresses, while also providing a window before [L1 gas prices](https://etherscan.io/chart/gasprice) consistently rose and potentially priced some users out of Ethereum.
### Additional Sybil Filtering
Optimism is for the people, not the sybils.
After the announcement of Airdrop #1, we received limited reports of sybil activity that was not excluded by our initial filtering.
We have since applied additional filtering to Airdrop #1 that excludes 17k addresses and recovers 14m OP.
Recovered OP will be redistributed proportionally to remaining addresses already included in Airdrop #1.
The [Airdrop #1 Allocations](#airdrop-1-allocations) table above has been updated accordingly to reflect this.
For a list of excluded sybil addresses, [see this spreadsheet](https://docs.google.com/spreadsheets/d/1kUAt-vrkID0yBkic72djWRxdliK8W_5rBGxq6-Iv3cg).
In the interest of maintaining the integrity of future OP Airdrops, we will not be publishing the additional filters used to remove these addresses.
If you have feedback about this additional sybil filtering process, please feel free to [fill out this feedback form](https://oplabs.typeform.com/sybil-feedback).
## What’s Next?
To check your eligibility for Airdrop #1 and claim your tokens visit [the airdrop app](https://app.optimism.io/airdrop/check).
We made our best effort to make Airdrop #1 as fair as possible.
However, there’s always a chance some great community members may have slipped through the cracks.
Don’t worry!
This is Airdrop #1, and 14% of the total initial token supply is reserved for future airdrops.
The best way to earn OP is to get involved in the Optimism Collective:
- If you want to build on Optimism, visit our [developer documentation](../developers/README.md) or jump in to [Discord](https://discord-gateway.optimism.io).
- If your talents are non-technical, jump in to [Discord](https://discord-gateway.optimism.io) to find other builders, apply to be a Support NERD, or just make some memes 🍉
- [Bridge your assets to Optimism here](https://app.optimism.io/bridge) and explore projects on OP
As always: stay safe and stay Optimistic.
## References
- **Application Transaction:** Any transaction, excluding token transfer and approval transactions.
- **Airdrop #1 Dashboard:** [https://dune.com/optimismfnd/optimism-airdrop-1](https://dune.com/optimismfnd/optimism-airdrop-1)
- **Detailed Protocol Metrics:** [https://dune.com/optimismfnd/Optimism](https://dune.com/optimismfnd/Optimism)
- **OP Token Address:** [0x4200000000000000000000000000000000000042](https://explorer.optimism.io/address/0x4200000000000000000000000000000000000042)
- **Optimism on Twitter:** [https://twitter.com/optimismFND](https://twitter.com/optimismFND)
- **Join the Optimism Discord:** [https://discord-gateway.optimism.io/](https://discord-gateway.optimism.io/)
- **CSV with the list of airdropped addresses:** [publish_op_airdrop1.csv](https://drive.google.com/file/d/1VVcHGHNMLeyRQMHcfkzT4fVbU2xPeT1X/view?usp=sharing)
---
title: OP Allocations
lang: en-US
---
## Allocations at a glance
![](../../assets/docs/governance/allocations/pie2.jpeg)
| Category | Percentage | Description |
| --- | --- | --- |
| Ecosystem Fund | 25% | The Ecosystem Fund is a proactive program meant to stimulate development in the Collective ecosystem by providing funding to projects and communities |
| Retroactive Public Goods Funding (RetroPGF) | 20% | RetroPGF is the Optimism Collective’s primary mechanism to adequately and reliably **reward** public goods for the impact they provide |
| User airdrops | 19% | A series of airdrops to reward users for specific helpful behaviors, beginning with Airdrop #1 |
| Core contributors | 19% | The people who’ve been working tirelessly to bring the Optimism Collective into existence |
| Sugar xaddies | 17% | Investors |
## User Airdrops
***Allocation: 19%***
The Optimism Foundation will distribute a portion of the OP allocation to members of the Optimism and Ethereum communities in multiple waves.
### Airdrop #1
The first user airdrop will distribute **5%** of the OP token supply.
This airdrop is targeted towards people who:
- Behave in positive-sum ways.
- Contribute actively to their communities.
- Have been priced out of Ethereum.
The addresses and amounts were chosen to reward Optimism users, as well as attract core Ethereum users to the OP family.
Airdrop #1 cast a wide net to distribute OP to more than 250k addresses. See the [Airdrop #1 documentation](airdrop-1.md) for a detailed breakdown of eligibility criteria.
### Future airdrops (#2, 3, …)
An allocation of **14%** of the OP token supply will be held in reserve for future user airdrops.
As a result of the game-able nature of airdrops, the Optimism Foundation will be responsible for determining airdrop metrics as fairly as possible.
The intent behind airdrops is to distribute them to addresses which positively impact the Optimism community.
The best way to improve your odds of receiving future airdrops is to get involved!
## Ecosystem Fund
***Allocation: 25%***
The Ecosystem Fund is an incentive program designed to stimulate development of the Collective ecosystem.
The Ecosystem Fund will be used to directly fund the communities and companies that drive the expansion of the Collective ecosystem (OPCOs).
The allocation dedicated to the Ecosystem Fund is to be further split into the following categories:
1. Governance Fund **(5.4%)**
2. Partner Fund **(5.4%)**
3. Seed Fund **(5.4%)**
4. Unallocated **(8.8%)**
The Ecosystem Fund is intended to be a kick-starting mechanism that will be phased out entirely once the OP in the fund has been depleted.
The Optimism Foundation expects the function served by the Ecosystem Fund (proactive funding) to eventually be replaced by private third-party investors who can expect to be paid out by the [Retroactive Public Goods Funding](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c) mechanism.
![](../../assets/docs/governance/allocations/ecosystem_fund_process.jpeg)
The wide variety of funding mechanisms ensures that airdrops constantly flow to users who make valuable contributions to the ecosystem.
These contributions result in ecosystem growth, ecosystem growth results in demand for block space, demand for block space results in revenues, and these revenues are then productively re-deployed into the ecosystem as additional airdrops.
Each fund can be thought of as a parallel experiment in OP allocation, enabling rapid iteration and doubling down on what works to further grow the ecosystem.
### Governance Fund
An allocation of **5.4%** of the OP token supply will be dedicated to the first Governance Fund.
All projects demonstrating usage on Optimism can request tokens from this fund at [gov.optimism.io](https://gov.optimism.io/)
### Partner Fund
An allocation of **5.4%** of the OP token supply will be dedicated to the Partner Fund.
These funds will be distributed strategically by the Optimism Foundation to grow the Optimism ecosystem for the benefit of the entire Collective.
### Seed Fund
An allocation of **5.4%** of the OP token supply will be dedicated to the Seed Fund, a fund for early stage projects launching in and adjacent to the Optimism ecosystem.
### Unspent allocation for future growth programs
An allocation of **8.8%** of the OP token supply will be held in reserve for future community growth programs.
## Retroactive Public Goods Funding
***Allocation: 20%***
[Retroactive Public Goods Funding (RetroPGF)](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c) will initially be distributed by the [Citizens' House](citizens-house.md).
RetroPGF rounds are expected to occur on a quarterly basis with the objective of ensuring that all OPCOs are rewarded adequately, appropriately, and reliably based on their impact to the Collective—key to our [vision](https://optimism.io/vision).
RetroPGF will be funded by several revenue sources:
1. 20% of the initial OP token supply held as the “RetroPGF Reserve”.
1. Optimism network transaction fees and sequencer revenue.
The Optimism Foundation may choose to increase the total OP payout of any given period by tapping into the 20% RetroPGF Reserve.
## Core Contributors
***Allocation: 19%***
The Core Contributors allocation will be distributed to the people who helped bring Optimism and the Optimism Collective from concept to reality, and will continue to pay for development of the protocol.
All tokens distributed as part of the Core Contributors allocation will be subject to a lockup period.
## Sugar Xaddies
***Allocation: 17%***
Investors.
They funded us and our crazy vision for the future.
Couldn’t-a done it without ya.
All tokens distributed as part of the Sugar Xaddies allocation will be subject to a lockup period.
## Token Distribution Details
At genesis there will be an initial total supply of 4,294,967,296 OP tokens.
The total token supply will inflate at a rate of 2% per year.
64% of the initial token supply (i.e., the aggregate amount of OP **not** reserved for core contributors or sugar xaddies) will be distributed to the community as described above.
These distributions will be administered over time by the Optimism Foundation in its role as steward of the Optimism Collective.
In Year 1, 30% of the initial token supply will be made available to the Foundation for distribution.
After the first year, token holders will vote to determine the Foundation’s annual OP distribution budget.
The Foundation expects to seek the following annual allocations:
- Year 2: 15% of the initial token supply
- Year 3: 10% of the initial token supply
- Year 4: 4% of the initial token supply
If the Foundation’s authorized OP expenditures ever reaches below 10% of the initial token supply, it is expected to request an additional allocation.
Taken together, the Foundation expects the total supply of unlocked OP tokens to approximate the graph below.
Note this graph is for illustration only, and actual unlocked supply will depend on governance and the rate that OP is allocated across user airdrops, the Ecosystem Fund, and RetroPGF.
![](../../assets/docs/governance/allocations/alloc-by-time.png)
---
title: What is the AttestationStation?
lang: en-US
---
![](../../assets/docs/governance/attestationstation/attestationstation.png)
The AttestationStation is an **attestation smart contract** deployed on Optimism.
The goal of the AttestationStation is to provide a permissionless and accessible data source for builders creating reputation-based applications. By enabling anyone to make arbitrary attestations about other addresses, we can create a rich library of qualitative and quantitative data that can be used across the ecosystem.
<!-- TODO: Add source code link when we have an authoritative source -->
## General FAQ
#### What are attestations?
Attestations are statements by a creator (who attested this) about a subject (who is being attested about). Attestations could present any qualitative or quantitative statement. To paint a picture — actors might submit attestations that are contextual to their brand, ecosystem, and governance structure.
![](../../assets/docs/governance/attestationstation/attestations.png)
#### What can attestations be used for?
We imagine the first use case for attestations is to create sybil resistant identity that can power [non-plutocratic governance](https://vitalik.ca/general/2021/08/16/voting3.html).
Longer term, this open-source primitive can be used for a variety of sybil-resistant applications including on-chain credit scoring / under collateralized loans.
#### How can you go from attestations to sybil-resistant identity?
Attestations in the AttestationStation are on-chain and can be used by other smart contracts in a variety of applications. Instead of having a single entity owning user data and identity, the AttestationStation is a graph of peer-to-peer (p2p) attestations.
The first step to get from attestations to sybil-resistant identity is to grow the number of attestations in the AttestationStation. To do that, we are taking a two pronged approach by growing the number of:
* **Trusted attestations**: These attestations are made by organizations like Gitcoin, DegenScore, Otterspace, etc. attest about individual community members.
* **Social attestations**: These are attestations from one address about another. Eg zain.eth says kathy.eth is a colleague, kathy.eth says will.eth is a friend, etc.
![](../../assets/docs/governance/attestationstation/network.png)
Anyone can then take the graph of p2p attestations from the AttestationStation and run computations like EigenTrust over the set of data to derive identity sets on top of a purely subjective web of trust.
![](../../assets/docs/governance/attestationstation/eigan.png)
To build a robust, trustworthy identity network, these computations will be run iteratively. We can start with a purely subjective web of trust, and use that starting point to derive a larger web of trust, and so on — we can begin to establish a credibly neutral reputation that is entirely peer-to-peer.
#### How is the AttestationStation different from other attestation products?
The AttestationStation is deliberately dead simple and serves as an invite to ecosystem contributors to come build an open-source and permissionless attestation graph together.
Creating this system in a decentralized and open-source manner is important because it allows for greater inclusion and representation of different perspectives. This can help to ensure that the system is fair and accessible to all, and that it accurately reflects the diversity of the communities it serves.
#### How do I use the AttestationStation?
See [the tutorial](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/ecosystem/attestation-station).
#### What are the contract addresses for the AttestationStation?
| Network | Address |
| - | - |
| Optimism Goerli | [`0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77`](https://goerli-explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77) |
| Optimism Mainnet | [`0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77`](https://explorer.optimism.io/address/0xEE36eaaD94d1Cc1d0eccaDb55C38bFfB6Be06C77) |
#### What products are built on the AttestationStation?
If your product is using the AttestationStation, make a PR including how you're using attestations to be added to the list 😊
* [AttestationStation Interface by sbvegan](https://attestationstation.xyz/)
* [Optimist Score by Flipside](https://science.flipsidecrypto.xyz/optimist/)
* [Optimism Attestor by Clique](https://provenance.clique.social/attestor/opattestor)
#### I am building on the AttestationStation but have some questions, where can I discuss these?
The best place to ask any dev related questions is the #dev-support channel on [the Optimism Discord](https://discord-gateway.optimism.io/). If you need additional support check out this [Help Article](https://help.optimism.io/hc/en-us/articles/9762044018843-How-do-I-get-project-support-marketing-integrations-etc-).
#### I want to apply for a grant to build on the AttestationStation, how can I do this?
You can learn more about the variety of grants program available at Optimism [here](allocations/#ecosystem-fund). As a reminder, your work should be published to a public GitHub repo.
#### What are some things I should build with the AttestationStation?
It will take a huge community effort to realize the potential that reputation has to transform web3. That’s why we started small with the AttestationStation and an open invite to come experiment with us. We can already think of a bunch of fun projects to build today like:
* **EiganTrust**: Aggregate attestations from various communities and use techniques like [EigenTrust](https://en.wikipedia.org/wiki/EigenTrust) to derive reputation
* **SybilRank**: Create a [SybilRank](https://users.cs.duke.edu/~qiangcao/sybilrank_project/index.html) calculator! (h/t Barry Whitehat for the suggestion)
* **Data visualization**: Create data visualizations representing the different types of attestations in the AttestationStation
* **Predictive attestations**: Instead of attesting “I trust XYZ”, try fun attestations like, “I believe XYZ will be considered trusted by a majority of node in the future”. Plus, what if we add a slashing condition to the predictive attestation?
* **Attestation delegation**: Build a system which manages attestations automatically for users. This system should enable users to delegate some of their attestation assignment to a third party. For instance, users may opt-in to delegating their trust scores to a sybil detection court system. Another project is to build that sybil detection court system!
* **Attestation import**: Write proxy contracts which import attestations of various formats into the standardized AttestationStation format so that they can be consumed by the standard AttestationStation tooling.
* **Viral attestations**: Create systems which make it fun and easy for users to attest useful information about each other.
* **Composable NFT allowlists**: Create a way for creators to easily build, manage, and share mint allowlists for upcoming NFT drops!
## Technical specifications
The following is the breakdown of Optimism's AttestationStation smart contract.
### State
#### attestations
The following is the nested mapping that stores all the attestations made.
```
mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations;
```
The following is a struct that represents a properly formatted attestation.
#### AttestationData
```
struct AttestationData {
address about;
bytes32 key;
bytes val;
}
```
### Events
#### AttestationCreated
This event is emitted when an attestation is successfully made.
```
event AttestationCreated(
address indexed creator,
address indexed about,
bytes32 indexed key,
bytes val
);
```
### Functions
#### attest
```
function attest(AttestationData[] memory _attestations) public
```
Records attestations to the AttestationStation's state and emits an `AttestationCreated` event with the address of the message sender, address the attestation is about, the bytes32 key, and bytes value.
Parameters:
| Name | Type | Description |
| -------------- | ----------------- | ----------------------------------- |
| \_attestations | AttestationData[] | Array of `AttestationData` structs. |
---
title: Overview
lang: en-US
---
The Citizens' House is a large-scale experiment in [non-plutocratic governance](https://vitalik.ca/general/2021/08/16/voting3.html) and retroactive funding of public goods.
The Citizens’ House will work alongside the Token House to govern the Optimism Collective.
**In its first stage, the Citizens’ House is solely responsible for voting on retroactive public goods funding (RetroPGF)**.
The initial set of Citizens in this stage is determined by (a) criteria set by the Optimism Foundation and (b) a special election from the Token House.
During this bootstrapping phase, the Optimism Foundation will determine the scope and amount of each funding round, as well as administer the voting process.
Holding a voting badge in this initial set of Citizens **does not guarantee permanent citizenship** or voting rights in all future rounds of RetroPGF.
In future stages, the Citizens’ House role will expand.
For example, in addition to voting on RetroPGF funding, the Citizens’ House will work alongside the Token House to govern allocation of protocol profit, collaborate on criteria for participation in the Citizens’ House, and engage in a system of checks and balances to enforce the Collective’s Codes of Conduct.
Funding for RetroPGF can come from:
- **[20% of the initial OP supply](./allocations.md#retroactive-public-goods-funding)**
- Redeployment of some profit generated by the Optimism protocol
Many values-aligned projects on Optimism have also chosen to contribute a portion of their revenue to retroPGF.
If your project wants to contribute funds, you can send ETH, ERC-20 tokens, or NFTs to the ENS address **retroPGF.eth**.
It is supported on **[Optimism](https://explorer.optimism.io/address/0x15dda60616ffca20371ed1659dbb78e888f65556)** as well as **[Ethereum mainnet](https://etherscan.io/address/0x15dda60616ffca20371ed1659dbb78e888f65556)**.
Everything donated retroPGF.eth will be allocated to a RetroPGF round.
**What is described here is an initial experiment for RetroPGF 2**. The specifics of this system will evolve as the Collective grows.
## How RetroPGF Works
RetroPGF stands for Retroactive Public Goods Funding.
This is a series of experiments where members of the Citizens’ House allocate protocol profits or portions of the token treasury to projects they deem have provided substantial public good along certain criteria.
RetroPGF is based on the idea that it’s easier to determine what *was* useful than to issue proactive grants for what *might* be useful.
In each round of RetroPGF, Citizens vote on how to allocate funding looking backwards over a prescribed time period.
These rewards create strong incentives for people to build public goods that benefit the Optimism Collective.
The aggregate effect is an ecosystem that is easier to build on, learn about, and connect to, in turn driving application usage and generating more demand for blockspace.
By funding public goods sustainably, the Collective can create a rich ecosystem and a better economy.
![The public goods virtuous cycle](../../assets/docs/governance/economics/virt_cycle.png)
RetroPGF also provides possible exit liquidity for public goods projects, which opens up a market for early investment in those projects.
This means builders can:
- Be compensated for their positive contributions without generating direct revenue
- Raise capital to bootstrap based on the early potential and promise of their project
RetroPGF is core to Optimism’s value of `impact = profit`: the idea that that positive impact to the collective should be rewarded with profit to the individual.
In 2021, Optimism ran its [first round of RetroPGF](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c).
Community members voted to distribute $1 million across 58 open source software projects.
RetroPGF 2 takes place in Jan 2023. Read more [here](./retropgf-2.md).
## A Commitment to Experimentation
RetroPGF is a longterm bet to build the future Optimists want to see.
The Collective will conduct regular rounds of RetroPGF, each different from the last.
This is an emergent process that will take community participation to grow and prune.
RetroPGF has three core components, each with substantial surface area for experimentation.
1. **Impact scoping**: what should the Collective fund?
How is it defined and decided on?
1. **Impact scoring:** how does the Citizens’ House evaluate impact?
What units, process, or tools do we use?
1. **Impact settlement:** how does voting work?
For the first several rounds of RetroPGF, the Optimism Foundation will decide on scope and voting mechanics with input from the community.
Eventually the set of variables around what to fund, how much to fund, and how to vote will be up to the Citizens’ House, with checks and balances from the Token House.
Over time, the Collective aims to expand the scope of RetroPGF to support the production of public goods beyond the immediate Optimism ecosystem.
To get there, we must refine the tools and processes used for RetroPGF based on regular experimentation.
---
title: Citizenship
lang: en-US
---
## Identity on Optimism
The Citizens’ House relies on the concept of identity-based governance.
This is distinct from the more common pattern of token-voting governance (as used in [Optimism’s Token House](./token-house.md)).
To start, the initial set of Citizens in this stage is determined by (a) criteria set by the Optimism Foundation and (b) a special election from the Token House.
Citizenship is conferred by a simple entry in the [AttestationStation](./attestation-station.md).
In future stages, the Token House and Citizens’ House together will collaborate on ongoing criteria for participation in the Citizens’ House.
This means the criteria for Citizenship will evolve over time.
Holding a voting badge in RetroPGF 2 does not guarantee voting rights in all future rounds of RetroPGF, as eventually membership will be determined by governance and may change.
How does this work?
- Optimism has a neutral, permissionless attestation contract called the [`AttestationStation`](./attestation-station.md)
- Optimism Governance (Token + Citizens’ House) has authority over a function that takes the AttestationStation as an argument and uses any data included to determine the set of Citizens.
Optimism governance can use any reputation data written to the AttestationStation contract to determine Citizenship in future iterations of the Citizens’ House.
This system is designed to be flexible and future-proof; rather than prescribe citizenship criteria at the outset, the Collective is keeping true to its principle of iterative governance.
For more information on Optimism's identity layer, visit the [AttestationStation documentation](./attestation-station.md).
## Becoming a Citizen
The Citizens’ House is initiated with the set of 90 voters in [RetroPGF 2](./retropgf-2.md).
Eventually, Citizenship is intended to be widely distributed to a large group of humans across the Optimism ecosystem with expertise in many different subcultures and industries.
Eventually, Optimism governance will have the power to set criteria for Citizenship.
This means voting eligibility may someday be subject to rules and restrictions unknown today.
**Voting in RetroPGF 2 does not guarantee permanent participation in the Citizens’ House and future iterations of RetroPGF.**
The next wave of Citizenships will be issued in advance of RetroPGF 3, later in 2023.
---
title: Find and Track Your Delegate
lang: en-US
---
- [Identify your current delegate and view the delegate's voting record](https://help.optimism.io/hc/en-us/articles/6389519530779-How-do-I-see-how-my-delegate-voted-)
- [View information about specific delegates (their share of the votes, etc.)](https://dune.com/optimismfnd/optimism-op-token-house)
<!--
- [Delegate aggregate information (GINI coefficient, etc.)](https://app.flipsidecrypto.com/dashboard/optimism-governance-l5WXpo)
-->
\ No newline at end of file
---
title: How to be a delegate
lang: en-US
---
Participating in Token House governance can require a serious time commitment.
For this reason, people are able to delegate their OP to community members who have explicitly volunteered to play an active role in Token House governance.
Being a delegate is an important job that comes with a lot of responsibility.
Before deciding to become a delegate you should be familiar with:
* The [Working Constitution](https://gov.optimism.io/t/working-constitution-of-the-optimism-collective/55).
* The [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md).
* The [Delegate Code of Conduct](https://gov.optimism.io/t/delegate-code-of-conduct/3943)
## Ready to be a delegate?
This is the process to become a delegate:
### Step 1: Commit
* Read the delegate responsibilities
* Post a completed [delegate commitment form](https://gov.optimism.io/t/delegate-commitments/235).
If you have any questions about the form, ask in the [delegation questions feedback thread](https://gov.optimism.io/t/delegation-questions-feedback-thread/236)
* It is not required, but you probably also want to [delegate your OP tokens' voting power to yourself](https://app.optimism.io/delegates).
Please note that the expectation is that token grants **will not be** self-delegated to grant recipients.
### Step 2: Set up your profile
* Set up your delegate profile.
Please note that delegate avatars are taken from ENS. 
[If you want to change your avatar, you’ll need to do it via ENS](https://medium.com/the-ethereum-name-service/step-by-step-guide-to-setting-an-nft-as-your-ens-profile-avatar-3562d39567fc).
The Optimism Foundation will add new profiles to the [delegate site](https://app.optimism.io/delegates) once per Season.
---
title: OP Economics
lang: en-US
---
::: tip Notice: OP Airdrop #1 is now available to claim
[Click here to claim](https://app.optimism.io/airdrop/check)
*Airdrop #1 does not require payment.*
*Stay safe!*
:::
The Optimism Collective is based on the idea that healthy public goods create a thriving and valuable ecosystem.
The economics of this ecosystem are designed to generate value for three constituencies:
- Value accrues to **tokenholders** through the productive re-deployment of sequencer revenue.
Sequencer revenue is primarily directed to fund public goods, which creates ecosystem value and drives demand for blockspace.
- Value accrues to **contributors and builders** directly from retroactive public goods funding and the markets it enables.
Builders also benefit symbiotically: it’s better to build in an ecosystem with well-funded tools, education, apps, and infrastructure.
- Value accrues to **users and community members** from ongoing airdrops of OP, from project incentives made possible by OP ecosystem funding, and from the benefit public goods provide.
Together, it looks like this:
![](../../assets/docs/governance/economics/virt_cycle.png)
Let’s break it down ⤵️
## Demand for OP blockspace generates revenue
Funding for the OP economy comes from ownership of the Optimism network and the value of its blockspace.
Today, funding comes directly from the centralized sequencer, accruing to The Optimism Foundation for redistribution.
In the future, funding can accrue directly to the protocol by selling the right to participate in Optimism’s decentralized sequencing network.
Simply put: the right to blockspace is the sustainable source of revenue that drives OP’s economic model and grows with the network itself.
## Revenue is distributed to public goods
Communities benefit from all sorts of public goods, from education to city infrastructure to open source software.
But many markets fail to provide adequate funding and support for those public goods.
It’s here that Optimism takes a step forward, by building a sustainable funding source into the mechanisms of the network itself.
Direct or auctioned sequencer revenue is distributed back to the ecosystem primarily through Retroactive Public Goods Funding ([RetroPGF](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c)).
This creates strong incentives for individuals to build for the public good of the Optimism Collective.
To start, the Citizens’ House will vote quarterly on distributions of funds to individuals, teams, projects, or communities that have provided subjective value over a specified time period.
This directly rewards community members for the positive impact of their work.
RetroPGF also provides possible exit liquidity for public goods projects, which opens up a market for early investment in those projects.
This means builders can:
- Be compensated for their positive contributions without generating direct revenue.
- Raise capital to bootstrap based on the early potential and promise of their project.
In the long-term, the Foundation expects this mechanism to drive a wide range of pro-social downstream effects, which the Collective can capture and expand upon through frequent experimentation and iteration.
## Value from public goods drives demand for blockspace
Funding public goods drives growth — and therefore network revenues — from the ground up.
In our [first round](https://vitalik.ca/general/2021/11/16/retro1.html) of RetroPGF, Optimism funded a set of hugely valuable developer tools and core infrastructure.
The aggregate effect is an ecosystem that is easier to build on, learn about, and connect to, in turn driving application usage and generating more demand for blockspace.
But what value do these public goods provide, and for whom is it valuable?
This is a rich design space.
Optimism’s governance is designed to create conditions for experimentation around this question.
The Foundation draws heavily on the work of Ostrom, Vitalik, and others to make sure funding for public goods is maximally efficient, effective, and safe.
To start, Foundation governance is shared and divided through a two-house system, where network parameters for the amount of funding are controlled by both houses, but the governance of RetroPGF allocation is determined by a set of Citizens with local knowledge and skin in the game.
Thoughtful allocation of public goods funding will create longterm value for builders, users, tokenholders, citizens and the entire Optimism Collective.
---
title: Existing Delegates
lang: en-US
---
::: danger Out of date information
This process is currently under construction for Season 3.
Check back soon for updated information.
:::
Thank you for being active in Optimism Token House Governance.
As a delegate, you should:
- Join the conversation in the below channels in our [Discord](https://discord-gateway.optimism.io/):
- [#gov-general](https://discord.com/channels/667044843901681675/968498307913637919): This channel is for general governance discussions.
- [#new-gov-temp-check](https://discord.com/channels/667044843901681675/1011238484373159956): This channel is where proposers post their proposal for early feedback.
As a delegate you should provide feedback on proposals consistently.
- [#token-house-gov](https://discord.com/channels/667044843901681675/991340698995544176): This announcement channel will help you keep up to date with big events in the governance process, such as when voting starts, meta-governance updates, etc
- [#delegate-discussion](https://discord.com/channels/667044843901681675/989611992295813241): If you are a delegate with more than 0.5% delegated voting power, join the conversation with other larger delegates to discuss proposals & the governance process.
- Provide feedback on draft proposals in the [Proposals Discussion](https://gov.optimism.io/c/proposals/38) section of the Forum
- If you have more than 0.5% of voting supply then you can also approve proposals.
Proposals need at least 2 approvals to move to a vote.
To approve a proposal post this phrase in a forum comment on Discourse:
> **I am an Optimism delegate with sufficient voting power and I believe this proposal is ready to move to a vote.**
- Vote on proposals via [Snapshot](https://snapshot.org/#/opcollective.eth).
Multi-sig support for votes coming soon.
- Post your voting rationale in a [Delegate Communications Thread](https://gov.optimism.io/c/governance/41)
- Join the community calls, which occur every other Tuesday at 1pm ET.
[The governance calendar](https://calendar.google.com/calendar/u/0/r?cid=Y180aHVpNzBpdG0wODllN3Q4cTUwaGVoMWtub0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t) shows which Tuesdays, and how to join the call.
- Stay up to date with voting cycles and general governance updates in the Governance section of the [Forum](https://gov.optimism.io/c/governance/41).
\ No newline at end of file
---
title: Governance Fund Overview
lang: en-US
---
*[To discuss the Governance Fund, head to the Optimism governance forum](https://gov.optimism.io/t/governance-fund-discussion-thread/213)*.
## Overview
5.4% of the total initial token supply (231,928,234 OP) will be distributed to Optimism projects and communities via the Governance Fund.
The goal of the Governance Fund is to empower the OP community to proactively incentivize future growth of projects and communities in the Optimism ecosystem.
You can read more about the total allocation of OP in the [Allocations section](./allocations.md) of our [Governance docs](./README.md).
The purpose of the Governance Fund is to incentivize sustainable growth of projects and communities in the Optimism ecosystem.
This does not mean that all grants must be incentive programs.
The Token House is welcome to consider any and all proposals which would drive growth or address a gap in the Optimism ecosystem, including public goods projects.
Governance funds should come with an expectation of growth-related deliverables.
It is not the intended purpose of the governance fund to retroactively fund public goods without an expectation of future work.
There is [a distinct OP allocation](./allocations.md#retroactive-public-goods-funding) dedicated to this, which will be distributed via the Citizens' House at a later date.
## Phase 1
Phase 1 had begun after Airdrop #1 and distributes the remainder of the Governance Fund (approx. 196,128,233 OP) to projects in the Optimism ecosystem.
Phase 1 will continue until the Governance Fund is exhausted.
You can see how the Governance Fund has been distributed to date in [the Governance Tracker](https://docs.google.com/spreadsheets/d/1eaHOlWB34ij1KGsXdaNyTQg4tt1Pu2JurJsElBb6a8k/edit#gid=0).
Any project on Optimism can [submit a forum proposal](proposals.md) to request any amount of OP tokens.
The proposal must include a plan for how the tokens will incentivize growth on Optimism.
Proposals are reviewed and voted on by the [Token House](token-house.md).
When a project gets a proposal approved, they are still eligible to apply for further proposals.
The Optimism Foundation recommends that projects distribute their initial allocation of tokens before requesting further tokens.
Projects are also encouraged to reference data from the success of their initial distribution to strengthen their case in future proposals.
## Phase 0
Phase 0 was designed to reward existing projects that have driven growth of the Optimism ecosystem.
The amount of funding for each project was determined by a set of on-chain metrics, calculated via a snapshot of the Optimism chain on April 1, 2022 at 12:00am UTC.
To see live tracking on govFund allocations please see the [Public tracker spreadsheet](https://docs.google.com/spreadsheets/d/1eaHOlWB34ij1KGsXdaNyTQg4tt1Pu2JurJsElBb6a8k/edit#gid=0).
\ No newline at end of file
---
title: How to Delegate Your Tokens
lang: en-US
---
### Why Delegate?
Participating in governance of the Optimism Token House can require a serious time commitment.
For this reason, people are able to delegate the voting power of their OP tokens to a community member who has explicitly volunteered to play an active role in Token House governance.
These volunteers are called delegates.
If you do not wish to delegate your voting power to a representative, you will still need to delegate *to yourself* in order to vote.
### How do I delegate my votes to a representative?
1. [Choose a delegate](https://app.optimism.io/delegates).
Take the time to read through delegate statements of interest, choosing your delegate is an important decision!
A healthy governance system is good for Optimism and for all OP holders. Choosing a good delegate is in each OP token holder's best interest.
This delegate will vote on your behalf.
You will not hold any voting power or execute your own votes but you will retain 100% ownership of your tokens, and can use them however you want.
View top delegates by voting weight on [Dune](https://dune.com/optimismfnd/optimism-op-token-house).
1. [Go to the delegates app](https://app.optimism.io/delegates) to see that you’ve delegated your tokens as intended.
Please note that delegation only becomes effective the next time [Snapshot](https://snapshot.org/#/opcollective.eth) measures how many votes each delegate has.
This happens at the start of every voting cycle, which is every three weeks.
1. Monitor how your delegate [has been voting on Snapshot](https://help.optimism.io/hc/en-us/articles/6389519530779-How-do-I-see-how-my-delegate-voted-).
Your delegate should also write up the reasoning behind their votes in a [Delegate Communications Thread](https://gov.optimism.io/c/governance/41).
Delegation is always in your control. You can return to the [delegation page](https://app.optimism.io/delegates) at any time to change your delegate selection, or to re-delegate voting power to yourself (more below).
### How do I delegate to myself?
Would you rather vote yourself?
No problem, but you’ll have to delegate to yourself first!
If you try to vote with your tokens but have not delegated to yourself, you will not be able to vote.
1. [Delegate to yourself](https://help.optimism.io/hc/en-us/articles/6296720540955-How-do-I-delegate-to-myself-).
1. [Go to the delegates app](https://app.optimism.io/delegates) to see that you’ve delegated your tokens to your own address.
Please note that delegation only becomes effective the next time [Snapshot](https://snapshot.org/#/opcollective.eth) measures how many votes each delegate has.
This happens at the start of the voting phase in a cycle, which is the last week of each cycle.
A new cycle starts every 3 weeks.
If you want to become a delegate that others delegate to you need to [follow this process](delegate.md#ready-to-be-a-delegate).
1. Once you’ve been added as a delegate in the next voting cycle, you can start [voting](https://snapshot.org/#/opcollective.eth)!
<!--
## Step 1: Claim or purchase OP tokens
To check your eligibility for Airdrop #1, scroll to the bottom of [this page](https://app.optimism.io/announcement).
If you didn’t qualify for Airdrop #1, don’t worry, there will be more.
Claim your tokens by visiting **[the airdrop app](https://app.optimism.io/airdrop/check).**
It doesn’t cost anything to claim the airdrop, except for gas fees.
You will need to make sure you have some ETH on the Optimism network to pay for gas (0.003 is usually enough).
[Learn here how to deposit ETH to Optimism](https://help.optimism.io/hc/en-us/sections/4413033248795-Deposit).
Didn’t qualify or just want more OP? OP is available to buy on all major exchanges deployed on Optimism.
## Step 2: Delegate your tokens (to someone else or yourself)
Participating in governance of the Optimism Token House will require a serious time commitment.
For this reason, The Optimism Foundation strongly encourages people to delegate the voting power of their OP tokens to a community member who has explicitly volunteered to play an active role in Token House governance.
If you do not wish to delegate your voting power to a representative, you will still need to delegate to yourself in order to vote.
* [Delegate your tokens](https://app.optimism.io/delegates).
[Here are directions if you wish to delegate them to yourself](https://help.optimism.io/hc/en-us/articles/6296720540955-How-do-I-delegate-to-myself-).
* [Delegate guide](delegate.md)
* [Delegate's voting record](https://help.optimism.io/hc/en-us/articles/6389519530779-How-do-I-see-how-my-delegate-voted-)
## Step 3: Join the conversation
Comment in the [Proposal Discussion](proposals.md) section of the [Forum](https://gov.optimism.io/) and provide feedback on draft proposals.
::: tip
Make sure you understand and follow the Forum's [code of conduct](https://gov.optimism.io/t/code-of-conduct/5/4)
:::
## Step 4: Vote (if you are a delegate)
Go to [Snapshot](https://snapshot.org/#/opcollective.eth), connect your wallet, and vote.
There is no minimum OP holding voting requirement, but you will need to have the OP tokens you wish to delegate or vote with in your wallet when the voting snapshot is taken.
Tokens that are staked or LP’d when the snapshot is taken do not carry voting power.
-->
\ No newline at end of file
---
title: Submitting Proposals
lang: en-US
---
::: danger Out of date information
This process is currently under construction for Season 3.
Check back soon for updated information.
:::
## Grant Proposals
::: tip
This information is here for your convenience.
The source of truth for all governance related processes is the [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md#proposal-process--components-of-a-valid-proposal).
The operating manual is currently in the process of being updated to reflect changes being voted on during Special Voting Cycles.
:::
Projects may submit a Phase 1 proposal to request OP tokens from the Governance Fund.
The purpose of the Governance Fund is to incentivize sustainable growth of projects and communities in the Optimism ecosystem.
This does not mean that all grants must be incentive programs.
The Token House is welcome to consider any and all proposals which would drive growth or address a gap in the Optimism ecosystem, including public goods projects.
However, funding should come with an expectation of growth-related deliverables.
It is not the intended purpose of the governance fund to retroactively fund public goods without an expectation of future work.
There is a distinct OP allocation dedicated to this, which will be distributed via [the Citizens' House](citizens-house.md) at a later date.
When writing your proposal, please adhere to the [grant proposal template](https://gov.optimism.io/t/grant-proposal-template/3233).
The process for submitting a grant proposal is currently undergoing changes.
A Grants Council, which will process grant applications in Season 3 was recently approved.
Stay tuned for more information on how to submit a proposal to the Council shortly.
**You can expect grant proposals to begin being processed again via the Grants Council on January 26th.**
## Other proposal types
There are [other types of proposals](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md#valid-proposal-types) that you can submit:
- Protocol Upgrade
- Inflation Adjustment
- Director Removal
There is no minimum holding requirement for submitting a proposal but your proposal must be approved by a delegate with sufficient voting power to move to a vote (as defined in [the operating manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md#valid-proposal-types)).
The process for submitting these proposals is very similar to the one for grants.
Where there are differences, they are noted in the table in the operating manual.
---
title: RetroPGF Round 2
lang: en-US
---
The Optimism Collective’s second round of Retroactive Public Goods Funding (RetroPGF) takes place in Q1 2023.
RetroPGF 2 will allocate 10m OP tokens to **fund public goods that support development and usage of the OP Stack.**
Optimism’s vision is to build a global system where positive impact to the collective is rewarded by profit to the individual.
RetroPGF is an experimental mechanism to realize this goal of **“impact = profit”**.
By funding public goods sustainably, the Collective can create a rich ecosystem and a better economy.
## Timeline
**Nomination window**: Jan 17 - Jan 31 19:00 GMT
**Project opt-in deadline**: Feb 14
**Voting window**: Feb 28 - Mar 14
## Scope of RetroPGF 2
**RetroPGF Round #2 will fund public goods that support development and usage of the OP Stack. This falls into three main categories:**
- **Infrastructure + Dependencies**: Software used to build or deploy the OP Stack; contributions to protocols or standards upon which the OP Stack runs; experiments that support future development of the core OP Stack protocol
- **Tooling + Utilities**: Work that helps builders create applications on Optimism mainnet, build on the OP Stack, interact with governance of the Collective, or use applications built on Optimism
- **Education**: Work to spread awareness and knowledge of how Optimism works, whether technically or socially
Today, the OP Stack refers to the OSS code powering Optimism. Today, this means the Optimism monorepo and its dependencies. Eligible projects **need not** be exclusive to the Optimism ecosystem, but there should be a connection. For example,
- **go-ethereum** is an important dependency of the OP Stack, but is not Optimism-specific.
- **Ethereum L2 explorer pages** which give stats on various L2s provide educational impact for the OP Stack, but are not Optimism-specific
RetroPGF 2 focuses on a narrow, Optimism-specific scope of impact to drive the core engine for ecosystem growth. In future rounds, this scope will expand.
## Nominations
Nominations will be open from **Jan 17 to Jan 31 19:00 GMT**. During this window, anyone can nominate a project for funding.
Here the term “project” can describe work that has been done by both an individual or a group.
Collections, a term used to describe projects without a single person or entity that can be verified via GitHub or Twitter, may only be nominated by the Optimism Foundation.
Nominations will take place on the [Optimism Governance Forum](https://gov.optimism.io/t/nominations-for-retropgf2/4636). Any community member can nominate a project by supplying:
- the project name
- A description of how the project has supported development and usage of the OP Stack
- a link to projects GitHub or Twitter
- (optional) contact info for the project or project lead
### You can nominate projects by category on the below forum posts
- [**Nominations for Infrastructure + Dependencies**](https://gov.optimism.io/t/infrastructure-dependencies-nominations-for-rpgf2/4637)
- [**Nominations for Tooling + Utilities**](https://gov.optimism.io/t/tooling-utilities-nominations-for-rpgf2/4639)
- [**Nominations for Education**](https://gov.optimism.io/t/education-nominations-for-rpgf2/4640)
## Funding Eligibility
Any project that has been nominated for RetroPGF 2 must complete a project profile on the **RetroPGF Application Manager** on the Optimism website in order to be eligible for voting.
The Application Manager will ask for information about your project, including authentication with Twitter or GitHub to prevent impersonations.
The Application Manager will also ask for information about team size, funding amount, impact provided to the Collective.
This information will help voters make choices about how to allocate funding.
After project nominations close, the Optimism Foundation will review completed profile submissions for spam and fraud.
Any project without a correctly verified GitHub or Twitter account, any project impersonating another team, or any project without a complete profile may be hidden from the voting round at the Foundation’s discretion.
For more information and guidance on the nomination and application process for projects, see the [**RetroPGF Project Manual**](https://www.notion.so/oplabs/Optimism-RetroPGF-2-Project-Manual-0a2e741133cd49b0b005ff759934b998).
## Voting Badge distribution
✨ Voting Badges for RetroPGF 2 will be distributed to a set of **90 community members.**
1. **One badge distributed to each voter from RetroPGF Round 1**, excluding full time employees of OP Labs or the Optimism Foundation. (14 badges, [list](https://docs.google.com/spreadsheets/d/1g4ilAByMNQsmlBC8cskQip7Ojd_qK6IhozJCyoVfU9k/edit#gid=0))
2. **One badge given to an individual selected by each of the previous voters in RetroPGF Round 1** (14 badges)
3. **One badge given to 10 Token House delegates, selected by a Token House vote.** (10 badges)
4. **One badge given to an individual selected by each of the badge-holding Token House delegates in (3) above** (10 badges)
5. **Twenty-one badges distributed to unaffiliated community contributors chosen by the Optimism Foundation**. (21 badges)
6. **Twenty-one badges distributed to individuals selected by each badge-holding community contributor in (5) above** (21 badges)
**Notes**
- Holding a voting badge for one round does not guarantee voting rights in subsequent rounds; Citizenship criteria are subject to change going forward
- Voting Badges in RetroPGF 2 are recorded as a simple attestation in the AttestationStation, not as a soulbound token. This approach preserves flexibility for future iterations
- Badgeholders are expected to adhere to the [Optimism Delegate Code of Conduct](https://gov.optimism.io/t/delegate-code-of-conduct/3943)
- An individual does not have to accept the voting badge. If an eligible person declines, the badge will not be distributed to someone else
- Anyone holding a Voting Badge will be able to mint an Optimist NFT – the inception of identity on Optimism
- Badgeholders with any participation or ownership stake in a project up for vote in RetroPGF 2 will be expected to not vote in favor of that project. Rules around conflicts of interest will be itemized in a forthcoming Badgeholder Manual
- Citizens’ House adheres to “one person one vote.” If an individual falls into more than one category above or is nominated more than once, they will still receive only one voting badge
Badgeholders will be added to a public channel on Discord for announcements and discussion.
## Voting
Each badgeholder will be asked to vote during a ten-day window from Feb 28 - Mar 14.
Voters will be able to filter, sort, and search projects.
Projects will be displayed with their answers to the application questions included in the project profile created via the RPGF Application Manager.
Votes will be aggregated as a simple weighted average, not as a quadratic vote.
Votes are submitted via a form and will only be accessible to the Foundation, which means an individual Citizen’s vote will not be public by default.
Once a vote is cast, the Citizen cannot update their vote.
---
title: Token House History
lang: en-US
---
## Seasons and voting cycles
Token House governance operates on a seasonal schedule.
Season two begins on August 25th, 2022, with Voting Cycle Five.
Seasons are separated with a three-week Reflection Period.
During each Season, Token House voting occurs via three-week voting cycles.
Voting Cycles begin on Thursday at 19:00p GMT (12p PST) and end on Wednesday at 19:00 GMT (12p PST).
At the beginning of a Season there may be a two-week voting cycle for policy and document proposals that come out of the previous Reflection Period.
During Reflection Periods, the Optimism Foundation will publish a proposed changelog to the Operating Manual and a set of governance proposals aimed at addressing the feedback received in the previous season.
There will be no active votes during Reflection Periods, so that delegates and the broader community have time to provide critical feedback to improve the next season.
Reflection periods are expected to be followed by two-week Special Voting Cycles during which we vote on proposals to modify the collective's structure.
You can see up to date information about the seasons and voting cycles in [the governance calendar](https://calendar.google.com/calendar/u/0/r?cid=Y180aHVpNzBpdG0wODllN3Q4cTUwaGVoMWtub0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t).
::: tip At the time of writing we in Season #2: August 25th - Nov 9th, 2022
Followed by:
- Reflection Period #2: Nov 10th - Nov 30th, 2022
- Season #3: Dec 1st, 2022 - Feb 15th, 2023
:::
## History
<!--
### Season #3: Dec 1st, 2022 - Feb 15th, 2023
* Voting system #12 (Jan 26th - Feb 15th)
* Voting system #11 (Jan 5th - Jan 25th)
* Voting system #10 (Dec 15th, 2022 - Jan 4th, 2023)
### Voting Cycle #9: Dec 1st - Dec 14th, 2022
### Reflection Period #2: Nov 10th - Nov 30th, 2022
-->
### Season #2: Aug 25th - Nov 9th, 2022
<!--
* Voting cycle #8 (Oct 20th - Nov 9th)
-->
* Voting cycle #7 (Sep 29th - Oct 19th)
* [Voting cycle #6 (Sep 8th - Sep 28th)](https://gov.optimism.io/t/voting-cycle-6-roundup/3481)
* Voting Cycle #5 (Aug 25th - Sep 7th):
Changes made during the Reflection Period are voted on during two-week Special Voting Cycles.
### Reflection Period #1
The reflection period was a break between season 1 and season 2 where the Optimism foundation, with the help of delegates and community members, updated and improved the governance process.
Document changes:
- [Operating Manual v2](https://gov.optimism.io/t/operating-manual-of-the-optimism-collective-v0-2-0/3370/8)
- [Grant Proposal Template v2](https://gov.optimism.io/t/grant-proposal-template/3233/15)
- [Introducing Governance Committees](https://gov.optimism.io/t/introducing-governance-committees/3238/60)
- [Committee Formation Proposal Template](https://gov.optimism.io/t/phase-1-committee-formation-proposal-template/3281/9)
### Season #1
* [Voting cycle #4](https://gov.optimism.io/t/voting-cycle-4-roundup/3055)
* [Voting cycle #3](https://gov.optimism.io/t/voting-cycle-3-roundup/2923)
* [Voting cycle #2](https://gov.optimism.io/t/voting-cycle-2-roundup/2754)
* [Voting cycle #1](https://gov.optimism.io/t/voting-cycle-1-roundup/2619)
---
title: Governing Documents
lang: en-US
---
[The Working Constitution](https://gov.optimism.io/t/working-constitution-of-the-optimism-collective/55) outlines governing provisions and principles for the Collective.
It will remain in effect no more than four years from the date of its adoption.
After that, authority over governance will be ceded to a permanent Bedrock Constitution that incorporates the lessons of the Collective’s prior governance experiments.
The Collective’s [Operating Manual](https://github.com/ethereum-optimism/OPerating-manual/blob/main/manual.md) describes current governance processes for the Token House.
It will evolve, with the Collective, over time.
The version linked here, from [The Optimism Foundation's github repository](https://github.com/ethereum-optimism/OPerating-manual), is the authoritative one.
---
title: Design Philosophy
lang: en-US
---
Optimism is built according to a strong design philosophy that stands on four main pillars: simplicity, pragmatism, sustainability, and, of course, optimism.
It's important to understand these pillars as they heavily influence the design of Optimism as a whole.
## Simplicity
Optimism is designed to be as simple as possible for the featureset it provides.
Ideally, Optimism should be composed of the minimum number of moving parts required for a secure, scalable, and flexible L2 system.
This simplicity gives Optimism's design a number of significant advantages over other more complex L2 constructions.
Simplicity reduces engineering overhead, which in turn means we can spend our time working on new features instead of re-creating existing ones.
Optimism prefers to use existing battle-tested Ethereum code and infrastructure where possible.
The most visible example of this philosophy in practice is the choice to use Geth as Optimism's client software.
When dealing with critical infrastructure, simplicity is also security.
Every line of code we write is an opportunity to introduce unintentional bugs.
A simple protocol means there's less code to write and, as a result, less surface area for potential mistakes.
A clean and minimal codebase is also more accessible to external contributors and auditors.
All of this serves to maximize the security and correctness of the Optimism protocol.
Simplicity is also important for the long-term vision of Optimism.
By limiting the amount of code that we write on top of Ethereum tooling, we're able to spend most of our time working directly with existing codebases.
Engineering effort that goes into Optimism can also directly benefit Ethereum, and vice versa.
This will only become more pronounced as the Optimism protocol solidifies and existing resources can be redirected towards core Ethereum infrastructure.
## Pragmatism
For all its idealism, the design process behind Optimism is ultimately driven by pragmatism.
The core Optimism team has real-world constraints, the projects that build on Optimism have real-world needs, and the users that engage with Optimism have real-world problems.
Optimism's design philosophy prioritizes user and developer needs over theoretical perfection.
Sometimes the best solution isn't the prettiest one.
Optimism is also developed with the understanding that any core team will have limited areas of expertise.
Optimism is developed iteratively and strives to continously pull feedback from users.
Many core Optimism features today (like [EVM Equivalence](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306)) were only made possible by this iterative approach to protocol development.
## Sustainability
Optimism is in it for the long haul.
Application developers need assurance that the platform they're building on will remain not only operational but competitive over long periods of time.
Optimism's design process is built around the idea of long-term sustainability and not taking shortcuts to scalability.
At the end of the day, a scalable system means nothing without the ecosystem that sustains it.
Sustainability actively influences Optimism's protocol design in ways that go hand-in-hand with our philosophy of simplicity.
The more complex a codebase, the more difficult it is for people outside of the core development team to actively contribute.
By keeping our codebase simple we're able to build a bigger community of contributors who can help maintain the protocol long-term.
## Optimism
Of course, none of this would be possible without a sense of optimism.
Our optimism about the Ethereum vision keeps this project moving forward.
We believe in an optimistic future for Ethereum, a future where we get to redesign our relationships to the institutions that coordinate our lives.
Although Optimism looks like a standalone blockchain, it's ultimately designed as an extension to Ethereum.
We keep this in mind whenever we're creating new features or trying to simplify existing ones.
Optimism is as close to Ethereum as possible not only for pragmatic reasons, but because Optimism exists so that Ethereum can succeed.
We hope that you can see the influence of this philosophy when looking at Optimism's design.
---
title: Rollup Protocol
lang: en-US
---
The big idea that makes Optimism possible is the Optimistic Rollup.
We'll go through a brief explainer of *how* Optimistic Rollups work at a high level.
Then we'll explain *why* Optimism is built as an Optimistic Rollup and why we believe it's the best option for a system that addresses all of our design goals.
## Optimistic Rollups TL;DR
Optimism is an "Optimistic Rollup," which is basically just a fancy way of describing a blockchain that piggy-backs off of the security of another "parent" blockchain.
Specifically, Optimistic Rollups take advantage of the consensus mechanism (like PoW or PoS) of their parent chain instead of providing their own.
In Optimism's case this parent blockchain is Ethereum.
<div align="center">
<img width="400" src="../../assets/docs/how-optimism-works/1.png">
</div>
## Block storage
<details>
<summary><b>Pre-Bedrock (current version)</b></summary>
All Optimism blocks are stored within a special smart contract on Ethereum called the [`CanonicalTransactionChain`](https://etherscan.io/address/0x5E4e65926BA27467555EB562121fac00D24E9dD2) (or CTC for short).
Optimism blocks are held within an append-only list inside of the CTC (we'll explain exactly how blocks are added to this list in the next section).
This append-only list forms the Optimism blockchain.
The `CanonicalTransactionChain` includes code that guarantees that the existing list of blocks cannot be modified by new Ethereum transactions.
However, this guarantee can be broken if the Ethereum blockchain itself is reorganized and the ordering of past Ethereum transactions is changed.
The Optimism mainnet is configured to be robust against block reorganizations of up to 50 Ethereum blocks.
If Ethereum experiences a reorg larger than this, Optimism will reorg as well.
Of course, it's a key security goal of Ethereum to not experience these sort of significant block reorganizations.
Optimism is therefore secure against large block reorganizations as long as the Ethereum consensus mechanism is too.
It's through this relationship (in part, at least) that Optimism derives its security properties from Ethereum.
</details>
<details>
<summary><b>Bedrock (coming Q1 2023)</b></summary>
In Bedrock L2 blocks are saved to the Ethereum blockchain using a non-contract address ([`0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0001`](https://etherscan.io/address/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0001)), to minimize the L1 gas expense.
As these blocks are submitted as transaction calldata on Ethereum, there is no way to modify or censor them after the "transaction" is included in a block that has enough attestations.
This is the way that Optimism inherits the availability and integrity guarantees of Ethereum.
Blocks are written to L1 in [a compressed format](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#batch-submission-wire-format) to reduce costs.
This is important because writing to L1 is [the major cost of Optimism transactions](../developers/build/transaction-fees.md).
</details>
## Block production
Optimism block production is primarily managed by a single party, called the "sequencer," which helps the network by providing the following services:
- Providing transaction confirmations and state updates.
- Constructing and executing L2 blocks.
- Submitting user transactions to L1.
<details>
<summary><b>Pre-Bedrock (current version)</b></summary>
The sequencer has no mempool and transactions are immediately accepted or rejected in the order they were received.
When a user sends their transaction to the sequencer, the sequencer checks that the transaction is valid (i.e. pays a sufficient fee) and then applies the transaction to its local state as a pending block.
These pending blocks are periodically submitted in large batches to Ethereum for finalization.
This batching process significantly reduces overall transaction fees by spreading fixed costs over all of the transactions within a given batch.
The sequencer also applies some basic compression techniques to minimize the amount of data published to Ethereum.
Because the sequencer is given priority write access to the L2 chain, the sequencer can provide a strong guarantee of what state will be finalized as soon as it decides on a new pending block.
In other words, it is precisely known what will be the impact of the transaction.
As a result, the L2 state can be reliably updated extremely quickly.
Benefits of this include a snappy, instant user experience, with things like near-real-time Uniswap price updates.
Alternatively, users can skip the sequencer entirely and submit their transactions directly to the `CanonicalTransactionChain` via an Ethereum transaction.
This is typically more expensive because the fixed cost of submitting this transaction is paid entirely by the user and is not amortized over many different transactions.
However, this alternative submission method has the advantage of being resistant to censorship by the sequencer.
Even if the sequencer is actively censoring a user, the user can always continue to use Optimism and recover any funds through this mechanism.
</details>
<details>
<summary><b>Bedrock (coming Q1 2023)</b></summary>
In Bedrock the sequencer does have a mempool, similar to L1 Ethereum, but the mempool is private to avoid opening opportunities for MEV.
Blocks are produced every two seconds, regardless of whether they are empty (no transactions), filled up to the block gas limit with transactions, or anything in between.
Transactions get to the sequencer in two ways:
1. Transactions submitted on L1 (called *deposits* whether they have assets attached or not) are included in the chain in the appropriate L2 block.
Every L2 block is identified by the "epoch" (the L1 block to which it corresponds, which typically has happened a few minutes before the L2 block) and its serial number within that epoch.
The first block of the epoch includes all the deposits that happened in the L1 block to which it corresponds.
If the sequencer attempts to ignore a legitimate L1 transaction it ends up with a state that is inconsistent with the verifiers, same as if the sequencer tried to fake the state by other means.
This provides Optimism with L1 Ethereum level censorship resistance.
You can read more about this mechanism [is the protocol specifications](https://github.com/ethereum-optimism/optimism/blob/develop/specs/derivation.md#deriving-the-transaction-list).
1. Transactions submitted directly to the sequnecer.
These transactions are a lot cheaper to submit (because you do not need the expense of a separate L1 transaction), but of course they cannot be made censorship resistant, because the sequencer is the only entity that knows about them.
</details>
For the moment, [The Optimism Foundation](https://www.optimism.io/) runs the only block producer. Refer to [Protocol specs](../protocol/README.md) section for more information about how we plan to decentralize the Sequencer role in the future.
## Block execution
<details>
<summary><b>Pre-Bedrock (current version)</b></summary>
Ethereum nodes download blocks from Ethereum's p2p network.
Optimism nodes instead download blocks directly from the append-only list of blocks held within the `CanonicalTransactionChain` contract.
See the above section regarding [block storage](#block-storage) for more information about how blocks are stored within this contract.
Optimism nodes are made up of two primary components, the Ethereum data indexer and the Optimism client software.
The Ethereum data indexer, also called the ["data transport layer"](https://github.com/ethereum-optimism/optimism/tree/develop/packages/data-transport-layer) (or DTL), reconstructs the Optimism blockchain from blocks published to the `CanonicalTransactionChain` contract.
The DTL searches for events emitted by the `CanonicalTransactionChain` that signal that new Optimism blocks have been published.
It then inspects the transactions that emitted these events to reconstruct the published blocks in the [standard Ethereum block format](https://ethereum.org/en/developers/docs/blocks/#block-anatomy).
The second part of the Optimism node, the Optimism client software, is an almost completely vanilla version of [Geth](https://github.com/ethereum/go-ethereum).
This means Optimism is close to identical to Ethereum under the hood.
In particular, Optimism shares the same [Ethereum Virtual Machine](https://ethereum.org/en/developers/docs/evm/), the same [account and state structure](https://ethereum.org/en/developers/docs/accounts/), and the same [gas metering mechanism and fee schedule](https://ethereum.org/en/developers/docs/gas/).
We refer to this architecture as ["EVM Equivalence"](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) and it means that most Ethereum tools (even the most complex ones) "just work" with Optimism.
The Optimism client software continuously monitors the DTL for newly indexed blocks.
When a new block is indexed, the client software will download it and execute the transactions included within it.
The process of executing a transaction on Optimism is the same as on Ethereum: we load the Optimism state, apply the transaction against that state, and then record the resulting state changes.
This process is then repeated for each new block indexed by the DTL.
</details>
<details>
<summary><b>Bedrock (coming Q1 2023)</b></summary>
The execution engine (implemented as the `op-geth` component) receive blocks using two mechanisms:
1. The execution engine can update itself using peer to peer network with other execution engines.
This operates the same way that the L1 execution clients synchronize the state across the network.
You can read more about it [in the specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md#happy-path-sync).
1. The rollup node (implemented as the `op-node` component) derives the L2 blocks from L1.
This mechanism is slower, but censorship resistant.
You can read more about it [in the specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/exec-engine.md#worst-case-sync).
</details>
## Bridging assets between layers
Optimism is designed so that users can send arbitrary messages between smart contracts on Optimism and Ethereum.
This makes it possible to transfer assets, including ERC20 tokens, between the two networks.
The exact mechanism by which this communication occurs differs depending on the direction in which messages are being sent.
Optimism uses this functionality in the Standard bridge to allow users to deposit assets (ERC20s and ETH) from Ethereum to Optimism and also allow withdrawals of the same from Optimism back to Ethereum.
See the [developer documentation and examples](../developers/bridge/standard-bridge/) on details on the inner workings of the Standard bridge.
### Moving from Ethereum to Optimism
In Optimism terminology, transactions going from Ethereum (L1) to Optimism (L2) are called *deposits*, even if they do not have any assets attached to them.
<details>
<summary><b>Pre-Bedrock (current version)</b></summary>
To send messages from Ethereum to Optimism, users simply need to trigger the `CanonicalTransactionChain` contract on Ethereum to create a new block on Optimism block.
See the above section on [block production](#block-production) for additional context.
User-created blocks can include transactions that will appear to originate from the address that generated the block.
</details>
<details>
<summary><b>Bedrock (coming Q1 2023)</b></summary>
The contract interface for deposits is very similar, you use [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-comm) or [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol).
Deposit transactions become part of the canonical blockchain in the first L2 block of the "epoch" corresponding to the L1 block where the deposits were made.
This L2 block will usually be created a few minutes after the corresponding L1 block.
You can read more about this [in the specs](https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md).
</details>
### Moving from Optimism to Ethereum
<details>
<summary><b>Pre-Bedrock (current version)</b></summary>
It's not possible for contracts on Optimism to easily generate transactions on Ethereum in the same way as Ethereum contracts can generate transactions on Optimism.
As a result, the process of sending data from Optimism back to Ethereum is somewhat more involved.
Instead of automatically generating authenticated transactions, we must instead be able to make provable statements about the state of Optimism to contracts sitting on Ethereum.
Making provable statements about the state of Optimism requires a [cryptographic commitment](https://en.wikipedia.org/wiki/Commitment_scheme) in the form of the root of the Optimism's [state trie](https://medium.com/@eiki1212/ethereum-state-trie-architecture-explained-a30237009d4e).
Optimism's state is updated after each block, so this commitment will also change after every block.
Commitments are regularly published (approximately once or twice per hour) to a smart contract on Ethereum called the [`StateCommitmentChain`](https://etherscan.io/address/0xBe5dAb4A2e9cd0F27300dB4aB94BeE3A233AEB19).
Users can use these commitments to generate [Merkle tree proofs](https://en.wikipedia.org/wiki/Merkle_tree) about the state of Optimism.
These proofs can be verified by smart contracts on Ethereum.
Optimism maintains a convenient cross-chain communication contract, the [`L1CrossDomainMessenger`](https://etherscan.io/address/0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1), which can verify these proofs, typically used for withdrawals, on behalf of other contracts.
These proofs can be used to make verifiable statements about the data within the storage of any contract on Optimism at a specific block height.
This basic functionality can then be used to enable contracts on Optimism to send messages to contracts on Ethereum.
The [`L2ToL1MessagePasser`](https://explorer.optimism.io/address/0x4200000000000000000000000000000000000000) contract (predeployed to the Optimism network) can be used by contracts on Optimism to store a message in the Optimism state.
Users can then prove to contracts on Ethereum that a given contract on Optimism did, in fact, mean to send some given message by showing that the hash of this message has been stored within the `L2ToL1MessagePasser` contract.
</details>
<details>
<summary><b>Bedrock (coming Q1 2023)</b></summary>
Withdrawals (the term is used for any Optimism to Ethereum message, regardless of whether it has attached assets or not) have three stages:
1. You initialize withdrawals with an L2 transaction.
1. Wait for the next output root to be submitted to L1 (you can see this on [the SDK](../sdk/js-client.md)) and then submit the withdrawal proof using `proveWithdrawalTransaction`.
This new step enables off-chain monitoring of the withdrawals, which makes it easier to identify incorrect withdrawals or output roots.
This protects Optimism users against a whole class of potential bridge vulnerabilities.
1. After the fault challenge period ends (a week on mainnet, less than that on the test network), finalize the withdrawal.
[You can read the full withdrawal specifications here](https://github.com/ethereum-optimism/optimism/blob/develop/specs/withdrawals.md)
</details>
## Fault proofs
In an Optimistic Rollup, state commitments are published to Ethereum without any direct proof of the validity of these commitments.
Instead, these commitments are considered pending for a period of time (called the "challenge window").
If a proposed state commitment goes unchallenged for the duration of the challenge window (currently set to 7 days), then it is considered final.
Once a commitment is considered final, smart contracts on Ethereum can safely accept withdrawal proofs about the state of Optimism based on that commitment.
When a state commitment is challenged, it can be invalidated through a "fault proof" ([formerly known as a "fraud proof"](https://github.com/ethereum-optimism/optimistic-specs/discussions/53)) process.
If the commitment is successfully challenged, then it is removed from the `StateCommitmentChain` to eventually be replaced by another proposed commitment.
It's important to note that a successful challenge does not roll back Optimism itself, only the published commitments about the state of the chain.
The ordering of transactions and the state of Optimism is unchanged by a fault proof challenge.
The fault proof process is currently undergoing major redevelopment as a side-effect of the November 11th [EVM Equivalence](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) update.
You can read more about this process within the [Protocol specs](../protocol/README.md) section of this website.
---
title: Protocol specs
lang: en-US
---
With the OVM 2.0 upgrade, which happened on November 11th, 2021, the Optimism protocol went through its biggest upgrade to date. The primary focus of this upgrade was [EVM Equivalence](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), a new design for Optimism that brought it close to 1:1 parity with Ethereum. For a high level overview of the current protocol version, see ['How Optimism works'](./2-rollup-protocol.md) section.
## Roadmap
Below is a brief summary of some of the planned [Optimism roadmap](https://www.optimism.io/about) releases.
## Next gen fault proofs
As part of the OVM 2.0 upgrade, the **Optimism fault proof mechanism had to be temporarily disabled**. This means that users of the Optimism network currently need to trust the Sequencer node (run by Optimism PBC) to publish valid state roots to Ethereum. **You can read more about our security model [here](../security-model/optimism-security-model.md)**.
We're making progress on the upgrade fault proof mechanism and we expect to productionize our work in 2022. You can keep up with developments in the [Cannon repository](https://github.com/ethereum-optimism/cannon/).
## Decentralizing the sequencer
Currently, Optimism runs the sole sequencer on Optimism. This does not mean that Optimism can censor user transactions. However, it is still desirable to decentralize the sequencer over time, eliminating Optimism's role entirely so that anyone can participate in the network as a block producer.
The first step to decentralizing the sequencer is to still have one sequencer at a time, but rotate that sequencer with some frequency. The precise mechanic for sequencer rotation is not yet finalized, but will involve two components:
- an **economic mechanism** which creates a competitive market for sequencing, and redirects excess sequencer profits [towards protocol development](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c).
- a **governance mechanism** which prevents sequencers from prioritizing short-term profits over the long-term health of the network.
After this, the next step is to support multiple concurrent sequencers. This can be simply achieved by adopting a standard BFT consensus protocol, as used by other L1 protocols and sidechains like Polygon and Cosmos.
You can keep up with the roadmap progress in [Cannon repository](https://github.com/ethereum-optimism/cannon/) for the fault proofs and [Optimism specs repository](https://github.com/ethereum-optimism/optimistic-specs) for the overall protocol work.
\ No newline at end of file
---
title: The Cannonical Transaction Chain (CTC) Format
lang: en-US
---
Every transaction submitted to Optimism is written to the mainnet Ethereum blockchain as call data, this is how Optimism inherits the availability and integrity guarantees of Ethereum.
This is also the cause of the majority of the cost of Optimism transactions.
At the time of writing it is cheaper to write a kilobytes to storage on Optimism than it is to add one byte to the calldata on Ethereum.
## Initial solution
The initial solution was to write a header with supporting data followed by a list of transactions.
To interpret a transaction, you can search for it on [Etherscan](https://etherscan.io/).
To interpret a CTC transaction you need to **Click to see more** to see the calldata (called "Input Data" by Etherscan):
![Transaction input data](../../assets/docs/protocol/compressed-ctc/input-data.png)
For example, here are the fields and their values for this [initial solution CTC transaction](https://etherscan.io/tx/0xf5a2dd9d0815ad4dcee00063ff8f8f3fd44b3bd8ffc1f7f6c7f7f0b4b086c5a7/advanced):
| Bytes | Field Size | Field | Value | Comments |
| ---------: | ---------: | ------------------| ----- | -------- |
| 0-3 | 4 | Function signature | 0xd0f89344 | [appendSequencerBatch()](https://www.4byte.directory/signatures/?bytes4_signature=0xd0f89344) |
| 4-8 | 5 | Starting tx index | 4025992 | [this transaction](https://explorer.optimism.io/tx/4025992) |
| 9-11 | 3 | Elements to append | 89 |
| 12-14 | 3 | Batch contexts | 15 |
| 15-30 | 14 | **Context 0** (multiple fields) |
| 15-17 | 3 | Transactions sent directly to L2 | 3 |
| 18-20 | 3 | Deposits with this context | 0 |
| 21-25 | 5 | Timestamp | 1646146436 | `block.timestamp` for transactions in this context (Tue Mar 01 2022 14:53:56 UTC)
| 26-30 | 5 | L1 block number | The L1 block number in this context, as obtained by calling [OVM_L1BlockNumber](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/predeploys/iOVM_L1BlockNumber.sol). ([14301739](https://etherscan.io/block/14301739)) |
| 31-46 | 14 | **Context 1** |
| 31-33 | 3 | Transactions sent directly to L2 | 8 |
| 34-36 | 3 | Deposits | 0 |
| 37-41 | 5 | Timestamp | 1646146451 | 15 seconds after the previous batch
| 42-47 | 5 | L1 block number | [14301739](https://etherscan.io/block/14301739)
| 16n+15-16n+30 | 14 | **Context n** |
| 16n+15-16n+17 | 3 | Transactions sent directly to L2 |
| 16n+18-16n+20 | 3 | Deposits with this context
| 16n+21-16n+25 | 5 | Timestamp
| 16n+26-16n+30 | 5 | L1 block number
This transaction has 15 batch contexts (numbered 0-14), so the first byte after the last context in this transaction, which starts the transaction list, is `16*15+15=255`.
Transactions are provided as a three byte length followed by the RLP encoded transaction.
Looking at locations 255-257, we see `0x00016e`, so the first transaction is 366 bytes long, at locations 258-623.
The next transaction length starts at byte 624, and the next transaction itself starts at byte 627.
## Additional batch types
The initial solution did not have a batch type.
However, to modify the format (for example, to add compression) a batch type is needed.
The solution is to set the timestamp of the first context to zero.
This does not create ambiguity because a timestamp of zero represents January 1st, 1970 (based on the UNIX convention), which cannot happen.
The block number then represents the transaction type.
## CTC transaction type zero
After the normal header and the first context, which has a timestamp of zero and a block number of zero, the other contexts contain the normal data.
After that the list of transaction lengths and transaction data is compressed using [zlib](https://nodejs.org/api/zlib.html).
---
title: Contract Overview
lang: en-US
---
## Introduction
<!-- - Welcome! Give context -- "how to read these docs" -->
Optimism is a Layer 2 scaling protocol for Ethereum applications.
I.e., it makes transactions cheap. Real cheap.
We aim to make transacting on Ethereum affordable and
accessible to anyone.
This document is intended for anyone looking for a deeper understanding of how the protocol works
'under the hood'.
Optimism is meant to look, feel and behave like Ethereum but cheaper and faster.
For developers building on our Optimism, we aim to make the transition as seamless as possible.
With very few exceptions,
existing Solidity smart contracts can run on L2 exactly how they run on L1.
Similarly, off-chain code (ie. UIs and wallets), should be able to interact with L2 contract with little more than an updated RPC endpoint.
## System Overview
The smart contracts in the Optimism protocol can be separated into a few key components. We will discuss each component in more detail below.
- **[Chain:](#chain-contracts)** Contracts on layer-1, which hold the ordering of layer-2 transactions, and commitments to the associated layer-2 state roots.
- **[Verification:](#verification)** Contracts on layer-1 which implement the process for challenging a transaction result.
- **[Bridge:](#bridge-contracts)** Contracts which facilitate message passing between layer-1 and layer-2.
- **[Predeploys:](#predeployed-contracts)** A set of essential contracts which are deployed and available in the genesis state of the system. These contracts are similar to Ethereum's precompiles, however they are written in Solidity, and can be found at addresses prefixed with 0x42.
## Chain Contracts
The Chain is composed of a set of contracts running on the Ethereum mainnet. These contracts store ordered
lists of:
1. An _ordered_ list of all transactions applied to the L2 state.
2. The proposed state root which would results from the application of each transaction.
3. Transactions sent from L1 to L2, which are pending inclusion in the ordered list.
<!--
**Planned section outline**
- Delineation between CTC and SCC,
- **high priority**: explain once and for all that challenges roll back state roots, but NOT transactions
- Diagram of "the chains" and what is stored on chain -- ideally illustrates the "roll up" mechanism whereby only roots of batches are SSTOREd
- Sequencing -- what are the properties, what are the implications
- Ring buffer?? (lean deprioritize)
-->
The chain is composed of the following concrete contracts:
<!-- concrete contracts stackex : -->
### [`CanonicalTransactionChain`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L1/rollup/CanonicalTransactionChain.sol) (CTC)
The Canonical Transaction Chain (CTC) contract is an append-only log of transactions which must be applied to the OVM state. It defines the ordering of transactions by writing them to the `CTC:batches` instance of the Chain Storage Container. The CTC also allows any account to `enqueue()` an L2 transaction, which the Sequencer must eventually append to the rollup state.
### [`StateCommitmentChain`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L1/rollup/StateCommitmentChain.sol) (SCC)
The State Commitment Chain (SCC) contract contains a list of proposed state roots which Proposers assert to be a result of each transaction in the Canonical Transaction Chain (CTC). Elements here have a 1:1 correspondence with transactions in the CTC, and should be the unique state root calculated off-chain by applying the canonical transactions one by one.
### [`ChainStorageContainer`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L1/rollup/ChainStorageContainer.sol)
Provides reusable storage in the form of a "Ring Buffer" data structure, which will overwrite storage slots that are no longer needed. There are three Chain Storage Containers deployed, two are controlled by the CTC, one by the SCC.
<!-- stackex: TODO - create a stackexchange Q and A, to make this term real. -->
## Verification
In the previous section, we mentioned that the Chain includes a list of the _proposed_ state roots
resulting from each transaction. Here we explain a bit more about how these proposals happen, and how
we come to trust them.
In brief: If a proposed state root is not the correct result of executing a transaction, then a Verifier (which is anyone running an Optimism 'full node') can initiate a transaction result challenge. If the transaction result is successfully proven to be incorrect, the Verifier will receive a reward taken from funds which a Sequencer must put up as a bond.
::: Notice
This system is still being written, so these details are likely to
change
:::
### [`BondManager`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L1/verification/BondManager.sol)
The Bond Manager contract handles deposits in the form of an ERC20 token from bonded Proposers. It also handles the accounting of gas costs spent by a Verifier during the course of a challenge. In the event of a successful challenge, the faulty Proposer's bond is slashed, and the Verifier's gas costs are refunded.
## Bridge Contracts
The Bridge contracts implement the functionality required to pass messages between layer 1 and layer 2. [You can read an overview
here](/docs/developers/bridge/messaging.html)
<!--
**Planned section outline**
- Low-level tools (ovmL1TXORIGIN, state committment access)
### Key concepts
- **Relaying** refers to executing a message sent from the other domain, ie. "this message was relayed
-->
The Bridge is composed of the following concrete contracts:
### [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L1/messaging/L1CrossDomainMessenger.sol)
The L1 Cross Domain Messenger (L1xDM) contract sends messages from L1 to L2, and relays messages from L2 onto L1. In the event that a message sent from L1 to L2 is rejected for exceeding the L2 epoch gas limit, it can be resubmitted via this contract's replay function.
### [`L2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L2/messaging/L2CrossDomainMessenger.sol)
The L2 Cross Domain Messenger (L2xDM) contract sends messages from L2 to L1, and is the entry point for L2 messages sent via the L1 Cross Domain Messenger.
## The Standard Bridge
One common case of message passing is "transferring" either ERC-20
tokens or ETH between L1 and Optimism. To deposit tokens
into Optimism, the bridge locks them on L1 and mints equivalent
tokens in Optimism. To withdraw tokens, the bridge burns the
Optimism tokens and releases the locked L1 tokens. [More details
are here](/docs/developers/bridge/standard-bridge.html)
### [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L1/messaging/L1StandardBridge.sol)
The L1 part of the Standard Bridge. Responsible for finalising withdrawals from L2 and initiating deposits into L2 of ETH and compliant ERC20s.
### [`L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L2/messaging/L2StandardBridge.sol)
The L2 part of the Standard Bridge. Responsible for finalising deposits from L1 and initiating withdrawals from L2 of ETH and compliant ERC20s.
### [`L2StandardTokenFactory`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L2/messaging/L2StandardTokenFactory.sol)
Factory contract for creating standard L2 token representations of L1 ERC20s compatible with and working on the standard bridge.
[See here for more information](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/standard-bridge-standard-token).
## Predeployed Contracts
"Predeploys" are a set of essential L2 contracts which are deployed and available in the genesis state of the system. These contracts are similar to Ethereum's precompiles, however they are written in Solidity and can be found in the OVM at addresses prefixed with 0x42.
Looking up predeploys is available in the Solidity library [`Lib_PredeployAddresses`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/libraries/constants/Lib_PredeployAddresses.sol) as well as in the `@eth-optimism/contracts` package as `predeploys` export.
The following concrete contracts are predeployed:
### [`OVM_L1MessageSender`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L2/predeploys/iOVM_L1MessageSender.sol)
The L1MessageSender is a predeployed contract running on L2.
During the execution of cross domain transaction from L1 to L2, it returns the address of the L1 account (either an EOA or contract) which sent the message to L2 via the Canonical Transaction Chain's `enqueue()` function.
Note that this contract is not written in Solidity. However,
the interface linked above still works as if it were. In this way
it is similar to the EVM's predeploys.
### [`OVM_L2ToL1MessagePasser`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L2/predeploys/OVM_L2ToL1MessagePasser.sol)
The L2 to L1 Message Passer is a utility contract which facilitate an L1 proof of a message on L2. The L1 Cross Domain Messenger performs this proof in its _verifyStorageProof function, which verifies the existence of the transaction hash in this contract's `sentMessages` mapping.
<!--
### [`OVM_SequencerEntrypoint`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_SequencerEntrypoint.sol)
The Sequencer Entrypoint is a predeploy which, despite its name, can in fact be called by any account. It accepts a more efficient compressed calldata format, which it decompresses and encodes to the standard EIP155 transaction format. This contract is the implementation referenced by the Proxy Sequencer Entrypoint, thus enabling the Optimism team to upgrade the decompression of calldata from the Sequencer.
-->
### [`OVM_SequencerFeeVault`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/L2/predeploys/OVM_SequencerFeeVault.sol)
This contract holds fees paid to the sequencer until there is enough to
justify the transaction cost of sending them to L1 where they are used to
pay for L1 transaction costs (mostly the cost of publishing all L2 transaction
data as CALLDATA on L1).
<!-- we already have this above
### [`OVM_L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/optimistic-ethereum/OVM/bridge/tokens/OVM_L2StandardBridge.sol)
The L2 part of the Standard Bridge. Responsible for finalising deposits from L1 and initiating withdrawals from L2 of ETH and compliant ERC20s.
See [Standard Bridge](../developers/bridge/standard-bridge.md) for details.
-->
<!--
### [`OVM_ExecutionManagerWrapper`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_ExecutionManagerWrapper.sol)
This is the one contract on L2 that can call another contract without having to
go through virtualization. It is used to call
OVM_ExecutionManager ovm-executionmanager.
-->
<!--
### [`ERC1820Registry`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/ERC1820Registry.sol)
[ERC1820](https://eips.ethereum.org/EIPS/eip-1820) specifies a registry
service that lets addresses report what interfaces they support and ask
about other addresses.
-->
### [`Lib_AddressManager`](https://github.com/ethereum-optimism/optimism/blob/master/packages/contracts/contracts/libraries/resolver/Lib_AddressManager.sol)
This is a library that stores the mappings between names and their addresses.
It is used by `L1CrossDomainMessenger`.
<!-- I don't know why check-md thinks this is a problem link -->
<!--
### [`OVM_ExecutionManagerWrapper`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/OVM_ExecutionManagerWrapper.sol)
This is the one contract on L2 that can call another contract without having to
go through virtualization. It is used to call
OVM_ExecutionManager ovm-executionmanager .
### [`ERC1820Registry`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/optimistic-ethereum/OVM/predeploys/ERC1820Registry.sol)
[ERC1820](https://eips.ethereum.org/EIPS/eip-1820) specifies a registry
service that lets addresses report what interfaces they support and ask
about other addresses.
-->
<script>
// This redirect is here because search engines still know
// https://community.optimism.io/docs/protocol/sequencing/
// See https://github.com/ethereum-optimism/community-hub/issues/377
export default {
mounted () {
window.location.href="/docs/protocol/#decentralizing-the-sequencer"
}
}
</script>
---
title: SDK
lang: en-US
---
In most ways Optimism is [EVM equivalent](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306).
However, the are [a few differences](../developers/build/differences/), which sometimes require decentralized applications to access Optimism-specific services.
For example, decentralized applications might need to estimate gas costs.
The standard Ethereum tooling assumes that gas cost is proportional to the gas used by the transaction, which is correct on L1, but not on Optimism.
[Our gas costs are predominately the cost of writing the transaction to L1](../developers/build/transaction-fees.md), which depends on the transaction size, not the amount of processing required.
This difference requires us to have separate methods to provide gas estimates.
There are three ways to access Optimism services:
1. [On chain contract calls](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/docs).
This is the way your contracts can get Optimism information or services directly.
1. [The JavaScript SDK](js-client.md). For use when you write JavaScript or TypeScript code, either in the client or a Node.js server.
1. [Off chain, using RPC](../developers/build/json-rpc.md). Which is more complicated but usable from any development stack (Python, Rust, etc.).
::: tip Improving the SDK
If you find a bug, or if there's a feature you think we should add, there are several ways to inform us.
- [Go on our Discord](https://discord-gateway.optimism.io/), and then ask in **#dev-support**.
- Submit an issue on [our Github](https://github.com/ethereum-optimism/optimism/issues).
:::
---
title: Alchemy SDK
lang: en-US
---
In addition to the standard [Ethereum RPC interface](../useful-tools/networks.md), Alchemy makes available an SDK.
- [Quickstart](https://docs.alchemy.com/reference/alchemy-sdk-quickstart/?a=818c11a8da)
- [Examples](https://docs.alchemy.com/reference/optimism-sdk-examples/?a=818c11a8da)
---
title: The Optimism Client SDK
lang: en-US
---
There are a few areas in which the SDK can help you navigate [the differences between Optimism and Ethereum](../developers/build/differences/):
- [Gas costs](../developers/build/transaction-fees.md)
- [Interlayer communication](../developers/bridge/basics.md)
## JavaScript SDK
[Reference](https://sdk.optimism.io/)
Tutorials:
- [Viewing transactions between layers](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/sdk-view-tx)
- [Bridging ETH with the Optimism SDK](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-bridge-eth)
- [Bridging ERC-20 with the Optimism SDK](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/cross-dom-bridge-erc20)
- [Estimate the costs of an Optimistic (L2) transaction](https://github.com/ethereum-optimism/optimism-tutorial/tree/main/sdk-estimate-gas)
\ No newline at end of file
---
title: Optimism's Security Model
lang: en-US
---
Security in crypto is vital. Users, developers and infrastructure providers expect the code to work as intended. When it doesn't, these same groups (rightfully) demand a fast, secure, and clearly communicated fix.
Over the years at Optimism we have learned a great deal about this delicate balance, and the importance of transparency around security models. It is not good enough to just be transparent, it also needs to be accessible. If the average user cannot understand your security model then it does not matter how transparent it is.
In line with this philosophy of transparency we have a very accessible run down of [Optimism's security model](./optimism-security-model.md) that anyone can understand.
We also have a long standing [bug bounty program](./bounties.md) that has paid out one of the largest open source software bounties ever!
We take your trust very seriously and we are super excited to have you along for our decentralisation journey.
\ No newline at end of file
---
title: Bug Bounties
lang: en-US
---
## Optimism Bug Bounty Program
Optimism takes security seriously and as such, we have a massive bug bounty program. We don't just talk about it either! We have given out one of the [largest bounty payouts ever](https://medium.com/ethereum-optimism/disclosure-fixing-a-critical-bug-in-optimisms-geth-fork-a836ebdf7c94)! You can read more about that bug [here](https://www.saurik.com/optimism.html). Below are the various bug bounty programs we have, as well as how to reach out to us if your bug is not covered by an existing bounty.
### Main Bounty Page
Optimism has a very detailed [Bug bounty Page on Immunefi](https://immunefi.com/bounty/optimism/). In the listing you can find all the information relating to assets in scope, reporting, and the payout process.
### Bedrock Bounty
With our upcoming launch of [Bedrock](../developers/bedrock/how-is-bedrock-different.md) we will be launching a specific bug bounty program. Follow our [Twitter](https://twitter.com/optimismFND) or keep an eye on [Immunefi](https://immunefi.com/bounty/optimism/) for more information.
### Unscoped Bug
If you think you have found a critical or major bug that is not covered by our existing bug bounty, please report it to us via the Immunefi program regardless. We will seriously consider the impact of any issues, and have previously rewarded security researchers for bugs not within the stated scope of the program.
\ No newline at end of file
---
title: Optimism's Security Model
lang: en-US
---
The Optimism blockchain is a work in progress.
Constantly pushing to improve the security guarantees that users have while using Optimism is a top priority.
At the moment, **it’s important to understand that the security of the Optimism blockchain is dependent on a [multisig wallet](https://www.coindesk.com/tech/2020/11/10/multisignature-wallets-can-keep-your-coins-safer-if-you-use-them-right/)** managed by several anonymous individuals.
This multisig wallet can be used to upgrade core Optimism smart contracts without upgrade delays.
Please also keep in mind that just like any other system, **the Optimism codebase may contain unknown bugs** that could lead to the loss of some or all of the assets held within the system.
[Optimism’s smart contract codebase has been audited repeatedly](https://github.com/ethereum-optimism/optimism/tree/develop/technical-documents/audits) but **audits are not a stamp of approval** and **a completed audit does not mean that the audited codebase is free of bugs.**
It’s important to understand that using Optimism inherently exposes you to the risk of bugs within the Optimism codebase.
## Security Model FAQ
### Does Optimism have fault proofs?
**No**, Optimism does not currently have fault proofs.
**Fault proofs do not meaningfully improve the security of a system if that system can be upgraded within the 7 day challenge window (”fast upgrade keys”)**.
A system with fast upgrade keys, such as Optimism, is fully dependent on the upgrade keys for security.
Optimism’s goal is to be the first system that deploys fault proofs that can secure the system by themselves, without fast upgrade keys.
### Who manages the multisig?
The multisig is managed by an anonymous set of individuals.
Members are anonymous in order to make the multisig more difficult to compromise.
### How is Optimism planning to remove the multisig?
Check out Optimism’s detailed [Pragmatic Path to Decentralization](https://medium.com/ethereum-optimism/our-pragmatic-path-to-decentralization-cb5805ca43c1) post for a detailed view into how the multisig may be removed in a way that makes Optimism the first chain with true fault proof security.
### How can I help make Optimism more secure?
[Optimism has one of the biggest bug bounties (ever)](https://immunefi.com/bounty/optimism/).
You can earn up to $2,000,042 by finding critical bugs in the Optimism codebase.
You can also [run your own verifier node](https://github.com/smartcontracts/simple-optimism-node/) to detect network faults.
---
title: Developer tools
lang: en-US
---
Welcome to the Optimism developer tools!
If you are already familiar with [building on Optimism](../developers/README.md) and just need the tools to get cracking, you are in the right place!
## Connecting
You have the list of [networks](./networks.md) and [node providers](./providers.md) to help you connect to an Optimism node.
## Deploying
To get some ETH bridged over for deployment gas fees, use one of the available [faucets](./faucets.md) or just bridge ETH using the [Optimism Gateway](https://gateway.optimism.io/).
## Monitoring
Your app is live in production? Watch it grow using one of the [monitoring tools](./monitoring.md). We also have a set of [troubleshooting tools](./debugging.md) and [block explorers](./explorers.md) to help you provide a stellar service to your users.
---
title: Transaction Debugging Tools
lang: en-US
---
::: tip EVM Equivalence
As Optimism is [EVM Equivalent](https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), most debugging tools work out the box with Optimism.
:::
## Tenderly
[Tenderly](https://tenderly.co/) lets you inspect any transaction execution on the Optimism mainnet.
You can inspect the state of your verified contract in any step of the transactions execution, as well as step into or over function calls.
## Etherscan
[Etherscan](https://explorer.optimism.io) allows you to see events as well as revert messages for transactions that have been mined on Optimism (mainnet) as well as [Optimism Goerli (testnet)](https://goerli-explorer.optimism.io).
You can see more information about all the L2 features Etherscan offers in our [Explorers page](./explorers.md#etherscan).
## Blockscout
[Blockscout](https://blockscout.com/optimism/goerli/) allows you to see events on our Goerli testnet.
## Know other good tools?
Reach out to us in our [Discord](https://discord-gateway.optimism.io) or [make a PR](https://github.com/ethereum-optimism/community-hub/pulls).
We'd love your help in expanding the tools available to build Optimistically.
---
title: Block Explorers
lang: en-US
---
## Blockscout
We have a Blockscout explorer for the [Goerli testnet](https://blockscout.com/optimism/goerli/). It includes:
- [Verified contract source code, along with the ability to interact with it](https://blockscout.com/optimism/goerli/address/0x106941459A8768f5A92b770e280555FAF817576f/contracts#address-tabs)
- [Detailed transaction information](https://blockscout.com/optimism/goerli/tx/0xeb98c8279983cfee472c6067d2405acc130dca37e7536d6c83930e29aaa40e3e)
## Etherscan
We have Etherscan explorers for the [Optimism mainnet](https://explorer.optimism.io) and the [Optimism Goerli testnet](https://goerli-explorer.optimism.io).
Etherscan has lots of tools to help you debug transactions.
Optimistic Etherscan has all the tools you expect from Etherscan, such as:
- [Verified contract source code, along with the ability to interact with it](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#code)
- [Detailed transaction information](https://explorer.optimism.io/tx/0x292423266d6da24126dc4e0e81890c22a67295cc8b1a987e71ad84748511452f)
- And everything else you might find on Etherscan!
It's also got some Optimism-specific features:
- [A list of L1-to-L2 transactions](https://explorer.optimism.io/txsEnqueued)
- [A list of L2-to-L1 transactions](https://explorer.optimism.io/txsExit)
- [A tool for finalizing L2-to-L1 transactions](https://explorer.optimism.io/messagerelayer)
- And more! Just check it out and click around to find all of the available features.
## Access to pre-regenesis history
Because of our final regenesis on 11 November 2021, older transactions are not part of the current blockchain.
As such, they do not appear, for example, on [Etherscan](https://explorer.optimism.io/).
However, you **can** access transaction history between 23 June 2021 and the final regenesis using the Etherscan CSV exporting tool.
### Etherscan access
[Browse here](https://explorer.optimism.io/exportDataMain) and select your address and the type of report you want.
![export data](../../assets/docs/useful-tools/explorers/export.png)
### Dune access
[Click here](https://dune.com/optimismfnd/OVM1.0-User-Address-Transactions).
<!--
If none of the Etherscan CSV files contains the information you need, you can use a query on [Dune Analytics](https://dune.xyz), similar to [this query](https://dune.xyz/queries/354886?addr=%5Cx25E1c58040f27ECF20BBd4ca83a09290326896B3).
You have to log on with a Dune account, but their free tier is sufficient.
```sql
SELECT * FROM optimism.transactions
WHERE "from"='{{addr}}' or "to"='{{addr}}'
LIMIT 100
```
Notes:
- Make sure to select the data source **Optimism (OVM 1.0)**
- This is how you specify parameters in Dune, `{{` followed by the parameter name and then `}}`.
- Addresses are specified as `\x<hex address>` rather than `0x<hex address>`.
- The limit is not required, but here to save resources
-->
### Pre 23 June 2021 history
Follow these steps:
1. Clone go-ethereum (the standard version) and checkout version v1.9.10:
```sh
git clone https://github.com/ethereum/go-ethereum.git
cd go-ethereum
git checkout v1.9.10
```
1. Download the following three datadir folders:
- [Generation #1 (Jan to April)](https://storage.googleapis.com/sequencer-datadirs/datadir-gen1.zip)
- [Generation #2 (April to May)](https://storage.googleapis.com/sequencer-datadirs/datadir-gen2.zip)
- [Generation #3 (May to June)](https://storage.googleapis.com/sequencer-datadirs/datadir-gen3.zip)
1. Build and run the standard geth v1.9.10 with:
```sh
./build/bin/geth --datadir ./path/to/datadir --rpc
```
You can then use ERC20 events filters to get the events that you want for each address. Note that you will have to repeat this process for each datadir to get the full event history.
If you are non-technical and need help requesting this data please reach out to us in Discord and we will be happy to help.
---
title: Network Faucets
lang: en-US
---
## Testnet Faucets
#### Paradigm MultiFaucet
[Paradigm's MultiFaucet](https://faucet.paradigm.xyz/) is an easy way to get ETH on many different testnets at the same time.
One of those networks is Optimism Goerli.
#### Coinbase Wallet
Users of [Coinbase Wallet](https://www.coinbase.com/wallet) can get test ETH on Optimism Goerli directly from the wallet.
### Goerli Faucets
These are faucets you can use to get test ETH on Goerli.
We recommend getting Goerli ETH from Alchemy's faucet [here](https://goerlifaucet.com/?a=818c11a8da).
Alchemy's Goerli faucet provides 0.25 Goerli ETH per day.
Once you have that Goerli ETH, use [the Optimism Bridge](https://app.optimism.io/bridge) to get it on Optimism Goerli.
## Mainnet Faucets
If you need to get ETH on mainnet for a contract deployment, you should [bridge ETH onto L2](https://gateway.optimism.io/).
This process is relatively cheap and takes about 20 minutes to complete.
You can also get some ETH for expenses by [bridging enough USDC](https://optifaucet.com/) courtesy of [Perpetual Protocol](https://perp.com/).
---
title: Meta Transactions
lang: en-US
---
## What are those?
Meta-transactions let users sign transactions that are then submitted (and the gas paid for) by somebody else.
Optimism is a good place for meta-transactions because the low gas costs make it possible to explore business models that allow for payment for transactions by other means.
Here are some example use cases for meta-transactions:
- **On boarding**.
Users who aren't committed to web3 yet need to pay and go through [KYC](https://www.thalesgroup.com/en/markets/digital-identity-and-security/banking-payment/issuance/id-verification/know-your-customer) before they can do anything.
This is a bad initial experience that can cause people to decide they prefer to investigate something else, especially for non-
- **Privacy**.
It's easy to use a new address for privacy purposes.
It is a lot harder to transfer ETH to the new address privately without creating a connection between it and your identity.
- **Transaction payment by other means**.
With meta-transactions you can collect payment for transactions either using a different token (ERC-20) or a off-chain means (for example a credit card).
## OpenGSN
The [Gas Station Network](https://opengsn.org/) is distributed infrastructure for meta-transaction that allows you to create your own relay, or pay other relays to relay your users' traffic.
Relays have to post a bond, which they lose if they attempt to censor transactions (by pretending to accept them without sending them on chain).
See here for [their documentation](https://docs.opengsn.org/).
### Supported networks:
- [Optimism mainnet](https://docs.opengsn.org/networks/optimism/optimism.html)
- [Optimism Goerli](https://docs.opengsn.org/networks/optimism/goerli-optimism.html)
## Gelato
[Gelato](https://docs.gelato.network/developer-services/relay/what-is-relaying) uses a list of white-listed executors to relay transactions.
### Supported networks:
- Optimism mainnet
- Optimism Goerli
---
title: Monitoring
lang: en-US
---
## Status Page
Check out the [Optimism status page](https://status.optimism.io/).
This page includes public APIs, the gateway, deposits, withdraws and transaction sequencing for both Optimism mainnet and Optimism Goerli.
## Public Grafana Dashboard
[Optimism](https://optimism.io) maintains a [public Grafana dashboard](https://public-grafana.optimism.io/d/9hkhMxn7z/public-dashboard?orgId=1&refresh=5m) that tracks gas prices, transaction volume, and network uptime in one place.
You can view information about both the Optimism mainnet and the Optimism Goerli testnet on this dashboard.
## Block explorers
### Etherscan
Etherscan provides a lot of detailed information about what's happening on Optimism.
Check out the [Optimism mainnet explorer](https://explorer.optimism.io) as well as the [Optimism Goerli explorer](https://goerli-explorer.optimism.io) to get a look for yourself.
### Blockscout
Another block explorer for the Georli test network is [Blockscout](https://blockscout.com/optimism/goerli/).
## Dashboards on Dune Analytics
[Dune Analytics](https://dune.xyz) allows anyone to create dashboards that present information about Optimism Ethereum.
You can find a full list of community created dashboards for Optimism [here](https://dune.xyz/browse/dashboards?q=optimism), or [create your own](https://docs.dune.xyz/#queries) dashboard.
Here are some of our favorite dashboards so far:
- [General Optimism dashboard](https://dune.xyz/Marcov/Optimism-Ethereum)
- [Uniswap usage comparison, Ethereum vs. Optimism](https://dune.xyz/msilb7/Uniswap-v3-Usage-Comparison-on-Ethereum-vs-Optimism-(OVM-2.0))
---
title: Networks, Public RPC Endpoints, & APIs
lang: en-US
---
::: tip Developer Tip
We recommend using [Alchemy](https://www.alchemy.com/optimism) for its scalablity, reliability, and data accuracy.
:::
::: warning
Some API calls, such as the those in the [personal namespace](https://geth.ethereum.org/docs/rpc/ns-personal) make no sense in a shared environment.
Such RPCs are either totally unsupported, or will return nonsensical values.
:::
## Optimism (mainnet)
::: tip Pre-Bedrock
We are currently testing the [Bedrock](../developers/bedrock/bedrock.md) release.
The mainnet network has still not been updated.
:::
| Parameter | Value |
| --------- | ----- |
| Network Name | **`Optimism`** |
| Description | **`Mainnet`** |
| Chain ID | **`10`** |
| Explorer | **[https://explorer.optimism.io](https://explorer.optimism.io)** |
| HTTP Endpoint<sup>1</sup> | We recommend [Alchemy](https://docs.alchemy.com/reference/optimism-api-quickstart/?a=818c11a8da). Optimism also provides this endpoint: **`https://mainnet.optimism.io`.** _But it is not for production systems and is rate limited._ |
| L1 Contract Addresses | [link](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments/mainnet#layer-1-contracts) |
| L2 Contract Addresses | [link](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments/mainnet#layer-2-contracts) |
| chainid.link | [https://chainid.link/?network=optimism](https://chainid.link/?network=optimism)
::: tip Developer Tip
If you are seeing rate limit issues when testing with the public end point, or if you need websocket functionality, we recommend signing up for [Alchemy's](https://www.alchemy.com/optimism) free trial.
:::
(1) Some API calls, such as those in the [personal namespace](https://geth.ethereum.org/docs/rpc/ns-personal) make no sense in a shared environment.
Such RPCs are either not supported, or will return nonsensical values.
### API Options:
1. Get free access to Optimism through [Alchemy](https://www.alchemy.com/optimism)
2. For small scale tests, you can use our public API:
- HTTP endpoint: [https://mainnet.optimism.io](https://mainnet.optimism.io) (note, this is for testing. For production, use Alchemy)
You can run a large application for free using [Alchemy](https://www.alchemy.com/optimism). We’ve done extensive diligence and Alchemy is our recommendation due to reliability, scalability, and data correctness. They're the default API provider and developer platform for top projects like OpenSea and Facebook.
## Optimism Goerli
::: tip Purpose
This is our test network.
It has already been updated to the [Bedrock](../developers/bedrock/bedrock.md) release.
:::
| Parameter | Value |
| --------- | ----- |
| Network Name | **`Optimism Goerli`** |
| Description | **`Testnet (public)`** |
| Chain ID | **`420`** |
| Explorer | **[https://goerli-explorer.optimism.io](https://goerli-explorer.optimism.io)** |
| HTTP Endpoint | **`https://goerli.optimism.io`** |
| chainid.link | [https://chainid.link/?network=optimism-goerli](https://chainid.link/?network=optimism-goerli)
### Contract addresses
The authoritative list of contract addresses is [in the monorepo](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/deployments/goerli).
They are reproduced here for convenience
| Contract name | Address |
| - | -
| L1CrossDomainMessenger | [`0x5086d1eEF304eb5284A0f6720f79403b4e9bE294`](https://goerli.etherscan.io/address/0x5086d1eEF304eb5284A0f6720f79403b4e9bE294)
| L1ERC721Bridge | [`0x0F9C590b958002E8B10a7431979c1aF882772E88`](https://goerli.etherscan.io/address/0x0F9C590b958002E8B10a7431979c1aF882772E88)
| L1StandardBridge | [`0x636Af16bf2f682dD3109e60102b8E1A089FedAa8`](https://goerli.etherscan.io/address/0x636Af16bf2f682dD3109e60102b8E1A089FedAa8)
| L2OutputOracle | [`0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0`](https://goerli.etherscan.io/address/0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0)
| Lib_AddressManager | [`0xa6f73589243a6A7a9023b1Fa0651b1d89c177111`](https://goerli.etherscan.io/address/0xa6f73589243a6A7a9023b1Fa0651b1d89c177111)
| OptimismMintableERC20Factory | [`0x883dcF8B05364083D849D8bD226bC8Cb4c42F9C5`](https://goerli.etherscan.io/address/0x883dcF8B05364083D849D8bD226bC8Cb4c42F9C5)
| OptimismPortal | [`0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383`](https://goerli.etherscan.io/address/0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383) |
| PortalSender | [`0xe7FACd39531ee3C313330E93B4d7a8B8A3c84Aa4`](https://goerli.etherscan.io/address/0xe7FACd39531ee3C313330E93B4d7a8B8A3c84Aa4)
| ProxyAdmin | [`0x01d3670863c3F4b24D7b107900f0b75d4BbC6e0d`](https://goerli.etherscan.io/address/0x01d3670863c3F4b24D7b107900f0b75d4BbC6e0d)
| SystemConfig | [`0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60`](https://goerli.etherscan.io/address/0xAe851f927Ee40dE99aaBb7461C00f9622ab91d60)
| SystemDictator | [`0x1f0613A44c9a8ECE7B3A2e0CdBdF0F5B47A50971`](https://goerli.etherscan.io/address/0x1f0613A44c9a8ECE7B3A2e0CdBdF0F5B47A50971)
### API Options
1. Get free access to Optimism through [Alchemy](https://www.alchemy.com/optimism)
2. For small scale tests, you can use our public API:
- HTTP endpoint: [https://goerli.optimism.io](https://goerli.optimism.io) (note, this is for testing. For production, use Alchemy)
You can run a large application for free using [Alchemy](https://www.alchemy.com/optimism). We’ve done extensive diligence and Alchemy is our recommendation due to reliability, scalability, and data correctness.
They're the default API provider and developer platform for top projects like OpenSea and Facebook.
They also support websocket functionality, which our public endpoint does not.
To see the full list of providers visit [Node & API Providers](./providers.md).
### Test ETH
[The Optimism Faucet](https://optimismfaucet.xyz/) provides Optimism Goerli ETH.
Alternatively, if you already have Goerli ETH, you can [bridge it](https://app.optimism.io/bridge). For more faucet options see [Network Faucets](./faucets.md).
::: warning Ignore other networks
Optimism mainnet and Optimism Goerli are, from Optimism's perspective, production networks. This means our Goerli network is something you can rely on for consistent state and uptime. We have other testnets that we use to test our code (such as the Goerli Nightly network) that we use to test new features. These networks are for _us_ to test, and therefore might not have reliable state & uptime.
If you want to test out our new infrastructure before it is stable, or are interested in working on the latest and greatest protocols please check these networks out! If not, they are best ignored.
We try to make sure they work and preserve the state.
In the months prior to a major release, such as Bedrock, we may have a different network for testing dapps on that release.
However, we also have other networks such as [Goerli Nightly](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments/goerli-nightly).
These are networks that are used for internal Optimism development.
Unless you are working on the Optimism protocol (rather than building things on top of it), ignore these network.
:::
---
title: Oracles
lang: en-US
---
[Oracles](https://ethereum.org/en/developers/docs/oracles/) provide off-chain data on chain.
This allows code running on a blockchain to access a wide variety of information.
For example, a [stablecoin](https://ethereum.org/en/stablecoins/) that accepts ETH as collateral needs to know the ETH/USD exchange rate:
- How many stablecoins can we give a user for a given amount of ETH?
- Do we need to liquidate any deposits because they are under collateralized?
Different oracles have different security assumptions and different levels of decentralization.
Usually they are either run by the organization that produces the information, or have a mechanism to reward entites that provide accurate information and penalize those that provide incorrect information.
## Types of oracles
There are two types of oracles:
1. **Push oracles** are updated continously and always have up to date information available on chain.
1. **Pull oracles** are only updated when information is requested by a contract.
Pull oracles are themselves divided into two types:
1. Double-transaction oracles, which require two transactions.
The first transaction is the request for information, which usually causes the oracle to emit an event that triggers some off-chain mechanism to provide the answer (through its own transaction).
The second transaction actually reads on-chain the result from the oracle and uses it.
1. Single-transaction oracles, which only require one transaction, such as [Chainlink's random nunber generator](https://docs.chain.link/docs/get-a-random-number/#request-random-values).
The way this works is that the transaction that requests the information includes a callback (address and the call data to provide it).
When the oracle is updated (which also happens through a transaction, but one that is not sent by the user), the oracle uses the callback to inform a contract of the result.
## Gas Oracle
Optimism provides a [Gas Price Oracle](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol) that provides information about [gas prices and related parameters](../developers/build/transaction-fees.md).
It can also calculate the total cost of a transaction for you before you send it.
This contract is a predeploy at address `0x420000000000000000000000000000000000000F`:
- [On the production Optimism network](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#readContract)
- [On the Optimism Goerli test network](https://goerli-explorer.optimism.io/address/0x420000000000000000000000000000000000000F)
This is a push Oracle.
Optimism updates the gas price parameters on chain whenever those parameters change.
The L1 gas price, which can be volatile, is only pushed once every 5 minutes, and each time can change only by up to 20%.
## Chainlink
On Optimism [Chainlink](https://chain.link/) provides a number of [price feeds](https://docs.chain.link/docs/optimism-price-feeds/).
Those feeds are available on the production network.
This is a push Oracle.
You can always get up to date information (see, for example, [here (scroll down to **latestAnswer**)](https://explorer.optimism.io/address/0x13e3Ee699D1909E989722E753853AE30b17e08c5#readContract)).
[See this guide to learn how to use the Chainlink feeds](https://docs.chain.link/docs/get-the-latest-price/).
## Portal
[Portal](https://portal-docs.readthedocs.io/en/latest/index.html) offers users private, scalable, and fast compute power at low costs.
### Verifiable Randomness Function (VRF)
#### Portal
Portal providers a source of randomness on chain (for now on Optimism Goerli, but eventually also on the Optimism mainnet).
[You can learn how to use it here](https://portal-docs.readthedocs.io/en/latest/user-vrf-docs.html).
It is a single-transaction pull oracle.
#### Band
[Band](https://bandprotocol.com/vrf) provides a source of [on-chain randomness](https://bandprotocol.com/vrf).
[You can learn how to use it here](https://docs.bandchain.org/vrf/getting-started.html).
It is a single-transaction pull oracle.
## Universal Market Access (UMA)
[UMA](https://umaproject.org/) is a generic oracle.
It lets any contract request information (ask a question), and any staked entity can provide an answer.
Other external entities can dispute the proposed answer by providing their own answer and a putting up their own stake.
In the case of dispute the question goes to a vote of token holders.
The token holders that vote with the majority are assumed to be truthful and get rewarded.
The external entities that proposed the correct answer are rewarded.
Those that proposed the wrong answer lose their stake.
[See here for the UMA addresses on Optimism](https://github.com/UMAprotocol/protocol/blob/master/packages/core/networks/10.json).
[See here for instructions how to use UMA](https://docs.umaproject.org/build-walkthrough/build-process).
UMA is a pull Oracle, it does not get information until it is requested by a contract.
This means that an decentralized application needs to issue two transactions.
First, a transaction that causes a contract on the blockchain to ask for the information.
Later (in the case of UMA 48 hours later if there is no dispute, longer if there is), a second transaction need to be triggered to causes the contract to read from the Oracle and see the response to the request.
## Uniswap
Technically speaking [Uniswap](https://uniswap.io/) is not an oracle, because the information comes from on-chain sources.
However, Uniswap pools do provide [quotes that give the relative costs of assets](https://docs.uniswap.org/protocol/concepts/V3-overview/oracle).
::: warning
Using onchain asset prices, especially those in low liquidity pools, makes you vulnerable to price manipulation.
:::
To use Uniswap as an Oracle:
1. See [the list of pools on Optimism](https://info.uniswap.org/#/optimism/).
1. To find the pool address, [look at the Uniswap factory](https://explorer.optimism.io/address/0x1f98431c8ad98523631ae4a59f267346ea31f984#readContract).
Use **getPool** with these parameters:
| Parameter | Meaning |
| ------------------- | ----------------------------------- |
| One token address | [Address of the ERC-20 contract for that token on Optimism (chainId 10)](https://static.optimism.io/optimism.tokenlist.json) |
| Other token address | [Address of the ERC-20 contract for that token on Optimism (chainId 10)](https://static.optimism.io/optimism.tokenlist.json) |
| Pool fee | The pool fee percentage times ten thousand. For example, for 0.3% enter `3000` |
1. In your contract, use [IUniswapV3PoolState](https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/pool/IUniswapV3PoolState.sol) and [IUniswapV3PoolDerivedState](https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/pool/IUniswapV3PoolDerivedState.sol) to get the pool state.
---
title: Node & API Providers
lang: en-US
---
## Alchemy
::: warning
Some API calls, such as the those in the [personal namespace](https://geth.ethereum.org/docs/rpc/ns-personal) make no sense in a shared environment.
Such RPCs are either totally unsupported, or will return nonsensical values.
:::
::: tip
**We strongly recommend all developers use [Alchemy](https://www.alchemy.com/optimism).**
**We’ve done extensive diligence and Alchemy powers our public API.**
We recommend Alchemy due to (a) the reliability, scalability and data correctness of its developer platform and (b) the comprehensive set of tooling and APIs they provide. You can run large applications on their massive free tier.
:::
### Alchemy's Description & Pricing
[Alchemy](https://docs.alchemy.com/reference/optimism-api-quickstart/?a=818c11a8da) is our recommended Optimism API provider and developer platform. Its robust, free tier offers access to (1) enhanced features like SDKs and enhanced APIs and (2) hosted Optimism nodes.
_(1) Alchemy's enhanced features_
Alchemy's free private RPC endpoint provides a complimentary suite of proprietary tools.
These include a custom-built [Ethers.js SDK](https://www.alchemy.com/sdk/?a=818c11a8da) (which are supersets of the Ethers.js Provider and Wallet libraries) and [enhanced APIs](https://www.alchemy.com/enhanced-apis/?a=818c11a8da) such as NFT, Transfers, and Notify APIs.
https://www.alchemy.com/optimism/sdk
_(2) Alchemy's hosted Optimism nodes_
Alchemy hosts our public API because of its data accuracy, reliability, and scalability.
It counts companies like OpenSea, dYdX, and Facebook among its customers.
Its API takes only a few lines of codes to get up and running. Alchemy isn't just a node provider, but also a developer platform and API provider.
### Alchemy's Supported Networks
- Optimism Ethereum
- Optimism Goerli
[Sign up for a free Alchemy account here](https://www.alchemy.com/optimism).
## Ankr
### Description and Pricing
[Ankr](https://www.ankr.com/) provides a geo-distributed and decentralized (free) public and premium (Pay-as-you-go) [Optimism RPC](https://www.ankr.com/rpc/optimism/) comprised of many independent blockchain nodes running worldwide for low-latency and incredibly reliable connections.
Moreover, Ankr offers access to developer tooling on Optimism like SDKs and [Advanced APIs](https://www.ankr.com/advanced-api/) such as NFT, Token and Query API.
### Supported Networks
- Optimism Ethereum
- Optimism Goerli
## Blast
### Description and Pricing
[Blast](https://blastapi.io/) offers access to dedicated Optimism nodes with free data access up to 12M API calls per month and 25 req/s. Paid subscription tiers are available for all development needs.
On the platform, there are also [Public APIs](https://blastapi.io/public-api/optimism) available that can be added easily in Metamask.
### Supported Networks
- Optimism Ethereum
- Optimism Goerli
## BlockVision
[BlockVision](https://blockvision.org/) is a one-stop development platform and on-chain data retrieval portal for developers that boasts impressively low-latencies and high availability.
### Supported Networks
- Optimism Ethereum
## GetBlock
[GetBlock developer](https://getblock.io/en/nodes/optimism/) tools and valuable insights guarantee a simple and reliable API access to multiple blockchains.
### Supported Networks
- Optimism Ethereum
- Optimism Goerli
## Infura
### Description and Pricing
[Infura](https://infura.io) offers access to hosted Optimism nodes via addon.
This addon is currently free but Infura seems to intend to charge $200/month for this addon in the future.
### Supported Networks
- Optimism Ethereum
- Optimism Goerli
## NodeReal
### Description and pricing
[NodeReal](https://nodereal.io/) is a one-stop blockchain infrastructure and services provider, we provide scalable, reliable, and efficient blockchain solutions for everyone, aiming to support the massive adoption, growth, and long-term success of the Web3 ecosystem. You can connect to the Optimism MegaNode for free every month. Details are [here](https://nodereal.io/pricing)
[Sign up for your free account here](https://nodereal.io/meganode).
### Supported Networks
- Optimism Ethereum
## NOWNodes
### Description and pricing
[NOWNodes](https://nownodes.io/nodes/optimism) provide access to mainnet Optimism node. Shared and Dedicated nodes are Available. You can connect to the shared Optimism node for free for one month with 5,000 requests/per day or for €20 with 1,000,000 requests/per month.
The URL format is `https://optimism.nownodes.io/?API_key=<API key from NOWNodes`.
### Supported Networks
- Optimism Ethereum
## Pocket Network
### Description and pricing
[Pocket](https://www.portal.pokt.network/) offers access to a highly-available dedicated pool of Optimism nodes coordinated autonomously by the Pocket Network protocol. Through the Pocket Portal, developers may mint Optimism RPC endpoints with a generous free tier of 1M requests per day (per endpoint, max 2 per account).
### Supported Networks
- Optimism Ethereum
## QuickNode
### Description and Pricing
[QuickNode](https://www.quicknode.com/) offers access to hosted Optimism nodes for $9/month at its cheapest tier.
You can select addons, like "Archive Mode" or "Trace Mode" for an additional cost.
### Supported Networks
- Optimism Ethereum
- Optimism Goerli
## Don't see your company here?
We try to keep this list up to date as we find out about more infrastructure providers who maintain Optimism nodes.
If you're a node provider and you'd like to be included in this list, please message us in our [Discord](https://discord-gateway.optimism.io) or [make a PR](https://github.com/ethereum-optimism/community-hub/pulls).
This source diff could not be displayed because it is too large. You can view the blob instead.
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