pages

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package pages owns the public page tree. Every page is revisioned content with a parent and a translated slug; its public path follows from the published tree. A module page (for example "Blog") hands its whole subtree to a module instance.

Home (the root page)      /
├── About                 /about
│   └── Team              /about/team
└── Blog [module: blog]   /blog, /blog/<anything below is the module's>

The published routes are read on every public request, so the Service keeps them in memory with the page revisions they point to (revisions never change). Publishing in this process replaces them at once; changes by other processes show up within the cache's time to live (see SetCacheTTL).

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is content.ErrNotFound: a page that does not exist, is
	// in the trash, or is not published where that is asked.
	ErrNotFound = content.ErrNotFound
	// ErrInvalid is a page the tree cannot take as it is; ErrSlug,
	// ErrParent and ErrHome say what is wrong.
	ErrInvalid = errors.New("invalid_page")
	// ErrCycle refuses to place a page below itself or below one of its own
	// subpages.
	ErrCycle = errors.New("page_cycle")
	// ErrHasChildren refuses to delete a page that still has subpages: they
	// would lose their place in the tree.
	ErrHasChildren = errors.New("page_has_children")
	// ErrModuleParent refuses subpages below a module page, whose module
	// owns every address below it.
	ErrModuleParent = errors.New("module_page_cannot_have_children")
	// ErrParentDeleted keeps a page in the trash while its parent page is
	// in the trash too, or gone.
	ErrParentDeleted = errors.New("page_parent_deleted")
	// ErrDuplicate is a published slug that a sibling page has in the same
	// language, or a top-level slug the site uses itself, such as the
	// admin's.
	ErrDuplicate = errors.New("duplicate_page_slug")
	// ErrMounted refuses to publish a module page for a module that another
	// published page shows already: each module instance has one place.
	ErrMounted = errors.New("module_already_mounted")
	// ErrUnknownMount is a module page for a module the site does not use.
	ErrUnknownMount = errors.New("unknown_module")
	// ErrSlug refuses a slug that is not lowercase letters, digits and
	// dashes, or a missing slug when none can be made from the title.
	ErrSlug = fmt.Errorf("%w: slug", ErrInvalid)
	// ErrParent refuses a page other than Home without a parent page.
	ErrParent = fmt.Errorf("%w: parent", ErrInvalid)
	// ErrHome refuses to take the home page off the site: it cannot be
	// unpublished, scheduled to be unpublished or deleted.
	ErrHome = fmt.Errorf("%w: home", ErrInvalid)
)

Functions

func Links(routes *router.Router, base string) router.PathFunc

Links gives the addresses of a module's routes on the page it is placed on, for links built outside its own handlers, such as in a block on another page or a search index. base is the page's MountURL: the paths are those of routes.At(base), and NoLinks while base is "".

base, err := s.pages.MountURL(ctx, s.key, locale)
...
paths := pages.Links(s.public, base)
url, _ := paths("post", "slug", "hello") // "/en/news/hello"
func NoLinks(string, ...any) (string, error)

NoLinks is a router.PathFunc that gives every route the empty address, for an instance on no page (MountURL is ""): its items are still listed, without links to pages that do not exist.

func Schema

func Schema() fs.FS

Schema holds this package's schema files per dialect (see db.ApplySchema).

Types

type Draft

type Draft = content.Draft[Fields, Text]

Draft is a page as an editor saves it.

type Fields

type Fields struct {
	ParentID string `db:"parent_id,null"`
	// Position orders the page among its siblings, lower first; siblings
	// with equal positions are ordered by ID, which follows the order they
	// were created in. It belongs to the page, not to a revision: Move
	// changes it at once, also in the published menu. Save places a new
	// page last among its siblings, and a page that changes parent last
	// among its new siblings, unless Position is set to another value than
	// the page has. A published page whose new parent is not published yet
	// keeps its place in the published menu until it is; then it moves to
	// its new place.
	Position int `db:"-"`
	// Module is the instance key of the mounted module, empty for content pages.
	Module string `db:"module"`
}

