templates

package
v0.60.0 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package templates renders filex's mails and the pages it shows strangers from Liquid templates an administrator can change: filex ships one for each (defaults/, the catalogue in catalog.go), the install's administrator may replace any of them for the whole install, and a tenant's administrator for their own tenant. Removing a replacement is "back to the default".

How one is rendered (docs/TEMPLATES.md):

  • The template to use is the tenant's replacement, else the install's, else filex's own; the same for the layout it is put into.
  • A render that involves only filex's own templates runs in-process (internal/templates/engine): they are part of the program. A render that involves ANY replaced template runs in the sandbox (internal/templates/sandbox), where time, memory and output are capped.
  • Whatever comes out passes htmlx.Sanitize: no script, no event handler, no javascript: address. A mail's CSS is then written into its elements (htmlx.InlineCSS) and its plain-text part made from the HTML, unless the template has a plain-text body of its own.
  • ⚠⚠ A replaced template that fails - a syntax error, a ceiling, a filter error - is noted on its row (the editor shows it) and the render is done again with filex's own templates. A broken template never costs a visitor their page or a person their mail.

Index

Constants

View Source
const (
	// PageTimeout bounds a page: a visitor is waiting for it. ⚠ Wall time,
	// the fresh instance included: a render that takes 10 ms on an idle
	// machine took over 250 ms (the first ceiling) on a busy one, and a
	// template that is merely slow under load must not be replaced and marked
	// failed - the ceiling is there for a loop that never ends.
	PageTimeout = time.Second
	// MailTimeout bounds a mail: it is sent off the request's path.
	MailTimeout = 3 * time.Second
	// MaxSource is the longest template the editor saves.
	MaxSource = sandbox.MaxSource
)

The ceilings of a render of a replaced template (sandbox has the memory and output ones).

View Source
const ProductMark = `` /* 386-byte string literal not displayed */

ProductMark is the filex mark the public pages' footer shows: the Go side's one copy of LogoMark (packages/core), for the pages that are drawn without a bundler - the templates' `product.mark` and the handlers' fallback pages (handlers.publicBrandMark). Kept in step with LogoMark by the duplication gate (web/tests/quality/duplication.test.ts, brand-mark).

Variables

