react-email-studio 3.8.1 → 3.8.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/CHANGELOG.md +17 -0
- package/README.md +5 -2
- package/RELEASE.md +137 -0
- package/dist/index.cjs +143 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +143 -52
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -827,6 +827,65 @@ function countBlocksInDesign(rows) {
|
|
|
827
827
|
}, 0);
|
|
828
828
|
return rows.reduce((sum, r) => sum + r.cells.reduce((s, c) => s + colBlocks(c), 0), 0);
|
|
829
829
|
}
|
|
830
|
+
function isSingleBlockInRow(row) {
|
|
831
|
+
if (!row?.cells) return false;
|
|
832
|
+
let count = 0;
|
|
833
|
+
for (const col of row.cells) {
|
|
834
|
+
if (!Array.isArray(col)) continue;
|
|
835
|
+
count += col.length;
|
|
836
|
+
if (count > 1) return false;
|
|
837
|
+
}
|
|
838
|
+
return count === 1;
|
|
839
|
+
}
|
|
840
|
+
function resolveBlockMovePlan(rows, rowId, cellIdx, blockIndex, inner) {
|
|
841
|
+
const rowIndex = rows.findIndex((r) => r.id === rowId);
|
|
842
|
+
const row = rowIndex >= 0 ? rows[rowIndex] : void 0;
|
|
843
|
+
if (!row) return null;
|
|
844
|
+
if (inner) {
|
|
845
|
+
const loc = toColumnLoc(rowId, cellIdx, {
|
|
846
|
+
parentBlockIdx: inner.parentBlockIdx,
|
|
847
|
+
innerCellIdx: inner.cellIdx
|
|
848
|
+
});
|
|
849
|
+
const col2 = getColumnBlocks(rows, loc);
|
|
850
|
+
const idx = inner.contentIdx;
|
|
851
|
+
return {
|
|
852
|
+
mode: "column",
|
|
853
|
+
blockIndex: idx,
|
|
854
|
+
canMoveUp: idx > 0,
|
|
855
|
+
canMoveDown: idx < col2.length - 1
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const col = row.cells?.[cellIdx];
|
|
859
|
+
const colLen = Array.isArray(col) ? col.length : 0;
|
|
860
|
+
if (colLen > 1) {
|
|
861
|
+
return {
|
|
862
|
+
mode: "column",
|
|
863
|
+
blockIndex,
|
|
864
|
+
canMoveUp: blockIndex > 0,
|
|
865
|
+
canMoveDown: blockIndex < colLen - 1
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
if (colLen === 1 && (isRootContentRow(row) || isSingleBlockInRow(row))) {
|
|
869
|
+
return {
|
|
870
|
+
mode: "row",
|
|
871
|
+
canMoveUp: rowIndex > 0,
|
|
872
|
+
canMoveDown: rowIndex < rows.length - 1
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
if (colLen === 1) {
|
|
876
|
+
return {
|
|
877
|
+
mode: "row",
|
|
878
|
+
canMoveUp: rowIndex > 0,
|
|
879
|
+
canMoveDown: rowIndex < rows.length - 1
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
mode: "column",
|
|
884
|
+
blockIndex,
|
|
885
|
+
canMoveUp: false,
|
|
886
|
+
canMoveDown: false
|
|
887
|
+
};
|
|
888
|
+
}
|
|
830
889
|
|
|
831
890
|
// src/lib/htmlUtils.ts
|
|
832
891
|
function escHtmlAttr(s) {
|
|
@@ -835,6 +894,11 @@ function escHtmlAttr(s) {
|
|
|
835
894
|
function escHtml(s) {
|
|
836
895
|
return String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
837
896
|
}
|
|
897
|
+
function textBlockContentToHtml(content) {
|
|
898
|
+
const raw = typeof content === "string" ? content : "";
|
|
899
|
+
if (/^\s*<[^>]+>/.test(raw)) return raw;
|
|
900
|
+
return escHtml(raw).replace(/\r?\n/g, "<br/>");
|
|
901
|
+
}
|
|
838
902
|
function utf8ToBase64(raw) {
|
|
839
903
|
const bytes = new TextEncoder().encode(raw);
|
|
840
904
|
let bin = "";
|
|
@@ -1175,8 +1239,7 @@ function blockToHtml(cb) {
|
|
|
1175
1239
|
case "text": {
|
|
1176
1240
|
const shell = emailSurfaceBgCss(p);
|
|
1177
1241
|
const inner = `font-size:${lenPx(p.fontSize)};color:${p.color};text-align:${p.align};font-weight:${p.fontWeight || 400};font-style:${p.italic ? "italic" : "normal"};text-decoration:${p.underline ? "underline" : "none"};line-height:${lh(p.lineHeight)};letter-spacing:${lenPx(p.letterSpacing)};font-family:${p.fontFamily || "Georgia,serif"}`;
|
|
1178
|
-
const
|
|
1179
|
-
const body = /^\s*<[^>]+>/.test(content) ? content : escHtml(content).replace(/\r?\n/g, "<br/>");
|
|
1242
|
+
const body = textBlockContentToHtml(p.content);
|
|
1180
1243
|
return `<div style="${pd(p.padding)};${marginCss()}${shell}"><div style="${inner}">${body}</div></div>`;
|
|
1181
1244
|
}
|
|
1182
1245
|
case "html": {
|
|
@@ -3021,7 +3084,7 @@ function ContentBlock({ block, selected, onClick, preview, C }) {
|
|
|
3021
3084
|
"div",
|
|
3022
3085
|
{
|
|
3023
3086
|
style: { fontSize: p.fontSize, color: p.color, textAlign: p.align, fontWeight: p.fontWeight || (p.bold ? 700 : 400), fontStyle: p.italic ? "italic" : "normal", textDecoration: p.underline ? "underline" : "none", lineHeight: p.lineHeight || 1.65, letterSpacing: `${p.letterSpacing || 0}px`, fontFamily: p.fontFamily || "Georgia,serif" },
|
|
3024
|
-
dangerouslySetInnerHTML: { __html: p.content }
|
|
3087
|
+
dangerouslySetInnerHTML: { __html: textBlockContentToHtml(p.content) }
|
|
3025
3088
|
}
|
|
3026
3089
|
)
|
|
3027
3090
|
);
|
|
@@ -7440,50 +7503,64 @@ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
|
7440
7503
|
function ContextMenu({ x, y, items, onClose, C }) {
|
|
7441
7504
|
const ref = useRef4(null);
|
|
7442
7505
|
useEffect5(() => {
|
|
7443
|
-
const
|
|
7444
|
-
|
|
7445
|
-
|
|
7506
|
+
const onPointerDown = (ev) => {
|
|
7507
|
+
if (ref.current?.contains(ev.target)) return;
|
|
7508
|
+
onClose();
|
|
7509
|
+
};
|
|
7510
|
+
const onKeyDown = (ev) => {
|
|
7511
|
+
if (ev.key === "Escape") onClose();
|
|
7512
|
+
};
|
|
7513
|
+
window.addEventListener("pointerdown", onPointerDown);
|
|
7514
|
+
window.addEventListener("keydown", onKeyDown);
|
|
7446
7515
|
return () => {
|
|
7447
|
-
window.removeEventListener("
|
|
7448
|
-
window.removeEventListener("keydown",
|
|
7516
|
+
window.removeEventListener("pointerdown", onPointerDown);
|
|
7517
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
7449
7518
|
};
|
|
7450
7519
|
}, [onClose]);
|
|
7451
|
-
return /* @__PURE__ */ jsx8(
|
|
7452
|
-
"
|
|
7520
|
+
return /* @__PURE__ */ jsx8(
|
|
7521
|
+
"div",
|
|
7453
7522
|
{
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7523
|
+
ref,
|
|
7524
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
7525
|
+
style: { position: "fixed", left: x, top: y, zIndex: 9999, background: C.sidebar, border: `1px solid ${C.border}`, borderRadius: 7, padding: "4px 0", boxShadow: `0 8px 32px rgba(0,0,0,.4)`, minWidth: 160 },
|
|
7526
|
+
children: items.map((item, i) => item.sep ? /* @__PURE__ */ jsx8("div", { style: { height: 1, background: C.border, margin: "3px 0" } }, i) : /* @__PURE__ */ jsxs6(
|
|
7527
|
+
"button",
|
|
7528
|
+
{
|
|
7529
|
+
type: "button",
|
|
7530
|
+
disabled: !!item.disabled,
|
|
7531
|
+
onClick: () => {
|
|
7532
|
+
if (item.disabled) return;
|
|
7533
|
+
item.action();
|
|
7534
|
+
onClose();
|
|
7535
|
+
},
|
|
7536
|
+
style: {
|
|
7537
|
+
display: "block",
|
|
7538
|
+
width: "100%",
|
|
7539
|
+
background: "none",
|
|
7540
|
+
border: "none",
|
|
7541
|
+
padding: "7px 14px",
|
|
7542
|
+
textAlign: "left",
|
|
7543
|
+
cursor: item.disabled ? "not-allowed" : "pointer",
|
|
7544
|
+
fontSize: 12,
|
|
7545
|
+
color: item.disabled ? C.muted : item.danger ? C.danger : C.text,
|
|
7546
|
+
fontWeight: item.bold ? 700 : 400,
|
|
7547
|
+
opacity: item.disabled ? 0.45 : 1
|
|
7548
|
+
},
|
|
7549
|
+
onMouseEnter: (e) => {
|
|
7550
|
+
if (!item.disabled) e.currentTarget.style.background = C.surface;
|
|
7551
|
+
},
|
|
7552
|
+
onMouseLeave: (e) => {
|
|
7553
|
+
e.currentTarget.style.background = "none";
|
|
7554
|
+
},
|
|
7555
|
+
children: [
|
|
7556
|
+
item.icon && /* @__PURE__ */ jsx8("span", { style: { marginRight: 8 }, children: item.icon }),
|
|
7557
|
+
item.label
|
|
7558
|
+
]
|
|
7559
|
+
},
|
|
7560
|
+
i
|
|
7561
|
+
))
|
|
7562
|
+
}
|
|
7563
|
+
);
|
|
7487
7564
|
}
|
|
7488
7565
|
|
|
7489
7566
|
// src/editor/overlays/ConfirmDialog.tsx
|
|
@@ -8087,10 +8164,27 @@ var ReactEmailEditorComponent = forwardRef(
|
|
|
8087
8164
|
else dropContent(rowId, cellIdx, { kind: "duplicate", fromIdx: contentIdx });
|
|
8088
8165
|
}
|
|
8089
8166
|
};
|
|
8167
|
+
const shiftBlockFromMenu = (rowId, cellIdx, ci, dir, inner = null) => {
|
|
8168
|
+
const blockIndex = inner ? inner.contentIdx : ci;
|
|
8169
|
+
const plan = resolveBlockMovePlan(rowsRef.current, rowId, cellIdx, blockIndex, inner);
|
|
8170
|
+
if (!plan) return;
|
|
8171
|
+
if (dir < 0 && !plan.canMoveUp || dir > 0 && !plan.canMoveDown) return;
|
|
8172
|
+
if (plan.mode === "row") {
|
|
8173
|
+
moveRow(rowId, dir);
|
|
8174
|
+
return;
|
|
8175
|
+
}
|
|
8176
|
+
moveContent(rowId, cellIdx, plan.blockIndex, dir, inner);
|
|
8177
|
+
};
|
|
8090
8178
|
const handleContextMenu = (e, rowId, cellIdx, ci, inner = null) => {
|
|
8179
|
+
e.preventDefault();
|
|
8180
|
+
e.stopPropagation();
|
|
8181
|
+
const blockIndex = inner ? inner.contentIdx : ci;
|
|
8182
|
+
const plan = resolveBlockMovePlan(rowsRef.current, rowId, cellIdx, blockIndex, inner);
|
|
8183
|
+
const canMoveUp = plan?.canMoveUp ?? false;
|
|
8184
|
+
const canMoveDown = plan?.canMoveDown ?? false;
|
|
8091
8185
|
setCtxMenu({ x: e.clientX, y: e.clientY, items: [
|
|
8092
|
-
{ icon: "\u2191", label: "Move Up", action: () =>
|
|
8093
|
-
{ icon: "\u2193", label: "Move Down", action: () =>
|
|
8186
|
+
{ icon: "\u2191", label: "Move Up", disabled: !canMoveUp, action: () => shiftBlockFromMenu(rowId, cellIdx, ci, -1, inner) },
|
|
8187
|
+
{ icon: "\u2193", label: "Move Down", disabled: !canMoveDown, action: () => shiftBlockFromMenu(rowId, cellIdx, ci, 1, inner) },
|
|
8094
8188
|
{ sep: true },
|
|
8095
8189
|
{ icon: "\u29C9", label: "Duplicate", action: () => dropContent(rowId, cellIdx, inner ? { kind: "duplicate", fromIdx: inner.contentIdx, fromNested: { parentBlockIdx: inner.parentBlockIdx, innerCellIdx: inner.cellIdx } } : { kind: "duplicate", fromIdx: ci }) },
|
|
8096
8190
|
{ sep: true },
|
|
@@ -8100,17 +8194,14 @@ var ReactEmailEditorComponent = forwardRef(
|
|
|
8100
8194
|
const handleLayoutContextMenu = (e, rowId) => {
|
|
8101
8195
|
e.preventDefault();
|
|
8102
8196
|
e.stopPropagation();
|
|
8103
|
-
const
|
|
8197
|
+
const currentRows = rowsRef.current;
|
|
8198
|
+
const ri = currentRows.findIndex((r) => r.id === rowId);
|
|
8104
8199
|
setSelectedRowId(rowId);
|
|
8105
8200
|
setSelContentId(null);
|
|
8106
8201
|
setSelMeta(null);
|
|
8107
8202
|
setCtxMenu({ x: e.clientX, y: e.clientY, items: [
|
|
8108
|
-
{ icon: "\u2191", label: "Move row up", disabled: ri <= 0, action: () =>
|
|
8109
|
-
|
|
8110
|
-
} },
|
|
8111
|
-
{ icon: "\u2193", label: "Move row down", disabled: ri < 0 || ri >= rows.length - 1, action: () => {
|
|
8112
|
-
if (ri >= 0 && ri < rows.length - 1) moveRow(rowId, 1);
|
|
8113
|
-
} },
|
|
8203
|
+
{ icon: "\u2191", label: "Move row up", disabled: ri <= 0, action: () => moveRow(rowId, -1) },
|
|
8204
|
+
{ icon: "\u2193", label: "Move row down", disabled: ri < 0 || ri >= currentRows.length - 1, action: () => moveRow(rowId, 1) },
|
|
8114
8205
|
{ sep: true },
|
|
8115
8206
|
{ icon: "\u29C9", label: "Duplicate row", action: () => dupRow(rowId) },
|
|
8116
8207
|
{ sep: true },
|