Fields place a page in the tree.

type Node

type Node struct {
	Page Page
	// Depth is 0 for Home, 1 for its children and so on.
	Depth int
	// Path is the page's path in the requested language from the latest
	// slugs, such as "/about/team"; empty when the page or a page above it
	// has no slug in that language.
	Path string
	// First and Last tell whether the page can move up or down among its
	// siblings.
	First bool
	Last  bool
}

Node is a page in the latest tree.

type Page

type Page = content.Item[Fields, Text]

Page is a page with its translations, as content stores keep items.

type Resolution

type Resolution struct {
	Page  Page
	Route Route
	// Rest is the path below a module page, "/" for the page itself.
	Rest string
}

Resolution is the published page behind a request path.

type Revision

type Revision = content.Revision

Revision describes one saved version of a page.

type Route

type Route struct {
	Locale     string `db:"locale"`
	Path       string `db:"path"`
	PageID     string `db:"page_id"`
	RevisionID string `db:"revision_id"`
	ParentID   string `db:"parent_id"`
	Module     string `db:"module"`
	Position   int    `db:"position"`
	Depth      int    `db:"depth"`
	Title      string `db:"title"`
}

Route is one published path. Module pages own every path below theirs.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service keeps the page tree: it saves, publishes and moves pages, and finds the published page behind a request path. It is safe for concurrent use.

func New

func New(database *db.DB, provider locales.Provider, modules func() []string, reserved ...string) (*Service, error)

New creates the service. modules lists the instance keys that may be mounted; reserved lists top-level slugs the application uses itself.

func (*Service) Alternates

func (s *Service) Alternates(ctx context.Context, pageID string) (map[string]string, error)

Alternates maps each locale to the published path of the same page.

func (*Service) Content

func (s *Service) Content() *content.Revisions[Fields, Text]

Content returns the revision store behind the pages, for code that needs its rules, such as which translations are complete (Spec). Changes to the tree go through the Service, which keeps the tree valid.

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, id string) error

Delete moves a page without children to the trash; it leaves the menu and its address at once. Home cannot be deleted.

func (*Service) Generation

func (s *Service) Generation() uint64

Generation counts the changes to the published routes in this process: publishing, unpublishing or moving a page. It only goes up, so a cache of what is made from published pages, such as the app's output cache, keeps it with what it made and makes it again once it moved on. Changes by other processes show when the cache's time to live (see SetCacheTTL) has passed, without changing it.

func (*Service) History

func (s *Service) History(ctx context.Context, id string) ([]Revision, error)

History lists the revisions of a page, newest first.

func (*Service) Home

func (s *Service) Home(ctx context.Context) (string, error)

Home returns the ID of the root page, which is served at "/".

func (*Service) Init

func (s *Service) Init(ctx context.Context, titles map[string]string) error

Init creates and publishes the Home page when it does not exist yet. titles gives the Home title per locale; "Home" is used otherwise.

func (*Service) Latest

func (s *Service) Latest(ctx context.Context, id string) (Page, error)

Latest returns the newest revision of a page, published or not, as the admin edits it. It returns ErrNotFound for a page that does not exist or is in the trash.

func (*Service) ListLatest

func (s *Service) ListLatest(ctx context.Context) ([]Page, error)

ListLatest returns the latest revision of every page, newest page first; Tree returns them in tree order.

func (*Service) MountPath

func (s *Service) MountPath(ctx context.Context, module, locale string) (string, error)

MountPath is the published path of a module instance in locale.

func (*Service) MountURL

func (s *Service) MountURL(ctx context.Context, module, locale string) (string, error)

MountURL is the address of the page a module instance is placed on in locale, with the language prefix, such as "/en/news". Home never holds a module (Save clears it), so the address always has a path after the prefix. It is "" without an error while the instance is on no published page in locale, so modules can leave their links empty. A module gives the addresses of its routes below it with Links.

