diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-01-02 12:33:26 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2019-03-23 18:51:22 +0100 |
commit | 597e418cb02883418f2cebb41400e8e61413f651 (patch) | |
tree | 177ad9c540b2583b6dab138c9f0490d28989c7f7 /hugolib/page.go | |
parent | 44f5c1c14cb1f42cc5f01739c289e9cfc83602af (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.go | 2360 |
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 |