package markdown import ( "context" "fmt" "io" "strings" "github.com/microcosm-cc/bluemonday" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" gm_html "github.com/yuin/goldmark/renderer/html" "lmika.dev/lmika/weiro/models" ) type Renderer struct { mdParser goldmark.Markdown bmPolicy *bluemonday.Policy htmlTransforms []htmlTransform } func NewRendererForUI() *Renderer { mdParser := goldmark.New( goldmark.WithExtensions(extension.GFM), goldmark.WithRendererOptions( gm_html.WithUnsafe(), ), ) bmPolicy := bluemonday.UGCPolicy() return &Renderer{ mdParser: mdParser, bmPolicy: bmPolicy, htmlTransforms: []htmlTransform{ rewriteImgSrc(func(ctx context.Context, src string) string { if strings.HasPrefix(src, "/uploads/") { if site, ok := models.GetSite(ctx); ok { return fmt.Sprintf("/sites/%v/uploads/slug/%v", site.ID, strings.TrimPrefix(src, "/uploads/")) } return src } return src }), }, } } func NewRendererForSite() *Renderer { mdParser := goldmark.New( goldmark.WithExtensions(extension.GFM), goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), goldmark.WithRendererOptions( gm_html.WithUnsafe(), ), ) return &Renderer{ mdParser: mdParser, } } func (r *Renderer) Render(ctx context.Context, in string) (string, error) { var sb strings.Builder if err := r.mdParser.Convert([]byte(in), &sb); err != nil { return "", err } outHTML := applyTransforms(ctx, sb.String(), r.htmlTransforms) if r.bmPolicy != nil { return r.bmPolicy.Sanitize(outHTML), nil } return sb.String(), nil } func (r *Renderer) RenderTo(ctx context.Context, w io.Writer, in string) error { s, err := r.Render(ctx, in) if err != nil { return err } _, err = w.Write([]byte(s)) return err }