@tightknitai/block-kitchen 0.10.25 → 0.10.27

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, pointerWithin, closestCenter, DndContext, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
2
2
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
3
- import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, Lock, Type, Bold, Italic, Strikethrough, Underline, Code, AtSign, Hash, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
3
+ import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, ImagePlus, Lock, Type, Upload, Bold, Italic, Strikethrough, Underline, Code, AtSign, Hash, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
5
  import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo, useId, useSyncExternalStore, useLayoutEffect } from 'react';
6
6
  import { clsx } from 'clsx';
@@ -1928,6 +1928,36 @@ function CustomEmojiProvider({ customEmojis, children }) {
1928
1928
  function useCustomEmojis() {
1929
1929
  return useContext(CustomEmojiContext);
1930
1930
  }
1931
+ var DEFAULT_IMAGE_UPLOAD_ACCEPT = ["image/png", "image/jpeg", "image/gif"];
1932
+ var ImageUploadContext = createContext(null);
1933
+ function ImageUploadProvider({
1934
+ imageUpload,
1935
+ children
1936
+ }) {
1937
+ const uploadImage = imageUpload?.uploadImage;
1938
+ const accept = imageUpload?.accept;
1939
+ const value = useMemo(
1940
+ () => uploadImage ? { uploadImage, accept: accept ?? DEFAULT_IMAGE_UPLOAD_ACCEPT } : null,
1941
+ [uploadImage, accept]
1942
+ );
1943
+ return /* @__PURE__ */ jsx(ImageUploadContext.Provider, { value, children });
1944
+ }
1945
+ function useImageUpload() {
1946
+ return useContext(ImageUploadContext);
1947
+ }
1948
+ function isAcceptedImage(file, accept) {
1949
+ return accept.includes(file.type);
1950
+ }
1951
+ function uploadErrorMessage(error) {
1952
+ return error instanceof Error && error.message ? error.message : "Upload failed. Try again.";
1953
+ }
1954
+ function isFileDrag(dataTransfer) {
1955
+ return Boolean(dataTransfer && Array.from(dataTransfer.types).includes("Files"));
1956
+ }
1957
+ function formatList(accept) {
1958
+ const names = accept.map((type) => type.replace(/^image\//, "").toUpperCase());
1959
+ return names.length > 1 ? `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}` : names[0] ?? "";
1960
+ }
1931
1961
  var MentionsContext = createContext(null);
1932
1962
  function MentionsProvider({ mentions, children }) {
1933
1963
  return /* @__PURE__ */ jsx(MentionsContext.Provider, { value: mentions ?? null, children });
@@ -3239,6 +3269,13 @@ var PopoverContent = React.forwardRef(({ className, align = "start", sideOffset
3239
3269
  }
3240
3270
  ) }));
3241
3271
  PopoverContent.displayName = PopoverPrimitive.Content.displayName;
3272
+ function returnFocusUnlessMoved(event) {
3273
+ const active = document.activeElement;
3274
+ const closing = event.target instanceof Node ? event.target : null;
3275
+ if (active && active !== document.body && !closing?.contains(active)) {
3276
+ event.preventDefault();
3277
+ }
3278
+ }
3242
3279
  function matchesQuery(name, query) {
3243
3280
  const needle = query.replace(/^\s*#\s*/, "").trim().toLowerCase();
3244
3281
  return needle === "" || name.toLowerCase().includes(needle);
@@ -4474,7 +4511,7 @@ function PaletteItem({
4474
4511
  {
4475
4512
  ref: setNodeRef,
4476
4513
  className: cn(
4477
- "group flex items-center gap-2 rounded px-2 py-1 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground has-[:focus-visible]:bg-accent has-[:focus-visible]:text-foreground",
4514
+ "group flex items-start gap-2 rounded px-2 py-1 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-foreground has-[:focus-visible]:bg-accent has-[:focus-visible]:text-foreground",
4478
4515
  isDragging && "opacity-50"
4479
4516
  ),
4480
4517
  children: [
@@ -4482,16 +4519,12 @@ function PaletteItem({
4482
4519
  "div",
4483
4520
  {
4484
4521
  ref: setActivatorNodeRef,
4485
- className: "-my-1 flex min-w-0 flex-1 cursor-grab items-center gap-2 rounded py-1 active:cursor-grabbing focus-visible:outline-none",
4522
+ className: "-my-1 flex min-w-0 flex-1 cursor-grab items-start gap-2 rounded py-1 active:cursor-grabbing focus-visible:outline-none",
4486
4523
  ...attributes,
4487
4524
  ...listeners,
4488
4525
  children: [
4489
- /* @__PURE__ */ jsx(GripVertical, { className: "h-3.5 w-3.5 shrink-0" }),
4490
- /* @__PURE__ */ jsx("span", { className: "truncate", children: variant.label }),
4491
- variant.badge ? /* @__PURE__ */ jsxs(Fragment, { children: [
4492
- " ",
4493
- /* @__PURE__ */ jsx(VariantBadge, { badge: variant.badge })
4494
- ] }) : null
4526
+ /* @__PURE__ */ jsx(GripVertical, { className: "mt-[3px] h-3.5 w-3.5 shrink-0" }),
4527
+ /* @__PURE__ */ jsx(RailLabel, { variant })
4495
4528
  ]
4496
4529
  }
4497
4530
  ),
@@ -4516,13 +4549,15 @@ function LockedPaletteItem({
4516
4549
  }) {
4517
4550
  const select = variant.onLockedSelect;
4518
4551
  const className = cn(
4519
- "flex w-full items-center text-left text-muted-foreground",
4520
- isSheet ? "min-h-11 gap-3 rounded-md px-3 py-2 text-sm font-medium" : "gap-2 rounded px-2 py-1 text-sm"
4552
+ "flex w-full text-left text-muted-foreground",
4553
+ isSheet ? "min-h-11 items-center gap-3 rounded-md px-3 py-2 text-sm font-medium" : "items-start gap-2 rounded px-2 py-1 text-sm"
4521
4554
  );
4522
4555
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
4523
- /* @__PURE__ */ jsx(Lock, { "aria-hidden": true, className: cn("shrink-0", isSheet ? "h-4 w-4" : "h-3.5 w-3.5") }),
4524
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: variant.label }),
4525
- variant.badge ? /* @__PURE__ */ jsx(VariantBadge, { badge: variant.badge }) : null
4556
+ /* @__PURE__ */ jsx(Lock, { "aria-hidden": true, className: cn("shrink-0", isSheet ? "h-4 w-4" : "mt-[3px] h-3.5 w-3.5") }),
4557
+ isSheet ? /* @__PURE__ */ jsxs(Fragment, { children: [
4558
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: variant.label }),
4559
+ variant.badge ? /* @__PURE__ */ jsx(VariantBadge, { badge: variant.badge }) : null
4560
+ ] }) : /* @__PURE__ */ jsx(RailLabel, { variant })
4526
4561
  ] });
4527
4562
  if (!select) {
4528
4563
  return /* @__PURE__ */ jsx("div", { "aria-disabled": "true", className, children: content });
@@ -4544,12 +4579,31 @@ function LockedPaletteItem({
4544
4579
  }
4545
4580
  );
4546
4581
  }
4582
+ function RailLabel({ variant }) {
4583
+ if (!variant.badge) {
4584
+ return /* @__PURE__ */ jsx("span", { className: "truncate", children: variant.label });
4585
+ }
4586
+ return /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col items-start gap-0.5", children: [
4587
+ /* @__PURE__ */ jsx("span", { className: "max-w-full truncate", children: variant.label }),
4588
+ " ",
4589
+ /* @__PURE__ */ jsx(VariantBadge, { badge: variant.badge })
4590
+ ] });
4591
+ }
4547
4592
  function VariantBadge({ badge }) {
4548
4593
  const Icon = badge.icon;
4549
- return /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 rounded-full border bg-background px-1.5 text-[10px] font-medium leading-4 text-muted-foreground", children: [
4550
- Icon ? /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "h-2.5 w-2.5 shrink-0" }) : null,
4551
- badge.label
4552
- ] });
4594
+ return /* @__PURE__ */ jsxs(
4595
+ "span",
4596
+ {
4597
+ className: cn(
4598
+ "inline-flex shrink-0 items-center gap-1 rounded-full border bg-background px-1.5 text-[10px] font-medium leading-4 text-muted-foreground",
4599
+ badge.className
4600
+ ),
4601
+ children: [
4602
+ Icon ? /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "h-2.5 w-2.5 shrink-0" }) : null,
4603
+ badge.label
4604
+ ]
4605
+ }
4606
+ );
4553
4607
  }
