@bendyline/docblocks-react 2.4.0 → 2.6.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
@@ -1,66 +1,75 @@
1
+ import {
2
+ createBrowserSaveAsAdapter
3
+ } from "./chunk-UMEDHMT7.js";
1
4
  import {
2
5
  BADGE_GLYPHS,
3
6
  BADGE_LABELS,
4
- GitContext,
5
7
  buildBadgeMap,
6
8
  conflictedPaths,
7
- isFileDirty,
9
+ isFileDirty
10
+ } from "./chunk-V2ENBGUA.js";
11
+ import {
12
+ useMenuKeyboard
13
+ } from "./chunk-MRUK56JS.js";
14
+ import {
15
+ GitContext,
8
16
  useGitContext
9
- } from "./chunk-HFYPTTCH.js";
17
+ } from "./chunk-6E635KCJ.js";
10
18
  import {
11
19
  createImageSaveOutput,
12
20
  updateExportTargetExtension
13
21
  } from "./chunk-FNSRCCBC.js";
22
+ import {
23
+ runExport
24
+ } from "./chunk-ODYNOPC3.js";
25
+ import {
26
+ buildExportFilename
27
+ } from "./chunk-M5Y5WO7Z.js";
28
+ import {
29
+ createLocalProofingIgnoreStore
30
+ } from "./chunk-DUKTAKB4.js";
14
31
  import {
15
32
  AccentColorSettings,
16
33
  DB_CHROME_COLORS,
34
+ DEFAULT_PROOFING_PREFERENCES,
17
35
  DEFAULT_WRITE_CANVAS_FONT_SCHEME,
18
36
  DEFAULT_WRITE_CANVAS_PREFERENCES,
37
+ ProofingSettingsControls,
19
38
  SettingsDialog,
20
39
  ThemeSettings,
21
40
  WRITE_CANVAS_FONT_SCHEMES,
22
41
  WriteCanvasSettingsControls,
23
42
  loadAccentColor,
43
+ loadProofingPreferences,
24
44
  loadThemePreference,
25
45
  loadWriteCanvasPreferences,
26
46
  resolveWriteCanvasFonts,
27
47
  saveAccentColor,
48
+ saveProofingPreferences,
28
49
  saveThemePreference,
29
50
  saveWriteCanvasPreferences
30
- } from "./chunk-VRRL6VTD.js";
51
+ } from "./chunk-Q6XNIKDG.js";
31
52
  import {
32
53
  pickEmptyDocumentPrompt,
33
54
  useResponsivePreviewViewportPreset
34
55
  } from "./chunk-JDJVRDOP.js";
35
56
  import {
36
57
  ExportDialog
37
- } from "./chunk-SIIEGOHY.js";
38
- import {
39
- runExport
40
- } from "./chunk-ODYNOPC3.js";
41
- import {
42
- createBrowserSaveAsAdapter
43
- } from "./chunk-UMEDHMT7.js";
44
- import {
45
- useMenuKeyboard
46
- } from "./chunk-MRUK56JS.js";
58
+ } from "./chunk-FMDLZ4ZG.js";
47
59
  import "./chunk-YBEYTVU2.js";
48
- import {
49
- Dialog
50
- } from "./chunk-LG6HAWCK.js";
51
- import {
52
- buildExportFilename
53
- } from "./chunk-M5Y5WO7Z.js";
54
60
  import {
55
61
  DEFAULT_OPTIONS,
56
62
  loadLastExportOptions
57
63
  } from "./chunk-EBWYGTN7.js";
64
+ import {
65
+ Dialog
66
+ } from "./chunk-LG6HAWCK.js";
58
67
 
59
68
  // src/FileExplorer/FileExplorer.tsx
60
69
  import { useCallback as useCallback4, useEffect as useEffect4, useMemo, useRef as useRef4, useState as useState4 } from "react";
61
70
  import {
62
71
  isFileSystemMoveStateError,
63
- parseWorkspacePath as parseWorkspacePath2
72
+ parseWorkspacePath as parseWorkspacePath4
64
73
  } from "@bendyline/docblocks/filesystem";
65
74
 
66
75
  // src/FileExplorer/useFileTree.ts
