best-email-builder 0.13.0 → 0.13.1
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 +7 -0
- package/dist/ai.js +2 -2
- package/dist/builder.js +5 -5
- package/dist/{chunk-6QTSXJG4.js → chunk-2IDNT724.js} +2 -2
- package/dist/{chunk-G7G3TRWG.js → chunk-EKQA3YAI.js} +5 -5
- package/dist/{chunk-Q5UPFM7J.js → chunk-NPE7WFT5.js} +2 -2
- package/dist/{chunk-Q5UPFM7J.js.map → chunk-NPE7WFT5.js.map} +1 -1
- package/dist/{chunk-Q2ZUJFOY.js → chunk-P6IEVWZY.js} +2 -2
- package/dist/{chunk-LSOR4OII.js → chunk-RVRKAXYC.js} +3 -3
- package/dist/{chunk-JJ56652Y.js → chunk-SYQC7JNA.js} +9 -5
- package/dist/chunk-SYQC7JNA.js.map +1 -0
- package/dist/index.js +6 -6
- package/dist/library.js +4 -4
- package/dist/server.js +2 -2
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-JJ56652Y.js.map +0 -1
- /package/dist/{chunk-6QTSXJG4.js.map → chunk-2IDNT724.js.map} +0 -0
- /package/dist/{chunk-G7G3TRWG.js.map → chunk-EKQA3YAI.js.map} +0 -0
- /package/dist/{chunk-Q2ZUJFOY.js.map → chunk-P6IEVWZY.js.map} +0 -0
- /package/dist/{chunk-LSOR4OII.js.map → chunk-RVRKAXYC.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ All notable changes are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.13.1] — 2026-10-20
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Deleting a template in the library selects the next one of the list (the
|
|
13
|
+
one before it when it was the last). Deleting the first template showed
|
|
14
|
+
*Template not found*, and deleting another one jumped to the first.
|
|
15
|
+
|
|
9
16
|
## [0.13.0] — 2026-10-20
|
|
10
17
|
|
|
11
18
|
### Added
|
package/dist/ai.js
CHANGED
package/dist/builder.js
CHANGED
|
@@ -5,19 +5,19 @@ import {
|
|
|
5
5
|
TEMPLATE_GALLERY,
|
|
6
6
|
buildGallery,
|
|
7
7
|
createStarterMjml
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-EKQA3YAI.js";
|
|
9
|
+
import "./chunk-RVRKAXYC.js";
|
|
10
|
+
import "./chunk-P6IEVWZY.js";
|
|
11
11
|
import "./chunk-UFKCLU2D.js";
|
|
12
12
|
import "./chunk-UGF3YJ64.js";
|
|
13
13
|
import {
|
|
14
14
|
StorageError,
|
|
15
15
|
createHttpStorage,
|
|
16
16
|
createMemoryStorage
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2IDNT724.js";
|
|
18
18
|
import {
|
|
19
19
|
EMAIL_BUILDER_VERSION
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-NPE7WFT5.js";
|
|
21
21
|
import "./chunk-A4KF7NMP.js";
|
|
22
22
|
import {
|
|
23
23
|
CUSTOM_GROUP,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
readDocument,
|
|
8
8
|
sortTags,
|
|
9
9
|
validateTag
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-NPE7WFT5.js";
|
|
11
11
|
import {
|
|
12
12
|
DEFAULT_ASSETS_PATH,
|
|
13
13
|
DEFAULT_VARIABLES,
|
|
@@ -948,4 +948,4 @@ export {
|
|
|
948
948
|
createHttpStorage,
|
|
949
949
|
createMemoryStorage
|
|
950
950
|
};
|
|
951
|
-
//# sourceMappingURL=chunk-
|
|
951
|
+
//# sourceMappingURL=chunk-2IDNT724.js.map
|
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
useLayoutWidth,
|
|
7
7
|
useSearchShortcut,
|
|
8
8
|
withThumbnailImages
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-RVRKAXYC.js";
|
|
10
10
|
import {
|
|
11
11
|
BRAND_NOTES,
|
|
12
12
|
countImagesWithoutPicture,
|
|
13
13
|
logoWidthFor
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-P6IEVWZY.js";
|
|
15
15
|
import {
|
|
16
16
|
cleanRichText,
|
|
17
17
|
escapeText,
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
createHttpStorage,
|
|
52
52
|
useT,
|
|
53
53
|
useUi
|
|
54
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-2IDNT724.js";
|
|
55
55
|
import {
|
|
56
56
|
DEFAULT_BUTTON_URL,
|
|
57
57
|
EMAIL_BUILDER_VERSION,
|
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
setAttributes,
|
|
95
95
|
setContent,
|
|
96
96
|
toProject
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-NPE7WFT5.js";
|
|
98
98
|
import {
|
|
99
99
|
isSafeUrl
|
|
100
100
|
} from "./chunk-A4KF7NMP.js";
|
|
@@ -6341,4 +6341,4 @@ export {
|
|
|
6341
6341
|
EmailBuilder,
|
|
6342
6342
|
EmailBuilderBoundary
|
|
6343
6343
|
};
|
|
6344
|
-
//# sourceMappingURL=chunk-
|
|
6344
|
+
//# sourceMappingURL=chunk-EKQA3YAI.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-ZTUW25BG.js";
|
|
4
4
|
|
|
5
5
|
// src/core/constants.js
|
|
6
|
-
var EMAIL_BUILDER_VERSION = "0.13.
|
|
6
|
+
var EMAIL_BUILDER_VERSION = "0.13.1";
|
|
7
7
|
var EMAIL_DOCUMENT_SCHEMA_VERSION = 1;
|
|
8
8
|
var EMAIL_DEVICES = ["desktop", "mobile"];
|
|
9
9
|
var EMAIL_MOBILE_WIDTH = 375;
|
|
@@ -872,4 +872,4 @@ export {
|
|
|
872
872
|
checkTemplateTags,
|
|
873
873
|
sortTags
|
|
874
874
|
};
|
|
875
|
-
//# sourceMappingURL=chunk-
|
|
875
|
+
//# sourceMappingURL=chunk-NPE7WFT5.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/constants.js","../src/core/document.js","../src/engine/model.js","../src/engine/mjml.js","../src/blocks/shared.js","../src/features/brand/brand.js","../src/features/tags/tags.js","../src/validation/images.js"],"sourcesContent":["// @ts-check\n/**\n * Email builder constants.\n *\n * `EMAIL_BUILDER_VERSION` versions THIS package (UI, blocks, rendering);\n * `EMAIL_DOCUMENT_SCHEMA_VERSION` versions the stored JSON envelope (the\n * project JSON). Both are written with every template, so a document read by\n * a newer version knows whether it must be migrated (see `core/document.js`).\n */\n\nexport const EMAIL_BUILDER_VERSION = '0.13.0';\n\nexport const EMAIL_DOCUMENT_SCHEMA_VERSION = 1;\n\n/** @typedef {'desktop'|'mobile'} EmailDevice */\n\n/** @type {EmailDevice[]} */\nexport const EMAIL_DEVICES = ['desktop', 'mobile'];\n\n/** Content column width — the email client standard. */\nexport const EMAIL_CONTENT_WIDTH = 600;\n\n/** Mobile preview frame width (standard iPhone, the common worst case). */\nexport const EMAIL_MOBILE_WIDTH = 375;\n","// @ts-check\n/**\n * DOCUMENT envelope — what gets stored (the project JSON).\n *\n * HTML is only a compilation output; what gets edited is the engine's\n * project. It is not stored bare but wrapped with:\n *\n * schemaVersion the shape of THIS envelope (migrations, see MIGRATIONS);\n * engine the engine that can read `project` back;\n * engineVersion its version at write time;\n * builderVersion the builder version that wrote it;\n * project the engine data, opaque to the rest of the code.\n *\n * A document from another engine, a future schema or a malformed one is\n * REJECTED on read rather than half-loaded: a template silently emptied and\n * then saved again would be lost.\n *\n * Dependency-free module: testable outside the browser.\n */\n\nimport { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION } from './constants.js';\n\n/**\n * @typedef {{\n * schemaVersion: number,\n * engine: string,\n * engineVersion: string,\n * builderVersion: string,\n * project: Record<string, any>,\n * }} EmailDocument\n */\n\n/**\n * Envelope migrations, from N to N+1. Empty while only one schema exists; the\n * first entry will be `1: (doc) => ({ ...doc, schemaVersion: 2 })`.\n *\n * @type {Record<number, (document: any) => any>}\n */\nconst MIGRATIONS = {};\n\nexport class EmailDocumentError extends Error {\n /** @param {string} message */\n constructor(message) {\n super(message);\n this.name = 'EmailDocumentError';\n this.status = 422;\n }\n}\n\nfunction isPlainObject(value) {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * @param {{engine: string, engineVersion: string, project: object}} parts\n * @returns {EmailDocument}\n */\nexport function createDocument({ engine, engineVersion, project }) {\n if (!engine) throw new EmailDocumentError('Document: missing engine');\n if (!isPlainObject(project)) throw new EmailDocumentError('Document: invalid engine project');\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine,\n engineVersion: String(engineVersion || ''),\n builderVersion: EMAIL_BUILDER_VERSION,\n project: /** @type {Record<string, any>} */ (project),\n };\n}\n\n/**\n * Reads a document (object or JSON string), migrates it to the current schema\n * and validates it.\n *\n * @param {unknown} raw\n * @returns {EmailDocument}\n * @throws {EmailDocumentError}\n */\nexport function readDocument(raw) {\n let value = raw;\n if (typeof value === 'string') {\n try {\n value = JSON.parse(value);\n } catch {\n throw new EmailDocumentError('Unreadable document: invalid JSON');\n }\n }\n if (!isPlainObject(value)) throw new EmailDocumentError('Unreadable document: object expected');\n\n /** @type {any} */\n let document = { ...(/** @type {object} */ (value)) };\n const version = Number(document.schemaVersion);\n if (!Number.isInteger(version) || version < 1) {\n throw new EmailDocumentError('Unreadable document: missing schema version');\n }\n if (version > EMAIL_DOCUMENT_SCHEMA_VERSION) {\n throw new EmailDocumentError(\n `Document written by a newer version of the builder (schema ${version}) — ` +\n 'reload the page'\n );\n }\n for (let v = version; v < EMAIL_DOCUMENT_SCHEMA_VERSION; v += 1) {\n const migrate = MIGRATIONS[v];\n if (!migrate) throw new EmailDocumentError(`Document: no migration from schema ${v}`);\n document = migrate(document);\n }\n\n if (typeof document.engine !== 'string' || !document.engine) {\n throw new EmailDocumentError('Unreadable document: missing engine');\n }\n if (!isPlainObject(document.project)) {\n throw new EmailDocumentError('Unreadable document: missing engine project');\n }\n\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine: document.engine,\n engineVersion: String(document.engineVersion || ''),\n builderVersion: String(document.builderVersion || ''),\n project: document.project,\n };\n}\n\n/** @param {EmailDocument} document */\nexport function serializeDocument(document) {\n return JSON.stringify(readDocument(document));\n}\n","// @ts-check\n/**\n * THE EMAIL MODEL — the builder's own document, an MJML tree with ids.\n *\n * { id, tag: 'mj-text', attributes: { color: '#2b3038' }, children: [], content: 'Hello' }\n *\n * One node per MJML element. \"Ending\" elements (`mj-text`, `mj-button`…)\n * keep their inner HTML in `content`; the others hold `children`. Ids are\n * opaque, unique within a tree, made of `[a-z0-9]` so they can live in a CSS\n * class (the canvas finds an element by them, see `canvasMjml.js`).\n *\n * Every operation is PURE: it returns a new tree and never changes the one it\n * got, sharing the untouched branches. The undo history is then just a list\n * of trees.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\n/**\n * @typedef {{\n * id: string,\n * tag: string,\n * attributes: Record<string, string>,\n * children: EmailNode[],\n * content: string|null,\n * }} EmailNode\n */\n\n/** MJML elements whose inner content is HTML (MJML calls them \"ending tags\"). */\nexport const ENDING_TAGS = new Set([\n 'mj-text',\n 'mj-button',\n 'mj-navbar-link',\n 'mj-social-element',\n 'mj-table',\n 'mj-raw',\n 'mj-title',\n 'mj-preview',\n 'mj-style',\n 'mj-accordion-title',\n 'mj-accordion-text',\n]);\n\n/** Elements that hold a whole section's place in the body. */\nexport const SECTION_TAGS = new Set(['mj-section', 'mj-wrapper', 'mj-hero']);\n\n/** Elements that are never removed, moved or duplicated. */\nexport const LOCKED_TAGS = new Set(['mjml', 'mj-head', 'mj-body']);\n\nlet counter = 0;\n\n/** A fresh id: short, unique for the page's lifetime, `[a-z0-9]` only. */\nexport function createId() {\n counter = (counter + 1) % 1679616; // 36^4\n return `n${Date.now().toString(36).slice(-5)}${counter.toString(36).padStart(4, '0')}${Math.floor(Math.random() * 1296)\n .toString(36)\n .padStart(2, '0')}`;\n}\n\n/**\n * @param {string} tag\n * @param {{attributes?: Record<string, string>, children?: EmailNode[], content?: string|null, id?: string}} [parts]\n * @returns {EmailNode}\n */\nexport function createNode(tag, { attributes = {}, children = [], content = null, id = createId() } = {}) {\n return { id, tag, attributes: { ...attributes }, children: [...children], content: ENDING_TAGS.has(tag) ? String(content ?? '') : null };\n}\n\n/**\n * Deep copy with NEW ids everywhere (duplicate, paste, insert a block twice).\n * @param {EmailNode} node\n * @returns {EmailNode}\n */\nexport function cloneWithNewIds(node) {\n return {\n id: createId(),\n tag: node.tag,\n attributes: { ...node.attributes },\n children: node.children.map(cloneWithNewIds),\n content: node.content,\n };\n}\n\n/* ── Reading ─────────────────────────────────────────────────────────────── */\n\n/**\n * The node with this id and its ancestors (root first), or null.\n * @param {EmailNode} root @param {string} id\n * @returns {EmailNode[]|null}\n */\nexport function pathTo(root, id) {\n if (root.id === id) return [root];\n for (const child of root.children) {\n const path = pathTo(child, id);\n if (path) return [root, ...path];\n }\n return null;\n}\n\n/** @param {EmailNode} root @param {string|null|undefined} id */\nexport function findNode(root, id) {\n if (!id) return null;\n const path = pathTo(root, id);\n return path ? path[path.length - 1] : null;\n}\n\n/** @param {EmailNode} root @param {string} id */\nexport function parentOf(root, id) {\n const path = pathTo(root, id);\n return path && path.length > 1 ? path[path.length - 2] : null;\n}\n\n/**\n * Closest node (itself included) whose tag is one of `tags`.\n * @param {EmailNode} root @param {string} id @param {Set<string>|string[]} tags\n */\nexport function closestOfTags(root, id, tags) {\n const set = tags instanceof Set ? tags : new Set(tags);\n const path = pathTo(root, id);\n if (!path) return null;\n for (let i = path.length - 1; i >= 0; i -= 1) if (set.has(path[i].tag)) return path[i];\n return null;\n}\n\n/** First node of a tag, depth first. @param {EmailNode} root @param {string} tag @returns {EmailNode|null} */\nexport function findFirst(root, tag) {\n if (root.tag === tag) return root;\n for (const child of root.children) {\n const found = findFirst(child, tag);\n if (found) return found;\n }\n return null;\n}\n\n/** Every node, depth first. @param {EmailNode} root @param {(node: EmailNode) => void} visit */\nexport function walk(root, visit) {\n visit(root);\n for (const child of root.children) walk(child, visit);\n}\n\n/* ── Writing (pure) ──────────────────────────────────────────────────────── */\n\n/**\n * The tree with the node `id` replaced by `update(node)`.\n * @param {EmailNode} root @param {string} id @param {(node: EmailNode) => EmailNode} update\n * @returns {EmailNode}\n */\nexport function updateNode(root, id, update) {\n if (root.id === id) return update(root);\n let changed = false;\n const children = root.children.map((child) => {\n const next = updateNode(child, id, update);\n if (next !== child) changed = true;\n return next;\n });\n return changed ? { ...root, children } : root;\n}\n\n/**\n * Sets (string) or removes (null, '') attributes.\n * @param {EmailNode} root @param {string} id @param {Record<string, string|null|undefined>} attributes\n */\nexport function setAttributes(root, id, attributes) {\n return updateNode(root, id, (node) => {\n const next = { ...node.attributes };\n for (const [key, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === '') delete next[key];\n else next[key] = String(value);\n }\n return { ...node, attributes: next };\n });\n}\n\n/** @param {EmailNode} root @param {string} id @param {string} content */\nexport function setContent(root, id, content) {\n return updateNode(root, id, (node) => (node.content === null ? node : { ...node, content: String(content ?? '') }));\n}\n\n/**\n * Inserts nodes into `parentId` at `index` (clamped).\n * @param {EmailNode} root @param {string} parentId @param {number} index @param {EmailNode[]} nodes\n */\nexport function insertNodes(root, parentId, index, nodes) {\n return updateNode(root, parentId, (parent) => {\n const at = Math.max(0, Math.min(index, parent.children.length));\n const children = [...parent.children];\n children.splice(at, 0, ...nodes);\n return { ...parent, children };\n });\n}\n\n/** @param {EmailNode} root @param {string} id */\nexport function removeNode(root, id) {\n const parent = parentOf(root, id);\n if (!parent) return root;\n return updateNode(root, parent.id, (p) => ({ ...p, children: p.children.filter((c) => c.id !== id) }));\n}\n\n/**\n * Moves a node under `parentId` at `index`, where `index` counts the target's\n * children BEFORE the node leaves its place (what a drop indicator shows).\n * @param {EmailNode} root @param {string} id @param {string} parentId @param {number} index\n */\nexport function moveNode(root, id, parentId, index) {\n const node = findNode(root, id);\n const from = parentOf(root, id);\n if (!node || !from) return root;\n // A node cannot go inside itself.\n if (pathTo(node, parentId)) return root;\n let at = index;\n if (from.id === parentId) {\n const current = from.children.findIndex((c) => c.id === id);\n if (current < at) at -= 1;\n if (current === at) return root;\n }\n return insertNodes(removeNode(root, id), parentId, at, [node]);\n}\n\n/**\n * Index of a node among its siblings, and their count.\n * @param {EmailNode} root @param {string} id\n */\nexport function positionOf(root, id) {\n const parent = parentOf(root, id);\n if (!parent) return { parent: null, index: -1, count: 0 };\n return { parent, index: parent.children.findIndex((c) => c.id === id), count: parent.children.length };\n}\n\n/* ── Snapshot (the stored project) ───────────────────────────────────────── */\n\n/** Shape version of the stored project (`{ format, root }`). */\nexport const PROJECT_FORMAT = 1;\n\n/** @param {EmailNode} root */\nexport function toProject(root) {\n return { format: PROJECT_FORMAT, root: strip(root) };\n}\n\n/** @param {EmailNode} node @returns {any} */\nfunction strip(node) {\n /** @type {any} */\n const out = { id: node.id, tag: node.tag };\n if (Object.keys(node.attributes).length) out.attributes = { ...node.attributes };\n if (node.content !== null) out.content = node.content;\n if (node.children.length) out.children = node.children.map(strip);\n return out;\n}\n\n/**\n * Reads a stored project back into a tree. Ids are kept when they are valid\n * and unique, replaced otherwise. Throws on anything that is not a project of\n * this format.\n * @param {unknown} project\n * @returns {EmailNode}\n */\nexport function fromProject(project) {\n const value = /** @type {any} */ (project);\n if (!value || typeof value !== 'object' || value.format !== PROJECT_FORMAT || !value.root) {\n throw new Error('Not a project of this builder');\n }\n const seen = new Set();\n /** @param {any} raw @returns {EmailNode} */\n const read = (raw) => {\n if (!raw || typeof raw !== 'object' || typeof raw.tag !== 'string' || !/^(mjml|mj-[a-z-]+)$/.test(raw.tag)) {\n throw new Error('Invalid element in the project');\n }\n let id = typeof raw.id === 'string' && /^[a-z0-9]{1,40}$/.test(raw.id) ? raw.id : createId();\n if (seen.has(id)) id = createId();\n seen.add(id);\n /** @type {Record<string, string>} */\n const attributes = {};\n for (const [k, v] of Object.entries(raw.attributes || {})) {\n if (/^[a-z][a-z0-9-]*$/i.test(k) && v !== null && v !== undefined) attributes[k] = String(v);\n }\n return createNode(raw.tag, {\n id,\n attributes,\n content: ENDING_TAGS.has(raw.tag) ? String(raw.content ?? '') : null,\n children: ENDING_TAGS.has(raw.tag) ? [] : (Array.isArray(raw.children) ? raw.children : []).map(read),\n });\n };\n const root = read(value.root);\n if (root.tag !== 'mjml' || !findFirst(root, 'mj-body')) throw new Error('The project has no email body');\n return root;\n}\n","// @ts-check\n/**\n * MJML ⇄ MODEL — reading MJML into the builder's tree, writing it back.\n *\n * MJML is close enough to XML to be read by a small scanner, and only two\n * kinds of element exist:\n * - containers (`mj-section`, `mj-column`…), made of other MJML elements;\n * - \"ending\" elements (`mj-text`, `mj-button`…), whose inside is HTML kept\n * as is, up to the matching closing tag.\n * Anything else between containers (stray text, comments, HTML outside an\n * ending element) is not MJML and is dropped.\n *\n * Attribute values are DECODED in the model (`&` → `&`) and encoded again\n * on write: the model holds what the attribute means, the MJML how to write\n * it.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { ENDING_TAGS, createNode, findFirst } from './model.js';\n\n/** @typedef {import('./model.js').EmailNode} EmailNode */\n\nconst NAMED_ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: ' ' };\n\n/** @param {string} value */\nexport function decodeAttribute(value) {\n return value.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (match, entity) => {\n if (entity[0] === '#') {\n const code = entity[1] === 'x' || entity[1] === 'X' ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);\n return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : match;\n }\n const named = /** @type {Record<string, string>} */ (NAMED_ENTITIES)[entity.toLowerCase()];\n return named ?? match;\n });\n}\n\n/** @param {string} value */\nexport function encodeAttribute(value) {\n return String(value).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n}\n\n/**\n * Reads the attributes of an opening tag, from `start` (just after the name)\n * to its `>`. Quotes may contain `>`.\n * @param {string} source @param {number} start\n * @returns {{attributes: Record<string, string>, end: number, selfClosing: boolean}}\n */\nfunction readAttributes(source, start) {\n /** @type {Record<string, string>} */\n const attributes = {};\n let i = start;\n const n = source.length;\n while (i < n) {\n while (i < n && /\\s/.test(source[i])) i += 1;\n if (source[i] === '>') return { attributes, end: i + 1, selfClosing: false };\n if (source[i] === '/' && source[i + 1] === '>') return { attributes, end: i + 2, selfClosing: true };\n if (i >= n) break;\n const nameMatch = /^[^\\s=/>\"']+/.exec(source.slice(i, i + 200));\n if (!nameMatch) {\n i += 1;\n continue;\n }\n const name = nameMatch[0].toLowerCase();\n i += nameMatch[0].length;\n while (i < n && /\\s/.test(source[i])) i += 1;\n let value = '';\n if (source[i] === '=') {\n i += 1;\n while (i < n && /\\s/.test(source[i])) i += 1;\n const quote = source[i];\n if (quote === '\"' || quote === \"'\") {\n const close = source.indexOf(quote, i + 1);\n const stop = close === -1 ? n : close;\n value = source.slice(i + 1, stop);\n i = stop + 1;\n } else {\n let bare = /^[^\\s>]*/.exec(source.slice(i))?.[0] || '';\n // `width=100/>`: the slash closes the tag, it is not in the value.\n if (bare.endsWith('/') && source[i + bare.length] === '>') bare = bare.slice(0, -1);\n value = bare;\n i += bare.length;\n }\n }\n attributes[name] = decodeAttribute(value);\n }\n return { attributes, end: n, selfClosing: false };\n}\n\n/**\n * MJML elements of a fragment (no `<mjml>` needed): `<mj-section>…</mj-section><mj-section>…`.\n * @param {string} source\n * @returns {EmailNode[]}\n */\nexport function parseMjmlFragment(source) {\n const text = String(source || '').replace(/^\\uFEFF/, '');\n /** @type {EmailNode} */\n const top = createNode('mj-fragment');\n /** @type {EmailNode[]} */\n const stack = [top];\n let i = 0;\n const n = text.length;\n\n while (i < n) {\n const lt = text.indexOf('<', i);\n if (lt === -1) break;\n i = lt;\n if (text.startsWith('<!--', i)) {\n const close = text.indexOf('-->', i + 4);\n i = close === -1 ? n : close + 3;\n continue;\n }\n if (text.startsWith('<?', i) || text.startsWith('<!', i)) {\n const close = text.indexOf('>', i);\n i = close === -1 ? n : close + 1;\n continue;\n }\n const closing = text[i + 1] === '/';\n const nameMatch = /^[a-z][a-z0-9-]*/i.exec(text.slice(i + (closing ? 2 : 1), i + 60));\n if (!nameMatch) {\n i += 1;\n continue;\n }\n const tag = nameMatch[0].toLowerCase();\n const afterName = i + (closing ? 2 : 1) + nameMatch[0].length;\n const isMjml = tag === 'mjml' || tag.startsWith('mj-');\n\n if (closing) {\n const close = text.indexOf('>', afterName);\n i = close === -1 ? n : close + 1;\n if (!isMjml) continue;\n // Close up to the matching element (tolerates a forgotten closing tag).\n for (let depth = stack.length - 1; depth > 0; depth -= 1) {\n if (stack[depth].tag === tag) {\n stack.length = depth;\n break;\n }\n }\n continue;\n }\n\n const { attributes, end, selfClosing } = readAttributes(text, afterName);\n i = end;\n if (!isMjml) continue;\n\n const parent = stack[stack.length - 1];\n if (ENDING_TAGS.has(tag)) {\n let content = '';\n if (!selfClosing) {\n const closeRe = new RegExp(`</${tag}\\\\s*>`, 'ig');\n closeRe.lastIndex = i;\n const close = closeRe.exec(text);\n const stop = close ? close.index : n;\n content = text.slice(i, stop).trim();\n i = close ? close.index + close[0].length : n;\n }\n parent.children.push(createNode(tag, { attributes, content }));\n continue;\n }\n const node = createNode(tag, { attributes });\n parent.children.push(node);\n if (!selfClosing) stack.push(node);\n }\n return top.children;\n}\n\n/**\n * A whole email. Always returns an `mjml` root holding an `mj-body`: a bare\n * fragment is wrapped, a missing body is added.\n * @param {string} source\n * @returns {EmailNode}\n */\nexport function parseMjml(source) {\n const nodes = parseMjmlFragment(source);\n let root = nodes.find((node) => node.tag === 'mjml') || null;\n if (!root) {\n const body = nodes.find((node) => node.tag === 'mj-body');\n const head = nodes.find((node) => node.tag === 'mj-head');\n root = createNode('mjml', {\n children: [\n ...(head ? [head] : []),\n body || createNode('mj-body', { children: nodes.filter((node) => node.tag !== 'mj-head') }),\n ],\n });\n }\n if (!findFirst(root, 'mj-body')) root.children.push(createNode('mj-body'));\n return root;\n}\n\n/**\n * The MJML of a tree. `pretty`: one element per line, indented (what is\n * stored and downloaded); otherwise on one line.\n * @param {EmailNode} node\n * @param {{pretty?: boolean, attributes?: (node: EmailNode) => Record<string, string>, extraChildren?: (node: EmailNode) => string}} [options]\n * `attributes`: replaces a node's attributes on write (the canvas adds its\n * markers there); `extraChildren`: MJML appended inside a container.\n */\nexport function serializeMjml(node, options = {}) {\n const { pretty = true } = options;\n /** @param {EmailNode} current @param {number} depth @returns {string} */\n const write = (current, depth) => {\n const indent = pretty ? ' '.repeat(depth) : '';\n const attributes = options.attributes ? options.attributes(current) : current.attributes;\n const attrs = Object.entries(attributes)\n .map(([key, value]) => ` ${key}=\"${encodeAttribute(value)}\"`)\n .join('');\n if (current.content !== null) {\n return `${indent}<${current.tag}${attrs}>${current.content}</${current.tag}>`;\n }\n const inner = current.children.map((child) => write(child, depth + 1));\n const extra = options.extraChildren ? options.extraChildren(current) : '';\n if (extra) inner.push(pretty ? `${' '.repeat(depth + 1)}${extra}` : extra);\n if (!inner.length) return `${indent}<${current.tag}${attrs} />`;\n return pretty\n ? `${indent}<${current.tag}${attrs}>\\n${inner.join('\\n')}\\n${indent}</${current.tag}>`\n : `<${current.tag}${attrs}>${inner.join('')}</${current.tag}>`;\n };\n return write(node, 0);\n}\n","// @ts-check\n/**\n * Building blocks shared by all blocks: palette, fonts, recurring properties.\n *\n * -- DEFAULT PALETTE ---------------------------------------------------------\n * Plain hex values: email clients load no stylesheet and understand neither\n * `var()` nor modern color functions, so every value ends up inline in a\n * `style=\"\"` attribute.\n *\n * -- EVERY BLOCK SETS ITS OWN TEXT ATTRIBUTES --------------------------------\n * Font, size, line height and color are written on the element itself, never\n * left to MJML defaults (the \"Ubuntu\" font loaded from Google, line height 1):\n * the canvas and the sent email then render the same thing, and Outlook gets\n * explicit values.\n *\n * Fonts: only web-safe stacks, installed everywhere and understood by\n * Outlook - a web font only renders in a minority of email clients.\n */\n\n/** @typedef {import('../core/EmailEditor.js').BlockProperty} BlockProperty */\n\n/** Text for an MJML attribute or element. @param {string} text */\nexport function escapeXml(text) {\n return String(text ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n/** The default palette. */\nexport const PALETTE = {\n ink: '#2b3038',\n inkSoft: '#41506b',\n navy: '#212e5a',\n brand: '#2977bc',\n border: '#dfe6f0',\n canvas: '#eceff4',\n surface: '#ffffff',\n white: '#ffffff',\n};\n\n/** Default link of a new button. */\nexport const DEFAULT_BUTTON_URL = 'https://example.com';\n\nexport const FONT_STACK = 'Arial, Helvetica, sans-serif';\n\n/** @type {{value: string, label: string}[]} */\nexport const FONT_OPTIONS = [\n { value: 'Arial, Helvetica, sans-serif', label: 'Arial' },\n { value: 'Verdana, Geneva, sans-serif', label: 'Verdana' },\n { value: 'Tahoma, Geneva, sans-serif', label: 'Tahoma' },\n { value: \"'Trebuchet MS', Helvetica, sans-serif\", label: 'Trebuchet' },\n { value: 'Georgia, Times, serif', label: 'Georgia' },\n { value: \"'Times New Roman', Times, serif\", label: 'Times' },\n { value: \"'Courier New', Courier, monospace\", label: 'Courier' },\n];\n\n/** @type {{value: string, label: string}[]} */\nexport const ALIGN_OPTIONS = [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n];\n\n/** Properties panel groups, in display order. */\nexport const GROUPS = {\n content: 'Content',\n link: 'Link',\n typography: 'Typography',\n colors: 'Colors',\n layout: 'Layout',\n spacing: 'Spacing',\n};\n\n/** Recurring properties. */\nexport const prop = {\n /** @returns {BlockProperty} */\n fontFamily: () => ({ key: 'font-family', label: 'Font', type: 'select', group: GROUPS.typography, options: FONT_OPTIONS }),\n /** @returns {BlockProperty} */\n fontSize: (min = 10, max = 48) => ({ key: 'font-size', label: 'Size', type: 'size', group: GROUPS.typography, min, max, unit: 'px' }),\n /** @returns {BlockProperty} */\n lineHeight: () => ({ key: 'line-height', label: 'Line height', type: 'size', group: GROUPS.typography, min: 10, max: 80, unit: 'px' }),\n /** @returns {BlockProperty} */\n color: (label = 'Text') => ({ key: 'color', label, type: 'color', group: GROUPS.colors }),\n /** @returns {BlockProperty} */\n background: (key = 'background-color', label = 'Background') => ({ key, label, type: 'color', group: GROUPS.colors }),\n /** @returns {BlockProperty} */\n align: (options = ALIGN_OPTIONS) => ({ key: 'align', label: 'Alignment', type: 'align', group: GROUPS.layout, options }),\n /** @returns {BlockProperty} */\n padding: (key = 'padding', label = 'Padding') => ({ key, label, type: 'spacing', group: GROUPS.spacing }),\n /** @returns {BlockProperty} */\n href: (label = 'Link URL') => ({ key: 'href', label, type: 'url', group: GROUPS.link, placeholder: 'https://' }),\n /** @returns {BlockProperty} */\n radius: (max = 40) => ({ key: 'border-radius', label: 'Corner radius', type: 'size', group: GROUPS.layout, min: 0, max, unit: 'px' }),\n};\n\n/**\n * White one-column section: the wrapper of a content block dropped outside\n * any column (MJML does not accept text directly inside the body).\n *\n * Warning: padding in LONGHAND form, never `padding=\"...\"`. The canvas starts\n * from the longhand defaults (20 px for a section), which override the\n * shorthand, while the export omits them - the screen and the email would\n * diverge (see core/mjmlAttributes).\n *\n * @param {string} inner\n */\nexport function inWhiteSection(inner) {\n return (\n `<mj-section background-color=\"${PALETTE.surface}\" padding-top=\"0px\" padding-bottom=\"0px\">` +\n `<mj-column>${inner}</mj-column></mj-section>`\n );\n}\n","// @ts-check\n/**\n * BRAND — the look of a company's emails: its name, its logo, a main color,\n * a color for headings and a font. Set once (by hand, or read from the\n * company's website), it is used by every new email, template and block,\n * and can be applied to the email being edited.\n *\n * `applyBrand` repaints an email by ROLE, not by exact color, so it works on\n * any email — a template, an imported one, one the user built:\n *\n * buttons filled with the main color, text in the color that\n * reads on it (white or ink);\n * colored sections (banners) take the main color; their texts and\n * buttons switch to what reads on it;\n * headings (bold, 18 px and more, on white) take the headings\n * color; body text and gray footers keep theirs;\n * colored links take the main color, or the headings color when the\n * main one is too light to read on white;\n * fonts every text, button and menu link;\n * header (optional) the logo — or the name — at the top, and\n * the name in the footer.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { FONT_OPTIONS, PALETTE, escapeXml } from '../../blocks/shared.js';\nimport { createNode } from '../../engine/model.js';\nimport { parseMjml, parseMjmlFragment, serializeMjml } from '../../engine/mjml.js';\n\n/**\n * @typedef {{\n * name?: string,\n * logoUrl?: string,\n * logoWidth?: number,\n * primary?: string,\n * heading?: string,\n * font?: string,\n * website?: string,\n * }} Brand\n * `primary`: buttons, banners, links; `heading`: headings; colors as\n * `#rrggbb`. `font`: one of the builder's web-safe stacks (FONT_OPTIONS).\n * `website`: where it was read from, to read it again.\n *\n * @typedef {import('../../engine/model.js').EmailNode} EmailNode\n */\n\nconst HEX = /^#[0-9a-f]{6}$/;\nconst FONTS = new Set(FONT_OPTIONS.map((option) => option.value));\nexport const MAX_LOGO_WIDTH = 240;\n\n/* ── Colors ─────────────────────────────────────────────────────────────── */\n\n/**\n * A CSS color as RGB(A), or null: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(),\n * rgba(), hsl(), hsla(), and the keywords white and black.\n * @param {unknown} value\n * @returns {{r: number, g: number, b: number, a: number}|null}\n */\nexport function parseColor(value) {\n const source = String(value ?? '').trim().toLowerCase();\n if (source === 'white') return { r: 255, g: 255, b: 255, a: 1 };\n if (source === 'black') return { r: 0, g: 0, b: 0, a: 1 };\n let match = source.match(/^#([0-9a-f]{3,8})$/);\n if (match) {\n const hex = match[1];\n if (hex.length === 3 || hex.length === 4) {\n const [r, g, b, a = 'ff'] = hex.split('').map((c) => c + c);\n return { r: parseInt(r, 16), g: parseInt(g, 16), b: parseInt(b, 16), a: parseInt(a, 16) / 255 };\n }\n if (hex.length === 6 || hex.length === 8) {\n return {\n r: parseInt(hex.slice(0, 2), 16),\n g: parseInt(hex.slice(2, 4), 16),\n b: parseInt(hex.slice(4, 6), 16),\n a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1,\n };\n }\n return null;\n }\n match = source.match(/^(rgba?|hsla?)\\(([^)]*)\\)$/);\n if (!match) return null;\n const parts = match[2].split(/[\\s,/]+/).filter(Boolean);\n if (parts.length < 3) return null;\n const alpha = parts[3] === undefined ? 1 : parts[3].endsWith('%') ? parseFloat(parts[3]) / 100 : parseFloat(parts[3]);\n if (match[1].startsWith('rgb')) {\n const channel = (/** @type {string} */ part) => (part.endsWith('%') ? (parseFloat(part) * 255) / 100 : parseFloat(part));\n const [r, g, b] = parts.slice(0, 3).map(channel);\n if ([r, g, b, alpha].some((n) => Number.isNaN(n))) return null;\n const clamp = (/** @type {number} */ n) => Math.max(0, Math.min(255, Math.round(n)));\n return { r: clamp(r), g: clamp(g), b: clamp(b), a: alpha };\n }\n const h = parseFloat(parts[0]);\n const s = parseFloat(parts[1]) / 100;\n const l = parseFloat(parts[2]) / 100;\n if ([h, s, l, alpha].some((n) => Number.isNaN(n))) return null;\n return { ...fromHsl(((h % 360) + 360) % 360, Math.max(0, Math.min(1, s)), Math.max(0, Math.min(1, l))), a: alpha };\n}\n\n/** @param {number} h @param {number} s @param {number} l */\nfunction fromHsl(h, s, l) {\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n const [r, g, b] = h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];\n return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255) };\n}\n\n/** @param {{r: number, g: number, b: number}} color */\nexport const toHex = ({ r, g, b }) => `#${[r, g, b].map((n) => n.toString(16).padStart(2, '0')).join('')}`;\n\n/** Hue (0-360), saturation and lightness (0-1). @param {{r: number, g: number, b: number}} color */\nexport function toHsl({ r, g, b }) {\n const [R, G, B] = [r / 255, g / 255, b / 255];\n const max = Math.max(R, G, B);\n const min = Math.min(R, G, B);\n const l = (max + min) / 2;\n if (max === min) return { h: 0, s: 0, l };\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n const h = max === R ? ((G - B) / d + (G < B ? 6 : 0)) * 60 : max === G ? ((B - R) / d + 2) * 60 : ((R - G) / d + 4) * 60;\n return { h, s, l };\n}\n\n/** WCAG relative luminance. @param {{r: number, g: number, b: number}} color */\nfunction luminance({ r, g, b }) {\n const linear = (/** @type {number} */ n) => {\n const c = n / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);\n}\n\n/** WCAG contrast ratio of two colors (1 to 21). @param {string} a @param {string} b */\nexport function contrastRatio(a, b) {\n const ca = parseColor(a);\n const cb = parseColor(b);\n if (!ca || !cb) return 1;\n const [hi, lo] = [luminance(ca), luminance(cb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** The text color that reads on `background`: white or ink. @param {string} background */\nexport function readableOn(background) {\n return contrastRatio(background, PALETTE.white) >= contrastRatio(background, PALETTE.ink) ? PALETTE.white : PALETTE.ink;\n}\n\n/**\n * A color meant to be noticed (a banner, a filled button), as opposed to\n * white, light tints, grays and near-black.\n * @param {unknown} value\n */\nexport function isColored(value) {\n const color = parseColor(value);\n if (!color || color.a < 0.5) return false;\n const { s, l } = toHsl(color);\n return s >= 0.2 && l >= 0.08 && l <= 0.88;\n}\n\n/** Dark enough to carry white text (navy, charcoal…). @param {unknown} value */\nfunction isDark(value) {\n const color = parseColor(value);\n return Boolean(color && color.a >= 0.5 && luminance(color) < 0.12);\n}\n\n/** A light gray (footers, captions): left as it is. @param {unknown} value */\nfunction isMuted(value) {\n const color = parseColor(value);\n if (!color) return false;\n const { s, l } = toHsl(color);\n return s < 0.2 && l > 0.4;\n}\n\n/* ── The brand itself ───────────────────────────────────────────────────── */\n\n/** @param {unknown} value */\nconst cleanColor = (value) => {\n const color = parseColor(value);\n return color && color.a >= 0.5 ? toHex(color) : '';\n};\n\n/**\n * A brand as stored and used: unknown fields dropped, colors as #rrggbb,\n * the font one of the builder's stacks, the logo an https or site-relative\n * image. Whatever is invalid is left out (the builder's default is used).\n * @param {unknown} input\n * @returns {Brand}\n */\nexport function cleanBrand(input) {\n const source = /** @type {Record<string, unknown>} */ (input && typeof input === 'object' ? input : {});\n /** @type {Brand} */\n const brand = {};\n const name = typeof source.name === 'string' ? source.name.replace(/\\s+/g, ' ').trim().slice(0, 80) : '';\n if (name) brand.name = name;\n const logo = typeof source.logoUrl === 'string' ? source.logoUrl.trim() : '';\n if (logo && logo.length <= 2000 && (/^https:\\/\\/[^\\s\"'<>]+$/i.test(logo) || /^\\/(?!\\/)[^\\s\"'<>]*$/.test(logo))) {\n brand.logoUrl = logo;\n const width = Math.round(Number(source.logoWidth));\n if (width >= 24 && width <= MAX_LOGO_WIDTH) brand.logoWidth = width;\n }\n const primary = cleanColor(source.primary);\n if (primary && HEX.test(primary)) brand.primary = primary;\n const heading = cleanColor(source.heading);\n if (heading && HEX.test(heading)) brand.heading = heading;\n if (typeof source.font === 'string' && FONTS.has(source.font)) brand.font = source.font;\n const website = typeof source.website === 'string' ? source.website.trim() : '';\n if (/^https?:\\/\\/[^\\s\"'<>]+$/i.test(website) && website.length <= 500) brand.website = website;\n return brand;\n}\n\n/** Does the brand change anything? @param {Brand|null|undefined} brand */\nexport const hasBrand = (brand) => Boolean(brand && Object.keys(cleanBrand(brand)).some((key) => key !== 'website'));\n\n/**\n * The sender the templates use: the brand's name and logo over the host's\n * sender (whose postal address stays).\n * @param {import('../templates/starter.js').StarterSender|null|undefined} sender\n * @param {Brand|null|undefined} brand\n */\nexport function brandedSender(sender, brand) {\n const base = { ...(sender || {}) };\n if (!brand) return base;\n if (brand.name) base.name = brand.name;\n if (brand.logoUrl) {\n base.logoUrl = brand.logoUrl;\n base.logoWidth = brand.logoWidth || base.logoWidth;\n }\n return base;\n}\n\n/**\n * The colors applyBrand paints with.\n * @param {Brand} brand\n */\nfunction paletteOf(brand) {\n const primary = brand.primary || null;\n return {\n primary,\n onPrimary: primary ? readableOn(primary) : null,\n heading: brand.heading || null,\n // A link must read on white: a too-light main color gives way.\n link: primary ? (contrastRatio(primary, PALETTE.white) >= 3 ? primary : brand.heading || PALETTE.ink) : null,\n font: brand.font || null,\n };\n}\n\n/* ── Painting ───────────────────────────────────────────────────────────── */\n\nconst SECTION_LIKE = new Set(['mj-section', 'mj-wrapper', 'mj-hero', 'mj-column', 'mj-group']);\nconst FONT_TAGS = new Set(['mj-text', 'mj-button', 'mj-navbar-link', 'mj-social-element', 'mj-table']);\n\n/** @param {string|undefined} size */\nconst px = (size) => {\n const n = parseFloat(String(size ?? ''));\n return Number.isNaN(n) ? 0 : n;\n};\n\n/** @param {string|undefined} weight */\nconst isBold = (weight) => /^(bold|bolder|[6-9]00)$/.test(String(weight ?? '').trim());\n\n/**\n * The links of a text in a new color, when theirs is a color (a gray or a\n * black link is a choice, and stays).\n * @param {string} html @param {string} color\n */\nfunction recolorLinks(html, color) {\n return html.replace(/<a\\b[^>]*>/gi, (tag) =>\n tag.replace(/(style\\s*=\\s*\")([^\"]*)(\")/i, (_, open, style, close) =>\n `${open}${style.replace(/(^|;)(\\s*color\\s*:\\s*)([^;]+)/i, (whole, sep, prop, value) =>\n isColored(value) ? `${sep}${prop}${color}` : whole\n )}${close}`\n )\n );\n}\n\n/**\n * One node and what it holds, in the brand's colors.\n * @param {EmailNode} node\n * @param {ReturnType<typeof paletteOf>} palette\n * @param {boolean} onColor the node sits on a colored background\n * @returns {EmailNode}\n */\nfunction paint(node, palette, onColor) {\n const attributes = { ...node.attributes };\n let content = node.content;\n let childrenOnColor = onColor;\n const set = (/** @type {string} */ key, /** @type {string|null} */ value) => {\n if (value) attributes[key] = value;\n };\n\n if (SECTION_LIKE.has(node.tag)) {\n const background = attributes['background-color'];\n if (background && palette.primary && (isColored(background) || (isDark(background) && node.tag !== 'mj-column'))) {\n attributes['background-color'] = palette.primary;\n childrenOnColor = true;\n } else if (background && parseColor(background)) {\n childrenOnColor = false;\n }\n }\n\n if (FONT_TAGS.has(node.tag) && palette.font) set('font-family', palette.font);\n\n if (node.tag === 'mj-text' && content !== null) {\n if (onColor) {\n set('color', palette.onPrimary);\n } else {\n const heading = isBold(attributes['font-weight']) && px(attributes['font-size']) >= 18;\n if (heading && palette.heading && !isMuted(attributes.color)) attributes.color = palette.heading;\n if (palette.link) content = recolorLinks(content, palette.link);\n }\n }\n\n if (node.tag === 'mj-button') {\n const background = attributes['background-color'];\n // MJML's own default is a dark gray: a button without color is filled.\n const filled = !background || isColored(background) || isDark(background);\n if (palette.primary && filled) {\n set('background-color', onColor ? palette.onPrimary : palette.primary);\n set('color', onColor ? palette.primary : palette.onPrimary);\n } else if (palette.primary && onColor) {\n // A light button on a banner: kept light, its text in the main color.\n set('color', readableOn(background) === PALETTE.ink && contrastRatio(palette.primary, background) < 3 ? PALETTE.ink : palette.primary);\n } else if (palette.link && isColored(attributes.color)) {\n // An outlined button on white.\n attributes.color = palette.link;\n if (isColored(attributes['border-color'])) attributes['border-color'] = palette.link;\n }\n }\n\n if (node.tag === 'mj-navbar-link' && isColored(attributes.color)) {\n set('color', onColor ? palette.onPrimary : palette.link);\n }\n\n const children = node.children.map((child) => paint(child, palette, childrenOnColor));\n return { ...node, attributes, content, children };\n}\n\n/** @param {string} html */\nconst plainText = (html) =>\n html\n .replace(/<[^>]*>/g, ' ')\n .replace(/ /g, ' ')\n .replace(/&/g, '&')\n .replace(/\\s+/g, ' ')\n .trim();\n\n/** @param {EmailNode} body */\nconst sectionsOf = (body) =>\n body.children.flatMap((child) => (child.tag === 'mj-wrapper' ? child.children.filter((c) => c.tag === 'mj-section') : [child]));\n\n/**\n * The logo, or the name, at the top; the name in the footer.\n * @param {EmailNode} root\n * @param {Brand} brand\n * @param {{names: string[], logos: string[]}} previous what the email may still show\n * @returns {EmailNode}\n */\nfunction rebrandHeader(root, brand, previous) {\n const body = root.children.find((child) => child.tag === 'mj-body');\n if (!body) return root;\n const names = previous.names.map((name) => String(name || '').trim()).filter((name) => name.length >= 2);\n const known = new Set(names.map((name) => name.toLowerCase()));\n const sections = sectionsOf(body);\n /** @type {Map<string, EmailNode>} */\n const replaced = new Map();\n\n const top = sections[0];\n const column = top?.children.find((child) => child.tag === 'mj-column');\n const first = column?.children[0];\n if (first?.tag === 'mj-image') {\n const small = px(first.attributes.width) > 0 && px(first.attributes.width) <= MAX_LOGO_WIDTH;\n const wasLogo = small || previous.logos.includes(first.attributes.src) || known.has(String(first.attributes.alt || '').toLowerCase());\n if (wasLogo && brand.logoUrl) {\n replaced.set(first.id, {\n ...first,\n attributes: {\n ...first.attributes,\n src: brand.logoUrl,\n alt: brand.name || first.attributes.alt || '',\n width: `${brand.logoWidth || Math.min(px(first.attributes.width) || 140, MAX_LOGO_WIDTH)}px`,\n },\n });\n }\n } else if (first?.tag === 'mj-text' && first.content !== null && known.has(plainText(first.content).toLowerCase())) {\n if (brand.logoUrl) {\n const keep = Object.fromEntries(Object.entries(first.attributes).filter(([key]) => key.startsWith('padding') || key === 'align'));\n replaced.set(\n first.id,\n createNode('mj-image', {\n id: first.id,\n attributes: { src: brand.logoUrl, alt: brand.name || '', width: `${brand.logoWidth || 140}px`, align: 'left', ...keep },\n })\n );\n } else if (brand.name) {\n replaced.set(first.id, { ...first, content: escapeXml(brand.name) });\n }\n }\n\n // The footer: the old name in its line becomes the new one.\n const bottom = sections.length > 1 ? sections[sections.length - 1] : null;\n const newName = brand.name;\n if (bottom && newName) {\n const swap = (/** @type {EmailNode} */ node) => {\n if (node.tag === 'mj-text' && node.content !== null) {\n let html = node.content;\n for (const name of names) {\n if (name !== newName) html = html.split(escapeXml(name)).join(escapeXml(newName));\n }\n if (html !== node.content) replaced.set(node.id, { ...node, content: html });\n }\n node.children.forEach(swap);\n };\n swap(bottom);\n }\n\n if (!replaced.size) return root;\n const rebuild = (/** @type {EmailNode} */ node) => replaced.get(node.id) || { ...node, children: node.children.map(rebuild) };\n return rebuild(root);\n}\n\n/**\n * An email in the brand's look.\n * @param {string} mjml\n * @param {Brand|null|undefined} brand\n * @param {{header?: boolean, previous?: {names?: (string|undefined)[], logos?: (string|undefined)[]}}} [options]\n * `header`: also put the logo (or name) at the top and the name in the\n * footer, replacing what `previous` lists (the names and logos the email\n * may still show: the host's sender, the brand before, \"Your company\").\n * @returns {string}\n */\nexport function applyBrand(mjml, brand, { header = false, previous = {} } = {}) {\n const clean = cleanBrand(brand);\n if (!hasBrand(clean)) return mjml;\n let root = parseMjml(mjml);\n root = paint(root, paletteOf(clean), false);\n if (header) {\n root = rebrandHeader(root, clean, {\n names: /** @type {string[]} */ ((previous.names || []).filter(Boolean)),\n logos: /** @type {string[]} */ ((previous.logos || []).filter(Boolean)),\n });\n }\n return serializeMjml(root);\n}\n\n/**\n * A fragment (a block about to be inserted) in the brand's look.\n * @param {string} mjml @param {Brand|null|undefined} brand\n */\nexport function applyBrandToFragment(mjml, brand) {\n const clean = cleanBrand(brand);\n if (!mjml || !hasBrand(clean)) return mjml;\n const palette = paletteOf(clean);\n return parseMjmlFragment(mjml)\n .map((node) => serializeMjml(paint(node, palette, false), { pretty: false }))\n .join('');\n}\n\n/**\n * A ready-made email (the starter, a gallery template) in the brand's look,\n * its header and footer naming the brand rather than the host's sender or\n * the \"Your company\" placeholder.\n * @param {string} mjml\n * @param {Brand|null|undefined} brand\n * @param {{sender?: import('../templates/starter.js').StarterSender|null, t?: (key: string) => string}} [context]\n */\nexport function brandTemplate(mjml, brand, { sender = null, t = (key) => key } = {}) {\n return applyBrand(mjml, brand, {\n header: true,\n previous: { names: [sender?.name, t('Your company')], logos: [sender?.logoUrl] },\n });\n}\n","// @ts-check\n/**\n * TAGS — colored labels on templates, to sort a library at a glance.\n *\n * The workspace keeps one list of tags, shared by every user like the custom\n * variables; a template carries at most `MAX_TEMPLATE_TAGS` of them, by id.\n * A tag's name is unique (case aside) and its color is one of `TAG_COLORS`,\n * so every tag reads well in the light and the dark theme.\n *\n * The same rules run in the browser (the tags dialog, the memory storage) and\n * on the server (`best-email-builder/server`).\n */\n\nimport { identity } from '../../i18n/index.js';\n\n/** @typedef {{id: string, label: string, color: string}} TemplateTag */\n\nexport const MAX_TEMPLATE_TAGS = 3;\n/** Tags in a workspace. */\nexport const MAX_TAGS = 100;\nexport const TAG_LIMITS = { label: 24 };\nexport const TAG_COLORS = ['gray', 'red', 'orange', 'amber', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'fuchsia', 'pink'];\n\nconst sameName = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;\n\n/**\n * @param {any} input `{label, color}`\n * @param {{existing?: TemplateTag[], editing?: string|null, t?: import('../../i18n/index.js').Translate}} [options]\n * `editing`: the id of the tag being edited (its own name is not a duplicate)\n * @returns {{tag: {label: string, color: string}, errors?: undefined} | {tag?: undefined, errors: Record<string, string>}}\n */\nexport function validateTag(input, { existing = [], editing = null, t = identity } = {}) {\n /** @type {Record<string, string>} */\n const errors = {};\n const label = String(input?.label ?? '').replace(/\\s+/g, ' ').trim();\n const color = String(input?.color ?? '');\n if (!label) errors.label = t('Name is required');\n else if (label.length > TAG_LIMITS.label) errors.label = t('{max} characters at most', { max: TAG_LIMITS.label });\n else if (existing.some((tag) => String(tag.id) !== String(editing) && sameName(tag.label, label))) {\n errors.label = t('A tag with this name already exists');\n }\n if (!TAG_COLORS.includes(color)) errors.color = t('Choose a color');\n if (Object.keys(errors).length) return { errors };\n return { tag: { label, color } };\n}\n\n/**\n * The tags a template may carry: known ids, each once, `MAX_TEMPLATE_TAGS`\n * at most, in the list's order.\n *\n * @param {unknown} ids\n * @param {TemplateTag[]} catalog\n * @param {{t?: import('../../i18n/index.js').Translate}} [options]\n * @returns {{ids: string[], error?: undefined} | {ids?: undefined, error: {code: string, message: string}}}\n */\nexport function checkTemplateTags(ids, catalog, { t = identity } = {}) {\n if (!Array.isArray(ids)) return { error: { code: 'INVALID_TAGS', message: t('A list of tags is expected') } };\n const wanted = new Set(ids.map(String));\n if (wanted.size > MAX_TEMPLATE_TAGS) {\n return { error: { code: 'TOO_MANY_TAGS', message: t('{max} tags at most per template', { max: MAX_TEMPLATE_TAGS }) } };\n }\n const known = new Set(catalog.map((tag) => String(tag.id)));\n if ([...wanted].some((id) => !known.has(id))) return { error: { code: 'UNKNOWN_TAG', message: t('Tag not found') } };\n return { ids: catalog.map((tag) => String(tag.id)).filter((id) => wanted.has(id)) };\n}\n\n/** The list's order: by name. @param {TemplateTag[]} tags */\nexport const sortTags = (tags) => [...tags].sort((a, b) => a.label.localeCompare(b.label));\n","// @ts-check\n/**\n * Images UPLOADED in the builder — one rule shared by the browser (immediate\n * rejection, storage/client.js) and the server (storage/server/assetFile.js).\n *\n * PNG, JPEG, GIF: the three formats every email client displays. WebP and SVG\n * do not display in Outlook, and an SVG can carry script.\n * The server reads the type from the file BYTES; the browser, from `file.type`.\n */\n\nimport { identity } from '../i18n/index.js';\n\n/** Maximum image size: beyond it, the email becomes slow to open. */\nexport const MAX_IMAGE_BYTES = 2 * 1024 * 1024;\n\n/** Accepted types → extension of the public key. */\nexport const IMAGE_TYPES = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif' };\n\n/** Public key: 32 hex characters of the hash + extension. */\nexport const ASSET_KEY_PATTERN = /^[a-f0-9]{32}\\.(png|jpg|gif)$/;\n\nconst SIGNATURES = [\n { type: 'image/png', bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] },\n { type: 'image/jpeg', bytes: [0xff, 0xd8, 0xff] },\n { type: 'image/gif', bytes: [0x47, 0x49, 0x46, 0x38] },\n];\n\n/**\n * Image type detected from the file signature, or `null`.\n * @param {Uint8Array} bytes\n * @returns {{type: string, ext: string}|null}\n */\nexport function detectImage(bytes) {\n if (!bytes || bytes.length < 8) return null;\n const found = SIGNATURES.find((s) => s.bytes.every((b, i) => bytes[i] === b));\n return found ? { type: found.type, ext: IMAGE_TYPES[found.type] } : null;\n}\n\n/**\n * Immediate rejection of a file the server would refuse, or `null`.\n * @param {{type?: string, size?: number}} file\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function imageUploadError(file, { t = identity } = {}) {\n if (!(String(file?.type || '') in IMAGE_TYPES)) return t('Format rejected: PNG, JPEG or GIF expected');\n if (Number(file?.size || 0) > MAX_IMAGE_BYTES) {\n return t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) });\n }\n return null;\n}\n"],"mappings":"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","names":["prop"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/constants.js","../src/core/document.js","../src/engine/model.js","../src/engine/mjml.js","../src/blocks/shared.js","../src/features/brand/brand.js","../src/features/tags/tags.js","../src/validation/images.js"],"sourcesContent":["// @ts-check\n/**\n * Email builder constants.\n *\n * `EMAIL_BUILDER_VERSION` versions THIS package (UI, blocks, rendering);\n * `EMAIL_DOCUMENT_SCHEMA_VERSION` versions the stored JSON envelope (the\n * project JSON). Both are written with every template, so a document read by\n * a newer version knows whether it must be migrated (see `core/document.js`).\n */\n\nexport const EMAIL_BUILDER_VERSION = '0.13.1';\n\nexport const EMAIL_DOCUMENT_SCHEMA_VERSION = 1;\n\n/** @typedef {'desktop'|'mobile'} EmailDevice */\n\n/** @type {EmailDevice[]} */\nexport const EMAIL_DEVICES = ['desktop', 'mobile'];\n\n/** Content column width — the email client standard. */\nexport const EMAIL_CONTENT_WIDTH = 600;\n\n/** Mobile preview frame width (standard iPhone, the common worst case). */\nexport const EMAIL_MOBILE_WIDTH = 375;\n","// @ts-check\n/**\n * DOCUMENT envelope — what gets stored (the project JSON).\n *\n * HTML is only a compilation output; what gets edited is the engine's\n * project. It is not stored bare but wrapped with:\n *\n * schemaVersion the shape of THIS envelope (migrations, see MIGRATIONS);\n * engine the engine that can read `project` back;\n * engineVersion its version at write time;\n * builderVersion the builder version that wrote it;\n * project the engine data, opaque to the rest of the code.\n *\n * A document from another engine, a future schema or a malformed one is\n * REJECTED on read rather than half-loaded: a template silently emptied and\n * then saved again would be lost.\n *\n * Dependency-free module: testable outside the browser.\n */\n\nimport { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION } from './constants.js';\n\n/**\n * @typedef {{\n * schemaVersion: number,\n * engine: string,\n * engineVersion: string,\n * builderVersion: string,\n * project: Record<string, any>,\n * }} EmailDocument\n */\n\n/**\n * Envelope migrations, from N to N+1. Empty while only one schema exists; the\n * first entry will be `1: (doc) => ({ ...doc, schemaVersion: 2 })`.\n *\n * @type {Record<number, (document: any) => any>}\n */\nconst MIGRATIONS = {};\n\nexport class EmailDocumentError extends Error {\n /** @param {string} message */\n constructor(message) {\n super(message);\n this.name = 'EmailDocumentError';\n this.status = 422;\n }\n}\n\nfunction isPlainObject(value) {\n return Boolean(value) && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * @param {{engine: string, engineVersion: string, project: object}} parts\n * @returns {EmailDocument}\n */\nexport function createDocument({ engine, engineVersion, project }) {\n if (!engine) throw new EmailDocumentError('Document: missing engine');\n if (!isPlainObject(project)) throw new EmailDocumentError('Document: invalid engine project');\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine,\n engineVersion: String(engineVersion || ''),\n builderVersion: EMAIL_BUILDER_VERSION,\n project: /** @type {Record<string, any>} */ (project),\n };\n}\n\n/**\n * Reads a document (object or JSON string), migrates it to the current schema\n * and validates it.\n *\n * @param {unknown} raw\n * @returns {EmailDocument}\n * @throws {EmailDocumentError}\n */\nexport function readDocument(raw) {\n let value = raw;\n if (typeof value === 'string') {\n try {\n value = JSON.parse(value);\n } catch {\n throw new EmailDocumentError('Unreadable document: invalid JSON');\n }\n }\n if (!isPlainObject(value)) throw new EmailDocumentError('Unreadable document: object expected');\n\n /** @type {any} */\n let document = { ...(/** @type {object} */ (value)) };\n const version = Number(document.schemaVersion);\n if (!Number.isInteger(version) || version < 1) {\n throw new EmailDocumentError('Unreadable document: missing schema version');\n }\n if (version > EMAIL_DOCUMENT_SCHEMA_VERSION) {\n throw new EmailDocumentError(\n `Document written by a newer version of the builder (schema ${version}) — ` +\n 'reload the page'\n );\n }\n for (let v = version; v < EMAIL_DOCUMENT_SCHEMA_VERSION; v += 1) {\n const migrate = MIGRATIONS[v];\n if (!migrate) throw new EmailDocumentError(`Document: no migration from schema ${v}`);\n document = migrate(document);\n }\n\n if (typeof document.engine !== 'string' || !document.engine) {\n throw new EmailDocumentError('Unreadable document: missing engine');\n }\n if (!isPlainObject(document.project)) {\n throw new EmailDocumentError('Unreadable document: missing engine project');\n }\n\n return {\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n engine: document.engine,\n engineVersion: String(document.engineVersion || ''),\n builderVersion: String(document.builderVersion || ''),\n project: document.project,\n };\n}\n\n/** @param {EmailDocument} document */\nexport function serializeDocument(document) {\n return JSON.stringify(readDocument(document));\n}\n","// @ts-check\n/**\n * THE EMAIL MODEL — the builder's own document, an MJML tree with ids.\n *\n * { id, tag: 'mj-text', attributes: { color: '#2b3038' }, children: [], content: 'Hello' }\n *\n * One node per MJML element. \"Ending\" elements (`mj-text`, `mj-button`…)\n * keep their inner HTML in `content`; the others hold `children`. Ids are\n * opaque, unique within a tree, made of `[a-z0-9]` so they can live in a CSS\n * class (the canvas finds an element by them, see `canvasMjml.js`).\n *\n * Every operation is PURE: it returns a new tree and never changes the one it\n * got, sharing the untouched branches. The undo history is then just a list\n * of trees.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\n/**\n * @typedef {{\n * id: string,\n * tag: string,\n * attributes: Record<string, string>,\n * children: EmailNode[],\n * content: string|null,\n * }} EmailNode\n */\n\n/** MJML elements whose inner content is HTML (MJML calls them \"ending tags\"). */\nexport const ENDING_TAGS = new Set([\n 'mj-text',\n 'mj-button',\n 'mj-navbar-link',\n 'mj-social-element',\n 'mj-table',\n 'mj-raw',\n 'mj-title',\n 'mj-preview',\n 'mj-style',\n 'mj-accordion-title',\n 'mj-accordion-text',\n]);\n\n/** Elements that hold a whole section's place in the body. */\nexport const SECTION_TAGS = new Set(['mj-section', 'mj-wrapper', 'mj-hero']);\n\n/** Elements that are never removed, moved or duplicated. */\nexport const LOCKED_TAGS = new Set(['mjml', 'mj-head', 'mj-body']);\n\nlet counter = 0;\n\n/** A fresh id: short, unique for the page's lifetime, `[a-z0-9]` only. */\nexport function createId() {\n counter = (counter + 1) % 1679616; // 36^4\n return `n${Date.now().toString(36).slice(-5)}${counter.toString(36).padStart(4, '0')}${Math.floor(Math.random() * 1296)\n .toString(36)\n .padStart(2, '0')}`;\n}\n\n/**\n * @param {string} tag\n * @param {{attributes?: Record<string, string>, children?: EmailNode[], content?: string|null, id?: string}} [parts]\n * @returns {EmailNode}\n */\nexport function createNode(tag, { attributes = {}, children = [], content = null, id = createId() } = {}) {\n return { id, tag, attributes: { ...attributes }, children: [...children], content: ENDING_TAGS.has(tag) ? String(content ?? '') : null };\n}\n\n/**\n * Deep copy with NEW ids everywhere (duplicate, paste, insert a block twice).\n * @param {EmailNode} node\n * @returns {EmailNode}\n */\nexport function cloneWithNewIds(node) {\n return {\n id: createId(),\n tag: node.tag,\n attributes: { ...node.attributes },\n children: node.children.map(cloneWithNewIds),\n content: node.content,\n };\n}\n\n/* ── Reading ─────────────────────────────────────────────────────────────── */\n\n/**\n * The node with this id and its ancestors (root first), or null.\n * @param {EmailNode} root @param {string} id\n * @returns {EmailNode[]|null}\n */\nexport function pathTo(root, id) {\n if (root.id === id) return [root];\n for (const child of root.children) {\n const path = pathTo(child, id);\n if (path) return [root, ...path];\n }\n return null;\n}\n\n/** @param {EmailNode} root @param {string|null|undefined} id */\nexport function findNode(root, id) {\n if (!id) return null;\n const path = pathTo(root, id);\n return path ? path[path.length - 1] : null;\n}\n\n/** @param {EmailNode} root @param {string} id */\nexport function parentOf(root, id) {\n const path = pathTo(root, id);\n return path && path.length > 1 ? path[path.length - 2] : null;\n}\n\n/**\n * Closest node (itself included) whose tag is one of `tags`.\n * @param {EmailNode} root @param {string} id @param {Set<string>|string[]} tags\n */\nexport function closestOfTags(root, id, tags) {\n const set = tags instanceof Set ? tags : new Set(tags);\n const path = pathTo(root, id);\n if (!path) return null;\n for (let i = path.length - 1; i >= 0; i -= 1) if (set.has(path[i].tag)) return path[i];\n return null;\n}\n\n/** First node of a tag, depth first. @param {EmailNode} root @param {string} tag @returns {EmailNode|null} */\nexport function findFirst(root, tag) {\n if (root.tag === tag) return root;\n for (const child of root.children) {\n const found = findFirst(child, tag);\n if (found) return found;\n }\n return null;\n}\n\n/** Every node, depth first. @param {EmailNode} root @param {(node: EmailNode) => void} visit */\nexport function walk(root, visit) {\n visit(root);\n for (const child of root.children) walk(child, visit);\n}\n\n/* ── Writing (pure) ──────────────────────────────────────────────────────── */\n\n/**\n * The tree with the node `id` replaced by `update(node)`.\n * @param {EmailNode} root @param {string} id @param {(node: EmailNode) => EmailNode} update\n * @returns {EmailNode}\n */\nexport function updateNode(root, id, update) {\n if (root.id === id) return update(root);\n let changed = false;\n const children = root.children.map((child) => {\n const next = updateNode(child, id, update);\n if (next !== child) changed = true;\n return next;\n });\n return changed ? { ...root, children } : root;\n}\n\n/**\n * Sets (string) or removes (null, '') attributes.\n * @param {EmailNode} root @param {string} id @param {Record<string, string|null|undefined>} attributes\n */\nexport function setAttributes(root, id, attributes) {\n return updateNode(root, id, (node) => {\n const next = { ...node.attributes };\n for (const [key, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === '') delete next[key];\n else next[key] = String(value);\n }\n return { ...node, attributes: next };\n });\n}\n\n/** @param {EmailNode} root @param {string} id @param {string} content */\nexport function setContent(root, id, content) {\n return updateNode(root, id, (node) => (node.content === null ? node : { ...node, content: String(content ?? '') }));\n}\n\n/**\n * Inserts nodes into `parentId` at `index` (clamped).\n * @param {EmailNode} root @param {string} parentId @param {number} index @param {EmailNode[]} nodes\n */\nexport function insertNodes(root, parentId, index, nodes) {\n return updateNode(root, parentId, (parent) => {\n const at = Math.max(0, Math.min(index, parent.children.length));\n const children = [...parent.children];\n children.splice(at, 0, ...nodes);\n return { ...parent, children };\n });\n}\n\n/** @param {EmailNode} root @param {string} id */\nexport function removeNode(root, id) {\n const parent = parentOf(root, id);\n if (!parent) return root;\n return updateNode(root, parent.id, (p) => ({ ...p, children: p.children.filter((c) => c.id !== id) }));\n}\n\n/**\n * Moves a node under `parentId` at `index`, where `index` counts the target's\n * children BEFORE the node leaves its place (what a drop indicator shows).\n * @param {EmailNode} root @param {string} id @param {string} parentId @param {number} index\n */\nexport function moveNode(root, id, parentId, index) {\n const node = findNode(root, id);\n const from = parentOf(root, id);\n if (!node || !from) return root;\n // A node cannot go inside itself.\n if (pathTo(node, parentId)) return root;\n let at = index;\n if (from.id === parentId) {\n const current = from.children.findIndex((c) => c.id === id);\n if (current < at) at -= 1;\n if (current === at) return root;\n }\n return insertNodes(removeNode(root, id), parentId, at, [node]);\n}\n\n/**\n * Index of a node among its siblings, and their count.\n * @param {EmailNode} root @param {string} id\n */\nexport function positionOf(root, id) {\n const parent = parentOf(root, id);\n if (!parent) return { parent: null, index: -1, count: 0 };\n return { parent, index: parent.children.findIndex((c) => c.id === id), count: parent.children.length };\n}\n\n/* ── Snapshot (the stored project) ───────────────────────────────────────── */\n\n/** Shape version of the stored project (`{ format, root }`). */\nexport const PROJECT_FORMAT = 1;\n\n/** @param {EmailNode} root */\nexport function toProject(root) {\n return { format: PROJECT_FORMAT, root: strip(root) };\n}\n\n/** @param {EmailNode} node @returns {any} */\nfunction strip(node) {\n /** @type {any} */\n const out = { id: node.id, tag: node.tag };\n if (Object.keys(node.attributes).length) out.attributes = { ...node.attributes };\n if (node.content !== null) out.content = node.content;\n if (node.children.length) out.children = node.children.map(strip);\n return out;\n}\n\n/**\n * Reads a stored project back into a tree. Ids are kept when they are valid\n * and unique, replaced otherwise. Throws on anything that is not a project of\n * this format.\n * @param {unknown} project\n * @returns {EmailNode}\n */\nexport function fromProject(project) {\n const value = /** @type {any} */ (project);\n if (!value || typeof value !== 'object' || value.format !== PROJECT_FORMAT || !value.root) {\n throw new Error('Not a project of this builder');\n }\n const seen = new Set();\n /** @param {any} raw @returns {EmailNode} */\n const read = (raw) => {\n if (!raw || typeof raw !== 'object' || typeof raw.tag !== 'string' || !/^(mjml|mj-[a-z-]+)$/.test(raw.tag)) {\n throw new Error('Invalid element in the project');\n }\n let id = typeof raw.id === 'string' && /^[a-z0-9]{1,40}$/.test(raw.id) ? raw.id : createId();\n if (seen.has(id)) id = createId();\n seen.add(id);\n /** @type {Record<string, string>} */\n const attributes = {};\n for (const [k, v] of Object.entries(raw.attributes || {})) {\n if (/^[a-z][a-z0-9-]*$/i.test(k) && v !== null && v !== undefined) attributes[k] = String(v);\n }\n return createNode(raw.tag, {\n id,\n attributes,\n content: ENDING_TAGS.has(raw.tag) ? String(raw.content ?? '') : null,\n children: ENDING_TAGS.has(raw.tag) ? [] : (Array.isArray(raw.children) ? raw.children : []).map(read),\n });\n };\n const root = read(value.root);\n if (root.tag !== 'mjml' || !findFirst(root, 'mj-body')) throw new Error('The project has no email body');\n return root;\n}\n","// @ts-check\n/**\n * MJML ⇄ MODEL — reading MJML into the builder's tree, writing it back.\n *\n * MJML is close enough to XML to be read by a small scanner, and only two\n * kinds of element exist:\n * - containers (`mj-section`, `mj-column`…), made of other MJML elements;\n * - \"ending\" elements (`mj-text`, `mj-button`…), whose inside is HTML kept\n * as is, up to the matching closing tag.\n * Anything else between containers (stray text, comments, HTML outside an\n * ending element) is not MJML and is dropped.\n *\n * Attribute values are DECODED in the model (`&` → `&`) and encoded again\n * on write: the model holds what the attribute means, the MJML how to write\n * it.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { ENDING_TAGS, createNode, findFirst } from './model.js';\n\n/** @typedef {import('./model.js').EmailNode} EmailNode */\n\nconst NAMED_ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '\"', apos: \"'\", nbsp: ' ' };\n\n/** @param {string} value */\nexport function decodeAttribute(value) {\n return value.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (match, entity) => {\n if (entity[0] === '#') {\n const code = entity[1] === 'x' || entity[1] === 'X' ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);\n return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : match;\n }\n const named = /** @type {Record<string, string>} */ (NAMED_ENTITIES)[entity.toLowerCase()];\n return named ?? match;\n });\n}\n\n/** @param {string} value */\nexport function encodeAttribute(value) {\n return String(value).replace(/&/g, '&').replace(/\"/g, '"').replace(/</g, '<').replace(/>/g, '>');\n}\n\n/**\n * Reads the attributes of an opening tag, from `start` (just after the name)\n * to its `>`. Quotes may contain `>`.\n * @param {string} source @param {number} start\n * @returns {{attributes: Record<string, string>, end: number, selfClosing: boolean}}\n */\nfunction readAttributes(source, start) {\n /** @type {Record<string, string>} */\n const attributes = {};\n let i = start;\n const n = source.length;\n while (i < n) {\n while (i < n && /\\s/.test(source[i])) i += 1;\n if (source[i] === '>') return { attributes, end: i + 1, selfClosing: false };\n if (source[i] === '/' && source[i + 1] === '>') return { attributes, end: i + 2, selfClosing: true };\n if (i >= n) break;\n const nameMatch = /^[^\\s=/>\"']+/.exec(source.slice(i, i + 200));\n if (!nameMatch) {\n i += 1;\n continue;\n }\n const name = nameMatch[0].toLowerCase();\n i += nameMatch[0].length;\n while (i < n && /\\s/.test(source[i])) i += 1;\n let value = '';\n if (source[i] === '=') {\n i += 1;\n while (i < n && /\\s/.test(source[i])) i += 1;\n const quote = source[i];\n if (quote === '\"' || quote === \"'\") {\n const close = source.indexOf(quote, i + 1);\n const stop = close === -1 ? n : close;\n value = source.slice(i + 1, stop);\n i = stop + 1;\n } else {\n let bare = /^[^\\s>]*/.exec(source.slice(i))?.[0] || '';\n // `width=100/>`: the slash closes the tag, it is not in the value.\n if (bare.endsWith('/') && source[i + bare.length] === '>') bare = bare.slice(0, -1);\n value = bare;\n i += bare.length;\n }\n }\n attributes[name] = decodeAttribute(value);\n }\n return { attributes, end: n, selfClosing: false };\n}\n\n/**\n * MJML elements of a fragment (no `<mjml>` needed): `<mj-section>…</mj-section><mj-section>…`.\n * @param {string} source\n * @returns {EmailNode[]}\n */\nexport function parseMjmlFragment(source) {\n const text = String(source || '').replace(/^\\uFEFF/, '');\n /** @type {EmailNode} */\n const top = createNode('mj-fragment');\n /** @type {EmailNode[]} */\n const stack = [top];\n let i = 0;\n const n = text.length;\n\n while (i < n) {\n const lt = text.indexOf('<', i);\n if (lt === -1) break;\n i = lt;\n if (text.startsWith('<!--', i)) {\n const close = text.indexOf('-->', i + 4);\n i = close === -1 ? n : close + 3;\n continue;\n }\n if (text.startsWith('<?', i) || text.startsWith('<!', i)) {\n const close = text.indexOf('>', i);\n i = close === -1 ? n : close + 1;\n continue;\n }\n const closing = text[i + 1] === '/';\n const nameMatch = /^[a-z][a-z0-9-]*/i.exec(text.slice(i + (closing ? 2 : 1), i + 60));\n if (!nameMatch) {\n i += 1;\n continue;\n }\n const tag = nameMatch[0].toLowerCase();\n const afterName = i + (closing ? 2 : 1) + nameMatch[0].length;\n const isMjml = tag === 'mjml' || tag.startsWith('mj-');\n\n if (closing) {\n const close = text.indexOf('>', afterName);\n i = close === -1 ? n : close + 1;\n if (!isMjml) continue;\n // Close up to the matching element (tolerates a forgotten closing tag).\n for (let depth = stack.length - 1; depth > 0; depth -= 1) {\n if (stack[depth].tag === tag) {\n stack.length = depth;\n break;\n }\n }\n continue;\n }\n\n const { attributes, end, selfClosing } = readAttributes(text, afterName);\n i = end;\n if (!isMjml) continue;\n\n const parent = stack[stack.length - 1];\n if (ENDING_TAGS.has(tag)) {\n let content = '';\n if (!selfClosing) {\n const closeRe = new RegExp(`</${tag}\\\\s*>`, 'ig');\n closeRe.lastIndex = i;\n const close = closeRe.exec(text);\n const stop = close ? close.index : n;\n content = text.slice(i, stop).trim();\n i = close ? close.index + close[0].length : n;\n }\n parent.children.push(createNode(tag, { attributes, content }));\n continue;\n }\n const node = createNode(tag, { attributes });\n parent.children.push(node);\n if (!selfClosing) stack.push(node);\n }\n return top.children;\n}\n\n/**\n * A whole email. Always returns an `mjml` root holding an `mj-body`: a bare\n * fragment is wrapped, a missing body is added.\n * @param {string} source\n * @returns {EmailNode}\n */\nexport function parseMjml(source) {\n const nodes = parseMjmlFragment(source);\n let root = nodes.find((node) => node.tag === 'mjml') || null;\n if (!root) {\n const body = nodes.find((node) => node.tag === 'mj-body');\n const head = nodes.find((node) => node.tag === 'mj-head');\n root = createNode('mjml', {\n children: [\n ...(head ? [head] : []),\n body || createNode('mj-body', { children: nodes.filter((node) => node.tag !== 'mj-head') }),\n ],\n });\n }\n if (!findFirst(root, 'mj-body')) root.children.push(createNode('mj-body'));\n return root;\n}\n\n/**\n * The MJML of a tree. `pretty`: one element per line, indented (what is\n * stored and downloaded); otherwise on one line.\n * @param {EmailNode} node\n * @param {{pretty?: boolean, attributes?: (node: EmailNode) => Record<string, string>, extraChildren?: (node: EmailNode) => string}} [options]\n * `attributes`: replaces a node's attributes on write (the canvas adds its\n * markers there); `extraChildren`: MJML appended inside a container.\n */\nexport function serializeMjml(node, options = {}) {\n const { pretty = true } = options;\n /** @param {EmailNode} current @param {number} depth @returns {string} */\n const write = (current, depth) => {\n const indent = pretty ? ' '.repeat(depth) : '';\n const attributes = options.attributes ? options.attributes(current) : current.attributes;\n const attrs = Object.entries(attributes)\n .map(([key, value]) => ` ${key}=\"${encodeAttribute(value)}\"`)\n .join('');\n if (current.content !== null) {\n return `${indent}<${current.tag}${attrs}>${current.content}</${current.tag}>`;\n }\n const inner = current.children.map((child) => write(child, depth + 1));\n const extra = options.extraChildren ? options.extraChildren(current) : '';\n if (extra) inner.push(pretty ? `${' '.repeat(depth + 1)}${extra}` : extra);\n if (!inner.length) return `${indent}<${current.tag}${attrs} />`;\n return pretty\n ? `${indent}<${current.tag}${attrs}>\\n${inner.join('\\n')}\\n${indent}</${current.tag}>`\n : `<${current.tag}${attrs}>${inner.join('')}</${current.tag}>`;\n };\n return write(node, 0);\n}\n","// @ts-check\n/**\n * Building blocks shared by all blocks: palette, fonts, recurring properties.\n *\n * -- DEFAULT PALETTE ---------------------------------------------------------\n * Plain hex values: email clients load no stylesheet and understand neither\n * `var()` nor modern color functions, so every value ends up inline in a\n * `style=\"\"` attribute.\n *\n * -- EVERY BLOCK SETS ITS OWN TEXT ATTRIBUTES --------------------------------\n * Font, size, line height and color are written on the element itself, never\n * left to MJML defaults (the \"Ubuntu\" font loaded from Google, line height 1):\n * the canvas and the sent email then render the same thing, and Outlook gets\n * explicit values.\n *\n * Fonts: only web-safe stacks, installed everywhere and understood by\n * Outlook - a web font only renders in a minority of email clients.\n */\n\n/** @typedef {import('../core/EmailEditor.js').BlockProperty} BlockProperty */\n\n/** Text for an MJML attribute or element. @param {string} text */\nexport function escapeXml(text) {\n return String(text ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n/** The default palette. */\nexport const PALETTE = {\n ink: '#2b3038',\n inkSoft: '#41506b',\n navy: '#212e5a',\n brand: '#2977bc',\n border: '#dfe6f0',\n canvas: '#eceff4',\n surface: '#ffffff',\n white: '#ffffff',\n};\n\n/** Default link of a new button. */\nexport const DEFAULT_BUTTON_URL = 'https://example.com';\n\nexport const FONT_STACK = 'Arial, Helvetica, sans-serif';\n\n/** @type {{value: string, label: string}[]} */\nexport const FONT_OPTIONS = [\n { value: 'Arial, Helvetica, sans-serif', label: 'Arial' },\n { value: 'Verdana, Geneva, sans-serif', label: 'Verdana' },\n { value: 'Tahoma, Geneva, sans-serif', label: 'Tahoma' },\n { value: \"'Trebuchet MS', Helvetica, sans-serif\", label: 'Trebuchet' },\n { value: 'Georgia, Times, serif', label: 'Georgia' },\n { value: \"'Times New Roman', Times, serif\", label: 'Times' },\n { value: \"'Courier New', Courier, monospace\", label: 'Courier' },\n];\n\n/** @type {{value: string, label: string}[]} */\nexport const ALIGN_OPTIONS = [\n { value: 'left', label: 'Left' },\n { value: 'center', label: 'Center' },\n { value: 'right', label: 'Right' },\n];\n\n/** Properties panel groups, in display order. */\nexport const GROUPS = {\n content: 'Content',\n link: 'Link',\n typography: 'Typography',\n colors: 'Colors',\n layout: 'Layout',\n spacing: 'Spacing',\n};\n\n/** Recurring properties. */\nexport const prop = {\n /** @returns {BlockProperty} */\n fontFamily: () => ({ key: 'font-family', label: 'Font', type: 'select', group: GROUPS.typography, options: FONT_OPTIONS }),\n /** @returns {BlockProperty} */\n fontSize: (min = 10, max = 48) => ({ key: 'font-size', label: 'Size', type: 'size', group: GROUPS.typography, min, max, unit: 'px' }),\n /** @returns {BlockProperty} */\n lineHeight: () => ({ key: 'line-height', label: 'Line height', type: 'size', group: GROUPS.typography, min: 10, max: 80, unit: 'px' }),\n /** @returns {BlockProperty} */\n color: (label = 'Text') => ({ key: 'color', label, type: 'color', group: GROUPS.colors }),\n /** @returns {BlockProperty} */\n background: (key = 'background-color', label = 'Background') => ({ key, label, type: 'color', group: GROUPS.colors }),\n /** @returns {BlockProperty} */\n align: (options = ALIGN_OPTIONS) => ({ key: 'align', label: 'Alignment', type: 'align', group: GROUPS.layout, options }),\n /** @returns {BlockProperty} */\n padding: (key = 'padding', label = 'Padding') => ({ key, label, type: 'spacing', group: GROUPS.spacing }),\n /** @returns {BlockProperty} */\n href: (label = 'Link URL') => ({ key: 'href', label, type: 'url', group: GROUPS.link, placeholder: 'https://' }),\n /** @returns {BlockProperty} */\n radius: (max = 40) => ({ key: 'border-radius', label: 'Corner radius', type: 'size', group: GROUPS.layout, min: 0, max, unit: 'px' }),\n};\n\n/**\n * White one-column section: the wrapper of a content block dropped outside\n * any column (MJML does not accept text directly inside the body).\n *\n * Warning: padding in LONGHAND form, never `padding=\"...\"`. The canvas starts\n * from the longhand defaults (20 px for a section), which override the\n * shorthand, while the export omits them - the screen and the email would\n * diverge (see core/mjmlAttributes).\n *\n * @param {string} inner\n */\nexport function inWhiteSection(inner) {\n return (\n `<mj-section background-color=\"${PALETTE.surface}\" padding-top=\"0px\" padding-bottom=\"0px\">` +\n `<mj-column>${inner}</mj-column></mj-section>`\n );\n}\n","// @ts-check\n/**\n * BRAND — the look of a company's emails: its name, its logo, a main color,\n * a color for headings and a font. Set once (by hand, or read from the\n * company's website), it is used by every new email, template and block,\n * and can be applied to the email being edited.\n *\n * `applyBrand` repaints an email by ROLE, not by exact color, so it works on\n * any email — a template, an imported one, one the user built:\n *\n * buttons filled with the main color, text in the color that\n * reads on it (white or ink);\n * colored sections (banners) take the main color; their texts and\n * buttons switch to what reads on it;\n * headings (bold, 18 px and more, on white) take the headings\n * color; body text and gray footers keep theirs;\n * colored links take the main color, or the headings color when the\n * main one is too light to read on white;\n * fonts every text, button and menu link;\n * header (optional) the logo — or the name — at the top, and\n * the name in the footer.\n *\n * Dependency-free: runs in the browser, on the server and in unit tests.\n */\n\nimport { FONT_OPTIONS, PALETTE, escapeXml } from '../../blocks/shared.js';\nimport { createNode } from '../../engine/model.js';\nimport { parseMjml, parseMjmlFragment, serializeMjml } from '../../engine/mjml.js';\n\n/**\n * @typedef {{\n * name?: string,\n * logoUrl?: string,\n * logoWidth?: number,\n * primary?: string,\n * heading?: string,\n * font?: string,\n * website?: string,\n * }} Brand\n * `primary`: buttons, banners, links; `heading`: headings; colors as\n * `#rrggbb`. `font`: one of the builder's web-safe stacks (FONT_OPTIONS).\n * `website`: where it was read from, to read it again.\n *\n * @typedef {import('../../engine/model.js').EmailNode} EmailNode\n */\n\nconst HEX = /^#[0-9a-f]{6}$/;\nconst FONTS = new Set(FONT_OPTIONS.map((option) => option.value));\nexport const MAX_LOGO_WIDTH = 240;\n\n/* ── Colors ─────────────────────────────────────────────────────────────── */\n\n/**\n * A CSS color as RGB(A), or null: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb(),\n * rgba(), hsl(), hsla(), and the keywords white and black.\n * @param {unknown} value\n * @returns {{r: number, g: number, b: number, a: number}|null}\n */\nexport function parseColor(value) {\n const source = String(value ?? '').trim().toLowerCase();\n if (source === 'white') return { r: 255, g: 255, b: 255, a: 1 };\n if (source === 'black') return { r: 0, g: 0, b: 0, a: 1 };\n let match = source.match(/^#([0-9a-f]{3,8})$/);\n if (match) {\n const hex = match[1];\n if (hex.length === 3 || hex.length === 4) {\n const [r, g, b, a = 'ff'] = hex.split('').map((c) => c + c);\n return { r: parseInt(r, 16), g: parseInt(g, 16), b: parseInt(b, 16), a: parseInt(a, 16) / 255 };\n }\n if (hex.length === 6 || hex.length === 8) {\n return {\n r: parseInt(hex.slice(0, 2), 16),\n g: parseInt(hex.slice(2, 4), 16),\n b: parseInt(hex.slice(4, 6), 16),\n a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1,\n };\n }\n return null;\n }\n match = source.match(/^(rgba?|hsla?)\\(([^)]*)\\)$/);\n if (!match) return null;\n const parts = match[2].split(/[\\s,/]+/).filter(Boolean);\n if (parts.length < 3) return null;\n const alpha = parts[3] === undefined ? 1 : parts[3].endsWith('%') ? parseFloat(parts[3]) / 100 : parseFloat(parts[3]);\n if (match[1].startsWith('rgb')) {\n const channel = (/** @type {string} */ part) => (part.endsWith('%') ? (parseFloat(part) * 255) / 100 : parseFloat(part));\n const [r, g, b] = parts.slice(0, 3).map(channel);\n if ([r, g, b, alpha].some((n) => Number.isNaN(n))) return null;\n const clamp = (/** @type {number} */ n) => Math.max(0, Math.min(255, Math.round(n)));\n return { r: clamp(r), g: clamp(g), b: clamp(b), a: alpha };\n }\n const h = parseFloat(parts[0]);\n const s = parseFloat(parts[1]) / 100;\n const l = parseFloat(parts[2]) / 100;\n if ([h, s, l, alpha].some((n) => Number.isNaN(n))) return null;\n return { ...fromHsl(((h % 360) + 360) % 360, Math.max(0, Math.min(1, s)), Math.max(0, Math.min(1, l))), a: alpha };\n}\n\n/** @param {number} h @param {number} s @param {number} l */\nfunction fromHsl(h, s, l) {\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n const [r, g, b] = h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];\n return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255) };\n}\n\n/** @param {{r: number, g: number, b: number}} color */\nexport const toHex = ({ r, g, b }) => `#${[r, g, b].map((n) => n.toString(16).padStart(2, '0')).join('')}`;\n\n/** Hue (0-360), saturation and lightness (0-1). @param {{r: number, g: number, b: number}} color */\nexport function toHsl({ r, g, b }) {\n const [R, G, B] = [r / 255, g / 255, b / 255];\n const max = Math.max(R, G, B);\n const min = Math.min(R, G, B);\n const l = (max + min) / 2;\n if (max === min) return { h: 0, s: 0, l };\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n const h = max === R ? ((G - B) / d + (G < B ? 6 : 0)) * 60 : max === G ? ((B - R) / d + 2) * 60 : ((R - G) / d + 4) * 60;\n return { h, s, l };\n}\n\n/** WCAG relative luminance. @param {{r: number, g: number, b: number}} color */\nfunction luminance({ r, g, b }) {\n const linear = (/** @type {number} */ n) => {\n const c = n / 255;\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;\n };\n return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b);\n}\n\n/** WCAG contrast ratio of two colors (1 to 21). @param {string} a @param {string} b */\nexport function contrastRatio(a, b) {\n const ca = parseColor(a);\n const cb = parseColor(b);\n if (!ca || !cb) return 1;\n const [hi, lo] = [luminance(ca), luminance(cb)].sort((x, y) => y - x);\n return (hi + 0.05) / (lo + 0.05);\n}\n\n/** The text color that reads on `background`: white or ink. @param {string} background */\nexport function readableOn(background) {\n return contrastRatio(background, PALETTE.white) >= contrastRatio(background, PALETTE.ink) ? PALETTE.white : PALETTE.ink;\n}\n\n/**\n * A color meant to be noticed (a banner, a filled button), as opposed to\n * white, light tints, grays and near-black.\n * @param {unknown} value\n */\nexport function isColored(value) {\n const color = parseColor(value);\n if (!color || color.a < 0.5) return false;\n const { s, l } = toHsl(color);\n return s >= 0.2 && l >= 0.08 && l <= 0.88;\n}\n\n/** Dark enough to carry white text (navy, charcoal…). @param {unknown} value */\nfunction isDark(value) {\n const color = parseColor(value);\n return Boolean(color && color.a >= 0.5 && luminance(color) < 0.12);\n}\n\n/** A light gray (footers, captions): left as it is. @param {unknown} value */\nfunction isMuted(value) {\n const color = parseColor(value);\n if (!color) return false;\n const { s, l } = toHsl(color);\n return s < 0.2 && l > 0.4;\n}\n\n/* ── The brand itself ───────────────────────────────────────────────────── */\n\n/** @param {unknown} value */\nconst cleanColor = (value) => {\n const color = parseColor(value);\n return color && color.a >= 0.5 ? toHex(color) : '';\n};\n\n/**\n * A brand as stored and used: unknown fields dropped, colors as #rrggbb,\n * the font one of the builder's stacks, the logo an https or site-relative\n * image. Whatever is invalid is left out (the builder's default is used).\n * @param {unknown} input\n * @returns {Brand}\n */\nexport function cleanBrand(input) {\n const source = /** @type {Record<string, unknown>} */ (input && typeof input === 'object' ? input : {});\n /** @type {Brand} */\n const brand = {};\n const name = typeof source.name === 'string' ? source.name.replace(/\\s+/g, ' ').trim().slice(0, 80) : '';\n if (name) brand.name = name;\n const logo = typeof source.logoUrl === 'string' ? source.logoUrl.trim() : '';\n if (logo && logo.length <= 2000 && (/^https:\\/\\/[^\\s\"'<>]+$/i.test(logo) || /^\\/(?!\\/)[^\\s\"'<>]*$/.test(logo))) {\n brand.logoUrl = logo;\n const width = Math.round(Number(source.logoWidth));\n if (width >= 24 && width <= MAX_LOGO_WIDTH) brand.logoWidth = width;\n }\n const primary = cleanColor(source.primary);\n if (primary && HEX.test(primary)) brand.primary = primary;\n const heading = cleanColor(source.heading);\n if (heading && HEX.test(heading)) brand.heading = heading;\n if (typeof source.font === 'string' && FONTS.has(source.font)) brand.font = source.font;\n const website = typeof source.website === 'string' ? source.website.trim() : '';\n if (/^https?:\\/\\/[^\\s\"'<>]+$/i.test(website) && website.length <= 500) brand.website = website;\n return brand;\n}\n\n/** Does the brand change anything? @param {Brand|null|undefined} brand */\nexport const hasBrand = (brand) => Boolean(brand && Object.keys(cleanBrand(brand)).some((key) => key !== 'website'));\n\n/**\n * The sender the templates use: the brand's name and logo over the host's\n * sender (whose postal address stays).\n * @param {import('../templates/starter.js').StarterSender|null|undefined} sender\n * @param {Brand|null|undefined} brand\n */\nexport function brandedSender(sender, brand) {\n const base = { ...(sender || {}) };\n if (!brand) return base;\n if (brand.name) base.name = brand.name;\n if (brand.logoUrl) {\n base.logoUrl = brand.logoUrl;\n base.logoWidth = brand.logoWidth || base.logoWidth;\n }\n return base;\n}\n\n/**\n * The colors applyBrand paints with.\n * @param {Brand} brand\n */\nfunction paletteOf(brand) {\n const primary = brand.primary || null;\n return {\n primary,\n onPrimary: primary ? readableOn(primary) : null,\n heading: brand.heading || null,\n // A link must read on white: a too-light main color gives way.\n link: primary ? (contrastRatio(primary, PALETTE.white) >= 3 ? primary : brand.heading || PALETTE.ink) : null,\n font: brand.font || null,\n };\n}\n\n/* ── Painting ───────────────────────────────────────────────────────────── */\n\nconst SECTION_LIKE = new Set(['mj-section', 'mj-wrapper', 'mj-hero', 'mj-column', 'mj-group']);\nconst FONT_TAGS = new Set(['mj-text', 'mj-button', 'mj-navbar-link', 'mj-social-element', 'mj-table']);\n\n/** @param {string|undefined} size */\nconst px = (size) => {\n const n = parseFloat(String(size ?? ''));\n return Number.isNaN(n) ? 0 : n;\n};\n\n/** @param {string|undefined} weight */\nconst isBold = (weight) => /^(bold|bolder|[6-9]00)$/.test(String(weight ?? '').trim());\n\n/**\n * The links of a text in a new color, when theirs is a color (a gray or a\n * black link is a choice, and stays).\n * @param {string} html @param {string} color\n */\nfunction recolorLinks(html, color) {\n return html.replace(/<a\\b[^>]*>/gi, (tag) =>\n tag.replace(/(style\\s*=\\s*\")([^\"]*)(\")/i, (_, open, style, close) =>\n `${open}${style.replace(/(^|;)(\\s*color\\s*:\\s*)([^;]+)/i, (whole, sep, prop, value) =>\n isColored(value) ? `${sep}${prop}${color}` : whole\n )}${close}`\n )\n );\n}\n\n/**\n * One node and what it holds, in the brand's colors.\n * @param {EmailNode} node\n * @param {ReturnType<typeof paletteOf>} palette\n * @param {boolean} onColor the node sits on a colored background\n * @returns {EmailNode}\n */\nfunction paint(node, palette, onColor) {\n const attributes = { ...node.attributes };\n let content = node.content;\n let childrenOnColor = onColor;\n const set = (/** @type {string} */ key, /** @type {string|null} */ value) => {\n if (value) attributes[key] = value;\n };\n\n if (SECTION_LIKE.has(node.tag)) {\n const background = attributes['background-color'];\n if (background && palette.primary && (isColored(background) || (isDark(background) && node.tag !== 'mj-column'))) {\n attributes['background-color'] = palette.primary;\n childrenOnColor = true;\n } else if (background && parseColor(background)) {\n childrenOnColor = false;\n }\n }\n\n if (FONT_TAGS.has(node.tag) && palette.font) set('font-family', palette.font);\n\n if (node.tag === 'mj-text' && content !== null) {\n if (onColor) {\n set('color', palette.onPrimary);\n } else {\n const heading = isBold(attributes['font-weight']) && px(attributes['font-size']) >= 18;\n if (heading && palette.heading && !isMuted(attributes.color)) attributes.color = palette.heading;\n if (palette.link) content = recolorLinks(content, palette.link);\n }\n }\n\n if (node.tag === 'mj-button') {\n const background = attributes['background-color'];\n // MJML's own default is a dark gray: a button without color is filled.\n const filled = !background || isColored(background) || isDark(background);\n if (palette.primary && filled) {\n set('background-color', onColor ? palette.onPrimary : palette.primary);\n set('color', onColor ? palette.primary : palette.onPrimary);\n } else if (palette.primary && onColor) {\n // A light button on a banner: kept light, its text in the main color.\n set('color', readableOn(background) === PALETTE.ink && contrastRatio(palette.primary, background) < 3 ? PALETTE.ink : palette.primary);\n } else if (palette.link && isColored(attributes.color)) {\n // An outlined button on white.\n attributes.color = palette.link;\n if (isColored(attributes['border-color'])) attributes['border-color'] = palette.link;\n }\n }\n\n if (node.tag === 'mj-navbar-link' && isColored(attributes.color)) {\n set('color', onColor ? palette.onPrimary : palette.link);\n }\n\n const children = node.children.map((child) => paint(child, palette, childrenOnColor));\n return { ...node, attributes, content, children };\n}\n\n/** @param {string} html */\nconst plainText = (html) =>\n html\n .replace(/<[^>]*>/g, ' ')\n .replace(/ /g, ' ')\n .replace(/&/g, '&')\n .replace(/\\s+/g, ' ')\n .trim();\n\n/** @param {EmailNode} body */\nconst sectionsOf = (body) =>\n body.children.flatMap((child) => (child.tag === 'mj-wrapper' ? child.children.filter((c) => c.tag === 'mj-section') : [child]));\n\n/**\n * The logo, or the name, at the top; the name in the footer.\n * @param {EmailNode} root\n * @param {Brand} brand\n * @param {{names: string[], logos: string[]}} previous what the email may still show\n * @returns {EmailNode}\n */\nfunction rebrandHeader(root, brand, previous) {\n const body = root.children.find((child) => child.tag === 'mj-body');\n if (!body) return root;\n const names = previous.names.map((name) => String(name || '').trim()).filter((name) => name.length >= 2);\n const known = new Set(names.map((name) => name.toLowerCase()));\n const sections = sectionsOf(body);\n /** @type {Map<string, EmailNode>} */\n const replaced = new Map();\n\n const top = sections[0];\n const column = top?.children.find((child) => child.tag === 'mj-column');\n const first = column?.children[0];\n if (first?.tag === 'mj-image') {\n const small = px(first.attributes.width) > 0 && px(first.attributes.width) <= MAX_LOGO_WIDTH;\n const wasLogo = small || previous.logos.includes(first.attributes.src) || known.has(String(first.attributes.alt || '').toLowerCase());\n if (wasLogo && brand.logoUrl) {\n replaced.set(first.id, {\n ...first,\n attributes: {\n ...first.attributes,\n src: brand.logoUrl,\n alt: brand.name || first.attributes.alt || '',\n width: `${brand.logoWidth || Math.min(px(first.attributes.width) || 140, MAX_LOGO_WIDTH)}px`,\n },\n });\n }\n } else if (first?.tag === 'mj-text' && first.content !== null && known.has(plainText(first.content).toLowerCase())) {\n if (brand.logoUrl) {\n const keep = Object.fromEntries(Object.entries(first.attributes).filter(([key]) => key.startsWith('padding') || key === 'align'));\n replaced.set(\n first.id,\n createNode('mj-image', {\n id: first.id,\n attributes: { src: brand.logoUrl, alt: brand.name || '', width: `${brand.logoWidth || 140}px`, align: 'left', ...keep },\n })\n );\n } else if (brand.name) {\n replaced.set(first.id, { ...first, content: escapeXml(brand.name) });\n }\n }\n\n // The footer: the old name in its line becomes the new one.\n const bottom = sections.length > 1 ? sections[sections.length - 1] : null;\n const newName = brand.name;\n if (bottom && newName) {\n const swap = (/** @type {EmailNode} */ node) => {\n if (node.tag === 'mj-text' && node.content !== null) {\n let html = node.content;\n for (const name of names) {\n if (name !== newName) html = html.split(escapeXml(name)).join(escapeXml(newName));\n }\n if (html !== node.content) replaced.set(node.id, { ...node, content: html });\n }\n node.children.forEach(swap);\n };\n swap(bottom);\n }\n\n if (!replaced.size) return root;\n const rebuild = (/** @type {EmailNode} */ node) => replaced.get(node.id) || { ...node, children: node.children.map(rebuild) };\n return rebuild(root);\n}\n\n/**\n * An email in the brand's look.\n * @param {string} mjml\n * @param {Brand|null|undefined} brand\n * @param {{header?: boolean, previous?: {names?: (string|undefined)[], logos?: (string|undefined)[]}}} [options]\n * `header`: also put the logo (or name) at the top and the name in the\n * footer, replacing what `previous` lists (the names and logos the email\n * may still show: the host's sender, the brand before, \"Your company\").\n * @returns {string}\n */\nexport function applyBrand(mjml, brand, { header = false, previous = {} } = {}) {\n const clean = cleanBrand(brand);\n if (!hasBrand(clean)) return mjml;\n let root = parseMjml(mjml);\n root = paint(root, paletteOf(clean), false);\n if (header) {\n root = rebrandHeader(root, clean, {\n names: /** @type {string[]} */ ((previous.names || []).filter(Boolean)),\n logos: /** @type {string[]} */ ((previous.logos || []).filter(Boolean)),\n });\n }\n return serializeMjml(root);\n}\n\n/**\n * A fragment (a block about to be inserted) in the brand's look.\n * @param {string} mjml @param {Brand|null|undefined} brand\n */\nexport function applyBrandToFragment(mjml, brand) {\n const clean = cleanBrand(brand);\n if (!mjml || !hasBrand(clean)) return mjml;\n const palette = paletteOf(clean);\n return parseMjmlFragment(mjml)\n .map((node) => serializeMjml(paint(node, palette, false), { pretty: false }))\n .join('');\n}\n\n/**\n * A ready-made email (the starter, a gallery template) in the brand's look,\n * its header and footer naming the brand rather than the host's sender or\n * the \"Your company\" placeholder.\n * @param {string} mjml\n * @param {Brand|null|undefined} brand\n * @param {{sender?: import('../templates/starter.js').StarterSender|null, t?: (key: string) => string}} [context]\n */\nexport function brandTemplate(mjml, brand, { sender = null, t = (key) => key } = {}) {\n return applyBrand(mjml, brand, {\n header: true,\n previous: { names: [sender?.name, t('Your company')], logos: [sender?.logoUrl] },\n });\n}\n","// @ts-check\n/**\n * TAGS — colored labels on templates, to sort a library at a glance.\n *\n * The workspace keeps one list of tags, shared by every user like the custom\n * variables; a template carries at most `MAX_TEMPLATE_TAGS` of them, by id.\n * A tag's name is unique (case aside) and its color is one of `TAG_COLORS`,\n * so every tag reads well in the light and the dark theme.\n *\n * The same rules run in the browser (the tags dialog, the memory storage) and\n * on the server (`best-email-builder/server`).\n */\n\nimport { identity } from '../../i18n/index.js';\n\n/** @typedef {{id: string, label: string, color: string}} TemplateTag */\n\nexport const MAX_TEMPLATE_TAGS = 3;\n/** Tags in a workspace. */\nexport const MAX_TAGS = 100;\nexport const TAG_LIMITS = { label: 24 };\nexport const TAG_COLORS = ['gray', 'red', 'orange', 'amber', 'lime', 'green', 'teal', 'cyan', 'blue', 'violet', 'fuchsia', 'pink'];\n\nconst sameName = (a, b) => a.localeCompare(b, undefined, { sensitivity: 'accent' }) === 0;\n\n/**\n * @param {any} input `{label, color}`\n * @param {{existing?: TemplateTag[], editing?: string|null, t?: import('../../i18n/index.js').Translate}} [options]\n * `editing`: the id of the tag being edited (its own name is not a duplicate)\n * @returns {{tag: {label: string, color: string}, errors?: undefined} | {tag?: undefined, errors: Record<string, string>}}\n */\nexport function validateTag(input, { existing = [], editing = null, t = identity } = {}) {\n /** @type {Record<string, string>} */\n const errors = {};\n const label = String(input?.label ?? '').replace(/\\s+/g, ' ').trim();\n const color = String(input?.color ?? '');\n if (!label) errors.label = t('Name is required');\n else if (label.length > TAG_LIMITS.label) errors.label = t('{max} characters at most', { max: TAG_LIMITS.label });\n else if (existing.some((tag) => String(tag.id) !== String(editing) && sameName(tag.label, label))) {\n errors.label = t('A tag with this name already exists');\n }\n if (!TAG_COLORS.includes(color)) errors.color = t('Choose a color');\n if (Object.keys(errors).length) return { errors };\n return { tag: { label, color } };\n}\n\n/**\n * The tags a template may carry: known ids, each once, `MAX_TEMPLATE_TAGS`\n * at most, in the list's order.\n *\n * @param {unknown} ids\n * @param {TemplateTag[]} catalog\n * @param {{t?: import('../../i18n/index.js').Translate}} [options]\n * @returns {{ids: string[], error?: undefined} | {ids?: undefined, error: {code: string, message: string}}}\n */\nexport function checkTemplateTags(ids, catalog, { t = identity } = {}) {\n if (!Array.isArray(ids)) return { error: { code: 'INVALID_TAGS', message: t('A list of tags is expected') } };\n const wanted = new Set(ids.map(String));\n if (wanted.size > MAX_TEMPLATE_TAGS) {\n return { error: { code: 'TOO_MANY_TAGS', message: t('{max} tags at most per template', { max: MAX_TEMPLATE_TAGS }) } };\n }\n const known = new Set(catalog.map((tag) => String(tag.id)));\n if ([...wanted].some((id) => !known.has(id))) return { error: { code: 'UNKNOWN_TAG', message: t('Tag not found') } };\n return { ids: catalog.map((tag) => String(tag.id)).filter((id) => wanted.has(id)) };\n}\n\n/** The list's order: by name. @param {TemplateTag[]} tags */\nexport const sortTags = (tags) => [...tags].sort((a, b) => a.label.localeCompare(b.label));\n","// @ts-check\n/**\n * Images UPLOADED in the builder — one rule shared by the browser (immediate\n * rejection, storage/client.js) and the server (storage/server/assetFile.js).\n *\n * PNG, JPEG, GIF: the three formats every email client displays. WebP and SVG\n * do not display in Outlook, and an SVG can carry script.\n * The server reads the type from the file BYTES; the browser, from `file.type`.\n */\n\nimport { identity } from '../i18n/index.js';\n\n/** Maximum image size: beyond it, the email becomes slow to open. */\nexport const MAX_IMAGE_BYTES = 2 * 1024 * 1024;\n\n/** Accepted types → extension of the public key. */\nexport const IMAGE_TYPES = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif' };\n\n/** Public key: 32 hex characters of the hash + extension. */\nexport const ASSET_KEY_PATTERN = /^[a-f0-9]{32}\\.(png|jpg|gif)$/;\n\nconst SIGNATURES = [\n { type: 'image/png', bytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] },\n { type: 'image/jpeg', bytes: [0xff, 0xd8, 0xff] },\n { type: 'image/gif', bytes: [0x47, 0x49, 0x46, 0x38] },\n];\n\n/**\n * Image type detected from the file signature, or `null`.\n * @param {Uint8Array} bytes\n * @returns {{type: string, ext: string}|null}\n */\nexport function detectImage(bytes) {\n if (!bytes || bytes.length < 8) return null;\n const found = SIGNATURES.find((s) => s.bytes.every((b, i) => bytes[i] === b));\n return found ? { type: found.type, ext: IMAGE_TYPES[found.type] } : null;\n}\n\n/**\n * Immediate rejection of a file the server would refuse, or `null`.\n * @param {{type?: string, size?: number}} file\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function imageUploadError(file, { t = identity } = {}) {\n if (!(String(file?.type || '') in IMAGE_TYPES)) return t('Format rejected: PNG, JPEG or GIF expected');\n if (Number(file?.size || 0) > MAX_IMAGE_BYTES) {\n return t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) });\n }\n return null;\n}\n"],"mappings":"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","names":["prop"]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
parseColor,
|
|
6
6
|
toHex,
|
|
7
7
|
toHsl
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-NPE7WFT5.js";
|
|
9
9
|
|
|
10
10
|
// src/renderer/prepareMjml.js
|
|
11
11
|
var MJ_IMAGE = /<mj-image\b((?:[^>"']|"[^"]*"|'[^']*')*?)\s*(?:\/>|>\s*<\/mj-image\s*>)/gi;
|
|
@@ -353,4 +353,4 @@ export {
|
|
|
353
353
|
countImagesWithoutPicture,
|
|
354
354
|
prepareMjml
|
|
355
355
|
};
|
|
356
|
-
//# sourceMappingURL=chunk-
|
|
356
|
+
//# sourceMappingURL=chunk-P6IEVWZY.js.map
|
|
@@ -15,10 +15,10 @@ import {
|
|
|
15
15
|
TabsTrigger,
|
|
16
16
|
cn,
|
|
17
17
|
useUi
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-2IDNT724.js";
|
|
19
19
|
import {
|
|
20
20
|
EMAIL_MOBILE_WIDTH
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-NPE7WFT5.js";
|
|
22
22
|
|
|
23
23
|
// src/features/templates/thumbnails.js
|
|
24
24
|
var THUMBNAIL_IMAGE = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 300'%3E%3Crect width='600' height='300' fill='%23dfe6f0'/%3E%3C/svg%3E";
|
|
@@ -291,4 +291,4 @@ export {
|
|
|
291
291
|
withPlaceholderImages,
|
|
292
292
|
useLayoutWidth
|
|
293
293
|
};
|
|
294
|
-
//# sourceMappingURL=chunk-
|
|
294
|
+
//# sourceMappingURL=chunk-RVRKAXYC.js.map
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useLayoutWidth,
|
|
6
6
|
useSearchShortcut,
|
|
7
7
|
withPlaceholderImages
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-RVRKAXYC.js";
|
|
9
9
|
import {
|
|
10
10
|
Button,
|
|
11
11
|
Dialog,
|
|
@@ -23,14 +23,14 @@ import {
|
|
|
23
23
|
cn,
|
|
24
24
|
createHttpStorage,
|
|
25
25
|
useUi
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2IDNT724.js";
|
|
27
27
|
import {
|
|
28
28
|
MAX_TEMPLATE_TAGS,
|
|
29
29
|
TAG_COLORS,
|
|
30
30
|
TAG_LIMITS,
|
|
31
31
|
sortTags,
|
|
32
32
|
validateTag
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-NPE7WFT5.js";
|
|
34
34
|
|
|
35
35
|
// src/ui/library/TemplateLibrary.jsx
|
|
36
36
|
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
@@ -531,10 +531,14 @@ function Library({
|
|
|
531
531
|
destructive: true
|
|
532
532
|
});
|
|
533
533
|
if (!ok) return;
|
|
534
|
+
const visible = [...favoriteEntries, ...otherTemplates];
|
|
535
|
+
const at = visible.findIndex((e) => e.key === selection);
|
|
536
|
+
const next = at === -1 ? null : visible[at + 1] || visible[at - 1] || null;
|
|
534
537
|
setBusy("delete");
|
|
535
538
|
try {
|
|
536
539
|
await storage.remove(template.id);
|
|
537
|
-
|
|
540
|
+
setItems((s) => ({ ...s, list: s.list.filter((item) => String(item.id) !== String(template.id)) }));
|
|
541
|
+
setSelection(next?.key ?? null);
|
|
538
542
|
setDetailsOpen(false);
|
|
539
543
|
await reload();
|
|
540
544
|
notify("success", t("Template deleted."));
|
|
@@ -862,4 +866,4 @@ function TemplateLibrary({ locale = "en", messages, onNotify, ...props }) {
|
|
|
862
866
|
export {
|
|
863
867
|
TemplateLibrary
|
|
864
868
|
};
|
|
865
|
-
//# sourceMappingURL=chunk-
|
|
869
|
+
//# sourceMappingURL=chunk-SYQC7JNA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/library/TemplateLibrary.jsx","../src/ui/library/TagBadge.jsx","../src/ui/library/TagsDialog.jsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AlertCircle,\n ArrowLeft,\n Copy,\n Download,\n LayoutTemplate,\n Loader2,\n PencilRuler,\n RefreshCw,\n Search,\n Star,\n Tags,\n Trash2,\n X,\n} from 'lucide-react';\nimport { withPlaceholderImages } from '../../features/templates/thumbnails.js';\nimport { EmailPreview } from '../../features/preview/PreviewButton.jsx';\nimport { createHttpStorage } from '../../storage/client.js';\nimport { NoticeArea, UiProvider, useUi } from '../context.jsx';\nimport { Button } from '../primitives/button.jsx';\nimport { cn } from '../primitives/cn.js';\nimport { Input } from '../primitives/fields.jsx';\nimport { FilterChip, FilterReset, SectionLabel } from '../primitives/misc.jsx';\nimport { SEARCH_KEYS, SearchKeyHint, useSearchShortcut } from '../primitives/searchShortcut.jsx';\nimport { useLayoutWidth } from '../useLayoutWidth.js';\nimport { TAG_SWATCHES, TagBadge } from './TagBadge.jsx';\nimport TagsDialog from './TagsDialog.jsx';\n\n/**\n * TEMPLATE LIBRARY — the first screen: pick a template, see it, open it.\n *\n * ┌──────────────────────────────────────────────────────────────────┐\n * │ ▣ Email template library × │\n * ├──────────────┬──────────────────────────┬────────────────────────┤\n * │ New template │ Name ☆ │ Desktop | Mobile │\n * │ 🔍 Ctrl K │ code │ ┌────────────────────┐ │\n * │ (All)(●Promo)│ Origin, subject, dates… │ │ rendered email │ │\n * │ FAVORITES │ │ │ │ │\n * │ TEMPLATES │ Tags [Promo] [Events] ✎ │ │ │ │\n * │ │ [ Edit ] [ Duplicate ] │ │ │ │\n * │ │ [Copy HTML] [Download] 🗑 │ └────────────────────┘ │\n * └──────────────┴──────────────────────────┴────────────────────────┘\n *\n * Standalone: it never mounts the editor. \"Edit\" and \"New template\" only\n * call `onEdit(template)` / `onCreate()`, and the host decides what opens —\n * `EmailBuilder` (see `EmailStudio`, which wires the two), its own screen, a\n * route. Likewise `EmailBuilder` works without it; a new template starts\n * from a base there, with its *Templates* button.\n *\n * Templates `storage.list()`; preview through `storage.render()` when the\n * storage has it, else the HTML compiled at the last save.\n * *Copy HTML* and *Download* take that HTML (`load()`).\n * Favorites the star of the details. A favorite leaves its section for the\n * one at the top. Kept in this browser (`favoritesKey`, `null`\n * turns favorites off).\n * Tags up to three colored tags on a template, chosen and created in\n * the tags dialog, when the storage has them (`listTags`…).\n * The search finds them too, and the tags under it filter\n * the list: one or several (a template with any of them), or\n * *All*. Only the tags some template carries are offered.\n *\n * Layout follows the library's own width, like the builder: three columns,\n * then the preview under the details, then (phone) list and details as two\n * screens.\n */\n\nconst DATE_FORMAT = { day: '2-digit', month: '2-digit', year: 'numeric' };\nconst DEFAULT_FAVORITES_KEY = 'best-email-builder:favorites';\n\nconst matches = (query, ...fields) => {\n const q = query.trim().toLowerCase();\n return !q || fields.some((f) => String(f || '').toLowerCase().includes(q));\n};\n\n/** `October newsletter` → `october_newsletter`: the code line under a name. */\nconst codeOf = (name, fallback) =>\n String(name || '')\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 48) || fallback;\n\n/** Keys of the starred entries: `template:<id>`. */\nfunction useFavorites(storageKey) {\n const [favorites, setFavorites] = useState(() => {\n if (!storageKey) return new Set();\n try {\n return new Set(JSON.parse(localStorage.getItem(storageKey) || '[]'));\n } catch {\n return new Set();\n }\n });\n const toggle = useCallback(\n (key) =>\n setFavorites((current) => {\n const next = new Set(current);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n try {\n localStorage.setItem(storageKey, JSON.stringify([...next]));\n } catch {\n // Private window, blocked storage: the star holds until the page closes.\n }\n return next;\n }),\n [storageKey]\n );\n return { enabled: Boolean(storageKey), favorites, toggle };\n}\n\nfunction download(name, html) {\n const blob = new Blob([html], { type: 'text/html;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `${codeOf(name, 'email')}.html`;\n a.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction Row({ entry, selected, favorite, onSelect }) {\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n aria-current={selected ? 'true' : undefined}\n data-testid=\"eb-library-template\"\n className={cn(\n 'eb:w-full eb:rounded-lg2 eb:border eb:px-2.5 eb:py-2 eb:text-left eb:transition-colors eb:outline-none',\n 'eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n selected ? 'eb:border-primary/50 eb:bg-primary/5' : 'eb:border-transparent eb:hover:bg-surface-75'\n )}\n >\n <span className=\"eb:flex eb:items-center eb:gap-1.5\">\n <span className=\"eb:min-w-0 eb:flex-1 eb:truncate eb:text-[13px] eb:text-foreground\">{entry.name}</span>\n {favorite && <Star className=\"eb:size-3.5 eb:shrink-0 eb:fill-current eb:text-warning\" aria-hidden=\"true\" />}\n </span>\n <span className=\"eb:mt-0.5 eb:block eb:truncate eb:font-mono eb:text-[11px] eb:text-foreground-lighter\">{entry.code}</span>\n {/* One line whatever the tags: too many fade out before the date, and\n every row keeps the same height. */}\n <span className=\"eb:mt-1 eb:flex eb:min-h-[18px] eb:items-center eb:gap-2\">\n <span className=\"eb:flex eb:min-w-0 eb:flex-1 eb:gap-1 eb:overflow-hidden eb:[mask-image:linear-gradient(to_right,#000_calc(100%-1.5rem),transparent)]\">\n {entry.tags.map((tag) => (\n <TagBadge key={tag.id} tag={tag} className=\"eb:max-w-[7.5rem]\" />\n ))}\n </span>\n {entry.date && <span className=\"eb:shrink-0 eb:text-[10.5px] eb:text-foreground-muted\">{entry.date}</span>}\n </span>\n </button>\n );\n}\n\nfunction Section({ label, children, ...props }) {\n return (\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\" {...props}>\n <SectionLabel>{label}</SectionLabel>\n {children}\n </div>\n );\n}\n\nfunction Library({\n storage: injectedStorage,\n title,\n description,\n selectedId = null,\n favoritesKey = DEFAULT_FAVORITES_KEY,\n deleteDisabled = null,\n onEdit,\n onCreate,\n onClose,\n className,\n}) {\n const { t, locale, notify, confirm } = useUi();\n const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);\n const canTag = typeof storage.listTags === 'function' && typeof storage.setTemplateTags === 'function';\n const dateFormat = useMemo(() => new Intl.DateTimeFormat(locale, DATE_FORMAT), [locale]);\n const formatDate = useCallback((at) => (at ? dateFormat.format(new Date(at)) : ''), [dateFormat]);\n const stars = useFavorites(favoritesKey);\n\n const rootRef = useRef(null);\n const searchRef = useRef(null);\n const layout = useLayoutWidth(rootRef);\n\n const [items, setItems] = useState({ status: 'loading', list: [], error: null });\n /** The workspace's tags, by name. */\n const [tags, setTags] = useState([]);\n const [tagsOpen, setTagsOpen] = useState(false);\n const [query, setQuery] = useState('');\n /** The tags picked under the search (ids); none is *All*. */\n const [picked, setPicked] = useState(() => new Set());\n /** `template:<id>` */\n const [selection, setSelection] = useState(selectedId != null ? `template:${selectedId}` : null);\n /** Phone layout: the details screen is shown over the list. */\n const [detailsOpen, setDetailsOpen] = useState(false);\n const [detail, setDetail] = useState({ status: 'idle', template: null, error: null });\n const [preview, setPreview] = useState({ html: '', loading: false, error: null });\n const [device, setDevice] = useState('desktop');\n const [busy, setBusy] = useState(null);\n\n // Only the LAST read counts: a slow answer arriving after a newer one\n // (opening, then a duplicate) would show a stale list.\n const lastRead = useRef(0);\n const reload = useCallback(async () => {\n const ticket = ++lastRead.current;\n if (typeof storage.list !== 'function') {\n setItems({ status: 'ready', list: [], error: null });\n return;\n }\n setItems((s) => ({ ...s, status: s.list.length ? 'ready' : 'loading', error: null }));\n try {\n // Tags that cannot be read leave the templates readable, untagged.\n const [list, tagList] = await Promise.all([storage.list(), canTag ? storage.listTags().catch(() => []) : []]);\n if (ticket === lastRead.current) {\n setItems({ status: 'ready', list, error: null });\n setTags(tagList);\n }\n } catch (error) {\n if (ticket === lastRead.current) setItems({ status: 'error', list: [], error: error.message });\n }\n }, [storage, canTag]);\n\n useEffect(() => {\n reload();\n }, [reload]);\n\n /* ── entries ───────────────────────────────────────────────────────────── */\n\n const templateEntries = useMemo(() => {\n const byId = new Map(tags.map((tag) => [String(tag.id), tag]));\n return items.list.map((item) => ({\n key: `template:${item.id}`,\n id: item.id,\n name: item.name,\n code: codeOf(item.name, `#${item.id}`),\n tags: (item.tags || []).map((id) => byId.get(String(id))).filter(Boolean),\n date: formatDate(item.updatedAt || item.createdAt),\n subject: item.subject,\n item,\n }));\n }, [items.list, tags, formatDate]);\n /** The filters: the tags some template carries, by name. */\n const usedTags = useMemo(() => {\n const used = new Set(items.list.flatMap((item) => (item.tags || []).map(String)));\n return tags.filter((tag) => used.has(String(tag.id)));\n }, [items.list, tags]);\n // A picked tag deleted or left by every template no longer filters.\n const active = new Set(usedTags.map((tag) => String(tag.id)).filter((id) => picked.has(id)));\n const togglePicked = (id) =>\n setPicked((current) => {\n const next = new Set(current);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n const shown = templateEntries.filter(\n (e) =>\n (active.size === 0 || e.tags.some((tag) => active.has(String(tag.id)))) &&\n matches(query, e.name, e.code, e.subject, ...e.tags.map((tag) => tag.label))\n );\n const favoriteEntries = shown.filter((e) => stars.favorites.has(e.key));\n const otherTemplates = shown.filter((e) => !stars.favorites.has(e.key));\n\n // First selection: the requested template, else the most recent one.\n useEffect(() => {\n if (items.status !== 'ready' || selection) return;\n if (items.list.length) setSelection(`template:${items.list[0].id}`);\n }, [items, selection]);\n\n // Ctrl/Cmd+K focuses the search.\n useSearchShortcut(searchRef);\n\n const selected = templateEntries.find((e) => e.key === selection) || null;\n const templateId = selection?.startsWith('template:') ? selection.slice('template:'.length) : null;\n\n // Details of a saved template (MJML and compiled HTML are not in the list).\n useEffect(() => {\n if (templateId == null) {\n setDetail({ status: 'idle', template: null, error: null });\n return undefined;\n }\n let live = true;\n setDetail((d) => ({ status: 'loading', template: String(d.template?.id) === templateId ? d.template : null, error: null }));\n storage.load(templateId).then(\n (template) => live && setDetail({ status: 'ready', template, error: null }),\n (error) => live && setDetail({ status: 'error', template: null, error: error.message })\n );\n return () => {\n live = false;\n };\n }, [templateId, storage]);\n\n // Preview: rendered by the storage (server) when it can, else the saved HTML.\n const template = detail.template;\n const source = template\n ? { mjml: template.mjml, subject: template.subject, preheader: template.preheader, html: template.html || '' }\n : null;\n const sourceKey = template ? `template:${template.id}:${template.revision}` : '';\n useEffect(() => {\n if (!source) {\n setPreview({ html: '', loading: templateId != null && detail.status === 'loading', error: null });\n return undefined;\n }\n if (typeof storage.render !== 'function' || !source.mjml) {\n setPreview({ html: source.html, loading: false, error: source.html ? null : t('No preview: this storage cannot render emails.') });\n return undefined;\n }\n let live = true;\n setPreview((p) => ({ ...p, loading: true, error: null }));\n storage.render({ mjml: source.mjml, subject: source.subject, preheader: source.preheader }).then(\n (result) => live && setPreview({ html: withPlaceholderImages(result.html), loading: false, error: null }),\n (error) => live && setPreview({ html: source.html, loading: false, error: source.html ? null : error.message })\n );\n return () => {\n live = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceKey, storage]);\n\n /* ── actions ───────────────────────────────────────────────────────────── */\n\n const select = (key) => {\n setSelection(key);\n setDetailsOpen(true);\n };\n\n const remove = async () => {\n if (deleteDisabled) {\n notify('info', deleteDisabled);\n return;\n }\n const ok = await confirm({\n title: t('Delete “{name}”?', { name: template.name }),\n description: t('The template and its versions will be deleted.'),\n confirmLabel: t('Delete'),\n destructive: true,\n });\n if (!ok) return;\n // The next template of the list takes its place (the previous one at the end).\n const visible = [...favoriteEntries, ...otherTemplates];\n const at = visible.findIndex((e) => e.key === selection);\n const next = at === -1 ? null : visible[at + 1] || visible[at - 1] || null;\n setBusy('delete');\n try {\n await storage.remove(template.id);\n // Out of the list at once: the first selection must not pick it again before the reload.\n setItems((s) => ({ ...s, list: s.list.filter((item) => String(item.id) !== String(template.id)) }));\n setSelection(next?.key ?? null);\n setDetailsOpen(false);\n await reload();\n notify('success', t('Template deleted.'));\n } catch (error) {\n notify('error', error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const duplicate = async () => {\n setBusy('duplicate');\n try {\n const { template: copy } = await storage.duplicate(template.id);\n await reload();\n setSelection(`template:${copy.id}`);\n notify('success', t('Template duplicated.'));\n } catch (error) {\n notify('error', error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const copyHtml = async () => {\n try {\n await navigator.clipboard.writeText(template.html);\n notify('success', t('HTML copied.'));\n } catch {\n notify('error', t('The browser refused to copy.'));\n }\n };\n\n /** The selected template's tags, as the dialog sets them. @param {string[]} ids */\n const setTemplateTags = (ids) =>\n setItems((s) => ({ ...s, list: s.list.map((item) => (String(item.id) === templateId ? { ...item, tags: ids } : item)) }));\n /** The list of tags after a change; a deleted tag leaves every template. */\n const changeTags = (list, { deleted } = {}) => {\n setTags(list);\n if (deleted == null) return;\n setItems((s) => ({\n ...s,\n list: s.list.map((item) => (item.tags?.includes(deleted) ? { ...item, tags: item.tags.filter((id) => id !== deleted) } : item)),\n }));\n };\n\n const canList = typeof storage.list === 'function';\n const rowProps = (entry) => ({\n entry,\n selected: entry.key === selection,\n favorite: stars.favorites.has(entry.key),\n onSelect: () => select(entry.key),\n });\n\n /* ── left column ───────────────────────────────────────────────────────── */\n\n const listColumn = (\n <div key=\"list\" className=\"eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col\" data-testid=\"eb-library-list\">\n <div className=\"eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-b eb:border-border eb:p-3\">\n {onCreate && (\n <Button className=\"eb:w-full\" onClick={() => onCreate()}>\n <PencilRuler />\n {t('New template')}\n </Button>\n )}\n <div className=\"eb:relative\">\n <Search className=\"eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted\" />\n <Input\n ref={searchRef}\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Escape' && query) {\n e.preventDefault();\n setQuery('');\n }\n }}\n placeholder={t('Search templates')}\n aria-label={t('Search templates')}\n aria-keyshortcuts={SEARCH_KEYS}\n className=\"eb:pr-16 eb:pl-8 eb:[&::-webkit-search-cancel-button]:hidden\"\n />\n <div className=\"eb:absolute eb:top-1/2 eb:right-2 eb:flex eb:-translate-y-1/2 eb:items-center eb:gap-1\">\n {query && (\n <button type=\"button\" onClick={() => setQuery('')} aria-label={t('Clear the search')} className=\"eb:cursor-pointer\">\n <X className=\"eb:size-3.5 eb:text-foreground-muted eb:hover:text-foreground\" />\n </button>\n )}\n <SearchKeyHint>{query ? 'Esc' : null}</SearchKeyHint>\n </div>\n </div>\n {usedTags.length > 0 && (\n <div className=\"eb:flex eb:items-start eb:gap-2.5\">\n {/* No tag picked: every template. */}\n <div\n role=\"group\"\n aria-label={t('Filter by tag')}\n className=\"eb:-m-1 eb:flex eb:max-h-[4.75rem] eb:min-w-0 eb:flex-1 eb:flex-wrap eb:gap-1 eb:overflow-y-auto eb:p-1\"\n data-testid=\"eb-library-tag-filter\"\n >\n {usedTags.map((tag) => (\n <FilterChip\n key={tag.id}\n pressed={active.has(String(tag.id))}\n onClick={() => togglePicked(String(tag.id))}\n className=\"eb:h-6 eb:max-w-full eb:px-2.5 eb:text-[11.5px]\"\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n 'eb:size-2 eb:shrink-0 eb:rounded-full',\n TAG_SWATCHES[tag.color] || TAG_SWATCHES.gray,\n active.has(String(tag.id)) && 'eb:ring-1 eb:ring-primary-foreground'\n )}\n />\n <span className=\"eb:truncate\">{tag.label}</span>\n </FilterChip>\n ))}\n </div>\n <FilterReset\n label={t('Clear the tag filters')}\n active={active.size > 0}\n onClick={() => setPicked(new Set())}\n className=\"eb:size-6\"\n data-testid=\"eb-library-tag-reset\"\n />\n </div>\n )}\n </div>\n\n <div className=\"eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-3\">\n {stars.enabled && favoriteEntries.length > 0 && (\n <Section label={t('Favorites')} role=\"group\" aria-label={t('Favorites')} data-testid=\"eb-library-favorites\">\n {favoriteEntries.map((entry) => (\n <Row key={entry.key} {...rowProps(entry)} />\n ))}\n </Section>\n )}\n {canList && (\n <Section label={`${t('Templates')} (${otherTemplates.length})`} data-testid=\"eb-library-templates\">\n {items.status === 'loading' ? (\n <p className=\"eb:m-0 eb:flex eb:items-center eb:gap-2 eb:px-1 eb:text-[12px] eb:text-foreground-lighter\">\n <Loader2 className=\"eb:size-3.5 eb:animate-spin\" />\n {t('Loading…')}\n </p>\n ) : items.status === 'error' ? (\n <div className=\"eb:px-1 eb:text-[12px] eb:text-destructive\">\n <p className=\"eb:m-0\">{t('Could not load the templates.')}</p>\n <p className=\"eb:m-0 eb:mt-0.5 eb:text-foreground-muted\">{items.error}</p>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:mt-2\" onClick={reload}>\n <RefreshCw />\n {t('Retry')}\n </Button>\n </div>\n ) : otherTemplates.length === 0 ? (\n <p className=\"eb:m-0 eb:px-1 eb:text-[12px] eb:leading-relaxed eb:text-foreground-lighter\">\n {query\n ? t('Nothing matches “{query}”.', { query })\n : templateEntries.length\n ? t('All your templates are in the favorites.')\n : t('No template yet: create the first one.')}\n </p>\n ) : (\n otherTemplates.map((entry) => <Row key={entry.key} {...rowProps(entry)} />)\n )}\n </Section>\n )}\n </div>\n </div>\n );\n\n /* ── details ───────────────────────────────────────────────────────────── */\n\n const meta =\n selected || template\n ? {\n name: template?.name ?? selected.name,\n code: selected?.code ?? codeOf(template?.name, `#${templateId}`),\n description: template?.description ?? selected?.item?.description,\n origin: t('Builder'),\n subject: (template ?? selected?.item)?.subject,\n preheader: (template ?? selected?.item)?.preheader,\n createdAt: (template ?? selected?.item)?.createdAt,\n createdBy: (template ?? selected?.item)?.createdBy,\n updatedAt: (template ?? selected?.item)?.updatedAt,\n updatedBy: (template ?? selected?.item)?.updatedBy,\n }\n : null;\n const favorite = selection ? stars.favorites.has(selection) : false;\n\n const detailsBody =\n templateId != null && detail.status === 'error' ? (\n <p className=\"eb:m-0 eb:flex eb:items-start eb:gap-2 eb:text-[13px] eb:text-destructive\">\n <AlertCircle className=\"eb:mt-0.5 eb:size-4 eb:shrink-0\" />\n {detail.error}\n </p>\n ) : meta ? (\n <>\n <div className=\"eb:flex eb:flex-col eb:gap-1\">\n <div className=\"eb:flex eb:items-center eb:gap-2\">\n <h3 className=\"eb:m-0 eb:min-w-0 eb:flex-1 eb:text-[15px] eb:font-medium eb:text-foreground\">{meta.name}</h3>\n {stars.enabled && (\n <Button\n variant=\"ghost\"\n size=\"icon-sm-compact\"\n className={cn('eb:shrink-0', favorite ? 'eb:text-warning' : 'eb:text-foreground-muted eb:hover:text-warning')}\n onClick={() => stars.toggle(selection)}\n aria-pressed={favorite}\n aria-label={favorite ? t('Remove from favorites') : t('Add to favorites')}\n title={favorite ? t('Remove from favorites') : t('Add to favorites')}\n >\n <Star className={cn('eb:size-4', favorite && 'eb:fill-current')} />\n </Button>\n )}\n </div>\n <p className=\"eb:m-0 eb:font-mono eb:text-[11.5px] eb:text-foreground-lighter\">{meta.code}</p>\n {meta.description && (\n <p className=\"eb:m-0 eb:text-[12.5px] eb:leading-relaxed eb:text-foreground-lighter\">{meta.description}</p>\n )}\n </div>\n\n <dl className=\"eb:m-0 eb:grid eb:grid-cols-[110px_minmax(0,1fr)] eb:gap-x-3 eb:gap-y-1.5 eb:text-[12px]\">\n <dt className=\"eb:text-foreground-muted\">{t('Origin')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">{meta.origin}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Subject')}</dt>\n <dd className=\"eb:m-0 eb:break-words eb:text-foreground\">{meta.subject || '—'}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Preview text')}</dt>\n <dd className=\"eb:m-0 eb:break-words eb:text-foreground-lighter\">{meta.preheader || '—'}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Created')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">\n {formatDate(meta.createdAt) || '—'}\n {meta.createdBy ? ` ${t('by {name}', { name: meta.createdBy })}` : ''}\n </dd>\n {meta.updatedAt && (\n <>\n <dt className=\"eb:text-foreground-muted\">{t('Updated')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">\n {formatDate(meta.updatedAt)}\n {meta.updatedBy ? ` ${t('by {name}', { name: meta.updatedBy })}` : ''}\n </dd>\n </>\n )}\n {canTag && selected && (\n <>\n <dt className=\"eb:flex eb:h-6 eb:items-center eb:text-foreground-muted\">{t('Tags')}</dt>\n <dd className=\"eb:m-0 eb:flex eb:min-w-0 eb:flex-wrap eb:items-center eb:gap-1\" data-testid=\"eb-library-tags\">\n {selected.tags.map((tag) => (\n <TagBadge key={tag.id} tag={tag} className=\"eb:text-[11.5px]\" />\n ))}\n <Button\n variant=\"ghost\"\n size=\"xs\"\n className=\"eb:text-foreground-lighter\"\n onClick={() => setTagsOpen(true)}\n data-testid=\"eb-library-tags-button\"\n >\n <Tags />\n {selected.tags.length ? t('Edit tags') : t('Add tags')}\n </Button>\n </dd>\n </>\n )}\n </dl>\n </>\n ) : (\n <div className=\"eb:flex eb:flex-1 eb:items-center eb:justify-center\">\n <div className=\"eb:flex eb:w-full eb:flex-col eb:items-center eb:justify-center eb:gap-2 eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:px-6 eb:py-12 eb:text-center\">\n <span className=\"eb:flex eb:size-12 eb:items-center eb:justify-center eb:rounded-full eb:bg-surface-200 eb:text-foreground-muted\">\n <LayoutTemplate className=\"eb:size-6\" />\n </span>\n <p className=\"eb:m-0 eb:text-[13px] eb:font-medium eb:text-foreground\">{t('Choose a template')}</p>\n <p className=\"eb:m-0 eb:max-w-sm eb:text-[13px] eb:leading-relaxed eb:text-foreground-muted\">\n {t('Select a template on the left.')}\n </p>\n </div>\n </div>\n );\n\n const actions = template ? (\n <>\n <div className=\"eb:flex eb:gap-2\">\n {onEdit && (\n <Button className=\"eb:flex-1\" onClick={() => onEdit(template)}>\n <PencilRuler />\n {t('Edit')}\n </Button>\n )}\n <Button variant=\"outline\" className=\"eb:flex-1\" disabled={!!busy} onClick={duplicate}>\n {busy === 'duplicate' ? <Loader2 className=\"eb:animate-spin\" /> : <Copy />}\n {t('Duplicate')}\n </Button>\n </div>\n {(template.html || typeof storage.remove === 'function') && (\n <div className=\"eb:flex eb:gap-2\">\n {template.html && (\n <>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:flex-1\" onClick={copyHtml}>\n <Copy />\n {t('Copy HTML')}\n </Button>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:flex-1\" onClick={() => download(template.name, template.html)}>\n <Download />\n {t('Download')}\n </Button>\n </>\n )}\n {typeof storage.remove === 'function' && (\n <Button\n variant=\"outline\"\n size=\"sm-compact\"\n className={cn('eb:text-destructive', !template.html && 'eb:ml-auto', deleteDisabled && 'eb:opacity-50 eb:hover:bg-background eb:hover:text-destructive')}\n disabled={!!busy}\n onClick={remove}\n aria-disabled={deleteDisabled ? true : undefined}\n aria-label={t('Delete')}\n title={deleteDisabled || t('Delete')}\n >\n {busy === 'delete' ? <Loader2 className=\"eb:animate-spin\" /> : <Trash2 />}\n {!template.html && t('Delete')}\n </Button>\n )}\n </div>\n )}\n </>\n ) : null;\n\n const previewPane =\n preview.html || preview.loading || preview.error ? (\n <EmailPreview html={preview.html} loading={preview.loading} error={preview.error} device={device} onDeviceChange={setDevice} />\n ) : (\n <div className=\"eb:flex eb:h-full eb:min-h-40 eb:items-center eb:justify-center eb:rounded-lg2 eb:border eb:border-dashed eb:border-border eb:p-6 eb:text-center eb:text-[13px] eb:text-foreground-lighter\">\n {t('The template preview appears here.')}\n </div>\n );\n\n const detailsColumn = (\n <div key=\"details\" className=\"eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col\" data-testid=\"eb-library-details\">\n {layout === 'narrow' && (\n <div className=\"eb:shrink-0 eb:border-b eb:border-border eb:px-2 eb:py-1.5\">\n <Button variant=\"ghost\" size=\"sm-compact\" onClick={() => setDetailsOpen(false)}>\n <ArrowLeft />\n {t('All templates')}\n </Button>\n </div>\n )}\n <div className=\"eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-4\">\n {detailsBody}\n {layout !== 'wide' && meta && <div className=\"eb:h-[560px] eb:shrink-0\">{previewPane}</div>}\n </div>\n {actions && <div className=\"eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-t eb:border-border eb:p-3\">{actions}</div>}\n </div>\n );\n\n return (\n <div\n ref={rootRef}\n className={cn('eb-root eb:relative eb:flex eb:h-full eb:min-h-0 eb:flex-col eb:bg-background eb:text-foreground', className)}\n data-testid=\"template-library\"\n data-layout={layout}\n >\n <header className=\"eb:flex eb:shrink-0 eb:items-center eb:gap-3 eb:border-b eb:border-border eb:px-5 eb:py-3.5\">\n <span className=\"eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-lg2 eb:bg-primary/10 eb:text-primary\">\n <LayoutTemplate className=\"eb:size-5\" />\n </span>\n <div className=\"eb:min-w-0 eb:flex-1\">\n <h2 className=\"eb:m-0 eb:truncate eb:text-base eb:font-normal eb:text-foreground\">{title ?? t('Email template library')}</h2>\n <p className=\"eb:m-0 eb:truncate eb:text-[13px] eb:text-foreground-muted\">\n {description ?? t('Ready-made templates to copy into your campaigns.')}\n </p>\n </div>\n {onClose && (\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={onClose} aria-label={t('Close')}>\n <X />\n </Button>\n )}\n </header>\n\n {canTag && selected && (\n <TagsDialog\n open={tagsOpen}\n onOpenChange={setTagsOpen}\n template={selected.item}\n tags={tags}\n storage={storage}\n onTagsChange={changeTags}\n onTemplateTags={setTemplateTags}\n />\n )}\n\n {layout === 'narrow' ? (\n <div className=\"eb:grid eb:min-h-0 eb:flex-1 eb:grid-cols-[minmax(0,1fr)]\">\n {detailsOpen && selection ? detailsColumn : listColumn}\n </div>\n ) : (\n <div\n className={cn(\n 'eb:grid eb:min-h-0 eb:flex-1 eb:grid-rows-1 eb:divide-x eb:divide-border eb:overflow-hidden',\n layout === 'wide' ? 'eb:grid-cols-[290px_minmax(0,1fr)_minmax(0,1fr)]' : 'eb:grid-cols-[260px_minmax(0,1fr)]'\n )}\n >\n {listColumn}\n {detailsColumn}\n {layout === 'wide' && (\n <div className=\"eb:min-h-0 eb:min-w-0 eb:p-3\" data-testid=\"eb-library-preview\">\n {previewPane}\n </div>\n )}\n </div>\n )}\n <NoticeArea />\n </div>\n );\n}\n\n/**\n * @param {{\n * storage?: import('../../storage/client.js').TemplateStorage,\n * locale?: string,\n * messages?: Record<string, string>,\n * onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,\n * title?: string,\n * description?: string,\n * selectedId?: string|number|null,\n * favoritesKey?: string|null,\n * deleteDisabled?: string|null,\n * onEdit?: (template: import('../../storage/client.js').StoredTemplate) => void,\n * onCreate?: () => void,\n * onClose?: () => void,\n * className?: string,\n * }} props `deleteDisabled`: *Delete* stays in its place but deletes\n * nothing — a click shows this text (a demo, a user without the right).\n */\nexport default function TemplateLibrary({ locale = 'en', messages, onNotify, ...props }) {\n return (\n <UiProvider locale={locale} messages={messages} onNotify={onNotify}>\n <Library {...props} />\n </UiProvider>\n );\n}\n","import { cn } from '../primitives/cn.js';\n\n/**\n * A tag as the library shows it. One class list per color of `TAG_COLORS`\n * (features/tags/tags.js), written out so the stylesheet has them all, and\n * readable in the light and the dark theme.\n */\nconst TAG_STYLES = {\n gray: 'eb:border-gray-500/30 eb:bg-gray-500/10 eb:text-gray-700 eb:dark:text-gray-300',\n red: 'eb:border-red-500/30 eb:bg-red-500/10 eb:text-red-700 eb:dark:text-red-300',\n orange: 'eb:border-orange-500/30 eb:bg-orange-500/10 eb:text-orange-700 eb:dark:text-orange-300',\n amber: 'eb:border-amber-500/40 eb:bg-amber-500/15 eb:text-amber-800 eb:dark:text-amber-300',\n lime: 'eb:border-lime-600/40 eb:bg-lime-500/15 eb:text-lime-800 eb:dark:text-lime-300',\n green: 'eb:border-green-600/30 eb:bg-green-500/10 eb:text-green-700 eb:dark:text-green-300',\n teal: 'eb:border-teal-600/30 eb:bg-teal-500/10 eb:text-teal-700 eb:dark:text-teal-300',\n cyan: 'eb:border-cyan-600/30 eb:bg-cyan-500/10 eb:text-cyan-700 eb:dark:text-cyan-300',\n blue: 'eb:border-blue-500/30 eb:bg-blue-500/10 eb:text-blue-700 eb:dark:text-blue-300',\n violet: 'eb:border-violet-500/30 eb:bg-violet-500/10 eb:text-violet-700 eb:dark:text-violet-300',\n fuchsia: 'eb:border-fuchsia-500/30 eb:bg-fuchsia-500/10 eb:text-fuchsia-700 eb:dark:text-fuchsia-300',\n pink: 'eb:border-pink-500/30 eb:bg-pink-500/10 eb:text-pink-700 eb:dark:text-pink-300',\n};\n\n/** The color's swatch in the tags dialog. */\nexport const TAG_SWATCHES = {\n gray: 'eb:bg-gray-500',\n red: 'eb:bg-red-500',\n orange: 'eb:bg-orange-500',\n amber: 'eb:bg-amber-500',\n lime: 'eb:bg-lime-500',\n green: 'eb:bg-green-600',\n teal: 'eb:bg-teal-600',\n cyan: 'eb:bg-cyan-500',\n blue: 'eb:bg-blue-500',\n violet: 'eb:bg-violet-500',\n fuchsia: 'eb:bg-fuchsia-500',\n pink: 'eb:bg-pink-500',\n};\n\n/** @param {{tag: import('../../features/tags/tags.js').TemplateTag, className?: string}} props */\nexport function TagBadge({ tag, className }) {\n return (\n <span\n data-testid=\"eb-tag\"\n data-color={tag.color}\n className={cn(\n 'eb:inline-block eb:max-w-full eb:shrink-0 eb:truncate eb:rounded-md eb:border eb:px-1.5 eb:py-0 eb:text-[10px]',\n TAG_STYLES[tag.color] || TAG_STYLES.gray,\n className\n )}\n >\n {tag.label}\n </span>\n );\n}\n","import { useEffect, useState } from 'react';\nimport { Check, Loader2, Pencil, Plus, Tags, Trash2 } from 'lucide-react';\nimport { MAX_TEMPLATE_TAGS, TAG_COLORS, TAG_LIMITS, sortTags, validateTag } from '../../features/tags/tags.js';\nimport { useUi } from '../context.jsx';\nimport { Button } from '../primitives/button.jsx';\nimport { cn } from '../primitives/cn.js';\nimport { Input, Label } from '../primitives/fields.jsx';\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../primitives/overlays.jsx';\nimport { TAG_SWATCHES, TagBadge } from './TagBadge.jsx';\n\n/** A new tag takes the next color, so tags made in a row differ. */\nconst colorAfter = (tags) => TAG_COLORS[(TAG_COLORS.indexOf('blue') + tags.length) % TAG_COLORS.length];\n\n/**\n * TAGS — the tags of one template, and the workspace's list of tags.\n *\n * ┌─────────────────────────────────────────────┐\n * │ ▣ Tags × │\n * │ Up to 3 tags on “October newsletter”. │\n * │ ☑ [Marketing] ✎ 🗑 │\n * │ ☐ [Events] ✎ 🗑 │\n * │ 1 / 3 │\n * │ New tag [ Name ] │\n * │ ● ● ● ● ● ● ● ● ● ● ● ● [ + Create ] │\n * │ [ Done ] │\n * └─────────────────────────────────────────────┘\n *\n * Everything is saved as it is done: ticking a tag, creating one (ticked at\n * once while the template has room), renaming it, deleting it — from every\n * template. Errors show in the dialog: the library's notices are under it.\n *\n * @param {{\n * open: boolean,\n * onOpenChange: (open: boolean) => void,\n * template: {id: string|number, name: string, tags?: string[]},\n * tags: import('../../features/tags/tags.js').TemplateTag[],\n * storage: import('../../storage/client.js').TemplateStorage,\n * onTagsChange: (tags: import('../../features/tags/tags.js').TemplateTag[], change?: {deleted?: string}) => void,\n * onTemplateTags: (tagIds: string[]) => void,\n * }} props\n */\nexport default function TagsDialog({ open, onOpenChange, template, tags, storage, onTagsChange, onTemplateTags }) {\n const { t, confirm } = useUi();\n const chosen = (template.tags || []).map(String);\n const full = chosen.length >= MAX_TEMPLATE_TAGS;\n /** null: creating; else the id of the tag being edited. */\n const [editing, setEditing] = useState(null);\n const [form, setForm] = useState({ label: '', color: colorAfter(tags) });\n const [errors, setErrors] = useState({});\n const [failure, setFailure] = useState(null);\n /** What is being saved: `form`, `tick:<id>`, `delete:<id>`. */\n const [busy, setBusy] = useState(null);\n\n const colorName = {\n gray: t('Gray'),\n red: t('Red'),\n orange: t('Orange'),\n amber: t('Yellow'),\n lime: t('Lime'),\n green: t('Green'),\n teal: t('Teal'),\n cyan: t('Cyan'),\n blue: t('Blue'),\n violet: t('Violet'),\n fuchsia: t('Fuchsia'),\n pink: t('Pink'),\n };\n\n const reset = (list = tags) => {\n setEditing(null);\n setForm({ label: '', color: colorAfter(list) });\n setErrors({});\n };\n useEffect(() => {\n if (!open) return;\n reset();\n setFailure(null);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n /** @param {string} what @param {() => Promise<void>} work */\n const run = async (what, work) => {\n setBusy(what);\n setFailure(null);\n try {\n await work();\n } catch (error) {\n setFailure(error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const tick = (id) =>\n run(`tick:${id}`, async () => {\n const next = chosen.includes(id) ? chosen.filter((tagId) => tagId !== id) : [...chosen, id];\n onTemplateTags(await storage.setTemplateTags(template.id, next));\n });\n\n const submit = (event) => {\n event.preventDefault();\n const check = validateTag(form, { existing: tags, editing, t });\n if (check.errors) {\n setErrors(check.errors);\n return;\n }\n run('form', async () => {\n const saved = await storage.saveTag(editing ? { id: editing, ...check.tag } : check.tag, { create: !editing });\n const list = sortTags([...tags.filter((tag) => tag.id !== saved.id), saved]);\n onTagsChange(list);\n if (!editing && !full) onTemplateTags(await storage.setTemplateTags(template.id, [...chosen, saved.id]));\n reset(list);\n });\n };\n\n const remove = async (tag) => {\n const ok = await confirm({\n title: t('Delete the tag “{name}”?', { name: tag.label }),\n description: t('It is removed from every template.'),\n confirmLabel: t('Delete'),\n destructive: true,\n });\n if (!ok) return;\n run(`delete:${tag.id}`, async () => {\n await storage.deleteTag(tag.id);\n const list = tags.filter((other) => other.id !== tag.id);\n onTagsChange(list, { deleted: tag.id });\n if (editing === tag.id) reset(list);\n });\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"eb:sm:max-w-[460px]\" data-testid=\"eb-tags-dialog\">\n <DialogHeader>\n <div className=\"eb:flex eb:items-center eb:gap-3\">\n <span className=\"eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-lg2 eb:bg-primary/10 eb:text-primary\">\n <Tags className=\"eb:size-5\" />\n </span>\n <div className=\"eb:min-w-0\">\n <DialogTitle className=\"eb:text-base eb:font-normal\">{t('Tags')}</DialogTitle>\n <DialogDescription className=\"eb:text-[13px]\">\n {t('Up to {max} tags on “{name}”.', { max: MAX_TEMPLATE_TAGS, name: template.name })}\n </DialogDescription>\n </div>\n </div>\n </DialogHeader>\n\n <div className=\"eb:flex eb:flex-col eb:gap-1\">\n {tags.length === 0 ? (\n <p className=\"eb:m-0 eb:rounded-lg2 eb:border eb:border-dashed eb:border-border eb:p-3 eb:text-center eb:text-[12.5px] eb:text-foreground-lighter\">\n {t('No tag yet: create the first one below.')}\n </p>\n ) : (\n <ul className=\"eb:flex eb:max-h-56 eb:flex-col eb:gap-0.5 eb:overflow-y-auto\" aria-label={t('Tags')}>\n {tags.map((tag) => {\n const checked = chosen.includes(tag.id);\n const locked = !checked && full;\n return (\n <li\n key={tag.id}\n data-testid=\"eb-tags-option\"\n className={cn('eb:flex eb:items-center eb:gap-2 eb:rounded-md eb:px-2 eb:py-1', editing === tag.id ? 'eb:bg-primary/5' : 'eb:hover:bg-surface-75')}\n >\n <label\n className={cn('eb:flex eb:min-w-0 eb:flex-1 eb:items-center eb:gap-2.5', locked ? 'eb:cursor-not-allowed eb:opacity-50' : 'eb:cursor-pointer')}\n title={locked ? t('{max} tags at most: untick one to choose another.', { max: MAX_TEMPLATE_TAGS }) : undefined}\n >\n <input\n type=\"checkbox\"\n className=\"eb:size-4 eb:shrink-0 eb:accent-primary\"\n checked={checked}\n disabled={locked || busy !== null}\n onChange={() => tick(tag.id)}\n aria-label={tag.label}\n />\n <TagBadge tag={tag} className=\"eb:text-[12px]\" />\n {busy === `tick:${tag.id}` && <Loader2 className=\"eb:size-3.5 eb:animate-spin eb:text-foreground-muted\" />}\n </label>\n <Button\n variant=\"ghost\"\n size=\"icon-xs\"\n className=\"eb:text-foreground-muted\"\n disabled={busy !== null}\n onClick={() => {\n setEditing(tag.id);\n setForm({ label: tag.label, color: tag.color });\n setErrors({});\n }}\n aria-label={t('Edit the tag “{name}”', { name: tag.label })}\n title={t('Edit')}\n >\n <Pencil />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon-xs\"\n className=\"eb:text-foreground-muted eb:hover:text-destructive\"\n disabled={busy !== null}\n onClick={() => remove(tag)}\n aria-label={t('Delete the tag “{name}”', { name: tag.label })}\n title={t('Delete')}\n >\n {busy === `delete:${tag.id}` ? <Loader2 className=\"eb:animate-spin\" /> : <Trash2 />}\n </Button>\n </li>\n );\n })}\n </ul>\n )}\n {tags.length > 0 && (\n <p className=\"eb:m-0 eb:px-2 eb:text-right eb:text-[11.5px] eb:text-foreground-muted\" aria-live=\"polite\" data-testid=\"eb-tags-count\">\n {full ? t('{max} tags at most: untick one to choose another.', { max: MAX_TEMPLATE_TAGS }) : `${chosen.length} / ${MAX_TEMPLATE_TAGS}`}\n </p>\n )}\n </div>\n\n <form onSubmit={submit} className=\"eb:flex eb:flex-col eb:gap-2.5 eb:border-t eb:border-border eb:pt-4\" noValidate data-testid=\"eb-tags-form\">\n <Label htmlFor=\"eb-tag-label\">{editing ? t('Edit tag') : t('New tag')}</Label>\n <Input\n id=\"eb-tag-label\"\n value={form.label}\n maxLength={TAG_LIMITS.label}\n placeholder={t('Name')}\n onChange={(event) => {\n setForm((f) => ({ ...f, label: event.target.value }));\n setErrors((e) => ({ ...e, label: undefined }));\n }}\n aria-invalid={errors.label ? true : undefined}\n aria-describedby={errors.label ? 'eb-tag-label-error' : undefined}\n />\n {errors.label && (\n <p id=\"eb-tag-label-error\" className=\"eb:m-0 eb:text-[12px] eb:text-destructive\">\n {errors.label}\n </p>\n )}\n {/* The colors fill the row up to the buttons, shrinking a little in a\n long language; the buttons go under them when the row is too\n narrow (editing, a phone). A basis, not a width, so the colors\n never make the dialog wider than a phone. */}\n <div className=\"eb:flex eb:flex-wrap eb:items-center eb:gap-3\">\n <div role=\"radiogroup\" aria-label={t('Color')} className=\"eb:flex eb:min-w-0 eb:flex-[1_1_16rem] eb:items-center eb:justify-between eb:gap-1\">\n {TAG_COLORS.map((color) => {\n const picked = form.color === color;\n return (\n <button\n key={color}\n type=\"button\"\n role=\"radio\"\n aria-checked={picked}\n aria-label={colorName[color]}\n title={colorName[color]}\n onClick={() => setForm((f) => ({ ...f, color }))}\n className={cn(\n 'eb:flex eb:aspect-square eb:min-w-4 eb:shrink eb:basis-6 eb:items-center eb:justify-center eb:rounded-full eb:text-white eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n TAG_SWATCHES[color],\n picked && 'eb:ring-2 eb:ring-foreground/60 eb:ring-offset-2 eb:ring-offset-background'\n )}\n >\n {picked && <Check className=\"eb:size-3.5\" />}\n </button>\n );\n })}\n </div>\n <div className=\"eb:ml-auto eb:flex eb:gap-2\">\n {editing && (\n <Button variant=\"outline\" size=\"sm\" onClick={() => reset()} disabled={busy !== null}>\n {t('Cancel')}\n </Button>\n )}\n <Button type=\"submit\" size=\"sm\" variant={editing ? 'default' : 'outline'} disabled={busy !== null}>\n {busy === 'form' ? <Loader2 className=\"eb:animate-spin\" /> : !editing && <Plus />}\n {editing ? t('Save') : t('Create')}\n </Button>\n </div>\n </div>\n {form.label.trim() && !errors.label && (\n <p className=\"eb:m-0 eb:flex eb:items-center eb:gap-2 eb:text-[11.5px] eb:text-foreground-muted\">\n {t('Preview')}\n <TagBadge tag={{ id: 'preview', label: form.label.trim(), color: form.color }} className=\"eb:text-[12px]\" />\n </p>\n )}\n </form>\n\n {failure && (\n <p role=\"alert\" className=\"eb:m-0 eb:text-[12.5px] eb:text-destructive\">\n {failure}\n </p>\n )}\n\n <div className=\"eb:flex eb:justify-end\">\n <Button onClick={() => onOpenChange(false)}>{t('Done')}</Button>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","Loader2","Tags","Trash2","jsx","jsx","jsxs","useState","useEffect","template","Loader2","Tags","Trash2"]}
|
package/dist/index.js
CHANGED
|
@@ -15,20 +15,20 @@ import {
|
|
|
15
15
|
emailBlocks,
|
|
16
16
|
emailBuilderFeatures,
|
|
17
17
|
isFeatureEnabled
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-EKQA3YAI.js";
|
|
19
19
|
import {
|
|
20
20
|
TemplateLibrary
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-SYQC7JNA.js";
|
|
22
22
|
import {
|
|
23
23
|
withPlaceholderImages
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-RVRKAXYC.js";
|
|
25
25
|
import {
|
|
26
26
|
applyVariables,
|
|
27
27
|
applyVariablesToText,
|
|
28
28
|
unknownVariables,
|
|
29
29
|
usedVariables
|
|
30
30
|
} from "./chunk-2KOI5SWV.js";
|
|
31
|
-
import "./chunk-
|
|
31
|
+
import "./chunk-P6IEVWZY.js";
|
|
32
32
|
import {
|
|
33
33
|
simulateDarkMode
|
|
34
34
|
} from "./chunk-UFKCLU2D.js";
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
createMemoryStorage,
|
|
41
41
|
useT,
|
|
42
42
|
useUi
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-2IDNT724.js";
|
|
44
44
|
import {
|
|
45
45
|
EMAIL_BUILDER_VERSION,
|
|
46
46
|
EMAIL_DEVICES,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
serializeDocument,
|
|
59
59
|
serializeMjml,
|
|
60
60
|
validateTag
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-NPE7WFT5.js";
|
|
62
62
|
import "./chunk-A4KF7NMP.js";
|
|
63
63
|
import {
|
|
64
64
|
CUSTOM_GROUP,
|
package/dist/library.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TemplateLibrary
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SYQC7JNA.js";
|
|
4
4
|
import {
|
|
5
5
|
withPlaceholderImages
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RVRKAXYC.js";
|
|
7
7
|
import "./chunk-UFKCLU2D.js";
|
|
8
8
|
import {
|
|
9
9
|
StorageError,
|
|
10
10
|
createHttpStorage,
|
|
11
11
|
createMemoryStorage
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-2IDNT724.js";
|
|
13
13
|
import {
|
|
14
14
|
EMAIL_BUILDER_VERSION,
|
|
15
15
|
MAX_TEMPLATE_TAGS,
|
|
16
16
|
TAG_COLORS,
|
|
17
17
|
validateTag
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-NPE7WFT5.js";
|
|
19
19
|
import {
|
|
20
20
|
LOCALES,
|
|
21
21
|
createTranslator,
|
package/dist/server.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
logoWidthFor,
|
|
15
15
|
prepareMjml,
|
|
16
16
|
readPage
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-P6IEVWZY.js";
|
|
18
18
|
import {
|
|
19
19
|
simulateDarkMode
|
|
20
20
|
} from "./chunk-UFKCLU2D.js";
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
serializeDocument,
|
|
38
38
|
sortTags,
|
|
39
39
|
validateTag
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-NPE7WFT5.js";
|
|
41
41
|
import {
|
|
42
42
|
IMAGE_ATTRIBUTES,
|
|
43
43
|
LINK_ATTRIBUTES,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* project JSON). Both are written with every template, so a document read by
|
|
7
7
|
* a newer version knows whether it must be migrated (see `core/document.js`).
|
|
8
8
|
*/
|
|
9
|
-
export const EMAIL_BUILDER_VERSION: "0.13.
|
|
9
|
+
export const EMAIL_BUILDER_VERSION: "0.13.1";
|
|
10
10
|
export const EMAIL_DOCUMENT_SCHEMA_VERSION: 1;
|
|
11
11
|
/** @typedef {'desktop'|'mobile'} EmailDevice */
|
|
12
12
|
/** @type {EmailDevice[]} */
|
|
@@ -20,7 +20,7 @@ export function createNativeAdapter({ loadCompiler }?: {
|
|
|
20
20
|
* @typedef {import('./model.js').EmailNode} EmailNode
|
|
21
21
|
*/
|
|
22
22
|
export const ENGINE_NAME: "beb-mjml";
|
|
23
|
-
export const ENGINE_VERSION: "0.13.
|
|
23
|
+
export const ENGINE_VERSION: "0.13.1";
|
|
24
24
|
export const EMPTY_MJML: "<mjml><mj-body background-color=\"#eceff4\" width=\"600px\"></mj-body></mjml>";
|
|
25
25
|
export type EmailEditorAdapter = import("../core/EmailEditor.js").EmailEditorAdapter;
|
|
26
26
|
export type EmailBlock = import("../core/EmailEditor.js").EmailBlock;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "best-email-builder",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Drag-and-drop email builder for React: MJML output that renders in Outlook, Gmail and Apple Mail, with server-side rendering, versions, variables and image uploads.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ui/library/TemplateLibrary.jsx","../src/ui/library/TagBadge.jsx","../src/ui/library/TagsDialog.jsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AlertCircle,\n ArrowLeft,\n Copy,\n Download,\n LayoutTemplate,\n Loader2,\n PencilRuler,\n RefreshCw,\n Search,\n Star,\n Tags,\n Trash2,\n X,\n} from 'lucide-react';\nimport { withPlaceholderImages } from '../../features/templates/thumbnails.js';\nimport { EmailPreview } from '../../features/preview/PreviewButton.jsx';\nimport { createHttpStorage } from '../../storage/client.js';\nimport { NoticeArea, UiProvider, useUi } from '../context.jsx';\nimport { Button } from '../primitives/button.jsx';\nimport { cn } from '../primitives/cn.js';\nimport { Input } from '../primitives/fields.jsx';\nimport { FilterChip, FilterReset, SectionLabel } from '../primitives/misc.jsx';\nimport { SEARCH_KEYS, SearchKeyHint, useSearchShortcut } from '../primitives/searchShortcut.jsx';\nimport { useLayoutWidth } from '../useLayoutWidth.js';\nimport { TAG_SWATCHES, TagBadge } from './TagBadge.jsx';\nimport TagsDialog from './TagsDialog.jsx';\n\n/**\n * TEMPLATE LIBRARY — the first screen: pick a template, see it, open it.\n *\n * ┌──────────────────────────────────────────────────────────────────┐\n * │ ▣ Email template library × │\n * ├──────────────┬──────────────────────────┬────────────────────────┤\n * │ New template │ Name ☆ │ Desktop | Mobile │\n * │ 🔍 Ctrl K │ code │ ┌────────────────────┐ │\n * │ (All)(●Promo)│ Origin, subject, dates… │ │ rendered email │ │\n * │ FAVORITES │ │ │ │ │\n * │ TEMPLATES │ Tags [Promo] [Events] ✎ │ │ │ │\n * │ │ [ Edit ] [ Duplicate ] │ │ │ │\n * │ │ [Copy HTML] [Download] 🗑 │ └────────────────────┘ │\n * └──────────────┴──────────────────────────┴────────────────────────┘\n *\n * Standalone: it never mounts the editor. \"Edit\" and \"New template\" only\n * call `onEdit(template)` / `onCreate()`, and the host decides what opens —\n * `EmailBuilder` (see `EmailStudio`, which wires the two), its own screen, a\n * route. Likewise `EmailBuilder` works without it; a new template starts\n * from a base there, with its *Templates* button.\n *\n * Templates `storage.list()`; preview through `storage.render()` when the\n * storage has it, else the HTML compiled at the last save.\n * *Copy HTML* and *Download* take that HTML (`load()`).\n * Favorites the star of the details. A favorite leaves its section for the\n * one at the top. Kept in this browser (`favoritesKey`, `null`\n * turns favorites off).\n * Tags up to three colored tags on a template, chosen and created in\n * the tags dialog, when the storage has them (`listTags`…).\n * The search finds them too, and the tags under it filter\n * the list: one or several (a template with any of them), or\n * *All*. Only the tags some template carries are offered.\n *\n * Layout follows the library's own width, like the builder: three columns,\n * then the preview under the details, then (phone) list and details as two\n * screens.\n */\n\nconst DATE_FORMAT = { day: '2-digit', month: '2-digit', year: 'numeric' };\nconst DEFAULT_FAVORITES_KEY = 'best-email-builder:favorites';\n\nconst matches = (query, ...fields) => {\n const q = query.trim().toLowerCase();\n return !q || fields.some((f) => String(f || '').toLowerCase().includes(q));\n};\n\n/** `October newsletter` → `october_newsletter`: the code line under a name. */\nconst codeOf = (name, fallback) =>\n String(name || '')\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 48) || fallback;\n\n/** Keys of the starred entries: `template:<id>`. */\nfunction useFavorites(storageKey) {\n const [favorites, setFavorites] = useState(() => {\n if (!storageKey) return new Set();\n try {\n return new Set(JSON.parse(localStorage.getItem(storageKey) || '[]'));\n } catch {\n return new Set();\n }\n });\n const toggle = useCallback(\n (key) =>\n setFavorites((current) => {\n const next = new Set(current);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n try {\n localStorage.setItem(storageKey, JSON.stringify([...next]));\n } catch {\n // Private window, blocked storage: the star holds until the page closes.\n }\n return next;\n }),\n [storageKey]\n );\n return { enabled: Boolean(storageKey), favorites, toggle };\n}\n\nfunction download(name, html) {\n const blob = new Blob([html], { type: 'text/html;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `${codeOf(name, 'email')}.html`;\n a.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction Row({ entry, selected, favorite, onSelect }) {\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n aria-current={selected ? 'true' : undefined}\n data-testid=\"eb-library-template\"\n className={cn(\n 'eb:w-full eb:rounded-lg2 eb:border eb:px-2.5 eb:py-2 eb:text-left eb:transition-colors eb:outline-none',\n 'eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n selected ? 'eb:border-primary/50 eb:bg-primary/5' : 'eb:border-transparent eb:hover:bg-surface-75'\n )}\n >\n <span className=\"eb:flex eb:items-center eb:gap-1.5\">\n <span className=\"eb:min-w-0 eb:flex-1 eb:truncate eb:text-[13px] eb:text-foreground\">{entry.name}</span>\n {favorite && <Star className=\"eb:size-3.5 eb:shrink-0 eb:fill-current eb:text-warning\" aria-hidden=\"true\" />}\n </span>\n <span className=\"eb:mt-0.5 eb:block eb:truncate eb:font-mono eb:text-[11px] eb:text-foreground-lighter\">{entry.code}</span>\n {/* One line whatever the tags: too many fade out before the date, and\n every row keeps the same height. */}\n <span className=\"eb:mt-1 eb:flex eb:min-h-[18px] eb:items-center eb:gap-2\">\n <span className=\"eb:flex eb:min-w-0 eb:flex-1 eb:gap-1 eb:overflow-hidden eb:[mask-image:linear-gradient(to_right,#000_calc(100%-1.5rem),transparent)]\">\n {entry.tags.map((tag) => (\n <TagBadge key={tag.id} tag={tag} className=\"eb:max-w-[7.5rem]\" />\n ))}\n </span>\n {entry.date && <span className=\"eb:shrink-0 eb:text-[10.5px] eb:text-foreground-muted\">{entry.date}</span>}\n </span>\n </button>\n );\n}\n\nfunction Section({ label, children, ...props }) {\n return (\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\" {...props}>\n <SectionLabel>{label}</SectionLabel>\n {children}\n </div>\n );\n}\n\nfunction Library({\n storage: injectedStorage,\n title,\n description,\n selectedId = null,\n favoritesKey = DEFAULT_FAVORITES_KEY,\n deleteDisabled = null,\n onEdit,\n onCreate,\n onClose,\n className,\n}) {\n const { t, locale, notify, confirm } = useUi();\n const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);\n const canTag = typeof storage.listTags === 'function' && typeof storage.setTemplateTags === 'function';\n const dateFormat = useMemo(() => new Intl.DateTimeFormat(locale, DATE_FORMAT), [locale]);\n const formatDate = useCallback((at) => (at ? dateFormat.format(new Date(at)) : ''), [dateFormat]);\n const stars = useFavorites(favoritesKey);\n\n const rootRef = useRef(null);\n const searchRef = useRef(null);\n const layout = useLayoutWidth(rootRef);\n\n const [items, setItems] = useState({ status: 'loading', list: [], error: null });\n /** The workspace's tags, by name. */\n const [tags, setTags] = useState([]);\n const [tagsOpen, setTagsOpen] = useState(false);\n const [query, setQuery] = useState('');\n /** The tags picked under the search (ids); none is *All*. */\n const [picked, setPicked] = useState(() => new Set());\n /** `template:<id>` */\n const [selection, setSelection] = useState(selectedId != null ? `template:${selectedId}` : null);\n /** Phone layout: the details screen is shown over the list. */\n const [detailsOpen, setDetailsOpen] = useState(false);\n const [detail, setDetail] = useState({ status: 'idle', template: null, error: null });\n const [preview, setPreview] = useState({ html: '', loading: false, error: null });\n const [device, setDevice] = useState('desktop');\n const [busy, setBusy] = useState(null);\n\n // Only the LAST read counts: a slow answer arriving after a newer one\n // (opening, then a duplicate) would show a stale list.\n const lastRead = useRef(0);\n const reload = useCallback(async () => {\n const ticket = ++lastRead.current;\n if (typeof storage.list !== 'function') {\n setItems({ status: 'ready', list: [], error: null });\n return;\n }\n setItems((s) => ({ ...s, status: s.list.length ? 'ready' : 'loading', error: null }));\n try {\n // Tags that cannot be read leave the templates readable, untagged.\n const [list, tagList] = await Promise.all([storage.list(), canTag ? storage.listTags().catch(() => []) : []]);\n if (ticket === lastRead.current) {\n setItems({ status: 'ready', list, error: null });\n setTags(tagList);\n }\n } catch (error) {\n if (ticket === lastRead.current) setItems({ status: 'error', list: [], error: error.message });\n }\n }, [storage, canTag]);\n\n useEffect(() => {\n reload();\n }, [reload]);\n\n /* ── entries ───────────────────────────────────────────────────────────── */\n\n const templateEntries = useMemo(() => {\n const byId = new Map(tags.map((tag) => [String(tag.id), tag]));\n return items.list.map((item) => ({\n key: `template:${item.id}`,\n id: item.id,\n name: item.name,\n code: codeOf(item.name, `#${item.id}`),\n tags: (item.tags || []).map((id) => byId.get(String(id))).filter(Boolean),\n date: formatDate(item.updatedAt || item.createdAt),\n subject: item.subject,\n item,\n }));\n }, [items.list, tags, formatDate]);\n /** The filters: the tags some template carries, by name. */\n const usedTags = useMemo(() => {\n const used = new Set(items.list.flatMap((item) => (item.tags || []).map(String)));\n return tags.filter((tag) => used.has(String(tag.id)));\n }, [items.list, tags]);\n // A picked tag deleted or left by every template no longer filters.\n const active = new Set(usedTags.map((tag) => String(tag.id)).filter((id) => picked.has(id)));\n const togglePicked = (id) =>\n setPicked((current) => {\n const next = new Set(current);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n const shown = templateEntries.filter(\n (e) =>\n (active.size === 0 || e.tags.some((tag) => active.has(String(tag.id)))) &&\n matches(query, e.name, e.code, e.subject, ...e.tags.map((tag) => tag.label))\n );\n const favoriteEntries = shown.filter((e) => stars.favorites.has(e.key));\n const otherTemplates = shown.filter((e) => !stars.favorites.has(e.key));\n\n // First selection: the requested template, else the most recent one.\n useEffect(() => {\n if (items.status !== 'ready' || selection) return;\n if (items.list.length) setSelection(`template:${items.list[0].id}`);\n }, [items, selection]);\n\n // Ctrl/Cmd+K focuses the search.\n useSearchShortcut(searchRef);\n\n const selected = templateEntries.find((e) => e.key === selection) || null;\n const templateId = selection?.startsWith('template:') ? selection.slice('template:'.length) : null;\n\n // Details of a saved template (MJML and compiled HTML are not in the list).\n useEffect(() => {\n if (templateId == null) {\n setDetail({ status: 'idle', template: null, error: null });\n return undefined;\n }\n let live = true;\n setDetail((d) => ({ status: 'loading', template: String(d.template?.id) === templateId ? d.template : null, error: null }));\n storage.load(templateId).then(\n (template) => live && setDetail({ status: 'ready', template, error: null }),\n (error) => live && setDetail({ status: 'error', template: null, error: error.message })\n );\n return () => {\n live = false;\n };\n }, [templateId, storage]);\n\n // Preview: rendered by the storage (server) when it can, else the saved HTML.\n const template = detail.template;\n const source = template\n ? { mjml: template.mjml, subject: template.subject, preheader: template.preheader, html: template.html || '' }\n : null;\n const sourceKey = template ? `template:${template.id}:${template.revision}` : '';\n useEffect(() => {\n if (!source) {\n setPreview({ html: '', loading: templateId != null && detail.status === 'loading', error: null });\n return undefined;\n }\n if (typeof storage.render !== 'function' || !source.mjml) {\n setPreview({ html: source.html, loading: false, error: source.html ? null : t('No preview: this storage cannot render emails.') });\n return undefined;\n }\n let live = true;\n setPreview((p) => ({ ...p, loading: true, error: null }));\n storage.render({ mjml: source.mjml, subject: source.subject, preheader: source.preheader }).then(\n (result) => live && setPreview({ html: withPlaceholderImages(result.html), loading: false, error: null }),\n (error) => live && setPreview({ html: source.html, loading: false, error: source.html ? null : error.message })\n );\n return () => {\n live = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceKey, storage]);\n\n /* ── actions ───────────────────────────────────────────────────────────── */\n\n const select = (key) => {\n setSelection(key);\n setDetailsOpen(true);\n };\n\n const remove = async () => {\n if (deleteDisabled) {\n notify('info', deleteDisabled);\n return;\n }\n const ok = await confirm({\n title: t('Delete “{name}”?', { name: template.name }),\n description: t('The template and its versions will be deleted.'),\n confirmLabel: t('Delete'),\n destructive: true,\n });\n if (!ok) return;\n setBusy('delete');\n try {\n await storage.remove(template.id);\n setSelection(null);\n setDetailsOpen(false);\n await reload();\n notify('success', t('Template deleted.'));\n } catch (error) {\n notify('error', error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const duplicate = async () => {\n setBusy('duplicate');\n try {\n const { template: copy } = await storage.duplicate(template.id);\n await reload();\n setSelection(`template:${copy.id}`);\n notify('success', t('Template duplicated.'));\n } catch (error) {\n notify('error', error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const copyHtml = async () => {\n try {\n await navigator.clipboard.writeText(template.html);\n notify('success', t('HTML copied.'));\n } catch {\n notify('error', t('The browser refused to copy.'));\n }\n };\n\n /** The selected template's tags, as the dialog sets them. @param {string[]} ids */\n const setTemplateTags = (ids) =>\n setItems((s) => ({ ...s, list: s.list.map((item) => (String(item.id) === templateId ? { ...item, tags: ids } : item)) }));\n /** The list of tags after a change; a deleted tag leaves every template. */\n const changeTags = (list, { deleted } = {}) => {\n setTags(list);\n if (deleted == null) return;\n setItems((s) => ({\n ...s,\n list: s.list.map((item) => (item.tags?.includes(deleted) ? { ...item, tags: item.tags.filter((id) => id !== deleted) } : item)),\n }));\n };\n\n const canList = typeof storage.list === 'function';\n const rowProps = (entry) => ({\n entry,\n selected: entry.key === selection,\n favorite: stars.favorites.has(entry.key),\n onSelect: () => select(entry.key),\n });\n\n /* ── left column ───────────────────────────────────────────────────────── */\n\n const listColumn = (\n <div key=\"list\" className=\"eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col\" data-testid=\"eb-library-list\">\n <div className=\"eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-b eb:border-border eb:p-3\">\n {onCreate && (\n <Button className=\"eb:w-full\" onClick={() => onCreate()}>\n <PencilRuler />\n {t('New template')}\n </Button>\n )}\n <div className=\"eb:relative\">\n <Search className=\"eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted\" />\n <Input\n ref={searchRef}\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Escape' && query) {\n e.preventDefault();\n setQuery('');\n }\n }}\n placeholder={t('Search templates')}\n aria-label={t('Search templates')}\n aria-keyshortcuts={SEARCH_KEYS}\n className=\"eb:pr-16 eb:pl-8 eb:[&::-webkit-search-cancel-button]:hidden\"\n />\n <div className=\"eb:absolute eb:top-1/2 eb:right-2 eb:flex eb:-translate-y-1/2 eb:items-center eb:gap-1\">\n {query && (\n <button type=\"button\" onClick={() => setQuery('')} aria-label={t('Clear the search')} className=\"eb:cursor-pointer\">\n <X className=\"eb:size-3.5 eb:text-foreground-muted eb:hover:text-foreground\" />\n </button>\n )}\n <SearchKeyHint>{query ? 'Esc' : null}</SearchKeyHint>\n </div>\n </div>\n {usedTags.length > 0 && (\n <div className=\"eb:flex eb:items-start eb:gap-2.5\">\n {/* No tag picked: every template. */}\n <div\n role=\"group\"\n aria-label={t('Filter by tag')}\n className=\"eb:-m-1 eb:flex eb:max-h-[4.75rem] eb:min-w-0 eb:flex-1 eb:flex-wrap eb:gap-1 eb:overflow-y-auto eb:p-1\"\n data-testid=\"eb-library-tag-filter\"\n >\n {usedTags.map((tag) => (\n <FilterChip\n key={tag.id}\n pressed={active.has(String(tag.id))}\n onClick={() => togglePicked(String(tag.id))}\n className=\"eb:h-6 eb:max-w-full eb:px-2.5 eb:text-[11.5px]\"\n >\n <span\n aria-hidden=\"true\"\n className={cn(\n 'eb:size-2 eb:shrink-0 eb:rounded-full',\n TAG_SWATCHES[tag.color] || TAG_SWATCHES.gray,\n active.has(String(tag.id)) && 'eb:ring-1 eb:ring-primary-foreground'\n )}\n />\n <span className=\"eb:truncate\">{tag.label}</span>\n </FilterChip>\n ))}\n </div>\n <FilterReset\n label={t('Clear the tag filters')}\n active={active.size > 0}\n onClick={() => setPicked(new Set())}\n className=\"eb:size-6\"\n data-testid=\"eb-library-tag-reset\"\n />\n </div>\n )}\n </div>\n\n <div className=\"eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-3\">\n {stars.enabled && favoriteEntries.length > 0 && (\n <Section label={t('Favorites')} role=\"group\" aria-label={t('Favorites')} data-testid=\"eb-library-favorites\">\n {favoriteEntries.map((entry) => (\n <Row key={entry.key} {...rowProps(entry)} />\n ))}\n </Section>\n )}\n {canList && (\n <Section label={`${t('Templates')} (${otherTemplates.length})`} data-testid=\"eb-library-templates\">\n {items.status === 'loading' ? (\n <p className=\"eb:m-0 eb:flex eb:items-center eb:gap-2 eb:px-1 eb:text-[12px] eb:text-foreground-lighter\">\n <Loader2 className=\"eb:size-3.5 eb:animate-spin\" />\n {t('Loading…')}\n </p>\n ) : items.status === 'error' ? (\n <div className=\"eb:px-1 eb:text-[12px] eb:text-destructive\">\n <p className=\"eb:m-0\">{t('Could not load the templates.')}</p>\n <p className=\"eb:m-0 eb:mt-0.5 eb:text-foreground-muted\">{items.error}</p>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:mt-2\" onClick={reload}>\n <RefreshCw />\n {t('Retry')}\n </Button>\n </div>\n ) : otherTemplates.length === 0 ? (\n <p className=\"eb:m-0 eb:px-1 eb:text-[12px] eb:leading-relaxed eb:text-foreground-lighter\">\n {query\n ? t('Nothing matches “{query}”.', { query })\n : templateEntries.length\n ? t('All your templates are in the favorites.')\n : t('No template yet: create the first one.')}\n </p>\n ) : (\n otherTemplates.map((entry) => <Row key={entry.key} {...rowProps(entry)} />)\n )}\n </Section>\n )}\n </div>\n </div>\n );\n\n /* ── details ───────────────────────────────────────────────────────────── */\n\n const meta =\n selected || template\n ? {\n name: template?.name ?? selected.name,\n code: selected?.code ?? codeOf(template?.name, `#${templateId}`),\n description: template?.description ?? selected?.item?.description,\n origin: t('Builder'),\n subject: (template ?? selected?.item)?.subject,\n preheader: (template ?? selected?.item)?.preheader,\n createdAt: (template ?? selected?.item)?.createdAt,\n createdBy: (template ?? selected?.item)?.createdBy,\n updatedAt: (template ?? selected?.item)?.updatedAt,\n updatedBy: (template ?? selected?.item)?.updatedBy,\n }\n : null;\n const favorite = selection ? stars.favorites.has(selection) : false;\n\n const detailsBody =\n templateId != null && detail.status === 'error' ? (\n <p className=\"eb:m-0 eb:flex eb:items-start eb:gap-2 eb:text-[13px] eb:text-destructive\">\n <AlertCircle className=\"eb:mt-0.5 eb:size-4 eb:shrink-0\" />\n {detail.error}\n </p>\n ) : meta ? (\n <>\n <div className=\"eb:flex eb:flex-col eb:gap-1\">\n <div className=\"eb:flex eb:items-center eb:gap-2\">\n <h3 className=\"eb:m-0 eb:min-w-0 eb:flex-1 eb:text-[15px] eb:font-medium eb:text-foreground\">{meta.name}</h3>\n {stars.enabled && (\n <Button\n variant=\"ghost\"\n size=\"icon-sm-compact\"\n className={cn('eb:shrink-0', favorite ? 'eb:text-warning' : 'eb:text-foreground-muted eb:hover:text-warning')}\n onClick={() => stars.toggle(selection)}\n aria-pressed={favorite}\n aria-label={favorite ? t('Remove from favorites') : t('Add to favorites')}\n title={favorite ? t('Remove from favorites') : t('Add to favorites')}\n >\n <Star className={cn('eb:size-4', favorite && 'eb:fill-current')} />\n </Button>\n )}\n </div>\n <p className=\"eb:m-0 eb:font-mono eb:text-[11.5px] eb:text-foreground-lighter\">{meta.code}</p>\n {meta.description && (\n <p className=\"eb:m-0 eb:text-[12.5px] eb:leading-relaxed eb:text-foreground-lighter\">{meta.description}</p>\n )}\n </div>\n\n <dl className=\"eb:m-0 eb:grid eb:grid-cols-[110px_minmax(0,1fr)] eb:gap-x-3 eb:gap-y-1.5 eb:text-[12px]\">\n <dt className=\"eb:text-foreground-muted\">{t('Origin')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">{meta.origin}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Subject')}</dt>\n <dd className=\"eb:m-0 eb:break-words eb:text-foreground\">{meta.subject || '—'}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Preview text')}</dt>\n <dd className=\"eb:m-0 eb:break-words eb:text-foreground-lighter\">{meta.preheader || '—'}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Created')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">\n {formatDate(meta.createdAt) || '—'}\n {meta.createdBy ? ` ${t('by {name}', { name: meta.createdBy })}` : ''}\n </dd>\n {meta.updatedAt && (\n <>\n <dt className=\"eb:text-foreground-muted\">{t('Updated')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">\n {formatDate(meta.updatedAt)}\n {meta.updatedBy ? ` ${t('by {name}', { name: meta.updatedBy })}` : ''}\n </dd>\n </>\n )}\n {canTag && selected && (\n <>\n <dt className=\"eb:flex eb:h-6 eb:items-center eb:text-foreground-muted\">{t('Tags')}</dt>\n <dd className=\"eb:m-0 eb:flex eb:min-w-0 eb:flex-wrap eb:items-center eb:gap-1\" data-testid=\"eb-library-tags\">\n {selected.tags.map((tag) => (\n <TagBadge key={tag.id} tag={tag} className=\"eb:text-[11.5px]\" />\n ))}\n <Button\n variant=\"ghost\"\n size=\"xs\"\n className=\"eb:text-foreground-lighter\"\n onClick={() => setTagsOpen(true)}\n data-testid=\"eb-library-tags-button\"\n >\n <Tags />\n {selected.tags.length ? t('Edit tags') : t('Add tags')}\n </Button>\n </dd>\n </>\n )}\n </dl>\n </>\n ) : (\n <div className=\"eb:flex eb:flex-1 eb:items-center eb:justify-center\">\n <div className=\"eb:flex eb:w-full eb:flex-col eb:items-center eb:justify-center eb:gap-2 eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:px-6 eb:py-12 eb:text-center\">\n <span className=\"eb:flex eb:size-12 eb:items-center eb:justify-center eb:rounded-full eb:bg-surface-200 eb:text-foreground-muted\">\n <LayoutTemplate className=\"eb:size-6\" />\n </span>\n <p className=\"eb:m-0 eb:text-[13px] eb:font-medium eb:text-foreground\">{t('Choose a template')}</p>\n <p className=\"eb:m-0 eb:max-w-sm eb:text-[13px] eb:leading-relaxed eb:text-foreground-muted\">\n {t('Select a template on the left.')}\n </p>\n </div>\n </div>\n );\n\n const actions = template ? (\n <>\n <div className=\"eb:flex eb:gap-2\">\n {onEdit && (\n <Button className=\"eb:flex-1\" onClick={() => onEdit(template)}>\n <PencilRuler />\n {t('Edit')}\n </Button>\n )}\n <Button variant=\"outline\" className=\"eb:flex-1\" disabled={!!busy} onClick={duplicate}>\n {busy === 'duplicate' ? <Loader2 className=\"eb:animate-spin\" /> : <Copy />}\n {t('Duplicate')}\n </Button>\n </div>\n {(template.html || typeof storage.remove === 'function') && (\n <div className=\"eb:flex eb:gap-2\">\n {template.html && (\n <>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:flex-1\" onClick={copyHtml}>\n <Copy />\n {t('Copy HTML')}\n </Button>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:flex-1\" onClick={() => download(template.name, template.html)}>\n <Download />\n {t('Download')}\n </Button>\n </>\n )}\n {typeof storage.remove === 'function' && (\n <Button\n variant=\"outline\"\n size=\"sm-compact\"\n className={cn('eb:text-destructive', !template.html && 'eb:ml-auto', deleteDisabled && 'eb:opacity-50 eb:hover:bg-background eb:hover:text-destructive')}\n disabled={!!busy}\n onClick={remove}\n aria-disabled={deleteDisabled ? true : undefined}\n aria-label={t('Delete')}\n title={deleteDisabled || t('Delete')}\n >\n {busy === 'delete' ? <Loader2 className=\"eb:animate-spin\" /> : <Trash2 />}\n {!template.html && t('Delete')}\n </Button>\n )}\n </div>\n )}\n </>\n ) : null;\n\n const previewPane =\n preview.html || preview.loading || preview.error ? (\n <EmailPreview html={preview.html} loading={preview.loading} error={preview.error} device={device} onDeviceChange={setDevice} />\n ) : (\n <div className=\"eb:flex eb:h-full eb:min-h-40 eb:items-center eb:justify-center eb:rounded-lg2 eb:border eb:border-dashed eb:border-border eb:p-6 eb:text-center eb:text-[13px] eb:text-foreground-lighter\">\n {t('The template preview appears here.')}\n </div>\n );\n\n const detailsColumn = (\n <div key=\"details\" className=\"eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col\" data-testid=\"eb-library-details\">\n {layout === 'narrow' && (\n <div className=\"eb:shrink-0 eb:border-b eb:border-border eb:px-2 eb:py-1.5\">\n <Button variant=\"ghost\" size=\"sm-compact\" onClick={() => setDetailsOpen(false)}>\n <ArrowLeft />\n {t('All templates')}\n </Button>\n </div>\n )}\n <div className=\"eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-4\">\n {detailsBody}\n {layout !== 'wide' && meta && <div className=\"eb:h-[560px] eb:shrink-0\">{previewPane}</div>}\n </div>\n {actions && <div className=\"eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-t eb:border-border eb:p-3\">{actions}</div>}\n </div>\n );\n\n return (\n <div\n ref={rootRef}\n className={cn('eb-root eb:relative eb:flex eb:h-full eb:min-h-0 eb:flex-col eb:bg-background eb:text-foreground', className)}\n data-testid=\"template-library\"\n data-layout={layout}\n >\n <header className=\"eb:flex eb:shrink-0 eb:items-center eb:gap-3 eb:border-b eb:border-border eb:px-5 eb:py-3.5\">\n <span className=\"eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-lg2 eb:bg-primary/10 eb:text-primary\">\n <LayoutTemplate className=\"eb:size-5\" />\n </span>\n <div className=\"eb:min-w-0 eb:flex-1\">\n <h2 className=\"eb:m-0 eb:truncate eb:text-base eb:font-normal eb:text-foreground\">{title ?? t('Email template library')}</h2>\n <p className=\"eb:m-0 eb:truncate eb:text-[13px] eb:text-foreground-muted\">\n {description ?? t('Ready-made templates to copy into your campaigns.')}\n </p>\n </div>\n {onClose && (\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={onClose} aria-label={t('Close')}>\n <X />\n </Button>\n )}\n </header>\n\n {canTag && selected && (\n <TagsDialog\n open={tagsOpen}\n onOpenChange={setTagsOpen}\n template={selected.item}\n tags={tags}\n storage={storage}\n onTagsChange={changeTags}\n onTemplateTags={setTemplateTags}\n />\n )}\n\n {layout === 'narrow' ? (\n <div className=\"eb:grid eb:min-h-0 eb:flex-1 eb:grid-cols-[minmax(0,1fr)]\">\n {detailsOpen && selection ? detailsColumn : listColumn}\n </div>\n ) : (\n <div\n className={cn(\n 'eb:grid eb:min-h-0 eb:flex-1 eb:grid-rows-1 eb:divide-x eb:divide-border eb:overflow-hidden',\n layout === 'wide' ? 'eb:grid-cols-[290px_minmax(0,1fr)_minmax(0,1fr)]' : 'eb:grid-cols-[260px_minmax(0,1fr)]'\n )}\n >\n {listColumn}\n {detailsColumn}\n {layout === 'wide' && (\n <div className=\"eb:min-h-0 eb:min-w-0 eb:p-3\" data-testid=\"eb-library-preview\">\n {previewPane}\n </div>\n )}\n </div>\n )}\n <NoticeArea />\n </div>\n );\n}\n\n/**\n * @param {{\n * storage?: import('../../storage/client.js').TemplateStorage,\n * locale?: string,\n * messages?: Record<string, string>,\n * onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,\n * title?: string,\n * description?: string,\n * selectedId?: string|number|null,\n * favoritesKey?: string|null,\n * deleteDisabled?: string|null,\n * onEdit?: (template: import('../../storage/client.js').StoredTemplate) => void,\n * onCreate?: () => void,\n * onClose?: () => void,\n * className?: string,\n * }} props `deleteDisabled`: *Delete* stays in its place but deletes\n * nothing — a click shows this text (a demo, a user without the right).\n */\nexport default function TemplateLibrary({ locale = 'en', messages, onNotify, ...props }) {\n return (\n <UiProvider locale={locale} messages={messages} onNotify={onNotify}>\n <Library {...props} />\n </UiProvider>\n );\n}\n","import { cn } from '../primitives/cn.js';\n\n/**\n * A tag as the library shows it. One class list per color of `TAG_COLORS`\n * (features/tags/tags.js), written out so the stylesheet has them all, and\n * readable in the light and the dark theme.\n */\nconst TAG_STYLES = {\n gray: 'eb:border-gray-500/30 eb:bg-gray-500/10 eb:text-gray-700 eb:dark:text-gray-300',\n red: 'eb:border-red-500/30 eb:bg-red-500/10 eb:text-red-700 eb:dark:text-red-300',\n orange: 'eb:border-orange-500/30 eb:bg-orange-500/10 eb:text-orange-700 eb:dark:text-orange-300',\n amber: 'eb:border-amber-500/40 eb:bg-amber-500/15 eb:text-amber-800 eb:dark:text-amber-300',\n lime: 'eb:border-lime-600/40 eb:bg-lime-500/15 eb:text-lime-800 eb:dark:text-lime-300',\n green: 'eb:border-green-600/30 eb:bg-green-500/10 eb:text-green-700 eb:dark:text-green-300',\n teal: 'eb:border-teal-600/30 eb:bg-teal-500/10 eb:text-teal-700 eb:dark:text-teal-300',\n cyan: 'eb:border-cyan-600/30 eb:bg-cyan-500/10 eb:text-cyan-700 eb:dark:text-cyan-300',\n blue: 'eb:border-blue-500/30 eb:bg-blue-500/10 eb:text-blue-700 eb:dark:text-blue-300',\n violet: 'eb:border-violet-500/30 eb:bg-violet-500/10 eb:text-violet-700 eb:dark:text-violet-300',\n fuchsia: 'eb:border-fuchsia-500/30 eb:bg-fuchsia-500/10 eb:text-fuchsia-700 eb:dark:text-fuchsia-300',\n pink: 'eb:border-pink-500/30 eb:bg-pink-500/10 eb:text-pink-700 eb:dark:text-pink-300',\n};\n\n/** The color's swatch in the tags dialog. */\nexport const TAG_SWATCHES = {\n gray: 'eb:bg-gray-500',\n red: 'eb:bg-red-500',\n orange: 'eb:bg-orange-500',\n amber: 'eb:bg-amber-500',\n lime: 'eb:bg-lime-500',\n green: 'eb:bg-green-600',\n teal: 'eb:bg-teal-600',\n cyan: 'eb:bg-cyan-500',\n blue: 'eb:bg-blue-500',\n violet: 'eb:bg-violet-500',\n fuchsia: 'eb:bg-fuchsia-500',\n pink: 'eb:bg-pink-500',\n};\n\n/** @param {{tag: import('../../features/tags/tags.js').TemplateTag, className?: string}} props */\nexport function TagBadge({ tag, className }) {\n return (\n <span\n data-testid=\"eb-tag\"\n data-color={tag.color}\n className={cn(\n 'eb:inline-block eb:max-w-full eb:shrink-0 eb:truncate eb:rounded-md eb:border eb:px-1.5 eb:py-0 eb:text-[10px]',\n TAG_STYLES[tag.color] || TAG_STYLES.gray,\n className\n )}\n >\n {tag.label}\n </span>\n );\n}\n","import { useEffect, useState } from 'react';\nimport { Check, Loader2, Pencil, Plus, Tags, Trash2 } from 'lucide-react';\nimport { MAX_TEMPLATE_TAGS, TAG_COLORS, TAG_LIMITS, sortTags, validateTag } from '../../features/tags/tags.js';\nimport { useUi } from '../context.jsx';\nimport { Button } from '../primitives/button.jsx';\nimport { cn } from '../primitives/cn.js';\nimport { Input, Label } from '../primitives/fields.jsx';\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '../primitives/overlays.jsx';\nimport { TAG_SWATCHES, TagBadge } from './TagBadge.jsx';\n\n/** A new tag takes the next color, so tags made in a row differ. */\nconst colorAfter = (tags) => TAG_COLORS[(TAG_COLORS.indexOf('blue') + tags.length) % TAG_COLORS.length];\n\n/**\n * TAGS — the tags of one template, and the workspace's list of tags.\n *\n * ┌─────────────────────────────────────────────┐\n * │ ▣ Tags × │\n * │ Up to 3 tags on “October newsletter”. │\n * │ ☑ [Marketing] ✎ 🗑 │\n * │ ☐ [Events] ✎ 🗑 │\n * │ 1 / 3 │\n * │ New tag [ Name ] │\n * │ ● ● ● ● ● ● ● ● ● ● ● ● [ + Create ] │\n * │ [ Done ] │\n * └─────────────────────────────────────────────┘\n *\n * Everything is saved as it is done: ticking a tag, creating one (ticked at\n * once while the template has room), renaming it, deleting it — from every\n * template. Errors show in the dialog: the library's notices are under it.\n *\n * @param {{\n * open: boolean,\n * onOpenChange: (open: boolean) => void,\n * template: {id: string|number, name: string, tags?: string[]},\n * tags: import('../../features/tags/tags.js').TemplateTag[],\n * storage: import('../../storage/client.js').TemplateStorage,\n * onTagsChange: (tags: import('../../features/tags/tags.js').TemplateTag[], change?: {deleted?: string}) => void,\n * onTemplateTags: (tagIds: string[]) => void,\n * }} props\n */\nexport default function TagsDialog({ open, onOpenChange, template, tags, storage, onTagsChange, onTemplateTags }) {\n const { t, confirm } = useUi();\n const chosen = (template.tags || []).map(String);\n const full = chosen.length >= MAX_TEMPLATE_TAGS;\n /** null: creating; else the id of the tag being edited. */\n const [editing, setEditing] = useState(null);\n const [form, setForm] = useState({ label: '', color: colorAfter(tags) });\n const [errors, setErrors] = useState({});\n const [failure, setFailure] = useState(null);\n /** What is being saved: `form`, `tick:<id>`, `delete:<id>`. */\n const [busy, setBusy] = useState(null);\n\n const colorName = {\n gray: t('Gray'),\n red: t('Red'),\n orange: t('Orange'),\n amber: t('Yellow'),\n lime: t('Lime'),\n green: t('Green'),\n teal: t('Teal'),\n cyan: t('Cyan'),\n blue: t('Blue'),\n violet: t('Violet'),\n fuchsia: t('Fuchsia'),\n pink: t('Pink'),\n };\n\n const reset = (list = tags) => {\n setEditing(null);\n setForm({ label: '', color: colorAfter(list) });\n setErrors({});\n };\n useEffect(() => {\n if (!open) return;\n reset();\n setFailure(null);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open]);\n\n /** @param {string} what @param {() => Promise<void>} work */\n const run = async (what, work) => {\n setBusy(what);\n setFailure(null);\n try {\n await work();\n } catch (error) {\n setFailure(error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const tick = (id) =>\n run(`tick:${id}`, async () => {\n const next = chosen.includes(id) ? chosen.filter((tagId) => tagId !== id) : [...chosen, id];\n onTemplateTags(await storage.setTemplateTags(template.id, next));\n });\n\n const submit = (event) => {\n event.preventDefault();\n const check = validateTag(form, { existing: tags, editing, t });\n if (check.errors) {\n setErrors(check.errors);\n return;\n }\n run('form', async () => {\n const saved = await storage.saveTag(editing ? { id: editing, ...check.tag } : check.tag, { create: !editing });\n const list = sortTags([...tags.filter((tag) => tag.id !== saved.id), saved]);\n onTagsChange(list);\n if (!editing && !full) onTemplateTags(await storage.setTemplateTags(template.id, [...chosen, saved.id]));\n reset(list);\n });\n };\n\n const remove = async (tag) => {\n const ok = await confirm({\n title: t('Delete the tag “{name}”?', { name: tag.label }),\n description: t('It is removed from every template.'),\n confirmLabel: t('Delete'),\n destructive: true,\n });\n if (!ok) return;\n run(`delete:${tag.id}`, async () => {\n await storage.deleteTag(tag.id);\n const list = tags.filter((other) => other.id !== tag.id);\n onTagsChange(list, { deleted: tag.id });\n if (editing === tag.id) reset(list);\n });\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"eb:sm:max-w-[460px]\" data-testid=\"eb-tags-dialog\">\n <DialogHeader>\n <div className=\"eb:flex eb:items-center eb:gap-3\">\n <span className=\"eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-lg2 eb:bg-primary/10 eb:text-primary\">\n <Tags className=\"eb:size-5\" />\n </span>\n <div className=\"eb:min-w-0\">\n <DialogTitle className=\"eb:text-base eb:font-normal\">{t('Tags')}</DialogTitle>\n <DialogDescription className=\"eb:text-[13px]\">\n {t('Up to {max} tags on “{name}”.', { max: MAX_TEMPLATE_TAGS, name: template.name })}\n </DialogDescription>\n </div>\n </div>\n </DialogHeader>\n\n <div className=\"eb:flex eb:flex-col eb:gap-1\">\n {tags.length === 0 ? (\n <p className=\"eb:m-0 eb:rounded-lg2 eb:border eb:border-dashed eb:border-border eb:p-3 eb:text-center eb:text-[12.5px] eb:text-foreground-lighter\">\n {t('No tag yet: create the first one below.')}\n </p>\n ) : (\n <ul className=\"eb:flex eb:max-h-56 eb:flex-col eb:gap-0.5 eb:overflow-y-auto\" aria-label={t('Tags')}>\n {tags.map((tag) => {\n const checked = chosen.includes(tag.id);\n const locked = !checked && full;\n return (\n <li\n key={tag.id}\n data-testid=\"eb-tags-option\"\n className={cn('eb:flex eb:items-center eb:gap-2 eb:rounded-md eb:px-2 eb:py-1', editing === tag.id ? 'eb:bg-primary/5' : 'eb:hover:bg-surface-75')}\n >\n <label\n className={cn('eb:flex eb:min-w-0 eb:flex-1 eb:items-center eb:gap-2.5', locked ? 'eb:cursor-not-allowed eb:opacity-50' : 'eb:cursor-pointer')}\n title={locked ? t('{max} tags at most: untick one to choose another.', { max: MAX_TEMPLATE_TAGS }) : undefined}\n >\n <input\n type=\"checkbox\"\n className=\"eb:size-4 eb:shrink-0 eb:accent-primary\"\n checked={checked}\n disabled={locked || busy !== null}\n onChange={() => tick(tag.id)}\n aria-label={tag.label}\n />\n <TagBadge tag={tag} className=\"eb:text-[12px]\" />\n {busy === `tick:${tag.id}` && <Loader2 className=\"eb:size-3.5 eb:animate-spin eb:text-foreground-muted\" />}\n </label>\n <Button\n variant=\"ghost\"\n size=\"icon-xs\"\n className=\"eb:text-foreground-muted\"\n disabled={busy !== null}\n onClick={() => {\n setEditing(tag.id);\n setForm({ label: tag.label, color: tag.color });\n setErrors({});\n }}\n aria-label={t('Edit the tag “{name}”', { name: tag.label })}\n title={t('Edit')}\n >\n <Pencil />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon-xs\"\n className=\"eb:text-foreground-muted eb:hover:text-destructive\"\n disabled={busy !== null}\n onClick={() => remove(tag)}\n aria-label={t('Delete the tag “{name}”', { name: tag.label })}\n title={t('Delete')}\n >\n {busy === `delete:${tag.id}` ? <Loader2 className=\"eb:animate-spin\" /> : <Trash2 />}\n </Button>\n </li>\n );\n })}\n </ul>\n )}\n {tags.length > 0 && (\n <p className=\"eb:m-0 eb:px-2 eb:text-right eb:text-[11.5px] eb:text-foreground-muted\" aria-live=\"polite\" data-testid=\"eb-tags-count\">\n {full ? t('{max} tags at most: untick one to choose another.', { max: MAX_TEMPLATE_TAGS }) : `${chosen.length} / ${MAX_TEMPLATE_TAGS}`}\n </p>\n )}\n </div>\n\n <form onSubmit={submit} className=\"eb:flex eb:flex-col eb:gap-2.5 eb:border-t eb:border-border eb:pt-4\" noValidate data-testid=\"eb-tags-form\">\n <Label htmlFor=\"eb-tag-label\">{editing ? t('Edit tag') : t('New tag')}</Label>\n <Input\n id=\"eb-tag-label\"\n value={form.label}\n maxLength={TAG_LIMITS.label}\n placeholder={t('Name')}\n onChange={(event) => {\n setForm((f) => ({ ...f, label: event.target.value }));\n setErrors((e) => ({ ...e, label: undefined }));\n }}\n aria-invalid={errors.label ? true : undefined}\n aria-describedby={errors.label ? 'eb-tag-label-error' : undefined}\n />\n {errors.label && (\n <p id=\"eb-tag-label-error\" className=\"eb:m-0 eb:text-[12px] eb:text-destructive\">\n {errors.label}\n </p>\n )}\n {/* The colors fill the row up to the buttons, shrinking a little in a\n long language; the buttons go under them when the row is too\n narrow (editing, a phone). A basis, not a width, so the colors\n never make the dialog wider than a phone. */}\n <div className=\"eb:flex eb:flex-wrap eb:items-center eb:gap-3\">\n <div role=\"radiogroup\" aria-label={t('Color')} className=\"eb:flex eb:min-w-0 eb:flex-[1_1_16rem] eb:items-center eb:justify-between eb:gap-1\">\n {TAG_COLORS.map((color) => {\n const picked = form.color === color;\n return (\n <button\n key={color}\n type=\"button\"\n role=\"radio\"\n aria-checked={picked}\n aria-label={colorName[color]}\n title={colorName[color]}\n onClick={() => setForm((f) => ({ ...f, color }))}\n className={cn(\n 'eb:flex eb:aspect-square eb:min-w-4 eb:shrink eb:basis-6 eb:items-center eb:justify-center eb:rounded-full eb:text-white eb:outline-none eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n TAG_SWATCHES[color],\n picked && 'eb:ring-2 eb:ring-foreground/60 eb:ring-offset-2 eb:ring-offset-background'\n )}\n >\n {picked && <Check className=\"eb:size-3.5\" />}\n </button>\n );\n })}\n </div>\n <div className=\"eb:ml-auto eb:flex eb:gap-2\">\n {editing && (\n <Button variant=\"outline\" size=\"sm\" onClick={() => reset()} disabled={busy !== null}>\n {t('Cancel')}\n </Button>\n )}\n <Button type=\"submit\" size=\"sm\" variant={editing ? 'default' : 'outline'} disabled={busy !== null}>\n {busy === 'form' ? <Loader2 className=\"eb:animate-spin\" /> : !editing && <Plus />}\n {editing ? t('Save') : t('Create')}\n </Button>\n </div>\n </div>\n {form.label.trim() && !errors.label && (\n <p className=\"eb:m-0 eb:flex eb:items-center eb:gap-2 eb:text-[11.5px] eb:text-foreground-muted\">\n {t('Preview')}\n <TagBadge tag={{ id: 'preview', label: form.label.trim(), color: form.color }} className=\"eb:text-[12px]\" />\n </p>\n )}\n </form>\n\n {failure && (\n <p role=\"alert\" className=\"eb:m-0 eb:text-[12.5px] eb:text-destructive\">\n {failure}\n </p>\n )}\n\n <div className=\"eb:flex eb:justify-end\">\n <Button onClick={() => onOpenChange(false)}>{t('Done')}</Button>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa,aAAAA,YAAW,SAAS,QAAQ,YAAAC,iBAAgB;AAClE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA;AAAA,OACK;;;AC0BH;AAlCJ,IAAM,aAAa;AAAA,EACjB,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AACR;AAGO,IAAM,eAAe;AAAA,EAC1B,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AACR;AAGO,SAAS,SAAS,EAAE,KAAK,UAAU,GAAG;AAC3C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAY;AAAA,MACZ,cAAY,IAAI;AAAA,MAChB,WAAW;AAAA,QACT;AAAA,QACA,WAAW,IAAI,KAAK,KAAK,WAAW;AAAA,QACpC;AAAA,MACF;AAAA,MAEC,cAAI;AAAA;AAAA,EACP;AAEJ;;;ACrDA,SAAS,WAAW,gBAAgB;AACpC,SAAS,OAAO,SAAS,QAAQ,MAAM,MAAM,cAAc;AAwI7C,gBAAAC,MAEF,YAFE;AA9Hd,IAAM,aAAa,CAAC,SAAS,YAAY,WAAW,QAAQ,MAAM,IAAI,KAAK,UAAU,WAAW,MAAM;AA8BvF,SAAR,WAA4B,EAAE,MAAM,cAAc,UAAU,MAAM,SAAS,cAAc,eAAe,GAAG;AAChH,QAAM,EAAE,GAAG,QAAQ,IAAI,MAAM;AAC7B,QAAM,UAAU,SAAS,QAAQ,CAAC,GAAG,IAAI,MAAM;AAC/C,QAAM,OAAO,OAAO,UAAU;AAE9B,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE,OAAO,IAAI,OAAO,WAAW,IAAI,EAAE,CAAC;AACvE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,CAAC,CAAC;AACvC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAE3C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,IAAI;AAErC,QAAM,YAAY;AAAA,IAChB,MAAM,EAAE,MAAM;AAAA,IACd,KAAK,EAAE,KAAK;AAAA,IACZ,QAAQ,EAAE,QAAQ;AAAA,IAClB,OAAO,EAAE,QAAQ;AAAA,IACjB,MAAM,EAAE,MAAM;AAAA,IACd,OAAO,EAAE,OAAO;AAAA,IAChB,MAAM,EAAE,MAAM;AAAA,IACd,MAAM,EAAE,MAAM;AAAA,IACd,MAAM,EAAE,MAAM;AAAA,IACd,QAAQ,EAAE,QAAQ;AAAA,IAClB,SAAS,EAAE,SAAS;AAAA,IACpB,MAAM,EAAE,MAAM;AAAA,EAChB;AAEA,QAAM,QAAQ,CAAC,OAAO,SAAS;AAC7B,eAAW,IAAI;AACf,YAAQ,EAAE,OAAO,IAAI,OAAO,WAAW,IAAI,EAAE,CAAC;AAC9C,cAAU,CAAC,CAAC;AAAA,EACd;AACA,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM;AACN,eAAW,IAAI;AAAA,EAEjB,GAAG,CAAC,IAAI,CAAC;AAGT,QAAM,MAAM,OAAO,MAAM,SAAS;AAChC,YAAQ,IAAI;AACZ,eAAW,IAAI;AACf,QAAI;AACF,YAAM,KAAK;AAAA,IACb,SAAS,OAAO;AACd,iBAAW,MAAM,OAAO;AAAA,IAC1B,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,QAAM,OAAO,CAAC,OACZ,IAAI,QAAQ,EAAE,IAAI,YAAY;AAC5B,UAAM,OAAO,OAAO,SAAS,EAAE,IAAI,OAAO,OAAO,CAAC,UAAU,UAAU,EAAE,IAAI,CAAC,GAAG,QAAQ,EAAE;AAC1F,mBAAe,MAAM,QAAQ,gBAAgB,SAAS,IAAI,IAAI,CAAC;AAAA,EACjE,CAAC;AAEH,QAAM,SAAS,CAAC,UAAU;AACxB,UAAM,eAAe;AACrB,UAAM,QAAQ,YAAY,MAAM,EAAE,UAAU,MAAM,SAAS,EAAE,CAAC;AAC9D,QAAI,MAAM,QAAQ;AAChB,gBAAU,MAAM,MAAM;AACtB;AAAA,IACF;AACA,QAAI,QAAQ,YAAY;AACtB,YAAM,QAAQ,MAAM,QAAQ,QAAQ,UAAU,EAAE,IAAI,SAAS,GAAG,MAAM,IAAI,IAAI,MAAM,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;AAC7G,YAAM,OAAO,SAAS,CAAC,GAAG,KAAK,OAAO,CAAC,QAAQ,IAAI,OAAO,MAAM,EAAE,GAAG,KAAK,CAAC;AAC3E,mBAAa,IAAI;AACjB,UAAI,CAAC,WAAW,CAAC,KAAM,gBAAe,MAAM,QAAQ,gBAAgB,SAAS,IAAI,CAAC,GAAG,QAAQ,MAAM,EAAE,CAAC,CAAC;AACvG,YAAM,IAAI;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,QAAM,SAAS,OAAO,QAAQ;AAC5B,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO,EAAE,sCAA4B,EAAE,MAAM,IAAI,MAAM,CAAC;AAAA,MACxD,aAAa,EAAE,oCAAoC;AAAA,MACnD,cAAc,EAAE,QAAQ;AAAA,MACxB,aAAa;AAAA,IACf,CAAC;AACD,QAAI,CAAC,GAAI;AACT,QAAI,UAAU,IAAI,EAAE,IAAI,YAAY;AAClC,YAAM,QAAQ,UAAU,IAAI,EAAE;AAC9B,YAAM,OAAO,KAAK,OAAO,CAAC,UAAU,MAAM,OAAO,IAAI,EAAE;AACvD,mBAAa,MAAM,EAAE,SAAS,IAAI,GAAG,CAAC;AACtC,UAAI,YAAY,IAAI,GAAI,OAAM,IAAI;AAAA,IACpC,CAAC;AAAA,EACH;AAEA,SACE,gBAAAA,KAAC,UAAO,MAAY,cAClB,+BAAC,iBAAc,WAAU,uBAAsB,eAAY,kBACzD;AAAA,oBAAAA,KAAC,gBACC,+BAAC,SAAI,WAAU,oCACb;AAAA,sBAAAA,KAAC,UAAK,WAAU,oHACd,0BAAAA,KAAC,QAAK,WAAU,aAAY,GAC9B;AAAA,MACA,qBAAC,SAAI,WAAU,cACb;AAAA,wBAAAA,KAAC,eAAY,WAAU,+BAA+B,YAAE,MAAM,GAAE;AAAA,QAChE,gBAAAA,KAAC,qBAAkB,WAAU,kBAC1B,YAAE,2CAAiC,EAAE,KAAK,mBAAmB,MAAM,SAAS,KAAK,CAAC,GACrF;AAAA,SACF;AAAA,OACF,GACF;AAAA,IAEA,qBAAC,SAAI,WAAU,gCACZ;AAAA,WAAK,WAAW,IACf,gBAAAA,KAAC,OAAE,WAAU,uIACV,YAAE,yCAAyC,GAC9C,IAEA,gBAAAA,KAAC,QAAG,WAAU,iEAAgE,cAAY,EAAE,MAAM,GAC/F,eAAK,IAAI,CAAC,QAAQ;AACjB,cAAM,UAAU,OAAO,SAAS,IAAI,EAAE;AACtC,cAAM,SAAS,CAAC,WAAW;AAC3B,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,eAAY;AAAA,YACZ,WAAW,GAAG,kEAAkE,YAAY,IAAI,KAAK,oBAAoB,wBAAwB;AAAA,YAEjJ;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW,GAAG,2DAA2D,SAAS,wCAAwC,mBAAmB;AAAA,kBAC7I,OAAO,SAAS,EAAE,qDAAqD,EAAE,KAAK,kBAAkB,CAAC,IAAI;AAAA,kBAErG;AAAA,oCAAAA;AAAA,sBAAC;AAAA;AAAA,wBACC,MAAK;AAAA,wBACL,WAAU;AAAA,wBACV;AAAA,wBACA,UAAU,UAAU,SAAS;AAAA,wBAC7B,UAAU,MAAM,KAAK,IAAI,EAAE;AAAA,wBAC3B,cAAY,IAAI;AAAA;AAAA,oBAClB;AAAA,oBACA,gBAAAA,KAAC,YAAS,KAAU,WAAU,kBAAiB;AAAA,oBAC9C,SAAS,QAAQ,IAAI,EAAE,MAAM,gBAAAA,KAAC,WAAQ,WAAU,wDAAuD;AAAA;AAAA;AAAA,cAC1G;AAAA,cACA,gBAAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,WAAU;AAAA,kBACV,UAAU,SAAS;AAAA,kBACnB,SAAS,MAAM;AACb,+BAAW,IAAI,EAAE;AACjB,4BAAQ,EAAE,OAAO,IAAI,OAAO,OAAO,IAAI,MAAM,CAAC;AAC9C,8BAAU,CAAC,CAAC;AAAA,kBACd;AAAA,kBACA,cAAY,EAAE,mCAAyB,EAAE,MAAM,IAAI,MAAM,CAAC;AAAA,kBAC1D,OAAO,EAAE,MAAM;AAAA,kBAEf,0BAAAA,KAAC,UAAO;AAAA;AAAA,cACV;AAAA,cACA,gBAAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,WAAU;AAAA,kBACV,UAAU,SAAS;AAAA,kBACnB,SAAS,MAAM,OAAO,GAAG;AAAA,kBACzB,cAAY,EAAE,qCAA2B,EAAE,MAAM,IAAI,MAAM,CAAC;AAAA,kBAC5D,OAAO,EAAE,QAAQ;AAAA,kBAEhB,mBAAS,UAAU,IAAI,EAAE,KAAK,gBAAAA,KAAC,WAAQ,WAAU,mBAAkB,IAAK,gBAAAA,KAAC,UAAO;AAAA;AAAA,cACnF;AAAA;AAAA;AAAA,UA5CK,IAAI;AAAA,QA6CX;AAAA,MAEJ,CAAC,GACH;AAAA,MAED,KAAK,SAAS,KACb,gBAAAA,KAAC,OAAE,WAAU,0EAAyE,aAAU,UAAS,eAAY,iBAClH,iBAAO,EAAE,qDAAqD,EAAE,KAAK,kBAAkB,CAAC,IAAI,GAAG,OAAO,MAAM,MAAM,iBAAiB,IACtI;AAAA,OAEJ;AAAA,IAEA,qBAAC,UAAK,UAAU,QAAQ,WAAU,uEAAsE,YAAU,MAAC,eAAY,gBAC7H;AAAA,sBAAAA,KAAC,SAAM,SAAQ,gBAAgB,oBAAU,EAAE,UAAU,IAAI,EAAE,SAAS,GAAE;AAAA,MACtE,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAO,KAAK;AAAA,UACZ,WAAW,WAAW;AAAA,UACtB,aAAa,EAAE,MAAM;AAAA,UACrB,UAAU,CAAC,UAAU;AACnB,oBAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM,OAAO,MAAM,EAAE;AACpD,sBAAU,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,OAAU,EAAE;AAAA,UAC/C;AAAA,UACA,gBAAc,OAAO,QAAQ,OAAO;AAAA,UACpC,oBAAkB,OAAO,QAAQ,uBAAuB;AAAA;AAAA,MAC1D;AAAA,MACC,OAAO,SACN,gBAAAA,KAAC,OAAE,IAAG,sBAAqB,WAAU,6CAClC,iBAAO,OACV;AAAA,MAMF,qBAAC,SAAI,WAAU,iDACb;AAAA,wBAAAA,KAAC,SAAI,MAAK,cAAa,cAAY,EAAE,OAAO,GAAG,WAAU,sFACtD,qBAAW,IAAI,CAAC,UAAU;AACzB,gBAAM,SAAS,KAAK,UAAU;AAC9B,iBACE,gBAAAA;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,gBAAc;AAAA,cACd,cAAY,UAAU,KAAK;AAAA,cAC3B,OAAO,UAAU,KAAK;AAAA,cACtB,SAAS,MAAM,QAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE;AAAA,cAC/C,WAAW;AAAA,gBACT;AAAA,gBACA,aAAa,KAAK;AAAA,gBAClB,UAAU;AAAA,cACZ;AAAA,cAEC,oBAAU,gBAAAA,KAAC,SAAM,WAAU,eAAc;AAAA;AAAA,YAbrC;AAAA,UAcP;AAAA,QAEJ,CAAC,GACH;AAAA,QACA,qBAAC,SAAI,WAAU,+BACZ;AAAA,qBACC,gBAAAA,KAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAS,MAAM,MAAM,GAAG,UAAU,SAAS,MAC5E,YAAE,QAAQ,GACb;AAAA,UAEF,qBAAC,UAAO,MAAK,UAAS,MAAK,MAAK,SAAS,UAAU,YAAY,WAAW,UAAU,SAAS,MAC1F;AAAA,qBAAS,SAAS,gBAAAA,KAAC,WAAQ,WAAU,mBAAkB,IAAK,CAAC,WAAW,gBAAAA,KAAC,QAAK;AAAA,YAC9E,UAAU,EAAE,MAAM,IAAI,EAAE,QAAQ;AAAA,aACnC;AAAA,WACF;AAAA,SACF;AAAA,MACC,KAAK,MAAM,KAAK,KAAK,CAAC,OAAO,SAC5B,qBAAC,OAAE,WAAU,qFACV;AAAA,UAAE,SAAS;AAAA,QACZ,gBAAAA,KAAC,YAAS,KAAK,EAAE,IAAI,WAAW,OAAO,KAAK,MAAM,KAAK,GAAG,OAAO,KAAK,MAAM,GAAG,WAAU,kBAAiB;AAAA,SAC5G;AAAA,OAEJ;AAAA,IAEC,WACC,gBAAAA,KAAC,OAAE,MAAK,SAAQ,WAAU,+CACvB,mBACH;AAAA,IAGF,gBAAAA,KAAC,SAAI,WAAU,0BACb,0BAAAA,KAAC,UAAO,SAAS,MAAM,aAAa,KAAK,GAAI,YAAE,MAAM,GAAE,GACzD;AAAA,KACF,GACF;AAEJ;;;AFhKM,SA4bM,UA3bJ,OAAAC,MADF,QAAAC,aAAA;AArEN,IAAM,cAAc,EAAE,KAAK,WAAW,OAAO,WAAW,MAAM,UAAU;AACxE,IAAM,wBAAwB;AAE9B,IAAM,UAAU,CAAC,UAAU,WAAW;AACpC,QAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,SAAO,CAAC,KAAK,OAAO,KAAK,CAAC,MAAM,OAAO,KAAK,EAAE,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AAC3E;AAGA,IAAM,SAAS,CAAC,MAAM,aACpB,OAAO,QAAQ,EAAE,EACd,UAAU,KAAK,EACf,QAAQ,UAAU,EAAE,EACpB,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE,KAAK;AAGrB,SAAS,aAAa,YAAY;AAChC,QAAM,CAAC,WAAW,YAAY,IAAIC,UAAS,MAAM;AAC/C,QAAI,CAAC,WAAY,QAAO,oBAAI,IAAI;AAChC,QAAI;AACF,aAAO,IAAI,IAAI,KAAK,MAAM,aAAa,QAAQ,UAAU,KAAK,IAAI,CAAC;AAAA,IACrE,QAAQ;AACN,aAAO,oBAAI,IAAI;AAAA,IACjB;AAAA,EACF,CAAC;AACD,QAAM,SAAS;AAAA,IACb,CAAC,QACC,aAAa,CAAC,YAAY;AACxB,YAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,UAAI,KAAK,IAAI,GAAG,EAAG,MAAK,OAAO,GAAG;AAAA,UAC7B,MAAK,IAAI,GAAG;AACjB,UAAI;AACF,qBAAa,QAAQ,YAAY,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;AAAA,MAC5D,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,IACT,CAAC;AAAA,IACH,CAAC,UAAU;AAAA,EACb;AACA,SAAO,EAAE,SAAS,QAAQ,UAAU,GAAG,WAAW,OAAO;AAC3D;AAEA,SAAS,SAAS,MAAM,MAAM;AAC5B,QAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,0BAA0B,CAAC;AACjE,QAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,QAAM,IAAI,SAAS,cAAc,GAAG;AACpC,IAAE,OAAO;AACT,IAAE,WAAW,GAAG,OAAO,MAAM,OAAO,CAAC;AACrC,IAAE,MAAM;AACR,aAAW,MAAM,IAAI,gBAAgB,GAAG,GAAG,GAAI;AACjD;AAEA,SAAS,IAAI,EAAE,OAAO,UAAU,UAAU,SAAS,GAAG;AACpD,SACE,gBAAAD;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS;AAAA,MACT,gBAAc,WAAW,SAAS;AAAA,MAClC,eAAY;AAAA,MACZ,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,WAAW,yCAAyC;AAAA,MACtD;AAAA,MAEA;AAAA,wBAAAA,MAAC,UAAK,WAAU,sCACd;AAAA,0BAAAD,KAAC,UAAK,WAAU,sEAAsE,gBAAM,MAAK;AAAA,UAChG,YAAY,gBAAAA,KAAC,QAAK,WAAU,2DAA0D,eAAY,QAAO;AAAA,WAC5G;AAAA,QACA,gBAAAA,KAAC,UAAK,WAAU,yFAAyF,gBAAM,MAAK;AAAA,QAGpH,gBAAAC,MAAC,UAAK,WAAU,4DACd;AAAA,0BAAAD,KAAC,UAAK,WAAU,yIACb,gBAAM,KAAK,IAAI,CAAC,QACf,gBAAAA,KAAC,YAAsB,KAAU,WAAU,uBAA5B,IAAI,EAA4C,CAChE,GACH;AAAA,UACC,MAAM,QAAQ,gBAAAA,KAAC,UAAK,WAAU,yDAAyD,gBAAM,MAAK;AAAA,WACrG;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,SAAS,QAAQ,EAAE,OAAO,UAAU,GAAG,MAAM,GAAG;AAC9C,SACE,gBAAAC,MAAC,SAAI,WAAU,kCAAkC,GAAG,OAClD;AAAA,oBAAAD,KAAC,gBAAc,iBAAM;AAAA,IACpB;AAAA,KACH;AAEJ;AAEA,SAAS,QAAQ;AAAA,EACf,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAG;AACD,QAAM,EAAE,GAAG,QAAQ,QAAQ,QAAQ,IAAI,MAAM;AAC7C,QAAM,UAAU,QAAQ,MAAM,mBAAmB,kBAAkB,GAAG,CAAC,eAAe,CAAC;AACvF,QAAM,SAAS,OAAO,QAAQ,aAAa,cAAc,OAAO,QAAQ,oBAAoB;AAC5F,QAAM,aAAa,QAAQ,MAAM,IAAI,KAAK,eAAe,QAAQ,WAAW,GAAG,CAAC,MAAM,CAAC;AACvF,QAAM,aAAa,YAAY,CAAC,OAAQ,KAAK,WAAW,OAAO,IAAI,KAAK,EAAE,CAAC,IAAI,IAAK,CAAC,UAAU,CAAC;AAChG,QAAM,QAAQ,aAAa,YAAY;AAEvC,QAAM,UAAU,OAAO,IAAI;AAC3B,QAAM,YAAY,OAAO,IAAI;AAC7B,QAAM,SAAS,eAAe,OAAO;AAErC,QAAM,CAAC,OAAO,QAAQ,IAAIE,UAAS,EAAE,QAAQ,WAAW,MAAM,CAAC,GAAG,OAAO,KAAK,CAAC;AAE/E,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAS,CAAC,CAAC;AACnC,QAAM,CAAC,UAAU,WAAW,IAAIA,UAAS,KAAK;AAC9C,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAS,EAAE;AAErC,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAS,MAAM,oBAAI,IAAI,CAAC;AAEpD,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAS,cAAc,OAAO,YAAY,UAAU,KAAK,IAAI;AAE/F,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAS,KAAK;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAS,EAAE,QAAQ,QAAQ,UAAU,MAAM,OAAO,KAAK,CAAC;AACpF,QAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,EAAE,MAAM,IAAI,SAAS,OAAO,OAAO,KAAK,CAAC;AAChF,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAS,SAAS;AAC9C,QAAM,CAAC,MAAM,OAAO,IAAIA,UAAS,IAAI;AAIrC,QAAM,WAAW,OAAO,CAAC;AACzB,QAAM,SAAS,YAAY,YAAY;AACrC,UAAM,SAAS,EAAE,SAAS;AAC1B,QAAI,OAAO,QAAQ,SAAS,YAAY;AACtC,eAAS,EAAE,QAAQ,SAAS,MAAM,CAAC,GAAG,OAAO,KAAK,CAAC;AACnD;AAAA,IACF;AACA,aAAS,CAAC,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,KAAK,SAAS,UAAU,WAAW,OAAO,KAAK,EAAE;AACpF,QAAI;AAEF,YAAM,CAAC,MAAM,OAAO,IAAI,MAAM,QAAQ,IAAI,CAAC,QAAQ,KAAK,GAAG,SAAS,QAAQ,SAAS,EAAE,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5G,UAAI,WAAW,SAAS,SAAS;AAC/B,iBAAS,EAAE,QAAQ,SAAS,MAAM,OAAO,KAAK,CAAC;AAC/C,gBAAQ,OAAO;AAAA,MACjB;AAAA,IACF,SAAS,OAAO;AACd,UAAI,WAAW,SAAS,QAAS,UAAS,EAAE,QAAQ,SAAS,MAAM,CAAC,GAAG,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC/F;AAAA,EACF,GAAG,CAAC,SAAS,MAAM,CAAC;AAEpB,EAAAC,WAAU,MAAM;AACd,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AAIX,QAAM,kBAAkB,QAAQ,MAAM;AACpC,UAAM,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC;AAC7D,WAAO,MAAM,KAAK,IAAI,CAAC,UAAU;AAAA,MAC/B,KAAK,YAAY,KAAK,EAAE;AAAA,MACxB,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,MAAM,OAAO,KAAK,MAAM,IAAI,KAAK,EAAE,EAAE;AAAA,MACrC,OAAO,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,OAAO,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,OAAO;AAAA,MACxE,MAAM,WAAW,KAAK,aAAa,KAAK,SAAS;AAAA,MACjD,SAAS,KAAK;AAAA,MACd;AAAA,IACF,EAAE;AAAA,EACJ,GAAG,CAAC,MAAM,MAAM,MAAM,UAAU,CAAC;AAEjC,QAAM,WAAW,QAAQ,MAAM;AAC7B,UAAM,OAAO,IAAI,IAAI,MAAM,KAAK,QAAQ,CAAC,UAAU,KAAK,QAAQ,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC;AAChF,WAAO,KAAK,OAAO,CAAC,QAAQ,KAAK,IAAI,OAAO,IAAI,EAAE,CAAC,CAAC;AAAA,EACtD,GAAG,CAAC,MAAM,MAAM,IAAI,CAAC;AAErB,QAAM,SAAS,IAAI,IAAI,SAAS,IAAI,CAAC,QAAQ,OAAO,IAAI,EAAE,CAAC,EAAE,OAAO,CAAC,OAAO,OAAO,IAAI,EAAE,CAAC,CAAC;AAC3F,QAAM,eAAe,CAAC,OACpB,UAAU,CAAC,YAAY;AACrB,UAAM,OAAO,IAAI,IAAI,OAAO;AAC5B,QAAI,KAAK,IAAI,EAAE,EAAG,MAAK,OAAO,EAAE;AAAA,QAC3B,MAAK,IAAI,EAAE;AAChB,WAAO;AAAA,EACT,CAAC;AACH,QAAM,QAAQ,gBAAgB;AAAA,IAC5B,CAAC,OACE,OAAO,SAAS,KAAK,EAAE,KAAK,KAAK,CAAC,QAAQ,OAAO,IAAI,OAAO,IAAI,EAAE,CAAC,CAAC,MACrE,QAAQ,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,EAAE,KAAK,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC;AAAA,EAC/E;AACA,QAAM,kBAAkB,MAAM,OAAO,CAAC,MAAM,MAAM,UAAU,IAAI,EAAE,GAAG,CAAC;AACtE,QAAM,iBAAiB,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,UAAU,IAAI,EAAE,GAAG,CAAC;AAGtE,EAAAA,WAAU,MAAM;AACd,QAAI,MAAM,WAAW,WAAW,UAAW;AAC3C,QAAI,MAAM,KAAK,OAAQ,cAAa,YAAY,MAAM,KAAK,CAAC,EAAE,EAAE,EAAE;AAAA,EACpE,GAAG,CAAC,OAAO,SAAS,CAAC;AAGrB,oBAAkB,SAAS;AAE3B,QAAM,WAAW,gBAAgB,KAAK,CAAC,MAAM,EAAE,QAAQ,SAAS,KAAK;AACrE,QAAM,aAAa,WAAW,WAAW,WAAW,IAAI,UAAU,MAAM,YAAY,MAAM,IAAI;AAG9F,EAAAA,WAAU,MAAM;AACd,QAAI,cAAc,MAAM;AACtB,gBAAU,EAAE,QAAQ,QAAQ,UAAU,MAAM,OAAO,KAAK,CAAC;AACzD,aAAO;AAAA,IACT;AACA,QAAI,OAAO;AACX,cAAU,CAAC,OAAO,EAAE,QAAQ,WAAW,UAAU,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,EAAE,WAAW,MAAM,OAAO,KAAK,EAAE;AAC1H,YAAQ,KAAK,UAAU,EAAE;AAAA,MACvB,CAACC,cAAa,QAAQ,UAAU,EAAE,QAAQ,SAAS,UAAAA,WAAU,OAAO,KAAK,CAAC;AAAA,MAC1E,CAAC,UAAU,QAAQ,UAAU,EAAE,QAAQ,SAAS,UAAU,MAAM,OAAO,MAAM,QAAQ,CAAC;AAAA,IACxF;AACA,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,YAAY,OAAO,CAAC;AAGxB,QAAM,WAAW,OAAO;AACxB,QAAM,SAAS,WACX,EAAE,MAAM,SAAS,MAAM,SAAS,SAAS,SAAS,WAAW,SAAS,WAAW,MAAM,SAAS,QAAQ,GAAG,IAC3G;AACJ,QAAM,YAAY,WAAW,YAAY,SAAS,EAAE,IAAI,SAAS,QAAQ,KAAK;AAC9E,EAAAD,WAAU,MAAM;AACd,QAAI,CAAC,QAAQ;AACX,iBAAW,EAAE,MAAM,IAAI,SAAS,cAAc,QAAQ,OAAO,WAAW,WAAW,OAAO,KAAK,CAAC;AAChG,aAAO;AAAA,IACT;AACA,QAAI,OAAO,QAAQ,WAAW,cAAc,CAAC,OAAO,MAAM;AACxD,iBAAW,EAAE,MAAM,OAAO,MAAM,SAAS,OAAO,OAAO,OAAO,OAAO,OAAO,EAAE,gDAAgD,EAAE,CAAC;AACjI,aAAO;AAAA,IACT;AACA,QAAI,OAAO;AACX,eAAW,CAAC,OAAO,EAAE,GAAG,GAAG,SAAS,MAAM,OAAO,KAAK,EAAE;AACxD,YAAQ,OAAO,EAAE,MAAM,OAAO,MAAM,SAAS,OAAO,SAAS,WAAW,OAAO,UAAU,CAAC,EAAE;AAAA,MAC1F,CAAC,WAAW,QAAQ,WAAW,EAAE,MAAM,sBAAsB,OAAO,IAAI,GAAG,SAAS,OAAO,OAAO,KAAK,CAAC;AAAA,MACxG,CAAC,UAAU,QAAQ,WAAW,EAAE,MAAM,OAAO,MAAM,SAAS,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM,QAAQ,CAAC;AAAA,IAChH;AACA,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EAEF,GAAG,CAAC,WAAW,OAAO,CAAC;AAIvB,QAAM,SAAS,CAAC,QAAQ;AACtB,iBAAa,GAAG;AAChB,mBAAe,IAAI;AAAA,EACrB;AAEA,QAAM,SAAS,YAAY;AACzB,QAAI,gBAAgB;AAClB,aAAO,QAAQ,cAAc;AAC7B;AAAA,IACF;AACA,UAAM,KAAK,MAAM,QAAQ;AAAA,MACvB,OAAO,EAAE,8BAAoB,EAAE,MAAM,SAAS,KAAK,CAAC;AAAA,MACpD,aAAa,EAAE,gDAAgD;AAAA,MAC/D,cAAc,EAAE,QAAQ;AAAA,MACxB,aAAa;AAAA,IACf,CAAC;AACD,QAAI,CAAC,GAAI;AACT,YAAQ,QAAQ;AAChB,QAAI;AACF,YAAM,QAAQ,OAAO,SAAS,EAAE;AAChC,mBAAa,IAAI;AACjB,qBAAe,KAAK;AACpB,YAAM,OAAO;AACb,aAAO,WAAW,EAAE,mBAAmB,CAAC;AAAA,IAC1C,SAAS,OAAO;AACd,aAAO,SAAS,MAAM,OAAO;AAAA,IAC/B,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,QAAM,YAAY,YAAY;AAC5B,YAAQ,WAAW;AACnB,QAAI;AACF,YAAM,EAAE,UAAU,KAAK,IAAI,MAAM,QAAQ,UAAU,SAAS,EAAE;AAC9D,YAAM,OAAO;AACb,mBAAa,YAAY,KAAK,EAAE,EAAE;AAClC,aAAO,WAAW,EAAE,sBAAsB,CAAC;AAAA,IAC7C,SAAS,OAAO;AACd,aAAO,SAAS,MAAM,OAAO;AAAA,IAC/B,UAAE;AACA,cAAQ,IAAI;AAAA,IACd;AAAA,EACF;AAEA,QAAM,WAAW,YAAY;AAC3B,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,SAAS,IAAI;AACjD,aAAO,WAAW,EAAE,cAAc,CAAC;AAAA,IACrC,QAAQ;AACN,aAAO,SAAS,EAAE,8BAA8B,CAAC;AAAA,IACnD;AAAA,EACF;AAGA,QAAM,kBAAkB,CAAC,QACvB,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,SAAU,OAAO,KAAK,EAAE,MAAM,aAAa,EAAE,GAAG,MAAM,MAAM,IAAI,IAAI,IAAK,EAAE,EAAE;AAE1H,QAAM,aAAa,CAAC,MAAM,EAAE,QAAQ,IAAI,CAAC,MAAM;AAC7C,YAAQ,IAAI;AACZ,QAAI,WAAW,KAAM;AACrB,aAAS,CAAC,OAAO;AAAA,MACf,GAAG;AAAA,MACH,MAAM,EAAE,KAAK,IAAI,CAAC,SAAU,KAAK,MAAM,SAAS,OAAO,IAAI,EAAE,GAAG,MAAM,MAAM,KAAK,KAAK,OAAO,CAAC,OAAO,OAAO,OAAO,EAAE,IAAI,IAAK;AAAA,IAChI,EAAE;AAAA,EACJ;AAEA,QAAM,UAAU,OAAO,QAAQ,SAAS;AACxC,QAAM,WAAW,CAAC,WAAW;AAAA,IAC3B;AAAA,IACA,UAAU,MAAM,QAAQ;AAAA,IACxB,UAAU,MAAM,UAAU,IAAI,MAAM,GAAG;AAAA,IACvC,UAAU,MAAM,OAAO,MAAM,GAAG;AAAA,EAClC;AAIA,QAAM,aACJ,gBAAAF,MAAC,SAAe,WAAU,6CAA4C,eAAY,mBAChF;AAAA,oBAAAA,MAAC,SAAI,WAAU,gFACZ;AAAA,kBACC,gBAAAA,MAAC,UAAO,WAAU,aAAY,SAAS,MAAM,SAAS,GACpD;AAAA,wBAAAD,KAAC,eAAY;AAAA,QACZ,EAAE,cAAc;AAAA,SACnB;AAAA,MAEF,gBAAAC,MAAC,SAAI,WAAU,eACb;AAAA,wBAAAD,KAAC,UAAO,WAAU,sHAAqH;AAAA,QACvI,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,WAAW,CAAC,MAAM;AAChB,kBAAI,EAAE,QAAQ,YAAY,OAAO;AAC/B,kBAAE,eAAe;AACjB,yBAAS,EAAE;AAAA,cACb;AAAA,YACF;AAAA,YACA,aAAa,EAAE,kBAAkB;AAAA,YACjC,cAAY,EAAE,kBAAkB;AAAA,YAChC,qBAAmB;AAAA,YACnB,WAAU;AAAA;AAAA,QACZ;AAAA,QACA,gBAAAC,MAAC,SAAI,WAAU,0FACZ;AAAA,mBACC,gBAAAD,KAAC,YAAO,MAAK,UAAS,SAAS,MAAM,SAAS,EAAE,GAAG,cAAY,EAAE,kBAAkB,GAAG,WAAU,qBAC9F,0BAAAA,KAAC,KAAE,WAAU,iEAAgE,GAC/E;AAAA,UAEF,gBAAAA,KAAC,iBAAe,kBAAQ,QAAQ,MAAK;AAAA,WACvC;AAAA,SACF;AAAA,MACC,SAAS,SAAS,KACjB,gBAAAC,MAAC,SAAI,WAAU,qCAEb;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAY,EAAE,eAAe;AAAA,YAC7B,WAAU;AAAA,YACV,eAAY;AAAA,YAEX,mBAAS,IAAI,CAAC,QACb,gBAAAC;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS,OAAO,IAAI,OAAO,IAAI,EAAE,CAAC;AAAA,gBAClC,SAAS,MAAM,aAAa,OAAO,IAAI,EAAE,CAAC;AAAA,gBAC1C,WAAU;AAAA,gBAEV;AAAA,kCAAAD;AAAA,oBAAC;AAAA;AAAA,sBACC,eAAY;AAAA,sBACZ,WAAW;AAAA,wBACT;AAAA,wBACA,aAAa,IAAI,KAAK,KAAK,aAAa;AAAA,wBACxC,OAAO,IAAI,OAAO,IAAI,EAAE,CAAC,KAAK;AAAA,sBAChC;AAAA;AAAA,kBACF;AAAA,kBACA,gBAAAA,KAAC,UAAK,WAAU,eAAe,cAAI,OAAM;AAAA;AAAA;AAAA,cAbpC,IAAI;AAAA,YAcX,CACD;AAAA;AAAA,QACH;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,EAAE,uBAAuB;AAAA,YAChC,QAAQ,OAAO,OAAO;AAAA,YACtB,SAAS,MAAM,UAAU,oBAAI,IAAI,CAAC;AAAA,YAClC,WAAU;AAAA,YACV,eAAY;AAAA;AAAA,QACd;AAAA,SACF;AAAA,OAEJ;AAAA,IAEA,gBAAAC,MAAC,SAAI,WAAU,+EACZ;AAAA,YAAM,WAAW,gBAAgB,SAAS,KACzC,gBAAAD,KAAC,WAAQ,OAAO,EAAE,WAAW,GAAG,MAAK,SAAQ,cAAY,EAAE,WAAW,GAAG,eAAY,wBAClF,0BAAgB,IAAI,CAAC,UACpB,gBAAAA,KAAC,OAAqB,GAAG,SAAS,KAAK,KAA7B,MAAM,GAA0B,CAC3C,GACH;AAAA,MAED,WACC,gBAAAA,KAAC,WAAQ,OAAO,GAAG,EAAE,WAAW,CAAC,KAAK,eAAe,MAAM,KAAK,eAAY,wBACzE,gBAAM,WAAW,YAChB,gBAAAC,MAAC,OAAE,WAAU,6FACX;AAAA,wBAAAD,KAACK,UAAA,EAAQ,WAAU,+BAA8B;AAAA,QAChD,EAAE,eAAU;AAAA,SACf,IACE,MAAM,WAAW,UACnB,gBAAAJ,MAAC,SAAI,WAAU,8CACb;AAAA,wBAAAD,KAAC,OAAE,WAAU,UAAU,YAAE,+BAA+B,GAAE;AAAA,QAC1D,gBAAAA,KAAC,OAAE,WAAU,6CAA6C,gBAAM,OAAM;AAAA,QACtE,gBAAAC,MAAC,UAAO,SAAQ,WAAU,MAAK,cAAa,WAAU,WAAU,SAAS,QACvE;AAAA,0BAAAD,KAAC,aAAU;AAAA,UACV,EAAE,OAAO;AAAA,WACZ;AAAA,SACF,IACE,eAAe,WAAW,IAC5B,gBAAAA,KAAC,OAAE,WAAU,+EACV,kBACG,EAAE,wCAA8B,EAAE,MAAM,CAAC,IACzC,gBAAgB,SACd,EAAE,0CAA0C,IAC5C,EAAE,wCAAwC,GAClD,IAEA,eAAe,IAAI,CAAC,UAAU,gBAAAA,KAAC,OAAqB,GAAG,SAAS,KAAK,KAA7B,MAAM,GAA0B,CAAE,GAE9E;AAAA,OAEJ;AAAA,OA/GO,MAgHT;AAKF,QAAM,OACJ,YAAY,WACR;AAAA,IACE,MAAM,UAAU,QAAQ,SAAS;AAAA,IACjC,MAAM,UAAU,QAAQ,OAAO,UAAU,MAAM,IAAI,UAAU,EAAE;AAAA,IAC/D,aAAa,UAAU,eAAe,UAAU,MAAM;AAAA,IACtD,QAAQ,EAAE,SAAS;AAAA,IACnB,UAAU,YAAY,UAAU,OAAO;AAAA,IACvC,YAAY,YAAY,UAAU,OAAO;AAAA,IACzC,YAAY,YAAY,UAAU,OAAO;AAAA,IACzC,YAAY,YAAY,UAAU,OAAO;AAAA,IACzC,YAAY,YAAY,UAAU,OAAO;AAAA,IACzC,YAAY,YAAY,UAAU,OAAO;AAAA,EAC3C,IACA;AACN,QAAM,WAAW,YAAY,MAAM,UAAU,IAAI,SAAS,IAAI;AAE9D,QAAM,cACJ,cAAc,QAAQ,OAAO,WAAW,UACtC,gBAAAC,MAAC,OAAE,WAAU,6EACX;AAAA,oBAAAD,KAAC,eAAY,WAAU,mCAAkC;AAAA,IACxD,OAAO;AAAA,KACV,IACE,OACF,gBAAAC,MAAA,YACE;AAAA,oBAAAA,MAAC,SAAI,WAAU,gCACb;AAAA,sBAAAA,MAAC,SAAI,WAAU,oCACb;AAAA,wBAAAD,KAAC,QAAG,WAAU,gFAAgF,eAAK,MAAK;AAAA,QACvG,MAAM,WACL,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,WAAW,GAAG,eAAe,WAAW,oBAAoB,gDAAgD;AAAA,YAC5G,SAAS,MAAM,MAAM,OAAO,SAAS;AAAA,YACrC,gBAAc;AAAA,YACd,cAAY,WAAW,EAAE,uBAAuB,IAAI,EAAE,kBAAkB;AAAA,YACxE,OAAO,WAAW,EAAE,uBAAuB,IAAI,EAAE,kBAAkB;AAAA,YAEnE,0BAAAA,KAAC,QAAK,WAAW,GAAG,aAAa,YAAY,iBAAiB,GAAG;AAAA;AAAA,QACnE;AAAA,SAEJ;AAAA,MACA,gBAAAA,KAAC,OAAE,WAAU,mEAAmE,eAAK,MAAK;AAAA,MACzF,KAAK,eACJ,gBAAAA,KAAC,OAAE,WAAU,yEAAyE,eAAK,aAAY;AAAA,OAE3G;AAAA,IAEA,gBAAAC,MAAC,QAAG,WAAU,4FACZ;AAAA,sBAAAD,KAAC,QAAG,WAAU,4BAA4B,YAAE,QAAQ,GAAE;AAAA,MACtD,gBAAAA,KAAC,QAAG,WAAU,6BAA6B,eAAK,QAAO;AAAA,MACvD,gBAAAA,KAAC,QAAG,WAAU,4BAA4B,YAAE,SAAS,GAAE;AAAA,MACvD,gBAAAA,KAAC,QAAG,WAAU,4CAA4C,eAAK,WAAW,UAAI;AAAA,MAC9E,gBAAAA,KAAC,QAAG,WAAU,4BAA4B,YAAE,cAAc,GAAE;AAAA,MAC5D,gBAAAA,KAAC,QAAG,WAAU,oDAAoD,eAAK,aAAa,UAAI;AAAA,MACxF,gBAAAA,KAAC,QAAG,WAAU,4BAA4B,YAAE,SAAS,GAAE;AAAA,MACvD,gBAAAC,MAAC,QAAG,WAAU,6BACX;AAAA,mBAAW,KAAK,SAAS,KAAK;AAAA,QAC9B,KAAK,YAAY,IAAI,EAAE,aAAa,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,KAAK;AAAA,SACrE;AAAA,MACC,KAAK,aACJ,gBAAAA,MAAA,YACE;AAAA,wBAAAD,KAAC,QAAG,WAAU,4BAA4B,YAAE,SAAS,GAAE;AAAA,QACvD,gBAAAC,MAAC,QAAG,WAAU,6BACX;AAAA,qBAAW,KAAK,SAAS;AAAA,UACzB,KAAK,YAAY,IAAI,EAAE,aAAa,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,KAAK;AAAA,WACrE;AAAA,SACF;AAAA,MAED,UAAU,YACT,gBAAAA,MAAA,YACE;AAAA,wBAAAD,KAAC,QAAG,WAAU,2DAA2D,YAAE,MAAM,GAAE;AAAA,QACnF,gBAAAC,MAAC,QAAG,WAAU,mEAAkE,eAAY,mBACzF;AAAA,mBAAS,KAAK,IAAI,CAAC,QAClB,gBAAAD,KAAC,YAAsB,KAAU,WAAU,sBAA5B,IAAI,EAA2C,CAC/D;AAAA,UACD,gBAAAC;AAAA,YAAC;AAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS,MAAM,YAAY,IAAI;AAAA,cAC/B,eAAY;AAAA,cAEZ;AAAA,gCAAAD,KAACM,OAAA,EAAK;AAAA,gBACL,SAAS,KAAK,SAAS,EAAE,WAAW,IAAI,EAAE,UAAU;AAAA;AAAA;AAAA,UACvD;AAAA,WACF;AAAA,SACF;AAAA,OAEJ;AAAA,KACF,IAEA,gBAAAN,KAAC,SAAI,WAAU,uDACb,0BAAAC,MAAC,SAAI,WAAU,uKACb;AAAA,oBAAAD,KAAC,UAAK,WAAU,mHACd,0BAAAA,KAAC,kBAAe,WAAU,aAAY,GACxC;AAAA,IACA,gBAAAA,KAAC,OAAE,WAAU,2DAA2D,YAAE,mBAAmB,GAAE;AAAA,IAC/F,gBAAAA,KAAC,OAAE,WAAU,iFACV,YAAE,gCAAgC,GACrC;AAAA,KACF,GACF;AAGJ,QAAM,UAAU,WACd,gBAAAC,MAAA,YACE;AAAA,oBAAAA,MAAC,SAAI,WAAU,oBACZ;AAAA,gBACC,gBAAAA,MAAC,UAAO,WAAU,aAAY,SAAS,MAAM,OAAO,QAAQ,GAC1D;AAAA,wBAAAD,KAAC,eAAY;AAAA,QACZ,EAAE,MAAM;AAAA,SACX;AAAA,MAEF,gBAAAC,MAAC,UAAO,SAAQ,WAAU,WAAU,aAAY,UAAU,CAAC,CAAC,MAAM,SAAS,WACxE;AAAA,iBAAS,cAAc,gBAAAD,KAACK,UAAA,EAAQ,WAAU,mBAAkB,IAAK,gBAAAL,KAAC,QAAK;AAAA,QACvE,EAAE,WAAW;AAAA,SAChB;AAAA,OACF;AAAA,KACE,SAAS,QAAQ,OAAO,QAAQ,WAAW,eAC3C,gBAAAC,MAAC,SAAI,WAAU,oBACZ;AAAA,eAAS,QACR,gBAAAA,MAAA,YACE;AAAA,wBAAAA,MAAC,UAAO,SAAQ,WAAU,MAAK,cAAa,WAAU,aAAY,SAAS,UACzE;AAAA,0BAAAD,KAAC,QAAK;AAAA,UACL,EAAE,WAAW;AAAA,WAChB;AAAA,QACA,gBAAAC,MAAC,UAAO,SAAQ,WAAU,MAAK,cAAa,WAAU,aAAY,SAAS,MAAM,SAAS,SAAS,MAAM,SAAS,IAAI,GACpH;AAAA,0BAAAD,KAAC,YAAS;AAAA,UACT,EAAE,UAAU;AAAA,WACf;AAAA,SACF;AAAA,MAED,OAAO,QAAQ,WAAW,cACzB,gBAAAC;AAAA,QAAC;AAAA;AAAA,UACC,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,WAAW,GAAG,uBAAuB,CAAC,SAAS,QAAQ,cAAc,kBAAkB,gEAAgE;AAAA,UACvJ,UAAU,CAAC,CAAC;AAAA,UACZ,SAAS;AAAA,UACT,iBAAe,iBAAiB,OAAO;AAAA,UACvC,cAAY,EAAE,QAAQ;AAAA,UACtB,OAAO,kBAAkB,EAAE,QAAQ;AAAA,UAElC;AAAA,qBAAS,WAAW,gBAAAD,KAACK,UAAA,EAAQ,WAAU,mBAAkB,IAAK,gBAAAL,KAACO,SAAA,EAAO;AAAA,YACtE,CAAC,SAAS,QAAQ,EAAE,QAAQ;AAAA;AAAA;AAAA,MAC/B;AAAA,OAEJ;AAAA,KAEJ,IACE;AAEJ,QAAM,cACJ,QAAQ,QAAQ,QAAQ,WAAW,QAAQ,QACzC,gBAAAP,KAAC,gBAAa,MAAM,QAAQ,MAAM,SAAS,QAAQ,SAAS,OAAO,QAAQ,OAAO,QAAgB,gBAAgB,WAAW,IAE7H,gBAAAA,KAAC,SAAI,WAAU,8LACZ,YAAE,oCAAoC,GACzC;AAGJ,QAAM,gBACJ,gBAAAC,MAAC,SAAkB,WAAU,6CAA4C,eAAY,sBAClF;AAAA,eAAW,YACV,gBAAAD,KAAC,SAAI,WAAU,8DACb,0BAAAC,MAAC,UAAO,SAAQ,SAAQ,MAAK,cAAa,SAAS,MAAM,eAAe,KAAK,GAC3E;AAAA,sBAAAD,KAAC,aAAU;AAAA,MACV,EAAE,eAAe;AAAA,OACpB,GACF;AAAA,IAEF,gBAAAC,MAAC,SAAI,WAAU,+EACZ;AAAA;AAAA,MACA,WAAW,UAAU,QAAQ,gBAAAD,KAAC,SAAI,WAAU,4BAA4B,uBAAY;AAAA,OACvF;AAAA,IACC,WAAW,gBAAAA,KAAC,SAAI,WAAU,gFAAgF,mBAAQ;AAAA,OAb5G,SAcT;AAGF,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,GAAG,oGAAoG,SAAS;AAAA,MAC3H,eAAY;AAAA,MACZ,eAAa;AAAA,MAEb;AAAA,wBAAAA,MAAC,YAAO,WAAU,+FAChB;AAAA,0BAAAD,KAAC,UAAK,WAAU,oHACd,0BAAAA,KAAC,kBAAe,WAAU,aAAY,GACxC;AAAA,UACA,gBAAAC,MAAC,SAAI,WAAU,wBACb;AAAA,4BAAAD,KAAC,QAAG,WAAU,qEAAqE,mBAAS,EAAE,wBAAwB,GAAE;AAAA,YACxH,gBAAAA,KAAC,OAAE,WAAU,8DACV,yBAAe,EAAE,mDAAmD,GACvE;AAAA,aACF;AAAA,UACC,WACC,gBAAAA,KAAC,UAAO,SAAQ,SAAQ,MAAK,WAAU,SAAS,SAAS,cAAY,EAAE,OAAO,GAC5E,0BAAAA,KAAC,KAAE,GACL;AAAA,WAEJ;AAAA,QAEC,UAAU,YACT,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,cAAc;AAAA,YACd,UAAU,SAAS;AAAA,YACnB;AAAA,YACA;AAAA,YACA,cAAc;AAAA,YACd,gBAAgB;AAAA;AAAA,QAClB;AAAA,QAGD,WAAW,WACV,gBAAAA,KAAC,SAAI,WAAU,6DACZ,yBAAe,YAAY,gBAAgB,YAC9C,IAEA,gBAAAC;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,WAAW,SAAS,qDAAqD;AAAA,YAC3E;AAAA,YAEC;AAAA;AAAA,cACA;AAAA,cACA,WAAW,UACV,gBAAAD,KAAC,SAAI,WAAU,gCAA+B,eAAY,sBACvD,uBACH;AAAA;AAAA;AAAA,QAEJ;AAAA,QAEF,gBAAAA,KAAC,cAAW;AAAA;AAAA;AAAA,EACd;AAEJ;AAoBe,SAAR,gBAAiC,EAAE,SAAS,MAAM,UAAU,UAAU,GAAG,MAAM,GAAG;AACvF,SACE,gBAAAA,KAAC,cAAW,QAAgB,UAAoB,UAC9C,0BAAAA,KAAC,WAAS,GAAG,OAAO,GACtB;AAEJ;","names":["useEffect","useState","Loader2","Tags","Trash2","jsx","jsx","jsxs","useState","useEffect","template","Loader2","Tags","Trash2"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|