diff --git a/.github/contributing.md b/.github/contributing.md index 11c8617f..ae77e6be 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -34,16 +34,24 @@ $ pnpm install ### Setup VitePress Dev Environment -You may start VitePress local dev environment by running `pnpm run dev`. +At first, execute the `pnpm run build` command. ```bash -$ pnpm run dev +$ pnpm run build ``` -The easiest way to start testing out VitePress is to tweak the VitePress docs. You may run `pnpm run docs` folder to boot up VitePress documentation site locally, with live reloading of the source code. +You only need to do this once for your fresh project. It copies required files and makes sure everything is in place. After this, you only need to run `dev` related commands. + +The easiest way to start testing out VitePress is to tweak the VitePress docs. You may run `pnpm run docs` to boot up VitePress documentation site locally, with live reloading of the source code. ```bash $ pnpm run docs ``` After executing the above command, visit http://localhost:3000 and try modifying the source code. You'll get live update. + +If you don't need docs site up and running, you may start VitePress local dev environment with `pnpm run dev`. + +```bash +$ pnpm run dev +``` diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a8dec8a0..589fd35d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,9 @@ jobs: uses: actions/checkout@v3 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v2.0.1 + with: + version: 7.0.1 - name: Set node version to ${{ matrix.node_version }} uses: actions/setup-node@v3 @@ -24,8 +26,11 @@ jobs: - name: Install deps run: pnpm install - - name: Build - run: pnpm run build + - name: Lint + run: pnpm run lint-fail - name: Test - run: pnpm run test + run: pnpm run test-run + + - name: Build + run: pnpm run build diff --git a/.gitignore b/.gitignore index 7c5ac29f..d73d7b2d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,10 @@ /src/node/shared.ts *.log .DS_Store +.idea .vite_opt_cache +.vscode dist node_modules -TODOs.md -.vscode -.idea pnpm-global +TODOs.md diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..3bd3b7de --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +shell-emulator=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..3b6e8917 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +/docs +/examples +*.css +*.md +*.vue +dist +pnpm-lock.yaml diff --git a/README.md b/README.md index 089440fc..4d8cbbbf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# (WIP) VitePress πŸ“πŸ’¨ +# VitePress (alpha) πŸ“πŸ’¨ [![Test](https://github.com/vuejs/vitepress/workflows/Test/badge.svg)](https://github.com/vuejs/vitepress/actions) [![npm](https://img.shields.io/npm/v/vitepress)](https://www.npmjs.com/package/vitepress) @@ -7,6 +7,8 @@ VitePress is [VuePress](https://vuepress.vuejs.org)' spiritual successor, built on top of [vite](https://github.com/vitejs/vite). +Currently, it's in the `alpha` stage. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases. + ## Documentation To check out docs, visit [vitepress.vuejs.org](https://vitepress.vuejs.org). diff --git a/__tests__/client/theme-default/support/sideBar.spec.ts b/__tests__/client/theme-default/support/sideBar.spec.ts deleted file mode 100644 index 604c945d..00000000 --- a/__tests__/client/theme-default/support/sideBar.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { - isSideBarEmpty, - getSideBarConfig, - getFlatSideBarLinks -} from 'client/theme-default/support/sideBar' - -describe('client/theme-default/support/sideBar', () => { - it('checks if the given sidebar is empty', () => { - expect(isSideBarEmpty(undefined)).toBe(true) - expect(isSideBarEmpty(false)).toBe(true) - expect(isSideBarEmpty([])).toBe(true) - - expect(isSideBarEmpty('auto')).toBe(false) - expect(isSideBarEmpty([{ text: 'a', link: '/a' }])).toBe(false) - }) - - it('gets the correct sidebar items', () => { - expect(getSideBarConfig(false, '')).toEqual(false) - expect(getSideBarConfig('auto', '')).toEqual('auto') - - const sidebar = [{ text: 'Title 01', link: 'title-01' }] - const expected = [{ text: 'Title 01', link: 'title-01' }] - - expect(getSideBarConfig(sidebar, '')).toEqual(expected) - }) - - it('gets the correct sidebar items from the given path', () => { - const sidebar = { - '/guide/': [{ text: 'G', link: 'g' }], - '/': [{ text: 'R', link: 'r' }] - } - - expect(getSideBarConfig(sidebar, '/')).toEqual(sidebar['/']) - expect(getSideBarConfig(sidebar, '/guide/')).toEqual(sidebar['/guide/']) - }) - - it('gets the correct sidebar items with various combination', () => { - const s = { - '/guide/': [{ text: 'G', link: 'g' }], - api: [{ text: 'A', link: 'a' }] - } - - expect(getSideBarConfig(s, '/guide/')).toEqual(s['/guide/']) - // no ending slash should not match - expect(getSideBarConfig(s, '/guide')).not.toEqual(s['/guide/']) - expect(getSideBarConfig(s, 'guide/')).toEqual(s['/guide/']) - expect(getSideBarConfig(s, 'guide/nested')).toEqual(s['/guide/']) - expect(getSideBarConfig(s, '/guide/nested')).toEqual(s['/guide/']) - expect(getSideBarConfig(s, 'guide/nested/')).toEqual(s['/guide/']) - expect(getSideBarConfig(s, '/api/')).toEqual(s['api']) - expect(getSideBarConfig(s, '/api')).toEqual(s['api']) - expect(getSideBarConfig(s, 'api/')).toEqual(s['api']) - expect(getSideBarConfig(s, 'api/nested')).toEqual(s['api']) - expect(getSideBarConfig(s, '/api/nested')).toEqual(s['api']) - expect(getSideBarConfig(s, 'api/nested/')).toEqual(s['api']) - expect(getSideBarConfig(s, '/')).toEqual('auto') - }) - - it('creates flat sidebar links', () => { - const sidebar = [ - { text: 'Title 01', link: '/title-01' }, - { text: 'Title 02', link: '/title-02' }, - { text: 'Title 03', link: '/title-03' } - ] - - const expected = [ - { text: 'Title 01', link: '/title-01' }, - { text: 'Title 02', link: '/title-02' }, - { text: 'Title 03', link: '/title-03' } - ] - - expect(getFlatSideBarLinks(sidebar)).toEqual(expected) - }) - - it('creates flat sidebar links with mixed sidebar group', () => { - const sidebar = [ - { - text: 'Title 01', - link: '/title-01', - children: [ - { text: 'Children 01', link: '/children-01' }, - { text: 'Children 02', link: '/children-02' } - ] - }, - { text: 'Title 02', link: '/title-02' }, - { text: 'Title 03', link: '/title-03' } - ] - - const expected = [ - { text: 'Title 01', link: '/title-01' }, - { text: 'Children 01', link: '/children-01' }, - { text: 'Children 02', link: '/children-02' }, - { text: 'Title 02', link: '/title-02' }, - { text: 'Title 03', link: '/title-03' } - ] - - expect(getFlatSideBarLinks(sidebar)).toEqual(expected) - }) - - it('ignores any items with no `link` property', () => { - const sidebar = [ - { - text: 'Title 01', - children: [ - { text: 'Children 01', link: '/children-01' }, - { text: 'Children 02', link: '/children-02' } - ] - }, - { text: 'Title 02', link: '/title-02' } - ] - - const expected = [ - { text: 'Children 01', link: '/children-01' }, - { text: 'Children 02', link: '/children-02' }, - { text: 'Title 02', link: '/title-02' } - ] - - expect(getFlatSideBarLinks(sidebar)).toEqual(expected) - }) - - it('removes `.md` or `.html` extention', () => { - const sidebar = [ - { text: 'Title 01', link: '/title-01.md' }, - { text: 'Title 02', link: '/title-02.html' } - ] - - const expected = [ - { text: 'Title 01', link: '/title-01' }, - { text: 'Title 02', link: '/title-02' } - ] - - expect(getFlatSideBarLinks(sidebar)).toEqual(expected) - }) -}) diff --git a/__tests__/client/theme-default/utils.spec.ts b/__tests__/client/theme-default/utils.spec.ts deleted file mode 100644 index 52f01d4a..00000000 --- a/__tests__/client/theme-default/utils.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as Utils from 'client/theme-default/utils' - -describe('client/theme-default/utils', () => { - describe('ensureStartingSlash', () => { - it('should add slash to the beginning of the given path', () => { - expect(Utils.ensureStartingSlash('path')).toBe('/path') - expect(Utils.ensureStartingSlash('path/nested')).toBe('/path/nested') - expect(Utils.ensureStartingSlash('/path')).toBe('/path') - expect(Utils.ensureStartingSlash('/path/nested')).toBe('/path/nested') - }) - }) - - describe('ensureEndingSlash', () => { - it('should add slash to the end of the given path', () => { - expect(Utils.ensureEndingSlash('path')).toBe('path/') - expect(Utils.ensureEndingSlash('path/nested')).toBe('path/nested/') - expect(Utils.ensureEndingSlash('path/')).toBe('path/') - expect(Utils.ensureEndingSlash('path/nested/')).toBe('path/nested/') - expect(Utils.ensureEndingSlash('path/page.html')).toBe('path/page.html') - }) - }) - - describe('removeExtention', () => { - it('removes `.md` or `.html` extention from the path', () => { - expect(Utils.removeExtention('/')).toBe('/') - expect(Utils.removeExtention('index')).toBe('/') - expect(Utils.removeExtention('index.md')).toBe('/') - expect(Utils.removeExtention('index.html')).toBe('/') - expect(Utils.removeExtention('/index')).toBe('/') - expect(Utils.removeExtention('/index.md')).toBe('/') - expect(Utils.removeExtention('/index.html')).toBe('/') - expect(Utils.removeExtention('path')).toBe('path') - expect(Utils.removeExtention('path.md')).toBe('path') - expect(Utils.removeExtention('path.html')).toBe('path') - expect(Utils.removeExtention('path/')).toBe('path/') - expect(Utils.removeExtention('path/nested.md')).toBe('path/nested') - expect(Utils.removeExtention('path/nested.html')).toBe('path/nested') - expect(Utils.removeExtention('path/nested/index')).toBe('path/nested/') - }) - }) -}) diff --git a/__tests__/node/utils/deeplyParseHeader.spec.ts b/__tests__/node/utils/deeplyParseHeader.spec.ts index ded20eac..976073a4 100644 --- a/__tests__/node/utils/deeplyParseHeader.spec.ts +++ b/__tests__/node/utils/deeplyParseHeader.spec.ts @@ -1,28 +1,29 @@ +import { test, expect } from 'vitest' import { deeplyParseHeader } from 'node/utils/parseHeader' test('deeplyParseHeader', () => { const asserts: Record = { - // Remove tail html + // remove tail html '# `H1` ': '# H1', '# *H1* ': '# H1', - // Reserve code-wrapped tail html + // reserve code-wrapped tail html '# `H1` ``': '# H1 ', '# *H1* ``': '# H1 ', - // Remove leading html + // remove leading html '# `H1`': '# H1', '# *H1*': '# H1', - // Reserve code-wrapped leading html + // reserve code-wrapped leading html '# `` `H1`': '# H1', '# `` *H1*': '# H1', - // Remove middle html + // remove middle html '# `H1` `H2`': '# H1 H2', '# `H1` `H2`': '# H1 H2', - // Reserve middle html + // reserve middle html '# `H1` `` `H2`': '# H1 H2', '# `H1` `` `H2`': '# H1 H2' } diff --git a/__tests__/node/utils/parseHeader.spec.ts b/__tests__/node/utils/parseHeader.spec.ts index a150cff0..790f4373 100644 --- a/__tests__/node/utils/parseHeader.spec.ts +++ b/__tests__/node/utils/parseHeader.spec.ts @@ -1,3 +1,4 @@ +import { describe, test, expect } from 'vitest' import { parseHeader } from 'node/utils/parseHeader' describe('parseHeader', () => { diff --git a/__tests__/node/utils/removeNonCodeWrappedHTML.spec.ts b/__tests__/node/utils/removeNonCodeWrappedHTML.spec.ts index d5481ad8..17d506fc 100644 --- a/__tests__/node/utils/removeNonCodeWrappedHTML.spec.ts +++ b/__tests__/node/utils/removeNonCodeWrappedHTML.spec.ts @@ -1,3 +1,4 @@ +import { test, expect } from 'vitest' import { removeNonCodeWrappedHTML } from 'node/utils/parseHeader' test('removeNonCodeWrappedHTML', () => { diff --git a/__tests__/tsconfig.json b/__tests__/tsconfig.json index 10d9e9a2..49605e11 100644 --- a/__tests__/tsconfig.json +++ b/__tests__/tsconfig.json @@ -1,14 +1,8 @@ { + "extends": "../tsconfig.json", "compilerOptions": { "baseUrl": ".", - "module": "esnext", - "moduleResolution": "node", - "strict": true, - "noUnusedLocals": true, - "skipLibCheck": true, - "esModuleInterop": true, - "lib": ["ESNext", "DOM"], - "types": ["node", "vitest/global"], + "types": ["node", "vitest/globals"], "paths": { "node/*": ["../src/node/*"], "client/*": ["../src/client/*"] diff --git a/__tests__/vitest.config.js b/__tests__/vitest.config.js deleted file mode 100644 index cd9c2340..00000000 --- a/__tests__/vitest.config.js +++ /dev/null @@ -1,10 +0,0 @@ -import path from 'path' - -export default { - resolve: { - alias: { - node: path.resolve(__dirname, '../src/node'), - client: path.resolve(__dirname, '../src/client') - } - } -} diff --git a/__tests__/vitest.config.ts b/__tests__/vitest.config.ts new file mode 100644 index 00000000..96d9ca01 --- /dev/null +++ b/__tests__/vitest.config.ts @@ -0,0 +1,17 @@ +import { dirname, resolve } from 'path' +import { fileURLToPath } from 'url' +import { defineConfig } from 'vite' + +const dir = dirname(fileURLToPath(import.meta.url)) + +export default defineConfig({ + resolve: { + alias: { + node: resolve(dir, '../src/node'), + client: resolve(dir, '../src/client') + } + }, + test: { + globals: true + } +}) diff --git a/api-extractor.client.json b/api-extractor.client.json deleted file mode 100644 index 9540d6b4..00000000 --- a/api-extractor.client.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - - "projectFolder": "./src/client", - - "mainEntryPointFilePath": "./dist/temp/index.d.ts", - - "dtsRollup": { - "enabled": true, - "publicTrimmedFilePath": "./dist/client/index.d.ts" - }, - - "apiReport": { - "enabled": false - }, - - "docModel": { - "enabled": false - }, - - "tsdocMetadata": { - "enabled": false - }, - - "messages": { - "compilerMessageReporting": { - "default": { - "logLevel": "warning" - } - }, - - "extractorMessageReporting": { - "default": { - "logLevel": "warning", - "addToApiReportFile": true - }, - - "ae-missing-release-tag": { - "logLevel": "none" - } - }, - - "tsdocMessageReporting": { - "default": { - "logLevel": "warning" - }, - - "tsdoc-undefined-tag": { - "logLevel": "none" - } - } - } -} diff --git a/api-extractor.node.json b/api-extractor.node.json deleted file mode 100644 index 3d85f836..00000000 --- a/api-extractor.node.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - - "projectFolder": "./src/node", - - "mainEntryPointFilePath": "./dist/temp/index.d.ts", - - "dtsRollup": { - "enabled": true, - "publicTrimmedFilePath": "./dist/node/index.d.ts" - }, - - "apiReport": { - "enabled": false - }, - - "docModel": { - "enabled": false - }, - - "tsdocMetadata": { - "enabled": false - }, - - "messages": { - "compilerMessageReporting": { - "default": { - "logLevel": "warning" - } - }, - - "extractorMessageReporting": { - "default": { - "logLevel": "warning", - "addToApiReportFile": true - }, - - "ae-missing-release-tag": { - "logLevel": "none" - } - }, - - "tsdocMessageReporting": { - "default": { - "logLevel": "warning" - }, - - "tsdoc-undefined-tag": { - "logLevel": "none" - } - } - } -} diff --git a/bin/vitepress.js b/bin/vitepress.js index 0aafe1a3..7c50ec90 100755 --- a/bin/vitepress.js +++ b/bin/vitepress.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -require('../dist/node/cli') +import('../dist/node/cli.js') diff --git a/client.d.ts b/client.d.ts index 78cda7e2..f206e730 100644 --- a/client.d.ts +++ b/client.d.ts @@ -1,4 +1,3 @@ -// re-export vite client types -// with strict installers like pnpm, user won't be able to reference vite/client -// in project root +// re-export vite client types. with strict installers like pnpm, user won't +// be able to reference vite/client in project root. /// diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index cfb87e63..f3581977 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -4,15 +4,32 @@ export default defineConfig({ lang: 'en-US', title: 'VitePress', description: 'Vite & Vue powered static site generator.', + lastUpdated: true, themeConfig: { - repo: 'vuejs/vitepress', - docsDir: 'docs', - docsBranch: 'main', - editLinks: true, - editLinkText: 'Edit this page on GitHub', - lastUpdated: 'Last Updated', + nav: nav(), + + sidebar: { + '/guide/': sidebarGuide(), + '/config/': sidebarConfig() + }, + + editLink: { + repo: 'vuejs/vitepress', + branch: 'next', + dir: 'docs', + text: 'Edit this page on GitHub' + }, + + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' } + ], + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright Β© 2019-present Evan You' + }, algolia: { appId: '8J64VVRP8K', @@ -21,73 +38,89 @@ export default defineConfig({ }, carbonAds: { - carbon: 'CEBDT27Y', - custom: 'CKYD62QM', + code: 'CEBDT27Y', placement: 'vuejsorg' - }, - - nav: [ - { text: 'Guide', link: '/', activeMatch: '^/$|^/guide/' }, - { - text: 'Config Reference', - link: '/config/basics', - activeMatch: '^/config/' - }, - { - text: 'Release Notes', - link: 'https://github.com/vuejs/vitepress/releases' - } - ], - - sidebar: { - '/guide/': getGuideSidebar(), - '/config/': getConfigSidebar(), - '/': getGuideSidebar() } } }) -function getGuideSidebar() { +function nav() { + return [ + { text: 'Guide', link: '/guide/what-is-vitepress', activeMatch: '/guide/' }, + { text: 'Configs', link: '/config/introduction', activeMatch: '/config/' }, + { + text: 'Changelog', + link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md' + } + ] +} + +function sidebarGuide() { return [ { text: 'Introduction', - children: [ - { text: 'What is VitePress?', link: '/' }, + collapsible: true, + items: [ + { text: 'What is VitePress?', link: '/guide/what-is-vitepress' }, { text: 'Getting Started', link: '/guide/getting-started' }, { text: 'Configuration', link: '/guide/configuration' }, - { text: 'Asset Handling', link: '/guide/assets' }, - { text: 'Markdown Extensions', link: '/guide/markdown' }, - { text: 'Using Vue in Markdown', link: '/guide/using-vue' }, - { text: 'Deploying', link: '/guide/deploy' } + { text: 'Deploying', link: '/guide/deploying' } ] }, { - text: 'Advanced', - children: [ + text: 'Writing', + collapsible: true, + items: [ + { text: 'Markdown', link: '/guide/markdown' }, + { text: 'Asset Handling', link: '/guide/asset-handling' }, { text: 'Frontmatter', link: '/guide/frontmatter' }, - { text: 'Theming', link: '/guide/theming' }, - { text: 'API Reference', link: '/guide/api' }, + { text: 'Using Vue in Markdown', link: '/guide/using-vue' }, + { text: 'API Reference', link: '/guide/api' } + ] + }, + { + text: 'Theme', + collapsible: true, + items: [ + { text: 'Introduction', link: '/guide/theme-introduction' }, + { text: 'Nav', link: '/guide/theme-nav' }, + { text: 'Sidebar', link: '/guide/theme-sidebar' }, + { text: 'Prev Next Link', link: '/guide/theme-prev-next-link' }, + { text: 'Edit Link', link: '/guide/theme-edit-link' }, + { text: 'Last Updated', link: '/guide/theme-last-updated' }, + { text: 'Layout', link: '/guide/theme-layout' }, + { text: 'Homepage', link: '/guide/theme-homepage' }, + { text: 'Footer', link: '/guide/theme-footer' }, + { text: 'Search', link: '/guide/theme-search' }, + { text: 'Carbon Ads', link: '/guide/theme-carbon-ads' } + ] + }, + { + text: 'Migrations', + collapsible: true, + items: [ + { + text: 'Migration from VuePress', + link: '/guide/migration-from-vuepress' + }, { - text: 'Differences from Vuepress', - link: '/guide/differences-from-vuepress' + text: 'Migration from VitePress 0.x', + link: '/guide/migration-from-vitepress-0' } ] } ] } -function getConfigSidebar() { +function sidebarConfig() { return [ { - text: 'App Config', - children: [{ text: 'Basics', link: '/config/basics' }] - }, - { - text: 'Theme Config', - children: [ - { text: 'Homepage', link: '/config/homepage' }, - { text: 'Algolia Search', link: '/config/algolia-search' }, - { text: 'Carbon Ads', link: '/config/carbon-ads' } + text: 'Config', + items: [ + { text: 'Introduction', link: '/config/introduction' }, + { text: 'App Configs', link: '/config/app-configs' }, + { text: 'Theme Configs', link: '/config/theme-configs' }, + { text: 'Frontmatter Configs', link: '/config/frontmatter-configs' } ] } ] diff --git a/docs/config/algolia-search.md b/docs/config/algolia-search.md deleted file mode 100644 index 132b0a56..00000000 --- a/docs/config/algolia-search.md +++ /dev/null @@ -1,53 +0,0 @@ -# Theme Config: Algolia Search - -The `themeConfig.algolia` option allows you to use [Algolia DocSearch](https://docsearch.algolia.com). To enable it, you need to provide at least appId, apiKey and indexName: - -```js -module.exports = { - themeConfig: { - algolia: { - appId: 'your_app_id', - apiKey: 'your_api_key', - indexName: 'index_name' - } - } -} -``` - -For more options, check out [Algolia DocSearch's documentation](https://docsearch.algolia.com/docs/api/). You can pass any extra option alongside other options, e.g. passing `searchParameters`: - -```js -module.exports = { - themeConfig: { - algolia: { - appId: 'your_app_id', - apiKey: 'your_api_key', - indexName: 'index_name', - searchParameters: { - facetFilters: ['tags:guide,api'] - } - } - } -} -``` - -## Internationalization (i18n) - -If you have multiple locales in your documentation and you have defined a `locales` object in your `themeConfig`: - -```js -module.exports = { - themeConfig: { - locales: { - // ... - }, - algolia: { - appId: 'your_app_id', - apiKey: 'your_api_key', - indexName: 'index_name' - } - } -} -``` - -VitePress will automatically add a `lang` _facetFilter_ to the `searchParameters.facetFilter` array with the correct language value. Algolia automatically adds the correct facet filter based on the `lang` attribute on the `` tag. This will match search results with the currently viewed language of the page. diff --git a/docs/config/app-configs.md b/docs/config/app-configs.md new file mode 100644 index 00000000..96d3b118 --- /dev/null +++ b/docs/config/app-configs.md @@ -0,0 +1,161 @@ +# App Configs + +App configs are where you can define the global settings of the site. App configs define fundamental settings that are not only limited to the theme configs such as configuration for "base directory", or the "title" of the site. + +```ts +export default { + // These are app level configs. + lang: 'en-US', + title: 'VitePress', + description: 'Vite & Vue powered static site generator.', + ... +} +``` + +## base + +- Type: `string` +- Default: `/` + +The base URL the site will be deployed at. You will need to set this if you plan to deploy your site under a sub path, for example, GitHub pages. If you plan to deploy your site to `https://foo.github.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash. + +The base is automatically prepended to all the URLs that start with / in other options, so you only need to specify it once. + +```ts +export default { + base: '/base/' +} +``` + +## lang + +- Type: `string` +- Default: `en-US` + +The lang attribute for the site. This will render as a `` tag in the page HTML. + +```ts +export default { + lang: 'en-US' +} +``` + +## title + +- Type: `string` +- Default: `VitePress` + +Title for the site. This will be displayed in the nav bar. Also used as the suffix for all page titles unless `titleTemplate` is defined. + +```ts +export default { + title: 'VitePress' +} +``` + +## titleTemplate + +- Type: `string | boolean` + +The suffix for the title. For example, if you set `title` as `VitePress` and set `titleTemplate` as `My Site`, the html title becomes `VitePress | My Site`. + +Set `false` to disable the feature. If the option is `undefined`, then the value of `title` option will be used. + +```ts +export default { + title: 'VitePress', + titleTemplate: 'Vite & Vue powered static site generator' +} +``` + +## description + +- Type: `string` +- Default: `A VitePress site` + +Description for the site. This will render as a `` tag in the page HTML. + +```ts +export default { + description: 'A VitePress site' +} +``` + +## markdown + +- Type: `MarkdownOption` + +Configre Markdown parser options. VitePress uses [Markdown-it](https://github.com/markdown-it/markdown-it) as the parser, and [Shiki](https://shiki.matsu.io/) to highlight language syntax. Inside this option, you may pass various Markdown related options to fit your needs. + +```js +export default { + markdown: { + theme: 'material-palenight', + lineNumbers: true + } +} +``` + +Below shows the the full option you may define within this object. + +```ts +interface MarkdownOptions extends MarkdownIt.Options { + // Syntax highlight theme for Shiki. + // See: https://github.com/shikijs/shiki/blob/main/docs/themes.md#all-themes + theme?: Shiki.Theme | { light: Shiki.Theme, dark: Shiki.Theme } + + // Enable line numbers in code block. + lineNumbers?: boolean + + // markdown-it-anchor plugin options. + // See: https://github.com/valeriangalliat/markdown-it-anchor + anchor?: { + permalink?: anchor.AnchorOptions['permalink'] + } + + // markdown-it-attrs plugin options. + // See: https://github.com/arve0/markdown-it-attrs + attrs?: { + leftDelimiter?: string + rightDelimiter?: string + allowedAttributes?: string[] + disable?: boolean + } + + // markdown-it-toc-done-right plugin options + // https://github.com/nagaozen/markdown-it-toc-done-right + toc?: any + + // Configure the Markdown-it instance to fully customize + // how it works. + config?: (md: MarkdownIt) => void +} +``` + +## appearance + +- Type: `boolean` +- Default: `true` + +Whether to enable "Dark Mode" or not. If the option is set to `true`, it adds `.dark` class to the `` tag depending on the users preference. + +It also injects inline script that tries to read users settings from local storage by `vitepress-theme-appearance` key and restores users preferred color mode. + +```ts +export default { + appearance: true +} +``` + +## lastUpdated + +- Type: `boolean` +- Default: `false` + +Use git commit to get the timestamp. This option enables the default theme to display the page's last updated time. You can customize the text via [`themeConfig.lastUpdatedText`](theme-configs#lastupdatedtext) option. + +```ts +export default { + lastUpdated: true +} +``` diff --git a/docs/config/basics.md b/docs/config/basics.md deleted file mode 100644 index 5955ecb5..00000000 --- a/docs/config/basics.md +++ /dev/null @@ -1,59 +0,0 @@ -# App Config: Basics - -::: tip -The config reference is incomplete since the config format may still receive further changes. For a complete reference of the current available options, refer to [config.ts](https://github.com/vuejs/vitepress/blob/45b65ce8b63bd54f345bfc3383eb2416b6769dc9/src/node/config.ts#L30-L65). -::: - -## base - -- Type: `string` -- Default: `/` - -The base URL the site will be deployed at. You will need to set this if you plan to deploy your site under a sub path, for example, GitHub pages. If you plan to deploy your site to `https://foo.github.io/bar/`, then you should set base to `'/bar/'`. It should always start and end with a slash. - -The `base` is automatically prepended to all the URLs that start with `/` in other options, so you only need to specify it once. - -```js -module.exports = { - base: '/base/' -} -``` - -## lang - -- Type: `string` -- Default: `en-US` - -The `lang` attribute for the site. This will render as a `` tag in the page HTML. - -```js -module.exports = { - lang: 'en-US' -} -``` - -## title - -- Type: `string` -- Default: `VitePress` - -Title for the site. This will be the suffix for all page titles, and displayed in the navbar. - -```js -module.exports = { - title: 'VitePress' -} -``` - -## description - -- Type: `string` -- Default: `A VitePress site` - -Description for the site. This will render as a `` tag in the page HTML. - -```js -module.exports = { - description: 'A VitePress site' -} -``` diff --git a/docs/config/carbon-ads.md b/docs/config/carbon-ads.md deleted file mode 100644 index 3bb074d1..00000000 --- a/docs/config/carbon-ads.md +++ /dev/null @@ -1,15 +0,0 @@ -# Theme Config: Carbon Ads - -VitePress has built in native support for [Carbon Ads](https://www.carbonads.net). By defining the Carbon Ads credentials in config, VitePress will display ads on the page. - -```js -module.exports = { - themeConfig: { - carbonAds: { - carbon: 'your-carbon-key', - custom: 'your-carbon-custom', - placement: 'your-carbon-placement' - } - } -} -``` diff --git a/docs/config/frontmatter-configs.md b/docs/config/frontmatter-configs.md new file mode 100644 index 00000000..d57e6cba --- /dev/null +++ b/docs/config/frontmatter-configs.md @@ -0,0 +1,188 @@ +# Frontmatter Configs + +Frontmatter enables page based configuration. On every markdown, you’re free to add Any settings to override any global app or theme configs. Also, there are configs which you can only define in Frontmatter. + +```yaml +--- +title: Docs with VitePress +editLink: true +--- +``` + +You may access frontmatter by `$frontmatter` helper inside any markdown file. + +```md +{{ $frontmatter.title }} +``` + +## title + +- Type: `string` + +Title for the page. It's same as [config.title](../config/app-configs#title), and it overrides the app config. + +```yaml +--- +title: VitePress +--- +``` + +## titleTemplate + +- Type: `string | boolean` + +The suffix for the title. It's same as [config.titleTemplate](../config/app-configs#titleTemplate), and it overrides the app config. + +```yaml +--- +title: VitePress, +titleTemplate: Vite & Vue powered static site generator. +--- +``` + +## description + +- Type: `string` + +Title for the page. It's same as [config.description](../config/app-configs#description), and it overrides the app config. + +```yaml +--- +description: VitePress +--- +``` + +### head + +- Type: `Head[]` + +Specify extra head tags to be injected: + +```yaml +--- +head: + - - meta + - name: description + content: hello + - - meta + - name: keywords + content: super duper SEO +--- +``` + +```ts +type Head = + | [string, Record] + | [string, Record, string] +``` + +## layout + +- Type: `doc | home | page` +- Default: `doc` + +Determines the layout of the page. + +- `doc` - It applies default documentation styles to the markdown content. +- `home` - Special layout for "Home Page". You may add extra options such as `hero` and `features` to rappidly create beautiful landing page. +- `page` - Behave similar to `doc` but it aplies no styles to the content. Useful when you want to create a fully custom page. + +```yaml +--- +layout: doc +--- +``` + +## hero + +- Type: `Hero` + +This option only take effect when `layout` is set to `home`. + +It defines contents of home hero section. + +```yaml +--- +layout: home + +hero: + name: VuePress + text: Vite & Vue powered static site generator. + tagline: Lorem ipsum... + actions: + - theme: brand + text: Get Started + link: /guide/what-is-vitepress + - theme: alt + text: View on GitHub + link: https://github.com/vuejs/vitepress +--- +``` + +```ts +interface Hero { + // The string shown top of `text`. Comes with brand color + // and expected to be short, such as product name. + name?: string + + // The main text for the hero section. This will be defined + // as `h1` tag. + text: string + + // Tagline displayed below `text`. + tagline?: string + + // Action buttons to display in home hero section. + actions?: HeroAction[] +} + +interface HeroAction { + // Color theme of the button. Defaults to `brand`. + theme?: 'brand' | 'alt' + + // Label of the button. + text: string + + // Destination link of the button. + link: string +} +``` + +## features + +- Type: `Feature[]` + +This option only take effect when `layout` is set to `home`. + +It defines items to display in features section. + +```yaml +--- +layout: home + +features: + - icon: ⚑️ + title: Vite, The DX that can't be beat + details: Lorem ipsum... + - icon: πŸ–– + title: Power of Vue meets Markdown + details: Lorem ipsum... + - icon: πŸ› οΈ + title: Simple and minimal, always + details: Lorem ipsum... +--- +``` + +```ts +interface Feature { + // Show icon on each feature box. Currently, only emojis + // are supported. + icon?: string + + // Title of the feature. + title: string + + // Details of the feature. + details: string +} +``` diff --git a/docs/config/homepage.md b/docs/config/homepage.md deleted file mode 100644 index 0abaad7b..00000000 --- a/docs/config/homepage.md +++ /dev/null @@ -1,23 +0,0 @@ -# Theme Config: Homepage - -VitePress provides a homepage layout. To use it, specify `home: true` plus some other metadata in your root `index.md`'s [YAML frontmatter](../guide/frontmatter). This is an example of how it works: - -```yaml ---- -home: true -heroImage: /logo.png -heroAlt: Logo image -heroText: Hero Title -tagline: Hero subtitle -actionText: Get Started -actionLink: /guide/ -features: - - title: Simplicity First - details: Minimal setup with markdown-centered project structure helps you focus on writing. - - title: Vue-Powered - details: Enjoy the dev experience of Vue + webpack, use Vue components in markdown, and develop custom themes with Vue. - - title: Performant - details: VitePress generates pre-rendered static HTML for each page, and runs as an SPA once a page is loaded. -footer: MIT Licensed | Copyright Β© 2019-present Evan You ---- -``` diff --git a/docs/config/introduction.md b/docs/config/introduction.md new file mode 100644 index 00000000..8128105f --- /dev/null +++ b/docs/config/introduction.md @@ -0,0 +1,72 @@ +# Introduction + +Place your configuration file at `.vitepress/config.js`. This is where all VitePress-specific files will be placed. + +``` +. +β”œβ”€ docs +β”‚ β”œβ”€ .vitepress +β”‚ β”‚ └─ config.js +β”‚ └─ index.md +└─ package.json +``` + +VitePress comes with 2 types of configs. One is the [App Configs](./app-configs) which configures the site's fundamental features such as setting title of the site, or customize how markdown parser works. Second is the [Theme Config](./theme-configs) which configures the theme of the site, for example, adding a sidebar, or add features such as "Edit this page on GitHub" link. + +There's also another configuration you may do in [Frontmatter](./frontmatter-configs). Frontmatter configs can override global configs defined on App Configs or Theme Configs for that specific page. However, there're several options that are only available at frontmatter as well. + +Please refer to the corresponding configs page to learn more. + +## Config Intellisense + +Since VitePress ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints: + +```js +/** + * @type {import('vitepress').UserConfig} + */ +const config = { + // ... +} + +export default config +``` + +Alternatively, you can use the `defineConfig` helper at which should provide intellisense without the need for jsdoc annotations: + +```js +import { defineConfig } from 'vitepress' + +export default defineConfig({ + // ... +}) +``` + +VitePress also directly supports TS config files. You can use `.vitepress/config.ts` with the `defineConfig` helper as well. + +## Typed Theme Config + +By default, `defineConfig` helper leverages the theme config type from default theme: + +```ts +import { defineConfig } from 'vitepress' + +export default defineConfig({ + themeConfig: { + // Type is `DefaultTheme.Config` + } +}) +``` + +If you use a custom theme and want type checks for the theme config, you'll need to use `defineConfigWithTheme` instead, and pass the config type for your custom theme via a generic argument: + +```ts +import { defineConfigWithTheme } from 'vitepress' +import { ThemeConfig } from 'your-theme' + +export default defineConfigWithTheme({ + themeConfig: { + // Type is `ThemeConfig` + } +}) +``` diff --git a/docs/config/theme-configs.md b/docs/config/theme-configs.md new file mode 100644 index 00000000..17c24708 --- /dev/null +++ b/docs/config/theme-configs.md @@ -0,0 +1,230 @@ +# Theme Configs + +Theme configs let you customize your theme. You can define theme configs by adding `themeConfig` key to the config file. + +```ts +export default { + lang: 'en-US', + title: 'VitePress', + description: 'Vite & Vue powered static site generator.', + + // Theme related configurations. + themeConfig: { + logo: '/logo.svg', + nav: [...], + sidebar: { ... } + } +} +``` + +Here it describes the settings for the VitePress default theme. If you're using a custom theme created by others, these settings may not have any effect, or might behave differently. + +## logo + +- Type: `string` + +Logo file to display in nav bar, right before the site title. + +```ts +export default { + themeConfig: { + logo: '/logo.svg' + } +} +``` + +## siteTitle + +- Type: `string | false` + +You can customize this item to replace the default site title (`title` in app config) in nav. When set to `false`, title in nav will be disabled. Useful when you have `logo` that already contains the site title text. + +```ts +export default { + themeConfig: { + siteTitle: 'Hello World' + } +} +``` + +## nav + +- Type: `NavItem` + +The configuration for the nav menu item. You may learn more details at [Theme: Nav](../guide/theme-nav#navigation-links). + +```js +export default { + themeConfig: { + nav: [ + { text: 'Guide', link: '/guide' }, + { + text: 'Dropdown Menu', + items: [ + { text: 'Item A', link: '/item-1' }, + { text: 'Item B', link: '/item-2' }, + { text: 'Item C', link: '/item-3' } + ] + } + ] + } +} +``` + +```ts +type NavItem = NavItemWithLink | NavItemWithChildren + +type NavItemWithLink = { + text: string + link: string + activeMatch?: string +} + +interface NavItemWithChildren { + text?: string + items: NavItemWithLink[] +} +``` + +## sidebar + +- Type: `Sidebar` + +The configuration for the sidebar menu item. You may learn more details at [Theme: Nav](../guide/theme-sidebar). + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Guide', + items: [ + { text: 'Introduction', link: '/introduction' }, + { text: 'Getting Started', link: '/getting-started' }, + ... + ] + } + ] + } +} +``` + +```ts +type Sidebar = SidebarGroup[] | SidebarMulti + +interface SidebarMulti { + [path: string]: SidebarGroup[] +} + +interface SidebarGroup { + text: string + items: SidebarItem[] + collapsible?: boolean + collapsed?: boolean +} + +interface SidebarItem { + text: string + link: string +} +``` + +## socialLinks + +- Type: `SocialLink` + +You may define this option to show your social account links with icons in nav. + +```js +export default { + themeConfig: { + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' }, + { icon: 'twitter', link: '...' }, + { icon: 'discord', link: '...' } + ] + } +} +``` + +```ts +interface SocialLink { + icon: SocialLinkIcon + link: string +} + +type SocialLinkIcon = + | 'discord' + | 'facebook' + | 'github' + | 'instagram' + | 'linkedin' + | 'slack' + | 'twitter' + | 'youtube' +``` + +## footer + +- Type: `Footer` + +Footer configuration. You can add a message and copyright. The footer will displayed only when the page doesn't contain sidebar due to design reason. + +```ts +export default { + themeConfig: { + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright Β© 2019-present Evan You' + } + } +} +``` + +```ts +export interface Footer { + message?: string + copyright?: string +} +``` + +## lastUpdatedText + +- Type: `string` +- Default: `Last updated` + +The prefix text showing right before the last updated time. + +```ts +export default { + themeConfig: { + lastUpdatedText: 'Updated Date' + } +} +``` + +## carbonAds + +- Type: `CarbonAds` + +A option to display [Carbon Ads](https://www.carbonads.net/). + +```ts +export default { + themeConfig: { + carbonAds: { + code: 'your-carbon-code', + placement: 'your-carbon-placement' + } + } +} +``` + +```ts +export interface CarbonAds { + code: string, + placement: string +} +``` + +Learn more in [Theme: Carbon Ads](../guide/theme-carbon-ads) diff --git a/docs/guide/api.md b/docs/guide/api.md index 5db50b8c..28eeb0c0 100644 --- a/docs/guide/api.md +++ b/docs/guide/api.md @@ -1,12 +1,12 @@ # API Reference -## Helper Methods +VitePress offers several built in API to let you access app data. VitePress also comes with few built-in component that can be used globally. -The following methods are globally importable from `vitepress` and are typically used in custom theme Vue components. However, they are also usable inside `.md` pages because markdown files are compiled into Vue single-file components. +The helper methods are globally importable from `vitepress` and are typically used in custom theme Vue components. However, they are also usable inside `.md` pages because markdown files are compiled into Vue single-file components. Methods that start with `use*` indicates that it is a [Vue 3 Composition API](https://vuejs.org/guide/introduction.html#composition-api) function that can only be used inside `setup()` or ` ``` -### `useRoute` +## `useRoute` Returns the current route object with the following type: @@ -48,7 +49,7 @@ interface Route { } ``` -### `useRouter` +## `useRouter` Returns the VitePress router instance so you can programmatically navigate to another page. @@ -59,19 +60,15 @@ interface Router { } ``` -### `withBase` +## `withBase` - **Type**: `(path: string) => string` - Appends the configured [`base`](../config/basics#base) to a given URL path. Also see [Base URL](./assets#base-url). - -## Global Components - -VitePress comes with few built-in component that can be used globally. You may use these components in your markdown or your custom theme configuration. +Appends the configured [`base`](../config/app-configs#base) to a given URL path. Also see [Base URL](./asset-handling#base-url). -### `` +## `` -The `` component displays the rendered markdown contents. Useful [when creating your own theme](./theming). +The `` component displays the rendered markdown contents. Useful [when creating your own theme](./theme-introduction). ```vue ``` -### `` +## `` -The `` component renders its slot only at client side. +The `` component renders its slot only at client side. Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the universal code requirements. In short, make sure to only access Browser / DOM APIs in beforeMount or mounted hooks. If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the `ClientOnly` component. -```html +```vue-html diff --git a/docs/guide/assets.md b/docs/guide/asset-handling.md similarity index 100% rename from docs/guide/assets.md rename to docs/guide/asset-handling.md diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index f2661653..852a36bf 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -1,10 +1,8 @@ # Configuration -## Overview +Without any configuration, the page is pretty minimal, and the user has no way to navigate around the site. To customize your site, let's first create a `.vitepress` directory inside your docs directory. This is where all VitePress-specific files will be placed. Your project structure is probably like this: -Without any configuration, the page is pretty minimal, and the user has no way to navigate around the site. To customize your site, let’s first create a `.vitepress` directory inside your docs directory. This is where all VitePress-specific files will be placed. Your project structure is probably like this: - -```bash +``` . β”œβ”€ docs β”‚ β”œβ”€ .vitepress @@ -22,58 +20,8 @@ export default { } ``` -Check out the [Config Reference](../config/basics) for a full list of options. - -## Config Intellisense - -Since VitePress ships with TypeScript typings, you can leverage your IDE's intellisense with jsdoc type hints: - -```js -/** - * @type {import('vitepress').UserConfig} - */ -const config = { - // ... -} - -export default config -``` - -Alternatively, you can use the `defineConfig` helper at which should provide intellisense without the need for jsdoc annotations: - -```js -import { defineConfig } from 'vitepress' - -export default defineConfig({ - // ... -}) -``` - -VitePress also directly supports TS config files. You can use `.vitepress/config.ts` with the `defineConfig` helper as well. - -## Typed Theme Config - -By default, `defineConfig` helper leverages the theme config type from default theme: +In the above example, the site will have the title of `VitePress`, and `Just playing around` as description meta tag. -```ts -import { defineConfig } from 'vitepress' +Learn everything about VitePress features at [Theme: Introduction](./theme-introduction) to find how to configure specific features with in this config file. -export default defineConfig({ - themeConfig: { - // Type is `DefaultTheme.Config` - } -}) -``` - -If you use a custom theme and want type checks for the theme config, you'll need to use `defineConfigWithTheme` instead, and pass the config type for your custom theme via a generic argument: - -```ts -import { defineConfigWithTheme } from 'vitepress' -import { ThemeConfig } from 'your-theme' - -export default defineConfigWithTheme({ - themeConfig: { - // Type is `ThemeConfig` - } -}) -``` +You may also find all configuration references at [Configs](../config/introduction). diff --git a/docs/guide/deploy.md b/docs/guide/deploying.md similarity index 97% rename from docs/guide/deploy.md rename to docs/guide/deploying.md index 8497ecd6..5e9d53f6 100644 --- a/docs/guide/deploy.md +++ b/docs/guide/deploying.md @@ -1,7 +1,3 @@ ---- -sidebarDepth: 3 ---- - # Deploying The following guides are based on some shared assumptions: @@ -19,7 +15,7 @@ The following guides are based on some shared assumptions: } ``` -## Building The Docs +## Build and test locally You may run `yarn docs:build` command to build the docs. @@ -29,8 +25,6 @@ $ yarn docs:build By default, the build output will be placed at `.vitepress/dist`. You may deploy this `dist` folder to any of your preferred platforms. -### Testing The Docs Locally - Once you've built the docs, you may test them locally by running `yarn docs:serve` command. ```bash @@ -94,7 +88,7 @@ cd - You can also run the above script in your CI setup to enable automatic deployment on each push. ::: -### GitHub Pages and Travis CI +## GitHub Pages and Travis CI 1. Set the correct `base` in `docs/.vitepress/config.js`. @@ -141,9 +135,8 @@ deploy: 3. Create a file called `.gitlab-ci.yml` in the root of your project with the content below. This will build and deploy your site whenever you make changes to your content: ```yaml -image: node:16.5.0 +image: node:10.22.0 pages: - stage: deploy cache: paths: - node_modules/ @@ -153,8 +146,8 @@ pages: artifacts: paths: - public - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + only: + - main ``` ## Netlify diff --git a/docs/guide/differences-from-vuepress.md b/docs/guide/differences-from-vuepress.md deleted file mode 100644 index b6c6eab5..00000000 --- a/docs/guide/differences-from-vuepress.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -sidebarDepth: 2 ---- - -# Differences from VuePress - -VitePress and VuePress have different [design goals](../index.md). Both projects share similar config naming conventions. VitePress aims to have the bare minimum features needed for authoring docs. Other features are pushed to Themes. On the other hand, VuePress has more features out-of-the-box or enabled by its ecosystem of plugins. - -::: tip -If you are using VuePress, there is no need to migrate to VitePress. Both projects are going to continue to co-exist for the foreseeable future. -::: - -::: warning -Note this is early WIP! Currently, the focus is on making Vite stable and feature-complete first. It is not recommended to use this for anything serious yet. -::: - -In case you decide to move your project to VitePress, this is a list of differences from [VuePress v1.7.1](https://github.com/vuejs/vuepress/releases/tag/v1.7.1) that you need to take into account. - -## General - -- Missing - - YAML and TOML are not supported formats for site config. Only javascript is supported for `.vitepress/config.js` - - [Plugins](https://vuepress.vuejs.org/plugin/) support, features are implemented in themes - - [permalink support](https://vuepress.vuejs.org/guide/permalinks.html) - - `.vitepress/templates` - - Components in `.vitepress/components` [are not auto registered as global components](https://vuepress.vuejs.org) -- Differences - - [Public files](https://vuepress.vuejs.org/guide/assets.html#public-files) that are directly copied to dist root moved from `.vitepress/public/` is `public/` - - [styling](https://vuepress.vuejs.org/config/#styling) `.vitepress/styles/index.styl` and `.vitepress/styles/palette.styl` is not supported. See [Customizing CSS](./theming#customizing-css). - - [App Level Enhancements](https://vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements) API, app enhancements `.vitepress/enhanceApp.js` is now done in `.vitepress/theme/index.js`. See [Extending the Default Theme](./theming#extending-the-default-theme). - -## Markdown - -- Missing - - Support for [toml in frontmatter](https://vuepress.vuejs.org/guide/frontmatter.html#alternative-frontmatter-formats) - - [details block](https://vuepress.vuejs.org/guide/markdown.html#custom-containers) - - [markdown slots](https://vuepress.vuejs.org/guide/markdown-slot.html) - - `~` prefix to explicitly specify a url is a [webpack module request](https://vuepress.vuejs.org/guide/assets.html#relative-urls) - -## Site Config - -- Missing - - `temp` - - `dest` - - [`theme` from a dependency](https://vuepress.vuejs.org/theme/using-a-theme.html#using-a-theme-from-a-dependency) - - `permalink` - - [`port`](https://vuepress.vuejs.org/config/#port) - - [`shouldPrefetch`](https://vuepress.vuejs.org/config/#shouldprefetch) - - [`cache`](https://vuepress.vuejs.org/config/#cache) - - [`extraWatchFiles`](https://vuepress.vuejs.org/config/#extrawatchfiles) - - [`patterns`](https://vuepress.vuejs.org/config/#patterns) - - [`plugins`](https://vuepress.vuejs.org/config/#pluggable) - - [`markdown.pageSuffix`](https://vuepress.vuejs.org/config/#markdown-pagesuffix) - - [`markdown.slugify`](https://vuepress.vuejs.org/config/#markdown-slugify) - - [`markdown.plugins`](https://vuepress.vuejs.org/config/#markdown-plugins) - - [`markdown.extractHeaders`](https://vuepress.vuejs.org/config/#markdown-extractheaders) - - `markdown.extendMarkdown` to `markdown.config` - - `configureWebpack`, `chainWebpack`, `postcss`, `Stylus`, `scss`, `Sass`, `less` configs - - [`evergreen`](https://vuepress.vuejs.org/config/#evergreen) - -## Default Theme Config - -- Missing - - [`smoothScroll`](https://vuepress.vuejs.org/theme/default-theme-config.html#smooth-scrolling) - - [`displayAllHeaders`](https://vuepress.vuejs.org/theme/default-theme-config.html#displaying-header-links-of-all-pages) - - [`activeHeaderLinks`](https://vuepress.vuejs.org/theme/default-theme-config.html#active-header-links) - - `sidebarDepth` and `initialOpenGroupIndex` for [sidebar groups](https://vuepress.vuejs.org/theme/default-theme-config.html#sidebar-groups) -- Differences - - `searchMaxSuggestions` is `search.maxSuggestions` - - `algolia` is `search.algolia` - - `searchPlaceholder` is `search.placeholder` - -## Default Theme - -- Missing - - [`` and ``](https://vuepress.vuejs.org/theme/default-theme-config.html#code-groups-and-code-blocks) - -## Computed Globals - -- Missing - - `$lang` - - `$localePath` - -## Frontmatter Predefined Variables - -- Missing - - `description` - - [`meta`](https://vuepress.vuejs.org/guide/frontmatter.html#meta) - - [`metaTitle`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables) - - `lang` - - [`layout`](https://vuepress.vuejs.org/guide/frontmatter.html#layout) - - [`permalink`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables) - - [`canonicalUrl`](https://vuepress.vuejs.org/guide/frontmatter.html#predefined-variables) - -## Frontmatter Default Theme Variables - -- Missing - - `prev`, `next` - - [`search`](https://vuepress.vuejs.org/guide/frontmatter.html#search) - - [`tags`](https://vuepress.vuejs.org/guide/frontmatter.html#tags) - - [`pageClass`](https://vuepress.vuejs.org/theme/default-theme-config.html#custom-page-class) - - [`layout`](https://vuepress.vuejs.org/theme/default-theme-config.html#custom-layout-for-specific-pages) - -## siteData - -- Missing - - [`pages`](https://vuepress.vuejs.org/theme/writing-a-theme.html#site-and-page-metadata) - -## pageData - -- Missing - - `key` - - `path` - - `regularPath` - -## Global Components - -- Missing - - [``](https://vuepress.vuejs.org/guide/using-vue.html#badge) diff --git a/docs/guide/frontmatter.md b/docs/guide/frontmatter.md index 40fd2691..c3dbcd29 100644 --- a/docs/guide/frontmatter.md +++ b/docs/guide/frontmatter.md @@ -9,7 +9,7 @@ editLink: true --- ``` -Between the triple-dashed lines, you can set [predefined variables](#predefined-variables), or even create custom ones of your own. These variables can be used via the special $frontmatter variable. +Between the triple-dashed lines, you can set [predefined variables](../config/frontmatter-configs), or even create custom ones of your own. These variables can be used via the special $frontmatter variable. Here’s an example of how you could use it in your Markdown file: @@ -24,7 +24,7 @@ editLink: true Guide content ``` -## Alternative frontmatter Formats +## Alternative Frontmatter Formats VitePress also supports JSON frontmatter syntax, starting and ending in curly braces: @@ -36,52 +36,3 @@ VitePress also supports JSON frontmatter syntax, starting and ending in curly br } --- ``` - -## Predefined Variables - -### title - -- Type: `string` -- Default: `h1_title || siteData.title` - -Title of the current page. - -### head - -- Type: `array` -- Default: `undefined` - -Specify extra head tags to be injected: - -```yaml ---- -head: - - - meta - - name: description - content: hello - - - meta - - name: keywords - content: super duper SEO ---- -``` - -### navbar - -- Type: `boolean` -- Default: `undefined` - -You can disable the navbar on a specific page with `navbar: false` - -### sidebar - -- Type: `boolean|'auto'` -- Default: `undefined` - -You can decide to show the sidebar on a specific page with `sidebar: auto` or disable it with `sidebar: false` - -### editLink - -- Type: `boolean` -- Default: `undefined` - -Define if this page should include an edit link. diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 2b891fd3..315940c3 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -1,51 +1,87 @@ # Getting Started -This section will help you build a basic VitePress documentation site from ground up. If you already have an existing project and would like to keep documentation inside the project, start from Step 3. +This section will help you build a basic VitePress documentation site from ground up. If you already have an existing project and would like to keep documentation inside the project, start from Step 2. -- **Step. 1:** Create and change into a new directory. +::: warning +VitePress is currently in `alpha` status. It is already suitable for out-of-the-box documentation use, but the config and theming API may still change between minor releases. +::: - ```bash - $ mkdir vitepress-starter && cd vitepress-starter - ``` +## Step. 1: Create a new project -- **Step. 2:** Initialize with your preferred package manager. +Create and change into a new directory. - ```bash - $ yarn init - ``` +```bash +$ mkdir vitepress-starter && cd vitepress-starter +``` -- **Step. 3:** Install VitePress locally. +Then, initialize with your preferred package manager. - ```bash - $ yarn add --dev vitepress - ``` +```bash +$ yarn init +``` -- **Step. 4:** Create your first document. +## Step. 2: Install VitePress - ```bash - $ mkdir docs && echo '# Hello VitePress' > docs/index.md - ``` +Add VitePress as a dependency for the project. -- **Step. 5:** Add some scripts to `package.json`. +```bash +$ yarn add --dev vitepress +``` - ```json - { - "scripts": { - "docs:dev": "vitepress dev docs", - "docs:build": "vitepress build docs", - "docs:serve": "vitepress serve docs" - } - } - ``` +Create your first document. -- **Step. 6:** Serve the documentation site in the local server. +```bash +$ mkdir docs && echo '# Hello VitePress' > docs/index.md +``` - ```bash - $ yarn docs:dev - ``` +## Step. 3: Boot up dev environment - VitePress will start a hot-reloading development server at `http://localhost:3000`. +Add some scripts to `package.json`. -By now, you should have a basic but functional VitePress documentation site. +```json +{ + ... + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:serve": "vitepress serve docs" + }, + ... +} +``` -When your documentation site starts to take shape, be sure to read the [deployment guide](./deploy). +Serve the documentation site in the local server. + +```bash +$ yarn docs:dev +``` + +VitePress will start a hot-reloading development server at `http://localhost:3000`. + +## Step. 4: Add more pages + +Let's add another page to the site. Create a file name `getting-started.md` along with `index.md` you've created in Step. 2. Now your directory structure should look like this. + +``` +. +β”œβ”€ docs +β”‚ β”œβ”€ getting-started.md +β”‚ └─ index.md +└─ package.json +``` + +Then, try to access `http://localhost:3000/getting-started` and you should see the content of `getting-started` is shown. + +This is how VitePress works basically. The directory structure corresponds with the URL path. You add files, and just try to access it. + +## What's next? + +By now, you should have a basic but functional VitePress documentation site. But currently, the user has no way to navigate around the site because it's missing for example sidebar menu we have on this site. + +To enable those navigations, we must add some configurations to the site. Head to [configuration guide](./configuration) to learn how to configure VitePress. + +If you would like to know more about what you can do within the page, for example, writing markdown contents, or using Vue Component, check out the "Writing" section of the docs. [Markdown guide](./markdown) would be a greate starting point. + +If you want to know how to customize how the site looks (Theme), and find out the features VitePress's default theme provides, visit [Theme: Introduction](./theme-introduction). + +When your documentation site starts to take shape, be sure to read the [deployment guide](./deploying). diff --git a/docs/guide/global-component.md b/docs/guide/global-component.md deleted file mode 100644 index ce843322..00000000 --- a/docs/guide/global-component.md +++ /dev/null @@ -1,32 +0,0 @@ -# Global Component - -VitePress comes with few built-in component that can be used globally. You may use these components in your markdown or your custom theme configuration. - -## Content - -The `Content` component displays the rendered markdown contents. Useful [when creating your own theme](./theming). - -```vue - -``` - -## ClientOnly - -The `ClientOnly` component renderes its slot only at client side. - -Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the universal code requirements. In short, make sure to only access Browser / DOM APIs in beforeMount or mounted hooks. - -If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the `ClientOnly` component. - -```html - - - -``` - -## OutboundLink - -The indicator `OutboundLink` is used to denote external links. In VitePress, this component has been followed by every external link. diff --git a/docs/guide/markdown.md b/docs/guide/markdown.md index 4f11eecf..846b867f 100644 --- a/docs/guide/markdown.md +++ b/docs/guide/markdown.md @@ -1,15 +1,15 @@ ---- -sidebarDepth: 3 ---- - # Markdown Extensions +VitePress comes with built in Markdown Extensions. + ## Header Anchors Headers automatically get anchor links applied. Rendering of anchors can be configured using the `markdown.anchor` option. ## Links +Both internal and external links gets special treatments. + ### Internal Links Internal links are converted to router link for SPA navigation. Also, every `index.md` contained in each sub-directory will automatically be converted to `index.html`, with corresponding URL `/`. @@ -123,51 +123,53 @@ Custom containers can be defined by their types, titles, and contents. **Input** ```md -::: tip -This is a tip +::: info +This is an info box. ::: -::: info -This is an info box +::: tip +This is a tip. ::: ::: warning -This is a warning +This is a warning. ::: ::: danger -This is a dangerous warning +This is a dangerous warning. ::: ::: details -This is a details block, which does not work in Internet Explorer or old versions of Edge. +This is a details block. ::: ``` **Output** -::: tip -This is a tip +::: info +This is an info box. ::: -::: info -This is an info box +::: tip +This is a tip. ::: ::: warning -This is a warning +This is a dangerous warning. ::: ::: danger -This is a dangerous warning +This is a dangerous warning. ::: ::: details -This is a details block, which does not work in Internet Explorer or Edge. +This is a details block. ::: ### Custom Title +You may set custom title by appending the text right after the "type" of the container. + **Input** ````md @@ -176,11 +178,9 @@ Danger zone, do not proceed ::: ::: details Click me to view the code - ```js console.log('Hello, VitePress!') ``` - ::: ```` @@ -191,16 +191,14 @@ Danger zone, do not proceed ::: ::: details Click me to view the code - ```js console.log('Hello, VitePress!') ``` - ::: ## Syntax Highlighting in Code Blocks -VitePress uses [Prism](https://prismjs.com) to highlight language syntax in Markdown code blocks, using coloured text. Prism supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block: +VitePress uses [Shiki](https://shiki.matsu.io/) to highlight language syntax in Markdown code blocks, using coloured text. Shiki supports a wide variety of programming languages. All you need to do is append a valid language alias to the beginning backticks for the code block: **Input** @@ -213,17 +211,6 @@ export default { ``` ```` -**Output** - -```js -export default { - name: 'MyComponent' - // ... -} -``` - -**Input** - ```` ```html
    @@ -236,15 +223,24 @@ export default { **Output** +```js +export default { + name: 'MyComponent' + // ... +} +``` + ```html
    • - {{ todo.text }} + {{ todo.text }}
    ``` -A [list of valid languages](https://prismjs.com/#languages-list) is available on Prism’s site. +A [list of valid languages](https://github.com/shikijs/shiki/blob/main/docs/languages.md) is available on Shiki’s repository. + +You may also customize syntax highlight theme in app config. Please see [`markdown` options](../config/app-configs#markdown) for more details. ## Line Highlighting in Code Blocks @@ -283,7 +279,7 @@ In addition to a single line, you can also specify multiple single lines, ranges **Input** ```` -```js{1,4,6-8} +```js{1,4,6-7} export default { // Highlighted data () { return { @@ -291,7 +287,7 @@ export default { // Highlighted This line isn't highlighted, but this and the next 2 are.`, motd: 'VitePress is awesome', - lorem: 'ipsum', + lorem: 'ipsum' } } } @@ -300,7 +296,7 @@ export default { // Highlighted **Output** -```js{1,4,6-8} +```js{1,4,6-7} export default { // Highlighted data () { return { @@ -319,46 +315,14 @@ export default { // Highlighted You can enable line numbers for each code blocks via config: ```js -module.exports = { +export default { markdown: { lineNumbers: true } } ``` -- Demo: - - - - Image - - - - - Image - - - +Please see [`markdown` options](../config/app-configs#markdown) for more details. ## Import Code Snippets @@ -439,8 +403,8 @@ module.exports = { permalink: anchor.permalink.headerLink() }, - // options for markdown-it-table-of-contents - toc: { includeLevel: [1, 2] }, + // options for markdown-it-toc-done-right + toc: { level: [1, 2] }, config: (md) => { // use more markdown-it plugins! @@ -449,3 +413,5 @@ module.exports = { } } ``` + +See full list of configurable properties in [Configs: App Configs](../config/app-configs#markdown). diff --git a/docs/guide/migration-from-vitepress-0.md b/docs/guide/migration-from-vitepress-0.md new file mode 100644 index 00000000..80c5803f --- /dev/null +++ b/docs/guide/migration-from-vitepress-0.md @@ -0,0 +1,23 @@ +# Migration from VitePress 0.x + +If you're coming from VitePress 0.x version, there're several breaking changes due to new features and enhancement. Please follow this guide to see how to migrate your app over to the latest VitePress. + +## App Config + +- The internationalization feature is not yet implemented. + +## Theme Config + +- `sidebar` option has changed its structure. + - `children` key is now named `items`. + - Top level item may not contain `link` at the moment. We're planning to bring it back. +- `repo`, `repoLabel`, `docsDir`, `docsBranch`, `editLinks`, `editLinkText` are removed in favor of more flexible api. + - For adding GitHub link with icon to the nav, use [Social Links](./theme-nav.html#navigation-links) feature. + - For adding "Edit this page" feature, use [Edit Link](./theme-edit-link) feature. +- `lastUpdated` option is now split into `config.lastUpdated` and `themeConfig.lastUpdatedText`. +- `carbonAds.carbon` is changed to `carbonAds.code`. + +## Frontmatter Config + +- `home: true` option has changed to `layout: home`. Also, many Homepage related settings have been modified to provide additional features. See [Homepage guide](./theme-homepage) for details. +- `footer` option is moved to [`themeConfig.footer`](../config/theme-configs#footer). diff --git a/docs/guide/migration-from-vuepress.md b/docs/guide/migration-from-vuepress.md new file mode 100644 index 00000000..75faf43a --- /dev/null +++ b/docs/guide/migration-from-vuepress.md @@ -0,0 +1,3 @@ +# Migration from VuePress + +Coming soon... diff --git a/docs/guide/theme-carbon-ads.md b/docs/guide/theme-carbon-ads.md new file mode 100644 index 00000000..1c0c170e --- /dev/null +++ b/docs/guide/theme-carbon-ads.md @@ -0,0 +1,22 @@ +# Carbon Ads + +VitePress has built in native support for [Carbon Ads](https://www.carbonads.net/). By defining the Carbon Ads credentials in config, VitePress will display ads on the page. + +```js +export default { + themeConfig: { + carbonAds: { + code: 'your-carbon-code', + placement: 'your-carbon-placement' + } + } +} +``` + +These values are used to call carbon CDN script as shown below. + +```js +`//cdn.carbonads.com/carbon.js?serve=${code}&placement=${placement}` +``` + +To learn more about Carbon Ads configuration, please visit [Carbon Ads website](https://www.carbonads.net/). diff --git a/docs/guide/theme-edit-link.md b/docs/guide/theme-edit-link.md new file mode 100644 index 00000000..39eba214 --- /dev/null +++ b/docs/guide/theme-edit-link.md @@ -0,0 +1,3 @@ +# Edit Link + +Documentation coming soon... diff --git a/docs/guide/theme-footer.md b/docs/guide/theme-footer.md new file mode 100644 index 00000000..a3e0efde --- /dev/null +++ b/docs/guide/theme-footer.md @@ -0,0 +1,26 @@ +# Footer + +When the [page layout](./theme-layout) is set to either `home` or `page`, VitePress will display global footer at the bottom of the page. Set `themeConfig.footer` to configure footer content. + +```ts +export default { + themeConfig: { + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright Β© 2019-present Evan You' + } + } +} +``` + +```ts +export interface Footer { + // The message shown rigth before copyright. + message?: string + + // The actual copyright text. + copyright?: string +} +``` + +Note that footer will not be displayed when the page layout is set to `doc`. diff --git a/docs/guide/theme-homepage.md b/docs/guide/theme-homepage.md new file mode 100644 index 00000000..d859e073 --- /dev/null +++ b/docs/guide/theme-homepage.md @@ -0,0 +1,116 @@ +# Homepage + +VitePress default theme provides a homepage layout, which you can also see used on [the homepage of this site](../). You may use it on any of your pages by specifying `layout: home` in the [frontmatter](./frontmatter). + +```yaml +--- +layout: home +--- +``` + +However, this option alone wouldn't do much. You can add several different pre templated "sections" to the homepage by setting additional other options such as `hero` and `feaures`. + +## Hero Section + +The Hero section comes at the top of the homepage. Here's how you can configure the Hero section. + +```yaml +--- +layout: home + +hero: + name: VuePress + text: Vite & Vue powered static site generator. + tagline: Lorem ipsum... + actions: + - theme: brand + text: Get Started + link: /guide/what-is-vitepress + - theme: alt + text: View on GitHub + link: https://github.com/vuejs/vitepress +--- +``` + +```ts +interface Hero { + // The string shown top of `text`. Comes with brand color + // and expected to be short, such as product name. + name?: string + + // The main text for the hero section. This will be defined + // as `h1` tag. + text: string + + // Tagline displayed below `text`. + tagline?: string + + // Action buttons to display in home hero section. + actions?: HeroAction[] +} + +interface HeroAction { + // Color theme of the button. Defaults to `brand`. + theme?: 'brand' | 'alt' + + // Label of the button. + text: string + + // Destination link of the button. + link: string +} +``` + +### Customizing the name color + +VitePress uses the brand color (`--vp-c-brand`) for the `name`. However, you may customize this color by overriding `--vp-home-hero-name-color` variable. + +```css +:root { + --vp-home-hero-name-color: blue; +} +``` + +Also you may customize it further by combining `--vp-home-hero-name-background` to give the `name` gradient color. + +```css +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe, #41d1ff); +} +``` + +## Features Section + +In Features section, you can list any number of features you would like to show right after the Hero section. To cinfugure it, pass `features` option to the frontmatter. + +```yaml +--- +layout: home + +features: + - icon: ⚑️ + title: Vite, The DX that can't be beat + details: Lorem ipsum... + - icon: πŸ–– + title: Power of Vue meets Markdown + details: Lorem ipsum... + - icon: πŸ› οΈ + title: Simple and minimal, always + details: Lorem ipsum... +--- +``` + +```ts +interface Feature { + // Show icon on each feature box. Currently, only emojis + // are supported. + icon?: string + + // Title of the feature. + title: string + + // Details of the feature. + details: string +} +``` diff --git a/docs/guide/theming.md b/docs/guide/theme-introduction.md similarity index 62% rename from docs/guide/theming.md rename to docs/guide/theme-introduction.md index 6e1e349f..4fddcba6 100644 --- a/docs/guide/theming.md +++ b/docs/guide/theme-introduction.md @@ -1,10 +1,25 @@ -# Theming +# Theme Introduction + +VitePress comes with its default theme providing many features out of the box. Learn more about each feature on its dedicated page listed below. + +- [Nav](./theme-nav) +- [Sidebar](./theme-sidebar) +- [Prev Next Link](./theme-prev-next-link) +- [Edit Link](./theme-edit-link) +- [Last Updated](./theme-last-updated) +- [Layout](./theme-layout) +- [Homepage](./theme-homepage) +- [Footer](./theme-footer) +- [Search](./theme-search) +- [Carbon Ads](./theme-carbon-ads) + +If you don't find the features you're looking for, or you would rather create your own theme, you may customize VitePress to fit your requirements. ## Using a Custom Theme You can enable a custom theme by adding the `.vitepress/theme/index.js` file (the "theme entry file"). -```bash +``` . β”œβ”€ docs β”‚ β”œβ”€ .vitepress @@ -39,10 +54,14 @@ import Layout from './Layout.vue' export default { Layout, - NotFound: () => 'custom 404', // <- this is a Vue 3 functional component + + // this is a Vue 3 functional component + NotFound: () => 'custom 404', + enhanceApp({ app, router, siteData }) { - // app is the Vue 3 app instance from `createApp()`. router is VitePress' - // custom router. `siteData` is a `ref` of current site-level metadata. + // app is the Vue 3 app instance from `createApp()`. + // router is VitePress' custom router. `siteData` is + // a `ref` of current site-level metadata. } } ``` @@ -53,7 +72,9 @@ export default { ``` @@ -64,6 +85,7 @@ To distribute a theme, simply export the object in your package entry. To consum ```js // .vitepress/theme/index.js import Theme from 'awesome-vitepress-theme' + export default Theme ``` @@ -81,7 +103,7 @@ export default { ...DefaultTheme, enhanceApp({ app }) { // register global components - app.component('MyGlobalComponent' /* ... */) + app.component('MyGlobalComponent', /* ... */) } } ``` @@ -103,8 +125,8 @@ export default DefaultTheme ```css /* .vitepress/theme/custom.css */ :root { - --c-brand: #646cff; - --c-brand-light: #747bff; + --vp-c-brand: #646cff; + --vp-c-brand-light: #747bff; } ``` @@ -112,7 +134,7 @@ See [default theme CSS variables](https://github.com/vuejs/vitepress/blob/main/s ### Layout Slots -The default theme's `` component has a few slots that can be used to inject content at certain locations of the page. Here's an example of injecting a component into the top of the sidebar: +The default theme's `` component has a few slots that can be used to inject content at certain locations of the page. Here's an example of injecting a component into the before outline: ```js // .vitepress/theme/index.js @@ -121,7 +143,8 @@ import MyLayout from './MyLayout.vue' export default { ...DefaultTheme, - // override the Layout with a wrapper component that injects the slots + // override the Layout with a wrapper component that + // injects the slots Layout: MyLayout } ``` @@ -130,26 +153,47 @@ export default { ``` +Or you could use render function as well. + +```js +// .vitepress/theme/index.js +import DefaultTheme from 'vitepress/theme' +import MyComponent from './MyComponent.vue' + +export default { + ...DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + 'sidebar-top': () => h(MyComponent) + }) + } +} +``` + Full list of slots available in the default theme layout: -- `navbar-search` -- `sidebar-top` -- `sidebar-bottom` -- `page-top-ads` -- `page-top` -- `page-bottom` -- `page-bottom-ads` -- Only when `home: true` is enabled via frontmatter: - - `home-hero` - - `home-features` - - `home-footer` +- When `layout: 'doc'` (default) is enabled via frontmatter: + - `aside-top` + - `aside-bottom` + - `aside-outline-before` + - `aside-outline-after` + - `aside-ads-before` + - `aside-ads-after` +- When `layout: 'home'` is enabled via frontmatter: + - `home-hero-before` + - `home-hero-after` + - `home-features-before` + - `home-features-after` diff --git a/docs/guide/theme-last-updated.md b/docs/guide/theme-last-updated.md new file mode 100644 index 00000000..db41c99d --- /dev/null +++ b/docs/guide/theme-last-updated.md @@ -0,0 +1,3 @@ +# Last Updated + +Documentation coming soon... diff --git a/docs/guide/theme-layout.md b/docs/guide/theme-layout.md new file mode 100644 index 00000000..8ea0c5f6 --- /dev/null +++ b/docs/guide/theme-layout.md @@ -0,0 +1,38 @@ +--- +layout: doc +--- + +# Layout + +You may choose the page layout by setting `layout` option to the page [frontmatter](./frontmatter). There are 3 layout options, `doc`, `page`, and `home`. If nothing is specified, then the page is treated as `doc` page. + +```yaml +--- +layout: doc +--- +``` + +## Doc Layout + +Option `doc` is the default layout and it styles the whole Markdown content into "documentation" look. It works by wrapping whole content within `vp-doc` css class, and applying styles to elements underneath it. + +Almost all generic elements such as `p`, or `h2` get special styling. Therefore, keep in mind that if you add any custom HTML inside a Markdown content, those will get affected by those styles as well. + +It also provides documentation specific features listed below. These features are only enabled in this layout. + +- Edit Link +- Prev Next Link +- Outline +- [Carbon Ads](./theme-carbon-ads) + +## Page Layout + +Option `page` is treated as "blank page". The Markdown will still be parsed, and all of the [Markdown Extensions](./markdown) work as same as `doc` layout, but it wouldn't get any default stylings. + +The page layout will let you style everything by you without VitePress theme affecting the markup. This is useful when you want to create your own custom page. + +Note that even in this layout, sidebar will still show up if the page has a matching sidebar config. + +## Home Layout + +Option `home` will generate templated "Homepage". In this layout, you can set extra options such as `hero` and `features` to customize the content further. Please visit [Theme: Home Page](./theme-homepage) for more details. diff --git a/docs/guide/theme-nav.md b/docs/guide/theme-nav.md new file mode 100644 index 00000000..92413974 --- /dev/null +++ b/docs/guide/theme-nav.md @@ -0,0 +1,164 @@ +# Nav + +The Nav is the navigation bar displayed on top of the page. It contains the site title, global menu links, etc. + +## Site Title and Logo + +By default, nav shows the title of the site refferencing [`config.title`](../config/app-configs.html#title) value. If you would like to change what's displayed on nav, you may define custom text in `themeConfig.siteTitle` option. + +```js +export default { + themeConfig: { + siteTitle: 'My Custom Title' + } +} +``` + +If you have a logo for your site, you can display it by passing in the path to the image. You should place the logo within `public` directly, and define the absolute path to it. + +```js +export default { + themeConfig: { + logo: '/my-logo.svg' + } +} +``` + +When adding a logo, it gets displayed along with the site title. If your logo is all you need and if you would like to hide the site title text, set `false` to the `siteTitle` option. + +```js +export default { + themeConfig: { + logo: '/my-logo.svg', + siteTitle: false + } +} +``` + +## Navigation Links + +You may define `themeConfig.nav` option to add links to your nav. + +```js +export default { + themeConfig: { + nav: [ + { text: 'Guide', link: '/guide' }, + { text: 'Configs', link: '/configs' }, + { text: 'Changelog', link: 'https://github.com/...' } + ] + } +} +``` + +The `text` is the actual text displayed in nav, and the `link` is the link that will be navigated to when the text is clicked. For the link, set path to the actual file without `.md` prefix, and alsways start with `/`. + +Nav links can also be dropdown menus. To do this, set `items` key on link option. + +```js +export default { + themeConfig: { + nav: [ + { text: 'Guide', link: '/guide' }, + { + text: 'Dropdown Menu', + items: [ + { text: 'Item A', link: '/item-1' }, + { text: 'Item B', link: '/item-2' }, + { text: 'Item C', link: '/item-3' } + ] + } + ] + } +} +``` + +Note that dropdown menu title (`Dropdown Menu` in the above example) can not have `link` property since it becomes a button to open dropdown dialog. + +You may further add "sections" to the dropdown menu items as well by passing in more nested items. + +```js +export default { + themeConfig: { + nav: [ + { text: 'Guide', link: '/guide' }, + { + text: 'Dropdown Menu', + items: [ + { + // Title for the section. + text: 'Section A Title', + items: [ + { text: 'Section A Item A', link: '...' }, + { text: 'Section B Item B', link: '...' } + ] + } + ] + }, + { + text: 'Dropdown Menu', + items: [ + { + // You may also omit the title. + items: [ + { text: 'Section A Item A', link: '...' }, + { text: 'Section B Item B', link: '...' } + ] + } + ] + } + ] + } +} +``` + +### Customize link's "active" state + +Nav menu items will be highlighted when the current page is under the matching path. if you would like to customize the path to be mathced, define `activeMatch` property and regex as a string value. + +```js +export default { + themeConfig: { + nav: [ + // This link gets active state when the user is + // on `/config/` path. + { + text: 'Guide', + link: '/guide', + activeMatch: '/config/' + } + ] + } +} +``` + +::: warning +`activeMatch` is expected to be a regex string, but you must define it as a string. We can't use actual RegExp object here because it isn't serializable during the build time. +::: + +## Social Links + +You may define `socialLinks` option to show your social account links with icons. + +```js +export default { + themeConfig: { + socialLinks: [ + { icon: 'github', link: 'https://github.com/vuejs/vitepress' }, + { icon: 'twitter', link: '...' }, + { icon: 'discord', link: '...' } + ] + } +} +``` + +The list of supported icons are shown below. + +- `discord` +- `facebook` +- `github` +- `instagram` +- `linkedin` +- `slack` +- `twitter` +- `youtube` diff --git a/docs/guide/theme-prev-next-link.md b/docs/guide/theme-prev-next-link.md new file mode 100644 index 00000000..c248d1f3 --- /dev/null +++ b/docs/guide/theme-prev-next-link.md @@ -0,0 +1,3 @@ +# Prev Next Link + +Documentation coming soon... diff --git a/docs/guide/theme-search.md b/docs/guide/theme-search.md new file mode 100644 index 00000000..f33d3b35 --- /dev/null +++ b/docs/guide/theme-search.md @@ -0,0 +1,3 @@ +# Search + +Documentation coming soon... diff --git a/docs/guide/theme-sidebar.md b/docs/guide/theme-sidebar.md new file mode 100644 index 00000000..3a2c853d --- /dev/null +++ b/docs/guide/theme-sidebar.md @@ -0,0 +1,159 @@ +# Sidebar + +The sidebar is the main navigation block for your documentation. You can configure the sidebar menu in `themeConfig.sidebar`. + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Guide', + items: [ + { text: 'Introduction', link: '/introduction' }, + { text: 'Getting Started', link: '/getting-started' }, + ... + ] + } + ] + } +} +``` + +## The Basics + +The simplest form of the sidebar menu is passing in a single array of links. The first level item defines the "section" for the sidebar. It should contain `text`, which is the title of the section, and `items` which are the actual navigation links. + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Section Title A', + items: [ + { text: 'Item A', link: '/item-a' }, + { text: 'Item B', link: '/item-b' }, + ... + ] + }, + { + text: 'Section Title B', + items: [ + { text: 'Item C', link: '/item-c' }, + { text: 'Item D', link: '/item-d' }, + ... + ] + } + ] + } +} +``` + +Each `link` should specify the path to the actual file starting with `/`. If you add trailing slash to the end of link, it will show `index.md` of the corresponding directory. + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Guide', + items: [ + // This shows `/guide/index.md` page. + { text: 'Introduction', link: '/guide/' } + ] + } + ] + } +} +``` + +## Multiple Sidebars + +You may show different sidebar depending on the page path. For example, as shown on this site, you might want to create a separate sections of content in your documentation like "Guide" page and `Config` page. + +To do so, first organize your pages into directories for each desired section: + +``` +. +β”œβ”€ guide/ +β”‚ β”œβ”€ index.md +β”‚ β”œβ”€ one.md +β”‚ └─ two.md +└─ config/ + β”œβ”€ index.md + β”œβ”€ three.md + └─ four.md +``` + +Then, update your configuration to define your sidebar for each section. This time, you should pass an object instead of an array. + +```js +export default { + themeConfig: { + sidebar: { + // This sidebar gets displayed when user is + // under `guide` directory. + '/guide/': { + text: 'Guide', + items: [ + // This shows `/guide/index.md` page. + { text: 'Index', link: '/guide/' }, // /guide/index.md + { text: 'One', link: '/guide/one' }, // /guide/one.md + { text: 'Two', link: '/guide/two' } // /guide/two.md + ] + }, + + // This sidebar gets displayed when user is + // under `config` directory. + '/config/': { + text: 'Config', + items: [ + // This shows `/guide/index.md` page. + { text: 'Index', link: '/config/' }, // /config/index.mdasdfasdfasdfasdfaf + { text: 'Three', link: '/config/three' }, // /config/three.md + { text: 'Four', link: '/config/four' } // /config/four.md + ] + } + } + } +} +``` + +## Collapsible Sidebar Groups + +By adding `collapsible` option to the sidebar group, it shows a toggle button to hide/show each section. + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Section Title A', + collapsible: true, + items: [...] + }, + { + text: 'Section Title B', + collapsible: true, + items: [...] + } + ] + } +} +``` + +All sections are "open" by default. If you would like them to be "closed" on intial page load, set `collapsed` option to `true`. + +```js +export default { + themeConfig: { + sidebar: [ + { + text: 'Section Title A', + collapsible: true, + collapsed: true, + items: [...] + } + ] + } +} +``` diff --git a/docs/guide/using-vue.md b/docs/guide/using-vue.md index d6d5ce7c..998d3aa8 100644 --- a/docs/guide/using-vue.md +++ b/docs/guide/using-vue.md @@ -1,7 +1,3 @@ ---- -sidebarDepth: 3 ---- - # Using Vue in Markdown In VitePress, each markdown file is compiled into HTML and then processed as a Vue Single-File Component. This means you can use any Vue features inside the markdown, including dynamic templating, using Vue components, or arbitrary in-page Vue component logic by adding a ` @@ -107,7 +104,7 @@ This is a .md using a custom component ### Registering global components in the theme -If the components are going to be used across several pages in the docs, they can be registered globally in the theme (or as part of extending the default VitePress theme). Check out the [Theming Guide](./theming) for more information. +If the components are going to be used across several pages in the docs, they can be registered globally in the theme (or as part of extending the default VitePress theme). Check out the [Theming Guide](./theme-introduction) for more information. In `.vitepress/theme/index.js`, the `enhanceApp` function receives the Vue `app` instance so you can [register components](https://vuejs.org/guide/components/registration.html) as you would do in a regular Vue application. @@ -200,7 +197,7 @@ export default { ## Built-In Components -VitePress provides Built-In Vue Components like `ClientOnly` and `OutboundLink`, check out the [Global Component Guide](./global-component) for more information. +VitePress provides Built-In Vue Components like `ClientOnly` and `OutboundLink`, check out the [Global Component Guide](./api) for more information. **Also see:** @@ -214,7 +211,7 @@ If you are using or demoing components that are not SSR-friendly (for example, c ```md - + ``` @@ -236,7 +233,10 @@ If your module `export default` a Vue component, you can register it dynamically ```vue - diff --git a/src/client/theme-default/NotFound.vue b/src/client/theme-default/NotFound.vue index 35cef3e9..193d11cd 100644 --- a/src/client/theme-default/NotFound.vue +++ b/src/client/theme-default/NotFound.vue @@ -1,23 +1,84 @@ + + - + +.title { + padding-top: 12px; + letter-spacing: 2px; + line-height: 20px; + font-size: 20px; + font-weight: 700; +} + +.divider { + margin: 24px auto 18px; + width: 64px; + height: 1px; + background-color: var(--vp-c-divider) +} + +.quote { + margin: 0 auto; + max-width: 256px; + font-size: 14px; + font-weight: 500; + color: var(--vp-c-text-2); +} + +.action { + padding-top: 20px; +} + +.link { + display: inline-block; + border: 1px solid var(--vp-c-brand); + border-radius: 16px; + padding: 3px 16px; + font-size: 14px; + font-weight: 500; + color: var(--vp-c-brand); + transition: border-color 0.25s, color .25s; +} + +.link:hover { + border-color: var(--vp-c-brand-dark); + color: var(--vp-c-brand-dark); +} + diff --git a/src/client/theme-default/components/AlgoliaSearchBox.vue b/src/client/theme-default/components/AlgoliaSearchBox.vue deleted file mode 100644 index 510e15d4..00000000 --- a/src/client/theme-default/components/AlgoliaSearchBox.vue +++ /dev/null @@ -1,188 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/BuySellAds.vue b/src/client/theme-default/components/BuySellAds.vue deleted file mode 100644 index 6c998b39..00000000 --- a/src/client/theme-default/components/BuySellAds.vue +++ /dev/null @@ -1,152 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/CarbonAds.vue b/src/client/theme-default/components/CarbonAds.vue deleted file mode 100644 index df597fb3..00000000 --- a/src/client/theme-default/components/CarbonAds.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/EditLink.vue b/src/client/theme-default/components/EditLink.vue deleted file mode 100644 index ffdab5ea..00000000 --- a/src/client/theme-default/components/EditLink.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/Home.vue b/src/client/theme-default/components/Home.vue deleted file mode 100644 index c4c976a4..00000000 --- a/src/client/theme-default/components/Home.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/HomeFeatures.vue b/src/client/theme-default/components/HomeFeatures.vue deleted file mode 100644 index c435248e..00000000 --- a/src/client/theme-default/components/HomeFeatures.vue +++ /dev/null @@ -1,143 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/HomeFooter.vue b/src/client/theme-default/components/HomeFooter.vue deleted file mode 100644 index 55f27af4..00000000 --- a/src/client/theme-default/components/HomeFooter.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/HomeHero.vue b/src/client/theme-default/components/HomeHero.vue deleted file mode 100644 index eb396dd0..00000000 --- a/src/client/theme-default/components/HomeHero.vue +++ /dev/null @@ -1,161 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/LastUpdated.vue b/src/client/theme-default/components/LastUpdated.vue deleted file mode 100644 index 7adf7222..00000000 --- a/src/client/theme-default/components/LastUpdated.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/NavBar.vue b/src/client/theme-default/components/NavBar.vue deleted file mode 100644 index 5cefd432..00000000 --- a/src/client/theme-default/components/NavBar.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/NavBarTitle.vue b/src/client/theme-default/components/NavBarTitle.vue deleted file mode 100644 index 8b9d3709..00000000 --- a/src/client/theme-default/components/NavBarTitle.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/NavDropdownLink.vue b/src/client/theme-default/components/NavDropdownLink.vue deleted file mode 100644 index eb1001fd..00000000 --- a/src/client/theme-default/components/NavDropdownLink.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/NavDropdownLinkItem.vue b/src/client/theme-default/components/NavDropdownLinkItem.vue deleted file mode 100644 index 907d6ad5..00000000 --- a/src/client/theme-default/components/NavDropdownLinkItem.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/NavLink.vue b/src/client/theme-default/components/NavLink.vue deleted file mode 100644 index 29e76747..00000000 --- a/src/client/theme-default/components/NavLink.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/NavLinks.vue b/src/client/theme-default/components/NavLinks.vue deleted file mode 100644 index bc221596..00000000 --- a/src/client/theme-default/components/NavLinks.vue +++ /dev/null @@ -1,52 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/NextAndPrevLinks.vue b/src/client/theme-default/components/NextAndPrevLinks.vue deleted file mode 100644 index e826aca4..00000000 --- a/src/client/theme-default/components/NextAndPrevLinks.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/Page.vue b/src/client/theme-default/components/Page.vue deleted file mode 100644 index 7a1d422b..00000000 --- a/src/client/theme-default/components/Page.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/PageFooter.vue b/src/client/theme-default/components/PageFooter.vue deleted file mode 100644 index 93c7ce60..00000000 --- a/src/client/theme-default/components/PageFooter.vue +++ /dev/null @@ -1,44 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/SideBar.vue b/src/client/theme-default/components/SideBar.vue deleted file mode 100644 index 0c112e18..00000000 --- a/src/client/theme-default/components/SideBar.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/SideBarLink.ts b/src/client/theme-default/components/SideBarLink.ts deleted file mode 100644 index 42ba5012..00000000 --- a/src/client/theme-default/components/SideBarLink.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { FunctionalComponent, h, VNode } from 'vue' -import { useRoute, useData } from 'vitepress' -import { Header } from '../../shared' -import { DefaultTheme } from '../config' -import { joinUrl, isActive } from '../utils' - -interface HeaderWithChildren extends Header { - children?: Header[] -} - -export const SideBarLink: FunctionalComponent<{ - item: DefaultTheme.SideBarItem - depth?: number -}> = (props) => { - const route = useRoute() - const { site, frontmatter } = useData() - const depth = props.depth || 1 - const maxDepth = frontmatter.value.sidebarDepth || Infinity - - const headers = route.data.headers - const text = props.item.text - const link = resolveLink(site.value.base, props.item.link) - const children = (props.item as DefaultTheme.SideBarGroup).children - const active = isActive(route, props.item.link) - const childItems = - depth < maxDepth - ? createChildren(active, children, headers, depth + 1) - : null - - return h('li', { class: 'sidebar-link' }, [ - h( - link ? 'a' : 'p', - { - class: { 'sidebar-link-item': true, active }, - href: link - }, - text - ), - childItems - ]) -} - -function resolveLink(base: string, path?: string): string | undefined { - if (path === undefined) { - return path - } - - // keep relative hash to the same page - if (path.startsWith('#')) { - return path - } - - return joinUrl(base, path) -} - -function createChildren( - active: boolean, - children?: DefaultTheme.SideBarItem[], - headers?: Header[], - depth = 1 -): VNode | null { - if (children && children.length > 0) { - return h( - 'ul', - { class: 'sidebar-links' }, - children.map((c) => { - return h(SideBarLink, { item: c, depth }) - }) - ) - } - - return active && headers - ? createChildren(false, resolveHeaders(headers), undefined, depth) - : null -} - -function resolveHeaders(headers: Header[]): DefaultTheme.SideBarItem[] { - return mapHeaders(groupHeaders(headers)) -} - -function groupHeaders(headers: Header[]): HeaderWithChildren[] { - headers = headers.map((h) => Object.assign({}, h)) - let lastH2: HeaderWithChildren - headers.forEach((h) => { - if (h.level === 2) { - lastH2 = h - } else if (lastH2) { - ;(lastH2.children || (lastH2.children = [])).push(h) - } - }) - return headers.filter((h) => h.level === 2) -} - -function mapHeaders(headers: HeaderWithChildren[]): DefaultTheme.SideBarItem[] { - return headers.map((header) => ({ - text: header.title, - link: `#${header.slug}`, - children: header.children ? mapHeaders(header.children) : undefined - })) -} diff --git a/src/client/theme-default/components/SideBarLinks.vue b/src/client/theme-default/components/SideBarLinks.vue deleted file mode 100644 index 768013e3..00000000 --- a/src/client/theme-default/components/SideBarLinks.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/src/client/theme-default/components/ToggleSideBarButton.vue b/src/client/theme-default/components/ToggleSideBarButton.vue deleted file mode 100644 index 07448cc0..00000000 --- a/src/client/theme-default/components/ToggleSideBarButton.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - - diff --git a/src/client/theme-default/components/VPAlgoliaSearchBox.vue b/src/client/theme-default/components/VPAlgoliaSearchBox.vue new file mode 100644 index 00000000..c4eede49 --- /dev/null +++ b/src/client/theme-default/components/VPAlgoliaSearchBox.vue @@ -0,0 +1,127 @@ + + + diff --git a/src/client/theme-default/components/VPBackdrop.vue b/src/client/theme-default/components/VPBackdrop.vue new file mode 100644 index 00000000..1aa5d0b1 --- /dev/null +++ b/src/client/theme-default/components/VPBackdrop.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/client/theme-default/components/VPBox.vue b/src/client/theme-default/components/VPBox.vue new file mode 100644 index 00000000..2593492a --- /dev/null +++ b/src/client/theme-default/components/VPBox.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/client/theme-default/components/VPButton.vue b/src/client/theme-default/components/VPButton.vue new file mode 100644 index 00000000..d6c5d246 --- /dev/null +++ b/src/client/theme-default/components/VPButton.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/client/theme-default/components/VPCarbonAds.vue b/src/client/theme-default/components/VPCarbonAds.vue new file mode 100644 index 00000000..aa4d0c83 --- /dev/null +++ b/src/client/theme-default/components/VPCarbonAds.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/client/theme-default/components/VPContent.vue b/src/client/theme-default/components/VPContent.vue new file mode 100644 index 00000000..b93a0df5 --- /dev/null +++ b/src/client/theme-default/components/VPContent.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/client/theme-default/components/VPDoc.vue b/src/client/theme-default/components/VPDoc.vue new file mode 100644 index 00000000..d817351e --- /dev/null +++ b/src/client/theme-default/components/VPDoc.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/src/client/theme-default/components/VPDocAside.vue b/src/client/theme-default/components/VPDocAside.vue new file mode 100644 index 00000000..4232d156 --- /dev/null +++ b/src/client/theme-default/components/VPDocAside.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/client/theme-default/components/VPDocAsideCarbonAds.vue b/src/client/theme-default/components/VPDocAsideCarbonAds.vue new file mode 100644 index 00000000..061a94d8 --- /dev/null +++ b/src/client/theme-default/components/VPDocAsideCarbonAds.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/client/theme-default/components/VPDocAsideOutline.vue b/src/client/theme-default/components/VPDocAsideOutline.vue new file mode 100644 index 00000000..26b556ad --- /dev/null +++ b/src/client/theme-default/components/VPDocAsideOutline.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/client/theme-default/components/VPDocAsideSponsors.vue b/src/client/theme-default/components/VPDocAsideSponsors.vue new file mode 100644 index 00000000..a42f7da5 --- /dev/null +++ b/src/client/theme-default/components/VPDocAsideSponsors.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/client/theme-default/components/VPDocFooter.vue b/src/client/theme-default/components/VPDocFooter.vue new file mode 100644 index 00000000..cb6deeed --- /dev/null +++ b/src/client/theme-default/components/VPDocFooter.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/client/theme-default/components/VPDocFooterLastUpdated.vue b/src/client/theme-default/components/VPDocFooterLastUpdated.vue new file mode 100644 index 00000000..efaac851 --- /dev/null +++ b/src/client/theme-default/components/VPDocFooterLastUpdated.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/client/theme-default/components/VPFlyout.vue b/src/client/theme-default/components/VPFlyout.vue new file mode 100644 index 00000000..ed31ee73 --- /dev/null +++ b/src/client/theme-default/components/VPFlyout.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/client/theme-default/components/VPFooter.vue b/src/client/theme-default/components/VPFooter.vue new file mode 100644 index 00000000..b8f6537c --- /dev/null +++ b/src/client/theme-default/components/VPFooter.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/client/theme-default/components/VPHero.vue b/src/client/theme-default/components/VPHero.vue new file mode 100644 index 00000000..51987dc5 --- /dev/null +++ b/src/client/theme-default/components/VPHero.vue @@ -0,0 +1,310 @@ + + + + + diff --git a/src/client/theme-default/components/VPHome.vue b/src/client/theme-default/components/VPHome.vue new file mode 100644 index 00000000..12e85c39 --- /dev/null +++ b/src/client/theme-default/components/VPHome.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/client/theme-default/components/VPHomeFeatures.vue b/src/client/theme-default/components/VPHomeFeatures.vue new file mode 100644 index 00000000..86fc2a50 --- /dev/null +++ b/src/client/theme-default/components/VPHomeFeatures.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/client/theme-default/components/VPHomeHero.vue b/src/client/theme-default/components/VPHomeHero.vue new file mode 100644 index 00000000..a12be5a6 --- /dev/null +++ b/src/client/theme-default/components/VPHomeHero.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/client/theme-default/components/VPHomeSponsors.vue b/src/client/theme-default/components/VPHomeSponsors.vue new file mode 100644 index 00000000..102912d5 --- /dev/null +++ b/src/client/theme-default/components/VPHomeSponsors.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/client/theme-default/components/VPLink.vue b/src/client/theme-default/components/VPLink.vue new file mode 100644 index 00000000..bf29e4b5 --- /dev/null +++ b/src/client/theme-default/components/VPLink.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/src/client/theme-default/components/VPLocalNav.vue b/src/client/theme-default/components/VPLocalNav.vue new file mode 100644 index 00000000..cd6dbe4e --- /dev/null +++ b/src/client/theme-default/components/VPLocalNav.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/client/theme-default/components/VPMenu.vue b/src/client/theme-default/components/VPMenu.vue new file mode 100644 index 00000000..7dad2062 --- /dev/null +++ b/src/client/theme-default/components/VPMenu.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/client/theme-default/components/VPMenuGroup.vue b/src/client/theme-default/components/VPMenuGroup.vue new file mode 100644 index 00000000..167874be --- /dev/null +++ b/src/client/theme-default/components/VPMenuGroup.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/client/theme-default/components/VPMenuLink.vue b/src/client/theme-default/components/VPMenuLink.vue new file mode 100644 index 00000000..8fb00a69 --- /dev/null +++ b/src/client/theme-default/components/VPMenuLink.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/client/theme-default/components/VPNav.vue b/src/client/theme-default/components/VPNav.vue new file mode 100644 index 00000000..9897e1a6 --- /dev/null +++ b/src/client/theme-default/components/VPNav.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBar.vue b/src/client/theme-default/components/VPNavBar.vue new file mode 100644 index 00000000..4e27b1f6 --- /dev/null +++ b/src/client/theme-default/components/VPNavBar.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarAppearance.vue b/src/client/theme-default/components/VPNavBarAppearance.vue new file mode 100644 index 00000000..62c8d9fc --- /dev/null +++ b/src/client/theme-default/components/VPNavBarAppearance.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarExtra.vue b/src/client/theme-default/components/VPNavBarExtra.vue new file mode 100644 index 00000000..aceca5ce --- /dev/null +++ b/src/client/theme-default/components/VPNavBarExtra.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarHamburger.vue b/src/client/theme-default/components/VPNavBarHamburger.vue new file mode 100644 index 00000000..1e014177 --- /dev/null +++ b/src/client/theme-default/components/VPNavBarHamburger.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarMenu.vue b/src/client/theme-default/components/VPNavBarMenu.vue new file mode 100644 index 00000000..c8fb6df2 --- /dev/null +++ b/src/client/theme-default/components/VPNavBarMenu.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarMenuGroup.vue b/src/client/theme-default/components/VPNavBarMenuGroup.vue new file mode 100644 index 00000000..f31c6d2c --- /dev/null +++ b/src/client/theme-default/components/VPNavBarMenuGroup.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/client/theme-default/components/VPNavBarMenuLink.vue b/src/client/theme-default/components/VPNavBarMenuLink.vue new file mode 100644 index 00000000..556f641f --- /dev/null +++ b/src/client/theme-default/components/VPNavBarMenuLink.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarSearch.vue b/src/client/theme-default/components/VPNavBarSearch.vue new file mode 100644 index 00000000..1d2973a5 --- /dev/null +++ b/src/client/theme-default/components/VPNavBarSearch.vue @@ -0,0 +1,270 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarSocialLinks.vue b/src/client/theme-default/components/VPNavBarSocialLinks.vue new file mode 100644 index 00000000..e5dfd630 --- /dev/null +++ b/src/client/theme-default/components/VPNavBarSocialLinks.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarTitle.vue b/src/client/theme-default/components/VPNavBarTitle.vue new file mode 100644 index 00000000..c09c06dc --- /dev/null +++ b/src/client/theme-default/components/VPNavBarTitle.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavBarTranslations.vue b/src/client/theme-default/components/VPNavBarTranslations.vue new file mode 100644 index 00000000..fe91597b --- /dev/null +++ b/src/client/theme-default/components/VPNavBarTranslations.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavScreen.vue b/src/client/theme-default/components/VPNavScreen.vue new file mode 100644 index 00000000..7204ac8f --- /dev/null +++ b/src/client/theme-default/components/VPNavScreen.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavScreenAppearance.vue b/src/client/theme-default/components/VPNavScreenAppearance.vue new file mode 100644 index 00000000..3a804195 --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenAppearance.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavScreenMenu.vue b/src/client/theme-default/components/VPNavScreenMenu.vue new file mode 100644 index 00000000..d75ef61d --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenMenu.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/client/theme-default/components/VPNavScreenMenuGroup.vue b/src/client/theme-default/components/VPNavScreenMenuGroup.vue new file mode 100644 index 00000000..d66e02c9 --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenMenuGroup.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavScreenMenuGroupLink.vue b/src/client/theme-default/components/VPNavScreenMenuGroupLink.vue new file mode 100644 index 00000000..61f5bd83 --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenMenuGroupLink.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavScreenMenuGroupSection.vue b/src/client/theme-default/components/VPNavScreenMenuGroupSection.vue new file mode 100644 index 00000000..35fa4be3 --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenMenuGroupSection.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavScreenMenuLink.vue b/src/client/theme-default/components/VPNavScreenMenuLink.vue new file mode 100644 index 00000000..f7ae9800 --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenMenuLink.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/client/theme-default/components/VPNavScreenSocialLinks.vue b/src/client/theme-default/components/VPNavScreenSocialLinks.vue new file mode 100644 index 00000000..45f4d318 --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenSocialLinks.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/client/theme-default/components/VPNavScreenTranslations.vue b/src/client/theme-default/components/VPNavScreenTranslations.vue new file mode 100644 index 00000000..7305554e --- /dev/null +++ b/src/client/theme-default/components/VPNavScreenTranslations.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/src/client/theme-default/components/VPPage.vue b/src/client/theme-default/components/VPPage.vue new file mode 100644 index 00000000..015926b7 --- /dev/null +++ b/src/client/theme-default/components/VPPage.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/VPSidebar.vue b/src/client/theme-default/components/VPSidebar.vue new file mode 100644 index 00000000..4d486e80 --- /dev/null +++ b/src/client/theme-default/components/VPSidebar.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/src/client/theme-default/components/VPSidebarGroup.vue b/src/client/theme-default/components/VPSidebarGroup.vue new file mode 100644 index 00000000..19890585 --- /dev/null +++ b/src/client/theme-default/components/VPSidebarGroup.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/src/client/theme-default/components/VPSidebarLink.vue b/src/client/theme-default/components/VPSidebarLink.vue new file mode 100644 index 00000000..41937655 --- /dev/null +++ b/src/client/theme-default/components/VPSidebarLink.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/client/theme-default/components/VPSkipLink.vue b/src/client/theme-default/components/VPSkipLink.vue new file mode 100644 index 00000000..5324e8bd --- /dev/null +++ b/src/client/theme-default/components/VPSkipLink.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/client/theme-default/components/VPSocialLink.vue b/src/client/theme-default/components/VPSocialLink.vue new file mode 100644 index 00000000..5b86ccdd --- /dev/null +++ b/src/client/theme-default/components/VPSocialLink.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/client/theme-default/components/VPSocialLinks.vue b/src/client/theme-default/components/VPSocialLinks.vue new file mode 100644 index 00000000..27a9d849 --- /dev/null +++ b/src/client/theme-default/components/VPSocialLinks.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/client/theme-default/components/VPSponsors.vue b/src/client/theme-default/components/VPSponsors.vue new file mode 100644 index 00000000..299723cf --- /dev/null +++ b/src/client/theme-default/components/VPSponsors.vue @@ -0,0 +1,52 @@ + + + diff --git a/src/client/theme-default/components/VPSponsorsGrid.vue b/src/client/theme-default/components/VPSponsorsGrid.vue new file mode 100644 index 00000000..e67ab6e8 --- /dev/null +++ b/src/client/theme-default/components/VPSponsorsGrid.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/client/theme-default/components/VPSwitch.vue b/src/client/theme-default/components/VPSwitch.vue new file mode 100644 index 00000000..94c47b70 --- /dev/null +++ b/src/client/theme-default/components/VPSwitch.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/client/theme-default/components/VPSwitchAppearance.vue b/src/client/theme-default/components/VPSwitchAppearance.vue new file mode 100644 index 00000000..2d83ed64 --- /dev/null +++ b/src/client/theme-default/components/VPSwitchAppearance.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/client/theme-default/components/icons/OutboundLink.vue b/src/client/theme-default/components/icons/OutboundLink.vue deleted file mode 100644 index 4d74eeeb..00000000 --- a/src/client/theme-default/components/icons/OutboundLink.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/src/client/theme-default/components/icons/VPIconAlignJustify.vue b/src/client/theme-default/components/icons/VPIconAlignJustify.vue new file mode 100644 index 00000000..653dab13 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconAlignJustify.vue @@ -0,0 +1,8 @@ + diff --git a/src/client/theme-default/components/icons/VPIconAlignLeft.vue b/src/client/theme-default/components/icons/VPIconAlignLeft.vue new file mode 100644 index 00000000..8dc84eae --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconAlignLeft.vue @@ -0,0 +1,8 @@ + diff --git a/src/client/theme-default/components/icons/VPIconAlignRight.vue b/src/client/theme-default/components/icons/VPIconAlignRight.vue new file mode 100644 index 00000000..16cbb3c7 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconAlignRight.vue @@ -0,0 +1,8 @@ + diff --git a/src/client/theme-default/components/icons/ArrowLeft.vue b/src/client/theme-default/components/icons/VPIconArrowLeft.vue similarity index 100% rename from src/client/theme-default/components/icons/ArrowLeft.vue rename to src/client/theme-default/components/icons/VPIconArrowLeft.vue diff --git a/src/client/theme-default/components/icons/ArrowRight.vue b/src/client/theme-default/components/icons/VPIconArrowRight.vue similarity index 100% rename from src/client/theme-default/components/icons/ArrowRight.vue rename to src/client/theme-default/components/icons/VPIconArrowRight.vue diff --git a/src/client/theme-default/components/icons/VPIconChevronDown.vue b/src/client/theme-default/components/icons/VPIconChevronDown.vue new file mode 100644 index 00000000..d72f4ee8 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconChevronDown.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconChevronLeft.vue b/src/client/theme-default/components/icons/VPIconChevronLeft.vue new file mode 100644 index 00000000..013eb7fd --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconChevronLeft.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconChevronRight.vue b/src/client/theme-default/components/icons/VPIconChevronRight.vue new file mode 100644 index 00000000..cf72418f --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconChevronRight.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconChevronUp.vue b/src/client/theme-default/components/icons/VPIconChevronUp.vue new file mode 100644 index 00000000..0b6a873e --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconChevronUp.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconDiscord.vue b/src/client/theme-default/components/icons/VPIconDiscord.vue new file mode 100644 index 00000000..59174574 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconDiscord.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconEdit.vue b/src/client/theme-default/components/icons/VPIconEdit.vue new file mode 100644 index 00000000..f30a62dd --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconEdit.vue @@ -0,0 +1,6 @@ + diff --git a/src/client/theme-default/components/icons/VPIconExternalLink.vue b/src/client/theme-default/components/icons/VPIconExternalLink.vue new file mode 100644 index 00000000..72459909 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconExternalLink.vue @@ -0,0 +1,13 @@ + diff --git a/src/client/theme-default/components/icons/VPIconFacebook.vue b/src/client/theme-default/components/icons/VPIconFacebook.vue new file mode 100644 index 00000000..6d9b6740 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconFacebook.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconGitHub.vue b/src/client/theme-default/components/icons/VPIconGitHub.vue new file mode 100644 index 00000000..ef59cdab --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconGitHub.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconHeart.vue b/src/client/theme-default/components/icons/VPIconHeart.vue new file mode 100644 index 00000000..d408828f --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconHeart.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconInstagram.vue b/src/client/theme-default/components/icons/VPIconInstagram.vue new file mode 100644 index 00000000..2b77d52c --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconInstagram.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconLanguages.vue b/src/client/theme-default/components/icons/VPIconLanguages.vue new file mode 100644 index 00000000..71a728fc --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconLanguages.vue @@ -0,0 +1,9 @@ + diff --git a/src/client/theme-default/components/icons/VPIconLinkedIn.vue b/src/client/theme-default/components/icons/VPIconLinkedIn.vue new file mode 100644 index 00000000..75b321fd --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconLinkedIn.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconMinus.vue b/src/client/theme-default/components/icons/VPIconMinus.vue new file mode 100644 index 00000000..e0229195 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconMinus.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconMinusSquare.vue b/src/client/theme-default/components/icons/VPIconMinusSquare.vue new file mode 100644 index 00000000..266ae3da --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconMinusSquare.vue @@ -0,0 +1,6 @@ + diff --git a/src/client/theme-default/components/icons/VPIconMoon.vue b/src/client/theme-default/components/icons/VPIconMoon.vue new file mode 100644 index 00000000..a9b205c6 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconMoon.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconMoreHorizontal.vue b/src/client/theme-default/components/icons/VPIconMoreHorizontal.vue new file mode 100644 index 00000000..6fa7fca2 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconMoreHorizontal.vue @@ -0,0 +1,7 @@ + diff --git a/src/client/theme-default/components/icons/VPIconPlus.vue b/src/client/theme-default/components/icons/VPIconPlus.vue new file mode 100644 index 00000000..74d9f695 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconPlus.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconPlusSquare.vue b/src/client/theme-default/components/icons/VPIconPlusSquare.vue new file mode 100644 index 00000000..88e5b5cf --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconPlusSquare.vue @@ -0,0 +1,6 @@ + diff --git a/src/client/theme-default/components/icons/VPIconSlack.vue b/src/client/theme-default/components/icons/VPIconSlack.vue new file mode 100644 index 00000000..9a1896ce --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconSlack.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconSun.vue b/src/client/theme-default/components/icons/VPIconSun.vue new file mode 100644 index 00000000..8ecb25ba --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconSun.vue @@ -0,0 +1,13 @@ + diff --git a/src/client/theme-default/components/icons/VPIconTwitter.vue b/src/client/theme-default/components/icons/VPIconTwitter.vue new file mode 100644 index 00000000..53250404 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconTwitter.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/components/icons/VPIconYouTube.vue b/src/client/theme-default/components/icons/VPIconYouTube.vue new file mode 100644 index 00000000..f5bc35a0 --- /dev/null +++ b/src/client/theme-default/components/icons/VPIconYouTube.vue @@ -0,0 +1,5 @@ + diff --git a/src/client/theme-default/composables/activeSidebarLink.ts b/src/client/theme-default/composables/activeSidebarLink.ts deleted file mode 100644 index 1b3b1d01..00000000 --- a/src/client/theme-default/composables/activeSidebarLink.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { onMounted, onUnmounted, onUpdated } from 'vue' - -export function useActiveSidebarLinks() { - let rootActiveLink: HTMLAnchorElement | null = null - let activeLink: HTMLAnchorElement | null = null - - const onScroll = throttleAndDebounce(setActiveLink, 300) - - function setActiveLink(): void { - const sidebarLinks = getSidebarLinks() - const anchors = getAnchors(sidebarLinks) - - for (let i = 0; i < anchors.length; i++) { - const anchor = anchors[i] - const nextAnchor = anchors[i + 1] - - const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor) - - if (isActive) { - history.replaceState(null, document.title, hash ? hash : ' ') - activateLink(hash) - return - } - } - } - - function activateLink(hash: string | null): void { - deactiveLink(activeLink) - deactiveLink(rootActiveLink) - - activeLink = document.querySelector(`.sidebar a[href="${hash}"]`) - - if (!activeLink) { - return - } - - activeLink.classList.add('active') - - // also add active class to parent h2 anchors - const rootLi = activeLink.closest('.sidebar-links > ul > li') - - if (rootLi && rootLi !== activeLink.parentElement) { - rootActiveLink = rootLi.querySelector('a') - rootActiveLink && rootActiveLink.classList.add('active') - } else { - rootActiveLink = null - } - } - - function deactiveLink(link: HTMLAnchorElement | null): void { - link && link.classList.remove('active') - } - - onMounted(() => { - setActiveLink() - window.addEventListener('scroll', onScroll) - }) - - onUpdated(() => { - // sidebar update means a route change - activateLink(decodeURIComponent(location.hash)) - }) - - onUnmounted(() => { - window.removeEventListener('scroll', onScroll) - }) -} - -function getSidebarLinks(): HTMLAnchorElement[] { - return [].slice.call( - document.querySelectorAll('.sidebar a.sidebar-link-item') - ) -} - -function getAnchors(sidebarLinks: HTMLAnchorElement[]): HTMLAnchorElement[] { - return [].slice - .call(document.querySelectorAll('.header-anchor')) - .filter((anchor: HTMLAnchorElement) => - sidebarLinks.some((sidebarLink) => sidebarLink.hash === anchor.hash) - ) as HTMLAnchorElement[] -} - -function getPageOffset(): number { - return (document.querySelector('.nav-bar') as HTMLElement).offsetHeight -} - -function getAnchorTop(anchor: HTMLAnchorElement): number { - const pageOffset = getPageOffset() - - return anchor.parentElement!.offsetTop - pageOffset - 15 -} - -function isAnchorActive( - index: number, - anchor: HTMLAnchorElement, - nextAnchor: HTMLAnchorElement -): [boolean, string | null] { - const scrollTop = window.scrollY - - if (index === 0 && scrollTop === 0) { - return [true, null] - } - - if (scrollTop < getAnchorTop(anchor)) { - return [false, null] - } - - if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) { - return [true, decodeURIComponent(anchor.hash)] - } - - return [false, null] -} - -function throttleAndDebounce(fn: () => void, delay: number): () => void { - let timeout: number - let called = false - - return () => { - if (timeout) { - clearTimeout(timeout) - } - - if (!called) { - fn() - called = true - setTimeout(() => { - called = false - }, delay) - } else { - timeout = setTimeout(fn, delay) - } - } -} diff --git a/src/client/theme-default/composables/aside.ts b/src/client/theme-default/composables/aside.ts new file mode 100644 index 00000000..afdaaba9 --- /dev/null +++ b/src/client/theme-default/composables/aside.ts @@ -0,0 +1,21 @@ +import { computed } from 'vue' +import { useMediaQuery } from '@vueuse/core' +import { useSidebar } from './sidebar' + +export function useAside() { + const { hasSidebar } = useSidebar() + const is960 = useMediaQuery('(min-width: 960px)') + const is1280 = useMediaQuery('(min-width: 1280px)') + + const isAsideEnabled = computed(() => { + if (!is1280.value && !is960.value) { + return false + } + + return hasSidebar.value ? is1280.value : is960.value + }) + + return { + isAsideEnabled + } +} diff --git a/src/client/theme-default/composables/copy-code.ts b/src/client/theme-default/composables/copy-code.ts new file mode 100644 index 00000000..bd5d73ab --- /dev/null +++ b/src/client/theme-default/composables/copy-code.ts @@ -0,0 +1,48 @@ +import { nextTick, watch } from 'vue' +import { inBrowser, useData } from 'vitepress' + +export function useCopyCode() { + const { page } = useData() + + if (inBrowser) + watch( + () => page.value.relativePath, + () => { + nextTick(() => { + document + .querySelectorAll( + '.vp-doc div[class*="language-"]>span.copy' + ) + .forEach(handleElement) + }) + }, + { immediate: true, flush: 'post' } + ) +} + +function handleElement(el: HTMLElement) { + el.onclick = () => { + const parent = el.parentElement + + if (!parent) { + return + } + + const isShell = + parent.classList.contains('language-sh') || + parent.classList.contains('language-bash') + + let { innerText: text = '' } = parent + + if (isShell) { + text = text.replace(/^ *\$ /gm, '') + } + + navigator.clipboard.writeText(text).then(() => { + el.classList.add('copied') + setTimeout(() => { + el.classList.remove('copied') + }, 3000) + }) + } +} diff --git a/src/client/theme-default/composables/edit-link.ts b/src/client/theme-default/composables/edit-link.ts new file mode 100644 index 00000000..32bf66c2 --- /dev/null +++ b/src/client/theme-default/composables/edit-link.ts @@ -0,0 +1,26 @@ +import { computed } from 'vue' +import { useData } from 'vitepress' + +export function useEditLink() { + const { theme, page } = useData() + + return computed(() => { + const url = [ + 'https://github.com', + theme.value.editLink?.repo || '???', + 'edit', + theme.value.editLink?.branch || 'main', + theme.value.editLink?.dir || null, + page.value.relativePath + ] + .filter((v) => v) + .join('/') + + const text = theme.value.editLink?.text ?? 'Edit this page' + + return { + url, + text + } + }) +} diff --git a/src/client/theme-default/composables/editLink.ts b/src/client/theme-default/composables/editLink.ts deleted file mode 100644 index f6cfdcc3..00000000 --- a/src/client/theme-default/composables/editLink.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { computed } from 'vue' -import { useData } from 'vitepress' -import { endingSlashRE, isExternal } from '../utils' - -const bitbucketRE = /bitbucket.org/ - -export function useEditLink() { - const { page, theme, frontmatter } = useData() - - const url = computed(() => { - const { - repo, - docsDir = '', - docsBranch = 'master', - docsRepo = repo, - editLinks - } = theme.value - - const showEditLink = - frontmatter.value.editLink != null - ? frontmatter.value.editLink - : editLinks - const { relativePath } = page.value - - if (!showEditLink || !relativePath || !repo) { - return null - } - - return createUrl(repo, docsRepo, docsDir, docsBranch, relativePath) - }) - - const text = computed(() => { - return theme.value.editLinkText || 'Edit this page' - }) - - return { - url, - text - } -} - -function createUrl( - repo: string, - docsRepo: string, - docsDir: string, - docsBranch: string, - path: string -): string { - return bitbucketRE.test(repo) - ? createBitbucketUrl(repo, docsRepo, docsDir, docsBranch, path) - : createGitHubUrl(repo, docsRepo, docsDir, docsBranch, path) -} - -function createGitHubUrl( - repo: string, - docsRepo: string, - docsDir: string, - docsBranch: string, - path: string -): string { - const base = isExternal(docsRepo) - ? docsRepo - : `https://github.com/${docsRepo}` - - return ( - base.replace(endingSlashRE, '') + - `/edit` + - `/${docsBranch}/` + - (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') + - path - ) -} - -function createBitbucketUrl( - repo: string, - docsRepo: string, - docsDir: string, - docsBranch: string, - path: string -): string { - const base = isExternal(docsRepo) ? docsRepo : repo - - return ( - base.replace(endingSlashRE, '') + - `/src` + - `/${docsBranch}/` + - (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '') + - path + - `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default` - ) -} diff --git a/src/client/theme-default/composables/flyout.ts b/src/client/theme-default/composables/flyout.ts new file mode 100644 index 00000000..f71c1d54 --- /dev/null +++ b/src/client/theme-default/composables/flyout.ts @@ -0,0 +1,58 @@ +import { Ref, ref, watch, readonly, onUnmounted } from 'vue' + +interface UseFlyoutOptions { + el: Ref + onFocus?(): void + onBlur?(): void +} + +export const focusedElement = ref() + +let active = false +let listeners = 0 + +export function useFlyout(options: UseFlyoutOptions) { + const focus = ref(false) + + if (typeof window !== 'undefined') { + !active && activateFocusTracking() + + listeners++ + + const unwatch = watch(focusedElement, (el) => { + if (el === options.el.value || options.el.value?.contains(el as Node)) { + focus.value = true + options.onFocus?.() + } else { + focus.value = false + options.onBlur?.() + } + }) + + onUnmounted(() => { + unwatch() + + listeners-- + + if (!listeners) { + deactivateFocusTracking() + } + }) + } + + return readonly(focus) +} + +function activateFocusTracking() { + document.addEventListener('focusin', handleFocusIn) + active = true + focusedElement.value = document.activeElement as HTMLElement +} + +function deactivateFocusTracking() { + document.removeEventListener('focusin', handleFocusIn) +} + +function handleFocusIn() { + focusedElement.value = document.activeElement as HTMLElement +} diff --git a/src/client/theme-default/composables/nav.ts b/src/client/theme-default/composables/nav.ts index 7c935721..d719dbb3 100644 --- a/src/client/theme-default/composables/nav.ts +++ b/src/client/theme-default/composables/nav.ts @@ -1,7 +1,39 @@ -import { computed } from 'vue' +import { ref, computed } from 'vue' import { useData, useRoute } from 'vitepress' import type { DefaultTheme } from '../config' +export function useNav() { + const isScreenOpen = ref(false) + + function openScreen() { + isScreenOpen.value = true + window.addEventListener('resize', closeScreenOnTabletWindow) + } + + function closeScreen() { + isScreenOpen.value = false + window.removeEventListener('resize', closeScreenOnTabletWindow) + } + + function toggleScreen() { + isScreenOpen.value ? closeScreen() : openScreen() + } + + /** + * Close screen when the user resizes the window wider than tablet size. + */ + function closeScreenOnTabletWindow() { + window.outerWidth >= 768 && closeScreen() + } + + return { + isScreenOpen, + openScreen, + closeScreen, + toggleScreen + } +} + export function useLanguageLinks() { const { site, localePath, theme } = useData() diff --git a/src/client/theme-default/composables/navLink.ts b/src/client/theme-default/composables/navLink.ts deleted file mode 100644 index bc7a4d44..00000000 --- a/src/client/theme-default/composables/navLink.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { computed, Ref } from 'vue' -import { useRoute, withBase } from 'vitepress' -import { isExternal as isExternalCheck } from '../utils' -import type { DefaultTheme } from '../config' - -export function useNavLink(item: Ref) { - const route = useRoute() - - const isExternal = isExternalCheck(item.value.link) - - const props = computed(() => { - const routePath = normalizePath(`/${route.data.relativePath}`) - - let active = false - if (item.value.activeMatch) { - active = new RegExp(item.value.activeMatch).test(routePath) - } else { - const itemPath = normalizePath(item.value.link) - active = - itemPath === '/' - ? itemPath === routePath - : routePath.startsWith(itemPath) - } - - return { - class: { - active, - isExternal - }, - href: isExternal ? item.value.link : withBase(item.value.link), - target: item.value.target || (isExternal ? `_blank` : null), - rel: item.value.rel || (isExternal ? `noopener noreferrer` : null), - 'aria-label': item.value.ariaLabel - } - }) - - return { - props, - isExternal - } -} - -function normalizePath(path: string): string { - return path - .replace(/#.*$/, '') - .replace(/\?.*$/, '') - .replace(/\.(html|md)$/, '') - .replace(/\/index$/, '/') -} diff --git a/src/client/theme-default/composables/nextAndPrevLinks.ts b/src/client/theme-default/composables/nextAndPrevLinks.ts deleted file mode 100644 index d7006ea6..00000000 --- a/src/client/theme-default/composables/nextAndPrevLinks.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { computed } from 'vue' -import { useData } from 'vitepress' -import { isArray, ensureStartingSlash, removeExtention } from '../utils' -import { getSideBarConfig, getFlatSideBarLinks } from '../support/sideBar' - -export function useNextAndPrevLinks() { - const { page, theme } = useData() - - const path = computed(() => { - return removeExtention(ensureStartingSlash(page.value.relativePath)) - }) - - const candidates = computed(() => { - const config = getSideBarConfig(theme.value.sidebar, path.value) - - return isArray(config) ? getFlatSideBarLinks(config) : [] - }) - - const index = computed(() => { - return candidates.value.findIndex((item) => { - return item.link === path.value - }) - }) - - const next = computed(() => { - if ( - theme.value.nextLinks !== false && - index.value > -1 && - index.value < candidates.value.length - 1 - ) { - return candidates.value[index.value + 1] - } - }) - - const prev = computed(() => { - if (theme.value.prevLinks !== false && index.value > 0) { - return candidates.value[index.value - 1] - } - }) - - const hasLinks = computed(() => !!next.value || !!prev.value) - - return { - next, - prev, - hasLinks - } -} diff --git a/src/client/theme-default/composables/outline.ts b/src/client/theme-default/composables/outline.ts new file mode 100644 index 00000000..698aeda6 --- /dev/null +++ b/src/client/theme-default/composables/outline.ts @@ -0,0 +1,178 @@ +import { Ref, computed, onMounted, onUpdated, onUnmounted } from 'vue' +import { Header, useData } from 'vitepress' +import { useAside } from '../composables/aside' +import { throttleAndDebounce } from '../support/utils' + +interface HeaderWithChildren extends Header { + children?: Header[] + hidden?: boolean +} + +interface MenuItemWithLinkAndChildren { + text: string + link: string + children?: MenuItemWithLinkAndChildren[] + hidden?: boolean +} + +// magic number to avoid repeated retrieval +const PAGE_OFFSET = 56 + +export function useOutline() { + const { page } = useData() + + const hasOutline = computed(() => { + return page.value.headers.length > 0 + }) + + return { + hasOutline + } +} + +export function resolveHeaders(headers: Header[]) { + return mapHeaders(groupHeaders(headers)) +} + +function groupHeaders(headers: Header[]): HeaderWithChildren[] { + headers = headers.map((h) => Object.assign({}, h)) + + let lastH2: HeaderWithChildren | undefined + + for (const h of headers) { + if (h.level === 2) { + lastH2 = h + } else if (lastH2 && h.level <= 3) { + ;(lastH2.children || (lastH2.children = [])).push(h) + } + } + + return headers.filter((h) => h.level === 2) +} + +function mapHeaders( + headers: HeaderWithChildren[] +): MenuItemWithLinkAndChildren[] { + return headers.map((header) => ({ + text: header.title, + link: `#${header.slug}`, + children: header.children ? mapHeaders(header.children) : undefined, + hidden: header.hidden + })) +} + +export function useActiveAnchor( + container: Ref, + marker: Ref +) { + const { isAsideEnabled } = useAside() + + const onScroll = throttleAndDebounce(setActiveLink, 100) + + let prevActiveLink: HTMLAnchorElement | null = null + + onMounted(() => { + requestAnimationFrame(setActiveLink) + window.addEventListener('scroll', onScroll) + }) + + onUpdated(() => { + // sidebar update means a route change + activateLink(location.hash) + }) + + onUnmounted(() => { + window.removeEventListener('scroll', onScroll) + }) + + function setActiveLink() { + if (!isAsideEnabled.value) { + return + } + + const links = [].slice.call( + container.value.querySelectorAll('.outline-link') + ) as HTMLAnchorElement[] + + const anchors = [].slice + .call(document.querySelectorAll('.content .header-anchor')) + .filter((anchor: HTMLAnchorElement) => { + return links.some((link) => { + return link.hash === anchor.hash && anchor.offsetParent !== null + }) + }) as HTMLAnchorElement[] + + const scrollY = window.scrollY + const innerHeight = window.innerHeight + const offsetHeight = document.body.offsetHeight + const isBottom = scrollY + innerHeight === offsetHeight + + // page bottom - highlight last one + if (anchors.length && isBottom) { + activateLink(anchors[anchors.length - 1].hash) + return + } + + for (let i = 0; i < anchors.length; i++) { + const anchor = anchors[i] + const nextAnchor = anchors[i + 1] + + const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor) + + if (isActive) { + history.replaceState(null, document.title, hash ? hash : ' ') + activateLink(hash) + return + } + } + } + + function activateLink(hash: string | null) { + if (prevActiveLink) { + prevActiveLink.classList.remove('active') + } + + if (hash !== null) { + prevActiveLink = container.value.querySelector( + `a[href="${decodeURIComponent(hash)}"]` + ) as HTMLAnchorElement + } + + const activeLink = prevActiveLink + + if (activeLink) { + activeLink.classList.add('active') + marker.value.style.top = activeLink.offsetTop + 33 + 'px' + marker.value.style.opacity = '1' + } else { + marker.value.style.top = '33px' + marker.value.style.opacity = '0' + } + } +} + +function getAnchorTop(anchor: HTMLAnchorElement): number { + return anchor.parentElement!.offsetTop - PAGE_OFFSET - 15 +} + +function isAnchorActive( + index: number, + anchor: HTMLAnchorElement, + nextAnchor: HTMLAnchorElement | undefined +): [boolean, string | null] { + const scrollTop = window.scrollY + + if (index === 0 && scrollTop === 0) { + return [true, null] + } + + if (scrollTop < getAnchorTop(anchor)) { + return [false, null] + } + + if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) { + return [true, anchor.hash] + } + + return [false, null] +} diff --git a/src/client/theme-default/composables/prev-next.ts b/src/client/theme-default/composables/prev-next.ts new file mode 100644 index 00000000..e09cb0e0 --- /dev/null +++ b/src/client/theme-default/composables/prev-next.ts @@ -0,0 +1,22 @@ +import { computed } from 'vue' +import { useData } from 'vitepress' +import { isActive } from '../support/utils' +import { getSidebar, getFlatSideBarLinks } from '../support/sidebar' + +export function usePrevNext() { + const { page, theme } = useData() + + return computed(() => { + const sidebar = getSidebar(theme.value.sidebar, page.value.relativePath) + const candidates = getFlatSideBarLinks(sidebar) + + const index = candidates.findIndex((link) => { + return isActive(page.value.relativePath, link.link) + }) + + return { + prev: candidates[index - 1], + next: candidates[index + 1] + } + }) +} diff --git a/src/client/theme-default/composables/repo.ts b/src/client/theme-default/composables/repo.ts deleted file mode 100644 index 5bb2112b..00000000 --- a/src/client/theme-default/composables/repo.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { computed } from 'vue' -import { useData } from 'vitepress' -import type { DefaultTheme } from '../config' -import { EXTERNAL_URL_RE } from '../../shared' - -export const platforms = ['GitHub', 'GitLab', 'Bitbucket'].map((platform) => { - return [platform, new RegExp(platform, 'i')] as const -}) - -export function useRepo() { - const { site } = useData() - - return computed(() => { - const theme = site.value.themeConfig as DefaultTheme.Config - const name = theme.docsRepo || theme.repo - - if (!name) { - return null - } - - const link = getRepoUrl(name) - const text = getRepoText(link, theme.repoLabel) - - return { text, link } - }) -} - -function getRepoUrl(repo: string): string { - // if the full url is not provided, default to GitHub repo - return EXTERNAL_URL_RE.test(repo) ? repo : `https://github.com/${repo}` -} - -function getRepoText(url: string, text?: string): string { - if (text) { - return text - } - - // if no label is provided, deduce it from the repo url - const hosts = url.match(/^https?:\/\/[^/]+/) - - if (!hosts) { - return 'Source' - } - - const platform = platforms.find(([_p, re]) => re.test(hosts[0])) - - if (platform && platform[0]) { - return platform[0] - } - - return 'Source' -} diff --git a/src/client/theme-default/composables/sideBar.ts b/src/client/theme-default/composables/sideBar.ts deleted file mode 100644 index 96fb6b24..00000000 --- a/src/client/theme-default/composables/sideBar.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { computed } from 'vue' -import { useRoute, useData } from 'vitepress' -import { Header } from '../../shared' -import { useActiveSidebarLinks } from '../composables/activeSidebarLink' -import { getSideBarConfig } from '../support/sideBar' -import { DefaultTheme } from '../config' - -export function useSideBar() { - const route = useRoute() - const { site } = useData() - - useActiveSidebarLinks() - - return computed(() => { - // at first, we'll check if we can find the sidebar setting in frontmatter. - const headers = route.data.headers - const frontSidebar = route.data.frontmatter.sidebar - const sidebarDepth = route.data.frontmatter.sidebarDepth - - // if it's `false`, we'll just return an empty array here. - if (frontSidebar === false) { - return [] - } - - // if it's `auto`, render headers of the current page - if (frontSidebar === 'auto') { - return resolveAutoSidebar(headers, sidebarDepth) - } - - // now, there's no sidebar setting at frontmatter; let's see the configs - const themeSidebar = getSideBarConfig( - site.value.themeConfig.sidebar, - route.data.relativePath - ) - - if (themeSidebar === false) { - return [] - } - - if (themeSidebar === 'auto') { - return resolveAutoSidebar(headers, sidebarDepth) - } - - return themeSidebar - }) -} - -function resolveAutoSidebar( - headers: Header[], - depth: number -): DefaultTheme.SideBarItem[] { - const ret: DefaultTheme.SideBarItem[] = [] - - if (headers === undefined) { - return [] - } - - let lastH2: DefaultTheme.SideBarItem | undefined = undefined - headers.forEach(({ level, title, slug }) => { - if (level - 1 > depth) { - return - } - - const item: DefaultTheme.SideBarItem = { - text: title, - link: `#${slug}` - } - if (level === 2) { - lastH2 = item - ret.push(item) - } else if (lastH2) { - ;((lastH2 as any).children || ((lastH2 as any).children = [])).push(item) - } - }) - - return ret -} diff --git a/src/client/theme-default/composables/sidebar.ts b/src/client/theme-default/composables/sidebar.ts new file mode 100644 index 00000000..dae2a6ab --- /dev/null +++ b/src/client/theme-default/composables/sidebar.ts @@ -0,0 +1,74 @@ +import { Ref, ref, computed, watchEffect, onMounted, onUnmounted } from 'vue' +import { useRoute, useData } from 'vitepress' +import { getSidebar } from '../support/sidebar' + +export function useSidebar() { + const route = useRoute() + const { theme, frontmatter } = useData() + + const isOpen = ref(false) + + const sidebar = computed(() => { + const sidebarConfig = theme.value.sidebar + const relativePath = route.data.relativePath + + return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [] + }) + + const hasSidebar = computed(() => { + return frontmatter.value.sidebar !== false && sidebar.value.length > 0 + }) + + function open() { + isOpen.value = true + } + + function close() { + isOpen.value = false + } + + function toggle() { + isOpen.value ? close() : open() + } + + return { + isOpen, + sidebar, + hasSidebar, + open, + close, + toggle + } +} + +/** + * a11y: cache the element that opened the Sidebar (the menu button) then + * focus that button again when Menu is closed with Escape key. + */ +export function useCloseSidebarOnEscape( + isOpen: Ref, + close: () => void +) { + let triggerElement: HTMLButtonElement | undefined + + watchEffect(() => { + triggerElement = isOpen.value + ? (document.activeElement as HTMLButtonElement) + : undefined + }) + + onMounted(() => { + window.addEventListener('keyup', onEscape) + }) + + onUnmounted(() => { + window.removeEventListener('keyup', onEscape) + }) + + function onEscape(e: KeyboardEvent) { + if (e.key === 'Escape' && isOpen.value) { + close() + triggerElement?.focus() + } + } +} diff --git a/src/client/theme-default/composables/sponsor-grid.ts b/src/client/theme-default/composables/sponsor-grid.ts new file mode 100644 index 00000000..5113a079 --- /dev/null +++ b/src/client/theme-default/composables/sponsor-grid.ts @@ -0,0 +1,135 @@ +import { Ref, onMounted, onUnmounted } from 'vue' +import { throttleAndDebounce } from '../support/utils' + +export interface GridSetting { + [size: string]: [number, number][] +} + +export type GridSize = 'xmini' | 'mini' | 'small' | 'medium' | 'big' + +export interface UseSponsorsGridOprions { + el: Ref + size?: GridSize +} + +/** + * Defines grid configuration for each sponsor size in touple. + * + * [Screen widh, Column size] + * + * It sets grid size on matching screen size. For example, `[768, 5]` will + * set 5 columns when screen size is bigger or equal to 768px. + * + * Column will set only when item size is bigger than the column size. For + * example, even we define 5 columns, if we only have 1 sponsor yet, we would + * like to show it in 1 column to make it stand out. + */ +const GridSettings: GridSetting = { + xmini: [[0, 2]], + mini: [], + small: [ + [920, 6], + [768, 5], + [640, 4], + [480, 3], + [0, 2] + ], + medium: [ + [960, 5], + [832, 4], + [640, 3], + [480, 2] + ], + big: [ + [832, 3], + [640, 2] + ] +} + +export function useSponsorsGrid({ + el, + size = 'medium' +}: UseSponsorsGridOprions) { + const onResize = throttleAndDebounce(manage, 100) + + onMounted(() => { + manage() + window.addEventListener('resize', onResize) + }) + + onUnmounted(() => { + window.removeEventListener('resize', onResize) + }) + + function manage() { + adjustSlots(el.value!, size) + } +} + +function adjustSlots(el: HTMLElement, size: GridSize) { + const tsize = el.children.length + const asize = el.querySelectorAll('.vp-sponsor-grid-item:not(.empty)').length + + const grid = setGrid(el, size, asize) + + manageSlots(el, grid, tsize, asize) +} + +function setGrid(el: HTMLElement, size: GridSize, items: number) { + const settings = GridSettings[size] + const screen = window.innerWidth + + let grid = 1 + + settings.some(([breakpoint, value]) => { + if (screen >= breakpoint) { + grid = items < value ? items : value + return true + } + }) + + setGridData(el, grid) + + return grid +} + +function setGridData(el: HTMLElement, value: number) { + el.dataset.vpGrid = String(value) +} + +function manageSlots( + el: HTMLElement, + grid: number, + tsize: number, + asize: number +) { + const diff = tsize - asize + const rem = asize % grid + const drem = rem === 0 ? rem : grid - rem + + neutralizeSlots(el, drem - diff) +} + +function neutralizeSlots(el: HTMLElement, count: number) { + if (count === 0) { + return + } + + count > 0 ? addSlots(el, count) : removeSlots(el, count * -1) +} + +function addSlots(el: HTMLElement, count: number) { + for (let i = 0; i < count; i++) { + const slot = document.createElement('div') + + slot.classList.add('vp-sponsor-grid-item', 'empty') + + el.append(slot) + } +} + +function removeSlots(el: HTMLElement, count: number) { + for (let i = 0; i < count; i++) { + el.removeChild(el.lastElementChild!) + } +} diff --git a/src/client/theme-default/fonts/inter-cyrillic-ext.woff2 b/src/client/theme-default/fonts/inter-cyrillic-ext.woff2 new file mode 100644 index 00000000..e5ccce41 Binary files /dev/null and b/src/client/theme-default/fonts/inter-cyrillic-ext.woff2 differ diff --git a/src/client/theme-default/fonts/inter-cyrillic.woff2 b/src/client/theme-default/fonts/inter-cyrillic.woff2 new file mode 100644 index 00000000..56063960 Binary files /dev/null and b/src/client/theme-default/fonts/inter-cyrillic.woff2 differ diff --git a/src/client/theme-default/fonts/inter-greek-ext.woff2 b/src/client/theme-default/fonts/inter-greek-ext.woff2 new file mode 100644 index 00000000..e07f5c96 Binary files /dev/null and b/src/client/theme-default/fonts/inter-greek-ext.woff2 differ diff --git a/src/client/theme-default/fonts/inter-greek.woff2 b/src/client/theme-default/fonts/inter-greek.woff2 new file mode 100644 index 00000000..94c69138 Binary files /dev/null and b/src/client/theme-default/fonts/inter-greek.woff2 differ diff --git a/src/client/theme-default/fonts/inter-latin-ext.woff2 b/src/client/theme-default/fonts/inter-latin-ext.woff2 new file mode 100644 index 00000000..0e5999b6 Binary files /dev/null and b/src/client/theme-default/fonts/inter-latin-ext.woff2 differ diff --git a/src/client/theme-default/fonts/inter-latin.woff2 b/src/client/theme-default/fonts/inter-latin.woff2 new file mode 100644 index 00000000..2fbab974 Binary files /dev/null and b/src/client/theme-default/fonts/inter-latin.woff2 differ diff --git a/src/client/theme-default/fonts/inter-vietnamese.woff2 b/src/client/theme-default/fonts/inter-vietnamese.woff2 new file mode 100644 index 00000000..f29f5de6 Binary files /dev/null and b/src/client/theme-default/fonts/inter-vietnamese.woff2 differ diff --git a/src/client/theme-default/index.ts b/src/client/theme-default/index.ts index bcd79051..ba3dc291 100644 --- a/src/client/theme-default/index.ts +++ b/src/client/theme-default/index.ts @@ -1,14 +1,23 @@ +import './styles/fonts.css' import './styles/vars.css' -import './styles/layout.css' -import './styles/code.css' -import './styles/custom-blocks.css' -import './styles/sidebar-links.css' +import './styles/base.css' +import './styles/utils.css' +import './styles/components/custom-block.css' +import './styles/components/vp-code.css' +import './styles/components/vp-doc.css' +import './styles/components/vp-sponsor.css' import { Theme } from 'vitepress' import Layout from './Layout.vue' import NotFound from './NotFound.vue' export { DefaultTheme } from './config' + +export { default as VPHomeHero } from './components/VPHomeHero.vue' +export { default as VPHomeFeatures } from './components/VPHomeFeatures.vue' +export { default as VPHomeSponsors } from './components/VPHomeSponsors.vue' +export { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue' + const theme: Theme = { Layout, NotFound diff --git a/src/client/theme-default/styles/base.css b/src/client/theme-default/styles/base.css new file mode 100644 index 00000000..9b6044ed --- /dev/null +++ b/src/client/theme-default/styles/base.css @@ -0,0 +1,215 @@ +*, +::before, +::after { + box-sizing: border-box; +} + +html { + line-height: 1.4; + font-size: 16px; + -webkit-text-size-adjust: 100%; +} + +html.dark { + color-scheme: dark; +} + +body { + margin: 0; + width: 100%; + min-width: 320px; + min-height: 100vh; + line-height: 24px; + font-family: var(--vp-font-family-base); + font-size: 16px; + font-weight: 400; + color: var(--vp-c-text-1); + background-color: var(--vp-c-bg); + direction: ltr; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +main { + display: block; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + line-height: 24px; + font-size: 16px; + font-weight: 400; +} + +p { + margin: 0; +} + +strong, +b { + font-weight: 600; +} + +/** + * Avoid 300ms click delay on touch devices that support the `touch-action` + * CSS property. + * + * In particular, unlike most other browsers, IE11+Edge on Windows 10 on + * touch devices and IE Mobile 10-11 DON'T remove the click delay when + * `` is present. + * However, they DO support removing the click delay via + * `touch-action: manipulation`. + * + * See: + * - http://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch + * - http://caniuse.com/#feat=css-touch-action + * - http://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay + */ +a, +area, +button, +[role="button"], +input, +label, +select, +summary, +textarea { + touch-action: manipulation; +} + +a { + color: inherit; + text-decoration: inherit; +} + +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +blockquote { + margin: 0; +} + +pre, +code, +kbd, +samp { + font-family: var(--vp-font-family-mono); +} + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + vertical-align: middle; +} + +figure { + margin: 0; +} + +img, +video { + max-width: 100%; + height: auto; +} + +button, +input, +optgroup, +select, +textarea { + border: 0; + padding: 0; + line-height: inherit; + color: inherit; +} + +button { + padding: 0; + font-family: inherit;; + background-color: transparent; + background-image: none; +} + +button, +[role="button"] { + cursor: pointer; +} + +button:focus, +button:focus-visible { + outline: 1px dotted; + outline: 4px auto -webkit-focus-ring-color; +} + +button:focus:not(:focus-visible) { + outline: none !important; +} + +input:focus, +textarea:focus, +select:focus { + outline: none; +} + +table { + border-collapse: collapse; +} + +input { + background-color: transparent; +} + +input:-ms-input-placeholder, +textarea:-ms-input-placeholder { + color: var(--vp-c-text-3); +} + +input::-ms-input-placeholder, +textarea::-ms-input-placeholder { + color: var(--vp-c-text-3); +} + +input::placeholder, +textarea::placeholder { + color: var(--vp-c-text-3); +} + +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"] { + -moz-appearance: textfield; +} + +textarea { + resize: vertical; +} + +select { + -webkit-appearance: none; +} + +fieldset { + margin: 0; + padding: 0; +} diff --git a/src/client/theme-default/styles/code.css b/src/client/theme-default/styles/code.css deleted file mode 100644 index 436e2baa..00000000 --- a/src/client/theme-default/styles/code.css +++ /dev/null @@ -1,299 +0,0 @@ -code { - margin: 0; - border-radius: 3px; - padding: 0.25rem 0.5rem; - font-family: var(--code-font-family); - font-size: 0.85em; - color: var(--c-text-light); - background-color: var(--code-inline-bg-color); -} - -code .token.deleted { - color: #ec5975; -} - -code .token.inserted { - color: var(--c-brand); -} - -div[class*='language-'] { - position: relative; - margin: 1rem -1.5rem; - background-color: var(--code-bg-color); - overflow-x: auto; -} - -li > div[class*='language-'] { - border-radius: 6px 0 0 6px; - margin: 1rem -1.5rem 1rem -1.25rem; - line-height: initial; -} - -@media (min-width: 420px) { - div[class*='language-'] { - margin: 1rem 0; - border-radius: 6px; - } - - li > div[class*='language-'] { - margin: 1rem 0 1rem 0rem; - border-radius: 6px; - } -} - -[class*='language-'] pre, -[class*='language-'] code { - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; - background: transparent; -} - -[class*='language-'] pre { - position: relative; - z-index: 1; - margin: 0; - padding: 1.25rem 1.5rem; - overflow-x: auto; -} - -[class*='language-'] code { - padding: 0; - line-height: var(--code-line-height); - font-size: var(--code-font-size); - color: #eee; -} - -/* Line highlighting */ - -.highlight-lines { - position: absolute; - top: 0; - bottom: 0; - left: 0; - padding: 1.25rem 0; - width: 100%; - line-height: var(--code-line-height); - font-family: var(--code-font-family); - font-size: var(--code-font-size); - user-select: none; - overflow: hidden; -} - -.highlight-lines .highlighted { - background-color: rgba(0, 0, 0, 0.66); -} - -/* Line numbers mode */ - -div[class*='language-'].line-numbers-mode { - padding-left: 3.5rem; -} - -.line-numbers-wrapper { - position: absolute; - top: 0; - bottom: 0; - left: 0; - z-index: 3; - border-right: 1px solid rgba(0, 0, 0, 0.5); - padding: 1.25rem 0; - width: 3.5rem; - text-align: center; - line-height: var(--code-line-height); - font-family: var(--code-font-family); - font-size: var(--code-font-size); - color: #888; -} - -/* Language marker */ - -div[class*='language-']:before { - position: absolute; - top: 0.6em; - right: 1em; - z-index: 2; - font-size: 0.8rem; - color: #888; -} - -div[class~='language-html']:before, -div[class~='language-markup']:before { - content: 'html'; -} - -div[class~='language-md']:before, -div[class~='language-markdown']:before { - content: 'md'; -} - -div[class~='language-css']:before { - content: 'css'; -} - -div[class~='language-sass']:before { - content: 'sass'; -} - -div[class~='language-scss']:before { - content: 'scss'; -} - -div[class~='language-less']:before { - content: 'less'; -} - -div[class~='language-stylus']:before { - content: 'styl'; -} - -div[class~='language-js']:before, -div[class~='language-javascript']:before { - content: 'js'; -} - -div[class~='language-ts']:before, -div[class~='language-typescript']:before { - content: 'ts'; -} - -div[class~='language-json']:before { - content: 'json'; -} - -div[class~='language-rb']:before, -div[class~='language-ruby']:before { - content: 'rb'; -} - -div[class~='language-py']:before, -div[class~='language-python']:before { - content: 'py'; -} - -div[class~='language-sh']:before, -div[class~='language-bash']:before { - content: 'sh'; -} - -div[class~='language-php']:before { - content: 'php'; -} - -div[class~='language-go']:before { - content: 'go'; -} - -div[class~='language-rust']:before { - content: 'rust'; -} - -div[class~='language-java']:before { - content: 'java'; -} - -div[class~='language-c']:before { - content: 'c'; -} - -div[class~='language-yaml']:before { - content: 'yaml'; -} - -div[class~='language-dockerfile']:before { - content: 'dockerfile'; -} - -div[class~='language-vue']:before { - content: 'vue'; -} - -/** - * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML. - * Based on https://github.com/chriskempson/tomorrow-theme - * - * @author Rose Pritchard - */ -.token.comment, -.token.block-comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: #999; -} - -.token.punctuation { - color: #ccc; -} - -.token.tag, -.token.attr-name, -.token.namespace, -.token.deleted { - color: #e2777a; -} - -.token.function-name { - color: #6196cc; -} - -.token.boolean, -.token.number, -.token.function { - color: #f08d49; -} - -.token.property, -.token.class-name, -.token.constant, -.token.symbol { - color: #f8c555; -} - -.token.selector, -.token.important, -.token.atrule, -.token.keyword, -.token.builtin { - color: #cc99cd; -} - -.token.string, -.token.char, -.token.attr-value, -.token.regex, -.token.variable { - color: #7ec699; -} - -.token.operator, -.token.entity, -.token.url { - color: #67cdcc; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.token.inserted { - color: green; -} diff --git a/src/client/theme-default/styles/components/custom-block.css b/src/client/theme-default/styles/components/custom-block.css new file mode 100644 index 00000000..81bc71e4 --- /dev/null +++ b/src/client/theme-default/styles/components/custom-block.css @@ -0,0 +1,90 @@ +.custom-block { + border: 1px solid transparent; + border-radius: 8px; + padding: 16px 16px 8px; + line-height: 24px; + font-size: 14px; + color: var(--vp-c-text-2); +} + +.custom-block.info { + border-color: var(--vp-custom-block-info-border); + color: var(--vp-custom-block-info-text); + background-color: var(--vp-custom-block-info-bg); +} + +.custom-block.info code { + background-color: var(--vp-custom-block-info-code-bg); +} + +.custom-block.tip { + border-color: var(--vp-custom-block-tip-border); + color: var(--vp-custom-block-tip-text); + background-color: var(--vp-custom-block-tip-bg); +} + +.custom-block.tip code { + background-color: var(--vp-custom-block-tip-code-bg); +} + +.custom-block.warning { + border-color: var(--vp-custom-block-warning-border); + color: var(--vp-custom-block-warning-text); + background-color: var(--vp-custom-block-warning-bg); +} + +.custom-block.warning code { + background-color: var(--vp-custom-block-warning-code-bg); +} + +.custom-block.danger { + border-color: var(--vp-custom-block-danger-border); + color: var(--vp-custom-block-danger-text); + background-color: var(--vp-custom-block-danger-bg); +} + +.custom-block.danger code { + background-color: var(--vp-custom-block-danger-code-bg); +} + +.custom-block.details { + border-color: var(--vp-custom-block-details-border); + color: var(--vp-custom-block-details-text); + background-color: var(--vp-custom-block-details-bg); +} + +.custom-block.details code { + background-color: var(--vp-custom-block-details-code-bg); +} + +.custom-block-title { + font-weight: 700; +} + +.custom-block p + p { + margin: 8px 0; +} + +.custom-block.details summary { + margin: 0 0 8px; + font-weight: 700; +} + +.custom-block.details summary + p { + margin: 8px 0; +} + +.custom-block a { + color: inherit; + font-weight: 600; + text-decoration: underline; + transition: opacity 0.25s; +} + +.custom-block a:hover { + opacity: 0.6; +} + +.custom-block code { + font-size: var(--vp-custom-block-code-font-size); +} diff --git a/src/client/theme-default/styles/components/vp-code.css b/src/client/theme-default/styles/components/vp-code.css new file mode 100644 index 00000000..ee74e5b2 --- /dev/null +++ b/src/client/theme-default/styles/components/vp-code.css @@ -0,0 +1,7 @@ +.dark .vp-code-light { + display: none; +} + +html:not(.dark) .vp-code-dark { + display: none; +} diff --git a/src/client/theme-default/styles/components/vp-doc.css b/src/client/theme-default/styles/components/vp-doc.css new file mode 100644 index 00000000..e43aa8f3 --- /dev/null +++ b/src/client/theme-default/styles/components/vp-doc.css @@ -0,0 +1,463 @@ +/** + * Headings + * -------------------------------------------------------------------------- */ + +.vp-doc h1, +.vp-doc h2, +.vp-doc h3, +.vp-doc h4, +.vp-doc h5, +.vp-doc h6 { + position: relative; + font-weight: 600; + outline: none; +} + +.vp-doc h1 { + letter-spacing: -0.02em; + line-height: 40px; + font-size: 28px; +} + +.vp-doc h2 { + margin: 48px 0 0; + border-top: 1px solid var(--vp-c-divider-light); + padding-top: 24px; + letter-spacing: -0.02em; + line-height: 32px; + font-size: 24px; +} + +.vp-doc h3 { + margin: 32px 0 0; + letter-spacing: -0.01em; + line-height: 28px; + font-size: 20px; +} + +.vp-doc .header-anchor { + float: left; + margin-left: -0.87em; + padding-right: 0.23em; + font-weight: 500; + opacity: 0; + transition: color 0.25s, opacity 0.25s; +} + +.vp-doc h1:hover .header-anchor, +.vp-doc h1 .header-anchor:focus, +.vp-doc h2:hover .header-anchor, +.vp-doc h2 .header-anchor:focus, +.vp-doc h3:hover .header-anchor, +.vp-doc h3 .header-anchor:focus, +.vp-doc h4:hover .header-anchor, +.vp-doc h4 .header-anchor:focus, +.vp-doc h5:hover .header-anchor, +.vp-doc h5 .header-anchor:focus, +.vp-doc h6:hover .header-anchor, +.vp-doc h6 .header-anchor:focus { + opacity: 1; +} + +@media (min-width: 768px) { + .vp-doc h1 { + letter-spacing: -0.02em; + line-height: 40px; + font-size: 32px; + } +} + +/** + * Paragraph and inline elements + * -------------------------------------------------------------------------- */ + +.vp-doc p, +.vp-doc summary { + margin: 16px 0; +} + +.vp-doc p { + line-height: 28px; +} + +.vp-doc blockquote { + margin: 16px 0; + border-left: 2px solid var(--vp-c-divider); + padding-left: 16px; + transition: border-color 0.5s; +} + +.vp-doc blockquote > p { + margin: 0; + font-size: 16px; + color: var(--vp-c-text-2); + transition: color 0.5s; +} + +.vp-doc a { + font-weight: 500; + color: var(--vp-c-brand); + text-decoration-style: dotted; + transition: color 0.25s; +} + +.vp-doc a:hover { + color: var(--vp-c-brand-dark); +} + +.vp-doc strong { + font-weight: 600; +} + +/** + * Lists + * -------------------------------------------------------------------------- */ + +.vp-doc ul, +.vp-doc ol { + padding-left: 1.25rem; + margin: 16px 0; +} + +.vp-doc ul { + list-style: disc; +} + +.vp-doc ol { + list-style: decimal; +} + +.vp-doc li + li { + margin-top: 8px; +} + +.vp-doc li > ol, +.vp-doc li > ul { + margin: 8px 0 0; +} + +/** + * Table + * -------------------------------------------------------------------------- */ + +.vp-doc table { + display: block; + border-collapse: collapse; + margin: 20px 0; + overflow-x: auto; +} + +.vp-doc tr { + border-top: 1px solid var(--vp-c-divider); + transition: background-color 0.5s; +} + +.vp-doc tr:nth-child(2n) { + background-color: var(--vp-c-bg-soft); +} + +.vp-doc th, +.vp-doc td { + border: 1px solid var(--vp-c-divider); + padding: 12px 16px; +} + +.vp-doc th { + font-size: 16px; + font-weight: 600; + background-color: var(--vp-c-white-soft); +} + +.dark .vp-doc th { + background-color: var(--vp-c-black); +} + +/** + * Decorational elements + * -------------------------------------------------------------------------- */ + +.vp-doc hr { + margin: 16px 0; + border: none; + border-top: 1px solid var(--vp-c-divider-light); +} + +/** + * Custom Block + * -------------------------------------------------------------------------- */ + +.vp-doc .custom-block { + margin: 16px 0; +} + +.vp-doc .custom-block p { + margin: 8px 0; + line-height: 24px; +} + +.vp-doc .custom-block p:first-child { + margin: 0; +} + +.vp-doc .custom-block a { + color: inherit; + font-weight: 600; + text-decoration: underline; + transition: opacity .25s; +} + +.vp-doc .custom-block a:hover { + opacity: 0.6; +} + +.vp-doc .custom-block code { + font-size: var(--vp-custom-block-code-font-size); + font-weight: 700; + color: inherit; +} + +.vp-doc .custom-block div[class*='language-'] { + margin: 8px 0; +} + +.vp-doc .custom-block div[class*='language-'] code { + font-weight: 400; + background-color: var(--vp-code-block-bg); +} + +/** + * Code + * -------------------------------------------------------------------------- */ + +/* inline code */ +.vp-doc :not(pre, h1, h2, h3, h4, h5, h6) > code { + font-size: var(--vp-code-font-size); +} + +.vp-doc :not(pre) > code { + border-radius: 4px; + padding: 3px 6px; + color: var(--vp-c-text-code); + background-color: var(--vp-c-bg-mute); + transition: color 0.5s, background-color 0.5s; +} + +.vp-doc h1 > code, +.vp-doc h2 > code, +.vp-doc h3 > code { + font-size: 0.9em; +} + +.vp-doc a > code { + color: var(--vp-c-brand); + transition: color 0.25s; +} + +.vp-doc a:hover > code { + color: var(--vp-c-brand-dark); +} + +.vp-doc div[class*='language-'] { + position: relative; + margin: 16px -24px; + background-color: var(--vp-code-block-bg); + overflow-x: auto; + transition: background-color 0.5s; +} + +@media (min-width: 640px) { + .vp-doc div[class*='language-'] { + border-radius: 8px; + margin: 16px 0; + } +} + +@media (max-width: 639px) { + .vp-doc li div[class*='language-'] { + border-radius: 8px 0 0 8px; + } +} + +.vp-doc div[class*='language-'] + div[class*='language-'], +.vp-doc div[class$='-api'] + div[class*='language-'], +.vp-doc div[class*='language-'] + div[class$='-api'] > div[class*='language-'] { + margin-top: -8px; +} + +.vp-doc [class*='language-'] pre, +.vp-doc [class*='language-'] code { + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +.vp-doc [class*='language-'] pre { + position: relative; + z-index: 1; + margin: 0; + padding: 16px 0; + background: transparent; + overflow-x: auto; +} + +.vp-doc [class*='language-'] code { + display: block; + padding: 0 24px; + width: fit-content; + line-height: var(--vp-code-line-height); + font-size: var(--vp-code-font-size); + color: var(--vp-code-block-color); + transition: color 0.5s; +} + +.vp-doc .highlight-lines { + position: absolute; + top: 0; + bottom: 0; + left: 0; + padding-top: 16px; + width: 100%; + line-height: var(--vp-code-line-height); + font-family: var(--vp-font-family-mono); + font-size: var(--vp-code-font-size); + user-select: none; + overflow: hidden; +} + +.vp-doc .highlight-lines .highlighted { + background-color: var(--vp-code-line-highlight-color); + transition: background-color 0.5s; +} + +.vp-doc div[class*='language-'].line-numbers-mode { + padding-left: 32px; +} + +.vp-doc div[class*='language-'].line-numbers-mode pre { + padding-left: 16px; +} + +.vp-doc .line-numbers-wrapper { + position: absolute; + top: 0; + bottom: 0; + left: 0; + z-index: 3; + border-right: 1px solid var(--vp-c-divider-dark-2); + padding-top: 16px; + width: 32px; + text-align: center; + font-family: var(--vp-font-family-mono); + line-height: var(--vp-code-line-height); + font-size: var(--vp-code-font-size); + color: var(--vp-code-line-number-color); + transition: border-color 0.5s, color 0.5s; +} + +.vp-doc [class*='language-'] > span.copy { + position: absolute; + top: 8px; + right: 8px; + z-index: 2; + display: block; + justify-content: center; + align-items: center; + border-radius: 4px; + width: 40px; + height: 40px; + background-color: var(--vp-code-block-bg); + opacity: 0; + cursor: pointer; + background-image: var(--vp-icon-copy); + background-position: 50%; + background-size: 20px; + background-repeat: no-repeat; + transition: opacity 0.25s; +} + +.vp-doc [class*='language-']:hover > span.copy { + opacity: 1; +} + +.vp-doc [class*='language-'] > span.copy:hover { + background-color: var(--vp-code-copy-code-hover-bg); +} + +.vp-doc [class*='language-'] > span.copy.copied, +.vp-doc [class*='language-'] > span.copy:hover.copied { + border-radius: 0 4px 4px 0; + background-color: var(--vp-code-copy-code-hover-bg); + background-image: var(--vp-icon-copied); +} + +.vp-doc [class*='language-'] > span.copy.copied::before, +.vp-doc [class*='language-'] > span.copy:hover.copied::before { + position: relative; + left: -65px; + display: block; + border-radius: 4px 0 0 4px; + padding-top: 8px; + width: 64px; + height: 40px; + text-align: center; + font-size: 12px; + font-weight: 500; + color: var(--vp-code-copy-code-active-text); + background-color: var(--vp-code-copy-code-hover-bg); + white-space: nowrap; + content: "Copied"; +} + +.vp-doc [class*='language-']:before { + position: absolute; + top: 6px; + right: 12px; + z-index: 2; + font-size: 12px; + font-weight: 500; + color: var(--vp-c-text-dark-3); + transition: color 0.5s, opacity 0.5s; +} + +.vp-doc [class*='language-']:hover:before { + opacity: 0; +} + +.vp-doc [class~='language-c']:before { content: 'c'; } +.vp-doc [class~='language-css']:before { content: 'css'; } +.vp-doc [class~='language-go']:before { content: 'go'; } +.vp-doc [class~='language-html']:before { content: 'html'; } +.vp-doc [class~='language-java']:before { content: 'java'; } +.vp-doc [class~='language-javascript']:before { content: 'js'; } +.vp-doc [class~='language-js']:before { content: 'js'; } +.vp-doc [class~='language-json']:before { content: 'json'; } +.vp-doc [class~='language-jsx']:before { content: 'jsx'; } +.vp-doc [class~='language-less']:before { content: 'less'; } +.vp-doc [class~='language-markdown']:before { content: 'md'; } +.vp-doc [class~='language-md']:before { content: 'md' } +.vp-doc [class~='language-php']:before { content: 'php'; } +.vp-doc [class~='language-python']:before { content: 'py'; } +.vp-doc [class~='language-py']:before { content: 'py'; } +.vp-doc [class~='language-rb']:before { content: 'rb'; } +.vp-doc [class~='language-ruby']:before { content: 'rb'; } +.vp-doc [class~='language-rust']:before { content: 'rust'; } +.vp-doc [class~='language-sass']:before { content: 'sass'; } +.vp-doc [class~='language-scss']:before { content: 'scss'; } +.vp-doc [class~='language-sh']:before { content: 'sh'; } +.vp-doc [class~='language-bash']:before { content: 'sh'; } +.vp-doc [class~='language-stylus']:before { content: 'styl'; } +.vp-doc [class~='language-vue-html']:before { content: 'template'; } +.vp-doc [class~='language-typescript']:before { content: 'ts'; } +.vp-doc [class~='language-ts']:before { content: 'ts'; } +.vp-doc [class~='language-tsx']:before { content: 'tsx'; } +.vp-doc [class~='language-vue']:before { content: 'vue'; } +.vp-doc [class~='language-yaml']:before { content: 'yaml'; } diff --git a/src/client/theme-default/styles/components/vp-sponsor.css b/src/client/theme-default/styles/components/vp-sponsor.css new file mode 100644 index 00000000..09fb6cb1 --- /dev/null +++ b/src/client/theme-default/styles/components/vp-sponsor.css @@ -0,0 +1,130 @@ +/** + * VPSponsors styles are defined as global because a new class gets + * allied in onMounted` hook and we can't use socped style. + */ +.vp-sponsor { + border-radius: 16px; + overflow: hidden; +} + +.vp-sponsor.aside { + border-radius: 12px; +} + +.vp-sponsor-section + .vp-sponsor-section { + margin-top: 4px; +} + +.vp-sponsor-tier { + margin-bottom: 4px; + text-align: center; + letter-spacing: 1px; + line-height: 24px; + width: 100%; + font-weight: 600; + color: var(--vp-c-text-2); + background-color: var(--vp-c-bg-soft); +} + +.vp-sponsor.normal .vp-sponsor-tier { + padding: 13px 0 11px; + font-size: 14px; +} + +.vp-sponsor.aside .vp-sponsor-tier { + padding: 9px 0 7px; + font-size: 12px; +} + +.vp-sponsor-grid + .vp-sponsor-tier { + margin-top: 4px; +} + +.vp-sponsor-grid { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.vp-sponsor-grid.xmini .vp-sponsor-grid-link { height: 64px; } +.vp-sponsor-grid.xmini .vp-sponsor-grid-image { max-width: 64px; max-height: 22px } + +.vp-sponsor-grid.mini .vp-sponsor-grid-link { height: 72px; } +.vp-sponsor-grid.mini .vp-sponsor-grid-image { max-width: 96px; max-height: 24px } + +.vp-sponsor-grid.small .vp-sponsor-grid-link { height: 96px; } +.vp-sponsor-grid.small .vp-sponsor-grid-image { max-width: 96px; max-height: 24px } + +.vp-sponsor-grid.medium .vp-sponsor-grid-link { height: 112px; } +.vp-sponsor-grid.medium .vp-sponsor-grid-image { max-width: 120px; max-height: 36px } + +.vp-sponsor-grid.big .vp-sponsor-grid-link { height: 184px; } +.vp-sponsor-grid.big .vp-sponsor-grid-image { max-width: 192px; max-height: 56px } + +.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item { + width: calc((100% - 4px) / 2); +} + +.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item { + width: calc((100% - 4px * 2) / 3); +} + +.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item { + width: calc((100% - 4px * 3) / 4); +} + +.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item { + width: calc((100% - 4px * 4) / 5); +} + +.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item { + width: calc((100% - 4px * 5) / 6); +} + +.vp-sponsor-grid-item { + flex-shrink: 0; + width: 100%; + background-color: var(--vp-c-bg-soft); + transition: background-color 0.25s; +} + +.vp-sponsor-grid-item:hover { + background-color: var(--vp-c-bg-mute); +} + +.vp-sponsor-grid-item:hover .vp-sponsor-grid-image { + filter: grayscale(0) invert(0); +} + +.vp-sponsor-grid-item.empty:hover { + background-color: var(--vp-c-bg-soft); +} + +.dark .vp-sponsor-grid-item:hover { + background-color: var(--vp-c-white-soft); +} + +.dark .vp-sponsor-grid-item.empty:hover { + background-color: var(--vp-c-black-mute); +} + +.vp-sponsor-grid-link { + display: flex; +} + +.vp-sponsor-grid-box { + display: flex; + justify-content: center; + align-items: center; + width: 100%; +} + +.vp-sponsor-grid-image { + max-width: 100%; + filter: grayscale(1); + transition: filter 0.25s; +} + +.dark .vp-sponsor-grid-image { + filter: grayscale(1) invert(1); +} diff --git a/src/client/theme-default/styles/custom-blocks.css b/src/client/theme-default/styles/custom-blocks.css deleted file mode 100644 index 38507ace..00000000 --- a/src/client/theme-default/styles/custom-blocks.css +++ /dev/null @@ -1,76 +0,0 @@ -.custom-block.tip, -.custom-block.info, -.custom-block.warning, -.custom-block.danger { - margin: 1rem 0; - border-left: 0.5rem solid; - padding: 0.1rem 1.5rem; - overflow-x: auto; -} - -.custom-block.tip { - background-color: #f3f5f7; - border-color: var(--c-brand); -} - -.custom-block.info { - background-color: #f3f5f7; - border-color: var(--c-text-light-2); -} - -.custom-block.warning { - border-color: #e7c000; - color: #6b5900; - background-color: rgba(255, 229, 100, 0.3); -} - -.custom-block.warning .custom-block-title { - color: #b29400; -} - -.custom-block.warning a { - color: var(--c-text); -} - -.custom-block.danger { - border-color: #c00; - color: #4d0000; - background-color: #ffe6e6; -} - -.custom-block.danger .custom-block-title { - color: #900; -} - -.custom-block.danger a { - color: var(--c-text); -} - -.custom-block.details { - position: relative; - display: block; - border-radius: 2px; - margin: 1.6em 0; - padding: 1.6em; - background-color: #eee; -} - -.custom-block.details h4 { - margin-top: 0; -} - -.custom-block.details figure:last-child, -.custom-block.details p:last-child { - margin-bottom: 0; - padding-bottom: 0; -} - -.custom-block.details summary { - outline: none; - cursor: pointer; -} - -.custom-block-title { - margin-bottom: -0.4rem; - font-weight: 600; -} diff --git a/src/client/theme-default/styles/fonts.css b/src/client/theme-default/styles/fonts.css new file mode 100644 index 00000000..e9c9db97 --- /dev/null +++ b/src/client/theme-default/styles/fonts.css @@ -0,0 +1,67 @@ +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url(../fonts/inter-latin.woff2) format('woff2'); + unicode-range: U+00??, U+0131, U+0152-0153, U+02bb-02bc, U+02c6, U+02da, + U+02dc, U+2000-206f, U+2074, U+20ac, U+2122, U+2191, U+2193, U+2212, U+2215, + U+feff, U+fffd; +} + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 400 500 600 700 900; + font-display: swap; + src: url(../fonts/inter-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-024f, U+0259, U+1e??, U+2020, U+20a0-20ab, U+20ad-20cf, + U+2113, U+2c60-2c7f, U+a720-a7ff; +} + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 400 500 600 700 900; + font-display: swap; + src: url(../fonts/inter-cyrillic.woff2) format('woff2'); + unicode-range: U+0400-045f, U+0490-0491, U+04b0-04b1, U+2116; +} + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 400 500 600 700 900; + font-display: swap; + src: url(../fonts/inter-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052f, U+1c80-1c88, U+20b4, U+2de0-2dff, U+a640-a69f, + U+fe2e-fe2f; +} + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 400 500 600 700 900; + font-display: swap; + src: url(../fonts/inter-greek.woff2) format('woff2'); + unicode-range: U+0370-03ff; +} + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 400 500 600 700 900; + font-display: swap; + src: url(../fonts/inter-greek-ext.woff2) format('woff2'); + unicode-range: U+1f??; +} + +@font-face { + font-family: Inter; + font-style: normal; + font-weight: 400 500 600 700 900; + font-display: swap; + src: url(../fonts/inter-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01a0-01a1, + U+01af-01b0, U+1ea0-1ef9, U+20ab; +} diff --git a/src/client/theme-default/styles/layout.css b/src/client/theme-default/styles/layout.css deleted file mode 100644 index 8d9e0a8e..00000000 --- a/src/client/theme-default/styles/layout.css +++ /dev/null @@ -1,236 +0,0 @@ -*, -::before, -::after { - box-sizing: border-box; -} - -html { - line-height: 1.4; - font-size: 16px; - -webkit-text-size-adjust: 100%; -} - -body { - margin: 0; - width: 100%; - min-width: 320px; - min-height: 100vh; - line-height: 1.4; - font-family: var(--font-family-base); - font-size: 16px; - font-weight: 400; - color: var(--c-text); - background-color: var(--c-bg); - direction: ltr; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -main { - display: block; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - margin: 0; - line-height: 1.25; -} - -h1, -h2, -h3, -h4, -h5, -h6, -strong, -b { - font-weight: 600; -} - -h1:hover .header-anchor, -h1:focus .header-anchor, -h2:hover .header-anchor, -h2:focus .header-anchor, -h3:hover .header-anchor, -h3:focus .header-anchor, -h4:hover .header-anchor, -h4:focus .header-anchor, -h5:hover .header-anchor, -h5:focus .header-anchor, -h6:hover .header-anchor, -h6:focus .header-anchor { - opacity: 1; -} - -h1 { - margin-top: 1.5rem; - font-size: 1.9rem; -} - -@media screen and (min-width: 420px) { - h1 { - font-size: 2.2rem; - } -} - -h2 { - margin-top: 2.25rem; - margin-bottom: 1.25rem; - border-bottom: 1px solid var(--c-divider); - padding-bottom: 0.3rem; - line-height: 1.25; - font-size: 1.65rem; - /* overflow-x: auto; */ -} - -h2 + h3 { - margin-top: 1.5rem; -} - -h3 { - margin-top: 2rem; - font-size: 1.35rem; -} - -h4 { - font-size: 1.15rem; -} - -p, -ol, -ul { - margin: 1rem 0; - line-height: 1.7; -} - -a, -area, -button, -[role='button'], -input, -label, -select, -summary, -textarea { - touch-action: manipulation; -} - -a { - text-decoration: none; - color: var(--c-brand); -} - -a:hover { - text-decoration: underline; -} - -a.header-anchor { - float: left; - margin-top: 0.125em; - margin-left: -0.87em; - padding-right: 0.23em; - font-size: 0.85em; - opacity: 0; -} - -a.header-anchor:hover, -a.header-anchor:focus { - text-decoration: none; -} - -figure { - margin: 0; -} - -img { - max-width: 100%; -} - -ul, -ol { - padding-left: 1.25em; -} - -li > ul, -li > ol { - margin: 0; -} - -table { - display: block; - border-collapse: collapse; - margin: 1rem 0; - overflow-x: auto; -} - -tr { - border-top: 1px solid #dfe2e5; -} - -tr:nth-child(2n) { - background-color: #f6f8fa; -} - -th, -td { - border: 1px solid #dfe2e5; - padding: 0.6em 1em; -} - -blockquote { - margin: 1rem 0; - border-left: 0.2rem solid #dfe2e5; - padding: 0.25rem 0 0.25rem 1rem; - font-size: 1rem; - color: #999; -} - -blockquote > p { - margin: 0; -} - -form { - margin: 0; -} - -.theme.sidebar-open .sidebar-mask { - display: block; -} - -.theme.no-navbar > h1, -.theme.no-navbar > h2, -.theme.no-navbar > h3, -.theme.no-navbar > h4, -.theme.no-navbar > h5, -.theme.no-navbar > h6 { - margin-top: 1.5rem; - padding-top: 0; -} - -.theme.no-navbar aside { - top: 0; -} - -@media screen and (min-width: 720px) { - .theme.no-sidebar aside { - display: none; - } - - .theme.no-sidebar main { - margin-left: 0; - } -} - -.sidebar-mask { - position: fixed; - z-index: 2; - display: none; - width: 100vw; - height: 100vh; -} diff --git a/src/client/theme-default/styles/sidebar-links.css b/src/client/theme-default/styles/sidebar-links.css deleted file mode 100644 index c15f53fd..00000000 --- a/src/client/theme-default/styles/sidebar-links.css +++ /dev/null @@ -1,107 +0,0 @@ -.sidebar-links { - margin: 0; - padding: 0; - list-style: none; -} - -.sidebar-link-item { - display: block; - margin: 0; - border-left: 0.25rem solid transparent; - color: var(--c-text); -} - -a.sidebar-link-item:hover { - text-decoration: none; - color: var(--c-brand); -} - -a.sidebar-link-item.active { - color: var(--c-brand); -} - -.sidebar > .sidebar-links { - padding: 0.75rem 0 5rem; -} - -@media (min-width: 720px) { - .sidebar > .sidebar-links { - padding: 1.5rem 0; - } -} - -.sidebar > .sidebar-links > .sidebar-link + .sidebar-link { - padding-top: 0.5rem; -} - -@media (min-width: 720px) { - .sidebar > .sidebar-links > .sidebar-link + .sidebar-link { - padding-top: 1.25rem; - } -} - -.sidebar > .sidebar-links > .sidebar-link > .sidebar-link-item { - padding: 0.35rem 1.5rem 0.35rem 1.25rem; - font-size: 1.1rem; - font-weight: 700; -} - -.sidebar > .sidebar-links > .sidebar-link > a.sidebar-link-item.active { - border-left-color: var(--c-brand); - font-weight: 600; -} - -.sidebar - > .sidebar-links - > .sidebar-link - > .sidebar-links - > .sidebar-link - > .sidebar-link-item { - display: block; - padding: 0.35rem 1.5rem 0.35rem 2rem; - line-height: 1.4; - font-size: 1rem; - font-weight: 400; -} - -.sidebar - > .sidebar-links - > .sidebar-link - > .sidebar-links - > .sidebar-link - > a.sidebar-link-item.active { - border-left-color: var(--c-brand); - font-weight: 600; -} - -.sidebar - > .sidebar-links - > .sidebar-link - > .sidebar-links - > .sidebar-link - > .sidebar-links - > .sidebar-link - > .sidebar-link-item { - display: block; - padding: 0.3rem 1.5rem 0.3rem 3rem; - line-height: 1.4; - font-size: 0.9rem; - font-weight: 400; -} - -.sidebar - > .sidebar-links - > .sidebar-link - > .sidebar-links - > .sidebar-link - > .sidebar-links - > .sidebar-link - > .sidebar-links - > .sidebar-link - > .sidebar-link-item { - display: block; - padding: 0.3rem 1.5rem 0.3rem 4rem; - line-height: 1.4; - font-size: 0.9rem; - font-weight: 400; -} diff --git a/src/client/theme-default/styles/utils.css b/src/client/theme-default/styles/utils.css new file mode 100644 index 00000000..65c7e55e --- /dev/null +++ b/src/client/theme-default/styles/utils.css @@ -0,0 +1,9 @@ +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + white-space: nowrap; + clip: rect(0 0 0 0); + clip-path: inset(50%); + overflow: hidden; +} diff --git a/src/client/theme-default/styles/vars.css b/src/client/theme-default/styles/vars.css index 78ec1203..ee03cfd1 100644 --- a/src/client/theme-default/styles/vars.css +++ b/src/client/theme-default/styles/vars.css @@ -1,74 +1,360 @@ -/** Base Styles */ +/** + * Colors Base + * + * These are the pure base color presets. Most of the time, you should not be + * using these colors directly in the theme but rather use "Colors Theme" + * instead because those are "Theme (light or dark)" dependant. + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-white: #ffffff; + --vp-c-white-soft: #f9f9f9; + --vp-c-white-mute: #f1f1f1; + + --vp-c-black: #1a1a1a; + --vp-c-black-pure: #000000; + --vp-c-black-soft: #242424; + --vp-c-black-mute: #2f2f2f; + + --vp-c-gray: #8e8e8e; + --vp-c-gray-light-1: #aeaeae; + --vp-c-gray-light-2: #c7c7c7; + --vp-c-gray-light-3: #d1d1d1; + --vp-c-gray-light-4: #e5e5e5; + --vp-c-gray-light-5: #f2f2f2; + --vp-c-gray-dark-1: #636363; + --vp-c-gray-dark-2: #484848; + --vp-c-gray-dark-3: #3a3a3a; + --vp-c-gray-dark-4: #282828; + --vp-c-gray-dark-5: #202020; + + --vp-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vp-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vp-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vp-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vp-c-text-light-1: var(--vp-c-indigo); + --vp-c-text-light-2: rgba(60, 60, 60, 0.70); + --vp-c-text-light-3: rgba(60, 60, 60, 0.33); + --vp-c-text-light-4: rgba(60, 60, 60, 0.18); + + --vp-c-text-dark-1: rgba(255, 255, 255, 0.87); + --vp-c-text-dark-2: rgba(235, 235, 235, 0.60); + --vp-c-text-dark-3: rgba(235, 235, 235, 0.38); + --vp-c-text-dark-4: rgba(235, 235, 235, 0.18); + + --vp-c-indigo: #213547; + --vp-c-indigo-soft: #476582; + --vp-c-indigo-light: #aac8e4; + --vp-c-indigo-lighter: #c9def1; + --vp-c-indigo-dark: #1d2f3f; + --vp-c-indigo-darker: #14212e; + + --vp-c-green: #42b883; + --vp-c-green-light: #42d392; + --vp-c-green-lighter: #35eb9a; + --vp-c-green-dark: #33a06f; + --vp-c-green-darker: #155f3e; + --vp-c-green-dimm-1: rgba(66, 184, 131, 0.5); + --vp-c-green-dimm-2: rgba(66, 184, 131, 0.25); + --vp-c-green-dimm-3: rgba(66, 184, 131, 0.05); + + --vp-c-yellow: #ffc517; + --vp-c-yellow-light: #fcd253; + --vp-c-yellow-lighter: #fcfc7c; + --vp-c-yellow-dark: #e0ad15; + --vp-c-yellow-darker: #ad850e; + --vp-c-yellow-dimm-1: rgba(255, 197, 23, 0.5); + --vp-c-yellow-dimm-2: rgba(255, 197, 23, 0.25); + --vp-c-yellow-dimm-3: rgba(255, 197, 23, 0.05); + + --vp-c-red: #ed3c50; + --vp-c-red-light: #f54e82; + --vp-c-red-lighter: #fd1d7c; + --vp-c-red-dark: #cd2d3f; + --vp-c-red-darker: #ab2131; + --vp-c-red-dimm-1: rgba(237, 60, 80, 0.5); + --vp-c-red-dimm-2: rgba(237, 60, 80, 0.25); + --vp-c-red-dimm-3: rgba(237, 60, 80, 0.05); +} + +/** + * Colors Theme + * -------------------------------------------------------------------------- */ + :root { - /** - * Colors - * --------------------------------------------------------------------- */ + --vp-c-bg: var(--vp-c-white); + --vp-c-bg-soft: var(--vp-c-white-soft); + --vp-c-bg-mute: var(--vp-c-white-mute); + --vp-c-bg-alt: var(--vp-c-white-soft); + + --vp-c-divider: var(--vp-c-divider-light-1); + --vp-c-divider-light: var(--vp-c-divider-light-2); - --c-white: #ffffff; - --c-white-dark: #f8f8f8; - --c-black: #000000; + --vp-c-divider-inverse: var(--vp-c-divider-dark-1); + --vp-c-divider-inverse-light: var(--vp-c-divider-dark-2); - --c-divider-light: rgba(60, 60, 67, 0.12); - --c-divider-dark: rgba(84, 84, 88, 0.48); + --vp-c-text-1: var(--vp-c-text-light-1); + --vp-c-text-2: var(--vp-c-text-light-2); + --vp-c-text-3: var(--vp-c-text-light-3); + --vp-c-text-4: var(--vp-c-text-light-4); - --c-text-light-1: #2c3e50; - --c-text-light-2: #476582; - --c-text-light-3: #90a4b7; + --vp-c-text-inverse-1: var(--vp-c-text-dark-1); + --vp-c-text-inverse-2: var(--vp-c-text-dark-2); + --vp-c-text-inverse-3: var(--vp-c-text-dark-3); + --vp-c-text-inverse-4: var(--vp-c-text-dark-4); - --c-brand: #3eaf7c; - --c-brand-light: #4abf8a; + --vp-c-text-code: var(--vp-c-indigo-soft); - /** - * Typography - * --------------------------------------------------------------------- */ + --vp-c-brand: var(--vp-c-green); + --vp-c-brand-light: var(--vp-c-green-light); + --vp-c-brand-lighter: var(--vp-c-green-lighter); + --vp-c-brand-dark: var(--vp-c-green-dark); + --vp-c-brand-darker: var(--vp-c-green-darker); - --font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, - Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - --font-family-mono: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; + --vp-c-sponsor: #fd1d7c; +} - /** - * Z Indexes - * - * Algolia SearchBox has a z-index of 200, so make sure not to go above - * that value. - * --------------------------------------------------------------------- */ +.dark { + --vp-c-bg: var(--vp-c-black-soft); + --vp-c-bg-soft: var(--vp-c-black-mute); + --vp-c-bg-mute: var(--vp-c-gray-dark-3); + --vp-c-bg-alt: var(--vp-c-black); - --z-index-navbar: 10; - --z-index-sidebar: 6; + --vp-c-divider: var(--vp-c-divider-dark-1); + --vp-c-divider-light: var(--vp-c-divider-dark-2); - /** - * Shadows - * --------------------------------------------------------------------- */ + --vp-c-divider-inverse: var(--vp-c-divider-light-1); + --vp-c-divider-inverse-light: var(--vp-c-divider-light-2); - --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06); - --shadow-2: 0 3px 12px rgba(0, 0, 0, 0.07), 0 1px 4px rgba(0, 0, 0, 0.07); - --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08); - --shadow-4: 0 14px 44px rgba(0, 0, 0, 0.12), 0 3px 9px rgba(0, 0, 0, 0.12); - --shadow-5: 0 18px 56px rgba(0, 0, 0, 0.16), 0 4px 12px rgba(0, 0, 0, 0.16); + --vp-c-text-1: var(--vp-c-text-dark-1); + --vp-c-text-2: var(--vp-c-text-dark-2); + --vp-c-text-3: var(--vp-c-text-dark-3); + --vp-c-text-4: var(--vp-c-text-dark-4); - /** - * Sizes - * --------------------------------------------------------------------- */ + --vp-c-text-inverse-1: var(--vp-c-text-light-1); + --vp-c-text-inverse-2: var(--vp-c-text-light-2); + --vp-c-text-inverse-3: var(--vp-c-text-light-3); + --vp-c-text-inverse-4: var(--vp-c-text-light-4); - --header-height: 3.6rem; + --vp-c-text-code: var(--vp-c-indigo-lighter); } -/** Fallback Styles */ +/** + * Typography + * -------------------------------------------------------------------------- */ + :root { - --c-divider: var(--c-divider-light); + --vp-font-family-base: Inter, -apple-system, + BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, + 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + --vp-font-family-mono: Menlo, Monaco, Consolas, 'Courier New', monospace; +} - --c-text: var(--c-text-light-1); - --c-text-light: var(--c-text-light-2); - --c-text-lighter: var(--c-text-light-3); +/** + * Shadows + * -------------------------------------------------------------------------- */ + +:root { + --vp-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06); + --vp-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.07), 0 1px 4px rgba(0, 0, 0, 0.07); + --vp-shadow-3: 0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08); + --vp-shadow-4: 0 14px 44px rgba(0, 0, 0, 0.12), 0 3px 9px rgba(0, 0, 0, 0.12); + --vp-shadow-5: 0 18px 56px rgba(0, 0, 0, 0.16), 0 4px 12px rgba(0, 0, 0, 0.16); +} + +/** + * Z-indexes + * -------------------------------------------------------------------------- */ + +:root { + --vp-z-index-local-nav: 10; + --vp-z-index-nav: 20; + --vp-z-index-backdrop: 30; + --vp-z-index-sidebar: 40; + --vp-z-index-footer: 50; +} + +/** + * Icons + * -------------------------------------------------------------------------- */ + + :root { + --vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' class='h-6 w-6' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2'/%3E%3C/svg%3E"); + --vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' height='20' width='20' stroke='rgba(128,128,128,1)' stroke-width='2' class='h-6 w-6' viewBox='0 0 24 24'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2M9 5a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2M9 5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2m-6 9 2 2 4-4'/%3E%3C/svg%3E"); +} + +/** + * Layouts + * -------------------------------------------------------------------------- */ + +:root { + --vp-layout-max-width: 1440px; +} + +/** + * Component: Code + * -------------------------------------------------------------------------- */ + +:root { + --vp-code-line-height: 1.7; + --vp-code-font-size: 0.875em; + + --vp-code-block-color: var(--vp-c-text-dark-1); + --vp-code-block-bg: #292d3e; + + --vp-code-line-highlight-color: rgba(0, 0, 0, 0.5); + --vp-code-line-number-color: var(--vp-c-text-dark-3); + + --vp-code-copy-code-hover-bg: rgba(255, 255, 255, 0.05); + --vp-code-copy-code-active-text: var(--vp-c-text-dark-2); +} - --c-bg: var(--c-white); - --c-bg-accent: var(--c-white-dark); +.dark { + --vp-code-block-bg: var(--vp-c-bg-alt); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-text-dark-1); + --vp-button-brand-bg: var(--vp-c-brand); + --vp-button-brand-hover-border: var(--vp-c-brand-light); + --vp-button-brand-hover-text: var(--vp-c-text-dark-1); + --vp-button-brand-hover-bg: var(--vp-c-brand-light); + --vp-button-brand-active-border: var(--vp-c-brand-light); + --vp-button-brand-active-text: var(--vp-c-text-dark-1); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); + + --vp-button-alt-border: var(--vp-c-gray-light-3); + --vp-button-alt-text: var(--vp-c-text-light-1); + --vp-button-alt-bg: var(--vp-c-gray-light-5); + --vp-button-alt-hover-border: var(--vp-c-gray-light-3); + --vp-button-alt-hover-text: var(--vp-c-text-light-1); + --vp-button-alt-hover-bg: var(--vp-c-gray-light-4); + --vp-button-alt-active-border: var(--vp-c-gray-light-3); + --vp-button-alt-active-text: var(--vp-c-text-light-1); + --vp-button-alt-active-bg: var(--vp-c-gray-light-3); + + --vp-button-sponsor-border: var(--vp-c-gray-light-3); + --vp-button-sponsor-text: var(--vp-c-text-light-2); + --vp-button-sponsor-bg: transparent; + --vp-button-sponsor-hover-border: var(--vp-c-sponsor); + --vp-button-sponsor-hover-text: var(--vp-c-sponsor); + --vp-button-sponsor-hover-bg: transparent; + --vp-button-sponsor-active-border: var(--vp-c-sponsor); + --vp-button-sponsor-active-text: var(--vp-c-sponsor); + --vp-button-sponsor-active-bg: transparent; +} + +.dark { + --vp-button-brand-border: var(--vp-c-brand-light); + --vp-button-brand-text: var(--vp-c-text-dark-1); + --vp-button-brand-bg: var(--vp-c-brand-dark); + --vp-button-brand-hover-border: var(--vp-c-brand-lighter); + --vp-button-brand-hover-text: var(--vp-c-text-dark-1); + --vp-button-brand-hover-bg: var(--vp-c-brand); + --vp-button-brand-active-border: var(--vp-c-brand-lighter); + --vp-button-brand-active-text: var(--vp-c-text-dark-1); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); + + --vp-button-alt-border: var(--vp-c-gray-dark-2); + --vp-button-alt-text: var(--vp-c-text-dark-1); + --vp-button-alt-bg: var(--vp-c-bg-mute); + --vp-button-alt-hover-border: var(--vp-c-gray-dark-2); + --vp-button-alt-hover-text: var(--vp-c-text-dark-1); + --vp-button-alt-hover-bg: var(--vp-c-gray-dark-2); + --vp-button-alt-active-border: var(--vp-c-gray-dark-2); + --vp-button-alt-active-text: var(--vp-c-text-dark-1); + --vp-button-alt-active-bg: var(--vp-button-alt-bg); + + --vp-button-sponsor-border: var(--vp-c-gray-dark-1); + --vp-button-sponsor-text: var(--vp-c-text-dark-2); +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-code-font-size: 13px; + + --vp-custom-block-info-border: var(--vp-c-divider-light); + --vp-custom-block-info-text: var(--vp-c-text-2); + --vp-custom-block-info-bg: var(--vp-c-white-soft); + --vp-custom-block-info-code-bg: var(--vp-c-gray-light-4); + + --vp-custom-block-tip-border: var(--vp-c-green-dimm-1); + --vp-custom-block-tip-text: var(--vp-c-green-darker); + --vp-custom-block-tip-bg: var(--vp-c-green-dimm-3); + --vp-custom-block-tip-code-bg: var(--vp-custom-block-tip-bg); + + --vp-custom-block-warning-border: var(--vp-c-yellow-dimm-1); + --vp-custom-block-warning-text: var(--vp-c-yellow-darker); + --vp-custom-block-warning-bg: var(--vp-c-yellow-dimm-3); + --vp-custom-block-warning-code-bg: var(--vp-custom-block-warning-bg); + + --vp-custom-block-danger-border: var(--vp-c-red-dimm-1); + --vp-custom-block-danger-text: var(--vp-c-red-darker); + --vp-custom-block-danger-bg: var(--vp-c-red-dimm-3); + --vp-custom-block-danger-code-bg: var(--vp-custom-block-danger-bg); + + --vp-custom-block-details-border: var(--vp-custom-block-info-border); + --vp-custom-block-details-text: var(--vp-custom-block-info-text); + --vp-custom-block-details-bg: var(--vp-custom-block-info-bg); + --vp-custom-block-details-code-bg: var(--vp-custom-block-details-bg); +} + +.dark { + --vp-custom-block-info-border: var(--vp-c-divider-light); + --vp-custom-block-info-bg: var(--vp-c-black-mute); + --vp-custom-block-info-code-bg: var(--vp-c-gray-dark-4); + + --vp-custom-block-tip-border: var(--vp-c-green-dimm-2); + --vp-custom-block-tip-text: var(--vp-c-green-light); + + --vp-custom-block-warning-border: var(--vp-c-yellow-dimm-2); + --vp-custom-block-warning-text: var(--vp-c-yellow-light); + + --vp-custom-block-danger-border: var(--vp-c-red-dimm-2); + --vp-custom-block-danger-text: var(--vp-c-red-light); +} + +/** + * Component: Nav + * -------------------------------------------------------------------------- */ + +:root { + --vp-nav-height: var(--vp-nav-height-mobile); + --vp-nav-height-mobile: 56px; + --vp-nav-height-desktop: 72px; +} + +@media (min-width: 960px) { + :root { + --vp-nav-height: var(--vp-nav-height-desktop); + } +} + +/** + * Component: Sidebar + * -------------------------------------------------------------------------- */ + +:root { + --vp-sidebar-width: 272px; +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: var(--vp-c-brand); + --vp-home-hero-name-background: transparent; - --code-line-height: 24px; - --code-font-family: var(--font-family-mono); - --code-font-size: 14px; - --code-inline-bg-color: rgba(27, 31, 35, 0.05); - --code-bg-color: #282c34; + --vp-home-hero-image-background-image: none; + --vp-home-hero-image-filter: none; } diff --git a/src/client/theme-default/support/sideBar.ts b/src/client/theme-default/support/sideBar.ts deleted file mode 100644 index 63ccce17..00000000 --- a/src/client/theme-default/support/sideBar.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { DefaultTheme } from '../config' -import { isArray, ensureStartingSlash, removeExtention } from '../utils' - -export function isSideBarConfig( - sidebar: DefaultTheme.SideBarConfig | DefaultTheme.MultiSideBarConfig -): sidebar is DefaultTheme.SideBarConfig { - return sidebar === false || sidebar === 'auto' || isArray(sidebar) -} - -export function isSideBarGroup( - item: DefaultTheme.SideBarItem -): item is DefaultTheme.SideBarGroup { - return (item as DefaultTheme.SideBarGroup).children !== undefined -} - -export function isSideBarEmpty(sidebar?: DefaultTheme.SideBarConfig): boolean { - return isArray(sidebar) ? sidebar.length === 0 : !sidebar -} - -/** - * Get the `SideBarConfig` from sidebar option. This method will ensure to get - * correct sidebar config from `MultiSideBarConfig` with various path - * combinations such as matching `guide/` and `/guide/`. If no matching config - * was found, it will return `auto` as a fallback. - */ -export function getSideBarConfig( - sidebar: DefaultTheme.SideBarConfig | DefaultTheme.MultiSideBarConfig, - path: string -): DefaultTheme.SideBarConfig { - if (isSideBarConfig(sidebar)) { - return sidebar - } - - path = ensureStartingSlash(path) - - for (const dir in sidebar) { - // make sure the multi sidebar key starts with slash too - if (path.startsWith(ensureStartingSlash(dir))) { - return sidebar[dir] - } - } - - return 'auto' -} - -/** - * Get flat sidebar links from the sidebar items. This method is useful for - * creating the "next and prev link" feature. It will ignore any items that - * don't have `link` property and removes `.md` or `.html` extension if a - * link contains it. - */ -export function getFlatSideBarLinks( - sidebar: DefaultTheme.SideBarItem[] -): DefaultTheme.SideBarLink[] { - return sidebar.reduce((links, item) => { - if (item.link) { - links.push({ text: item.text, link: removeExtention(item.link) }) - } - - if (isSideBarGroup(item)) { - links = [...links, ...getFlatSideBarLinks(item.children)] - } - - return links - }, []) -} diff --git a/src/client/theme-default/support/sidebar.ts b/src/client/theme-default/support/sidebar.ts new file mode 100644 index 00000000..cbb057a1 --- /dev/null +++ b/src/client/theme-default/support/sidebar.ts @@ -0,0 +1,42 @@ +import { DefaultTheme } from '../config' +import { ensureStartingSlash } from './utils' + +/** + * Get the `Sidebar` from sidebar option. This method will ensure to get correct + * sidebar config from `MultiSideBarConfig` with various path combinations such + * as matching `guide/` and `/guide/`. If no matching config was found, it will + * return empty array. + */ +export function getSidebar( + sidebar: DefaultTheme.Sidebar, + path: string +): DefaultTheme.SidebarGroup[] { + if (Array.isArray(sidebar)) { + return sidebar + } + + path = ensureStartingSlash(path) + + for (const dir in sidebar) { + // make sure the multi sidebar key starts with slash too + if (path.startsWith(ensureStartingSlash(dir))) { + return sidebar[dir] + } + } + + return [] +} + +export function getFlatSideBarLinks( + sidebar: DefaultTheme.SidebarGroup[] +): DefaultTheme.SidebarItem[] { + const links: DefaultTheme.SidebarItem[] = [] + + for (const group of sidebar) { + for (const link of group.items) { + links.push(link) + } + } + + return links +} diff --git a/src/client/theme-default/support/utils.ts b/src/client/theme-default/support/utils.ts new file mode 100644 index 00000000..be2a39c8 --- /dev/null +++ b/src/client/theme-default/support/utils.ts @@ -0,0 +1,85 @@ +import { ref } from 'vue' +import { withBase } from 'vitepress' + +export const HASH_RE = /#.*$/ +export const EXT_RE = /(index)?\.(md|html)$/ +export const OUTBOUND_RE = /^[a-z]+:/i + +const inBrowser = typeof window !== 'undefined' +const hashRef = ref(inBrowser ? location.hash : '') + +export function isExternal(path: string): boolean { + return OUTBOUND_RE.test(path) +} + +export function throttleAndDebounce(fn: () => void, delay: number): () => void { + let timeout: number + let called = false + + return () => { + if (timeout) { + clearTimeout(timeout) + } + + if (!called) { + fn() + called = true + setTimeout(() => { + called = false + }, delay) + } else { + timeout = setTimeout(fn, delay) + } + } +} + +export function isActive( + currentPath: string, + matchPath?: string, + asRegex: boolean = false +): boolean { + if (matchPath === undefined) { + return false + } + + currentPath = normalize(`/${currentPath}`) + + if (asRegex) { + return new RegExp(matchPath).test(currentPath) + } + + if (normalize(matchPath) !== currentPath) { + return false + } + + const hashMatch = matchPath.match(HASH_RE) + + if (hashMatch) { + return hashRef.value === hashMatch[0] + } + + return true +} + +export function ensureStartingSlash(path: string): string { + return /^\//.test(path) ? path : `/${path}` +} + +export function normalize(path: string): string { + return decodeURI(path).replace(HASH_RE, '').replace(EXT_RE, '') +} + +export function normalizeLink(url: string): string { + if (isExternal(url)) { + return url + } + + const { pathname, search, hash } = new URL(url, 'http://example.com') + + const normalizedPath = + pathname.endsWith('/') || pathname.endsWith('.html') + ? url + : `${pathname.replace(/(\.md)?$/, '.html')}${search}${hash}` + + return withBase(normalizedPath) +} diff --git a/src/client/theme-default/utils.ts b/src/client/theme-default/utils.ts deleted file mode 100644 index 4c6f88de..00000000 --- a/src/client/theme-default/utils.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Route } from 'vitepress' - -export const hashRE = /#.*$/ -export const extRE = /(index)?\.(md|html)$/ -export const endingSlashRE = /\/$/ -export const outboundRE = /^[a-z]+:/i - -export function isNullish(value: any): value is null | undefined { - return value === null || value === undefined -} - -export function isArray(value: any): value is any[] { - return Array.isArray(value) -} - -export function isExternal(path: string): boolean { - return outboundRE.test(path) -} - -export function isActive(route: Route, path?: string): boolean { - if (path === undefined) { - return false - } - - const routePath = normalize(`/${route.data.relativePath}`) - const pagePath = normalize(path) - - return routePath === pagePath -} - -export function normalize(path: string): string { - return decodeURI(path).replace(hashRE, '').replace(extRE, '') -} - -export function joinUrl(base: string, path: string): string { - const baseEndsWithSlash = base.endsWith('/') - const pathStartsWithSlash = path.startsWith('/') - - if (baseEndsWithSlash && pathStartsWithSlash) { - return base.slice(0, -1) + path - } - - if (!baseEndsWithSlash && !pathStartsWithSlash) { - return `${base}/${path}` - } - - return base + path -} - -/** - * get the path without filename (the last segment). for example, if the given - * path is `/guide/getting-started.html`, this method will return `/guide/`. - * Always with a trailing slash. - */ -export function getPathDirName(path: string): string { - const segments = path.split('/') - - if (segments[segments.length - 1]) { - segments.pop() - } - - return ensureEndingSlash(segments.join('/')) -} - -export function ensureSlash(path: string): string { - return ensureEndingSlash(ensureStartingSlash(path)) -} - -export function ensureStartingSlash(path: string): string { - return /^\//.test(path) ? path : `/${path}` -} - -export function ensureEndingSlash(path: string): string { - return /(\.html|\/)$/.test(path) ? path : `${path}/` -} - -/** - * Remove `.md` or `.html` extention from the given path. It also converts - * `index` to slush. - */ -export function removeExtention(path: string): string { - return path.replace(/(index)?(\.(md|html))?$/, '') || '/' -} diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index 1220a29d..b00f54c4 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -1,10 +1,12 @@ { - "extends": "../tsconfig.json", + "extends": "../../tsconfig.json", "compilerOptions": { "baseUrl": ".", "outDir": "../../dist/client", "target": "esnext", "module": "esnext", + "declaration": true, + "declarationDir": "../../dist/client-types", "lib": ["ESNext", "DOM"], "types": ["vite/client"], "paths": { diff --git a/src/node/alias.ts b/src/node/alias.ts index 863e4f17..899aa71c 100644 --- a/src/node/alias.ts +++ b/src/node/alias.ts @@ -1,14 +1,17 @@ -import path from 'path' +import { createRequire } from 'module' +import { resolve, join } from 'path' +import { fileURLToPath } from 'url' import { Alias, AliasOptions } from 'vite' -const PKG_ROOT = path.join(__dirname, '../../') -export const DIST_CLIENT_PATH = path.join(__dirname, '../client') -export const APP_PATH = path.join(DIST_CLIENT_PATH, 'app') -export const SHARED_PATH = path.join(DIST_CLIENT_PATH, 'shared') -export const DEFAULT_THEME_PATH = path.join(DIST_CLIENT_PATH, 'theme-default') +const require = createRequire(import.meta.url) +const PKG_ROOT = resolve(fileURLToPath(import.meta.url), '../..') -// special virtual file -// we can't directly import '/@siteData' because +export const DIST_CLIENT_PATH = resolve(PKG_ROOT, 'client') +export const APP_PATH = join(DIST_CLIENT_PATH, 'app') +export const SHARED_PATH = join(DIST_CLIENT_PATH, 'shared') +export const DEFAULT_THEME_PATH = join(DIST_CLIENT_PATH, 'theme-default') + +// special virtual file. we can't directly import '/@siteData' because // - it's not an actual file so we can't use tsconfig paths to redirect it // - TS doesn't allow shimming a module that starts with '/' export const SITE_DATA_ID = '@siteData' @@ -39,16 +42,19 @@ export function resolveAliases(root: string, themeDir: string): AliasOptions { })), { find: /^vitepress$/, - replacement: path.join(__dirname, '../client/index') + replacement: join(DIST_CLIENT_PATH, '/index') }, { find: /^vitepress\/theme$/, - replacement: path.join(__dirname, '../client/theme-default/index') + replacement: join(DIST_CLIENT_PATH, '/theme-default/index') }, // alias for local linked development - { find: /^vitepress\//, replacement: PKG_ROOT + '/' }, - // make sure it always use the same vue dependency that comes with - // vitepress itself + { + find: /^vitepress\//, + replacement: PKG_ROOT + '/' + }, + // make sure it always use the same vue dependency that comes + // with vitepress itself { find: /^vue$/, replacement: vuePath diff --git a/src/node/build/build.ts b/src/node/build/build.ts index 6dd1d2b2..91c08742 100644 --- a/src/node/build/build.ts +++ b/src/node/build/build.ts @@ -1,11 +1,11 @@ import fs from 'fs-extra' -import { bundle, okMark, failMark } from './bundle' +import path from 'path' +import ora from 'ora' import { BuildOptions } from 'vite' +import { OutputChunk, OutputAsset } from 'rollup' import { resolveConfig } from '../config' import { renderPage } from './render' -import { OutputChunk, OutputAsset } from 'rollup' -import ora from 'ora' -import path from 'path' +import { bundle, okMark, failMark } from './bundle' export async function build( root: string, diff --git a/src/node/build/buildMPAClient.ts b/src/node/build/buildMPAClient.ts index 6d89d6cd..ca3c97be 100644 --- a/src/node/build/buildMPAClient.ts +++ b/src/node/build/buildMPAClient.ts @@ -1,6 +1,6 @@ import { build } from 'vite' -import { SiteConfig } from '..' import { RollupOutput } from 'rollup' +import { SiteConfig } from '..' const virtualEntry = 'client.js' diff --git a/src/node/build/bundle.ts b/src/node/build/bundle.ts index 7a5a46f1..ac849112 100644 --- a/src/node/build/bundle.ts +++ b/src/node/build/bundle.ts @@ -1,11 +1,11 @@ import ora from 'ora' import path from 'path' import fs from 'fs-extra' +import { build, BuildOptions, UserConfig as ViteUserConfig } from 'vite' +import { RollupOutput } from 'rollup' import { slash } from '../utils/slash' -import { APP_PATH } from '../alias' import { SiteConfig } from '../config' -import { RollupOutput } from 'rollup' -import { build, BuildOptions, UserConfig as ViteUserConfig } from 'vite' +import { APP_PATH } from '../alias' import { createVitePressPlugin } from '../plugin' import { buildMPAClient } from './buildMPAClient' @@ -41,11 +41,11 @@ export async function bundle( // resolve options to pass to vite const { rollupOptions } = options - const resolveViteConfig = (ssr: boolean): ViteUserConfig => ({ + const resolveViteConfig = async (ssr: boolean): Promise => ({ root: srcDir, base: config.site.base, logLevel: 'warn', - plugins: createVitePressPlugin( + plugins: await createVitePressPlugin( root, config, ssr, @@ -108,8 +108,8 @@ export async function bundle( spinner.start('building client + server bundles...') try { ;[clientResult, serverResult] = await (Promise.all([ - config.mpa ? null : build(resolveViteConfig(false)), - build(resolveViteConfig(true)) + config.mpa ? null : build(await resolveViteConfig(false)), + build(await resolveViteConfig(true)) ]) as Promise<[RollupOutput, RollupOutput]>) } catch (e) { spinner.stopAndPersist({ diff --git a/src/node/build/render.ts b/src/node/build/render.ts index 88fef643..aa5f217e 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -1,11 +1,15 @@ -import path from 'path' +import { createRequire } from 'module' import fs from 'fs-extra' -import { SiteConfig, resolveSiteDataByRoute } from '../config' -import { HeadConfig } from '../shared' +import path from 'path' +import { pathToFileURL } from 'url' +import escape from 'escape-html' import { normalizePath, transformWithEsbuild } from 'vite' import { RollupOutput, OutputChunk, OutputAsset } from 'rollup' +import { HeadConfig, createTitle } from '../shared' import { slash } from '../utils/slash' -import escape from 'escape-html' +import { SiteConfig, resolveSiteDataByRoute } from '../config' + +const require = createRequire(import.meta.url) export async function renderPage( config: SiteConfig, @@ -16,7 +20,9 @@ export async function renderPage( pageToHashMap: Record, hashMapString: string ) { - const { createApp } = require(path.join(config.tempDir, `app.js`)) + const { createApp } = await import( + pathToFileURL(path.join(config.tempDir, `app.js`)).toString() + ) const { app, router } = createApp() const routePath = `/${page.replace(/\.md$/, '')}` const siteData = resolveSiteDataByRoute(config.site, routePath) @@ -34,7 +40,9 @@ export async function renderPage( } // render page - const content = await require(rendererPath).renderToString(app) + const content = await import(pathToFileURL(rendererPath).toString()).then( + (r) => r.renderToString(app) + ) const pageName = page.replace(/\//g, '_') // server build doesn't need hash @@ -45,10 +53,9 @@ export async function renderPage( const pageClientJsFileName = `assets/${pageName}.${pageHash}.lean.js` // resolve page data so we can render head tags - const { __pageData } = require(path.join( - config.tempDir, - pageServerJsFileName - )) + const { __pageData } = await import( + pathToFileURL(path.join(config.tempDir, pageServerJsFileName)).toString() + ) const pageData = JSON.parse(__pageData) let preloadLinks = config.mpa @@ -92,10 +99,8 @@ export async function renderPage( ? `` : '' - const title: string = - pageData.title && pageData.title !== 'Home' - ? `${pageData.title} | ${siteData.title}` - : siteData.title + const title: string = createTitle(siteData, pageData) + const description: string = pageData.description || siteData.description const head = addSocialTags( title, @@ -127,9 +132,7 @@ export async function renderPage( ${title} - + ${stylesheetLink} ${preloadLinksString} ${prefetchLinkString} diff --git a/src/node/cli.ts b/src/node/cli.ts index 0a2cf4fb..b583b37a 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -1,10 +1,11 @@ -import chalk from 'chalk' +import c from 'picocolors' import minimist from 'minimist' import { createServer, build, serve } from '.' +import { version } from '../../package.json' const argv: any = minimist(process.argv.slice(2)) -console.log(chalk.cyan(`vitepress v${require('../../package.json').version}`)) +console.log(c.cyan(`vitepress v${version}`)) const command = argv._[0] const root = argv._[command ? 1 : 0] @@ -20,20 +21,20 @@ if (!command || command === 'dev') { server.printUrls() }) .catch((err) => { - console.error(chalk.red(`failed to start server. error:\n`), err) + console.error(c.red(`failed to start server. error:\n`), err) process.exit(1) }) } else if (command === 'build') { build(root, argv).catch((err) => { - console.error(chalk.red(`build error:\n`), err) + console.error(c.red(`build error:\n`), err) process.exit(1) }) } else if (command === 'serve') { serve(argv).catch((err) => { - console.error(chalk.red(`failed to start server. error:\n`), err) + console.error(c.red(`failed to start server. error:\n`), err) process.exit(1) }) } else { - console.log(chalk.red(`unknown command "${command}".`)) + console.log(c.red(`unknown command "${command}".`)) process.exit(1) } diff --git a/src/node/config.ts b/src/node/config.ts index a9277b85..c9bf2a14 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -1,7 +1,7 @@ import path from 'path' import fs from 'fs-extra' -import chalk from 'chalk' -import globby from 'globby' +import c from 'picocolors' +import fg from 'fast-glob' import { normalizePath, AliasOptions, @@ -14,8 +14,9 @@ import { SiteData, HeadConfig, LocaleConfig, - createLangDictionary, - DefaultTheme + DefaultTheme, + APPEARANCE_KEY, + createLangDictionary } from './shared' import { resolveAliases, DEFAULT_THEME_PATH } from './alias' import { MarkdownOptions } from './markdown/markdown' @@ -25,15 +26,15 @@ export { resolveSiteDataByRoute } from './shared' const debug = _debug('vitepress:config') -export type { MarkdownOptions } - export interface UserConfig { extends?: RawConfigExports - lang?: string base?: string + lang?: string title?: string + titleTemplate?: string | boolean description?: string head?: HeadConfig[] + appearance?: boolean themeConfig?: ThemeConfig locales?: Record markdown?: MarkdownOptions @@ -123,14 +124,14 @@ export async function resolveConfig( ? userThemeDir : DEFAULT_THEME_PATH - // Important: globby/fast-glob doesn't guarantee order of the returned files. + // Important: fast-glob doesn't guarantee order of the returned files. // We must sort the pages so the input list to rollup is stable across // builds - otherwise different input order could result in different exports // order in shared chunks which in turns invalidates the hash of every chunk! // JavaScript built-in sort() is mandated to be stable as of ES2019 and // supported in Node 12+, which is required by Vite. const pages = ( - await globby(['**.md'], { + await fg(['**.md'], { cwd: srcDir, ignore: ['**/node_modules', ...(userConfig.srcExclude || [])] }) @@ -188,7 +189,7 @@ async function resolveUserConfig( : {} if (configPath) { - debug(`loaded config at ${chalk.yellow(configPath)}`) + debug(`loaded config at ${c.yellow(configPath)}`) } else { debug(`no config file found.`) } @@ -243,15 +244,42 @@ export async function resolveSiteData( mode = 'development' ): Promise { userConfig = userConfig || (await resolveUserConfig(root, command, mode))[0] + return { lang: userConfig.lang || 'en-US', title: userConfig.title || 'VitePress', + titleTemplate: userConfig.titleTemplate, description: userConfig.description || 'A VitePress site', base: userConfig.base ? userConfig.base.replace(/([^/])$/, '$1/') : '/', - head: userConfig.head || [], + head: resolveSiteDataHead(userConfig), + appearance: userConfig.appearance ?? true, themeConfig: userConfig.themeConfig || {}, locales: userConfig.locales || {}, langs: createLangDictionary(userConfig), scrollOffset: userConfig.scrollOffset || 90 } } + +function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] { + const head = userConfig?.head ?? [] + + // add inline script to apply dark mode, if user enables the feature. + // this is required to prevent "flush" on initial page load. + if (userConfig?.appearance ?? true) { + head.push([ + 'script', + {}, + ` + ;(() => { + const saved = localStorage.getItem('${APPEARANCE_KEY}') + const prefereDark = window.matchMedia('(prefers-color-scheme: dark)').matches + if (!saved || saved === 'auto' ? prefereDark : saved === 'dark') { + document.documentElement.classList.add('dark') + } + })() + ` + ]) + } + + return head +} diff --git a/src/node/index.ts b/src/node/index.ts index bc08b91b..e2a6ddee 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -1,8 +1,8 @@ +export * from './config' export * from './server' +export * from './markdown/markdown' export * from './build/build' export * from './serve/serve' -export * from './config' -export * from './markdown/markdown' // shared types export type { diff --git a/src/node/markdown/markdown.ts b/src/node/markdown/markdown.ts index 46cbff35..1ed38025 100644 --- a/src/node/markdown/markdown.ts +++ b/src/node/markdown/markdown.ts @@ -1,4 +1,5 @@ import MarkdownIt from 'markdown-it' +import { Theme } from 'shiki' import { parseHeader } from '../utils/parseHeader' import { highlight } from './plugins/highlight' import { slugify } from './plugins/slugify' @@ -16,7 +17,9 @@ import { Header } from '../shared' import anchor from 'markdown-it-anchor' import attrs from 'markdown-it-attrs' import emoji from 'markdown-it-emoji' -import toc from 'markdown-it-table-of-contents' +import toc from 'markdown-it-toc-done-right' + +export type ThemeOptions = Theme | { light: Theme; dark: Theme } export interface MarkdownOptions extends MarkdownIt.Options { lineNumbers?: boolean @@ -28,8 +31,10 @@ export interface MarkdownOptions extends MarkdownIt.Options { leftDelimiter?: string rightDelimiter?: string allowedAttributes?: string[] + disable?: boolean } - // https://github.com/Oktavilla/markdown-it-table-of-contents + theme?: ThemeOptions + // https://github.com/nagaozen/markdown-it-toc-done-right toc?: any externalLinks?: Record } @@ -48,15 +53,15 @@ export interface MarkdownRenderer extends MarkdownIt { export type { Header } -export const createMarkdownRenderer = ( +export const createMarkdownRenderer = async ( srcDir: string, options: MarkdownOptions = {}, base: string -): MarkdownRenderer => { +): Promise => { const md = MarkdownIt({ html: true, linkify: true, - highlight, + highlight: await highlight(options.theme), ...options }) as MarkdownRenderer @@ -78,17 +83,23 @@ export const createMarkdownRenderer = ( }, base ) - // 3rd party plugins - .use(attrs, options.attrs) - .use(anchor, { - slugify, - permalink: anchor.permalink.ariaHidden({}), - ...options.anchor - }) + + // 3rd party plugins + if (!options.attrs?.disable) { + md.use(attrs, options.attrs) + } + + md.use(anchor, { + slugify, + permalink: anchor.permalink.ariaHidden({}), + ...options.anchor + }) .use(toc, { slugify, - includeLevel: [2, 3], - format: parseHeader, + level: [2, 3], + format: (x: string, htmlencode: (s: string) => string) => + htmlencode(parseHeader(x)), + listType: 'ul', ...options.toc }) .use(emoji) diff --git a/src/node/markdown/plugins/component.ts b/src/node/markdown/plugins/component.ts index accacb8d..6ca6b463 100644 --- a/src/node/markdown/plugins/component.ts +++ b/src/node/markdown/plugins/component.ts @@ -3,10 +3,101 @@ import { RuleBlock } from 'markdown-it/lib/parser_block' import blockNames from 'markdown-it/lib/common/html_blocks' import { HTML_OPEN_CLOSE_TAG_RE } from 'markdown-it/lib/common/html_re' -// Replacing the default htmlBlock rule to allow using custom components at -// root level +/** + * Vue reserved tags + * + * @see https://vuejs.org/api/built-in-components.html + */ +const vueReservedTags = [ + 'template', + 'component', + 'transition', + 'transition-group', + 'keep-alive', + 'slot', + 'teleport' +] + +/** + * According to markdown spec, all non-block html tags are treated as "inline" + * tags (wrapped with

    ), including those "unknown" tags. + * + * Therefore, markdown-it processes "inline" tags and "unknown" tags in the + * same way, and does not care if a tag is "inline" or "unknown". + * + * As we want to take those "unknown" tags as custom components, we should + * treat them as "block" tags. + * + * So we have to distinguish between "inline" and "unknown" tags ourselves. + * + * The inline tags list comes from MDN. + * + * @see https://spec.commonmark.org/0.29/#raw-html + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements + */ +const inlineTags = [ + 'a', + 'abbr', + 'acronym', + 'audio', + 'b', + 'bdi', + 'bdo', + 'big', + 'br', + 'button', + 'canvas', + 'cite', + 'code', + 'data', + 'datalist', + 'del', + 'dfn', + 'em', + 'embed', + 'i', + // iframe is treated as HTML blocks in markdown spec + // 'iframe', + 'img', + 'input', + 'ins', + 'kbd', + 'label', + 'map', + 'mark', + 'meter', + 'noscript', + 'object', + 'output', + 'picture', + 'progress', + 'q', + 'ruby', + 's', + 'samp', + 'script', + 'select', + 'slot', + 'small', + 'span', + 'strong', + 'sub', + 'sup', + 'svg', + 'template', + 'textarea', + 'time', + 'u', + 'tt', + 'var', + 'video', + 'wbr' +] -// An array of opening and corresponding closing sequences for html tags, +// replacing the default htmlBlock rule to allow using custom components at +// root level +// +// an array of opening and corresponding closing sequences for html tags, // last argument defines whether it can terminate a paragraph or not const HTML_SEQUENCES: [RegExp, RegExp, boolean][] = [ [/^<(script|pre|style)(?=(\s|>|$))/i, /<\/(script|pre|style)>/i, true], @@ -14,10 +105,24 @@ const HTML_SEQUENCES: [RegExp, RegExp, boolean][] = [ [/^<\?/, /\?>/, true], [/^/, true], [/^/, true], - // PascalCase Components - [/^<[A-Z]/, />/, true], - // custom elements with hyphens - [/^<\w+\-/, />/, true], + + // MODIFIED HERE: treat vue reserved tags as block tags + [ + new RegExp('^|$))', 'i'), + /^$/, + true + ], + + // MODIFIED HERE: treat unknown tags as block tags (custom components), + // excluding known inline tags + [ + new RegExp( + '^]' + ), + /^$/, + true + ], + [ new RegExp('^|$))', 'i'), /^$/, @@ -67,8 +172,7 @@ const htmlBlock: RuleBlock = (state, startLine, endLine, silent): boolean => { nextLine = startLine + 1 - // If we are here - we detected HTML block. - // Let's roll down till block end. + // if we are here - we detected HTML block. let's roll down till block end if (!HTML_SEQUENCES[i][1].test(lineText)) { for (; nextLine < endLine; nextLine++) { if (state.sCount[nextLine] < state.blkIndent) { diff --git a/src/node/markdown/plugins/containers.ts b/src/node/markdown/plugins/containers.ts index 9e3caa23..def665b2 100644 --- a/src/node/markdown/plugins/containers.ts +++ b/src/node/markdown/plugins/containers.ts @@ -6,7 +6,7 @@ export const containerPlugin = (md: MarkdownIt) => { md.use(...createContainer('tip', 'TIP')) .use(...createContainer('info', 'INFO')) .use(...createContainer('warning', 'WARNING')) - .use(...createContainer('danger', 'WARNING')) + .use(...createContainer('danger', 'DANGER')) .use(...createContainer('details', 'Details')) // explicitly escape Vue syntax .use(container, 'v-pre', { @@ -34,7 +34,7 @@ function createContainer(klass: string, defaultTitle: string): ContainerArgs { if (token.nesting === 1) { if (klass === 'details') { return `
    ${ - info ? `${info}` : '' + info ? `${info}` : `Details` }\n` } return `

    ${ diff --git a/src/node/markdown/plugins/highlight.ts b/src/node/markdown/plugins/highlight.ts index aab5f996..eb27b559 100644 --- a/src/node/markdown/plugins/highlight.ts +++ b/src/node/markdown/plugins/highlight.ts @@ -1,52 +1,28 @@ -import chalk from 'chalk' -import escapeHtml from 'escape-html' -import prism from 'prismjs' +import { getHighlighter } from 'shiki' +import type { ThemeOptions } from '../markdown' -// prism is listed as actual dep so it's ok to require -const loadLanguages = require('prismjs/components/index') +export async function highlight(theme: ThemeOptions = 'material-palenight') { + const themes = typeof theme === 'string' ? [theme] : [theme.dark, theme.light] + const highlighter = await getHighlighter({ themes }) + const preRE = /^/ -// required to make embedded highlighting work... -loadLanguages(['markup', 'css', 'javascript']) + return (str: string, lang: string) => { + lang = lang || 'text' -function wrap(code: string, lang: string): string { - if (lang === 'text') { - code = escapeHtml(code) - } - return `

    ${code}
    ` -} - -export const highlight = (str: string, lang: string) => { - if (!lang) { - return wrap(str, 'text') - } - lang = lang.toLowerCase() - const rawLang = lang - if (lang === 'vue' || lang === 'html') { - lang = 'markup' - } - if (lang === 'md') { - lang = 'markdown' - } - if (lang === 'ts') { - lang = 'typescript' - } - if (lang === 'py') { - lang = 'python' - } - if (!prism.languages[lang]) { - try { - loadLanguages([lang]) - } catch (e) { - console.warn( - chalk.yellow( - `[vitepress] Syntax highlight for language "${lang}" is not supported.` - ) - ) + if (typeof theme === 'string') { + return highlighter + .codeToHtml(str, { lang, theme }) + .replace(preRE, '
    ')
         }
    +
    +    const dark = highlighter
    +      .codeToHtml(str, { lang, theme: theme.dark })
    +      .replace(preRE, '
    ')
    +
    +    const light = highlighter
    +      .codeToHtml(str, { lang, theme: theme.light })
    +      .replace(preRE, '
    ')
    +
    +    return dark + light
       }
    -  if (prism.languages[lang]) {
    -    const code = prism.highlight(str, prism.languages[lang], lang)
    -    return wrap(code, rawLang)
    -  }
    -  return wrap(str, 'text')
     }
    diff --git a/src/node/markdown/plugins/highlightLines.ts b/src/node/markdown/plugins/highlightLines.ts
    index 28b18393..f7e5293a 100644
    --- a/src/node/markdown/plugins/highlightLines.ts
    +++ b/src/node/markdown/plugins/highlightLines.ts
    @@ -1,6 +1,7 @@
     // Modified from https://github.com/egoist/markdown-it-highlight-lines
     import MarkdownIt from 'markdown-it'
     
    +const RE = /{([\d,-]+)}/
     const wrapperRE = /^
    /
     
     export const highlightLinePlugin = (md: MarkdownIt) => {
    @@ -9,16 +10,34 @@ export const highlightLinePlugin = (md: MarkdownIt) => {
         const [tokens, idx, options] = args
         const token = tokens[idx]
     
    -    // due to use of markdown-it-attrs, the {0} syntax would have been converted
    -    // to attrs on the token
    +    // due to use of markdown-it-attrs, the {0} syntax would have been
    +    // converted to attrs on the token
         const attr = token.attrs && token.attrs[0]
    +
    +    let lines = null
    +
         if (!attr) {
    -      return fence(...args)
    +      // markdown-it-attrs maybe disabled
    +      const rawInfo = token.info
    +
    +      if (!rawInfo || !RE.test(rawInfo)) {
    +        return fence(...args)
    +      }
    +
    +      const langName = rawInfo.replace(RE, '').trim()
    +
    +      // ensure the next plugin get the correct lang
    +      token.info = langName
    +
    +      lines = RE.exec(rawInfo)![1]
         }
     
    -    const lines = attr[0]
    -    if (!lines || !/[\d,-]+/.test(lines)) {
    -      return fence(...args)
    +    if (!lines) {
    +      lines = attr![0]
    +
    +      if (!lines || !/[\d,-]+/.test(lines)) {
    +        return fence(...args)
    +      }
         }
     
         const lineNumbers = lines
    @@ -30,6 +49,7 @@ export const highlightLinePlugin = (md: MarkdownIt) => {
           : token.content
     
         const rawCode = code.replace(wrapperRE, '')
    +
         const highlightLinesCode = rawCode
           .split('\n')
           .map((split, index) => {
    diff --git a/src/node/markdown/plugins/image.ts b/src/node/markdown/plugins/image.ts
    index 641a04c7..59dadcc1 100644
    --- a/src/node/markdown/plugins/image.ts
    +++ b/src/node/markdown/plugins/image.ts
    @@ -10,6 +10,15 @@ export const imagePlugin = (md: MarkdownIt) => {
         if (url && !EXTERNAL_URL_RE.test(url) && !/^\.?\//.test(url)) {
           token.attrSet('src', './' + url)
         }
    +
    +    if (token.attrIndex('alt') && token.children != null) {
    +      token.attrs![token.attrIndex('alt')][1] = self.renderInlineAsText(
    +        token.children,
    +        options,
    +        env
    +      )
    +    }
    +
         return self.renderToken(tokens, idx, options)
       }
     }
    diff --git a/src/node/markdown/plugins/lineNumbers.ts b/src/node/markdown/plugins/lineNumbers.ts
    index 94a8f5ec..739b0222 100644
    --- a/src/node/markdown/plugins/lineNumbers.ts
    +++ b/src/node/markdown/plugins/lineNumbers.ts
    @@ -21,7 +21,7 @@ export const lineNumberPlugin = (md: MarkdownIt) => {
     
         const finalCode = rawCode
           .replace(/<\/div>$/, `${lineNumbersWrapperCode}
    `) - .replace(/"(language-\w+)"/, '"$1 line-numbers-mode"') + .replace(/"(language-\w*)"/, '"$1 line-numbers-mode"') return finalCode } diff --git a/src/node/markdown/plugins/preWrapper.ts b/src/node/markdown/plugins/preWrapper.ts index fcd06f17..9a23b797 100644 --- a/src/node/markdown/plugins/preWrapper.ts +++ b/src/node/markdown/plugins/preWrapper.ts @@ -15,6 +15,6 @@ export const preWrapperPlugin = (md: MarkdownIt) => { const [tokens, idx] = args const token = tokens[idx] const rawCode = fence(...args) - return `
    ${rawCode}
    ` + return `
    ${rawCode}
    ` } } diff --git a/src/node/markdownToVue.ts b/src/node/markdownToVue.ts index 22b9eb38..273d4dc1 100644 --- a/src/node/markdownToVue.ts +++ b/src/node/markdownToVue.ts @@ -1,14 +1,14 @@ import fs from 'fs' import path from 'path' +import c from 'picocolors' import matter from 'gray-matter' import LRUCache from 'lru-cache' -import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown' -import { deeplyParseHeader } from './utils/parseHeader' import { PageData, HeadConfig, EXTERNAL_URL_RE } from './shared' import { slash } from './utils/slash' -import chalk from 'chalk' -import _debug from 'debug' +import { deeplyParseHeader } from './utils/parseHeader' import { getGitTimestamp } from './utils/getGitTimestamp' +import { createMarkdownRenderer, MarkdownOptions } from './markdown/markdown' +import _debug from 'debug' const debug = _debug('vitepress:md') const cache = new LRUCache({ max: 1024 }) @@ -21,7 +21,7 @@ export interface MarkdownCompileResult { includes: string[] } -export function createMarkdownToVueRenderFn( +export async function createMarkdownToVueRenderFn( srcDir: string, options: MarkdownOptions = {}, pages: string[], @@ -30,7 +30,8 @@ export function createMarkdownToVueRenderFn( base: string, includeLastUpdatedData = false ) { - const md = createMarkdownRenderer(srcDir, options, base) + const md = await createMarkdownRenderer(srcDir, options, base) + pages = pages.map((p) => slash(p.replace(/\.md$/, ''))) const userDefineRegex = userDefines @@ -95,10 +96,8 @@ export function createMarkdownToVueRenderFn( const deadLinks: string[] = [] const recordDeadLink = (url: string) => { console.warn( - chalk.yellow( - `\n(!) Found dead link ${chalk.cyan(url)} in file ${chalk.white.dim( - file - )}` + c.yellow( + `\n(!) Found dead link ${c.cyan(url)} in file ${c.white(c.dim(file))}` ) ) deadLinks.push(url) @@ -134,6 +133,7 @@ export function createMarkdownToVueRenderFn( const pageData: PageData = { title: inferTitle(frontmatter, content), + titleTemplate: frontmatter.titleTemplate, description: inferDescription(frontmatter), frontmatter, headers: data.headers || [], @@ -188,26 +188,32 @@ function genPageDataCode(tags: string[], data: PageData) { defaultExportRE.test(tagSrc) || namedDefaultExportRE.test(tagSrc) tags[existingScriptIndex] = tagSrc.replace( scriptRE, - code + (hasDefaultExport ? `` : `\nexport default{}\n`) + `` + code + + (hasDefaultExport + ? `` + : `\nexport default {name:'${data.relativePath}'}`) + + `` ) } else { - tags.unshift(``) + tags.unshift( + `` + ) } return tags } -const inferTitle = (frontmatter: any, content: string) => { +const inferTitle = (frontmatter: Record, content: string) => { if (frontmatter.title) { return deeplyParseHeader(frontmatter.title) } - if (frontmatter.home) { - return 'Home' - } + const match = content.match(/^\s*#+\s+(.*)/m) + if (match) { return deeplyParseHeader(match[1].trim()) } + return '' } diff --git a/src/node/plugin.ts b/src/node/plugin.ts index efa8f1e6..af91a343 100644 --- a/src/node/plugin.ts +++ b/src/node/plugin.ts @@ -7,6 +7,8 @@ import { slash } from './utils/slash' import { OutputAsset, OutputChunk } from 'rollup' import { staticDataPlugin } from './staticDataPlugin' +type Awaited = T extends Promise ? P : never + const hashRE = /\.(\w+)\.js$/ const staticInjectMarkerRE = /\b(const _hoisted_\d+ = \/\*(?:#|@)__PURE__\*\/\s*createStaticVNode)\("(.*)", (\d+)\)/g @@ -28,7 +30,7 @@ const isPageChunk = ( chunk.facadeModuleId.endsWith('.md') ) -export function createVitePressPlugin( +export async function createVitePressPlugin( root: string, siteConfig: SiteConfig, ssr = false, @@ -46,13 +48,15 @@ export function createVitePressPlugin( pages } = siteConfig - let markdownToVue: ReturnType + let markdownToVue: Awaited> // lazy require plugin-vue to respect NODE_ENV in @vue/compiler-x - const vuePlugin = require('@vitejs/plugin-vue')({ - include: [/\.vue$/, /\.md$/], - ...userVuePluginOptions - }) + const vuePlugin = await import('@vitejs/plugin-vue').then((r) => + r.default({ + include: [/\.vue$/, /\.md$/], + ...userVuePluginOptions + }) + ) const processClientJS = (code: string, id: string) => { return scriptClientRE.test(code) @@ -70,9 +74,9 @@ export function createVitePressPlugin( const vitePressPlugin: Plugin = { name: 'vitepress', - configResolved(resolvedConfig) { + async configResolved(resolvedConfig) { config = resolvedConfig - markdownToVue = createMarkdownToVueRenderFn( + markdownToVue = await createMarkdownToVueRenderFn( srcDir, markdown, pages, @@ -89,9 +93,8 @@ export function createVitePressPlugin( alias }, define: { - __CARBON__: !!site.themeConfig.carbonAds?.carbon, - __BSA__: !!site.themeConfig.carbonAds?.custom, - __ALGOLIA__: !!site.themeConfig.algolia + __ALGOLIA__: !!site.themeConfig.algolia, + __CARBON__: !!site.themeConfig.carbonAds }, optimizeDeps: { // force include vue to avoid duplicated copies when linked + optimized diff --git a/src/node/serve/serve.ts b/src/node/serve/serve.ts index 778f7e72..3ec0bc3e 100644 --- a/src/node/serve/serve.ts +++ b/src/node/serve/serve.ts @@ -1,7 +1,7 @@ import sirv from 'sirv' import compression from 'compression' -import { resolveConfig } from '../config' import polka from 'polka' +import { resolveConfig } from '../config' function trimChar(str: string, char: string) { while (str.charAt(0) === char) { @@ -33,8 +33,8 @@ export async function serve(options: ServeOptions = {}) { immutable: true, setHeaders(res, pathname) { if (!pathname.includes('/assets/')) { - // force server validation for non-asset files since they are not - // fingerprinted. + // force server validation for non-asset files since they + // are not fingerprinted res.setHeader('cache-control', 'no-cache') } } diff --git a/src/node/server.ts b/src/node/server.ts index f0b7bd57..4f81fef4 100644 --- a/src/node/server.ts +++ b/src/node/server.ts @@ -12,7 +12,7 @@ export async function createServer( root: config.srcDir, base: config.site.base, // logLevel: 'warn', - plugins: createVitePressPlugin(root, config), + plugins: await createVitePressPlugin(root, config), server: serverOptions }) } diff --git a/src/node/shims.d.ts b/src/node/shims.d.ts index 097e3cc8..1cfe5a83 100644 --- a/src/node/shims.d.ts +++ b/src/node/shims.d.ts @@ -8,11 +8,6 @@ declare module 'markdown-it-emoji' { export default def } -declare module 'markdown-it-table-of-contents' { - const def: any - export default def -} - declare module 'markdown-it-container' { const def: any export default def @@ -23,16 +18,6 @@ declare module 'escape-html' { export default def } -declare module 'prismjs' { - const def: any - export default def -} - -declare module 'prismjs/components/index' { - const def: any - export default def -} - declare module 'diacritics' { export const remove: (str: string) => string } diff --git a/src/node/tsconfig.json b/src/node/tsconfig.json index 5681fb02..26db4671 100644 --- a/src/node/tsconfig.json +++ b/src/node/tsconfig.json @@ -1,10 +1,10 @@ { - "extends": "../tsconfig.json", + "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es2019", + "target": "es2020", "baseUrl": ".", "outDir": "../../dist/node", - "module": "commonjs", + "module": "esnext", "types": ["node"], "sourceMap": true }, diff --git a/src/node/utils/parseHeader.ts b/src/node/utils/parseHeader.ts index d1b40c80..b303e222 100644 --- a/src/node/utils/parseHeader.ts +++ b/src/node/utils/parseHeader.ts @@ -14,7 +14,7 @@ import emojiData from 'markdown-it-emoji/lib/data/full.json' const parseEmojis = (str: string) => { return str.replace( /:(.+?):/g, - (placeholder, key) => emojiData[key] || placeholder + (placeholder, key) => (emojiData as any)[key] || placeholder ) } diff --git a/src/shared/shared.ts b/src/shared/shared.ts index 64d78615..f296f026 100644 --- a/src/shared/shared.ts +++ b/src/shared/shared.ts @@ -1,4 +1,4 @@ -import { LocaleConfig, SiteData } from '../../types/shared' +import { SiteData, PageData, LocaleConfig } from '../../types/shared' export type { SiteData, @@ -10,6 +10,7 @@ export type { } from '../../types/shared' export const EXTERNAL_URL_RE = /^https?:/i +export const APPEARANCE_KEY = 'vitepress-theme-appearance' // @ts-ignore export const inBrowser = typeof window !== 'undefined' @@ -82,6 +83,36 @@ export function resolveSiteDataByRoute( }) } +/** + * Create the page title string based on configs. + */ +export function createTitle(siteData: SiteData, pageData: PageData): string { + const title = pageData.title || siteData.title + const template = pageData.titleTemplate ?? siteData.titleTemplate + const templateString = createTitleTemplate(siteData.title, template) + + return `${title}${templateString}` +} + +function createTitleTemplate( + siteTitle: string, + template?: string | boolean +): string { + if (template === false) { + return '' + } + + if (template === true || template === undefined) { + return ` | ${siteTitle}` + } + + if (siteTitle === template) { + return '' + } + + return ` | ${template}` +} + /** * Clean up the route by removing the `base` path if it's set in config. */ diff --git a/theme.d.ts b/theme.d.ts index e18d5bb6..f53cbc93 100644 --- a/theme.d.ts +++ b/theme.d.ts @@ -1,9 +1,14 @@ // so that users can do `import DefaultTheme from 'vitepress/theme'` import { ComponentOptions } from 'vue' -declare const defaultTheme: { +export const VPHomeHero = ComponentOptions +export const VPHomeFeatures = ComponentOptions +export const VPHomeSponsors = ComponentOptions +export const VPDocAsideSponsors = ComponentOptions + +declare const theme: { Layout: ComponentOptions NotFound: ComponentOptions } -export default defaultTheme +export default theme diff --git a/src/tsconfig.json b/tsconfig.json similarity index 58% rename from src/tsconfig.json rename to tsconfig.json index 23959e09..1ca3b78d 100644 --- a/src/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,14 @@ { "compilerOptions": { "module": "esnext", + "target": "esnext", "moduleResolution": "node", + "esModuleInterop": true, "strict": true, - "noUnusedLocals": true, "skipLibCheck": true, - "esModuleInterop": true, - "lib": ["ESNext"] + "noUnusedLocals": true, + "resolveJsonModule": true, + "lib": ["ESNext", "DOM"] }, - "include": ["../types/shared.d.ts"] + "exclude": ["**/node_modules/**", "**/dist/**"] } diff --git a/types/default-theme.d.ts b/types/default-theme.d.ts index 60a45fa7..2f44cd11 100644 --- a/types/default-theme.d.ts +++ b/types/default-theme.d.ts @@ -1,125 +1,195 @@ export namespace DefaultTheme { export interface Config { + /** + * The logo file of the site. + * + * @example '/logo.svg' + */ logo?: string - nav?: NavItem[] | false - sidebar?: SideBarConfig | MultiSideBarConfig /** - * GitHub repository following the format /. - * - * @example `"vuejs/vue-next"` + * Custom site title in navbar. If the value is undefined, + * `config.title` will be used. */ - repo?: string + siteTitle?: string | false /** - * Customize the header label. Defaults to GitHub/Gitlab/Bitbucket - * depending on the provided repo. - * - * @example `"Contribute!"` + * The nav items. */ - repoLabel?: string + nav?: NavItem[] /** - * If your docs are in a different repository from your main project. - * - * @example `"vuejs/docs-next"` + * The sidebar items. */ - docsRepo?: string + sidebar?: Sidebar /** - * If your docs are not at the root of the repo. - * - * @example `"docs"` + * Info for the edit link. If it's undefined, the edit link feature will + * be disabled. */ - docsDir?: string + editLink?: EditLink /** - * If your docs are in a different branch. Defaults to `master`. + * Set custom last updated text. * - * @example `"next"` + * @default 'Last updated' */ - docsBranch?: string + lastUpdatedText?: string /** - * Enable links to edit pages at the bottom of the page. + * The social links to be displayed at the end of the nav bar. Perfect for + * placing links to social services such as GitHub, Twitter, Facebook, etc. */ - editLinks?: boolean + socialLinks?: SocialLink[] /** - * Custom text for edit link. Defaults to "Edit this page". + * The footer configuration. */ - editLinkText?: string + footer?: Footer /** - * Show last updated time at the bottom of the page. Defaults to `false`. - * If given a string, it will be displayed as a prefix (default value: - * "Last Updated"). + * Adds locale menu to the nav. This option should be used when you have + * your translated sites outside of the project. */ - lastUpdated?: string | boolean - - prevLinks?: boolean - nextLinks?: boolean - - locales?: Record> + localeLinks?: LocaleLinks + /** + * The algolia options. Leave it undefined to disable the search feature. + */ algolia?: AlgoliaSearchOptions - carbonAds?: { - carbon: string - custom?: string - placement: string - } + /** + * The carbon ads options. Leave it undefined to disable the ads feature. + */ + carbonAds?: CarbonAdsOptions } - // navbar -------------------------------------------------------------------- + // nav ----------------------------------------------------------------------- export type NavItem = NavItemWithLink | NavItemWithChildren - export interface NavItemBase { + export type NavItemWithLink = { text: string - target?: string - rel?: string - ariaLabel?: string - activeMatch?: string - } - - export interface NavItemWithLink extends NavItemBase { link: string + + /** + * `activeMatch` is expected to be a regex string. We can't use actual + * RegExp object here because it isn't serializable + */ + activeMatch?: string } - export interface NavItemWithChildren extends NavItemBase { + export interface NavItemWithChildren { + text?: string items: NavItemWithLink[] } // sidebar ------------------------------------------------------------------- - export type SideBarConfig = SideBarItem[] | 'auto' | false + export type Sidebar = SidebarGroup[] | SidebarMulti - export interface MultiSideBarConfig { - [path: string]: SideBarConfig + export interface SidebarMulti { + [path: string]: SidebarGroup[] } - export type SideBarItem = SideBarLink | SideBarGroup + export interface SidebarGroup { + text: string + items: SidebarItem[] + + /** + * If `true`, toggle button is shown. + * + * @default false + */ + collapsible?: boolean - export interface SideBarLink { + /** + * If `true`, collapsible group is collapsed by default. + * + * @default false + */ + collapsed?: boolean + } + + export interface SidebarItem { text: string link: string } - export interface SideBarGroup { - text: string - link?: string + // edit link ----------------------------------------------------------------- + export interface EditLink { /** - * @default false + * Repo of the site. + * + * @example 'vuejs/docs' + */ + repo: string + + /** + * Branch of the repo. + * + * @default 'main' + */ + branch?: string + + /** + * If your docs are not at the root of the repo. + * + * @example 'docs' */ - collapsable?: boolean + dir?: string - children: SideBarItem[] + /** + * Custom text for edit link. + * + * @default 'Edit this page' + */ + text?: string } - // algolia ------------------------------------------------------------------ - // partially copied from @docsearch/react/dist/esm/DocSearch.d.ts + // social link --------------------------------------------------------------- + + export interface SocialLink { + icon: SocialLinkIcon + link: string + } + + export type SocialLinkIcon = + | 'discord' + | 'facebook' + | 'github' + | 'instagram' + | 'linkedin' + | 'slack' + | 'twitter' + | 'youtube' + + // footer -------------------------------------------------------------------- + + export interface Footer { + message?: string + copyright?: string + } + + // locales ------------------------------------------------------------------- + + export interface LocaleLinks { + text: string + items: LocaleLink[] + } + + export interface LocaleLink { + text: string + link: string + } + + // algolia ------------------------------------------------------------------ + + /** + * The Algolia search options. Partially copied from + * `@docsearch/react/dist/esm/DocSearch.d.ts` + */ export interface AlgoliaSearchOptions { appId: string apiKey: string @@ -130,17 +200,10 @@ export namespace DefaultTheme { initialQuery?: string } - // locales ------------------------------------------------------------------- - - export interface LocaleConfig { - /** - * Text for the language dropdown. - */ - selectText?: string + // carbon ads ---------------------------------------------------------------- - /** - * Label for this locale in the language dropdown. - */ - label?: string + export interface CarbonAdsOptions { + code: string + placement: string } } diff --git a/types/shared.d.ts b/types/shared.d.ts index 645c0114..d23dfae4 100644 --- a/types/shared.d.ts +++ b/types/shared.d.ts @@ -5,25 +5,38 @@ export { DefaultTheme } from './default-theme' export interface PageData { relativePath: string title: string + titleTemplate?: string | boolean description: string headers: Header[] frontmatter: Record lastUpdated?: number } +export interface Header { + level: number + title: string + slug: string +} + export interface SiteData { base: string + /** * Language of the site as it should be set on the `html` element. + * * @example `en-US`, `zh-CN` */ lang: string + title: string + titleTemplate?: string | boolean description: string head: HeadConfig[] + appearance: boolean themeConfig: ThemeConfig scrollOffset: number | string locales: Record + /** * Available locales for the site when it has defined `locales` in its * `themeConfig`. This object is otherwise empty. Keys are paths like `/` or @@ -50,15 +63,10 @@ export type HeadConfig = | [string, Record] | [string, Record, string] -export interface Header { - level: number - title: string - slug: string -} - export interface LocaleConfig { lang: string title?: string + titleTemplate?: string | boolean description?: string head?: HeadConfig[] label?: string