@standhigher/react-email-editor 0.4.1 → 0.5.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/README.md +17 -15
- package/README.zh-CN.md +17 -15
- package/dist/index.d.ts +44 -17
- package/dist/index.js +453 -249
- package/dist/styles.css +41 -31
- package/docs/advanced-examples.md +5 -1
- package/docs/advanced-examples.zh-CN.md +5 -1
- package/docs/api.md +24 -10
- package/docs/api.zh-CN.md +24 -10
- package/docs/changelog.md +169 -0
- package/{CHANGELOG.md → docs/changelog.zh-CN.md} +71 -0
- package/docs/custom-blocks.md +3 -3
- package/docs/custom-blocks.zh-CN.md +3 -3
- package/docs/release.md +38 -0
- package/docs/release.zh-CN.md +38 -0
- package/docs/template-engines.md +73 -0
- package/docs/template-engines.zh-CN.md +73 -0
- package/package.json +22 -12
- package/docs/assets/architecture.png +0 -0
- package/docs/assets/architecture.zh-CN.png +0 -0
package/dist/index.js
CHANGED
|
@@ -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: [
|
|
@@ -3840,7 +3865,7 @@ function buildRenderContext(node, mode, body = DEFAULT_EMAIL_BODY, definition =
|
|
|
3840
3865
|
}
|
|
3841
3866
|
|
|
3842
3867
|
// src/inspector/InspectorControls.tsx
|
|
3843
|
-
import { useId as
|
|
3868
|
+
import { useId as useId11 } from "react";
|
|
3844
3869
|
|
|
3845
3870
|
// src/constants/style-groups.ts
|
|
3846
3871
|
var STYLE_GROUPS = [
|
|
@@ -3885,7 +3910,7 @@ function inheritedStyleValue(id, body) {
|
|
|
3885
3910
|
}
|
|
3886
3911
|
|
|
3887
3912
|
// src/inspector/components/BasicControl.tsx
|
|
3888
|
-
import { useRef as
|
|
3913
|
+
import { useRef as useRef10, useId as useId7 } from "react";
|
|
3889
3914
|
|
|
3890
3915
|
// src/inspector/HtmlCodeEditor.tsx
|
|
3891
3916
|
import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect6, useRef as useRef8 } from "react";
|
|
@@ -4079,41 +4104,147 @@ function HtmlCodeEditor({
|
|
|
4079
4104
|
);
|
|
4080
4105
|
}
|
|
4081
4106
|
|
|
4082
|
-
// src/
|
|
4083
|
-
import { useState as
|
|
4107
|
+
// src/components/PersonalizationMenu.tsx
|
|
4108
|
+
import { useId as useId6, useRef as useRef9, useState as useState13 } from "react";
|
|
4109
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
4110
|
+
|
|
4111
|
+
// src/components/usePersonalizationMenu.ts
|
|
4112
|
+
import { useLayoutEffect as useLayoutEffect7, useState as useState12 } from "react";
|
|
4113
|
+
function usePersonalizationMenu(open, rootRef, menuRef, focusOnOpen, setOpen) {
|
|
4114
|
+
const [position, setPosition] = useState12({ left: 0, top: 0, maxHeight: 240 });
|
|
4115
|
+
useLayoutEffect7(() => {
|
|
4116
|
+
var _a;
|
|
4117
|
+
const root = rootRef.current;
|
|
4118
|
+
const menu = menuRef.current;
|
|
4119
|
+
if (!open || !root || !menu) return;
|
|
4120
|
+
const ownerDocument = root.ownerDocument;
|
|
4121
|
+
const ownerWindow = ownerDocument.defaultView;
|
|
4122
|
+
if (!ownerWindow) return;
|
|
4123
|
+
const place = () => {
|
|
4124
|
+
var _a2, _b;
|
|
4125
|
+
const anchor = root.getBoundingClientRect();
|
|
4126
|
+
const inputBounds = (_b = (_a2 = root.closest(".ree-input-with-variable")) == null ? void 0 : _a2.getBoundingClientRect()) != null ? _b : anchor;
|
|
4127
|
+
const width = menu.offsetWidth;
|
|
4128
|
+
const height = Math.min(menu.scrollHeight + 2, 240);
|
|
4129
|
+
const below = Math.max(0, ownerWindow.innerHeight - inputBounds.bottom - 8);
|
|
4130
|
+
const above = Math.max(0, inputBounds.top - 8);
|
|
4131
|
+
const up = below < height && above > below;
|
|
4132
|
+
const maxHeight = Math.min(240, up ? above : below);
|
|
4133
|
+
const left = anchor.left + width > ownerWindow.innerWidth - 8 ? anchor.right - width : anchor.left;
|
|
4134
|
+
setPosition({
|
|
4135
|
+
left: Math.max(8, Math.min(left, ownerWindow.innerWidth - width - 8)),
|
|
4136
|
+
top: up ? inputBounds.top - Math.min(height, maxHeight) : inputBounds.bottom,
|
|
4137
|
+
maxHeight
|
|
4138
|
+
});
|
|
4139
|
+
};
|
|
4140
|
+
const onPointerDown = (event) => {
|
|
4141
|
+
if (!root.contains(event.target) && !menu.contains(event.target)) setOpen(false);
|
|
4142
|
+
};
|
|
4143
|
+
const onKeyDown = (event) => {
|
|
4144
|
+
var _a2, _b;
|
|
4145
|
+
const target = event.target;
|
|
4146
|
+
if (!root.contains(target) && !menu.contains(target)) return;
|
|
4147
|
+
if (event.key === "Tab" && menu.contains(target)) {
|
|
4148
|
+
const buttons = menu.querySelectorAll("button");
|
|
4149
|
+
if (target !== (event.shiftKey ? buttons[0] : buttons[buttons.length - 1])) return;
|
|
4150
|
+
if (event.shiftKey) event.preventDefault();
|
|
4151
|
+
setOpen(false);
|
|
4152
|
+
(_a2 = root.querySelector("button")) == null ? void 0 : _a2.focus({ preventScroll: true });
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
if (event.key !== "Escape") return;
|
|
4156
|
+
event.preventDefault();
|
|
4157
|
+
event.stopPropagation();
|
|
4158
|
+
setOpen(false);
|
|
4159
|
+
(_b = root.querySelector("button")) == null ? void 0 : _b.focus({ preventScroll: true });
|
|
4160
|
+
};
|
|
4161
|
+
menu.setAttribute("popover", "manual");
|
|
4162
|
+
menu.showPopover();
|
|
4163
|
+
place();
|
|
4164
|
+
if (focusOnOpen.current) (_a = menu.querySelector("button")) == null ? void 0 : _a.focus({ preventScroll: true });
|
|
4165
|
+
const observer = new ResizeObserver(place);
|
|
4166
|
+
observer.observe(root);
|
|
4167
|
+
observer.observe(menu);
|
|
4168
|
+
ownerWindow.addEventListener("resize", place);
|
|
4169
|
+
ownerDocument.addEventListener("scroll", place, true);
|
|
4170
|
+
ownerDocument.addEventListener("pointerdown", onPointerDown, true);
|
|
4171
|
+
ownerDocument.addEventListener("keydown", onKeyDown, true);
|
|
4172
|
+
return () => {
|
|
4173
|
+
menu.hidePopover();
|
|
4174
|
+
observer.disconnect();
|
|
4175
|
+
ownerWindow.removeEventListener("resize", place);
|
|
4176
|
+
ownerDocument.removeEventListener("scroll", place, true);
|
|
4177
|
+
ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
|
|
4178
|
+
ownerDocument.removeEventListener("keydown", onKeyDown, true);
|
|
4179
|
+
};
|
|
4180
|
+
}, [open, rootRef, menuRef, focusOnOpen, setOpen]);
|
|
4181
|
+
return position;
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
// src/components/PersonalizationMenu.tsx
|
|
4084
4185
|
import { jsx as jsx40, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
4085
|
-
function
|
|
4086
|
-
|
|
4186
|
+
function PersonalizationMenu({
|
|
4187
|
+
variables,
|
|
4188
|
+
onInsert,
|
|
4189
|
+
suffix = false
|
|
4190
|
+
}) {
|
|
4191
|
+
const [open, setOpen] = useState13(false);
|
|
4192
|
+
const [host, setHost] = useState13(null);
|
|
4193
|
+
const id = useId6();
|
|
4194
|
+
const rootRef = useRef9(null);
|
|
4195
|
+
const menuRef = useRef9(null);
|
|
4196
|
+
const focusOnOpen = useRef9(false);
|
|
4197
|
+
const position = usePersonalizationMenu(open && variables.length > 0, rootRef, menuRef, focusOnOpen, setOpen);
|
|
4087
4198
|
if (variables.length === 0) return null;
|
|
4088
|
-
return /* @__PURE__ */ jsxs29("div", { className: "ree-
|
|
4089
|
-
/* @__PURE__ */
|
|
4199
|
+
return /* @__PURE__ */ jsxs29("div", { className: suffix ? "ree-personalization ree-personalization-suffix" : "ree-personalization", ref: rootRef, children: [
|
|
4200
|
+
/* @__PURE__ */ jsxs29(
|
|
4090
4201
|
"button",
|
|
4091
4202
|
{
|
|
4092
4203
|
type: "button",
|
|
4093
|
-
className: "ree-
|
|
4094
|
-
|
|
4095
|
-
title: "
|
|
4096
|
-
"aria-label": "Insert variable",
|
|
4204
|
+
className: suffix ? "ree-personalization-suffix-btn" : "ree-personalization-btn",
|
|
4205
|
+
"aria-label": suffix ? "Insert variable" : void 0,
|
|
4206
|
+
title: suffix ? "Personalization" : void 0,
|
|
4097
4207
|
"aria-expanded": open,
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4208
|
+
"aria-controls": open ? id : void 0,
|
|
4209
|
+
onClick: (event) => {
|
|
4210
|
+
var _a;
|
|
4211
|
+
focusOnOpen.current = event.detail === 0;
|
|
4212
|
+
const root = rootRef.current;
|
|
4213
|
+
if (root) setHost((_a = root.closest(".ree-root")) != null ? _a : root.ownerDocument.body);
|
|
4214
|
+
setOpen((current) => !current);
|
|
4215
|
+
},
|
|
4216
|
+
onKeyDown: (event) => {
|
|
4217
|
+
var _a, _b;
|
|
4218
|
+
if (event.key === "Tab" && open && !event.shiftKey) {
|
|
4219
|
+
event.preventDefault();
|
|
4220
|
+
(_b = (_a = menuRef.current) == null ? void 0 : _a.querySelector("button")) == null ? void 0 : _b.focus();
|
|
4221
|
+
}
|
|
4109
4222
|
},
|
|
4110
4223
|
children: [
|
|
4111
|
-
|
|
4112
|
-
|
|
4224
|
+
suffix ? "{ }" : "Personalization",
|
|
4225
|
+
suffix ? null : /* @__PURE__ */ jsx40("svg", { viewBox: "0 0 20 20", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx40("path", { d: "M5.2 7.5 10 12.3l4.8-4.8 1.2 1.2L10 14.7 4 8.7l1.2-1.2Z" }) })
|
|
4113
4226
|
]
|
|
4114
|
-
}
|
|
4115
|
-
|
|
4116
|
-
|
|
4227
|
+
}
|
|
4228
|
+
),
|
|
4229
|
+
open && host ? createPortal2(
|
|
4230
|
+
/* @__PURE__ */ jsx40("div", { id, ref: menuRef, className: "ree-var-menu ree-personalization-menu", style: position, children: variables.map((variable) => /* @__PURE__ */ jsxs29(
|
|
4231
|
+
"button",
|
|
4232
|
+
{
|
|
4233
|
+
type: "button",
|
|
4234
|
+
className: "ree-var-item",
|
|
4235
|
+
onClick: () => {
|
|
4236
|
+
onInsert(variable.name);
|
|
4237
|
+
setOpen(false);
|
|
4238
|
+
},
|
|
4239
|
+
children: [
|
|
4240
|
+
/* @__PURE__ */ jsx40("span", { className: "ree-var-name", children: variable.name }),
|
|
4241
|
+
variable.description ? /* @__PURE__ */ jsx40("span", { className: "ree-var-label", children: variable.description }) : null
|
|
4242
|
+
]
|
|
4243
|
+
},
|
|
4244
|
+
variable.name
|
|
4245
|
+
)) }),
|
|
4246
|
+
host
|
|
4247
|
+
) : null
|
|
4117
4248
|
] });
|
|
4118
4249
|
}
|
|
4119
4250
|
|
|
@@ -4121,8 +4252,8 @@ function VariablesDropdown({ variables, onInsert }) {
|
|
|
4121
4252
|
import { jsx as jsx41, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
4122
4253
|
function BasicControl({ control, value, variables, onChange }) {
|
|
4123
4254
|
var _a, _b, _c, _d, _e;
|
|
4124
|
-
const ref =
|
|
4125
|
-
const id =
|
|
4255
|
+
const ref = useRef10(null);
|
|
4256
|
+
const id = useId7();
|
|
4126
4257
|
const shown = value == null || typeof value === "number" && !Number.isFinite(value) ? "" : String(value);
|
|
4127
4258
|
const insertVariable = (token) => {
|
|
4128
4259
|
var _a2, _b2;
|
|
@@ -4130,6 +4261,11 @@ function BasicControl({ control, value, variables, onChange }) {
|
|
|
4130
4261
|
const start = (_a2 = element == null ? void 0 : element.selectionStart) != null ? _a2 : shown.length;
|
|
4131
4262
|
const end = (_b2 = element == null ? void 0 : element.selectionEnd) != null ? _b2 : shown.length;
|
|
4132
4263
|
onChange(shown.slice(0, start) + token + shown.slice(end));
|
|
4264
|
+
requestAnimationFrame(() => {
|
|
4265
|
+
var _a3, _b3;
|
|
4266
|
+
(_a3 = ref.current) == null ? void 0 : _a3.focus();
|
|
4267
|
+
(_b3 = ref.current) == null ? void 0 : _b3.setSelectionRange(start + token.length, start + token.length);
|
|
4268
|
+
});
|
|
4133
4269
|
};
|
|
4134
4270
|
if (control.kind === "boolean") return /* @__PURE__ */ jsx41(CheckboxControl, { label: control.label, checked: Boolean(value), onChange });
|
|
4135
4271
|
if (control.kind === "color") return /* @__PURE__ */ jsx41(ColorControl, { label: control.label, value: shown, resetTo: String((_a = control.defaultValue) != null ? _a : ""), onChange });
|
|
@@ -4138,30 +4274,32 @@ function BasicControl({ control, value, variables, onChange }) {
|
|
|
4138
4274
|
/* @__PURE__ */ jsx41(HtmlCodeEditor, { value: shown, placeholder: control.placeholder, rows: control.rows, onChange })
|
|
4139
4275
|
] });
|
|
4140
4276
|
const textual = control.kind === "text" || control.kind === "textarea" || control.kind === "url";
|
|
4277
|
+
const hasVariables = textual && variables.length > 0;
|
|
4141
4278
|
const invalidUrl = control.kind === "url" && shown.trim() && !safeEmailUrl(shown);
|
|
4279
|
+
const input = control.kind === "select" ? /* @__PURE__ */ jsx41(SelectControl, { id, label: control.label, value: shown, options: (_b = control.options) != null ? _b : [], emptyLabel: (_c = control.placeholder) != null ? _c : "None", onChange }) : control.kind === "textarea" ? /* @__PURE__ */ jsx41("textarea", { id, ref: (element) => {
|
|
4280
|
+
ref.current = element;
|
|
4281
|
+
}, className: "ree-input ree-textarea", value: shown, placeholder: control.placeholder, rows: (_d = control.rows) != null ? _d : 4, onChange: (event) => onChange(event.target.value) }) : /* @__PURE__ */ jsx41(TextInput, { id, ref: (element) => {
|
|
4282
|
+
ref.current = element;
|
|
4283
|
+
}, type: control.kind === "number" ? "number" : "text", value: shown, min: control.min, max: control.max, step: control.step, placeholder: control.placeholder, "aria-invalid": Boolean(invalidUrl) || Boolean((_e = control.validateValue) == null ? void 0 : _e.call(control, value)), onChange: (event) => onChange(control.kind === "number" && event.target.value !== "" ? Number(event.target.value) : event.target.value) });
|
|
4142
4284
|
return /* @__PURE__ */ jsxs30("div", { className: "ree-field", children: [
|
|
4143
|
-
/* @__PURE__ */
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
ref.current = element;
|
|
4152
|
-
}, className: "ree-input ree-textarea", value: shown, placeholder: control.placeholder, rows: (_d = control.rows) != null ? _d : 4, onChange: (event) => onChange(event.target.value) }) : /* @__PURE__ */ jsx41(TextInput, { id, ref: (element) => {
|
|
4153
|
-
ref.current = element;
|
|
4154
|
-
}, type: control.kind === "number" ? "number" : "text", value: shown, min: control.min, max: control.max, step: control.step, placeholder: control.placeholder, "aria-invalid": Boolean(invalidUrl) || Boolean((_e = control.validateValue) == null ? void 0 : _e.call(control, value)), onChange: (event) => onChange(control.kind === "number" && event.target.value !== "" ? Number(event.target.value) : event.target.value) })
|
|
4285
|
+
/* @__PURE__ */ jsx41("div", { className: "ree-field-label", children: /* @__PURE__ */ jsxs30("label", { htmlFor: id, children: [
|
|
4286
|
+
control.label,
|
|
4287
|
+
control.unit ? ` (${control.unit})` : ""
|
|
4288
|
+
] }) }),
|
|
4289
|
+
hasVariables ? /* @__PURE__ */ jsxs30("div", { className: "ree-input-with-variable", children: [
|
|
4290
|
+
input,
|
|
4291
|
+
/* @__PURE__ */ jsx41(PersonalizationMenu, { suffix: true, variables, onInsert: insertVariable })
|
|
4292
|
+
] }) : input
|
|
4155
4293
|
] });
|
|
4156
4294
|
}
|
|
4157
4295
|
|
|
4158
4296
|
// src/inspector/components/ListControl.tsx
|
|
4159
|
-
import { useId as
|
|
4297
|
+
import { useId as useId8 } from "react";
|
|
4160
4298
|
|
|
4161
4299
|
// src/inspector/components/useListRows.ts
|
|
4162
|
-
import { useLayoutEffect as
|
|
4300
|
+
import { useLayoutEffect as useLayoutEffect8, useRef as useRef11, useState as useState14 } from "react";
|
|
4163
4301
|
function useListRows(value, onChange) {
|
|
4164
|
-
const [state, setState] =
|
|
4302
|
+
const [state, setState] = useState14({ source: void 0, rows: [], nextKey: 0 });
|
|
4165
4303
|
if (state.source !== value) {
|
|
4166
4304
|
const available = [...state.rows];
|
|
4167
4305
|
let nextKey = state.nextKey;
|
|
@@ -4173,8 +4311,8 @@ function useListRows(value, onChange) {
|
|
|
4173
4311
|
});
|
|
4174
4312
|
setState({ source: value, rows, nextKey });
|
|
4175
4313
|
}
|
|
4176
|
-
const latest =
|
|
4177
|
-
|
|
4314
|
+
const latest = useRef11(state);
|
|
4315
|
+
useLayoutEffect8(() => {
|
|
4178
4316
|
latest.current = state;
|
|
4179
4317
|
return () => {
|
|
4180
4318
|
latest.current = null;
|
|
@@ -4219,7 +4357,7 @@ import { jsx as jsx42, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
|
4219
4357
|
function ListControl(_a) {
|
|
4220
4358
|
var _b = _a, { control, value, onChange } = _b, props = __objRest(_b, ["control", "value", "onChange"]);
|
|
4221
4359
|
var _a2;
|
|
4222
|
-
const id =
|
|
4360
|
+
const id = useId8();
|
|
4223
4361
|
const schema = (_a2 = control.itemControls) != null ? _a2 : {};
|
|
4224
4362
|
const { rows, updateRow, moveRow, removeRow, addRow } = useListRows(value, onChange);
|
|
4225
4363
|
return /* @__PURE__ */ jsxs31("fieldset", { className: "ree-plugin-list", children: [
|
|
@@ -4239,15 +4377,15 @@ function ListControl(_a) {
|
|
|
4239
4377
|
}
|
|
4240
4378
|
|
|
4241
4379
|
// src/inspector/components/ImageControl.tsx
|
|
4242
|
-
import { useContext as useContext4, useEffect as useEffect5, useId as
|
|
4380
|
+
import { useContext as useContext4, useEffect as useEffect5, useId as useId9, useRef as useRef12, useState as useState15 } from "react";
|
|
4243
4381
|
import { jsx as jsx43, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
4244
4382
|
function ImageControl({ control, value, onChange }) {
|
|
4245
4383
|
var _a;
|
|
4246
4384
|
const config = useContext4(EditorConfigContext);
|
|
4247
|
-
const controller =
|
|
4248
|
-
const id =
|
|
4249
|
-
const [loading, setLoading] =
|
|
4250
|
-
const [error, setError] =
|
|
4385
|
+
const controller = useRef12(null);
|
|
4386
|
+
const id = useId9();
|
|
4387
|
+
const [loading, setLoading] = useState15(false);
|
|
4388
|
+
const [error, setError] = useState15("");
|
|
4251
4389
|
const shown = String(value != null ? value : "");
|
|
4252
4390
|
const url = safeEmailUrl(shown, true);
|
|
4253
4391
|
useEffect5(() => () => {
|
|
@@ -4284,12 +4422,12 @@ function ImageControl({ control, value, onChange }) {
|
|
|
4284
4422
|
}
|
|
4285
4423
|
|
|
4286
4424
|
// src/inspector/components/RichTextControl.tsx
|
|
4287
|
-
import { useContext as useContext5, useId as
|
|
4425
|
+
import { useContext as useContext5, useId as useId10 } from "react";
|
|
4288
4426
|
import { jsx as jsx44, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
4289
4427
|
function RichTextControl({ control, value, variables, body, onChange }) {
|
|
4290
4428
|
var _a, _b;
|
|
4291
4429
|
const parent = useContext5(PluginEditorContext);
|
|
4292
|
-
const id =
|
|
4430
|
+
const id = useId10();
|
|
4293
4431
|
const context = {
|
|
4294
4432
|
block: (_a = parent == null ? void 0 : parent.block) != null ? _a : { id, type: "control", controls: {} },
|
|
4295
4433
|
controls: { [control.name]: value },
|
|
@@ -4347,7 +4485,7 @@ function InspectorControls({
|
|
|
4347
4485
|
onCommitStyle
|
|
4348
4486
|
}) {
|
|
4349
4487
|
var _a, _b, _c, _d, _e;
|
|
4350
|
-
const toolbarId =
|
|
4488
|
+
const toolbarId = useId11();
|
|
4351
4489
|
const contentControls = ((_a = definition.controls) != null ? _a : []).filter((control) => control.name !== definition.inlineControl);
|
|
4352
4490
|
const CustomInspector = definition.inspector;
|
|
4353
4491
|
const context = buildRenderContext(block, "canvas", body, definition);
|
|
@@ -4476,6 +4614,9 @@ function Inspector({
|
|
|
4476
4614
|
] });
|
|
4477
4615
|
}
|
|
4478
4616
|
|
|
4617
|
+
// src/stage/Stage.tsx
|
|
4618
|
+
import { useMemo as useMemo5 } from "react";
|
|
4619
|
+
|
|
4479
4620
|
// src/stage/canvas/Canvas.tsx
|
|
4480
4621
|
import { useDroppable } from "@dnd-kit/core";
|
|
4481
4622
|
import {
|
|
@@ -4499,26 +4640,95 @@ function viewportWidth(device, body) {
|
|
|
4499
4640
|
}
|
|
4500
4641
|
|
|
4501
4642
|
// src/stage/canvas/CanvasBlock.tsx
|
|
4502
|
-
import { useId as
|
|
4643
|
+
import { useId as useId13, useMemo as useMemo3, useState as useState16 } from "react";
|
|
4503
4644
|
import { useSortable } from "@dnd-kit/sortable";
|
|
4504
4645
|
import { CSS } from "@dnd-kit/utilities";
|
|
4505
4646
|
|
|
4506
4647
|
// src/stage/canvas/components/PluginCanvasContent.tsx
|
|
4507
4648
|
import { useMemo as useMemo2 } from "react";
|
|
4508
4649
|
|
|
4509
|
-
// src/render/template-
|
|
4510
|
-
var TOKEN_PATTERN = /\{\{
|
|
4650
|
+
// src/render/default-template-engine.ts
|
|
4651
|
+
var TOKEN_PATTERN = /\{\{(-[ \t\r\n])?([\s\S]*?)([ \t\r\n]-)?\}\}/g;
|
|
4652
|
+
var UNSUPPORTED_IDENTIFIERS = /* @__PURE__ */ new Set([
|
|
4653
|
+
"and",
|
|
4654
|
+
"or",
|
|
4655
|
+
"not",
|
|
4656
|
+
"len",
|
|
4657
|
+
"index",
|
|
4658
|
+
"slice",
|
|
4659
|
+
"call",
|
|
4660
|
+
"print",
|
|
4661
|
+
"printf",
|
|
4662
|
+
"println",
|
|
4663
|
+
"html",
|
|
4664
|
+
"js",
|
|
4665
|
+
"urlquery",
|
|
4666
|
+
"eq",
|
|
4667
|
+
"ne",
|
|
4668
|
+
"lt",
|
|
4669
|
+
"le",
|
|
4670
|
+
"gt",
|
|
4671
|
+
"ge",
|
|
4672
|
+
"break",
|
|
4673
|
+
"continue",
|
|
4674
|
+
"else",
|
|
4675
|
+
"end"
|
|
4676
|
+
]);
|
|
4677
|
+
var PATH_PATTERN = /^(?:\.|\$|(?:\$\.|\.)?[\p{L}_][\p{L}\p{Nd}_]*(?:\.[\p{L}_][\p{L}\p{Nd}_]*)*)$/u;
|
|
4678
|
+
function supportsExpression(expression) {
|
|
4679
|
+
if (PATH_PATTERN.test(expression) && !UNSUPPORTED_IDENTIFIERS.has(expression)) return true;
|
|
4680
|
+
if (/^-?\d+(?:\.\d+)?$/.test(expression)) return true;
|
|
4681
|
+
if (/^"(?:[^"\\]|\\.)*"$/.test(expression)) {
|
|
4682
|
+
try {
|
|
4683
|
+
JSON.parse(expression);
|
|
4684
|
+
return true;
|
|
4685
|
+
} catch (e) {
|
|
4686
|
+
return false;
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
return false;
|
|
4690
|
+
}
|
|
4691
|
+
function supportsTokens(tokens) {
|
|
4692
|
+
var _a;
|
|
4693
|
+
const branches = [];
|
|
4694
|
+
for (const token of tokens) {
|
|
4695
|
+
if (token.text !== void 0) {
|
|
4696
|
+
if (token.text.includes("{{")) return false;
|
|
4697
|
+
continue;
|
|
4698
|
+
}
|
|
4699
|
+
const tag = (_a = token.tag) != null ? _a : "";
|
|
4700
|
+
const branch = /^(if|range|with)\s+([\s\S]+)$/.exec(tag);
|
|
4701
|
+
if (branch) {
|
|
4702
|
+
if (!supportsExpression(branch[2].trim())) return false;
|
|
4703
|
+
branches.push({ kind: branch[1], hasElse: false });
|
|
4704
|
+
} else if (tag === "end") {
|
|
4705
|
+
if (!branches.pop()) return false;
|
|
4706
|
+
} else if (tag === "else" || /^else\s+if\s+/.test(tag)) {
|
|
4707
|
+
const current = branches.at(-1);
|
|
4708
|
+
if (!current || current.hasElse) return false;
|
|
4709
|
+
if (tag === "else") current.hasElse = true;
|
|
4710
|
+
else if (current.kind !== "if" || !supportsExpression(tag.replace(/^else\s+if\s+/, ""))) return false;
|
|
4711
|
+
} else if (!supportsExpression(tag)) return false;
|
|
4712
|
+
}
|
|
4713
|
+
return branches.length === 0;
|
|
4714
|
+
}
|
|
4511
4715
|
function tokenize(template) {
|
|
4512
4716
|
var _a;
|
|
4513
4717
|
const tokens = [];
|
|
4514
4718
|
let lastIndex = 0;
|
|
4719
|
+
let trimNext = false;
|
|
4515
4720
|
for (const match of template.matchAll(TOKEN_PATTERN)) {
|
|
4516
4721
|
const index = (_a = match.index) != null ? _a : 0;
|
|
4517
|
-
|
|
4518
|
-
|
|
4722
|
+
let text = template.slice(lastIndex, index);
|
|
4723
|
+
if (trimNext) text = text.replace(/^[ \t\r\n]+/, "");
|
|
4724
|
+
if (match[1]) text = text.replace(/[ \t\r\n]+$/, "");
|
|
4725
|
+
if (text) tokens.push({ text });
|
|
4726
|
+
tokens.push({ tag: match[2].trim() });
|
|
4727
|
+
trimNext = Boolean(match[3]);
|
|
4519
4728
|
lastIndex = index + match[0].length;
|
|
4520
4729
|
}
|
|
4521
|
-
|
|
4730
|
+
const tail = template.slice(lastIndex);
|
|
4731
|
+
if (tail) tokens.push({ text: trimNext ? tail.replace(/^[ \t\r\n]+/, "") : tail });
|
|
4522
4732
|
return tokens;
|
|
4523
4733
|
}
|
|
4524
4734
|
function parseNodes(tokens, startIndex) {
|
|
@@ -4533,11 +4743,11 @@ function parseNodes(tokens, startIndex) {
|
|
|
4533
4743
|
continue;
|
|
4534
4744
|
}
|
|
4535
4745
|
const tag = (_a = token.tag) != null ? _a : "";
|
|
4536
|
-
if (tag === "else" ||
|
|
4746
|
+
if (tag === "else" || /^else\s+if\s+/.test(tag)) {
|
|
4537
4747
|
return { nodes, nextIndex: index, stopTag: "else" };
|
|
4538
4748
|
}
|
|
4539
4749
|
if (tag === "end") return { nodes, nextIndex: index, stopTag: "end" };
|
|
4540
|
-
const branchMatch = /^(if|range|with)\s+(
|
|
4750
|
+
const branchMatch = /^(if|range|with)\s+([\s\S]+)$/.exec(tag);
|
|
4541
4751
|
if (branchMatch) {
|
|
4542
4752
|
const branch = parseBranch(
|
|
4543
4753
|
tokens,
|
|
@@ -4561,7 +4771,7 @@ function parseBranch(tokens, startIndex, kind, expression) {
|
|
|
4561
4771
|
let nextIndex = body.nextIndex;
|
|
4562
4772
|
if (body.stopTag === "else") {
|
|
4563
4773
|
const elseTag = (_b = (_a = tokens[body.nextIndex]) == null ? void 0 : _a.tag) != null ? _b : "";
|
|
4564
|
-
const elseIfMatch = /^else\s+if\s+(
|
|
4774
|
+
const elseIfMatch = /^else\s+if\s+([\s\S]+)$/.exec(elseTag);
|
|
4565
4775
|
if (elseIfMatch) {
|
|
4566
4776
|
const nested = parseBranch(
|
|
4567
4777
|
tokens,
|
|
@@ -4585,42 +4795,44 @@ function parseBranch(tokens, startIndex, kind, expression) {
|
|
|
4585
4795
|
function readPath(value, path) {
|
|
4586
4796
|
if (path === "") return value;
|
|
4587
4797
|
return path.split(".").reduce((current, key) => {
|
|
4588
|
-
if (current && typeof current === "object") return current[key];
|
|
4798
|
+
if (current && typeof current === "object" && Object.hasOwn(current, key)) return current[key];
|
|
4589
4799
|
return void 0;
|
|
4590
4800
|
}, value);
|
|
4591
4801
|
}
|
|
4592
4802
|
function resolveExpression(expression, root, current) {
|
|
4593
4803
|
const trimmed = expression.trim();
|
|
4594
4804
|
if (trimmed === ".") return current;
|
|
4805
|
+
if (trimmed === "$") return root;
|
|
4595
4806
|
if (trimmed === "true") return true;
|
|
4596
4807
|
if (trimmed === "false") return false;
|
|
4597
4808
|
if (trimmed === "nil") return null;
|
|
4598
|
-
if (
|
|
4599
|
-
if (trimmed.startsWith('"')
|
|
4600
|
-
return trimmed.slice(1, -1);
|
|
4601
|
-
}
|
|
4809
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
4810
|
+
if (trimmed.startsWith('"')) return JSON.parse(trimmed);
|
|
4602
4811
|
if (trimmed.startsWith("$.")) return readPath(root, trimmed.slice(2));
|
|
4603
4812
|
if (trimmed.startsWith(".")) return readPath(current, trimmed.slice(1));
|
|
4604
4813
|
return readPath(current, trimmed);
|
|
4605
4814
|
}
|
|
4606
4815
|
function isTruthy(value) {
|
|
4607
4816
|
if (value == null || value === false || value === "" || value === 0) return false;
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4817
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
4818
|
+
if (value instanceof Map || value instanceof Set) return value.size > 0;
|
|
4819
|
+
if (typeof value === "object") {
|
|
4820
|
+
const prototype = Object.getPrototypeOf(value);
|
|
4821
|
+
if (prototype === Object.prototype || prototype === null) return Object.keys(value).length > 0;
|
|
4822
|
+
}
|
|
4823
|
+
return true;
|
|
4612
4824
|
}
|
|
4613
4825
|
function renderNodes(nodes, root, current) {
|
|
4614
4826
|
return nodes.map((node) => {
|
|
4615
4827
|
if (node.kind === "text") return node.value;
|
|
4616
4828
|
if (node.kind === "expression") {
|
|
4617
4829
|
const value2 = resolveExpression(node.expression, root, current);
|
|
4618
|
-
return value2 == null ? "" :
|
|
4830
|
+
return value2 == null ? "" : esc(String(value2)).replaceAll("'", "'");
|
|
4619
4831
|
}
|
|
4620
4832
|
const value = resolveExpression(node.expression, root, current);
|
|
4621
4833
|
if (node.kind === "range") {
|
|
4622
|
-
const items = Array.isArray(value) ? value : value && typeof value === "object" ? Object.
|
|
4623
|
-
return items.map((item) => renderNodes(node.children, root, item)).join("");
|
|
4834
|
+
const items = Array.isArray(value) ? value : value && typeof value === "object" ? Object.keys(value).sort().map((key) => value[key]) : [];
|
|
4835
|
+
return items.length > 0 ? items.map((item) => renderNodes(node.children, root, item)).join("") : renderNodes(node.alternate, root, current);
|
|
4624
4836
|
}
|
|
4625
4837
|
if (!isTruthy(value)) {
|
|
4626
4838
|
return renderNodes(node.alternate, root, current);
|
|
@@ -4629,9 +4841,40 @@ function renderNodes(nodes, root, current) {
|
|
|
4629
4841
|
return renderNodes(node.children, root, childContext);
|
|
4630
4842
|
}).join("");
|
|
4631
4843
|
}
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4844
|
+
var defaultTemplateEngine = Object.freeze({
|
|
4845
|
+
render(template, data) {
|
|
4846
|
+
const tokens = tokenize(template);
|
|
4847
|
+
if (!supportsTokens(tokens)) return template;
|
|
4848
|
+
return renderNodes(parseNodes(tokens, 0).nodes, data, data);
|
|
4849
|
+
}
|
|
4850
|
+
});
|
|
4851
|
+
|
|
4852
|
+
// src/render/template-preview.ts
|
|
4853
|
+
function normalizeEngineError(error) {
|
|
4854
|
+
return error instanceof Error ? error : new Error(String(error) || "Template engine failed.", { cause: error });
|
|
4855
|
+
}
|
|
4856
|
+
function renderTemplatePreview(template, data, options = {}) {
|
|
4857
|
+
var _a;
|
|
4858
|
+
const engine = (_a = options.templateEngine) != null ? _a : defaultTemplateEngine;
|
|
4859
|
+
if (data === void 0 && engine === defaultTemplateEngine) return template;
|
|
4860
|
+
try {
|
|
4861
|
+
const html = engine.render(template, structuredClone(data != null ? data : {}));
|
|
4862
|
+
if (typeof html !== "string") throw new TypeError("TemplateEngine.render must return a string synchronously.");
|
|
4863
|
+
return html;
|
|
4864
|
+
} catch (error) {
|
|
4865
|
+
throw normalizeEngineError(error);
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
function validateTemplate(template, options = {}) {
|
|
4869
|
+
var _a, _b;
|
|
4870
|
+
const engine = (_a = options.templateEngine) != null ? _a : defaultTemplateEngine;
|
|
4871
|
+
try {
|
|
4872
|
+
const issues = (_b = engine.validate) == null ? void 0 : _b.call(engine, template);
|
|
4873
|
+
if (issues !== void 0 && !Array.isArray(issues)) throw new TypeError("TemplateEngine.validate must return an array synchronously.");
|
|
4874
|
+
return issues;
|
|
4875
|
+
} catch (error) {
|
|
4876
|
+
throw normalizeEngineError(error);
|
|
4877
|
+
}
|
|
4635
4878
|
}
|
|
4636
4879
|
|
|
4637
4880
|
// src/render/block-html.ts
|
|
@@ -4656,7 +4899,7 @@ function variableControlName(name) {
|
|
|
4656
4899
|
const token = /^\{\{-?\s*([\s\S]*?)\s*-?\}\}$/.exec(name.trim());
|
|
4657
4900
|
const inner = (token ? token[1] : name).trim();
|
|
4658
4901
|
const control = inner.startsWith(".") ? inner.slice(1) : inner;
|
|
4659
|
-
if (!control || !/^[
|
|
4902
|
+
if (!control || !/^[\p{L}_][\p{L}\p{Nd}_]*(?:\.[\p{L}_][\p{L}\p{Nd}_]*)*$/u.test(control)) return null;
|
|
4660
4903
|
return control;
|
|
4661
4904
|
}
|
|
4662
4905
|
function dottedListedToken(name) {
|
|
@@ -4722,12 +4965,12 @@ function renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email",
|
|
|
4722
4965
|
}
|
|
4723
4966
|
function resolveBlockContent(rendered, node, options) {
|
|
4724
4967
|
var _a, _b;
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
}
|
|
4968
|
+
const normalized = applyListedVariableDots(rendered, (_a = options.variables) != null ? _a : []);
|
|
4969
|
+
if (!options.resolveHtmlTemplate) return normalized;
|
|
4728
4970
|
return renderTemplatePreview(
|
|
4729
|
-
|
|
4730
|
-
(_b = options.templateData) != null ? _b : node.defaultData
|
|
4971
|
+
normalized,
|
|
4972
|
+
(_b = options.templateData) != null ? _b : node.defaultData,
|
|
4973
|
+
options
|
|
4731
4974
|
);
|
|
4732
4975
|
}
|
|
4733
4976
|
function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body = DEFAULT_EMAIL_BODY) {
|
|
@@ -4753,7 +4996,7 @@ function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options
|
|
|
4753
4996
|
}
|
|
4754
4997
|
|
|
4755
4998
|
// src/stage/canvas/components/HtmlCanvas.tsx
|
|
4756
|
-
import { useCallback, useEffect as useEffect6, useRef as
|
|
4999
|
+
import { useCallback, useEffect as useEffect6, useRef as useRef13 } from "react";
|
|
4757
5000
|
|
|
4758
5001
|
// src/render/head-styles.ts
|
|
4759
5002
|
function extractHeadStyles(html) {
|
|
@@ -4774,9 +5017,9 @@ function frameHtml(html, baseCss) {
|
|
|
4774
5017
|
}
|
|
4775
5018
|
function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
4776
5019
|
const hasHtmlTag = /<\/?[a-z][\w:-]*(?:\s[^>]*)?>/i.test(html);
|
|
4777
|
-
const containerRef =
|
|
4778
|
-
const frameRef =
|
|
4779
|
-
const cleanupRef =
|
|
5020
|
+
const containerRef = useRef13(null);
|
|
5021
|
+
const frameRef = useRef13(null);
|
|
5022
|
+
const cleanupRef = useRef13();
|
|
4780
5023
|
const observeContent = useCallback(() => {
|
|
4781
5024
|
var _a;
|
|
4782
5025
|
(_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
|
|
@@ -4869,15 +5112,17 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
|
4869
5112
|
|
|
4870
5113
|
// src/stage/canvas/components/PluginCanvasContent.tsx
|
|
4871
5114
|
import { jsx as jsx49, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
4872
|
-
function PluginCanvasContent({ block, body, definition, variables, selected, toolbarId, toolbarCollapsed, select, onCommitControl, onCommitControls, onCommitStyle }) {
|
|
5115
|
+
function PluginCanvasContent({ block, body, definition, variables, templateEngine, selected, toolbarId, toolbarCollapsed, select, onCommitControl, onCommitControls, onCommitStyle }) {
|
|
4873
5116
|
var _a, _b;
|
|
4874
5117
|
const variableSnapshot = useMemo2(() => structuredClone(variables), [variables]);
|
|
4875
5118
|
const context = useMemo2(() => definition ? buildRenderContext(block, "canvas", body, definition) : null, [block, body, definition]);
|
|
4876
5119
|
const renderedHtml = useMemo2(() => definition && context ? renderBlockContentFromContext(context, definition, {
|
|
4877
5120
|
resolveHtmlTemplate: true,
|
|
5121
|
+
templateEngine,
|
|
5122
|
+
variables,
|
|
4878
5123
|
templateData: mergeTemplateData(variables, block.defaultData),
|
|
4879
5124
|
body
|
|
4880
|
-
}) : "", [block.defaultData, body, context, definition, variables]);
|
|
5125
|
+
}) : "", [block.defaultData, body, context, definition, variables, templateEngine]);
|
|
4881
5126
|
if (!definition || !context) return /* @__PURE__ */ jsxs38("div", { className: "ree-plugin-error", role: "alert", children: [
|
|
4882
5127
|
"Missing block plugin: ",
|
|
4883
5128
|
block.type,
|
|
@@ -4897,7 +5142,7 @@ function PluginCanvasContent({ block, body, definition, variables, selected, too
|
|
|
4897
5142
|
}
|
|
4898
5143
|
|
|
4899
5144
|
// src/stage/canvas/components/BlockActions.tsx
|
|
4900
|
-
import { useId as
|
|
5145
|
+
import { useId as useId12 } from "react";
|
|
4901
5146
|
import { jsx as jsx50, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
4902
5147
|
function BlockActions({
|
|
4903
5148
|
inlineEdit,
|
|
@@ -4907,7 +5152,7 @@ function BlockActions({
|
|
|
4907
5152
|
onDuplicate,
|
|
4908
5153
|
onRemove
|
|
4909
5154
|
}) {
|
|
4910
|
-
const tooltipId =
|
|
5155
|
+
const tooltipId = useId12();
|
|
4911
5156
|
const toggleLabel = toolbarCollapsed ? "Expand Toolbar" : "Collapse Toolbar";
|
|
4912
5157
|
return /* @__PURE__ */ jsxs39(
|
|
4913
5158
|
"div",
|
|
@@ -4994,6 +5239,7 @@ function CanvasBlock({
|
|
|
4994
5239
|
selected,
|
|
4995
5240
|
dropEdge,
|
|
4996
5241
|
variables,
|
|
5242
|
+
templateEngine,
|
|
4997
5243
|
onSelect,
|
|
4998
5244
|
onCommitControl,
|
|
4999
5245
|
onCommitControls,
|
|
@@ -5003,8 +5249,9 @@ function CanvasBlock({
|
|
|
5003
5249
|
}) {
|
|
5004
5250
|
var _a, _b, _c;
|
|
5005
5251
|
const block = useMemo3(() => __spreadProps(__spreadValues({}, sourceBlock), { style: resolveBlockStyle(sourceBlock.style, definition) }), [sourceBlock, definition]);
|
|
5006
|
-
const
|
|
5007
|
-
const
|
|
5252
|
+
const renderKey = useMemo3(() => ({ definition, templateEngine, variables }), [definition, templateEngine, variables]);
|
|
5253
|
+
const toolbarId = useId13();
|
|
5254
|
+
const [toolbarCollapsed, setToolbarCollapsed] = useState16(false);
|
|
5008
5255
|
const dragData = { kind: "canvas" };
|
|
5009
5256
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: block.id, data: dragData });
|
|
5010
5257
|
const select = (event) => {
|
|
@@ -5064,7 +5311,7 @@ function CanvasBlock({
|
|
|
5064
5311
|
{
|
|
5065
5312
|
className: `ree-block-content${(definition == null ? void 0 : definition.inlineControl) ? " is-inline" : ""}`,
|
|
5066
5313
|
style: canvasBlockStyle(block, body),
|
|
5067
|
-
children: /* @__PURE__ */ jsx51(BlockErrorBoundary, { identity: sourceBlock, resetKey:
|
|
5314
|
+
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 }) })
|
|
5068
5315
|
}
|
|
5069
5316
|
),
|
|
5070
5317
|
definition ? /* @__PURE__ */ jsx51("span", { className: "ree-block-badge", children: definition.title }) : null
|
|
@@ -5083,6 +5330,7 @@ function Canvas({
|
|
|
5083
5330
|
dropTargetId,
|
|
5084
5331
|
dropEdge,
|
|
5085
5332
|
variables,
|
|
5333
|
+
templateEngine,
|
|
5086
5334
|
device,
|
|
5087
5335
|
onSelect,
|
|
5088
5336
|
onCommitControl,
|
|
@@ -5149,6 +5397,7 @@ function Canvas({
|
|
|
5149
5397
|
selected: block.id === selectedId,
|
|
5150
5398
|
dropEdge: dropTargetId === block.id ? dropEdge : null,
|
|
5151
5399
|
variables,
|
|
5400
|
+
templateEngine,
|
|
5152
5401
|
onSelect: () => onSelect(block.id),
|
|
5153
5402
|
onCommitControl,
|
|
5154
5403
|
onCommitControls,
|
|
@@ -5171,68 +5420,17 @@ function Canvas({
|
|
|
5171
5420
|
}
|
|
5172
5421
|
|
|
5173
5422
|
// src/stage/preview/Preview.tsx
|
|
5174
|
-
import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo4, useRef as
|
|
5175
|
-
|
|
5176
|
-
// src/render/document-html.ts
|
|
5177
|
-
function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
|
|
5178
|
-
const source = ensureEmailDocument(document);
|
|
5179
|
-
const body = source.body;
|
|
5180
|
-
const baseCss = emailBaseCss(body);
|
|
5181
|
-
const rendered = source.blocks.map((node, index) => {
|
|
5182
|
-
var _a;
|
|
5183
|
-
const blockData = __spreadValues(__spreadValues({}, options.templateData), node.defaultData);
|
|
5184
|
-
const block = blockToHtml(node, definitions, "email", {
|
|
5185
|
-
resolveHtmlTemplate: options.resolveHtmlTemplates,
|
|
5186
|
-
body,
|
|
5187
|
-
variables: options.variables,
|
|
5188
|
-
templateData: mergeTemplateData(
|
|
5189
|
-
(_a = options.variables) != null ? _a : [],
|
|
5190
|
-
Object.keys(blockData).length > 0 ? blockData : void 0
|
|
5191
|
-
)
|
|
5192
|
-
});
|
|
5193
|
-
const gap = index < source.blocks.length - 1 ? spacerHtml(body.blockGap) : "";
|
|
5194
|
-
return block + gap;
|
|
5195
|
-
}).join("\n");
|
|
5196
|
-
const { html: content, styles } = extractHeadStyles(rendered);
|
|
5197
|
-
return [
|
|
5198
|
-
"<!DOCTYPE html>",
|
|
5199
|
-
'<html lang="en">',
|
|
5200
|
-
"<head>",
|
|
5201
|
-
'<meta charset="utf-8" />',
|
|
5202
|
-
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
5203
|
-
"<title>Email</title>",
|
|
5204
|
-
styles,
|
|
5205
|
-
"</head>",
|
|
5206
|
-
`<body style="margin:0;padding:0;${baseCss};background-color:#ffffff;word-spacing:normal;">`,
|
|
5207
|
-
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="${baseCss};background-color:#ffffff;table-layout:fixed;">`,
|
|
5208
|
-
"<tr>",
|
|
5209
|
-
`<td align="center" style="padding:0;${baseCss};">`,
|
|
5210
|
-
`<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;">`,
|
|
5211
|
-
"<tr>",
|
|
5212
|
-
`<td style="box-sizing:border-box;${styleToCss(surfaceStyle(body))};">`,
|
|
5213
|
-
content,
|
|
5214
|
-
"</td>",
|
|
5215
|
-
"</tr>",
|
|
5216
|
-
"</table>",
|
|
5217
|
-
"</td>",
|
|
5218
|
-
"</tr>",
|
|
5219
|
-
"</table>",
|
|
5220
|
-
"</body>",
|
|
5221
|
-
"</html>"
|
|
5222
|
-
].join("\n");
|
|
5223
|
-
}
|
|
5224
|
-
|
|
5225
|
-
// src/stage/preview/Preview.tsx
|
|
5423
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef14 } from "react";
|
|
5226
5424
|
import { jsx as jsx53 } from "react/jsx-runtime";
|
|
5227
|
-
function Preview({ document,
|
|
5228
|
-
const frameRef =
|
|
5229
|
-
const cleanupRef =
|
|
5425
|
+
function Preview({ document, config, variables, device }) {
|
|
5426
|
+
const frameRef = useRef14(null);
|
|
5427
|
+
const cleanupRef = useRef14();
|
|
5230
5428
|
const html = useMemo4(
|
|
5231
|
-
() =>
|
|
5429
|
+
() => config.toHtml(document, {
|
|
5232
5430
|
resolveHtmlTemplates: true,
|
|
5233
5431
|
variables
|
|
5234
5432
|
}),
|
|
5235
|
-
[document,
|
|
5433
|
+
[document, config, variables]
|
|
5236
5434
|
);
|
|
5237
5435
|
const observeContent = useCallback2(() => {
|
|
5238
5436
|
var _a;
|
|
@@ -5378,6 +5576,7 @@ function Stage({
|
|
|
5378
5576
|
onDeviceChange,
|
|
5379
5577
|
onSelectBlock
|
|
5380
5578
|
}) {
|
|
5579
|
+
const previewKey = useMemo5(() => ({ config: session.config, variables: session.variables }), [session.config, session.variables]);
|
|
5381
5580
|
const { targetId, dropEdge } = useEditorDragState();
|
|
5382
5581
|
return /* @__PURE__ */ jsxs42("div", { className: "ree-col ree-col-center", children: [
|
|
5383
5582
|
/* @__PURE__ */ jsx55(
|
|
@@ -5399,6 +5598,7 @@ function Stage({
|
|
|
5399
5598
|
dropTargetId: targetId,
|
|
5400
5599
|
dropEdge,
|
|
5401
5600
|
variables: session.variables,
|
|
5601
|
+
templateEngine: session.config.templateEngine,
|
|
5402
5602
|
device,
|
|
5403
5603
|
onSelect: onSelectBlock,
|
|
5404
5604
|
onCommitControl: session.updateControl,
|
|
@@ -5407,11 +5607,11 @@ function Stage({
|
|
|
5407
5607
|
onDuplicate: session.duplicateBlock,
|
|
5408
5608
|
onRemove: session.removeBlock
|
|
5409
5609
|
}
|
|
5410
|
-
) }) : /* @__PURE__ */ jsx55("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx55(BlockErrorBoundary, { identity: session.document, resetKey:
|
|
5610
|
+
) }) : /* @__PURE__ */ jsx55("div", { className: "ree-stage-view", children: /* @__PURE__ */ jsx55(BlockErrorBoundary, { identity: session.document, resetKey: previewKey, children: /* @__PURE__ */ jsx55(
|
|
5411
5611
|
Preview,
|
|
5412
5612
|
{
|
|
5413
5613
|
document: session.document,
|
|
5414
|
-
|
|
5614
|
+
config: session.config,
|
|
5415
5615
|
variables: session.variables,
|
|
5416
5616
|
device
|
|
5417
5617
|
}
|
|
@@ -5420,75 +5620,22 @@ function Stage({
|
|
|
5420
5620
|
}
|
|
5421
5621
|
|
|
5422
5622
|
// src/shell/SubjectBar.tsx
|
|
5423
|
-
import { useId as
|
|
5623
|
+
import { useId as useId14, useRef as useRef15 } from "react";
|
|
5424
5624
|
|
|
5425
5625
|
// src/shell/constants.ts
|
|
5426
5626
|
var EMPTY_VARIABLES = [];
|
|
5427
5627
|
var PERSONALIZATION_HINT = "Personalization fields will appear blank if no data is available.";
|
|
5428
5628
|
|
|
5429
|
-
// src/shell/PersonalizationMenu.tsx
|
|
5430
|
-
import { useEffect as useEffect8, useRef as useRef14, useState as useState16 } from "react";
|
|
5431
|
-
import { jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
5432
|
-
function PersonalizationMenu({
|
|
5433
|
-
variables,
|
|
5434
|
-
onInsert
|
|
5435
|
-
}) {
|
|
5436
|
-
const [open, setOpen] = useState16(false);
|
|
5437
|
-
const rootRef = useRef14(null);
|
|
5438
|
-
useEffect8(() => {
|
|
5439
|
-
const root = rootRef.current;
|
|
5440
|
-
if (!open || !root) return;
|
|
5441
|
-
const ownerDocument = root.ownerDocument;
|
|
5442
|
-
const onPointerDown = (event) => {
|
|
5443
|
-
if (!root.contains(event.target)) setOpen(false);
|
|
5444
|
-
};
|
|
5445
|
-
ownerDocument.addEventListener("pointerdown", onPointerDown, true);
|
|
5446
|
-
return () => ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
|
|
5447
|
-
}, [open]);
|
|
5448
|
-
if (variables.length === 0) return null;
|
|
5449
|
-
return /* @__PURE__ */ jsxs43("div", { className: "ree-personalization", ref: rootRef, children: [
|
|
5450
|
-
/* @__PURE__ */ jsxs43(
|
|
5451
|
-
"button",
|
|
5452
|
-
{
|
|
5453
|
-
type: "button",
|
|
5454
|
-
className: "ree-personalization-btn",
|
|
5455
|
-
"aria-expanded": open,
|
|
5456
|
-
onClick: () => setOpen((current) => !current),
|
|
5457
|
-
children: [
|
|
5458
|
-
"Personalization",
|
|
5459
|
-
/* @__PURE__ */ jsx56("svg", { viewBox: "0 0 20 20", width: "16", height: "16", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx56("path", { d: "M5.2 7.5 10 12.3l4.8-4.8 1.2 1.2L10 14.7 4 8.7l1.2-1.2Z" }) })
|
|
5460
|
-
]
|
|
5461
|
-
}
|
|
5462
|
-
),
|
|
5463
|
-
open ? /* @__PURE__ */ jsx56("div", { className: "ree-var-menu ree-personalization-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs43(
|
|
5464
|
-
"button",
|
|
5465
|
-
{
|
|
5466
|
-
type: "button",
|
|
5467
|
-
className: "ree-var-item",
|
|
5468
|
-
onClick: () => {
|
|
5469
|
-
onInsert(variable.name);
|
|
5470
|
-
setOpen(false);
|
|
5471
|
-
},
|
|
5472
|
-
children: [
|
|
5473
|
-
/* @__PURE__ */ jsx56("span", { className: "ree-var-name", children: variable.name }),
|
|
5474
|
-
variable.description ? /* @__PURE__ */ jsx56("span", { className: "ree-var-label", children: variable.description }) : null
|
|
5475
|
-
]
|
|
5476
|
-
},
|
|
5477
|
-
variable.name
|
|
5478
|
-
)) }) : null
|
|
5479
|
-
] });
|
|
5480
|
-
}
|
|
5481
|
-
|
|
5482
5629
|
// src/shell/SubjectBar.tsx
|
|
5483
|
-
import { Fragment as Fragment5, jsx as
|
|
5630
|
+
import { Fragment as Fragment5, jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
5484
5631
|
function SubjectBar({
|
|
5485
5632
|
subject,
|
|
5486
5633
|
variables,
|
|
5487
5634
|
onSubjectChange
|
|
5488
5635
|
}) {
|
|
5489
5636
|
const inputRef = useRef15(null);
|
|
5490
|
-
const subjectId =
|
|
5491
|
-
const countId =
|
|
5637
|
+
const subjectId = useId14();
|
|
5638
|
+
const countId = useId14();
|
|
5492
5639
|
const insertVariable = (token) => {
|
|
5493
5640
|
var _a, _b;
|
|
5494
5641
|
const input = inputRef.current;
|
|
@@ -5506,10 +5653,10 @@ function SubjectBar({
|
|
|
5506
5653
|
(_b2 = inputRef.current) == null ? void 0 : _b2.setSelectionRange(cursor, cursor);
|
|
5507
5654
|
});
|
|
5508
5655
|
};
|
|
5509
|
-
return /* @__PURE__ */
|
|
5510
|
-
/* @__PURE__ */
|
|
5511
|
-
/* @__PURE__ */
|
|
5512
|
-
/* @__PURE__ */
|
|
5656
|
+
return /* @__PURE__ */ jsxs43("div", { className: "ree-subject-bar", children: [
|
|
5657
|
+
/* @__PURE__ */ jsx56("label", { className: "ree-subject-label", htmlFor: subjectId, children: "Email subject" }),
|
|
5658
|
+
/* @__PURE__ */ jsxs43("div", { className: "ree-subject-field", children: [
|
|
5659
|
+
/* @__PURE__ */ jsx56(
|
|
5513
5660
|
TextInput,
|
|
5514
5661
|
{
|
|
5515
5662
|
id: subjectId,
|
|
@@ -5521,15 +5668,15 @@ function SubjectBar({
|
|
|
5521
5668
|
onChange: (event) => onSubjectChange(event.target.value)
|
|
5522
5669
|
}
|
|
5523
5670
|
),
|
|
5524
|
-
/* @__PURE__ */
|
|
5671
|
+
/* @__PURE__ */ jsxs43("span", { id: countId, className: "ree-subject-count", children: [
|
|
5525
5672
|
subject.length,
|
|
5526
5673
|
"/",
|
|
5527
5674
|
SUBJECT_MAX_LENGTH
|
|
5528
5675
|
] })
|
|
5529
5676
|
] }),
|
|
5530
|
-
variables.length > 0 ? /* @__PURE__ */
|
|
5531
|
-
/* @__PURE__ */
|
|
5532
|
-
/* @__PURE__ */
|
|
5677
|
+
variables.length > 0 ? /* @__PURE__ */ jsxs43(Fragment5, { children: [
|
|
5678
|
+
/* @__PURE__ */ jsx56(PersonalizationMenu, { variables, onInsert: insertVariable }),
|
|
5679
|
+
/* @__PURE__ */ jsx56("p", { className: "ree-subject-hint", children: PERSONALIZATION_HINT })
|
|
5533
5680
|
] }) : null
|
|
5534
5681
|
] });
|
|
5535
5682
|
}
|
|
@@ -5634,9 +5781,59 @@ function createBlockNode(type, definitions) {
|
|
|
5634
5781
|
};
|
|
5635
5782
|
}
|
|
5636
5783
|
|
|
5784
|
+
// src/render/document-html.ts
|
|
5785
|
+
function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
|
|
5786
|
+
const source = ensureEmailDocument(document);
|
|
5787
|
+
const body = source.body;
|
|
5788
|
+
const baseCss = emailBaseCss(body);
|
|
5789
|
+
const rendered = source.blocks.map((node, index) => {
|
|
5790
|
+
var _a;
|
|
5791
|
+
const blockData = __spreadValues(__spreadValues({}, options.templateData), node.defaultData);
|
|
5792
|
+
const block = blockToHtml(node, definitions, "email", {
|
|
5793
|
+
resolveHtmlTemplate: options.resolveHtmlTemplates,
|
|
5794
|
+
templateEngine: options.templateEngine,
|
|
5795
|
+
body,
|
|
5796
|
+
variables: options.variables,
|
|
5797
|
+
templateData: mergeTemplateData(
|
|
5798
|
+
(_a = options.variables) != null ? _a : [],
|
|
5799
|
+
Object.keys(blockData).length > 0 ? blockData : void 0
|
|
5800
|
+
)
|
|
5801
|
+
});
|
|
5802
|
+
const gap = index < source.blocks.length - 1 ? spacerHtml(body.blockGap) : "";
|
|
5803
|
+
return block + gap;
|
|
5804
|
+
}).join("\n");
|
|
5805
|
+
const { html: content, styles } = extractHeadStyles(rendered);
|
|
5806
|
+
return [
|
|
5807
|
+
"<!DOCTYPE html>",
|
|
5808
|
+
'<html lang="en">',
|
|
5809
|
+
"<head>",
|
|
5810
|
+
'<meta charset="utf-8" />',
|
|
5811
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
5812
|
+
"<title>Email</title>",
|
|
5813
|
+
styles,
|
|
5814
|
+
"</head>",
|
|
5815
|
+
`<body style="margin:0;padding:0;${baseCss};background-color:#ffffff;word-spacing:normal;">`,
|
|
5816
|
+
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="${baseCss};background-color:#ffffff;table-layout:fixed;">`,
|
|
5817
|
+
"<tr>",
|
|
5818
|
+
`<td align="center" style="padding:0;${baseCss};">`,
|
|
5819
|
+
`<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;">`,
|
|
5820
|
+
"<tr>",
|
|
5821
|
+
`<td style="box-sizing:border-box;${styleToCss(surfaceStyle(body))};">`,
|
|
5822
|
+
content,
|
|
5823
|
+
"</td>",
|
|
5824
|
+
"</tr>",
|
|
5825
|
+
"</table>",
|
|
5826
|
+
"</td>",
|
|
5827
|
+
"</tr>",
|
|
5828
|
+
"</table>",
|
|
5829
|
+
"</body>",
|
|
5830
|
+
"</html>"
|
|
5831
|
+
].join("\n");
|
|
5832
|
+
}
|
|
5833
|
+
|
|
5637
5834
|
// src/plugins/config.ts
|
|
5638
5835
|
function buildEmailEditorConfig(options = {}) {
|
|
5639
|
-
var _a, _b, _c;
|
|
5836
|
+
var _a, _b, _c, _d;
|
|
5640
5837
|
const definitions = new Map(DEFAULT_BLOCKS.map((block) => [block.type, block]));
|
|
5641
5838
|
const customTypes = /* @__PURE__ */ new Set();
|
|
5642
5839
|
for (const plugin of (_a = options.plugins) != null ? _a : []) {
|
|
@@ -5651,16 +5848,21 @@ function buildEmailEditorConfig(options = {}) {
|
|
|
5651
5848
|
for (const type of visibleBuiltins) if (!builtinTypes.has(type)) throw new Error(`Unknown builtin block: ${type}`);
|
|
5652
5849
|
const all = Object.freeze([...definitions.values()].map((plugin) => snapshotValue(plugin)));
|
|
5653
5850
|
const registered = new Map(all.map((plugin) => [plugin.type, plugin]));
|
|
5851
|
+
const templateEngine = (_c = options.templateEngine) != null ? _c : defaultTemplateEngine;
|
|
5654
5852
|
return Object.freeze({
|
|
5853
|
+
templateEngine,
|
|
5655
5854
|
definitions: all,
|
|
5656
|
-
library: Object.freeze([...visibleBuiltins.map((type) => registered.get(type)), ...((
|
|
5855
|
+
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))]),
|
|
5657
5856
|
pickImage: options.pickImage,
|
|
5658
5857
|
createBlock: (type) => {
|
|
5659
5858
|
const block = createBlockNode(type, all);
|
|
5660
5859
|
if (!block) throw new Error(`Unknown block plugin: ${type}`);
|
|
5661
5860
|
return block;
|
|
5662
5861
|
},
|
|
5663
|
-
toHtml: (document, htmlOptions) =>
|
|
5862
|
+
toHtml: (document, htmlOptions) => {
|
|
5863
|
+
var _a2;
|
|
5864
|
+
return documentToHtml(document, all, __spreadProps(__spreadValues({}, htmlOptions), { templateEngine: (_a2 = htmlOptions == null ? void 0 : htmlOptions.templateEngine) != null ? _a2 : templateEngine }));
|
|
5865
|
+
},
|
|
5664
5866
|
validate: (document) => validateDocument(document, all)
|
|
5665
5867
|
});
|
|
5666
5868
|
}
|
|
@@ -5897,7 +6099,7 @@ function useEditorSession(editor) {
|
|
|
5897
6099
|
}
|
|
5898
6100
|
|
|
5899
6101
|
// src/shell/EmailEditorShell.tsx
|
|
5900
|
-
import { jsx as
|
|
6102
|
+
import { jsx as jsx57, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
5901
6103
|
function EmailEditorShell({ editor, height = "100%" }) {
|
|
5902
6104
|
var _a;
|
|
5903
6105
|
const session = useEditorSession(editor);
|
|
@@ -5935,8 +6137,8 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
5935
6137
|
selectBlock(null);
|
|
5936
6138
|
};
|
|
5937
6139
|
const showCanvas = () => setViewMode("edit");
|
|
5938
|
-
return /* @__PURE__ */
|
|
5939
|
-
/* @__PURE__ */
|
|
6140
|
+
return /* @__PURE__ */ jsx57(EditorConfigContext.Provider, { value: session.config, children: /* @__PURE__ */ jsx57(RichTextFocusContext.Provider, { value: { active: activeRichText, activate: setActiveRichText }, children: /* @__PURE__ */ jsxs44("div", { className: "ree-root", style: { height }, onKeyDown: handleKeyDown, children: [
|
|
6141
|
+
/* @__PURE__ */ jsx57(
|
|
5940
6142
|
SubjectBar,
|
|
5941
6143
|
{
|
|
5942
6144
|
subject: (_a = session.document.subject) != null ? _a : "",
|
|
@@ -5944,8 +6146,8 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
5944
6146
|
onSubjectChange: session.setSubject
|
|
5945
6147
|
}
|
|
5946
6148
|
),
|
|
5947
|
-
/* @__PURE__ */
|
|
5948
|
-
/* @__PURE__ */
|
|
6149
|
+
/* @__PURE__ */ jsxs44("div", { className: "ree-body", children: [
|
|
6150
|
+
/* @__PURE__ */ jsxs44(
|
|
5949
6151
|
EditorDnd,
|
|
5950
6152
|
{
|
|
5951
6153
|
blocks: session.document.blocks,
|
|
@@ -5957,7 +6159,7 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
5957
6159
|
onReorder: session.reorderBlocks,
|
|
5958
6160
|
onLibraryDragStart: showCanvas,
|
|
5959
6161
|
children: [
|
|
5960
|
-
/* @__PURE__ */
|
|
6162
|
+
/* @__PURE__ */ jsx57(
|
|
5961
6163
|
BlockLibrary,
|
|
5962
6164
|
{
|
|
5963
6165
|
definitions: session.config.library,
|
|
@@ -5967,7 +6169,7 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
5967
6169
|
}
|
|
5968
6170
|
}
|
|
5969
6171
|
),
|
|
5970
|
-
/* @__PURE__ */
|
|
6172
|
+
/* @__PURE__ */ jsx57(
|
|
5971
6173
|
Stage,
|
|
5972
6174
|
{
|
|
5973
6175
|
session,
|
|
@@ -5981,7 +6183,7 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
5981
6183
|
]
|
|
5982
6184
|
}
|
|
5983
6185
|
),
|
|
5984
|
-
/* @__PURE__ */
|
|
6186
|
+
/* @__PURE__ */ jsx57(
|
|
5985
6187
|
Inspector,
|
|
5986
6188
|
{
|
|
5987
6189
|
panel: inspectorPanel,
|
|
@@ -6001,21 +6203,21 @@ function EmailEditorShell({ editor, height = "100%" }) {
|
|
|
6001
6203
|
}
|
|
6002
6204
|
|
|
6003
6205
|
// src/EmailEditor.tsx
|
|
6004
|
-
import { jsx as
|
|
6206
|
+
import { jsx as jsx58 } from "react/jsx-runtime";
|
|
6005
6207
|
function EmailEditor(props) {
|
|
6006
|
-
return /* @__PURE__ */
|
|
6208
|
+
return /* @__PURE__ */ jsx58(EmailEditorShell, __spreadValues({}, props));
|
|
6007
6209
|
}
|
|
6008
6210
|
|
|
6009
6211
|
// src/plugins/useEmailEditorConfig.ts
|
|
6010
|
-
import { useMemo as
|
|
6212
|
+
import { useMemo as useMemo6 } from "react";
|
|
6011
6213
|
function useEmailEditorConfig(options = {}) {
|
|
6012
|
-
const { plugins, builtins, pickImage } = options;
|
|
6013
|
-
return
|
|
6214
|
+
const { plugins, builtins, pickImage, templateEngine } = options;
|
|
6215
|
+
return useMemo6(() => buildEmailEditorConfig({ plugins, builtins, pickImage, templateEngine }), [plugins, builtins, pickImage, templateEngine]);
|
|
6014
6216
|
}
|
|
6015
6217
|
|
|
6016
6218
|
// src/shell/useEmailEditor.ts
|
|
6017
|
-
import { useEffect as
|
|
6018
|
-
var useOptionsEffect = typeof window === "undefined" ?
|
|
6219
|
+
import { useEffect as useEffect8, useInsertionEffect, useLayoutEffect as useLayoutEffect9, useState as useState19 } from "react";
|
|
6220
|
+
var useOptionsEffect = typeof window === "undefined" ? useEffect8 : useLayoutEffect9;
|
|
6019
6221
|
function useEmailEditor(options = {}) {
|
|
6020
6222
|
const [store] = useState19(() => createEditorStore(options));
|
|
6021
6223
|
useInsertionEffect(() => {
|
|
@@ -6033,12 +6235,12 @@ function useEmailEditorState(editor) {
|
|
|
6033
6235
|
}
|
|
6034
6236
|
|
|
6035
6237
|
// src/plugins/components/ControlEditor.tsx
|
|
6036
|
-
import { jsx as
|
|
6238
|
+
import { jsx as jsx59 } from "react/jsx-runtime";
|
|
6037
6239
|
function ControlEditor({ control }) {
|
|
6038
6240
|
const context = usePluginEditor();
|
|
6039
6241
|
const definition = context.controlDefinitions.find((item) => item.name === control);
|
|
6040
6242
|
if (!definition) throw new Error(`Unknown plugin control: ${control}`);
|
|
6041
|
-
return /* @__PURE__ */
|
|
6243
|
+
return /* @__PURE__ */ jsx59(ControlRenderer, { control: definition, value: context.controls[control], body: context.body, variables: context.variables, onChange: (value) => context.setControl(control, value) });
|
|
6042
6244
|
}
|
|
6043
6245
|
export {
|
|
6044
6246
|
BLOCK_TYPES,
|
|
@@ -6070,6 +6272,7 @@ export {
|
|
|
6070
6272
|
createDimensionControls,
|
|
6071
6273
|
createEmailDocument,
|
|
6072
6274
|
createTypographyControls,
|
|
6275
|
+
defaultTemplateEngine,
|
|
6073
6276
|
defineBlock,
|
|
6074
6277
|
documentToHtml,
|
|
6075
6278
|
generateId,
|
|
@@ -6082,5 +6285,6 @@ export {
|
|
|
6082
6285
|
useEmailEditor,
|
|
6083
6286
|
useEmailEditorConfig,
|
|
6084
6287
|
useEmailEditorState,
|
|
6288
|
+
validateTemplate,
|
|
6085
6289
|
variablesToTemplateData
|
|
6086
6290
|
};
|