@seliseblocks/mailcraft 0.2.8 → 0.2.9
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 +99 -0
- package/DOCS.md +27 -27
- package/README.md +1 -1
- package/README.md.txt +1 -1
- package/dist/mailcraft-editor.bundle.js +58 -56
- package/dist/mailcraft-editor.bundle.js.map +3 -3
- package/package.json +2 -1
- package/src/core/css-cascade.js +117 -117
- package/src/core/editor-core.js +57 -5
- package/src/core/i18n/index.js +83 -83
- package/src/core/ids.js +1 -1
- package/src/core/import-html.js +152 -11
- 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 +61 -0
- package/src/core/variables.js +11 -11
- package/src/mailcraft-editor.js +41 -2
- package/src/render/fields.js +28 -2
- package/src/render/focus-preserve.js +158 -158
- package/src/render/rte.js +6 -0
- package/src/render/story.js +415 -415
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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.9] — 2026-09-01
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- 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.
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
- 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.
|
|
14
|
+
- 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.
|
|
15
|
+
- 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.
|
|
16
|
+
- Empty `<p></p>` elements no longer import as phantom padded rows.
|
|
17
|
+
- 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.
|
|
18
|
+
- 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.
|
|
19
|
+
- 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.
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
- 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).
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- 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.
|
|
26
|
+
|
|
27
|
+
## [0.2.8] — 2026-09-01
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Mobile layout modes and per-device visibility handling.
|
|
31
|
+
- Open Graph card generation tooling.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- HTML import preserves the row → column → blocks structure.
|
|
35
|
+
- Docs site renders the Quick start page (`/README.html` no longer 404s).
|
|
36
|
+
|
|
37
|
+
## [0.2.7] — 2026-09-01
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- Brand artwork SVGs and theme-aware logo rendering.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- Drag and drop: dropped sections land where the indicator line shows.
|
|
44
|
+
|
|
45
|
+
## [0.2.6] — 2026-08-31
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- Round-trip: saving never mutates the design.
|
|
49
|
+
|
|
50
|
+
## [0.2.5] — 2026-08-31
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- Visible elevation on the settings panel.
|
|
54
|
+
|
|
55
|
+
## [0.2.4] — 2026-08-31
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- Marketing homepage on the docs site.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
- Slider behaviour under RTL locales.
|
|
62
|
+
|
|
63
|
+
## [0.2.3] — 2026-08-31
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- Full content-area border as a theme option.
|
|
67
|
+
|
|
68
|
+
## [0.2.2] — 2026-08-31
|
|
69
|
+
|
|
70
|
+
### Changed
|
|
71
|
+
- Locales load lazily, one file per language, instead of shipping all translations up front.
|
|
72
|
+
- Preview polish.
|
|
73
|
+
|
|
74
|
+
## [0.2.1] — 2026-08-31
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
- Canvas and chrome polish.
|
|
78
|
+
|
|
79
|
+
## [0.2.0] — 2026-08-31
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
- TypeScript declarations ship with the package.
|
|
83
|
+
- Condition and Loop dynamic-content marker blocks, exported as literal `{{#if}}` / `{{#each}}` template tags.
|
|
84
|
+
- Expanded translations and improved AI draft functionality.
|
|
85
|
+
|
|
86
|
+
### Removed
|
|
87
|
+
- Seeded example assets in the library; local library handling improved.
|
|
88
|
+
|
|
89
|
+
## [0.1.1] — 2026-08-30
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
- Documentation site; the full configuration surface documented; restyled demo.
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
- Contributing link 404; dark scrollbar on a light page.
|
|
96
|
+
|
|
97
|
+
## [0.1.0] — 2026-08-30
|
|
98
|
+
|
|
99
|
+
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
|
|
|
@@ -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>
|
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
|
|
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
|
|