@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/dist/canvas.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  useDocumentStore,
17
17
  useImageLibrary,
18
18
  useLabels
19
- } from "./chunk-K7KQC7LX.js";
19
+ } from "./chunk-PXFZVLNQ.js";
20
20
  import {
21
21
  Button,
22
22
  Input,
@@ -218,7 +218,8 @@ function ImagePicker({
218
218
  const accepts = (file) => matchesAccept(file, library?.accept);
219
219
  const full = quota !== void 0 && quota.used >= quota.limit;
220
220
  const upload = async (files) => {
221
- if (!library || !files?.length) return;
221
+ const send = library?.upload?.bind(library);
222
+ if (!send || !files?.length) return;
222
223
  const mine = session.current;
223
224
  const list = Array.from(files).filter((f) => {
224
225
  if (accepts(f)) return true;
@@ -227,7 +228,7 @@ function ImagePicker({
227
228
  });
228
229
  if (!list.length) return;
229
230
  setPending((n) => n + list.length);
230
- const results = await Promise.allSettled(list.map(async (f) => library.upload(f)));
231
+ const results = await Promise.allSettled(list.map(async (f) => send(f)));
231
232
  setPending((n) => n - list.length);
232
233
  for (const r of results) {
233
234
  if (r.status === "rejected") {
@@ -262,155 +263,163 @@ function ImagePicker({
262
263
  setDragging(false);
263
264
  if (!full) void upload(e.dataTransfer.files);
264
265
  };
265
- return /* @__PURE__ */ jsx2(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(DialogContent, { className: "editor-dark gap-0 overflow-hidden border border-panel-border bg-panel p-0 text-panel-fg ring-0 sm:max-w-3xl", children: [
266
- /* @__PURE__ */ jsxs(DialogHeader, { className: "border-b border-panel-border px-5 py-4", children: [
267
- /* @__PURE__ */ jsx2(DialogTitle, { children: labels.imagePickerTitle }),
268
- quota && /* @__PURE__ */ jsx2(DialogDescription, { className: "text-panel-muted-fg", children: labels.imagePickerQuota(quota.used, quota.limit) })
269
- ] }),
270
- /* @__PURE__ */ jsxs("div", { className: "flex max-h-[70vh] flex-col gap-4 overflow-y-auto p-5", children: [
271
- library && (full ? /* @__PURE__ */ jsx2("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__ */ jsxs(
272
- "div",
273
- {
274
- onDragOver: (e) => {
275
- e.preventDefault();
276
- setDragging(true);
277
- },
278
- onDragLeave: () => setDragging(false),
279
- onDrop,
280
- className: cn(
281
- "flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed px-6 py-8 text-center transition-colors",
282
- dragging ? "border-brand bg-brand-soft" : "border-panel-border bg-panel-elevated"
283
- ),
284
- children: [
285
- /* @__PURE__ */ jsx2(ImageUp, { className: "size-8 text-panel-muted-fg" }),
286
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
287
- /* @__PURE__ */ jsx2("span", { children: labels.imagePickerDrop }),
288
- /* @__PURE__ */ jsx2(
289
- Button,
290
- {
291
- type: "button",
292
- size: "sm",
293
- className: "bg-brand text-brand-fg hover:bg-brand/90",
294
- onClick: () => fileInput.current?.click(),
295
- children: labels.imagePickerBrowse
296
- }
297
- )
298
- ] }),
299
- /* @__PURE__ */ jsx2(
300
- "input",
301
- {
302
- ref: fileInput,
303
- type: "file",
304
- accept: library.accept ?? "image/*",
305
- multiple: true,
306
- hidden: true,
307
- onChange: (e) => {
308
- void upload(e.target.files);
309
- e.target.value = "";
310
- }
311
- }
312
- )
313
- ]
314
- }
315
- )),
316
- /* @__PURE__ */ jsxs(
317
- "form",
318
- {
319
- className: "flex gap-2",
320
- onSubmit: (e) => {
321
- e.preventDefault();
322
- const value = url.trim();
323
- if (value) onSelect(value);
324
- },
325
- children: [
326
- /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
327
- /* @__PURE__ */ jsx2(Link2, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-panel-muted-fg" }),
328
- /* @__PURE__ */ jsx2(
329
- Input,
330
- {
331
- value: url,
332
- onChange: (e) => setUrl(e.target.value),
333
- placeholder: labels.imagePickerUrlPlaceholder,
334
- className: "h-9 border-panel-border bg-panel-elevated pl-8 text-panel-fg placeholder:text-panel-muted-fg"
335
- }
336
- )
337
- ] }),
338
- /* @__PURE__ */ jsx2(
339
- Button,
340
- {
341
- type: "submit",
342
- size: "lg",
343
- variant: "outline",
344
- disabled: !url.trim(),
345
- className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
346
- children: labels.imagePickerUseUrl
347
- }
348
- )
349
- ]
350
- }
351
- ),
352
- library && /* @__PURE__ */ jsxs("section", { children: [
353
- /* @__PURE__ */ jsx2("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
354
- loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ jsx2("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsx2(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ jsx2("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ jsxs("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
355
- Array.from({ length: pending }, (_, i) => /* @__PURE__ */ jsx2(
356
- "li",
266
+ return /* @__PURE__ */ jsx2(Dialog, { open, onOpenChange: (next) => !next && onClose(), children: /* @__PURE__ */ jsxs(
267
+ DialogContent,
268
+ {
269
+ onDragOver: (e) => e.preventDefault(),
270
+ onDrop: (e) => e.preventDefault(),
271
+ className: "editor-dark gap-0 overflow-hidden border border-panel-border bg-panel p-0 text-panel-fg ring-0 sm:max-w-3xl",
272
+ children: [
273
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "border-b border-panel-border px-5 py-4", children: [
274
+ /* @__PURE__ */ jsx2(DialogTitle, { children: labels.imagePickerTitle }),
275
+ quota && /* @__PURE__ */ jsx2(DialogDescription, { className: "text-panel-muted-fg", children: labels.imagePickerQuota(quota.used, quota.limit) })
276
+ ] }),
277
+ /* @__PURE__ */ jsxs("div", { className: "flex max-h-[70vh] flex-col gap-4 overflow-y-auto p-5", children: [
278
+ library?.upload && (full ? /* @__PURE__ */ jsx2("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__ */ jsxs(
279
+ "div",
357
280
  {
358
- className: "flex aspect-[4/3] items-center justify-center rounded-lg border border-panel-border bg-panel-elevated",
359
- children: /* @__PURE__ */ jsx2(Spinner, {})
360
- },
361
- `pending-${i}`
281
+ onDragOver: (e) => {
282
+ e.preventDefault();
283
+ setDragging(true);
284
+ },
285
+ onDragLeave: () => setDragging(false),
286
+ onDrop,
287
+ className: cn(
288
+ "flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed px-6 py-8 text-center transition-colors",
289
+ dragging ? "border-brand bg-brand-soft" : "border-panel-border bg-panel-elevated"
290
+ ),
291
+ children: [
292
+ /* @__PURE__ */ jsx2(ImageUp, { className: "size-8 text-panel-muted-fg" }),
293
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2 text-sm text-panel-muted-fg", children: [
294
+ /* @__PURE__ */ jsx2("span", { children: labels.imagePickerDrop }),
295
+ /* @__PURE__ */ jsx2(
296
+ Button,
297
+ {
298
+ type: "button",
299
+ size: "sm",
300
+ className: "bg-brand text-brand-fg hover:bg-brand/90",
301
+ onClick: () => fileInput.current?.click(),
302
+ children: labels.imagePickerBrowse
303
+ }
304
+ )
305
+ ] }),
306
+ /* @__PURE__ */ jsx2(
307
+ "input",
308
+ {
309
+ ref: fileInput,
310
+ type: "file",
311
+ accept: library.accept ?? "image/*",
312
+ multiple: true,
313
+ hidden: true,
314
+ onChange: (e) => {
315
+ void upload(e.target.files);
316
+ e.target.value = "";
317
+ }
318
+ }
319
+ )
320
+ ]
321
+ }
362
322
  )),
363
- images.map((image) => {
364
- const selected = image.url === currentUrl;
365
- const confirming = confirmId === image.id;
366
- return /* @__PURE__ */ jsxs("li", { className: "group relative", children: [
367
- /* @__PURE__ */ jsx2(
368
- "button",
369
- {
370
- type: "button",
371
- onClick: () => onSelect(image.url),
372
- title: image.name,
373
- className: cn(
374
- "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",
375
- selected ? "border-brand ring-2 ring-brand" : "border-panel-border hover:border-panel-muted-fg"
376
- ),
377
- children: /* @__PURE__ */ jsx2(
378
- "img",
323
+ library?.allowUrl !== false && /* @__PURE__ */ jsxs(
324
+ "form",
325
+ {
326
+ className: "flex gap-2",
327
+ onSubmit: (e) => {
328
+ e.preventDefault();
329
+ const value = url.trim();
330
+ if (value) onSelect(value);
331
+ },
332
+ children: [
333
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
334
+ /* @__PURE__ */ jsx2(Link2, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-panel-muted-fg" }),
335
+ /* @__PURE__ */ jsx2(
336
+ Input,
379
337
  {
380
- src: image.url,
381
- alt: image.name ?? "",
382
- loading: "lazy",
383
- className: "size-full object-contain"
338
+ value: url,
339
+ onChange: (e) => setUrl(e.target.value),
340
+ placeholder: labels.imagePickerUrlPlaceholder,
341
+ className: "h-9 border-panel-border bg-panel-elevated pl-8 text-panel-fg placeholder:text-panel-muted-fg"
384
342
  }
385
343
  )
386
- }
387
- ),
388
- selected && /* @__PURE__ */ jsx2("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__ */ jsx2(Check, { className: "size-3" }) }),
389
- image.name && /* @__PURE__ */ jsx2("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 }),
390
- library.remove && /* @__PURE__ */ jsxs(
391
- "button",
344
+ ] }),
345
+ /* @__PURE__ */ jsx2(
346
+ Button,
347
+ {
348
+ type: "submit",
349
+ size: "lg",
350
+ variant: "outline",
351
+ disabled: !url.trim(),
352
+ className: "border-panel-border bg-panel-elevated text-panel-fg hover:bg-panel-hover hover:text-panel-fg",
353
+ children: labels.imagePickerUseUrl
354
+ }
355
+ )
356
+ ]
357
+ }
358
+ ),
359
+ library && /* @__PURE__ */ jsxs("section", { children: [
360
+ /* @__PURE__ */ jsx2("h3", { className: "mb-2 text-xs font-medium tracking-wide text-panel-muted-fg uppercase", children: labels.imagePickerGallery }),
361
+ loading && images.length === 0 && pending === 0 ? /* @__PURE__ */ jsx2("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsx2(Spinner, {}) }) : images.length === 0 && pending === 0 ? /* @__PURE__ */ jsx2("p", { className: "py-6 text-center text-sm text-panel-muted-fg", children: labels.imagePickerEmpty }) : /* @__PURE__ */ jsxs("ul", { className: "grid grid-cols-3 gap-3 sm:grid-cols-4", children: [
362
+ Array.from({ length: pending }, (_, i) => /* @__PURE__ */ jsx2(
363
+ "li",
392
364
  {
393
- type: "button",
394
- onClick: () => void remove(image),
395
- onBlur: () => confirming && setConfirmId(null),
396
- title: confirming ? labels.imagePickerConfirmDelete : labels.imagePickerDelete,
397
- "aria-label": labels.imagePickerDelete,
398
- className: cn(
399
- "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",
400
- confirming ? "bg-red-600 opacity-100" : "bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80"
365
+ className: "flex aspect-[4/3] items-center justify-center rounded-lg border border-panel-border bg-panel-elevated",
366
+ children: /* @__PURE__ */ jsx2(Spinner, {})
367
+ },
368
+ `pending-${i}`
369
+ )),
370
+ images.map((image) => {
371
+ const selected = image.url === currentUrl;
372
+ const confirming = confirmId === image.id;
373
+ return /* @__PURE__ */ jsxs("li", { className: "group relative", children: [
374
+ /* @__PURE__ */ jsx2(
375
+ "button",
376
+ {
377
+ type: "button",
378
+ onClick: () => onSelect(image.url),
379
+ title: image.name,
380
+ className: cn(
381
+ "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",
382
+ selected ? "border-brand ring-2 ring-brand" : "border-panel-border hover:border-panel-muted-fg"
383
+ ),
384
+ children: /* @__PURE__ */ jsx2(
385
+ "img",
386
+ {
387
+ src: image.url,
388
+ alt: image.name ?? "",
389
+ loading: "lazy",
390
+ className: "size-full object-contain"
391
+ }
392
+ )
393
+ }
401
394
  ),
402
- children: [
403
- /* @__PURE__ */ jsx2(Trash2, { className: "size-3.5" }),
404
- confirming && labels.imagePickerConfirmDelete
405
- ]
406
- }
407
- )
408
- ] }, image.id);
409
- })
395
+ selected && /* @__PURE__ */ jsx2("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__ */ jsx2(Check, { className: "size-3" }) }),
396
+ image.name && /* @__PURE__ */ jsx2("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 }),
397
+ library.remove && /* @__PURE__ */ jsxs(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ onClick: () => void remove(image),
402
+ onBlur: () => confirming && setConfirmId(null),
403
+ title: confirming ? labels.imagePickerConfirmDelete : labels.imagePickerDelete,
404
+ "aria-label": labels.imagePickerDelete,
405
+ className: cn(
406
+ "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",
407
+ confirming ? "bg-red-600 opacity-100" : "bg-black/60 opacity-0 group-hover:opacity-100 hover:bg-black/80"
408
+ ),
409
+ children: [
410
+ /* @__PURE__ */ jsx2(Trash2, { className: "size-3.5" }),
411
+ confirming && labels.imagePickerConfirmDelete
412
+ ]
413
+ }
414
+ )
415
+ ] }, image.id);
416
+ })
417
+ ] })
418
+ ] })
410
419
  ] })
411
- ] })
412
- ] })
413
- ] }) });
420
+ ]
421
+ }
422
+ ) });
414
423
  }
415
424
  function matchesAccept(file, accept) {
416
425
  if (!accept) return file.type.startsWith("image/");
@@ -3776,6 +3785,10 @@ function EmailEditor({
3776
3785
  // app.grapesjs.com and leave a session-storage entry, which a host would then have
3777
3786
  // to disclose and, for the entry, ask consent for.
3778
3787
  telemetry: false,
3788
+ // Font Awesome from cdnjs by default, a third party a host would have to name in its
3789
+ // privacy policy. Nothing we show uses it: GrapesJS draws its toolbar and layer icons
3790
+ // as SVG, and its icon-font panels and style manager are not rendered here.
3791
+ cssIcons: "",
3779
3792
  canvasCss: CANVAS_CSS,
3780
3793
  fromElement: false,
3781
3794
  panels: { defaults: [] },