@alpaca-editor/core 1.0.4015 → 1.0.4018

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 (110) hide show
  1. package/dist/components/ui/calendar.js +2 -6
  2. package/dist/components/ui/calendar.js.map +1 -1
  3. package/dist/components/ui/card.d.ts +2 -1
  4. package/dist/components/ui/card.js +2 -2
  5. package/dist/components/ui/card.js.map +1 -1
  6. package/dist/components/ui/copy-button.js +31 -3
  7. package/dist/components/ui/copy-button.js.map +1 -1
  8. package/dist/config/config.js +20 -13
  9. package/dist/config/config.js.map +1 -1
  10. package/dist/editor/FieldList.js +1 -1
  11. package/dist/editor/FieldList.js.map +1 -1
  12. package/dist/editor/FieldListField.js +1 -1
  13. package/dist/editor/FieldListField.js.map +1 -1
  14. package/dist/editor/ScrollingContentTree.d.ts +2 -1
  15. package/dist/editor/ScrollingContentTree.js +2 -2
  16. package/dist/editor/ScrollingContentTree.js.map +1 -1
  17. package/dist/editor/client/editContext.d.ts +1 -0
  18. package/dist/editor/client/editContext.js.map +1 -1
  19. package/dist/editor/client/itemsRepository.d.ts +2 -2
  20. package/dist/editor/client/itemsRepository.js +15 -5
  21. package/dist/editor/client/itemsRepository.js.map +1 -1
  22. package/dist/editor/client/operations.js +18 -6
  23. package/dist/editor/client/operations.js.map +1 -1
  24. package/dist/editor/client/pageModelBuilder.js +2 -1
  25. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  26. package/dist/editor/context-menu/InsertMenu.js +45 -12
  27. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  28. package/dist/editor/field-types/DateFieldEditor.js +1 -1
  29. package/dist/editor/field-types/DateFieldEditor.js.map +1 -1
  30. package/dist/editor/field-types/DateTimeFieldEditor.d.ts +5 -0
  31. package/dist/editor/field-types/DateTimeFieldEditor.js +151 -0
  32. package/dist/editor/field-types/DateTimeFieldEditor.js.map +1 -0
  33. package/dist/editor/field-types/InternalLinkFieldEditor.js +1 -1
  34. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  35. package/dist/editor/field-types/TreeListEditor.js +17 -6
  36. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  37. package/dist/editor/fieldTypes.d.ts +6 -0
  38. package/dist/editor/menubar/ToolbarFactory.js +1 -1
  39. package/dist/editor/menubar/ToolbarFactory.js.map +1 -1
  40. package/dist/editor/menubar/toolbar-sections/EditControls.d.ts +7 -1
  41. package/dist/editor/menubar/toolbar-sections/EditControls.js +2 -2
  42. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  43. package/dist/editor/page-viewer/EditorForm.js +3 -1
  44. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  45. package/dist/editor/page-viewer/PageViewer.d.ts +2 -1
  46. package/dist/editor/page-viewer/PageViewer.js +7 -5
  47. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  48. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
  49. package/dist/editor/page-viewer/PageViewerFrame.js +3 -2
  50. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  51. package/dist/editor/sidebar/ComponentPalette.js +28 -1
  52. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  53. package/dist/editor/sidebar/Debug.js +13 -3
  54. package/dist/editor/sidebar/Debug.js.map +1 -1
  55. package/dist/editor/ui/ItemSearch.d.ts +1 -0
  56. package/dist/editor/ui/ItemSearch.js +5 -5
  57. package/dist/editor/ui/ItemSearch.js.map +1 -1
  58. package/dist/editor/ui/SimpleTabs.d.ts +2 -1
  59. package/dist/editor/ui/SimpleTabs.js +8 -7
  60. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  61. package/dist/editor/ui/Spinner.d.ts +1 -1
  62. package/dist/page-wizard/WizardSteps.js +5 -4
  63. package/dist/page-wizard/WizardSteps.js.map +1 -1
  64. package/dist/page-wizard/steps/CollectStep.js +1 -1
  65. package/dist/page-wizard/steps/CollectStep.js.map +1 -1
  66. package/dist/page-wizard/steps/ContentStep.js +4 -3
  67. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  68. package/dist/page-wizard/steps/FindItemsStep.js +2 -13
  69. package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
  70. package/dist/page-wizard/steps/usePageCreator.js +2 -4
  71. package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
  72. package/dist/revision.d.ts +2 -2
  73. package/dist/revision.js +2 -2
  74. package/dist/styles.css +357 -60
  75. package/package.json +1 -1
  76. package/src/components/ui/calendar.tsx +2 -18
  77. package/src/components/ui/card.tsx +5 -0
  78. package/src/components/ui/copy-button.tsx +32 -3
  79. package/src/config/config.tsx +21 -14
  80. package/src/editor/FieldList.tsx +1 -1
  81. package/src/editor/FieldListField.tsx +1 -1
  82. package/src/editor/ScrollingContentTree.tsx +3 -0
  83. package/src/editor/client/editContext.ts +1 -0
  84. package/src/editor/client/itemsRepository.ts +25 -7
  85. package/src/editor/client/operations.ts +19 -5
  86. package/src/editor/client/pageModelBuilder.ts +1 -1
  87. package/src/editor/context-menu/InsertMenu.tsx +77 -30
  88. package/src/editor/field-types/DateFieldEditor.tsx +8 -2
  89. package/src/editor/field-types/DateTimeFieldEditor.tsx +281 -0
  90. package/src/editor/field-types/InternalLinkFieldEditor.tsx +1 -1
  91. package/src/editor/field-types/TreeListEditor.tsx +41 -8
  92. package/src/editor/fieldTypes.ts +8 -0
  93. package/src/editor/menubar/ToolbarFactory.tsx +2 -1
  94. package/src/editor/menubar/toolbar-sections/EditControls.tsx +37 -23
  95. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
  96. package/src/editor/page-viewer/EditorForm.tsx +3 -1
  97. package/src/editor/page-viewer/PageViewer.tsx +18 -15
  98. package/src/editor/page-viewer/PageViewerFrame.tsx +11 -2
  99. package/src/editor/sidebar/ComponentPalette.tsx +36 -1
  100. package/src/editor/sidebar/Debug.tsx +14 -6
  101. package/src/editor/ui/ItemSearch.tsx +6 -3
  102. package/src/editor/ui/SimpleTabs.tsx +10 -1
  103. package/src/editor/ui/Spinner.tsx +1 -1
  104. package/src/page-wizard/WizardSteps.tsx +2 -3
  105. package/src/page-wizard/steps/CollectStep.tsx +1 -1
  106. package/src/page-wizard/steps/ContentStep.tsx +18 -12
  107. package/src/page-wizard/steps/FindItemsStep.tsx +1 -50
  108. package/src/page-wizard/steps/usePageCreator.ts +2 -3
  109. package/src/revision.ts +2 -2
  110. package/styles.css +1 -0
