diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go index dcaf8d3e179..c547fe1e0d1 100644 --- a/markup/goldmark/convert.go +++ b/markup/goldmark/convert.go @@ -47,8 +47,7 @@ import ( // Provider is the package entry point. var Provider converter.ProviderProvider = provide{} -type provide struct { -} +type provide struct{} func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) { md := newMarkdown(cfg) @@ -199,10 +198,21 @@ func (b *bufWriter) Flush() error { type renderContext struct { *bufWriter - pos int + positions []int renderContextData } +func (ctx *renderContext) pushPos(n int) { + ctx.positions = append(ctx.positions, n) +} + +func (ctx *renderContext) popPos() int { + i := len(ctx.positions) - 1 + p := ctx.positions[i] + ctx.positions = ctx.positions[:i] + return p +} + type renderContextData interface { RenderContext() converter.RenderContext DocumentContext() converter.DocumentContext diff --git a/markup/goldmark/integration_test.go b/markup/goldmark/integration_test.go index fd90a6824e5..0f47f4adabd 100644 --- a/markup/goldmark/integration_test.go +++ b/markup/goldmark/integration_test.go @@ -61,6 +61,42 @@ foo `) } +// Issue 9504 +func TestLinkInTitle(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- content/p1.md -- +--- +title: "p1" +--- +## Hello [Test](https://example.com) +-- layouts/_default/single.html -- +{{ .Content }} +-- layouts/_default/_markup/render-heading.html -- + + {{ .Text | safeHTML }} + # + +-- layouts/_default/_markup/render-link.html -- +{{ .Text | safeHTML }} + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + NeedsOsFS: false, + }, + ).Build() + + b.AssertFileContent("public/p1/index.html", + "

\n Hello Test\n\n #\n

", + ) +} + func BenchmarkSiteWithRenderHooks(b *testing.B) { files := ` -- config.toml -- diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go index e6d959abfe7..5c600204cf9 100644 --- a/markup/goldmark/render_hooks.go +++ b/markup/goldmark/render_hooks.go @@ -144,16 +144,13 @@ 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{ - "hl_lines": true, - "hl_style": true, - "linenos": true, - "linenostart": true, - } -) +// 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 { @@ -197,12 +194,13 @@ 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.pos = ctx.Buffer.Len() + ctx.pushPos(ctx.Buffer.Len()) return ast.WalkContinue, nil } - text := ctx.Buffer.Bytes()[ctx.pos:] - ctx.Buffer.Truncate(ctx.pos) + pos := ctx.popPos() + text := ctx.Buffer.Bytes()[pos:] + ctx.Buffer.Truncate(pos) err := h.ImageRenderer.RenderLink( w, @@ -263,12 +261,13 @@ 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.pos = ctx.Buffer.Len() + ctx.pushPos(ctx.Buffer.Len()) return ast.WalkContinue, nil } - text := ctx.Buffer.Bytes()[ctx.pos:] - ctx.Buffer.Truncate(ctx.pos) + pos := ctx.popPos() + text := ctx.Buffer.Bytes()[pos:] + ctx.Buffer.Truncate(pos) err := h.LinkRenderer.RenderLink( w, @@ -395,12 +394,13 @@ 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.pos = ctx.Buffer.Len() + ctx.pushPos(ctx.Buffer.Len()) return ast.WalkContinue, nil } - text := ctx.Buffer.Bytes()[ctx.pos:] - ctx.Buffer.Truncate(ctx.pos) + pos := ctx.popPos() + text := ctx.Buffer.Bytes()[pos:] + ctx.Buffer.Truncate(pos) // All ast.Heading nodes are guaranteed to have an attribute called "id" // that is an array of bytes that encode a valid string. anchori, _ := n.AttributeString("id") @@ -440,8 +440,7 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n return ast.WalkContinue, nil } -type links struct { -} +type links struct{} // Extend implements goldmark.Extender. func (e *links) Extend(m goldmark.Markdown) {