@bendyline/docblocks-react 2.3.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.txt +353 -45
- package/dist/{ExportToolbarControls-N3TCL5DD.js → ExportToolbarControls-BUVJCSMV.js} +1 -1
- package/dist/{ShareDialog-PAMOQQFB.js → ShareDialog-4OX3UPJ4.js} +150 -3
- package/dist/{chunk-BI7NVU6T.js → chunk-FNSRCCBC.js} +10 -0
- package/dist/export/index.d.ts +23 -1
- package/dist/export/index.js +5 -1
- package/dist/index.d.ts +75 -64
- package/dist/index.js +891 -191
- package/package.json +10 -8
- package/src/styles/docblocks.css +79 -1
package/dist/index.js
CHANGED
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
useGitContext
|
|
9
9
|
} from "./chunk-HFYPTTCH.js";
|
|
10
10
|
import {
|
|
11
|
+
createImageSaveOutput,
|
|
11
12
|
updateExportTargetExtension
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-FNSRCCBC.js";
|
|
13
14
|
import {
|
|
14
15
|
AccentColorSettings,
|
|
15
16
|
DB_CHROME_COLORS,
|
|
@@ -97,6 +98,22 @@ function equivalentPathKeys(path) {
|
|
|
97
98
|
function hasEquivalentPath(paths, path) {
|
|
98
99
|
return equivalentPathKeys(path).some((candidate) => paths.has(candidate));
|
|
99
100
|
}
|
|
101
|
+
function sameEntry(left, right) {
|
|
102
|
+
if (left.kind !== right.kind || left.name !== right.name || left.path !== right.path)
|
|
103
|
+
return false;
|
|
104
|
+
if (left.kind === "file" && right.kind === "file") {
|
|
105
|
+
return left.lastModified === right.lastModified;
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
function reconcileEntries(current, incoming) {
|
|
110
|
+
const currentByPath = new Map(current.map((entry) => [entry.path, entry]));
|
|
111
|
+
const reconciled = incoming.map((entry) => {
|
|
112
|
+
const previous = currentByPath.get(entry.path);
|
|
113
|
+
return previous && sameEntry(previous, entry) ? previous : entry;
|
|
114
|
+
});
|
|
115
|
+
return reconciled.length === current.length && reconciled.every((entry, index) => entry === current[index]) ? current : reconciled;
|
|
116
|
+
}
|
|
100
117
|
async function readProviderDirectory(provider, path) {
|
|
101
118
|
const canonical = parseWorkspacePath(path);
|
|
102
119
|
const readOnce = async () => {
|
|
@@ -172,7 +189,7 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
|
|
|
172
189
|
try {
|
|
173
190
|
const root = await readProviderDirectory(sourceProvider, "");
|
|
174
191
|
if (!isCurrent()) return;
|
|
175
|
-
setEntries(root);
|
|
192
|
+
setEntries((current) => reconcileEntries(current, root));
|
|
176
193
|
setRootIssue(null);
|
|
177
194
|
} catch (caught) {
|
|
178
195
|
if (isCurrent()) setRootIssue(readIssue(caught, ""));
|
|
@@ -191,8 +208,11 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
|
|
|
191
208
|
const children = await readProviderDirectory(sourceProvider, dirPath);
|
|
192
209
|
if (!isCurrent()) return;
|
|
193
210
|
setChildEntries((prev) => {
|
|
211
|
+
const current = prev.get(canonical) ?? [];
|
|
212
|
+
const reconciled = reconcileEntries(current, children);
|
|
213
|
+
if (prev.has(canonical) && reconciled === current) return prev;
|
|
194
214
|
const next = new Map(prev);
|
|
195
|
-
next.set(canonical,
|
|
215
|
+
next.set(canonical, reconciled);
|
|
196
216
|
return next;
|
|
197
217
|
});
|
|
198
218
|
setChildIssues((prev) => {
|
|
@@ -259,7 +279,7 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
|
|
|
259
279
|
[loadChildren]
|
|
260
280
|
);
|
|
261
281
|
const refresh = useCallback(async () => {
|
|
262
|
-
await loadRoot();
|
|
282
|
+
await loadRoot(false);
|
|
263
283
|
const expandedPaths = [...expanded];
|
|
264
284
|
for (const dirPath of expandedPaths) {
|
|
265
285
|
await loadChildren(dirPath);
|
|
@@ -333,18 +353,21 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
|
|
|
333
353
|
directoriesToRefresh.clear();
|
|
334
354
|
drainRefreshes();
|
|
335
355
|
};
|
|
336
|
-
const
|
|
356
|
+
const requestDirectoryRefresh = (path) => {
|
|
337
357
|
if (disposed || fullRefreshRequested) return;
|
|
338
358
|
directoriesToRefresh.add(workspacePathDirname(parseWorkspacePath(path)));
|
|
339
359
|
drainRefreshes();
|
|
340
360
|
};
|
|
341
361
|
const subscription = providerV2.watch(
|
|
342
362
|
(event) => {
|
|
343
|
-
if (event.type
|
|
344
|
-
|
|
345
|
-
|
|
363
|
+
if (event.type !== "overflow") {
|
|
364
|
+
requestDirectoryRefresh(event.path);
|
|
365
|
+
if (event.type === "moved" && event.destinationPath !== null) {
|
|
366
|
+
requestDirectoryRefresh(event.destinationPath);
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
requestRefresh();
|
|
346
370
|
}
|
|
347
|
-
requestRefresh();
|
|
348
371
|
},
|
|
349
372
|
{
|
|
350
373
|
onError: (caught) => {
|
|
@@ -498,6 +521,9 @@ function NewFileIcon() {
|
|
|
498
521
|
function NewFolderIcon() {
|
|
499
522
|
return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-folder-plus" });
|
|
500
523
|
}
|
|
524
|
+
function OpenFolderIcon() {
|
|
525
|
+
return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-folder-open" });
|
|
526
|
+
}
|
|
501
527
|
function SortByNameIcon() {
|
|
502
528
|
return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-arrow-down-a-z" });
|
|
503
529
|
}
|
|
@@ -658,6 +684,7 @@ function FileTreeNode({
|
|
|
658
684
|
selected,
|
|
659
685
|
badge,
|
|
660
686
|
gitActions,
|
|
687
|
+
actions = [],
|
|
661
688
|
focusable = true,
|
|
662
689
|
posInSet,
|
|
663
690
|
setSize,
|
|
@@ -829,6 +856,18 @@ function FileTreeNode({
|
|
|
829
856
|
);
|
|
830
857
|
}
|
|
831
858
|
}, [closeMenu, entry.path, onTogglePin, pinned]);
|
|
859
|
+
const handleCustomAction = useCallback2(
|
|
860
|
+
async (action) => {
|
|
861
|
+
closeMenu(false);
|
|
862
|
+
setActionError(null);
|
|
863
|
+
try {
|
|
864
|
+
await action.onSelect();
|
|
865
|
+
} catch (caught) {
|
|
866
|
+
setActionError(caught instanceof Error ? caught.message : `Unable to ${action.label}.`);
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
[closeMenu]
|
|
870
|
+
);
|
|
832
871
|
useEffect2(() => {
|
|
833
872
|
if (!showContext) return;
|
|
834
873
|
function handleClose(e) {
|
|
@@ -1009,6 +1048,22 @@ function FileTreeNode({
|
|
|
1009
1048
|
children: pinned ? "Unpin" : "Pin"
|
|
1010
1049
|
}
|
|
1011
1050
|
),
|
|
1051
|
+
actions.length > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1052
|
+
/* @__PURE__ */ jsx3("div", { className: "db-tree-context-divider", role: "separator" }),
|
|
1053
|
+
actions.map((action) => /* @__PURE__ */ jsx3(
|
|
1054
|
+
"button",
|
|
1055
|
+
{
|
|
1056
|
+
type: "button",
|
|
1057
|
+
role: "menuitem",
|
|
1058
|
+
tabIndex: -1,
|
|
1059
|
+
className: "db-tree-context-item",
|
|
1060
|
+
disabled: action.disabled,
|
|
1061
|
+
onClick: () => void handleCustomAction(action),
|
|
1062
|
+
children: action.label
|
|
1063
|
+
},
|
|
1064
|
+
action.label
|
|
1065
|
+
))
|
|
1066
|
+
] }),
|
|
1012
1067
|
gitActions && (gitActions.viewChanges || gitActions.fileHistory) && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1013
1068
|
/* @__PURE__ */ jsx3("div", { className: "db-tree-context-divider", role: "separator" }),
|
|
1014
1069
|
gitActions.viewChanges && /* @__PURE__ */ jsx3(
|
|
@@ -1565,7 +1620,18 @@ function sortFileEntries(entries, mode) {
|
|
|
1565
1620
|
|
|
1566
1621
|
// src/FileExplorer/FileExplorer.tsx
|
|
1567
1622
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1568
|
-
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1623
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1624
|
+
".txt",
|
|
1625
|
+
".md",
|
|
1626
|
+
".html",
|
|
1627
|
+
".htm",
|
|
1628
|
+
".docx",
|
|
1629
|
+
".pdf",
|
|
1630
|
+
".pptx",
|
|
1631
|
+
".xlsx",
|
|
1632
|
+
".dbk",
|
|
1633
|
+
".zip"
|
|
1634
|
+
]);
|
|
1569
1635
|
var INTERNAL_DRAG_TYPE = "application/x-docblocks-entry";
|
|
1570
1636
|
var NEW_ITEM_ERROR_ID = "db-new-item-error";
|
|
1571
1637
|
function normalisePath2(path) {
|
|
@@ -1616,6 +1682,8 @@ function FileExplorer({
|
|
|
1616
1682
|
onPinnedDocumentDelete,
|
|
1617
1683
|
onTogglePin,
|
|
1618
1684
|
onSelect,
|
|
1685
|
+
onOpenWorkspaceFolder,
|
|
1686
|
+
actionsForEntry,
|
|
1619
1687
|
onTreeMutation,
|
|
1620
1688
|
onTreeChange,
|
|
1621
1689
|
onImportFiles,
|
|
@@ -1975,6 +2043,7 @@ function FileExplorer({
|
|
|
1975
2043
|
selected: tree.selectedPath !== null && normalisePath2(tree.selectedPath) === normalisePath2(entry.path),
|
|
1976
2044
|
badge: badgeFor(entry),
|
|
1977
2045
|
gitActions: gitActionsFor(entry),
|
|
2046
|
+
actions: actionsForEntry?.(entry),
|
|
1978
2047
|
onToggle: tree.toggleExpand,
|
|
1979
2048
|
onSelect: handleSelect,
|
|
1980
2049
|
confirmDelete,
|
|
@@ -2029,6 +2098,7 @@ function FileExplorer({
|
|
|
2029
2098
|
childIssues,
|
|
2030
2099
|
badgeFor,
|
|
2031
2100
|
gitActionsFor,
|
|
2101
|
+
actionsForEntry,
|
|
2032
2102
|
draggedEntry,
|
|
2033
2103
|
dropTarget,
|
|
2034
2104
|
handleInternalDragStart,
|
|
@@ -2092,6 +2162,17 @@ function FileExplorer({
|
|
|
2092
2162
|
)
|
|
2093
2163
|
] }),
|
|
2094
2164
|
/* @__PURE__ */ jsx5("span", { className: "db-explorer-action-divider", "aria-hidden": "true" }),
|
|
2165
|
+
onOpenWorkspaceFolder && /* @__PURE__ */ jsx5(
|
|
2166
|
+
"button",
|
|
2167
|
+
{
|
|
2168
|
+
type: "button",
|
|
2169
|
+
className: "db-explorer-btn",
|
|
2170
|
+
onClick: onOpenWorkspaceFolder,
|
|
2171
|
+
title: "Open this folder",
|
|
2172
|
+
"aria-label": "Open this folder",
|
|
2173
|
+
children: /* @__PURE__ */ jsx5(OpenFolderIcon, {})
|
|
2174
|
+
}
|
|
2175
|
+
),
|
|
2095
2176
|
/* @__PURE__ */ jsx5(
|
|
2096
2177
|
"button",
|
|
2097
2178
|
{
|
|
@@ -2523,21 +2604,22 @@ import {
|
|
|
2523
2604
|
DocumentVersionManager
|
|
2524
2605
|
} from "@bendyline/squisq/versions";
|
|
2525
2606
|
import {
|
|
2526
|
-
FileSystemContentContainer,
|
|
2607
|
+
FileSystemContentContainer as FileSystemContentContainer2,
|
|
2527
2608
|
createFileMediaProvider,
|
|
2528
2609
|
decodeUtf8Text,
|
|
2529
|
-
FsError as
|
|
2530
|
-
getFileSystemProviderV2 as
|
|
2610
|
+
FsError as FsError6,
|
|
2611
|
+
getFileSystemProviderV2 as getFileSystemProviderV27,
|
|
2531
2612
|
isQuotaExceededError,
|
|
2532
2613
|
moveFileSystemEntry as moveFileSystemEntry2,
|
|
2533
|
-
parseWorkspacePath as
|
|
2614
|
+
parseWorkspacePath as parseWorkspacePath7,
|
|
2534
2615
|
workspacePathContains
|
|
2535
2616
|
} from "@bendyline/docblocks/filesystem";
|
|
2536
2617
|
import {
|
|
2537
|
-
createFileSystemDocumentTarget,
|
|
2618
|
+
createFileSystemDocumentTarget as createFileSystemDocumentTarget2,
|
|
2538
2619
|
DocumentCommitConflictError,
|
|
2539
2620
|
DocumentSessionConflictError
|
|
2540
2621
|
} from "@bendyline/docblocks/document";
|
|
2622
|
+
import { createMediaProviderFromContainer } from "@bendyline/squisq/storage";
|
|
2541
2623
|
import { isElectronHost as isElectronHost3, getDocBlocksHost } from "@bendyline/docblocks/host";
|
|
2542
2624
|
import {
|
|
2543
2625
|
parseSharedDocumentHash
|
|
@@ -3154,7 +3236,7 @@ function useDocumentSession(autoSaveDelayMs = 500) {
|
|
|
3154
3236
|
import { lazy, Suspense } from "react";
|
|
3155
3237
|
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
3156
3238
|
var ExportToolbarControlsImplementation = lazy(
|
|
3157
|
-
() => import("./ExportToolbarControls-
|
|
3239
|
+
() => import("./ExportToolbarControls-BUVJCSMV.js").then((module) => ({
|
|
3158
3240
|
default: module.ExportToolbarControls
|
|
3159
3241
|
}))
|
|
3160
3242
|
);
|
|
@@ -3671,7 +3753,123 @@ function hasControlCharacter(value) {
|
|
|
3671
3753
|
}
|
|
3672
3754
|
|
|
3673
3755
|
// src/DocBlocksShell/import-files.ts
|
|
3674
|
-
import {
|
|
3756
|
+
import {
|
|
3757
|
+
FsError as FsError3,
|
|
3758
|
+
getFileSystemProviderV2 as getFileSystemProviderV23,
|
|
3759
|
+
parseWorkspacePath as parseWorkspacePath4
|
|
3760
|
+
} from "@bendyline/docblocks/filesystem";
|
|
3761
|
+
|
|
3762
|
+
// src/DocBlocksShell/outside-in-contract.ts
|
|
3763
|
+
var OUTSIDE_IN_FORMAT_IDS = ["html", "docx", "pdf", "pptx", "xlsx"];
|
|
3764
|
+
var FORMAT_IDS = new Set(OUTSIDE_IN_FORMAT_IDS);
|
|
3765
|
+
var UPDATE_FROM_MARKDOWN_KEY = "squisq-updatefrommarkdown";
|
|
3766
|
+
function normalizePath(path) {
|
|
3767
|
+
const leading = path.replace(/\\/g, "/").startsWith("/") ? "/" : "";
|
|
3768
|
+
const parts = path.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
3769
|
+
if (parts.some((part) => part === "." || part === "..")) {
|
|
3770
|
+
throw new Error(`Outside-in paths must be canonical workspace paths: ${path}`);
|
|
3771
|
+
}
|
|
3772
|
+
return leading + parts.join("/");
|
|
3773
|
+
}
|
|
3774
|
+
function join(parent, child) {
|
|
3775
|
+
if (!parent || parent === "/") return parent === "/" ? `/${child}` : child;
|
|
3776
|
+
return `${parent}/${child}`;
|
|
3777
|
+
}
|
|
3778
|
+
function slug(stem) {
|
|
3779
|
+
return stem.normalize("NFKD").replace(/\p{Mark}+/gu, "").toLocaleLowerCase("en-US").replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-+|-+$/g, "") || "document";
|
|
3780
|
+
}
|
|
3781
|
+
function resolveOutsideInLayout(path) {
|
|
3782
|
+
const targetPath = normalizePath(path);
|
|
3783
|
+
const slash = targetPath.lastIndexOf("/");
|
|
3784
|
+
const parentDirectory = slash < 0 ? "" : slash === 0 ? "/" : targetPath.slice(0, slash);
|
|
3785
|
+
const filename = slash < 0 ? targetPath : targetPath.slice(slash + 1);
|
|
3786
|
+
const dot = filename.lastIndexOf(".");
|
|
3787
|
+
if (dot <= 0) return null;
|
|
3788
|
+
const rawFormat = filename.slice(dot + 1).toLowerCase();
|
|
3789
|
+
const format = rawFormat === "htm" ? "html" : rawFormat;
|
|
3790
|
+
if (!FORMAT_IDS.has(format)) return null;
|
|
3791
|
+
const stem = filename.slice(0, dot);
|
|
3792
|
+
const companionName = `${stem}_files`;
|
|
3793
|
+
const companionDirectory = join(parentDirectory, companionName);
|
|
3794
|
+
const markdownFilename = `${slug(stem)}.md`;
|
|
3795
|
+
const backupDirectory = join(companionDirectory, ".original");
|
|
3796
|
+
const backupFilename = `original.${format}`;
|
|
3797
|
+
return {
|
|
3798
|
+
targetPath,
|
|
3799
|
+
format,
|
|
3800
|
+
parentDirectory,
|
|
3801
|
+
stem,
|
|
3802
|
+
companionName,
|
|
3803
|
+
companionDirectory,
|
|
3804
|
+
markdownFilename,
|
|
3805
|
+
markdownPath: join(companionDirectory, markdownFilename),
|
|
3806
|
+
relativeTargetPath: `../${filename}`,
|
|
3807
|
+
backupDirectory,
|
|
3808
|
+
backupFilename,
|
|
3809
|
+
backupPath: join(backupDirectory, backupFilename)
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3812
|
+
function chooseOutsideInMarkdownPath(layout, paths) {
|
|
3813
|
+
const canonical = normalizePath(layout.markdownPath);
|
|
3814
|
+
const normalized = paths.map(normalizePath);
|
|
3815
|
+
const exact = normalized.find((path) => path === canonical);
|
|
3816
|
+
if (exact) return exact;
|
|
3817
|
+
const folded = normalized.find(
|
|
3818
|
+
(path) => path.toLocaleLowerCase("en-US") === canonical.toLocaleLowerCase("en-US")
|
|
3819
|
+
);
|
|
3820
|
+
if (folded) return folded;
|
|
3821
|
+
const prefix = `${normalizePath(layout.companionDirectory).replace(/\/$/, "")}/`;
|
|
3822
|
+
const markdown = normalized.filter(
|
|
3823
|
+
(path) => path.startsWith(prefix) && !path.slice(prefix.length).includes("/") && path.toLocaleLowerCase("en-US").endsWith(".md")
|
|
3824
|
+
);
|
|
3825
|
+
return markdown.length === 1 ? markdown[0] : null;
|
|
3826
|
+
}
|
|
3827
|
+
async function readOutsideInMetadata(source) {
|
|
3828
|
+
const { readOutsideInMetadata: readMetadata } = await import("@bendyline/squisq-formats/outside-in");
|
|
3829
|
+
const metadata = readMetadata(source);
|
|
3830
|
+
return metadata ? {
|
|
3831
|
+
...metadata,
|
|
3832
|
+
updateFromMarkdown: await isOutsideInMarkdownEditingEnabled(source)
|
|
3833
|
+
} : null;
|
|
3834
|
+
}
|
|
3835
|
+
async function withOutsideInMetadata(source, layout) {
|
|
3836
|
+
const { withOutsideInMetadata: addMetadata } = await import("@bendyline/squisq-formats/outside-in");
|
|
3837
|
+
return addMetadata(source, layout);
|
|
3838
|
+
}
|
|
3839
|
+
async function isOutsideInMarkdownEditingEnabled(source) {
|
|
3840
|
+
const module = await import("@bendyline/squisq-formats/outside-in");
|
|
3841
|
+
if (module.isOutsideInMarkdownEditingEnabled) {
|
|
3842
|
+
return module.isOutsideInMarkdownEditingEnabled(source);
|
|
3843
|
+
}
|
|
3844
|
+
const { parseFrontmatter, splitFrontmatterBlock } = await import("@bendyline/squisq/markdown");
|
|
3845
|
+
const block = splitFrontmatterBlock(source).frontmatter;
|
|
3846
|
+
if (!block) return false;
|
|
3847
|
+
const firstBreak = block.indexOf("\n");
|
|
3848
|
+
if (firstBreak < 0) return false;
|
|
3849
|
+
const yaml = block.slice(firstBreak + 1).replace(/\r?\n---(?:\r?\n)?$/, "");
|
|
3850
|
+
return parseFrontmatter(yaml)?.[UPDATE_FROM_MARKDOWN_KEY] === true;
|
|
3851
|
+
}
|
|
3852
|
+
async function withOutsideInMarkdownEditing(source, layout, enabled = true) {
|
|
3853
|
+
const module = await import("@bendyline/squisq-formats/outside-in");
|
|
3854
|
+
if (module.withOutsideInMarkdownEditing) {
|
|
3855
|
+
return module.withOutsideInMarkdownEditing(source, layout, enabled);
|
|
3856
|
+
}
|
|
3857
|
+
const { setFrontmatterValues } = await import("@bendyline/squisq/markdown");
|
|
3858
|
+
return setFrontmatterValues(await withOutsideInMetadata(source, layout), {
|
|
3859
|
+
[UPDATE_FROM_MARKDOWN_KEY]: enabled
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3862
|
+
async function importOutsideInDocument(source, options = {}) {
|
|
3863
|
+
const { importOutsideInDocument: importDocument } = await import("@bendyline/squisq-formats/outside-in");
|
|
3864
|
+
const imported = await importDocument(source, options);
|
|
3865
|
+
const layout = resolveOutsideInLayout(source.targetPath);
|
|
3866
|
+
if (!layout) throw new Error(`Outside-in editing does not support "${source.targetPath}".`);
|
|
3867
|
+
return { ...imported, layout };
|
|
3868
|
+
}
|
|
3869
|
+
async function renderOutsideInDocument(source, options = {}) {
|
|
3870
|
+
const { renderOutsideInDocument: renderDocument } = await import("@bendyline/squisq-formats/outside-in");
|
|
3871
|
+
return renderDocument(source, options);
|
|
3872
|
+
}
|
|
3675
3873
|
|
|
3676
3874
|
// src/DocBlocksShell/provider-io.ts
|
|
3677
3875
|
import {
|
|
@@ -3730,23 +3928,68 @@ async function claimAvailablePath(provider, desiredPath) {
|
|
|
3730
3928
|
}
|
|
3731
3929
|
throw new Error(`Too many documents are already named like \u201C${desiredPath}\u201D.`);
|
|
3732
3930
|
}
|
|
3733
|
-
async function
|
|
3734
|
-
|
|
3735
|
-
|
|
3931
|
+
async function writeProviderBytes(provider, path, data, mode) {
|
|
3932
|
+
const providerV2 = getFileSystemProviderV23(provider);
|
|
3933
|
+
if (providerV2) {
|
|
3934
|
+
await providerV2.writeFile(parseWorkspacePath4(path), data, {
|
|
3935
|
+
mode,
|
|
3936
|
+
createParents: true,
|
|
3937
|
+
expectedVersion: mode === "create" ? null : void 0
|
|
3938
|
+
});
|
|
3939
|
+
return;
|
|
3736
3940
|
}
|
|
3737
|
-
if (
|
|
3738
|
-
|
|
3739
|
-
const container = await docxToContainer(await file.arrayBuffer());
|
|
3740
|
-
const markdown = await container.readDocument() ?? "";
|
|
3741
|
-
await deps.persistMedia(container, destPath);
|
|
3742
|
-
return markdown;
|
|
3941
|
+
if (mode === "create" && await providerEntryExists(provider, path)) {
|
|
3942
|
+
throw new FsError3("already-exists", "File already exists.", { operation: "write", path });
|
|
3743
3943
|
}
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3944
|
+
await provider.writeBinary(path, data);
|
|
3945
|
+
}
|
|
3946
|
+
var OUTSIDE_IN_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".pptx", ".xlsx"]);
|
|
3947
|
+
async function claimOutsideInTarget(provider, desiredPath) {
|
|
3948
|
+
for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
|
|
3949
|
+
const candidate = attempt === 1 ? desiredPath : withCopySuffix(desiredPath, attempt);
|
|
3950
|
+
const layout = resolveOutsideInLayout(candidate);
|
|
3951
|
+
if (!layout) throw new Error(`Outside-in editing does not support \u201C${desiredPath}\u201D.`);
|
|
3952
|
+
if (await providerEntryExists(provider, layout.companionDirectory)) continue;
|
|
3953
|
+
try {
|
|
3954
|
+
await writeProviderBytes(provider, candidate, new Uint8Array(), "create");
|
|
3955
|
+
return { path: candidate, renamed: attempt > 1 };
|
|
3956
|
+
} catch (error) {
|
|
3957
|
+
if (!isAlreadyExists(error)) throw error;
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
throw new Error(`Too many documents are already named like \u201C${desiredPath}\u201D.`);
|
|
3961
|
+
}
|
|
3962
|
+
async function importOutsideInFile(file, provider) {
|
|
3963
|
+
const claim = await claimOutsideInTarget(provider, file.name);
|
|
3964
|
+
const created = [claim.path];
|
|
3965
|
+
try {
|
|
3966
|
+
const sourceBytes = await file.arrayBuffer();
|
|
3967
|
+
const imported = await importOutsideInDocument({
|
|
3968
|
+
data: sourceBytes,
|
|
3969
|
+
targetPath: claim.path
|
|
3970
|
+
});
|
|
3971
|
+
for (const entry of await imported.container.listFiles()) {
|
|
3972
|
+
if (/\.md$/i.test(entry.path)) continue;
|
|
3973
|
+
const data = await imported.container.readFile(entry.path);
|
|
3974
|
+
if (!data) continue;
|
|
3975
|
+
const destination = `${imported.layout.companionDirectory}/${entry.path.replace(/^\/+/, "")}`;
|
|
3976
|
+
await writeProviderBytes(provider, destination, data, "create");
|
|
3977
|
+
created.push(destination);
|
|
3978
|
+
}
|
|
3979
|
+
await writeProviderText(provider, imported.layout.markdownPath, imported.markdown, "create");
|
|
3980
|
+
created.push(imported.layout.markdownPath);
|
|
3981
|
+
await writeProviderBytes(provider, claim.path, sourceBytes, "upsert");
|
|
3982
|
+
return claim;
|
|
3983
|
+
} catch (error) {
|
|
3984
|
+
for (const path of created.reverse()) {
|
|
3985
|
+
await removeProviderEntry(provider, path).catch(() => void 0);
|
|
3986
|
+
}
|
|
3987
|
+
throw error;
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
async function readImportedMarkdown(file, ext, destPath, provider) {
|
|
3991
|
+
if (ext === ".md" || ext === ".txt") {
|
|
3992
|
+
return file.text();
|
|
3750
3993
|
}
|
|
3751
3994
|
const snapshot = await decodeDbkWorkspace(await file.arrayBuffer(), {
|
|
3752
3995
|
targetDocumentPath: destPath
|
|
@@ -3761,8 +4004,8 @@ async function writeDbkCompanions(snapshot, provider) {
|
|
|
3761
4004
|
await writeProviderText(provider, entry.path, entry.content, "create");
|
|
3762
4005
|
}
|
|
3763
4006
|
}
|
|
3764
|
-
var SUPPORTED_EXTENSIONS2 = /* @__PURE__ */ new Set([".md", ".txt",
|
|
3765
|
-
async function importDroppedFiles(files, provider
|
|
4007
|
+
var SUPPORTED_EXTENSIONS2 = /* @__PURE__ */ new Set([".md", ".txt", ...OUTSIDE_IN_EXTENSIONS, ".dbk", ".zip"]);
|
|
4008
|
+
async function importDroppedFiles(files, provider) {
|
|
3766
4009
|
const result = { imported: [], failed: [], unsupported: [] };
|
|
3767
4010
|
for (const file of files) {
|
|
3768
4011
|
const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
|
|
@@ -3773,9 +4016,14 @@ async function importDroppedFiles(files, provider, deps) {
|
|
|
3773
4016
|
const baseName = file.name.replace(/\.[^.]+$/, "");
|
|
3774
4017
|
let claimed = null;
|
|
3775
4018
|
try {
|
|
4019
|
+
if (OUTSIDE_IN_EXTENSIONS.has(ext)) {
|
|
4020
|
+
const imported = await importOutsideInFile(file, provider);
|
|
4021
|
+
result.imported.push({ source: file.name, ...imported });
|
|
4022
|
+
continue;
|
|
4023
|
+
}
|
|
3776
4024
|
const claim = await claimAvailablePath(provider, `${baseName}.md`);
|
|
3777
4025
|
claimed = claim.path;
|
|
3778
|
-
const markdown = await readImportedMarkdown(file, ext, claim.path, provider
|
|
4026
|
+
const markdown = await readImportedMarkdown(file, ext, claim.path, provider);
|
|
3779
4027
|
await writeProviderText(provider, claim.path, markdown);
|
|
3780
4028
|
result.imported.push({ source: file.name, path: claim.path, renamed: claim.renamed });
|
|
3781
4029
|
} catch (error) {
|
|
@@ -3807,7 +4055,7 @@ function summariseImport(result) {
|
|
|
3807
4055
|
if (renamed.length === 1) {
|
|
3808
4056
|
return {
|
|
3809
4057
|
kind: "success",
|
|
3810
|
-
message: `Imported as ${renamed[0].path} \u2014 a document
|
|
4058
|
+
message: `Imported as ${renamed[0].path} \u2014 a document with that name or companion folder already exists.`
|
|
3811
4059
|
};
|
|
3812
4060
|
}
|
|
3813
4061
|
if (renamed.length > 1) {
|
|
@@ -3819,6 +4067,241 @@ function summariseImport(result) {
|
|
|
3819
4067
|
return null;
|
|
3820
4068
|
}
|
|
3821
4069
|
|
|
4070
|
+
// src/DocBlocksShell/outside-in.ts
|
|
4071
|
+
import {
|
|
4072
|
+
FileSystemContentContainer,
|
|
4073
|
+
FsError as FsError4,
|
|
4074
|
+
getFileSystemProviderV2 as getFileSystemProviderV24,
|
|
4075
|
+
parseWorkspacePath as parseWorkspacePath5
|
|
4076
|
+
} from "@bendyline/docblocks/filesystem";
|
|
4077
|
+
import {
|
|
4078
|
+
createFileSystemDocumentTarget
|
|
4079
|
+
} from "@bendyline/docblocks/document";
|
|
4080
|
+
var OUTSIDE_IN_EXTENSION = /\.(?:html?|docx|pdf|pptx|xlsx)$/i;
|
|
4081
|
+
var SQUISQ_RUNTIME_DIRECTORY = "_squisq";
|
|
4082
|
+
var SQUISQ_RUNTIME_FILENAME = "squisq-player.js";
|
|
4083
|
+
function withoutLeadingSlash(path) {
|
|
4084
|
+
return parseWorkspacePath5(path);
|
|
4085
|
+
}
|
|
4086
|
+
function withLegacySlash(path, like) {
|
|
4087
|
+
const canonical = withoutLeadingSlash(path);
|
|
4088
|
+
return like.startsWith("/") && canonical ? `/${canonical}` : canonical;
|
|
4089
|
+
}
|
|
4090
|
+
function dirname(path) {
|
|
4091
|
+
const canonical = withoutLeadingSlash(path);
|
|
4092
|
+
const slash = canonical.lastIndexOf("/");
|
|
4093
|
+
return slash < 0 ? "" : canonical.slice(0, slash);
|
|
4094
|
+
}
|
|
4095
|
+
function join2(parent, child) {
|
|
4096
|
+
return parent ? `${parent}/${child}` : child;
|
|
4097
|
+
}
|
|
4098
|
+
function relativePath(fromDirectory, targetPath) {
|
|
4099
|
+
const from = withoutLeadingSlash(fromDirectory).split("/").filter(Boolean);
|
|
4100
|
+
const target = withoutLeadingSlash(targetPath).split("/").filter(Boolean);
|
|
4101
|
+
let shared = 0;
|
|
4102
|
+
while (shared < from.length && shared < target.length && from[shared] === target[shared]) {
|
|
4103
|
+
shared++;
|
|
4104
|
+
}
|
|
4105
|
+
const segments = [...from.slice(shared).map(() => ".."), ...target.slice(shared)];
|
|
4106
|
+
return segments.join("/") || ".";
|
|
4107
|
+
}
|
|
4108
|
+
async function readText(provider, path) {
|
|
4109
|
+
const v2 = getFileSystemProviderV24(provider);
|
|
4110
|
+
if (!v2) return provider.readFile(path);
|
|
4111
|
+
const current = await v2.readFile(parseWorkspacePath5(path));
|
|
4112
|
+
if (!current) return null;
|
|
4113
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(current.data);
|
|
4114
|
+
}
|
|
4115
|
+
async function readBytes(provider, path) {
|
|
4116
|
+
const v2 = getFileSystemProviderV24(provider);
|
|
4117
|
+
if (v2) return (await v2.readFile(parseWorkspacePath5(path)))?.data ?? null;
|
|
4118
|
+
return provider.readBinary(path);
|
|
4119
|
+
}
|
|
4120
|
+
async function writeBytes(provider, path, data, mode = "upsert") {
|
|
4121
|
+
const v2 = getFileSystemProviderV24(provider);
|
|
4122
|
+
if (v2) {
|
|
4123
|
+
await v2.writeFile(parseWorkspacePath5(path), data, {
|
|
4124
|
+
mode,
|
|
4125
|
+
createParents: true,
|
|
4126
|
+
expectedVersion: mode === "create" ? null : void 0
|
|
4127
|
+
});
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
if (mode === "create" && await provider.exists(path)) {
|
|
4131
|
+
throw new FsError4("already-exists", "File already exists.", { operation: "write", path });
|
|
4132
|
+
}
|
|
4133
|
+
await provider.writeBinary(path, data);
|
|
4134
|
+
}
|
|
4135
|
+
async function listCompanionFiles(provider, layout) {
|
|
4136
|
+
try {
|
|
4137
|
+
const entries = await provider.readDirectory(layout.companionDirectory);
|
|
4138
|
+
return entries.filter((entry) => entry.kind === "file").map((entry) => entry.path);
|
|
4139
|
+
} catch (error) {
|
|
4140
|
+
if (error instanceof FsError4 && error.code === "not-found") return [];
|
|
4141
|
+
throw error;
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
async function removeOutsideInCompanion(provider, layout) {
|
|
4145
|
+
const providerV2 = getFileSystemProviderV24(provider);
|
|
4146
|
+
if (providerV2) {
|
|
4147
|
+
const path = parseWorkspacePath5(layout.companionDirectory);
|
|
4148
|
+
if (await providerV2.stat(path) !== null) {
|
|
4149
|
+
await providerV2.remove(path, { recursive: true, missing: "ignore" });
|
|
4150
|
+
}
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
if (await provider.exists(layout.companionDirectory)) {
|
|
4154
|
+
await provider.delete(layout.companionDirectory);
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
async function persistImportedMedia(provider, layout, container) {
|
|
4158
|
+
const entries = await container.listFiles();
|
|
4159
|
+
for (const entry of entries) {
|
|
4160
|
+
if (/\.md$/i.test(entry.path)) continue;
|
|
4161
|
+
const data = await container.readFile(entry.path);
|
|
4162
|
+
if (!data) continue;
|
|
4163
|
+
await writeBytes(provider, join2(layout.companionDirectory, entry.path), data, "create");
|
|
4164
|
+
}
|
|
4165
|
+
}
|
|
4166
|
+
async function loadEditableShellDocument(provider, selectedPath) {
|
|
4167
|
+
if (!OUTSIDE_IN_EXTENSION.test(selectedPath)) {
|
|
4168
|
+
const content = await readText(provider, selectedPath);
|
|
4169
|
+
return content === null ? null : {
|
|
4170
|
+
displayPath: selectedPath,
|
|
4171
|
+
sourcePath: selectedPath,
|
|
4172
|
+
content,
|
|
4173
|
+
outsideIn: null,
|
|
4174
|
+
outsideInEditingEnabled: true
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
4177
|
+
const resolved = resolveOutsideInLayout(selectedPath);
|
|
4178
|
+
if (!resolved) return null;
|
|
4179
|
+
const layout = {
|
|
4180
|
+
...resolved,
|
|
4181
|
+
targetPath: withLegacySlash(resolved.targetPath, selectedPath),
|
|
4182
|
+
companionDirectory: withLegacySlash(resolved.companionDirectory, selectedPath),
|
|
4183
|
+
markdownPath: withLegacySlash(resolved.markdownPath, selectedPath)
|
|
4184
|
+
};
|
|
4185
|
+
const candidates = (await listCompanionFiles(provider, layout)).map(
|
|
4186
|
+
(path) => withLegacySlash(path, selectedPath)
|
|
4187
|
+
);
|
|
4188
|
+
const chosen = chooseOutsideInMarkdownPath(layout, candidates);
|
|
4189
|
+
if (chosen) {
|
|
4190
|
+
const content = await readText(provider, chosen);
|
|
4191
|
+
if (content === null) return null;
|
|
4192
|
+
const metadata = await readOutsideInMetadata(content);
|
|
4193
|
+
if (metadata && metadata.format !== layout.format) {
|
|
4194
|
+
throw new Error(
|
|
4195
|
+
`${chosen} is configured for ${metadata.format}, not ${layout.format}. Rename the rendered file back or repair the companion frontmatter.`
|
|
4196
|
+
);
|
|
4197
|
+
}
|
|
4198
|
+
const linkedContent = await withOutsideInMetadata(content, layout);
|
|
4199
|
+
if (linkedContent !== content) await writeProviderText(provider, chosen, linkedContent);
|
|
4200
|
+
return {
|
|
4201
|
+
displayPath: selectedPath,
|
|
4202
|
+
sourcePath: chosen,
|
|
4203
|
+
content: linkedContent,
|
|
4204
|
+
outsideIn: { ...layout, markdownPath: chosen },
|
|
4205
|
+
outsideInEditingEnabled: await isOutsideInMarkdownEditingEnabled(linkedContent)
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4208
|
+
const rendered = await readBytes(provider, selectedPath);
|
|
4209
|
+
if (!rendered) return null;
|
|
4210
|
+
const imported = await importOutsideInDocument({
|
|
4211
|
+
data: rendered,
|
|
4212
|
+
targetPath: selectedPath
|
|
4213
|
+
});
|
|
4214
|
+
const importedLayout = {
|
|
4215
|
+
...imported.layout,
|
|
4216
|
+
targetPath: withLegacySlash(imported.layout.targetPath, selectedPath),
|
|
4217
|
+
companionDirectory: withLegacySlash(imported.layout.companionDirectory, selectedPath),
|
|
4218
|
+
markdownPath: withLegacySlash(imported.layout.markdownPath, selectedPath)
|
|
4219
|
+
};
|
|
4220
|
+
await persistImportedMedia(provider, importedLayout, imported.container);
|
|
4221
|
+
await writeProviderText(provider, importedLayout.markdownPath, imported.markdown, "create");
|
|
4222
|
+
return {
|
|
4223
|
+
displayPath: selectedPath,
|
|
4224
|
+
sourcePath: importedLayout.markdownPath,
|
|
4225
|
+
content: imported.markdown,
|
|
4226
|
+
outsideIn: importedLayout,
|
|
4227
|
+
outsideInEditingEnabled: false
|
|
4228
|
+
};
|
|
4229
|
+
}
|
|
4230
|
+
async function enableOutsideInMarkdownEditing(provider, document2) {
|
|
4231
|
+
const layout = document2.outsideIn;
|
|
4232
|
+
if (!layout) throw new Error("This file does not support outside-in Markdown editing.");
|
|
4233
|
+
const original = await readBytes(provider, layout.targetPath);
|
|
4234
|
+
if (!original) throw new Error(`The rendered file "${layout.targetPath}" was not found.`);
|
|
4235
|
+
try {
|
|
4236
|
+
await writeBytes(provider, layout.backupPath, original, "create");
|
|
4237
|
+
} catch (error) {
|
|
4238
|
+
if (!(error instanceof FsError4 && error.code === "already-exists")) throw error;
|
|
4239
|
+
}
|
|
4240
|
+
const content = await withOutsideInMarkdownEditing(document2.content, layout);
|
|
4241
|
+
if (content !== document2.content) await writeProviderText(provider, document2.sourcePath, content);
|
|
4242
|
+
return { ...document2, content, outsideIn: layout, outsideInEditingEnabled: true };
|
|
4243
|
+
}
|
|
4244
|
+
async function findRuntimePath(provider, targetPath) {
|
|
4245
|
+
let directory = dirname(targetPath);
|
|
4246
|
+
for (; ; ) {
|
|
4247
|
+
const candidateDirectory = join2(directory, SQUISQ_RUNTIME_DIRECTORY);
|
|
4248
|
+
if (await providerEntryExists(provider, candidateDirectory)) {
|
|
4249
|
+
const providerV2 = getFileSystemProviderV24(provider);
|
|
4250
|
+
if (providerV2) {
|
|
4251
|
+
const entry = await providerV2.stat(parseWorkspacePath5(candidateDirectory));
|
|
4252
|
+
if (entry?.kind !== "directory") {
|
|
4253
|
+
throw new Error(`${candidateDirectory} must be a directory.`);
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
return join2(candidateDirectory, SQUISQ_RUNTIME_FILENAME);
|
|
4257
|
+
}
|
|
4258
|
+
if (!directory) break;
|
|
4259
|
+
directory = dirname(directory);
|
|
4260
|
+
}
|
|
4261
|
+
return join2(SQUISQ_RUNTIME_DIRECTORY, SQUISQ_RUNTIME_FILENAME);
|
|
4262
|
+
}
|
|
4263
|
+
async function writeRuntimeIfNeeded(provider, runtimePath) {
|
|
4264
|
+
const { PLAYER_BUNDLE } = await import("@bendyline/squisq-react/standalone-source");
|
|
4265
|
+
const current = await readText(provider, runtimePath);
|
|
4266
|
+
if (current !== PLAYER_BUNDLE) await writeProviderText(provider, runtimePath, PLAYER_BUNDLE);
|
|
4267
|
+
}
|
|
4268
|
+
function createOutsideInDocumentTarget(provider, layout, onCommitted) {
|
|
4269
|
+
const sourceTarget = createFileSystemDocumentTarget(provider, layout.markdownPath);
|
|
4270
|
+
return {
|
|
4271
|
+
key: `${provider.id}:outside-in:${parseWorkspacePath5(layout.targetPath)}`,
|
|
4272
|
+
async commit(request) {
|
|
4273
|
+
if (!await isOutsideInMarkdownEditingEnabled(request.content)) {
|
|
4274
|
+
throw new Error(
|
|
4275
|
+
"Outside-in editing is read-only until squisq-updatefrommarkdown: true is set."
|
|
4276
|
+
);
|
|
4277
|
+
}
|
|
4278
|
+
const runtimePath = layout.format === "html" ? await findRuntimePath(provider, layout.targetPath) : null;
|
|
4279
|
+
const outputDirectory = dirname(layout.targetPath);
|
|
4280
|
+
const rendered = await renderOutsideInDocument(
|
|
4281
|
+
{
|
|
4282
|
+
markdown: request.content,
|
|
4283
|
+
targetPath: layout.targetPath,
|
|
4284
|
+
container: new FileSystemContentContainer(provider, layout.companionDirectory)
|
|
4285
|
+
},
|
|
4286
|
+
runtimePath ? {
|
|
4287
|
+
html: {
|
|
4288
|
+
playerScriptPath: relativePath(outputDirectory, runtimePath),
|
|
4289
|
+
basePath: relativePath(outputDirectory, layout.companionDirectory)
|
|
4290
|
+
}
|
|
4291
|
+
} : {}
|
|
4292
|
+
);
|
|
4293
|
+
await sourceTarget.commit({ ...request, targetKey: sourceTarget.key });
|
|
4294
|
+
if (runtimePath) await writeRuntimeIfNeeded(provider, runtimePath);
|
|
4295
|
+
await writeBytes(provider, layout.targetPath, rendered.bytes);
|
|
4296
|
+
onCommitted?.();
|
|
4297
|
+
return {};
|
|
4298
|
+
}
|
|
4299
|
+
};
|
|
4300
|
+
}
|
|
4301
|
+
function createOutsideInContentContainer(provider, layout) {
|
|
4302
|
+
return new FileSystemContentContainer(provider, layout.companionDirectory);
|
|
4303
|
+
}
|
|
4304
|
+
|
|
3822
4305
|
// src/components/usePromptDialog.ts
|
|
3823
4306
|
import React2, { useCallback as useCallback11, useEffect as useEffect12, useRef as useRef11, useState as useState12 } from "react";
|
|
3824
4307
|
|
|
@@ -4232,9 +4715,9 @@ import { useCallback as useCallback14, useEffect as useEffect14, useRef as useRe
|
|
|
4232
4715
|
|
|
4233
4716
|
// src/DocBlocksShell/document-links.ts
|
|
4234
4717
|
import {
|
|
4235
|
-
FsError as
|
|
4236
|
-
getFileSystemProviderV2 as
|
|
4237
|
-
parseWorkspacePath as
|
|
4718
|
+
FsError as FsError5,
|
|
4719
|
+
getFileSystemProviderV2 as getFileSystemProviderV25,
|
|
4720
|
+
parseWorkspacePath as parseWorkspacePath6
|
|
4238
4721
|
} from "@bendyline/docblocks/filesystem";
|
|
4239
4722
|
var DEFAULT_DOCUMENT_DISCOVERY_LIMITS = Object.freeze({
|
|
4240
4723
|
entries: 1e4,
|
|
@@ -4257,9 +4740,9 @@ function positiveLimit(value, fallback, label) {
|
|
|
4257
4740
|
return selected;
|
|
4258
4741
|
}
|
|
4259
4742
|
async function readDirectory(provider, path) {
|
|
4260
|
-
const providerV2 =
|
|
4743
|
+
const providerV2 = getFileSystemProviderV25(provider);
|
|
4261
4744
|
if (!providerV2) return provider.readDirectory(path);
|
|
4262
|
-
const entries = await providerV2.readDirectory(
|
|
4745
|
+
const entries = await providerV2.readDirectory(parseWorkspacePath6(path));
|
|
4263
4746
|
return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
|
|
4264
4747
|
}
|
|
4265
4748
|
function cancellationError(signal) {
|
|
@@ -4326,7 +4809,7 @@ async function collectMarkdownFiles(provider, root, options = {}) {
|
|
|
4326
4809
|
while (pending.length > 0) {
|
|
4327
4810
|
if (options.signal?.aborted) throw cancellationError(options.signal);
|
|
4328
4811
|
const current = pending.pop();
|
|
4329
|
-
const canonical =
|
|
4812
|
+
const canonical = parseWorkspacePath6(current.path);
|
|
4330
4813
|
if (visited.has(canonical)) continue;
|
|
4331
4814
|
visited.add(canonical);
|
|
4332
4815
|
if (visited.size > maxDirectories) {
|
|
@@ -4339,7 +4822,7 @@ async function collectMarkdownFiles(provider, root, options = {}) {
|
|
|
4339
4822
|
try {
|
|
4340
4823
|
entries = await readWithBudget(provider, canonical, deadline, options.signal);
|
|
4341
4824
|
} catch (error) {
|
|
4342
|
-
if (error instanceof
|
|
4825
|
+
if (error instanceof FsError5 && error.code === "not-found") continue;
|
|
4343
4826
|
throw error;
|
|
4344
4827
|
}
|
|
4345
4828
|
entryCount += entries.length;
|
|
@@ -4368,7 +4851,7 @@ async function collectMarkdownFiles(provider, root, options = {}) {
|
|
|
4368
4851
|
}
|
|
4369
4852
|
return markdown;
|
|
4370
4853
|
}
|
|
4371
|
-
function
|
|
4854
|
+
function dirname2(path) {
|
|
4372
4855
|
const clean = path.replace(/^\/+/, "");
|
|
4373
4856
|
const index = clean.lastIndexOf("/");
|
|
4374
4857
|
return index < 0 ? "" : clean.slice(0, index);
|
|
@@ -4392,16 +4875,16 @@ function relativeMarkdownLink(fromFile, toFile) {
|
|
|
4392
4875
|
}
|
|
4393
4876
|
function createDocumentLinkCandidates(entries, selectedFile, query) {
|
|
4394
4877
|
const normalizedQuery = query.trim().toLowerCase();
|
|
4395
|
-
const selected =
|
|
4878
|
+
const selected = parseWorkspacePath6(selectedFile);
|
|
4396
4879
|
const candidates = [];
|
|
4397
4880
|
for (const entry of entries) {
|
|
4398
|
-
if (entry.kind !== "file" ||
|
|
4881
|
+
if (entry.kind !== "file" || parseWorkspacePath6(entry.path) === selected) continue;
|
|
4399
4882
|
const label = entry.name.replace(/\.md$/iu, "");
|
|
4400
4883
|
const path = relativeMarkdownLink(selectedFile, entry.path);
|
|
4401
4884
|
if (normalizedQuery && !label.toLowerCase().includes(normalizedQuery) && !path.toLowerCase().includes(normalizedQuery)) {
|
|
4402
4885
|
continue;
|
|
4403
4886
|
}
|
|
4404
|
-
const description =
|
|
4887
|
+
const description = dirname2(entry.path);
|
|
4405
4888
|
candidates.push(description ? { path, label, description } : { path, label });
|
|
4406
4889
|
}
|
|
4407
4890
|
candidates.sort((left, right) => left.label.localeCompare(right.label));
|
|
@@ -4465,14 +4948,14 @@ var WorkspaceAuthorityBarrier = class {
|
|
|
4465
4948
|
|
|
4466
4949
|
// src/DocBlocksShell/transient-workspace-move.ts
|
|
4467
4950
|
import {
|
|
4468
|
-
getFileSystemProviderV2 as
|
|
4951
|
+
getFileSystemProviderV2 as getFileSystemProviderV26
|
|
4469
4952
|
} from "@bendyline/docblocks/filesystem";
|
|
4470
4953
|
function pathDepth(path) {
|
|
4471
4954
|
return path ? path.split("/").length : 0;
|
|
4472
4955
|
}
|
|
4473
4956
|
async function copyTransientWorkspaceContents(source, destination) {
|
|
4474
|
-
const sourceV2 =
|
|
4475
|
-
const destinationV2 =
|
|
4957
|
+
const sourceV2 = getFileSystemProviderV26(source);
|
|
4958
|
+
const destinationV2 = getFileSystemProviderV26(destination);
|
|
4476
4959
|
if (!sourceV2 || !destinationV2) {
|
|
4477
4960
|
throw new Error("Moving a temporary document requires a current workspace provider.");
|
|
4478
4961
|
}
|
|
@@ -4534,6 +5017,21 @@ async function copyTransientWorkspaceContents(source, destination) {
|
|
|
4534
5017
|
}
|
|
4535
5018
|
}
|
|
4536
5019
|
|
|
5020
|
+
// src/DocBlocksShell/native-file-actions.ts
|
|
5021
|
+
function createNativeFileActions(entry, workspaceId, host) {
|
|
5022
|
+
if (entry.kind !== "file" || workspaceId === null || host === null) return [];
|
|
5023
|
+
return [
|
|
5024
|
+
{
|
|
5025
|
+
label: "Open containing folder",
|
|
5026
|
+
onSelect: () => host.shell.revealInFolder(workspaceId, entry.path)
|
|
5027
|
+
},
|
|
5028
|
+
{
|
|
5029
|
+
label: "Copy full path",
|
|
5030
|
+
onSelect: () => host.clipboard.writeWorkspacePath(workspaceId, entry.path)
|
|
5031
|
+
}
|
|
5032
|
+
];
|
|
5033
|
+
}
|
|
5034
|
+
|
|
4537
5035
|
// src/DocBlocksShell/welcome-document.ts
|
|
4538
5036
|
var WELCOME_DOCUMENT_PATH = "/aboutDocBlocks.md";
|
|
4539
5037
|
var WELCOME_DOCUMENT_CONTENT = [
|
|
@@ -4542,7 +5040,7 @@ var WELCOME_DOCUMENT_CONTENT = [
|
|
|
4542
5040
|
"squisq-cover-slide: false",
|
|
4543
5041
|
"---",
|
|
4544
5042
|
"",
|
|
4545
|
-
'# DocBlocks
|
|
5043
|
+
'# DocBlocks {[title subtitle="Local-first writing for pages, documents, slideshows, and video"]}',
|
|
4546
5044
|
"",
|
|
4547
5045
|
"Write in plain Markdown. Shape it visually. Keep it portable.",
|
|
4548
5046
|
"",
|
|
@@ -4756,7 +5254,7 @@ function stripExtension(name) {
|
|
|
4756
5254
|
return name.replace(/\.[^.]+$/, "");
|
|
4757
5255
|
}
|
|
4758
5256
|
function normaliseProviderPath(p) {
|
|
4759
|
-
const canonical =
|
|
5257
|
+
const canonical = parseWorkspacePath7(p);
|
|
4760
5258
|
return canonical ? `/${canonical}` : "/";
|
|
4761
5259
|
}
|
|
4762
5260
|
function sameProviderPath(a, b) {
|
|
@@ -4769,32 +5267,32 @@ function relocateProviderPath(path, oldPath, newPath) {
|
|
|
4769
5267
|
return `${newPath.replace(/\/+$/, "")}${current.slice(oldNormalised.length)}`;
|
|
4770
5268
|
}
|
|
4771
5269
|
function pathContains(parentPath2, candidatePath) {
|
|
4772
|
-
return workspacePathContains(
|
|
5270
|
+
return workspacePathContains(parseWorkspacePath7(parentPath2), parseWorkspacePath7(candidatePath));
|
|
4773
5271
|
}
|
|
4774
5272
|
async function readProviderDirectory2(provider, path) {
|
|
4775
|
-
const providerV2 =
|
|
5273
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
4776
5274
|
if (!providerV2) return provider.readDirectory(path);
|
|
4777
|
-
const entries = await providerV2.readDirectory(
|
|
5275
|
+
const entries = await providerV2.readDirectory(parseWorkspacePath7(path));
|
|
4778
5276
|
return entries.map((entry) => ({ kind: entry.kind, name: entry.name, path: entry.path }));
|
|
4779
5277
|
}
|
|
4780
5278
|
async function readProviderText(provider, path) {
|
|
4781
|
-
const providerV2 =
|
|
5279
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
4782
5280
|
if (!providerV2) return provider.readFile(path);
|
|
4783
|
-
const file = await providerV2.readFile(
|
|
4784
|
-
return file ? decodeUtf8Text(file.data, { label: "The document", path:
|
|
5281
|
+
const file = await providerV2.readFile(parseWorkspacePath7(path));
|
|
5282
|
+
return file ? decodeUtf8Text(file.data, { label: "The document", path: parseWorkspacePath7(path) }) : null;
|
|
4785
5283
|
}
|
|
4786
5284
|
async function pinnedProviderFileExists(provider, path) {
|
|
4787
|
-
const providerV2 =
|
|
5285
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
4788
5286
|
if (!providerV2) return provider.exists(path);
|
|
4789
|
-
const entry = await providerV2.stat(
|
|
5287
|
+
const entry = await providerV2.stat(parseWorkspacePath7(path));
|
|
4790
5288
|
if (!entry) return false;
|
|
4791
5289
|
if (entry.kind !== "file") throw new Error("The pinned path is no longer a document.");
|
|
4792
5290
|
return true;
|
|
4793
5291
|
}
|
|
4794
5292
|
async function removePinnedProviderFile(provider, path) {
|
|
4795
|
-
const providerV2 =
|
|
5293
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
4796
5294
|
if (providerV2) {
|
|
4797
|
-
const canonical =
|
|
5295
|
+
const canonical = parseWorkspacePath7(path);
|
|
4798
5296
|
const entry = await providerV2.stat(canonical);
|
|
4799
5297
|
if (!entry) return false;
|
|
4800
5298
|
if (entry.kind !== "file") throw new Error("The pinned path is no longer a document.");
|
|
@@ -4810,13 +5308,13 @@ async function removePinnedProviderFile(provider, path) {
|
|
|
4810
5308
|
return true;
|
|
4811
5309
|
}
|
|
4812
5310
|
async function readStableFileSnapshot(provider, path) {
|
|
4813
|
-
const providerV2 =
|
|
5311
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
4814
5312
|
if (providerV2) {
|
|
4815
|
-
const read = await providerV2.readFile(
|
|
5313
|
+
const read = await providerV2.readFile(parseWorkspacePath7(path));
|
|
4816
5314
|
return {
|
|
4817
5315
|
content: read ? decodeUtf8Text(read.data, {
|
|
4818
5316
|
label: "The document",
|
|
4819
|
-
path:
|
|
5317
|
+
path: parseWorkspacePath7(path)
|
|
4820
5318
|
}) : null,
|
|
4821
5319
|
version: read?.entry.version ?? null
|
|
4822
5320
|
};
|
|
@@ -4852,7 +5350,7 @@ async function ensureHandleWritePermission(handle) {
|
|
|
4852
5350
|
}
|
|
4853
5351
|
}
|
|
4854
5352
|
async function copyProviderToContainer(src, container, pathPrefix) {
|
|
4855
|
-
const providerV2 =
|
|
5353
|
+
const providerV2 = getFileSystemProviderV27(src);
|
|
4856
5354
|
if (providerV2) {
|
|
4857
5355
|
const snapshot = await providerV2.snapshot();
|
|
4858
5356
|
for (const entry of snapshot.entries) {
|
|
@@ -5099,7 +5597,7 @@ function DocBlocksShell({
|
|
|
5099
5597
|
const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] = useState15(null);
|
|
5100
5598
|
useEffect15(() => {
|
|
5101
5599
|
if (!provider || getTransientWorkspace(provider.id)) return;
|
|
5102
|
-
const providerV2 =
|
|
5600
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
5103
5601
|
return providerV2 ? retainFileSystemProvider(providerV2) : void 0;
|
|
5104
5602
|
}, [provider]);
|
|
5105
5603
|
const [descriptorRefreshKey, setDescriptorRefreshKey] = useState15(0);
|
|
@@ -5186,7 +5684,7 @@ function DocBlocksShell({
|
|
|
5186
5684
|
void scan();
|
|
5187
5685
|
const scanOnFocus = () => void scan();
|
|
5188
5686
|
window.addEventListener("focus", scanOnFocus);
|
|
5189
|
-
const providerV2 =
|
|
5687
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
5190
5688
|
const subscription = providerV2?.capabilities.watch ? providerV2.watch(
|
|
5191
5689
|
(event) => {
|
|
5192
5690
|
if (event.type !== "modified") void scan();
|
|
@@ -5224,8 +5722,8 @@ function DocBlocksShell({
|
|
|
5224
5722
|
cancelled = true;
|
|
5225
5723
|
};
|
|
5226
5724
|
}, [activeWorkspaceDescriptor]);
|
|
5227
|
-
const
|
|
5228
|
-
const git = useGit(provider,
|
|
5725
|
+
const nativeWorkspaceId = provider && activeWorkspaceDescriptor?.id === activeWorkspaceId && provider.id === activeWorkspaceId && activeWorkspaceDescriptor.type === "electron-native" ? activeWorkspaceDescriptor.id : null;
|
|
5726
|
+
const git = useGit(provider, nativeWorkspaceId, resolvedTheme);
|
|
5229
5727
|
const gitRef = useRef15(git);
|
|
5230
5728
|
gitRef.current = git;
|
|
5231
5729
|
const { scheduleRefresh: gitScheduleRefresh } = git;
|
|
@@ -5250,6 +5748,28 @@ function DocBlocksShell({
|
|
|
5250
5748
|
[activeWorkspaceDescriptor]
|
|
5251
5749
|
);
|
|
5252
5750
|
const [selectedFile, setSelectedFile] = useState15(null);
|
|
5751
|
+
const [selectedSourceFile, setSelectedSourceFile] = useState15(null);
|
|
5752
|
+
const [selectedOutsideIn, setSelectedOutsideIn] = useState15(null);
|
|
5753
|
+
const [selectedOutsideInEditingEnabled, setSelectedOutsideInEditingEnabled] = useState15(false);
|
|
5754
|
+
const adoptSelectedDocument = useCallback15((document2) => {
|
|
5755
|
+
setSelectedFile(document2?.displayPath ?? null);
|
|
5756
|
+
setSelectedSourceFile(document2?.sourcePath ?? null);
|
|
5757
|
+
setSelectedOutsideIn(document2?.outsideIn ?? null);
|
|
5758
|
+
setSelectedOutsideInEditingEnabled(document2?.outsideInEditingEnabled ?? false);
|
|
5759
|
+
}, []);
|
|
5760
|
+
const adoptRegularDocument = useCallback15((path) => {
|
|
5761
|
+
if (path === null) {
|
|
5762
|
+
setSelectedFile(null);
|
|
5763
|
+
setSelectedSourceFile(null);
|
|
5764
|
+
setSelectedOutsideIn(null);
|
|
5765
|
+
setSelectedOutsideInEditingEnabled(false);
|
|
5766
|
+
return;
|
|
5767
|
+
}
|
|
5768
|
+
setSelectedFile(path);
|
|
5769
|
+
setSelectedSourceFile(path);
|
|
5770
|
+
setSelectedOutsideIn(null);
|
|
5771
|
+
setSelectedOutsideInEditingEnabled(false);
|
|
5772
|
+
}, []);
|
|
5253
5773
|
useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath);
|
|
5254
5774
|
const exportDestinationAdapter = useMemo3(() => {
|
|
5255
5775
|
if (!selectedFile) return void 0;
|
|
@@ -5264,6 +5784,10 @@ function DocBlocksShell({
|
|
|
5264
5784
|
}
|
|
5265
5785
|
return createBrowserSaveAsAdapter();
|
|
5266
5786
|
}, [activeWorkspaceId, selectedFile]);
|
|
5787
|
+
const saveRenderedImageOutput = useMemo3(
|
|
5788
|
+
() => isElectronHost3() && exportDestinationAdapter ? createImageSaveOutput(exportDestinationAdapter) : void 0,
|
|
5789
|
+
[exportDestinationAdapter]
|
|
5790
|
+
);
|
|
5267
5791
|
const [selectedFolder, setSelectedFolder] = useState15(null);
|
|
5268
5792
|
const [folderEntries, setFolderEntries] = useState15([]);
|
|
5269
5793
|
const visibleFolderEntries = useMemo3(
|
|
@@ -5300,9 +5824,12 @@ function DocBlocksShell({
|
|
|
5300
5824
|
const preparedCloseRequestRef = useRef15(null);
|
|
5301
5825
|
const pendingDbkConflictsRef = useRef15(/* @__PURE__ */ new Map());
|
|
5302
5826
|
const createDocumentTarget = useCallback15(
|
|
5303
|
-
(fsProvider, workspaceId, filePath) => {
|
|
5827
|
+
(fsProvider, workspaceId, filePath, outsideIn = null) => {
|
|
5828
|
+
if (outsideIn) {
|
|
5829
|
+
return createOutsideInDocumentTarget(fsProvider, outsideIn, gitScheduleRefresh);
|
|
5830
|
+
}
|
|
5304
5831
|
const transient = getTransientWorkspace(workspaceId);
|
|
5305
|
-
const baseTarget =
|
|
5832
|
+
const baseTarget = createFileSystemDocumentTarget2(fsProvider, filePath);
|
|
5306
5833
|
const originKind = transient?.descriptor.origin?.kind;
|
|
5307
5834
|
const needsElectronHost = originKind === "loose-file" || originKind === "dbk";
|
|
5308
5835
|
if (!transient?.descriptor.origin || needsElectronHost && !isElectronHost3()) {
|
|
@@ -5506,52 +6033,58 @@ function DocBlocksShell({
|
|
|
5506
6033
|
try {
|
|
5507
6034
|
await touchWorkspace2(ws.id);
|
|
5508
6035
|
if (requestId !== navigationRequestRef.current) return null;
|
|
5509
|
-
let
|
|
5510
|
-
let openedContent = "";
|
|
6036
|
+
let openedDocument = null;
|
|
5511
6037
|
const transitioned = await documentSession.transitionWithLoad(async () => {
|
|
5512
6038
|
if (requestId !== navigationRequestRef.current) return null;
|
|
5513
6039
|
if (filePath) {
|
|
5514
|
-
const
|
|
6040
|
+
const document2 = await loadEditableShellDocument(fsProvider, filePath);
|
|
5515
6041
|
if (requestId !== navigationRequestRef.current) return null;
|
|
5516
|
-
if (
|
|
5517
|
-
openedFile = filePath;
|
|
5518
|
-
openedContent = content;
|
|
5519
|
-
}
|
|
6042
|
+
if (document2 !== null) openedDocument = document2;
|
|
5520
6043
|
}
|
|
5521
6044
|
return {
|
|
5522
|
-
target:
|
|
5523
|
-
|
|
6045
|
+
target: openedDocument ? createDocumentTarget(
|
|
6046
|
+
fsProvider,
|
|
6047
|
+
ws.id,
|
|
6048
|
+
openedDocument.sourcePath,
|
|
6049
|
+
openedDocument.outsideIn
|
|
6050
|
+
) : null,
|
|
6051
|
+
content: openedDocument?.content ?? ""
|
|
5524
6052
|
};
|
|
5525
6053
|
});
|
|
5526
6054
|
if (!transitioned) return null;
|
|
6055
|
+
const acceptedDocument = openedDocument;
|
|
5527
6056
|
setProvider(fsProvider);
|
|
5528
6057
|
adopted = true;
|
|
5529
6058
|
setActiveWorkspaceId(ws.id);
|
|
5530
|
-
if (
|
|
5531
|
-
|
|
6059
|
+
if (acceptedDocument) {
|
|
6060
|
+
adoptSelectedDocument(acceptedDocument);
|
|
5532
6061
|
setSelectedFolder(null);
|
|
5533
6062
|
setFolderEntries([]);
|
|
5534
6063
|
const effectiveView = view ?? "wysiwyg";
|
|
5535
6064
|
setInitialView(effectiveView);
|
|
5536
6065
|
setInitialSharedMode(sharedMode);
|
|
5537
6066
|
if (!transient) {
|
|
5538
|
-
saveLastState({
|
|
6067
|
+
saveLastState({
|
|
6068
|
+
workspaceId: wsId,
|
|
6069
|
+
filePath: acceptedDocument.displayPath,
|
|
6070
|
+
view: effectiveView
|
|
6071
|
+
});
|
|
5539
6072
|
}
|
|
5540
6073
|
} else {
|
|
5541
|
-
|
|
6074
|
+
adoptSelectedDocument(null);
|
|
5542
6075
|
setSelectedFolder(null);
|
|
5543
6076
|
setFolderEntries([]);
|
|
5544
6077
|
setInitialSharedMode(null);
|
|
5545
6078
|
}
|
|
5546
6079
|
if (push) {
|
|
5547
|
-
pushHash(ws.id,
|
|
6080
|
+
pushHash(ws.id, acceptedDocument?.displayPath ?? null);
|
|
5548
6081
|
}
|
|
5549
6082
|
return fsProvider;
|
|
5550
6083
|
} finally {
|
|
5551
|
-
if (!transient && !adopted) await
|
|
6084
|
+
if (!transient && !adopted) await getFileSystemProviderV27(fsProvider)?.dispose();
|
|
5552
6085
|
}
|
|
5553
6086
|
},
|
|
5554
|
-
[createDocumentTarget, documentSession, pushHash]
|
|
6087
|
+
[adoptSelectedDocument, createDocumentTarget, documentSession, pushHash]
|
|
5555
6088
|
);
|
|
5556
6089
|
const adoptTransientWorkspace = useCallback15(
|
|
5557
6090
|
async (options) => {
|
|
@@ -5594,7 +6127,7 @@ function DocBlocksShell({
|
|
|
5594
6127
|
});
|
|
5595
6128
|
mem.replaceContents(snapshot);
|
|
5596
6129
|
} catch (error) {
|
|
5597
|
-
await
|
|
6130
|
+
await getFileSystemProviderV27(mem)?.dispose();
|
|
5598
6131
|
throw error;
|
|
5599
6132
|
}
|
|
5600
6133
|
return adoptTransientWorkspace({
|
|
@@ -5622,7 +6155,7 @@ function DocBlocksShell({
|
|
|
5622
6155
|
if (content !== null && isCurrent()) {
|
|
5623
6156
|
await documentSession.transitionTo(createDocumentTarget(fs, fs.id, aboutPath), content);
|
|
5624
6157
|
if (!isCurrent()) return;
|
|
5625
|
-
|
|
6158
|
+
adoptRegularDocument(aboutPath);
|
|
5626
6159
|
setInitialView("preview");
|
|
5627
6160
|
setInitialSharedMode(null);
|
|
5628
6161
|
setExplorerKey((k) => k + 1);
|
|
@@ -5639,7 +6172,7 @@ function DocBlocksShell({
|
|
|
5639
6172
|
try {
|
|
5640
6173
|
await writeProviderText(fs, welcomePath, welcomeContent, "create");
|
|
5641
6174
|
} catch (error) {
|
|
5642
|
-
if (!(error instanceof
|
|
6175
|
+
if (!(error instanceof FsError6 && error.code === "already-exists")) throw error;
|
|
5643
6176
|
const existing = await readProviderText(fs, welcomePath);
|
|
5644
6177
|
if (existing === null) throw error;
|
|
5645
6178
|
seededContent = existing;
|
|
@@ -5650,7 +6183,7 @@ function DocBlocksShell({
|
|
|
5650
6183
|
seededContent
|
|
5651
6184
|
);
|
|
5652
6185
|
if (!isCurrent()) return;
|
|
5653
|
-
|
|
6186
|
+
adoptRegularDocument(welcomePath);
|
|
5654
6187
|
setInitialView("preview");
|
|
5655
6188
|
setInitialSharedMode(null);
|
|
5656
6189
|
setExplorerKey((k) => k + 1);
|
|
@@ -5658,7 +6191,7 @@ function DocBlocksShell({
|
|
|
5658
6191
|
saveLastState({ workspaceId: fs.id, filePath: welcomePath, view: "preview" });
|
|
5659
6192
|
if (!isWelcomeGatewayDismissed()) setShowWelcomeGateway(true);
|
|
5660
6193
|
},
|
|
5661
|
-
[createDocumentTarget, documentSession, pushHash]
|
|
6194
|
+
[adoptRegularDocument, createDocumentTarget, documentSession, pushHash]
|
|
5662
6195
|
);
|
|
5663
6196
|
const startupOpenFromIdsRef = useRef15(openFromIds);
|
|
5664
6197
|
startupOpenFromIdsRef.current = openFromIds;
|
|
@@ -5784,12 +6317,12 @@ function DocBlocksShell({
|
|
|
5784
6317
|
const p = await createElectronProviderFromWorkspace(ws);
|
|
5785
6318
|
if (!p) continue;
|
|
5786
6319
|
if (!isCurrent()) {
|
|
5787
|
-
await
|
|
6320
|
+
await getFileSystemProviderV27(p)?.dispose();
|
|
5788
6321
|
return;
|
|
5789
6322
|
}
|
|
5790
6323
|
await touchWorkspace2(ws.id);
|
|
5791
6324
|
if (!isCurrent()) {
|
|
5792
|
-
await
|
|
6325
|
+
await getFileSystemProviderV27(p)?.dispose();
|
|
5793
6326
|
return;
|
|
5794
6327
|
}
|
|
5795
6328
|
fsProvider = p;
|
|
@@ -5798,12 +6331,12 @@ function DocBlocksShell({
|
|
|
5798
6331
|
const restored = await (await loadNativeFileSystem()).restoreNativeFolder(ws.id);
|
|
5799
6332
|
if (restored) {
|
|
5800
6333
|
if (!isCurrent()) {
|
|
5801
|
-
await
|
|
6334
|
+
await getFileSystemProviderV27(restored)?.dispose();
|
|
5802
6335
|
return;
|
|
5803
6336
|
}
|
|
5804
6337
|
await touchWorkspace2(ws.id);
|
|
5805
6338
|
if (!isCurrent()) {
|
|
5806
|
-
await
|
|
6339
|
+
await getFileSystemProviderV27(restored)?.dispose();
|
|
5807
6340
|
return;
|
|
5808
6341
|
}
|
|
5809
6342
|
fsProvider = restored;
|
|
@@ -5812,12 +6345,12 @@ function DocBlocksShell({
|
|
|
5812
6345
|
} else {
|
|
5813
6346
|
const p = await createIndexedDbFileSystemProvider(ws.id, ws.name);
|
|
5814
6347
|
if (!isCurrent()) {
|
|
5815
|
-
await
|
|
6348
|
+
await getFileSystemProviderV27(p)?.dispose();
|
|
5816
6349
|
return;
|
|
5817
6350
|
}
|
|
5818
6351
|
await touchWorkspace2(ws.id);
|
|
5819
6352
|
if (!isCurrent()) {
|
|
5820
|
-
await
|
|
6353
|
+
await getFileSystemProviderV27(p)?.dispose();
|
|
5821
6354
|
return;
|
|
5822
6355
|
}
|
|
5823
6356
|
fsProvider = p;
|
|
@@ -5839,7 +6372,7 @@ function DocBlocksShell({
|
|
|
5839
6372
|
if (!isCurrent()) return;
|
|
5840
6373
|
const p = await createElectronFileSystemProvider(info.id, info.name, info.rootPath);
|
|
5841
6374
|
if (!isCurrent()) {
|
|
5842
|
-
await
|
|
6375
|
+
await getFileSystemProviderV27(p)?.dispose();
|
|
5843
6376
|
return;
|
|
5844
6377
|
}
|
|
5845
6378
|
fsProvider = p;
|
|
@@ -5848,7 +6381,7 @@ function DocBlocksShell({
|
|
|
5848
6381
|
if (!isCurrent()) return;
|
|
5849
6382
|
const p = await createIndexedDbFileSystemProvider(defaultWs.id, defaultWs.name);
|
|
5850
6383
|
if (!isCurrent()) {
|
|
5851
|
-
await
|
|
6384
|
+
await getFileSystemProviderV27(p)?.dispose();
|
|
5852
6385
|
return;
|
|
5853
6386
|
}
|
|
5854
6387
|
fsProvider = p;
|
|
@@ -5864,7 +6397,7 @@ function DocBlocksShell({
|
|
|
5864
6397
|
setActiveWorkspaceId(fsProvider.id);
|
|
5865
6398
|
adopted = true;
|
|
5866
6399
|
} finally {
|
|
5867
|
-
if (!adopted) await
|
|
6400
|
+
if (!adopted) await getFileSystemProviderV27(fsProvider)?.dispose();
|
|
5868
6401
|
}
|
|
5869
6402
|
})().catch(() => {
|
|
5870
6403
|
if (isCurrent()) {
|
|
@@ -6047,12 +6580,21 @@ function DocBlocksShell({
|
|
|
6047
6580
|
setVersionsContainer(null);
|
|
6048
6581
|
return;
|
|
6049
6582
|
}
|
|
6583
|
+
if (selectedOutsideIn) {
|
|
6584
|
+
const container2 = createOutsideInContentContainer(provider, selectedOutsideIn);
|
|
6585
|
+
const mp2 = createMediaProviderFromContainer(container2);
|
|
6586
|
+
mediaContainerRef.current = container2;
|
|
6587
|
+
versionsContainerRef.current = container2;
|
|
6588
|
+
setMediaProvider(mp2);
|
|
6589
|
+
setVersionsContainer(container2);
|
|
6590
|
+
return () => mp2.dispose();
|
|
6591
|
+
}
|
|
6050
6592
|
const parentDir = dirnameOf(selectedFile);
|
|
6051
6593
|
const base = basenameOf(selectedFile);
|
|
6052
6594
|
const baseNoExt = base.replace(/\.[^.]+$/, "");
|
|
6053
|
-
const container = new
|
|
6595
|
+
const container = new FileSystemContentContainer2(provider, parentDir);
|
|
6054
6596
|
const vPrefix = parentDir ? `${parentDir}/${baseNoExt}_files` : `${baseNoExt}_files`;
|
|
6055
|
-
const vContainer = new
|
|
6597
|
+
const vContainer = new FileSystemContentContainer2(provider, vPrefix);
|
|
6056
6598
|
const mp = createFileMediaProvider(container, base);
|
|
6057
6599
|
mediaContainerRef.current = container;
|
|
6058
6600
|
versionsContainerRef.current = vContainer;
|
|
@@ -6061,8 +6603,12 @@ function DocBlocksShell({
|
|
|
6061
6603
|
return () => {
|
|
6062
6604
|
mp.dispose();
|
|
6063
6605
|
};
|
|
6064
|
-
}, [provider, selectedFile, mediaEpoch]);
|
|
6065
|
-
const documentLinkProvider = useDocumentLinkProvider(
|
|
6606
|
+
}, [provider, selectedFile, selectedOutsideIn, mediaEpoch]);
|
|
6607
|
+
const documentLinkProvider = useDocumentLinkProvider(
|
|
6608
|
+
provider,
|
|
6609
|
+
selectedSourceFile ?? selectedFile,
|
|
6610
|
+
documentLinkEpoch
|
|
6611
|
+
);
|
|
6066
6612
|
useEffect15(() => {
|
|
6067
6613
|
const ref = versioningRef;
|
|
6068
6614
|
if (!ref) return;
|
|
@@ -6083,10 +6629,10 @@ function DocBlocksShell({
|
|
|
6083
6629
|
}, [versioningRef, effectiveVersioning, versionBasename, selectedFile, versionsContainer]);
|
|
6084
6630
|
useEffect15(() => {
|
|
6085
6631
|
if (!provider) return;
|
|
6086
|
-
const providerV2 =
|
|
6632
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
6087
6633
|
if (!providerV2?.capabilities.watch) return;
|
|
6088
|
-
|
|
6089
|
-
|
|
6634
|
+
const watchedFile = selectedSourceFile ?? selectedFile;
|
|
6635
|
+
if (!watchedFile || !documentSnapshot.targetKey) return;
|
|
6090
6636
|
const targetKey = documentSnapshot.targetKey;
|
|
6091
6637
|
let disposed = false;
|
|
6092
6638
|
let reading = false;
|
|
@@ -6140,7 +6686,7 @@ function DocBlocksShell({
|
|
|
6140
6686
|
disposed = true;
|
|
6141
6687
|
void subscription.dispose();
|
|
6142
6688
|
};
|
|
6143
|
-
}, [provider, selectedFile, documentSession, documentSnapshot.targetKey]);
|
|
6689
|
+
}, [provider, selectedFile, selectedSourceFile, documentSession, documentSnapshot.targetKey]);
|
|
6144
6690
|
const transitionAwayFromDocument = useCallback15(
|
|
6145
6691
|
async (requestId) => {
|
|
6146
6692
|
if (requestId !== navigationRequestRef.current) return false;
|
|
@@ -6173,7 +6719,7 @@ function DocBlocksShell({
|
|
|
6173
6719
|
}
|
|
6174
6720
|
const snapshot = await documentSession.resolveConflict("use-external");
|
|
6175
6721
|
if (!snapshot.targetKey) {
|
|
6176
|
-
|
|
6722
|
+
adoptSelectedDocument(null);
|
|
6177
6723
|
if (activeWorkspaceId) pushHash(activeWorkspaceId, null);
|
|
6178
6724
|
}
|
|
6179
6725
|
} catch (error) {
|
|
@@ -6182,7 +6728,7 @@ function DocBlocksShell({
|
|
|
6182
6728
|
message: error instanceof Error ? error.message : "Could not reload the external document."
|
|
6183
6729
|
});
|
|
6184
6730
|
}
|
|
6185
|
-
}, [activeWorkspaceId, documentSession, pushHash]);
|
|
6731
|
+
}, [activeWorkspaceId, adoptSelectedDocument, documentSession, pushHash]);
|
|
6186
6732
|
const handleKeepLocalDocument = useCallback15(async () => {
|
|
6187
6733
|
const conflictKey = documentSession.getSnapshot().conflict?.targetKey;
|
|
6188
6734
|
try {
|
|
@@ -6274,12 +6820,12 @@ function DocBlocksShell({
|
|
|
6274
6820
|
if (!await transitionAwayFromDocument(requestId)) return;
|
|
6275
6821
|
setProvider(nextProvider);
|
|
6276
6822
|
setActiveWorkspaceId(ws.id);
|
|
6277
|
-
|
|
6823
|
+
adoptSelectedDocument(null);
|
|
6278
6824
|
setSelectedFolder(null);
|
|
6279
6825
|
setFolderEntries([]);
|
|
6280
6826
|
pushHash(ws.id, null);
|
|
6281
6827
|
},
|
|
6282
|
-
[pushHash, transitionAwayFromDocument]
|
|
6828
|
+
[adoptSelectedDocument, pushHash, transitionAwayFromDocument]
|
|
6283
6829
|
);
|
|
6284
6830
|
const unpinDocument = useCallback15((document2) => {
|
|
6285
6831
|
const key = pinnedDocumentKey(document2);
|
|
@@ -6307,7 +6853,7 @@ function DocBlocksShell({
|
|
|
6307
6853
|
const handleTogglePin = useCallback15(
|
|
6308
6854
|
(path) => {
|
|
6309
6855
|
if (!activeWorkspaceId || !provider) return;
|
|
6310
|
-
const canonicalPath =
|
|
6856
|
+
const canonicalPath = parseWorkspacePath7(path);
|
|
6311
6857
|
if (!canonicalPath) return;
|
|
6312
6858
|
const document2 = {
|
|
6313
6859
|
workspaceId: activeWorkspaceId,
|
|
@@ -6397,17 +6943,23 @@ function DocBlocksShell({
|
|
|
6397
6943
|
return;
|
|
6398
6944
|
}
|
|
6399
6945
|
let disappearedDuringRead = false;
|
|
6946
|
+
let openedDocument = null;
|
|
6400
6947
|
const transitioned = await documentSession.transitionWithLoad(async () => {
|
|
6401
6948
|
if (requestId !== navigationRequestRef.current) return null;
|
|
6402
|
-
|
|
6403
|
-
if (
|
|
6949
|
+
openedDocument = await loadEditableShellDocument(openedProvider, document2.path);
|
|
6950
|
+
if (openedDocument === null) {
|
|
6404
6951
|
disappearedDuringRead = true;
|
|
6405
6952
|
return null;
|
|
6406
6953
|
}
|
|
6407
6954
|
if (requestId !== navigationRequestRef.current) return null;
|
|
6408
6955
|
return {
|
|
6409
|
-
target: createDocumentTarget(
|
|
6410
|
-
|
|
6956
|
+
target: createDocumentTarget(
|
|
6957
|
+
openedProvider,
|
|
6958
|
+
workspace.id,
|
|
6959
|
+
openedDocument.sourcePath,
|
|
6960
|
+
openedDocument.outsideIn
|
|
6961
|
+
),
|
|
6962
|
+
content: openedDocument.content
|
|
6411
6963
|
};
|
|
6412
6964
|
});
|
|
6413
6965
|
if (!transitioned) {
|
|
@@ -6419,7 +6971,8 @@ function DocBlocksShell({
|
|
|
6419
6971
|
setProvider(openedProvider);
|
|
6420
6972
|
setActiveWorkspaceId(workspace.id);
|
|
6421
6973
|
setActiveWorkspaceDescriptor(workspace);
|
|
6422
|
-
|
|
6974
|
+
if (!openedDocument) return;
|
|
6975
|
+
adoptSelectedDocument(openedDocument);
|
|
6423
6976
|
setSelectedFolder(null);
|
|
6424
6977
|
setFolderEntries([]);
|
|
6425
6978
|
setInitialView("wysiwyg");
|
|
@@ -6439,7 +6992,7 @@ function DocBlocksShell({
|
|
|
6439
6992
|
} finally {
|
|
6440
6993
|
if (nextProvider && ownsNextProvider && !adoptedNextProvider) {
|
|
6441
6994
|
try {
|
|
6442
|
-
await
|
|
6995
|
+
await getFileSystemProviderV27(nextProvider)?.dispose();
|
|
6443
6996
|
} catch {
|
|
6444
6997
|
}
|
|
6445
6998
|
}
|
|
@@ -6447,6 +7000,7 @@ function DocBlocksShell({
|
|
|
6447
7000
|
},
|
|
6448
7001
|
[
|
|
6449
7002
|
activeWorkspaceId,
|
|
7003
|
+
adoptSelectedDocument,
|
|
6450
7004
|
closeWelcomeGateway,
|
|
6451
7005
|
confirmMissingPinnedDocument,
|
|
6452
7006
|
createDocumentTarget,
|
|
@@ -6513,7 +7067,7 @@ function DocBlocksShell({
|
|
|
6513
7067
|
setProvider(openedDestination);
|
|
6514
7068
|
setActiveWorkspaceId(destination.id);
|
|
6515
7069
|
setActiveWorkspaceDescriptor(destination);
|
|
6516
|
-
|
|
7070
|
+
adoptRegularDocument(selectedFile);
|
|
6517
7071
|
setPinnedDocuments(
|
|
6518
7072
|
(current) => movePinnedDocumentsToWorkspace(current, activeWorkspaceId, {
|
|
6519
7073
|
workspaceId: destination.id,
|
|
@@ -6550,7 +7104,7 @@ function DocBlocksShell({
|
|
|
6550
7104
|
} catch (error) {
|
|
6551
7105
|
if (destinationProvider && !adoptedDestination) {
|
|
6552
7106
|
try {
|
|
6553
|
-
await
|
|
7107
|
+
await getFileSystemProviderV27(destinationProvider)?.dispose();
|
|
6554
7108
|
} catch {
|
|
6555
7109
|
}
|
|
6556
7110
|
}
|
|
@@ -6559,6 +7113,7 @@ function DocBlocksShell({
|
|
|
6559
7113
|
},
|
|
6560
7114
|
[
|
|
6561
7115
|
activeWorkspaceId,
|
|
7116
|
+
adoptRegularDocument,
|
|
6562
7117
|
closeWelcomeGateway,
|
|
6563
7118
|
createDocumentTarget,
|
|
6564
7119
|
documentSession,
|
|
@@ -6590,7 +7145,7 @@ function DocBlocksShell({
|
|
|
6590
7145
|
if (!await transitionAwayFromDocument(requestId)) return;
|
|
6591
7146
|
setProvider(provider2);
|
|
6592
7147
|
setActiveWorkspaceId(descriptor2.id);
|
|
6593
|
-
|
|
7148
|
+
adoptSelectedDocument(null);
|
|
6594
7149
|
setSelectedFolder(null);
|
|
6595
7150
|
setFolderEntries([]);
|
|
6596
7151
|
pushHash(descriptor2.id, null);
|
|
@@ -6609,13 +7164,13 @@ function DocBlocksShell({
|
|
|
6609
7164
|
if (!await transitionAwayFromDocument(requestId)) return;
|
|
6610
7165
|
setProvider(nativeProvider);
|
|
6611
7166
|
setActiveWorkspaceId(descriptor.id);
|
|
6612
|
-
|
|
7167
|
+
adoptSelectedDocument(null);
|
|
6613
7168
|
setSelectedFolder(null);
|
|
6614
7169
|
setFolderEntries([]);
|
|
6615
7170
|
pushHash(descriptor.id, null);
|
|
6616
7171
|
} catch {
|
|
6617
7172
|
}
|
|
6618
|
-
}, [pushHash, transitionAwayFromDocument]);
|
|
7173
|
+
}, [adoptSelectedDocument, pushHash, transitionAwayFromDocument]);
|
|
6619
7174
|
const handleWorkspaceCloned = useCallback15(
|
|
6620
7175
|
(info) => {
|
|
6621
7176
|
const requestId = ++navigationRequestRef.current;
|
|
@@ -6637,13 +7192,13 @@ function DocBlocksShell({
|
|
|
6637
7192
|
if (!await transitionAwayFromDocument(requestId)) return;
|
|
6638
7193
|
setProvider(cloneProvider);
|
|
6639
7194
|
setActiveWorkspaceId(descriptor.id);
|
|
6640
|
-
|
|
7195
|
+
adoptSelectedDocument(null);
|
|
6641
7196
|
setSelectedFolder(null);
|
|
6642
7197
|
setFolderEntries([]);
|
|
6643
7198
|
pushHash(descriptor.id, null);
|
|
6644
7199
|
})();
|
|
6645
7200
|
},
|
|
6646
|
-
[pushHash, transitionAwayFromDocument]
|
|
7201
|
+
[adoptSelectedDocument, pushHash, transitionAwayFromDocument]
|
|
6647
7202
|
);
|
|
6648
7203
|
const handleNewFile = useCallback15(async () => {
|
|
6649
7204
|
if (!provider) return;
|
|
@@ -6661,12 +7216,12 @@ function DocBlocksShell({
|
|
|
6661
7216
|
try {
|
|
6662
7217
|
const transitioned = await documentSession.transitionWithLoad(async () => {
|
|
6663
7218
|
if (requestId !== navigationRequestRef.current) return null;
|
|
6664
|
-
const providerV2 =
|
|
7219
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
6665
7220
|
if (providerV2) {
|
|
6666
7221
|
try {
|
|
6667
7222
|
await writeProviderText(provider, path, content, "create");
|
|
6668
7223
|
} catch (error) {
|
|
6669
|
-
if (error instanceof
|
|
7224
|
+
if (error instanceof FsError6 && error.code === "already-exists") {
|
|
6670
7225
|
throw new Error("A document with that name already exists.");
|
|
6671
7226
|
}
|
|
6672
7227
|
throw error;
|
|
@@ -6693,7 +7248,7 @@ function DocBlocksShell({
|
|
|
6693
7248
|
showToast("error", error instanceof Error ? error.message : "Could not create the document.");
|
|
6694
7249
|
return;
|
|
6695
7250
|
}
|
|
6696
|
-
|
|
7251
|
+
adoptRegularDocument(path);
|
|
6697
7252
|
setInitialView("wysiwyg");
|
|
6698
7253
|
setInitialSharedMode(null);
|
|
6699
7254
|
setExplorerKey((k) => k + 1);
|
|
@@ -6703,6 +7258,7 @@ function DocBlocksShell({
|
|
|
6703
7258
|
}
|
|
6704
7259
|
}, [
|
|
6705
7260
|
provider,
|
|
7261
|
+
adoptRegularDocument,
|
|
6706
7262
|
activeWorkspaceId,
|
|
6707
7263
|
pushHash,
|
|
6708
7264
|
closeWelcomeGateway,
|
|
@@ -6727,9 +7283,9 @@ function DocBlocksShell({
|
|
|
6727
7283
|
}
|
|
6728
7284
|
const path = `/${folderName}`;
|
|
6729
7285
|
try {
|
|
6730
|
-
const providerV2 =
|
|
7286
|
+
const providerV2 = getFileSystemProviderV27(provider);
|
|
6731
7287
|
if (providerV2) {
|
|
6732
|
-
await providerV2.createDirectory(
|
|
7288
|
+
await providerV2.createDirectory(parseWorkspacePath7(path), { mode: "create" });
|
|
6733
7289
|
} else {
|
|
6734
7290
|
if (await providerEntryExists(provider, path)) {
|
|
6735
7291
|
throw new Error("A file or folder with that name already exists.");
|
|
@@ -6737,11 +7293,11 @@ function DocBlocksShell({
|
|
|
6737
7293
|
await provider.createDirectory(path);
|
|
6738
7294
|
}
|
|
6739
7295
|
} catch (error) {
|
|
6740
|
-
const message = error instanceof
|
|
7296
|
+
const message = error instanceof FsError6 && error.code === "already-exists" ? "A file or folder with that name already exists." : error instanceof Error ? error.message : "Could not create the folder.";
|
|
6741
7297
|
showToast("error", message);
|
|
6742
7298
|
return;
|
|
6743
7299
|
}
|
|
6744
|
-
|
|
7300
|
+
adoptSelectedDocument(null);
|
|
6745
7301
|
setSelectedFolder(path);
|
|
6746
7302
|
setFolderEntries([]);
|
|
6747
7303
|
setExplorerKey((key) => key + 1);
|
|
@@ -6750,6 +7306,7 @@ function DocBlocksShell({
|
|
|
6750
7306
|
if (effectiveCompact) setMobileShowEditor(true);
|
|
6751
7307
|
}, [
|
|
6752
7308
|
provider,
|
|
7309
|
+
adoptSelectedDocument,
|
|
6753
7310
|
promptForText,
|
|
6754
7311
|
showToast,
|
|
6755
7312
|
closeWelcomeGateway,
|
|
@@ -6780,6 +7337,15 @@ function DocBlocksShell({
|
|
|
6780
7337
|
await getDocBlocksHost().shell.revealInFolder(ws.id);
|
|
6781
7338
|
}
|
|
6782
7339
|
}, [activeWorkspaceId]);
|
|
7340
|
+
const handleOpenWorkspaceFolder = useCallback15(() => {
|
|
7341
|
+
if (!isElectronHost3() || !nativeWorkspaceId) return;
|
|
7342
|
+
void getDocBlocksHost().shell.openWorkspaceFolder(nativeWorkspaceId).catch((error) => {
|
|
7343
|
+
showToast(
|
|
7344
|
+
"error",
|
|
7345
|
+
error instanceof Error ? error.message : "Could not open this workspace folder."
|
|
7346
|
+
);
|
|
7347
|
+
});
|
|
7348
|
+
}, [nativeWorkspaceId, showToast]);
|
|
6783
7349
|
useEffect15(() => {
|
|
6784
7350
|
if (!isElectronHost3()) return;
|
|
6785
7351
|
const host = getDocBlocksHost();
|
|
@@ -6851,22 +7417,27 @@ function DocBlocksShell({
|
|
|
6851
7417
|
if (kind === "directory") {
|
|
6852
7418
|
if (!await transitionAwayFromDocument(requestId)) return;
|
|
6853
7419
|
if (requestId !== navigationRequestRef.current) return;
|
|
6854
|
-
|
|
7420
|
+
adoptSelectedDocument(null);
|
|
6855
7421
|
setSelectedFolder(path);
|
|
6856
7422
|
const entries = await readProviderDirectory2(provider, path);
|
|
6857
7423
|
if (requestId !== navigationRequestRef.current) return;
|
|
6858
7424
|
setFolderEntries(entries);
|
|
6859
7425
|
pushHash(activeWorkspaceId, null);
|
|
6860
7426
|
} else {
|
|
6861
|
-
let
|
|
7427
|
+
let openedDocument = null;
|
|
6862
7428
|
try {
|
|
6863
7429
|
const transitioned = await documentSession.transitionWithLoad(async () => {
|
|
6864
7430
|
if (requestId !== navigationRequestRef.current) return null;
|
|
6865
|
-
|
|
6866
|
-
if (requestId !== navigationRequestRef.current ||
|
|
7431
|
+
openedDocument = await loadEditableShellDocument(provider, path);
|
|
7432
|
+
if (requestId !== navigationRequestRef.current || openedDocument === null) return null;
|
|
6867
7433
|
return {
|
|
6868
|
-
target: createDocumentTarget(
|
|
6869
|
-
|
|
7434
|
+
target: createDocumentTarget(
|
|
7435
|
+
provider,
|
|
7436
|
+
activeWorkspaceId,
|
|
7437
|
+
openedDocument.sourcePath,
|
|
7438
|
+
openedDocument.outsideIn
|
|
7439
|
+
),
|
|
7440
|
+
content: openedDocument.content
|
|
6870
7441
|
};
|
|
6871
7442
|
});
|
|
6872
7443
|
if (!transitioned) return;
|
|
@@ -6878,7 +7449,8 @@ function DocBlocksShell({
|
|
|
6878
7449
|
return;
|
|
6879
7450
|
}
|
|
6880
7451
|
if (requestId !== navigationRequestRef.current) return;
|
|
6881
|
-
|
|
7452
|
+
if (!openedDocument) return;
|
|
7453
|
+
adoptSelectedDocument(openedDocument);
|
|
6882
7454
|
setSelectedFolder(null);
|
|
6883
7455
|
setFolderEntries([]);
|
|
6884
7456
|
setInitialView("wysiwyg");
|
|
@@ -6895,15 +7467,91 @@ function DocBlocksShell({
|
|
|
6895
7467
|
pushHash,
|
|
6896
7468
|
effectiveCompact,
|
|
6897
7469
|
closeWelcomeGateway,
|
|
7470
|
+
adoptSelectedDocument,
|
|
6898
7471
|
createDocumentTarget,
|
|
6899
7472
|
documentSession,
|
|
6900
7473
|
transitionAwayFromDocument,
|
|
6901
7474
|
showToast
|
|
6902
7475
|
]
|
|
6903
7476
|
);
|
|
7477
|
+
const handleEnableOutsideInEditing = useCallback15(
|
|
7478
|
+
async (path) => {
|
|
7479
|
+
if (!provider || !activeWorkspaceId) {
|
|
7480
|
+
throw new Error("Open the workspace before enabling Markdown editing.");
|
|
7481
|
+
}
|
|
7482
|
+
const requestId = ++navigationRequestRef.current;
|
|
7483
|
+
const editableRef = { current: null };
|
|
7484
|
+
const transitioned = await documentSession.transitionWithLoad(async () => {
|
|
7485
|
+
if (requestId !== navigationRequestRef.current) return null;
|
|
7486
|
+
const opened = await loadEditableShellDocument(provider, path);
|
|
7487
|
+
if (!opened?.outsideIn) {
|
|
7488
|
+
throw new Error("This file does not support outside-in Markdown editing.");
|
|
7489
|
+
}
|
|
7490
|
+
const editable2 = await enableOutsideInMarkdownEditing(provider, opened);
|
|
7491
|
+
editableRef.current = editable2;
|
|
7492
|
+
return {
|
|
7493
|
+
target: createDocumentTarget(
|
|
7494
|
+
provider,
|
|
7495
|
+
activeWorkspaceId,
|
|
7496
|
+
editable2.sourcePath,
|
|
7497
|
+
editable2.outsideIn
|
|
7498
|
+
),
|
|
7499
|
+
content: editable2.content
|
|
7500
|
+
};
|
|
7501
|
+
});
|
|
7502
|
+
const editable = editableRef.current;
|
|
7503
|
+
if (!transitioned || !editable || requestId !== navigationRequestRef.current) return;
|
|
7504
|
+
adoptSelectedDocument(editable);
|
|
7505
|
+
setSelectedFolder(null);
|
|
7506
|
+
setFolderEntries([]);
|
|
7507
|
+
setInitialView("wysiwyg");
|
|
7508
|
+
setInitialSharedMode(null);
|
|
7509
|
+
closeWelcomeGateway();
|
|
7510
|
+
pushHash(activeWorkspaceId, path);
|
|
7511
|
+
saveLastState({ workspaceId: activeWorkspaceId, filePath: path, view: "wysiwyg" });
|
|
7512
|
+
if (effectiveCompact) setMobileShowEditor(true);
|
|
7513
|
+
showToast(
|
|
7514
|
+
"success",
|
|
7515
|
+
`Markdown editing enabled. The original is backed up at ${editable.outsideIn.backupPath}.`
|
|
7516
|
+
);
|
|
7517
|
+
},
|
|
7518
|
+
[
|
|
7519
|
+
activeWorkspaceId,
|
|
7520
|
+
adoptSelectedDocument,
|
|
7521
|
+
closeWelcomeGateway,
|
|
7522
|
+
createDocumentTarget,
|
|
7523
|
+
documentSession,
|
|
7524
|
+
effectiveCompact,
|
|
7525
|
+
provider,
|
|
7526
|
+
pushHash,
|
|
7527
|
+
showToast
|
|
7528
|
+
]
|
|
7529
|
+
);
|
|
7530
|
+
const outsideInActionsForEntry = useCallback15(
|
|
7531
|
+
(entry) => {
|
|
7532
|
+
if (entry.kind !== "file" || resolveOutsideInLayout(entry.path) === null) return [];
|
|
7533
|
+
if (selectedOutsideInEditingEnabled && selectedFile !== null && sameProviderPath(selectedFile, entry.path)) {
|
|
7534
|
+
return [];
|
|
7535
|
+
}
|
|
7536
|
+
return [
|
|
7537
|
+
{
|
|
7538
|
+
label: "Allow editing via markdown",
|
|
7539
|
+
onSelect: () => handleEnableOutsideInEditing(entry.path)
|
|
7540
|
+
}
|
|
7541
|
+
];
|
|
7542
|
+
},
|
|
7543
|
+
[handleEnableOutsideInEditing, selectedFile, selectedOutsideInEditingEnabled]
|
|
7544
|
+
);
|
|
7545
|
+
const actionsForEntry = useCallback15(
|
|
7546
|
+
(entry) => {
|
|
7547
|
+
const nativeActions = isElectronHost3() ? createNativeFileActions(entry, nativeWorkspaceId, getDocBlocksHost()) : [];
|
|
7548
|
+
return [...nativeActions, ...outsideInActionsForEntry(entry)];
|
|
7549
|
+
},
|
|
7550
|
+
[nativeWorkspaceId, outsideInActionsForEntry]
|
|
7551
|
+
);
|
|
6904
7552
|
const handleEditorLinkClick = useCallback15(
|
|
6905
7553
|
(href) => {
|
|
6906
|
-
const target = resolveShellEditorLinkTarget(href, selectedFile);
|
|
7554
|
+
const target = resolveShellEditorLinkTarget(href, selectedSourceFile ?? selectedFile);
|
|
6907
7555
|
if (!target) {
|
|
6908
7556
|
showToast(
|
|
6909
7557
|
"error",
|
|
@@ -6949,31 +7597,93 @@ function DocBlocksShell({
|
|
|
6949
7597
|
})();
|
|
6950
7598
|
return true;
|
|
6951
7599
|
},
|
|
6952
|
-
[handleSelect, provider, selectedFile, showToast]
|
|
7600
|
+
[handleSelect, provider, selectedFile, selectedSourceFile, showToast]
|
|
6953
7601
|
);
|
|
6954
7602
|
const handleTreeMutation = useCallback15(
|
|
6955
7603
|
async (change, mutate) => {
|
|
6956
|
-
if (!provider
|
|
7604
|
+
if (!provider) {
|
|
6957
7605
|
await mutate();
|
|
6958
7606
|
return;
|
|
6959
7607
|
}
|
|
7608
|
+
let mutateDocument = mutate;
|
|
7609
|
+
if (change.kind === "file") {
|
|
7610
|
+
const oldLayout = resolveOutsideInLayout(
|
|
7611
|
+
change.type === "move" ? change.oldPath : change.path
|
|
7612
|
+
);
|
|
7613
|
+
if (oldLayout && change.type === "move") {
|
|
7614
|
+
const nextLayout = resolveOutsideInLayout(change.newPath);
|
|
7615
|
+
if (!nextLayout || nextLayout.format !== oldLayout.format) {
|
|
7616
|
+
throw new Error(
|
|
7617
|
+
`Keep the .${oldLayout.format} extension when renaming this outside-in document.`
|
|
7618
|
+
);
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
if (oldLayout && change.type === "delete") {
|
|
7622
|
+
mutateDocument = async () => {
|
|
7623
|
+
await mutate();
|
|
7624
|
+
try {
|
|
7625
|
+
await removeOutsideInCompanion(provider, oldLayout);
|
|
7626
|
+
} catch (error) {
|
|
7627
|
+
showToast(
|
|
7628
|
+
"error",
|
|
7629
|
+
error instanceof Error ? `The rendered file was deleted, but its companion could not be removed: ${error.message}` : "The rendered file was deleted, but its companion could not be removed."
|
|
7630
|
+
);
|
|
7631
|
+
}
|
|
7632
|
+
};
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
if (!activeWorkspaceId || !selectedFile) {
|
|
7636
|
+
await mutateDocument();
|
|
7637
|
+
return;
|
|
7638
|
+
}
|
|
6960
7639
|
if (change.type === "move") {
|
|
6961
7640
|
const nextFile = relocateProviderPath(selectedFile, change.oldPath, change.newPath);
|
|
6962
7641
|
if (nextFile !== selectedFile) {
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
7642
|
+
const nextSource = relocateProviderPath(
|
|
7643
|
+
selectedSourceFile ?? selectedFile,
|
|
7644
|
+
change.oldPath,
|
|
7645
|
+
change.newPath
|
|
6966
7646
|
);
|
|
7647
|
+
let nextOutsideIn = null;
|
|
7648
|
+
if (selectedOutsideIn) {
|
|
7649
|
+
const resolved = resolveOutsideInLayout(nextFile);
|
|
7650
|
+
if (!resolved)
|
|
7651
|
+
throw new Error("The outside-in target must keep a supported extension.");
|
|
7652
|
+
nextOutsideIn = { ...resolved, markdownPath: nextSource };
|
|
7653
|
+
}
|
|
7654
|
+
const snapshot = await documentSession.retarget(
|
|
7655
|
+
createDocumentTarget(provider, activeWorkspaceId, nextSource, nextOutsideIn),
|
|
7656
|
+
mutateDocument
|
|
7657
|
+
);
|
|
7658
|
+
if (nextOutsideIn) {
|
|
7659
|
+
const linkedContent = await withOutsideInMetadata(snapshot.content, nextOutsideIn);
|
|
7660
|
+
if (linkedContent !== snapshot.content && snapshot.targetKey) {
|
|
7661
|
+
documentSession.edit(linkedContent, {
|
|
7662
|
+
targetKey: snapshot.targetKey,
|
|
7663
|
+
generation: snapshot.generation
|
|
7664
|
+
});
|
|
7665
|
+
await documentSession.flush("transition");
|
|
7666
|
+
}
|
|
7667
|
+
}
|
|
6967
7668
|
return;
|
|
6968
7669
|
}
|
|
6969
7670
|
}
|
|
6970
7671
|
if (change.type === "delete" && pathContains(change.path, selectedFile)) {
|
|
6971
|
-
await documentSession.delete(
|
|
7672
|
+
await documentSession.delete(mutateDocument);
|
|
6972
7673
|
return;
|
|
6973
7674
|
}
|
|
6974
|
-
await
|
|
7675
|
+
await mutateDocument();
|
|
6975
7676
|
},
|
|
6976
|
-
[
|
|
7677
|
+
[
|
|
7678
|
+
provider,
|
|
7679
|
+
activeWorkspaceId,
|
|
7680
|
+
selectedFile,
|
|
7681
|
+
selectedSourceFile,
|
|
7682
|
+
selectedOutsideIn,
|
|
7683
|
+
documentSession,
|
|
7684
|
+
createDocumentTarget,
|
|
7685
|
+
showToast
|
|
7686
|
+
]
|
|
6977
7687
|
);
|
|
6978
7688
|
const handleTreeChange = useCallback15(
|
|
6979
7689
|
async (change) => {
|
|
@@ -6988,7 +7698,12 @@ function DocBlocksShell({
|
|
|
6988
7698
|
const nextFile = selectedFile ? relocateProviderPath(selectedFile, change.oldPath, change.newPath) : null;
|
|
6989
7699
|
const nextFolder = selectedFolder ? relocateProviderPath(selectedFolder, change.oldPath, change.newPath) : null;
|
|
6990
7700
|
if (nextFile !== selectedFile) {
|
|
6991
|
-
|
|
7701
|
+
if (nextFile && selectedOutsideIn) {
|
|
7702
|
+
const opened = await loadEditableShellDocument(provider, nextFile);
|
|
7703
|
+
adoptSelectedDocument(opened);
|
|
7704
|
+
} else {
|
|
7705
|
+
adoptRegularDocument(nextFile);
|
|
7706
|
+
}
|
|
6992
7707
|
if (activeWorkspaceId) {
|
|
6993
7708
|
pushHash(activeWorkspaceId, nextFile);
|
|
6994
7709
|
if (nextFile) {
|
|
@@ -7014,7 +7729,7 @@ function DocBlocksShell({
|
|
|
7014
7729
|
if (selectedFile) {
|
|
7015
7730
|
const exists = await providerEntryExists(provider, selectedFile);
|
|
7016
7731
|
if (!exists) {
|
|
7017
|
-
|
|
7732
|
+
adoptSelectedDocument(null);
|
|
7018
7733
|
if (activeWorkspaceId) pushHash(activeWorkspaceId, null);
|
|
7019
7734
|
}
|
|
7020
7735
|
}
|
|
@@ -7025,7 +7740,10 @@ function DocBlocksShell({
|
|
|
7025
7740
|
},
|
|
7026
7741
|
[
|
|
7027
7742
|
provider,
|
|
7743
|
+
adoptRegularDocument,
|
|
7744
|
+
adoptSelectedDocument,
|
|
7028
7745
|
selectedFile,
|
|
7746
|
+
selectedOutsideIn,
|
|
7029
7747
|
selectedFolder,
|
|
7030
7748
|
activeWorkspaceId,
|
|
7031
7749
|
pinnedDocuments,
|
|
@@ -7097,7 +7815,7 @@ function DocBlocksShell({
|
|
|
7097
7815
|
const parent = dirnameOf(document2.path);
|
|
7098
7816
|
let newPath;
|
|
7099
7817
|
try {
|
|
7100
|
-
newPath =
|
|
7818
|
+
newPath = parseWorkspacePath7(parent ? `${parent}/${newName}` : newName);
|
|
7101
7819
|
} catch {
|
|
7102
7820
|
showToast("error", "Use a valid document name.");
|
|
7103
7821
|
return;
|
|
@@ -7139,7 +7857,7 @@ function DocBlocksShell({
|
|
|
7139
7857
|
} finally {
|
|
7140
7858
|
if (access.owned) {
|
|
7141
7859
|
try {
|
|
7142
|
-
await
|
|
7860
|
+
await getFileSystemProviderV27(access.provider)?.dispose();
|
|
7143
7861
|
} catch {
|
|
7144
7862
|
}
|
|
7145
7863
|
}
|
|
@@ -7208,7 +7926,7 @@ function DocBlocksShell({
|
|
|
7208
7926
|
} finally {
|
|
7209
7927
|
if (access.owned) {
|
|
7210
7928
|
try {
|
|
7211
|
-
await
|
|
7929
|
+
await getFileSystemProviderV27(access.provider)?.dispose();
|
|
7212
7930
|
} catch {
|
|
7213
7931
|
}
|
|
7214
7932
|
}
|
|
@@ -7226,42 +7944,15 @@ function DocBlocksShell({
|
|
|
7226
7944
|
showToast
|
|
7227
7945
|
]
|
|
7228
7946
|
);
|
|
7229
|
-
const persistImportedMedia = useCallback15(
|
|
7230
|
-
async (source, target, importedMarkdownPath) => {
|
|
7231
|
-
const parentDir = dirnameOf(importedMarkdownPath);
|
|
7232
|
-
const folder = basenameOf(importedMarkdownPath).replace(/\.[^.]+$/, "") + "_files";
|
|
7233
|
-
const mediaRoot = parentDir ? `${parentDir}/${folder}` : folder;
|
|
7234
|
-
const entries = await source.listFiles();
|
|
7235
|
-
const targetV2 = getFileSystemProviderV25(target);
|
|
7236
|
-
for (const entry of entries) {
|
|
7237
|
-
if (entry.path.endsWith(".md")) continue;
|
|
7238
|
-
const data = await source.readFile(entry.path);
|
|
7239
|
-
if (!data) continue;
|
|
7240
|
-
const cleanPath = entry.path.replace(/^\/+/, "");
|
|
7241
|
-
const destination = `${mediaRoot}/${cleanPath}`;
|
|
7242
|
-
if (targetV2) {
|
|
7243
|
-
await targetV2.writeFile(parseWorkspacePath5(destination), data, {
|
|
7244
|
-
mode: "upsert",
|
|
7245
|
-
createParents: true
|
|
7246
|
-
});
|
|
7247
|
-
} else {
|
|
7248
|
-
await target.writeBinary(destination, data);
|
|
7249
|
-
}
|
|
7250
|
-
}
|
|
7251
|
-
},
|
|
7252
|
-
[]
|
|
7253
|
-
);
|
|
7254
7947
|
const handleImportFiles = useCallback15(
|
|
7255
7948
|
async (files) => {
|
|
7256
7949
|
if (!provider) return;
|
|
7257
|
-
const result = await importDroppedFiles(files, provider
|
|
7258
|
-
persistMedia: (source, path) => persistImportedMedia(source, provider, path)
|
|
7259
|
-
});
|
|
7950
|
+
const result = await importDroppedFiles(files, provider);
|
|
7260
7951
|
setExplorerKey((k) => k + 1);
|
|
7261
7952
|
const summary = summariseImport(result);
|
|
7262
7953
|
if (summary) showToast(summary.kind, summary.message);
|
|
7263
7954
|
},
|
|
7264
|
-
[provider,
|
|
7955
|
+
[provider, showToast]
|
|
7265
7956
|
);
|
|
7266
7957
|
const handleEditorChange = useCallback15(
|
|
7267
7958
|
(source) => {
|
|
@@ -7308,7 +7999,7 @@ function DocBlocksShell({
|
|
|
7308
7999
|
throw error;
|
|
7309
8000
|
}
|
|
7310
8001
|
if (!isCurrent()) {
|
|
7311
|
-
await
|
|
8002
|
+
await getFileSystemProviderV27(mem)?.dispose();
|
|
7312
8003
|
await revokeAbandonedResource();
|
|
7313
8004
|
return;
|
|
7314
8005
|
}
|
|
@@ -7356,7 +8047,7 @@ function DocBlocksShell({
|
|
|
7356
8047
|
} finally {
|
|
7357
8048
|
try {
|
|
7358
8049
|
if (!adoptionStarted) {
|
|
7359
|
-
await
|
|
8050
|
+
await getFileSystemProviderV27(mem)?.dispose();
|
|
7360
8051
|
}
|
|
7361
8052
|
} finally {
|
|
7362
8053
|
if (!opened) await revokeAbandonedResource();
|
|
@@ -7426,7 +8117,7 @@ function DocBlocksShell({
|
|
|
7426
8117
|
push: true
|
|
7427
8118
|
});
|
|
7428
8119
|
} finally {
|
|
7429
|
-
if (!adoptionStarted) await
|
|
8120
|
+
if (!adoptionStarted) await getFileSystemProviderV27(mem)?.dispose();
|
|
7430
8121
|
}
|
|
7431
8122
|
},
|
|
7432
8123
|
[adoptTransientWorkspace]
|
|
@@ -7523,7 +8214,7 @@ function DocBlocksShell({
|
|
|
7523
8214
|
try {
|
|
7524
8215
|
await copyProviderToContainer(p, container, folder);
|
|
7525
8216
|
} finally {
|
|
7526
|
-
if (ownsProvider) await
|
|
8217
|
+
if (ownsProvider) await getFileSystemProviderV27(p)?.dispose();
|
|
7527
8218
|
}
|
|
7528
8219
|
}
|
|
7529
8220
|
if (usedFolders.size === 0) {
|
|
@@ -7657,7 +8348,7 @@ function DocBlocksShell({
|
|
|
7657
8348
|
const p = await createElectronFileSystemProvider(info.id, info.name, info.rootPath);
|
|
7658
8349
|
setProvider(p);
|
|
7659
8350
|
setActiveWorkspaceId(info.id);
|
|
7660
|
-
|
|
8351
|
+
adoptSelectedDocument(null);
|
|
7661
8352
|
setSelectedFolder(null);
|
|
7662
8353
|
setFolderEntries([]);
|
|
7663
8354
|
} else {
|
|
@@ -7665,12 +8356,13 @@ function DocBlocksShell({
|
|
|
7665
8356
|
const fsProvider = await createIndexedDbFileSystemProvider(defaultWs.id, defaultWs.name);
|
|
7666
8357
|
setProvider(fsProvider);
|
|
7667
8358
|
setActiveWorkspaceId(defaultWs.id);
|
|
7668
|
-
|
|
8359
|
+
adoptSelectedDocument(null);
|
|
7669
8360
|
setSelectedFolder(null);
|
|
7670
8361
|
setFolderEntries([]);
|
|
7671
8362
|
}
|
|
7672
8363
|
}, [
|
|
7673
8364
|
activeWorkspaceId,
|
|
8365
|
+
adoptSelectedDocument,
|
|
7674
8366
|
confirmAction,
|
|
7675
8367
|
handleWorkspaceSelect,
|
|
7676
8368
|
pinnedDocuments,
|
|
@@ -7807,6 +8499,8 @@ function DocBlocksShell({
|
|
|
7807
8499
|
onPinnedDocumentDelete: handlePinnedDocumentDelete,
|
|
7808
8500
|
onTogglePin: handleTogglePin,
|
|
7809
8501
|
onSelect: handleSelect,
|
|
8502
|
+
onOpenWorkspaceFolder: isElectronHost3() && nativeWorkspaceId ? handleOpenWorkspaceFolder : void 0,
|
|
8503
|
+
actionsForEntry,
|
|
7810
8504
|
onTreeMutation: handleTreeMutation,
|
|
7811
8505
|
onTreeChange: handleTreeChange,
|
|
7812
8506
|
onImportFiles: handleImportFiles,
|
|
@@ -7905,10 +8599,13 @@ function DocBlocksShell({
|
|
|
7905
8599
|
EditorShell,
|
|
7906
8600
|
{
|
|
7907
8601
|
initialMarkdown: editorContent,
|
|
8602
|
+
readOnly: selectedOutsideIn !== null && !selectedOutsideInEditingEnabled,
|
|
7908
8603
|
initialView,
|
|
7909
8604
|
defaultViewportPreset: defaultPreviewViewportPreset,
|
|
7910
8605
|
articleId: selectedFile,
|
|
7911
8606
|
fileName: selectedFile,
|
|
8607
|
+
saveCoverImageOutput: saveRenderedImageOutput,
|
|
8608
|
+
saveDashboardImageOutput: saveRenderedImageOutput,
|
|
7912
8609
|
onChange: handleEditorChange,
|
|
7913
8610
|
onLinkClick: handleEditorLinkClick,
|
|
7914
8611
|
colorScheme: resolvedTheme,
|
|
@@ -8079,6 +8776,9 @@ export {
|
|
|
8079
8776
|
WorkspacePicker,
|
|
8080
8777
|
WriteCanvasSettingsControls,
|
|
8081
8778
|
buildExportFilename,
|
|
8779
|
+
createImageSaveOutput as createCoverImageSaveOutput,
|
|
8780
|
+
createImageSaveOutput as createDashboardImageSaveOutput,
|
|
8781
|
+
createImageSaveOutput,
|
|
8082
8782
|
loadLastExportOptions,
|
|
8083
8783
|
resolveWriteCanvasFonts,
|
|
8084
8784
|
runExport,
|