@agent-native/core 0.176.5-nightly-20260903175350 → 0.176.5
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/CHANGELOG.md +18 -0
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +13 -7
- package/corpus/templates/content/CHANGELOG.md +1 -0
- package/corpus/templates/content/actions/_collection-export.ts +925 -0
- package/corpus/templates/content/actions/export-document.ts +79 -255
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +39 -38
- package/corpus/templates/content/app/components/editor/database/DatabaseExportDialog.tsx +444 -113
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +4 -0
- package/corpus/templates/content/app/i18n-data.ts +99 -0
- package/corpus/templates/content/docs/product/capabilities/content.portability.collection-export.md +139 -0
- package/corpus/templates/content/docs/product/encyclopedia.md +12 -8
- package/corpus/templates/content/docs/product/features/content.feature.take-the-whole-vault-with-you.md +5 -1
- package/corpus/templates/content/docs/product/roadmap.md +1 -1
- package/corpus/templates/content/shared/database-collection-export.ts +161 -0
- package/corpus/templates/content/shared/database-csv-export.ts +6 -3
- package/corpus/templates/design/CHANGELOG.md +1 -0
- package/corpus/templates/factory/CHANGELOG.md +20 -0
- package/corpus/templates/mail/CHANGELOG.md +12 -0
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +9 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/realtime-token.d.ts +1 -1
- package/package.json +3 -3
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
actionErrorMessage,
|
|
3
|
+
useActionMutation,
|
|
4
|
+
} from "@agent-native/core/client/hooks";
|
|
2
5
|
import { useT } from "@agent-native/core/client/i18n";
|
|
3
6
|
import type {
|
|
4
7
|
ContentDatabaseFilter,
|
|
@@ -7,7 +10,6 @@ import type {
|
|
|
7
10
|
} from "@shared/api";
|
|
8
11
|
import { IconDownload, IconLoader2 } from "@tabler/icons-react";
|
|
9
12
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
-
import { toast } from "sonner";
|
|
11
13
|
|
|
12
14
|
import { Button } from "@/components/ui/button";
|
|
13
15
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
} from "@/components/ui/dialog";
|
|
21
23
|
import { cn } from "@/lib/utils";
|
|
22
24
|
|
|
25
|
+
export type DatabaseExportFormat = "csv" | "markdown" | "html" | "pdf";
|
|
23
26
|
export type DatabaseExportScopeKind = "all_members" | "current_view";
|
|
24
27
|
|
|
25
28
|
export interface DatabaseExportProperty {
|
|
@@ -27,6 +30,7 @@ export interface DatabaseExportProperty {
|
|
|
27
30
|
name: string;
|
|
28
31
|
type: string;
|
|
29
32
|
visible: boolean;
|
|
33
|
+
primaryBody?: boolean;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export interface DatabaseExportContext {
|
|
@@ -41,13 +45,39 @@ export interface DatabaseExportContext {
|
|
|
41
45
|
properties: DatabaseExportProperty[];
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
interface
|
|
48
|
+
export interface DatabaseExportSelection {
|
|
49
|
+
propertyIds: string[];
|
|
50
|
+
includePrimaryBody: boolean;
|
|
51
|
+
blockPropertyIds: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type DatabaseExportSelections = Record<
|
|
55
|
+
DatabaseExportFormat,
|
|
56
|
+
DatabaseExportSelection
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
export interface CollectionExportArchiveFile {
|
|
60
|
+
path: string;
|
|
61
|
+
content: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface CollectionExportResult {
|
|
45
65
|
filename: string;
|
|
46
66
|
mimeType: string;
|
|
47
67
|
content: string;
|
|
68
|
+
format: DatabaseExportFormat;
|
|
69
|
+
print: boolean;
|
|
70
|
+
archiveFiles?: CollectionExportArchiveFile[];
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
|
|
73
|
+
const EXPORT_FORMATS: DatabaseExportFormat[] = [
|
|
74
|
+
"csv",
|
|
75
|
+
"markdown",
|
|
76
|
+
"html",
|
|
77
|
+
"pdf",
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export function defaultDatabaseExportPropertyIds(
|
|
51
81
|
properties: DatabaseExportProperty[],
|
|
52
82
|
) {
|
|
53
83
|
return properties
|
|
@@ -55,6 +85,31 @@ export function defaultDatabaseCsvPropertyIds(
|
|
|
55
85
|
.map((property) => property.id);
|
|
56
86
|
}
|
|
57
87
|
|
|
88
|
+
export function additionalDatabaseExportBlockProperties(
|
|
89
|
+
properties: DatabaseExportProperty[],
|
|
90
|
+
) {
|
|
91
|
+
return properties.filter(
|
|
92
|
+
(property) => property.type === "blocks" && !property.primaryBody,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function defaultDatabaseExportSelections(
|
|
97
|
+
properties: DatabaseExportProperty[],
|
|
98
|
+
): DatabaseExportSelections {
|
|
99
|
+
const propertyIds = defaultDatabaseExportPropertyIds(properties);
|
|
100
|
+
const selection = (includePrimaryBody: boolean) => ({
|
|
101
|
+
propertyIds: [...propertyIds],
|
|
102
|
+
includePrimaryBody,
|
|
103
|
+
blockPropertyIds: [],
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
csv: selection(false),
|
|
107
|
+
markdown: selection(true),
|
|
108
|
+
html: selection(true),
|
|
109
|
+
pdf: selection(true),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
58
113
|
export function shouldInitializeDatabaseExportDialog(
|
|
59
114
|
wasOpen: boolean,
|
|
60
115
|
open: boolean,
|
|
@@ -62,15 +117,27 @@ export function shouldInitializeDatabaseExportDialog(
|
|
|
62
117
|
return open && !wasOpen;
|
|
63
118
|
}
|
|
64
119
|
|
|
65
|
-
export function
|
|
120
|
+
export function updateDatabaseExportSelection(
|
|
121
|
+
selections: DatabaseExportSelections,
|
|
122
|
+
format: DatabaseExportFormat,
|
|
123
|
+
patch: Partial<DatabaseExportSelection>,
|
|
124
|
+
) {
|
|
125
|
+
return {
|
|
126
|
+
...selections,
|
|
127
|
+
[format]: { ...selections[format], ...patch },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function databaseExportRequest(args: {
|
|
66
132
|
id: string;
|
|
133
|
+
format: DatabaseExportFormat;
|
|
67
134
|
context: DatabaseExportContext;
|
|
68
135
|
scope: DatabaseExportScopeKind;
|
|
69
|
-
|
|
136
|
+
selection: DatabaseExportSelection;
|
|
70
137
|
}) {
|
|
71
138
|
return {
|
|
72
139
|
id: args.id,
|
|
73
|
-
format:
|
|
140
|
+
format: args.format,
|
|
74
141
|
collection: {
|
|
75
142
|
scope:
|
|
76
143
|
args.scope === "current_view"
|
|
@@ -80,137 +147,363 @@ export function databaseCsvRequest(args: {
|
|
|
80
147
|
query: args.context.query,
|
|
81
148
|
}
|
|
82
149
|
: { kind: "all_members" as const },
|
|
83
|
-
propertyIds: args.propertyIds,
|
|
150
|
+
propertyIds: args.selection.propertyIds,
|
|
151
|
+
includePrimaryBody: args.selection.includePrimaryBody,
|
|
152
|
+
blockPropertyIds: args.selection.blockPropertyIds,
|
|
84
153
|
},
|
|
85
154
|
};
|
|
86
155
|
}
|
|
87
156
|
|
|
88
|
-
function
|
|
89
|
-
|
|
157
|
+
function crc32(bytes: Uint8Array) {
|
|
158
|
+
let crc = 0xffffffff;
|
|
159
|
+
for (const byte of bytes) {
|
|
160
|
+
crc ^= byte;
|
|
161
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
162
|
+
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function buildStoreOnlyZip(files: CollectionExportArchiveFile[]) {
|
|
169
|
+
const encoder = new TextEncoder();
|
|
170
|
+
const localParts: Uint8Array[] = [];
|
|
171
|
+
const centralParts: Uint8Array[] = [];
|
|
172
|
+
let offset = 0;
|
|
173
|
+
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
const name = encoder.encode(file.path);
|
|
176
|
+
const content = encoder.encode(file.content);
|
|
177
|
+
const checksum = crc32(content);
|
|
178
|
+
const local = new Uint8Array(30);
|
|
179
|
+
const localView = new DataView(local.buffer);
|
|
180
|
+
localView.setUint32(0, 0x04034b50, true);
|
|
181
|
+
localView.setUint16(4, 20, true);
|
|
182
|
+
localView.setUint16(6, 0x0800, true);
|
|
183
|
+
localView.setUint16(12, 0x0021, true);
|
|
184
|
+
localView.setUint32(14, checksum, true);
|
|
185
|
+
localView.setUint32(18, content.length, true);
|
|
186
|
+
localView.setUint32(22, content.length, true);
|
|
187
|
+
localView.setUint16(26, name.length, true);
|
|
188
|
+
localParts.push(local, name, content);
|
|
189
|
+
|
|
190
|
+
const central = new Uint8Array(46);
|
|
191
|
+
const centralView = new DataView(central.buffer);
|
|
192
|
+
centralView.setUint32(0, 0x02014b50, true);
|
|
193
|
+
centralView.setUint16(4, 20, true);
|
|
194
|
+
centralView.setUint16(6, 20, true);
|
|
195
|
+
centralView.setUint16(8, 0x0800, true);
|
|
196
|
+
centralView.setUint16(14, 0x0021, true);
|
|
197
|
+
centralView.setUint32(16, checksum, true);
|
|
198
|
+
centralView.setUint32(20, content.length, true);
|
|
199
|
+
centralView.setUint32(24, content.length, true);
|
|
200
|
+
centralView.setUint16(28, name.length, true);
|
|
201
|
+
centralView.setUint32(42, offset, true);
|
|
202
|
+
centralParts.push(central, name);
|
|
203
|
+
offset += local.length + name.length + content.length;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const centralOffset = offset;
|
|
207
|
+
const centralSize = centralParts.reduce((sum, part) => sum + part.length, 0);
|
|
208
|
+
const end = new Uint8Array(22);
|
|
209
|
+
const endView = new DataView(end.buffer);
|
|
210
|
+
endView.setUint32(0, 0x06054b50, true);
|
|
211
|
+
endView.setUint16(8, files.length, true);
|
|
212
|
+
endView.setUint16(10, files.length, true);
|
|
213
|
+
endView.setUint32(12, centralSize, true);
|
|
214
|
+
endView.setUint32(16, centralOffset, true);
|
|
215
|
+
|
|
216
|
+
const output = new Uint8Array(centralOffset + centralSize + end.length);
|
|
217
|
+
let writeOffset = 0;
|
|
218
|
+
for (const part of [...localParts, ...centralParts, end]) {
|
|
219
|
+
output.set(part, writeOffset);
|
|
220
|
+
writeOffset += part.length;
|
|
221
|
+
}
|
|
222
|
+
return output;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function downloadBlob(blob: Blob, filename: string) {
|
|
90
226
|
const url = URL.createObjectURL(blob);
|
|
91
227
|
const link = window.document.createElement("a");
|
|
92
228
|
link.href = url;
|
|
93
|
-
link.download =
|
|
229
|
+
link.download = filename;
|
|
94
230
|
window.document.body.appendChild(link);
|
|
95
231
|
link.click();
|
|
96
232
|
link.remove();
|
|
97
233
|
URL.revokeObjectURL(url);
|
|
98
234
|
}
|
|
99
235
|
|
|
236
|
+
function printExportHtml(result: CollectionExportResult) {
|
|
237
|
+
const iframe = window.document.createElement("iframe");
|
|
238
|
+
const url = URL.createObjectURL(
|
|
239
|
+
new Blob([result.content], { type: "text/html;charset=utf-8" }),
|
|
240
|
+
);
|
|
241
|
+
iframe.setAttribute("aria-hidden", "true");
|
|
242
|
+
iframe.style.position = "fixed";
|
|
243
|
+
iframe.style.insetInlineEnd = "0";
|
|
244
|
+
iframe.style.bottom = "0";
|
|
245
|
+
iframe.style.width = "0";
|
|
246
|
+
iframe.style.height = "0";
|
|
247
|
+
iframe.style.border = "0";
|
|
248
|
+
let cleanedUp = false;
|
|
249
|
+
const cleanup = () => {
|
|
250
|
+
if (cleanedUp) return;
|
|
251
|
+
cleanedUp = true;
|
|
252
|
+
URL.revokeObjectURL(url);
|
|
253
|
+
iframe.remove();
|
|
254
|
+
};
|
|
255
|
+
iframe.addEventListener("load", () => {
|
|
256
|
+
const frameWindow = iframe.contentWindow;
|
|
257
|
+
if (!frameWindow) {
|
|
258
|
+
cleanup();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
frameWindow.addEventListener(
|
|
262
|
+
"afterprint",
|
|
263
|
+
() => window.setTimeout(cleanup, 500),
|
|
264
|
+
{ once: true },
|
|
265
|
+
);
|
|
266
|
+
frameWindow.focus();
|
|
267
|
+
frameWindow.print();
|
|
268
|
+
});
|
|
269
|
+
iframe.src = url;
|
|
270
|
+
window.document.body.appendChild(iframe);
|
|
271
|
+
window.setTimeout(cleanup, 60_000);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function deliverCollectionExport(result: CollectionExportResult) {
|
|
275
|
+
if (result.format === "pdf" && result.print) {
|
|
276
|
+
printExportHtml(result);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (result.format === "markdown") {
|
|
280
|
+
if (!result.archiveFiles?.length) {
|
|
281
|
+
throw new Error("Markdown export did not include package files.");
|
|
282
|
+
}
|
|
283
|
+
const archive = buildStoreOnlyZip(result.archiveFiles);
|
|
284
|
+
const archiveBuffer = archive.buffer.slice(
|
|
285
|
+
archive.byteOffset,
|
|
286
|
+
archive.byteOffset + archive.byteLength,
|
|
287
|
+
) as ArrayBuffer;
|
|
288
|
+
downloadBlob(
|
|
289
|
+
new Blob([archiveBuffer], { type: "application/zip" }),
|
|
290
|
+
result.filename,
|
|
291
|
+
);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
downloadBlob(
|
|
295
|
+
new Blob([result.content], { type: result.mimeType }),
|
|
296
|
+
result.filename,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
100
300
|
export function DatabaseExportDialog({
|
|
101
301
|
documentId,
|
|
102
302
|
context,
|
|
103
|
-
defaultScope,
|
|
104
303
|
open,
|
|
105
304
|
onOpenChange,
|
|
106
305
|
}: {
|
|
107
306
|
documentId: string;
|
|
108
307
|
context: DatabaseExportContext | null;
|
|
109
|
-
defaultScope: DatabaseExportScopeKind;
|
|
110
308
|
open: boolean;
|
|
111
309
|
onOpenChange: (open: boolean) => void;
|
|
112
310
|
}) {
|
|
113
311
|
const t = useT();
|
|
114
312
|
const exportDocument = useActionMutation("export-document");
|
|
115
|
-
const [
|
|
116
|
-
const [
|
|
313
|
+
const [format, setFormat] = useState<DatabaseExportFormat>("csv");
|
|
314
|
+
const [scope, setScope] = useState<DatabaseExportScopeKind>("current_view");
|
|
315
|
+
const [selections, setSelections] = useState(() =>
|
|
316
|
+
defaultDatabaseExportSelections(context?.properties ?? []),
|
|
317
|
+
);
|
|
318
|
+
const [error, setError] = useState<string | null>(null);
|
|
117
319
|
const wasOpenRef = useRef(false);
|
|
118
|
-
const
|
|
119
|
-
|
|
320
|
+
const hasCurrentView = Boolean(context?.viewId.trim());
|
|
321
|
+
const defaults = useMemo(
|
|
322
|
+
() => defaultDatabaseExportSelections(context?.properties ?? []),
|
|
120
323
|
[context],
|
|
121
324
|
);
|
|
325
|
+
const selection = selections[format];
|
|
326
|
+
const scalarProperties =
|
|
327
|
+
context?.properties.filter((property) => property.type !== "blocks") ?? [];
|
|
328
|
+
const blockProperties = additionalDatabaseExportBlockProperties(
|
|
329
|
+
context?.properties ?? [],
|
|
330
|
+
);
|
|
331
|
+
const formatLabels: Record<DatabaseExportFormat, string> = {
|
|
332
|
+
csv: t("editor.toolbar.exportFormatCsv"),
|
|
333
|
+
markdown: t("editor.toolbar.exportFormatMarkdown"),
|
|
334
|
+
html: t("editor.toolbar.exportFormatHtml"),
|
|
335
|
+
pdf: t("editor.toolbar.exportFormatPdf"),
|
|
336
|
+
};
|
|
122
337
|
|
|
123
338
|
useEffect(() => {
|
|
124
339
|
if (shouldInitializeDatabaseExportDialog(wasOpenRef.current, open)) {
|
|
125
|
-
|
|
126
|
-
|
|
340
|
+
setFormat("csv");
|
|
341
|
+
setScope(hasCurrentView ? "current_view" : "all_members");
|
|
342
|
+
setSelections(defaults);
|
|
343
|
+
setError(null);
|
|
127
344
|
}
|
|
128
345
|
wasOpenRef.current = open;
|
|
129
|
-
}, [
|
|
346
|
+
}, [defaults, hasCurrentView, open]);
|
|
130
347
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
: current.filter((x) => x !== id),
|
|
348
|
+
const updateSelection = (patch: Partial<DatabaseExportSelection>) => {
|
|
349
|
+
setError(null);
|
|
350
|
+
setSelections((current) =>
|
|
351
|
+
updateDatabaseExportSelection(current, format, patch),
|
|
136
352
|
);
|
|
137
353
|
};
|
|
138
354
|
|
|
355
|
+
const toggleId = (
|
|
356
|
+
key: "propertyIds" | "blockPropertyIds",
|
|
357
|
+
id: string,
|
|
358
|
+
checked: boolean,
|
|
359
|
+
) => {
|
|
360
|
+
const current = selection[key];
|
|
361
|
+
updateSelection({
|
|
362
|
+
[key]: checked
|
|
363
|
+
? [...new Set([...current, id])]
|
|
364
|
+
: current.filter((candidate) => candidate !== id),
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
|
|
139
368
|
const handleExport = async () => {
|
|
140
369
|
if (!context || exportDocument.isPending) return;
|
|
370
|
+
setError(null);
|
|
141
371
|
try {
|
|
142
372
|
const result = (await exportDocument.mutateAsync(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
373
|
+
databaseExportRequest({
|
|
374
|
+
id: documentId,
|
|
375
|
+
format,
|
|
376
|
+
context,
|
|
377
|
+
scope,
|
|
378
|
+
selection,
|
|
379
|
+
}),
|
|
380
|
+
)) as CollectionExportResult;
|
|
381
|
+
deliverCollectionExport(result);
|
|
147
382
|
onOpenChange(false);
|
|
148
|
-
} catch (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
383
|
+
} catch (caught) {
|
|
384
|
+
setError(
|
|
385
|
+
actionErrorMessage(caught) ??
|
|
386
|
+
(caught instanceof Error
|
|
387
|
+
? caught.message
|
|
388
|
+
: t("editor.toolbar.exportFailed")),
|
|
389
|
+
);
|
|
153
390
|
}
|
|
154
391
|
};
|
|
155
392
|
|
|
156
393
|
if (!context) return null;
|
|
157
394
|
|
|
395
|
+
const setDialogOpen = (nextOpen: boolean) => {
|
|
396
|
+
if (!nextOpen && exportDocument.isPending) return;
|
|
397
|
+
onOpenChange(nextOpen);
|
|
398
|
+
};
|
|
399
|
+
|
|
158
400
|
return (
|
|
159
|
-
<Dialog open={open} onOpenChange={
|
|
160
|
-
<DialogContent
|
|
401
|
+
<Dialog open={open} onOpenChange={setDialogOpen}>
|
|
402
|
+
<DialogContent
|
|
403
|
+
className="max-h-[min(42rem,calc(100vh-2rem))] gap-5 overflow-y-auto sm:max-w-lg"
|
|
404
|
+
data-database-export-dialog
|
|
405
|
+
>
|
|
161
406
|
<DialogHeader>
|
|
162
407
|
<DialogTitle>{t("editor.toolbar.exportDatabase")}</DialogTitle>
|
|
163
408
|
</DialogHeader>
|
|
164
|
-
<div className="
|
|
165
|
-
<
|
|
166
|
-
className="
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
role="radio"
|
|
173
|
-
aria-checked={scope === "current_view"}
|
|
174
|
-
onClick={() => setScope("current_view")}
|
|
175
|
-
className={cn(
|
|
176
|
-
"rounded-md border px-3 py-2 text-start text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
177
|
-
scope === "current_view"
|
|
178
|
-
? "border-foreground bg-muted"
|
|
179
|
-
: "border-border hover:bg-muted/60",
|
|
180
|
-
)}
|
|
181
|
-
>
|
|
182
|
-
<span className="block font-medium">
|
|
183
|
-
{t("editor.toolbar.currentView")}
|
|
184
|
-
</span>
|
|
185
|
-
<span className="block truncate text-xs text-muted-foreground">
|
|
186
|
-
{context.viewName}
|
|
187
|
-
</span>
|
|
188
|
-
</button>
|
|
189
|
-
<button
|
|
190
|
-
type="button"
|
|
191
|
-
role="radio"
|
|
192
|
-
aria-checked={scope === "all_members"}
|
|
193
|
-
onClick={() => setScope("all_members")}
|
|
194
|
-
className={cn(
|
|
195
|
-
"rounded-md border px-3 py-2 text-start text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
196
|
-
scope === "all_members"
|
|
197
|
-
? "border-foreground bg-muted"
|
|
198
|
-
: "border-border hover:bg-muted/60",
|
|
199
|
-
)}
|
|
409
|
+
<div className="grid gap-5">
|
|
410
|
+
<fieldset disabled={exportDocument.isPending}>
|
|
411
|
+
<legend className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
412
|
+
{t("editor.toolbar.exportFormat")}
|
|
413
|
+
</legend>
|
|
414
|
+
<div
|
|
415
|
+
className="grid grid-cols-2 gap-2 sm:grid-cols-4"
|
|
416
|
+
role="radiogroup"
|
|
200
417
|
>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
418
|
+
{EXPORT_FORMATS.map((candidate) => (
|
|
419
|
+
<button
|
|
420
|
+
key={candidate}
|
|
421
|
+
type="button"
|
|
422
|
+
role="radio"
|
|
423
|
+
aria-checked={format === candidate}
|
|
424
|
+
onClick={() => {
|
|
425
|
+
setError(null);
|
|
426
|
+
setFormat(candidate);
|
|
427
|
+
}}
|
|
428
|
+
className={cn(
|
|
429
|
+
"rounded-md border px-3 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
430
|
+
format === candidate
|
|
431
|
+
? "border-foreground bg-muted"
|
|
432
|
+
: "border-border hover:bg-muted/60",
|
|
433
|
+
)}
|
|
434
|
+
>
|
|
435
|
+
{formatLabels[candidate]}
|
|
436
|
+
</button>
|
|
437
|
+
))}
|
|
212
438
|
</div>
|
|
213
|
-
|
|
439
|
+
</fieldset>
|
|
440
|
+
|
|
441
|
+
<fieldset disabled={exportDocument.isPending}>
|
|
442
|
+
<legend className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
443
|
+
{t("editor.toolbar.exportScope")}
|
|
444
|
+
</legend>
|
|
445
|
+
{hasCurrentView ? (
|
|
446
|
+
<div
|
|
447
|
+
className="grid grid-cols-1 gap-2 sm:grid-cols-2"
|
|
448
|
+
role="radiogroup"
|
|
449
|
+
>
|
|
450
|
+
<button
|
|
451
|
+
type="button"
|
|
452
|
+
role="radio"
|
|
453
|
+
aria-checked={scope === "current_view"}
|
|
454
|
+
onClick={() => {
|
|
455
|
+
setError(null);
|
|
456
|
+
setScope("current_view");
|
|
457
|
+
}}
|
|
458
|
+
className={cn(
|
|
459
|
+
"rounded-md border px-3 py-2 text-start text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
460
|
+
scope === "current_view"
|
|
461
|
+
? "border-foreground bg-muted"
|
|
462
|
+
: "border-border hover:bg-muted/60",
|
|
463
|
+
)}
|
|
464
|
+
>
|
|
465
|
+
<span className="block font-medium">
|
|
466
|
+
{t("editor.toolbar.currentView")}
|
|
467
|
+
</span>
|
|
468
|
+
<span className="block truncate text-xs text-muted-foreground">
|
|
469
|
+
{context.viewName}
|
|
470
|
+
</span>
|
|
471
|
+
</button>
|
|
472
|
+
<button
|
|
473
|
+
type="button"
|
|
474
|
+
role="radio"
|
|
475
|
+
aria-checked={scope === "all_members"}
|
|
476
|
+
onClick={() => {
|
|
477
|
+
setError(null);
|
|
478
|
+
setScope("all_members");
|
|
479
|
+
}}
|
|
480
|
+
className={cn(
|
|
481
|
+
"rounded-md border px-3 py-2 text-start text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
482
|
+
scope === "all_members"
|
|
483
|
+
? "border-foreground bg-muted"
|
|
484
|
+
: "border-border hover:bg-muted/60",
|
|
485
|
+
)}
|
|
486
|
+
>
|
|
487
|
+
<span className="block font-medium">
|
|
488
|
+
{t("editor.toolbar.allDatabaseMembers")}
|
|
489
|
+
</span>
|
|
490
|
+
<span className="block text-xs text-muted-foreground">
|
|
491
|
+
{t("editor.toolbar.allDatabaseMembersDetail")}
|
|
492
|
+
</span>
|
|
493
|
+
</button>
|
|
494
|
+
</div>
|
|
495
|
+
) : (
|
|
496
|
+
<div className="rounded-md border border-foreground bg-muted px-3 py-2 text-sm font-medium">
|
|
497
|
+
{t("editor.toolbar.allDatabaseMembers")}
|
|
498
|
+
</div>
|
|
499
|
+
)}
|
|
500
|
+
</fieldset>
|
|
501
|
+
|
|
502
|
+
<fieldset disabled={exportDocument.isPending}>
|
|
503
|
+
<legend className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
504
|
+
{t("editor.toolbar.exportProperties")}
|
|
505
|
+
</legend>
|
|
506
|
+
<div className="max-h-44 overflow-y-auto pe-1">
|
|
214
507
|
<label className="flex items-center gap-3 rounded-md px-2 py-1.5 text-sm">
|
|
215
508
|
<Checkbox
|
|
216
509
|
checked
|
|
@@ -219,39 +512,75 @@ export function DatabaseExportDialog({
|
|
|
219
512
|
/>
|
|
220
513
|
<span>{t("editor.toolbar.titleColumn")}</span>
|
|
221
514
|
</label>
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
515
|
+
{scalarProperties.map((property) => (
|
|
516
|
+
<label
|
|
517
|
+
key={property.id}
|
|
518
|
+
className="flex cursor-pointer items-center gap-3 rounded-md px-2 py-1.5 text-sm hover:bg-muted/60"
|
|
519
|
+
>
|
|
520
|
+
<Checkbox
|
|
521
|
+
checked={selection.propertyIds.includes(property.id)}
|
|
522
|
+
onCheckedChange={(value) =>
|
|
523
|
+
toggleId("propertyIds", property.id, value === true)
|
|
524
|
+
}
|
|
525
|
+
/>
|
|
526
|
+
<span className="min-w-0 flex-1 truncate">
|
|
527
|
+
{property.name}
|
|
528
|
+
</span>
|
|
529
|
+
</label>
|
|
530
|
+
))}
|
|
531
|
+
</div>
|
|
532
|
+
</fieldset>
|
|
533
|
+
|
|
534
|
+
<fieldset disabled={exportDocument.isPending}>
|
|
535
|
+
<legend className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
536
|
+
{t("editor.toolbar.exportPageContent")}
|
|
537
|
+
</legend>
|
|
538
|
+
<div className="max-h-44 overflow-y-auto pe-1">
|
|
539
|
+
<label className="flex cursor-pointer items-center gap-3 rounded-md px-2 py-1.5 text-sm hover:bg-muted/60">
|
|
540
|
+
<Checkbox
|
|
541
|
+
checked={selection.includePrimaryBody}
|
|
542
|
+
onCheckedChange={(value) =>
|
|
543
|
+
updateSelection({ includePrimaryBody: value === true })
|
|
544
|
+
}
|
|
545
|
+
/>
|
|
546
|
+
<span>{t("editor.toolbar.primaryPageBody")}</span>
|
|
547
|
+
</label>
|
|
548
|
+
{blockProperties.map((property) => (
|
|
549
|
+
<label
|
|
550
|
+
key={property.id}
|
|
551
|
+
className="flex cursor-pointer items-center gap-3 rounded-md px-2 py-1.5 text-sm hover:bg-muted/60"
|
|
552
|
+
>
|
|
553
|
+
<Checkbox
|
|
554
|
+
checked={selection.blockPropertyIds.includes(property.id)}
|
|
555
|
+
onCheckedChange={(value) =>
|
|
556
|
+
toggleId("blockPropertyIds", property.id, value === true)
|
|
557
|
+
}
|
|
558
|
+
/>
|
|
559
|
+
<span className="min-w-0 flex-1 truncate">
|
|
560
|
+
{property.name}
|
|
561
|
+
</span>
|
|
562
|
+
<span className="text-xs text-muted-foreground">
|
|
563
|
+
{t("editor.toolbar.blocksColumn")}
|
|
564
|
+
</span>
|
|
565
|
+
</label>
|
|
566
|
+
))}
|
|
567
|
+
</div>
|
|
568
|
+
</fieldset>
|
|
569
|
+
|
|
570
|
+
{error ? (
|
|
571
|
+
<div
|
|
572
|
+
role="alert"
|
|
573
|
+
className="rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive"
|
|
574
|
+
>
|
|
575
|
+
{error}
|
|
247
576
|
</div>
|
|
248
|
-
|
|
577
|
+
) : null}
|
|
249
578
|
</div>
|
|
250
579
|
<DialogFooter>
|
|
251
580
|
<Button
|
|
252
581
|
type="button"
|
|
253
582
|
variant="outline"
|
|
254
|
-
onClick={() =>
|
|
583
|
+
onClick={() => setDialogOpen(false)}
|
|
255
584
|
disabled={exportDocument.isPending}
|
|
256
585
|
>
|
|
257
586
|
{t("comments.cancel")}
|
|
@@ -267,8 +596,10 @@ export function DatabaseExportDialog({
|
|
|
267
596
|
<IconDownload className="me-2 size-4" />
|
|
268
597
|
)}
|
|
269
598
|
{exportDocument.isPending
|
|
270
|
-
? t("editor.toolbar.
|
|
271
|
-
|
|
599
|
+
? t("editor.toolbar.preparingExport", {
|
|
600
|
+
format: formatLabels[format],
|
|
601
|
+
})
|
|
602
|
+
: t("editor.toolbar.export")}
|
|
272
603
|
</Button>
|
|
273
604
|
</DialogFooter>
|
|
274
605
|
</DialogContent>
|