package main import ( "bytes" "strings" kemoji "github.com/kyokomi/emoji/v2" "github.com/yuin/goldmark" emoji "github.com/yuin/goldmark-emoji" "github.com/yuin/goldmark-emoji/definition" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/util" ) var emojilib definition.Emojis var defaultMarkdown, absoluteMarkdown goldmark.Markdown func initMarkdown() { defaultGoldmarkOptions := []goldmark.Option{ goldmark.WithRendererOptions( html.WithUnsafe(), ), goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), goldmark.WithExtensions( extension.Table, extension.Strikethrough, extension.Footnote, extension.Typographer, extension.Linkify, // Emojis emoji.New( emoji.WithEmojis(emojiGoLib()), ), ), } defaultMarkdown = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{absoluteLinks: false}))...) absoluteMarkdown = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{absoluteLinks: true}))...) } func renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err error) { var buffer bytes.Buffer if absoluteLinks { err = absoluteMarkdown.Convert([]byte(source), &buffer) } else { err = defaultMarkdown.Convert([]byte(source), &buffer) } return buffer.Bytes(), err } // Extensions etc... // All emojis from emoji lib func emojiGoLib() definition.Emojis { if emojilib == nil { var emojis []definition.Emoji for shotcode, e := range kemoji.CodeMap() { emojis = append(emojis, definition.NewEmoji(e, []rune(e), strings.ReplaceAll(shotcode, ":", ""))) } emojilib = definition.NewEmojis(emojis...) } return emojilib } // Links type customExtension struct { absoluteLinks bool } func (l *customExtension) Extend(m goldmark.Markdown) { m.Renderer().AddOptions(renderer.WithNodeRenderers( util.Prioritized(&customRenderer{ absoluteLinks: l.absoluteLinks, }, 500), )) } type customRenderer struct { absoluteLinks bool } func (c *customRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) { r.Register(ast.KindLink, c.renderLink) r.Register(ast.KindImage, c.renderImage) } func (c *customRenderer) renderLink(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if entering { n := node.(*ast.Link) _, _ = w.WriteString("') } else { _, _ = w.WriteString("") } return ast.WalkContinue, nil } func (c *customRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil } n := node.(*ast.Image) // Make URL absolute if it's relative destination := util.URLEscape(n.Destination, true) if bytes.HasPrefix(destination, []byte("/")) { destination = util.EscapeHTML(append([]byte(appConfig.Server.PublicAddress), destination...)) } else { destination = util.EscapeHTML(destination) } _, _ = w.WriteString("") _, _ = w.WriteString("\"")") return ast.WalkSkipChildren, nil }