]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Add Page.RenderShortcodes
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 1 Aug 2023 16:12:36 +0000 (18:12 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 3 Aug 2023 18:00:57 +0000 (20:00 +0200)
A layouts/shortcodes/include.html shortcode may look like this:

```html
{{ $p := site.GetPage (.Get 0) }}
{{ $p.RenderShortcodes }}
```

Fixes #7297

14 files changed:
hugolib/page.go
hugolib/page__new.go
hugolib/page__output.go
hugolib/page__per_output.go
hugolib/page_test.go
hugolib/rendershortcodes_test.go [new file with mode: 0644]
hugolib/shortcode.go
hugolib/shortcode_page.go
hugolib/shortcode_test.go
resources/page/page.go
resources/page/page_nop.go
resources/page/testhelpers_test.go
tpl/internal/go_templates/texttemplate/hugo_template.go
tpl/template.go

index f8f96615688609a122d45d70d096bda1fa3272e6..81ba68aa42a7969a6e43d5f32871f72e13c51a5f 100644 (file)
@@ -114,6 +114,10 @@ func (pa pageSiteAdapter) GetPage(ref string) (page.Page, error) {
 }
 
 type pageState struct {
+       // Incremented for each new page created.
+       // Note that this will change between builds for a given Page.
+       id int
+
        // This slice will be of same length as the number of global slice of output
        // formats (for all sites).
        pageOutputs []*pageOutput
@@ -772,7 +776,7 @@ Loop:
                        currShortcode.pos = it.Pos()
                        currShortcode.length = iter.Current().Pos() - it.Pos()
                        if currShortcode.placeholder == "" {
-                               currShortcode.placeholder = createShortcodePlaceholder("s", currShortcode.ordinal)
+                               currShortcode.placeholder = createShortcodePlaceholder("s", p.id, currShortcode.ordinal)
                        }
 
                        if currShortcode.name != "" {
@@ -784,7 +788,7 @@ Loop:
                                currShortcode.params = s
                        }
 
-                       currShortcode.placeholder = createShortcodePlaceholder("s", ordinal)
+                       currShortcode.placeholder = createShortcodePlaceholder("s", p.id, ordinal)
                        ordinal++
                        s.shortcodes = append(s.shortcodes, currShortcode)
 
index 14db28c3df64aaaef4031e37e3edc187b29f7e85..108e5717f3f6e19338b4f64d167113f9e16a425c 100644 (file)
@@ -31,14 +31,19 @@ import (
        "github.com/gohugoio/hugo/resources/page"
 )
 
+var pageIdCounter atomic.Int64
+
 func newPageBase(metaProvider *pageMeta) (*pageState, error) {
        if metaProvider.s == nil {
                panic("must provide a Site")
        }
 
+       id := int(pageIdCounter.Add(1))
+
        s := metaProvider.s
 
        ps := &pageState{
+               id:                                id,
                pageOutput:                        nopPageOutput,
                pageOutputTemplateVariationsState: atomic.NewUint32(0),
                pageCommon: &pageCommon{
index 25ce26b7a4f2dbabec75e08698bc63a7844d251f..21f58e795866b3b03b995819f1f55eb0ef267e54 100644 (file)
@@ -86,6 +86,7 @@ type pageOutput struct {
        page.ContentProvider
        page.PageRenderProvider
        page.TableOfContentsProvider
+       page.RenderShortcodesProvider
 
        // May be nil.
        cp *pageContentOutput
@@ -99,6 +100,7 @@ func (p *pageOutput) initContentProvider(cp *pageContentOutput) {
        p.ContentProvider = cp
        p.PageRenderProvider = cp
        p.TableOfContentsProvider = cp
+       p.RenderShortcodesProvider = cp
        p.cp = cp
 
 }
index 89dc5ac77a69e588282d916aa432f51b79aa1a3a..e806ca339ecfcdefd4b1b3b4ed7e1c7c84dcd57d 100644 (file)
@@ -103,6 +103,30 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
                        return err
                }
 
+               ctxCallback := func(cp2 *pageContentOutput) {
+                       cp.p.cmap.hasNonMarkdownShortcode = cp.p.cmap.hasNonMarkdownShortcode || cp2.p.cmap.hasNonMarkdownShortcode
+                       // Merge content placeholders
+                       for k, v := range cp2.contentPlaceholders {
+                               cp.contentPlaceholders[k] = v
+                       }
+
+                       if p.s.watching() {
+                               for _, s := range cp2.p.shortcodeState.shortcodes {
+                                       for _, templ := range s.templs {
+                                               dependencyTracker.Add(templ.(identity.Manager))
+                                       }
+                               }
+                       }
+
+                       // Transfer shortcode names so HasShortcode works for shortcodes from included pages.
+                       cp.p.shortcodeState.transferNames(cp2.p.shortcodeState)
+                       if cp2.p.pageOutputTemplateVariationsState.Load() == 2 {
+                               cp.p.pageOutputTemplateVariationsState.Store(2)
+                       }
+               }
+
+               ctx = tpl.SetCallbackFunctionInContext(ctx, ctxCallback)
+
                var hasVariants bool
                cp.workContent, hasVariants, err = p.contentToRender(ctx, p.source.parsed, p.cmap, cp.contentPlaceholders)
                if err != nil {
@@ -350,6 +374,63 @@ func (p *pageContentOutput) Fragments(ctx context.Context) *tableofcontents.Frag
        return p.tableOfContents
 }
 
+func (p *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HTML, error) {
+       p.p.s.initInit(ctx, p.initToC, p.p)
+       source := p.p.source.parsed.Input()
+       renderedShortcodes := p.contentPlaceholders
+       var insertPlaceholders bool
+       var hasVariants bool
+       var cb func(*pageContentOutput)
+       if v := tpl.GetCallbackFunctionFromContext(ctx); v != nil {
+               if fn, ok := v.(func(*pageContentOutput)); ok {
+                       insertPlaceholders = true
+                       cb = fn
+               }
+       }
+       c := make([]byte, 0, len(source)+(len(source)/10))
+       for _, it := range p.p.cmap.items {
+               switch v := it.(type) {
+               case pageparser.Item:
+                       c = append(c, source[v.Pos():v.Pos()+len(v.Val(source))]...)
+               case pageContentReplacement:
+                       // Ignore.
+               case *shortcode:
+                       if !insertPlaceholders || !v.insertPlaceholder() {
+                               // Insert the rendered shortcode.
+                               renderedShortcode, found := renderedShortcodes[v.placeholder]
+                               if !found {
+                                       // This should never happen.
+                                       panic(fmt.Sprintf("rendered shortcode %q not found", v.placeholder))
+                               }
+
+                               b, more, err := renderedShortcode.renderShortcode(ctx)
+                               if err != nil {
+                                       return "", fmt.Errorf("failed to render shortcode: %w", err)
+                               }
+                               hasVariants = hasVariants || more
+                               c = append(c, []byte(b)...)
+
+                       } else {
+                               // Insert the placeholder so we can insert the content after
+                               // markdown processing.
+                               c = append(c, []byte(v.placeholder)...)
+                       }
+               default:
+                       panic(fmt.Sprintf("unknown item type %T", it))
+               }
+       }
+
+       if hasVariants {
+               p.p.pageOutputTemplateVariationsState.Store(2)
+       }
+
+       if cb != nil {
+               cb(p)
+       }
+
+       return helpers.BytesToHTML(c), nil
+}
+
 func (p *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
        p.p.s.initInit(ctx, p.initToC, p.p)
        return p.tableOfContentsHTML
index 5237b6340965f5f56d38940b637d40cc5621ccbc..fd115385dd92b2a5332ddf9dc170f9a6de2043fc 100644 (file)
@@ -1998,7 +1998,6 @@ func TestRenderWithoutArgument(t *testing.T) {
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
                },
        ).BuildE()
 
diff --git a/hugolib/rendershortcodes_test.go b/hugolib/rendershortcodes_test.go
new file mode 100644 (file)
index 0000000..c6fa711
--- /dev/null
@@ -0,0 +1,232 @@
+// Copyright 2023 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 hugolib
+
+import (
+       "strings"
+       "testing"
+)
+
+func TestRenderShortcodesBasic(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+disableKinds = ["home", "taxonomy", "term"]
+-- content/p1.md --
+---
+title: "p1"
+---
+## p1-h1
+{{% include "p2" %}}
+-- content/p2.md --
+---
+title: "p2"
+---
+### p2-h1
+{{< withhtml >}}
+### p2-h2
+{{% withmarkdown %}}
+### p2-h3
+{{% include "p3" %}}
+-- content/p3.md --
+---
+title: "p3"
+---
+### p3-h1
+{{< withhtml >}}
+### p3-h2
+{{% withmarkdown %}}
+{{< level3 >}}
+-- layouts/shortcodes/include.html --
+{{ $p := site.GetPage (.Get 0) }}
+{{ $p.RenderShortcodes }}
+-- layouts/shortcodes/withhtml.html --
+<div>{{ .Page.Title }} withhtml</div>
+-- layouts/shortcodes/withmarkdown.html --
+#### {{ .Page.Title }} withmarkdown
+-- layouts/shortcodes/level3.html --
+Level 3: {{ .Page.Title }}
+-- layouts/_default/single.html --
+Fragments: {{ .Fragments.Identifiers }}|
+HasShortcode Level 1: {{ .HasShortcode "include" }}|
+HasShortcode Level 2: {{ .HasShortcode "withmarkdown" }}|
+HasShortcode Level 3: {{ .HasShortcode "level3" }}|
+HasSHortcode not found: {{ .HasShortcode "notfound" }}|
+Content: {{ .Content }}|
+`
+
+       b := NewIntegrationTestBuilder(
+               IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+               },
+       ).Build()
+
+       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|",
+               "HasShortcode Level 2: true|",
+               "HasShortcode Level 3: true|",
+               "HasSHortcode not found: false|",
+       )
+
+       // TODO1 more assertions.
+
+}
+
+func TestRenderShortcodesNestedMultipleOutputFormatTemplates(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+disableKinds = ["home", "taxonomy", "term", "section", "rss", "sitemap", "robotsTXT", "404"]
+[outputs]
+page = ["html", "json"]
+-- content/p1.md --
+---
+title: "p1"
+---
+## p1-h1
+{{% include "p2" %}}
+-- content/p2.md --
+---
+title: "p2"
+---
+### p2-h1
+{{% myshort %}}
+-- layouts/shortcodes/include.html --
+{{ $p := site.GetPage (.Get 0) }}
+{{ $p.RenderShortcodes }}
+-- layouts/shortcodes/myshort.html --
+Myshort HTML.
+-- layouts/shortcodes/myshort.json --
+Myshort JSON.
+-- layouts/_default/single.html --
+HTML: {{ .Content }}
+-- layouts/_default/single.json --
+JSON: {{ .Content }}
+
+
+`
+
+       b := NewIntegrationTestBuilder(
+               IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+               },
+       ).Build()
+
+       b.AssertFileContent("public/p1/index.html", "Myshort HTML")
+       b.AssertFileContent("public/p1/index.json", "Myshort JSON")
+
+}
+
+func TestRenderShortcodesEditNested(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+disableLiveReload = true
+disableKinds = ["home", "taxonomy", "term", "section", "rss", "sitemap", "robotsTXT", "404"]
+-- content/p1.md --
+---
+title: "p1"
+---
+## p1-h1
+{{% include "p2" %}}
+-- content/p2.md --
+---
+title: "p2"
+---
+### p2-h1
+{{% myshort %}}
+-- layouts/shortcodes/include.html --
+{{ $p := site.GetPage (.Get 0) }}
+{{ $p.RenderShortcodes }}
+-- layouts/shortcodes/myshort.html --
+Myshort Original.
+-- layouts/_default/single.html --
+ {{ .Content }}
+
+
+
+`
+
+       b := NewIntegrationTestBuilder(
+               IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+                       Running:     true,
+               },
+       ).Build()
+
+       b.AssertFileContent("public/p1/index.html", "Myshort Original.")
+
+       b.EditFileReplace("layouts/shortcodes/myshort.html", func(s string) string {
+               return "Myshort Edited."
+       })
+       b.Build()
+       b.AssertFileContent("public/p1/index.html", "Myshort Edited.")
+
+}
+
+func TestRenderShortcodesEditIncludedPage(t *testing.T) {
+       t.Parallel()
+
+       files := `
+-- hugo.toml --
+disableLiveReload = true
+disableKinds = ["home", "taxonomy", "term", "section", "rss", "sitemap", "robotsTXT", "404"]
+-- content/p1.md --
+---
+title: "p1"
+---
+## p1-h1
+{{% include "p2" %}}
+-- content/p2.md --
+---
+title: "p2"
+---
+### Original
+{{% myshort %}}
+-- layouts/shortcodes/include.html --
+{{ $p := site.GetPage (.Get 0) }}
+{{ $p.RenderShortcodes }}
+-- layouts/shortcodes/myshort.html --
+Myshort Original.
+-- layouts/_default/single.html --
+ {{ .Content }}
+
+
+
+`
+
+       b := NewIntegrationTestBuilder(
+               IntegrationTestConfig{
+                       T:           t,
+                       TxtarString: files,
+                       Running:     true,
+               },
+       ).Build()
+
+       b.AssertFileContent("public/p1/index.html", "Original")
+
+       b.EditFileReplace("content/p2.md", func(s string) string {
+               return strings.Replace(s, "Original", "Edited", 1)
+       })
+       b.Build()
+       b.AssertFileContent("public/p1/index.html", "Edited")
+
+}
index c12fb888b06a8d9b6c49a288b7a27d55f69d15c2..e201d4d6ba39ef14127c2e652868e4ec73a563a3 100644 (file)
@@ -185,8 +185,8 @@ func (scp *ShortcodeWithPage) page() page.Page {
 // Note - this value must not contain any markup syntax
 const shortcodePlaceholderPrefix = "HAHAHUGOSHORTCODE"
 
-func createShortcodePlaceholder(id string, ordinal int) string {
-       return shortcodePlaceholderPrefix + id + strconv.Itoa(ordinal) + "HBHB"
+func createShortcodePlaceholder(sid string, id, ordinal int) string {
+       return shortcodePlaceholderPrefix + strconv.Itoa(id) + sid + strconv.Itoa(ordinal) + "HBHB"
 }
 
 type shortcode struct {
index 20fa22d2fe6f03d5ee03661bca9db7e6af1b126b..f351daae0bc9868a5eb7f103d0c0cb92125fa64c 100644 (file)
@@ -21,7 +21,7 @@ import (
 )
 
 // A placeholder for the TableOfContents markup. This is what we pass to the Goldmark etc. renderers.
-var tocShortcodePlaceholder = createShortcodePlaceholder("TOC", 0)
+var tocShortcodePlaceholder = createShortcodePlaceholder("TOC", 0, 0)
 
 // shortcodeRenderer is typically used to delay rendering of inner shortcodes
 // marked with placeholders in the content.
index 774794c56f6048be0f26d2fcc622b4401d04b5c8..6ee68673f8ac2463b2d61ec3f3049a299ed43cc5 100644 (file)
@@ -948,7 +948,6 @@ title: "p1"
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
                },
        ).Build()
 
@@ -991,7 +990,6 @@ title: "p1"
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
                },
        ).Build()
 
@@ -1023,7 +1021,6 @@ echo "foo";
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
                },
        ).Build()
 
@@ -1061,7 +1058,6 @@ title: "p1"
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
                },
        ).Build()
 
@@ -1098,8 +1094,8 @@ Title: {{ .Get "title" | safeHTML }}
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
-                       Verbose:     true,
+
+                       Verbose: true,
                },
        ).Build()
 
@@ -1191,8 +1187,8 @@ C'est un test
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
-                       Verbose:     true,
+
+                       Verbose: true,
                },
        ).Build()
 
@@ -1229,8 +1225,8 @@ InnerDeindent: {{ .Get 0 }}: {{ len .InnerDeindent }}
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
-                       Verbose:     true,
+
+                       Verbose: true,
                },
        ).Build()
 
@@ -1269,8 +1265,8 @@ Inner: {{ .Get 0 }}: {{ len .Inner }}
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
-                       Verbose:     true,
+
+                       Verbose: true,
                },
        ).BuildE()
 
@@ -1306,8 +1302,8 @@ Hello.
                IntegrationTestConfig{
                        T:           t,
                        TxtarString: files,
-                       Running:     true,
-                       Verbose:     true,
+
+                       Verbose: true,
                },
        ).Build()
 
index b1c8671958859deeb285858f3dc82fe693f9684f..54fc71f3310f456dc31c687a11f69de2714dfa20 100644 (file)
@@ -277,6 +277,7 @@ type PageRenderProvider interface {
 // PageWithoutContent is the Page without any of the content methods.
 type PageWithoutContent interface {
        RawContentProvider
+       RenderShortcodesProvider
        resource.Resource
        PageMetaProvider
        resource.LanguageProvider
@@ -362,6 +363,11 @@ type RawContentProvider interface {
        RawContent() string
 }
 
+type RenderShortcodesProvider interface {
+       // RenderShortcodes returns RawContent with any shortcodes rendered.
+       RenderShortcodes(context.Context) (template.HTML, error)
+}
+
 // RefProvider provides the methods needed to create reflinks to pages.
 type RefProvider interface {
        // Ref returns an absolute URl to a page.
index 59765ebf296bf4684ab02f28ba6a459859a1f378..735d6eea808650db35f48cf8154db48b9b5e96f4 100644 (file)
@@ -401,6 +401,10 @@ func (p *nopPage) RawContent() string {
        return ""
 }
 
+func (p *nopPage) RenderShortcodes(ctx context.Context) (template.HTML, error) {
+       return "", nil
+}
+
 func (p *nopPage) ReadingTime(context.Context) int {
        return 0
 }
index 50f297caba68fa41c682e2bbd046b6872e25725a..ca2c4ff53f52ebfc786b7642b4254c28d536ffc9 100644 (file)
@@ -118,15 +118,15 @@ func (p *testPage) Err() resource.ResourceError {
 }
 
 func (p *testPage) Aliases() []string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) AllTranslations() Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) AlternativeOutputFormats() OutputFormats {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Author() Author {
@@ -138,19 +138,19 @@ func (p *testPage) Authors() AuthorList {
 }
 
 func (p *testPage) BaseFileName() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) BundleType() files.ContentClass {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Content(context.Context) (any, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) ContentBaseName() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) CurrentSection() Page {
@@ -178,11 +178,11 @@ func (p *testPage) Description() string {
 }
 
 func (p *testPage) Dir() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Draft() bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Eq(other any) bool {
@@ -194,11 +194,11 @@ func (p *testPage) ExpiryDate() time.Time {
 }
 
 func (p *testPage) Ext() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Extension() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) File() source.File {
@@ -206,15 +206,15 @@ func (p *testPage) File() source.File {
 }
 
 func (p *testPage) FileInfo() hugofs.FileMetaInfo {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Filename() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) FirstSection() Page {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) FuzzyWordCount(context.Context) int {
@@ -222,19 +222,19 @@ func (p *testPage) FuzzyWordCount(context.Context) int {
 }
 
 func (p *testPage) GetPage(ref string) (Page, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) GetPageWithTemplateInfo(info tpl.Info, ref string) (Page, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) GetParam(key string) any {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) GetTerms(taxonomy string) Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) GetRelatedDocsHandler() *RelatedDocsHandler {
@@ -250,27 +250,27 @@ func (p *testPage) CodeOwners() []string {
 }
 
 func (p *testPage) HasMenuCurrent(menuID string, me *navigation.MenuEntry) bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) HasShortcode(name string) bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Hugo() hugo.HugoInfo {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) InSection(other any) (bool, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsAncestor(other any) (bool, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsDescendant(other any) (bool, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsDraft() bool {
@@ -278,27 +278,27 @@ func (p *testPage) IsDraft() bool {
 }
 
 func (p *testPage) IsHome() bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsMenuCurrent(menuID string, inme *navigation.MenuEntry) bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsNode() bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsPage() bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsSection() bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) IsTranslated() bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Keywords() []string {
@@ -314,7 +314,7 @@ func (p *testPage) Lang() string {
 }
 
 func (p *testPage) Language() *langs.Language {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) LanguagePrefix() string {
@@ -348,11 +348,11 @@ func (p *testPage) LinkTitle() string {
 }
 
 func (p *testPage) LogicalName() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) MediaType() media.Type {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Menus() navigation.PageMenus {
@@ -360,11 +360,11 @@ func (p *testPage) Menus() navigation.PageMenus {
 }
 
 func (p *testPage) Name() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Next() Page {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) NextInSection() Page {
@@ -376,19 +376,19 @@ func (p *testPage) NextPage() Page {
 }
 
 func (p *testPage) OutputFormats() OutputFormats {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Pages() Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) RegularPages() Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) RegularPagesRecursive() Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Paginate(seq any, options ...any) (*Pager, error) {
@@ -412,11 +412,11 @@ func (p *testPage) Page() Page {
 }
 
 func (p *testPage) Parent() Page {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Ancestors() Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Path() string {
@@ -428,19 +428,19 @@ func (p *testPage) Pathc() string {
 }
 
 func (p *testPage) Permalink() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Plain(context.Context) string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) PlainWords(context.Context) []string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Prev() Page {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) PrevInSection() Page {
@@ -460,15 +460,19 @@ func (p *testPage) RSSLink() template.URL {
 }
 
 func (p *testPage) RawContent() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
+}
+
+func (p *testPage) RenderShortcodes(context.Context) (template.HTML, error) {
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) ReadingTime(context.Context) int {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Ref(argsm map[string]any) (string, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) RefFrom(argsm map[string]any, source any) (string, error) {
@@ -476,11 +480,11 @@ func (p *testPage) RefFrom(argsm map[string]any, source any) (string, error) {
 }
 
 func (p *testPage) RelPermalink() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) RelRef(argsm map[string]any) (string, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) RelRefFrom(argsm map[string]any, source any) (string, error) {
@@ -488,27 +492,27 @@ func (p *testPage) RelRefFrom(argsm map[string]any, source any) (string, error)
 }
 
 func (p *testPage) Render(ctx context.Context, layout ...string) (template.HTML, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) RenderString(ctx context.Context, args ...any) (template.HTML, error) {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) ResourceType() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Resources() resource.Resources {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Scratch() *maps.Scratch {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Store() *maps.Scratch {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) RelatedKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
@@ -525,7 +529,7 @@ func (p *testPage) Section() string {
 }
 
 func (p *testPage) Sections() Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) SectionsEntries() []string {
@@ -541,7 +545,7 @@ func (p *testPage) Site() Site {
 }
 
 func (p *testPage) Sites() Sites {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Slug() string {
@@ -553,11 +557,11 @@ func (p *testPage) String() string {
 }
 
 func (p *testPage) Summary(context.Context) template.HTML {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) TableOfContents(context.Context) template.HTML {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Title() string {
@@ -565,7 +569,7 @@ func (p *testPage) Title() string {
 }
 
 func (p *testPage) TranslationBaseName() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) TranslationKey() string {
@@ -573,11 +577,11 @@ func (p *testPage) TranslationKey() string {
 }
 
 func (p *testPage) Translations() Pages {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Truncated(context.Context) bool {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Type() string {
@@ -589,7 +593,7 @@ func (p *testPage) URL() string {
 }
 
 func (p *testPage) UniqueID() string {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) Weight() int {
@@ -597,11 +601,11 @@ func (p *testPage) Weight() int {
 }
 
 func (p *testPage) WordCount(context.Context) int {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func (p *testPage) GetIdentity() identity.Identity {
-       panic("tespage: not implemented")
+       panic("testpage: not implemented")
 }
 
 func createTestPages(num int) Pages {
index acfa2f16630714a6c58ed6fec67a78edfb237142..78be55e1858018a2acb6a47d68453d8acff740b1 100644 (file)
@@ -60,9 +60,10 @@ func NewExecuter(helper ExecHelper) Executer {
 }
 
 type (
-       pageContextKeyType    string
-       hasLockContextKeyType string
-       stackContextKeyType   string
+       pageContextKeyType     string
+       hasLockContextKeyType  string
+       stackContextKeyType    string
+       callbackContextKeyType string
 )
 
 const (
@@ -70,6 +71,9 @@ const (
        PageContextKey = pageContextKeyType("page")
        // Used in partialCached to signal to nested templates that a lock is already taken.
        HasLockContextKey = hasLockContextKeyType("hasLock")
+
+       // Used to pass down a callback function to nested templates.
+       CallbackContextKey = callbackContextKeyType("callback")
 )
 
 // Note: The context is currently not fully implemented in Hugo. This is a work in progress.
index 91a482c25042c1c3edff49e38367b33f7d173fcd..70fdf2d3d21daeb5ca75a64fc8750aeb3a9ca292 100644 (file)
@@ -191,6 +191,14 @@ func SetHasLockInContext(ctx context.Context, hasLock bool) context.Context {
        return context.WithValue(ctx, texttemplate.HasLockContextKey, hasLock)
 }
 
+func GetCallbackFunctionFromContext(ctx context.Context) any {
+       return ctx.Value(texttemplate.CallbackContextKey)
+}
+
+func SetCallbackFunctionInContext(ctx context.Context, fn any) context.Context {
+       return context.WithValue(ctx, texttemplate.CallbackContextKey, fn)
+}
+
 const hugoNewLinePlaceholder = "___hugonl_"
 
 var (