@standhigher/react-email-editor 0.3.0 → 0.4.3
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 +26 -132
- package/README.zh-CN.md +26 -132
- package/dist/index.d.ts +355 -37
- package/dist/index.js +573 -433
- package/dist/styles.css +41 -31
- package/docs/advanced-examples.md +265 -0
- package/docs/advanced-examples.zh-CN.md +265 -0
- package/docs/api.md +277 -0
- package/docs/api.zh-CN.md +277 -0
- package/docs/assets/architecture.svg +84 -0
- package/docs/assets/architecture.zh-CN.svg +84 -0
- package/docs/basic-examples.md +113 -0
- package/docs/basic-examples.zh-CN.md +113 -0
- package/docs/changelog.md +125 -0
- package/{CHANGELOG.md → docs/changelog.zh-CN.md} +62 -0
- package/docs/custom-blocks.md +27 -31
- package/docs/custom-blocks.zh-CN.md +26 -30
- package/docs/release.md +38 -0
- package/docs/release.zh-CN.md +38 -0
- package/package.json +20 -5
package/dist/index.js
CHANGED
|
@@ -31,9 +31,6 @@ var __objRest = (source, exclude) => {
|
|
|
31
31
|
return target;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
// src/EmailEditor.tsx
|
|
35
|
-
import { forwardRef as forwardRef3 } from "react";
|
|
36
|
-
|
|
37
34
|
// src/plugins/editor-context.ts
|
|
38
35
|
import { createContext, useContext } from "react";
|
|
39
36
|
var RichTextFocusContext = createContext({ active: null, activate: () => {
|
|
@@ -47,7 +44,7 @@ function usePluginEditor() {
|
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
// src/shell/EmailEditorShell.tsx
|
|
50
|
-
import {
|
|
47
|
+
import { useState as useState18 } from "react";
|
|
51
48
|
|
|
52
49
|
// src/editor-dnd/EditorDnd.tsx
|
|
53
50
|
import { useLayoutEffect as useLayoutEffect5, useMemo, useRef as useRef7, useState as useState7 } from "react";
|
|
@@ -2730,13 +2727,13 @@ function bindPluginComponent(Component2) {
|
|
|
2730
2727
|
// src/plugins/controls.ts
|
|
2731
2728
|
import { createElement as createElement2 } from "react";
|
|
2732
2729
|
|
|
2733
|
-
// src/
|
|
2730
|
+
// src/utils/snapshot.ts
|
|
2734
2731
|
import { isValidElement } from "react";
|
|
2735
|
-
function
|
|
2736
|
-
if (Array.isArray(value)) return Object.freeze(value.map((item) =>
|
|
2732
|
+
function snapshotValue(value) {
|
|
2733
|
+
if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshotValue(item)));
|
|
2737
2734
|
if (!value || typeof value !== "object" || isValidElement(value)) return value;
|
|
2738
2735
|
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return value;
|
|
2739
|
-
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key,
|
|
2736
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshotValue(item)])));
|
|
2740
2737
|
}
|
|
2741
2738
|
|
|
2742
2739
|
// src/plugins/control-validation.ts
|
|
@@ -2761,10 +2758,10 @@ function controlConstraintError(kind, options, value) {
|
|
|
2761
2758
|
// src/plugins/controls.ts
|
|
2762
2759
|
function buildControl(kind, options, fallback, parse) {
|
|
2763
2760
|
var _b;
|
|
2764
|
-
const _a =
|
|
2761
|
+
const _a = snapshotValue(options), { component: Component2, validate } = _a, config = __objRest(_a, ["component", "validate"]);
|
|
2765
2762
|
const defaultValue = structuredClone((_b = config.defaultValue) != null ? _b : fallback);
|
|
2766
2763
|
const read = (value) => value === void 0 ? structuredClone(defaultValue) : parse(value);
|
|
2767
|
-
return
|
|
2764
|
+
return snapshotValue(__spreadProps(__spreadValues({}, config), {
|
|
2768
2765
|
kind,
|
|
2769
2766
|
defaultValue,
|
|
2770
2767
|
read,
|
|
@@ -2799,8 +2796,8 @@ var EmailControls = {
|
|
|
2799
2796
|
}),
|
|
2800
2797
|
list(options) {
|
|
2801
2798
|
const _a = options, { controls } = _a, config = __objRest(_a, ["controls"]);
|
|
2802
|
-
const schema =
|
|
2803
|
-
return
|
|
2799
|
+
const schema = snapshotValue(controls);
|
|
2800
|
+
return snapshotValue(__spreadProps(__spreadValues({}, buildControl("list", config, [], (value) => Array.isArray(value) ? value.map((item) => readControlValues(schema, item && typeof item === "object" ? item : {})) : [])), { itemControls: schema }));
|
|
2804
2801
|
},
|
|
2805
2802
|
custom(options) {
|
|
2806
2803
|
const _a = options, { read } = _a, config = __objRest(_a, ["read"]);
|
|
@@ -2811,7 +2808,7 @@ var EmailControls = {
|
|
|
2811
2808
|
// src/plugins/define-block.ts
|
|
2812
2809
|
function defineBlock(input) {
|
|
2813
2810
|
var _a, _b, _c;
|
|
2814
|
-
const options =
|
|
2811
|
+
const options = snapshotValue(input);
|
|
2815
2812
|
if (!options.type.trim() || !options.title.trim()) throw new Error("Block type and title are required.");
|
|
2816
2813
|
const schema = (_a = options.controls) != null ? _a : {};
|
|
2817
2814
|
const readControls = (source) => {
|
|
@@ -2824,7 +2821,7 @@ function defineBlock(input) {
|
|
|
2824
2821
|
for (const item of (_b = options.styleControls) != null ? _b : []) styles.set(item.id, item);
|
|
2825
2822
|
const inline = Object.entries(schema).filter(([, spec]) => spec.kind === "richText" && spec.inline);
|
|
2826
2823
|
if (inline.length > 1 && !options.Canvas) throw new Error(`${options.type}: multiple inline controls require a Canvas component.`);
|
|
2827
|
-
return
|
|
2824
|
+
return snapshotValue({
|
|
2828
2825
|
type: options.type,
|
|
2829
2826
|
title: options.title,
|
|
2830
2827
|
description: options.description,
|
|
@@ -3843,7 +3840,7 @@ function buildRenderContext(node, mode, body = DEFAULT_EMAIL_BODY, definition =
|
|
|
3843
3840
|
}
|
|
3844
3841
|
|
|
3845
3842
|
// src/inspector/InspectorControls.tsx
|
|
3846
|
-
import { useId as
|
|
3843
|
+
import { useId as useId11 } from "react";
|
|
3847
3844
|
|
|
3848
3845
|
// src/constants/style-groups.ts
|
|
3849
3846
|
var STYLE_GROUPS = [
|
|
@@ -3888,7 +3885,7 @@ function inheritedStyleValue(id, body) {
|
|
|
3888
3885
|
}
|
|
3889
3886
|
|
|
3890
3887
|
// src/inspector/components/BasicControl.tsx
|
|
3891
|
-
import { useRef as
|
|
3888
|
+
import { useRef as useRef10, useId as useId7 } from "react";
|
|
3892
3889
|
|
|
3893
3890
|
// src/inspector/HtmlCodeEditor.tsx
|
|
3894
3891
|
import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect6, useRef as useRef8 } from "react";
|
|
@@ -4082,41 +4079,147 @@ function HtmlCodeEditor({
|
|
|
4082
4079
|
);
|
|
4083
4080
|
}
|
|
4084
4081
|
|
|
4085
|
-
// src/
|
|
4086
|
-
import { useState as
|
|
4082
|
+
// src/components/PersonalizationMenu.tsx
|
|
4083
|
+
import { useId as useId6, useRef as useRef9, useState as useState13 } from "react";
|
|
4084
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
4085
|
+
|
|
4086
|
+
// src/components/usePersonalizationMenu.ts
|
|
4087
|
+
import { useLayoutEffect as useLayoutEffect7, useState as useState12 } from "react";
|
|
4088
|
+
function usePersonalizationMenu(open, rootRef, menuRef, focusOnOpen, setOpen) {
|
|
4089
|
+
const [position, setPosition] = useState12({ left: 0, top: 0, maxHeight: 240 });
|
|
4090
|
+
useLayoutEffect7(() => {
|
|
4091
|
+
var _a;
|
|
4092
|
+
const root = rootRef.current;
|
|
4093
|
+
const menu = menuRef.current;
|
|
4094
|
+
if (!open || !root || !menu) return;
|
|
4095
|
+
const ownerDocument = root.ownerDocument;
|
|
4096
|
+
const ownerWindow = ownerDocument.defaultView;
|
|
4097
|
+
if (!ownerWindow) return;
|
|
4098
|
+
const place = () => {
|
|
4099
|
+
var _a2, _b;
|
|
4100
|
+
const anchor = root.getBoundingClientRect();
|
|
4101
|
+
const inputBounds = (_b = (_a2 = root.closest(".ree-input-with-variable")) == null ? void 0 : _a2.getBoundingClientRect()) != null ? _b : anchor;
|
|
4102
|
+
const width = menu.offsetWidth;
|
|
4103
|
+
const height = Math.min(menu.scrollHeight + 2, 240);
|
|
4104
|
+
const below = Math.max(0, ownerWindow.innerHeight - inputBounds.bottom - 8);
|
|
4105
|
+
const above = Math.max(0, inputBounds.top - 8);
|
|
4106
|
+
const up = below < height && above > below;
|
|
4107
|
+
const maxHeight = Math.min(240, up ? above : below);
|
|
4108
|
+
const left = anchor.left + width > ownerWindow.innerWidth - 8 ? anchor.right - width : anchor.left;
|
|
4109
|
+
setPosition({
|
|
4110
|
+
left: Math.max(8, Math.min(left, ownerWindow.innerWidth - width - 8)),
|
|
4111
|
+
top: up ? inputBounds.top - Math.min(height, maxHeight) : inputBounds.bottom,
|
|
4112
|
+
maxHeight
|
|
4113
|
+
});
|
|
4114
|
+
};
|
|
4115
|
+
const onPointerDown = (event) => {
|
|
4116
|
+
if (!root.contains(event.target) && !menu.contains(event.target)) setOpen(false);
|
|
4117
|
+
};
|
|
4118
|
+
const onKeyDown = (event) => {
|
|
4119
|
+
var _a2, _b;
|
|
4120
|
+
const target = event.target;
|
|
4121
|
+
if (!root.contains(target) && !menu.contains(target)) return;
|
|
4122
|
+
if (event.key === "Tab" && menu.contains(target)) {
|
|
4123
|
+
const buttons = menu.querySelectorAll("button");
|
|
4124
|
+
if (target !== (event.shiftKey ? buttons[0] : buttons[buttons.length - 1])) return;
|
|
4125
|
+
if (event.shiftKey) event.preventDefault();
|
|
4126
|
+
setOpen(false);
|
|
4127
|
+
(_a2 = root.querySelector("button")) == null ? void 0 : _a2.focus({ preventScroll: true });
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
if (event.key !== "Escape") return;
|
|
4131
|
+
event.preventDefault();
|
|
4132
|
+
event.stopPropagation();
|
|
4133
|
+
setOpen(false);
|
|
4134
|
+
(_b = root.querySelector("button")) == null ? void 0 : _b.focus({ preventScroll: true });
|
|
4135
|
+
};
|
|
4136
|
+
menu.setAttribute("popover", "manual");
|
|
4137
|
+
menu.showPopover();
|
|
4138
|
+
place();
|
|
4139
|
+
if (focusOnOpen.current) (_a = menu.querySelector("button")) == null ? void 0 : _a.focus({ preventScroll: true });
|
|
4140
|
+
const observer = new ResizeObserver(place);
|
|
4141
|
+
observer.observe(root);
|
|
4142
|
+
observer.observe(menu);
|
|
4143
|
+
ownerWindow.addEventListener("resize", place);
|
|
4144
|
+
ownerDocument.addEventListener("scroll", place, true);
|
|
4145
|
+
ownerDocument.addEventListener("pointerdown", onPointerDown, true);
|
|
4146
|
+
ownerDocument.addEventListener("keydown", onKeyDown, true);
|
|
4147
|
+
return () => {
|
|
4148
|
+
menu.hidePopover();
|
|
4149
|
+
observer.disconnect();
|
|
4150
|
+
ownerWindow.removeEventListener("resize", place);
|
|
4151
|
+
ownerDocument.removeEventListener("scroll", place, true);
|
|
4152
|
+
ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
|
|
4153
|
+
ownerDocument.removeEventListener("keydown", onKeyDown, true);
|
|
4154
|
+
};
|
|
4155
|
+
}, [open, rootRef, menuRef, focusOnOpen, setOpen]);
|
|
4156
|
+
return position;
|
|
4157
|
+
}
|
|
4158
|
+
|
|
4159
|
+
// src/components/PersonalizationMenu.tsx
|
|
4087
4160
|
import { jsx as jsx40, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
4088
|
-
function
|
|
4089
|
-
|
|
4161
|
+
function PersonalizationMenu({
|
|
4162
|
+
variables,
|
|
4163
|
+
onInsert,
|
|
4164
|
+
suffix = false
|
|
4165
|
+
}) {
|
|
4166
|
+
const [open, setOpen] = useState13(false);
|
|
4167
|
+
const [host, setHost] = useState13(null);
|
|
4168
|
+
const id = useId6();
|
|
4169
|
+
const rootRef = useRef9(null);
|
|
4170
|
+
const menuRef = useRef9(null);
|
|
4171
|
+
const focusOnOpen = useRef9(false);
|
|
4172
|
+
const position = usePersonalizationMenu(open && variables.length > 0, rootRef, menuRef, focusOnOpen, setOpen);
|
|
4090
4173
|
if (variables.length === 0) return null;
|
|
4091
|
-
return /* @__PURE__ */ jsxs29("div", { className: "ree-
|
|
4092
|
-
/* @__PURE__ */
|
|
4174
|
+
return /* @__PURE__ */ jsxs29("div", { className: suffix ? "ree-personalization ree-personalization-suffix" : "ree-personalization", ref: rootRef, children: [
|
|
4175
|
+
/* @__PURE__ */ jsxs29(
|
|
4093
4176
|
"button",
|
|
4094
4177
|
{
|
|
4095
4178
|
type: "button",
|
|
4096
|
-
className: "ree-
|
|
4097
|
-
|
|
4098
|
-
title: "
|
|
4099
|
-
"aria-label": "Insert variable",
|
|
4179
|
+
className: suffix ? "ree-personalization-suffix-btn" : "ree-personalization-btn",
|
|
4180
|
+
"aria-label": suffix ? "Insert variable" : void 0,
|
|
4181
|
+
title: suffix ? "Personalization" : void 0,
|
|
4100
4182
|
"aria-expanded": open,
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4183
|
+
"aria-controls": open ? id : void 0,
|
|
4184
|
+
onClick: (event) => {
|
|
4185
|
+
var _a;
|
|
4186
|
+
focusOnOpen.current = event.detail === 0;
|
|
4187
|
+
const root = rootRef.current;
|
|
4188
|
+
if (root) setHost((_a = root.closest(".ree-root")) != null ? _a : root.ownerDocument.body);
|
|
4189
|
+
setOpen((current) => !current);
|
|
4190
|
+
},
|
|
4191
|
+
onKeyDown: (event) => {
|
|
4192
|
+
var _a, _b;
|
|
4193
|
+
if (event.key === "Tab" && open && !event.shiftKey) {
|
|
4194
|
+
event.preventDefault();
|
|
4195
|
+
(_b = (_a = menuRef.current) == null ? void 0 : _a.querySelector("button")) == null ? void 0 : _b.focus();
|
|
4196
|
+
}
|
|
4112
4197
|
},
|
|
4113
4198
|
children: [
|
|
4114
|
-
|
|
4115
|
-
|
|
4199
|
+
suffix ? "{ }" : "Personalization",
|
|
4200
|
+
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" }) })
|
|
4116
4201
|
]
|
|
4117
|
-
}
|
|
4118
|
-
|
|
4119
|
-
|
|
4202
|
+
}
|
|
4203
|
+
),
|
|
4204
|
+
open && host ? createPortal2(
|
|
4205
|
+
/* @__PURE__ */ jsx40("div", { id, ref: menuRef, className: "ree-var-menu ree-personalization-menu", style: position, children: variables.map((variable) => /* @__PURE__ */ jsxs29(
|
|
4206
|
+
"button",
|
|
4207
|
+
{
|
|
4208
|
+
type: "button",
|
|
4209
|
+
className: "ree-var-item",
|
|
4210
|
+
onClick: () => {
|
|
4211
|
+
onInsert(variable.name);
|
|
4212
|
+
setOpen(false);
|
|
4213
|
+
},
|
|
4214
|
+
children: [
|
|
4215
|
+
/* @__PURE__ */ jsx40("span", { className: "ree-var-name", children: variable.name }),
|
|
4216
|
+
variable.description ? /* @__PURE__ */ jsx40("span", { className: "ree-var-label", children: variable.description }) : null
|
|
4217
|
+
]
|
|
4218
|
+
},
|
|
4219
|
+
variable.name
|
|
4220
|
+
)) }),
|
|
4221
|
+
host
|
|
4222
|
+
) : null
|
|
4120
4223
|
] });
|
|
4121
4224
|
}
|
|
4122
4225
|
|
|
@@ -4124,8 +4227,8 @@ function VariablesDropdown({ variables, onInsert }) {
|
|
|
4124
4227
|
import { jsx as jsx41, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
4125
4228
|
function BasicControl({ control, value, variables, onChange }) {
|
|
4126
4229
|
var _a, _b, _c, _d, _e;
|
|
4127
|
-
const ref =
|
|
4128
|
-
const id =
|
|
4230
|
+
const ref = useRef10(null);
|
|
4231
|
+
const id = useId7();
|
|
4129
4232
|
const shown = value == null || typeof value === "number" && !Number.isFinite(value) ? "" : String(value);
|
|
4130
4233
|
const insertVariable = (token) => {
|
|
4131
4234
|
var _a2, _b2;
|
|
@@ -4133,6 +4236,11 @@ function BasicControl({ control, value, variables, onChange }) {
|
|
|
4133
4236
|
const start = (_a2 = element == null ? void 0 : element.selectionStart) != null ? _a2 : shown.length;
|
|
4134
4237
|
const end = (_b2 = element == null ? void 0 : element.selectionEnd) != null ? _b2 : shown.length;
|
|
4135
4238
|
onChange(shown.slice(0, start) + token + shown.slice(end));
|
|
4239
|
+
requestAnimationFrame(() => {
|
|
4240
|
+
var _a3, _b3;
|
|
4241
|
+
(_a3 = ref.current) == null ? void 0 : _a3.focus();
|
|
4242
|
+
(_b3 = ref.current) == null ? void 0 : _b3.setSelectionRange(start + token.length, start + token.length);
|
|
4243
|
+
});
|
|
4136
4244
|
};
|
|
4137
4245
|
if (control.kind === "boolean") return /* @__PURE__ */ jsx41(CheckboxControl, { label: control.label, checked: Boolean(value), onChange });
|
|
4138
4246
|
if (control.kind === "color") return /* @__PURE__ */ jsx41(ColorControl, { label: control.label, value: shown, resetTo: String((_a = control.defaultValue) != null ? _a : ""), onChange });
|
|
@@ -4141,30 +4249,32 @@ function BasicControl({ control, value, variables, onChange }) {
|
|
|
4141
4249
|
/* @__PURE__ */ jsx41(HtmlCodeEditor, { value: shown, placeholder: control.placeholder, rows: control.rows, onChange })
|
|
4142
4250
|
] });
|
|
4143
4251
|
const textual = control.kind === "text" || control.kind === "textarea" || control.kind === "url";
|
|
4252
|
+
const hasVariables = textual && variables.length > 0;
|
|
4144
4253
|
const invalidUrl = control.kind === "url" && shown.trim() && !safeEmailUrl(shown);
|
|
4254
|
+
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) => {
|
|
4255
|
+
ref.current = element;
|
|
4256
|
+
}, 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) => {
|
|
4257
|
+
ref.current = element;
|
|
4258
|
+
}, 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) });
|
|
4145
4259
|
return /* @__PURE__ */ jsxs30("div", { className: "ree-field", children: [
|
|
4146
|
-
/* @__PURE__ */
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
ref.current = element;
|
|
4155
|
-
}, 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) => {
|
|
4156
|
-
ref.current = element;
|
|
4157
|
-
}, 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) })
|
|
4260
|
+
/* @__PURE__ */ jsx41("div", { className: "ree-field-label", children: /* @__PURE__ */ jsxs30("label", { htmlFor: id, children: [
|
|
4261
|
+
control.label,
|
|
4262
|
+
control.unit ? ` (${control.unit})` : ""
|
|
4263
|
+
] }) }),
|
|
4264
|
+
hasVariables ? /* @__PURE__ */ jsxs30("div", { className: "ree-input-with-variable", children: [
|
|
4265
|
+
input,
|
|
4266
|
+
/* @__PURE__ */ jsx41(PersonalizationMenu, { suffix: true, variables, onInsert: insertVariable })
|
|
4267
|
+
] }) : input
|
|
4158
4268
|
] });
|
|
4159
4269
|
}
|
|
4160
4270
|
|
|
4161
4271
|
// src/inspector/components/ListControl.tsx
|
|
4162
|
-
import { useId as
|
|
4272
|
+
import { useId as useId8 } from "react";
|
|
4163
4273
|
|
|
4164
4274
|
// src/inspector/components/useListRows.ts
|
|
4165
|
-
import { useLayoutEffect as
|
|
4275
|
+
import { useLayoutEffect as useLayoutEffect8, useRef as useRef11, useState as useState14 } from "react";
|
|
4166
4276
|
function useListRows(value, onChange) {
|
|
4167
|
-
const [state, setState] =
|
|
4277
|
+
const [state, setState] = useState14({ source: void 0, rows: [], nextKey: 0 });
|
|
4168
4278
|
if (state.source !== value) {
|
|
4169
4279
|
const available = [...state.rows];
|
|
4170
4280
|
let nextKey = state.nextKey;
|
|
@@ -4176,8 +4286,8 @@ function useListRows(value, onChange) {
|
|
|
4176
4286
|
});
|
|
4177
4287
|
setState({ source: value, rows, nextKey });
|
|
4178
4288
|
}
|
|
4179
|
-
const latest =
|
|
4180
|
-
|
|
4289
|
+
const latest = useRef11(state);
|
|
4290
|
+
useLayoutEffect8(() => {
|
|
4181
4291
|
latest.current = state;
|
|
4182
4292
|
return () => {
|
|
4183
4293
|
latest.current = null;
|
|
@@ -4222,7 +4332,7 @@ import { jsx as jsx42, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
|
4222
4332
|
function ListControl(_a) {
|
|
4223
4333
|
var _b = _a, { control, value, onChange } = _b, props = __objRest(_b, ["control", "value", "onChange"]);
|
|
4224
4334
|
var _a2;
|
|
4225
|
-
const id =
|
|
4335
|
+
const id = useId8();
|
|
4226
4336
|
const schema = (_a2 = control.itemControls) != null ? _a2 : {};
|
|
4227
4337
|
const { rows, updateRow, moveRow, removeRow, addRow } = useListRows(value, onChange);
|
|
4228
4338
|
return /* @__PURE__ */ jsxs31("fieldset", { className: "ree-plugin-list", children: [
|
|
@@ -4242,15 +4352,15 @@ function ListControl(_a) {
|
|
|
4242
4352
|
}
|
|
4243
4353
|
|
|
4244
4354
|
// src/inspector/components/ImageControl.tsx
|
|
4245
|
-
import { useContext as useContext4, useEffect as useEffect5, useId as
|
|
4355
|
+
import { useContext as useContext4, useEffect as useEffect5, useId as useId9, useRef as useRef12, useState as useState15 } from "react";
|
|
4246
4356
|
import { jsx as jsx43, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
4247
4357
|
function ImageControl({ control, value, onChange }) {
|
|
4248
4358
|
var _a;
|
|
4249
4359
|
const config = useContext4(EditorConfigContext);
|
|
4250
|
-
const controller =
|
|
4251
|
-
const id =
|
|
4252
|
-
const [loading, setLoading] =
|
|
4253
|
-
const [error, setError] =
|
|
4360
|
+
const controller = useRef12(null);
|
|
4361
|
+
const id = useId9();
|
|
4362
|
+
const [loading, setLoading] = useState15(false);
|
|
4363
|
+
const [error, setError] = useState15("");
|
|
4254
4364
|
const shown = String(value != null ? value : "");
|
|
4255
4365
|
const url = safeEmailUrl(shown, true);
|
|
4256
4366
|
useEffect5(() => () => {
|
|
@@ -4287,12 +4397,12 @@ function ImageControl({ control, value, onChange }) {
|
|
|
4287
4397
|
}
|
|
4288
4398
|
|
|
4289
4399
|
// src/inspector/components/RichTextControl.tsx
|
|
4290
|
-
import { useContext as useContext5, useId as
|
|
4400
|
+
import { useContext as useContext5, useId as useId10 } from "react";
|
|
4291
4401
|
import { jsx as jsx44, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
4292
4402
|
function RichTextControl({ control, value, variables, body, onChange }) {
|
|
4293
4403
|
var _a, _b;
|
|
4294
4404
|
const parent = useContext5(PluginEditorContext);
|
|
4295
|
-
const id =
|
|
4405
|
+
const id = useId10();
|
|
4296
4406
|
const context = {
|
|
4297
4407
|
block: (_a = parent == null ? void 0 : parent.block) != null ? _a : { id, type: "control", controls: {} },
|
|
4298
4408
|
controls: { [control.name]: value },
|
|
@@ -4350,7 +4460,7 @@ function InspectorControls({
|
|
|
4350
4460
|
onCommitStyle
|
|
4351
4461
|
}) {
|
|
4352
4462
|
var _a, _b, _c, _d, _e;
|
|
4353
|
-
const toolbarId =
|
|
4463
|
+
const toolbarId = useId11();
|
|
4354
4464
|
const contentControls = ((_a = definition.controls) != null ? _a : []).filter((control) => control.name !== definition.inlineControl);
|
|
4355
4465
|
const CustomInspector = definition.inspector;
|
|
4356
4466
|
const context = buildRenderContext(block, "canvas", body, definition);
|
|
@@ -4502,7 +4612,7 @@ function viewportWidth(device, body) {
|
|
|
4502
4612
|
}
|
|
4503
4613
|
|
|
4504
4614
|
// src/stage/canvas/CanvasBlock.tsx
|
|
4505
|
-
import { useId as
|
|
4615
|
+
import { useId as useId13, useMemo as useMemo3, useState as useState16 } from "react";
|
|
4506
4616
|
import { useSortable } from "@dnd-kit/sortable";
|
|
4507
4617
|
import { CSS } from "@dnd-kit/utilities";
|
|
4508
4618
|
|
|
@@ -4638,8 +4748,10 @@ function renderTemplatePreview(html, defaultData) {
|
|
|
4638
4748
|
}
|
|
4639
4749
|
|
|
4640
4750
|
// src/render/block-html.ts
|
|
4641
|
-
function
|
|
4642
|
-
|
|
4751
|
+
function assertBlockRenderOptions(options) {
|
|
4752
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
4753
|
+
throw new TypeError("Block render options must be an object.");
|
|
4754
|
+
}
|
|
4643
4755
|
}
|
|
4644
4756
|
function renderPreparedBlock(context, definition) {
|
|
4645
4757
|
try {
|
|
@@ -4712,23 +4824,23 @@ function mergeTemplateData(variables, defaultData) {
|
|
|
4712
4824
|
}
|
|
4713
4825
|
function renderBlockContent(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
|
|
4714
4826
|
var _a;
|
|
4715
|
-
|
|
4827
|
+
assertBlockRenderOptions(options);
|
|
4716
4828
|
const rendered = renderBlock(
|
|
4717
4829
|
node,
|
|
4718
4830
|
definitions,
|
|
4719
4831
|
mode,
|
|
4720
|
-
(_a =
|
|
4832
|
+
(_a = options.body) != null ? _a : DEFAULT_EMAIL_BODY
|
|
4721
4833
|
);
|
|
4722
|
-
return resolveBlockContent(rendered, node,
|
|
4834
|
+
return resolveBlockContent(rendered, node, options);
|
|
4723
4835
|
}
|
|
4724
|
-
function resolveBlockContent(rendered, node,
|
|
4836
|
+
function resolveBlockContent(rendered, node, options) {
|
|
4725
4837
|
var _a, _b;
|
|
4726
|
-
if (!
|
|
4727
|
-
return applyListedVariableDots(rendered, (_a =
|
|
4838
|
+
if (!options.resolveHtmlTemplate) {
|
|
4839
|
+
return applyListedVariableDots(rendered, (_a = options.variables) != null ? _a : []);
|
|
4728
4840
|
}
|
|
4729
4841
|
return renderTemplatePreview(
|
|
4730
4842
|
rendered,
|
|
4731
|
-
(_b =
|
|
4843
|
+
(_b = options.templateData) != null ? _b : node.defaultData
|
|
4732
4844
|
);
|
|
4733
4845
|
}
|
|
4734
4846
|
function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body = DEFAULT_EMAIL_BODY) {
|
|
@@ -4742,10 +4854,10 @@ function renderBlock(node, definitions = DEFAULT_BLOCKS, mode = "email", body =
|
|
|
4742
4854
|
}
|
|
4743
4855
|
function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options = {}) {
|
|
4744
4856
|
var _a, _b, _c;
|
|
4745
|
-
|
|
4746
|
-
const body = (_a =
|
|
4857
|
+
assertBlockRenderOptions(options);
|
|
4858
|
+
const body = (_a = options.body) != null ? _a : DEFAULT_EMAIL_BODY;
|
|
4747
4859
|
const style = resolveBlockStyle(node.style, getBlockDefinition(node.type, definitions));
|
|
4748
|
-
const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({},
|
|
4860
|
+
const inner = renderBlockContent(node, definitions, mode, __spreadProps(__spreadValues({}, options), {
|
|
4749
4861
|
body
|
|
4750
4862
|
}));
|
|
4751
4863
|
const height = cssPx((_b = style.dimension) == null ? void 0 : _b.height);
|
|
@@ -4754,7 +4866,7 @@ function blockToHtml(node, definitions = DEFAULT_BLOCKS, mode = "email", options
|
|
|
4754
4866
|
}
|
|
4755
4867
|
|
|
4756
4868
|
// src/stage/canvas/components/HtmlCanvas.tsx
|
|
4757
|
-
import { useCallback, useEffect as useEffect6, useRef as
|
|
4869
|
+
import { useCallback, useEffect as useEffect6, useRef as useRef13 } from "react";
|
|
4758
4870
|
|
|
4759
4871
|
// src/render/head-styles.ts
|
|
4760
4872
|
function extractHeadStyles(html) {
|
|
@@ -4775,9 +4887,9 @@ function frameHtml(html, baseCss) {
|
|
|
4775
4887
|
}
|
|
4776
4888
|
function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
4777
4889
|
const hasHtmlTag = /<\/?[a-z][\w:-]*(?:\s[^>]*)?>/i.test(html);
|
|
4778
|
-
const containerRef =
|
|
4779
|
-
const frameRef =
|
|
4780
|
-
const cleanupRef =
|
|
4890
|
+
const containerRef = useRef13(null);
|
|
4891
|
+
const frameRef = useRef13(null);
|
|
4892
|
+
const cleanupRef = useRef13();
|
|
4781
4893
|
const observeContent = useCallback(() => {
|
|
4782
4894
|
var _a;
|
|
4783
4895
|
(_a = cleanupRef.current) == null ? void 0 : _a.call(cleanupRef);
|
|
@@ -4785,8 +4897,8 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
|
4785
4897
|
const frame = frameRef.current;
|
|
4786
4898
|
const document = frame == null ? void 0 : frame.contentDocument;
|
|
4787
4899
|
if (!container || !frame || !(document == null ? void 0 : document.body)) return;
|
|
4788
|
-
const
|
|
4789
|
-
if (!
|
|
4900
|
+
const window2 = document.defaultView;
|
|
4901
|
+
if (!window2) return;
|
|
4790
4902
|
let animationFrame = 0;
|
|
4791
4903
|
const fit = () => {
|
|
4792
4904
|
const availableWidth = container.clientWidth;
|
|
@@ -4798,12 +4910,12 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
|
4798
4910
|
container.style.height = `${contentHeight}px`;
|
|
4799
4911
|
};
|
|
4800
4912
|
const scheduleFit = () => {
|
|
4801
|
-
|
|
4802
|
-
animationFrame =
|
|
4913
|
+
window2.cancelAnimationFrame(animationFrame);
|
|
4914
|
+
animationFrame = window2.requestAnimationFrame(fit);
|
|
4803
4915
|
};
|
|
4804
|
-
const observer = new
|
|
4916
|
+
const observer = new window2.ResizeObserver(scheduleFit);
|
|
4805
4917
|
let previousWidth = container.clientWidth;
|
|
4806
|
-
const containerObserver = new
|
|
4918
|
+
const containerObserver = new window2.ResizeObserver(() => {
|
|
4807
4919
|
if (container.clientWidth === previousWidth) return;
|
|
4808
4920
|
previousWidth = container.clientWidth;
|
|
4809
4921
|
scheduleFit();
|
|
@@ -4816,7 +4928,7 @@ function HtmlCanvas({ html, baseCss = "", onSelect }) {
|
|
|
4816
4928
|
observer.disconnect();
|
|
4817
4929
|
containerObserver.disconnect();
|
|
4818
4930
|
document.removeEventListener("load", scheduleFit, true);
|
|
4819
|
-
|
|
4931
|
+
window2.cancelAnimationFrame(animationFrame);
|
|
4820
4932
|
};
|
|
4821
4933
|
}, []);
|
|
4822
4934
|
useEffect6(() => () => {
|
|
@@ -4898,7 +5010,7 @@ function PluginCanvasContent({ block, body, definition, variables, selected, too
|
|
|
4898
5010
|
}
|
|
4899
5011
|
|
|
4900
5012
|
// src/stage/canvas/components/BlockActions.tsx
|
|
4901
|
-
import { useId as
|
|
5013
|
+
import { useId as useId12 } from "react";
|
|
4902
5014
|
import { jsx as jsx50, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
4903
5015
|
function BlockActions({
|
|
4904
5016
|
inlineEdit,
|
|
@@ -4908,7 +5020,7 @@ function BlockActions({
|
|
|
4908
5020
|
onDuplicate,
|
|
4909
5021
|
onRemove
|
|
4910
5022
|
}) {
|
|
4911
|
-
const tooltipId =
|
|
5023
|
+
const tooltipId = useId12();
|
|
4912
5024
|
const toggleLabel = toolbarCollapsed ? "Expand Toolbar" : "Collapse Toolbar";
|
|
4913
5025
|
return /* @__PURE__ */ jsxs39(
|
|
4914
5026
|
"div",
|
|
@@ -5004,8 +5116,8 @@ function CanvasBlock({
|
|
|
5004
5116
|
}) {
|
|
5005
5117
|
var _a, _b, _c;
|
|
5006
5118
|
const block = useMemo3(() => __spreadProps(__spreadValues({}, sourceBlock), { style: resolveBlockStyle(sourceBlock.style, definition) }), [sourceBlock, definition]);
|
|
5007
|
-
const toolbarId =
|
|
5008
|
-
const [toolbarCollapsed, setToolbarCollapsed] =
|
|
5119
|
+
const toolbarId = useId13();
|
|
5120
|
+
const [toolbarCollapsed, setToolbarCollapsed] = useState16(false);
|
|
5009
5121
|
const dragData = { kind: "canvas" };
|
|
5010
5122
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: block.id, data: dragData });
|
|
5011
5123
|
const select = (event) => {
|
|
@@ -5172,7 +5284,7 @@ function Canvas({
|
|
|
5172
5284
|
}
|
|
5173
5285
|
|
|
5174
5286
|
// src/stage/preview/Preview.tsx
|
|
5175
|
-
import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo4, useRef as
|
|
5287
|
+
import { useCallback as useCallback2, useEffect as useEffect7, useMemo as useMemo4, useRef as useRef14 } from "react";
|
|
5176
5288
|
|
|
5177
5289
|
// src/render/document-html.ts
|
|
5178
5290
|
function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
|
|
@@ -5226,8 +5338,8 @@ function documentToHtml(document, definitions = DEFAULT_BLOCKS, options = {}) {
|
|
|
5226
5338
|
// src/stage/preview/Preview.tsx
|
|
5227
5339
|
import { jsx as jsx53 } from "react/jsx-runtime";
|
|
5228
5340
|
function Preview({ document, definitions, variables, device }) {
|
|
5229
|
-
const frameRef =
|
|
5230
|
-
const cleanupRef =
|
|
5341
|
+
const frameRef = useRef14(null);
|
|
5342
|
+
const cleanupRef = useRef14();
|
|
5231
5343
|
const html = useMemo4(
|
|
5232
5344
|
() => documentToHtml(document, definitions, {
|
|
5233
5345
|
resolveHtmlTemplates: true,
|
|
@@ -5420,75 +5532,23 @@ function Stage({
|
|
|
5420
5532
|
] });
|
|
5421
5533
|
}
|
|
5422
5534
|
|
|
5535
|
+
// src/shell/SubjectBar.tsx
|
|
5536
|
+
import { useId as useId14, useRef as useRef15 } from "react";
|
|
5537
|
+
|
|
5423
5538
|
// src/shell/constants.ts
|
|
5424
5539
|
var EMPTY_VARIABLES = [];
|
|
5425
5540
|
var PERSONALIZATION_HINT = "Personalization fields will appear blank if no data is available.";
|
|
5426
5541
|
|
|
5427
5542
|
// src/shell/SubjectBar.tsx
|
|
5428
|
-
import {
|
|
5429
|
-
|
|
5430
|
-
// src/shell/PersonalizationMenu.tsx
|
|
5431
|
-
import { useEffect as useEffect8, useRef as useRef14, useState as useState16 } from "react";
|
|
5432
|
-
import { jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
5433
|
-
function PersonalizationMenu({
|
|
5434
|
-
variables,
|
|
5435
|
-
onInsert
|
|
5436
|
-
}) {
|
|
5437
|
-
const [open, setOpen] = useState16(false);
|
|
5438
|
-
const rootRef = useRef14(null);
|
|
5439
|
-
useEffect8(() => {
|
|
5440
|
-
const root = rootRef.current;
|
|
5441
|
-
if (!open || !root) return;
|
|
5442
|
-
const ownerDocument = root.ownerDocument;
|
|
5443
|
-
const onPointerDown = (event) => {
|
|
5444
|
-
if (!root.contains(event.target)) setOpen(false);
|
|
5445
|
-
};
|
|
5446
|
-
ownerDocument.addEventListener("pointerdown", onPointerDown, true);
|
|
5447
|
-
return () => ownerDocument.removeEventListener("pointerdown", onPointerDown, true);
|
|
5448
|
-
}, [open]);
|
|
5449
|
-
if (variables.length === 0) return null;
|
|
5450
|
-
return /* @__PURE__ */ jsxs43("div", { className: "ree-personalization", ref: rootRef, children: [
|
|
5451
|
-
/* @__PURE__ */ jsxs43(
|
|
5452
|
-
"button",
|
|
5453
|
-
{
|
|
5454
|
-
type: "button",
|
|
5455
|
-
className: "ree-personalization-btn",
|
|
5456
|
-
"aria-expanded": open,
|
|
5457
|
-
onClick: () => setOpen((current) => !current),
|
|
5458
|
-
children: [
|
|
5459
|
-
"Personalization",
|
|
5460
|
-
/* @__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" }) })
|
|
5461
|
-
]
|
|
5462
|
-
}
|
|
5463
|
-
),
|
|
5464
|
-
open ? /* @__PURE__ */ jsx56("div", { className: "ree-var-menu ree-personalization-menu", children: variables.map((variable) => /* @__PURE__ */ jsxs43(
|
|
5465
|
-
"button",
|
|
5466
|
-
{
|
|
5467
|
-
type: "button",
|
|
5468
|
-
className: "ree-var-item",
|
|
5469
|
-
onClick: () => {
|
|
5470
|
-
onInsert(variable.name);
|
|
5471
|
-
setOpen(false);
|
|
5472
|
-
},
|
|
5473
|
-
children: [
|
|
5474
|
-
/* @__PURE__ */ jsx56("span", { className: "ree-var-name", children: variable.name }),
|
|
5475
|
-
variable.description ? /* @__PURE__ */ jsx56("span", { className: "ree-var-label", children: variable.description }) : null
|
|
5476
|
-
]
|
|
5477
|
-
},
|
|
5478
|
-
variable.name
|
|
5479
|
-
)) }) : null
|
|
5480
|
-
] });
|
|
5481
|
-
}
|
|
5482
|
-
|
|
5483
|
-
// src/shell/SubjectBar.tsx
|
|
5484
|
-
import { Fragment as Fragment5, jsx as jsx57, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
5543
|
+
import { Fragment as Fragment5, jsx as jsx56, jsxs as jsxs43 } from "react/jsx-runtime";
|
|
5485
5544
|
function SubjectBar({
|
|
5486
5545
|
subject,
|
|
5487
5546
|
variables,
|
|
5488
5547
|
onSubjectChange
|
|
5489
5548
|
}) {
|
|
5490
5549
|
const inputRef = useRef15(null);
|
|
5491
|
-
const
|
|
5550
|
+
const subjectId = useId14();
|
|
5551
|
+
const countId = useId14();
|
|
5492
5552
|
const insertVariable = (token) => {
|
|
5493
5553
|
var _a, _b;
|
|
5494
5554
|
const input = inputRef.current;
|
|
@@ -5506,13 +5566,13 @@ function SubjectBar({
|
|
|
5506
5566
|
(_b2 = inputRef.current) == null ? void 0 : _b2.setSelectionRange(cursor, cursor);
|
|
5507
5567
|
});
|
|
5508
5568
|
};
|
|
5509
|
-
return /* @__PURE__ */
|
|
5510
|
-
/* @__PURE__ */
|
|
5511
|
-
/* @__PURE__ */
|
|
5512
|
-
/* @__PURE__ */
|
|
5569
|
+
return /* @__PURE__ */ jsxs43("div", { className: "ree-subject-bar", children: [
|
|
5570
|
+
/* @__PURE__ */ jsx56("label", { className: "ree-subject-label", htmlFor: subjectId, children: "Email subject" }),
|
|
5571
|
+
/* @__PURE__ */ jsxs43("div", { className: "ree-subject-field", children: [
|
|
5572
|
+
/* @__PURE__ */ jsx56(
|
|
5513
5573
|
TextInput,
|
|
5514
5574
|
{
|
|
5515
|
-
id:
|
|
5575
|
+
id: subjectId,
|
|
5516
5576
|
ref: inputRef,
|
|
5517
5577
|
className: "ree-subject-input",
|
|
5518
5578
|
value: subject,
|
|
@@ -5521,19 +5581,22 @@ function SubjectBar({
|
|
|
5521
5581
|
onChange: (event) => onSubjectChange(event.target.value)
|
|
5522
5582
|
}
|
|
5523
5583
|
),
|
|
5524
|
-
/* @__PURE__ */
|
|
5584
|
+
/* @__PURE__ */ jsxs43("span", { id: countId, className: "ree-subject-count", children: [
|
|
5525
5585
|
subject.length,
|
|
5526
5586
|
"/",
|
|
5527
5587
|
SUBJECT_MAX_LENGTH
|
|
5528
5588
|
] })
|
|
5529
5589
|
] }),
|
|
5530
|
-
variables.length > 0 ? /* @__PURE__ */
|
|
5531
|
-
/* @__PURE__ */
|
|
5532
|
-
/* @__PURE__ */
|
|
5590
|
+
variables.length > 0 ? /* @__PURE__ */ jsxs43(Fragment5, { children: [
|
|
5591
|
+
/* @__PURE__ */ jsx56(PersonalizationMenu, { variables, onInsert: insertVariable }),
|
|
5592
|
+
/* @__PURE__ */ jsx56("p", { className: "ree-subject-hint", children: PERSONALIZATION_HINT })
|
|
5533
5593
|
] }) : null
|
|
5534
5594
|
] });
|
|
5535
5595
|
}
|
|
5536
5596
|
|
|
5597
|
+
// src/shell/useEditorSession.ts
|
|
5598
|
+
import { useCallback as useCallback3, useState as useState17, useSyncExternalStore } from "react";
|
|
5599
|
+
|
|
5537
5600
|
// src/utils/id.ts
|
|
5538
5601
|
function generateId(prefix = "block") {
|
|
5539
5602
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
@@ -5542,62 +5605,6 @@ function generateId(prefix = "block") {
|
|
|
5542
5605
|
return `${prefix}-${Math.random().toString(36).slice(2, 10)}`;
|
|
5543
5606
|
}
|
|
5544
5607
|
|
|
5545
|
-
// src/document/create-block.ts
|
|
5546
|
-
function createBlockNode(type, definitions) {
|
|
5547
|
-
var _a, _b;
|
|
5548
|
-
const definition = getBlockDefinition(type, definitions);
|
|
5549
|
-
if (!definition) return null;
|
|
5550
|
-
const controls = structuredClone(definition.readControls({}));
|
|
5551
|
-
let style = resolveBlockStyle(definition.defaultStyle, definition);
|
|
5552
|
-
for (const control of (_a = definition.styleControls) != null ? _a : []) {
|
|
5553
|
-
const [group, key] = control.id.split(".");
|
|
5554
|
-
const stored = (_b = definition.defaultStyle) == null ? void 0 : _b[group];
|
|
5555
|
-
if (control.defaultValue !== void 0 && (!stored || !(key in stored))) style = writeStyleValue(style, control.id, control.defaultValue);
|
|
5556
|
-
}
|
|
5557
|
-
return {
|
|
5558
|
-
id: generateId(type),
|
|
5559
|
-
type,
|
|
5560
|
-
controls,
|
|
5561
|
-
style,
|
|
5562
|
-
defaultData: definition.defaultData ? structuredClone(definition.defaultData) : void 0
|
|
5563
|
-
};
|
|
5564
|
-
}
|
|
5565
|
-
|
|
5566
|
-
// src/plugins/config.ts
|
|
5567
|
-
function createEmailEditorConfig(options = {}) {
|
|
5568
|
-
var _a, _b, _c;
|
|
5569
|
-
const definitions = new Map(DEFAULT_BLOCKS.map((block) => [block.type, block]));
|
|
5570
|
-
const customTypes = /* @__PURE__ */ new Set();
|
|
5571
|
-
for (const plugin of (_a = options.plugins) != null ? _a : []) {
|
|
5572
|
-
if (customTypes.has(plugin.type)) throw new Error(`Duplicate block plugin: ${plugin.type}`);
|
|
5573
|
-
if (definitions.has(plugin.type) && !plugin.override) throw new Error(`Block ${plugin.type} already exists. Set override: true to replace it.`);
|
|
5574
|
-
customTypes.add(plugin.type);
|
|
5575
|
-
definitions.set(plugin.type, plugin);
|
|
5576
|
-
}
|
|
5577
|
-
const builtinTypes = new Set(DEFAULT_BLOCKS.map((block) => block.type));
|
|
5578
|
-
const visibleBuiltins = options.builtins === false ? [] : (_b = options.builtins) != null ? _b : [...builtinTypes];
|
|
5579
|
-
if (new Set(visibleBuiltins).size !== visibleBuiltins.length) throw new Error("Duplicate builtin in library order.");
|
|
5580
|
-
for (const type of visibleBuiltins) if (!builtinTypes.has(type)) throw new Error(`Unknown builtin block: ${type}`);
|
|
5581
|
-
const all = Object.freeze([...definitions.values()].map((plugin) => snapshotConfig(plugin)));
|
|
5582
|
-
const registered = new Map(all.map((plugin) => [plugin.type, plugin]));
|
|
5583
|
-
return Object.freeze({
|
|
5584
|
-
definitions: all,
|
|
5585
|
-
library: Object.freeze([...visibleBuiltins.map((type) => registered.get(type)), ...((_c = options.plugins) != null ? _c : []).filter((plugin) => !builtinTypes.has(plugin.type)).map((plugin) => registered.get(plugin.type))]),
|
|
5586
|
-
pickImage: options.pickImage,
|
|
5587
|
-
createBlock: (type) => {
|
|
5588
|
-
const block = createBlockNode(type, all);
|
|
5589
|
-
if (!block) throw new Error(`Unknown block plugin: ${type}`);
|
|
5590
|
-
return block;
|
|
5591
|
-
},
|
|
5592
|
-
toHtml: (document, htmlOptions) => documentToHtml(document, all, htmlOptions),
|
|
5593
|
-
validate: (document) => validateDocument(document, all)
|
|
5594
|
-
});
|
|
5595
|
-
}
|
|
5596
|
-
var DEFAULT_EDITOR_CONFIG = createEmailEditorConfig();
|
|
5597
|
-
|
|
5598
|
-
// src/shell/useEditorSession.ts
|
|
5599
|
-
import { useCallback as useCallback3, useEffect as useEffect9, useLayoutEffect as useLayoutEffect8, useMemo as useMemo5, useRef as useRef16, useState as useState17 } from "react";
|
|
5600
|
-
|
|
5601
5608
|
// src/document/mutations.ts
|
|
5602
5609
|
function moveItem(items, from, to) {
|
|
5603
5610
|
const next = items.slice();
|
|
@@ -5666,68 +5673,215 @@ function updateBodySettings(document, patch) {
|
|
|
5666
5673
|
});
|
|
5667
5674
|
}
|
|
5668
5675
|
|
|
5676
|
+
// src/document/create-block.ts
|
|
5677
|
+
function createBlockNode(type, definitions) {
|
|
5678
|
+
var _a, _b;
|
|
5679
|
+
const definition = getBlockDefinition(type, definitions);
|
|
5680
|
+
if (!definition) return null;
|
|
5681
|
+
const controls = structuredClone(definition.readControls({}));
|
|
5682
|
+
let style = resolveBlockStyle(definition.defaultStyle, definition);
|
|
5683
|
+
for (const control of (_a = definition.styleControls) != null ? _a : []) {
|
|
5684
|
+
const [group, key] = control.id.split(".");
|
|
5685
|
+
const stored = (_b = definition.defaultStyle) == null ? void 0 : _b[group];
|
|
5686
|
+
if (control.defaultValue !== void 0 && (!stored || !(key in stored))) style = writeStyleValue(style, control.id, control.defaultValue);
|
|
5687
|
+
}
|
|
5688
|
+
return {
|
|
5689
|
+
id: generateId(type),
|
|
5690
|
+
type,
|
|
5691
|
+
controls,
|
|
5692
|
+
style,
|
|
5693
|
+
defaultData: definition.defaultData ? structuredClone(definition.defaultData) : void 0
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
// src/plugins/config.ts
|
|
5698
|
+
function buildEmailEditorConfig(options = {}) {
|
|
5699
|
+
var _a, _b, _c;
|
|
5700
|
+
const definitions = new Map(DEFAULT_BLOCKS.map((block) => [block.type, block]));
|
|
5701
|
+
const customTypes = /* @__PURE__ */ new Set();
|
|
5702
|
+
for (const plugin of (_a = options.plugins) != null ? _a : []) {
|
|
5703
|
+
if (customTypes.has(plugin.type)) throw new Error(`Duplicate block plugin: ${plugin.type}`);
|
|
5704
|
+
if (definitions.has(plugin.type) && !plugin.override) throw new Error(`Block ${plugin.type} already exists. Set override: true to replace it.`);
|
|
5705
|
+
customTypes.add(plugin.type);
|
|
5706
|
+
definitions.set(plugin.type, plugin);
|
|
5707
|
+
}
|
|
5708
|
+
const builtinTypes = new Set(DEFAULT_BLOCKS.map((block) => block.type));
|
|
5709
|
+
const visibleBuiltins = options.builtins === false ? [] : (_b = options.builtins) != null ? _b : [...builtinTypes];
|
|
5710
|
+
if (new Set(visibleBuiltins).size !== visibleBuiltins.length) throw new Error("Duplicate builtin in library order.");
|
|
5711
|
+
for (const type of visibleBuiltins) if (!builtinTypes.has(type)) throw new Error(`Unknown builtin block: ${type}`);
|
|
5712
|
+
const all = Object.freeze([...definitions.values()].map((plugin) => snapshotValue(plugin)));
|
|
5713
|
+
const registered = new Map(all.map((plugin) => [plugin.type, plugin]));
|
|
5714
|
+
return Object.freeze({
|
|
5715
|
+
definitions: all,
|
|
5716
|
+
library: Object.freeze([...visibleBuiltins.map((type) => registered.get(type)), ...((_c = options.plugins) != null ? _c : []).filter((plugin) => !builtinTypes.has(plugin.type)).map((plugin) => registered.get(plugin.type))]),
|
|
5717
|
+
pickImage: options.pickImage,
|
|
5718
|
+
createBlock: (type) => {
|
|
5719
|
+
const block = createBlockNode(type, all);
|
|
5720
|
+
if (!block) throw new Error(`Unknown block plugin: ${type}`);
|
|
5721
|
+
return block;
|
|
5722
|
+
},
|
|
5723
|
+
toHtml: (document, htmlOptions) => documentToHtml(document, all, htmlOptions),
|
|
5724
|
+
validate: (document) => validateDocument(document, all)
|
|
5725
|
+
});
|
|
5726
|
+
}
|
|
5727
|
+
var DEFAULT_EDITOR_CONFIG = buildEmailEditorConfig();
|
|
5728
|
+
|
|
5729
|
+
// src/shell/editor-store.ts
|
|
5730
|
+
var stores = /* @__PURE__ */ new WeakMap();
|
|
5731
|
+
function copyDocument(document) {
|
|
5732
|
+
return ensureEmailDocument(structuredClone(document));
|
|
5733
|
+
}
|
|
5734
|
+
function getEditorStore(editor) {
|
|
5735
|
+
const store = stores.get(editor);
|
|
5736
|
+
if (!store) throw new Error("EmailEditor requires an instance returned by useEmailEditor().");
|
|
5737
|
+
return store;
|
|
5738
|
+
}
|
|
5739
|
+
function createEditorStore(options) {
|
|
5740
|
+
var _a, _b, _c, _d;
|
|
5741
|
+
let callbacks = options;
|
|
5742
|
+
const base = copyDocument((_a = options.initialDocument) != null ? _a : createEmptyDocument());
|
|
5743
|
+
const initial = options.body === void 0 ? base : __spreadProps(__spreadValues({}, base), { body: mergeEmailBody(base.body, options.body) });
|
|
5744
|
+
let snapshot = {
|
|
5745
|
+
document: initial,
|
|
5746
|
+
config: (_b = options.config) != null ? _b : DEFAULT_EDITOR_CONFIG,
|
|
5747
|
+
variables: structuredClone((_c = options.variables) != null ? _c : EMPTY_VARIABLES),
|
|
5748
|
+
resetKey: generateId("session")
|
|
5749
|
+
};
|
|
5750
|
+
let state = snapshotValue({ document: copyDocument(initial) });
|
|
5751
|
+
let bodyKey = JSON.stringify(options.body);
|
|
5752
|
+
let variablesKey = JSON.stringify((_d = options.variables) != null ? _d : EMPTY_VARIABLES);
|
|
5753
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
5754
|
+
const notify = () => listeners.forEach((listener) => listener());
|
|
5755
|
+
let optionsChanged = false;
|
|
5756
|
+
let pendingBodyDocument;
|
|
5757
|
+
const setSnapshot = (next) => {
|
|
5758
|
+
if (next.document !== snapshot.document) state = snapshotValue({ document: copyDocument(next.document) });
|
|
5759
|
+
snapshot = next;
|
|
5760
|
+
};
|
|
5761
|
+
const flushOptions = () => {
|
|
5762
|
+
const shouldNotify = optionsChanged;
|
|
5763
|
+
const document = pendingBodyDocument;
|
|
5764
|
+
const onChange = callbacks.onChange;
|
|
5765
|
+
optionsChanged = false;
|
|
5766
|
+
pendingBodyDocument = void 0;
|
|
5767
|
+
if (shouldNotify) notify();
|
|
5768
|
+
if (document) onChange == null ? void 0 : onChange(copyDocument(document));
|
|
5769
|
+
};
|
|
5770
|
+
const publish = (next) => {
|
|
5771
|
+
setSnapshot(next);
|
|
5772
|
+
notify();
|
|
5773
|
+
};
|
|
5774
|
+
const commit = (document, bodyChange = false) => {
|
|
5775
|
+
var _a2, _b2;
|
|
5776
|
+
flushOptions();
|
|
5777
|
+
if (document === snapshot.document) return;
|
|
5778
|
+
const next = copyDocument(document);
|
|
5779
|
+
publish(__spreadProps(__spreadValues({}, snapshot), { document: next }));
|
|
5780
|
+
(_a2 = callbacks.onChange) == null ? void 0 : _a2.call(callbacks, copyDocument(next));
|
|
5781
|
+
if (bodyChange) (_b2 = callbacks.onBodyChange) == null ? void 0 : _b2.call(callbacks, structuredClone(next.body));
|
|
5782
|
+
};
|
|
5783
|
+
const replace = (document) => {
|
|
5784
|
+
var _a2;
|
|
5785
|
+
flushOptions();
|
|
5786
|
+
const next = copyDocument(document);
|
|
5787
|
+
publish(__spreadProps(__spreadValues({}, snapshot), { document: next, resetKey: generateId("session") }));
|
|
5788
|
+
(_a2 = callbacks.onChange) == null ? void 0 : _a2.call(callbacks, copyDocument(next));
|
|
5789
|
+
};
|
|
5790
|
+
const getDocument = () => copyDocument(snapshot.document);
|
|
5791
|
+
const editor = Object.freeze({
|
|
5792
|
+
getDocument,
|
|
5793
|
+
getJson: () => JSON.stringify(getDocument(), null, 2),
|
|
5794
|
+
getHtml: () => snapshot.config.toHtml(getDocument(), { variables: structuredClone(snapshot.variables) }),
|
|
5795
|
+
getRenderedHtml: () => snapshot.config.toHtml(getDocument(), { variables: structuredClone(snapshot.variables), resolveHtmlTemplates: true }),
|
|
5796
|
+
getData: () => {
|
|
5797
|
+
const document = getDocument();
|
|
5798
|
+
const { config } = snapshot;
|
|
5799
|
+
const variables = structuredClone(snapshot.variables);
|
|
5800
|
+
return {
|
|
5801
|
+
document,
|
|
5802
|
+
json: JSON.stringify(document, null, 2),
|
|
5803
|
+
html: config.toHtml(document, { variables }),
|
|
5804
|
+
renderedHtml: config.toHtml(document, { variables, resolveHtmlTemplates: true })
|
|
5805
|
+
};
|
|
5806
|
+
},
|
|
5807
|
+
setDocument: replace,
|
|
5808
|
+
reset: () => replace(initial),
|
|
5809
|
+
validate: () => snapshot.config.validate(getDocument())
|
|
5810
|
+
});
|
|
5811
|
+
const store = {
|
|
5812
|
+
editor,
|
|
5813
|
+
subscribe: (listener) => {
|
|
5814
|
+
listeners.add(listener);
|
|
5815
|
+
return () => {
|
|
5816
|
+
listeners.delete(listener);
|
|
5817
|
+
};
|
|
5818
|
+
},
|
|
5819
|
+
getSnapshot: () => snapshot,
|
|
5820
|
+
getState: () => state,
|
|
5821
|
+
commit,
|
|
5822
|
+
flushOptions,
|
|
5823
|
+
updateOptions: (nextOptions) => {
|
|
5824
|
+
var _a2, _b2, _c2;
|
|
5825
|
+
callbacks = nextOptions;
|
|
5826
|
+
const nextVariablesKey = JSON.stringify((_a2 = nextOptions.variables) != null ? _a2 : EMPTY_VARIABLES);
|
|
5827
|
+
const nextBodyKey = JSON.stringify(nextOptions.body);
|
|
5828
|
+
let next = snapshot;
|
|
5829
|
+
const config = (_b2 = nextOptions.config) != null ? _b2 : DEFAULT_EDITOR_CONFIG;
|
|
5830
|
+
if (config !== next.config || nextVariablesKey !== variablesKey) {
|
|
5831
|
+
next = __spreadProps(__spreadValues({}, next), { config, variables: structuredClone((_c2 = nextOptions.variables) != null ? _c2 : EMPTY_VARIABLES) });
|
|
5832
|
+
}
|
|
5833
|
+
variablesKey = nextVariablesKey;
|
|
5834
|
+
let bodyChanged = false;
|
|
5835
|
+
if (bodyKey !== nextBodyKey && nextOptions.body !== void 0) {
|
|
5836
|
+
const body = mergeEmailBody(next.document.body, nextOptions.body);
|
|
5837
|
+
bodyChanged = !sameEmailBody(next.document.body, body);
|
|
5838
|
+
if (bodyChanged) next = __spreadProps(__spreadValues({}, next), { document: __spreadProps(__spreadValues({}, next.document), { body }) });
|
|
5839
|
+
}
|
|
5840
|
+
bodyKey = nextBodyKey;
|
|
5841
|
+
if (next !== snapshot) {
|
|
5842
|
+
setSnapshot(next);
|
|
5843
|
+
optionsChanged = true;
|
|
5844
|
+
}
|
|
5845
|
+
if (bodyChanged) pendingBodyDocument = next.document;
|
|
5846
|
+
}
|
|
5847
|
+
};
|
|
5848
|
+
stores.set(editor, store);
|
|
5849
|
+
return store;
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5669
5852
|
// src/shell/useEditorSession.ts
|
|
5670
|
-
function useEditorSession({
|
|
5671
|
-
initialDocument,
|
|
5672
|
-
body,
|
|
5673
|
-
config = DEFAULT_EDITOR_CONFIG,
|
|
5674
|
-
variables = EMPTY_VARIABLES,
|
|
5675
|
-
onChange,
|
|
5676
|
-
onBodyChange
|
|
5677
|
-
}) {
|
|
5853
|
+
function useEditorSession(editor) {
|
|
5678
5854
|
var _a;
|
|
5855
|
+
const store = getEditorStore(editor);
|
|
5856
|
+
const { document, config, variables, resetKey } = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
5679
5857
|
const definitions = config.definitions;
|
|
5680
|
-
const [document, setDocument] = useState17(() => {
|
|
5681
|
-
const base = ensureEmailDocument(
|
|
5682
|
-
structuredClone(initialDocument != null ? initialDocument : createEmptyDocument())
|
|
5683
|
-
);
|
|
5684
|
-
if (body === void 0) return base;
|
|
5685
|
-
return __spreadProps(__spreadValues({}, base), { body: mergeEmailBody(base.body, body) });
|
|
5686
|
-
});
|
|
5687
5858
|
const [selectedId, setSelectedId] = useState17(null);
|
|
5688
|
-
const
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
documentRef.current = next;
|
|
5695
|
-
setDocument(next);
|
|
5696
|
-
onChange == null ? void 0 : onChange(structuredClone(next));
|
|
5697
|
-
},
|
|
5698
|
-
[onChange]
|
|
5699
|
-
);
|
|
5700
|
-
useEffect9(() => {
|
|
5701
|
-
if (body === void 0) return;
|
|
5702
|
-
const current = documentRef.current;
|
|
5703
|
-
const nextBody = mergeEmailBody(current.body, body);
|
|
5704
|
-
if (sameEmailBody(resolveEmailBody(current.body), nextBody)) return;
|
|
5705
|
-
commit(__spreadProps(__spreadValues({}, current), { body: nextBody }));
|
|
5706
|
-
}, [body, commit]);
|
|
5707
|
-
const getDocument = useCallback3(
|
|
5708
|
-
() => ensureEmailDocument(structuredClone(documentRef.current)),
|
|
5709
|
-
[]
|
|
5710
|
-
);
|
|
5859
|
+
const [previousReset, setPreviousReset] = useState17({ editor, resetKey });
|
|
5860
|
+
if (previousReset.editor !== editor || previousReset.resetKey !== resetKey) {
|
|
5861
|
+
setPreviousReset({ editor, resetKey });
|
|
5862
|
+
setSelectedId(null);
|
|
5863
|
+
}
|
|
5864
|
+
const commit = store.commit;
|
|
5711
5865
|
const selectBlock = useCallback3((id) => {
|
|
5712
5866
|
setSelectedId(id);
|
|
5713
5867
|
}, []);
|
|
5714
5868
|
const insertBlock = useCallback3(
|
|
5715
5869
|
(type, index) => {
|
|
5716
|
-
const node =
|
|
5870
|
+
const node = definitions.some((definition) => definition.type === type) ? config.createBlock(type) : null;
|
|
5717
5871
|
if (!node) return false;
|
|
5718
|
-
commit(insertBlockNode(
|
|
5872
|
+
commit(insertBlockNode(store.getSnapshot().document, node, index));
|
|
5719
5873
|
setSelectedId(node.id);
|
|
5720
5874
|
return true;
|
|
5721
5875
|
},
|
|
5722
|
-
[commit, definitions]
|
|
5876
|
+
[commit, definitions, config, store]
|
|
5723
5877
|
);
|
|
5724
5878
|
const updateControls = useCallback3(
|
|
5725
5879
|
(id, patch) => {
|
|
5726
|
-
const current =
|
|
5880
|
+
const current = store.getSnapshot().document;
|
|
5727
5881
|
const next = updateBlockControls(current, id, patch);
|
|
5728
5882
|
if (next !== current) commit(next);
|
|
5729
5883
|
},
|
|
5730
|
-
[commit]
|
|
5884
|
+
[commit, store]
|
|
5731
5885
|
);
|
|
5732
5886
|
const updateControl = useCallback3(
|
|
5733
5887
|
(id, name, value) => updateControls(id, { [name]: value }),
|
|
@@ -5735,58 +5889,56 @@ function useEditorSession({
|
|
|
5735
5889
|
);
|
|
5736
5890
|
const updateStyle = useCallback3(
|
|
5737
5891
|
(id, styleId, value) => {
|
|
5738
|
-
commit(updateBlockStyle(
|
|
5892
|
+
commit(updateBlockStyle(store.getSnapshot().document, id, styleId, value));
|
|
5739
5893
|
},
|
|
5740
|
-
[commit]
|
|
5894
|
+
[commit, store]
|
|
5741
5895
|
);
|
|
5742
5896
|
const updateBody = useCallback3(
|
|
5743
5897
|
(patch) => {
|
|
5744
|
-
const next = updateBodySettings(
|
|
5745
|
-
commit(next);
|
|
5746
|
-
onBodyChange == null ? void 0 : onBodyChange(structuredClone(next.body));
|
|
5898
|
+
const next = updateBodySettings(store.getSnapshot().document, patch);
|
|
5899
|
+
store.commit(next, true);
|
|
5747
5900
|
},
|
|
5748
|
-
[
|
|
5901
|
+
[store]
|
|
5749
5902
|
);
|
|
5750
5903
|
const moveBlock = useCallback3(
|
|
5751
5904
|
(id, direction) => {
|
|
5752
|
-
commit(moveBlockBy(
|
|
5905
|
+
commit(moveBlockBy(store.getSnapshot().document, id, direction));
|
|
5753
5906
|
},
|
|
5754
|
-
[commit]
|
|
5907
|
+
[commit, store]
|
|
5755
5908
|
);
|
|
5756
5909
|
const duplicateBlock = useCallback3(
|
|
5757
5910
|
(id) => {
|
|
5758
|
-
commit(duplicateBlockNode(
|
|
5911
|
+
commit(duplicateBlockNode(store.getSnapshot().document, id));
|
|
5759
5912
|
},
|
|
5760
|
-
[commit]
|
|
5913
|
+
[commit, store]
|
|
5761
5914
|
);
|
|
5762
5915
|
const removeBlock = useCallback3(
|
|
5763
5916
|
(id) => {
|
|
5764
|
-
commit(removeBlockNode(
|
|
5917
|
+
commit(removeBlockNode(store.getSnapshot().document, id));
|
|
5765
5918
|
setSelectedId((current) => current === id ? null : current);
|
|
5766
5919
|
},
|
|
5767
|
-
[commit]
|
|
5920
|
+
[commit, store]
|
|
5768
5921
|
);
|
|
5769
5922
|
const reorderBlocks = useCallback3(
|
|
5770
5923
|
(activeId, overId) => {
|
|
5771
|
-
commit(reorderBlockNodes(
|
|
5924
|
+
commit(reorderBlockNodes(store.getSnapshot().document, activeId, overId));
|
|
5772
5925
|
},
|
|
5773
|
-
[commit]
|
|
5926
|
+
[commit, store]
|
|
5774
5927
|
);
|
|
5775
5928
|
const setSubject = useCallback3(
|
|
5776
5929
|
(subject) => {
|
|
5777
|
-
commit(withSubject(
|
|
5930
|
+
commit(withSubject(store.getSnapshot().document, subject.slice(0, SUBJECT_MAX_LENGTH)));
|
|
5778
5931
|
},
|
|
5779
|
-
[commit]
|
|
5932
|
+
[commit, store]
|
|
5780
5933
|
);
|
|
5781
|
-
const
|
|
5782
|
-
const selectedBlock = (_a = resolvedDocument.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
|
|
5934
|
+
const selectedBlock = (_a = document.blocks.find((block) => block.id === selectedId)) != null ? _a : null;
|
|
5783
5935
|
const selectedDefinition = selectedBlock ? getBlockDefinition(selectedBlock.type, definitions) : void 0;
|
|
5784
|
-
return {
|
|
5785
|
-
document
|
|
5786
|
-
getDocument,
|
|
5936
|
+
return __spreadProps(__spreadValues({}, editor), {
|
|
5937
|
+
document,
|
|
5787
5938
|
definitions,
|
|
5788
5939
|
config,
|
|
5789
5940
|
variables,
|
|
5941
|
+
resetKey,
|
|
5790
5942
|
selectedId,
|
|
5791
5943
|
selectedBlock,
|
|
5792
5944
|
selectedDefinition,
|
|
@@ -5801,166 +5953,152 @@ function useEditorSession({
|
|
|
5801
5953
|
removeBlock,
|
|
5802
5954
|
reorderBlocks,
|
|
5803
5955
|
setSubject
|
|
5804
|
-
};
|
|
5805
|
-
}
|
|
5806
|
-
|
|
5807
|
-
// src/shell/useEditorHandle.ts
|
|
5808
|
-
import { useImperativeHandle } from "react";
|
|
5809
|
-
function useEditorHandle(ref, { getDocument, definitions, variables }) {
|
|
5810
|
-
useImperativeHandle(ref, () => ({
|
|
5811
|
-
getData: () => {
|
|
5812
|
-
const document = getDocument();
|
|
5813
|
-
return {
|
|
5814
|
-
document,
|
|
5815
|
-
json: JSON.stringify(document, null, 2),
|
|
5816
|
-
html: documentToHtml(document, definitions, { variables }),
|
|
5817
|
-
renderedHtml: documentToHtml(document, definitions, {
|
|
5818
|
-
resolveHtmlTemplates: true,
|
|
5819
|
-
variables
|
|
5820
|
-
})
|
|
5821
|
-
};
|
|
5822
|
-
},
|
|
5823
|
-
getDocument,
|
|
5824
|
-
getJson: () => JSON.stringify(getDocument(), null, 2),
|
|
5825
|
-
getHtml: () => documentToHtml(getDocument(), definitions, { variables }),
|
|
5826
|
-
getRenderedHtml: () => documentToHtml(getDocument(), definitions, {
|
|
5827
|
-
resolveHtmlTemplates: true,
|
|
5828
|
-
variables
|
|
5829
|
-
})
|
|
5830
|
-
}), [getDocument, definitions, variables]);
|
|
5956
|
+
});
|
|
5831
5957
|
}
|
|
5832
5958
|
|
|
5833
5959
|
// src/shell/EmailEditorShell.tsx
|
|
5834
|
-
import { jsx as
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
const [previousSelectedId, setPreviousSelectedId] = useState18(session.selectedId);
|
|
5860
|
-
if (previousSelectedId !== session.selectedId) {
|
|
5861
|
-
setPreviousSelectedId(session.selectedId);
|
|
5862
|
-
if (!session.selectedId) setInspectorPanel("body");
|
|
5960
|
+
import { jsx as jsx57, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
5961
|
+
function EmailEditorShell({ editor, height = "100%" }) {
|
|
5962
|
+
var _a;
|
|
5963
|
+
const session = useEditorSession(editor);
|
|
5964
|
+
const [activeRichText, setActiveRichText] = useState18(null);
|
|
5965
|
+
const [viewMode, setViewMode] = useState18("edit");
|
|
5966
|
+
const [device, setDevice] = useState18("desktop");
|
|
5967
|
+
const [inspectorPanel, setInspectorPanel] = useState18("body");
|
|
5968
|
+
const [previousReset, setPreviousReset] = useState18({ editor, key: session.resetKey });
|
|
5969
|
+
if (previousReset.editor !== editor || previousReset.key !== session.resetKey) {
|
|
5970
|
+
setPreviousReset({ editor, key: session.resetKey });
|
|
5971
|
+
setActiveRichText(null);
|
|
5972
|
+
setInspectorPanel("body");
|
|
5973
|
+
}
|
|
5974
|
+
const [previousSelectedId, setPreviousSelectedId] = useState18(session.selectedId);
|
|
5975
|
+
if (previousSelectedId !== session.selectedId) {
|
|
5976
|
+
setPreviousSelectedId(session.selectedId);
|
|
5977
|
+
if (!session.selectedId) setInspectorPanel("body");
|
|
5978
|
+
}
|
|
5979
|
+
const selectBlock = (id) => {
|
|
5980
|
+
const hadSelection = session.selectedId !== null;
|
|
5981
|
+
session.selectBlock(id);
|
|
5982
|
+
if (!id) {
|
|
5983
|
+
setInspectorPanel("body");
|
|
5984
|
+
return;
|
|
5863
5985
|
}
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
}
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5986
|
+
if (!hadSelection) setInspectorPanel("block");
|
|
5987
|
+
};
|
|
5988
|
+
const handleKeyDown = (event) => {
|
|
5989
|
+
if (event.key !== "Escape") return;
|
|
5990
|
+
const target = event.target;
|
|
5991
|
+
const tag = target == null ? void 0 : target.tagName;
|
|
5992
|
+
if ((target == null ? void 0 : target.isContentEditable) || tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") {
|
|
5993
|
+
return;
|
|
5994
|
+
}
|
|
5995
|
+
selectBlock(null);
|
|
5996
|
+
};
|
|
5997
|
+
const showCanvas = () => setViewMode("edit");
|
|
5998
|
+
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: [
|
|
5999
|
+
/* @__PURE__ */ jsx57(
|
|
6000
|
+
SubjectBar,
|
|
6001
|
+
{
|
|
6002
|
+
subject: (_a = session.document.subject) != null ? _a : "",
|
|
6003
|
+
variables: session.variables,
|
|
6004
|
+
onSubjectChange: session.setSubject
|
|
5879
6005
|
}
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
/* @__PURE__ */ jsx58(
|
|
5885
|
-
SubjectBar,
|
|
6006
|
+
),
|
|
6007
|
+
/* @__PURE__ */ jsxs44("div", { className: "ree-body", children: [
|
|
6008
|
+
/* @__PURE__ */ jsxs44(
|
|
6009
|
+
EditorDnd,
|
|
5886
6010
|
{
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
6011
|
+
blocks: session.document.blocks,
|
|
6012
|
+
definitions: session.definitions,
|
|
6013
|
+
onInsertBlock: (type, index) => {
|
|
6014
|
+
showCanvas();
|
|
6015
|
+
if (session.insertBlock(type, index)) setInspectorPanel("block");
|
|
6016
|
+
},
|
|
6017
|
+
onReorder: session.reorderBlocks,
|
|
6018
|
+
onLibraryDragStart: showCanvas,
|
|
6019
|
+
children: [
|
|
6020
|
+
/* @__PURE__ */ jsx57(
|
|
6021
|
+
BlockLibrary,
|
|
6022
|
+
{
|
|
6023
|
+
definitions: session.config.library,
|
|
6024
|
+
onAdd: (type) => {
|
|
6025
|
+
showCanvas();
|
|
6026
|
+
if (session.insertBlock(type)) setInspectorPanel("block");
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
),
|
|
6030
|
+
/* @__PURE__ */ jsx57(
|
|
6031
|
+
Stage,
|
|
6032
|
+
{
|
|
6033
|
+
session,
|
|
6034
|
+
viewMode,
|
|
6035
|
+
device,
|
|
6036
|
+
onViewModeChange: setViewMode,
|
|
6037
|
+
onDeviceChange: setDevice,
|
|
6038
|
+
onSelectBlock: selectBlock
|
|
6039
|
+
}
|
|
6040
|
+
)
|
|
6041
|
+
]
|
|
5890
6042
|
}
|
|
5891
6043
|
),
|
|
5892
|
-
/* @__PURE__ */
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
showCanvas();
|
|
5911
|
-
if (session.insertBlock(type)) setInspectorPanel("block");
|
|
5912
|
-
}
|
|
5913
|
-
}
|
|
5914
|
-
),
|
|
5915
|
-
/* @__PURE__ */ jsx58(
|
|
5916
|
-
Stage,
|
|
5917
|
-
{
|
|
5918
|
-
session,
|
|
5919
|
-
viewMode,
|
|
5920
|
-
device,
|
|
5921
|
-
onViewModeChange: setViewMode,
|
|
5922
|
-
onDeviceChange: setDevice,
|
|
5923
|
-
onSelectBlock: selectBlock
|
|
5924
|
-
}
|
|
5925
|
-
)
|
|
5926
|
-
]
|
|
5927
|
-
}
|
|
5928
|
-
),
|
|
5929
|
-
/* @__PURE__ */ jsx58(
|
|
5930
|
-
Inspector,
|
|
5931
|
-
{
|
|
5932
|
-
panel: inspectorPanel,
|
|
5933
|
-
body: session.document.body,
|
|
5934
|
-
block: session.selectedBlock,
|
|
5935
|
-
definition: session.selectedDefinition,
|
|
5936
|
-
variables: session.variables,
|
|
5937
|
-
onPanelChange: setInspectorPanel,
|
|
5938
|
-
onCommitBody: session.updateBody,
|
|
5939
|
-
onCommitControl: session.updateControl,
|
|
5940
|
-
onCommitControls: session.updateControls,
|
|
5941
|
-
onCommitStyle: session.updateStyle
|
|
5942
|
-
}
|
|
5943
|
-
)
|
|
5944
|
-
] })
|
|
5945
|
-
] }) }) });
|
|
5946
|
-
}
|
|
5947
|
-
);
|
|
6044
|
+
/* @__PURE__ */ jsx57(
|
|
6045
|
+
Inspector,
|
|
6046
|
+
{
|
|
6047
|
+
panel: inspectorPanel,
|
|
6048
|
+
body: session.document.body,
|
|
6049
|
+
block: session.selectedBlock,
|
|
6050
|
+
definition: session.selectedDefinition,
|
|
6051
|
+
variables: session.variables,
|
|
6052
|
+
onPanelChange: setInspectorPanel,
|
|
6053
|
+
onCommitBody: session.updateBody,
|
|
6054
|
+
onCommitControl: session.updateControl,
|
|
6055
|
+
onCommitControls: session.updateControls,
|
|
6056
|
+
onCommitStyle: session.updateStyle
|
|
6057
|
+
}
|
|
6058
|
+
)
|
|
6059
|
+
] })
|
|
6060
|
+
] }, session.resetKey) }) });
|
|
6061
|
+
}
|
|
5948
6062
|
|
|
5949
6063
|
// src/EmailEditor.tsx
|
|
5950
|
-
import { jsx as
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
6064
|
+
import { jsx as jsx58 } from "react/jsx-runtime";
|
|
6065
|
+
function EmailEditor(props) {
|
|
6066
|
+
return /* @__PURE__ */ jsx58(EmailEditorShell, __spreadValues({}, props));
|
|
6067
|
+
}
|
|
6068
|
+
|
|
6069
|
+
// src/plugins/useEmailEditorConfig.ts
|
|
6070
|
+
import { useMemo as useMemo5 } from "react";
|
|
6071
|
+
function useEmailEditorConfig(options = {}) {
|
|
6072
|
+
const { plugins, builtins, pickImage } = options;
|
|
6073
|
+
return useMemo5(() => buildEmailEditorConfig({ plugins, builtins, pickImage }), [plugins, builtins, pickImage]);
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
// src/shell/useEmailEditor.ts
|
|
6077
|
+
import { useEffect as useEffect8, useInsertionEffect, useLayoutEffect as useLayoutEffect9, useState as useState19 } from "react";
|
|
6078
|
+
var useOptionsEffect = typeof window === "undefined" ? useEffect8 : useLayoutEffect9;
|
|
6079
|
+
function useEmailEditor(options = {}) {
|
|
6080
|
+
const [store] = useState19(() => createEditorStore(options));
|
|
6081
|
+
useInsertionEffect(() => {
|
|
6082
|
+
store.updateOptions(options);
|
|
6083
|
+
});
|
|
6084
|
+
useOptionsEffect(store.flushOptions);
|
|
6085
|
+
return store.editor;
|
|
6086
|
+
}
|
|
6087
|
+
|
|
6088
|
+
// src/shell/useEmailEditorState.ts
|
|
6089
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
6090
|
+
function useEmailEditorState(editor) {
|
|
6091
|
+
const store = getEditorStore(editor);
|
|
6092
|
+
return useSyncExternalStore2(store.subscribe, store.getState, store.getState);
|
|
6093
|
+
}
|
|
5956
6094
|
|
|
5957
6095
|
// src/plugins/components/ControlEditor.tsx
|
|
5958
|
-
import { jsx as
|
|
6096
|
+
import { jsx as jsx59 } from "react/jsx-runtime";
|
|
5959
6097
|
function ControlEditor({ control }) {
|
|
5960
6098
|
const context = usePluginEditor();
|
|
5961
6099
|
const definition = context.controlDefinitions.find((item) => item.name === control);
|
|
5962
6100
|
if (!definition) throw new Error(`Unknown plugin control: ${control}`);
|
|
5963
|
-
return /* @__PURE__ */
|
|
6101
|
+
return /* @__PURE__ */ jsx59(ControlRenderer, { control: definition, value: context.controls[control], body: context.body, variables: context.variables, onChange: (value) => context.setControl(control, value) });
|
|
5964
6102
|
}
|
|
5965
6103
|
export {
|
|
5966
6104
|
BLOCK_TYPES,
|
|
@@ -5991,7 +6129,6 @@ export {
|
|
|
5991
6129
|
createDecorationControls,
|
|
5992
6130
|
createDimensionControls,
|
|
5993
6131
|
createEmailDocument,
|
|
5994
|
-
createEmailEditorConfig,
|
|
5995
6132
|
createTypographyControls,
|
|
5996
6133
|
defineBlock,
|
|
5997
6134
|
documentToHtml,
|
|
@@ -6002,5 +6139,8 @@ export {
|
|
|
6002
6139
|
renderBlockContent,
|
|
6003
6140
|
renderTemplatePreview,
|
|
6004
6141
|
resolveEmailBody,
|
|
6142
|
+
useEmailEditor,
|
|
6143
|
+
useEmailEditorConfig,
|
|
6144
|
+
useEmailEditorState,
|
|
6005
6145
|
variablesToTemplateData
|
|
6006
6146
|
};
|