package site
import (
"html/template"
"strings"
"time"
)
// Page kinds.
const (
KindPage = "page" // a Markdown file
KindList = "list" // a collection's index: content//_index.md, or generated
KindTerm = "term" // one taxonomy value: /tags/linux/
KindTerms = "terms" // a taxonomy's index: /tags/
KindError = "404" // content/404.md
)
// Page is anything that becomes one HTML file.
type Page struct {
Kind string
Source string // path under content/, slash-separated; empty for generated pages
Title string
Summary string
Date time.Time
Updated time.Time
Draft bool
NoIndex bool
Layout string
Path string // URL path: "/", "/about/", "/articles/x/", "/404.html"
Permalink string // absolute URL
Image string // social card, as written in front matter
ImageAlt string // what the social card shows, for people who can't see it
Weight int
Content template.HTML
Params map[string]any // every front matter key, as written
Sections []Section
Collection string // first folder under content/, if any
Terms map[string][]string // taxonomy -> values on this page
Pages []*Page // for list, term and terms pages: what they list
Term string // for a term page: the value; for a terms page: the taxonomy
Taxonomy string // for term and terms pages
Prev, Next *Page // neighbours within the collection, in its order
WordCount int
Raw string // the Markdown body as written
RedirectTo string // front matter redirect_to: listed like any page, built as a redirect
PublishAt time.Time // front matter publish_at: left out of live builds until then
LeadImage *LeadImage // the picture the body opens with, if it opens with one
Paginator *Paginator // for list and taxonomy pages
Event *Event // set when the front matter has start: (event.go)
Site *Site
eventStart, eventEnd string // start: and end: as written, read in the site's time zone
}
// LeadImage is an image at the very top of a page's body, which templates can
// use as the page's social card.
type LeadImage struct{ Src, Alt string }
// Paginator is one page of a paginated list.
type Paginator struct {
Number int // 1-based
TotalPages int // at least 1
TotalItems int // across every page
Items []*Page // the items on this page
First *Page // page 1
Prev, Next *Page // neighbouring pages, nil at either end
All []*Page // every page of the list, in order
}
// PageLink is one entry in a row of numbered page links.
type PageLink struct {
Number int
Path string
Current bool
Gap bool // stands for the pages left out: draw "…"
}
// Links is a row of numbered links: the first and last pages, and those
// within around of the current one, with gaps for the rest.
//
// {{ range .Paginator.Links 2 }}…{{ end }}
func (pg *Paginator) Links(around int) []PageLink {
var out []PageLink
for i, p := range pg.All {
n := i + 1
if n == 1 || n == pg.TotalPages || (n >= pg.Number-around && n <= pg.Number+around) {
out = append(out, PageLink{Number: n, Path: p.Path, Current: n == pg.Number})
} else if len(out) > 0 && !out[len(out)-1].Gap {
out = append(out, PageLink{Gap: true})
}
}
return out
}
// Section is one block of a page built from sections.
type Section struct {
Type string
Data map[string]any
}
// Href is where a link to the page should go: its own address, or, for a
// page that redirects (a link post, a page that moved), its destination.
func (p *Page) Href() string {
if p.RedirectTo != "" {
return p.RedirectTo
}
return p.Path
}
// IsHome reports whether this is the front page.
func (p *Page) IsHome() bool { return p.Path == "/" }
// ReadingTime is minutes at 220 words a minute, rounded, at least one.
func (p *Page) ReadingTime() int {
m := (p.WordCount + 110) / 220
if m < 1 {
return 1
}
return m
}
// Lastmod is the date a sitemap should give: updated, else date.
func (p *Page) Lastmod() time.Time {
if !p.Updated.IsZero() {
return p.Updated
}
return p.Date
}
// Param returns one front matter value, or nil.
func (p *Page) Param(key string) any { return p.Params[key] }
// InSection reports whether the page lives under the given URL path, for
// marking the current item in a menu: /articles/ is active on /articles/x/.
func (p *Page) InSection(path string) bool {
if path == "/" {
return p.Path == "/"
}
return strings.HasPrefix(p.Path, path)
}