Help us shape the future of Go! Take the 2026 Go Developer Survey

frontend

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 65 Imported by: 0

Documentation

Overview

Package frontend serves the public website. WebThings resolves the URL (language prefix, page tree, module mounts) and builds one View per request; the website's templates only render it.

The layout gets the whole View: site, menu, languages and title. A content template gets only its own typed data (View.Content), so it stays small and its go-clue @dot contract can be checked:

layout.gohtml               frontend.View, with {{content}} for the page
page.gohtml                 frontend.Page, a content page
<key>/<name>.gohtml         a module screen, for example news/post.gohtml
404.gohtml                  frontend.NotFound (optional)
search.gohtml               frontend.SearchView, at /<locale>/search (optional)

Module screens are looked up by the instance key first ("news/post"), then by the module type ("publishing/post"), so instances of one module can share templates or each have their own. Modules declare their templates (see Template); App.WriteTemplates writes starting points for them.

Modules also offer components, parts of a page any template includes with a function such as {{commentThread "remarks" .ID}}; a theme replaces one with <key>/<name>.gohtml or <type>/<name>.gohtml, core's with site/<name>.gohtml, and otherwise the module's default renders (see Component).

The templates of a site are grouped in themes, one folder each (see Themes); the paths above are relative to the theme.

Every template gets the page language from the {{locale}} function and the localizer the translation functions take from {{localizer}}, as in {{tl localizer "Read more"}}. Any other template engine can be used by implementing Renderer, which receives the whole View.

Requests from HTMX receive only the content, without the layout, with the theme's regions out of band (see Templates). One that targets a fragment of the theme gets only that fragment. One that targets another element gets the content, with HX-Retarget and HX-Reswap headers so HTMX swaps the content element; with hx-select (HX-Request-Type "full") the whole page comes back. Of the theme's menu region, a navigation gets only the items whose state changes from the page the visitor comes from (HX-Current-URL), when the items carry their ElementID (see MenuItem). The theme's head region, regions/head.gohtml, replaces the page's tags in <head>, such as the canonical, alternate and feed links, with those of the page arrived at (see Templates).

The layout and content templates also have go-partial's helpers. Of those, url, request, urlIs, urlStarts and urlContains see the page's whole path, for example /en/news/hello on a news post; content is the content template in the layout. slot and hasSlot place the theme's regions in the layout, and oob and oobAttr tell a region it goes out of band. The others, partial, runtime, ctx, basePath, joinPath and urlPath, belong to go-partial's own page structure; basePath is "". Template functions of the site and of modules cannot take any of these names, nor locale, localizer, requiredSlot and deferredSlot: Start refuses them (see render.Reserved).

Index

Constants

View Source
const (
	// DefaultSearchLimit is the number of results the search screen shows,
	// and what Frontend.Search returns for a limit of zero or less.
	DefaultSearchLimit = 20
	// MaxSearchLimit is the most results one search returns, whatever a
	// template asks for, so a search stays cheap.
	MaxSearchLimit = 100
)

Search limits: results per search and the length of a query.

View Source
const CalendarReference = "core:calendar"

CalendarReference is the reference of CalendarTexts in the site's texts (see App.TranslationCatalog).

View Source
const DefaultSearchThrottle = 30

DefaultSearchThrottle is how many searches one visitor may run per minute on the search screen (see SetSearchThrottle).

View Source
const DefaultTheme = "default"

DefaultTheme is the theme other themes fall back to.

View Source
const FormTheme = "webthings"

FormTheme is the go-form theme visitor forms render with unless the site sets another: go-form's markup with the classes of webthings.css.

View Source
const Honeypot = "website"

Honeypot is the name of the hidden field that people leave empty and many bots fill in: go-form's spam trap (form.Info.Honeypot).

View Source
const KeptFormCookie = "wt_form_"

KeptFormCookie goes before the form's key in the name of the cookie that brings a visitor's kept post to its page (see KeepForm): a request with such a cookie may get a page of its own, which caches must not keep for others.

View Source
const OrganizationID = "/#organization"

OrganizationID is the "@id" of the site's Organization item, for items that name it, such as the author or publisher of an article: Thing{"@id": frontend.OrganizationID}. Like other addresses, it is made absolute.

View Source
const PreviewParam = "revision"

PreviewParam is the query parameter that names the revision to preview.

View Source
const ResultThrottled = "throttled"

ResultThrottled is the result Actions returns a visitor with who posted more often than its limits allow: Back(w, r, key, ResultThrottled).

View Source
const SearchPath = "/search"

SearchPath is the path of the search screen below the language prefix.

Variables

View Source
var ErrNoTemplate = errors.New("template_not_found")

ErrNoTemplate is returned when neither the theme nor the module has a template.

View Source
var Floating = time.FixedZone("floating", 0)

Floating is the location of civil times: calendar dates and wall-clock times that are not an instant in the site's timezone. It has no offset, like UTC, but Format never moves a time in Floating to another zone, so a date stored as midnight UTC stays on its day west of UTC.

Functions

func AbsoluteURL

func AbsoluteURL(base string, r *http.Request, path string) string

AbsoluteURL makes path absolute with base, or with the address of r when base is empty; for links in e-mail and share links. The address of r is its own TLS state and Host header; X-Forwarded-Proto and X-Forwarded-Host, or else Forwarded, count only from trusted proxies (webthings.Config.TrustedProxies), as go-router's middleware.Origin reads them. A Host header is what the client sent, so sites set base in production.

func Actions

func Actions(key string, next http.Handler, limits ActionLimits) http.Handler

Actions wraps next, a handler of a module's visitor actions (webthings.Env.Actions, at /_/<key>/...), with limits, always in the same order: the body is limited first, so that even the answer to a throttled visitor, which reads the form to find the way back, reads no more than Body bytes; then each visitor gets Posts posts per Window. key is the module instance, which names the result in the page address.

func Back

func Back(w http.ResponseWriter, r *http.Request, key, result string)

Back redirects to the page the form was on, adding result to its query, for example Back(w, r, "comment", "sent") returns to /en/news/hello?comment=sent. A #fragment of the page is kept. Only paths on this site are followed. Without a back field, as when a post over the body limit could not be read, the visitor returns to the Referer when that is an address on this site, and to the home page otherwise.

func BackPath

func BackPath(r *http.Request) string

BackPath returns the path of the page a visitor's form was on (the back field of VisitorForm, or the Referer when the form has none, see Back), without query or fragment, or "" when it is not a path on this site.

func BackWith

func BackWith(w http.ResponseWriter, r *http.Request, key, result string, extra url.Values)

BackWith is Back with more query values, for example a code the page shows: BackWith(w, r, "signup", "registered", url.Values{"code": {code}}).

func BuiltInCalendar

func BuiltInCalendar(locale string) bool

BuiltInCalendar reports whether the names of months and days and the date patterns of locale, such as "nl", "en_US" or "nl-BE", are built in: a catalogue need not translate them (see CalendarTexts), as what it does not translate shows the built-in ones.

func CalendarTexts

func CalendarTexts() []extract.Message

CalendarTexts are the names and patterns Format looks up in the translations, with their contexts: the English names of the months ("month", "month-short") and days ("day", "day-short"), AM and PM ("time"), and the styles of the date patterns ("date": "short", "medium", "long", "full" and "time", translated as a pattern such as "dd.MM.y"). A translation replaces the built-in name or pattern; a language with built-in ones (BuiltInCalendar) needs none. Extraction adds them to the site's texts (see App.TranslationCatalog).

func Changed

func Changed(r *http.Request) bool

Changed reports whether the post r may have changed what the site's pages show: true unless NothingChanged was called for it.

func Civil

func Civil(t time.Time) time.Time

Civil returns the wall clock of t (its date and time of day in t's own location) in Floating, so Format shows it unchanged. Modules return dates without a zone (stored as midnight UTC) and times in a zone chosen on purpose, such as an event in "UTC", through Civil. A zero time stays zero. Store a civil time with t.UTC(), which keeps the same date and time.

func ClientIP

func ClientIP(r *http.Request) string

ClientIP is the visitor's IP address: the address the request came from, or, when that is a trusted proxy (webthings.Config.TrustedProxies), the address the proxy reports in X-Forwarded-For, X-Real-IP or Forwarded. Without trusted proxies those headers are ignored, since anyone can send them. App finds it with go-router's RealIP middleware; this is middleware.ClientIP.

func Component

func Component(ctx context.Context, key, name string, data any) (template.HTML, error)

Component renders the component name of the module instance key with data, for the template functions of modules, for example Component(ctx, "remarks", "thread", thread). Key "site" is a component of core. The theme's <key>/<name>.gohtml or <type>/<name>.gohtml is used when it has one, otherwise the module's default.

func CoreAssets

func CoreAssets(file string) http.Handler

CoreAssets serves a file core offers themes at the route of the same name: webthings.css, the optional stylesheet of the default components, at /_/webthings.css; focal-points.css, the classes of focalClass, at /_/focal-points.css; and partial-errors.css, go-partial's stylesheet of the box that takes the place of a part of a page that failed (in development, or without an error fragment template), at /_/partial-errors.css. The box has no style attributes, so a Content-Security-Policy does not block it, and is plain text without this stylesheet. The response has a strong ETag and, at the file's current version, may be cached for a year.

func CoreTexts

func CoreTexts() []string

CoreTexts are the texts core translates in Go with the site's translations rather than in a template, so extraction finds them only through this list: the title of the 404 page, the names of social networks, the labels of share links and profiles (site.NetworkName), the title of feeds with the site's name ("%[1]s from %[2]s", see Feed), the message of a theme's 500.gohtml (go-partial's ext/errors SafeMessage, its .Message), which arrives translated in the page's language, and the status texts of the error page's heading (its .Title; see errorStatuses).

