summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--deps/deps.go10
-rw-r--r--helpers/content.go3
-rw-r--r--hugolib/config.go1
-rw-r--r--hugolib/embedded_shortcodes_test.go2
-rw-r--r--hugolib/hugo_sites.go37
-rw-r--r--hugolib/hugo_sites_build.go1
-rw-r--r--hugolib/hugo_sites_build_test.go33
-rw-r--r--hugolib/page.go243
-rw-r--r--hugolib/pageSort.go2
-rw-r--r--hugolib/pageSort_test.go4
-rw-r--r--hugolib/page_bundler_handlers.go4
-rw-r--r--hugolib/page_bundler_test.go25
-rw-r--r--hugolib/page_test.go33
-rw-r--r--hugolib/page_without_content.go67
-rw-r--r--hugolib/shortcode.go44
-rw-r--r--hugolib/shortcode_test.go14
-rw-r--r--hugolib/site.go17
-rw-r--r--hugolib/site_render.go5
18 files changed, 392 insertions, 153 deletions
diff --git a/deps/deps.go b/deps/deps.go
index fd9635444..475d678a9 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -4,6 +4,7 @@ import (
"io/ioutil"
"log"
"os"
+ "time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
@@ -54,6 +55,9 @@ type Deps struct {
translationProvider ResourceProvider
Metrics metrics.Provider
+
+ // Timeout is configurable in site config.
+ Timeout time.Duration
}
// ResourceProvider is used to create and refresh, and clone resources needed.
@@ -128,6 +132,11 @@ func New(cfg DepsCfg) (*Deps, error) {
sp := source.NewSourceSpec(ps, fs.Source)
+ timeoutms := cfg.Language.GetInt("timeout")
+ if timeoutms <= 0 {
+ timeoutms = 3000
+ }
+
d := &Deps{
Fs: fs,
Log: logger,
@@ -139,6 +148,7 @@ func New(cfg DepsCfg) (*Deps, error) {
SourceSpec: sp,
Cfg: cfg.Language,
Language: cfg.Language,
+ Timeout: time.Duration(timeoutms) * time.Millisecond,
}
if cfg.Cfg.GetBool("templateMetrics") {
diff --git a/helpers/content.go b/helpers/content.go
index f2cfc9b0f..f12a55ba8 100644
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -400,6 +400,9 @@ func (c ContentSpec) mmarkRender(ctx *RenderingContext) []byte {
// ExtractTOC extracts Table of Contents from content.
func ExtractTOC(content []byte) (newcontent []byte, toc []byte) {
+ if !bytes.Contains(content, []byte("<nav>")) {
+ return content, nil
+ }
origContent := make([]byte, len(content))
copy(origContent, content)
first := []byte(`<nav>
diff --git a/hugolib/config.go b/hugolib/config.go
index b166e7729..cc808597c 100644
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -435,6 +435,7 @@ func loadDefaultSettingsFor(v *viper.Viper) error {
v.SetDefault("disableAliases", false)
v.SetDefault("debug", false)
v.SetDefault("disableFastRender", false)
+ v.SetDefault("timeout", 10000) // 10 seconds
// Remove in Hugo 0.39
diff --git a/hugolib/embedded_shortcodes_test.go b/hugolib/embedded_shortcodes_test.go
index fb663f1cb..fb1bd1282 100644
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -69,7 +69,7 @@ func doTestShortcodeCrossrefs(t *testing.T, relative bool) {
require.Len(t, s.RegularPages, 1)
- output := string(s.RegularPages[0].content)
+ output := string(s.RegularPages[0].content())
if !strings.Contains(output, expected) {
t.Errorf("Got\n%q\nExpected\n%q", output, expected)
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 7eec98329..bafb89e24 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -560,37 +560,22 @@ func (h *HugoSites) setupTranslations() {
}
func (s *Site) preparePagesForRender(cfg *BuildCfg) {
-
- pageChan := make(chan *Page)
- wg := &sync.WaitGroup{}
-
- numWorkers := getGoMaxProcs() * 4
-
- for i := 0; i < numWorkers; i++ {
- wg.Add(1)
- go func(pages <-chan *Page, wg *sync.WaitGroup) {
- defer wg.Done()
- for p := range pages {
- if err := p.prepareForRender(cfg); err != nil {
- s.Log.ERROR.Printf("Failed to prepare page %q for render: %s", p.BaseFileName(), err)
-
- }
- }
- }(pageChan, wg)
- }
-
for _, p := range s.Pages {
- pageChan <- p
+ p.setContentInit(cfg)
+ // The skip render flag is used in many tests. To make sure that they
+ // have access to the content, we need to manually initialize it here.
+ if cfg.SkipRender {
+ p.initContent()
+ }
}
for _, p := range s.headlessPages {
- pageChan <- p
+ p.setContentInit(cfg)
+ if cfg.SkipRender {
+ p.initContent()
+ }
}
- close(pageChan)
-
- wg.Wait()
-
}
// Pages returns all pages for all sites.
@@ -598,7 +583,7 @@ func (h *HugoSites) Pages() Pages {
return h.Sites[0].AllPages
}
-func handleShortcodes(p *Page, rawContentCopy []byte) ([]byte, error) {
+func handleShortcodes(p *PageWithoutContent, rawContentCopy []byte) ([]byte, error) {
if p.shortcodeState != nil && len(p.shortcodeState.contentShortcodes) > 0 {
p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.shortcodeState.contentShortcodes), p.BaseFileName())
err := p.shortcodeState.executeShortcodesForDelta(p)
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index 1c4ee7b63..dcff4b3b2 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -224,6 +224,7 @@ func (h *HugoSites) render(config *BuildCfg) error {
s.initRenderFormats()
for i, rf := range s.renderFormats {
s.rc = &siteRenderingContext{Format: rf}
+
s.preparePagesForRender(config)
if !config.SkipRender {
diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go
index 0515def4e..87eb2cb29 100644
--- a/hugolib/hugo_sites_build_test.go
+++ b/hugolib/hugo_sites_build_test.go
@@ -378,9 +378,9 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
b.AssertFileContent("public/en/tags/tag1/index.html", "Tag1|Hello|http://example.com/blog/en/tags/tag1/")
// Check Blackfriday config
- require.True(t, strings.Contains(string(doc1fr.content), "&laquo;"), string(doc1fr.content))
- require.False(t, strings.Contains(string(doc1en.content), "&laquo;"), string(doc1en.content))
- require.True(t, strings.Contains(string(doc1en.content), "&ldquo;"), string(doc1en.content))
+ require.True(t, strings.Contains(string(doc1fr.content()), "&laquo;"), string(doc1fr.content()))
+ require.False(t, strings.Contains(string(doc1en.content()), "&laquo;"), string(doc1en.content()))
+ require.True(t, strings.Contains(string(doc1en.content()), "&ldquo;"), string(doc1en.content()))
// Check that the drafts etc. are not built/processed/rendered.
assertShouldNotBuild(t, b.H)
@@ -630,9 +630,9 @@ func assertShouldNotBuild(t *testing.T, sites *HugoSites) {
for _, p := range s.rawAllPages {
// No HTML when not processed
require.Equal(t, p.shouldBuild(), bytes.Contains(p.workContent, []byte("</")), p.BaseFileName()+": "+string(p.workContent))
- require.Equal(t, p.shouldBuild(), p.content != "", p.BaseFileName())
+ require.Equal(t, p.shouldBuild(), p.content() != "", p.BaseFileName())
- require.Equal(t, p.shouldBuild(), p.content != "", p.BaseFileName())
+ require.Equal(t, p.shouldBuild(), p.content() != "", p.BaseFileName())
}
}
@@ -753,6 +753,29 @@ var tocShortcode = `
{{ .Page.TableOfContents }}
`
+func TestSelfReferencedContentInShortcode(t *testing.T) {
+ t.Parallel()
+
+ b := newMultiSiteTestDefaultBuilder(t)
+
+ var (
+ shortcode = `{{- .Page.Content -}}{{- .Page.Summary -}}{{- .Page.Plain -}}{{- .Page.PlainWords -}}{{- .Page.WordCount -}}{{- .Page.ReadingTime -}}`
+
+ page = `---
+title: sctest
+---
+Empty:{{< mycontent >}}:
+`
+ )
+
+ b.WithTemplatesAdded("layouts/shortcodes/mycontent.html", shortcode)
+ b.WithContent("post/simple.en.md", page)
+
+ b.CreateSites().Build(BuildCfg{})
+
+ b.AssertFileContent("public/en/post/simple/index.html", "Empty:[]00:")
+}
+
var tocPageSimple = `---
title: tocTest
publishdate: "2000-01-01"
diff --git a/hugolib/page.go b/hugolib/page.go
index ebd7a3a2a..5f9f86a0f 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -15,6 +15,7 @@ package hugolib
import (
"bytes"
+ "context"
"errors"
"fmt"
"reflect"
@@ -89,6 +90,7 @@ const (
type Page struct {
*pageInit
+ *pageContentInit
// Kind is the discriminator that identifies the different page types
// in the different page collections. This can, as an example, be used
@@ -127,17 +129,22 @@ type Page struct {
// Params contains configuration defined in the params section of page frontmatter.
params map[string]interface{}
+ // Called when needed to init the content (render shortcodes etc.).
+ contentInitFn func(p *Page) func()
+
// Content sections
- content template.HTML
- Summary template.HTML
+ contentv template.HTML
+ summary template.HTML
TableOfContents template.HTML
+ // Passed to the shortcodes
+ pageWithoutContent *PageWithoutContent
Aliases []string
Images []Image
Videos []Video
- Truncated bool
+ truncated bool
Draft bool
Status string
@@ -263,8 +270,69 @@ type Page struct {
targetPathDescriptorPrototype *targetPathDescriptor
}
+func (p *Page) initContent() {
+ p.contentInit.Do(func() {
+ // This careful dance is here to protect against circular loops in shortcode/content
+ // constructs.
+ // TODO(bep) context vs the remote shortcodes
+ ctx, cancel := context.WithTimeout(context.Background(), p.s.Timeout)
+ defer cancel()
+ c := make(chan error, 1)
+
+ go func() {
+ var err error
+ p.contentInitMu.Lock()
+ defer p.contentInitMu.Unlock()
+
+ if p.contentInitFn != nil {
+ p.contentInitFn(p)()
+ }
+ if len(p.summary) == 0 {
+ if err = p.setAutoSummary(); err != nil {
+ err = fmt.Errorf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err)
+ }
+ }
+ c <- err
+ }()
+
+ select {
+ case <-ctx.Done():
+ p.s.Log.WARN.Printf(`WARNING: Timed out creating content for page %q (.Content will be empty). This is most likely a circular shortcode content loop that should be fixed. If this is just a shortcode calling a slow remote service, try to set "timeout=20000" (or higher, value is in milliseconds) in config.toml.`, p.pathOrTitle())
+ case err := <-c:
+ if err != nil {
+ p.s.Log.ERROR.Println(err)
+ }
+ }
+ })
+
+}
+
+// This is sent to the shortcodes for this page. Not doing that will create an infinite regress. So,
+// shortcodes can access .Page.TableOfContents, but not .Page.Content etc.
+func (p *Page) withoutContent() *PageWithoutContent {
+ p.pageInit.withoutContentInit.Do(func() {
+ p.pageWithoutContent = &PageWithoutContent{Page: p}
+ })
+ return p.pageWithoutContent
+}
+
func (p *Page) Content() (interface{}, error) {
- return p.content, nil
+ return p.content(), nil
+}
+
+func (p *Page) Truncated() bool {
+ p.initContent()
+ return p.truncated
+}
+
+func (p *Page) content() template.HTML {
+ p.initContent()
+ return p.contentv
+}
+
+func (p *Page) Summary() template.HTML {
+ p.initContent()
+ return p.summary
}
// Sites is a convenience method to get all the Hugo sites/languages configured.
@@ -341,9 +409,25 @@ type pageInit struct {
pageMenusInit sync.Once
pageMetaInit sync.Once
pageOutputInit sync.Once
- plainInit sync.Once
- plainWordsInit sync.Once
renderingConfigInit sync.Once
+ withoutContentInit sync.Once
+}
+
+type pageContentInit struct {
+ contentInitMu sync.Mutex
+ contentInit sync.Once
+ plainInit sync.Once
+ plainWordsInit sync.Once
+}
+
+func (p *Page) resetContent(init func(page *Page) func()) {
+ p.pageContentInit = &pageContentInit{}
+ if init == nil {
+ init = func(page *Page) func() {
+ return func() {}
+ }
+ }
+ p.contentInitFn = init
}
// IsNode returns whether this is an item of one of the list types in Hugo,
@@ -455,26 +539,34 @@ func (p *Page) createWorkContentCopy() {
}
func (p *Page) Plain() string {
- p.initPlain()
+ p.initContent()
+ p.initPlain(true)
return p.plain
}
-func (p *Page) PlainWords() []string {
- p.initPlainWords()
- return p.plainWords
-}
-
-func (p *Page) initPlain() {
+func (p *Page) initPlain(lock bool) {
p.plainInit.Do(func() {
- p.plain = helpers.StripHTML(string(p.content))
- return
+ if lock {
+ p.contentInitMu.Lock()
+ defer p.contentInitMu.Unlock()
+ }
+ p.plain = helpers.StripHTML(string(p.contentv))
})
}
-func (p *Page) initPlainWords() {
+func (p *Page) PlainWords() []string {
+ p.initContent()
+ p.initPlainWords(true)
+ return p.plainWords
+}
+
+func (p *Page) initPlainWords(lock bool) {
p.plainWordsInit.Do(func() {
- p.plainWords = strings.Fields(p.Plain())
- return
+ if lock {
+ p.contentInitMu.Lock()
+ defer p.contentInitMu.Unlock()
+ }
+ p.plainWords = strings.Fields(p.plain)
})
}
@@ -622,7 +714,7 @@ func (p *Page) replaceDivider(content []byte) []byte {
replaced, truncated := replaceDivider(content, summaryDivider, internalSummaryDivider)
- p.Truncated = truncated
+ p.truncated = truncated
return replaced
}
@@ -641,7 +733,7 @@ func (p *Page) setUserDefinedSummaryIfProvided(rawContentCopy []byte) (*summaryC
return nil, nil
}
- p.Summary = helpers.BytesToHTML(sc.summary)
+ p.summary = helpers.BytesToHTML(sc.summary)
return sc, nil
}
@@ -731,15 +823,21 @@ func splitUserDefinedSummaryAndContent(markup string, c []byte) (sc *summaryCont
func (p *Page) setAutoSummary() error {
var summary string
var truncated bool
+ // This careful init dance could probably be refined, but it is purely for performance
+ // reasons. These "plain" methods are expensive if the plain content is never actually
+ // used.
+ p.initPlain(false)
if p.isCJKLanguage {
- summary, truncated = p.s.ContentSpec.TruncateWordsByRune(p.PlainWords())
+ p.initPlainWords(false)
+ summary, truncated = p.s.ContentSpec.TruncateWordsByRune(p.plainWords)
} else {
- summary, truncated = p.s.ContentSpec.TruncateWordsToWholeSentence(p.Plain())
+ summary, truncated = p.s.ContentSpec.TruncateWordsToWholeSentence(p.plain)
}
- p.Summary = template.HTML(summary)
- p.Truncated = truncated
+ p.summary = template.HTML(summary)
+ p.truncated = truncated
return nil
+
}
func (p *Page) renderContent(content []byte) []byte {
@@ -788,11 +886,12 @@ func (s *Site) newPage(filename string) *Page {
func (s *Site) newPageFromFile(fi *fileInfo) *Page {
return &Page{
- pageInit: &pageInit{},
- Kind: kindFromFileInfo(fi),
- contentType: "",
- Source: Source{File: fi},
- Keywords: []string{}, Sitemap: Sitemap{Priority: -1},
+ pageInit: &pageInit{},
+ pageContentInit: &pageContentInit{},
+ Kind: kindFromFileInfo(fi),
+ contentType: "",
+ Source: Source{File: fi},
+ Keywords: []string{}, Sitemap: Sitemap{Priority: -1},
params: make(map[string]interface{}),
translations: make(Pages, 0),
sections: sectionsFromFile(fi),
@@ -876,10 +975,11 @@ func (p *Page) FuzzyWordCount() int {
}
func (p *Page) analyzePage() {
+ p.initContent()
p.pageMetaInit.Do(func() {
if p.isCJKLanguage {
p.wordCount = 0
- for _, word := range p.PlainWords() {
+ for _, word := range p.plainWords {
runeCount := utf8.RuneCountInString(word)
if len(word) == runeCount {
p.wordCount++
@@ -888,7 +988,7 @@ func (p *Page) analyzePage() {
}
}
} else {
- p.wordCount = helpers.TotalWords(p.Plain())
+ p.wordCount = helpers.TotalWords(p.plain)
}
// TODO(bep) is set in a test. Fix that.
@@ -1045,10 +1145,8 @@ func (p *Page) subResourceTargetPathFactory(base string) string {
return path.Join(p.relTargetPathBase, base)
}
-func (p *Page) prepareForRender(cfg *BuildCfg) error {
- s := p.s
-
- if !p.shouldRenderTo(s.rc.Format) {
+func (p *Page) setContentInit(cfg *BuildCfg) error {
+ if !p.shouldRenderTo(p.s.rc.Format) {
// No need to prepare
return nil
}
@@ -1058,11 +1156,40 @@ func (p *Page) prepareForRender(cfg *BuildCfg) error {
shortcodeUpdate = p.shortcodeState.updateDelta()
}
- if !shortcodeUpdate && !cfg.whatChanged.other {
- // No need to process it again.
- return nil
+ resetFunc := func(page *Page) func() {
+ return func() {
+ err := page.prepareForRender(cfg)
+ if err != nil {
+ p.s.Log.ERROR.Printf("Failed to prepare page %q for render: %s", page.Path(), err)
+ }
+ }
+ }
+
+ if shortcodeUpdate || cfg.whatChanged.other {
+ p.resetContent(resetFunc)
+ }
+
+ // Handle bundled pages.
+ for _, r := range p.Resources.ByType(pageResourceType) {
+ shortcodeUpdate = false
+ bp := r.(*Page)
+
+ if bp.shortcodeState != nil {
+ shortcodeUpdate = bp.shortcodeState.updateDelta()
+ }
+
+ if shortcodeUpdate || cfg.whatChanged.other {
+ p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
+ bp.resetContent(resetFunc)
+ }
}
+ return nil
+}
+
+func (p *Page) prepareForRender(cfg *BuildCfg) error {
+ s := p.s
+
// If we got this far it means that this is either a new Page pointer
// or a template or similar has changed so wee need to do a rerendering
// of the shortcodes etc.
@@ -1080,14 +1207,10 @@ func (p *Page) prepareForRender(cfg *BuildCfg) error {
workContentCopy = p.workContent
}
- if p.Markup == "markdown" {
- tmpContent, tmpTableOfContents := helpers.ExtractTOC(workContentCopy)
- p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents)
- workContentCopy = tmpContent
- }
-
var err error
- if workContentCopy, err = handleShortcodes(p, workContentCopy); err != nil {
+ // Note: The shortcodes in a page cannot access the page content it lives in,
+ // hence the withoutContent().
+ if workContentCopy, err = handleShortcodes(p.withoutContent(), workContentCopy); err != nil {
s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
}
@@ -1102,28 +1225,10 @@ func (p *Page) prepareForRender(cfg *BuildCfg) error {
workContentCopy = summaryContent.content
}
- p.content = helpers.BytesToHTML(workContentCopy)
-
- if summaryContent == nil {
- if err := p.setAutoSummary(); err != nil {
- s.Log.ERROR.Printf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err)
- }
- }
+ p.contentv = helpers.BytesToHTML(workContentCopy)
} else {
- p.content = helpers.BytesToHTML(workContentCopy)
- }
-
- //analyze for raw stats
- p.analyzePage()
-
- // Handle bundled pages.
- for _, r := range p.Resources.ByType(pageResourceType) {
- p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
- bp := r.(*Page)
- if err := bp.prepareForRender(cfg); err != nil {
- s.Log.ERROR.Printf("Failed to prepare bundled page %q for render: %s", bp.BaseFileName(), err)
- }
+ p.contentv = helpers.BytesToHTML(workContentCopy)
}
return nil
@@ -1701,9 +1806,10 @@ func (p *Page) SaveSource() error {
return p.SaveSourceAs(p.FullFilePath())
}
+// TODO(bep) lazy consolidate
func (p *Page) processShortcodes() error {
p.shortcodeState = newShortcodeHandler(p)
- tmpContent, err := p.shortcodeState.extractShortcodes(string(p.workContent), p)
+ tmpContent, err := p.shortcodeState.extractShortcodes(string(p.workContent), p.withoutContent())
if err != nil {
return err
}
@@ -1724,7 +1830,7 @@ func (p *Page) prepareLayouts() error {
if p.Kind == KindPage {
if !p.IsRenderable() {
self := "__" + p.UniqueID()
- err := p.s.TemplateHandler().AddLateTemplate(self, string(p.content))
+ err := p.s.TemplateHandler().AddLateTemplate(self, string(p.content()))
if err != nil {
return err
}
@@ -1833,8 +1939,11 @@ func (p *Page) updatePageDates() {
// copy creates a copy of this page with the lazy sync.Once vars reset
// so they will be evaluated again, for word count calculations etc.
func (p *Page) copy() *Page {
+ p.contentInitMu.Lock()
c := *p
+ p.contentInitMu.Unlock()
c.pageInit = &pageInit{}
+ c.pageContentInit = &pageContentInit{}
return &c
}
diff --git a/hugolib/pageSort.go b/hugolib/pageSort.go
index 26682a3c8..cd312ac2f 100644
--- a/hugolib/pageSort.go
+++ b/hugolib/pageSort.go
@@ -237,7 +237,7 @@ func (p Pages) ByLength() Pages {
key := "pageSort.ByLength"
length := func(p1, p2 *Page) bool {
- return len(p1.content) < len(p2.content)
+ return len(p1.content()) < len(p2.content())
}
pages, _ := spc.get(key, pageBy(length).Sort, p)
diff --git a/hugolib/pageSort_test.go b/hugolib/pageSort_test.go
index 2b0ceb367..84711d288 100644
--- a/hugolib/pageSort_test.go
+++ b/hugolib/pageSort_test.go
@@ -80,7 +80,7 @@ func TestSortByN(t *testing.T) {
{(Pages).ByPublishDate, func(p Pages) bool { return p[0].PublishDate == d4 }},
{(Pages).ByExpiryDate, func(p Pages) bool { return p[0].ExpiryDate == d4 }},
{(Pages).ByLastmod, func(p Pages) bool { return p[1].Lastmod == d3 }},
- {(Pages).ByLength, func(p Pages) bool { return p[0].content == "b_content" }},
+ {(Pages).ByLength, func(p Pages) bool { return p[0].content() == "b_content" }},
} {
setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "ab", "cde", "fg"}, [4]int{0, 3, 2, 1}, p)
@@ -168,7 +168,7 @@ func setSortVals(dates [4]time.Time, titles [4]string, weights [4]int, pages Pag
pages[len(dates)-1-i].linkTitle = pages[i].title + "l"
pages[len(dates)-1-i].PublishDate = dates[i]
pages[len(dates)-1-i].ExpiryDate = dates[i]
- pages[len(dates)-1-i].content = template.HTML(titles[i] + "_content")
+ pages[len(dates)-1-i].contentv = template.HTML(titles[i] + "_content")
}
lastLastMod := pages[2].Lastmod
pages[2].Lastmod = pages[1].Lastmod
diff --git a/hugolib/page_bundler_handlers.go b/hugolib/page_bundler_handlers.go
index c22b719d1..eca324294 100644
--- a/hugolib/page_bundler_handlers.go
+++ b/hugolib/page_bundler_handlers.go
@@ -286,6 +286,10 @@ func (c *contentHandlers) handlePageContent() contentHandler {
p.workContent = p.replaceDivider(p.workContent)
p.workContent = p.renderContent(p.workContent)
+ tmpContent, tmpTableOfContents := helpers.ExtractTOC(p.workContent)
+ p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents)
+ p.workContent = tmpContent
+
if !ctx.doNotAddToSiteCollections {
ctx.pages <- p
}
diff --git a/hugolib/page_bundler_test.go b/hugolib/page_bundler_test.go
index d6aac1774..a41069d52 100644
--- a/hugolib/page_bundler_test.go
+++ b/hugolib/page_bundler_test.go
@@ -87,7 +87,7 @@ func TestPageBundlerSiteRegular(t *testing.T) {
assert.Equal(singlePage, s.getPage("page", "a/1"))
assert.Equal(singlePage, s.getPage("page", "1"))
- assert.Contains(singlePage.content, "TheContent")
+ assert.Contains(singlePage.content(), "TheContent")
if ugly {
assert.Equal("/a/1.html", singlePage.RelPermalink())
@@ -129,9 +129,12 @@ func TestPageBundlerSiteRegular(t *testing.T) {
firstPage := pageResources[0].(*Page)
secondPage := pageResources[1].(*Page)
assert.Equal(filepath.FromSlash("b/my-bundle/1.md"), firstPage.pathOrTitle(), secondPage.pathOrTitle())
- assert.Contains(firstPage.content, "TheContent")
+ assert.Contains(firstPage.content(), "TheContent")
assert.Equal(6, len(leafBundle1.Resources))
+ // Verify shortcode in bundled page
+ assert.Contains(secondPage.content(), filepath.FromSlash("MyShort in b/my-bundle/2.md"))
+
// https://github.com/gohugoio/hugo/issues/4582
assert.Equal(leafBundle1, firstPage.Parent())
assert.Equal(leafBundle1, secondPage.Parent())
@@ -395,7 +398,7 @@ HEADLESS {{< myShort >}}
assert.Equal("Headless Bundle in Topless Bar", headless.Title())
assert.Equal("", headless.RelPermalink())
assert.Equal("", headless.Permalink())
- assert.Contains(headless.content, "HEADLESS SHORTCODE")
+ assert.Contains(headless.content(), "HEADLESS SHORTCODE")
headlessResources := headless.Resources
assert.Equal(3, len(headlessResources))
@@ -404,7 +407,7 @@ HEADLESS {{< myShort >}}
assert.NotNil(pageResource)
assert.IsType(&Page{}, pageResource)
p := pageResource.(*Page)
- assert.Contains(p.content, "SHORTCODE")
+ assert.Contains(p.content(), "SHORTCODE")
assert.Equal("p1.md", p.Name())
th := testHelper{s.Cfg, s.Fs, t}
@@ -441,6 +444,17 @@ date: 2017-10-09
TheContent.
`
+ pageContentShortcode := `---
+title: "Bundle Galore"
+slug: pageslug
+date: 2017-10-09
+---
+
+TheContent.
+
+{{< myShort >}}
+`
+
pageWithImageShortcodeAndResourceMetadataContent := `---
title: "Bundle Galore"
slug: pageslug
@@ -487,6 +501,7 @@ Thumb RelPermalink: {{ $thumb.RelPermalink }}
`
myShort := `
+MyShort in {{ .Page.Path }}:
{{ $sunset := .Page.Resources.GetByPrefix "my-sunset-2" }}
{{ with $sunset }}
Short Sunset RelPermalink: {{ .RelPermalink }}
@@ -520,7 +535,7 @@ Short Thumb Width: {{ $thumb.Width }}
// Bundle
writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "1.md"), pageContent)
- writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "2.md"), pageContent)
+ writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "2.md"), pageContentShortcode)
writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "custom-mime.bep"), "bepsays")
writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "c", "logo.png"), "content")
diff --git a/hugolib/page_test.go b/hugolib/page_test.go
index f47f4944f..bbec1ea42 100644
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -481,7 +481,7 @@ func checkPageTitle(t *testing.T, page *Page, title string) {