@alpaca-editor/core 1.0.4000 → 1.0.4008

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
@@ -154,6 +154,21 @@ function FieldListForSingleItem({
154
154
  ? (fields as Field[])
155
155
  : (Object.values(fields) as Field[]);
156
156
 
157
+ // Create section order mapping before any filtering to preserve original order
158
+ const originalSectionOrder = new Map<string, number>();
159
+ fieldsArray.forEach((field) => {
160
+ const section = field.section || "";
161
+ const sectionSortOrder = field.sectionSortOrder || 0;
162
+
163
+ // Use the minimum sectionSortOrder for each section to ensure consistent ordering
164
+ if (!originalSectionOrder.has(section)) {
165
+ originalSectionOrder.set(section, sectionSortOrder);
166
+ } else {
167
+ const existing = originalSectionOrder.get(section)!;
168
+ originalSectionOrder.set(section, Math.min(existing, sectionSortOrder));
169
+ }
170
+ });
171
+
157
172
  if (filter) fieldsArray = fieldsArray.filter(filter);
158
173
 
159
174
  if (searchFilter) {
@@ -173,21 +188,29 @@ function FieldListForSingleItem({
173
188
  // Group initialization
174
189
  const section = value.section || "";
175
190
 
176
- if (value.descriptor.item.id)
177
- if (!acc[section]) {
178
- acc[section] = [];
179
- }
180
- // Grouping
181
- acc[section].push(value);
191
+ if (!acc[section]) {
192
+ acc[section] = [];
193
+ }
194
+
195
+ // Only add fields that have a valid descriptor item id
196
+ if (value.descriptor.item.id) {
197
+ acc[section].push(value);
198
+ }
182
199
 
183
200
  return acc;
184
201
  }, {} as any);
185
202
 
186
203
  const sectionList: Field[][] = Object.values(sectionsGrouping);
187
- sectionList.sort(
188
- (a: Field[], b: Field[]) =>
189
- (a[0]?.sectionSortOrder || 0) - (b[0]?.sectionSortOrder || 0),
190
- );
204
+
205
+ // Sort sections using the original section order, not the first field after filtering
206
+ sectionList.sort((a: Field[], b: Field[]) => {
207
+ const sectionA = a[0]?.section || "";
208
+ const sectionB = b[0]?.section || "";
209
+ const orderA = originalSectionOrder.get(sectionA) || 0;
210
+ const orderB = originalSectionOrder.get(sectionB) || 0;
211
+
212
+ return orderA - orderB;
213
+ });
191
214
 
192
215
  function getSectionFields(fields: Field[]) {
193
216
  fields.sort((a: any, b: any) => a?.sortOrder - b?.sortOrder);
@@ -204,15 +227,18 @@ function FieldListForSingleItem({
204
227
  ));
205
228
  }
206
229
  return sectionList
207
- .map((x) => x[0]?.section)
208
- .filter((x) => !sections || sections.includes(x!))
209
- .map((section) =>
210
- simplified ? (
211
- getSectionFields(sectionsGrouping[section!])
230
+ .filter((sectionFields) => {
231
+ const section = sectionFields[0]?.section;
232
+ return !sections || sections.includes(section!);
233
+ })
234
+ .map((sectionFields) => {
235
+ const section = sectionFields[0]?.section;
236
+ return simplified ? (
237
+ getSectionFields(sectionFields)
212
238
  ) : (
213
239
  <Section key={section} title={section!}>
214
- {getSectionFields(sectionsGrouping[section!])}
240
+ {getSectionFields(sectionFields)}
215
241
  </Section>
216
- ),
217
- );
242
+ );
243
+ });
218
244
  }
@@ -5,7 +5,6 @@ import { EditorConfiguration } from "../config/types";
5
5
 
6
6
  import { useEffect, useRef, useState } from "react";
7
7
 
8
- import { OverlayPanel } from "primereact/overlaypanel";
9
8
  import { FieldHistoryItem, SingleValidatorResult } from "../types";
10
9
 
11
10
  import { getSessionWithFieldLock } from "./utils";
@@ -19,9 +18,20 @@ import {
19
18
  import { useThrottledCallback } from "use-debounce";
20
19
  import { SimpleIconButton } from "./ui/SimpleIconButton";
21
20
  import { FieldHistory } from "./FieldHistory";
22
- import { History, MessageCircle, WandSparkles } from "lucide-react";
21
+ import {
22
+ History,
23
+ MessageCircle,
24
+ WandSparkles,
25
+ MoreVertical,
26
+ ExternalLink,
27
+ } from "lucide-react";
23
28
  import { Checkbox } from "../components/ui/checkbox";
24
29
  import { CopyButton } from "../components/ui/copy-button";
30
+ import {
31
+ Popover,
32
+ PopoverContent,
33
+ PopoverTrigger,
34
+ } from "../components/ui/popover";
25
35
 
26
36
  export default function FieldListField({
27
37
  field,
@@ -50,8 +60,10 @@ export default function FieldListField({
50
60
  const fieldItem = field.descriptor.item;
51
61
 
52
62
  const [showRawValue, setShowRawValue] = useState(false);
63
+ const [showFieldJson, setShowFieldJson] = useState(false);
64
+ const [fieldMenuOpen, setFieldMenuOpen] = useState(false);
65
+ const [fieldHistoryOpen, setFieldHistoryOpen] = useState(false);
53
66
  const generatorsOverlay = useRef<FieldActionsOverlayRef>(null);
54
- const fieldHistoryOverlay = useRef<OverlayPanel>(null);
55
67
  const [generatorButtons, setButtons] = useState<FieldButton[]>();
56
68
  const [isModified, setIsModified] = useState(false);
57
69
  const [isSaved, setIsSaved] = useState(false);
@@ -134,7 +146,7 @@ export default function FieldListField({
134
146
  fieldLockedBySession.sessionId !== editContext.sessionId) ||
135
147
  false;
136
148
 
137
- let classNames = " flex items-stretch";
149
+ let classNames = "flex items-stretch";
138
150
  if (executingAction?.state == "running") classNames += " executing-action ";
139
151
  else {
140
152
  if (isFocusedField) classNames += " focused-field ";
@@ -197,6 +209,21 @@ export default function FieldListField({
197
209
 
198
210
  const config = editContext.configuration.fieldTypes[field.type];
199
211
 
212
+ const handleOpenFieldItem = () => {
213
+ // Load the field item in the editor
214
+ editContext.loadItem({
215
+ id: field.id,
216
+ language: "en",
217
+ version: 0,
218
+ });
219
+ setFieldMenuOpen(false);
220
+ };
221
+
222
+ const handleShowFieldJson = () => {
223
+ setShowFieldJson(!showFieldJson);
224
+ setFieldMenuOpen(false);
225
+ };
226
+
200
227
  return (
201
228
  <div
202
229
  key={
@@ -228,9 +255,35 @@ export default function FieldListField({
228
255
 
229
256
  <div className="flex-1">
230
257
  <div className="flex min-h-7 flex-wrap items-center gap-x-3 gap-y-1">
231
- <label className="block text-xs font-medium select-text">
232
- {title || field.displayName || field.name}
233
- </label>
258
+ {!simplified ? (
259
+ <Popover open={fieldMenuOpen} onOpenChange={setFieldMenuOpen}>
260
+ <PopoverTrigger asChild>
261
+ <button className="rounded py-0.5 text-left text-xs font-medium transition-colors hover:bg-gray-100">
262
+ {title || field.displayName || field.name}
263
+ </button>
264
+ </PopoverTrigger>
265
+ <PopoverContent className="w-56 p-1" align="start" side="bottom">
266
+ <button
267
+ className="flex w-full items-center gap-2 rounded px-3 py-2 text-sm transition-colors hover:bg-gray-100"
268
+ onClick={handleOpenFieldItem}
269
+ >
270
+ <ExternalLink strokeWidth={1} className="h-4 w-4" />
271
+ Open Field Item
272
+ </button>
273
+ <button
274
+ className="flex w-full items-center gap-2 rounded px-3 py-2 text-sm transition-colors hover:bg-gray-100"
275
+ onClick={handleShowFieldJson}
276
+ >
277
+ <span className="font-mono text-sm">{"{}"}</span>
278
+ {showFieldJson ? "Hide" : "Show"} Field JSON
279
+ </button>
280
+ </PopoverContent>
281
+ </Popover>
282
+ ) : (
283
+ <label className="block text-xs font-medium select-text">
284
+ {title || field.displayName || field.name}
285
+ </label>
286
+ )}
234
287
 
235
288
  {fieldLockedBySession &&
236
289
  fieldLockedBySession.sessionId !== editContext.sessionId && (
@@ -308,22 +361,30 @@ export default function FieldListField({
308
361
  label="Add Comment"
309
362
  />
310
363
  {!readonly && renderGeneratorButtons()}
311
- <SimpleIconButton
312
- icon={<History strokeWidth={1} className="h-3.5 w-3.5" />}
313
- onClick={(ev) => {
314
- fieldHistoryOverlay.current?.toggle(ev);
315
- ev.preventDefault();
316
- ev.stopPropagation();
317
- }}
318
- label="Field History"
319
- />
320
- <OverlayPanel ref={fieldHistoryOverlay} className="p-1">
321
- <FieldHistory
322
- field={field}
323
- onHover={(x) => setHistoryEntry(x)}
324
- onRevert={() => fieldHistoryOverlay.current?.hide()}
325
- />
326
- </OverlayPanel>
364
+ <Popover
365
+ open={fieldHistoryOpen}
366
+ onOpenChange={setFieldHistoryOpen}
367
+ >
368
+ <PopoverTrigger asChild>
369
+ <button
370
+ className="rounded p-1 transition-colors hover:bg-gray-100"
371
+ aria-label="Field History"
372
+ >
373
+ <History strokeWidth={1} className="h-3.5 w-3.5" />
374
+ </button>
375
+ </PopoverTrigger>
376
+ <PopoverContent
377
+ className="w-80 p-2"
378
+ align="end"
379
+ side="bottom"
380
+ >
381
+ <FieldHistory
382
+ field={field}
383
+ onHover={(x) => setHistoryEntry(x)}
384
+ onRevert={() => setFieldHistoryOpen(false)}
385
+ />
386
+ </PopoverContent>
387
+ </Popover>
327
388
  </div>
328
389
  </>
329
390
  )}
@@ -333,6 +394,20 @@ export default function FieldListField({
333
394
  {executingAction?.message}
334
395
  </div>
335
396
  )}
397
+
398
+ {/* Field JSON Display */}
399
+ {showFieldJson && (
400
+ <div className="mt-2 mb-2 rounded border bg-gray-50 p-2 text-xs">
401
+ <div className="mb-2 flex items-center justify-between">
402
+ <span className="font-medium">Field JSON</span>
403
+ <CopyButton textToCopy={JSON.stringify(field, null, 2)} />
404
+ </div>
405
+ <pre className="max-h-40 overflow-auto text-xs whitespace-pre-wrap">
406
+ {JSON.stringify(field, null, 2)}
407
+ </pre>
408
+ </div>
409
+ )}
410
+
336
411
  {!compareToField && (
337
412
  <div className="mt-1 p-0 text-xs">
338
413
  {getFieldEditor(
@@ -1796,14 +1796,16 @@ export function EditorClient({
1796
1796
  return;
1797
1797
  }
1798
1798
 
1799
+ console.log("dragObject", dragObject);
1800
+
1799
1801
  if (
1800
1802
  dragObject.type == "component" ||
1801
- (dragObject.type == "link-component" && dragObject.component)
1803
+ (dragObject.type == "items" && dragObject.items)
1802
1804
  ) {
1803
1805
  const parentComponent =
1804
1806
  parentId && page ? getComponentById(parentId, page) : null;
1805
1807
 
1806
- if (dragObject.type == "link-component") {
1808
+ if (dragObject.type == "items" && dragObject.items) {
1807
1809
  op = {
1808
1810
  type: "link-component",
1809
1811
  mainItem: page!.item.descriptor,
@@ -1817,9 +1819,10 @@ export function EditorClient({
1817
1819
  placeholderIndex: index,
1818
1820
  date: new Date().toISOString(),
1819
1821
  id: uuid(),
1820
- linkedComponentItem: dragObject.component,
1822
+ linkedComponentItem: dragObject.items[0],
1821
1823
  description: "Link component",
1822
1824
  } as LinkComponentOperation;
1825
+ console.log("op", op);
1823
1826
  } else {
1824
1827
  if (!dragObject.component) return;
1825
1828
  op = {
@@ -11,6 +11,7 @@ import { fetchItems, fetchItemStubs } from "../services/contentService";
11
11
  import { getItemDescriptor } from "../utils";
12
12
  import { EditedField, ModifiedField } from "./editContext";
13
13
  import { User } from "../../types";
14
+ import { templatesRootItemId } from "../../config/config";
14
15
 
15
16
  export type ItemChange = {
16
17
  item: ItemDescriptor;
@@ -43,6 +44,52 @@ export type ItemsRepository = {
43
44
  clear: () => void;
44
45
  };
45
46
 
47
+ // Standard Sitecore template IDs for template-related items
48
+ const TEMPLATE_TEMPLATE_ID = "AB86861A-6030-46C5-B394-E8F99E8B87DB"; // Template template
49
+ const TEMPLATE_FIELD_TEMPLATE_ID = "455A3E98-A627-4B40-8035-E683A0331AC7"; // Template field template
50
+ const TEMPLATE_SECTION_TEMPLATE_ID = "E269FBB5-3750-427A-9149-7AA950B49301"; // Template section template
51
+
52
+ /**
53
+ * Checks if an item change represents a template-related modification that should trigger cache clearing
54
+ */
55
+ const isTemplateChange = async (
56
+ change: ItemChange,
57
+ getItem: (descriptor: ItemDescriptor) => Promise<FullItem | undefined>,
58
+ ): Promise<boolean> => {
59
+ try {
60
+ const item = await getItem(change.item);
61
+ if (!item) return false;
62
+
63
+ // Check if the item is under the /sitecore/templates path
64
+ if (
65
+ item.path &&
66
+ item.path.toLowerCase().startsWith("/sitecore/templates")
67
+ ) {
68
+ return true;
69
+ }
70
+
71
+ // Check if the item's templateId indicates it's a template-related item
72
+ const templateId = item.templateId?.toLowerCase();
73
+ if (
74
+ templateId === TEMPLATE_TEMPLATE_ID.toLowerCase() ||
75
+ templateId === TEMPLATE_FIELD_TEMPLATE_ID.toLowerCase() ||
76
+ templateId === TEMPLATE_SECTION_TEMPLATE_ID.toLowerCase()
77
+ ) {
78
+ return true;
79
+ }
80
+
81
+ // Check if the item is under the templates root using the configured templates root ID
82
+ if (item.path && item.path.includes(templatesRootItemId)) {
83
+ return true;
84
+ }
85
+
86
+ return false;
87
+ } catch (error) {
88
+ console.error("Error checking if item change is template-related:", error);
89
+ return false;
90
+ }
91
+ };
92
+
46
93
  export function useItemsRepository(
47
94
  setModifiedFields: React.Dispatch<React.SetStateAction<ModifiedField[]>>,
48
95
  setLastEditedFields: React.Dispatch<React.SetStateAction<EditedField[]>>,
@@ -509,16 +556,40 @@ export function useItemsRepository(
509
556
  [setRevision],
510
557
  );
511
558
 
559
+ const clear = useCallback(() => {
560
+ itemsMap.current.clear();
561
+ stubsMap.current.clear();
562
+ fallbackDependencyMap.current.clear();
563
+ //setRevision((x) => x + 1);
564
+ }, []);
565
+
512
566
  const subscribeItemsChanged = useCallback(
513
567
  (callback: (changes: ItemChange[]) => void) => {
514
- itemsChangedListeners.current.add(callback);
568
+ const wrappedCallback = async (changes: ItemChange[]) => {
569
+ // Check if any of the changes are template-related
570
+ const templateChanges = await Promise.all(
571
+ changes.map((change) => isTemplateChange(change, getItem)),
572
+ );
573
+
574
+ // If any template changes are detected, clear the cache
575
+ if (templateChanges.some((isTemplate) => isTemplate)) {
576
+ console.log("Template changes detected, clearing cache...");
577
+ clear();
578
+ setRevision((prev) => prev + 1);
579
+ }
580
+
581
+ // Call the original callback
582
+ callback(changes);
583
+ };
584
+
585
+ itemsChangedListeners.current.add(wrappedCallback);
515
586
 
516
587
  // Return an unsubscribe function
517
588
  return () => {
518
- itemsChangedListeners.current.delete(callback);
589
+ itemsChangedListeners.current.delete(wrappedCallback);
519
590
  };
520
591
  },
521
- [],
592
+ [clear, getItem],
522
593
  );
523
594
 
524
595
  const triggerItemsChangedEvent = (changes: ItemChange[]) => {
@@ -528,13 +599,6 @@ export function useItemsRepository(
528
599
  }
529
600
  };
530
601
 
531
- const clear = useCallback(() => {
532
- itemsMap.current.clear();
533
- stubsMap.current.clear();
534
- fallbackDependencyMap.current.clear();
535
- //setRevision((x) => x + 1);
536
- }, []);
537
-
538
602
  const repo = useMemo(() => {
539
603
  return {
540
604
  getItems,
@@ -161,7 +161,7 @@ export function usePageModel(
161
161
  datasourceItem,
162
162
  items,
163
163
  placeholders,
164
- isShared: !(datasourceItem?.path.startsWith(pageItem.path) || true),
164
+ isShared: !datasourceItem?.path?.startsWith(pageItem.path) || false,
165
165
  parentPlaceholder,
166
166
  editable: skeleton.editable ?? true,
167
167
  type: datasourceItem?.templateName || name, // TODO: We need the name for AI page wizard - we should not use the template name
@@ -41,8 +41,8 @@ export const deleteItemCommand: ItemCommand = {
41
41
  message: (
42
42
  <div>
43
43
  Are you sure you want to delete{" "}
44
- <em>{items.map((x) => x.name).join(", ")}</em>? This cannot be
45
- undone.
44
+ <em>{items.map((x) => x.name).join(", ")}</em>?
45
+ <div className="mt-4">This cannot be undone.</div>
46
46
  </div>
47
47
  ),
48
48
  header: "Confirmation",
@@ -358,7 +358,6 @@ export const publishItemCommand: ItemCommand = {
358
358
  },
359
359
  };
360
360
 
361
-
362
361
  export const exportItemsCommand: ItemCommand = {
363
362
  id: "exportItem",
364
363
  label: "Export",
@@ -0,0 +1,132 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { format } from "date-fns";
5
+ import { Calendar as CalendarIcon } from "lucide-react";
6
+ import { useEditContext } from "../client/editContext";
7
+ import { cn } from "../../lib/utils";
8
+ import { Button } from "../../components/ui/button";
9
+ import {
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from "../../components/ui/popover";
14
+ import { Calendar } from "../../components/ui/calendar";
15
+ import { useEffect, useState } from "react";
16
+ import { ProgressSpinner } from "primereact/progressspinner";
17
+ import { DateField, DateFieldValue } from "../fieldTypes";
18
+
19
+ // Helper function to format date to compact ISO format: YYYYMMDDTHHMMSSZ
20
+ const formatDateToCompactISO = (date: Date): string => {
21
+ const year = date.getUTCFullYear().toString();
22
+ const month = (date.getUTCMonth() + 1).toString().padStart(2, "0");
23
+ const day = date.getUTCDate().toString().padStart(2, "0");
24
+ const hours = date.getUTCHours().toString().padStart(2, "0");
25
+ const minutes = date.getUTCMinutes().toString().padStart(2, "0");
26
+ const seconds = date.getUTCSeconds().toString().padStart(2, "0");
27
+
28
+ return `${year}${month}${day}T${hours}${minutes}${seconds}Z`;
29
+ };
30
+
31
+ export function DateFieldEditor({
32
+ field,
33
+ readOnly,
34
+ }: {
35
+ field: DateField;
36
+ readOnly?: boolean;
37
+ }) {
38
+ const editContext = useEditContext();
39
+ const [isUpdating, setIsUpdating] = useState(false);
40
+ const [date, setDate] = useState<Date | undefined>(undefined);
41
+
42
+ useEffect(() => {
43
+ // Parse the date from the field rawValue (compact ISO format: YYYYMMDDTHHMMSSZ)
44
+ const dateString = field.rawValue;
45
+
46
+ if (dateString && dateString.trim() !== "") {
47
+ try {
48
+ // Handle compact ISO format: 20250714T220000Z
49
+ let parsedDate: Date;
50
+ if (dateString.match(/^\d{8}T\d{6}Z$/)) {
51
+ // Convert compact format to standard ISO: 20250714T220000Z -> 2025-07-14T22:00:00Z
52
+ const standardISO = dateString.replace(
53
+ /(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/,
54
+ "$1-$2-$3T$4:$5:$6Z",
55
+ );
56
+ parsedDate = new Date(standardISO);
57
+ } else {
58
+ // Fallback to direct parsing
59
+ parsedDate = new Date(dateString);
60
+ }
61
+
62
+ if (!isNaN(parsedDate.getTime())) {
63
+ setDate(parsedDate);
64
+ } else {
65
+ setDate(undefined);
66
+ }
67
+ } catch (error) {
68
+ console.warn("Failed to parse date from field value:", dateString);
69
+ setDate(undefined);
70
+ }
71
+ } else {
72
+ setDate(undefined);
73
+ }
74
+ }, [field.rawValue]);
75
+
76
+ if (!editContext) return null;
77
+
78
+ const fieldItem = field.descriptor.item;
79
+ if (!fieldItem) return null;
80
+
81
+ if (isUpdating) {
82
+ return <ProgressSpinner style={{ width: "18px", height: "18px" }} />;
83
+ }
84
+
85
+ const handleDateSelect = async (selectedDate: Date | undefined) => {
86
+ if (readOnly) return;
87
+
88
+ setIsUpdating(true);
89
+
90
+ try {
91
+ // Format date to compact ISO format: YYYYMMDDTHHMMSSZ
92
+ const rawValue = selectedDate ? formatDateToCompactISO(selectedDate) : "";
93
+
94
+ await editContext.operations.editField({
95
+ field: field.descriptor,
96
+ rawValue: rawValue,
97
+ value: rawValue,
98
+ refresh: "immediate",
99
+ });
100
+ } catch (error) {
101
+ console.error("Failed to update date field:", error);
102
+ // Show error to user if possible
103
+ if (editContext.showToast) {
104
+ editContext.showToast("Failed to update date. Please try again.");
105
+ }
106
+ } finally {
107
+ setIsUpdating(false);
108
+ }
109
+ };
110
+
111
+ return (
112
+ <Popover>
113
+ <PopoverTrigger asChild>
114
+ <Button
115
+ variant="outline"
116
+ data-empty={!date}
117
+ className={cn(
118
+ "bg-gray-5 justify-start p-1 text-left text-xs font-normal",
119
+ !date && "text-muted-foreground",
120
+ )}
121
+ disabled={readOnly}
122
+ >
123
+ <CalendarIcon className="mr-2 h-4 w-4" />
124
+ {date ? format(date, "PPP") : <span>Pick a date!</span>}
125
+ </Button>
126
+ </PopoverTrigger>
127
+ <PopoverContent className="w-auto p-0" align="start">
128
+ <Calendar mode="single" selected={date} onSelect={handleDateSelect} />
129
+ </PopoverContent>
130
+ </Popover>
131
+ );
132
+ }
@@ -36,7 +36,7 @@ export function ImageFieldEditor({
36
36
 
37
37
  return (
38
38
  <div>
39
- <div className="focus-shadow flex h-full flex-wrap gap-2">
39
+ <div className="focus-shadow bg-gray-5 flex h-full flex-wrap gap-2">
40
40
  <div className="relative flex h-48 w-48 cursor-pointer items-center justify-center border border-gray-300">
41
41
  {!readOnly && <ImageEditor field={field} />}
42
42
  </div>
@@ -137,8 +137,8 @@ export function InternalLinkFieldEditor({
137
137
  <div
138
138
  ref={texboxRef}
139
139
  className={classNames(
140
- "justiy-between focus-shadow flex cursor-pointer justify-between border p-1.5 text-xs",
141
- readOnly ? "bg-gray-50" : "bg-white",
140
+ "justiy-between focus-shadow bg-gray-5 flex cursor-pointer justify-between border p-1.5 text-xs",
141
+ readOnly ? "bg-gray-100" : "",
142
142
  )}
143
143
  onClick={(e) => {
144
144
  if (readOnly) return;
@@ -34,8 +34,8 @@ export function LinkFieldEditor({
34
34
  <>
35
35
  <div
36
36
  className={classNames(
37
- "focus-shadow border-gray-3 bg-gray-5 flex justify-between border p-2",
38
- readOnly ? "bg-gray-100" : "cursor-pointer bg-white",
37
+ "focus-shadow border-gray-3 bg-gray-5 flex justify-between border p-1.5",
38
+ readOnly ? "bg-gray-100" : "cursor-pointer",
39
39
  )}
40
40
  onClick={(e) => {
41
41
  if (readOnly) return;
@@ -57,7 +57,11 @@ export function MultiLineText({
57
57
  fieldItem.language &&
58
58
  editContextRef.current?.focusedField?.item.version === fieldItem.version
59
59
  ) {
60
- if (inputRef.current) {
60
+ // Only focus if no other element currently has focus (e.g., popover buttons)
61
+ if (
62
+ inputRef.current &&
63
+ (!document.activeElement || document.activeElement === document.body)
64
+ ) {
61
65
  inputRef.current.focus();
62
66
  }
63
67
  }
@@ -34,7 +34,7 @@ export function PictureFieldEditor({
34
34
 
35
35
  return (
36
36
  <div>
37
- <div className="focus-shadow flex h-full flex-wrap gap-2">
37
+ <div className="focus-shadow bg-gray-5 flex h-full flex-wrap gap-2">
38
38
  {field?.value?.variants?.map((variant) => (
39
39
  <div key={variant.name}>
40
40
  <div
@@ -141,7 +141,11 @@ export function SingleLineText({
141
141
  fieldItem.language &&
142
142
  editContextRef.current?.focusedField?.item.version === fieldItem.version
143
143
  ) {
144
- if (inputRef.current) {
144
+ // Only focus if no other element currently has focus (e.g., popover buttons)
145
+ if (
146
+ inputRef.current &&
147
+ (!document.activeElement || document.activeElement === document.body)
148
+ ) {
145
149
  inputRef.current.focus();
146
150
  }
147
151
  }