Repository: gohugoio/hugo (language: go)

Issue:
Transfer paragraph attributes to image when used as a block
See discussion in https://discourse.gohugo.io/t/is-it-possible-to-use-attribute-lists-with-render-hooks/31374/6?u=bep

So, images are by definition an inline element, but it most typically is used in block contexts, e.g.:

```md
This is some text.

![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 1")
{ .myclass }

And then some more text.

```

I have not tested the above, but I suspect that the attributes list `{ .myclass }` will be applied to the surround paragraph block.

Since the parsing complexity prevents us from adding attributes to links/images, an option may be to transfer the surrounding block's attributes to the image.

We can certainly have an option to toggle this off, but I suspect that most people would say that the attribute above belongs to the image and not the paragraph.

Thoughts?


Add markdownimage.Ordinal
Like we have on code blocks. 


Discussion / hints:
For images that are treated as blocks this make sense.

but it does raise the question of what the config option should be... such as:
- "treat images as blocks"
- "treat "orphan" images as blocks"
- "treat images with attributes as blocks"

Just a thought:
Would it be possible to do something similar to how footnotes are treated for inline elements (<img> and <a>)?  would that help with the parsing complexity? 

@HenrySkup thanks for your input.

1) As to the config option, I think it should be very specific. We're not "treating images as blocks" -- they're still an inline element that happens to be surrounded by a block (paragraph). So, something ala `moveParagraphAttributesToImageBlocks` or ... something.

2) I don't understand the footnote reference, but when working with Goldmark, it's much easier to reason with its AST (instead of the actual markdown text) and say stuff ala "if an image is the only child of a paragraph, then ...".

> 2\. I don't understand the footnote reference, but when working with Goldmark, it's much easier to reason with its AST (instead of the actual markdown text) and say stuff ala "if an image is the only child of a paragraph, then ...".

I was thinking about something like:

```
This would be some normal text. ![alt](url "title){^1} Bla bla bla.


{^1}: attribute list
```

but I now realize that this would prob be just as difficult and quite a departure from markdown syntax.

However on the forum @jmooring made [an excellent comment](https://discourse.gohugo.io/t/is-it-possible-to-use-attribute-lists-with-render-hooks/31374/9).  An i realized that I too had hacked together a render hook for this... though haven't been using it...

[Gist here.](https://gist.github.com/HenrySkup/fa8abe26ac387f139f04f256cf28629e)

If I remember correctly, there were some issues creating a properly formatted HTML string.


> 2\. I don't understand the footnote reference, but when working with Goldmark, it's much easier to reason with its AST (instead of the actual markdown text) and say stuff ala "if an image is the only child of a paragraph, then ...".

Just found [this in the common mark spec](https://spec.commonmark.org/0.27/#example-545), which is *kind of* what I meant... i think.


Fix patch:
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
index a8666bdf080..a59da939e8b 100644
--- a/markup/converter/hooks/hooks.go
+++ b/markup/converter/hooks/hooks.go
@@ -37,6 +37,12 @@ type LinkContext interface {
 	PlainText() string
 }
 
+type ImageLinkContext interface {
+	LinkContext
+	IsBlock() bool
+	Ordinal() int
+}
+
 type CodeblockContext interface {
 	AttributesProvider
 	text.Positioner
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index ba85831b029..a179cd2339a 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -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"
@@ -33,6 +33,10 @@ import (
 	"github.com/yuin/goldmark/text"
 )
 
+const (
+	internalAttrPrefix = "_h__"
+)
+
 // Provider is the package entry point.
 var Provider converter.ProviderProvider = provide{}
 
@@ -92,6 +96,8 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
 		parserOptions []parser.Option
 	)
 
+	extensions = append(extensions, images.New(cfg.Parser.WrapStandAloneImageWithinParagraph))
+
 	if mcfg.Highlight.CodeFences {
 		extensions = append(extensions, codeblocks.New())
 	}
@@ -131,7 +137,6 @@ 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())
 	}
diff --git a/markup/goldmark/goldmark_config/config.go b/markup/goldmark/goldmark_config/config.go
index a3238091b18..ff0b6bbef92 100644
--- a/markup/goldmark/goldmark_config/config.go
+++ b/markup/goldmark/goldmark_config/config.go
@@ -36,8 +36,9 @@ var Default = Config{
 		Unsafe: false,
 	},
 	Parser: Parser{
-		AutoHeadingID:     true,
-		AutoHeadingIDType: AutoHeadingIDTypeGitHub,
+		AutoHeadingID:                      true,
+		AutoHeadingIDType:                  AutoHeadingIDTypeGitHub,
+		WrapStandAloneImageWithinParagraph: true,
 		Attribute: ParserAttribute{
 			Title: true,
 			Block: false,
@@ -88,6 +89,9 @@ type Parser struct {
 
 	// Enables custom attributes.
 	Attribute ParserAttribute
+
+	// Whether to wrap stand-alone images within a paragraph or not.
+	WrapStandAloneImageWithinParagraph bool
 }
 
 type ParserAttribute struct {
diff --git a/markup/goldmark/images/transform.go b/markup/goldmark/images/transform.go
new file mode 100644
index 00000000000..4eb793ae158
--- /dev/null
+++ b/markup/goldmark/images/transform.go
@@ -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
+
+	})
+
+}
diff --git a/markup/goldmark/links/transform.go b/markup/goldmark/links/transform.go
new file mode 100644
index 00000000000..b4f6b6dc52f
--- /dev/null
+++ b/markup/goldmark/links/transform.go
@@ -0,0 +1,73 @@
+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 (
+	linksExtension 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"
+)
+
+func New(wrapStandAloneImageWithinParagraph bool) goldmark.Extender {
+	return &linksExtension{wrapStandAloneImageWithinParagraph: wrapStandAloneImageWithinParagraph}
+}
+
+func (e *linksExtension) 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) {
+	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()
+
+			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
+
+	})
+
+}
diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go
index e28f816d658..f36f9f4e690 100644
--- a/markup/goldmark/render_hooks.go
+++ b/markup/goldmark/render_hooks.go
@@ -20,6 +20,7 @@ import (
 	"github.com/gohugoio/hugo/common/types/hstring"
 	"github.com/gohugoio/hugo/markup/converter/hooks"
 	"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
+	"github.com/gohugoio/hugo/markup/goldmark/images"
 	"github.com/gohugoio/hugo/markup/goldmark/internal/render"
 	"github.com/gohugoio/hugo/markup/internal/attributes"
 
@@ -52,16 +53,13 @@ type linkContext struct {
 	title       string
 	text        hstring.RenderedString
 	plainText   string
+	*attributes.AttributesHolder
 }
 
 func (ctx linkContext) Destination() string {
 	return ctx.destination
 }
 
-func (ctx linkContext) Resolved() bool {
-	return false
-}
-
 func (ctx linkContext) Page() any {
 	return ctx.page
 }
@@ -78,6 +76,20 @@ func (ctx linkContext) Title() string {
 	return ctx.title
 }
 
+type imageLinkContext struct {
+	linkContext
+	ordinal int
+	isBlock bool
+}
+
+func (ctx imageLinkContext) IsBlock() bool {
+	return ctx.isBlock
+}
+
+func (ctx imageLinkContext) Ordinal() int {
+	return ctx.ordinal
+}
+
 type headingContext struct {
 	page      any
 	level     int
@@ -151,14 +163,36 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
 	text := ctx.Buffer.Bytes()[pos:]
 	ctx.Buffer.Truncate(pos)
 
+	var (
+		isBlock bool
+		ordinal int
+	)
+	if b, ok := n.AttributeString(images.AttrIsBlock); ok && b.(bool) {
+		isBlock = true
+	}
+	if n, ok := n.AttributeString(images.AttrOrdinal); ok {
+		ordinal = n.(int)
+	}
+
+	// We use the attributes to signal from the parser whether the image is in
+	// a block context or not.
+	// We may find a better way to do that, but for now, we'll need to remove any
+	// internal attributes before rendering.
+	attrs := r.filterInternalAttributes(n.Attributes())
+
 	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)),
