diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-04-19 18:06:40 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2018-04-21 22:02:56 +0200 |
commit | 4d26ab33dcef704086f43828d1dfb4b8beae2593 (patch) | |
tree | 986c2a878326238ad62dc13c76ed311fef1a855e | |
parent | d6a2024e6b675ca30629bbbc7e8a6592defc942c (diff) |
Make .Content (almost) always available in shortcodes
This resolves some surprising behaviour when reading other pages' content from shortcodes. Before this commit, that behaviour was undefined. Note that this has never been an issue from regular templates.
It will still not be possible to get **the current shortcode's page's rendered content**. That would have impressed Einstein.
The new and well defined rules are:
* `.Page.Content` from a shortcode will be empty. The related `.Page.Truncated` `.Page.Summary`, `.Page.WordCount`, `.Page.ReadingTime`, `.Page.Plain` and `.Page.PlainWords` will also have empty values.
* For _other pages_ (retrieved via `.Page.Site.GetPage`, `.Site.Pages` etc.) the `.Content` is there to use as you please as long as you don't have infinite content recursion in your shortcode/content setup. See below.
* `.Page.TableOfContents` is good to go (but does not support shortcodes in headlines; this is unchanged)
If you get into a situation of infinite recursion, the `.Content` will be empty. Run `hugo -v` for more information.
Fixes #4632
Fixes #4653
Fixes #4655
-rw-r--r-- | deps/deps.go | 10 | ||||
-rw-r--r-- | helpers/content.go | 3 | ||||
-rw-r--r-- | hugolib/config.go | 1 | ||||
-rw-r--r-- | hugolib/embedded_shortcodes_test.go | 2 | ||||
-rw-r--r-- | hugolib/hugo_sites.go | 37 | ||||
-rw-r--r-- | hugolib/hugo_sites_build.go | 1 | ||||
-rw-r--r-- | hugolib/hugo_sites_build_test.go | 33 | ||||
-rw-r--r-- | hugolib/page.go | 243 | ||||
-rw-r--r-- | hugolib/pageSort.go | 2 | ||||
-rw-r--r-- | hugolib/pageSort_test.go | 4 | ||||
-rw-r--r-- | hugolib/page_bundler_handlers.go | 4 | ||||
-rw-r--r-- | hugolib/page_bundler_test.go | 25 | ||||
-rw-r--r-- | hugolib/page_test.go | 33 | ||||
-rw-r--r-- | hugolib/page_without_content.go | 67 | ||||
-rw-r--r-- | hugolib/shortcode.go | 44 | ||||
-rw-r--r-- | hugolib/shortcode_test.go | 14 | ||||
-rw-r--r-- | hugolib/site.go | 17 | ||||
-rw-r--r-- | hugolib/site_render.go | 5 |
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), "«"), string(doc1fr.content)) - require.False(t, strings.Contains(string(doc1en.content), "«"), string(doc1en.content)) - require.True(t, strings.Contains(string(doc1en.content), "“"), string(doc1en.content)) + require.True(t, strings.Contains(string(doc1fr.content()), "«"), string(doc1fr.content())) + require.False(t, strings.Contains(string(doc1en.content()), "«"), string(doc1en.content())) + require.True(t, strings.Contains(string(doc1en.content()), "“"), 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 }}: {{ $s |