@alpaca-editor/core 1.0.4000 → 1.0.4007

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 (141) hide show
  1. package/dist/client-components/index.d.ts +1 -0
  2. package/dist/client-components/index.js +1 -0
  3. package/dist/client-components/index.js.map +1 -1
  4. package/dist/components/ui/calendar.d.ts +7 -0
  5. package/dist/components/ui/calendar.js +62 -0
  6. package/dist/components/ui/calendar.js.map +1 -0
  7. package/dist/config/config.js +11 -0
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/editor/ContentTree.d.ts +2 -1
  10. package/dist/editor/ContentTree.js +2 -2
  11. package/dist/editor/ContentTree.js.map +1 -1
  12. package/dist/editor/FieldList.js +37 -10
  13. package/dist/editor/FieldList.js.map +1 -1
  14. package/dist/editor/FieldListField.js +21 -10
  15. package/dist/editor/FieldListField.js.map +1 -1
  16. package/dist/editor/client/EditorClient.js +5 -3
  17. package/dist/editor/client/EditorClient.js.map +1 -1
  18. package/dist/editor/client/itemsRepository.js +57 -9
  19. package/dist/editor/client/itemsRepository.js.map +1 -1
  20. package/dist/editor/client/pageModelBuilder.js +1 -1
  21. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  22. package/dist/editor/commands/itemCommands.js +1 -1
  23. package/dist/editor/commands/itemCommands.js.map +1 -1
  24. package/dist/editor/field-types/DateFieldEditor.d.ts +5 -0
  25. package/dist/editor/field-types/DateFieldEditor.js +93 -0
  26. package/dist/editor/field-types/DateFieldEditor.js.map +1 -0
  27. package/dist/editor/field-types/ImageFieldEditor.js +1 -1
  28. package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
  29. package/dist/editor/field-types/InternalLinkFieldEditor.js +1 -1
  30. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  31. package/dist/editor/field-types/LinkFieldEditor.js +1 -1
  32. package/dist/editor/field-types/LinkFieldEditor.js.map +1 -1
  33. package/dist/editor/field-types/MultiLineText.js +3 -1
  34. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  35. package/dist/editor/field-types/PictureFieldEditor.js +1 -1
  36. package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
  37. package/dist/editor/field-types/SingleLineText.js +3 -1
  38. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  39. package/dist/editor/field-types/TreeListEditor.js +71 -6
  40. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  41. package/dist/editor/field-types/richtext/components/ReactSlate.js +1 -1
  42. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  43. package/dist/editor/fieldTypes.d.ts +1 -1
  44. package/dist/editor/media-selector/AiImageSearch.js +2 -1
  45. package/dist/editor/media-selector/AiImageSearch.js.map +1 -1
  46. package/dist/editor/media-selector/AiImageSearchPrompt.js +2 -1
  47. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  48. package/dist/editor/page-viewer/DeviceToolbar.js +3 -2
  49. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  50. package/dist/editor/page-viewer/MiniMap.js +6 -1
  51. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  52. package/dist/editor/services/aiService.d.ts +9 -1
  53. package/dist/editor/services/aiService.js.map +1 -1
  54. package/dist/editor/sidebar/Insert.js +1 -1
  55. package/dist/editor/sidebar/Insert.js.map +1 -1
  56. package/dist/editor/sidebar/MainContentTree.d.ts +1 -1
  57. package/dist/editor/sidebar/MainContentTree.js +19 -7
  58. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  59. package/dist/editor/ui/Icons.js +1 -1
  60. package/dist/editor/ui/Icons.js.map +1 -1
  61. package/dist/editor/ui/ItemNameDialogNew.js +14 -10
  62. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  63. package/dist/editor/ui/PerfectTree.d.ts +2 -0
  64. package/dist/editor/ui/PerfectTree.js +1 -1
  65. package/dist/editor/ui/PerfectTree.js.map +1 -1
  66. package/dist/editor/ui/Spinner.js +1 -1
  67. package/dist/editor/ui/Spinner.js.map +1 -1
  68. package/dist/page-wizard/steps/ContentStep.js +56 -17
  69. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  70. package/dist/page-wizard/steps/FindItemsStep.d.ts +2 -0
  71. package/dist/page-wizard/steps/FindItemsStep.js +293 -0
  72. package/dist/page-wizard/steps/FindItemsStep.js.map +1 -0
  73. package/dist/page-wizard/steps/ImagesStep.js +9 -1
  74. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  75. package/dist/page-wizard/steps/LayoutStep.js +24 -14
  76. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  77. package/dist/page-wizard/steps/MetaDataStep.js +21 -11
  78. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  79. package/dist/page-wizard/steps/SelectStep.js +38 -31
  80. package/dist/page-wizard/steps/SelectStep.js.map +1 -1
  81. package/dist/page-wizard/steps/StructureStep.d.ts +2 -0
  82. package/dist/page-wizard/steps/StructureStep.js +156 -0
  83. package/dist/page-wizard/steps/StructureStep.js.map +1 -0
  84. package/dist/page-wizard/utils/dataAccessor.d.ts +39 -0
  85. package/dist/page-wizard/utils/dataAccessor.js +222 -0
  86. package/dist/page-wizard/utils/dataAccessor.js.map +1 -0
  87. package/dist/revision.d.ts +2 -2
  88. package/dist/revision.js +2 -2
  89. package/dist/splash-screen/RecentPages.js +14 -3
  90. package/dist/splash-screen/RecentPages.js.map +1 -1
  91. package/dist/styles.css +178 -0
  92. package/package.json +3 -1
  93. package/src/client-components/index.ts +1 -3
  94. package/src/components/ui/calendar.tsx +175 -0
  95. package/src/config/config.tsx +11 -1
  96. package/src/editor/ContentTree.tsx +4 -1
  97. package/src/editor/FieldList.tsx +44 -18
  98. package/src/editor/FieldListField.tsx +98 -23
  99. package/src/editor/client/EditorClient.tsx +6 -3
  100. package/src/editor/client/itemsRepository.ts +74 -10
  101. package/src/editor/client/pageModelBuilder.ts +1 -1
  102. package/src/editor/commands/itemCommands.tsx +2 -3
  103. package/src/editor/field-types/DateFieldEditor.tsx +132 -0
  104. package/src/editor/field-types/ImageFieldEditor.tsx +1 -1
  105. package/src/editor/field-types/InternalLinkFieldEditor.tsx +2 -2
  106. package/src/editor/field-types/LinkFieldEditor.tsx +2 -2
  107. package/src/editor/field-types/MultiLineText.tsx +5 -1
  108. package/src/editor/field-types/PictureFieldEditor.tsx +1 -1
  109. package/src/editor/field-types/SingleLineText.tsx +5 -1
  110. package/src/editor/field-types/TreeListEditor.tsx +136 -50
  111. package/src/editor/field-types/richtext/components/ReactSlate.tsx +5 -0
  112. package/src/editor/fieldTypes.ts +1 -1
  113. package/src/editor/media-selector/AiImageSearch.tsx +2 -1
  114. package/src/editor/media-selector/AiImageSearchPrompt.tsx +2 -1
  115. package/src/editor/page-viewer/DeviceToolbar.tsx +17 -5
  116. package/src/editor/page-viewer/MiniMap.tsx +7 -2
  117. package/src/editor/services/aiService.ts +10 -1
  118. package/src/editor/sidebar/Insert.tsx +1 -1
  119. package/src/editor/sidebar/MainContentTree.tsx +26 -14
  120. package/src/editor/ui/Icons.tsx +16 -5
  121. package/src/editor/ui/ItemNameDialogNew.tsx +42 -30
  122. package/src/editor/ui/PerfectTree.tsx +3 -1
  123. package/src/editor/ui/Spinner.tsx +3 -1
  124. package/src/page-wizard/steps/ContentStep.tsx +68 -20
  125. package/src/page-wizard/steps/FindItemsStep.tsx +546 -0
  126. package/src/page-wizard/steps/ImagesStep.tsx +13 -1
  127. package/src/page-wizard/steps/LayoutStep.tsx +27 -16
  128. package/src/page-wizard/steps/MetaDataStep.tsx +26 -13
  129. package/src/page-wizard/steps/SelectStep.tsx +61 -32
  130. package/src/page-wizard/steps/StructureStep.tsx +350 -0
  131. package/src/page-wizard/utils/dataAccessor.ts +275 -0
  132. package/src/revision.ts +2 -2
  133. package/src/splash-screen/RecentPages.tsx +22 -3
  134. package/dist/editor/ai/GhostWriter.d.ts +0 -1
  135. package/dist/editor/ai/GhostWriter.js +0 -347
  136. package/dist/editor/ai/GhostWriter.js.map +0 -1
  137. package/dist/editor/component-designer/ComponentDesignerAiTerminal.d.ts +0 -1
  138. package/dist/editor/component-designer/ComponentDesignerAiTerminal.js +0 -7
  139. package/dist/editor/component-designer/ComponentDesignerAiTerminal.js.map +0 -1
  140. /package/src/editor/ai/{GhostWriter.tsx → GhostWriter.tsx_} +0 -0
  141. /package/src/editor/component-designer/{ComponentDesignerAiTerminal.tsx → ComponentDesignerAiTerminal.tsx_} +0 -0
