chore: 初始化项目仓库

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 21:27:39 +08:00
commit b7addb5c7a
50 changed files with 15964 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
/**
* Nuxt Content v3 (minimark) AST 工具函数
* 集中管理 body AST 的文本提取、标题提取、slugify 等操作
*/
// --- 文本提取 ---
const SKIP_TAGS = new Set(['style', 'script', 'pre', 'code', 'head', 'meta', 'link'])
export function extractTextFromAst(node: any): string {
if (!node) return ''
if (typeof node === 'string') return node
if (Array.isArray(node)) {
const tag = typeof node[0] === 'string' ? node[0] : ''
if (SKIP_TAGS.has(tag)) return ''
const start = typeof node[1] === 'object' && !Array.isArray(node[1]) && node[1] !== null ? 2 : 1
return node.slice(start).map(extractTextFromAst).join('')
}
if (typeof node === 'object') {
if (typeof node.value === 'string') return node.value
if (node.children) return extractTextFromAst(node.children)
if (node.value) return extractTextFromAst(node.value)
}
return ''
}
// --- 标题提取 ---
export interface Heading {
id: string
text: string
level: number
}
function extractTextFromNode(node: any): string {
if (!node) return ''
if (typeof node === 'string') return node
if (Array.isArray(node)) return node.map(extractTextFromNode).join('')
if (typeof node === 'object') {
if (typeof node.value === 'string') return node.value
if (node.children) return extractTextFromNode(node.children)
}
return ''
}
export function extractHeadings(node: any, list: Heading[] = []): Heading[] {
if (!node) return list
if (Array.isArray(node)) {
if (typeof node[0] === 'string' && /^h[1-6]$/.test(node[0])) {
const level = Number(node[0].charAt(1))
const props = typeof node[1] === 'object' && !Array.isArray(node[1]) ? node[1] : {}
const childStart = typeof node[1] === 'object' && !Array.isArray(node[1]) ? 2 : 1
const text = node.slice(childStart).map(extractTextFromNode).join('').trim()
if (text) {
list.push({ id: props.id || slugify(text), text, level })
}
}
for (const child of node) {
if (Array.isArray(child) || (typeof child === 'object' && child !== null)) {
extractHeadings(child, list)
}
}
return list
}
if (typeof node === 'object') {
const tag = node.tag || node.type
if (tag && /^h[1-6]$/.test(tag)) {
const level = Number(tag.charAt(1))
const text = extractTextFromNode(node).trim()
if (text) {
list.push({ id: node.props?.id || slugify(text), text, level })
}
}
if (node.children) extractHeadings(node.children, list)
if (node.value) extractHeadings(node.value, list)
if (node.body) extractHeadings(node.body, list)
}
return list
}
// --- slugify ---
export function slugify(text: string): string {
return text
.trim()
.toLowerCase()
.replace(/[\s ]+/g, '-')
.replace(/[^\p{L}\p{N}\-]/gu, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '') || 'section'
}
// --- 去重(给 heading id 加后缀)---
export function deduplicateIds(list: Heading[]): Heading[] {
const seen = new Map<string, number>()
for (const h of list) {
if (seen.has(h.id)) {
const n = (seen.get(h.id) || 1) + 1
seen.set(h.id, n)
h.id = `${h.id}-${n}`
} else {
seen.set(h.id, 1)
}
}
return list
}
// --- 从 body 提取所有标题(去重后)---
export function getHeadingsFromBody(body: Record<string, any> | null | undefined): Heading[] {
if (!body) return []
const list: Heading[] = []
extractHeadings(body.value || body, list)
return deduplicateIds(list)
}