@@ -98,6 +107,22 @@ function equivalentPathKeys(path) {
98
107
  function hasEquivalentPath(paths, path) {
99
108
  return equivalentPathKeys(path).some((candidate) => paths.has(candidate));
100
109
  }
110
+ function sameEntry(left, right) {
111
+ if (left.kind !== right.kind || left.name !== right.name || left.path !== right.path)
112
+ return false;
113
+ if (left.kind === "file" && right.kind === "file") {
114
+ return left.lastModified === right.lastModified;
115
+ }
116
+ return true;
117
+ }
118
+ function reconcileEntries(current, incoming) {
119
+ const currentByPath = new Map(current.map((entry) => [entry.path, entry]));
120
+ const reconciled = incoming.map((entry) => {
121
+ const previous = currentByPath.get(entry.path);
122
+ return previous && sameEntry(previous, entry) ? previous : entry;
123
+ });
124
+ return reconciled.length === current.length && reconciled.every((entry, index) => entry === current[index]) ? current : reconciled;
125
+ }
101
126
  async function readProviderDirectory(provider, path) {
102
127
  const canonical = parseWorkspacePath(path);
103
128
  const readOnce = async () => {
@@ -173,7 +198,7 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
173
198
  try {
174
199
  const root = await readProviderDirectory(sourceProvider, "");
175
200
  if (!isCurrent()) return;
176
- setEntries(root);
201
+ setEntries((current) => reconcileEntries(current, root));
177
202
  setRootIssue(null);
178
203
  } catch (caught) {
179
204
  if (isCurrent()) setRootIssue(readIssue(caught, ""));
@@ -192,8 +217,11 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
192
217
  const children = await readProviderDirectory(sourceProvider, dirPath);
193
218
  if (!isCurrent()) return;
194
219
  setChildEntries((prev) => {
220
+ const current = prev.get(canonical) ?? [];
221
+ const reconciled = reconcileEntries(current, children);
222
+ if (prev.has(canonical) && reconciled === current) return prev;
195
223
  const next = new Map(prev);
196
- next.set(canonical, children);
224
+ next.set(canonical, reconciled);
197
225
  return next;
198
226
  });
199
227
  setChildIssues((prev) => {
@@ -260,7 +288,7 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
260
288
  [loadChildren]
261
289
  );
262
290
  const refresh = useCallback(async () => {
263
- await loadRoot();
291
+ await loadRoot(false);
264
292
  const expandedPaths = [...expanded];
265
293
  for (const dirPath of expandedPaths) {
266
294
  await loadChildren(dirPath);
@@ -334,18 +362,21 @@ function useFileTree(provider, metadataRefreshKey, metadataRefreshPath) {
334
362
  directoriesToRefresh.clear();
335
363
  drainRefreshes();
336
364
  };
337
- const requestMetadataRefresh = (path) => {
365
+ const requestDirectoryRefresh = (path) => {
338
366
  if (disposed || fullRefreshRequested) return;
339
367
  directoriesToRefresh.add(workspacePathDirname(parseWorkspacePath(path)));
340
368
  drainRefreshes();
341
369
  };
342
370
  const subscription = providerV2.watch(
343
371
  (event) => {
344
- if (event.type === "modified") {
345
- requestMetadataRefresh(event.path);
346
- return;
372
+ if (event.type !== "overflow") {
373
+ requestDirectoryRefresh(event.path);
374
+ if (event.type === "moved" && event.destinationPath !== null) {
375
+ requestDirectoryRefresh(event.destinationPath);
376
+ }
377
+ } else {
378
+ requestRefresh();
347
379
  }
348
- requestRefresh();
349
380
  },
350
381
  {
351
382
  onError: (caught) => {
@@ -499,6 +530,9 @@ function NewFileIcon() {
499
530
  function NewFolderIcon() {
500
531
  return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-folder-plus" });
501
532
  }
533
+ function OpenFolderIcon() {
534
+ return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-folder-open" });
535
+ }
502
536
  function SortByNameIcon() {
503
537
  return /* @__PURE__ */ jsx(FontAwesomeIcon, { icon: "fa-solid fa-arrow-down-a-z" });
504
538
  }
@@ -1571,46 +1605,609 @@ function filterVisibleFileEntries(entries) {
1571
1605
  function compareText(left, right) {
1572
1606
  return left < right ? -1 : left > right ? 1 : 0;
1573
1607
  }
1574
- function modifiedTime(entry) {
1575
- if (entry.kind !== "file" || !entry.lastModified) return null;
1576
- const timestamp = Date.parse(entry.lastModified);
1577
- return Number.isNaN(timestamp) ? null : timestamp;
1608
+ function modifiedTime(entry) {
1609
+ if (entry.kind !== "file" || !entry.lastModified) return null;
1610
+ const timestamp = Date.parse(entry.lastModified);
1611
+ return Number.isNaN(timestamp) ? null : timestamp;
1612
+ }
1613
+ function sortFileEntries(entries, mode) {
1614
+ return [...entries].sort((left, right) => {
1615
+ if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
1616
+ if (left.kind === "directory" || right.kind === "directory" || mode === "name") {
1617
+ return compareText(left.name, right.name);
1618
+ }
1619
+ const leftTime = modifiedTime(left);
1620
+ const rightTime = modifiedTime(right);
1621
+ if (leftTime !== null && rightTime !== null && leftTime !== rightTime) {
1622
+ return rightTime - leftTime;
1623
+ }
1624
+ if (leftTime === null && rightTime !== null) return 1;
1625
+ if (leftTime !== null && rightTime === null) return -1;
1626
+ return compareText(left.name, right.name);
1627
+ });
1628
+ }
1629
+
1630
+ // src/DocBlocksShell/outside-in.ts
1631
+ import {
1632
+ FileSystemContentContainer,
1633
+ FsError as FsError3,
1634
+ getFileSystemProviderV2 as getFileSystemProviderV23,
1635
+ parseWorkspacePath as parseWorkspacePath3
1636
+ } from "@bendyline/docblocks/filesystem";
1637
+ import {
1638
+ createFileSystemDocumentTarget
1639
+ } from "@bendyline/docblocks/document";
1640
+
1641
+ // src/DocBlocksShell/provider-io.ts
1642
+ import {
1643
+ FsError as FsError2,
1644
+ getFileSystemProviderV2 as getFileSystemProviderV22,
1645
+ parseWorkspacePath as parseWorkspacePath2
1646
+ } from "@bendyline/docblocks/filesystem";
1647
+ async function providerEntryExists(provider, path) {
1648
+ const providerV2 = getFileSystemProviderV22(provider);
1649
+ return providerV2 ? await providerV2.stat(parseWorkspacePath2(path)) !== null : provider.exists(path);
1650
+ }
1651
+ async function writeProviderText(provider, path, content, mode = "upsert") {
1652
+ const providerV2 = getFileSystemProviderV22(provider);
1653
+ if (providerV2) {
1654
+ await providerV2.writeFile(parseWorkspacePath2(path), new TextEncoder().encode(content), {
1655
+ mode,
1656
+ createParents: true,
1657
+ expectedVersion: mode === "create" ? null : void 0
1658
+ });
1659
+ return;
1660
+ }
1661
+ if (mode === "create" && await provider.exists(path)) {
1662
+ throw new FsError2("already-exists", "File already exists.", { operation: "write", path });
1663
+ }
1664
+ await provider.writeFile(path, content);
1665
+ }
1666
+ async function removeProviderEntry(provider, path) {
1667
+ const providerV2 = getFileSystemProviderV22(provider);
1668
+ if (providerV2) {
1669
+ await providerV2.remove(parseWorkspacePath2(path), { recursive: true, missing: "ignore" });
1670
+ return;
1671
+ }
1672
+ await provider.delete(path);
1673
+ }
1674
+
1675
+ // src/DocBlocksShell/outside-in-contract.ts
1676
+ var OUTSIDE_IN_FORMAT_IDS = ["html", "docx", "pdf", "pptx", "xlsx", "csv"];
1677
+ var FORMAT_IDS = new Set(OUTSIDE_IN_FORMAT_IDS);
1678
+ var UPDATE_FROM_MARKDOWN_KEY = "squisq-updatefrommarkdown";
1679
+ var HTML_OUTPUT_KEY = "squisq-html-output";
1680
+ async function loadOutsideInModule() {
1681
+ return await import("@bendyline/squisq-formats/outside-in");
1682
+ }
1683
+ function normalizePath(path) {
1684
+ const leading = path.replace(/\\/g, "/").startsWith("/") ? "/" : "";
1685
+ const parts = path.replace(/\\/g, "/").split("/").filter(Boolean);
1686
+ if (parts.some((part) => part === "." || part === "..")) {
1687
+ throw new Error(`Outside-in paths must be canonical workspace paths: ${path}`);
1688
+ }
1689
+ return leading + parts.join("/");
1690
+ }
1691
+ function join(parent, child) {
1692
+ if (!parent || parent === "/") return parent === "/" ? `/${child}` : child;
1693
+ return `${parent}/${child}`;
1694
+ }
1695
+ function slug(stem) {
1696
+ return stem.normalize("NFKD").replace(/\p{Mark}+/gu, "").toLocaleLowerCase("en-US").replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-+|-+$/g, "") || "document";
1697
+ }
1698
+ function resolveOutsideInLayout(path) {
1699
+ const targetPath = normalizePath(path);
1700
+ const slash = targetPath.lastIndexOf("/");
1701
+ const parentDirectory = slash < 0 ? "" : slash === 0 ? "/" : targetPath.slice(0, slash);
1702
+ const filename = slash < 0 ? targetPath : targetPath.slice(slash + 1);
1703
+ const dot = filename.lastIndexOf(".");
1704
+ if (dot <= 0) return null;
1705
+ const rawFormat = filename.slice(dot + 1).toLowerCase();
1706
+ const format = rawFormat === "htm" ? "html" : rawFormat;
1707
+ if (!FORMAT_IDS.has(format)) return null;
1708
+ const stem = filename.slice(0, dot);
1709
+ const companionName = `${stem}_files`;
1710
+ const companionDirectory = join(parentDirectory, companionName);
1711
+ const markdownFilename = `${slug(stem)}.md`;
1712
+ const backupDirectory = join(companionDirectory, ".original");
1713
+ const backupFilename = `original.${format}`;
1714
+ return {
1715
+ targetPath,
1716
+ format,
1717
+ parentDirectory,
1718
+ stem,
1719
+ companionName,
1720
+ companionDirectory,
1721
+ markdownFilename,
1722
+ markdownPath: join(companionDirectory, markdownFilename),
1723
+ relativeTargetPath: `../${filename}`,
1724
+ backupDirectory,
1725
+ backupFilename,
1726
+ backupPath: join(backupDirectory, backupFilename)
1727
+ };
1728
+ }
1729
+ function chooseOutsideInMarkdownPath(layout, paths) {
1730
+ const canonical = normalizePath(layout.markdownPath);
1731
+ const normalized = paths.map(normalizePath);
1732
+ const exact = normalized.find((path) => path === canonical);
1733
+ if (exact) return exact;
1734
+ const folded = normalized.find(
1735
+ (path) => path.toLocaleLowerCase("en-US") === canonical.toLocaleLowerCase("en-US")
1736
+ );
1737
+ if (folded) return folded;
1738
+ const prefix = `${normalizePath(layout.companionDirectory).replace(/\/$/, "")}/`;
1739
+ const markdown = normalized.filter(
1740
+ (path) => path.startsWith(prefix) && !path.slice(prefix.length).includes("/") && path.toLocaleLowerCase("en-US").endsWith(".md")
1741
+ );
1742
+ return markdown.length === 1 ? markdown[0] : null;
1743
+ }
1744
+ async function readOutsideInMetadata(source) {
1745
+ const { readOutsideInMetadata: readMetadata } = await loadOutsideInModule();
1746
+ const metadata = readMetadata(source);
1747
+ return metadata ? {
1748
+ ...metadata,
1749
+ updateFromMarkdown: await isOutsideInMarkdownEditingEnabled(source)
1750
+ } : null;
1751
+ }
1752
+ async function withOutsideInMetadata(source, layout) {
1753
+ const { withOutsideInMetadata: addMetadata } = await loadOutsideInModule();
1754
+ return addMetadata(source, layout);
1755
+ }
1756
+ async function isOutsideInMarkdownEditingEnabled(source) {
1757
+ const module = await loadOutsideInModule();
1758
+ if (module.isOutsideInMarkdownEditingEnabled) {
1759
+ return module.isOutsideInMarkdownEditingEnabled(source);
1760
+ }
1761
+ const { parseFrontmatter, splitFrontmatterBlock } = await import("@bendyline/squisq/markdown");
1762
+ const block = splitFrontmatterBlock(source).frontmatter;
1763
+ if (!block) return false;
1764
+ const firstBreak = block.indexOf("\n");
1765
+ if (firstBreak < 0) return false;
1766
+ const yaml = block.slice(firstBreak + 1).replace(/\r?\n---(?:\r?\n)?$/, "");
1767
+ return parseFrontmatter(yaml)?.[UPDATE_FROM_MARKDOWN_KEY] === true;
1768
+ }
1769
+ async function withOutsideInMarkdownEditing(source, layout, enabled = true) {
1770
+ const module = await loadOutsideInModule();
1771
+ if (module.withOutsideInMarkdownEditing) {
1772
+ return module.withOutsideInMarkdownEditing(source, layout, enabled);
1773
+ }
1774
+ const { setFrontmatterValues } = await import("@bendyline/squisq/markdown");
1775
+ return setFrontmatterValues(await withOutsideInMetadata(source, layout), {
1776
+ [UPDATE_FROM_MARKDOWN_KEY]: enabled
1777
+ });
1778
+ }
1779
+ async function readOutsideInHtmlOutput(source) {
1780
+ const { parseFrontmatter, splitFrontmatterBlock } = await import("@bendyline/squisq/markdown");
1781
+ const block = splitFrontmatterBlock(source).frontmatter;
1782
+ if (!block) return null;
1783
+ const firstBreak = block.indexOf("\n");
1784
+ if (firstBreak < 0) return null;
1785
+ const yaml = block.slice(firstBreak + 1).replace(/\r?\n---(?:\r?\n)?$/, "");
1786
+ const value = parseFrontmatter(yaml)?.[HTML_OUTPUT_KEY];
1787
+ return value === "interactive" || value === "static" ? value : null;
1788
+ }
1789
+ async function withOutsideInHtmlOutput(source, output) {
1790
+ const { setFrontmatterValues } = await import("@bendyline/squisq/markdown");
1791
+ return setFrontmatterValues(source, { [HTML_OUTPUT_KEY]: output });
1792
+ }
1793
+ async function importOutsideInDocument(source, options = {}) {
1794
+ const { importOutsideInDocument: importDocument } = await loadOutsideInModule();
1795
+ const imported = await importDocument(source, options);
1796
+ const layout = resolveOutsideInLayout(source.targetPath);
1797
+ if (!layout) throw new Error(`Outside-in editing does not support "${source.targetPath}".`);
1798
+ return { ...imported, layout };
1799
+ }
1800
+ async function renderOutsideInDocument(source, options = {}) {
1801
+ const { renderOutsideInDocument: renderDocument } = await loadOutsideInModule();
1802
+ return renderDocument(source, options);
1803
+ }
1804
+
1805
+ // src/DocBlocksShell/outside-in.ts
1806
+ var OUTSIDE_IN_EXTENSION = /\.(?:html?|docx|pdf|pptx|xlsx|csv)$/i;
1807
+ var IMAGE_MIME_TYPES = {
1808
+ avif: "image/avif",
1809
+ bmp: "image/bmp",
1810
+ gif: "image/gif",
1811
+ ico: "image/x-icon",
1812
+ jpeg: "image/jpeg",
1813
+ jpg: "image/jpeg",
1814
+ png: "image/png",
1815
+ webp: "image/webp"
1816
+ };
1817
+ var SQUISQ_RUNTIME_DIRECTORY = "_squisq";
1818
+ var SQUISQ_RUNTIME_FILENAME = "squisq-player.js";
1819
+ function withoutLeadingSlash(path) {
1820
+ return parseWorkspacePath3(path);
1821
+ }
1822
+ function withLegacySlash(path, like) {
1823
+ const canonical = withoutLeadingSlash(path);
1824
+ return like.startsWith("/") && canonical ? `/${canonical}` : canonical;
1825
+ }
1826
+ function dirname(path) {
1827
+ const canonical = withoutLeadingSlash(path);
1828
+ const slash = canonical.lastIndexOf("/");
1829
+ return slash < 0 ? "" : canonical.slice(0, slash);
1830
+ }
1831
+ function join2(parent, child) {
1832
+ return parent ? `${parent}/${child}` : child;
1833
+ }
1834
+ function relativePath(fromDirectory, targetPath) {
1835
+ const from = withoutLeadingSlash(fromDirectory).split("/").filter(Boolean);
1836
+ const target = withoutLeadingSlash(targetPath).split("/").filter(Boolean);
1837
+ let shared = 0;
1838
+ while (shared < from.length && shared < target.length && from[shared] === target[shared]) {
1839
+ shared++;
1840
+ }
1841
+ const segments = [...from.slice(shared).map(() => ".."), ...target.slice(shared)];
1842
+ return segments.join("/") || ".";
1843
+ }
1844
+ async function readText(provider, path) {
1845
+ const v2 = getFileSystemProviderV23(provider);
1846
+ if (!v2) return provider.readFile(path);
1847
+ const current = await v2.readFile(parseWorkspacePath3(path));
1848
+ if (!current) return null;
1849
+ return new TextDecoder("utf-8", { fatal: true }).decode(current.data);
1850
+ }
1851
+ async function readBytes(provider, path) {
1852
+ const v2 = getFileSystemProviderV23(provider);
1853
+ if (v2) return (await v2.readFile(parseWorkspacePath3(path)))?.data ?? null;
1854
+ return provider.readBinary(path);
1855
+ }
1856
+ async function writeBytes(provider, path, data, mode = "upsert") {
1857
+ const v2 = getFileSystemProviderV23(provider);
1858
+ if (v2) {
1859
+ await v2.writeFile(parseWorkspacePath3(path), data, {
1860
+ mode,
1861
+ createParents: true,
1862
+ expectedVersion: mode === "create" ? null : void 0
1863
+ });
1864
+ return;
1865
+ }
1866
+ if (mode === "create" && await provider.exists(path)) {
1867
+ throw new FsError3("already-exists", "File already exists.", { operation: "write", path });
1868
+ }
1869
+ await provider.writeBinary(path, data);
1870
+ }
1871
+ async function listCompanionFiles(provider, layout) {
1872
+ try {
1873
+ const entries = await provider.readDirectory(layout.companionDirectory);
1874
+ return entries.filter((entry) => entry.kind === "file").map((entry) => entry.path);
1875
+ } catch (error) {
1876
+ if (error instanceof FsError3 && error.code === "not-found") return [];
1877
+ throw error;
1878
+ }
1879
+ }
1880
+ async function removeOutsideInCompanion(provider, layout) {
1881
+ const providerV2 = getFileSystemProviderV23(provider);
1882
+ if (providerV2) {
1883
+ const path = parseWorkspacePath3(layout.companionDirectory);
1884
+ if (await providerV2.stat(path) !== null) {
1885
+ await providerV2.remove(path, { recursive: true, missing: "ignore" });
1886
+ }
1887
+ return;
1888
+ }
1889
+ if (await provider.exists(layout.companionDirectory)) {
1890
+ await provider.delete(layout.companionDirectory);
1891
+ }
1892
+ }
1893
+ async function persistImportedMedia(provider, layout, container) {
1894
+ const entries = await container.listFiles();
1895
+ for (const entry of entries) {
1896
+ if (/\.md$/i.test(entry.path)) continue;
1897
+ const data = await container.readFile(entry.path);
1898
+ if (!data) continue;
1899
+ await writeBytes(provider, join2(layout.companionDirectory, entry.path), data, "create");
1900
+ }
1901
+ }
1902
+ async function loadEditableShellDocument(provider, selectedPath) {
1903
+ const imageExtension = selectedPath.slice(selectedPath.lastIndexOf(".") + 1).toLowerCase();
1904
+ const imageMimeType = IMAGE_MIME_TYPES[imageExtension];
1905
+ if (imageMimeType) {
1906
+ const data = await readBytes(provider, selectedPath);
1907
+ return data === null ? null : {
1908
+ displayPath: selectedPath,
1909
+ sourcePath: selectedPath,
1910
+ content: "",
1911
+ outsideIn: null,
1912
+ outsideInEditingEnabled: false,
1913
+ image: { data, mimeType: imageMimeType }
1914
+ };
1915
+ }
1916
+ if (!OUTSIDE_IN_EXTENSION.test(selectedPath)) {
1917
+ const content = await readText(provider, selectedPath);
1918
+ return content === null ? null : {
1919
+ displayPath: selectedPath,
1920
+ sourcePath: selectedPath,
1921
+ content,
1922
+ outsideIn: null,
1923
+ outsideInEditingEnabled: true
1924
+ };
1925
+ }
1926
+ const resolved = resolveOutsideInLayout(selectedPath);
1927
+ if (!resolved) return null;
1928
+ const layout = {
1929
+ ...resolved,
1930
+ targetPath: withLegacySlash(resolved.targetPath, selectedPath),
1931
+ companionDirectory: withLegacySlash(resolved.companionDirectory, selectedPath),
1932
+ markdownPath: withLegacySlash(resolved.markdownPath, selectedPath)
1933
+ };
1934
+ const candidates = (await listCompanionFiles(provider, layout)).map(
1935
+ (path) => withLegacySlash(path, selectedPath)
1936
+ );
1937
+ const chosen = chooseOutsideInMarkdownPath(layout, candidates);
1938
+ if (chosen) {
1939
+ const content = await readText(provider, chosen);
1940
+ if (content === null) return null;
1941
+ const metadata = await readOutsideInMetadata(content);
1942
+ if (metadata && metadata.format !== layout.format) {
1943
+ throw new Error(
1944
+ `${chosen} is configured for ${metadata.format}, not ${layout.format}. Rename the rendered file back or repair the companion frontmatter.`
1945
+ );
1946
+ }
1947
+ const linkedContent = await withOutsideInMetadata(content, layout);
1948
+ if (linkedContent !== content) await writeProviderText(provider, chosen, linkedContent);
1949
+ return {
1950
+ displayPath: selectedPath,
1951
+ sourcePath: chosen,
1952
+ content: linkedContent,
1953
+ outsideIn: { ...layout, markdownPath: chosen },
1954
+ outsideInEditingEnabled: await isOutsideInMarkdownEditingEnabled(linkedContent)
1955
+ };
1956
+ }
1957
+ const rendered = await readBytes(provider, selectedPath);
1958
+ if (!rendered) return null;
1959
+ const imported = await importOutsideInDocument({
1960
+ data: rendered,
1961
+ targetPath: selectedPath
1962
+ });
1963
+ const importedLayout = {
1964
+ ...imported.layout,
1965
+ targetPath: withLegacySlash(imported.layout.targetPath, selectedPath),
1966
+ companionDirectory: withLegacySlash(imported.layout.companionDirectory, selectedPath),
1967
+ markdownPath: withLegacySlash(imported.layout.markdownPath, selectedPath)
1968
+ };
1969
+ await persistImportedMedia(provider, importedLayout, imported.container);
1970
+ await writeProviderText(provider, importedLayout.markdownPath, imported.markdown, "create");
1971
+ return {
1972
+ displayPath: selectedPath,
1973
+ sourcePath: importedLayout.markdownPath,
1974
+ content: imported.markdown,
1975
+ outsideIn: importedLayout,
1976
+ outsideInEditingEnabled: false
1977
+ };
1978
+ }
1979
+ async function enableOutsideInMarkdownEditing(provider, document2) {
1980
+ const layout = document2.outsideIn;
1981
+ if (!layout) throw new Error("This file does not support outside-in Markdown editing.");
1982
+ const original = await readBytes(provider, layout.targetPath);
1983
+ if (!original) throw new Error(`The rendered file "${layout.targetPath}" was not found.`);
1984
+ try {
1985
+ await writeBytes(provider, layout.backupPath, original, "create");
1986
+ } catch (error) {
1987
+ if (!(error instanceof FsError3 && error.code === "already-exists")) throw error;
1988
+ }
1989
+ const content = await withOutsideInMarkdownEditing(document2.content, layout);
1990
+ if (content !== document2.content) await writeProviderText(provider, document2.sourcePath, content);
1991
+ return { ...document2, content, outsideIn: layout, outsideInEditingEnabled: true };
1992
+ }
1993
+ async function findRuntimePath(provider, targetPath) {
1994
+ let directory = dirname(targetPath);
1995
+ for (; ; ) {
1996
+ const candidateDirectory = join2(directory, SQUISQ_RUNTIME_DIRECTORY);
1997
+ if (await providerEntryExists(provider, candidateDirectory)) {
1998
+ const providerV2 = getFileSystemProviderV23(provider);
1999
+ if (providerV2) {
2000
+ const entry = await providerV2.stat(parseWorkspacePath3(candidateDirectory));
2001
+ if (entry?.kind !== "directory") {
2002
+ throw new Error(`${candidateDirectory} must be a directory.`);
2003
+ }
2004
+ }
2005
+ return join2(candidateDirectory, SQUISQ_RUNTIME_FILENAME);
2006
+ }
2007
+ if (!directory) break;
2008
+ directory = dirname(directory);
2009
+ }
2010
+ return join2(SQUISQ_RUNTIME_DIRECTORY, SQUISQ_RUNTIME_FILENAME);
2011
+ }
2012
+ async function writeRuntimeIfNeeded(provider, runtimePath) {
2013
+ const { PLAYER_BUNDLE } = await import("@bendyline/squisq-react/standalone-source");
2014
+ const current = await readText(provider, runtimePath);
2015
+ if (current !== PLAYER_BUNDLE) await writeProviderText(provider, runtimePath, PLAYER_BUNDLE);
2016
+ }
2017
+ async function renderPlainOutsideInHtml(provider, layout, markdown) {
2018
+ const [{ parseMarkdown }, { markdownDocToPlainHtml }] = await Promise.all([
2019
+ import("@bendyline/squisq/markdown"),
2020
+ import("@bendyline/squisq-formats/html")
2021
+ ]);
2022
+ const container = new FileSystemContentContainer(provider, layout.companionDirectory);
2023
+ const images = /* @__PURE__ */ new Map();
2024
+ for (const entry of await container.listFiles()) {
2025
+ const outputPath = `${layout.companionName}/${entry.path}`;
2026
+ images.set(entry.path, outputPath);
2027
+ images.set(`./${entry.path}`, outputPath);
2028
+ }
2029
+ return new TextEncoder().encode(
2030
+ markdownDocToPlainHtml(parseMarkdown(markdown), {
2031
+ title: layout.stem,
2032
+ images: images.size > 0 ? images : void 0
2033
+ })
2034
+ );
2035
+ }
2036
+ async function prepareOutsideInRender(provider, layout, markdown) {
2037
+ const htmlOutput = layout.format === "html" ? await readOutsideInHtmlOutput(markdown) : null;
2038
+ if (htmlOutput === "static") {
2039
+ return {
2040
+ bytes: await renderPlainOutsideInHtml(provider, layout, markdown),
2041
+ runtimePath: null
2042
+ };
2043
+ }
2044
+ const runtimePath = layout.format === "html" ? await findRuntimePath(provider, layout.targetPath) : null;
2045
+ const outputDirectory = dirname(layout.targetPath);
2046
+ const rendered = await renderOutsideInDocument(
2047
+ {
2048
+ markdown,
2049
+ targetPath: layout.targetPath,
2050
+ container: new FileSystemContentContainer(provider, layout.companionDirectory)
2051
+ },
2052
+ runtimePath ? {
2053
+ html: {
2054
+ playerScriptPath: relativePath(outputDirectory, runtimePath),
2055
+ basePath: relativePath(outputDirectory, layout.companionDirectory)
2056
+ },
2057
+ ...htmlOutput === "interactive" ? { formatOptions: { html: { mode: "slideshow" } } } : {}
2058
+ } : {}
2059
+ );
2060
+ return { bytes: rendered.bytes, runtimePath };
1578
2061
  }
1579
- function sortFileEntries(entries, mode) {
1580
- return [...entries].sort((left, right) => {
1581
- if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
1582
- if (left.kind === "directory" || right.kind === "directory" || mode === "name") {
1583
- return compareText(left.name, right.name);
1584
- }
1585
- const leftTime = modifiedTime(left);
1586
- const rightTime = modifiedTime(right);
1587
- if (leftTime !== null && rightTime !== null && leftTime !== rightTime) {
1588
- return rightTime - leftTime;
2062
+ async function createNewOutsideInDocument(provider, targetPath, htmlOutput) {
2063
+ const layout = resolveOutsideInLayout(targetPath);
2064
+ if (!layout) throw new Error(`Outside-in editing does not support "${targetPath}".`);
2065
+ if (layout.format !== "html" && htmlOutput !== void 0) {
2066
+ throw new Error("Only Web pages have an HTML output style.");
2067
+ }
2068
+ if (await providerEntryExists(provider, layout.targetPath) || await providerEntryExists(provider, layout.companionDirectory)) {
2069
+ throw new FsError3(
2070
+ "already-exists",
2071
+ "A file or companion folder with that name already exists.",
2072
+ {
2073
+ operation: "write",
2074
+ path: layout.targetPath
2075
+ }
2076
+ );
2077
+ }
2078
+ let content = await withOutsideInMarkdownEditing(`# ${layout.stem}
2079
+ `, layout);
2080
+ if (layout.format === "html") {
2081
+ content = await withOutsideInHtmlOutput(content, htmlOutput ?? "interactive");
2082
+ }
2083
+ const prepared = await prepareOutsideInRender(provider, layout, content);
2084
+ if (prepared.runtimePath) await writeRuntimeIfNeeded(provider, prepared.runtimePath);
2085
+ await writeBytes(provider, layout.targetPath, prepared.bytes, "create");
2086
+ await writeProviderText(provider, layout.markdownPath, content, "create");
2087
+ return {
2088
+ displayPath: layout.targetPath,
2089
+ sourcePath: layout.markdownPath,
2090
+ content,
2091
+ outsideIn: layout,
2092
+ outsideInEditingEnabled: true
2093
+ };
2094
+ }
2095
+ function createOutsideInDocumentTarget(provider, layout, onCommitted) {
2096
+ const sourceTarget = createFileSystemDocumentTarget(provider, layout.markdownPath);
2097
+ return {
2098
+ key: `${provider.id}:outside-in:${parseWorkspacePath3(layout.targetPath)}`,
2099
+ async commit(request) {
2100
+ if (!await isOutsideInMarkdownEditingEnabled(request.content)) {
2101
+ throw new Error(
2102
+ "Outside-in editing is read-only until squisq-updatefrommarkdown: true is set."
2103
+ );
2104
+ }
2105
+ const rendered = await prepareOutsideInRender(provider, layout, request.content);
2106
+ await sourceTarget.commit({ ...request, targetKey: sourceTarget.key });
2107
+ if (rendered.runtimePath) await writeRuntimeIfNeeded(provider, rendered.runtimePath);
2108
+ await writeBytes(provider, layout.targetPath, rendered.bytes);
2109
+ onCommitted?.();
2110
+ return {};
1589
2111
  }
1590
- if (leftTime === null && rightTime !== null) return 1;
1591
- if (leftTime !== null && rightTime === null) return -1;
1592
- return compareText(left.name, right.name);
1593
- });
2112
+ };
2113
+ }
2114
+ function createOutsideInContentContainer(provider, layout) {
2115
+ return new FileSystemContentContainer(provider, layout.companionDirectory);
1594
2116
  }
1595
2117
 
1596
- // src/FileExplorer/FileExplorer.tsx
1597
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1598
- var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
1599
- ".txt",
1600
- ".md",
2118
+ // src/DocBlocksShell/import-file-types.ts
2119
+ var OUTSIDE_IN_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([
2120
+ ".csv",
2121
+ ".docx",
1601
2122
  ".html",
1602
2123
  ".htm",
1603
- ".docx",
1604
2124
  ".pdf",
1605
2125
  ".pptx",
1606
- ".xlsx",
1607
- ".dbk",
1608
- ".zip"
2126
+ ".xlsx"
2127
+ ]);
2128
+ var BUNDLE_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([".dbk", ".zip"]);
2129
+ var DIRECT_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([
2130
+ ".avif",
2131
+ ".bash",
2132
+ ".bmp",
2133
+ ".c",
2134
+ ".cc",
2135
+ ".cpp",
2136
+ ".cs",
2137
+ ".css",
2138
+ ".gif",
2139
+ ".go",
2140
+ ".h",
2141
+ ".hpp",
2142
+ ".ico",
2143
+ ".ini",
2144
+ ".java",
2145
+ ".jpeg",
2146
+ ".jpg",
2147
+ ".js",
2148
+ ".json",
2149
+ ".jsonc",
2150
+ ".jsx",
2151
+ ".kt",
2152
+ ".kts",
2153
+ ".less",
2154
+ ".log",
2155
+ ".lua",
2156
+ ".markdown",
2157
+ ".md",
2158
+ ".mdown",
2159
+ ".mjs",
2160
+ ".php",
2161
+ ".png",
2162
+ ".py",
2163
+ ".rb",
2164
+ ".rs",
2165
+ ".scss",
2166
+ ".sh",
2167
+ ".sql",
2168
+ ".svg",
2169
+ ".swift",
2170
+ ".toml",
2171
+ ".ts",
2172
+ ".tsv",
2173
+ ".tsx",
2174
+ ".txt",
2175
+ ".webp",
2176
+ ".xml",
2177
+ ".yaml",
2178
+ ".yml",
2179
+ ".zsh"
1609
2180
  ]);
2181
+ var DIRECT_IMPORT_BASENAMES = /* @__PURE__ */ new Set(["dockerfile", "license", "makefile", "readme"]);
2182
+ function extensionOfFileName(name) {
2183
+ const dot = name.lastIndexOf(".");
2184
+ return dot <= 0 ? "" : name.slice(dot).toLowerCase();
2185
+ }
2186
+ function isSupportedImportFile(file) {
2187
+ const extension = extensionOfFileName(file.name);
2188
+ if (OUTSIDE_IN_IMPORT_EXTENSIONS.has(extension) || BUNDLE_IMPORT_EXTENSIONS.has(extension) || DIRECT_IMPORT_EXTENSIONS.has(extension)) {
2189
+ return true;
2190
+ }
2191
+ const mimeType = file.type.toLowerCase();
2192
+ if (mimeType.startsWith("text/") || mimeType.startsWith("image/")) return true;
2193
+ return !extension && DIRECT_IMPORT_BASENAMES.has(file.name.toLowerCase());
2194
+ }
2195
+
2196
+ // src/FileExplorer/FileExplorer.tsx
2197
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1610
2198
  var INTERNAL_DRAG_TYPE = "application/x-docblocks-entry";
1611
2199
  var NEW_ITEM_ERROR_ID = "db-new-item-error";
2200
+ var NEW_FILE_EXTENSIONS = {
2201
+ markdown: ".md",
2202
+ docx: ".docx",
2203
+ xlsx: ".xlsx",
2204
+ pdf: ".pdf",
2205
+ "web-interactive": ".html",
2206
+ "web-static": ".html"
2207
+ };
2208
+ var SELECTABLE_FILE_EXTENSION = /\.(?:md|docx|xlsx|pdf|html?)$/i;
1612
2209
  function normalisePath2(path) {
1613
- return parseWorkspacePath2(path);
2210
+ return parseWorkspacePath4(path);
1614
2211
  }
1615
2212
  function hasEquivalentPath2(paths, path) {
1616
2213
  const canonical = normalisePath2(path);
@@ -1657,6 +2254,7 @@ function FileExplorer({
1657
2254
  onPinnedDocumentDelete,
1658
2255
  onTogglePin,
1659
2256
  onSelect,
2257
+ onOpenWorkspaceFolder,
1660
2258
  actionsForEntry,
1661
2259
  onTreeMutation,
1662
2260
  onTreeChange,
@@ -1675,6 +2273,7 @@ function FileExplorer({
1675
2273
  [pinnedPaths]
1676
2274
  );
1677
2275
  const [newItemName, setNewItemName] = useState4("");
2276
+ const [newFileFormat, setNewFileFormat] = useState4("markdown");
1678
2277
  const [uncontrolledSortMode, setUncontrolledSortMode] = useState4("name");
1679
2278
  const selectedSortMode = sortMode ?? uncontrolledSortMode;
1680
2279
  const selectSortMode = useCallback4(
@@ -1709,12 +2308,11 @@ function FileExplorer({
1709
2308
  const hasSupported = useCallback4((dt) => {
1710
2309
  for (const item of Array.from(dt.items)) {
1711
2310
  if (item.kind !== "file") continue;
1712
- const name = item.getAsFile?.()?.name;
1713
- if (!name) return true;
1714
- const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
1715
- if (SUPPORTED_EXTENSIONS.has(ext)) return true;
2311
+ const file = item.getAsFile();
2312
+ if (!file) return true;
2313
+ if (isSupportedImportFile(file)) return true;
1716
2314
  }
1717
- return dt.items.length > 0;
2315
+ return Array.from(dt.files).some(isSupportedImportFile);
1718
2316
  }, []);
1719
2317
  const handleDragEnter = useCallback4(
1720
2318
  (e) => {
@@ -1748,11 +2346,8 @@ function FileExplorer({
1748
2346
  e.preventDefault();
1749
2347
  dragCounter.current = 0;
1750
2348
  setDragOver(false);
1751
- const supported = Array.from(e.dataTransfer.files).filter((f) => {
1752
- const ext = f.name.slice(f.name.lastIndexOf(".")).toLowerCase();
1753
- return SUPPORTED_EXTENSIONS.has(ext);
1754
- });
1755
- if (supported.length > 0) onImportFiles?.(supported);
2349
+ const files = Array.from(e.dataTransfer.files);
2350
+ if (files.length > 0) onImportFiles?.(files);
1756
2351
  },
1757
2352
  [onImportFiles]
1758
2353
  );
@@ -1791,9 +2386,24 @@ function FileExplorer({
1791
2386
  setNewItemCreationPending(true);
1792
2387
  try {
1793
2388
  if (itemType === "file") {
1794
- const filename = name.endsWith(".md") ? name : `${name}.md`;
2389
+ const stem = name.replace(SELECTABLE_FILE_EXTENSION, "");
2390
+ if (!stem) {
2391
+ setNewItemError("Enter a file name before the extension.");
2392
+ return;
2393
+ }
2394
+ const filename = `${stem}${NEW_FILE_EXTENSIONS[newFileFormat]}`;
1795
2395
  createdPath = `${prefix}${filename}`;
1796
- await tree.createFile(createdPath, "");
2396
+ if (newFileFormat === "markdown") {
2397
+ await tree.createFile(createdPath, "");
2398
+ } else {
2399
+ if (!provider) throw new Error("Open a workspace before creating a document.");
2400
+ await createNewOutsideInDocument(
2401
+ provider,
2402
+ createdPath,
2403
+ newFileFormat === "web-static" ? "static" : newFileFormat === "web-interactive" ? "interactive" : void 0
2404
+ );
2405
+ await tree.refresh();
2406
+ }
1797
2407
  handleSelect(createdPath);
1798
2408
  } else if (itemType === "directory") {
1799
2409
  await tree.createDirectory(createdPath);
@@ -1808,7 +2418,7 @@ function FileExplorer({
1808
2418
  setNewItemName("");
1809
2419
  setNewItemType(null);
1810
2420
  onTreeChange?.({ type: "create", path: createdPath });
1811
- }, [newItemName, newItemType, tree, handleSelect, onTreeChange]);
2421
+ }, [newFileFormat, newItemName, newItemType, onTreeChange, provider, tree, handleSelect]);
1812
2422
  const handleMoveToWorkspace = useCallback4(async () => {
1813
2423
  if (!onMoveToWorkspace || !moveDestinationId || movingToWorkspace) return;
1814
2424
  setMovingToWorkspace(true);
@@ -2136,6 +2746,17 @@ function FileExplorer({
2136
2746
  )
2137
2747
  ] }),
2138
2748
  /* @__PURE__ */ jsx5("span", { className: "db-explorer-action-divider", "aria-hidden": "true" }),
2749
+ onOpenWorkspaceFolder && /* @__PURE__ */ jsx5(
2750
+ "button",
2751
+ {
2752
+ type: "button",
2753
+ className: "db-explorer-btn",
2754
+ onClick: onOpenWorkspaceFolder,
2755
+ title: "Open this folder",
2756
+ "aria-label": "Open this folder",
2757
+ children: /* @__PURE__ */ jsx5(OpenFolderIcon, {})
2758
+ }
2759
+ ),
2139
2760
  /* @__PURE__ */ jsx5(
2140
2761
  "button",
2141
2762
  {
@@ -2159,6 +2780,7 @@ function FileExplorer({
2159
2780
  disabled: newItemCreationPending,
2160
2781
  onClick: () => {
2161
2782
  setNewItemError(null);
2783
+ setNewFileFormat("markdown");
2162
2784
  setNewItemType("file");
2163
2785
  },
2164
2786
  title: "New File",
@@ -2237,7 +2859,7 @@ function FileExplorer({
2237
2859
  /* @__PURE__ */ jsxs4(
2238
2860
  "form",
2239
2861
  {
2240
- className: "db-new-item-row",
2862
+ className: `db-new-item-row db-new-item-row--${newItemType}`,
2241
2863
  "aria-busy": newItemCreationPending,
2242
2864
  onSubmit: (e) => {
2243
2865
  e.preventDefault();
@@ -2262,14 +2884,41 @@ function FileExplorer({
2262
2884
  if (e.key === "Escape") {
2263
2885
  setNewItemType(null);
2264
2886
  setNewItemName("");
2887
+ setNewFileFormat("markdown");
2265
2888
  setNewItemError(null);
2266
2889
  }
2267
2890
  },
2268
2891
  autoFocus: true
2269
2892
  }
2270
2893
  ),
2271
- newItemType === "file" && /* @__PURE__ */ jsx5("span", { className: "db-new-item-suffix", children: ".md" }),
2272
- /* @__PURE__ */ jsx5("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
2894
+ newItemType === "file" ? /* @__PURE__ */ jsxs4("div", { className: "db-new-item-file-controls", children: [
2895
+ /* @__PURE__ */ jsxs4(
2896
+ "select",
2897
+ {
2898
+ className: "db-new-item-format",
2899
+ "aria-label": "New file type",
2900
+ value: newFileFormat,
2901
+ disabled: newItemCreationPending,
2902
+ onChange: (event) => {
2903
+ setNewFileFormat(event.currentTarget.value);
2904
+ setNewItemError(null);
2905
+ },
2906
+ children: [
2907
+ /* @__PURE__ */ jsxs4("optgroup", { label: "Document", children: [
2908
+ /* @__PURE__ */ jsx5("option", { value: "markdown", children: "Markdown (.md)" }),
2909
+ /* @__PURE__ */ jsx5("option", { value: "docx", children: "Word document (.docx)" }),
2910
+ /* @__PURE__ */ jsx5("option", { value: "xlsx", children: "Excel workbook (.xlsx)" }),
2911
+ /* @__PURE__ */ jsx5("option", { value: "pdf", children: "PDF document (.pdf)" })
2912
+ ] }),
2913
+ /* @__PURE__ */ jsxs4("optgroup", { label: "Web page", children: [
2914
+ /* @__PURE__ */ jsx5("option", { value: "web-interactive", children: "Web page \u2014 Interactive (.html)" }),
2915
+ /* @__PURE__ */ jsx5("option", { value: "web-static", children: "Web page \u2014 Static (.html)" })
2916
+ ] })
2917
+ ]
2918
+ }
2919
+ ),
2920
+ /* @__PURE__ */ jsx5("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
2921
+ ] }) : /* @__PURE__ */ jsx5("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
2273
2922
  ]
2274
2923
  }
2275
2924
  ),
@@ -2623,6 +3272,8 @@ function AppMenu({
2623
3272
  onAccentColorChange,
2624
3273
  writeCanvasSettings = DEFAULT_WRITE_CANVAS_PREFERENCES,
2625
3274
  onWriteCanvasSettingsChange,
3275
+ proofingPreferences = DEFAULT_PROOFING_PREFERENCES,
3276
+ onProofingPreferencesChange,
2626
3277
  versioningPreference = "browser-only",
2627
3278
  onVersioningPreferenceChange,
2628
3279
  onDownloadAllWorkspaces,
@@ -2797,6 +3448,13 @@ function AppMenu({
2797
3448
  onChange: (settings) => onWriteCanvasSettingsChange?.(settings)
2798
3449
  }
2799
3450
  ),
3451
+ /* @__PURE__ */ jsx7(
3452
+ ProofingSettingsControls,
3453
+ {
3454
+ value: proofingPreferences,
3455
+ onChange: (settings) => onProofingPreferencesChange?.(settings)
3456
+ }
3457
+ ),
2800
3458
  getStorageEstimate && /* @__PURE__ */ jsxs6("fieldset", { className: "db-settings-fieldset", children: [
2801
3459
  /* @__PURE__ */ jsx7("legend", { className: "db-settings-legend", children: "Storage" }),
2802
3460
  /* @__PURE__ */ jsx7("p", { className: "db-settings-hint", children: storageEstimate ? `DocBlocks documents and app data are using ${formatBytes(
@@ -3199,7 +3857,7 @@ function useDocumentSession(autoSaveDelayMs = 500) {
3199
3857
  import { lazy, Suspense } from "react";
3200
3858
  import { jsx as jsx10 } from "react/jsx-runtime";
3201
3859
  var ExportToolbarControlsImplementation = lazy(
3202
- () => import("./ExportToolbarControls-N3TCL5DD.js").then((module) => ({
3860
+ () => import("./ExportToolbarControls-QYLBRWM7.js").then((module) => ({
3203
3861
  default: module.ExportToolbarControls
3204
3862
  }))
3205
3863
  );
@@ -3322,27 +3980,39 @@ function useGit(provider, requestedWorkspaceId, theme) {
3322
3980
  const available = gitApi !== null && capabilities?.gitAvailable === true;
3323
3981
  const [repo, setRepo] = useState10(null);
3324
3982
  const [remoteWeb, setRemoteWeb] = useState10(null);
3983
+ const [pendingGrant, setPendingGrant] = useState10(null);
3984
+ const loadRemote = useCallback9(
3985
+ (repositoryId2, isCancelled) => {
3986
+ if (!gitApi) return;
3987
+ void gitApi.listRemotes(repositoryId2).then(
3988
+ (remotes) => {
3989
+ if (isCancelled() || !remotes.ok) return;
3990
+ const first = remotes.value[0];
3991
+ setRemoteWeb(first?.web ?? null);
3992
+ },
3993
+ () => void 0
3994
+ );
3995
+ },
3996
+ [gitApi]
3997
+ );
3325
3998
  useEffect9(() => {
3326
3999
  setRepo(null);
3327
4000
  setRemoteWeb(null);
4001
+ setPendingGrant(null);
3328
4002
  if (!gitApi || !workspaceId || !available) return;
3329
4003
  let cancelled = false;
4004
+ const isCancelled = () => cancelled;
3330
4005
  void gitApi.detectRepo(workspaceId).then(
3331
4006
  (result) => {
3332
- if (cancelled || !result.ok || !result.value.isRepo || !result.value.repositoryId) {
4007
+ if (cancelled || !result.ok || !result.value.isRepo) return;
4008
+ if (!result.value.repositoryId) {
4009
+ if (result.value.requiresExpandedGrant) {
4010
+ setPendingGrant({ repositoryRoot: result.value.repositoryRoot ?? null });
4011
+ }
3333
4012
  return;
3334
4013
  }
3335
4014
  setRepo(result.value);
3336
- void gitApi.listRemotes(result.value.repositoryId).then(
3337
- (remotes) => {
3338
- if (cancelled || !remotes.ok) return;
3339
- const first = remotes.value[0];
3340
- setRemoteWeb(first?.web ?? null);
3341
- },
3342
- // No remote is a supported state; a failed probe degrades to it
3343
- // rather than surfacing an unhandled rejection.
3344
- () => void 0
3345
- );
4015
+ loadRemote(result.value.repositoryId, isCancelled);
3346
4016
  },
3347
4017
  /* detection failure → not a repo, which is the default state */
3348
4018
  () => void 0
@@ -3350,7 +4020,25 @@ function useGit(provider, requestedWorkspaceId, theme) {
3350
4020
  return () => {
3351
4021
  cancelled = true;
3352
4022
  };
3353
- }, [gitApi, workspaceId, available]);
4023
+ }, [gitApi, workspaceId, available, loadRemote]);
4024
+ const [grantBusy, setGrantBusy] = useState10(false);
4025
+ const enableExpandedRepo = useCallback9(
4026
+ async (opts) => {
4027
+ if (!gitApi || !workspaceId) return false;
4028
+ setGrantBusy(true);
4029
+ try {
4030
+ const result = await gitApi.grantExpandedRepo(workspaceId, opts);
4031
+ if (!result.ok || !result.value.repositoryId) return false;
4032
+ setRepo(result.value);
4033
+ setPendingGrant(null);
4034
+ loadRemote(result.value.repositoryId, () => false);
4035
+ return true;
4036
+ } finally {
4037
+ setGrantBusy(false);
4038
+ }
4039
+ },
4040
+ [gitApi, workspaceId, loadRemote]
4041
+ );
3354
4042
  const repositoryId = repo?.repositoryId ?? null;
3355
4043
  const isRepo = repositoryId !== null;
3356
4044
  const { status, refresh, scheduleRefresh } = useGitStatus(gitApi, repositoryId, isRepo);
@@ -3499,6 +4187,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
3499
4187
  capabilities,
3500
4188
  repo,
3501
4189
  repositoryId,
4190
+ pendingGrant,
4191
+ grantBusy,
4192
+ enableExpandedRepo,
3502
4193
  remoteWeb,
3503
4194
  gitApi,
3504
4195
  provider,
@@ -3526,6 +4217,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
3526
4217
  capabilities,
3527
4218
  repo,
3528
4219
  repositoryId,
4220
+ pendingGrant,
4221
+ grantBusy,
4222
+ enableExpandedRepo,
3529
4223
  remoteWeb,
3530
4224
  gitApi,
3531
4225
  provider,
@@ -3717,158 +4411,10 @@ function hasControlCharacter(value) {
3717
4411
 
3718
4412
  // src/DocBlocksShell/import-files.ts
3719
4413
  import {
3720
- FsError as FsError3,
3721
- getFileSystemProviderV2 as getFileSystemProviderV23,
3722
- parseWorkspacePath as parseWorkspacePath4
3723
- } from "@bendyline/docblocks/filesystem";
3724
-
3725
- // src/DocBlocksShell/outside-in-contract.ts
3726
- var OUTSIDE_IN_FORMAT_IDS = ["html", "docx", "pdf", "pptx", "xlsx"];
3727
- var FORMAT_IDS = new Set(OUTSIDE_IN_FORMAT_IDS);
3728
- var UPDATE_FROM_MARKDOWN_KEY = "squisq-updatefrommarkdown";
3729
- function normalizePath(path) {
3730
- const leading = path.replace(/\\/g, "/").startsWith("/") ? "/" : "";
3731
- const parts = path.replace(/\\/g, "/").split("/").filter(Boolean);
3732
- if (parts.some((part) => part === "." || part === "..")) {
3733
- throw new Error(`Outside-in paths must be canonical workspace paths: ${path}`);
3734
- }
3735
- return leading + parts.join("/");
3736
- }
3737
- function join(parent, child) {
3738
- if (!parent || parent === "/") return parent === "/" ? `/${child}` : child;
3739
- return `${parent}/${child}`;
3740
- }
3741
- function slug(stem) {
3742
- return stem.normalize("NFKD").replace(/\p{Mark}+/gu, "").toLocaleLowerCase("en-US").replace(/[^\p{Letter}\p{Number}]+/gu, "-").replace(/^-+|-+$/g, "") || "document";
3743
- }
3744
- function resolveOutsideInLayout(path) {
3745
- const targetPath = normalizePath(path);
3746
- const slash = targetPath.lastIndexOf("/");
3747
- const parentDirectory = slash < 0 ? "" : slash === 0 ? "/" : targetPath.slice(0, slash);
3748
- const filename = slash < 0 ? targetPath : targetPath.slice(slash + 1);
3749
- const dot = filename.lastIndexOf(".");
3750
- if (dot <= 0) return null;
3751
- const rawFormat = filename.slice(dot + 1).toLowerCase();
3752
- const format = rawFormat === "htm" ? "html" : rawFormat;
3753
- if (!FORMAT_IDS.has(format)) return null;
3754
- const stem = filename.slice(0, dot);
3755
- const companionName = `${stem}_files`;
3756
- const companionDirectory = join(parentDirectory, companionName);
3757
- const markdownFilename = `${slug(stem)}.md`;
3758
- const backupDirectory = join(companionDirectory, ".original");
3759
- const backupFilename = `original.${format}`;
3760
- return {
3761
- targetPath,
3762
- format,
3763
- parentDirectory,
3764
- stem,
3765
- companionName,
3766
- companionDirectory,
3767
- markdownFilename,
3768
- markdownPath: join(companionDirectory, markdownFilename),
3769
- relativeTargetPath: `../${filename}`,
3770
- backupDirectory,
3771
- backupFilename,
3772
- backupPath: join(backupDirectory, backupFilename)
3773
- };
3774
- }
3775
- function chooseOutsideInMarkdownPath(layout, paths) {
3776
- const canonical = normalizePath(layout.markdownPath);
3777
- const normalized = paths.map(normalizePath);
3778
- const exact = normalized.find((path) => path === canonical);
3779
- if (exact) return exact;
3780
- const folded = normalized.find(
3781
- (path) => path.toLocaleLowerCase("en-US") === canonical.toLocaleLowerCase("en-US")
3782
- );
3783
- if (folded) return folded;
3784
- const prefix = `${normalizePath(layout.companionDirectory).replace(/\/$/, "")}/`;
3785
- const markdown = normalized.filter(
3786
- (path) => path.startsWith(prefix) && !path.slice(prefix.length).includes("/") && path.toLocaleLowerCase("en-US").endsWith(".md")
3787
- );
3788
- return markdown.length === 1 ? markdown[0] : null;
3789
- }
3790
- async function readOutsideInMetadata(source) {
3791
- const { readOutsideInMetadata: readMetadata } = await import("@bendyline/squisq-formats/outside-in");
3792
- const metadata = readMetadata(source);
3793
- return metadata ? {
3794
- ...metadata,
3795
- updateFromMarkdown: await isOutsideInMarkdownEditingEnabled(source)
3796
- } : null;
3797
- }
3798
- async function withOutsideInMetadata(source, layout) {
3799
- const { withOutsideInMetadata: addMetadata } = await import("@bendyline/squisq-formats/outside-in");
3800
- return addMetadata(source, layout);
3801
- }
3802
- async function isOutsideInMarkdownEditingEnabled(source) {
3803
- const module = await import("@bendyline/squisq-formats/outside-in");
3804
- if (module.isOutsideInMarkdownEditingEnabled) {
3805
- return module.isOutsideInMarkdownEditingEnabled(source);
3806
- }
3807
- const { parseFrontmatter, splitFrontmatterBlock } = await import("@bendyline/squisq/markdown");
3808
- const block = splitFrontmatterBlock(source).frontmatter;
3809
- if (!block) return false;
3810
- const firstBreak = block.indexOf("\n");
3811
- if (firstBreak < 0) return false;
3812
- const yaml = block.slice(firstBreak + 1).replace(/\r?\n---(?:\r?\n)?$/, "");
3813
- return parseFrontmatter(yaml)?.[UPDATE_FROM_MARKDOWN_KEY] === true;
3814
- }
3815
- async function withOutsideInMarkdownEditing(source, layout, enabled = true) {
3816
- const module = await import("@bendyline/squisq-formats/outside-in");
3817
- if (module.withOutsideInMarkdownEditing) {
3818
- return module.withOutsideInMarkdownEditing(source, layout, enabled);
3819
- }
3820
- const { setFrontmatterValues } = await import("@bendyline/squisq/markdown");
3821
- return setFrontmatterValues(await withOutsideInMetadata(source, layout), {
3822
- [UPDATE_FROM_MARKDOWN_KEY]: enabled
3823
- });
3824
- }
3825
- async function importOutsideInDocument(source, options = {}) {
3826
- const { importOutsideInDocument: importDocument } = await import("@bendyline/squisq-formats/outside-in");
3827
- const imported = await importDocument(source, options);
3828
- const layout = resolveOutsideInLayout(source.targetPath);
3829
- if (!layout) throw new Error(`Outside-in editing does not support "${source.targetPath}".`);
3830
- return { ...imported, layout };
3831
- }
3832
- async function renderOutsideInDocument(source, options = {}) {
3833
- const { renderOutsideInDocument: renderDocument } = await import("@bendyline/squisq-formats/outside-in");
3834
- return renderDocument(source, options);
3835
- }
3836
-
3837
- // src/DocBlocksShell/provider-io.ts
3838
- import {
3839
- FsError as FsError2,
3840
- getFileSystemProviderV2 as getFileSystemProviderV22,
3841
- parseWorkspacePath as parseWorkspacePath3
4414
+ FsError as FsError4,
4415
+ getFileSystemProviderV2 as getFileSystemProviderV24,
4416
+ parseWorkspacePath as parseWorkspacePath5
3842
4417
  } from "@bendyline/docblocks/filesystem";
3843
- async function providerEntryExists(provider, path) {
3844
- const providerV2 = getFileSystemProviderV22(provider);
3845
- return providerV2 ? await providerV2.stat(parseWorkspacePath3(path)) !== null : provider.exists(path);
3846
- }
3847
- async function writeProviderText(provider, path, content, mode = "upsert") {
3848
- const providerV2 = getFileSystemProviderV22(provider);
3849
- if (providerV2) {
3850
- await providerV2.writeFile(parseWorkspacePath3(path), new TextEncoder().encode(content), {
3851
- mode,
3852
- createParents: true,
3853
- expectedVersion: mode === "create" ? null : void 0
3854
- });
3855
- return;
3856
- }
3857
- if (mode === "create" && await provider.exists(path)) {
3858
- throw new FsError2("already-exists", "File already exists.", { operation: "write", path });
3859
- }
3860
- await provider.writeFile(path, content);
3861
- }
3862
- async function removeProviderEntry(provider, path) {
3863
- const providerV2 = getFileSystemProviderV22(provider);
3864
- if (providerV2) {
3865
- await providerV2.remove(parseWorkspacePath3(path), { recursive: true, missing: "ignore" });
3866
- return;
3867
- }
3868
- await provider.delete(path);
3869
- }
3870
-
3871
- // src/DocBlocksShell/import-files.ts
3872
4418
  var MAX_NAME_ATTEMPTS = 100;
3873
4419
  function withCopySuffix(path, n) {
3874
4420
  const dot = path.lastIndexOf(".");
@@ -3877,7 +4423,7 @@ function withCopySuffix(path, n) {
3877
4423
  return `${path.slice(0, dot)} (${n})${path.slice(dot)}`;
3878
4424
  }
3879
4425
  function isAlreadyExists(error) {
3880
- return error instanceof FsError3 && error.code === "already-exists";
4426
+ return error instanceof FsError4 && error.code === "already-exists";
3881
4427
  }
3882
4428
  async function claimAvailablePath(provider, desiredPath) {
3883
4429
  for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
@@ -3892,9 +4438,9 @@ async function claimAvailablePath(provider, desiredPath) {
3892
4438
  throw new Error(`Too many documents are already named like \u201C${desiredPath}\u201D.`);
3893
4439
  }
3894
4440
  async function writeProviderBytes(provider, path, data, mode) {
3895
- const providerV2 = getFileSystemProviderV23(provider);
4441
+ const providerV2 = getFileSystemProviderV24(provider);
3896
4442
  if (providerV2) {
3897
- await providerV2.writeFile(parseWorkspacePath4(path), data, {
4443
+ await providerV2.writeFile(parseWorkspacePath5(path), data, {
3898
4444
  mode,
3899
4445
  createParents: true,
3900
4446
  expectedVersion: mode === "create" ? null : void 0
@@ -3902,11 +4448,10 @@ async function writeProviderBytes(provider, path, data, mode) {
3902
4448
  return;
3903
4449
  }
3904
4450
  if (mode === "create" && await providerEntryExists(provider, path)) {
3905
- throw new FsError3("already-exists", "File already exists.", { operation: "write", path });
4451
+ throw new FsError4("already-exists", "File already exists.", { operation: "write", path });
3906
4452
  }
3907
4453
  await provider.writeBinary(path, data);
3908
4454
  }
3909
- var OUTSIDE_IN_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".pptx", ".xlsx"]);
3910
4455
  async function claimOutsideInTarget(provider, desiredPath) {
3911
4456
  for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
3912
4457
  const candidate = attempt === 1 ? desiredPath : withCopySuffix(desiredPath, attempt);
@@ -3950,10 +4495,7 @@ async function importOutsideInFile(file, provider) {
3950
4495
  throw error;
3951
4496
  }
3952
4497
  }
3953
- async function readImportedMarkdown(file, ext, destPath, provider) {
3954
- if (ext === ".md" || ext === ".txt") {
3955
- return file.text();
3956
- }
4498
+ async function readImportedMarkdown(file, destPath, provider) {
3957
4499
  const snapshot = await decodeDbkWorkspace(await file.arrayBuffer(), {
3958
4500
  targetDocumentPath: destPath
3959
4501
  });
@@ -3967,27 +4509,31 @@ async function writeDbkCompanions(snapshot, provider) {
3967
4509
  await writeProviderText(provider, entry.path, entry.content, "create");
3968
4510
  }
3969
4511
  }
3970
- var SUPPORTED_EXTENSIONS2 = /* @__PURE__ */ new Set([".md", ".txt", ...OUTSIDE_IN_EXTENSIONS, ".dbk", ".zip"]);
3971
4512
  async function importDroppedFiles(files, provider) {
3972
4513
  const result = { imported: [], failed: [], unsupported: [] };
3973
4514
  for (const file of files) {
3974
- const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
3975
- if (!SUPPORTED_EXTENSIONS2.has(ext)) {
4515
+ const ext = extensionOfFileName(file.name);
4516
+ if (!isSupportedImportFile(file)) {
3976
4517
  result.unsupported.push(file.name);
3977
4518
  continue;
3978
4519
  }
3979
- const baseName = file.name.replace(/\.[^.]+$/, "");
3980
4520
  let claimed = null;
3981
4521
  try {
3982
- if (OUTSIDE_IN_EXTENSIONS.has(ext)) {
4522
+ if (OUTSIDE_IN_IMPORT_EXTENSIONS.has(ext)) {
3983
4523
  const imported = await importOutsideInFile(file, provider);
3984
4524
  result.imported.push({ source: file.name, ...imported });
3985
4525
  continue;
3986
4526
  }
3987
- const claim = await claimAvailablePath(provider, `${baseName}.md`);
4527
+ const isBundle = BUNDLE_IMPORT_EXTENSIONS.has(ext);
4528
+ const desiredPath = isBundle ? `${file.name.replace(/\.[^.]+$/, "")}.md` : file.name;
4529
+ const claim = await claimAvailablePath(provider, desiredPath);
3988
4530
  claimed = claim.path;
3989
- const markdown = await readImportedMarkdown(file, ext, claim.path, provider);
3990
- await writeProviderText(provider, claim.path, markdown);
4531
+ if (isBundle) {
4532
+ const markdown = await readImportedMarkdown(file, claim.path, provider);
4533
+ await writeProviderText(provider, claim.path, markdown);
4534
+ } else {
4535
+ await writeProviderBytes(provider, claim.path, await file.arrayBuffer(), "upsert");
4536
+ }
3991
4537
  result.imported.push({ source: file.name, path: claim.path, renamed: claim.renamed });
3992
4538
  } catch (error) {
3993
4539
  if (claimed) {
@@ -4003,16 +4549,13 @@ async function importDroppedFiles(files, provider) {
4003
4549
  }
4004
4550
  function summariseImport(result) {
4005
4551
  const { imported, failed, unsupported } = result;
4006
- if (failed.length > 0) {
4007
- const detail = failed.length === 1 ? `Could not import ${failed[0].source} \u2014 ${failed[0].message}` : `Could not import ${failed.length} of ${failed.length + imported.length} files.`;
4552
+ const rejectedCount = failed.length + unsupported.length;
4553
+ if (rejectedCount > 0) {
4554
+ const detail = failed.length === 1 && unsupported.length === 0 ? `Could not import ${failed[0].source} \u2014 ${failed[0].message}` : unsupported.length === 1 && failed.length === 0 ? `${unsupported[0]} is not a file type DocBlocks can import.` : `Could not import ${rejectedCount} of ${rejectedCount + imported.length} files.`;
4008
4555
  return { kind: "error", message: detail };
4009
4556
  }
4010
4557
  if (imported.length === 0) {
4011
- if (unsupported.length === 0) return null;
4012
- return {
4013
- kind: "error",
4014
- message: unsupported.length === 1 ? `${unsupported[0]} is not a file type DocBlocks can import.` : `${unsupported.length} dropped files are not file types DocBlocks can import.`
4015
- };
4558
+ return null;
4016
4559
  }
4017
4560
  const renamed = imported.filter((entry) => entry.renamed);
4018
4561
  if (renamed.length === 1) {
@@ -4030,241 +4573,6 @@ function summariseImport(result) {
4030
4573
  return null;
4031
4574
  }
4032
4575
 
4033
- // src/DocBlocksShell/outside-in.ts
4034
- import {
4035
- FileSystemContentContainer,
4036
- FsError as FsError4,
4037
- getFileSystemProviderV2 as getFileSystemProviderV24,
4038
- parseWorkspacePath as parseWorkspacePath5
4039
- } from "@bendyline/docblocks/filesystem";
4040
- import {
4041
- createFileSystemDocumentTarget
4042
- } from "@bendyline/docblocks/document";
4043
- var OUTSIDE_IN_EXTENSION = /\.(?:html?|docx|pdf|pptx|xlsx)$/i;
4044
- var SQUISQ_RUNTIME_DIRECTORY = "_squisq";
4045
- var SQUISQ_RUNTIME_FILENAME = "squisq-player.js";
4046
- function withoutLeadingSlash(path) {
4047
- return parseWorkspacePath5(path);
4048
- }
4049
- function withLegacySlash(path, like) {
4050
- const canonical = withoutLeadingSlash(path);
4051
- return like.startsWith("/") && canonical ? `/${canonical}` : canonical;
4052
- }
4053
- function dirname(path) {
4054
- const canonical = withoutLeadingSlash(path);
4055
- const slash = canonical.lastIndexOf("/");
4056
- return slash < 0 ? "" : canonical.slice(0, slash);
4057
- }
4058
- function join2(parent, child) {
4059
- return parent ? `${parent}/${child}` : child;
4060
- }
4061
- function relativePath(fromDirectory, targetPath) {
4062
- const from = withoutLeadingSlash(fromDirectory).split("/").filter(Boolean);
4063
- const target = withoutLeadingSlash(targetPath).split("/").filter(Boolean);
4064
- let shared = 0;
4065
- while (shared < from.length && shared < target.length && from[shared] === target[shared]) {
4066
- shared++;
4067
- }
4068
- const segments = [...from.slice(shared).map(() => ".."), ...target.slice(shared)];
4069
- return segments.join("/") || ".";
4070
- }
4071
- async function readText(provider, path) {
4072
- const v2 = getFileSystemProviderV24(provider);
4073
- if (!v2) return provider.readFile(path);
4074
- const current = await v2.readFile(parseWorkspacePath5(path));
4075
- if (!current) return null;
4076
- return new TextDecoder("utf-8", { fatal: true }).decode(current.data);
4077
- }
4078
- async function readBytes(provider, path) {
4079
- const v2 = getFileSystemProviderV24(provider);
4080
- if (v2) return (await v2.readFile(parseWorkspacePath5(path)))?.data ?? null;
4081
- return provider.readBinary(path);
4082
- }
4083
- async function writeBytes(provider, path, data, mode = "upsert") {
4084
- const v2 = getFileSystemProviderV24(provider);
4085
- if (v2) {
4086
- await v2.writeFile(parseWorkspacePath5(path), data, {
4087
- mode,
4088
- createParents: true,
4089
- expectedVersion: mode === "create" ? null : void 0
4090
- });
4091
- return;
4092
- }
4093
- if (mode === "create" && await provider.exists(path)) {
4094
- throw new FsError4("already-exists", "File already exists.", { operation: "write", path });
4095
- }
4096
- await provider.writeBinary(path, data);
4097
- }
4098
- async function listCompanionFiles(provider, layout) {
4099
- try {
4100
- const entries = await provider.readDirectory(layout.companionDirectory);
4101
- return entries.filter((entry) => entry.kind === "file").map((entry) => entry.path);
4102
- } catch (error) {
4103
- if (error instanceof FsError4 && error.code === "not-found") return [];
4104
- throw error;
4105
- }
4106
- }
4107
- async function removeOutsideInCompanion(provider, layout) {
4108
- const providerV2 = getFileSystemProviderV24(provider);
4109
- if (providerV2) {
4110
- const path = parseWorkspacePath5(layout.companionDirectory);
4111
- if (await providerV2.stat(path) !== null) {
4112
- await providerV2.remove(path, { recursive: true, missing: "ignore" });
4113
- }
4114
- return;
4115
- }
4116
- if (await provider.exists(layout.companionDirectory)) {
4117
- await provider.delete(layout.companionDirectory);
4118
- }
4119
- }
4120
- async function persistImportedMedia(provider, layout, container) {
4121
- const entries = await container.listFiles();
4122
- for (const entry of entries) {
4123
- if (/\.md$/i.test(entry.path)) continue;
4124
- const data = await container.readFile(entry.path);
4125
- if (!data) continue;
4126
- await writeBytes(provider, join2(layout.companionDirectory, entry.path), data, "create");
4127
- }
4128
- }
4129
- async function loadEditableShellDocument(provider, selectedPath) {
4130
- if (!OUTSIDE_IN_EXTENSION.test(selectedPath)) {
4131
- const content = await readText(provider, selectedPath);
4132
- return content === null ? null : {
4133
- displayPath: selectedPath,
4134
- sourcePath: selectedPath,
4135
- content,
4136
- outsideIn: null,
4137
- outsideInEditingEnabled: true
4138
- };
4139
- }
4140
- const resolved = resolveOutsideInLayout(selectedPath);
4141
- if (!resolved) return null;
4142
- const layout = {
4143
- ...resolved,
4144
- targetPath: withLegacySlash(resolved.targetPath, selectedPath),
4145
- companionDirectory: withLegacySlash(resolved.companionDirectory, selectedPath),
4146
- markdownPath: withLegacySlash(resolved.markdownPath, selectedPath)
4147
- };
4148
- const candidates = (await listCompanionFiles(provider, layout)).map(
4149
- (path) => withLegacySlash(path, selectedPath)
4150
- );
4151
- const chosen = chooseOutsideInMarkdownPath(layout, candidates);
4152
- if (chosen) {
4153
- const content = await readText(provider, chosen);
4154
- if (content === null) return null;
4155
- const metadata = await readOutsideInMetadata(content);
4156
- if (metadata && metadata.format !== layout.format) {
4157
- throw new Error(
4158
- `${chosen} is configured for ${metadata.format}, not ${layout.format}. Rename the rendered file back or repair the companion frontmatter.`
4159
- );
4160
- }
4161
- const linkedContent = await withOutsideInMetadata(content, layout);
4162
- if (linkedContent !== content) await writeProviderText(provider, chosen, linkedContent);
4163
- return {
4164
- displayPath: selectedPath,
4165
- sourcePath: chosen,
4166
- content: linkedContent,
4167
- outsideIn: { ...layout, markdownPath: chosen },
4168
- outsideInEditingEnabled: await isOutsideInMarkdownEditingEnabled(linkedContent)
4169
- };
4170
- }
4171
- const rendered = await readBytes(provider, selectedPath);
4172
- if (!rendered) return null;
4173
- const imported = await importOutsideInDocument({
4174
- data: rendered,
4175
- targetPath: selectedPath
4176
- });
4177
- const importedLayout = {
4178
- ...imported.layout,
4179
- targetPath: withLegacySlash(imported.layout.targetPath, selectedPath),
4180
- companionDirectory: withLegacySlash(imported.layout.companionDirectory, selectedPath),
4181
- markdownPath: withLegacySlash(imported.layout.markdownPath, selectedPath)
4182
- };
4183
- await persistImportedMedia(provider, importedLayout, imported.container);
4184
- await writeProviderText(provider, importedLayout.markdownPath, imported.markdown, "create");
4185
- return {
4186
- displayPath: selectedPath,
4187
- sourcePath: importedLayout.markdownPath,
4188
- content: imported.markdown,
4189
- outsideIn: importedLayout,
4190
- outsideInEditingEnabled: false
4191
- };
4192
- }
4193
- async function enableOutsideInMarkdownEditing(provider, document2) {
4194
- const layout = document2.outsideIn;
4195
- if (!layout) throw new Error("This file does not support outside-in Markdown editing.");
4196
- const original = await readBytes(provider, layout.targetPath);
4197
- if (!original) throw new Error(`The rendered file "${layout.targetPath}" was not found.`);
4198
- try {
4199
- await writeBytes(provider, layout.backupPath, original, "create");
4200
- } catch (error) {
4201
- if (!(error instanceof FsError4 && error.code === "already-exists")) throw error;
4202
- }
4203
- const content = await withOutsideInMarkdownEditing(document2.content, layout);
4204
- if (content !== document2.content) await writeProviderText(provider, document2.sourcePath, content);
4205
- return { ...document2, content, outsideIn: layout, outsideInEditingEnabled: true };
4206
- }
4207
- async function findRuntimePath(provider, targetPath) {
4208
- let directory = dirname(targetPath);
4209
- for (; ; ) {
4210
- const candidateDirectory = join2(directory, SQUISQ_RUNTIME_DIRECTORY);
4211
- if (await providerEntryExists(provider, candidateDirectory)) {
4212
- const providerV2 = getFileSystemProviderV24(provider);
4213
- if (providerV2) {
4214
- const entry = await providerV2.stat(parseWorkspacePath5(candidateDirectory));
4215
- if (entry?.kind !== "directory") {
4216
- throw new Error(`${candidateDirectory} must be a directory.`);
4217
- }
4218
- }
4219
- return join2(candidateDirectory, SQUISQ_RUNTIME_FILENAME);
4220
- }
4221
- if (!directory) break;
4222
- directory = dirname(directory);
4223
- }
4224
- return join2(SQUISQ_RUNTIME_DIRECTORY, SQUISQ_RUNTIME_FILENAME);
4225
- }
4226
- async function writeRuntimeIfNeeded(provider, runtimePath) {
4227
- const { PLAYER_BUNDLE } = await import("@bendyline/squisq-react/standalone-source");
4228
- const current = await readText(provider, runtimePath);
4229
- if (current !== PLAYER_BUNDLE) await writeProviderText(provider, runtimePath, PLAYER_BUNDLE);
4230
- }
4231
- function createOutsideInDocumentTarget(provider, layout, onCommitted) {
4232
- const sourceTarget = createFileSystemDocumentTarget(provider, layout.markdownPath);
4233
- return {
4234
- key: `${provider.id}:outside-in:${parseWorkspacePath5(layout.targetPath)}`,
4235
- async commit(request) {
4236
- if (!await isOutsideInMarkdownEditingEnabled(request.content)) {
4237
- throw new Error(
4238
- "Outside-in editing is read-only until squisq-updatefrommarkdown: true is set."
4239
- );
4240
- }
4241
- const runtimePath = layout.format === "html" ? await findRuntimePath(provider, layout.targetPath) : null;
4242
- const outputDirectory = dirname(layout.targetPath);
4243
- const rendered = await renderOutsideInDocument(
4244
- {
4245
- markdown: request.content,
4246
- targetPath: layout.targetPath,
4247
- container: new FileSystemContentContainer(provider, layout.companionDirectory)
4248
- },
4249
- runtimePath ? {
4250
- html: {
4251
- playerScriptPath: relativePath(outputDirectory, runtimePath),
4252
- basePath: relativePath(outputDirectory, layout.companionDirectory)
4253
- }
4254
- } : {}
4255
- );
4256
- await sourceTarget.commit({ ...request, targetKey: sourceTarget.key });
4257
- if (runtimePath) await writeRuntimeIfNeeded(provider, runtimePath);
4258
- await writeBytes(provider, layout.targetPath, rendered.bytes);
4259
- onCommitted?.();
4260
- return {};
4261
- }
4262
- };
4263
- }
4264
- function createOutsideInContentContainer(provider, layout) {
4265
- return new FileSystemContentContainer(provider, layout.companionDirectory);
4266
- }
4267
-
4268
4576
  // src/components/usePromptDialog.ts
4269
4577
  import React2, { useCallback as useCallback11, useEffect as useEffect12, useRef as useRef11, useState as useState12 } from "react";
4270
4578
 
@@ -4980,6 +5288,21 @@ async function copyTransientWorkspaceContents(source, destination) {
4980
5288
  }
4981
5289
  }
4982
5290
 
5291
+ // src/DocBlocksShell/native-file-actions.ts
5292
+ function createNativeFileActions(entry, workspaceId, host) {
5293
+ if (entry.kind !== "file" || workspaceId === null || host === null) return [];
5294
+ return [
5295
+ {
5296
+ label: "Open containing folder",
5297
+ onSelect: () => host.shell.revealInFolder(workspaceId, entry.path)
5298
+ },
5299
+ {
5300
+ label: "Copy full path",
5301
+ onSelect: () => host.clipboard.writeWorkspacePath(workspaceId, entry.path)
5302
+ }
5303
+ ];
5304
+ }
5305
+
4983
5306
  // src/DocBlocksShell/welcome-document.ts
4984
5307
  var WELCOME_DOCUMENT_PATH = "/aboutDocBlocks.md";
4985
5308
  var WELCOME_DOCUMENT_CONTENT = [
@@ -5078,9 +5401,12 @@ function loadEditorShell() {
5078
5401
  return editorShellModulePromise;
5079
5402
  }
5080
5403
  var EditorShell = lazy2(loadEditorShell);
5081
- var GitUI = lazy2(() => import("./GitUI-CAKFVKWY.js").then((m) => ({ default: m.GitUI })));
5404
+ var GitUI = lazy2(() => import("./GitUI-L5T3XHZG.js").then((m) => ({ default: m.GitUI })));
5405
+ var GitGrantNotice = lazy2(
5406
+ () => import("./GitGrantNotice-I2VNKPLH.js").then((m) => ({ default: m.GitGrantNotice }))
5407
+ );
5082
5408
  var GitToolbarControl = lazy2(
5083
- () => import("./GitToolbarControl-VJVK2B4X.js").then((m) => ({ default: m.GitToolbarControl }))
5409
+ () => import("./GitToolbarControl-O5AXBC6T.js").then((m) => ({ default: m.GitToolbarControl }))
5084
5410
  );
5085
5411
  var DOCBLOCKS_VIDEO_EXPORT_PALETTE = Object.freeze({
5086
5412
  overlay: "rgba(0, 0, 0, 0.72)",
@@ -5351,6 +5677,8 @@ function useIsMobile(breakpoint = 768) {
5351
5677
  }, [breakpoint]);
5352
5678
  return isMobile;
5353
5679
  }
5680
+ var DESKTOP_TOOLBAR_WRAP_WIDTH = 900;
5681
+ var WEB_TOOLBAR_WRAP_WIDTH = 700;
5354
5682
  function FolderGlyph() {
5355
5683
  return /* @__PURE__ */ jsx14(
5356
5684
  "svg",
@@ -5382,12 +5710,35 @@ function FileGlyph() {
5382
5710
  }
5383
5711
  );
5384
5712
  }
5713
+ function CollapseSidebarGlyph() {
5714
+ return /* @__PURE__ */ jsxs12(
5715
+ "svg",
5716
+ {
5717
+ viewBox: "0 0 64 64",
5718
+ fill: "none",
5719
+ stroke: "currentColor",
5720
+ strokeWidth: "3",
5721
+ strokeLinecap: "round",
5722
+ strokeLinejoin: "round",
5723
+ "aria-hidden": "true",
5724
+ children: [
5725
+ /* @__PURE__ */ jsx14("rect", { x: "7", y: "10", width: "50", height: "44", rx: "6" }),
5726
+ /* @__PURE__ */ jsx14("path", { d: "M24 10v44" }),
5727
+ /* @__PURE__ */ jsx14("path", { d: "m17 25-7 7 7 7" })
5728
+ ]
5729
+ }
5730
+ );
5731
+ }
5385
5732
  function DocBlocksShell({
5386
5733
  theme: hostTheme = "auto",
5387
5734
  logoUrl,
5388
5735
  issueReportVersion,
5389
5736
  appBuildDate,
5390
5737
  ffmpegWasm,
5738
+ calcEngineFactory,
5739
+ proofing,
5740
+ proofingDefaultEnabled,
5741
+ proofingIgnoreStore,
5391
5742
  homeDocumentTitle,
5392
5743
  homeDocumentPath,
5393
5744
  allowVersioning = true,
@@ -5440,6 +5791,11 @@ function DocBlocksShell({
5440
5791
  setWriteCanvasSettings(settings);
5441
5792
  saveWriteCanvasPreferences(settings);
5442
5793
  }, []);
5794
+ const [proofingPreferences, setProofingPreferences] = useState15(loadProofingPreferences);
5795
+ const handleProofingPreferencesChange = useCallback15((settings) => {
5796
+ setProofingPreferences(settings);
5797
+ saveProofingPreferences(settings);
5798
+ }, []);
5443
5799
  const [viewPreferences, setViewPreferences] = useState15(loadViewPreferences);
5444
5800
  const handleViewPreferencesChange = useCallback15((prefs) => {
5445
5801
  setViewPreferences(prefs);
@@ -5456,6 +5812,27 @@ function DocBlocksShell({
5456
5812
  const [sidebarWidth, setSidebarWidth] = useState15(loadSidebarWidth);
5457
5813
  const [compactLayout, setCompactLayout] = useState15(false);
5458
5814
  const effectiveCompact = isMobile || compactLayout;
5815
+ const editorAreaRef = useRef15(null);
5816
+ const [desktopToolbarWrapped, setDesktopToolbarWrapped] = useState15(false);
5817
+ useEffect15(() => {
5818
+ const editorArea = editorAreaRef.current;
5819
+ if (!editorArea || effectiveCompact) {
5820
+ setDesktopToolbarWrapped(false);
5821
+ return;
5822
+ }
5823
+ const wrapWidth = isElectronHost3() ? DESKTOP_TOOLBAR_WRAP_WIDTH : WEB_TOOLBAR_WRAP_WIDTH;
5824
+ const updateWrappedState = () => {
5825
+ setDesktopToolbarWrapped(editorArea.getBoundingClientRect().width <= wrapWidth);
5826
+ };
5827
+ updateWrappedState();
5828
+ if (typeof ResizeObserver === "undefined") {
5829
+ window.addEventListener("resize", updateWrappedState);
5830
+ return () => window.removeEventListener("resize", updateWrappedState);
5831
+ }
5832
+ const observer = new ResizeObserver(updateWrappedState);
5833
+ observer.observe(editorArea);
5834
+ return () => observer.disconnect();
5835
+ }, [effectiveCompact]);
5459
5836
  const showBrowserStorageWarning = !isElectronHost3();
5460
5837
  const appVersion = isElectronHost3() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web";
5461
5838
  const issueReportUrl = buildIssueReportUrl({
@@ -5501,13 +5878,13 @@ function DocBlocksShell({
5501
5878
  lastRaw = drag.startWidth + (ev.clientX - drag.startX);
5502
5879
  if (lastRaw < SIDEBAR_COLLAPSE_THRESHOLD && sidebarRef.current) {
5503
5880
  sidebarRef.current.style.width = `${SIDEBAR_WIDTH_MIN}px`;
5504
- sidebarRef.current.style.opacity = "0.45";
5881
+ sidebarRef.current.classList.add("db-shell-sidebar--collapse-preview");
5505
5882
  return;
5506
5883
  }
5507
5884
  const clamped = Math.min(SIDEBAR_WIDTH_MAX, Math.max(SIDEBAR_WIDTH_MIN, lastRaw));
5508
5885
  if (sidebarRef.current) {
5509
5886
  sidebarRef.current.style.width = `${clamped}px`;
5510
- sidebarRef.current.style.opacity = "";
5887
+ sidebarRef.current.classList.remove("db-shell-sidebar--collapse-preview");
5511
5888
  }
5512
5889
  };
5513
5890
  const onUp = () => {
@@ -5516,7 +5893,7 @@ function DocBlocksShell({
5516
5893
  document.body.style.userSelect = "";
5517
5894
  document.body.classList.remove("db-resizing-sidebar");
5518
5895
  if (sidebarRef.current) {
5519
- sidebarRef.current.style.opacity = "";
5896
+ sidebarRef.current.classList.remove("db-shell-sidebar--collapse-preview");
5520
5897
  }
5521
5898
  if (lastRaw < SIDEBAR_COLLAPSE_THRESHOLD) {
5522
5899
  setCompactLayout(true);
@@ -5543,6 +5920,11 @@ function DocBlocksShell({
5543
5920
  const [workspaceStartupError, setWorkspaceStartupError] = useState15(null);
5544
5921
  const [activeWorkspaceId, setActiveWorkspaceId] = useState15(null);
5545
5922
  const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] = useState15(null);
5923
+ const defaultProofingIgnoreStore = useMemo3(
5924
+ () => createLocalProofingIgnoreStore(() => activeWorkspaceId),
5925
+ [activeWorkspaceId]
5926
+ );
5927
+ const effectiveProofingIgnoreStore = proofingIgnoreStore === void 0 ? defaultProofingIgnoreStore : proofingIgnoreStore;
5546
5928
  useEffect15(() => {
5547
5929
  if (!provider || getTransientWorkspace(provider.id)) return;
5548
5930
  const providerV2 = getFileSystemProviderV27(provider);
@@ -5670,8 +6052,8 @@ function DocBlocksShell({
5670
6052
  cancelled = true;
5671
6053
  };
5672
6054
  }, [activeWorkspaceDescriptor]);
5673
- const gitWorkspaceId = provider && activeWorkspaceDescriptor?.id === activeWorkspaceId && provider.id === activeWorkspaceId && activeWorkspaceDescriptor.type === "electron-native" ? activeWorkspaceDescriptor.id : null;
5674
- const git = useGit(provider, gitWorkspaceId, resolvedTheme);
6055
+ const nativeWorkspaceId = provider && activeWorkspaceDescriptor?.id === activeWorkspaceId && provider.id === activeWorkspaceId && activeWorkspaceDescriptor.type === "electron-native" ? activeWorkspaceDescriptor.id : null;
6056
+ const git = useGit(provider, nativeWorkspaceId, resolvedTheme);
5675
6057
  const gitRef = useRef15(git);
5676
6058
  gitRef.current = git;
5677
6059
  const { scheduleRefresh: gitScheduleRefresh } = git;
@@ -5699,11 +6081,13 @@ function DocBlocksShell({
5699
6081
  const [selectedSourceFile, setSelectedSourceFile] = useState15(null);
5700
6082
  const [selectedOutsideIn, setSelectedOutsideIn] = useState15(null);
5701
6083
  const [selectedOutsideInEditingEnabled, setSelectedOutsideInEditingEnabled] = useState15(false);
6084
+ const [selectedImage, setSelectedImage] = useState15(void 0);
5702
6085
  const adoptSelectedDocument = useCallback15((document2) => {
5703
6086
  setSelectedFile(document2?.displayPath ?? null);
5704
6087
  setSelectedSourceFile(document2?.sourcePath ?? null);
5705
6088
  setSelectedOutsideIn(document2?.outsideIn ?? null);
5706
6089
  setSelectedOutsideInEditingEnabled(document2?.outsideInEditingEnabled ?? false);
6090
+ setSelectedImage(document2?.image);
5707
6091
  }, []);
5708
6092
  const adoptRegularDocument = useCallback15((path) => {
5709
6093
  if (path === null) {
@@ -5711,13 +6095,27 @@ function DocBlocksShell({
5711
6095
  setSelectedSourceFile(null);
5712
6096
  setSelectedOutsideIn(null);
5713
6097
  setSelectedOutsideInEditingEnabled(false);
6098
+ setSelectedImage(void 0);
5714
6099
  return;
5715
6100
  }
5716
6101
  setSelectedFile(path);
5717
6102
  setSelectedSourceFile(path);
5718
6103
  setSelectedOutsideIn(null);
5719
6104
  setSelectedOutsideInEditingEnabled(false);
6105
+ setSelectedImage(void 0);
5720
6106
  }, []);
6107
+ const selectedImageUrl = useMemo3(() => {
6108
+ if (!selectedImage || typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
6109
+ return void 0;
6110
+ }
6111
+ return URL.createObjectURL(new Blob([selectedImage.data], { type: selectedImage.mimeType }));
6112
+ }, [selectedImage]);
6113
+ useEffect15(
6114
+ () => () => {
6115
+ if (selectedImageUrl) URL.revokeObjectURL(selectedImageUrl);
6116
+ },
6117
+ [selectedImageUrl]
6118
+ );
5721
6119
  useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath);
5722
6120
  const exportDestinationAdapter = useMemo3(() => {
5723
6121
  if (!selectedFile) return void 0;
@@ -6580,7 +6978,7 @@ function DocBlocksShell({
6580
6978
  const providerV2 = getFileSystemProviderV27(provider);
6581
6979
  if (!providerV2?.capabilities.watch) return;
6582
6980
  const watchedFile = selectedSourceFile ?? selectedFile;
6583
- if (!watchedFile || !documentSnapshot.targetKey) return;
6981
+ if (!watchedFile || !documentSnapshot.targetKey || selectedImage) return;
6584
6982
  const targetKey = documentSnapshot.targetKey;
6585
6983
  let disposed = false;
6586
6984
  let reading = false;
@@ -6634,7 +7032,14 @@ function DocBlocksShell({
6634
7032
  disposed = true;
6635
7033
  void subscription.dispose();
6636
7034
  };
6637
- }, [provider, selectedFile, selectedSourceFile, documentSession, documentSnapshot.targetKey]);
7035
+ }, [
7036
+ provider,
7037
+ selectedFile,
7038
+ selectedSourceFile,
7039
+ selectedImage,
7040
+ documentSession,
7041
+ documentSnapshot.targetKey
7042
+ ]);
6638
7043
  const transitionAwayFromDocument = useCallback15(
6639
7044
  async (requestId) => {
6640
7045
  if (requestId !== navigationRequestRef.current) return false;
@@ -7285,6 +7690,15 @@ function DocBlocksShell({
7285
7690
  await getDocBlocksHost().shell.revealInFolder(ws.id);
7286
7691
  }
7287
7692
  }, [activeWorkspaceId]);
7693
+ const handleOpenWorkspaceFolder = useCallback15(() => {
7694
+ if (!isElectronHost3() || !nativeWorkspaceId) return;
7695
+ void getDocBlocksHost().shell.openWorkspaceFolder(nativeWorkspaceId).catch((error) => {
7696
+ showToast(
7697
+ "error",
7698
+ error instanceof Error ? error.message : "Could not open this workspace folder."
7699
+ );
7700
+ });
7701
+ }, [nativeWorkspaceId, showToast]);
7288
7702
  useEffect15(() => {
7289
7703
  if (!isElectronHost3()) return;
7290
7704
  const host = getDocBlocksHost();
@@ -7481,6 +7895,13 @@ function DocBlocksShell({
7481
7895
  },
7482
7896
  [handleEnableOutsideInEditing, selectedFile, selectedOutsideInEditingEnabled]
7483
7897
  );
7898
+ const actionsForEntry = useCallback15(
7899
+ (entry) => {
7900
+ const nativeActions = isElectronHost3() ? createNativeFileActions(entry, nativeWorkspaceId, getDocBlocksHost()) : [];
7901
+ return [...nativeActions, ...outsideInActionsForEntry(entry)];
7902
+ },
7903
+ [nativeWorkspaceId, outsideInActionsForEntry]
7904
+ );
7484
7905
  const handleEditorLinkClick = useCallback15(
7485
7906
  (href) => {
7486
7907
  const target = resolveShellEditorLinkTarget(href, selectedSourceFile ?? selectedFile);
@@ -7630,7 +8051,7 @@ function DocBlocksShell({
7630
8051
  const nextFile = selectedFile ? relocateProviderPath(selectedFile, change.oldPath, change.newPath) : null;
7631
8052
  const nextFolder = selectedFolder ? relocateProviderPath(selectedFolder, change.oldPath, change.newPath) : null;
7632
8053
  if (nextFile !== selectedFile) {
7633
- if (nextFile && selectedOutsideIn) {
8054
+ if (nextFile) {
7634
8055
  const opened = await loadEditableShellDocument(provider, nextFile);
7635
8056
  adoptSelectedDocument(opened);
7636
8057
  } else {
@@ -7675,7 +8096,6 @@ function DocBlocksShell({
7675
8096
  adoptRegularDocument,
7676
8097
  adoptSelectedDocument,
7677
8098
  selectedFile,
7678
- selectedOutsideIn,
7679
8099
  selectedFolder,
7680
8100
  activeWorkspaceId,
7681
8101
  pinnedDocuments,
@@ -8304,7 +8724,7 @@ function DocBlocksShell({
8304
8724
  return /* @__PURE__ */ jsx14(
8305
8725
  "div",
8306
8726
  {
8307
- className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}`,
8727
+ className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}${desktopToolbarWrapped ? " db-shell--desktop-toolbar-wrapped" : ""}`,
8308
8728
  "data-theme": resolvedTheme,
8309
8729
  "data-accent": accentColor,
8310
8730
  "data-document-status": documentSnapshot.status,
@@ -8365,6 +8785,8 @@ function DocBlocksShell({
8365
8785
  onAccentColorChange: handleAccentColorChange,
8366
8786
  writeCanvasSettings,
8367
8787
  onWriteCanvasSettingsChange: handleWriteCanvasSettingsChange,
8788
+ proofingPreferences,
8789
+ onProofingPreferencesChange: handleProofingPreferencesChange,
8368
8790
  versioningPreference,
8369
8791
  onVersioningPreferenceChange: handleVersioningPreferenceChange,
8370
8792
  onDownloadAllWorkspaces: handleDownloadAllWorkspaces,
@@ -8431,7 +8853,8 @@ function DocBlocksShell({
8431
8853
  onPinnedDocumentDelete: handlePinnedDocumentDelete,
8432
8854
  onTogglePin: handleTogglePin,
8433
8855
  onSelect: handleSelect,
8434
- actionsForEntry: outsideInActionsForEntry,
8856
+ onOpenWorkspaceFolder: isElectronHost3() && nativeWorkspaceId ? handleOpenWorkspaceFolder : void 0,
8857
+ actionsForEntry,
8435
8858
  onTreeMutation: handleTreeMutation,
8436
8859
  onTreeChange: handleTreeChange,
8437
8860
  onImportFiles: handleImportFiles,
@@ -8475,6 +8898,7 @@ function DocBlocksShell({
8475
8898
  ]
8476
8899
  }
8477
8900
  ),
8901
+ git.available && git.pendingGrant && /* @__PURE__ */ jsx14(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx14(GitGrantNotice, {}) }),
8478
8902
  /* @__PURE__ */ jsxs12("div", { className: "db-shell-sidebar-footer", children: [
8479
8903
  /* @__PURE__ */ jsx14("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
8480
8904
  /* @__PURE__ */ jsx14("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
@@ -8494,6 +8918,10 @@ function DocBlocksShell({
8494
8918
  }
8495
8919
  )
8496
8920
  ] })
8921
+ ] }),
8922
+ /* @__PURE__ */ jsxs12("div", { className: "db-sidebar-collapse-preview", "aria-hidden": "true", children: [
8923
+ /* @__PURE__ */ jsx14("span", { className: "db-sidebar-collapse-preview-icon", children: /* @__PURE__ */ jsx14(CollapseSidebarGlyph, {}) }),
8924
+ /* @__PURE__ */ jsx14("span", { className: "db-sidebar-collapse-preview-label", children: "Release to hide files" })
8497
8925
  ] })
8498
8926
  ]
8499
8927
  }
@@ -8511,6 +8939,7 @@ function DocBlocksShell({
8511
8939
  (!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs12(
8512
8940
  "main",
8513
8941
  {
8942
+ ref: editorAreaRef,
8514
8943
  "aria-label": "Document editor",
8515
8944
  className: updateAvailable && onApplyUpdate && updateStatusBarVisible ? "db-shell-editor-area db-shell-editor-area--has-update" : "db-shell-editor-area",
8516
8945
  style: {
@@ -8530,11 +8959,13 @@ function DocBlocksShell({
8530
8959
  EditorShell,
8531
8960
  {
8532
8961
  initialMarkdown: editorContent,
8533
- readOnly: selectedOutsideIn !== null && !selectedOutsideInEditingEnabled,
8962
+ readOnly: selectedImage !== void 0 || selectedOutsideIn !== null && !selectedOutsideInEditingEnabled,
8534
8963
  initialView,
8535
8964
  defaultViewportPreset: defaultPreviewViewportPreset,
8536
8965
  articleId: selectedFile,
8537
8966
  fileName: selectedFile,
8967
+ imageSrc: selectedImageUrl,
8968
+ imageAlt: basenameOf(selectedFile),
8538
8969
  saveCoverImageOutput: saveRenderedImageOutput,
8539
8970
  saveDashboardImageOutput: saveRenderedImageOutput,
8540
8971
  onChange: handleEditorChange,
@@ -8545,6 +8976,12 @@ function DocBlocksShell({
8545
8976
  placeholder: editorPlaceholder,
8546
8977
  outlineWidth: 280,
8547
8978
  mediaProvider,
8979
+ calcEngineFactory,
8980
+ proofing,
8981
+ proofingDefaultEnabled,
8982
+ proofingSpellingEnabled: proofingPreferences.spelling,
8983
+ proofingGrammarEnabled: proofingPreferences.grammar,
8984
+ proofingIgnoreStore: effectiveProofingIgnoreStore,
8548
8985
  showCodeCopyButton,
8549
8986
  onCopyCode,
8550
8987
  allowRecording,
@@ -8552,7 +8989,7 @@ function DocBlocksShell({
8552
8989
  allowPresentationFullscreen,
8553
8990
  documentLinkProvider,
8554
8991
  workspaceContainer: versionsContainer ?? void 0,
8555
- allowVersioning: effectiveVersioning,
8992
+ allowVersioning: selectedImage === void 0 && effectiveVersioning,
8556
8993
  viewPreferences,
8557
8994
  onViewPreferencesChange: handleViewPreferencesChange,
8558
8995
  versionBasename: versionBasename ?? stripExtension(basenameOf(selectedFile)),
@@ -8695,12 +9132,14 @@ export {
8695
9132
  AccentColorSettings,
8696
9133
  AppMenu,
8697
9134
  DEFAULT_OPTIONS,
9135
+ DEFAULT_PROOFING_PREFERENCES,
8698
9136
  DEFAULT_WRITE_CANVAS_FONT_SCHEME,
8699
9137
  DocBlocksShell,
8700
9138
  ExportDialog,
8701
9139
  ExportToolbarControls,
8702
9140
  FileExplorer,
8703
9141
  FileTreeNode,
9142
+ ProofingSettingsControls,
8704
9143
  SettingsDialog,
8705
9144
  ThemeSettings,
8706
9145
  WRITE_CANVAS_FONT_SCHEMES,
@@ -8711,8 +9150,10 @@ export {
8711
9150
  createImageSaveOutput as createDashboardImageSaveOutput,
8712
9151
  createImageSaveOutput,
8713
9152
  loadLastExportOptions,
9153
+ loadProofingPreferences,
8714
9154
  resolveWriteCanvasFonts,
8715
9155
  runExport,
9156
+ saveProofingPreferences,
8716
9157
  updateExportTargetExtension,
8717
9158
  useDocumentSession,
8718
9159
  useFileTree