Files

113 lines
3.1 KiB
Go
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.
package utils
import (
"bytes"
"html/template"
"strings"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
goldmarkhtml "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
)
// markdownEngine 全局 Markdown 渲染引擎
// 启用 GFM 扩展(表格、删除线、任务列表、自动链接等)
// 启用 WithUnsafe 允许原始 HTML 透传,兼容旧文章中的 HTML 内容
var markdownEngine = goldmark.New(
goldmark.WithExtensions(extension.GFM),
goldmark.WithRendererOptions(
goldmarkhtml.WithUnsafe(),
goldmarkhtml.WithHardWraps(),
),
)
// searchableTextParser 复用同一套扩展配置,但只做解析,不渲染。
// 与 markdownEngine 共享 goldmark 实例的 Parser,避免重复构造解析器开销。
var searchableTextParser = markdownEngine.Parser()
// 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())
}
// GenerateSearchableText 提取 Markdown 中的可见纯文本(基于 goldmark AST)
func GenerateSearchableText(raw string) string {
if raw == "" {
return ""
}
// 全角空格转半角
raw = strings.ReplaceAll(raw, "\u3000", " ")
source := []byte(raw)
doc := searchableTextParser.Parse(text.NewReader(source))
var sb strings.Builder
extractSearchableText(doc, source, &sb)
// 压缩空白(等价于原 reMultiSpace + TrimSpace)
return strings.Join(strings.Fields(sb.String()), " ")
}
// extractSearchableText 深度优先遍历 AST,把可见文本写入 sb
func extractSearchableText(node ast.Node, source []byte, sb *strings.Builder) {
switch n := node.(type) {
case *ast.Image:
// 图片:整棵子树跳过(含 alt 文本),对应原 reImage / reRefImage
return
case *ast.HTMLBlock, *ast.RawHTML:
// 原始 HTML:跳过,对应原 reHTMLTag
return
case *ast.AutoLink:
// 自动链接 <url> / <email>:保留 URL / 邮箱
// n.URL(source) 返回链接目标;邮箱自动链接会带 mailto: 前缀,这里去掉
url := string(n.URL(source))
url = strings.TrimPrefix(url, "mailto:")
sb.WriteString(url)
sb.WriteByte(' ')
return
case *ast.FencedCodeBlock, *ast.CodeBlock:
lines := n.Lines()
for i := 0; i < lines.Len(); i++ {
seg := lines.At(i)
sb.Write(seg.Value(source))
}
sb.WriteByte(' ')
return
case *ast.Text:
sb.Write(n.Segment.Value(source))
if n.SoftLineBreak() || n.HardLineBreak() {
sb.WriteByte(' ')
}
return
case *ast.String:
// 代码块 / 部分扩展产生的字符串节点
sb.Write(n.Value)
return
}
// 递归子节点
for child := node.FirstChild(); child != nil; child = child.NextSibling() {
extractSearchableText(child, source, sb)
}
// 块级元素结束后补一个空格,避免相邻段落/列表项文本粘连
switch node.Kind() {
case ast.KindParagraph, ast.KindHeading, ast.KindListItem,
ast.KindBlockquote, ast.KindList:
sb.WriteByte(' ')
}
}