diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-04-09 10:33:04 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-06-08 11:21:34 +0200 |
commit | b39689393ccb8434d9a57658a64b77568c718e99 (patch) | |
tree | 944d679ac8e908444eb00d0d405ec3c131f31bab /hugolib/site_sections.go | |
parent | bef5048580b38b0c29edef4eb8c67915033120e9 (diff) |
hugolib: Enable nested sections
Fixes #465
Diffstat (limited to 'hugolib/site_sections.go')
-rw-r--r-- | hugolib/site_sections.go | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/hugolib/site_sections.go b/hugolib/site_sections.go new file mode 100644 index 000000000..151baac84 --- /dev/null +++ b/hugolib/site_sections.go @@ -0,0 +1,295 @@ +// 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" + + radix "github.com/hashicorp/go-immutable-radix" + "github.com/spf13/hugo/helpers" +) + +// Deprecated: Use .Site.Home.Sections. +// TODO(bep) Hugo 0.23 = Reuse as an alias for home's sections. +func (s *SiteInfo) Sections() Taxonomy { + + helpText := `In Hugo 0.22 we introduced nested sections, so this method now returns an empty taxonomy. + +To list sections with its pages, you can do something like this: + +{{ range $.Site.Home.Sections }} + Section: {{ .Title }} + {{ range .Pages }} + Section Page: {{ .Title }} + {{ end }} +{{ end }} + +To get a specific section, you can do this: + +{{ $section := $.Site.GetPage "section" "blog" }} +` + + helpers.Deprecated("Site", "Sections", helpText, true) + + return Taxonomy{} +} + +// 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 +} + +// current returns the page's current section. +// Note that this will return nil for pages that is not regular, home or section pages. +// Note that for paginated sections and home pages, this will return the original page pointer. +func (p *Page) current() *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 + } + + if po, ok := other.(*PageOutput); ok { + other = po.Page + } + + pp, ok := other.(*Page) + if !ok { + return false, fmt.Errorf("%T not supported in InSection", other) + } + + if pp == nil { + return false, nil + } + + return pp.current() == p.current(), 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 + ) + + for i, p := range s.Pages { + if p.Kind != KindPage { + if p.Kind == KindHome { + home = p + } + 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) + } else if !found { + // We don't know what to do with this section yet. + undecided = append(undecided, p) + } + + pagePath := path.Join(sectionKey, sectPageKey, strconv.Itoa(i)) + inPages.Insert([]byte(pagePath), 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) + } + } + } + + // Create any missing root sections. + for _, p := range undecided { + sectionKey := p.sections[0] + sect, found := sectionPages[sectionKey] + if !found { + sect = s.newSectionPage(sectionKey) + 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 + rootPages = inPages.Commit().Root() + rootSections = inSections.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 +} |