@bendyline/docblocks-react 2.2.0 → 2.2.2
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/THIRD_PARTY_NOTICES.txt +11842 -0
- package/dist/{ExportDialog-VYATD5UC.js → ExportDialog-HPFPXELL.js} +1 -1
- package/dist/{ExportToolbarControls-C46GMPKC.js → ExportToolbarControls-773QRE4R.js} +141 -79
- package/dist/{GitToolbarControl-FW3BZFXV.js → GitToolbarControl-VJVK2B4X.js} +4 -3
- package/dist/{GitUI-GMH3PAYL.js → GitUI-CAKFVKWY.js} +4 -3
- package/dist/chunk-APC4KI4B.js +0 -0
- package/dist/{chunk-BPGEBGAN.js → chunk-JDJVRDOP.js} +23 -0
- package/dist/{chunk-3CZ27FOA.js → chunk-MHTQORMH.js} +1 -17
- package/dist/{chunk-TFMO7EVO.js → chunk-MRUK56JS.js} +23 -4
- package/dist/chunk-VRRL6VTD.js +606 -0
- package/dist/editor.d.ts +12 -1
- package/dist/editor.js +9 -3
- package/dist/export/index.d.ts +1 -3
- package/dist/export/index.js +1 -1
- package/dist/index.d.ts +61 -8
- package/dist/index.js +2154 -787
- package/dist/settings/index.d.ts +34 -2
- package/dist/settings/index.js +9 -3
- package/package.json +11 -10
- package/src/styles/docblocks.css +487 -17
- package/dist/chunk-3QSZY74C.js +0 -255
package/dist/index.js
CHANGED
|
@@ -13,27 +13,34 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
AccentColorSettings,
|
|
15
15
|
DB_CHROME_COLORS,
|
|
16
|
+
DEFAULT_WRITE_CANVAS_FONT_SCHEME,
|
|
16
17
|
DEFAULT_WRITE_CANVAS_PREFERENCES,
|
|
17
18
|
SettingsDialog,
|
|
18
19
|
ThemeSettings,
|
|
20
|
+
WRITE_CANVAS_FONT_SCHEMES,
|
|
19
21
|
WriteCanvasSettingsControls,
|
|
20
22
|
loadAccentColor,
|
|
21
23
|
loadThemePreference,
|
|
22
24
|
loadWriteCanvasPreferences,
|
|
25
|
+
resolveWriteCanvasFonts,
|
|
23
26
|
saveAccentColor,
|
|
24
27
|
saveThemePreference,
|
|
25
28
|
saveWriteCanvasPreferences
|
|
26
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-VRRL6VTD.js";
|
|
27
30
|
import {
|
|
28
|
-
pickEmptyDocumentPrompt
|
|
29
|
-
|
|
31
|
+
pickEmptyDocumentPrompt,
|
|
32
|
+
useResponsivePreviewViewportPreset
|
|
33
|
+
} from "./chunk-JDJVRDOP.js";
|
|
30
34
|
import {
|
|
31
35
|
ExportDialog
|
|
32
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-MHTQORMH.js";
|
|
33
37
|
import {
|
|
34
38
|
buildExportFilename,
|
|
35
39
|
runExport
|
|
36
40
|
} from "./chunk-GHQ4X7KM.js";
|
|
41
|
+
import {
|
|
42
|
+
useMenuKeyboard
|
|
43
|
+
} from "./chunk-MRUK56JS.js";
|
|
37
44
|
import "./chunk-YBEYTVU2.js";
|
|
38
45
|
import {
|
|
39
46
|
Dialog
|
|
@@ -44,7 +51,7 @@ import {
|
|
|
44
51
|
} from "./chunk-EBWYGTN7.js";
|
|
45
52
|
|
|
46
53
|
// src/FileExplorer/FileExplorer.tsx
|
|
47
|
-
import { useCallback as
|
|
54
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState4 } from "react";
|
|
48
55
|
import {
|
|
49
56
|
isFileSystemMoveStateError,
|
|
50
57
|
parseWorkspacePath as parseWorkspacePath2
|
|
@@ -54,10 +61,12 @@ import {
|
|
|
54
61
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
55
62
|
import {
|
|
56
63
|
FsError,
|
|
64
|
+
fsErrorFromUnknown,
|
|
57
65
|
getFileSystemProviderV2,
|
|
58
66
|
moveFileSystemEntry,
|
|
59
67
|
parseWorkspacePath
|
|
60
68
|
} from "@bendyline/docblocks/filesystem";
|
|
69
|
+
var DIRECTORY_NOT_FOUND_RETRY_DELAYS_MS = Object.freeze([40, 120]);
|
|
61
70
|
function normalisePath(path) {
|
|
62
71
|
return parseWorkspacePath(path);
|
|
63
72
|
}
|
|
@@ -83,10 +92,44 @@ function hasEquivalentPath(paths, path) {
|
|
|
83
92
|
return equivalentPathKeys(path).some((candidate) => paths.has(candidate));
|
|
84
93
|
}
|
|
85
94
|
async function readProviderDirectory(provider, path) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
const canonical = parseWorkspacePath(path);
|
|
96
|
+
const readOnce = async () => {
|
|
97
|
+
const providerV2 = getFileSystemProviderV2(provider);
|
|
98
|
+
if (!providerV2) return provider.readDirectory(path);
|
|
99
|
+
const entries = await providerV2.readDirectory(canonical);
|
|
100
|
+
return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
|
|
101
|
+
};
|
|
102
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
103
|
+
try {
|
|
104
|
+
return await readOnce();
|
|
105
|
+
} catch (caught) {
|
|
106
|
+
const error = directoryReadError(caught, canonical);
|
|
107
|
+
const retryDelay = DIRECTORY_NOT_FOUND_RETRY_DELAYS_MS[attempt];
|
|
108
|
+
if (error.code !== "not-found" || retryDelay === void 0) throw error;
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function directoryReadError(caught, requestedPath) {
|
|
114
|
+
const error = fsErrorFromUnknown(caught, { operation: "list", path: requestedPath });
|
|
115
|
+
if (error.path !== null) return error;
|
|
116
|
+
return new FsError(error.code, error.message, {
|
|
117
|
+
operation: error.operation ?? "list",
|
|
118
|
+
path: requestedPath,
|
|
119
|
+
destinationPath: error.destinationPath ?? void 0,
|
|
120
|
+
retryable: error.retryable
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function readIssue(caught, directoryPath) {
|
|
124
|
+
const canonical = parseWorkspacePath(directoryPath);
|
|
125
|
+
const error = directoryReadError(caught, canonical);
|
|
126
|
+
return Object.freeze({
|
|
127
|
+
directoryPath: canonical,
|
|
128
|
+
path: error.path ?? canonical,
|
|
129
|
+
code: error.code,
|
|
130
|
+
message: error.message,
|
|
131
|
+
retryable: error.retryable || error.code === "not-found"
|
|
132
|
+
});
|
|
90
133
|
}
|
|
91
134
|
function useFileTree(provider) {
|
|
92
135
|
const [entries, setEntries] = useState([]);
|
|
@@ -94,12 +137,15 @@ function useFileTree(provider) {
|
|
|
94
137
|
const [selectedPath, setSelectedPath] = useState(null);
|
|
95
138
|
const [selectedKind, setSelectedKind] = useState(null);
|
|
96
139
|
const [loading, setLoading] = useState(false);
|
|
97
|
-
const [
|
|
140
|
+
const [rootIssue, setRootIssue] = useState(null);
|
|
141
|
+
const [childIssues, setChildIssues] = useState(/* @__PURE__ */ new Map());
|
|
98
142
|
const [childEntries, setChildEntries] = useState(/* @__PURE__ */ new Map());
|
|
99
143
|
const providerRef = useRef(provider);
|
|
100
144
|
providerRef.current = provider;
|
|
101
|
-
const
|
|
102
|
-
|
|
145
|
+
const directoryRequestSequenceRef = useRef(0);
|
|
146
|
+
const directoryRequestsRef = useRef(/* @__PURE__ */ new Map());
|
|
147
|
+
const reportRootIssue = useCallback((caught) => {
|
|
148
|
+
setRootIssue(readIssue(caught, ""));
|
|
103
149
|
}, []);
|
|
104
150
|
const loadRoot = useCallback(async () => {
|
|
105
151
|
const sourceProvider = providerRef.current;
|
|
@@ -108,43 +154,60 @@ function useFileTree(provider) {
|
|
|
108
154
|
setLoading(false);
|
|
109
155
|
return;
|
|
110
156
|
}
|
|
157
|
+
const requestId = ++directoryRequestSequenceRef.current;
|
|
158
|
+
directoryRequestsRef.current.set("", requestId);
|
|
159
|
+
const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get("") === requestId;
|
|
111
160
|
setLoading(true);
|
|
112
|
-
setError(null);
|
|
113
161
|
try {
|
|
114
162
|
const root = await readProviderDirectory(sourceProvider, "");
|
|
115
|
-
if (
|
|
163
|
+
if (!isCurrent()) return;
|
|
116
164
|
setEntries(root);
|
|
165
|
+
setRootIssue(null);
|
|
117
166
|
} catch (caught) {
|
|
118
|
-
if (
|
|
167
|
+
if (isCurrent()) setRootIssue(readIssue(caught, ""));
|
|
119
168
|
} finally {
|
|
120
|
-
if (
|
|
169
|
+
if (isCurrent()) setLoading(false);
|
|
121
170
|
}
|
|
122
|
-
}, [
|
|
123
|
-
const loadChildren = useCallback(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
}, []);
|
|
172
|
+
const loadChildren = useCallback(async (dirPath) => {
|
|
173
|
+
const sourceProvider = providerRef.current;
|
|
174
|
+
if (!sourceProvider) return;
|
|
175
|
+
const canonical = normalisePath(dirPath);
|
|
176
|
+
const requestId = ++directoryRequestSequenceRef.current;
|
|
177
|
+
directoryRequestsRef.current.set(canonical, requestId);
|
|
178
|
+
const isCurrent = () => providerRef.current === sourceProvider && directoryRequestsRef.current.get(canonical) === requestId;
|
|
179
|
+
try {
|
|
180
|
+
const children = await readProviderDirectory(sourceProvider, dirPath);
|
|
181
|
+
if (!isCurrent()) return;
|
|
182
|
+
setChildEntries((prev) => {
|
|
183
|
+
const next = new Map(prev);
|
|
184
|
+
next.set(canonical, children);
|
|
185
|
+
return next;
|
|
186
|
+
});
|
|
187
|
+
setChildIssues((prev) => {
|
|
188
|
+
if (!prev.has(canonical)) return prev;
|
|
189
|
+
const next = new Map(prev);
|
|
190
|
+
next.delete(canonical);
|
|
191
|
+
return next;
|
|
192
|
+
});
|
|
193
|
+
} catch (caught) {
|
|
194
|
+
if (!isCurrent()) return;
|
|
195
|
+
setChildIssues((prev) => {
|
|
196
|
+
const next = new Map(prev);
|
|
197
|
+
next.set(canonical, readIssue(caught, dirPath));
|
|
198
|
+
return next;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}, []);
|
|
141
202
|
useEffect(() => {
|
|
142
203
|
setEntries([]);
|
|
143
204
|
setExpanded(/* @__PURE__ */ new Set());
|
|
144
205
|
setChildEntries(/* @__PURE__ */ new Map());
|
|
206
|
+
setChildIssues(/* @__PURE__ */ new Map());
|
|
145
207
|
setSelectedPath(null);
|
|
146
208
|
setSelectedKind(null);
|
|
147
|
-
|
|
209
|
+
setRootIssue(null);
|
|
210
|
+
directoryRequestsRef.current.clear();
|
|
148
211
|
void loadRoot();
|
|
149
212
|
}, [provider, loadRoot]);
|
|
150
213
|
const toggleExpand = useCallback(
|
|
@@ -191,6 +254,13 @@ function useFileTree(provider) {
|
|
|
191
254
|
await loadChildren(dirPath);
|
|
192
255
|
}
|
|
193
256
|
}, [loadRoot, loadChildren, expanded]);
|
|
257
|
+
const retryDirectory = useCallback(
|
|
258
|
+
async (path) => {
|
|
259
|
+
if (normalisePath(path) === "") await loadRoot();
|
|
260
|
+
else await loadChildren(path);
|
|
261
|
+
},
|
|
262
|
+
[loadRoot, loadChildren]
|
|
263
|
+
);
|
|
194
264
|
const refreshRef = useRef(refresh);
|
|
195
265
|
refreshRef.current = refresh;
|
|
196
266
|
useEffect(() => {
|
|
@@ -214,7 +284,7 @@ function useFileTree(provider) {
|
|
|
214
284
|
await refreshRef.current();
|
|
215
285
|
} while (refreshAgain && !disposed);
|
|
216
286
|
} catch (caught) {
|
|
217
|
-
|
|
287
|
+
reportRootIssue(caught);
|
|
218
288
|
} finally {
|
|
219
289
|
refreshing = false;
|
|
220
290
|
if (refreshAgain && !disposed) requestRefresh();
|
|
@@ -229,23 +299,25 @@ function useFileTree(provider) {
|
|
|
229
299
|
{
|
|
230
300
|
onError: (caught) => {
|
|
231
301
|
if (disposed) return;
|
|
232
|
-
|
|
302
|
+
reportRootIssue(caught);
|
|
233
303
|
requestRefresh();
|
|
234
304
|
}
|
|
235
305
|
}
|
|
236
306
|
);
|
|
237
307
|
void subscription.ready.catch((caught) => {
|
|
238
|
-
if (!disposed)
|
|
308
|
+
if (!disposed) reportRootIssue(caught);
|
|
239
309
|
});
|
|
240
310
|
return () => {
|
|
241
311
|
disposed = true;
|
|
242
312
|
void subscription.dispose();
|
|
243
313
|
};
|
|
244
|
-
}, [provider,
|
|
314
|
+
}, [provider, reportRootIssue]);
|
|
245
315
|
useEffect(() => {
|
|
246
316
|
if (!provider) return;
|
|
317
|
+
const providerV2 = getFileSystemProviderV2(provider);
|
|
318
|
+
if (providerV2?.capabilities.watch) return;
|
|
247
319
|
const refreshOnFocus = () => {
|
|
248
|
-
void refreshRef.current().catch(
|
|
320
|
+
void refreshRef.current().catch(reportRootIssue);
|
|
249
321
|
};
|
|
250
322
|
const refreshOnVisibility = () => {
|
|
251
323
|
if (document.visibilityState === "visible") refreshOnFocus();
|
|
@@ -256,7 +328,7 @@ function useFileTree(provider) {
|
|
|
256
328
|
window.removeEventListener("focus", refreshOnFocus);
|
|
257
329
|
document.removeEventListener("visibilitychange", refreshOnVisibility);
|
|
258
330
|
};
|
|
259
|
-
}, [provider,
|
|
331
|
+
}, [provider, reportRootIssue]);
|
|
260
332
|
const createFile = useCallback(
|
|
261
333
|
async (path, content = "") => {
|
|
262
334
|
if (!providerRef.current) return;
|
|
@@ -331,6 +403,7 @@ function useFileTree(provider) {
|
|
|
331
403
|
);
|
|
332
404
|
setExpanded(nextExpanded);
|
|
333
405
|
setChildEntries(/* @__PURE__ */ new Map());
|
|
406
|
+
setChildIssues(/* @__PURE__ */ new Map());
|
|
334
407
|
await loadRoot();
|
|
335
408
|
for (const dirPath of nextExpanded) {
|
|
336
409
|
await loadChildren(dirPath);
|
|
@@ -344,7 +417,10 @@ function useFileTree(provider) {
|
|
|
344
417
|
selectedPath,
|
|
345
418
|
selectedKind,
|
|
346
419
|
loading,
|
|
347
|
-
error,
|
|
420
|
+
error: rootIssue?.message ?? null,
|
|
421
|
+
rootIssue,
|
|
422
|
+
childIssues,
|
|
423
|
+
childEntries,
|
|
348
424
|
toggleExpand,
|
|
349
425
|
select,
|
|
350
426
|
reveal,
|
|
@@ -353,8 +429,7 @@ function useFileTree(provider) {
|
|
|
353
429
|
deleteEntry,
|
|
354
430
|
renameEntry,
|
|
355
431
|
refresh,
|
|
356
|
-
|
|
357
|
-
...{ childEntries }
|
|
432
|
+
retryDirectory
|
|
358
433
|
};
|
|
359
434
|
}
|
|
360
435
|
|
|
@@ -379,6 +454,9 @@ function FolderIcon() {
|
|
|
379
454
|
function MoreIcon() {
|
|
380
455
|
return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-ellipsis" });
|
|
381
456
|
}
|
|
457
|
+
function PinIcon() {
|
|
458
|
+
return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-thumbtack" });
|
|
459
|
+
}
|
|
382
460
|
function WorkspaceIcon() {
|
|
383
461
|
return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-gear" });
|
|
384
462
|
}
|
|
@@ -425,6 +503,8 @@ function FileTreeNode({
|
|
|
425
503
|
confirmDelete = defaultConfirmDelete,
|
|
426
504
|
onDelete,
|
|
427
505
|
onRename,
|
|
506
|
+
pinned = false,
|
|
507
|
+
onTogglePin,
|
|
428
508
|
draggable = false,
|
|
429
509
|
dragging = false,
|
|
430
510
|
dropTarget = false,
|
|
@@ -432,7 +512,8 @@ function FileTreeNode({
|
|
|
432
512
|
onDragEnd,
|
|
433
513
|
onDragOverEntry,
|
|
434
514
|
onDropEntry,
|
|
435
|
-
renderChildren
|
|
515
|
+
renderChildren,
|
|
516
|
+
childError
|
|
436
517
|
}) {
|
|
437
518
|
const [renaming, setRenaming] = useState2(false);
|
|
438
519
|
const [renameValue, setRenameValue] = useState2(entry.name);
|
|
@@ -445,8 +526,10 @@ function FileTreeNode({
|
|
|
445
526
|
const rowRef = useRef2(null);
|
|
446
527
|
const keyboardMenuRef = useRef2(false);
|
|
447
528
|
const renameSubmittedRef = useRef2(false);
|
|
529
|
+
const returnFocusAfterRenameRef = useRef2(false);
|
|
448
530
|
const isDir = entry.kind === "directory";
|
|
449
|
-
const icon =
|
|
531
|
+
const icon = expanded ? "\u25BE" : "\u25B8";
|
|
532
|
+
const nestedFileInset = !isDir && depth > 0 ? 8 : 0;
|
|
450
533
|
const handleClick = useCallback2(() => {
|
|
451
534
|
if (isDir) {
|
|
452
535
|
onToggle(entry.path);
|
|
@@ -537,22 +620,27 @@ function FileTreeNode({
|
|
|
537
620
|
}, [entry.name, closeMenu]);
|
|
538
621
|
const handleRenameCancel = useCallback2(() => {
|
|
539
622
|
renameSubmittedRef.current = true;
|
|
623
|
+
returnFocusAfterRenameRef.current = true;
|
|
540
624
|
setRenaming(false);
|
|
541
625
|
}, []);
|
|
542
|
-
const handleRenameSubmit = useCallback2(
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
626
|
+
const handleRenameSubmit = useCallback2(
|
|
627
|
+
async (returnFocus = false) => {
|
|
628
|
+
if (renameSubmittedRef.current) return;
|
|
629
|
+
renameSubmittedRef.current = true;
|
|
630
|
+
returnFocusAfterRenameRef.current = returnFocus;
|
|
631
|
+
setRenaming(false);
|
|
632
|
+
if (renameValue && renameValue !== entry.name) {
|
|
633
|
+
const parentPath2 = entry.path.includes("/") ? entry.path.slice(0, entry.path.lastIndexOf("/")) : "";
|
|
634
|
+
const newPath = parentPath2 ? `${parentPath2}/${renameValue}` : renameValue;
|
|
635
|
+
try {
|
|
636
|
+
await onRename(entry.path, newPath, entry.kind);
|
|
637
|
+
} catch (caught) {
|
|
638
|
+
setActionError(caught instanceof Error ? caught.message : "Unable to rename this entry.");
|
|
639
|
+
}
|
|
552
640
|
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
|
|
641
|
+
},
|
|
642
|
+
[renameValue, entry.name, entry.path, entry.kind, onRename]
|
|
643
|
+
);
|
|
556
644
|
const handleDeleteClick = useCallback2(async () => {
|
|
557
645
|
closeMenu(false);
|
|
558
646
|
setActionError(null);
|
|
@@ -566,6 +654,17 @@ function FileTreeNode({
|
|
|
566
654
|
setActionError(caught instanceof Error ? caught.message : "Unable to delete this entry.");
|
|
567
655
|
}
|
|
568
656
|
}, [entry.name, entry.path, entry.kind, confirmDelete, onDelete, closeMenu]);
|
|
657
|
+
const handleTogglePin = useCallback2(async () => {
|
|
658
|
+
closeMenu(false);
|
|
659
|
+
setActionError(null);
|
|
660
|
+
try {
|
|
661
|
+
await onTogglePin?.(entry.path);
|
|
662
|
+
} catch (caught) {
|
|
663
|
+
setActionError(
|
|
664
|
+
caught instanceof Error ? caught.message : `Unable to ${pinned ? "unpin" : "pin"} this file.`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
}, [closeMenu, entry.path, onTogglePin, pinned]);
|
|
569
668
|
useEffect2(() => {
|
|
570
669
|
if (!showContext) return;
|
|
571
670
|
function handleClose(e) {
|
|
@@ -591,6 +690,11 @@ function FileTreeNode({
|
|
|
591
690
|
inputRef.current.select();
|
|
592
691
|
}
|
|
593
692
|
}, [renaming]);
|
|
693
|
+
useEffect2(() => {
|
|
694
|
+
if (renaming || !returnFocusAfterRenameRef.current) return;
|
|
695
|
+
returnFocusAfterRenameRef.current = false;
|
|
696
|
+
rowRef.current?.focus({ preventScroll: true });
|
|
697
|
+
}, [renaming]);
|
|
594
698
|
useEffect2(() => {
|
|
595
699
|
if (!showContext || !keyboardMenuRef.current) return;
|
|
596
700
|
focusMenuItem(0);
|
|
@@ -622,7 +726,7 @@ function FileTreeNode({
|
|
|
622
726
|
{
|
|
623
727
|
ref: rowRef,
|
|
624
728
|
className: `db-tree-row ${selected ? "db-tree-row--selected" : ""} ${dragging ? "db-tree-row--dragging" : ""} ${dropTarget ? "db-tree-row--drop-target" : ""}`,
|
|
625
|
-
style: { paddingLeft: depth *
|
|
729
|
+
style: { paddingLeft: depth * 12 + 4 + nestedFileInset },
|
|
626
730
|
onClick: handleClick,
|
|
627
731
|
onContextMenu: handleContextMenu,
|
|
628
732
|
draggable: draggable && !renaming,
|
|
@@ -665,7 +769,7 @@ function FileTreeNode({
|
|
|
665
769
|
}
|
|
666
770
|
},
|
|
667
771
|
children: [
|
|
668
|
-
/* @__PURE__ */ jsx2("span", { className: "db-tree-icon", children: icon }),
|
|
772
|
+
(isDir || depth === 0) && /* @__PURE__ */ jsx2("span", { className: "db-tree-icon", children: isDir ? icon : null }),
|
|
669
773
|
renaming ? /* @__PURE__ */ jsx2(
|
|
670
774
|
"input",
|
|
671
775
|
{
|
|
@@ -678,7 +782,7 @@ function FileTreeNode({
|
|
|
678
782
|
if (e.key !== "Enter" && e.key !== "Escape") return;
|
|
679
783
|
e.preventDefault();
|
|
680
784
|
e.stopPropagation();
|
|
681
|
-
if (e.key === "Enter") void handleRenameSubmit();
|
|
785
|
+
if (e.key === "Enter") void handleRenameSubmit(true);
|
|
682
786
|
else handleRenameCancel();
|
|
683
787
|
},
|
|
684
788
|
onClick: (e) => e.stopPropagation()
|
|
@@ -697,6 +801,7 @@ function FileTreeNode({
|
|
|
697
801
|
"aria-haspopup": "menu",
|
|
698
802
|
"aria-expanded": showContext,
|
|
699
803
|
"aria-keyshortcuts": "Shift+F10",
|
|
804
|
+
title: "More actions (Shift+F10)",
|
|
700
805
|
tabIndex: -1,
|
|
701
806
|
children: /* @__PURE__ */ jsx2(MoreIcon, {})
|
|
702
807
|
}
|
|
@@ -728,6 +833,17 @@ function FileTreeNode({
|
|
|
728
833
|
children: "Rename"
|
|
729
834
|
}
|
|
730
835
|
),
|
|
836
|
+
!isDir && onTogglePin && /* @__PURE__ */ jsx2(
|
|
837
|
+
"button",
|
|
838
|
+
{
|
|
839
|
+
type: "button",
|
|
840
|
+
role: "menuitem",
|
|
841
|
+
tabIndex: -1,
|
|
842
|
+
className: "db-tree-context-item",
|
|
843
|
+
onClick: () => void handleTogglePin(),
|
|
844
|
+
children: pinned ? "Unpin" : "Pin"
|
|
845
|
+
}
|
|
846
|
+
),
|
|
731
847
|
gitActions && (gitActions.viewChanges || gitActions.fileHistory) && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
732
848
|
/* @__PURE__ */ jsx2("div", { className: "db-tree-context-divider", role: "separator" }),
|
|
733
849
|
gitActions.viewChanges && /* @__PURE__ */ jsx2(
|
|
@@ -790,7 +906,8 @@ function FileTreeNode({
|
|
|
790
906
|
),
|
|
791
907
|
document.body
|
|
792
908
|
),
|
|
793
|
-
isDir && expanded && renderChildren && /* @__PURE__ */ jsx2("div", { className: "db-tree-children", role: "group", children: renderChildren(entry.path) })
|
|
909
|
+
isDir && expanded && renderChildren && /* @__PURE__ */ jsx2("div", { className: "db-tree-children", role: "group", children: renderChildren(entry.path) }),
|
|
910
|
+
isDir && expanded && childError
|
|
794
911
|
] });
|
|
795
912
|
}
|
|
796
913
|
|
|
@@ -854,8 +971,409 @@ function treeKeyAction(rows, activePath, key) {
|
|
|
854
971
|
}
|
|
855
972
|
}
|
|
856
973
|
|
|
857
|
-
// src/FileExplorer/
|
|
974
|
+
// src/FileExplorer/PinnedDocuments.tsx
|
|
975
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef3, useState as useState3 } from "react";
|
|
976
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
977
|
+
|
|
978
|
+
// src/DocBlocksShell/pinned-documents.ts
|
|
979
|
+
import { tryParseWorkspacePath } from "@bendyline/docblocks/filesystem";
|
|
980
|
+
var PINNED_DOCUMENTS_STORAGE_KEY = "docblocks-pinned-documents-v1";
|
|
981
|
+
var MAX_PINNED_DOCUMENTS = 100;
|
|
982
|
+
var MAX_WORKSPACE_ID_LENGTH = 256;
|
|
983
|
+
var MAX_WORKSPACE_NAME_LENGTH = 256;
|
|
984
|
+
var MAX_DOCUMENT_PATH_LENGTH = 4096;
|
|
985
|
+
function resolvePinnedDocumentStorage() {
|
|
986
|
+
try {
|
|
987
|
+
const candidate = Reflect.get(globalThis, "localStorage");
|
|
988
|
+
if (typeof candidate !== "object" || candidate === null || typeof Reflect.get(candidate, "getItem") !== "function" || typeof Reflect.get(candidate, "setItem") !== "function") {
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
return candidate;
|
|
992
|
+
} catch {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function pinnedDocumentKey(document2) {
|
|
997
|
+
const path = tryParseWorkspacePath(document2.path);
|
|
998
|
+
return `${document2.workspaceId}\0${path ?? document2.path}`;
|
|
999
|
+
}
|
|
1000
|
+
function pinnedDocumentName(document2) {
|
|
1001
|
+
const canonical = tryParseWorkspacePath(document2.path);
|
|
1002
|
+
const path = canonical ?? document2.path;
|
|
1003
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
1004
|
+
}
|
|
1005
|
+
function pinnedDocumentDisplayPath(document2) {
|
|
1006
|
+
return `${document2.workspaceName}/${document2.path}`;
|
|
1007
|
+
}
|
|
1008
|
+
function missingPinnedDocumentMessage(document2) {
|
|
1009
|
+
return `${pinnedDocumentName(document2)} is no longer at ${pinnedDocumentDisplayPath(document2)}. Unpin this file?`;
|
|
1010
|
+
}
|
|
1011
|
+
function parsePinnedDocument(value) {
|
|
1012
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
1013
|
+
const record = value;
|
|
1014
|
+
const keys = Object.keys(record);
|
|
1015
|
+
if (keys.some((key) => key !== "workspaceId" && key !== "workspaceName" && key !== "path") || typeof record.workspaceId !== "string" || record.workspaceId.length === 0 || record.workspaceId.length > MAX_WORKSPACE_ID_LENGTH || typeof record.workspaceName !== "string" || record.workspaceName.length === 0 || record.workspaceName.length > MAX_WORKSPACE_NAME_LENGTH || typeof record.path !== "string" || record.path.length === 0 || record.path.length > MAX_DOCUMENT_PATH_LENGTH) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
const path = tryParseWorkspacePath(record.path);
|
|
1019
|
+
if (!path) return null;
|
|
1020
|
+
return {
|
|
1021
|
+
workspaceId: record.workspaceId,
|
|
1022
|
+
workspaceName: record.workspaceName,
|
|
1023
|
+
path
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function parsePinnedDocuments(value) {
|
|
1027
|
+
if (!Array.isArray(value)) return [];
|
|
1028
|
+
const parsed = [];
|
|
1029
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1030
|
+
for (const valueItem of value.slice(0, MAX_PINNED_DOCUMENTS)) {
|
|
1031
|
+
const document2 = parsePinnedDocument(valueItem);
|
|
1032
|
+
if (!document2) continue;
|
|
1033
|
+
const key = pinnedDocumentKey(document2);
|
|
1034
|
+
if (seen.has(key)) continue;
|
|
1035
|
+
seen.add(key);
|
|
1036
|
+
parsed.push(document2);
|
|
1037
|
+
}
|
|
1038
|
+
return parsed;
|
|
1039
|
+
}
|
|
1040
|
+
function loadPinnedDocuments(storage = resolvePinnedDocumentStorage()) {
|
|
1041
|
+
if (!storage) return [];
|
|
1042
|
+
try {
|
|
1043
|
+
const raw = storage.getItem(PINNED_DOCUMENTS_STORAGE_KEY);
|
|
1044
|
+
return raw === null ? [] : parsePinnedDocuments(JSON.parse(raw));
|
|
1045
|
+
} catch {
|
|
1046
|
+
return [];
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function savePinnedDocuments(documents, storage = resolvePinnedDocumentStorage()) {
|
|
1050
|
+
if (!storage) return;
|
|
1051
|
+
try {
|
|
1052
|
+
storage.setItem(PINNED_DOCUMENTS_STORAGE_KEY, JSON.stringify(parsePinnedDocuments(documents)));
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function togglePinnedDocument(documents, candidate) {
|
|
1057
|
+
const parsed = parsePinnedDocument(candidate);
|
|
1058
|
+
if (!parsed) return [...documents];
|
|
1059
|
+
const key = pinnedDocumentKey(parsed);
|
|
1060
|
+
if (documents.some((document2) => pinnedDocumentKey(document2) === key)) {
|
|
1061
|
+
return documents.filter((document2) => pinnedDocumentKey(document2) !== key);
|
|
1062
|
+
}
|
|
1063
|
+
return [parsed, ...documents].slice(0, MAX_PINNED_DOCUMENTS);
|
|
1064
|
+
}
|
|
1065
|
+
function removePinnedDocument(documents, target) {
|
|
1066
|
+
const key = pinnedDocumentKey(target);
|
|
1067
|
+
return documents.filter((document2) => pinnedDocumentKey(document2) !== key);
|
|
1068
|
+
}
|
|
1069
|
+
function relocatePinnedDocuments(documents, workspaceId, oldPath, newPath) {
|
|
1070
|
+
const oldCanonical = tryParseWorkspacePath(oldPath);
|
|
1071
|
+
const newCanonical = tryParseWorkspacePath(newPath);
|
|
1072
|
+
if (!oldCanonical || !newCanonical) return [...documents];
|
|
1073
|
+
return parsePinnedDocuments(
|
|
1074
|
+
documents.map((document2) => {
|
|
1075
|
+
if (document2.workspaceId !== workspaceId) return document2;
|
|
1076
|
+
if (document2.path === oldCanonical) return { ...document2, path: newCanonical };
|
|
1077
|
+
if (!document2.path.startsWith(`${oldCanonical}/`)) return document2;
|
|
1078
|
+
return {
|
|
1079
|
+
...document2,
|
|
1080
|
+
path: `${newCanonical}${document2.path.slice(oldCanonical.length)}`
|
|
1081
|
+
};
|
|
1082
|
+
})
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
function renamePinnedDocumentWorkspace(documents, workspaceId, workspaceName) {
|
|
1086
|
+
return documents.map(
|
|
1087
|
+
(document2) => document2.workspaceId === workspaceId ? { ...document2, workspaceName } : document2
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
function movePinnedDocumentsToWorkspace(documents, sourceWorkspaceId, destination) {
|
|
1091
|
+
return parsePinnedDocuments(
|
|
1092
|
+
documents.map(
|
|
1093
|
+
(document2) => document2.workspaceId === sourceWorkspaceId ? { ...document2, ...destination } : document2
|
|
1094
|
+
)
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// src/FileExplorer/PinnedDocuments.tsx
|
|
858
1099
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1100
|
+
function displayName(document2) {
|
|
1101
|
+
const name = pinnedDocumentName(document2);
|
|
1102
|
+
return name.endsWith(".md") ? name.slice(0, -3) : name;
|
|
1103
|
+
}
|
|
1104
|
+
function PinnedDocumentRow({
|
|
1105
|
+
document: pinnedDocument,
|
|
1106
|
+
selected,
|
|
1107
|
+
onSelect,
|
|
1108
|
+
onUnpin,
|
|
1109
|
+
onRename,
|
|
1110
|
+
onDelete
|
|
1111
|
+
}) {
|
|
1112
|
+
const [showContext, setShowContext] = useState3(false);
|
|
1113
|
+
const [contextPos, setContextPos] = useState3({ x: 0, y: 0 });
|
|
1114
|
+
const [actionError, setActionError] = useState3(null);
|
|
1115
|
+
const contextRef = useRef3(null);
|
|
1116
|
+
const selectRef = useRef3(null);
|
|
1117
|
+
const keyboardMenuRef = useRef3(false);
|
|
1118
|
+
const missing = pinnedDocument.availability === "missing";
|
|
1119
|
+
const name = pinnedDocumentName(pinnedDocument);
|
|
1120
|
+
const displayPath = pinnedDocumentDisplayPath(pinnedDocument);
|
|
1121
|
+
const hasActions = Boolean(onUnpin || onRename || onDelete);
|
|
1122
|
+
const closeMenu = useCallback3((returnFocus) => {
|
|
1123
|
+
setShowContext(false);
|
|
1124
|
+
keyboardMenuRef.current = false;
|
|
1125
|
+
if (returnFocus) selectRef.current?.focus({ preventScroll: true });
|
|
1126
|
+
}, []);
|
|
1127
|
+
const openMenuAt = useCallback3((x, y, keyboard) => {
|
|
1128
|
+
keyboardMenuRef.current = keyboard;
|
|
1129
|
+
setContextPos({ x, y });
|
|
1130
|
+
setShowContext(true);
|
|
1131
|
+
}, []);
|
|
1132
|
+
const handleContextMenu = useCallback3(
|
|
1133
|
+
(event) => {
|
|
1134
|
+
if (!hasActions) return;
|
|
1135
|
+
event.preventDefault();
|
|
1136
|
+
event.stopPropagation();
|
|
1137
|
+
openMenuAt(event.clientX, event.clientY, false);
|
|
1138
|
+
},
|
|
1139
|
+
[hasActions, openMenuAt]
|
|
1140
|
+
);
|
|
1141
|
+
const handleMoreClick = useCallback3(
|
|
1142
|
+
(event) => {
|
|
1143
|
+
event.preventDefault();
|
|
1144
|
+
event.stopPropagation();
|
|
1145
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
1146
|
+
openMenuAt(rect.right, rect.bottom, false);
|
|
1147
|
+
},
|
|
1148
|
+
[openMenuAt]
|
|
1149
|
+
);
|
|
1150
|
+
const openMenuByKeyboard = useCallback3(() => {
|
|
1151
|
+
if (!hasActions) return;
|
|
1152
|
+
const rect = selectRef.current?.getBoundingClientRect();
|
|
1153
|
+
openMenuAt(rect ? rect.left + 16 : 0, rect ? rect.bottom : 0, true);
|
|
1154
|
+
}, [hasActions, openMenuAt]);
|
|
1155
|
+
const menuItems = useCallback3(
|
|
1156
|
+
() => [...contextRef.current?.querySelectorAll('[role="menuitem"]') ?? []],
|
|
1157
|
+
[]
|
|
1158
|
+
);
|
|
1159
|
+
const focusMenuItem = useCallback3(
|
|
1160
|
+
(index) => {
|
|
1161
|
+
const items = menuItems();
|
|
1162
|
+
if (items.length === 0) return;
|
|
1163
|
+
const target = items[(index % items.length + items.length) % items.length];
|
|
1164
|
+
target?.focus({ preventScroll: true });
|
|
1165
|
+
},
|
|
1166
|
+
[menuItems]
|
|
1167
|
+
);
|
|
1168
|
+
const handleMenuKeyDown = useCallback3(
|
|
1169
|
+
(event) => {
|
|
1170
|
+
if (event.key === "Escape" || event.key === "Tab") {
|
|
1171
|
+
event.preventDefault();
|
|
1172
|
+
event.stopPropagation();
|
|
1173
|
+
closeMenu(true);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const items = menuItems();
|
|
1177
|
+
const current = items.indexOf(document.activeElement);
|
|
1178
|
+
switch (event.key) {
|
|
1179
|
+
case "ArrowDown":
|
|
1180
|
+
event.preventDefault();
|
|
1181
|
+
focusMenuItem(current + 1);
|
|
1182
|
+
break;
|
|
1183
|
+
case "ArrowUp":
|
|
1184
|
+
event.preventDefault();
|
|
1185
|
+
focusMenuItem(current - 1);
|
|
1186
|
+
break;
|
|
1187
|
+
case "Home":
|
|
1188
|
+
event.preventDefault();
|
|
1189
|
+
focusMenuItem(0);
|
|
1190
|
+
break;
|
|
1191
|
+
case "End":
|
|
1192
|
+
event.preventDefault();
|
|
1193
|
+
focusMenuItem(items.length - 1);
|
|
1194
|
+
break;
|
|
1195
|
+
default:
|
|
1196
|
+
break;
|
|
1197
|
+
}
|
|
1198
|
+
},
|
|
1199
|
+
[closeMenu, focusMenuItem, menuItems]
|
|
1200
|
+
);
|
|
1201
|
+
const runAction = useCallback3(
|
|
1202
|
+
async (action, fallbackMessage) => {
|
|
1203
|
+
closeMenu(false);
|
|
1204
|
+
setActionError(null);
|
|
1205
|
+
if (!action) return;
|
|
1206
|
+
try {
|
|
1207
|
+
await action(pinnedDocument);
|
|
1208
|
+
} catch (caught) {
|
|
1209
|
+
setActionError(caught instanceof Error ? caught.message : fallbackMessage);
|
|
1210
|
+
}
|
|
1211
|
+
},
|
|
1212
|
+
[closeMenu, pinnedDocument]
|
|
1213
|
+
);
|
|
1214
|
+
useEffect3(() => {
|
|
1215
|
+
if (!showContext) return;
|
|
1216
|
+
function handleOutsideAction(event) {
|
|
1217
|
+
if (!contextRef.current?.contains(event.target)) setShowContext(false);
|
|
1218
|
+
}
|
|
1219
|
+
function handleScroll() {
|
|
1220
|
+
setShowContext(false);
|
|
1221
|
+
}
|
|
1222
|
+
document.addEventListener("click", handleOutsideAction, true);
|
|
1223
|
+
document.addEventListener("contextmenu", handleOutsideAction, true);
|
|
1224
|
+
document.addEventListener("scroll", handleScroll, { capture: true, once: true });
|
|
1225
|
+
return () => {
|
|
1226
|
+
document.removeEventListener("click", handleOutsideAction, true);
|
|
1227
|
+
document.removeEventListener("contextmenu", handleOutsideAction, true);
|
|
1228
|
+
document.removeEventListener("scroll", handleScroll, true);
|
|
1229
|
+
};
|
|
1230
|
+
}, [showContext]);
|
|
1231
|
+
useEffect3(() => {
|
|
1232
|
+
if (showContext && keyboardMenuRef.current) focusMenuItem(0);
|
|
1233
|
+
}, [focusMenuItem, showContext]);
|
|
1234
|
+
useLayoutEffect2(() => {
|
|
1235
|
+
if (!showContext || !contextRef.current) return;
|
|
1236
|
+
const rect = contextRef.current.getBoundingClientRect();
|
|
1237
|
+
setContextPos((current) => ({
|
|
1238
|
+
x: Math.max(4, Math.min(current.x, window.innerWidth - rect.width - 4)),
|
|
1239
|
+
y: Math.max(4, Math.min(current.y, window.innerHeight - rect.height - 4))
|
|
1240
|
+
}));
|
|
1241
|
+
}, [showContext]);
|
|
1242
|
+
return /* @__PURE__ */ jsxs3("div", { className: `db-pinned-document-row${selected ? " db-pinned-document-row--selected" : ""}`, children: [
|
|
1243
|
+
/* @__PURE__ */ jsx3(
|
|
1244
|
+
"button",
|
|
1245
|
+
{
|
|
1246
|
+
ref: selectRef,
|
|
1247
|
+
type: "button",
|
|
1248
|
+
className: "db-pinned-document",
|
|
1249
|
+
"aria-current": selected ? "page" : void 0,
|
|
1250
|
+
"aria-label": `${name}, ${displayPath}${missing ? ", missing" : ""}`,
|
|
1251
|
+
"aria-keyshortcuts": hasActions ? "Shift+F10" : void 0,
|
|
1252
|
+
title: displayPath,
|
|
1253
|
+
onClick: () => void onSelect(pinnedDocument),
|
|
1254
|
+
onContextMenu: handleContextMenu,
|
|
1255
|
+
onKeyDown: (event) => {
|
|
1256
|
+
if (event.key === "ContextMenu" || event.key === "F10" && event.shiftKey) {
|
|
1257
|
+
event.preventDefault();
|
|
1258
|
+
openMenuByKeyboard();
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
children: /* @__PURE__ */ jsxs3("span", { className: "db-pinned-document-heading", children: [
|
|
1262
|
+
/* @__PURE__ */ jsx3("span", { className: "db-pinned-document-name", children: displayName(pinnedDocument) }),
|
|
1263
|
+
missing && /* @__PURE__ */ jsx3("span", { className: "db-pinned-document-missing", children: "(missing)" })
|
|
1264
|
+
] })
|
|
1265
|
+
}
|
|
1266
|
+
),
|
|
1267
|
+
hasActions && /* @__PURE__ */ jsx3(
|
|
1268
|
+
"button",
|
|
1269
|
+
{
|
|
1270
|
+
type: "button",
|
|
1271
|
+
className: `db-pinned-document-more${showContext ? " db-pinned-document-more--active" : ""}`,
|
|
1272
|
+
"aria-label": `More actions for ${name}`,
|
|
1273
|
+
"aria-haspopup": "menu",
|
|
1274
|
+
"aria-expanded": showContext,
|
|
1275
|
+
title: "More actions",
|
|
1276
|
+
onClick: handleMoreClick,
|
|
1277
|
+
onContextMenu: handleMoreClick,
|
|
1278
|
+
children: /* @__PURE__ */ jsx3(MoreIcon, {})
|
|
1279
|
+
}
|
|
1280
|
+
),
|
|
1281
|
+
actionError && /* @__PURE__ */ jsx3("div", { className: "db-tree-error db-pinned-document-error", role: "alert", children: actionError }),
|
|
1282
|
+
showContext && createPortal2(
|
|
1283
|
+
/* @__PURE__ */ jsxs3(
|
|
1284
|
+
"div",
|
|
1285
|
+
{
|
|
1286
|
+
ref: contextRef,
|
|
1287
|
+
className: "db-tree-context",
|
|
1288
|
+
style: { left: contextPos.x, top: contextPos.y },
|
|
1289
|
+
role: "menu",
|
|
1290
|
+
"aria-label": `Actions for ${name}`,
|
|
1291
|
+
onKeyDown: handleMenuKeyDown,
|
|
1292
|
+
children: [
|
|
1293
|
+
onRename && /* @__PURE__ */ jsx3(
|
|
1294
|
+
"button",
|
|
1295
|
+
{
|
|
1296
|
+
type: "button",
|
|
1297
|
+
role: "menuitem",
|
|
1298
|
+
tabIndex: -1,
|
|
1299
|
+
className: "db-tree-context-item",
|
|
1300
|
+
onClick: () => void runAction(onRename, "Unable to rename this document."),
|
|
1301
|
+
children: "Rename"
|
|
1302
|
+
}
|
|
1303
|
+
),
|
|
1304
|
+
onUnpin && /* @__PURE__ */ jsx3(
|
|
1305
|
+
"button",
|
|
1306
|
+
{
|
|
1307
|
+
type: "button",
|
|
1308
|
+
role: "menuitem",
|
|
1309
|
+
tabIndex: -1,
|
|
1310
|
+
className: "db-tree-context-item",
|
|
1311
|
+
onClick: () => void runAction(onUnpin, "Unable to unpin this document."),
|
|
1312
|
+
children: "Unpin"
|
|
1313
|
+
}
|
|
1314
|
+
),
|
|
1315
|
+
onDelete && (onRename || onUnpin) && /* @__PURE__ */ jsx3("div", { className: "db-tree-context-divider", role: "separator" }),
|
|
1316
|
+
onDelete && /* @__PURE__ */ jsx3(
|
|
1317
|
+
"button",
|
|
1318
|
+
{
|
|
1319
|
+
type: "button",
|
|
1320
|
+
role: "menuitem",
|
|
1321
|
+
tabIndex: -1,
|
|
1322
|
+
className: "db-tree-context-item db-tree-context-item--danger",
|
|
1323
|
+
onClick: () => void runAction(onDelete, "Unable to delete this document."),
|
|
1324
|
+
children: "Delete"
|
|
1325
|
+
}
|
|
1326
|
+
)
|
|
1327
|
+
]
|
|
1328
|
+
}
|
|
1329
|
+
),
|
|
1330
|
+
document.body
|
|
1331
|
+
)
|
|
1332
|
+
] });
|
|
1333
|
+
}
|
|
1334
|
+
function PinnedDocuments({
|
|
1335
|
+
documents,
|
|
1336
|
+
activeWorkspaceId,
|
|
1337
|
+
activeFilePath,
|
|
1338
|
+
onSelect,
|
|
1339
|
+
onUnpin,
|
|
1340
|
+
onRename,
|
|
1341
|
+
onDelete
|
|
1342
|
+
}) {
|
|
1343
|
+
const titleId = useId();
|
|
1344
|
+
if (documents.length === 0) return null;
|
|
1345
|
+
return /* @__PURE__ */ jsxs3("section", { className: "db-pinned-documents", "aria-labelledby": titleId, children: [
|
|
1346
|
+
/* @__PURE__ */ jsxs3("h2", { id: titleId, className: "db-explorer-title db-pinned-documents-title", children: [
|
|
1347
|
+
/* @__PURE__ */ jsx3(PinIcon, {}),
|
|
1348
|
+
/* @__PURE__ */ jsx3("span", { children: "Pinned" })
|
|
1349
|
+
] }),
|
|
1350
|
+
/* @__PURE__ */ jsx3("div", { className: "db-pinned-document-list", children: documents.map((document2) => /* @__PURE__ */ jsx3(
|
|
1351
|
+
PinnedDocumentRow,
|
|
1352
|
+
{
|
|
1353
|
+
document: document2,
|
|
1354
|
+
selected: document2.workspaceId === activeWorkspaceId && document2.path === activeFilePath?.replace(/^\/+|\/+$/g, ""),
|
|
1355
|
+
onSelect,
|
|
1356
|
+
onUnpin,
|
|
1357
|
+
onRename,
|
|
1358
|
+
onDelete
|
|
1359
|
+
},
|
|
1360
|
+
`${document2.workspaceId}\0${document2.path}`
|
|
1361
|
+
)) })
|
|
1362
|
+
] });
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/FileExplorer/entry-visibility.ts
|
|
1366
|
+
function isHiddenFileEntry(entry) {
|
|
1367
|
+
const name = entry.path.replace(/^\/+/, "").split("/").pop() ?? "";
|
|
1368
|
+
if (name.startsWith(".")) return true;
|
|
1369
|
+
return entry.kind === "directory" && name.endsWith("_files");
|
|
1370
|
+
}
|
|
1371
|
+
function filterVisibleFileEntries(entries) {
|
|
1372
|
+
return entries.filter((entry) => !isHiddenFileEntry(entry));
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// src/FileExplorer/FileExplorer.tsx
|
|
1376
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
859
1377
|
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".docx", ".pdf", ".dbk", ".zip"]);
|
|
860
1378
|
var INTERNAL_DRAG_TYPE = "application/x-docblocks-entry";
|
|
861
1379
|
var NEW_ITEM_ERROR_ID = "db-new-item-error";
|
|
@@ -892,17 +1410,17 @@ function canMoveTo(entry, directoryPath) {
|
|
|
892
1410
|
function isInternalDrag(dataTransfer) {
|
|
893
1411
|
return Array.from(dataTransfer.types).includes(INTERNAL_DRAG_TYPE);
|
|
894
1412
|
}
|
|
895
|
-
function isHiddenEntry(entry) {
|
|
896
|
-
const name = entry.path.replace(/^\/+/, "").split("/").pop() ?? "";
|
|
897
|
-
if (name.startsWith(".")) return true;
|
|
898
|
-
return entry.kind === "directory" && name.endsWith("_files");
|
|
899
|
-
}
|
|
900
|
-
function filterVisible(entries) {
|
|
901
|
-
return entries.filter((e) => !isHiddenEntry(e));
|
|
902
|
-
}
|
|
903
1413
|
function FileExplorer({
|
|
904
1414
|
provider,
|
|
905
1415
|
activeFilePath,
|
|
1416
|
+
activeWorkspaceId,
|
|
1417
|
+
pinnedDocuments = [],
|
|
1418
|
+
pinnedPaths = [],
|
|
1419
|
+
onPinnedDocumentSelect,
|
|
1420
|
+
onPinnedDocumentUnpin,
|
|
1421
|
+
onPinnedDocumentRename,
|
|
1422
|
+
onPinnedDocumentDelete,
|
|
1423
|
+
onTogglePin,
|
|
906
1424
|
onSelect,
|
|
907
1425
|
onTreeMutation,
|
|
908
1426
|
onTreeChange,
|
|
@@ -913,33 +1431,37 @@ function FileExplorer({
|
|
|
913
1431
|
className
|
|
914
1432
|
}) {
|
|
915
1433
|
const tree = useFileTree(provider);
|
|
916
|
-
const { childEntries } = tree;
|
|
1434
|
+
const { childEntries, childIssues } = tree;
|
|
917
1435
|
const { reveal } = tree;
|
|
918
1436
|
const git = useGitContext();
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const [
|
|
924
|
-
const [
|
|
925
|
-
const [
|
|
926
|
-
const
|
|
927
|
-
const [
|
|
928
|
-
const [
|
|
929
|
-
const [
|
|
930
|
-
const [
|
|
931
|
-
const [
|
|
932
|
-
const
|
|
933
|
-
const [
|
|
934
|
-
const
|
|
935
|
-
|
|
1437
|
+
const pinnedPathSet = useMemo(
|
|
1438
|
+
() => new Set(pinnedPaths.map((path) => normalisePath2(path))),
|
|
1439
|
+
[pinnedPaths]
|
|
1440
|
+
);
|
|
1441
|
+
const [newItemName, setNewItemName] = useState4("");
|
|
1442
|
+
const [newItemType, setNewItemType] = useState4(null);
|
|
1443
|
+
const [newItemCreationPending, setNewItemCreationPending] = useState4(false);
|
|
1444
|
+
const newItemCreationPendingRef = useRef4(false);
|
|
1445
|
+
const [newItemError, setNewItemError] = useState4(null);
|
|
1446
|
+
const [dragOver, setDragOver] = useState4(false);
|
|
1447
|
+
const [draggedEntry, setDraggedEntry] = useState4(null);
|
|
1448
|
+
const [dropTarget, setDropTarget] = useState4(null);
|
|
1449
|
+
const [moveError, setMoveError] = useState4(null);
|
|
1450
|
+
const [movePanelOpen, setMovePanelOpen] = useState4(false);
|
|
1451
|
+
const [moveDestinationId, setMoveDestinationId] = useState4(moveDestinations[0]?.id ?? "");
|
|
1452
|
+
const [movingToWorkspace, setMovingToWorkspace] = useState4(false);
|
|
1453
|
+
const [moveWorkspaceError, setMoveWorkspaceError] = useState4(null);
|
|
1454
|
+
const dragCounter = useRef4(0);
|
|
1455
|
+
const [activePath, setActivePath] = useState4(null);
|
|
1456
|
+
const treeRef = useRef4(null);
|
|
1457
|
+
useEffect4(() => {
|
|
936
1458
|
if (moveDestinations.some((destination) => destination.id === moveDestinationId)) return;
|
|
937
1459
|
setMoveDestinationId(moveDestinations[0]?.id ?? "");
|
|
938
1460
|
}, [moveDestinationId, moveDestinations]);
|
|
939
|
-
|
|
1461
|
+
useEffect4(() => {
|
|
940
1462
|
if (activeFilePath) reveal(activeFilePath, "file");
|
|
941
1463
|
}, [activeFilePath, provider, reveal]);
|
|
942
|
-
const hasSupported =
|
|
1464
|
+
const hasSupported = useCallback4((dt) => {
|
|
943
1465
|
for (const item of Array.from(dt.items)) {
|
|
944
1466
|
if (item.kind !== "file") continue;
|
|
945
1467
|
const name = item.getAsFile?.()?.name;
|
|
@@ -949,7 +1471,7 @@ function FileExplorer({
|
|
|
949
1471
|
}
|
|
950
1472
|
return dt.items.length > 0;
|
|
951
1473
|
}, []);
|
|
952
|
-
const handleDragEnter =
|
|
1474
|
+
const handleDragEnter = useCallback4(
|
|
953
1475
|
(e) => {
|
|
954
1476
|
if (isInternalDrag(e.dataTransfer)) return;
|
|
955
1477
|
e.preventDefault();
|
|
@@ -958,12 +1480,12 @@ function FileExplorer({
|
|
|
958
1480
|
},
|
|
959
1481
|
[hasSupported, dragCounter]
|
|
960
1482
|
);
|
|
961
|
-
const handleDragOver =
|
|
1483
|
+
const handleDragOver = useCallback4((e) => {
|
|
962
1484
|
if (isInternalDrag(e.dataTransfer)) return;
|
|
963
1485
|
e.preventDefault();
|
|
964
1486
|
e.dataTransfer.dropEffect = "copy";
|
|
965
1487
|
}, []);
|
|
966
|
-
const handleDragLeave =
|
|
1488
|
+
const handleDragLeave = useCallback4(
|
|
967
1489
|
(e) => {
|
|
968
1490
|
if (isInternalDrag(e.dataTransfer)) return;
|
|
969
1491
|
e.preventDefault();
|
|
@@ -975,7 +1497,7 @@ function FileExplorer({
|
|
|
975
1497
|
},
|
|
976
1498
|
[dragCounter]
|
|
977
1499
|
);
|
|
978
|
-
const handleDrop =
|
|
1500
|
+
const handleDrop = useCallback4(
|
|
979
1501
|
(e) => {
|
|
980
1502
|
if (isInternalDrag(e.dataTransfer)) return;
|
|
981
1503
|
e.preventDefault();
|
|
@@ -989,7 +1511,7 @@ function FileExplorer({
|
|
|
989
1511
|
},
|
|
990
1512
|
[onImportFiles]
|
|
991
1513
|
);
|
|
992
|
-
const handleSelect =
|
|
1514
|
+
const handleSelect = useCallback4(
|
|
993
1515
|
(path) => {
|
|
994
1516
|
const findKind = (p) => {
|
|
995
1517
|
for (const e of tree.entries) {
|
|
@@ -1008,7 +1530,7 @@ function FileExplorer({
|
|
|
1008
1530
|
},
|
|
1009
1531
|
[tree, onSelect, childEntries]
|
|
1010
1532
|
);
|
|
1011
|
-
const handleNewItemSubmit =
|
|
1533
|
+
const handleNewItemSubmit = useCallback4(async () => {
|
|
1012
1534
|
if (newItemCreationPendingRef.current) return;
|
|
1013
1535
|
if (!newItemName.trim()) {
|
|
1014
1536
|
setNewItemType(null);
|
|
@@ -1041,7 +1563,7 @@ function FileExplorer({
|
|
|
1041
1563
|
setNewItemType(null);
|
|
1042
1564
|
onTreeChange?.({ type: "create", path: createdPath });
|
|
1043
1565
|
}, [newItemName, newItemType, tree, onTreeChange]);
|
|
1044
|
-
const handleMoveToWorkspace =
|
|
1566
|
+
const handleMoveToWorkspace = useCallback4(async () => {
|
|
1045
1567
|
if (!onMoveToWorkspace || !moveDestinationId || movingToWorkspace) return;
|
|
1046
1568
|
setMovingToWorkspace(true);
|
|
1047
1569
|
setMoveWorkspaceError(null);
|
|
@@ -1055,7 +1577,7 @@ function FileExplorer({
|
|
|
1055
1577
|
setMovingToWorkspace(false);
|
|
1056
1578
|
}
|
|
1057
1579
|
}, [moveDestinationId, movingToWorkspace, onMoveToWorkspace]);
|
|
1058
|
-
const runTreeMutation =
|
|
1580
|
+
const runTreeMutation = useCallback4(
|
|
1059
1581
|
async (change, mutate) => {
|
|
1060
1582
|
if (onTreeMutation) await onTreeMutation(change, mutate);
|
|
1061
1583
|
else await mutate();
|
|
@@ -1063,14 +1585,14 @@ function FileExplorer({
|
|
|
1063
1585
|
},
|
|
1064
1586
|
[onTreeMutation, onTreeChange]
|
|
1065
1587
|
);
|
|
1066
|
-
const handleDelete =
|
|
1588
|
+
const handleDelete = useCallback4(
|
|
1067
1589
|
async (path, kind) => {
|
|
1068
1590
|
const change = { type: "delete", path, kind };
|
|
1069
1591
|
await runTreeMutation(change, () => tree.deleteEntry(path));
|
|
1070
1592
|
},
|
|
1071
1593
|
[tree, runTreeMutation]
|
|
1072
1594
|
);
|
|
1073
|
-
const handleRename =
|
|
1595
|
+
const handleRename = useCallback4(
|
|
1074
1596
|
async (oldPath, newPath, kind) => {
|
|
1075
1597
|
setMoveError(null);
|
|
1076
1598
|
const change = { type: "move", oldPath, newPath, kind };
|
|
@@ -1086,7 +1608,7 @@ function FileExplorer({
|
|
|
1086
1608
|
},
|
|
1087
1609
|
[tree, runTreeMutation, onTreeChange]
|
|
1088
1610
|
);
|
|
1089
|
-
const handleMove =
|
|
1611
|
+
const handleMove = useCallback4(
|
|
1090
1612
|
async (entry, directoryPath) => {
|
|
1091
1613
|
if (!canMoveTo(entry, directoryPath)) return;
|
|
1092
1614
|
const newPath = pathInDirectory(entry, directoryPath);
|
|
@@ -1097,18 +1619,18 @@ function FileExplorer({
|
|
|
1097
1619
|
},
|
|
1098
1620
|
[handleRename, tree]
|
|
1099
1621
|
);
|
|
1100
|
-
const handleInternalDragStart =
|
|
1622
|
+
const handleInternalDragStart = useCallback4((e, entry) => {
|
|
1101
1623
|
e.dataTransfer.effectAllowed = "move";
|
|
1102
1624
|
e.dataTransfer.setData(INTERNAL_DRAG_TYPE, entry.path);
|
|
1103
1625
|
e.dataTransfer.setData("text/plain", entry.path);
|
|
1104
1626
|
setDraggedEntry(entry);
|
|
1105
1627
|
setMoveError(null);
|
|
1106
1628
|
}, []);
|
|
1107
|
-
const handleInternalDragEnd =
|
|
1629
|
+
const handleInternalDragEnd = useCallback4(() => {
|
|
1108
1630
|
setDraggedEntry(null);
|
|
1109
1631
|
setDropTarget(null);
|
|
1110
1632
|
}, []);
|
|
1111
|
-
const handleEntryDragOver =
|
|
1633
|
+
const handleEntryDragOver = useCallback4(
|
|
1112
1634
|
(e, entry) => {
|
|
1113
1635
|
if (!isInternalDrag(e.dataTransfer)) return;
|
|
1114
1636
|
e.stopPropagation();
|
|
@@ -1123,7 +1645,7 @@ function FileExplorer({
|
|
|
1123
1645
|
},
|
|
1124
1646
|
[draggedEntry]
|
|
1125
1647
|
);
|
|
1126
|
-
const handleEntryDrop =
|
|
1648
|
+
const handleEntryDrop = useCallback4(
|
|
1127
1649
|
(e, entry) => {
|
|
1128
1650
|
if (!isInternalDrag(e.dataTransfer)) return;
|
|
1129
1651
|
e.preventDefault();
|
|
@@ -1135,7 +1657,7 @@ function FileExplorer({
|
|
|
1135
1657
|
},
|
|
1136
1658
|
[draggedEntry, handleMove]
|
|
1137
1659
|
);
|
|
1138
|
-
const handleRootDragOver =
|
|
1660
|
+
const handleRootDragOver = useCallback4(
|
|
1139
1661
|
(e) => {
|
|
1140
1662
|
if (!isInternalDrag(e.dataTransfer) || !draggedEntry) return;
|
|
1141
1663
|
e.preventDefault();
|
|
@@ -1146,7 +1668,7 @@ function FileExplorer({
|
|
|
1146
1668
|
},
|
|
1147
1669
|
[draggedEntry]
|
|
1148
1670
|
);
|
|
1149
|
-
const handleRootDrop =
|
|
1671
|
+
const handleRootDrop = useCallback4(
|
|
1150
1672
|
(e) => {
|
|
1151
1673
|
if (!isInternalDrag(e.dataTransfer)) return;
|
|
1152
1674
|
e.preventDefault();
|
|
@@ -1156,7 +1678,7 @@ function FileExplorer({
|
|
|
1156
1678
|
},
|
|
1157
1679
|
[draggedEntry, handleMove]
|
|
1158
1680
|
);
|
|
1159
|
-
const badgeFor =
|
|
1681
|
+
const badgeFor = useCallback4(
|
|
1160
1682
|
(entry) => {
|
|
1161
1683
|
if (!git?.repo) return void 0;
|
|
1162
1684
|
const key = entry.path.startsWith("/") ? entry.path : `/${entry.path}`;
|
|
@@ -1166,7 +1688,7 @@ function FileExplorer({
|
|
|
1166
1688
|
},
|
|
1167
1689
|
[git]
|
|
1168
1690
|
);
|
|
1169
|
-
const gitActionsFor =
|
|
1691
|
+
const gitActionsFor = useCallback4(
|
|
1170
1692
|
(entry) => {
|
|
1171
1693
|
if (!git?.repo || entry.kind !== "file") return void 0;
|
|
1172
1694
|
const path = entry.path.startsWith("/") ? entry.path : `/${entry.path}`;
|
|
@@ -1183,7 +1705,7 @@ function FileExplorer({
|
|
|
1183
1705
|
roots: tree.entries,
|
|
1184
1706
|
childrenOf: (dirPath) => getEquivalentPathValue(childEntries, dirPath) ?? [],
|
|
1185
1707
|
isExpanded: (dirPath) => hasEquivalentPath2(tree.expanded, dirPath),
|
|
1186
|
-
isVisible: (entry) => !
|
|
1708
|
+
isVisible: (entry) => !isHiddenFileEntry(entry)
|
|
1187
1709
|
}),
|
|
1188
1710
|
[tree.entries, tree.expanded, childEntries]
|
|
1189
1711
|
);
|
|
@@ -1193,7 +1715,7 @@ function FileExplorer({
|
|
|
1193
1715
|
return visibleRows.find((row) => normalisePath2(row.path) === target)?.path ?? null;
|
|
1194
1716
|
}, [visibleRows, tree.selectedPath]);
|
|
1195
1717
|
const activeRowPath = resolveActiveRow(visibleRows, activePath, selectedRowPath);
|
|
1196
|
-
const focusRow =
|
|
1718
|
+
const focusRow = useCallback4((path) => {
|
|
1197
1719
|
const rows = treeRef.current?.querySelectorAll('[role="treeitem"]');
|
|
1198
1720
|
for (const row of rows ?? []) {
|
|
1199
1721
|
if (row.dataset.path === path) {
|
|
@@ -1202,7 +1724,7 @@ function FileExplorer({
|
|
|
1202
1724
|
}
|
|
1203
1725
|
}
|
|
1204
1726
|
}, []);
|
|
1205
|
-
const handleTreeKeyDown =
|
|
1727
|
+
const handleTreeKeyDown = useCallback4(
|
|
1206
1728
|
(e) => {
|
|
1207
1729
|
const target = e.target;
|
|
1208
1730
|
if (!target.classList?.contains("db-tree-row")) return;
|
|
@@ -1227,41 +1749,67 @@ function FileExplorer({
|
|
|
1227
1749
|
},
|
|
1228
1750
|
[visibleRows, activeRowPath, focusRow, tree]
|
|
1229
1751
|
);
|
|
1230
|
-
const renderEntries =
|
|
1752
|
+
const renderEntries = useCallback4(
|
|
1231
1753
|
(entries, depth) => {
|
|
1232
|
-
const visible =
|
|
1233
|
-
return visible.map((entry, index) =>
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1754
|
+
const visible = filterVisibleFileEntries(entries);
|
|
1755
|
+
return visible.map((entry, index) => {
|
|
1756
|
+
const childIssue = entry.kind === "directory" ? getEquivalentPathValue(childIssues, entry.path) : void 0;
|
|
1757
|
+
return /* @__PURE__ */ jsx4(
|
|
1758
|
+
FileTreeNode,
|
|
1759
|
+
{
|
|
1760
|
+
entry,
|
|
1761
|
+
depth,
|
|
1762
|
+
focusable: entry.path === activeRowPath,
|
|
1763
|
+
posInSet: index + 1,
|
|
1764
|
+
setSize: visible.length,
|
|
1765
|
+
onRowFocus: setActivePath,
|
|
1766
|
+
expanded: hasEquivalentPath2(tree.expanded, entry.path),
|
|
1767
|
+
selected: tree.selectedPath !== null && normalisePath2(tree.selectedPath) === normalisePath2(entry.path),
|
|
1768
|
+
badge: badgeFor(entry),
|
|
1769
|
+
gitActions: gitActionsFor(entry),
|
|
1770
|
+
onToggle: tree.toggleExpand,
|
|
1771
|
+
onSelect: handleSelect,
|
|
1772
|
+
confirmDelete,
|
|
1773
|
+
onDelete: handleDelete,
|
|
1774
|
+
onRename: handleRename,
|
|
1775
|
+
pinned: entry.kind === "file" && pinnedPathSet.has(normalisePath2(entry.path)),
|
|
1776
|
+
onTogglePin: entry.kind === "file" ? onTogglePin : void 0,
|
|
1777
|
+
draggable: true,
|
|
1778
|
+
dragging: draggedEntry?.path === entry.path,
|
|
1779
|
+
dropTarget: dropTarget === entry.path,
|
|
1780
|
+
onDragStart: handleInternalDragStart,
|
|
1781
|
+
onDragEnd: handleInternalDragEnd,
|
|
1782
|
+
onDragOverEntry: handleEntryDragOver,
|
|
1783
|
+
onDropEntry: handleEntryDrop,
|
|
1784
|
+
renderChildren: (dirPath) => {
|
|
1785
|
+
const children = getEquivalentPathValue(childEntries, dirPath) ?? [];
|
|
1786
|
+
return renderEntries(children, depth + 1);
|
|
1787
|
+
},
|
|
1788
|
+
childError: childIssue ? /* @__PURE__ */ jsxs4(
|
|
1789
|
+
"div",
|
|
1790
|
+
{
|
|
1791
|
+
className: "db-tree-error db-tree-error--child",
|
|
1792
|
+
role: "alert",
|
|
1793
|
+
"data-directory-path": childIssue.directoryPath,
|
|
1794
|
+
children: [
|
|
1795
|
+
/* @__PURE__ */ jsx4("span", { children: childIssue.message }),
|
|
1796
|
+
" ",
|
|
1797
|
+
/* @__PURE__ */ jsx4(
|
|
1798
|
+
"button",
|
|
1799
|
+
{
|
|
1800
|
+
type: "button",
|
|
1801
|
+
className: "db-tree-error-retry",
|
|
1802
|
+
onClick: () => void tree.retryDirectory(childIssue.directoryPath),
|
|
1803
|
+
children: "Retry folder"
|
|
1804
|
+
}
|
|
1805
|
+
)
|
|
1806
|
+
]
|
|
1807
|
+
}
|
|
1808
|
+
) : void 0
|
|
1809
|
+
},
|
|
1810
|
+
entry.path
|
|
1811
|
+
);
|
|
1812
|
+
});
|
|
1265
1813
|
},
|
|
1266
1814
|
[
|
|
1267
1815
|
tree,
|
|
@@ -1270,6 +1818,7 @@ function FileExplorer({
|
|
|
1270
1818
|
handleDelete,
|
|
1271
1819
|
handleRename,
|
|
1272
1820
|
childEntries,
|
|
1821
|
+
childIssues,
|
|
1273
1822
|
badgeFor,
|
|
1274
1823
|
gitActionsFor,
|
|
1275
1824
|
draggedEntry,
|
|
@@ -1278,10 +1827,12 @@ function FileExplorer({
|
|
|
1278
1827
|
handleInternalDragEnd,
|
|
1279
1828
|
handleEntryDragOver,
|
|
1280
1829
|
handleEntryDrop,
|
|
1281
|
-
activeRowPath
|
|
1830
|
+
activeRowPath,
|
|
1831
|
+
pinnedPathSet,
|
|
1832
|
+
onTogglePin
|
|
1282
1833
|
]
|
|
1283
1834
|
);
|
|
1284
|
-
return /* @__PURE__ */
|
|
1835
|
+
return /* @__PURE__ */ jsxs4(
|
|
1285
1836
|
"div",
|
|
1286
1837
|
{
|
|
1287
1838
|
className: `db-file-explorer ${dragOver ? "db-file-explorer--drop-active" : ""} ${className ?? ""}`,
|
|
@@ -1290,10 +1841,22 @@ function FileExplorer({
|
|
|
1290
1841
|
onDragLeave: handleDragLeave,
|
|
1291
1842
|
onDrop: handleDrop,
|
|
1292
1843
|
children: [
|
|
1293
|
-
/* @__PURE__ */
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1844
|
+
onPinnedDocumentSelect && /* @__PURE__ */ jsx4(
|
|
1845
|
+
PinnedDocuments,
|
|
1846
|
+
{
|
|
1847
|
+
documents: pinnedDocuments,
|
|
1848
|
+
activeWorkspaceId,
|
|
1849
|
+
activeFilePath,
|
|
1850
|
+
onSelect: onPinnedDocumentSelect,
|
|
1851
|
+
onUnpin: onPinnedDocumentUnpin,
|
|
1852
|
+
onRename: onPinnedDocumentRename,
|
|
1853
|
+
onDelete: onPinnedDocumentDelete
|
|
1854
|
+
}
|
|
1855
|
+
),
|
|
1856
|
+
/* @__PURE__ */ jsxs4("div", { className: "db-explorer-toolbar", children: [
|
|
1857
|
+
/* @__PURE__ */ jsx4("span", { className: "db-explorer-title", children: "Files" }),
|
|
1858
|
+
/* @__PURE__ */ jsxs4("div", { className: "db-explorer-actions", children: [
|
|
1859
|
+
/* @__PURE__ */ jsx4(
|
|
1297
1860
|
"button",
|
|
1298
1861
|
{
|
|
1299
1862
|
className: "db-explorer-btn",
|
|
@@ -1304,10 +1867,10 @@ function FileExplorer({
|
|
|
1304
1867
|
},
|
|
1305
1868
|
title: "New Folder",
|
|
1306
1869
|
"aria-label": "New Folder",
|
|
1307
|
-
children: /* @__PURE__ */
|
|
1870
|
+
children: /* @__PURE__ */ jsx4(NewFolderIcon, {})
|
|
1308
1871
|
}
|
|
1309
1872
|
),
|
|
1310
|
-
/* @__PURE__ */
|
|
1873
|
+
/* @__PURE__ */ jsx4(
|
|
1311
1874
|
"button",
|
|
1312
1875
|
{
|
|
1313
1876
|
className: "db-explorer-btn",
|
|
@@ -1318,12 +1881,12 @@ function FileExplorer({
|
|
|
1318
1881
|
},
|
|
1319
1882
|
title: "New File",
|
|
1320
1883
|
"aria-label": "New File",
|
|
1321
|
-
children: /* @__PURE__ */
|
|
1884
|
+
children: /* @__PURE__ */ jsx4(NewFileIcon, {})
|
|
1322
1885
|
}
|
|
1323
1886
|
)
|
|
1324
1887
|
] })
|
|
1325
1888
|
] }),
|
|
1326
|
-
onMoveToWorkspace && /* @__PURE__ */
|
|
1889
|
+
onMoveToWorkspace && /* @__PURE__ */ jsx4("div", { className: "db-transient-move", children: !movePanelOpen ? /* @__PURE__ */ jsx4(
|
|
1327
1890
|
"button",
|
|
1328
1891
|
{
|
|
1329
1892
|
type: "button",
|
|
@@ -1334,7 +1897,7 @@ function FileExplorer({
|
|
|
1334
1897
|
},
|
|
1335
1898
|
children: "Move this into a workspace"
|
|
1336
1899
|
}
|
|
1337
|
-
) : /* @__PURE__ */
|
|
1900
|
+
) : /* @__PURE__ */ jsxs4(
|
|
1338
1901
|
"form",
|
|
1339
1902
|
{
|
|
1340
1903
|
className: "db-transient-move-form",
|
|
@@ -1344,8 +1907,8 @@ function FileExplorer({
|
|
|
1344
1907
|
void handleMoveToWorkspace();
|
|
1345
1908
|
},
|
|
1346
1909
|
children: [
|
|
1347
|
-
/* @__PURE__ */
|
|
1348
|
-
/* @__PURE__ */
|
|
1910
|
+
/* @__PURE__ */ jsx4("label", { className: "db-transient-move-label", htmlFor: "db-transient-move-destination", children: "Move this document into" }),
|
|
1911
|
+
/* @__PURE__ */ jsx4(
|
|
1349
1912
|
"select",
|
|
1350
1913
|
{
|
|
1351
1914
|
id: "db-transient-move-destination",
|
|
@@ -1356,13 +1919,13 @@ function FileExplorer({
|
|
|
1356
1919
|
setMoveDestinationId(event.currentTarget.value);
|
|
1357
1920
|
setMoveWorkspaceError(null);
|
|
1358
1921
|
},
|
|
1359
|
-
children: moveDestinations.map((destination) => /* @__PURE__ */
|
|
1922
|
+
children: moveDestinations.map((destination) => /* @__PURE__ */ jsx4("option", { value: destination.id, children: destination.name }, destination.id))
|
|
1360
1923
|
}
|
|
1361
1924
|
),
|
|
1362
|
-
moveDestinations.length === 0 && /* @__PURE__ */
|
|
1363
|
-
moveWorkspaceError && /* @__PURE__ */
|
|
1364
|
-
/* @__PURE__ */
|
|
1365
|
-
/* @__PURE__ */
|
|
1925
|
+
moveDestinations.length === 0 && /* @__PURE__ */ jsx4("p", { className: "db-transient-move-hint", children: "Open or create another workspace first." }),
|
|
1926
|
+
moveWorkspaceError && /* @__PURE__ */ jsx4("div", { className: "db-transient-move-error", role: "alert", children: moveWorkspaceError }),
|
|
1927
|
+
/* @__PURE__ */ jsxs4("div", { className: "db-transient-move-actions", children: [
|
|
1928
|
+
/* @__PURE__ */ jsx4(
|
|
1366
1929
|
"button",
|
|
1367
1930
|
{
|
|
1368
1931
|
type: "button",
|
|
@@ -1375,7 +1938,7 @@ function FileExplorer({
|
|
|
1375
1938
|
children: "Cancel"
|
|
1376
1939
|
}
|
|
1377
1940
|
),
|
|
1378
|
-
/* @__PURE__ */
|
|
1941
|
+
/* @__PURE__ */ jsx4(
|
|
1379
1942
|
"button",
|
|
1380
1943
|
{
|
|
1381
1944
|
type: "submit",
|
|
@@ -1388,8 +1951,8 @@ function FileExplorer({
|
|
|
1388
1951
|
]
|
|
1389
1952
|
}
|
|
1390
1953
|
) }),
|
|
1391
|
-
newItemType && /* @__PURE__ */
|
|
1392
|
-
/* @__PURE__ */
|
|
1954
|
+
newItemType && /* @__PURE__ */ jsxs4("div", { className: "db-new-item", children: [
|
|
1955
|
+
/* @__PURE__ */ jsxs4(
|
|
1393
1956
|
"form",
|
|
1394
1957
|
{
|
|
1395
1958
|
className: "db-new-item-row",
|
|
@@ -1399,7 +1962,7 @@ function FileExplorer({
|
|
|
1399
1962
|
void handleNewItemSubmit();
|
|
1400
1963
|
},
|
|
1401
1964
|
children: [
|
|
1402
|
-
/* @__PURE__ */
|
|
1965
|
+
/* @__PURE__ */ jsx4(
|
|
1403
1966
|
"input",
|
|
1404
1967
|
{
|
|
1405
1968
|
className: "db-new-item-input",
|
|
@@ -1423,20 +1986,36 @@ function FileExplorer({
|
|
|
1423
1986
|
autoFocus: true
|
|
1424
1987
|
}
|
|
1425
1988
|
),
|
|
1426
|
-
newItemType === "file" && /* @__PURE__ */
|
|
1427
|
-
/* @__PURE__ */
|
|
1989
|
+
newItemType === "file" && /* @__PURE__ */ jsx4("span", { className: "db-new-item-suffix", children: ".md" }),
|
|
1990
|
+
/* @__PURE__ */ jsx4("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
|
|
1428
1991
|
]
|
|
1429
1992
|
}
|
|
1430
1993
|
),
|
|
1431
|
-
newItemError && /* @__PURE__ */
|
|
1432
|
-
] }),
|
|
1433
|
-
moveError && /* @__PURE__ */ jsx3("div", { className: "db-tree-error", role: "alert", children: moveError }),
|
|
1434
|
-
tree.error && /* @__PURE__ */ jsxs3("div", { className: "db-tree-error", role: "alert", children: [
|
|
1435
|
-
/* @__PURE__ */ jsx3("span", { children: tree.error }),
|
|
1436
|
-
" ",
|
|
1437
|
-
/* @__PURE__ */ jsx3("button", { type: "button", className: "db-tree-error-retry", onClick: () => void tree.refresh(), children: "Retry" })
|
|
1994
|
+
newItemError && /* @__PURE__ */ jsx4("div", { id: NEW_ITEM_ERROR_ID, className: "db-tree-error", role: "alert", children: newItemError })
|
|
1438
1995
|
] }),
|
|
1439
|
-
|
|
1996
|
+
moveError && /* @__PURE__ */ jsx4("div", { className: "db-tree-error", role: "alert", children: moveError }),
|
|
1997
|
+
tree.rootIssue && /* @__PURE__ */ jsxs4(
|
|
1998
|
+
"div",
|
|
1999
|
+
{
|
|
2000
|
+
className: "db-tree-error",
|
|
2001
|
+
role: "alert",
|
|
2002
|
+
"data-directory-path": tree.rootIssue.directoryPath,
|
|
2003
|
+
children: [
|
|
2004
|
+
/* @__PURE__ */ jsx4("span", { children: tree.rootIssue.message }),
|
|
2005
|
+
" ",
|
|
2006
|
+
/* @__PURE__ */ jsx4(
|
|
2007
|
+
"button",
|
|
2008
|
+
{
|
|
2009
|
+
type: "button",
|
|
2010
|
+
className: "db-tree-error-retry",
|
|
2011
|
+
onClick: () => void tree.retryDirectory(""),
|
|
2012
|
+
children: "Retry"
|
|
2013
|
+
}
|
|
2014
|
+
)
|
|
2015
|
+
]
|
|
2016
|
+
}
|
|
2017
|
+
),
|
|
2018
|
+
tree.loading ? /* @__PURE__ */ jsx4("div", { className: "db-tree", role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx4("div", { className: "db-tree-loading", children: "Loading..." }) }) : filterVisibleFileEntries(tree.entries).length === 0 ? /* @__PURE__ */ jsx4("div", { className: "db-tree", children: /* @__PURE__ */ jsx4("div", { className: "db-tree-empty", children: "No files yet" }) }) : /* @__PURE__ */ jsx4(
|
|
1440
2019
|
"div",
|
|
1441
2020
|
{
|
|
1442
2021
|
ref: treeRef,
|
|
@@ -1455,17 +2034,17 @@ function FileExplorer({
|
|
|
1455
2034
|
}
|
|
1456
2035
|
|
|
1457
2036
|
// src/WorkspacePicker/WorkspacePicker.tsx
|
|
1458
|
-
import { Fragment as Fragment2, useState as
|
|
2037
|
+
import { Fragment as Fragment2, useState as useState5, useEffect as useEffect5, useCallback as useCallback5, useRef as useRef5 } from "react";
|
|
1459
2038
|
import { listWorkspaces, saveWorkspace, touchWorkspace } from "@bendyline/docblocks/workspace";
|
|
1460
2039
|
import { isElectronHost } from "@bendyline/docblocks/host";
|
|
1461
|
-
import { jsx as
|
|
2040
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1462
2041
|
function isNativeFileSystemSupported() {
|
|
1463
2042
|
return typeof globalThis !== "undefined" && typeof globalThis.showDirectoryPicker === "function";
|
|
1464
2043
|
}
|
|
1465
2044
|
function WorkspacePath({ path }) {
|
|
1466
|
-
return path.split(/([\\/])/).map((segment, index) => /* @__PURE__ */
|
|
2045
|
+
return path.split(/([\\/])/).map((segment, index) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1467
2046
|
segment,
|
|
1468
|
-
(segment === "\\" || segment === "/") && /* @__PURE__ */
|
|
2047
|
+
(segment === "\\" || segment === "/") && /* @__PURE__ */ jsx5("wbr", {})
|
|
1469
2048
|
] }, index));
|
|
1470
2049
|
}
|
|
1471
2050
|
function WorkspacePicker({
|
|
@@ -1476,35 +2055,50 @@ function WorkspacePicker({
|
|
|
1476
2055
|
refreshKey,
|
|
1477
2056
|
className
|
|
1478
2057
|
}) {
|
|
1479
|
-
const [workspaces, setWorkspaces] =
|
|
1480
|
-
const [isOpen, setIsOpen] =
|
|
1481
|
-
const [creatingNew, setCreatingNew] =
|
|
1482
|
-
const [newWorkspaceName, setNewWorkspaceName] =
|
|
1483
|
-
const [newWorkspaceError, setNewWorkspaceError] =
|
|
1484
|
-
const [newWorkspacePending, setNewWorkspacePending] =
|
|
1485
|
-
const pickerRef =
|
|
1486
|
-
|
|
2058
|
+
const [workspaces, setWorkspaces] = useState5([]);
|
|
2059
|
+
const [isOpen, setIsOpen] = useState5(false);
|
|
2060
|
+
const [creatingNew, setCreatingNew] = useState5(false);
|
|
2061
|
+
const [newWorkspaceName, setNewWorkspaceName] = useState5("");
|
|
2062
|
+
const [newWorkspaceError, setNewWorkspaceError] = useState5(null);
|
|
2063
|
+
const [newWorkspacePending, setNewWorkspacePending] = useState5(false);
|
|
2064
|
+
const pickerRef = useRef5(null);
|
|
2065
|
+
const triggerRef = useRef5(null);
|
|
2066
|
+
const closeDropdown = useCallback5((returnFocus) => {
|
|
2067
|
+
setIsOpen(false);
|
|
2068
|
+
setCreatingNew(false);
|
|
2069
|
+
setNewWorkspaceError(null);
|
|
2070
|
+
if (returnFocus) triggerRef.current?.focus({ preventScroll: true });
|
|
2071
|
+
}, []);
|
|
2072
|
+
useEffect5(() => {
|
|
1487
2073
|
if (!isOpen) return;
|
|
1488
2074
|
function handleOutsideClick(e) {
|
|
1489
2075
|
if (pickerRef.current && !pickerRef.current.contains(e.target)) {
|
|
1490
|
-
|
|
1491
|
-
setCreatingNew(false);
|
|
1492
|
-
setNewWorkspaceError(null);
|
|
2076
|
+
closeDropdown(false);
|
|
1493
2077
|
}
|
|
1494
2078
|
}
|
|
2079
|
+
function handleKeyDown(e) {
|
|
2080
|
+
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
2081
|
+
e.preventDefault();
|
|
2082
|
+
e.stopPropagation();
|
|
2083
|
+
closeDropdown(true);
|
|
2084
|
+
}
|
|
1495
2085
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
1496
|
-
|
|
1497
|
-
|
|
2086
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
2087
|
+
return () => {
|
|
2088
|
+
document.removeEventListener("mousedown", handleOutsideClick);
|
|
2089
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
2090
|
+
};
|
|
2091
|
+
}, [closeDropdown, isOpen]);
|
|
1498
2092
|
const electron = isElectronHost();
|
|
1499
|
-
const refresh =
|
|
2093
|
+
const refresh = useCallback5(async () => {
|
|
1500
2094
|
const list = await listWorkspaces();
|
|
1501
2095
|
const filtered = electron ? list.filter((w) => w.type === "electron-native" || w.type === "transient") : list.filter((w) => w.type !== "electron-native");
|
|
1502
2096
|
setWorkspaces(filtered);
|
|
1503
2097
|
}, [electron]);
|
|
1504
|
-
|
|
2098
|
+
useEffect5(() => {
|
|
1505
2099
|
refresh();
|
|
1506
2100
|
}, [refresh, activeWorkspaceId, refreshKey]);
|
|
1507
|
-
const handleSelect =
|
|
2101
|
+
const handleSelect = useCallback5(
|
|
1508
2102
|
async (ws) => {
|
|
1509
2103
|
await touchWorkspace(ws.id);
|
|
1510
2104
|
onSelect(ws);
|
|
@@ -1512,7 +2106,7 @@ function WorkspacePicker({
|
|
|
1512
2106
|
},
|
|
1513
2107
|
[onSelect]
|
|
1514
2108
|
);
|
|
1515
|
-
const handleStartCreateNew =
|
|
2109
|
+
const handleStartCreateNew = useCallback5(() => {
|
|
1516
2110
|
const existingNames = new Set(workspaces.map((workspace) => workspace.name.toLowerCase()));
|
|
1517
2111
|
let suffix = workspaces.length + 1;
|
|
1518
2112
|
while (existingNames.has(`workspace ${suffix}`.toLowerCase())) suffix += 1;
|
|
@@ -1520,12 +2114,12 @@ function WorkspacePicker({
|
|
|
1520
2114
|
setNewWorkspaceError(null);
|
|
1521
2115
|
setCreatingNew(true);
|
|
1522
2116
|
}, [workspaces]);
|
|
1523
|
-
const handleCancelCreateNew =
|
|
2117
|
+
const handleCancelCreateNew = useCallback5(() => {
|
|
1524
2118
|
setCreatingNew(false);
|
|
1525
2119
|
setNewWorkspaceName("");
|
|
1526
2120
|
setNewWorkspaceError(null);
|
|
1527
2121
|
}, []);
|
|
1528
|
-
const handleCreateNew =
|
|
2122
|
+
const handleCreateNew = useCallback5(async () => {
|
|
1529
2123
|
if (newWorkspacePending) return;
|
|
1530
2124
|
const name = newWorkspaceName.trim();
|
|
1531
2125
|
if (!name) {
|
|
@@ -1564,10 +2158,11 @@ function WorkspacePicker({
|
|
|
1564
2158
|
}, [newWorkspaceName, newWorkspacePending, onSelect, refresh, workspaces]);
|
|
1565
2159
|
const activeWs = workspaces.find((w) => w.id === activeWorkspaceId);
|
|
1566
2160
|
const activeWorkspaceName = activeWs?.name ?? "No workspace";
|
|
1567
|
-
return /* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
2161
|
+
return /* @__PURE__ */ jsxs5("div", { ref: pickerRef, className: `db-workspace-picker ${className ?? ""}`, children: [
|
|
2162
|
+
/* @__PURE__ */ jsxs5(
|
|
1569
2163
|
"button",
|
|
1570
2164
|
{
|
|
2165
|
+
ref: triggerRef,
|
|
1571
2166
|
className: "db-workspace-picker-btn",
|
|
1572
2167
|
onClick: () => {
|
|
1573
2168
|
const nextOpen = !isOpen;
|
|
@@ -1576,10 +2171,11 @@ function WorkspacePicker({
|
|
|
1576
2171
|
},
|
|
1577
2172
|
title: "Switch workspace",
|
|
1578
2173
|
"aria-label": `Switch workspace, current: ${activeWorkspaceName}`,
|
|
2174
|
+
"aria-expanded": isOpen,
|
|
1579
2175
|
children: [
|
|
1580
|
-
/* @__PURE__ */
|
|
1581
|
-
/* @__PURE__ */
|
|
1582
|
-
/* @__PURE__ */
|
|
2176
|
+
/* @__PURE__ */ jsx5("span", { className: "db-workspace-picker-label", children: activeWorkspaceName }),
|
|
2177
|
+
/* @__PURE__ */ jsx5("span", { className: "db-workspace-picker-compact-icon", children: /* @__PURE__ */ jsx5(FolderIcon, {}) }),
|
|
2178
|
+
/* @__PURE__ */ jsx5(
|
|
1583
2179
|
"span",
|
|
1584
2180
|
{
|
|
1585
2181
|
className: `db-workspace-picker-caret${isOpen ? " db-workspace-picker-caret--open" : ""}`,
|
|
@@ -1589,24 +2185,24 @@ function WorkspacePicker({
|
|
|
1589
2185
|
]
|
|
1590
2186
|
}
|
|
1591
2187
|
),
|
|
1592
|
-
isOpen && /* @__PURE__ */
|
|
1593
|
-
workspaces.map((ws) => /* @__PURE__ */
|
|
2188
|
+
isOpen && /* @__PURE__ */ jsxs5("div", { className: "db-workspace-dropdown", children: [
|
|
2189
|
+
workspaces.map((ws) => /* @__PURE__ */ jsx5(
|
|
1594
2190
|
"button",
|
|
1595
2191
|
{
|
|
1596
2192
|
className: `db-workspace-dropdown-item ${ws.id === activeWorkspaceId ? "db-workspace-dropdown-item--active" : ""}`,
|
|
1597
2193
|
onClick: () => handleSelect(ws),
|
|
1598
|
-
children: /* @__PURE__ */
|
|
1599
|
-
/* @__PURE__ */
|
|
1600
|
-
/* @__PURE__ */
|
|
1601
|
-
(ws.type === "native" || ws.type === "electron-native") && /* @__PURE__ */
|
|
2194
|
+
children: /* @__PURE__ */ jsxs5("span", { className: "db-workspace-details", children: [
|
|
2195
|
+
/* @__PURE__ */ jsxs5("span", { className: "db-workspace-heading", children: [
|
|
2196
|
+
/* @__PURE__ */ jsx5("span", { children: ws.name }),
|
|
2197
|
+
(ws.type === "native" || ws.type === "electron-native") && /* @__PURE__ */ jsx5("span", { className: "db-workspace-type", children: "(folder)" })
|
|
1602
2198
|
] }),
|
|
1603
|
-
ws.rootPath && /* @__PURE__ */
|
|
2199
|
+
ws.rootPath && /* @__PURE__ */ jsx5("span", { className: "db-workspace-path", title: ws.rootPath, children: /* @__PURE__ */ jsx5(WorkspacePath, { path: ws.rootPath }) })
|
|
1604
2200
|
] })
|
|
1605
2201
|
},
|
|
1606
2202
|
ws.id
|
|
1607
2203
|
)),
|
|
1608
|
-
/* @__PURE__ */
|
|
1609
|
-
!electron && (creatingNew ? /* @__PURE__ */
|
|
2204
|
+
/* @__PURE__ */ jsx5("div", { className: "db-workspace-dropdown-divider" }),
|
|
2205
|
+
!electron && (creatingNew ? /* @__PURE__ */ jsxs5(
|
|
1610
2206
|
"form",
|
|
1611
2207
|
{
|
|
1612
2208
|
className: "db-workspace-create",
|
|
@@ -1616,8 +2212,8 @@ function WorkspacePicker({
|
|
|
1616
2212
|
void handleCreateNew();
|
|
1617
2213
|
},
|
|
1618
2214
|
children: [
|
|
1619
|
-
/* @__PURE__ */
|
|
1620
|
-
/* @__PURE__ */
|
|
2215
|
+
/* @__PURE__ */ jsx5("label", { className: "db-workspace-create-label", htmlFor: "db-new-workspace-name", children: "Workspace name" }),
|
|
2216
|
+
/* @__PURE__ */ jsx5(
|
|
1621
2217
|
"input",
|
|
1622
2218
|
{
|
|
1623
2219
|
id: "db-new-workspace-name",
|
|
@@ -1637,9 +2233,9 @@ function WorkspacePicker({
|
|
|
1637
2233
|
}
|
|
1638
2234
|
}
|
|
1639
2235
|
),
|
|
1640
|
-
newWorkspaceError && /* @__PURE__ */
|
|
1641
|
-
/* @__PURE__ */
|
|
1642
|
-
/* @__PURE__ */
|
|
2236
|
+
newWorkspaceError && /* @__PURE__ */ jsx5("p", { id: "db-new-workspace-error", className: "db-workspace-create-error", role: "alert", children: newWorkspaceError }),
|
|
2237
|
+
/* @__PURE__ */ jsxs5("div", { className: "db-workspace-create-actions", children: [
|
|
2238
|
+
/* @__PURE__ */ jsx5(
|
|
1643
2239
|
"button",
|
|
1644
2240
|
{
|
|
1645
2241
|
type: "button",
|
|
@@ -1649,15 +2245,15 @@ function WorkspacePicker({
|
|
|
1649
2245
|
children: "Cancel"
|
|
1650
2246
|
}
|
|
1651
2247
|
),
|
|
1652
|
-
/* @__PURE__ */
|
|
2248
|
+
/* @__PURE__ */ jsx5("button", { type: "submit", disabled: newWorkspacePending, children: newWorkspacePending ? "Creating\xE2\u20AC\xA6" : "Create" })
|
|
1653
2249
|
] })
|
|
1654
2250
|
]
|
|
1655
2251
|
}
|
|
1656
|
-
) : /* @__PURE__ */
|
|
1657
|
-
/* @__PURE__ */
|
|
1658
|
-
/* @__PURE__ */
|
|
2252
|
+
) : /* @__PURE__ */ jsx5("button", { className: "db-workspace-dropdown-item", onClick: handleStartCreateNew, children: /* @__PURE__ */ jsxs5("span", { className: "db-workspace-dropdown-action-label", children: [
|
|
2253
|
+
/* @__PURE__ */ jsx5(NewFolderIcon, {}),
|
|
2254
|
+
/* @__PURE__ */ jsx5("span", { children: "New Workspace" })
|
|
1659
2255
|
] }) })),
|
|
1660
|
-
(electron || isNativeFileSystemSupported()) && /* @__PURE__ */
|
|
2256
|
+
(electron || isNativeFileSystemSupported()) && /* @__PURE__ */ jsx5(
|
|
1661
2257
|
"button",
|
|
1662
2258
|
{
|
|
1663
2259
|
className: "db-workspace-dropdown-item",
|
|
@@ -1668,7 +2264,7 @@ function WorkspacePicker({
|
|
|
1668
2264
|
children: "Open Folder..."
|
|
1669
2265
|
}
|
|
1670
2266
|
),
|
|
1671
|
-
onCloneRepository && /* @__PURE__ */
|
|
2267
|
+
onCloneRepository && /* @__PURE__ */ jsx5(
|
|
1672
2268
|
"button",
|
|
1673
2269
|
{
|
|
1674
2270
|
className: "db-workspace-dropdown-item",
|
|
@@ -1684,8 +2280,7 @@ function WorkspacePicker({
|
|
|
1684
2280
|
}
|
|
1685
2281
|
|
|
1686
2282
|
// src/DocBlocksShell/DocBlocksShell.tsx
|
|
1687
|
-
import { useState as
|
|
1688
|
-
import { MediaContext } from "@bendyline/squisq-react";
|
|
2283
|
+
import { useState as useState15, useCallback as useCallback15, useEffect as useEffect15, useRef as useRef15, useMemo as useMemo3, lazy as lazy2, Suspense as Suspense2 } from "react";
|
|
1689
2284
|
import {
|
|
1690
2285
|
DocumentVersionManager
|
|
1691
2286
|
} from "@bendyline/squisq/versions";
|
|
@@ -1696,6 +2291,7 @@ import {
|
|
|
1696
2291
|
FsError as FsError5,
|
|
1697
2292
|
getFileSystemProviderV2 as getFileSystemProviderV25,
|
|
1698
2293
|
isQuotaExceededError,
|
|
2294
|
+
moveFileSystemEntry as moveFileSystemEntry2,
|
|
1699
2295
|
parseWorkspacePath as parseWorkspacePath5,
|
|
1700
2296
|
workspacePathContains
|
|
1701
2297
|
} from "@bendyline/docblocks/filesystem";
|
|
@@ -1704,7 +2300,7 @@ import {
|
|
|
1704
2300
|
DocumentCommitConflictError,
|
|
1705
2301
|
DocumentSessionConflictError
|
|
1706
2302
|
} from "@bendyline/docblocks/document";
|
|
1707
|
-
import { isElectronHost as
|
|
2303
|
+
import { isElectronHost as isElectronHost3, getDocBlocksHost } from "@bendyline/docblocks/host";
|
|
1708
2304
|
import {
|
|
1709
2305
|
parseSharedDocumentHash
|
|
1710
2306
|
} from "@bendyline/docblocks/share";
|
|
@@ -1722,8 +2318,9 @@ import {
|
|
|
1722
2318
|
} from "@bendyline/docblocks/workspace";
|
|
1723
2319
|
|
|
1724
2320
|
// src/AppMenu/AppMenu.tsx
|
|
1725
|
-
import { useState as
|
|
1726
|
-
import {
|
|
2321
|
+
import { useState as useState6, useCallback as useCallback6, useRef as useRef6, useEffect as useEffect6 } from "react";
|
|
2322
|
+
import { isElectronHost as isElectronHost2 } from "@bendyline/docblocks/host";
|
|
2323
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1727
2324
|
function formatBytes(bytes) {
|
|
1728
2325
|
if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
|
|
1729
2326
|
let value = bytes;
|
|
@@ -1753,13 +2350,15 @@ function AppMenu({
|
|
|
1753
2350
|
appVersion,
|
|
1754
2351
|
appBuildDate
|
|
1755
2352
|
}) {
|
|
1756
|
-
const [isOpen, setIsOpen] =
|
|
1757
|
-
const [showAbout, setShowAbout] =
|
|
1758
|
-
const [showSettings, setShowSettings] =
|
|
1759
|
-
const [requestingPersistentStorage, setRequestingPersistentStorage] =
|
|
1760
|
-
const
|
|
1761
|
-
const
|
|
1762
|
-
|
|
2353
|
+
const [isOpen, setIsOpen] = useState6(false);
|
|
2354
|
+
const [showAbout, setShowAbout] = useState6(false);
|
|
2355
|
+
const [showSettings, setShowSettings] = useState6(false);
|
|
2356
|
+
const [requestingPersistentStorage, setRequestingPersistentStorage] = useState6(false);
|
|
2357
|
+
const moreInformationUrl = isElectronHost2() ? "https://docblocks.com/desktop/" : "https://docblocks.com/web/";
|
|
2358
|
+
const containerRef = useRef6(null);
|
|
2359
|
+
const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(isOpen, setIsOpen);
|
|
2360
|
+
const [storageEstimate, setStorageEstimate] = useState6(null);
|
|
2361
|
+
useEffect6(() => {
|
|
1763
2362
|
if (!showSettings || !getStorageEstimate) return;
|
|
1764
2363
|
let cancelled = false;
|
|
1765
2364
|
getStorageEstimate().then((estimate) => {
|
|
@@ -1771,22 +2370,26 @@ function AppMenu({
|
|
|
1771
2370
|
cancelled = true;
|
|
1772
2371
|
};
|
|
1773
2372
|
}, [showSettings, getStorageEstimate]);
|
|
1774
|
-
|
|
2373
|
+
useEffect6(() => {
|
|
1775
2374
|
if (!isOpen) return;
|
|
1776
2375
|
function handleOutsideClick(e) {
|
|
1777
|
-
if (
|
|
1778
|
-
|
|
2376
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
2377
|
+
closeMenu(false);
|
|
1779
2378
|
}
|
|
1780
2379
|
}
|
|
1781
2380
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
1782
2381
|
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
1783
|
-
}, [isOpen]);
|
|
1784
|
-
const handleAction =
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2382
|
+
}, [closeMenu, isOpen]);
|
|
2383
|
+
const handleAction = useCallback6(
|
|
2384
|
+
(action) => {
|
|
2385
|
+
closeMenu(false);
|
|
2386
|
+
action();
|
|
2387
|
+
},
|
|
2388
|
+
[closeMenu]
|
|
2389
|
+
);
|
|
2390
|
+
const handleKeepBrowserDataFromSettings = useCallback6(async () => {
|
|
1789
2391
|
if (!onKeepBrowserData || requestingPersistentStorage) return;
|
|
2392
|
+
setShowSettings(false);
|
|
1790
2393
|
setRequestingPersistentStorage(true);
|
|
1791
2394
|
try {
|
|
1792
2395
|
await onKeepBrowserData();
|
|
@@ -1794,17 +2397,19 @@ function AppMenu({
|
|
|
1794
2397
|
setRequestingPersistentStorage(false);
|
|
1795
2398
|
}
|
|
1796
2399
|
}, [onKeepBrowserData, requestingPersistentStorage]);
|
|
1797
|
-
return /* @__PURE__ */
|
|
1798
|
-
/* @__PURE__ */
|
|
1799
|
-
/* @__PURE__ */
|
|
2400
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
2401
|
+
/* @__PURE__ */ jsxs6("div", { ref: containerRef, className: "db-app-menu", children: [
|
|
2402
|
+
/* @__PURE__ */ jsxs6(
|
|
1800
2403
|
"button",
|
|
1801
2404
|
{
|
|
2405
|
+
ref: triggerRef,
|
|
1802
2406
|
className: "db-app-menu-btn",
|
|
1803
2407
|
onClick: () => setIsOpen(!isOpen),
|
|
2408
|
+
onKeyDown: handleTriggerKeyDown,
|
|
1804
2409
|
"aria-expanded": isOpen,
|
|
1805
|
-
"aria-haspopup": "
|
|
2410
|
+
"aria-haspopup": "menu",
|
|
1806
2411
|
children: [
|
|
1807
|
-
logoUrl ? /* @__PURE__ */
|
|
2412
|
+
logoUrl ? /* @__PURE__ */ jsx6(
|
|
1808
2413
|
"img",
|
|
1809
2414
|
{
|
|
1810
2415
|
src: logoUrl,
|
|
@@ -1813,8 +2418,8 @@ function AppMenu({
|
|
|
1813
2418
|
width: 1266,
|
|
1814
2419
|
height: 544
|
|
1815
2420
|
}
|
|
1816
|
-
) : /* @__PURE__ */
|
|
1817
|
-
/* @__PURE__ */
|
|
2421
|
+
) : /* @__PURE__ */ jsx6("span", { className: "db-app-menu-label", children: "docblocks" }),
|
|
2422
|
+
/* @__PURE__ */ jsx6(
|
|
1818
2423
|
"span",
|
|
1819
2424
|
{
|
|
1820
2425
|
className: `db-app-menu-caret${isOpen ? " db-app-menu-caret--open" : ""}`,
|
|
@@ -1824,86 +2429,100 @@ function AppMenu({
|
|
|
1824
2429
|
]
|
|
1825
2430
|
}
|
|
1826
2431
|
),
|
|
1827
|
-
isOpen && /* @__PURE__ */
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2432
|
+
isOpen && /* @__PURE__ */ jsxs6(
|
|
2433
|
+
"div",
|
|
2434
|
+
{
|
|
2435
|
+
ref: menuRef,
|
|
2436
|
+
className: "db-app-menu-dropdown",
|
|
2437
|
+
role: "menu",
|
|
2438
|
+
onKeyDown: handleMenuKeyDown,
|
|
2439
|
+
children: [
|
|
2440
|
+
/* @__PURE__ */ jsx6(
|
|
2441
|
+
"button",
|
|
2442
|
+
{
|
|
2443
|
+
className: "db-app-menu-item",
|
|
2444
|
+
role: "menuitem",
|
|
2445
|
+
tabIndex: -1,
|
|
2446
|
+
onClick: () => handleAction(() => setShowSettings(true)),
|
|
2447
|
+
children: "Settings"
|
|
2448
|
+
}
|
|
2449
|
+
),
|
|
2450
|
+
onInstallApp && /* @__PURE__ */ jsx6(
|
|
2451
|
+
"button",
|
|
2452
|
+
{
|
|
2453
|
+
className: "db-app-menu-item",
|
|
2454
|
+
role: "menuitem",
|
|
2455
|
+
tabIndex: -1,
|
|
2456
|
+
onClick: () => handleAction(() => void onInstallApp()),
|
|
2457
|
+
children: "Install DocBlocks\u2026"
|
|
2458
|
+
}
|
|
2459
|
+
),
|
|
2460
|
+
onKeepBrowserData && /* @__PURE__ */ jsx6(
|
|
2461
|
+
"button",
|
|
2462
|
+
{
|
|
2463
|
+
className: "db-app-menu-item",
|
|
2464
|
+
role: "menuitem",
|
|
2465
|
+
tabIndex: -1,
|
|
2466
|
+
onClick: () => handleAction(() => void handleKeepBrowserDataFromSettings()),
|
|
2467
|
+
children: "Protect data from browser cleanup"
|
|
2468
|
+
}
|
|
2469
|
+
),
|
|
2470
|
+
onDownloadAllWorkspaces && /* @__PURE__ */ jsx6(
|
|
2471
|
+
"button",
|
|
2472
|
+
{
|
|
2473
|
+
className: "db-app-menu-item",
|
|
2474
|
+
role: "menuitem",
|
|
2475
|
+
tabIndex: -1,
|
|
2476
|
+
onClick: () => handleAction(() => void onDownloadAllWorkspaces()),
|
|
2477
|
+
children: "Download all workspaces"
|
|
2478
|
+
}
|
|
2479
|
+
),
|
|
2480
|
+
/* @__PURE__ */ jsx6("div", { className: "db-app-menu-divider" }),
|
|
2481
|
+
/* @__PURE__ */ jsx6(
|
|
2482
|
+
"button",
|
|
2483
|
+
{
|
|
2484
|
+
className: "db-app-menu-item",
|
|
2485
|
+
role: "menuitem",
|
|
2486
|
+
tabIndex: -1,
|
|
2487
|
+
onClick: () => handleAction(() => setShowAbout(true)),
|
|
2488
|
+
children: "About"
|
|
2489
|
+
}
|
|
2490
|
+
)
|
|
2491
|
+
]
|
|
2492
|
+
}
|
|
2493
|
+
)
|
|
1875
2494
|
] }),
|
|
1876
|
-
showSettings && /* @__PURE__ */
|
|
1877
|
-
/* @__PURE__ */
|
|
2495
|
+
showSettings && /* @__PURE__ */ jsxs6(SettingsDialog, { onClose: () => setShowSettings(false), children: [
|
|
2496
|
+
/* @__PURE__ */ jsx6(
|
|
1878
2497
|
ThemeSettings,
|
|
1879
2498
|
{
|
|
1880
2499
|
value: themePreference,
|
|
1881
2500
|
onChange: (preference) => onThemeChange?.(preference)
|
|
1882
2501
|
}
|
|
1883
2502
|
),
|
|
1884
|
-
/* @__PURE__ */
|
|
2503
|
+
/* @__PURE__ */ jsx6(
|
|
1885
2504
|
AccentColorSettings,
|
|
1886
2505
|
{
|
|
1887
2506
|
value: accentColor,
|
|
1888
2507
|
onChange: (color) => onAccentColorChange?.(color)
|
|
1889
2508
|
}
|
|
1890
2509
|
),
|
|
1891
|
-
/* @__PURE__ */
|
|
2510
|
+
/* @__PURE__ */ jsx6(
|
|
1892
2511
|
WriteCanvasSettingsControls,
|
|
1893
2512
|
{
|
|
1894
2513
|
value: writeCanvasSettings,
|
|
1895
2514
|
onChange: (settings) => onWriteCanvasSettingsChange?.(settings)
|
|
1896
2515
|
}
|
|
1897
2516
|
),
|
|
1898
|
-
getStorageEstimate && /* @__PURE__ */
|
|
1899
|
-
/* @__PURE__ */
|
|
1900
|
-
/* @__PURE__ */
|
|
2517
|
+
getStorageEstimate && /* @__PURE__ */ jsxs6("fieldset", { className: "db-settings-fieldset", children: [
|
|
2518
|
+
/* @__PURE__ */ jsx6("legend", { className: "db-settings-legend", children: "Storage" }),
|
|
2519
|
+
/* @__PURE__ */ jsx6("p", { className: "db-settings-hint", children: storageEstimate ? `DocBlocks documents and app data are using ${formatBytes(
|
|
1901
2520
|
storageEstimate.usage
|
|
1902
2521
|
)} of the ${formatBytes(
|
|
1903
2522
|
storageEstimate.quota
|
|
1904
2523
|
)} this browser allows for the site.` : "Storage usage is not available in this browser." }),
|
|
1905
|
-
storagePersistent !== void 0 && /* @__PURE__ */
|
|
1906
|
-
onKeepBrowserData && /* @__PURE__ */
|
|
2524
|
+
storagePersistent !== void 0 && /* @__PURE__ */ jsx6("p", { className: "db-settings-hint", children: storagePersistent ? "Protected from routine browser cleanup on this device." : "Browsers may clear site data under storage pressure unless protection is granted." }),
|
|
2525
|
+
onKeepBrowserData && /* @__PURE__ */ jsx6(
|
|
1907
2526
|
"button",
|
|
1908
2527
|
{
|
|
1909
2528
|
type: "button",
|
|
@@ -1914,16 +2533,16 @@ function AppMenu({
|
|
|
1914
2533
|
}
|
|
1915
2534
|
)
|
|
1916
2535
|
] }),
|
|
1917
|
-
onVersioningPreferenceChange && /* @__PURE__ */
|
|
1918
|
-
/* @__PURE__ */
|
|
1919
|
-
/* @__PURE__ */
|
|
2536
|
+
onVersioningPreferenceChange && /* @__PURE__ */ jsxs6("fieldset", { className: "db-settings-fieldset", children: [
|
|
2537
|
+
/* @__PURE__ */ jsx6("legend", { className: "db-settings-legend", children: "Version history" }),
|
|
2538
|
+
/* @__PURE__ */ jsxs6("p", { className: "db-settings-hint", children: [
|
|
1920
2539
|
"When on, DocBlocks keeps prior revisions of each document inside a sibling",
|
|
1921
2540
|
" ",
|
|
1922
|
-
/* @__PURE__ */
|
|
2541
|
+
/* @__PURE__ */ jsx6("code", { children: "<name>_files/.versions/" }),
|
|
1923
2542
|
" folder. Individual workspaces can override this default in their own settings."
|
|
1924
2543
|
] }),
|
|
1925
|
-
/* @__PURE__ */
|
|
1926
|
-
/* @__PURE__ */
|
|
2544
|
+
/* @__PURE__ */ jsxs6("label", { className: "db-settings-radio", children: [
|
|
2545
|
+
/* @__PURE__ */ jsx6(
|
|
1927
2546
|
"input",
|
|
1928
2547
|
{
|
|
1929
2548
|
type: "radio",
|
|
@@ -1935,8 +2554,8 @@ function AppMenu({
|
|
|
1935
2554
|
),
|
|
1936
2555
|
"On for all workspaces"
|
|
1937
2556
|
] }),
|
|
1938
|
-
/* @__PURE__ */
|
|
1939
|
-
/* @__PURE__ */
|
|
2557
|
+
/* @__PURE__ */ jsxs6("label", { className: "db-settings-radio", children: [
|
|
2558
|
+
/* @__PURE__ */ jsx6(
|
|
1940
2559
|
"input",
|
|
1941
2560
|
{
|
|
1942
2561
|
type: "radio",
|
|
@@ -1948,8 +2567,8 @@ function AppMenu({
|
|
|
1948
2567
|
),
|
|
1949
2568
|
"On in browser workspaces, off for local folders"
|
|
1950
2569
|
] }),
|
|
1951
|
-
/* @__PURE__ */
|
|
1952
|
-
/* @__PURE__ */
|
|
2570
|
+
/* @__PURE__ */ jsxs6("label", { className: "db-settings-radio", children: [
|
|
2571
|
+
/* @__PURE__ */ jsx6(
|
|
1953
2572
|
"input",
|
|
1954
2573
|
{
|
|
1955
2574
|
type: "radio",
|
|
@@ -1963,31 +2582,49 @@ function AppMenu({
|
|
|
1963
2582
|
] })
|
|
1964
2583
|
] })
|
|
1965
2584
|
] }),
|
|
1966
|
-
showAbout && /* @__PURE__ */
|
|
1967
|
-
/* @__PURE__ */
|
|
1968
|
-
/* @__PURE__ */
|
|
2585
|
+
showAbout && /* @__PURE__ */ jsxs6(Dialog, { title: "About DocBlocks", onClose: () => setShowAbout(false), children: [
|
|
2586
|
+
/* @__PURE__ */ jsxs6("p", { children: [
|
|
2587
|
+
/* @__PURE__ */ jsx6("strong", { children: "DocBlocks" }),
|
|
1969
2588
|
" is a local-first Markdown document editor. Your files stay under your control."
|
|
1970
2589
|
] }),
|
|
1971
|
-
|
|
1972
|
-
/* @__PURE__ */
|
|
1973
|
-
|
|
2590
|
+
/* @__PURE__ */ jsxs6("aside", { className: "db-about-beta", children: [
|
|
2591
|
+
/* @__PURE__ */ jsx6("strong", { children: "Beta Software." }),
|
|
2592
|
+
" We're still working through initial hiccups and issues. Please bear with us, make sure you keep backups, and",
|
|
2593
|
+
" ",
|
|
2594
|
+
/* @__PURE__ */ jsx6(
|
|
2595
|
+
"a",
|
|
2596
|
+
{
|
|
2597
|
+
href: "https://github.com/bendyline/docblocks/issues/new",
|
|
2598
|
+
target: "_blank",
|
|
2599
|
+
rel: "noopener noreferrer",
|
|
2600
|
+
children: "open issues"
|
|
2601
|
+
}
|
|
2602
|
+
),
|
|
2603
|
+
" ",
|
|
2604
|
+
"where you find them. Thanks!"
|
|
2605
|
+
] }),
|
|
2606
|
+
appVersion && /* @__PURE__ */ jsxs6("p", { className: "db-about-version", children: [
|
|
2607
|
+
/* @__PURE__ */ jsx6("span", { children: "Version" }),
|
|
2608
|
+
/* @__PURE__ */ jsx6("code", { "aria-label": `DocBlocks version ${appVersion}`, children: appVersion })
|
|
1974
2609
|
] }),
|
|
1975
|
-
appBuildDate && /* @__PURE__ */
|
|
1976
|
-
/* @__PURE__ */
|
|
1977
|
-
/* @__PURE__ */
|
|
2610
|
+
appBuildDate && /* @__PURE__ */ jsxs6("p", { className: "db-about-version", children: [
|
|
2611
|
+
/* @__PURE__ */ jsx6("span", { children: "Build" }),
|
|
2612
|
+
/* @__PURE__ */ jsx6("time", { dateTime: appBuildDate, children: appBuildDate })
|
|
1978
2613
|
] }),
|
|
1979
|
-
/* @__PURE__ */
|
|
2614
|
+
/* @__PURE__ */ jsxs6("p", { children: [
|
|
1980
2615
|
"Built with",
|
|
1981
2616
|
" ",
|
|
1982
|
-
/* @__PURE__ */
|
|
2617
|
+
/* @__PURE__ */ jsx6("a", { href: "https://github.com/bendyline/squisq", target: "_blank", rel: "noopener noreferrer", children: "squisq" }),
|
|
1983
2618
|
" ",
|
|
1984
2619
|
"by",
|
|
1985
2620
|
" ",
|
|
1986
|
-
/* @__PURE__ */
|
|
2621
|
+
/* @__PURE__ */ jsx6("a", { href: "https://bendyline.com", target: "_blank", rel: "noopener noreferrer", children: "Bendyline" }),
|
|
1987
2622
|
"."
|
|
1988
2623
|
] }),
|
|
1989
|
-
/* @__PURE__ */
|
|
1990
|
-
/* @__PURE__ */
|
|
2624
|
+
/* @__PURE__ */ jsxs6("p", { className: "db-dialog-links", children: [
|
|
2625
|
+
/* @__PURE__ */ jsx6("a", { href: moreInformationUrl, target: "_blank", rel: "noopener noreferrer", children: "More information..." }),
|
|
2626
|
+
/* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
|
|
2627
|
+
/* @__PURE__ */ jsx6(
|
|
1991
2628
|
"a",
|
|
1992
2629
|
{
|
|
1993
2630
|
href: "https://github.com/bendyline/docblocks",
|
|
@@ -1996,8 +2633,8 @@ function AppMenu({
|
|
|
1996
2633
|
children: "GitHub"
|
|
1997
2634
|
}
|
|
1998
2635
|
),
|
|
1999
|
-
/* @__PURE__ */
|
|
2000
|
-
/* @__PURE__ */
|
|
2636
|
+
/* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
|
|
2637
|
+
/* @__PURE__ */ jsx6(
|
|
2001
2638
|
"a",
|
|
2002
2639
|
{
|
|
2003
2640
|
href: "https://github.com/bendyline/docblocks/releases",
|
|
@@ -2006,8 +2643,8 @@ function AppMenu({
|
|
|
2006
2643
|
children: "Release notes"
|
|
2007
2644
|
}
|
|
2008
2645
|
),
|
|
2009
|
-
/* @__PURE__ */
|
|
2010
|
-
/* @__PURE__ */
|
|
2646
|
+
/* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
|
|
2647
|
+
/* @__PURE__ */ jsx6(
|
|
2011
2648
|
"a",
|
|
2012
2649
|
{
|
|
2013
2650
|
href: "https://github.com/bendyline/docblocks/issues",
|
|
@@ -2016,8 +2653,8 @@ function AppMenu({
|
|
|
2016
2653
|
children: "Support"
|
|
2017
2654
|
}
|
|
2018
2655
|
),
|
|
2019
|
-
/* @__PURE__ */
|
|
2020
|
-
/* @__PURE__ */
|
|
2656
|
+
/* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
|
|
2657
|
+
/* @__PURE__ */ jsx6(
|
|
2021
2658
|
"a",
|
|
2022
2659
|
{
|
|
2023
2660
|
href: "https://github.com/bendyline/docblocks/blob/main/LICENSE",
|
|
@@ -2026,8 +2663,8 @@ function AppMenu({
|
|
|
2026
2663
|
children: "License (MIT)"
|
|
2027
2664
|
}
|
|
2028
2665
|
),
|
|
2029
|
-
/* @__PURE__ */
|
|
2030
|
-
/* @__PURE__ */
|
|
2666
|
+
/* @__PURE__ */ jsx6("span", { className: "db-dialog-sep", children: "\xB7" }),
|
|
2667
|
+
/* @__PURE__ */ jsx6(
|
|
2031
2668
|
"a",
|
|
2032
2669
|
{
|
|
2033
2670
|
href: "https://github.com/bendyline/docblocks/blob/main/NOTICE.md",
|
|
@@ -2042,82 +2679,101 @@ function AppMenu({
|
|
|
2042
2679
|
}
|
|
2043
2680
|
|
|
2044
2681
|
// src/WorkspacePicker/WorkspaceSettingsButton.tsx
|
|
2045
|
-
import { useState as
|
|
2046
|
-
import { jsx as
|
|
2682
|
+
import { useState as useState7, useCallback as useCallback7, useRef as useRef7, useEffect as useEffect7 } from "react";
|
|
2683
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2047
2684
|
function WorkspaceSettingsButton({
|
|
2048
2685
|
onSettings,
|
|
2049
2686
|
onRename,
|
|
2050
2687
|
onDownload,
|
|
2051
2688
|
onRemove
|
|
2052
2689
|
}) {
|
|
2053
|
-
const [isOpen, setIsOpen] =
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2690
|
+
const [isOpen, setIsOpen] = useState7(false);
|
|
2691
|
+
const containerRef = useRef7(null);
|
|
2692
|
+
const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(isOpen, setIsOpen);
|
|
2693
|
+
useEffect7(() => {
|
|
2056
2694
|
if (!isOpen) return;
|
|
2057
2695
|
function handleOutsideClick(e) {
|
|
2058
|
-
if (
|
|
2059
|
-
|
|
2696
|
+
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
|
2697
|
+
closeMenu(false);
|
|
2060
2698
|
}
|
|
2061
2699
|
}
|
|
2062
2700
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
2063
2701
|
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
2064
|
-
}, [isOpen]);
|
|
2065
|
-
const handleAction =
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2702
|
+
}, [closeMenu, isOpen]);
|
|
2703
|
+
const handleAction = useCallback7(
|
|
2704
|
+
(action) => {
|
|
2705
|
+
closeMenu(false);
|
|
2706
|
+
action();
|
|
2707
|
+
},
|
|
2708
|
+
[closeMenu]
|
|
2709
|
+
);
|
|
2710
|
+
return /* @__PURE__ */ jsxs7("div", { ref: containerRef, className: "db-ws-settings", children: [
|
|
2711
|
+
/* @__PURE__ */ jsx7(
|
|
2071
2712
|
"button",
|
|
2072
2713
|
{
|
|
2714
|
+
ref: triggerRef,
|
|
2073
2715
|
className: "db-ws-settings-btn",
|
|
2074
2716
|
onClick: () => setIsOpen(!isOpen),
|
|
2717
|
+
onKeyDown: handleTriggerKeyDown,
|
|
2075
2718
|
"aria-expanded": isOpen,
|
|
2076
|
-
"aria-haspopup": "
|
|
2719
|
+
"aria-haspopup": "menu",
|
|
2077
2720
|
"aria-label": "Workspace settings",
|
|
2078
2721
|
title: "Workspace settings",
|
|
2079
|
-
children: /* @__PURE__ */
|
|
2722
|
+
children: /* @__PURE__ */ jsx7(WorkspaceIcon, {})
|
|
2080
2723
|
}
|
|
2081
2724
|
),
|
|
2082
|
-
isOpen && /* @__PURE__ */
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2725
|
+
isOpen && /* @__PURE__ */ jsxs7(
|
|
2726
|
+
"div",
|
|
2727
|
+
{
|
|
2728
|
+
ref: menuRef,
|
|
2729
|
+
className: "db-ws-settings-dropdown",
|
|
2730
|
+
role: "menu",
|
|
2731
|
+
onKeyDown: handleMenuKeyDown,
|
|
2732
|
+
children: [
|
|
2733
|
+
/* @__PURE__ */ jsx7(
|
|
2734
|
+
"button",
|
|
2735
|
+
{
|
|
2736
|
+
className: "db-ws-settings-item",
|
|
2737
|
+
role: "menuitem",
|
|
2738
|
+
tabIndex: -1,
|
|
2739
|
+
onClick: () => handleAction(onSettings),
|
|
2740
|
+
children: "Workspace settings\u2026"
|
|
2741
|
+
}
|
|
2742
|
+
),
|
|
2743
|
+
/* @__PURE__ */ jsx7(
|
|
2744
|
+
"button",
|
|
2745
|
+
{
|
|
2746
|
+
className: "db-ws-settings-item",
|
|
2747
|
+
role: "menuitem",
|
|
2748
|
+
tabIndex: -1,
|
|
2749
|
+
onClick: () => handleAction(onRename),
|
|
2750
|
+
children: "Rename workspace"
|
|
2751
|
+
}
|
|
2752
|
+
),
|
|
2753
|
+
/* @__PURE__ */ jsx7(
|
|
2754
|
+
"button",
|
|
2755
|
+
{
|
|
2756
|
+
className: "db-ws-settings-item",
|
|
2757
|
+
role: "menuitem",
|
|
2758
|
+
tabIndex: -1,
|
|
2759
|
+
onClick: () => handleAction(onDownload),
|
|
2760
|
+
children: "Download workspace"
|
|
2761
|
+
}
|
|
2762
|
+
),
|
|
2763
|
+
/* @__PURE__ */ jsx7("div", { className: "db-ws-settings-divider" }),
|
|
2764
|
+
/* @__PURE__ */ jsx7(
|
|
2765
|
+
"button",
|
|
2766
|
+
{
|
|
2767
|
+
className: "db-ws-settings-item db-ws-settings-item--danger",
|
|
2768
|
+
role: "menuitem",
|
|
2769
|
+
tabIndex: -1,
|
|
2770
|
+
onClick: () => handleAction(onRemove),
|
|
2771
|
+
children: "Remove workspace"
|
|
2772
|
+
}
|
|
2773
|
+
)
|
|
2774
|
+
]
|
|
2775
|
+
}
|
|
2776
|
+
)
|
|
2121
2777
|
] });
|
|
2122
2778
|
}
|
|
2123
2779
|
|
|
@@ -2157,7 +2813,7 @@ function resolveVersioningEnabled(workspace, globalPref) {
|
|
|
2157
2813
|
}
|
|
2158
2814
|
|
|
2159
2815
|
// src/WorkspacePicker/WorkspaceSettingsDialog.tsx
|
|
2160
|
-
import { jsx as
|
|
2816
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2161
2817
|
var VERSIONING_LABELS = {
|
|
2162
2818
|
on: "on",
|
|
2163
2819
|
"browser-only": "on for browser workspaces, off for local folders",
|
|
@@ -2176,23 +2832,23 @@ function WorkspaceSettingsDialog({
|
|
|
2176
2832
|
globalVersioningPreference
|
|
2177
2833
|
);
|
|
2178
2834
|
const inheritLabel = `Inherit default \u2014 currently ${VERSIONING_LABELS[globalVersioningPreference]} (${inheritedEnabled ? "on" : "off"} for this workspace)`;
|
|
2179
|
-
return /* @__PURE__ */
|
|
2180
|
-
/* @__PURE__ */
|
|
2181
|
-
/* @__PURE__ */
|
|
2835
|
+
return /* @__PURE__ */ jsxs8(Dialog, { title: "Workspace settings", onClose, children: [
|
|
2836
|
+
/* @__PURE__ */ jsxs8("p", { className: "db-settings-hint", children: [
|
|
2837
|
+
/* @__PURE__ */ jsx8("strong", { children: workspace.name }),
|
|
2182
2838
|
" \xB7",
|
|
2183
2839
|
" ",
|
|
2184
2840
|
isLocal ? "Local folder workspace" : "Browser workspace"
|
|
2185
2841
|
] }),
|
|
2186
|
-
/* @__PURE__ */
|
|
2187
|
-
/* @__PURE__ */
|
|
2188
|
-
/* @__PURE__ */
|
|
2842
|
+
/* @__PURE__ */ jsxs8("fieldset", { className: "db-settings-fieldset", children: [
|
|
2843
|
+
/* @__PURE__ */ jsx8("legend", { className: "db-settings-legend", children: "Version history" }),
|
|
2844
|
+
/* @__PURE__ */ jsxs8("p", { className: "db-settings-hint", children: [
|
|
2189
2845
|
"Controls whether DocBlocks keeps prior revisions in",
|
|
2190
2846
|
" ",
|
|
2191
|
-
/* @__PURE__ */
|
|
2847
|
+
/* @__PURE__ */ jsx8("code", { children: "<name>_files/.versions/" }),
|
|
2192
2848
|
" for documents in this workspace."
|
|
2193
2849
|
] }),
|
|
2194
|
-
/* @__PURE__ */
|
|
2195
|
-
/* @__PURE__ */
|
|
2850
|
+
/* @__PURE__ */ jsxs8("label", { className: "db-settings-radio", children: [
|
|
2851
|
+
/* @__PURE__ */ jsx8(
|
|
2196
2852
|
"input",
|
|
2197
2853
|
{
|
|
2198
2854
|
type: "radio",
|
|
@@ -2204,8 +2860,8 @@ function WorkspaceSettingsDialog({
|
|
|
2204
2860
|
),
|
|
2205
2861
|
inheritLabel
|
|
2206
2862
|
] }),
|
|
2207
|
-
/* @__PURE__ */
|
|
2208
|
-
/* @__PURE__ */
|
|
2863
|
+
/* @__PURE__ */ jsxs8("label", { className: "db-settings-radio", children: [
|
|
2864
|
+
/* @__PURE__ */ jsx8(
|
|
2209
2865
|
"input",
|
|
2210
2866
|
{
|
|
2211
2867
|
type: "radio",
|
|
@@ -2217,8 +2873,8 @@ function WorkspaceSettingsDialog({
|
|
|
2217
2873
|
),
|
|
2218
2874
|
"On for this workspace"
|
|
2219
2875
|
] }),
|
|
2220
|
-
/* @__PURE__ */
|
|
2221
|
-
/* @__PURE__ */
|
|
2876
|
+
/* @__PURE__ */ jsxs8("label", { className: "db-settings-radio", children: [
|
|
2877
|
+
/* @__PURE__ */ jsx8(
|
|
2222
2878
|
"input",
|
|
2223
2879
|
{
|
|
2224
2880
|
type: "radio",
|
|
@@ -2235,14 +2891,14 @@ function WorkspaceSettingsDialog({
|
|
|
2235
2891
|
}
|
|
2236
2892
|
|
|
2237
2893
|
// src/hooks/useDocumentSession.ts
|
|
2238
|
-
import { useState as
|
|
2894
|
+
import { useState as useState8, useSyncExternalStore } from "react";
|
|
2239
2895
|
import {
|
|
2240
2896
|
DocumentRecoveryJournal,
|
|
2241
2897
|
DocumentSession,
|
|
2242
2898
|
getDefaultDocumentRecoveryStorage
|
|
2243
2899
|
} from "@bendyline/docblocks/document";
|
|
2244
2900
|
function useDocumentSession(autoSaveDelayMs = 500) {
|
|
2245
|
-
const [session] =
|
|
2901
|
+
const [session] = useState8(
|
|
2246
2902
|
() => new DocumentSession({
|
|
2247
2903
|
autoSaveDelayMs,
|
|
2248
2904
|
recoveryJournal: new DocumentRecoveryJournal(getDefaultDocumentRecoveryStorage())
|
|
@@ -2258,38 +2914,38 @@ function useDocumentSession(autoSaveDelayMs = 500) {
|
|
|
2258
2914
|
|
|
2259
2915
|
// src/Export/DeferredExportToolbarControls.tsx
|
|
2260
2916
|
import { lazy, Suspense } from "react";
|
|
2261
|
-
import { jsx as
|
|
2917
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2262
2918
|
var ExportToolbarControlsImplementation = lazy(
|
|
2263
|
-
() => import("./ExportToolbarControls-
|
|
2919
|
+
() => import("./ExportToolbarControls-773QRE4R.js").then((module) => ({
|
|
2264
2920
|
default: module.ExportToolbarControls
|
|
2265
2921
|
}))
|
|
2266
2922
|
);
|
|
2267
2923
|
function ExportToolbarControls(props) {
|
|
2268
|
-
return /* @__PURE__ */
|
|
2924
|
+
return /* @__PURE__ */ jsx9(Suspense, { fallback: null, children: /* @__PURE__ */ jsx9(ExportToolbarControlsImplementation, { ...props }) });
|
|
2269
2925
|
}
|
|
2270
2926
|
|
|
2271
2927
|
// src/Git/useGit.ts
|
|
2272
|
-
import { useCallback as
|
|
2928
|
+
import { useCallback as useCallback9, useEffect as useEffect9, useMemo as useMemo2, useRef as useRef9, useState as useState10 } from "react";
|
|
2273
2929
|
import { maybeGetDocBlocksHost } from "@bendyline/docblocks/host";
|
|
2274
2930
|
|
|
2275
2931
|
// src/Git/useGitStatus.ts
|
|
2276
|
-
import { useCallback as
|
|
2932
|
+
import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef8, useState as useState9 } from "react";
|
|
2277
2933
|
var SCHEDULE_REFRESH_MS = 1500;
|
|
2278
2934
|
function statusKey(status) {
|
|
2279
2935
|
return JSON.stringify(status);
|
|
2280
2936
|
}
|
|
2281
2937
|
function useGitStatus(gitApi, repositoryId, enabled) {
|
|
2282
|
-
const [status, setStatus] =
|
|
2283
|
-
const lastKeyRef =
|
|
2284
|
-
const timerRef =
|
|
2285
|
-
const generationRef =
|
|
2286
|
-
const apply =
|
|
2938
|
+
const [status, setStatus] = useState9(null);
|
|
2939
|
+
const lastKeyRef = useRef8(null);
|
|
2940
|
+
const timerRef = useRef8(null);
|
|
2941
|
+
const generationRef = useRef8(0);
|
|
2942
|
+
const apply = useCallback8((next) => {
|
|
2287
2943
|
const key = statusKey(next);
|
|
2288
2944
|
if (key === lastKeyRef.current) return;
|
|
2289
2945
|
lastKeyRef.current = key;
|
|
2290
2946
|
setStatus(next);
|
|
2291
2947
|
}, []);
|
|
2292
|
-
|
|
2948
|
+
useEffect8(() => {
|
|
2293
2949
|
generationRef.current += 1;
|
|
2294
2950
|
if (timerRef.current) {
|
|
2295
2951
|
clearTimeout(timerRef.current);
|
|
@@ -2307,7 +2963,7 @@ function useGitStatus(gitApi, repositoryId, enabled) {
|
|
|
2307
2963
|
unsubscribe();
|
|
2308
2964
|
};
|
|
2309
2965
|
}, [gitApi, repositoryId, enabled, apply]);
|
|
2310
|
-
const refresh =
|
|
2966
|
+
const refresh = useCallback8(() => {
|
|
2311
2967
|
if (!gitApi || !repositoryId || !enabled) return;
|
|
2312
2968
|
const generation = generationRef.current;
|
|
2313
2969
|
void gitApi.status(repositoryId).then(
|
|
@@ -2321,14 +2977,14 @@ function useGitStatus(gitApi, repositoryId, enabled) {
|
|
|
2321
2977
|
() => void 0
|
|
2322
2978
|
);
|
|
2323
2979
|
}, [gitApi, repositoryId, enabled, apply]);
|
|
2324
|
-
const scheduleRefresh =
|
|
2980
|
+
const scheduleRefresh = useCallback8(() => {
|
|
2325
2981
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2326
2982
|
timerRef.current = setTimeout(() => {
|
|
2327
2983
|
timerRef.current = null;
|
|
2328
2984
|
refresh();
|
|
2329
2985
|
}, SCHEDULE_REFRESH_MS);
|
|
2330
2986
|
}, [refresh]);
|
|
2331
|
-
|
|
2987
|
+
useEffect8(() => {
|
|
2332
2988
|
return () => {
|
|
2333
2989
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
2334
2990
|
};
|
|
@@ -2354,8 +3010,8 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2354
3010
|
const host = maybeGetDocBlocksHost();
|
|
2355
3011
|
const gitApi = host?.git ?? null;
|
|
2356
3012
|
const workspaceId = gitApi ? requestedWorkspaceId : null;
|
|
2357
|
-
const [capabilities, setCapabilities] =
|
|
2358
|
-
|
|
3013
|
+
const [capabilities, setCapabilities] = useState10(null);
|
|
3014
|
+
useEffect9(() => {
|
|
2359
3015
|
if (!gitApi) return;
|
|
2360
3016
|
let cancelled = false;
|
|
2361
3017
|
let timer = null;
|
|
@@ -2381,9 +3037,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2381
3037
|
};
|
|
2382
3038
|
}, [gitApi]);
|
|
2383
3039
|
const available = gitApi !== null && capabilities?.gitAvailable === true;
|
|
2384
|
-
const [repo, setRepo] =
|
|
2385
|
-
const [remoteWeb, setRemoteWeb] =
|
|
2386
|
-
|
|
3040
|
+
const [repo, setRepo] = useState10(null);
|
|
3041
|
+
const [remoteWeb, setRemoteWeb] = useState10(null);
|
|
3042
|
+
useEffect9(() => {
|
|
2387
3043
|
setRepo(null);
|
|
2388
3044
|
setRemoteWeb(null);
|
|
2389
3045
|
if (!gitApi || !workspaceId || !available) return;
|
|
@@ -2416,15 +3072,15 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2416
3072
|
const isRepo = repositoryId !== null;
|
|
2417
3073
|
const { status, refresh, scheduleRefresh } = useGitStatus(gitApi, repositoryId, isRepo);
|
|
2418
3074
|
const badges = useMemo2(() => buildBadgeMap(status?.changes ?? []), [status]);
|
|
2419
|
-
const [busy, setBusy] =
|
|
2420
|
-
const [lastResult, setLastResult] =
|
|
2421
|
-
const [dialog, setDialog] =
|
|
2422
|
-
const openDialog =
|
|
3075
|
+
const [busy, setBusy] = useState10(null);
|
|
3076
|
+
const [lastResult, setLastResult] = useState10(null);
|
|
3077
|
+
const [dialog, setDialog] = useState10({ kind: "none" });
|
|
3078
|
+
const openDialog = useCallback9((next) => {
|
|
2423
3079
|
setLastResult(null);
|
|
2424
3080
|
setDialog(next);
|
|
2425
3081
|
}, []);
|
|
2426
|
-
const closeDialog =
|
|
2427
|
-
const routeError =
|
|
3082
|
+
const closeDialog = useCallback9(() => setDialog({ kind: "none" }), []);
|
|
3083
|
+
const routeError = useCallback9(
|
|
2428
3084
|
(operation, error) => {
|
|
2429
3085
|
if (error.code === "auth-failed") {
|
|
2430
3086
|
setDialog({
|
|
@@ -2438,11 +3094,11 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2438
3094
|
},
|
|
2439
3095
|
[]
|
|
2440
3096
|
);
|
|
2441
|
-
const statusRef =
|
|
2442
|
-
|
|
3097
|
+
const statusRef = useRef9(status);
|
|
3098
|
+
useEffect9(() => {
|
|
2443
3099
|
statusRef.current = status;
|
|
2444
3100
|
}, [status]);
|
|
2445
|
-
const runAction =
|
|
3101
|
+
const runAction = useCallback9(
|
|
2446
3102
|
async (kind, operation, action, successMessage) => {
|
|
2447
3103
|
if (!gitApi || !repositoryId) return false;
|
|
2448
3104
|
setBusy(kind);
|
|
@@ -2471,7 +3127,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2471
3127
|
},
|
|
2472
3128
|
[gitApi, repositoryId, refresh, routeError]
|
|
2473
3129
|
);
|
|
2474
|
-
const commit =
|
|
3130
|
+
const commit = useCallback9(
|
|
2475
3131
|
(message, paths) => runAction(
|
|
2476
3132
|
"commit",
|
|
2477
3133
|
"commit",
|
|
@@ -2480,7 +3136,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2480
3136
|
),
|
|
2481
3137
|
[runAction]
|
|
2482
3138
|
);
|
|
2483
|
-
const push =
|
|
3139
|
+
const push = useCallback9(async () => {
|
|
2484
3140
|
const setUpstream = statusRef.current?.upstream === null;
|
|
2485
3141
|
await runAction(
|
|
2486
3142
|
"push",
|
|
@@ -2489,13 +3145,13 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2489
3145
|
setUpstream ? "Branch published" : "Pushed"
|
|
2490
3146
|
);
|
|
2491
3147
|
}, [runAction]);
|
|
2492
|
-
const pull =
|
|
3148
|
+
const pull = useCallback9(async () => {
|
|
2493
3149
|
await runAction("pull", "pull", (api, repository) => api.pull(repository), "Pulled");
|
|
2494
3150
|
}, [runAction]);
|
|
2495
|
-
const fetchRemote =
|
|
3151
|
+
const fetchRemote = useCallback9(async () => {
|
|
2496
3152
|
await runAction("fetch", "fetch", (api, repository) => api.fetch(repository), "Fetched");
|
|
2497
3153
|
}, [runAction]);
|
|
2498
|
-
const createBranch =
|
|
3154
|
+
const createBranch = useCallback9(
|
|
2499
3155
|
(name) => runAction(
|
|
2500
3156
|
"branch",
|
|
2501
3157
|
"branch",
|
|
@@ -2504,7 +3160,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2504
3160
|
),
|
|
2505
3161
|
[runAction]
|
|
2506
3162
|
);
|
|
2507
|
-
const switchBranch =
|
|
3163
|
+
const switchBranch = useCallback9(
|
|
2508
3164
|
(name) => runAction(
|
|
2509
3165
|
"branch",
|
|
2510
3166
|
"branch",
|
|
@@ -2513,7 +3169,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2513
3169
|
),
|
|
2514
3170
|
[runAction]
|
|
2515
3171
|
);
|
|
2516
|
-
const openOnRemote =
|
|
3172
|
+
const openOnRemote = useCallback9(
|
|
2517
3173
|
(filePath) => {
|
|
2518
3174
|
if (!remoteWeb || !host) return;
|
|
2519
3175
|
let url = remoteWeb.webUrl;
|
|
@@ -2525,7 +3181,7 @@ function useGit(provider, requestedWorkspaceId, theme) {
|
|
|
2525
3181
|
},
|
|
2526
3182
|
[remoteWeb, host]
|
|
2527
3183
|
);
|
|
2528
|
-
const createPullRequest =
|
|
3184
|
+
const createPullRequest = useCallback9(async () => {
|
|
2529
3185
|
if (!gitApi || !repositoryId) return;
|
|
2530
3186
|
setBusy("pr");
|
|
2531
3187
|
setLastResult(null);
|
|
@@ -2643,7 +3299,7 @@ function retainFileSystemProvider(provider) {
|
|
|
2643
3299
|
}
|
|
2644
3300
|
|
|
2645
3301
|
// src/DocBlocksShell/document-title.ts
|
|
2646
|
-
import { useEffect as
|
|
3302
|
+
import { useEffect as useEffect10 } from "react";
|
|
2647
3303
|
var APP_TITLE = "DocBlocks";
|
|
2648
3304
|
var INSTALLED_DISPLAY_QUERIES = [
|
|
2649
3305
|
"(display-mode: window-controls-overlay)",
|
|
@@ -2667,7 +3323,7 @@ function titleForSelectedFile(selectedFile, homeDocumentTitle = APP_TITLE, homeD
|
|
|
2667
3323
|
return includeAppName ? `${name} - ${APP_TITLE}` : name;
|
|
2668
3324
|
}
|
|
2669
3325
|
function useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath) {
|
|
2670
|
-
|
|
3326
|
+
useEffect10(() => {
|
|
2671
3327
|
if (typeof document === "undefined") return;
|
|
2672
3328
|
const displayQueries = typeof globalThis.matchMedia === "function" ? INSTALLED_DISPLAY_QUERIES.map((query) => globalThis.matchMedia(query)) : [];
|
|
2673
3329
|
const updateTitle = () => {
|
|
@@ -2719,6 +3375,63 @@ async function decodeDbkWorkspace(bytes, options) {
|
|
|
2719
3375
|
return snapshot;
|
|
2720
3376
|
}
|
|
2721
3377
|
|
|
3378
|
+
// src/DocBlocksShell/editor-link-navigation.ts
|
|
3379
|
+
import { HOST_WIRE_LIMITS, isBoundedString, parseExternalHttpUrl } from "@bendyline/docblocks/host";
|
|
3380
|
+
var URI_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:/;
|
|
3381
|
+
var MARKDOWN_EXTENSION = /\.(?:md|markdown|mdown)$/iu;
|
|
3382
|
+
function resolveShellEditorLinkTarget(href, documentPath) {
|
|
3383
|
+
if (!isBoundedString(href, HOST_WIRE_LIMITS.urlCharacters, 1)) return null;
|
|
3384
|
+
if (href.startsWith("#")) return { kind: "fragment" };
|
|
3385
|
+
const externalUrl = parseExternalHttpUrl(href);
|
|
3386
|
+
if (externalUrl) return { kind: "external", url: externalUrl };
|
|
3387
|
+
if (documentPath === null || href.startsWith("//") || URI_SCHEME.test(href)) return null;
|
|
3388
|
+
const splitAt = href.search(/[?#]/u);
|
|
3389
|
+
const encodedPath = splitAt === -1 ? href : href.slice(0, splitAt);
|
|
3390
|
+
if (!encodedPath) return null;
|
|
3391
|
+
let decodedPath;
|
|
3392
|
+
try {
|
|
3393
|
+
decodedPath = decodeURIComponent(encodedPath);
|
|
3394
|
+
} catch {
|
|
3395
|
+
return null;
|
|
3396
|
+
}
|
|
3397
|
+
if (decodedPath.includes("\\") || hasControlCharacter(decodedPath)) return null;
|
|
3398
|
+
const documentSegments = normalizeDocumentPath(documentPath);
|
|
3399
|
+
if (!documentSegments) return null;
|
|
3400
|
+
const targetSegments = decodedPath.startsWith("/") ? [] : documentSegments.slice(0, -1);
|
|
3401
|
+
for (const segment of decodedPath.split("/")) {
|
|
3402
|
+
if (segment === "" || segment === ".") continue;
|
|
3403
|
+
if (segment === "..") {
|
|
3404
|
+
if (targetSegments.length === 0) return null;
|
|
3405
|
+
targetSegments.pop();
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
if (segment.length > 255) return null;
|
|
3409
|
+
targetSegments.push(segment);
|
|
3410
|
+
}
|
|
3411
|
+
const path = targetSegments.join("/");
|
|
3412
|
+
if (!path || path.length > HOST_WIRE_LIMITS.pathCharacters || !MARKDOWN_EXTENSION.test(path)) {
|
|
3413
|
+
return null;
|
|
3414
|
+
}
|
|
3415
|
+
return { kind: "workspace", path };
|
|
3416
|
+
}
|
|
3417
|
+
function normalizeDocumentPath(path) {
|
|
3418
|
+
if (!isBoundedString(path, HOST_WIRE_LIMITS.pathCharacters, 1)) return null;
|
|
3419
|
+
const segments = [];
|
|
3420
|
+
for (const segment of path.replace(/\\/g, "/").split("/")) {
|
|
3421
|
+
if (segment === "" || segment === ".") continue;
|
|
3422
|
+
if (segment === ".." || segment.length > 255 || hasControlCharacter(segment)) return null;
|
|
3423
|
+
segments.push(segment);
|
|
3424
|
+
}
|
|
3425
|
+
return segments.length > 0 ? segments : null;
|
|
3426
|
+
}
|
|
3427
|
+
function hasControlCharacter(value) {
|
|
3428
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
3429
|
+
const code = value.charCodeAt(index);
|
|
3430
|
+
if (code <= 31 || code === 127) return true;
|
|
3431
|
+
}
|
|
3432
|
+
return false;
|
|
3433
|
+
}
|
|
3434
|
+
|
|
2722
3435
|
// src/DocBlocksShell/import-files.ts
|
|
2723
3436
|
import { FsError as FsError3 } from "@bendyline/docblocks/filesystem";
|
|
2724
3437
|
|
|
@@ -2869,33 +3582,33 @@ function summariseImport(result) {
|
|
|
2869
3582
|
}
|
|
2870
3583
|
|
|
2871
3584
|
// src/components/usePromptDialog.ts
|
|
2872
|
-
import React2, { useCallback as
|
|
3585
|
+
import React2, { useCallback as useCallback11, useEffect as useEffect12, useRef as useRef11, useState as useState12 } from "react";
|
|
2873
3586
|
|
|
2874
3587
|
// src/components/PromptDialog.tsx
|
|
2875
|
-
import { useCallback as
|
|
2876
|
-
import { Fragment as Fragment4, jsx as
|
|
3588
|
+
import { useCallback as useCallback10, useEffect as useEffect11, useId as useId2, useRef as useRef10, useState as useState11 } from "react";
|
|
3589
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2877
3590
|
function PromptDialog({ request, onSettle }) {
|
|
2878
|
-
const [value, setValue] =
|
|
2879
|
-
const inputRef =
|
|
2880
|
-
const inputId =
|
|
2881
|
-
const cancel =
|
|
2882
|
-
const submit =
|
|
3591
|
+
const [value, setValue] = useState11(request.initialValue ?? "");
|
|
3592
|
+
const inputRef = useRef10(null);
|
|
3593
|
+
const inputId = useId2();
|
|
3594
|
+
const cancel = useCallback10(() => onSettle(null), [onSettle]);
|
|
3595
|
+
const submit = useCallback10(() => {
|
|
2883
3596
|
if (value.trim() === "") return;
|
|
2884
3597
|
onSettle(value);
|
|
2885
3598
|
}, [onSettle, value]);
|
|
2886
|
-
|
|
3599
|
+
useEffect11(() => {
|
|
2887
3600
|
inputRef.current?.select();
|
|
2888
3601
|
}, []);
|
|
2889
|
-
return /* @__PURE__ */
|
|
3602
|
+
return /* @__PURE__ */ jsx10(
|
|
2890
3603
|
Dialog,
|
|
2891
3604
|
{
|
|
2892
3605
|
title: request.title,
|
|
2893
3606
|
onClose: cancel,
|
|
2894
3607
|
initialFocusRef: inputRef,
|
|
2895
3608
|
closeOnBackdrop: false,
|
|
2896
|
-
footer: /* @__PURE__ */
|
|
2897
|
-
/* @__PURE__ */
|
|
2898
|
-
/* @__PURE__ */
|
|
3609
|
+
footer: /* @__PURE__ */ jsxs9(Fragment4, { children: [
|
|
3610
|
+
/* @__PURE__ */ jsx10("button", { type: "button", className: "db-git-secondary-btn", onClick: cancel, children: "Cancel" }),
|
|
3611
|
+
/* @__PURE__ */ jsx10(
|
|
2899
3612
|
"button",
|
|
2900
3613
|
{
|
|
2901
3614
|
type: "button",
|
|
@@ -2906,9 +3619,9 @@ function PromptDialog({ request, onSettle }) {
|
|
|
2906
3619
|
}
|
|
2907
3620
|
)
|
|
2908
3621
|
] }),
|
|
2909
|
-
children: /* @__PURE__ */
|
|
2910
|
-
/* @__PURE__ */
|
|
2911
|
-
/* @__PURE__ */
|
|
3622
|
+
children: /* @__PURE__ */ jsxs9("div", { className: "db-git-form-row", children: [
|
|
3623
|
+
/* @__PURE__ */ jsx10("label", { className: "db-git-form-label", htmlFor: inputId, children: request.label }),
|
|
3624
|
+
/* @__PURE__ */ jsx10(
|
|
2912
3625
|
"input",
|
|
2913
3626
|
{
|
|
2914
3627
|
id: inputId,
|
|
@@ -2931,17 +3644,17 @@ function PromptDialog({ request, onSettle }) {
|
|
|
2931
3644
|
|
|
2932
3645
|
// src/components/usePromptDialog.ts
|
|
2933
3646
|
function usePromptDialog() {
|
|
2934
|
-
const [pending, setPending] =
|
|
2935
|
-
const pendingRef =
|
|
2936
|
-
const nextIdRef =
|
|
2937
|
-
const settlePending =
|
|
3647
|
+
const [pending, setPending] = useState12(null);
|
|
3648
|
+
const pendingRef = useRef11(null);
|
|
3649
|
+
const nextIdRef = useRef11(0);
|
|
3650
|
+
const settlePending = useCallback11((value) => {
|
|
2938
3651
|
const current = pendingRef.current;
|
|
2939
3652
|
if (!current) return;
|
|
2940
3653
|
pendingRef.current = null;
|
|
2941
3654
|
setPending((open) => open?.id === current.id ? null : open);
|
|
2942
3655
|
current.settle(value);
|
|
2943
3656
|
}, []);
|
|
2944
|
-
const prompt =
|
|
3657
|
+
const prompt = useCallback11(
|
|
2945
3658
|
(request) => new Promise((resolve) => {
|
|
2946
3659
|
settlePending(null);
|
|
2947
3660
|
const next = { id: ++nextIdRef.current, request, settle: resolve };
|
|
@@ -2950,7 +3663,7 @@ function usePromptDialog() {
|
|
|
2950
3663
|
}),
|
|
2951
3664
|
[settlePending]
|
|
2952
3665
|
);
|
|
2953
|
-
|
|
3666
|
+
useEffect12(() => {
|
|
2954
3667
|
return () => settlePending(null);
|
|
2955
3668
|
}, [settlePending]);
|
|
2956
3669
|
const promptDialog = pending ? React2.createElement(PromptDialog, {
|
|
@@ -2963,28 +3676,28 @@ function usePromptDialog() {
|
|
|
2963
3676
|
}
|
|
2964
3677
|
|
|
2965
3678
|
// src/components/useConfirmDialog.ts
|
|
2966
|
-
import React4, { useCallback as
|
|
3679
|
+
import React4, { useCallback as useCallback13, useEffect as useEffect13, useRef as useRef13, useState as useState13 } from "react";
|
|
2967
3680
|
|
|
2968
3681
|
// src/components/ConfirmDialog.tsx
|
|
2969
|
-
import { useCallback as
|
|
2970
|
-
import { Fragment as Fragment5, jsx as
|
|
3682
|
+
import { useCallback as useCallback12, useRef as useRef12 } from "react";
|
|
3683
|
+
import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2971
3684
|
function ConfirmDialog({ request, onSettle }) {
|
|
2972
3685
|
const acknowledge = request.kind === "acknowledge";
|
|
2973
3686
|
const destructive = request.kind === "confirm" && request.destructive === true;
|
|
2974
|
-
const cancelRef =
|
|
2975
|
-
const confirmRef =
|
|
2976
|
-
const accept =
|
|
2977
|
-
const reject =
|
|
3687
|
+
const cancelRef = useRef12(null);
|
|
3688
|
+
const confirmRef = useRef12(null);
|
|
3689
|
+
const accept = useCallback12(() => onSettle(true), [onSettle]);
|
|
3690
|
+
const reject = useCallback12(() => onSettle(false), [onSettle]);
|
|
2978
3691
|
const close = acknowledge ? accept : reject;
|
|
2979
|
-
return /* @__PURE__ */
|
|
3692
|
+
return /* @__PURE__ */ jsx11(
|
|
2980
3693
|
Dialog,
|
|
2981
3694
|
{
|
|
2982
3695
|
title: request.title,
|
|
2983
3696
|
onClose: close,
|
|
2984
3697
|
initialFocusRef: destructive ? cancelRef : confirmRef,
|
|
2985
|
-
footer: /* @__PURE__ */
|
|
2986
|
-
!acknowledge && /* @__PURE__ */
|
|
2987
|
-
/* @__PURE__ */
|
|
3698
|
+
footer: /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
3699
|
+
!acknowledge && /* @__PURE__ */ jsx11("button", { ref: cancelRef, type: "button", className: "db-git-secondary-btn", onClick: reject, children: request.cancelLabel ?? "Cancel" }),
|
|
3700
|
+
/* @__PURE__ */ jsx11(
|
|
2988
3701
|
"button",
|
|
2989
3702
|
{
|
|
2990
3703
|
ref: confirmRef,
|
|
@@ -2995,24 +3708,24 @@ function ConfirmDialog({ request, onSettle }) {
|
|
|
2995
3708
|
}
|
|
2996
3709
|
)
|
|
2997
3710
|
] }),
|
|
2998
|
-
children: /* @__PURE__ */
|
|
3711
|
+
children: /* @__PURE__ */ jsx11("p", { className: "db-dialog-message", children: request.message })
|
|
2999
3712
|
}
|
|
3000
3713
|
);
|
|
3001
3714
|
}
|
|
3002
3715
|
|
|
3003
3716
|
// src/components/useConfirmDialog.ts
|
|
3004
3717
|
function useConfirmDialog() {
|
|
3005
|
-
const [pending, setPending] =
|
|
3006
|
-
const pendingRef =
|
|
3007
|
-
const nextIdRef =
|
|
3008
|
-
const settlePending =
|
|
3718
|
+
const [pending, setPending] = useState13(null);
|
|
3719
|
+
const pendingRef = useRef13(null);
|
|
3720
|
+
const nextIdRef = useRef13(0);
|
|
3721
|
+
const settlePending = useCallback13((value) => {
|
|
3009
3722
|
const current = pendingRef.current;
|
|
3010
3723
|
if (!current) return;
|
|
3011
3724
|
pendingRef.current = null;
|
|
3012
3725
|
setPending((open2) => open2?.id === current.id ? null : open2);
|
|
3013
3726
|
current.settle(value);
|
|
3014
3727
|
}, []);
|
|
3015
|
-
const open =
|
|
3728
|
+
const open = useCallback13(
|
|
3016
3729
|
(request) => new Promise((resolve) => {
|
|
3017
3730
|
settlePending(false);
|
|
3018
3731
|
const next = { id: ++nextIdRef.current, request, settle: resolve };
|
|
@@ -3021,17 +3734,17 @@ function useConfirmDialog() {
|
|
|
3021
3734
|
}),
|
|
3022
3735
|
[settlePending]
|
|
3023
3736
|
);
|
|
3024
|
-
const confirm =
|
|
3737
|
+
const confirm = useCallback13(
|
|
3025
3738
|
(request) => open({ kind: "confirm", ...request }),
|
|
3026
3739
|
[open]
|
|
3027
3740
|
);
|
|
3028
|
-
const acknowledge =
|
|
3741
|
+
const acknowledge = useCallback13(
|
|
3029
3742
|
async (request) => {
|
|
3030
3743
|
await open({ kind: "acknowledge", ...request });
|
|
3031
3744
|
},
|
|
3032
3745
|
[open]
|
|
3033
3746
|
);
|
|
3034
|
-
|
|
3747
|
+
useEffect13(() => {
|
|
3035
3748
|
return () => settlePending(false);
|
|
3036
3749
|
}, [settlePending]);
|
|
3037
3750
|
const confirmDialog = pending ? React4.createElement(ConfirmDialog, {
|
|
@@ -3084,7 +3797,7 @@ function buildIssueReportUrl(environment) {
|
|
|
3084
3797
|
}
|
|
3085
3798
|
|
|
3086
3799
|
// src/DocBlocksShell/last-state.ts
|
|
3087
|
-
import { tryParseWorkspacePath } from "@bendyline/docblocks/filesystem";
|
|
3800
|
+
import { tryParseWorkspacePath as tryParseWorkspacePath2 } from "@bendyline/docblocks/filesystem";
|
|
3088
3801
|
var LAST_STATE_KEY = "docblocks:lastState";
|
|
3089
3802
|
var MAX_WORKSPACE_ID_CHARACTERS = 256;
|
|
3090
3803
|
var MAX_WORKSPACE_PATH_CHARACTERS = 4096;
|
|
@@ -3105,7 +3818,7 @@ function parseLastState(value) {
|
|
|
3105
3818
|
if (!hasExactKeys(record, ["workspaceId", "filePath", "view"])) return null;
|
|
3106
3819
|
if (!isBoundedText(record.workspaceId, MAX_WORKSPACE_ID_CHARACTERS)) return null;
|
|
3107
3820
|
if (!isBoundedText(record.filePath, MAX_WORKSPACE_PATH_CHARACTERS)) return null;
|
|
3108
|
-
const parsedPath =
|
|
3821
|
+
const parsedPath = tryParseWorkspacePath2(record.filePath);
|
|
3109
3822
|
if (!parsedPath) return null;
|
|
3110
3823
|
if (typeof record.view !== "string" || !EDITOR_VIEWS.has(record.view)) return null;
|
|
3111
3824
|
return {
|
|
@@ -3205,8 +3918,8 @@ function saveViewPreferences(preferences) {
|
|
|
3205
3918
|
}
|
|
3206
3919
|
|
|
3207
3920
|
// src/DocBlocksShell/UpdateAvailableNotice.tsx
|
|
3208
|
-
import { useState as
|
|
3209
|
-
import { Fragment as Fragment6, jsx as
|
|
3921
|
+
import { useState as useState14 } from "react";
|
|
3922
|
+
import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3210
3923
|
var UPDATE_PROMPT_ID = "db-update-available-prompt";
|
|
3211
3924
|
function UpdateAvailableNotice({
|
|
3212
3925
|
available,
|
|
@@ -3214,11 +3927,11 @@ function UpdateAvailableNotice({
|
|
|
3214
3927
|
blocked = false,
|
|
3215
3928
|
statusBarVisible
|
|
3216
3929
|
}) {
|
|
3217
|
-
const [promptOpen, setPromptOpen] =
|
|
3930
|
+
const [promptOpen, setPromptOpen] = useState14(false);
|
|
3218
3931
|
if (!available || !onApplyUpdate) return null;
|
|
3219
3932
|
const promptVisible = promptOpen && !blocked;
|
|
3220
|
-
return /* @__PURE__ */
|
|
3221
|
-
/* @__PURE__ */
|
|
3933
|
+
return /* @__PURE__ */ jsxs11(Fragment6, { children: [
|
|
3934
|
+
/* @__PURE__ */ jsx12(
|
|
3222
3935
|
"button",
|
|
3223
3936
|
{
|
|
3224
3937
|
type: "button",
|
|
@@ -3231,19 +3944,27 @@ function UpdateAvailableNotice({
|
|
|
3231
3944
|
title: "A new version of DocBlocks is available",
|
|
3232
3945
|
children: "Update available"
|
|
3233
3946
|
}
|
|
3234
|
-
),
|
|
3235
|
-
promptVisible && /* @__PURE__ */
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3947
|
+
),
|
|
3948
|
+
promptVisible && /* @__PURE__ */ jsxs11(
|
|
3949
|
+
"div",
|
|
3950
|
+
{
|
|
3951
|
+
id: UPDATE_PROMPT_ID,
|
|
3952
|
+
className: `db-update-banner${statusBarVisible ? "" : " db-update-banner--floating"}`,
|
|
3953
|
+
role: "alert",
|
|
3954
|
+
children: [
|
|
3955
|
+
/* @__PURE__ */ jsx12("span", { children: "A new version of DocBlocks is available. Reload to update the editor and site pages." }),
|
|
3956
|
+
/* @__PURE__ */ jsxs11("div", { className: "db-update-banner-actions", children: [
|
|
3957
|
+
/* @__PURE__ */ jsx12("button", { type: "button", onClick: onApplyUpdate, disabled: blocked, children: "Reload" }),
|
|
3958
|
+
/* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setPromptOpen(false), children: "Later" })
|
|
3959
|
+
] })
|
|
3960
|
+
]
|
|
3961
|
+
}
|
|
3962
|
+
)
|
|
3242
3963
|
] });
|
|
3243
3964
|
}
|
|
3244
3965
|
|
|
3245
3966
|
// src/DocBlocksShell/useDocumentLinkProvider.ts
|
|
3246
|
-
import { useCallback as
|
|
3967
|
+
import { useCallback as useCallback14, useEffect as useEffect14, useRef as useRef14 } from "react";
|
|
3247
3968
|
|
|
3248
3969
|
// src/DocBlocksShell/document-links.ts
|
|
3249
3970
|
import {
|
|
@@ -3425,8 +4146,8 @@ function createDocumentLinkCandidates(entries, selectedFile, query) {
|
|
|
3425
4146
|
|
|
3426
4147
|
// src/DocBlocksShell/useDocumentLinkProvider.ts
|
|
3427
4148
|
function useDocumentLinkProvider(provider, selectedFile, epoch = 0) {
|
|
3428
|
-
const cacheRef =
|
|
3429
|
-
|
|
4149
|
+
const cacheRef = useRef14(null);
|
|
4150
|
+
useEffect14(
|
|
3430
4151
|
() => () => {
|
|
3431
4152
|
if (cacheRef.current?.provider !== provider || cacheRef.current.epoch !== epoch) return;
|
|
3432
4153
|
cacheRef.current.controller.abort();
|
|
@@ -3434,7 +4155,7 @@ function useDocumentLinkProvider(provider, selectedFile, epoch = 0) {
|
|
|
3434
4155
|
},
|
|
3435
4156
|
[provider, epoch]
|
|
3436
4157
|
);
|
|
3437
|
-
return
|
|
4158
|
+
return useCallback14(
|
|
3438
4159
|
async (query) => {
|
|
3439
4160
|
if (!provider || !selectedFile) return [];
|
|
3440
4161
|
let cache = cacheRef.current;
|
|
@@ -3549,8 +4270,93 @@ async function copyTransientWorkspaceContents(source, destination) {
|
|
|
3549
4270
|
}
|
|
3550
4271
|
}
|
|
3551
4272
|
|
|
4273
|
+
// src/DocBlocksShell/welcome-document.ts
|
|
4274
|
+
var WELCOME_DOCUMENT_PATH = "/aboutDocBlocks.md";
|
|
4275
|
+
var WELCOME_DOCUMENT_CONTENT = [
|
|
4276
|
+
"---",
|
|
4277
|
+
"squisq-theme: warm-earth",
|
|
4278
|
+
"squisq-cover-slide: false",
|
|
4279
|
+
"---",
|
|
4280
|
+
"",
|
|
4281
|
+
'# DocBlocks: one Markdown file, many finished forms {[title subtitle="Local-first writing for pages, documents, slideshows, and video"]}',
|
|
4282
|
+
"",
|
|
4283
|
+
"Write in plain Markdown. Shape it visually. Keep it portable.",
|
|
4284
|
+
"",
|
|
4285
|
+
"## A document editor that never asks you to stop owning the document {[factCard]}",
|
|
4286
|
+
"",
|
|
4287
|
+
"DocBlocks pairs a friendly visual writing surface with the durable source format you already know. There is no account to create, no proprietary document to rescue later, and no requirement to send your work to a hosted editor.",
|
|
4288
|
+
"",
|
|
4289
|
+
"## One source, four ways to publish {[diagram]}",
|
|
4290
|
+
"",
|
|
4291
|
+
"```diagram",
|
|
4292
|
+
"+------------+",
|
|
4293
|
+
"| Markdown |",
|
|
4294
|
+
"+-----+------+",
|
|
4295
|
+
" |",
|
|
4296
|
+
" +---+------+------------+-----------+",
|
|
4297
|
+
" | | | |",
|
|
4298
|
+
" v v v v",
|
|
4299
|
+
"+-------+ +---------+ +------------+ +-------+",
|
|
4300
|
+
"| Pages | |Documents| | Slideshows | | Video |",
|
|
4301
|
+
"+-------+ +---------+ +------------+ +-------+",
|
|
4302
|
+
"```",
|
|
4303
|
+
"",
|
|
4304
|
+
"## Write visually, then look under the hood whenever you like {[content]}",
|
|
4305
|
+
"",
|
|
4306
|
+
"Use the rich editor for headings, lists, tables, links, images, media, and everyday formatting. Switch to raw Markdown for exact control. Add themes, transform styles, and squiggly-square section annotations when a passage deserves its own visual treatment. The readable `.md` source stays at the center of the workflow.",
|
|
4307
|
+
"",
|
|
4308
|
+
"## Preview the finished idea before you export it {[list]}",
|
|
4309
|
+
"",
|
|
4310
|
+
"- **Document** \u2014 Read the work as a polished, continuous document.",
|
|
4311
|
+
"- **Page** \u2014 See how it behaves as a web page.",
|
|
4312
|
+
"- **Slideshow** \u2014 Step through headings as designed presentation blocks.",
|
|
4313
|
+
"- **Video** \u2014 Rehearse timing, motion, narration, and media in a player-ready view.",
|
|
4314
|
+
"",
|
|
4315
|
+
'## Local or installed: choose the workspace that fits {[twoColumn header="Work where your files make sense" left="Web|Browser-local workspaces or folders you explicitly grant" right="Desktop|Real folders, native menus, file opening, and updates"]}',
|
|
4316
|
+
"",
|
|
4317
|
+
'## In the project or in the pipeline {[twoColumn header="The same Markdown keeps moving" left="VS Code|Rich editing beside tabs, source control, and the project tree" right="CLI + MCP|Build, serve, convert, render, inspect, validate, and automate"]}',
|
|
4318
|
+
"",
|
|
4319
|
+
"## The quiet conveniences are there, too {[list]}",
|
|
4320
|
+
"",
|
|
4321
|
+
"- **Workspaces and file navigation** keep related documents organized without hiding their source.",
|
|
4322
|
+
"- **Automatic saving** protects the active revision while still surfacing conflicts or save errors.",
|
|
4323
|
+
"- **Optional version history** keeps plain sibling revisions when you want a trail of changes.",
|
|
4324
|
+
"- **Copy-by-link sharing** creates a bounded Markdown copy that can open directly in a chosen Play view.",
|
|
4325
|
+
"- **Offline-ready, local-first storage** keeps browser work on this device and native work inside folders you choose.",
|
|
4326
|
+
"",
|
|
4327
|
+
"## Export without rebuilding the document {[list]}",
|
|
4328
|
+
"",
|
|
4329
|
+
"- Create **Word documents and PDFs** for review, printing, and fixed-layout sharing.",
|
|
4330
|
+
"- Create **PowerPoint decks** from the same annotated sections you preview as a slideshow.",
|
|
4331
|
+
"- Publish **HTML pages, packaged HTML, and EPUB e-books**.",
|
|
4332
|
+
"- Keep portable **Markdown or DBK bundles** with their media assets.",
|
|
4333
|
+
"- Use the linked CLI registry for **spreadsheets, MP4 video, animated GIF, batch conversion, and more**.",
|
|
4334
|
+
"",
|
|
4335
|
+
"## Local-first is concrete, not decorative {[quote]}",
|
|
4336
|
+
"",
|
|
4337
|
+
"> Your browser workspace stays on this device. Native folders are opened only when you choose them. Your Markdown remains useful in DocBlocks, Git, a terminal, another editor, or an AI-assisted workflow.",
|
|
4338
|
+
"",
|
|
4339
|
+
"## Agents get document tools, not a blank check {[factCard]}",
|
|
4340
|
+
"",
|
|
4341
|
+
"The local MCP server can inspect, validate, preview, compare, and convert documents. Results remain temporary session artifacts until they are deliberately saved, and filesystem access begins with explicit roots rather than assumed authority.",
|
|
4342
|
+
"",
|
|
4343
|
+
"## Start with three small moves {[list]}",
|
|
4344
|
+
"",
|
|
4345
|
+
"1. Create a document with **New file** in the file pane.",
|
|
4346
|
+
"2. Write visually or in Markdown source; switch views whenever it helps.",
|
|
4347
|
+
"3. Open **Play** or **Export** when the idea is ready to become something else.",
|
|
4348
|
+
"",
|
|
4349
|
+
"## Explore DocBlocks {[list]}",
|
|
4350
|
+
"",
|
|
4351
|
+
"- [Desktop app](https://docblocks.com/desktop/) \u2014 macOS, Windows, and Linux.",
|
|
4352
|
+
"- [VS Code extension](https://docblocks.com/vscode/) \u2014 rich Markdown editing inside a workspace.",
|
|
4353
|
+
"- [CLI and MCP server](https://docblocks.com/cli/) \u2014 conversion, rendering, and agent workflows.",
|
|
4354
|
+
"- [Supported formats](https://docblocks.com/formats/) \u2014 current import and export directions.",
|
|
4355
|
+
"- [Documentation](https://docblocks.com/docs/) \u2014 guides for using and extending DocBlocks."
|
|
4356
|
+
].join("\n");
|
|
4357
|
+
|
|
3552
4358
|
// src/DocBlocksShell/DocBlocksShell.tsx
|
|
3553
|
-
import { Fragment as Fragment7, jsx as
|
|
4359
|
+
import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3554
4360
|
var editorShellModulePromise = null;
|
|
3555
4361
|
function loadEditorShell() {
|
|
3556
4362
|
editorShellModulePromise ?? (editorShellModulePromise = (async () => {
|
|
@@ -3562,9 +4368,9 @@ function loadEditorShell() {
|
|
|
3562
4368
|
return editorShellModulePromise;
|
|
3563
4369
|
}
|
|
3564
4370
|
var EditorShell = lazy2(loadEditorShell);
|
|
3565
|
-
var GitUI = lazy2(() => import("./GitUI-
|
|
4371
|
+
var GitUI = lazy2(() => import("./GitUI-CAKFVKWY.js").then((m) => ({ default: m.GitUI })));
|
|
3566
4372
|
var GitToolbarControl = lazy2(
|
|
3567
|
-
() => import("./GitToolbarControl-
|
|
4373
|
+
() => import("./GitToolbarControl-VJVK2B4X.js").then((m) => ({ default: m.GitToolbarControl }))
|
|
3568
4374
|
);
|
|
3569
4375
|
var DOCBLOCKS_VIDEO_EXPORT_PALETTE = Object.freeze({
|
|
3570
4376
|
overlay: "rgba(0, 0, 0, 0.72)",
|
|
@@ -3639,10 +4445,10 @@ function isMemoryWorkspaceProvider(provider) {
|
|
|
3639
4445
|
return typeof candidate.captureContents === "function" && typeof candidate.replaceContents === "function" && typeof candidate.treeVersion === "number";
|
|
3640
4446
|
}
|
|
3641
4447
|
function useOsTheme() {
|
|
3642
|
-
const [dark, setDark] =
|
|
4448
|
+
const [dark, setDark] = useState15(
|
|
3643
4449
|
() => typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
3644
4450
|
);
|
|
3645
|
-
|
|
4451
|
+
useEffect15(() => {
|
|
3646
4452
|
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
3647
4453
|
const handler = (e) => setDark(e.matches);
|
|
3648
4454
|
mq.addEventListener("change", handler);
|
|
@@ -3713,6 +4519,32 @@ async function readProviderText(provider, path) {
|
|
|
3713
4519
|
const file = await providerV2.readFile(parseWorkspacePath5(path));
|
|
3714
4520
|
return file ? decodeUtf8Text(file.data, { label: "The document", path: parseWorkspacePath5(path) }) : null;
|
|
3715
4521
|
}
|
|
4522
|
+
async function pinnedProviderFileExists(provider, path) {
|
|
4523
|
+
const providerV2 = getFileSystemProviderV25(provider);
|
|
4524
|
+
if (!providerV2) return provider.exists(path);
|
|
4525
|
+
const entry = await providerV2.stat(parseWorkspacePath5(path));
|
|
4526
|
+
if (!entry) return false;
|
|
4527
|
+
if (entry.kind !== "file") throw new Error("The pinned path is no longer a document.");
|
|
4528
|
+
return true;
|
|
4529
|
+
}
|
|
4530
|
+
async function removePinnedProviderFile(provider, path) {
|
|
4531
|
+
const providerV2 = getFileSystemProviderV25(provider);
|
|
4532
|
+
if (providerV2) {
|
|
4533
|
+
const canonical = parseWorkspacePath5(path);
|
|
4534
|
+
const entry = await providerV2.stat(canonical);
|
|
4535
|
+
if (!entry) return false;
|
|
4536
|
+
if (entry.kind !== "file") throw new Error("The pinned path is no longer a document.");
|
|
4537
|
+
await providerV2.remove(canonical, {
|
|
4538
|
+
recursive: false,
|
|
4539
|
+
missing: "error",
|
|
4540
|
+
expectedVersion: entry.version
|
|
4541
|
+
});
|
|
4542
|
+
return true;
|
|
4543
|
+
}
|
|
4544
|
+
if (!await provider.exists(path)) return false;
|
|
4545
|
+
await provider.delete(path);
|
|
4546
|
+
return true;
|
|
4547
|
+
}
|
|
3716
4548
|
async function readStableFileSnapshot(provider, path) {
|
|
3717
4549
|
const providerV2 = getFileSystemProviderV25(provider);
|
|
3718
4550
|
if (providerV2) {
|
|
@@ -3798,10 +4630,10 @@ async function createElectronProviderFromWorkspace(ws) {
|
|
|
3798
4630
|
return createElectronFileSystemProvider(ws.id, ws.name, ws.rootPath);
|
|
3799
4631
|
}
|
|
3800
4632
|
function useIsMobile(breakpoint = 768) {
|
|
3801
|
-
const [isMobile, setIsMobile] =
|
|
4633
|
+
const [isMobile, setIsMobile] = useState15(
|
|
3802
4634
|
() => typeof window !== "undefined" && window.matchMedia(`(max-width: ${breakpoint}px)`).matches
|
|
3803
4635
|
);
|
|
3804
|
-
|
|
4636
|
+
useEffect15(() => {
|
|
3805
4637
|
const mq = window.matchMedia(`(max-width: ${breakpoint}px)`);
|
|
3806
4638
|
const handler = (e) => setIsMobile(e.matches);
|
|
3807
4639
|
mq.addEventListener("change", handler);
|
|
@@ -3810,7 +4642,7 @@ function useIsMobile(breakpoint = 768) {
|
|
|
3810
4642
|
return isMobile;
|
|
3811
4643
|
}
|
|
3812
4644
|
function FolderGlyph() {
|
|
3813
|
-
return /* @__PURE__ */
|
|
4645
|
+
return /* @__PURE__ */ jsx13(
|
|
3814
4646
|
"svg",
|
|
3815
4647
|
{
|
|
3816
4648
|
viewBox: "0 0 24 24",
|
|
@@ -3819,12 +4651,12 @@ function FolderGlyph() {
|
|
|
3819
4651
|
strokeWidth: "1.5",
|
|
3820
4652
|
strokeLinejoin: "round",
|
|
3821
4653
|
"aria-hidden": "true",
|
|
3822
|
-
children: /* @__PURE__ */
|
|
4654
|
+
children: /* @__PURE__ */ jsx13("path", { d: "M3 7a1 1 0 0 1 1-1h5l2 2h9a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z" })
|
|
3823
4655
|
}
|
|
3824
4656
|
);
|
|
3825
4657
|
}
|
|
3826
4658
|
function FileGlyph() {
|
|
3827
|
-
return /* @__PURE__ */
|
|
4659
|
+
return /* @__PURE__ */ jsxs12(
|
|
3828
4660
|
"svg",
|
|
3829
4661
|
{
|
|
3830
4662
|
viewBox: "0 0 24 24",
|
|
@@ -3834,8 +4666,8 @@ function FileGlyph() {
|
|
|
3834
4666
|
strokeLinejoin: "round",
|
|
3835
4667
|
"aria-hidden": "true",
|
|
3836
4668
|
children: [
|
|
3837
|
-
/* @__PURE__ */
|
|
3838
|
-
/* @__PURE__ */
|
|
4669
|
+
/* @__PURE__ */ jsx13("path", { d: "M6 3h8l5 5v12a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z" }),
|
|
4670
|
+
/* @__PURE__ */ jsx13("path", { d: "M14 3v5h5" })
|
|
3839
4671
|
]
|
|
3840
4672
|
}
|
|
3841
4673
|
);
|
|
@@ -3856,67 +4688,77 @@ function DocBlocksShell({
|
|
|
3856
4688
|
versioningRef,
|
|
3857
4689
|
updateAvailable = false,
|
|
3858
4690
|
onApplyUpdate,
|
|
4691
|
+
statusBarSlotRight,
|
|
3859
4692
|
offlineReady = false
|
|
3860
4693
|
}) {
|
|
3861
4694
|
const osTheme = useOsTheme();
|
|
3862
|
-
const [themePreference, setThemePreference] =
|
|
3863
|
-
const [accentColor, setAccentColor] =
|
|
3864
|
-
const [writeCanvasSettings, setWriteCanvasSettings] =
|
|
4695
|
+
const [themePreference, setThemePreference] = useState15(loadThemePreference);
|
|
4696
|
+
const [accentColor, setAccentColor] = useState15(loadAccentColor);
|
|
4697
|
+
const [writeCanvasSettings, setWriteCanvasSettings] = useState15(
|
|
3865
4698
|
loadWriteCanvasPreferences
|
|
3866
4699
|
);
|
|
4700
|
+
const editorWriteCanvasSettings = useMemo3(
|
|
4701
|
+
() => ({
|
|
4702
|
+
textSize: writeCanvasSettings.textSize,
|
|
4703
|
+
lineSpacing: writeCanvasSettings.lineSpacing,
|
|
4704
|
+
...resolveWriteCanvasFonts(writeCanvasSettings.fontScheme)
|
|
4705
|
+
}),
|
|
4706
|
+
[writeCanvasSettings]
|
|
4707
|
+
);
|
|
3867
4708
|
const resolvedTheme = resolveShellTheme({ preference: themePreference, hostTheme, osTheme });
|
|
3868
|
-
const handleThemeChange =
|
|
4709
|
+
const handleThemeChange = useCallback15((pref) => {
|
|
3869
4710
|
setThemePreference(pref);
|
|
3870
4711
|
saveThemePreference(pref);
|
|
3871
4712
|
}, []);
|
|
3872
|
-
|
|
3873
|
-
if (
|
|
4713
|
+
useEffect15(() => {
|
|
4714
|
+
if (isElectronHost3() || typeof document === "undefined") return;
|
|
3874
4715
|
const metas = document.querySelectorAll('meta[name="theme-color"]');
|
|
3875
4716
|
metas.forEach((meta) => {
|
|
3876
4717
|
meta.content = DB_CHROME_COLORS[resolvedTheme];
|
|
3877
4718
|
});
|
|
3878
4719
|
}, [resolvedTheme]);
|
|
3879
|
-
const handleAccentColorChange =
|
|
4720
|
+
const handleAccentColorChange = useCallback15((color) => {
|
|
3880
4721
|
setAccentColor(color);
|
|
3881
4722
|
saveAccentColor(color);
|
|
3882
4723
|
}, []);
|
|
3883
|
-
const handleWriteCanvasSettingsChange =
|
|
4724
|
+
const handleWriteCanvasSettingsChange = useCallback15((settings) => {
|
|
3884
4725
|
setWriteCanvasSettings(settings);
|
|
3885
4726
|
saveWriteCanvasPreferences(settings);
|
|
3886
4727
|
}, []);
|
|
3887
|
-
const [viewPreferences, setViewPreferences] =
|
|
3888
|
-
const handleViewPreferencesChange =
|
|
4728
|
+
const [viewPreferences, setViewPreferences] = useState15(loadViewPreferences);
|
|
4729
|
+
const handleViewPreferencesChange = useCallback15((prefs) => {
|
|
3889
4730
|
setViewPreferences(prefs);
|
|
3890
4731
|
saveViewPreferences(prefs);
|
|
3891
4732
|
}, []);
|
|
3892
4733
|
const isMobile = useIsMobile();
|
|
3893
|
-
const
|
|
4734
|
+
const defaultPreviewViewportPreset = useResponsivePreviewViewportPreset();
|
|
4735
|
+
const [mobileShowEditor, setMobileShowEditor] = useState15(
|
|
3894
4736
|
() => isMobile && isWelcomeGatewayDismissed()
|
|
3895
4737
|
);
|
|
3896
|
-
|
|
4738
|
+
useEffect15(() => {
|
|
3897
4739
|
if (isMobile) void loadEditorShell();
|
|
3898
4740
|
}, [isMobile]);
|
|
3899
|
-
const [sidebarWidth, setSidebarWidth] =
|
|
3900
|
-
const [compactLayout, setCompactLayout] =
|
|
4741
|
+
const [sidebarWidth, setSidebarWidth] = useState15(loadSidebarWidth);
|
|
4742
|
+
const [compactLayout, setCompactLayout] = useState15(false);
|
|
3901
4743
|
const effectiveCompact = isMobile || compactLayout;
|
|
3902
|
-
const showBrowserStorageWarning = !
|
|
3903
|
-
const appVersion =
|
|
4744
|
+
const showBrowserStorageWarning = !isElectronHost3();
|
|
4745
|
+
const appVersion = isElectronHost3() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web";
|
|
3904
4746
|
const issueReportUrl = buildIssueReportUrl({
|
|
3905
4747
|
reportedAt: /* @__PURE__ */ new Date(),
|
|
3906
4748
|
version: appVersion,
|
|
3907
4749
|
userAgent: typeof navigator === "undefined" ? "" : navigator.userAgent
|
|
3908
4750
|
});
|
|
3909
|
-
const [browserStoragePersistent, setBrowserStoragePersistent] =
|
|
3910
|
-
const [saveToast, setSaveToast] =
|
|
3911
|
-
const saveToastTimerRef =
|
|
3912
|
-
const showToast =
|
|
4751
|
+
const [browserStoragePersistent, setBrowserStoragePersistent] = useState15(false);
|
|
4752
|
+
const [saveToast, setSaveToast] = useState15(null);
|
|
4753
|
+
const saveToastTimerRef = useRef15(null);
|
|
4754
|
+
const showToast = useCallback15((kind, message) => {
|
|
3913
4755
|
setSaveToast({ kind, message });
|
|
3914
4756
|
if (saveToastTimerRef.current) clearTimeout(saveToastTimerRef.current);
|
|
3915
4757
|
saveToastTimerRef.current = setTimeout(() => setSaveToast(null), 3e3);
|
|
3916
4758
|
}, []);
|
|
3917
4759
|
const { prompt: promptForText, promptDialog } = usePromptDialog();
|
|
3918
4760
|
const { confirm: confirmAction, acknowledge, confirmDialog } = useConfirmDialog();
|
|
3919
|
-
const confirmDeleteEntry =
|
|
4761
|
+
const confirmDeleteEntry = useCallback15(
|
|
3920
4762
|
(message) => confirmAction({
|
|
3921
4763
|
title: "Delete",
|
|
3922
4764
|
message,
|
|
@@ -3925,12 +4767,12 @@ function DocBlocksShell({
|
|
|
3925
4767
|
}),
|
|
3926
4768
|
[confirmAction]
|
|
3927
4769
|
);
|
|
3928
|
-
const [installPromptEvent, setInstallPromptEvent] =
|
|
4770
|
+
const [installPromptEvent, setInstallPromptEvent] = useState15(
|
|
3929
4771
|
null
|
|
3930
4772
|
);
|
|
3931
|
-
const sidebarRef =
|
|
3932
|
-
const dragStateRef =
|
|
3933
|
-
const handleResizerPointerDown =
|
|
4773
|
+
const sidebarRef = useRef15(null);
|
|
4774
|
+
const dragStateRef = useRef15(null);
|
|
4775
|
+
const handleResizerPointerDown = useCallback15(
|
|
3934
4776
|
(e) => {
|
|
3935
4777
|
if (e.button !== 0) return;
|
|
3936
4778
|
dragStateRef.current = { startX: e.clientX, startWidth: sidebarWidth };
|
|
@@ -3982,17 +4824,17 @@ function DocBlocksShell({
|
|
|
3982
4824
|
},
|
|
3983
4825
|
[sidebarWidth]
|
|
3984
4826
|
);
|
|
3985
|
-
const [provider, setProvider] =
|
|
3986
|
-
const [workspaceStartupError, setWorkspaceStartupError] =
|
|
3987
|
-
const [activeWorkspaceId, setActiveWorkspaceId] =
|
|
3988
|
-
const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] =
|
|
3989
|
-
|
|
4827
|
+
const [provider, setProvider] = useState15(null);
|
|
4828
|
+
const [workspaceStartupError, setWorkspaceStartupError] = useState15(null);
|
|
4829
|
+
const [activeWorkspaceId, setActiveWorkspaceId] = useState15(null);
|
|
4830
|
+
const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] = useState15(null);
|
|
4831
|
+
useEffect15(() => {
|
|
3990
4832
|
if (!provider || getTransientWorkspace(provider.id)) return;
|
|
3991
4833
|
const providerV2 = getFileSystemProviderV25(provider);
|
|
3992
4834
|
return providerV2 ? retainFileSystemProvider(providerV2) : void 0;
|
|
3993
4835
|
}, [provider]);
|
|
3994
|
-
const [descriptorRefreshKey, setDescriptorRefreshKey] =
|
|
3995
|
-
|
|
4836
|
+
const [descriptorRefreshKey, setDescriptorRefreshKey] = useState15(0);
|
|
4837
|
+
useEffect15(() => {
|
|
3996
4838
|
let cancelled = false;
|
|
3997
4839
|
if (!activeWorkspaceId) {
|
|
3998
4840
|
setActiveWorkspaceDescriptor(null);
|
|
@@ -4005,16 +4847,100 @@ function DocBlocksShell({
|
|
|
4005
4847
|
cancelled = true;
|
|
4006
4848
|
};
|
|
4007
4849
|
}, [activeWorkspaceId, descriptorRefreshKey]);
|
|
4008
|
-
const [
|
|
4850
|
+
const [pinnedDocuments, setPinnedDocuments] = useState15(loadPinnedDocuments);
|
|
4851
|
+
const [pinnedDocumentAvailability, setPinnedDocumentAvailability] = useState15(() => /* @__PURE__ */ new Map());
|
|
4852
|
+
useEffect15(() => {
|
|
4853
|
+
savePinnedDocuments(pinnedDocuments);
|
|
4854
|
+
}, [pinnedDocuments]);
|
|
4855
|
+
useEffect15(() => {
|
|
4856
|
+
if (!isElectronHost3()) return;
|
|
4857
|
+
getDocBlocksHost().menu.setPinnedDocuments(
|
|
4858
|
+
pinnedDocuments.map((document2) => ({
|
|
4859
|
+
workspaceId: document2.workspaceId,
|
|
4860
|
+
workspaceName: document2.workspaceName,
|
|
4861
|
+
path: document2.path
|
|
4862
|
+
}))
|
|
4863
|
+
);
|
|
4864
|
+
}, [pinnedDocuments]);
|
|
4865
|
+
const setPinnedAvailability = useCallback15(
|
|
4866
|
+
(document2, availability) => {
|
|
4867
|
+
const key = pinnedDocumentKey(document2);
|
|
4868
|
+
setPinnedDocumentAvailability((current) => {
|
|
4869
|
+
if (current.get(key) === availability) return current;
|
|
4870
|
+
const next = new Map(current);
|
|
4871
|
+
next.set(key, availability);
|
|
4872
|
+
return next;
|
|
4873
|
+
});
|
|
4874
|
+
},
|
|
4875
|
+
[]
|
|
4876
|
+
);
|
|
4877
|
+
const pinnedDocumentItems = useMemo3(
|
|
4878
|
+
() => pinnedDocuments.map((document2) => ({
|
|
4879
|
+
...document2,
|
|
4880
|
+
availability: pinnedDocumentAvailability.get(pinnedDocumentKey(document2)) ?? "unknown"
|
|
4881
|
+
})),
|
|
4882
|
+
[pinnedDocumentAvailability, pinnedDocuments]
|
|
4883
|
+
);
|
|
4884
|
+
useEffect15(() => {
|
|
4885
|
+
if (!provider || !activeWorkspaceId) return;
|
|
4886
|
+
const activePins = pinnedDocuments.filter(
|
|
4887
|
+
(document2) => document2.workspaceId === activeWorkspaceId
|
|
4888
|
+
);
|
|
4889
|
+
if (activePins.length === 0) return;
|
|
4890
|
+
let cancelled = false;
|
|
4891
|
+
let scanning = false;
|
|
4892
|
+
let scanAgain = false;
|
|
4893
|
+
const scan = async () => {
|
|
4894
|
+
if (scanning) {
|
|
4895
|
+
scanAgain = true;
|
|
4896
|
+
return;
|
|
4897
|
+
}
|
|
4898
|
+
scanning = true;
|
|
4899
|
+
try {
|
|
4900
|
+
do {
|
|
4901
|
+
scanAgain = false;
|
|
4902
|
+
await Promise.all(
|
|
4903
|
+
activePins.map(async (document2) => {
|
|
4904
|
+
try {
|
|
4905
|
+
const exists = await providerEntryExists(provider, document2.path);
|
|
4906
|
+
if (!cancelled) setPinnedAvailability(document2, exists ? "available" : "missing");
|
|
4907
|
+
} catch {
|
|
4908
|
+
if (!cancelled) setPinnedAvailability(document2, "unknown");
|
|
4909
|
+
}
|
|
4910
|
+
})
|
|
4911
|
+
);
|
|
4912
|
+
} while (scanAgain && !cancelled);
|
|
4913
|
+
} finally {
|
|
4914
|
+
scanning = false;
|
|
4915
|
+
}
|
|
4916
|
+
};
|
|
4917
|
+
void scan();
|
|
4918
|
+
const scanOnFocus = () => void scan();
|
|
4919
|
+
window.addEventListener("focus", scanOnFocus);
|
|
4920
|
+
const providerV2 = getFileSystemProviderV25(provider);
|
|
4921
|
+
const subscription = providerV2?.capabilities.watch ? providerV2.watch(
|
|
4922
|
+
(event) => {
|
|
4923
|
+
if (event.type !== "modified") void scan();
|
|
4924
|
+
},
|
|
4925
|
+
{ onError: () => void 0 }
|
|
4926
|
+
) : null;
|
|
4927
|
+
void subscription?.ready.catch(() => void 0);
|
|
4928
|
+
return () => {
|
|
4929
|
+
cancelled = true;
|
|
4930
|
+
window.removeEventListener("focus", scanOnFocus);
|
|
4931
|
+
void subscription?.dispose();
|
|
4932
|
+
};
|
|
4933
|
+
}, [activeWorkspaceId, pinnedDocuments, provider, setPinnedAvailability]);
|
|
4934
|
+
const [transientMoveDestinations, setTransientMoveDestinations] = useState15(
|
|
4009
4935
|
[]
|
|
4010
4936
|
);
|
|
4011
|
-
|
|
4937
|
+
useEffect15(() => {
|
|
4012
4938
|
if (activeWorkspaceDescriptor?.type !== "transient") {
|
|
4013
4939
|
setTransientMoveDestinations([]);
|
|
4014
4940
|
return;
|
|
4015
4941
|
}
|
|
4016
4942
|
let cancelled = false;
|
|
4017
|
-
const electron =
|
|
4943
|
+
const electron = isElectronHost3();
|
|
4018
4944
|
void listWorkspaces2().then((workspaces) => {
|
|
4019
4945
|
if (cancelled) return;
|
|
4020
4946
|
setTransientMoveDestinations(
|
|
@@ -4031,21 +4957,21 @@ function DocBlocksShell({
|
|
|
4031
4957
|
}, [activeWorkspaceDescriptor]);
|
|
4032
4958
|
const gitWorkspaceId = provider && activeWorkspaceDescriptor?.id === activeWorkspaceId && provider.id === activeWorkspaceId && activeWorkspaceDescriptor.type === "electron-native" ? activeWorkspaceDescriptor.id : null;
|
|
4033
4959
|
const git = useGit(provider, gitWorkspaceId, resolvedTheme);
|
|
4034
|
-
const gitRef =
|
|
4960
|
+
const gitRef = useRef15(git);
|
|
4035
4961
|
gitRef.current = git;
|
|
4036
4962
|
const { scheduleRefresh: gitScheduleRefresh } = git;
|
|
4037
|
-
const [workspaceSettingsOpen, setWorkspaceSettingsOpen] =
|
|
4038
|
-
const [versioningPreference, setVersioningPreference] =
|
|
4039
|
-
const handleVersioningPreferenceChange =
|
|
4963
|
+
const [workspaceSettingsOpen, setWorkspaceSettingsOpen] = useState15(false);
|
|
4964
|
+
const [versioningPreference, setVersioningPreference] = useState15(loadVersioningPreference);
|
|
4965
|
+
const handleVersioningPreferenceChange = useCallback15((pref) => {
|
|
4040
4966
|
setVersioningPreference(pref);
|
|
4041
4967
|
saveVersioningPreference(pref);
|
|
4042
4968
|
}, []);
|
|
4043
4969
|
const effectiveVersioning = allowVersioning && resolveVersioningEnabled(activeWorkspaceDescriptor, versioningPreference);
|
|
4044
|
-
const handleOpenWorkspaceSettings =
|
|
4970
|
+
const handleOpenWorkspaceSettings = useCallback15(() => {
|
|
4045
4971
|
if (!activeWorkspaceDescriptor) return;
|
|
4046
4972
|
setWorkspaceSettingsOpen(true);
|
|
4047
4973
|
}, [activeWorkspaceDescriptor]);
|
|
4048
|
-
const handleWorkspaceVersioningOverrideChange =
|
|
4974
|
+
const handleWorkspaceVersioningOverrideChange = useCallback15(
|
|
4049
4975
|
async (override) => {
|
|
4050
4976
|
if (!activeWorkspaceDescriptor) return;
|
|
4051
4977
|
await saveWorkspace2({ ...activeWorkspaceDescriptor, versioningOverride: override });
|
|
@@ -4054,10 +4980,10 @@ function DocBlocksShell({
|
|
|
4054
4980
|
},
|
|
4055
4981
|
[activeWorkspaceDescriptor]
|
|
4056
4982
|
);
|
|
4057
|
-
const [selectedFile, setSelectedFile] =
|
|
4983
|
+
const [selectedFile, setSelectedFile] = useState15(null);
|
|
4058
4984
|
useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath);
|
|
4059
4985
|
const exportDestinationAdapter = useMemo3(() => {
|
|
4060
|
-
if (!
|
|
4986
|
+
if (!isElectronHost3() || !activeWorkspaceId || !selectedFile) return void 0;
|
|
4061
4987
|
const documentId = JSON.stringify([activeWorkspaceId, selectedFile]);
|
|
4062
4988
|
const host = getDocBlocksHost().exports;
|
|
4063
4989
|
return {
|
|
@@ -4066,8 +4992,12 @@ function DocBlocksShell({
|
|
|
4066
4992
|
saveBlob: async (blob, filename, target) => host.save(documentId, filename, target?.grantId ?? null, await blob.arrayBuffer())
|
|
4067
4993
|
};
|
|
4068
4994
|
}, [activeWorkspaceId, selectedFile]);
|
|
4069
|
-
const [selectedFolder, setSelectedFolder] =
|
|
4070
|
-
const [folderEntries, setFolderEntries] =
|
|
4995
|
+
const [selectedFolder, setSelectedFolder] = useState15(null);
|
|
4996
|
+
const [folderEntries, setFolderEntries] = useState15([]);
|
|
4997
|
+
const visibleFolderEntries = useMemo3(
|
|
4998
|
+
() => filterVisibleFileEntries(folderEntries),
|
|
4999
|
+
[folderEntries]
|
|
5000
|
+
);
|
|
4071
5001
|
const { session: documentSession, snapshot: documentSnapshot } = useDocumentSession(500);
|
|
4072
5002
|
const editorContent = documentSnapshot.content;
|
|
4073
5003
|
const editorSessionScope = useMemo3(
|
|
@@ -4077,28 +5007,28 @@ function DocBlocksShell({
|
|
|
4077
5007
|
} : null,
|
|
4078
5008
|
[documentSnapshot.frozen, documentSnapshot.generation, documentSnapshot.targetKey]
|
|
4079
5009
|
);
|
|
4080
|
-
const [editorPresentationEpoch, setEditorPresentationEpoch] =
|
|
5010
|
+
const [editorPresentationEpoch, setEditorPresentationEpoch] = useState15(0);
|
|
4081
5011
|
const editorKey = `${documentSnapshot.generation}:${editorPresentationEpoch}`;
|
|
4082
5012
|
const editorPlaceholder = useMemo3(() => {
|
|
4083
5013
|
void editorKey;
|
|
4084
5014
|
return pickEmptyDocumentPrompt();
|
|
4085
5015
|
}, [editorKey]);
|
|
4086
|
-
const [explorerKey, setExplorerKey] =
|
|
4087
|
-
const [documentLinkEpoch, setDocumentLinkEpoch] =
|
|
4088
|
-
const [initialView, setInitialView] =
|
|
4089
|
-
const [initialSharedMode, setInitialSharedMode] =
|
|
4090
|
-
const [showWelcomeGateway, setShowWelcomeGateway] =
|
|
4091
|
-
const navigationRequestRef =
|
|
5016
|
+
const [explorerKey, setExplorerKey] = useState15(0);
|
|
5017
|
+
const [documentLinkEpoch, setDocumentLinkEpoch] = useState15(0);
|
|
5018
|
+
const [initialView, setInitialView] = useState15("wysiwyg");
|
|
5019
|
+
const [initialSharedMode, setInitialSharedMode] = useState15(null);
|
|
5020
|
+
const [showWelcomeGateway, setShowWelcomeGateway] = useState15(false);
|
|
5021
|
+
const navigationRequestRef = useRef15(0);
|
|
4092
5022
|
const workspaceAuthorityBarrier = useMemo3(() => new WorkspaceAuthorityBarrier(), []);
|
|
4093
|
-
const preparedCloseRequestRef =
|
|
4094
|
-
const pendingDbkConflictsRef =
|
|
4095
|
-
const createDocumentTarget =
|
|
5023
|
+
const preparedCloseRequestRef = useRef15(null);
|
|
5024
|
+
const pendingDbkConflictsRef = useRef15(/* @__PURE__ */ new Map());
|
|
5025
|
+
const createDocumentTarget = useCallback15(
|
|
4096
5026
|
(fsProvider, workspaceId, filePath) => {
|
|
4097
5027
|
const transient = getTransientWorkspace(workspaceId);
|
|
4098
5028
|
const baseTarget = createFileSystemDocumentTarget(fsProvider, filePath);
|
|
4099
5029
|
const originKind = transient?.descriptor.origin?.kind;
|
|
4100
5030
|
const needsElectronHost = originKind === "loose-file" || originKind === "dbk";
|
|
4101
|
-
if (!transient?.descriptor.origin || needsElectronHost && !
|
|
5031
|
+
if (!transient?.descriptor.origin || needsElectronHost && !isElectronHost3()) {
|
|
4102
5032
|
return {
|
|
4103
5033
|
key: baseTarget.key,
|
|
4104
5034
|
async commit(request) {
|
|
@@ -4258,18 +5188,18 @@ function DocBlocksShell({
|
|
|
4258
5188
|
},
|
|
4259
5189
|
[gitScheduleRefresh]
|
|
4260
5190
|
);
|
|
4261
|
-
const mediaContainerRef =
|
|
4262
|
-
const [mediaProvider, setMediaProvider] =
|
|
4263
|
-
const [mediaEpoch, setMediaEpoch] =
|
|
4264
|
-
const versionsContainerRef =
|
|
4265
|
-
const [versionsContainer, setVersionsContainer] =
|
|
4266
|
-
const pushHash =
|
|
5191
|
+
const mediaContainerRef = useRef15(null);
|
|
5192
|
+
const [mediaProvider, setMediaProvider] = useState15(null);
|
|
5193
|
+
const [mediaEpoch, setMediaEpoch] = useState15(0);
|
|
5194
|
+
const versionsContainerRef = useRef15(null);
|
|
5195
|
+
const [versionsContainer, setVersionsContainer] = useState15(null);
|
|
5196
|
+
const pushHash = useCallback15((wsId, filePath) => {
|
|
4267
5197
|
const hash = buildHash(wsId, filePath);
|
|
4268
5198
|
if (window.location.hash !== hash) {
|
|
4269
5199
|
window.history.pushState(null, "", hash);
|
|
4270
5200
|
}
|
|
4271
5201
|
}, []);
|
|
4272
|
-
const openFromIds =
|
|
5202
|
+
const openFromIds = useCallback15(
|
|
4273
5203
|
async (wsId, filePath, push, view, navigationRequestId, sharedMode = null) => {
|
|
4274
5204
|
const requestId = navigationRequestId ?? ++navigationRequestRef.current;
|
|
4275
5205
|
if (requestId !== navigationRequestRef.current) return null;
|
|
@@ -4346,7 +5276,7 @@ function DocBlocksShell({
|
|
|
4346
5276
|
},
|
|
4347
5277
|
[createDocumentTarget, documentSession, pushHash]
|
|
4348
5278
|
);
|
|
4349
|
-
const adoptTransientWorkspace =
|
|
5279
|
+
const adoptTransientWorkspace = useCallback15(
|
|
4350
5280
|
async (options) => {
|
|
4351
5281
|
const descriptor = {
|
|
4352
5282
|
id: options.id,
|
|
@@ -4375,7 +5305,7 @@ function DocBlocksShell({
|
|
|
4375
5305
|
},
|
|
4376
5306
|
[openFromIds]
|
|
4377
5307
|
);
|
|
4378
|
-
const openSharedDocument =
|
|
5308
|
+
const openSharedDocument = useCallback15(
|
|
4379
5309
|
async (payload, navigationRequestId) => {
|
|
4380
5310
|
const id = `transient-shared-${navigationRequestId}`;
|
|
4381
5311
|
const mem = await createMemoryFileSystemProvider(id, "Shared document");
|
|
@@ -4403,7 +5333,7 @@ function DocBlocksShell({
|
|
|
4403
5333
|
},
|
|
4404
5334
|
[adoptTransientWorkspace]
|
|
4405
5335
|
);
|
|
4406
|
-
const seedWelcomeFile =
|
|
5336
|
+
const seedWelcomeFile = useCallback15(
|
|
4407
5337
|
async (fs, navigationRequestId) => {
|
|
4408
5338
|
const isCurrent = () => navigationRequestId === void 0 || navigationRequestId === navigationRequestRef.current;
|
|
4409
5339
|
if (!isCurrent()) return;
|
|
@@ -4426,39 +5356,8 @@ function DocBlocksShell({
|
|
|
4426
5356
|
return;
|
|
4427
5357
|
}
|
|
4428
5358
|
if (entries.length > 0) return;
|
|
4429
|
-
const welcomePath =
|
|
4430
|
-
const welcomeContent =
|
|
4431
|
-
"# DocBlocks: the local-first Markdown editor",
|
|
4432
|
-
"",
|
|
4433
|
-
"Your markdown can do anything.",
|
|
4434
|
-
"",
|
|
4435
|
-
"DocBlocks is a free, local-first Markdown editor for creating, organizing, and publishing documents without sending your writing to a server. What you write here can become a Word or PDF document, a slide deck, a web page, or a video.",
|
|
4436
|
-
"",
|
|
4437
|
-
"Simple to write. Beautiful wherever it goes.",
|
|
4438
|
-
"",
|
|
4439
|
-
"## Features",
|
|
4440
|
-
"",
|
|
4441
|
-
"- **Rich Markdown editing** \u2014 Write visually or switch to raw Markdown source at any time. Section annotations can change how individual blocks are presented.",
|
|
4442
|
-
"- **Workspaces** \u2014 Organize documents into separate browser workspaces or folders on your device.",
|
|
4443
|
-
"- **Useful everywhere** \u2014 Export Word, PowerPoint, PDF, HTML, and Markdown in the app, with EPUB and additional formats available through the CLI.",
|
|
4444
|
-
"- **Playback and video** \u2014 Preview a document as a rich presentation and export it as an MP4 video.",
|
|
4445
|
-
"- **Private by default** \u2014 No ads, accounts, or tracking. Your browser workspace stays on this device.",
|
|
4446
|
-
"",
|
|
4447
|
-
"## Getting Started",
|
|
4448
|
-
"",
|
|
4449
|
-
"1. Create a document with the **New file** button in the file pane.",
|
|
4450
|
-
"2. Start writing in Markdown \u2014 the editor supports headings, lists, links, images, and more.",
|
|
4451
|
-
"3. Your work is saved automatically.",
|
|
4452
|
-
"",
|
|
4453
|
-
"Built with [squisq](https://github.com/bendyline/squisq) by [Bendyline](https://bendyline.com).",
|
|
4454
|
-
"",
|
|
4455
|
-
"## Explore DocBlocks",
|
|
4456
|
-
"",
|
|
4457
|
-
"- [Desktop app](https://docblocks.com/desktop/) for macOS, Windows, and Linux",
|
|
4458
|
-
"- [VS Code extension](https://docblocks.com/vscode/) for rich editing inside your workspace",
|
|
4459
|
-
"- [CLI and MCP server](https://docblocks.com/cli/) for conversion and agent workflows",
|
|
4460
|
-
"- [Supported formats](https://docblocks.com/formats/) and [documentation](https://docblocks.com/docs/)"
|
|
4461
|
-
].join("\n");
|
|
5359
|
+
const welcomePath = WELCOME_DOCUMENT_PATH;
|
|
5360
|
+
const welcomeContent = WELCOME_DOCUMENT_CONTENT;
|
|
4462
5361
|
let seededContent = welcomeContent;
|
|
4463
5362
|
try {
|
|
4464
5363
|
await writeProviderText(fs, welcomePath, welcomeContent, "create");
|
|
@@ -4484,19 +5383,19 @@ function DocBlocksShell({
|
|
|
4484
5383
|
},
|
|
4485
5384
|
[createDocumentTarget, documentSession, pushHash]
|
|
4486
5385
|
);
|
|
4487
|
-
const startupOpenFromIdsRef =
|
|
5386
|
+
const startupOpenFromIdsRef = useRef15(openFromIds);
|
|
4488
5387
|
startupOpenFromIdsRef.current = openFromIds;
|
|
4489
|
-
const startupOpenSharedDocumentRef =
|
|
5388
|
+
const startupOpenSharedDocumentRef = useRef15(openSharedDocument);
|
|
4490
5389
|
startupOpenSharedDocumentRef.current = openSharedDocument;
|
|
4491
|
-
const startupSeedWelcomeFileRef =
|
|
5390
|
+
const startupSeedWelcomeFileRef = useRef15(seedWelcomeFile);
|
|
4492
5391
|
startupSeedWelcomeFileRef.current = seedWelcomeFile;
|
|
4493
|
-
const closeWelcomeGateway =
|
|
5392
|
+
const closeWelcomeGateway = useCallback15(() => {
|
|
4494
5393
|
setShowWelcomeGateway((showing) => {
|
|
4495
5394
|
if (showing) markWelcomeGatewayDismissed();
|
|
4496
5395
|
return false;
|
|
4497
5396
|
});
|
|
4498
5397
|
}, []);
|
|
4499
|
-
const handleStartWriting =
|
|
5398
|
+
const handleStartWriting = useCallback15(() => {
|
|
4500
5399
|
closeWelcomeGateway();
|
|
4501
5400
|
setInitialView("wysiwyg");
|
|
4502
5401
|
setInitialSharedMode(null);
|
|
@@ -4505,12 +5404,12 @@ function DocBlocksShell({
|
|
|
4505
5404
|
saveLastState({ workspaceId: activeWorkspaceId, filePath: selectedFile, view: "wysiwyg" });
|
|
4506
5405
|
}
|
|
4507
5406
|
}, [closeWelcomeGateway, activeWorkspaceId, selectedFile]);
|
|
4508
|
-
|
|
5407
|
+
useEffect15(() => {
|
|
4509
5408
|
const requestId = ++navigationRequestRef.current;
|
|
4510
5409
|
let cancelled = false;
|
|
4511
5410
|
const isCurrent = () => !cancelled && requestId === navigationRequestRef.current;
|
|
4512
5411
|
void (async () => {
|
|
4513
|
-
const electron =
|
|
5412
|
+
const electron = isElectronHost3();
|
|
4514
5413
|
let workspaceIdRemap = {};
|
|
4515
5414
|
if (electron) {
|
|
4516
5415
|
try {
|
|
@@ -4702,7 +5601,7 @@ function DocBlocksShell({
|
|
|
4702
5601
|
if (navigationRequestRef.current === requestId) navigationRequestRef.current += 1;
|
|
4703
5602
|
};
|
|
4704
5603
|
}, [pushHash, workspaceAuthorityBarrier]);
|
|
4705
|
-
|
|
5604
|
+
useEffect15(() => {
|
|
4706
5605
|
const onPopState = () => {
|
|
4707
5606
|
const requestedHash = window.location.hash;
|
|
4708
5607
|
const restoreCurrentHash = () => {
|
|
@@ -4744,7 +5643,7 @@ function DocBlocksShell({
|
|
|
4744
5643
|
window.addEventListener("popstate", onPopState);
|
|
4745
5644
|
return () => window.removeEventListener("popstate", onPopState);
|
|
4746
5645
|
}, [activeWorkspaceId, openFromIds, openSharedDocument, selectedFile, showToast]);
|
|
4747
|
-
|
|
5646
|
+
useEffect15(() => {
|
|
4748
5647
|
if (!showBrowserStorageWarning || typeof navigator === "undefined") return;
|
|
4749
5648
|
const storage = navigator.storage;
|
|
4750
5649
|
if (!storage || typeof storage.persisted !== "function") return;
|
|
@@ -4757,8 +5656,8 @@ function DocBlocksShell({
|
|
|
4757
5656
|
cancelled = true;
|
|
4758
5657
|
};
|
|
4759
5658
|
}, [showBrowserStorageWarning]);
|
|
4760
|
-
|
|
4761
|
-
if (
|
|
5659
|
+
useEffect15(() => {
|
|
5660
|
+
if (isElectronHost3() || typeof window === "undefined") return;
|
|
4762
5661
|
const onBeforeInstallPrompt = (e) => {
|
|
4763
5662
|
e.preventDefault();
|
|
4764
5663
|
setInstallPromptEvent(e);
|
|
@@ -4780,7 +5679,7 @@ function DocBlocksShell({
|
|
|
4780
5679
|
window.removeEventListener("appinstalled", onAppInstalled);
|
|
4781
5680
|
};
|
|
4782
5681
|
}, []);
|
|
4783
|
-
const handleInstallApp =
|
|
5682
|
+
const handleInstallApp = useCallback15(async () => {
|
|
4784
5683
|
const promptEvent = installPromptEvent;
|
|
4785
5684
|
setInstallPromptEvent(null);
|
|
4786
5685
|
if (!promptEvent) return;
|
|
@@ -4790,7 +5689,7 @@ function DocBlocksShell({
|
|
|
4790
5689
|
} catch {
|
|
4791
5690
|
}
|
|
4792
5691
|
}, [installPromptEvent]);
|
|
4793
|
-
|
|
5692
|
+
useEffect15(() => {
|
|
4794
5693
|
const handler = (e) => {
|
|
4795
5694
|
const target = e.target.closest?.("[data-view]");
|
|
4796
5695
|
if (target) {
|
|
@@ -4806,7 +5705,7 @@ function DocBlocksShell({
|
|
|
4806
5705
|
window.addEventListener("click", handler, true);
|
|
4807
5706
|
return () => window.removeEventListener("click", handler, true);
|
|
4808
5707
|
}, [activeWorkspaceId, selectedFile, closeWelcomeGateway]);
|
|
4809
|
-
|
|
5708
|
+
useEffect15(() => {
|
|
4810
5709
|
const onKey = (e) => {
|
|
4811
5710
|
const sKey = e.key === "s" || e.key === "S";
|
|
4812
5711
|
const accel = e.ctrlKey || e.metaKey;
|
|
@@ -4828,30 +5727,30 @@ function DocBlocksShell({
|
|
|
4828
5727
|
window.addEventListener("keydown", onKey, true);
|
|
4829
5728
|
return () => window.removeEventListener("keydown", onKey, true);
|
|
4830
5729
|
}, [documentSession, showToast]);
|
|
4831
|
-
|
|
5730
|
+
useEffect15(() => {
|
|
4832
5731
|
return () => {
|
|
4833
5732
|
if (saveToastTimerRef.current) clearTimeout(saveToastTimerRef.current);
|
|
4834
5733
|
};
|
|
4835
5734
|
}, []);
|
|
4836
|
-
const [offlineReadyToast, setOfflineReadyToast] =
|
|
4837
|
-
const offlineReadyAnnouncedRef =
|
|
4838
|
-
|
|
5735
|
+
const [offlineReadyToast, setOfflineReadyToast] = useState15(false);
|
|
5736
|
+
const offlineReadyAnnouncedRef = useRef15(false);
|
|
5737
|
+
useEffect15(() => {
|
|
4839
5738
|
if (!offlineReady || offlineReadyAnnouncedRef.current) return;
|
|
4840
5739
|
offlineReadyAnnouncedRef.current = true;
|
|
4841
5740
|
setOfflineReadyToast(true);
|
|
4842
5741
|
const timer = setTimeout(() => setOfflineReadyToast(false), 3e3);
|
|
4843
5742
|
return () => clearTimeout(timer);
|
|
4844
5743
|
}, [offlineReady]);
|
|
4845
|
-
const [storageFull, setStorageFull] =
|
|
4846
|
-
|
|
5744
|
+
const [storageFull, setStorageFull] = useState15(false);
|
|
5745
|
+
useEffect15(() => {
|
|
4847
5746
|
if (documentSnapshot.error && isQuotaExceededError(documentSnapshot.error)) {
|
|
4848
5747
|
setStorageFull(true);
|
|
4849
5748
|
} else if (documentSnapshot.status === "saved") {
|
|
4850
5749
|
setStorageFull(false);
|
|
4851
5750
|
}
|
|
4852
5751
|
}, [documentSnapshot.error, documentSnapshot.status]);
|
|
4853
|
-
const autoPersistRequestedRef =
|
|
4854
|
-
|
|
5752
|
+
const autoPersistRequestedRef = useRef15(false);
|
|
5753
|
+
useEffect15(() => {
|
|
4855
5754
|
if (!showBrowserStorageWarning || browserStoragePersistent) return;
|
|
4856
5755
|
if (autoPersistRequestedRef.current) return;
|
|
4857
5756
|
if (documentSnapshot.status !== "saved") return;
|
|
@@ -4863,7 +5762,7 @@ function DocBlocksShell({
|
|
|
4863
5762
|
}).catch(() => {
|
|
4864
5763
|
});
|
|
4865
5764
|
}, [showBrowserStorageWarning, browserStoragePersistent, documentSnapshot.status]);
|
|
4866
|
-
|
|
5765
|
+
useEffect15(() => {
|
|
4867
5766
|
if (!provider || !selectedFile) {
|
|
4868
5767
|
mediaContainerRef.current = null;
|
|
4869
5768
|
versionsContainerRef.current = null;
|
|
@@ -4887,7 +5786,7 @@ function DocBlocksShell({
|
|
|
4887
5786
|
};
|
|
4888
5787
|
}, [provider, selectedFile, mediaEpoch]);
|
|
4889
5788
|
const documentLinkProvider = useDocumentLinkProvider(provider, selectedFile, documentLinkEpoch);
|
|
4890
|
-
|
|
5789
|
+
useEffect15(() => {
|
|
4891
5790
|
const ref = versioningRef;
|
|
4892
5791
|
if (!ref) return;
|
|
4893
5792
|
const assign = (mgr2) => {
|
|
@@ -4905,7 +5804,7 @@ function DocBlocksShell({
|
|
|
4905
5804
|
assign(mgr);
|
|
4906
5805
|
return () => assign(null);
|
|
4907
5806
|
}, [versioningRef, effectiveVersioning, versionBasename, selectedFile, versionsContainer]);
|
|
4908
|
-
|
|
5807
|
+
useEffect15(() => {
|
|
4909
5808
|
if (!provider) return;
|
|
4910
5809
|
const providerV2 = getFileSystemProviderV25(provider);
|
|
4911
5810
|
if (!providerV2?.capabilities.watch) return;
|
|
@@ -4965,7 +5864,7 @@ function DocBlocksShell({
|
|
|
4965
5864
|
void subscription.dispose();
|
|
4966
5865
|
};
|
|
4967
5866
|
}, [provider, selectedFile, documentSession, documentSnapshot.targetKey]);
|
|
4968
|
-
const transitionAwayFromDocument =
|
|
5867
|
+
const transitionAwayFromDocument = useCallback15(
|
|
4969
5868
|
async (requestId) => {
|
|
4970
5869
|
if (requestId !== navigationRequestRef.current) return false;
|
|
4971
5870
|
try {
|
|
@@ -4984,7 +5883,7 @@ function DocBlocksShell({
|
|
|
4984
5883
|
},
|
|
4985
5884
|
[documentSession, showToast]
|
|
4986
5885
|
);
|
|
4987
|
-
const handleUseExternalDocument =
|
|
5886
|
+
const handleUseExternalDocument = useCallback15(async () => {
|
|
4988
5887
|
const conflictKey = documentSession.getSnapshot().conflict?.targetKey;
|
|
4989
5888
|
const pendingDbk = conflictKey ? pendingDbkConflictsRef.current.get(conflictKey) : void 0;
|
|
4990
5889
|
try {
|
|
@@ -5007,7 +5906,7 @@ function DocBlocksShell({
|
|
|
5007
5906
|
});
|
|
5008
5907
|
}
|
|
5009
5908
|
}, [activeWorkspaceId, documentSession, pushHash]);
|
|
5010
|
-
const handleKeepLocalDocument =
|
|
5909
|
+
const handleKeepLocalDocument = useCallback15(async () => {
|
|
5011
5910
|
const conflictKey = documentSession.getSnapshot().conflict?.targetKey;
|
|
5012
5911
|
try {
|
|
5013
5912
|
await documentSession.resolveConflict("use-local");
|
|
@@ -5020,7 +5919,7 @@ function DocBlocksShell({
|
|
|
5020
5919
|
});
|
|
5021
5920
|
}
|
|
5022
5921
|
}, [documentSession]);
|
|
5023
|
-
|
|
5922
|
+
useEffect15(() => {
|
|
5024
5923
|
const needsWarning = ["dirty", "saving", "error", "conflict"].includes(documentSnapshot.status);
|
|
5025
5924
|
if (!documentSnapshot.targetKey) return;
|
|
5026
5925
|
const flushBestEffort = () => {
|
|
@@ -5046,8 +5945,8 @@ function DocBlocksShell({
|
|
|
5046
5945
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
5047
5946
|
};
|
|
5048
5947
|
}, [documentSession, documentSnapshot.status, documentSnapshot.targetKey]);
|
|
5049
|
-
|
|
5050
|
-
if (!
|
|
5948
|
+
useEffect15(() => {
|
|
5949
|
+
if (!isElectronHost3()) return;
|
|
5051
5950
|
const lifecycle = getDocBlocksHost().lifecycle;
|
|
5052
5951
|
const stopPrepare = lifecycle.onPrepareClose(async (request) => {
|
|
5053
5952
|
preparedCloseRequestRef.current = request.requestId;
|
|
@@ -5073,7 +5972,7 @@ function DocBlocksShell({
|
|
|
5073
5972
|
stopCancel();
|
|
5074
5973
|
};
|
|
5075
5974
|
}, [documentSession]);
|
|
5076
|
-
const handleWorkspaceSelect =
|
|
5975
|
+
const handleWorkspaceSelect = useCallback15(
|
|
5077
5976
|
async (ws) => {
|
|
5078
5977
|
const requestId = ++navigationRequestRef.current;
|
|
5079
5978
|
await touchWorkspace2(ws.id);
|
|
@@ -5105,7 +6004,184 @@ function DocBlocksShell({
|
|
|
5105
6004
|
},
|
|
5106
6005
|
[pushHash, transitionAwayFromDocument]
|
|
5107
6006
|
);
|
|
5108
|
-
const
|
|
6007
|
+
const unpinDocument = useCallback15((document2) => {
|
|
6008
|
+
const key = pinnedDocumentKey(document2);
|
|
6009
|
+
setPinnedDocuments((current) => removePinnedDocument(current, document2));
|
|
6010
|
+
setPinnedDocumentAvailability((current) => {
|
|
6011
|
+
if (!current.has(key)) return current;
|
|
6012
|
+
const next = new Map(current);
|
|
6013
|
+
next.delete(key);
|
|
6014
|
+
return next;
|
|
6015
|
+
});
|
|
6016
|
+
}, []);
|
|
6017
|
+
const confirmMissingPinnedDocument = useCallback15(
|
|
6018
|
+
async (document2) => {
|
|
6019
|
+
setPinnedAvailability(document2, "missing");
|
|
6020
|
+
const shouldUnpin = await confirmAction({
|
|
6021
|
+
title: "File not found",
|
|
6022
|
+
message: missingPinnedDocumentMessage(document2),
|
|
6023
|
+
confirmLabel: "OK",
|
|
6024
|
+
cancelLabel: "Cancel"
|
|
6025
|
+
});
|
|
6026
|
+
if (shouldUnpin) unpinDocument(document2);
|
|
6027
|
+
},
|
|
6028
|
+
[confirmAction, setPinnedAvailability, unpinDocument]
|
|
6029
|
+
);
|
|
6030
|
+
const handleTogglePin = useCallback15(
|
|
6031
|
+
(path) => {
|
|
6032
|
+
if (!activeWorkspaceId || !provider) return;
|
|
6033
|
+
const canonicalPath = parseWorkspacePath5(path);
|
|
6034
|
+
if (!canonicalPath) return;
|
|
6035
|
+
const document2 = {
|
|
6036
|
+
workspaceId: activeWorkspaceId,
|
|
6037
|
+
workspaceName: activeWorkspaceDescriptor?.name ?? provider.label,
|
|
6038
|
+
path: canonicalPath
|
|
6039
|
+
};
|
|
6040
|
+
const alreadyPinned = pinnedDocuments.some(
|
|
6041
|
+
(candidate) => pinnedDocumentKey(candidate) === pinnedDocumentKey(document2)
|
|
6042
|
+
);
|
|
6043
|
+
setPinnedDocuments((current) => togglePinnedDocument(current, document2));
|
|
6044
|
+
if (alreadyPinned) {
|
|
6045
|
+
setPinnedDocumentAvailability((current) => {
|
|
6046
|
+
const key = pinnedDocumentKey(document2);
|
|
6047
|
+
if (!current.has(key)) return current;
|
|
6048
|
+
const next = new Map(current);
|
|
6049
|
+
next.delete(key);
|
|
6050
|
+
return next;
|
|
6051
|
+
});
|
|
6052
|
+
} else {
|
|
6053
|
+
setPinnedAvailability(document2, "available");
|
|
6054
|
+
}
|
|
6055
|
+
},
|
|
6056
|
+
[
|
|
6057
|
+
activeWorkspaceDescriptor?.name,
|
|
6058
|
+
activeWorkspaceId,
|
|
6059
|
+
pinnedDocuments,
|
|
6060
|
+
provider,
|
|
6061
|
+
setPinnedAvailability
|
|
6062
|
+
]
|
|
6063
|
+
);
|
|
6064
|
+
const activeWorkspacePinnedPaths = useMemo3(
|
|
6065
|
+
() => pinnedDocuments.filter((document2) => document2.workspaceId === activeWorkspaceId).map((document2) => document2.path),
|
|
6066
|
+
[activeWorkspaceId, pinnedDocuments]
|
|
6067
|
+
);
|
|
6068
|
+
const handlePinnedDocumentSelect = useCallback15(
|
|
6069
|
+
async (document2) => {
|
|
6070
|
+
const requestId = ++navigationRequestRef.current;
|
|
6071
|
+
const workspace = await getWorkspace(document2.workspaceId);
|
|
6072
|
+
if (requestId !== navigationRequestRef.current) return;
|
|
6073
|
+
if (!workspace) {
|
|
6074
|
+
await confirmMissingPinnedDocument(document2);
|
|
6075
|
+
return;
|
|
6076
|
+
}
|
|
6077
|
+
let nextProvider = activeWorkspaceId === workspace.id ? provider : null;
|
|
6078
|
+
let ownsNextProvider = false;
|
|
6079
|
+
let adoptedNextProvider = nextProvider !== null;
|
|
6080
|
+
try {
|
|
6081
|
+
if (!nextProvider) {
|
|
6082
|
+
const transient = getTransientWorkspace(workspace.id);
|
|
6083
|
+
if (transient) {
|
|
6084
|
+
nextProvider = transient.provider;
|
|
6085
|
+
} else if (workspace.type === "electron-native") {
|
|
6086
|
+
nextProvider = isElectronHost3() ? await createElectronProviderFromWorkspace(workspace) : null;
|
|
6087
|
+
ownsNextProvider = nextProvider !== null;
|
|
6088
|
+
} else if (workspace.type === "native") {
|
|
6089
|
+
nextProvider = await (await loadNativeFileSystem()).restoreNativeFolder(workspace.id);
|
|
6090
|
+
ownsNextProvider = nextProvider !== null;
|
|
6091
|
+
} else {
|
|
6092
|
+
nextProvider = await createIndexedDbFileSystemProvider(workspace.id, workspace.name);
|
|
6093
|
+
ownsNextProvider = true;
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
if (requestId !== navigationRequestRef.current) return;
|
|
6097
|
+
if (!nextProvider) {
|
|
6098
|
+
setPinnedAvailability(document2, "unknown");
|
|
6099
|
+
showToast(
|
|
6100
|
+
"error",
|
|
6101
|
+
`DocBlocks could not open \u201C${workspace.name}\u201D. Open that workspace and grant access, then try again.`
|
|
6102
|
+
);
|
|
6103
|
+
return;
|
|
6104
|
+
}
|
|
6105
|
+
const openedProvider = nextProvider;
|
|
6106
|
+
let exists;
|
|
6107
|
+
try {
|
|
6108
|
+
exists = await providerEntryExists(openedProvider, document2.path);
|
|
6109
|
+
} catch (error) {
|
|
6110
|
+
setPinnedAvailability(document2, "unknown");
|
|
6111
|
+
showToast(
|
|
6112
|
+
"error",
|
|
6113
|
+
error instanceof Error ? error.message : "Could not check the pinned document."
|
|
6114
|
+
);
|
|
6115
|
+
return;
|
|
6116
|
+
}
|
|
6117
|
+
if (requestId !== navigationRequestRef.current) return;
|
|
6118
|
+
if (!exists) {
|
|
6119
|
+
await confirmMissingPinnedDocument(document2);
|
|
6120
|
+
return;
|
|
6121
|
+
}
|
|
6122
|
+
let disappearedDuringRead = false;
|
|
6123
|
+
const transitioned = await documentSession.transitionWithLoad(async () => {
|
|
6124
|
+
if (requestId !== navigationRequestRef.current) return null;
|
|
6125
|
+
const content = await readProviderText(openedProvider, document2.path);
|
|
6126
|
+
if (content === null) {
|
|
6127
|
+
disappearedDuringRead = true;
|
|
6128
|
+
return null;
|
|
6129
|
+
}
|
|
6130
|
+
if (requestId !== navigationRequestRef.current) return null;
|
|
6131
|
+
return {
|
|
6132
|
+
target: createDocumentTarget(openedProvider, workspace.id, document2.path),
|
|
6133
|
+
content
|
|
6134
|
+
};
|
|
6135
|
+
});
|
|
6136
|
+
if (!transitioned) {
|
|
6137
|
+
if (disappearedDuringRead) await confirmMissingPinnedDocument(document2);
|
|
6138
|
+
return;
|
|
6139
|
+
}
|
|
6140
|
+
if (requestId !== navigationRequestRef.current) return;
|
|
6141
|
+
adoptedNextProvider = true;
|
|
6142
|
+
setProvider(openedProvider);
|
|
6143
|
+
setActiveWorkspaceId(workspace.id);
|
|
6144
|
+
setActiveWorkspaceDescriptor(workspace);
|
|
6145
|
+
setSelectedFile(document2.path);
|
|
6146
|
+
setSelectedFolder(null);
|
|
6147
|
+
setFolderEntries([]);
|
|
6148
|
+
setInitialView("wysiwyg");
|
|
6149
|
+
setInitialSharedMode(null);
|
|
6150
|
+
setExplorerKey((key) => key + 1);
|
|
6151
|
+
setPinnedAvailability(document2, "available");
|
|
6152
|
+
closeWelcomeGateway();
|
|
6153
|
+
pushHash(workspace.id, document2.path);
|
|
6154
|
+
saveLastState({ workspaceId: workspace.id, filePath: document2.path, view: "wysiwyg" });
|
|
6155
|
+
if (effectiveCompact) setMobileShowEditor(true);
|
|
6156
|
+
void touchWorkspace2(workspace.id).catch(() => void 0);
|
|
6157
|
+
} catch (error) {
|
|
6158
|
+
showToast(
|
|
6159
|
+
"error",
|
|
6160
|
+
error instanceof Error ? error.message : "Could not open the pinned document."
|
|
6161
|
+
);
|
|
6162
|
+
} finally {
|
|
6163
|
+
if (nextProvider && ownsNextProvider && !adoptedNextProvider) {
|
|
6164
|
+
try {
|
|
6165
|
+
await getFileSystemProviderV25(nextProvider)?.dispose();
|
|
6166
|
+
} catch {
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
}
|
|
6170
|
+
},
|
|
6171
|
+
[
|
|
6172
|
+
activeWorkspaceId,
|
|
6173
|
+
closeWelcomeGateway,
|
|
6174
|
+
confirmMissingPinnedDocument,
|
|
6175
|
+
createDocumentTarget,
|
|
6176
|
+
documentSession,
|
|
6177
|
+
effectiveCompact,
|
|
6178
|
+
provider,
|
|
6179
|
+
pushHash,
|
|
6180
|
+
setPinnedAvailability,
|
|
6181
|
+
showToast
|
|
6182
|
+
]
|
|
6183
|
+
);
|
|
6184
|
+
const handleMoveTransientWorkspace = useCallback15(
|
|
5109
6185
|
async (destinationWorkspaceId) => {
|
|
5110
6186
|
if (!activeWorkspaceId || !provider || !selectedFile) {
|
|
5111
6187
|
throw new Error("Select the temporary document before moving it.");
|
|
@@ -5161,6 +6237,12 @@ function DocBlocksShell({
|
|
|
5161
6237
|
setActiveWorkspaceId(destination.id);
|
|
5162
6238
|
setActiveWorkspaceDescriptor(destination);
|
|
5163
6239
|
setSelectedFile(selectedFile);
|
|
6240
|
+
setPinnedDocuments(
|
|
6241
|
+
(current) => movePinnedDocumentsToWorkspace(current, activeWorkspaceId, {
|
|
6242
|
+
workspaceId: destination.id,
|
|
6243
|
+
workspaceName: destination.name
|
|
6244
|
+
})
|
|
6245
|
+
);
|
|
5164
6246
|
setSelectedFolder(null);
|
|
5165
6247
|
setFolderEntries([]);
|
|
5166
6248
|
setInitialView("wysiwyg");
|
|
@@ -5177,7 +6259,7 @@ function DocBlocksShell({
|
|
|
5177
6259
|
void touchWorkspace2(destination.id).catch(() => void 0);
|
|
5178
6260
|
if (sourceTargetKey) pendingDbkConflictsRef.current.delete(sourceTargetKey);
|
|
5179
6261
|
const origin = transient.descriptor.origin;
|
|
5180
|
-
if (
|
|
6262
|
+
if (isElectronHost3() && origin && (origin.kind === "loose-file" || origin.kind === "dbk")) {
|
|
5181
6263
|
try {
|
|
5182
6264
|
await getDocBlocksHost().external.revoke(origin.resourceId);
|
|
5183
6265
|
} catch {
|
|
@@ -5211,10 +6293,10 @@ function DocBlocksShell({
|
|
|
5211
6293
|
transientMoveDestinations
|
|
5212
6294
|
]
|
|
5213
6295
|
);
|
|
5214
|
-
const handleOpenFolder =
|
|
6296
|
+
const handleOpenFolder = useCallback15(async () => {
|
|
5215
6297
|
const requestId = ++navigationRequestRef.current;
|
|
5216
6298
|
try {
|
|
5217
|
-
if (
|
|
6299
|
+
if (isElectronHost3()) {
|
|
5218
6300
|
const info = await getDocBlocksHost().workspaces.pickFolder();
|
|
5219
6301
|
if (!info) return;
|
|
5220
6302
|
if (requestId !== navigationRequestRef.current) return;
|
|
@@ -5257,7 +6339,7 @@ function DocBlocksShell({
|
|
|
5257
6339
|
} catch {
|
|
5258
6340
|
}
|
|
5259
6341
|
}, [pushHash, transitionAwayFromDocument]);
|
|
5260
|
-
const handleWorkspaceCloned =
|
|
6342
|
+
const handleWorkspaceCloned = useCallback15(
|
|
5261
6343
|
(info) => {
|
|
5262
6344
|
const requestId = ++navigationRequestRef.current;
|
|
5263
6345
|
void (async () => {
|
|
@@ -5286,7 +6368,7 @@ function DocBlocksShell({
|
|
|
5286
6368
|
},
|
|
5287
6369
|
[pushHash, transitionAwayFromDocument]
|
|
5288
6370
|
);
|
|
5289
|
-
const handleNewFile =
|
|
6371
|
+
const handleNewFile = useCallback15(async () => {
|
|
5290
6372
|
if (!provider) return;
|
|
5291
6373
|
const name = await promptForText({
|
|
5292
6374
|
title: "New document",
|
|
@@ -5352,7 +6434,7 @@ function DocBlocksShell({
|
|
|
5352
6434
|
promptForText,
|
|
5353
6435
|
showToast
|
|
5354
6436
|
]);
|
|
5355
|
-
const handleNewFolder =
|
|
6437
|
+
const handleNewFolder = useCallback15(async () => {
|
|
5356
6438
|
if (!provider) return;
|
|
5357
6439
|
const name = await promptForText({
|
|
5358
6440
|
title: "New folder",
|
|
@@ -5398,8 +6480,8 @@ function DocBlocksShell({
|
|
|
5398
6480
|
pushHash,
|
|
5399
6481
|
effectiveCompact
|
|
5400
6482
|
]);
|
|
5401
|
-
const actionNewHandledRef =
|
|
5402
|
-
|
|
6483
|
+
const actionNewHandledRef = useRef15(false);
|
|
6484
|
+
useEffect15(() => {
|
|
5403
6485
|
if (!provider || actionNewHandledRef.current) return;
|
|
5404
6486
|
if (typeof window === "undefined") return;
|
|
5405
6487
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -5414,15 +6496,15 @@ function DocBlocksShell({
|
|
|
5414
6496
|
);
|
|
5415
6497
|
void handleNewFile();
|
|
5416
6498
|
}, [provider, handleNewFile]);
|
|
5417
|
-
const handleRevealWorkspace =
|
|
5418
|
-
if (!
|
|
6499
|
+
const handleRevealWorkspace = useCallback15(async () => {
|
|
6500
|
+
if (!isElectronHost3() || !activeWorkspaceId) return;
|
|
5419
6501
|
const ws = await getWorkspace(activeWorkspaceId);
|
|
5420
6502
|
if (ws?.type === "electron-native" && ws.rootPath) {
|
|
5421
6503
|
await getDocBlocksHost().shell.revealInFolder(ws.id);
|
|
5422
6504
|
}
|
|
5423
6505
|
}, [activeWorkspaceId]);
|
|
5424
|
-
|
|
5425
|
-
if (!
|
|
6506
|
+
useEffect15(() => {
|
|
6507
|
+
if (!isElectronHost3()) return;
|
|
5426
6508
|
const host = getDocBlocksHost();
|
|
5427
6509
|
return host.onMenuCommand((cmd) => {
|
|
5428
6510
|
switch (cmd) {
|
|
@@ -5485,7 +6567,7 @@ function DocBlocksShell({
|
|
|
5485
6567
|
}
|
|
5486
6568
|
});
|
|
5487
6569
|
}, [handleNewFile, handleOpenFolder, handleRevealWorkspace, acknowledge]);
|
|
5488
|
-
const handleSelect =
|
|
6570
|
+
const handleSelect = useCallback15(
|
|
5489
6571
|
async (path, kind) => {
|
|
5490
6572
|
if (!provider || !activeWorkspaceId) return;
|
|
5491
6573
|
const requestId = ++navigationRequestRef.current;
|
|
@@ -5542,7 +6624,57 @@ function DocBlocksShell({
|
|
|
5542
6624
|
showToast
|
|
5543
6625
|
]
|
|
5544
6626
|
);
|
|
5545
|
-
const
|
|
6627
|
+
const handleEditorLinkClick = useCallback15(
|
|
6628
|
+
(href) => {
|
|
6629
|
+
const target = resolveShellEditorLinkTarget(href, selectedFile);
|
|
6630
|
+
if (!target) {
|
|
6631
|
+
showToast(
|
|
6632
|
+
"error",
|
|
6633
|
+
"DocBlocks only opens HTTP(S) links or Markdown files inside this workspace."
|
|
6634
|
+
);
|
|
6635
|
+
return true;
|
|
6636
|
+
}
|
|
6637
|
+
if (target.kind === "fragment") return false;
|
|
6638
|
+
if (target.kind === "external") {
|
|
6639
|
+
if (isElectronHost3()) {
|
|
6640
|
+
void getDocBlocksHost().shell.openExternal(target.url).catch((error) => {
|
|
6641
|
+
showToast(
|
|
6642
|
+
"error",
|
|
6643
|
+
error instanceof Error ? error.message : "Could not open the external link."
|
|
6644
|
+
);
|
|
6645
|
+
});
|
|
6646
|
+
} else if (typeof window !== "undefined") {
|
|
6647
|
+
try {
|
|
6648
|
+
window.open(target.url, "_blank", "noopener,noreferrer");
|
|
6649
|
+
} catch (error) {
|
|
6650
|
+
showToast(
|
|
6651
|
+
"error",
|
|
6652
|
+
error instanceof Error ? error.message : "Could not open the external link."
|
|
6653
|
+
);
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
return true;
|
|
6657
|
+
}
|
|
6658
|
+
void (async () => {
|
|
6659
|
+
if (!provider) return;
|
|
6660
|
+
try {
|
|
6661
|
+
if (!await providerEntryExists(provider, target.path)) {
|
|
6662
|
+
showToast("error", "The linked Markdown file was not found in this workspace.");
|
|
6663
|
+
return;
|
|
6664
|
+
}
|
|
6665
|
+
await handleSelect(target.path, "file");
|
|
6666
|
+
} catch (error) {
|
|
6667
|
+
showToast(
|
|
6668
|
+
"error",
|
|
6669
|
+
error instanceof Error ? error.message : "Could not open the linked Markdown file."
|
|
6670
|
+
);
|
|
6671
|
+
}
|
|
6672
|
+
})();
|
|
6673
|
+
return true;
|
|
6674
|
+
},
|
|
6675
|
+
[handleSelect, provider, selectedFile, showToast]
|
|
6676
|
+
);
|
|
6677
|
+
const handleTreeMutation = useCallback15(
|
|
5546
6678
|
async (change, mutate) => {
|
|
5547
6679
|
if (!provider || !activeWorkspaceId || !selectedFile) {
|
|
5548
6680
|
await mutate();
|
|
@@ -5566,11 +6698,16 @@ function DocBlocksShell({
|
|
|
5566
6698
|
},
|
|
5567
6699
|
[provider, activeWorkspaceId, selectedFile, documentSession, createDocumentTarget]
|
|
5568
6700
|
);
|
|
5569
|
-
const handleTreeChange =
|
|
6701
|
+
const handleTreeChange = useCallback15(
|
|
5570
6702
|
async (change) => {
|
|
5571
6703
|
if (!provider) return;
|
|
5572
6704
|
setDocumentLinkEpoch((epoch) => epoch + 1);
|
|
5573
6705
|
if (change?.type === "move" && change.oldPath && change.newPath) {
|
|
6706
|
+
if (activeWorkspaceId) {
|
|
6707
|
+
setPinnedDocuments(
|
|
6708
|
+
(current) => relocatePinnedDocuments(current, activeWorkspaceId, change.oldPath, change.newPath)
|
|
6709
|
+
);
|
|
6710
|
+
}
|
|
5574
6711
|
const nextFile = selectedFile ? relocateProviderPath(selectedFile, change.oldPath, change.newPath) : null;
|
|
5575
6712
|
const nextFolder = selectedFolder ? relocateProviderPath(selectedFolder, change.oldPath, change.newPath) : null;
|
|
5576
6713
|
if (nextFile !== selectedFile) {
|
|
@@ -5590,6 +6727,13 @@ function DocBlocksShell({
|
|
|
5590
6727
|
if (nextFolder) setFolderEntries(await readProviderDirectory2(provider, nextFolder));
|
|
5591
6728
|
return;
|
|
5592
6729
|
}
|
|
6730
|
+
if (change?.type === "delete" && activeWorkspaceId) {
|
|
6731
|
+
for (const document2 of pinnedDocuments) {
|
|
6732
|
+
if (document2.workspaceId === activeWorkspaceId && pathContains(change.path, document2.path)) {
|
|
6733
|
+
setPinnedAvailability(document2, "missing");
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
}
|
|
5593
6737
|
if (selectedFile) {
|
|
5594
6738
|
const exists = await providerEntryExists(provider, selectedFile);
|
|
5595
6739
|
if (!exists) {
|
|
@@ -5602,9 +6746,210 @@ function DocBlocksShell({
|
|
|
5602
6746
|
setFolderEntries(entries);
|
|
5603
6747
|
}
|
|
5604
6748
|
},
|
|
5605
|
-
[
|
|
6749
|
+
[
|
|
6750
|
+
provider,
|
|
6751
|
+
selectedFile,
|
|
6752
|
+
selectedFolder,
|
|
6753
|
+
activeWorkspaceId,
|
|
6754
|
+
pinnedDocuments,
|
|
6755
|
+
pushHash,
|
|
6756
|
+
setPinnedAvailability
|
|
6757
|
+
]
|
|
6758
|
+
);
|
|
6759
|
+
const acquirePinnedDocumentProvider = useCallback15(
|
|
6760
|
+
async (document2) => {
|
|
6761
|
+
const workspace = await getWorkspace(document2.workspaceId);
|
|
6762
|
+
if (!workspace) return { kind: "missing-workspace" };
|
|
6763
|
+
if (activeWorkspaceId === workspace.id && provider) {
|
|
6764
|
+
return { kind: "ready", workspace, provider, owned: false };
|
|
6765
|
+
}
|
|
6766
|
+
const transient = getTransientWorkspace(workspace.id);
|
|
6767
|
+
if (transient) {
|
|
6768
|
+
return { kind: "ready", workspace, provider: transient.provider, owned: false };
|
|
6769
|
+
}
|
|
6770
|
+
let pinnedProvider = null;
|
|
6771
|
+
if (workspace.type === "electron-native") {
|
|
6772
|
+
pinnedProvider = isElectronHost3() ? await createElectronProviderFromWorkspace(workspace) : null;
|
|
6773
|
+
} else if (workspace.type === "native") {
|
|
6774
|
+
pinnedProvider = await (await loadNativeFileSystem()).restoreNativeFolder(workspace.id);
|
|
6775
|
+
} else {
|
|
6776
|
+
pinnedProvider = await createIndexedDbFileSystemProvider(workspace.id, workspace.name);
|
|
6777
|
+
}
|
|
6778
|
+
if (!pinnedProvider) return { kind: "unavailable", workspace };
|
|
6779
|
+
return { kind: "ready", workspace, provider: pinnedProvider, owned: true };
|
|
6780
|
+
},
|
|
6781
|
+
[activeWorkspaceId, provider]
|
|
6782
|
+
);
|
|
6783
|
+
const markRelocatedPinnedDocument = useCallback15((document2, newPath) => {
|
|
6784
|
+
const relocated = {
|
|
6785
|
+
workspaceId: document2.workspaceId,
|
|
6786
|
+
workspaceName: document2.workspaceName,
|
|
6787
|
+
path: newPath
|
|
6788
|
+
};
|
|
6789
|
+
setPinnedDocuments(
|
|
6790
|
+
(current) => relocatePinnedDocuments(current, document2.workspaceId, document2.path, newPath)
|
|
6791
|
+
);
|
|
6792
|
+
setPinnedDocumentAvailability((current) => {
|
|
6793
|
+
const oldKey = pinnedDocumentKey(document2);
|
|
6794
|
+
const nextKey = pinnedDocumentKey(relocated);
|
|
6795
|
+
const next = new Map(current);
|
|
6796
|
+
next.delete(oldKey);
|
|
6797
|
+
next.set(nextKey, "available");
|
|
6798
|
+
return next;
|
|
6799
|
+
});
|
|
6800
|
+
}, []);
|
|
6801
|
+
const handlePinnedDocumentRename = useCallback15(
|
|
6802
|
+
async (document2) => {
|
|
6803
|
+
if (document2.availability === "missing") {
|
|
6804
|
+
await confirmMissingPinnedDocument(document2);
|
|
6805
|
+
return;
|
|
6806
|
+
}
|
|
6807
|
+
const oldName = basenameOf(document2.path);
|
|
6808
|
+
const response = await promptForText({
|
|
6809
|
+
title: "Rename document",
|
|
6810
|
+
label: "Document name",
|
|
6811
|
+
initialValue: oldName,
|
|
6812
|
+
confirmLabel: "Rename"
|
|
6813
|
+
});
|
|
6814
|
+
if (response === null) return;
|
|
6815
|
+
const newName = response.trim();
|
|
6816
|
+
if (!newName || /[\\/]/.test(newName)) {
|
|
6817
|
+
showToast("error", "Use a document name without slashes.");
|
|
6818
|
+
return;
|
|
6819
|
+
}
|
|
6820
|
+
const parent = dirnameOf(document2.path);
|
|
6821
|
+
let newPath;
|
|
6822
|
+
try {
|
|
6823
|
+
newPath = parseWorkspacePath5(parent ? `${parent}/${newName}` : newName);
|
|
6824
|
+
} catch {
|
|
6825
|
+
showToast("error", "Use a valid document name.");
|
|
6826
|
+
return;
|
|
6827
|
+
}
|
|
6828
|
+
if (newPath === document2.path) return;
|
|
6829
|
+
const access = await acquirePinnedDocumentProvider(document2);
|
|
6830
|
+
if (access.kind === "missing-workspace") {
|
|
6831
|
+
await confirmMissingPinnedDocument(document2);
|
|
6832
|
+
return;
|
|
6833
|
+
}
|
|
6834
|
+
if (access.kind === "unavailable") {
|
|
6835
|
+
setPinnedAvailability(document2, "unknown");
|
|
6836
|
+
showToast(
|
|
6837
|
+
"error",
|
|
6838
|
+
`DocBlocks could not open "${access.workspace.name}". Open that workspace and grant access, then try again.`
|
|
6839
|
+
);
|
|
6840
|
+
return;
|
|
6841
|
+
}
|
|
6842
|
+
try {
|
|
6843
|
+
if (!await pinnedProviderFileExists(access.provider, document2.path)) {
|
|
6844
|
+
await confirmMissingPinnedDocument(document2);
|
|
6845
|
+
return;
|
|
6846
|
+
}
|
|
6847
|
+
const change = {
|
|
6848
|
+
type: "move",
|
|
6849
|
+
oldPath: document2.path,
|
|
6850
|
+
newPath,
|
|
6851
|
+
kind: "file"
|
|
6852
|
+
};
|
|
6853
|
+
const mutate = () => moveFileSystemEntry2(access.provider, document2.path, newPath, "file");
|
|
6854
|
+
if (access.provider === provider && access.workspace.id === activeWorkspaceId) {
|
|
6855
|
+
await handleTreeMutation(change, mutate);
|
|
6856
|
+
await handleTreeChange(change);
|
|
6857
|
+
setExplorerKey((key) => key + 1);
|
|
6858
|
+
} else {
|
|
6859
|
+
await mutate();
|
|
6860
|
+
}
|
|
6861
|
+
markRelocatedPinnedDocument(document2, newPath);
|
|
6862
|
+
} finally {
|
|
6863
|
+
if (access.owned) {
|
|
6864
|
+
try {
|
|
6865
|
+
await getFileSystemProviderV25(access.provider)?.dispose();
|
|
6866
|
+
} catch {
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
},
|
|
6871
|
+
[
|
|
6872
|
+
acquirePinnedDocumentProvider,
|
|
6873
|
+
activeWorkspaceId,
|
|
6874
|
+
confirmMissingPinnedDocument,
|
|
6875
|
+
handleTreeChange,
|
|
6876
|
+
handleTreeMutation,
|
|
6877
|
+
markRelocatedPinnedDocument,
|
|
6878
|
+
promptForText,
|
|
6879
|
+
provider,
|
|
6880
|
+
setPinnedAvailability,
|
|
6881
|
+
showToast
|
|
6882
|
+
]
|
|
6883
|
+
);
|
|
6884
|
+
const handlePinnedDocumentDelete = useCallback15(
|
|
6885
|
+
async (document2) => {
|
|
6886
|
+
if (document2.availability === "missing") {
|
|
6887
|
+
await confirmMissingPinnedDocument(document2);
|
|
6888
|
+
return;
|
|
6889
|
+
}
|
|
6890
|
+
const access = await acquirePinnedDocumentProvider(document2);
|
|
6891
|
+
if (access.kind === "missing-workspace") {
|
|
6892
|
+
await confirmMissingPinnedDocument(document2);
|
|
6893
|
+
return;
|
|
6894
|
+
}
|
|
6895
|
+
if (access.kind === "unavailable") {
|
|
6896
|
+
setPinnedAvailability(document2, "unknown");
|
|
6897
|
+
showToast(
|
|
6898
|
+
"error",
|
|
6899
|
+
`DocBlocks could not open "${access.workspace.name}". Open that workspace and grant access, then try again.`
|
|
6900
|
+
);
|
|
6901
|
+
return;
|
|
6902
|
+
}
|
|
6903
|
+
try {
|
|
6904
|
+
if (!await pinnedProviderFileExists(access.provider, document2.path)) {
|
|
6905
|
+
await confirmMissingPinnedDocument(document2);
|
|
6906
|
+
return;
|
|
6907
|
+
}
|
|
6908
|
+
if (!await confirmDeleteEntry(
|
|
6909
|
+
`Delete the document "${basenameOf(document2.path)}"? This cannot be undone.`
|
|
6910
|
+
)) {
|
|
6911
|
+
return;
|
|
6912
|
+
}
|
|
6913
|
+
const change = {
|
|
6914
|
+
type: "delete",
|
|
6915
|
+
path: document2.path,
|
|
6916
|
+
kind: "file"
|
|
6917
|
+
};
|
|
6918
|
+
const mutate = async () => {
|
|
6919
|
+
if (!await removePinnedProviderFile(access.provider, document2.path)) {
|
|
6920
|
+
throw new Error("The document disappeared before it could be deleted.");
|
|
6921
|
+
}
|
|
6922
|
+
};
|
|
6923
|
+
if (access.provider === provider && access.workspace.id === activeWorkspaceId) {
|
|
6924
|
+
await handleTreeMutation(change, mutate);
|
|
6925
|
+
await handleTreeChange(change);
|
|
6926
|
+
setExplorerKey((key) => key + 1);
|
|
6927
|
+
} else {
|
|
6928
|
+
await mutate();
|
|
6929
|
+
}
|
|
6930
|
+
setPinnedAvailability(document2, "missing");
|
|
6931
|
+
} finally {
|
|
6932
|
+
if (access.owned) {
|
|
6933
|
+
try {
|
|
6934
|
+
await getFileSystemProviderV25(access.provider)?.dispose();
|
|
6935
|
+
} catch {
|
|
6936
|
+
}
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6939
|
+
},
|
|
6940
|
+
[
|
|
6941
|
+
acquirePinnedDocumentProvider,
|
|
6942
|
+
activeWorkspaceId,
|
|
6943
|
+
confirmDeleteEntry,
|
|
6944
|
+
confirmMissingPinnedDocument,
|
|
6945
|
+
handleTreeChange,
|
|
6946
|
+
handleTreeMutation,
|
|
6947
|
+
provider,
|
|
6948
|
+
setPinnedAvailability,
|
|
6949
|
+
showToast
|
|
6950
|
+
]
|
|
5606
6951
|
);
|
|
5607
|
-
const persistImportedMedia =
|
|
6952
|
+
const persistImportedMedia = useCallback15(
|
|
5608
6953
|
async (source, target, importedMarkdownPath) => {
|
|
5609
6954
|
const parentDir = dirnameOf(importedMarkdownPath);
|
|
5610
6955
|
const folder = basenameOf(importedMarkdownPath).replace(/\.[^.]+$/, "") + "_files";
|
|
@@ -5629,7 +6974,7 @@ function DocBlocksShell({
|
|
|
5629
6974
|
},
|
|
5630
6975
|
[]
|
|
5631
6976
|
);
|
|
5632
|
-
const handleImportFiles =
|
|
6977
|
+
const handleImportFiles = useCallback15(
|
|
5633
6978
|
async (files) => {
|
|
5634
6979
|
if (!provider) return;
|
|
5635
6980
|
const result = await importDroppedFiles(files, provider, {
|
|
@@ -5641,7 +6986,7 @@ function DocBlocksShell({
|
|
|
5641
6986
|
},
|
|
5642
6987
|
[provider, persistImportedMedia, showToast]
|
|
5643
6988
|
);
|
|
5644
|
-
const handleEditorChange =
|
|
6989
|
+
const handleEditorChange = useCallback15(
|
|
5645
6990
|
(source) => {
|
|
5646
6991
|
if (!editorSessionScope) return;
|
|
5647
6992
|
try {
|
|
@@ -5651,7 +6996,7 @@ function DocBlocksShell({
|
|
|
5651
6996
|
},
|
|
5652
6997
|
[documentSession, editorSessionScope]
|
|
5653
6998
|
);
|
|
5654
|
-
const handleRenameWorkspace =
|
|
6999
|
+
const handleRenameWorkspace = useCallback15(async () => {
|
|
5655
7000
|
if (!activeWorkspaceId) return;
|
|
5656
7001
|
const ws = await getWorkspace(activeWorkspaceId);
|
|
5657
7002
|
if (!ws) return;
|
|
@@ -5663,9 +7008,12 @@ function DocBlocksShell({
|
|
|
5663
7008
|
});
|
|
5664
7009
|
if (!newName || newName === ws.name) return;
|
|
5665
7010
|
await saveWorkspace2({ ...ws, name: newName });
|
|
7011
|
+
setPinnedDocuments(
|
|
7012
|
+
(current) => renamePinnedDocumentWorkspace(current, activeWorkspaceId, newName)
|
|
7013
|
+
);
|
|
5666
7014
|
setDescriptorRefreshKey((key) => key + 1);
|
|
5667
7015
|
}, [activeWorkspaceId, promptForText]);
|
|
5668
|
-
const openTransient =
|
|
7016
|
+
const openTransient = useCallback15(
|
|
5669
7017
|
async (req, navigationRequestId) => {
|
|
5670
7018
|
const isCurrent = () => navigationRequestId === navigationRequestRef.current;
|
|
5671
7019
|
const host = getDocBlocksHost();
|
|
@@ -5740,8 +7088,8 @@ function DocBlocksShell({
|
|
|
5740
7088
|
},
|
|
5741
7089
|
[adoptTransientWorkspace]
|
|
5742
7090
|
);
|
|
5743
|
-
|
|
5744
|
-
if (!
|
|
7091
|
+
useEffect15(() => {
|
|
7092
|
+
if (!isElectronHost3()) return;
|
|
5745
7093
|
const host = getDocBlocksHost();
|
|
5746
7094
|
return host.onOpenRequest((req) => {
|
|
5747
7095
|
const requestId = ++navigationRequestRef.current;
|
|
@@ -5764,7 +7112,7 @@ function DocBlocksShell({
|
|
|
5764
7112
|
});
|
|
5765
7113
|
});
|
|
5766
7114
|
}, [openFromIds, openTransient, workspaceAuthorityBarrier, showToast]);
|
|
5767
|
-
const openTransientFromHandle =
|
|
7115
|
+
const openTransientFromHandle = useCallback15(
|
|
5768
7116
|
async (handle) => {
|
|
5769
7117
|
const name = handle.name;
|
|
5770
7118
|
const isBundle = /\.(dbk|zip)$/i.test(name);
|
|
@@ -5806,8 +7154,8 @@ function DocBlocksShell({
|
|
|
5806
7154
|
},
|
|
5807
7155
|
[adoptTransientWorkspace]
|
|
5808
7156
|
);
|
|
5809
|
-
|
|
5810
|
-
if (
|
|
7157
|
+
useEffect15(() => {
|
|
7158
|
+
if (isElectronHost3() || typeof window === "undefined") return;
|
|
5811
7159
|
const launchQueue = window.launchQueue;
|
|
5812
7160
|
if (!launchQueue) return;
|
|
5813
7161
|
launchQueue.setConsumer((params) => {
|
|
@@ -5822,7 +7170,7 @@ function DocBlocksShell({
|
|
|
5822
7170
|
});
|
|
5823
7171
|
}, [openTransientFromHandle, showToast]);
|
|
5824
7172
|
const updateStatusBarVisible = viewPreferences.showStatusBar === true && selectedFile !== null && mediaProvider !== null;
|
|
5825
|
-
const handleDownloadWorkspace =
|
|
7173
|
+
const handleDownloadWorkspace = useCallback15(async () => {
|
|
5826
7174
|
if (!provider) return;
|
|
5827
7175
|
try {
|
|
5828
7176
|
await documentSession.flush("backup");
|
|
@@ -5845,14 +7193,14 @@ function DocBlocksShell({
|
|
|
5845
7193
|
showToast("error", "Failed to download workspace. See console for details.");
|
|
5846
7194
|
}
|
|
5847
7195
|
}, [provider, documentSession, showToast]);
|
|
5848
|
-
const handleDownloadAllWorkspaces =
|
|
7196
|
+
const handleDownloadAllWorkspaces = useCallback15(async () => {
|
|
5849
7197
|
try {
|
|
5850
7198
|
await documentSession.flush("backup");
|
|
5851
7199
|
const [{ MemoryContentContainer }, { containerToZip }] = await Promise.all([
|
|
5852
7200
|
import("@bendyline/squisq/storage"),
|
|
5853
7201
|
import("@bendyline/squisq-formats/container")
|
|
5854
7202
|
]);
|
|
5855
|
-
const electron =
|
|
7203
|
+
const electron = isElectronHost3();
|
|
5856
7204
|
const all = await listWorkspaces2();
|
|
5857
7205
|
const candidates = all.filter(
|
|
5858
7206
|
(w) => electron ? w.type === "electron-native" || w.type === "transient" : w.type !== "electron-native"
|
|
@@ -5924,7 +7272,7 @@ function DocBlocksShell({
|
|
|
5924
7272
|
showToast("error", "Failed to download all workspaces. See console for details.");
|
|
5925
7273
|
}
|
|
5926
7274
|
}, [documentSession, acknowledge, showToast]);
|
|
5927
|
-
const handleKeepBrowserData =
|
|
7275
|
+
const handleKeepBrowserData = useCallback15(async () => {
|
|
5928
7276
|
if (typeof navigator === "undefined") return;
|
|
5929
7277
|
const storage = navigator.storage;
|
|
5930
7278
|
if (!storage || typeof storage.persist !== "function") {
|
|
@@ -5961,7 +7309,7 @@ function DocBlocksShell({
|
|
|
5961
7309
|
});
|
|
5962
7310
|
}
|
|
5963
7311
|
}, [acknowledge, showToast]);
|
|
5964
|
-
const getBrowserStorageEstimate =
|
|
7312
|
+
const getBrowserStorageEstimate = useCallback15(async () => {
|
|
5965
7313
|
if (typeof navigator === "undefined") return null;
|
|
5966
7314
|
const storage = navigator.storage;
|
|
5967
7315
|
if (!storage || typeof storage.estimate !== "function") return null;
|
|
@@ -5973,7 +7321,7 @@ function DocBlocksShell({
|
|
|
5973
7321
|
return null;
|
|
5974
7322
|
}
|
|
5975
7323
|
}, []);
|
|
5976
|
-
const handleRemoveWorkspace =
|
|
7324
|
+
const handleRemoveWorkspace = useCallback15(async () => {
|
|
5977
7325
|
if (!activeWorkspaceId) return;
|
|
5978
7326
|
const ws = await getWorkspace(activeWorkspaceId);
|
|
5979
7327
|
const destroysDocuments = ws?.type === "indexeddb";
|
|
@@ -5994,7 +7342,7 @@ function DocBlocksShell({
|
|
|
5994
7342
|
}
|
|
5995
7343
|
} else if (ws?.type === "transient") {
|
|
5996
7344
|
const origin = ws.origin;
|
|
5997
|
-
if (
|
|
7345
|
+
if (isElectronHost3() && origin && (origin.kind === "loose-file" || origin.kind === "dbk")) {
|
|
5998
7346
|
try {
|
|
5999
7347
|
await getDocBlocksHost().external.revoke(origin.resourceId);
|
|
6000
7348
|
} catch {
|
|
@@ -6006,8 +7354,13 @@ function DocBlocksShell({
|
|
|
6006
7354
|
await (await loadIndexedDbFileSystem()).deleteIndexedDBWorkspaceData(activeWorkspaceId);
|
|
6007
7355
|
}
|
|
6008
7356
|
await removeWorkspace(activeWorkspaceId);
|
|
7357
|
+
for (const document2 of pinnedDocuments) {
|
|
7358
|
+
if (document2.workspaceId === activeWorkspaceId) {
|
|
7359
|
+
setPinnedAvailability(document2, "missing");
|
|
7360
|
+
}
|
|
7361
|
+
}
|
|
6009
7362
|
if (requestId !== navigationRequestRef.current) return;
|
|
6010
|
-
const electron =
|
|
7363
|
+
const electron = isElectronHost3();
|
|
6011
7364
|
const remaining = (await listWorkspaces2()).filter(
|
|
6012
7365
|
(w) => electron ? w.type === "electron-native" : w.type !== "electron-native"
|
|
6013
7366
|
);
|
|
@@ -6039,19 +7392,26 @@ function DocBlocksShell({
|
|
|
6039
7392
|
setSelectedFolder(null);
|
|
6040
7393
|
setFolderEntries([]);
|
|
6041
7394
|
}
|
|
6042
|
-
}, [
|
|
6043
|
-
|
|
7395
|
+
}, [
|
|
7396
|
+
activeWorkspaceId,
|
|
7397
|
+
confirmAction,
|
|
7398
|
+
handleWorkspaceSelect,
|
|
7399
|
+
pinnedDocuments,
|
|
7400
|
+
setPinnedAvailability,
|
|
7401
|
+
transitionAwayFromDocument
|
|
7402
|
+
]);
|
|
7403
|
+
return /* @__PURE__ */ jsx13(
|
|
6044
7404
|
"div",
|
|
6045
7405
|
{
|
|
6046
7406
|
className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}`,
|
|
6047
7407
|
"data-theme": resolvedTheme,
|
|
6048
7408
|
"data-accent": accentColor,
|
|
6049
7409
|
"data-document-status": documentSnapshot.status,
|
|
6050
|
-
children: /* @__PURE__ */
|
|
7410
|
+
children: /* @__PURE__ */ jsxs12(GitContext.Provider, { value: git, children: [
|
|
6051
7411
|
promptDialog,
|
|
6052
7412
|
confirmDialog,
|
|
6053
|
-
workspaceStartupError && /* @__PURE__ */
|
|
6054
|
-
saveToast && /* @__PURE__ */
|
|
7413
|
+
workspaceStartupError && /* @__PURE__ */ jsx13("div", { className: "db-save-toast db-save-toast--error", role: "alert", "aria-live": "assertive", children: workspaceStartupError }),
|
|
7414
|
+
saveToast && /* @__PURE__ */ jsx13(
|
|
6055
7415
|
"div",
|
|
6056
7416
|
{
|
|
6057
7417
|
className: "db-save-toast db-save-toast--" + saveToast.kind,
|
|
@@ -6060,22 +7420,22 @@ function DocBlocksShell({
|
|
|
6060
7420
|
children: saveToast.message
|
|
6061
7421
|
}
|
|
6062
7422
|
),
|
|
6063
|
-
offlineReadyToast && !saveToast && /* @__PURE__ */
|
|
6064
|
-
documentSnapshot.conflict && /* @__PURE__ */
|
|
6065
|
-
/* @__PURE__ */
|
|
6066
|
-
/* @__PURE__ */
|
|
6067
|
-
/* @__PURE__ */
|
|
6068
|
-
/* @__PURE__ */
|
|
7423
|
+
offlineReadyToast && !saveToast && /* @__PURE__ */ jsx13("div", { className: "db-save-toast db-save-toast--success", role: "status", "aria-live": "polite", children: "DocBlocks is ready to work offline." }),
|
|
7424
|
+
documentSnapshot.conflict && /* @__PURE__ */ jsxs12("div", { className: "db-document-conflict", role: "alert", children: [
|
|
7425
|
+
/* @__PURE__ */ jsx13("span", { children: "This document changed outside DocBlocks. Your unsaved version is still intact." }),
|
|
7426
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-document-conflict-actions", children: [
|
|
7427
|
+
/* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleKeepLocalDocument(), children: "Keep mine" }),
|
|
7428
|
+
/* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleUseExternalDocument(), children: "Reload external" })
|
|
6069
7429
|
] })
|
|
6070
7430
|
] }),
|
|
6071
|
-
storageFull && !documentSnapshot.conflict && /* @__PURE__ */
|
|
6072
|
-
/* @__PURE__ */
|
|
6073
|
-
/* @__PURE__ */
|
|
6074
|
-
/* @__PURE__ */
|
|
6075
|
-
/* @__PURE__ */
|
|
7431
|
+
storageFull && !documentSnapshot.conflict && /* @__PURE__ */ jsxs12("div", { className: "db-storage-full-banner", role: "alert", children: [
|
|
7432
|
+
/* @__PURE__ */ jsx13("span", { children: "Browser storage is full -- changes can\u2019t be saved. Free up space or back up your work now." }),
|
|
7433
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-storage-full-banner-actions", children: [
|
|
7434
|
+
/* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleDownloadAllWorkspaces(), children: "Download all workspaces" }),
|
|
7435
|
+
/* @__PURE__ */ jsx13("button", { type: "button", onClick: () => setStorageFull(false), children: "Dismiss" })
|
|
6076
7436
|
] })
|
|
6077
7437
|
] }),
|
|
6078
|
-
workspaceSettingsOpen && activeWorkspaceDescriptor && /* @__PURE__ */
|
|
7438
|
+
workspaceSettingsOpen && activeWorkspaceDescriptor && /* @__PURE__ */ jsx13(
|
|
6079
7439
|
WorkspaceSettingsDialog,
|
|
6080
7440
|
{
|
|
6081
7441
|
workspace: activeWorkspaceDescriptor,
|
|
@@ -6084,8 +7444,8 @@ function DocBlocksShell({
|
|
|
6084
7444
|
onClose: () => setWorkspaceSettingsOpen(false)
|
|
6085
7445
|
}
|
|
6086
7446
|
),
|
|
6087
|
-
/* @__PURE__ */
|
|
6088
|
-
(!effectiveCompact || !mobileShowEditor) && /* @__PURE__ */
|
|
7447
|
+
/* @__PURE__ */ jsxs12("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
|
|
7448
|
+
(!effectiveCompact || !mobileShowEditor) && /* @__PURE__ */ jsxs12(
|
|
6089
7449
|
"aside",
|
|
6090
7450
|
{
|
|
6091
7451
|
ref: sidebarRef,
|
|
@@ -6093,8 +7453,8 @@ function DocBlocksShell({
|
|
|
6093
7453
|
"aria-label": "Workspace and files",
|
|
6094
7454
|
style: effectiveCompact ? void 0 : { width: `${sidebarWidth}px` },
|
|
6095
7455
|
children: [
|
|
6096
|
-
/* @__PURE__ */
|
|
6097
|
-
/* @__PURE__ */
|
|
7456
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-shell-sidebar-header", children: [
|
|
7457
|
+
/* @__PURE__ */ jsx13(
|
|
6098
7458
|
AppMenu,
|
|
6099
7459
|
{
|
|
6100
7460
|
logoUrl,
|
|
@@ -6115,7 +7475,7 @@ function DocBlocksShell({
|
|
|
6115
7475
|
appBuildDate
|
|
6116
7476
|
}
|
|
6117
7477
|
),
|
|
6118
|
-
/* @__PURE__ */
|
|
7478
|
+
/* @__PURE__ */ jsx13(
|
|
6119
7479
|
WorkspacePicker,
|
|
6120
7480
|
{
|
|
6121
7481
|
activeWorkspaceId,
|
|
@@ -6125,7 +7485,7 @@ function DocBlocksShell({
|
|
|
6125
7485
|
onCloneRepository: git.available ? () => git.openDialog({ kind: "clone" }) : void 0
|
|
6126
7486
|
}
|
|
6127
7487
|
),
|
|
6128
|
-
/* @__PURE__ */
|
|
7488
|
+
/* @__PURE__ */ jsx13(
|
|
6129
7489
|
WorkspaceSettingsButton,
|
|
6130
7490
|
{
|
|
6131
7491
|
onSettings: handleOpenWorkspaceSettings,
|
|
@@ -6134,37 +7494,38 @@ function DocBlocksShell({
|
|
|
6134
7494
|
onRemove: handleRemoveWorkspace
|
|
6135
7495
|
}
|
|
6136
7496
|
),
|
|
6137
|
-
compactLayout && !isMobile && /* @__PURE__ */
|
|
7497
|
+
compactLayout && !isMobile && /* @__PURE__ */ jsx13(
|
|
6138
7498
|
"button",
|
|
6139
7499
|
{
|
|
6140
7500
|
className: "db-restore-split",
|
|
6141
7501
|
onClick: () => setCompactLayout(false),
|
|
6142
7502
|
"aria-label": "Restore split view",
|
|
6143
7503
|
title: "Restore split view",
|
|
6144
|
-
children: /* @__PURE__ */
|
|
7504
|
+
children: /* @__PURE__ */ jsx13(SplitViewIcon, {})
|
|
6145
7505
|
}
|
|
6146
7506
|
),
|
|
6147
|
-
/* @__PURE__ */
|
|
6148
|
-
"span",
|
|
6149
|
-
{
|
|
6150
|
-
className: "db-window-drag-grip",
|
|
6151
|
-
"data-tooltip": "Click and drag here to move your window around.",
|
|
6152
|
-
"aria-hidden": true
|
|
6153
|
-
}
|
|
6154
|
-
)
|
|
7507
|
+
/* @__PURE__ */ jsx13("span", { className: "db-window-drag-grip", "aria-hidden": true })
|
|
6155
7508
|
] }),
|
|
6156
|
-
git.available && /* @__PURE__ */
|
|
7509
|
+
git.available && /* @__PURE__ */ jsx13(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx13(
|
|
6157
7510
|
GitUI,
|
|
6158
7511
|
{
|
|
6159
7512
|
onOpenFile: (path) => void handleSelect(path, "file"),
|
|
6160
7513
|
onWorkspaceCloned: handleWorkspaceCloned
|
|
6161
7514
|
}
|
|
6162
7515
|
) }),
|
|
6163
|
-
/* @__PURE__ */
|
|
7516
|
+
/* @__PURE__ */ jsx13(
|
|
6164
7517
|
FileExplorer,
|
|
6165
7518
|
{
|
|
6166
7519
|
provider,
|
|
7520
|
+
activeWorkspaceId,
|
|
6167
7521
|
activeFilePath: selectedFile,
|
|
7522
|
+
pinnedDocuments: pinnedDocumentItems,
|
|
7523
|
+
pinnedPaths: activeWorkspacePinnedPaths,
|
|
7524
|
+
onPinnedDocumentSelect: (document2) => void handlePinnedDocumentSelect(document2),
|
|
7525
|
+
onPinnedDocumentUnpin: unpinDocument,
|
|
7526
|
+
onPinnedDocumentRename: handlePinnedDocumentRename,
|
|
7527
|
+
onPinnedDocumentDelete: handlePinnedDocumentDelete,
|
|
7528
|
+
onTogglePin: handleTogglePin,
|
|
6168
7529
|
onSelect: handleSelect,
|
|
6169
7530
|
onTreeMutation: handleTreeMutation,
|
|
6170
7531
|
onTreeChange: handleTreeChange,
|
|
@@ -6175,17 +7536,17 @@ function DocBlocksShell({
|
|
|
6175
7536
|
},
|
|
6176
7537
|
explorerKey
|
|
6177
7538
|
),
|
|
6178
|
-
isMobile && showWelcomeGateway && /* @__PURE__ */
|
|
7539
|
+
isMobile && showWelcomeGateway && /* @__PURE__ */ jsxs12(
|
|
6179
7540
|
"section",
|
|
6180
7541
|
{
|
|
6181
7542
|
className: "db-mobile-first-run",
|
|
6182
7543
|
"aria-labelledby": "db-mobile-first-run-title",
|
|
6183
7544
|
children: [
|
|
6184
|
-
/* @__PURE__ */
|
|
6185
|
-
/* @__PURE__ */
|
|
6186
|
-
/* @__PURE__ */
|
|
6187
|
-
/* @__PURE__ */
|
|
6188
|
-
/* @__PURE__ */
|
|
7545
|
+
/* @__PURE__ */ jsx13("p", { className: "db-mobile-first-run-eyebrow", children: "Local-first Markdown editor" }),
|
|
7546
|
+
/* @__PURE__ */ jsx13("h1", { id: "db-mobile-first-run-title", children: "Welcome to DocBlocks" }),
|
|
7547
|
+
/* @__PURE__ */ jsx13("p", { children: "Write visually, keep plain Markdown underneath, and export the same document in useful formats. Your browser workspace stays on this device." }),
|
|
7548
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-mobile-first-run-actions", children: [
|
|
7549
|
+
/* @__PURE__ */ jsx13(
|
|
6189
7550
|
"button",
|
|
6190
7551
|
{
|
|
6191
7552
|
type: "button",
|
|
@@ -6196,7 +7557,7 @@ function DocBlocksShell({
|
|
|
6196
7557
|
children: "Tour the welcome document"
|
|
6197
7558
|
}
|
|
6198
7559
|
),
|
|
6199
|
-
/* @__PURE__ */
|
|
7560
|
+
/* @__PURE__ */ jsx13(
|
|
6200
7561
|
"button",
|
|
6201
7562
|
{
|
|
6202
7563
|
type: "button",
|
|
@@ -6209,22 +7570,22 @@ function DocBlocksShell({
|
|
|
6209
7570
|
]
|
|
6210
7571
|
}
|
|
6211
7572
|
),
|
|
6212
|
-
/* @__PURE__ */
|
|
6213
|
-
/* @__PURE__ */
|
|
6214
|
-
/* @__PURE__ */
|
|
6215
|
-
/* @__PURE__ */
|
|
6216
|
-
/* @__PURE__ */
|
|
6217
|
-
/* @__PURE__ */
|
|
6218
|
-
showBrowserStorageWarning && /* @__PURE__ */
|
|
6219
|
-
/* @__PURE__ */
|
|
6220
|
-
/* @__PURE__ */
|
|
7573
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-shell-sidebar-footer", children: [
|
|
7574
|
+
/* @__PURE__ */ jsx13("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
|
|
7575
|
+
/* @__PURE__ */ jsx13("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
|
|
7576
|
+
/* @__PURE__ */ jsx13("a", { href: "https://docblocks.com/terms/", target: "_blank", rel: "noopener noreferrer", children: "Terms" }),
|
|
7577
|
+
/* @__PURE__ */ jsx13("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
|
|
7578
|
+
/* @__PURE__ */ jsx13("a", { href: issueReportUrl, target: "_blank", rel: "noopener noreferrer", children: "Report issue" }),
|
|
7579
|
+
showBrowserStorageWarning && /* @__PURE__ */ jsxs12(Fragment7, { children: [
|
|
7580
|
+
/* @__PURE__ */ jsx13("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
|
|
7581
|
+
/* @__PURE__ */ jsx13(
|
|
6221
7582
|
"button",
|
|
6222
7583
|
{
|
|
6223
7584
|
type: "button",
|
|
6224
7585
|
className: "db-shell-sidebar-footer-action",
|
|
6225
7586
|
onClick: () => void handleDownloadAllWorkspaces(),
|
|
6226
|
-
title: "Browser
|
|
6227
|
-
children: "
|
|
7587
|
+
title: "Browser documents can be removed automatically. Download all workspaces now.",
|
|
7588
|
+
children: "Back up browser docs"
|
|
6228
7589
|
}
|
|
6229
7590
|
)
|
|
6230
7591
|
] })
|
|
@@ -6232,7 +7593,7 @@ function DocBlocksShell({
|
|
|
6232
7593
|
]
|
|
6233
7594
|
}
|
|
6234
7595
|
),
|
|
6235
|
-
!effectiveCompact && /* @__PURE__ */
|
|
7596
|
+
!effectiveCompact && /* @__PURE__ */ jsx13(
|
|
6236
7597
|
"div",
|
|
6237
7598
|
{
|
|
6238
7599
|
className: "db-shell-sidebar-resizer",
|
|
@@ -6242,7 +7603,7 @@ function DocBlocksShell({
|
|
|
6242
7603
|
onPointerDown: handleResizerPointerDown
|
|
6243
7604
|
}
|
|
6244
7605
|
),
|
|
6245
|
-
(!effectiveCompact || mobileShowEditor) && /* @__PURE__ */
|
|
7606
|
+
(!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs12(
|
|
6246
7607
|
"main",
|
|
6247
7608
|
{
|
|
6248
7609
|
"aria-label": "Document editor",
|
|
@@ -6255,21 +7616,23 @@ function DocBlocksShell({
|
|
|
6255
7616
|
position: "relative"
|
|
6256
7617
|
},
|
|
6257
7618
|
children: [
|
|
6258
|
-
selectedFile && mediaProvider ? /* @__PURE__ */
|
|
6259
|
-
/* @__PURE__ */
|
|
7619
|
+
selectedFile && mediaProvider ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
|
|
7620
|
+
/* @__PURE__ */ jsx13(
|
|
6260
7621
|
Suspense2,
|
|
6261
7622
|
{
|
|
6262
|
-
fallback: /* @__PURE__ */
|
|
6263
|
-
children: /* @__PURE__ */
|
|
7623
|
+
fallback: /* @__PURE__ */ jsx13("div", { className: "db-shell-empty", role: "status", children: "Loading your document\u2026" }),
|
|
7624
|
+
children: /* @__PURE__ */ jsx13(
|
|
6264
7625
|
EditorShell,
|
|
6265
7626
|
{
|
|
6266
7627
|
initialMarkdown: editorContent,
|
|
6267
7628
|
initialView,
|
|
7629
|
+
defaultViewportPreset: defaultPreviewViewportPreset,
|
|
6268
7630
|
articleId: selectedFile,
|
|
6269
7631
|
fileName: selectedFile,
|
|
6270
7632
|
onChange: handleEditorChange,
|
|
7633
|
+
onLinkClick: handleEditorLinkClick,
|
|
6271
7634
|
colorScheme: resolvedTheme,
|
|
6272
|
-
writeCanvasSettings,
|
|
7635
|
+
writeCanvasSettings: editorWriteCanvasSettings,
|
|
6273
7636
|
height: "100%",
|
|
6274
7637
|
placeholder: editorPlaceholder,
|
|
6275
7638
|
outlineWidth: 280,
|
|
@@ -6283,28 +7646,29 @@ function DocBlocksShell({
|
|
|
6283
7646
|
versioningPrunePolicy,
|
|
6284
7647
|
versioningAutoSaveIdleMs,
|
|
6285
7648
|
onSaveVersion,
|
|
6286
|
-
|
|
7649
|
+
statusBarSlotRight,
|
|
7650
|
+
toolbarSlotLeft: effectiveCompact ? /* @__PURE__ */ jsx13(
|
|
6287
7651
|
"button",
|
|
6288
7652
|
{
|
|
6289
7653
|
className: "db-mobile-back",
|
|
6290
7654
|
onClick: () => setMobileShowEditor(false),
|
|
6291
7655
|
"aria-label": "Show file list",
|
|
6292
|
-
children: /* @__PURE__ */
|
|
7656
|
+
children: /* @__PURE__ */ jsx13("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) })
|
|
6293
7657
|
}
|
|
6294
7658
|
) : void 0,
|
|
6295
|
-
toolbarSlotRight: /* @__PURE__ */
|
|
6296
|
-
compactLayout && !isMobile && /* @__PURE__ */
|
|
7659
|
+
toolbarSlotRight: /* @__PURE__ */ jsxs12(Fragment7, { children: [
|
|
7660
|
+
compactLayout && !isMobile && /* @__PURE__ */ jsx13(
|
|
6297
7661
|
"button",
|
|
6298
7662
|
{
|
|
6299
7663
|
className: "db-restore-split",
|
|
6300
7664
|
onClick: () => setCompactLayout(false),
|
|
6301
7665
|
"aria-label": "Restore split view",
|
|
6302
7666
|
title: "Restore split view",
|
|
6303
|
-
children: /* @__PURE__ */
|
|
7667
|
+
children: /* @__PURE__ */ jsx13(SplitViewIcon, {})
|
|
6304
7668
|
}
|
|
6305
7669
|
),
|
|
6306
|
-
git.repo && /* @__PURE__ */
|
|
6307
|
-
/* @__PURE__ */
|
|
7670
|
+
git.repo && /* @__PURE__ */ jsx13(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx13(GitToolbarControl, { selectedFile }) }),
|
|
7671
|
+
/* @__PURE__ */ jsx13(
|
|
6308
7672
|
ExportToolbarControls,
|
|
6309
7673
|
{
|
|
6310
7674
|
selectedFile,
|
|
@@ -6322,15 +7686,15 @@ function DocBlocksShell({
|
|
|
6322
7686
|
)
|
|
6323
7687
|
}
|
|
6324
7688
|
),
|
|
6325
|
-
showWelcomeGateway && !isMobile && /* @__PURE__ */
|
|
6326
|
-
/* @__PURE__ */
|
|
7689
|
+
showWelcomeGateway && !isMobile && /* @__PURE__ */ jsxs12("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
|
|
7690
|
+
/* @__PURE__ */ jsxs12("span", { className: "db-welcome-gateway-text", children: [
|
|
6327
7691
|
"You\u2019re watching this welcome doc in ",
|
|
6328
|
-
/* @__PURE__ */
|
|
7692
|
+
/* @__PURE__ */ jsx13("strong", { children: "Slideshow" }),
|
|
6329
7693
|
" ",
|
|
6330
7694
|
"view\u2014 it\u2019s a regular markdown file, and so is everything you\u2019ll write."
|
|
6331
7695
|
] }),
|
|
6332
|
-
/* @__PURE__ */
|
|
6333
|
-
/* @__PURE__ */
|
|
7696
|
+
/* @__PURE__ */ jsx13("button", { className: "db-welcome-gateway-cta", onClick: handleStartWriting, children: "Start writing" }),
|
|
7697
|
+
/* @__PURE__ */ jsx13(
|
|
6334
7698
|
"button",
|
|
6335
7699
|
{
|
|
6336
7700
|
className: "db-welcome-gateway-dismiss",
|
|
@@ -6341,39 +7705,39 @@ function DocBlocksShell({
|
|
|
6341
7705
|
}
|
|
6342
7706
|
)
|
|
6343
7707
|
] })
|
|
6344
|
-
] }) : selectedFolder ? /* @__PURE__ */
|
|
6345
|
-
effectiveCompact && /* @__PURE__ */
|
|
6346
|
-
/* @__PURE__ */
|
|
7708
|
+
] }) : selectedFolder ? /* @__PURE__ */ jsxs12("div", { className: "db-folder-view", children: [
|
|
7709
|
+
effectiveCompact && /* @__PURE__ */ jsxs12("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
|
|
7710
|
+
/* @__PURE__ */ jsx13("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) }),
|
|
6347
7711
|
"Back to files"
|
|
6348
7712
|
] }),
|
|
6349
|
-
/* @__PURE__ */
|
|
6350
|
-
/* @__PURE__ */
|
|
6351
|
-
/* @__PURE__ */
|
|
7713
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-folder-view-header", children: [
|
|
7714
|
+
/* @__PURE__ */ jsx13("span", { className: "db-folder-view-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) }),
|
|
7715
|
+
/* @__PURE__ */ jsx13("span", { className: "db-folder-view-path", children: selectedFolder })
|
|
6352
7716
|
] }),
|
|
6353
|
-
|
|
7717
|
+
visibleFolderEntries.length === 0 ? /* @__PURE__ */ jsx13("p", { className: "db-folder-view-empty", children: "This folder is empty." }) : /* @__PURE__ */ jsx13("ul", { className: "db-folder-view-list", children: visibleFolderEntries.map((entry) => /* @__PURE__ */ jsxs12(
|
|
6354
7718
|
"li",
|
|
6355
7719
|
{
|
|
6356
7720
|
className: "db-folder-view-item",
|
|
6357
7721
|
onClick: () => handleSelect(entry.path, entry.kind),
|
|
6358
7722
|
children: [
|
|
6359
|
-
/* @__PURE__ */
|
|
7723
|
+
/* @__PURE__ */ jsx13("span", { className: "db-folder-view-item-icon", children: entry.kind === "directory" ? /* @__PURE__ */ jsx13(FolderGlyph, {}) : /* @__PURE__ */ jsx13(FileGlyph, {}) }),
|
|
6360
7724
|
entry.name
|
|
6361
7725
|
]
|
|
6362
7726
|
},
|
|
6363
7727
|
entry.path
|
|
6364
7728
|
)) })
|
|
6365
|
-
] }) : /* @__PURE__ */
|
|
6366
|
-
effectiveCompact && /* @__PURE__ */
|
|
6367
|
-
/* @__PURE__ */
|
|
7729
|
+
] }) : /* @__PURE__ */ jsxs12("div", { className: "db-shell-empty db-shell-empty--workspace", children: [
|
|
7730
|
+
effectiveCompact && /* @__PURE__ */ jsxs12("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
|
|
7731
|
+
/* @__PURE__ */ jsx13("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx13(FolderGlyph, {}) }),
|
|
6368
7732
|
"Back to files"
|
|
6369
7733
|
] }),
|
|
6370
|
-
/* @__PURE__ */
|
|
6371
|
-
/* @__PURE__ */
|
|
6372
|
-
/* @__PURE__ */
|
|
6373
|
-
/* @__PURE__ */
|
|
6374
|
-
/* @__PURE__ */
|
|
6375
|
-
/* @__PURE__ */
|
|
6376
|
-
/* @__PURE__ */
|
|
7734
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-workspace-empty-content", children: [
|
|
7735
|
+
/* @__PURE__ */ jsx13("span", { className: "db-workspace-empty-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(FileGlyph, {}) }),
|
|
7736
|
+
/* @__PURE__ */ jsx13("h1", { children: activeWorkspaceDescriptor?.name ?? "Workspace" }),
|
|
7737
|
+
/* @__PURE__ */ jsx13("p", { children: "Choose a Markdown document from the sidebar, or create a new one." }),
|
|
7738
|
+
/* @__PURE__ */ jsxs12("div", { className: "db-workspace-empty-actions", children: [
|
|
7739
|
+
/* @__PURE__ */ jsx13("button", { type: "button", onClick: () => void handleNewFile(), children: "New document" }),
|
|
7740
|
+
/* @__PURE__ */ jsx13(
|
|
6377
7741
|
"button",
|
|
6378
7742
|
{
|
|
6379
7743
|
type: "button",
|
|
@@ -6382,7 +7746,7 @@ function DocBlocksShell({
|
|
|
6382
7746
|
children: "New folder"
|
|
6383
7747
|
}
|
|
6384
7748
|
),
|
|
6385
|
-
(
|
|
7749
|
+
(isElectronHost3() || typeof globalThis.showDirectoryPicker === "function") && /* @__PURE__ */ jsx13(
|
|
6386
7750
|
"button",
|
|
6387
7751
|
{
|
|
6388
7752
|
type: "button",
|
|
@@ -6392,10 +7756,10 @@ function DocBlocksShell({
|
|
|
6392
7756
|
}
|
|
6393
7757
|
)
|
|
6394
7758
|
] }),
|
|
6395
|
-
/* @__PURE__ */
|
|
7759
|
+
/* @__PURE__ */ jsx13("p", { className: "db-workspace-empty-hint", children: "Browser workspaces stay on this device. Use the sidebar backup action to keep a portable copy." })
|
|
6396
7760
|
] })
|
|
6397
7761
|
] }),
|
|
6398
|
-
/* @__PURE__ */
|
|
7762
|
+
/* @__PURE__ */ jsx13(
|
|
6399
7763
|
UpdateAvailableNotice,
|
|
6400
7764
|
{
|
|
6401
7765
|
available: updateAvailable,
|
|
@@ -6416,6 +7780,7 @@ export {
|
|
|
6416
7780
|
AccentColorSettings,
|
|
6417
7781
|
AppMenu,
|
|
6418
7782
|
DEFAULT_OPTIONS,
|
|
7783
|
+
DEFAULT_WRITE_CANVAS_FONT_SCHEME,
|
|
6419
7784
|
DocBlocksShell,
|
|
6420
7785
|
ExportDialog,
|
|
6421
7786
|
ExportToolbarControls,
|
|
@@ -6423,10 +7788,12 @@ export {
|
|
|
6423
7788
|
FileTreeNode,
|
|
6424
7789
|
SettingsDialog,
|
|
6425
7790
|
ThemeSettings,
|
|
7791
|
+
WRITE_CANVAS_FONT_SCHEMES,
|
|
6426
7792
|
WorkspacePicker,
|
|
6427
7793
|
WriteCanvasSettingsControls,
|
|
6428
7794
|
buildExportFilename,
|
|
6429
7795
|
loadLastExportOptions,
|
|
7796
|
+
resolveWriteCanvasFonts,
|
|
6430
7797
|
runExport,
|
|
6431
7798
|
updateExportTargetExtension,
|
|
6432
7799
|
useDocumentSession,
|