Compare commits

...

23 Commits

Author SHA1 Message Date
d91a20e65c merge upstream 2026-04-05 23:33:24 +08:00
pengzhanbo
cfc89adab8 chore: update security deps 2026-04-04 16:35:48 +08:00
pengzhanbo
e0ba59a6f9 build: update changelog 2026-04-03 02:56:28 +08:00
pengzhanbo
352874b29a build: publish v1.0.0-rc.193 2026-04-03 02:25:46 +08:00
pengzhanbo
c824ad85f4 chore: update gitignore 2026-04-03 02:18:23 +08:00
pengzhanbo
db2eda82f3 build: update clean scripts 2026-04-03 02:18:01 +08:00
pengzhanbo
e9fe35bc4f
fix(theme): fix sidebar items prefix not handled correctly, close #876 (#885) 2026-04-03 02:13:18 +08:00
pengzhanbo
709ade741c chore: improve comment 2026-04-03 02:06:32 +08:00
pengzhanbo
d8b79e89e8
refactor(plugin-search): improve search index update (#884) 2026-04-03 01:58:25 +08:00
pengzhanbo
dbc6f0be0f
fix(theme): fix auto-sidebar group icon error inherit, close #873 (#883) 2026-04-02 22:05:54 +08:00
pengzhanbo
9fe294b9dd fix(theme): fix MarkdownOptions types 2026-04-02 21:15:33 +08:00
pengzhanbo
ecf100cfc6 docs: update security.md 2026-04-02 21:15:08 +08:00
pengzhanbo
b7ee45642e docs: update contributing.md 2026-04-02 21:14:51 +08:00
pengzhanbo
54c05c8cea docs: add claude.md 2026-04-02 21:14:34 +08:00
pengzhanbo
86cb872ce6 refactor: migrate onWatched to onPageUpdated 2026-04-02 21:14:16 +08:00
pengzhanbo
a6cb3820b1 refactor: remove deprecated enhancement 2026-04-02 21:12:59 +08:00
pengzhanbo
184d1aee76 build: improve tsdown bundle config 2026-04-02 20:59:23 +08:00
pengzhanbo
cbc5c55891 perf: update deps to latest 2026-04-02 20:57:51 +08:00
mcenahle
4f40f8441d
docs: add "mcenahle Docs" to demo page (#882)
* Update demos.md

* chore: fix URLs for 哦麦 MC logo and preview images

Updated logo and preview image URLs for 哦麦 MC in demos.md.

---------

Co-authored-by: pengzhanbo <volodymyr@foxmail.com>
2026-04-02 20:54:47 +08:00
pengzhanbo
fe0d4bbc92
feat: improve accessibility features (#869) 2026-04-02 20:49:20 +08:00
pengzhanbo
39a76a35d7
feat(plugin-md-power)!: use # as the comment delimiter (#870) 2026-04-02 20:48:55 +08:00
pengzhanbo
a01bc13c66
fix(plugin-md-power): fix tsdown icon (#878) 2026-04-02 20:48:34 +08:00
pengzhanbo
1b213d4c28
fix(theme): add bulletin to outline ignores (#879) 2026-04-02 20:48:10 +08:00
63 changed files with 3865 additions and 4768 deletions

3
.gitignore vendored
View File

@ -15,3 +15,6 @@ dist/
coverage/ coverage/
.idea .idea
.claude/
!.claude/skills/

File diff suppressed because it is too large Load Diff

111
CLAUDE.md Normal file
View File

@ -0,0 +1,111 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
vuepress-theme-plume is a VuePress 2 theme monorepo for building blogs, documentation, and knowledge bases.
It includes a main theme, several plugins, a CLI tool, and example implementations.
## Commands
```bash
# Install dependencies
pnpm install
# Build all packages (required after clone, outputs to lib/)
pnpm build
# Development - runs theme + docs dev servers concurrently
pnpm dev
# Lint (eslint + stylelint)
pnpm lint
pnpm lint:fix # auto-fix
# Run tests (vitest)
pnpm test
# Run a single test file
pnpm test src/path/to/file.spec.ts
# Run tests related to changed files (for pre-commit)
cross-env TZ=Etc/UTC vitest related --run
# Build docs only
pnpm docs:build
# Serve docs locally
pnpm docs:serve
# Release workflow
pnpm release # runs lint + build + version bump + changelog + git commit
```
## Monorepo Structure
```txt
├── theme/ # Main VuePress theme (vuepress-theme-plume)
├── plugins/ # VuePress plugins
│ ├── plugin-search/ # Full-text fuzzy search
│ ├── plugin-md-power/ # Markdown enhancements
│ └── plugin-fonts/ # Special character font support
├── cli/ # CLI tool (create project scaffolding)
├── docs/ # Documentation site
└── examples/ # Example implementations
├── pure-blog/
└── layout-slots/
```
## Theme Architecture
The theme is organized into three layers:
- **`src/node/`** - Build-time code (runs during `vuepress build/dev`)
- `prepare/` - Content preparation (frontmatter parsing, collection resolution)
- `plugins/` - VuePress plugin registration
- `config/` - Theme configuration handling
- `autoFrontmatter/` - Automatic frontmatter generation
- **`src/client/`** - Client-side code (runs in browser)
- `components/` - Vue components
- `composables/` - Vue composables (outline, search, etc.)
- `styles/` - CSS/SCSS styles
- `features/` - Feature-specific components and logic
- **`src/shared/`** - Shared code (used by both node and client)
- `frontmatter/` - Frontmatter schemas and utilities
- `locale/` - i18n translations
- `options.ts` - Theme options types
- `features/` - Feature flags and shared feature logic
## Build Output
Each package uses [tsdown](https://tsdown.dev/) to compile TypeScript. Build output goes to `lib/`:
- `lib/node/` - Node-side exports
- `lib/client/` - Client-side exports
- `lib/shared/` - Shared exports
The `lib/` directory is gitignored and must be built with `pnpm build`.
## Testing
Tests use Vitest with coverage enabled. Test files are located at `**/__test__/**/*.spec.ts` and are excluded from coverage reports. Run tests with timezone fixed to UTC to ensure consistent results.
## Key Dependencies
- **VuePress**: v2.0.0-rc.28 with @vuepress/bundler-vite
- **Vue**: ^3.5.30
- **Shiki**: ^4.x for syntax highlighting
- **VueUse**: ^14.x for composables
- **markdown-it**: ^14.x for Markdown processing
## Development Notes
- Node.js 20.19.0+ required
- pnpm catalogs are used for dependency management (`dev`, `peer`, `prod`, `vuepress`)
- The theme depends on `vuepress-plugin-md-power` and `@vuepress-plume/plugin-search` as workspace dependencies
- Some peer dependencies are optional (e.g., artplayer, dashjs, three.js)
- Plugins (`plugins/*`) do not have dev commands — changes require `pnpm build` to take effect
- The `lib/` directory is gitignored and must be rebuilt after `pnpm install`

View File

@ -19,7 +19,7 @@ In the `plugins` directory:
Development requirements: Development requirements:
- [Node.js](http://nodejs.org/) version 20.6.0+ - [Node.js](http://nodejs.org/) version 20.19.0+
- [pnpm](https://pnpm.io/zh/) version 9+ - [pnpm](https://pnpm.io/zh/) version 9+
Clone the repository and install dependencies: Clone the repository and install dependencies:

View File

@ -19,7 +19,7 @@
开发要求: 开发要求:
- [Node.js](http://nodejs.org/) version 20.6.0+ - [Node.js](http://nodejs.org/) version 20.19.0+
- [pnpm](https://pnpm.io/zh/) version 9+ - [pnpm](https://pnpm.io/zh/) version 9+
克隆代码仓库,并安装依赖: 克隆代码仓库,并安装依赖:

View File

@ -4,8 +4,8 @@
| Version | Supported | | Version | Supported |
| ---------------- | ------------------ | | ---------------- | ------------------ |
| >= 1.0.0-rc.170 | :white_check_mark: | | >= 1.0.0-rc.190 | :white_check_mark: |
| < 1.0.0-rc.170 | :x: | | < 1.0.0-rc.190 | :x: |
## Reporting a Vulnerability ## Reporting a Vulnerability

View File

@ -1,7 +1,7 @@
{ {
"name": "create-vuepress-theme-plume", "name": "create-vuepress-theme-plume",
"type": "module", "type": "module",
"version": "1.0.0-rc.192", "version": "1.0.0-rc.193",
"description": "The cli for create vuepress-theme-plume's project", "description": "The cli for create vuepress-theme-plume's project",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)", "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT", "license": "MIT",
@ -27,7 +27,7 @@
"templates" "templates"
], ],
"scripts": { "scripts": {
"build": "tsdown" "build": "tsdown --config-loader unrun"
}, },
"dependencies": { "dependencies": {
"@clack/prompts": "catalog:prod", "@clack/prompts": "catalog:prod",

View File

@ -93,6 +93,12 @@ docs:
logo: https://official.skycraft.cn/i/3.jpg logo: https://official.skycraft.cn/i/3.jpg
url: https://docs.skycraft.cn/ url: https://docs.skycraft.cn/
preview: https://bbsimage.skycraft.cn/docs-preview.jpg preview: https://bbsimage.skycraft.cn/docs-preview.jpg
-
name: mcenahle Docs
desc: mcenahle 的文档网站。
logo: https://d.mcenahle.cn/images/logo.png
url: https://d.mcenahle.cn/
preview: https://mcenahle.cn/resources/docs-site-preview.jpg
blog: blog:
- -

View File

@ -3,6 +3,9 @@ title: File Tree
createTime: 2025/10/08 14:41:57 createTime: 2025/10/08 14:41:57
icon: mdi:file-tree icon: mdi:file-tree
permalink: /en/guide/markdown/file-tree/ permalink: /en/guide/markdown/file-tree/
badge:
text: Change
type: warning
--- ---
## Overview ## Overview
@ -18,7 +21,7 @@ displayed, simply add a slash `/` at the end of the list item.
The following syntax can be used to customize the appearance of the file tree: The following syntax can be used to customize the appearance of the file tree:
- Emphasize file or directory names by making them bold, e.g., `**README.md**` - Emphasize file or directory names by making them bold, e.g., `**README.md**`
- Add comments to files or directories by adding additional text after the name - Add comments to files or directories by appending a comment starting with `#` after the name, for example, `README.md This is a README file`
- Mark files or directories as **added** or **deleted** by prefixing the name with `++` or `--` - Mark files or directories as **added** or **deleted** by prefixing the name with `++` or `--`
- Use `...` or `…` as the name to add placeholder files and directories. - Use `...` or `…` as the name to add placeholder files and directories.
- Add `icon="simple"` or `icon="colored"` after `:::file-tree` to switch to simple icons or colored icons. The default is colored icons. - Add `icon="simple"` or `icon="colored"` after `:::file-tree` to switch to simple icons or colored icons. The default is colored icons.
@ -34,7 +37,7 @@ The following syntax can be used to customize the appearance of the file tree:
- ++ config.ts - ++ config.ts
- -- page1.md - -- page1.md
- README.md - README.md
- theme A **theme** directory - theme # A **theme** directory
- client - client
- components - components
- **Navbar.vue** - **Navbar.vue**
@ -61,7 +64,7 @@ The following syntax can be used to customize the appearance of the file tree:
- ++ config.ts - ++ config.ts
- -- page1.md - -- page1.md
- README.md - README.md
- theme A **theme** directory - theme # A **theme** directory
- client - client
- components - components
- **Navbar.vue** - **Navbar.vue**

View File

@ -28,10 +28,10 @@ A typical VuePress static site has the following file structure:
:::file-tree :::file-tree
- my-site - my-site
- docs \# Source directory - docs # Source directory
- .vuepress/ - .vuepress/
- … - …
- README.md \# Homepage - README.md # Homepage
- package.json - package.json
::: :::

View File

@ -13,22 +13,22 @@ For projects created via the [command-line tool](./usage.md#command-line-install
::: file-tree ::: file-tree
- .git/ - .git/
- **docs** \# Documentation source directory - **docs** # Documentation source directory
- .vuepress \# VuePress configuration directory - .vuepress/ # VuePress configuration directory
- public/ \# Static assets - public/ # Static assets
- client.ts \# Client configuration (optional) - client.ts # Client configuration (optional)
- collections.ts \# Collections configuration (optional) - collections.ts # Collections configuration (optional)
- config.ts \# VuePress main configuration - config.ts # VuePress main configuration
- navbar.ts \# Navbar configuration (optional) - navbar.ts # Navbar configuration (optional)
- plume.config.ts \# Theme configuration file (optional) - plume.config.ts # Theme configuration file (optional)
- demo \# `doc` type collection - demo # `doc` type collection
- foo.md - foo.md
- bar.md - bar.md
- blog \# `post` type collection - blog # `post` type collection
- preview \# Blog category - preview # Blog category
- markdown.md \# Category article - markdown.md # Category article
- article.md \# Blog article - article.md # Blog article
- README.md \# Site homepage - README.md # Site homepage
- … - …
- package.json - package.json
- pnpm-lock.yaml - pnpm-lock.yaml

View File

@ -33,7 +33,7 @@ A typical project structure might look like:
- rust # Rust Programming Notes - rust # Rust Programming Notes
- tuple.md - tuple.md
- struct.md - struct.md
- README.md # Site Homepage - README.md # Site Homepage
::: :::
### Configuration via `sidebar` ### Configuration via `sidebar`

View File

@ -139,14 +139,14 @@ The numeric part serves as the **sorting basis**. Directories without numbers ar
::: file-tree ::: file-tree
- docs - docs
- blog \# post type collection - blog # post type collection
- 1.Frontend - 1.Frontend
- 1.html/ - 1.html/
- 2.css/ - 2.css/
- 3.javascript/ - 3.javascript/
- 2.Backend/ - 2.Backend/
- DevOps/ - DevOps/
- typescript \# doc type collection - typescript # doc type collection
- 1.Basics - 1.Basics
- 1.Variables.md - 1.Variables.md
- 2.Types.md - 2.Types.md

View File

@ -3,6 +3,9 @@ title: 文件树
createTime: 2024/09/30 14:41:57 createTime: 2024/09/30 14:41:57
icon: mdi:file-tree icon: mdi:file-tree
permalink: /guide/markdown/file-tree/ permalink: /guide/markdown/file-tree/
badge:
text: 变更
type: warning
--- ---
## 概述 ## 概述
@ -17,12 +20,19 @@ permalink: /guide/markdown/file-tree/
以下语法可用于自定义文件树的外观: 以下语法可用于自定义文件树的外观:
- 通过加粗文件名或目录名来突出显示,例如 `**README.md**` - 通过加粗文件名或目录名来突出显示,例如 `**README.md**`
- 通过在名称后添加更多文本来为文件或目录添加注释 - 通过在名称后添加`#` 开头的注释来为文件或目录添加注释,例如 `README.md # 这是一个 README 文件`
- 通过在名称前添加 `++``--` 来标记文件或目录为 **新增** 或 **删除** - 通过在名称前添加 `++``--` 来标记文件或目录为 **新增** 或 **删除**
- 使用 `...``…` 作为名称来添加占位符文件和目录。 - 使用 `...``…` 作为名称来添加占位符文件和目录。
- 在 `:::file-tree` 后添加 `icon="simple"` 或 添加 `icon="colored"` 可以切换为简单图标或彩色图标,默认为彩色图标。 - 在 `:::file-tree` 后添加 `icon="simple"` 或 添加 `icon="colored"` 可以切换为简单图标或彩色图标,默认为彩色图标。
- 在 `:::file-tree` 后添加 `title="xxxx"` 可以为文件树添加标题。 - 在 `:::file-tree` 后添加 `title="xxxx"` 可以为文件树添加标题。
::: important `rc.193` 主题更新说明
过去 `file-tree` 使用 **空格** 来区分文件名和注释,这在某些情况下会导致问题,例如文件名中包含空格时。
为了解决这个问题,我们引入了 **# 号注释** 语法,您可以在文件名后添加以 `#` 开头的注释,例如 `README.md # 这是一个 README 文件`
**此修改为 ==破坏性更新=={.danger} 更新。**
:::
**输入:** **输入:**
```md /++/ /--/ ```md /++/ /--/
@ -33,7 +43,7 @@ permalink: /guide/markdown/file-tree/
- ++ config.ts - ++ config.ts
- -- page1.md - -- page1.md
- README.md - README.md
- theme 一个 **主题** 目录 - theme # 一个 **主题** 目录
- client - client
- components - components
- **Navbar.vue** - **Navbar.vue**
@ -60,7 +70,7 @@ permalink: /guide/markdown/file-tree/
- ++ config.ts - ++ config.ts
- -- page1.md - -- page1.md
- README.md - README.md
- theme 一个 **主题** 目录 - theme # 一个 **主题** 目录
- client - client
- components - components
- **Navbar.vue** - **Navbar.vue**

View File

@ -27,10 +27,10 @@ permalink: /guide/collection/
:::file-tree :::file-tree
- my-site - my-site
- docs \# 源目录 - docs # 源目录
- .vuepress/ - .vuepress/
- … - …
- README.md \# 首页 - README.md # 首页
- package.json - package.json
::: :::

View File

@ -12,22 +12,22 @@ permalink: /guide/project-structure/
::: file-tree ::: file-tree
- .git/ - .git/
- **docs** \# 文档源目录 - **docs** # 文档源目录
- .vuepress \# VuePress 配置目录 - .vuepress/ # VuePress 配置目录
- public/ \# 静态资源 - public/ # 静态资源
- client.ts \# 客户端配置(可选) - client.ts # 客户端配置(可选)
- collections.ts \# Collections 配置(可选) - collections.ts # Collections 配置(可选)
- config.ts \# VuePress 主配置 - config.ts # VuePress 主配置
- navbar.ts \# 导航栏配置(可选) - navbar.ts # 导航栏配置(可选)
- plume.config.ts \# 主题配置文件(可选) - plume.config.ts # 主题配置文件(可选)
- demo \# `doc` 类型 collection - demo # `doc` 类型 collection
- foo.md - foo.md
- bar.md - bar.md
- blog \# `post` 类型 collection - blog # `post` 类型 collection
- preview \# 博客分类 - preview # 博客分类
- markdown.md \# 分类文章 - markdown.md # 分类文章
- article.md \# 博客文章 - article.md # 博客文章
- README.md \# 站点首页 - README.md # 站点首页
- … - …
- package.json - package.json
- pnpm-lock.yaml - pnpm-lock.yaml

View File

@ -33,7 +33,7 @@ tags:
- rust # Rust 编程笔记 - rust # Rust 编程笔记
- tuple.md - tuple.md
- struct.md - struct.md
- README.md # 站点首页 - README.md # 站点首页
::: :::
### 通过`sidebar` 配置 ### 通过`sidebar` 配置

View File

@ -130,14 +130,14 @@ const dir = /\d+\.[\s\S]+/
::: file-tree ::: file-tree
- docs - docs
- blog \# post 类型 collection - blog # post 类型 collection
- 1.前端 - 1.前端
- 1.html/ - 1.html/
- 2.css/ - 2.css/
- 3.javascript/ - 3.javascript/
- 2.后端/ - 2.后端/
- 运维/ - 运维/
- typescript \# doc 类型 collection - typescript # doc 类型 collection
- 1.基础 - 1.基础
- 1.变量.md - 1.变量.md
- 2.类型.md - 2.类型.md

View File

@ -3,7 +3,7 @@
"type": "module", "type": "module",
"version": "1.0.0-rc.192", "version": "1.0.0-rc.192",
"private": true, "private": true,
"packageManager": "pnpm@10.30.3", "packageManager": "pnpm@10.33.0",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)", "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
@ -18,9 +18,9 @@
"pnpm": ">=9" "pnpm": ">=9"
}, },
"scripts": { "scripts": {
"build": "pnpm clean && pnpm build:package", "build": "pnpm run clean && pnpm build:package",
"build:package": "pnpm -r --stream build", "build:package": "pnpm -r --stream build",
"clean": "pnpm -r --stream clean", "clean": "pnpm -r --stream run clean",
"dev": "pnpm --stream '/(dev:package|docs:dev)/'", "dev": "pnpm --stream '/(dev:package|docs:dev)/'",
"dev:package": "pnpm --parallel dev", "dev:package": "pnpm --parallel dev",
"docs:dev": "wait-on -d 100 theme/lib/node/index.js && pnpm -F=docs docs:dev", "docs:dev": "wait-on -d 100 theme/lib/node/index.js && pnpm -F=docs docs:dev",
@ -36,7 +36,7 @@
"lint:css": "stylelint **/*.{css,vue}", "lint:css": "stylelint **/*.{css,vue}",
"test": "cross-env TZ=Etc/UTC vitest --coverage", "test": "cross-env TZ=Etc/UTC vitest --coverage",
"prepare": "husky", "prepare": "husky",
"release:changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", "release:changelog": "conventional-changelog -p angular",
"release:check": "pnpm lint && pnpm build", "release:check": "pnpm lint && pnpm build",
"release:sync": "node scripts/mirror-sync.mjs", "release:sync": "node scripts/mirror-sync.mjs",
"release:publish": "pnpm -r publish --tag latest", "release:publish": "pnpm -r publish --tag latest",
@ -60,7 +60,8 @@
"@vitest/coverage-v8": "catalog:dev", "@vitest/coverage-v8": "catalog:dev",
"bumpp": "catalog:dev", "bumpp": "catalog:dev",
"commitizen": "catalog:dev", "commitizen": "catalog:dev",
"conventional-changelog-cli": "catalog:dev", "conventional-changelog": "catalog:dev",
"conventional-changelog-angular": "catalog:dev",
"cpx2": "catalog:dev", "cpx2": "catalog:dev",
"cross-env": "catalog:dev", "cross-env": "catalog:dev",
"cz-conventional-changelog": "catalog:dev", "cz-conventional-changelog": "catalog:dev",
@ -90,12 +91,19 @@
"resolutions": { "resolutions": {
"@bufbuild/protobuf": "^2.11.0", "@bufbuild/protobuf": "^2.11.0",
"@eslint-community/eslint-utils": "catalog:peer", "@eslint-community/eslint-utils": "catalog:peer",
"@shikijs/core": "^4.0.2",
"@shikijs/twoslash": "^4.0.2",
"@typescript-eslint/types": "catalog:peer", "@typescript-eslint/types": "catalog:peer",
"@typescript-eslint/utils": "catalog:peer", "@typescript-eslint/utils": "catalog:peer",
"baseline-browser-mapping": "^2.10.0", "@xmldom/xmldom": ">=0.9.9",
"baseline-browser-mapping": "^2.10.13",
"chokidar": "catalog:prod", "chokidar": "catalog:prod",
"esbuild": "catalog:prod", "esbuild": "catalog:prod",
"lodash": ">=4.18.1",
"lodash-es": ">=4.18.1",
"sass-embedded": "catalog:peer", "sass-embedded": "catalog:peer",
"shiki": "^4.0.2",
"tmp": ">=0.2.5",
"vite": "catalog:dev", "vite": "catalog:dev",
"vue-router": "catalog:prod" "vue-router": "catalog:prod"
}, },

View File

@ -1,7 +1,7 @@
{ {
"name": "@vuepress-plume/plugin-fonts", "name": "@vuepress-plume/plugin-fonts",
"type": "module", "type": "module",
"version": "1.0.0-rc.192", "version": "1.0.0-rc.193",
"description": "The Plugin for VuePress 2 - fonts", "description": "The Plugin for VuePress 2 - fonts",
"author": "pengzhanbo <volodymyr@foxmail.com>", "author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT", "license": "MIT",
@ -30,7 +30,7 @@
"build": "pnpm run tsdown && pnpm run copy", "build": "pnpm run tsdown && pnpm run copy",
"clean": "rimraf --glob ./lib", "clean": "rimraf --glob ./lib",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png,woff2}\" lib", "copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png,woff2}\" lib",
"tsdown": "tsdown" "tsdown": "tsdown --config-loader unrun"
}, },
"peerDependencies": { "peerDependencies": {
"vuepress": "catalog:vuepress" "vuepress": "catalog:vuepress"

View File

@ -1,7 +1,5 @@
import { defineConfig } from 'tsdown' import { defineConfig, type UserConfig } from 'tsdown'
import { argv } from '../../scripts/tsdown-args.mjs' import { argv } from '../../scripts/tsdown-args'
/** @import {Options} from 'tsdown' */
const clientExternal = [ const clientExternal = [
/.*\.vue$/, /.*\.vue$/,
@ -9,15 +7,13 @@ const clientExternal = [
] ]
export default defineConfig(() => { export default defineConfig(() => {
/** @type {Options} */ const DEFAULT_OPTIONS: UserConfig = {
const DEFAULT_OPTIONS = {
dts: true, dts: true,
sourcemap: false, sourcemap: false,
format: 'esm', format: 'esm',
fixedExtension: false, fixedExtension: false,
} }
/** @type {Options[]} */ const options: UserConfig[] = []
const options = []
if (argv.node) { if (argv.node) {
options.push({ options.push({
@ -36,7 +32,7 @@ export default defineConfig(() => {
entry: ['./src/client/config.ts'], entry: ['./src/client/config.ts'],
outDir: './lib/client', outDir: './lib/client',
dts: false, dts: false,
external: clientExternal, deps: { neverBundle: clientExternal },
}, },
]) ])
} }

View File

@ -72,7 +72,7 @@ describe('fileTreePlugin', () => {
- client - client
- components - components
- **Navbar.vue** - **Navbar.vue**
- index.ts \# comment - index.ts # comment
- node - node
- index.ts - index.ts
- .gitignore - .gitignore

View File

@ -1,7 +1,7 @@
{ {
"name": "vuepress-plugin-md-power", "name": "vuepress-plugin-md-power",
"type": "module", "type": "module",
"version": "1.0.0-rc.192", "version": "1.0.0-rc.193",
"description": "The Plugin for VuePress 2 - markdown power", "description": "The Plugin for VuePress 2 - markdown power",
"author": "pengzhanbo <volodymyr@foxmail.com>", "author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT", "license": "MIT",
@ -36,8 +36,8 @@
"clean": "rimraf --glob ./lib", "clean": "rimraf --glob ./lib",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib", "copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"copy:watch": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib -w", "copy:watch": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib -w",
"tsdown": "tsdown", "tsdown": "tsdown --config-loader unrun",
"tsdown:watch": "tsdown --watch -- -c" "tsdown:watch": "tsdown --config-loader unrun --watch -- -c"
}, },
"peerDependencies": { "peerDependencies": {
"artplayer": "catalog:peer", "artplayer": "catalog:peer",

View File

@ -119,9 +119,9 @@ export function parseFileTreeNodeInfo(info: string): FileTreeNodeProps {
// Extract filename and comment // Extract filename and comment
if (filename === '' && !focus) { if (filename === '' && !focus) {
const spaceIndex = info.indexOf(' ') const sharpIndex = info.indexOf('#')
filename = info.slice(0, spaceIndex === -1 ? info.length : spaceIndex) filename = info.slice(0, sharpIndex === -1 ? info.length : sharpIndex).trim()
info = spaceIndex === -1 ? '' : info.slice(spaceIndex) info = sharpIndex === -1 ? '' : info.slice(sharpIndex)
} }
comment = info.trim() comment = info.trim()

View File

@ -559,11 +559,11 @@ export const definitions: Definitions = {
'rolldown.config.prod.cjs': 'vscode-icons:file-type-light-rolldown', 'rolldown.config.prod.cjs': 'vscode-icons:file-type-light-rolldown',
'rolldown.config.prod.mjs': 'vscode-icons:file-type-light-rolldown', 'rolldown.config.prod.mjs': 'vscode-icons:file-type-light-rolldown',
'rolldown.config.prod.ts': 'vscode-icons:file-type-light-rolldown', 'rolldown.config.prod.ts': 'vscode-icons:file-type-light-rolldown',
'tsdown.config.js': 'vscode-icons:file-type-light-tsdown', 'tsdown.config.js': 'vscode-icons:file-type-tsdown',
'tsdown.config.cjs': 'vscode-icons:file-type-light-tsdown', 'tsdown.config.cjs': 'vscode-icons:file-type-tsdown',
'tsdown.config.mjs': 'vscode-icons:file-type-light-tsdown', 'tsdown.config.mjs': 'vscode-icons:file-type-tsdown',
'tsdown.config.ts': 'vscode-icons:file-type-light-tsdown', 'tsdown.config.ts': 'vscode-icons:file-type-tsdown',
'tsdown.config.json': 'vscode-icons:file-type-light-tsdown', 'tsdown.config.json': 'vscode-icons:file-type-tsdown',
'.oxlintignore': 'vscode-icons:file-type-oxc', '.oxlintignore': 'vscode-icons:file-type-oxc',
'.oxlintrc.json': 'vscode-icons:file-type-oxc', '.oxlintrc.json': 'vscode-icons:file-type-oxc',

View File

@ -1,7 +1,5 @@
import { defineConfig } from 'tsdown' import { defineConfig, type UserConfig } from 'tsdown'
import { argv } from '../../scripts/tsdown-args.mjs' import { argv } from '../../scripts/tsdown-args'
/** @import {Options} from 'tsdown' */
const config = [ const config = [
{ dir: 'composables', files: ['codeRepl.ts', 'pdf.ts', 'rustRepl.ts', 'size.ts', 'audio.ts', 'demo.ts', 'mark.ts', 'decrypt.ts'] }, { dir: 'composables', files: ['codeRepl.ts', 'pdf.ts', 'rustRepl.ts', 'size.ts', 'audio.ts', 'demo.ts', 'mark.ts', 'decrypt.ts'] },
@ -18,8 +16,7 @@ const clientExternal = [
] ]
export default defineConfig((cli) => { export default defineConfig((cli) => {
/** @type {Options} */ const DEFAULT_OPTIONS: UserConfig = {
const DEFAULT_OPTIONS = {
dts: true, dts: true,
sourcemap: false, sourcemap: false,
format: 'esm', format: 'esm',
@ -27,8 +24,7 @@ export default defineConfig((cli) => {
fixedExtension: false, fixedExtension: false,
} }
/** @type {Options[]} */ const options: UserConfig[] = []
const options = []
// shared // shared
options.push({ options.push({
@ -43,7 +39,7 @@ export default defineConfig((cli) => {
entry: ['./src/node/index.ts'], entry: ['./src/node/index.ts'],
outDir: './lib/node', outDir: './lib/node',
target: 'node20.19.0', target: 'node20.19.0',
external: ['markdown-it', /^@?vuepress/], deps: { neverBundle: ['markdown-it', /^@?vuepress/] },
}) })
} }
@ -52,7 +48,7 @@ export default defineConfig((cli) => {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: files.map(file => `./src/client/${dir}/${file}`), entry: files.map(file => `./src/client/${dir}/${file}`),
outDir: `./lib/client/${dir}`, outDir: `./lib/client/${dir}`,
external: clientExternal, deps: { neverBundle: clientExternal },
}))) })))
} }
return options return options

View File

@ -1,7 +1,7 @@
{ {
"name": "@vuepress-plume/plugin-search", "name": "@vuepress-plume/plugin-search",
"type": "module", "type": "module",
"version": "1.0.0-rc.192", "version": "1.0.0-rc.193",
"description": "The Plugin for VuePress 2 - local search", "description": "The Plugin for VuePress 2 - local search",
"author": "pengzhanbo <volodymyr@foxmail.com>", "author": "pengzhanbo <volodymyr@foxmail.com>",
"license": "MIT", "license": "MIT",
@ -34,7 +34,7 @@
"build": "pnpm run tsdown && pnpm run copy", "build": "pnpm run tsdown && pnpm run copy",
"clean": "rimraf --glob ./lib", "clean": "rimraf --glob ./lib",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib", "copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib",
"tsdown": "tsdown" "tsdown": "tsdown --config-loader unrun"
}, },
"peerDependencies": { "peerDependencies": {
"vuepress": "catalog:vuepress" "vuepress": "catalog:vuepress"

View File

@ -31,6 +31,11 @@ export interface SearchIndexOptions {
isSearchable: SearchPluginOptions['isSearchable'] isSearchable: SearchPluginOptions['isSearchable']
} }
interface UpdateSearchIndexOptions extends Omit<SearchIndexOptions, 'app'> {
/** VuePress page instance / VuePress 页面实例 */
page: Page
}
/** /**
* Internal index object structure for MiniSearch. * Internal index object structure for MiniSearch.
* *
@ -165,29 +170,25 @@ export async function prepareSearchIndex({
* *
* *
* *
* @param filepath - Path of the modified file relative to project root / * @param app - VuePress application instance / VuePress
* @param options - Search index preparation options / * @param options - Search index preparation options /
* @param options.app - VuePress application instance / VuePress * @param options.page - VuePress page instance / VuePress
* @param options.isSearchable - Function to filter searchable pages / * @param options.isSearchable - Function to filter searchable pages /
* @param options.searchOptions - MiniSearch configuration / MiniSearch * @param options.searchOptions - MiniSearch configuration / MiniSearch
*/ */
export async function onSearchIndexUpdated( export async function onSearchIndexUpdated(
filepath: string, app: App,
{ {
app, page,
isSearchable, isSearchable,
searchOptions, searchOptions,
}: SearchIndexOptions, }: UpdateSearchIndexOptions,
): Promise<void> { ): Promise<void> {
const pages = isSearchable ? app.pages.filter(isSearchable) : app.pages if (isSearchable && !isSearchable(page))
if (pages.some(p => p.filePathRelative?.endsWith(filepath))) { return
await indexFile(
app.pages.find(p => p.filePathRelative?.endsWith(filepath))!, await indexFile(page, searchOptions, isSearchable)
searchOptions, await writeTemp(app)
isSearchable,
)
await writeTemp(app)
}
} }
/** /**
@ -195,23 +196,24 @@ export async function onSearchIndexUpdated(
* *
* *
* *
* @param filepath - Path of the removed file relative to project root / * @param app - VuePress application instance / VuePress
* @param options - Search index preparation options / * @param options - Search index preparation options /
* @param options.app - VuePress application instance / VuePress * @param options.page - VuePress page instance / VuePress
* @param options.isSearchable - Function to filter searchable pages / * @param options.isSearchable - Function to filter searchable pages /
* @param options.searchOptions - MiniSearch configuration / MiniSearch * @param options.searchOptions - MiniSearch configuration / MiniSearch
*/ */
export async function onSearchIndexRemoved( export async function onSearchIndexRemoved(
filepath: string, app: App,
{ {
app, page,
isSearchable, isSearchable,
searchOptions, searchOptions,
}: SearchIndexOptions, }: UpdateSearchIndexOptions,
): Promise<void> { ): Promise<void> {
const pages = isSearchable ? app.pages.filter(isSearchable) : app.pages if (isSearchable && !isSearchable(page))
if (pages.some(p => p.filePathRelative?.endsWith(filepath))) { return
const page = app.pages.find(p => p.filePathRelative?.endsWith(filepath))!
if (page.filePathRelative) {
const fileId = page.path const fileId = page.path
const locale = page.pathLocale const locale = page.pathLocale
const lang = page.lang const lang = page.lang

View File

@ -1,7 +1,6 @@
import type { Plugin } from 'vuepress/core' import type { Plugin } from 'vuepress/core'
import type { SearchPluginOptions } from '../shared/index.js' import type { SearchPluginOptions } from '../shared/index.js'
import { addViteOptimizeDepsInclude, getFullLocaleConfig } from '@vuepress/helper' import { addViteOptimizeDepsInclude, getFullLocaleConfig } from '@vuepress/helper'
import chokidar from 'chokidar'
import { getDirname, path } from 'vuepress/utils' import { getDirname, path } from 'vuepress/utils'
import { SEARCH_LOCALES } from './locales/index.js' import { SEARCH_LOCALES } from './locales/index.js'
import { onSearchIndexRemoved, onSearchIndexUpdated, prepareSearchIndex, prepareSearchIndexPlaceholder } from './prepareSearchIndex.js' import { onSearchIndexRemoved, onSearchIndexUpdated, prepareSearchIndex, prepareSearchIndexPlaceholder } from './prepareSearchIndex.js'
@ -72,22 +71,16 @@ export function searchPlugin({
} }
}, },
onWatched: (app, watchers) => { onPageUpdated: async (app, type, page) => {
const searchIndexWatcher = chokidar.watch('pages', { if (!page?.filePathRelative)
cwd: app.dir.temp(), return
ignoreInitial: true,
ignored: (filepath, stats) => Boolean(stats?.isFile()) && !filepath.endsWith('.js'), if (type === 'create' || type === 'update') {
}) await onSearchIndexUpdated(app, { page, isSearchable, searchOptions })
searchIndexWatcher.on('add', (filepath) => { }
onSearchIndexUpdated(filepath, { app, isSearchable, searchOptions }) else if (type === 'delete') {
}) await onSearchIndexRemoved(app, { page, isSearchable, searchOptions })
searchIndexWatcher.on('change', (filepath) => { }
onSearchIndexUpdated(filepath, { app, isSearchable, searchOptions })
})
searchIndexWatcher.on('unlink', (filepath) => {
onSearchIndexRemoved(filepath, { app, isSearchable, searchOptions })
})
watchers.push(searchIndexWatcher)
}, },
}) })
} }

View File

@ -1,7 +1,5 @@
import { defineConfig } from 'tsdown' import { defineConfig, type UserConfig } from 'tsdown'
import { argv } from '../../scripts/tsdown-args.mjs' import { argv } from '../../scripts/tsdown-args'
/** @import {Options} from 'tsdown' */
const sharedExternal = [ const sharedExternal = [
/.*\/shared\/index\.js$/, /.*\/shared\/index\.js$/,
@ -15,16 +13,14 @@ const clientExternal = [
] ]
export default defineConfig(() => { export default defineConfig(() => {
/** @type {Options} */ const DEFAULT_OPTIONS: UserConfig = {
const DEFAULT_OPTIONS = {
dts: true, dts: true,
sourcemap: false, sourcemap: false,
format: 'esm', format: 'esm',
fixedExtension: false, fixedExtension: false,
} }
/** @type {Options[]} */ const options: UserConfig[] = []
const options = []
// shared // shared
options.push({ options.push({
@ -38,7 +34,7 @@ export default defineConfig(() => {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/node/index.ts'], entry: ['./src/node/index.ts'],
outDir: './lib/node', outDir: './lib/node',
external: sharedExternal, deps: { neverBundle: sharedExternal },
target: 'node20.19.0', target: 'node20.19.0',
}) })
} }
@ -50,21 +46,21 @@ export default defineConfig(() => {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/client/utils/index.ts'], entry: ['./src/client/utils/index.ts'],
outDir: './lib/client/utils', outDir: './lib/client/utils',
external: clientExternal, deps: { neverBundle: clientExternal },
}, },
// client/composables/index.js // client/composables/index.js
{ {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/client/composables/index.ts'], entry: ['./src/client/composables/index.ts'],
outDir: './lib/client/composables', outDir: './lib/client/composables',
external: clientExternal, deps: { neverBundle: clientExternal },
}, },
// client/config.js // client/config.js
{ {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/client/config.ts'], entry: ['./src/client/config.ts'],
outDir: './lib/client', outDir: './lib/client',
external: clientExternal, deps: { neverBundle: clientExternal },
dts: false, dts: false,
}, },
// client/index.js // client/index.js
@ -72,10 +68,10 @@ export default defineConfig(() => {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/client/index.ts'], entry: ['./src/client/index.ts'],
outDir: './lib/client', outDir: './lib/client',
external: [ deps: { neverBundle: [
...clientExternal, ...clientExternal,
'./composables/index.js', './composables/index.js',
], ] },
}, },
]) ])
} }

7220
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
catalogMode: prefer catalogMode: prefer
ignoreWorkspaceRootCheck: true ignoreWorkspaceRootCheck: true
shamefullyHoist: true shamefullyHoist: true
shellEmulator: true shellEmulator: true
strictPeerDependencies: false strictPeerDependencies: false
@ -9,117 +11,110 @@ trustPolicyExclude:
- cached-factory - cached-factory
- memfs - memfs
- semver - semver
- '@mdit/plugin-attrs'
- '@mdit/plugin-mark'
- '@mdit/plugin-sup'
- '@mdit/plugin-sub'
- '@mdit/plugin-tab'
packages: packages:
- docs - docs
- theme - theme
- cli - cli
- plugins/* - plugins/*
- examples/* - examples/*
patchedDependencies: patchedDependencies:
floating-vue: patches/floating-vue.patch floating-vue: patches/floating-vue.patch
catalogs: catalogs:
dev: dev:
'@commitlint/cli': ^20.4.3 '@commitlint/cli': ^20.5.0
'@commitlint/config-conventional': ^20.4.3 '@commitlint/config-conventional': ^20.5.0
'@lunariajs/core': ^0.1.1 '@lunariajs/core': ^0.1.1
'@pengzhanbo/eslint-config-vue': ^2.1.0 '@pengzhanbo/eslint-config-vue': ^2.2.0
'@pengzhanbo/stylelint-config': ^2.1.0 '@pengzhanbo/stylelint-config': ^2.2.0
'@simonwep/pickr': ^1.9.1 '@simonwep/pickr': ^1.9.1
'@types/express': ^5.0.6 '@types/express': ^5.0.6
'@types/js-yaml': ^4.0.9 '@types/js-yaml': ^4.0.9
'@types/less': ^3.0.8 '@types/less': ^3.0.8
'@types/markdown-it': ^14.1.2 '@types/markdown-it': ^14.1.2
'@types/minimist': ^1.2.5 '@types/minimist': ^1.2.5
'@types/node': ^25.3.3 '@types/node': ^25.5.0
'@types/picomatch': ^4.0.2 '@types/picomatch': ^4.0.2
'@types/qrcode': ^1.5.6 '@types/qrcode': ^1.5.6
'@types/stylus': ^0.48.43 '@types/stylus': ^0.48.43
'@types/three': ^0.183.1 '@types/three': ^0.183.1
'@types/webpack-env': ^1.18.8 '@types/webpack-env': ^1.18.8
'@vitest/coverage-v8': ^4.0.18 '@vitest/coverage-v8': ^4.1.2
bumpp: ^10.4.1 bumpp: ^11.0.1
commitizen: ^4.3.1 commitizen: ^4.3.1
conventional-changelog-cli: ^5.0.0 conventional-changelog: ^7.2.0
conventional-changelog-angular: ^8.3.1
cpx2: ^8.0.0 cpx2: ^8.0.0
cross-env: 7.0.3 cross-env: 7.0.3
cz-conventional-changelog: ^3.3.0 cz-conventional-changelog: ^3.3.0
eslint: ^10.0.2 eslint: ^10.1.0
http-server: ^14.1.1 http-server: ^14.1.1
husky: ^9.1.7 husky: ^9.1.7
less: ^4.5.1 less: ^4.6.4
lint-staged: ^16.3.2 lint-staged: ^16.4.0
markdown-it: ^14.1.1 markdown-it: ^14.1.1
memfs: ^4.56.11 memfs: ^4.57.1
mermaid: ^11.12.3 mermaid: ^11.14.0
minimist: ^1.2.8 minimist: ^1.2.8
postcss: ^8.5.8 postcss: ^8.5.8
rimraf: ^6.1.3 rimraf: ^6.1.3
stylelint: ^17.4.0 stylelint: ^17.6.0
stylus: ^0.64.0 stylus: ^0.64.0
tsconfig-vuepress: ^7.0.0 tsconfig-vuepress: ^7.0.0
tsdown: ^0.20.3 tsdown: ^0.21.7
typescript: ^5.9.3 typescript: ^5.9.3
vite: ^8.0.0-beta.16 vite: ^8.0.3
vitest: ^4.0.18 vitest: ^4.1.2
wait-on: ^9.0.4 wait-on: ^9.0.4
peer: peer:
'@eslint-community/eslint-utils': ^4.9.1 '@eslint-community/eslint-utils': ^4.9.1
'@iconify/json': ^2.2.446 '@iconify/json': ^2.2.458
'@mathjax/src': ^4.1.1 '@mathjax/src': ^4.1.1
'@pinyin-pro/data': ^1.3.1 '@pinyin-pro/data': ^1.3.1
'@typescript-eslint/types': ^8.56.1 '@typescript-eslint/types': ^8.58.0
'@typescript-eslint/utils': ^8.56.1 '@typescript-eslint/utils': ^8.58.0
artplayer: ^5.3.0 artplayer: ^5.4.0
dashjs: ^5.1.1 dashjs: ^5.1.1
gsap: ^3.14.2 gsap: ^3.14.2
hls.js: ^1.6.15 hls.js: ^1.6.15
mpegts.js: 1.7.3 mpegts.js: 1.7.3
ogl: ^1.0.11 ogl: ^1.0.11
pinyin-pro: ^3.28.0 pinyin-pro: ^3.28.0
postprocessing: ^6.38.3 postprocessing: ^6.39.0
pyodide: ^0.29.3 pyodide: ^0.29.3
sass: ^1.97.3 sass: ^1.98.0
sass-embedded: ^1.97.3 sass-embedded: ^1.98.0
swiper: ^12.1.2 swiper: ^12.1.3
three: ^0.183.2 three: ^0.183.2
prod: prod:
'@clack/prompts': ^1.1.0 '@clack/prompts': ^1.2.0
'@iconify/utils': ^3.1.0 '@iconify/utils': ^3.1.0
'@iconify/vue': ^5.0.0 '@iconify/vue': ^5.0.0
'@mdit/plugin-attrs': ^0.25.1 '@mdit/plugin-attrs': ^0.25.2
'@mdit/plugin-footnote': ^0.23.1 '@mdit/plugin-footnote': ^0.23.2
'@mdit/plugin-mark': ^0.23.1 '@mdit/plugin-mark': ^0.23.2
'@mdit/plugin-sub': ^0.24.1 '@mdit/plugin-sub': ^0.24.2
'@mdit/plugin-sup': ^0.24.1 '@mdit/plugin-sup': ^0.24.2
'@mdit/plugin-tab': ^0.24.1 '@mdit/plugin-tab': ^0.24.2
'@mdit/plugin-tasklist': ^0.23.1 '@mdit/plugin-tasklist': ^0.23.2
'@pengzhanbo/utils': ^3.3.1 '@pengzhanbo/utils': ^3.3.1
'@vueuse/core': ^14.2.1 '@vueuse/core': ^14.2.1
'@vueuse/integrations': ^14.2.1 '@vueuse/integrations': ^14.2.1
cac: ^7.0.0 cac: ^7.0.0
chart.js: ^4.5.1 chart.js: ^4.5.1
chokidar: 5.0.0 chokidar: 5.0.0
dayjs: ^1.11.19 dayjs: ^1.11.20
echarts: ^6.0.0 echarts: ^6.0.0
esbuild: ^0.27.3 esbuild: ^0.27.5
flowchart.ts: ^3.0.1 flowchart.ts: ^3.0.1
focus-trap: ^8.0.0 focus-trap: ^8.0.1
gray-matter: ^4.0.3 gray-matter: ^4.0.3
handlebars: ^4.7.8 handlebars: ^4.7.9
hash-wasm: ^4.12.0 hash-wasm: ^4.12.0
image-size: ^2.0.2 image-size: ^2.0.2
js-yaml: ^4.1.1 js-yaml: ^4.1.1
katex: ^0.16.33 katex: ^0.16.44
local-pkg: ^1.1.2 local-pkg: ^1.1.2
lru-cache: ^11.2.6 lru-cache: ^11.2.7
mark.js: ^8.11.1 mark.js: ^8.11.1
markdown-it-cjk-friendly: ^2.0.2 markdown-it-cjk-friendly: ^2.0.2
markdown-it-container: ^4.0.0 markdown-it-container: ^4.0.0
@ -127,45 +122,44 @@ catalogs:
markmap-toolbar: ^0.18.12 markmap-toolbar: ^0.18.12
markmap-view: ^0.18.12 markmap-view: ^0.18.12
minisearch: ^7.2.0 minisearch: ^7.2.0
nano-spawn: ^2.0.0 nano-spawn: ^2.1.0
nanoid: ^5.1.6 nanoid: ^5.1.7
os-locale: ^8.0.0 os-locale: ^8.0.0
p-map: ^7.0.4 p-map: ^7.0.4
package-manager-detector: ^1.6.0 package-manager-detector: ^1.6.0
picocolors: ^1.1.1 picocolors: ^1.1.1
picomatch: ^4.0.3 picomatch: ^4.0.4
qrcode: ^1.5.4 qrcode: ^1.5.4
shiki: ^4.0.1 shiki: ^4.0.2
sort-package-json: ^3.6.1 sort-package-json: ^3.6.1
tm-grammars: ^1.31.5 tm-grammars: ^1.31.15
tm-themes: ^1.12.1 tm-themes: ^1.12.2
vue: ^3.5.29 vue: ^3.5.31
vue-router: ^5.0.3 vue-router: ^5.0.4
vuepress: vuepress:
'@vuepress/bundler-vite': 2.0.0-rc.26 '@vuepress/bundler-vite': 2.0.0-rc.28
'@vuepress/helper': 2.0.0-rc.123 '@vuepress/helper': 2.0.0-rc.128
'@vuepress/plugin-cache': 2.0.0-rc.123 '@vuepress/plugin-cache': 2.0.0-rc.128
'@vuepress/plugin-comment': 2.0.0-rc.123 '@vuepress/plugin-comment': 2.0.0-rc.128
'@vuepress/plugin-copy-code': 2.0.0-rc.123 '@vuepress/plugin-copy-code': 2.0.0-rc.128
'@vuepress/plugin-docsearch': 2.0.0-rc.123 '@vuepress/plugin-docsearch': 2.0.0-rc.128
'@vuepress/plugin-git': 2.0.0-rc.123 '@vuepress/plugin-git': 2.0.0-rc.128
'@vuepress/plugin-llms': 2.0.0-rc.123 '@vuepress/plugin-llms': 2.0.0-rc.128
'@vuepress/plugin-markdown-chart': 2.0.0-rc.123 '@vuepress/plugin-markdown-chart': 2.0.0-rc.128
'@vuepress/plugin-markdown-hint': 2.0.0-rc.123 '@vuepress/plugin-markdown-hint': 2.0.0-rc.128
'@vuepress/plugin-markdown-image': 2.0.0-rc.123 '@vuepress/plugin-markdown-image': 2.0.0-rc.128
'@vuepress/plugin-markdown-include': 2.0.0-rc.123 '@vuepress/plugin-markdown-include': 2.0.0-rc.128
'@vuepress/plugin-markdown-math': 2.0.0-rc.123 '@vuepress/plugin-markdown-math': 2.0.0-rc.128
'@vuepress/plugin-nprogress': 2.0.0-rc.123 '@vuepress/plugin-nprogress': 2.0.0-rc.128
'@vuepress/plugin-photo-swipe': 2.0.0-rc.123 '@vuepress/plugin-photo-swipe': 2.0.0-rc.128
'@vuepress/plugin-reading-time': 2.0.0-rc.123 '@vuepress/plugin-reading-time': 2.0.0-rc.128
'@vuepress/plugin-replace-assets': 2.0.0-rc.123 '@vuepress/plugin-replace-assets': 2.0.0-rc.128
'@vuepress/plugin-seo': 2.0.0-rc.123 '@vuepress/plugin-seo': 2.0.0-rc.128
'@vuepress/plugin-shiki': 2.0.0-rc.123 '@vuepress/plugin-shiki': 2.0.0-rc.128
'@vuepress/plugin-sitemap': 2.0.0-rc.123 '@vuepress/plugin-sitemap': 2.0.0-rc.128
'@vuepress/plugin-watermark': 2.0.0-rc.123 '@vuepress/plugin-watermark': 2.0.0-rc.128
'@vuepress/shiki-twoslash': 2.0.0-rc.123 '@vuepress/shiki-twoslash': 2.0.0-rc.128
vuepress: 2.0.0-rc.26 vuepress: 2.0.0-rc.28
onlyBuiltDependencies: onlyBuiltDependencies:
- '@parcel/watcher' - '@parcel/watcher'
- core-js - core-js

View File

@ -33,9 +33,7 @@ async function npmMirrorSync() {
resolve() resolve()
}) })
req.on('error', (error) => { req.on('error', reject)
reject(error)
})
req.end() req.end()
}) })

View File

@ -1,16 +1,10 @@
import process from 'node:process' import process from 'node:process'
import minimist from 'minimist' import minimist from 'minimist'
// interface ArgvOptions { interface ArgvOptions {
// client: boolean client: boolean
// node: boolean node: boolean
// } }
/**
* @typedef {object} ArgvOptions
* @property {boolean} client -
* @property {boolean} node - node
*/
const rawArgv = process.argv.slice(2) const rawArgv = process.argv.slice(2)
const tsupArgv = rawArgv.includes('--') ? rawArgv.slice(rawArgv.indexOf('--') + 1) : [] const tsupArgv = rawArgv.includes('--') ? rawArgv.slice(rawArgv.indexOf('--') + 1) : []
@ -30,8 +24,7 @@ const parsed = tsupArgv.length
all: true, all: true,
} }
/** @type {ArgvOptions} */ export const argv: ArgvOptions = {
export const argv = {
client: parsed.client || parsed.all, client: parsed.client || parsed.all,
node: parsed.node || parsed.all, node: parsed.node || parsed.all,
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "vuepress-theme-plume", "name": "vuepress-theme-plume",
"type": "module", "type": "module",
"version": "1.0.0-rc.192", "version": "1.0.0-rc.193",
"description": "A Blog&Document Theme for VuePress 2.0", "description": "A Blog&Document Theme for VuePress 2.0",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)", "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT", "license": "MIT",
@ -62,8 +62,8 @@
"clean": "rimraf --glob ./lib", "clean": "rimraf --glob ./lib",
"copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png,woff2}\" lib", "copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png,woff2}\" lib",
"copy:watch": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png,woff2}\" lib -w", "copy:watch": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png,woff2}\" lib -w",
"tsdown": "tsdown", "tsdown": "tsdown --config-loader unrun",
"tsdown:watch": "tsdown --watch -- -c" "tsdown:watch": "tsdown --config-loader unrun --watch -- -c"
}, },
"peerDependencies": { "peerDependencies": {
"@iconify/json": ">=2", "@iconify/json": ">=2",

View File

@ -2,7 +2,7 @@
import VPIcon from '@theme/VPIcon.vue' import VPIcon from '@theme/VPIcon.vue'
import { computed, toRef } from 'vue' import { computed, toRef } from 'vue'
import { useRouter, withBase } from 'vuepress/client' import { useRouter, withBase } from 'vuepress/client'
import { useLink } from '../composables/index.js' import { useData, useLink } from '../composables/index.js'
interface Props { interface Props {
tag?: string tag?: string
@ -21,7 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
text: '', text: '',
}) })
const router = useRouter() const router = useRouter()
const { theme: themeData } = useData()
const component = computed(() => { const component = computed(() => {
return props.tag || props.href ? 'a' : 'button' return props.tag || props.href ? 'a' : 'button'
}) })
@ -44,12 +44,15 @@ function linkTo(e: Event) {
:class="[size, theme]" :class="[size, theme]"
:href=" link ? link[0] === '#' || isExternalProtocol ? link : withBase(link) : undefined" :href=" link ? link[0] === '#' || isExternalProtocol ? link : withBase(link) : undefined"
:target="target ?? (isExternal ? '_blank' : undefined)" :target="target ?? (isExternal ? '_blank' : undefined)"
:rel="rel ?? (isExternal ? 'noreferrer' : undefined)" :rel="rel ?? (isExternal ? 'noopener noreferrer' : undefined)"
@click="linkTo($event)" @click="linkTo($event)"
> >
<span class="button-content"> <span class="button-content">
<VPIcon v-if="icon" :name="icon" /> <VPIcon v-if="icon" :name="icon" />
<slot><span>{{ text }}</span></slot> <slot><span>{{ text }}</span></slot>
<span v-if="isExternal" class="visually-hidden">
{{ themeData.openNewWindowText || '(Open in new window)' }}
</span>
<VPIcon v-if="suffixIcon" :name="suffixIcon" /> <VPIcon v-if="suffixIcon" :name="suffixIcon" />
</span> </span>
</Component> </Component>
@ -73,6 +76,10 @@ function linkTo(e: Event) {
background-color 0.1s; background-color 0.1s;
} }
.vp-button:focus-visible {
outline-offset: 4px;
}
.vp-button.medium { .vp-button.medium {
padding: 0 20px; padding: 0 20px;
font-size: 14px; font-size: 14px;

View File

@ -51,6 +51,8 @@ async function onSubmit() {
type="password" type="password"
autocomplete="off" autocomplete="off"
:placeholder="theme.encryptPlaceholder ?? 'Enter Password'" :placeholder="theme.encryptPlaceholder ?? 'Enter Password'"
:aria-invalid="errorCode === 1"
:aria-describedby="errorCode === 1 ? 'encrypt-error' : undefined"
@keyup.enter="onSubmit" @keyup.enter="onSubmit"
@focus="!password && (errorCode = 0)" @focus="!password && (errorCode = 0)"
@input="password && (errorCode = 0)" @input="password && (errorCode = 0)"

View File

@ -138,6 +138,10 @@ function onBlur() {
transition: color var(--vp-t-color); transition: color var(--vp-t-color);
} }
.vp-flyout .button:focus-visible {
outline-offset: 4px;
}
.option-icon { .option-icon {
margin-right: 0; margin-right: 0;
font-size: 16px; font-size: 16px;

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, toRef } from 'vue' import { computed, toRef } from 'vue'
import { useRouter, withBase } from 'vuepress/client' import { useRouter, withBase } from 'vuepress/client'
import { useLink } from '../composables/index.js' import { useData, useLink } from '../composables/index.js'
const props = defineProps<{ const props = defineProps<{
tag?: string tag?: string
@ -13,6 +13,7 @@ const props = defineProps<{
}>() }>()
const router = useRouter() const router = useRouter()
const { theme } = useData()
const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span')) const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span'))
@ -32,11 +33,20 @@ function linkTo(e: Event) {
class="vp-link" :class="{ link, 'no-icon': noIcon, 'vp-external-link-icon': isExternal }" class="vp-link" :class="{ link, 'no-icon': noIcon, 'vp-external-link-icon': isExternal }"
:href="link ? isExternalProtocol ? link : isExternal ? link : withBase(link) : undefined" :href="link ? isExternalProtocol ? link : isExternal ? link : withBase(link) : undefined"
:target="target ?? (isExternal ? '_blank' : undefined)" :target="target ?? (isExternal ? '_blank' : undefined)"
:rel="rel ?? (isExternal ? 'noreferrer' : undefined)" :rel="rel ?? (isExternal ? 'noopener noreferrer' : undefined)"
@click="linkTo($event)" @click="linkTo($event)"
> >
<slot> <slot>
{{ text || href }} {{ text || href }}
</slot> </slot>
<span v-if="isExternal && !noIcon" class="visually-hidden">
{{ theme.openNewWindowText || '(Open in new window)' }}
</span>
</Component> </Component>
</template> </template>
<style>
.vp-link:focus-visible {
border-radius: 2px;
}
</style>

View File

@ -58,12 +58,6 @@ function onItemInteraction(e: MouseEvent | Event) {
toggle() toggle()
} }
} }
function onCaretClick() {
if (item.link) {
toggle()
}
}
</script> </script>
<template> <template>
@ -107,17 +101,16 @@ function onCaretClick() {
/> />
</Component> </Component>
<div <button
v-if="item.collapsed != null" v-if="item.collapsed != null"
type="button"
class="caret" class="caret"
role="button" :aria-label="`${collapsed ? 'Expand' : 'Collapse'} ${item.text}`"
aria-label="toggle section" :aria-expanded="!collapsed"
tabindex="0" tabindex="-1"
@click="onCaretClick"
@keydown.enter="onCaretClick"
> >
<span class="vpi-chevron-right caret-icon" /> <span class="vpi-chevron-right caret-icon" />
</div> </button>
</div> </div>
<template v-if="item.items && item.items.length && depth < 5"> <template v-if="item.items && item.items.length && depth < 5">

View File

@ -56,6 +56,11 @@ const label = computed(() => {
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
.vp-social-link:focus-visible {
border-radius: 50%;
outline-offset: 4px;
}
.vp-social-link > :deep([class*="vpi-"]), .vp-social-link > :deep([class*="vpi-"]),
.vp-social-link > :deep(.vp-icon.is-svg) { .vp-social-link > :deep(.vp-icon.is-svg) {
width: 20px; width: 20px;

View File

@ -1,6 +1,16 @@
<script lang="ts" setup>
defineProps<{
ariaChecked?: boolean
}>()
</script>
<template> <template>
<!-- eslint-disable-next-line vue-a11y/role-has-required-aria-props --> <button
<button class="vp-switch" type="button" role="switch"> class="vp-switch"
type="button"
role="switch"
:aria-checked="ariaChecked ?? false"
>
<span class="check"> <span class="check">
<span v-if="$slots.default" class="icon"> <span v-if="$slots.default" class="icon">
<slot /> <slot />
@ -28,6 +38,10 @@
border-color: var(--vp-c-brand-1); border-color: var(--vp-c-brand-1);
} }
.vp-switch:focus-visible {
outline-offset: 4px;
}
.check { .check {
position: absolute; position: absolute;
top: 1px; top: 1px;

View File

@ -6,6 +6,10 @@ import { enableTransitions, resolveTransitionKeyframes, useData } from '../compo
const checked = ref(false) const checked = ref(false)
const { theme, isDark } = useData() const { theme, isDark } = useData()
watchPostEffect(() => {
checked.value = isDark.value
})
const transitionMode = computed(() => { const transitionMode = computed(() => {
const transition = theme.value.transition const transition = theme.value.transition
const options = typeof transition === 'object' ? transition : {} const options = typeof transition === 'object' ? transition : {}
@ -15,8 +19,12 @@ const transitionMode = computed(() => {
return typeof options.appearance === 'string' ? options.appearance : 'fade' return typeof options.appearance === 'string' ? options.appearance : 'fade'
}) })
function shouldReduceMotion(): boolean {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches
}
const toggleAppearance = inject('toggle-appearance', async ({ clientX, clientY }: MouseEvent) => { const toggleAppearance = inject('toggle-appearance', async ({ clientX, clientY }: MouseEvent) => {
if (!enableTransitions() || transitionMode.value === false) { if (!enableTransitions() || transitionMode.value === false || shouldReduceMotion()) {
isDark.value = !isDark.value isDark.value = !isDark.value
return return
} }
@ -80,6 +88,12 @@ watchPostEffect(() => {
/* rtl:ignore */ /* rtl:ignore */
transform: translateX(18px); transform: translateX(18px);
} }
@media (prefers-reduced-motion: reduce) {
.vp-switch-appearance :deep(.check) {
transition: none !important;
}
}
</style> </style>
<style> <style>

View File

@ -104,6 +104,8 @@ export function useHeaders(): Ref<MenuItem[]> {
return headers return headers
} }
const IGNORE_WRAPPERS = ['.vp-bulletin', '.vp-demo-wrapper']
/** /**
* Get headers from the page content * Get headers from the page content
* Extracts and filters headings based on the outline configuration * Extracts and filters headings based on the outline configuration
@ -117,7 +119,7 @@ export function useHeaders(): Ref<MenuItem[]> {
export function getHeaders(range?: ThemeOutline): MenuItem[] { export function getHeaders(range?: ThemeOutline): MenuItem[] {
const heading = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] const heading = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
const ignores = Array.from(document.querySelectorAll( const ignores = Array.from(document.querySelectorAll(
heading.map(h => `.vp-demo-wrapper ${h}`).join(','), heading.map(h => IGNORE_WRAPPERS.map(w => `${w} ${h}`)).flat().join(','),
)) ))
const headers = Array.from( const headers = Array.from(
document.querySelectorAll(heading.map(h => `.vp-doc ${h}`).join(',')), document.querySelectorAll(heading.map(h => `.vp-doc ${h}`).join(',')),

View File

@ -262,6 +262,11 @@ figure {
margin: 0; margin: 0;
} }
:where(#app) :focus-visible {
outline: var(--vp-focus-ring-width) solid var(--vp-focus-ring-color);
outline-offset: var(--vp-focus-ring-offset);
}
h1, h1,
h2, h2,
h3, h3,

View File

@ -703,3 +703,12 @@
--vp-c-control-hover: var(--vp-c-default-2); --vp-c-control-hover: var(--vp-c-default-2);
--vp-c-control-disabled: var(--vp-c-default-soft); --vp-c-control-disabled: var(--vp-c-default-soft);
} }
/**
* Focus ring
* -------------------------------------------------------------------------- */
:root {
--vp-focus-ring-color: var(--vp-c-brand-1);
--vp-focus-ring-width: 2px;
--vp-focus-ring-offset: 2px;
}

View File

@ -1,75 +0,0 @@
/**
* Temporary enhancement for VuePress app.
* This enhancement will be removed in the next version of vuepress/core.
*
* VuePress
* vuepress/core
*/
import type { App } from 'vuepress'
import { fs, hash } from 'vuepress/utils'
/**
* Cache structure for writeTemp operations.
* Tracks content hash and writing promises for optimization.
*
* writeTemp
*
*/
interface WriteTempCache {
/** Content hash for change detection / 用于变更检测的内容哈希 */
hash?: string
/** Current writing promise / 当前写入承诺 */
current?: Promise<void>
/** Next writing promise to chain / 要链接的下一个写入承诺 */
next?: () => Promise<void>
}
/**
* Enhance the VuePress app with optimized writeTemp method.
* Implements caching and promise chaining for better performance.
*
* 使 writeTemp VuePress
*
*
* @param app - VuePress application instance / VuePress
*/
export function enhanceApp(app: App): void {
// rewrite writeTemp to cache the writing promise
const cache = new Map<string, WriteTempCache>()
app.writeTemp = async function (file: string, content: string): Promise<string> {
const filePath = app.dir.temp(file)
const contentHash = hash(content)
let item = cache.get(filePath)
if (!item) {
cache.set(filePath, (item = {}))
}
// if content hash is the same as the last one, skip writing
if (item.hash === contentHash) {
return filePath
}
item.hash = contentHash
if (!item.current) {
item.current = (async () => {
await fs.outputFile(filePath, content)
// if there is a next writing promise, chain it with the current one
item.current = item.next?.()
return item.current
})()
}
else {
// if there is a current writing promise, save the next writing promise
item.next = async () => {
await fs.outputFile(filePath, content)
item.next = undefined
item.current = undefined
}
}
await item.current
return filePath
}
}

View File

@ -28,6 +28,8 @@ export const deLocale: ThemeLocaleText = {
copyrightCreationReprintText: 'Nachdruck von:', copyrightCreationReprintText: 'Nachdruck von:',
copyrightLicenseText: 'Lizenz:', copyrightLicenseText: 'Lizenz:',
openNewWindowText: '(In neuem Fenster öffnen)',
notFound: { notFound: {
code: '404', code: '404',
title: 'Seite nicht gefunden', title: 'Seite nicht gefunden',

View File

@ -22,6 +22,8 @@ export const enLocale: ThemeLocaleText = {
copyrightCreationReprintText: 'This article is reprint from:', copyrightCreationReprintText: 'This article is reprint from:',
copyrightLicenseText: 'License under:', copyrightLicenseText: 'License under:',
openNewWindowText: '(Open in new window)',
encryptButtonText: 'Confirm', encryptButtonText: 'Confirm',
encryptPlaceholder: 'Enter password', encryptPlaceholder: 'Enter password',
encryptGlobalText: 'Only password can access this site', encryptGlobalText: 'Only password can access this site',

View File

@ -28,6 +28,8 @@ export const frLocale: ThemeLocaleText = {
copyrightCreationReprintText: 'Reproduit de :', copyrightCreationReprintText: 'Reproduit de :',
copyrightLicenseText: 'Licence :', copyrightLicenseText: 'Licence :',
openNewWindowText: '(Ouvrir dans une nouvelle fenêtre)',
notFound: { notFound: {
code: '404', code: '404',
title: 'Page non trouvée', title: 'Page non trouvée',

View File

@ -28,6 +28,8 @@ export const jaLocale: ThemeLocaleText = {
copyrightCreationReprintText: '本文の転載元:', copyrightCreationReprintText: '本文の転載元:',
copyrightLicenseText: 'ライセンス:', copyrightLicenseText: 'ライセンス:',
openNewWindowText: '(新しいウィンドウで開く)',
notFound: { notFound: {
code: '404', code: '404',
title: 'ページが見つかりません', title: 'ページが見つかりません',

View File

@ -40,6 +40,8 @@ export const koLocale: ThemeLocaleText = {
categoryText: '카테고리', categoryText: '카테고리',
archiveTotalText: '{count}개의 글', archiveTotalText: '{count}개의 글',
openNewWindowText: '(새 창에서 열기)',
notFound: { notFound: {
code: '404', code: '404',
title: '페이지를 찾을 수 없습니다', title: '페이지를 찾을 수 없습니다',

View File

@ -28,6 +28,8 @@ export const ruLocale: ThemeLocaleText = {
copyrightCreationReprintText: 'Перепечатано из:', copyrightCreationReprintText: 'Перепечатано из:',
copyrightLicenseText: 'Лицензия:', copyrightLicenseText: 'Лицензия:',
openNewWindowText: '(Открыть в новой вкладке)',
notFound: { notFound: {
code: '404', code: '404',
title: 'Страница не найдена', title: 'Страница не найдена',

View File

@ -28,6 +28,8 @@ export const zhTwLocale: ThemeLocaleText = {
copyrightCreationReprintText: '本文轉載自:', copyrightCreationReprintText: '本文轉載自:',
copyrightLicenseText: '授權條款:', copyrightLicenseText: '授權條款:',
openNewWindowText: '(在新窗口打開)',
notFound: { notFound: {
code: '404', code: '404',
title: '頁面未找到', title: '頁面未找到',

View File

@ -27,6 +27,8 @@ export const zhLocale: ThemeLocaleText = {
copyrightCreationReprintText: '本文转载自:', copyrightCreationReprintText: '本文转载自:',
copyrightLicenseText: '许可证:', copyrightLicenseText: '许可证:',
openNewWindowText: '(在新窗口打开)',
notFound: { notFound: {
code: '404', code: '404',
title: '页面未找到', title: '页面未找到',

View File

@ -1,5 +1,4 @@
import type { App } from 'vuepress' import type { App } from 'vuepress'
import { watch } from 'chokidar'
import { perf } from '../utils/index.js' import { perf } from '../utils/index.js'
import { prepareArticleTagColors } from './prepareArticleTagColor.js' import { prepareArticleTagColors } from './prepareArticleTagColor.js'
import { prepareCollections } from './prepareCollections.js' import { prepareCollections } from './prepareCollections.js'
@ -29,24 +28,3 @@ export async function prepareData(app: App): Promise<void> {
perf.log('prepare:data') perf.log('prepare:data')
} }
/**
* Watch for changes in prepared data and re-prepare when needed
*
*
*/
export function watchPrepare(
app: App,
watchers: any[],
): void {
const pagesWatcher = watch('pages', {
cwd: app.dir.temp(),
ignoreInitial: true,
ignored: (filepath, stats) => Boolean(stats?.isFile()) && !filepath.endsWith('.js'),
})
watchers.push(pagesWatcher)
pagesWatcher.on('change', () => prepareData(app))
pagesWatcher.on('add', () => prepareData(app))
pagesWatcher.on('unlink', () => prepareData(app))
}

View File

@ -173,7 +173,7 @@ function getAutoDirSidebar(
current.text = title current.text = title
} }
} }
if (frontmatter.icon) { if (frontmatter.icon && dir.endsWith('.md')) {
current.icon = frontmatter.icon as ThemeIcon current.icon = frontmatter.icon as ThemeIcon
} }
if (parent?.items?.length) { if (parent?.items?.length) {
@ -235,7 +235,12 @@ function getAllSidebar(): Record<string, ThemeSidebar> {
const sidebar = locale === '/' ? (opt.sidebar || options.sidebar) : opt.sidebar const sidebar = locale === '/' ? (opt.sidebar || options.sidebar) : opt.sidebar
locales[locale] = {} locales[locale] = {}
for (const [key, value] of entries(sidebar || {})) { for (const [key, value] of entries(sidebar || {})) {
locales[locale][ensureLeadingSlash(key)] = value locales[locale][ensureLeadingSlash(key)] = isPlainObject(value) && 'items' in value
? { ...value, prefix: value.prefix?.startsWith('/') ? value.prefix : normalizeLink(locale, removeLeadingSlash(key)) }
: {
items: value,
prefix: normalizeLink(locale, removeLeadingSlash(key)),
}
} }
const collections = rawCollections?.filter(item => item.type === 'doc') const collections = rawCollections?.filter(item => item.type === 'doc')
if (collections?.length) { if (collections?.length) {

View File

@ -12,11 +12,10 @@ import {
templateBuildRenderer, templateBuildRenderer,
} from './config/index.js' } from './config/index.js'
import { detectThemeOptions, detectVersions } from './detector/index.js' import { detectThemeOptions, detectVersions } from './detector/index.js'
import { enhanceApp } from './enhance.js'
import { configLoader } from './loadConfig/index.js' import { configLoader } from './loadConfig/index.js'
import { createPages, extendsPageData } from './pages/index.js' import { createPages, extendsPageData } from './pages/index.js'
import { setupPlugins } from './plugins/index.js' import { setupPlugins } from './plugins/index.js'
import { prepareData, watchPrepare } from './prepare/index.js' import { prepareData } from './prepare/index.js'
import { prepareThemeData } from './prepare/prepareThemeData.js' import { prepareThemeData } from './prepare/prepareThemeData.js'
import { perf, resolve, setTranslateLang, templates, THEME_NAME } from './utils/index.js' import { perf, resolve, setTranslateLang, templates, THEME_NAME } from './utils/index.js'
@ -40,8 +39,6 @@ import { perf, resolve, setTranslateLang, templates, THEME_NAME } from './utils/
*/ */
export function plumeTheme(options: ThemeOptions = {}): Theme { export function plumeTheme(options: ThemeOptions = {}): Theme {
return (app) => { return (app) => {
enhanceApp(app)
setTranslateLang(app.options.lang) setTranslateLang(app.options.lang)
perf.init(app.env.isDebug) perf.init(app.env.isDebug)
@ -87,9 +84,12 @@ export function plumeTheme(options: ThemeOptions = {}): Theme {
await prepareData(app) await prepareData(app)
}, },
onPageUpdated: async (app) => {
await prepareData(app)
},
onWatched: async (app, watchers) => { onWatched: async (app, watchers) => {
configLoader.watch(watchers as any) configLoader.watch(watchers as any)
watchPrepare(app, watchers)
watchAutoFrontmatter(app, watchers as any) watchAutoFrontmatter(app, watchers as any)
}, },
} }

View File

@ -12,7 +12,7 @@ import type { MarkdownPowerPluginOptions } from 'vuepress-plugin-md-power'
*/ */
export interface MarkdownOptions extends MarkdownPowerPluginOptions, export interface MarkdownOptions extends MarkdownPowerPluginOptions,
MarkdownChartPluginOptions, MarkdownChartPluginOptions,
Pick<MarkdownHintPluginOptions, 'alert' | 'hint'> { Partial<Pick<MarkdownHintPluginOptions, 'alert' | 'hint'>> {
/** /**
* *
* @deprecated use `demo` instead * @deprecated use `demo` instead

View File

@ -339,6 +339,13 @@ export interface ThemeLocaleText {
*/ */
nextPageLabel?: string nextPageLabel?: string
/**
*
*
* @default '(Open in new window)'
*/
openNewWindowText?: string
/** /**
* 404 * 404
*/ */
@ -404,13 +411,44 @@ export interface ThemeLocaleText {
*/ */
encryptPlaceholder?: string encryptPlaceholder?: string
// 以下字段与 PageContextMenu 相关 ------ start
/**
*
*/
copyPageText?: string copyPageText?: string
/**
*
*/
copiedPageText?: string copiedPageText?: string
/**
*
*/
copingPageText?: string copingPageText?: string
/**
*
*/
copyTagline?: string copyTagline?: string
/**
* Markdown
*/
viewMarkdown?: string viewMarkdown?: string
/**
* Markdown
*/
viewMarkdownTagline?: string viewMarkdownTagline?: string
/**
* AI
*/
askAIText?: string askAIText?: string
/**
* AI
*/
askAITagline?: string askAITagline?: string
/**
* AI
*/
askAIMessage?: string askAIMessage?: string
// 以上字段与 PageContextMenu 相关 ------ end
} }

View File

@ -1,8 +1,8 @@
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import process from 'node:process' import process from 'node:process'
import { defineConfig } from 'tsdown' import { defineConfig, type UserConfig } from 'tsdown'
import { argv } from '../scripts/tsdown-args.mjs' import { argv } from '../scripts/tsdown-args'
/** @import {Options} from 'tsdown' */ /** @import {Options} from 'tsdown' */
@ -24,25 +24,22 @@ const featuresComposables = fs.readdirSync(
) )
export default defineConfig((cli) => { export default defineConfig((cli) => {
/** @type {Options} */ const DEFAULT_OPTIONS: UserConfig = {
const DEFAULT_OPTIONS = {
dts: true, dts: true,
sourcemap: false, sourcemap: false,
watch: cli.watch, watch: cli.watch,
format: 'esm', format: 'esm',
silent: !!cli.watch,
clean: !cli.watch, clean: !cli.watch,
fixedExtension: false, fixedExtension: false,
} }
/** @type {Options[]} */ const options: UserConfig[] = []
const options = []
// shared // shared
options.push({ options.push({
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/shared/index.ts'], entry: ['./src/shared/index.ts'],
outDir: './lib/shared', outDir: './lib/shared',
external: ['sax'], deps: { neverBundle: ['sax'] },
}) })
if (argv.node) { if (argv.node) {
@ -50,20 +47,20 @@ export default defineConfig((cli) => {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/node/index.ts'], entry: ['./src/node/index.ts'],
outDir: './lib/node', outDir: './lib/node',
external: [...sharedExternal, '@pinyin-pro/data/complete'], deps: { neverBundle: [...sharedExternal, '@pinyin-pro/data/complete'] },
target: 'node20.19.0', target: 'node20.19.0',
watch: false, watch: false,
}) })
} }
if (argv.client) { if (argv.client) {
options.push(...[ options.push(
// client/utils/index.js // client/utils/index.js
{ {
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: ['./src/client/utils/index.ts'], entry: ['./src/client/utils/index.ts'],
outDir: './lib/client/utils', outDir: './lib/client/utils',
platform: 'browser', platform: 'browser',
external: clientExternal, deps: { neverBundle: clientExternal },
}, },
// client/composables/index.js // client/composables/index.js
{ {
@ -71,10 +68,10 @@ export default defineConfig((cli) => {
entry: ['./src/client/composables/index.ts'], entry: ['./src/client/composables/index.ts'],
outDir: './lib/client/composables', outDir: './lib/client/composables',
platform: 'browser', platform: 'browser',
external: [ deps: { neverBundle: [
...clientExternal, ...clientExternal,
'../utils/index.js', '../utils/index.js',
], ] },
}, },
// client/config.js // client/config.js
{ {
@ -83,11 +80,11 @@ export default defineConfig((cli) => {
outDir: './lib/client', outDir: './lib/client',
dts: false, dts: false,
platform: 'browser', platform: 'browser',
external: [ deps: { neverBundle: [
...clientExternal, ...clientExternal,
'./composables/index.js', './composables/index.js',
'./utils/index.js', './utils/index.js',
], ] },
}, },
// client/index.js // client/index.js
{ {
@ -95,26 +92,26 @@ export default defineConfig((cli) => {
entry: ['./src/client/index.ts'], entry: ['./src/client/index.ts'],
outDir: './lib/client', outDir: './lib/client',
platform: 'browser', platform: 'browser',
external: [ deps: { neverBundle: [
...clientExternal, ...clientExternal,
'./composables/index.js', './composables/index.js',
'./utils/index.js', './utils/index.js',
'./config.js', './config.js',
], ] },
}, },
...featuresComposables.map(file => ({ ...featuresComposables.map(file => ({
...DEFAULT_OPTIONS, ...DEFAULT_OPTIONS,
entry: [`./src/client/features/composables/${file}`], entry: [`./src/client/features/composables/${file}`],
outDir: `./lib/client/features/composables/`, outDir: `./lib/client/features/composables/`,
platform: 'browser', platform: 'browser',
external: [ deps: { neverBundle: [
...clientExternal, ...clientExternal,
'../../composables/index.js', '../../composables/index.js',
'../../utils/index.js', '../../utils/index.js',
...featuresComposables.map(file => `./${file.replace('.ts', '.js')}`), ...featuresComposables.map(file => `./${file.replace('.ts', '.js')}`),
], ] },
})), } as UserConfig)),
]) )
} }
return options return options
}) })

View File

@ -14,7 +14,8 @@
"theme/**/*", "theme/**/*",
"docs/.vuepress/**/*", "docs/.vuepress/**/*",
"cli/**/*", "cli/**/*",
"scripts/**/*" "scripts/**/*",
"**/tsdown.config.ts"
], ],
"exclude": [ "exclude": [
"**/node_modules/**", "**/node_modules/**",