markup: Handle attribute lists in code fences
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 23 Feb 2021 17:04:05 +0000 (18:04 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 24 Feb 2021 10:16:06 +0000 (11:16 +0100)
Fixes #8278

markup/goldmark/convert.go
markup/goldmark/convert_test.go
markup/goldmark/render_hooks.go

index 629e2b15a180e5b9f32b65eb10ee1f6d9d5ec805..639fddaceaf96dd984acb8640cac717d98986b9c 100644 (file)
@@ -22,6 +22,7 @@ import (
        "runtime/debug"
 
        "github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
+       "github.com/yuin/goldmark/ast"
 
        "github.com/gohugoio/hugo/identity"
 
@@ -321,7 +322,28 @@ func newHighlighting(cfg highlight.Config) goldmark.Extender {
                                        highlight.WriteCodeTag(w, language)
                                        return
                                }
-                               w.WriteString(`<div class="highlight">`)
+
+                               w.WriteString(`<div class="highlight`)
+
+                               var attributes []ast.Attribute
+                               if ctx.Attributes() != nil {
+                                       attributes = ctx.Attributes().All()
+                               }
+
+                               if attributes != nil {
+                                       class, found := ctx.Attributes().GetString("class")
+                                       if found {
+                                               w.WriteString(" ")
+                                               w.Write(util.EscapeHTML(class.([]byte)))
+
+                                       }
+                                       _, _ = w.WriteString("\"")
+                                       renderAttributes(w, true, attributes...)
+                               } else {
+                                       _, _ = w.WriteString("\"")
+                               }
+
+                               w.WriteString(">")
                                return
                        }
 
index d35d4d1fd42f3c4c47fd0b875697ae06849270e4..c7367dd01500d279953b42fcf0331cf9935de566 100644 (file)
@@ -226,6 +226,25 @@ func TestConvertAttributes(t *testing.T) {
                        "> foo\n> bar\n{#id .className attrName=attrValue class=\"class1 class2\"}\n",
                        "<blockquote id=\"id\" class=\"className class1 class2\"><p>foo\nbar</p>\n</blockquote>\n",
                },
+               /*{
+                       // TODO(bep) this needs an upstream fix, see https://github.com/yuin/goldmark/issues/195
+                       "Code block, CodeFences=false",
+                       func(conf *markup_config.Config) {
+                               withBlockAttributes(conf)
+                               conf.Highlight.CodeFences = false
+                       },
+                       "```bash\necho 'foo';\n```\n{.myclass}",
+                       "TODO",
+               },*/
+               {
+                       "Code block, CodeFences=true",
+                       func(conf *markup_config.Config) {
+                               withBlockAttributes(conf)
+                               conf.Highlight.CodeFences = true
+                       },
+                       "```bash\necho 'foo';\n````\n{.myclass id=\"myid\"}",
+                       "<div class=\"highlight myclass\" id=\"myid\"><pre style",
+               },
                {
                        "Paragraph",
                        withBlockAttributes,
index 6bedc897e22405337ffde8ee4ac21d751b68c9c0..41db4011bd3c6056d4008966f77f3a56d45ebffc 100644 (file)
 package goldmark
 
 import (
+       "bytes"
        "sync"
 
+       "github.com/spf13/cast"
+
        "github.com/gohugoio/hugo/markup/converter/hooks"
 
        "github.com/yuin/goldmark"
@@ -135,13 +138,41 @@ func (r *hookedRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer)
        reg.Register(ast.KindHeading, r.renderHeading)
 }
 
-// https://github.com/yuin/goldmark/blob/b611cd333a492416b56aa8d94b04a67bf0096ab2/renderer/html/html.go#L404
-func (r *hookedRenderer) RenderAttributes(w util.BufWriter, node ast.Node) {
-       for _, attr := range node.Attributes() {
+func (r *hookedRenderer) renderAttributesForNode(w util.BufWriter, node ast.Node) {
+       renderAttributes(w, false, node.Attributes()...)
+}
+
+var (
+
+       // Attributes with special meaning that does not make sense to render in HTML.
+       attributeExcludes = map[string]bool{
+               "linenos":     true,
+               "hl_lines":    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
+               }
+
+               if attributeExcludes[string(attr.Name)] {
+                       continue
+               }
+
                _, _ = w.WriteString(" ")
                _, _ = w.Write(attr.Name)
                _, _ = w.WriteString(`="`)
-               _, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
+
+               switch v := attr.Value.(type) {
+               case []byte:
+                       _, _ = w.Write(util.EscapeHTML(v))
+               default:
+                       w.WriteString(cast.ToString(v))
+               }
+
                _ = w.WriteByte('"')
        }
 }
@@ -282,7 +313,7 @@ func (r *hookedRenderer) renderDefaultHeading(w util.BufWriter, source []byte, n
                _, _ = w.WriteString("<h")
                _ = w.WriteByte("0123456"[n.Level])
                if n.Attributes() != nil {
-                       r.RenderAttributes(w, node)
+                       r.renderAttributesForNode(w, node)
                }
                _ = w.WriteByte('>')
        } else {