+		imageLinkContext{
+			linkContext: linkContext{
+				page:             ctx.DocumentContext().Document,
+				destination:      string(n.Destination),
+				title:            string(n.Title),
+				text:             hstring.RenderedString(text),
+				plainText:        string(n.Text(source)),
+				AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerGeneral),
+			},
+			ordinal: ordinal,
+			isBlock: isBlock,
 		},
 	)
 
@@ -167,6 +201,17 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
 	return ast.WalkContinue, err
 }
 
+func (r *hookedRenderer) filterInternalAttributes(attrs []ast.Attribute) []ast.Attribute {
+	n := 0
+	for _, x := range attrs {
+		if !bytes.HasPrefix(x.Name, []byte(internalAttrPrefix)) {
+			attrs[n] = x
+			n++
+		}
+	}
+	return attrs[:n]
+}
+
 // Fall back to the default Goldmark render funcs. Method below borrowed from:
 // https://github.com/yuin/goldmark/blob/b611cd333a492416b56aa8d94b04a67bf0096ab2/renderer/html/html.go#L404
 func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
@@ -186,6 +231,10 @@ func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, nod
 		r.Writer.Write(w, n.Title)
 		_ = w.WriteByte('"')
 	}
+	if n.Attributes() != nil {
+		attrs := r.filterInternalAttributes(n.Attributes())
+		attributes.RenderASTAttributes(w, attrs...)
+	}
 	if r.XHTML {
 		_, _ = w.WriteString(" />")
 	} else {
@@ -224,11 +273,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,
 		},
 	)
 
@@ -292,10 +342,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,
 		},
 	)
 
diff --git a/markup/internal/attributes/attributes.go b/markup/internal/attributes/attributes.go
index 6887409834e..a67b51acbc8 100644
--- a/markup/internal/attributes/attributes.go
+++ b/markup/internal/attributes/attributes.go
@@ -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


Test patch:
diff --git a/markup/goldmark/images/integration_test.go b/markup/goldmark/images/integration_test.go
new file mode 100644
index 00000000000..e8d1b880e2b
--- /dev/null
+++ b/markup/goldmark/images/integration_test.go
@@ -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: ![Inline Image](/inline.jpg). Some more text.
+
+![Block Image](/block.jpg)
+{.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>")
+	})
+
+}
diff --git a/markup/goldmark/links/integration_test.go b/markup/goldmark/links/integration_test.go
new file mode 100644
index 00000000000..20d4d74b102
--- /dev/null
+++ b/markup/goldmark/links/integration_test.go
@@ -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: ![Inline Image](/inline.jpg). Some more text.
+
+![Block Image](/block.jpg)
+{.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 }}|{{ .Ordinal }}" />
+</figure>
+{{ else }}
+	<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}|{{ .Ordinal }}" />
+{{ 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|0\" />\n. Some more text.</p>",
+			"<figure class=\"b\">\n\t<img src=\"/block.jpg\" alt=\"Block Image|1\" />",
+		)
+	})
+
+	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>")
+	})
+
+}