Skip to content

Fix preview rendering and inline text editing in email builder#5

Open
yonardr wants to merge 6 commits into
mainfrom
feature/email-builder
Open

Fix preview rendering and inline text editing in email builder#5
yonardr wants to merge 6 commits into
mainfrom
feature/email-builder

Conversation

@yonardr

@yonardr yonardr commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two independent fixes in the email builder (sendry-web). Atomic commits.

1. Preview showed grey text in dark mode

The shared email wrapper carries a @media (prefers-color-scheme: dark) block. Previews render it in an iframe, so under a dark OS theme the text became light-grey on the forced-white block background — unreadable.

Fix: strip the dark-mode block from preview output only (JS previews before srcdoc; server-rendered template preview via stripDarkModeCSS). Sent emails keep dark mode.

2. Text next to an icon/link was not editable

Inline editing was disabled for any element with a non-text child, so status text sharing a <p> with an icon (Оплачен <img>) or a link wasn't editable.

Fix: replaceTextVars now rewrites placeholders only in text content (no longer breaks href="{{.Var}}", which is neutralized to # via replaceHrefVars); <img>/<a> no longer block editing — surrounding text is wrapped in editable spans, link text is edited separately.

@yonardr yonardr force-pushed the feature/email-builder branch from 690c1d2 to 180a813 Compare June 9, 2026 00:10
@yonardr yonardr changed the title fix(web): корректное превью блоков и инлайн-редактирование текста рядом с иконками/ссылками Fix preview rendering and inline text editing in email builder Jun 9, 2026
@yonardr yonardr force-pushed the feature/email-builder branch from 180a813 to 21a3d15 Compare June 9, 2026 00:14
Rustem added 2 commits June 9, 2026 11:40
The shared wrapper's `@media (prefers-color-scheme: dark)` block made
preview text light-grey on the forced-white block background under a
dark OS theme. Strip that block from preview output only (JS previews
before `srcdoc`; server-rendered template preview via `stripDarkModeCSS`).
Sent emails are unaffected.
Inline editing was blocked for elements with a non-text child, so text
sharing a `<p>` with an icon or link wasn't editable.

- `replaceTextVars` now rewrites placeholders only in text content, so
  `href="{{.Var}}"` is no longer corrupted (neutralized to `#` via
  `replaceHrefVars`).
- `<img>`/`<a>` no longer block editing: surrounding text is wrapped in
  editable spans, link text is edited separately. Listener wiring moved
  to `bindEditable`.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets two UX issues in the Sendry email builder preview/editor: dark-mode CSS leaking into iframe previews (making forced-light previews unreadable) and inline text editing being blocked when text is adjacent to non-text children like icons/links.

Changes:

  • Strip @media (prefers-color-scheme: dark) CSS from preview output (client-side preview iframes and server-rendered template preview).
  • Update builder placeholder rewriting to avoid modifying variables inside HTML tags/attributes, and neutralize href="{{.Var}}" placeholders.
  • Adjust inline-edit enabling to allow editing text nodes in mixed-content elements by wrapping text fragments in editable spans and refactoring edit binding logic.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/web/views/template_view.html Strips dark-mode media block from rendered template preview before setting iframe.srcdoc.
internal/web/views/block_view.html Strips dark-mode media block from block preview iframe HTML.
internal/web/views/block_form.html Strips dark-mode media block from block form live preview iframe HTML.
internal/web/static/js/builder.js Updates variable placeholder rewriting and expands inline-edit support for mixed-content nodes (icon/link + text).
internal/web/handlers/templates.go Adds server-side stripDarkModeCSS for template preview output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/web/static/js/builder.js
indexOf(original) hit the first match, so editing a repeated short
fragment rewrote the wrong one. Record each fragment's source offset at
wrap time and apply edits by offset; fall back to indexOf if it drifts.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment on lines +552 to 559
edits.forEach(function(e) {
var pos = e.idx + offset;
newSourceHTML = newSourceHTML.slice(0, pos) + e.current + newSourceHTML.slice(pos + e.original.length);
offset += e.current.length - e.original.length;
e.node.dataset.originalText = e.current;
if (e.node.dataset.sourceIndex !== undefined) e.node.dataset.sourceIndex = String(e.idx);
changes++;
});

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment on lines +485 to +503
if (mixed) {
var textNodes = [];
node.childNodes.forEach(function(c) {
if (c.nodeType === 3 && c.nodeValue && c.nodeValue.trim()) textNodes.push(c);
});
textNodes.forEach(function(tn) {
var raw = tn.nodeValue;
var trimmed = raw.trim();
if (!trimmed) return;
var at = locate(trimmed);
if (at === -1) return;
var span = document.createElement('span');
span.dataset.inlineText = '1';
span.textContent = raw;
tn.parentNode.replaceChild(span, tn);
bindEditable(span, trimmed, item, previewEl, at);
});
return;
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Comment on lines +378 to +380
function replaceHrefVars(html) {
return html.replace(/(href=")(\{\{\.\w+\}\})(")/gi, '$1#$3');
}
Comment thread internal/web/views/template_view.html Outdated
if (!r.ok) return r.text().then(function(t) { throw new Error(t); });
return r.text();
}).then(function(rendered) {
rendered = rendered.replace(/@media\s*\(prefers-color-scheme:\s*dark\)\s*\{[\s\S]*?\}\s*\}/g, '');
Comment thread internal/web/views/block_view.html Outdated
if (!r.ok) return r.text().then(function(t) { throw new Error(t); });
return r.text();
}).then(function(html) {
html = html.replace(/@media\s*\(prefers-color-scheme:\s*dark\)\s*\{[\s\S]*?\}\s*\}/g, '');
Comment thread internal/web/views/block_form.html Outdated
if (!r.ok) return r.text().then(function(t) { throw new Error(t); });
return r.text();
}).then(function(html) {
html = html.replace(/@media\s*\(prefers-color-scheme:\s*dark\)\s*\{[\s\S]*?\}\s*\}/g, '');
Comment on lines +488 to +492
var darkModeBlockRe = regexp.MustCompile(`(?s)@media\s*\(prefers-color-scheme:\s*dark\)\s*\{.*?\}\s*\}`)

func stripDarkModeCSS(html string) string {
return darkModeBlockRe.ReplaceAllString(html, "")
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants