camox 0.14.2 → 0.16.0

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.
Files changed (75) hide show
  1. package/dist/components/AuthGate.js +6 -0
  2. package/dist/core/components/AddBlockControlBar.js +30 -26
  3. package/dist/core/components/lexical/InlineContentEditable.js +19 -15
  4. package/dist/core/components/lexical/InlineLexicalEditor.js +62 -42
  5. package/dist/core/components/lexical/SelectionBroadcaster.js +25 -21
  6. package/dist/core/components/lexical/SidebarLexicalEditor.js +33 -21
  7. package/dist/core/createBlock.js +524 -488
  8. package/dist/core/createLayout.js +40 -28
  9. package/dist/core/hooks/useFieldSelection.js +12 -8
  10. package/dist/core/hooks/useIsEditable.js +6 -0
  11. package/dist/core/hooks/useOverlayMessage.js +19 -15
  12. package/dist/core/lib/contentType.d.ts +8 -8
  13. package/dist/core/lib/contentType.js +63 -0
  14. package/dist/features/content/CamoxContent.js +92 -88
  15. package/dist/features/content/components/AssetCard.js +46 -42
  16. package/dist/features/content/components/AssetCardSkeleton.js +8 -4
  17. package/dist/features/content/components/ContentSidebar.js +8 -4
  18. package/dist/features/content/components/UploadDropZone.js +31 -27
  19. package/dist/features/content/components/UploadProgressDrawer.js +98 -90
  20. package/dist/features/preview/CamoxPreview.js +162 -146
  21. package/dist/features/preview/components/AddBlockSheet.js +105 -101
  22. package/dist/features/preview/components/AgentChatSheet.js +74 -21
  23. package/dist/features/preview/components/AssetFieldEditor.js +98 -90
  24. package/dist/features/preview/components/AssetLightbox.js +21 -17
  25. package/dist/features/preview/components/AssetPickerGrid.js +84 -80
  26. package/dist/features/preview/components/BlockActionsPopover.js +35 -31
  27. package/dist/features/preview/components/BlockErrorBoundary.js +12 -8
  28. package/dist/features/preview/components/CreatePageModal.js +1 -1
  29. package/dist/features/preview/components/DebouncedFieldEditor.js +41 -37
  30. package/dist/features/preview/components/EditPageModal.js +114 -98
  31. package/dist/features/preview/components/FieldOverlayStyles.js +8 -4
  32. package/dist/features/preview/components/FieldToolbar.js +95 -54
  33. package/dist/features/preview/components/Frame.js +50 -46
  34. package/dist/features/preview/components/ItemFieldsEditor.js +81 -79
  35. package/dist/features/preview/components/LinkFieldEditor.js +138 -134
  36. package/dist/features/preview/components/MultipleAssetFieldEditor.js +105 -97
  37. package/dist/features/preview/components/OverlayTracker.js +19 -15
  38. package/dist/features/preview/components/Overlays.js +27 -23
  39. package/dist/features/preview/components/PageContentSheet.js +54 -4
  40. package/dist/features/preview/components/PageLocationFieldset.js +113 -109
  41. package/dist/features/preview/components/PagePicker.js +1 -1
  42. package/dist/features/preview/components/PageTree.js +443 -399
  43. package/dist/features/preview/components/PeekedBlock.js +69 -65
  44. package/dist/features/preview/components/PreviewPanel.js +64 -52
  45. package/dist/features/preview/components/PreviewSideSheet.js +25 -16
  46. package/dist/features/preview/components/PreviewToolbar.js +127 -123
  47. package/dist/features/preview/components/RepeatableItemsList.js +176 -171
  48. package/dist/features/preview/components/ShikiMarkdown.js +18 -14
  49. package/dist/features/preview/components/UnlinkAssetButton.js +74 -70
  50. package/dist/features/preview/components/useRepeatableItemActions.js +266 -0
  51. package/dist/features/preview/components/useUpdateBlockPosition.js +13 -9
  52. package/dist/features/provider/CamoxProvider.js +60 -52
  53. package/dist/features/provider/components/CamoxAppContext.js +10 -6
  54. package/dist/features/provider/components/CommandPalette.js +77 -69
  55. package/dist/features/provider/useAdminShortcuts.js +11 -7
  56. package/dist/features/routes/pageRoute.js +8 -4
  57. package/dist/features/studio/CamoxStudio.js +23 -19
  58. package/dist/features/studio/components/EnvironmentMenu.js +32 -28
  59. package/dist/features/studio/components/Navbar.js +62 -54
  60. package/dist/features/studio/components/ProjectMenu.js +131 -123
  61. package/dist/features/studio/components/UserButton.js +15 -11
  62. package/dist/features/studio/useTheme.js +82 -42
  63. package/dist/features/vite/definitionsSync.js +5 -5
  64. package/dist/features/vite/skillGeneration.js +43 -8
  65. package/dist/features/vite/vite.d.ts +1 -1
  66. package/dist/features/vite/vite.js +20 -2
  67. package/dist/hooks/use-marquee-selection.js +36 -32
  68. package/dist/lib/auth.js +49 -22
  69. package/dist/lib/normalized-data.js +55 -47
  70. package/dist/lib/use-project-room.js +22 -18
  71. package/dist/studio.css +1 -1
  72. package/package.json +10 -11
  73. package/skills/camox-block/SKILL.md +4 -0
  74. package/skills/camox-cli/SKILL.md +142 -0
  75. package/skills/camox-layout/SKILL.md +4 -0