The names of months and days and the date patterns of Format, which Format looks up with a context, are CalendarTexts.

func FocalPointsStylesheet

func FocalPointsStylesheet() fs.FS

FocalPointsStylesheet returns a file system that holds focal-points.css, the classes of focalClass (media.FocalPoint.PositionClass), for other parts of the site that serve it with their own assets, such as the admin's crop previews.

func FormatDate

func FormatDate(locale string, t time.Time, style string) string

FormatDate formats t in locale with a style (short, medium, long, full) or a pattern, in t's own timezone.

func FormatMoney

func FormatMoney(locale string, amount int64, code string) string

FormatMoney formats an amount in minor units (cents) of an ISO 4217 currency, for example FormatMoney("nl", 4500, "EUR") is "€ 45,00".

func Funcs

func Funcs() template.FuncMap

Funcs are the core template functions. Module functions come with their modules; all of them can be used in every template.

go-clue:funcmap

func HeadScaffold

func HeadScaffold() string

HeadScaffold is a starting point for regions/head.gohtml, the tags of the page in <head>, which the layout places with {{slot "head"}}: core's tags (webthings/head-tags.gohtml, see Templates) and room for the theme's own. Without the file, core's tags alone are the region.

func IsSpam

func IsSpam(r *http.Request) bool

IsSpam reports whether the honeypot field was filled in (form.IsSpam). Answer such requests as if they succeeded, but store nothing: when it reports true, it tells the app that the post changed nothing (NothingChanged).

func LayoutScaffold

func LayoutScaffold() string

LayoutScaffold is a starting point for layout.gohtml.

func Limits

func Limits(key string, limits ActionLimits) func(http.Handler) http.Handler

Limits is Actions as middleware, for the routes of a module's action router (webthings.Env.Actions): all of them with Use, or one with RouteRegistration.Use.

func Locale

func Locale(ctx context.Context) string

Locale is the language of the page being rendered, for template functions.

func MailScaffold

func MailScaffold(t Template) string

MailScaffold is a mail template's Example, or a starting point with every field of its data in the text.

func NothingChanged

func NothingChanged(r *http.Request)

NothingChanged tells the app that the visitor's post r changed nothing the site's pages show, such as a vote on a closed poll: the output cache keeps its pages (see TrackChanges). Call it only for an answer that stored nothing a page shows.

func PreviewRevision

func PreviewRevision(r *http.Request, adminURL string) string

PreviewRevision returns the revision a request asks to preview (see PreviewParam) when it comes from a signed-in editor who may read the admin section of adminURL, such as the section's index (see webthings.Env.AdminURL); "" otherwise. Module handlers look the revision up for the content at the address (see content.Revisions.PreviewBySlug) and pass what they show to RevisionOf.

func Request

func Request(ctx context.Context) *http.Request

Request is the request of the page being rendered, for template functions that read its query, for example ?comment=sent after a visitor's post.

func Result

func Result(r *http.Request, key string, allowed ...string) string

Result returns the result of a visitor action that the page address carries as ?<key>=<result> (see Back), when it is one of allowed, and "" otherwise, so a page never shows a made-up result from the address bar.

func Robots

func Robots(baseURL, sitemapPath string) http.Handler

Robots serves /robots.txt, which names the sitemap at sitemapPath, such as the path of the app's route named sitemap. It does not name the admin, whose path a site may choose to keep to itself: admin pages answer with X-Robots-Tag: noindex, which keeps them out of search results also when other sites link to them, as a Disallow line cannot.

func Scaffold

func Scaffold(t Template) string

Scaffold is a starting point for a template: its go-clue contract and every field of its data, in plain HTML to build the design around.

func Schema

func Schema() fs.FS

Schema holds this package's schema files per dialect (see db.ApplySchema): the table of kept posts.

func Secure

func Secure(r *http.Request) bool

Secure reports whether the request arrived over HTTPS: its own TLS state, or what a trusted proxy reports with X-Forwarded-Proto or Forwarded. A cookie set for the visitor can then be marked Secure; the headers alone are not believed, since anyone can send them. It is go-router's middleware.Scheme.

func Service

func Service(ctx context.Context, key string) any

Service returns the API of a module instance while a page renders, for template functions, for example Service(ctx, "news").(*publishing.Service).

func ServiceOf

func ServiceOf[T any](ctx context.Context, key string) (T, bool)

ServiceOf returns the API of the module instance key while a page renders, as Service does, when it is a T, such as ServiceOf[*publishing.Service](ctx, "news"); a module's template functions use it. An instance key the site has no T of is a mistake in a template that would show nothing: it is logged at WARN once, with the keys of the instances there are, such as `posts: no publishing instance "nieuws" (known: blog, news)`, and in development (see SetDiagnostics) the render fails with it: ServiceOf panics with the error, which the template turns into the error of the function's call. Outside a render it reports false.

func StyleScaffold

func StyleScaffold() string

StyleScaffold is a starting point for assets/site.css, the stylesheet of the theme that LayoutScaffold links with {{styles}}.

func Summary

func Summary(query Query, length int, texts ...string) string

Summary is a snippet (see Query.Snippet) of the first text that matches the query, or of the first text that is not empty.

func TemplateFuncs

func TemplateFuncs(ctx context.Context) template.FuncMap

TemplateFuncs are every template function with ctx bound, as templates call them: the core functions, the modules' and the site's own, and localizer and locale for the language. ctx is the context of the request a Renderer renders, or of Frontend.Execute; elsewhere it gives nil. A Renderer for another template engine uses it to offer the functions; see Renderer.

func Throttle

func Throttle(limit int, window time.Duration, next http.Handler, options ...ThrottleOption) http.Handler

