@agent-native/core 0.128.1 → 0.128.3
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/server/email-template.ts +33 -0
- package/corpus/templates/chat/_gitignore +2 -0
- package/corpus/templates/clips/AGENTS.md +6 -1
- package/corpus/templates/clips/actions/complete-transactional-email-summary.ts +56 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +258 -76
- package/corpus/templates/clips/actions/lib/direct-video.ts +96 -0
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +53 -2
- package/corpus/templates/clips/actions/lib/loom-video.ts +2 -62
- package/corpus/templates/clips/actions/lib/video-download-limits.ts +62 -0
- package/corpus/templates/clips/actions/list-transactional-email-ai-requests.ts +249 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +65 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +55 -0
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +28 -126
- package/corpus/templates/clips/app/global.css +23 -0
- package/corpus/templates/clips/app/hooks/use-transactional-email-bridge.ts +98 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +20 -0
- package/corpus/templates/clips/app/routes/_app.tsx +2 -0
- package/corpus/templates/clips/app/routes/import.tsx +396 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +8 -6
- package/corpus/templates/clips/app/routes/record.tsx +13 -71
- package/corpus/templates/clips/changelog/2026-07-25-two-clip-emails-now-include-a-concise-summary-generated-secu.md +6 -0
- package/corpus/templates/clips/data/clips-transactional-emails/config.json +3 -0
- package/corpus/templates/clips/server/jobs/transactional-emails.ts +1034 -0
- package/corpus/templates/clips/server/lib/transactional-email-store.ts +865 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +270 -0
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/routes/api/view-event.post.ts +59 -2
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
- package/corpus/templates/content/AGENTS.md +8 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +28 -3
- package/corpus/templates/content/actions/_property-utils.ts +32 -2
- package/corpus/templates/content/actions/configure-document-property.ts +12 -2
- package/corpus/templates/content/actions/delete-document-property.ts +13 -3
- package/corpus/templates/content/actions/duplicate-document-property.ts +13 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +5 -2
- package/corpus/templates/content/actions/list-document-properties.ts +12 -3
- package/corpus/templates/content/actions/move-database-item.ts +74 -55
- package/corpus/templates/content/actions/reorder-document-property.ts +20 -4
- package/corpus/templates/content/actions/set-document-property.ts +10 -1
- package/corpus/templates/content/actions/update-content-database-personal-view.ts +7 -0
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +31 -21
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +30 -3
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +8 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +72 -20
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +79 -10
- package/corpus/templates/content/app/components/editor/database/TimelineView.tsx +8 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +6 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +246 -26
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +491 -197
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +3 -3
- package/corpus/templates/content/app/components/sidebar/sidebar-reorder.tsx +409 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +117 -3
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +1 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +62 -18
- package/corpus/templates/content/app/hooks/use-documents.ts +13 -5
- package/corpus/templates/content/app/i18n-data.ts +261 -3
- package/corpus/templates/content/app/routes/_app.page.$id.tsx +9 -2
- package/corpus/templates/content/changelog/2026-07-24-database-page-properties-keep-their-context.md +6 -0
- package/corpus/templates/content/changelog/2026-07-26-sidebar-references-can-now-be-personally-reordered.md +6 -0
- package/corpus/templates/content/docs/solutions/2026-07-24-database-property-context-switch-diagnosis.md +249 -0
- package/corpus/templates/content/docs/solutions/content-sidebar-order-shape.md +389 -0
- package/corpus/templates/content/shared/api.ts +28 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/editor/DragHandle.ts +63 -9
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/server/email-template.d.ts +7 -0
- package/dist/server/email-template.d.ts.map +1 -1
- package/dist/server/email-template.js +24 -0
- package/dist/server/email-template.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/templates/chat/_gitignore +2 -0
- package/package.json +3 -2
- package/src/server/email-template.ts +33 -0
- package/src/templates/chat/_gitignore +2 -0
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +0 -631
- package/dist/cli/sync-builder-starter-manifest.d.ts +0 -79
- package/dist/cli/sync-builder-starter-manifest.d.ts.map +0 -1
- package/dist/cli/sync-builder-starter-manifest.js +0 -426
- package/dist/cli/sync-builder-starter-manifest.js.map +0 -1
- package/src/cli/sync-builder-starter-manifest.ts +0 -631
|
@@ -175,7 +175,7 @@ export function FavoriteDocumentItem({
|
|
|
175
175
|
}}
|
|
176
176
|
>
|
|
177
177
|
<IconStar size={14} className="me-2 fill-current" />
|
|
178
|
-
{t("sidebar.
|
|
178
|
+
{t("sidebar.unpinFromSidebar")}
|
|
179
179
|
</DropdownMenuItem>
|
|
180
180
|
)}
|
|
181
181
|
{canEdit && canManage && <DropdownMenuSeparator />}
|
|
@@ -419,8 +419,8 @@ export function DocumentTreeItem({
|
|
|
419
419
|
className={cn("me-2", node.isFavorite && "fill-current")}
|
|
420
420
|
/>
|
|
421
421
|
{node.isFavorite
|
|
422
|
-
? "
|
|
423
|
-
: "
|
|
422
|
+
? t("sidebar.unpinFromSidebar")
|
|
423
|
+
: t("sidebar.pinToSidebar")}
|
|
424
424
|
</DropdownMenuItem>
|
|
425
425
|
)}
|
|
426
426
|
{canEdit && canManage && <DropdownMenuSeparator />}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closestCenter,
|
|
3
|
+
DndContext,
|
|
4
|
+
KeyboardSensor,
|
|
5
|
+
PointerSensor,
|
|
6
|
+
useSensor,
|
|
7
|
+
useSensors,
|
|
8
|
+
type Announcements,
|
|
9
|
+
type DragEndEvent,
|
|
10
|
+
type DragOverEvent,
|
|
11
|
+
type DragStartEvent,
|
|
12
|
+
} from "@dnd-kit/core";
|
|
13
|
+
import {
|
|
14
|
+
arrayMove,
|
|
15
|
+
sortableKeyboardCoordinates,
|
|
16
|
+
SortableContext,
|
|
17
|
+
useSortable,
|
|
18
|
+
verticalListSortingStrategy,
|
|
19
|
+
} from "@dnd-kit/sortable";
|
|
20
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
21
|
+
import {
|
|
22
|
+
createContext,
|
|
23
|
+
useContext,
|
|
24
|
+
useEffect,
|
|
25
|
+
useRef,
|
|
26
|
+
useState,
|
|
27
|
+
type CSSProperties,
|
|
28
|
+
type ReactNode,
|
|
29
|
+
} from "react";
|
|
30
|
+
|
|
31
|
+
import { cn } from "@/lib/utils";
|
|
32
|
+
|
|
33
|
+
export interface SidebarReorderItem {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
parentId: string | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SidebarReorderLabels {
|
|
40
|
+
drag: (label: string) => string;
|
|
41
|
+
moveUp: string;
|
|
42
|
+
moveDown: string;
|
|
43
|
+
moveTo: string;
|
|
44
|
+
moveToPosition: (position: number) => string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface SidebarReorderContextValue {
|
|
48
|
+
items: SidebarReorderItem[];
|
|
49
|
+
activeId: string | null;
|
|
50
|
+
overId: string | null;
|
|
51
|
+
dragBounds: { minY: number; maxY: number } | null;
|
|
52
|
+
registerItemNode: (itemId: string, node: HTMLElement | null) => void;
|
|
53
|
+
onReorder: (
|
|
54
|
+
itemIds: string[],
|
|
55
|
+
moved: { itemId: string; position: number },
|
|
56
|
+
) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const SidebarReorderContext = createContext<SidebarReorderContextValue | null>(
|
|
60
|
+
null,
|
|
61
|
+
);
|
|
62
|
+
const DRAG_RELEASE_CLICK_WINDOW_MS = 60;
|
|
63
|
+
|
|
64
|
+
export function isSidebarDragReleaseClick(
|
|
65
|
+
event: Pick<MouseEvent, "button" | "detail" | "target">,
|
|
66
|
+
itemId: string,
|
|
67
|
+
) {
|
|
68
|
+
if (event.button !== 0 || event.detail !== 1) return false;
|
|
69
|
+
const target = event.target;
|
|
70
|
+
if (!(target instanceof Element)) return false;
|
|
71
|
+
const row = target.closest<HTMLElement>("[data-sidebar-reorder-item-id]");
|
|
72
|
+
return row?.dataset.sidebarReorderItemId === itemId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isPointerSidebarDrag(activatorEvent: Event) {
|
|
76
|
+
return (
|
|
77
|
+
typeof PointerEvent !== "undefined" &&
|
|
78
|
+
activatorEvent instanceof PointerEvent
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function reorderedSidebarItemIds(
|
|
83
|
+
items: SidebarReorderItem[],
|
|
84
|
+
activeId: string,
|
|
85
|
+
overId: string,
|
|
86
|
+
) {
|
|
87
|
+
const activeIndex = items.findIndex((item) => item.id === activeId);
|
|
88
|
+
const overIndex = items.findIndex((item) => item.id === overId);
|
|
89
|
+
if (activeIndex < 0 || overIndex < 0 || activeIndex === overIndex) {
|
|
90
|
+
return items.map((item) => item.id);
|
|
91
|
+
}
|
|
92
|
+
if (items[activeIndex].parentId !== items[overIndex].parentId) {
|
|
93
|
+
return items.map((item) => item.id);
|
|
94
|
+
}
|
|
95
|
+
const siblingIndexes = items.flatMap((item, index) =>
|
|
96
|
+
item.parentId === items[activeIndex].parentId ? [index] : [],
|
|
97
|
+
);
|
|
98
|
+
const activeSiblingIndex = siblingIndexes.indexOf(activeIndex);
|
|
99
|
+
const overSiblingIndex = siblingIndexes.indexOf(overIndex);
|
|
100
|
+
const reorderedSiblings = arrayMove(
|
|
101
|
+
siblingIndexes.map((index) => items[index]),
|
|
102
|
+
activeSiblingIndex,
|
|
103
|
+
overSiblingIndex,
|
|
104
|
+
);
|
|
105
|
+
const nextItems = [...items];
|
|
106
|
+
siblingIndexes.forEach((index, siblingIndex) => {
|
|
107
|
+
nextItems[index] = reorderedSiblings[siblingIndex];
|
|
108
|
+
});
|
|
109
|
+
return nextItems.map((item) => item.id);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function sidebarReorderAnnouncement(
|
|
113
|
+
items: SidebarReorderItem[],
|
|
114
|
+
itemId: string,
|
|
115
|
+
overId: string | null,
|
|
116
|
+
labels: SidebarReorderLabels,
|
|
117
|
+
) {
|
|
118
|
+
const item = items.find((candidate) => candidate.id === itemId);
|
|
119
|
+
if (!item) return undefined;
|
|
120
|
+
const siblings = items.filter(
|
|
121
|
+
(candidate) => candidate.parentId === item.parentId,
|
|
122
|
+
);
|
|
123
|
+
const over = siblings.find((candidate) => candidate.id === overId);
|
|
124
|
+
const position = Math.max(0, siblings.indexOf(over ?? item));
|
|
125
|
+
return `${labels.drag(item.label)}. ${labels.moveToPosition(position + 1)}.`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function constrainedSidebarTransform<
|
|
129
|
+
Transform extends { x: number; y: number },
|
|
130
|
+
>(
|
|
131
|
+
transform: Transform,
|
|
132
|
+
isDragging: boolean,
|
|
133
|
+
bounds: { minY: number; maxY: number } | null,
|
|
134
|
+
) {
|
|
135
|
+
return {
|
|
136
|
+
...transform,
|
|
137
|
+
x: 0,
|
|
138
|
+
y:
|
|
139
|
+
isDragging && bounds
|
|
140
|
+
? Math.min(Math.max(transform.y, bounds.minY), bounds.maxY)
|
|
141
|
+
: transform.y,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function SidebarReorderProvider({
|
|
146
|
+
items,
|
|
147
|
+
labels,
|
|
148
|
+
onReorder,
|
|
149
|
+
children,
|
|
150
|
+
}: {
|
|
151
|
+
items: SidebarReorderItem[];
|
|
152
|
+
labels: SidebarReorderLabels;
|
|
153
|
+
onReorder: (
|
|
154
|
+
itemIds: string[],
|
|
155
|
+
moved: { itemId: string; position: number },
|
|
156
|
+
) => void;
|
|
157
|
+
children: ReactNode;
|
|
158
|
+
}) {
|
|
159
|
+
const itemNodes = useRef(new Map<string, HTMLElement>());
|
|
160
|
+
const suppressedClickItemId = useRef<string | null>(null);
|
|
161
|
+
const suppressedClickTimer = useRef<ReturnType<typeof setTimeout> | null>(
|
|
162
|
+
null,
|
|
163
|
+
);
|
|
164
|
+
const [activeId, setActiveId] = useState<string | null>(null);
|
|
165
|
+
const [overId, setOverId] = useState<string | null>(null);
|
|
166
|
+
const [dragBounds, setDragBounds] = useState<{
|
|
167
|
+
minY: number;
|
|
168
|
+
maxY: number;
|
|
169
|
+
} | null>(null);
|
|
170
|
+
const sensors = useSensors(
|
|
171
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
172
|
+
useSensor(KeyboardSensor, {
|
|
173
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
const announcements: Announcements = {
|
|
177
|
+
onDragStart: ({ active }) =>
|
|
178
|
+
sidebarReorderAnnouncement(items, String(active.id), null, labels),
|
|
179
|
+
onDragOver: ({ active, over }) =>
|
|
180
|
+
sidebarReorderAnnouncement(
|
|
181
|
+
items,
|
|
182
|
+
String(active.id),
|
|
183
|
+
over ? String(over.id) : null,
|
|
184
|
+
labels,
|
|
185
|
+
),
|
|
186
|
+
onDragEnd: ({ active, over }) =>
|
|
187
|
+
sidebarReorderAnnouncement(
|
|
188
|
+
items,
|
|
189
|
+
String(active.id),
|
|
190
|
+
over ? String(over.id) : null,
|
|
191
|
+
labels,
|
|
192
|
+
),
|
|
193
|
+
onDragCancel: ({ active }) =>
|
|
194
|
+
sidebarReorderAnnouncement(items, String(active.id), null, labels),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
function preventDraggedLinkNavigation(event: MouseEvent) {
|
|
199
|
+
const itemId = suppressedClickItemId.current;
|
|
200
|
+
if (!itemId || !isSidebarDragReleaseClick(event, itemId)) return;
|
|
201
|
+
|
|
202
|
+
event.preventDefault();
|
|
203
|
+
suppressedClickItemId.current = null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
document.addEventListener("click", preventDraggedLinkNavigation, true);
|
|
207
|
+
return () => {
|
|
208
|
+
document.removeEventListener("click", preventDraggedLinkNavigation, true);
|
|
209
|
+
if (suppressedClickTimer.current) {
|
|
210
|
+
clearTimeout(suppressedClickTimer.current);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}, []);
|
|
214
|
+
|
|
215
|
+
function registerItemNode(itemId: string, node: HTMLElement | null) {
|
|
216
|
+
if (node) itemNodes.current.set(itemId, node);
|
|
217
|
+
else itemNodes.current.delete(itemId);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function handleDragStart(event: DragStartEvent) {
|
|
221
|
+
const itemId = String(event.active.id);
|
|
222
|
+
const item = items.find((candidate) => candidate.id === itemId);
|
|
223
|
+
const activeNode = itemNodes.current.get(itemId);
|
|
224
|
+
if (!item || !activeNode) return;
|
|
225
|
+
|
|
226
|
+
const activeRect = activeNode.getBoundingClientRect();
|
|
227
|
+
const siblingRects = items
|
|
228
|
+
.filter((candidate) => candidate.parentId === item.parentId)
|
|
229
|
+
.flatMap((candidate) => {
|
|
230
|
+
const node = itemNodes.current.get(candidate.id);
|
|
231
|
+
return node ? [node.getBoundingClientRect()] : [];
|
|
232
|
+
});
|
|
233
|
+
setActiveId(itemId);
|
|
234
|
+
setOverId(itemId);
|
|
235
|
+
setDragBounds({
|
|
236
|
+
minY:
|
|
237
|
+
Math.min(...siblingRects.map((rect) => rect.top), activeRect.top) -
|
|
238
|
+
activeRect.top,
|
|
239
|
+
maxY:
|
|
240
|
+
Math.max(
|
|
241
|
+
...siblingRects.map((rect) => rect.bottom),
|
|
242
|
+
activeRect.bottom,
|
|
243
|
+
) - activeRect.bottom,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function handleDragOver(event: DragOverEvent) {
|
|
248
|
+
setOverId(event.over ? String(event.over.id) : null);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function clearDragState() {
|
|
252
|
+
setActiveId(null);
|
|
253
|
+
setOverId(null);
|
|
254
|
+
setDragBounds(null);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function suppressReleaseClick(itemId: string) {
|
|
258
|
+
suppressedClickItemId.current = itemId;
|
|
259
|
+
if (suppressedClickTimer.current) {
|
|
260
|
+
clearTimeout(suppressedClickTimer.current);
|
|
261
|
+
}
|
|
262
|
+
suppressedClickTimer.current = setTimeout(() => {
|
|
263
|
+
if (suppressedClickItemId.current === itemId) {
|
|
264
|
+
suppressedClickItemId.current = null;
|
|
265
|
+
}
|
|
266
|
+
suppressedClickTimer.current = null;
|
|
267
|
+
}, DRAG_RELEASE_CLICK_WINDOW_MS);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function handleDragEnd(event: DragEndEvent) {
|
|
271
|
+
const itemId = String(event.active.id);
|
|
272
|
+
if (isPointerSidebarDrag(event.activatorEvent)) {
|
|
273
|
+
suppressReleaseClick(itemId);
|
|
274
|
+
}
|
|
275
|
+
const overId = event.over?.id;
|
|
276
|
+
if (overId) {
|
|
277
|
+
const currentIds = items.map((item) => item.id);
|
|
278
|
+
const nextIds = reorderedSidebarItemIds(
|
|
279
|
+
items,
|
|
280
|
+
String(event.active.id),
|
|
281
|
+
String(overId),
|
|
282
|
+
);
|
|
283
|
+
if (nextIds.some((id, index) => id !== currentIds[index])) {
|
|
284
|
+
onReorder(nextIds, { itemId, position: nextIds.indexOf(itemId) });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
clearDragState();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<SidebarReorderContext.Provider
|
|
292
|
+
value={{
|
|
293
|
+
items,
|
|
294
|
+
activeId,
|
|
295
|
+
overId,
|
|
296
|
+
dragBounds,
|
|
297
|
+
registerItemNode,
|
|
298
|
+
onReorder,
|
|
299
|
+
}}
|
|
300
|
+
>
|
|
301
|
+
<DndContext
|
|
302
|
+
sensors={sensors}
|
|
303
|
+
collisionDetection={closestCenter}
|
|
304
|
+
autoScroll={false}
|
|
305
|
+
onDragStart={handleDragStart}
|
|
306
|
+
onDragOver={handleDragOver}
|
|
307
|
+
onDragEnd={handleDragEnd}
|
|
308
|
+
onDragCancel={clearDragState}
|
|
309
|
+
accessibility={{
|
|
310
|
+
announcements,
|
|
311
|
+
screenReaderInstructions: {
|
|
312
|
+
draggable: `${labels.moveTo}. ${labels.moveUp}. ${labels.moveDown}.`,
|
|
313
|
+
},
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
<SortableContext
|
|
317
|
+
items={items.map((item) => item.id)}
|
|
318
|
+
strategy={verticalListSortingStrategy}
|
|
319
|
+
>
|
|
320
|
+
{children}
|
|
321
|
+
</SortableContext>
|
|
322
|
+
</DndContext>
|
|
323
|
+
</SidebarReorderContext.Provider>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export function useSidebarReorderItem(itemId: string) {
|
|
328
|
+
const context = useContext(SidebarReorderContext);
|
|
329
|
+
const sortable = useSortable({ id: itemId, disabled: !context });
|
|
330
|
+
const item = context?.items.find((candidate) => candidate.id === itemId);
|
|
331
|
+
const siblings = item
|
|
332
|
+
? (context?.items.filter(
|
|
333
|
+
(candidate) => candidate.parentId === item.parentId,
|
|
334
|
+
) ?? [])
|
|
335
|
+
: [];
|
|
336
|
+
const siblingIndex = siblings.findIndex(
|
|
337
|
+
(candidate) => candidate.id === itemId,
|
|
338
|
+
);
|
|
339
|
+
const activeSiblingIndex = siblings.findIndex(
|
|
340
|
+
(candidate) => candidate.id === context?.activeId,
|
|
341
|
+
);
|
|
342
|
+
const transform = sortable.transform
|
|
343
|
+
? constrainedSidebarTransform(
|
|
344
|
+
sortable.transform,
|
|
345
|
+
sortable.isDragging,
|
|
346
|
+
context?.dragBounds ?? null,
|
|
347
|
+
)
|
|
348
|
+
: null;
|
|
349
|
+
const dropIndicator: "before" | "after" | null =
|
|
350
|
+
context?.activeId &&
|
|
351
|
+
context.activeId !== itemId &&
|
|
352
|
+
context.overId === itemId &&
|
|
353
|
+
activeSiblingIndex >= 0
|
|
354
|
+
? activeSiblingIndex < siblingIndex
|
|
355
|
+
? "after"
|
|
356
|
+
: "before"
|
|
357
|
+
: null;
|
|
358
|
+
|
|
359
|
+
function moveToSibling(target: SidebarReorderItem | undefined) {
|
|
360
|
+
if (!context || !target) return;
|
|
361
|
+
const nextIds = reorderedSidebarItemIds(context.items, itemId, target.id);
|
|
362
|
+
context.onReorder(nextIds, {
|
|
363
|
+
itemId,
|
|
364
|
+
position: nextIds.indexOf(itemId),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
setNodeRef: (node: HTMLElement | null) => {
|
|
370
|
+
sortable.setNodeRef(node);
|
|
371
|
+
context?.registerItemNode(itemId, node);
|
|
372
|
+
},
|
|
373
|
+
style: {
|
|
374
|
+
transform: CSS.Transform.toString(transform),
|
|
375
|
+
transition: sortable.transition,
|
|
376
|
+
opacity: sortable.isDragging ? 0.55 : undefined,
|
|
377
|
+
} satisfies CSSProperties,
|
|
378
|
+
attributes: sortable.attributes,
|
|
379
|
+
listeners: sortable.listeners,
|
|
380
|
+
itemId,
|
|
381
|
+
isDragging: sortable.isDragging,
|
|
382
|
+
dropIndicator,
|
|
383
|
+
siblings,
|
|
384
|
+
siblingIndex,
|
|
385
|
+
moveUp: () => moveToSibling(siblings[siblingIndex - 1]),
|
|
386
|
+
moveDown: () => moveToSibling(siblings[siblingIndex + 1]),
|
|
387
|
+
moveTo: (position: number) => moveToSibling(siblings[position]),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function SidebarDropIndicator({
|
|
392
|
+
placement,
|
|
393
|
+
className,
|
|
394
|
+
}: {
|
|
395
|
+
placement: "before" | "after" | null;
|
|
396
|
+
className?: string;
|
|
397
|
+
}) {
|
|
398
|
+
if (!placement) return null;
|
|
399
|
+
return (
|
|
400
|
+
<span
|
|
401
|
+
aria-hidden="true"
|
|
402
|
+
className={cn(
|
|
403
|
+
"pointer-events-none absolute inset-x-1 z-20 h-0.5 rounded-full bg-foreground",
|
|
404
|
+
placement === "before" ? "-top-px" : "-bottom-px",
|
|
405
|
+
className,
|
|
406
|
+
)}
|
|
407
|
+
/>
|
|
408
|
+
);
|
|
409
|
+
}
|
|
@@ -110,6 +110,25 @@ export function removeOptimisticItemFromContentDatabase(
|
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
export function moveOptimisticContentDatabaseItem(
|
|
114
|
+
current: ContentDatabaseResponse | undefined,
|
|
115
|
+
itemId: string,
|
|
116
|
+
position: number,
|
|
117
|
+
) {
|
|
118
|
+
if (!current) return current;
|
|
119
|
+
const currentIndex = current.items.findIndex((item) => item.id === itemId);
|
|
120
|
+
if (currentIndex < 0 || current.items.length < 2) return current;
|
|
121
|
+
const nextIndex = Math.min(Math.max(position, 0), current.items.length - 1);
|
|
122
|
+
if (currentIndex === nextIndex) return current;
|
|
123
|
+
const items = [...current.items];
|
|
124
|
+
const [moved] = items.splice(currentIndex, 1);
|
|
125
|
+
items.splice(nextIndex, 0, moved);
|
|
126
|
+
return {
|
|
127
|
+
...current,
|
|
128
|
+
items: items.map((item, index) => ({ ...item, position: index })),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
113
132
|
export function preserveScopedDatabasePlaceholder<T>(
|
|
114
133
|
previous: T | undefined,
|
|
115
134
|
previousQuery: Pick<Query, "queryKey"> | undefined,
|
|
@@ -823,10 +842,62 @@ export function useMoveDatabaseItem(documentId: string) {
|
|
|
823
842
|
return useActionMutation<ContentDatabaseResponse, MoveDatabaseItemRequest>(
|
|
824
843
|
"move-database-item",
|
|
825
844
|
{
|
|
826
|
-
|
|
845
|
+
skipActionQueryInvalidation: true,
|
|
846
|
+
onMutate: async (variables) => {
|
|
847
|
+
await queryClient.cancelQueries({
|
|
848
|
+
queryKey: ["action", "get-content-database"],
|
|
849
|
+
});
|
|
850
|
+
const previous = queryClient.getQueriesData<ContentDatabaseResponse>({
|
|
851
|
+
queryKey: ["action", "get-content-database"],
|
|
852
|
+
});
|
|
853
|
+
if (variables.itemId) {
|
|
854
|
+
queryClient.setQueriesData<ContentDatabaseResponse>(
|
|
855
|
+
{ queryKey: ["action", "get-content-database"] },
|
|
856
|
+
(current) => {
|
|
857
|
+
if (
|
|
858
|
+
variables.databaseId &&
|
|
859
|
+
current?.database.id !== variables.databaseId
|
|
860
|
+
) {
|
|
861
|
+
return current;
|
|
862
|
+
}
|
|
863
|
+
return moveOptimisticContentDatabaseItem(
|
|
864
|
+
current,
|
|
865
|
+
variables.itemId!,
|
|
866
|
+
variables.position,
|
|
867
|
+
);
|
|
868
|
+
},
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
return { previous };
|
|
872
|
+
},
|
|
873
|
+
onError: (_error, _variables, context) => {
|
|
874
|
+
const rollback = context as
|
|
875
|
+
| {
|
|
876
|
+
previous?: Array<
|
|
877
|
+
[readonly unknown[], ContentDatabaseResponse | undefined]
|
|
878
|
+
>;
|
|
879
|
+
}
|
|
880
|
+
| undefined;
|
|
881
|
+
for (const [queryKey, data] of rollback?.previous ?? []) {
|
|
882
|
+
queryClient.setQueryData(queryKey, data);
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
onSuccess: (data) => {
|
|
886
|
+
writeContentDatabaseResponseToCache(queryClient, documentId, data);
|
|
887
|
+
queryClient.setQueryData(
|
|
888
|
+
contentDatabaseByIdQueryKey(data.database.id),
|
|
889
|
+
data,
|
|
890
|
+
);
|
|
891
|
+
},
|
|
892
|
+
onSettled: (_data, _error, variables) => {
|
|
827
893
|
queryClient.invalidateQueries({
|
|
828
894
|
queryKey: contentDatabaseQueryKey(documentId),
|
|
829
895
|
});
|
|
896
|
+
if (variables.databaseId) {
|
|
897
|
+
queryClient.invalidateQueries({
|
|
898
|
+
queryKey: contentDatabaseByIdQueryKey(variables.databaseId),
|
|
899
|
+
});
|
|
900
|
+
}
|
|
830
901
|
queryClient.invalidateQueries({
|
|
831
902
|
queryKey: ["action", "list-documents"],
|
|
832
903
|
});
|
|
@@ -875,6 +946,31 @@ export function useUpdateContentDatabasePersonalView(
|
|
|
875
946
|
ContentDatabasePersonalViewResponse,
|
|
876
947
|
UpdateContentDatabasePersonalViewRequest
|
|
877
948
|
>("update-content-database-personal-view", {
|
|
949
|
+
skipActionQueryInvalidation: true,
|
|
950
|
+
onMutate: async (variables) => {
|
|
951
|
+
if (!databaseId) return undefined;
|
|
952
|
+
const queryKey = [
|
|
953
|
+
"action",
|
|
954
|
+
"get-content-database-personal-view",
|
|
955
|
+
{ databaseId },
|
|
956
|
+
] as const;
|
|
957
|
+
await queryClient.cancelQueries({ queryKey });
|
|
958
|
+
const previous = queryClient.getQueryData(queryKey);
|
|
959
|
+
queryClient.setQueryData(queryKey, {
|
|
960
|
+
databaseId,
|
|
961
|
+
overrides: variables.overrides,
|
|
962
|
+
});
|
|
963
|
+
return { previous };
|
|
964
|
+
},
|
|
965
|
+
onError: (_error, _variables, context) => {
|
|
966
|
+
if (!databaseId) return;
|
|
967
|
+
const previous = (context as { previous?: unknown } | undefined)
|
|
968
|
+
?.previous;
|
|
969
|
+
queryClient.setQueryData(
|
|
970
|
+
["action", "get-content-database-personal-view", { databaseId }],
|
|
971
|
+
previous,
|
|
972
|
+
);
|
|
973
|
+
},
|
|
878
974
|
onSuccess: (data) => {
|
|
879
975
|
if (!databaseId) return;
|
|
880
976
|
queryClient.setQueryData(
|
|
@@ -882,6 +978,16 @@ export function useUpdateContentDatabasePersonalView(
|
|
|
882
978
|
data,
|
|
883
979
|
);
|
|
884
980
|
},
|
|
981
|
+
onSettled: () => {
|
|
982
|
+
if (!databaseId) return;
|
|
983
|
+
queryClient.invalidateQueries({
|
|
984
|
+
queryKey: [
|
|
985
|
+
"action",
|
|
986
|
+
"get-content-database-personal-view",
|
|
987
|
+
{ databaseId },
|
|
988
|
+
],
|
|
989
|
+
});
|
|
990
|
+
},
|
|
885
991
|
});
|
|
886
992
|
}
|
|
887
993
|
|
|
@@ -974,7 +1080,11 @@ export function useAddContentDatabaseSourceFieldProperty(documentId: string) {
|
|
|
974
1080
|
queryKey: ["action", "get-content-database-source", { documentId }],
|
|
975
1081
|
});
|
|
976
1082
|
queryClient.invalidateQueries({
|
|
977
|
-
queryKey: [
|
|
1083
|
+
queryKey: [
|
|
1084
|
+
"action",
|
|
1085
|
+
"list-document-properties",
|
|
1086
|
+
{ documentId, databaseId: data.databaseId },
|
|
1087
|
+
],
|
|
978
1088
|
});
|
|
979
1089
|
},
|
|
980
1090
|
});
|
|
@@ -995,7 +1105,11 @@ export function useMaterializeBuilderRequiredFields(documentId: string) {
|
|
|
995
1105
|
queryKey: ["action", "get-content-database-source", { documentId }],
|
|
996
1106
|
});
|
|
997
1107
|
queryClient.invalidateQueries({
|
|
998
|
-
queryKey: [
|
|
1108
|
+
queryKey: [
|
|
1109
|
+
"action",
|
|
1110
|
+
"list-document-properties",
|
|
1111
|
+
{ documentId, databaseId: data.database.id },
|
|
1112
|
+
],
|
|
999
1113
|
});
|
|
1000
1114
|
},
|
|
1001
1115
|
});
|