@@ -20,50 +20,54 @@ import { CSS } from "@dnd-kit/utilities";
20
20
 
21
21
  //#region src/features/preview/components/MultipleAssetFieldEditor.tsx
22
22
  const SortableAssetItem = (t0) => {
23
- const $ = c(42);
23
+ const $ = c(43);
24
+ if ($[0] !== "edd84331a88dec86cb85175ea4b5855869d1ffb6129d2a357826a8aa2e61f7a0") {
25
+ for (let $i = 0; $i < 43; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
26
+ $[0] = "edd84331a88dec86cb85175ea4b5855869d1ffb6129d2a357826a8aa2e61f7a0";
27
+ }
24
28
  const { asset, assetType, onRemove, onAssetOpen } = t0;
25
29
  const t1 = String(asset._fileId);
26
30
  let t2;
27
- if ($[0] !== t1) {
31
+ if ($[1] !== t1) {
28
32
  t2 = { id: t1 };
29
- $[0] = t1;
30
- $[1] = t2;
31
- } else t2 = $[1];
33
+ $[1] = t1;
34
+ $[2] = t2;
35
+ } else t2 = $[2];
32
36
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable(t2);
33
37
  let t3;
34
- if ($[2] !== transform) {
38
+ if ($[3] !== transform) {
35
39
  t3 = CSS.Transform.toString(transform);
36
- $[2] = transform;
37
- $[3] = t3;
38
- } else t3 = $[3];
40
+ $[3] = transform;
41
+ $[4] = t3;
42
+ } else t3 = $[4];
39
43
  const t4 = isDragging ? .5 : 1;
40
44
  let t5;
41
- if ($[4] !== t3 || $[5] !== t4 || $[6] !== transition) {
45
+ if ($[5] !== t3 || $[6] !== t4 || $[7] !== transition) {
42
46
  t5 = {
43
47
  transform: t3,
44
48
  transition,
45
49
  opacity: t4
46
50
  };
47
- $[4] = t3;
48
- $[5] = t4;
49
- $[6] = transition;
50
- $[7] = t5;
51
- } else t5 = $[7];
51
+ $[5] = t3;
52
+ $[6] = t4;
53
+ $[7] = transition;
54
+ $[8] = t5;
55
+ } else t5 = $[8];
52
56
  const style = t5;
53
57
  const t6 = !isDragging && "hover:bg-accent/75";
54
58
  let t7;
55
- if ($[8] !== t6) {
59
+ if ($[9] !== t6) {
56
60
  t7 = cn("flex flex-row items-center gap-2 px-1 py-1 max-w-full rounded-lg text-foreground transition-none group", t6);
57
- $[8] = t6;
58
- $[9] = t7;
59
- } else t7 = $[9];
61
+ $[9] = t6;
62
+ $[10] = t7;
63
+ } else t7 = $[10];
60
64
  let t8;
61
- if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
65
+ if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
62
66
  t8 = /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" });
63
- $[10] = t8;
64
- } else t8 = $[10];
67
+ $[11] = t8;
68
+ } else t8 = $[11];
65
69
  let t9;
