diff options
author | Bjørn Erik Pedersen <[email protected]> | 2024-04-11 17:46:18 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2024-04-15 09:49:57 +0200 |
commit | df11327ba90179747be2b25574ac48c2f336b298 (patch) | |
tree | 70f7487cee6815109fc325ed0619130bb29834ca | |
parent | a18e2bcb9a2c556e03dc7e790b0343c83163877a (diff) | |
download | hugo-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.go | 2 | ||||
-rw-r--r-- | hugolib/page__content.go | 5 | ||||
-rw-r--r-- | hugolib/page__meta.go | 24 | ||||
-rw-r--r-- | hugolib/page__per_output.go | 17 | ||||
-rw-r--r-- | hugolib/rendershortcodes_test.go | 96 | ||||
-rw-r--r-- | hugolib/shortcode.go | 6 | ||||
-rw-r--r-- | markup/converter/converter.go | 9 | ||||
-rw-r--r-- | markup/converter/hooks/hooks.go | 20 | ||||
-rw-r--r-- | markup/goldmark/codeblocks/render.go | 27 | ||||
-rw-r--r-- | markup/goldmark/convert.go | 2 | ||||
-rw-r--r-- | markup/goldmark/hugocontext/hugocontext.go | 165 | ||||
-rw-r--r-- | markup/goldmark/hugocontext/hugocontext_test.go | 34 | ||||
-rw-r--r-- | markup/goldmark/internal/render/context.go | 25 | ||||
-rw-r--r-- | markup/goldmark/render_hooks.go | 26 | ||||
-rw-r--r-- | resources/page/pagemeta/page_frontmatter.go | 3 | ||||
-rw-r--r-- | tpl/template.go | 2 | ||||
-rw-r--r-- | tpl/tplimpl/embedded/templates/_default/_markup/render-image.html | 2 | ||||
-rw-r--r-- | tpl/tplimpl/embedded/templates/_default/_markup/render-link.html | 6 |
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 -}} |