@standhigher/react-email-editor 0.4.3 → 0.5.2
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/README.md +5 -8
- package/README.zh-CN.md +5 -8
- package/dist/index.d.ts +44 -17
- package/dist/index.js +581 -263
- package/dist/styles.css +44 -0
- package/docs/advanced-examples.md +5 -1
- package/docs/advanced-examples.zh-CN.md +5 -1
- package/docs/api.md +23 -11
- package/docs/api.zh-CN.md +23 -11
- package/docs/changelog.md +60 -0
- package/docs/changelog.zh-CN.md +60 -0
- package/docs/custom-blocks.md +3 -3
- package/docs/custom-blocks.zh-CN.md +3 -3
- package/docs/release.md +4 -4
- package/docs/release.zh-CN.md +4 -4
- package/docs/template-engines.md +81 -0
- package/docs/template-engines.zh-CN.md +81 -0
- package/package.json +6 -11
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ function usePluginEditor() {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
// src/shell/EmailEditorShell.tsx
|
|
47
|
-
import { useState as
|
|
47
|
+
import { useState as useState20 } from "react";
|
|
48
48
|
|
|
49
49
|
// src/editor-dnd/EditorDnd.tsx
|
|
50
50
|
import { useLayoutEffect as useLayoutEffect5, useMemo, useRef as useRef7, useState as useState7 } from "react";
|
|
@@ -142,7 +142,7 @@ var DEFAULT_EMAIL_BODY = {
|
|
|
142
142
|
textColor: "#000000",
|
|
143
143
|
backgroundColor: "#ffffff",
|
|
144
144
|
contentWidth: CANVAS_DEVICE_WIDTH.desktop,
|
|
145
|
-
fontFamily: "
|
|
145
|
+
fontFamily: "",
|
|
146
146
|
fontWeight: FONT_WEIGHTS.normal,
|
|
147
147
|
linkColor: "#005bd3",
|
|
148
148
|
linkDecoration: LINK_DECORATIONS.underline,
|
|
@@ -379,7 +379,7 @@ var DEFAULT_TYPOGRAPHY = Object.freeze({
|
|
|
379
379
|
function typographyWithBody(body) {
|
|
380
380
|
return __spreadValues(__spreadValues({}, DEFAULT_TYPOGRAPHY), body ? {
|
|
381
381
|
color: body.textColor,
|
|
382
|
-
fontFamily: body.fontFamily,
|
|
382
|
+
fontFamily: cssFont(body.fontFamily, DEFAULT_TYPOGRAPHY.fontFamily),
|
|
383
383
|
fontWeight: body.fontWeight,
|
|
384
384
|
linkColor: body.linkColor,
|
|
385
385
|
linkDecoration: body.linkDecoration
|
|
@@ -2661,9 +2661,34 @@ function createEmailDocument(blocks = []) {
|
|
|
2661
2661
|
function createEmptyDocument() {
|
|
2662
2662
|
return { version: DOCUMENT_VERSION, subject: "", body: resolveEmailBody(), blocks: [] };
|
|
2663
2663
|
}
|
|
2664
|
+
function isRecord(value) {
|
|
2665
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2666
|
+
}
|
|
2664
2667
|
function ensureEmailDocument(document) {
|
|
2665
|
-
if (document
|
|
2666
|
-
throw new
|
|
2668
|
+
if (!isRecord(document)) {
|
|
2669
|
+
throw new TypeError("Invalid email document: expected an object.");
|
|
2670
|
+
}
|
|
2671
|
+
if (!Array.isArray(document.blocks)) {
|
|
2672
|
+
throw new TypeError("Invalid email document: blocks must be an array.");
|
|
2673
|
+
}
|
|
2674
|
+
if (document.body != null && !isRecord(document.body)) {
|
|
2675
|
+
throw new TypeError("Invalid email document: body must be an object when provided.");
|
|
2676
|
+
}
|
|
2677
|
+
if (document.subject !== void 0 && typeof document.subject !== "string") {
|
|
2678
|
+
throw new TypeError("Invalid email document: subject must be a string when provided.");
|
|
2679
|
+
}
|
|
2680
|
+
for (const [index, block] of document.blocks.entries()) {
|
|
2681
|
+
const path = `Invalid email document: blocks[${index}]`;
|
|
2682
|
+
if (!isRecord(block)) throw new TypeError(`${path} must be an object.`);
|
|
2683
|
+
if (typeof block.id !== "string" || !block.id.trim()) {
|
|
2684
|
+
throw new TypeError(`${path}.id must be a non-empty string.`);
|
|
2685
|
+
}
|
|
2686
|
+
if (typeof block.type !== "string" || !block.type.trim()) {
|
|
2687
|
+
throw new TypeError(`${path}.type must be a non-empty string.`);
|
|
2688
|
+
}
|
|
2689
|
+
if (!isRecord(block.controls)) {
|
|
2690
|
+
throw new TypeError(`${path}.controls must be an object.`);
|
|
2691
|
+
}
|
|
2667
2692
|
}
|
|
2668
2693
|
return __spreadProps(__spreadValues({}, document), { body: resolveEmailBody(document.body) });
|
|
2669
2694
|
}
|
|
@@ -2807,7 +2832,7 @@ var EmailControls = {
|
|
|
2807
2832
|
|
|
2808
2833
|
// src/plugins/define-block.ts
|
|
2809
2834
|
function defineBlock(input) {
|
|
2810
|
-
var _a, _b, _c;
|
|
2835
|
+
var _a, _b, _c, _d;
|
|
2811
2836
|
const options = snapshotValue(input);
|
|
2812
2837
|
if (!options.type.trim() || !options.title.trim()) throw new Error("Block type and title are required.");
|
|
2813
2838
|
const schema = (_a = options.controls) != null ? _a : {};
|
|
@@ -2825,7 +2850,7 @@ function defineBlock(input) {
|
|
|
2825
2850
|
type: options.type,
|
|
2826
2851
|
title: options.title,
|
|
2827
2852
|
description: options.description,
|
|
2828
|
-
icon: options.icon,
|
|
2853
|
+
icon: (_c = options.icon) != null ? _c : esc(Array.from(options.title.trim())[0]),
|
|
2829
2854
|
override: options.override,
|
|
2830
2855
|
schema,
|
|
2831
2856
|
controls: Object.entries(schema).map(([name, spec]) => __spreadProps(__spreadValues({}, spec), { name })),
|
|
@@ -2834,7 +2859,7 @@ function defineBlock(input) {
|
|
|
2834
2859
|
inspectorSections: options.inspectorSections,
|
|
2835
2860
|
defaultStyle: options.defaultStyle,
|
|
2836
2861
|
defaultData: options.defaultData,
|
|
2837
|
-
inlineControl: options.Canvas ? void 0 : (
|
|
2862
|
+
inlineControl: options.Canvas ? void 0 : (_d = inline[0]) == null ? void 0 : _d[0],
|
|
2838
2863
|
canvas: bindPluginComponent(options.Canvas),
|
|
2839
2864
|
inspector: bindPluginComponent(options.Inspector),
|
|
2840
2865
|
renderEmail: (ctx) => {
|
|
@@ -3471,7 +3496,7 @@ var BODY_CONTROL_SECTIONS = [
|
|
|
3471
3496
|
{ title: "General", controls: [
|
|
3472
3497
|
{ id: "textColor", group: "typography", label: "Text color", control: "color" },
|
|
3473
3498
|
{ id: "linkColor", group: "typography", label: "Link color", control: "color" },
|
|
3474
|
-
{ id: "fontFamily", group: "typography", label: "Font family", control: "select", options: copyFontFamilies() },
|
|
3499
|
+
{ id: "fontFamily", group: "typography", label: "Font family", control: "select", options: [{ label: "Default", value: "" }, ...copyFontFamilies()] },
|
|
3475
3500
|
__spreadProps(__spreadValues({}, FONT_WEIGHT), { id: "fontWeight", label: "Font weight" })
|
|
3476
3501
|
] },
|
|
3477
3502
|
{ title: "Content", controls: [
|
|
@@ -4589,12 +4614,11 @@ function Inspector({
|
|
|
4589
4614
|
] });
|
|
4590
4615
|
}
|
|
4591
4616
|
|
|
4592
|
-
// src/stage/
|
|
4593
|
-
import {
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
} from "@dnd-kit/sortable";
|
|
4617
|
+
// src/stage/Stage.tsx
|
|
4618
|
+
import { useMemo as useMemo4, useState as useState18 } from "react";
|
|
4619
|
+
|
|
4620
|
+
// src/stage/preview/usePreviewBuffer.ts
|
|
4621
|
+
import { useCallback, useEffect as useEffect6, useLayoutEffect as useLayoutEffect9, useReducer, useRef as useRef13, useState as useState16 } from "react";
|
|
4598
4622
|
|
|
4599
4623
|
// src/render/layout.ts
|
|
4600
4624
|
function surfaceStyle(body) {
|
|
@@ -4611,27 +4635,214 @@ function viewportWidth(device, body) {
|
|
|
4611
4635
|
return `${emailViewportWidth(device, body.contentWidth)}px`;
|
|
4612
4636
|
}
|
|
4613
4637
|
|
|
4638
|
+
// src/stage/preview/buffer.ts
|
|
4639
|
+
function createPreviewBuffer() {
|
|
4640
|
+
return { frames: [null, null], front: null, failure: null };
|
|
4641
|
+
}
|
|
4642
|
+
function previewBufferReducer(state, action) {
|
|
4643
|
+
var _a, _b, _c;
|
|
4644
|
+
switch (action.type) {
|
|
4645
|
+
case "generated": {
|
|
4646
|
+
const match = action.force ? -1 : state.frames.findIndex((frame) => frame && frame.status !== "failed" && frame.html === action.snapshot.html && frame.width === action.snapshot.width);
|
|
4647
|
+
const target = match < 0 ? state.front === 0 ? 1 : 0 : match;
|
|
4648
|
+
const frames = [...state.frames];
|
|
4649
|
+
frames[target] = match < 0 ? action.snapshot : __spreadProps(__spreadValues({}, frames[target]), { source: action.snapshot.source });
|
|
4650
|
+
const ready = ((_a = frames[target]) == null ? void 0 : _a.status) === "ready";
|
|
4651
|
+
return { frames, front: ready ? target : state.front, failure: ready ? null : state.failure };
|
|
4652
|
+
}
|
|
4653
|
+
case "ready": {
|
|
4654
|
+
const index = state.frames.findIndex((frame) => frame && frame.id === action.id && frame.status === "loading");
|
|
4655
|
+
if (index < 0) return state;
|
|
4656
|
+
const frames = [...state.frames];
|
|
4657
|
+
frames[index] = __spreadProps(__spreadValues({}, frames[index]), { status: "ready" });
|
|
4658
|
+
const current = ((_b = frames[index]) == null ? void 0 : _b.source) === action.source;
|
|
4659
|
+
return { frames, front: current ? index : state.front, failure: current ? null : state.failure };
|
|
4660
|
+
}
|
|
4661
|
+
case "failed": {
|
|
4662
|
+
if (action.id === void 0) {
|
|
4663
|
+
const frames2 = [state.frames[0], state.frames[1]];
|
|
4664
|
+
for (const index2 of [0, 1]) {
|
|
4665
|
+
const frame = frames2[index2];
|
|
4666
|
+
if ((frame == null ? void 0 : frame.status) === "loading") frames2[index2] = __spreadProps(__spreadValues({}, frame), { status: "failed" });
|
|
4667
|
+
}
|
|
4668
|
+
return __spreadProps(__spreadValues({}, state), { frames: frames2, failure: action.failure });
|
|
4669
|
+
}
|
|
4670
|
+
const index = state.frames.findIndex((frame) => frame && frame.id === action.id && frame.status === "loading");
|
|
4671
|
+
if (index < 0) return state;
|
|
4672
|
+
const frames = [...state.frames];
|
|
4673
|
+
frames[index] = __spreadProps(__spreadValues({}, frames[index]), { status: "failed" });
|
|
4674
|
+
return __spreadProps(__spreadValues({}, state), { frames, failure: ((_c = frames[index]) == null ? void 0 : _c.source) === action.failure.source ? action.failure : state.failure });
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
|
|
4679
|
+
// src/stage/preview/constants.ts
|
|
4680
|
+
var PREVIEW_DEBOUNCE_MS = 400;
|
|
4681
|
+
var PREVIEW_MAX_WAIT_MS = 2e3;
|
|
4682
|
+
var PREVIEW_LOAD_TIMEOUT_MS = 15e3;
|
|
4683
|
+
var PREVIEW_BUFFER_SLOTS = [0, 1];
|
|
4684
|
+
|
|
4685
|
+
// src/stage/preview/usePreviewBuffer.ts
|
|
4686
|
+
function usePreviewBuffer(source, requested) {
|
|
4687
|
+
var _a;
|
|
4688
|
+
const [state, dispatch] = useReducer(previewBufferReducer, void 0, createPreviewBuffer);
|
|
4689
|
+
const [request, setRequest] = useState16({ key: requested ? 1 : 0, force: false });
|
|
4690
|
+
const previousRequested = useRef13(requested);
|
|
4691
|
+
const sourceRef = useRef13(source);
|
|
4692
|
+
const pendingSince = useRef13(null);
|
|
4693
|
+
const attempted = useRef13(null);
|
|
4694
|
+
const revision = useRef13(0);
|
|
4695
|
+
useLayoutEffect9(() => {
|
|
4696
|
+
sourceRef.current = source;
|
|
4697
|
+
}, [source]);
|
|
4698
|
+
const flush = useCallback(() => setRequest((value) => __spreadProps(__spreadValues({}, value), { key: value.key + 1, force: false })), []);
|
|
4699
|
+
useEffect6(() => {
|
|
4700
|
+
var _a2, _b, _c;
|
|
4701
|
+
const enteringPreview = requested && !previousRequested.current;
|
|
4702
|
+
previousRequested.current = requested;
|
|
4703
|
+
const newRequest = request.key > 0 && ((_a2 = attempted.current) == null ? void 0 : _a2.requestKey) !== request.key;
|
|
4704
|
+
const force = newRequest && request.force;
|
|
4705
|
+
if (((_b = attempted.current) == null ? void 0 : _b.source) === source && !force) {
|
|
4706
|
+
attempted.current.requestKey = request.key;
|
|
4707
|
+
return;
|
|
4708
|
+
}
|
|
4709
|
+
const immediate = enteringPreview || newRequest;
|
|
4710
|
+
(_c = pendingSince.current) != null ? _c : pendingSince.current = Date.now();
|
|
4711
|
+
const remaining = Math.max(0, PREVIEW_MAX_WAIT_MS - (Date.now() - pendingSince.current));
|
|
4712
|
+
let idle;
|
|
4713
|
+
let cancelled = false;
|
|
4714
|
+
const generate = () => {
|
|
4715
|
+
if (cancelled || sourceRef.current !== source) return;
|
|
4716
|
+
attempted.current = { source, requestKey: request.key };
|
|
4717
|
+
pendingSince.current = null;
|
|
4718
|
+
try {
|
|
4719
|
+
const html = source.config.toHtml(source.document, { resolveHtmlTemplates: true, variables: source.variables });
|
|
4720
|
+
dispatch({ type: "generated", snapshot: { id: ++revision.current, source, html, width: viewportWidth(source.device, source.document.body), status: "loading" }, force });
|
|
4721
|
+
} catch (error) {
|
|
4722
|
+
dispatch({ type: "failed", failure: { source, message: error instanceof Error ? error.message : String(error) || "Email preview failed." } });
|
|
4723
|
+
}
|
|
4724
|
+
};
|
|
4725
|
+
const delay = immediate ? 0 : Math.min(PREVIEW_DEBOUNCE_MS, remaining);
|
|
4726
|
+
const timer = setTimeout(() => {
|
|
4727
|
+
if (immediate || typeof requestIdleCallback !== "function") generate();
|
|
4728
|
+
else idle = requestIdleCallback(generate, { timeout: Math.max(1, remaining - delay) });
|
|
4729
|
+
}, delay);
|
|
4730
|
+
return () => {
|
|
4731
|
+
cancelled = true;
|
|
4732
|
+
clearTimeout(timer);
|
|
4733
|
+
if (idle !== void 0) cancelIdleCallback(idle);
|
|
4734
|
+
};
|
|
4735
|
+
}, [source, requested, request]);
|
|
4736
|
+
const markReady = useCallback((id) => {
|
|
4737
|
+
dispatch({ type: "ready", id, source: sourceRef.current });
|
|
4738
|
+
}, []);
|
|
4739
|
+
const markFailed = useCallback((id, message) => {
|
|
4740
|
+
dispatch({ type: "failed", id, failure: { source: sourceRef.current, message } });
|
|
4741
|
+
}, []);
|
|
4742
|
+
const retry = useCallback(() => setRequest((value) => __spreadProps(__spreadValues({}, value), { key: value.key + 1, force: true })), []);
|
|
4743
|
+
const front = state.front === null ? null : state.frames[state.front];
|
|
4744
|
+
const failure = ((_a = state.failure) == null ? void 0 : _a.source) === source ? state.failure : null;
|
|
4745
|
+
const displayFailure = failure != null ? failure : state.front === null ? state.failure : null;
|
|
4746
|
+
return __spreadProps(__spreadValues({}, state), { failure, displayFailure, ready: (front == null ? void 0 : front.source) === source, markReady, markFailed, flush, retry });
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
// src/stage/canvas/Canvas.tsx
|
|
4750
|
+
import { useDroppable } from "@dnd-kit/core";
|
|
4751
|
+
import {
|
|
4752
|
+
SortableContext,
|
|
4753
|
+
verticalListSortingStrategy
|
|
4754
|
+
} from "@dnd-kit/sortable";
|
|
4755
|
+
|
|
4614
4756
|
// src/stage/canvas/CanvasBlock.tsx
|
|
4615
|
-
import { useId as useId13, useMemo as useMemo3, useState as
|
|
4757
|
+
import { useId as useId13, useMemo as useMemo3, useState as useState17 } from "react";
|
|
4616
4758
|
import { useSortable } from "@dnd-kit/sortable";
|
|
4617
4759
|
import { CSS } from "@dnd-kit/utilities";
|
|
4618
4760
|
|
|
4619
4761
|
// src/stage/canvas/components/PluginCanvasContent.tsx
|
|
4620
4762
|
import { useMemo as useMemo2 } from "react";
|
|
4621
4763
|
|
|
4622
|
-
// src/render/template-
|
|
4623
|
-
var TOKEN_PATTERN = /\{\{
|
|
4764
|
+
// src/render/default-template-engine.ts
|
|
4765
|
+
var TOKEN_PATTERN = /\{\{(-[ \t\r\n])?([\s\S]*?)([ \t\r\n]-)?\}\}/g;
|
|
4766
|
+
var UNSUPPORTED_IDENTIFIERS = /* @__PURE__ */ new Set([
|
|
4767
|
+
"and",
|
|
4768
|
+
"or",
|
|
4769
|
+
"not",
|
|
4770
|
+
"len",
|
|
4771
|
+
"index",
|
|
4772
|
+
"slice",
|
|
4773
|
+
"call",
|
|
4774
|
+
"print",
|
|
4775
|
+
"printf",
|
|
4776
|
+
"println",
|
|
4777
|
+
"html",
|
|
4778
|
+
"js",
|
|
4779
|
+
"urlquery",
|
|
4780
|
+
"eq",
|
|
4781
|
+
"ne",
|
|
4782
|
+
"lt",
|
|
4783
|
+
"le",
|
|
4784
|
+
"gt",
|
|
4785
|
+
"ge",
|
|
4786
|
+
"break",
|
|
4787
|
+
"continue",
|
|
4788
|
+
"else",
|
|
4789
|
+
"end"
|
|
4790
|
+
]);
|
|
4791
|
+
var PATH_PATTERN = /^(?:\.|\$|(?:\$\.|\.)?[\p{L}_][\p{L}\p{Nd}_]*(?:\.[\p{L}_][\p{L}\p{Nd}_]*)*)$/u;
|
|
4792
|
+
function supportsExpression(expression) {
|
|
4793
|
+
if (PATH_PATTERN.test(expression) && !UNSUPPORTED_IDENTIFIERS.has(expression)) return true;
|
|
4794
|
+
if (/^-?\d+(?:\.\d+)?$/.test(expression)) return true;
|
|
4795
|
+
if (/^"(?:[^"\\]|\\.)*"$/.test(expression)) {
|
|
4796
|
+
try {
|
|
4797
|
+
JSON.parse(expression);
|
|
4798
|
+
return true;
|
|
4799
|
+
} catch (e) {
|
|
4800
|
+
return false;
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
return false;
|
|
4804
|
+
}
|
|
4805
|
+
function supportsTokens(tokens) {
|
|
4806
|
+
var _a;
|
|
4807
|
+
const branches = [];
|
|
4808
|
+
for (const token of tokens) {
|
|
4809
|
+
if (token.text !== void 0) {
|
|
4810
|
+
if (token.text.includes("{{")) return false;
|
|
4811
|
+
continue;
|
|
4812
|
+
}
|
|
4813
|
+
const tag = (_a = token.tag) != null ? _a : "";
|
|
4814
|
+
const branch = /^(if|range|with)\s+([\s\S]+)$/.exec(tag);
|
|
4815
|
+
if (branch) {
|
|
4816
|
+
if (!supportsExpression(branch[2].trim())) return false;
|
|
4817
|
+
branches.push({ kind: branch[1], hasElse: false });
|
|
4818
|
+
} else if (tag === "end") {
|
|
4819
|
+
if (!branches.pop()) return false;
|
|
4820
|
+
} else if (tag === "else" || /^else\s+if\s+/.test(tag)) {
|
|
4821
|
+
const current = branches.at(-1);
|
|
4822
|
+
if (!current || current.hasElse) return false;
|
|
4823
|
+
if (tag === "else") current.hasElse = true;
|
|
4824
|
+
else if (current.kind !== "if" || !supportsExpression(tag.replace(/^else\s+if\s+/, ""))) return false;
|
|
4825
|
+
} else if (!supportsExpression(tag)) return false;
|
|
4826
|
+
}
|
|
4827
|
+
return branches.length === 0;
|
|
4828
|
+
}
|
|
4624
4829
|
function tokenize(template) {
|
|
4625
4830
|
var _a;
|
|
4626
4831
|
const tokens = [];
|
|
4627
4832
|
let lastIndex = 0;
|
|
4833
|
+
let trimNext = false;
|
|
4628
4834
|
for (const match of template.matchAll(TOKEN_PATTERN)) {
|
|
4629
4835
|
const index = (_a = match.index) != null ? _a : 0;
|
|
4630
|
-
|
|
4631
|
-
|
|
4836
|
+
let text = template.slice(lastIndex, index);
|
|
4837
|
+
if (trimNext) text = text.replace(/^[ \t\r\n]+/, "");
|
|
4838
|
+
if (match[1]) text = text.replace(/[ \t\r\n]+$/, "");
|
|
4839
|
+
if (text) tokens.push({ text });
|
|
4840
|
+
tokens.push({ tag: match[2].trim() });
|
|
4841
|
+
trimNext = Boolean(match[3]);
|
|
4632
4842
|
lastIndex = index + match[0].length;
|
|
4633
4843
|
}
|
|
4634
|
-
|
|
4844
|
+
const tail = template.slice(lastIndex);
|
|
4845
|
+
if (tail) tokens.push({ text: trimNext ? tail.replace(/^[ \t\r\n]+/, "") : tail });
|
|
4635
4846
|
return tokens;
|
|
4636
4847
|
}
|
|
4637
4848
|
function parseNodes(tokens, startIndex) {
|
|
@@ -4646,11 +4857,11 @@ function parseNodes(tokens, startIndex) {
|
|
|
4646
4857
|
continue;
|
|
4647
4858
|
}
|
|
4648
4859
|
const tag = (_a = token.tag) != null ? _a : "";
|
|
4649
|
-
if (tag === "else" ||
|
|
4860
|
+
if (tag === "else" || /^else\s+if\s+/.test(tag)) {
|
|
4650
4861
|
return { nodes, nextIndex: index, stopTag: "else" };
|
|
4651
4862
|
}
|
|
4652
4863
|
if (tag === "end") return { nodes, nextIndex: index, stopTag: "end" };
|
|
4653
|
-
const branchMatch = /^(if|range|with)\s+(
|
|
4864
|
+
const branchMatch = /^(if|range|with)\s+([\s\S]+)$/.exec(tag);
|
|
4654
4865
|
if (branchMatch) {
|
|
4655
4866
|
const branch = parseBranch(
|
|
4656
4867
|
tokens,
|
|
@@ -4674,7 +4885,7 @@ function parseBranch(tokens, startIndex, kind, expression) {
|
|
|
4674
4885
|
let nextIndex = body.nextIndex;
|
|
4675
4886
|
if (body.stopTag === "else") {
|
|
4676
4887
|
const elseTag = (_b = (_a = tokens[body.nextIndex]) == null ? void 0 : _a.tag) != null ? _b : "";
|
|
4677
|
-
const elseIfMatch = /^else\s+if\s+(
|
|
4888
|
+
const elseIfMatch = /^else\s+if\s+([\s\S]+)$/.exec(elseTag);
|
|
4678
4889
|
if (elseIfMatch) {
|
|
4679
4890
|
const nested = parseBranch(
|
|
4680
4891
|
tokens,
|
|
@@ -4698,42 +4909,44 @@ function parseBranch(tokens, startIndex, kind, expression) {
|
|
|
4698
4909
|
function readPath(value, path) {
|
|
4699
4910
|
if (path === "") return value;
|
|
4700
4911
|
return path.split(".").reduce((current, key) => {
|
|
4701
|
-
if (current && typeof current === "object") return current[key];
|
|
4912
|
+
if (current && typeof current === "object" && Object.hasOwn(current, key)) return current[key];
|
|
4702
4913
|
return void 0;
|
|
4703
4914
|
}, value);
|
|
4704
4915
|
}
|
|
4705
4916
|
function resolveExpression(expression, root, current) {
|
|
4706
4917
|
const trimmed = expression.trim();
|
|
4707
4918
|
if (trimmed === ".") return current;
|
|
4919
|
+
if (trimmed === "$") return root;
|
|
4708
4920
|
if (trimmed === "true") return true;
|
|
4709
4921
|
if (trimmed === "false") return false;
|
|
4710
4922
|
if (trimmed === "nil") return null;
|
|
4711
|
-
if (
|
|
4712
|
-
if (trimmed.startsWith('"')
|
|
4713
|
-
return trimmed.slice(1, -1);
|
|
4714
|
-
}
|
|
4923
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
4924
|
+
if (trimmed.startsWith('"')) return JSON.parse(trimmed);
|
|
4715
4925
|
if (trimmed.startsWith("$.")) return readPath(root, trimmed.slice(2));
|
|
4716
4926
|
if (trimmed.startsWith(".")) return readPath(current, trimmed.slice(1));
|
|
4717
4927
|
return readPath(current, trimmed);
|
|
4718
4928
|
}
|
|
4719
4929
|
function isTruthy(value) {
|
|
4720
4930
|
if (value == null || value === false || value === "" || value === 0) return false;
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4931
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
4932
|
+
if (value instanceof Map || value instanceof Set) return value.size > 0;
|
|
4933
|
+
if (typeof value === "object") {
|
|
4934
|
+
const prototype = Object.getPrototypeOf(value);
|
|
4935
|
+
if (prototype === Object.prototype || prototype === null) return Object.keys(value).length > 0;
|
|
4936
|
+
}
|
|
4937
|
+
return true;
|
|
4725
4938
|
}
|
|
4726
4939
|
function renderNodes(nodes, root, current) {
|
|
4727
4940
|
return nodes.map((node) => {
|
|
4728
4941
|
if (node.kind === "text") return node.value;
|
|
4729
4942
|
if (node.kind === "expression") {
|
|
4730
4943
|
const value2 = resolveExpression(node.expression, root, current);
|
|
4731
|
-
return value2 == null ? "" :
|
|
4944
|
+
return value2 == null ? "" : esc(String(value2)).replaceAll("'", "'");
|
|
4732
4945
|
}
|
|
4733
4946
|
const value = resolveExpression(node.expression, root, current);
|
|
4734
4947
|
if (node.kind === "range") {
|
|
4735
|
-
const items = Array.isArray(value) ? value : value && typeof value === "object" ? Object.
|
|
4736
|
-
return items.map((item) => renderNodes(node.children, root, item)).join("");
|
|
4948
|
+
const items = Array.isArray(value) ? value : value && typeof value === "object" ? Object.keys(value).sort().map((key) => value[key]) : [];
|
|
4949
|
+
return items.length > 0 ? items.map((item) => renderNodes(node.children, root, item)).join("") : renderNodes(node.alternate, root, current);
|
|
4737
4950
|
}
|
|
4738
4951
|
if (!isTruthy(value)) {
|
|
4739
4952
|
return renderNodes(node.alternate, root, current);
|
|
@@ -4742,9 +4955,40 @@ function renderNodes(nodes, root, current) {
|
|
|
4742
4955
|
return renderNodes(node.children, root, childContext);
|
|
4743
4956
|
}).join("");
|
|
4744
4957
|
}
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4958
|
+
var defaultTemplateEngine = Object.freeze({
|
|
4959
|
+
render(template, data) {
|
|
4960
|
+
const tokens = tokenize(template);
|
|
4961
|
+
if (!supportsTokens(tokens)) return template;
|
|
4962
|
+
return renderNodes(parseNodes(tokens, 0).nodes, data, data);
|
|
4963
|
+
}
|
|
4964
|
+
});
|
|
4965
|
+
|
|
4966
|
+
// src/render/template-preview.ts
|
|
4967
|
+
function normalizeEngineError(error) {
|
|
4968
|
+
return error instanceof Error ? error : new Error(String(error) || "Template engine failed.", { cause: error });
|
|
4969
|
+
}
|
|
4970
|
+
function renderTemplatePreview(template, data, options = {}) {
|
|
4971
|
+
var _a;
|
|
4972
|
+
const engine = (_a = options.templateEngine) != null ? _a : defaultTemplateEngine;
|
|
4973
|
+
if (data === void 0 && engine === defaultTemplateEngine) return template;
|
|
4974
|
+
try {
|
|
4975
|
+
const html = engine.render(template, structuredClone(data != null ? data : {}));
|
|
4976
|
+
if (typeof html !== "string") throw new TypeError("TemplateEngine.render must return a string synchronously.");
|
|
4977
|
+
return html;
|
|
4978
|
+
} catch (error) {
|
|
4979
|
+
throw normalizeEngineError(error);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
function validateTemplate(template, options = {}) {
|
|
4983
|
+
var _a, _b;
|
|
4984
|
+
const engine = (_a = options.templateEngine) != null ? _a : defaultTemplateEngine;
|
|
4985
|
+
try {
|
|
4986
|
+
const issues = (_b = engine.validate) == null ? void 0 : _b.call(engine, template);
|
|
4987
|
+
if (issues !== void 0 && !Array.isArray(issues)) throw new TypeError("TemplateEngine.validate must return an array synchronously.");
|
|
4988
|
+
return issues;
|
|
4989
|
+
} catch (error) {
|
|
4990
|
+
throw normalizeEngineError(error);
|
|
4991
|
+
}
|
|
4748
4992
|
}
|
|
4749
4993
|
|
|
4750
4994
|
// src/render/block-html.ts
|
|
@@ -4769,7 +5013,7 @@ function variableControlName(name) {
|
|
|
4769
5013
|
const token = /^\{\{-?\s*([\s\S]*?)\s*-?\}\}$/.exec(name.trim());
|
|
4770
5014
|
const inner = (token ? token[1] : name).trim();
|
|
4771
5015
|
const control = inner.startsWith(".") ? inner.slice(1) : inner;
|
|
4772
|
-
if (!control || !/^[
|
|
5016
|
+
if (!control || !/^[\p{L}_][\p{L}\p{Nd}_]*(?:\.[\p{L}_][\p{L}\p{Nd}_]*)*$/u.test(control)) return null;
|
|
4773
5017
|
return control;
|
|
4774
5018
|
}
|
|
4775
5019
|
function dottedListedToken(name) {
|
|
@@ -4835,12 +5079,12 @@ function renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email",
|
|
|
4835
5079
|
}
|
|
4836
5080
|
function resolveBlockContent(rendered, node, options) {
|
|
4837
5081
|
var _a, _b;
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
}
|
|
5082
|
+
const normalized = applyListedVariableDots(rendered, (_a = options.variables) != null ? _a : []);
|
|
5083
|
+
if (!options.resolveHtmlTemplate) return normalized;
|
|
4841
5084
|
return renderTemplatePreview(
|
|
4842
|
-
|
|
4843
|
-
(_b = options.templateData) != null ? _b : node.defaultData
|
|
5085
|
+
normalized,
|
|
5086
|
+
(_b = options.templateData) != null ? _b : node.defaultData,
|
|
5087
|
+
options
|
|
4844
5088
|
);
|
|
4845
5089
|
}
|
|
4846
5090
|
function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body = DEFAULT_EMAIL_BODY) {
|
|
@@ -4866,7 +5110,7 @@ function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options
|
|
|
4866
5110
|
}
|
|
4867
5111
|
|
|
4868
5112
|
// src/stage/canvas/components/HtmlCanvas.tsx
|
|
4869
|
-
import { useCallback, useEffect as
|
|
5113
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useRef as useRef14 } from "react";
|
|
4870
5114
|
|
|
4871
5115
|
// src/render/head-styles.ts
|
|
4872
5116
|
function extractHeadStyles(html) {
|
|
@@ -4887,10 +5131,10 @@ function frameHtml(html, baseCss) {
|
|
|
4887
5131
|
}
|
|
4888
5132
|
function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
4889
5133
|
const hasHtmlTag = /<\/?[a-z][\w:-]*(?:\s[^>]*)?>/i.test(html);
|
|
4890
|
-
const containerRef =
|
|
4891
|
-
const frameRef =
|
|
4892
|
-
const cleanupRef =
|
|
4893
|
-
const observeContent =
|
|
5134
|
+
const containerRef = useRef14(null);
|
|
5135
|
+
const frameRef = useRef14(null);
|
|
5136
|
+
const cleanupRef = useRef14();
|
|
5137
|
+
const observeContent = useCallback2(() => {
|
|
4894
5138
|
var _a;
|
|
4895
5139
|
(_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
|
|
4896
5140
|
const container = containerRef.current;
|
|
@@ -4931,7 +5175,7 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
|
4931
5175
|
window2.cancelAnimationFrame(animationFrame);
|
|
4932
5176
|
};
|
|
4933
5177
|
}, []);
|
|
4934
|
-
|
|
5178
|
+
useEffect7(() => () => {
|
|
4935
5179
|
var _a;
|
|
4936
5180
|
return (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
|
|
4937
5181
|
}, [hasHtmlTag, html]);
|
|
@@ -4982,15 +5226,17 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
|
4982
5226
|
|
|
4983
5227
|
// src/stage/canvas/components/PluginCanvasContent.tsx
|
|
4984
5228
|
import { jsx as jsx49, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
4985
|
-
function PluginCanvasContent({ block, body, definition, variables, selected, toolbarId, toolbarCollapsed, select, onCommitControl, onCommitControls, onCommitStyle }) {
|
|
5229
|
+
function PluginCanvasContent({ block, body, definition, variables, templateEngine, selected, toolbarId, toolbarCollapsed, select, onCommitControl, onCommitControls, onCommitStyle }) {
|
|
4986
5230
|
var _a, _b;
|
|
4987
5231
|
const variableSnapshot = useMemo2(() => structuredClone(variables), [variables]);
|
|
4988
5232
|
const context = useMemo2(() => definition ? buildRenderContext(block, "canvas", body, definition) : null, [block, body, definition]);
|
|
4989
5233
|
const renderedHtml = useMemo2(() => definition && context ? renderBlockContentFromContext(context, definition, {
|
|
4990
5234
|
resolveHtmlTemplate: true,
|
|
5235
|
+
templateEngine,
|
|
5236
|
+
variables,
|
|
4991
5237
|
templateData: mergeTemplateData(variables, block.defaultData),
|
|
4992
5238
|
body
|
|
4993
|
-
}) : "", [block.defaultData, body, context, definition, variables]);
|
|
5239
|
+
}) : "", [block.defaultData, body, context, definition, variables, templateEngine]);
|
|
4994
5240
|
if (!definition || !context) return /* @__PURE__ */ jsxs38("div", { className: "ree-plugin-error", role: "alert", children: [
|
|
4995
5241
|
"Missing block plugin: ",
|
|
4996
5242
|
block.type,
|
|
@@ -5101,12 +5347,14 @@ function canvasBlockStyle(block, body) {
|
|
|
5101
5347
|
// src/stage/canvas/CanvasBlock.tsx
|
|
5102
5348
|
import { jsx as jsx51, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
5103
5349
|
function CanvasBlock({
|
|
5350
|
+
active = true,
|
|
5104
5351
|
block: sourceBlock,
|
|
5105
5352
|
body,
|
|
5106
5353
|
definition,
|
|
5107
5354
|
selected,
|
|
5108
5355
|
dropEdge,
|
|
5109
5356
|
variables,
|
|
5357
|
+
templateEngine,
|
|
5110
5358
|
onSelect,
|
|
5111
5359
|
onCommitControl,
|
|
5112
5360
|
onCommitControls,
|
|
@@ -5116,10 +5364,11 @@ function CanvasBlock({
|
|
|
5116
5364
|
}) {
|
|
5117
5365
|
var _a, _b, _c;
|
|
5118
5366
|
const block = useMemo3(() => __spreadProps(__spreadValues({}, sourceBlock), { style: resolveBlockStyle(sourceBlock.style, definition) }), [sourceBlock, definition]);
|
|
5367
|
+
const renderKey = useMemo3(() => ({ definition, templateEngine, variables }), [definition, templateEngine, variables]);
|
|
5119
5368
|
const toolbarId = useId13();
|
|
5120
|
-
const [toolbarCollapsed, setToolbarCollapsed] =
|
|
5369
|
+
const [toolbarCollapsed, setToolbarCollapsed] = useState17(false);
|
|
5121
5370
|
const dragData = { kind: "canvas" };
|
|
5122
|
-
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: block.id, data: dragData });
|
|
5371
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: block.id, data: dragData, disabled: !active });
|
|
5123
5372
|
const select = (event) => {
|
|
5124
5373
|
event == null ? void 0 : event.stopPropagation();
|
|
5125
5374
|
onSelect();
|
|
@@ -5177,7 +5426,7 @@ function CanvasBlock({
|
|
|
5177
5426
|
{
|
|
5178
5427
|
className: `ree-block-content${(definition == null ? void 0 : definition.inlineControl) ? " is-inline" : ""}`,
|
|
5179
5428
|
style: canvasBlockStyle(block, body),
|
|
5180
|
-
children: /* @__PURE__ */ jsx51(BlockErrorBoundary, { identity: sourceBlock, resetKey:
|
|
5429
|
+
children: /* @__PURE__ */ jsx51(BlockErrorBoundary, { identity: sourceBlock, resetKey: renderKey, children: /* @__PURE__ */ jsx51(PluginCanvasContent, { block, body, definition, variables, templateEngine, selected, toolbarId, toolbarCollapsed, select: onSelect, onCommitControl, onCommitControls, onCommitStyle }) })
|
|
5181
5430
|
}
|
|
5182
5431
|
),
|
|
5183
5432
|
definition ? /* @__PURE__ */ jsx51("span", { className: "ree-block-badge", children: definition.title }) : null
|
|
@@ -5189,6 +5438,7 @@ function CanvasBlock({
|
|
|
5189
5438
|
// src/stage/canvas/Canvas.tsx
|
|
5190
5439
|
import { jsx as jsx52 } from "react/jsx-runtime";
|
|
5191
5440
|
function Canvas({
|
|
5441
|
+
active = true,
|
|
5192
5442
|
blocks,
|
|
5193
5443
|
body,
|
|
5194
5444
|
definitions,
|
|
@@ -5196,6 +5446,7 @@ function Canvas({
|
|
|
5196
5446
|
dropTargetId,
|
|
5197
5447
|
dropEdge,
|
|
5198
5448
|
variables,
|
|
5449
|
+
templateEngine,
|
|
5199
5450
|
device,
|
|
5200
5451
|
onSelect,
|
|
5201
5452
|
onCommitControl,
|
|
@@ -5206,6 +5457,7 @@ function Canvas({
|
|
|
5206
5457
|
}) {
|
|
5207
5458
|
const { setNodeRef, isOver } = useDroppable({
|
|
5208
5459
|
id: CANVAS_DROP_ID,
|
|
5460
|
+
disabled: !active,
|
|
5209
5461
|
data: { kind: "canvas" }
|
|
5210
5462
|
});
|
|
5211
5463
|
return /* @__PURE__ */ jsx52(
|
|
@@ -5220,9 +5472,9 @@ function Canvas({
|
|
|
5220
5472
|
if (!ownerWindow || !(target instanceof ownerWindow.Element)) return;
|
|
5221
5473
|
if (target.closest(".ree-block")) return;
|
|
5222
5474
|
onSelect(null);
|
|
5223
|
-
const
|
|
5224
|
-
if (
|
|
5225
|
-
|
|
5475
|
+
const active2 = ownerDocument.activeElement;
|
|
5476
|
+
if (active2 instanceof ownerWindow.HTMLElement && active2.closest(".ree-rte")) {
|
|
5477
|
+
active2.blur();
|
|
5226
5478
|
}
|
|
5227
5479
|
},
|
|
5228
5480
|
children: /* @__PURE__ */ jsx52(
|
|
@@ -5257,11 +5509,13 @@ function Canvas({
|
|
|
5257
5509
|
CanvasBlock,
|
|
5258
5510
|
{
|
|
5259
5511
|
block,
|
|
5512
|
+
active,
|
|
5260
5513
|
body,
|
|
5261
5514
|
definition: getBlockDefinition(block.type, definitions),
|
|
5262
5515
|
selected: block.id === selectedId,
|
|
5263
5516
|
dropEdge: dropTargetId === block.id ? dropEdge : null,
|
|
5264
5517
|
variables,
|
|
5518
|
+
templateEngine,
|
|
5265
5519
|
onSelect: () => onSelect(block.id),
|
|
5266
5520
|
onCommitControl,
|
|
5267
5521
|
onCommitControls,
|
|
@@ -5284,135 +5538,134 @@ function Canvas({
|
|
|
5284
5538
|
}
|
|
5285
5539
|
|
|
5286
5540
|
// src/stage/preview/Preview.tsx
|
|
5287
|
-
import {
|
|
5288
|
-
|
|
5289
|
-
// src/render/document-html.ts
|
|
5290
|
-
function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
|
|
5291
|
-
const source = ensureEmailDocument(document);
|
|
5292
|
-
const body = source.body;
|
|
5293
|
-
const baseCss = emailBaseCss(body);
|
|
5294
|
-
const rendered = source.blocks.map((node, index) => {
|
|
5295
|
-
var _a;
|
|
5296
|
-
const blockData = __spreadValues(__spreadValues({}, options.templateData), node.defaultData);
|
|
5297
|
-
const block = blockToHtml(node, definitions, "email", {
|
|
5298
|
-
resolveHtmlTemplate: options.resolveHtmlTemplates,
|
|
5299
|
-
body,
|
|
5300
|
-
variables: options.variables,
|
|
5301
|
-
templateData: mergeTemplateData(
|
|
5302
|
-
(_a = options.variables) != null ? _a : [],
|
|
5303
|
-
Object.keys(blockData).length > 0 ? blockData : void 0
|
|
5304
|
-
)
|
|
5305
|
-
});
|
|
5306
|
-
const gap = index < source.blocks.length - 1 ? spacerHtml(body.blockGap) : "";
|
|
5307
|
-
return block + gap;
|
|
5308
|
-
}).join("\n");
|
|
5309
|
-
const { html: content, styles } = extractHeadStyles(rendered);
|
|
5310
|
-
return [
|
|
5311
|
-
"<!DOCTYPE html>",
|
|
5312
|
-
'<html lang="en">',
|
|
5313
|
-
"<head>",
|
|
5314
|
-
'<meta charset="utf-8" />',
|
|
5315
|
-
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
5316
|
-
"<title>Email</title>",
|
|
5317
|
-
styles,
|
|
5318
|
-
"</head>",
|
|
5319
|
-
`<body style="margin:0;padding:0;${baseCss};background-color:#ffffff;word-spacing:normal;">`,
|
|
5320
|
-
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="${baseCss};background-color:#ffffff;table-layout:fixed;">`,
|
|
5321
|
-
"<tr>",
|
|
5322
|
-
`<td align="center" style="padding:0;${baseCss};">`,
|
|
5323
|
-
`<table role="presentation" align="center" width="${body.contentWidth}" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:${body.contentWidth}px;margin-left:auto;margin-right:auto;table-layout:fixed;">`,
|
|
5324
|
-
"<tr>",
|
|
5325
|
-
`<td style="box-sizing:border-box;${styleToCss(surfaceStyle(body))};">`,
|
|
5326
|
-
content,
|
|
5327
|
-
"</td>",
|
|
5328
|
-
"</tr>",
|
|
5329
|
-
"</table>",
|
|
5330
|
-
"</td>",
|
|
5331
|
-
"</tr>",
|
|
5332
|
-
"</table>",
|
|
5333
|
-
"</body>",
|
|
5334
|
-
"</html>"
|
|
5335
|
-
].join("\n");
|
|
5336
|
-
}
|
|
5541
|
+
import { useRef as useRef16 } from "react";
|
|
5337
5542
|
|
|
5338
|
-
// src/stage/preview/
|
|
5543
|
+
// src/stage/preview/PreviewFrame.tsx
|
|
5544
|
+
import { useLayoutEffect as useLayoutEffect10, useRef as useRef15 } from "react";
|
|
5339
5545
|
import { jsx as jsx53 } from "react/jsx-runtime";
|
|
5340
|
-
function
|
|
5341
|
-
const frameRef =
|
|
5342
|
-
const
|
|
5343
|
-
|
|
5344
|
-
() => documentToHtml(document, definitions, {
|
|
5345
|
-
resolveHtmlTemplates: true,
|
|
5346
|
-
variables
|
|
5347
|
-
}),
|
|
5348
|
-
[document, definitions, variables]
|
|
5349
|
-
);
|
|
5350
|
-
const observeContent = useCallback2(() => {
|
|
5351
|
-
var _a;
|
|
5352
|
-
(_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
|
|
5546
|
+
function PreviewFrame({ snapshot, onReady, onFailed }) {
|
|
5547
|
+
const frameRef = useRef15(null);
|
|
5548
|
+
const { id, html } = snapshot;
|
|
5549
|
+
useLayoutEffect10(() => {
|
|
5353
5550
|
const frame = frameRef.current;
|
|
5354
|
-
const
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
let
|
|
5358
|
-
const
|
|
5359
|
-
|
|
5551
|
+
const ownerWindow = frame == null ? void 0 : frame.ownerDocument.defaultView;
|
|
5552
|
+
if (!frame || !ownerWindow) return;
|
|
5553
|
+
let status = "loading";
|
|
5554
|
+
let stopObserving;
|
|
5555
|
+
const fail = (message) => {
|
|
5556
|
+
if (status !== "loading") return;
|
|
5557
|
+
status = "failed";
|
|
5558
|
+
clearTimeout(timer);
|
|
5559
|
+
stopObserving == null ? void 0 : stopObserving();
|
|
5560
|
+
stopObserving = void 0;
|
|
5561
|
+
onFailed(id, message);
|
|
5360
5562
|
};
|
|
5361
|
-
const
|
|
5362
|
-
|
|
5363
|
-
|
|
5563
|
+
const timer = setTimeout(() => fail("Email preview timed out. Please retry."), PREVIEW_LOAD_TIMEOUT_MS);
|
|
5564
|
+
const observeContent = () => {
|
|
5565
|
+
if (status !== "loading") return;
|
|
5566
|
+
stopObserving == null ? void 0 : stopObserving();
|
|
5567
|
+
stopObserving = void 0;
|
|
5568
|
+
try {
|
|
5569
|
+
const frameDocument = frame.contentDocument;
|
|
5570
|
+
const frameWindow = frameDocument == null ? void 0 : frameDocument.defaultView;
|
|
5571
|
+
if (!(frameDocument == null ? void 0 : frameDocument.body) || !frameWindow) throw new Error("Email preview could not be measured. Please retry.");
|
|
5572
|
+
let animationFrame = 0;
|
|
5573
|
+
let readyFrame = 0;
|
|
5574
|
+
const fit = () => {
|
|
5575
|
+
try {
|
|
5576
|
+
const height = frameDocument.body.getBoundingClientRect().height;
|
|
5577
|
+
if (!Number.isFinite(height) || height < 0) throw new Error("Email preview returned an invalid height. Please retry.");
|
|
5578
|
+
frame.style.height = `${Math.max(1, height)}px`;
|
|
5579
|
+
return true;
|
|
5580
|
+
} catch (error) {
|
|
5581
|
+
fail(error instanceof Error ? error.message : "Email preview could not be measured. Please retry.");
|
|
5582
|
+
return false;
|
|
5583
|
+
}
|
|
5584
|
+
};
|
|
5585
|
+
const scheduleFit = () => {
|
|
5586
|
+
ownerWindow.cancelAnimationFrame(animationFrame);
|
|
5587
|
+
animationFrame = ownerWindow.requestAnimationFrame(fit);
|
|
5588
|
+
};
|
|
5589
|
+
const observer = new frameWindow.ResizeObserver(scheduleFit);
|
|
5590
|
+
stopObserving = () => {
|
|
5591
|
+
observer.disconnect();
|
|
5592
|
+
frameDocument.removeEventListener("load", scheduleFit, true);
|
|
5593
|
+
ownerWindow.cancelAnimationFrame(animationFrame);
|
|
5594
|
+
ownerWindow.cancelAnimationFrame(readyFrame);
|
|
5595
|
+
};
|
|
5596
|
+
observer.observe(frameDocument.body);
|
|
5597
|
+
frameDocument.addEventListener("load", scheduleFit, true);
|
|
5598
|
+
if (!fit()) return;
|
|
5599
|
+
readyFrame = ownerWindow.requestAnimationFrame(() => {
|
|
5600
|
+
if (status !== "loading" || !fit()) return;
|
|
5601
|
+
status = "ready";
|
|
5602
|
+
clearTimeout(timer);
|
|
5603
|
+
onReady(id);
|
|
5604
|
+
});
|
|
5605
|
+
} catch (error) {
|
|
5606
|
+
fail(error instanceof Error ? error.message : "Email preview could not be loaded. Please retry.");
|
|
5607
|
+
}
|
|
5364
5608
|
};
|
|
5365
|
-
const
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5609
|
+
const loadError = () => fail("Email preview could not be loaded. Please retry.");
|
|
5610
|
+
frame.addEventListener("load", observeContent);
|
|
5611
|
+
frame.addEventListener("error", loadError);
|
|
5612
|
+
return () => {
|
|
5613
|
+
status = "disposed";
|
|
5614
|
+
clearTimeout(timer);
|
|
5615
|
+
stopObserving == null ? void 0 : stopObserving();
|
|
5616
|
+
frame.removeEventListener("load", observeContent);
|
|
5617
|
+
frame.removeEventListener("error", loadError);
|
|
5373
5618
|
};
|
|
5374
|
-
}, []);
|
|
5375
|
-
|
|
5376
|
-
var _a;
|
|
5377
|
-
return (_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
|
|
5378
|
-
}, [html]);
|
|
5379
|
-
return /* @__PURE__ */ jsx53("div", { className: "ree-preview-stage", children: /* @__PURE__ */ jsx53(
|
|
5380
|
-
"div",
|
|
5381
|
-
{
|
|
5382
|
-
className: "ree-email-viewport",
|
|
5383
|
-
style: {
|
|
5384
|
-
width: viewportWidth(device, document.body),
|
|
5385
|
-
marginLeft: "auto",
|
|
5386
|
-
marginRight: "auto"
|
|
5387
|
-
},
|
|
5388
|
-
children: /* @__PURE__ */ jsx53(
|
|
5389
|
-
"iframe",
|
|
5390
|
-
{
|
|
5391
|
-
ref: frameRef,
|
|
5392
|
-
className: "ree-preview-frame",
|
|
5393
|
-
title: "Email preview",
|
|
5394
|
-
srcDoc: html,
|
|
5395
|
-
onLoad: observeContent,
|
|
5396
|
-
style: {
|
|
5397
|
-
backgroundColor: "#ffffff"
|
|
5398
|
-
}
|
|
5399
|
-
}
|
|
5400
|
-
)
|
|
5401
|
-
}
|
|
5402
|
-
) });
|
|
5619
|
+
}, [id, onReady, onFailed]);
|
|
5620
|
+
return /* @__PURE__ */ jsx53("iframe", { ref: frameRef, className: "ree-preview-frame", title: "Email preview", sandbox: "allow-same-origin", srcDoc: html });
|
|
5403
5621
|
}
|
|
5404
5622
|
|
|
5405
|
-
// src/stage/
|
|
5623
|
+
// src/stage/preview/Preview.tsx
|
|
5406
5624
|
import { jsx as jsx54, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
5625
|
+
function Preview({ frames, front, failure, onReady, onFailed, onRetry }) {
|
|
5626
|
+
const scrollRef = useRef16({ top: 0, left: 0 });
|
|
5627
|
+
return /* @__PURE__ */ jsxs41("div", { className: "ree-preview-buffer", children: [
|
|
5628
|
+
PREVIEW_BUFFER_SLOTS.map((index) => {
|
|
5629
|
+
const snapshot = frames[index];
|
|
5630
|
+
const visible = front === index;
|
|
5631
|
+
return /* @__PURE__ */ jsx54(
|
|
5632
|
+
"div",
|
|
5633
|
+
__spreadProps(__spreadValues({
|
|
5634
|
+
className: `ree-preview-stage${visible ? "" : " is-buffered"}`,
|
|
5635
|
+
"aria-hidden": !visible
|
|
5636
|
+
}, !visible ? { inert: "" } : {}), {
|
|
5637
|
+
ref: (node) => {
|
|
5638
|
+
if (node && visible) {
|
|
5639
|
+
node.scrollTop = scrollRef.current.top;
|
|
5640
|
+
node.scrollLeft = scrollRef.current.left;
|
|
5641
|
+
}
|
|
5642
|
+
},
|
|
5643
|
+
onScroll: (event) => {
|
|
5644
|
+
if (visible) scrollRef.current = { top: event.currentTarget.scrollTop, left: event.currentTarget.scrollLeft };
|
|
5645
|
+
},
|
|
5646
|
+
children: /* @__PURE__ */ jsx54("div", { className: "ree-email-viewport", style: { width: snapshot == null ? void 0 : snapshot.width, marginLeft: "auto", marginRight: "auto" }, children: snapshot ? /* @__PURE__ */ jsx54(PreviewFrame, { snapshot, onReady, onFailed }, snapshot.id) : null })
|
|
5647
|
+
}),
|
|
5648
|
+
index
|
|
5649
|
+
);
|
|
5650
|
+
}),
|
|
5651
|
+
failure ? /* @__PURE__ */ jsxs41("div", { className: "ree-plugin-error ree-preview-error", role: "alert", children: [
|
|
5652
|
+
/* @__PURE__ */ jsx54("p", { children: failure.message }),
|
|
5653
|
+
/* @__PURE__ */ jsx54(Button, { onClick: onRetry, children: "Retry" })
|
|
5654
|
+
] }) : null
|
|
5655
|
+
] });
|
|
5656
|
+
}
|
|
5657
|
+
|
|
5658
|
+
// src/stage/StageHeader.tsx
|
|
5659
|
+
import { jsx as jsx55, jsxs as jsxs42 } from "react/jsx-runtime";
|
|
5407
5660
|
function StageHeader({
|
|
5408
5661
|
viewMode,
|
|
5409
5662
|
device,
|
|
5410
5663
|
onViewModeChange,
|
|
5411
5664
|
onDeviceChange
|
|
5412
5665
|
}) {
|
|
5413
|
-
return /* @__PURE__ */
|
|
5414
|
-
/* @__PURE__ */
|
|
5415
|
-
/* @__PURE__ */
|
|
5666
|
+
return /* @__PURE__ */ jsxs42("div", { className: "ree-col-head ree-stage-head", children: [
|
|
5667
|
+
/* @__PURE__ */ jsxs42("div", { className: "ree-segment", role: "group", "aria-label": "Editor mode", children: [
|
|
5668
|
+
/* @__PURE__ */ jsx55(
|
|
5416
5669
|
"button",
|
|
5417
5670
|
{
|
|
5418
5671
|
type: "button",
|
|
@@ -5421,14 +5674,14 @@ function StageHeader({
|
|
|
5421
5674
|
"aria-label": "Edit",
|
|
5422
5675
|
title: "Edit",
|
|
5423
5676
|
onClick: () => onViewModeChange("edit"),
|
|
5424
|
-
children: /* @__PURE__ */
|
|
5425
|
-
/* @__PURE__ */
|
|
5426
|
-
/* @__PURE__ */
|
|
5677
|
+
children: /* @__PURE__ */ jsxs42("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
5678
|
+
/* @__PURE__ */ jsx55("path", { d: "M12 20h9" }),
|
|
5679
|
+
/* @__PURE__ */ jsx55("path", { d: "M16.5 3.5a2.1 2.1 0 0 1 3 3L8 18l-4 1 1-4 11.5-11.5Z" })
|
|
5427
5680
|
] })
|
|
5428
5681
|
}
|
|
5429
5682
|
),
|
|
5430
|
-
/* @__PURE__ */
|
|
5431
|
-
/* @__PURE__ */
|
|
5683
|
+
/* @__PURE__ */ jsx55("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
|
|
5684
|
+
/* @__PURE__ */ jsx55(
|
|
5432
5685
|
"button",
|
|
5433
5686
|
{
|
|
5434
5687
|
type: "button",
|
|
@@ -5437,15 +5690,15 @@ function StageHeader({
|
|
|
5437
5690
|
"aria-label": "Preview",
|
|
5438
5691
|
title: "Preview",
|
|
5439
5692
|
onClick: () => onViewModeChange("preview"),
|
|
5440
|
-
children: /* @__PURE__ */
|
|
5441
|
-
/* @__PURE__ */
|
|
5442
|
-
/* @__PURE__ */
|
|
5693
|
+
children: /* @__PURE__ */ jsxs42("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
5694
|
+
/* @__PURE__ */ jsx55("path", { d: "M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" }),
|
|
5695
|
+
/* @__PURE__ */ jsx55("circle", { cx: "12", cy: "12", r: "2.5" })
|
|
5443
5696
|
] })
|
|
5444
5697
|
}
|
|
5445
5698
|
)
|
|
5446
5699
|
] }),
|
|
5447
|
-
/* @__PURE__ */
|
|
5448
|
-
/* @__PURE__ */
|
|
5700
|
+
/* @__PURE__ */ jsxs42("div", { className: "ree-segment", role: "group", "aria-label": "Canvas device", children: [
|
|
5701
|
+
/* @__PURE__ */ jsx55(
|
|
5449
5702
|
"button",
|
|
5450
5703
|
{
|
|
5451
5704
|
type: "button",
|
|
@@ -5454,15 +5707,15 @@ function StageHeader({
|
|
|
5454
5707
|
"aria-label": "Desktop",
|
|
5455
5708
|
title: "Desktop",
|
|
5456
5709
|
onClick: () => onDeviceChange("desktop"),
|
|
5457
|
-
children: /* @__PURE__ */
|
|
5458
|
-
/* @__PURE__ */
|
|
5459
|
-
/* @__PURE__ */
|
|
5460
|
-
/* @__PURE__ */
|
|
5710
|
+
children: /* @__PURE__ */ jsxs42("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
5711
|
+
/* @__PURE__ */ jsx55("rect", { x: "3", y: "4", width: "18", height: "12", rx: "1.5" }),
|
|
5712
|
+
/* @__PURE__ */ jsx55("path", { d: "M8 20h8" }),
|
|
5713
|
+
/* @__PURE__ */ jsx55("path", { d: "M12 16v4" })
|
|
5461
5714
|
] })
|
|
5462
5715
|
}
|
|
5463
5716
|
),
|
|
5464
|
-
/* @__PURE__ */
|
|
5465
|
-
/* @__PURE__ */
|
|
5717
|
+
/* @__PURE__ */ jsx55("span", { className: "ree-segment-divider", "aria-hidden": "true" }),
|
|
5718
|
+
/* @__PURE__ */ jsx55(
|
|
5466
5719
|
"button",
|
|
5467
5720
|
{
|
|
5468
5721
|
type: "button",
|
|
@@ -5471,9 +5724,9 @@ function StageHeader({
|
|
|
5471
5724
|
"aria-label": "Mobile",
|
|
5472
5725
|
title: "Mobile",
|
|
5473
5726
|
onClick: () => onDeviceChange("mobile"),
|
|
5474
|
-
children: /* @__PURE__ */
|
|
5475
|
-
/* @__PURE__ */
|
|
5476
|
-
/* @__PURE__ */
|
|
5727
|
+
children: /* @__PURE__ */ jsxs42("svg", { viewBox: "0 0 24 24", width: "20", height: "20", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
5728
|
+
/* @__PURE__ */ jsx55("rect", { x: "7", y: "2.5", width: "10", height: "19", rx: "2" }),
|
|
5729
|
+
/* @__PURE__ */ jsx55("path", { d: "M11 18.5h2" })
|
|
5477
5730
|
] })
|
|
5478
5731
|
}
|
|
5479
5732
|
)
|
|
@@ -5482,7 +5735,7 @@ function StageHeader({
|
|
|
5482
5735
|
}
|
|
5483
5736
|
|
|
5484
5737
|
// src/stage/Stage.tsx
|
|
5485
|
-
import { jsx as
|
|
5738
|
+
import { jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
5486
5739
|
function Stage({
|
|
5487
5740
|
session,
|
|
5488
5741
|
viewMode,
|
|
@@ -5491,62 +5744,70 @@ function Stage({
|
|
|
5491
5744
|
onDeviceChange,
|
|
5492
5745
|
onSelectBlock
|
|
5493
5746
|
}) {
|
|
5747
|
+
const source = useMemo4(() => ({ document: session.document, config: session.config, variables: session.variables, device }), [session.document, session.config, session.variables, device]);
|
|
5748
|
+
const requested = viewMode === "preview";
|
|
5749
|
+
const preview = usePreviewBuffer(source, requested);
|
|
5750
|
+
const [previewVisible, setPreviewVisible] = useState18(false);
|
|
5751
|
+
if (!requested && previewVisible) setPreviewVisible(false);
|
|
5752
|
+
if (requested && !previewVisible && (preview.ready || preview.failure)) setPreviewVisible(true);
|
|
5753
|
+
const editing = !requested;
|
|
5494
5754
|
const { targetId, dropEdge } = useEditorDragState();
|
|
5495
|
-
return /* @__PURE__ */
|
|
5496
|
-
/* @__PURE__ */
|
|
5755
|
+
return /* @__PURE__ */ jsxs43("div", { className: "ree-col ree-col-center", children: [
|
|
5756
|
+
/* @__PURE__ */ jsx56(
|
|
5497
5757
|
StageHeader,
|
|
5498
5758
|
{
|
|
5499
5759
|
viewMode,
|
|
5500
5760
|
device,
|
|
5501
|
-
onViewModeChange
|
|
5761
|
+
onViewModeChange: (mode) => {
|
|
5762
|
+
if (mode === "preview") preview.flush();
|
|
5763
|
+
onViewModeChange(mode);
|
|
5764
|
+
},
|
|
5502
5765
|
onDeviceChange
|
|
5503
5766
|
}
|
|
5504
5767
|
),
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
}
|
|
5531
|
-
) }) })
|
|
5768
|
+
/* @__PURE__ */ jsxs43("div", { className: "ree-stage-content", "aria-busy": requested && !preview.ready && !preview.failure, children: [
|
|
5769
|
+
/* @__PURE__ */ jsx56("div", __spreadProps(__spreadValues({ className: `ree-stage-view${previewVisible ? " is-buffered" : ""}`, "aria-hidden": previewVisible }, !editing ? { inert: "" } : {}), { children: /* @__PURE__ */ jsx56(
|
|
5770
|
+
Canvas,
|
|
5771
|
+
{
|
|
5772
|
+
blocks: session.document.blocks,
|
|
5773
|
+
body: session.document.body,
|
|
5774
|
+
definitions: session.definitions,
|
|
5775
|
+
active: editing,
|
|
5776
|
+
selectedId: editing ? session.selectedId : null,
|
|
5777
|
+
dropTargetId: targetId,
|
|
5778
|
+
dropEdge,
|
|
5779
|
+
variables: session.variables,
|
|
5780
|
+
templateEngine: session.config.templateEngine,
|
|
5781
|
+
device,
|
|
5782
|
+
onSelect: onSelectBlock,
|
|
5783
|
+
onCommitControl: session.updateControl,
|
|
5784
|
+
onCommitControls: session.updateControls,
|
|
5785
|
+
onCommitStyle: session.updateStyle,
|
|
5786
|
+
onDuplicate: session.duplicateBlock,
|
|
5787
|
+
onRemove: session.removeBlock
|
|
5788
|
+
}
|
|
5789
|
+
) })),
|
|
5790
|
+
/* @__PURE__ */ jsx56("div", __spreadProps(__spreadValues({ className: `ree-stage-view${previewVisible ? "" : " is-buffered"}`, "aria-hidden": !previewVisible }, !previewVisible ? { inert: "" } : {}), { children: /* @__PURE__ */ jsx56(Preview, { frames: preview.frames, front: preview.front, failure: preview.displayFailure, onReady: preview.markReady, onFailed: preview.markFailed, onRetry: preview.retry }) })),
|
|
5791
|
+
requested && !preview.ready && !preview.failure ? /* @__PURE__ */ jsx56("div", { className: "ree-preview-pending", role: "status", children: "Preparing preview\u2026" }) : null
|
|
5792
|
+
] })
|
|
5532
5793
|
] });
|
|
5533
5794
|
}
|
|
5534
5795
|
|
|
5535
5796
|
// src/shell/SubjectBar.tsx
|
|
5536
|
-
import { useId as useId14, useRef as
|
|
5797
|
+
import { useId as useId14, useRef as useRef17 } from "react";
|
|
5537
5798
|
|
|
5538
5799
|
// src/shell/constants.ts
|
|
5539
5800
|
var EMPTY_VARIABLES = [];
|
|
5540
5801
|
var PERSONALIZATION_HINT = "Personalization fields will appear blank if no data is available.";
|
|
5541
5802
|
|
|
5542
5803
|
// src/shell/SubjectBar.tsx
|
|
5543
|
-
import { Fragment as Fragment5, jsx as
|
|
5804
|
+
import { Fragment as Fragment5, jsx as jsx57, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
5544
5805
|
function SubjectBar({
|
|
5545
5806
|
subject,
|
|
5546
5807
|
variables,
|
|
5547
5808
|
onSubjectChange
|
|
5548
5809
|
}) {
|
|
5549
|
-
const inputRef =
|
|
5810
|
+
const inputRef = useRef17(null);
|
|
5550
5811
|
const subjectId = useId14();
|
|
5551
5812
|
const countId = useId14();
|
|
5552
5813
|
const insertVariable = (token) => {
|
|
@@ -5566,10 +5827,10 @@ function SubjectBar({
|
|
|
5566
5827
|
(_b2 = inputRef.current) == null ? void 0 : _b2.setSelectionRange(cursor, cursor);
|
|
5567
5828
|
});
|
|
5568
5829
|
};
|
|
5569
|
-
return /* @__PURE__ */
|
|
5570
|
-
/* @__PURE__ */
|
|
5571
|
-
/* @__PURE__ */
|
|
5572
|
-
/* @__PURE__ */
|
|
5830
|
+
return /* @__PURE__ */ jsxs44("div", { className: "ree-subject-bar", children: [
|
|
5831
|
+
/* @__PURE__ */ jsx57("label", { className: "ree-subject-label", htmlFor: subjectId, children: "Email subject" }),
|
|
5832
|
+
/* @__PURE__ */ jsxs44("div", { className: "ree-subject-field", children: [
|
|
5833
|
+
/* @__PURE__ */ jsx57(
|
|
5573
5834
|
TextInput,
|
|
5574
5835
|
{
|
|
5575
5836
|
id: subjectId,
|
|
@@ -5581,21 +5842,21 @@ function SubjectBar({
|
|
|
5581
5842
|
onChange: (event) => onSubjectChange(event.target.value)
|
|
5582
5843
|
}
|
|
5583
5844
|
),
|
|
5584
|
-
/* @__PURE__ */
|
|
5845
|
+
/* @__PURE__ */ jsxs44("span", { id: countId, className: "ree-subject-count", children: [
|
|
5585
5846
|
subject.length,
|
|
5586
5847
|
"/",
|
|
5587
5848
|
SUBJECT_MAX_LENGTH
|
|
5588
5849
|
] })
|
|
5589
5850
|
] }),
|
|
5590
|
-
variables.length > 0 ? /* @__PURE__ */
|
|
5591
|
-
/* @__PURE__ */
|
|
5592
|
-
/* @__PURE__ */
|
|
5851
|
+
variables.length > 0 ? /* @__PURE__ */ jsxs44(Fragment5, { children: [
|
|
5852
|
+
/* @__PURE__ */ jsx57(PersonalizationMenu, { variables, onInsert: insertVariable }),
|
|
5853
|
+
/* @__PURE__ */ jsx57("p", { className: "ree-subject-hint", children: PERSONALIZATION_HINT })
|
|
5593
5854
|
] }) : null
|
|
5594
5855
|
] });
|
|
5595
5856
|
}
|
|
5596
5857
|
|
|
5597
5858
|
// src/shell/useEditorSession.ts
|
|
5598
|
-
import { useCallback as useCallback3, useState as
|
|
5859
|
+
import { useCallback as useCallback3, useState as useState19, useSyncExternalStore } from "react";
|
|
5599
5860
|
|
|
5600
5861
|
// src/utils/id.ts
|
|
5601
5862
|
function generateId(prefix = "block") {
|
|
@@ -5694,9 +5955,59 @@ function createBlockNode(type, definitions) {
|
|
|
5694
5955
|
};
|
|
5695
5956
|
}
|
|
5696
5957
|
|
|
5958
|
+
// src/render/document-html.ts
|
|
5959
|
+
function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
|
|
5960
|
+
const source = ensureEmailDocument(document);
|
|
5961
|
+
const body = source.body;
|
|
5962
|
+
const baseCss = emailBaseCss(body);
|
|
5963
|
+
const rendered = source.blocks.map((node, index) => {
|
|
5964
|
+
var _a;
|
|
5965
|
+
const blockData = __spreadValues(__spreadValues({}, options.templateData), node.defaultData);
|
|
5966
|
+
const block = blockToHtml(node, definitions, "email", {
|
|
5967
|
+
resolveHtmlTemplate: options.resolveHtmlTemplates,
|
|
5968
|
+
templateEngine: options.templateEngine,
|
|
5969
|
+
body,
|
|
5970
|
+
variables: options.variables,
|
|
5971
|
+
templateData: mergeTemplateData(
|
|
5972
|
+
(_a = options.variables) != null ? _a : [],
|
|
5973
|
+
Object.keys(blockData).length > 0 ? blockData : void 0
|
|
5974
|
+
)
|
|
5975
|
+
});
|
|
5976
|
+
const gap = index < source.blocks.length - 1 ? spacerHtml(body.blockGap) : "";
|
|
5977
|
+
return block + gap;
|
|
5978
|
+
}).join("\n");
|
|
5979
|
+
const { html: content, styles } = extractHeadStyles(rendered);
|
|
5980
|
+
return [
|
|
5981
|
+
"<!DOCTYPE html>",
|
|
5982
|
+
'<html lang="en">',
|
|
5983
|
+
"<head>",
|
|
5984
|
+
'<meta charset="utf-8" />',
|
|
5985
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
5986
|
+
"<title>Email</title>",
|
|
5987
|
+
styles,
|
|
5988
|
+
"</head>",
|
|
5989
|
+
`<body style="margin:0;padding:0;${baseCss};background-color:#ffffff;word-spacing:normal;">`,
|
|
5990
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="${baseCss};background-color:#ffffff;table-layout:fixed;">`,
|
|
5991
|
+
"<tr>",
|
|
5992
|
+
`<td align="center" style="padding:0;${baseCss};">`,
|
|
5993
|
+
`<table role="presentation" align="center" width="${body.contentWidth}" cellpadding="0" cellspacing="0" border="0" style="width:100%;max-width:${body.contentWidth}px;margin-left:auto;margin-right:auto;table-layout:fixed;">`,
|
|
5994
|
+
"<tr>",
|
|
5995
|
+
`<td style="box-sizing:border-box;${styleToCss(surfaceStyle(body))};">`,
|
|
5996
|
+
content,
|
|
5997
|
+
"</td>",
|
|
5998
|
+
"</tr>",
|
|
5999
|
+
"</table>",
|
|
6000
|
+
"</td>",
|
|
6001
|
+
"</tr>",
|
|
6002
|
+
"</table>",
|
|
6003
|
+
"</body>",
|
|
6004
|
+
"</html>"
|
|
6005
|
+
].join("\n");
|
|
6006
|
+
}
|
|
6007
|
+
|
|
5697
6008
|
// src/plugins/config.ts
|
|
5698
6009
|
function buildEmailEditorConfig(options = {}) {
|
|
5699
|
-
var _a, _b, _c;
|
|
6010
|
+
var _a, _b, _c, _d;
|
|
5700
6011
|
const definitions = new Map(DEFAULT_BLOCKS.map((block) => [block.type, block]));
|
|
5701
6012
|
const customTypes = /* @__PURE__ */ new Set();
|
|
5702
6013
|
for (const plugin of (_a = options.plugins) != null ? _a : []) {
|
|
@@ -5711,16 +6022,21 @@ function buildEmailEditorConfig(options = {}) {
|
|
|
5711
6022
|
for (const type of visibleBuiltins) if (!builtinTypes.has(type)) throw new Error(`Unknown builtin block: ${type}`);
|
|
5712
6023
|
const all = Object.freeze([...definitions.values()].map((plugin) => snapshotValue(plugin)));
|
|
5713
6024
|
const registered = new Map(all.map((plugin) => [plugin.type, plugin]));
|
|
6025
|
+
const templateEngine = (_c = options.templateEngine) != null ? _c : defaultTemplateEngine;
|
|
5714
6026
|
return Object.freeze({
|
|
6027
|
+
templateEngine,
|
|
5715
6028
|
definitions: all,
|
|
5716
|
-
library: Object.freeze([...visibleBuiltins.map((type) => registered.get(type)), ...((
|
|
6029
|
+
library: Object.freeze([...visibleBuiltins.map((type) => registered.get(type)), ...((_d = options.plugins) != null ? _d : []).filter((plugin) => !builtinTypes.has(plugin.type)).map((plugin) => registered.get(plugin.type))]),
|
|
5717
6030
|
pickImage: options.pickImage,
|
|
5718
6031
|
createBlock: (type) => {
|
|
5719
6032
|
const block = createBlockNode(type, all);
|
|
5720
6033
|
if (!block) throw new Error(`Unknown block plugin: ${type}`);
|
|
5721
6034
|
return block;
|
|
5722
6035
|
},
|
|
5723
|
-
toHtml: (document, htmlOptions) =>
|
|
6036
|
+
toHtml: (document, htmlOptions) => {
|
|
6037
|
+
var _a2;
|
|
6038
|
+
return documentToHtml(document, all, __spreadProps(__spreadValues({}, htmlOptions), { templateEngine: (_a2 = htmlOptions == null ? void 0 : htmlOptions.templateEngine) != null ? _a2 : templateEngine }));
|
|
6039
|
+
},
|
|
5724
6040
|
validate: (document) => validateDocument(document, all)
|
|
5725
6041
|
});
|
|
5726
6042
|
}
|
|
@@ -5855,8 +6171,8 @@ function useEditorSession(editor) {
|
|
|
5855
6171
|
const store = getEditorStore(editor);
|
|
5856
6172
|
const { document, config, variables, resetKey } = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
5857
6173
|
const definitions = config.definitions;
|
|
5858
|
-
const [selectedId, setSelectedId] =
|
|
5859
|
-
const [previousReset, setPreviousReset] =
|
|
6174
|
+
const [selectedId, setSelectedId] = useState19(null);
|
|
6175
|
+
const [previousReset, setPreviousReset] = useState19({ editor, resetKey });
|
|
5860
6176
|
if (previousReset.editor !== editor || previousReset.resetKey !== resetKey) {
|
|
5861
6177
|
setPreviousReset({ editor, resetKey });
|
|
5862
6178
|
setSelectedId(null);
|
|
@@ -5957,21 +6273,21 @@ function useEditorSession(editor) {
|
|
|
5957
6273
|
}
|
|
5958
6274
|
|
|
5959
6275
|
// src/shell/EmailEditorShell.tsx
|
|
5960
|
-
import { jsx as
|
|
6276
|
+
import { jsx as jsx58, jsxs as jsxs45 } from "react/jsx-runtime";
|
|
5961
6277
|
function EmailEditorShell({ editor, height = "100%" }) {
|
|
5962
6278
|
var _a;
|
|
5963
6279
|
const session = useEditorSession(editor);
|
|
5964
|
-
const [activeRichText, setActiveRichText] =
|
|
5965
|
-
const [viewMode, setViewMode] =
|
|
5966
|
-
const [device, setDevice] =
|
|
5967
|
-
const [inspectorPanel, setInspectorPanel] =
|
|
5968
|
-
const [previousReset, setPreviousReset] =
|
|
6280
|
+
const [activeRichText, setActiveRichText] = useState20(null);
|
|
6281
|
+
const [viewMode, setViewMode] = useState20("edit");
|
|
6282
|
+
const [device, setDevice] = useState20("desktop");
|
|
6283
|
+
const [inspectorPanel, setInspectorPanel] = useState20("body");
|
|
6284
|
+
const [previousReset, setPreviousReset] = useState20({ editor, key: session.resetKey });
|
|
5969
6285
|
if (previousReset.editor !== editor || previousReset.key !== session.resetKey) {
|
|
5970
6286
|
setPreviousReset({ editor, key: session.resetKey });
|
|
5971
6287
|
setActiveRichText(null);
|
|
5972
6288
|
setInspectorPanel("body");
|
|
5973
6289
|
}
|
|
5974
|
-
const [previousSelectedId, setPreviousSelectedId] =
|
|
6290
|
+
const [previousSelectedId, setPreviousSelectedId] = useState20(session.selectedId);
|
|
5975
6291
|
if (previousSelectedId !== session.selectedId) {
|
|
5976
6292
|
setPreviousSelectedId(session.selectedId);
|
|
5977
6293
|
if (!session.selectedId) setInspectorPanel("body");
|
|
@@ -5995,8 +6311,8 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
5995
6311
|
selectBlock(null);
|
|
5996
6312
|
};
|
|
5997
6313
|
const showCanvas = () => setViewMode("edit");
|
|
5998
|
-
return /* @__PURE__ */
|
|
5999
|
-
/* @__PURE__ */
|
|
6314
|
+
return /* @__PURE__ */ jsx58(EditorConfigContext.Provider, { value: session.config, children: /* @__PURE__ */ jsx58(RichTextFocusContext.Provider, { value: { active: activeRichText, activate: setActiveRichText }, children: /* @__PURE__ */ jsxs45("div", { className: "ree-root", style: { height }, onKeyDown: handleKeyDown, children: [
|
|
6315
|
+
/* @__PURE__ */ jsx58(
|
|
6000
6316
|
SubjectBar,
|
|
6001
6317
|
{
|
|
6002
6318
|
subject: (_a = session.document.subject) != null ? _a : "",
|
|
@@ -6004,8 +6320,8 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
6004
6320
|
onSubjectChange: session.setSubject
|
|
6005
6321
|
}
|
|
6006
6322
|
),
|
|
6007
|
-
/* @__PURE__ */
|
|
6008
|
-
/* @__PURE__ */
|
|
6323
|
+
/* @__PURE__ */ jsxs45("div", { className: "ree-body", children: [
|
|
6324
|
+
/* @__PURE__ */ jsxs45(
|
|
6009
6325
|
EditorDnd,
|
|
6010
6326
|
{
|
|
6011
6327
|
blocks: session.document.blocks,
|
|
@@ -6017,7 +6333,7 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
6017
6333
|
onReorder: session.reorderBlocks,
|
|
6018
6334
|
onLibraryDragStart: showCanvas,
|
|
6019
6335
|
children: [
|
|
6020
|
-
/* @__PURE__ */
|
|
6336
|
+
/* @__PURE__ */ jsx58(
|
|
6021
6337
|
BlockLibrary,
|
|
6022
6338
|
{
|
|
6023
6339
|
definitions: session.config.library,
|
|
@@ -6027,7 +6343,7 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
6027
6343
|
}
|
|
6028
6344
|
}
|
|
6029
6345
|
),
|
|
6030
|
-
/* @__PURE__ */
|
|
6346
|
+
/* @__PURE__ */ jsx58(
|
|
6031
6347
|
Stage,
|
|
6032
6348
|
{
|
|
6033
6349
|
session,
|
|
@@ -6041,7 +6357,7 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
6041
6357
|
]
|
|
6042
6358
|
}
|
|
6043
6359
|
),
|
|
6044
|
-
/* @__PURE__ */
|
|
6360
|
+
/* @__PURE__ */ jsx58(
|
|
6045
6361
|
Inspector,
|
|
6046
6362
|
{
|
|
6047
6363
|
panel: inspectorPanel,
|
|
@@ -6061,23 +6377,23 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
6061
6377
|
}
|
|
6062
6378
|
|
|
6063
6379
|
// src/EmailEditor.tsx
|
|
6064
|
-
import { jsx as
|
|
6380
|
+
import { jsx as jsx59 } from "react/jsx-runtime";
|
|
6065
6381
|
function EmailEditor(props) {
|
|
6066
|
-
return /* @__PURE__ */
|
|
6382
|
+
return /* @__PURE__ */ jsx59(EmailEditorShell, __spreadValues({}, props));
|
|
6067
6383
|
}
|
|
6068
6384
|
|
|
6069
6385
|
// src/plugins/useEmailEditorConfig.ts
|
|
6070
6386
|
import { useMemo as useMemo5 } from "react";
|
|
6071
6387
|
function useEmailEditorConfig(options = {}) {
|
|
6072
|
-
const { plugins, builtins, pickImage } = options;
|
|
6073
|
-
return useMemo5(() => buildEmailEditorConfig({ plugins, builtins, pickImage }), [plugins, builtins, pickImage]);
|
|
6388
|
+
const { plugins, builtins, pickImage, templateEngine } = options;
|
|
6389
|
+
return useMemo5(() => buildEmailEditorConfig({ plugins, builtins, pickImage, templateEngine }), [plugins, builtins, pickImage, templateEngine]);
|
|
6074
6390
|
}
|
|
6075
6391
|
|
|
6076
6392
|
// src/shell/useEmailEditor.ts
|
|
6077
|
-
import { useEffect as useEffect8, useInsertionEffect, useLayoutEffect as
|
|
6078
|
-
var useOptionsEffect = typeof window === "undefined" ? useEffect8 :
|
|
6393
|
+
import { useEffect as useEffect8, useInsertionEffect, useLayoutEffect as useLayoutEffect11, useState as useState21 } from "react";
|
|
6394
|
+
var useOptionsEffect = typeof window === "undefined" ? useEffect8 : useLayoutEffect11;
|
|
6079
6395
|
function useEmailEditor(options = {}) {
|
|
6080
|
-
const [store] =
|
|
6396
|
+
const [store] = useState21(() => createEditorStore(options));
|
|
6081
6397
|
useInsertionEffect(() => {
|
|
6082
6398
|
store.updateOptions(options);
|
|
6083
6399
|
});
|
|
@@ -6093,12 +6409,12 @@ function useEmailEditorState(editor) {
|
|
|
6093
6409
|
}
|
|
6094
6410
|
|
|
6095
6411
|
// src/plugins/components/ControlEditor.tsx
|
|
6096
|
-
import { jsx as
|
|
6412
|
+
import { jsx as jsx60 } from "react/jsx-runtime";
|
|
6097
6413
|
function ControlEditor({ control }) {
|
|
6098
6414
|
const context = usePluginEditor();
|
|
6099
6415
|
const definition = context.controlDefinitions.find((item) => item.name === control);
|
|
6100
6416
|
if (!definition) throw new Error(`Unknown plugin control: ${control}`);
|
|
6101
|
-
return /* @__PURE__ */
|
|
6417
|
+
return /* @__PURE__ */ jsx60(ControlRenderer, { control: definition, value: context.controls[control], body: context.body, variables: context.variables, onChange: (value) => context.setControl(control, value) });
|
|
6102
6418
|
}
|
|
6103
6419
|
export {
|
|
6104
6420
|
BLOCK_TYPES,
|
|
@@ -6130,6 +6446,7 @@ export {
|
|
|
6130
6446
|
createDimensionControls,
|
|
6131
6447
|
createEmailDocument,
|
|
6132
6448
|
createTypographyControls,
|
|
6449
|
+
defaultTemplateEngine,
|
|
6133
6450
|
defineBlock,
|
|
6134
6451
|
documentToHtml,
|
|
6135
6452
|
generateId,
|
|
@@ -6142,5 +6459,6 @@ export {
|
|
|
6142
6459
|
useEmailEditor,
|
|
6143
6460
|
useEmailEditorConfig,
|
|
6144
6461
|
useEmailEditorState,
|
|
6462
|
+
validateTemplate,
|
|
6145
6463
|
variablesToTemplateData
|
|
6146
6464
|
};
|