4554
4608
  function SendDialog({
4555
4609
  open,
@@ -4713,6 +4767,73 @@ function SendDialog({
4713
4767
  ] })
4714
4768
  ] }) });
4715
4769
  }
4770
+ var ALT_TEXT_MAX_LENGTH = 2e3;
4771
+ function altTextFromFileName(file) {
4772
+ const name = file.name.replace(/\.[^.]+$/, "").trim();
4773
+ return (name || "Image").slice(0, ALT_TEXT_MAX_LENGTH);
4774
+ }
4775
+ function useCanvasImageDrop({
4776
+ upload,
4777
+ enabled,
4778
+ onAddBlock
4779
+ }) {
4780
+ const [dragging, setDragging] = useState(false);
4781
+ const [pending, setPending] = useState(0);
4782
+ const [error, setError] = useState(null);
4783
+ const onAddBlockRef = useRef(onAddBlock);
4784
+ onAddBlockRef.current = onAddBlock;
4785
+ const addFiles = async (activeUpload, files) => {
4786
+ const images = files.filter((file) => isAcceptedImage(file, activeUpload.accept));
4787
+ const skipped = files.length - images.length;
4788
+ setError(
4789
+ skipped > 0 ? `${skipped === 1 ? "1 file was" : `${skipped} files were`} skipped. Only ${formatList(activeUpload.accept)} images can be added.` : null
4790
+ );
4791
+ if (images.length === 0) {
4792
+ return;
4793
+ }
4794
+ setPending((count) => count + images.length);
4795
+ const results = await Promise.allSettled(images.map((file) => activeUpload.uploadImage(file)));
4796
+ setPending((count) => count - images.length);
4797
+ const failures = [];
4798
+ results.forEach((result, idx) => {
4799
+ const file = images[idx];
4800
+ if (result.status === "fulfilled") {
4801
+ onAddBlockRef.current?.({ type: "image", image_url: result.value.url, alt_text: altTextFromFileName(file) });
4802
+ } else {
4803
+ failures.push(`Couldn't upload "${file.name}": ${uploadErrorMessage(result.reason)}`);
4804
+ }
4805
+ });
4806
+ if (failures.length > 0) {
4807
+ setError(failures.join(" "));
4808
+ }
4809
+ };
4810
+ const isOwnEvent = (e) => isFileDrag(e.dataTransfer) && e.currentTarget.contains(e.target);
4811
+ const handlers = upload && enabled && onAddBlock ? {
4812
+ onDragOver: (e) => {
4813
+ if (!isOwnEvent(e)) {
4814
+ return;
4815
+ }
4816
+ e.preventDefault();
4817
+ e.dataTransfer.dropEffect = "copy";
4818
+ setDragging(true);
4819
+ },
4820
+ onDragLeave: (e) => {
4821
+ const next = e.relatedTarget;
4822
+ if (!next || !e.currentTarget.contains(next)) {
4823
+ setDragging(false);
4824
+ }
4825
+ },
4826
+ onDrop: (e) => {
4827
+ if (!isOwnEvent(e)) {
4828
+ return;
4829
+ }
4830
+ e.preventDefault();
4831
+ setDragging(false);
4832
+ void addFiles(upload, Array.from(e.dataTransfer.files));
4833
+ }
4834
+ } : void 0;
4835
+ return { handlers, dragging, pending, error, dismissError: () => setError(null) };
4836
+ }
4716
4837
  function BlockActionButtons({
4717
4838
  actions,
4718
4839
  position,
@@ -4967,6 +5088,129 @@ function AlertEditor({ block, onChange }) {
4967
5088
  ) })