@@ -2,7 +2,6 @@ import { Splitter, SplitterPanel } from "primereact/splitter";
2
2
  import { useEditContext } from "../client/editContext";
3
3
  import { useEffect, useState } from "react";
4
4
 
5
- import { ListBox } from "primereact/listbox";
6
5
  import { ReferencedItem, TreeListField } from "../fieldTypes";
7
6
  import { getLookupSources } from "../services/editService";
8
7
  import { SimpleIconButton } from "../ui/SimpleIconButton";
@@ -39,6 +38,7 @@ export default function TreeListEditor({
39
38
  const [values, setValues] = useState<ReferencedItem[]>([]);
40
39
  const [hoveredItemId, setHoveredItemId] = useState<string | undefined>();
41
40
  const [hoveredItem, setHoveredItem] = useState<FullItem | undefined>();
41
+ const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
42
42
 
43
43
  if (!editContext) return;
44
44
 
@@ -147,9 +147,49 @@ export default function TreeListEditor({
147
147
  });
148
148
  }
149
149
 
150
+ const handleDragStart = (event: React.DragEvent, index: number) => {
151
+ event.dataTransfer.setData("text/plain", index.toString());
152
+ event.dataTransfer.effectAllowed = "move";
153
+ setDraggedIndex(index);
154
+ };
155
+
156
+ const handleDragOver = (event: React.DragEvent) => {
157
+ event.preventDefault();
158
+ event.dataTransfer.dropEffect = "move";
159
+ };
160
+
161
+ const handleDrop = async (event: React.DragEvent, dropIndex: number) => {
162
+ event.preventDefault();
163
+ const dragIndex = parseInt(event.dataTransfer.getData("text/plain"));
164
+
165
+ if (dragIndex === dropIndex) {
166
+ setDraggedIndex(null);
167
+ return;
168
+ }
169
+
170
+ const newValues = [...values];
171
+ const [draggedItem] = newValues.splice(dragIndex, 1);
172
+ if (!draggedItem) return;
173
+ newValues.splice(dropIndex, 0, draggedItem);
174
+
175
+ setValues(newValues);
176
+ setDraggedIndex(null);
177
+
178
+ await editContext?.operations.editField({
179
+ field: field.descriptor,
180
+ value: newValues,
181
+ rawValue: newValues.map((x) => normalizeGuid(x.id)).join("|"),
182
+ refresh: "waitForQuietPeriod",
183
+ });
184
+ };
185
+
186
+ const handleDragEnd = () => {
187
+ setDraggedIndex(null);
188
+ };
189
+
150
190
  return (
151
191
  <div className="focus-shadow border border-gray-200">
152
- <div className="mb-1 border-b border-gray-200 bg-white p-2">
192
+ <div className="bg-gray-5 mb-1 border-b border-gray-200 p-2">
153
193
  <ItemSearch
154
194
  rootItemIds={rootItemIds.map((x) => normalizeGuid(x))}
155
195
  itemSelected={async (item) => {
@@ -208,56 +248,102 @@ export default function TreeListEditor({
208
248
  />
209
249
  </div>
210
250
  <div className="relative h-full flex-1">
211
- <ListBox
212
- className="absolute inset-0 overflow-auto text-xs"
213
- options={values}
214
- optionLabel="path"
215
- metaKeySelection={true}
216
- emptyMessage={"None selected"}
217
- multiple={true}
218
- value={selectedFromList}
219
- onChange={(e) => setSelectedFromList(e.value)}
220
- itemTemplate={(option) => (
221
- <div
222
- className="group relative flex items-center gap-1.5 select-none hover:bg-gray-50"
223
- onMouseEnter={async () => {
224
- setHoveredItemId(option.id);
225
- // setExpandIdPath(option.idPath || "");
226
- setSelectedItemNodesInTree([option]);
227
- }}
228
- onDoubleClick={() => {
229
- if (!readOnly) removeFromList([option]);
230
- }}
231
- >
232
- <img src={option.icon} className="h-4 w-4" />
233
- {trimPath(option.path)}{" "}
234
- {activeOpenButtonId === option.id ? (
235
- <Button
236
- className="h-4 p-1"
237
- size="sm"
238
- onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
239
- e.stopPropagation();
240
- editContext.loadItem(option.id);
241
- setActiveOpenButtonId("");
242
- }}
243
- >
244
- Open
245
- </Button>
246
- ) : (
247
- <button
248
- className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100"
249
- onClick={(e) => {
250
- e.stopPropagation();
251
- setActiveOpenButtonId(option.id);
252
- }}
253
- title="Open"
254
- >
255
- <i className="pi pi-arrow-up-right text-xs text-gray-500" />
256
- </button>
257
- )}
251
+ <div className="absolute inset-0 overflow-auto border border-gray-300 bg-white text-xs">
252
+ {values.length === 0 ? (
253
+ <div className="p-3 text-center text-gray-500">
254
+ None selected
258
255
  </div>
256
+ ) : (
257
+ values.map((option, index) => (
258
+ <div
259
+ key={option.id}
260
+ draggable={!readOnly}
261
+ onDragStart={(e) => handleDragStart(e, index)}
262
+ onDragOver={handleDragOver}
263
+ onDrop={(e) => handleDrop(e, index)}
264
+ onDragEnd={handleDragEnd}
265
+ className={`group relative flex cursor-pointer items-center gap-1.5 border-b border-gray-100 p-1 select-none last:border-b-0 hover:bg-gray-50 ${
266
+ selectedFromList.includes(option) ? "bg-blue-100" : ""
267
+ } ${draggedIndex === index ? "opacity-50" : ""} ${
268
+ !readOnly ? "cursor-move" : ""
269
+ }`}
270
+ onClick={(e) => {
271
+ if (e.ctrlKey) {
272
+ // Multi-select mode when Ctrl is pressed
273
+ if (selectedFromList.includes(option)) {
274
+ setSelectedFromList(
275
+ selectedFromList.filter(
276
+ (item) => item.id !== option.id,
277
+ ),
278
+ );
279
+ } else {
280
+ setSelectedFromList([
281
+ ...selectedFromList,
282
+ option,
283
+ ]);
284
+ }
285
+ } else {
286
+ // Single-select mode when Ctrl is not pressed
287
+ if (
288
+ selectedFromList.length === 1 &&
289
+ selectedFromList.includes(option)
290
+ ) {
291
+ // Deselect if this is the only selected item
292
+ setSelectedFromList([]);
293
+ } else {
294
+ // Replace selection with just this item
295
+ setSelectedFromList([option]);
296
+ }
297
+ }
298
+ }}
299
+ onMouseEnter={async () => {
300
+ setHoveredItemId(option.id);
301
+ setSelectedItemNodesInTree([
302
+ {
303
+ id: option.id,
304
+ path: option.path,
305
+ name: option.name,
306
+ icon: option.icon,
307
+ idPath: option.idPath || "",
308
+ },
309
+ ]);
310
+ }}
311
+ onDoubleClick={() => {
312
+ if (!readOnly) removeFromList([option]);
313
+ }}
314
+ >
315
+ <img src={option.icon} className="h-4 w-4" />
316
+ {trimPath(option.path)}{" "}
317
+ {activeOpenButtonId === option.id ? (
318
+ <Button
319
+ className="h-4 p-1"
320
+ size="sm"
321
+ onClick={(
322
+ e: React.MouseEvent<HTMLButtonElement>,
323
+ ) => {
324
+ e.stopPropagation();
325
+ editContext.loadItem(option.id);
326
+ setActiveOpenButtonId("");
327
+ }}
328
+ >
329
+ Open
330
+ </Button>
331
+ ) : (
332
+ <button
333
+ className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100"
334
+ onClick={(e) => {
335
+ e.stopPropagation();
336
+ setActiveOpenButtonId(option.id);
337
+ }}
338
+ title="Open"
339
+ >
340
+ <i className="pi pi-arrow-up-right text-xs text-gray-500" />
341
+ </button>
342
+ )}
343
+ </div>
344
+ ))
259
345
  )}
260
- />
346
+ </div>
261
347
  </div>
262
348
  </div>
263
349
  </div>
@@ -724,6 +724,11 @@ export const ReactSlate = forwardRef<any, ReactSlateProps>((props, ref) => {
724
724
  )}
725
725
  readOnly={readOnly}
726
726
  placeholder={placeholder}
727
+ renderPlaceholder={({ attributes, children }) => (
728
+ <span {...attributes} className="p-2 text-gray-500">
729
+ {children}
730
+ </span>
731
+ )}
727
732
  onFocus={onFocus}
728
733
  onBlur={onBlur}
729
734
  onKeyDown={handleKeyDown}
@@ -124,7 +124,7 @@ export type DateField = Field & {
124
124
  value: DateFieldValue;
125
125
  };
126
126
 
127
- export type DateFieldValue = Field & {
127
+ export type DateFieldValue = {
128
128
  date: string;
129
129
  };
130
130
 
@@ -70,7 +70,8 @@ export function AiImageSearch({
70
70
  );
71
71
 
72
72
  setLoadingPrompt(false);
73
- const newPrompt = result.responseText.replaceAll("\n", "");
73
+ const lastMessage = result?.messages?.[result.messages.length - 1];
74
+ const newPrompt = lastMessage?.content?.replaceAll("\n", "") || "";
74
75
  setPrompt(newPrompt);
75
76
  } catch (error) {
76
77
  if (error instanceof Error && error.name !== "AbortError") {
@@ -53,7 +53,8 @@ export function AiImageSearchPrompt({
53
53
  );
54
54
 
55
55
  setLoadingPrompt(false);
56
- const newPrompt = result.responseText.replaceAll("\n", "");
56
+ const lastMessage = result?.messages?.[result.messages.length - 1];
57
+ const newPrompt = lastMessage?.content?.replaceAll("\n", "") || "";
57
58
  setPrompt(newPrompt);
58
59
  } catch (error) {
59
60
  if (error instanceof Error && error.name !== "AbortError") {
@@ -5,6 +5,7 @@ import { RotateDeviceIcon } from "../ui/Icons";
5
5
  import { useDebouncedCallback } from "use-debounce";
6
6
  import { PageViewContext } from "./pageViewContext";
7
7
  import { EditorConfiguration } from "../../config/types";
8
+ import { cn } from "../../lib/utils";
8
9
 
9
10
  export function DeviceToolbar({
10
11
  pageViewContext,
@@ -17,17 +18,17 @@ export function DeviceToolbar({
17
18
  (width: number | undefined) => {
18
19
  pageViewContext.setDeviceWidth(width);
19
20
  },
20
- 400
21
+ 400,
21
22
  );
22
23
  const debouncedSetDeviceHeight = useDebouncedCallback(
23
24
  (height: number | undefined) => {
24
25
  pageViewContext.setDeviceHeight(height);
25
26
  },
26
- 400
27
+ 400,
27
28
  );
28
29
 
29
30
  return (
30
- <div className="flex justify-center w-full items-center bg-gray-100 p-1 z-1000 text-sm gap-2 border-b">
31
+ <div className="bg-gray-4 z-1000 mb-2 flex w-full items-center justify-center gap-2 text-sm">
31
32
  <Dropdown
32
33
  options={configuration.devices.map((x) => ({
33
34
  label: x.name,
@@ -53,6 +54,7 @@ export function DeviceToolbar({
53
54
  />
54
55
  <SimpleIconButton
55
56
  icon="pi pi-lock"
57
+ className="hover:bg-gray-3"
56
58
  selected={pageViewContext.lockHeight || false}
57
59
  onClick={() =>
58
60
  pageViewContext.setLockHeight(!pageViewContext.lockHeight)
@@ -60,10 +62,20 @@ export function DeviceToolbar({
60
62
  label="Lock Height"
61
63
  />
62
64
  <SimpleIconButton
63
- icon={<RotateDeviceIcon className="w-4 h-4" />}
65
+ className="hover:bg-gray-3"
66
+ icon={
67
+ <RotateDeviceIcon
68
+ className={cn(
69
+ "h-4 w-4 transition-transform duration-300",
70
+ pageViewContext.rotate ? "rotate-90" : "",
71
+ )}
72
+ />
73
+ }
64
74
  selected={pageViewContext.rotate || false}
65
75
  onClick={() => pageViewContext.setRotate(!pageViewContext.rotate)}
66
- label="Rotate"
76
+ label={
77
+ pageViewContext.rotate ? "Switch to Portrait" : "Switch to Landscape"
78
+ }
67
79
  />
68
80
  </div>
69
81
  );
@@ -123,9 +123,14 @@ export function MiniMap({
123
123
  }
124
124
 
125
125
  if (correspondingNode && originalNode) {
126
- if (originalNode.tagName === "HEAD")
126
+ if (originalNode.tagName === "HEAD") {
127
127
  correspondingNode.innerHTML = originalNode.innerHTML;
128
- else {
128
+ } else if (
129
+ correspondingNode.parentNode?.nodeType === Node.DOCUMENT_NODE
130
+ ) {
131
+ // Can't use outerHTML when parent is the document node
132
+ correspondingNode.innerHTML = originalNode.innerHTML;
133
+ } else {
129
134
  correspondingNode.outerHTML = originalNode.outerHTML;
130
135
  }
131
136
  }
@@ -34,6 +34,15 @@ type Message = {
34
34
  role: string;
35
35
  };
36
36
 
37
+ export interface ExecutePromptResponse {
38
+ editOperations: any[];
39
+ messages: Message[];
40
+ numInputTokens: number;
41
+ numOutputTokens: number;
42
+ numCachedTokens: number;
43
+ state: string;
44
+ }
45
+
37
46
  export async function executePrompt(
38
47
  messages: Message[],
39
48
  editContext: EditContextType,
@@ -48,7 +57,7 @@ export async function executePrompt(
48
57
  options?: RequestInit,
49
58
  model?: string,
50
59
  callback?: (response: any) => void,
51
- ): Promise<any> {
60
+ ): Promise<ExecutePromptResponse | null> {
52
61
  const context = createAiContext({ editContext });
53
62
 
54
63
  const response = await fetch(context.endpoint, {
@@ -16,7 +16,7 @@ export function Insert() {
16
16
  content: (
17
17
  <div className="relative flex-1">
18
18
  <div className="absolute inset-0 overflow-auto">
19
- <MainContentTree mode="insert" />
19
+ <MainContentTree mode="insert-component" />
20
20
  </div>
21
21
  </div>
22
22
  ),
@@ -10,7 +10,7 @@ export function MainContentTree({
10
10
  mode,
11
11
  rootItemId,
12
12
  }: {
13
- mode: "insert" | "normal" | "select-page";
13
+ mode: "insert-component" | "normal" | "select-page";
14
14
  rootItemId?: string;
15
15
  }) {
16
16
  const editContext = useEditContext();
@@ -22,17 +22,28 @@ export function MainContentTree({
22
22
  const [selectedItemIds, setSelectedItemIds] = useState<string[]>([]);
23
23
 
24
24
  const isDraggable = useCallback(
25
- (x: ItemTreeNodeData) => {
26
- return (
27
- mode === "insert" &&
28
- insertOptions?.find(
29
- (insertOption: InsertOption) =>
30
- insertOption.typeId == x.templateId ||
31
- insertOption.compatibleTypeIds?.find(
32
- (compatibleTemplate) => compatibleTemplate == x.templateId,
33
- ) !== undefined,
34
- ) !== undefined
35
- );
25
+ (item: ItemTreeNodeData) => {
26
+ // Only allow dragging in insert-component mode
27
+ if (mode !== "insert-component") {
28
+ return true;
29
+ }
30
+
31
+ if (!insertOptions) return false;
32
+
33
+ // Check if the item's template is compatible with any insert option
34
+ return insertOptions.some((insertOption) => {
35
+ // Direct type match
36
+ if (insertOption.typeId === item.templateId) {
37
+ return true;
38
+ }
39
+
40
+ // Compatible type match
41
+ return (
42
+ insertOption.compatibleTypeIds?.some(
43
+ (compatibleTypeId) => compatibleTypeId === item.templateId,
44
+ ) ?? false
45
+ );
46
+ });
36
47
  },
37
48
  [mode, insertOptions],
38
49
  );
@@ -76,10 +87,10 @@ export function MainContentTree({
76
87
  scrollToSelected={true}
77
88
  className="h-full"
78
89
  selectPagesOnly={mode === "select-page"}
79
- selectionMode={mode == "insert" ? "none" : "multiple"}
90
+ selectionMode={mode == "insert-component" ? "none" : "multiple"}
80
91
  selectedItemIds={selectedItemIds}
81
92
  onSelectionChange={(selection) => {
82
- if (mode === "insert") return;
93
+ if (mode === "insert-component") return;
83
94
  const selectedItems = selection as ItemTreeNodeData[];
84
95
  setSelectedItemIds(selectedItems.map((x) => x.id));
85
96
  if (selectedItems.length > 0 && selectedItems[0])
@@ -89,6 +100,7 @@ export function MainContentTree({
89
100
  version: selectedItems[0].version,
90
101
  });
91
102
  }}
103
+ showGrabCursorForDraggableNodes={mode === "insert-component"}
92
104
  isDraggable={isDraggable}
93
105
  renderNode={(node, defaultRenderer) => (
94
106
  <div className="group flex w-full gap-4">
@@ -393,14 +393,25 @@ export function FormEditIcon({ className }: { className?: string }) {
393
393
  export function RotateDeviceIcon({ className }: { className?: string }) {
394
394
  return (
395
395
  <svg
396
- viewBox="0 0 24 24"
396
+ width="16"
397
+ height="18"
398
+ viewBox="0 0 16 18"
399
+ fill="none"
397
400
  xmlns="http://www.w3.org/2000/svg"
398
401
  className={className}
399
- stroke="currentColor"
400
- fill="currentColor"
401
402
  >
402
- <path d="M21.323 8.616l-4.94-4.94a1.251 1.251 0 0 0-1.767 0l-10.94 10.94a1.251 1.251 0 0 0 0 1.768l4.94 4.94a1.25 1.25 0 0 0 1.768 0l10.94-10.94a1.251 1.251 0 0 0 0-1.768zM14 5.707L19.293 11 11.5 18.793 6.207 13.5zm-4.323 14.91a.25.25 0 0 1-.354 0l-1.47-1.47.5-.5-2-2-.5.5-1.47-1.47a.25.25 0 0 1 0-.354L5.5 14.207l5.293 5.293zm10.94-10.94l-.617.616L14.707 5l.616-.616a.25.25 0 0 1 .354 0l4.94 4.94a.25.25 0 0 1 0 .353zm1.394 6.265V18a3.003 3.003 0 0 1-3 3h-3.292l1.635 1.634-.707.707-2.848-2.847 2.848-2.848.707.707L15.707 20h3.304a2.002 2.002 0 0 0 2-2v-2.058zM4 9H3V7a3.003 3.003 0 0 1 3-3h3.293L7.646 2.354l.707-.707 2.848 2.847L8.354 7.34l-.707-.707L9.28 5H6a2.002 2.002 0 0 0-2 2z" />
403
- <path fill="none" d="M0 0h24v24H0z" />
403
+ <path
404
+ d="M10.24 9C10.8561 9 11.1641 9 11.3994 9.12456C11.6064 9.23413 11.7746 9.40897 11.8801 9.62401C12 9.86848 12 10.1885 12 10.8286L12 15.1714C12 15.8115 12 16.1315 11.8801 16.376C11.7746 16.591 11.6064 16.7659 11.3994 16.8754C11.1641 17 10.8561 17 10.24 17L2.76 17C2.14394 17 1.83591 17 1.60061 16.8754C1.39363 16.7659 1.22535 16.591 1.11989 16.376C0.999999 16.1315 0.999999 15.8115 0.999999 15.1714L1 10.8286C1 10.1885 1 9.86848 1.11989 9.62401C1.22535 9.40897 1.39363 9.23413 1.60061 9.12456C1.83591 9 2.14394 9 2.76 9L10.24 9Z"
405
+ stroke="#111111"
406
+ stroke-linecap="round"
407
+ stroke-linejoin="round"
408
+ />
409
+ <path
410
+ d="M15 11L15 9.11111C15 7.24427 15 6.31085 14.6321 5.59781C14.3086 4.9706 13.7923 4.46067 13.1572 4.14109C12.4353 3.77778 11.4902 3.77778 9.6 3.77778L6 3.77778M6 3.77778L8.8125 6.55556M6 3.77778L8.8125 1"
411
+ stroke="#111111"
412
+ stroke-linecap="round"
413
+ stroke-linejoin="round"
414
+ />
404
415
  </svg>
405
416
  );
406
417
  }
@@ -1,4 +1,9 @@
1
- import { Dialog } from "primereact/dialog";
1
+ import {
2
+ Dialog,
3
+ DialogContent,
4
+ DialogHeader,
5
+ DialogTitle,
6
+ } from "../../components/ui/dialog";
2
7
 
3
8
  import { useEffect, useRef, useState } from "react";
4
9
  import DialogButtons from "./DialogButtons";
@@ -32,6 +37,12 @@ export function ItemNameDialog(
32
37
  setName(props?.name || "");
33
38
  }, [props]);
34
39
 
40
+ // Focus and select the input when dialog opens
41
+ useEffect(() => {
42
+ nameRef.current?.focus();
43
+ nameRef.current?.select();
44
+ }, []);
45
+
35
46
  const checkNameValidDebounced = useDebouncedCallback(
36
47
  async () => checkName(),
37
48
  500,
@@ -81,38 +92,39 @@ export function ItemNameDialog(
81
92
 
82
93
  return (
83
94
  <Dialog
84
- visible={true}
85
- onHide={() => {
86
- props.onClose?.(null);
87
- }}
88
- onShow={() => {
89
- nameRef.current?.focus();
90
- nameRef.current?.select();
95
+ open={true}
96
+ onOpenChange={(open) => {
97
+ if (!open) {
98
+ props.onClose?.(null);
99
+ }
91
100
  }}
92
- style={{ width: "400px" }}
93
- header={props?.title ?? "Name"}
94
101
  >
95
- <div className="p-3">
96
- <div className="my-2 text-sm">
97
- {props.message || "Enter a name for the new item:"}
102
+ <DialogContent style={{ width: "400px" }}>
103
+ <DialogHeader>
104
+ <DialogTitle>{props?.title ?? "Name"}</DialogTitle>
105
+ </DialogHeader>
106
+ <div className="p-3">
107
+ <div className="my-2 text-sm">
108
+ {props.message || "Enter a name for the new item:"}
109
+ </div>
110
+ <InputText
111
+ ref={nameRef}
112
+ value={name}
113
+ className="w-full"
114
+ onChange={(e) => setName(e.target.value)}
115
+ onKeyDown={(ev) => {
116
+ if (ev.key === "Enter" && isValid) handleOk();
117
+ }}
118
+ />
119
+ {validationError && (
120
+ <div className="mt-2 text-red-500">{validationError}</div>
121
+ )}
98
122
  </div>
99
- <InputText
100
- ref={nameRef}
101
- value={name}
102
- className="w-full"
103
- onChange={(e) => setName(e.target.value)}
104
- onKeyDown={(ev) => {
105
- if (ev.key === "Enter" && isValid) handleOk();
106
- }}
107
- />
108
- {validationError && (
109
- <div className="mt-2 text-red-500">{validationError}</div>
110
- )}
111
- </div>
112
- <DialogButtons>
113
- <Button onClick={handleOk} label="Ok" disabled={!isValid} />
114
- <Button onClick={() => props.onClose?.(null)} label="Cancel" />
115
- </DialogButtons>
123
+ <DialogButtons>
124
+ <Button onClick={handleOk} label="Ok" disabled={!isValid} />
125
+ <Button onClick={() => props.onClose?.(null)} label="Cancel" />
126
+ </DialogButtons>
127
+ </DialogContent>
116
128
  </Dialog>
117
129
  );
118
130
  }
@@ -16,6 +16,8 @@ export interface TreeNode<T = any> {
16
16
  data?: T;
17
17
  /** Indicates if the node is expandable (has or can have children) */
18
18
  hasChildren?: boolean;
19
+ /** Indicates if the node can be dragged */
20
+ isDraggable?: boolean;
19
21
  /**
20
22
  * If present, contains the node's children.
21
23
  * Use undefined to signal that children have not yet been loaded.
@@ -530,7 +532,7 @@ const NodeContent = memo(
530
532
  return (
531
533
  <div
532
534
  className="tree-node mb-0.5 flex cursor-pointer items-center"
533
- draggable={enableDragAndDrop}
535
+ draggable={enableDragAndDrop && !!node.isDraggable}
534
536
  onClick={handleSelect}
535
537
  onDragStart={(event) => handleDragStart(event)}
536
538
  onDragEnd={onDragEnd as any}
@@ -4,6 +4,8 @@ export function Spinner({
4
4
  size?: "2xl" | "3xl" | "4xl" | "5xl";
5
5
  }) {
6
6
  return (
7
- <i className={`pi pi-cog pi-spin text-${size} text-theme-secondary/40`}></i>
7
+ <i
8
+ className={`pi pi-spin pi-spinner text-${size} text-theme-secondary/40`}
9
+ ></i>
8
10
  );
9
11
  }