View Source
var Defs = []Def{
	{Key: "mail.layout", Kind: KindLayout, Vars: []string{"content"}, /* contains filtered or unexported fields */},
	{Key: "mail.share", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"item.name", "item.kind", "item.size", "item.size_text", "link.url", "link.pin", "link.pin_withheld", "link.days_left"},
		// contains filtered or unexported fields
	},
	{Key: "mail.file_request", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"folder.name", "link.url", "link.pin", "link.pin_withheld", "link.days_left", "limits.max_files", "limits.max_file_mb", "limits.types"},
		// contains filtered or unexported fields
	},
	{Key: "mail.grant", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"item.path", "link.url"},
		// contains filtered or unexported fields
	},
	{Key: "mail.account", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"account.email", "account.password", "link.url"},
		// contains filtered or unexported fields
	},
	{Key: "mail.notification", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"notice.event", "notice.title", "notice.lines", "link.url"},
		// contains filtered or unexported fields
	},
	{Key: "mail.digest", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"notice.title", "notice.lines", "digest.minutes", "link.url"},
		// contains filtered or unexported fields
	},
	{Key: "mail.smtp_test", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true, Vars: []string{}, /* contains filtered or unexported fields */},
	{Key: "mail.cloud_verify", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"tenant.slug", "verify.token", "verify.ttl"},
		// contains filtered or unexported fields
	},
	{Key: "mail.app", Kind: KindMail, Layout: "mail.layout", Subject: true, Text: true,
		Vars: []string{"app.name", "message.subject", "message.body"},
		// contains filtered or unexported fields
	},

	{Key: "page.layout", Kind: KindLayout, Vars: []string{"content"}, /* contains filtered or unexported fields */},
	{Key: "page.pin", Kind: KindPage, Layout: "page.layout", Hooks: []string{"name=pin"},
		Vars: []string{"form.action", "form.error", "link.kind"},
		// contains filtered or unexported fields
	},
	{Key: "page.unlocked", Kind: KindPage, Layout: "page.layout", Vars: []string{}, /* contains filtered or unexported fields */},
	{Key: "page.error", Kind: KindPage, Layout: "page.layout",
		Vars: []string{"error.code", "error.key", "error.title", "error.body"},
		// contains filtered or unexported fields
	},
	{Key: "page.zip_wait", Kind: KindPage, Layout: "page.layout", Hooks: []string{"bar", "pct", "dl"},
		Vars: []string{"folder.name", "zip.url"},
		// contains filtered or unexported fields
	},
	{Key: "page.folder", Kind: KindPage, Layout: "page.layout",
		Vars: []string{"folder.name", "folder.path", "folder.up_url", "folder.zip_url", "folder.gallery", "folder.counts", "entries"},
		// contains filtered or unexported fields
	},
	{Key: "page.file_request", Kind: KindPage, Layout: "page.layout",
		Hooks: []string{"card", "sub", "drop", "hint", "file", "nameField", "uploaderName", "note", "files", "bar", "barfill", "msg", "send", "foot"},
		Vars:  []string{"folder.name", "form.name_max"},
		// contains filtered or unexported fields
	},
	{Key: "page.app", Kind: KindPage, Layout: "page.layout",
		Vars: []string{"app.title", "files"},
		// contains filtered or unexported fields
	},

	{Key: "page.delete_confirm", Kind: KindPage, Layout: "page.layout", Hooks: []string{"name=confirm"},
		Vars: []string{"file.name", "form.action"},
		// contains filtered or unexported fields
	},
	{Key: "page.delete_done", Kind: KindPage, Layout: "page.layout", Vars: []string{}, /* contains filtered or unexported fields */},
	{Key: "page.cache_wait", Kind: KindPage, Layout: "page.layout", Hooks: []string{"bar", "pct"},
		Vars: []string{"file.name", "file.percent"},
		// contains filtered or unexported fields
	},

	{Key: "page.shell", Kind: KindShell, Hooks: []string{"{{ app_head }}", "{{ app }}"}, Vars: []string{"app", "app_head", "link.kind"}, /* contains filtered or unexported fields */},
	{Key: "slot.share.title", Kind: KindSlot, Vars: slotVars, /* contains filtered or unexported fields */},
	{Key: "slot.share.intro", Kind: KindSlot, Vars: slotVars, /* contains filtered or unexported fields */},
	{Key: "slot.share.footer", Kind: KindSlot, Vars: slotVars, /* contains filtered or unexported fields */},
	{Key: "slot.file_request.title", Kind: KindSlot, Vars: slotVars, /* contains filtered or unexported fields */},
	{Key: "slot.file_request.intro", Kind: KindSlot, Vars: slotVars, /* contains filtered or unexported fields */},
	{Key: "slot.file_request.footer", Kind: KindSlot, Vars: slotVars, /* contains filtered or unexported fields */},
}

Defs is every template filex ships, in the order the editor lists them.

View Source
var ErrMissingHooks = errors.New("the page's own script needs something the template took out")

ErrMissingHooks: the draft lacks what the page's script needs.

View Source
var ErrTooLarge = fmt.Errorf("a template may be at most %d KiB", MaxSource>>10)

ErrTooLarge: a draft's text is over MaxSource.

View Source
var ErrUnknown = errors.New("there is no template with that name")

ErrUnknown: no template has that name.

View Source
var Globals = []string{
	"lang", "dir",
	"brand.name", "brand.logo_url", "brand.footer_text", "brand.hide_powered_by",
	"theme.accent", "theme.accent_hover", "theme.on_accent",
	"product.name", "product.url", "product.link",
	"site.name", "site.url",
}

Globals are the variables every template has.

Functions

func Default

func Default(key, part string) string

Default is filex's own text of a template's part: "" (the body), "subject" or "text". "" when it has none.

func HTML

func HTML(s string) any

HTML marks s as markup filex made, for a template's data.

func Send

func Send(ctx context.Context, r *Renderer, m MessageSender, to string, t Target, key string, data map[string]any) error

