@seliseblocks/mailcraft 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/DOCS.md +5 -5
- package/README.md +3 -0
- package/README.md.txt +3 -0
- package/dist/mailcraft-editor.bundle.js +75 -60
- package/dist/mailcraft-editor.bundle.js.map +3 -3
- package/package.json +2 -2
- package/src/core/blocks.js +30 -11
- package/src/core/css-cascade.js +120 -117
- package/src/core/editor-core.js +25 -13
- package/src/core/export.js +83 -10
- package/src/core/i18n/index.js +83 -83
- package/src/core/icons.js +0 -1
- package/src/core/ids.js +1 -1
- package/src/core/import-html.js +545 -47
- package/src/core/layout-style.js +100 -100
- package/src/core/parse.js +10 -10
- package/src/core/placeholder.js +15 -15
- package/src/core/sanitize.js +11 -1
- package/src/core/variables.js +11 -11
- package/src/mailcraft-editor.js +3 -2
- package/src/render/block-body.js +17 -5
- package/src/render/canvas.js +3 -0
- package/src/render/focus-preserve.js +158 -158
- package/src/render/story.js +415 -415
- package/src/render/style.js +7 -0
- package/types/index.d.ts +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ All notable changes to `@seliseblocks/mailcraft` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.2.10] — 2026-09-01
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **A per-block font survives a save.** Heading, text, list, button, menu and table blocks read their `font-family` back on import, so a font chosen in the inspector is still there after export → reload. A text run claims its family at block level only when every piece of the run shares it — a mixed-typography run keeps its inline declarations instead — and a claimed family is removed from the run's own HTML, so the same declaration never ships twice and a second save produces the same bytes as the first.
|
|
11
|
+
- The heading block's **Condensed** stack folds back into the Font style toggle it came from, instead of importing as an opaque per-block font.
|
|
12
|
+
- The document font is read from the `<body>` first. The element scan that stood in for it crowned the *first block's* font, so one custom-font heading at the top of an email flipped the whole theme on reload; the scan stays as the fallback for emails that declare nothing on the body.
|
|
13
|
+
- **An imported font stack is now selectable in the Font controls.** The select ships ten email-safe stacks and an imported email's stack matches none of them — a `<select>` whose value is in no option shows nothing selected, so the panel read "Inherit — theme font" about a heading really set in Georgia, and the only way to learn what font it was was to overwrite it. The document's own stack keeps a seat of its own, labelled by its first family.
|
|
14
|
+
- **Values that only restate the theme are inherit again.** The exporter writes every inherited value as a concrete declaration, so a round trip used to come back with the theme font, text ink and content background stamped onto every block and row as an explicit override — visually identical, but a later theme edit no longer reached anything. Anything genuinely different from the theme is a real override and stays.
|
|
15
|
+
- Styling that quietly reset on every save/reload is read back: a **list**'s font, size, ink, line spacing, item gap and spacing; a **table**'s font, size, width, cell padding, alignment and header tint; a **menu**'s item spacing; an **image** block's own padding; a **divider**'s spacing (a declared zero included, so a tightened divider stops springing back to 14px); a **column**'s border.
|
|
16
|
+
- Zebra striping is claimed only from the renderer's own stripe tint, and only where there are enough body rows to tell — imported tables were striped by default before, whether the source was or not.
|
|
17
|
+
- A section's **background overlay** survives. The tint ships as the exporter's own `linear-gradient(rgba(20,22,24,α)…)` over the photo; unread, the percentage vanished on every save while the image stayed. Only that exact signature is folded back — a foreign gradient says nothing about MailCraft's tint and stays out of the model.
|
|
18
|
+
- **The save → reload round trip now preserves every remaining inspector control** (a full fidelity pass against the exporter's own markup — export → import → export is byte-identical from the first reload on):
|
|
19
|
+
- A **linked image**'s width — it lives on the wrapping anchor, so every linked logo reloaded at full width.
|
|
20
|
+
- A **heading**'s line spacing and its own padding, an explicit zero included.
|
|
21
|
+
- **Social strips**: Outline and Bare no longer reload as filled Square badges (`background:transparent` is the absence of a fill, not a fill); a badge strip keeps its fill color instead of the black/white contrast ink painted over it; per-network colors fold back into the **Brand** palette; icon spacing reads off the anchors instead of the row gutter; and a strip with **Show network names** on reloads as the social block it is — it used to come back as a menu of links.
|
|
22
|
+
- A text block's **Show on** (device visibility) — recognized blocks kept it, text runs dropped it.
|
|
23
|
+
- Adjacent zero-padded **text blocks stay separate blocks**: buffered together, the second lost its size, weight — everything — on every save. A text block's declared-zero spacing sticks, too.
|
|
24
|
+
- **Rows**: vertical align; the single-column gutter (content crept 10px wider per side on every reload — a social strip's or data table's row included); the outside-margin sliders' values; and the explicit mobile modes (two-up, reverse) read back off their own classes.
|
|
25
|
+
- **Columns**: background, radius, inner padding and border now survive `loadTemplate`/`loadDoc` at all — `normalizeDoc` rebuilt every column without its styling, so a card column lost its paint before the export ever ran.
|
|
26
|
+
- The **Inline SVG block no longer vanishes** on reload (previously the sanitizer dropped the drawing and the whole row with it), and the export now ships the same width-carrying span the canvas draws, so the Width slider reaches sent mail and reads back.
|
|
27
|
+
- **Line spacing stops drifting**: the exporter ships line-height in px (Outlook needs a length), and dividing back rarely landed on the slider's value — 1.6 at 16px reloaded as 1.625, moving again on every save. Recovered ratios snap to the nearest slider step that reproduces the same pixels.
|
|
28
|
+
- Colors reload as the **hex** the picker understands, not CSSOM's `rgb(…)` serialization (button, divider, menu, social) — also what lets the theme-equality folds actually match.
|
|
29
|
+
- **`theme.link` is no longer hijacked on import** by menu items, whose navigation color outvoted the document's real links.
|
|
30
|
+
- Dynamic-content **marker rows stop picking up wrapper backgrounds** — a page background stamped onto one painted a colored band in the canvas that no sent mail would show.
|
|
31
|
+
- HTML import: a nested layout table's header-cell veto now applies only to the candidate's own row, so a **data table inside a column** keeps the gutter around it.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- **Fidelity markers.** A handful of blocks render into markup that cannot be read back into the block it came from — a countdown bakes its digits into text, a video is just a linked image, a section box and a code sample are styled divs like any other, a raw-CSS block is a bare `<style>`, and a flex/grid row is a div no table walker can re-shape. The export now stamps a compact attribute layer (`data-mc`, `data-mcp`, `data-mcr`, plus the inert `mc-keep` class) that the importer trusts when present and ignores otherwise; mail clients ignore unknown attributes wholesale. The payoff on reload: a **countdown is live again** instead of stale baked digits, video / section box / code / raw CSS keep their identity and every setting, **flex and grid rows stop collapsing to one column**, and "On mobile: Keep columns" survives. Content halves are still read from the rendered markup and sanitized like any import — a marker is data about a block, never markup to inject. Hosts that want pristine HTML can pass `exportHtml({ markers: false })` and accept the lossy (content-preserving) reload.
|
|
35
|
+
- **The theme's Link color now does something.** It was a dead control end-to-end: the canvas painted content links in the *host's* brand accent, the export shipped no color (recipients saw their client's default blue), and the importer re-guessed the value from a vote that menu items always won. Now the canvas paints sheet links from the theme, the export stamps the color inline on every anchor that has none of its own (mail clients have no stylesheet to inherit from — a hand-colored link keeps its color), the import folds restated stamps back to inherit so a later Link color edit still reaches every link, and menu/social anchors are barred from the recovery vote.
|
|
36
|
+
- **Row outside margins render in sent mail, and Max width ships at all.** Margins were exported on a `<td>`, which every major client ignores; Max width existed only on the canvas. Both now ride a wrapper `<div>` inside the cell — margins outside the painted box, exactly as the canvas draws it, auto-centered when capped — and read back on reload. Rows using neither keep the old markup byte for byte.
|
|
37
|
+
- The **raw-CSS block round-trips as itself**: it reloaded as an opaque HTML block while its rules were *also* folded inline onto every matched element, doubling the styling on each save. Marked, it comes back as the css block (note included) and the fold skips it; foreign `<style>` tags keep today's fold-inline behaviour.
|
|
38
|
+
- `test/roundtrip.test.mjs` — the round-trip fidelity suite: one assertion per inspector control (blocks, rows, columns, theme), block-identity coverage for every marked type, a byte-convergence check, and the `markers: false` degradation floor asserting no content is ever silently dropped. Runs in `npm test`.
|
|
39
|
+
|
|
40
|
+
### Removed
|
|
41
|
+
- The **Embed block**. It exported an `<iframe>`, and mail clients strip iframes wholesale — Gmail, every Outlook, Yahoo and the rest drop the tag or render a blank gap — so the block only worked in browser contexts and shipped dead weight in every real send. A document saved with one loses nothing: it reloads as a raw-HTML block holding the same iframe markup it always exported.
|
|
42
|
+
|
|
7
43
|
## [0.2.9] — 2026-09-01
|
|
8
44
|
|
|
9
45
|
### Fixed
|
package/DOCS.md
CHANGED
|
@@ -45,9 +45,9 @@ A user drags rows and blocks onto a canvas, edits text in place, styles it in an
|
|
|
45
45
|
| Runtime dependencies | none |
|
|
46
46
|
| Framework | none — a custom element works in React, Angular, Vue, Svelte and plain HTML |
|
|
47
47
|
| Isolation | Shadow DOM; the editor's CSS and the host's cannot reach each other |
|
|
48
|
-
| Size | ~
|
|
48
|
+
| Size | ~683 KB minified, ~189 KB gzipped, one file |
|
|
49
49
|
| Languages | 31, RTL automatic |
|
|
50
|
-
| Content blocks |
|
|
50
|
+
| Content blocks | 19 — text, heading, image, button, divider, spacer, social, video, countdown, menu, list, table, box, html, css, code, svg, condition, loop |
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
@@ -538,7 +538,7 @@ template, not to your app.
|
|
|
538
538
|
|
|
539
539
|
| method | returns |
|
|
540
540
|
|---|---|
|
|
541
|
-
| `exportHtml()` | send-ready email HTML |
|
|
541
|
+
| `exportHtml(options?)` | send-ready email HTML. `{ markers: false }` omits the `data-mc*` fidelity markers for pristine output — countdown, video, section box, code and raw-CSS blocks, flex/grid rows and “Keep columns” then reload lossily (content always survives) |
|
|
542
542
|
| `importHtml(html)` | number of rows produced |
|
|
543
543
|
| `loadTemplate({ name, html })` | — |
|
|
544
544
|
| `undo()` / `redo()` | — |
|
|
@@ -771,14 +771,14 @@ Useful when debugging an integration, or before changing the source.
|
|
|
771
771
|
|
|
772
772
|
**Import.** Real-world email HTML — inline and class styles, builder scaffolding, per-side borders, card columns, social strips — becomes native blocks wherever the shape is recognizable. Nested grids and `rowspan`/`colspan` survive as raw-HTML blocks: rendered and exported, not block-editable.
|
|
773
773
|
|
|
774
|
-
**Export** reads back the rendered DOM, so what the user sees is what ships. Import and export
|
|
774
|
+
**Export** reads back the rendered DOM, so what the user sees is what ships. Import and export stay round-trip compatible: re-importing an export restores every inspector setting, and exporting again reproduces the same bytes. A few blocks render into markup that cannot be read back (a countdown bakes its digits, a video is a linked image), so the export stamps a compact `data-mc*` attribute layer that the importer trusts and mail clients ignore — `exportHtml({ markers: false })` omits it for hosts that want pristine HTML, trading a lossy (but content-preserving) reload.
|
|
775
775
|
|
|
776
776
|
**Build.** `build.js` is a zero-dependency bundler that turns the ESM sources into one plain `<script>`, then minifies through esbuild with a sourcemap alongside. esbuild is a dev dependency only — consumers install nothing transitive, and a clone with no `node_modules` still produces a working bundle.
|
|
777
777
|
|
|
778
778
|
```sh
|
|
779
779
|
npm install # devDependencies only: esbuild (minify), jsdom (DOM tests), c8 (coverage)
|
|
780
780
|
node build.js # rebuild dist/ after any change under src/
|
|
781
|
-
npm test #
|
|
781
|
+
npm test # 14 suites — core logic runs DOM-free; the editor, importer and round-trip suites run on jsdom
|
|
782
782
|
```
|
|
783
783
|
|
|
784
784
|
Deeper notes for coding agents and anyone changing the source live in `AGENTS.md` in the repository.
|
package/README.md
CHANGED
|
@@ -84,6 +84,9 @@ DATA HTML in, HTML out. There is no JSON document format in the public API.
|
|
|
84
84
|
editor.importHtml(html) apply HTML (same importer)
|
|
85
85
|
editor.exportHtml() -> string send-ready email HTML
|
|
86
86
|
Persist by storing exportHtml() and passing it back to loadTemplate().
|
|
87
|
+
exportHtml({ markers: false }) omits the data-mc* fidelity markers
|
|
88
|
+
(pristine HTML; countdown/video/box/code/CSS blocks and flex/grid
|
|
89
|
+
rows then reload lossily).
|
|
87
90
|
Unclassifiable markup survives as a raw-HTML block. Nothing is dropped.
|
|
88
91
|
Condition/Loop blocks export literal {{#if expr}}/{{#each expr}} tags
|
|
89
92
|
(always balanced) for the host's templating engine; never evaluated
|
package/README.md.txt
CHANGED
|
@@ -84,6 +84,9 @@ DATA HTML in, HTML out. There is no JSON document format in the public API.
|
|
|
84
84
|
editor.importHtml(html) apply HTML (same importer)
|
|
85
85
|
editor.exportHtml() -> string send-ready email HTML
|
|
86
86
|
Persist by storing exportHtml() and passing it back to loadTemplate().
|
|
87
|
+
exportHtml({ markers: false }) omits the data-mc* fidelity markers
|
|
88
|
+
(pristine HTML; countdown/video/box/code/CSS blocks and flex/grid
|
|
89
|
+
rows then reload lossily).
|
|
87
90
|
Unclassifiable markup survives as a raw-HTML block. Nothing is dropped.
|
|
88
91
|
Condition/Loop blocks export literal {{#if expr}}/{{#each expr}} tags
|
|
89
92
|
(always balanced) for the host's templating engine; never evaluated
|