Skip to content

Commit

Permalink
markup/goldmark: Add removeSurroundingParagraph for Markdown images
Browse files Browse the repository at this point in the history
This both:

* Removes any surrounding paragraph nodes
* And transfers any attributes from the surrounding paragraph down to the image node

Closes gohugoio#8362
Fixes gohugoio#10492
  • Loading branch information
bep committed Dec 3, 2022
1 parent d373774 commit 4008331
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 17 deletions.
9 changes: 6 additions & 3 deletions markup/goldmark/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ package goldmark
import (
"bytes"

"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
"github.com/gohugoio/hugo/markup/goldmark/images"
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
"github.com/gohugoio/hugo/markup/goldmark/internal/render"

"github.com/gohugoio/hugo/identity"

"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/yuin/goldmark"
Expand Down Expand Up @@ -131,11 +131,14 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
if cfg.Parser.Attribute.Title {
parserOptions = append(parserOptions, parser.WithAttribute())
}

if cfg.Parser.Attribute.Block {
extensions = append(extensions, attributes.New())
}

if cfg.Parser.Image.RemoveSurroundingParagraph {
extensions = append(extensions, images.New())
}

md := goldmark.New(
goldmark.WithExtensions(
extensions...,
Expand Down
9 changes: 9 additions & 0 deletions markup/goldmark/goldmark_config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ type Parser struct {

// Enables custom attributes.
Attribute ParserAttribute

// Parsing configuration for images.
Image ParserImage
}

type ParserAttribute struct {
Expand All @@ -96,3 +99,9 @@ type ParserAttribute struct {
// Enables custom attributeds for blocks.
Block bool
}

type ParserImage struct {
// Remove any surrounding <p> tags around images.
// Also copies any attributes from the <p> tag to the <img> tag.
RemoveSurroundingParagraph bool
}
63 changes: 63 additions & 0 deletions markup/goldmark/images/integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package images_test

import (
"testing"

"github.com/gohugoio/hugo/hugolib"
)

func TestRemoveSurroundingParagraph(t *testing.T) {
t.Parallel()

filesTemplate := `
-- config.toml --
[markup.goldmark.renderer]
unsafe = false
[markup.goldmark.parser]
[markup.goldmark.parser.image]
removeSurroundingParagraph = true
[markup.goldmark.parser.attribute]
block = true
title = true
-- content/p1.md --
---
title: "p1"
---
![Image](/img.jpg)
{.b}
-- layouts/_default/single.html --
{{ .Content }}
`

t.Run("With Hook", func(t *testing.T) {
files := filesTemplate + `-- layouts/_default/_markup/render-image.html --
<figure class="{{ .Attributes.class }}">
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
</figure>
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()

b.AssertFileContent("public/p1/index.html", "<figure class=\"b\">\n <img src=\"/img.jpg\" alt=\"Image\" />\n</figure>")
})

t.Run("No Hook", func(t *testing.T) {
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: filesTemplate,
NeedsOsFS: false,
},
).Build()

b.AssertFileContent("public/p1/index.html", `<img src="/img.jpg" alt="Image" class="b">`)
})

}
56 changes: 56 additions & 0 deletions markup/goldmark/images/transform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package images

import (
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)

type (
imagesExtension struct{}
)

func New() goldmark.Extender {
return &imagesExtension{}
}

func (e *imagesExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithASTTransformers(
util.Prioritized(&Transformer{}, 300),
),
)
}

type Transformer struct {
}

// Transform transforms the provided Markdown AST.
func (t *Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser.Context) {
ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
if !enter {
return ast.WalkContinue, nil
}

if n, ok := node.(*ast.Image); ok {
if p := n.Parent(); p != nil {
if p.Kind() == ast.KindParagraph {
for _, attr := range p.Attributes() {
// Transfer any attribute set down to the image.
// Image elements does not support attributes on its own,
// so it's safe to just set without checking first.
n.SetAttribute(attr.Name, attr.Value)
}
grandParent := p.Parent()
grandParent.ReplaceChild(grandParent, p, n)
}
}
}

return ast.WalkContinue, nil

})

}
35 changes: 21 additions & 14 deletions markup/goldmark/render_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type linkContext struct {
title string
text hstring.RenderedString
plainText string
*attributes.AttributesHolder
}

func (ctx linkContext) Destination() string {
Expand Down Expand Up @@ -154,11 +155,12 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
err := lr.RenderLink(
w,
linkContext{
page: ctx.DocumentContext().Document,
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
plainText: string(n.Text(source)),
page: ctx.DocumentContext().Document,
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
plainText: string(n.Text(source)),
AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
},
)

Expand Down Expand Up @@ -186,6 +188,9 @@ func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, nod
r.Writer.Write(w, n.Title)
_ = w.WriteByte('"')
}
if n.Attributes() != nil {
attributes.RenderASTAttributes(w, node.Attributes()...)
}
if r.XHTML {
_, _ = w.WriteString(" />")
} else {
Expand Down Expand Up @@ -224,11 +229,12 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
err := lr.RenderLink(
w,
linkContext{
page: ctx.DocumentContext().Document,
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
plainText: string(n.Text(source)),
page: ctx.DocumentContext().Document,
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
plainText: string(n.Text(source)),
AttributesHolder: attributes.Empty,
},
)

Expand Down Expand Up @@ -292,10 +298,11 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
err := lr.RenderLink(
w,
linkContext{
page: ctx.DocumentContext().Document,
destination: url,
text: hstring.RenderedString(label),
plainText: label,
page: ctx.DocumentContext().Document,
destination: url,
text: hstring.RenderedString(label),
plainText: label,
AttributesHolder: attributes.Empty,
},
)

Expand Down
3 changes: 3 additions & 0 deletions markup/internal/attributes/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ func (a Attribute) ValueString() string {
return cast.ToString(a.Value)
}

// Empty holds no attributes.
var Empty = &AttributesHolder{}

type AttributesHolder struct {
// What we get from Goldmark.
attributes []Attribute
Expand Down

0 comments on commit 4008331

Please sign in to comment.