Send renders the mail key for t and delivers it to to, tagged with its language. A nil sender is mailer.ErrNotConfigured; the mailer's own errors come back as they are (callers tell "not set up" from "failed").

Types

type Brand

type Brand struct {
	Name          string
	LogoURL       string
	FooterText    string
	HidePoweredBy bool
	// Accent, AccentHover and OnAccent are #rrggbb colours, checked.
	Accent, AccentHover, OnAccent string
	// ThemeCSS is the pages' theme and accent stylesheet (<style> blocks
	// filex made from checked values).
	ThemeCSS string
	// SiteURL is the address the install (or the tenant) is reached at.
	SiteURL string
}

Brand is a tenant's (or the install's) identity as the templates see it: the branding settings, the theme the pages wear, and the colours of a mail's button. The handlers' branding source fills it (Renderer.Brand).

type Def

type Def struct {
	Key  string `json:"key"`
	Kind Kind   `json:"kind"`
	// Layout is the layout the template is put into ("" for none).
	Layout string `json:"layout,omitempty"`
	// Subject: the template has a subject line (a mail).
	Subject bool `json:"subject,omitempty"`
	// Text: the template may have a plain-text body of its own (a mail).
	Text bool `json:"text,omitempty"`
	// Hooks are what the page's own script needs to find in the page: element
	// ids, and `name=…` for a form field. A template without one of them is
	// not saved - the page would stop working.
	Hooks []string `json:"hooks,omitempty"`
	// Vars are the variables the template is given, besides the ones every
	// template has (Globals), as dotted paths.
	Vars []string `json:"vars"`
	// contains filtered or unexported fields
}

Def describes one template filex ships.

func Lookup

func Lookup(key string) *Def

Lookup is the template named key; nil for a name filex does not ship.

func (*Def) AllVars

func (d *Def) AllVars() []string

AllVars is every variable a template has: the globals, its kind's and its own.

func (*Def) Sample

func (d *Def) Sample() map[string]any

Sample is the data a preview of the template renders with: realistic values for every variable it has.

type Draft

type Draft struct {
	Subject string `json:"subject"`
	Body    string `json:"body"`
	Text    string `json:"text"`
}

Draft is what the editor holds: a template's texts before they are saved.

type Kind

type Kind string

Kind is what a template is.

const (
	// KindMail is a mail's content: a subject, an HTML body put into the mail
	// layout, and optionally a plain-text body of its own.
	KindMail Kind = "mail"
	// KindPage is a page filex serves a stranger without JavaScript, put into
	// the page layout.
	KindPage Kind = "page"
	// KindLayout frames every mail (mail.layout) or every page (page.layout):
	// `{{ content }}` is where the mail's or page's own part goes.
	KindLayout Kind = "layout"
	// KindShell is the document the public link page's application is
	// loaded in (page.shell): `{{ app }}` is where it goes.
	KindShell Kind = "shell"
	// KindSlot is a short text the public link page shows in a fixed place of
	// its card (a title, an introduction, a footer line); empty = the page's
	// own words.
	KindSlot Kind = "slot"
)

type Mail

type Mail struct {
	Subject string
	HTML    string
	Text    string
}

Mail is a rendered mail.

type MessageSender

type MessageSender interface {
	SendMessage(ctx context.Context, to string, m mailer.Message) error
}

MessageSender delivers a rendered mail (*mailer.Service).

type Preview

type Preview struct {
	Subject string `json:"subject,omitempty"`
	HTML    string `json:"html"`
	Text    string `json:"text,omitempty"`
	// Removed is what the sanitizer took out of the draft's output ("<script>",
	// "onclick") - it will not reach anybody.
	Removed []string `json:"removed,omitempty"`
	// Missing are the hooks the page's own script needs that the draft does
	// not have (Def.Hooks); a draft with any is not saved.
	Missing []string `json:"missing,omitempty"`
}

Preview is a draft rendered with sample data, in the sandbox, strictly (a variable the template has not got is an error).

type Renderer