4968
5089
  ] });
4969
5090
  }
5091
+ function ImageUrlInput({
5092
+ id,
5093
+ value,
5094
+ placeholder,
5095
+ onChange
5096
+ }) {
5097
+ const upload = useImageUpload();
5098
+ const input = /* @__PURE__ */ jsx(Input, { id, type: "url", value, placeholder, onChange: (e) => onChange(e.target.value) });
5099
+ if (!upload) {
5100
+ return input;
5101
+ }
5102
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
5103
+ /* @__PURE__ */ jsx(UploadDropZone, { upload, inputId: id, onUploaded: onChange, variant: "inline" }),
5104
+ input
5105
+ ] });
5106
+ }
5107
+ function UploadDropZone({
5108
+ upload,
5109
+ inputId,
5110
+ onUploaded,
5111
+ variant
5112
+ }) {
5113
+ const [uploading, setUploading] = useState(false);
5114
+ const [dragging, setDragging] = useState(false);
5115
+ const [error, setError] = useState(null);
5116
+ const fileInputRef = useRef(null);
5117
+ const onUploadedRef = useRef(onUploaded);
5118
+ onUploadedRef.current = onUploaded;
5119
+ const uploadFile = async (file) => {
5120
+ if (!isAcceptedImage(file, upload.accept)) {
5121
+ setError(`"${file.name}" isn't a supported image. Use ${formatList(upload.accept)}.`);
5122
+ return;
5123
+ }
5124
+ setError(null);
5125
+ setUploading(true);
5126
+ try {
5127
+ const { url } = await upload.uploadImage(file);
5128
+ onUploadedRef.current(url);
5129
+ } catch (err) {
5130
+ setError(uploadErrorMessage(err));
5131
+ } finally {
5132
+ setUploading(false);
5133
+ }
5134
+ };
5135
+ const onDragOver = (e) => {
5136
+ if (!isFileDrag(e.dataTransfer)) {
5137
+ return;
5138
+ }
5139
+ e.preventDefault();
5140
+ e.stopPropagation();
5141
+ setDragging(true);
5142
+ };
5143
+ const onDrop = (e) => {
5144
+ if (!isFileDrag(e.dataTransfer)) {
5145
+ return;
5146
+ }
5147
+ e.preventDefault();
5148
+ e.stopPropagation();
5149
+ setDragging(false);
5150
+ const [file] = Array.from(e.dataTransfer.files);
5151
+ if (file && !uploading) {
5152
+ void uploadFile(file);
5153
+ }
5154
+ };
5155
+ const fileInput = /* @__PURE__ */ jsx(
5156
+ "input",
5157
+ {
5158
+ ref: fileInputRef,
5159
+ id: `${inputId}-file`,
5160
+ type: "file",
5161
+ accept: upload.accept.join(","),
5162
+ className: "hidden",
5163
+ tabIndex: -1,
5164
+ "aria-hidden": "true",
5165
+ onChange: (e) => {
5166
+ const file = e.target.files?.[0];
5167
+ e.target.value = "";
5168
+ if (file) {
5169
+ void uploadFile(file);
5170
+ }
5171
+ }
5172
+ }
5173
+ );
5174
+ const pickButton = (label) => /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", className: "h-7", disabled: uploading, onClick: () => fileInputRef.current?.click(), children: label });
5175
+ const dropHandlers = { onDragOver, onDragLeave: () => setDragging(false), onDrop };
5176
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5177
+ variant === "panel" ? /* @__PURE__ */ jsxs(
5178
+ "section",
5179
+ {
5180
+ "aria-label": "Upload an image",
5181
+ ...dropHandlers,
5182
+ className: cn(
5183
+ "flex flex-col items-center justify-center gap-2 rounded-md border border-dashed px-4 py-6 text-center text-xs text-muted-foreground transition-colors",
5184
+ dragging && "border-primary bg-primary/5 text-primary"
5185
+ ),
5186
+ children: [
5187
+ /* @__PURE__ */ jsx(Upload, { className: cn("h-5 w-5", uploading && "animate-pulse"), "aria-hidden": "true" }),
5188
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", children: uploading ? "Uploading\u2026" : dragging ? "Drop to upload" : "Drag an image here, or" }),
5189
+ pickButton("Choose file"),
5190
+ /* @__PURE__ */ jsx("span", { className: "text-[11px]", children: formatList(upload.accept) }),
5191
+ fileInput
5192
+ ]
5193
+ }
5194
+ ) : /* @__PURE__ */ jsxs(
5195
+ "section",
5196
+ {
5197
+ "aria-label": "Upload an image",
5198
+ ...dropHandlers,
5199
+ className: cn(
5200
+ "flex items-center gap-2 rounded-md border border-dashed px-3 py-2 text-[11px] text-muted-foreground transition-colors",
5201
+ dragging && "border-primary bg-primary/5 text-primary"
5202
+ ),
5203
+ children: [
5204
+ /* @__PURE__ */ jsx(Upload, { className: cn("h-3.5 w-3.5 shrink-0", uploading && "animate-pulse"), "aria-hidden": "true" }),
5205
+ /* @__PURE__ */ jsx("span", { className: "flex-1", "aria-live": "polite", children: uploading ? "Uploading\u2026" : dragging ? "Drop to upload" : "Drop an image here, or paste a URL below" }),
5206
+ pickButton("Upload image"),
5207
+ fileInput
5208
+ ]
5209
+ }
5210
+ ),
5211
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "text-[11px] leading-snug text-destructive", children: error }) : null
5212
+ ] });
5213
+ }
4970
5214
  function CardEditor({
4971
5215
  block,
4972
5216
  onChange,
@@ -5073,13 +5317,12 @@ function ImageFields({
5073
5317
  };
5074
5318
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-md border bg-muted/20 p-3", children: [
5075
5319
  /* @__PURE__ */ jsx(EditorField, { label, help, htmlFor: `${idPrefix}-url`, children: /* @__PURE__ */ jsx(
5076
- Input,
5320
+ ImageUrlInput,
5077
5321
  {
5078
5322
  id: `${idPrefix}-url`,
5079
- type: "url",
5080
5323
  value: url,
5081
5324
  placeholder: "e.g. https://example.com/image.png",
5082
- onChange: (e) => update({ url: e.target.value })
5325
+ onChange: (next) => update({ url: next })
5083
5326
  }
5084
5327
  ) }),
5085
5328
  /* @__PURE__ */ jsx(EditorField, { label: "Alt text", help: "Describes the image for screen readers.", htmlFor: `${idPrefix}-alt`, children: /* @__PURE__ */ jsx(
@@ -7639,15 +7882,14 @@ function ContainerEditor({ block, onChange }) {
7639
7882
  }
7640
7883
  ) : null,
7641
7884
  /* @__PURE__ */ jsx(EditorField, { label: "Icon URL", help: "Optional image shown left of the title.", htmlFor: "container-icon", children: /* @__PURE__ */ jsx(
7642
- Input,
7885
+ ImageUrlInput,
7643
7886
  {
7644
7887
  id: "container-icon",
7645
- type: "url",
7646
7888
  value: iconUrl,
7647
7889
  placeholder: "https://example.com/icon.png",
7648
- onChange: (e) => onChange({
7890
+ onChange: (url) => onChange({
7649
7891
  ...block,
7650
- icon: e.target.value ? { type: "image", image_url: e.target.value, alt_text: iconAlt || "Container icon" } : void 0
7892
+ icon: url ? { type: "image", image_url: url, alt_text: iconAlt || "Container icon" } : void 0
7651
7893
  })
7652
7894
  }
7653
7895
  ) }),
@@ -8145,13 +8387,12 @@ function ImageItemCard({
8145
8387
  help: "A publicly accessible image URL. Best at small sizes (around 20px).",
8146
8388
  htmlFor: `ctx-img-url-${index}`,
8147
8389
  children: /* @__PURE__ */ jsx(
8148
- Input,
8390
+ ImageUrlInput,
8149
8391
  {
8150
8392
  id: `ctx-img-url-${index}`,
8151
- type: "url",
8152
8393
  value: element.image_url,
8153
8394
  placeholder: "e.g. https://example.com/avatar.png",
8154
- onChange: (e) => onChange({ ...element, image_url: e.target.value })
8395
+ onChange: (url) => onChange({ ...element, image_url: url })
8155
8396
  }
8156
8397
  )
8157
8398
  }
@@ -8893,36 +9134,44 @@ function HeaderEditor({ block, onChange }) {
8893
9134
  ] });
8894
9135
  }
8895
9136
  function ImageEditor({ block, onChange }) {
8896
- const source = "slack_file" in block ? "slack_file" : "url";
9137
+ const upload = useImageUpload();
9138
+ const [urlSource, setUrlSource] = useState(upload ? "upload" : "url");
9139
+ const isSlackFile = "slack_file" in block;
9140
+ const source = isSlackFile ? "slack_file" : upload ? urlSource : "url";
8897
9141
  const titleText = block.title?.text ?? "";
8898
9142
  const setSource = (next) => {
8899
9143
  if (next === source) {
8900
9144
  return;
8901
9145
  }
8902
- if (next === "url") {
9146
+ if (next !== "slack_file") {
9147
+ setUrlSource(next);
9148
+ }
9149
+ if (next !== "slack_file" && isSlackFile) {
8903
9150
  const { slack_file: _omit, ...rest } = block;
8904
9151
  onChange({ image_url: "", ...rest });
8905
- } else {
9152
+ } else if (next === "slack_file") {
8906
9153
  const { image_url: _omit, ...rest } = block;
8907
9154
  onChange({ slack_file: { id: "" }, ...rest });
8908
9155
  }
8909
9156
  };
9157
+ const sourceOptions = [
9158
+ ...upload ? [["upload", "Upload image"]] : [],
9159
+ ["url", "Public URL"],
9160
+ ["slack_file", "Slack file"]
9161
+ ];
8910
9162
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
8911
9163
  /* @__PURE__ */ jsx(
8912
9164
  EditorField,
8913
9165
  {
8914
9166
  label: "Image source",
8915
- help: "Where the image comes from: a public URL, or a file already uploaded to your Slack workspace.",
9167
+ help: upload ? "Where the image comes from: a file from your device, a public URL, or a file already uploaded to your Slack workspace." : "Where the image comes from: a public URL, or a file already uploaded to your Slack workspace.",
8916
9168
  children: /* @__PURE__ */ jsx(
8917
9169
  RadioGroup,
8918
9170
  {
8919
9171
  value: source,
8920
9172
  onValueChange: (v) => setSource(v),
8921
9173
  className: "flex flex-row flex-wrap gap-3",
8922
- children: [
8923
- ["url", "Public URL"],
8924
- ["slack_file", "Slack file"]
8925
- ].map(([value, label]) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
9174
+ children: sourceOptions.map(([value, label]) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
8926
9175
  /* @__PURE__ */ jsx(RadioGroupItem, { value, id: `img-source-${value}` }),
8927
9176
  /* @__PURE__ */ jsx(Label, { htmlFor: `img-source-${value}`, className: "text-xs", children: label })
8928
9177
  ] }, value))
@@ -8930,7 +9179,15 @@ function ImageEditor({ block, onChange }) {
8930
9179
  )
8931
9180
  }
8932
9181
  ),
8933
- "slack_file" in block ? /* @__PURE__ */ jsx(SlackFileFields, { block, onChange }) : /* @__PURE__ */ jsx(EditorField, { label: "Image URL", help: "A publicly accessible image URL (PNG, JPG, GIF).", htmlFor: "img-url", children: /* @__PURE__ */ jsx(
9182
+ "slack_file" in block ? /* @__PURE__ */ jsx(SlackFileFields, { block, onChange }) : source === "upload" && upload ? /* @__PURE__ */ jsx(
9183
+ UploadDropZone,
9184
+ {
9185
+ upload,
9186
+ inputId: "img-upload",
9187
+ variant: "panel",
9188
+ onUploaded: (url) => onChange({ ...block, image_url: url })
9189
+ }
9190
+ ) : /* @__PURE__ */ jsx(EditorField, { label: "Image URL", help: "A publicly accessible image URL (PNG, JPG, GIF).", htmlFor: "img-url", children: /* @__PURE__ */ jsx(
8934
9191
  Input,
8935
9192
  {
8936
9193
  id: "img-url",
@@ -10113,13 +10370,12 @@ function ImageAccessoryFields({ image, onChange }) {
10113
10370
  help: "A publicly accessible image URL (PNG, JPG, GIF).",
10114
10371
  htmlFor: "section-acc-img-url",
10115
10372
  children: /* @__PURE__ */ jsx(
10116
- Input,
10373
+ ImageUrlInput,
10117
10374
  {
10118
10375
  id: "section-acc-img-url",
10119
- type: "url",
10120
10376
  value: image.image_url ?? "",
10121
10377
  placeholder: "e.g. https://example.com/thumb.png",
10122
- onChange: (e) => onChange({ ...image, image_url: e.target.value })
10378
+ onChange: (url) => onChange({ ...image, image_url: url })
10123
10379
  }
10124
10380
  )
10125
10381
  }
@@ -10482,10 +10738,24 @@ function BlockEditor({
10482
10738
  const custom = useCustomBlockEditor(block);
10483
10739
  const CustomForm = custom?.component;
10484
10740
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
10485
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-foreground", children: [
10486
- "Edit ",
10487
- custom ? custom.label : labelForBlockType(block.type)
10488
- ] }) }),
10741
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
10742
+ /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-foreground", children: [
10743
+ "Edit ",
10744
+ custom ? custom.label : labelForBlockType(block.type)
10745
+ ] }),
10746
+ onDone ? /* @__PURE__ */ jsx(
10747
+ Button,
10748
+ {
10749
+ type: "button",
10750
+ variant: "ghost",
10751
+ size: "icon",
10752
+ "aria-label": "Close editor",
10753
+ className: "-my-1 -mr-1 h-7 w-7 text-muted-foreground",
10754
+ onClick: onDone,
10755
+ children: /* @__PURE__ */ jsx(X, {})
10756
+ }
10757
+ ) : null
10758
+ ] }),
10489
10759
  errors && errors.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-0.5 rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: errors.map((err, i) => /* @__PURE__ */ jsx("li", { children: err }, i)) }) : null,
10490
10760
  CustomForm ? /* @__PURE__ */ jsx(CustomForm, { block, onChange }) : dispatch(block, onChange),
10491
10761
  /* @__PURE__ */ jsx(
@@ -10647,15 +10917,23 @@ function BlockRow({
10647
10917
  children: preview
10648
10918
  }
10649
10919
  ) }),
10650
- /* @__PURE__ */ jsx(PopoverContent, { className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]", align: "start", children: /* @__PURE__ */ jsx(
10651
- BlockEditor,
10920
+ /* @__PURE__ */ jsx(
10921
+ PopoverContent,
10652
10922
  {
10653
- block: builderBlock.block,
10654
- errors,
10655
- onChange: (next) => onUpdate(builderBlock.id, next),
10656
- onDone: () => onOpenChange?.(false)
10923
+ className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]",
10924
+ align: "start",
10925
+ onCloseAutoFocus: returnFocusUnlessMoved,
10926
+ children: /* @__PURE__ */ jsx(
10927
+ BlockEditor,
10928
+ {
10929
+ block: builderBlock.block,
10930
+ errors,
10931
+ onChange: (next) => onUpdate(builderBlock.id, next),
10932
+ onDone: () => onOpenChange?.(false)
10933
+ }
10934
+ )
10657
10935
  }
10658
- ) })
10936
+ )
10659
10937
  ] });
