@seliseblocks/mailcraft 0.2.8 → 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 +135 -0
- package/DOCS.md +32 -32
- package/README.md +4 -1
- package/README.md.txt +4 -1
- package/dist/mailcraft-editor.bundle.js +74 -57
- package/dist/mailcraft-editor.bundle.js.map +3 -3
- package/package.json +3 -2
- package/src/core/blocks.js +30 -11
- package/src/core/css-cascade.js +4 -1
- package/src/core/editor-core.js +80 -16
- package/src/core/export.js +83 -10
- package/src/core/icons.js +0 -1
- package/src/core/import-html.js +694 -55
- package/src/core/sanitize.js +72 -1
- package/src/mailcraft-editor.js +44 -4
- package/src/render/block-body.js +17 -5
- package/src/render/canvas.js +3 -0
- package/src/render/fields.js +28 -2
- package/src/render/rte.js +6 -0
- package/src/render/style.js +7 -0
- package/types/index.d.ts +10 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@seliseblocks/mailcraft` are documented here.
|
|
4
|
+
|
|
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
|
+
|
|
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
|
+
|
|
43
|
+
## [0.2.9] — 2026-09-01
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- HTML import keeps a section that paints one background image as **one row**: builder sections (Beefree, Stripo — `table.row[background-image]` holding a stack of per-block tables) previously shredded into one row per block, each stamped with its own copy of the background image, so the export re-drew the image's top slice once per block. Blocks keep their per-block padding through the merge; rows with their own background, frame or image are real band boundaries and are never merged.
|
|
47
|
+
- HTML import reads the section cell's own background color ahead of its wrapper's — a dark hero cell inside a white content table kept `#ffffff` before, so white text over a background photo sat on a white band whenever the image hadn't loaded. Legacy `bgcolor` on a `<tr>` is now read too.
|
|
48
|
+
- Imported headings inherit `color` and `text-align` from the section cell the way text runs always did — a white `<h1>` whose color lived on the `<td>` imported in the default dark ink and vanished into the hero image. An own value on the heading still wins.
|
|
49
|
+
- A bare text run with no element of its own (`<td style="font-size:30px;font-weight:800;color:#0065b2">{{Code}}</td>`) now reads its typography off the surrounding cell — verification codes and similar merge-tag cells imported at the theme default before, dropping size, weight, color and line-height on every save.
|
|
50
|
+
- A card drawn across several stacked content tables (top piece `border-radius:16px 16px 0 0; border-bottom:none`, side-borders-only middles, bottom piece `0 0 16px 16px`) no longer doubles its frame: the fragments are consumed into the one canvas frame the theme claims, while horizontal edges between sections survive as separators.
|
|
51
|
+
- Beefree-style dividers — a content-free cell whose only drawing is a `border-top`, usually in a `width="20%"` inner table — import as the divider block they draw instead of a junk text row holding a hair space.
|
|
52
|
+
- Empty `<p></p>` elements no longer import as phantom padded rows.
|
|
53
|
+
- The inspector's color swatch normalizes `rgb()`, `rgba()` and 3-digit hex before feeding the native picker, which only accepts `#rrggbb` — imported non-hex colors showed a black swatch and opened the picker at black.
|
|
54
|
+
- Text size, Text color, Line spacing, Text weight and Align now reach imported (and AI-drafted) text and list blocks whose content carries its own inline styles. Text size scales the inline hierarchy proportionally (a 15/26/15px block moved to a 45px base becomes 45/78/45); the other controls take ownership of their property, the way the Font control already did. The rewrite happens only when the control is moved, lands in a single undo step, and untouched documents are never rewritten.
|
|
55
|
+
- The rich-text toolbar's Smaller/Larger text buttons clamp to the same range as the inspector (8–96 for text, 12–120 for headings). Previously they clamped to a private 10–64, so one "Larger text" click on a 96px block shrank it to 64.
|
|
56
|
+
- The Smaller/Larger text buttons no longer appear on Section box and Raw HTML blocks, where no size is rendered — clicking them only wrote an inert `size` value into the saved document.
|
|
57
|
+
- Imported list items now pass through the same sanitizer as every other import path: event handlers, classes and `mso-*` noise are stripped while whitelisted inline typography survives.
|
|
58
|
+
- The docs site's Quick-start hero follows the site's own theme toggle instead of the OS scheme, so the colour logo shows on the light page (the near-white dark-mode wordmark no longer paints on white).
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
- Copy button in the code view's toolbar — puts the source pane on the clipboard, unsaved edits included, with a "Copied" confirmation. Localized in all 31 locales from existing strings.
|
|
62
|
+
|
|
63
|
+
## [0.2.8] — 2026-09-01
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- Mobile layout modes and per-device visibility handling.
|
|
67
|
+
- Open Graph card generation tooling.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
- HTML import preserves the row → column → blocks structure.
|
|
71
|
+
- Docs site renders the Quick start page (`/README.html` no longer 404s).
|
|
72
|
+
|
|
73
|
+
## [0.2.7] — 2026-09-01
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
- Brand artwork SVGs and theme-aware logo rendering.
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
- Drag and drop: dropped sections land where the indicator line shows.
|
|
80
|
+
|
|
81
|
+
## [0.2.6] — 2026-08-31
|
|
82
|
+
|
|
83
|
+
### Fixed
|
|
84
|
+
- Round-trip: saving never mutates the design.
|
|
85
|
+
|
|
86
|
+
## [0.2.5] — 2026-08-31
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
- Visible elevation on the settings panel.
|
|
90
|
+
|
|
91
|
+
## [0.2.4] — 2026-08-31
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
- Marketing homepage on the docs site.
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
- Slider behaviour under RTL locales.
|
|
98
|
+
|
|
99
|
+
## [0.2.3] — 2026-08-31
|
|
100
|
+
|
|
101
|
+
### Added
|
|
102
|
+
- Full content-area border as a theme option.
|
|
103
|
+
|
|
104
|
+
## [0.2.2] — 2026-08-31
|
|
105
|
+
|
|
106
|
+
### Changed
|
|
107
|
+
- Locales load lazily, one file per language, instead of shipping all translations up front.
|
|
108
|
+
- Preview polish.
|
|
109
|
+
|
|
110
|
+
## [0.2.1] — 2026-08-31
|
|
111
|
+
|
|
112
|
+
### Changed
|
|
113
|
+
- Canvas and chrome polish.
|
|
114
|
+
|
|
115
|
+
## [0.2.0] — 2026-08-31
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
- TypeScript declarations ship with the package.
|
|
119
|
+
- Condition and Loop dynamic-content marker blocks, exported as literal `{{#if}}` / `{{#each}}` template tags.
|
|
120
|
+
- Expanded translations and improved AI draft functionality.
|
|
121
|
+
|
|
122
|
+
### Removed
|
|
123
|
+
- Seeded example assets in the library; local library handling improved.
|
|
124
|
+
|
|
125
|
+
## [0.1.1] — 2026-08-30
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
- Documentation site; the full configuration surface documented; restyled demo.
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
- Contributing link 404; dark scrollbar on a light page.
|
|
132
|
+
|
|
133
|
+
## [0.1.0] — 2026-08-30
|
|
134
|
+
|
|
135
|
+
Initial release: a drag-and-drop HTML email editor packaged as a zero-dependency Web Component — HTML-only host contract, configurable top bar, `createEditor` mount API, minified single-file build, and a test suite covering the core, render layer and keyboard shortcuts.
|
package/DOCS.md
CHANGED
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
- [Why it is built this way](#why-it-is-built-this-way)
|
|
9
9
|
- [How to use it](#how-to-use-it)
|
|
10
10
|
- [Install](#install)
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
11
|
+
- [Mount the editor](#mount-the-editor)
|
|
12
|
+
- [Wire it into a framework](#wire-it-into-a-framework)
|
|
13
|
+
- [Save and restore](#save-and-restore)
|
|
14
|
+
- [Load templates](#load-templates)
|
|
15
|
+
- [Add merge variables](#add-merge-variables)
|
|
16
|
+
- [Accept image uploads](#accept-image-uploads)
|
|
17
|
+
- [Choose what the top bar shows](#choose-what-the-top-bar-shows)
|
|
18
|
+
- [Use the keyboard shortcuts](#use-the-keyboard-shortcuts)
|
|
19
|
+
- [Configure the footer strip](#configure-the-footer-strip)
|
|
20
|
+
- [Set language, direction and theme](#set-language-direction-and-theme)
|
|
21
|
+
- [Match your app's typography](#match-your-apps-typography)
|
|
22
|
+
- [Match your brand color](#match-your-brand-color)
|
|
23
23
|
- [API reference](#api-reference)
|
|
24
24
|
- [How it works inside](#how-it-works-inside)
|
|
25
25
|
|
|
@@ -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
|
|
|
@@ -55,7 +55,7 @@ A user drags rows and blocks onto a canvas, edits text in place, styles it in an
|
|
|
55
55
|
|
|
56
56
|
Four decisions shape the whole API. Each is a constraint the host would otherwise inherit.
|
|
57
57
|
|
|
58
|
-
### 1. HTML in, HTML out — no document format
|
|
58
|
+
### 1. HTML in, HTML out — there is no document format
|
|
59
59
|
|
|
60
60
|
The obvious design is to expose the editor's internal document as JSON and let hosts store that. It is also the one that ages worst: the moment a host persists that JSON, its shape becomes a public contract. Every new block type, every renamed prop, every changed default becomes a migration the host has to run against rows in its own database.
|
|
61
61
|
|
|
@@ -101,7 +101,7 @@ Validation runs *before* the provider is called, so a rejected file never reache
|
|
|
101
101
|
|
|
102
102
|
An editor embedded in a product that already has a header, a breadcrumb and a Save button ends up with two bars and two logos stacked on each other. So the top bar is switchable down to the individual control, and can be removed entirely.
|
|
103
103
|
|
|
104
|
-
Most of what the bar does survives without it. Undo, redo and export are element methods; the screenshot methods never needed the bar at all; and the keyboard shortcuts are bound at the window level, not on the bar, so `Ctrl/Cmd+Z`, `Shift+Ctrl/Cmd+Z` and `Ctrl/Cmd+E` keep working either way. Three panels are opened only from the bar, though — the live **preview** overlay, the **Code** modal and the **AI draft** panel — so dropping those parts does take them out of reach. The table under [
|
|
104
|
+
Most of what the bar does survives without it. Undo, redo and export are element methods; the screenshot methods never needed the bar at all; and the keyboard shortcuts are bound at the window level, not on the bar, so `Ctrl/Cmd+Z`, `Shift+Ctrl/Cmd+Z` and `Ctrl/Cmd+E` keep working either way. Three panels are opened only from the bar, though — the live **preview** overlay, the **Code** modal and the **AI draft** panel — so dropping those parts does take them out of reach. The table under [Choose what the top bar shows](#choose-what-the-top-bar-shows) says which is which.
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
@@ -133,7 +133,7 @@ Without a bundler:
|
|
|
133
133
|
<script src="https://unpkg.com/@seliseblocks/mailcraft/dist/mailcraft-editor.bundle.js"></script>
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
##
|
|
136
|
+
## Mount the editor
|
|
137
137
|
|
|
138
138
|
**As a tag**, when the container is part of your markup:
|
|
139
139
|
|
|
@@ -183,7 +183,7 @@ It is a wrapper, not a second implementation — it creates the same element and
|
|
|
183
183
|
|
|
184
184
|
By default the editor is appended, so existing content in the container survives; pass `{ replace: true }` to empty it first.
|
|
185
185
|
|
|
186
|
-
##
|
|
186
|
+
## Wire it into a framework
|
|
187
187
|
|
|
188
188
|
The element is framework-agnostic; only the plumbing differs.
|
|
189
189
|
|
|
@@ -217,7 +217,7 @@ export function EmailEditor({ html, onSave }) {
|
|
|
217
217
|
|
|
218
218
|
**Vue / Svelte** — both set DOM properties for non-string bindings automatically, so `<mailcraft-editor :toolbar="cfg">` works as written.
|
|
219
219
|
|
|
220
|
-
##
|
|
220
|
+
## Save and restore
|
|
221
221
|
|
|
222
222
|
```js
|
|
223
223
|
// save
|
|
@@ -231,7 +231,7 @@ Editing continues where it left off. Anything the importer cannot classify into
|
|
|
231
231
|
|
|
232
232
|
The editor also autosaves to `localStorage`, scoped **per browser tab**, so two tabs are two independent documents. That is a convenience for reload, not your persistence layer.
|
|
233
233
|
|
|
234
|
-
##
|
|
234
|
+
## Load templates
|
|
235
235
|
|
|
236
236
|
Templates are host content **and** host UI. The editor ships no catalogue and has no Templates tab — you render your own picker and push the choice in:
|
|
237
237
|
|
|
@@ -241,7 +241,7 @@ editor.loadTemplate({ name: 'Welcome', html });
|
|
|
241
241
|
|
|
242
242
|
Applying one is a normal undoable edit with a toast, and your string is never mutated. Ready-made examples ship in the package under `examples/templates/`, with a working picker in [`examples/vanilla.html`](examples/vanilla.html) — both host-app content, shipped in the package for you to lift but not part of its API.
|
|
243
243
|
|
|
244
|
-
##
|
|
244
|
+
## Add merge variables
|
|
245
245
|
|
|
246
246
|
```html
|
|
247
247
|
<mailcraft-editor variables="first_name,company,unsubscribe_url"></mailcraft-editor>
|
|
@@ -267,7 +267,7 @@ The exporter balances the document as a whole: a stray end marker emits nothing,
|
|
|
267
267
|
|
|
268
268
|
A start marker's expression field suggests the host's `variables` in a dropdown but stays free text, since conditions and loops routinely reference names that aren't inline tokens (`order.items`, a boolean flag, whatever your engine understands).
|
|
269
269
|
|
|
270
|
-
##
|
|
270
|
+
## Accept image uploads
|
|
271
271
|
|
|
272
272
|
A provider and a `maxBytes` ceiling are both required. The package ships no files of its own: with no provider the library opens empty and holds only what is dropped into it — data URIs, kept in the local draft, which no email client renders. With a provider but no `maxBytes` every upload is refused.
|
|
273
273
|
|
|
@@ -322,7 +322,7 @@ Setting `editor.storageProvider = null` drops back to that empty local library.
|
|
|
322
322
|
|
|
323
323
|
`examples/vanilla.html` wires a working provider over IndexedDB — latency, cursor paging, server-side folders and search, uploads that survive a reload, deletes that stay deleted. It is the shape of a real integration with `fetch` swapped out, and it is where every file in the live demo's Assets modal comes from.
|
|
324
324
|
|
|
325
|
-
##
|
|
325
|
+
## Choose what the top bar shows
|
|
326
326
|
|
|
327
327
|
```html
|
|
328
328
|
<mailcraft-editor toolbar="none"></mailcraft-editor> <!-- no bar -->
|
|
@@ -340,7 +340,7 @@ The **attribute names what to keep**; the **property names what to drop**. Marku
|
|
|
340
340
|
|
|
341
341
|
`none` is the documented spelling for "no bar"; `hidden`, `off` and `false` are accepted as the same thing, and `toolbar="all"` is the explicit form of the default. The property takes `false` for no bar, and an object where only the keys set to `false` do anything — unlisted parts stay on.
|
|
342
342
|
|
|
343
|
-
###
|
|
343
|
+
### The cost of a hidden part
|
|
344
344
|
|
|
345
345
|
| part | with the bar gone |
|
|
346
346
|
|---|---|
|
|
@@ -356,7 +356,7 @@ Screenshots are unaffected by any of this: `screenshotPng()`, `previewScreenshot
|
|
|
356
356
|
|
|
357
357
|
`editor.core` reaches the rest (`core.openCode()`, `core.setState({ aiOpen: true })`, `core.setState({ previewOpen: true })`), but `EditorCore` is internal — its shape is free to change between versions. Keep `code`, `ai` or `preview` in the bar if you need them.
|
|
358
358
|
|
|
359
|
-
##
|
|
359
|
+
## Use the keyboard shortcuts
|
|
360
360
|
|
|
361
361
|
Bound at the window level while the editor is connected, so they work with any bar configuration, including none.
|
|
362
362
|
|
|
@@ -374,7 +374,7 @@ Typing is never hijacked: inside a form field, `Ctrl/Cmd+Z` is the browser's own
|
|
|
374
374
|
|
|
375
375
|
One caveat if your app binds the same keys: the handler asks whether the event came from a text field, not whether it came from inside the editor. `Ctrl/Cmd+E` anywhere on the page opens the export dialog, and `Backspace` on a non-field element elsewhere in your UI deletes the editor's selected block. An editor kept on a route of its own never notices; one sitting beside your own keyboard-driven UI might, and the fix is to `stopPropagation()` on the keydowns you own before they reach `window`.
|
|
376
376
|
|
|
377
|
-
##
|
|
377
|
+
## Configure the footer strip
|
|
378
378
|
|
|
379
379
|
The editor carries a one-line attribution along the bottom of the shell:
|
|
380
380
|
|
|
@@ -411,7 +411,7 @@ editor.messages = { 'footer.poweredBy': 'Powered by Acme' };
|
|
|
411
411
|
|
|
412
412
|
Hiding the strip collapses its row — the canvas keeps every pixel it had.
|
|
413
413
|
|
|
414
|
-
##
|
|
414
|
+
## Set language, direction and theme
|
|
415
415
|
|
|
416
416
|
```html
|
|
417
417
|
<mailcraft-editor locale="de" theme="dark"></mailcraft-editor>
|
|
@@ -429,7 +429,7 @@ editor.messages = { 'action.export': 'Send to campaign' };
|
|
|
429
429
|
|
|
430
430
|
Every key, with its English default, is listed in [Every message key](#every-message-key). English is always the floor: a key your table leaves out shows the built-in English rather than a gap, and `missingKeys(yourTable)` tells you what a full replacement still lacks.
|
|
431
431
|
|
|
432
|
-
##
|
|
432
|
+
## Match your app's typography
|
|
433
433
|
|
|
434
434
|
```html
|
|
435
435
|
<mailcraft-editor ui-font="inherit"></mailcraft-editor>
|
|
@@ -438,7 +438,7 @@ Every key, with its English default, is listed in [Every message key](#every-mes
|
|
|
438
438
|
|
|
439
439
|
Editor chrome only — never the fonts inside the email being edited.
|
|
440
440
|
|
|
441
|
-
##
|
|
441
|
+
## Match your brand color
|
|
442
442
|
|
|
443
443
|
```html
|
|
444
444
|
<mailcraft-editor accent="#e11d48"></mailcraft-editor>
|
|
@@ -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
|
@@ -59,7 +59,7 @@ Exported HTML is valid input to `loadTemplate`, so saving the export *is* saving
|
|
|
59
59
|
|
|
60
60
|
Every attribute is also a property. TypeScript declarations ship with the package. Full reference and integration recipes: **[DOCS.md](DOCS.md)**.
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## Paste into your AI agent
|
|
63
63
|
|
|
64
64
|
Everything an assistant needs to wire this up correctly:
|
|
65
65
|
|
|
@@ -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
|
@@ -59,7 +59,7 @@ Exported HTML is valid input to `loadTemplate`, so saving the export *is* saving
|
|
|
59
59
|
|
|
60
60
|
Every attribute is also a property. TypeScript declarations ship with the package. Full reference and integration recipes: **[DOCS.md](DOCS.md)**.
|
|
61
61
|
|
|
62
|
-
##
|
|
62
|
+
## Paste into your AI agent
|
|
63
63
|
|
|
64
64
|
Everything an assistant needs to wire this up correctly:
|
|
65
65
|
|
|
@@ -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
|