Compare commits
341 Commits
v1.0.0-rc.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d69e0b9765 | ||
|
|
02038f2df0 | ||
|
|
e5126663ef | ||
|
|
402f259086 | ||
|
|
58ea2fc8cb | ||
|
|
6ebb1bda6e | ||
|
|
68f39695c4 | ||
|
|
76787f6530 | ||
|
|
e2b47da532 | ||
|
|
035d521e96 | ||
|
|
bfd0c8409c | ||
|
|
e11c7a8fcd | ||
|
|
1329051536 | ||
|
|
0677f6749e | ||
|
|
28963eb419 | ||
|
|
cfc89adab8 | ||
|
|
e0ba59a6f9 | ||
|
|
352874b29a | ||
|
|
c824ad85f4 | ||
|
|
db2eda82f3 | ||
|
|
e9fe35bc4f | ||
|
|
709ade741c | ||
|
|
d8b79e89e8 | ||
|
|
dbc6f0be0f | ||
|
|
9fe294b9dd | ||
|
|
ecf100cfc6 | ||
|
|
b7ee45642e | ||
|
|
54c05c8cea | ||
|
|
86cb872ce6 | ||
|
|
a6cb3820b1 | ||
|
|
184d1aee76 | ||
|
|
cbc5c55891 | ||
|
|
4f40f8441d | ||
|
|
fe0d4bbc92 | ||
|
|
39a76a35d7 | ||
|
|
a01bc13c66 | ||
|
|
1b213d4c28 | ||
|
|
aede6f5d87 | ||
|
|
7febfbf237 | ||
|
|
7ce4e40521 | ||
|
|
12c4f5b39e | ||
|
|
aa54090b5d | ||
|
|
192b260d2b | ||
|
|
75df783295 | ||
|
|
97a5ba20c3 | ||
|
|
896c7e22df | ||
|
|
77856e36c5 | ||
|
|
552f0f5c32 | ||
|
|
7751e4c798 | ||
|
|
17646708b1 | ||
|
|
f14d663bb5 | ||
|
|
50fa747ec1 | ||
|
|
f6da09df54 | ||
|
|
9b9f8f3f77 | ||
|
|
aa19049f5b | ||
|
|
fd1dd7c695 | ||
|
|
916e9141d9 | ||
|
|
ca51a345fb | ||
|
|
f11e8501d0 | ||
|
|
3f047914d5 | ||
|
|
0a3810be2b | ||
|
|
c541e05997 | ||
|
|
feb69a282e | ||
|
|
c109d54961 | ||
|
|
948c31779b | ||
|
|
09a95b7597 | ||
|
|
ce32605aee | ||
|
|
f7d3546962 | ||
|
|
5980fd81f3 | ||
|
|
f2fe79f923 | ||
|
|
7234eebe7e | ||
|
|
479680bba6 | ||
|
|
cd68f1cdd9 | ||
|
|
97c7a53aed | ||
|
|
03dc9da8dc | ||
|
|
a4c9c85b00 | ||
|
|
1ed3dd9154 | ||
|
|
bfd1d1692f | ||
|
|
6db6a58c27 | ||
|
|
9751059fcd | ||
|
|
fc9984b27c | ||
|
|
5930c60462 | ||
|
|
f173d069bd | ||
|
|
5c201e3ed0 | ||
|
|
77da8a3470 | ||
|
|
9c3899135b | ||
|
|
dc160b3db4 | ||
|
|
84bd873efa | ||
|
|
8d4ce99c16 | ||
|
|
8a873e1e58 | ||
|
|
2780abd782 | ||
|
|
b1f996cb0e | ||
|
|
78a2859398 | ||
|
|
8daddcac5d | ||
|
|
c5759e3a2e | ||
|
|
3e68b44771 | ||
|
|
d2b4654ae3 | ||
|
|
5a73b59297 | ||
|
|
98a969c112 | ||
|
|
8cd08f4f02 | ||
|
|
32e4f92c61 | ||
|
|
d573fada7a | ||
|
|
a13ed1f503 | ||
|
|
07c274cdbf | ||
|
|
ab4ff06756 | ||
|
|
b65529743e | ||
|
|
c46379bc9f | ||
|
|
6cd8066ee0 | ||
|
|
4ad05ad9f5 | ||
|
|
6a41d44322 | ||
|
|
f804ba8420 | ||
|
|
2a5bd30fe3 | ||
|
|
a3d8e225b9 | ||
|
|
f51dff1d58 | ||
|
|
85fc35f119 | ||
|
|
48970dd559 | ||
|
|
1dfbb872f7 | ||
|
|
79397faa65 | ||
|
|
7b17ccf378 | ||
|
|
7a0aee5fb9 | ||
|
|
a94333d9bd | ||
|
|
5217be0bfc | ||
|
|
6420ddf8ba | ||
|
|
6656b3213b | ||
|
|
a74c475d7c | ||
|
|
6d0781b647 | ||
|
|
cc3582c1f9 | ||
|
|
2cadb7d88c | ||
|
|
7ed70230af | ||
|
|
36c0eddd86 | ||
|
|
b242e59bbe | ||
|
|
9bbba1ada6 | ||
|
|
e2c9d50539 | ||
|
|
ed5c53f494 | ||
|
|
fe9ee0dbfc | ||
|
|
2bcf761ef1 | ||
|
|
ccf34d4bc8 | ||
|
|
f7c0fe0dd3 | ||
|
|
3f9422df83 | ||
|
|
46ec0fb123 | ||
|
|
a5f6f991f8 | ||
|
|
eb73f3d3f1 | ||
|
|
a15f4e206d | ||
|
|
ce19b84232 | ||
|
|
b86a121707 | ||
|
|
c0bb6bcc14 | ||
|
|
98684ed66a | ||
|
|
15aa159999 | ||
|
|
956869ab1e | ||
|
|
6e601f9f0e | ||
|
|
f7bc044147 | ||
|
|
a91cdb60d7 | ||
|
|
15e62010c2 | ||
|
|
a350e62645 | ||
|
|
95d345bf6d | ||
|
|
c42a601467 | ||
|
|
46797a0757 | ||
|
|
65da8469ce | ||
|
|
6383347813 | ||
|
|
2c360ac59e | ||
|
|
b128511c28 | ||
|
|
8f2f93ec7e | ||
|
|
8faba7bf10 | ||
|
|
c8ab57e843 | ||
|
|
9a77bf4eef | ||
|
|
157281aec8 | ||
|
|
07710247bb | ||
|
|
9b2b73e05b | ||
|
|
8f3c070d07 | ||
|
|
29b5197c47 | ||
|
|
5f82bdeb67 | ||
|
|
d677fc99de | ||
|
|
4b1cecf2bd | ||
|
|
32f4a8be5a | ||
|
|
3553022597 | ||
|
|
cd37921975 | ||
|
|
78b4b9f572 | ||
|
|
ab4354e648 | ||
|
|
20ebeb5e62 | ||
|
|
4957c8b1de | ||
|
|
3df96e2702 | ||
|
|
3c4985ac1a | ||
|
|
3a907e0ba8 | ||
|
|
db8a46eb4c | ||
|
|
c97a5af473 | ||
|
|
bba98984d6 | ||
|
|
41d2a81a09 | ||
|
|
6a3babcf76 | ||
|
|
f599a4223c | ||
|
|
e0b972c3cb | ||
|
|
2bb5c0e2d5 | ||
|
|
7691cdc9a0 | ||
|
|
c1f59cf451 | ||
|
|
fc3676d6dc | ||
|
|
73f4935ca9 | ||
|
|
f9b8c6adf2 | ||
|
|
8c1d34cb87 | ||
|
|
631188df85 | ||
|
|
a9082dc012 | ||
|
|
1b62b0bf6c | ||
|
|
75c12ba458 | ||
|
|
25c3880ea4 | ||
|
|
ddb77a06a5 | ||
|
|
a4bea8202b | ||
|
|
57617e6658 | ||
|
|
ea54d08a50 | ||
|
|
945cc72860 | ||
|
|
4884b1acce | ||
|
|
532ad960a4 | ||
|
|
ad1f02de62 | ||
|
|
20728f504d | ||
|
|
5b780c28d0 | ||
|
|
e8fa516b2e | ||
|
|
e87ae4fc16 | ||
|
|
5b07f2dd21 | ||
|
|
6fc6385de4 | ||
|
|
ab26dec457 | ||
|
|
4119b67e0b | ||
|
|
05d55e5035 | ||
|
|
f95e7c8412 | ||
|
|
4e7fb91a56 | ||
|
|
cd120220f2 | ||
|
|
a63c094df9 | ||
|
|
336205627e | ||
|
|
a5dfef7202 | ||
|
|
87cda0c824 | ||
|
|
b6ee4a4b3d | ||
|
|
2fc6ea5064 | ||
|
|
aa8e774a1b | ||
|
|
606f47a5a6 | ||
|
|
e6daf07456 | ||
|
|
3d48446769 | ||
|
|
802911c179 | ||
|
|
192017b892 | ||
|
|
cfa70320c8 | ||
|
|
d4ad65a1ea | ||
|
|
0e38265f96 | ||
|
|
fbb6ec9a63 | ||
|
|
19868d147e | ||
|
|
73e6b9d5ca | ||
|
|
ca3986917c | ||
|
|
be565baf59 | ||
|
|
aa6168c31d | ||
|
|
51e1f5260c | ||
|
|
441b991b65 | ||
|
|
5fb7b7a216 | ||
|
|
2d40e20f51 | ||
|
|
7f9d72416a | ||
|
|
ded49c4e88 | ||
|
|
0a23b94232 | ||
|
|
6325d097cb | ||
|
|
65d3c0551e | ||
|
|
eeb2ce5350 | ||
|
|
e0c0e639d0 | ||
|
|
1471547bcd | ||
|
|
6cd97bb4b8 | ||
|
|
1f2458f75c | ||
|
|
437d56dd6c | ||
|
|
7adb00be5b | ||
|
|
74b4ddf8f5 | ||
|
|
d30325dd96 | ||
|
|
ca84eaeb13 | ||
|
|
0212ec34a8 | ||
|
|
707d534b95 | ||
|
|
4abc1eeb58 | ||
|
|
1503a20fbe | ||
|
|
a2d52602d3 | ||
|
|
fffff72fcd | ||
|
|
89cb6a585a | ||
|
|
c476c2059b | ||
|
|
1686836e38 | ||
|
|
5c0d211d82 | ||
|
|
aa9c64f00f | ||
|
|
f505a2636e | ||
|
|
8cec3f23e4 | ||
|
|
338ca4ad7c | ||
|
|
86b7f2e695 | ||
|
|
3a07f590cb | ||
|
|
0f350226ef | ||
|
|
729a0d6840 | ||
|
|
9afb57815c | ||
|
|
3b99ecc46f | ||
|
|
a8c689f6e9 | ||
|
|
4a8fd3f7fc | ||
|
|
b3843c7d97 | ||
|
|
c2bd0f938e | ||
|
|
385059f214 | ||
|
|
c6347676cd | ||
|
|
104370ca42 | ||
|
|
9f3de6b8ea | ||
|
|
3b5e2cc5b3 | ||
|
|
53b90b512c | ||
|
|
9ed4acc097 | ||
|
|
2e1ad23112 | ||
|
|
133c4f996f | ||
|
|
b37af47dca | ||
|
|
2a8385f3aa | ||
|
|
22b65fd22c | ||
|
|
b87a5cfd7a | ||
|
|
4f7b0cabb6 | ||
|
|
5996f1062f | ||
|
|
4d2361a704 | ||
|
|
fae5825618 | ||
|
|
731b11f159 | ||
|
|
1dd9dabb63 | ||
|
|
a05aa7f598 | ||
|
|
687439e697 | ||
|
|
bb30b51a9b | ||
|
|
9357b21af1 | ||
|
|
5cbf0ddfcf | ||
|
|
001896b5fa | ||
|
|
69b9d0bc3d | ||
|
|
dd958c30cf | ||
|
|
2bb5625eb4 | ||
|
|
3523df74db | ||
|
|
7a95f6ab95 | ||
|
|
242ef3a7a8 | ||
|
|
086528606e | ||
|
|
2757301d61 | ||
|
|
3051b5d146 | ||
|
|
3bca540085 | ||
|
|
cfc9cefe78 | ||
|
|
5c00e4f9cf | ||
|
|
2c39edf849 | ||
|
|
46b2da25a6 | ||
|
|
7c0dfc562e | ||
|
|
0da7055c2b | ||
|
|
6902067ea6 | ||
|
|
172aa3fb9c | ||
|
|
89314f4cb4 | ||
|
|
22a5f10add | ||
|
|
94e755c422 | ||
|
|
49d84dcfb4 | ||
|
|
e8e8614d6e | ||
|
|
96eb076496 | ||
|
|
2059cf92ff | ||
|
|
c8f16bbf43 | ||
|
|
27e9ab0982 | ||
|
|
bc27437730 | ||
|
|
21045599d0 | ||
|
|
e32c2d2d6b |
1
.gitattributes
vendored
@ -4,6 +4,7 @@
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.webp binary
|
||||
*.ico binary
|
||||
*.gif binary
|
||||
*.tff binary
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
title: '[Bug]'
|
||||
title: '[Bug] '
|
||||
labels:
|
||||
- bug
|
||||
assignees: pengzhanbo
|
||||
@ -58,10 +58,16 @@ body:
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Minimal reproduction
|
||||
description: |-
|
||||
If you are not reporting something obvious, a minimal reproduction repo and related log is required. you can fork [stackblitz.com](https://stackblitz.com/edit/vuepress-theme-plume-playground) to create a minimal reproduction.
|
||||
placeholder: reproduction repo url
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: If you are not reporting something obvious, a minimal reproduction repo and related log is required.
|
||||
description: If you are not reporting something obvious, related log is required.
|
||||
placeholder: Add any other context about the problem here. Especially the issue occurs in certain OS, browser or configuration.
|
||||
|
||||
12
.github/ISSUE_TEMPLATE/bug-report.zh-CN.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: 问题报告
|
||||
description: 创建一份问题报告以帮助我们改进
|
||||
title: '[Bug]'
|
||||
title: '[Bug] '
|
||||
labels:
|
||||
- bug
|
||||
assignees: pengzhanbo
|
||||
@ -58,10 +58,16 @@ body:
|
||||
description: 一个清晰简洁的错误描述。
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: 最小复现
|
||||
description: |-
|
||||
若非报告显而易见的问题,需提供最小化复现仓库及相关日志。你可通过 fork [stackblitz.com](https://stackblitz.com/edit/vuepress-theme-plume-playground) 来创建最小化复现环境。
|
||||
placeholder: 复现项目 url
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: 附加上下文
|
||||
description: 如果你报告的问题不明显,需要提供最小复现仓库及相关日志。
|
||||
description: 如果你报告的问题不明显,需要提供相关日志。
|
||||
placeholder: 在此添加有关问题的其他上下文信息。特别是问题在特定操作系统、浏览器或配置下出现。
|
||||
|
||||
@ -2,15 +2,25 @@ name: Deploy Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
# 以下文件发生变化时触发部署,这些文件与版本无关,因此可以自动更新
|
||||
- docs/demos.md
|
||||
- docs/sponsor.md
|
||||
- CONTRIBUTING.md
|
||||
- CONTRIBUTING.en-US.md
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -18,9 +28,9 @@ jobs:
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install deps
|
||||
@ -6,11 +6,14 @@ on:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -18,9 +21,9 @@ jobs:
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install deps
|
||||
@ -6,6 +6,10 @@ on:
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@ -13,15 +17,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install deps
|
||||
55
.github/workflows/release.yaml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
uses: ./.github/workflows/lint.yaml
|
||||
|
||||
test:
|
||||
uses: ./.github/workflows/test.yaml
|
||||
|
||||
release:
|
||||
if: github.repository == 'pengzhanbo/vuepress-theme-plume'
|
||||
needs: [test, lint]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
registry-url: https://registry.npmjs.org
|
||||
cache: pnpm
|
||||
|
||||
- name: Install deps
|
||||
run: pnpm install
|
||||
|
||||
- name: Update npm
|
||||
run: npm i -g npm@latest
|
||||
|
||||
- name: Build And Publish
|
||||
id: publish
|
||||
run: |
|
||||
pnpm build
|
||||
pnpm release:publish --no-git-checks
|
||||
pnpm release:sync
|
||||
|
||||
- run: npx changelogithub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
||||
deploy:
|
||||
uses: ./.github/workflows/docs-deploy.yaml
|
||||
22
.github/workflows/release.yml
vendored
@ -1,22 +0,0 @@
|
||||
name: Add Release Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- run: npx changelogithub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
@ -6,6 +6,10 @@ on:
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
unit-test:
|
||||
@ -13,15 +17,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
|
||||
- name: Install deps
|
||||
@ -34,6 +38,6 @@ jobs:
|
||||
|
||||
- name: Upload coverage
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
3
.gitignore
vendored
@ -15,3 +15,6 @@ dist/
|
||||
|
||||
coverage/
|
||||
.idea
|
||||
|
||||
.claude/
|
||||
!.claude/skills/
|
||||
|
||||
3
.npmrc
@ -1,3 +0,0 @@
|
||||
strict-peer-dependencies=false
|
||||
shamefully-hoist=true
|
||||
shell-emulator=true
|
||||
1
.vscode/settings.json
vendored
@ -76,6 +76,7 @@
|
||||
"nprogress",
|
||||
"pnpm",
|
||||
"portfinder",
|
||||
"qrcode",
|
||||
"shiki",
|
||||
"shikiji",
|
||||
"shikijs",
|
||||
|
||||
1260
CHANGELOG.md
111
CLAUDE.md
Normal 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`
|
||||
@ -19,7 +19,7 @@ In the `plugins` directory:
|
||||
|
||||
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+
|
||||
|
||||
Clone the repository and install dependencies:
|
||||
|
||||
@ -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+
|
||||
|
||||
克隆代码仓库,并安装依赖:
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ---------------- | ------------------ |
|
||||
| >= 1.0.0-rc.154 | :white_check_mark: |
|
||||
| < 1.0.0-rc.154 | :x: |
|
||||
| >= 1.0.0-rc.190 | :white_check_mark: |
|
||||
| < 1.0.0-rc.190 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-vuepress-theme-plume",
|
||||
"type": "module",
|
||||
"version": "1.0.0-rc.160",
|
||||
"version": "1.0.0-rc.196",
|
||||
"description": "The cli for create vuepress-theme-plume's project",
|
||||
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
|
||||
"license": "MIT",
|
||||
@ -27,7 +27,7 @@
|
||||
"templates"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsdown"
|
||||
"build": "tsdown --config-loader unrun"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "catalog:prod",
|
||||
@ -36,12 +36,17 @@
|
||||
"handlebars": "catalog:prod",
|
||||
"nano-spawn": "catalog:prod",
|
||||
"os-locale": "catalog:prod",
|
||||
"picocolors": "catalog:prod"
|
||||
"picocolors": "catalog:prod",
|
||||
"sort-package-json": "catalog:prod"
|
||||
},
|
||||
"plume-deps": {
|
||||
"vuepress": "2.0.0-rc.24",
|
||||
"vue": "^3.5.18",
|
||||
"vuepress": "2.0.0-rc.28",
|
||||
"vue": "^3.5.32",
|
||||
"http-server": "^14.1.1",
|
||||
"typescript": "^5.9.2"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +1,86 @@
|
||||
import type { Bundler, Langs, Options } from './types.js'
|
||||
|
||||
/**
|
||||
* Language options for VuePress configuration
|
||||
*
|
||||
* 语言选项,用于 VuePress 配置
|
||||
*/
|
||||
export const languageOptions: Options<Langs> = [
|
||||
{ label: 'English', value: 'en-US' },
|
||||
{ label: '简体中文', value: 'zh-CN' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Bundler options for VuePress build tool
|
||||
*
|
||||
* 构建器选项,用于 VuePress 构建工具
|
||||
*/
|
||||
export const bundlerOptions: Options<Bundler> = [
|
||||
{ label: 'Vite', value: 'vite' },
|
||||
{ label: 'Webpack', value: 'webpack' },
|
||||
]
|
||||
|
||||
/**
|
||||
* Operation mode for VuePress CLI
|
||||
*
|
||||
* VuePress CLI 操作模式
|
||||
* @readonly
|
||||
* @enum {number}
|
||||
*/
|
||||
export enum Mode {
|
||||
/**
|
||||
* Initialize existing directory
|
||||
*
|
||||
* 初始化现有目录
|
||||
*/
|
||||
init,
|
||||
/**
|
||||
* Create new project
|
||||
*
|
||||
* 创建新项目
|
||||
*/
|
||||
create,
|
||||
}
|
||||
|
||||
/**
|
||||
* Deployment type for VuePress site
|
||||
*
|
||||
* VuePress 站点部署类型
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export enum DeployType {
|
||||
/**
|
||||
* GitHub Pages deployment
|
||||
*
|
||||
* GitHub Pages 部署
|
||||
*/
|
||||
github = 'github',
|
||||
/**
|
||||
* Vercel deployment
|
||||
*
|
||||
* Vercel 部署
|
||||
*/
|
||||
vercel = 'vercel',
|
||||
/**
|
||||
* Netlify deployment
|
||||
*
|
||||
* Netlify 部署
|
||||
*/
|
||||
netlify = 'netlify',
|
||||
/**
|
||||
* Custom deployment
|
||||
*
|
||||
* 自定义部署
|
||||
*/
|
||||
custom = 'custom',
|
||||
}
|
||||
|
||||
/**
|
||||
* Deployment options for hosting platforms
|
||||
*
|
||||
* 部署选项,用于托管平台
|
||||
*/
|
||||
export const deployOptions: Options<DeployType> = [
|
||||
{ label: 'Custom', value: DeployType.custom },
|
||||
{ label: 'GitHub Pages', value: DeployType.github },
|
||||
|
||||
@ -8,6 +8,15 @@ import { createPackageJson } from './packageJson.js'
|
||||
import { createRender } from './render.js'
|
||||
import { getTemplate, readFiles, readJsonFile, writeFiles } from './utils/index.js'
|
||||
|
||||
/**
|
||||
* Generate VuePress project files
|
||||
*
|
||||
* 生成 VuePress 项目文件
|
||||
*
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @param data - Resolved configuration data / 解析后的配置数据
|
||||
* @param cwd - Current working directory / 当前工作目录
|
||||
*/
|
||||
export async function generate(
|
||||
mode: Mode,
|
||||
data: ResolvedData,
|
||||
@ -106,6 +115,14 @@ export async function generate(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create documentation files based on configuration
|
||||
*
|
||||
* 根据配置创建文档文件
|
||||
*
|
||||
* @param data - Resolved configuration data / 解析后的配置数据
|
||||
* @returns Array of file objects / 文件对象数组
|
||||
*/
|
||||
async function createDocsFiles(data: ResolvedData): Promise<File[]> {
|
||||
const fileList: File[] = []
|
||||
if (data.multiLanguage) {
|
||||
@ -131,6 +148,15 @@ async function createDocsFiles(data: ResolvedData): Promise<File[]> {
|
||||
return updateFileListTarget(fileList, data.docsDir)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file list target path
|
||||
*
|
||||
* 更新文件列表的目标路径
|
||||
*
|
||||
* @param fileList - Array of files / 文件数组
|
||||
* @param target - Target directory path / 目标目录路径
|
||||
* @returns Updated file array / 更新后的文件数组
|
||||
*/
|
||||
function updateFileListTarget(fileList: File[], target: string): File[] {
|
||||
return fileList.map(({ filepath, content }) => ({
|
||||
filepath: path.join(target, filepath),
|
||||
|
||||
@ -1,3 +1,15 @@
|
||||
/**
|
||||
* VuePress Theme Plume CLI Entry Point
|
||||
*
|
||||
* VuePress Theme Plume CLI 入口文件
|
||||
*
|
||||
* This module provides command-line interface for creating and initializing
|
||||
* VuePress projects with vuepress-theme-plume.
|
||||
*
|
||||
* 本模块提供用于创建和初始化 VuePress 项目的命令行接口。
|
||||
*
|
||||
* @module cli
|
||||
*/
|
||||
import cac from 'cac'
|
||||
import { version } from '../package.json'
|
||||
import { Mode } from './constants.js'
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import type { Locale } from '../types.js'
|
||||
|
||||
/**
|
||||
* English locale configuration for CLI prompts and messages.
|
||||
*
|
||||
* CLI 提示和消息的英语本地化配置。
|
||||
*/
|
||||
export const en: Locale = {
|
||||
'question.root': 'Where would you want to initialize VuePress?',
|
||||
'question.site.name': 'Site Name:',
|
||||
|
||||
@ -2,6 +2,15 @@ import type { Langs, Locale } from '../types.js'
|
||||
import { en } from './en.js'
|
||||
import { zh } from './zh.js'
|
||||
|
||||
/**
|
||||
* Locale configurations for different languages.
|
||||
*
|
||||
* 不同语言的本地化配置。
|
||||
*
|
||||
* Maps language codes to their respective locale strings.
|
||||
*
|
||||
* 将语言代码映射到相应的本地化字符串。
|
||||
*/
|
||||
export const locales: Record<Langs, Locale> = {
|
||||
'zh-CN': zh,
|
||||
'en-US': en,
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import type { Locale } from '../types.js'
|
||||
|
||||
/**
|
||||
* Chinese (Simplified) locale configuration for CLI prompts and messages.
|
||||
*
|
||||
* CLI 提示和消息的简体中文本地化配置。
|
||||
*/
|
||||
export const zh: Locale = {
|
||||
'question.root': '您想在哪里初始化 VuePress?',
|
||||
'question.site.name': '站点名称:',
|
||||
|
||||
@ -1,9 +1,41 @@
|
||||
import type { File, ResolvedData } from './types.js'
|
||||
import { kebabCase } from '@pengzhanbo/utils'
|
||||
import { attemptAsync, kebabCase } from '@pengzhanbo/utils'
|
||||
import spawn from 'nano-spawn'
|
||||
import _sortPackageJson from 'sort-package-json'
|
||||
import { Mode } from './constants.js'
|
||||
import { readJsonFile, resolve } from './utils/index.js'
|
||||
|
||||
/**
|
||||
* Sort package.json fields in a consistent order.
|
||||
*
|
||||
* 按一致顺序排序 package.json 字段。
|
||||
*
|
||||
* @param json - Package.json object to sort / 要排序的 package.json 对象
|
||||
* @returns Sorted package.json object / 排序后的 package.json 对象
|
||||
*/
|
||||
function sortPackageJson(json: Record<any, any>) {
|
||||
return _sortPackageJson(json, {
|
||||
sortOrder: ['name', 'type', 'version', 'private', 'description', 'packageManager', 'author', 'license', 'scripts', 'devDependencies', 'dependencies', 'pnpm'],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create package.json file for VuePress project
|
||||
*
|
||||
* 为 VuePress 项目创建 package.json 文件
|
||||
*
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @param pkg - Existing package.json data / 现有的 package.json 数据
|
||||
* @param data - Resolved configuration data / 解析后的配置数据
|
||||
* @param data.packageManager - Package manager to use / 要使用的包管理器
|
||||
* @param data.siteName - Site name / 站点名称
|
||||
* @param data.siteDescription - Site description / 站点描述
|
||||
* @param data.docsDir - Documentation directory path / 文档目录路径
|
||||
* @param data.bundler - Bundler to use / 要使用的打包器
|
||||
* @param data.injectNpmScripts - Whether to inject npm scripts / 是否注入 npm 脚本
|
||||
*
|
||||
* @returns File object with package.json content / 包含 package.json 内容的文件对象
|
||||
*/
|
||||
export async function createPackageJson(
|
||||
mode: Mode,
|
||||
pkg: Record<string, any>,
|
||||
@ -14,7 +46,6 @@ export async function createPackageJson(
|
||||
siteDescription,
|
||||
bundler,
|
||||
injectNpmScripts,
|
||||
useTs,
|
||||
}: ResolvedData,
|
||||
): Promise<File> {
|
||||
if (mode === Mode.create) {
|
||||
@ -24,10 +55,11 @@ export async function createPackageJson(
|
||||
pkg.description = siteDescription
|
||||
|
||||
if (packageManager !== 'npm') {
|
||||
let version = await getPackageManagerVersion(packageManager)
|
||||
let [, version] = await attemptAsync(getPackageManagerVersion, packageManager)
|
||||
if (version) {
|
||||
if (packageManager === 'yarn' && version.startsWith('1'))
|
||||
version = '4.6.0'
|
||||
if (packageManager === 'yarn' && version.startsWith('1')) {
|
||||
version = '4.10.3'
|
||||
}
|
||||
pkg.packageManager = `${packageManager}@${version}`
|
||||
|
||||
// pnpm@10 should add `onlyBuiltDependencies`
|
||||
@ -39,12 +71,12 @@ export async function createPackageJson(
|
||||
}
|
||||
}
|
||||
|
||||
const userInfo = await getUserInfo()
|
||||
const [, userInfo] = await attemptAsync(getUserInfo)
|
||||
if (userInfo) {
|
||||
pkg.author = userInfo.username + (userInfo.email ? ` <${userInfo.email}>` : '')
|
||||
}
|
||||
pkg.license = 'MIT'
|
||||
pkg.engines = { node: '^20.6.0 || >=22.0.0' }
|
||||
pkg.engines = { node: '^20.19.0 || >=22.0.0' }
|
||||
}
|
||||
|
||||
if (injectNpmScripts) {
|
||||
@ -76,36 +108,41 @@ export async function createPackageJson(
|
||||
if (!hasDep('vue'))
|
||||
deps.push('vue')
|
||||
|
||||
if (useTs)
|
||||
deps.push('typescript')
|
||||
deps.push('typescript')
|
||||
|
||||
for (const dep of deps)
|
||||
pkg.devDependencies[dep] = meta[dep]
|
||||
|
||||
return {
|
||||
filepath: 'package.json',
|
||||
content: JSON.stringify(pkg, null, 2),
|
||||
content: JSON.stringify(sortPackageJson(pkg), null, 2),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user information from git global configuration.
|
||||
*
|
||||
* 从 git 全局配置获取用户信息。
|
||||
*
|
||||
* @returns User information object with username and email / 包含用户名和邮箱的用户信息对象
|
||||
* @throws Error if git command fails / 如果 git 命令失败则抛出错误
|
||||
*/
|
||||
async function getUserInfo() {
|
||||
try {
|
||||
const { output: username } = await spawn('git', ['config', '--global', 'user.name'])
|
||||
const { output: email } = await spawn('git', ['config', '--global', 'user.email'])
|
||||
console.log('userInfo', username, email)
|
||||
return { username, email }
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
}
|
||||
const { output: username } = await spawn('git', ['config', '--global', 'user.name'])
|
||||
const { output: email } = await spawn('git', ['config', '--global', 'user.email'])
|
||||
return { username, email }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of a package manager.
|
||||
*
|
||||
* 获取包管理器的版本。
|
||||
*
|
||||
* @param pkg - Package manager name (npm, yarn, pnpm) / 包管理器名称
|
||||
* @returns Version string of the package manager / 包管理器的版本字符串
|
||||
* @throws Error if package manager command fails / 如果包管理器命令失败则抛出错误
|
||||
*/
|
||||
async function getPackageManagerVersion(pkg: string) {
|
||||
try {
|
||||
const { output } = await spawn(pkg, ['--version'])
|
||||
return output
|
||||
}
|
||||
catch {
|
||||
return null
|
||||
}
|
||||
const { output } = await spawn(pkg, ['--version'])
|
||||
return output
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Bundler, Langs, PromptResult } from './types.js'
|
||||
import { createRequire } from 'node:module'
|
||||
import process from 'node:process'
|
||||
import { cancel, confirm, group, select, text } from '@clack/prompts'
|
||||
import { osLocale } from 'os-locale'
|
||||
import osLocale from 'os-locale'
|
||||
import { bundlerOptions, deployOptions, DeployType, languageOptions, Mode } from './constants.js'
|
||||
import { setLang, t } from './translate.js'
|
||||
|
||||
@ -10,6 +10,15 @@ const require = createRequire(process.cwd())
|
||||
|
||||
const REG_DIR_CHAR = /[<>:"\\|?*[\]]/
|
||||
|
||||
/**
|
||||
* Prompt user for project configuration
|
||||
*
|
||||
* 提示用户输入项目配置
|
||||
*
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @param root - Optional root directory path / 可选的根目录路径
|
||||
* @returns Resolved prompt result / 解析后的提示结果
|
||||
*/
|
||||
export async function prompt(mode: Mode, root?: string): Promise<PromptResult> {
|
||||
let hasTs = false
|
||||
if (mode === Mode.init) {
|
||||
@ -21,7 +30,7 @@ export async function prompt(mode: Mode, root?: string): Promise<PromptResult> {
|
||||
|
||||
const result: PromptResult = await group({
|
||||
displayLang: async () => {
|
||||
const locale = await osLocale()
|
||||
const locale = osLocale()
|
||||
|
||||
if (locale === 'zh-CN' || locale === 'zh-Hans') {
|
||||
setLang('zh-CN')
|
||||
|
||||
@ -2,16 +2,33 @@ import type { ResolvedData } from './types.js'
|
||||
import { kebabCase } from '@pengzhanbo/utils'
|
||||
import handlebars from 'handlebars'
|
||||
|
||||
/**
|
||||
* Extended resolved data with additional rendering information
|
||||
*
|
||||
* 扩展的解析数据,包含额外的渲染信息
|
||||
*/
|
||||
export interface RenderData extends ResolvedData {
|
||||
/** Project name in kebab-case / 项目名称(kebab-case 格式) */
|
||||
name: string
|
||||
/** Site name / 网站名称 */
|
||||
siteName: string
|
||||
/** Locale configuration array / 语言配置数组 */
|
||||
locales: { path: string, lang: string, isEn: boolean, prefix: string }[]
|
||||
/** Whether default language is English / 默认语言是否为英语 */
|
||||
isEN: boolean
|
||||
}
|
||||
|
||||
handlebars.registerHelper('removeLeadingSlash', (path: string) => path.replace(/^\//, ''))
|
||||
handlebars.registerHelper('equal', (a: string, b: string) => a === b)
|
||||
|
||||
/**
|
||||
* Create render function with Handlebars template engine
|
||||
*
|
||||
* 使用 Handlebars 模板引擎创建渲染函数
|
||||
*
|
||||
* @param result - Resolved configuration data / 解析后的配置数据
|
||||
* @returns Render function that processes Handlebars templates / 处理 Handlebars 模板的渲染函数
|
||||
*/
|
||||
export function createRender(result: ResolvedData) {
|
||||
const data: RenderData = {
|
||||
...result,
|
||||
|
||||
@ -11,6 +11,14 @@ import { prompt } from './prompt.js'
|
||||
import { t } from './translate.js'
|
||||
import { getPackageManager } from './utils/index.js'
|
||||
|
||||
/**
|
||||
* Run the CLI workflow for VuePress project initialization or creation
|
||||
*
|
||||
* 执行 VuePress 项目初始化或创建的 CLI 工作流程
|
||||
*
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @param root - Root directory path / 根目录路径
|
||||
*/
|
||||
export async function run(mode: Mode, root?: string): Promise<void> {
|
||||
intro(colors.cyan('Welcome to VuePress and vuepress-theme-plume !'))
|
||||
|
||||
@ -70,6 +78,15 @@ export async function run(mode: Mode, root?: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve prompt result into final configuration data.
|
||||
*
|
||||
* 将提示结果解析为最终配置数据。
|
||||
*
|
||||
* @param result - Prompt result from user input / 用户输入的提示结果
|
||||
* @param mode - Operation mode (init or create) / 操作模式(初始化或创建)
|
||||
* @returns Resolved configuration data / 解析后的配置数据
|
||||
*/
|
||||
function resolveData(result: PromptResult, mode: Mode): ResolvedData {
|
||||
return {
|
||||
...result,
|
||||
|
||||
@ -6,6 +6,14 @@ interface Translate {
|
||||
t: (key: keyof Locale) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translate instance with specified language
|
||||
*
|
||||
* 创建指定语言的翻译实例
|
||||
*
|
||||
* @param lang - Language code / 语言代码
|
||||
* @returns Translate interface / 翻译接口
|
||||
*/
|
||||
function createTranslate(lang?: Langs): Translate {
|
||||
let current: Langs = lang || 'en-US'
|
||||
|
||||
@ -19,5 +27,21 @@ function createTranslate(lang?: Langs): Translate {
|
||||
|
||||
const translate = createTranslate()
|
||||
|
||||
/**
|
||||
* Get translated string by key
|
||||
*
|
||||
* 根据键获取翻译后的字符串
|
||||
*
|
||||
* @param key - Locale key / 本地化键名
|
||||
* @returns Translated string / 翻译后的字符串
|
||||
*/
|
||||
export const t: Translate['t'] = translate.t
|
||||
|
||||
/**
|
||||
* Set current language
|
||||
*
|
||||
* 设置当前语言
|
||||
*
|
||||
* @param lang - Language code to set / 要设置的语言代码
|
||||
*/
|
||||
export const setLang: Translate['setLang'] = translate.setLang
|
||||
|
||||
221
cli/src/types.ts
@ -1,57 +1,276 @@
|
||||
import type { DeployType } from './constants.js'
|
||||
|
||||
/**
|
||||
* Supported language codes for VuePress site
|
||||
*
|
||||
* VuePress 站点支持的语言代码
|
||||
*/
|
||||
export type Langs = 'zh-CN' | 'en-US'
|
||||
|
||||
/**
|
||||
* Locale configuration for CLI prompts and messages
|
||||
*
|
||||
* CLI 提示和消息的国际化配置
|
||||
*/
|
||||
export interface Locale {
|
||||
/**
|
||||
* Question: Project root directory name
|
||||
*
|
||||
* 问题:项目根目录名称
|
||||
*/
|
||||
'question.root': string
|
||||
/**
|
||||
* Question: Site name
|
||||
*
|
||||
* 问题:站点名称
|
||||
*/
|
||||
'question.site.name': string
|
||||
/**
|
||||
* Question: Site description
|
||||
*
|
||||
* 问题:站点描述
|
||||
*/
|
||||
'question.site.description': string
|
||||
/**
|
||||
* Question: Enable multi-language support
|
||||
*
|
||||
* 问题:启用多语言支持
|
||||
*/
|
||||
'question.multiLanguage': string
|
||||
/**
|
||||
* Question: Default language
|
||||
*
|
||||
* 问题:默认语言
|
||||
*/
|
||||
'question.defaultLanguage': string
|
||||
/**
|
||||
* Question: Build tool bundler
|
||||
*
|
||||
* 问题:构建工具
|
||||
*/
|
||||
'question.bundler': string
|
||||
/**
|
||||
* Question: Use TypeScript
|
||||
*
|
||||
* 问题:使用 TypeScript
|
||||
*/
|
||||
'question.useTs': string
|
||||
/**
|
||||
* Question: Inject npm scripts
|
||||
*
|
||||
* 问题:注入 npm 脚本
|
||||
*/
|
||||
'question.injectNpmScripts': string
|
||||
/**
|
||||
* Question: Initialize git repository
|
||||
*
|
||||
* 问题:初始化 git 仓库
|
||||
*/
|
||||
'question.git': string
|
||||
/**
|
||||
* Question: Deployment type
|
||||
*
|
||||
* 问题:部署类型
|
||||
*/
|
||||
'question.deploy': string
|
||||
/**
|
||||
* Question: Install dependencies
|
||||
*
|
||||
* 问题:安装依赖
|
||||
*/
|
||||
'question.installDeps': string
|
||||
|
||||
/**
|
||||
* Spinner: Start message
|
||||
*
|
||||
* 加载动画:开始消息
|
||||
*/
|
||||
'spinner.start': string
|
||||
/**
|
||||
* Spinner: Stop message
|
||||
*
|
||||
* 加载动画:停止消息
|
||||
*/
|
||||
'spinner.stop': string
|
||||
/**
|
||||
* Spinner: Git init message
|
||||
*
|
||||
* 加载动画:Git 初始化消息
|
||||
*/
|
||||
'spinner.git': string
|
||||
/**
|
||||
* Spinner: Install message
|
||||
*
|
||||
* 加载动画:安装消息
|
||||
*/
|
||||
'spinner.install': string
|
||||
/**
|
||||
* Spinner: Command hint message
|
||||
*
|
||||
* 加载动画:命令提示消息
|
||||
*/
|
||||
'spinner.command': string
|
||||
|
||||
/**
|
||||
* Hint: Cancel operation
|
||||
*
|
||||
* 提示:取消操作
|
||||
*/
|
||||
'hint.cancel': string
|
||||
/**
|
||||
* Hint: Root directory
|
||||
*
|
||||
* 提示:根目录
|
||||
*/
|
||||
'hint.root': string
|
||||
/**
|
||||
* Hint: Illegal root directory name
|
||||
*
|
||||
* 提示:非法的根目录名称
|
||||
*/
|
||||
'hint.root.illegal': string
|
||||
}
|
||||
|
||||
/**
|
||||
* Package manager types
|
||||
*
|
||||
* 包管理器类型
|
||||
*/
|
||||
export type PackageManager = 'npm' | 'yarn' | 'pnpm'
|
||||
|
||||
/**
|
||||
* Build tool bundler types
|
||||
*
|
||||
* 构建工具类型
|
||||
*/
|
||||
export type Bundler = 'vite' | 'webpack'
|
||||
|
||||
/**
|
||||
* Generic options type for CLI prompts
|
||||
*
|
||||
* CLI 提示的通用选项类型
|
||||
*
|
||||
* @template Value - The value type for options
|
||||
* @template Label - The label type for options
|
||||
*/
|
||||
export type Options<Value = string, Label = string> = { label: Label, value: Value }[]
|
||||
|
||||
/**
|
||||
* File structure for generated project
|
||||
*
|
||||
* 生成项目的文件结构
|
||||
*/
|
||||
export interface File {
|
||||
/**
|
||||
* File path relative to project root
|
||||
*
|
||||
* 相对于项目根目录的文件路径
|
||||
*/
|
||||
filepath: string
|
||||
/**
|
||||
* File content
|
||||
*
|
||||
* 文件内容
|
||||
*/
|
||||
content: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Result from CLI prompts
|
||||
*
|
||||
* CLI 提示结果
|
||||
*/
|
||||
export interface PromptResult {
|
||||
displayLang: string // cli display language
|
||||
/**
|
||||
* CLI display language
|
||||
*
|
||||
* CLI 显示语言
|
||||
*/
|
||||
displayLang: string
|
||||
/**
|
||||
* Project root directory name
|
||||
*
|
||||
* 项目根目录名称
|
||||
*/
|
||||
root: string
|
||||
/**
|
||||
* Site name
|
||||
*
|
||||
* 站点名称
|
||||
*/
|
||||
siteName: string
|
||||
/**
|
||||
* Site description
|
||||
*
|
||||
* 站点描述
|
||||
*/
|
||||
siteDescription: string
|
||||
/**
|
||||
* Build tool bundler
|
||||
*
|
||||
* 构建工具
|
||||
*/
|
||||
bundler: Bundler
|
||||
/**
|
||||
* Enable multi-language support
|
||||
*
|
||||
* 启用多语言支持
|
||||
*/
|
||||
multiLanguage: boolean
|
||||
/**
|
||||
* Default language
|
||||
*
|
||||
* 默认语言
|
||||
*/
|
||||
defaultLanguage: Langs
|
||||
/**
|
||||
* Use TypeScript
|
||||
*
|
||||
* 使用 TypeScript
|
||||
*/
|
||||
useTs: boolean
|
||||
/**
|
||||
* Inject npm scripts
|
||||
*
|
||||
* 注入 npm 脚本
|
||||
*/
|
||||
injectNpmScripts: boolean
|
||||
/**
|
||||
* Deployment type
|
||||
*
|
||||
* 部署类型
|
||||
*/
|
||||
deploy: DeployType
|
||||
/**
|
||||
* Initialize git repository
|
||||
*
|
||||
* 初始化 git 仓库
|
||||
*/
|
||||
git: boolean
|
||||
/**
|
||||
* Install dependencies
|
||||
*
|
||||
* 安装依赖
|
||||
*/
|
||||
install: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved data after processing prompts
|
||||
*
|
||||
* 处理提示后的解析数据
|
||||
*/
|
||||
export interface ResolvedData extends PromptResult {
|
||||
/**
|
||||
* Selected package manager
|
||||
*
|
||||
* 选择的包管理器
|
||||
*/
|
||||
packageManager: PackageManager
|
||||
/**
|
||||
* Documentation directory name
|
||||
*
|
||||
* 文档目录名称
|
||||
*/
|
||||
docsDir: string
|
||||
}
|
||||
|
||||
@ -2,6 +2,14 @@ import type { File } from '../types.js'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
|
||||
/**
|
||||
* Read all files from a directory recursively
|
||||
*
|
||||
* 递归读取目录下的所有文件
|
||||
*
|
||||
* @param root - Root directory path to read from / 要读取的根目录路径
|
||||
* @returns Array of file objects / 文件对象数组
|
||||
*/
|
||||
export async function readFiles(root: string): Promise<File[]> {
|
||||
const filepaths = await fs.readdir(root, { recursive: true })
|
||||
const files: File[] = []
|
||||
@ -18,6 +26,15 @@ export async function readFiles(root: string): Promise<File[]> {
|
||||
return files
|
||||
}
|
||||
|
||||
/**
|
||||
* Write files to target directory
|
||||
*
|
||||
* 将文件写入目标目录
|
||||
*
|
||||
* @param files - Array of file objects to write / 要写入的文件对象数组
|
||||
* @param target - Target directory path / 目标目录路径
|
||||
* @param rewrite - Optional function to rewrite file paths / 可选的文件路径重写函数
|
||||
*/
|
||||
export async function writeFiles(
|
||||
files: File[],
|
||||
target: string,
|
||||
@ -32,6 +49,14 @@ export async function writeFiles(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and parse JSON file
|
||||
*
|
||||
* 读取并解析 JSON 文件
|
||||
*
|
||||
* @param filepath - Path to JSON file / JSON 文件路径
|
||||
* @returns Parsed JSON object or null if parsing fails / 解析后的 JSON 对象,解析失败返回 null
|
||||
*/
|
||||
export async function readJsonFile<T extends Record<string, any> = Record<string, any>>(filepath: string): Promise<T | null> {
|
||||
try {
|
||||
const content = await fs.readFile(filepath, 'utf-8')
|
||||
|
||||
@ -1,6 +1,19 @@
|
||||
import type { PackageManager } from '../types.js'
|
||||
import process from 'node:process'
|
||||
|
||||
/**
|
||||
* Detect the current package manager from environment variables.
|
||||
*
|
||||
* 从环境变量检测当前使用的包管理器。
|
||||
*
|
||||
* @returns The detected package manager name / 检测到的包管理器名称
|
||||
* @example
|
||||
* // When using pnpm
|
||||
* const pm = getPackageManager() // returns 'pnpm'
|
||||
*
|
||||
* // When using npm
|
||||
* const pm = getPackageManager() // returns 'npm'
|
||||
*/
|
||||
export function getPackageManager(): PackageManager {
|
||||
const name = process.env?.npm_config_user_agent || 'npm'
|
||||
return name.split('/')[0] as PackageManager
|
||||
|
||||
@ -3,8 +3,24 @@ import { fileURLToPath } from 'node:url'
|
||||
|
||||
export const __dirname: string = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
/**
|
||||
* Resolve path relative to the project root
|
||||
*
|
||||
* 相对于项目根目录解析路径
|
||||
*
|
||||
* @param args - Path segments to resolve / 要解析的路径段
|
||||
* @returns Resolved absolute path / 解析后的绝对路径
|
||||
*/
|
||||
export const resolve = (...args: string[]): string => path.resolve(__dirname, '../', ...args)
|
||||
|
||||
/**
|
||||
* Get template directory path
|
||||
*
|
||||
* 获取模板目录路径
|
||||
*
|
||||
* @param dir - Subdirectory name within templates / templates 中的子目录名称
|
||||
* @returns Resolved template directory path / 解析后的模板目录路径
|
||||
*/
|
||||
export const getTemplate = (dir: string): string => resolve('templates', dir)
|
||||
|
||||
export * from './fs.js'
|
||||
|
||||
124
cli/templates/.vuepress/collections.ts.handlebars
Normal file
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @see https://theme-plume.vuejs.press/guide/collection/ 查看文档了解配置详情。
|
||||
*
|
||||
* Collections 配置文件,它在 `.vuepress/plume.config.{{#if useTs}}ts{{else}}js{{/if}}` 中被导入。
|
||||
*
|
||||
* 请注意,你应该先在这里配置好 Collections,然后再启动 vuepress,主题会在启动 vuepress 时,
|
||||
* 读取这里配置的 Collections,然后在与 Collection 相关的 Markdown 文件中,自动生成 permalink。
|
||||
*
|
||||
* collection 的 type 为 `post` 时,表示为 文档列表类型(即没有侧边导航栏,有文档列表页)
|
||||
* 可用于实现如 博客、专栏 等以文章列表聚合形式的文档集合 (内容相对碎片化的)
|
||||
*
|
||||
* collection 的 type 为 `doc` 时,表示为文档类型(即有侧边导航栏)
|
||||
* 可用于实现如 笔记、知识库、文档等以侧边导航栏形式的文档集合 (内容强关联、成体系的)
|
||||
* 如果发现 侧边栏没有显示,那么请检查你的配置是否正确,以及 Markdown 文件中的 permalink
|
||||
* 是否是以对应的 Collection 配置的 link 的前缀开头。 是否展示侧边栏是根据 页面链接 的前缀 与 `collection.link`
|
||||
* 的前缀是否匹配来决定。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 在受支持的 IDE 中会智能提示配置项。
|
||||
*
|
||||
* - `defineCollections` 是用于定义 collection 集合的帮助函数
|
||||
* - `defineCollection` 是用于定义单个 collection 配置的帮助函数
|
||||
*
|
||||
* 通过 `defineCollection` 定义的 collection 配置,应该填入 `defineCollections` 中
|
||||
*/
|
||||
import { defineCollection, defineCollections } from 'vuepress-theme-plume'
|
||||
|
||||
{{#if multiLanguage}}
|
||||
{{#each locales}}
|
||||
/* =================== locale: {{ lang }} ======================= */
|
||||
|
||||
const {{ prefix }}Blog = defineCollection({
|
||||
// post 类型,这里用于实现 博客功能
|
||||
type: 'post',
|
||||
// 文档集合所在目录,相对于 `docs{{ path }}`
|
||||
dir: 'blog',
|
||||
// 文档标题,它将用于在页面的面包屑导航中显示
|
||||
title: 'Blog',
|
||||
// 文章列表页的链接,如果 `linkPrefix` 未定义,它也将作为 相关的文章的 permalink 的前缀
|
||||
link: '/blog/',
|
||||
// linkPrefix: '/article/', // 相关文章的链接前缀
|
||||
// postList: true, // 是否启用文章列表页
|
||||
// tags: true, // 是否启用标签页
|
||||
// archives: true, // 是否启用归档页
|
||||
// categories: true, // 是否启用分类页
|
||||
// postCover: 'right', // 文章封面位置
|
||||
// pagination: 15, // 每页显示文章数量
|
||||
})
|
||||
|
||||
const {{ prefix }}DemoDoc = defineCollection({
|
||||
// doc 类型,该类型带有侧边栏
|
||||
type: 'doc',
|
||||
// 文档集合所在目录,相对于 `docs{{ path }}`
|
||||
dir: 'demo',
|
||||
// `dir` 所指向的目录中的所有 markdown 文件,其 permalink 需要以 `linkPrefix` 配置作为前缀
|
||||
// 如果 前缀不一致,则无法生成侧边栏。
|
||||
// 所以请确保 markdown 文件的 permalink 都以 `{{ path }}` + `linkPrefix` 开头
|
||||
linkPrefix: '/demo',
|
||||
// 文档标题,它将用于在页面的面包屑导航中显示
|
||||
title: 'Demo',
|
||||
// 手动配置侧边栏结构
|
||||
sidebar: ['', 'foo', 'bar'],
|
||||
// 根据文件结构自动生成侧边栏
|
||||
// sidebar: 'auto',
|
||||
})
|
||||
|
||||
/**
|
||||
* 导出所有的 collections
|
||||
* ({{ prefix }}Blog 为博客示例,如果不需要博客功能,请删除)
|
||||
* ({{ prefix }}DemoDoc 为参考示例,如果不需要它,请删除)
|
||||
*/
|
||||
export const {{ prefix }}Collections = defineCollections([
|
||||
{{ prefix }}Blog,
|
||||
{{ prefix }}DemoDoc,
|
||||
])
|
||||
|
||||
{{/each}}
|
||||
{{else}}
|
||||
const blog = defineCollection({
|
||||
// post 类型,这里用于实现 博客功能
|
||||
type: 'post',
|
||||
// 文档集合所在目录,相对于 `docs{{ path }}`
|
||||
dir: 'blog',
|
||||
// 文档标题,它将用于在页面的面包屑导航中显示
|
||||
title: 'Blog',
|
||||
// 文章列表页的链接,如果 `linkPrefix` 未定义,它也将作为 相关的文章的 permalink 的前缀
|
||||
link: '/blog/',
|
||||
// linkPrefix: '/article/', // 相关文章的链接前缀
|
||||
// postList: true, // 是否启用文章列表页
|
||||
// tags: true, // 是否启用标签页
|
||||
// archives: true, // 是否启用归档页
|
||||
// categories: true, // 是否启用分类页
|
||||
// postCover: 'right', // 文章封面位置
|
||||
// pagination: 15, // 每页显示文章数量
|
||||
})
|
||||
|
||||
const demoDoc = defineCollection({
|
||||
// doc 类型,该类型带有侧边栏
|
||||
type: 'doc',
|
||||
// 文档集合所在目录,相对于 `docs{{ path }}`
|
||||
dir: 'demo',
|
||||
// `dir` 所指向的目录中的所有 markdown 文件,其 permalink 需要以 `linkPrefix` 配置作为前缀
|
||||
// 如果 前缀不一致,则无法生成侧边栏。
|
||||
// 所以请确保 markdown 文件的 permalink 都以 `linkPrefix` 开头
|
||||
linkPrefix: '/demo',
|
||||
// 文档标题,它将用于在页面的面包屑导航中显示
|
||||
title: 'Demo',
|
||||
// 手动配置侧边栏结构
|
||||
sidebar: ['', 'foo', 'bar'],
|
||||
// 根据文件结构自动生成侧边栏
|
||||
// sidebar: 'auto',
|
||||
})
|
||||
|
||||
/**
|
||||
* 导出所有的 collections
|
||||
* (blog 为博客示例,如果不需要博客功能,请删除)
|
||||
* (demoDoc 为参考示例,如果不需要它,请删除)
|
||||
*/
|
||||
export default defineCollections([
|
||||
blog,
|
||||
demoDoc,
|
||||
])
|
||||
{{/if}}
|
||||
@ -54,32 +54,15 @@ export default defineUserConfig({
|
||||
// contributors: true,
|
||||
// changelog: false,
|
||||
|
||||
/**
|
||||
* 博客
|
||||
* @see https://theme-plume.vuejs.press/config/basic/#blog
|
||||
*/
|
||||
// blog: false, // 禁用博客
|
||||
// blog: {
|
||||
// postList: true, // 是否启用文章列表页
|
||||
// tags: true, // 是否启用标签页
|
||||
// archives: true, // 是否启用归档页
|
||||
// categories: true, // 是否启用分类页
|
||||
// postCover: 'right', // 文章封面位置
|
||||
// pagination: 15, // 每页显示文章数量
|
||||
// },
|
||||
|
||||
/* 博客文章页面链接前缀 */
|
||||
article: '/article/',
|
||||
|
||||
/**
|
||||
* 编译缓存,加快编译速度
|
||||
* @see https://theme-plume.vuejs.press/config/basic/#cache
|
||||
* @see https://theme-plume.vuejs.press/config/theme/#cache
|
||||
*/
|
||||
cache: 'filesystem',
|
||||
|
||||
/**
|
||||
* 为 markdown 文件自动添加 frontmatter 配置
|
||||
* @see https://theme-plume.vuejs.press/config/basic/#autofrontmatter
|
||||
* @see https://theme-plume.vuejs.press/config/theme/#autofrontmatter
|
||||
*/
|
||||
// autoFrontmatter: {
|
||||
// permalink: true, // 是否生成永久链接
|
||||
@ -99,7 +82,7 @@ export default defineUserConfig({
|
||||
// provider: 'algolia',
|
||||
// appId: '',
|
||||
// apiKey: '',
|
||||
// indexName: '',
|
||||
// indices: [''],
|
||||
// },
|
||||
|
||||
/**
|
||||
@ -116,9 +99,9 @@ export default defineUserConfig({
|
||||
// readingTime: true,
|
||||
|
||||
/**
|
||||
* markdown
|
||||
* @see https://theme-plume.vuejs.press/config/markdown/
|
||||
*/
|
||||
* markdown
|
||||
* @see https://theme-plume.vuejs.press/config/markdown/
|
||||
*/
|
||||
// markdown: {
|
||||
// abbr: true, // 启用 abbr 语法 *[label]: content
|
||||
// annotation: true, // 启用 annotation 语法 [+label]: content
|
||||
@ -137,6 +120,7 @@ export default defineUserConfig({
|
||||
// jsfiddle: true, // 启用嵌入 jsfiddle 语法 @[jsfiddle](user/id)
|
||||
// npmTo: true, // 启用 npm-to 容器 ::: npm-to
|
||||
// demo: true, // 启用 demo 容器 ::: demo
|
||||
// collapse: true, // 启用折叠容器 ::: collapse
|
||||
// repl: { // 启用 代码演示容器
|
||||
// go: true, // ::: go-repl
|
||||
// rust: true, // ::: rust-repl
|
||||
@ -193,5 +177,18 @@ export default defineUserConfig({
|
||||
* @see https://theme-plume.vuejs.press/guide/features/encryption/
|
||||
*/
|
||||
// encrypt: {},
|
||||
|
||||
/**
|
||||
* 启用 llmstxt 插件,用于为大语言模型提供更友好的内容
|
||||
* @see https://theme-plume.vuejs.press/guide/features/llmstxt/
|
||||
*/
|
||||
// llmstxt: {
|
||||
{{#if multiLanguage}}
|
||||
// locale: '/', // 默认仅为主语言生成 llms 友好内容
|
||||
// locale: 'all', // 为所有语言生成 llms 友好内容
|
||||
{{else}}
|
||||
// locale: '/', // 默认仅为主语言生成 llms 友好内容
|
||||
{{/if}}
|
||||
// }
|
||||
}),
|
||||
})
|
||||
|
||||
@ -15,7 +15,7 @@ export const {{prefix}}Navbar = defineNavbarConfig([
|
||||
{ text: '{{#if isEn}}Archives{{else}}归档{{/if}}', link: '{{ path }}blog/archives/' },
|
||||
{
|
||||
text: '{{#if isEn}}Notes{{else}}笔记{{/if}}',
|
||||
items: [{ text: '{{#if isEn}}Demo{{else}}示例{{/if}}', link: '{{ path }}notes/demo/README.md' }]
|
||||
items: [{ text: '{{#if isEn}}Demo{{else}}示例{{/if}}', link: '{{ path }}demo/README.md' }]
|
||||
},
|
||||
])
|
||||
|
||||
@ -28,7 +28,7 @@ export default defineNavbarConfig([
|
||||
{ text: '{{#if isEn}}Archives{{else}}归档{{/if}}', link: '/blog/archives/' },
|
||||
{
|
||||
text: '{{#if isEn}}Notes{{else}}笔记{{/if}}',
|
||||
items: [{ text: '{{#if isEn}}Demo{{else}}示例{{/if}}', link: '/notes/demo/README.md' }]
|
||||
items: [{ text: '{{#if isEn}}Demo{{else}}示例{{/if}}', link: '/demo/README.md' }]
|
||||
},
|
||||
])
|
||||
{{/if}}
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
/**
|
||||
* @see https://theme-plume.vuejs.press/guide/document/ 查看文档了解配置详情。
|
||||
*
|
||||
* Notes 配置文件,它在 `.vuepress/plume.config.{{#if useTs}}ts{{else}}js{{/if}}` 中被导入。
|
||||
*
|
||||
* 请注意,你应该先在这里配置好 Notes,然后再启动 vuepress,主题会在启动 vuepress 时,
|
||||
* 读取这里配置的 Notes,然后在与 Note 相关的 Markdown 文件中,自动生成 permalink。
|
||||
*
|
||||
* 如果你发现 侧边栏没有显示,那么请检查你的配置是否正确,以及 Markdown 文件中的 permalink
|
||||
* 是否是以对应的 note 配置的 link 的前缀开头。 是否展示侧边栏是根据 页面链接 的前缀 与 `note.link`
|
||||
* 的前缀是否匹配来决定。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 在受支持的 IDE 中会智能提示配置项。
|
||||
*
|
||||
* - `defineNoteConfig` 是用于定义单个 note 配置的帮助函数
|
||||
* - `defineNotesConfig` 是用于定义 notes 集合的帮助函数
|
||||
*
|
||||
* 通过 `defineNoteConfig` 定义的 note 配置,应该填入 `defineNotesConfig` 的 notes 数组中
|
||||
*/
|
||||
import { defineNoteConfig, defineNotesConfig } from 'vuepress-theme-plume'
|
||||
|
||||
{{#if multiLanguage}}
|
||||
{{#each locales}}
|
||||
/* =================== locale: {{ lang }} ======================= */
|
||||
|
||||
const {{ prefix }}DemoNote = defineNoteConfig({
|
||||
dir: 'demo',
|
||||
// `dir` 所指向的目录中的所有 markdown 文件,其 permalink 需要以 `link` 配置作为前缀
|
||||
// 如果 前缀不一致,则无法生成侧边栏。
|
||||
// 所以请确保 markdown 文件的 permalink 都以 `link` 开头
|
||||
link: '/demo',
|
||||
// 手动配置侧边栏结构
|
||||
sidebar: ['', 'foo', 'bar'],
|
||||
// 根据文件结构自动生成侧边栏
|
||||
// sidebar: 'auto',
|
||||
})
|
||||
|
||||
/**
|
||||
* 导出所有的 note
|
||||
* 每一个 note 都应该填入到 `notes.notes` 数组中
|
||||
* ({{ prefix }}DemoNote 为参考示例,如果不需要它,请删除)
|
||||
*/
|
||||
export const {{ prefix }}Notes = defineNotesConfig({
|
||||
dir: '{{ removeLeadingSlash path }}notes',
|
||||
link: '{{ path }}',
|
||||
notes: [{{ prefix }}DemoNote],
|
||||
})
|
||||
|
||||
{{/each}}
|
||||
{{else}}
|
||||
const demoNote = defineNoteConfig({
|
||||
dir: 'demo',
|
||||
// `dir` 所指向的目录中的所有 markdown 文件,其 permalink 需要以 `link` 配置作为前缀
|
||||
// 如果 前缀不一致,则无法生成侧边栏。
|
||||
// 所以请确保 markdown 文件的 permalink 都以 `link` 开头
|
||||
link: '/demo',
|
||||
// 手动配置侧边栏结构
|
||||
sidebar: ['', 'foo', 'bar'],
|
||||
// 根据文件结构自动生成侧边栏
|
||||
// sidebar: 'auto',
|
||||
})
|
||||
|
||||
/**
|
||||
* 导出所有的 note
|
||||
* 每一个 note 都应该填入到 `notes.notes` 数组中
|
||||
* (DemoNote 为参考示例,如果不需要它,请删除)
|
||||
*/
|
||||
export default defineNotesConfig({
|
||||
dir: 'notes',
|
||||
link: '/',
|
||||
notes: [demoNote],
|
||||
})
|
||||
{{/if}}
|
||||
@ -12,15 +12,15 @@
|
||||
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
{{#if multiLanguage}}
|
||||
import { enCollections, zhCollections } from './collections'
|
||||
import { enNavbar, zhNavbar } from './navbar'
|
||||
import { enNotes, zhNotes } from './notes'
|
||||
{{else}}
|
||||
import navbar from './navbar'
|
||||
import notes from './notes'
|
||||
import collections from './collections'
|
||||
{{/if}}
|
||||
|
||||
/**
|
||||
* @see https://theme-plume.vuejs.press/config/basic/
|
||||
* @see https://theme-plume.vuejs.press/config/theme/
|
||||
*/
|
||||
export default defineThemeConfig({
|
||||
logo: 'https://theme-plume.vuejs.press/plume.png',
|
||||
@ -52,7 +52,7 @@ export default defineThemeConfig({
|
||||
|
||||
{{#unless multiLanguage}}
|
||||
/**
|
||||
* @see https://theme-plume.vuejs.press/config/basic/#profile
|
||||
* @see https://theme-plume.vuejs.press/config/theme/#profile
|
||||
*/
|
||||
profile: {
|
||||
avatar: 'https://theme-plume.vuejs.press/plume.png',
|
||||
@ -64,7 +64,7 @@ export default defineThemeConfig({
|
||||
},
|
||||
|
||||
navbar,
|
||||
notes,
|
||||
collections,
|
||||
|
||||
/**
|
||||
* 公告板
|
||||
@ -78,7 +78,7 @@ export default defineThemeConfig({
|
||||
// },
|
||||
|
||||
{{/unless}}
|
||||
/* 过渡动画 @see https://theme-plume.vuejs.press/config/basic/#transition */
|
||||
/* 过渡动画 @see https://theme-plume.vuejs.press/config/theme/#transition */
|
||||
// transition: {
|
||||
// page: true, // 启用 页面间跳转过渡动画
|
||||
// postList: true, // 启用 博客文章列表过渡动画
|
||||
@ -90,7 +90,7 @@ export default defineThemeConfig({
|
||||
{{#each locales}}
|
||||
'{{ path }}': {
|
||||
/**
|
||||
* @see https://theme-plume.vuejs.press/config/basic/#profile
|
||||
* @see https://theme-plume.vuejs.press/config/theme/#profile
|
||||
*/
|
||||
profile: {
|
||||
avatar: 'https://theme-plume.vuejs.press/plume.png',
|
||||
@ -102,7 +102,7 @@ export default defineThemeConfig({
|
||||
},
|
||||
|
||||
navbar: {{ prefix }}Navbar,
|
||||
notes: {{ prefix }}Notes,
|
||||
collections: {{ prefix }}Collections,
|
||||
|
||||
/**
|
||||
* 公告板
|
||||
|
||||
@ -56,11 +56,15 @@ jobs:
|
||||
# Run the build script
|
||||
{{#unless (equal packageManager "yarn")}}
|
||||
- name: Build VuePress site
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
run: {{packageManager}} run docs:build
|
||||
{{/unless}}
|
||||
{{#if (equal packageManager "yarn")}}
|
||||
- name: Build VuePress site
|
||||
uses: borales/actions-yarn@v4
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=8192
|
||||
with:
|
||||
cmd: docs:build
|
||||
{{/if}}
|
||||
|
||||
@ -5,7 +5,8 @@ config:
|
||||
-
|
||||
type: hero
|
||||
full: true
|
||||
background: tint-plate
|
||||
forceDark: true
|
||||
effect: lightning
|
||||
hero:
|
||||
name: Theme Plume
|
||||
tagline: VuePress Next Theme
|
||||
|
||||
@ -81,7 +81,7 @@ content right
|
||||
|
||||
**demo wrapper:**
|
||||
|
||||
::: demo-wrapper title="Demo" no-padding height="200px"
|
||||
::: window title="Demo" height="200px"
|
||||
<style scoped>
|
||||
.open-door {
|
||||
display: flex;
|
||||
@ -5,7 +5,8 @@ config:
|
||||
-
|
||||
type: hero
|
||||
full: true
|
||||
background: tint-plate
|
||||
forceDark: true
|
||||
effect: lightning
|
||||
hero:
|
||||
name: Theme Plume
|
||||
tagline: VuePress Next Theme
|
||||
|
||||
@ -95,9 +95,9 @@ H~2~O
|
||||
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
|
||||
- twitter - <Icon name="skill-icons:twitter" size="2em" />
|
||||
|
||||
**demo wrapper:**
|
||||
**示例容器:**
|
||||
|
||||
::: demo-wrapper title="示例" no-padding height="200px"
|
||||
::: window title="示例" height="200px"
|
||||
<style scoped>
|
||||
.open-door {
|
||||
display: flex;
|
||||
@ -137,68 +137,6 @@ const obj = {
|
||||
}
|
||||
```
|
||||
|
||||
**Code Blocks TwoSlash:**
|
||||
|
||||
```ts twoslash
|
||||
// @errors: 2339
|
||||
const welcome = 'Tudo bem gente?'
|
||||
const words = welcome.contains(' ')
|
||||
```
|
||||
|
||||
```ts twoslash
|
||||
import express from 'express'
|
||||
const app = express()
|
||||
app.get('/', (req, res) => {
|
||||
res.send
|
||||
})
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
```ts twoslash
|
||||
import { createHighlighter } from 'shiki'
|
||||
|
||||
const highlighter = await createHighlighter({ themes: ['nord'], langs: ['javascript'] })
|
||||
// @log: Custom log message
|
||||
const a = 1
|
||||
// @error: Custom error message
|
||||
const b = 1
|
||||
// @warn: Custom warning message
|
||||
const c = 1
|
||||
// @annotate: Custom annotation message
|
||||
```
|
||||
|
||||
```ts twoslash
|
||||
// @errors: 2540
|
||||
interface Todo {
|
||||
title: string
|
||||
}
|
||||
|
||||
const todo: Readonly<Todo> = {
|
||||
title: 'Delete inactive users'.toUpperCase(),
|
||||
// ^?
|
||||
}
|
||||
|
||||
todo.title = 'Hello'
|
||||
|
||||
Number.parseInt('123', 10)
|
||||
// ^|
|
||||
|
||||
//
|
||||
//
|
||||
```
|
||||
|
||||
```vue twoslash
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p>{{ count }}</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
**代码分组:**
|
||||
|
||||
::: code-tabs
|
||||
@ -6,6 +6,8 @@
|
||||
*.jpeg binary
|
||||
*.ico binary
|
||||
*.gif binary
|
||||
*.webp binary
|
||||
*.tff binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.pdf binary
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { Options } from 'tsdown'
|
||||
import { defineConfig } from 'tsdown'
|
||||
|
||||
export default defineConfig({
|
||||
@ -7,4 +6,5 @@ export default defineConfig({
|
||||
dts: true,
|
||||
format: 'esm',
|
||||
sourcemap: false,
|
||||
}) as Options
|
||||
fixedExtension: false,
|
||||
})
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type { ClientConfig } from 'vuepress/client'
|
||||
import { defineMermaidConfig } from '@vuepress/plugin-markdown-chart/client'
|
||||
import { h } from 'vue'
|
||||
import { defineAsyncComponent, h } from 'vue'
|
||||
import { Layout } from 'vuepress-theme-plume/client'
|
||||
import VPPostItem from 'vuepress-theme-plume/components/Posts/VPPostItem.vue'
|
||||
import PageContextMenu from 'vuepress-theme-plume/features/PageContextMenu.vue'
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import AsideNav from '~/components/AsideNav.vue'
|
||||
import { setupThemeColors } from '~/composables/theme-colors.js'
|
||||
@ -14,12 +16,17 @@ defineMermaidConfig({
|
||||
})
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app }) {
|
||||
app.component('VPPostItem', VPPostItem)
|
||||
app.component('TintPlate', defineAsyncComponent(() => import('vuepress-theme-plume/components/background/TintPlate.vue')))
|
||||
},
|
||||
setup() {
|
||||
setupThemeColors()
|
||||
},
|
||||
layouts: {
|
||||
Layout: h(Layout, null, {
|
||||
'aside-outline-after': () => h(AsideNav),
|
||||
'doc-title-after': () => h(PageContextMenu),
|
||||
}),
|
||||
},
|
||||
}) as ClientConfig
|
||||
|
||||
13
docs/.vuepress/collections/en/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { defineCollections, type ThemeCollections } from 'vuepress-theme-plume'
|
||||
import { themeConfig } from './theme-config.js'
|
||||
import { themeGuide } from './theme-guide.js'
|
||||
import { tools } from './tools.js'
|
||||
|
||||
export const enCollections: ThemeCollections = defineCollections([
|
||||
// 博客
|
||||
{ type: 'post', dir: '/blog/', link: '/blog/', title: 'Blog' },
|
||||
// 文档
|
||||
themeGuide,
|
||||
themeConfig,
|
||||
tools,
|
||||
])
|
||||
53
docs/.vuepress/collections/en/theme-config.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import type { ThemeCollectionItem } from 'vuepress-theme-plume'
|
||||
import { defineCollection } from 'vuepress-theme-plume'
|
||||
|
||||
export const themeConfig: ThemeCollectionItem = defineCollection({
|
||||
type: 'doc',
|
||||
title: 'Config',
|
||||
dir: 'config',
|
||||
linkPrefix: '/config/',
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Configuration',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'intro',
|
||||
'theme',
|
||||
'locales',
|
||||
'navbar',
|
||||
'sidebar',
|
||||
'collections',
|
||||
'markdown',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Page Configuration',
|
||||
prefix: 'frontmatter',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'basic',
|
||||
'home',
|
||||
'post',
|
||||
'friend',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Built-in Plugins',
|
||||
prefix: 'plugins',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'',
|
||||
'shiki',
|
||||
'search',
|
||||
'reading-time',
|
||||
'llms',
|
||||
'markdown-enhance',
|
||||
'markdown-power',
|
||||
'markdown-image',
|
||||
'markdown-math',
|
||||
'markdown-include',
|
||||
'watermark',
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
202
docs/.vuepress/collections/en/theme-guide.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import type { ThemeCollectionItem } from 'vuepress-theme-plume'
|
||||
import { defineCollection } from 'vuepress-theme-plume'
|
||||
|
||||
export const themeGuide: ThemeCollectionItem = defineCollection({
|
||||
type: 'doc',
|
||||
dir: 'guide',
|
||||
title: 'Guide',
|
||||
linkPrefix: '/guide/',
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Quick Start',
|
||||
collapsed: false,
|
||||
icon: 'carbon:idea',
|
||||
prefix: 'quick-start',
|
||||
items: [
|
||||
'intro',
|
||||
'usage',
|
||||
'project-structure',
|
||||
{
|
||||
text: 'Collection',
|
||||
link: 'collection',
|
||||
items: ['collection-post', 'collection-doc'],
|
||||
},
|
||||
'sidebar',
|
||||
'write',
|
||||
'auto-frontmatter',
|
||||
'locales',
|
||||
'deployment',
|
||||
'optimize-build',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Write',
|
||||
icon: 'fluent-mdl2:edit-create',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: 'markdown',
|
||||
icon: 'material-symbols:markdown-outline',
|
||||
prefix: 'markdown',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'basic',
|
||||
'extensions',
|
||||
'attrs',
|
||||
'emoji',
|
||||
'math',
|
||||
'table',
|
||||
'icons',
|
||||
'mark',
|
||||
'plot',
|
||||
'abbr',
|
||||
'annotation',
|
||||
'container',
|
||||
'github-alerts',
|
||||
'card',
|
||||
'steps',
|
||||
'file-tree',
|
||||
'code-tree',
|
||||
'field',
|
||||
'tabs',
|
||||
'qrcode',
|
||||
'timeline',
|
||||
'demo-wrapper',
|
||||
'flex',
|
||||
'collapse',
|
||||
'npm-to',
|
||||
'caniuse',
|
||||
'chat',
|
||||
'include',
|
||||
'env',
|
||||
'obsidian',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'code block',
|
||||
prefix: 'code',
|
||||
icon: 'ph:code-bold',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'intro',
|
||||
'features',
|
||||
'copy-code',
|
||||
'code-tabs',
|
||||
'import',
|
||||
'twoslash',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'code repl',
|
||||
prefix: 'repl',
|
||||
icon: 'carbon:demo',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'frontend',
|
||||
'rust',
|
||||
'golang',
|
||||
'kotlin',
|
||||
'python',
|
||||
'codepen',
|
||||
'jsFiddle',
|
||||
'codeSandbox',
|
||||
'replit',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'charts',
|
||||
icon: 'mdi:chart-line',
|
||||
prefix: 'chart',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'chart',
|
||||
'echarts',
|
||||
'mermaid',
|
||||
'flowchart',
|
||||
'markmap',
|
||||
'plantuml',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'resource embedded',
|
||||
icon: 'dashicons:embed-video',
|
||||
prefix: 'embed',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'pdf',
|
||||
'bilibili',
|
||||
'acfun',
|
||||
'youtube',
|
||||
'artplayer',
|
||||
'audioReader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Features',
|
||||
icon: 'lucide:box',
|
||||
collapsed: false,
|
||||
prefix: 'features',
|
||||
items: [
|
||||
'icon',
|
||||
'search',
|
||||
'image-preview',
|
||||
'comments',
|
||||
'bulletin',
|
||||
'encryption',
|
||||
'contributors',
|
||||
'changelog',
|
||||
'copyright',
|
||||
'watermark',
|
||||
'friend-links',
|
||||
'replace-assets',
|
||||
'seo',
|
||||
'sitemap',
|
||||
'llmstxt',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Component',
|
||||
prefix: 'components',
|
||||
icon: 'uiw:component',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'badge',
|
||||
'icon',
|
||||
'plot',
|
||||
'card',
|
||||
'link-card',
|
||||
'image-card',
|
||||
'card-grid',
|
||||
'card-masonry',
|
||||
'home-box',
|
||||
'repo-card',
|
||||
'npm-badge',
|
||||
'swiper',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Customization',
|
||||
icon: 'material-symbols:dashboard-customize-outline-rounded',
|
||||
collapsed: false,
|
||||
prefix: 'custom',
|
||||
items: [
|
||||
{ text: 'Custom Homepage', link: 'home', items: ['home-hero-effect'] },
|
||||
'style',
|
||||
'slots',
|
||||
'component-overrides',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'API',
|
||||
icon: 'mdi:api',
|
||||
prefix: 'api',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'client',
|
||||
'node',
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
20
docs/.vuepress/collections/en/tools.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { ThemeCollectionItem } from 'vuepress-theme-plume'
|
||||
import { defineCollection } from 'vuepress-theme-plume'
|
||||
|
||||
export const tools: ThemeCollectionItem = defineCollection({
|
||||
type: 'doc',
|
||||
dir: 'tools',
|
||||
title: 'Theme Tools',
|
||||
linkPrefix: '/tools/',
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Tools',
|
||||
icon: 'tabler:tools',
|
||||
items: [
|
||||
'custom-theme',
|
||||
'home-hero-tint-plate',
|
||||
'caniuse',
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
13
docs/.vuepress/collections/zh/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { defineCollections, type ThemeCollections } from 'vuepress-theme-plume'
|
||||
import { themeConfig } from './theme-config.js'
|
||||
import { themeGuide } from './theme-guide.js'
|
||||
import { tools } from './tools.js'
|
||||
|
||||
export const zhCollections: ThemeCollections = defineCollections([
|
||||
// 博客
|
||||
{ type: 'post', dir: '/blog/', link: '/blog/', title: '博客' },
|
||||
// 文档
|
||||
themeGuide,
|
||||
themeConfig,
|
||||
tools,
|
||||
])
|
||||
@ -1,9 +1,11 @@
|
||||
import type { ThemeNote } from 'vuepress-theme-plume'
|
||||
import { defineNoteConfig } from 'vuepress-theme-plume'
|
||||
import type { ThemeCollectionItem } from 'vuepress-theme-plume'
|
||||
import { defineCollection } from 'vuepress-theme-plume'
|
||||
|
||||
export const themeConfig: ThemeNote = defineNoteConfig({
|
||||
dir: 'theme/config',
|
||||
link: '/config/',
|
||||
export const themeConfig: ThemeCollectionItem = defineCollection({
|
||||
type: 'doc',
|
||||
title: '配置',
|
||||
dir: 'config',
|
||||
linkPrefix: '/config/',
|
||||
sidebar: [
|
||||
{
|
||||
text: '配置',
|
||||
@ -13,8 +15,8 @@ export const themeConfig: ThemeNote = defineNoteConfig({
|
||||
'theme',
|
||||
'locales',
|
||||
'navbar',
|
||||
'notes',
|
||||
'sidebar',
|
||||
'collections',
|
||||
'markdown',
|
||||
],
|
||||
},
|
||||
@ -38,6 +40,7 @@ export const themeConfig: ThemeNote = defineNoteConfig({
|
||||
'shiki',
|
||||
'search',
|
||||
'reading-time',
|
||||
'llms',
|
||||
'markdown-enhance',
|
||||
'markdown-power',
|
||||
'markdown-image',
|
||||
@ -1,9 +1,11 @@
|
||||
import type { ThemeNote } from 'vuepress-theme-plume'
|
||||
import { defineNoteConfig } from 'vuepress-theme-plume'
|
||||
import type { ThemeCollectionItem } from 'vuepress-theme-plume'
|
||||
import { defineCollection } from 'vuepress-theme-plume'
|
||||
|
||||
export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
dir: 'theme/guide',
|
||||
link: '/guide/',
|
||||
export const themeGuide: ThemeCollectionItem = defineCollection({
|
||||
type: 'doc',
|
||||
dir: 'guide',
|
||||
title: '指南',
|
||||
linkPrefix: '/guide/',
|
||||
sidebar: [
|
||||
{
|
||||
text: '从这里开始',
|
||||
@ -14,9 +16,14 @@ export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
'intro',
|
||||
'usage',
|
||||
'project-structure',
|
||||
{
|
||||
text: '集合',
|
||||
link: 'collection',
|
||||
items: ['collection-post', 'collection-doc'],
|
||||
},
|
||||
'sidebar',
|
||||
'write',
|
||||
'blog',
|
||||
'document',
|
||||
'auto-frontmatter',
|
||||
'locales',
|
||||
'deployment',
|
||||
'optimize-build',
|
||||
@ -35,18 +42,24 @@ export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
items: [
|
||||
'basic',
|
||||
'extensions',
|
||||
'attrs',
|
||||
'emoji',
|
||||
'math',
|
||||
'table',
|
||||
'icons',
|
||||
'mark',
|
||||
'plot',
|
||||
'abbr',
|
||||
'annotation',
|
||||
'container',
|
||||
'github-alerts',
|
||||
'card',
|
||||
'steps',
|
||||
'file-tree',
|
||||
'code-tree',
|
||||
'field',
|
||||
'tabs',
|
||||
'qrcode',
|
||||
'timeline',
|
||||
'demo-wrapper',
|
||||
'flex',
|
||||
@ -55,6 +68,8 @@ export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
'caniuse',
|
||||
'chat',
|
||||
'include',
|
||||
'env',
|
||||
'obsidian',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -138,6 +153,7 @@ export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
'replace-assets',
|
||||
'seo',
|
||||
'sitemap',
|
||||
'llmstxt',
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -166,7 +182,7 @@ export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
collapsed: false,
|
||||
prefix: 'custom',
|
||||
items: [
|
||||
'home',
|
||||
{ text: '自定义首页', link: 'home', items: ['home-hero-effect'] },
|
||||
'style',
|
||||
'slots',
|
||||
'component-overrides',
|
||||
20
docs/.vuepress/collections/zh/tools.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { ThemeCollectionItem } from 'vuepress-theme-plume'
|
||||
import { defineCollection } from 'vuepress-theme-plume'
|
||||
|
||||
export const tools: ThemeCollectionItem = defineCollection({
|
||||
type: 'doc',
|
||||
dir: 'tools',
|
||||
title: '工具',
|
||||
linkPrefix: '/tools/',
|
||||
sidebar: [
|
||||
{
|
||||
text: '工具',
|
||||
icon: 'tabler:tools',
|
||||
items: [
|
||||
'custom-theme',
|
||||
'home-hero-tint-plate',
|
||||
'caniuse',
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -3,9 +3,7 @@ import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { viteBundler } from '@vuepress/bundler-vite'
|
||||
import { addViteOptimizeDepsInclude, addViteSsrExternal } from '@vuepress/helper'
|
||||
import { llmsPlugin } from '@vuepress/plugin-llms'
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { tocGetter } from './llmstxtTOC.js'
|
||||
import { theme } from './theme.js'
|
||||
|
||||
const pnpmWorkspace = fs.readFileSync(path.resolve(__dirname, '../../pnpm-workspace.yaml'), 'utf-8')
|
||||
@ -28,7 +26,7 @@ export default defineUserConfig({
|
||||
['meta', { name: 'google-site-verification', content: 'AaTP7bapCAcoO9ZGE67ilpy99GL6tYqtD30tRHjO9Ps' }],
|
||||
],
|
||||
|
||||
pagePatterns: ['**/*.md', '!**/*.snippet.md', '!.vuepress', '!node_modules', '!docs/notes/theme/guide/代码演示/demo/*'],
|
||||
pagePatterns: ['**/*.md', '!**/*.snippet.md', '!.vuepress', '!node_modules', '!docs/guide/repl/demo/*'],
|
||||
|
||||
extendsBundlerOptions(bundlerOptions, app) {
|
||||
addViteOptimizeDepsInclude(bundlerOptions, app, '@simonwep/pickr')
|
||||
@ -47,16 +45,6 @@ export default defineUserConfig({
|
||||
'~/composables': path.resolve(__dirname, './themes/composables'),
|
||||
},
|
||||
|
||||
plugins: [
|
||||
llmsPlugin({
|
||||
llmsTxtTemplateGetter: {
|
||||
description: '一个简约易用的,功能丰富的 vuepress 文档&博客 主题',
|
||||
details: '',
|
||||
toc: tocGetter,
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
bundler: viteBundler(),
|
||||
shouldPrefetch: false,
|
||||
|
||||
|
||||
@ -1,108 +0,0 @@
|
||||
import type { LLMPage, LLMState } from '@vuepress/plugin-llms'
|
||||
import type { ThemeSidebarItem } from 'vuepress-theme-plume'
|
||||
import { generateTOCLink as rawGenerateTOCLink } from '@vuepress/plugin-llms'
|
||||
import { ensureEndingSlash, ensureLeadingSlash } from 'vuepress/shared'
|
||||
import { zhNotes } from './notes/zh/index.js'
|
||||
|
||||
const noteNames = {
|
||||
'/guide/': '指南',
|
||||
'/config/': '配置',
|
||||
'/tools/': '工具',
|
||||
}
|
||||
|
||||
function normalizePath(prefix: string, path = ''): string {
|
||||
if (path.startsWith('/'))
|
||||
return path
|
||||
|
||||
return `${ensureEndingSlash(prefix)}${path}`
|
||||
}
|
||||
|
||||
export function tocGetter(llmPages: LLMPage[], llmState: LLMState): string {
|
||||
let tableOfContent = ''
|
||||
const usagePages: LLMPage[] = []
|
||||
|
||||
// Blog
|
||||
tableOfContent += `### 博客\n\n`
|
||||
const blogList: string[] = []
|
||||
llmPages.forEach((page) => {
|
||||
if (page.path.startsWith('/article/') || page.path.startsWith('/blog/')) {
|
||||
usagePages.push(page)
|
||||
blogList.push(rawGenerateTOCLink(page, llmState))
|
||||
}
|
||||
})
|
||||
tableOfContent += `${blogList.filter(Boolean).join('')}\n`
|
||||
|
||||
const generateTOCLink = (path: string): string => {
|
||||
const filepath = path.endsWith('/') ? `${path}README.md` : path.endsWith('.md') ? path : `${path || 'README'}.md`
|
||||
const link = path.endsWith('/') ? `${path}index.html` : `${path}.html`
|
||||
const page = llmPages.find((item) => {
|
||||
return ensureLeadingSlash(item.filePathRelative || '') === filepath || link === item.path
|
||||
})
|
||||
|
||||
if (page) {
|
||||
usagePages.push(page)
|
||||
return rawGenerateTOCLink(page, llmState)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const processAutoSidebar = (prefix: string): string[] => {
|
||||
const list: string[] = []
|
||||
llmPages.forEach((page) => {
|
||||
if (ensureLeadingSlash(page.filePathRelative || '').startsWith(prefix)) {
|
||||
usagePages.push(page)
|
||||
list.push(rawGenerateTOCLink(page, llmState))
|
||||
}
|
||||
})
|
||||
return list.filter(Boolean)
|
||||
}
|
||||
|
||||
const processSidebar = (items: (string | ThemeSidebarItem)[], prefix: string): string[] => {
|
||||
const result: string[] = []
|
||||
items.forEach((item) => {
|
||||
if (typeof item === 'string') {
|
||||
result.push(generateTOCLink(normalizePath(prefix, item)))
|
||||
}
|
||||
else {
|
||||
if (item.link) {
|
||||
result.push(generateTOCLink(normalizePath(prefix, item.link)))
|
||||
}
|
||||
if (item.items === 'auto') {
|
||||
result.push(...processAutoSidebar(normalizePath(prefix, item.prefix)))
|
||||
}
|
||||
else if (item.items?.length) {
|
||||
result.push(...processSidebar(item.items, normalizePath(prefix, item.prefix)))
|
||||
}
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// Notes
|
||||
zhNotes.notes.forEach(({ dir, link, sidebar = [] }) => {
|
||||
tableOfContent += `### ${noteNames[link]}\n\n`
|
||||
const prefix = normalizePath('/notes/', dir)
|
||||
if (sidebar === 'auto') {
|
||||
tableOfContent += `${processAutoSidebar(prefix).join('')}\n`
|
||||
}
|
||||
else if (sidebar.length) {
|
||||
const home = generateTOCLink(ensureEndingSlash(prefix))
|
||||
const list = processSidebar(sidebar, prefix)
|
||||
if (home && !list.includes(home)) {
|
||||
list.unshift(home)
|
||||
}
|
||||
tableOfContent += `${list.join('')}\n`
|
||||
}
|
||||
})
|
||||
|
||||
// Others
|
||||
const unUsagePages = llmPages.filter(page => !usagePages.includes(page))
|
||||
if (unUsagePages.length) {
|
||||
tableOfContent += '### Others\n\n'
|
||||
tableOfContent += unUsagePages
|
||||
.map(page => rawGenerateTOCLink(page, llmState))
|
||||
.join('')
|
||||
}
|
||||
|
||||
return tableOfContent
|
||||
}
|
||||
@ -6,13 +6,13 @@ export const zhNavbar: ThemeNavItem[] = defineNavbarConfig([
|
||||
{
|
||||
text: '指南',
|
||||
icon: 'icon-park-outline:guide-board',
|
||||
link: '/notes/theme/guide/quick-start/intro.md',
|
||||
link: '/guide/quick-start/intro.md',
|
||||
activeMatch: '^/guide/',
|
||||
},
|
||||
{
|
||||
text: '配置',
|
||||
icon: 'icon-park-outline:setting-two',
|
||||
link: '/notes/theme/config/intro.md',
|
||||
link: '/config/intro.md',
|
||||
activeMatch: '^/config/',
|
||||
},
|
||||
{
|
||||
@ -78,11 +78,15 @@ export const enNavbar: ThemeNavItem[] = defineNavbarConfig([
|
||||
text: 'More',
|
||||
icon: 'icon-park-outline:more-three',
|
||||
items: [
|
||||
{ text: 'FAQ', link: '/en/faq/', icon: 'wpf:faq' },
|
||||
{ text: 'Theme Tools', link: '/en/tools/', icon: 'jam:tools' },
|
||||
{ text: 'Friend Links', link: '/en/friends/', icon: 'carbon:friendship' },
|
||||
{
|
||||
text: 'Vuepress',
|
||||
icon: 'logos:vue',
|
||||
items: [
|
||||
{ text: 'Official Docs', link: 'https://v2.vuepress.vuejs.org' },
|
||||
{ text: 'Ecosystem', link: 'https://ecosystem.vuejs.press/' },
|
||||
{ text: 'Official Docs', link: 'https://v2.vuepress.vuejs.org', icon: 'logos:vue' },
|
||||
{ text: 'Ecosystem', link: 'https://ecosystem.vuejs.press/', icon: 'logos:vue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -90,9 +94,10 @@ export const enNavbar: ThemeNavItem[] = defineNavbarConfig([
|
||||
{
|
||||
text: `${version}`,
|
||||
icon: 'codicon:versions',
|
||||
badge: 'New',
|
||||
items: [
|
||||
{ text: 'Changelog', link: '/changelog/' },
|
||||
{ text: 'Contributing', link: '/contributing/' },
|
||||
{ text: 'Changelog', link: '/en/changelog/' },
|
||||
{ text: 'Contributing', link: '/en/contributing/' },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import type { ThemeNoteListOptions } from 'vuepress-theme-plume'
|
||||
import { defineNotesConfig } from 'vuepress-theme-plume'
|
||||
import { themeConfig } from './theme-config'
|
||||
import { themeGuide } from './theme-guide'
|
||||
|
||||
export const enNotes: ThemeNoteListOptions = defineNotesConfig({
|
||||
dir: 'en/notes',
|
||||
link: '/',
|
||||
notes: [
|
||||
themeGuide,
|
||||
themeConfig,
|
||||
],
|
||||
})
|
||||
@ -1,28 +0,0 @@
|
||||
import type { ThemeNote } from 'vuepress-theme-plume'
|
||||
import { defineNoteConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export const themeConfig: ThemeNote = defineNoteConfig({
|
||||
dir: 'theme/config',
|
||||
link: '/config/',
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Config',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'intro',
|
||||
'basic',
|
||||
'locales',
|
||||
'notes',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'frontmatter',
|
||||
prefix: 'frontmatter',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'basic',
|
||||
'article',
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -1,68 +0,0 @@
|
||||
import type { ThemeNote } from 'vuepress-theme-plume'
|
||||
import { defineNoteConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export const themeGuide: ThemeNote = defineNoteConfig({
|
||||
dir: 'theme/guide',
|
||||
link: '/guide/',
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Quick Start',
|
||||
collapsed: false,
|
||||
icon: 'carbon:idea',
|
||||
prefix: 'quick-start',
|
||||
items: [
|
||||
'intro',
|
||||
'usage',
|
||||
'project-structure',
|
||||
'write',
|
||||
'blog',
|
||||
'document',
|
||||
'international',
|
||||
'deployment',
|
||||
'optimize-build',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Write',
|
||||
icon: 'fluent-mdl2:edit-create',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
text: 'markdown',
|
||||
icon: 'material-symbols:markdown-outline',
|
||||
prefix: 'markdown',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'basic',
|
||||
'extensions',
|
||||
'icons',
|
||||
'mark',
|
||||
'plot',
|
||||
'abbr',
|
||||
'annotation',
|
||||
'card',
|
||||
'steps',
|
||||
'file-tree',
|
||||
'tabs',
|
||||
'timeline',
|
||||
'demo-wrapper',
|
||||
'collapse',
|
||||
'npm-to',
|
||||
'caniuse',
|
||||
'include',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Customization',
|
||||
icon: 'material-symbols:dashboard-customize-outline-rounded',
|
||||
collapsed: false,
|
||||
prefix: 'custom',
|
||||
items: [
|
||||
'home',
|
||||
'style',
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -1,17 +0,0 @@
|
||||
import type { ThemeNoteListOptions } from 'vuepress-theme-plume'
|
||||
import { defineNotesConfig } from 'vuepress-theme-plume'
|
||||
// import { plugins } from './plugins'
|
||||
import { themeConfig } from './theme-config'
|
||||
import { themeGuide } from './theme-guide'
|
||||
import { tools } from './tools'
|
||||
|
||||
export const zhNotes: ThemeNoteListOptions = defineNotesConfig({
|
||||
dir: 'notes',
|
||||
link: '/',
|
||||
notes: [
|
||||
themeGuide,
|
||||
themeConfig,
|
||||
// plugins,
|
||||
tools,
|
||||
],
|
||||
})
|
||||
@ -1,33 +0,0 @@
|
||||
import type { ThemeNote } from 'vuepress-theme-plume'
|
||||
import { defineNoteConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export const plugins: ThemeNote = defineNoteConfig({
|
||||
dir: 'plugins',
|
||||
link: '/plugins/',
|
||||
sidebar: [
|
||||
{
|
||||
text: '插件',
|
||||
link: '/plugins/',
|
||||
items: [
|
||||
// 'caniuse',
|
||||
// 'iconify',
|
||||
'shiki',
|
||||
'md-power',
|
||||
'content-updated',
|
||||
{
|
||||
text: 'plugin-netlify-functions',
|
||||
dir: 'netlify-functions',
|
||||
link: '/plugins/plugin-netlify-functions/',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'介绍',
|
||||
'使用',
|
||||
'功能',
|
||||
'api',
|
||||
'functions',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -1,18 +0,0 @@
|
||||
import type { ThemeNote } from 'vuepress-theme-plume'
|
||||
import { defineNoteConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export const tools: ThemeNote = defineNoteConfig({
|
||||
dir: 'tools',
|
||||
link: '/tools/',
|
||||
sidebar: [
|
||||
{
|
||||
text: '工具',
|
||||
icon: 'tabler:tools',
|
||||
items: [
|
||||
'custom-theme',
|
||||
'home-hero-tint-plate',
|
||||
'caniuse',
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
@ -1,8 +1,8 @@
|
||||
import type { ThemeConfig } from 'vuepress-theme-plume'
|
||||
import path from 'node:path'
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
import { enCollections, zhCollections } from './collections/index.js'
|
||||
import { enNavbar, zhNavbar } from './navbar.js'
|
||||
import { enNotes, zhNotes } from './notes/index.js'
|
||||
|
||||
export default defineThemeConfig({
|
||||
logo: '/plume.png',
|
||||
@ -15,6 +15,8 @@ export default defineThemeConfig({
|
||||
organization: 'pengzhanbo',
|
||||
},
|
||||
|
||||
transition: { appearance: 'circle-clip' },
|
||||
|
||||
social: [
|
||||
{ icon: 'github', link: 'https://github.com/pengzhanbo/vuepress-theme-plume' },
|
||||
{ icon: 'qq', link: 'https://qm.qq.com/q/FbPPoOIscE' },
|
||||
@ -27,12 +29,12 @@ export default defineThemeConfig({
|
||||
|
||||
locales: {
|
||||
'/': {
|
||||
notes: zhNotes,
|
||||
navbar: zhNavbar,
|
||||
collections: zhCollections,
|
||||
},
|
||||
'/en/': {
|
||||
notes: enNotes,
|
||||
navbar: enNavbar,
|
||||
collections: enCollections,
|
||||
},
|
||||
},
|
||||
|
||||
@ -41,7 +43,6 @@ export default defineThemeConfig({
|
||||
'/article/enx7c9s/': '123456',
|
||||
},
|
||||
},
|
||||
autoFrontmatter: { exclude: ['**/*.snippet.*'] },
|
||||
|
||||
bulletin: {
|
||||
layout: 'top-right',
|
||||
|
||||
4
docs/.vuepress/public/_redirects
Normal file
@ -0,0 +1,4 @@
|
||||
/llms.md /llms.txt 200!
|
||||
/llms-full.md /llms-full.txt 200!
|
||||
/en/llms.md /en/llms.txt 200!
|
||||
/en/llms-full.md /en/llms-full.txt 200!
|
||||
BIN
docs/.vuepress/public/files/sample-1.pdf
Normal file
|
Before Width: | Height: | Size: 255 KiB |
BIN
docs/.vuepress/public/images/demos/pengzhanbo.webp
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
docs/.vuepress/public/images/hero-effects/beams.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
docs/.vuepress/public/images/hero-effects/dark-veil.png
Normal file
|
After Width: | Height: | Size: 906 KiB |
BIN
docs/.vuepress/public/images/hero-effects/dot-grid.png
Normal file
|
After Width: | Height: | Size: 308 KiB |
BIN
docs/.vuepress/public/images/hero-effects/hyper-speed.png
Normal file
|
After Width: | Height: | Size: 689 KiB |
BIN
docs/.vuepress/public/images/hero-effects/iridescence.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/.vuepress/public/images/hero-effects/lightning.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
docs/.vuepress/public/images/hero-effects/liquid-ether.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/.vuepress/public/images/hero-effects/orb.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/.vuepress/public/images/hero-effects/pixel-blast.png
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
docs/.vuepress/public/images/hero-effects/prism.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
docs/.vuepress/public/images/hero-effects/tint-plate.png
Normal file
|
After Width: | Height: | Size: 770 KiB |
@ -18,6 +18,15 @@ export const theme: Theme = plumeTheme({
|
||||
},
|
||||
|
||||
markdown: {
|
||||
icon: {
|
||||
provider: 'iconify',
|
||||
preload: [
|
||||
// used within <AsideNae />
|
||||
'tabler:star',
|
||||
'octicon:issue-opened-16',
|
||||
'ep:milk-tea',
|
||||
],
|
||||
},
|
||||
chartjs: true,
|
||||
echarts: true,
|
||||
markmap: true,
|
||||
@ -34,7 +43,9 @@ export const theme: Theme = plumeTheme({
|
||||
codeTree: true,
|
||||
field: true,
|
||||
imageSize: 'all',
|
||||
mark: 'lazy',
|
||||
pdf: true,
|
||||
qrcode: true,
|
||||
caniuse: true,
|
||||
acfun: true,
|
||||
bilibili: true,
|
||||
@ -46,6 +57,8 @@ export const theme: Theme = plumeTheme({
|
||||
codeSandbox: true,
|
||||
jsfiddle: true,
|
||||
demo: true,
|
||||
encrypt: true,
|
||||
obsidian: true,
|
||||
npmTo: ['pnpm', 'yarn', 'npm'],
|
||||
repl: {
|
||||
go: true,
|
||||
@ -75,4 +88,16 @@ export const theme: Theme = plumeTheme({
|
||||
content: 'vuepress-theme-plume',
|
||||
},
|
||||
},
|
||||
|
||||
llmstxt: {
|
||||
locale: 'all',
|
||||
llmsTxtTemplateGetter: {
|
||||
description: (_, { currentLocale }) => {
|
||||
return currentLocale === '/'
|
||||
? '一个简约易用的,功能丰富的 vuepress 文档&博客 主题'
|
||||
: 'An easy-to-use and feature-rich vuepress documentation and blog theme'
|
||||
},
|
||||
details: '',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { VPLink } from 'vuepress-theme-plume/client'
|
||||
import { VPIcon, VPLink } from 'vuepress-theme-plume/client'
|
||||
import { useRouteLocale } from 'vuepress/client'
|
||||
|
||||
interface Locale {
|
||||
@ -21,17 +21,17 @@ const locale = computed(() => locales[lang.value])
|
||||
<template>
|
||||
<div class="aside-nav-wrapper">
|
||||
<VPLink class="link" no-icon href="https://github.com/pengzhanbo/vuepress-theme-plume">
|
||||
<span class="vpi-github-star" />
|
||||
<VPIcon name="tabler:star" />
|
||||
<span class="link-text">{{ locale.star }}</span>
|
||||
<span class="vpi-arrow-right" />
|
||||
</VPLink>
|
||||
<VPLink class="link" no-icon href="https://github.com/pengzhanbo/vuepress-theme-plume/issues/new/choose">
|
||||
<span class="vpi-github-issue" />
|
||||
<VPIcon name="octicon:issue-opened-16" />
|
||||
<span class="link-text">{{ locale.issue }}</span>
|
||||
<span class="vpi-arrow-right" />
|
||||
</VPLink>
|
||||
<VPLink class="link" href="/sponsor/">
|
||||
<span class="vpi-bubble-tea" />
|
||||
<VPIcon name="ep:milk-tea" />
|
||||
<span class="link-text">{{ locale.sponsor }}</span>
|
||||
<span class="vpi-arrow-right" />
|
||||
</VPLink>
|
||||
@ -65,15 +65,7 @@ const locale = computed(() => locales[lang.value])
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.vpi-github-star {
|
||||
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m12 1.5l3.1 6.3l6.9 1l-5 4.8l1.2 6.9l-6.2-3.2l-6.2 3.2L7 13.6L2 8.8l6.9-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.vpi-github-issue {
|
||||
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath fill='%23000' d='M8 9.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3'/%3E%3Cpath fill='%23000' d='M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0M1.5 8a6.5 6.5 0 1 0 13 0a6.5 6.5 0 0 0-13 0'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.vpi-bubble-tea {
|
||||
--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m17.95 9l-1.478 8.69c-.25 1.463-.374 2.195-.936 2.631c-1.2.931-6.039.88-7.172 0c-.562-.436-.687-1.168-.936-2.632L5.95 9M6 9l.514-1.286a5.908 5.908 0 0 1 10.972 0L18 9M5 9h14m-7 0l4-7m-5.99 12h.01m1 4h.01m1.99-2h.01'/%3E%3C/svg%3E");
|
||||
.aside-nav-wrapper :deep([class*=" vpi-"]) {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,22 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { shallowRef, useId } from 'vue'
|
||||
import type { LocaleConfig } from 'vuepress'
|
||||
import { computed, shallowRef, useId } from 'vue'
|
||||
import { useRouteLocale } from 'vuepress/client'
|
||||
import { useCaniuse, useCaniuseFeaturesSearch, useCaniuseVersionSelect } from '../composables/caniuse.js'
|
||||
import CodeViewer from './CodeViewer.vue'
|
||||
|
||||
const LOCALES: LocaleConfig<
|
||||
Record<'select-feature' | 'placeholder' | 'embed-type' | 'output' | 'browser-version' | 'no-recommend', string>
|
||||
> = {
|
||||
'/': {
|
||||
'select-feature': '选择特性:',
|
||||
'placeholder': '输入特性',
|
||||
'embed-type': '嵌入方式:',
|
||||
'output': '输出:',
|
||||
'browser-version': '浏览器版本:',
|
||||
'no-recommend': '不推荐',
|
||||
},
|
||||
'/en/': {
|
||||
'select-feature': 'Select feature:',
|
||||
'placeholder': 'Input feature',
|
||||
'embed-type': 'Embed type: ',
|
||||
'output': 'Output:',
|
||||
'browser-version': 'Browser version: ',
|
||||
'no-recommend': 'Not recommended',
|
||||
},
|
||||
}
|
||||
|
||||
const listEl = shallowRef<HTMLUListElement | null>(null)
|
||||
const inputEl = shallowRef<HTMLInputElement | null>(null)
|
||||
const id = useId()
|
||||
const routeLocale = useRouteLocale()
|
||||
|
||||
const locale = computed(() => LOCALES[routeLocale.value])
|
||||
|
||||
const { feature, featureList, onSelect, isFocus } = useCaniuseFeaturesSearch(inputEl, listEl)
|
||||
const { past, pastList, future, futureList, embedType, embedTypeList } = useCaniuseVersionSelect()
|
||||
const { output, rendered } = useCaniuse({ feature, embedType, past, future })
|
||||
const { output } = useCaniuse({ feature, embedType, past, future })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="caniuse-config-wrapper">
|
||||
<form>
|
||||
<label class="caniuse-form-item" :for="`caniuse-feature-input-${id}`">
|
||||
<span>选择特性:</span>
|
||||
<span>{{ locale['select-feature'] }}</span>
|
||||
<div class="feature-input">
|
||||
<input
|
||||
:id="`caniuse-feature-input-${id}`"
|
||||
@ -24,7 +50,7 @@ const { output, rendered } = useCaniuse({ feature, embedType, past, future })
|
||||
class="feature-input__input"
|
||||
type="text"
|
||||
name="feature"
|
||||
placeholder="输入特性"
|
||||
:placeholder="locale.placeholder"
|
||||
>
|
||||
<span class="vpi-chevron-down" />
|
||||
<ul v-show="isFocus" ref="listEl" class="feature-list">
|
||||
@ -37,15 +63,14 @@ const { output, rendered } = useCaniuse({ feature, embedType, past, future })
|
||||
class="feature-list-item"
|
||||
@click="onSelect(item)"
|
||||
@keydown.enter="onSelect(item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
v-html="item.label"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</label>
|
||||
<div class="caniuse-form-item">
|
||||
<span>嵌入方式:</span>
|
||||
<span>{{ locale['embed-type'] }}</span>
|
||||
<div class="caniuse-embed-type">
|
||||
<label
|
||||
v-for="(item, index) in embedTypeList"
|
||||
@ -54,12 +79,11 @@ const { output, rendered } = useCaniuse({ feature, embedType, past, future })
|
||||
>
|
||||
<input :id="`caniuse-embed-${id}-${index}`" v-model="embedType" type="radio" name="embedType" :value="item.value">
|
||||
<span>{{ item.label }}</span>
|
||||
<Badge v-if="item.value === 'image'" type="warning" text="不推荐" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!embedType" class="caniuse-form-item">
|
||||
<span>浏览器版本:</span>
|
||||
<span>{{ locale['browser-version'] }}</span>
|
||||
<div class="caniuse-browser-version">
|
||||
<label :for="`caniuse-past-${id}`">
|
||||
<select :id="`caniuse-past-${id}`" v-model="past" name="past">
|
||||
@ -80,11 +104,10 @@ const { output, rendered } = useCaniuse({ feature, embedType, past, future })
|
||||
</div>
|
||||
</form>
|
||||
<div class="caniuse-output">
|
||||
<h4>输出:</h4>
|
||||
<h4>{{ locale.output }}</h4>
|
||||
<CodeViewer lang="md" :content="output" />
|
||||
</div>
|
||||
<div v-if="embedType === 'image'" v-html="rendered" />
|
||||
<CanIUseViewer v-else-if="feature" :feature="feature" :past="past" :future="future" />
|
||||
<CanIUseViewer v-if="feature" :feature="feature" :past="past" :future="future" :baseline="embedType === 'baseline'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,12 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { PlumeThemeHomeHeroTintPlate } from 'vuepress-theme-plume/client'
|
||||
import { computed, watch } from 'vue'
|
||||
import InputRange from './InputRange.vue'
|
||||
|
||||
const min = 20
|
||||
const max = 240
|
||||
|
||||
const tintPlate = defineModel<PlumeThemeHomeHeroTintPlate>({
|
||||
interface PlateItem {
|
||||
value: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
const tintPlate = defineModel<{
|
||||
r: PlateItem
|
||||
g: PlateItem
|
||||
b: PlateItem
|
||||
}>({
|
||||
required: true,
|
||||
})
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ defineProps<{
|
||||
<a :href="demo.url" target="_blank" rel="noopener noreferrer" :aria-label="demo.name" :title="demo.name">{{ demo.name }}</a>
|
||||
</span>
|
||||
<a v-if="demo.repo" :href="demo.repo" class="github" target="_blank" rel="noopener noreferrer" :aria-label="`Link to GitHub: ${demo.name}`">
|
||||
<span class="vpi-social-github" />
|
||||
<span class="vpi-simple-icons-github" />
|
||||
</a>
|
||||
</h3>
|
||||
<p :title="demo.desc">
|
||||
|
||||
87
docs/.vuepress/themes/components/EmojiList.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { list } = defineProps<{
|
||||
list: Record<string, string>
|
||||
}>()
|
||||
|
||||
const emojiList = computed(() => {
|
||||
return Object.entries(list).map(([key, value]) => ({
|
||||
source: `:${key}:`,
|
||||
rendered: value,
|
||||
}))
|
||||
})
|
||||
|
||||
const { copy, copied, text } = useClipboard()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="emoji-wrapper">
|
||||
<ul>
|
||||
<li
|
||||
v-for="{ source, rendered } in emojiList" :key="source"
|
||||
:class="{ copied: copied && text === source }"
|
||||
>
|
||||
<Abbreviation @click="copy(source)">
|
||||
{{ rendered }}
|
||||
<template #tooltip>
|
||||
{{ source }}
|
||||
</template>
|
||||
</Abbreviation>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.emoji-wrapper ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.emoji-wrapper ul li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
margin: 0;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 4px;
|
||||
transition: border-color var(--vp-t-color);
|
||||
}
|
||||
|
||||
.emoji-wrapper ul li.copied {
|
||||
position: relative;
|
||||
border-color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.emoji-wrapper ul li.copied::after {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
color: var(--vp-c-success-1);
|
||||
text-align: center;
|
||||
vertical-align: -0.125em;
|
||||
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59z'/%3E%3C/svg%3E");
|
||||
background-color: var(--vp-c-success-soft);
|
||||
}
|
||||
|
||||
.emoji-wrapper ul li .vp-abbr {
|
||||
text-decoration: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
</style>
|
||||
@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { PlumeThemeHomeHeroTintPlate } from 'vuepress-theme-plume/client'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import VPHomeHero from 'vuepress-theme-plume/components/Home/VPHomeHero.vue'
|
||||
import { useDarkMode } from 'vuepress-theme-plume/composables'
|
||||
@ -10,6 +9,11 @@ import SingleTintPlate from './SingleTintPlate.vue'
|
||||
import TripletTintPlate from './TripletTintPlate.vue'
|
||||
|
||||
type Mode = 'single' | 'triplet' | 'custom'
|
||||
interface HeroTripletTintPlate {
|
||||
r: { value: number, offset: number }
|
||||
g: { value: number, offset: number }
|
||||
b: { value: number, offset: number }
|
||||
}
|
||||
|
||||
const hero = { name: 'Theme Plume', tagline: 'Next Theme', text: '简约的,功能丰富', actions: [] }
|
||||
const modeList: { value: Mode, label: string }[] = [
|
||||
@ -21,7 +25,7 @@ const modeList: { value: Mode, label: string }[] = [
|
||||
const mode = ref<Mode>('single')
|
||||
const singleTintPlate = ref<number>(220)
|
||||
const tripletTintPlate = ref<[number, number, number]>([220, 220, 220])
|
||||
const customTintPlate = ref<PlumeThemeHomeHeroTintPlate>({
|
||||
const customTintPlate = ref<HeroTripletTintPlate>({
|
||||
r: { value: 220, offset: 36 },
|
||||
g: { value: 220, offset: 36 },
|
||||
b: { value: 220, offset: 36 },
|
||||
@ -30,13 +34,13 @@ const customTintPlate = ref<PlumeThemeHomeHeroTintPlate>({
|
||||
const tintPlate = computed(() => {
|
||||
switch (mode.value) {
|
||||
case 'single':
|
||||
return singleTintPlate.value
|
||||
return { rgb: singleTintPlate.value }
|
||||
case 'triplet':
|
||||
return tripletTintPlate.value.join(',')
|
||||
return { rgb: tripletTintPlate.value.join(',') }
|
||||
case 'custom':
|
||||
return customTintPlate.value
|
||||
default:
|
||||
return ''
|
||||
return { rgb: '' }
|
||||
}
|
||||
})
|
||||
|
||||
@ -48,11 +52,11 @@ watch(useDarkMode(), (value) => {
|
||||
|
||||
const output = computed(() => {
|
||||
const tint = tintPlate.value
|
||||
let content = `---\nhome: true\nconfig:\n -\n type: hero\n background: tint-plate\n tintPlate:`
|
||||
if (typeof tint === 'number' || typeof tint === 'string')
|
||||
content += ` ${tint}`
|
||||
|
||||
if (typeof tint === 'object') {
|
||||
let content = `---\nhome: true\nconfig:\n -\n type: hero\n effect: tint-plate\n effectConfig:`
|
||||
if ('rgb' in tint) {
|
||||
content += ` ${tint.rgb}`
|
||||
}
|
||||
else if (typeof tint === 'object') {
|
||||
content += `
|
||||
r:
|
||||
value: ${tint.r.value}
|
||||
@ -72,9 +76,9 @@ const output = computed(() => {
|
||||
<template>
|
||||
<div class="hero-tint-plate-wrapper">
|
||||
<h4>效果预览:</h4>
|
||||
<div :class="{ dark: isDark }">
|
||||
<div :data-theme="isDark ? 'dark' : 'light'">
|
||||
<DemoWrapper>
|
||||
<VPHomeHero type="hero" background="tint-plate" :tint-plate="tintPlate" :hero="hero" />
|
||||
<VPHomeHero type="hero" effect="TintPlate" :effect-config="tintPlate" :hero="hero" :index="0" />
|
||||
</DemoWrapper>
|
||||
</div>
|
||||
<p>
|
||||
|
||||
@ -1,16 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import VPButton from '@theme/VPButton.vue'
|
||||
import type { LocaleConfig } from 'vuepress'
|
||||
import { computed } from 'vue'
|
||||
import VPButton from 'vuepress-theme-plume/components/VPButton.vue'
|
||||
import { useRouteLocale } from 'vuepress/client'
|
||||
import { useThemeColors } from '../composables/theme-colors.js'
|
||||
import CodeViewer from './CodeViewer.vue'
|
||||
import ColorPick from './ColorPick.vue'
|
||||
|
||||
const locales: LocaleConfig<
|
||||
Record<'reset' | 'light' | 'dark' | 'desc' | 'tip', string>
|
||||
> = {
|
||||
'/': {
|
||||
reset: '重置',
|
||||
light: '浅色主题',
|
||||
dark: '深色主题',
|
||||
desc: '复制下方的代码到您的项目中,请参考',
|
||||
tip: '主题定制',
|
||||
},
|
||||
'/en/': {
|
||||
reset: 'Reset',
|
||||
light: 'Light theme',
|
||||
dark: 'Dark theme',
|
||||
desc: 'Copy the code below and paste it into your project, please refer to',
|
||||
tip: 'Theme customization',
|
||||
},
|
||||
}
|
||||
|
||||
const routeLocale = useRouteLocale()
|
||||
const locale = computed(() => locales[routeLocale.value])
|
||||
|
||||
const { lightColors, darkColors, css, reset } = useThemeColors()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPButton theme="alt" text="重置" @click="reset" />
|
||||
<VPButton theme="alt" :text="locale.reset" @click="reset" />
|
||||
|
||||
<h2>浅色主题</h2>
|
||||
<h2>{{ locale.light }}</h2>
|
||||
<div class="theme-colors-wrapper">
|
||||
<div v-for="({ name, group }, index) in lightColors" :key="index" class="group">
|
||||
<h4>{{ name }}</h4>
|
||||
@ -23,7 +48,7 @@ const { lightColors, darkColors, css, reset } = useThemeColors()
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<h2>深色主题</h2>
|
||||
<h2>{{ locale.dark }}</h2>
|
||||
<div class="theme-colors-wrapper">
|
||||
<div v-for="({ name, group }, index) in darkColors" :key="index" class="group">
|
||||
<h4>{{ name }}</h4>
|
||||
@ -36,7 +61,7 @@ const { lightColors, darkColors, css, reset } = useThemeColors()
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<p>复制下方的代码到您的项目中,请参考 <a href="/guide/custom-style/">主题定制</a> </p>
|
||||
<p>{{ locale.desc }} <a href="/guide/custom-style/">{{ locale.tip }}</a> </p>
|
||||
<CodeViewer :content="css" lang="css" />
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { LocaleConfig } from 'vuepress'
|
||||
import { onClickOutside, useDebounceFn, useEventListener, useLocalStorage } from '@vueuse/core'
|
||||
import { computed, onMounted, readonly, ref, watch } from 'vue'
|
||||
import { useRouteLocale } from 'vuepress/client'
|
||||
|
||||
interface Feature {
|
||||
label: string
|
||||
@ -14,41 +16,60 @@ interface SelectItem {
|
||||
|
||||
const api = 'https://caniuse.pengzhanbo.cn/features.json'
|
||||
|
||||
const pastVersions: SelectItem[] = [
|
||||
{ label: '不显示旧版本', value: '0' },
|
||||
...Array.from({ length: 5 }).fill(0).map((_, i) => ({
|
||||
label: `旧版本(当前版本 - ${i + 1})`,
|
||||
value: `${i + 1}`,
|
||||
})),
|
||||
]
|
||||
|
||||
const futureVersions: SelectItem[] = [
|
||||
{ label: '不显示未来版本', value: '0' },
|
||||
...Array.from({ length: 3 }).fill(0).map((_, i) => ({
|
||||
label: `未来版本(当前版本 + ${i + 1})`,
|
||||
value: `${i + 1}`,
|
||||
})),
|
||||
]
|
||||
const locales: LocaleConfig<
|
||||
Record<'past0' | 'pastIndex' | 'future0' | 'futureIndex', string>
|
||||
> = {
|
||||
'/': {
|
||||
past0: '不显示旧版本',
|
||||
pastIndex: '旧版本(当前版本 - {index})',
|
||||
future0: '不显示未来版本',
|
||||
futureIndex: '未来版本(当前版本 + {index})',
|
||||
},
|
||||
'/en/': {
|
||||
past0: 'Do not show old versions',
|
||||
pastIndex: 'Old version (current version - {index})',
|
||||
future0: 'Do not show future versions',
|
||||
futureIndex: 'Future version (current version + {index})',
|
||||
},
|
||||
}
|
||||
|
||||
const embedTypes: SelectItem[] = [
|
||||
{ label: 'iframe', value: '' },
|
||||
{ label: 'image', value: 'image' },
|
||||
{ label: 'caniuse', value: '' },
|
||||
{ label: 'baseline', value: 'baseline' },
|
||||
]
|
||||
|
||||
export function useCaniuseVersionSelect(): {
|
||||
past: Ref<string>
|
||||
future: Ref<string>
|
||||
embedType: Ref<string>
|
||||
pastList: Readonly<SelectItem[]>
|
||||
futureList: Readonly<SelectItem[]>
|
||||
pastList: ComputedRef<SelectItem[]>
|
||||
futureList: ComputedRef<SelectItem[]>
|
||||
embedTypeList: Readonly<SelectItem[]>
|
||||
} {
|
||||
const routeLocale = useRouteLocale()
|
||||
|
||||
const past = ref('2')
|
||||
const future = ref('1')
|
||||
const embedType = ref('')
|
||||
|
||||
const pastList = readonly(pastVersions)
|
||||
const futureList = readonly(futureVersions)
|
||||
const pastList = computed(() => {
|
||||
return [
|
||||
{ label: locales[routeLocale.value].past0 || '', value: '0' },
|
||||
...Array.from({ length: 5 }).fill(0).map((_, i) => ({
|
||||
label: locales[routeLocale.value]?.pastIndex?.replace('{index}', `${i + 1}`) ?? '',
|
||||
value: `${i + 1}`,
|
||||
})),
|
||||
]
|
||||
})
|
||||
const futureList = computed(() => {
|
||||
return [
|
||||
{ label: locales[routeLocale.value].future0 || '', value: '0' },
|
||||
...Array.from({ length: 3 }).fill(0).map((_, i) => ({
|
||||
label: locales[routeLocale.value]?.futureIndex?.replace('{index}', `${i + 1}`) ?? '',
|
||||
value: `${i + 1}`,
|
||||
})),
|
||||
]
|
||||
})
|
||||
const embedTypeList = readonly(embedTypes)
|
||||
|
||||
return {
|
||||
@ -65,11 +86,11 @@ export function useCaniuseFeaturesSearch(
|
||||
inputEl: Ref<HTMLInputElement | null>,
|
||||
listEl: Ref<HTMLUListElement | null>,
|
||||
): {
|
||||
featureList: Ref<Feature[] | undefined>
|
||||
isFocus: Ref<boolean>
|
||||
feature: ComputedRef<string>
|
||||
onSelect: (item: Feature) => void
|
||||
} {
|
||||
featureList: Ref<Feature[] | undefined>
|
||||
isFocus: Ref<boolean>
|
||||
feature: ComputedRef<string>
|
||||
onSelect: (item: Feature) => void
|
||||
} {
|
||||
const features = useLocalStorage('plume:caniuse-feature-list', [] as Feature[])
|
||||
const featuresUpdated = useLocalStorage('plume:caniuse-feature-list-updated', Date.now())
|
||||
const maxAge = 1000 * 60 * 60 * 24 * 3 // 3 days
|
||||
@ -137,19 +158,20 @@ export function useCaniuse({ feature, embedType, past, future }: {
|
||||
past: Ref<string>
|
||||
future: Ref<string>
|
||||
}): {
|
||||
output: ComputedRef<string>
|
||||
rendered: ComputedRef<string>
|
||||
} {
|
||||
output: ComputedRef<string>
|
||||
} {
|
||||
const output = computed(() => {
|
||||
let content = '@[caniuse'
|
||||
if (embedType.value)
|
||||
content += ` ${embedType.value}`
|
||||
|
||||
if (past.value !== '-2' || future.value !== '1') {
|
||||
if (past.value === '0' && future.value === '0')
|
||||
content += '{0}'
|
||||
else
|
||||
content += `{-${past.value},${future.value}}`
|
||||
if (embedType.value !== 'baseline') {
|
||||
if (past.value !== '-2' || future.value !== '1') {
|
||||
if (past.value === '0' && future.value === '0')
|
||||
content += '{0}'
|
||||
else
|
||||
content += `{-${past.value},${future.value}}`
|
||||
}
|
||||
}
|
||||
|
||||
content += ']('
|
||||
@ -160,21 +182,5 @@ export function useCaniuse({ feature, embedType, past, future }: {
|
||||
return `${content})`
|
||||
})
|
||||
|
||||
const rendered = computed(() => {
|
||||
if (!feature.value || !embedType.value)
|
||||
return ''
|
||||
return resolveCanIUse(feature.value)
|
||||
})
|
||||
|
||||
return { output, rendered }
|
||||
}
|
||||
|
||||
function resolveCanIUse(feature: string): string {
|
||||
const link = 'https://caniuse.bitsofco.de/image/'
|
||||
const alt = `Data on support for the ${feature} feature across the major browsers from caniuse.com`
|
||||
return `<p><picture>
|
||||
<source type="image/webp" srcset="${link}${feature}.webp">
|
||||
<source type="image/png" srcset="${link}${feature}.png">
|
||||
<img src="${link}${feature}.jpg" alt="${alt}" width="100%">
|
||||
</picture></p>`
|
||||
return { output }
|
||||
}
|
||||
|
||||
1925
docs/.vuepress/themes/composables/emoji.ts
Normal file
@ -1,135 +0,0 @@
|
||||
---
|
||||
title: 示例文章9
|
||||
createTime: 2024/03/01 22:56:03
|
||||
permalink: /article/z8zvx0ru/
|
||||
---
|
||||
|
||||
:::go-repl
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello World")
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::go-repl
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 10; i++ {
|
||||
dur := time.Duration(rand.Intn(1000)) * time.Millisecond
|
||||
fmt.Printf("Sleeping for %v\n", dur)
|
||||
// Sleep for a random duration between 0-1000ms
|
||||
time.Sleep(dur)
|
||||
}
|
||||
fmt.Println("Done!")
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: go-repl
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "Hello, playground")
|
||||
})
|
||||
|
||||
log.Println("Starting server...")
|
||||
l, err := net.Listen("tcp", "localhost:8080")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
go func() {
|
||||
log.Fatal(http.Serve(l, nil))
|
||||
}()
|
||||
|
||||
log.Println("Sending request...")
|
||||
res, err := http.Get("http://localhost:8080/hello")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Reading response...")
|
||||
if _, err := io.Copy(os.Stdout, res.Body); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: kotlin-repl
|
||||
|
||||
```kotlin
|
||||
class Contact(val id: Int, var email: String)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val contact = Contact(1, "mary@gmail.com")
|
||||
println(contact.id)
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: kotlin-repl
|
||||
|
||||
```kotlin
|
||||
fun mul(a: Int, b: Int): Int {
|
||||
return a * b
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
print(mul(-2, 4))
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: rust-repl
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: rust-repl
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
printlnl!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
@ -5,7 +5,8 @@ config:
|
||||
-
|
||||
type: hero
|
||||
full: true
|
||||
background: tint-plate
|
||||
effect: hyper-speed
|
||||
forceDark: true
|
||||
hero:
|
||||
name: Theme Plume
|
||||
tagline: VuePress Next Theme
|
||||
@ -158,21 +159,7 @@ npm run docs:dev
|
||||
|
||||
感谢所有的贡献者!
|
||||
|
||||
<Contributors
|
||||
:contributors="[
|
||||
'pengzhanbo',
|
||||
{ github: 'huankong233', name: 'huan_kong' },
|
||||
{ github: 'northword', name: 'Northword' },
|
||||
'KrLite',
|
||||
'shylock-wu',
|
||||
'hrradev',
|
||||
{ github: 'TheCoderAlex', name: 'Tang Zifeng' },
|
||||
{ github: 'HydroGest', name: 'MarkChai' },
|
||||
{ github: 'sunnyboy-mu', name: '小沐沐吖' },
|
||||
{ github: 'zhenghaoyang24', name: 'zhenghaoyang24' },
|
||||
{ github: 'shuoliuchn', name: 'Shuo Liu' },
|
||||
]"
|
||||
/>
|
||||
<Contributors :contributors="data" />
|
||||
|
||||
</div>
|
||||
|
||||
@ -184,4 +171,5 @@ npm run docs:dev
|
||||
|
||||
<script setup>
|
||||
import Contributors from '~/components/Contributors.vue'
|
||||
import data from '@source/contributors.json'
|
||||
</script>
|
||||
|
||||