This commit is contained in:
parent
f51dff1d58
commit
a3d8e225b9
@ -23,6 +23,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({
|
||||
},
|
||||
'sidebar',
|
||||
'write',
|
||||
'auto-frontmatter',
|
||||
'locales',
|
||||
'deployment',
|
||||
'optimize-build',
|
||||
|
||||
@ -23,6 +23,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({
|
||||
},
|
||||
'sidebar',
|
||||
'write',
|
||||
'auto-frontmatter',
|
||||
'locales',
|
||||
'deployment',
|
||||
'optimize-build',
|
||||
|
||||
@ -190,8 +190,11 @@ export default defineThemeConfig({
|
||||
* 是否自动生成 permalink
|
||||
*
|
||||
* @default true
|
||||
* - true: 自动生成 permalink
|
||||
* - false: 不生成 permalink
|
||||
* - 'filepath': 根据文件路径生成 permalink
|
||||
*/
|
||||
permalink?: boolean
|
||||
permalink?: boolean | 'filepath'
|
||||
|
||||
/**
|
||||
* 是否自动生成 createTime
|
||||
|
||||
@ -190,8 +190,11 @@ export default defineThemeConfig({
|
||||
* Whether to automatically generate permalink
|
||||
*
|
||||
* @default true
|
||||
* - true: auto generate permalink
|
||||
* - false: do not generate permalink
|
||||
* - 'filepath': generate permalink based on file path
|
||||
*/
|
||||
permalink?: boolean
|
||||
permalink?: boolean | 'filepath'
|
||||
|
||||
/**
|
||||
* Whether to automatically generate createTime
|
||||
|
||||
334
docs/en/guide/quick-start/auto-frontmatter.md
Normal file
334
docs/en/guide/quick-start/auto-frontmatter.md
Normal file
@ -0,0 +1,334 @@
|
||||
---
|
||||
title: frontmatter
|
||||
icon: material-symbols:markdown-outline-rounded
|
||||
createTime: 2026/01/15 15:03:10
|
||||
permalink: /en/guide/auto-frontmatter/
|
||||
---
|
||||
|
||||
## Auto-generating Frontmatter
|
||||
|
||||
This feature automatically generates frontmatter for each Markdown file.
|
||||
|
||||
::: details What is Frontmatter?
|
||||
Frontmatter is a metadata block written in YAML format at the very beginning of a Markdown file.
|
||||
You can think of it as the "ID card" or "configuration manual" of the Markdown file.
|
||||
It won't be rendered into the web page content directly, but is used to configure relevant parameters for the file.
|
||||
|
||||
Frontmatter is wrapped using three dashes (`---`) and located at the very start of the file:
|
||||
|
||||
```md
|
||||
---
|
||||
title: Post Title
|
||||
createTime: 2026/01/15 15:03:10
|
||||
---
|
||||
|
||||
Here is the Markdown content...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
The current theme supports auto-generated frontmatter including:
|
||||
|
||||
- `title`: Article title, generated based on the file name
|
||||
- `createTime`: Article creation time, generated based on the file creation time
|
||||
- `permalink`: Article permalink
|
||||
- Uses `nanoid` to generate an 8-character random string by default
|
||||
- Can be set to `filepath` to generate based on the file path
|
||||
|
||||
## Configuration
|
||||
|
||||
### Global Configuration
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
// autoFrontmatter: true, // Theme built-in configuration
|
||||
autoFrontmatter: {
|
||||
title: true, // Auto-generate title
|
||||
createTime: true, // Auto-generate creation time
|
||||
permalink: true, // Auto-generate permalink
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
// autoFrontmatter: true, // Theme built-in configuration
|
||||
autoFrontmatter: {
|
||||
title: true, // Auto-generate title
|
||||
createTime: true, // Auto-generate creation time
|
||||
permalink: true, // Auto-generate permalink
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Collection Configuration
|
||||
|
||||
You can configure autoFrontmatter separately for each collection.
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
collections: [
|
||||
{
|
||||
type: 'doc',
|
||||
dir: 'guide',
|
||||
title: '指南',
|
||||
// autoFrontmatter: true, // Theme built-in configuration
|
||||
autoFrontmatter: {
|
||||
title: true, // Auto-generate title
|
||||
createTime: true, // Auto-generate creation time
|
||||
permalink: true, // Auto-generate permalink
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
collections: [
|
||||
{
|
||||
type: 'doc',
|
||||
dir: 'guide',
|
||||
title: '指南',
|
||||
// autoFrontmatter: true, // Theme built-in configuration
|
||||
autoFrontmatter: {
|
||||
title: true, // Auto-generate title
|
||||
createTime: true, // Auto-generate creation time
|
||||
permalink: true, // Auto-generate permalink
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Custom Processing Logic
|
||||
|
||||
Use `transform(data, context, locale)` to configure custom processing logic.
|
||||
`data` is the frontmatter data, `context` is the file context, and `locale` is the current language path.
|
||||
|
||||
- `transform()` can also be an async function, returning a Promise.
|
||||
- `transform()` is applicable in both global configuration and collection configuration.
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
autoFrontmatter: {
|
||||
title: true, // Auto-generate title
|
||||
createTime: true, // Auto-generate creation time
|
||||
permalink: true, // Auto-generate permalink
|
||||
transform: (data, context, locale) => { // Custom transform
|
||||
// context.filePath // File absolute path
|
||||
// context.relativePath // File relative path, relative to source directory
|
||||
// context.content // Markdown content
|
||||
|
||||
data.foo ??= 'foo'
|
||||
return data
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
autoFrontmatter: {
|
||||
title: true, // Auto-generate title
|
||||
createTime: true, // Auto-generate creation time
|
||||
permalink: true, // Auto-generate permalink
|
||||
transform: (data, context, locale) => { // Custom transform
|
||||
// context.filePath // File absolute path
|
||||
// context.relativePath // File relative path, relative to source directory
|
||||
// context.content // Markdown content
|
||||
|
||||
data.foo ??= 'foo'
|
||||
return data
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Interface
|
||||
|
||||
```ts
|
||||
interface AutoFrontmatterContext {
|
||||
/**
|
||||
* File absolute path
|
||||
*/
|
||||
filepath: string
|
||||
/**
|
||||
* File relative path
|
||||
*/
|
||||
relativePath: string
|
||||
/**
|
||||
* File markdown content
|
||||
*/
|
||||
content: string
|
||||
}
|
||||
|
||||
interface AutoFrontmatterOptions {
|
||||
/**
|
||||
* Whether to auto-generate permalink
|
||||
*
|
||||
* - `false`: Do not auto-generate permalink
|
||||
* - `true`: Auto-generate permalink, using nanoid to generate an 8-digit random string
|
||||
* - `filepath`: Generate permalink based on file path
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
permalink?: boolean | 'filepath'
|
||||
/**
|
||||
* Whether to auto-generate createTime
|
||||
*
|
||||
* Reads the file creation time by default. `createTime` is more precise to the second than VuePress's default `date` time.
|
||||
*/
|
||||
createTime?: boolean
|
||||
/**
|
||||
* Whether to auto-generate title
|
||||
*
|
||||
* Reads the file name as the title by default.
|
||||
*/
|
||||
title?: boolean
|
||||
|
||||
/**
|
||||
* Custom frontmatter generation function
|
||||
*
|
||||
* - You should add new fields directly to `data`
|
||||
* - If a completely new `data` object is returned, it will overwrite the previous frontmatter
|
||||
* @param data Existing frontmatter on the page
|
||||
* @param context Context information of the current page
|
||||
* @param locale Current language path
|
||||
* @returns Returns the processed frontmatter
|
||||
*/
|
||||
transform?: (data: AutoFrontmatterData, context: AutoFrontmatterContext, locale: string) => AutoFrontmatterData | Promise<AutoFrontmatterData>
|
||||
}
|
||||
```
|
||||
|
||||
## Permalink
|
||||
|
||||
The theme uses `nanoid` to generate an 8-character random string as the file's permalink by default.
|
||||
|
||||
You can also configure `permalink` as `'filepath'` to generate the permalink based on the file path.
|
||||
Please note, if your file path contains Chinese characters,
|
||||
the theme recommends installing `pinyin-pro` in your project to support converting Chinese to pinyin.
|
||||
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm i pinyin-pro
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
autoFrontmatter: {
|
||||
permalink: 'filepath', // [!code hl]
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
autoFrontmatter: {
|
||||
permalink: 'filepath', // [!code hl]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Example:
|
||||
|
||||
::: code-tree
|
||||
|
||||
```md title="docs/blog/服务.md"
|
||||
---
|
||||
title: 服务
|
||||
permalink: /blog/wu-fu/
|
||||
---
|
||||
```
|
||||
|
||||
```md title="docs/blog/都城.md"
|
||||
---
|
||||
title: 都城
|
||||
permalink: /blog/dou-cheng/
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You probably noticed that in the example, the permalink for the `都城.md` file is `/blog/dou-cheng/`,
|
||||
which is incorrect. This is because `pinyin-pro`'s default dictionary cannot accurately identify polyphonic
|
||||
characters. If you need a more precise conversion result,you can manually install `@pinyin-pro/data`,
|
||||
and the theme will automatically load this dictionary to improve accuracy.
|
||||
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm i @pinyin-pro/data
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
```md title="docs/blog/都城.md"
|
||||
---
|
||||
title: 都城
|
||||
permalink: /blog/du-cheng/
|
||||
---
|
||||
```
|
||||
331
docs/guide/quick-start/auto-frontmatter.md
Normal file
331
docs/guide/quick-start/auto-frontmatter.md
Normal file
@ -0,0 +1,331 @@
|
||||
---
|
||||
title: frontmatter
|
||||
icon: material-symbols:markdown-outline-rounded
|
||||
createTime: 2026/01/15 15:03:10
|
||||
permalink: /guide/auto-frontmatter/
|
||||
---
|
||||
|
||||
## 自动生成 frontmatter
|
||||
|
||||
主题 自动为每个 Markdown 文件生成 frontmatter。
|
||||
|
||||
::: details 什么是 frontmatter ?
|
||||
Frontmatter(前言)是在 Markdown 文件最开头部分使用 YAML 格式编写的元数据区块。
|
||||
你可以把它想象成 Markdown 文件的“身份证”或“配置说明书”,它不会被直接渲染成网页内容,而是用于配置该文件的相关参数。
|
||||
|
||||
Frontmatter 使用三个连字符(---)包裹,位于文件的最开头:
|
||||
|
||||
```md
|
||||
---
|
||||
title: Post Title
|
||||
createTime: 2026/01/15 15:03:10
|
||||
---
|
||||
|
||||
这里是 Markdown 正文内容...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
当前主题支持自动生成的 frontmatter 包括:
|
||||
|
||||
- `title`: 文章标题,根据文件名生成
|
||||
- `createTime`: 文章创建时间,根据文件创建时间生成
|
||||
- `permalink`: 文章链接
|
||||
- 默认使用 `nanoid` 生成 8 位随机字符串
|
||||
- 可以设置为 `filepath` 根据文件路径生成
|
||||
|
||||
## 配置
|
||||
|
||||
### 全局配置
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
// autoFrontmatter: true, // 主题内置配置
|
||||
autoFrontmatter: {
|
||||
title: true, // 自动生成标题
|
||||
createTime: true, // 自动生成创建时间
|
||||
permalink: true, // 自动生成永久链接
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
// autoFrontmatter: true, // 主题内置配置
|
||||
autoFrontmatter: {
|
||||
title: true, // 自动生成标题
|
||||
createTime: true, // 自动生成创建时间
|
||||
permalink: true, // 自动生成永久链接
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 集合配置
|
||||
|
||||
可以给每一个集合单独配置 autoFrontmatter
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
collections: [
|
||||
{
|
||||
type: 'doc',
|
||||
dir: 'guide',
|
||||
title: '指南',
|
||||
// autoFrontmatter: true, // 主题内置配置
|
||||
autoFrontmatter: {
|
||||
title: true, // 自动生成标题
|
||||
createTime: true, // 自动生成创建时间
|
||||
permalink: true, // 自动生成永久链接
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
collections: [
|
||||
{
|
||||
type: 'doc',
|
||||
dir: 'guide',
|
||||
title: '指南',
|
||||
// autoFrontmatter: true, // 主题内置配置
|
||||
autoFrontmatter: {
|
||||
title: true, // 自动生成标题
|
||||
createTime: true, // 自动生成创建时间
|
||||
permalink: true, // 自动生成永久链接
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### 自定义处理逻辑
|
||||
|
||||
使用 `transform(data, context, locale)` 配置自定义处理逻辑,`data` 为 frontmatter 数据,`context` 为文件上下文,`locale` 为当前语言路径。
|
||||
|
||||
- `transform()` 也可以是异步函数,返回 Promise。
|
||||
- `transform()` 适用于 全局配置 和 集合配置 中。
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
autoFrontmatter: {
|
||||
title: true, // 自动生成标题
|
||||
createTime: true, // 自动生成创建时间
|
||||
permalink: true, // 自动生成永久链接
|
||||
transform: (data, context, locale) => { // 自定义转换
|
||||
// context.filePath // 文件绝对路径
|
||||
// context.relativePath // 文件相对路径,相对于源目录
|
||||
// context.content // markdown 正文内容
|
||||
|
||||
data.foo ??= 'foo'
|
||||
return data
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
autoFrontmatter: {
|
||||
title: true, // 自动生成标题
|
||||
createTime: true, // 自动生成创建时间
|
||||
permalink: true, // 自动生成永久链接
|
||||
transform: (data, context, locale) => { // 自定义转换
|
||||
// context.filePath // 文件绝对路径
|
||||
// context.relativePath // 文件相对路径,相对于源目录
|
||||
// context.content // markdown 正文内容
|
||||
|
||||
data.foo ??= 'foo'
|
||||
return data
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Interface
|
||||
|
||||
```ts
|
||||
interface AutoFrontmatterContext {
|
||||
/**
|
||||
* 文件绝对路径
|
||||
*/
|
||||
filepath: string
|
||||
/**
|
||||
* 文件相对路径
|
||||
*/
|
||||
relativePath: string
|
||||
/**
|
||||
* 文件 markdown 内容
|
||||
*/
|
||||
content: string
|
||||
}
|
||||
|
||||
interface AutoFrontmatterOptions {
|
||||
/**
|
||||
* 是否自动生成 permalink
|
||||
*
|
||||
* - `false`: 不自动生成 permalink
|
||||
* - `true`: 自动生成 permalink ,使用 nanoid 生成 8 位数随机字符串
|
||||
* - `filepath`: 根据文件路径生成 permalink
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
permalink?: boolean | 'filepath'
|
||||
/**
|
||||
* 是否自动生成 createTime
|
||||
*
|
||||
* 默认读取 文件创建时间,`createTime` 比 vuepress 默认的 `date` 时间更精准到秒
|
||||
*/
|
||||
createTime?: boolean
|
||||
/**
|
||||
* 是否自动生成 title
|
||||
*
|
||||
* 默认读取文件名作为标题
|
||||
*/
|
||||
title?: boolean
|
||||
|
||||
/**
|
||||
* 自定义 frontmatter 生成函数
|
||||
*
|
||||
* - 你应该直接将新字段添加到 `data` 中
|
||||
* - 如果返回全新的 `data` 对象,会覆盖之前的 frontmatter
|
||||
* @param data 页面已存在的 frontmatter
|
||||
* @param context 当前页面的上下文信息
|
||||
* @param locale 当前语言路径
|
||||
* @returns 返回处理后的 frontmatter
|
||||
*/
|
||||
transform?: (data: AutoFrontmatterData, context: AutoFrontmatterContext, locale: string) => AutoFrontmatterData | Promise<AutoFrontmatterData>
|
||||
}
|
||||
```
|
||||
|
||||
## 永久链接 permalink
|
||||
|
||||
主题默认使用 `nanoid` 生成一个 8 位的随机字符串作为文件的永久链接。
|
||||
|
||||
还可以将 `permalink` 配置为 `'filepath'` ,根据文件路径生成永久链接。
|
||||
请注意,如果你的文件路径中,包含中文,主题建议在你的项目中安装 `pinyin-pro` ,
|
||||
以支持将中文转换为拼音。
|
||||
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm i pinyin-pro
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: code-tabs#config
|
||||
|
||||
@tab .vuepress/config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
autoFrontmatter: {
|
||||
permalink: 'filepath', // [!code hl]
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@tab .vuepress/plume.config.ts
|
||||
|
||||
```ts twoslash
|
||||
import { defineThemeConfig } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineThemeConfig({
|
||||
autoFrontmatter: {
|
||||
permalink: 'filepath', // [!code hl]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
示例:
|
||||
|
||||
::: code-tree
|
||||
|
||||
```md title="docs/blog/服务.md"
|
||||
---
|
||||
title: 服务
|
||||
permalink: /blog/wu-fu/
|
||||
---
|
||||
```
|
||||
|
||||
```md title="docs/blog/都城.md"
|
||||
---
|
||||
title: 都城
|
||||
permalink: /blog/dou-cheng/
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
你应该已经发现 示例中的 `都城.md` 文件的 permalink 为 `/blog/dou-cheng/` ,这显然是错误的,这是因为 `pinyin-pro`
|
||||
默认的词库对于多音字并不能精确的识别,如果你需要更为精确的转换结果,可以手动安装 `@pinyin-pro/data`,
|
||||
主题为自动加载该词库,以提高精度。
|
||||
|
||||
::: npm-to
|
||||
|
||||
```sh
|
||||
npm i @pinyin-pro/data
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
```md title="docs/blog/都城.md"
|
||||
---
|
||||
title: 都城
|
||||
permalink: /blog/du-cheng/
|
||||
---
|
||||
```
|
||||
@ -71,6 +71,7 @@
|
||||
"@vuepress/shiki-twoslash": "catalog:vuepress",
|
||||
"gsap": "catalog:peer",
|
||||
"ogl": "catalog:peer",
|
||||
"pinyin-pro": "catalog:peer",
|
||||
"postprocessing": "catalog:peer",
|
||||
"swiper": "catalog:peer",
|
||||
"three": "catalog:peer",
|
||||
@ -92,6 +93,9 @@
|
||||
"ogl": {
|
||||
"optional": true
|
||||
},
|
||||
"pinyin-pro": {
|
||||
"optional": true
|
||||
},
|
||||
"postprocessing": {
|
||||
"optional": true
|
||||
},
|
||||
@ -146,8 +150,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "catalog:peer",
|
||||
"@pinyin-pro/data": "catalog:peer",
|
||||
"gsap": "catalog:peer",
|
||||
"ogl": "catalog:peer",
|
||||
"pinyin-pro": "catalog:peer",
|
||||
"postprocessing": "catalog:peer",
|
||||
"swiper": "catalog:peer",
|
||||
"three": "catalog:peer",
|
||||
|
||||
48
theme/src/node/autoFrontmatter/helper.ts
Normal file
48
theme/src/node/autoFrontmatter/helper.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { kebabCase } from '@pengzhanbo/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { ensureLeadingSlash, removeLeadingSlash } from 'vuepress/shared'
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import { getPinyin, hasPinyin } from '../utils/index.js'
|
||||
|
||||
export const EXCLUDE = ['!**/.vuepress/', '!**/node_modules/']
|
||||
|
||||
const NUMBER_RE = /^\d+\./
|
||||
|
||||
export function isReadme(filepath: string): boolean {
|
||||
return filepath.endsWith('README.md') || filepath.endsWith('index.md') || filepath.endsWith('readme.md')
|
||||
}
|
||||
|
||||
export function normalizeTitle(title: string): string {
|
||||
return title.replace(NUMBER_RE, '').trim()
|
||||
}
|
||||
|
||||
export function getFileCreateTime(filepath: string): string {
|
||||
const stats = fs.statSync(filepath)
|
||||
const time = stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime
|
||||
return dayjs(new Date(time)).format('YYYY/MM/DD HH:mm:ss')
|
||||
}
|
||||
|
||||
export function getCurrentName(filepath: string): string {
|
||||
if (isReadme(filepath))
|
||||
return normalizeTitle(path.dirname(filepath).slice(-1).split('/').pop() || 'Home')
|
||||
|
||||
return normalizeTitle(path.basename(filepath, '.md'))
|
||||
}
|
||||
|
||||
export async function getPermalinkByFilepath(filepath: string, base = '/'): Promise<string> {
|
||||
const relative = removeLeadingSlash(ensureLeadingSlash(filepath).replace(ensureLeadingSlash(base), ''))
|
||||
const dirs = path.dirname(relative).split('/').map(normalizeTitle)
|
||||
const basename = normalizeTitle(path.basename(relative, '.md'))
|
||||
if (hasPinyin) {
|
||||
const pinyin = await getPinyin()
|
||||
return path.join(
|
||||
...dirs.map(dir => slugify(pinyin?.(dir, { toneType: 'none', nonZh: 'consecutive' }) || dir)),
|
||||
slugify(pinyin?.(basename, { toneType: 'none', nonZh: 'consecutive' }) || basename),
|
||||
)
|
||||
}
|
||||
return path.join(...dirs.map(slugify), slugify(basename))
|
||||
}
|
||||
|
||||
function slugify(str: string): string {
|
||||
return kebabCase(str.trim())
|
||||
}
|
||||
48
theme/src/node/autoFrontmatter/resolveLinkBySidebar.ts
Normal file
48
theme/src/node/autoFrontmatter/resolveLinkBySidebar.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { ThemeSidebarItem } from '../../shared/index.js'
|
||||
import { ensureEndingSlash } from 'vuepress/shared'
|
||||
import { path } from 'vuepress/utils'
|
||||
|
||||
export function resolveLinkBySidebar(
|
||||
sidebar: 'auto' | (string | ThemeSidebarItem)[],
|
||||
_prefix: string,
|
||||
): Record<string, string> {
|
||||
const res: Record<string, string> = {}
|
||||
|
||||
if (sidebar === 'auto') {
|
||||
return res
|
||||
}
|
||||
|
||||
for (const item of sidebar) {
|
||||
if (typeof item !== 'string') {
|
||||
const { prefix, dir = '', link = '/', items, text = '' } = item
|
||||
getSidebarLink(items, link, text, path.join(_prefix, prefix || dir), res)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function getSidebarLink(items: 'auto' | (string | ThemeSidebarItem)[] | undefined, link: string, text: string, dir = '', res: Record<string, string> = {}) {
|
||||
if (items === 'auto')
|
||||
return
|
||||
|
||||
if (!items) {
|
||||
res[ensureEndingSlash(dir)] = link
|
||||
return
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (typeof item === 'string') {
|
||||
res[ensureEndingSlash(dir)] = link
|
||||
}
|
||||
else {
|
||||
const { prefix = '', dir: subDir = '', link: subLink = '/', items: subItems, text: subText = '' } = item
|
||||
getSidebarLink(
|
||||
subItems,
|
||||
path.join(link, subLink),
|
||||
subText,
|
||||
path.join(prefix[0] === '/' ? prefix : `/${dir}/${prefix || subDir}`),
|
||||
res,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,24 @@
|
||||
import type { AutoFrontmatterContext, AutoFrontmatterData, AutoFrontmatterOptions, AutoFrontmatterRule, ThemeDocCollection, ThemeSidebarItem } from '../../shared/index.js'
|
||||
import type {
|
||||
AutoFrontmatterContext,
|
||||
AutoFrontmatterData,
|
||||
AutoFrontmatterOptions,
|
||||
AutoFrontmatterRule,
|
||||
ThemeDocCollection,
|
||||
} from '../../shared/index.js'
|
||||
import type { ThemePostCollection } from '../../shared/index.js'
|
||||
import { hasOwn, toArray } from '@pengzhanbo/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import { ensureLeadingSlash, removeLeadingSlash } from 'vuepress/shared'
|
||||
import { fs, path } from 'vuepress/utils'
|
||||
import { ensureEndingSlash, ensureLeadingSlash, removeLeadingSlash } from 'vuepress/shared'
|
||||
import { path } from 'vuepress/utils'
|
||||
import { getThemeConfig } from '../loadConfig/index.js'
|
||||
import { nanoid } from '../utils/index.js'
|
||||
|
||||
const EXCLUDE = ['!**/.vuepress/', '!**/node_modules/']
|
||||
import {
|
||||
EXCLUDE,
|
||||
getCurrentName,
|
||||
getFileCreateTime,
|
||||
getPermalinkByFilepath,
|
||||
isReadme,
|
||||
} from './helper.js'
|
||||
import { resolveLinkBySidebar } from './resolveLinkBySidebar.js'
|
||||
|
||||
const rules: AutoFrontmatterRule[] = []
|
||||
|
||||
@ -100,7 +111,14 @@ async function generateWithPost(
|
||||
}
|
||||
|
||||
if (ep && !hasOwn(data, 'permalink')) {
|
||||
data.permalink = path.join(locale, collection.linkPrefix || collection.link || collection.dir, nanoid(), '/')
|
||||
data.permalink = path.join(
|
||||
locale,
|
||||
collection.linkPrefix || collection.link || collection.dir,
|
||||
ep === 'filepath'
|
||||
? await getPermalinkByFilepath(context.relativePath, path.join(locale, collection.dir))
|
||||
: nanoid(),
|
||||
'/',
|
||||
)
|
||||
}
|
||||
|
||||
data = await transform?.(data, context, locale) ?? data
|
||||
@ -136,12 +154,32 @@ async function generateWithDoc(
|
||||
}
|
||||
else if (collection.sidebar && collection.sidebar !== 'auto') {
|
||||
const res = resolveLinkBySidebar(collection.sidebar, ensureLeadingSlash(collection.dir))
|
||||
const file = ensureLeadingSlash(context.relativePath)
|
||||
const link = res[file] || res[path.dirname(file)] || ''
|
||||
data.permalink = path.join(locale, collection.linkPrefix, link, isReadme(context.relativePath) ? '' : nanoid(8), '/')
|
||||
const file = path.dirname(context.relativePath)
|
||||
const link = res[ensureLeadingSlash(ensureEndingSlash(file))] || '/'
|
||||
data.permalink = path.join(
|
||||
locale,
|
||||
collection.linkPrefix,
|
||||
link,
|
||||
isReadme(context.relativePath)
|
||||
? ''
|
||||
: ep === 'filepath'
|
||||
? await getPermalinkByFilepath(
|
||||
link === '/' ? context.relativePath : path.basename(context.relativePath),
|
||||
link === '/' ? path.join(locale, collection.dir) : '',
|
||||
)
|
||||
: nanoid(8),
|
||||
'/',
|
||||
)
|
||||
}
|
||||
else {
|
||||
data.permalink = path.join(locale, collection.linkPrefix, nanoid(8), '/')
|
||||
data.permalink = path.join(
|
||||
locale,
|
||||
collection.linkPrefix,
|
||||
ep === 'filepath'
|
||||
? await getPermalinkByFilepath(context.relativePath, path.join(locale, collection.dir))
|
||||
: nanoid(8),
|
||||
'/',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,86 +213,14 @@ async function generateWithRemain(
|
||||
}
|
||||
|
||||
if (ep && !hasOwn(data, 'permalink') && !isRoot) {
|
||||
data.permalink = path.join(locale, nanoid(8), '/')
|
||||
data.permalink = path.join(
|
||||
locale,
|
||||
ep === 'filepath' ? await getPermalinkByFilepath(context.relativePath, locale) : nanoid(8),
|
||||
'/',
|
||||
)
|
||||
}
|
||||
|
||||
data = await transform?.(data, context, locale) ?? data
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
function isReadme(filepath: string): boolean {
|
||||
return filepath.endsWith('README.md') || filepath.endsWith('index.md') || filepath.endsWith('readme.md')
|
||||
}
|
||||
|
||||
function normalizeTitle(title: string): string {
|
||||
return title.replace(/^\d+\./, '').trim()
|
||||
}
|
||||
|
||||
function getFileCreateTime(filepath: string): string {
|
||||
const stats = fs.statSync(filepath)
|
||||
const time = stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime
|
||||
return dayjs(new Date(time)).format('YYYY/MM/DD HH:mm:ss')
|
||||
}
|
||||
|
||||
function getCurrentName(filepath: string): string {
|
||||
if (isReadme(filepath))
|
||||
return normalizeTitle(path.dirname(filepath).slice(-1).split('/').pop() || 'Home')
|
||||
|
||||
return normalizeTitle(path.basename(filepath, '.md'))
|
||||
}
|
||||
|
||||
export function resolveLinkBySidebar(
|
||||
sidebar: 'auto' | (string | ThemeSidebarItem)[],
|
||||
_prefix: string,
|
||||
): Record<string, string> {
|
||||
const res: Record<string, string> = {}
|
||||
|
||||
if (sidebar === 'auto') {
|
||||
return res
|
||||
}
|
||||
|
||||
for (const item of sidebar) {
|
||||
if (typeof item !== 'string') {
|
||||
const { prefix, dir = '', link = '/', items, text = '' } = item
|
||||
getSidebarLink(items, link, text, path.join(_prefix, prefix || dir), res)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function getSidebarLink(items: 'auto' | (string | ThemeSidebarItem)[] | undefined, link: string, text: string, dir = '', res: Record<string, string> = {}) {
|
||||
if (items === 'auto')
|
||||
return
|
||||
|
||||
if (!items) {
|
||||
res[path.join(dir, `${text}.md`)] = link
|
||||
return
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (typeof item === 'string') {
|
||||
if (!link)
|
||||
continue
|
||||
if (item) {
|
||||
res[path.join(dir, `${item}.md`)] = link
|
||||
}
|
||||
else {
|
||||
res[path.join(dir, 'README.md')] = link
|
||||
res[path.join(dir, 'index.md')] = link
|
||||
res[path.join(dir, 'readme.md')] = link
|
||||
}
|
||||
res[dir] = link
|
||||
}
|
||||
else {
|
||||
const { prefix, dir: subDir = '', link: subLink = '/', items: subItems, text: subText = '' } = item
|
||||
getSidebarLink(
|
||||
subItems,
|
||||
path.join(link, subLink),
|
||||
subText,
|
||||
path.join(prefix?.[0] === '/' ? prefix : `/${dir}/${prefix}`, subDir),
|
||||
res,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ export * from './interopDefault.js'
|
||||
export * from './logger.js'
|
||||
export * from './package.js'
|
||||
export * from './path.js'
|
||||
export * from './pinyin.js'
|
||||
export * from './resolveContent.js'
|
||||
export * from './translate.js'
|
||||
export * from './writeTemp.js'
|
||||
|
||||
18
theme/src/node/utils/pinyin.ts
Normal file
18
theme/src/node/utils/pinyin.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { isPackageExists } from 'local-pkg'
|
||||
import { interopDefault } from './interopDefault'
|
||||
|
||||
let _pinyin: typeof import('pinyin-pro').pinyin | null = null
|
||||
|
||||
export const hasPinyin = isPackageExists('pinyin-pro')
|
||||
const hasPinyinData = isPackageExists('@pinyin-pro/data')
|
||||
|
||||
export async function getPinyin() {
|
||||
if (hasPinyin && !_pinyin) {
|
||||
const { pinyin, addDict } = (await import('pinyin-pro'))
|
||||
_pinyin = pinyin
|
||||
if (hasPinyinData) {
|
||||
addDict(await interopDefault(import('@pinyin-pro/data/complete')))
|
||||
}
|
||||
}
|
||||
return _pinyin
|
||||
}
|
||||
@ -1,4 +1,9 @@
|
||||
export type AutoFrontmatterData = Record<string, any>
|
||||
import type { LiteralUnion } from '@pengzhanbo/utils'
|
||||
|
||||
export type AutoFrontmatterData = Record<
|
||||
LiteralUnion<'title' | 'createTime' | 'permalink'>,
|
||||
any
|
||||
>
|
||||
|
||||
/**
|
||||
* The context of the markdown file
|
||||
@ -76,9 +81,16 @@ export interface AutoFrontmatterOptions {
|
||||
/**
|
||||
* 是否自动生成 permalink
|
||||
*
|
||||
* - `false`: 不自动生成 permalink
|
||||
* - `true`: 自动生成 permalink ,使用 nanoid 生成 8 位数随机字符串
|
||||
* - `filepath`: 根据文件路径生成 permalink
|
||||
*
|
||||
* 对于 `filepath`,如果文件路径中包含中文,可以手动安装 `pinyin-pro` ,
|
||||
* 主题内部会加载 `pinyin-pro` 进行中文拼音转换
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
permalink?: boolean
|
||||
permalink?: boolean | 'filepath'
|
||||
/**
|
||||
* 是否自动生成 createTime
|
||||
*
|
||||
@ -112,10 +124,5 @@ export interface AutoFrontmatterOptions {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
transform?: <
|
||||
D extends AutoFrontmatterData = AutoFrontmatterData,
|
||||
>(data: D,
|
||||
context: AutoFrontmatterContext,
|
||||
locale: string,
|
||||
) => D | Promise<D>
|
||||
transform?: (data: AutoFrontmatterData, context: AutoFrontmatterContext, locale: string) => AutoFrontmatterData | Promise<AutoFrontmatterData>
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user