@agent-native/core 0.81.3 → 0.83.0
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 +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/CommandMenu.tsx +34 -12
- package/corpus/core/src/file-upload/builder.ts +199 -37
- package/corpus/core/src/file-upload/index.ts +2 -0
- package/corpus/core/src/file-upload/types.ts +38 -0
- package/corpus/templates/analytics/actions/hubspot-deals.ts +64 -4
- package/corpus/templates/clips/actions/create-recording.ts +44 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +198 -88
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +12 -0
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +83 -29
- package/corpus/templates/clips/app/routes/record.tsx +12 -1
- package/corpus/templates/clips/server/lib/resumable-session.ts +39 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +2 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +186 -29
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +4 -0
- package/corpus/templates/clips/shared/recording-core.ts +5 -0
- package/corpus/templates/content/.env.local.example +6 -0
- package/corpus/templates/content/AGENTS.md +10 -1
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +14 -1
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +43 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +79 -7
- package/corpus/templates/content/actions/_database-source-utils.ts +987 -22
- package/corpus/templates/content/actions/_database-utils.ts +41 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +74 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +33 -8
- package/corpus/templates/content/actions/change-content-database-source-role.ts +14 -0
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +3 -0
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +71 -3
- package/corpus/templates/content/actions/list-content-databases.ts +41 -33
- package/corpus/templates/content/actions/process-builder-body-hydration.ts +47 -0
- package/corpus/templates/content/actions/update-document.ts +93 -0
- package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +277 -0
- package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +2 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +105 -17
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +5 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +92 -0
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +5 -3
- package/corpus/templates/content/app/components/editor/extensions/registryBlocks.ts +41 -0
- package/corpus/templates/content/app/components/editor/registrySlashItems.ts +4 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +28 -33
- package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +47 -0
- package/corpus/templates/content/app/global.css +10 -3
- package/corpus/templates/content/app/hooks/use-content-database.ts +24 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +37 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +24 -0
- package/corpus/templates/content/app/i18n-data.ts +212 -0
- package/corpus/templates/content/app/lib/content-command-search.ts +64 -0
- package/corpus/templates/content/app/root.tsx +236 -8
- package/corpus/templates/content/changelog/2026-06-29-builder-cms-sources-now-sync-article-bodies-through-content.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-builder-article-media-now-render-as-images-and-embeds-when-r.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-builder-source-components-now-render-as-preserved-preview-bl.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-cmd-k-search-now-opens-from-the-editor-and-finds-real-content.md +6 -0
- package/corpus/templates/content/changelog/2026-06-30-sidebar-favorites-now-keep-long-titles-tidy-and-stay-in-sync.md +6 -0
- package/corpus/templates/content/package.json +3 -1
- package/corpus/templates/content/scripts/check-native-deps.mjs +57 -0
- package/corpus/templates/content/scripts/dev-database.mjs +83 -0
- package/corpus/templates/content/scripts/seed-demo-user.mjs +107 -0
- package/corpus/templates/content/server/db/schema.ts +27 -0
- package/corpus/templates/content/server/plugins/db.ts +33 -0
- package/corpus/templates/content/shared/api.ts +45 -0
- package/corpus/templates/content/shared/builder-mdx.ts +905 -5
- package/corpus/templates/content/shared/nfm-registry.ts +2 -0
- package/corpus/templates/content/shared/source-component-block.ts +141 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +5 -1
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +229 -131
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +103 -6
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +382 -72
- package/corpus/templates/design/changelog/2026-06-30-canvas-editing-now-keeps-undo-redo-and-cross-screen-layer-mo.md +6 -0
- package/dist/client/CommandMenu.d.ts +6 -2
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +25 -14
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +137 -25
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/index.d.ts +1 -1
- package/dist/file-upload/index.d.ts.map +1 -1
- package/dist/file-upload/index.js.map +1 -1
- package/dist/file-upload/types.d.ts +26 -0
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -151,6 +151,7 @@ import {
|
|
|
151
151
|
useExecuteBuilderSourceExecution,
|
|
152
152
|
useMoveDatabaseItem,
|
|
153
153
|
usePrepareBuilderSourceReview,
|
|
154
|
+
useProcessBuilderBodyHydration,
|
|
154
155
|
useRefreshContentDatabaseSource,
|
|
155
156
|
useSetContentDatabaseSourceWriteMode,
|
|
156
157
|
useSuggestSourceJoinKey,
|
|
@@ -479,6 +480,7 @@ function DatabaseTable({
|
|
|
479
480
|
const changeSourceRole = useChangeContentDatabaseSourceRole(document.id);
|
|
480
481
|
const refreshSource = useRefreshContentDatabaseSource(document.id);
|
|
481
482
|
const disconnectSource = useDisconnectContentDatabaseSource(document.id);
|
|
483
|
+
const processBuilderBodies = useProcessBuilderBodyHydration(document.id);
|
|
482
484
|
const prepareBuilderReview = usePrepareBuilderSourceReview(document.id);
|
|
483
485
|
const executeBuilderExecution = useExecuteBuilderSourceExecution(document.id);
|
|
484
486
|
const setSourceWriteMode = useSetContentDatabaseSourceWriteMode(document.id);
|
|
@@ -1570,6 +1572,9 @@ function DatabaseTable({
|
|
|
1570
1572
|
sourceId,
|
|
1571
1573
|
})
|
|
1572
1574
|
}
|
|
1575
|
+
onHydrateBuilderBodies={(sourceId) =>
|
|
1576
|
+
processBuilderBodies.mutate({ sourceId })
|
|
1577
|
+
}
|
|
1573
1578
|
onDisconnectSource={(sourceId) =>
|
|
1574
1579
|
disconnectSource.mutate(
|
|
1575
1580
|
{
|
|
@@ -1635,6 +1640,7 @@ function DatabaseTable({
|
|
|
1635
1640
|
attachSource.isPending ||
|
|
1636
1641
|
changeSourceRole.isPending ||
|
|
1637
1642
|
refreshSource.isPending ||
|
|
1643
|
+
processBuilderBodies.isPending ||
|
|
1638
1644
|
disconnectSource.isPending ||
|
|
1639
1645
|
prepareBuilderReview.isPending ||
|
|
1640
1646
|
executeBuilderExecution.isPending ||
|
|
@@ -3539,6 +3545,7 @@ function DatabaseSettingsPanelSheet({
|
|
|
3539
3545
|
onChangeSourceRole,
|
|
3540
3546
|
onDisconnectSecondary,
|
|
3541
3547
|
onRefreshSource,
|
|
3548
|
+
onHydrateBuilderBodies,
|
|
3542
3549
|
onDisconnectSource,
|
|
3543
3550
|
onReviewBuilderUpdate,
|
|
3544
3551
|
onSetBuilderLiveWrites,
|
|
@@ -3580,6 +3587,7 @@ function DatabaseSettingsPanelSheet({
|
|
|
3580
3587
|
) => Promise<ContentDatabaseResponse>;
|
|
3581
3588
|
onDisconnectSecondary: (sourceId: string) => void;
|
|
3582
3589
|
onRefreshSource: (sourceId?: string) => void;
|
|
3590
|
+
onHydrateBuilderBodies: (sourceId: string) => void;
|
|
3583
3591
|
onDisconnectSource: (sourceId?: string) => void;
|
|
3584
3592
|
onReviewBuilderUpdate: () => void;
|
|
3585
3593
|
onSetBuilderLiveWrites: (enabled: boolean) => void;
|
|
@@ -3677,6 +3685,7 @@ function DatabaseSettingsPanelSheet({
|
|
|
3677
3685
|
setSourceNavStack([]);
|
|
3678
3686
|
}}
|
|
3679
3687
|
onRefreshSource={onRefreshSource}
|
|
3688
|
+
onHydrateBuilderBodies={onHydrateBuilderBodies}
|
|
3680
3689
|
onDisconnectSource={onDisconnectSource}
|
|
3681
3690
|
onReviewBuilderUpdate={onReviewBuilderUpdate}
|
|
3682
3691
|
onSetBuilderLiveWrites={onSetBuilderLiveWrites}
|
|
@@ -3976,6 +3985,7 @@ function DatabaseSettingsSourcePanel({
|
|
|
3976
3985
|
onChangeSourceRole,
|
|
3977
3986
|
onDisconnectSecondary,
|
|
3978
3987
|
onRefreshSource,
|
|
3988
|
+
onHydrateBuilderBodies,
|
|
3979
3989
|
onDisconnectSource,
|
|
3980
3990
|
onReviewBuilderUpdate,
|
|
3981
3991
|
onSetBuilderLiveWrites,
|
|
@@ -4004,6 +4014,7 @@ function DatabaseSettingsSourcePanel({
|
|
|
4004
4014
|
) => Promise<ContentDatabaseResponse>;
|
|
4005
4015
|
onDisconnectSecondary: (sourceId: string) => void;
|
|
4006
4016
|
onRefreshSource: (sourceId?: string) => void;
|
|
4017
|
+
onHydrateBuilderBodies: (sourceId: string) => void;
|
|
4007
4018
|
onDisconnectSource: (sourceId?: string) => void;
|
|
4008
4019
|
onReviewBuilderUpdate: () => void;
|
|
4009
4020
|
onSetBuilderLiveWrites: (enabled: boolean) => void;
|
|
@@ -4442,6 +4453,15 @@ function DatabaseSettingsSourcePanel({
|
|
|
4442
4453
|
</div>
|
|
4443
4454
|
) : null}
|
|
4444
4455
|
|
|
4456
|
+
{isBuilderSource && source.bodyHydration ? (
|
|
4457
|
+
<BuilderBodyHydrationCard
|
|
4458
|
+
source={source}
|
|
4459
|
+
canEdit={canEdit}
|
|
4460
|
+
pending={sourceActionPending}
|
|
4461
|
+
onHydrate={() => onHydrateBuilderBodies(source.id)}
|
|
4462
|
+
/>
|
|
4463
|
+
) : null}
|
|
4464
|
+
|
|
4445
4465
|
{isCodeMode ? (
|
|
4446
4466
|
<>
|
|
4447
4467
|
<div className="grid min-w-0 gap-2 rounded-lg border border-border bg-background p-3 text-sm">
|
|
@@ -5173,6 +5193,78 @@ function SourceRoleCard({
|
|
|
5173
5193
|
);
|
|
5174
5194
|
}
|
|
5175
5195
|
|
|
5196
|
+
function BuilderBodyHydrationCard({
|
|
5197
|
+
source,
|
|
5198
|
+
canEdit,
|
|
5199
|
+
pending,
|
|
5200
|
+
onHydrate,
|
|
5201
|
+
}: {
|
|
5202
|
+
source: ContentDatabaseSource;
|
|
5203
|
+
canEdit: boolean;
|
|
5204
|
+
pending: boolean;
|
|
5205
|
+
onHydrate: () => void;
|
|
5206
|
+
}) {
|
|
5207
|
+
const summary = source.bodyHydration;
|
|
5208
|
+
if (!summary || summary.total === 0) return null;
|
|
5209
|
+
const activeCount = summary.pending + summary.hydrating;
|
|
5210
|
+
const needsWork = activeCount > 0 || summary.error > 0;
|
|
5211
|
+
const hydratedLabel = dbText("builderBodiesHydrated", {
|
|
5212
|
+
hydrated: summary.hydrated,
|
|
5213
|
+
total: summary.total,
|
|
5214
|
+
});
|
|
5215
|
+
const detail = needsWork
|
|
5216
|
+
? [
|
|
5217
|
+
activeCount > 0
|
|
5218
|
+
? dbText("builderBodiesQueued", { count: activeCount })
|
|
5219
|
+
: null,
|
|
5220
|
+
summary.error > 0
|
|
5221
|
+
? dbText("builderBodySyncFailed", { count: summary.error })
|
|
5222
|
+
: null,
|
|
5223
|
+
]
|
|
5224
|
+
.filter(Boolean)
|
|
5225
|
+
.join(" · ")
|
|
5226
|
+
: dbText("builderBodiesReadyLocally");
|
|
5227
|
+
|
|
5228
|
+
return (
|
|
5229
|
+
<div className="grid min-w-0 gap-2 rounded-lg border border-border bg-background p-3">
|
|
5230
|
+
<div className="flex min-w-0 items-start justify-between gap-3">
|
|
5231
|
+
<div className="min-w-0">
|
|
5232
|
+
<div className="text-xs font-medium">{dbText("builderBodySync")}</div>
|
|
5233
|
+
<div className="mt-0.5 text-xs text-muted-foreground">
|
|
5234
|
+
{hydratedLabel}
|
|
5235
|
+
</div>
|
|
5236
|
+
</div>
|
|
5237
|
+
{needsWork ? (
|
|
5238
|
+
<Button
|
|
5239
|
+
type="button"
|
|
5240
|
+
size="sm"
|
|
5241
|
+
variant="outline"
|
|
5242
|
+
className="h-7 shrink-0 px-2 text-xs"
|
|
5243
|
+
disabled={!canEdit || pending}
|
|
5244
|
+
onClick={onHydrate}
|
|
5245
|
+
>
|
|
5246
|
+
{pending ? (
|
|
5247
|
+
<Spinner className="mr-1 size-3.5" />
|
|
5248
|
+
) : (
|
|
5249
|
+
<IconRefresh className="mr-1 size-3.5" />
|
|
5250
|
+
)}
|
|
5251
|
+
{summary.error > 0 ? dbText("retry") : dbText("resume")}
|
|
5252
|
+
</Button>
|
|
5253
|
+
) : null}
|
|
5254
|
+
</div>
|
|
5255
|
+
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
|
|
5256
|
+
<div
|
|
5257
|
+
className="h-full rounded-full bg-foreground transition-[width]"
|
|
5258
|
+
style={{
|
|
5259
|
+
width: `${Math.round((summary.hydrated / summary.total) * 100)}%`,
|
|
5260
|
+
}}
|
|
5261
|
+
/>
|
|
5262
|
+
</div>
|
|
5263
|
+
<div className="break-words text-xs text-muted-foreground">{detail}</div>
|
|
5264
|
+
</div>
|
|
5265
|
+
);
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5176
5268
|
const DETAIL_FIELD_NAME_HINTS = [
|
|
5177
5269
|
"name",
|
|
5178
5270
|
"title",
|
|
@@ -575,9 +575,11 @@ export function BuilderSourceReviewDialog({
|
|
|
575
575
|
<div className="font-medium">
|
|
576
576
|
{row.bodyChange.summary}
|
|
577
577
|
</div>
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
578
|
+
{row.bodyChange.warnings?.length ? (
|
|
579
|
+
<div className="mt-1 text-muted-foreground">
|
|
580
|
+
{row.bodyChange.warnings.join(" ")}
|
|
581
|
+
</div>
|
|
582
|
+
) : null}
|
|
581
583
|
</div>
|
|
582
584
|
) : null}
|
|
583
585
|
{showConflict ? (
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createRegistryBlockNode } from "@agent-native/core/client";
|
|
2
|
+
import { Extension } from "@tiptap/core";
|
|
3
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
2
4
|
|
|
3
5
|
/* -------------------------------------------------------------------------- */
|
|
4
6
|
/* Content's registry-block Tiptap node — a thin wrapper over the core node. */
|
|
@@ -46,4 +48,43 @@ export const RegistryBlockNode = createRegistryBlockNode({
|
|
|
46
48
|
mintId: createContentBlockId,
|
|
47
49
|
});
|
|
48
50
|
|
|
51
|
+
function sourceComponentIdCounts(doc: { descendants: (fn: any) => void }) {
|
|
52
|
+
const counts = new Map<string, number>();
|
|
53
|
+
doc.descendants((node: any) => {
|
|
54
|
+
if (
|
|
55
|
+
node.type?.name === "registryBlock" &&
|
|
56
|
+
node.attrs?.blockType === "source-component" &&
|
|
57
|
+
typeof node.attrs.blockId === "string" &&
|
|
58
|
+
node.attrs.blockId
|
|
59
|
+
) {
|
|
60
|
+
counts.set(node.attrs.blockId, (counts.get(node.attrs.blockId) ?? 0) + 1);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
return counts;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const LockedSourceComponentBlocks = Extension.create({
|
|
68
|
+
name: "lockedSourceComponentBlocks",
|
|
69
|
+
|
|
70
|
+
addProseMirrorPlugins() {
|
|
71
|
+
return [
|
|
72
|
+
new Plugin({
|
|
73
|
+
key: new PluginKey("lockedSourceComponentBlocks"),
|
|
74
|
+
filterTransaction(transaction, state) {
|
|
75
|
+
if (!transaction.docChanged) return true;
|
|
76
|
+
const before = sourceComponentIdCounts(state.doc);
|
|
77
|
+
if (before.size === 0) return true;
|
|
78
|
+
const after = sourceComponentIdCounts(transaction.doc);
|
|
79
|
+
if (after.size !== before.size) return false;
|
|
80
|
+
for (const [id, count] of before) {
|
|
81
|
+
if (after.get(id) !== count) return false;
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
];
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
49
90
|
export default RegistryBlockNode;
|
|
@@ -102,6 +102,9 @@ export function buildRegistrySlashItems(
|
|
|
102
102
|
// not a content-authoring block.
|
|
103
103
|
// - `inline-database` is registered in Phase 2 for render/round-trip
|
|
104
104
|
// compatibility; Phase 3 owns the `/database` insertion flow.
|
|
105
|
+
// - `source-component` is emitted by provider hydration to preserve
|
|
106
|
+
// source-native blocks; users should not author raw preservation
|
|
107
|
+
// markers by hand.
|
|
105
108
|
// The genuinely document-friendly rich blocks (callout, decision, diagram,
|
|
106
109
|
// wireframe, and the dev-doc/structured set) ARE offered.
|
|
107
110
|
includeSpec: (spec) =>
|
|
@@ -110,6 +113,7 @@ export function buildRegistrySlashItems(
|
|
|
110
113
|
"question-form",
|
|
111
114
|
"visual-questions",
|
|
112
115
|
"inline-database",
|
|
116
|
+
"source-component",
|
|
113
117
|
].includes(spec.type),
|
|
114
118
|
toItem: (spec, insert) => ({
|
|
115
119
|
title: spec.label,
|
|
@@ -59,6 +59,10 @@ import {
|
|
|
59
59
|
} from "@/hooks/use-documents";
|
|
60
60
|
import { cn } from "@/lib/utils";
|
|
61
61
|
|
|
62
|
+
import {
|
|
63
|
+
getDocumentSidebarSections,
|
|
64
|
+
isDirectLocalDocument,
|
|
65
|
+
} from "./document-sidebar-sections";
|
|
62
66
|
import { DocumentSidebarIcon, DocumentTreeItem } from "./DocumentTreeItem";
|
|
63
67
|
import { NotionButton } from "./NotionButton";
|
|
64
68
|
|
|
@@ -114,22 +118,6 @@ function collectDocumentSubtreeIds(documents: Document[], rootId: string) {
|
|
|
114
118
|
return deletedIds;
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
function isDirectLocalDocument(document: Pick<Document, "id" | "source">) {
|
|
118
|
-
return (
|
|
119
|
-
document.source?.mode === "local-files" &&
|
|
120
|
-
(document.id.startsWith("local-file:") ||
|
|
121
|
-
document.id.startsWith("local-folder:"))
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function isImportedLocalSourceDocument(
|
|
126
|
-
document: Pick<Document, "id" | "source">,
|
|
127
|
-
) {
|
|
128
|
-
return (
|
|
129
|
-
document.source?.mode === "local-files" && !isDirectLocalDocument(document)
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
121
|
type SidebarSectionId =
|
|
134
122
|
| "local-files"
|
|
135
123
|
| "shared-copies"
|
|
@@ -210,24 +198,23 @@ export function DocumentSidebar({
|
|
|
210
198
|
);
|
|
211
199
|
|
|
212
200
|
const treeDocuments = filterDocumentTreeDocuments(documents);
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
(document) => !isImportedLocalSourceDocument(document),
|
|
221
|
-
);
|
|
201
|
+
const {
|
|
202
|
+
localFileMode,
|
|
203
|
+
localSourceDocuments,
|
|
204
|
+
databaseDocuments,
|
|
205
|
+
favorites,
|
|
206
|
+
showFavorites,
|
|
207
|
+
} = getDocumentSidebarSections(documents, treeDocuments);
|
|
222
208
|
const localFileTree = buildDocumentTree(localSourceDocuments);
|
|
223
209
|
const databaseTree = buildDocumentTree(databaseDocuments);
|
|
224
210
|
const privateTree = databaseTree.filter((node) => node.visibility !== "org");
|
|
225
211
|
const organizationTree = databaseTree.filter(
|
|
226
212
|
(node) => node.visibility === "org",
|
|
227
213
|
);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
214
|
+
// Match the tree rows' right-side inset so favorite titles clip inside the
|
|
215
|
+
// visible sidebar instead of widening the scroll surface.
|
|
216
|
+
const favoriteRowWidth =
|
|
217
|
+
width === undefined ? undefined : Math.max(224, width - 8);
|
|
231
218
|
const activeDocument = activeDocumentId
|
|
232
219
|
? documents.find((doc) => doc.id === activeDocumentId)
|
|
233
220
|
: null;
|
|
@@ -878,21 +865,29 @@ export function DocumentSidebar({
|
|
|
878
865
|
) : (
|
|
879
866
|
<>
|
|
880
867
|
{/* Favorites */}
|
|
881
|
-
{
|
|
882
|
-
<div className="mb-2">
|
|
883
|
-
<div className="px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground
|
|
868
|
+
{showFavorites && (
|
|
869
|
+
<div className="mb-2 min-w-0">
|
|
870
|
+
<div className="flex min-w-0 items-center gap-1 px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
884
871
|
<IconStar size={10} />
|
|
885
|
-
|
|
872
|
+
<span className="min-w-0 flex-1 truncate">
|
|
873
|
+
{t("sidebar.favorites")}
|
|
874
|
+
</span>
|
|
886
875
|
</div>
|
|
887
876
|
{favorites.map((doc) => (
|
|
888
877
|
<button
|
|
889
878
|
key={doc.id}
|
|
890
879
|
className={cn(
|
|
891
|
-
"w-full
|
|
880
|
+
"flex w-full min-w-0 items-center gap-2 rounded-md px-4 py-[5px] text-start text-sm",
|
|
892
881
|
doc.id === activeDocumentId
|
|
893
882
|
? "bg-accent text-accent-foreground"
|
|
894
883
|
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
|
|
895
884
|
)}
|
|
885
|
+
style={{
|
|
886
|
+
width:
|
|
887
|
+
favoriteRowWidth === undefined
|
|
888
|
+
? undefined
|
|
889
|
+
: `${favoriteRowWidth}px`,
|
|
890
|
+
}}
|
|
896
891
|
onClick={() => {
|
|
897
892
|
navigateToDocument(doc.id);
|
|
898
893
|
onNavigate?.();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Document } from "@shared/api";
|
|
2
|
+
|
|
3
|
+
export function isDirectLocalDocument(
|
|
4
|
+
document: Pick<Document, "id" | "source">,
|
|
5
|
+
) {
|
|
6
|
+
return (
|
|
7
|
+
document.source?.mode === "local-files" &&
|
|
8
|
+
(document.id.startsWith("local-file:") ||
|
|
9
|
+
document.id.startsWith("local-folder:"))
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isImportedLocalSourceDocument(
|
|
14
|
+
document: Pick<Document, "id" | "source">,
|
|
15
|
+
) {
|
|
16
|
+
return (
|
|
17
|
+
document.source?.mode === "local-files" && !isDirectLocalDocument(document)
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getDocumentSidebarSections(
|
|
22
|
+
documents: Document[],
|
|
23
|
+
treeDocuments: Document[] = documents,
|
|
24
|
+
) {
|
|
25
|
+
const localFileMode = documents.some(isDirectLocalDocument);
|
|
26
|
+
const localSourceDocuments = localFileMode
|
|
27
|
+
? treeDocuments.filter(isDirectLocalDocument)
|
|
28
|
+
: treeDocuments.filter(isImportedLocalSourceDocument);
|
|
29
|
+
const databaseDocuments = localFileMode
|
|
30
|
+
? treeDocuments.filter((document) => !isDirectLocalDocument(document))
|
|
31
|
+
: treeDocuments.filter(
|
|
32
|
+
(document) => !isImportedLocalSourceDocument(document),
|
|
33
|
+
);
|
|
34
|
+
const favorites = documents.filter(
|
|
35
|
+
(document) =>
|
|
36
|
+
document.isFavorite &&
|
|
37
|
+
(localFileMode || !isImportedLocalSourceDocument(document)),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
localFileMode,
|
|
42
|
+
localSourceDocuments,
|
|
43
|
+
databaseDocuments,
|
|
44
|
+
favorites,
|
|
45
|
+
showFavorites: favorites.length > 0,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -2298,25 +2298,32 @@ audio.media-block__content {
|
|
|
2298
2298
|
|
|
2299
2299
|
/* ===== Collaboration cursors ===== */
|
|
2300
2300
|
.notion-editor .collaboration-cursor__selection {
|
|
2301
|
-
background-color:
|
|
2301
|
+
background-color: currentColor !important;
|
|
2302
2302
|
border-radius: 2px;
|
|
2303
2303
|
box-decoration-break: clone;
|
|
2304
2304
|
-webkit-box-decoration-break: clone;
|
|
2305
|
+
opacity: 0.16;
|
|
2305
2306
|
}
|
|
2306
2307
|
|
|
2307
|
-
.collaboration-cursor__caret {
|
|
2308
|
+
.notion-editor .collaboration-cursor__caret {
|
|
2308
2309
|
border-left: 2px solid;
|
|
2309
2310
|
border-color: currentColor;
|
|
2311
|
+
display: inline;
|
|
2310
2312
|
margin-left: -1px;
|
|
2311
2313
|
margin-right: -1px;
|
|
2312
2314
|
position: relative;
|
|
2313
2315
|
word-break: normal;
|
|
2314
2316
|
pointer-events: none;
|
|
2317
|
+
line-height: 1;
|
|
2315
2318
|
}
|
|
2316
|
-
.collaboration-cursor__label {
|
|
2319
|
+
.notion-editor .collaboration-cursor__label {
|
|
2317
2320
|
position: absolute;
|
|
2318
2321
|
top: -1.4em;
|
|
2319
2322
|
left: -1px;
|
|
2323
|
+
display: inline-block;
|
|
2324
|
+
max-width: min(180px, 50vw);
|
|
2325
|
+
overflow: hidden;
|
|
2326
|
+
text-overflow: ellipsis;
|
|
2320
2327
|
font-size: 11px;
|
|
2321
2328
|
font-weight: 600;
|
|
2322
2329
|
line-height: 1;
|
|
@@ -22,6 +22,8 @@ import type {
|
|
|
22
22
|
PrepareBuilderSourceExecutionRequest,
|
|
23
23
|
PrepareBuilderSourceReviewRequest,
|
|
24
24
|
PrepareBuilderSourceReviewResponse,
|
|
25
|
+
ProcessBuilderBodyHydrationRequest,
|
|
26
|
+
ProcessBuilderBodyHydrationResponse,
|
|
25
27
|
RefreshContentDatabaseSourceRequest,
|
|
26
28
|
ReviewContentDatabaseSourceChangeSetRequest,
|
|
27
29
|
SetContentDatabaseSourceWriteModeRequest,
|
|
@@ -408,6 +410,28 @@ export function useRefreshContentDatabaseSource(documentId: string) {
|
|
|
408
410
|
});
|
|
409
411
|
}
|
|
410
412
|
|
|
413
|
+
export function useProcessBuilderBodyHydration(documentId: string) {
|
|
414
|
+
const queryClient = useQueryClient();
|
|
415
|
+
return useActionMutation<
|
|
416
|
+
ProcessBuilderBodyHydrationResponse,
|
|
417
|
+
ProcessBuilderBodyHydrationRequest
|
|
418
|
+
>("process-builder-body-hydration", {
|
|
419
|
+
onSuccess: (_data, variables) => {
|
|
420
|
+
queryClient.invalidateQueries({
|
|
421
|
+
queryKey: contentDatabaseQueryKey(documentId),
|
|
422
|
+
});
|
|
423
|
+
queryClient.invalidateQueries({
|
|
424
|
+
queryKey: ["action", "get-content-database-source", { documentId }],
|
|
425
|
+
});
|
|
426
|
+
if (variables?.documentId) {
|
|
427
|
+
queryClient.invalidateQueries({
|
|
428
|
+
queryKey: ["action", "get-document", { id: variables.documentId }],
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
411
435
|
export function useDisconnectContentDatabaseSource(documentId: string) {
|
|
412
436
|
const queryClient = useQueryClient();
|
|
413
437
|
return useActionMutation<
|
|
@@ -12,6 +12,36 @@ import { toast } from "sonner";
|
|
|
12
12
|
|
|
13
13
|
import { useRestoreContentDatabase } from "./use-content-database";
|
|
14
14
|
|
|
15
|
+
const LIST_DOCUMENTS_QUERY_KEY = ["action", "list-documents", undefined];
|
|
16
|
+
|
|
17
|
+
export function mergeDocumentIntoDocumentCache(
|
|
18
|
+
old: unknown,
|
|
19
|
+
document: Document,
|
|
20
|
+
) {
|
|
21
|
+
return old && typeof old === "object" ? { ...old, ...document } : document;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function mergeDocumentIntoListDocumentsCache(
|
|
25
|
+
old: unknown,
|
|
26
|
+
document: Document,
|
|
27
|
+
) {
|
|
28
|
+
if (Array.isArray(old)) {
|
|
29
|
+
return old.map((item: Document) =>
|
|
30
|
+
item.id === document.id ? { ...item, ...document } : item,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!old || typeof old !== "object") return old;
|
|
35
|
+
const cached = old as { documents?: unknown };
|
|
36
|
+
if (!Array.isArray(cached.documents)) return old;
|
|
37
|
+
|
|
38
|
+
const nextDocuments = cached.documents.map((item: Document) =>
|
|
39
|
+
item.id === document.id ? { ...item, ...document } : item,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return { ...(old as object), documents: nextDocuments };
|
|
43
|
+
}
|
|
44
|
+
|
|
15
45
|
export function useDocuments() {
|
|
16
46
|
return useActionQuery<Document[]>("list-documents", undefined, {
|
|
17
47
|
select: (data: any) => {
|
|
@@ -42,6 +72,13 @@ export function useUpdateDocument() {
|
|
|
42
72
|
DocumentUpdateRequest & { id: string }
|
|
43
73
|
>("update-document", {
|
|
44
74
|
onSuccess: (data, variables) => {
|
|
75
|
+
queryClient.setQueryData(
|
|
76
|
+
["action", "get-document", { id: variables.id }],
|
|
77
|
+
(old: unknown) => mergeDocumentIntoDocumentCache(old, data),
|
|
78
|
+
);
|
|
79
|
+
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, (old: unknown) =>
|
|
80
|
+
mergeDocumentIntoListDocumentsCache(old, data),
|
|
81
|
+
);
|
|
45
82
|
queryClient.invalidateQueries({
|
|
46
83
|
queryKey: ["action", "get-document", { id: variables.id }],
|
|
47
84
|
});
|
|
@@ -2,6 +2,16 @@ const messages = {
|
|
|
2
2
|
root: {
|
|
3
3
|
commandContent: "內容",
|
|
4
4
|
commandSearchDocuments: "搜尋檔案",
|
|
5
|
+
commandSearchHeading: "搜尋",
|
|
6
|
+
commandSearchPlaceholder: "搜尋文件和資料庫...",
|
|
7
|
+
commandSearchLoading: "搜尋中...",
|
|
8
|
+
commandSearchError: "搜尋目前無法使用。",
|
|
9
|
+
commandSearchPartialError: "部分結果無法載入。",
|
|
10
|
+
commandSearchEmpty: "沒有相符的文件或資料庫。",
|
|
11
|
+
commandDocumentsHeading: "文件",
|
|
12
|
+
commandDatabasesHeading: "資料庫",
|
|
13
|
+
commandLocalFilesHeading: "本機檔案",
|
|
14
|
+
commandDatabaseResultDescription: "開啟資料庫頁面",
|
|
5
15
|
commandAppearance: "外觀",
|
|
6
16
|
toggleTheme: "切換主題",
|
|
7
17
|
metaTitle:
|
|
@@ -154,6 +164,14 @@ const messages = {
|
|
|
154
164
|
pathMissing: "引用路徑缺失",
|
|
155
165
|
selfReference: "此引用指向目前文件。",
|
|
156
166
|
},
|
|
167
|
+
sourceComponent: {
|
|
168
|
+
defaultTitle: "來源元件",
|
|
169
|
+
noPreviewRows: "沒有預覽列",
|
|
170
|
+
previewAvailable: "可預覽",
|
|
171
|
+
previewTruncated: "預覽已截斷",
|
|
172
|
+
previewUnavailable: "無法預覽",
|
|
173
|
+
needsAttention: "需要注意",
|
|
174
|
+
},
|
|
157
175
|
media: {
|
|
158
176
|
addAltTextDescription: "新增替代文字來描述該圖片。",
|
|
159
177
|
addAudio: "新增音訊",
|
|
@@ -585,6 +603,11 @@ const messages = {
|
|
|
585
603
|
builderSpace: "Builder 空間",
|
|
586
604
|
builderUpdateFailed: "Builder 更新失敗",
|
|
587
605
|
builderWriteModeWasNotChanged: "Builder 寫入模式未更改",
|
|
606
|
+
builderBodiesReadyLocally: "Builder 文章正文已在本機準備好。",
|
|
607
|
+
builderBodiesQueued: "{{count}} 個排隊中",
|
|
608
|
+
builderBodySync: "正文同步",
|
|
609
|
+
builderBodySyncFailed: "{{count}} 個失敗",
|
|
610
|
+
builderBodiesHydrated: "已同步 {{hydrated}} / {{total}} 個正文",
|
|
588
611
|
closeDatabaseSettings: "關閉資料庫設定",
|
|
589
612
|
closeSearch: "關閉搜尋",
|
|
590
613
|
databaseRowsAndLocalPropertiesWereKeptIntact: "資料庫行和本機屬性已保留。",
|
|
@@ -754,6 +777,7 @@ const messages = {
|
|
|
754
777
|
readOnly: "唯讀",
|
|
755
778
|
removeThisSource: "移除此來源",
|
|
756
779
|
renameView: "重新命名檢視",
|
|
780
|
+
resume: "繼續",
|
|
757
781
|
retry: "Retry",
|
|
758
782
|
reviewBeforeTheyReachBuilder: "在傳送到 Builder 前審核。",
|
|
759
783
|
reviewDiff: "審核差異",
|