10660
10938
  const adornment = position && renderBlockAdornment?.(position);
10661
10939
  const hoverChrome = /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -10921,7 +11199,7 @@ function ContainerRow({
10921
11199
  hasErrors ? "border-destructive/60" : "border-border"
10922
11200
  ),
10923
11201
  children: [
10924
- /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: (open) => onOpenBlockChange?.(open ? id : null), children: [
11202
+ /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: (open) => onOpenBlockChange?.(id, open), children: [
10925
11203
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
10926
11204
  "div",
10927
11205
  {
@@ -10933,7 +11211,7 @@ function ContainerRow({
10933
11211
  onKeyDown: (e) => {
10934
11212
  if (e.key === "Enter" || e.key === " ") {
10935
11213
  e.preventDefault();
10936
- onOpenBlockChange?.(!isOpen ? id : null);
11214
+ onOpenBlockChange?.(id, !isOpen);
10937
11215
  }
10938
11216
  },
10939
11217
  className: "flex w-full cursor-grab items-center gap-2 border-b bg-muted/40 px-3 py-2 text-left transition-colors hover:bg-muted active:cursor-grabbing",
@@ -10947,15 +11225,23 @@ function ContainerRow({
10947
11225
  ]
10948
11226
  }
10949
11227
  ) }),
10950
- /* @__PURE__ */ jsx(PopoverContent, { className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]", align: "start", children: /* @__PURE__ */ jsx(
10951
- BlockEditor,
11228
+ /* @__PURE__ */ jsx(
11229
+ PopoverContent,
10952
11230
  {
10953
- block,
10954
- errors,
10955
- onChange: (next) => onUpdate(id, next),
10956
- onDone: () => onOpenBlockChange?.(null)
11231
+ className: "w-[min(32rem,calc(100vw-1.5rem))] sm:w-[32rem]",
11232
+ align: "start",
11233
+ onCloseAutoFocus: returnFocusUnlessMoved,
11234
+ children: /* @__PURE__ */ jsx(
11235
+ BlockEditor,
11236
+ {
11237
+ block,
11238
+ errors,
11239
+ onChange: (next) => onUpdate(id, next),
11240
+ onDone: () => onOpenBlockChange?.(id, false)
11241
+ }
11242
+ )
10957
11243
  }
10958
- ) })
11244
+ )
10959
11245
  ] }),