Throttle allows each visitor (by ClientIP) at most limit posts per window through next; others get 429, or the OnLimit handler, and change nothing (NothingChanged). Requests that only read (go-router's middleware.SafeMethod: GET, HEAD, OPTIONS and TRACE) are not counted, and a limit or window of zero or less does not throttle. Posts are counted in memory, per process: a site on several servers allows limit posts per server, and a restart forgets them. It is go-router's RateLimit.

func TrackChanges

func TrackChanges(r *http.Request) *http.Request

TrackChanges returns r with a record of whether the post changes something, which NothingChanged clears and Changed reads. The app tracks the visitors' posts to the module actions and the pages.

func Translations

func Translations(dir string) (*translator.Translator, error)

Translations loads the site's own UI strings from <dir>/<locale>.po (for example translations/nl.po), for webthings.Config.Translations: templates use them as {{tl localizer "Read more"}}, with plurals {{tn localizer "%d comment" "%d comments" .Count .Count}} and contexts ctl and ctn (go-translator's functions), and core translates its own texts (see CoreTexts), go-form's labels and messages and the names of months and days with them. Text without a translation is shown as written (go-translator's ShowOriginal: for tn and ctn the singular when n is 1, the plural otherwise), so a newly enabled language works before its catalogue exists. A locale without a catalogue of its own uses the one of its language (go-translator's language fallback): nl_BE finds nl.po, and nl finds nl_NL.po. go-form's own messages, such as its validation errors, that a catalogue does not translate show in go-form's catalogue of the page's language.

func VisitorForm

func VisitorForm(ctx context.Context, model any, action, key string) form.RenderModel

VisitorForm is model, a struct with go-form's tags, as a visitor's form for templates, which render it with go-form's functions:

{{form_open .Form}}
    {{form_field .Form "name" nil}}
    <button type="submit">{{tl localizer "Send"}}</button>
{{form_close}}

It posts to action, with the spam trap (Honeypot) and, as its back field, the page being served or rendered (see Back): the element with the id key on that page, so a theme puts the form and its result in an element with that id. key, such as the module instance's key, also goes before the ids of the form's fields (form.Info.IDPrefix), so that the forms of several instances on one page keep ids of their own: the name field of the instance "guestbook" has the id guestbook-name. When the visitor's last post of this form, to action, had mistakes (KeepForm), the form shows that post again: the visitor's values and the messages at their fields. Of several such forms on the page, such as a comment form per post, only the one whose hidden fields hold the posted values does. A form in a fragment that loads after the page ({{deferredSlot}}) does not: the request for the page takes the kept post before the fragment's own request comes, so place such a form with {{slot}} or {{requiredSlot}}.

func WithActionModule

func WithActionModule(ctx context.Context, key string) context.Context

WithActionModule is ctx of a request for a visitor action of the module instance key, below /_/<key>: the app sets it for every action, and Fail logs a failing action with that module, as it logs a module's page with the module on the page (Mount.Module).

func WithTheme

func WithTheme(ctx context.Context, theme string) context.Context

WithTheme renders the request in another theme than the active one, for previews in the admin.

Types

type ActionLimits

type ActionLimits struct {
	// Body is the largest request body in bytes. It can only lower the
	// limit: the site's limit of module actions
	// (webthings.DefaultActionBodyLimit, or the module's
	// webthings.Parts.ActionBodyLimit) applies before Actions, so a larger
	// Body has no effect; raise Parts.ActionBodyLimit instead. Zero keeps
	// the site's limit.
	Body int64
	// Posts is how many posts one visitor may send per Window (see
	// Throttle); zero does not throttle.
	Posts  int
	Window time.Duration
	// Throttled answers a visitor over the limit. By default it returns the
	// visitor to the form's page with ResultThrottled, as Back does.
	Throttled http.Handler
}

ActionLimits are the limits of a module's visitor actions (see Actions).

type Calendars

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

Calendars keeps the names and patterns of months and days that Formats look up in translations, per language (see Format.Calendars). Its zero value is ready to use; Forget drops what it keeps, for translations that changed.

func (*Calendars) Forget

func (c *Calendars) Forget()

Forget drops the names and patterns kept, so they are looked up again.

type Content

type Content struct {
	// Template is the module's template name, for example "post".
	Template string
	// Module is the instance key. Render uses the module of the current page
	// when empty.
	Module string
	// Title and Description are the page's title and description, for the
	// layout's <title> and meta tags.
	Title       string
	Description string
	// Image is the image shown when the page is shared (View.Image).
	Image string
	// Data is what the template gets as the content, such as a post.
	Data any
	// Alternates maps locales to this content's path below the mount, for the
	// language switcher, for example {"en": "/hello", "nl": "/hallo"}. Nil
	// links to the mounted page in each language.
	Alternates map[string]string
	// EditURL is the admin screen of the content, for the edit link signed-in
	// editors see (View.Editor), built from a route name (see
	// webthings.Env.AdminURL), such as the editor of a news item. Empty links
	// to the module's admin section, its route "index".
	EditURL string
	// Status is the response status; zero is 200 OK.
	Status int
	// Revision is set when the content is a revision an editor previews
	// (see PreviewRevision and RevisionOf); Render marks the page as a
	// preview.
	Revision *RevisionPreview
	// StructuredData are the content's schema.org items, such as a post's
	// Article, which {{structuredData}} writes with the site's own (see
	// Thing); the first is the page's main entity.
	StructuredData []Thing
}

Content is what a module handler renders.

type EditLink struct {
	Name    string
	EditURL string
}

EditLink is View.Editor: the signed-in editor and the admin screen of the current page or module item.

type Feed

type Feed struct {
	// Name names the feed's routes on the module's router: Name, a dot
	// and the format, such as feed.atom and feed.rss for the default name
	// "feed".
	Name string
	// Path is where the feed is below the module's page, without the
	// extension, as the patterns of the module's routes: "/feed", the
	// default, serves <page>/feed.atom and <page>/feed.rss, and
	// "/category/{slug}/feed" a feed per category. Its last segment is not
	// a wildcard.
	Path string
	// Page names the module's route whose pages link to the feed in their
	// head ({{feedLinks}}), such as "category", the feed's wildcards
	// filled in with those of the page. Empty links the feed on every page
	// of the module; its Path then has no wildcards.
	Page string
	// Limit is how many of the newest entries the feed lists at most, such
	// as a module's option for its own feeds, up to site.MaxFeedSize. Zero,
	// the default, is the site's setting (site.Settings.FeedLimit).
	Limit int
	// Build returns the feed for a request of it.
	Build FeedFunc
}

Feed is a feed a module offers below the page it is on (webthings.Parts.Feeds), in Atom and RSS (see package feed). The app serves it on the module's router (Router) at Path below the page, with the extension of each format, as routes named Name and the format:

webthings.Parts{
	Public: routes,
	Feeds: []frontend.Feed{
		// <page>/feed.atom and <page>/feed.rss, routes feed.atom and feed.rss
		{Build: s.feed},
		// <page>/category/<slug>/feed.atom, linked on the category's page
		{Name: "category-feed", Path: "/category/{slug}/feed", Page: "category", Build: s.categoryFeed},
	},
}

The addresses of a feed come from its route names, as those of the module's other routes: routes.For(r).RoutePath("feed.atom") in a handler. The layout links to the feeds of the page it shows with {{feedLinks}}. A feed is a page of the site for the output cache, which keeps it for visitors until what visitors see changes, and for the caching of pages (ETag and the site's Cache-Control, see site.Settings.PageCache). Both answer a reader's If-None-Match, and an If-Modified-Since without it, with 304 Not Modified while the feed is unchanged. Its Last-Modified never goes back, also when the newest entry is taken off the site: it is when the app first served the feed as it is now, or its Updated time when that is later. Each process of the app keeps these times itself, so after a restart, or from another process, a reader that goes by the date gets the whole feed once more. It is not in sitemap.xml.

type FeedFunc

type FeedFunc func(r *http.Request, limit int) (feed.Feed, error)

FeedFunc returns a module's feed (Feed.Build) for a request of it, which the module's router serves: routes.For(r) builds addresses on the page, r.PathValue gives the wildcards of the feed's Path and MountFrom(r.Context()) the page and its language. limit is how many of the newest entries the feed lists, the feed's Limit or else the site setting (site.Settings.FeedLimit); the app leaves out entries beyond it.

The app fills in what the feed leaves empty: Title is the page's title, Link the module's page, ID the address of the feed's Atom document, Language the page's language, Author the site's name and Subtitle its tagline; Updated, without entries, is when the page was published. It sets Self to the address of the format served, makes links and image addresses that are paths on the site absolute, and gives an image without a type the one of its extension. Readers see the Title with the site's name, "News from Atelier Noord", translated with the site's translations. Entry content is HTML with absolute addresses, such as AbsoluteMarkdown gives. An error that wraps db.ErrNotFound, such as for a category that does not exist, answers with the site's 404 page; other errors are logged and answered with its error page (see Fail).

type FeedLink struct {
	Type  string
	Title string
	URL   string
}

FeedLink is a link to a feed of the page shown, as {{feedLinks}} writes it in the page's head: the feed's media type, its title as readers see it and its absolute address.

type Format

type Format struct {
	// Locale is a language code such as "nl", "en", "en-US" or "de_DE".
	Locale string
	// Zone is where times in UTC (or the server's local zone) are shown; nil
	// shows them as they are. Times in Floating (see Civil) and in any other
	// zone are always shown as they are.
	Zone *time.Location
	// Lookup returns the translation of a msgid in a context (msgctxt) in
	// Locale, and whether there is one, such as a translator's LookupC; it
	// supplies the names and patterns of months and days, which replace
	// the built-in ones (see CalendarTexts). Nil uses the built-in ones,
	// English for a language without.
	Lookup func(context, msgid string) (string, bool)
	// Calendars, when set, keeps the names and patterns Lookup gives per
	// language, so they are looked up once rather than for every date.
	// Formats that share it must have a Lookup with the same translations.
	Calendars *Calendars
}

Format formats dates, times and amounts of money in one language. Module code that writes e-mail uses it; Frontend.Format gives one with the site's timezone and translations, and FormatDate and FormatMoney are shortcuts.

func (Format) Date

func (f Format) Date(t time.Time, style string) string

Date formats the day of t with a style (short, medium, long or full; "" is long) or a pattern. A zero time gives "".

func (Format) DateRange

func (f Format) DateRange(start, end time.Time, style string) string

DateRange formats the days from start to end, leaving out what they share in the built-in languages, for example "25–27 October 2026". An end on the same day as start, or a zero end, gives one date.

func (Format) Money

func (f Format) Money(amount int64, code string) string

Money formats an amount in minor units (cents) of an ISO 4217 currency with the language's separators and symbol position: "€45.00" in English, "€ 45,00" in Dutch, "45,00 €" in German.

func (Format) Time

func (f Format) Time(t time.Time) string

Time formats the time of day of t, for example "14:00".

func (Format) TimeRange

func (f Format) TimeRange(start, end time.Time) string

TimeRange formats the times of day from start to end, for example "14:00–17:00"; a zero end gives only the start.

func (Format) Weekday

func (f Format) Weekday(day time.Weekday, style string) string

Weekday names a day of the week, in full or, with style "short", abbreviated: "Monday" and "Mon" in English, "maandag" and "ma" in Dutch.

type Frontend

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

Frontend builds Views and renders them. Module handlers receive it to render their screens with the site's menu, languages and layout.

func New

func New(
	settings *site.Service,
	tree *pages.Service,
	library *media.Service,
	renderer Renderer,
	funcs ...template.FuncMap,
) *Frontend

New creates a frontend with one renderer, for example another template engine. Themed gives every theme its own templates. funcs are the modules' and site's template functions: the renderer itself does not get them (see Renderer and TemplateFuncs), but components, mail and Execute use them, and the site's tl among them translates labels such as those of share links.

func Themed

func Themed(
	settings *site.Service,
	tree *pages.Service,
	library *media.Service,
	themes *Themes,
	funcs ...template.FuncMap,
) *Frontend

Themed creates a frontend that renders each theme with Go templates from its files (see Templates and Themes).

func (f *Frontend) APILink(r *http.Request) string

