@agent-native/core 0.77.8 → 0.77.11
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 +1 -1
- package/corpus/core/CHANGELOG.md +35 -0
- package/corpus/core/package.json +3 -3
- package/corpus/core/src/a2a/task-store.ts +25 -3
- package/corpus/core/src/agent/harness/store.ts +89 -55
- package/corpus/core/src/agent/observational-memory/store.ts +40 -14
- package/corpus/core/src/agent/run-store.ts +116 -59
- package/corpus/core/src/application-state/store.ts +36 -2
- package/corpus/core/src/audit/store.ts +37 -6
- package/corpus/core/src/browser-sessions/store.ts +31 -12
- package/corpus/core/src/chat-threads/store.ts +67 -3
- package/corpus/core/src/checkpoints/store.ts +14 -3
- package/corpus/core/src/cli/create.ts +9 -0
- package/corpus/core/src/collab/awareness.ts +12 -9
- package/corpus/core/src/collab/client.ts +4 -2
- package/corpus/core/src/collab/storage.ts +20 -2
- package/corpus/core/src/db/ddl-guard.ts +112 -0
- package/corpus/core/src/extensions/store.ts +76 -3
- package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
- package/corpus/core/src/integrations/config-store.ts +19 -18
- package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
- package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
- package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
- package/corpus/core/src/integrations/remote-push-store.ts +75 -35
- package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
- package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
- package/corpus/core/src/mcp/connect-store.ts +53 -16
- package/corpus/core/src/mcp/oauth-store.ts +32 -7
- package/corpus/core/src/notifications/store.ts +20 -4
- package/corpus/core/src/oauth-tokens/store.ts +45 -2
- package/corpus/core/src/observability/store.ts +154 -47
- package/corpus/core/src/progress/store.ts +36 -21
- package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
- package/corpus/core/src/provider-api/custom-registry.ts +15 -4
- package/corpus/core/src/provider-api/quota-governor.ts +15 -3
- package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
- package/corpus/core/src/resources/store.ts +66 -31
- package/corpus/core/src/secrets/storage.ts +18 -22
- package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
- package/corpus/core/src/server/auth.ts +22 -4
- package/corpus/core/src/server/better-auth-instance.ts +49 -15
- package/corpus/core/src/server/embed-session.ts +19 -4
- package/corpus/core/src/server/identity-sso-store.ts +40 -11
- package/corpus/core/src/server/recap-image-store.ts +28 -9
- package/corpus/core/src/settings/store.ts +14 -19
- package/corpus/core/src/usage/store.ts +51 -16
- package/corpus/core/src/workspace-connections/store.ts +200 -22
- package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
- package/corpus/templates/clips/chrome-extension/package.json +1 -1
- package/corpus/templates/content/actions/list-trashed-content-databases.ts +36 -2
- package/corpus/templates/content/actions/move-document.ts +79 -33
- package/corpus/templates/content/actions/restore-content-database.ts +40 -3
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +84 -2
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +177 -49
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +393 -219
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +632 -467
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +114 -14
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +56 -15
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +6 -5
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +12 -4
- package/corpus/templates/content/app/global.css +9 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +36 -7
- package/corpus/templates/content/app/i18n-data.ts +140 -10
- package/corpus/templates/content/changelog/2026-06-25-comment-cards-now-track-their-highlighted-text-while-scrolli.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-comments-now-scroll-with-the-document-and-stay-below-the-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-creating-a-database-from-the-slash-menu-no-longer-leaves-sta.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-sidebar-hover-controls-no-longer-leave-a-lingering-fade-when.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-sidebar-page-actions-are-now-easier-to-see-when-hovering-ina.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-text-selections-in-the-editor-no-longer-show-white-gaps-when.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-has-a-one-click-page-link-copy-button.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-the-editor-toolbar-now-shows-page-breadcrumbs-and-the-latest.md +6 -0
- package/corpus/templates/content/changelog/2026-06-25-the-page-command-now-leaves-a-notion-style-page-reference-an.md +6 -0
- package/corpus/templates/content/shared/nfm.ts +16 -0
- package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
- package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
- package/corpus/templates/plan/app/global.css +6 -11
- package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
- package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +19 -3
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/harness/store.d.ts.map +1 -1
- package/dist/agent/harness/store.js +63 -34
- package/dist/agent/harness/store.js.map +1 -1
- package/dist/agent/observational-memory/store.d.ts.map +1 -1
- package/dist/agent/observational-memory/store.js +30 -8
- package/dist/agent/observational-memory/store.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +103 -46
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/store.d.ts.map +1 -1
- package/dist/audit/store.js +18 -3
- package/dist/audit/store.js.map +1 -1
- package/dist/browser-sessions/store.d.ts.map +1 -1
- package/dist/browser-sessions/store.js +22 -7
- package/dist/browser-sessions/store.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +51 -3
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +12 -3
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +9 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/collab/awareness.d.ts +1 -1
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/awareness.js +12 -8
- package/dist/collab/awareness.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +4 -3
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +14 -2
- package/dist/collab/storage.js.map +1 -1
- package/dist/db/ddl-guard.d.ts +63 -0
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +85 -0
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +32 -3
- package/dist/extensions/store.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +44 -24
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +17 -11
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +24 -16
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.d.ts.map +1 -1
- package/dist/integrations/remote-commands-store.js +29 -21
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.d.ts.map +1 -1
- package/dist/integrations/remote-devices-store.js +33 -18
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.d.ts.map +1 -1
- package/dist/integrations/remote-push-store.js +53 -31
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
- package/dist/integrations/remote-run-events-store.js +26 -10
- package/dist/integrations/remote-run-events-store.js.map +1 -1
- package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
- package/dist/integrations/thread-mapping-store.js +10 -2
- package/dist/integrations/thread-mapping-store.js.map +1 -1
- package/dist/mcp/connect-store.d.ts.map +1 -1
- package/dist/mcp/connect-store.js +34 -16
- package/dist/mcp/connect-store.js.map +1 -1
- package/dist/mcp/oauth-store.d.ts.map +1 -1
- package/dist/mcp/oauth-store.js +22 -7
- package/dist/mcp/oauth-store.js.map +1 -1
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/notifications/store.js +15 -3
- package/dist/notifications/store.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +33 -2
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +72 -29
- package/dist/observability/store.js.map +1 -1
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +31 -16
- package/dist/progress/store.js.map +1 -1
- package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
- package/dist/provider-api/corpus-jobs-store.js +16 -5
- package/dist/provider-api/corpus-jobs-store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +14 -4
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/provider-api/quota-governor.d.ts.map +1 -1
- package/dist/provider-api/quota-governor.js +12 -3
- package/dist/provider-api/quota-governor.js.map +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
- package/dist/provider-api/staged-datasets-store.js +16 -5
- package/dist/provider-api/staged-datasets-store.js.map +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +56 -25
- package/dist/resources/store.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +10 -14
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
- package/dist/server/agent-teams-run-queue.js +15 -4
- package/dist/server/agent-teams-run-queue.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +16 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +55 -21
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +15 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts.map +1 -1
- package/dist/server/identity-sso-store.js +30 -11
- package/dist/server/identity-sso-store.js.map +1 -1
- package/dist/server/recap-image-store.d.ts.map +1 -1
- package/dist/server/recap-image-store.js +21 -7
- package/dist/server/recap-image-store.js.map +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +11 -13
- package/dist/settings/store.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +36 -11
- package/dist/usage/store.js.map +1 -1
- package/dist/workspace-connections/store.d.ts.map +1 -1
- package/dist/workspace-connections/store.js +57 -7
- package/dist/workspace-connections/store.js.map +1 -1
- package/package.json +3 -3
|
@@ -40,7 +40,6 @@ import {
|
|
|
40
40
|
useUpdateDocument,
|
|
41
41
|
} from "@/hooks/use-documents";
|
|
42
42
|
import { useLocalStorage } from "@/hooks/use-local-storage";
|
|
43
|
-
import { useIsMobile } from "@/hooks/use-mobile";
|
|
44
43
|
import { useDocumentSyncStatus } from "@/hooks/use-notion";
|
|
45
44
|
import {
|
|
46
45
|
canWriteLinkedLocalSource,
|
|
@@ -155,6 +154,32 @@ interface DocumentEditorBodyProps {
|
|
|
155
154
|
document: Document;
|
|
156
155
|
}
|
|
157
156
|
|
|
157
|
+
type PendingDocumentSave = {
|
|
158
|
+
title: string;
|
|
159
|
+
content: string;
|
|
160
|
+
save: (title: string, content: string) => unknown | Promise<unknown>;
|
|
161
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
type DocumentSaveResult = {
|
|
165
|
+
contentPersisted: boolean;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
function useMinViewportWidth(minWidth: number) {
|
|
169
|
+
const [matches, setMatches] = useState(false);
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (typeof window === "undefined") return;
|
|
173
|
+
const query = window.matchMedia(`(min-width: ${minWidth}px)`);
|
|
174
|
+
const update = () => setMatches(query.matches);
|
|
175
|
+
update();
|
|
176
|
+
query.addEventListener("change", update);
|
|
177
|
+
return () => query.removeEventListener("change", update);
|
|
178
|
+
}, [minWidth]);
|
|
179
|
+
|
|
180
|
+
return matches;
|
|
181
|
+
}
|
|
182
|
+
|
|
158
183
|
export function documentEditorTitleRegionClassName(hasDatabase: boolean) {
|
|
159
184
|
if (hasDatabase) {
|
|
160
185
|
return cn(
|
|
@@ -184,6 +209,38 @@ export function databaseMembershipDatabaseTitle(
|
|
|
184
209
|
return membership?.databaseTitle?.trim() || "Untitled database";
|
|
185
210
|
}
|
|
186
211
|
|
|
212
|
+
export function documentEditorBreadcrumbItems(
|
|
213
|
+
document: Pick<Document, "id" | "parentId" | "title" | "icon">, // i18n-ignore type expression
|
|
214
|
+
documents: Pick<Document, "id" | "parentId" | "title" | "icon">[], // i18n-ignore type expression
|
|
215
|
+
) {
|
|
216
|
+
const byId = new Map(documents.map((doc) => [doc.id, doc]));
|
|
217
|
+
const parents: { id: string; title: string; icon: string | null }[] = [];
|
|
218
|
+
const seen = new Set<string>([document.id]);
|
|
219
|
+
let parentId = document.parentId;
|
|
220
|
+
|
|
221
|
+
while (parentId) {
|
|
222
|
+
if (seen.has(parentId)) break;
|
|
223
|
+
seen.add(parentId);
|
|
224
|
+
const parent = byId.get(parentId);
|
|
225
|
+
if (!parent) break;
|
|
226
|
+
parents.unshift({
|
|
227
|
+
id: parent.id,
|
|
228
|
+
title: parent.title,
|
|
229
|
+
icon: parent.icon,
|
|
230
|
+
});
|
|
231
|
+
parentId = parent.parentId;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return [
|
|
235
|
+
...parents,
|
|
236
|
+
{
|
|
237
|
+
id: document.id,
|
|
238
|
+
title: document.title,
|
|
239
|
+
icon: document.icon,
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
|
|
187
244
|
function DatabaseMembershipBreadcrumb({
|
|
188
245
|
document,
|
|
189
246
|
onOpenDatabase,
|
|
@@ -246,6 +303,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
246
303
|
string | null
|
|
247
304
|
>(document.updatedAt ?? null);
|
|
248
305
|
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
306
|
+
const pendingDocumentSaveRef = useRef<PendingDocumentSave | null>(null);
|
|
249
307
|
// Separate freshness watermarks for title and content so that a content save
|
|
250
308
|
// never suppresses adopting a newer external title and vice versa.
|
|
251
309
|
const lastSavedTitleRef = useRef<{ title: string; updatedAt: string | null }>(
|
|
@@ -272,18 +330,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
272
330
|
const shouldFocusTitleRef = useRef(false);
|
|
273
331
|
const notionPageLinks = useMemo<NotionPageLink[]>(
|
|
274
332
|
() =>
|
|
275
|
-
documents.
|
|
276
|
-
doc.notionPageId
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
title: doc.title || "Untitled",
|
|
282
|
-
icon: doc.icon,
|
|
283
|
-
},
|
|
284
|
-
]
|
|
285
|
-
: [],
|
|
286
|
-
),
|
|
333
|
+
documents.map((doc) => ({
|
|
334
|
+
notionPageId: doc.notionPageId || doc.id,
|
|
335
|
+
documentId: doc.id,
|
|
336
|
+
title: doc.title || "Untitled",
|
|
337
|
+
icon: doc.icon,
|
|
338
|
+
})),
|
|
287
339
|
[documents],
|
|
288
340
|
);
|
|
289
341
|
const handleOpenNotionPageLink = useCallback(
|
|
@@ -346,6 +398,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
346
398
|
if (saveTimeoutRef.current) {
|
|
347
399
|
clearTimeout(saveTimeoutRef.current);
|
|
348
400
|
saveTimeoutRef.current = null;
|
|
401
|
+
pendingDocumentSaveRef.current = null;
|
|
349
402
|
}
|
|
350
403
|
}
|
|
351
404
|
if (!isInitializedRef.current) {
|
|
@@ -473,6 +526,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
473
526
|
if (saveTimeoutRef.current) {
|
|
474
527
|
clearTimeout(saveTimeoutRef.current);
|
|
475
528
|
saveTimeoutRef.current = null;
|
|
529
|
+
pendingDocumentSaveRef.current = null;
|
|
476
530
|
}
|
|
477
531
|
setLocalContent(serverContent);
|
|
478
532
|
lastSavedContentRef.current = {
|
|
@@ -574,67 +628,69 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
574
628
|
[document, documentId, queryClient, updateDocument],
|
|
575
629
|
);
|
|
576
630
|
|
|
577
|
-
const
|
|
578
|
-
(title: string, content: string) => {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const saved = await persistDocumentUpdates(updates);
|
|
603
|
-
// Adopt the server updatedAt per saved field.
|
|
604
|
-
const savedAt = saved?.updatedAt ?? new Date().toISOString();
|
|
605
|
-
if (updates.title !== undefined) {
|
|
606
|
-
lastSavedTitleRef.current = { title, updatedAt: savedAt };
|
|
607
|
-
}
|
|
608
|
-
if (updates.content !== undefined) {
|
|
609
|
-
lastSavedContentRef.current = { content, updatedAt: savedAt };
|
|
610
|
-
}
|
|
631
|
+
const saveDocumentImmediately = useCallback(
|
|
632
|
+
async (title: string, content: string): Promise<DocumentSaveResult> => {
|
|
633
|
+
// Never clobber a newer server version (e.g. an agent edit we haven't
|
|
634
|
+
// reconciled into the editor yet) with the editor's current — possibly
|
|
635
|
+
// stale — content. Guard per-field using the field's own watermark.
|
|
636
|
+
const titleIsStale =
|
|
637
|
+
!isLinkedLocalSourceDocument &&
|
|
638
|
+
documentUpdatedAtRef.current &&
|
|
639
|
+
lastSavedTitleRef.current.updatedAt &&
|
|
640
|
+
documentUpdatedAtRef.current > lastSavedTitleRef.current.updatedAt;
|
|
641
|
+
const contentIsStale =
|
|
642
|
+
!isLinkedLocalSourceDocument &&
|
|
643
|
+
documentUpdatedAtRef.current &&
|
|
644
|
+
lastSavedContentRef.current.updatedAt &&
|
|
645
|
+
documentUpdatedAtRef.current > lastSavedContentRef.current.updatedAt;
|
|
646
|
+
|
|
647
|
+
const updates: Record<string, string> = {};
|
|
648
|
+
if (title !== lastSavedTitleRef.current.title && !titleIsStale)
|
|
649
|
+
updates.title = title;
|
|
650
|
+
const contentChanged = content !== lastSavedContentRef.current.content;
|
|
651
|
+
if (contentChanged && !contentIsStale) updates.content = content;
|
|
652
|
+
if (Object.keys(updates).length === 0) {
|
|
653
|
+
return { contentPersisted: !contentChanged };
|
|
654
|
+
}
|
|
611
655
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
656
|
+
const saved = await persistDocumentUpdates(updates);
|
|
657
|
+
// Adopt the server updatedAt per saved field.
|
|
658
|
+
const savedAt = saved?.updatedAt ?? new Date().toISOString();
|
|
659
|
+
if (updates.title !== undefined) {
|
|
660
|
+
lastSavedTitleRef.current = { title, updatedAt: savedAt };
|
|
661
|
+
}
|
|
662
|
+
if (updates.content !== undefined) {
|
|
663
|
+
lastSavedContentRef.current = { content, updatedAt: savedAt };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Push-on-save: when auto-sync is on, trigger a Notion push
|
|
667
|
+
// immediately after the save lands in SQL. This eliminates the
|
|
668
|
+
// off-by-one race where a fixed-interval poll could fire between
|
|
669
|
+
// the debounce and the next save, reading the previous content.
|
|
670
|
+
// Pulls remain driven by the polling refetch in useDocumentSyncStatus.
|
|
671
|
+
if (autoSync) {
|
|
672
|
+
const status = queryClient.getQueryData<DocumentSyncStatus>([
|
|
673
|
+
"document-sync",
|
|
674
|
+
documentId,
|
|
675
|
+
]);
|
|
676
|
+
if (status?.pageId && !status.hasConflict) {
|
|
677
|
+
try {
|
|
678
|
+
const res = await fetch(
|
|
679
|
+
appApiPath(`/api/documents/${documentId}/notion/push`),
|
|
680
|
+
{ method: "POST" },
|
|
681
|
+
);
|
|
682
|
+
if (res.ok) {
|
|
683
|
+
const next = (await res.json()) as DocumentSyncStatus;
|
|
684
|
+
queryClient.setQueryData(["document-sync", documentId], next);
|
|
634
685
|
}
|
|
686
|
+
} catch {
|
|
687
|
+
// Non-fatal — next polling refetch will surface any error.
|
|
635
688
|
}
|
|
636
689
|
}
|
|
637
|
-
}
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
contentPersisted: !contentChanged || updates.content !== undefined,
|
|
693
|
+
};
|
|
638
694
|
},
|
|
639
695
|
[
|
|
640
696
|
documentId,
|
|
@@ -644,6 +700,37 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
644
700
|
queryClient,
|
|
645
701
|
],
|
|
646
702
|
);
|
|
703
|
+
const debouncedSave = useCallback(
|
|
704
|
+
(title: string, content: string) => {
|
|
705
|
+
if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current);
|
|
706
|
+
const pending: PendingDocumentSave = {
|
|
707
|
+
title,
|
|
708
|
+
content,
|
|
709
|
+
save: saveDocumentImmediately,
|
|
710
|
+
timeout: setTimeout(() => {
|
|
711
|
+
if (pendingDocumentSaveRef.current === pending) {
|
|
712
|
+
pendingDocumentSaveRef.current = null;
|
|
713
|
+
}
|
|
714
|
+
saveTimeoutRef.current = null;
|
|
715
|
+
void pending.save(pending.title, pending.content);
|
|
716
|
+
}, 500),
|
|
717
|
+
};
|
|
718
|
+
pendingDocumentSaveRef.current = pending;
|
|
719
|
+
saveTimeoutRef.current = pending.timeout;
|
|
720
|
+
},
|
|
721
|
+
[saveDocumentImmediately],
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
useEffect(() => {
|
|
725
|
+
return () => {
|
|
726
|
+
const pending = pendingDocumentSaveRef.current;
|
|
727
|
+
if (!pending) return;
|
|
728
|
+
clearTimeout(pending.timeout);
|
|
729
|
+
saveTimeoutRef.current = null;
|
|
730
|
+
pendingDocumentSaveRef.current = null;
|
|
731
|
+
void pending.save(pending.title, pending.content);
|
|
732
|
+
};
|
|
733
|
+
}, [documentId]);
|
|
647
734
|
|
|
648
735
|
// Collab-aware ingest flush: the `pull-document` action writes a one-shot
|
|
649
736
|
// `flush-request-<id>` app-state key when an external agent wants to ingest
|
|
@@ -729,6 +816,25 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
729
816
|
[canEdit, debouncedSave],
|
|
730
817
|
);
|
|
731
818
|
|
|
819
|
+
const handleContentSaveNow = useCallback(
|
|
820
|
+
async (newContent: string) => {
|
|
821
|
+
if (!canEdit) return false;
|
|
822
|
+
if (saveTimeoutRef.current) {
|
|
823
|
+
clearTimeout(saveTimeoutRef.current);
|
|
824
|
+
saveTimeoutRef.current = null;
|
|
825
|
+
pendingDocumentSaveRef.current = null;
|
|
826
|
+
}
|
|
827
|
+
localContentRef.current = newContent;
|
|
828
|
+
setLocalContent(newContent);
|
|
829
|
+
const result = await saveDocumentImmediately(
|
|
830
|
+
localTitleRef.current,
|
|
831
|
+
newContent,
|
|
832
|
+
);
|
|
833
|
+
return result.contentPersisted;
|
|
834
|
+
},
|
|
835
|
+
[canEdit, saveDocumentImmediately],
|
|
836
|
+
);
|
|
837
|
+
|
|
732
838
|
// Comments state — pending comment from text selection
|
|
733
839
|
const [pendingComment, setPendingComment] = useState<{
|
|
734
840
|
quotedText: string;
|
|
@@ -736,17 +842,19 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
736
842
|
anchor?: CommentTextAnchor;
|
|
737
843
|
range?: { from: number; to: number };
|
|
738
844
|
} | null>(null);
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
const { data: threads } = useComments(
|
|
845
|
+
const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null);
|
|
846
|
+
const [hoveredThreadId, setHoveredThreadId] = useState<string | null>(null);
|
|
847
|
+
const activeThreadId = hoveredThreadId ?? selectedThreadId;
|
|
848
|
+
const { data: threads, isLoading: commentsLoading } = useComments(
|
|
743
849
|
isLocalFileDocument ? null : documentId,
|
|
744
850
|
);
|
|
745
851
|
const hasComments =
|
|
746
852
|
!isLocalFileDocument &&
|
|
747
853
|
canEdit &&
|
|
748
854
|
((threads?.length ?? 0) > 0 || !!pendingComment);
|
|
749
|
-
const
|
|
855
|
+
const hasCommentRailSpace = useMinViewportWidth(1024);
|
|
856
|
+
const showDesktopComments = hasComments && hasCommentRailSpace;
|
|
857
|
+
const showCommentsSheet = hasComments && canEdit && !showDesktopComments;
|
|
750
858
|
|
|
751
859
|
const handleComment = useCallback(
|
|
752
860
|
(
|
|
@@ -756,11 +864,30 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
756
864
|
range?: { from: number; to: number },
|
|
757
865
|
) => {
|
|
758
866
|
setPendingComment({ quotedText, offsetTop, anchor, range });
|
|
759
|
-
|
|
867
|
+
setSelectedThreadId(null);
|
|
868
|
+
setHoveredThreadId(null);
|
|
760
869
|
},
|
|
761
870
|
[],
|
|
762
871
|
);
|
|
763
872
|
|
|
873
|
+
const clearCommentFocus = useCallback(() => {
|
|
874
|
+
setSelectedThreadId(null);
|
|
875
|
+
setHoveredThreadId(null);
|
|
876
|
+
}, []);
|
|
877
|
+
|
|
878
|
+
useEffect(() => {
|
|
879
|
+
setPendingComment(null);
|
|
880
|
+
clearCommentFocus();
|
|
881
|
+
}, [clearCommentFocus, documentId]);
|
|
882
|
+
|
|
883
|
+
useEffect(() => {
|
|
884
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
885
|
+
if (event.key === "Escape") clearCommentFocus();
|
|
886
|
+
};
|
|
887
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
888
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
889
|
+
}, [clearCommentFocus]);
|
|
890
|
+
|
|
764
891
|
const focusTitleEnd = useCallback(() => {
|
|
765
892
|
const textarea = titleInputRef.current;
|
|
766
893
|
if (!textarea) return;
|
|
@@ -816,6 +943,11 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
816
943
|
}
|
|
817
944
|
});
|
|
818
945
|
|
|
946
|
+
const toolbarBreadcrumbItems = useMemo(
|
|
947
|
+
() => documentEditorBreadcrumbItems(document, documents),
|
|
948
|
+
[document, documents],
|
|
949
|
+
);
|
|
950
|
+
|
|
819
951
|
if (!isLocalFileDocument && collabLoading) {
|
|
820
952
|
return <DocumentEditorSkeleton />;
|
|
821
953
|
}
|
|
@@ -823,11 +955,15 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
823
955
|
const sidebar = (
|
|
824
956
|
<CommentsSidebar
|
|
825
957
|
documentId={documentId}
|
|
958
|
+
threads={threads ?? []}
|
|
959
|
+
isLoading={commentsLoading}
|
|
826
960
|
pendingComment={pendingComment}
|
|
827
961
|
onPendingDone={() => setPendingComment(null)}
|
|
828
962
|
scrollContainerRef={scrollContainerRef}
|
|
829
963
|
activeThreadId={activeThreadId}
|
|
830
|
-
|
|
964
|
+
selectedThreadId={selectedThreadId}
|
|
965
|
+
onSelectedThreadChange={setSelectedThreadId}
|
|
966
|
+
onHoveredThreadChange={setHoveredThreadId}
|
|
831
967
|
currentUserEmail={session?.email}
|
|
832
968
|
/>
|
|
833
969
|
);
|
|
@@ -850,12 +986,25 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
850
986
|
<div
|
|
851
987
|
className="relative flex min-h-0 min-w-0 flex-1"
|
|
852
988
|
data-document-print-root
|
|
989
|
+
onPointerDownCapture={(event) => {
|
|
990
|
+
const target = event.target as HTMLElement | null;
|
|
991
|
+
if (
|
|
992
|
+
target?.closest("[data-comments-sidebar], [data-comment-thread]")
|
|
993
|
+
) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
clearCommentFocus();
|
|
997
|
+
}}
|
|
853
998
|
>
|
|
854
999
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col">
|
|
855
1000
|
<DocumentToolbar
|
|
856
1001
|
documentId={documentId}
|
|
857
1002
|
documentTitle={exportTitle}
|
|
858
1003
|
documentContent={exportContent}
|
|
1004
|
+
breadcrumbItems={toolbarBreadcrumbItems.map((item) =>
|
|
1005
|
+
item.id === documentId ? { ...item, title: exportTitle } : item,
|
|
1006
|
+
)}
|
|
1007
|
+
documentUpdatedAt={document.updatedAt}
|
|
859
1008
|
activeUsers={activeUsers}
|
|
860
1009
|
agentPresent={agentPresent}
|
|
861
1010
|
agentActive={agentActive}
|
|
@@ -875,160 +1024,187 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
875
1024
|
data-document-print-scroll
|
|
876
1025
|
>
|
|
877
1026
|
<div
|
|
878
|
-
className={
|
|
879
|
-
|
|
1027
|
+
className={cn(
|
|
1028
|
+
"flex min-h-full w-full min-w-0",
|
|
1029
|
+
showDesktopComments ? "justify-center" : "flex-col",
|
|
880
1030
|
)}
|
|
1031
|
+
data-document-scroll-content
|
|
881
1032
|
>
|
|
882
|
-
<
|
|
883
|
-
document={document}
|
|
884
|
-
onOpenDatabase={(databaseDocumentId) =>
|
|
885
|
-
navigate(`/page/${databaseDocumentId}`, { flushSync: true })
|
|
886
|
-
}
|
|
887
|
-
/>
|
|
888
|
-
{document.icon || !isDatabasePage ? (
|
|
889
|
-
<div className="mb-1">
|
|
890
|
-
{canEdit ? (
|
|
891
|
-
<EmojiPicker
|
|
892
|
-
icon={document.icon}
|
|
893
|
-
defaultIcon={defaultIcon}
|
|
894
|
-
defaultIconLabel={
|
|
895
|
-
defaultIconKind === "database" ? "database" : "page"
|
|
896
|
-
}
|
|
897
|
-
onSelect={(emoji) => {
|
|
898
|
-
void (async () => {
|
|
899
|
-
await persistDocumentUpdates({ icon: emoji });
|
|
900
|
-
})();
|
|
901
|
-
}}
|
|
902
|
-
/>
|
|
903
|
-
) : document.icon ? (
|
|
904
|
-
<div className="p-1 -ml-1 text-5xl leading-none">
|
|
905
|
-
{document.icon}
|
|
906
|
-
</div>
|
|
907
|
-
) : defaultIconKind === "database" && !isDatabasePage ? (
|
|
908
|
-
<div className="-ml-1 flex size-14 items-center justify-center rounded-md text-muted-foreground">
|
|
909
|
-
<IconDatabase className="size-12" aria-hidden="true" />
|
|
910
|
-
</div>
|
|
911
|
-
) : null}
|
|
912
|
-
</div>
|
|
913
|
-
) : null}
|
|
914
|
-
<textarea
|
|
915
|
-
ref={titleInputRef}
|
|
916
|
-
rows={1}
|
|
917
|
-
wrap="soft"
|
|
918
|
-
value={localTitle}
|
|
919
|
-
onChange={(e) =>
|
|
920
|
-
handleTitleChange(normalizeTitleText(e.target.value))
|
|
921
|
-
}
|
|
922
|
-
onPaste={handleTitlePaste}
|
|
923
|
-
onFocus={() => {
|
|
924
|
-
titleFocusedRef.current = true;
|
|
925
|
-
}}
|
|
926
|
-
onBlur={() => {
|
|
927
|
-
titleFocusedRef.current = false;
|
|
928
|
-
}}
|
|
929
|
-
onKeyDown={(e) => {
|
|
930
|
-
if (e.key === "Enter") {
|
|
931
|
-
e.preventDefault();
|
|
932
|
-
const pm = window.document.querySelector(
|
|
933
|
-
".ProseMirror",
|
|
934
|
-
) as HTMLElement | null;
|
|
935
|
-
pm?.focus();
|
|
936
|
-
}
|
|
937
|
-
}}
|
|
938
|
-
aria-label={t("editor.documentTitle")}
|
|
939
|
-
placeholder={t("editor.title")}
|
|
940
|
-
readOnly={!canEdit}
|
|
941
|
-
style={{ fieldSizing: "content" } as any}
|
|
1033
|
+
<div
|
|
942
1034
|
className={cn(
|
|
943
|
-
"
|
|
944
|
-
|
|
1035
|
+
"min-w-0",
|
|
1036
|
+
showDesktopComments ? "flex-1" : "w-full",
|
|
945
1037
|
)}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
className="flex-1 w-full max-w-3xl mx-auto px-4 pb-16 cursor-text sm:px-8 md:px-16"
|
|
959
|
-
onClick={(e) => {
|
|
960
|
-
if (e.target === e.currentTarget) {
|
|
961
|
-
const pm = e.currentTarget.querySelector(
|
|
962
|
-
".ProseMirror",
|
|
963
|
-
) as HTMLElement | null;
|
|
964
|
-
pm?.focus();
|
|
965
|
-
}
|
|
966
|
-
}}
|
|
967
|
-
>
|
|
968
|
-
{(() => {
|
|
969
|
-
// The primary "Content" Blocks field IS the document body, with
|
|
970
|
-
// the full collaborative editor. It renders chromeless when it's
|
|
971
|
-
// the only Blocks field, or inside a header/collapsible shell
|
|
972
|
-
// when the row has multiple Blocks fields.
|
|
973
|
-
const primaryEditor = (
|
|
974
|
-
<VisualEditor
|
|
975
|
-
key={documentId}
|
|
976
|
-
documentId={documentId}
|
|
977
|
-
content={
|
|
978
|
-
isLocalFileDocument ? localContent : document.content
|
|
979
|
-
}
|
|
980
|
-
contentUpdatedAt={
|
|
981
|
-
isLocalFileDocument
|
|
982
|
-
? (localContentUpdatedAt ?? document.updatedAt)
|
|
983
|
-
: document.updatedAt
|
|
1038
|
+
>
|
|
1039
|
+
<div
|
|
1040
|
+
className={documentEditorTitleRegionClassName(
|
|
1041
|
+
Boolean(document.database),
|
|
1042
|
+
)}
|
|
1043
|
+
>
|
|
1044
|
+
<DatabaseMembershipBreadcrumb
|
|
1045
|
+
document={document}
|
|
1046
|
+
onOpenDatabase={(databaseDocumentId) =>
|
|
1047
|
+
navigate(`/page/${databaseDocumentId}`, {
|
|
1048
|
+
flushSync: true,
|
|
1049
|
+
})
|
|
984
1050
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
canEdit
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1051
|
+
/>
|
|
1052
|
+
{document.icon || !isDatabasePage ? (
|
|
1053
|
+
<div className="mb-1">
|
|
1054
|
+
{canEdit ? (
|
|
1055
|
+
<EmojiPicker
|
|
1056
|
+
icon={document.icon}
|
|
1057
|
+
defaultIcon={defaultIcon}
|
|
1058
|
+
defaultIconLabel={
|
|
1059
|
+
defaultIconKind === "database" ? "database" : "page"
|
|
1060
|
+
}
|
|
1061
|
+
onSelect={(emoji) => {
|
|
1062
|
+
void (async () => {
|
|
1063
|
+
await persistDocumentUpdates({ icon: emoji });
|
|
1064
|
+
})();
|
|
1065
|
+
}}
|
|
1066
|
+
/>
|
|
1067
|
+
) : document.icon ? (
|
|
1068
|
+
<div className="p-1 -ml-1 text-5xl leading-none">
|
|
1069
|
+
{document.icon}
|
|
1070
|
+
</div>
|
|
1071
|
+
) : defaultIconKind === "database" && !isDatabasePage ? (
|
|
1072
|
+
<div className="-ml-1 flex size-14 items-center justify-center rounded-md text-muted-foreground">
|
|
1073
|
+
<IconDatabase
|
|
1074
|
+
className="size-12"
|
|
1075
|
+
aria-hidden="true"
|
|
1076
|
+
/>
|
|
1077
|
+
</div>
|
|
1078
|
+
) : null}
|
|
1079
|
+
</div>
|
|
1080
|
+
) : null}
|
|
1081
|
+
<textarea
|
|
1082
|
+
ref={titleInputRef}
|
|
1083
|
+
rows={1}
|
|
1084
|
+
wrap="soft"
|
|
1085
|
+
value={localTitle}
|
|
1086
|
+
onChange={(e) =>
|
|
1087
|
+
handleTitleChange(normalizeTitleText(e.target.value))
|
|
1005
1088
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1089
|
+
onPaste={handleTitlePaste}
|
|
1090
|
+
onFocus={() => {
|
|
1091
|
+
titleFocusedRef.current = true;
|
|
1092
|
+
}}
|
|
1093
|
+
onBlur={() => {
|
|
1094
|
+
titleFocusedRef.current = false;
|
|
1095
|
+
}}
|
|
1096
|
+
onKeyDown={(e) => {
|
|
1097
|
+
if (e.key === "Enter") {
|
|
1098
|
+
e.preventDefault();
|
|
1099
|
+
const pm = window.document.querySelector(
|
|
1100
|
+
".ProseMirror",
|
|
1101
|
+
) as HTMLElement | null;
|
|
1102
|
+
pm?.focus();
|
|
1103
|
+
}
|
|
1104
|
+
}}
|
|
1105
|
+
aria-label={t("editor.documentTitle")}
|
|
1106
|
+
placeholder={t("editor.title")}
|
|
1107
|
+
readOnly={!canEdit}
|
|
1108
|
+
style={{ fieldSizing: "content" } as any}
|
|
1109
|
+
className={cn(
|
|
1110
|
+
"block w-full resize-none overflow-hidden break-words border-none bg-transparent p-0 font-bold leading-tight text-foreground outline-none placeholder:text-muted-foreground/40",
|
|
1111
|
+
isDatabasePage ? "text-3xl" : "text-3xl md:text-4xl",
|
|
1112
|
+
)}
|
|
1010
1113
|
/>
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
// Only database rows have Blocks fields. Standalone pages and
|
|
1014
|
-
// local-file documents keep the plain chromeless body.
|
|
1015
|
-
if (document.databaseMembership && !isLocalFileDocument) {
|
|
1016
|
-
return (
|
|
1017
|
-
<DocumentBlockFields
|
|
1114
|
+
{document.databaseMembership && !isLocalFileDocument ? (
|
|
1115
|
+
<DocumentProperties
|
|
1018
1116
|
documentId={documentId}
|
|
1019
1117
|
canEdit={canEdit}
|
|
1020
|
-
primaryEditor={primaryEditor}
|
|
1021
1118
|
/>
|
|
1022
|
-
)
|
|
1023
|
-
|
|
1119
|
+
) : null}
|
|
1120
|
+
</div>
|
|
1121
|
+
{document.database ? (
|
|
1122
|
+
<div className={documentEditorDatabaseRegionClassName()}>
|
|
1123
|
+
<DocumentDatabase document={document} canEdit={canEdit} />
|
|
1124
|
+
</div>
|
|
1125
|
+
) : null}
|
|
1126
|
+
|
|
1127
|
+
<div
|
|
1128
|
+
className="flex-1 w-full max-w-3xl mx-auto px-4 pb-16 cursor-text sm:px-8 md:px-16"
|
|
1129
|
+
onClick={(e) => {
|
|
1130
|
+
if (e.target === e.currentTarget) {
|
|
1131
|
+
const pm = e.currentTarget.querySelector(
|
|
1132
|
+
".ProseMirror",
|
|
1133
|
+
) as HTMLElement | null;
|
|
1134
|
+
pm?.focus();
|
|
1135
|
+
}
|
|
1136
|
+
}}
|
|
1137
|
+
>
|
|
1138
|
+
{(() => {
|
|
1139
|
+
// The primary "Content" Blocks field IS the document body,
|
|
1140
|
+
// with the full collaborative editor. It renders chromeless
|
|
1141
|
+
// when it's the only Blocks field, or inside a
|
|
1142
|
+
// header/collapsible shell when the row has multiple Blocks
|
|
1143
|
+
// fields.
|
|
1144
|
+
const primaryEditor = (
|
|
1145
|
+
<VisualEditor
|
|
1146
|
+
key={documentId}
|
|
1147
|
+
documentId={documentId}
|
|
1148
|
+
content={
|
|
1149
|
+
isLocalFileDocument ? localContent : document.content
|
|
1150
|
+
}
|
|
1151
|
+
contentUpdatedAt={
|
|
1152
|
+
isLocalFileDocument
|
|
1153
|
+
? (localContentUpdatedAt ?? document.updatedAt)
|
|
1154
|
+
: document.updatedAt
|
|
1155
|
+
}
|
|
1156
|
+
onChange={handleContentChange}
|
|
1157
|
+
onSaveContent={handleContentSaveNow}
|
|
1158
|
+
ydoc={canEdit && !isLocalFileDocument ? ydoc : null}
|
|
1159
|
+
awareness={
|
|
1160
|
+
canEdit && !isLocalFileDocument ? awareness : null
|
|
1161
|
+
}
|
|
1162
|
+
user={currentUser}
|
|
1163
|
+
editable={canEdit}
|
|
1164
|
+
localFileMode={isLocalFileDocument}
|
|
1165
|
+
onComment={
|
|
1166
|
+
canEdit && !isLocalFileDocument
|
|
1167
|
+
? handleComment
|
|
1168
|
+
: undefined
|
|
1169
|
+
}
|
|
1170
|
+
commentThreads={threads ?? []}
|
|
1171
|
+
activeThreadId={activeThreadId}
|
|
1172
|
+
pendingHighlight={pendingComment?.range ?? null}
|
|
1173
|
+
onActivateThread={
|
|
1174
|
+
canEdit && !isLocalFileDocument
|
|
1175
|
+
? setSelectedThreadId
|
|
1176
|
+
: undefined
|
|
1177
|
+
}
|
|
1178
|
+
onJoinTitle={joinFirstBodyBlockToTitle}
|
|
1179
|
+
notionPageLinks={notionPageLinks}
|
|
1180
|
+
onOpenNotionPageLink={handleOpenNotionPageLink}
|
|
1181
|
+
notionPageId={document.notionPageId}
|
|
1182
|
+
/>
|
|
1183
|
+
);
|
|
1184
|
+
|
|
1185
|
+
// Only database rows have Blocks fields. Standalone pages
|
|
1186
|
+
// and local-file documents keep the plain chromeless body.
|
|
1187
|
+
if (document.databaseMembership && !isLocalFileDocument) {
|
|
1188
|
+
return (
|
|
1189
|
+
<DocumentBlockFields
|
|
1190
|
+
documentId={documentId}
|
|
1191
|
+
canEdit={canEdit}
|
|
1192
|
+
primaryEditor={primaryEditor}
|
|
1193
|
+
/>
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
return primaryEditor;
|
|
1198
|
+
})()}
|
|
1199
|
+
</div>
|
|
1200
|
+
</div>
|
|
1024
1201
|
|
|
1025
|
-
|
|
1026
|
-
})()}
|
|
1202
|
+
{showDesktopComments ? sidebar : null}
|
|
1027
1203
|
</div>
|
|
1028
1204
|
</div>
|
|
1029
1205
|
</div>
|
|
1030
1206
|
|
|
1031
|
-
{
|
|
1207
|
+
{showCommentsSheet ? (
|
|
1032
1208
|
<Sheet
|
|
1033
1209
|
open={hasComments}
|
|
1034
1210
|
onOpenChange={(open) => {
|
|
@@ -1039,9 +1215,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1039
1215
|
{sidebar}
|
|
1040
1216
|
</SheetContent>
|
|
1041
1217
|
</Sheet>
|
|
1042
|
-
) :
|
|
1043
|
-
hasComments && sidebar
|
|
1044
|
-
)}
|
|
1218
|
+
) : null}
|
|
1045
1219
|
</div>
|
|
1046
1220
|
</BlockRegistryProvider>
|
|
1047
1221
|
);
|