fix: prevent dev mode reset from overriding localStorage preferences

pull/5012/head
juji 5 months ago
commit 9ba446ce46

@ -38,11 +38,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: pnpm
- run: pnpm install
- run: pnpm build

@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Create Release for Tag
id: release_tag

@ -18,22 +18,22 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
node_version: [20, 22, latest]
node_version: [20, 22, 24, latest]
include:
- os: windows-latest
node_version: 22
node_version: 24
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
- name: Set node version to ${{ matrix.node_version }}
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node_version }}
cache: pnpm

@ -1,3 +1,37 @@
## [2.0.0-alpha.13](https://github.com/vuejs/vitepress/compare/v2.0.0-alpha.12...v2.0.0-alpha.13) (2025-11-13)
### Bug Fixes
- **client,a11y:** improve focus handling and scrolling behavior in router ([#4943](https://github.com/vuejs/vitepress/issues/4943)) ([d46107f](https://github.com/vuejs/vitepress/commit/d46107fa254d662d297b1362aa0d3b898ef96e2c))
- disable markdown-it-attrs for fenced code blocks ([0899618](https://github.com/vuejs/vitepress/commit/089961855653f862b71747e8179ef2647e06d626))
- git log parsing when there are empty commits in history ([#4965](https://github.com/vuejs/vitepress/issues/4965)) ([612c458](https://github.com/vuejs/vitepress/commit/612c45895df79a0c0e87ca040564bfe88ce04f62))
- print full path in dead links check ([2b77fb3](https://github.com/vuejs/vitepress/commit/2b77fb3a72058129edbaddd3c6f0f6ee24f983d5)), closes [#4919](https://github.com/vuejs/vitepress/issues/4919)
- rename `markdown.cjkFriendly` to `markdown.cjkFriendlyEmphasis` ([bce0b53](https://github.com/vuejs/vitepress/commit/bce0b53659fa3a57b2ed8431a0861939dadd118a)), closes [#4952](https://github.com/vuejs/vitepress/issues/4952)
- respect markdown.cache = false on build too ([6d7422f](https://github.com/vuejs/vitepress/commit/6d7422f8fa321c641b1d5be3fa0c382400a2b78f))
- simplify lang extraction logic; use markdown-it plugins in type-safe manner; bump deps ([4e548f5](https://github.com/vuejs/vitepress/commit/4e548f542469a366f327cdef1530bdb1a31542ad))
- **theme:** add lang and dir attributes to language picker ([f0b29d7](https://github.com/vuejs/vitepress/commit/f0b29d7ef32a33f61c355d19561176411ede4b48))
- **theme:** adjust margin of code blocks inside containers ([82fac5d](https://github.com/vuejs/vitepress/commit/82fac5d22c9e2b28d18dafcd458741a4b4d7a86b)), closes [#4921](https://github.com/vuejs/vitepress/issues/4921)
- **theme:** avoid use of `:where` in selector list for now ([c2eaccd](https://github.com/vuejs/vitepress/commit/c2eaccd0d2109a6c64cee9fe615e48daaf4eda0e)), closes [#4923](https://github.com/vuejs/vitepress/issues/4923)
- **theme:** disable whitespace wrapping for VPBadge ([#4968](https://github.com/vuejs/vitepress/issues/4968)) ([113d230](https://github.com/vuejs/vitepress/commit/113d2304784586028d9733036ccb585374731397))
- **theme:** use nav height css var for curtain top in sidebar ([#4993](https://github.com/vuejs/vitepress/issues/4993)) ([be260fd](https://github.com/vuejs/vitepress/commit/be260fda6efc1d6c4b56219d7a17a19ab7a4ba76))
### Features
- export cacheAllGitTimestamps and getGitTimestamp ([31d87e2](https://github.com/vuejs/vitepress/commit/31d87e27387ebdceb22c047cc5f821761276d5f7))
- **i18n,a11y:** change last update logic ([#4935](https://github.com/vuejs/vitepress/issues/4935)) ([187bf25](https://github.com/vuejs/vitepress/commit/187bf250e6496554fca0b070a5aba55484f7fc0b))
- **markdown:** support custom display-name for fenced code blocks ([#4960](https://github.com/vuejs/vitepress/issues/4960)) ([3d61619](https://github.com/vuejs/vitepress/commit/3d61619ec0f0458c7ae04e7954b72a8e2ff399c0))
- prevent `$` symbol selection in shell code ([#5025](https://github.com/vuejs/vitepress/issues/5025)) ([bf2715e](https://github.com/vuejs/vitepress/commit/bf2715ed67f290726fc6d4c85c203ca8f74cc907))
- **theme:** allow passing functions for nav links ([#4963](https://github.com/vuejs/vitepress/issues/4963)) ([34cfa91](https://github.com/vuejs/vitepress/commit/34cfa91b6f14d8adfaa2d3c9f3eb6ad8b889ef1c))
### Performance Improvements
- make a single git call for timestamps instead of calling it for each file ([#4958](https://github.com/vuejs/vitepress/issues/4958)) ([6dfcdd3](https://github.com/vuejs/vitepress/commit/6dfcdd3fe8dc73e7b4ad7783df9530dedac1f6bd))
### BREAKING CHANGES
- `markdown-it-attrs` is disabled for fenced code blocks. For most users no change is required. If you want to add classes to code blocks, do it using shiki transformers instead.
- Rename `cjkFriendly` to `cjkFriendlyEmphasis` in your vitepress config. Most people should be unaffected unless they want to disable the CJK emphasis behavior added v2.0.0-alpha.12.
## [2.0.0-alpha.12](https://github.com/vuejs/vitepress/compare/v2.0.0-alpha.11...v2.0.0-alpha.12) (2025-08-20)
### Bug Fixes

9
client.d.ts vendored

@ -3,3 +3,12 @@
/// <reference types="vite/client" />
export * from './dist/client/index.js'
declare global {
interface WindowEventMap {
'vitepress:codeGroupTabActivate': Event & {
/** code block element that was activated */
detail: Element
}
}
}

@ -153,17 +153,17 @@ Don't enable options like _Auto Minify_ for HTML code. It will remove comments f
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
# - uses: pnpm/action-setup@v3 # Uncomment this block if you're using pnpm
# - uses: pnpm/action-setup@v4 # Uncomment this block if you're using pnpm
# with:
# version: 9 # Not needed if you've set "packageManager" in package.json
# - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # or pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4

@ -11,7 +11,7 @@ To fix this in **GitHub Actions**, use the following in your workflow:
```yaml{4}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
```

@ -333,7 +333,7 @@ export default {
- 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 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. Relative bases are not supported.
The base is automatically prepended to all the URLs that start with / in other options, so you only need to specify it once.

@ -153,17 +153,17 @@ No active opciones como _Auto Minify_ para código HTML. Eso removera comentario
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # No necesario se lastUpdated no estuviera habilitado
# - uses: pnpm/action-setup@v3 # Desconecte eso si estuviera usando pnpm
# - uses: pnpm/action-setup@v4 # Desconecte eso si estuviera usando pnpm
# with:
# version: 9
# - uses: oven-sh/setup-bun@v1 # Desconecte eso se estuviera usando Bun
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # o pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4

@ -153,15 +153,15 @@ Cache-Control: max-age=31536000,immutable
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
# - uses: pnpm/action-setup@v3 # Uncomment this if you're using pnpm
# - uses: pnpm/action-setup@v4 # Uncomment this if you're using pnpm
# - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # or pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4

@ -158,17 +158,17 @@ HTML の _Auto Minify_ のようなオプションを有効にしないでくだ
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
# - uses: pnpm/action-setup@v3 # Uncomment this block if you're using pnpm
# - uses: pnpm/action-setup@v4 # Uncomment this block if you're using pnpm
# with:
# version: 9 # Not needed if you've set "packageManager" in package.json
# - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # or pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4

@ -11,7 +11,7 @@ VitePress は各ファイルの **直近の Git コミットのタイムスタ
```yaml{4}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
```

@ -152,17 +152,17 @@ HTML 코드에 대해 _Auto Minify_ 옵션을 활성화하지 마세요. 이는
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # lastUpdated가 활성화되지 않은 경우 필요하지 않음
# - uses: pnpm/action-setup@v3 # pnpm을 사용하는 경우 주석 해제
# - uses: pnpm/action-setup@v4 # pnpm을 사용하는 경우 주석 해제
# with:
# version: 9
# - uses: oven-sh/setup-bun@v1 # Bun을 사용하는 경우 주석 해제
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # 또는 pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4

@ -15,7 +15,7 @@
"open-cli": "^8.0.0",
"postcss-rtlcss": "^5.7.1",
"vitepress": "workspace:*",
"vitepress-plugin-group-icons": "^1.6.3",
"vitepress-plugin-llms": "^1.7.3"
"vitepress-plugin-group-icons": "^1.6.5",
"vitepress-plugin-llms": "^1.9.1"
}
}

@ -153,17 +153,17 @@ Não ative opções como _Auto Minify_ para código HTML. Isso removerá coment
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Não necessário se lastUpdated não estiver habilitado
# - uses: pnpm/action-setup@v3 # Descomente isso se estiver usando pnpm
# - uses: pnpm/action-setup@v4 # Descomente isso se estiver usando pnpm
# with:
# version: 9
# - uses: oven-sh/setup-bun@v1 # Descomente isso se estiver usando Bun
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # ou pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4

@ -153,17 +153,17 @@ Cache-Control: max-age=31536000,immutable
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Не требуется, если функция lastUpdated не включена
# - uses: pnpm/action-setup@v3 # Раскомментируйте, если вы используете pnpm
# - uses: pnpm/action-setup@v4 # Раскомментируйте, если вы используете pnpm
# with:
# version: 9
# - uses: oven-sh/setup-bun@v1 # Раскомментируйте, если вы используете Bun
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # или pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4

@ -1,6 +1,6 @@
# Что такое VitePress? {#what-is-vitepress}
VitePress — это [Генератор статических сайтов](https://en.wikipedia.org/wiki/Static_site_generator) (ГСС), предназначенный для быстрого создания сайтов, ориентированных на контент. В двух словах, VitePress берёт ваш исходный контент, написанный на [Markdown](https://ru.wikipedia.org/wiki/Markdown), применяет к нему тему и генерирует статические HTML-страницы, которые можно легко развернуть в любом месте.
VitePress — это [Генератор статических сайтов](https://ru.wikipedia.org/wiki/%D0%93%D0%B5%D0%BD%D0%B5%D1%80%D0%B0%D1%82%D0%BE%D1%80%D1%8B_%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D1%81%D0%B0%D0%B9%D1%82%D0%BE%D0%B2) (ГСС), предназначенный для быстрого создания сайтов, ориентированных на контент. В двух словах, VitePress берёт ваш исходный контент, написанный на [Markdown](https://ru.wikipedia.org/wiki/Markdown), применяет к нему тему и генерирует статические HTML-страницы, которые можно легко развернуть в любом месте.
<div class="tip custom-block" style="padding-top: 8px">
@ -12,13 +12,13 @@ VitePress — это [Генератор статических сайтов](ht
- **Документация**
VitePress поставляется с темой по умолчанию, предназначенной для технической документации. Она содержит эту страницу, которую вы сейчас читаете, а также документацию по [Vite](https://vitejs.dev/), [Rollup](https://rollupjs.org/), [Pinia](https://pinia.vuejs.org/), [VueUse](https://vueuse.org/), [Vitest](https://vitest.dev/), [D3](https://d3js.org/), [UnoCSS](https://unocss.dev/), [Iconify](https://iconify.design/) и [многое другое](https://github.com/search?q=/"vitepress":+/+language:json&type=code).
VitePress поставляется с темой по умолчанию, предназначенной для технической документации. Именно она обеспечивает работу этой страницы, которую вы сейчас читаете, а также документации для [Vite](https://vite-docs.ru/), [Rollup](https://rollupjs.org/), [Pinia](https://pinia-ru.netlify.app), [VueUse](https://vueuse.org/), [Vitest](https://vitest.dev/), [D3](https://d3js.org/), [UnoCSS](https://unocss.dev/), [Iconify](https://iconify.design/) и [многих других](https://github.com/search?q=/"vitepress":+/+language:json&type=code).
[Официальная документация Vue.js](https://vuejs.org/) также основана на VitePress, но использует пользовательскую тему, разделяемую между несколькими переводами.
[Официальная документация Vue.js](https://vuejs.org/) также основана на VitePress, но использует кастомную тему, общую для нескольких переводов.
- **Блоги, портфолио и маркетинговые сайты**
VitePress поддерживает [полностью кастомизированные темы](./custom-theme), при этом разработчики могут использовать стандартное приложение Vite + Vue. То, что он построен на базе Vite, также означает, что вы можете напрямую использовать плагины Vite из его богатой экосистемы. Кроме того, VitePress предоставляет гибкие API для [загрузки данных](./data-loading) (локальной или удаленной) и [динамической генерации маршрутов](./routing#dynamic-routes). С его помощью можно построить практически всё, что угодно, если данные могут быть определены во время сборки.
VitePress поддерживает [полностью кастомизированные темы](./custom-theme), при этом разработчики могут использовать стандартное приложение Vite + Vue. То, что он построен на базе Vite, также означает, что вы можете напрямую использовать плагины Vite из его богатой экосистемы. Кроме того, VitePress предоставляет гибкие API для [загрузки данных](./data-loading) (локальной или удалённой) и [динамической генерации маршрутов](./routing#dynamic-routes). С его помощью можно построить практически всё, что угодно, если данные могут быть определены во время сборки.
Официальный [блог Vue.js](https://blog.vuejs.org/) — это простой блог, который генерирует свою индексную страницу на основе локального контента.
@ -50,8 +50,8 @@ VitePress стремится обеспечить отличные возмож
## Что насчёт VuePress? {#what-about-vuepress}
VitePress — это духовный преемник VuePress 1. Оригинальный VuePress 1 был основан на Vue 2 и webpack. С Vue 3 и Vite под капотом VitePress обеспечивает значительно лучший опыт разработки (DX), лучшую производительность в продакшене, более отточенную стандартную тему и более гибкий API для кастомизации.
VitePress — это духовный наследник VuePress 1. Оригинальный VuePress 1 основывался на Vue 2 и webpack. Благодаря Vue 3 и Vite под капотом, VitePress обеспечивает значительно лучший опыт разработки (DX), более высокую производительность в продакшене, более отполированную тему по умолчанию и более гибкий API для кастомизации.
Различия в API между VitePress и VuePress 1 в основном касаются тем и кастомизации. Если вы используете VuePress 1 со стандартной темой, миграция на VitePress должна быть относительно простой.
Различия в API между VitePress и VuePress 1 в основном касаются тем и настройки. Если вы используете VuePress 1 с темой по умолчанию, миграция на VitePress должна пройти относительно просто.
Поддержка двух генераторов статических сайтов (SSG) параллельно не является устойчивой, поэтому команда Vue решила сосредоточиться на VitePress как на основном рекомендуемом SSG в долгосрочной перспективе. Теперь VuePress 1 признан устаревшим, а VuePress 2 передан команде сообщества VuePress для дальнейшей разработки и поддержки.
Поддерживать два SSG параллельно нецелесообразно, поэтому команда Vue решила сосредоточиться на VitePress как на основном рекомендуемом SSG в долгосрочной перспективе. Теперь VuePress 1 объявлен устаревшим, а VuePress 2 передан команде сообщества VuePress для дальнейшей разработки и поддержки.

@ -11,7 +11,7 @@ VitePress отображает время «последнего обновле
```yaml{4}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
```

@ -153,17 +153,17 @@ Cache-Control: max-age=31536000,immutable
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # 如果未启用 lastUpdated则不需要
# - uses: pnpm/action-setup@v3 # 如果使用 pnpm请取消此区域注释
# - uses: pnpm/action-setup@v4 # 如果使用 pnpm请取消此区域注释
# with:
# version: 9
# - uses: oven-sh/setup-bun@v1 # 如果使用 Bun请取消注释
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
cache: npm # 或 pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v4
@ -308,20 +308,20 @@ server {
index index.html;
location / {
# content location
# 内容位置
root /app;
# exact matches -> reverse clean urls -> folders -> not found
# 完全匹配 -> 反向清理 url -> 文件夹 -> 没有发现
try_files $uri $uri.html $uri/ =404;
# non existent pages
# 不存在的页面
error_page 404 /404.html;
# a folder without index.html raises 403 in this setup
# 在此设置中,如果文件夹没有 index.html就会引发 403 错误
error_page 403 /404.html;
# adjust caching headers
# files in the assets folder have hashes filenames
# 调整缓存标头
# assets 文件夹中的文件都有哈希文件名
location ~* ^/assets/ {
expires 1y;
add_header Cache-Control "public, immutable";

@ -55,8 +55,8 @@ export default DefaultTheme
```css
/* .vitepress/theme/my-fonts.css */
:root {
--vp-font-family-base: /* normal text font */
--vp-font-family-mono: /* code font */
--vp-font-family-base: /* 普通文本字体 */
--vp-font-family-mono: /* 代码字体 */
}
```

@ -11,7 +11,7 @@ VitePress 通过每个文件最近一次 Git 提交的时间戳显示"最后更
```yaml{4}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
```

@ -1,5 +1,5 @@
[build.environment]
NODE_VERSION = "22"
NODE_VERSION = "24"
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1"
[build]

@ -1,6 +1,6 @@
{
"name": "vitepress",
"version": "2.0.0-alpha.12",
"version": "2.0.0-alpha.13",
"description": "Vite & Vue powered static site generator",
"keywords": [
"vite",
@ -95,28 +95,28 @@
"*": "prettier --experimental-cli --ignore-unknown --write"
},
"dependencies": {
"@docsearch/css": "^4.0.0-beta.8",
"@docsearch/js": "^4.0.0-beta.8",
"@iconify-json/simple-icons": "^1.2.49",
"@shikijs/core": "^3.12.0",
"@shikijs/transformers": "^3.12.0",
"@shikijs/types": "^3.12.0",
"@docsearch/css": "^4.3.2",
"@docsearch/js": "^4.3.2",
"@iconify-json/simple-icons": "^1.2.58",
"@shikijs/core": "^3.15.0",
"@shikijs/transformers": "^3.15.0",
"@shikijs/types": "^3.15.0",
"@types/markdown-it": "^14.1.2",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/devtools-api": "^8.0.1",
"@vue/shared": "^3.5.20",
"@vueuse/core": "^13.8.0",
"@vueuse/integrations": "^13.8.0",
"focus-trap": "^7.6.5",
"@vue/devtools-api": "^8.0.3",
"@vue/shared": "^3.5.24",
"@vueuse/core": "^14.0.0",
"@vueuse/integrations": "^14.0.0",
"focus-trap": "^7.6.6",
"mark.js": "8.11.1",
"minisearch": "^7.1.2",
"shiki": "^3.12.0",
"vite": "^7.1.3",
"vue": "^3.5.20"
"minisearch": "^7.2.0",
"shiki": "^3.15.0",
"vite": "^7.2.2",
"vue": "^3.5.24"
},
"devDependencies": {
"@clack/prompts": "^1.0.0-alpha.4",
"@iconify/utils": "^3.0.1",
"@clack/prompts": "^1.0.0-alpha.6",
"@iconify/utils": "^3.0.2",
"@mdit-vue/plugin-component": "^3.0.2",
"@mdit-vue/plugin-frontmatter": "^3.0.2",
"@mdit-vue/plugin-headers": "^3.0.2",
@ -125,11 +125,11 @@
"@mdit-vue/plugin-toc": "^3.0.2",
"@mdit-vue/shared": "^3.0.2",
"@polka/compression": "^1.0.0-next.28",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-alias": "^6.0.0",
"@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-replace": "^6.0.2",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-replace": "^6.0.3",
"@types/cross-spawn": "^6.0.6",
"@types/debug": "^4.1.12",
"@types/fs-extra": "^11.0.4",
@ -139,62 +139,62 @@
"@types/markdown-it-container": "^2.0.10",
"@types/markdown-it-emoji": "^3.0.1",
"@types/minimist": "^1.2.5",
"@types/node": "^24.3.0",
"@types/node": "^24.10.1",
"@types/picomatch": "^4.0.2",
"@types/prompts": "^2.4.9",
"chokidar": "^4.0.3",
"conventional-changelog-cli": "^5.0.0",
"cross-spawn": "^7.0.6",
"debug": "^4.4.1",
"esbuild": "^0.25.9",
"debug": "^4.4.3",
"esbuild": "^0.25.0",
"execa": "^9.6.0",
"fs-extra": "^11.3.1",
"fs-extra": "^11.3.2",
"get-port": "^7.1.0",
"gray-matter": "^4.0.3",
"lint-staged": "^16.1.5",
"lint-staged": "^16.2.6",
"lodash.template": "^4.5.0",
"lru-cache": "^11.1.0",
"lru-cache": "^11.2.2",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-async": "^2.2.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-cjk-friendly": "^1.2.0",
"markdown-it-cjk-friendly": "^1.3.2",
"markdown-it-container": "^4.0.0",
"markdown-it-emoji": "^3.0.0",
"markdown-it-mathjax3": "^4.3.2",
"minimist": "^1.2.8",
"nanoid": "^5.1.5",
"ora": "^8.2.0",
"oxc-minify": "^0.82.3",
"p-map": "^7.0.3",
"nanoid": "^5.1.6",
"ora": "^9.0.0",
"oxc-minify": "^0.97.0",
"p-map": "^7.0.4",
"package-directory": "^8.1.0",
"path-to-regexp": "^6.3.0",
"picocolors": "^1.1.1",
"picomatch": "^4.0.3",
"playwright-chromium": "^1.55.0",
"playwright-chromium": "^1.56.1",
"polka": "^1.0.0-next.28",
"postcss": "^8.5.6",
"postcss-selector-parser": "^7.1.0",
"prettier": "^3.6.2",
"prompts": "^2.4.2",
"punycode": "^2.3.1",
"rimraf": "^6.0.1",
"rollup": "^4.49.0",
"rimraf": "^6.1.0",
"rollup": "^4.53.2",
"rollup-plugin-dts": "6.1.1",
"rollup-plugin-esbuild": "^6.2.1",
"semver": "^7.7.2",
"semver": "^7.7.3",
"simple-git-hooks": "^2.13.1",
"sirv": "^3.0.1",
"sitemap": "^8.0.0",
"tinyglobby": "^0.2.14",
"typescript": "^5.9.2",
"sirv": "^3.0.2",
"sitemap": "^9.0.0",
"tinyglobby": "^0.2.15",
"typescript": "^5.9.3",
"vitest": "4.0.0-beta.4",
"vue-tsc": "^3.0.6",
"wait-on": "^8.0.4"
"vue-tsc": "^3.1.3",
"wait-on": "^9.0.3"
},
"peerDependencies": {
"markdown-it-mathjax3": "^4",
"oxc-minify": "^0.82.3",
"oxc-minify": "*",
"postcss": "^8"
},
"peerDependenciesMeta": {
@ -208,5 +208,5 @@
"optional": true
}
},
"packageManager": "pnpm@10.14.0"
"packageManager": "pnpm@10.22.0"
}

@ -0,0 +1,23 @@
diff --git a/index.d.ts b/index.d.ts
index 41d4a858c6ece5a61a2088733cf8b333b45603d8..2cb19bcd8fc76ae82ebe87af742916e17f49334b 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,3 +1,15 @@
-import MarkdownIt = require("markdown-it");
-declare function attrs(md: MarkdownIt): void;
-export = attrs;
+import type MarkdownIt from 'markdown-it'
+
+export interface MarkdownItAttrsOptions {
+ /** left delimiter, default is `{`(left curly bracket) */
+ leftDelimiter?: string
+ /** right delimiter, default is `}`(right curly bracket) */
+ rightDelimiter?: string
+ /** rule of allowed attribute, empty means no limit */
+ allowedAttributes?: (string | RegExp)[]
+}
+
+export default function attrsPlugin(
+ md: MarkdownIt,
+ options?: MarkdownItAttrsOptions
+): void

@ -0,0 +1,14 @@
diff --git a/patterns.js b/patterns.js
index e56a3b785df29e726194f2b30e86bb19a78ef902..e1f6211b92cbb2bbbe2a3c317dd9e5f1f41ab8d1 100644
--- a/patterns.js
+++ b/patterns.js
@@ -28,7 +28,8 @@ module.exports = options => {
{
shift: 0,
block: true,
- info: utils.hasDelimiters('end', options)
+ info: utils.hasDelimiters('end', options),
+ markup: (str) => !/^[`~]{3,}$/.test(str)
}
],
transform: (tokens, i) => {

File diff suppressed because it is too large Load Diff

@ -2,6 +2,8 @@ packages:
- docs
- __tests__/*
autoInstallPeers: false
onlyBuiltDependencies:
- esbuild
- playwright-chromium
@ -12,8 +14,9 @@ overrides:
vite: npm:rolldown-vite@latest
patchedDependencies:
'@types/markdown-it-attrs': patches/@types__markdown-it-attrs.patch
'@types/mdurl@2.0.0': patches/@types__mdurl@2.0.0.patch
markdown-it-anchor@9.2.0: patches/markdown-it-anchor@9.2.0.patch
markdown-it-attrs@4.3.1: patches/markdown-it-attrs@4.3.1.patch
autoInstallPeers: false
shellEmulator: true

@ -27,10 +27,19 @@ export function useCodeGroups() {
if (import.meta.env.DEV) {
onContentUpdated(() => {
document.querySelectorAll('.vp-code-group > .blocks').forEach((el) => {
const group = el.closest('.vp-code-group')
const groupName = group?.getAttribute('data-group-name')
// Don't reset if user has a saved preference
if (groupName && getStoredTabIndex(groupName) !== null) {
return // Keep user's preference
}
// Only reset groups without saved preferences
Array.from(el.children).forEach((child) => {
child.classList.remove('active')
})
el.children[0].classList.add('active')
activate(el.children[0])
})
})
}
@ -92,7 +101,7 @@ export function useCodeGroups() {
if (!next || current === next) return
current.classList.remove('active')
next.classList.add('active')
activate(next)
const label = group?.querySelector(`label[for="${el.id}"]`)
label?.scrollIntoView({ block: 'nearest' })
@ -138,3 +147,10 @@ function syncTabsInOtherGroups(
}
})
}
function activate(el: Element): void {
el.classList.add('active')
window.dispatchEvent(
new CustomEvent('vitepress:codeGroupTabActivate', { detail: el })
)
}

@ -1,6 +1,6 @@
import { inBrowser } from 'vitepress'
import { isShell } from '../../shared'
const shellRE = /language-(shellscript|shell|bash|sh|zsh)/
const ignoredNodes = ['.vp-copy-ignore', '.diff.remove'].join(', ')
export function useCopyCode() {
@ -15,8 +15,6 @@ export function useCopyCode() {
return
}
const isShell = shellRE.test(parent.className)
// Clone the node and remove the ignored nodes
const clone = sibling.cloneNode(true) as HTMLElement
clone.querySelectorAll(ignoredNodes).forEach((node) => node.remove())
@ -26,7 +24,10 @@ export function useCopyCode() {
let text = clone.textContent || ''
if (isShell) {
// NOTE: Any changes to this the code here may also need to update
// `transformerDisableShellSymbolSelect` in `src/node/markdown/plugins/highlight.ts`
const lang = /language-(\w+)/.exec(parent.className)?.[1] || ''
if (isShell(lang)) {
text = text.replace(/^ *(\$|>) /gm, '').trim()
}

@ -88,8 +88,8 @@ const { isHome, hasSidebar } = useLayout()
@media (min-width: 1440px) {
.VPContent.has-sidebar {
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2);
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
padding-right: calc((100% - var(--vp-layout-max-width)) / 2);
padding-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}
</style>

@ -98,12 +98,6 @@ const classes = computed(() => {
}
}
@media (min-width: 1440px) {
.VPLocalNav.has-sidebar {
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}
.container {
display: flex;
justify-content: space-between;

@ -171,20 +171,14 @@ watchPostEffect(() => {
position: relative;
z-index: 1;
padding-left: var(--vp-sidebar-width);
}
.VPNavBar.has-sidebar .content-body {
padding-right: 32px;
}
}
@media (min-width: 1440px) {
.VPNavBar.has-sidebar .content {
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
.VPNavBar.has-sidebar .content-body {
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);
padding-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
padding-right: calc((100% - var(--vp-layout-max-width)) / 2 + 32px);
}
}
@ -193,6 +187,8 @@ watchPostEffect(() => {
justify-content: flex-end;
align-items: center;
height: var(--vp-nav-height);
margin-right: -100vw;
padding-right: 100vw;
transition: background-color 0.5s;
}
@ -252,7 +248,7 @@ watchPostEffect(() => {
@media (min-width: 1440px) {
.VPNavBar.has-sidebar .divider {
padding-left: calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
padding-left: calc((100% - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width));
}
}

@ -111,8 +111,8 @@ watch(
@media (min-width: 1440px) {
.VPSidebar {
padding-left: max(32px, calc((100vw - (var(--vp-layout-max-width) - 64px)) / 2));
width: calc((100vw - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));
width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
}
}

@ -5,7 +5,7 @@
"outDir": "../../dist/client",
"declaration": true,
"declarationDir": "../../dist/client-types",
"types": ["vite/client", "@types/node"],
"types": ["../../client.d.ts", "@types/node"],
"paths": {
"vitepress": ["index.ts"],
"vitepress/theme": ["../../theme.d.ts"]

@ -20,8 +20,8 @@ import type {
ThemeRegistrationAny
} from '@shikijs/types'
import anchorPlugin from 'markdown-it-anchor'
import { MarkdownItAsync, type Options } from 'markdown-it-async'
import attrsPlugin from 'markdown-it-attrs'
import { MarkdownItAsync, type MarkdownItAsyncOptions } from 'markdown-it-async'
import attrsPlugin, { type MarkdownItAttrsOptions } from 'markdown-it-attrs'
import mditCjkFriendly from 'markdown-it-cjk-friendly'
import { full as emojiPlugin } from 'markdown-it-emoji'
import type { BuiltinLanguage, BuiltinTheme, Highlighter } from 'shiki'
@ -30,7 +30,6 @@ import type { Awaitable } from '../shared'
import { containerPlugin, type ContainerOptions } from './plugins/containers'
import { gitHubAlertsPlugin } from './plugins/githubAlerts'
import { highlight as createHighlighter } from './plugins/highlight'
import { highlightLinePlugin } from './plugins/highlightLines'
import { imagePlugin, type Options as ImageOptions } from './plugins/image'
import { lineNumberPlugin } from './plugins/lineNumbers'
import { linkPlugin } from './plugins/link'
@ -48,7 +47,7 @@ export type ThemeOptions =
dark: ThemeRegistrationAny | BuiltinTheme
}
export interface MarkdownOptions extends Options {
export interface MarkdownOptions extends MarkdownItAsyncOptions {
/* ==================== General Options ==================== */
/**
@ -153,12 +152,7 @@ export interface MarkdownOptions extends Options {
* Options for `markdown-it-attrs`
* @see https://github.com/arve0/markdown-it-attrs
*/
attrs?: {
leftDelimiter?: string
rightDelimiter?: string
allowedAttributes?: Array<string | RegExp>
disable?: boolean
}
attrs?: MarkdownItAttrsOptions & { disable?: boolean }
/**
* Options for `markdown-it-emoji`
* @see https://github.com/markdown-it/markdown-it-emoji
@ -230,7 +224,10 @@ export interface MarkdownOptions extends Options {
export type MarkdownRenderer = MarkdownItAsync
let md: MarkdownRenderer | undefined
// highlight is marked as any to avoid type conflicts with plugins expecting
// regular markdown-it which has sync highlight function. Such plugins will fail
// if they access highlight directly but currently none of the ones we use do that.
let md: (MarkdownRenderer & { options: { highlight?: any } }) | undefined
let _disposeHighlighter: (() => void) | undefined
export function disposeMdItInstance() {
@ -263,7 +260,7 @@ export async function createMarkdownRenderer(
md = new MarkdownItAsync({ html: true, linkify: true, highlight, ...options })
md.linkify.set({ fuzzyLink: false })
md.use(restoreEntities)
restoreEntities(md)
if (options.preConfig) {
await options.preConfig(md)
@ -272,22 +269,21 @@ export async function createMarkdownRenderer(
const slugify = options.anchor?.slugify ?? defaultSlugify
// custom plugins
md.use(componentPlugin, { ...options.component })
.use(highlightLinePlugin)
.use(preWrapperPlugin, {
codeCopyButtonTitle,
languageLabel: options.languageLabel
})
.use(snippetPlugin, srcDir)
.use(containerPlugin, options.container)
.use(imagePlugin, options.image)
.use(
linkPlugin,
{ target: '_blank', rel: 'noreferrer', ...options.externalLinks },
base,
slugify
)
.use(lineNumberPlugin, options.lineNumbers)
componentPlugin(md, options.component)
preWrapperPlugin(md, {
codeCopyButtonTitle,
languageLabel: options.languageLabel
})
snippetPlugin(md, srcDir)
containerPlugin(md, options.container)
imagePlugin(md, options.image)
linkPlugin(
md,
{ target: '_blank', rel: 'noreferrer', ...options.externalLinks },
base,
slugify
)
lineNumberPlugin(md, options.lineNumbers)
const tableOpen = md.renderer.rules.table_open
md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
@ -299,17 +295,17 @@ export async function createMarkdownRenderer(
}
if (options.gfmAlerts !== false) {
md.use(gitHubAlertsPlugin, options.container)
gitHubAlertsPlugin(md, options.container)
}
// third party plugins
if (!options.attrs?.disable) {
md.use(attrsPlugin, options.attrs)
attrsPlugin(md, options.attrs)
}
md.use(emojiPlugin, { ...options.emoji })
emojiPlugin(md, options.emoji)
// mdit-vue plugins
md.use(anchorPlugin, {
anchorPlugin(md, {
slugify,
getTokensText: (tokens) => {
return tokens
@ -343,35 +339,33 @@ export async function createMarkdownRenderer(
state.tokens[idx + 1].children?.push(...linkTokens)
},
...options.anchor
} as anchorPlugin.AnchorOptions).use(frontmatterPlugin, {
...options.frontmatter
} as FrontmatterPluginOptions)
})
frontmatterPlugin(md, options.frontmatter)
if (options.headers) {
md.use(headersPlugin, {
headersPlugin(md, {
level: [2, 3, 4, 5, 6],
slugify,
...(typeof options.headers === 'boolean' ? undefined : options.headers)
} as HeadersPluginOptions)
})
}
md.use(sfcPlugin, {
...options.sfc
} as SfcPluginOptions)
.use(titlePlugin)
.use(tocPlugin, {
slugify,
...options.toc,
format: (s) => {
const title = s.replaceAll('&amp;', '&') // encoded twice because of restoreEntities
return options.toc?.format?.(title) ?? title
}
} as TocPluginOptions)
sfcPlugin(md, options.sfc)
titlePlugin(md)
tocPlugin(md, {
slugify,
...options.toc,
format: (s) => {
const title = s.replaceAll('&amp;', '&') // encoded twice because of restoreEntities
return options.toc?.format?.(title) ?? title
}
})
if (options.math) {
try {
const mathPlugin = await import('markdown-it-mathjax3')
md.use(mathPlugin.default ?? mathPlugin, {
;(mathPlugin.default ?? mathPlugin)(md, {
...(typeof options.math === 'boolean' ? {} : options.math)
})
const origMathInline = md.renderer.rules.math_inline!
@ -388,13 +382,13 @@ export async function createMarkdownRenderer(
}
} catch (error) {
throw new Error(
'You need to install `markdown-it-mathjax3` to use math support.'
'You need to install `markdown-it-mathjax3@^4` to use math support.'
)
}
}
if (options.cjkFriendlyEmphasis !== false && options.cjkFriendly !== false) {
md.use(mditCjkFriendly)
mditCjkFriendly(md)
}
// apply user config

@ -1,50 +1,52 @@
import {
transformerCompactLineOptions,
transformerMetaHighlight,
transformerNotationDiff,
transformerNotationErrorLevel,
transformerNotationFocus,
transformerNotationHighlight,
type TransformerCompactLineOption
transformerNotationHighlight
} from '@shikijs/transformers'
import { customAlphabet } from 'nanoid'
import c from 'picocolors'
import type { BundledLanguage, ShikiTransformer } from 'shiki'
import { createHighlighter, guessEmbeddedLanguages, isSpecialLang } from 'shiki'
import type { Logger } from 'vite'
import { isShell } from '../../shared'
import type { MarkdownOptions, ThemeOptions } from '../markdown'
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10)
/**
* 2 steps:
* Prevents the leading '$' symbol etc from being selectable/copyable. Also
* normalizes its syntax so there's no leading spaces, and only a single
* trailing space.
*
* 1. convert attrs into line numbers:
* {4,7-13,16,23-27,40} -> [4,7,8,9,10,11,12,13,16,23,24,25,26,27,40]
* 2. convert line numbers into line options:
* [{ line: number, classes: string[] }]
* NOTE: Any changes to this function may also need to update
* `src/client/app/composables/copyCode.ts`
*/
function attrsToLines(attrs: string): TransformerCompactLineOption[] {
attrs = attrs.replace(/^(?:\[.*?\])?.*?([\d,-]+).*/, '$1').trim()
const result: number[] = []
if (!attrs) {
return []
}
attrs
.split(',')
.map((v) => v.split('-').map((v) => parseInt(v, 10)))
.forEach(([start, end]) => {
if (start && end) {
result.push(
...Array.from({ length: end - start + 1 }, (_, i) => start + i)
)
} else {
result.push(start)
function transformerDisableShellSymbolSelect(): ShikiTransformer {
return {
name: 'vitepress:disable-shell-symbol-select',
tokens(tokensByLine) {
if (!isShell(this.options.lang)) return
for (const tokens of tokensByLine) {
if (tokens.length < 2) continue
// The first token should only be a symbol token
const firstTokenText = tokens[0].content.trim()
if (firstTokenText !== '$' && firstTokenText !== '>') continue
// The second token must have a leading space (separates the symbol)
if (tokens[1].content[0] !== ' ') continue
tokens[0].content = firstTokenText + ' '
tokens[0].htmlStyle ??= {}
tokens[0].htmlStyle['user-select'] = 'none'
tokens[0].htmlStyle['-webkit-user-select'] = 'none'
tokens[1].content = tokens[1].content.slice(1)
}
})
return result.map((v) => ({
line: v,
classes: ['highlighted']
}))
}
}
}
export async function highlight(
@ -76,6 +78,7 @@ export async function highlight(
await options?.shikiSetup?.(highlighter)
const transformers: ShikiTransformer[] = [
transformerMetaHighlight(),
transformerNotationDiff(),
transformerNotationFocus({
classActiveLine: 'has-focus',
@ -83,6 +86,7 @@ export async function highlight(
}),
transformerNotationHighlight(),
transformerNotationErrorLevel(),
transformerDisableShellSymbolSelect(),
{
name: 'vitepress:add-dir',
pre(node) {
@ -91,20 +95,24 @@ export async function highlight(
}
]
const vueRE = /-vue(?=:|$)/
const lineNoStartRE = /=(\d*)/
const lineNoRE = /:(no-)?line-numbers(=\d*)?$/
const mustacheRE = /\{\{.*?\}\}/g
// keep in sync with ./preWrapper.ts#extractLang
const langRE = /^[a-zA-Z0-9-_]+/
const vueRE = /-vue$/
return [
async (str: string, lang: string, attrs: string) => {
const vPre = vueRE.test(lang) ? '' : 'v-pre'
lang =
lang
.replace(lineNoStartRE, '')
.replace(lineNoRE, '')
.replace(vueRE, '')
.toLowerCase() || defaultLang
async (str, lang, attrs) => {
const match = langRE.exec(lang)
if (match) {
const orig = lang
lang = match[0].toLowerCase()
attrs = orig.slice(lang.length).replace(/(?<!=)\{/g, ' {') + ' ' + attrs
attrs = attrs.trim().replace(/\s+/g, ' ')
}
lang ||= defaultLang
const vPre = !vueRE.test(lang)
if (!vPre) lang = lang.slice(0, -4)
try {
// https://github.com/shikijs/shiki/issues/952
@ -123,12 +131,11 @@ export async function highlight(
lang = defaultLang
}
const lineOptions = attrsToLines(attrs)
const mustaches = new Map<string, string>()
const removeMustache = (s: string) => {
if (vPre) return s
return s.replace(mustacheRE, (match) => {
return s.replace(/\{\{.*?\}\}/g, (match) => {
let marker = mustaches.get(match)
if (!marker) {
marker = nanoid()
@ -154,7 +161,6 @@ export async function highlight(
lang,
transformers: [
...transformers,
transformerCompactLineOptions(lineOptions),
{
name: 'vitepress:v-pre',
pre(node) {

@ -1,48 +0,0 @@
// Modified from https://github.com/egoist/markdown-it-highlight-lines
// Now this plugin is only used to normalize line attrs.
// The else part of line highlights logic is in './highlight.ts'.
import type { MarkdownItAsync } from 'markdown-it-async'
const RE = /{([\d,-]+)}/
export const highlightLinePlugin = (md: MarkdownItAsync) => {
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (...args) => {
const [tokens, idx] = args
const token = tokens[idx]
// 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) {
// 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]
}
if (!lines) {
lines = attr![0]
if (!lines || !/[\d,-]+/.test(lines)) {
return fence(...args)
}
}
token.info += ' ' + lines
return fence(...args)
}
}

@ -12,14 +12,14 @@ export const lineNumberPlugin = (md: MarkdownItAsync, enable = false) => {
const info = tokens[idx].info
if (
(!enable && !/:line-numbers($| |=)/.test(info)) ||
(enable && /:no-line-numbers($| )/.test(info))
(!enable && !/:line-numbers\b/.test(info)) ||
(enable && /:no-line-numbers\b/.test(info))
) {
return rawCode
}
let startLineNumber = 1
const matchStartLineNumber = info.match(/=(\d*)/)
const matchStartLineNumber = info.match(/=(\d+)/)
if (matchStartLineNumber && matchStartLineNumber[1]) {
startLineNumber = parseInt(matchStartLineNumber[1])
}

@ -44,12 +44,12 @@ export function extractTitle(info: string, html = false) {
return info.match(/\[(.*)\]/)?.[1] || extractLang(info) || 'txt'
}
function extractLang(info: string) {
return info
.trim()
.replace(/=(\d*)/, '')
.replace(/:(no-)?line-numbers({| |$|=\d*).*/, '')
.replace(/(-vue|{| ).*$/, '')
.replace(/^vue-html$/, 'template')
.replace(/^ansi$/, '')
function extractLang(info: string): string {
return (
/^[a-zA-Z0-9-_]+/
.exec(info)?.[0]
.replace(/-vue$/, '') // remove -vue suffix
.replace(/^vue-html$/, 'template')
.replace(/^ansi$/, '') || ''
)
}

@ -221,6 +221,7 @@ export async function createVitePressPlugin(
this.environment.mode === 'dev' &&
this.environment.name === 'client'
) {
logDeadLinks(deadLinks, siteConfig.logger, true)
const payload: PageDataPayload = {
path: `/${siteConfig.rewrites.map[relativePath] || relativePath}`,
pageData
@ -238,15 +239,7 @@ export async function createVitePressPlugin(
renderStart() {
if (allDeadLinks.length > 0) {
allDeadLinks.forEach(({ url, file }, i) => {
siteConfig.logger.warn(
c.yellow(
`${i === 0 ? '\n\n' : ''}(!) Found dead link ${c.cyan(
url
)} in file ${c.white(c.dim(file))}`
)
)
})
logDeadLinks(allDeadLinks, siteConfig.logger)
siteConfig.logger.info(
c.cyan(
'\nIf this is expected, you can disable this check via config. Refer: https://vitepress.dev/reference/site-config#ignoredeadlinks\n'
@ -423,3 +416,22 @@ export async function createVitePressPlugin(
await dynamicRoutesPlugin(siteConfig)
]
}
function logDeadLinks(
deadLinks: MarkdownCompileResult['deadLinks'],
logger: SiteConfig['logger'],
devMode = false
) {
const logged = new Set<string>()
deadLinks.forEach(({ url, file }, i) => {
const key = `${file}:::${url}`
if (logged.has(key)) return
logged.add(key)
const prefix = '\n'.repeat(i === 0 ? (devMode ? 1 : 2) : 0)
logger.warn(
c.yellow(
`${prefix}(!) Found dead link ${c.cyan(url)} in file ${c.white(c.dim(file))}`
)
)
})
}

@ -353,3 +353,8 @@ type ObjectType = Record<PropertyKey, any>
export function isObject(value: unknown): value is ObjectType {
return Object.prototype.toString.call(value) === '[object Object]'
}
const shellLangs = ['shellscript', 'shell', 'bash', 'sh', 'zsh']
export function isShell(lang: string): boolean {
return shellLangs.includes(lang)
}

Loading…
Cancel
Save