Fix some  RenderShortcodes error cases - hugo - [fork] hugo port for 9front
 (HTM) git clone git@git.drkhsh.at/hugo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 1f23b4949c70a2a8f2084fa937e19e93a9fe890a
 (DIR) parent 5fc16390355f32b336836163907fc215034f5b73
 (HTM) Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Wed, 30 Oct 2024 18:10:09 +0100
       
       Fix some  RenderShortcodes error cases
       
       This issue fixes two cases where `{{__hugo_ctx` artifacts were left in the rendered output:
       
       1. Inclusion when `.RenderShortcodes` is wrapped in HTML.
       2. Inclusion of Markdown file without a trailing newline in some cases.
       
       Closes #12854
       Updates #12998
       
       Diffstat:
         M common/constants/constants.go       |       1 +
         M hugolib/collections_test.go         |      16 ++++++++--------
         M hugolib/integrationtest_builder.go  |      23 +++++++++++++++++++++++
         M hugolib/menu_test.go                |     124 +++++--------------------------
         M hugolib/page.go                     |      19 ++++++++++++++++++-
         M hugolib/rendershortcodes_test.go    |     112 +++++++++++++++++++++++++++++++
         M hugolib/shortcode_page.go           |       4 ++++
         M markup/goldmark/convert.go          |       2 +-
         M markup/goldmark/goldmark_integrati… |       2 +-
         M markup/goldmark/hugocontext/hugoco… |     111 +++++++++++++++++++++++++------
         M markup/goldmark/hugocontext/hugoco… |       2 +-
         M resources/page/page.go              |       2 ++
         M resources/page/page_markup_integra… |       6 +++---
       
       13 files changed, 283 insertions(+), 141 deletions(-)
       ---
 (DIR) diff --git a/common/constants/constants.go b/common/constants/constants.go
       @@ -21,6 +21,7 @@ const (
                ErrRemoteGetCSV  = "error-remote-getcsv"
        
                WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
       +        WarnRenderShortcodesInHTML     = "warning-rendershortcodes-in-html"
        )
        
        // Field/method names with special meaning.
 (DIR) diff --git a/hugolib/collections_test.go b/hugolib/collections_test.go
       @@ -71,9 +71,9 @@ tags_weight: %d
        {{ $pageGroups := slice $cool $blue }}
        {{ $weighted := slice $wp1 $wp2 }}
        
       -{{ printf "pages:%d:%T:%v/%v" (len $pages) $pages (index $pages 0) (index $pages 1) }}
       -{{ printf "pageGroups:%d:%T:%v/%v" (len $pageGroups) $pageGroups (index (index $pageGroups 0).Pages 0) (index (index $pageGroups 1).Pages 0)}}
       -{{ printf "weightedPages:%d::%T:%v" (len $weighted) $weighted $weighted | safeHTML }}
       +{{ printf "pages:%d:%T:%s|%s" (len $pages) $pages (index $pages 0).Path (index $pages 1).Path }}
       +{{ printf "pageGroups:%d:%T:%s|%s" (len $pageGroups) $pageGroups (index (index $pageGroups 0).Pages 0).Path (index (index $pageGroups 1).Pages 0).Path}}
       +{{ printf "weightedPages:%d:%T" (len $weighted) $weighted | safeHTML }}
        
        `)
                b.CreateSites().Build(BuildCfg{})
       @@ -82,9 +82,9 @@ tags_weight: %d
                c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 2)
        
                b.AssertFileContent("public/index.html",
       -                "pages:2:page.Pages:Page(/page1)/Page(/page2)",
       -                "pageGroups:2:page.PagesGroup:Page(/page1)/Page(/page2)",
       -                `weightedPages:2::page.WeightedPages:[WeightedPage(10,"Page") WeightedPage(20,"Page")]`)
       +                "pages:2:page.Pages:/page1|/page2",
       +                "pageGroups:2:page.PagesGroup:/page1|/page2",
       +                `weightedPages:2:page.WeightedPages`)
        }
        
        func TestUnionFunc(t *testing.T) {
       @@ -189,7 +189,7 @@ tags_weight: %d
        {{ $appendStrings := slice "a" "b" | append "c" "d" "e" }}
        {{ $appendStringsSlice := slice "a" "b" "c" | append (slice "c" "d") }}
        
       -{{ printf "pages:%d:%T:%v/%v" (len $pages) $pages (index $pages 0) (index $pages 1)  }}
       +{{ printf "pages:%d:%T:%s|%s" (len $pages) $pages (index $pages 0).Path (index $pages 1).Path  }}
        {{ printf "appendPages:%d:%T:%v/%v" (len $appendPages) $appendPages (index $appendPages 0).Kind (index $appendPages 8).Kind  }}
        {{ printf "appendStrings:%T:%v"  $appendStrings $appendStrings  }}
        {{ printf "appendStringsSlice:%T:%v"  $appendStringsSlice $appendStringsSlice }}
       @@ -207,7 +207,7 @@ tags_weight: %d
                c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 2)
        
                b.AssertFileContent("public/index.html",
       -                "pages:2:page.Pages:Page(/page2)/Page(/page1)",
       +                "pages:2:page.Pages:/page2|/page1",
                        "appendPages:9:page.Pages:home/page",
                        "appendStrings:[]string:[a b c d e]",
                        "appendStringsSlice:[]string:[a b c c d]",
 (DIR) diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
       @@ -2,6 +2,7 @@ package hugolib
        
        import (
                "bytes"
       +        "context"
                "encoding/base64"
                "errors"
                "fmt"
       @@ -32,6 +33,7 @@ import (
                "github.com/gohugoio/hugo/htesting"
                "github.com/gohugoio/hugo/hugofs"
                "github.com/spf13/afero"
       +        "github.com/spf13/cast"
                "golang.org/x/text/unicode/norm"
                "golang.org/x/tools/txtar"
        )
       @@ -294,6 +296,12 @@ func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...s
                }
        }
        
       +func (s *IntegrationTestBuilder) AssertFileContentEquals(filename string, match string) {
       +        s.Helper()
       +        content := s.FileContent(filename)
       +        s.Assert(content, qt.Equals, match, qt.Commentf(match))
       +}
       +
        func (s *IntegrationTestBuilder) AssertFileContentExact(filename string, matches ...string) {
                s.Helper()
                content := s.FileContent(filename)
       @@ -302,6 +310,16 @@ func (s *IntegrationTestBuilder) AssertFileContentExact(filename string, matches
                }
        }
        
       +func (s *IntegrationTestBuilder) AssertNoRenderShortcodesArtifacts() {
       +        s.Helper()
       +        for _, p := range s.H.Pages() {
       +                content, err := p.Content(context.Background())
       +                s.Assert(err, qt.IsNil)
       +                comment := qt.Commentf("Page: %s\n%s", p.Path(), content)
       +                s.Assert(strings.Contains(cast.ToString(content), "__hugo_ctx"), qt.IsFalse, comment)
       +        }
       +}
       +
        func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) {
                s.AssertFs(s.fs.PublishDir, matches...)
        }
       @@ -835,6 +853,11 @@ type IntegrationTestConfig struct {
        
                // The files to use on txtar format, see
                // https://pkg.go.dev/golang.org/x/exp/cmd/txtar
       +        // There are some conentions used in this test setup.
       +        // - §§§ can be used to wrap code fences.
       +        // - §§ can be used to wrap multiline strings.
       +        // - filenames prefixed with sourcefilename: will be read from the file system relative to the current dir.
       +        // - filenames with a .png or .jpg extension will be treated as binary and base64 decoded.
                TxtarString string
        
                // COnfig to use as the base. We will also read the config from the txtar.
 (DIR) diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go
       @@ -105,94 +105,6 @@ Menu Main:  {{ partial "menu.html" (dict "page" . "menu" "main") }}`,
                                "/sect3/|Sect3s|Sect3s|0|-|-|")
        }
        
       -// related issue #7594
       -func TestMenusSort(t *testing.T) {
       -        b := newTestSitesBuilder(t).WithSimpleConfigFile()
       -
       -        b.WithTemplatesAdded("index.html", `
       -{{ range $k, $v := .Site.Menus.main }}
       -Default1|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
       -{{ range $k, $v := .Site.Menus.main.ByWeight }}
       -ByWeight|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
       -{{ range $k, $v := (.Site.Menus.main.ByWeight).Reverse }}
       -Reverse|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
       -{{ range $k, $v := .Site.Menus.main }}
       -Default2|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
       -{{ range $k, $v := .Site.Menus.main.ByWeight }}
       -ByWeight|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
       -{{ range $k, $v := .Site.Menus.main }}
       -Default3|{{ $k }}|{{ $v.Weight }}|{{ $v.Name }}|{{ .URL }}|{{ $v.Page }}{{ end }}
       -`)
       -
       -        b.WithContent("_index.md", `
       ----
       -title: Home
       -menu:
       -  main:
       -    weight: 100
       ----`)
       -
       -        b.WithContent("blog/A.md", `
       ----
       -title: "A"
       -menu:
       -  main:
       -    weight: 10
       ----
       -`)
       -
       -        b.WithContent("blog/B.md", `
       ----
       -title: "B"
       -menu:
       -  main:
       -    weight: 20
       ----
       -`)
       -        b.WithContent("blog/C.md", `
       ----
       -title: "C"
       -menu:
       -  main:
       -    weight: 30
       ----
       -`)
       -
       -        b.Build(BuildCfg{})
       -
       -        b.AssertFileContent("public/index.html",
       -                `Default1|0|10|A|/blog/a/|Page(/blog/a)
       -        Default1|1|20|B|/blog/b/|Page(/blog/b)
       -        Default1|2|30|C|/blog/c/|Page(/blog/c)
       -        Default1|3|100|Home|/|Page(/)
       -
       -        ByWeight|0|10|A|/blog/a/|Page(/blog/a)
       -        ByWeight|1|20|B|/blog/b/|Page(/blog/b)
       -        ByWeight|2|30|C|/blog/c/|Page(/blog/c)
       -        ByWeight|3|100|Home|/|Page(/)
       -
       -        Reverse|0|100|Home|/|Page(/)
       -        Reverse|1|30|C|/blog/c/|Page(/blog/c)
       -        Reverse|2|20|B|/blog/b/|Page(/blog/b)
       -        Reverse|3|10|A|/blog/a/|Page(/blog/a)
       -
       -        Default2|0|10|A|/blog/a/|Page(/blog/a)
       -        Default2|1|20|B|/blog/b/|Page(/blog/b)
       -        Default2|2|30|C|/blog/c/|Page(/blog/c)
       -        Default2|3|100|Home|/|Page(/)
       -
       -        ByWeight|0|10|A|/blog/a/|Page(/blog/a)
       -        ByWeight|1|20|B|/blog/b/|Page(/blog/b)
       -        ByWeight|2|30|C|/blog/c/|Page(/blog/c)
       -        ByWeight|3|100|Home|/|Page(/)
       -
       -        Default3|0|10|A|/blog/a/|Page(/blog/a)
       -        Default3|1|20|B|/blog/b/|Page(/blog/b)
       -        Default3|2|30|C|/blog/c/|Page(/blog/c)
       -        Default3|3|100|Home|/|Page(/)`,
       -        )
       -}
       -
        func TestMenusFrontMatter(t *testing.T) {
                b := newTestSitesBuilder(t).WithSimpleConfigFile()
        
       @@ -437,8 +349,8 @@ url = "/blog/post3"
                commonTempl := `
        Main: {{ len .Site.Menus.main }}
        {{ range .Site.Menus.main }}
       -{{ .Title }}|HasMenuCurrent: {{ $.HasMenuCurrent "main" . }}|Page: {{ .Page }}
       -{{ .Title }}|IsMenuCurrent: {{ $.IsMenuCurrent "main" . }}|Page: {{ .Page }}
       +{{ .Title }}|HasMenuCurrent: {{ $.HasMenuCurrent "main" . }}|Page: {{ .Page.Path }}
       +{{ .Title }}|IsMenuCurrent: {{ $.IsMenuCurrent "main" . }}|Page: {{ .Page.Path }}
        {{ end }}
        `
        
       @@ -494,34 +406,34 @@ title: "Contact: With  No Menu Defined"
        
                b.AssertFileContent("public/index.html", `
        Main: 5
       -Home|HasMenuCurrent: false|Page: Page(/)
       -Blog|HasMenuCurrent: false|Page: Page(/blog)
       -My Post 2: With Menu Defined|HasMenuCurrent: false|Page: Page(/blog/post2)
       -My Post 3|HasMenuCurrent: false|Page: Page(/blog/post3)
       -Contact Us|HasMenuCurrent: false|Page: Page(/contact)
       +Home|HasMenuCurrent: false|Page: /
       +Blog|HasMenuCurrent: false|Page: /blog
       +My Post 2: With Menu Defined|HasMenuCurrent: false|Page: /blog/post2
       +My Post 3|HasMenuCurrent: false|Page: /blog/post3
       +Contact Us|HasMenuCurrent: false|Page: /contact
        `)
        
                b.AssertFileContent("public/blog/post1/index.html", `
       -Home|HasMenuCurrent: false|Page: Page(/)
       -Blog|HasMenuCurrent: true|Page: Page(/blog)
       +Home|HasMenuCurrent: false|Page: /
       +Blog|HasMenuCurrent: true|Page: /blog
        `)
        
                b.AssertFileContent("public/blog/post2/index.html", `
       -Home|HasMenuCurrent: false|Page: Page(/)
       -Blog|HasMenuCurrent: true|Page: Page(/blog)
       -Blog|IsMenuCurrent: false|Page: Page(/blog)
       +Home|HasMenuCurrent: false|Page: /
       +Blog|HasMenuCurrent: true|Page: /blog
       +Blog|IsMenuCurrent: false|Page: /blog
        `)
        
                b.AssertFileContent("public/blog/post3/index.html", `
       -Home|HasMenuCurrent: false|Page: Page(/)
       -Blog|HasMenuCurrent: true|Page: Page(/blog)
       +Home|HasMenuCurrent: false|Page: /
       +Blog|HasMenuCurrent: true|Page: /blog
        `)
        
                b.AssertFileContent("public/contact/index.html", `
       -Contact Us|HasMenuCurrent: false|Page: Page(/contact)
       -Contact Us|IsMenuCurrent: true|Page: Page(/contact)
       -Blog|HasMenuCurrent: false|Page: Page(/blog)
       -Blog|IsMenuCurrent: false|Page: Page(/blog)
       +Contact Us|HasMenuCurrent: false|Page: /contact
       +Contact Us|IsMenuCurrent: true|Page: /contact
       +Blog|HasMenuCurrent: false|Page: /blog
       +Blog|IsMenuCurrent: false|Page: /blog
        `)
        }
        
 (DIR) diff --git a/hugolib/page.go b/hugolib/page.go
       @@ -16,7 +16,9 @@ package hugolib
        import (
                "context"
                "fmt"
       +        "path/filepath"
                "strconv"
       +        "strings"
                "sync"
                "sync/atomic"
        
       @@ -358,7 +360,22 @@ func (p *pageState) Site() page.Site {
        }
        
        func (p *pageState) String() string {
       -        return fmt.Sprintf("Page(%s)", p.Path())
       +        var sb strings.Builder
       +        if p.File() != nil {
       +                // The forward slashes even on Windows is motivated by
       +                // getting stable tests.
       +                // This information is meant for getting positional information in logs,
       +                // so the direction of the slashes should not matter.
       +                sb.WriteString(filepath.ToSlash(p.File().Filename()))
       +                if p.File().IsContentAdapter() {
       +                        // Also include the path.
       +                        sb.WriteString(":")
       +                        sb.WriteString(p.Path())
       +                }
       +        } else {
       +                sb.WriteString(p.Path())
       +        }
       +        return sb.String()
        }
        
        // IsTranslated returns whether this content file is translated to
 (DIR) diff --git a/hugolib/rendershortcodes_test.go b/hugolib/rendershortcodes_test.go
       @@ -14,6 +14,7 @@
        package hugolib
        
        import (
       +        "path/filepath"
                "strings"
                "testing"
        )
       @@ -69,6 +70,7 @@ Content: {{ .Content }}|
        
                b := Test(t, files)
        
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/p1/index.html",
                        "Fragments: [p1-h1 p2-h1 p2-h2 p2-h3 p2-withmarkdown p3-h1 p3-h2 p3-withmarkdown]|",
                        "HasShortcode Level 1: true|",
       @@ -115,6 +117,7 @@ JSON: {{ .Content }}
        
                b := Test(t, files)
        
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/p1/index.html", "Myshort HTML")
                b.AssertFileContent("public/p1/index.json", "Myshort JSON")
        }
       @@ -147,9 +150,11 @@ Myshort Original.
         {{ .Content }}
        `
                b := TestRunning(t, files)
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/p1/index.html", "Myshort Original.")
        
                b.EditFileReplaceAll("layouts/shortcodes/myshort.html", "Original", "Edited").Build()
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/p1/index.html", "Myshort Edited.")
        }
        
       @@ -192,12 +197,14 @@ Myshort Original.
                        },
                ).Build()
        
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/p1/index.html", "Original")
        
                b.EditFileReplaceFunc("content/p2.md", func(s string) string {
                        return strings.Replace(s, "Original", "Edited", 1)
                })
                b.Build()
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/p1/index.html", "Edited")
        }
        
       @@ -233,8 +240,10 @@ Myshort Original.
        `
                b := TestRunning(t, files)
        
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/mysection/index.html", "p1-h1")
                b.EditFileReplaceAll("content/mysection/_index.md", "p1-h1", "p1-h1 Edited").Build()
       +        b.AssertNoRenderShortcodesArtifacts()
                b.AssertFileContent("public/mysection/index.html", "p1-h1 Edited")
        }
        
       @@ -314,6 +323,8 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
        
                b := Test(t, files)
        
       +        b.AssertNoRenderShortcodesArtifacts()
       +
                b.AssertFileContent("public/markdown/index.html",
                        // Images.
                        "Image: /posts/p1/pixel1.png|\nImage: /posts/p1/pixel2.png|\n|\nImage: /markdown/pixel3.png|</p>\n|",
       @@ -333,3 +344,104 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
        
                b.AssertFileContent("public/html/index.html", "! hugo_ctx")
        }
       +
       +// Issue 12854.
       +func TestRenderShortcodesWithHTML(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +disableLiveReload = true
       +disableKinds = ["home", "taxonomy", "term"]
       +markup.goldmark.renderer.unsafe = true
       +-- content/p1.md --
       +---
       +title: "p1"
       +---
       +{{% include "p2" %}}
       +-- content/p2.md --
       +---
       +title: "p2"
       +---
       +Hello <b>world</b>. Some **bold** text. Some Unicode: 神真美好.
       +-- layouts/shortcodes/include.html --
       +{{ with site.GetPage (.Get 0) }}
       +<div>{{ .RenderShortcodes }}</div>
       +{{ end }}
       +-- layouts/_default/single.html --
       +{{ .Content }}
       +`
       +
       +        b := TestRunning(t, files, TestOptWarn())
       +
       +        b.AssertNoRenderShortcodesArtifacts()
       +        b.AssertLogContains(filepath.ToSlash("WARN  .RenderShortcodes detected inside HTML block in \"/content/p1.md\"; this may not be what you intended, see https://gohugo.io/methods/page/rendershortcodes/#limitations\nYou can suppress this warning by adding the following to your site configuration:\nignoreLogs = ['warning-rendershortcodes-in-html']"))
       +        b.AssertFileContent("public/p1/index.html", "<div>Hello <b>world</b>. Some **bold** text. Some Unicode: 神真美好.\n</div>")
       +        b.EditFileReplaceAll("content/p2.md", "Hello", "Hello Edited").Build()
       +        b.AssertNoRenderShortcodesArtifacts()
       +        b.AssertFileContent("public/p1/index.html", "<div>Hello Edited <b>world</b>. Some **bold** text. Some Unicode: 神真美好.\n</div>")
       +}
       +
       +func TestRenderShortcodesIncludeMarkdownFileWithoutTrailingNewline(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- hugo.toml --
       +disableLiveReload = true
       +disableKinds = ["home", "taxonomy", "term"]
       +markup.goldmark.renderer.unsafe = true
       +-- content/p1.md --
       +---
       +title: "p1"
       +---
       +Content p1 id-1000.{{% include "p2" %}}{{% include "p3" %}}
       +
       +§§§ go
       +code_p1
       +§§§
       +§§§ go
       +code_p1_2
       +§§§
       +
       +§§§ go
       +code_p1_3
       +§§§
       +-- content/p2.md --
       +---
       +title: "p2"
       +---
       +§§§ bash
       +code_p2
       +§§§
       +Foo.
       +-- content/p3.md --
       +---
       +title: "p3"
       +---
       +§§§ php
       +code_p3
       +§§§
       +-- layouts/shortcodes/include.html --
       +{{ with site.GetPage (.Get 0) -}}
       +{{ .RenderShortcodes -}}
       +{{ end -}}
       +-- layouts/_default/single.html --
       +{{ .Content }}
       +-- layouts/_default/_markup/render-codeblock.html --
       +<code>{{ .Inner | safeHTML }}</code>
       +`
       +
       +        b := TestRunning(t, files, TestOptWarn())
       +
       +        b.AssertNoRenderShortcodesArtifacts()
       +        b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-1000.</p>\n<code>code_p2</code><p>Foo.\n</p>\n<code>code_p3</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
       +        b.EditFileReplaceAll("content/p1.md", "id-1000.", "id-100.").Build()
       +        b.AssertNoRenderShortcodesArtifacts()
       +        b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-100.</p>\n<code>code_p2</code><p>Foo.\n</p>\n<code>code_p3</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
       +        b.EditFileReplaceAll("content/p2.md", "code_p2", "codep2").Build()
       +        b.AssertNoRenderShortcodesArtifacts()
       +        b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-100.</p>\n<code>codep2</code><p>Foo.\n</p>\n<code>code_p3</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
       +        b.EditFileReplaceAll("content/p3.md", "code_p3", "code_p3_edited").Build()
       +        b.AssertNoRenderShortcodesArtifacts()
       +        b.AssertFileContentEquals("public/p1/index.html", "<p>Content p1 id-100.</p>\n<code>codep2</code><p>Foo.\n</p>\n<code>code_p3_edited</code><p></p>\n<code>code_p1</code><code>code_p1_2</code><code>code_p1_3</code>")
       +}
 (DIR) diff --git a/hugolib/shortcode_page.go b/hugolib/shortcode_page.go
       @@ -125,3 +125,7 @@ func newPageForRenderHook(p *pageState) page.Page {
        func (p *pageForRenderHooks) Unwrapv() any {
                return p.p
        }
       +
       +func (p *pageForRenderHooks) String() string {
       +        return p.p.String()
       +}
 (DIR) diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
       @@ -106,7 +106,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
                        renderer.WithNodeRenderers(util.Prioritized(emoji.NewHTMLRenderer(), 200)))
                var (
                        extensions = []goldmark.Extender{
       -                        hugocontext.New(),
       +                        hugocontext.New(pcfg.Logger),
                                newLinks(cfg),
                                newTocExtension(tocRendererOptions),
                                blockquotes.New(),
 (DIR) diff --git a/markup/goldmark/goldmark_integration_test.go b/markup/goldmark/goldmark_integration_test.go
       @@ -575,7 +575,7 @@ sc3_begin|{{ .Inner }}|sc3_end
                        // Issue #7332
                        "<span>:x:\n</span>",
                        // Issue #11587
       -                "<p>&#x2714;&#xfe0f;</p>",
       +                "<p>&#x2714;&#xfe0f;\n</p>",
                        // Should not be converted to emoji
                        "sc1_begin|:smiley:|sc1_end",
                        // Should be converted to emoji
 (DIR) diff --git a/markup/goldmark/hugocontext/hugocontext.go b/markup/goldmark/hugocontext/hugocontext.go
       @@ -16,20 +16,24 @@ package hugocontext
        import (
                "bytes"
                "fmt"
       +        "regexp"
                "strconv"
        
                "github.com/gohugoio/hugo/bufferpool"
       +        "github.com/gohugoio/hugo/common/constants"
       +        "github.com/gohugoio/hugo/common/loggers"
                "github.com/gohugoio/hugo/markup/goldmark/internal/render"
                "github.com/yuin/goldmark"
                "github.com/yuin/goldmark/ast"
                "github.com/yuin/goldmark/parser"
                "github.com/yuin/goldmark/renderer"
       +        "github.com/yuin/goldmark/renderer/html"
                "github.com/yuin/goldmark/text"
                "github.com/yuin/goldmark/util"
        )
        
       -func New() goldmark.Extender {
       -        return &hugoContextExtension{}
       +func New(logger loggers.Logger) goldmark.Extender {
       +        return &hugoContextExtension{logger: logger}
        }
        
        // Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
       @@ -37,14 +41,19 @@ func New() goldmark.Extender {
        func Wrap(b []byte, pid uint64) string {
                buf := bufferpool.GetBuffer()
                defer bufferpool.PutBuffer(buf)
       -        buf.Write(prefix)
       +        buf.Write(hugoCtxPrefix)
                buf.WriteString(" pid=")
                buf.WriteString(strconv.FormatUint(pid, 10))
       -        buf.Write(endDelim)
       +        buf.Write(hugoCtxEndDelim)
                buf.WriteByte('\n')
                buf.Write(b)
       -        buf.Write(prefix)
       -        buf.Write(closingDelimAndNewline)
       +        // To make sure that we're able to parse it, make sure it ends with a newline.
       +        if len(b) > 0 && b[len(b)-1] != '\n' {
       +                buf.WriteByte('\n')
       +        }
       +        buf.Write(hugoCtxPrefix)
       +        buf.Write(hugoCtxClosingDelim)
       +        buf.WriteByte('\n')
                return buf.String()
        }
        
       @@ -89,45 +98,100 @@ func (h *HugoContext) Kind() ast.NodeKind {
        }
        
        var (
       -        prefix                 = []byte("{{__hugo_ctx")
       -        endDelim               = []byte("}}")
       -        closingDelimAndNewline = []byte("/}}\n")
       +        hugoCtxPrefix       = []byte("{{__hugo_ctx")
       +        hugoCtxEndDelim     = []byte("}}")
       +        hugoCtxClosingDelim = []byte("/}}")
       +        hugoCtxRe           = regexp.MustCompile(`{{__hugo_ctx( pid=\d+)?/?}}\n?`)
        )
        
        var _ parser.InlineParser = (*hugoContextParser)(nil)
        
        type hugoContextParser struct{}
        
       -func (s *hugoContextParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
       -        line, _ := block.PeekLine()
       -        if !bytes.HasPrefix(line, prefix) {
       +func (a *hugoContextParser) Trigger() []byte {
       +        return []byte{'{'}
       +}
       +
       +func (s *hugoContextParser) Parse(parent ast.Node, reader text.Reader, pc parser.Context) ast.Node {
       +        line, _ := reader.PeekLine()
       +        if !bytes.HasPrefix(line, hugoCtxPrefix) {
                        return nil
                }
       -        end := bytes.Index(line, endDelim)
       +        end := bytes.Index(line, hugoCtxEndDelim)
                if end == -1 {
                        return nil
                }
        
       -        block.Advance(end + len(endDelim) + 1) // +1 for the newline
       +        reader.Advance(end + len(hugoCtxEndDelim) + 1) // +1 for the newline
        
                if line[end-1] == '/' {
                        return &HugoContext{Closing: true}
                }
        
       -        attrBytes := line[len(prefix)+1 : end]
       +        attrBytes := line[len(hugoCtxPrefix)+1 : end]
                h := &HugoContext{}
                h.parseAttrs(attrBytes)
                return h
        }
        
       -func (a *hugoContextParser) Trigger() []byte {
       -        return []byte{'{'}
       +type hugoContextRenderer struct {
       +        logger loggers.Logger
       +        html.Config
        }
        
       -type hugoContextRenderer struct{}
       +func (r *hugoContextRenderer) SetOption(name renderer.OptionName, value any) {
       +        r.Config.SetOption(name, value)
       +}
        
        func (r *hugoContextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
                reg.Register(kindHugoContext, r.handleHugoContext)
       +        reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
       +}
       +
       +func (r *hugoContextRenderer) stripHugoCtx(b []byte) ([]byte, bool) {
       +        if !bytes.Contains(b, hugoCtxPrefix) {
       +                return b, false
       +        }
       +        return hugoCtxRe.ReplaceAll(b, nil), true
       +}
       +
       +func (r *hugoContextRenderer) renderHTMLBlock(
       +        w util.BufWriter, source []byte, node ast.Node, entering bool,
       +) (ast.WalkStatus, error) {
       +        n := node.(*ast.HTMLBlock)
       +        if entering {
       +                if r.Unsafe {
       +                        l := n.Lines().Len()
       +                        for i := 0; i < l; i++ {
       +                                line := n.Lines().At(i)
       +                                linev := line.Value(source)
       +                                var stripped bool
       +                                linev, stripped = r.stripHugoCtx(linev)
       +                                if stripped {
       +                                        var p any
       +                                        ctx, ok := w.(*render.Context)
       +                                        if ok {
       +                                                p, _ = render.GetPageAndPageInner(ctx)
       +                                        }
       +                                        r.logger.Warnidf(constants.WarnRenderShortcodesInHTML, ".RenderShortcodes detected inside HTML block in %q; this may not be what you intended, see https://gohugo.io/methods/page/rendershortcodes/#limitations", p)
       +                                }
       +
       +                                r.Writer.SecureWrite(w, linev)
       +                        }
       +                } else {
       +                        _, _ = w.WriteString("<!-- raw HTML omitted -->\n")
       +                }
       +        } else {
       +                if n.HasClosure() {
       +                        if r.Unsafe {
       +                                closure := n.ClosureLine
       +                                r.Writer.SecureWrite(w, closure.Value(source))
       +                        } else {
       +                                _, _ = w.WriteString("<!-- raw HTML omitted -->\n")
       +                        }
       +                }
       +        }
       +        return ast.WalkContinue, nil
        }
        
        func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
       @@ -148,7 +212,9 @@ func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte,
                return ast.WalkContinue, nil
        }
        
       -type hugoContextExtension struct{}
       +type hugoContextExtension struct {
       +        logger loggers.Logger
       +}
        
        func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
                m.Parser().AddOptions(
       @@ -159,7 +225,12 @@ func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
        
                m.Renderer().AddOptions(
                        renderer.WithNodeRenderers(
       -                        util.Prioritized(&hugoContextRenderer{}, 50),
       +                        util.Prioritized(&hugoContextRenderer{
       +                                logger: a.logger,
       +                                Config: html.Config{
       +                                        Writer: html.DefaultWriter,
       +                                },
       +                        }, 50),
                        ),
                )
        }
 (DIR) diff --git a/markup/goldmark/hugocontext/hugocontext_test.go b/markup/goldmark/hugocontext/hugocontext_test.go
       @@ -24,7 +24,7 @@ func TestWrap(t *testing.T) {
        
                b := []byte("test")
        
       -        c.Assert(Wrap(b, 42), qt.Equals, "{{__hugo_ctx pid=42}}\ntest{{__hugo_ctx/}}\n")
       +        c.Assert(Wrap(b, 42), qt.Equals, "{{__hugo_ctx pid=42}}\ntest\n{{__hugo_ctx/}}\n")
        }
        
        func BenchmarkWrap(b *testing.B) {
 (DIR) diff --git a/resources/page/page.go b/resources/page/page.go
       @@ -17,6 +17,7 @@ package page
        
        import (
                "context"
       +        "fmt"
                "html/template"
        
                "github.com/gohugoio/hugo/markup/converter"
       @@ -180,6 +181,7 @@ type Page interface {
                ContentProvider
                TableOfContentsProvider
                PageWithoutContent
       +        fmt.Stringer
        }
        
        type PageFragment interface {
 (DIR) diff --git a/resources/page/page_markup_integration_test.go b/resources/page/page_markup_integration_test.go
       @@ -161,13 +161,13 @@ includecontent: {{ hugo.Context.MarkupScope }}|{{ $p.Markup.Render.Content }}|
        
                b := hugolib.Test(t, files)
        
       -        b.AssertFileContent("public/p1/index.html", "Render heading: title: P1 scope: |", "Foo scope: |")
       +        b.AssertFileContentExact("public/p1/index.html", "Render heading: title: P1 scope: |", "Foo scope: |")
        
       -        b.AssertFileContent("public/index.html",
       +        b.AssertFileContentExact("public/index.html",
       +                "Begin:\nincludecontent: home|Render heading: title: P3 scope: home|Foo scope: home|\n|\n:End",
                        "Render heading: title: P1 scope: home|",
                        "Foo scope: home|",
                        "Begin:\nincluderendershortcodes: home|</p>\nRender heading: title: P2 scope: home|<p>|:End",
       -                "Begin:\nincludecontent: home|Render heading: title: P3 scope: home|Foo scope: home|\n|\n:End",
                )
        }