summaryrefslogtreecommitdiffstats
path: root/hugolib/page.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-01-02 12:33:26 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2019-03-23 18:51:22 +0100
commit597e418cb02883418f2cebb41400e8e61413f651 (patch)
tree177ad9c540b2583b6dab138c9f0490d28989c7f7 /hugolib/page.go
parent44f5c1c14cb1f42cc5f01739c289e9cfc83602af (diff)
Make Page an interface
The main motivation of this commit is to add a `page.Page` interface to replace the very file-oriented `hugolib.Page` struct. This is all a preparation step for issue #5074, "pages from other data sources". But this also fixes a set of annoying limitations, especially related to custom output formats, and shortcodes. Most notable changes: * The inner content of shortcodes using the `{{%` as the outer-most delimiter will now be sent to the content renderer, e.g. Blackfriday. This means that any markdown will partake in the global ToC and footnote context etc. * The Custom Output formats are now "fully virtualized". This removes many of the current limitations. * The taxonomy list type now has a reference to the `Page` object. This improves the taxonomy template `.Title` situation and make common template constructs much simpler. See #5074 Fixes #5763 Fixes #5758 Fixes #5090 Fixes #5204 Fixes #4695 Fixes #5607 Fixes #5707 Fixes #5719 Fixes #3113 Fixes #5706 Fixes #5767 Fixes #5723 Fixes #5769 Fixes #5770 Fixes #5771 Fixes #5759 Fixes #5776 Fixes #5777 Fixes #5778
Diffstat (limited to 'hugolib/page.go')
-rw-r--r--hugolib/page.go2360
1 files changed, 553 insertions, 1807 deletions
diff --git a/hugolib/page.go b/hugolib/page.go
index 71070d1e8..24d659fb1 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
+// Copyright 2019 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.
@@ -15,2012 +15,836 @@ package hugolib
import (
"bytes"
- "context"
- "errors"
"fmt"
- "math/rand"
- "reflect"
-
- "github.com/gohugoio/hugo/common/hugo"
-
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/common/urls"
- "github.com/gohugoio/hugo/media"
-
- "github.com/gohugoio/hugo/langs"
-
- "github.com/gohugoio/hugo/related"
-
- "github.com/bep/gitmap"
-
- "github.com/gohugoio/hugo/helpers"
- "github.com/gohugoio/hugo/hugolib/pagemeta"
- "github.com/gohugoio/hugo/resources/resource"
-
- "github.com/gohugoio/hugo/output"
- "github.com/mitchellh/mapstructure"
-
"html/template"
- "io"
+ "os"
"path"
"path/filepath"
- "regexp"
"runtime"
+ "sort"
"strings"
- "sync"
- "time"
- "unicode/utf8"
- "github.com/gohugoio/hugo/compare"
- "github.com/gohugoio/hugo/source"
+ "github.com/bep/gitmap"
"github.com/spf13/cast"
-)
-var (
- cjk = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`)
+ "github.com/gohugoio/hugo/helpers"
- // This is all the kinds we can expect to find in .Site.Pages.
- allKindsInPages = []string{KindPage, KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm}
+ "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/parser/metadecoders"
- allKinds = append(allKindsInPages, []string{kindRSS, kindSitemap, kindRobotsTXT, kind404}...)
+ "github.com/gohugoio/hugo/parser/pageparser"
+ "github.com/pkg/errors"
- // Assert that it implements the Eqer interface.
- _ compare.Eqer = (*Page)(nil)
- _ compare.Eqer = (*PageOutput)(nil)
+ "github.com/gohugoio/hugo/output"
- // Assert that it implements the interface needed for related searches.
- _ related.Document = (*Page)(nil)
+ "github.com/gohugoio/hugo/media"
+ "github.com/gohugoio/hugo/source"
- // Page supports ref and relref
- _ urls.RefLinker = (*Page)(nil)
+ "github.com/gohugoio/hugo/common/collections"
+ "github.com/gohugoio/hugo/common/text"
+ "github.com/gohugoio/hugo/resources"
+ "github.com/gohugoio/hugo/resources/page"
+ "github.com/gohugoio/hugo/resources/resource"
)
-// Wraps a Page.
-type pageContainer interface {
- page() *Page
-}
-
-const (
- KindPage = "page"
-
- // The rest are node types; home page, sections etc.
-
- KindHome = "home"
- KindSection = "section"
- KindTaxonomy = "taxonomy"
- KindTaxonomyTerm = "taxonomyTerm"
-
- // Temporary state.
- kindUnknown = "unknown"
-
- // The following are (currently) temporary nodes,
- // i.e. nodes we create just to render in isolation.
- kindRSS = "RSS"
- kindSitemap = "sitemap"
- kindRobotsTXT = "robotsTXT"
- kind404 = "404"
-
- pageResourceType = "page"
+var (
+ _ page.Page = (*pageState)(nil)
+ _ collections.Grouper = (*pageState)(nil)
+ _ collections.Slicer = (*pageState)(nil)
)
-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
- // to to filter regular pages, find sections etc.
- // Kind will, for the pages available to the templates, be one of:
- // page, home, section, taxonomy and taxonomyTerm.
- // It is of string type to make it easy to reason about in
- // the templates.
- Kind string
-
- // Since Hugo 0.18 we got rid of the Node type. So now all pages are ...
- // pages (regular pages, home page, sections etc.).
- // Sections etc. will have child pages. These were earlier placed in .Data.Pages,
- // but can now be more intuitively also be fetched directly from .Pages.
- // This collection will be nil for regular pages.
- Pages Pages
-
- // Since Hugo 0.32, a Page can have resources such as images and CSS associated
- // with itself. The resource will typically be placed relative to the Page,
- // but templates should use the links (Permalink and RelPermalink)
- // provided by the Resource object.
- Resources resource.Resources
-
- // This is the raw front matter metadata that is going to be assigned to
- // the Resources above.
- resourcesMetadata []map[string]interface{}
-
- // translations will contain references to this page in other language
- // if available.
- translations Pages
-
- // A key that maps to translation(s) of this page. This value is fetched
- // from the page front matter.
- translationKey string
-
- // Params contains configuration defined in the params section of page frontmatter.
- params map[string]interface{}
-
- // Content sections
- contentv template.HTML
- summary template.HTML
- TableOfContents template.HTML
-
- // Passed to the shortcodes
- pageWithoutContent *PageWithoutContent
-
- Aliases []string
-
- Images []Image
- Videos []Video
-
- truncated bool
- Draft bool
- Status string
-
- // PageMeta contains page stats such as word count etc.
- PageMeta
-
- // Markup contains the markup type for the content.
- Markup string
-
- extension string
- contentType string
-
- Layout string
-
- // For npn-renderable pages (see IsRenderable), the content itself
- // is used as template and the template name is stored here.
- selfLayout string
-
- linkTitle string
-
- // Content items.
- pageContent
-
- // whether the content is in a CJK language.
- isCJKLanguage bool
-
- // the content stripped for HTML
- plain string // TODO should be []byte
- plainWords []string
-
- // rendering configuration
- renderingConfig *helpers.BlackFriday
-
- // menus
- pageMenus PageMenus
-
- source.File
-
- Position `json:"-"`
-
- GitInfo *gitmap.GitInfo
-
- // This was added as part of getting the Nodes (taxonomies etc.) to work as
- // Pages in Hugo 0.18.
- // It is deliberately named similar to Section, but not exported (for now).
- // We currently have only one level of section in Hugo, but the page can live
- // any number of levels down the file path.
- // To support taxonomies like /categories/hugo etc. we will need to keep track
- // of that information in a general way.
- // So, sections represents the path to the content, i.e. a content file or a
- // virtual content file in the situations where a taxonomy or a section etc.
- // isn't accomanied by one.
- sections []string
-
- // Will only be set for sections and regular pages.
- parent *Page
-
- // When we create paginator pages, we create a copy of the original,
- // but keep track of it here.
- origOnCopy *Page
-
- // Will only be set for section pages and the home page.
- subSections Pages
-
- s *Site
-
- // Pulled over from old Node. TODO(bep) reorg and group (embed)
-
- Site *SiteInfo `json:"-"`
-
- title string
- Description string
- Keywords []string
- data map[string]interface{}
-
- pagemeta.PageDates
-
- Sitemap Sitemap
- pagemeta.URLPath
- frontMatterURL string
-
- permalink string
- relPermalink string
-
- // relative target path without extension and any base path element
- // from the baseURL or the language code.
- // This is used to construct paths in the page resources.
- relTargetPathBase string
- // Is set to a forward slashed path if this is a Page resources living in a folder below its owner.
- resourcePath string
-
- // This is enabled if it is a leaf bundle (the "index.md" type) and it is marked as headless in front matter.
- // Being headless means that
- // 1. The page itself is not rendered to disk
- // 2. It is not available in .Site.Pages etc.
- // 3. But you can get it via .Site.GetPage
- headless bool
-
- layoutDescriptor output.LayoutDescriptor
-
- scratch *maps.Scratch
-
- // It would be tempting to use the language set on the Site, but in they way we do
- // multi-site processing, these values may differ during the initial page processing.
- language *langs.Language
-
- lang string
-
- // When in Fast Render Mode, we only render a sub set of the pages, i.e. the
- // pages the user is working on. There are, however, situations where we need to
- // signal other pages to be rendered.
- forceRender bool
-
- // The output formats this page will be rendered to.
- outputFormats output.Formats
-
- // This is the PageOutput that represents the first item in outputFormats.
- // Use with care, as there are potential for inifinite loops.
- mainPageOutput *PageOutput
-
- targetPathDescriptorPrototype *targetPathDescriptor
-}
-
-func stackTrace(length int) string {
- trace := make([]byte, length)
- runtime.Stack(trace, true)
- return string(trace)
-}
-
-func (p *Page) Data() interface{} {
- return p.data
-}
-
-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)
-
- p.contentInitMu.Lock()
- defer p.contentInitMu.Unlock()
-
- go func() {
- var err error
-
- err = p.prepareContent()
- if err != nil {
- c <- err
- return
- }
-
- select {
- case <-ctx.Done():
- return
- default:
- }
-
- if len(p.summary) == 0 {
- if err = p.setAutoSummary(); err != nil {
- err = p.errorf(err, "failed to set auto summary")
- }
- }
- c <- err
- }()
-
- select {
- case <-ctx.Done():
- p.s.Log.WARN.Printf("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=30000\" (or higher, value is in milliseconds) in config.toml.\n", p.pathOrTitle())
- case err := <-c:
- if err != nil {
- p.s.SendError(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
-}
-
-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.
-func (p *Page) Sites() SiteInfos {
- return p.s.owner.siteInfos()
-}
-
-// SearchKeywords implements the related.Document interface needed for fast page searches.
-func (p *Page) SearchKeywords(cfg related.IndexConfig) ([]related.Keyword, error) {
-
- v, err := p.Param(cfg.Name)
- if err != nil {
- return nil, err
- }
+var (
+ pageTypesProvider = resource.NewResourceTypesProvider(media.OctetType, pageResourceType)
+ nopPageOutput = &pageOutput{pagePerOutputProviders: nopPagePerOutput}
+)
- return cfg.ToKeywords(v)
+// pageContext provides contextual information about this page, for error
+// logging and similar.
+type pageContext interface {
+ posOffset(offset int) text.Position
+ wrapError(err error) error
+ getRenderingConfig() *helpers.BlackFriday
}
-// PubDate is when this page was or will be published.
-// NOTE: This is currently used for search only and is not meant to be used
-// directly in templates. We need to consolidate the dates in this struct.
-// TODO(bep) see https://github.com/gohugoio/hugo/issues/3854
-func (p *Page) PubDate() time.Time {
- if !p.PublishDate.IsZero() {
- return p.PublishDate
+// wrapErr adds some context to the given error if possible.
+func wrapErr(err error, ctx interface{}) error {
+ if pc, ok := ctx.(pageContext); ok {
+ return pc.wrapError(err)
}
- return p.Date
+ return err
}
-func (*Page) ResourceType() string {
- return pageResourceType
-}
-
-func (p *Page) RSSLink() template.URL {
- f, found := p.outputFormats.GetByName(output.RSSFormat.Name)
- if !found {
- return ""
- }
- return template.URL(newOutputFormat(p, f).Permalink())
+type pageSiteAdapter struct {
+ p page.Page
+ s *Site
}
-func (p *Page) createLayoutDescriptor() output.LayoutDescriptor {
- var section string
-
- switch p.Kind {
- case KindSection:
- // In Hugo 0.22 we introduce nested sections, but we still only
- // use the first level to pick the correct template. This may change in
- // the future.
- section = p.sections[0]
- case KindTaxonomy, KindTaxonomyTerm:
- section = p.s.taxonomiesPluralSingular[p.sections[0]]
- default:
- }
-
- return output.LayoutDescriptor{
- Kind: p.Kind,
- Type: p.Type(),
- Lang: p.Lang(),
- Layout: p.Layout,
- Section: section,
+func (pa pageSiteAdapter) GetPage(ref string) (page.Page, error) {
+ p, err := pa.s.getPageNew(pa.p, ref)
+ if p == nil {
+ // The nil struct has meaning in some situations, mostly to avoid breaking
+ // existing sites doing $nilpage.IsDescendant($p), which will always return
+ // false.
+ p = page.NilPage
}
+ return p, err
}
-// pageInit lazy initializes different parts of the page. It is extracted
-// into its own type so we can easily create a copy of a given page.
-type pageInit struct {
- languageInit sync.Once
- pageMenusInit sync.Once
- pageMetaInit sync.Once
- renderingConfigInit sync.Once
- withoutContentInit sync.Once
-}
+type pageState struct {
+ // This slice will be of same length as the number of global slice of output
+ // formats (for all sites).
+ pageOutputs []*pageOutput
-type pageContentInit struct {
- contentInitMu sync.Mutex
- contentInit sync.Once
- plainInit sync.Once
- plainWordsInit sync.Once
-}
+ // This will be shifted out when we start to render a new output format.
+ *pageOutput
-func (p *Page) resetContent() {
- p.pageContentInit = &pageContentInit{}
+ // Common for all output formats.
+ *pageCommon
}
-// IsNode returns whether this is an item of one of the list types in Hugo,
-// i.e. not a regular content page.
-func (p *Page) IsNode() bool {
- return p.Kind != KindPage
-}
+// Eq returns whether the current page equals the given page.
+// This is what's invoked when doing `{{ if eq $page $otherPage }}`
+func (p *pageState) Eq(other interface{}) bool {
+ pp, err := unwrapPage(other)
+ if err != nil {
+ return false
+ }
-// IsHome returns whether this is the home page.
-func (p *Page) IsHome() bool {
- return p.Kind == KindHome
+ return p == pp
}
-// IsSection returns whether this is a section page.
-func (p *Page) IsSection() bool {
- return p.Kind == KindSection
+func (p *pageState) GitInfo() *gitmap.GitInfo {
+ return p.gitInfo
}
-// IsPage returns whether this is a regular content page.
-func (p *Page) IsPage() bool {
- return p.Kind == KindPage
+func (p *pageState) MarshalJSON() ([]byte, error) {
+ return page.MarshalPageToJSON(p)
}
-// BundleType returns the bundle type: "leaf", "branch" or an empty string if it is none.
-// See https://gohugo.io/content-management/page-bundles/
-func (p *Page) BundleType() string {
- if p.IsNode() {
- return "branch"
- }
-
- var source interface{} = p.File
- if fi, ok := source.(*fileInfo); ok {
- switch fi.bundleTp {
- case bundleBranch:
- return "branch"
- case bundleLeaf:
- return "leaf"
+func (p *pageState) Pages() page.Pages {
+ p.pagesInit.Do(func() {
+ if p.pages != nil {
+ return
}
- }
-
- return ""
-}
-func (p *Page) MediaType() media.Type {
- return media.OctetType
-}
+ var pages page.Pages
-type PageMeta struct {
- wordCount int
- fuzzyWordCount int
- readingTime int
- Weight int
-}
-
-type Position struct {
- PrevPage *Page
- NextPage *Page
- PrevInSection *Page
- NextInSection *Page
-}
+ switch p.Kind() {
+ case page.KindPage:
+ case page.KindHome:
+ pages = p.s.RegularPages()
+ case page.KindTaxonomy:
+ termInfo := p.getTaxonomyNodeInfo()
+ taxonomy := p.s.Taxonomies[termInfo.plural].Get(termInfo.termKey)
+ pages = taxonomy.Pages()
+ case page.KindTaxonomyTerm:
+ plural := p.getTaxonomyNodeInfo().plural
+ // A list of all page.KindTaxonomy pages with matching plural
+ for _, p := range p.s.findPagesByKind(page.KindTaxonomy) {
+ if p.SectionsEntries()[0] == plural {
+ pages = append(pages, p)
+ }
+ }
+ case kind404, kindSitemap, kindRobotsTXT:
+ pages = p.s.Pages()
+ }
-type Pages []*Page
+ p.pages = pages
+ })
-func (ps Pages) String() string {
- return fmt.Sprintf("Pages(%d)", len(ps))
+ return p.pages
}
-// Used in tests.
-func (ps Pages) shuffle() {
- for i := range ps {
- j := rand.Intn(i + 1)
- ps[i], ps[j] = ps[j], ps[i]
+// RawContent returns the un-rendered source content without
+// any leading front matter.
+func (p *pageState) RawContent() string {
+ if p.source.parsed == nil {
+ return ""
}
-}
-
-func (ps Pages) findPagePosByFilename(filename string) int {
- for i, x := range ps {
- if x.Filename() == filename {
- return i
- }
+ start := p.source.posMainContent
+ if start == -1 {
+ start = 0
}
- return -1
+ return string(p.source.parsed.Input()[start:])
}
-func (ps Pages) removeFirstIfFound(p *Page) Pages {
- ii := -1
- for i, pp := range ps {
- if pp == p {
- ii = i
- break
- }
- }
+func (p *pageState) Resources() resource.Resources {
+ p.resourcesInit.Do(func() {
- if ii != -1 {
- ps = append(ps[:ii], ps[ii+1:]...)
- }
- return ps
-}
+ sort := func() {
+ sort.SliceStable(p.resources, func(i, j int) bool {
+ ri, rj := p.resources[i], p.resources[j]
+ if ri.ResourceType() < rj.ResourceType() {
+ return true
+ }
-func (ps Pages) findPagePosByFilnamePrefix(prefix string) int {
- if prefix == "" {
- return -1
- }
+ p1, ok1 := ri.(page.Page)
+ p2, ok2 := rj.(page.Page)
- lenDiff := -1
- currPos := -1
- prefixLen := len(prefix)
+ if ok1 != ok2 {
+ return ok2
+ }
- // Find the closest match
- for i, x := range ps {
- if strings.HasPrefix(x.Filename(), prefix) {
- diff := len(x.Filename()) - prefixLen
- if lenDiff == -1 || diff < lenDiff {
- lenDiff = diff
- currPos = i
- }
- }
- }
- return currPos
-}
+ if ok1 {
+ return page.DefaultPageSort(p1, p2)
+ }
-// findPagePos Given a page, it will find the position in Pages
-// will return -1 if not found
-func (ps Pages) findPagePos(page *Page) int {
- for i, x := range ps {
- if x.Filename() == page.Filename() {
- return i
+ return ri.RelPermalink() < rj.RelPermalink()
+ })
}
- }
- return -1
-}
-func (p *Page) Plain() string {
- p.initContent()
- p.initPlain(true)
- return p.plain
-}
+ sort()
-func (p *Page) initPlain(lock bool) {
- p.plainInit.Do(func() {
- if lock {
- p.contentInitMu.Lock()
- defer p.contentInitMu.Unlock()
+ if len(p.m.resourcesMetadata) > 0 {
+ resources.AssignMetadata(p.m.resourcesMetadata, p.resources...)
+ sort()
}
- p.plain = helpers.StripHTML(string(p.contentv))
- })
-}
-
-func (p *Page) PlainWords() []string {
- p.initContent()
- p.initPlainWords(true)
- return p.plainWords
-}
-func (p *Page) initPlainWords(lock bool) {
- p.plainWordsInit.Do(func() {
- if lock {
- p.contentInitMu.Lock()
- defer p.contentInitMu.Unlock()
- }
- p.plainWords = strings.Fields(p.plain)
})
+ return p.resources
}
-// Param is a convenience method to do lookups in Page's and Site's Params map,
-// in that order.
-//
-// This method is also implemented on Node and SiteInfo.
-func (p *Page) Param(key interface{}) (interface{}, error) {
- keyStr, err := cast.ToStringE(key)
- if err != nil {
- return nil, err
- }
-
- keyStr = strings.ToLower(keyStr)
- result, _ := p.traverseDirect(keyStr)
- if result != nil {
- return result, nil
- }
-
- keySegments := strings.Split(keyStr, ".")
- if len(keySegments) == 1 {
- return nil, nil
- }
-
- return p.traverseNested(keySegments)
-}
-
-func (p *Page) traverseDirect(key string) (interface{}, error) {
- keyStr := strings.ToLower(key)
- if val, ok := p.params[keyStr]; ok {
- return val, nil
+func (p *pageState) HasShortcode(name string) bool {
+ if p.shortcodeState == nil {
+ return false
}
- return p.Site.Params[keyStr], nil
+ return p.shortcodeState.nameSet[name]
}
-func (p *Page) traverseNested(keySegments []string) (interface{}, error) {
- result := traverse(keySegments, p.params)
- if result != nil {
- return result, nil
- }
-
- result = traverse(keySegments, p.Site.Params)
- if result != nil {
- return result, nil
- }
-
- // Didn't find anything, but also no problems.
- return nil, nil
+func (p *pageState) Site() page.Site {
+ return &p.s.Info
}
-func traverse(keys []string, m map[string]interface{}) interface{} {
- // Shift first element off.
- firstKey, rest := keys[0], keys[1:]
- result := m[firstKey]
-
- // No point in continuing here.
- if result == nil {
- return result
- }
-
- if len(rest) == 0 {
- // That was the last key.
- return result
+func (p *pageState) String() string {
+ if sourceRef := p.sourceRef(); sourceRef != "" {
+ return fmt.Sprintf("Page(%s)", sourceRef)
}
-
- // That was not the last key.
- return traverse(rest, cast.ToStringMap(result))
+ return fmt.Sprintf("Page(%q)", p.Title())
}
-func (p *Page) Author() Author {
- authors := p.Authors()
-
- for _, author := range authors {
- return author
- }
- return Author{}
+// IsTranslated returns whether this content file is translated to
+// other language(s).
+func (p *pageState) IsTranslated() bool {
+ p.s.h.init.translations.Do()
+ return len(p.translations) > 0
}
-func (p *Page) Authors() AuthorList {
- authorKeys, ok := p.params["authors"]
- if !ok {
- return AuthorList{}
- }
- authors := authorKeys.([]string)
- if len(authors) < 1 || len(p.Site.Authors) < 1 {
- return AuthorList{}
- }
-
- al := make(AuthorList)
- for _, author := range authors {
- a, ok := p.Site.Authors[author]
- if ok {
- al[author] = a
+// TranslationKey returns the key used to map language translations of this page.
+// It will use the translationKey set in front matter if set, or the content path and
+// filename (excluding any language code and extension), e.g. "about/index".
+// The Page Kind is always prepended.
+func (p *pageState) TranslationKey() string {
+ p.translationKeyInit.Do(func() {
+ if p.m.translationKey != "" {
+ p.translationKey = p.Kind() + "/" + p.m.translationKey
+ } else if p.IsPage() && p.File() != nil {
+ p.translationKey = path.Join(p.Kind(), filepath.ToSlash(p.File().Dir()), p.File().TranslationBaseName())
+ } else if p.IsNode() {
+ p.translationKey = path.Join(p.Kind(), p.SectionsPath())
}
- }
- return al
-}
-func (p *Page) UniqueID() string {
- return p.File.UniqueID()
-}
-
-// Returns the page as summary and main.
-func (p *Page) setUserDefinedSummary(rawContentCopy []byte) (*summaryContent, error) {
-
- sc, err := splitUserDefinedSummaryAndContent(p.Markup, rawContentCopy)
-
- if err != nil {
- return nil, err
- }
-
- if sc == nil {
- // No divider found
- return nil, nil
- }
+ })
- p.summary = helpers.BytesToHTML(sc.summary)
+ return p.translationKey
- return sc, nil
}
-// Make this explicit so there is no doubt about what is what.
-type summaryContent struct {
- summary []byte
- content []byte
+// AllTranslations returns all translations, including the current Page.
+func (p *pageState) AllTranslations() page.Pages {
+ p.s.h.init.translations.Do()
+ return p.allTranslations
}
-func splitUserDefinedSummaryAndContent(markup string, c []byte) (sc *summaryContent, err error) {
- defer func() {
- if r := recover(); r != nil {
- err = fmt.Errorf("summary split failed: %s", r)
- }
- }()
-
- startDivider := bytes.Index(c, internalSummaryDividerBaseBytes)
-
- if startDivider == -1 {
- return
- }
-
- startTag := "p"
- switch markup {
- case "asciidoc":
- startTag = "div"
-
- }
-
- // Walk back and forward to the surrounding tags.
- start := bytes.LastIndex(c[:startDivider], []byte("<"+startTag))
- end := bytes.Index(c[startDivider:], []byte("</"+startTag))
-
- if start == -1 {
- start = startDivider
- } else {
- start = startDivider - (startDivider - start)
- }
-
- if end == -1 {
- end = startDivider + len(internalSummaryDividerBase)
- } else {
- end = startDivider + end + len(startTag) + 3
- }
-
- var addDiv bool
-
- switch markup {
- case "rst":
- addDiv = true
- }
-
- withoutDivider := append(c[:start], bytes.Trim(c[end:], "\n")...)
-
- var summary []byte
-
- if len(withoutDivider) > 0 {
- summary = bytes.TrimSpace(withoutDivider[:start])
- }
-
- if addDiv {
- // For the rst
- summary = append(append([]byte(nil), summary...), []byte("</div>")...)
- }
-
- if err != nil {
- return
- }
+// Translations returns the translations excluding the current Page.
+func (p *pageState) Translations() page.Pages {
+ p.s.h.init.translations.Do()
+ return p.translations
+}
- sc = &summaryContent{
- summary: summary,
- content: bytes.TrimSpace(withoutDivider),
+func (p *pageState) getRenderingConfig() *helpers.BlackFriday {
+ if p.m.renderingConfig == nil {
+ return p.s.ContentSpec.BlackFriday
}
-
- return
+ return p.m.renderingConfig
}
-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 {
- p.initPlainWords(false)
- summary, truncated = p.s.ContentSpec.TruncateWordsByRune(p.plainWords)
- } else {
- summary, truncated = p.s.ContentSpec.TruncateWordsToWholeSentence(p.plain)
+func (ps *pageState) initCommonProviders(pp pagePaths) error {
+ if ps.IsPage() {
+ ps.posNextPrev = &nextPrev{init: ps.s.init.prevNext}
+ ps.posNextPrevSection = &nextPrev{init: ps.s.init.prevNextInSection}
+ ps.InSectionPositioner = newPagePositionInSection(ps.posNextPrevSection)
+ ps.Positioner = newPagePosition(ps.posNextPrev)
}
- p.summary = template.HTML(summary)
- p.truncated = truncated
- return nil
-
-}
+ ps.OutputFormatsProvider = pp
+ ps.targetPathDescriptor = pp.targetPathDescriptor
+ ps.RefProvider = newPageRef(ps)
+ ps.SitesProvider = &ps.s.Info
-func (p *Pag