aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2024-04-11 17:46:18 +0200
committerBjørn Erik Pedersen <[email protected]>2024-04-15 09:49:57 +0200
commitdf11327ba90179747be2b25574ac48c2f336b298 (patch)
tree70f7487cee6815109fc325ed0619130bb29834ca
parenta18e2bcb9a2c556e03dc7e790b0343c83163877a (diff)
downloadhugo-df11327ba90179747be2b25574ac48c2f336b298.tar.gz
hugo-df11327ba90179747be2b25574ac48c2f336b298.zip
Pass .RenderShortcodes' Page to render hooks as .PageInner
The main use case for this is to resolve links and resources (e.g. images) relative to the included `Page`. A typical `include` would similar to this: ```handlebars {{ with site.GetPage (.Get 0) }} {{ .RenderShortcodes }} {{ end }} ``` And when used in a Markdown file: ```markdown {{% include "/posts/p1" %}} ``` Any render hook triggered while rendering `/posts/p1` will get `/posts/p1` when calling `.PageInner`. Note that * This is only relevant for shortcodes included with `{{%` that calls `.RenderShortcodes`. * `.PageInner` is available in all render hooks that, before this commit, received `.Page`. * `.PageInner` will fall back to the value of `.Page` if not relevant and will always have a value. Fixes #12356
-rw-r--r--hugolib/content_map_page.go2
-rw-r--r--hugolib/page__content.go5
-rw-r--r--hugolib/page__meta.go24
-rw-r--r--hugolib/page__per_output.go17
-rw-r--r--hugolib/rendershortcodes_test.go96
-rw-r--r--hugolib/shortcode.go6
-rw-r--r--markup/converter/converter.go9
-rw-r--r--markup/converter/hooks/hooks.go20
-rw-r--r--markup/goldmark/codeblocks/render.go27
-rw-r--r--markup/goldmark/convert.go2
-rw-r--r--markup/goldmark/hugocontext/hugocontext.go165
-rw-r--r--markup/goldmark/hugocontext/hugocontext_test.go34
-rw-r--r--markup/goldmark/internal/render/context.go25
-rw-r--r--markup/goldmark/render_hooks.go26
-rw-r--r--resources/page/pagemeta/page_frontmatter.go3
-rw-r--r--tpl/template.go2
-rw-r--r--tpl/tplimpl/embedded/templates/_default/_markup/render-image.html2
-rw-r--r--tpl/tplimpl/embedded/templates/_default/_markup/render-link.html6
18 files changed, 443 insertions, 28 deletions
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
index 336fe197c..aa32b5320 100644
--- a/hugolib/content_map_page.go
+++ b/hugolib/content_map_page.go
@@ -1603,7 +1603,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
targetPaths := ps.targetPaths()
baseTarget := targetPaths.SubResourceBaseTarget
duplicateResourceFiles := true
- if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.pageConfig.Markup) {
+ if ps.m.pageConfig.IsGoldmark {
duplicateResourceFiles = ps.s.ContentSpec.Converters.GetMarkupConfig().Goldmark.DuplicateResourceFiles
}
diff --git a/hugolib/page__content.go b/hugolib/page__content.go
index 799fc89b6..f10c25d7b 100644
--- a/hugolib/page__content.go
+++ b/hugolib/page__content.go
@@ -522,6 +522,7 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
if err != nil {
return nil, err
}
+
if !ok {
return nil, errors.New("invalid state: astDoc is set but RenderContent returned false")
}
@@ -626,8 +627,10 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
return nil, err
}
- // Callback called from above (e.g. in .RenderString)
+ // Callback called from below (e.g. in .RenderString)
ctxCallback := func(cp2 *pageContentOutput, ct2 contentTableOfContents) {
+ cp.otherOutputs[cp2.po.p.pid] = cp2
+
// Merge content placeholders
for k, v := range ct2.contentPlaceholders {
ct.contentPlaceholders[k] = v
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index ebf57f3b3..d8203fe75 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -737,6 +737,8 @@ func (p *pageMeta) applyDefaultValues() error {
}
}
+ p.pageConfig.IsGoldmark = p.s.ContentSpec.Converters.IsGoldmark(p.pageConfig.Markup)
+
if p.pageConfig.Title == "" && p.f == nil {
switch p.Kind() {
case kinds.KindHome:
@@ -794,12 +796,26 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.
path = p.Path()
}
+ doc := newPageForRenderHook(ps)
+
+ documentLookup := func(id uint64) any {
+ if id == ps.pid {
+ // This prevents infinite recursion in some cases.
+ return doc
+ }
+ if v, ok := ps.pageOutput.pco.otherOutputs[id]; ok {
+ return v.po.p
+ }
+ return nil
+ }
+
cpp, err := cp.New(
converter.DocumentContext{
- Document: newPageForRenderHook(ps),
- DocumentID: id,
- DocumentName: path,
- Filename: filename,
+ Document: doc,
+ DocumentLookup: documentLookup,
+ DocumentID: id,
+ DocumentName: path,
+ Filename: filename,
},
)
if err != nil {
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index fac719ea9..4ff67c074 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -30,6 +30,7 @@ import (
"github.com/spf13/cast"
"github.com/gohugoio/hugo/markup/converter/hooks"
+ "github.com/gohugoio/hugo/markup/goldmark/hugocontext"
"github.com/gohugoio/hugo/markup/highlight/chromalexers"
"github.com/gohugoio/hugo/markup/tableofcontents"
@@ -68,8 +69,9 @@ var (
func newPageContentOutput(po *pageOutput) (*pageContentOutput, error) {
cp := &pageContentOutput{
- po: po,
- renderHooks: &renderHooks{},
+ po: po,
+ renderHooks: &renderHooks{},
+ otherOutputs: make(map[uint64]*pageContentOutput),
}
return cp, nil
}
@@ -83,6 +85,10 @@ type renderHooks struct {
type pageContentOutput struct {
po *pageOutput
+ // Other pages involved in rendering of this page,
+ // typically included with .RenderShortcodes.
+ otherOutputs map[uint64]*pageContentOutput
+
contentRenderedVersion int // Incremented on reset.
contentRendered bool // Set on content render.
@@ -165,6 +171,13 @@ func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HT
cb(pco, ct)
}
+ if tpl.Context.IsInGoldmark.Get(ctx) {
+ // This content will be parsed and rendered by Goldmark.
+ // Wrap it in a special Hugo markup to assign the correct Page from
+ // the stack.
+ c = hugocontext.Wrap(c, pco.po.p.pid)
+ }
+
return helpers.BytesToHTML(c), nil
}
diff --git a/hugolib/rendershortcodes_test.go b/hugolib/rendershortcodes_test.go
index 696ed8f41..67b1a85ef 100644
--- a/hugolib/rendershortcodes_test.go
+++ b/hugolib/rendershortcodes_test.go
@@ -200,3 +200,99 @@ Myshort Original.
b.Build()
b.AssertFileContent("public/p1/index.html", "Edited")
}
+
+func TestRenderShortcodesNestedPageContextIssue12356(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
+-- layouts/_default/_markup/render-image.html --
+{{- with .PageInner.Resources.Get .Destination -}}Image: {{ .RelPermalink }}|{{- end -}}
+-- layouts/_default/_markup/render-link.html --
+{{- with .PageInner.GetPage .Destination -}}Link: {{ .RelPermalink }}|{{- end -}}
+-- layouts/_default/_markup/render-heading.html --
+Heading: {{ .PageInner.Title }}: {{ .PlainText }}|
+-- layouts/_default/_markup/render-codeblock.html --
+CodeBlock: {{ .PageInner.Title }}: {{ .Type }}|
+-- layouts/_default/list.html --
+Content:{{ .Content }}|
+Fragments: {{ with .Fragments }}{{.Identifiers }}{{ end }}|
+-- layouts/_default/single.html --
+Content:{{ .Content }}|
+-- layouts/shortcodes/include.html --
+{{ with site.GetPage (.Get 0) }}
+ {{ .RenderShortcodes }}
+{{ end }}
+-- content/markdown/_index.md --
+---
+title: "Markdown"
+---
+# H1
+|{{% include "/posts/p1" %}}|
+![kitten](pixel3.png "Pixel 3")
+
+§§§go
+fmt.Println("Hello")
+§§§
+
+-- content/markdown2/_index.md --
+---
+title: "Markdown 2"
+---
+|{{< include "/posts/p1" >}}|
+-- content/html/_index.html --
+---
+title: "HTML"
+---
+|{{% include "/posts/p1" %}}|
+
+-- content/posts/p1/index.md --
+---
+title: "p1"
+---
+## H2-p1
+![kitten](pixel1.png "Pixel 1")
+![kitten](pixel2.png "Pixel 2")
+[p2](p2)
+
+§§§bash
+echo "Hello"
+§§§
+
+-- content/posts/p2/index.md --
+---
+title: "p2"
+---
+-- content/posts/p1/pixel1.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- content/posts/p1/pixel2.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- content/markdown/pixel3.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+-- content/html/pixel4.png --
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
+
+`
+
+ b := Test(t, files)
+
+ b.AssertFileContent("public/markdown/index.html",
+ // Images.
+ "Image: /posts/p1/pixel1.png|\nImage: /posts/p1/pixel2.png|\n|\nImage: /markdown/pixel3.png|</p>\n|",
+ // Links.
+ "Link: /posts/p2/|",
+ // Code blocks
+ "CodeBlock: p1: bash|", "CodeBlock: Markdown: go|",
+ // Headings.
+ "Heading: Markdown: H1|", "Heading: p1: H2-p1|",
+ // Fragments.
+ "Fragments: [h1 h2-p1]|",
+ // Check that the special context markup is not rendered.
+ "! hugo_ctx",
+ )
+
+ b.AssertFileContent("public/markdown2/index.html", "! hugo_ctx", "Content:<p>|\n ![kitten](pixel1.png \"Pixel 1\")\n![kitten](pixel2.png \"Pixel 2\")\n|</p>\n|")
+
+ b.AssertFileContent("public/html/index.html", "! hugo_ctx")
+}
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index 7cc2cba27..af4454a89 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -321,10 +321,16 @@ func prepareShortcode(
// Allow the caller to delay the rendering of the shortcode if needed.
var fn shortcodeRenderFunc = func(ctx context.Context) ([]byte, bool, error) {
+ if p.m.pageConfig.IsGoldmark && sc.doMarkup {
+ // Signal downwards that the content rendered will be
+ // parsed and rendered by Goldmark.
+ ctx = tpl.Context.IsInGoldmark.Set(ctx, true)
+ }
r, err := doRenderShortcode(ctx, level, s, tplVariants, sc, parent, p, isRenderString)
if err != nil {
return nil, false, toParseErr(err)
}
+
b, hasVariants, err := r.renderShortcode(ctx)
if err != nil {
return nil, false, toParseErr(err)
diff --git a/markup/converter/converter.go b/markup/converter/converter.go
index b66cb8730..57980f138 100644
--- a/markup/converter/converter.go
+++ b/markup/converter/converter.go
@@ -135,10 +135,11 @@ func (b Bytes) Bytes() []byte {
// DocumentContext holds contextual information about the document to convert.
type DocumentContext struct {
- Document any // May be nil. Usually a page.Page
- DocumentID string
- DocumentName string
- Filename string
+ Document any // May be nil. Usually a page.Page
+ DocumentLookup func(uint64) any // May be nil.
+ DocumentID string
+ DocumentName string
+ Filename string
}
// RenderContext holds contextual information about the content to render.
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
index bdc38f119..54babd320 100644
--- a/markup/converter/hooks/hooks.go
+++ b/markup/converter/hooks/hooks.go
@@ -32,8 +32,7 @@ type AttributesProvider interface {
// LinkContext is the context passed to a link render hook.
type LinkContext interface {
- // The Page being rendered.
- Page() any
+ PageProvider
// The link URL.
Destination() string
@@ -64,6 +63,7 @@ type ImageLinkContext interface {
type CodeblockContext interface {
AttributesProvider
text.Positioner
+ PageProvider
// Chroma highlighting processing options. This will only be filled if Type is a known Chroma Lexer.
Options() map[string]any
@@ -76,9 +76,6 @@ type CodeblockContext interface {
// Zero-based ordinal for all code blocks in the current document.
Ordinal() int
-
- // The owning Page.
- Page() any
}
type AttributesOptionsSliceProvider interface {
@@ -101,8 +98,7 @@ type IsDefaultCodeBlockRendererProvider interface {
// HeadingContext contains accessors to all attributes that a HeadingRenderer
// can use to render a heading.
type HeadingContext interface {
- // Page is the page containing the heading.
- Page() any
+ PageProvider
// Level is the level of the header (i.e. 1 for top-level, 2 for sub-level, etc.).
Level() int
// Anchor is the HTML id assigned to the heading.
@@ -116,6 +112,16 @@ type HeadingContext interface {
AttributesProvider
}
+type PageProvider interface {
+ // Page is the page being rendered.
+ Page() any
+
+ // PageInner may be different than Page when .RenderShortcodes is in play.
+ // The main use case for this is to include other pages' markdown into the current page
+ // but resolve resources and pages relative to the original.
+ PageInner() any
+}
+
// HeadingRenderer describes a uniquely identifiable rendering hook.
type HeadingRenderer interface {
// RenderHeading writes the rendered content to w using the data in w.
diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go
index 67053640d..5dfa8262f 100644
--- a/markup/goldmark/codeblocks/render.go
+++ b/markup/goldmark/codeblocks/render.go
@@ -108,6 +108,7 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
}
cbctx := &codeBlockContext{
page: ctx.DocumentContext().Document,
+ pageInner: r.getPageInner(ctx),
lang: lang,
code: s,
ordinal: ordinal,
@@ -132,7 +133,6 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
w,
cbctx,
)
-
if err != nil {
return ast.WalkContinue, herrors.NewFileErrorFromPos(err, cbctx.createPos())
}
@@ -140,11 +140,24 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
return ast.WalkContinue, nil
}
+func (r *htmlRenderer) getPageInner(rctx *render.Context) any {
+ pid := rctx.PeekPid()
+ if pid > 0 {
+ if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
+ if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
+ return v
+ }
+ }
+ }
+ return rctx.DocumentContext().Document
+}
+
type codeBlockContext struct {
- page any
- lang string
- code string
- ordinal int
+ page any
+ pageInner any
+ lang string
+ code string
+ ordinal int
// This is only used in error situations and is expensive to create,
// to delay creation until needed.
@@ -159,6 +172,10 @@ func (c *codeBlockContext) Page() any {
return c.page
}
+func (c *codeBlockContext) PageInner() any {
+ return c.pageInner
+}
+
func (c *codeBlockContext) Type() string {
return c.lang
}
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index 643293c93..d7180140d 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -18,6 +18,7 @@ import (
"bytes"
"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
+ "github.com/gohugoio/hugo/markup/goldmark/hugocontext"
"github.com/yuin/goldmark/util"
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
@@ -103,6 +104,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
renderer.WithNodeRenderers(util.Prioritized(emoji.NewHTMLRenderer(), 200)))
var (
extensions = []goldmark.Extender{
+ hugocontext.New(),
newLinks(cfg),
newTocExtension(tocRendererOptions),
}
diff --git a/markup/goldmark/hugocontext/hugocontext.go b/markup/goldmark/hugocontext/hugocontext.go
new file mode 100644
index 000000000..ed62bb8c6
--- /dev/null
+++ b/markup/goldmark/hugocontext/hugocontext.go
@@ -0,0 +1,165 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugocontext
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+
+ "github.com/gohugoio/hugo/bufferpool"
+ "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/text"
+ "github.com/yuin/goldmark/util"
+)
+
+func New() goldmark.Extender {
+ return &hugoContextExtension{}
+}
+
+// Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
+// in .RenderShortcodes.
+func Wrap(b []byte, pid uint64) []byte {
+ buf := bufferpool.GetBuffer()
+ defer bufferpool.PutBuffer(buf)
+ buf.Write(prefix)
+ buf.WriteString(" pid=")
+ buf.WriteString(strconv.FormatUint(pid, 10))
+ buf.Write(endDelim)
+ buf.WriteByte('\n')
+ buf.Write(b)
+ buf.Write(prefix)
+ buf.Write(closingDelimAndNewline)
+ return buf.Bytes()
+}
+
+var kindHugoContext = ast.NewNodeKind("HugoContext")
+
+// HugoContext is a node that represents a Hugo context.
+type HugoContext struct {
+ ast.BaseInline
+
+ Closing bool
+
+ // Internal page ID. Not persisted.
+ Pid uint64
+}
+
+// Dump implements Node.Dump.
+func (n *HugoContext) Dump(source []byte, level int) {
+ m := map[string]string{}
+ m["Pid"] = fmt.Sprintf("%v", n.Pid)
+ ast.DumpHelper(n, source, level, m, nil)
+}
+
+func (n *HugoContext) parseAttrs(attrBytes []byte) {
+ keyPairs := bytes.Split(attrBytes, []byte(" "))
+ for _, keyPair := range keyPairs {
+ kv := bytes.Split(keyPair, []byte("="))
+ if len(kv) != 2 {
+ continue
+ }
+ key := string(kv[0])
+ val := string(kv[1])
+ switch key {
+ case "pid":
+ pid, _ := strconv.ParseUint(val, 10, 64)
+ n.Pid = pid
+ }
+ }
+}
+
+func (h *HugoContext) Kind() ast.NodeKind {
+ return kindHugoContext
+}
+
+var (
+ prefix = []byte("{{__hugo_ctx")
+ endDelim = []byte("}}")
+ closingDelimAndNewline = []byte("/}}\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) {
+ return nil
+ }
+ end := bytes.Index(line, endDelim)
+ if end == -1 {
+ return nil
+ }
+
+ block.Advance(end + len(endDelim) + 1) // +1 for the newline
+
+ if line[end-1] == '/' {
+ return &HugoContext{Closing: true}
+ }
+
+ attrBytes := line[len(prefix)+1 : end]
+ h := &HugoContext{}
+ h.parseAttrs(attrBytes)
+ return h
+}
+
+func (a *hugoContextParser) Trigger() []byte {
+ return []byte{'{'}
+}
+
+type hugoContextRenderer struct{}
+
+func (r *hugoContextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(kindHugoContext, r.handleHugoContext)
+}
+
+func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ hctx := node.(*HugoContext)
+ ctx, ok := w.(*render.Context)
+ if !ok {
+ return ast.WalkContinue, nil
+ }
+ if hctx.Closing {
+ _ = ctx.PopPid()
+ } else {
+ ctx.PushPid(hctx.Pid)
+ }
+ return ast.WalkContinue, nil
+}
+
+type hugoContextExtension struct{}
+
+func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
+ m.Parser().AddOptions(
+ parser.WithInlineParsers(
+ util.Prioritized(&hugoContextParser{}, 50),
+ ),
+ )
+
+ m.Renderer().AddOptions(
+ renderer.WithNodeRenderers(
+ util.Prioritized(&hugoContextRenderer{}, 50),
+ ),
+ )
+}
diff --git a/markup/goldmark/hugocontext/hugocontext_test.go b/markup/goldmark/hugocontext/hugocontext_test.go
new file mode 100644
index 000000000..da96cc339
--- /dev/null
+++ b/markup/goldmark/hugocontext/hugocontext_test.go
@@ -0,0 +1,34 @@
+// Copyright 2024 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package hugocontext
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestWrap(t *testing.T) {
+ c := qt.New(t)
+
+ b := []byte("test")
+
+ c.Assert(string(Wrap(b, 42)), qt.Equals, "{{__hugo_ctx pid=42}}\ntest{{__hugo_ctx/}}\n")
+}
+
+func BenchmarkWrap(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Wrap([]byte("test"), 42)
+ }
+}
diff --git a/markup/goldmark/internal/render/context.go b/markup/goldmark/internal/render/context.go
index 578714339..306d26748 100644
--- a/markup/goldmark/internal/render/context.go
+++ b/markup/goldmark/internal/render/context.go
@@ -41,6 +41,7 @@ func (b *BufWriter) Flush() error {
type Context struct {
*BufWriter
positions []int
+ pids []uint64
ContextData
}
@@ -55,6 +56,30 @@ func (ctx *Context) PopPos() int {
return p
}
+// PushPid pushes a new page ID to the stack.
+func (ctx *Context) PushPid(pid uint64) {
+ ctx.pids = append(ctx.pids, pid)
+}
+
+// PeekPid returns the current page ID without removing it from the stack.
+func (ctx *Context) PeekPid() uint64 {
+ if len(ctx.pids) == 0 {
+ return 0
+ }
+ return ctx.pids[len(ctx.pids)-1]
+}
+
+// PopPid pops the last page ID from the stack.
+func (ctx *Context) PopPid() uint64 {
+ if len(ctx.pids) == 0 {
+ return 0
+ }
+ i := len(ctx.pids) - 1
+ p := ctx.pids[i]
+ ctx.pids = ctx.pids[:i]
+ return p
+}
+
type ContextData interface {
RenderContext() converter.RenderContext
DocumentContext() converter.DocumentContext
diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go
index 8dcdc39c3..c127a2c0e 100644
--- a/markup/goldmark/render_hooks.go
+++ b/markup/goldmark/render_hooks.go
@@ -49,6 +49,7 @@ func newLinks(cfg goldmark_config.Config) goldmark.Extender {
type linkContext struct {
page any
+ pageInner any
destination string
title string
text hstring.RenderedString
@@ -64,6 +65,10 @@ func (ctx linkContext) Page() any {
return ctx.page
}
+func (ctx linkContext) PageInner() any {
+ return ctx.pageInner
+}
+
func (ctx linkContext) Text() hstring.RenderedString {
return ctx.text
}
@@ -92,6 +97,7 @@ func (ctx imageLinkContext) Ordinal() int {
type headingContext struct {
page any
+ pageInner any
level int
anchor string
text hstring.RenderedString
@@ -103,6 +109,10 @@ func (ctx headingContext) Page() any {
return ctx.page
}
+func (ctx headingContext) PageInner() any {
+ return ctx.pageInner
+}
+
func (ctx headingContext) Level() int {
return ctx.level
}
@@ -186,6 +196,7 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
imageLinkContext{
linkContext: linkContext{
page: ctx.DocumentContext().Document,
+ pageInner: r.getPageInner(ctx),
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
@@ -200,6 +211,18 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
return ast.WalkContinue, err
}
+func (r *hookedRenderer) getPageInner(rctx *render.Context) any {
+ pid := rctx.PeekPid()
+ if pid > 0 {
+ if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
+ if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
+ return v
+ }
+ }
+ }
+ return rctx.DocumentContext().Document
+}
+
func (r *hookedRenderer) filterInternalAttributes(attrs []ast.Attribute) []ast.Attribute {
n := 0
for _, x := range attrs {
@@ -274,6 +297,7 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
w,
linkContext{
page: ctx.DocumentContext().Document,
+ pageInner: r.getPageInner(ctx),
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
@@ -339,6 +363,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
w,
linkContext{
page: ctx.DocumentContext().Document,
+ pageInner: r.getPageInner(ctx),
destination: url,
text: hstring.RenderedString(label),
plainText: label,
@@ -423,6 +448,7 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
w,
headingContext{
page: ctx.DocumentContext().Document,
+ pageInner: r.getPageInner(ctx),
level: n.Level,
anchor: string(anchor),
text: hstring.RenderedString(text),
diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go
index 4e412c666..123dd4b70 100644
--- a/resources/page/pagemeta/page_frontmatter.go
+++ b/resources/page/pagemeta/page_frontmatter.go
@@ -88,6 +88,9 @@ type PageConfig struct {
// User defined params.
Params maps.Params
+
+ // Compiled values.
+ IsGoldmark bool `json:"-"`
}
// FrontMatterHandler maps front matter into Page fields and .Params.
diff --git a/tpl/template.go b/tpl/template.go
index e9725bd74..5ef0eecb8 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -165,10 +165,12 @@ var Context = struct {
SetDependencyManagerInCurrentScope func(context.Context, identity.Manager) context.Context
DependencyScope hcontext.ContextDispatcher[int]
Page hcontext.ContextDispatcher[page]
+ IsInGoldmark hcontext.ContextDispatcher[bool]
}{
DependencyManagerScopedProvider: hcontext.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKey("DependencyManagerScopedProvider")),
DependencyScope: hcontext.NewContextDispatcher[int](contextKey("DependencyScope")),
Page: hcontext.NewContextDispatcher[page](contextKey("Page")),
+ IsInGoldmark: hcontext.NewContextDispatcher[bool](contextKey("IsInGoldmark")),
}
func init() {
diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html b/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html
index fa78cd00c..013e31235 100644
--- a/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html
+++ b/tpl/tplimpl/embedded/templates/_default/_markup/render-image.html
@@ -1,7 +1,7 @@
{{- $u := urls.Parse .Destination -}}
{{- $src := $u.String -}}
{{- if not $u.IsAbs -}}
- {{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}}
+ {{- with or (.PageInner.Resources.Get $u.Path) (resources.Get $u.Path) -}}
{{- $src = .RelPermalink -}}
{{- end -}}
{{- end -}}
diff --git a/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html b/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html
index cfc95ab3a..8903d3dfb 100644
--- a/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html
+++ b/tpl/tplimpl/embedded/templates/_default/_markup/render-link.html
@@ -1,11 +1,11 @@
{{- $u := urls.Parse .Destination -}}
{{- $href := $u.String -}}
{{- if strings.HasPrefix $u.String "#" }}
- {{- $href = printf "%s#%s" .Page.RelPermalink $u.Fragment }}
+ {{- $href = printf "%s#%s" .PageInner.RelPermalink $u.Fragment }}
{{- else if not $u.IsAbs -}}
{{- with or
- ($.Page.GetPage $u.Path)
- ($.Page.Resources.Get $u.Path)
+ ($.PageInner.GetPage $u.Path)
+ ($.PageInner.Resources.Get $u.Path)
(resources.Get $u.Path)
-}}
{{- $href = .RelPermalink -}}