type Renderer struct {
	Store       db.Store
	MultiTenant bool
	// Brand answers the identity a render wears; nil = none.
	Brand func(ctx context.Context, t Target) Brand
	// TenantForHost names the tenant a page's address belongs to (0 = the
	// install's), for a page that belongs to no link; nil = the install.
	TenantForHost func(ctx context.Context, host string) int64
	// contains filtered or unexported fields
}

Renderer renders templates. The zero value works on filex's own templates alone; Store makes replacements count.

func (*Renderer) Forget

func (r *Renderer) Forget()

Forget drops the cached output (a template was saved or removed).

func (*Renderer) Mail

func (r *Renderer) Mail(ctx context.Context, t Target, key string, data map[string]any) (Mail, error)

Mail renders the mail key for t with data (its own variables; the globals are added). It falls back to filex's own templates when a replacement fails, so its error is only ever filex's own template's.

func (*Renderer) Page

func (r *Renderer) Page(ctx context.Context, t Target, key string, data map[string]any) (string, error)

Page renders the page key for t. Like Mail, it falls back to filex's own templates when a replacement fails.

func (*Renderer) Preview

func (r *Renderer) Preview(ctx context.Context, t Target, key string, draft *Draft) (Preview, error)

Preview renders draft (nil: the template as it is now in t's scope) with the template's sample data. A layout is previewed around the mail or page it frames; the shell around a stand-in for the application.

func (*Renderer) Scope

func (r *Renderer) Scope(ctx context.Context, t Target) int64

Scope is Target t's template scope: the tenant whose replacements count, 0 for the install's. The admin handlers ask it for the caller's tenant.

func (*Renderer) SetBrand

func (r *Renderer) SetBrand(brand func(context.Context, Target) Brand, tenantForHost func(context.Context, string) int64)

SetBrand wires the identity a render wears and the tenant an address belongs to.

func (*Renderer) Slot

func (r *Renderer) Slot(ctx context.Context, t Target, key string, data map[string]any) string

Slot renders the text slot key: "" when neither the tenant nor the install wrote one (the page then says its own words), or when it failed.

func (*Renderer) Validate

func (r *Renderer) Validate(ctx context.Context, t Target, key string, draft Draft) (Preview, error)

Validate is what a save asks: the draft renders (Preview) and keeps every hook. The preview is returned either way, for the editor to show.

type Target

type Target struct {
	// TenantID is the tenant whose templates and branding are used: the
	// tenant a link was made in, the addressee's tenant. 0 is the install.
	TenantID int64
	// Host is the address a page was asked for at.
	Host string
	// ByHost: the branding is the address's (a page that belongs to no link,
	// such as the "this link is not available" page, says nothing about
	// whose link the token was).
	ByHost bool
	// Lang is the reader's language (srvtext tag).
	Lang string
}

Target is whose templates a render uses, and in what language.

Directories

Path Synopsis
Package engine is the Liquid dialect filex's editable templates are written in: the mail and public-page templates an administrator can change (internal/templates).
Package engine is the Liquid dialect filex's editable templates are written in: the mail and public-page templates an administrator can change (internal/templates).
Package htmlx is what happens to a rendered template's HTML before anybody sees it: Sanitize (no script can come out of a template), InlineCSS (a mail's stylesheet written into its elements, the way mail programs read it) and Text (a mail's plain-text version, made from its HTML).
Package htmlx is what happens to a rendered template's HTML before anybody sees it: Sanitize (no script can come out of a template), InlineCSS (a mail's stylesheet written into its elements, the way mail programs read it) and Text (a mail's plain-text version, made from its HTML).
Package sandbox renders a template somebody EDITED: the Liquid dialect of internal/templates/engine compiled to WebAssembly (guest/, built by build.sh into engine.wasm.gz) and run in the pure-Go wazero runtime, the way thumb/svgwasm draws an SVG.
Package sandbox renders a template somebody EDITED: the Liquid dialect of internal/templates/engine compiled to WebAssembly (guest/, built by build.sh into engine.wasm.gz) and run in the pure-Go wazero runtime, the way thumb/svgwasm draws an SVG.

Jump to

Keyboard shortcuts

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