Files
afilmory/apps/docs/plugins/remark-heading.ts
Innei 77f16490e4 chore: adjust docs and add cjk font
- Created a new documentation site under `apps/docs` using Vite, React, and MDX.
- Added essential files including `index.html`, `package.json`, and various configuration files.
- Implemented a file-system based routing system with automatic route generation from markdown files.
- Included support for syntax highlighting, responsive design, and TypeScript.
- Established a structure for documentation content with initial deployment guides and storage provider information.

Signed-off-by: Innei <tukon479@gmail.com>
2025-11-14 21:52:54 +08:00

129 lines
3.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Heading, Root } from 'mdast'
import type { Plugin } from 'unified'
import { visit } from 'unist-util-visit'
/**
* 生成标题ID的函数
* 保持与 toc-extractor.ts 中的 generateHeadingId 函数完全一致
*/
function generateHeadingId(text: string): string {
return text
.toLowerCase()
.replaceAll(/[^\w\s-]/g, '') // 移除特殊字符
.replaceAll(/\s+/g, '-') // 空格替换为连字符
.replaceAll(/-+/g, '-') // 多个连字符合并为一个
.replaceAll(/^-|-$/g, '') // 移除开头和结尾的连字符
}
/**
* 从 MDAST 节点中提取文本内容
*/
function extractTextFromNode(node: any): string {
if (typeof node === 'string') {
return node
}
if (node.type === 'text') {
return node.value || ''
}
if (node.type === 'inlineCode') {
return node.value || ''
}
if (node.type === 'strong' || node.type === 'emphasis') {
return node.children?.map(extractTextFromNode).join('') || ''
}
if (node.type === 'link') {
return node.children?.map(extractTextFromNode).join('') || ''
}
if (node.children && Array.isArray(node.children)) {
return node.children.map((element: any) => extractTextFromNode(element)).join('')
}
return ''
}
interface RemarkHeadingOptions {
/**
* 是否覆盖已存在的 ID
* @default false
*/
overrideExisting?: boolean
/**
* ID 前缀
* @default ''
*/
prefix?: string
/**
* 自定义 ID 生成函数
* @default generateHeadingId
*/
generateId?: (text: string) => string
}
/**
* Remark 插件:为标题自动添加 ID
*
* 此插件会遍历 Markdown/MDX 文档中的所有标题节点,
* 为没有 ID 的标题自动生成 ID确保与 TOC 提取器生成的 ID 一致
*
* @param options 配置选项
*/
const remarkHeading: Plugin<[RemarkHeadingOptions?], Root> = (options = {}) => {
const { overrideExisting = false, prefix = '', generateId = generateHeadingId } = options
return (tree: Root) => {
// 用于跟踪已使用的 ID避免重复
const usedIds = new Set<string>()
visit(tree, 'heading', (node: Heading) => {
// 检查是否已经有 ID
const existingId = node.data?.hProperties?.id as string | undefined
if (existingId && !overrideExisting) {
usedIds.add(existingId)
return
}
// 提取标题文本
const text = node.children.map((element: any) => extractTextFromNode(element)).join('')
if (!text.trim()) {
return // 跳过空标题
}
// 生成基础 ID
const baseId = prefix + generateId(text.trim())
let finalId = baseId
// 处理 ID 冲突,添加数字后缀
let counter = 1
while (usedIds.has(finalId)) {
finalId = `${baseId}-${counter}`
counter++
}
usedIds.add(finalId)
// 设置 ID
if (!node.data) {
node.data = {}
}
if (!node.data.hProperties) {
node.data.hProperties = {}
}
node.data.hProperties.id = finalId
})
}
}
export default remarkHeading
export { generateHeadingId, remarkHeading }