PhoenixKit. Templates. Editor
(phoenix_kit_templates v0.2.4)
View Source
A Phoenix.LiveComponent for editing a host's override files in place.
Compiled only when the host has phoenix_live_view, which this package
lists as an optional dependency: a host without it gets the renderer and
the write API and none of this. It depends on nothing else — no PhoenixKit,
no Gettext backend, no host assets. The markup is HEEx with daisyUI class
names, so a Tailwind host must scan this package's lib/ for them.
<.live_component
module={PhoenixKit.Templates.Editor}
id="email-templates"
root={MyApp.EmailTemplates.root()}
editable={true}
name_prefixes={["order_", "_header-shop", "_footer-shop"]}
locales={["et", "ru", "en"]}
preview={{MyApp.EmailPreview, :preview}}
sample_variables={%{"order_number" => "37"}}
after_write={{MyApp.EmailTemplates, :after_write}}
convert={%{to_text: {MyApp.EmailTemplates, :to_text}}}
/>Everything is read and written through PhoenixKit.Templates.Overrides
on the one root, so the renderer's cache is reset for that root after
every change — pass the same root string the host renders with.
Attributes
:root(required) — the host's template directory. Anything but a string shows an error and nothing can be written. Keep it fixed for the life of the page: the open form's notion of what the user left alone belongs to the root it was opened from.:editable—trueto allow saving, creating and deleting; anything else shows the files read-only. Defaultfalse. Checked again by every event, not only by what is rendered.:name_prefixes— which templates this editor sees and may write or delete: names starting with one of these strings. Checked on every select, save, create, copy and delete. Anything but a list counts as[], and entries that are not non-empty strings are dropped. Default[], which shows nothing.:locales— the language tabs, in order; anything but a list of non-empty strings is cleaned the way:name_prefixesis. A last tab, Fallback, edits the locale-less files (text.txt), used for any language without its own file.:preview—{module, function}or a 2-arity function called aspreview(name, locale)(localeisnilon the fallback tab, andnamemay be a shared part such as_header-x). It returns{subject, html}or{subject, html, text}— each a string ornil— or{:error, reason}. With a text version the preview has two tabs, HTML and Text, and a template opens on HTML (on Text when its HTML isnil); the text is shown as plain text. The HTML is shown in an<iframe sandbox srcdoc>withoutallow-scripts, so nothing in an edited template runs in the admin page; a{:safe, iodata}is escaped like a string, never trusted. Any other result, an exception, throw or exit is shown as an error rather than crashing the page, and the last three are logged. It is called on selecting a template or tab, after a save or copy, and when the host re-renders with a new callback, new variables or changed files — not for a template with no files yet. No callback, no preview pane.:sample_variables— the variables a template may use, as a map of name to a sample value. Listed beside the editor, and any placeholder in the current template that is not among them is flagged. The one map applies to every template, shared parts included, so a host whose headers and footers use its layout's variables ({{site_url}}and the like) lists those too.:after_write—{module, function}or a 1-arity function, called with the list of paths created by a save or a copy (a new template directory first, then files) — for example to change their owner. This package never does. The files are written before it is called; if it raises, throws or exits, that is shown as an error after the save's own result and logged with its stacktrace.:after_change—{module, function}or a 1-arity function, called with the template's name after any change on disk: a save that wrote or deleted files, a copy (the new name) or a deleted template — for example to refresh what the host shows about the files. Not called when nothing changed. Reported and logged like:after_write.:convert— the host's converters, a map with either or both keys, each{module, function}or a function. Each adds a button to the form; without them, and read-only, there are none.to_text: callback, called asto_text(:markdown, markdown)orto_text(:html, html)— the Fill text from content button: the form's Markdown if it is not blank, else its HTML, made into plain text and put in the Text field.markdown_to_html: callback, called asmarkdown_to_html(markdown)— the Markdown → HTML button: the form's Markdown rendered into the HTML field.
A converter returns a string or
{:error, reason}; anything else, an exception, throw or exit is an error, reported like the other callbacks, and the form is left as it was. It gets what is in the form, saved or not, with placeholders as typed, and should give them back as they are —{{name}}is for the message to fill in later: not substituted, and in a link target ([Pay]({{url}})) neither percent-encoded nor dropped. A Markdown renderer called with no variables may do either to a link target, so protect placeholders through it (the README's note onmarkdown). Nothing is written: the result waits in the form, the user's other unsaved fields as they were, until the user saves it.
What it does
- Lists the visible templates, captioned with their
labelpart, with names starting with_(headers, footers, layouts) in a group of their own. - Edits the
label,subject,text,markdownandhtmlparts per language tab. Saving writes the parts that changed; a part saved empty has its file deleted, so the message falls back to the next file in line — and a template left with no files loses its directory, staying open as an unsaved one. Line breaks are stored as\n. A save that is partly refused (one part too large, say) names the parts it saved and the ones it did not, and what was typed into those stays in the form to be fixed and saved again. Each tab is saved on its own: switching to another tab or template, or a reconnect, drops unsaved changes. - A part is written only when the user changed it from what the form was given (on opening the template or tab, or after a save): a part left alone is never written back over another session's change made meanwhile. An edited part wins — the last save of it, with no check that another session changed the file since. A part filled by a converter counts as edited.
- The form's buttons all submit it, Save first (the form's default
button), and a submit that names no button saves. A converter's
button is told apart by the
actionit submits, which LiveView's client sends along since 1.0 — this package's minimum. - The host's callbacks (
preview,after_write,after_change, the converters) run in the LiveView's process and block the page while they run: keep them fast. - Creates a template empty (it exists on disk once its first part is
saved) or as a copy of a listed one — every file in its directory,
including any of the host's own beside the parts
(
Overrides.copy_template/3). - A file that is not UTF-8 is shown as such, read-only, and never written over: fix it on disk.
- Deletes a template, after a confirmation.
The interface text is plain English: a host Gettext backend cannot be reached from here, and messages passed through one at runtime would never be extracted.