66
- if ($[11] !== attributes || $[12] !== listeners) {
70
+ if ($[12] !== attributes || $[13] !== listeners) {
67
71
  t9 = /* @__PURE__ */ jsx(Button, {
68
72
  type: "button",
69
73
  variant: "ghost",
@@ -73,19 +77,19 @@ const SortableAssetItem = (t0) => {
73
77
  ...listeners,
74
78
  children: t8
75
79
  });
76
- $[11] = attributes;
77
- $[12] = listeners;
78
- $[13] = t9;
79
- } else t9 = $[13];
80
+ $[12] = attributes;
81
+ $[13] = listeners;
82
+ $[14] = t9;
83
+ } else t9 = $[14];
80
84
  let t10;
81
- if ($[14] !== asset || $[15] !== onAssetOpen) {
85
+ if ($[15] !== asset || $[16] !== onAssetOpen) {
82
86
  t10 = () => onAssetOpen(asset);
83
- $[14] = asset;
84
- $[15] = onAssetOpen;
85
- $[16] = t10;
86
- } else t10 = $[16];
87
+ $[15] = asset;
88
+ $[16] = onAssetOpen;
89
+ $[17] = t10;
90
+ } else t10 = $[17];
87
91
  let t11;
88
- if ($[17] !== asset.alt || $[18] !== asset.filename || $[19] !== asset.url || $[20] !== assetType) {
92
+ if ($[18] !== asset.alt || $[19] !== asset.filename || $[20] !== asset.url || $[21] !== assetType) {
89
93
  t11 = assetType === "Image" ? /* @__PURE__ */ jsx("div", {
90
94
  className: "border-border h-12 w-12 shrink-0 overflow-hidden rounded border",
91
95
  children: /* @__PURE__ */ jsx("img", {
@@ -97,57 +101,57 @@ const SortableAssetItem = (t0) => {
97
101
  className: "border-border bg-muted flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded border",
98
102
  children: /* @__PURE__ */ jsx(FileIcon, { className: "text-muted-foreground h-6 w-6" })
99
103
  });
100
- $[17] = asset.alt;
101
- $[18] = asset.filename;
102
- $[19] = asset.url;
103
- $[20] = assetType;
104
- $[21] = t11;
105
- } else t11 = $[21];
104
+ $[18] = asset.alt;
105
+ $[19] = asset.filename;
106
+ $[20] = asset.url;
107
+ $[21] = assetType;
108
+ $[22] = t11;
109
+ } else t11 = $[22];
106
110
  const t12 = asset.filename || "Untitled";
107
111
  let t13;
108
- if ($[22] !== asset.filename || $[23] !== t12) {
112
+ if ($[23] !== asset.filename || $[24] !== t12) {
109
113
  t13 = /* @__PURE__ */ jsx("p", {
110
114
  className: "flex-1 truncate text-left text-sm",
111
115
  title: asset.filename,
112
116
  children: t12
113
117
  });
114
- $[22] = asset.filename;
115
- $[23] = t12;
116
- $[24] = t13;
117
- } else t13 = $[24];
118
+ $[23] = asset.filename;
119
+ $[24] = t12;
120
+ $[25] = t13;
121
+ } else t13 = $[25];
118
122
  let t14;
119
- if ($[25] !== t10 || $[26] !== t11 || $[27] !== t13) {
123
+ if ($[26] !== t10 || $[27] !== t11 || $[28] !== t13) {
120
124
  t14 = /* @__PURE__ */ jsxs("button", {
121
125
  type: "button",
122
126
  className: "flex min-w-0 flex-1 cursor-zoom-in items-center gap-2",
123
127
  onClick: t10,
124
128
  children: [t11, t13]
125
129
  });
126
- $[25] = t10;
127
- $[26] = t11;
128
- $[27] = t13;
129
- $[28] = t14;
130
- } else t14 = $[28];
130
+ $[26] = t10;
131
+ $[27] = t11;
132
+ $[28] = t13;
133
+ $[29] = t14;
134
+ } else t14 = $[29];
131
135
  let t15;
132
- if ($[29] !== asset._fileId || $[30] !== onRemove) {
136
+ if ($[30] !== asset._fileId || $[31] !== onRemove) {
133
137
  t15 = () => onRemove(asset._fileId);
134
- $[29] = asset._fileId;
135
- $[30] = onRemove;
136
- $[31] = t15;
137
- } else t15 = $[31];
138
+ $[30] = asset._fileId;
139
+ $[31] = onRemove;
140
+ $[32] = t15;
141
+ } else t15 = $[32];
138
142
  let t16;
139
- if ($[32] !== asset._fileId || $[33] !== t15) {
143
+ if ($[33] !== asset._fileId || $[34] !== t15) {
140
144
  t16 = /* @__PURE__ */ jsx(UnlinkAssetButton, {
141
145
  fileId: asset._fileId,
142
146
  onUnlink: t15,
143
147
  className: "hidden group-focus-within:flex group-hover:flex"
144
148
  });
145
- $[32] = asset._fileId;
146
- $[33] = t15;
147
- $[34] = t16;
148
- } else t16 = $[34];
149
+ $[33] = asset._fileId;
150
+ $[34] = t15;
151
+ $[35] = t16;
152
+ } else t16 = $[35];
149
153
  let t17;
150
- if ($[35] !== setNodeRef || $[36] !== style || $[37] !== t14 || $[38] !== t16 || $[39] !== t7 || $[40] !== t9) {
154
+ if ($[36] !== setNodeRef || $[37] !== style || $[38] !== t14 || $[39] !== t16 || $[40] !== t7 || $[41] !== t9) {
151
155
  t17 = /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("div", {
152
156
  ref: setNodeRef,
153
157
  style,
@@ -158,44 +162,48 @@ const SortableAssetItem = (t0) => {
158
162
  t16
159
163
  ]
160
164
  }) });
161
- $[35] = setNodeRef;
162
- $[36] = style;
163
- $[37] = t14;
164
- $[38] = t16;
165
- $[39] = t7;
166
- $[40] = t9;
167
- $[41] = t17;
168
- } else t17 = $[41];
165
+ $[36] = setNodeRef;
166
+ $[37] = style;
167
+ $[38] = t14;
168
+ $[39] = t16;
169
+ $[40] = t7;
170
+ $[41] = t9;
171
+ $[42] = t17;
172
+ } else t17 = $[42];
169
173
  return t17;
170
174
  };
171
175
  const MultipleAssetFieldEditor = (t0) => {
172
- const $ = c(15);
176
+ const $ = c(16);
177
+ if ($[0] !== "edd84331a88dec86cb85175ea4b5855869d1ffb6129d2a357826a8aa2e61f7a0") {
178
+ for (let $i = 0; $i < 16; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
179
+ $[0] = "edd84331a88dec86cb85175ea4b5855869d1ffb6129d2a357826a8aa2e61f7a0";
180
+ }
173
181
  const { fieldName, assetType, currentData, onFieldChange } = t0;
174
182
  const contentKey = assetType === "Image" ? "image" : "file";
175
183
  const isImage = assetType === "Image";
176
184
  const fileInputRef = React.useRef(null);
177
185
  const projectSlug = useProjectSlug();
178
186
  let t1;
179
- if ($[0] !== projectSlug) {
187
+ if ($[1] !== projectSlug) {
180
188
  t1 = projectQueries.getBySlug(projectSlug);
181
- $[0] = projectSlug;
182
- $[1] = t1;
183
- } else t1 = $[1];
189
+ $[1] = projectSlug;
190
+ $[2] = t1;
191
+ } else t1 = $[2];
184
192
  const { data: project } = useQuery(t1);
185
193
  const rawItems = currentData[fieldName] ?? [];
186
194
  let t2;
187
- if ($[2] !== contentKey) {
195
+ if ($[3] !== contentKey) {
188
196
  t2 = (item) => item[contentKey];
189
- $[2] = contentKey;
190
- $[3] = t2;
191
- } else t2 = $[3];
197
+ $[3] = contentKey;
198
+ $[4] = t2;
199
+ } else t2 = $[4];
192
200
  const items = rawItems.map(t2).filter(_temp);
193
201
  let t3;
194
- if ($[4] !== contentKey) {
202
+ if ($[5] !== contentKey) {
195
203
  t3 = (assets) => assets.map((a_0) => ({ [contentKey]: { _fileId: a_0._fileId } }));
196
- $[4] = contentKey;
197
- $[5] = t3;
198
- } else t3 = $[5];
204
+ $[5] = contentKey;
205
+ $[6] = t3;
206
+ } else t3 = $[6];
199
207
  const toStorageFormat = t3;
200
208
  const addFileId = (fileId) => {
201
209
  onFieldChange(fieldName, [...toStorageFormat(items), { [contentKey]: { _fileId: fileId } }]);
@@ -209,10 +217,10 @@ const MultipleAssetFieldEditor = (t0) => {
209
217
  const [pickerOpen, setPickerOpen] = React.useState(false);
210
218
  const [lightboxAsset, setLightboxAsset] = React.useState(null);
211
219
  let t4;
212
- if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
220
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
213
221
  t4 = { coordinateGetter: sortableKeyboardCoordinates };
214
- $[6] = t4;
215
- } else t4 = $[6];
222
+ $[7] = t4;
223
+ } else t4 = $[7];
216
224
  const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, t4));
217
225
  const handleDragEnd = (event) => {
218
226
  const { active, over } = event;
@@ -269,7 +277,7 @@ const MultipleAssetFieldEditor = (t0) => {
269
277
  })]
