mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
00868081f6
Site.assembleSections logic assumes that the the home page would always be the first in the Site's list of pages. This is not in fact guaranteed to be true. When it is not, the method can fail to set the parent for some or all root-level pages. Fixes #4447
345 lines
8.5 KiB
Go
345 lines
8.5 KiB
Go
// Copyright 2017 The Hugo Authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package hugolib
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gohugoio/hugo/helpers"
|
|
|
|
radix "github.com/hashicorp/go-immutable-radix"
|
|
)
|
|
|
|
// Sections returns the top level sections.
|
|
func (s *SiteInfo) Sections() Pages {
|
|
home, err := s.Home()
|
|
if err == nil {
|
|
return home.Sections()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Home is a shortcut to the home page, equivalent to .Site.GetPage "home".
|
|
func (s *SiteInfo) Home() (*Page, error) {
|
|
return s.GetPage(KindHome)
|
|
}
|
|
|
|
// Parent returns a section's parent section or a page's section.
|
|
// To get a section's subsections, see Page's Sections method.
|
|
func (p *Page) Parent() *Page {
|
|
return p.parent
|
|
}
|
|
|
|
// CurrentSection returns the page's current section or the page itself if home or a section.
|
|
// Note that this will return nil for pages that is not regular, home or section pages.
|
|
func (p *Page) CurrentSection() *Page {
|
|
v := p
|
|
if v.origOnCopy != nil {
|
|
v = v.origOnCopy
|
|
}
|
|
if v.IsHome() || v.IsSection() {
|
|
return v
|
|
}
|
|
|
|
return v.parent
|
|
}
|
|
|
|
// InSection returns whether the given page is in the current section.
|
|
// Note that this will always return false for pages that are
|
|
// not either regular, home or section pages.
|
|
func (p *Page) InSection(other interface{}) (bool, error) {
|
|
if p == nil || other == nil {
|
|
return false, nil
|
|
}
|
|
|
|
pp, err := unwrapPage(other)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if pp == nil {
|
|
return false, nil
|
|
}
|
|
|
|
return pp.CurrentSection() == p.CurrentSection(), nil
|
|
}
|
|
|
|
// IsDescendant returns whether the current page is a descendant of the given page.
|
|
// Note that this method is not relevant for taxonomy lists and taxonomy terms pages.
|
|
func (p *Page) IsDescendant(other interface{}) (bool, error) {
|
|
pp, err := unwrapPage(other)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if pp.Kind == KindPage && len(p.sections) == len(pp.sections) {
|
|
// A regular page is never its section's descendant.
|
|
return false, nil
|
|
}
|
|
return helpers.HasStringsPrefix(p.sections, pp.sections), nil
|
|
}
|
|
|
|
// IsAncestor returns whether the current page is an ancestor of the given page.
|
|
// Note that this method is not relevant for taxonomy lists and taxonomy terms pages.
|
|
func (p *Page) IsAncestor(other interface{}) (bool, error) {
|
|
pp, err := unwrapPage(other)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if p.Kind == KindPage && len(p.sections) == len(pp.sections) {
|
|
// A regular page is never its section's ancestor.
|
|
return false, nil
|
|
}
|
|
|
|
return helpers.HasStringsPrefix(pp.sections, p.sections), nil
|
|
}
|
|
|
|
// Eq returns whether the current page equals the given page.
|
|
// Note that this is more accurate than doing `{{ if eq $page $otherPage }}`
|
|
// since a Page can be embedded in another type.
|
|
func (p *Page) Eq(other interface{}) bool {
|
|
pp, err := unwrapPage(other)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return p == pp
|
|
}
|
|
|
|
func unwrapPage(in interface{}) (*Page, error) {
|
|
if po, ok := in.(*PageOutput); ok {
|
|
in = po.Page
|
|
}
|
|
|
|
pp, ok := in.(*Page)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%T not supported", in)
|
|
}
|
|
return pp, nil
|
|
}
|
|
|
|
// Sections returns this section's subsections, if any.
|
|
// Note that for non-sections, this method will always return an empty list.
|
|
func (p *Page) Sections() Pages {
|
|
return p.subSections
|
|
}
|
|
|
|
func (s *Site) assembleSections() Pages {
|
|
var newPages Pages
|
|
|
|
if !s.isEnabled(KindSection) {
|
|
return newPages
|
|
}
|
|
|
|
// Maps section kind pages to their path, i.e. "my/section"
|
|
sectionPages := make(map[string]*Page)
|
|
|
|
// The sections with content files will already have been created.
|
|
for _, sect := range s.findPagesByKind(KindSection) {
|
|
sectionPages[path.Join(sect.sections...)] = sect
|
|
}
|
|
|
|
const (
|
|
sectKey = "__hs"
|
|
sectSectKey = "_a" + sectKey
|
|
sectPageKey = "_b" + sectKey
|
|
)
|
|
|
|
var (
|
|
home *Page
|
|
inPages = radix.New().Txn()
|
|
inSections = radix.New().Txn()
|
|
undecided Pages
|
|
)
|
|
|
|
homes := s.findPagesByKind(KindHome)
|
|
if len(homes) == 1 {
|
|
home = homes[0]
|
|
} else if len(homes) > 1 {
|
|
panic("Too many homes")
|
|
}
|
|
|
|
for i, p := range s.Pages {
|
|
if p.Kind != KindPage {
|
|
continue
|
|
}
|
|
|
|
if len(p.sections) == 0 {
|
|
// Root level pages. These will have the home page as their Parent.
|
|
p.parent = home
|
|
continue
|
|
}
|
|
|
|
sectionKey := path.Join(p.sections...)
|
|
sect, found := sectionPages[sectionKey]
|
|
|
|
if !found && len(p.sections) == 1 {
|
|
// We only create content-file-less sections for the root sections.
|
|
sect = s.newSectionPage(p.sections[0])
|
|
sectionPages[sectionKey] = sect
|
|
newPages = append(newPages, sect)
|
|
found = true
|
|
}
|
|
|
|
if len(p.sections) > 1 {
|
|
// Create the root section if not found.
|
|
_, rootFound := sectionPages[p.sections[0]]
|
|
if !rootFound {
|
|
sect = s.newSectionPage(p.sections[0])
|
|
sectionPages[p.sections[0]] = sect
|
|
newPages = append(newPages, sect)
|
|
}
|
|
}
|
|
|
|
if found {
|
|
pagePath := path.Join(sectionKey, sectPageKey, strconv.Itoa(i))
|
|
inPages.Insert([]byte(pagePath), p)
|
|
} else {
|
|
undecided = append(undecided, p)
|
|
}
|
|
}
|
|
|
|
// Create any missing sections in the tree.
|
|
// A sub-section needs a content file, but to create a navigational tree,
|
|
// given a content file in /content/a/b/c/_index.md, we cannot create just
|
|
// the c section.
|
|
for _, sect := range sectionPages {
|
|
for i := len(sect.sections); i > 0; i-- {
|
|
sectionPath := sect.sections[:i]
|
|
sectionKey := path.Join(sectionPath...)
|
|
sect, found := sectionPages[sectionKey]
|
|
if !found {
|
|
sect = s.newSectionPage(sectionPath[len(sectionPath)-1])
|
|
sect.sections = sectionPath
|
|
sectionPages[sectionKey] = sect
|
|
newPages = append(newPages, sect)
|
|
}
|
|
}
|
|
}
|
|
|
|
for k, sect := range sectionPages {
|
|
inPages.Insert([]byte(path.Join(k, sectSectKey)), sect)
|
|
inSections.Insert([]byte(k), sect)
|
|
}
|
|
|
|
var (
|
|
currentSection *Page
|
|
children Pages
|
|
rootSections = inSections.Commit().Root()
|
|
)
|
|
|
|
for i, p := range undecided {
|
|
// Now we can decide where to put this page into the tree.
|
|
sectionKey := path.Join(p.sections...)
|
|
_, v, _ := rootSections.LongestPrefix([]byte(sectionKey))
|
|
sect := v.(*Page)
|
|
pagePath := path.Join(path.Join(sect.sections...), sectSectKey, "u", strconv.Itoa(i))
|
|
inPages.Insert([]byte(pagePath), p)
|
|
}
|
|
|
|
var rootPages = inPages.Commit().Root()
|
|
|
|
rootPages.Walk(func(path []byte, v interface{}) bool {
|
|
p := v.(*Page)
|
|
|
|
if p.Kind == KindSection {
|
|
if currentSection != nil {
|
|
// A new section
|
|
currentSection.setPagePages(children)
|
|
}
|
|
|
|
currentSection = p
|
|
children = make(Pages, 0)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// Regular page
|
|
p.parent = currentSection
|
|
children = append(children, p)
|
|
return false
|
|
})
|
|
|
|
if currentSection != nil {
|
|
currentSection.setPagePages(children)
|
|
}
|
|
|
|
// Build the sections hierarchy
|
|
for _, sect := range sectionPages {
|
|
if len(sect.sections) == 1 {
|
|
sect.parent = home
|
|
} else {
|
|
parentSearchKey := path.Join(sect.sections[:len(sect.sections)-1]...)
|
|
_, v, _ := rootSections.LongestPrefix([]byte(parentSearchKey))
|
|
p := v.(*Page)
|
|
sect.parent = p
|
|
}
|
|
|
|
if sect.parent != nil {
|
|
sect.parent.subSections = append(sect.parent.subSections, sect)
|
|
}
|
|
}
|
|
|
|
var (
|
|
sectionsParamId = "mainSections"
|
|
sectionsParamIdLower = strings.ToLower(sectionsParamId)
|
|
mainSections interface{}
|
|
mainSectionsFound bool
|
|
maxSectionWeight int
|
|
)
|
|
|
|
mainSections, mainSectionsFound = s.Info.Params[sectionsParamIdLower]
|
|
|
|
for _, sect := range sectionPages {
|
|
if sect.parent != nil {
|
|
sect.parent.subSections.Sort()
|
|
}
|
|
|
|
for i, p := range sect.Pages {
|
|
if i > 0 {
|
|
p.NextInSection = sect.Pages[i-1]
|
|
}
|
|
if i < len(sect.Pages)-1 {
|
|
p.PrevInSection = sect.Pages[i+1]
|
|
}
|
|
}
|
|
|
|
if !mainSectionsFound {
|
|
weight := len(sect.Pages) + (len(sect.Sections()) * 5)
|
|
if weight >= maxSectionWeight {
|
|
mainSections = []string{sect.Section()}
|
|
maxSectionWeight = weight
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to make this as backwards compatible as possible.
|
|
s.Info.Params[sectionsParamId] = mainSections
|
|
s.Info.Params[sectionsParamIdLower] = mainSections
|
|
|
|
return newPages
|
|
|
|
}
|
|
|
|
func (p *Page) setPagePages(pages Pages) {
|
|
pages.Sort()
|
|
p.Pages = pages
|
|
p.Data = make(map[string]interface{})
|
|
p.Data["Pages"] = pages
|
|
}
|