Add pagination support for home page, sections and taxonomies

Two new configuration properties, `Paginate` (default `0`) and `PaginatePath` (default `page`) are added.

Setting `paginate` to a positive value will split the list pages for the home page, sections and taxonomies into chunks of size of the `paginate` property.

A `.Paginator` is provided to help building a pager menu.

There are two ways to configure a `.Paginator`:

1. The simplest way is just to call `.Paginator.Pages` from a template. It will contain the pages for "that page" (`.Data.Pages` will (like today) contain all the pages).
2. Select a sub-set of the pages with the available template functions and pass the slice to `.Paginate` : `{{ range (.Paginate (where .Data.Pages "Type" "post")).Pages }}`

**NOTE:** For a given Node, it's one of the options above. It's perfectly legitimate to iterate over the same pager more than once, but it's static and cannot change.

The `.Paginator` contains enough information to build a full-blown paginator interface.

The pages are built on the form (note: BLANK means no value, i.e. home page):

```
[SECTION/TAXONOMY/BLANK]/index.html
[SECTION/TAXONOMY/BLANK]/page/1/index.html => redirect to  [SECTION/TAXONOMY/BLANK]/index.html
[SECTION/TAXONOMY/BLANK]/page/2/index.html
....
```

Fixes #96
This commit is contained in:
bep
2014-12-27 14:11:19 +01:00
parent 407e80a9ab
commit 37445bc6aa
7 changed files with 554 additions and 59 deletions

View File

