@alpaca-editor/core 1.0.4015 → 1.0.4017
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ui/calendar.js +2 -6
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/ui/card.d.ts +2 -1
- package/dist/components/ui/card.js +2 -2
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/copy-button.js +31 -3
- package/dist/components/ui/copy-button.js.map +1 -1
- package/dist/config/config.js +20 -13
- package/dist/config/config.js.map +1 -1
- package/dist/editor/FieldList.js +1 -1
- package/dist/editor/FieldList.js.map +1 -1
- package/dist/editor/FieldListField.js +1 -1
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ScrollingContentTree.d.ts +2 -1
- package/dist/editor/ScrollingContentTree.js +2 -2
- package/dist/editor/ScrollingContentTree.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +1 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/itemsRepository.d.ts +2 -2
- package/dist/editor/client/itemsRepository.js +15 -5
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.js +18 -6
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +2 -1
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/context-menu/InsertMenu.js +45 -12
- package/dist/editor/context-menu/InsertMenu.js.map +1 -1
- package/dist/editor/field-types/DateFieldEditor.js +1 -1
- package/dist/editor/field-types/DateFieldEditor.js.map +1 -1
- package/dist/editor/field-types/DateTimeFieldEditor.d.ts +5 -0
- package/dist/editor/field-types/DateTimeFieldEditor.js +151 -0
- package/dist/editor/field-types/DateTimeFieldEditor.js.map +1 -0
- package/dist/editor/field-types/InternalLinkFieldEditor.js +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/TreeListEditor.js +17 -6
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/fieldTypes.d.ts +6 -0
- package/dist/editor/menubar/ToolbarFactory.js +1 -1
- package/dist/editor/menubar/ToolbarFactory.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.d.ts +7 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +2 -2
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +3 -1
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.d.ts +2 -1
- package/dist/editor/page-viewer/PageViewer.js +7 -5
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +3 -2
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/sidebar/ComponentPalette.js +28 -1
- package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
- package/dist/editor/sidebar/Debug.js +13 -3
- package/dist/editor/sidebar/Debug.js.map +1 -1
- package/dist/editor/ui/ItemSearch.d.ts +1 -0
- package/dist/editor/ui/ItemSearch.js +5 -5
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/editor/ui/SimpleTabs.d.ts +2 -1
- package/dist/editor/ui/SimpleTabs.js +8 -7
- package/dist/editor/ui/SimpleTabs.js.map +1 -1
- package/dist/editor/ui/Spinner.d.ts +1 -1
- package/dist/page-wizard/WizardSteps.js +5 -4
- package/dist/page-wizard/WizardSteps.js.map +1 -1
- package/dist/page-wizard/steps/CollectStep.js +1 -1
- package/dist/page-wizard/steps/CollectStep.js.map +1 -1
- package/dist/page-wizard/steps/ContentStep.js +4 -3
- package/dist/page-wizard/steps/ContentStep.js.map +1 -1
- package/dist/page-wizard/steps/FindItemsStep.js +2 -13
- package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
- package/dist/page-wizard/steps/usePageCreator.js +2 -4
- package/dist/page-wizard/steps/usePageCreator.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +357 -60
- package/package.json +1 -1
- package/src/components/ui/calendar.tsx +2 -18
- package/src/components/ui/card.tsx +5 -0
- package/src/components/ui/copy-button.tsx +32 -3
- package/src/config/config.tsx +21 -14
- package/src/editor/FieldList.tsx +1 -1
- package/src/editor/FieldListField.tsx +1 -1
- package/src/editor/ScrollingContentTree.tsx +3 -0
- package/src/editor/client/editContext.ts +1 -0
- package/src/editor/client/itemsRepository.ts +25 -7
- package/src/editor/client/operations.ts +19 -5
- package/src/editor/client/pageModelBuilder.ts +1 -1
- package/src/editor/context-menu/InsertMenu.tsx +77 -30
- package/src/editor/field-types/DateFieldEditor.tsx +8 -2
- package/src/editor/field-types/DateTimeFieldEditor.tsx +281 -0
- package/src/editor/field-types/InternalLinkFieldEditor.tsx +1 -1
- package/src/editor/field-types/TreeListEditor.tsx +41 -8
- package/src/editor/fieldTypes.ts +8 -0
- package/src/editor/menubar/ToolbarFactory.tsx +2 -1
- package/src/editor/menubar/toolbar-sections/EditControls.tsx +37 -23
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
- package/src/editor/page-viewer/EditorForm.tsx +3 -1
- package/src/editor/page-viewer/PageViewer.tsx +18 -15
- package/src/editor/page-viewer/PageViewerFrame.tsx +11 -2
- package/src/editor/sidebar/ComponentPalette.tsx +36 -1
- package/src/editor/sidebar/Debug.tsx +14 -6
- package/src/editor/ui/ItemSearch.tsx +6 -3
- package/src/editor/ui/SimpleTabs.tsx +10 -1
- package/src/editor/ui/Spinner.tsx +1 -1
- package/src/page-wizard/WizardSteps.tsx +2 -3
- package/src/page-wizard/steps/CollectStep.tsx +1 -1
- package/src/page-wizard/steps/ContentStep.tsx +18 -12
- package/src/page-wizard/steps/FindItemsStep.tsx +1 -50
- package/src/page-wizard/steps/usePageCreator.ts +2 -3
- package/src/revision.ts +2 -2
- 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
|
-
|
|
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
|
|
197
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
}}
|
package/src/editor/fieldTypes.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
}
|
|
@@ -30,7 +30,9 @@ export function EditorForm({
|
|
|
30
30
|
if (!pageViewContext) pageViewContext = editContext.pageView;
|
|
31
31
|
|
|
32
32
|
const insertMode =
|
|
33
|
-
editContext.insertMode
|
|
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
|
|
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
|
|
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
|
)}
|