feat(templates): rich text editor + inline images in email templates#462
Open
shukiv wants to merge 2 commits into
Open
feat(templates): rich text editor + inline images in email templates#462shukiv wants to merge 2 commits into
shukiv wants to merge 2 commits into
Conversation
Replace the plain-text Body textarea in the template editor with the existing TipTap RichTextEditor (the same one the composer uses), giving templates formatting and inline images (drag-drop / toolbar, already supported via @tiptap/extension-image). - lib/template-types: add optional `bodyIsHtml` flag. Absent/false = legacy plain text; true = HTML from the rich editor. Backward compatible. - template-form: swap textarea for RichTextEditor. Legacy plain-text bodies are converted with plainTextToSafeHtml on load; saved bodies are sanitized HTML. Placeholder insertion now lands at the cursor via the editor. Inline images are stored as base64 data URIs (templates have no send context, so no cid), capped at 1 MB each to protect the localStorage-backed template store. - email-composer: applying a template branches on `bodyIsHtml` — HTML templates are sanitized and inserted as-is (plain-text compose mode flattens them); legacy plain templates keep the previous escape-and-wrap behaviour. - i18n: add settings.templates.image_too_large to all locales (English value; non-English need translation).
Member
|
It says the send path inlines template images but it doesn't. Template images are stored as plain data: URIs and never get a cid or registered for sending, so at send time they go out as raw data: URIs. Gmail and most clients strip these, so recipients see no image. |
Per review (bulwarkmail#462): template images were stored as data: URIs and, when a template was applied to the composer, went out at send time as raw data: URIs — which Gmail/Outlook strip, so recipients saw no image. On template apply, decode each inline data: image to a File and run it through the composer's existing handleImageUpload (uploads the blob, registers it in inlineImagesRef with a cid), then tag the img with data-cid. The existing send-time rewrite then converts those to cid: refs with real inline attachments, exactly like dropped/pasted images. Moved handleImageUpload above handleTemplateSelect to satisfy the new dependency.
Contributor
Author
|
Yeah you're right, my bad. Fixed it now: when a template is applied I decode its data: images, push them through the same upload path as dropped images so each gets a real blob + cid, and tag the img with data-cid. The send path then rewrites them to cid: like normal inline images. So they actually reach the recipient now instead of going out as data: URIs. |
hamedf62
added a commit
to hamedf62/webmail
that referenced
this pull request
Jun 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a rich text editor and inline images to email templates. The template
editor's Body was a plain
<textarea>; this swaps it for the existing TipTapRichTextEditor(the same component the composer uses), so templates getformatting, links, tables, and inline images — drag-drop or toolbar, already
supported via the
@tiptap/extension-imagedependency.Why
Templates could only hold plain text, so any branded/HTML email (signatures,
logos, formatted layouts) had to be rebuilt by hand each time. Reusing the
composer's editor keeps the UX consistent and adds almost no new surface.
How
lib/template-types— new optionalbodyIsHtmlflag. Absent/false =legacy plain text; true = HTML from the rich editor. Fully backward compatible.
template-form—<textarea>→RichTextEditor. Legacy plain-text bodiesare converted with
plainTextToSafeHtmlon load so they render correctly;saved bodies are run through
sanitizeEmailHtml. Placeholder insertion nowlands at the cursor via the editor instead of appending to the end.
have no send context, so no
cidis minted — the compose/send path handlesinlining when the template is applied). Each image is capped at 1 MB to
protect the localStorage-backed template store.
email-composer— applying a template branches onbodyIsHtml: HTMLtemplates are sanitized and inserted as-is (plain-text compose mode flattens
them via
htmlToPlainText); legacy plain templates keep the previousescape-and-wrap behaviour, so existing templates are unaffected.
Backward compatibility
Existing templates have no
bodyIsHtml→ treated as plain text exactly as before.They're upgraded to HTML only when re-saved through the new editor.
Notes / follow-ups
settings.templates.image_too_largewas added to all locales with the Englishstring; non-English entries need translation.
localStorage(the template store). The 1 MB/image capkeeps a few logos well within budget; this PR does not add server-side image
hosting for templates.
Test plan
npm run typecheck,npm run lint,npm run build— all greennpm run test:translations— 38/38 (locale keys consistent)