@bendyline/docblocks-react 2.5.0 → 2.6.1

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
@@ -1588,54 +1597,654 @@ function isHiddenFileEntry(entry) {
1588
1597
  if (name.startsWith(".")) return true;
1589
1598
  return entry.kind === "directory" && name.endsWith("_files");
1590
1599
  }
1591
- function filterVisibleFileEntries(entries) {
1592
- return entries.filter((entry) => !isHiddenFileEntry(entry));
1600
+ function filterVisibleFileEntries(entries) {
1601
+ return entries.filter((entry) => !isHiddenFileEntry(entry));
1602
+ }
1603
+
1604
+ // src/FileExplorer/entry-sort.ts
1605
+ function compareText(left, right) {
1606
+ return left < right ? -1 : left > right ? 1 : 0;
1607
+ }
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
+ function isOutsideInDataFormat(format) {
1801
+ return format === "csv" || format === "xlsx";
1802
+ }
1803
+ async function importOutsideInDataSidecar(source) {
1804
+ const layout = resolveOutsideInLayout(source.targetPath);
1805
+ if (!layout || !isOutsideInDataFormat(layout.format)) {
1806
+ throw new Error(`Outside-in data import does not support "${source.targetPath}".`);
1807
+ }
1808
+ const data = source.data instanceof ArrayBuffer ? source.data : source.data.buffer.slice(
1809
+ source.data.byteOffset,
1810
+ source.data.byteOffset + source.data.byteLength
1811
+ );
1812
+ const sourceName = source.targetPath.replace(/\\/g, "/").split("/").pop() ?? `data.${layout.format}`;
1813
+ const container = layout.format === "csv" ? await import("@bendyline/squisq-formats/csv").then(
1814
+ ({ csvToContainer }) => csvToContainer(data, { sourceName, sidecar: "always" })
1815
+ ) : await import("@bendyline/squisq-formats/xlsx").then(
1816
+ ({ xlsxToContainer }) => xlsxToContainer(data, {
1817
+ sourceName,
1818
+ sidecar: "always",
1819
+ sheetHeadings: false
1820
+ })
1821
+ );
1822
+ const importedMarkdown = await container.readDocument();
1823
+ if (importedMarkdown === null) {
1824
+ throw new Error(
1825
+ `The ${layout.format.toUpperCase()} importer did not create a Markdown companion.`
1826
+ );
1827
+ }
1828
+ return {
1829
+ layout,
1830
+ markdown: await withOutsideInMetadata(importedMarkdown, layout),
1831
+ container
1832
+ };
1833
+ }
1834
+ async function renderOutsideInDocument(source, options = {}) {
1835
+ const { renderOutsideInDocument: renderDocument } = await loadOutsideInModule();
1836
+ return renderDocument(source, options);
1837
+ }
1838
+
1839
+ // src/DocBlocksShell/outside-in.ts
1840
+ var OUTSIDE_IN_EXTENSION = /\.(?:html?|docx|pdf|pptx|xlsx|csv)$/i;
1841
+ var IMAGE_MIME_TYPES = {
1842
+ avif: "image/avif",
1843
+ bmp: "image/bmp",
1844
+ gif: "image/gif",
1845
+ ico: "image/x-icon",
1846
+ jpeg: "image/jpeg",
1847
+ jpg: "image/jpeg",
1848
+ png: "image/png",
1849
+ webp: "image/webp"
1850
+ };
1851
+ var SQUISQ_RUNTIME_DIRECTORY = "_squisq";
1852
+ var SQUISQ_RUNTIME_FILENAME = "squisq-player.js";
1853
+ function defaultOutsideInLayoutMode(outsideIn) {
1854
+ return outsideIn && isOutsideInDataFormat(outsideIn.format) ? "block" : void 0;
1855
+ }
1856
+ function withoutLeadingSlash(path) {
1857
+ return parseWorkspacePath3(path);
1858
+ }
1859
+ function withLegacySlash(path, like) {
1860
+ const canonical = withoutLeadingSlash(path);
1861
+ return like.startsWith("/") && canonical ? `/${canonical}` : canonical;
1862
+ }
1863
+ function dirname(path) {
1864
+ const canonical = withoutLeadingSlash(path);
1865
+ const slash = canonical.lastIndexOf("/");
1866
+ return slash < 0 ? "" : canonical.slice(0, slash);
1867
+ }
1868
+ function join2(parent, child) {
1869
+ return parent ? `${parent}/${child}` : child;
1870
+ }
1871
+ function relativePath(fromDirectory, targetPath) {
1872
+ const from = withoutLeadingSlash(fromDirectory).split("/").filter(Boolean);
1873
+ const target = withoutLeadingSlash(targetPath).split("/").filter(Boolean);
1874
+ let shared = 0;
1875
+ while (shared < from.length && shared < target.length && from[shared] === target[shared]) {
1876
+ shared++;
1877
+ }
1878
+ const segments = [...from.slice(shared).map(() => ".."), ...target.slice(shared)];
1879
+ return segments.join("/") || ".";
1880
+ }
1881
+ async function readText(provider, path) {
1882
+ const v2 = getFileSystemProviderV23(provider);
1883
+ if (!v2) return provider.readFile(path);
1884
+ const current = await v2.readFile(parseWorkspacePath3(path));
1885
+ if (!current) return null;
1886
+ return new TextDecoder("utf-8", { fatal: true }).decode(current.data);
1887
+ }
1888
+ async function readBytes(provider, path) {
1889
+ const v2 = getFileSystemProviderV23(provider);
1890
+ if (v2) return (await v2.readFile(parseWorkspacePath3(path)))?.data ?? null;
1891
+ return provider.readBinary(path);
1892
+ }
1893
+ async function writeBytes(provider, path, data, mode = "upsert") {
1894
+ const v2 = getFileSystemProviderV23(provider);
1895
+ if (v2) {
1896
+ await v2.writeFile(parseWorkspacePath3(path), data, {
1897
+ mode,
1898
+ createParents: true,
1899
+ expectedVersion: mode === "create" ? null : void 0
1900
+ });
1901
+ return;
1902
+ }
1903
+ if (mode === "create" && await provider.exists(path)) {
1904
+ throw new FsError3("already-exists", "File already exists.", { operation: "write", path });
1905
+ }
1906
+ await provider.writeBinary(path, data);
1907
+ }
1908
+ async function listCompanionFiles(provider, layout) {
1909
+ try {
1910
+ const entries = await provider.readDirectory(layout.companionDirectory);
1911
+ return entries.filter((entry) => entry.kind === "file").map((entry) => entry.path);
1912
+ } catch (error) {
1913
+ if (error instanceof FsError3 && error.code === "not-found") return [];
1914
+ throw error;
1915
+ }
1916
+ }
1917
+ async function removeOutsideInCompanion(provider, layout) {
1918
+ const providerV2 = getFileSystemProviderV23(provider);
1919
+ if (providerV2) {
1920
+ const path = parseWorkspacePath3(layout.companionDirectory);
1921
+ if (await providerV2.stat(path) !== null) {
1922
+ await providerV2.remove(path, { recursive: true, missing: "ignore" });
1923
+ }
1924
+ return;
1925
+ }
1926
+ if (await provider.exists(layout.companionDirectory)) {
1927
+ await provider.delete(layout.companionDirectory);
1928
+ }
1929
+ }
1930
+ async function persistImportedMedia(provider, layout, container) {
1931
+ const entries = await container.listFiles();
1932
+ for (const entry of entries) {
1933
+ if (/\.md$/i.test(entry.path)) continue;
1934
+ const data = await container.readFile(entry.path);
1935
+ if (!data) continue;
1936
+ await writeBytes(provider, join2(layout.companionDirectory, entry.path), data, "create");
1937
+ }
1938
+ }
1939
+ async function loadEditableShellDocument(provider, selectedPath) {
1940
+ const imageExtension = selectedPath.slice(selectedPath.lastIndexOf(".") + 1).toLowerCase();
1941
+ const imageMimeType = IMAGE_MIME_TYPES[imageExtension];
1942
+ if (imageMimeType) {
1943
+ const data = await readBytes(provider, selectedPath);
1944
+ return data === null ? null : {
1945
+ displayPath: selectedPath,
1946
+ sourcePath: selectedPath,
1947
+ content: "",
1948
+ outsideIn: null,
1949
+ outsideInEditingEnabled: false,
1950
+ image: { data, mimeType: imageMimeType }
1951
+ };
1952
+ }
1953
+ if (!OUTSIDE_IN_EXTENSION.test(selectedPath)) {
1954
+ const content = await readText(provider, selectedPath);
1955
+ return content === null ? null : {
1956
+ displayPath: selectedPath,
1957
+ sourcePath: selectedPath,
1958
+ content,
1959
+ outsideIn: null,
1960
+ outsideInEditingEnabled: true
1961
+ };
1962
+ }
1963
+ const resolved = resolveOutsideInLayout(selectedPath);
1964
+ if (!resolved) return null;
1965
+ const layout = {
1966
+ ...resolved,
1967
+ targetPath: withLegacySlash(resolved.targetPath, selectedPath),
1968
+ companionDirectory: withLegacySlash(resolved.companionDirectory, selectedPath),
1969
+ markdownPath: withLegacySlash(resolved.markdownPath, selectedPath)
1970
+ };
1971
+ const candidates = (await listCompanionFiles(provider, layout)).map(
1972
+ (path) => withLegacySlash(path, selectedPath)
1973
+ );
1974
+ const chosen = chooseOutsideInMarkdownPath(layout, candidates);
1975
+ if (chosen) {
1976
+ const content = await readText(provider, chosen);
1977
+ if (content === null) return null;
1978
+ const metadata = await readOutsideInMetadata(content);
1979
+ if (metadata && metadata.format !== layout.format) {
1980
+ throw new Error(
1981
+ `${chosen} is configured for ${metadata.format}, not ${layout.format}. Rename the rendered file back or repair the companion frontmatter.`
1982
+ );
1983
+ }
1984
+ const linkedContent = await withOutsideInMetadata(content, layout);
1985
+ if (linkedContent !== content) await writeProviderText(provider, chosen, linkedContent);
1986
+ return {
1987
+ displayPath: selectedPath,
1988
+ sourcePath: chosen,
1989
+ content: linkedContent,
1990
+ outsideIn: { ...layout, markdownPath: chosen },
1991
+ outsideInEditingEnabled: await isOutsideInMarkdownEditingEnabled(linkedContent)
1992
+ };
1993
+ }
1994
+ const rendered = await readBytes(provider, selectedPath);
1995
+ if (!rendered) return null;
1996
+ const imported = await importOutsideInDocument({
1997
+ data: rendered,
1998
+ targetPath: selectedPath
1999
+ });
2000
+ const importedLayout = {
2001
+ ...imported.layout,
2002
+ targetPath: withLegacySlash(imported.layout.targetPath, selectedPath),
2003
+ companionDirectory: withLegacySlash(imported.layout.companionDirectory, selectedPath),
2004
+ markdownPath: withLegacySlash(imported.layout.markdownPath, selectedPath)
2005
+ };
2006
+ await persistImportedMedia(provider, importedLayout, imported.container);
2007
+ await writeProviderText(provider, importedLayout.markdownPath, imported.markdown, "create");
2008
+ return {
2009
+ displayPath: selectedPath,
2010
+ sourcePath: importedLayout.markdownPath,
2011
+ content: imported.markdown,
2012
+ outsideIn: importedLayout,
2013
+ outsideInEditingEnabled: false
2014
+ };
2015
+ }
2016
+ async function enableOutsideInMarkdownEditing(provider, document2) {
2017
+ const layout = document2.outsideIn;
2018
+ if (!layout) throw new Error("This file does not support outside-in Markdown editing.");
2019
+ const original = await readBytes(provider, layout.targetPath);
2020
+ if (!original) throw new Error(`The rendered file "${layout.targetPath}" was not found.`);
2021
+ try {
2022
+ await writeBytes(provider, layout.backupPath, original, "create");
2023
+ } catch (error) {
2024
+ if (!(error instanceof FsError3 && error.code === "already-exists")) throw error;
2025
+ }
2026
+ const content = await withOutsideInMarkdownEditing(document2.content, layout);
2027
+ if (content !== document2.content) await writeProviderText(provider, document2.sourcePath, content);
2028
+ return { ...document2, content, outsideIn: layout, outsideInEditingEnabled: true };
2029
+ }
2030
+ async function findRuntimePath(provider, targetPath) {
2031
+ let directory = dirname(targetPath);
2032
+ for (; ; ) {
2033
+ const candidateDirectory = join2(directory, SQUISQ_RUNTIME_DIRECTORY);
2034
+ if (await providerEntryExists(provider, candidateDirectory)) {
2035
+ const providerV2 = getFileSystemProviderV23(provider);
2036
+ if (providerV2) {
2037
+ const entry = await providerV2.stat(parseWorkspacePath3(candidateDirectory));
2038
+ if (entry?.kind !== "directory") {
2039
+ throw new Error(`${candidateDirectory} must be a directory.`);
2040
+ }
2041
+ }
2042
+ return join2(candidateDirectory, SQUISQ_RUNTIME_FILENAME);
2043
+ }
2044
+ if (!directory) break;
2045
+ directory = dirname(directory);
2046
+ }
2047
+ return join2(SQUISQ_RUNTIME_DIRECTORY, SQUISQ_RUNTIME_FILENAME);
2048
+ }
2049
+ async function writeRuntimeIfNeeded(provider, runtimePath) {
2050
+ const { PLAYER_BUNDLE } = await import("@bendyline/squisq-react/standalone-source");
2051
+ const current = await readText(provider, runtimePath);
2052
+ if (current !== PLAYER_BUNDLE) await writeProviderText(provider, runtimePath, PLAYER_BUNDLE);
1593
2053
  }
1594
-
1595
- // src/FileExplorer/entry-sort.ts
1596
- function compareText(left, right) {
1597
- return left < right ? -1 : left > right ? 1 : 0;
2054
+ async function renderPlainOutsideInHtml(provider, layout, markdown) {
2055
+ const [{ parseMarkdown }, { markdownDocToPlainHtml }] = await Promise.all([
2056
+ import("@bendyline/squisq/markdown"),
2057
+ import("@bendyline/squisq-formats/html")
2058
+ ]);
2059
+ const container = new FileSystemContentContainer(provider, layout.companionDirectory);
2060
+ const images = /* @__PURE__ */ new Map();
2061
+ for (const entry of await container.listFiles()) {
2062
+ const outputPath = `${layout.companionName}/${entry.path}`;
2063
+ images.set(entry.path, outputPath);
2064
+ images.set(`./${entry.path}`, outputPath);
2065
+ }
2066
+ return new TextEncoder().encode(
2067
+ markdownDocToPlainHtml(parseMarkdown(markdown), {
2068
+ title: layout.stem,
2069
+ images: images.size > 0 ? images : void 0
2070
+ })
2071
+ );
1598
2072
  }
1599
- function modifiedTime(entry) {
1600
- if (entry.kind !== "file" || !entry.lastModified) return null;
1601
- const timestamp = Date.parse(entry.lastModified);
1602
- return Number.isNaN(timestamp) ? null : timestamp;
2073
+ async function prepareOutsideInRender(provider, layout, markdown) {
2074
+ const htmlOutput = layout.format === "html" ? await readOutsideInHtmlOutput(markdown) : null;
2075
+ if (htmlOutput === "static") {
2076
+ return {
2077
+ bytes: await renderPlainOutsideInHtml(provider, layout, markdown),
2078
+ runtimePath: null
2079
+ };
2080
+ }
2081
+ const runtimePath = layout.format === "html" ? await findRuntimePath(provider, layout.targetPath) : null;
2082
+ const outputDirectory = dirname(layout.targetPath);
2083
+ const rendered = await renderOutsideInDocument(
2084
+ {
2085
+ markdown,
2086
+ targetPath: layout.targetPath,
2087
+ container: new FileSystemContentContainer(provider, layout.companionDirectory)
2088
+ },
2089
+ runtimePath ? {
2090
+ html: {
2091
+ playerScriptPath: relativePath(outputDirectory, runtimePath),
2092
+ basePath: relativePath(outputDirectory, layout.companionDirectory)
2093
+ },
2094
+ ...htmlOutput === "interactive" ? { formatOptions: { html: { mode: "slideshow" } } } : {}
2095
+ } : {}
2096
+ );
2097
+ return { bytes: rendered.bytes, runtimePath };
1603
2098
  }
1604
- function sortFileEntries(entries, mode) {
1605
- return [...entries].sort((left, right) => {
1606
- if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
1607
- if (left.kind === "directory" || right.kind === "directory" || mode === "name") {
1608
- return compareText(left.name, right.name);
1609
- }
1610
- const leftTime = modifiedTime(left);
1611
- const rightTime = modifiedTime(right);
1612
- if (leftTime !== null && rightTime !== null && leftTime !== rightTime) {
1613
- return rightTime - leftTime;
2099
+ async function createNewOutsideInDocument(provider, targetPath, htmlOutput) {
2100
+ const layout = resolveOutsideInLayout(targetPath);
2101
+ if (!layout) throw new Error(`Outside-in editing does not support "${targetPath}".`);
2102
+ if (layout.format !== "html" && htmlOutput !== void 0) {
2103
+ throw new Error("Only Web pages have an HTML output style.");
2104
+ }
2105
+ if (await providerEntryExists(provider, layout.targetPath) || await providerEntryExists(provider, layout.companionDirectory)) {
2106
+ throw new FsError3(
2107
+ "already-exists",
2108
+ "A file or companion folder with that name already exists.",
2109
+ {
2110
+ operation: "write",
2111
+ path: layout.targetPath
2112
+ }
2113
+ );
2114
+ }
2115
+ let content = await withOutsideInMarkdownEditing(`# ${layout.stem}
2116
+ `, layout);
2117
+ if (layout.format === "html") {
2118
+ content = await withOutsideInHtmlOutput(content, htmlOutput ?? "interactive");
2119
+ }
2120
+ const prepared = await prepareOutsideInRender(provider, layout, content);
2121
+ if (prepared.runtimePath) await writeRuntimeIfNeeded(provider, prepared.runtimePath);
2122
+ await writeBytes(provider, layout.targetPath, prepared.bytes, "create");
2123
+ await writeProviderText(provider, layout.markdownPath, content, "create");
2124
+ return {
2125
+ displayPath: layout.targetPath,
2126
+ sourcePath: layout.markdownPath,
2127
+ content,
2128
+ outsideIn: layout,
2129
+ outsideInEditingEnabled: true
2130
+ };
2131
+ }
2132
+ function createOutsideInDocumentTarget(provider, layout, onCommitted) {
2133
+ const sourceTarget = createFileSystemDocumentTarget(provider, layout.markdownPath);
2134
+ return {
2135
+ key: `${provider.id}:outside-in:${parseWorkspacePath3(layout.targetPath)}`,
2136
+ async commit(request) {
2137
+ if (!await isOutsideInMarkdownEditingEnabled(request.content)) {
2138
+ throw new Error(
2139
+ "Outside-in editing is read-only until squisq-updatefrommarkdown: true is set."
2140
+ );
2141
+ }
2142
+ const rendered = await prepareOutsideInRender(provider, layout, request.content);
2143
+ await sourceTarget.commit({ ...request, targetKey: sourceTarget.key });
2144
+ if (rendered.runtimePath) await writeRuntimeIfNeeded(provider, rendered.runtimePath);
2145
+ await writeBytes(provider, layout.targetPath, rendered.bytes);
2146
+ onCommitted?.();
2147
+ return {};
1614
2148
  }
1615
- if (leftTime === null && rightTime !== null) return 1;
1616
- if (leftTime !== null && rightTime === null) return -1;
1617
- return compareText(left.name, right.name);
1618
- });
2149
+ };
2150
+ }
2151
+ function createOutsideInContentContainer(provider, layout) {
2152
+ return new FileSystemContentContainer(provider, layout.companionDirectory);
1619
2153
  }
1620
2154
 
1621
- // src/FileExplorer/FileExplorer.tsx
1622
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1623
- var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
1624
- ".txt",
1625
- ".md",
2155
+ // src/DocBlocksShell/import-file-types.ts
2156
+ var OUTSIDE_IN_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([
2157
+ ".csv",
2158
+ ".docx",
1626
2159
  ".html",
1627
2160
  ".htm",
1628
- ".docx",
1629
2161
  ".pdf",
1630
2162
  ".pptx",
1631
- ".xlsx",
1632
- ".dbk",
1633
- ".zip"
2163
+ ".xlsx"
1634
2164
  ]);
2165
+ var BUNDLE_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([".dbk", ".zip"]);
2166
+ var DIRECT_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([
2167
+ ".avif",
2168
+ ".bash",
2169
+ ".bmp",
2170
+ ".c",
2171
+ ".cc",
2172
+ ".cpp",
2173
+ ".cs",
2174
+ ".css",
2175
+ ".gif",
2176
+ ".go",
2177
+ ".h",
2178
+ ".hpp",
2179
+ ".ico",
2180
+ ".ini",
2181
+ ".java",
2182
+ ".jpeg",
2183
+ ".jpg",
2184
+ ".js",
2185
+ ".json",
2186
+ ".jsonc",
2187
+ ".jsx",
2188
+ ".kt",
2189
+ ".kts",
2190
+ ".less",
2191
+ ".log",
2192
+ ".lua",
2193
+ ".markdown",
2194
+ ".md",
2195
+ ".mdown",
2196
+ ".mjs",
2197
+ ".php",
2198
+ ".png",
2199
+ ".py",
2200
+ ".rb",
2201
+ ".rs",
2202
+ ".scss",
2203
+ ".sh",
2204
+ ".sql",
2205
+ ".svg",
2206
+ ".swift",
2207
+ ".toml",
2208
+ ".ts",
2209
+ ".tsv",
2210
+ ".tsx",
2211
+ ".txt",
2212
+ ".webp",
2213
+ ".xml",
2214
+ ".yaml",
2215
+ ".yml",
2216
+ ".zsh"
2217
+ ]);
2218
+ var DIRECT_IMPORT_BASENAMES = /* @__PURE__ */ new Set(["dockerfile", "license", "makefile", "readme"]);
2219
+ function extensionOfFileName(name) {
2220
+ const dot = name.lastIndexOf(".");
2221
+ return dot <= 0 ? "" : name.slice(dot).toLowerCase();
2222
+ }
2223
+ function isSupportedImportFile(file) {
2224
+ const extension = extensionOfFileName(file.name);
2225
+ if (OUTSIDE_IN_IMPORT_EXTENSIONS.has(extension) || BUNDLE_IMPORT_EXTENSIONS.has(extension) || DIRECT_IMPORT_EXTENSIONS.has(extension)) {
2226
+ return true;
2227
+ }
2228
+ const mimeType = file.type.toLowerCase();
2229
+ if (mimeType.startsWith("text/") || mimeType.startsWith("image/")) return true;
2230
+ return !extension && DIRECT_IMPORT_BASENAMES.has(file.name.toLowerCase());
2231
+ }
2232
+
2233
+ // src/FileExplorer/FileExplorer.tsx
2234
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1635
2235
  var INTERNAL_DRAG_TYPE = "application/x-docblocks-entry";
1636
2236
  var NEW_ITEM_ERROR_ID = "db-new-item-error";
2237
+ var NEW_FILE_EXTENSIONS = {
2238
+ markdown: ".md",
2239
+ docx: ".docx",
2240
+ xlsx: ".xlsx",
2241
+ pdf: ".pdf",
2242
+ "web-interactive": ".html",
2243
+ "web-static": ".html"
2244
+ };
2245
+ var SELECTABLE_FILE_EXTENSION = /\.(?:md|docx|xlsx|pdf|html?)$/i;
1637
2246
  function normalisePath2(path) {
1638
- return parseWorkspacePath2(path);
2247
+ return parseWorkspacePath4(path);
1639
2248
  }
1640
2249
  function hasEquivalentPath2(paths, path) {
1641
2250
  const canonical = normalisePath2(path);
@@ -1701,6 +2310,7 @@ function FileExplorer({
1701
2310
  [pinnedPaths]
1702
2311
  );
1703
2312
  const [newItemName, setNewItemName] = useState4("");
2313
+ const [newFileFormat, setNewFileFormat] = useState4("markdown");
1704
2314
  const [uncontrolledSortMode, setUncontrolledSortMode] = useState4("name");
1705
2315
  const selectedSortMode = sortMode ?? uncontrolledSortMode;
1706
2316
  const selectSortMode = useCallback4(
@@ -1735,12 +2345,11 @@ function FileExplorer({
1735
2345
  const hasSupported = useCallback4((dt) => {
1736
2346
  for (const item of Array.from(dt.items)) {
1737
2347
  if (item.kind !== "file") continue;
1738
- const name = item.getAsFile?.()?.name;
1739
- if (!name) return true;
1740
- const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
1741
- if (SUPPORTED_EXTENSIONS.has(ext)) return true;
2348
+ const file = item.getAsFile();
2349
+ if (!file) return true;
2350
+ if (isSupportedImportFile(file)) return true;
1742
2351
  }
1743
- return dt.items.length > 0;
2352
+ return Array.from(dt.files).some(isSupportedImportFile);
1744
2353
  }, []);
1745
2354
  const handleDragEnter = useCallback4(
1746
2355
  (e) => {
@@ -1774,11 +2383,8 @@ function FileExplorer({
1774
2383
  e.preventDefault();
1775
2384
  dragCounter.current = 0;
1776
2385
  setDragOver(false);
1777
- const supported = Array.from(e.dataTransfer.files).filter((f) => {
1778
- const ext = f.name.slice(f.name.lastIndexOf(".")).toLowerCase();
1779
- return SUPPORTED_EXTENSIONS.has(ext);
1780
- });
1781
- if (supported.length > 0) onImportFiles?.(supported);
2386
+ const files = Array.from(e.dataTransfer.files);
2387
+ if (files.length > 0) onImportFiles?.(files);
1782
2388
  },
1783
2389
  [onImportFiles]
1784
2390
  );
@@ -1817,9 +2423,24 @@ function FileExplorer({
1817
2423
  setNewItemCreationPending(true);
1818
2424
  try {
1819
2425
  if (itemType === "file") {
1820
- const filename = name.endsWith(".md") ? name : `${name}.md`;
2426
+ const stem = name.replace(SELECTABLE_FILE_EXTENSION, "");
2427
+ if (!stem) {
2428
+ setNewItemError("Enter a file name before the extension.");
2429
+ return;
2430
+ }
2431
+ const filename = `${stem}${NEW_FILE_EXTENSIONS[newFileFormat]}`;
1821
2432
  createdPath = `${prefix}${filename}`;
1822
- await tree.createFile(createdPath, "");
2433
+ if (newFileFormat === "markdown") {
2434
+ await tree.createFile(createdPath, "");
2435
+ } else {
2436
+ if (!provider) throw new Error("Open a workspace before creating a document.");
2437
+ await createNewOutsideInDocument(
2438
+ provider,
2439
+ createdPath,
2440
+ newFileFormat === "web-static" ? "static" : newFileFormat === "web-interactive" ? "interactive" : void 0
2441
+ );
2442
+ await tree.refresh();
2443
+ }
1823
2444
  handleSelect(createdPath);
1824
2445
  } else if (itemType === "directory") {
1825
2446
  await tree.createDirectory(createdPath);
@@ -1834,7 +2455,7 @@ function FileExplorer({
1834
2455
  setNewItemName("");
1835
2456
  setNewItemType(null);
1836
2457
  onTreeChange?.({ type: "create", path: createdPath });
1837
- }, [newItemName, newItemType, tree, handleSelect, onTreeChange]);
2458
+ }, [newFileFormat, newItemName, newItemType, onTreeChange, provider, tree, handleSelect]);
1838
2459
  const handleMoveToWorkspace = useCallback4(async () => {
1839
2460
  if (!onMoveToWorkspace || !moveDestinationId || movingToWorkspace) return;
1840
2461
  setMovingToWorkspace(true);
@@ -2196,6 +2817,7 @@ function FileExplorer({
2196
2817
  disabled: newItemCreationPending,
2197
2818
  onClick: () => {
2198
2819
  setNewItemError(null);
2820
+ setNewFileFormat("markdown");
2199
2821
  setNewItemType("file");
2200
2822
  },
2201
2823
  title: "New File",
@@ -2274,7 +2896,7 @@ function FileExplorer({
2274
2896
  /* @__PURE__ */ jsxs4(
2275
2897
  "form",
2276
2898
  {
2277
- className: "db-new-item-row",
2899
+ className: `db-new-item-row db-new-item-row--${newItemType}`,
2278
2900
  "aria-busy": newItemCreationPending,
2279
2901
  onSubmit: (e) => {
2280
2902
  e.preventDefault();
@@ -2299,14 +2921,41 @@ function FileExplorer({
2299
2921
  if (e.key === "Escape") {
2300
2922
  setNewItemType(null);
2301
2923
  setNewItemName("");
2924
+ setNewFileFormat("markdown");
2302
2925
  setNewItemError(null);
2303
2926
  }
2304
2927
  },
2305
2928
  autoFocus: true
2306
2929
  }
2307
2930
  ),
2308
- newItemType === "file" && /* @__PURE__ */ jsx5("span", { className: "db-new-item-suffix", children: ".md" }),
2309
- /* @__PURE__ */ jsx5("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
2931
+ newItemType === "file" ? /* @__PURE__ */ jsxs4("div", { className: "db-new-item-file-controls", children: [
2932
+ /* @__PURE__ */ jsxs4(
2933
+ "select",
2934
+ {
2935
+ className: "db-new-item-format",
2936
+ "aria-label": "New file type",
2937
+ value: newFileFormat,
2938
+ disabled: newItemCreationPending,
2939
+ onChange: (event) => {
2940
+ setNewFileFormat(event.currentTarget.value);
2941
+ setNewItemError(null);
2942
+ },
2943
+ children: [
2944
+ /* @__PURE__ */ jsxs4("optgroup", { label: "Document", children: [
2945
+ /* @__PURE__ */ jsx5("option", { value: "markdown", children: "Markdown (.md)" }),
2946
+ /* @__PURE__ */ jsx5("option", { value: "docx", children: "Word document (.docx)" }),
2947
+ /* @__PURE__ */ jsx5("option", { value: "xlsx", children: "Excel workbook (.xlsx)" }),
2948
+ /* @__PURE__ */ jsx5("option", { value: "pdf", children: "PDF document (.pdf)" })
2949
+ ] }),
2950
+ /* @__PURE__ */ jsxs4("optgroup", { label: "Web page", children: [
2951
+ /* @__PURE__ */ jsx5("option", { value: "web-interactive", children: "Web page \u2014 Interactive (.html)" }),
2952
+ /* @__PURE__ */ jsx5("option", { value: "web-static", children: "Web page \u2014 Static (.html)" })
2953
+ ] })
2954
+ ]
2955
+ }
2956
+ ),
2957
+ /* @__PURE__ */ jsx5("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
2958
+ ] }) : /* @__PURE__ */ jsx5("button", { type: "submit", className: "db-new-item-add", disabled: newItemCreationPending, children: newItemCreationPending ? "Adding\u2026" : "Add" })
2310
2959
  ]
2311
2960
  }
2312
2961
  ),
@@ -2660,6 +3309,8 @@ function AppMenu({
2660
3309
  onAccentColorChange,
2661
3310
  writeCanvasSettings = DEFAULT_WRITE_CANVAS_PREFERENCES,
2662
3311
  onWriteCanvasSettingsChange,
3312
+ proofingPreferences = DEFAULT_PROOFING_PREFERENCES,
3313
+ onProofingPreferencesChange,
2663
3314
  versioningPreference = "browser-only",
2664
3315
  onVersioningPreferenceChange,
2665
3316
  onDownloadAllWorkspaces,
@@ -2834,6 +3485,13 @@ function AppMenu({
2834
3485
  onChange: (settings) => onWriteCanvasSettingsChange?.(settings)
2835
3486
  }
2836
3487
  ),
3488
+ /* @__PURE__ */ jsx7(
3489
+ ProofingSettingsControls,
3490
+ {
3491
+ value: proofingPreferences,
3492
+ onChange: (settings) => onProofingPreferencesChange?.(settings)
3493
+ }
3494
+ ),
2837
3495
  getStorageEstimate && /* @__PURE__ */ jsxs6("fieldset", { className: "db-settings-fieldset", children: [
2838
3496
  /* @__PURE__ */ jsx7("legend", { className: "db-settings-legend", children: "Storage" }),
2839
3497
  /* @__PURE__ */ jsx7("p", { className: "db-settings-hint", children: storageEstimate ? `DocBlocks documents and app data are using ${formatBytes(
@@ -3236,7 +3894,7 @@ function useDocumentSession(autoSaveDelayMs = 500) {
3236
3894
  import { lazy, Suspense } from "react";
3237
3895
  import { jsx as jsx10 } from "react/jsx-runtime";
3238
3896
  var ExportToolbarControlsImplementation = lazy(
3239
- () => import("./ExportToolbarControls-BUVJCSMV.js").then((module) => ({
3897
+ () => import("./ExportToolbarControls-QYLBRWM7.js").then((module) => ({
3240
3898
  default: module.ExportToolbarControls
3241
3899
  }))
3242
3900
  );
@@ -3359,27 +4017,39 @@ function useGit(provider, requestedWorkspaceId, theme) {
3359
4017
  const available = gitApi !== null && capabilities?.gitAvailable === true;
3360
4018
  const [repo, setRepo] = useState10(null);
3361
4019
  const [remoteWeb, setRemoteWeb] = useState10(null);
4020
+ const [pendingGrant, setPendingGrant] = useState10(null);
4021
+ const loadRemote = useCallback9(
4022
+ (repositoryId2, isCancelled) => {
4023
+ if (!gitApi) return;
4024
+ void gitApi.listRemotes(repositoryId2).then(
4025
+ (remotes) => {
4026
+ if (isCancelled() || !remotes.ok) return;
4027
+ const first = remotes.value[0];
4028
+ setRemoteWeb(first?.web ?? null);
4029
+ },
4030
+ () => void 0
4031
+ );
4032
+ },
4033
+ [gitApi]
4034
+ );
3362
4035
  useEffect9(() => {
3363
4036
  setRepo(null);
3364
4037
  setRemoteWeb(null);
4038
+ setPendingGrant(null);
3365
4039
  if (!gitApi || !workspaceId || !available) return;
3366
4040
  let cancelled = false;
4041
+ const isCancelled = () => cancelled;
3367
4042
  void gitApi.detectRepo(workspaceId).then(
3368
4043
  (result) => {
3369
- if (cancelled || !result.ok || !result.value.isRepo || !result.value.repositoryId) {
4044
+ if (cancelled || !result.ok || !result.value.isRepo) return;
4045
+ if (!result.value.repositoryId) {
4046
+ if (result.value.requiresExpandedGrant) {
4047
+ setPendingGrant({ repositoryRoot: result.value.repositoryRoot ?? null });
4048
+ }
3370
4049
  return;
3371
4050
  }
3372
4051
  setRepo(result.value);
3373
- void gitApi.listRemotes(result.value.repositoryId).then(
3374
- (remotes) => {
3375
- if (cancelled || !remotes.ok) return;
3376
- const first = remotes.value[0];
3377
- setRemoteWeb(first?.web ?? null);
3378
- },
3379
- // No remote is a supported state; a failed probe degrades to it
3380
- // rather than surfacing an unhandled rejection.
3381
- () => void 0
3382
- );
4052
+ loadRemote(result.value.repositoryId, isCancelled);
3383
4053
  },
3384
4054
  /* detection failure → not a repo, which is the default state */
3385
4055
  () => void 0
@@ -3387,7 +4057,25 @@ function useGit(provider, requestedWorkspaceId, theme) {
3387
4057
  return () => {
3388
4058
  cancelled = true;
3389
4059
  };
3390
- }, [gitApi, workspaceId, available]);
4060
+ }, [gitApi, workspaceId, available, loadRemote]);
4061
+ const [grantBusy, setGrantBusy] = useState10(false);
4062
+ const enableExpandedRepo = useCallback9(
4063
+ async (opts) => {
4064
+ if (!gitApi || !workspaceId) return false;
4065
+ setGrantBusy(true);
4066
+ try {
4067
+ const result = await gitApi.grantExpandedRepo(workspaceId, opts);
4068
+ if (!result.ok || !result.value.repositoryId) return false;
4069
+ setRepo(result.value);
4070
+ setPendingGrant(null);
4071
+ loadRemote(result.value.repositoryId, () => false);
4072
+ return true;
4073
+ } finally {
4074
+ setGrantBusy(false);
4075
+ }
4076
+ },
4077
+ [gitApi, workspaceId, loadRemote]
4078
+ );
3391
4079
  const repositoryId = repo?.repositoryId ?? null;
3392
4080
  const isRepo = repositoryId !== null;
3393
4081
  const { status, refresh, scheduleRefresh } = useGitStatus(gitApi, repositoryId, isRepo);
@@ -3536,6 +4224,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
3536
4224
  capabilities,
3537
4225
  repo,
3538
4226
  repositoryId,
4227
+ pendingGrant,
4228
+ grantBusy,
4229
+ enableExpandedRepo,
3539
4230
  remoteWeb,
3540
4231
  gitApi,
3541
4232
  provider,
@@ -3563,6 +4254,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
3563
4254
  capabilities,
3564
4255
  repo,
3565
4256
  repositoryId,
4257
+ pendingGrant,
4258
+ grantBusy,
4259
+ enableExpandedRepo,
3566
4260
  remoteWeb,
3567
4261
  gitApi,
3568
4262
  provider,
@@ -3753,159 +4447,11 @@ function hasControlCharacter(value) {
3753
4447
  }
3754
4448
 
3755
4449
  // src/DocBlocksShell/import-files.ts
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
- }
3873
-
3874
- // src/DocBlocksShell/provider-io.ts
3875
- import {
3876
- FsError as FsError2,
3877
- getFileSystemProviderV2 as getFileSystemProviderV22,
3878
- parseWorkspacePath as parseWorkspacePath3
3879
- } from "@bendyline/docblocks/filesystem";
3880
- async function providerEntryExists(provider, path) {
3881
- const providerV2 = getFileSystemProviderV22(provider);
3882
- return providerV2 ? await providerV2.stat(parseWorkspacePath3(path)) !== null : provider.exists(path);
3883
- }
3884
- async function writeProviderText(provider, path, content, mode = "upsert") {
3885
- const providerV2 = getFileSystemProviderV22(provider);
3886
- if (providerV2) {
3887
- await providerV2.writeFile(parseWorkspacePath3(path), new TextEncoder().encode(content), {
3888
- mode,
3889
- createParents: true,
3890
- expectedVersion: mode === "create" ? null : void 0
3891
- });
3892
- return;
3893
- }
3894
- if (mode === "create" && await provider.exists(path)) {
3895
- throw new FsError2("already-exists", "File already exists.", { operation: "write", path });
3896
- }
3897
- await provider.writeFile(path, content);
3898
- }
3899
- async function removeProviderEntry(provider, path) {
3900
- const providerV2 = getFileSystemProviderV22(provider);
3901
- if (providerV2) {
3902
- await providerV2.remove(parseWorkspacePath3(path), { recursive: true, missing: "ignore" });
3903
- return;
3904
- }
3905
- await provider.delete(path);
3906
- }
3907
-
3908
- // src/DocBlocksShell/import-files.ts
4450
+ import {
4451
+ FsError as FsError4,
4452
+ getFileSystemProviderV2 as getFileSystemProviderV24,
4453
+ parseWorkspacePath as parseWorkspacePath5
4454
+ } from "@bendyline/docblocks/filesystem";
3909
4455
  var MAX_NAME_ATTEMPTS = 100;
3910
4456
  function withCopySuffix(path, n) {
3911
4457
  const dot = path.lastIndexOf(".");
@@ -3914,7 +4460,7 @@ function withCopySuffix(path, n) {
3914
4460
  return `${path.slice(0, dot)} (${n})${path.slice(dot)}`;
3915
4461
  }
3916
4462
  function isAlreadyExists(error) {
3917
- return error instanceof FsError3 && error.code === "already-exists";
4463
+ return error instanceof FsError4 && error.code === "already-exists";
3918
4464
  }
3919
4465
  async function claimAvailablePath(provider, desiredPath) {
3920
4466
  for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
@@ -3929,9 +4475,9 @@ async function claimAvailablePath(provider, desiredPath) {
3929
4475
  throw new Error(`Too many documents are already named like \u201C${desiredPath}\u201D.`);
3930
4476
  }
3931
4477
  async function writeProviderBytes(provider, path, data, mode) {
3932
- const providerV2 = getFileSystemProviderV23(provider);
4478
+ const providerV2 = getFileSystemProviderV24(provider);
3933
4479
  if (providerV2) {
3934
- await providerV2.writeFile(parseWorkspacePath4(path), data, {
4480
+ await providerV2.writeFile(parseWorkspacePath5(path), data, {
3935
4481
  mode,
3936
4482
  createParents: true,
3937
4483
  expectedVersion: mode === "create" ? null : void 0
@@ -3939,11 +4485,10 @@ async function writeProviderBytes(provider, path, data, mode) {
3939
4485
  return;
3940
4486
  }
3941
4487
  if (mode === "create" && await providerEntryExists(provider, path)) {
3942
- throw new FsError3("already-exists", "File already exists.", { operation: "write", path });
4488
+ throw new FsError4("already-exists", "File already exists.", { operation: "write", path });
3943
4489
  }
3944
4490
  await provider.writeBinary(path, data);
3945
4491
  }
3946
- var OUTSIDE_IN_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".pptx", ".xlsx"]);
3947
4492
  async function claimOutsideInTarget(provider, desiredPath) {
3948
4493
  for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
3949
4494
  const candidate = attempt === 1 ? desiredPath : withCopySuffix(desiredPath, attempt);
@@ -3964,10 +4509,9 @@ async function importOutsideInFile(file, provider) {
3964
4509
  const created = [claim.path];
3965
4510
  try {
3966
4511
  const sourceBytes = await file.arrayBuffer();
3967
- const imported = await importOutsideInDocument({
3968
- data: sourceBytes,
3969
- targetPath: claim.path
3970
- });
4512
+ const layout = resolveOutsideInLayout(claim.path);
4513
+ if (!layout) throw new Error(`Outside-in editing does not support \u201C${claim.path}\u201D.`);
4514
+ const imported = await (isOutsideInDataFormat(layout.format) ? importOutsideInDataSidecar({ data: sourceBytes, targetPath: claim.path }) : importOutsideInDocument({ data: sourceBytes, targetPath: claim.path }));
3971
4515
  for (const entry of await imported.container.listFiles()) {
3972
4516
  if (/\.md$/i.test(entry.path)) continue;
3973
4517
  const data = await imported.container.readFile(entry.path);
@@ -3987,10 +4531,7 @@ async function importOutsideInFile(file, provider) {
3987
4531
  throw error;
3988
4532
  }
3989
4533
  }
3990
- async function readImportedMarkdown(file, ext, destPath, provider) {
3991
- if (ext === ".md" || ext === ".txt") {
3992
- return file.text();
3993
- }
4534
+ async function readImportedMarkdown(file, destPath, provider) {
3994
4535
  const snapshot = await decodeDbkWorkspace(await file.arrayBuffer(), {
3995
4536
  targetDocumentPath: destPath
3996
4537
  });
@@ -4004,27 +4545,31 @@ async function writeDbkCompanions(snapshot, provider) {
4004
4545
  await writeProviderText(provider, entry.path, entry.content, "create");
4005
4546
  }
4006
4547
  }
4007
- var SUPPORTED_EXTENSIONS2 = /* @__PURE__ */ new Set([".md", ".txt", ...OUTSIDE_IN_EXTENSIONS, ".dbk", ".zip"]);
4008
4548
  async function importDroppedFiles(files, provider) {
4009
4549
  const result = { imported: [], failed: [], unsupported: [] };
4010
4550
  for (const file of files) {
4011
- const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
4012
- if (!SUPPORTED_EXTENSIONS2.has(ext)) {
4551
+ const ext = extensionOfFileName(file.name);
4552
+ if (!isSupportedImportFile(file)) {
4013
4553
  result.unsupported.push(file.name);
4014
4554
  continue;
4015
4555
  }
4016
- const baseName = file.name.replace(/\.[^.]+$/, "");
4017
4556
  let claimed = null;
4018
4557
  try {
4019
- if (OUTSIDE_IN_EXTENSIONS.has(ext)) {
4558
+ if (OUTSIDE_IN_IMPORT_EXTENSIONS.has(ext)) {
4020
4559
  const imported = await importOutsideInFile(file, provider);
4021
4560
  result.imported.push({ source: file.name, ...imported });
4022
4561
  continue;
4023
4562
  }
4024
- const claim = await claimAvailablePath(provider, `${baseName}.md`);
4563
+ const isBundle = BUNDLE_IMPORT_EXTENSIONS.has(ext);
4564
+ const desiredPath = isBundle ? `${file.name.replace(/\.[^.]+$/, "")}.md` : file.name;
4565
+ const claim = await claimAvailablePath(provider, desiredPath);
4025
4566
  claimed = claim.path;
4026
- const markdown = await readImportedMarkdown(file, ext, claim.path, provider);
4027
- await writeProviderText(provider, claim.path, markdown);
4567
+ if (isBundle) {
4568
+ const markdown = await readImportedMarkdown(file, claim.path, provider);
4569
+ await writeProviderText(provider, claim.path, markdown);
4570
+ } else {
4571
+ await writeProviderBytes(provider, claim.path, await file.arrayBuffer(), "upsert");
4572
+ }
4028
4573
  result.imported.push({ source: file.name, path: claim.path, renamed: claim.renamed });
4029
4574
  } catch (error) {
4030
4575
  if (claimed) {
@@ -4040,16 +4585,13 @@ async function importDroppedFiles(files, provider) {
4040
4585
  }
4041
4586
  function summariseImport(result) {
4042
4587
  const { imported, failed, unsupported } = result;
4043
- if (failed.length > 0) {
4044
- 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.`;
4588
+ const rejectedCount = failed.length + unsupported.length;
4589
+ if (rejectedCount > 0) {
4590
+ 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.`;
4045
4591
  return { kind: "error", message: detail };
4046
4592
  }
4047
4593
  if (imported.length === 0) {
4048
- if (unsupported.length === 0) return null;
4049
- return {
4050
- kind: "error",
4051
- 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.`
4052
- };
4594
+ return null;
4053
4595
  }
4054
4596
  const renamed = imported.filter((entry) => entry.renamed);
4055
4597
  if (renamed.length === 1) {
@@ -4067,241 +4609,6 @@ function summariseImport(result) {
4067
4609
  return null;
4068
4610
  }
4069
4611
 
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
-
4305
4612
  // src/components/usePromptDialog.ts
4306
4613
  import React2, { useCallback as useCallback11, useEffect as useEffect12, useRef as useRef11, useState as useState12 } from "react";
4307
4614
 
@@ -5130,9 +5437,12 @@ function loadEditorShell() {
5130
5437
  return editorShellModulePromise;
5131
5438
  }
5132
5439
  var EditorShell = lazy2(loadEditorShell);
5133
- var GitUI = lazy2(() => import("./GitUI-CAKFVKWY.js").then((m) => ({ default: m.GitUI })));
5440
+ var GitUI = lazy2(() => import("./GitUI-L5T3XHZG.js").then((m) => ({ default: m.GitUI })));
5441
+ var GitGrantNotice = lazy2(
5442
+ () => import("./GitGrantNotice-I2VNKPLH.js").then((m) => ({ default: m.GitGrantNotice }))
5443
+ );
5134
5444
  var GitToolbarControl = lazy2(
5135
- () => import("./GitToolbarControl-VJVK2B4X.js").then((m) => ({ default: m.GitToolbarControl }))
5445
+ () => import("./GitToolbarControl-O5AXBC6T.js").then((m) => ({ default: m.GitToolbarControl }))
5136
5446
  );
5137
5447
  var DOCBLOCKS_VIDEO_EXPORT_PALETTE = Object.freeze({
5138
5448
  overlay: "rgba(0, 0, 0, 0.72)",
@@ -5403,6 +5713,8 @@ function useIsMobile(breakpoint = 768) {
5403
5713
  }, [breakpoint]);
5404
5714
  return isMobile;
5405
5715
  }
5716
+ var DESKTOP_TOOLBAR_WRAP_WIDTH = 900;
5717
+ var WEB_TOOLBAR_WRAP_WIDTH = 700;
5406
5718
  function FolderGlyph() {
5407
5719
  return /* @__PURE__ */ jsx14(
5408
5720
  "svg",
@@ -5434,12 +5746,35 @@ function FileGlyph() {
5434
5746
  }
5435
5747
  );
5436
5748
  }
5749
+ function CollapseSidebarGlyph() {
5750
+ return /* @__PURE__ */ jsxs12(
5751
+ "svg",
5752
+ {
5753
+ viewBox: "0 0 64 64",
5754
+ fill: "none",
5755
+ stroke: "currentColor",
5756
+ strokeWidth: "3",
5757
+ strokeLinecap: "round",
5758
+ strokeLinejoin: "round",
5759
+ "aria-hidden": "true",
5760
+ children: [
5761
+ /* @__PURE__ */ jsx14("rect", { x: "7", y: "10", width: "50", height: "44", rx: "6" }),
5762
+ /* @__PURE__ */ jsx14("path", { d: "M24 10v44" }),
5763
+ /* @__PURE__ */ jsx14("path", { d: "m17 25-7 7 7 7" })
5764
+ ]
5765
+ }
5766
+ );
5767
+ }
5437
5768
  function DocBlocksShell({
5438
5769
  theme: hostTheme = "auto",
5439
5770
  logoUrl,
5440
5771
  issueReportVersion,
5441
5772
  appBuildDate,
5442
5773
  ffmpegWasm,
5774
+ calcEngineFactory,
5775
+ proofing,
5776
+ proofingDefaultEnabled,
5777
+ proofingIgnoreStore,
5443
5778
  homeDocumentTitle,
5444
5779
  homeDocumentPath,
5445
5780
  allowVersioning = true,
@@ -5492,6 +5827,11 @@ function DocBlocksShell({
5492
5827
  setWriteCanvasSettings(settings);
5493
5828
  saveWriteCanvasPreferences(settings);
5494
5829
  }, []);
5830
+ const [proofingPreferences, setProofingPreferences] = useState15(loadProofingPreferences);
5831
+ const handleProofingPreferencesChange = useCallback15((settings) => {
5832
+ setProofingPreferences(settings);
5833
+ saveProofingPreferences(settings);
5834
+ }, []);
5495
5835
  const [viewPreferences, setViewPreferences] = useState15(loadViewPreferences);
5496
5836
  const handleViewPreferencesChange = useCallback15((prefs) => {
5497
5837
  setViewPreferences(prefs);
@@ -5508,6 +5848,27 @@ function DocBlocksShell({
5508
5848
  const [sidebarWidth, setSidebarWidth] = useState15(loadSidebarWidth);
5509
5849
  const [compactLayout, setCompactLayout] = useState15(false);
5510
5850
  const effectiveCompact = isMobile || compactLayout;
5851
+ const editorAreaRef = useRef15(null);
5852
+ const [desktopToolbarWrapped, setDesktopToolbarWrapped] = useState15(false);
5853
+ useEffect15(() => {
5854
+ const editorArea = editorAreaRef.current;
5855
+ if (!editorArea || effectiveCompact) {
5856
+ setDesktopToolbarWrapped(false);
5857
+ return;
5858
+ }
5859
+ const wrapWidth = isElectronHost3() ? DESKTOP_TOOLBAR_WRAP_WIDTH : WEB_TOOLBAR_WRAP_WIDTH;
5860
+ const updateWrappedState = () => {
5861
+ setDesktopToolbarWrapped(editorArea.getBoundingClientRect().width <= wrapWidth);
5862
+ };
5863
+ updateWrappedState();
5864
+ if (typeof ResizeObserver === "undefined") {
5865
+ window.addEventListener("resize", updateWrappedState);
5866
+ return () => window.removeEventListener("resize", updateWrappedState);
5867
+ }
5868
+ const observer = new ResizeObserver(updateWrappedState);
5869
+ observer.observe(editorArea);
5870
+ return () => observer.disconnect();
5871
+ }, [effectiveCompact]);
5511
5872
  const showBrowserStorageWarning = !isElectronHost3();
5512
5873
  const appVersion = isElectronHost3() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web";
5513
5874
  const issueReportUrl = buildIssueReportUrl({
@@ -5553,13 +5914,13 @@ function DocBlocksShell({
5553
5914
  lastRaw = drag.startWidth + (ev.clientX - drag.startX);
5554
5915
  if (lastRaw < SIDEBAR_COLLAPSE_THRESHOLD && sidebarRef.current) {
5555
5916
  sidebarRef.current.style.width = `${SIDEBAR_WIDTH_MIN}px`;
5556
- sidebarRef.current.style.opacity = "0.45";
5917
+ sidebarRef.current.classList.add("db-shell-sidebar--collapse-preview");
5557
5918
  return;
5558
5919
  }
5559
5920
  const clamped = Math.min(SIDEBAR_WIDTH_MAX, Math.max(SIDEBAR_WIDTH_MIN, lastRaw));
5560
5921
  if (sidebarRef.current) {
5561
5922
  sidebarRef.current.style.width = `${clamped}px`;
5562
- sidebarRef.current.style.opacity = "";
5923
+ sidebarRef.current.classList.remove("db-shell-sidebar--collapse-preview");
5563
5924
  }
5564
5925
  };
5565
5926
  const onUp = () => {
@@ -5568,7 +5929,7 @@ function DocBlocksShell({
5568
5929
  document.body.style.userSelect = "";
5569
5930
  document.body.classList.remove("db-resizing-sidebar");
5570
5931
  if (sidebarRef.current) {
5571
- sidebarRef.current.style.opacity = "";
5932
+ sidebarRef.current.classList.remove("db-shell-sidebar--collapse-preview");
5572
5933
  }
5573
5934
  if (lastRaw < SIDEBAR_COLLAPSE_THRESHOLD) {
5574
5935
  setCompactLayout(true);
@@ -5595,6 +5956,11 @@ function DocBlocksShell({
5595
5956
  const [workspaceStartupError, setWorkspaceStartupError] = useState15(null);
5596
5957
  const [activeWorkspaceId, setActiveWorkspaceId] = useState15(null);
5597
5958
  const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] = useState15(null);
5959
+ const defaultProofingIgnoreStore = useMemo3(
5960
+ () => createLocalProofingIgnoreStore(() => activeWorkspaceId),
5961
+ [activeWorkspaceId]
5962
+ );
5963
+ const effectiveProofingIgnoreStore = proofingIgnoreStore === void 0 ? defaultProofingIgnoreStore : proofingIgnoreStore;
5598
5964
  useEffect15(() => {
5599
5965
  if (!provider || getTransientWorkspace(provider.id)) return;
5600
5966
  const providerV2 = getFileSystemProviderV27(provider);
@@ -5751,11 +6117,17 @@ function DocBlocksShell({
5751
6117
  const [selectedSourceFile, setSelectedSourceFile] = useState15(null);
5752
6118
  const [selectedOutsideIn, setSelectedOutsideIn] = useState15(null);
5753
6119
  const [selectedOutsideInEditingEnabled, setSelectedOutsideInEditingEnabled] = useState15(false);
6120
+ const [selectedImage, setSelectedImage] = useState15(void 0);
6121
+ const editorViewPreferences = useMemo3(() => {
6122
+ const layoutMode = defaultOutsideInLayoutMode(selectedOutsideIn);
6123
+ return layoutMode && viewPreferences.layoutMode === void 0 ? { ...viewPreferences, layoutMode } : viewPreferences;
6124
+ }, [selectedOutsideIn, viewPreferences]);
5754
6125
  const adoptSelectedDocument = useCallback15((document2) => {
5755
6126
  setSelectedFile(document2?.displayPath ?? null);
5756
6127
  setSelectedSourceFile(document2?.sourcePath ?? null);
5757
6128
  setSelectedOutsideIn(document2?.outsideIn ?? null);
5758
6129
  setSelectedOutsideInEditingEnabled(document2?.outsideInEditingEnabled ?? false);
6130
+ setSelectedImage(document2?.image);
5759
6131
  }, []);
5760
6132
  const adoptRegularDocument = useCallback15((path) => {
5761
6133
  if (path === null) {
@@ -5763,13 +6135,27 @@ function DocBlocksShell({
5763
6135
  setSelectedSourceFile(null);
5764
6136
  setSelectedOutsideIn(null);
5765
6137
  setSelectedOutsideInEditingEnabled(false);
6138
+ setSelectedImage(void 0);
5766
6139
  return;
5767
6140
  }
5768
6141
  setSelectedFile(path);
5769
6142
  setSelectedSourceFile(path);
5770
6143
  setSelectedOutsideIn(null);
5771
6144
  setSelectedOutsideInEditingEnabled(false);
6145
+ setSelectedImage(void 0);
5772
6146
  }, []);
6147
+ const selectedImageUrl = useMemo3(() => {
6148
+ if (!selectedImage || typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
6149
+ return void 0;
6150
+ }
6151
+ return URL.createObjectURL(new Blob([selectedImage.data], { type: selectedImage.mimeType }));
6152
+ }, [selectedImage]);
6153
+ useEffect15(
6154
+ () => () => {
6155
+ if (selectedImageUrl) URL.revokeObjectURL(selectedImageUrl);
6156
+ },
6157
+ [selectedImageUrl]
6158
+ );
5773
6159
  useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath);
5774
6160
  const exportDestinationAdapter = useMemo3(() => {
5775
6161
  if (!selectedFile) return void 0;
@@ -6632,7 +7018,7 @@ function DocBlocksShell({
6632
7018
  const providerV2 = getFileSystemProviderV27(provider);
6633
7019
  if (!providerV2?.capabilities.watch) return;
6634
7020
  const watchedFile = selectedSourceFile ?? selectedFile;
6635
- if (!watchedFile || !documentSnapshot.targetKey) return;
7021
+ if (!watchedFile || !documentSnapshot.targetKey || selectedImage) return;
6636
7022
  const targetKey = documentSnapshot.targetKey;
6637
7023
  let disposed = false;
6638
7024
  let reading = false;
@@ -6686,7 +7072,14 @@ function DocBlocksShell({
6686
7072
  disposed = true;
6687
7073
  void subscription.dispose();
6688
7074
  };
6689
- }, [provider, selectedFile, selectedSourceFile, documentSession, documentSnapshot.targetKey]);
7075
+ }, [
7076
+ provider,
7077
+ selectedFile,
7078
+ selectedSourceFile,
7079
+ selectedImage,
7080
+ documentSession,
7081
+ documentSnapshot.targetKey
7082
+ ]);
6690
7083
  const transitionAwayFromDocument = useCallback15(
6691
7084
  async (requestId) => {
6692
7085
  if (requestId !== navigationRequestRef.current) return false;
@@ -7698,7 +8091,7 @@ function DocBlocksShell({
7698
8091
  const nextFile = selectedFile ? relocateProviderPath(selectedFile, change.oldPath, change.newPath) : null;
7699
8092
  const nextFolder = selectedFolder ? relocateProviderPath(selectedFolder, change.oldPath, change.newPath) : null;
7700
8093
  if (nextFile !== selectedFile) {
7701
- if (nextFile && selectedOutsideIn) {
8094
+ if (nextFile) {
7702
8095
  const opened = await loadEditableShellDocument(provider, nextFile);
7703
8096
  adoptSelectedDocument(opened);
7704
8097
  } else {
@@ -7743,7 +8136,6 @@ function DocBlocksShell({
7743
8136
  adoptRegularDocument,
7744
8137
  adoptSelectedDocument,
7745
8138
  selectedFile,
7746
- selectedOutsideIn,
7747
8139
  selectedFolder,
7748
8140
  activeWorkspaceId,
7749
8141
  pinnedDocuments,
@@ -8372,7 +8764,7 @@ function DocBlocksShell({
8372
8764
  return /* @__PURE__ */ jsx14(
8373
8765
  "div",
8374
8766
  {
8375
- className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}`,
8767
+ className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}${desktopToolbarWrapped ? " db-shell--desktop-toolbar-wrapped" : ""}`,
8376
8768
  "data-theme": resolvedTheme,
8377
8769
  "data-accent": accentColor,
8378
8770
  "data-document-status": documentSnapshot.status,
@@ -8433,6 +8825,8 @@ function DocBlocksShell({
8433
8825
  onAccentColorChange: handleAccentColorChange,
8434
8826
  writeCanvasSettings,
8435
8827
  onWriteCanvasSettingsChange: handleWriteCanvasSettingsChange,
8828
+ proofingPreferences,
8829
+ onProofingPreferencesChange: handleProofingPreferencesChange,
8436
8830
  versioningPreference,
8437
8831
  onVersioningPreferenceChange: handleVersioningPreferenceChange,
8438
8832
  onDownloadAllWorkspaces: handleDownloadAllWorkspaces,
@@ -8544,6 +8938,7 @@ function DocBlocksShell({
8544
8938
  ]
8545
8939
  }
8546
8940
  ),
8941
+ git.available && git.pendingGrant && /* @__PURE__ */ jsx14(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx14(GitGrantNotice, {}) }),
8547
8942
  /* @__PURE__ */ jsxs12("div", { className: "db-shell-sidebar-footer", children: [
8548
8943
  /* @__PURE__ */ jsx14("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
8549
8944
  /* @__PURE__ */ jsx14("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
@@ -8563,6 +8958,10 @@ function DocBlocksShell({
8563
8958
  }
8564
8959
  )
8565
8960
  ] })
8961
+ ] }),
8962
+ /* @__PURE__ */ jsxs12("div", { className: "db-sidebar-collapse-preview", "aria-hidden": "true", children: [
8963
+ /* @__PURE__ */ jsx14("span", { className: "db-sidebar-collapse-preview-icon", children: /* @__PURE__ */ jsx14(CollapseSidebarGlyph, {}) }),
8964
+ /* @__PURE__ */ jsx14("span", { className: "db-sidebar-collapse-preview-label", children: "Release to hide files" })
8566
8965
  ] })
8567
8966
  ]
8568
8967
  }
@@ -8580,6 +8979,7 @@ function DocBlocksShell({
8580
8979
  (!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs12(
8581
8980
  "main",
8582
8981
  {
8982
+ ref: editorAreaRef,
8583
8983
  "aria-label": "Document editor",
8584
8984
  className: updateAvailable && onApplyUpdate && updateStatusBarVisible ? "db-shell-editor-area db-shell-editor-area--has-update" : "db-shell-editor-area",
8585
8985
  style: {
@@ -8599,11 +8999,13 @@ function DocBlocksShell({
8599
8999
  EditorShell,
8600
9000
  {
8601
9001
  initialMarkdown: editorContent,
8602
- readOnly: selectedOutsideIn !== null && !selectedOutsideInEditingEnabled,
9002
+ readOnly: selectedImage !== void 0 || selectedOutsideIn !== null && !selectedOutsideInEditingEnabled,
8603
9003
  initialView,
8604
9004
  defaultViewportPreset: defaultPreviewViewportPreset,
8605
9005
  articleId: selectedFile,
8606
- fileName: selectedFile,
9006
+ fileName: selectedSourceFile ?? selectedFile,
9007
+ imageSrc: selectedImageUrl,
9008
+ imageAlt: basenameOf(selectedFile),
8607
9009
  saveCoverImageOutput: saveRenderedImageOutput,
8608
9010
  saveDashboardImageOutput: saveRenderedImageOutput,
8609
9011
  onChange: handleEditorChange,
@@ -8614,6 +9016,12 @@ function DocBlocksShell({
8614
9016
  placeholder: editorPlaceholder,
8615
9017
  outlineWidth: 280,
8616
9018
  mediaProvider,
9019
+ calcEngineFactory,
9020
+ proofing,
9021
+ proofingDefaultEnabled,
9022
+ proofingSpellingEnabled: proofingPreferences.spelling,
9023
+ proofingGrammarEnabled: proofingPreferences.grammar,
9024
+ proofingIgnoreStore: effectiveProofingIgnoreStore,
8617
9025
  showCodeCopyButton,
8618
9026
  onCopyCode,
8619
9027
  allowRecording,
@@ -8621,8 +9029,8 @@ function DocBlocksShell({
8621
9029
  allowPresentationFullscreen,
8622
9030
  documentLinkProvider,
8623
9031
  workspaceContainer: versionsContainer ?? void 0,
8624
- allowVersioning: effectiveVersioning,
8625
- viewPreferences,
9032
+ allowVersioning: selectedImage === void 0 && effectiveVersioning,
9033
+ viewPreferences: editorViewPreferences,
8626
9034
  onViewPreferencesChange: handleViewPreferencesChange,
8627
9035
  versionBasename: versionBasename ?? stripExtension(basenameOf(selectedFile)),
8628
9036
  versioningPrunePolicy,
@@ -8764,12 +9172,14 @@ export {
8764
9172
  AccentColorSettings,
8765
9173
  AppMenu,
8766
9174
  DEFAULT_OPTIONS,
9175
+ DEFAULT_PROOFING_PREFERENCES,
8767
9176
  DEFAULT_WRITE_CANVAS_FONT_SCHEME,
8768
9177
  DocBlocksShell,
8769
9178
  ExportDialog,
8770
9179
  ExportToolbarControls,
8771
9180
  FileExplorer,
8772
9181
  FileTreeNode,
9182
+ ProofingSettingsControls,
8773
9183
  SettingsDialog,
8774
9184
  ThemeSettings,
8775
9185
  WRITE_CANVAS_FONT_SCHEMES,
@@ -8780,8 +9190,10 @@ export {
8780
9190
  createImageSaveOutput as createDashboardImageSaveOutput,
8781
9191
  createImageSaveOutput,
8782
9192
  loadLastExportOptions,
9193
+ loadProofingPreferences,
8783
9194
  resolveWriteCanvasFonts,
8784
9195
  runExport,
9196
+ saveProofingPreferences,
8785
9197
  updateExportTargetExtension,
8786
9198
  useDocumentSession,
8787
9199
  useFileTree