@bendyline/docblocks-react 2.5.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
@@ -1588,54 +1597,617 @@ 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
+ 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);
1593
2016
  }
1594
-
1595
- // src/FileExplorer/entry-sort.ts
1596
- function compareText(left, right) {
1597
- return left < right ? -1 : left > right ? 1 : 0;
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
+ );
1598
2035
  }
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;
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 };
1603
2061
  }
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;
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 {};
1614
2111
  }
1615
- if (leftTime === null && rightTime !== null) return 1;
1616
- if (leftTime !== null && rightTime === null) return -1;
1617
- return compareText(left.name, right.name);
1618
- });
2112
+ };
2113
+ }
2114
+ function createOutsideInContentContainer(provider, layout) {
2115
+ return new FileSystemContentContainer(provider, layout.companionDirectory);
1619
2116
  }
1620
2117
 
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",
2118
+ // src/DocBlocksShell/import-file-types.ts
2119
+ var OUTSIDE_IN_IMPORT_EXTENSIONS = /* @__PURE__ */ new Set([
2120
+ ".csv",
2121
+ ".docx",
1626
2122
  ".html",
1627
2123
  ".htm",
1628
- ".docx",
1629
2124
  ".pdf",
1630
2125
  ".pptx",
1631
- ".xlsx",
1632
- ".dbk",
1633
- ".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"
1634
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";
1635
2198
  var INTERNAL_DRAG_TYPE = "application/x-docblocks-entry";
1636
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;
1637
2209
  function normalisePath2(path) {
1638
- return parseWorkspacePath2(path);
2210
+ return parseWorkspacePath4(path);
1639
2211
  }
1640
2212
  function hasEquivalentPath2(paths, path) {
1641
2213
  const canonical = normalisePath2(path);
@@ -1701,6 +2273,7 @@ function FileExplorer({
1701
2273
  [pinnedPaths]
1702
2274
  );
1703
2275
  const [newItemName, setNewItemName] = useState4("");
2276
+ const [newFileFormat, setNewFileFormat] = useState4("markdown");
1704
2277
  const [uncontrolledSortMode, setUncontrolledSortMode] = useState4("name");
1705
2278
  const selectedSortMode = sortMode ?? uncontrolledSortMode;
1706
2279
  const selectSortMode = useCallback4(
@@ -1735,12 +2308,11 @@ function FileExplorer({
1735
2308
  const hasSupported = useCallback4((dt) => {
1736
2309
  for (const item of Array.from(dt.items)) {
1737
2310
  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;
2311
+ const file = item.getAsFile();
2312
+ if (!file) return true;
2313
+ if (isSupportedImportFile(file)) return true;
1742
2314
  }
1743
- return dt.items.length > 0;
2315
+ return Array.from(dt.files).some(isSupportedImportFile);
1744
2316
  }, []);
1745
2317
  const handleDragEnter = useCallback4(
1746
2318
  (e) => {
@@ -1774,11 +2346,8 @@ function FileExplorer({
1774
2346
  e.preventDefault();
1775
2347
  dragCounter.current = 0;
1776
2348
  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);
2349
+ const files = Array.from(e.dataTransfer.files);
2350
+ if (files.length > 0) onImportFiles?.(files);
1782
2351
  },
1783
2352
  [onImportFiles]
1784
2353
  );
@@ -1817,9 +2386,24 @@ function FileExplorer({
1817
2386
  setNewItemCreationPending(true);
1818
2387
  try {
1819
2388
  if (itemType === "file") {
1820
- 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]}`;
1821
2395
  createdPath = `${prefix}${filename}`;
1822
- 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
+ }
1823
2407
  handleSelect(createdPath);
1824
2408
  } else if (itemType === "directory") {
1825
2409
  await tree.createDirectory(createdPath);
@@ -1834,7 +2418,7 @@ function FileExplorer({
1834
2418
  setNewItemName("");
1835
2419
  setNewItemType(null);
1836
2420
  onTreeChange?.({ type: "create", path: createdPath });
1837
- }, [newItemName, newItemType, tree, handleSelect, onTreeChange]);
2421
+ }, [newFileFormat, newItemName, newItemType, onTreeChange, provider, tree, handleSelect]);
1838
2422
  const handleMoveToWorkspace = useCallback4(async () => {
1839
2423
  if (!onMoveToWorkspace || !moveDestinationId || movingToWorkspace) return;
1840
2424
  setMovingToWorkspace(true);
@@ -2196,6 +2780,7 @@ function FileExplorer({
2196
2780
  disabled: newItemCreationPending,
2197
2781
  onClick: () => {
2198
2782
  setNewItemError(null);
2783
+ setNewFileFormat("markdown");
2199
2784
  setNewItemType("file");
2200
2785
  },
2201
2786
  title: "New File",
@@ -2274,7 +2859,7 @@ function FileExplorer({
2274
2859
  /* @__PURE__ */ jsxs4(
2275
2860
  "form",
2276
2861
  {
2277
- className: "db-new-item-row",
2862
+ className: `db-new-item-row db-new-item-row--${newItemType}`,
2278
2863
  "aria-busy": newItemCreationPending,
2279
2864
  onSubmit: (e) => {
2280
2865
  e.preventDefault();
@@ -2299,14 +2884,41 @@ function FileExplorer({
2299
2884
  if (e.key === "Escape") {
2300
2885
  setNewItemType(null);
2301
2886
  setNewItemName("");
2887
+ setNewFileFormat("markdown");
2302
2888
  setNewItemError(null);
2303
2889
  }
2304
2890
  },
2305
2891
  autoFocus: true
2306
2892
  }
2307
2893
  ),
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" })
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" })
2310
2922
  ]
2311
2923
  }
2312
2924
  ),
@@ -2660,6 +3272,8 @@ function AppMenu({
2660
3272
  onAccentColorChange,
2661
3273
  writeCanvasSettings = DEFAULT_WRITE_CANVAS_PREFERENCES,
2662
3274
  onWriteCanvasSettingsChange,
3275
+ proofingPreferences = DEFAULT_PROOFING_PREFERENCES,
3276
+ onProofingPreferencesChange,
2663
3277
  versioningPreference = "browser-only",
2664
3278
  onVersioningPreferenceChange,
2665
3279
  onDownloadAllWorkspaces,
@@ -2834,6 +3448,13 @@ function AppMenu({
2834
3448
  onChange: (settings) => onWriteCanvasSettingsChange?.(settings)
2835
3449
  }
2836
3450
  ),
3451
+ /* @__PURE__ */ jsx7(
3452
+ ProofingSettingsControls,
3453
+ {
3454
+ value: proofingPreferences,
3455
+ onChange: (settings) => onProofingPreferencesChange?.(settings)
3456
+ }
3457
+ ),
2837
3458
  getStorageEstimate && /* @__PURE__ */ jsxs6("fieldset", { className: "db-settings-fieldset", children: [
2838
3459
  /* @__PURE__ */ jsx7("legend", { className: "db-settings-legend", children: "Storage" }),
2839
3460
  /* @__PURE__ */ jsx7("p", { className: "db-settings-hint", children: storageEstimate ? `DocBlocks documents and app data are using ${formatBytes(
@@ -3236,7 +3857,7 @@ function useDocumentSession(autoSaveDelayMs = 500) {
3236
3857
  import { lazy, Suspense } from "react";
3237
3858
  import { jsx as jsx10 } from "react/jsx-runtime";
3238
3859
  var ExportToolbarControlsImplementation = lazy(
3239
- () => import("./ExportToolbarControls-BUVJCSMV.js").then((module) => ({
3860
+ () => import("./ExportToolbarControls-QYLBRWM7.js").then((module) => ({
3240
3861
  default: module.ExportToolbarControls
3241
3862
  }))
3242
3863
  );
@@ -3359,27 +3980,39 @@ function useGit(provider, requestedWorkspaceId, theme) {
3359
3980
  const available = gitApi !== null && capabilities?.gitAvailable === true;
3360
3981
  const [repo, setRepo] = useState10(null);
3361
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
+ );
3362
3998
  useEffect9(() => {
3363
3999
  setRepo(null);
3364
4000
  setRemoteWeb(null);
4001
+ setPendingGrant(null);
3365
4002
  if (!gitApi || !workspaceId || !available) return;
3366
4003
  let cancelled = false;
4004
+ const isCancelled = () => cancelled;
3367
4005
  void gitApi.detectRepo(workspaceId).then(
3368
4006
  (result) => {
3369
- 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
+ }
3370
4012
  return;
3371
4013
  }
3372
4014
  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
- );
4015
+ loadRemote(result.value.repositoryId, isCancelled);
3383
4016
  },
3384
4017
  /* detection failure → not a repo, which is the default state */
3385
4018
  () => void 0
@@ -3387,7 +4020,25 @@ function useGit(provider, requestedWorkspaceId, theme) {
3387
4020
  return () => {
3388
4021
  cancelled = true;
3389
4022
  };
3390
- }, [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
+ );
3391
4042
  const repositoryId = repo?.repositoryId ?? null;
3392
4043
  const isRepo = repositoryId !== null;
3393
4044
  const { status, refresh, scheduleRefresh } = useGitStatus(gitApi, repositoryId, isRepo);
@@ -3536,6 +4187,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
3536
4187
  capabilities,
3537
4188
  repo,
3538
4189
  repositoryId,
4190
+ pendingGrant,
4191
+ grantBusy,
4192
+ enableExpandedRepo,
3539
4193
  remoteWeb,
3540
4194
  gitApi,
3541
4195
  provider,
@@ -3563,6 +4217,9 @@ function useGit(provider, requestedWorkspaceId, theme) {
3563
4217
  capabilities,
3564
4218
  repo,
3565
4219
  repositoryId,
4220
+ pendingGrant,
4221
+ grantBusy,
4222
+ enableExpandedRepo,
3566
4223
  remoteWeb,
3567
4224
  gitApi,
3568
4225
  provider,
@@ -3749,163 +4406,15 @@ function hasControlCharacter(value) {
3749
4406
  const code = value.charCodeAt(index);
3750
4407
  if (code <= 31 || code === 127) return true;
3751
4408
  }
3752
- return false;
3753
- }
3754
-
3755
- // 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);
4409
+ return false;
3906
4410
  }
3907
4411
 
3908
4412
  // src/DocBlocksShell/import-files.ts
4413
+ import {
4414
+ FsError as FsError4,
4415
+ getFileSystemProviderV2 as getFileSystemProviderV24,
4416
+ parseWorkspacePath as parseWorkspacePath5
4417
+ } from "@bendyline/docblocks/filesystem";
3909
4418
  var MAX_NAME_ATTEMPTS = 100;
3910
4419
  function withCopySuffix(path, n) {
3911
4420
  const dot = path.lastIndexOf(".");
@@ -3914,7 +4423,7 @@ function withCopySuffix(path, n) {
3914
4423
  return `${path.slice(0, dot)} (${n})${path.slice(dot)}`;
3915
4424
  }
3916
4425
  function isAlreadyExists(error) {
3917
- return error instanceof FsError3 && error.code === "already-exists";
4426
+ return error instanceof FsError4 && error.code === "already-exists";
3918
4427
  }
3919
4428
  async function claimAvailablePath(provider, desiredPath) {
3920
4429
  for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
@@ -3929,9 +4438,9 @@ async function claimAvailablePath(provider, desiredPath) {
3929
4438
  throw new Error(`Too many documents are already named like \u201C${desiredPath}\u201D.`);
3930
4439
  }
3931
4440
  async function writeProviderBytes(provider, path, data, mode) {
3932
- const providerV2 = getFileSystemProviderV23(provider);
4441
+ const providerV2 = getFileSystemProviderV24(provider);
3933
4442
  if (providerV2) {
3934
- await providerV2.writeFile(parseWorkspacePath4(path), data, {
4443
+ await providerV2.writeFile(parseWorkspacePath5(path), data, {
3935
4444
  mode,
3936
4445
  createParents: true,
3937
4446
  expectedVersion: mode === "create" ? null : void 0
@@ -3939,11 +4448,10 @@ async function writeProviderBytes(provider, path, data, mode) {
3939
4448
  return;
3940
4449
  }
3941
4450
  if (mode === "create" && await providerEntryExists(provider, path)) {
3942
- throw new FsError3("already-exists", "File already exists.", { operation: "write", path });
4451
+ throw new FsError4("already-exists", "File already exists.", { operation: "write", path });
3943
4452
  }
3944
4453
  await provider.writeBinary(path, data);
3945
4454
  }
3946
- var OUTSIDE_IN_EXTENSIONS = /* @__PURE__ */ new Set([".html", ".htm", ".docx", ".pdf", ".pptx", ".xlsx"]);
3947
4455
  async function claimOutsideInTarget(provider, desiredPath) {
3948
4456
  for (let attempt = 1; attempt <= MAX_NAME_ATTEMPTS; attempt++) {
3949
4457
  const candidate = attempt === 1 ? desiredPath : withCopySuffix(desiredPath, attempt);
@@ -3987,10 +4495,7 @@ async function importOutsideInFile(file, provider) {
3987
4495
  throw error;
3988
4496
  }
3989
4497
  }
3990
- async function readImportedMarkdown(file, ext, destPath, provider) {
3991
- if (ext === ".md" || ext === ".txt") {
3992
- return file.text();
3993
- }
4498
+ async function readImportedMarkdown(file, destPath, provider) {
3994
4499
  const snapshot = await decodeDbkWorkspace(await file.arrayBuffer(), {
3995
4500
  targetDocumentPath: destPath
3996
4501
  });
@@ -4004,27 +4509,31 @@ async function writeDbkCompanions(snapshot, provider) {
4004
4509
  await writeProviderText(provider, entry.path, entry.content, "create");
4005
4510
  }
4006
4511
  }
4007
- var SUPPORTED_EXTENSIONS2 = /* @__PURE__ */ new Set([".md", ".txt", ...OUTSIDE_IN_EXTENSIONS, ".dbk", ".zip"]);
4008
4512
  async function importDroppedFiles(files, provider) {
4009
4513
  const result = { imported: [], failed: [], unsupported: [] };
4010
4514
  for (const file of files) {
4011
- const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase();
4012
- if (!SUPPORTED_EXTENSIONS2.has(ext)) {
4515
+ const ext = extensionOfFileName(file.name);
4516
+ if (!isSupportedImportFile(file)) {
4013
4517
  result.unsupported.push(file.name);
4014
4518
  continue;
4015
4519
  }
4016
- const baseName = file.name.replace(/\.[^.]+$/, "");
4017
4520
  let claimed = null;
4018
4521
  try {
4019
- if (OUTSIDE_IN_EXTENSIONS.has(ext)) {
4522
+ if (OUTSIDE_IN_IMPORT_EXTENSIONS.has(ext)) {
4020
4523
  const imported = await importOutsideInFile(file, provider);
4021
4524
  result.imported.push({ source: file.name, ...imported });
4022
4525
  continue;
4023
4526
  }
4024
- 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);
4025
4530
  claimed = claim.path;
4026
- const markdown = await readImportedMarkdown(file, ext, claim.path, provider);
4027
- 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
+ }
4028
4537
  result.imported.push({ source: file.name, path: claim.path, renamed: claim.renamed });
4029
4538
  } catch (error) {
4030
4539
  if (claimed) {
@@ -4040,16 +4549,13 @@ async function importDroppedFiles(files, provider) {
4040
4549
  }
4041
4550
  function summariseImport(result) {
4042
4551
  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.`;
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.`;
4045
4555
  return { kind: "error", message: detail };
4046
4556
  }
4047
4557
  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
- };
4558
+ return null;
4053
4559
  }
4054
4560
  const renamed = imported.filter((entry) => entry.renamed);
4055
4561
  if (renamed.length === 1) {
@@ -4067,241 +4573,6 @@ function summariseImport(result) {
4067
4573
  return null;
4068
4574
  }
4069
4575
 
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
4576
  // src/components/usePromptDialog.ts
4306
4577
  import React2, { useCallback as useCallback11, useEffect as useEffect12, useRef as useRef11, useState as useState12 } from "react";
4307
4578
 
@@ -5130,9 +5401,12 @@ function loadEditorShell() {
5130
5401
  return editorShellModulePromise;
5131
5402
  }
5132
5403
  var EditorShell = lazy2(loadEditorShell);
5133
- 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
+ );
5134
5408
  var GitToolbarControl = lazy2(
5135
- () => import("./GitToolbarControl-VJVK2B4X.js").then((m) => ({ default: m.GitToolbarControl }))
5409
+ () => import("./GitToolbarControl-O5AXBC6T.js").then((m) => ({ default: m.GitToolbarControl }))
5136
5410
  );
5137
5411
  var DOCBLOCKS_VIDEO_EXPORT_PALETTE = Object.freeze({
5138
5412
  overlay: "rgba(0, 0, 0, 0.72)",
@@ -5403,6 +5677,8 @@ function useIsMobile(breakpoint = 768) {
5403
5677
  }, [breakpoint]);
5404
5678
  return isMobile;
5405
5679
  }
5680
+ var DESKTOP_TOOLBAR_WRAP_WIDTH = 900;
5681
+ var WEB_TOOLBAR_WRAP_WIDTH = 700;
5406
5682
  function FolderGlyph() {
5407
5683
  return /* @__PURE__ */ jsx14(
5408
5684
  "svg",
@@ -5434,12 +5710,35 @@ function FileGlyph() {
5434
5710
  }
5435
5711
  );
5436
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
+ }
5437
5732
  function DocBlocksShell({
5438
5733
  theme: hostTheme = "auto",
5439
5734
  logoUrl,
5440
5735
  issueReportVersion,
5441
5736
  appBuildDate,
5442
5737
  ffmpegWasm,
5738
+ calcEngineFactory,
5739
+ proofing,
5740
+ proofingDefaultEnabled,
5741
+ proofingIgnoreStore,
5443
5742
  homeDocumentTitle,
5444
5743
  homeDocumentPath,
5445
5744
  allowVersioning = true,
@@ -5492,6 +5791,11 @@ function DocBlocksShell({
5492
5791
  setWriteCanvasSettings(settings);
5493
5792
  saveWriteCanvasPreferences(settings);
5494
5793
  }, []);
5794
+ const [proofingPreferences, setProofingPreferences] = useState15(loadProofingPreferences);
5795
+ const handleProofingPreferencesChange = useCallback15((settings) => {
5796
+ setProofingPreferences(settings);
5797
+ saveProofingPreferences(settings);
5798
+ }, []);
5495
5799
  const [viewPreferences, setViewPreferences] = useState15(loadViewPreferences);
5496
5800
  const handleViewPreferencesChange = useCallback15((prefs) => {
5497
5801
  setViewPreferences(prefs);
@@ -5508,6 +5812,27 @@ function DocBlocksShell({
5508
5812
  const [sidebarWidth, setSidebarWidth] = useState15(loadSidebarWidth);
5509
5813
  const [compactLayout, setCompactLayout] = useState15(false);
5510
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]);
5511
5836
  const showBrowserStorageWarning = !isElectronHost3();
5512
5837
  const appVersion = isElectronHost3() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web";
5513
5838
  const issueReportUrl = buildIssueReportUrl({
@@ -5553,13 +5878,13 @@ function DocBlocksShell({
5553
5878
  lastRaw = drag.startWidth + (ev.clientX - drag.startX);
5554
5879
  if (lastRaw < SIDEBAR_COLLAPSE_THRESHOLD && sidebarRef.current) {
5555
5880
  sidebarRef.current.style.width = `${SIDEBAR_WIDTH_MIN}px`;
5556
- sidebarRef.current.style.opacity = "0.45";
5881
+ sidebarRef.current.classList.add("db-shell-sidebar--collapse-preview");
5557
5882
  return;
5558
5883
  }
5559
5884
  const clamped = Math.min(SIDEBAR_WIDTH_MAX, Math.max(SIDEBAR_WIDTH_MIN, lastRaw));
5560
5885
  if (sidebarRef.current) {
5561
5886
  sidebarRef.current.style.width = `${clamped}px`;
5562
- sidebarRef.current.style.opacity = "";
5887
+ sidebarRef.current.classList.remove("db-shell-sidebar--collapse-preview");
5563
5888
  }
5564
5889
  };
5565
5890
  const onUp = () => {
@@ -5568,7 +5893,7 @@ function DocBlocksShell({
5568
5893
  document.body.style.userSelect = "";
5569
5894
  document.body.classList.remove("db-resizing-sidebar");
5570
5895
  if (sidebarRef.current) {
5571
- sidebarRef.current.style.opacity = "";
5896
+ sidebarRef.current.classList.remove("db-shell-sidebar--collapse-preview");
5572
5897
  }
5573
5898
  if (lastRaw < SIDEBAR_COLLAPSE_THRESHOLD) {
5574
5899
  setCompactLayout(true);
@@ -5595,6 +5920,11 @@ function DocBlocksShell({
5595
5920
  const [workspaceStartupError, setWorkspaceStartupError] = useState15(null);
5596
5921
  const [activeWorkspaceId, setActiveWorkspaceId] = useState15(null);
5597
5922
  const [activeWorkspaceDescriptor, setActiveWorkspaceDescriptor] = useState15(null);
5923
+ const defaultProofingIgnoreStore = useMemo3(
5924
+ () => createLocalProofingIgnoreStore(() => activeWorkspaceId),
5925
+ [activeWorkspaceId]
5926
+ );
5927
+ const effectiveProofingIgnoreStore = proofingIgnoreStore === void 0 ? defaultProofingIgnoreStore : proofingIgnoreStore;
5598
5928
  useEffect15(() => {
5599
5929
  if (!provider || getTransientWorkspace(provider.id)) return;
5600
5930
  const providerV2 = getFileSystemProviderV27(provider);
@@ -5751,11 +6081,13 @@ function DocBlocksShell({
5751
6081
  const [selectedSourceFile, setSelectedSourceFile] = useState15(null);
5752
6082
  const [selectedOutsideIn, setSelectedOutsideIn] = useState15(null);
5753
6083
  const [selectedOutsideInEditingEnabled, setSelectedOutsideInEditingEnabled] = useState15(false);
6084
+ const [selectedImage, setSelectedImage] = useState15(void 0);
5754
6085
  const adoptSelectedDocument = useCallback15((document2) => {
5755
6086
  setSelectedFile(document2?.displayPath ?? null);
5756
6087
  setSelectedSourceFile(document2?.sourcePath ?? null);
5757
6088
  setSelectedOutsideIn(document2?.outsideIn ?? null);
5758
6089
  setSelectedOutsideInEditingEnabled(document2?.outsideInEditingEnabled ?? false);
6090
+ setSelectedImage(document2?.image);
5759
6091
  }, []);
5760
6092
  const adoptRegularDocument = useCallback15((path) => {
5761
6093
  if (path === null) {
@@ -5763,13 +6095,27 @@ function DocBlocksShell({
5763
6095
  setSelectedSourceFile(null);
5764
6096
  setSelectedOutsideIn(null);
5765
6097
  setSelectedOutsideInEditingEnabled(false);
6098
+ setSelectedImage(void 0);
5766
6099
  return;
5767
6100
  }
5768
6101
  setSelectedFile(path);
5769
6102
  setSelectedSourceFile(path);
5770
6103
  setSelectedOutsideIn(null);
5771
6104
  setSelectedOutsideInEditingEnabled(false);
6105
+ setSelectedImage(void 0);
5772
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
+ );
5773
6119
  useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath);
5774
6120
  const exportDestinationAdapter = useMemo3(() => {
5775
6121
  if (!selectedFile) return void 0;
@@ -6632,7 +6978,7 @@ function DocBlocksShell({
6632
6978
  const providerV2 = getFileSystemProviderV27(provider);
6633
6979
  if (!providerV2?.capabilities.watch) return;
6634
6980
  const watchedFile = selectedSourceFile ?? selectedFile;
6635
- if (!watchedFile || !documentSnapshot.targetKey) return;
6981
+ if (!watchedFile || !documentSnapshot.targetKey || selectedImage) return;
6636
6982
  const targetKey = documentSnapshot.targetKey;
6637
6983
  let disposed = false;
6638
6984
  let reading = false;
@@ -6686,7 +7032,14 @@ function DocBlocksShell({
6686
7032
  disposed = true;
6687
7033
  void subscription.dispose();
6688
7034
  };
6689
- }, [provider, selectedFile, selectedSourceFile, documentSession, documentSnapshot.targetKey]);
7035
+ }, [
7036
+ provider,
7037
+ selectedFile,
7038
+ selectedSourceFile,
7039
+ selectedImage,
7040
+ documentSession,
7041
+ documentSnapshot.targetKey
7042
+ ]);
6690
7043
  const transitionAwayFromDocument = useCallback15(
6691
7044
  async (requestId) => {
6692
7045
  if (requestId !== navigationRequestRef.current) return false;
@@ -7698,7 +8051,7 @@ function DocBlocksShell({
7698
8051
  const nextFile = selectedFile ? relocateProviderPath(selectedFile, change.oldPath, change.newPath) : null;
7699
8052
  const nextFolder = selectedFolder ? relocateProviderPath(selectedFolder, change.oldPath, change.newPath) : null;
7700
8053
  if (nextFile !== selectedFile) {
7701
- if (nextFile && selectedOutsideIn) {
8054
+ if (nextFile) {
7702
8055
  const opened = await loadEditableShellDocument(provider, nextFile);
7703
8056
  adoptSelectedDocument(opened);
7704
8057
  } else {
@@ -7743,7 +8096,6 @@ function DocBlocksShell({
7743
8096
  adoptRegularDocument,
7744
8097
  adoptSelectedDocument,
7745
8098
  selectedFile,
7746
- selectedOutsideIn,
7747
8099
  selectedFolder,
7748
8100
  activeWorkspaceId,
7749
8101
  pinnedDocuments,
@@ -8372,7 +8724,7 @@ function DocBlocksShell({
8372
8724
  return /* @__PURE__ */ jsx14(
8373
8725
  "div",
8374
8726
  {
8375
- className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}`,
8727
+ className: `db-shell${effectiveCompact ? " db-shell--mobile" : ""}${desktopToolbarWrapped ? " db-shell--desktop-toolbar-wrapped" : ""}`,
8376
8728
  "data-theme": resolvedTheme,
8377
8729
  "data-accent": accentColor,
8378
8730
  "data-document-status": documentSnapshot.status,
@@ -8433,6 +8785,8 @@ function DocBlocksShell({
8433
8785
  onAccentColorChange: handleAccentColorChange,
8434
8786
  writeCanvasSettings,
8435
8787
  onWriteCanvasSettingsChange: handleWriteCanvasSettingsChange,
8788
+ proofingPreferences,
8789
+ onProofingPreferencesChange: handleProofingPreferencesChange,
8436
8790
  versioningPreference,
8437
8791
  onVersioningPreferenceChange: handleVersioningPreferenceChange,
8438
8792
  onDownloadAllWorkspaces: handleDownloadAllWorkspaces,
@@ -8544,6 +8898,7 @@ function DocBlocksShell({
8544
8898
  ]
8545
8899
  }
8546
8900
  ),
8901
+ git.available && git.pendingGrant && /* @__PURE__ */ jsx14(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx14(GitGrantNotice, {}) }),
8547
8902
  /* @__PURE__ */ jsxs12("div", { className: "db-shell-sidebar-footer", children: [
8548
8903
  /* @__PURE__ */ jsx14("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
8549
8904
  /* @__PURE__ */ jsx14("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
@@ -8563,6 +8918,10 @@ function DocBlocksShell({
8563
8918
  }
8564
8919
  )
8565
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" })
8566
8925
  ] })
8567
8926
  ]
8568
8927
  }
@@ -8580,6 +8939,7 @@ function DocBlocksShell({
8580
8939
  (!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs12(
8581
8940
  "main",
8582
8941
  {
8942
+ ref: editorAreaRef,
8583
8943
  "aria-label": "Document editor",
8584
8944
  className: updateAvailable && onApplyUpdate && updateStatusBarVisible ? "db-shell-editor-area db-shell-editor-area--has-update" : "db-shell-editor-area",
8585
8945
  style: {
@@ -8599,11 +8959,13 @@ function DocBlocksShell({
8599
8959
  EditorShell,
8600
8960
  {
8601
8961
  initialMarkdown: editorContent,
8602
- readOnly: selectedOutsideIn !== null && !selectedOutsideInEditingEnabled,
8962
+ readOnly: selectedImage !== void 0 || selectedOutsideIn !== null && !selectedOutsideInEditingEnabled,
8603
8963
  initialView,
8604
8964
  defaultViewportPreset: defaultPreviewViewportPreset,
8605
8965
  articleId: selectedFile,
8606
8966
  fileName: selectedFile,
8967
+ imageSrc: selectedImageUrl,
8968
+ imageAlt: basenameOf(selectedFile),
8607
8969
  saveCoverImageOutput: saveRenderedImageOutput,
8608
8970
  saveDashboardImageOutput: saveRenderedImageOutput,
8609
8971
  onChange: handleEditorChange,
@@ -8614,6 +8976,12 @@ function DocBlocksShell({
8614
8976
  placeholder: editorPlaceholder,
8615
8977
  outlineWidth: 280,
8616
8978
  mediaProvider,
8979
+ calcEngineFactory,
8980
+ proofing,
8981
+ proofingDefaultEnabled,
8982
+ proofingSpellingEnabled: proofingPreferences.spelling,
8983
+ proofingGrammarEnabled: proofingPreferences.grammar,
8984
+ proofingIgnoreStore: effectiveProofingIgnoreStore,
8617
8985
  showCodeCopyButton,
8618
8986
  onCopyCode,
8619
8987
  allowRecording,
@@ -8621,7 +8989,7 @@ function DocBlocksShell({
8621
8989
  allowPresentationFullscreen,
8622
8990
  documentLinkProvider,
8623
8991
  workspaceContainer: versionsContainer ?? void 0,
8624
- allowVersioning: effectiveVersioning,
8992
+ allowVersioning: selectedImage === void 0 && effectiveVersioning,
8625
8993
  viewPreferences,
8626
8994
  onViewPreferencesChange: handleViewPreferencesChange,
8627
8995
  versionBasename: versionBasename ?? stripExtension(basenameOf(selectedFile)),
@@ -8764,12 +9132,14 @@ export {
8764
9132
  AccentColorSettings,
8765
9133
  AppMenu,
8766
9134
  DEFAULT_OPTIONS,
9135
+ DEFAULT_PROOFING_PREFERENCES,
8767
9136
  DEFAULT_WRITE_CANVAS_FONT_SCHEME,
8768
9137
  DocBlocksShell,
8769
9138
  ExportDialog,
8770
9139
  ExportToolbarControls,
8771
9140
  FileExplorer,
8772
9141
  FileTreeNode,
9142
+ ProofingSettingsControls,
8773
9143
  SettingsDialog,
8774
9144
  ThemeSettings,
8775
9145
  WRITE_CANVAS_FONT_SCHEMES,
@@ -8780,8 +9150,10 @@ export {
8780
9150
  createImageSaveOutput as createDashboardImageSaveOutput,
8781
9151
  createImageSaveOutput,
8782
9152
  loadLastExportOptions,
9153
+ loadProofingPreferences,
8783
9154
  resolveWriteCanvasFonts,
8784
9155
  runExport,
9156
+ saveProofingPreferences,
8785
9157
  updateExportTargetExtension,
8786
9158
  useDocumentSession,
8787
9159
  useFileTree