134 lines
4.3 KiB
Go
134 lines
4.3 KiB
Go
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)
|
|
}
|