10960
11246
  /* @__PURE__ */ jsx(
10961
11247
  "div",
@@ -10984,7 +11270,7 @@ function ContainerRow({
10984
11270
  previewTheme,
10985
11271
  errors: errorsByBlockId?.get(child.id),
10986
11272
  isOpen: openBlockId === child.id,
10987
- onOpenChange: (open) => onOpenBlockChange?.(open ? child.id : null),
11273
+ onOpenChange: (open) => onOpenBlockChange?.(child.id, open),
10988
11274
  onUpdate,
10989
11275
  onDuplicate,
10990
11276
  onDelete,
@@ -11019,7 +11305,7 @@ function ContainerRow({
11019
11305
  {
11020
11306
  type: "button",
11021
11307
  "aria-label": `Show ${errors.length} validation ${errors.length === 1 ? "issue" : "issues"}`,
11022
- onClick: () => onOpenBlockChange?.(id),
11308
+ onClick: () => onOpenBlockChange?.(id, true),
11023
11309
  className: "flex h-6 w-6 items-center justify-center rounded text-destructive hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
11024
11310
  children: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5" })
11025
11311
  }
@@ -11038,7 +11324,7 @@ function ContainerRow({
11038
11324
  /* @__PURE__ */ jsx(ToolbarButton2, { label: "Move down", disabled: index >= total - 1, onClick: () => onReorder(id, index + 1), children: /* @__PURE__ */ jsx(ArrowDown, { className: "h-3.5 w-3.5" }) })
11039
11325
  ] }) : null,
11040
11326
  /* @__PURE__ */ jsx(BlockActionButtons, { actions: blockActions, position, variant: "toolbar" }),
11041
- /* @__PURE__ */ jsx(ToolbarButton2, { label: "Edit", onClick: () => onOpenBlockChange?.(id), children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }) }),
11327
+ /* @__PURE__ */ jsx(ToolbarButton2, { label: "Edit", onClick: () => onOpenBlockChange?.(id, true), children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }) }),
11042
11328
  /* @__PURE__ */ jsx(ToolbarButton2, { label: "Duplicate", onClick: () => onDuplicate(id), children: /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" }) }),
11043
11329
  /* @__PURE__ */ jsx(ToolbarButton2, { label: "Delete", destructive: true, onClick: () => onDelete(id), children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }) })
