mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-30 22:39:58 +02:00
markup/goldmark: Add removeSurroundingParagraph for Markdown images
* Removes any surrounding paragraph nodes * And transfers any attributes from the surrounding paragraph down to the image node * Adds IsBlock and Ordinal (zero based) field to the image context passed to the image render hooks IsBlock is set to true if `wrapStandAloneImageWithinParagraph = false` and the image's parent node has only one child. Closes #8362 Fixes #10492 Fixes #10494 Fixes #10501
This commit is contained in:
113
markup/goldmark/images/integration_test.go
Normal file
113
markup/goldmark/images/integration_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package images_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestDisableWrapStandAloneImageWithinParagraph(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
filesTemplate := `
|
||||
-- config.toml --
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = false
|
||||
[markup.goldmark.parser]
|
||||
wrapStandAloneImageWithinParagraph = CONFIG_VALUE
|
||||
[markup.goldmark.parser.attribute]
|
||||
block = true
|
||||
title = true
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
|
||||
This is an inline image: . Some more text.
|
||||
|
||||

|
||||
{.b}
|
||||
|
||||
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Content }}
|
||||
`
|
||||
|
||||
t.Run("With Hook, no wrap", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "false")
|
||||
files = files + `-- layouts/_default/_markup/render-image.html --
|
||||
{{ if .IsBlock }}
|
||||
<figure class="{{ .Attributes.class }}">
|
||||
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
|
||||
</figure>
|
||||
{{ else }}
|
||||
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
|
||||
{{ end }}
|
||||
`
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
NeedsOsFS: false,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html",
|
||||
"This is an inline image: \n\t<img src=\"/inline.jpg\" alt=\"Inline Image\" />\n. Some more text.</p>",
|
||||
"<figure class=\"b\">\n\t<img src=\"/block.jpg\" alt=\"Block Image\" />",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("With Hook, wrap", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "true")
|
||||
files = files + `-- layouts/_default/_markup/render-image.html --
|
||||
{{ if .IsBlock }}
|
||||
<figure class="{{ .Attributes.class }}">
|
||||
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
|
||||
</figure>
|
||||
{{ else }}
|
||||
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" />
|
||||
{{ end }}
|
||||
`
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
NeedsOsFS: false,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html",
|
||||
"This is an inline image: \n\t<img src=\"/inline.jpg\" alt=\"Inline Image\" />\n. Some more text.</p>",
|
||||
"<p class=\"b\">\n\t<img src=\"/block.jpg\" alt=\"Block Image\" />\n</p>",
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("No Hook, no wrap", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "false")
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
NeedsOsFS: false,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "<p>This is an inline image: <img src=\"/inline.jpg\" alt=\"Inline Image\">. Some more text.</p>\n<img src=\"/block.jpg\" alt=\"Block Image\" class=\"b\">")
|
||||
})
|
||||
|
||||
t.Run("No Hook, wrap", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "CONFIG_VALUE", "true")
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
NeedsOsFS: false,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "<p class=\"b\"><img src=\"/block.jpg\" alt=\"Block Image\"></p>")
|
||||
})
|
||||
|
||||
}
|
77
markup/goldmark/images/transform.go
Normal file
77
markup/goldmark/images/transform.go
Normal file
@@ -0,0 +1,77 @@
|
||||
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 {
|
||||
wrapStandAloneImageWithinParagraph bool
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// Used to signal to the rendering step that an image is used in a block context.
|
||||
// Dont's change this; the prefix must match the internalAttrPrefix in the root goldmark package.
|
||||
AttrIsBlock = "_h__isBlock"
|
||||
AttrOrdinal = "_h__ordinal"
|
||||
)
|
||||
|
||||
func New(wrapStandAloneImageWithinParagraph bool) goldmark.Extender {
|
||||
return &imagesExtension{wrapStandAloneImageWithinParagraph: wrapStandAloneImageWithinParagraph}
|
||||
}
|
||||
|
||||
func (e *imagesExtension) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(
|
||||
parser.WithASTTransformers(
|
||||
util.Prioritized(&Transformer{wrapStandAloneImageWithinParagraph: e.wrapStandAloneImageWithinParagraph}, 300),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
type Transformer struct {
|
||||
wrapStandAloneImageWithinParagraph bool
|
||||
}
|
||||
|
||||
// Transform transforms the provided Markdown AST.
|
||||
func (t *Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser.Context) {
|
||||
var ordinal int
|
||||
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 {
|
||||
parent := n.Parent()
|
||||
n.SetAttributeString(AttrOrdinal, ordinal)
|
||||
ordinal++
|
||||
|
||||
if !t.wrapStandAloneImageWithinParagraph {
|
||||
isBlock := parent.ChildCount() == 1
|
||||
if isBlock {
|
||||
n.SetAttributeString(AttrIsBlock, true)
|
||||
}
|
||||
|
||||
if isBlock && parent.Kind() == ast.KindParagraph {
|
||||
for _, attr := range parent.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 := parent.Parent()
|
||||
grandParent.ReplaceChild(grandParent, parent, n)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ast.WalkContinue, nil
|
||||
|
||||
})
|
||||
|
||||
}
|
Reference in New Issue
Block a user