import MarkdownIt from 'markdown-it' import hljs from 'highlight.js' import DOMPurify from 'isomorphic-dompurify' import { stripFrontMatter } from './markdown-front-matter' const escapeHtml = (value: string): string => value .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", ''') const toSafeLanguageClass = (lang: string): string => { const normalized = lang.trim().toLowerCase().replaceAll(/[^a-z0-9_-]/g, '') return normalized || 'text' } const getHighlightedCode = (source: string, lang: string): string => { const normalizedLang = lang.trim().toLowerCase() if (normalizedLang && hljs.getLanguage(normalizedLang)) { return hljs.highlight(source, { language: normalizedLang, ignoreIllegals: true }).value } if (normalizedLang) { return hljs.highlightAuto(source).value } return escapeHtml(source) } const md = new MarkdownIt({ html: false, linkify: true, typographer: false, breaks: true, // 开启代码块语言识别 highlight: function (str: string, lang: string) { // 处理语言标签(如果没有指定语言,默认 text) const language = toSafeLanguageClass(lang || 'text') const highlightedCode = getHighlightedCode(str, lang || '') // 生成带语言标识 + 复制按钮的代码块外壳 return ( `
${language}` + `
` + `
${highlightedCode}
` ) }, }) /** 全局复制代码函数与事件绑定标记 */ declare global { interface Window { copyCode: (btn: HTMLButtonElement) => void __markdownCopyBound?: boolean } } // 只在客户端环境注册复制函数 if (typeof window !== 'undefined') { window.copyCode = function (btn: HTMLButtonElement) { const codeBlock = btn.closest('.code-block-wrapper')!.querySelector('code')! const text = codeBlock.textContent || '' navigator.clipboard.writeText(text).then(() => { const originalText = btn.textContent btn.textContent = '已复制' btn.classList.add('is-copied') // 2秒后恢复按钮文字 setTimeout(() => { btn.textContent = originalText btn.classList.remove('is-copied') }, 2000) }) } if (!window.__markdownCopyBound) { document.addEventListener('click', (event) => { const target = event.target if (!(target instanceof Element)) { return } const btn = target.closest('button[data-copy-code]') if (!btn) { return } window.copyCode(btn) }) window.__markdownCopyBound = true } } /** 将 Markdown 转为可安全用于 `v-html` 的 HTML(禁用源码中的原始 HTML)。 */ export function renderSafeMarkdown(src: string): string { if (!src.trim()) { return '' } return DOMPurify.sanitize(md.render(stripFrontMatter(src)), { USE_PROFILES: { html: true } }) }