diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-02-17 13:04:00 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-02-24 18:59:50 +0100 |
commit | 08fdca9d9365eaf1e496a12e2af5e18617bd0e66 (patch) | |
tree | 6c6942d1b74a4160d93a997860bafd52b92025f5 /markup/goldmark/render_hooks.go | |
parent | 2c20f5bc00b604e72b3b7e401fbdbf9447fe3470 (diff) |
Add Markdown diagrams and render hooks for code blocks
You can now create custom hook templates for code blocks, either one for all (`render-codeblock.html`) or for a given code language (e.g. `render-codeblock-go.html`).
We also used this new hook to add support for diagrams in Hugo:
* Goat (Go ASCII Tool) is built-in and enabled by default; just create a fenced code block with the language `goat` and start draw your Ascii diagrams.
* Another popular alternative for diagrams in Markdown, Mermaid (supported by GitHub), can also be implemented with a simple template. See the Hugo documentation for more information.
Updates #7765
Closes #9538
Fixes #9553
Fixes #8520
Fixes #6702
Fixes #9558
Diffstat (limited to 'markup/goldmark/render_hooks.go')
-rw-r--r-- | markup/goldmark/render_hooks.go | 143 |
1 files changed, 47 insertions, 96 deletions
diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go index 1862c2125..d5e35380a 100644 --- a/markup/goldmark/render_hooks.go +++ b/markup/goldmark/render_hooks.go @@ -16,11 +16,10 @@ package goldmark import ( "bytes" "strings" - "sync" - - "github.com/spf13/cast" "github.com/gohugoio/hugo/markup/converter/hooks" + "github.com/gohugoio/hugo/markup/goldmark/internal/render" + "github.com/gohugoio/hugo/markup/internal/attributes" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" @@ -44,28 +43,6 @@ func newLinks() goldmark.Extender { return &links{} } -type attributesHolder struct { - // What we get from Goldmark. - astAttributes []ast.Attribute - - // What we send to the the render hooks. - attributesInit sync.Once - attributes map[string]string -} - -func (a *attributesHolder) Attributes() map[string]string { - a.attributesInit.Do(func() { - a.attributes = make(map[string]string) - for _, attr := range a.astAttributes { - if strings.HasPrefix(string(attr.Name), "on") { - continue - } - a.attributes[string(attr.Name)] = string(util.EscapeHTML(attr.Value.([]byte))) - } - }) - return a.attributes -} - type linkContext struct { page interface{} destination string @@ -104,7 +81,7 @@ type headingContext struct { anchor string text string plainText string - *attributesHolder + *attributes.AttributesHolder } func (ctx headingContext) Page() interface{} { @@ -143,52 +120,17 @@ func (r *hookedRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) reg.Register(ast.KindHeading, r.renderHeading) } -func (r *hookedRenderer) renderAttributesForNode(w util.BufWriter, node ast.Node) { - renderAttributes(w, false, node.Attributes()...) -} - -// Attributes with special meaning that does not make sense to render in HTML. -var attributeExcludes = map[string]bool{ - "hl_lines": true, - "hl_style": true, - "linenos": true, - "linenostart": true, -} - -func renderAttributes(w util.BufWriter, skipClass bool, attributes ...ast.Attribute) { - for _, attr := range attributes { - if skipClass && bytes.Equal(attr.Name, []byte("class")) { - continue - } - - a := strings.ToLower(string(attr.Name)) - if attributeExcludes[a] || strings.HasPrefix(a, "on") { - continue - } - - _, _ = w.WriteString(" ") - _, _ = w.Write(attr.Name) - _, _ = w.WriteString(`="`) - - switch v := attr.Value.(type) { - case []byte: - _, _ = w.Write(util.EscapeHTML(v)) - default: - w.WriteString(cast.ToString(v)) - } - - _ = w.WriteByte('"') - } -} - func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Image) - var h hooks.Renderers + var lr hooks.LinkRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.ImageRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.ImageRendererType, nil) + ok = h != nil + if ok { + lr = h.(hooks.LinkRenderer) + } } if !ok { @@ -197,15 +139,15 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N if entering { // Store the current pos so we can capture the rendered text. - ctx.pushPos(ctx.Buffer.Len()) + ctx.PushPos(ctx.Buffer.Len()) return ast.WalkContinue, nil } - pos := ctx.popPos() + pos := ctx.PopPos() text := ctx.Buffer.Bytes()[pos:] ctx.Buffer.Truncate(pos) - err := h.ImageRenderer.RenderLink( + err := lr.RenderLink( w, linkContext{ page: ctx.DocumentContext().Document, @@ -216,7 +158,7 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N }, ) - ctx.AddIdentity(h.ImageRenderer) + ctx.AddIdentity(lr) return ast.WalkContinue, err } @@ -250,12 +192,15 @@ func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, nod func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Link) - var h hooks.Renderers + var lr hooks.LinkRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.LinkRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil) + ok = h != nil + if ok { + lr = h.(hooks.LinkRenderer) + } } if !ok { @@ -264,15 +209,15 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No if entering { // Store the current pos so we can capture the rendered text. - ctx.pushPos(ctx.Buffer.Len()) + ctx.PushPos(ctx.Buffer.Len()) return ast.WalkContinue, nil } - pos := ctx.popPos() + pos := ctx.PopPos() text := ctx.Buffer.Bytes()[pos:] ctx.Buffer.Truncate(pos) - err := h.LinkRenderer.RenderLink( + err := lr.RenderLink( w, linkContext{ page: ctx.DocumentContext().Document, @@ -286,7 +231,7 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No // TODO(bep) I have a working branch that fixes these rather confusing identity types, // but for now it's important that it's not .GetIdentity() that's added here, // to make sure we search the entire chain on changes. - ctx.AddIdentity(h.LinkRenderer) + ctx.AddIdentity(lr) return ast.WalkContinue, err } @@ -319,12 +264,15 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as } n := node.(*ast.AutoLink) - var h hooks.Renderers + var lr hooks.LinkRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.LinkRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil) + ok = h != nil + if ok { + lr = h.(hooks.LinkRenderer) + } } if !ok { @@ -337,7 +285,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as url = "mailto:" + url } - err := h.LinkRenderer.RenderLink( + err := lr.RenderLink( w, linkContext{ page: ctx.DocumentContext().Document, @@ -350,7 +298,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as // TODO(bep) I have a working branch that fixes these rather confusing identity types, // but for now it's important that it's not .GetIdentity() that's added here, // to make sure we search the entire chain on changes. - ctx.AddIdentity(h.LinkRenderer) + ctx.AddIdentity(lr) return ast.WalkContinue, err } @@ -383,12 +331,15 @@ func (r *hookedRenderer) renderAutoLinkDefault(w util.BufWriter, source []byte, func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Heading) - var h hooks.Renderers + var hr hooks.HeadingRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.HeadingRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.HeadingRendererType, nil) + ok = h != nil + if ok { + hr = h.(hooks.HeadingRenderer) + } } if !ok { @@ -397,11 +348,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast if entering { // Store the current pos so we can capture the rendered text. - ctx.pushPos(ctx.Buffer.Len()) + ctx.PushPos(ctx.Buffer.Len()) return ast.WalkContinue, nil } - pos := ctx.popPos() + pos := ctx.PopPos() text := ctx.Buffer.Bytes()[pos:] ctx.Buffer.Truncate(pos) // All ast.Heading nodes are guaranteed to have an attribute called "id" @@ -409,7 +360,7 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast anchori, _ := n.AttributeString("id") anchor := anchori.([]byte) - err := h.HeadingRenderer.RenderHeading( + err := hr.RenderHeading( w, headingContext{ page: ctx.DocumentContext().Document, @@ -417,11 +368,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast anchor: string(anchor), text: string(text), plainText: string(n.Text(source)), - attributesHolder: &attributesHolder{astAttributes: n.Attributes()}, + AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral), }, ) - ctx.AddIdentity(h.HeadingRenderer) + ctx.AddIdentity(hr) return ast.WalkContinue, err } @@ -432,7 +383,7 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n _, _ = w.WriteString("<h") _ = w.WriteByte("0123456"[n.Level]) if n.Attributes() != nil { - r.renderAttributesForNode(w, node) + attributes.RenderASTAttributes(w, node.Attributes()...) } _ = w.WriteByte('>') } else { |