@agent-native/core 0.114.0 → 0.114.1
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 +9 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/build-compatibility.ts +77 -0
- package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
- package/corpus/core/src/client/use-action.ts +32 -1
- package/corpus/core/src/deploy/build.ts +4 -1
- package/corpus/core/src/org/context.ts +56 -30
- package/corpus/core/src/server/action-routes.ts +52 -2
- package/corpus/core/src/server/auth-plugin.ts +9 -4
- package/corpus/core/src/server/better-auth-instance.ts +5 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/vite/client.ts +17 -0
- package/corpus/templates/content/AGENTS.md +2 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
- package/corpus/templates/content/actions/_content-document-access.ts +21 -0
- package/corpus/templates/content/actions/_content-favorites.ts +91 -0
- package/corpus/templates/content/actions/_content-files.ts +79 -23
- package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
- package/corpus/templates/content/actions/_content-space-access.ts +15 -17
- package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
- package/corpus/templates/content/actions/_content-spaces.ts +364 -167
- package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
- package/corpus/templates/content/actions/_database-utils.ts +249 -15
- package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
- package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
- package/corpus/templates/content/actions/_property-utils.ts +18 -9
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
- package/corpus/templates/content/actions/configure-document-property.ts +3 -0
- package/corpus/templates/content/actions/create-content-database.ts +2 -2
- package/corpus/templates/content/actions/create-content-space.ts +34 -0
- package/corpus/templates/content/actions/create-document.ts +4 -1
- package/corpus/templates/content/actions/delete-content-space.ts +19 -0
- package/corpus/templates/content/actions/delete-database-items.ts +28 -1
- package/corpus/templates/content/actions/delete-document-property.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +39 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
- package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
- package/corpus/templates/content/actions/get-content-database.ts +11 -2
- package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
- package/corpus/templates/content/actions/get-document.ts +32 -7
- package/corpus/templates/content/actions/import-content-source.ts +33 -1
- package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
- package/corpus/templates/content/actions/list-documents.ts +39 -7
- package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
- package/corpus/templates/content/actions/set-document-property.ts +41 -23
- package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
- package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
- package/corpus/templates/content/actions/update-document.ts +158 -66
- package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
- package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
- package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
- package/corpus/templates/content/app/entry.client.tsx +5 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
- package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
- package/corpus/templates/content/app/i18n-data.ts +243 -20
- package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
- package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
- package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
- package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
- package/corpus/templates/content/e2e/global-setup.ts +3 -2
- package/corpus/templates/content/parity/matrix.md +28 -28
- package/corpus/templates/content/parity/matrix.ts +8 -2
- package/corpus/templates/content/server/db/index.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +10 -0
- package/corpus/templates/content/server/lib/documents.ts +12 -7
- package/corpus/templates/content/server/plugins/db.ts +17 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/content/shared/database-form.ts +11 -2
- package/corpus/templates/content/vite.config.ts +1 -0
- package/dist/client/build-compatibility.d.ts +11 -0
- package/dist/client/build-compatibility.d.ts.map +1 -0
- package/dist/client/build-compatibility.js +56 -0
- package/dist/client/build-compatibility.js.map +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
- package/dist/client/route-chunk-recovery/index.js +1 -0
- package/dist/client/route-chunk-recovery/index.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +19 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +2 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/context.d.ts +4 -2
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +44 -22
- package/dist/org/context.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +32 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth-plugin.d.ts.map +1 -1
- package/dist/server/auth-plugin.js +9 -5
- package/dist/server/auth-plugin.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +4 -1
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/vite/client.d.ts +6 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +8 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/build-compatibility.ts +77 -0
- package/src/client/route-chunk-recovery/index.ts +1 -0
- package/src/client/use-action.ts +32 -1
- package/src/deploy/build.ts +4 -1
- package/src/org/context.ts +56 -30
- package/src/server/action-routes.ts +52 -2
- package/src/server/auth-plugin.ts +9 -4
- package/src/server/better-auth-instance.ts +5 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/vite/client.ts +17 -0
|
@@ -8,13 +8,19 @@ import {
|
|
|
8
8
|
type CollabUser,
|
|
9
9
|
} from "@agent-native/core/client/collab";
|
|
10
10
|
import {
|
|
11
|
+
setClientAppState,
|
|
11
12
|
useAvatarUrl,
|
|
12
13
|
useDbSync,
|
|
13
14
|
useSession,
|
|
14
15
|
} from "@agent-native/core/client/hooks";
|
|
15
16
|
import { useT } from "@agent-native/core/client/i18n";
|
|
16
17
|
import type { Document, DocumentSyncStatus } from "@shared/api";
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
IconDatabase,
|
|
20
|
+
IconFileText,
|
|
21
|
+
IconLoader2,
|
|
22
|
+
IconX,
|
|
23
|
+
} from "@tabler/icons-react";
|
|
18
24
|
import { IconLock } from "@tabler/icons-react";
|
|
19
25
|
import { useQueryClient } from "@tanstack/react-query";
|
|
20
26
|
import {
|
|
@@ -33,13 +39,33 @@ import {
|
|
|
33
39
|
contentBlockRegistry,
|
|
34
40
|
createContentBlockRenderContext,
|
|
35
41
|
} from "@/blocks/contentBlockRegistry";
|
|
42
|
+
import {
|
|
43
|
+
createContentSpaceSelectionQueue,
|
|
44
|
+
SELECTED_CONTENT_SPACE_STORAGE_KEY,
|
|
45
|
+
selectContentSpace,
|
|
46
|
+
} from "@/components/sidebar/select-content-space";
|
|
36
47
|
import { Button } from "@/components/ui/button";
|
|
37
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
Sheet,
|
|
50
|
+
SheetContent,
|
|
51
|
+
SheetHeader,
|
|
52
|
+
SheetTitle,
|
|
53
|
+
} from "@/components/ui/sheet";
|
|
38
54
|
import { useComments } from "@/hooks/use-comments";
|
|
39
|
-
import {
|
|
55
|
+
import {
|
|
56
|
+
useCreateContentDatabase,
|
|
57
|
+
useDeleteContentDatabase,
|
|
58
|
+
useProcessBuilderBodyHydration,
|
|
59
|
+
} from "@/hooks/use-content-database";
|
|
60
|
+
import {
|
|
61
|
+
useContentSpaces,
|
|
62
|
+
type ContentSpaceSummary,
|
|
63
|
+
} from "@/hooks/use-content-spaces";
|
|
40
64
|
import {
|
|
41
65
|
isDocumentUpdateConflict,
|
|
66
|
+
patchDocumentCaches,
|
|
42
67
|
useDocument,
|
|
68
|
+
useDeleteDocument,
|
|
43
69
|
useDocuments,
|
|
44
70
|
useUpdateDocument,
|
|
45
71
|
} from "@/hooks/use-documents";
|
|
@@ -63,12 +89,11 @@ import {
|
|
|
63
89
|
import { BuilderBodySyncingNotice } from "./BuilderBodySyncingNotice";
|
|
64
90
|
import type { CommentTextAnchor } from "./comment-anchors";
|
|
65
91
|
import { CommentsSidebar } from "./CommentsSidebar";
|
|
66
|
-
import { DescriptionField } from "./DescriptionField";
|
|
67
92
|
import { DocumentBlockFields } from "./DocumentBlockFields";
|
|
68
93
|
import { DocumentDatabase } from "./DocumentDatabase";
|
|
69
94
|
import { DocumentEditorSkeleton } from "./DocumentEditorSkeleton";
|
|
70
|
-
import {
|
|
71
|
-
import { DocumentToolbar } from "./DocumentToolbar";
|
|
95
|
+
import { DocumentInfoPanel } from "./DocumentInfoPanel";
|
|
96
|
+
import { DocumentToolbar, type ToolbarBreadcrumbItem } from "./DocumentToolbar";
|
|
72
97
|
import { EmojiPicker } from "./EmojiPicker";
|
|
73
98
|
import { NotionConflictBanner } from "./NotionConflictBanner";
|
|
74
99
|
import {
|
|
@@ -86,6 +111,37 @@ interface DocumentEditorProps {
|
|
|
86
111
|
|
|
87
112
|
type FieldSaveWatermark = { title: string; updatedAt: string | null };
|
|
88
113
|
type ContentSaveWatermark = { content: string; updatedAt: string | null };
|
|
114
|
+
type DocumentUtilityPanel = "info" | "comments" | null;
|
|
115
|
+
|
|
116
|
+
export function metadataUpdatesWithPendingTitle<
|
|
117
|
+
T extends {
|
|
118
|
+
title?: string;
|
|
119
|
+
content?: string;
|
|
120
|
+
description?: string;
|
|
121
|
+
icon?: string | null;
|
|
122
|
+
},
|
|
123
|
+
>(
|
|
124
|
+
updates: T,
|
|
125
|
+
currentTitle: string,
|
|
126
|
+
savedTitle: string,
|
|
127
|
+
): T & { title?: string } {
|
|
128
|
+
if (updates.title !== undefined || currentTitle === savedTitle)
|
|
129
|
+
return updates;
|
|
130
|
+
return { ...updates, title: currentTitle };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function titleMatchConfirmsSave(args: {
|
|
134
|
+
serverTitle: string;
|
|
135
|
+
localTitle: string;
|
|
136
|
+
lastSavedTitle: string;
|
|
137
|
+
pendingTitle: string | null;
|
|
138
|
+
}) {
|
|
139
|
+
if (args.serverTitle !== args.localTitle) return false;
|
|
140
|
+
return !(
|
|
141
|
+
args.pendingTitle === args.localTitle &&
|
|
142
|
+
args.localTitle !== args.lastSavedTitle
|
|
143
|
+
);
|
|
144
|
+
}
|
|
89
145
|
|
|
90
146
|
function adoptConfirmedSaveWatermarks({
|
|
91
147
|
saved,
|
|
@@ -249,7 +305,10 @@ export function databaseMembershipDatabaseTitle(
|
|
|
249
305
|
}
|
|
250
306
|
|
|
251
307
|
export function documentEditorBreadcrumbItems(
|
|
252
|
-
document: Pick<
|
|
308
|
+
document: Pick<
|
|
309
|
+
Document,
|
|
310
|
+
"id" | "parentId" | "title" | "icon" | "databaseMembership"
|
|
311
|
+
>, // i18n-ignore type expression
|
|
253
312
|
documents: Pick<Document, "id" | "parentId" | "title" | "icon">[], // i18n-ignore type expression
|
|
254
313
|
) {
|
|
255
314
|
const byId = new Map(documents.map((doc) => [doc.id, doc]));
|
|
@@ -270,7 +329,7 @@ export function documentEditorBreadcrumbItems(
|
|
|
270
329
|
parentId = parent.parentId;
|
|
271
330
|
}
|
|
272
331
|
|
|
273
|
-
|
|
332
|
+
const pageItems = [
|
|
274
333
|
...parents,
|
|
275
334
|
{
|
|
276
335
|
id: document.id,
|
|
@@ -278,39 +337,117 @@ export function documentEditorBreadcrumbItems(
|
|
|
278
337
|
icon: document.icon,
|
|
279
338
|
},
|
|
280
339
|
];
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function DatabaseMembershipBreadcrumb({
|
|
284
|
-
document,
|
|
285
|
-
onOpenDatabase,
|
|
286
|
-
}: {
|
|
287
|
-
document: Document;
|
|
288
|
-
onOpenDatabase: (databaseDocumentId: string) => void;
|
|
289
|
-
}) {
|
|
290
340
|
const membership = document.databaseMembership;
|
|
291
|
-
if (
|
|
341
|
+
if (
|
|
342
|
+
!membership ||
|
|
343
|
+
pageItems.some((item) => item.id === membership.databaseDocumentId)
|
|
344
|
+
) {
|
|
345
|
+
return pageItems;
|
|
346
|
+
}
|
|
292
347
|
|
|
293
|
-
|
|
348
|
+
return [
|
|
349
|
+
{
|
|
350
|
+
id: membership.databaseDocumentId,
|
|
351
|
+
title: databaseMembershipDatabaseTitle(membership),
|
|
352
|
+
icon: null,
|
|
353
|
+
},
|
|
354
|
+
...pageItems,
|
|
355
|
+
];
|
|
356
|
+
}
|
|
294
357
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
358
|
+
export function documentEditorBreadcrumbNavigationItems(
|
|
359
|
+
items: ToolbarBreadcrumbItem[],
|
|
360
|
+
documents: Pick<
|
|
361
|
+
Document,
|
|
362
|
+
| "id"
|
|
363
|
+
| "parentId"
|
|
364
|
+
| "title"
|
|
365
|
+
| "icon"
|
|
366
|
+
| "position"
|
|
367
|
+
| "databaseMembership"
|
|
368
|
+
| "source"
|
|
369
|
+
>[], // i18n-ignore type expression
|
|
370
|
+
spaces: Pick<ContentSpaceSummary, "filesDocumentId" | "name">[], // i18n-ignore type expression
|
|
371
|
+
context?: {
|
|
372
|
+
currentDocumentId: string;
|
|
373
|
+
currentParentId: string | null;
|
|
374
|
+
currentDatabaseSystemRole: string | null;
|
|
375
|
+
catalogDocumentId: string | null;
|
|
376
|
+
workspacesTitle: string;
|
|
377
|
+
},
|
|
378
|
+
): ToolbarBreadcrumbItem[] {
|
|
379
|
+
const documentById = new Map(documents.map((item) => [item.id, item]));
|
|
380
|
+
const workspaceDocumentIds = new Set(
|
|
381
|
+
spaces.map((space) => space.filesDocumentId),
|
|
308
382
|
);
|
|
383
|
+
|
|
384
|
+
const navigationItems = items.map<ToolbarBreadcrumbItem>((item) => {
|
|
385
|
+
if (item.id && workspaceDocumentIds.has(item.id)) {
|
|
386
|
+
return {
|
|
387
|
+
...item,
|
|
388
|
+
iconKind: "folder",
|
|
389
|
+
menuItems: spaces.map((space) => ({
|
|
390
|
+
id: space.filesDocumentId,
|
|
391
|
+
title: space.name,
|
|
392
|
+
icon: null,
|
|
393
|
+
iconKind: "folder",
|
|
394
|
+
})),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const current = item.id ? documentById.get(item.id) : null;
|
|
399
|
+
if (!current) return item;
|
|
400
|
+
const membershipDocumentId =
|
|
401
|
+
current.databaseMembership?.databaseDocumentId ?? null;
|
|
402
|
+
const siblings = documents
|
|
403
|
+
.filter((candidate) => {
|
|
404
|
+
if (candidate.source?.kind === "folder") return false;
|
|
405
|
+
if (candidate.parentId !== current.parentId) return false;
|
|
406
|
+
if (current.parentId) return true;
|
|
407
|
+
return (
|
|
408
|
+
candidate.databaseMembership?.databaseDocumentId ===
|
|
409
|
+
membershipDocumentId
|
|
410
|
+
);
|
|
411
|
+
})
|
|
412
|
+
.sort(
|
|
413
|
+
(left, right) =>
|
|
414
|
+
left.position - right.position ||
|
|
415
|
+
left.title.localeCompare(right.title),
|
|
416
|
+
);
|
|
417
|
+
if (siblings.length < 2) return item;
|
|
418
|
+
return {
|
|
419
|
+
...item,
|
|
420
|
+
menuItems: siblings.map((sibling) => ({
|
|
421
|
+
id: sibling.id,
|
|
422
|
+
title: sibling.title,
|
|
423
|
+
icon: sibling.icon,
|
|
424
|
+
})),
|
|
425
|
+
};
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (
|
|
429
|
+
context?.catalogDocumentId &&
|
|
430
|
+
context.currentParentId === null &&
|
|
431
|
+
context.currentDatabaseSystemRole === "files" &&
|
|
432
|
+
workspaceDocumentIds.has(context.currentDocumentId)
|
|
433
|
+
) {
|
|
434
|
+
const workspacesItem: ToolbarBreadcrumbItem = {
|
|
435
|
+
id: context.catalogDocumentId,
|
|
436
|
+
title: context.workspacesTitle,
|
|
437
|
+
iconKind: "folder",
|
|
438
|
+
};
|
|
439
|
+
return [workspacesItem, ...navigationItems];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return navigationItems;
|
|
309
443
|
}
|
|
310
444
|
|
|
311
445
|
function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
312
446
|
const t = useT();
|
|
313
447
|
const updateDocument = useUpdateDocument();
|
|
448
|
+
const createDatabase = useCreateContentDatabase(documentId);
|
|
449
|
+
const deleteContentDatabase = useDeleteContentDatabase();
|
|
450
|
+
const deleteDocument = useDeleteDocument();
|
|
314
451
|
const queryClient = useQueryClient();
|
|
315
452
|
const processBuilderBodies = useProcessBuilderBodyHydration(
|
|
316
453
|
document.databaseMembership?.databaseDocumentId ?? documentId,
|
|
@@ -328,9 +465,22 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
328
465
|
const navigate = useNavigate();
|
|
329
466
|
const documentsQuery = useDocuments();
|
|
330
467
|
const documents: Document[] = documentsQuery.data ?? [];
|
|
468
|
+
const contentSpacesQuery = useContentSpaces();
|
|
469
|
+
const contentSpaces = contentSpacesQuery.data?.spaces ?? [];
|
|
470
|
+
const workspaceSelectionQueueRef = useRef(createContentSpaceSelectionQueue());
|
|
471
|
+
const [, setStoredSpaceId] = useLocalStorage<string | null>(
|
|
472
|
+
SELECTED_CONTENT_SPACE_STORAGE_KEY,
|
|
473
|
+
null,
|
|
474
|
+
);
|
|
331
475
|
// Shared with DocumentToolbar via the same localStorage key — both read it.
|
|
332
476
|
const [autoSync] = useLocalStorage(`notion-auto-sync:${documentId}`, false);
|
|
333
477
|
const isLocalFileDocument = document.source?.mode === "local-files";
|
|
478
|
+
const canDelete =
|
|
479
|
+
!isLocalFileDocument &&
|
|
480
|
+
!document.database?.systemRole &&
|
|
481
|
+
(document.canManage === true ||
|
|
482
|
+
document.accessRole === "owner" ||
|
|
483
|
+
document.accessRole === "admin");
|
|
334
484
|
const isLinkedLocalSourceDocument = canWriteLinkedLocalSource(
|
|
335
485
|
documentId,
|
|
336
486
|
document.source,
|
|
@@ -343,9 +493,34 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
343
493
|
const pushDocumentToNotion = usePushDocumentToNotion(documentId);
|
|
344
494
|
const [localTitle, setLocalTitle] = useState("");
|
|
345
495
|
const [localContent, setLocalContent] = useState("");
|
|
496
|
+
const [newDocumentTypeChosen, setNewDocumentTypeChosen] = useState(false);
|
|
346
497
|
const [localContentUpdatedAt, setLocalContentUpdatedAt] = useState<
|
|
347
498
|
string | null
|
|
348
499
|
>(document.updatedAt ?? null);
|
|
500
|
+
const handleDeleteDocument = useCallback(async () => {
|
|
501
|
+
try {
|
|
502
|
+
if (document.database) {
|
|
503
|
+
await deleteContentDatabase.mutateAsync({
|
|
504
|
+
databaseId: document.database.id,
|
|
505
|
+
});
|
|
506
|
+
} else {
|
|
507
|
+
await deleteDocument.mutateAsync({ id: documentId });
|
|
508
|
+
}
|
|
509
|
+
navigate("/", { replace: true, flushSync: true });
|
|
510
|
+
} catch (error) {
|
|
511
|
+
toast.error(t("sidebar.failedDeletePage"), {
|
|
512
|
+
description:
|
|
513
|
+
error instanceof Error ? error.message : t("empty.genericError"),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}, [
|
|
517
|
+
deleteContentDatabase,
|
|
518
|
+
deleteDocument,
|
|
519
|
+
document.database,
|
|
520
|
+
documentId,
|
|
521
|
+
navigate,
|
|
522
|
+
t,
|
|
523
|
+
]);
|
|
349
524
|
const flushRequestKey = `flush-request-${documentId}`;
|
|
350
525
|
const [flushRequestWake, setFlushRequestWake] = useState(0);
|
|
351
526
|
const handleFlushRequestEvent = useCallback(
|
|
@@ -515,6 +690,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
515
690
|
if (prevDocIdRef.current !== documentId) {
|
|
516
691
|
prevDocIdRef.current = documentId;
|
|
517
692
|
isInitializedRef.current = false;
|
|
693
|
+
setNewDocumentTypeChosen(false);
|
|
518
694
|
if (saveTimeoutRef.current) {
|
|
519
695
|
clearTimeout(saveTimeoutRef.current);
|
|
520
696
|
saveTimeoutRef.current = null;
|
|
@@ -610,7 +786,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
610
786
|
useEffect(() => {
|
|
611
787
|
if (!document || !isInitializedRef.current) return;
|
|
612
788
|
if (isLinkedLocalSourceDocument) return;
|
|
613
|
-
const titleMatchesLocal =
|
|
789
|
+
const titleMatchesLocal = titleMatchConfirmsSave({
|
|
790
|
+
serverTitle: document.title,
|
|
791
|
+
localTitle,
|
|
792
|
+
lastSavedTitle: lastSavedTitleRef.current.title,
|
|
793
|
+
pendingTitle: pendingDocumentSaveRef.current?.title ?? null,
|
|
794
|
+
});
|
|
614
795
|
const contentMatchesLocal = document.content === localContent;
|
|
615
796
|
|
|
616
797
|
if (titleMatchesLocal) {
|
|
@@ -696,6 +877,11 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
696
877
|
...(baseUpdatedAt !== undefined ? { baseUpdatedAt } : {}),
|
|
697
878
|
});
|
|
698
879
|
} catch (error) {
|
|
880
|
+
if (updates.title !== undefined) {
|
|
881
|
+
patchDocumentCaches(queryClient, documentId, {
|
|
882
|
+
title: lastSavedTitleRef.current.title,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
699
885
|
if (!isLinkedLocalSource) throw error;
|
|
700
886
|
toast.warning(t("editor.localFileSavedHistoryNotUpdated"), {
|
|
701
887
|
description:
|
|
@@ -1110,10 +1296,12 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1110
1296
|
const handleTitleChange = useCallback(
|
|
1111
1297
|
(newTitle: string) => {
|
|
1112
1298
|
if (!editorCanEdit) return;
|
|
1299
|
+
localTitleRef.current = newTitle;
|
|
1113
1300
|
setLocalTitle(newTitle);
|
|
1301
|
+
patchDocumentCaches(queryClient, documentId, { title: newTitle });
|
|
1114
1302
|
debouncedSave(newTitle, localContentRef.current);
|
|
1115
1303
|
},
|
|
1116
|
-
[debouncedSave, editorCanEdit],
|
|
1304
|
+
[debouncedSave, documentId, editorCanEdit, queryClient],
|
|
1117
1305
|
);
|
|
1118
1306
|
|
|
1119
1307
|
const handleContentChange = useCallback(
|
|
@@ -1153,18 +1341,15 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1153
1341
|
} | null>(null);
|
|
1154
1342
|
const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null);
|
|
1155
1343
|
const [hoveredThreadId, setHoveredThreadId] = useState<string | null>(null);
|
|
1344
|
+
const [utilityPanel, setUtilityPanel] = useState<DocumentUtilityPanel>(null);
|
|
1156
1345
|
const activeThreadId = hoveredThreadId ?? selectedThreadId;
|
|
1157
1346
|
const { data: threads, isLoading: commentsLoading } = useComments(
|
|
1158
1347
|
canEdit && !isLocalFileDocument ? documentId : null,
|
|
1159
1348
|
);
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
const hasCommentRailSpace = useMinViewportWidth(1024);
|
|
1165
|
-
const showDesktopComments = hasComments && hasCommentRailSpace;
|
|
1166
|
-
const showCommentsSheet =
|
|
1167
|
-
hasComments && editorCanEdit && !showDesktopComments;
|
|
1349
|
+
const hasUtilityRailSpace = useMinViewportWidth(1024);
|
|
1350
|
+
const showDesktopUtilityPanel = utilityPanel !== null && hasUtilityRailSpace;
|
|
1351
|
+
const showUtilityPanelSheet =
|
|
1352
|
+
utilityPanel !== null && !showDesktopUtilityPanel;
|
|
1168
1353
|
|
|
1169
1354
|
const handleComment = useCallback(
|
|
1170
1355
|
(
|
|
@@ -1174,6 +1359,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1174
1359
|
range?: { from: number; to: number },
|
|
1175
1360
|
) => {
|
|
1176
1361
|
setPendingComment({ quotedText, offsetTop, anchor, range });
|
|
1362
|
+
setUtilityPanel("comments");
|
|
1177
1363
|
setSelectedThreadId(null);
|
|
1178
1364
|
setHoveredThreadId(null);
|
|
1179
1365
|
},
|
|
@@ -1187,6 +1373,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1187
1373
|
|
|
1188
1374
|
useEffect(() => {
|
|
1189
1375
|
setPendingComment(null);
|
|
1376
|
+
setUtilityPanel(null);
|
|
1190
1377
|
clearCommentFocus();
|
|
1191
1378
|
}, [clearCommentFocus, documentId]);
|
|
1192
1379
|
|
|
@@ -1254,11 +1441,67 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1254
1441
|
});
|
|
1255
1442
|
|
|
1256
1443
|
const toolbarBreadcrumbItems = useMemo(
|
|
1257
|
-
() =>
|
|
1258
|
-
|
|
1444
|
+
() =>
|
|
1445
|
+
documentEditorBreadcrumbNavigationItems(
|
|
1446
|
+
documentEditorBreadcrumbItems(document, documents),
|
|
1447
|
+
documents,
|
|
1448
|
+
contentSpaces,
|
|
1449
|
+
{
|
|
1450
|
+
currentDocumentId: document.id,
|
|
1451
|
+
currentParentId: document.parentId,
|
|
1452
|
+
currentDatabaseSystemRole: document.database?.systemRole ?? null,
|
|
1453
|
+
catalogDocumentId: contentSpacesQuery.data?.catalogDocumentId ?? null,
|
|
1454
|
+
workspacesTitle: t("sidebar.workspaces"),
|
|
1455
|
+
},
|
|
1456
|
+
),
|
|
1457
|
+
[
|
|
1458
|
+
contentSpaces,
|
|
1459
|
+
contentSpacesQuery.data?.catalogDocumentId,
|
|
1460
|
+
document,
|
|
1461
|
+
documents,
|
|
1462
|
+
t,
|
|
1463
|
+
],
|
|
1464
|
+
);
|
|
1465
|
+
|
|
1466
|
+
const handleOpenToolbarBreadcrumb = useCallback(
|
|
1467
|
+
(targetId: string) => {
|
|
1468
|
+
const targetDocument = documents.find((item) => item.id === targetId);
|
|
1469
|
+
const filesDocumentId =
|
|
1470
|
+
targetDocument?.databaseMembership?.databaseDocumentId ?? targetId;
|
|
1471
|
+
const space = contentSpaces.find(
|
|
1472
|
+
(candidate) => candidate.filesDocumentId === filesDocumentId,
|
|
1473
|
+
);
|
|
1474
|
+
if (!space) {
|
|
1475
|
+
navigate(`/page/${targetId}`, { flushSync: true });
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
void workspaceSelectionQueueRef
|
|
1479
|
+
.current(() =>
|
|
1480
|
+
selectContentSpace({
|
|
1481
|
+
space,
|
|
1482
|
+
syncApplicationState: (selected) =>
|
|
1483
|
+
setClientAppState(
|
|
1484
|
+
"content-space",
|
|
1485
|
+
{
|
|
1486
|
+
spaceId: selected.id,
|
|
1487
|
+
name: selected.name,
|
|
1488
|
+
kind: selected.kind,
|
|
1489
|
+
filesDatabaseId: selected.filesDatabaseId,
|
|
1490
|
+
},
|
|
1491
|
+
{ requestSource: "content-breadcrumb" },
|
|
1492
|
+
),
|
|
1493
|
+
persistSelection: setStoredSpaceId,
|
|
1494
|
+
openFiles: () => navigate(`/page/${targetId}`, { flushSync: true }),
|
|
1495
|
+
}),
|
|
1496
|
+
)
|
|
1497
|
+
.catch((error) => {
|
|
1498
|
+
toast.error(error instanceof Error ? error.message : String(error));
|
|
1499
|
+
});
|
|
1500
|
+
},
|
|
1501
|
+
[contentSpaces, documents, navigate, setStoredSpaceId],
|
|
1259
1502
|
);
|
|
1260
1503
|
|
|
1261
|
-
const
|
|
1504
|
+
const commentsSidebar = (
|
|
1262
1505
|
<CommentsSidebar
|
|
1263
1506
|
documentId={documentId}
|
|
1264
1507
|
threads={threads ?? []}
|
|
@@ -1271,10 +1514,34 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1271
1514
|
onSelectedThreadChange={setSelectedThreadId}
|
|
1272
1515
|
onHoveredThreadChange={setHoveredThreadId}
|
|
1273
1516
|
currentUserEmail={session?.email}
|
|
1517
|
+
forceVisible
|
|
1274
1518
|
/>
|
|
1275
1519
|
);
|
|
1276
1520
|
const defaultIconKind = documentEditorDefaultIconKind(document);
|
|
1277
1521
|
const isDatabasePage = Boolean(document.database);
|
|
1522
|
+
const showNewDocumentTypeChooser =
|
|
1523
|
+
canEdit &&
|
|
1524
|
+
!isLocalFileDocument &&
|
|
1525
|
+
!isDatabasePage &&
|
|
1526
|
+
!newDocumentTypeChosen &&
|
|
1527
|
+
!localTitle.trim() &&
|
|
1528
|
+
!document.description?.trim() &&
|
|
1529
|
+
isEffectivelyEmptyDocumentContent(localContent);
|
|
1530
|
+
const handleChoosePage = useCallback(() => {
|
|
1531
|
+
setNewDocumentTypeChosen(true);
|
|
1532
|
+
requestAnimationFrame(() => titleInputRef.current?.focus());
|
|
1533
|
+
}, []);
|
|
1534
|
+
const handleChooseDatabase = useCallback(async () => {
|
|
1535
|
+
try {
|
|
1536
|
+
await createDatabase.mutateAsync({ documentId });
|
|
1537
|
+
setNewDocumentTypeChosen(true);
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
toast.error(t("sidebar.failedCreateDatabase"), {
|
|
1540
|
+
description:
|
|
1541
|
+
error instanceof Error ? error.message : t("empty.genericError"),
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
}, [createDatabase, documentId, t]);
|
|
1278
1545
|
const defaultIcon =
|
|
1279
1546
|
defaultIconKind === "database" && !isDatabasePage ? (
|
|
1280
1547
|
<IconDatabase className="size-12" aria-hidden="true" />
|
|
@@ -1283,6 +1550,36 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1283
1550
|
const exportContent = isInitializedRef.current
|
|
1284
1551
|
? localContent
|
|
1285
1552
|
: document.content;
|
|
1553
|
+
const utilityPanelTitle =
|
|
1554
|
+
utilityPanel === "info" ? t("editor.toolbar.info") : t("comments.title");
|
|
1555
|
+
const utilityPanelContent = utilityPanel ? (
|
|
1556
|
+
<div className="w-full min-w-0 bg-background" data-document-utility-panel>
|
|
1557
|
+
<div className="sticky top-0 z-10 flex h-12 items-center border-b border-border bg-background px-4">
|
|
1558
|
+
<h2 className="text-sm font-semibold">{utilityPanelTitle}</h2>
|
|
1559
|
+
{hasUtilityRailSpace ? (
|
|
1560
|
+
<button
|
|
1561
|
+
type="button"
|
|
1562
|
+
className="ms-auto flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1563
|
+
aria-label={t("editor.toolbar.closeUtilityPanel")}
|
|
1564
|
+
onClick={() => setUtilityPanel(null)}
|
|
1565
|
+
>
|
|
1566
|
+
<IconX size={16} />
|
|
1567
|
+
</button>
|
|
1568
|
+
) : null}
|
|
1569
|
+
</div>
|
|
1570
|
+
{utilityPanel === "info" ? (
|
|
1571
|
+
<DocumentInfoPanel
|
|
1572
|
+
document={document}
|
|
1573
|
+
canEdit={editorCanEdit}
|
|
1574
|
+
onSaveDescription={(description) =>
|
|
1575
|
+
persistDocumentUpdates({ description })
|
|
1576
|
+
}
|
|
1577
|
+
/>
|
|
1578
|
+
) : (
|
|
1579
|
+
commentsSidebar
|
|
1580
|
+
)}
|
|
1581
|
+
</div>
|
|
1582
|
+
) : null;
|
|
1286
1583
|
|
|
1287
1584
|
return (
|
|
1288
1585
|
<BlockRegistryProvider
|
|
@@ -1318,6 +1615,15 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1318
1615
|
canEdit={canEdit}
|
|
1319
1616
|
hideFromSearch={document.hideFromSearch}
|
|
1320
1617
|
source={document.source}
|
|
1618
|
+
canDelete={canDelete}
|
|
1619
|
+
deletePending={
|
|
1620
|
+
deleteDocument.isPending || deleteContentDatabase.isPending
|
|
1621
|
+
}
|
|
1622
|
+
onDelete={handleDeleteDocument}
|
|
1623
|
+
utilityPanel={utilityPanel}
|
|
1624
|
+
onUtilityPanelChange={setUtilityPanel}
|
|
1625
|
+
showCommentsControl={editorCanEdit && !isLocalFileDocument}
|
|
1626
|
+
onOpenBreadcrumbItem={handleOpenToolbarBreadcrumb}
|
|
1321
1627
|
/>
|
|
1322
1628
|
|
|
1323
1629
|
{!isLocalFileDocument ? (
|
|
@@ -1332,14 +1638,14 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1332
1638
|
<div
|
|
1333
1639
|
className={cn(
|
|
1334
1640
|
"flex min-h-full w-full min-w-0",
|
|
1335
|
-
|
|
1641
|
+
showDesktopUtilityPanel ? "justify-center" : "flex-col",
|
|
1336
1642
|
)}
|
|
1337
1643
|
data-document-scroll-content
|
|
1338
1644
|
>
|
|
1339
1645
|
<div
|
|
1340
1646
|
className={cn(
|
|
1341
1647
|
"min-w-0",
|
|
1342
|
-
|
|
1648
|
+
showDesktopUtilityPanel ? "flex-1" : "w-full",
|
|
1343
1649
|
)}
|
|
1344
1650
|
>
|
|
1345
1651
|
<div
|
|
@@ -1347,14 +1653,6 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1347
1653
|
Boolean(document.database),
|
|
1348
1654
|
)}
|
|
1349
1655
|
>
|
|
1350
|
-
<DatabaseMembershipBreadcrumb
|
|
1351
|
-
document={document}
|
|
1352
|
-
onOpenDatabase={(databaseDocumentId) =>
|
|
1353
|
-
navigate(`/page/${databaseDocumentId}`, {
|
|
1354
|
-
flushSync: true,
|
|
1355
|
-
})
|
|
1356
|
-
}
|
|
1357
|
-
/>
|
|
1358
1656
|
{document.icon || !isDatabasePage ? (
|
|
1359
1657
|
<div className="mb-1">
|
|
1360
1658
|
{editorCanEdit ? (
|
|
@@ -1366,9 +1664,13 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1366
1664
|
}
|
|
1367
1665
|
onSelect={(emoji) => {
|
|
1368
1666
|
void (async () => {
|
|
1369
|
-
const
|
|
1370
|
-
icon: emoji,
|
|
1371
|
-
|
|
1667
|
+
const updates = metadataUpdatesWithPendingTitle(
|
|
1668
|
+
{ icon: emoji },
|
|
1669
|
+
localTitleRef.current,
|
|
1670
|
+
lastSavedTitleRef.current.title,
|
|
1671
|
+
);
|
|
1672
|
+
const saved =
|
|
1673
|
+
await persistDocumentUpdates(updates);
|
|
1372
1674
|
// Icon-only save: never CAS-guarded server-side
|
|
1373
1675
|
// (no content in this call), so this can't come
|
|
1374
1676
|
// back as a conflict — narrow defensively anyway
|
|
@@ -1381,7 +1683,7 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1381
1683
|
saved?.updatedAt ?? new Date().toISOString(),
|
|
1382
1684
|
title: localTitleRef.current,
|
|
1383
1685
|
content: localContentRef.current,
|
|
1384
|
-
updates
|
|
1686
|
+
updates,
|
|
1385
1687
|
lastSavedTitleRef,
|
|
1386
1688
|
lastSavedContentRef,
|
|
1387
1689
|
});
|
|
@@ -1436,23 +1738,6 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1436
1738
|
isDatabasePage ? "text-3xl" : "text-3xl md:text-4xl",
|
|
1437
1739
|
)}
|
|
1438
1740
|
/>
|
|
1439
|
-
{!document.database ? (
|
|
1440
|
-
<DescriptionField
|
|
1441
|
-
description={document.description}
|
|
1442
|
-
canEdit={editorCanEdit}
|
|
1443
|
-
label={t("editor.properties.description")}
|
|
1444
|
-
placeholder={t("editor.properties.addPageDescription")}
|
|
1445
|
-
onSave={(description) =>
|
|
1446
|
-
persistDocumentUpdates({ description })
|
|
1447
|
-
}
|
|
1448
|
-
/>
|
|
1449
|
-
) : null}
|
|
1450
|
-
{document.databaseMembership && !isLocalFileDocument ? (
|
|
1451
|
-
<DocumentProperties
|
|
1452
|
-
documentId={documentId}
|
|
1453
|
-
canEdit={editorCanEdit}
|
|
1454
|
-
/>
|
|
1455
|
-
) : null}
|
|
1456
1741
|
</div>
|
|
1457
1742
|
{document.database ? (
|
|
1458
1743
|
<div className={documentEditorDatabaseRegionClassName()}>
|
|
@@ -1460,123 +1745,179 @@ function DocumentEditorBody({ documentId, document }: DocumentEditorBodyProps) {
|
|
|
1460
1745
|
</div>
|
|
1461
1746
|
) : null}
|
|
1462
1747
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1748
|
+
{!isDatabasePage ? (
|
|
1749
|
+
<div
|
|
1750
|
+
className="flex-1 w-full max-w-3xl mx-auto px-4 pb-16 cursor-text sm:px-8 md:px-16"
|
|
1751
|
+
onClick={(e) => {
|
|
1752
|
+
if (e.target === e.currentTarget) {
|
|
1753
|
+
const pm = e.currentTarget.querySelector(
|
|
1754
|
+
".ProseMirror",
|
|
1755
|
+
) as HTMLElement | null;
|
|
1756
|
+
pm?.focus();
|
|
1757
|
+
}
|
|
1758
|
+
}}
|
|
1759
|
+
>
|
|
1760
|
+
{(() => {
|
|
1761
|
+
if (bodyHydrationPending) {
|
|
1762
|
+
return (
|
|
1763
|
+
<BuilderBodySyncingNotice
|
|
1764
|
+
title={t("editor.builderBodySyncing")}
|
|
1765
|
+
description={t(
|
|
1766
|
+
"editor.builderBodySyncingDescription",
|
|
1767
|
+
)}
|
|
1768
|
+
/>
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1485
1771
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
notionPageLinks={notionPageLinks}
|
|
1533
|
-
onOpenNotionPageLink={handleOpenNotionPageLink}
|
|
1534
|
-
notionPageId={document.notionPageId}
|
|
1535
|
-
/>
|
|
1536
|
-
);
|
|
1537
|
-
|
|
1538
|
-
// Only database rows have Blocks fields. Standalone pages
|
|
1539
|
-
// and local-file documents keep the plain chromeless body.
|
|
1540
|
-
if (document.databaseMembership && !isLocalFileDocument) {
|
|
1541
|
-
return (
|
|
1542
|
-
<DocumentBlockFields
|
|
1772
|
+
if (showNewDocumentTypeChooser) {
|
|
1773
|
+
return (
|
|
1774
|
+
<div
|
|
1775
|
+
className="flex flex-wrap gap-2 pt-3"
|
|
1776
|
+
aria-label={t("sidebar.newPage")}
|
|
1777
|
+
>
|
|
1778
|
+
<Button
|
|
1779
|
+
type="button"
|
|
1780
|
+
variant="outline"
|
|
1781
|
+
className="justify-start gap-2"
|
|
1782
|
+
disabled={
|
|
1783
|
+
!editorCanEdit || createDatabase.isPending
|
|
1784
|
+
}
|
|
1785
|
+
onClick={handleChoosePage}
|
|
1786
|
+
>
|
|
1787
|
+
<IconFileText />
|
|
1788
|
+
{t("sidebar.page")}
|
|
1789
|
+
</Button>
|
|
1790
|
+
<Button
|
|
1791
|
+
type="button"
|
|
1792
|
+
variant="outline"
|
|
1793
|
+
className="justify-start gap-2"
|
|
1794
|
+
disabled={
|
|
1795
|
+
!editorCanEdit || createDatabase.isPending
|
|
1796
|
+
}
|
|
1797
|
+
onClick={() => void handleChooseDatabase()}
|
|
1798
|
+
>
|
|
1799
|
+
{createDatabase.isPending ? (
|
|
1800
|
+
<IconLoader2 className="animate-spin" />
|
|
1801
|
+
) : (
|
|
1802
|
+
<IconDatabase />
|
|
1803
|
+
)}
|
|
1804
|
+
{t("sidebar.database")}
|
|
1805
|
+
</Button>
|
|
1806
|
+
</div>
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// The primary "Content" Blocks field IS the document body,
|
|
1811
|
+
// with the full collaborative editor. It renders chromeless
|
|
1812
|
+
// when it's the only Blocks field, or inside a
|
|
1813
|
+
// header/collapsible shell when the row has multiple Blocks
|
|
1814
|
+
// fields.
|
|
1815
|
+
const primaryEditor = (
|
|
1816
|
+
<VisualEditor
|
|
1817
|
+
key={`${documentId}:${editorCanEdit && !isLocalFileDocument ? "live" : "snapshot"}`}
|
|
1543
1818
|
documentId={documentId}
|
|
1544
|
-
|
|
1545
|
-
|
|
1819
|
+
content={
|
|
1820
|
+
isLocalFileDocument
|
|
1821
|
+
? localContent
|
|
1822
|
+
: document.content
|
|
1823
|
+
}
|
|
1824
|
+
contentUpdatedAt={
|
|
1825
|
+
isLocalFileDocument
|
|
1826
|
+
? (localContentUpdatedAt ?? document.updatedAt)
|
|
1827
|
+
: document.updatedAt
|
|
1828
|
+
}
|
|
1829
|
+
onChange={handleContentChange}
|
|
1830
|
+
onSaveContent={handleContentSaveNow}
|
|
1831
|
+
// Bind the shared Y.Doc/awareness for viewers too — the
|
|
1832
|
+
// editor is non-editable for them and VisualEditor blocks
|
|
1833
|
+
// any local Y.Doc mutation, so they get live edits +
|
|
1834
|
+
// cursors without ever writing. Excludes local-file docs.
|
|
1835
|
+
ydoc={collabEnabled ? ydoc : null}
|
|
1836
|
+
collabSynced={collabEnabled ? collabSynced : true}
|
|
1837
|
+
awareness={collabEnabled ? awareness : null}
|
|
1838
|
+
user={currentUser}
|
|
1839
|
+
editable={editorCanEdit}
|
|
1840
|
+
localFileMode={isLocalFileDocument}
|
|
1841
|
+
localFilePath={
|
|
1842
|
+
isLocalFileDocument ? document.source?.path : null
|
|
1843
|
+
}
|
|
1844
|
+
onComment={
|
|
1845
|
+
editorCanEdit && !isLocalFileDocument
|
|
1846
|
+
? handleComment
|
|
1847
|
+
: undefined
|
|
1848
|
+
}
|
|
1849
|
+
commentThreads={threads ?? []}
|
|
1850
|
+
activeThreadId={activeThreadId}
|
|
1851
|
+
pendingHighlight={pendingComment?.range ?? null}
|
|
1852
|
+
onActivateThread={
|
|
1853
|
+
editorCanEdit && !isLocalFileDocument
|
|
1854
|
+
? setSelectedThreadId
|
|
1855
|
+
: undefined
|
|
1856
|
+
}
|
|
1857
|
+
onJoinTitle={joinFirstBodyBlockToTitle}
|
|
1858
|
+
notionPageLinks={notionPageLinks}
|
|
1859
|
+
onOpenNotionPageLink={handleOpenNotionPageLink}
|
|
1860
|
+
notionPageId={document.notionPageId}
|
|
1546
1861
|
/>
|
|
1547
1862
|
);
|
|
1548
|
-
}
|
|
1549
1863
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1864
|
+
// Only database rows have Blocks fields. Standalone pages
|
|
1865
|
+
// and local-file documents keep the plain chromeless body.
|
|
1866
|
+
if (document.databaseMembership && !isLocalFileDocument) {
|
|
1867
|
+
return (
|
|
1868
|
+
<DocumentBlockFields
|
|
1869
|
+
documentId={documentId}
|
|
1870
|
+
canEdit={editorCanEdit}
|
|
1871
|
+
primaryEditor={primaryEditor}
|
|
1872
|
+
/>
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
return primaryEditor;
|
|
1877
|
+
})()}
|
|
1878
|
+
{!bodyHydrationPending &&
|
|
1879
|
+
!isLocalFileDocument &&
|
|
1880
|
+
collabLoading ? (
|
|
1881
|
+
<div
|
|
1882
|
+
className="mt-4 inline-flex items-center gap-2 rounded-md border border-border bg-muted/40 px-3 py-2 text-xs text-muted-foreground"
|
|
1883
|
+
role="status"
|
|
1884
|
+
>
|
|
1885
|
+
<IconLoader2 className="size-3.5 animate-spin" />
|
|
1886
|
+
{t("editor.collabConnectingReadOnly")}
|
|
1887
|
+
</div>
|
|
1888
|
+
) : null}
|
|
1889
|
+
</div>
|
|
1890
|
+
) : null}
|
|
1564
1891
|
</div>
|
|
1565
1892
|
|
|
1566
|
-
{
|
|
1893
|
+
{showDesktopUtilityPanel ? (
|
|
1894
|
+
<aside className="w-80 shrink-0 border-s border-border">
|
|
1895
|
+
{utilityPanelContent}
|
|
1896
|
+
</aside>
|
|
1897
|
+
) : null}
|
|
1567
1898
|
</div>
|
|
1568
1899
|
</div>
|
|
1569
1900
|
</div>
|
|
1570
1901
|
|
|
1571
|
-
{
|
|
1902
|
+
{showUtilityPanelSheet ? (
|
|
1572
1903
|
<Sheet
|
|
1573
|
-
open={
|
|
1904
|
+
open={utilityPanel !== null}
|
|
1574
1905
|
onOpenChange={(open) => {
|
|
1575
|
-
if (!open)
|
|
1906
|
+
if (!open) {
|
|
1907
|
+
setUtilityPanel(null);
|
|
1908
|
+
setPendingComment(null);
|
|
1909
|
+
}
|
|
1576
1910
|
}}
|
|
1577
1911
|
>
|
|
1578
|
-
<SheetContent
|
|
1579
|
-
|
|
1912
|
+
<SheetContent
|
|
1913
|
+
side="right"
|
|
1914
|
+
className="w-[85vw] max-w-sm p-0"
|
|
1915
|
+
aria-describedby={undefined}
|
|
1916
|
+
>
|
|
1917
|
+
<SheetHeader className="sr-only">
|
|
1918
|
+
<SheetTitle>{utilityPanelTitle}</SheetTitle>
|
|
1919
|
+
</SheetHeader>
|
|
1920
|
+
{utilityPanelContent}
|
|
1580
1921
|
</SheetContent>
|
|
1581
1922
|
</Sheet>
|
|
1582
1923
|
) : null}
|