Fill placeholders when a template is inserted

Templates were a fixed subject and body, so anything that changed per
message -- who it is going to, today's date -- had to be typed over
afterwards.

Eight names are recognised: recipientName, recipientFirstName,
recipientEmail, myName, myEmail, subject, date and time. Dates and times
go through datetime.ts rather than toLocaleDateString, so a template
follows the date order and clock the app was already told to use.

Filling happens on insert rather than on send. What a placeholder came to
is then visible in the composer and can be edited, instead of the message
changing between writing it and sending it.

Two things are deliberately left alone. A placeholder that cannot be
answered yet -- a recipient's name on a draft nobody has addressed -- stays
in the body as written, because substituting an empty string produces
"Hi ,", which is wrong rather than visibly unfinished; leaving the name
says which word is still missing and can be typed over. And a name that is
not a placeholder is left as written too, since a body that quietly ate an
unrecognised token would be worse than one that shows it.

Values are escaped on the way into HTML: a display name comes from a
contact card or a typed address and is not trusted markup.
This commit is contained in:
2026-09-01 21:30:18 -07:00
parent 34578ba426
commit c85525f3ed
6 changed files with 239 additions and 3 deletions
+10 -1
View File
@@ -309,7 +309,16 @@ minimisable and maximisable; full-screen on mobile.
identity. Signature images live in Files too and are turned into inline
`cid:` parts when the message is sent.
- **Templates**: named subject + body, inserted into any draft, managed in
Settings.
Settings. Both carry **placeholders**`{{recipientName}}`,
`{{recipientFirstName}}`, `{{recipientEmail}}`, `{{myName}}`, `{{myEmail}}`,
`{{subject}}`, `{{date}}` and `{{time}}` — filled at the moment the template
is inserted, so what they came to is visible and editable before anything is
sent rather than changing under the message afterwards. Dates and times
follow the same format settings as the rest of the app. A placeholder that
cannot be answered yet — a recipient's name on a draft nobody has addressed —
is **left in the body exactly as written**, because substituting an empty
string there produces "Hi ,", a greeting that is wrong rather than one that
is visibly unfinished. A name that is not a placeholder is left alone too.
- **Attachments** by picking or dragging onto the composer, with progress per
file and the size limit the server states (`MAX_UPLOAD_BYTES`, 50 MB by
default). A pasted image is inserted inline instead, and pasted HTML is