commit
850d2005e5
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -33,9 +33,10 @@
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.stylelint": "explicit",
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll.markdownlint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnPaste": false,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
|
||||
@ -334,6 +334,20 @@ type NavItem = string | {
|
||||
|
||||
详细配置请查看 [此文档](/config/notes/)
|
||||
|
||||
### aside
|
||||
|
||||
- 类型: `boolean | 'left'`
|
||||
- 默认值: `true`
|
||||
- 详情:
|
||||
|
||||
是否显示侧边栏
|
||||
|
||||
- `false` 表示禁用 右侧边栏
|
||||
- `true` 表示启用 右侧边栏
|
||||
- `'left` 表示将有侧边栏移动到文章内容左侧,sidebar 右侧
|
||||
|
||||
每个页面可以通过 [frontmatter aside](./frontmatter/basic.md#aside) 覆盖层级配置。
|
||||
|
||||
### outline
|
||||
|
||||
- 类型: `false | number | [number, number] | 'deep'`
|
||||
@ -348,6 +362,8 @@ type NavItem = string | {
|
||||
|
||||
`'deep'` 与 `[2, 6]` 相同,将显示从 `<h2>` 到 `<h6>` 的所有标题。
|
||||
|
||||
当 [aside](#aside) 被禁用时,`outline` 也会被禁用
|
||||
|
||||
每个页面可以通过 [frontmatter outline](./frontmatter/basic.md#outline) 覆盖层级配置。
|
||||
|
||||
### selectLanguageName
|
||||
@ -366,7 +382,7 @@ type NavItem = string | {
|
||||
- 默认值: `''`
|
||||
- 详情:
|
||||
|
||||
_选择语言菜单_ 的文字。
|
||||
_选择语言菜单_ 的文本。
|
||||
|
||||
如果你在站点配置中设置了多个 [locales](#locales) ,那么 _选择语言菜单_ 就会显示在导航栏中仓库按钮的旁边。
|
||||
|
||||
@ -386,7 +402,7 @@ type NavItem = string | {
|
||||
- 默认值: `'Menu'`
|
||||
- 详情:
|
||||
|
||||
移动设备下的导航栏中 菜单选项的文字。
|
||||
移动设备下的导航栏中 菜单选项的文本。
|
||||
|
||||
### returnToTopLabel
|
||||
|
||||
@ -394,7 +410,7 @@ type NavItem = string | {
|
||||
- 默认值: `'return to top'`
|
||||
- 详情:
|
||||
|
||||
移动设备下的导航栏中返回顶部的文字。
|
||||
移动设备下的导航栏中返回顶部的文本。
|
||||
|
||||
### outlineLabel
|
||||
|
||||
@ -402,7 +418,7 @@ type NavItem = string | {
|
||||
- 默认值: `'On this page'`
|
||||
- 详情:
|
||||
|
||||
移动设备下的导航栏中大纲标题的文字
|
||||
移动设备下的导航栏中大纲标题的文本
|
||||
|
||||
### editLink
|
||||
|
||||
@ -414,7 +430,7 @@ type NavItem = string | {
|
||||
|
||||
- 类型: `string`
|
||||
- 默认值: `'Edit this page'`
|
||||
- 详情: 编辑链接文字
|
||||
- 详情: 编辑链接文本
|
||||
|
||||
### editLinkPattern
|
||||
|
||||
@ -428,7 +444,7 @@ type NavItem = string | {
|
||||
|
||||
- 类型: `string`
|
||||
- 默认值: `''`
|
||||
- 详情: 文档仓库配置, 用于生成 `Edit this page` 链接,如果为空,默认使用 `repo` 配置的值
|
||||
- 详情: 文档仓库配置, 用于生成 `Edit this page` 链接。
|
||||
|
||||
### docsBranch
|
||||
|
||||
@ -478,19 +494,19 @@ interface LastUpdatedOptions {
|
||||
|
||||
- 类型: `string`
|
||||
- 默认值: `'Contributors'`
|
||||
- 详情: 贡献者的文字
|
||||
- 详情: 贡献者的文本
|
||||
|
||||
### prevPageLabel
|
||||
|
||||
- 类型: `string`
|
||||
- 默认值: `'Previous Page'`
|
||||
- 详情: 上一页的文字
|
||||
- 详情: 上一页的文本
|
||||
|
||||
### nextPageLabel
|
||||
|
||||
- 类型: `string`
|
||||
- 默认值: `'Next Page'`
|
||||
- 详情: 下一页的文字
|
||||
- 详情: 下一页的文本
|
||||
|
||||
### notFound
|
||||
|
||||
|
||||
@ -4,6 +4,9 @@ author: pengzhanbo
|
||||
icon: fluent:markdown-20-filled
|
||||
createTime: 2024/03/05 11:10:39
|
||||
permalink: /guide/markdown/basic/
|
||||
tags:
|
||||
- 指南
|
||||
- markdown
|
||||
---
|
||||
|
||||
::: note
|
||||
|
||||
@ -4,6 +4,9 @@ author: pengzhanbo
|
||||
icon: fluent-mdl2:auto-enhance-on
|
||||
createTime: 2024/03/05 23:29:07
|
||||
permalink: /guide/markdown/extensions/
|
||||
tags:
|
||||
- 指南
|
||||
- markdown
|
||||
---
|
||||
|
||||
## 标题锚点
|
||||
|
||||
@ -2,10 +2,448 @@
|
||||
title: 更多
|
||||
author: pengzhanbo
|
||||
icon: ic:outline-auto-fix-high
|
||||
outline: 2
|
||||
createTime: 2024/03/05 16:27:59
|
||||
permalink: /guide/markdown/advance/
|
||||
tags:
|
||||
- 指南
|
||||
- markdown
|
||||
---
|
||||
|
||||
## 卡片
|
||||
|
||||
对于想要突出显示的内容,可以将其放在 卡片容器 `::: card` 中。
|
||||
|
||||
如果需要在空间足够时并排显示多个卡片,可以使用 `card-grid` 容器,包裹 多个卡片。
|
||||
|
||||
### 语法
|
||||
|
||||
```md
|
||||
<!-- 单个卡片 -->
|
||||
::: card title="标题" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
|
||||
<!-- 多个卡片 -->
|
||||
:::: card-grid
|
||||
|
||||
::: card title="卡片标题 1" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
|
||||
::: card title="卡片标题 2" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
|
||||
::::
|
||||
```
|
||||
|
||||
卡片 支持 可选的 `title` 和 `icon`。 其中, icon 支持传入 图片链接,也可以传入 iconify 图标名。
|
||||
|
||||
### 示例
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
::: card title="卡片标题" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
::: card title="卡片标题" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
:::: card-grid
|
||||
::: card title="卡片标题 1" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
|
||||
::: card title="卡片标题 2" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
::::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
:::: card-grid
|
||||
::: card title="卡片标题 1" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
|
||||
::: card title="卡片标题 2" icon="twemoji:astonished-face"
|
||||
|
||||
这里是卡片内容。
|
||||
:::
|
||||
::::
|
||||
|
||||
::: info
|
||||
如果你更喜欢通过组件的方式使用 卡片,你可以查看 [卡片组件](/guide/features/component/#card) 。
|
||||
:::
|
||||
|
||||
## 步骤
|
||||
|
||||
有时候,你需要将内容 划分为递进的步骤展示,你可以使用 `steps` 容器 实现。
|
||||
|
||||
### 语法
|
||||
|
||||
在 `steps` 容器内,使用 有序列表 (或无序列表) 来表示步骤。你可以在 容器内使用 任意 markdown 语法。
|
||||
|
||||
````md
|
||||
::: steps
|
||||
|
||||
1. 步骤 1
|
||||
|
||||
相关内容
|
||||
|
||||
2. 步骤 2
|
||||
|
||||
相关内容
|
||||
|
||||
:::
|
||||
````
|
||||
|
||||
### 示例
|
||||
|
||||
输入:
|
||||
|
||||
````md
|
||||
:::: steps
|
||||
1. 步骤 1
|
||||
|
||||
```ts
|
||||
console.log('Hello World!')
|
||||
```
|
||||
|
||||
2. 步骤 2
|
||||
|
||||
这里是步骤 2 的相关内容
|
||||
|
||||
3. 步骤 3
|
||||
|
||||
::: tip
|
||||
提示容器
|
||||
:::
|
||||
|
||||
4. 结束
|
||||
::::
|
||||
````
|
||||
|
||||
输出:
|
||||
|
||||
:::: steps
|
||||
|
||||
1. 步骤 1
|
||||
|
||||
```ts
|
||||
console.log('Hello World!')
|
||||
```
|
||||
|
||||
2. 步骤 2
|
||||
|
||||
这里是步骤 2 的相关内容
|
||||
|
||||
3. 步骤 3
|
||||
|
||||
::: tip
|
||||
提示容器
|
||||
:::
|
||||
|
||||
4. 结束
|
||||
::::
|
||||
|
||||
## 选项组
|
||||
|
||||
在 Markdown 中支持选项卡。
|
||||
|
||||
你需要将选项卡包装在 `tabs` 容器中。
|
||||
|
||||
你可以在 `tabs` 容器中添加一个 id 后缀,该后缀将用作选项卡 id。
|
||||
所有具有相同 id 的选项卡将共享相同的切换事件。
|
||||
|
||||
```md
|
||||
::: tabs#fruit
|
||||
|
||||
<!-- 这里,fruit 将用作 id,它是可选的 -->
|
||||
|
||||
<!-- 选项卡内容 -->
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
在这个容器内,你应该使用 `@tab` 标记来标记和分隔选项卡内容。
|
||||
|
||||
在 `@tab` 标记后,你可以添加文本 `:active` 默认激活选项卡,之后的文本将被解析为选项卡标题。
|
||||
|
||||
```md
|
||||
::: tabs
|
||||
|
||||
@tab 标题 1
|
||||
|
||||
<!-- tab 1 内容 -->
|
||||
|
||||
@tab 标题 2
|
||||
|
||||
<!-- tab 2 内容 -->
|
||||
|
||||
@tab:active 标题 3
|
||||
|
||||
<!-- tab 3 将会被默认激活 -->
|
||||
|
||||
<!-- tab 3 内容 -->
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
默认情况下,标题将用作选项卡的值,但你可以使用 id 后缀覆盖它。
|
||||
|
||||
```md
|
||||
::: tabs
|
||||
|
||||
@tab 标题 1
|
||||
|
||||
<!-- 此处,选项卡 1 的标题“标题 1”将用作值。 -->
|
||||
|
||||
<!-- tab 1 内容 -->
|
||||
|
||||
@tab 标题 2#值 2
|
||||
|
||||
<!-- 这里,tab 2 的标题将是 “标题 2”,但它会使用 “值 2” 作为选项卡的值-->
|
||||
|
||||
<!-- tab 2 内容 -->
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
你可以在每个选项卡中使用 Vue 语法和组件,并且你可以访问 value 和 isActive,
|
||||
表示选项卡的绑定值和选项卡是否处于激活状态。
|
||||
|
||||
**输入:**
|
||||
|
||||
````
|
||||
::: tabs
|
||||
@tab npm
|
||||
|
||||
npm 应该与 Node.js 被一同安装。
|
||||
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
corepack use pnpm@8
|
||||
```
|
||||
|
||||
:::
|
||||
````
|
||||
|
||||
**输出:**
|
||||
|
||||
::: tabs
|
||||
@tab npm
|
||||
|
||||
npm 应该与 Node.js 被一同安装。
|
||||
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
corepack use pnpm@8
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## “隐秘”文本
|
||||
|
||||
有时候,你不想直接把文本内容毫无保留的展示出来,想要保留一些 隐秘性,
|
||||
可能是为了引起读者的好奇心,也可能纯粹是故意增加点阅读障碍,让文章变得更加有趣。
|
||||
|
||||
为了满足这种小小的心思,主题提供了一个 **“隐秘”文本** 的有趣小功能。它看起来像这样:
|
||||
|
||||
:::demo-wrapper
|
||||
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
|
||||
力量!于是,!!我在床上翻了个身!! !
|
||||
:::
|
||||
|
||||
读者不能直接阅读到完整的内容,部分的内容被 “遮住”,需要鼠标悬停到内容上,才能看到被遮住的内容。
|
||||
|
||||
### 配置
|
||||
|
||||
该功能默认不启用,你需要在 `theme` 配置中启用。
|
||||
|
||||
::: code-tabs
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
plugins: {
|
||||
markdownPower: {
|
||||
plot: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
`markdownPower.plot` 支持传入 `boolean | PlotOptions` 类型
|
||||
|
||||
```ts
|
||||
interface PlotOptions {
|
||||
/**
|
||||
* 是否启用 `!! !!` markdown (该标记为非标准标记,脱离插件将不生效)
|
||||
* 如果设置为 false, 则表示不启用该标记,只能使用 <Plot /> 组件
|
||||
* @default true
|
||||
*/
|
||||
tag?: boolean
|
||||
|
||||
/**
|
||||
* 遮罩层颜色
|
||||
*/
|
||||
mask?: string | { light: string, dark: string }
|
||||
|
||||
/**
|
||||
* 文本颜色
|
||||
*/
|
||||
color?: string | { light: string, dark: string }
|
||||
|
||||
/**
|
||||
* 触发方式
|
||||
*
|
||||
* @default 'hover'
|
||||
*/
|
||||
trigger?: 'hover' | 'click'
|
||||
}
|
||||
```
|
||||
|
||||
### 语法
|
||||
|
||||
```md
|
||||
!!需要隐秘的内容!!
|
||||
```
|
||||
|
||||
如果不想使用 非标准的 `!! !!` 标记语法,你可以将 `plot.tag` 设置为 `false` ,
|
||||
然后使用 [`<Plot />`](/guide/features/component/#plot) 组件替代。
|
||||
|
||||
### 示例
|
||||
|
||||
输入:
|
||||
|
||||
```md
|
||||
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
|
||||
力量!于是,!!我在床上翻了个身!! !
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
:::demo-wrapper
|
||||
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
|
||||
力量!于是,!!我在床上翻了个身!! !
|
||||
:::
|
||||
|
||||
## 示例容器
|
||||
|
||||
有时候,你可能需要在 内容中补充一些 示例,但期望能与 其它内容 分隔开来呈现。
|
||||
主题支持在 Markdown 文件中添加示例容器。
|
||||
|
||||
### 语法
|
||||
|
||||
````md
|
||||
::: demo-wrapper
|
||||
添加你的示例
|
||||
:::
|
||||
````
|
||||
|
||||
### 选项
|
||||
|
||||
- `title="xxx"`:标题
|
||||
- `no-padding`:不添加内边距
|
||||
- `img`: 仅包含图片时使用
|
||||
- `height="xxx"`: 高度
|
||||
|
||||
### 示例
|
||||
|
||||
仅包含图片:
|
||||
|
||||
```md
|
||||
::: demo-wrapper img no-padding
|
||||

|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
::: demo-wrapper img no-padding
|
||||

|
||||
:::
|
||||
|
||||
包含 markdown 语法:
|
||||
|
||||
```md
|
||||
::: demo-wrapper title="标题"
|
||||
### 三级标题
|
||||
|
||||
这是示例容器中的内容。
|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
::: demo-wrapper title="标题"
|
||||
|
||||
### 三级标题
|
||||
|
||||
这是示例容器中的内容。
|
||||
:::
|
||||
|
||||
包含 html /vue 代码:
|
||||
|
||||
```md
|
||||
::: demo-wrapper
|
||||
<h1 class="your-demo-title">这是标题</h1>
|
||||
<p class="your-demo-paragraph">这是段落</p>
|
||||
|
||||
<style>
|
||||
.your-demo-title {
|
||||
color: red;
|
||||
}
|
||||
.your-demo-paragraph {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
::: demo-wrapper
|
||||
|
||||
<h1 class="your-demo-title">这是标题</h1>
|
||||
<p class="your-demo-paragraph">这是段落</p>
|
||||
|
||||
<style>
|
||||
.your-demo-title {
|
||||
color: red !important;
|
||||
}
|
||||
.your-demo-paragraph {
|
||||
color: blue !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
:::
|
||||
|
||||
## iconify 图标
|
||||
|
||||
在 Markdown 文件中使用 [iconify](https://iconify.design/) 的图标。 主题虽然提供了
|
||||
@ -108,284 +546,6 @@ github: :[tdesign:logo-github-filled]:
|
||||
修改大小::[tdesign:logo-github-filled 36px]:
|
||||
修改大小颜色::[tdesign:logo-github-filled 36px/#f00]:
|
||||
|
||||
## “隐秘”文本
|
||||
|
||||
有时候,你不想直接把文本内容毫无保留的展示出来,想要保留一些 隐秘性,
|
||||
可能是为了引起读者的好奇心,也可能纯粹是故意增加点阅读障碍,让文章变得更加有趣。
|
||||
|
||||
为了满足这种小小的心思,主题提供了一个 **“隐秘”文本** 的有趣小功能。它看起来像这样:
|
||||
|
||||
:::demo-wrapper
|
||||
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
|
||||
力量!于是,!!我在床上翻了个身!! !
|
||||
:::
|
||||
|
||||
读者不能直接阅读到完整的内容,部分的内容被 “遮住”,需要鼠标悬停到内容上,才能看到被遮住的内容。
|
||||
|
||||
### 配置
|
||||
|
||||
该功能默认不启用,你需要在 `theme` 配置中启用。
|
||||
|
||||
::: code-tabs
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
plugins: {
|
||||
markdownPower: {
|
||||
plot: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
`markdownPower.plot` 支持传入 `boolean | PlotOptions` 类型
|
||||
|
||||
```ts
|
||||
interface PlotOptions {
|
||||
/**
|
||||
* 是否启用 `!! !!` markdown (该标记为非标准标记,脱离插件将不生效)
|
||||
* 如果设置为 false, 则表示不启用该标记,只能使用 <Plot /> 组件
|
||||
* @default true
|
||||
*/
|
||||
tag?: boolean
|
||||
|
||||
/**
|
||||
* 遮罩层颜色
|
||||
*/
|
||||
mask?: string | { light: string, dark: string }
|
||||
|
||||
/**
|
||||
* 文本颜色
|
||||
*/
|
||||
color?: string | { light: string, dark: string }
|
||||
|
||||
/**
|
||||
* 触发方式
|
||||
*
|
||||
* @default 'hover'
|
||||
*/
|
||||
trigger?: 'hover' | 'click'
|
||||
}
|
||||
```
|
||||
|
||||
### 语法
|
||||
|
||||
```md
|
||||
!!需要隐秘的内容!!
|
||||
```
|
||||
|
||||
如果不想使用 非标准的 `!! !!` 标记语法,你可以将 `plot.tag` 设置为 `false` ,
|
||||
然后使用 [`<Plot />`](/guide/features/component/#plot) 组件替代。
|
||||
|
||||
### 示例
|
||||
|
||||
输入:
|
||||
|
||||
```md
|
||||
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
|
||||
力量!于是,!!我在床上翻了个身!! !
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
:::demo-wrapper
|
||||
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
|
||||
力量!于是,!!我在床上翻了个身!! !
|
||||
:::
|
||||
|
||||
## 选项组
|
||||
|
||||
让你的 Markdown 文件支持选项卡。
|
||||
|
||||
你需要将选项卡包装在 `tabs` 容器中。
|
||||
|
||||
你可以在 `tabs` 容器中添加一个 id 后缀,该后缀将用作选项卡 id。
|
||||
所有具有相同 id 的选项卡将共享相同的切换事件。
|
||||
|
||||
```md
|
||||
::: tabs#fruit
|
||||
|
||||
<!-- 这里,fruit 将用作 id,它是可选的 -->
|
||||
|
||||
<!-- 选项卡内容 -->
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
在这个容器内,你应该使用 `@tab` 标记来标记和分隔选项卡内容。
|
||||
|
||||
在 `@tab` 标记后,你可以添加文本 `:active` 默认激活选项卡,之后的文本将被解析为选项卡标题。
|
||||
|
||||
```md
|
||||
::: tabs
|
||||
|
||||
@tab 标题 1
|
||||
|
||||
<!-- tab 1 内容 -->
|
||||
|
||||
@tab 标题 2
|
||||
|
||||
<!-- tab 2 内容 -->
|
||||
|
||||
@tab:active 标题 3
|
||||
|
||||
<!-- tab 3 将会被默认激活 -->
|
||||
|
||||
<!-- tab 3 内容 -->
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
默认情况下,标题将用作选项卡的值,但你可以使用 id 后缀覆盖它。
|
||||
|
||||
```md
|
||||
::: tabs
|
||||
|
||||
@tab 标题 1
|
||||
|
||||
<!-- 此处,选项卡 1 的标题“标题 1”将用作值。 -->
|
||||
|
||||
<!-- tab 1 内容 -->
|
||||
|
||||
@tab 标题 2#值 2
|
||||
|
||||
<!-- 这里,tab 2 的标题将是 “标题 2”,但它会使用 “值 2” 作为选项卡的值-->
|
||||
|
||||
<!-- tab 2 内容 -->
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
你可以在每个选项卡中使用 Vue 语法和组件,并且你可以访问 value 和 isActive,
|
||||
表示选项卡的绑定值和选项卡是否处于激活状态。
|
||||
|
||||
**输入:**
|
||||
|
||||
````
|
||||
::: tabs
|
||||
@tab npm
|
||||
|
||||
npm 应该与 Node.js 被一同安装。
|
||||
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
corepack use pnpm@8
|
||||
```
|
||||
|
||||
:::
|
||||
````
|
||||
|
||||
**输出:**
|
||||
|
||||
::: tabs
|
||||
@tab npm
|
||||
|
||||
npm 应该与 Node.js 被一同安装。
|
||||
|
||||
@tab pnpm
|
||||
|
||||
```sh
|
||||
corepack enable
|
||||
corepack use pnpm@8
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 示例容器
|
||||
|
||||
有时候,你可能需要在 内容中补充一些 示例,但期望能与 其它内容 分隔开来呈现。
|
||||
主题支持在 Markdown 文件中添加示例容器。
|
||||
|
||||
### 语法
|
||||
|
||||
````md
|
||||
::: demo-wrapper
|
||||
添加你的示例
|
||||
:::
|
||||
````
|
||||
|
||||
### 选项
|
||||
|
||||
- `title="xxx"`:标题
|
||||
- `no-padding`:不添加内边距
|
||||
- `img`: 仅包含图片时使用
|
||||
- `height="xxx"`: 高度
|
||||
|
||||
### 示例
|
||||
|
||||
仅包含图片:
|
||||
|
||||
```md
|
||||
::: demo-wrapper img no-padding
|
||||

|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
::: demo-wrapper img no-padding
|
||||

|
||||
:::
|
||||
|
||||
包含 markdown 语法:
|
||||
|
||||
```md
|
||||
::: demo-wrapper title="标题"
|
||||
### 三级标题
|
||||
|
||||
这是示例容器中的内容。
|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
::: demo-wrapper title="标题"
|
||||
|
||||
### 三级标题
|
||||
|
||||
这是示例容器中的内容。
|
||||
:::
|
||||
|
||||
包含 html /vue 代码:
|
||||
|
||||
```md
|
||||
::: demo-wrapper
|
||||
<h1 class="your-demo-title">这是标题</h1>
|
||||
<p class="your-demo-paragraph">这是段落</p>
|
||||
|
||||
<style>
|
||||
.your-demo-title {
|
||||
color: red;
|
||||
}
|
||||
.your-demo-paragraph {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
:::
|
||||
```
|
||||
|
||||
**输出:**
|
||||
::: demo-wrapper
|
||||
|
||||
<h1 class="your-demo-title">这是标题</h1>
|
||||
<p class="your-demo-paragraph">这是段落</p>
|
||||
|
||||
<style>
|
||||
.your-demo-title {
|
||||
color: red !important;
|
||||
}
|
||||
.your-demo-paragraph {
|
||||
color: blue !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
:::
|
||||
|
||||
## can I use
|
||||
|
||||
此功能默认不启用,你可以在配置文件中启用它。
|
||||
|
||||
@ -14,9 +14,23 @@ permalink: /guide/markdown/experiment/
|
||||
该功能由 [shiki](https://shiki.style/) 和 [@shikijs/twoslash](https://shiki.style/packages/twoslash) 提供支持,
|
||||
并整合在 [@vuepress-plume/plugin-shikiji](https://github.com/pengzhanbo/vuepress-theme-plume/tree/main/plugins/plugin-shikiji) 中。
|
||||
|
||||
> [!important]
|
||||
> 从 `vuepress@2.0.0-rc.12` 开始,不再需要对 `@vuepress/markdown` 进行额外的 hack 操作,
|
||||
> 因此,现在你可以安全的使用这项功能了!
|
||||
:::important
|
||||
从 `vuepress@2.0.0-rc.12` 开始,不再需要对 `@vuepress/markdown` 进行额外的 hack 操作,
|
||||
因此,现在你可以安全的使用这项功能了!
|
||||
:::
|
||||
|
||||
::: warning
|
||||
`twoslash` 是一个比较耗时的功能,由于它需要对代码进行类型编译,如果代码引入的包 比较大,会花费较长时间。
|
||||
|
||||
特别的,由于 vuepress 启动时,会预编译所有的 markdown 文件,因此它会直接影响 vuepress 的启动时间,
|
||||
如果 包含了比较多的 `twoslash` 代码块,这可能会使 vuepress 启动时间变得很长。
|
||||
|
||||
比如,在未使用 `twoslash` 时,vuepress 的启动时间区间大约在 `300ms ~ 1000ms` 之间,而在使用 `twoslash` 后,
|
||||
可能某一个 `twoslash` 的代码块编译耗时就需要额外再等待 `500ms` 以上。
|
||||
|
||||
但不必担心 markdown 文件热更新时的编译耗时,主题针对 代码高亮编译 耗时做了优化,即使 单个 markdown 文件
|
||||
中包含多个 代码块,主题也仅会对 **有变更的代码块** 进行编译,因此热更新的速度依然非常快。
|
||||
:::
|
||||
|
||||
### 概述
|
||||
|
||||
|
||||
@ -8,11 +8,33 @@ permalink: /guide/code/intro/
|
||||
|
||||
## 概述
|
||||
|
||||
主题 使用 [Shiki](https://github.com/shikijs/shiki) 在 Markdown 代码块实现语法高亮。
|
||||
主题 使用 [Shiki](https://shiki.style/) 在 Markdown 代码块实现语法高亮。
|
||||
|
||||
:::info
|
||||
主题默认 加载了 [Shiki](https://shiki.style/) 支持的超过 190+ 的 语言,这可能导致 在启用 vuepress 服务时,
|
||||
需要多等待 **300ms ~ 600ms** 左右的时间来加载所有的 语言。
|
||||
|
||||
因此,如果比较在意 vuepress 启动时间,建议修改配置为仅加载 您所需要的 语言。
|
||||
|
||||
示例:
|
||||
|
||||
```ts
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
plugins: {
|
||||
shiki: {
|
||||
languages: ['javascript', 'typescript', 'vue', 'bash', 'sh'], // [!code highlight]
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 语言
|
||||
|
||||
[Shiki](https://github.com/shikijs/shiki) 支持 超过 190+ 种语言,
|
||||
[Shiki](https://shiki.style/) 支持 超过 190+ 种语言,
|
||||
你可以在 [languages](https://shiki.style/languages) 查看所有支持的语言列表。
|
||||
|
||||
你可以通过以下语法为你使用的 语言所编写的代码 实现高亮效果:
|
||||
@ -40,7 +62,7 @@ console.log(a)
|
||||
|
||||
## 高亮主题
|
||||
|
||||
[Shiki](https://github.com/shikijs/shiki) 支持 超过 40+ 种高亮主题。
|
||||
[Shiki](https://shiki.style/) 支持 超过 40+ 种高亮主题。
|
||||
|
||||
你可以在 [Themes](https://shiki.style/themes) 找到所有支持的主题列表,根据个人的喜欢自定义
|
||||
高亮主题。
|
||||
@ -63,7 +85,7 @@ export default defineUserConfig({
|
||||
|
||||
## 更多支持
|
||||
|
||||
得益于 [Shiki](https://github.com/shikijs/shiki) 的强大能力,Theme Plume 还为 代码块
|
||||
得益于 [Shiki](https://shiki.style/) 的强大能力,Theme Plume 还为 代码块
|
||||
提供了 更多的 [特性支持](/guide/code/features/),它们让 代码块具备更强的表达能力。
|
||||
|
||||
同时,为了方便 更好的 完成 代码演示,Theme Plume 还提供了嵌入 [CodePen](/guide/code/code-pen/),
|
||||
|
||||
@ -9,11 +9,15 @@ permalink: /guide/features/component/
|
||||
|
||||
## 概述
|
||||
|
||||
VuePress 支持在 Markdown 文件中使用 组件。
|
||||
|
||||
主题提供了一些具有通用性的组件,可以在任何地方使用。
|
||||
|
||||
## `<Badge />` <Badge type="tip" text="badge" />
|
||||
## 徽章 <Badge type="tip" text="badge" />
|
||||
|
||||
标签,用于在页面中增加一些提示信息。
|
||||
使用 `<Badge>` 组件来显示 行内信息,如状态或标签。
|
||||
|
||||
将你想显示的内容传递给 `<Badge>` 组件的 `text` 属性。
|
||||
|
||||
### Props
|
||||
|
||||
@ -38,37 +42,37 @@ permalink: /guide/features/component/
|
||||
- VuePress - <Badge type="warning" text="v2" />
|
||||
- VuePress - <Badge type="danger" text="v2" />
|
||||
|
||||
## `<Iconify />`
|
||||
## 图标
|
||||
|
||||
支持 iconify 所有图标,支持通过 icon name 加载图标。
|
||||
支持 iconify 所有图标,通过 icon name 加载图标。
|
||||
|
||||
可在 [iconify search](https://icon-sets.iconify.design/) 搜索图标使用。
|
||||
|
||||
### Props
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| ----- | ------ | ---------------- | ---------- |
|
||||
| name | string | `''` | icon name |
|
||||
| color | string | `'currentcolor'` | icon color |
|
||||
| size | string | `'1em'` | icon size |
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| ----- | ------ | ---------------- | -------- |
|
||||
| name | string | `''` | 图标名 |
|
||||
| color | string | `'currentcolor'` | 图标颜色 |
|
||||
| size | string | `'1em'` | 图标大小 |
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
- home - <Iconify name="material-symbols:home" color="currentColor" size="1em" />
|
||||
- vscode - <Iconify name="skill-icons:vscode-dark" size="2em" />
|
||||
- twitter - <Iconify name="skill-icons:twitter" size="2em" />
|
||||
- home - <Icon name="material-symbols:home" color="currentColor" size="1em" />
|
||||
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
|
||||
- twitter - <Icon name="skill-icons:twitter" size="2em" />
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
- home - <Iconify name="material-symbols:home" color="currentColor" size="1em" />
|
||||
- vscode - <Iconify name="skill-icons:vscode-dark" size="2em" />
|
||||
- twitter - <Iconify name="skill-icons:twitter" size="2em" />
|
||||
- home - <Icon name="material-symbols:home" color="currentColor" size="1em" />
|
||||
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
|
||||
- twitter - <Icon name="skill-icons:twitter" size="2em" />
|
||||
|
||||
## `<Plot />`
|
||||
## “隐秘”文本
|
||||
|
||||
[“隐秘”文本](/guide/markdown/advance/#隐秘-文本) 组件。
|
||||
使用 `<Plot>` 组件显示 [“隐秘”文本](/guide/markdown/advance/#隐秘-文本) ,能够更灵活的控制行为。
|
||||
|
||||
该组件默认不启用,你需要在 theme 配置中启用。
|
||||
|
||||
@ -97,23 +101,139 @@ export default defineUserConfig({
|
||||
| mask | `string \| { light: string, dark: string }` | `#000` | 遮罩颜色 |
|
||||
| color | `string \| { light: string, dark: string }` | `#fff` | 文本颜色 |
|
||||
|
||||
输入:
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
- 鼠标悬停 - <Plot>悬停时可见</Plot>
|
||||
- 点击 - <Plot trigger="click">点击时可见</Plot>
|
||||
```
|
||||
|
||||
输出:
|
||||
**输出:**
|
||||
|
||||
- 鼠标悬停 - <Plot>悬停时可见</Plot>
|
||||
- 点击 - <Plot trigger="click">点击时可见</Plot>
|
||||
|
||||
## `<HomeBox />`
|
||||
## 卡片
|
||||
|
||||
- 类型:`Component`
|
||||
使用 `<Card>` 组件在页面中显示卡片。
|
||||
|
||||
自定义首页时,提供给 区域 的 包装容器。
|
||||
也可以使用 markdown [卡片容器](/guide/markdown/advance/#卡片) 语法,替代 `<Card>` 组件。
|
||||
|
||||
### Props
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| ----- | --------------------------- | ------ | ---------------------------------------------------------------- |
|
||||
| title | `string` | `''` | 标题 |
|
||||
| icon | `string \| { svg: string }` | `''` | 显示在标题左侧的图标,支持 iconify 所有图标,也可以使用 图片链接 |
|
||||
|
||||
### 插槽
|
||||
|
||||
| 名称 | 说明 |
|
||||
| ------- | ---------- |
|
||||
| default | 卡片内容 |
|
||||
| title | 自定义标题 |
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
<Card title="卡片标题" icon="twemoji:astonished-face">
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<template #title>
|
||||
<p style="color: red">卡片标题</p>
|
||||
</template>
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
<Card title="卡片标题" icon="twemoji:astonished-face">
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<template #title>
|
||||
<p style="color: red;margin:0">卡片标题</p>
|
||||
</template>
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
|
||||
## 链接卡片
|
||||
|
||||
使用 `<LinkCard>` 组件在页面中显示链接卡片。
|
||||
|
||||
### Props
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| ----------- | --------------------------- | ------ | ---------------------------------------------------------------- |
|
||||
| title | `string` | `''` | 标题 |
|
||||
| icon | `string \| { svg: string }` | `''` | 显示在标题左侧的图标,支持 iconify 所有图标,也可以使用 图片链接 |
|
||||
| href | `string` | `''` | 链接 |
|
||||
| description | `string` | `''` | 详情 |
|
||||
|
||||
### 插槽
|
||||
|
||||
| 名称 | 说明 |
|
||||
| ------- | ------------ |
|
||||
| default | 卡片详情内容 |
|
||||
| title | 自定义标题 |
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
<LinkCard title="卡片标题" href="/" description="这里是卡片内容" />
|
||||
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
<LinkCard title="卡片标题" href="/" description="这里是卡片内容" />
|
||||
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
|
||||
|
||||
## 卡片排列容器
|
||||
|
||||
当你需要将多个卡片排列,可以使用 `<CardGrid>` 组件。在空间足够时,多个卡片会自动排列。
|
||||
|
||||
**输入:**
|
||||
|
||||
```md
|
||||
<CardGrid>
|
||||
<Card title="卡片标题" icon="twemoji:astonished-face">
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
<Card title="卡片标题" icon="twemoji:astonished-face">
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<LinkCard title="卡片标题" href="/" />
|
||||
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
|
||||
</CardGrid>
|
||||
```
|
||||
|
||||
**输出:**
|
||||
|
||||
<CardGrid>
|
||||
<Card title="卡片标题" icon="twemoji:astonished-face">
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
<Card title="卡片标题" icon="twemoji:astonished-face">
|
||||
这里是卡片内容。
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
<CardGrid>
|
||||
<LinkCard title="链接卡片" href="/" />
|
||||
<LinkCard icon="twemoji:astonished-face" title="链接卡片" href="/" />
|
||||
</CardGrid>
|
||||
|
||||
## 首页布局容器
|
||||
|
||||
自定义首页时,使用 `<HomeBox>`提供给 区域 的 包装容器。
|
||||
|
||||
### Props
|
||||
|
||||
|
||||
@ -21,195 +21,185 @@ permalink: /guide/quick-start/
|
||||
|
||||
使用本主题,你需要首先新建一个项目,并安装`vuepress@next`以及本主题
|
||||
|
||||
### 步骤 1
|
||||
:::: steps
|
||||
|
||||
**创建一个新文件夹,并进入目录:**
|
||||
- ### 新建文件夹并进入目录
|
||||
|
||||
``` sh
|
||||
mkdir my-blog
|
||||
cd my-blog
|
||||
```
|
||||
``` sh
|
||||
mkdir my-blog
|
||||
cd my-blog
|
||||
```
|
||||
|
||||
### 步骤 2
|
||||
- ### 初始化项目
|
||||
|
||||
**初始化项目:**
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
``` sh
|
||||
git init
|
||||
pnpm init
|
||||
```
|
||||
|
||||
``` sh
|
||||
git init
|
||||
pnpm init
|
||||
```
|
||||
@tab yarn
|
||||
|
||||
@tab yarn
|
||||
``` sh
|
||||
git init
|
||||
yarn init
|
||||
```
|
||||
|
||||
``` sh
|
||||
git init
|
||||
yarn init
|
||||
```
|
||||
@tab npm
|
||||
|
||||
@tab npm
|
||||
``` sh
|
||||
git init
|
||||
npm init
|
||||
```
|
||||
|
||||
``` sh
|
||||
git init
|
||||
npm init
|
||||
```
|
||||
:::
|
||||
|
||||
:::
|
||||
- ### 安装相关依赖
|
||||
|
||||
### 步骤 3
|
||||
安装 `vuepress@next` 和 `vuepress-theme-plume` 作为本地依赖。
|
||||
|
||||
**安装相关依赖:**
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
|
||||
安装 `vuepress@next` 和 `vuepress-theme-plume` 作为本地依赖。
|
||||
```sh
|
||||
# 安装 vuepress
|
||||
pnpm add -D vuepress@next vue
|
||||
# 安装 主题和打包工具
|
||||
pnpm add -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
@tab yarn
|
||||
|
||||
```sh
|
||||
# 安装 vuepress
|
||||
pnpm add -D vuepress@next vue
|
||||
# 安装 主题和打包工具
|
||||
pnpm add -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
``` sh
|
||||
# 安装 vuepress
|
||||
yarn add -D vuepress@next
|
||||
# 安装 主题和打包工具
|
||||
yarn add -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
@tab npm
|
||||
|
||||
``` sh
|
||||
# 安装 vuepress
|
||||
yarn add -D vuepress@next
|
||||
# 安装 主题和打包工具
|
||||
yarn add -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
``` sh
|
||||
# 安装 vuepress
|
||||
npm i -D vuepress@next
|
||||
# 安装 主题和打包工具
|
||||
npm i -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
|
||||
@tab npm
|
||||
:::
|
||||
|
||||
``` sh
|
||||
# 安装 vuepress
|
||||
npm i -D vuepress@next
|
||||
# 安装 主题和打包工具
|
||||
npm i -D vuepress-theme-plume @vuepress/bundler-vite@next
|
||||
```
|
||||
:::warning
|
||||
主题当前版本 已适配至 `vuepress@2.0.0-rc.13`,你应该安装这个版本的 VuePress。
|
||||
高于或低于这个版本,可能会存在潜在的兼容性问题。
|
||||
:::
|
||||
|
||||
:::
|
||||
- ### 在 `package.json` 中添加 `script`
|
||||
|
||||
:::warning
|
||||
主题当前版本 已适配至 `vuepress@2.0.0-rc.13`,你应该安装这个版本的 VuePress。
|
||||
高于或低于这个版本,可能会存在潜在的兼容性问题。
|
||||
:::
|
||||
::: code-tabs
|
||||
@tab package.json
|
||||
|
||||
### 步骤 4
|
||||
|
||||
**在 `package.json` 中添加 `script`**
|
||||
|
||||
::: code-tabs
|
||||
@tab package.json
|
||||
|
||||
``` json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "vuepress dev docs",
|
||||
"build": "vuepress build docs"
|
||||
``` json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "vuepress dev docs",
|
||||
"build": "vuepress build docs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
:::
|
||||
:::
|
||||
|
||||
`vuepress` 默认将文档源码放在 `docs` 目录下。
|
||||
`vuepress` 默认将文档源码放在 `docs` 目录下。
|
||||
|
||||
### 步骤 5
|
||||
- ### 将默认的临时目录和缓存目录添加到`.gitignore` 文件中
|
||||
|
||||
**将默认的临时目录和缓存目录添加到`.gitignore` 文件中**
|
||||
::: code-tabs
|
||||
@tab .gitignore
|
||||
|
||||
::: code-tabs
|
||||
@tab .gitignore
|
||||
``` txt
|
||||
node_modules
|
||||
.temp
|
||||
.cache
|
||||
```
|
||||
|
||||
``` txt
|
||||
node_modules
|
||||
.temp
|
||||
.cache
|
||||
```
|
||||
@tab sh
|
||||
|
||||
@tab sh
|
||||
``` sh
|
||||
echo 'node_modules' >> .gitignore
|
||||
echo '.temp' >> .gitignore
|
||||
echo '.cache' >> .gitignore
|
||||
```
|
||||
|
||||
``` sh
|
||||
echo 'node_modules' >> .gitignore
|
||||
echo '.temp' >> .gitignore
|
||||
echo '.cache' >> .gitignore
|
||||
```
|
||||
:::
|
||||
|
||||
:::
|
||||
- ### 在 `docs/.vuepress/config.{js,ts}` 中配置主题
|
||||
|
||||
### 步骤 6
|
||||
::: code-tabs
|
||||
@tab docs/.vuepress/config.js
|
||||
|
||||
**在 `docs/.vuepress/config.{js,ts}` 中配置主题**
|
||||
``` ts
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { viteBundler } from '@vuepress/bundler-vite'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
::: code-tabs
|
||||
@tab docs/.vuepress/config.js
|
||||
export default defineUserConfig({
|
||||
// 请不要忘记设置默认语言
|
||||
lang: 'zh-CN',
|
||||
theme: plumeTheme({
|
||||
// more...
|
||||
}),
|
||||
bundler: viteBundler(),
|
||||
})
|
||||
```
|
||||
|
||||
``` ts
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { viteBundler } from '@vuepress/bundler-vite'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
:::
|
||||
|
||||
export default defineUserConfig({
|
||||
// 请不要忘记设置默认语言
|
||||
lang: 'zh-CN',
|
||||
theme: plumeTheme({
|
||||
// more...
|
||||
}),
|
||||
bundler: viteBundler(),
|
||||
})
|
||||
```
|
||||
:::warning
|
||||
无论是否需要使用 **多语言** ,你都应该为 VuePress 配置 正确 `lang` 选项值。
|
||||
主题需要根据 `lang` 选项来确定语言环境文本。
|
||||
:::
|
||||
|
||||
:::
|
||||
- ### 在 `docs` 目录下新建 `README.md` 文件
|
||||
|
||||
:::warning
|
||||
无论是否需要使用 **多语言** ,你都应该为 VuePress 配置 正确 `lang` 选项值。
|
||||
主题需要根据 `lang` 选项来确定语言环境文本。
|
||||
:::
|
||||
声明首页配置。
|
||||
::: code-tabs
|
||||
@tab README.md
|
||||
|
||||
### 步骤 7
|
||||
``` md
|
||||
---
|
||||
home: true
|
||||
---
|
||||
```
|
||||
|
||||
**在 `docs` 目录下新建 `README.md` 文件**
|
||||
:::
|
||||
|
||||
声明首页配置。
|
||||
::: code-tabs
|
||||
@tab README.md
|
||||
- ### 在本地服务器启动你的文档站点
|
||||
|
||||
``` md
|
||||
---
|
||||
home: true
|
||||
---
|
||||
```
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
|
||||
:::
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### 步骤 8
|
||||
@tab yarn
|
||||
|
||||
**在本地服务器启动你的文档站点:**
|
||||
``` sh
|
||||
yarn dev
|
||||
```
|
||||
|
||||
::: code-tabs
|
||||
@tab pnpm
|
||||
@tab npm
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
``` sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
@tab yarn
|
||||
:::
|
||||
|
||||
``` sh
|
||||
yarn dev
|
||||
```
|
||||
Vuepress 会在 [http://localhost:8080](http://localhost:8080) 。启动一个热重载的开发服务器。当你修改你的 Markdown 文件时,浏览器中的内容也会自动更新。
|
||||
|
||||
@tab npm
|
||||
- ### 完成
|
||||
|
||||
``` sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Vuepress 会在 [http://localhost:8080](http://localhost:8080) 。启动一个热重载的开发服务器。当你修改你的 Markdown 文件时,浏览器中的内容也会自动更新。
|
||||
::::
|
||||
|
||||
@ -112,7 +112,7 @@ home: true
|
||||
config:
|
||||
-
|
||||
type: banner
|
||||
banner: https://file.mo7.cc/api/public/bz
|
||||
banner: https://api.pengzhanbo.cn/wallpaper/bing
|
||||
bannerMask:
|
||||
light: 0.1
|
||||
dark: 0.3
|
||||
|
||||
@ -38,6 +38,7 @@ export async function highlight(
|
||||
defaultHighlightLang: defaultLang = '',
|
||||
codeTransformers: userTransformers = [],
|
||||
whitespace = false,
|
||||
languages = Object.keys(bundledLanguages),
|
||||
} = options
|
||||
|
||||
const highlighter = await getHighlighter({
|
||||
@ -45,7 +46,7 @@ export async function highlight(
|
||||
typeof theme === 'object' && 'light' in theme && 'dark' in theme
|
||||
? [theme.light, theme.dark]
|
||||
: [theme],
|
||||
langs: [...Object.keys(bundledLanguages), ...(options.languages || [])],
|
||||
langs: languages,
|
||||
langAlias: options.languageAlias,
|
||||
})
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import VPShortPostList from '@theme/Blog/VPShortPostList.vue'
|
||||
import { useArchives, useBlogExtract } from '../../composables/blog.js'
|
||||
import { useBlogExtract } from '../../composables/blog-extract.js'
|
||||
import { useArchives } from '../../composables/blog-archives.js'
|
||||
|
||||
const { archives: archivesLink } = useBlogExtract()
|
||||
const { archives } = useArchives()
|
||||
|
||||
@ -4,7 +4,7 @@ import { computed, ref, watch } from 'vue'
|
||||
import { useRoute, withBase } from 'vuepress/client'
|
||||
import { isLinkHttp } from 'vuepress/shared'
|
||||
import VPLink from '@theme/VPLink.vue'
|
||||
import { useBlogExtract } from '../../composables/blog.js'
|
||||
import { useBlogExtract } from '../../composables/blog-extract.js'
|
||||
import { useData } from '../../composables/data.js'
|
||||
import { inBrowser } from '../../utils/index.js'
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from 'vuepress/client'
|
||||
import VPLink from '@theme/VPLink.vue'
|
||||
import { useBlogExtract } from '../../composables/blog.js'
|
||||
import { useBlogExtract } from '../../composables/blog-extract.js'
|
||||
|
||||
const props = defineProps<{
|
||||
isLocal?: boolean
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import VPShortPostList from '@theme/Blog/VPShortPostList.vue'
|
||||
import { useBlogExtract, useTags } from '../../composables/blog.js'
|
||||
import { useBlogExtract } from '../../composables/blog-extract.js'
|
||||
import { useTags } from '../../composables/blog-tags.js'
|
||||
|
||||
const { tags, currentTag, postList, handleTagClick } = useTags()
|
||||
const { tags: tagsLink } = useBlogExtract()
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import VPTransitionDrop from '@theme/VPTransitionDrop.vue'
|
||||
import VPPostItem from '@theme/Blog/VPPostItem.vue'
|
||||
import VPPagination from '@theme/Blog/VPPagination.vue'
|
||||
import { usePostListControl } from '../../composables/blog.js'
|
||||
import { usePostListControl } from '../../composables/blog-post-list.js'
|
||||
|
||||
const {
|
||||
postList,
|
||||
|
||||
@ -191,14 +191,23 @@ useHomeHeroTintPlate(
|
||||
height: calc(100% + var(--vp-footer-height, 0px));
|
||||
}
|
||||
|
||||
@property --vp-home-hero-bg-filter {
|
||||
inherits: false;
|
||||
initial-value: #fff;
|
||||
syntax: "<color>";
|
||||
}
|
||||
|
||||
.bg-filter::after {
|
||||
--vp-home-hero-bg-filter: var(--vp-c-bg);
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background: linear-gradient(to bottom, var(--vp-c-bg) 0, transparent 40%, transparent 60%, var(--vp-c-bg) 140%);
|
||||
background: linear-gradient(to bottom, var(--vp-home-hero-bg-filter) 0, transparent 45%, transparent 55%, var(--vp-home-hero-bg-filter) 140%);
|
||||
transition: --vp-home-hero-bg-filter var(--t-color);
|
||||
}
|
||||
|
||||
.bg-filter canvas {
|
||||
|
||||
@ -37,7 +37,7 @@ const link = computed(() => {
|
||||
.vp__img {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
margin: 0.3em;
|
||||
margin: 0 0.3em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
74
theme/src/client/components/global/VPCard.vue
Normal file
74
theme/src/client/components/global/VPCard.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import VPIcon from '@theme/VPIcon.vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string
|
||||
icon?: string | { svg: string }
|
||||
}>()
|
||||
|
||||
const icon = computed<string | { svg: string } | undefined>(() => {
|
||||
if (props.icon?.[0] === '{') {
|
||||
try {
|
||||
return JSON.parse(icon) as { svg: string }
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
return props.icon
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article class="vp-card-wrapper">
|
||||
<slot name="title">
|
||||
<p v-if="title || icon" class="title">
|
||||
<VPIcon v-if="icon" :name="icon" />
|
||||
<span v-if="title" v-html="title" />
|
||||
</p>
|
||||
</slot>
|
||||
<div class="body">
|
||||
<slot />
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-card-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 16px 20px;
|
||||
margin: 16px 0;
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
transition: border-color var(--t-color), box-shadow var(--t-color);
|
||||
}
|
||||
|
||||
.vp-card-wrapper:hover {
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.vp-card-wrapper :deep(.vp-iconify) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vp-card-wrapper .title {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-card-wrapper .body :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.vp-card-wrapper .body :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
23
theme/src/client/components/global/VPCardGrid.vue
Normal file
23
theme/src/client/components/global/VPCardGrid.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div class="vp-card-grid">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-card-grid {
|
||||
display: grid;
|
||||
gap: 16px 20px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.vp-card-grid > * {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.vp-card-grid {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
86
theme/src/client/components/global/VPLinkCard.vue
Normal file
86
theme/src/client/components/global/VPLinkCard.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import VPLink from '@theme/VPLink.vue'
|
||||
import VPIcon from '@theme/VPIcon.vue'
|
||||
|
||||
defineProps<{
|
||||
href: string
|
||||
title: string
|
||||
icon?: string | { svg: string }
|
||||
description?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-link-card">
|
||||
<span class="body">
|
||||
<VPLink :href="href" no-icon class="link">
|
||||
<slot name="title">
|
||||
<VPIcon v-if="icon" :name="icon" />
|
||||
<span v-if="title" v-html="title" />
|
||||
</slot>
|
||||
</VPLink>
|
||||
<slot>
|
||||
<p v-if="description" v-html="description" />
|
||||
</slot>
|
||||
</span>
|
||||
<span class="vpi-arrow-right" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-link-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
padding: 16px 20px;
|
||||
margin: 16px 0;
|
||||
background-color: transparent;
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
transition: border-color var(--t-color), box-shadow var(--t-color), background-color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-link-card:hover {
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border-color: var(--vp-c-brand-2);
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.vp-link-card .body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.vp-link-card .body > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.vp-link-card .link {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-text-1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.vp-link-card .link::before {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.vp-link-card .link :deep(.vp-iconify) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.vpi-arrow-right {
|
||||
margin-top: 2px;
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
||||
31
theme/src/client/composables/blog-archives.ts
Normal file
31
theme/src/client/composables/blog-archives.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { computed } from 'vue'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { useLocalePostList } from './blog-post-list.js'
|
||||
|
||||
export type ShortPostItem = Pick<PlumeThemeBlogPostItem, 'title' | 'path' | 'createTime'>
|
||||
|
||||
export function useArchives() {
|
||||
const list = useLocalePostList()
|
||||
const archives = computed(() => {
|
||||
const archives: { label: string, list: ShortPostItem[] }[] = []
|
||||
|
||||
list.value.forEach((item) => {
|
||||
const createTime = item.createTime.split(' ')[0]
|
||||
const year = createTime.split('/')[0]
|
||||
let current = archives.find(archive => archive.label === year)
|
||||
if (!current) {
|
||||
current = { label: year, list: [] }
|
||||
archives.push(current)
|
||||
}
|
||||
current.list.push({
|
||||
title: item.title,
|
||||
path: item.path,
|
||||
createTime: createTime.slice(year.length + 1).replace(/\//g, '-'),
|
||||
})
|
||||
})
|
||||
|
||||
return archives
|
||||
})
|
||||
|
||||
return { archives }
|
||||
}
|
||||
41
theme/src/client/composables/blog-extract.ts
Normal file
41
theme/src/client/composables/blog-extract.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { useRouteLocale } from 'vuepress/client'
|
||||
import { computed } from 'vue'
|
||||
import type { PresetLocale } from '../../shared/index.js'
|
||||
import { useLocalePostList } from './blog-post-list.js'
|
||||
import { useTags } from './blog-tags.js'
|
||||
import { useData } from './data.js'
|
||||
import { useLocaleLink } from './locale.js'
|
||||
|
||||
declare const __PLUME_PRESET_LOCALE__: Record<string, PresetLocale>
|
||||
|
||||
const presetLocales = __PLUME_PRESET_LOCALE__
|
||||
|
||||
export function useBlogExtract() {
|
||||
const { theme } = useData()
|
||||
const locale = useRouteLocale()
|
||||
const postList = useLocalePostList()
|
||||
const { tags: tagsList } = useTags()
|
||||
const blog = computed(() => theme.value.blog || {})
|
||||
|
||||
const hasBlogExtract = computed(() => blog.value.archives !== false || blog.value.tags !== false)
|
||||
const tagsLink = useLocaleLink(blog.value.tagsLink || 'blog/tags/')
|
||||
const archiveLink = useLocaleLink(blog.value.archivesLink || 'blog/archives/')
|
||||
|
||||
const tags = computed(() => ({
|
||||
link: tagsLink.value,
|
||||
text: presetLocales[locale.value]?.tag || presetLocales['/'].tag,
|
||||
total: tagsList.value.length,
|
||||
}))
|
||||
|
||||
const archives = computed(() => ({
|
||||
link: archiveLink.value,
|
||||
text: presetLocales[locale.value]?.archive || presetLocales['/'].archive,
|
||||
total: postList.value.length,
|
||||
}))
|
||||
|
||||
return {
|
||||
hasBlogExtract,
|
||||
tags,
|
||||
archives,
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,8 @@ import { useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
|
||||
import { computed } from 'vue'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { useData, useLocaleLink, useRouteQuery } from '../composables/index.js'
|
||||
import { toArray } from '../utils/index.js'
|
||||
import { useTagColors } from './tag-colors.js'
|
||||
import { useData } from './data.js'
|
||||
import { useRouteQuery } from './route-query.js'
|
||||
|
||||
const DEFAULT_PER_PAGE = 10
|
||||
|
||||
@ -133,121 +132,3 @@ export function usePostListControl() {
|
||||
changePage,
|
||||
}
|
||||
}
|
||||
|
||||
const extractLocales: Record<string, { tags: string, archives: string }> = {
|
||||
'zh-CN': { tags: '标签', archives: '归档' },
|
||||
'en': { tags: 'Tags', archives: 'Archives' },
|
||||
'zh-TW': { tags: '標籤', archives: '歸檔' },
|
||||
}
|
||||
|
||||
export function useBlogExtract() {
|
||||
const { theme } = useData()
|
||||
const locale = usePageLang()
|
||||
const postList = useLocalePostList()
|
||||
const { tags: tagsList } = useTags()
|
||||
const blog = computed(() => theme.value.blog || {})
|
||||
|
||||
const hasBlogExtract = computed(() => blog.value.archives !== false || blog.value.tags !== false)
|
||||
const tagsLink = useLocaleLink(blog.value.tagsLink || 'blog/tags/')
|
||||
const archiveLink = useLocaleLink(blog.value.archivesLink || 'blog/archives/')
|
||||
|
||||
const tags = computed(() => ({
|
||||
link: tagsLink.value,
|
||||
text: extractLocales[locale.value]?.tags || extractLocales.en.tags,
|
||||
total: tagsList.value.length,
|
||||
}))
|
||||
|
||||
const archives = computed(() => ({
|
||||
link: archiveLink.value,
|
||||
text: extractLocales[locale.value]?.archives || extractLocales.en.archives,
|
||||
total: postList.value.length,
|
||||
}))
|
||||
|
||||
return {
|
||||
hasBlogExtract,
|
||||
tags,
|
||||
archives,
|
||||
}
|
||||
}
|
||||
|
||||
export type ShortPostItem = Pick<PlumeThemeBlogPostItem, 'title' | 'path' | 'createTime'>
|
||||
|
||||
export function useTags() {
|
||||
const list = useLocalePostList()
|
||||
|
||||
const colors = useTagColors()
|
||||
|
||||
const tags = computed(() => {
|
||||
const tagMap: Record<string, number> = {}
|
||||
list.value.forEach((item) => {
|
||||
if (item.tags) {
|
||||
toArray(item.tags).forEach((tag) => {
|
||||
if (tagMap[tag])
|
||||
tagMap[tag] += 1
|
||||
else
|
||||
tagMap[tag] = 1
|
||||
})
|
||||
}
|
||||
})
|
||||
return Object.keys(tagMap).map(tag => ({
|
||||
name: tag,
|
||||
count: tagMap[tag] > 99 ? '99+' : tagMap[tag],
|
||||
className: `vp-tag-${colors.value[tag]}`,
|
||||
}))
|
||||
})
|
||||
|
||||
const currentTag = useRouteQuery<string>('tag')
|
||||
|
||||
const postList = computed<ShortPostItem[]>(() => {
|
||||
if (!currentTag.value)
|
||||
return []
|
||||
|
||||
return list.value.filter((item) => {
|
||||
if (item.tags)
|
||||
return toArray(item.tags).includes(currentTag.value)
|
||||
|
||||
return false
|
||||
}).map(item => ({
|
||||
title: item.title,
|
||||
path: item.path,
|
||||
createTime: item.createTime.split(' ')[0].replace(/\//g, '-'),
|
||||
}))
|
||||
})
|
||||
|
||||
const handleTagClick = (tag: string) => {
|
||||
currentTag.value = tag
|
||||
}
|
||||
|
||||
return {
|
||||
tags,
|
||||
currentTag,
|
||||
postList,
|
||||
handleTagClick,
|
||||
}
|
||||
}
|
||||
|
||||
export function useArchives() {
|
||||
const list = useLocalePostList()
|
||||
const archives = computed(() => {
|
||||
const archives: { label: string, list: ShortPostItem[] }[] = []
|
||||
|
||||
list.value.forEach((item) => {
|
||||
const createTime = item.createTime.split(' ')[0]
|
||||
const year = createTime.split('/')[0]
|
||||
let current = archives.find(archive => archive.label === year)
|
||||
if (!current) {
|
||||
current = { label: year, list: [] }
|
||||
archives.push(current)
|
||||
}
|
||||
current.list.push({
|
||||
title: item.title,
|
||||
path: item.path,
|
||||
createTime: createTime.slice(year.length + 1).replace(/\//g, '-'),
|
||||
})
|
||||
})
|
||||
|
||||
return archives
|
||||
})
|
||||
|
||||
return { archives }
|
||||
}
|
||||
62
theme/src/client/composables/blog-tags.ts
Normal file
62
theme/src/client/composables/blog-tags.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { computed } from 'vue'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { toArray } from '../utils/index.js'
|
||||
import { useTagColors } from './tag-colors.js'
|
||||
import { useLocalePostList } from './blog-post-list.js'
|
||||
import { useRouteQuery } from './route-query.js'
|
||||
|
||||
type ShortPostItem = Pick<PlumeThemeBlogPostItem, 'title' | 'path' | 'createTime'>
|
||||
|
||||
export function useTags() {
|
||||
const list = useLocalePostList()
|
||||
|
||||
const colors = useTagColors()
|
||||
|
||||
const tags = computed(() => {
|
||||
const tagMap: Record<string, number> = {}
|
||||
list.value.forEach((item) => {
|
||||
if (item.tags) {
|
||||
toArray(item.tags).forEach((tag) => {
|
||||
if (tagMap[tag])
|
||||
tagMap[tag] += 1
|
||||
else
|
||||
tagMap[tag] = 1
|
||||
})
|
||||
}
|
||||
})
|
||||
return Object.keys(tagMap).map(tag => ({
|
||||
name: tag,
|
||||
count: tagMap[tag] > 99 ? '99+' : tagMap[tag],
|
||||
className: `vp-tag-${colors.value[tag]}`,
|
||||
}))
|
||||
})
|
||||
|
||||
const currentTag = useRouteQuery<string>('tag')
|
||||
|
||||
const postList = computed<ShortPostItem[]>(() => {
|
||||
if (!currentTag.value)
|
||||
return []
|
||||
|
||||
return list.value.filter((item) => {
|
||||
if (item.tags)
|
||||
return toArray(item.tags).includes(currentTag.value)
|
||||
|
||||
return false
|
||||
}).map(item => ({
|
||||
title: item.title,
|
||||
path: item.path,
|
||||
createTime: item.createTime.split(' ')[0].replace(/\//g, '-'),
|
||||
}))
|
||||
})
|
||||
|
||||
const handleTagClick = (tag: string) => {
|
||||
currentTag.value = tag
|
||||
}
|
||||
|
||||
return {
|
||||
tags,
|
||||
currentTag,
|
||||
postList,
|
||||
handleTagClick,
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ export * from './theme-data.js'
|
||||
export * from './dark-mode.js'
|
||||
export * from './data.js'
|
||||
export * from './scroll-promise.js'
|
||||
export * from './scroll-behavior.js'
|
||||
|
||||
export * from './sidebar.js'
|
||||
export * from './aside.js'
|
||||
@ -12,7 +13,10 @@ export * from './edit-link.js'
|
||||
export * from './latest-updated.js'
|
||||
export * from './contributors.js'
|
||||
|
||||
export * from './blog.js'
|
||||
export * from './blog-post-list.js'
|
||||
export * from './blog-extract.js'
|
||||
export * from './blog-tags.js'
|
||||
export * from './blog-archives.js'
|
||||
export * from './tag-colors.js'
|
||||
|
||||
export * from './locale.js'
|
||||
|
||||
25
theme/src/client/composables/scroll-behavior.ts
Normal file
25
theme/src/client/composables/scroll-behavior.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import type { Router } from 'vuepress/client'
|
||||
import { nextTick } from 'vue'
|
||||
import { inBrowser } from '../utils/index.js'
|
||||
import { useScrollPromise } from './scroll-promise.js'
|
||||
|
||||
export function enhanceScrollBehavior(router: Router) {
|
||||
// handle scrollBehavior with transition
|
||||
const scrollBehavior = router.options.scrollBehavior!
|
||||
router.options.scrollBehavior = async (...args) => {
|
||||
await useScrollPromise().wait()
|
||||
return scrollBehavior(...args)
|
||||
}
|
||||
|
||||
router.beforeEach(() => {
|
||||
if (inBrowser) {
|
||||
document.documentElement.classList.remove('smooth')
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => nextTick(() => {
|
||||
if (inBrowser) {
|
||||
document.documentElement.classList.add('smooth')
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -2,61 +2,19 @@ import './styles/index.css'
|
||||
|
||||
import { defineClientConfig } from 'vuepress/client'
|
||||
import type { ClientConfig } from 'vuepress/client'
|
||||
import { h } from 'vue'
|
||||
import VPBadge from './components/global/VPBadge.vue'
|
||||
import { setupDarkMode, setupWatermark, useScrollPromise } from './composables/index.js'
|
||||
import { enhanceScrollBehavior, setupDarkMode, setupWatermark } from './composables/index.js'
|
||||
import { globalComponents } from './globalComponents.js'
|
||||
import Layout from './layouts/Layout.vue'
|
||||
import NotFound from './layouts/NotFound.vue'
|
||||
import VPHomeBox from './components/Home/VPHomeBox.vue'
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app, router }) {
|
||||
setupDarkMode(app)
|
||||
// global component
|
||||
app.component('Badge', VPBadge)
|
||||
app.component('VPBadge', VPBadge) // alias
|
||||
|
||||
app.component('DocSearch', () => {
|
||||
const SearchComponent
|
||||
= app.component('Docsearch') || app.component('SearchBox')
|
||||
if (SearchComponent)
|
||||
return h(SearchComponent)
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
app.component('PageComment', (props) => {
|
||||
const CommentService = app.component('CommentService')
|
||||
if (CommentService)
|
||||
return h(CommentService, props)
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
app.component('Icon', (props) => {
|
||||
const Iconify = app.component('Iconify')
|
||||
if (Iconify)
|
||||
return h(Iconify, props)
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
/** @deprecated */
|
||||
app.component('HomeBox', VPHomeBox)
|
||||
app.component('VPHomeBox', VPHomeBox)
|
||||
|
||||
// handle scrollBehavior with transition
|
||||
const scrollBehavior = router.options.scrollBehavior!
|
||||
router.options.scrollBehavior = async (...args) => {
|
||||
await useScrollPromise().wait()
|
||||
return scrollBehavior(...args)
|
||||
}
|
||||
enhanceScrollBehavior(router)
|
||||
globalComponents(app)
|
||||
},
|
||||
setup() {
|
||||
setupWatermark()
|
||||
},
|
||||
layouts: {
|
||||
Layout,
|
||||
NotFound,
|
||||
},
|
||||
layouts: { Layout, NotFound },
|
||||
}) as ClientConfig
|
||||
|
||||
50
theme/src/client/globalComponents.ts
Normal file
50
theme/src/client/globalComponents.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import type { App } from 'vue'
|
||||
import { h } from 'vue'
|
||||
import VPHomeBox from '@theme/Home/VPHomeBox.vue'
|
||||
import VPCard from '@theme/global/VPCard.vue'
|
||||
import VPLinkCard from '@theme/global/VPLinkCard.vue'
|
||||
import VPBadge from '@theme/global/VPBadge.vue'
|
||||
import VPCardGrid from '@theme/global/VPCardGrid.vue'
|
||||
|
||||
export function globalComponents(app: App) {
|
||||
app.component('Badge', VPBadge)
|
||||
app.component('VPBadge', VPBadge)
|
||||
|
||||
app.component('VPCard', VPCard)
|
||||
app.component('Card', VPCard)
|
||||
|
||||
app.component('VPCardGrid', VPCardGrid)
|
||||
app.component('CardGrid', VPCardGrid)
|
||||
|
||||
app.component('VPLinkCard', VPLinkCard)
|
||||
app.component('LinkCard', VPLinkCard)
|
||||
|
||||
app.component('DocSearch', () => {
|
||||
const SearchComponent
|
||||
= app.component('Docsearch') || app.component('SearchBox')
|
||||
if (SearchComponent)
|
||||
return h(SearchComponent)
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
app.component('PageComment', (props) => {
|
||||
const CommentService = app.component('CommentService')
|
||||
if (CommentService)
|
||||
return h(CommentService, props)
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
app.component('Icon', (props) => {
|
||||
const Iconify = app.component('Iconify')
|
||||
if (Iconify)
|
||||
return h(Iconify, props)
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
/** @deprecated */
|
||||
app.component('HomeBox', VPHomeBox)
|
||||
app.component('VPHomeBox', VPHomeBox)
|
||||
}
|
||||
@ -11,6 +11,9 @@ export { default as Layout } from './layouts/Layout.vue'
|
||||
export { default as NotFound } from './layouts/NotFound.vue'
|
||||
|
||||
export { default as VPBadge } from './components/global/VPBadge.vue'
|
||||
export { default as VPCard } from './components/global/VPCard.vue'
|
||||
export { default as VPCardGrid } from './components/global/VPCardGrid.vue'
|
||||
|
||||
export { default as VPImage } from './components/VPImage.vue'
|
||||
export { default as VPButton } from './components/VPButton.vue'
|
||||
export { default as VPLink } from './components/VPLink.vue'
|
||||
|
||||
@ -39,12 +39,21 @@
|
||||
}
|
||||
|
||||
.vp-doc h3 {
|
||||
margin: 32px 0 0;
|
||||
margin: 32px 0 16px;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.vp-doc h4,
|
||||
.vp-doc h5,
|
||||
.vp-doc h6 {
|
||||
margin: 24px 0 16px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.vp-doc .header-anchor {
|
||||
position: relative;
|
||||
color: inherit;
|
||||
@ -89,6 +98,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.vp-doc img {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.vp-doc img + img {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
@ -148,10 +161,6 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vp-doc a > img {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
208
theme/src/client/styles/custom-block.css
Normal file
208
theme/src/client/styles/custom-block.css
Normal file
@ -0,0 +1,208 @@
|
||||
/* --------------------- demo-wrapper ------------------------ */
|
||||
.vp-doc .demo-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 40px;
|
||||
margin: 40px -16px;
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
transition: var(--t-color);
|
||||
transition-property: border, box-shadow;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 0;
|
||||
border-bottom: solid 1px var(--vp-c-divider);
|
||||
transition: border-bottom var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-container {
|
||||
min-height: 0;
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
transition: background-color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.has-title .demo-head {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.only-img {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.only-img img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.only-img .demo-container,
|
||||
.vp-doc .demo-wrapper.no-padding .demo-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.has-height .demo-container {
|
||||
height: var(--demo-container-height);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 5px 0 5px 8px;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #ccc;
|
||||
border-radius: 100%;
|
||||
transition: background-color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(1) {
|
||||
background-color: var(--vp-c-danger-3);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(2) {
|
||||
background-color: var(--vp-c-warning-3);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(3) {
|
||||
background-color: var(--vp-c-green-3);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
padding: 0 16px;
|
||||
margin: 0 20px -1px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
transition: var(--t-color);
|
||||
transition-property: color, background-color;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title p {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title::after,
|
||||
.vp-doc .demo-wrapper .demo-title::before {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
content: " ";
|
||||
transition: background var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title::before {
|
||||
left: 100%;
|
||||
background: radial-gradient(16px at right top, transparent 50%, var(--vp-c-bg-alt) 50%);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title::after {
|
||||
right: 100%;
|
||||
background: radial-gradient(16px at left top, transparent 50%, var(--vp-c-bg-alt) 50%);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-container > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-container > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 419px) {
|
||||
.vp-doc .demo-wrapper {
|
||||
margin: 40px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------- Steps --------------------------- */
|
||||
.vp-doc .vp-steps {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.vp-doc .vp-steps > ol,
|
||||
.vp-doc .vp-steps > ul {
|
||||
padding-inline-start: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.vp-doc .vp-steps > ol > li,
|
||||
.vp-doc .vp-steps > ul > li {
|
||||
position: relative;
|
||||
min-height: 22px;
|
||||
padding-bottom: 1px;
|
||||
padding-left: 44px;
|
||||
}
|
||||
|
||||
.vp-doc .vp-steps > ol > li::before,
|
||||
.vp-doc .vp-steps > ul > li::before {
|
||||
position: absolute;
|
||||
inset-inline-start: 0;
|
||||
top: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
color: var(--vp-c-text-1);
|
||||
text-align: center;
|
||||
content: counter(list-item);
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 100%;
|
||||
transition: var(--t-color);
|
||||
transition-property: color, background-color, border-color;
|
||||
}
|
||||
|
||||
.vp-doc .vp-steps > ol > li:not(:last-of-type)::after,
|
||||
.vp-doc .vp-steps > ul > li:not(:last-of-type)::after {
|
||||
position: absolute;
|
||||
inset-inline-start: 14px;
|
||||
top: 34px;
|
||||
bottom: 6px;
|
||||
width: 1px;
|
||||
content: "";
|
||||
background-color: var(--vp-c-divider);
|
||||
transition: background-color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .vp-steps > ol > li > :first-child,
|
||||
.vp-doc .vp-steps > ul > li > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.vp-doc .vp-steps > ol > li > :first-child:where(h1,h2,h3,h4,h5,h6),
|
||||
.vp-doc .vp-steps > ul > li > :first-child:where(h1,h2,h3,h4,h5,h6) {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.vp-doc .vp-steps > ol > li + li,
|
||||
.vp-doc .vp-steps > ul > li + li {
|
||||
margin-top: 0;
|
||||
}
|
||||
@ -8,6 +8,7 @@
|
||||
@import url("./utils.css");
|
||||
@import url("./content.css");
|
||||
@import url("./code.css");
|
||||
@import url("./custom-block.css");
|
||||
|
||||
@import url("./twoslash.css");
|
||||
@import url("./md-enhance.css");
|
||||
|
||||
@ -587,148 +587,6 @@
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* --------------------- demo-wrapper ------------------------ */
|
||||
.vp-doc .demo-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 40px;
|
||||
margin: 40px -16px;
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
transition: var(--t-color);
|
||||
transition-property: border, box-shadow;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.has-title .demo-head {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.only-img {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.only-img img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.only-img .demo-container,
|
||||
.vp-doc .demo-wrapper.no-padding .demo-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper.has-height .demo-container {
|
||||
height: var(--demo-container-height);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 0;
|
||||
border-bottom: solid 1px var(--vp-c-divider);
|
||||
transition: border-bottom var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 5px 0 5px 8px;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #ccc;
|
||||
border-radius: 100%;
|
||||
transition: background-color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(1) {
|
||||
background-color: var(--vp-c-danger-3);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(2) {
|
||||
background-color: var(--vp-c-warning-3);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-ctrl i:nth-child(3) {
|
||||
background-color: var(--vp-c-green-3);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
padding: 0 16px;
|
||||
margin: 0 20px -1px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
transition: var(--t-color);
|
||||
transition-property: color, background-color;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title p {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title::after,
|
||||
.vp-doc .demo-wrapper .demo-title::before {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
content: " ";
|
||||
transition: background var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title::before {
|
||||
left: 100%;
|
||||
background: radial-gradient(16px at right top, transparent 50%, var(--vp-c-bg-alt) 50%);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-title::after {
|
||||
right: 100%;
|
||||
background: radial-gradient(16px at left top, transparent 50%, var(--vp-c-bg-alt) 50%);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-container {
|
||||
min-height: 0;
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
transition: background-color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-container > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.vp-doc .demo-wrapper .demo-container > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 419px) {
|
||||
.vp-doc .demo-wrapper {
|
||||
margin: 40px 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------- Markdown Enhance: Mermaid ------------------------ */
|
||||
.mermaid-actions {
|
||||
padding-right: 20px;
|
||||
|
||||
1
theme/src/client/styles/normalize.css
vendored
1
theme/src/client/styles/normalize.css
vendored
@ -23,7 +23,6 @@ html {
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: 80px;
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,10 @@
|
||||
margin: 0.3em;
|
||||
}
|
||||
|
||||
.smooth {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* ----------------- Transition ------------------------ */
|
||||
.fade-slide-y-enter-active {
|
||||
transition: all 0.25s ease !important;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { addViteConfig, addViteOptimizeDepsInclude, addViteSsrNoExternal } from '@vuepress/helper'
|
||||
import { addViteConfig, addViteOptimizeDepsExclude, addViteOptimizeDepsInclude, addViteSsrNoExternal } from '@vuepress/helper'
|
||||
import type { App } from 'vuepress'
|
||||
|
||||
export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
|
||||
@ -9,6 +9,7 @@ export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
|
||||
})
|
||||
|
||||
addViteOptimizeDepsInclude(bundlerOptions, app, '@vueuse/core', true)
|
||||
addViteOptimizeDepsExclude(bundlerOptions, app, '@theme')
|
||||
|
||||
addViteSsrNoExternal(bundlerOptions, app, [
|
||||
'@vuepress/helper',
|
||||
@ -1,5 +1,12 @@
|
||||
export * from './resolveThemeOption.js'
|
||||
export * from './resolveLocaleOptions.js'
|
||||
export * from './resolveThemeData.js'
|
||||
export * from './resolveProvideData.js'
|
||||
export * from './resolveAlias.js'
|
||||
|
||||
export * from './extendsBundlerOptions.js'
|
||||
export * from './templateBuildRenderer.js'
|
||||
|
||||
export * from './resolveSearchOptions.js'
|
||||
export * from './resolvePageHead.js'
|
||||
export * from './resolveEncrypt.js'
|
||||
|
||||
18
theme/src/node/config/resolveAlias.ts
Normal file
18
theme/src/node/config/resolveAlias.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import { resolve } from '../utils.js'
|
||||
|
||||
export function resolveAlias() {
|
||||
return {
|
||||
...Object.fromEntries(
|
||||
fs.readdirSync(
|
||||
resolve('client/components'),
|
||||
{ encoding: 'utf-8', recursive: true },
|
||||
)
|
||||
.filter(file => file.endsWith('.vue'))
|
||||
.map(file => [
|
||||
path.join('@theme', file),
|
||||
resolve('client/components', file),
|
||||
]),
|
||||
),
|
||||
}
|
||||
}
|
||||
28
theme/src/node/config/resolveProvideData.ts
Normal file
28
theme/src/node/config/resolveProvideData.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { App } from 'vuepress'
|
||||
import { entries, fromEntries, getRootLangPath, isPlainObject } from '@vuepress/helper'
|
||||
import type { PlumeThemeEncrypt, PlumeThemePluginOptions } from '../../shared/index.js'
|
||||
import { PRESET_LOCALES } from '../locales/index.js'
|
||||
import { resolveEncrypt } from './resolveEncrypt.js'
|
||||
|
||||
export function resolveProvideData(
|
||||
app: App,
|
||||
plugins: PlumeThemePluginOptions,
|
||||
encrypt?: PlumeThemeEncrypt,
|
||||
|
||||
): Record<string, any> {
|
||||
const root = getRootLangPath(app)
|
||||
|
||||
return {
|
||||
// 注入 加密配置
|
||||
...resolveEncrypt(encrypt),
|
||||
// 注入水印配置
|
||||
__PLUME_WM_FP__: isPlainObject(plugins.watermark)
|
||||
? plugins.watermark.fullPage !== false
|
||||
: true,
|
||||
// 注入多语言配置
|
||||
__PLUME_PRESET_LOCALE__: fromEntries(
|
||||
entries(PRESET_LOCALES)
|
||||
.map(([locale, value]) => [locale === root ? '/' : locale, value]),
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,10 @@ import type { DocsearchPluginOptions } from '@vuepress/plugin-docsearch'
|
||||
import type { SearchPluginOptions } from '@vuepress-plume/plugin-search'
|
||||
import { DOCSEARCH_LOCALES, SEARCH_LOCALES } from '../locales/index.js'
|
||||
|
||||
export function resolveSearchOptions(app: App, { locales, ...options }: SearchPluginOptions = {}): SearchPluginOptions {
|
||||
export function resolveSearchOptions(
|
||||
app: App,
|
||||
{ locales, ...options }: SearchPluginOptions = {},
|
||||
): SearchPluginOptions {
|
||||
return {
|
||||
...options,
|
||||
locales: getLocaleConfig({
|
||||
@ -15,7 +18,10 @@ export function resolveSearchOptions(app: App, { locales, ...options }: SearchPl
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDocsearchOptions(app: App, { locales, ...options }: DocsearchPluginOptions = {}): DocsearchPluginOptions {
|
||||
export function resolveDocsearchOptions(
|
||||
app: App,
|
||||
{ locales, ...options }: DocsearchPluginOptions = {},
|
||||
): DocsearchPluginOptions {
|
||||
return {
|
||||
...options,
|
||||
locales: getLocaleConfig({
|
||||
|
||||
19
theme/src/node/config/resolveThemeOption.ts
Normal file
19
theme/src/node/config/resolveThemeOption.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { PlumeThemeOptions } from '../../shared/index.js'
|
||||
import { logger } from '../utils.js'
|
||||
|
||||
export function resolveThemeOptions({ themePlugins, plugins, encrypt, hostname, ...localeOptions }: PlumeThemeOptions) {
|
||||
const pluginOptions = plugins ?? themePlugins ?? {}
|
||||
|
||||
if (themePlugins) {
|
||||
logger.warn(
|
||||
`The 'themePlugins' option is deprecated. Please use 'plugins' instead.`,
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
pluginOptions,
|
||||
encrypt,
|
||||
hostname,
|
||||
localeOptions,
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { type TemplateRendererContext, templateRenderer } from 'vuepress/utils'
|
||||
import { getThemePackage } from './utils.js'
|
||||
import { getThemePackage } from '../utils.js'
|
||||
|
||||
export function templateBuildRenderer(template: string, context: TemplateRendererContext) {
|
||||
const pkg = getThemePackage()
|
||||
@ -5,4 +5,7 @@ export * from '../shared/index.js'
|
||||
|
||||
export { plumeTheme }
|
||||
|
||||
/**
|
||||
* @deprecated 请使用 具名导出 替代 默认导出
|
||||
*/
|
||||
export default plumeTheme
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { SearchLocaleOptions } from '@vuepress-plume/plugin-search'
|
||||
import type { PlumeThemeLocaleData } from '../../shared/index.js'
|
||||
import type { PresetLocale } from '../types.js'
|
||||
import type { PlumeThemeLocaleData, PresetLocale } from '../../shared/index.js'
|
||||
|
||||
export const enLocale: PlumeThemeLocaleData = {
|
||||
selectLanguageName: 'English',
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import type { DocsearchLocaleOptions } from '@vuepress/plugin-docsearch'
|
||||
import type { SearchLocaleOptions } from '@vuepress-plume/plugin-search'
|
||||
import type { PlumeThemeLocaleData } from '../../shared/index.js'
|
||||
import type { PresetLocale } from '../types.js'
|
||||
import type { PlumeThemeLocaleData, PresetLocale } from '../../shared/index.js'
|
||||
import { zhDocsearchLocale, zhLocale, zhPresetLocale, zhSearchLocale } from './zh.js'
|
||||
import { enLocale, enPresetLocale, enSearchLocale } from './en.js'
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import type { DocsearchLocaleOptions } from '@vuepress/plugin-docsearch'
|
||||
import type { SearchLocaleOptions } from '@vuepress-plume/plugin-search'
|
||||
import type { PlumeThemeLocaleData } from '../../shared/index.js'
|
||||
import type { PresetLocale } from '../types.js'
|
||||
import type { PlumeThemeLocaleData, PresetLocale } from '../../shared/index.js'
|
||||
|
||||
export const zhLocale: PlumeThemeLocaleData = {
|
||||
selectLanguageName: '简体中文',
|
||||
|
||||
@ -39,6 +39,60 @@ export const customContainerPlugins: Plugin[] = [
|
||||
return '</div></div>\n'
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* :::steps
|
||||
* 1. 步骤 1
|
||||
* xxx
|
||||
* 2. 步骤 2
|
||||
* xxx
|
||||
* 3. ...
|
||||
* :::
|
||||
*/
|
||||
containerPlugin({
|
||||
type: 'steps',
|
||||
before() {
|
||||
return '<div class="vp-steps">'
|
||||
},
|
||||
after() {
|
||||
return '</div>'
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* ::: card title="xxx" icon="xxx"
|
||||
* xxx
|
||||
* :::
|
||||
*/
|
||||
containerPlugin({
|
||||
type: 'card',
|
||||
before(info) {
|
||||
const title = resolveAttr(info, 'title')
|
||||
const icon = resolveAttr(info, 'icon')
|
||||
return `<VPCard${title ? ` title="${title}"` : ''}${icon ? ` icon="${icon}"` : ''}>`
|
||||
},
|
||||
after() {
|
||||
return '</VPCard>'
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* :::: card-grid
|
||||
* ::: card
|
||||
* xxx
|
||||
* :::
|
||||
* ::: card
|
||||
* xxx
|
||||
* :::
|
||||
* ::::
|
||||
*/
|
||||
containerPlugin({
|
||||
type: 'card-grid',
|
||||
before() {
|
||||
return '<VPCardGrid>'
|
||||
},
|
||||
after() {
|
||||
return '</VPCardGrid>'
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
@ -1,62 +1,40 @@
|
||||
import type { Page, Theme } from 'vuepress/core'
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import { isPlainObject } from '@vuepress/helper'
|
||||
import type { PlumeThemeOptions, PlumeThemePageData } from '../shared/index.js'
|
||||
import { getPlugins } from './plugins/index.js'
|
||||
import { extendsPageData, setupPage } from './setupPages.js'
|
||||
import { THEME_NAME, logger, resolve, templates } from './utils.js'
|
||||
import { resolveEncrypt, resolveLocaleOptions, resolvePageHead } from './config/index.js'
|
||||
import { extendsBundlerOptions } from './extendsBundlerOptions.js'
|
||||
import { templateBuildRenderer } from './templateBuildRenderer.js'
|
||||
import { THEME_NAME, resolve, templates } from './utils.js'
|
||||
import {
|
||||
extendsBundlerOptions,
|
||||
resolveAlias,
|
||||
resolveLocaleOptions,
|
||||
resolvePageHead,
|
||||
resolveProvideData,
|
||||
resolveThemeOptions,
|
||||
templateBuildRenderer,
|
||||
} from './config/index.js'
|
||||
import { setupPrepare, watchPrepare } from './prepare/index.js'
|
||||
|
||||
export function plumeTheme({
|
||||
themePlugins,
|
||||
plugins,
|
||||
encrypt,
|
||||
hostname,
|
||||
...localeOptions
|
||||
}: PlumeThemeOptions = {}): Theme {
|
||||
const pluginOptions = plugins ?? themePlugins ?? {}
|
||||
|
||||
const watermarkFullPage = isPlainObject(pluginOptions.watermark)
|
||||
? pluginOptions.watermark.fullPage !== false
|
||||
: true
|
||||
|
||||
if (themePlugins) {
|
||||
logger.warn(
|
||||
`The 'themePlugins' option is deprecated. Please use 'plugins' instead.`,
|
||||
)
|
||||
}
|
||||
export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
|
||||
const {
|
||||
localeOptions: rawLocaleOptions,
|
||||
pluginOptions,
|
||||
hostname,
|
||||
encrypt,
|
||||
} = resolveThemeOptions(options)
|
||||
|
||||
return (app) => {
|
||||
localeOptions = resolveLocaleOptions(app, localeOptions)
|
||||
const localeOptions = resolveLocaleOptions(app, rawLocaleOptions)
|
||||
|
||||
return {
|
||||
name: THEME_NAME,
|
||||
|
||||
define: {
|
||||
...resolveEncrypt(encrypt),
|
||||
__PLUME_WM_FP__: watermarkFullPage,
|
||||
},
|
||||
define: resolveProvideData(app, pluginOptions, encrypt),
|
||||
|
||||
templateBuild: templates('build.html'),
|
||||
|
||||
clientConfigFile: resolve('client/config.js'),
|
||||
|
||||
alias: {
|
||||
...Object.fromEntries(
|
||||
fs.readdirSync(
|
||||
resolve('client/components'),
|
||||
{ encoding: 'utf-8', recursive: true },
|
||||
)
|
||||
.filter(file => file.endsWith('.vue'))
|
||||
.map(file => [
|
||||
path.join('@theme', file),
|
||||
resolve('client/components', file),
|
||||
]),
|
||||
),
|
||||
},
|
||||
alias: resolveAlias(),
|
||||
|
||||
plugins: getPlugins({ app, pluginOptions, localeOptions, encrypt, hostname }),
|
||||
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
export interface PresetLocale {
|
||||
home: string
|
||||
blog: string
|
||||
tag: string
|
||||
archive: string
|
||||
}
|
||||
@ -38,3 +38,10 @@ export type SocialLinkIconUnion =
|
||||
| 'xbox'
|
||||
|
||||
export type SocialLinkIcon = SocialLinkIconUnion | { svg: string }
|
||||
|
||||
export interface PresetLocale {
|
||||
home: string
|
||||
blog: string
|
||||
tag: string
|
||||
archive: string
|
||||
}
|
||||
|
||||
@ -48,9 +48,9 @@ export interface PlumeThemeLocaleData extends LocaleData {
|
||||
social?: SocialLink[]
|
||||
|
||||
/**
|
||||
* Navbar config
|
||||
* 导航栏配置
|
||||
*
|
||||
* Set to `false` to disable navbar in current locale
|
||||
* 设置为 `false` 将会禁用导航栏
|
||||
*/
|
||||
navbar?: false | NavItem[]
|
||||
|
||||
@ -75,114 +75,119 @@ export interface PlumeThemeLocaleData extends LocaleData {
|
||||
/**
|
||||
* 笔记配置, 笔记中的文章默认不会出现在首页文章列表
|
||||
*
|
||||
* 注:你也可以将notes配置到navbar中,默认自动生成在右侧栏目中
|
||||
* 注:也可以将notes配置到navbar中
|
||||
*/
|
||||
notes?: false | NotesDataOptions
|
||||
|
||||
/**
|
||||
* 要显示的标题级别。
|
||||
*
|
||||
* 单个数字表示只显示该级别的标题。
|
||||
*
|
||||
* 如果传递的是一个元组,第一个数字是最小级别,第二个数字是最大级别。
|
||||
*
|
||||
* 'deep' 与 [2, 6] 相同,将显示从 <h2> 到 <h6> 的所有标题。
|
||||
*
|
||||
* @default [2, 3]
|
||||
*/
|
||||
outline?: ThemeOutline
|
||||
|
||||
/**
|
||||
* 是否显示侧边栏
|
||||
*
|
||||
* - `false` 表示禁用 右侧边栏
|
||||
* - `true` 表示启用 右侧边栏
|
||||
* - `'left` 表示将有侧边栏移动到文章内容左侧,sidebar 右侧
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
aside?: boolean | 'left'
|
||||
|
||||
/**
|
||||
* language text
|
||||
* 选择语言菜单 的文本。
|
||||
*/
|
||||
selectLanguageText?: string
|
||||
/**
|
||||
* language aria label
|
||||
* 选择语言菜单 的 `aria-label` 属性。
|
||||
*/
|
||||
selectLanguageAriaLabel?: string
|
||||
/**
|
||||
* language name
|
||||
* 语言名称
|
||||
*
|
||||
* 仅能在主题配置的 locales 的内部生效 。它将被用作 locale 的语言名称,展示在 选择语言菜单 内。
|
||||
*/
|
||||
selectLanguageName?: string
|
||||
|
||||
/**
|
||||
* Page meta - edit link config
|
||||
*
|
||||
* Whether to show "Edit this page" or not
|
||||
*
|
||||
* 是否显示 "编辑此页"
|
||||
*/
|
||||
editLink?: boolean
|
||||
|
||||
/**
|
||||
* Page meta - edit link config
|
||||
* "编辑此页" 的文本
|
||||
*
|
||||
* The text to replace the default "Edit this page"
|
||||
* @default "Edit this page"
|
||||
*/
|
||||
editLinkText?: string
|
||||
|
||||
/**
|
||||
* Page meta - edit link config
|
||||
*
|
||||
* Pattern of edit link
|
||||
* "编辑此页" 的链接匹配模式
|
||||
*
|
||||
* @example ':repo/edit/:branch/:path'
|
||||
*/
|
||||
editLinkPattern?: string
|
||||
/**
|
||||
* Page meta - edit link config
|
||||
*
|
||||
* Use `repo` config by default
|
||||
*
|
||||
* Set this config if your docs is placed in a different repo
|
||||
* 文档仓库配置, 用于生成 Edit this page 链接
|
||||
*/
|
||||
docsRepo?: string
|
||||
|
||||
/**
|
||||
* Page meta - edit link config
|
||||
*
|
||||
* Set this config if the branch of your docs is not 'main'
|
||||
* 文档仓库分支配置,用于生成 `Edit this page` 链接。
|
||||
*/
|
||||
docsBranch?: string
|
||||
|
||||
/**
|
||||
* Page meta - edit link config
|
||||
*
|
||||
* Set this config if your docs is placed in sub dir of your `docsRepo`
|
||||
* 文档仓库目录配置,用于生成 `Edit this page` 链接。
|
||||
*/
|
||||
docsDir?: string
|
||||
/**
|
||||
* Page meta - last updated config
|
||||
* 最后更新时间
|
||||
*
|
||||
* Whether to show "Last Updated" or not
|
||||
* @default { text: 'Last Updated', formatOptions: { dateStyle: 'short', timeStyle: 'short' } }
|
||||
*/
|
||||
lastUpdated?: false | LastUpdatedOptions
|
||||
|
||||
/**
|
||||
* @deprecated Use `lastUpdated.text` instead.
|
||||
* @deprecated 使用 `lastUpdated.text` 代替.
|
||||
*
|
||||
* Set custom last updated text.
|
||||
* "最后更新时间" 的文本
|
||||
*
|
||||
* @default 'Last updated'
|
||||
*/
|
||||
lastUpdatedText?: string
|
||||
|
||||
/**
|
||||
* Page meta - contributors config
|
||||
*
|
||||
* Whether to show "Contributors" or not
|
||||
* 是否显示贡献者
|
||||
*/
|
||||
contributors?: boolean
|
||||
/**
|
||||
* Page meta - contributors config
|
||||
*
|
||||
* The text to replace the default "Contributors"
|
||||
* 贡献者的文本
|
||||
*/
|
||||
contributorsText?: string
|
||||
|
||||
// backToHome?: string
|
||||
|
||||
/**
|
||||
* sidebar menu label
|
||||
* 移动设备下的导航栏中 菜单选项的文字。
|
||||
*
|
||||
* @default 'Menu'
|
||||
*/
|
||||
sidebarMenuLabel?: string
|
||||
|
||||
/**
|
||||
* return to top label
|
||||
* 移动设备下的导航栏中返回顶部的文字。
|
||||
*
|
||||
* @default 'return to top'
|
||||
*/
|
||||
returnToTopLabel?: string
|
||||
|
||||
@ -193,15 +198,30 @@ export interface PlumeThemeLocaleData extends LocaleData {
|
||||
*/
|
||||
outlineLabel?: string
|
||||
|
||||
/**
|
||||
* 上一页的文本
|
||||
*
|
||||
* @default 'Previous Page'
|
||||
*/
|
||||
prevPageLabel?: string
|
||||
|
||||
/**
|
||||
* 下一页的文本
|
||||
*
|
||||
* @default 'Next Page'
|
||||
*/
|
||||
nextPageLabel?: string
|
||||
|
||||
/**
|
||||
* 是否显示外部链接图标
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
externalLinkIcon?: string
|
||||
|
||||
/**
|
||||
* 页脚配置。
|
||||
*/
|
||||
footer?:
|
||||
| false
|
||||
| {
|
||||
@ -210,7 +230,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
|
||||
}
|
||||
|
||||
/**
|
||||
* 404 page options
|
||||
* 404 页面配置
|
||||
*/
|
||||
notFound?: {
|
||||
code?: string | number
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user