@@ -0,0 +1,281 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { format } from "date-fns";
5
+ import { Calendar as CalendarIcon, Clock } 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 { Input } from "../../components/ui/input";
16
+ import { useEffect, useState } from "react";
17
+ import { ProgressSpinner } from "primereact/progressspinner";
18
+ import { DateTimeField, DateTimeFieldValue } from "../fieldTypes";
19
+
20
+ // Helper function to format date to compact ISO format: YYYYMMDDTHHMMSSZ
21
+ const formatDateToCompactISO = (date: Date): string => {
22
+ const year = date.getUTCFullYear().toString();
23
+ const month = (date.getUTCMonth() + 1).toString().padStart(2, "0");
24
+ const day = date.getUTCDate().toString().padStart(2, "0");
25
+ const hours = date.getUTCHours().toString().padStart(2, "0");
26
+ const minutes = date.getUTCMinutes().toString().padStart(2, "0");
27
+ const seconds = date.getUTCSeconds().toString().padStart(2, "0");
28
+
29
+ return `${year}${month}${day}T${hours}${minutes}${seconds}Z`;
30
+ };
31
+
32
+ export function DateTimeFieldEditor({
33
+ field,
34
+ readOnly,
35
+ }: {
36
+ field: DateTimeField;
37
+ readOnly?: boolean;
38
+ }) {
39
+ const editContext = useEditContext();
40
+ const [isUpdating, setIsUpdating] = useState(false);
41
+ const [date, setDate] = useState<Date | undefined>(undefined);
42
+ const [time, setTime] = useState<string>("");
43
+ const [hours, setHours] = useState<number>(0);
44
+ const [minutes, setMinutes] = useState<number>(0);
45
+
46
+ useEffect(() => {
47
+ // Parse the date from the field rawValue (compact ISO format: YYYYMMDDTHHMMSSZ)
48
+ const dateString = field.rawValue;
49
+
50
+ if (dateString && dateString.trim() !== "") {
51
+ try {
52
+ // Handle compact ISO format: 20250714T220000Z
53
+ let parsedDate: Date;
54
+ if (dateString.match(/^\d{8}T\d{6}Z$/)) {
55
+ // Convert compact format to standard ISO: 20250714T220000Z -> 2025-07-14T22:00:00Z
56
+ const standardISO = dateString.replace(
57
+ /(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z/,
58
+ "$1-$2-$3T$4:$5:$6Z",
59
+ );
60
+ parsedDate = new Date(standardISO);
61
+ } else {
62
+ // Fallback to direct parsing
63
+ parsedDate = new Date(dateString);
64
+ }
65
+
66
+ if (!isNaN(parsedDate.getTime())) {
67
+ setDate(parsedDate);
68
+ const parsedHours = parsedDate.getHours();
69
+ const parsedMinutes = parsedDate.getMinutes();
70
+ setHours(parsedHours);
71
+ setMinutes(parsedMinutes);
72
+ setTime(
73
+ `${parsedHours.toString().padStart(2, "0")}:${parsedMinutes.toString().padStart(2, "0")}`,
74
+ );
75
+ } else {
76
+ setDate(undefined);
77
+ setHours(0);
78
+ setMinutes(0);
79
+ setTime("");
80
+ }
81
+ } catch (error) {
82
+ console.warn("Failed to parse date from field value:", dateString);
83
+ setDate(undefined);
84
+ setHours(0);
85
+ setMinutes(0);
86
+ setTime("");
87
+ }
88
+ } else {
89
+ setDate(undefined);
90
+ setHours(0);
91
+ setMinutes(0);
92
+ setTime("");
93
+ }
94
+ }, [field.rawValue]);
95
+
96
+ if (!editContext) return null;
97
+
98
+ const fieldItem = field.descriptor.item;
99
+ if (!fieldItem) return null;
100
+
101
+ const updateDateTime = async (newDate?: Date, newTime?: string) => {
102
+ if (readOnly) return;
103
+
104
+ setIsUpdating(true);
105
+
106
+ try {
107
+ let finalDate: Date | undefined;
108
+
109
+ if (newDate || date) {
110
+ const baseDate = newDate || date!;
111
+ const timeToUse = newTime !== undefined ? newTime : time;
112
+
113
+ if (timeToUse && timeToUse.match(/^\d{2}:\d{2}$/)) {
114
+ const timeParts = timeToUse.split(":");
115
+ if (timeParts.length === 2 && timeParts[0] && timeParts[1]) {
116
+ const hours = parseInt(timeParts[0], 10);
117
+ const minutes = parseInt(timeParts[1], 10);
118
+ if (!isNaN(hours) && !isNaN(minutes)) {
119
+ finalDate = new Date(baseDate);
120
+ finalDate.setHours(hours, minutes, 0, 0);
121
+ } else {
122
+ finalDate = baseDate;
123
+ }
124
+ } else {
125
+ finalDate = baseDate;
126
+ }
127
+ } else {
128
+ finalDate = baseDate;
129
+ }
130
+ }
131
+
132
+ // Format date to compact ISO format: YYYYMMDDTHHMMSSZ
133
+ const rawValue = finalDate ? formatDateToCompactISO(finalDate) : "";
134
+
135
+ await editContext.operations.editField({
136
+ field: field.descriptor,
137
+ rawValue: rawValue,
138
+ value: rawValue,
139
+ refresh: "immediate",
140
+ });
141
+ } catch (error) {
142
+ console.error("Failed to update datetime field:", error);
143
+ // Show error to user if possible
144
+ if (editContext.showToast) {
145
+ editContext.showToast("Failed to update date/time. Please try again.");
146
+ }
147
+ } finally {
148
+ setIsUpdating(false);
149
+ }
150
+ };
151
+
152
+ const handleDateSelect = async (selectedDate: Date | undefined) => {
153
+ setDate(selectedDate);
154
+ await updateDateTime(selectedDate, time);
155
+ };
156
+
157
+ const handleHoursChange = async (newHours: number) => {
158
+ setHours(newHours);
159
+ const newTime = `${newHours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
160
+ setTime(newTime);
161
+ if (date) {
162
+ await updateDateTime(date, newTime);
163
+ }
164
+ };
165
+
166
+ const handleMinutesChange = async (newMinutes: number) => {
167
+ setMinutes(newMinutes);
168
+ const newTime = `${hours.toString().padStart(2, "0")}:${newMinutes.toString().padStart(2, "0")}`;
169
+ setTime(newTime);
170
+ if (date) {
171
+ await updateDateTime(date, newTime);
172
+ }
173
+ };
174
+
175
+ return (
176
+ <div className="flex items-center gap-2">
177
+ <Popover enableIframeClickDetection={false}>
178
+ <PopoverTrigger asChild>
179
+ <Button
180
+ variant="outline"
181
+ data-empty={!date}
182
+ className={cn(
183
+ "bg-gray-5 justify-start p-1 text-left text-xs font-normal",
184
+ !date && "text-muted-foreground",
185
+ )}
186
+ disabled={readOnly}
187
+ >
188
+ <CalendarIcon className="mr-2 h-4 w-4" />
189
+ {date ? format(date, "PPP") : <span>Pick a date!</span>}
190
+ </Button>
191
+ </PopoverTrigger>
192
+ <PopoverContent className="w-auto p-0" align="start">
193
+ <div
194
+ className="p-3"
195
+ onMouseDown={(e) => e.stopPropagation()}
196
+ onClick={(e) => e.stopPropagation()}
197
+ >
198
+ <Calendar
199
+ mode="single"
200
+ selected={date}
201
+ onSelect={handleDateSelect}
202
+ />
203
+ </div>
204
+ </PopoverContent>
205
+ </Popover>
206
+
207
+ <div
208
+ className={cn(
209
+ "bg-gray-5 flex items-center justify-start gap-1 px-2 text-left text-xs font-normal",
210
+ !date && "text-muted-foreground",
211
+ )}
212
+ >
213
+ <Clock className="text-muted-foreground h-4 w-4" />
214
+
215
+ {/* Hours Dropdown */}
216
+ <Popover enableIframeClickDetection={false}>
217
+ <PopoverTrigger asChild>
218
+ <Button
219
+ variant="ghost"
220
+ className="bg-gray-5 h-8 w-12 p-1 text-xs"
221
+ disabled={readOnly || !date}
222
+ >
223
+ {hours.toString().padStart(2, "0")}
224
+ </Button>
225
+ </PopoverTrigger>
226
+ <PopoverContent className="w-auto p-2" align="start">
227
+ <div
228
+ className="grid max-h-48 grid-cols-4 gap-1 overflow-y-auto"
229
+ onMouseDown={(e) => e.stopPropagation()}
230
+ onClick={(e) => e.stopPropagation()}
231
+ >
232
+ {Array.from({ length: 24 }, (_, i) => (
233
+ <Button
234
+ key={i}
235
+ variant={hours === i ? "default" : "ghost"}
236
+ className="h-8 w-8 p-0 text-xs"
237
+ onClick={() => handleHoursChange(i)}
238
+ >
239
+ {i.toString().padStart(2, "0")}
240
+ </Button>
241
+ ))}
242
+ </div>
243
+ </PopoverContent>
244
+ </Popover>
245
+
246
+ <span className="text-muted-foreground">:</span>
247
+
248
+ {/* Minutes Dropdown */}
249
+ <Popover enableIframeClickDetection={false}>
250
+ <PopoverTrigger asChild>
251
+ <Button
252
+ variant="ghost"
253
+ className="bg-gray-5 h-8 w-12 p-1 text-xs"
254
+ disabled={readOnly || !date}
255
+ >
256
+ {minutes.toString().padStart(2, "0")}
257
+ </Button>
258
+ </PopoverTrigger>
259
+ <PopoverContent className="w-auto p-2" align="start">
260
+ <div
261
+ className="grid max-h-48 grid-cols-4 gap-1 overflow-y-auto"
262
+ onMouseDown={(e) => e.stopPropagation()}
263
+ onClick={(e) => e.stopPropagation()}
264
+ >
265
+ {Array.from({ length: 60 }, (_, i) => (
266
+ <Button
267
+ key={i}
268
+ variant={minutes === i ? "default" : "ghost"}
269
+ className="h-8 w-8 p-0 text-xs"
270
+ onClick={() => handleMinutesChange(i)}
271
+ >
272
+ {i.toString().padStart(2, "0")}
273
+ </Button>
274
+ ))}
275
+ </div>
276
+ </PopoverContent>
277
+ </Popover>
278
+ </div>
279
+ </div>
280
+ );
281
+ }
@@ -128,7 +128,7 @@ export function InternalLinkFieldEditor({
128
128
  selectedItemId={
129
129
  link?.itemId ? normalizeKey(link.itemId) : undefined
130
130
  }
131
- rootItemId={rootItemIds[0]}
131
+ rootItemIds={rootItemIds}
132
132
  expandedItemId={
133
133
  link?.itemId ? normalizeKey(link.itemId) : undefined
134
134
  }
@@ -44,6 +44,7 @@ export default function TreeListEditor({
44
44
  const [hoveredItemId, setHoveredItemId] = useState<string | undefined>();
45
45
  const [hoveredItem, setHoveredItem] = useState<FullItem | undefined>();
46
46
  const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
47
+ const [lastClickedIndex, setLastClickedIndex] = useState<number | null>(null);
47
48
 
48
49
  if (!editContext) return;
49
50
 
@@ -193,10 +194,15 @@ export default function TreeListEditor({
193
194
  };
194
195
 
195
196
  return (
196
- <div className="focus-shadow border border-gray-200">
197
- <div className="bg-gray-5 border-b border-gray-200 p-2">
197
+ <div
198
+ className={`focus-shadow border border-gray-200 ${readOnly ? "bg-gray-5" : ""}`}
199
+ >
200
+ <div
201
+ className={`border-b border-gray-200 p-2 ${readOnly ? "bg-gray-5" : "bg-gray-5"}`}
202
+ >
198
203
  <ItemSearch
199
204
  rootItemIds={rootItemIds.map((x) => normalizeGuid(x))}
205
+ disabled={readOnly}
200
206
  itemSelected={async (item) => {
201
207
  addToList([
202
208
  {
@@ -212,7 +218,9 @@ export default function TreeListEditor({
212
218
  </div>
213
219
  <Splitter className="h-60">
214
220
  <SplitterPanel size={50} className="relative">
215
- <div className="absolute inset-0 overflow-auto">
221
+ <div
222
+ className={`absolute inset-0 overflow-auto ${readOnly ? "bg-gray-5" : ""}`}
223
+ >
216
224
  <ContentTree
217
225
  expandIdPath={hoveredItem?.idPath}
218
226
  language={editContext.currentItemDescriptor?.language ?? "en"}
@@ -238,7 +246,9 @@ export default function TreeListEditor({
238
246
  <SplitterPanel>
239
247
  <div className="relative h-full w-full">
240
248
  <div className="absolute inset-0 flex">
241
- <div className="flex flex-col justify-center gap-1 bg-gray-100 p-1">
249
+ <div
250
+ className={`flex flex-col justify-center gap-1 p-1 ${readOnly ? "bg-gray-5" : "bg-gray-100"}`}
251
+ >
242
252
  <SimpleIconButton
243
253
  label="Add"
244
254
  icon="pi pi-angle-right"
@@ -253,14 +263,19 @@ export default function TreeListEditor({
253
263
  />
254
264
  </div>
255
265
  <div className="relative h-full flex-1">
256
- <div className="absolute inset-0 overflow-auto bg-white text-xs">
266
+ <div
267
+ className={`absolute inset-0 overflow-auto text-xs ${readOnly ? "bg-gray-5" : "bg-white"}`}
268
+ >
257
269
  {values.length === 0 ? (
258
270
  <div className="p-3 text-center text-gray-500">
259
271
  None selected
260
272
  </div>
261
273
  ) : (
262
274
  values.map((option, index) => (
263
- <Tooltip key={option.id} delayDuration={1000}>
275
+ <Tooltip
276
+ key={`${option.id}-${index}`}
277
+ delayDuration={1000}
278
+ >
264
279
  <TooltipTrigger asChild>
265
280
  <div
266
281
  draggable={!readOnly}
@@ -276,12 +291,27 @@ export default function TreeListEditor({
276
291
  !readOnly ? "cursor-move" : ""
277
292
  }`}
278
293
  onClick={(e) => {
279
- if (e.ctrlKey) {
294
+ if (e.shiftKey && lastClickedIndex !== null) {
295
+ // Range selection mode when Shift is pressed
296
+ const startIndex = Math.min(
297
+ lastClickedIndex,
298
+ index,
299
+ );
300
+ const endIndex = Math.max(
301
+ lastClickedIndex,
302
+ index,
303
+ );
304
+ const rangeItems = values.slice(
305
+ startIndex,
306
+ endIndex + 1,
307
+ );
308
+ setSelectedFromList(rangeItems);
309
+ } else if (e.ctrlKey) {
280
310
  // Multi-select mode when Ctrl is pressed
281
311
  if (selectedFromList.includes(option)) {
282
312
  setSelectedFromList(
283
313
  selectedFromList.filter(
284
- (item) => item.id !== option.id,
314
+ (item) => item !== option,
285
315
  ),
286
316
  );
287
317
  } else {
@@ -290,6 +320,7 @@ export default function TreeListEditor({
290
320
  option,
291
321
  ]);
292
322
  }
323
+ setLastClickedIndex(index);
293
324
  } else {
294
325
  // Single-select mode when Ctrl is not pressed
295
326
  if (
@@ -298,9 +329,11 @@ export default function TreeListEditor({
298
329
  ) {
299
330
  // Deselect if this is the only selected item
300
331
  setSelectedFromList([]);
332
+ setLastClickedIndex(null);
301
333
  } else {
302
334
  // Replace selection with just this item
303
335
  setSelectedFromList([option]);
336
+ setLastClickedIndex(index);
304
337
  }
305
338
  }
306
339
  }}
@@ -128,6 +128,14 @@ export type DateFieldValue = {
128
128
  date: string;
129
129
  };
130
130
 
131
+ export type DateTimeField = Field & {
132
+ value: DateTimeFieldValue;
133
+ };
134
+
135
+ export type DateTimeFieldValue = {
136
+ date: string;
137
+ };
138
+
131
139
  export type NumberField = Field & {
132
140
  value: number;
133
141
  };
@@ -13,7 +13,8 @@ export function createEditToolbar(editContext: EditContextType) {
13
13
  if (!editContext) return null;
14
14
 
15
15
  const hasLayout = editContext.contentEditorItem?.hasLayout;
16
- const isReviewsView = editContext.viewName === "reviews";
16
+ const isReviewsView =
17
+ editContext.viewName === "reviews" || editContext.viewName === "comments";
17
18
 
18
19
  // For items without layout (simple content editor)
19
20
  if (!hasLayout) {
@@ -2,7 +2,17 @@ import { useEditContext } from "../../client/editContext";
2
2
  import { SimpleIconButton } from "../../ui/SimpleIconButton";
3
3
  import { EyeIcon, MessagesSquare, Pencil } from "lucide-react";
4
4
 
5
- export function EditControls() {
5
+ interface EditControlsProps {
6
+ hideSuggestions?: boolean;
7
+ hideEdit?: boolean;
8
+ hidePreview?: boolean;
9
+ }
10
+
11
+ export function EditControls({
12
+ hideSuggestions,
13
+ hideEdit,
14
+ hidePreview,
15
+ }: EditControlsProps = {}) {
6
16
  const editContext = useEditContext();
7
17
 
8
18
  if (!editContext) return null;
@@ -17,7 +27,7 @@ export function EditControls() {
17
27
 
18
28
  return (
19
29
  <>
20
- {!editContext.user?.isLimitedPreviewUser && (
30
+ {!hideEdit && !editContext.user?.isLimitedPreviewUser && (
21
31
  <SimpleIconButton
22
32
  icon={<Pencil className="h-6 w-6 p-1" strokeWidth={1} />}
23
33
  label="Edit"
@@ -27,28 +37,32 @@ export function EditControls() {
27
37
  />
28
38
  )}
29
39
 
30
- <SimpleIconButton
31
- icon={<EyeIcon className="h-6 w-6 p-1" strokeWidth={1} />}
32
- label="Preview"
33
- size="large"
34
- selected={editContext.mode === "preview"}
35
- onClick={() => editContext.setMode("preview")}
36
- />
40
+ {!hidePreview && (
41
+ <SimpleIconButton
42
+ icon={<EyeIcon className="h-6 w-6 p-1" strokeWidth={1} />}
43
+ label="Preview"
44
+ size="large"
45
+ selected={editContext.mode === "preview"}
46
+ onClick={() => editContext.setMode("preview")}
47
+ />
48
+ )}
37
49
 
38
- <SimpleIconButton
39
- className="relative"
40
- selected={editContext?.mode === "suggestions"}
41
- icon={<MessagesSquare strokeWidth={1} className="h-6 w-6 p-1" />}
42
- label="Write suggestions"
43
- size="large"
44
- onClick={() => editContext?.setMode("suggestions")}
45
- >
46
- {totalCount > 0 && (
47
- <div className="bg-theme-secondary text-3xs absolute -top-1 -right-1 flex h-[16px] min-w-[16px] items-center justify-center rounded-full px-1 text-white">
48
- {totalCount > 99 ? "99+" : totalCount}
49
- </div>
50
- )}
51
- </SimpleIconButton>
50
+ {!hideSuggestions && (
51
+ <SimpleIconButton
52
+ className="relative"
53
+ selected={editContext?.mode === "suggestions"}
54
+ icon={<MessagesSquare strokeWidth={1} className="h-6 w-6 p-1" />}
55
+ label="Write suggestions"
56
+ size="large"
57
+ onClick={() => editContext?.setMode("suggestions")}
58
+ >
59
+ {totalCount > 0 && (
60
+ <div className="bg-theme-secondary text-3xs absolute -top-1 -right-1 flex h-[16px] min-w-[16px] items-center justify-center rounded-full px-1 text-white">
61
+ {totalCount > 99 ? "99+" : totalCount}
62
+ </div>
63
+ )}
64
+ </SimpleIconButton>
65
+ )}
52
66
  </>
53
67
  );
54
68
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect, useState } from "react";
4
4
  import { useEditContext } from "../client/editContext";
5
- import { classNames } from "primereact/utils";
5
+
6
6
  import { MenuItem } from "primereact/menuitem";
7
7
 
8
8
  import { getAbsoluteIconUrl } from "../utils";
@@ -30,7 +30,9 @@ export function EditorForm({
30
30
  if (!pageViewContext) pageViewContext = editContext.pageView;
31
31
 
32
32
  const insertMode =
33
- editContext.insertMode && editContext.mode === "edit" && !compareView;
33
+ (editContext.insertMode || editContext.selectedForInsertion) &&
34
+ editContext.mode === "edit" &&
35
+ !compareView;
34
36
  const [activeTabKey, setActiveTabKey] = useState(
35
37
  initialActiveTab || "content",
36
38
  );
@@ -15,6 +15,7 @@ export function PageViewer({
15
15
  name,
16
16
  followEditsDefault,
17
17
  className,
18
+ noMargins,
18
19
  }: {
19
20
  pageViewContext?: PageViewContext;
20
21
  showFormEditor: boolean;
@@ -22,6 +23,7 @@ export function PageViewer({
22
23
  name: string;
23
24
  followEditsDefault?: boolean;
24
25
  className?: string;
26
+ noMargins?: boolean;
25
27
  }) {
26
28
  const editContext = useEditContext();
27
29
 
@@ -34,6 +36,10 @@ export function PageViewer({
34
36
  setFormEditorCollapsed(false);
35
37
  }, [editContext?.insertMode, editContext?.activeEditorTab]);
36
38
 
39
+ useEffect(() => {
40
+ setFormEditorCollapsed(false);
41
+ }, [editContext?.selection]);
42
+
37
43
  useEffect(() => {
38
44
  if (
39
45
  followEdits &&
@@ -81,7 +87,13 @@ export function PageViewer({
81
87
  defaultSize: 300,
82
88
  collapsible: true,
83
89
  content: (
84
- <div className="h-full w-full py-2 pl-2">
90
+ <div
91
+ className={cn(
92
+ "h-full w-full",
93
+ noMargins ? "py-2" : "py-2 pl-2",
94
+ formEditorCollapsed && "hidden",
95
+ )}
96
+ >
85
97
  <div className="border-gray-3 flex h-full w-full flex-col rounded-md border bg-white">
86
98
  <div className="flex-1">
87
99
  <EditorForm
@@ -102,11 +114,6 @@ export function PageViewer({
102
114
  label="Follow"
103
115
  onClick={() => setFollowEdits((f) => !f)}
104
116
  />
105
- <SimpleIconButton
106
- icon={<PanelLeftClose size={12} />}
107
- label="Collapse"
108
- onClick={() => setFormEditorCollapsed(true)}
109
- />
110
117
  </div>
111
118
  </div>
112
119
  </div>
@@ -122,6 +129,11 @@ export function PageViewer({
122
129
  <PageViewerFrame
123
130
  compareView={compareView || false}
124
131
  pageViewContext={pageViewContext}
132
+ className={cn(
133
+ "py-2",
134
+ noMargins ? "pr-0" : "px-2",
135
+ noMargins && showFormEditor && "ml-2",
136
+ )}
125
137
  />
126
138
  ),
127
139
  });
@@ -134,15 +146,6 @@ export function PageViewer({
134
146
  direction={editContext?.isMobile ? "vertical" : "horizontal"}
135
147
  localStorageKey={`editor.pageViewer.${name}`}
136
148
  />
137
- {formEditorCollapsed && (
138
- <div className="absolute bottom-0 left-0 z-10 flex items-center justify-center">
139
- <SimpleIconButton
140
- icon={<PanelLeftOpen size={12} />}
141
- label="Open Form"
142
- onClick={() => setFormEditorCollapsed(false)}
143
- />
144
- </div>
145
- )}
146
149
  </div>
147
150
  );
148
151
  }
@@ -6,7 +6,7 @@ import { PageEditorChrome } from "../page-editor-chrome/PageEditorChrome";
6
6
  import { PageViewContext } from "./pageViewContext";
7
7
  import morphdom from "morphdom";
8
8
  import uuid from "react-uuid";
9
-
9
+ import { cn } from "../../lib/utils";
10
10
  import {
11
11
  findComponentRect,
12
12
  findFieldElement,
@@ -42,9 +42,11 @@ declare global {
42
42
  export function PageViewerFrame({
43
43
  compareView,
44
44
  pageViewContext,
45
+ className,
45
46
  }: {
46
47
  compareView: boolean;
47
48
  pageViewContext?: PageViewContext;
49
+ className?: string;
48
50
  }) {
49
51
  const editContext = useEditContext();
50
52
 
@@ -761,7 +763,14 @@ export function PageViewerFrame({
761
763
  : pageViewContext.deviceHeight || 640;
762
764
 
763
765
  return (
764
- <div className="relative mt-[9px] mr-2 ml-2 flex h-full w-full flex-col items-center select-none">
766
+ <div
767
+ className={cn(
768
+ "relative flex h-full w-full flex-col items-center select-none",
769
+
770
+ className,
771
+ editContext.showRightSidebar && "pr-0",
772
+ )}
773
+ >
765
774
  {!editContext.pageView.fullscreen && (
766
775
  <EditorWarnings item={pageViewContext.page?.item} />
767
776
  )}