@mjml-agent-editor/editor 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/canvas.cjs +156 -143
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.js +157 -144
- package/dist/canvas.js.map +1 -1
- package/dist/{chunk-K7KQC7LX.js → chunk-PXFZVLNQ.js} +1 -1
- package/dist/chunk-PXFZVLNQ.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-K7KQC7LX.js.map +0 -1
package/README.md
CHANGED
|
@@ -116,7 +116,8 @@ const images: ImageLibrary = {
|
|
|
116
116
|
|
|
117
117
|
`quota` (`{ used, limit }`) is shown in the picker and stops uploads when reached. Leave
|
|
118
118
|
`remove` out for a read-only gallery — a host that caps uploads but cannot reclaim the
|
|
119
|
-
storage would otherwise be handing out a way round its own cap.
|
|
119
|
+
storage would otherwise be handing out a way round its own cap. Leave `upload` out, and set
|
|
120
|
+
`allowUrl: false`, for a gallery of your own images and nothing else.
|
|
120
121
|
|
|
121
122
|
Without a library the picker takes URLs only. The editor never inlines a file as base64:
|
|
122
123
|
most mail clients block `data:` images, and every agent turn would pay for the bytes.
|
package/dist/canvas.cjs
CHANGED
|
@@ -1124,7 +1124,8 @@ function ImagePicker({
|
|
|
1124
1124
|
const accepts = (file) => matchesAccept(file, library?.accept);
|
|
1125
1125
|
const full = quota !== void 0 && quota.used >= quota.limit;
|
|
1126
1126
|
const upload = async (files) => {
|
|
1127
|
-
|
|
1127
|
+
const send = library?.upload?.bind(library);
|
|
1128
|
+
if (!send || !files?.length) return;
|
|
1128
1129
|
const mine = session.current;
|
|
1129
1130
|
const list = Array.from(files).filter((f) => {
|
|
1130
1131
|
if (accepts(f)) return true;
|
|
@@ -1133,7 +1134,7 @@ function ImagePicker({
|
|
|
1133
1134
|
});
|
|
1134
1135
|
if (!list.length) return;
|
|
1135
1136
|
setPending((n) => n + list.length);
|
|
1136
|
-
const results = await Promise.allSettled(list.map(async (f) =>
|
|
1137
|
+
const results = await Promise.allSettled(list.map(async (f) => send(f)));
|
|
1137
1138
|
setPending((n) => n - list.length);
|
|
1138
1139
|
for (const r of results) {
|
|
1139
1140
|
if (r.status === "rejected") {
|
|
@@ -1168,155 +1169,163 @@ function ImagePicker({
|
|
|
1168
1169
|
setDragging(false);
|
|
1169
1170
|
if (!full) void upload(e.dataTransfer.files);
|
|
1170
1171
|
};
|
|
1171
|
-
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
"
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
},
|
|
1184
|
-
|
|
1185
|
-
onDrop,
|
|
1186
|
-
className: cn(
|
|
1187
|
-
"flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed px-6 py-8 text-center transition-colors",
|
|
1188
|
-
dragging ? "border-brand bg-brand-soft" : "border-panel-border bg-panel-elevated"
|
|
1189
|
-
),
|
|
1190
|
-
children: [
|
|
1191
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.ImageUp, { className: "size-8 text-panel-muted-fg" }),
|
|
1192
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
|
|
1193
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: labels.imagePickerDrop }),
|
|
1194
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1195
|
-
Button,
|
|
1196
|
-
{
|
|
1197
|
-
type: "button",
|
|
1198
|
-
size: "sm",
|
|
1199
|
-
className: "bg-brand text-brand-fg hover:bg-brand/90",
|
|
1200
|
-
onClick: () => fileInput.current?.click(),
|
|
1201
|
-
children: labels.imagePickerBrowse
|
|
1202
|
-
}
|
|
1203
|
-
)
|
|
1204
|
-
] }),
|
|
1205
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1206
|
-
"input",
|
|
1207
|
-
{
|
|
1208
|
-
ref: fileInput,
|
|
1209
|
-
type: "file",
|
|
1210
|
-
accept: library.accept ?? "image/*",
|
|
1211
|
-
multiple: true,
|
|
1212
|
-
hidden: true,
|
|
1213
|
-
onChange: (e) => {
|
|
1214
|
-
void upload(e.target.files);
|
|
1215
|
-
e.target.value = "";
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
)
|
|
1219
|
-
]
|
|
1220
|
-
}
|
|
1221
|
-
)),
|
|
1222
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1223
|
-
"form",
|
|
1224
|
-
{
|
|
1225
|
-
className: "flex gap-2",
|
|
1226
|
-
onSubmit: (e) => {
|
|
1227
|
-
e.preventDefault();
|
|
1228
|
-
const value = url.trim();
|
|
1229
|
-
if (value) onSelect(value);
|
|
1230
|
-
},
|
|
1231
|
-
children: [
|
|
1232
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative flex-1", children: [
|
|
1233
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Link2, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-panel-muted-fg" }),
|
|
1234
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1235
|
-
Input,
|
|
1236
|
-
{
|
|
1237
|
-
value: url,
|
|
1238
|
-
onChange: (e) => setUrl(e.target.value),
|
|
1239
|
-
placeholder: labels.imagePickerUrlPlaceholder,
|
|
1240
|
-
className: "h-9 border-panel-border bg-panel-elevated pl-8 text-panel-fg placeholder:text-panel-muted-fg"
|
|
1241
|
-
}
|
|
1242
|
-
)
|
|
1243
|
-
] }),
|
|
1244
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1245
|
-
Button,
|
|
1246
|
-
{
|
|
1247
|
-
type: "submit",
|
|
1248
|
-
size: "lg",
|
|
1249
|
-
variant: "outline",
|
|
1250
|
-
disabled: !url.trim(),
|
|
1251
|
-
className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
|
|
1252
|
-
children: labels.imagePickerUseUrl
|
|
1253
|
-
}
|
|
1254
|
-
)
|
|
1255
|
-
]
|
|
1256
|
-
}
|
|
1257
|
-
),
|
|
1258
|
-
library && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { children: [
|
|
1259
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
|
|
1260
|
-
loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
|
|
1261
|
-
Array.from({ length: pending }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1262
|
-
"li",
|
|
1172
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1173
|
+
DialogContent,
|
|
1174
|
+
{
|
|
1175
|
+
onDragOver: (e) => e.preventDefault(),
|
|
1176
|
+
onDrop: (e) => e.preventDefault(),
|
|
1177
|
+
className: "editor-dark gap-0 overflow-hidden border border-panel-border bg-panel p-0 text-panel-fg ring-0 sm:max-w-3xl",
|
|
1178
|
+
children: [
|
|
1179
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(DialogHeader, { className: "border-b border-panel-border px-5 py-4", children: [
|
|
1180
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogTitle, { children: labels.imagePickerTitle }),
|
|
1181
|
+
quota && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogDescription, { className: "text-panel-muted-fg", children: labels.imagePickerQuota(quota.used, quota.limit) })
|
|
1182
|
+
] }),
|
|
1183
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex max-h-[70vh] flex-col gap-4 overflow-y-auto p-5", children: [
|
|
1184
|
+
library?.upload && (full ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "rounded-lg border border-panel-border bg-panel-elevated px-4 py-3 text-sm text-panel-muted-fg", children: labels.imagePickerQuotaFull }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1185
|
+
"div",
|
|
1263
1186
|
{
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1187
|
+
onDragOver: (e) => {
|
|
1188
|
+
e.preventDefault();
|
|
1189
|
+
setDragging(true);
|
|
1190
|
+
},
|
|
1191
|
+
onDragLeave: () => setDragging(false),
|
|
1192
|
+
onDrop,
|
|
1193
|
+
className: cn(
|
|
1194
|
+
"flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed px-6 py-8 text-center transition-colors",
|
|
1195
|
+
dragging ? "border-brand bg-brand-soft" : "border-panel-border bg-panel-elevated"
|
|
1196
|
+
),
|
|
1197
|
+
children: [
|
|
1198
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.ImageUp, { className: "size-8 text-panel-muted-fg" }),
|
|
1199
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
|
|
1200
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: labels.imagePickerDrop }),
|
|
1201
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1202
|
+
Button,
|
|
1203
|
+
{
|
|
1204
|
+
type: "button",
|
|
1205
|
+
size: "sm",
|
|
1206
|
+
className: "bg-brand text-brand-fg hover:bg-brand/90",
|
|
1207
|
+
onClick: () => fileInput.current?.click(),
|
|
1208
|
+
children: labels.imagePickerBrowse
|
|
1209
|
+
}
|
|
1210
|
+
)
|
|
1211
|
+
] }),
|
|
1212
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1213
|
+
"input",
|
|
1214
|
+
{
|
|
1215
|
+
ref: fileInput,
|
|
1216
|
+
type: "file",
|
|
1217
|
+
accept: library.accept ?? "image/*",
|
|
1218
|
+
multiple: true,
|
|
1219
|
+
hidden: true,
|
|
1220
|
+
onChange: (e) => {
|
|
1221
|
+
void upload(e.target.files);
|
|
1222
|
+
e.target.value = "";
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
)
|
|
1226
|
+
]
|
|
1227
|
+
}
|
|
1268
1228
|
)),
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1284
|
-
"img",
|
|
1229
|
+
library?.allowUrl !== false && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1230
|
+
"form",
|
|
1231
|
+
{
|
|
1232
|
+
className: "flex gap-2",
|
|
1233
|
+
onSubmit: (e) => {
|
|
1234
|
+
e.preventDefault();
|
|
1235
|
+
const value = url.trim();
|
|
1236
|
+
if (value) onSelect(value);
|
|
1237
|
+
},
|
|
1238
|
+
children: [
|
|
1239
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative flex-1", children: [
|
|
1240
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Link2, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-panel-muted-fg" }),
|
|
1241
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1242
|
+
Input,
|
|
1285
1243
|
{
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
className: "
|
|
1244
|
+
value: url,
|
|
1245
|
+
onChange: (e) => setUrl(e.target.value),
|
|
1246
|
+
placeholder: labels.imagePickerUrlPlaceholder,
|
|
1247
|
+
className: "h-9 border-panel-border bg-panel-elevated pl-8 text-panel-fg placeholder:text-panel-muted-fg"
|
|
1290
1248
|
}
|
|
1291
1249
|
)
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1250
|
+
] }),
|
|
1251
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1252
|
+
Button,
|
|
1253
|
+
{
|
|
1254
|
+
type: "submit",
|
|
1255
|
+
size: "lg",
|
|
1256
|
+
variant: "outline",
|
|
1257
|
+
disabled: !url.trim(),
|
|
1258
|
+
className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
|
|
1259
|
+
children: labels.imagePickerUseUrl
|
|
1260
|
+
}
|
|
1261
|
+
)
|
|
1262
|
+
]
|
|
1263
|
+
}
|
|
1264
|
+
),
|
|
1265
|
+
library && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { children: [
|
|
1266
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
|
|
1267
|
+
loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
|
|
1268
|
+
Array.from({ length: pending }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1269
|
+
"li",
|
|
1298
1270
|
{
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1271
|
+
className: "flex aspect-[4/3] items-center justify-center rounded-lg border border-panel-border bg-panel-elevated",
|
|
1272
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, {})
|
|
1273
|
+
},
|
|
1274
|
+
`pending-${i}`
|
|
1275
|
+
)),
|
|
1276
|
+
images.map((image) => {
|
|
1277
|
+
const selected = image.url === currentUrl;
|
|
1278
|
+
const confirming = confirmId === image.id;
|
|
1279
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "group relative", children: [
|
|
1280
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1281
|
+
"button",
|
|
1282
|
+
{
|
|
1283
|
+
type: "button",
|
|
1284
|
+
onClick: () => onSelect(image.url),
|
|
1285
|
+
title: image.name,
|
|
1286
|
+
className: cn(
|
|
1287
|
+
"block aspect-[4/3] w-full overflow-hidden rounded-lg border bg-black/25 transition outline-none focus-visible:ring-2 focus-visible:ring-brand",
|
|
1288
|
+
selected ? "border-brand ring-2 ring-brand" : "border-panel-border hover:border-panel-muted-fg"
|
|
1289
|
+
),
|
|
1290
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1291
|
+
"img",
|
|
1292
|
+
{
|
|
1293
|
+
src: image.url,
|
|
1294
|
+
alt: image.name ?? "",
|
|
1295
|
+
loading: "lazy",
|
|
1296
|
+
className: "size-full object-contain"
|
|
1297
|
+
}
|
|
1298
|
+
)
|
|
1299
|
+
}
|
|
1307
1300
|
),
|
|
1308
|
-
children:
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1301
|
+
selected && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pointer-events-none absolute top-1.5 left-1.5 flex size-5 items-center justify-center rounded-full bg-brand text-brand-fg", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Check, { className: "size-3" }) }),
|
|
1302
|
+
image.name && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "pointer-events-none absolute inset-x-0 bottom-0 truncate rounded-b-lg bg-black/60 px-2 py-1 text-[11px] text-white opacity-0 transition group-hover:opacity-100", children: image.name }),
|
|
1303
|
+
library.remove && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1304
|
+
"button",
|
|
1305
|
+
{
|
|
1306
|
+
type: "button",
|
|
1307
|
+
onClick: () => void remove(image),
|
|
1308
|
+
onBlur: () => confirming && setConfirmId(null),
|
|
1309
|
+
title: confirming ? labels.imagePickerConfirmDelete : labels.imagePickerDelete,
|
|
1310
|
+
"aria-label": labels.imagePickerDelete,
|
|
1311
|
+
className: cn(
|
|
1312
|
+
"absolute top-1.5 right-1.5 flex h-6 items-center gap-1 rounded-md px-1.5 text-xs text-white transition focus-visible:opacity-100",
|
|
1313
|
+
confirming ? "bg-red-600 opacity-100" : "bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80"
|
|
1314
|
+
),
|
|
1315
|
+
children: [
|
|
1316
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Trash2, { className: "size-3.5" }),
|
|
1317
|
+
confirming && labels.imagePickerConfirmDelete
|
|
1318
|
+
]
|
|
1319
|
+
}
|
|
1320
|
+
)
|
|
1321
|
+
] }, image.id);
|
|
1322
|
+
})
|
|
1323
|
+
] })
|
|
1324
|
+
] })
|
|
1316
1325
|
] })
|
|
1317
|
-
]
|
|
1318
|
-
|
|
1319
|
-
|
|
1326
|
+
]
|
|
1327
|
+
}
|
|
1328
|
+
) });
|
|
1320
1329
|
}
|
|
1321
1330
|
function matchesAccept(file, accept) {
|
|
1322
1331
|
if (!accept) return file.type.startsWith("image/");
|
|
@@ -4665,6 +4674,10 @@ function EmailEditor({
|
|
|
4665
4674
|
// app.grapesjs.com and leave a session-storage entry, which a host would then have
|
|
4666
4675
|
// to disclose and, for the entry, ask consent for.
|
|
4667
4676
|
telemetry: false,
|
|
4677
|
+
// Font Awesome from cdnjs by default, a third party a host would have to name in its
|
|
4678
|
+
// privacy policy. Nothing we show uses it: GrapesJS draws its toolbar and layer icons
|
|
4679
|
+
// as SVG, and its icon-font panels and style manager are not rendered here.
|
|
4680
|
+
cssIcons: "",
|
|
4668
4681
|
canvasCss: CANVAS_CSS,
|
|
4669
4682
|
fromElement: false,
|
|
4670
4683
|
panels: { defaults: [] },
|