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
- Variables
- func Default(key, part string) string
- func HTML(s string) any
- func Send(ctx context.Context, r *Renderer, m MessageSender, to string, t Target, ...) error
- type Brand
- type Def
- type Draft
- type Kind
- type Mail
- type MessageSender
- type Preview
- type Renderer
- func (r *Renderer) Forget()
- func (r *Renderer) Mail(ctx context.Context, t Target, key string, data map[string]any) (Mail, error)
- func (r *Renderer) Page(ctx context.Context, t Target, key string, data map[string]any) (string, error)
- func (r *Renderer) Preview(ctx context.Context, t Target, key string, draft *Draft) (Preview, error)
- func (r *Renderer) Scope(ctx context.Context, t Target) int64
- func (r *Renderer) SetBrand(brand func(context.Context, Target) Brand, ...)
- func (r *Renderer) Slot(ctx context.Context, t Target, key string, data map[string]any) string
- func (r *Renderer) Validate(ctx context.Context, t Target, key string, draft Draft) (Preview, error)
- type Target
Constants ¶
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).
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 ¶
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.
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.
var ErrTooLarge = fmt.Errorf("a template may be at most %d KiB", MaxSource>>10)
ErrTooLarge: a draft's text is over MaxSource.
var ErrUnknown = errors.New("there is no template with that name")
ErrUnknown: no template has that name.
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 ¶
Default is filex's own text of a template's part: "" (the body), "subject" or "text". "" when it has none.
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
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.
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 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 ¶
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.
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. |