270
278
  });
271
279
  let t6;
272
- if ($[7] !== lightboxAsset || $[8] !== setLightboxAsset) {
280
+ if ($[8] !== lightboxAsset || $[9] !== setLightboxAsset) {
273
281
  t6 = lightboxAsset && /* @__PURE__ */ jsx(AssetLightbox, {
274
282
  open: !!lightboxAsset,
275
283
  onOpenChange: (open) => {
@@ -277,22 +285,22 @@ const MultipleAssetFieldEditor = (t0) => {
277
285
  },
278
286
  fileId: lightboxAsset._fileId
279
287
  });
280
- $[7] = lightboxAsset;
281
- $[8] = setLightboxAsset;
282
- $[9] = t6;
283
- } else t6 = $[9];
288
+ $[8] = lightboxAsset;
289
+ $[9] = setLightboxAsset;
290
+ $[10] = t6;
291
+ } else t6 = $[10];
284
292
  let t7;
285
- if ($[10] !== T0 || $[11] !== t5 || $[12] !== t6 || $[13] !== uploadFiles) {
293
+ if ($[11] !== T0 || $[12] !== t5 || $[13] !== t6 || $[14] !== uploadFiles) {
286
294
  t7 = /* @__PURE__ */ jsxs(T0, {
287
295
  onDrop: uploadFiles,
288
296
  children: [t5, t6]
289
297
  });
290
- $[10] = T0;
291
- $[11] = t5;
292
- $[12] = t6;
293
- $[13] = uploadFiles;
294
- $[14] = t7;
295
- } else t7 = $[14];
298
+ $[11] = T0;
299
+ $[12] = t5;
300
+ $[13] = t6;
301
+ $[14] = uploadFiles;
302
+ $[15] = t7;
303
+ } else t7 = $[15];
296
304
  return t7;
297
305
  };
