git@cosmocr.at/gitweb.git @ HEAD · summary refs log
HEAD / internal / render / render.go /
internal/render/render.go · 2.8 KiB · raw
view: SourceMarkdown ☑ force

// Package render converts markdown to sanitized HTML (for repo READMEs) and // provides the matching syntax-highlighting stylesheet shared by README code // blocks and blob views. package render

import ( "bytes"

chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
"github.com/microcosm-cc/bluemonday"
"github.com/yuin/goldmark"
highlighting "github.com/yuin/goldmark-highlighting/v2"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
ghtml "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util"

)

// chromaStyle is the chroma style whose CSS classes the highlighter emits and // CSS() generates. Class-based (not inline) so the page CSP can stay strict. const chromaStyle = "github"

// Renderer turns markdown into sanitized HTML. type Renderer struct { md goldmark.Markdown policy *bluemonday.Policy }

// New builds a Renderer with GitHub-flavored markdown + class-based syntax // highlighting, and a sanitizer that permits the highlighter's markup. func New() *Renderer { md := goldmark.New( goldmark.WithExtensions( extension.GFM, highlighting.NewHighlighting( highlighting.WithStyle(chromaStyle), highlighting.WithFormatOptions(chromahtml.WithClasses(true)), ), ), goldmark.WithParserOptions( parser.WithAutoHeadingID(), // Divert ```mermaid blocks before the highlighter turns them to code. parser.WithASTTransformers(util.Prioritized(mermaidTransformer{}, 100)), ), goldmark.WithRendererOptions( ghtml.WithUnsafe(), // raw HTML is re-checked by bluemonday below renderer.WithNodeRenderers(util.Prioritized(mermaidHTMLRenderer{}, 100)), ), )

p := bluemonday.UGCPolicy()
// Allow the chroma highlighter's class-tagged markup (and the mermaid
// <pre class="mermaid"> container).
p.AllowAttrs("class").OnElements("span", "code", "pre", "div")
// Heading anchor ids produced by WithAutoHeadingID.
p.AllowAttrs("id").Globally()
// GFM task-list checkboxes.
p.AllowAttrs("type", "checked", "disabled").OnElements("input")
// Tables from GFM.
p.AllowAttrs("align").OnElements("td", "th")

return &Renderer{md: md, policy: p}

}

// ToHTML renders markdown source to sanitized HTML. func (r *Renderer) ToHTML(src []byte) ([]byte, error) { var buf bytes.Buffer if err := r.md.Convert(src, &buf); err != nil { return nil, err } return r.policy.SanitizeBytes(buf.Bytes()), nil }

// CSS returns the chroma stylesheet for the highlighter's classes. Served as a // static stylesheet so inline styles are never needed. func CSS() ([]byte, error) { style := styles.Get(chromaStyle) formatter := chromahtml.New(chromahtml.WithClasses(true)) var buf bytes.Buffer if err := formatter.WriteCSS(&buf, style); err != nil { return nil, err } return buf.Bytes(), nil }