func (*Service) Move

func (s *Service) Move(ctx context.Context, id string, offset int) error

Move moves a page offset places among its siblings in the latest tree: -1 is one place up, 1 one place down; moves past the first or last place stop there. The order is not revisioned, so the published menu follows at once. Home has no siblings and does not move.

func (*Service) MoveTo

func (s *Service) MoveTo(ctx context.Context, id, parentID string, index int, author string) (bool, error)

MoveTo places a page at index among the children of parentID in the latest tree, 0 being the first place; an index past the last place puts it last. author is the user who moves the page.

Below the page's current parent this is a reorder like Move: positions are not revisioned, so the published menu follows at once. Below another parent the page's address changes, so MoveTo saves a new draft revision with that parent, as choosing another parent in the editor does: the published menu keeps the page at its old place until the draft is published. MoveTo reports whether it saved a draft.

The same rules as for Save apply: a page cannot go below itself or the pages below it (ErrCycle), nor below a module page (ErrModuleParent); every page except Home needs a parent (ErrParent), and Home stays the root (ErrCycle, as every page is below it).

func (*Service) Planned

func (s *Service) Planned(ctx context.Context) ([]Page, error)

Planned returns the latest revision of the pages with a planned publication change.

func (*Service) Preview

func (s *Service) Preview(ctx context.Context, locale, path, revisionID string) (Page, error)

Preview returns the page revision an editor previews at path in locale: a revision of the page published at path, or of a page whose revision would be at path (see PreviewPath), such as a page that was never published or whose slug changes. A revision of any other page is ErrNotFound, so a preview never shows one page's text at the address of another; so is a revision without text in locale.

func (*Service) PreviewPath

func (s *Service) PreviewPath(ctx context.Context, page Page, locale string) (string, error)

PreviewPath is the path, without language prefix, at which a page revision shows in locale: below its parent's published path, with the revision's slug; "/" for the home page. ErrNotFound when the parent is not published in locale, or the revision has no text in it.

func (*Service) Publish

func (s *Service) Publish(ctx context.Context, id, revisionID string) error

Publish makes a revision public and rebuilds the published routes. It cancels the page's planned publication changes, like Unpublish.

func (*Service) Published

func (s *Service) Published(ctx context.Context, route Route) (Page, error)

Published returns the published revision a route points to, for example to search the text of published pages.

func (*Service) Purge

func (s *Service) Purge(ctx context.Context, id string) error

Purge removes a page in the trash for good, with its revisions.

func (*Service) Recent

func (s *Service) Recent(ctx context.Context, limit int) ([]Page, error)

Recent returns the latest revision of at most limit pages, the most recently changed first.

func (*Service) Resolve

func (s *Service) Resolve(ctx context.Context, locale, path string) (Resolution, error)

Resolve finds the published page for a path. Paths below a module page resolve to that page with the remainder in Rest.

func (*Service) Restore

func (s *Service) Restore(ctx context.Context, id, revisionID, author string) (Page, error)

Restore copies an older revision of a page into a new latest revision by author, as if it had been saved again. It does not publish it. The page keeps its current place among its siblings, and the old revision's parent must still be a valid place.

func (*Service) Reviews

func (s *Service) Reviews() content.Reviewing[Fields, Text]

Reviews is the review workflow of pages (see content.Reviewing). Publishing pages, by hand or scheduled, follows it through Publish.

func (*Service) Revision

func (s *Service) Revision(ctx context.Context, id, revisionID string) (Page, error)

Revision returns one revision of a page, such as an older one shown in its history.

func (*Service) Routes

func (s *Service) Routes(ctx context.Context, locale string) ([]Route, error)

Routes lists all published paths in a locale in tree order, for navigation: a copy, which the caller may change (see Table.Routes).

func (*Service) RunDue

