@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/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-BI7NVU6T.js";
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, children);
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 requestMetadataRefresh = (path) => {
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 === "modified") {
344
- requestMetadataRefresh(event.path);
345
- return;
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([".txt", ".md", ".docx", ".pdf", ".dbk", ".zip"]);
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 FsError5,
2530
- getFileSystemProviderV2 as getFileSystemProviderV25,
2610
+ FsError as FsError6,
2611
+ getFileSystemProviderV2 as getFileSystemProviderV27,
2531
2612
  isQuotaExceededError,
2532
2613
  moveFileSystemEntry as moveFileSystemEntry2,
2533
- parseWorkspacePath as parseWorkspacePath5,
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-N3TCL5DD.js").then((module) => ({
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 { FsError as FsError3 } from "@bendyline/docblocks/filesystem";
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 readImportedMarkdown(file, ext, destPath, provider, deps) {
3734
- if (ext === ".md" || ext === ".txt") {
3735
- return file.text();
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 (ext === ".docx") {
3738
- const { docxToContainer } = await import("@bendyline/squisq-formats/docx");
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
- if (ext === ".pdf") {
3745
- const { pdfToContainer } = await import("@bendyline/squisq-formats/pdf");
3746
- const container = await pdfToContainer(await file.arrayBuffer());
3747
- const markdown = await container.readDocument() ?? "";
3748
- await deps.persistMedia(container, destPath);
3749
- return markdown;
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", ".docx", ".pdf", ".dbk", ".zip"]);
3765
- async function importDroppedFiles(files, provider, deps) {
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, deps);
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 named ${renamed[0].source.replace(/\.[^.]+$/, "")}.md already exists.`
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 FsError4,
4236
- getFileSystemProviderV2 as getFileSystemProviderV23,
4237
- parseWorkspacePath as parseWorkspacePath4
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 = getFileSystemProviderV23(provider);
4743
+ const providerV2 = getFileSystemProviderV25(provider);
4261
4744
  if (!providerV2) return provider.readDirectory(path);
4262
- const entries = await providerV2.readDirectory(parseWorkspacePath4(path));
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 = parseWorkspacePath4(current.path);
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 FsError4 && error.code === "not-found") continue;
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 dirname(path) {
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 = parseWorkspacePath4(selectedFile);
4878
+ const selected = parseWorkspacePath6(selectedFile);
4396
4879
  const candidates = [];
4397
4880
  for (const entry of entries) {
4398
- if (entry.kind !== "file" || parseWorkspacePath4(entry.path) === selected) continue;
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 = dirname(entry.path);
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 getFileSystemProviderV24
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 = getFileSystemProviderV24(source);
4475
- const destinationV2 = getFileSystemProviderV24(destination);
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: one Markdown file, many finished forms {[title subtitle="Local-first writing for pages, documents, slideshows, and video"]}',
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 = parseWorkspacePath5(p);
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(parseWorkspacePath5(parentPath2), parseWorkspacePath5(candidatePath));
5270
+ return workspacePathContains(parseWorkspacePath7(parentPath2), parseWorkspacePath7(candidatePath));
4773
5271
  }
4774
5272
  async function readProviderDirectory2(provider, path) {
4775
- const providerV2 = getFileSystemProviderV25(provider);
5273
+ const providerV2 = getFileSystemProviderV27(provider);
4776
5274
  if (!providerV2) return provider.readDirectory(path);
4777
- const entries = await providerV2.readDirectory(parseWorkspacePath5(path));
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 = getFileSystemProviderV25(provider);
5279
+ const providerV2 = getFileSystemProviderV27(provider);
4782
5280
  if (!providerV2) return provider.readFile(path);
4783
- const file = await providerV2.readFile(parseWorkspacePath5(path));
4784
- return file ? decodeUtf8Text(file.data, { label: "The document", path: parseWorkspacePath5(path) }) : null;
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 = getFileSystemProviderV25(provider);
5285
+ const providerV2 = getFileSystemProviderV27(provider);
4788
5286
  if (!providerV2) return provider.exists(path);
4789
- const entry = await providerV2.stat(parseWorkspacePath5(path));
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 = getFileSystemProviderV25(provider);
5293
+ const providerV2 = getFileSystemProviderV27(provider);
4796
5294
  if (providerV2) {
4797
- const canonical = parseWorkspacePath5(path);
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 = getFileSystemProviderV25(provider);
5311
+ const providerV2 = getFileSystemProviderV27(provider);
4814
5312
  if (providerV2) {
4815
- const read = await providerV2.readFile(parseWorkspacePath5(path));
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: parseWorkspacePath5(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 = getFileSystemProviderV25(src);
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 = getFileSystemProviderV25(provider);
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 = getFileSystemProviderV25(provider);
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 gitWorkspaceId = provider && activeWorkspaceDescriptor?.id === activeWorkspaceId && provider.id === activeWorkspaceId && activeWorkspaceDescriptor.type === "electron-native" ? activeWorkspaceDescriptor.id : null;
5228
- const git = useGit(provider, gitWorkspaceId, resolvedTheme);
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 = createFileSystemDocumentTarget(fsProvider, filePath);
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 openedFile = null;
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 content = await readProviderText(fsProvider, filePath);
6040
+ const document2 = await loadEditableShellDocument(fsProvider, filePath);
5515
6041
  if (requestId !== navigationRequestRef.current) return null;
5516
- if (content !== null) {
5517
- openedFile = filePath;
5518
- openedContent = content;
5519
- }
6042
+ if (document2 !== null) openedDocument = document2;
5520
6043
  }
5521
6044
  return {
5522
- target: openedFile ? createDocumentTarget(fsProvider, ws.id, openedFile) : null,
5523
- content: openedContent
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 (openedFile) {
5531
- setSelectedFile(openedFile);
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({ workspaceId: wsId, filePath: openedFile, view: effectiveView });
6067
+ saveLastState({
6068
+ workspaceId: wsId,
6069
+ filePath: acceptedDocument.displayPath,
6070
+ view: effectiveView
6071
+ });
5539
6072
  }
5540
6073
  } else {
5541
- setSelectedFile(null);
6074
+ adoptSelectedDocument(null);
5542
6075
  setSelectedFolder(null);
5543
6076
  setFolderEntries([]);
5544
6077
  setInitialSharedMode(null);
5545
6078
  }
5546
6079
  if (push) {
5547
- pushHash(ws.id, openedFile);
6080
+ pushHash(ws.id, acceptedDocument?.displayPath ?? null);
5548
6081
  }
5549
6082
  return fsProvider;
5550
6083
  } finally {
5551
- if (!transient && !adopted) await getFileSystemProviderV25(fsProvider)?.dispose();
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 getFileSystemProviderV25(mem)?.dispose();
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
- setSelectedFile(aboutPath);
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 FsError5 && error.code === "already-exists")) throw error;
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
- setSelectedFile(welcomePath);
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 getFileSystemProviderV25(p)?.dispose();
6320
+ await getFileSystemProviderV27(p)?.dispose();
5788
6321
  return;
5789
6322
  }
5790
6323
  await touchWorkspace2(ws.id);
5791
6324
  if (!isCurrent()) {
5792
- await getFileSystemProviderV25(p)?.dispose();
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 getFileSystemProviderV25(restored)?.dispose();
6334
+ await getFileSystemProviderV27(restored)?.dispose();
5802
6335
  return;
5803
6336
  }
5804
6337
  await touchWorkspace2(ws.id);
5805
6338
  if (!isCurrent()) {
5806
- await getFileSystemProviderV25(restored)?.dispose();
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 getFileSystemProviderV25(p)?.dispose();
6348
+ await getFileSystemProviderV27(p)?.dispose();
5816
6349
  return;
5817
6350
  }
5818
6351
  await touchWorkspace2(ws.id);
5819
6352
  if (!isCurrent()) {
5820
- await getFileSystemProviderV25(p)?.dispose();
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 getFileSystemProviderV25(p)?.dispose();
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 getFileSystemProviderV25(p)?.dispose();
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 getFileSystemProviderV25(fsProvider)?.dispose();
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 FileSystemContentContainer(provider, parentDir);
6595
+ const container = new FileSystemContentContainer2(provider, parentDir);
6054
6596
  const vPrefix = parentDir ? `${parentDir}/${baseNoExt}_files` : `${baseNoExt}_files`;
6055
- const vContainer = new FileSystemContentContainer(provider, vPrefix);
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(provider, selectedFile, documentLinkEpoch);
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 = getFileSystemProviderV25(provider);
6632
+ const providerV2 = getFileSystemProviderV27(provider);
6087
6633
  if (!providerV2?.capabilities.watch) return;
6088
- if (!selectedFile || !documentSnapshot.targetKey) return;
6089
- const watchedFile = selectedFile;
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
- setSelectedFile(null);
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
- setSelectedFile(null);
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 = parseWorkspacePath5(path);
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
- const content = await readProviderText(openedProvider, document2.path);
6403
- if (content === null) {
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(openedProvider, workspace.id, document2.path),
6410
- content
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
- setSelectedFile(document2.path);
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 getFileSystemProviderV25(nextProvider)?.dispose();
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
- setSelectedFile(selectedFile);
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 getFileSystemProviderV25(destinationProvider)?.dispose();
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
- setSelectedFile(null);
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
- setSelectedFile(null);
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
- setSelectedFile(null);
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 = getFileSystemProviderV25(provider);
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 FsError5 && error.code === "already-exists") {
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
- setSelectedFile(path);
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 = getFileSystemProviderV25(provider);
7286
+ const providerV2 = getFileSystemProviderV27(provider);
6731
7287
  if (providerV2) {
6732
- await providerV2.createDirectory(parseWorkspacePath5(path), { mode: "create" });
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 FsError5 && error.code === "already-exists" ? "A file or folder with that name already exists." : error instanceof Error ? error.message : "Could not create the folder.";
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
- setSelectedFile(null);
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
- setSelectedFile(null);
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 content = null;
7427
+ let openedDocument = null;
6862
7428
  try {
6863
7429
  const transitioned = await documentSession.transitionWithLoad(async () => {
6864
7430
  if (requestId !== navigationRequestRef.current) return null;
6865
- content = await readProviderText(provider, path);
6866
- if (requestId !== navigationRequestRef.current || content === null) return null;
7431
+ openedDocument = await loadEditableShellDocument(provider, path);
7432
+ if (requestId !== navigationRequestRef.current || openedDocument === null) return null;
6867
7433
  return {
6868
- target: createDocumentTarget(provider, activeWorkspaceId, path),
6869
- content
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
- setSelectedFile(path);
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 || !activeWorkspaceId || !selectedFile) {
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
- await documentSession.retarget(
6964
- createDocumentTarget(provider, activeWorkspaceId, nextFile),
6965
- mutate
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(mutate);
7672
+ await documentSession.delete(mutateDocument);
6972
7673
  return;
6973
7674
  }
6974
- await mutate();
7675
+ await mutateDocument();
6975
7676
  },
6976
- [provider, activeWorkspaceId, selectedFile, documentSession, createDocumentTarget]
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
- setSelectedFile(nextFile);
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
- setSelectedFile(null);
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 = parseWorkspacePath5(parent ? `${parent}/${newName}` : newName);
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 getFileSystemProviderV25(access.provider)?.dispose();
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 getFileSystemProviderV25(access.provider)?.dispose();
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, persistImportedMedia, showToast]
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 getFileSystemProviderV25(mem)?.dispose();
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 getFileSystemProviderV25(mem)?.dispose();
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 getFileSystemProviderV25(mem)?.dispose();
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 getFileSystemProviderV25(p)?.dispose();
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
- setSelectedFile(null);
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
- setSelectedFile(null);
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,