APILink is the absolute address of the page r shows in the content API (package api), for a <link rel="alternate" type="application/json">: /api/v1/pages/<id>?locale=<locale> for the page itself, and /api/v1/pages/resolve?path=<address> for an address below a module's page, such as a post's. It is "" while the API is off, and for a request on no page, such as an error page.

func (*Frontend) AbsoluteMarkdown

func (f *Frontend) AbsoluteMarkdown(r *http.Request, source string) template.HTML

AbsoluteMarkdown renders editor content for readers away from the site, such as entries of a feed: as Markdown does, in the language of the request's page, but with every address of the site absolute (the site's BaseURL, or the address of r) and images as e-mail shows them (MailImage), a copy every reader can show, with its size. Relative links lead from the address of r, such as the feed's, as a reader resolves them in a document without a base (see markdown.Options.Base).

func (*Frontend) AltText

func (f *Frontend) AltText(ctx context.Context, id, locale string) string

AltText is the alt text editors wrote in the media library for the image asset id in locale, or "", such as for an image in e-mail.

func (*Frontend) Assets

func (f *Frontend) Assets() http.Handler

Assets serves the themes' assets/ folders at the route "theme", /theme/{file...}: /theme/<theme>/<file> from that theme and /theme/<file> from the active one. Themes find files they lack in the default theme. Every response has a strong ETag; a request for a file's current version (see asset) may be cached for a year, any other one revalidates.

func (*Frontend) BackLocale

func (f *Frontend) BackLocale(r *http.Request) string

BackLocale returns the language of the page a visitor's form was on: the first segment of its path (see BackPath) when that is an enabled language, and the site's default language otherwise. It returns "" when the languages cannot be read.

func (*Frontend) BindForm

func (f *Frontend) BindForm(r *http.Request, input any) (form.FieldErrors, error)

BindForm maps a visitor's post onto input, a pointer to a struct with go-form's tags, and validates it, as a module action does before it stores anything:

var in PostInput

errs, err := env.Frontend.BindForm(r, &in)
if err != nil || len(errs) > 0 {
	env.Frontend.KeepForm(w, r, key, in, errs)
	frontend.Back(w, r, key, "invalid")

	return
}

It is go-form's Bind: text is trimmed (MapOptions.TrimSpace), and its line breaks are read as "\n", as a browser counts them for maxlength, rather than the "\r\n" it sends (MapOptions.NormalizeNewlines). A value that cannot be read as its field's type, such as "abc" for a number, comes back as an error with the rule "invalid" in place of what validation says about the field; the others carry the rule they broke (form.FieldValidationError.Rule), for a result code per rule. Limits of an input that implements form.Overrider apply. The messages are in the language of the page the form was on (BackLocale), translated with the site's translations, for the form to show at its fields (KeepForm). err is only set when the post cannot be mapped at all. Date-times of datetime-local inputs are read in the site's timezone, in which visitor forms show them (MapOptions.Location), as the admin does.

func (*Frontend) CheckTemplates

func (f *Frontend) CheckTemplates() ([]TemplateProblem, error)

CheckTemplates parses every template of every theme, *.gohtml in any folder, with the functions and render stages the theme's pages render with, and reports every problem a render would fail with: a template that does not parse, a function the site does not have, such as one of a module that is not in app.Use, and a {{template}} that names a template no file of the theme defines (go-partial's Partial.CheckTemplates). A file of the default theme that other themes use is reported once, with all of them. A site with a Renderer of its own has nothing to check. The error is that of reading the files.

func (*Frontend) CleanupForms

func (f *Frontend) CleanupForms(ctx context.Context) error

CleanupForms removes the kept posts (KeepForm) whose five minutes have passed without a view of their page. Keeping a post does this too; App also runs it with its hourly cleanup, so posts nobody came back for do not stay when no new ones come.

func (*Frontend) Editors

func (f *Frontend) Editors(next http.Handler) http.Handler

Editors wraps the public site: it recognises signed-in editors, so Editor and View.Editor work below it, and keeps their pages out of caches.

func (*Frontend) Execute

func (f *Frontend) Execute(ctx context.Context, name, locale string, data any) (string, error)

Execute renders a template of the active theme on its own, without the layout, for example Execute(ctx, "partials/card", "nl", data), through go-partial as a part of the theme's pages. Templates in partials/ can be used, and every template function works in locale.

func (*Frontend) Fail

func (f *Frontend) Fail(w http.ResponseWriter, r *http.Request, err error)

Fail answers a public request that failed with err, for module handlers and visitor actions as for core's own pages: content that does not exist (db.ErrNotFound, which content and pages return) gets the site's 404 page; any other error is logged with the module and path and answered with status 500 by the theme's error page, 500.gohtml, with its message in the page's language (see Templates), or go-partial's plain error markup for a theme without one (go-partial's ext/errors Respond). The cause ends up in the log rather than in the visitor's browser. An HTMX request, such as a form post, gets the page too, which the interactions client's reloadOnError shows as the whole page.

func (f *Frontend) FeedLinks(r *http.Request, title string) []FeedLink

FeedLinks are the links to the feeds of the page r shows, both formats of each, Atom first: the module's feeds linked on every page of it, and those linked on the page's route (Feed.Page). title is the page's title, the title of the links to the latter; the others have the title of the module's page. As for {{feedLinks}}, there are none on an error page, such as a category that does not exist, whose feed does not exist either: the request a Renderer gets knows the page's status.

func (*Frontend) FeedURL

func (f *Frontend) FeedURL(r *http.Request, format string, name ...string) string

FeedURL is the address of the feed of the page r shows in format, "atom" or "rss", such as "/en/news/feed.atom", for a visible link to it: the feed linked on the page's route (Feed.Page), such as a category's on the category's page, or else the one linked on every page of the module; the first the module declared of either. A name picks the feed with that Name instead. It is "" for a page without such a feed, an unknown format and, as for FeedLinks, an error page.

func (*Frontend) Format

func (f *Frontend) Format(ctx context.Context, locale string) Format

Format returns the formatting of locale with the site's timezone and the site's translations, as the template functions use it.

func (*Frontend) Handler

func (f *Frontend) Handler(modules map[string]http.Handler) http.Handler

Handler serves the public site: "/" redirects to the default language, "/<locale>/..." resolves through the page tree. Paths at and below a module page go to that module's handler (see Router).

func (*Frontend) Has

func (f *Frontend) Has(theme, name string) bool

Has reports whether a theme has a template, for example "news/post".

func (*Frontend) Image

func (f *Frontend) Image(ctx context.Context, id string) media.Image

Image is how a page shows an image asset by default: a copy at most media.ImageWidth wide in media.DefaultVariantFormat, with a srcset of smaller ones, its size and the original for downloads (see media.Service.Image). Module views give their images with it, as ImageURL, ImageSrcset, ImageWidth, ImageHeight and ImageOriginalURL fields (<X>URL and so on for an image named otherwise, such as Photo); an empty id gives an empty Image.

func (*Frontend) KeepForm

func (f *Frontend) KeepForm(w http.ResponseWriter, r *http.Request, key string, model any, errs form.FieldErrors)

KeepForm keeps a visitor's failed post of the form key for the page the form was on (see BackPath), for VisitorForm to show it again there: the values r posted of model's fields (r.PostForm), without passwords, files and the spam trap (Honeypot), and errs at their fields, such as those BindForm returned. model is the input struct, a go-form Schema, or the form VisitorForm made; key and the address r posted to must be those VisitorForm made the form with. Call it before the redirect, Back or BackWith. Nothing is kept when the form was on no page of this site; problems are logged, and the visitor then returns with the result alone. A failed post changed nothing: KeepForm tells the app so (NothingChanged).

One visitor (by ClientIP) has at most ten posts kept at a time, and none for more than five minutes; the actions' throttle (Limits) bounds how fast they come.

func (*Frontend) Mail

func (f *Frontend) Mail(ctx context.Context, locale, key, name string, data any) (mail.Message, error)

Mail writes a module's e-mail from its template name in locale, for example Mail(ctx, "nl", "agenda", "confirmation", data): the subject, text and optional HTML of the message. The caller sets the recipients.

Every part renders through go-partial as a part of the theme's pages, with its functions, render stages and diagnostics: the subject and text as plain text (go-partial's RenderText), the HTML part as HTML.

func (*Frontend) MailImage

func (f *Frontend) MailImage(ctx context.Context, id string) media.Image

MailImage is how e-mail shows an image asset: a copy at most media.MailImageWidth wide, never enlarged, as a JPEG, or as a PNG when the upload is a PNG with transparency (media.MailFormat); a GIF keeps its own address. Its URL is a path on the site when the library serves the file: mail needs it made absolute. An empty id gives an empty Image.

func (*Frontend) Markdown

func (f *Frontend) Markdown(ctx context.Context, source string) template.HTML

Markdown renders editor content with media resolved, in the language of the page being rendered or of the request's page (see MountFrom). JPEG and PNG images are shown as resized copies, as Image gives them; an image without a description in the Markdown gets its alt text from the media library.

func (*Frontend) MarkdownOptions

func (f *Frontend) MarkdownOptions(ctx context.Context, locale, base string, mail bool) markdown.Options