298
306
  function _temp(a) {
@@ -11,13 +11,17 @@ import * as React from "react";
11
11
  * Listening for focus commands from parent (for sidebar-triggered focus)
12
12
  */
13
13
  const OverlayTracker = () => {
14
- const $ = c(8);
14
+ const $ = c(9);
15
+ if ($[0] !== "ff61be3a0e44ee40cf7da4c940df8a337c7da414db4f8b92a3d25603d05282e6") {
16
+ for (let $i = 0; $i < 9; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
17
+ $[0] = "ff61be3a0e44ee40cf7da4c940df8a337c7da414db4f8b92a3d25603d05282e6";
18
+ }
15
19
  const { window: iframeWindow } = useFrame();
16
20
  const isPageContentSheetOpen = useSelector(previewStore, _temp);
17
21
  const isAddBlockSheetOpen = useSelector(previewStore, _temp2);
18
22
  let t0;
19
23
  let t1;
20
- if ($[0] !== iframeWindow || $[1] !== isAddBlockSheetOpen) {
24
+ if ($[1] !== iframeWindow || $[2] !== isAddBlockSheetOpen) {
21
25
  t0 = () => {
22
26
  if (!iframeWindow || !isAddBlockSheetOpen) return;
23
27
  const handleClick = _temp3;
@@ -25,18 +29,18 @@ const OverlayTracker = () => {
25
29
  return () => iframeWindow.document.removeEventListener("click", handleClick);
26
30
  };
27
31
  t1 = [iframeWindow, isAddBlockSheetOpen];
28
- $[0] = iframeWindow;
29
- $[1] = isAddBlockSheetOpen;
30
- $[2] = t0;
31
- $[3] = t1;
32
+ $[1] = iframeWindow;
33
+ $[2] = isAddBlockSheetOpen;
34
+ $[3] = t0;
35
+ $[4] = t1;
32
36
  } else {
33
- t0 = $[2];
34
- t1 = $[3];
37
+ t0 = $[3];
38
+ t1 = $[4];
35
39
  }
36
40
  React.useEffect(t0, t1);
37
41
  let t2;
38
42
  let t3;
39
- if ($[4] !== iframeWindow || $[5] !== isPageContentSheetOpen) {
43
+ if ($[5] !== iframeWindow || $[6] !== isPageContentSheetOpen) {
40
44
  t2 = () => {
41
45
  if (!iframeWindow) return;
42
46
  const handleMessage = (event) => {
@@ -53,13 +57,13 @@ const OverlayTracker = () => {
53
57
  return () => iframeWindow.removeEventListener("message", handleMessage);
54
58
  };
55
59
  t3 = [iframeWindow, isPageContentSheetOpen];
56
- $[4] = iframeWindow;
57
- $[5] = isPageContentSheetOpen;
58
- $[6] = t2;
59
- $[7] = t3;
60
+ $[5] = iframeWindow;
61
+ $[6] = isPageContentSheetOpen;
62
+ $[7] = t2;
63
+ $[8] = t3;
60
64
  } else {
61
- t2 = $[6];
62
- t3 = $[7];
65
+ t2 = $[7];
66
+ t3 = $[8];
63
67
  }
64
68
  React.useEffect(t2, t3);
65
69
  return null;
@@ -8,7 +8,11 @@ import * as React from "react";
8
8
 
9
9
  //#region src/features/preview/components/Overlays.tsx
10
10
  const Overlays = (t0) => {
11
- const $ = c(14);
11
+ const $ = c(15);
12
+ if ($[0] !== "f759f7c90424510dda53603baa60750e2781cc80efac98df6bfb96a5f03ff4f9") {
13
+ for (let $i = 0; $i < 15; $i += 1) $[$i] = Symbol.for("react.memo_cache_sentinel");
14
+ $[0] = "f759f7c90424510dda53603baa60750e2781cc80efac98df6bfb96a5f03ff4f9";
15
+ }
12
16
  const { iframeElement } = t0;
13
17
  const isPageContentSheetOpen = useSelector(previewStore, _temp);
14
18
  const selection = useSelector(previewStore, _temp2);
@@ -16,7 +20,7 @@ const Overlays = (t0) => {
16
20
  const page = usePreviewedPage();
17
21
  const { pageBlocks } = usePageBlocks(page);
18
22
  let t1;
19
- if ($[0] !== pageBlocks) {
23
+ if ($[1] !== pageBlocks) {
20
24
  t1 = () => {
21
25
  const handleMessage = (event) => {
22
26
  if (!isOverlayMessage(event.data)) return;
@@ -40,18 +44,18 @@ const Overlays = (t0) => {
40
44
  window.addEventListener("message", handleMessage);
41
45
  return () => window.removeEventListener("message", handleMessage);
42
46
  };
43
- $[0] = pageBlocks;
44
- $[1] = t1;
45
- } else t1 = $[1];
47
+ $[1] = pageBlocks;
48
+ $[2] = t1;
49
+ } else t1 = $[2];
46
50
  let t2;
47
- if ($[2] !== page) {
51
+ if ($[3] !== page) {
48
52
  t2 = [page];
49
- $[2] = page;
50
- $[3] = t2;
51
- } else t2 = $[3];
53
+ $[3] = page;
54
+ $[4] = t2;
55
+ } else t2 = $[4];
52
56
  React.useEffect(t1, t2);
53
57
  let t3;
54
- if ($[4] !== iframeElement?.contentWindow || $[5] !== isPageContentSheetOpen || $[6] !== peekedBlock || $[7] !== selection) {
58
+ if ($[5] !== iframeElement?.contentWindow || $[6] !== isPageContentSheetOpen || $[7] !== peekedBlock || $[8] !== selection) {
55
59
  t3 = () => {
56
60
  if (isPageContentSheetOpen) return;
57
61
  if (peekedBlock) return;
@@ -66,26 +70,26 @@ const Overlays = (t0) => {
66
70
  };
67
71
  iframeElement?.contentWindow?.postMessage(message_0, "*");
68
72
  };
69
- $[4] = iframeElement?.contentWindow;
70
- $[5] = isPageContentSheetOpen;
71
- $[6] = peekedBlock;
72
- $[7] = selection;
73
- $[8] = t3;
74
- } else t3 = $[8];
73
+ $[5] = iframeElement?.contentWindow;
74
+ $[6] = isPageContentSheetOpen;
75
+ $[7] = peekedBlock;
76
+ $[8] = selection;
77
+ $[9] = t3;
78
+ } else t3 = $[9];
75
79
  let t4;
76
- if ($[9] !== iframeElement || $[10] !== isPageContentSheetOpen || $[11] !== peekedBlock || $[12] !== selection) {
80
+ if ($[10] !== iframeElement || $[11] !== isPageContentSheetOpen || $[12] !== peekedBlock || $[13] !== selection) {
77
81
  t4 = [
78
82
  selection,
79
83
  isPageContentSheetOpen,
80
84
  peekedBlock,
81
85
  iframeElement
82
86
  ];
83
- $[9] = iframeElement;
84
- $[10] = isPageContentSheetOpen;
85
- $[11] = peekedBlock;
86
- $[12] = selection;
87
- $[13] = t4;
88
- } else t4 = $[13];
87
+ $[10] = iframeElement;
88
+ $[11] = isPageContentSheetOpen;
89
+ $[12] = peekedBlock;
90
+ $[13] = selection;
91
+ $[14] = t4;
92
+ } else t4 = $[14];
89
93
  React.useEffect(t3, t4);
90
94
  return null;
91
95
  };
@@ -7,6 +7,7 @@ import { useCamoxApp } from "../../provider/components/CamoxAppContext.js";
7
7
  import { trackClientEvent } from "../../../lib/analytics-client.js";
8
8
  import { fieldTypesDictionary } from "../../../core/lib/fieldTypes.js";
9
9
  import { SingleAssetFieldEditor } from "./AssetFieldEditor.js";
10
+ import { useRepeatableItemActions } from "./useRepeatableItemActions.js";
10
11
  import { ItemFieldsEditor, formatFieldName } from "./ItemFieldsEditor.js";
11
12
  import { LinkFieldEditor } from "./LinkFieldEditor.js";
12
13
  import { MultipleAssetFieldEditor } from "./MultipleAssetFieldEditor.js";
@@ -16,8 +17,11 @@ import { useMutation, useQueries, useQuery } from "@tanstack/react-query";
16
17
  import { useSelector } from "@xstate/store/react";
17
18
  import * as React from "react";
18
19
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
20
+ import { Button } from "@camox/ui/button";
21
+ import { CircleMinus, CirclePlus, CornerLeftUp } from "lucide-react";
19
22
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@camox/ui/dropdown-menu";
20
23
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@camox/ui/select";
24
+ import { Spinner } from "@camox/ui/spinner";
21
25
  import { Switch } from "@camox/ui/switch";
22
26
  import { Breadcrumb, BreadcrumbEllipsis, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@camox/ui/breadcrumb";
23
27
 
@@ -151,6 +155,20 @@ const PageContentSheet = () => {
151
155
  itemsMap
152
156
  ]);
153
157
  const currentItem = currentItemId != null ? itemsMap.get(currentItemId) : null;
158
+ const isItemLoading = currentItemId != null && !currentItem;
159
+ const siblingCount = React.useMemo(() => {
160
+ if (!currentItem) return 0;
161
+ let count = 0;
162
+ for (const it of itemsMap.values()) if (it.fieldName === currentItem.fieldName && it.parentItemId === currentItem.parentItemId) count++;
163
+ return count;
164
+ }, [currentItem, itemsMap]);
165
+ const { canAdd: canAddSibling, addItem: addSibling, canRemove: canRemoveCurrent, removeItem: removeCurrent } = useRepeatableItemActions({
166
+ blockId: block?.id ?? -1,
167
+ fieldName: currentItem?.fieldName ?? "",
168
+ parentItemId: currentItem?.parentItemId ?? null,
169
+ arraySchema: itemArraySchema,
170
+ siblingCount
171
+ });
154
172
  const rawCurrentData = currentItem ? currentItem.content : block?.content ?? {};
155
173
  const currentData = React.useMemo(() => {
156
174
  const resolveFile = (marker) => {
@@ -353,9 +371,12 @@ const PageContentSheet = () => {
353
371
  ]
354
372
  })
355
373
  })]
356
- }), /* @__PURE__ */ jsxs("div", {
374
+ }), /* @__PURE__ */ jsx("div", {
357
375
  className: "flex-1 overflow-auto",
358
- children: [
376
+ children: isItemLoading ? /* @__PURE__ */ jsx("div", {
377
+ className: "flex h-full items-center justify-center py-12",
378
+ children: /* @__PURE__ */ jsx(Spinner, {})
379
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
359
380
  currentItemId == null && !fieldHasOwnView && settingsFields.length > 0 && /* @__PURE__ */ jsxs("div", {
360
381
  className: "border-border space-y-4 border-b px-4 py-4",
361
382
  children: [/* @__PURE__ */ jsx(Label, {
@@ -505,8 +526,37 @@ const PageContentSheet = () => {
505
526
  itemsMap,
506
527
  fieldIdPrefix,
507
528
  autoFocusFieldName
508
- }, currentItemId ?? `block-${block.id}`)
509
- ]
529
+ }, currentItemId ?? `block-${block.id}`),
530
+ !isViewingAsset && !isViewingLink && currentItemId != null && currentItem && /* @__PURE__ */ jsxs("div", {
531
+ className: "border-border flex items-center gap-1 border-t px-4 py-4",
532
+ children: [
533
+ canAddSibling && /* @__PURE__ */ jsxs(Button, {
534
+ type: "button",
535
+ variant: "ghost",
536
+ size: "sm",
537
+ className: "text-muted-foreground justify-start",
538
+ onClick: () => addSibling({ afterPosition: currentItem.position }),
539
+ children: [/* @__PURE__ */ jsx(CirclePlus, { className: "h-4 w-4" }), "Add item"]
540
+ }),
541
+ canRemoveCurrent && /* @__PURE__ */ jsxs(Button, {
542
+ type: "button",
543
+ variant: "ghost",
544
+ size: "sm",
545
+ className: "text-muted-foreground justify-start",
546
+ onClick: () => removeCurrent(currentItemId, { onSuccess: () => previewStore.send({ type: "selectParent" }) }),
547
+ children: [/* @__PURE__ */ jsx(CircleMinus, { className: "h-4 w-4" }), "Remove item"]
548
+ }),
549
+ /* @__PURE__ */ jsxs(Button, {
550
+ type: "button",
551
+ variant: "ghost",
552
+ size: "sm",
553
+ className: "text-muted-foreground justify-start",
554
+ onClick: () => previewStore.send({ type: "selectParent" }),
555
+ children: [/* @__PURE__ */ jsx(CornerLeftUp, { className: "h-4 w-4" }), "Select parent"]
556
+ })
557
+ ]
558
+ })
559
+ ] })
510
560
  })]
511
561
  });
512
562
  };