feat(theme): add filepath permalink support for auto frontmatter, #815 (#822)

This commit is contained in:
pengzhanbo 2026-01-19 21:43:41 +08:00 committed by GitHub
parent f51dff1d58
commit a3d8e225b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 865 additions and 98 deletions

View File

@ -23,6 +23,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({
},
'sidebar',
'write',
'auto-frontmatter',
'locales',
'deployment',
'optimize-build',

View File

@ -23,6 +23,7 @@ export const themeGuide: ThemeCollectionItem = defineCollection({
},
'sidebar',
'write',
'auto-frontmatter',
'locales',
'deployment',
'optimize-build',

View File

@ -190,8 +190,11 @@ export default defineThemeConfig({
* 是否自动生成 permalink
*
* @default true
* - true: 自动生成 permalink
* - false: 不生成 permalink
* - 'filepath': 根据文件路径生成 permalink
*/
permalink?: boolean
permalink?: boolean | 'filepath'
/**
* 是否自动生成 createTime

View File

@ -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

View 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/
---
```

View 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/
---
```

View File

@ -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",

View 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())
}

View 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,
)
}
}
}

View File

@ -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,
)
}
}
}

View File

@ -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'

View 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
}

View File

@ -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>
}