@agent-native/core 0.164.11 → 0.164.15
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/templates/calendar/.agents/skills/event-management/SKILL.md +84 -0
- package/corpus/templates/calendar/AGENTS.md +5 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -1
- package/corpus/templates/calendar/actions/delete-events.ts +508 -0
- package/corpus/templates/calendar/actions/event-action-helpers.ts +1 -1
- package/corpus/templates/calendar/actions/list-events.ts +53 -0
- package/corpus/templates/calendar/changelog/2026-08-18-the-agent-can-now-remove-many-meetings-at-once-such-as-every.md +6 -0
- package/corpus/templates/calendar/server/lib/event-weekday.ts +112 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +25 -2
- package/corpus/templates/content/actions/_database-utils.ts +2 -0
- package/corpus/templates/content/actions/_local-folder-source.ts +186 -2
- package/corpus/templates/content/actions/connect-local-folder-source.ts +23 -8
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +3 -3
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +2 -2
- package/corpus/templates/content/actions/sync-local-folder-source.ts +130 -12
- package/corpus/templates/content/app/components/LocalFolderLiveSync.tsx +77 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +354 -8
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +23 -1
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +78 -1
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +36 -0
- package/corpus/templates/content/app/components/editor/local-source-write-state.ts +52 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +244 -6
- package/corpus/templates/content/app/components/sidebar/local-source-hierarchy.ts +189 -0
- package/corpus/templates/content/app/hooks/use-create-page.ts +26 -4
- package/corpus/templates/content/app/hooks/use-documents.ts +83 -0
- package/corpus/templates/content/app/i18n-data.ts +79 -0
- package/corpus/templates/content/app/lib/desktop-content-files.ts +57 -1
- package/corpus/templates/content/app/lib/local-content-source-files.ts +210 -34
- package/corpus/templates/content/app/lib/local-folder-live-sync.ts +191 -0
- package/corpus/templates/content/app/root.tsx +2 -1
- package/corpus/templates/content/app/routes/_app.local-files.tsx +103 -38
- package/corpus/templates/content/docs/product/capabilities/content.source.file-folder.md +2 -2
- package/corpus/templates/content/docs/product/features/content.feature.bring-your-local-work.md +3 -3
- package/corpus/templates/content/docs/product/roadmap.md +2 -2
- package/corpus/templates/content/docs/solutions/2026-08-18-content-navigation-coherence-shape.md +482 -0
- package/corpus/templates/content/shared/api.ts +25 -0
- package/corpus/templates/factory/AGENTS.md +2 -1
- package/corpus/templates/factory/actions/get-factory-graph-version.ts +69 -0
- package/corpus/templates/factory/actions/list-factory-graph-versions.ts +82 -0
- package/corpus/templates/factory/actions/restore-factory-graph-version.ts +163 -0
- package/corpus/templates/factory/actions/save-factory-graph.ts +93 -60
- package/corpus/templates/factory/app/components/factory/FactoryHistoryView.tsx +709 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +62 -2
- package/corpus/templates/factory/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +43 -0
- package/corpus/templates/factory/app/routes/factory.tsx +115 -15
- package/corpus/templates/factory/server/plugins/agent-chat.ts +3 -1
- package/dist/app-config/auth.d.ts +4 -0
- package/dist/app-config/auth.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-mcp-config.d.ts +2 -2
- package/dist/cli/code-agent-mcp-config.js +34 -9
- package/dist/client/org/OrgSwitcher.js +8 -4
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp-client/config.d.ts +11 -0
- package/dist/mcp-client/config.js +33 -26
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +15 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/actions-access-control.mdx +136 -2
- package/docs/content/actions-agent-tools.mdx +101 -0
- package/docs/content/actions-defining.mdx +2 -2
- package/docs/content/audit-log.mdx +1 -1
- package/docs/content/authentication.mdx +1 -1
- package/docs/content/cloudflare.mdx +43 -0
- package/docs/content/creating-templates.mdx +1 -1
- package/docs/content/deploy-an-app.mdx +109 -0
- package/docs/content/deployment-environment-variables.mdx +101 -0
- package/docs/content/deployment.mdx +125 -792
- package/docs/content/docs-components.mdx +2 -2
- package/docs/content/environment-variables.mdx +41 -41
- package/docs/content/file-uploads.mdx +74 -40
- package/docs/content/harness-agents.mdx +2 -2
- package/docs/content/internationalization.mdx +61 -65
- package/docs/content/key-concepts.mdx +3 -3
- package/docs/content/multi-app-workspace.mdx +2 -2
- package/docs/content/netlify.mdx +47 -0
- package/docs/content/node-docker.mdx +138 -0
- package/docs/content/organizations-teams-permissions.mdx +5 -5
- package/docs/content/other-platforms.mdx +27 -0
- package/docs/content/ssr-caching.mdx +45 -0
- package/docs/content/template-assets-developers.mdx +2 -2
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +2 -2
- package/docs/content/{local-file-mode.mdx → template-content-local-files.mdx} +2 -1
- package/docs/content/template-content-sync.mdx +2 -2
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +2 -2
- package/docs/content/updating-ui-in-production.mdx +35 -0
- package/docs/content/vercel.mdx +38 -0
- package/docs/content/what-is-agent-native.mdx +1 -1
- package/docs/content/workspace-deployment.mdx +350 -0
- package/package.json +1 -1
- package/docs/content/agents.mdx +0 -9
- package/docs/content/database.mdx +0 -317
- package/docs/content/human-approval.mdx +0 -156
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { ContentDatabaseItem, DocumentProperty } from "@shared/api";
|
|
2
|
+
|
|
3
|
+
const LOCAL_SOURCE_DIRECTORY_PREFIX = "local-source-directory:";
|
|
4
|
+
|
|
5
|
+
function normalizedSourcePath(path: string | undefined) {
|
|
6
|
+
const normalized = (path ?? "").replace(/\\/g, "/").replace(/^\/+/, "");
|
|
7
|
+
if (
|
|
8
|
+
!normalized ||
|
|
9
|
+
normalized.includes("\0") ||
|
|
10
|
+
normalized.split("/").some((part) => !part || part === "." || part === "..")
|
|
11
|
+
) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parentPath(path: string) {
|
|
18
|
+
const separator = path.lastIndexOf("/");
|
|
19
|
+
return separator === -1 ? "" : path.slice(0, separator);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sourceIds(item: ContentDatabaseItem) {
|
|
23
|
+
const value = item.properties.find(
|
|
24
|
+
(property) => property.definition.systemRole === "files_source",
|
|
25
|
+
)?.value;
|
|
26
|
+
return Array.isArray(value)
|
|
27
|
+
? value.filter(
|
|
28
|
+
(candidate): candidate is string =>
|
|
29
|
+
typeof candidate === "string" && candidate !== "local",
|
|
30
|
+
)
|
|
31
|
+
: [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function localSourceItemIdentity(item: ContentDatabaseItem) {
|
|
35
|
+
return sourceIds(item)[0] ?? item.document.source?.rootPath ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function directoryId(sourceIdentity: string, path: string) {
|
|
39
|
+
return `${LOCAL_SOURCE_DIRECTORY_PREFIX}${encodeURIComponent(sourceIdentity)}:${encodeURIComponent(path)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function systemProperties(
|
|
43
|
+
item: ContentDatabaseItem,
|
|
44
|
+
sourceIdentity: string,
|
|
45
|
+
parentId: string | null,
|
|
46
|
+
): DocumentProperty[] {
|
|
47
|
+
return item.properties
|
|
48
|
+
.filter((property) => property.definition.systemRole)
|
|
49
|
+
.map((property) => {
|
|
50
|
+
if (property.definition.systemRole === "files_parent") {
|
|
51
|
+
return { ...property, value: parentId, editable: false };
|
|
52
|
+
}
|
|
53
|
+
if (property.definition.systemRole === "files_source") {
|
|
54
|
+
return { ...property, value: [sourceIdentity], editable: false };
|
|
55
|
+
}
|
|
56
|
+
return { ...property, editable: false };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function directoryItem(
|
|
61
|
+
template: ContentDatabaseItem,
|
|
62
|
+
sourceIdentity: string,
|
|
63
|
+
path: string,
|
|
64
|
+
position: number,
|
|
65
|
+
): ContentDatabaseItem {
|
|
66
|
+
const parent = parentPath(path);
|
|
67
|
+
const parentId = parent ? directoryId(sourceIdentity, parent) : null;
|
|
68
|
+
const id = directoryId(sourceIdentity, path);
|
|
69
|
+
return {
|
|
70
|
+
id,
|
|
71
|
+
databaseId: template.databaseId,
|
|
72
|
+
document: {
|
|
73
|
+
...template.document,
|
|
74
|
+
id,
|
|
75
|
+
parentId,
|
|
76
|
+
title: path.split("/").pop()?.replace(/[-_]+/g, " ") || path,
|
|
77
|
+
content: "",
|
|
78
|
+
description: "",
|
|
79
|
+
icon: null,
|
|
80
|
+
position,
|
|
81
|
+
isFavorite: false,
|
|
82
|
+
hideFromSearch: true,
|
|
83
|
+
accessRole: "viewer",
|
|
84
|
+
canView: true,
|
|
85
|
+
canComment: false,
|
|
86
|
+
canEdit: false,
|
|
87
|
+
canManage: false,
|
|
88
|
+
databaseMembership: undefined,
|
|
89
|
+
source: {
|
|
90
|
+
mode: "local-files",
|
|
91
|
+
kind: "folder",
|
|
92
|
+
path,
|
|
93
|
+
rootPath: template.document.source?.rootPath,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
position,
|
|
97
|
+
properties: systemProperties(template, sourceIdentity, parentId),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface LocalSourceHierarchySelection {
|
|
102
|
+
sourceId?: string | null;
|
|
103
|
+
rootPath?: string | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function firstLocalSourceDocumentId(items: ContentDatabaseItem[]) {
|
|
107
|
+
return items.find(
|
|
108
|
+
(item) =>
|
|
109
|
+
item.document.source?.mode === "local-files" &&
|
|
110
|
+
item.document.source.kind !== "folder",
|
|
111
|
+
)?.document.id;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function projectLocalSourceHierarchy(
|
|
115
|
+
items: ContentDatabaseItem[],
|
|
116
|
+
selection: LocalSourceHierarchySelection = {},
|
|
117
|
+
) {
|
|
118
|
+
const localFiles = items.filter((item) => {
|
|
119
|
+
if (
|
|
120
|
+
item.document.source?.mode !== "local-files" ||
|
|
121
|
+
item.document.source.kind === "folder"
|
|
122
|
+
) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (selection.sourceId) {
|
|
126
|
+
return sourceIds(item).includes(selection.sourceId);
|
|
127
|
+
}
|
|
128
|
+
if (selection.rootPath) {
|
|
129
|
+
return item.document.source.rootPath === selection.rootPath;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
const retained = items.filter(
|
|
134
|
+
(item) => item.document.source?.mode !== "local-files",
|
|
135
|
+
);
|
|
136
|
+
const directories = new Map<
|
|
137
|
+
string,
|
|
138
|
+
{ template: ContentDatabaseItem; sourceIdentity: string; path: string }
|
|
139
|
+
>();
|
|
140
|
+
const projectedFiles: ContentDatabaseItem[] = [];
|
|
141
|
+
|
|
142
|
+
for (const item of localFiles) {
|
|
143
|
+
const path = normalizedSourcePath(item.document.source?.path);
|
|
144
|
+
const sourceIdentity =
|
|
145
|
+
selection.sourceId && sourceIds(item).includes(selection.sourceId)
|
|
146
|
+
? selection.sourceId
|
|
147
|
+
: localSourceItemIdentity(item);
|
|
148
|
+
if (!path || !sourceIdentity) continue;
|
|
149
|
+
const folder = parentPath(path);
|
|
150
|
+
if (folder) {
|
|
151
|
+
const parts = folder.split("/");
|
|
152
|
+
for (let index = 1; index <= parts.length; index += 1) {
|
|
153
|
+
const directoryPath = parts.slice(0, index).join("/");
|
|
154
|
+
directories.set(`${sourceIdentity}\0${directoryPath}`, {
|
|
155
|
+
template: item,
|
|
156
|
+
sourceIdentity,
|
|
157
|
+
path: directoryPath,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const parentId = folder ? directoryId(sourceIdentity, folder) : null;
|
|
162
|
+
projectedFiles.push({
|
|
163
|
+
...item,
|
|
164
|
+
document: { ...item.document, parentId },
|
|
165
|
+
properties: item.properties.map((property) =>
|
|
166
|
+
property.definition.systemRole === "files_parent"
|
|
167
|
+
? { ...property, value: parentId }
|
|
168
|
+
: property,
|
|
169
|
+
),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const projectedDirectories = [...directories.values()]
|
|
174
|
+
.sort((left, right) =>
|
|
175
|
+
left.sourceIdentity === right.sourceIdentity
|
|
176
|
+
? left.path.localeCompare(right.path)
|
|
177
|
+
: left.sourceIdentity.localeCompare(right.sourceIdentity),
|
|
178
|
+
)
|
|
179
|
+
.map((directory, index) =>
|
|
180
|
+
directoryItem(
|
|
181
|
+
directory.template,
|
|
182
|
+
directory.sourceIdentity,
|
|
183
|
+
directory.path,
|
|
184
|
+
index,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return [...retained, ...projectedDirectories, ...projectedFiles];
|
|
189
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Document } from "@shared/api";
|
|
2
2
|
import { useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { useCallback } from "react";
|
|
4
|
-
import { useNavigate } from "react-router";
|
|
4
|
+
import { useLocation, useNavigate } from "react-router";
|
|
5
5
|
import { toast } from "sonner";
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
SELECTED_CONTENT_SPACE_STORAGE_KEY,
|
|
11
11
|
} from "@/components/sidebar/select-content-space";
|
|
12
12
|
import { useContentSpaces } from "@/hooks/use-content-spaces";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
rollbackOptimisticCreatedDocument,
|
|
15
|
+
useCreateDocument,
|
|
16
|
+
} from "@/hooks/use-documents";
|
|
14
17
|
import { useLocalStorage } from "@/hooks/use-local-storage";
|
|
15
18
|
import { documentQueryFilter } from "@/lib/document-query";
|
|
16
19
|
import { markDocumentCreationPending } from "@/lib/optimistic-document";
|
|
@@ -34,6 +37,7 @@ export function useCreatePage(opts?: {
|
|
|
34
37
|
awaitPersist?: boolean;
|
|
35
38
|
}) {
|
|
36
39
|
const navigate = useNavigate();
|
|
40
|
+
const location = useLocation();
|
|
37
41
|
const queryClient = useQueryClient();
|
|
38
42
|
const createDocument = useCreateDocument();
|
|
39
43
|
const contentSpacesQuery = useContentSpaces();
|
|
@@ -78,11 +82,19 @@ export function useCreatePage(opts?: {
|
|
|
78
82
|
createdAt: now,
|
|
79
83
|
updatedAt: now,
|
|
80
84
|
});
|
|
85
|
+
const previousDocuments = queryClient.getQueryData(
|
|
86
|
+
LIST_DOCUMENTS_QUERY_KEY,
|
|
87
|
+
);
|
|
88
|
+
const previousPath = `${location.pathname}${location.search}${location.hash}`;
|
|
81
89
|
|
|
82
90
|
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, (old: any) => {
|
|
83
91
|
const docs: Document[] =
|
|
84
92
|
old?.documents ?? (Array.isArray(old) ? old : []);
|
|
85
|
-
|
|
93
|
+
if (Array.isArray(old)) return [...docs, tempDoc];
|
|
94
|
+
return {
|
|
95
|
+
...(old && typeof old === "object" ? old : {}),
|
|
96
|
+
documents: [...docs, tempDoc],
|
|
97
|
+
};
|
|
86
98
|
});
|
|
87
99
|
queryClient.setQueryData(["action", "get-document", { id }], tempDoc);
|
|
88
100
|
|
|
@@ -111,11 +123,18 @@ export function useCreatePage(opts?: {
|
|
|
111
123
|
};
|
|
112
124
|
|
|
113
125
|
const onPersistError = (err: unknown) => {
|
|
126
|
+
rollbackOptimisticCreatedDocument(
|
|
127
|
+
queryClient,
|
|
128
|
+
id,
|
|
129
|
+
previousDocuments !== undefined,
|
|
130
|
+
);
|
|
114
131
|
queryClient.invalidateQueries({
|
|
115
132
|
queryKey: ["action", "list-documents"],
|
|
116
133
|
});
|
|
117
134
|
queryClient.removeQueries(documentQueryFilter(id));
|
|
118
|
-
if (shouldNavigate)
|
|
135
|
+
if (shouldNavigate) {
|
|
136
|
+
navigate(previousPath, { replace: true, flushSync: true });
|
|
137
|
+
}
|
|
119
138
|
toast.error("Failed to create page", {
|
|
120
139
|
description:
|
|
121
140
|
err instanceof Error ? err.message : "Something went wrong",
|
|
@@ -137,6 +156,9 @@ export function useCreatePage(opts?: {
|
|
|
137
156
|
},
|
|
138
157
|
[
|
|
139
158
|
createDocument,
|
|
159
|
+
location.hash,
|
|
160
|
+
location.pathname,
|
|
161
|
+
location.search,
|
|
140
162
|
navigate,
|
|
141
163
|
onAfterNavigate,
|
|
142
164
|
queryClient,
|
|
@@ -66,6 +66,89 @@ export const LIST_DOCUMENTS_QUERY_KEY = [
|
|
|
66
66
|
undefined,
|
|
67
67
|
] as const;
|
|
68
68
|
|
|
69
|
+
export function restoreListDocumentsSnapshot(
|
|
70
|
+
queryClient: Pick<QueryClient, "removeQueries" | "setQueryData">,
|
|
71
|
+
snapshot: unknown,
|
|
72
|
+
) {
|
|
73
|
+
if (snapshot === undefined) {
|
|
74
|
+
queryClient.removeQueries({ queryKey: LIST_DOCUMENTS_QUERY_KEY });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, snapshot);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function rollbackOptimisticCreatedDocument(
|
|
81
|
+
queryClient: Pick<
|
|
82
|
+
QueryClient,
|
|
83
|
+
"getQueryData" | "removeQueries" | "setQueryData"
|
|
84
|
+
>,
|
|
85
|
+
documentId: string,
|
|
86
|
+
hadListSnapshot: boolean,
|
|
87
|
+
) {
|
|
88
|
+
const current = queryClient.getQueryData(LIST_DOCUMENTS_QUERY_KEY);
|
|
89
|
+
const documents: Document[] = Array.isArray(current)
|
|
90
|
+
? current
|
|
91
|
+
: ((current as DocumentListResponse | undefined)?.documents ?? []);
|
|
92
|
+
const remaining = documents.filter((document) => document.id !== documentId);
|
|
93
|
+
|
|
94
|
+
if (!hadListSnapshot && remaining.length === 0) {
|
|
95
|
+
queryClient.removeQueries({ queryKey: LIST_DOCUMENTS_QUERY_KEY });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (Array.isArray(current)) {
|
|
100
|
+
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, remaining);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, {
|
|
105
|
+
...(current && typeof current === "object" ? current : {}),
|
|
106
|
+
documents: remaining,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function restoreDeletedDocumentSnapshots(
|
|
111
|
+
queryClient: Pick<QueryClient, "getQueryData" | "setQueryData">,
|
|
112
|
+
listSnapshot: unknown,
|
|
113
|
+
documentSnapshots: Array<[readonly unknown[], unknown]>,
|
|
114
|
+
deletedDocumentIds: Iterable<string>,
|
|
115
|
+
) {
|
|
116
|
+
const deletedIds = new Set(deletedDocumentIds);
|
|
117
|
+
const snapshotDocuments: Document[] = Array.isArray(listSnapshot)
|
|
118
|
+
? listSnapshot
|
|
119
|
+
: ((listSnapshot as DocumentListResponse | undefined)?.documents ?? []);
|
|
120
|
+
const current = queryClient.getQueryData(LIST_DOCUMENTS_QUERY_KEY);
|
|
121
|
+
const currentDocuments: Document[] = Array.isArray(current)
|
|
122
|
+
? current
|
|
123
|
+
: ((current as DocumentListResponse | undefined)?.documents ?? []);
|
|
124
|
+
const currentDocumentIds = new Set(
|
|
125
|
+
currentDocuments.map((document) => document.id),
|
|
126
|
+
);
|
|
127
|
+
const restoredDocuments = snapshotDocuments.filter(
|
|
128
|
+
(document) =>
|
|
129
|
+
deletedIds.has(document.id) && !currentDocumentIds.has(document.id),
|
|
130
|
+
);
|
|
131
|
+
const documents = [...currentDocuments, ...restoredDocuments];
|
|
132
|
+
|
|
133
|
+
if (Array.isArray(current)) {
|
|
134
|
+
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, documents);
|
|
135
|
+
} else {
|
|
136
|
+
queryClient.setQueryData(LIST_DOCUMENTS_QUERY_KEY, {
|
|
137
|
+
...(current && typeof current === "object"
|
|
138
|
+
? current
|
|
139
|
+
: listSnapshot && typeof listSnapshot === "object"
|
|
140
|
+
? listSnapshot
|
|
141
|
+
: {}),
|
|
142
|
+
documents,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
for (const [queryKey, data] of documentSnapshots) {
|
|
146
|
+
if (queryClient.getQueryData(queryKey) === undefined) {
|
|
147
|
+
queryClient.setQueryData(queryKey, data);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
69
152
|
const DOCUMENT_LIST_PAGE_SIZE = 200;
|
|
70
153
|
|
|
71
154
|
export async function fetchCompleteDocumentList(
|
|
@@ -2876,6 +2876,7 @@ const editorToolbarMessages = {
|
|
|
2876
2876
|
pushFailed: "Push failed.",
|
|
2877
2877
|
pushLocal: "Push local",
|
|
2878
2878
|
pushToNotion: "Push to Notion",
|
|
2879
|
+
redo: "Redo",
|
|
2879
2880
|
revealInFinder: "Reveal in Finder",
|
|
2880
2881
|
revealedLocalFile: "Revealed local file",
|
|
2881
2882
|
searchNotionPages: "Search Notion pages...",
|
|
@@ -2896,6 +2897,7 @@ const editorToolbarMessages = {
|
|
|
2896
2897
|
disconnectedNotionWorkspace: "Disconnected Notion workspace.",
|
|
2897
2898
|
unlinkFailed: "Unlink failed.",
|
|
2898
2899
|
unlinkedFromNotion: "Unlinked from Notion.",
|
|
2900
|
+
undo: "Undo",
|
|
2899
2901
|
versionHistory: "Version history",
|
|
2900
2902
|
};
|
|
2901
2903
|
|
|
@@ -3153,6 +3155,13 @@ const enUS = {
|
|
|
3153
3155
|
"This Blocks field changed elsewhere. Your edit wasn't saved; the latest version is now shown.",
|
|
3154
3156
|
couldNotReadLocalSourceFile: "Could not read local source file",
|
|
3155
3157
|
couldNotSaveLocalFile: "Could not save local file",
|
|
3158
|
+
localFileChangedWithUnsavedEdits:
|
|
3159
|
+
"The file changed on disk while this page had unsaved edits.",
|
|
3160
|
+
localFileReadOnlySnapshot:
|
|
3161
|
+
"Read-only snapshot · Last synced from {{device}} at {{date}}",
|
|
3162
|
+
copyUnsavedText: "Copy my unsaved text",
|
|
3163
|
+
unsavedTextCopied: "Unsaved text copied",
|
|
3164
|
+
useDiskVersion: "Use disk version",
|
|
3156
3165
|
collabConnectingReadOnly:
|
|
3157
3166
|
"Connecting live editor. Showing a read-only snapshot.",
|
|
3158
3167
|
liveDocumentSaveBeforeSyncFailed:
|
|
@@ -3497,10 +3506,17 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
3497
3506
|
editor: {
|
|
3498
3507
|
couldNotReadLocalSourceFile: "无法读取本地源文件",
|
|
3499
3508
|
couldNotSaveLocalFile: "无法保存本地文件",
|
|
3509
|
+
localFileChangedWithUnsavedEdits:
|
|
3510
|
+
"此页面有未保存的编辑时,磁盘上的文件已更改。",
|
|
3511
|
+
copyUnsavedText: "复制我未保存的文本",
|
|
3512
|
+
unsavedTextCopied: "已复制未保存的文本",
|
|
3513
|
+
useDiskVersion: "使用磁盘版本",
|
|
3500
3514
|
documentTitle: "文档标题",
|
|
3501
3515
|
localFileSavedHistoryNotUpdated: "本地文件已保存,但历史记录未更新",
|
|
3502
3516
|
title: "标题",
|
|
3503
3517
|
toolbar: {
|
|
3518
|
+
undo: "撤销",
|
|
3519
|
+
redo: "重做",
|
|
3504
3520
|
conflict: "冲突",
|
|
3505
3521
|
conflictResolutionFailed: "冲突解决失败。",
|
|
3506
3522
|
conflictResolvedFromLocalDocument: "已从本本文档解决冲突。",
|
|
@@ -3725,6 +3741,11 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
3725
3741
|
editor: {
|
|
3726
3742
|
couldNotReadLocalSourceFile: "No se pudo leer el archivo local de origen",
|
|
3727
3743
|
couldNotSaveLocalFile: "No se pudo guardar el archivo local",
|
|
3744
|
+
localFileChangedWithUnsavedEdits:
|
|
3745
|
+
"El archivo del disco cambió mientras esta página tenía ediciones sin guardar.",
|
|
3746
|
+
copyUnsavedText: "Copiar mi texto sin guardar",
|
|
3747
|
+
unsavedTextCopied: "Texto sin guardar copiado",
|
|
3748
|
+
useDiskVersion: "Usar la versión del disco",
|
|
3728
3749
|
documentTitle: "Título del documento",
|
|
3729
3750
|
localFileSavedHistoryNotUpdated:
|
|
3730
3751
|
"El archivo local se guardó, pero el historial no se actualizó",
|
|
@@ -3791,6 +3812,8 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
3791
3812
|
"Transcribe este video y agrega la transcripción debajo.",
|
|
3792
3813
|
},
|
|
3793
3814
|
toolbar: {
|
|
3815
|
+
undo: "Deshacer",
|
|
3816
|
+
redo: "Rehacer",
|
|
3794
3817
|
conflict: "Conflicto",
|
|
3795
3818
|
conflictResolutionFailed: "Error al resolver el conflicto.",
|
|
3796
3819
|
conflictResolvedFromLocalDocument:
|
|
@@ -3929,6 +3952,14 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
3929
3952
|
},
|
|
3930
3953
|
},
|
|
3931
3954
|
"fr-FR": {
|
|
3955
|
+
editor: {
|
|
3956
|
+
localFileChangedWithUnsavedEdits:
|
|
3957
|
+
"Le fichier sur le disque a changé alors que cette page contenait des modifications non enregistrées.",
|
|
3958
|
+
copyUnsavedText: "Copier mon texte non enregistré",
|
|
3959
|
+
unsavedTextCopied: "Texte non enregistré copié",
|
|
3960
|
+
useDiskVersion: "Utiliser la version du disque",
|
|
3961
|
+
toolbar: { undo: "Annuler", redo: "Rétablir" },
|
|
3962
|
+
},
|
|
3932
3963
|
sidebar: {
|
|
3933
3964
|
addChild: "Ajouter un enfant",
|
|
3934
3965
|
addChildTo: "Ajouter un enfant à {{title}}",
|
|
@@ -3965,6 +3996,14 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
3965
3996
|
},
|
|
3966
3997
|
},
|
|
3967
3998
|
"de-DE": {
|
|
3999
|
+
editor: {
|
|
4000
|
+
localFileChangedWithUnsavedEdits:
|
|
4001
|
+
"Die Datei auf dem Datenträger wurde geändert, während diese Seite ungespeicherte Änderungen enthielt.",
|
|
4002
|
+
copyUnsavedText: "Meinen ungespeicherten Text kopieren",
|
|
4003
|
+
unsavedTextCopied: "Ungespeicherter Text kopiert",
|
|
4004
|
+
useDiskVersion: "Version vom Datenträger verwenden",
|
|
4005
|
+
toolbar: { undo: "Rückgängig", redo: "Wiederholen" },
|
|
4006
|
+
},
|
|
3968
4007
|
sidebar: {
|
|
3969
4008
|
addChild: "Unterelement hinzufügen",
|
|
3970
4009
|
addChildTo: "Unterelement zu {{title}} hinzufügen",
|
|
@@ -4000,6 +4039,14 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
4000
4039
|
},
|
|
4001
4040
|
},
|
|
4002
4041
|
"ja-JP": {
|
|
4042
|
+
editor: {
|
|
4043
|
+
localFileChangedWithUnsavedEdits:
|
|
4044
|
+
"このページに未保存の編集がある間に、ディスク上のファイルが変更されました。",
|
|
4045
|
+
copyUnsavedText: "未保存のテキストをコピー",
|
|
4046
|
+
unsavedTextCopied: "未保存のテキストをコピーしました",
|
|
4047
|
+
useDiskVersion: "ディスク上の版を使用",
|
|
4048
|
+
toolbar: { undo: "元に戻す", redo: "やり直す" },
|
|
4049
|
+
},
|
|
4003
4050
|
sidebar: {
|
|
4004
4051
|
addChild: "子項目を追加",
|
|
4005
4052
|
addChildTo: "{{title}} に子項目を追加",
|
|
@@ -4033,6 +4080,14 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
4033
4080
|
},
|
|
4034
4081
|
},
|
|
4035
4082
|
"ko-KR": {
|
|
4083
|
+
editor: {
|
|
4084
|
+
localFileChangedWithUnsavedEdits:
|
|
4085
|
+
"이 페이지에 저장하지 않은 편집 내용이 있는 동안 디스크의 파일이 변경되었습니다.",
|
|
4086
|
+
copyUnsavedText: "저장하지 않은 텍스트 복사",
|
|
4087
|
+
unsavedTextCopied: "저장하지 않은 텍스트를 복사했습니다",
|
|
4088
|
+
useDiskVersion: "디스크 버전 사용",
|
|
4089
|
+
toolbar: { undo: "실행 취소", redo: "다시 실행" },
|
|
4090
|
+
},
|
|
4036
4091
|
sidebar: {
|
|
4037
4092
|
addChild: "하위 항목 추가",
|
|
4038
4093
|
addChildTo: "{{title}}에 하위 항목 추가",
|
|
@@ -4065,6 +4120,14 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
4065
4120
|
},
|
|
4066
4121
|
},
|
|
4067
4122
|
"pt-BR": {
|
|
4123
|
+
editor: {
|
|
4124
|
+
localFileChangedWithUnsavedEdits:
|
|
4125
|
+
"O arquivo no disco foi alterado enquanto esta página tinha edições não salvas.",
|
|
4126
|
+
copyUnsavedText: "Copiar meu texto não salvo",
|
|
4127
|
+
unsavedTextCopied: "Texto não salvo copiado",
|
|
4128
|
+
useDiskVersion: "Usar versão do disco",
|
|
4129
|
+
toolbar: { undo: "Desfazer", redo: "Refazer" },
|
|
4130
|
+
},
|
|
4068
4131
|
sidebar: {
|
|
4069
4132
|
addChild: "Adicionar filho",
|
|
4070
4133
|
addChildTo: "Adicionar filho a {{title}}",
|
|
@@ -4099,6 +4162,14 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
4099
4162
|
},
|
|
4100
4163
|
},
|
|
4101
4164
|
"hi-IN": {
|
|
4165
|
+
editor: {
|
|
4166
|
+
localFileChangedWithUnsavedEdits:
|
|
4167
|
+
"इस पेज में सहेजे नहीं गए बदलाव होने के दौरान डिस्क की फ़ाइल बदल गई।",
|
|
4168
|
+
copyUnsavedText: "मेरा सहेजा नहीं गया टेक्स्ट कॉपी करें",
|
|
4169
|
+
unsavedTextCopied: "सहेजा नहीं गया टेक्स्ट कॉपी किया गया",
|
|
4170
|
+
useDiskVersion: "डिस्क वाला संस्करण उपयोग करें",
|
|
4171
|
+
toolbar: { undo: "पूर्ववत करें", redo: "फिर से करें" },
|
|
4172
|
+
},
|
|
4102
4173
|
sidebar: {
|
|
4103
4174
|
addChild: "चाइल्ड जोड़ें",
|
|
4104
4175
|
addChildTo: "{{title}} में चाइल्ड जोड़ें",
|
|
@@ -4131,6 +4202,14 @@ const rawLiteralLocaleMessages: Partial<Record<LocaleCode, PartialMessages>> = {
|
|
|
4131
4202
|
},
|
|
4132
4203
|
},
|
|
4133
4204
|
"ar-SA": {
|
|
4205
|
+
editor: {
|
|
4206
|
+
localFileChangedWithUnsavedEdits:
|
|
4207
|
+
"تغيّر الملف على القرص أثناء وجود تعديلات غير محفوظة في هذه الصفحة.",
|
|
4208
|
+
copyUnsavedText: "نسخ النص غير المحفوظ",
|
|
4209
|
+
unsavedTextCopied: "تم نسخ النص غير المحفوظ",
|
|
4210
|
+
useDiskVersion: "استخدام نسخة القرص",
|
|
4211
|
+
toolbar: { undo: "تراجع", redo: "إعادة" },
|
|
4212
|
+
},
|
|
4134
4213
|
sidebar: {
|
|
4135
4214
|
addChild: "إضافة عنصر فرعي",
|
|
4136
4215
|
addChildTo: "إضافة عنصر فرعي إلى {{title}}",
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
export interface DesktopContentFilesFolder {
|
|
2
2
|
id?: string;
|
|
3
3
|
name: string;
|
|
4
|
-
|
|
4
|
+
kind?: "persistent" | "temporary";
|
|
5
|
+
repository?: {
|
|
6
|
+
localId: string;
|
|
7
|
+
branch?: string;
|
|
8
|
+
commit?: string;
|
|
9
|
+
detached?: boolean;
|
|
10
|
+
};
|
|
11
|
+
contentSource?: {
|
|
12
|
+
sourceId: string;
|
|
13
|
+
databaseId?: string;
|
|
14
|
+
};
|
|
5
15
|
sourcePrefix?: string;
|
|
6
16
|
updatedAt?: string;
|
|
7
17
|
}
|
|
@@ -10,6 +20,27 @@ export interface DesktopContentFilesFolderRequest {
|
|
|
10
20
|
folderId?: string;
|
|
11
21
|
}
|
|
12
22
|
|
|
23
|
+
/** Opaque content fingerprint returned by the trusted local bridge. */
|
|
24
|
+
export type DesktopContentFileRevision = string;
|
|
25
|
+
|
|
26
|
+
export interface DesktopContentFileConflict {
|
|
27
|
+
path: string;
|
|
28
|
+
expectedRevision?: string | null;
|
|
29
|
+
actualRevision?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type DesktopContentFilesChange = {
|
|
33
|
+
folderId: string;
|
|
34
|
+
revision: string;
|
|
35
|
+
changedAt: string;
|
|
36
|
+
missing?: boolean;
|
|
37
|
+
reason?: "attached" | "changed" | "missing";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type DesktopContentFilesWatchResult =
|
|
41
|
+
| { ok: true; unsubscribe(): void }
|
|
42
|
+
| { ok: false; error: string; unavailable?: boolean };
|
|
43
|
+
|
|
13
44
|
export type DesktopContentFilesResult =
|
|
14
45
|
| {
|
|
15
46
|
ok: true;
|
|
@@ -17,6 +48,8 @@ export type DesktopContentFilesResult =
|
|
|
17
48
|
folders?: DesktopContentFilesFolder[];
|
|
18
49
|
files?: string[];
|
|
19
50
|
sources?: Record<string, string>;
|
|
51
|
+
revisions?: Record<string, DesktopContentFileRevision>;
|
|
52
|
+
identities?: Record<string, string>;
|
|
20
53
|
controlResources?: Record<string, string>;
|
|
21
54
|
}
|
|
22
55
|
| {
|
|
@@ -25,6 +58,8 @@ export type DesktopContentFilesResult =
|
|
|
25
58
|
canceled?: boolean;
|
|
26
59
|
folder?: DesktopContentFilesFolder;
|
|
27
60
|
folders?: DesktopContentFilesFolder[];
|
|
61
|
+
code?: "conflict" | "unavailable" | "invalid-request";
|
|
62
|
+
conflict?: DesktopContentFileConflict;
|
|
28
63
|
};
|
|
29
64
|
|
|
30
65
|
export interface DesktopContentFilesApi {
|
|
@@ -32,18 +67,27 @@ export interface DesktopContentFilesApi {
|
|
|
32
67
|
request?: DesktopContentFilesFolderRequest,
|
|
33
68
|
): Promise<DesktopContentFilesResult>;
|
|
34
69
|
chooseFolder(): Promise<DesktopContentFilesResult>;
|
|
70
|
+
associateSource?(request: {
|
|
71
|
+
folderId: string;
|
|
72
|
+
sourceId: string;
|
|
73
|
+
databaseId?: string;
|
|
74
|
+
}): Promise<DesktopContentFilesResult>;
|
|
35
75
|
writeFiles(request: {
|
|
36
76
|
folderId?: string;
|
|
37
77
|
files: Record<string, string>;
|
|
78
|
+
expectedRevisions: Record<string, string | null>;
|
|
38
79
|
}): Promise<DesktopContentFilesResult>;
|
|
39
80
|
writeFile(request: {
|
|
40
81
|
folderId?: string;
|
|
41
82
|
path: string;
|
|
42
83
|
content: string;
|
|
84
|
+
/** The revision observed by the editor; a mismatch must not overwrite. */
|
|
85
|
+
expectedRevision: string | null;
|
|
43
86
|
}): Promise<DesktopContentFilesResult>;
|
|
44
87
|
deleteFile?(request: {
|
|
45
88
|
folderId?: string;
|
|
46
89
|
path: string;
|
|
90
|
+
expectedRevision: string;
|
|
47
91
|
}): Promise<DesktopContentFilesResult>;
|
|
48
92
|
readFiles(
|
|
49
93
|
request?: DesktopContentFilesFolderRequest,
|
|
@@ -55,6 +99,18 @@ export interface DesktopContentFilesApi {
|
|
|
55
99
|
clearFolder(
|
|
56
100
|
request?: DesktopContentFilesFolderRequest,
|
|
57
101
|
): Promise<DesktopContentFilesResult>;
|
|
102
|
+
subscribeChanges?(
|
|
103
|
+
request: DesktopContentFilesFolderRequest,
|
|
104
|
+
): Promise<DesktopContentFilesResult>;
|
|
105
|
+
unsubscribeChanges?(
|
|
106
|
+
request: DesktopContentFilesFolderRequest,
|
|
107
|
+
): Promise<DesktopContentFilesResult>;
|
|
108
|
+
onChange?(callback: (change: DesktopContentFilesChange) => void): () => void;
|
|
109
|
+
/** Optional until the installed Desktop bridge supports live folder events. */
|
|
110
|
+
watchFiles?(
|
|
111
|
+
request: DesktopContentFilesFolderRequest,
|
|
112
|
+
onChange: (change: DesktopContentFilesChange) => void,
|
|
113
|
+
): Promise<DesktopContentFilesWatchResult>;
|
|
58
114
|
}
|
|
59
115
|
|
|
60
116
|
type WindowWithAgentNativeDesktop = Window & {
|