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 {
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
--- /dev/null
+// 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")
+
+}
}
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 {
}
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 {
}
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 {
}
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 {
}
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 {
}
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 {
}
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 {
}
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 {
}
func (p *testPage) Language() *langs.Language {
- panic("tespage: not implemented")
+ panic("testpage: not implemented")
}
func (p *testPage) LanguagePrefix() 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 {
}
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 {
}
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) {
}
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 {
}
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 {
}
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) {
}
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) {
}
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) {
}
func (p *testPage) Sections() Pages {
- panic("tespage: not implemented")
+ panic("testpage: not implemented")
}
func (p *testPage) SectionsEntries() []string {
}
func (p *testPage) Sites() Sites {
- panic("tespage: not implemented")
+ panic("testpage: not implemented")
}
func (p *testPage) Slug() 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 {
}
func (p *testPage) TranslationBaseName() string {
- panic("tespage: not implemented")
+ panic("testpage: not implemented")
}
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 {
}
func (p *testPage) UniqueID() string {
- panic("tespage: not implemented")
+ panic("testpage: not implemented")
}
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 {