@@ -21,6 +21,7 @@ import (
"io"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
@@ -37,6 +38,7 @@ import (
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/nitro"
"github.com/spf13/viper"
"sync/atomic"
)
var _ = transform.AbsURL
@@ -86,27 +88,28 @@ type targetList struct {
}
type SiteInfo struct {
BaseUrl template.URL
Taxonomies TaxonomyList
Authors AuthorList
Social SiteSocial
Indexes *TaxonomyList // legacy, should be identical to Taxonomies
Sections Taxonomy
Pages *Pages
Files []*source.File
Recent *Pages // legacy, should be identical to Pages
Menus *Menus
Hugo *HugoInfo
Title string
Author map[string]interface{}
LanguageCode string
DisqusShortname string
Copyright string
LastChange time.Time
Permalinks PermalinkOverrides
Params map[string]interface{}
BuildDrafts bool
canonifyUrls bool
BaseUrl template.URL
Taxonomies TaxonomyList
Authors AuthorList
Social SiteSocial
Indexes *TaxonomyList // legacy, should be identical to Taxonomies
Sections Taxonomy
Pages *Pages
Files []*source.File
Recent *Pages // legacy, should be identical to Pages
Menus *Menus
Hugo *HugoInfo
Title string
Author map[string]interface{}
LanguageCode string
DisqusShortname string
Copyright string
LastChange time.Time
Permalinks PermalinkOverrides
Params map[string]interface{}
BuildDrafts bool
canonifyUrls bool
paginationPageCount uint64
}
// SiteSocial is a place to put social details on a site level. These are the
@@ -205,6 +208,10 @@ func (s *SiteInfo) RelRef(ref string, page *Page) (string, error) {
return s.refLink(ref, page, true)
}
func (s *SiteInfo) addToPaginationPageCount(cnt uint64) {
atomic.AddUint64(&s.paginationPageCount, cnt)
}
type runmode struct {
Watching bool
}
@@ -614,7 +621,7 @@ func (s *Site) getMenusFromConfig() Menus {
if strings.HasPrefix(menuEntry.Url, "/") {
// make it match the nodes
menuEntryUrl := menuEntry.Url
menuEntryUrl = s.prepUrl(menuEntryUrl)
menuEntryUrl = helpers.UrlizeAndPrep(menuEntryUrl)
if !s.Info.canonifyUrls {
menuEntryUrl = helpers.AddContextRoot(string(s.Info.BaseUrl), menuEntryUrl)
}
@@ -943,17 +950,63 @@ func (s *Site) newTaxonomyNode(t taxRenderInfo) (*Node, string) {
func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error, wg *sync.WaitGroup) {
defer wg.Done()
var n *Node
for t := range taxes {
n, base := s.newTaxonomyNode(t)
layouts := []string{"taxonomy/" + t.singular + ".html", "indexes/" + t.singular + ".html", "_default/taxonomy.html", "_default/list.html"}
b, err := s.renderPage("taxononomy "+t.singular, n, s.appendThemeTemplates(layouts)...)
var base string
layouts := s.appendThemeTemplates(
[]string{"taxonomy/" + t.singular + ".html", "indexes/" + t.singular + ".html", "_default/taxonomy.html", "_default/list.html"})
n, base = s.newTaxonomyNode(t)
b, err := s.renderPage("taxononomy "+t.singular, n, layouts...)
if err != nil {
results <- err
continue
} else {
err := s.WriteDestPage(base+".html", b)
if err != nil {
results <- err
}
err = s.WriteDestPage(base, b)
if err != nil {
results <- err
continue
}
if n.paginator != nil {
paginatePath := viper.GetString("paginatePath")
// write alias for page 1
s.WriteDestAlias(fmt.Sprintf("%s/%s/%d/index.html", base, paginatePath, 1), s.permalink(base))
pagers := n.paginator.Pagers()
for i, pager := range pagers {
if i == 0 {
// already created
continue
}
taxonomyPagerNode, _ := s.newTaxonomyNode(t)
taxonomyPagerNode.paginator = pager
if pager.TotalPages() > 0 {
taxonomyPagerNode.Date = pager.Pages()[0].Date
}
pageNumber := i + 1
htmlBase := fmt.Sprintf("/%s/%s/%d", base, paginatePath, pageNumber)
b, err := s.renderPage(fmt.Sprintf("taxononomy_%s_%d", t.singular, pageNumber), taxonomyPagerNode, layouts...)
if err != nil {
results <- err
continue
}
err = s.WriteDestPage(htmlBase, b)
if err != nil {
results <- err
continue
}
}
}
@@ -1005,34 +1058,76 @@ func (s *Site) RenderListsOfTaxonomyTerms() (err error) {
return
}
func (s *Site) newSectionListNode(section string, data WeightedPages) *Node {
n := s.NewNode()
if viper.GetBool("PluralizeListTitles") {
n.Title = strings.Title(inflect.Pluralize(section))
} else {
n.Title = strings.Title(section)
}
s.setUrls(n, section)
n.Date = data[0].Page.Date
n.Data["Pages"] = data.Pages()
return n
}
// Render a page for each section
func (s *Site) RenderSectionLists() error {
for section, data := range s.Sections {
n := s.NewNode()
if viper.GetBool("PluralizeListTitles") {
n.Title = strings.Title(inflect.Pluralize(section))
} else {
n.Title = strings.Title(section)
}
s.setUrls(n, section)
n.Date = data[0].Page.Date
n.Data["Pages"] = data.Pages()
layouts := []string{"section/" + section + ".html", "_default/section.html", "_default/list.html", "indexes/" + section + ".html", "_default/indexes.html"}
b, err := s.renderPage("section "+section, n, s.appendThemeTemplates(layouts)...)
layouts := s.appendThemeTemplates(
[]string{"section/" + section + ".html", "_default/section.html", "_default/list.html", "indexes/" + section + ".html", "_default/indexes.html"})
n := s.newSectionListNode(section, data)
b, err := s.renderPage(fmt.Sprintf("section%s_%d", section, 1), n, s.appendThemeTemplates(layouts)...)
if err != nil {
return err
}
if err := s.WriteDestPage(section, b); err != nil {
if err := s.WriteDestPage(fmt.Sprintf("/%s", section), b); err != nil {
return err
}
if n.paginator != nil {
paginatePath := viper.GetString("paginatePath")
// write alias for page 1
s.WriteDestAlias(filepath.FromSlash(fmt.Sprintf("/%s/%s/%d", section, paginatePath, 1)), s.permalink(section))
pagers := n.paginator.Pagers()
for i, pager := range pagers {
if i == 0 {
// already created
continue
}
sectionPagerNode := s.newSectionListNode(section, data)
sectionPagerNode.paginator = pager
if pager.TotalPages() > 0 {
sectionPagerNode.Date = pager.Pages()[0].Date
}
pageNumber := i + 1
htmlBase := fmt.Sprintf("/%s/%s/%d", section, paginatePath, pageNumber)
b, err := s.renderPage(fmt.Sprintf("section_%s_%d", section, pageNumber), sectionPagerNode, layouts...)
if err != nil {
return err
}
if err := s.WriteDestPage(filepath.FromSlash(htmlBase), b); err != nil {
return err
}
}
}
if !viper.GetBool("DisableRSS") && section != "" {
// XML Feed
n.Url = s.permalinkStr(section + "/index.xml")
n.Permalink = s.permalink(section)
rssLayouts := []string{"section/" + section + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
b, err = s.renderXML("section "+section+" rss", n, s.appendThemeTemplates(rssLayouts)...)
b, err := s.renderXML("section "+section+" rss", n, s.appendThemeTemplates(rssLayouts)...)
if err != nil {
return err
}
@@ -1054,8 +1149,10 @@ func (s *Site) newHomeNode() *Node {
func (s *Site) RenderHomePage() error {
n := s.newHomeNode()
layouts := []string{"index.html", "_default/list.html", "_default/single.html"}
b, err := s.renderPage("homepage", n, s.appendThemeTemplates(layouts)...)
layouts := s.appendThemeTemplates([]string{"index.html", "_default/list.html", "_default/single.html"})
b, err := s.renderPage("homepage", n, layouts...)
if err != nil {
return err
}
@@ -1063,6 +1160,39 @@ func (s *Site) RenderHomePage() error {
return err
}
if n.paginator != nil {
paginatePath := viper.GetString("paginatePath")
// write alias for page 1
s.WriteDestAlias(filepath.FromSlash(fmt.Sprintf("/%s/%d", paginatePath, 1)), s.permalink("/"))
pagers := n.paginator.Pagers()
for i, pager := range pagers {
if i == 0 {
// already created
continue
}
homePagerNode := s.newHomeNode()
homePagerNode.paginator = pager
if pager.TotalPages() > 0 {
homePagerNode.Date = pager.Pages()[0].Date
}
pageNumber := i + 1
htmlBase := fmt.Sprintf("/%s/%d", paginatePath, pageNumber)
b, err := s.renderPage(fmt.Sprintf("homepage_%d", pageNumber), homePagerNode, layouts...)
if err != nil {
return err
}
if err := s.WriteDestPage(filepath.FromSlash(htmlBase), b); err != nil {
return err
}
}
}
if !viper.GetBool("DisableRSS") {
// XML Feed
n.Url = s.permalinkStr("index.xml")
@@ -1163,7 +1293,9 @@ func (s *Site) Stats() {
jww.FEEDBACK.Println(s.draftStats())
jww.FEEDBACK.Println(s.futureStats())
jww.FEEDBACK.Printf("%d pages created \n", len(s.Pages))
if viper.GetInt("paginate") > 0 {
jww.FEEDBACK.Printf("%d paginator pages created \n", s.Info.paginationPageCount)
}
taxonomies := viper.GetStringMapString("Taxonomies")
for _, pl := range taxonomies {
@@ -1172,7 +1304,7 @@ func (s *Site) Stats() {
}
func (s *Site) setUrls(n *Node, in string) {
n.Url = s.prepUrl(in)
n.Url = helpers.UrlizeAndPrep(in)
n.Permalink = s.permalink(n.Url)
n.RSSLink = s.permalink(in + ".xml")
}
@@ -1182,19 +1314,7 @@ func (s *Site) permalink(plink string) template.HTML {
}
func (s *Site) permalinkStr(plink string) string {
return helpers.MakePermalink(string(viper.GetString("BaseUrl")), s.prepUrl(plink)).String()
}
func (s *Site) prepUrl(in string) string {
return s.PrettifyUrl(helpers.Urlize(in))
}
func (s *Site) PrettifyUrl(in string) string {
return helpers.UrlPrep(viper.GetBool("UglyUrls"), in)
}
func (s *Site) PrettifyPath(in string) string {
return helpers.PathPrep(viper.GetBool("UglyUrls"), in)
return helpers.MakePermalink(string(viper.GetString("BaseUrl")), helpers.UrlizeAndPrep(plink)).String()
}
func (s *Site) NewNode() *Node {