MarkdownOptions are the options to render editor content with (markdown.Render, markdown.PlainMedia) as the site does: links to media give the file, and an image without a description in the Markdown gets its alt text in locale from the media library. base is markdown.Options.Base, such as "https://example.com/en/" for readers away from the site, or "" for the site's own pages. Images are shown as a page shows them (Image), a resized copy with a srcset; with mail, as e-mail shows them (MailImage), one copy every reader can show, such as for a feed or a mailing.

func (*Frontend) MediaURL

func (f *Frontend) MediaURL(ctx context.Context, id string) string

MediaURL is the public URL of a media asset, or "". Images on pages use Image instead, which does not send the uploaded original.

func (*Frontend) ModulePreviewURL

func (f *Frontend) ModulePreviewURL(ctx context.Context, key, locale, relative, revisionID string) string

ModulePreviewURL is the address at which an editor previews a revision of module content, at relative below the page of the module instance key, for example "/hello"; "" while the instance is on no page in locale.

func (*Frontend) NotFound

func (f *Frontend) NotFound(w http.ResponseWriter, r *http.Request)

NotFound renders 404.gohtml when the site has one, with the title "Not found" in the page's language (the site's translations, see Translations); otherwise a plain 404.

func (*Frontend) PagePreviewURL

func (f *Frontend) PagePreviewURL(ctx context.Context, page pages.Page, locale string) string

PagePreviewURL is the address at which an editor previews a page revision in locale (see PreviewParam); "" when it has none, because its parent page is not published.

func (*Frontend) Render

func (f *Frontend) Render(w http.ResponseWriter, r *http.Request, content Content)

Render shows module content in the site layout.

func (*Frontend) Route

func (f *Frontend) Route(name string, args ...any) string

Route is the address of a named route of the app's root router, with its parameters as name and value pairs, as go-router's RoutePathArgs fills them in: Route("theme", "file", "default/site.css"), or a module's visitor action, whose names have the instance key in front, such as Route("downloads.download", "id", id). It is "" for an unknown route or a missing parameter, which is logged, and before the app has built its routes; they exist by the time a module's Setup runs.

func (*Frontend) Router

func (f *Frontend) Router() *router.Router

Router returns a go-router for a module's public handler (Parts.Public), one per module instance. The site resolves the page a module is on and serves the router at the page's address for that request (go-router's ServeAt), so routes are relative to the page, whichever address the page has: "/{$}" is the page itself, "/{slug}" an item below it. Handlers see the request's whole path.

routes := env.Frontend.Router()
routes.Get("/{$}", list)
routes.Get("/{slug}", show).As("show")

Every request without a route, any path or method, gets NotFound: the site's 404 page rather than a plain "404 page not found" or "405 method not allowed". That answer is go-router's NotFound and MethodNotAllowed, not an interception of statuses, so a route that answers 404 itself, such as a lookup of a code that does not exist, keeps its own page. Modules therefore register no "/" route of their own. Route names are the module's own; in a handler, routes.For(r) builds their addresses at the page the request is on, such as routes.For(r).RoutePathArgs("show", "slug", slug) for /en/news/hello. Other routers report them with the instance key in front, such as news.show.

func (*Frontend) Search

func (f *Frontend) Search(ctx context.Context, locale, query string, limit int) ([]SearchResult, error)

Search finds published pages and module content in locale that contain every word of query, best matches first. A module that fails is logged and left out, so one module cannot break the search.

func (*Frontend) SendMail

func (f *Frontend) SendMail(ctx context.Context, sender mail.Sender, letter Letter) error

SendMail writes letter with its mail template and sends it with sender.

It is meant for mail a visitor's post or an editor's action causes, after the post itself was stored: the mail is sent without the request's cancellation, so it still goes out when the visitor leaves the page (the sender's own timeout bounds it), and a failure is logged with the module and the mail's name. The error is returned for callers that tell the visitor; most ignore it.

func (*Frontend) ServeFeeds

func (f *Frontend) ServeFeeds(key string, routes *router.Router, feeds []Feed) error

ServeFeeds registers a module's feeds (webthings.Parts.Feeds) on its router, the module's Public router of Router, before the app serves. It returns an error for a feed it cannot serve: one without Build, a Path that does not start with a slash or ends in a wildcard, a Page the router has no route for, a feed with wildcards linked on every page, a Limit below zero or above site.MaxFeedSize, or routes whose path or name the router has already.

func (*Frontend) SetBaseURL

func (f *Frontend) SetBaseURL(base string)

SetBaseURL sets the site's public address, for example "https://example.com", for absolute links such as share links; empty uses the address of each request.

func (*Frontend) SetComponents

func (f *Frontend) SetComponents(components map[string][]Template)

SetComponents tells the frontend the components of the module instances, by key; their Example is used when a theme has none.

func (*Frontend) SetDatabase

func (f *Frontend) SetDatabase(database *db.DB)

SetDatabase gives the frontend the site's database, which keeps visitors' posts with mistakes (KeepForm) for their pages; Schema has its table. App calls it at Start. Without it no post is kept.

func (*Frontend) SetDiagnostics

func (f *Frontend) SetDiagnostics(diagnostics render.Diagnostics)

SetDiagnostics sets what the themes' template renderers report, for example render metrics, and whether they show errors in detail (development); see render.Diagnostics. A Renderer of the site's own is left alone. Call it before serving.

func (*Frontend) SetEditors

func (f *Frontend) SetEditors(identify func(r *http.Request) (SignedIn, bool))

SetEditors sets how the frontend recognises a signed-in editor from a request; App connects it to the admin's sessions.

func (*Frontend) SetFormTheme

func (f *Frontend) SetFormTheme(name string)

SetFormTheme names the go-form theme the site's visitor forms render with (form_field and the other form functions), such as one the site registered with go-form's formtemplates.RegisterClassTheme with the classes of its CSS; empty is FormTheme. Call it before serving.

func (*Frontend) SetMail

func (f *Frontend) SetMail(templates map[string][]Template)

SetMail tells the frontend the mail templates of the module instances, by key; their Example (or a generated text) is used when a theme has none.

func (*Frontend) SetModules

func (f *Frontend) SetModules(types map[string]string, services map[string]any)

SetModules tells the frontend the type and API of every module instance: types let "news/post" fall back to "publishing/post", services are what template functions use (see Service).

func (*Frontend) SetReload

func (f *Frontend) SetReload(reload bool)

SetReload makes the frontend read the themes' templates again for every render when reload is true, so edits to template files show without a restart; for development (webthings.Config.ReloadTemplates). Otherwise every template, the layout, content templates, partials and components, is parsed once. Call it before serving.

func (*Frontend) SetRoutes

func (f *Frontend) SetRoutes(routes *router.Router)

SetRoutes gives the frontend the app's root router, whose named routes Route builds addresses of. webthings.App sets it before it serves.

func (*Frontend) SetSearch

func (f *Frontend) SetSearch(searchers map[string]SearchFunc)

SetSearch gives the frontend the modules' search functions by instance key.

func (*Frontend) SetSearchThrottle

func (f *Frontend) SetSearchThrottle(limit int, window time.Duration)

SetSearchThrottle allows each visitor (by ClientIP) at most limit searches per window on the search screen; more get the search page with SearchView.Throttled and status 429 Too Many Requests. A search reads all published content, so a visitor sending queries in a loop would keep the site busy. Visiting the screen without a query is not counted, nor is the {{search}} template function, whose queries the site's templates choose. Searches are counted in memory, per process. Zero or less turns the limit off; the default is DefaultSearchThrottle searches per minute.

func (*Frontend) SetStructuredData

func (f *Frontend) SetStructuredData(funcs []StructuredDataFunc)

SetStructuredData sets the functions that add to the structured data of every page, in the order of the modules (see StructuredDataFunc).

func (*Frontend) SetTranslations

func (f *Frontend) SetTranslations(tr *translator.Translator)

SetTranslations sets the site's translations (see Translations): the themes' templates, components and mail get its tl, tn, ctl and ctn, and core translates with it its own texts (CoreTexts), go-form's texts of visitor forms and the names of months and days (see Format). Without, every text shows as written. Call it before serving.

func (*Frontend) Sitemap

func (f *Frontend) Sitemap(baseURL string, modules map[string]SitemapFunc, version func() uint64) http.Handler

Sitemap serves /sitemap.xml: every published page in every language, with language alternates, plus the paths modules report below their pages. A module that fails is logged and left out, so one module cannot break the sitemap. It lists at most 50,000 URLs, the most a sitemap file may hold.

version is the version of what the sitemap lists, such as the app's output cache version, which moves on with every change in this process of the published pages and the modules' content. The sitemap is built again as soon as it moved on, so a page published in this process is in the next sitemap, also one the output cache keeps for the new version. Otherwise it is built at most once every ten seconds. It lists the published routes as the page service keeps them in memory (see pages.Service.SetCacheTTL, webthings.Config.CacheTTL), so changes made by other processes show up within that cache time plus ten seconds. A nil version only counts the time.

func (*Frontend) SocialImageURL

func (f *Frontend) SocialImageURL(ctx context.Context, ref string) string

SocialImageURL is the image shown when a page is shared, as View.Image has it: a 1200 × 630 JPEG of the image ref, an asset ID or a media URL, cut around its focal point (see Site.SocialImageURL). Modules give it as the image of their structured data, such as an article's. Other files and addresses keep their own; "" gives "".