func (s *Service) RunDue(ctx context.Context, now time.Time, required string) (int, error)

RunDue publishes and unpublishes the pages whose time has come, rebuilding the routes like Publish and Unpublish do; see content.Scheduler.

func (*Service) Save

func (s *Service) Save(ctx context.Context, draft Draft) (Page, error)

Save stores a new revision after checking its place in the tree. The root page stays the root; every other page needs a parent. A translation with a title and without a slug gets one made from the title, unique among the page's siblings in the latest tree.

func (*Service) Schedule

func (s *Service) Schedule(ctx context.Context, id string, schedule content.Schedule) error

Schedule plans the publication of a page revision and/or the page's unpublication (see content.Schedule).

func (*Service) SetCacheTTL

func (s *Service) SetCacheTTL(ttl time.Duration)

SetCacheTTL sets how long the published routes are kept in memory (30 seconds by default). Publishing in this process applies at once. Zero or less disables the cache.

func (*Service) Table

func (s *Service) Table(ctx context.Context) (*Table, error)

Table returns the published route table, the same one until the routes change (see Table), loading it when needed.

func (*Service) Trashed

func (s *Service) Trashed(ctx context.Context) ([]Page, error)

Trashed returns the latest revision of the pages in the trash, most recently deleted first.

func (*Service) Tree

func (s *Service) Tree(ctx context.Context, locale string) ([]Node, error)

Tree returns the latest revision of every page in tree order: Home first, then each page followed by its children, siblings in menu order (by position, then by ID; see siblingOrder). Pages whose parent no longer exists follow at the end. locale chooses the slugs of Node.Path. The translations of Node.Page hold only slugs and titles; Latest loads a page in full.

func (*Service) Undelete

func (s *Service) Undelete(ctx context.Context, id string) error

Undelete brings a page back from the trash, at its place in the tree. Its parent must be there (ErrParentDeleted); a published page must not take a slug its siblings use now (ErrDuplicate) nor a module placed on another page meanwhile (ErrMounted).

func (*Service) Unpublish

func (s *Service) Unpublish(ctx context.Context, id string) error

Unpublish takes a page off the public site; its revisions stay, so it can be published again. The home page cannot be unpublished (ErrHome).

func (*Service) Unschedule

func (s *Service) Unschedule(ctx context.Context, id string) error

Unschedule cancels a page's planned publication change.

type Table

type Table struct {
	// contains filtered or unexported fields
}

Table is one version of the published route table, indexed for each lookup. Publishing, unpublishing or moving a page, and the end of the cache's time to live (see SetCacheTTL), replace it with a new one, so what is made from the routes, such as a menu, can be kept alongside the Table it was made from and made again for another one. A Table never changes; nothing may change what its methods return either.

func (*Table) Alternates

func (t *Table) Alternates(pageID string) map[string]string

Alternates maps each locale to the published path of a page, as Service.Alternates does, without copying: the map is the Table's own, to read and never to change; nil for a page that is not published.

func (*Table) Lookup

func (t *Table) Lookup(locale, path string) (route Route, rest string, ok bool)

Lookup finds the published route of a path in locale, as Resolve does, without loading the page: the page at the path, or the module page above it with the path below that page in rest ("/" for the page itself).

func (*Table) Routes

func (t *Table) Routes(locale string) []Route

Routes lists the published paths in a locale in tree order, as Service.Routes does, without copying them: the list is the Table's own, to read and never to change.

type Text

type Text struct {
	Slug        string `db:"slug"`
	Title       string `db:"title"`
	Description string `db:"description"`
	Body        string `db:"body"`
}

Text is a page's translated content. Body is Markdown.

Directories

Path Synopsis
Package pageadmin is the Pages section of the admin: the page tree, where editors add, move and publish pages and place modules on them, and the editor of each page, built with package editor.
Package pageadmin is the Pages section of the admin: the page tree, where editors add, move and publish pages and place modules on them, and the editor of each page, built with package editor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL