package utils import ( "bytes" "html/template" "regexp" "strings" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" goldmarkhtml "github.com/yuin/goldmark/renderer/html" ) // markdownEngine 全局 Markdown 渲染引擎 // 启用 GFM 扩展(表格、删除线、任务列表、自动链接等) // 启用 WithUnsafe 允许原始 HTML 透传,兼容旧文章中的 HTML 内容 var markdownEngine = goldmark.New( goldmark.WithExtensions(extension.GFM), goldmark.WithRendererOptions( goldmarkhtml.WithUnsafe(), goldmarkhtml.WithHardWraps(), ), ) // RenderMarkdown 将 Markdown 文本渲染为 HTML func RenderMarkdown(source string) template.HTML { var buf bytes.Buffer if err := markdownEngine.Convert([]byte(source), &buf); err != nil { // 渲染失败时原样返回,避免页面空白 return template.HTML(source) } return template.HTML(buf.String()) } // 全局预编译正则(保持不变,但移除冗余) var ( // 块级语法 reHeading = regexp.MustCompile(`(?m)^\s*#{1,6}\s+`) // 列表 reUnorderedList = regexp.MustCompile(`(?m)^\s*[-*+]\s+`) // 有序列表 reOrderedList = regexp.MustCompile(`(?m)^\s*\d+[.)]\s+`) // 块引用 reBlockquote = regexp.MustCompile(`(?m)^\s*>\s+`) // 分割线 reHr = regexp.MustCompile(`(?m)^\s*[-*_]{3,}\s*$`) // 引用定义 reFootnoteDef = regexp.MustCompile(`(?m)^\[\^.+?\]:\s.*$`) reRefDef = regexp.MustCompile(`(?m)^\s*\[.+?\]:\s*(.*)$`) // 图片(全部移除) reImage = regexp.MustCompile(`!\[.*?\]\(.*?\)`) reRefImage = regexp.MustCompile(`!\[.*?\](\[.*?\])?`) // 内联链接 [text](url) → 保留 text reLink = regexp.MustCompile(`\[([^\]]*)\]\([^)]*\)`) // 引用式链接 [text][id] 或 [text][] → 保留 text reRefLink = regexp.MustCompile(`\[([^\]]*)\](\[[^\]]*\])?`) // 行内标记(加粗、斜体、删除线) reBoldStar = regexp.MustCompile(`\*\*(.+?)\*\*`) reBoldUnd = regexp.MustCompile(`__(.+?)__`) reItalicStar = regexp.MustCompile(`\*(.+?)\*`) // 注意:必须在粗体之后处理 reItalicUnd = regexp.MustCompile(`_(.+?)_`) // 同上 reStrike = regexp.MustCompile(`~~(.+?)~~`) // 删除线 // 为保留代码内容(可能含关键词),此处只移除反引号,但代码块可能多行,我们保留内容。 reCodeInline = regexp.MustCompile("`([^`]*)`") reCodeBlock = regexp.MustCompile("(?s)```(.*?)```") // HTML 标签 reHTMLTag = regexp.MustCompile(`<[^>]+>`) // 表格分隔线(移除 --- 行,保留内容) reTableSep = regexp.MustCompile(`(?m)^\s*\|?\s*[-:]+[-| :]+\s*\|?\s*$`) // 多余空白 reMultiSpace = regexp.MustCompile(`\s+`) ) // GenerateSearchableText 提取 Markdown 中的可见纯文本 func GenerateSearchableText(raw string) string { if raw == "" { return "" } text := raw // 1. 全角空格转半角 text = strings.ReplaceAll(text, "\u3000", " ") // 2. 移除块级标记(标题、列表、引用、分割线) text = reHeading.ReplaceAllString(text, "") text = reUnorderedList.ReplaceAllString(text, "") text = reOrderedList.ReplaceAllString(text, "") text = reBlockquote.ReplaceAllString(text, "") text = reHr.ReplaceAllString(text, "") // 3. 移除引用定义行 text = reFootnoteDef.ReplaceAllString(text, "") text = reRefDef.ReplaceAllString(text, "") // 4. 移除图片(所有形式) text = reImage.ReplaceAllString(text, "") text = reRefImage.ReplaceAllString(text, "") // 5. 移除代码块(整块移除,因为代码块内容通常不参与自然语言搜索) text = reCodeBlock.ReplaceAllString(text, "$1") // 6. 处理链接(先引用式后内联) text = reLink.ReplaceAllString(text, "$1") text = reRefLink.ReplaceAllString(text, "$1") // 7. 移除行内代码反引号(保留代码内容) text = reCodeInline.ReplaceAllString(text, "") // 8. 移除粗体、斜体、删除线标记(保留文字) text = reBoldStar.ReplaceAllString(text, "$1") text = reBoldUnd.ReplaceAllString(text, "$1") text = reItalicStar.ReplaceAllString(text, "$1") text = reItalicUnd.ReplaceAllString(text, "$1") text = reStrike.ReplaceAllString(text, "$1") // 9. 移除表格分隔线(保留表格内容) text = reTableSep.ReplaceAllString(text, "") // 10. 移除 HTML 标签 text = reHTMLTag.ReplaceAllString(text, "") // 11. 压缩空白 text = reMultiSpace.ReplaceAllString(text, " ") return strings.TrimSpace(text) }