func (*Frontend) TagURI

func (f *Frontend) TagURI(r *http.Request, date time.Time, specific string) string

TagURI is an ID for a feed or entry that stays the same when its address changes (see feed.TagURI): a tag: URI on the site's host, that of BaseURL or else of r, without its port. date is a day the site had the host, such as when the item was created, and specific tells the item apart from every other the site named that day, such as "news/<post ID>".

func (*Frontend) Template

func (f *Frontend) Template(theme, key, name string) string

Template returns the template for a module screen in a theme: <key>/<name> when the theme has it, otherwise <type>/<name>. A theme's own templates come before those it has from the default theme (see Themes), both names (go-partial's FirstExistingIn).

func (*Frontend) ThemeCSP

func (f *Frontend) ThemeCSP(ctx context.Context) map[string][]string

ThemeCSP are the Content-Security-Policy sources of the theme a request renders in (see Theme.CSP): the active theme's, or the previewed one's (see WithTheme); nil when the theme has none or the settings cannot be read. The map is the theme's own, to read and never to change.

func (*Frontend) ThemeScreenshot

func (f *Frontend) ThemeScreenshot(theme Theme) string

ThemeScreenshot is the address of the picture of theme its theme.json names (Theme.Screenshot), versioned like {{asset}}, for pages outside the theme, such as the admin's theme cards; "" without one.

func (*Frontend) ThemeStyles

func (f *Frontend) ThemeStyles() http.Handler

ThemeStyles serves the values stylesheets of the themes at the route "theme.values", /theme/<theme>/values.css, from the current settings (see themeStyle); a theme without settings with "css" has none, and a theme's own assets/values.css is not served there. The response has a strong ETag; a request for the current version may be cached for a year, any other one revalidates.

func (*Frontend) Themes

func (f *Frontend) Themes() *Themes

Themes returns the site's themes; nil when it uses a single renderer.

func (*Frontend) ValidateSchema

func (f *Frontend) ValidateSchema(r *http.Request, schema form.Schema, values url.Values) form.FieldErrors

ValidateSchema checks the values of a visitor's form defined at runtime, a go-form Schema such as a form an editor put together, as BindForm checks a struct: go-form's errors, with their messages translated with the site's translations into the language of the page being served (Mount.Locale), for the page to show at their fields with form_field or form_render.

type Label

type Label map[string]string

Label is a text with optional translations, written in theme.json as a string or as an object of language codes.

func (Label) In

func (l Label) In(language string) string

In returns the label in language, the untranslated one, or English.

func (*Label) UnmarshalJSON

func (l *Label) UnmarshalJSON(data []byte) error

UnmarshalJSON reads a plain string as the text for every language (key ""), and an object as a text per language code.

type Language

type Language struct {
	// Locale is the language code, such as "nl".
	Locale string
	// URL is the address of the page in this language.
	URL string
	// Active is set for the language being shown.
	Active bool
	// Default is set for the site's default language. When the page has
	// more than one language, themes also give its address as the
	// x-default alternate, for visitors whose language the site does not
	// have, as the sitemap does:
	//
	//	{{if .Default}}<link rel="alternate" hreflang="x-default" href="{{absURL .URL}}">{{end}}
	Default bool
}

Language is one language of the site in a language switch: the current page in that language, or the home page when there is no current page. Languages the page is not translated into are left out.

type Letter

type Letter struct {
	// Locale is the language the mail is written in, such as the visitor's
	// language, or the admin language for a notification to staff.
	Locale string
	// Key is the module instance, Name the mail template of its Module.Mail.
	Key  string
	Name string
	// Data is what the template gets as its dot.
	Data any
	// To are the recipients' addresses.
	To []string
	// ReplyTo is the address answers go to, such as the visitor who wrote a
	// message; empty for the sender's own address.
	ReplyTo string
}

Letter is a module e-mail to send with SendMail: the mail template Name of the module instance Key (see Mail), written in Locale with Data.

type MenuItem struct {
	// ID is the page's ID, Path its path without language prefix, for
	// example "/about/team", so templates can tell pages apart.
	ID   string
	Path string
	// ElementID is the HTML id of the item's element, "menu-" and the
	// page's ID, as in <a id="{{.ElementID}}" href="{{.URL}}">. When the
	// items of the theme's menu region, regions/menu.gohtml, carry it, an
	// HTMX navigation sends only the items whose Active changes, rather
	// than the whole menu; the region must then show nothing else that
	// changes from page to page. Without it, the whole menu goes, as it
	// does with every other request. Pages added, moved or renamed show in
	// the menu with the next whole page.
	ElementID string
	Title     string
	// URL is the page's address in the page's language.
	URL string
	// Active is set for the page being shown and the pages above it, so a
	// menu can mark where the visitor is.
	Active bool
	// Children are the published pages below this one.
	Children []MenuItem
}

MenuItem is one published page in the site's menu, with the pages below it, in the order editors gave them. The menu is made once for every version of the published pages and shared by the requests that show it: a Renderer reads it and never changes it.

type Mount

type Mount struct {
	// Locale is the language of the request, such as "en".
	Locale string
	// Path is the page path without language prefix, for example "/blog".
	Path string
	// URL is the full path of the request, for example "/en/blog/hello".
	URL string
	// PageID is the ID of the page the request is on or below.
	PageID string
	// Module is the key of the module instance placed on that page; empty
	// for content pages.
	Module string
	// Title is the published title of the page in Locale. A newer draft of
	// the page never shows here, so module screens can use it as their title.
	Title string
	// contains filtered or unexported fields
}

Mount is where the current request sits in the page tree.

func MountFrom

func MountFrom(ctx context.Context) Mount

MountFrom returns the request's position; module handlers use it for links.

type NotFound

type NotFound struct {
	HomeURL string
}

NotFound is the data of 404.gohtml.

type Page

type Page struct {
	ID string
	// ParentID is the parent page's ID, empty for the home page.
	ParentID string
	// Slug is the page's own part of its path in this language, Path the
	// whole path without language prefix, for example "team" and
	// "/about/team"; "/" for the home page. In a preview Path and URL are
	// where the previewed revision shows: below the parent's published
	// path, with the revision's slug (see pages.Service.PreviewPath), also
	// for a page that was never published.
	Slug        string
	Path        string
	Title       string
	Description string
	Body        template.HTML
	URL         string
	// IsHome is set for the home page, which sites often design differently.
	IsHome bool
}

Page is a published content page.

type Profile

type Profile struct {
	// Network is one of site.Networks, for example "instagram", for an icon.
	Network string
	// Label is the network's name in the page's language.
	Label string
	URL   string
}

Profile is one of the site's social media profiles as templates see it.

type Query

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

Query is a parsed search: its words, folded for comparison.

func ParseQuery

func ParseQuery(text string) Query

ParseQuery splits a search into words. Case and accents are folded away, so matching ignores them.

func (Query) Empty

func (q Query) Empty() bool

Empty reports whether the query has no words.

func (Query) Score

func (q Query) Score(title string, texts ...string) int

Score is 0 unless every word occurs in the title or in one of the texts. A word in the title counts 10, a word elsewhere 1, and the whole query in the title 5 more, so title matches rank above text matches.

func (Query) Snippet

func (q Query) Snippet(text string, length int) string

Snippet is about length characters of text around the first matching word, cut at word boundaries, with "…" where text was left out. Without a match it is the start of text.

type Renderer

type Renderer interface {
	Render(w http.ResponseWriter, r *http.Request, name string, view View) error
	Has(name string) bool
}

Renderer turns a View into HTML. name is the template name without extension, for example "page" or "news/post". Has reports whether a template exists.

A Renderer other than the built-in Templates (webthings.Config.Renderer) renders the layout around the content itself: View.Content is the content template's data, the rest of View the layout's. It answers "page", "404" (optional, see Has), "search" (optional) and module screens such as "news/post", and HTMX requests as it sees fit. Render's request carries the page being rendered, so TemplateFuncs(r.Context()) gives every template function (core, modules' and site's, with localizer and locale of the page) ready to call; modules' functions such as {{posts "news" 3}} and components such as {{commentThread "remarks" .ID}} need them. When Render returns an error it must not have written anything, so the frontend can answer with its error page (see Frontend.Fail) and log it; the one exception is an error that wraps render.ErrShown, returned after the renderer answered with an error page itself, such as a theme's 500.gohtml.

func Missing

func Missing() Renderer

Missing is the renderer of a site without templates; it explains what to add.

func Templates

func Templates(files fs.FS, funcs ...template.FuncMap) Renderer

Templates renders with Go templates from files: layout.gohtml, with the View, wraps <name>.gohtml, with View.Content. Requests from HTMX receive only the content. The localizer in templates is the page language, for use with go-translator's `tl`.

Templates in partials/ can be used in every template, for example {{template "partials/card.gohtml" .}}. Functions whose first parameter is a context.Context get the request's context; Locale and Service work there.

For HTMX, a theme has two more folders. A template in regions/, such as regions/menu.gohtml, is a part of the layout that the layout places with {{slot "menu"}}: it gets the View, and goes along out of band with every HTMX request for the content, so a menu or a language switch follows the page. Of regions/menu.gohtml, whose items carry their ElementID, a navigation sends only the items that change (see MenuItem).