11044
11330
  ]
@@ -11071,7 +11357,7 @@ function ContainerRow({
11071
11357
  {
11072
11358
  type: "button",
11073
11359
  "aria-label": "Edit container",
11074
- onClick: () => onOpenBlockChange?.(id),
11360
+ onClick: () => onOpenBlockChange?.(id, true),
11075
11361
  className: "flex h-9 w-9 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground",
11076
11362
  children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
11077
11363
  }
@@ -11149,10 +11435,14 @@ function Surface({
11149
11435
  isPaletteDrag = false,
11150
11436
  onOpenPalette,
11151
11437
  blockActions,
11152
- renderBlockAdornment
11438
+ renderBlockAdornment,
11439
+ onAddBlock,
11440
+ imageDropEnabled = true
11153
11441
  }) {
11154
11442
  const { setNodeRef, isOver } = useDroppable({ id: SURFACE_DROPPABLE_ID });
11155
11443
  const isDark = previewTheme === "dark";
11444
+ const imageDrop = useCanvasImageDrop({ upload: useImageUpload(), enabled: imageDropEnabled, onAddBlock });
11445
+ const isDropTarget = isPaletteDrag || imageDrop.dragging;
11156
11446
  const showEndDropZone = isPaletteDrag && isOver && blocks.length > 0;
11157
11447
  const blocksList = /* @__PURE__ */ jsx(
11158
11448
  "div",
@@ -11161,10 +11451,19 @@ function Surface({
11161
11451
  className: cn(
11162
11452
  "flex min-h-[180px] flex-col px-3 py-2 transition-colors sm:min-h-[240px] sm:px-5",
11163
11453
  isDark ? "bg-[#1a1d21]" : "bg-white",
11164
- isPaletteDrag && (isDark ? "bg-[#22262c] outline-2 outline-primary/40 -outline-offset-2 outline-dashed" : "bg-[#f5f8ff] outline-2 outline-primary/40 -outline-offset-2 outline-dashed"),
11165
- isPaletteDrag && isOver && (isDark ? "bg-[#2c3036]" : "bg-[#eaf0ff]")
11454
+ isDropTarget && (isDark ? "bg-[#22262c] outline-2 outline-primary/40 -outline-offset-2 outline-dashed" : "bg-[#f5f8ff] outline-2 outline-primary/40 -outline-offset-2 outline-dashed"),
11455
+ (isPaletteDrag && isOver || imageDrop.dragging) && (isDark ? "bg-[#2c3036]" : "bg-[#eaf0ff]")
11166
11456
  ),
11167
- children: blocks.length === 0 ? /* @__PURE__ */ jsx(EmptyState, { isDark, isPaletteDrag, isOver, onOpenPalette }) : /* @__PURE__ */ jsxs(SortableContext, { items: blocks.map((b) => b.id), strategy: verticalListSortingStrategy, children: [
11457
+ children: blocks.length === 0 ? /* @__PURE__ */ jsx(
11458
+ EmptyState,
11459
+ {
11460
+ isDark,
11461
+ isPaletteDrag,
11462
+ isOver,
11463
+ isFileDrag: imageDrop.dragging,
11464
+ onOpenPalette
11465
+ }
11466
+ ) : /* @__PURE__ */ jsxs(SortableContext, { items: blocks.map((b) => b.id), strategy: verticalListSortingStrategy, children: [
11168
11467
  blocks.map(
11169
11468
  (block, idx) => block.block.type === "container" ? /* @__PURE__ */ jsx(
11170
11469
  ContainerRow,
@@ -11194,7 +11493,7 @@ function Surface({
11194
11493
  previewTheme,
11195
11494
  errors: errorsByBlockId?.get(block.id),
11196
11495
  isOpen: openBlockId === block.id,
11197
- onOpenChange: (open) => onOpenBlockChange?.(open ? block.id : null),
11496
+ onOpenChange: (open) => onOpenBlockChange?.(block.id, open),
11198
11497
  onUpdate,
11199
11498
  onDuplicate,
11200
11499
  onDelete,
@@ -11209,7 +11508,7 @@ function Surface({
11209
11508
  block.id
11210
11509
  )
11211
11510
  ),
11212
- showEndDropZone ? /* @__PURE__ */ jsx(DropIndicator2, {}) : null
11511
+ showEndDropZone || imageDrop.dragging ? /* @__PURE__ */ jsx(DropIndicator2, {}) : null
11213
11512
  ] })
11214
11513
  }
11215
11514
  );
@@ -11218,16 +11517,28 @@ function Surface({
11218
11517
  {
11219
11518
  "aria-label": "Block preview",
11220
11519
  className: cn("flex min-w-0 flex-1 flex-col overflow-y-auto bg-muted p-3 sm:p-6"),
11221
- children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsx(
11222
- SlackMessageFrame,
11223
- {
11224
- workspaceName,
11225
- authorName,
11226
- authorIcon,
11227
- isDark,
11228
- children: blocksList
11229
- }
11230
- ) })
11520
+ ...imageDrop.handlers,
11521
+ children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-2xl", children: [
11522
+ imageDrop.handlers ? /* @__PURE__ */ jsx(
11523
+ ImageDropStatus,
11524
+ {
11525
+ dragging: imageDrop.dragging,
11526
+ pending: imageDrop.pending,
11527
+ error: imageDrop.error,
11528
+ onDismissError: imageDrop.dismissError
11529
+ }
11530
+ ) : null,
11531
+ previewSurface === "modal" ? /* @__PURE__ */ jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsx(
11532
+ SlackMessageFrame,
11533
+ {
11534
+ workspaceName,
11535
+ authorName,
11536
+ authorIcon,
11537
+ isDark,
11538
+ children: blocksList
11539
+ }
11540
+ )
11541
+ ] })
11231
11542
  }
11232
11543
  );
11233
11544
  }
@@ -11356,9 +11667,10 @@ function EmptyState({
11356
11667
  isDark,
11357
11668
  isPaletteDrag,
11358
11669
  isOver,
11670
+ isFileDrag: isFileDrag2 = false,
11359
11671
  onOpenPalette
11360
11672
  }) {
11361
- const active = isPaletteDrag && isOver;
11673
+ const active = isPaletteDrag && isOver || isFileDrag2;
11362
11674
  return /* @__PURE__ */ jsxs(
11363
11675
  "div",
11364
11676
  {
@@ -11385,7 +11697,7 @@ function EmptyState({
11385
11697
  "text-sm font-semibold transition-colors",
11386
11698
  active ? "text-primary" : isDark ? "text-white" : "text-[#1d1c1d]"
11387
11699
  ),
11388
- children: active ? "Drop to add block" : "Start adding blocks!"
11700
+ children: isFileDrag2 ? "Drop to add image" : active ? "Drop to add block" : "Start adding blocks!"
11389
11701
  }
11390
11702
  ),
11391
11703
  /* @__PURE__ */ jsx("p", { className: "text-xs", children: active ? "Release to insert it here." : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -11409,6 +11721,40 @@ function EmptyState({
11409
11721
  }
11410
11722
  );
11411
11723
  }
11724
+ function ImageDropStatus({
11725
+ dragging,
11726
+ pending,
11727
+ error,
11728
+ onDismissError
11729
+ }) {
11730
+ const progress = dragging ? "Drop images to add them at the end of the message." : pending > 0 ? `Uploading ${pending === 1 ? "1 image" : `${pending} images`}\u2026` : null;
11731
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
11732
+ /* @__PURE__ */ jsx("div", { role: "status", className: "empty:hidden", children: progress ? /* @__PURE__ */ jsxs("p", { className: "mb-2 flex items-center gap-1.5 text-xs text-muted-foreground", children: [
11733
+ /* @__PURE__ */ jsx(ImagePlus, { className: cn("h-3.5 w-3.5", pending > 0 && !dragging && "animate-pulse"), "aria-hidden": "true" }),
11734
+ progress
11735
+ ] }) : null }),
11736
+ error ? /* @__PURE__ */ jsxs(
11737
+ "div",
11738
+ {
11739
+ role: "alert",
11740
+ className: "mb-2 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive",
11741
+ children: [
11742
+ /* @__PURE__ */ jsx("p", { className: "flex-1", children: error }),
11743
+ /* @__PURE__ */ jsx(
11744
+ "button",
11745
+ {
11746
+ type: "button",
11747
+ "aria-label": "Dismiss",
11748
+ onClick: onDismissError,
11749
+ className: "rounded p-0.5 hover:bg-destructive/10 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
11750
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
11751
+ }
11752
+ )
11753
+ ]
11754
+ }
11755
+ ) : null
11756
+ ] });
11757
+ }
11412
11758
  function DropIndicator2() {
11413
11759
  return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "pointer-events-none relative my-1 h-0.5 w-full rounded-full bg-primary", children: /* @__PURE__ */ jsx("span", { className: "-left-1 -top-[3px] absolute h-2 w-2 rounded-full bg-primary shadow-[0_0_0_2px_var(--color-background)]" }) });
11414
11760
  }
@@ -11971,6 +12317,7 @@ function BlockKitchen(props) {
11971
12317
  blockEditors,
11972
12318
  customEmojis,
11973
12319
  mentions,
12320
+ imageUpload,
11974
12321
  loadChannels,
11975
12322
  loadSendAsUserStatus,
11976
12323
  onSend,
@@ -12101,6 +12448,9 @@ function BlockKitchen(props) {
12101
12448
  setPaletteAdvanced(false);
12102
12449
  }
12103
12450
  const [openBlockId, setOpenBlockId] = useState(null);
12451
+ const setBlockOpen = useCallback((id, open) => {
12452
+ setOpenBlockId((current) => open ? id : current === id ? null : current);
12453
+ }, []);
12104
12454
  const [uncontrolledPreviewTheme, setUncontrolledPreviewTheme] = useState(defaultPreviewTheme);
12105
12455
  const isPreviewThemeControlled = controlledPreviewTheme !== void 0;
12106
12456
  const previewTheme = controlledPreviewTheme ?? uncontrolledPreviewTheme;
@@ -12354,7 +12704,7 @@ function BlockKitchen(props) {
12354
12704
  }
12355
12705
  )
12356
12706
  ] }),
12357
- /* @__PURE__ */ jsx(MentionsProvider, { mentions, children: /* @__PURE__ */ jsx(BlockEditorsProvider, { blockEditors, children: /* @__PURE__ */ jsx(
12707
+ /* @__PURE__ */ jsx(MentionsProvider, { mentions, children: /* @__PURE__ */ jsx(ImageUploadProvider, { imageUpload, children: /* @__PURE__ */ jsx(BlockEditorsProvider, { blockEditors, children: /* @__PURE__ */ jsx(
12358
12708
  Surface,
12359
12709
  {
12360
12710
  blocks,
@@ -12366,7 +12716,7 @@ function BlockKitchen(props) {
12366
12716
  previewSurface,
12367
12717
  errorsByBlockId: validation.byBlockId,
12368
12718
  openBlockId,
12369
- onOpenBlockChange: setOpenBlockId,
12719
+ onOpenBlockChange: setBlockOpen,
12370
12720
  onUpdate: updateBlock,
12371
12721
  onDuplicate: duplicateBlock,
12372
12722
  onDelete: removeBlock,
@@ -12374,9 +12724,11 @@ function BlockKitchen(props) {
12374
12724
  isPaletteDrag: activePaletteVariant !== null,
12375
12725
  onOpenPalette: () => setPaletteOpen(true),
12376
12726
  blockActions,
12377
- renderBlockAdornment
12727
+ renderBlockAdornment,
12728
+ onAddBlock: addBlock,
12729
+ imageDropEnabled: !disabledBlockTypes?.includes("image")
12378
12730
  }
12379
- ) }) })
12731
+ ) }) }) })
12380
12732
  ] })
12381
12733
  ]
12382
12734
  }