@mjml-agent-editor/editor 0.1.12 → 0.1.14
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 +46 -0
- package/dist/canvas.cjs +1102 -119
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.d.cts +2 -0
- package/dist/canvas.d.ts +2 -0
- package/dist/canvas.js +936 -83
- package/dist/canvas.js.map +1 -1
- package/dist/{chunk-4N2OJT22.js → chunk-SR3NM6VC.js} +129 -31
- package/dist/chunk-SR3NM6VC.js.map +1 -0
- package/dist/index.cjs +87 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +3 -1
- package/package.json +7 -4
- package/src/components/editor/editor-theme.css +29 -0
- package/dist/chunk-4N2OJT22.js.map +0 -1
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "./chunk-V6RR4VRD.js";
|
|
19
19
|
|
|
20
20
|
// src/stores.tsx
|
|
21
|
-
import { createContext, useContext, useMemo } from "react";
|
|
21
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
22
22
|
|
|
23
23
|
// src/labels.ts
|
|
24
24
|
var DEFAULT_LABELS = {
|
|
@@ -49,6 +49,29 @@ var DEFAULT_LABELS = {
|
|
|
49
49
|
rteBackgroundColor: "Background color",
|
|
50
50
|
documentLoadFailed: "Failed to load the document.",
|
|
51
51
|
documentSaveFailed: "Failed to save changes.",
|
|
52
|
+
retry: "Retry",
|
|
53
|
+
chooseImage: "Choose image",
|
|
54
|
+
imagePickerTitle: "Choose an image",
|
|
55
|
+
imagePickerDrop: "Drag images here, or",
|
|
56
|
+
imagePickerBrowse: "Browse files",
|
|
57
|
+
imagePickerUrlPlaceholder: "\u2026or paste an image URL",
|
|
58
|
+
imagePickerUseUrl: "Use URL",
|
|
59
|
+
imagePickerGallery: "Your images",
|
|
60
|
+
imagePickerEmpty: "No images yet \u2014 the ones you upload will be kept here.",
|
|
61
|
+
imagePickerQuota: (used, limit) => `${used} of ${limit} uploads used`,
|
|
62
|
+
imagePickerQuotaFull: "Upload limit reached \u2014 pick an image below or paste a URL.",
|
|
63
|
+
imagePickerDelete: "Delete image",
|
|
64
|
+
imagePickerConfirmDelete: "Click again to delete",
|
|
65
|
+
imagePickerLoadFailed: "Failed to load your images.",
|
|
66
|
+
imageUploadFailed: "Failed to upload the image.",
|
|
67
|
+
imageDeleteFailed: "Failed to delete the image.",
|
|
68
|
+
imagePickerUnsupported: (fileName) => `${fileName} is not a supported image type.`,
|
|
69
|
+
imageFieldRemove: "Remove image",
|
|
70
|
+
colorPickerOpen: "Pick a color",
|
|
71
|
+
colorPickerEyedropper: "Pick from the screen",
|
|
72
|
+
colorPickerDocument: "In this email",
|
|
73
|
+
colorPickerPresets: "Presets",
|
|
74
|
+
colorPickerTransparent: "Transparent",
|
|
52
75
|
commentOnSection: "Comment on the whole section",
|
|
53
76
|
closeComments: "Close",
|
|
54
77
|
noCommentsYet: "No comments yet \u2014 add the first one.",
|
|
@@ -90,13 +113,27 @@ function mergeLabels(overrides) {
|
|
|
90
113
|
import { jsx } from "react/jsx-runtime";
|
|
91
114
|
var StoreContext = createContext(null);
|
|
92
115
|
var LabelContext = createContext(null);
|
|
116
|
+
var TurnContext = createContext({
|
|
117
|
+
running: false,
|
|
118
|
+
setRunning: () => {
|
|
119
|
+
},
|
|
120
|
+
locked: false,
|
|
121
|
+
setLocked: () => {
|
|
122
|
+
}
|
|
123
|
+
});
|
|
93
124
|
function EditorStoreProvider({
|
|
94
125
|
stores,
|
|
95
126
|
labels,
|
|
96
127
|
children
|
|
97
128
|
}) {
|
|
98
129
|
const merged = useMemo(() => mergeLabels(labels), [labels]);
|
|
99
|
-
|
|
130
|
+
const [running, setRunning] = useState(false);
|
|
131
|
+
const [locked, setLocked] = useState(false);
|
|
132
|
+
const turn = useMemo(() => ({ running, setRunning, locked, setLocked }), [running, locked]);
|
|
133
|
+
return /* @__PURE__ */ jsx(StoreContext.Provider, { value: stores, children: /* @__PURE__ */ jsx(LabelContext.Provider, { value: merged, children: /* @__PURE__ */ jsx(TurnContext.Provider, { value: turn, children }) }) });
|
|
134
|
+
}
|
|
135
|
+
function useAgentTurn() {
|
|
136
|
+
return useContext(TurnContext);
|
|
100
137
|
}
|
|
101
138
|
function useLabels() {
|
|
102
139
|
return useContext(LabelContext) ?? mergeLabels();
|
|
@@ -116,12 +153,15 @@ function useDocumentStore() {
|
|
|
116
153
|
function useCommentStore() {
|
|
117
154
|
return useStores().comments;
|
|
118
155
|
}
|
|
156
|
+
function useImageLibrary() {
|
|
157
|
+
return useStores().images;
|
|
158
|
+
}
|
|
119
159
|
|
|
120
160
|
// src/index.ts
|
|
121
161
|
import { STARTER_MJML } from "@mjml-agent-editor/core";
|
|
122
162
|
|
|
123
163
|
// src/components/editor/EditorHeader.tsx
|
|
124
|
-
import { useEffect, useState } from "react";
|
|
164
|
+
import { useEffect, useState as useState2 } from "react";
|
|
125
165
|
import { toast } from "sonner";
|
|
126
166
|
import {
|
|
127
167
|
Undo2,
|
|
@@ -420,6 +460,22 @@ function DialogTitle({ className, ...props }) {
|
|
|
420
460
|
}
|
|
421
461
|
);
|
|
422
462
|
}
|
|
463
|
+
function DialogDescription({
|
|
464
|
+
className,
|
|
465
|
+
...props
|
|
466
|
+
}) {
|
|
467
|
+
return /* @__PURE__ */ jsx6(
|
|
468
|
+
DialogPrimitive.Description,
|
|
469
|
+
{
|
|
470
|
+
"data-slot": "dialog-description",
|
|
471
|
+
className: cn(
|
|
472
|
+
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
473
|
+
className
|
|
474
|
+
),
|
|
475
|
+
...props
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
}
|
|
423
479
|
|
|
424
480
|
// src/components/editor/EditorHeader.tsx
|
|
425
481
|
import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -465,17 +521,18 @@ function EditorHeader({
|
|
|
465
521
|
}) {
|
|
466
522
|
const documents = useDocumentStore();
|
|
467
523
|
const labels = useLabels();
|
|
468
|
-
const [name, setName] =
|
|
469
|
-
const [state, setState] =
|
|
524
|
+
const [name, setName] = useState2("");
|
|
525
|
+
const [state, setState] = useState2({
|
|
470
526
|
device: "Desktop",
|
|
471
527
|
canUndo: false,
|
|
472
528
|
canRedo: false,
|
|
473
529
|
saveStatus: "idle",
|
|
474
530
|
zoom: 100,
|
|
475
|
-
contentWidth: "600px"
|
|
531
|
+
contentWidth: "600px",
|
|
532
|
+
readOnly: false
|
|
476
533
|
});
|
|
477
|
-
const [code, setCode] =
|
|
478
|
-
const [preview, setPreview] =
|
|
534
|
+
const [code, setCode] = useState2({ mjml: "", html: "" });
|
|
535
|
+
const [preview, setPreview] = useState2("");
|
|
479
536
|
useEffect(() => {
|
|
480
537
|
documents.get(docId).then((d) => setName(d.name)).catch(() => {
|
|
481
538
|
});
|
|
@@ -569,6 +626,7 @@ function EditorHeader({
|
|
|
569
626
|
{
|
|
570
627
|
variant: "ghost",
|
|
571
628
|
size: "sm",
|
|
629
|
+
disabled: state.readOnly,
|
|
572
630
|
className: cn(darkGhost, "gap-1.5 rounded-md border border-panel-border max-xl:hidden"),
|
|
573
631
|
children: [
|
|
574
632
|
/* @__PURE__ */ jsx7("span", { className: "text-panel-muted-fg", children: labels.contentWidth }),
|
|
@@ -801,7 +859,7 @@ function EditorHeader({
|
|
|
801
859
|
// src/components/chat/ChatPanel.tsx
|
|
802
860
|
import { useChat } from "@ai-sdk/react";
|
|
803
861
|
import { DefaultChatTransport } from "ai";
|
|
804
|
-
import { useEffect as useEffect2, useRef, useState as
|
|
862
|
+
import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
|
|
805
863
|
import { toast as toast2 } from "sonner";
|
|
806
864
|
import {
|
|
807
865
|
Brain,
|
|
@@ -815,6 +873,27 @@ import {
|
|
|
815
873
|
ChevronRight
|
|
816
874
|
} from "lucide-react";
|
|
817
875
|
|
|
876
|
+
// src/lib/refusal.ts
|
|
877
|
+
function refusal(error) {
|
|
878
|
+
if (!(error instanceof Error)) return null;
|
|
879
|
+
const status = error.status;
|
|
880
|
+
const deliberate = typeof status === "number" ? status >= 400 && status < 500 : null;
|
|
881
|
+
if (deliberate === false) return null;
|
|
882
|
+
if (error.message.startsWith("{")) {
|
|
883
|
+
try {
|
|
884
|
+
const parsed = JSON.parse(error.message);
|
|
885
|
+
return typeof parsed.error === "string" && parsed.error.trim() !== "" ? parsed.error : null;
|
|
886
|
+
} catch {
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const message = error.message.trim();
|
|
891
|
+
return deliberate && message !== "" && !message.startsWith("<") ? error.message : null;
|
|
892
|
+
}
|
|
893
|
+
function saveToastId(docId) {
|
|
894
|
+
return `document-save-${docId}`;
|
|
895
|
+
}
|
|
896
|
+
|
|
818
897
|
// src/components/ui/message.tsx
|
|
819
898
|
import "react";
|
|
820
899
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
@@ -1199,16 +1278,6 @@ function toBlocks(parts) {
|
|
|
1199
1278
|
}
|
|
1200
1279
|
return blocks;
|
|
1201
1280
|
}
|
|
1202
|
-
function refusal(error) {
|
|
1203
|
-
const body = error instanceof Error ? error.message : "";
|
|
1204
|
-
if (!body.startsWith("{")) return null;
|
|
1205
|
-
try {
|
|
1206
|
-
const parsed = JSON.parse(body);
|
|
1207
|
-
return typeof parsed.error === "string" && parsed.error.trim() !== "" ? parsed.error : null;
|
|
1208
|
-
} catch {
|
|
1209
|
-
return null;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
1281
|
function ToolMarker({ tool }) {
|
|
1213
1282
|
const labels = useLabels();
|
|
1214
1283
|
const name = tool.type.slice(5);
|
|
@@ -1270,8 +1339,10 @@ function ChatPanel({
|
|
|
1270
1339
|
onSectionEditEnd
|
|
1271
1340
|
}) {
|
|
1272
1341
|
const labels = useLabels();
|
|
1273
|
-
const [input, setInput] =
|
|
1342
|
+
const [input, setInput] = useState3("");
|
|
1274
1343
|
const taRef = useRef(null);
|
|
1344
|
+
const sending = useRef(false);
|
|
1345
|
+
const [flushing, setFlushing] = useState3(false);
|
|
1275
1346
|
const times = useRef(/* @__PURE__ */ new Map());
|
|
1276
1347
|
const timeFor = (id) => {
|
|
1277
1348
|
if (!times.current.has(id)) {
|
|
@@ -1335,6 +1406,9 @@ function ChatPanel({
|
|
|
1335
1406
|
}
|
|
1336
1407
|
}, [messages, onLiveUpdate, onSectionEditStart, onSectionEditEnd]);
|
|
1337
1408
|
const busy = status === "submitted" || status === "streaming";
|
|
1409
|
+
const { setRunning, locked } = useAgentTurn();
|
|
1410
|
+
useEffect2(() => setRunning(busy || flushing), [busy, flushing, setRunning]);
|
|
1411
|
+
useEffect2(() => () => setRunning(false), [setRunning]);
|
|
1338
1412
|
const lastMsg = messages[messages.length - 1];
|
|
1339
1413
|
const toolRunning = lastMsg?.role === "assistant" && lastMsg.parts.some((p) => {
|
|
1340
1414
|
if (!p.type.startsWith("tool-")) return false;
|
|
@@ -1349,8 +1423,19 @@ function ChatPanel({
|
|
|
1349
1423
|
};
|
|
1350
1424
|
const send = async (text) => {
|
|
1351
1425
|
const trimmed = text.trim();
|
|
1352
|
-
if (!trimmed || busy) return;
|
|
1353
|
-
|
|
1426
|
+
if (!trimmed || busy || locked || sending.current) return;
|
|
1427
|
+
sending.current = true;
|
|
1428
|
+
setFlushing(true);
|
|
1429
|
+
try {
|
|
1430
|
+
await onBeforeSend();
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
console.error(error);
|
|
1433
|
+
toast2.error(refusal(error) ?? labels.documentSaveFailed, { id: saveToastId(docId) });
|
|
1434
|
+
return;
|
|
1435
|
+
} finally {
|
|
1436
|
+
sending.current = false;
|
|
1437
|
+
setFlushing(false);
|
|
1438
|
+
}
|
|
1354
1439
|
void sendMessage({ text: trimmed });
|
|
1355
1440
|
setInput("");
|
|
1356
1441
|
if (taRef.current) taRef.current.style.height = "auto";
|
|
@@ -1432,7 +1517,7 @@ function ChatPanel({
|
|
|
1432
1517
|
Button,
|
|
1433
1518
|
{
|
|
1434
1519
|
className: "mb-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
|
|
1435
|
-
disabled: busy,
|
|
1520
|
+
disabled: busy || flushing || locked,
|
|
1436
1521
|
onClick: () => void send(APPLY_COMMENTS_PROMPT),
|
|
1437
1522
|
children: [
|
|
1438
1523
|
/* @__PURE__ */ jsx14(Sparkles, {}),
|
|
@@ -1491,7 +1576,7 @@ function ChatPanel({
|
|
|
1491
1576
|
{
|
|
1492
1577
|
type: "submit",
|
|
1493
1578
|
size: "sm",
|
|
1494
|
-
disabled: busy || !input.trim(),
|
|
1579
|
+
disabled: busy || flushing || locked || !input.trim(),
|
|
1495
1580
|
className: "bg-brand text-brand-fg hover:bg-brand/90",
|
|
1496
1581
|
children: [
|
|
1497
1582
|
/* @__PURE__ */ jsx14(Send, {}),
|
|
@@ -1510,7 +1595,7 @@ function ChatPanel({
|
|
|
1510
1595
|
|
|
1511
1596
|
// src/components/comments/CanvasComments.tsx
|
|
1512
1597
|
import { useEditorMaybe } from "@grapesjs/react";
|
|
1513
|
-
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as
|
|
1598
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState4 } from "react";
|
|
1514
1599
|
import { Check as Check2, MessageSquarePlus, X as X2 } from "lucide-react";
|
|
1515
1600
|
import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1516
1601
|
var keyOf = (c) => c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;
|
|
@@ -1526,11 +1611,11 @@ function CanvasComments({
|
|
|
1526
1611
|
const labels = useLabels();
|
|
1527
1612
|
const editor = useEditorMaybe();
|
|
1528
1613
|
const containerRef = useRef2(null);
|
|
1529
|
-
const [comments, setComments] =
|
|
1530
|
-
const [positions, setPositions] =
|
|
1531
|
-
const [activeKey, setActiveKey] =
|
|
1532
|
-
const [composeInfo, setComposeInfo] =
|
|
1533
|
-
const [body, setBody] =
|
|
1614
|
+
const [comments, setComments] = useState4([]);
|
|
1615
|
+
const [positions, setPositions] = useState4({});
|
|
1616
|
+
const [activeKey, setActiveKey] = useState4(null);
|
|
1617
|
+
const [composeInfo, setComposeInfo] = useState4(null);
|
|
1618
|
+
const [body, setBody] = useState4("");
|
|
1534
1619
|
const reload = useCallback(() => {
|
|
1535
1620
|
commentStore.list(docId).then(setComments).catch(console.error);
|
|
1536
1621
|
}, [docId]);
|
|
@@ -1779,13 +1864,26 @@ export {
|
|
|
1779
1864
|
DEFAULT_LABELS,
|
|
1780
1865
|
mergeLabels,
|
|
1781
1866
|
EditorStoreProvider,
|
|
1867
|
+
useAgentTurn,
|
|
1782
1868
|
useLabels,
|
|
1783
1869
|
useDocumentStore,
|
|
1784
1870
|
useCommentStore,
|
|
1871
|
+
useImageLibrary,
|
|
1872
|
+
Popover,
|
|
1873
|
+
PopoverTrigger,
|
|
1874
|
+
PopoverAnchor,
|
|
1875
|
+
PopoverContent,
|
|
1876
|
+
Dialog,
|
|
1877
|
+
DialogContent,
|
|
1878
|
+
DialogHeader,
|
|
1879
|
+
DialogTitle,
|
|
1880
|
+
DialogDescription,
|
|
1785
1881
|
EditorHeader,
|
|
1882
|
+
refusal,
|
|
1883
|
+
saveToastId,
|
|
1786
1884
|
Markdown,
|
|
1787
1885
|
ChatPanel,
|
|
1788
1886
|
CanvasComments,
|
|
1789
1887
|
STARTER_MJML
|
|
1790
1888
|
};
|
|
1791
|
-
//# sourceMappingURL=chunk-
|
|
1889
|
+
//# sourceMappingURL=chunk-SR3NM6VC.js.map
|