mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
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
This commit is contained in:
parent
5fc1639035
commit
1f23b4949c
13 changed files with 283 additions and 141 deletions
|
@ -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.
|
||||
|
|
|
@ -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]",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
`)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -575,7 +575,7 @@ sc3_begin|{{ .Inner }}|sc3_end
|
|||
// Issue #7332
|
||||
"<span>:x:\n</span>",
|
||||
// Issue #11587
|
||||
"<p>✔️</p>",
|
||||
"<p>✔️\n</p>",
|
||||
// Should not be converted to emoji
|
||||
"sc1_begin|:smiley:|sc1_end",
|
||||
// Should be converted to emoji
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue