diff --git a/.github/workflows/cr-comment.yml b/.github/workflows/cr-comment.yml index d6b81790..203b7e11 100644 --- a/.github/workflows/cr-comment.yml +++ b/.github/workflows/cr-comment.yml @@ -13,7 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: cr-tracked - github_token: ${{ secrets.CR_PAT }} + - run: gh issue edit ${{ github.event.issue.number }} --add-label cr-tracked --repo ${{ github.repository }} + env: + GITHUB_TOKEN: ${{ secrets.CR_PAT }} diff --git a/.github/workflows/cr.yml b/.github/workflows/cr.yml index 545938c7..b8b833ef 100644 --- a/.github/workflows/cr.yml +++ b/.github/workflows/cr.yml @@ -13,12 +13,26 @@ on: - 'art/**' - 'docs/**' - '*.md' + push: + branches: [main] + paths-ignore: + - '.github/**' + - '__tests__/**' + - 'art/**' + - 'docs/**' + - '*.md' + tags-ignore: + - '*' permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }} + cancel-in-progress: true + jobs: release: - if: ${{ !github.event.pull_request.draft && contains(github.event.pull_request.labels.*.name, 'cr-tracked') && !contains(github.event.pull_request.labels.*.name, 'spam') && !contains(github.event.pull_request.labels.*.name, 'invalid') }} + if: ${{ ((github.event_name == 'pull_request' && !github.event.pull_request.draft && contains(github.event.pull_request.labels.*.name, 'cr-tracked') && !contains(github.event.pull_request.labels.*.name, 'spam') && !contains(github.event.pull_request.labels.*.name, 'invalid')) || (github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'release:'))) && github.repository == 'vuejs/vitepress' }} runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d4e59be..dc527e35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,5 @@ name: Test -env: - NODE_OPTIONS: --max-old-space-size=6144 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' - VITEST_SEGFAULT_RETRY: 3 - on: push: branches: [main] @@ -20,11 +15,15 @@ concurrency: jobs: test: - runs-on: ubuntu-latest - strategy: matrix: + os: [ubuntu-latest] node_version: [18, 20, 22] + include: + - os: windows-latest + node_version: 22 + + runs-on: ${{ matrix.os }} steps: - name: Checkout @@ -41,6 +40,8 @@ jobs: - name: Install deps run: pnpm install + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - name: Install Playwright run: pnpm playwright install chromium diff --git a/CHANGELOG.md b/CHANGELOG.md index 0affb2e8..ec6ee176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,172 @@ +## [2.0.0-alpha.3](https://github.com/vuejs/vitepress/compare/v2.0.0-alpha.2...v2.0.0-alpha.3) (2025-02-24) + +### Bug Fixes + +- **build:** `--minify` not working as documented ([9b5c037](https://github.com/vuejs/vitepress/commit/9b5c0377cd3474447c84b2901801287f3caf3d82)), closes [#4523](https://github.com/vuejs/vitepress/issues/4523) +- **build:** deterministic code group ids ([#4565](https://github.com/vuejs/vitepress/issues/4565)) ([b930b8d](https://github.com/vuejs/vitepress/commit/b930b8d5310f1691d8d9f009f45b70122e4ce800)) +- **markdown:** include content of all tokens in heading ids ([68dff2a](https://github.com/vuejs/vitepress/commit/68dff2af8547ae70f6622ac826affd76f2f6378e)), closes [#4561](https://github.com/vuejs/vitepress/issues/4561) +- **client:** set correct oldURL and newURL for hashchange ([#4573](https://github.com/vuejs/vitepress/issues/4573)) ([d1f2afd](https://github.com/vuejs/vitepress/commit/d1f2afdf0fbb022f12cc12295723b3b7c7ef5cb1)) +- **theme:** allow interactions behind scroll shadow ([#4537](https://github.com/vuejs/vitepress/issues/4537)) ([091d584](https://github.com/vuejs/vitepress/commit/091d5840ae15b64e04e8c07fbc0263a2749571bd)) +- **theme:** code block contrast ratio ([#4487](https://github.com/vuejs/vitepress/issues/4487)) ([5dccaee](https://github.com/vuejs/vitepress/commit/5dccaeef055beb109919f8990032975a0d630384)) +- **build:** fix flaky embedded languages highlighting ([#4566](https://github.com/vuejs/vitepress/issues/4566)) ([1969cf4](https://github.com/vuejs/vitepress/commit/1969cf4f3b93ad105595e4e2f8b030b04eb1c975)) + +### Features + +- **cli:** support custom `srcDir` ([#4270](https://github.com/vuejs/vitepress/issues/4270)) ([518c094](https://github.com/vuejs/vitepress/commit/518c0945f159aae679ef710bb48ae3ab3891cc9f)) +- **cli:** support custom npm scripts prefix ([#4271](https://github.com/vuejs/vitepress/issues/4271)) ([e5a0ee8](https://github.com/vuejs/vitepress/commit/e5a0ee8161752a77c5bb9546245a940cb5f28fb8)) +- **build:** dynamic routes plugin overhaul ([#4525](https://github.com/vuejs/vitepress/issues/4525)) ([a62ea6a](https://github.com/vuejs/vitepress/commit/a62ea6a832a33b756642b24ad5d38c248e08b554)) +- **build:** update to shiki v3 ([#4571](https://github.com/vuejs/vitepress/issues/4571)) ([52c2aa1](https://github.com/vuejs/vitepress/commit/52c2aa178d4b3fa98b863cf28f0ccf6d2aabcd93)) +- **build:** use `markdown-it-async`, remove `synckit` ([#4507](https://github.com/vuejs/vitepress/issues/4507)) ([8062235](https://github.com/vuejs/vitepress/commit/80622356f1d648577ee47ee3a44b04bb015ee462)) + +### BREAKING CHANGES + +- markdown-it-async is used instead of markdown-it. If you're using custom content renderer for local search, you'll need to do `await md.renderAsync` instead of `md.render`. +- Internals are modified a bit to better support vite 6 and handle HMR more correctly. For most users this won't need any change on their side. +- shiki is upgraded to v3. There shouldn't be any breaking change but if you see any issue, please report it. + +## [2.0.0-alpha.2](https://github.com/vuejs/vitepress/compare/v2.0.0-alpha.1...v2.0.0-alpha.2) (2025-01-23) + +### Bug Fixes + +- fix docsearch navigation and rendering ([e035027](https://github.com/vuejs/vitepress/commit/e0350275b39258a61ee867840ce1c6f5b2cecf2a)) +- **types:** support preload built-in shiki languages as string ([#4513](https://github.com/vuejs/vitepress/issues/4513)) ([4f77b4f](https://github.com/vuejs/vitepress/commit/4f77b4fdfdbe945e482348a57731bff5fb4672fc)) + +### Features + +- allow `markdown.config` and `markdown.preConfig` to accept async function ([#4512](https://github.com/vuejs/vitepress/issues/4512)) ([b88ae8d](https://github.com/vuejs/vitepress/commit/b88ae8d4a11a20104b2007c2631eb7aeb123d965)) +- support same page navigation in `router.go` and expose decoded hash and query from the `route` object ([#4511](https://github.com/vuejs/vitepress/issues/4511)) ([23d3281](https://github.com/vuejs/vitepress/commit/23d3281ed6f1111ab15708ca1fd86202674f8ef7)) + +## [2.0.0-alpha.1](https://github.com/vuejs/vitepress/compare/v1.6.2...v2.0.0-alpha.1) (2025-01-22) + +### Features + +- upgrade vite to v6 ([#4504](https://github.com/vuejs/vitepress/issues/4504)) ([6a2efc3](https://github.com/vuejs/vitepress/commit/6a2efc385c90b088241db05f5263b2f3e1f757cf)) + +## [1.6.3](https://github.com/vuejs/vitepress/compare/v1.6.2...v1.6.3) (2025-01-22) + +### Bug Fixes + +- docsearch not rendering properly ([3e4120e](https://github.com/vuejs/vitepress/commit/3e4120e94805156bf63587fd633162433dbaf260)) + +## [1.6.2](https://github.com/vuejs/vitepress/compare/v1.6.1...v1.6.2) (2025-01-22) + +### Bug Fixes + +- fix static content removal for lean chunks due to Vue 3.5 changes ([#4508](https://github.com/vuejs/vitepress/issues/4508)) ([8214cae](https://github.com/vuejs/vitepress/commit/8214cae21bb16842d8870d5867e974146c51fd61)) + +## [1.6.1](https://github.com/vuejs/vitepress/compare/v1.6.0...v1.6.1) (2025-01-20) + +### Bug Fixes + +- **build:** escape `$` in replace pattern in dynamic routes plugin ([e812916](https://github.com/vuejs/vitepress/commit/e8129167c76104d59d31a77b16dff3458e6af5eb)), closes [#4499](https://github.com/vuejs/vitepress/issues/4499) +- **theme/regression:** broken hero heading at certain viewports ([37dbe89](https://github.com/vuejs/vitepress/commit/37dbe895d4cf813e6eb1289f24c637945eec0d1f)) + +# [1.6.0](https://github.com/vuejs/vitepress/compare/v1.5.0...v1.6.0) (2025-01-20) + +### Bug Fixes + +- **build:** out of order css in prod builds ([241d17d](https://github.com/vuejs/vitepress/commit/241d17d9839f06b17c3898b1a8ba0f9fa12da0d1)), closes [#4098](https://github.com/vuejs/vitepress/issues/4098) +- **build:** properly strip vpi-social css declaration in debug mode ([c61182a](https://github.com/vuejs/vitepress/commit/c61182ab278350699b5d50461788478a340790aa)) +- **build:** respect `vite.clearScreen` in build ([8ea776a](https://github.com/vuejs/vitepress/commit/8ea776addc2c3bcabf3c707a9a81d6e0080a8fcb)), closes [#4468](https://github.com/vuejs/vitepress/issues/4468) +- **build:** specify mode for iconify ([8a5e8ea](https://github.com/vuejs/vitepress/commit/8a5e8ea4f5b7cba0a6c909d8949f0c20426104a6)) +- **theme:** apply `externalLinkIcon` option on `VPHome` ([#4492](https://github.com/vuejs/vitepress/issues/4492)) ([fe48943](https://github.com/vuejs/vitepress/commit/fe48943640895d859811b81f86d78c3e510dbe54)) +- **theme:** don't show external link icon for images ([096bba1](https://github.com/vuejs/vitepress/commit/096bba19fb61c4b2f8f527046b4b0fe2e91c6bd6)) +- **theme:** ignore footnote-ref for outline ([1832617](https://github.com/vuejs/vitepress/commit/183261753b04c2c96ddb8c10e520c748c6d3e613)), closes [#4402](https://github.com/vuejs/vitepress/issues/4402) +- **theme:** includes text to h1 tag for hero page ([#4472](https://github.com/vuejs/vitepress/issues/4472)) ([bd896c6](https://github.com/vuejs/vitepress/commit/bd896c638f8046f6546b5b32e8f98f3707aa8d05)), closes [#4453](https://github.com/vuejs/vitepress/issues/4453) + +### Features + +- **build:** export normalize function from shared chunk ([616f63f](https://github.com/vuejs/vitepress/commit/616f63f5f08a57347f2800e2d147d5bcd1cd072d)), closes [#4401](https://github.com/vuejs/vitepress/issues/4401) +- **theme:** allow customizing skip to content label ([ff254dc](https://github.com/vuejs/vitepress/commit/ff254dcbe6f2bcc89c34d2d2f4050229dc094400)), closes [#4288](https://github.com/vuejs/vitepress/issues/4288) +- **theme:** export VPNavBarSearch ([23522ab](https://github.com/vuejs/vitepress/commit/23522ab83ff33802d382fa066578dd87eb06789d)), closes [#4476](https://github.com/vuejs/vitepress/issues/4476) +- **theme:** export VPFeatures ([#4356](https://github.com/vuejs/vitepress/issues/4356)) ([6442e17](https://github.com/vuejs/vitepress/commit/6442e174838aec9668325bb1199419908e7dd728)) + +### Miscellaneous + +- **build:** shiki transformers now use v3 [matching algorithm](https://shiki.style/packages/transformers#matching-algorithm) ([373f9b9](https://github.com/vuejs/vitepress/commit/373f9b933ee44f33a15ebdcfcb6db6dfac52f739)) + +# [1.5.0](https://github.com/vuejs/vitepress/compare/v1.4.5...v1.5.0) (2024-11-04) + +### Features + +- on-demand social icons ([#4339](https://github.com/vuejs/vitepress/issues/4339)) ([05f2f0d](https://github.com/vuejs/vitepress/commit/05f2f0d26153ace74b6c023184224d4fada137c2)), closes [#4256](https://github.com/vuejs/vitepress/issues/4256) [#4135](https://github.com/vuejs/vitepress/issues/4135) [#4076](https://github.com/vuejs/vitepress/issues/4076) [#3809](https://github.com/vuejs/vitepress/issues/3809) [#3750](https://github.com/vuejs/vitepress/issues/3750) [#1214](https://github.com/vuejs/vitepress/issues/1214) [#2768](https://github.com/vuejs/vitepress/issues/2768) [#2861](https://github.com/vuejs/vitepress/issues/2861) + +## [1.4.5](https://github.com/vuejs/vitepress/compare/v1.4.4...v1.4.5) (2024-11-03) + +### Bug Fixes + +- lang lazy load not working with twoslash ([fc92a77](https://github.com/vuejs/vitepress/commit/fc92a77a5d871b5252bcb82639f5c3551d5c70bb)), closes [#4334](https://github.com/vuejs/vitepress/issues/4334) +- typo in missing language check ([dcb8450](https://github.com/vuejs/vitepress/commit/dcb8450f1166d7731c26a0eb5ec6d931bc283172)) + +## [1.4.3](https://github.com/vuejs/vitepress/compare/v1.4.2...v1.4.3) (2024-10-31) + +### Performance Improvements + +- lazy load shiki languages ([#4326](https://github.com/vuejs/vitepress/issues/4326)) ([8299778](https://github.com/vuejs/vitepress/commit/829977876a21da4f0af5d27593a2d81eb9af0c33)) + +## [1.4.2](https://github.com/vuejs/vitepress/compare/v1.4.1...v1.4.2) (2024-10-29) + +### Bug Fixes + +- cache markdown-it instance and properly dispose shiki on config reload ([#4321](https://github.com/vuejs/vitepress/issues/4321)) ([45968cd](https://github.com/vuejs/vitepress/commit/45968cdc509e04f8e191d28ba7d8d97b08fc578e)) ([acfe97f](https://github.com/vuejs/vitepress/commit/acfe97f60872d251c75c5985ca9841f84392850d)) +- **regression:** hmr not working with markdown includes due to wrong cache key ([615aed5](https://github.com/vuejs/vitepress/commit/615aed5df700ad98f82a74fbc785d290d1c5a018)), closes [#4289](https://github.com/vuejs/vitepress/issues/4289) [#4303](https://github.com/vuejs/vitepress/issues/4303) +- remove explicit chinese fonts ([#4286](https://github.com/vuejs/vitepress/issues/4286)) ([668e9f7](https://github.com/vuejs/vitepress/commit/668e9f7050f236d571fe2fc2f41e5548d974b503)) ([b893550](https://github.com/vuejs/vitepress/commit/b8935502fbb590449e7d094bdde9c9ae1ac67d0c)), closes [#4286](https://github.com/vuejs/vitepress/issues/4286) +- **theme/a11y:** don't select search result unless mouse is actually moved ([e638d85](https://github.com/vuejs/vitepress/commit/e638d855cfc8e4e0c9b95c284548ae31233139f5)), closes [#4297](https://github.com/vuejs/vitepress/issues/4297) +- **theme:** add types for `VPLink`, `VPSocialLink` and `VPSocialLinks` exports ([#4284](https://github.com/vuejs/vitepress/issues/4284)) ([fcae4d5](https://github.com/vuejs/vitepress/commit/fcae4d5554df2130b9a7e5ad8e0cc83eccf88bec)) +- **theme:** don't escape html in `siteTitle` ([#4308](https://github.com/vuejs/vitepress/issues/4308)) ([bd690d6](https://github.com/vuejs/vitepress/commit/bd690d6a9b895f15d5906d245b404f02cfce7489)) + +## [1.4.1](https://github.com/vuejs/vitepress/compare/v1.4.0...v1.4.1) (2024-10-13) + +### Bug Fixes + +- broken rewrites on windows ([#4268](https://github.com/vuejs/vitepress/issues/4268)) ([b46d6d3](https://github.com/vuejs/vitepress/commit/b46d6d3a204f5ce347647bfd1ab8073bf313afd6)) +- **client:** use `usePreferredDark` with `appearance: "force-auto"` ([#4263](https://github.com/vuejs/vitepress/issues/4263)) ([3e8fc40](https://github.com/vuejs/vitepress/commit/3e8fc40c3621da1ef35645d376dab7765b35bb40)) +- **client:** wrong script async check ([461a5b0](https://github.com/vuejs/vitepress/commit/461a5b001d29f95169f60fe28bc610e3f6e8fd66)) +- **theme:** bind missing no icon prop in the menu link component [#4260](https://github.com/vuejs/vitepress/issues/4260) ([b96712c](https://github.com/vuejs/vitepress/commit/b96712c0744f9ac7ebd65cf4087b2e9fd0d6762b)) +- **theme:** improve local search input a11y ([#4066](https://github.com/vuejs/vitepress/issues/4066)) ([92b92ae](https://github.com/vuejs/vitepress/commit/92b92aefcab9fbb28b51da70ee8ab21724098277)) + +### Features + +- **experimental:** support passing function for rewrites ([#4274](https://github.com/vuejs/vitepress/issues/4274)) ([8436472](https://github.com/vuejs/vitepress/commit/8436472c7874cb16caf9432660b395ca9ba68f9d)) + +# [1.4.0](https://github.com/vuejs/vitepress/compare/v1.3.4...v1.4.0) (2024-10-07) + +### Bug Fixes + +- `vueRE` conflicting with `lineNoRE` ([#4247](https://github.com/vuejs/vitepress/issues/4247)) ([2ac64b8](https://github.com/vuejs/vitepress/commit/2ac64b8d4180f2a7c54fda57df7f3a0a52488d62)) +- hmr not updating page data in rewritten paths and file path is wrong in mdit for dynamic routes ([c46e4b7](https://github.com/vuejs/vitepress/commit/c46e4b784ddb9ce3bd1cfcc3de1d1d676535cb5b)), closes [#4172](https://github.com/vuejs/vitepress/issues/4172) +- remove font synthesis in webfont mode, google fonts now support italic axis in inter ([1628918](https://github.com/vuejs/vitepress/commit/1628918f30b5602b83c51a2a8f4ec5e773cf7445)) +- **theme:** change the order of CSS rules of `VPFlyout` ([#4225](https://github.com/vuejs/vitepress/issues/4225)) ([68150a6](https://github.com/vuejs/vitepress/commit/68150a6f3349c1741ed5683e3010d9ecea02f3a8)), closes [#4224](https://github.com/vuejs/vitepress/issues/4224) +- **theme:** respect custom tag prop in VPButton component ([#4185](https://github.com/vuejs/vitepress/issues/4185)) ([9c5d348](https://github.com/vuejs/vitepress/commit/9c5d348c034eb6773562c93cad699d287051aa7b)) + +### Features + +- add `data-title` attribute for code group label tag ([#4152](https://github.com/vuejs/vitepress/issues/4152)) ([bc7271d](https://github.com/vuejs/vitepress/commit/bc7271d258047feb8a39c97ebc5e2a16bf899bb5)) +- allow ignoring certain headers and their subtrees completely in outline ([3e11b6a](https://github.com/vuejs/vitepress/commit/3e11b6abf5fbe80c2bc733f590ab57c7b2cc06f2)), closes [#4171](https://github.com/vuejs/vitepress/issues/4171) +- **client:** add `onAfterPageLoad` hook in router ([#4126](https://github.com/vuejs/vitepress/issues/4126)) ([315c220](https://github.com/vuejs/vitepress/commit/315c22004993f6f1cbdbb59178e46745d8e505a6)) +- support adding extra attributes to snippet imports (useful for twoslash) ([#4100](https://github.com/vuejs/vitepress/issues/4100)) ([e8f7dd1](https://github.com/vuejs/vitepress/commit/e8f7dd16f6139fdfd129b86caff4b6613dd1e887)) +- **theme:** expose theme default VPLink & VPSocialLink(s) component ([#4178](https://github.com/vuejs/vitepress/issues/4178)) ([615e33b](https://github.com/vuejs/vitepress/commit/615e33bb24d5005574af971ffcf1f41d751a855c)) +- trigger `onContentUpdated` on frontmatter-only changes too ([0db269a](https://github.com/vuejs/vitepress/commit/0db269a4c5d90ecf69f0219982cdf8f335e787ce)) + +## [1.3.4](https://github.com/vuejs/vitepress/compare/v1.3.3...v1.3.4) (2024-08-24) + +### Bug Fixes + +- check if `_importGlobMap` (vite internal) exists before using it ([612d66f](https://github.com/vuejs/vitepress/commit/612d66fbb5162d9905cfb10919ca1761ce8c4680)) + +## [1.3.3](https://github.com/vuejs/vitepress/compare/v1.3.2...v1.3.3) (2024-08-17) + +### Miscellaneous + +- bump deps ([a20db24](https://github.com/vuejs/vitepress/commit/a20db247822438ac4e0e76bc4a2b4ee2f5d94940)) + +## [1.3.2](https://github.com/vuejs/vitepress/compare/v1.3.1...v1.3.2) (2024-08-05) + +### Bug Fixes + +- multiple cache busting imports causing useData to fail ([2b3e486](https://github.com/vuejs/vitepress/commit/2b3e486ab913ff77707410b9cee3ba6d256ccc95)), closes [#3820](https://github.com/vuejs/vitepress/issues/3820), reverts [#3398](https://github.com/vuejs/vitepress/issues/3398), reopens [#3363](https://github.com/vuejs/vitepress/issues/3363) +- **theme:** excerpt style in LocalSearchBox ([#4050](https://github.com/vuejs/vitepress/issues/4050)) ([2bc0d39](https://github.com/vuejs/vitepress/commit/2bc0d39d5089841986f0988fc9cfe15533d3a0c6)) + ## [1.3.1](https://github.com/vuejs/vitepress/compare/v1.3.0...v1.3.1) (2024-07-14) ### Bug Fixes diff --git a/README.md b/README.md index 2e70a766..134226a4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![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) +[![nightly releases](https://img.shields.io/badge/nightly-releases-orange)](https://nightly.akryum.dev/vuejs/vitepress) [![chat](https://img.shields.io/badge/chat-discord-blue?logo=discord)](https://chat.vuejs.org) --- diff --git a/__tests__/e2e/.vitepress/config.ts b/__tests__/e2e/.vitepress/config.ts index 65f845ab..121761e7 100644 --- a/__tests__/e2e/.vitepress/config.ts +++ b/__tests__/e2e/.vitepress/config.ts @@ -165,13 +165,21 @@ export default defineConfig({ search: { provider: 'local', options: { - _render(src, env, md) { - const html = md.render(src, env) + async _render(src, env, md) { + const html = await md.renderAsync(src, env) if (env.frontmatter?.search === false) return '' if (env.relativePath.startsWith('local-search/excluded')) return '' return html } } } + }, + vite: { + server: { + watch: { + usePolling: true, + interval: 100 + } + } } }) diff --git a/__tests__/e2e/data-loading/basic.data.mts b/__tests__/e2e/data-loading/basic.data.mts index 1a70d74a..56aab77e 100644 --- a/__tests__/e2e/data-loading/basic.data.mts +++ b/__tests__/e2e/data-loading/basic.data.mts @@ -1,4 +1,4 @@ -import fs from 'fs' +import fs from 'node:fs' import { defineLoader } from 'vitepress' type Data = Record[] @@ -7,14 +7,10 @@ export declare const data: Data export default defineLoader({ watch: ['./data/*'], async load(files: string[]): Promise { - const foo = fs.readFileSync( - files.find((f) => f.endsWith('foo.json'))!, - 'utf-8' - ) - const bar = fs.readFileSync( - files.find((f) => f.endsWith('bar.json'))!, - 'utf-8' - ) - return [JSON.parse(foo), JSON.parse(bar)] + const data: Data = [] + for (const file of files.sort().filter((file) => file.endsWith('.json'))) { + data.push(JSON.parse(fs.readFileSync(file, 'utf-8'))) + } + return data } }) diff --git a/__tests__/e2e/data-loading/data.test.ts b/__tests__/e2e/data-loading/data.test.ts index f12e9766..fafc403a 100644 --- a/__tests__/e2e/data-loading/data.test.ts +++ b/__tests__/e2e/data-loading/data.test.ts @@ -1,3 +1,6 @@ +import fs from 'node:fs/promises' +import { fileURLToPath } from 'node:url' + describe('static data file support in vite 3', () => { beforeAll(async () => { await goto('/data-loading/data') @@ -7,10 +10,10 @@ describe('static data file support in vite 3', () => { expect(await page.textContent('pre#basic')).toMatchInlineSnapshot(` "[ { - "foo": true + "a": true }, { - "bar": true + "b": true } ]" `) @@ -39,4 +42,67 @@ describe('static data file support in vite 3', () => { ]" `) }) + + test.runIf(!process.env.VITE_TEST_BUILD)('hmr works', async () => { + const a = fileURLToPath(new URL('./data/a.json', import.meta.url)) + const b = fileURLToPath(new URL('./data/b.json', import.meta.url)) + + try { + await fs.writeFile(a, JSON.stringify({ a: false }, null, 2) + '\n') + await page.waitForFunction( + () => + document.querySelector('pre#basic')?.textContent === + JSON.stringify([{ a: false }, { b: true }], null, 2), + undefined, + { timeout: 3000 } + ) + } finally { + await fs.writeFile(a, JSON.stringify({ a: true }, null, 2) + '\n') + } + + let err = true + + try { + await fs.unlink(b) + await page.waitForFunction( + () => + document.querySelector('pre#basic')?.textContent === + JSON.stringify([{ a: true }], null, 2), + undefined, + { timeout: 3000 } + ) + err = false + } finally { + if (err) { + await fs.writeFile(b, JSON.stringify({ b: true }, null, 2) + '\n') + } + } + + try { + await fs.writeFile(b, JSON.stringify({ b: false }, null, 2) + '\n') + await page.waitForFunction( + () => + document.querySelector('pre#basic')?.textContent === + JSON.stringify([{ a: true }, { b: false }], null, 2), + undefined, + { timeout: 3000 } + ) + } finally { + await fs.writeFile(b, JSON.stringify({ b: true }, null, 2) + '\n') + } + }) + + /* + MODIFY a.json with { a: false } + this should trigger a hmr update and the content should be updated to [{ a: false }, { b: true }] + reset a.json + + DELETE b.json + this should trigger a hmr update and the content should be updated to [{ a: true }] + reset b.json if failed + + CREATE b.json with { b: false } + this should trigger a hmr update and the content should be updated to [{ a: true }, { b: false }] + reset b.json + */ }) diff --git a/__tests__/e2e/data-loading/data/a.json b/__tests__/e2e/data-loading/data/a.json new file mode 100644 index 00000000..eb174f2f --- /dev/null +++ b/__tests__/e2e/data-loading/data/a.json @@ -0,0 +1,3 @@ +{ + "a": true +} diff --git a/__tests__/e2e/data-loading/data/b.json b/__tests__/e2e/data-loading/data/b.json new file mode 100644 index 00000000..d44f4ab8 --- /dev/null +++ b/__tests__/e2e/data-loading/data/b.json @@ -0,0 +1,3 @@ +{ + "b": true +} diff --git a/__tests__/e2e/data-loading/data/bar.json b/__tests__/e2e/data-loading/data/bar.json deleted file mode 100644 index 01309803..00000000 --- a/__tests__/e2e/data-loading/data/bar.json +++ /dev/null @@ -1 +0,0 @@ -{ "bar": true } diff --git a/__tests__/e2e/data-loading/data/foo.json b/__tests__/e2e/data-loading/data/foo.json deleted file mode 100644 index 46a10aa7..00000000 --- a/__tests__/e2e/data-loading/data/foo.json +++ /dev/null @@ -1 +0,0 @@ -{ "foo": true } diff --git a/__tests__/e2e/dynamic-routes/[id].paths.ts b/__tests__/e2e/dynamic-routes/[id].paths.ts index 3eca4d91..12a8bc32 100644 --- a/__tests__/e2e/dynamic-routes/[id].paths.ts +++ b/__tests__/e2e/dynamic-routes/[id].paths.ts @@ -1,8 +1,14 @@ -export default { - async paths() { - return [ - { params: { id: 'foo' }, content: `# Foo` }, - { params: { id: 'bar' }, content: `# Bar` } - ] +import { defineRoutes } from 'vitepress' +import paths from './paths' + +export default defineRoutes({ + async paths(watchedFiles: string[]) { + // console.log('watchedFiles', watchedFiles) + return paths + }, + watch: ['**/data-loading/**/*.json'], + async transformPageData(pageData) { + // console.log('transformPageData', pageData.filePath) + pageData.title += ' - transformed' } -} +}) diff --git a/__tests__/e2e/dynamic-routes/paths.ts b/__tests__/e2e/dynamic-routes/paths.ts new file mode 100644 index 00000000..5adb0ed4 --- /dev/null +++ b/__tests__/e2e/dynamic-routes/paths.ts @@ -0,0 +1,4 @@ +export default [ + { params: { id: 'foo' }, content: `# Foo` }, + { params: { id: 'bar' }, content: `# Bar` } +] diff --git a/__tests__/e2e/vitestGlobalSetup.ts b/__tests__/e2e/vitestGlobalSetup.ts index 97eb114c..74596801 100644 --- a/__tests__/e2e/vitestGlobalSetup.ts +++ b/__tests__/e2e/vitestGlobalSetup.ts @@ -1,8 +1,8 @@ import getPort from 'get-port' +import type { Server } from 'node:net' import { chromium, type BrowserServer } from 'playwright-chromium' -import { build, createServer, serve } from 'vitepress' import type { ViteDevServer } from 'vite' -import type { Server } from 'net' +import { build, createServer, serve } from 'vitepress' let browserServer: BrowserServer let server: ViteDevServer | Server diff --git a/__tests__/init/init.test.ts b/__tests__/init/init.test.ts index f4c9956b..7766b060 100644 --- a/__tests__/init/init.test.ts +++ b/__tests__/init/init.test.ts @@ -1,9 +1,9 @@ import fs from 'fs-extra' import getPort from 'get-port' import { nanoid } from 'nanoid' -import path from 'path' +import path from 'node:path' +import { fileURLToPath, URL } from 'node:url' import { chromium } from 'playwright-chromium' -import { fileURLToPath, URL } from 'url' import { createServer, scaffold, ScaffoldThemeType } from 'vitepress' const tempDir = fileURLToPath(new URL('./.temp', import.meta.url)) diff --git a/__tests__/unit/client/theme-default/composables/outline.test.ts b/__tests__/unit/client/theme-default/composables/outline.test.ts index 06180f1a..af659c9f 100644 --- a/__tests__/unit/client/theme-default/composables/outline.test.ts +++ b/__tests__/unit/client/theme-default/composables/outline.test.ts @@ -1,5 +1,11 @@ import { resolveHeaders } from 'client/theme-default/composables/outline' +const element = { + classList: { + contains: () => false + } +} as unknown as HTMLHeadElement + describe('client/theme-default/composables/outline', () => { describe('resolveHeader', () => { test('levels range', () => { @@ -9,12 +15,14 @@ describe('client/theme-default/composables/outline', () => { { level: 2, title: 'h2 - 1', - link: '#h2-1' + link: '#h2-1', + element }, { level: 3, title: 'h3 - 1', - link: '#h3-1' + link: '#h3-1', + element } ], [2, 3] @@ -28,9 +36,12 @@ describe('client/theme-default/composables/outline', () => { { level: 3, title: 'h3 - 1', - link: '#h3-1' + link: '#h3-1', + children: [], + element } - ] + ], + element } ]) }) @@ -42,12 +53,14 @@ describe('client/theme-default/composables/outline', () => { { level: 2, title: 'h2 - 1', - link: '#h2-1' + link: '#h2-1', + element }, { level: 3, title: 'h3 - 1', - link: '#h3-1' + link: '#h3-1', + element } ], 2 @@ -56,7 +69,9 @@ describe('client/theme-default/composables/outline', () => { { level: 2, title: 'h2 - 1', - link: '#h2-1' + link: '#h2-1', + children: [], + element } ]) }) @@ -68,42 +83,50 @@ describe('client/theme-default/composables/outline', () => { { level: 2, title: 'h2 - 1', - link: '#h2-1' + link: '#h2-1', + element }, { level: 3, title: 'h3 - 1', - link: '#h3-1' + link: '#h3-1', + element }, { level: 4, title: 'h4 - 1', - link: '#h4-1' + link: '#h4-1', + element }, { level: 3, title: 'h3 - 2', - link: '#h3-2' + link: '#h3-2', + element }, { level: 4, title: 'h4 - 2', - link: '#h4-2' + link: '#h4-2', + element }, { level: 2, title: 'h2 - 2', - link: '#h2-2' + link: '#h2-2', + element }, { level: 3, title: 'h3 - 3', - link: '#h3-3' + link: '#h3-3', + element }, { level: 4, title: 'h4 - 3', - link: '#h4-3' + link: '#h4-3', + element } ], 'deep' @@ -122,9 +145,12 @@ describe('client/theme-default/composables/outline', () => { { level: 4, title: 'h4 - 1', - link: '#h4-1' + link: '#h4-1', + children: [], + element } - ] + ], + element }, { level: 3, @@ -134,11 +160,15 @@ describe('client/theme-default/composables/outline', () => { { level: 4, title: 'h4 - 2', - link: '#h4-2' + link: '#h4-2', + children: [], + element } - ] + ], + element } - ] + ], + element }, { level: 2, @@ -153,11 +183,15 @@ describe('client/theme-default/composables/outline', () => { { level: 4, title: 'h4 - 3', - link: '#h4-3' + link: '#h4-3', + children: [], + element } - ] + ], + element } - ] + ], + element } ]) }) diff --git a/__tests__/unit/node/markdown/plugins/snippet.test.ts b/__tests__/unit/node/markdown/plugins/snippet.test.ts index 0ae97dd2..aa940784 100644 --- a/__tests__/unit/node/markdown/plugins/snippet.test.ts +++ b/__tests__/unit/node/markdown/plugins/snippet.test.ts @@ -1,4 +1,9 @@ -import { dedent, rawPathToToken } from 'node/markdown/plugins/snippet' +import { + dedent, + findRegion, + rawPathToToken +} from 'node/markdown/plugins/snippet' +import { expect } from 'vitest' const removeEmptyKeys = >(obj: T) => { return Object.fromEntries( @@ -94,9 +99,228 @@ describe('node/markdown/plugins/snippet', () => { }) }) - test('rawPathToToken', () => { - rawPathTokenMap.forEach(([rawPath, token]) => { + describe('rawPathToToken', () => { + test.each(rawPathTokenMap)('%s', (rawPath, token) => { expect(removeEmptyKeys(rawPathToToken(rawPath))).toEqual(token) }) }) + + describe('findRegion', () => { + it('returns null when no region markers are present', () => { + const lines = ['function foo() {', ' console.log("hello");', '}'] + expect(findRegion(lines, 'foo')).toBeNull() + }) + + it('ignores non-matching region names', () => { + const lines = [ + '// #region regionA', + 'some code here', + '// #endregion regionA' + ] + expect(findRegion(lines, 'regionC')).toBeNull() + }) + + it('returns null if a region start marker exists without a matching end marker', () => { + const lines = [ + '// #region missingEnd', + 'console.log("inside region");', + 'console.log("still inside");' + ] + expect(findRegion(lines, 'missingEnd')).toBeNull() + }) + + it('returns null if an end marker exists without a preceding start marker', () => { + const lines = [ + '// #endregion ghostRegion', + 'console.log("stray end marker");' + ] + expect(findRegion(lines, 'ghostRegion')).toBeNull() + }) + + it('detects C#/JavaScript style region markers with matching tags', () => { + const lines = [ + 'Console.WriteLine("Before region");', + '#region hello', + 'Console.WriteLine("Hello, World!");', + '#endregion hello', + 'Console.WriteLine("After region");' + ] + const result = findRegion(lines, 'hello') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + 'Console.WriteLine("Hello, World!");' + ) + } + }) + + it('detects region markers even when the end marker omits the region name', () => { + const lines = [ + 'Console.WriteLine("Before region");', + '#region hello', + 'Console.WriteLine("Hello, World!");', + '#endregion', + 'Console.WriteLine("After region");' + ] + const result = findRegion(lines, 'hello') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + 'Console.WriteLine("Hello, World!");' + ) + } + }) + + it('handles indented region markers correctly', () => { + const lines = [ + ' Console.WriteLine("Before region");', + ' #region hello', + ' Console.WriteLine("Hello, World!");', + ' #endregion hello', + ' Console.WriteLine("After region");' + ] + const result = findRegion(lines, 'hello') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + ' Console.WriteLine("Hello, World!");' + ) + } + }) + + it('detects TypeScript style region markers', () => { + const lines = [ + 'let regexp: RegExp[] = [];', + '// #region foo', + 'let start = -1;', + '// #endregion foo' + ] + const result = findRegion(lines, 'foo') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + 'let start = -1;' + ) + } + }) + + it('detects CSS style region markers', () => { + const lines = [ + '.body-content {', + '/* #region foo */', + ' padding-left: 15px;', + '/* #endregion foo */', + ' padding-right: 15px;', + '}' + ] + const result = findRegion(lines, 'foo') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + ' padding-left: 15px;' + ) + } + }) + + it('detects HTML style region markers', () => { + const lines = [ + '
Some content
', + '', + '

Hello world

', + '', + '
Other content
' + ] + const result = findRegion(lines, 'foo') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + '

Hello world

' + ) + } + }) + + it('detects Visual Basic style region markers (with case-insensitive "End")', () => { + const lines = [ + 'Console.WriteLine("VB")', + '#Region VBRegion', + ' Console.WriteLine("Inside region")', + '#End Region VBRegion', + 'Console.WriteLine("Done")' + ] + const result = findRegion(lines, 'VBRegion') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + ' Console.WriteLine("Inside region")' + ) + } + }) + + it('detects Bat style region markers', () => { + const lines = ['::#region foo', 'echo off', '::#endregion foo'] + const result = findRegion(lines, 'foo') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + 'echo off' + ) + } + }) + + it('detects C/C++ style region markers using #pragma', () => { + const lines = [ + '#pragma region foo', + 'int a = 1;', + '#pragma endregion foo' + ] + const result = findRegion(lines, 'foo') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + 'int a = 1;' + ) + } + }) + + it('returns the first complete region when multiple regions exist', () => { + const lines = [ + '// #region foo', + 'first region content', + '// #endregion foo', + '// #region foo', + 'second region content', + '// #endregion foo' + ] + const result = findRegion(lines, 'foo') + expect(result).not.toBeNull() + if (result) { + expect(lines.slice(result.start, result.end).join('\n')).toBe( + 'first region content' + ) + } + }) + + it('handles nested regions with different names properly', () => { + const lines = [ + '// #region foo', + "console.log('line before nested');", + '// #region bar', + "console.log('nested content');", + '// #endregion bar', + '// #endregion foo' + ] + const result = findRegion(lines, 'foo') + expect(result).not.toBeNull() + if (result) { + const extracted = lines.slice(result.start, result.end).join('\n') + const expected = [ + "console.log('line before nested');", + '// #region bar', + "console.log('nested content');", + '// #endregion bar' + ].join('\n') + expect(extracted).toBe(expected) + } + }) + }) }) diff --git a/__tests__/unit/node/utils/moduleGraph.test.ts b/__tests__/unit/node/utils/moduleGraph.test.ts new file mode 100644 index 00000000..9b3db9e5 --- /dev/null +++ b/__tests__/unit/node/utils/moduleGraph.test.ts @@ -0,0 +1,72 @@ +import { ModuleGraph } from 'node/utils/moduleGraph' + +describe('node/utils/moduleGraph', () => { + let graph: ModuleGraph + + beforeEach(() => { + graph = new ModuleGraph() + }) + + it('should correctly delete a module and its dependents', () => { + graph.add('A', ['B', 'C']) + graph.add('B', ['D']) + graph.add('C', []) + graph.add('D', []) + + expect(graph.delete('D')).toEqual(new Set(['D', 'B', 'A'])) + }) + + it('should handle shared dependencies correctly', () => { + graph.add('A', ['B', 'C']) + graph.add('B', ['D']) + graph.add('C', ['D']) // Shared dependency + graph.add('D', []) + + expect(graph.delete('D')).toEqual(new Set(['A', 'B', 'C', 'D'])) + }) + + it('merges dependencies correctly', () => { + // Add module A with dependency B + graph.add('A', ['B']) + // Merge new dependency C into module A (B should remain) + graph.add('A', ['C']) + + // Deleting B should remove A as well, since A depends on B. + expect(graph.delete('B')).toEqual(new Set(['B', 'A'])) + }) + + it('handles cycles gracefully', () => { + // Create a cycle: A -> B, B -> C, C -> A. + graph.add('A', ['B']) + graph.add('B', ['C']) + graph.add('C', ['A']) + + // Deleting any module in the cycle should delete all modules in the cycle. + expect(graph.delete('A')).toEqual(new Set(['A', 'B', 'C'])) + }) + + it('cleans up dependencies when deletion', () => { + // Setup A -> B relationship. + graph.add('A', ['B']) + graph.add('B', []) + + // Deleting B should remove both B and A from the graph. + expect(graph.delete('B')).toEqual(new Set(['B', 'A'])) + + // After deletion, add modules again. + graph.add('C', []) + graph.add('A', ['C']) // Now A depends only on C. + + expect(graph.delete('C')).toEqual(new Set(['C', 'A'])) + }) + + it('handles independent modules', () => { + // Modules with no dependencies. + graph.add('X', []) + graph.add('Y', []) + + // Deletion of one should only remove that module. + expect(graph.delete('X')).toEqual(new Set(['X'])) + expect(graph.delete('Y')).toEqual(new Set(['Y'])) + }) +}) diff --git a/__tests__/unit/vitest.config.ts b/__tests__/unit/vitest.config.ts index ff26e679..b028d4ee 100644 --- a/__tests__/unit/vitest.config.ts +++ b/__tests__/unit/vitest.config.ts @@ -1,7 +1,7 @@ -import { dirname, resolve } from 'path' -import { fileURLToPath } from 'url' -import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vitest/config' const dir = dirname(fileURLToPath(import.meta.url)) diff --git a/docs/.postcssrc.json b/docs/.postcssrc.json new file mode 100644 index 00000000..edc6a490 --- /dev/null +++ b/docs/.postcssrc.json @@ -0,0 +1,8 @@ +{ + "plugins": { + "postcss-rtlcss": { + "ltrPrefix": ":where([dir=\"ltr\"])", + "rtlPrefix": ":where([dir=\"rtl\"])" + } + } +} diff --git a/docs/.vitepress/config/es.ts b/docs/.vitepress/config/es.ts index d924c5b8..d30fc89f 100644 --- a/docs/.vitepress/config/es.ts +++ b/docs/.vitepress/config/es.ts @@ -48,7 +48,8 @@ export const es = defineConfig({ sidebarMenuLabel: 'Menu Lateral', darkModeSwitchLabel: 'Tema Oscuro', lightModeSwitchTitle: 'Cambiar a modo claro', - darkModeSwitchTitle: 'Cambiar a modo oscuro' + darkModeSwitchTitle: 'Cambiar a modo oscuro', + skipToContentLabel: 'Saltar al contenido' } }) diff --git a/docs/.vitepress/config/fa.ts b/docs/.vitepress/config/fa.ts new file mode 100644 index 00000000..5c91d0bc --- /dev/null +++ b/docs/.vitepress/config/fa.ts @@ -0,0 +1,223 @@ +import { createRequire } from 'module' +import { defineConfig, type DefaultTheme } from 'vitepress' + +const require = createRequire(import.meta.url) +const pkg = require('vitepress/package.json') + +export const fa = defineConfig({ + title: 'ویت‌پرس', + lang: 'fa-IR', + description: 'Vite & Vue powered static site generator.', + dir: 'rtl', + markdown: { + container: { + tipLabel: 'نکته', + warningLabel: 'هشدار', + dangerLabel: 'خطر', + infoLabel: 'اطلاعات', + detailsLabel: 'جزئیات' + } + }, + themeConfig: { + nav: nav(), + sidebar: { + '/fa/guide/': { base: '/fa/guide/', items: sidebarGuide() }, + '/fa/reference/': { base: '/fa/reference/', items: sidebarReference() } + }, + + editLink: { + pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path', + text: 'ویرایش این صفحه در گیت‌هاب' + }, + + footer: { + message: 'انتشار یافته تحت لایسنس MIT', + copyright: 'حق نسخه‌برداری © 2019-کنون Evan You' + }, + + docFooter: { + prev: 'قبلی', + next: 'بعدی' + }, + + outline: { + label: 'در این صفحه' + }, + + lastUpdated: { + text: 'آخرین به‌روزرسانی‌', + formatOptions: { + dateStyle: 'short', + timeStyle: 'medium' + } + }, + + langMenuLabel: 'تغییر زبان', + returnToTopLabel: 'بازگشت به بالا', + sidebarMenuLabel: 'منوی جانبی', + darkModeSwitchLabel: 'تم تاریک', + lightModeSwitchTitle: 'رفتن به حالت روشن', + darkModeSwitchTitle: 'رفتن به حالت تاریک', + notFound: { + linkLabel: 'بازگشت به خانه', + linkText: 'بازگشت به خانه', + title: 'صفحه مورد نظر یافت نشد', + code: '۴۰۴', + quote: + 'اما اگر جهت خود را تغییر ندهید و اگر ادامه دهید به دنبال چیزی که دنبال می‌کنید، ممکن است در نهایت به جایی که در حال رفتن به سمتش هستید، برسید.' + }, + siteTitle: 'ویت‌پرس' + } +}) + +function nav(): DefaultTheme.NavItem[] { + return [ + { + text: 'راهنما', + link: 'fa/guide/what-is-vitepress', + activeMatch: '/guide/' + }, + { + text: 'مرجع', + link: 'fa/reference/site-config', + activeMatch: '/reference/' + }, + { + text: pkg.version, + items: [ + { + text: 'Changelog', + link: 'https://github.com/vuejs/vitepress/blob/main/CHANGELOG.md' + }, + { + text: 'مشارکت', + link: 'https://github.com/vuejs/vitepress/blob/main/.github/contributing.md' + } + ] + } + ] +} + +function sidebarGuide(): DefaultTheme.SidebarItem[] { + return [ + { + text: 'معرفی', + collapsed: false, + items: [ + { text: 'ویت‌پرس چیست؟', link: 'what-is-vitepress' }, + { text: 'شروع کار', link: 'getting-started' }, + { text: 'مسیریابی', link: 'routing' }, + { text: 'استقرار', link: 'deploy' } + ] + }, + { + text: 'نوشتن', + collapsed: false, + items: [ + { text: 'افزونه‌های Markdown', link: 'markdown' }, + { text: 'مدیریت منابع', link: 'asset-handling' }, + { text: 'Frontmatter', link: 'frontmatter' }, + { text: 'استفاده از Vue در Markdown', link: 'using-vue' }, + { text: 'بین‌المللی سازی', link: 'i18n' } + ] + }, + { + text: 'شخصی‌سازی', + collapsed: false, + items: [ + { text: 'استفاده از تم شخصی', link: 'custom-theme' }, + { + text: 'گسترش تم پیش‌فرض', + link: 'extending-default-theme' + }, + { text: 'بارگیری داده در زمان Build', link: 'data-loading' }, + { text: 'سازگاری SSR', link: 'ssr-compat' }, + { text: 'اتصال به CMS', link: 'cms' } + ] + }, + { + text: 'آزمایشی', + collapsed: false, + items: [ + { text: 'حالت MPA', link: 'mpa-mode' }, + { text: 'جنریت کردن Sitemap', link: 'sitemap-generation' } + ] + }, + { text: 'پیکربندی و مرجع API', base: 'fa/reference/', link: 'site-config' } + ] +} + +function sidebarReference(): DefaultTheme.SidebarItem[] { + return [ + { + text: 'مرجع', + base: 'fa/reference/', + items: [ + { text: 'پیکربندی Site', link: 'site-config' }, + { text: 'پیکربندی Frontmatter', link: 'frontmatter-config' }, + { text: 'Runtime API', link: 'runtime-api' }, + { text: 'CLI', link: 'cli' }, + { + text: 'تم پیش‌فرض', + base: 'fa/reference/default-theme-', + items: [ + { text: 'بررسی اجمالی', link: 'config' }, + { text: 'ناوبری', link: 'nav' }, + { text: 'نوار کنار صفحه', link: 'sidebar' }, + { text: 'صفحه اصلی', link: 'home-page' }, + { text: 'پاورقی', link: 'footer' }, + { text: 'طرح', link: 'layout' }, + { text: 'نشان', link: 'badge' }, + { text: 'صفحه تیم', link: 'team-page' }, + { text: 'لینک‌های قبلی / بعدی', link: 'prev-next-links' }, + { text: 'ویرایش لینک', link: 'edit-link' }, + { text: 'Timestamp آخرین به‌روزرسانی', link: 'last-updated' }, + { text: 'جستجو', link: 'search' }, + { text: 'تبلیغات Carbon', link: 'carbon-ads' } + ] + } + ] + } + ] +} + +export const search: DefaultTheme.AlgoliaSearchOptions['locales'] = { + fa: { + placeholder: 'جستجوی مستندات', + translations: { + button: { + buttonText: 'جستجو', + buttonAriaLabel: 'جستجو' + }, + modal: { + searchBox: { + resetButtonTitle: 'آغاز مجدد جستجو', + resetButtonAriaLabel: 'آغاز مجدد جستجو', + cancelButtonText: 'لغو', + cancelButtonAriaLabel: 'لغو' + }, + startScreen: { + recentSearchesTitle: 'جستجو‌های اخیر', + noRecentSearchesText: 'تاریخچه جستجویی یافت نشد.', + saveRecentSearchButtonTitle: 'ذخیره تاریخچه جستجو', + removeRecentSearchButtonTitle: 'حذف تاریخچه جستجو', + favoriteSearchesTitle: 'موارد دلخواه', + removeFavoriteSearchButtonTitle: 'حذف مورد دلخواه' + }, + errorScreen: { + titleText: 'نتیجه‌ای یافت نشد برای', + helpText: 'اتصال شبکه خود را بررسی کنید' + }, + footer: { + selectText: 'انتخاب', + navigateText: 'رفتن', + closeText: 'بستن', + searchByText: ' جستجو با ' + }, + noResultsScreen: { + noResultsText: 'نتیجه‌ای یافت نشد برای' + } + } + } + } +} diff --git a/docs/.vitepress/config/index.ts b/docs/.vitepress/config/index.ts index 2715ccdc..08a81fb9 100644 --- a/docs/.vitepress/config/index.ts +++ b/docs/.vitepress/config/index.ts @@ -6,6 +6,7 @@ import { pt } from './pt' import { ru } from './ru' import { es } from './es' import { ko } from './ko' +import { fa } from './fa' export default defineConfig({ ...shared, @@ -15,6 +16,7 @@ export default defineConfig({ pt: { label: 'Português', ...pt }, ru: { label: 'Русский', ...ru }, es: { label: 'Español', ...es }, - ko: { label: '한국어', ...ko } + ko: { label: '한국어', ...ko }, + fa: { label: 'فارسی', ...fa } } }) diff --git a/docs/.vitepress/config/ko.ts b/docs/.vitepress/config/ko.ts index 44f7b446..faebabf4 100644 --- a/docs/.vitepress/config/ko.ts +++ b/docs/.vitepress/config/ko.ts @@ -18,7 +18,7 @@ export const ko = defineConfig({ editLink: { pattern: 'https://github.com/vuejs/vitepress/edit/main/docs/:path', - text: 'GitHub에서 이 페이지를 편집하세요' + text: '이 페이지 편집 제안하기' }, footer: { @@ -32,7 +32,7 @@ export const ko = defineConfig({ }, outline: { - label: '이 페이지에서' + label: '이 페이지 목차' }, lastUpdated: { @@ -44,7 +44,8 @@ export const ko = defineConfig({ sidebarMenuLabel: '사이드바 메뉴', darkModeSwitchLabel: '다크 모드', lightModeSwitchTitle: '라이트 모드로 변경', - darkModeSwitchTitle: '다크 모드로 변경' + darkModeSwitchTitle: '다크 모드로 변경', + skipToContentLabel: '본문으로 건너뛰기' } }) @@ -105,7 +106,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { collapsed: false, items: [ { - text: '마크다운 확장', + text: '마크다운 확장 기능', link: 'markdown' }, { @@ -113,33 +114,33 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { link: 'asset-handling' }, { - text: '프론트마터', + text: '전문(Front-matter)', link: 'frontmatter' }, { - text: '마크다운에서 Vue 사용', + text: '마크다운에서 Vue 사용하기', link: 'using-vue' }, { - text: '국제화', + text: 'i18n', link: 'i18n' } ] }, { - text: '사용자 정의', + text: '커스텀', collapsed: false, items: [ { - text: '맞춤 테마 사용', + text: '커스텀 테마 사용하기', link: 'custom-theme' }, { - text: '기본 테마 확장', + text: '기본 테마 확장하기', link: 'extending-default-theme' }, { - text: '빌드할 때 데이터 로딩', + text: '빌드할 때 데이터 로딩하기', link: 'data-loading' }, { @@ -147,7 +148,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { link: 'ssr-compat' }, { - text: 'CMS 연결', + text: 'CMS 연결하기', link: 'cms' } ] @@ -167,7 +168,7 @@ function sidebarGuide(): DefaultTheme.SidebarItem[] { ] }, { - text: '설정 & API 참조', + text: '구성 & API 레퍼런스', base: '/ko/reference/', link: 'site-config' } @@ -180,7 +181,7 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { text: '레퍼런스', items: [ { text: '사이트 구성', link: 'site-config' }, - { text: '머리말 구성', link: 'frontmatter-config' }, + { text: '전문(front-matter) 구성', link: 'frontmatter-config' }, { text: '런타임 API', link: 'runtime-api' }, { text: 'CLI', link: 'cli' }, { @@ -188,16 +189,16 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { base: '/ko/reference/default-theme-', items: [ { text: '개요', link: 'config' }, - { text: '네비게이션', link: 'nav' }, + { text: '네비게이션 바', link: 'nav' }, { text: '사이드바', link: 'sidebar' }, { text: '홈 페이지', link: 'home-page' }, { text: '푸터', link: 'footer' }, { text: '레이아웃', link: 'layout' }, - { text: '배지', link: 'badge' }, + { text: '배지(badge)', link: 'badge' }, { text: '팀 페이지', link: 'team-page' }, - { text: '이전 / 다음 링크', link: 'prev-next-links' }, + { text: '이전/다음 링크', link: 'prev-next-links' }, { text: '편집 링크', link: 'edit-link' }, - { text: '마지막 업데이트 시간', link: 'last-updated' }, + { text: '마지막 업데이트 날짜', link: 'last-updated' }, { text: '검색', link: 'search' }, { text: '카본 광고', link: 'carbon-ads' } ] diff --git a/docs/.vitepress/config/pt.ts b/docs/.vitepress/config/pt.ts index f8ce02a1..12cb52fa 100644 --- a/docs/.vitepress/config/pt.ts +++ b/docs/.vitepress/config/pt.ts @@ -48,7 +48,8 @@ export const pt = defineConfig({ sidebarMenuLabel: 'Menu Lateral', darkModeSwitchLabel: 'Tema Escuro', lightModeSwitchTitle: 'Mudar para Modo Claro', - darkModeSwitchTitle: 'Mudar para Modo Escuro' + darkModeSwitchTitle: 'Mudar para Modo Escuro', + skipToContentLabel: 'Pular para o Conteúdo' } }) diff --git a/docs/.vitepress/config/ru.ts b/docs/.vitepress/config/ru.ts index 6e536347..b75b1b77 100644 --- a/docs/.vitepress/config/ru.ts +++ b/docs/.vitepress/config/ru.ts @@ -42,7 +42,8 @@ export const ru = defineConfig({ darkModeSwitchTitle: 'Переключить на тёмную тему', sidebarMenuLabel: 'Меню', returnToTopLabel: 'Вернуться к началу', - langMenuLabel: 'Изменить язык' + langMenuLabel: 'Изменить язык', + skipToContentLabel: 'Перейти к содержимому' } }) @@ -143,7 +144,7 @@ function sidebarReference(): DefaultTheme.SidebarItem[] { { text: 'Навигация', link: 'nav' }, { text: 'Сайдбар', link: 'sidebar' }, { text: 'Главная страница', link: 'home-page' }, - { text: 'Подвал', link: 'footer' }, + { text: 'Футер', link: 'footer' }, { text: 'Макет', link: 'layout' }, { text: 'Значки', link: 'badge' }, { text: 'Страница команды', link: 'team-page' }, diff --git a/docs/.vitepress/config/shared.ts b/docs/.vitepress/config/shared.ts index 65907247..1f4961d2 100644 --- a/docs/.vitepress/config/shared.ts +++ b/docs/.vitepress/config/shared.ts @@ -1,9 +1,15 @@ import { defineConfig } from 'vitepress' -import { search as zhSearch } from './zh' -import { search as ptSearch } from './pt' -import { search as ruSearch } from './ru' +import { + groupIconMdPlugin, + groupIconVitePlugin, + localIconLoader +} from 'vitepress-plugin-group-icons' import { search as esSearch } from './es' +import { search as faSearch } from './fa' import { search as koSearch } from './ko' +import { search as ptSearch } from './pt' +import { search as ruSearch } from './ru' +import { search as zhSearch } from './zh' export const shared = defineConfig({ title: 'VitePress', @@ -25,7 +31,37 @@ export const shared = defineConfig({ return code.replace(/\[\!\!code/g, '[!code') } } - ] + ], + config(md) { + // TODO: remove when https://github.com/vuejs/vitepress/issues/4431 is fixed + const fence = md.renderer.rules.fence! + md.renderer.rules.fence = function (tokens, idx, options, env, self) { + const { localeIndex = 'root' } = env + const codeCopyButtonTitle = (() => { + switch (localeIndex) { + case 'es': + return 'Copiar código' + case 'fa': + return 'کپی کد' + case 'ko': + return '코드 복사' + case 'pt': + return 'Copiar código' + case 'ru': + return 'Скопировать код' + case 'zh': + return '复制代码' + default: + return 'Copy code' + } + })() + return fence(tokens, idx, options, env, self).replace( + '', + `` + ) + } + md.use(groupIconMdPlugin) + } }, sitemap: { @@ -60,18 +96,32 @@ export const shared = defineConfig({ provider: 'algolia', options: { appId: '8J64VVRP8K', - apiKey: 'a18e2f4cc5665f6602c5631fd868adfd', + apiKey: '52f578a92b88ad6abde815aae2b0ad7c', indexName: 'vitepress', locales: { ...zhSearch, ...ptSearch, ...ruSearch, ...esSearch, - ...koSearch + ...koSearch, + ...faSearch } } }, carbonAds: { code: 'CEBDT27Y', placement: 'vuejsorg' } + }, + vite: { + plugins: [ + groupIconVitePlugin({ + customIcon: { + vitepress: localIconLoader( + import.meta.url, + '../../public/vitepress-logo-mini.svg' + ), + firebase: 'logos:firebase' + } + }) + ] } }) diff --git a/docs/.vitepress/config/zh.ts b/docs/.vitepress/config/zh.ts index b7463980..e9d5fbcd 100644 --- a/docs/.vitepress/config/zh.ts +++ b/docs/.vitepress/config/zh.ts @@ -48,7 +48,8 @@ export const zh = defineConfig({ sidebarMenuLabel: '菜单', darkModeSwitchLabel: '主题', lightModeSwitchTitle: '切换到浅色模式', - darkModeSwitchTitle: '切换到深色模式' + darkModeSwitchTitle: '切换到深色模式', + skipToContentLabel: '跳转到内容' } }) diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 00000000..0e7ba37f --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,5 @@ +import Theme from 'vitepress/theme' +import 'virtual:group-icons.css' +import './styles.css' + +export default Theme diff --git a/docs/.vitepress/theme/styles.css b/docs/.vitepress/theme/styles.css new file mode 100644 index 00000000..1f397744 --- /dev/null +++ b/docs/.vitepress/theme/styles.css @@ -0,0 +1,40 @@ +@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@100..900&display=swap'); + +:root:where(:lang(fa)) { + --vp-font-family-base: + 'Vazirmatn', 'Inter', ui-sans-serif, system-ui, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; +} + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #bd34fe 50%, + #47caff 50% + ); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/* used in reference/default-theme-search */ +img[src='/search.png'] { + width: 100%; + aspect-ratio: 1 / 1; +} diff --git a/docs/en/guide/custom-theme.md b/docs/en/guide/custom-theme.md index 1d168ce9..96943c9f 100644 --- a/docs/en/guide/custom-theme.md +++ b/docs/en/guide/custom-theme.md @@ -49,8 +49,7 @@ interface EnhanceAppContext { The theme entry file should export the theme as its default export: -```js -// .vitepress/theme/index.js +```js [.vitepress/theme/index.js] // You can directly import Vue files in the theme entry // VitePress is pre-configured with @vitejs/plugin-vue. @@ -72,8 +71,7 @@ Inside your layout component, it works just like a normal Vite + Vue 3 applicati The most basic layout component needs to contain a [``](../reference/runtime-api#content) component: -```vue - +```vue [.vitepress/theme/Layout.vue]