package main import ( "bytes" "io" marktag "git.jlel.se/jlelse/goldmark-mark" chromahtml "github.com/alecthomas/chroma/formatters/html" "github.com/yuin/goldmark" emoji "github.com/yuin/goldmark-emoji" highlighting "github.com/yuin/goldmark-highlighting" "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" ) func (a *goBlog) initMarkdown() { if a.md != nil && a.absoluteMd != nil && a.titleMd != nil { // Already initialized return } defaultGoldmarkOptions := []goldmark.Option{ goldmark.WithRendererOptions( html.WithUnsafe(), ), goldmark.WithParserOptions( parser.WithAutoHeadingID(), ), goldmark.WithExtensions( extension.Table, extension.Strikethrough, extension.Footnote, extension.Typographer, extension.Linkify, marktag.Mark, emoji.Emoji, highlighting.NewHighlighting( highlighting.WithCustomStyle(chromaGoBlogStyle), highlighting.WithFormatOptions( chromahtml.ClassPrefix("c-"), chromahtml.WithClasses(true), ), ), ), } publicAddress := "" if srv := a.cfg.Server; srv != nil { publicAddress = srv.PublicAddress } a.md = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{ absoluteLinks: false, publicAddress: publicAddress, }))...) a.absoluteMd = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{ absoluteLinks: true, publicAddress: publicAddress, }))...) a.titleMd = goldmark.New( goldmark.WithParser( // Override, no need for special Markdown parsers parser.NewParser( parser.WithBlockParsers( util.Prioritized(parser.NewParagraphParser(), 1000)), parser.WithInlineParsers(), parser.WithParagraphTransformers(), ), ), goldmark.WithExtensions( extension.Typographer, emoji.Emoji, ), ) } func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLinks bool) (err error) { if absoluteLinks { err = a.absoluteMd.Convert([]byte(source), w) } else { err = a.md.Convert([]byte(source), w) } return err } func (a *goBlog) renderText(s string) string { if s == "" { return "" } pipeReader, pipeWriter := io.Pipe() go func() { writeErr := a.renderMarkdownToWriter(pipeWriter, s, false) _ = pipeWriter.CloseWithError(writeErr) }() text, readErr := htmlTextFromReader(pipeReader) _ = pipeReader.CloseWithError(readErr) if readErr != nil { return "" } return text } func (a *goBlog) renderMdTitle(s string) string { if s == "" { return "" } pipeReader, pipeWriter := io.Pipe() go func() { writeErr := a.titleMd.Convert([]byte(s), pipeWriter) _ = pipeWriter.CloseWithError(writeErr) }() text, readErr := htmlTextFromReader(pipeReader) _ = pipeReader.CloseWithError(readErr) if readErr != nil { return "" } return text } // Extensions etc... // Links type customExtension struct { absoluteLinks bool publicAddress string } func (l *customExtension) Extend(m goldmark.Markdown) { m.Renderer().AddOptions(renderer.WithNodeRenderers( util.Prioritized(&customRenderer{ absoluteLinks: l.absoluteLinks, publicAddress: l.publicAddress, }, 500), )) } type customRenderer struct { absoluteLinks bool publicAddress string } 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) dest := string(n.Destination) // Make destination absolute if it's relative if c.absoluteLinks && c.publicAddress != "" { resolved, err := resolveURLReferences(c.publicAddress, dest) if err != nil { return ast.WalkStop, err } if len(resolved) > 0 { dest = resolved[0] } } hb := newHtmlBuilder(w) hb.writeElementOpen("a", "href", dest) imgEls := []interface{}{"src", dest, "alt", string(n.Text(source)), "loading", "lazy"} if len(n.Title) > 0 { imgEls = append(imgEls, "title", string(n.Title)) } hb.writeElementOpen("img", imgEls...) hb.writeElementClose("a") return ast.WalkSkipChildren, nil }