regions/head.gohtml is the region of the page's tags in <head>: its <title>, the canonical and alternate links, {{feedLinks}}, the description, the tags for sharing and {{structuredData}}, the page's JSON-LD (see Thing). The layout places it in <head> with {{slot "head"}}; it needs no ID and no {{oobAttr}}. A navigation replaces the tags it wrote on the page left with those of the page arrived at, so none stays behind and none is doubled: on a whole page the tags sit between two empty <template> markers, and with HTMX they are removed and added with two out-of-band swaps (go-partial's exp/head). The <title> goes on its own, which HTMX sets as the document's title.

A theme without regions/head.gohtml, also in the default theme, whose layout places {{slot "head"}}, gets core's head region: the title (on the home page the site's SEO title when there is one), the description (the page's, the SEO description or the tagline), robots for a preview, the canonical and alternate links, og:title, og:description, og:type, og:url, og:site_name, og:locale, og:image, twitter:card, the icons, {{feedLinks}}, {{apiLink}} and {{structuredData}}; a page with status 400 or above has no canonical, alternate or og:url. Those tags are webthings/head-tags.gohtml, which a theme's own regions/head.gohtml can call and add to:

{{template "webthings/head-tags.gohtml" .}}
<meta name="theme-color" content="#336699">

A layout without {{slot "head"}} writes its tags itself and gets no head region from core.

A template in fragments/, such as fragments/comments.gohtml, is a part of a page that HTMX can ask for on its own: a template places it with {{slot "comments"}}, or {{requiredSlot "comments"}}, which fails when the theme has no such fragment, and a request whose target is #comments gets only that fragment; {{deferredSlot "comments"}} places a placeholder that loads it after the page, {{deferredSlot "comments" "reveal"}} when it scrolls into view. A fragment gets View.Content, like the content template; its outer element has the fragment's name as ID, a region's also {{oobAttr}}. A fragment of search.gohtml, such as the results a live search asks for while the visitor types, comes with the regions out of band, as the content does: the search's address changes with every query, and the <title> of regions/head.gohtml and the language links follow it.

A theme with a 500.gohtml shows it when a template fails, outside development: on its own, without the layout, with go-partial's ext/errors Data as its dot and the View for @model (see render.Diagnostics.ErrorPage). Its .Message is a safe sentence, already translated in the page's language with the site's translations (see CoreTexts), so {{.Message}} shows it.

Every template is parsed once and kept; see Frontend.SetReload for development.

type RevisionPreview

type RevisionPreview struct {
	// Number is the revision's number in the item's history.
	Number int
	// Published is set when the previewed revision is the published one;
	// usually it is not.
	Published bool
	// PublishedURL is the same address without the preview.
	PublishedURL string
}

RevisionPreview is View.Revision: the revision an editor previews.

func RevisionOf

func RevisionOf[F, T any](r *http.Request, item content.Item[F, T], previewID string) *RevisionPreview

RevisionOf is Content.Revision for an item a module shows: set when item holds the revision the request previews (see PreviewRevision), nil otherwise.

type SearchFunc

type SearchFunc func(ctx context.Context, locale, query string, limit int) ([]SearchResult, error)

SearchFunc searches a module instance's published content in locale for at most limit results. Modules parse query with ParseQuery and score and summarise with its methods, so all results rank alike.

type SearchResult

type SearchResult struct {
	Title string
	// Summary is plain text around the first match.
	Summary string
	URL     string
	// Module is the module instance key, empty for pages.
	Module string
	// Type is the module type, for example "publishing", or "page".
	Type string
	// Score orders the results, highest first; see Query.Score.
	Score int
}

SearchResult is one hit of the site search.

func Rank

func Rank(results []SearchResult, limit int) []SearchResult

Rank sorts results best first (by score, then title) and keeps at most limit of them.

type SearchView

type SearchView struct {
	Query   string
	Results []SearchResult
	// URL is the search screen in this language, for the form's action.
	URL string
	// Throttled is set when the visitor searched too often in a short time
	// (see Frontend.SetSearchThrottle): the search did not run, Results is
	// empty and the response has status 429, so the template shows a
	// message instead, also where a live search swaps the results.
	Throttled bool
}

SearchView is the data of search.gohtml.

type ShareBar

type ShareBar struct {
	// URL is the absolute address of the shared page, Title its title.
	URL   string
	Title string
	Links []ShareLink
}

ShareBar is the data of the site/share component.

type ShareLink struct {
	// Network is facebook, x, linkedin, whatsapp, telegram, email or bluesky.
	Network string
	// Label is the network's name; the share function translates it.
	Label string
	URL   string
}

ShareLink is a link that shares a page on one network.

func ShareLinks(page, title string) []ShareLink

ShareLinks returns the share links for an absolute page address and its title, in a fixed order, with English labels.

type SignedIn

type SignedIn struct {
	UserID string
	Name   string
	// CanEdit reports whether the editor may change what an admin URL shows,
	// such as the editor of a page; CanView whether they may see it, which
	// allows previews (see PreviewParam).
	CanEdit func(adminURL string) bool
	CanView func(adminURL string) bool
	// AdminURL is the address of a named admin route, such as
	// "admin.pages.edit" with "id", as admin.Admin.TryURL builds it: links
	// into the admin never assume its path. It returns an error for an
	// unknown route or parameter.
	AdminURL router.PathFunc
}

SignedIn is an editor, signed in to the admin, viewing the public site.

func Editor

func Editor(ctx context.Context) (SignedIn, bool)

Editor returns the signed-in editor viewing the site; ok is false for visitors. Middleware can use it, for example to leave editors out of statistics.

type Site

type Site struct {
	Name           string
	Tagline        string
	SEOTitle       string
	SEODescription string
	// Footer is the editors' footer text, rendered from Markdown.
	Footer template.HTML
	// LogoURL is the logo, an image of the media library that the editors
	// chose in the settings, at most 600 pixels wide, never enlarged, in
	// the format it was uploaded in (media.OriginalFormat): a PNG stays a
	// lossless PNG and a JPEG a JPEG. A PNG no wider, a GIF and an SVG
	// keep their own address. A theme's own logo is one of its assets
	// ({{asset "logo.svg"}}), not LogoURL.
	LogoURL string
	// FaviconURL is the favicon as a 32 × 32 PNG, for <link rel="icon">;
	// AppleTouchIconURL is the same image as a 180 × 180 PNG, for <link
	// rel="apple-touch-icon">. Both are cut square around the image's focal
	// point.
	FaviconURL        string
	AppleTouchIconURL string
	// SocialImageURL is the image shown when a page is shared on social
	// media and has no image of its own (Site settings), for og:image: a
	// 1200 × 630 JPEG cut around the image's focal point, or smaller with
	// those proportions when the image is.
	SocialImageURL string
	Organisation   string
	Email          string
	Phone          string
	Address        string
	HomeURL        string
	// Profiles are the site's social media profiles, in the order of
	// site.Networks, labelled in the page's language.
	Profiles []Profile
}

Site is the website identity in the current language.

type SitemapFunc

type SitemapFunc func(ctx context.Context, locale string) ([]string, error)

SitemapFunc lists a module's public paths in a locale, relative to the page the module is mounted on, for example "/hello-world".

type StructuredDataFunc

type StructuredDataFunc func(ctx context.Context, view View, items []Thing) []Thing

StructuredDataFunc adds to the structured data of a page, or changes it, for a module that adds to the pages of others, such as a registrations module that gives an event its offers. items are the page's items from its module (View.StructuredData, see Content.StructuredData) and those earlier functions returned; it returns them with its own, and gets the page shown, View.Content and View.Module telling what it is.

type Template

type Template struct {
	// Name is the file name without extension, for example "post".
	Name string
	// Data is a value of the template's data type, for example Post{}.
	Data any
	// Example is a hand-written starting point. Empty generates one from Data.
	Example string
}

Template is a template a module renders, declared so sites can be checked and starting points can be generated.

func CoreComponents

func CoreComponents() []Template

CoreComponents are the components of core, rendered by its template functions and replaced by a theme with site/<name>.gohtml.

func SearchTemplate

func SearchTemplate() Template

SearchTemplate declares search.gohtml, for checks and starting points.

type TemplateProblem

type TemplateProblem struct {
	// Themes are the themes that render the file, such as "default" and
	// "dark" for a file of the default theme that dark does not replace.
	Themes []string
	// Path is the file in the site's templates, in the folder of the theme
	// it comes from, such as "default/layout.gohtml"; the file alone for a
	// single theme at the root of the templates.
	Path string
	// Line and Column are where the problem is in the file, counted from 1;
	// Column is 0 when the parser does not say.
	Line   int
	Column int
	// Func is the function the template calls that the site does not
	// have, such as "newsletterForm"; empty for other problems.
	Func string
	// Template is the template a {{template}} names that no file defines;
	// empty for other problems.
	Template string
	// Suggestion is the function or template whose name is closest to Func
	// or Template; empty when none is close.
	Suggestion string
	// Message says what is wrong, without the position, such as
	// `function "newsletterForm" not defined`.
	Message string
	// EveryPage reports whether every page of the Themes fails with it: a
	// template that does not parse or calls a function the site does not
	// have, in layout.gohtml, regions/ or partials/, which every page
	// parses.
	EveryPage bool
}

TemplateProblem is a problem of a theme's template that CheckTemplates finds: one its renders fail with.

func (TemplateProblem) Error

func (p TemplateProblem) Error() string

Error is the position and the message, with the suggestion when there is one.

func (TemplateProblem) Position

func (p TemplateProblem) Position() string

Position is the problem's file, line and, when known, column, such as "default/layout.gohtml:69:9".

type Theme

type Theme struct {
	// Name is the folder name.
	Name        string
	Title       string
	Description string
	Settings    []ThemeSetting
	// CSP are the sources, by directive, that the site's
	// Content-Security-Policy allows for pages in the theme, besides the
	// site's own, such as a font host: theme.json's "csp", as
	// {"font-src": ["https://fonts.example.com"]}, checked with
	// site.CheckCSPSources.
	CSP map[string][]string
	// Screenshot is a picture of the theme that the admin shows with it,
	// theme.json's "screenshot": the path of an image in the theme's
	// assets/ folder, such as "screenshot.png"; empty without one. See
	// checkScreenshot.
	Screenshot string
	// Files are the theme's templates on top of the default theme's.
	Files fs.FS
	// contains filtered or unexported fields
}

Theme is one design.

func (Theme) Assets

func (theme Theme) Assets() fs.FS

Assets are the files below the theme's assets/ folder.

func (Theme) Values

func (theme Theme) Values(stored map[string]string) map[string]string

Values are a theme's settings for templates: stored values over defaults.

type ThemeSetting

type ThemeSetting struct {
	Name string `json:"name"`
	// Label is shown in the admin, one text or one per admin language.
	Label Label `json:"label"`
	// Type is color, text, select or number.
	Type    string   `json:"type"`
	Options []string `json:"options"`
	// Default is the value without one saved; for a number, the number
	// alone, such as "38".
	Default string `json:"default"`
	// Min and Max bound a number, both included; a number needs both.
	Min *float64 `json:"min"`
	Max *float64 `json:"max"`
	// Step is the difference between a number's values, counted from Min,
	// such as 0.5; zero allows any number between Min and Max.
	Step float64 `json:"step"`
	// Unit is the CSS unit of a number, one of cssUnits, such as "rem",
	// "px" or "%", which the values stylesheet writes after it; empty for
	// a number without one, such as a line height.
	Unit string `json:"unit"`
	// CSS is the custom property, such as "--accent", that the theme's
	// values stylesheet sets to the value of a color, select or number
	// setting (see Themes); a select's options must then be plain CSS
	// values (see cssOption).
	CSS string `json:"css"`
}

ThemeSetting is a value editors choose for a theme.

func (ThemeSetting) Clean

func (s ThemeSetting) Clean(value string) (string, error)

Clean validates a value for this setting; empty means the default.

type ThemeView

type ThemeView struct {
	Name string
	// Values are the theme's settings, with defaults for unset ones.
	Values map[string]string
}

ThemeView is the active theme as templates see it.

type Themes

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

Themes are the designs of a site, one folder each in the templates:

templates/default/layout.gohtml
templates/default/page.gohtml
templates/default/assets/site.css     served at /theme/site.css
templates/default/theme.json          optional title and settings
templates/dark/layout.gohtml          only what differs from default
templates/dark/assets/site.css

A theme finds the templates and assets it does not have in "default", so a variant can be just a stylesheet. A site with layout.gohtml directly in its templates folder has one theme, "default", made of the whole folder.

theme.json names the theme and declares settings that editors change in the admin, such as colours; templates read them as {{.Theme.Values.accent}} in the layout or {{theme "accent"}} anywhere:

{
  "title": "Atelier",
  "description": "Warm colours, serif headings",
  "settings": [
    {"name": "accent", "label": "Accent colour", "type": "color", "default": "#c2410c", "css": "--accent"},
    {"name": "headings", "label": "Headings", "type": "select", "options": ["serif", "sans"], "default": "serif"},
    {"name": "measure", "label": "Text width", "type": "number", "min": 30, "max": 48, "step": 1, "unit": "rem", "default": "38", "css": "--measure"},
    {"name": "notice", "label": {"en": "Notice", "nl": "Mededeling"}, "type": "text"}
  ]
}

A number setting has a min and a max, and optionally a step (from min) and a unit; its value is the number alone, such as "38", and a template adds the unit itself. A colour, select or number setting with "css" becomes a custom property of the theme's values stylesheet, /theme/<theme>/values.css, which {{themeStyle}} links: :root { --accent: #c2410c; --measure: 38rem; }, a number with its unit. It needs no style attribute, so it works under a Content-Security-Policy without 'unsafe-inline' (see Frontend.ThemeStyles). A text setting never goes into CSS.

func LoadThemes

func LoadThemes(files fs.FS) (*Themes, error)

LoadThemes reads the themes in files. Every folder with a layout.gohtml or a theme.json is a theme; layout.gohtml at the root makes the root the only theme. Files without any themes give an empty list.

func (*Themes) Get

func (t *Themes) Get(name string) (Theme, bool)

Get returns the named theme, or the first theme when it does not exist.

func (*Themes) List

func (t *Themes) List() []Theme

List returns the themes, the default theme first.

type Thing

type Thing map[string]any

Thing is one schema.org item of a page's structured data, such as an Article: its "@type" and properties, as encoding/json writes them: text, numbers, booleans, time.Time, nested Things and lists. Empty values, such as "", nil, a zero time.Time and empty lists, are left out, so a module can name every property without checking for content first. Addresses in the properties that hold them (see urlProperties) may be paths, such as a post's URL or an image of the media library: they are made absolute. Dates are best given as text, in the content's own timezone, such as "2026-10-07" or "2026-10-07T19:30:00+02:00".

func ItemList

func ItemList(name string, urls ...string) Thing

ItemList is the structured data of a page that lists items with pages of their own, such as a news list or an agenda (Content.StructuredData): an ItemList named name of their addresses, in the order the page shows them. Items without an address are left out; without any, ItemList is nil, which adds nothing.

type ThrottleOption

type ThrottleOption func(*throttle)

ThrottleOption changes how Throttle answers.

func OnLimit

func OnLimit(handler http.Handler) ThrottleOption

OnLimit answers requests over the limit with handler instead of a bare 429, for example to return to the form: OnLimit(http.HandlerFunc(func(w, r) { frontend.Back(w, r, "comment", "throttled") })).

type View

type View struct {
	Site   Site
	Locale string
	// Path is the current URL path, including the language prefix. Links
	// here, in Menu and Languages are paths; {{absURL .Path}} makes one
	// absolute, for example for a canonical link or hreflang.
	Path        string
	Title       string
	Description string
	// Image is an optional absolute or site-relative image URL for sharing;
	// Site.SocialImageURL is the site's default. An image from the media
	// library is given as Site.SocialImageURL is.
	Image string
	// Menu is the published page tree in this language, shared with
	// other requests: read it, never change it (see MenuItem).
	Menu []MenuItem
	// Languages links to this same content in the other languages.
	Languages []Language
	// Page is the current page; for module screens the page they are mounted on.
	Page *Page
	// Module is the key of the module instance on module screens, for example
	// "faq", so one template can serve several instances differently.
	Module string
	// Content is the data of the content template: a Page, a NotFound or the
	// module's view.
	Content any
	// StructuredData are the schema.org items of a module's content
	// (Content.StructuredData), which {{structuredData}} writes with those
	// of the site and the page (see Thing).
	StructuredData []Thing
	// Preview is true when an editor previews a revision instead of the
	// published content (see PreviewParam); Revision says which, for a
	// banner such as "Preview of revision 4 (not published)". Layouts
	// should also add <meta name="robots" content="noindex"> then.
	Preview  bool
	Revision *RevisionPreview
	// Theme is the active theme and the values editors chose for it.
	Theme ThemeView
	// Editor is set when a signed-in editor who may edit the current page or
	// module item views it; nil for visitors. See Editors.
	Editor *EditLink
	// Status is the response's status, such as 404 for 404.gohtml; zero
	// is 200 OK. Templates gives it to the response; for another
	// Renderer, the frontend sets it on the response writer Render gets.
	Status int
	// contains filtered or unexported fields
}

View is everything a public template receives.

type When

type When struct {
	Start  time.Time
	End    time.Time
	AllDay bool
}

When is when something takes place, such as an event, for templates to show in one line: Start and End in the zone they are shown in, such as an event's own timezone, or as civil times (see Civil). End is zero when it has no end; for something that takes AllDay it is the last day. The events module's Event and the registrations module's Subject give theirs with a When method, so one partial shows both:

{{/* @dot github.com/donseba/go-webthings/frontend.When */}}
{{if .AllDay}}{{dateRange .Start .End "full"}}
{{else if .SameDay}}{{date .Start "full"}} · {{timeRange .Start .End}}
{{else}}{{date .Start "full"}}, {{time .Start}} – {{date .End "full"}}, {{time .End}}{{end}}

func (When) IsZero

func (w When) IsZero() bool

IsZero reports whether the time is not known: Start is zero, as for a registration for something other than an event.

func (When) SameDay

func (w When) SameDay() bool

SameDay reports whether it ends on the day it starts, by the calendar of Start's and End's own zones, or has no end.

Jump to

Keyboard shortcuts

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