@bendyline/docblocks-react 2.1.0 → 2.1.2

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.
Files changed (49) hide show
  1. package/README.md +12 -3
  2. package/dist/ExportDialog-VYATD5UC.js +9 -0
  3. package/dist/{ExportToolbarControls-GHAOJMH6.js → ExportToolbarControls-C46GMPKC.js} +70 -41
  4. package/dist/{GitToolbarControl-GNMRVKWQ.js → GitToolbarControl-FW3BZFXV.js} +2 -3
  5. package/dist/{GitUI-VHOBQY43.js → GitUI-GMH3PAYL.js} +3 -4
  6. package/dist/LazyEditorShell-LU4QFPDX.js +5 -0
  7. package/dist/{ShareDialog-63ENBEDZ.js → ShareDialog-PAMOQQFB.js} +0 -1
  8. package/dist/{chunk-UNYZQXWR.js → chunk-3CZ27FOA.js} +45 -10
  9. package/dist/{chunk-FEWQUQML.js → chunk-3QSZY74C.js} +1 -2
  10. package/dist/chunk-BI7NVU6T.js +31 -0
  11. package/dist/{chunk-VHDP4IPL.js → chunk-BPGEBGAN.js} +0 -1
  12. package/dist/{chunk-DD4VFJQI.js → chunk-EBWYGTN7.js} +2 -1
  13. package/dist/{chunk-ZI56BNB7.js → chunk-GHQ4X7KM.js} +27 -7
  14. package/dist/{chunk-5A4KEWKT.js → chunk-HFYPTTCH.js} +0 -1
  15. package/dist/{chunk-FMGTHSTX.js → chunk-LG6HAWCK.js} +0 -1
  16. package/dist/{chunk-57ZJJOC5.js → chunk-TFMO7EVO.js} +0 -1
  17. package/dist/{chunk-4XXSWN3T.js → chunk-YBEYTVU2.js} +0 -1
  18. package/dist/editor.js +1 -2
  19. package/dist/export/index.d.ts +18 -8
  20. package/dist/export/index.js +10 -7
  21. package/dist/index.d.ts +16 -7
  22. package/dist/index.js +359 -103
  23. package/dist/run-export-Z5BVFLIR.js +9 -0
  24. package/dist/settings/index.js +2 -3
  25. package/package.json +8 -8
  26. package/src/styles/docblocks.css +485 -9
  27. package/dist/ExportDialog-7LDIXJZ3.js +0 -10
  28. package/dist/ExportDialog-7LDIXJZ3.js.map +0 -1
  29. package/dist/ExportToolbarControls-GHAOJMH6.js.map +0 -1
  30. package/dist/GitToolbarControl-GNMRVKWQ.js.map +0 -1
  31. package/dist/GitUI-VHOBQY43.js.map +0 -1
  32. package/dist/LazyEditorShell-EF2BWRXU.js +0 -6
  33. package/dist/LazyEditorShell-EF2BWRXU.js.map +0 -1
  34. package/dist/ShareDialog-63ENBEDZ.js.map +0 -1
  35. package/dist/chunk-4XXSWN3T.js.map +0 -1
  36. package/dist/chunk-57ZJJOC5.js.map +0 -1
  37. package/dist/chunk-5A4KEWKT.js.map +0 -1
  38. package/dist/chunk-DD4VFJQI.js.map +0 -1
  39. package/dist/chunk-FEWQUQML.js.map +0 -1
  40. package/dist/chunk-FMGTHSTX.js.map +0 -1
  41. package/dist/chunk-UNYZQXWR.js.map +0 -1
  42. package/dist/chunk-VHDP4IPL.js.map +0 -1
  43. package/dist/chunk-ZI56BNB7.js.map +0 -1
  44. package/dist/editor.js.map +0 -1
  45. package/dist/export/index.js.map +0 -1
  46. package/dist/index.js.map +0 -1
  47. package/dist/run-export-ICTZITXC.js +0 -10
  48. package/dist/run-export-ICTZITXC.js.map +0 -1
  49. package/dist/settings/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -6,7 +6,10 @@ import {
6
6
  conflictedPaths,
7
7
  isFileDirty,
8
8
  useGitContext
9
- } from "./chunk-5A4KEWKT.js";
9
+ } from "./chunk-HFYPTTCH.js";
10
+ import {
11
+ updateExportTargetExtension
12
+ } from "./chunk-BI7NVU6T.js";
10
13
  import {
11
14
  AccentColorSettings,
12
15
  DB_CHROME_COLORS,
@@ -20,25 +23,25 @@ import {
20
23
  saveAccentColor,
21
24
  saveThemePreference,
22
25
  saveWriteCanvasPreferences
23
- } from "./chunk-FEWQUQML.js";
26
+ } from "./chunk-3QSZY74C.js";
24
27
  import {
25
28
  pickEmptyDocumentPrompt
26
- } from "./chunk-VHDP4IPL.js";
29
+ } from "./chunk-BPGEBGAN.js";
27
30
  import {
28
31
  ExportDialog
29
- } from "./chunk-UNYZQXWR.js";
32
+ } from "./chunk-3CZ27FOA.js";
30
33
  import {
31
34
  buildExportFilename,
32
35
  runExport
33
- } from "./chunk-ZI56BNB7.js";
34
- import "./chunk-4XXSWN3T.js";
36
+ } from "./chunk-GHQ4X7KM.js";
37
+ import "./chunk-YBEYTVU2.js";
35
38
  import {
36
39
  Dialog
37
- } from "./chunk-FMGTHSTX.js";
40
+ } from "./chunk-LG6HAWCK.js";
38
41
  import {
39
42
  DEFAULT_OPTIONS,
40
43
  loadLastExportOptions
41
- } from "./chunk-DD4VFJQI.js";
44
+ } from "./chunk-EBWYGTN7.js";
42
45
 
43
46
  // src/FileExplorer/FileExplorer.tsx
44
47
  import { useCallback as useCallback3, useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState3 } from "react";
@@ -218,13 +221,19 @@ function useFileTree(provider) {
218
221
  }
219
222
  })();
220
223
  };
221
- const subscription = providerV2.watch(requestRefresh, {
222
- onError: (caught) => {
223
- if (disposed) return;
224
- reportError(caught);
224
+ const subscription = providerV2.watch(
225
+ (event) => {
226
+ if (event.type === "modified") return;
225
227
  requestRefresh();
228
+ },
229
+ {
230
+ onError: (caught) => {
231
+ if (disposed) return;
232
+ reportError(caught);
233
+ requestRefresh();
234
+ }
226
235
  }
227
- });
236
+ );
228
237
  void subscription.ready.catch((caught) => {
229
238
  if (!disposed) reportError(caught);
230
239
  });
@@ -1291,11 +1300,11 @@ function FileExplorer({
1291
1300
  disabled: newItemCreationPending,
1292
1301
  onClick: () => {
1293
1302
  setNewItemError(null);
1294
- setNewItemType("file");
1303
+ setNewItemType("directory");
1295
1304
  },
1296
- title: "New File",
1297
- "aria-label": "New File",
1298
- children: /* @__PURE__ */ jsx3(NewFileIcon, {})
1305
+ title: "New Folder",
1306
+ "aria-label": "New Folder",
1307
+ children: /* @__PURE__ */ jsx3(NewFolderIcon, {})
1299
1308
  }
1300
1309
  ),
1301
1310
  /* @__PURE__ */ jsx3(
@@ -1305,11 +1314,11 @@ function FileExplorer({
1305
1314
  disabled: newItemCreationPending,
1306
1315
  onClick: () => {
1307
1316
  setNewItemError(null);
1308
- setNewItemType("directory");
1317
+ setNewItemType("file");
1309
1318
  },
1310
- title: "New Folder",
1311
- "aria-label": "New Folder",
1312
- children: /* @__PURE__ */ jsx3(NewFolderIcon, {})
1319
+ title: "New File",
1320
+ "aria-label": "New File",
1321
+ children: /* @__PURE__ */ jsx3(NewFileIcon, {})
1313
1322
  }
1314
1323
  )
1315
1324
  ] })
@@ -1469,12 +1478,18 @@ function WorkspacePicker({
1469
1478
  }) {
1470
1479
  const [workspaces, setWorkspaces] = useState4([]);
1471
1480
  const [isOpen, setIsOpen] = useState4(false);
1481
+ const [creatingNew, setCreatingNew] = useState4(false);
1482
+ const [newWorkspaceName, setNewWorkspaceName] = useState4("");
1483
+ const [newWorkspaceError, setNewWorkspaceError] = useState4(null);
1484
+ const [newWorkspacePending, setNewWorkspacePending] = useState4(false);
1472
1485
  const pickerRef = useRef4(null);
1473
1486
  useEffect4(() => {
1474
1487
  if (!isOpen) return;
1475
1488
  function handleOutsideClick(e) {
1476
1489
  if (pickerRef.current && !pickerRef.current.contains(e.target)) {
1477
1490
  setIsOpen(false);
1491
+ setCreatingNew(false);
1492
+ setNewWorkspaceError(null);
1478
1493
  }
1479
1494
  }
1480
1495
  document.addEventListener("mousedown", handleOutsideClick);
@@ -1497,8 +1512,34 @@ function WorkspacePicker({
1497
1512
  },
1498
1513
  [onSelect]
1499
1514
  );
1515
+ const handleStartCreateNew = useCallback4(() => {
1516
+ const existingNames = new Set(workspaces.map((workspace) => workspace.name.toLowerCase()));
1517
+ let suffix = workspaces.length + 1;
1518
+ while (existingNames.has(`workspace ${suffix}`.toLowerCase())) suffix += 1;
1519
+ setNewWorkspaceName(`Workspace ${suffix}`);
1520
+ setNewWorkspaceError(null);
1521
+ setCreatingNew(true);
1522
+ }, [workspaces]);
1523
+ const handleCancelCreateNew = useCallback4(() => {
1524
+ setCreatingNew(false);
1525
+ setNewWorkspaceName("");
1526
+ setNewWorkspaceError(null);
1527
+ }, []);
1500
1528
  const handleCreateNew = useCallback4(async () => {
1501
- const name = `Workspace ${workspaces.length + 1}`;
1529
+ if (newWorkspacePending) return;
1530
+ const name = newWorkspaceName.trim();
1531
+ if (!name) {
1532
+ setNewWorkspaceError("Enter a workspace name.");
1533
+ return;
1534
+ }
1535
+ if (name.length > 80) {
1536
+ setNewWorkspaceError("Workspace names must be 80 characters or fewer.");
1537
+ return;
1538
+ }
1539
+ if (workspaces.some((workspace) => workspace.name.toLowerCase() === name.toLowerCase())) {
1540
+ setNewWorkspaceError("A workspace with that name already exists.");
1541
+ return;
1542
+ }
1502
1543
  const id = `ws-${Date.now()}`;
1503
1544
  const descriptor = {
1504
1545
  id,
@@ -1506,11 +1547,21 @@ function WorkspacePicker({
1506
1547
  type: "indexeddb",
1507
1548
  lastOpened: (/* @__PURE__ */ new Date()).toISOString()
1508
1549
  };
1509
- await saveWorkspace(descriptor);
1510
- await refresh();
1511
- onSelect(descriptor);
1512
- setIsOpen(false);
1513
- }, [workspaces.length, onSelect, refresh]);
1550
+ setNewWorkspacePending(true);
1551
+ setNewWorkspaceError(null);
1552
+ try {
1553
+ await saveWorkspace(descriptor);
1554
+ await refresh();
1555
+ onSelect(descriptor);
1556
+ setCreatingNew(false);
1557
+ setNewWorkspaceName("");
1558
+ setIsOpen(false);
1559
+ } catch {
1560
+ setNewWorkspaceError("The workspace could not be created. Try again.");
1561
+ } finally {
1562
+ setNewWorkspacePending(false);
1563
+ }
1564
+ }, [newWorkspaceName, newWorkspacePending, onSelect, refresh, workspaces]);
1514
1565
  const activeWs = workspaces.find((w) => w.id === activeWorkspaceId);
1515
1566
  const activeWorkspaceName = activeWs?.name ?? "No workspace";
1516
1567
  return /* @__PURE__ */ jsxs4("div", { ref: pickerRef, className: `db-workspace-picker ${className ?? ""}`, children: [
@@ -1518,7 +1569,11 @@ function WorkspacePicker({
1518
1569
  "button",
1519
1570
  {
1520
1571
  className: "db-workspace-picker-btn",
1521
- onClick: () => setIsOpen(!isOpen),
1572
+ onClick: () => {
1573
+ const nextOpen = !isOpen;
1574
+ setIsOpen(nextOpen);
1575
+ if (!nextOpen) handleCancelCreateNew();
1576
+ },
1522
1577
  title: "Switch workspace",
1523
1578
  "aria-label": `Switch workspace, current: ${activeWorkspaceName}`,
1524
1579
  children: [
@@ -1551,10 +1606,57 @@ function WorkspacePicker({
1551
1606
  ws.id
1552
1607
  )),
1553
1608
  /* @__PURE__ */ jsx4("div", { className: "db-workspace-dropdown-divider" }),
1554
- !electron && /* @__PURE__ */ jsx4("button", { className: "db-workspace-dropdown-item", onClick: handleCreateNew, children: /* @__PURE__ */ jsxs4("span", { className: "db-workspace-dropdown-action-label", children: [
1609
+ !electron && (creatingNew ? /* @__PURE__ */ jsxs4(
1610
+ "form",
1611
+ {
1612
+ className: "db-workspace-create",
1613
+ "aria-busy": newWorkspacePending,
1614
+ onSubmit: (event) => {
1615
+ event.preventDefault();
1616
+ void handleCreateNew();
1617
+ },
1618
+ children: [
1619
+ /* @__PURE__ */ jsx4("label", { className: "db-workspace-create-label", htmlFor: "db-new-workspace-name", children: "Workspace name" }),
1620
+ /* @__PURE__ */ jsx4(
1621
+ "input",
1622
+ {
1623
+ id: "db-new-workspace-name",
1624
+ className: "db-workspace-create-input",
1625
+ value: newWorkspaceName,
1626
+ maxLength: 80,
1627
+ disabled: newWorkspacePending,
1628
+ "aria-invalid": newWorkspaceError !== null,
1629
+ "aria-describedby": newWorkspaceError ? "db-new-workspace-error" : void 0,
1630
+ autoFocus: true,
1631
+ onChange: (event) => {
1632
+ setNewWorkspaceName(event.target.value);
1633
+ setNewWorkspaceError(null);
1634
+ },
1635
+ onKeyDown: (event) => {
1636
+ if (event.key === "Escape") handleCancelCreateNew();
1637
+ }
1638
+ }
1639
+ ),
1640
+ newWorkspaceError && /* @__PURE__ */ jsx4("p", { id: "db-new-workspace-error", className: "db-workspace-create-error", role: "alert", children: newWorkspaceError }),
1641
+ /* @__PURE__ */ jsxs4("div", { className: "db-workspace-create-actions", children: [
1642
+ /* @__PURE__ */ jsx4(
1643
+ "button",
1644
+ {
1645
+ type: "button",
1646
+ className: "db-workspace-create-cancel",
1647
+ disabled: newWorkspacePending,
1648
+ onClick: handleCancelCreateNew,
1649
+ children: "Cancel"
1650
+ }
1651
+ ),
1652
+ /* @__PURE__ */ jsx4("button", { type: "submit", disabled: newWorkspacePending, children: newWorkspacePending ? "Creating\xE2\u20AC\xA6" : "Create" })
1653
+ ] })
1654
+ ]
1655
+ }
1656
+ ) : /* @__PURE__ */ jsx4("button", { className: "db-workspace-dropdown-item", onClick: handleStartCreateNew, children: /* @__PURE__ */ jsxs4("span", { className: "db-workspace-dropdown-action-label", children: [
1555
1657
  /* @__PURE__ */ jsx4(NewFolderIcon, {}),
1556
1658
  /* @__PURE__ */ jsx4("span", { children: "New Workspace" })
1557
- ] }) }),
1659
+ ] }) })),
1558
1660
  (electron || isNativeFileSystemSupported()) && /* @__PURE__ */ jsx4(
1559
1661
  "button",
1560
1662
  {
@@ -1583,7 +1685,6 @@ function WorkspacePicker({
1583
1685
 
1584
1686
  // src/DocBlocksShell/DocBlocksShell.tsx
1585
1687
  import { useState as useState14, useCallback as useCallback14, useEffect as useEffect14, useRef as useRef14, useMemo as useMemo3, lazy as lazy2, Suspense as Suspense2 } from "react";
1586
- import "@bendyline/squisq-editor-react/styles";
1587
1688
  import { MediaContext } from "@bendyline/squisq-react";
1588
1689
  import {
1589
1690
  DocumentVersionManager
@@ -1648,7 +1749,9 @@ function AppMenu({
1648
1749
  onKeepBrowserData,
1649
1750
  onInstallApp,
1650
1751
  getStorageEstimate,
1651
- storagePersistent
1752
+ storagePersistent,
1753
+ appVersion,
1754
+ appBuildDate
1652
1755
  }) {
1653
1756
  const [isOpen, setIsOpen] = useState5(false);
1654
1757
  const [showAbout, setShowAbout] = useState5(false);
@@ -1745,8 +1848,8 @@ function AppMenu({
1745
1848
  {
1746
1849
  className: "db-app-menu-item",
1747
1850
  role: "menuitem",
1748
- onClick: () => handleAction(() => void onKeepBrowserData()),
1749
- children: "Keep data in browser for longer"
1851
+ onClick: () => handleAction(() => setShowSettings(true)),
1852
+ children: "Storage protection\u2026"
1750
1853
  }
1751
1854
  ),
1752
1855
  onDownloadAllWorkspaces && /* @__PURE__ */ jsx5(
@@ -1799,7 +1902,7 @@ function AppMenu({
1799
1902
  )} of the ${formatBytes(
1800
1903
  storageEstimate.quota
1801
1904
  )} this browser allows for the site.` : "Storage usage is not available in this browser." }),
1802
- storagePersistent !== void 0 && /* @__PURE__ */ jsx5("p", { className: "db-settings-hint", children: storagePersistent ? "Data is marked persistent, so the browser will avoid evicting it." : "Data is not yet marked persistent." }),
1905
+ storagePersistent !== void 0 && /* @__PURE__ */ jsx5("p", { className: "db-settings-hint", children: storagePersistent ? "Protected from routine browser cleanup on this device." : "Browsers may clear site data under storage pressure unless protection is granted." }),
1803
1906
  onKeepBrowserData && /* @__PURE__ */ jsx5(
1804
1907
  "button",
1805
1908
  {
@@ -1807,7 +1910,7 @@ function AppMenu({
1807
1910
  className: "db-settings-action",
1808
1911
  disabled: requestingPersistentStorage,
1809
1912
  onClick: () => void handleKeepBrowserDataFromSettings(),
1810
- children: requestingPersistentStorage ? "Requesting persistent storage\u2026" : "Keep data in browser for longer"
1913
+ children: requestingPersistentStorage ? "Requesting storage protection\u2026" : "Protect data from browser cleanup"
1811
1914
  }
1812
1915
  )
1813
1916
  ] }),
@@ -1863,7 +1966,15 @@ function AppMenu({
1863
1966
  showAbout && /* @__PURE__ */ jsxs5(Dialog, { title: "About DocBlocks", onClose: () => setShowAbout(false), children: [
1864
1967
  /* @__PURE__ */ jsxs5("p", { children: [
1865
1968
  /* @__PURE__ */ jsx5("strong", { children: "DocBlocks" }),
1866
- " is a markdown document editor that runs entirely in your browser. Your files stay on your device."
1969
+ " is a local-first Markdown document editor. Your files stay under your control."
1970
+ ] }),
1971
+ appVersion && /* @__PURE__ */ jsxs5("p", { className: "db-about-version", children: [
1972
+ /* @__PURE__ */ jsx5("span", { children: "Version" }),
1973
+ /* @__PURE__ */ jsx5("code", { "aria-label": `DocBlocks version ${appVersion}`, children: appVersion })
1974
+ ] }),
1975
+ appBuildDate && /* @__PURE__ */ jsxs5("p", { className: "db-about-version", children: [
1976
+ /* @__PURE__ */ jsx5("span", { children: "Build" }),
1977
+ /* @__PURE__ */ jsx5("time", { dateTime: appBuildDate, children: appBuildDate })
1867
1978
  ] }),
1868
1979
  /* @__PURE__ */ jsxs5("p", { children: [
1869
1980
  "Built with",
@@ -1886,6 +1997,26 @@ function AppMenu({
1886
1997
  }
1887
1998
  ),
1888
1999
  /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2000
+ /* @__PURE__ */ jsx5(
2001
+ "a",
2002
+ {
2003
+ href: "https://github.com/bendyline/docblocks/releases",
2004
+ target: "_blank",
2005
+ rel: "noopener noreferrer",
2006
+ children: "Release notes"
2007
+ }
2008
+ ),
2009
+ /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2010
+ /* @__PURE__ */ jsx5(
2011
+ "a",
2012
+ {
2013
+ href: "https://github.com/bendyline/docblocks/issues",
2014
+ target: "_blank",
2015
+ rel: "noopener noreferrer",
2016
+ children: "Support"
2017
+ }
2018
+ ),
2019
+ /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
1889
2020
  /* @__PURE__ */ jsx5(
1890
2021
  "a",
1891
2022
  {
@@ -2129,7 +2260,7 @@ function useDocumentSession(autoSaveDelayMs = 500) {
2129
2260
  import { lazy, Suspense } from "react";
2130
2261
  import { jsx as jsx8 } from "react/jsx-runtime";
2131
2262
  var ExportToolbarControlsImplementation = lazy(
2132
- () => import("./ExportToolbarControls-GHAOJMH6.js").then((module) => ({
2263
+ () => import("./ExportToolbarControls-C46GMPKC.js").then((module) => ({
2133
2264
  default: module.ExportToolbarControls
2134
2265
  }))
2135
2266
  );
@@ -2514,17 +2645,45 @@ function retainFileSystemProvider(provider) {
2514
2645
  // src/DocBlocksShell/document-title.ts
2515
2646
  import { useEffect as useEffect9 } from "react";
2516
2647
  var APP_TITLE = "DocBlocks";
2517
- function titleForSelectedFile(selectedFile, homeDocumentTitle = APP_TITLE, homeDocumentPath) {
2518
- if (!selectedFile || selectedFile === homeDocumentPath) return homeDocumentTitle;
2648
+ var INSTALLED_DISPLAY_QUERIES = [
2649
+ "(display-mode: window-controls-overlay)",
2650
+ "(display-mode: standalone)"
2651
+ ];
2652
+ function unbrandedHomeTitle(homeDocumentTitle) {
2653
+ if (homeDocumentTitle === APP_TITLE) return "";
2654
+ for (const separator of [" \u2014 ", " - ", ": ", " | "]) {
2655
+ const prefix = `${APP_TITLE}${separator}`;
2656
+ if (homeDocumentTitle.startsWith(prefix)) return homeDocumentTitle.slice(prefix.length);
2657
+ }
2658
+ return homeDocumentTitle;
2659
+ }
2660
+ function titleForSelectedFile(selectedFile, homeDocumentTitle = APP_TITLE, homeDocumentPath, includeAppName = true) {
2661
+ if (!selectedFile || selectedFile === homeDocumentPath) {
2662
+ return includeAppName ? homeDocumentTitle : unbrandedHomeTitle(homeDocumentTitle);
2663
+ }
2519
2664
  const filename = selectedFile.replace(/^\/+/, "").split("/").pop();
2520
2665
  const name = filename?.replace(/\.[^.]+$/, "");
2521
- return name ? `${name} - ${APP_TITLE}` : APP_TITLE;
2666
+ if (!name) return includeAppName ? APP_TITLE : "";
2667
+ return includeAppName ? `${name} - ${APP_TITLE}` : name;
2522
2668
  }
2523
2669
  function useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath) {
2524
2670
  useEffect9(() => {
2525
- if (typeof document !== "undefined") {
2526
- document.title = titleForSelectedFile(selectedFile, homeDocumentTitle, homeDocumentPath);
2527
- }
2671
+ if (typeof document === "undefined") return;
2672
+ const displayQueries = typeof globalThis.matchMedia === "function" ? INSTALLED_DISPLAY_QUERIES.map((query) => globalThis.matchMedia(query)) : [];
2673
+ const updateTitle = () => {
2674
+ const includeAppName = !displayQueries.some((query) => query.matches);
2675
+ document.title = titleForSelectedFile(
2676
+ selectedFile,
2677
+ homeDocumentTitle,
2678
+ homeDocumentPath,
2679
+ includeAppName
2680
+ );
2681
+ };
2682
+ updateTitle();
2683
+ for (const query of displayQueries) query.addEventListener("change", updateTitle);
2684
+ return () => {
2685
+ for (const query of displayQueries) query.removeEventListener("change", updateTitle);
2686
+ };
2528
2687
  }, [homeDocumentPath, homeDocumentTitle, selectedFile]);
2529
2688
  }
2530
2689
 
@@ -3055,7 +3214,7 @@ function UpdateAvailableNotice({
3055
3214
  blocked = false,
3056
3215
  statusBarVisible
3057
3216
  }) {
3058
- const [promptOpen, setPromptOpen] = useState13(false);
3217
+ const [promptOpen, setPromptOpen] = useState13(true);
3059
3218
  if (!available || !onApplyUpdate) return null;
3060
3219
  const promptVisible = promptOpen && !blocked;
3061
3220
  return /* @__PURE__ */ jsxs10(Fragment6, { children: [
@@ -3074,9 +3233,9 @@ function UpdateAvailableNotice({
3074
3233
  }
3075
3234
  ),
3076
3235
  promptVisible && /* @__PURE__ */ jsxs10("div", { id: UPDATE_PROMPT_ID, className: "db-update-banner", role: "alert", children: [
3077
- /* @__PURE__ */ jsx11("span", { children: "A new version of DocBlocks is available." }),
3236
+ /* @__PURE__ */ jsx11("span", { children: "A new version of DocBlocks is available. Reload to update the editor and site pages." }),
3078
3237
  /* @__PURE__ */ jsxs10("div", { className: "db-update-banner-actions", children: [
3079
- /* @__PURE__ */ jsx11("button", { type: "button", onClick: onApplyUpdate, children: "Reload" }),
3238
+ /* @__PURE__ */ jsx11("button", { type: "button", onClick: onApplyUpdate, disabled: blocked, children: "Reload" }),
3080
3239
  /* @__PURE__ */ jsx11("button", { type: "button", onClick: () => setPromptOpen(false), children: "Later" })
3081
3240
  ] })
3082
3241
  ] })
@@ -3392,14 +3551,20 @@ async function copyTransientWorkspaceContents(source, destination) {
3392
3551
 
3393
3552
  // src/DocBlocksShell/DocBlocksShell.tsx
3394
3553
  import { Fragment as Fragment7, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3395
- var EditorShell = lazy2(async () => {
3396
- const workersReady = globalThis.docBlocksMonacoWorkersReady;
3397
- await workersReady?.catch(() => void 0);
3398
- return import("./LazyEditorShell-EF2BWRXU.js");
3399
- });
3400
- var GitUI = lazy2(() => import("./GitUI-VHOBQY43.js").then((m) => ({ default: m.GitUI })));
3554
+ var editorShellModulePromise = null;
3555
+ function loadEditorShell() {
3556
+ editorShellModulePromise ?? (editorShellModulePromise = (async () => {
3557
+ const workersReady = globalThis.docBlocksMonacoWorkersReady;
3558
+ const editorReady = import("./LazyEditorShell-LU4QFPDX.js");
3559
+ const [editorModule] = await Promise.all([editorReady, workersReady?.catch(() => void 0)]);
3560
+ return editorModule;
3561
+ })());
3562
+ return editorShellModulePromise;
3563
+ }
3564
+ var EditorShell = lazy2(loadEditorShell);
3565
+ var GitUI = lazy2(() => import("./GitUI-GMH3PAYL.js").then((m) => ({ default: m.GitUI })));
3401
3566
  var GitToolbarControl = lazy2(
3402
- () => import("./GitToolbarControl-GNMRVKWQ.js").then((m) => ({ default: m.GitToolbarControl }))
3567
+ () => import("./GitToolbarControl-FW3BZFXV.js").then((m) => ({ default: m.GitToolbarControl }))
3403
3568
  );
3404
3569
  var DOCBLOCKS_VIDEO_EXPORT_PALETTE = Object.freeze({
3405
3570
  overlay: "rgba(0, 0, 0, 0.72)",
@@ -3679,6 +3844,8 @@ function DocBlocksShell({
3679
3844
  theme: hostTheme = "auto",
3680
3845
  logoUrl,
3681
3846
  issueReportVersion,
3847
+ appBuildDate,
3848
+ ffmpegWasm,
3682
3849
  homeDocumentTitle,
3683
3850
  homeDocumentPath,
3684
3851
  allowVersioning = true,
@@ -3723,14 +3890,20 @@ function DocBlocksShell({
3723
3890
  saveViewPreferences(prefs);
3724
3891
  }, []);
3725
3892
  const isMobile = useIsMobile();
3726
- const [mobileShowEditor, setMobileShowEditor] = useState14(false);
3893
+ const [mobileShowEditor, setMobileShowEditor] = useState14(
3894
+ () => isMobile && isWelcomeGatewayDismissed()
3895
+ );
3896
+ useEffect14(() => {
3897
+ if (isMobile) void loadEditorShell();
3898
+ }, [isMobile]);
3727
3899
  const [sidebarWidth, setSidebarWidth] = useState14(loadSidebarWidth);
3728
3900
  const [compactLayout, setCompactLayout] = useState14(false);
3729
3901
  const effectiveCompact = isMobile || compactLayout;
3730
3902
  const showBrowserStorageWarning = !isElectronHost2();
3903
+ const appVersion = isElectronHost2() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web";
3731
3904
  const issueReportUrl = buildIssueReportUrl({
3732
3905
  reportedAt: /* @__PURE__ */ new Date(),
3733
- version: isElectronHost2() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web",
3906
+ version: appVersion,
3734
3907
  userAgent: typeof navigator === "undefined" ? "" : navigator.userAgent
3735
3908
  });
3736
3909
  const [browserStoragePersistent, setBrowserStoragePersistent] = useState14(false);
@@ -4259,25 +4432,25 @@ function DocBlocksShell({
4259
4432
  "",
4260
4433
  "Your markdown can do anything.",
4261
4434
  "",
4262
- "DocBlocks is a free browser-based markdown document editor that lets you create, organize, and manage your documents right in the browser. What you write here can become a Word or PDF doc, a slide deck, an e-book, or a video.",
4435
+ "DocBlocks is a free, local-first Markdown editor for creating, organizing, and publishing documents without sending your writing to a server. What you write here can become a Word or PDF document, a slide deck, a web page, or a video.",
4263
4436
  "",
4264
4437
  "Simple to write. Beautiful wherever it goes.",
4265
4438
  "",
4266
4439
  "## Features",
4267
4440
  "",
4268
- "- **Rich Markdown Editing** -- Write in a visual editor or switch to raw markdown source anytime. Use section annotations to change the visualization for blocks of content.",
4269
- "- **Workspaces** -- Organize your documents into separate workspaces in the browser or on your device.",
4270
- "- **Useful Everywhere** -- Your content is usable across multiple formats -- Microsoft Word .docx, PowerPoint, PDF, HTML, EPUB e-books, and Markdown.",
4271
- "- **Playback & Video** -- Preview your documents as rich visual presentations and export them as MP4 video",
4272
- "- **No BS** -- Free, no ads, no accounts, no tracking - everything runs locally in your browser",
4441
+ "- **Rich Markdown editing** \u2014 Write visually or switch to raw Markdown source at any time. Section annotations can change how individual blocks are presented.",
4442
+ "- **Workspaces** \u2014 Organize documents into separate browser workspaces or folders on your device.",
4443
+ "- **Useful everywhere** \u2014 Export Word, PowerPoint, PDF, HTML, and Markdown in the app, with EPUB and additional formats available through the CLI.",
4444
+ "- **Playback and video** \u2014 Preview a document as a rich presentation and export it as an MP4 video.",
4445
+ "- **Private by default** \u2014 No ads, accounts, or tracking. Your browser workspace stays on this device.",
4273
4446
  "",
4274
4447
  "## Getting Started",
4275
4448
  "",
4276
- "1. Create a new file using the **New File** button in the sidebar",
4277
- "2. Start writing in markdown -- the editor supports headings, lists, links, images, and more",
4278
- "3. Your work is saved automatically",
4449
+ "1. Create a document with the **New file** button in the file pane.",
4450
+ "2. Start writing in Markdown \u2014 the editor supports headings, lists, links, images, and more.",
4451
+ "3. Your work is saved automatically.",
4279
4452
  "",
4280
- "Built on [Squiggly Square markdown extensions](https://github.com/bendyline/squisq) by [Bendyline](https://bendyline.com).",
4453
+ "Built with [squisq](https://github.com/bendyline/squisq) by [Bendyline](https://bendyline.com).",
4281
4454
  "",
4282
4455
  "## Explore DocBlocks",
4283
4456
  "",
@@ -5179,6 +5352,52 @@ function DocBlocksShell({
5179
5352
  promptForText,
5180
5353
  showToast
5181
5354
  ]);
5355
+ const handleNewFolder = useCallback14(async () => {
5356
+ if (!provider) return;
5357
+ const name = await promptForText({
5358
+ title: "New folder",
5359
+ label: "Folder name",
5360
+ initialValue: "Untitled folder",
5361
+ confirmLabel: "Create"
5362
+ });
5363
+ if (!name) return;
5364
+ const folderName = name.trim();
5365
+ if (!folderName || /[\\/]/.test(folderName)) {
5366
+ showToast("error", "Use a folder name without slashes.");
5367
+ return;
5368
+ }
5369
+ const path = `/${folderName}`;
5370
+ try {
5371
+ const providerV2 = getFileSystemProviderV25(provider);
5372
+ if (providerV2) {
5373
+ await providerV2.createDirectory(parseWorkspacePath5(path), { mode: "create" });
5374
+ } else {
5375
+ if (await providerEntryExists(provider, path)) {
5376
+ throw new Error("A file or folder with that name already exists.");
5377
+ }
5378
+ await provider.createDirectory(path);
5379
+ }
5380
+ } catch (error) {
5381
+ const message = error instanceof FsError5 && error.code === "already-exists" ? "A file or folder with that name already exists." : error instanceof Error ? error.message : "Could not create the folder.";
5382
+ showToast("error", message);
5383
+ return;
5384
+ }
5385
+ setSelectedFile(null);
5386
+ setSelectedFolder(path);
5387
+ setFolderEntries([]);
5388
+ setExplorerKey((key) => key + 1);
5389
+ closeWelcomeGateway();
5390
+ if (activeWorkspaceId) pushHash(activeWorkspaceId, null);
5391
+ if (effectiveCompact) setMobileShowEditor(true);
5392
+ }, [
5393
+ provider,
5394
+ promptForText,
5395
+ showToast,
5396
+ closeWelcomeGateway,
5397
+ activeWorkspaceId,
5398
+ pushHash,
5399
+ effectiveCompact
5400
+ ]);
5182
5401
  const actionNewHandledRef = useRef14(false);
5183
5402
  useEffect14(() => {
5184
5403
  if (!provider || actionNewHandledRef.current) return;
@@ -5867,10 +6086,11 @@ function DocBlocksShell({
5867
6086
  ),
5868
6087
  /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
5869
6088
  (!effectiveCompact || !mobileShowEditor) && /* @__PURE__ */ jsxs11(
5870
- "div",
6089
+ "aside",
5871
6090
  {
5872
6091
  ref: sidebarRef,
5873
6092
  className: "db-shell-sidebar",
6093
+ "aria-label": "Workspace and files",
5874
6094
  style: effectiveCompact ? void 0 : { width: `${sidebarWidth}px` },
5875
6095
  children: [
5876
6096
  /* @__PURE__ */ jsxs11("div", { className: "db-shell-sidebar-header", children: [
@@ -5890,7 +6110,9 @@ function DocBlocksShell({
5890
6110
  onKeepBrowserData: showBrowserStorageWarning && !browserStoragePersistent ? handleKeepBrowserData : void 0,
5891
6111
  onInstallApp: installPromptEvent ? handleInstallApp : void 0,
5892
6112
  getStorageEstimate: showBrowserStorageWarning ? getBrowserStorageEstimate : void 0,
5893
- storagePersistent: showBrowserStorageWarning ? browserStoragePersistent : void 0
6113
+ storagePersistent: showBrowserStorageWarning ? browserStoragePersistent : void 0,
6114
+ appVersion,
6115
+ appBuildDate
5894
6116
  }
5895
6117
  ),
5896
6118
  /* @__PURE__ */ jsx12(
@@ -5945,12 +6167,46 @@ function DocBlocksShell({
5945
6167
  },
5946
6168
  explorerKey
5947
6169
  ),
6170
+ isMobile && showWelcomeGateway && /* @__PURE__ */ jsxs11(
6171
+ "section",
6172
+ {
6173
+ className: "db-mobile-first-run",
6174
+ "aria-labelledby": "db-mobile-first-run-title",
6175
+ children: [
6176
+ /* @__PURE__ */ jsx12("p", { className: "db-mobile-first-run-eyebrow", children: "Local-first Markdown editor" }),
6177
+ /* @__PURE__ */ jsx12("h1", { id: "db-mobile-first-run-title", children: "Welcome to DocBlocks" }),
6178
+ /* @__PURE__ */ jsx12("p", { children: "Write visually, keep plain Markdown underneath, and export the same document in useful formats. Your browser workspace stays on this device." }),
6179
+ /* @__PURE__ */ jsxs11("div", { className: "db-mobile-first-run-actions", children: [
6180
+ /* @__PURE__ */ jsx12(
6181
+ "button",
6182
+ {
6183
+ type: "button",
6184
+ onClick: () => {
6185
+ closeWelcomeGateway();
6186
+ setMobileShowEditor(true);
6187
+ },
6188
+ children: "Tour the welcome document"
6189
+ }
6190
+ ),
6191
+ /* @__PURE__ */ jsx12(
6192
+ "button",
6193
+ {
6194
+ type: "button",
6195
+ className: "db-mobile-first-run-secondary",
6196
+ onClick: () => void handleNewFile(),
6197
+ children: "Create a document"
6198
+ }
6199
+ )
6200
+ ] })
6201
+ ]
6202
+ }
6203
+ ),
5948
6204
  /* @__PURE__ */ jsxs11("div", { className: "db-shell-sidebar-footer", children: [
5949
6205
  /* @__PURE__ */ jsx12("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
5950
6206
  /* @__PURE__ */ jsx12("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
5951
6207
  /* @__PURE__ */ jsx12("a", { href: "https://docblocks.com/terms/", target: "_blank", rel: "noopener noreferrer", children: "Terms" }),
5952
6208
  /* @__PURE__ */ jsx12("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
5953
- /* @__PURE__ */ jsx12("a", { href: issueReportUrl, target: "_blank", rel: "noopener noreferrer", children: "Issues" }),
6209
+ /* @__PURE__ */ jsx12("a", { href: issueReportUrl, target: "_blank", rel: "noopener noreferrer", children: "Report issue" }),
5954
6210
  showBrowserStorageWarning && /* @__PURE__ */ jsxs11(Fragment7, { children: [
5955
6211
  /* @__PURE__ */ jsx12("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
5956
6212
  /* @__PURE__ */ jsx12(
@@ -5981,6 +6237,7 @@ function DocBlocksShell({
5981
6237
  (!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs11(
5982
6238
  "main",
5983
6239
  {
6240
+ "aria-label": "Document editor",
5984
6241
  className: updateAvailable && onApplyUpdate && updateStatusBarVisible ? "db-shell-editor-area db-shell-editor-area--has-update" : "db-shell-editor-area",
5985
6242
  style: {
5986
6243
  flex: 1,
@@ -5994,7 +6251,7 @@ function DocBlocksShell({
5994
6251
  /* @__PURE__ */ jsx12(
5995
6252
  Suspense2,
5996
6253
  {
5997
- fallback: /* @__PURE__ */ jsx12("div", { className: "db-shell-empty", role: "status", children: "Loading editor\u2026" }),
6254
+ fallback: /* @__PURE__ */ jsx12("div", { className: "db-shell-empty", role: "status", children: "Loading your document\u2026" }),
5998
6255
  children: /* @__PURE__ */ jsx12(
5999
6256
  EditorShell,
6000
6257
  {
@@ -6047,6 +6304,7 @@ function DocBlocksShell({
6047
6304
  destinationAdapter: exportDestinationAdapter,
6048
6305
  colorScheme: resolvedTheme,
6049
6306
  videoExportPalette: DOCBLOCKS_VIDEO_EXPORT_PALETTE,
6307
+ ffmpegWasm,
6050
6308
  initialSharedMode
6051
6309
  }
6052
6310
  )
@@ -6056,11 +6314,12 @@ function DocBlocksShell({
6056
6314
  )
6057
6315
  }
6058
6316
  ),
6059
- showWelcomeGateway && /* @__PURE__ */ jsxs11("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
6317
+ showWelcomeGateway && !isMobile && /* @__PURE__ */ jsxs11("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
6060
6318
  /* @__PURE__ */ jsxs11("span", { className: "db-welcome-gateway-text", children: [
6061
6319
  "You\u2019re watching this welcome doc in ",
6062
6320
  /* @__PURE__ */ jsx12("strong", { children: "Slideshow" }),
6063
- " view -- it\u2019s a regular markdown file, and so is everything you\u2019ll write."
6321
+ " ",
6322
+ "view\u2014 it\u2019s a regular markdown file, and so is everything you\u2019ll write."
6064
6323
  ] }),
6065
6324
  /* @__PURE__ */ jsx12("button", { className: "db-welcome-gateway-cta", onClick: handleStartWriting, children: "Start writing" }),
6066
6325
  /* @__PURE__ */ jsx12(
@@ -6095,12 +6354,38 @@ function DocBlocksShell({
6095
6354
  },
6096
6355
  entry.path
6097
6356
  )) })
6098
- ] }) : /* @__PURE__ */ jsxs11("div", { className: "db-shell-empty", children: [
6357
+ ] }) : /* @__PURE__ */ jsxs11("div", { className: "db-shell-empty db-shell-empty--workspace", children: [
6099
6358
  effectiveCompact && /* @__PURE__ */ jsxs11("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
6100
6359
  /* @__PURE__ */ jsx12("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx12(FolderGlyph, {}) }),
6101
6360
  "Back to files"
6102
6361
  ] }),
6103
- /* @__PURE__ */ jsx12("p", { children: "Select a file to start editing, or create a new one." })
6362
+ /* @__PURE__ */ jsxs11("div", { className: "db-workspace-empty-content", children: [
6363
+ /* @__PURE__ */ jsx12("span", { className: "db-workspace-empty-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx12(FileGlyph, {}) }),
6364
+ /* @__PURE__ */ jsx12("h1", { children: activeWorkspaceDescriptor?.name ? `${activeWorkspaceDescriptor.name} is ready` : "Your workspace is ready" }),
6365
+ /* @__PURE__ */ jsx12("p", { children: "Create your first Markdown document, or open a folder you already use." }),
6366
+ /* @__PURE__ */ jsxs11("div", { className: "db-workspace-empty-actions", children: [
6367
+ /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => void handleNewFile(), children: "Create your first document" }),
6368
+ /* @__PURE__ */ jsx12(
6369
+ "button",
6370
+ {
6371
+ type: "button",
6372
+ className: "db-workspace-empty-secondary",
6373
+ onClick: () => void handleNewFolder(),
6374
+ children: "New folder"
6375
+ }
6376
+ ),
6377
+ (isElectronHost2() || typeof globalThis.showDirectoryPicker === "function") && /* @__PURE__ */ jsx12(
6378
+ "button",
6379
+ {
6380
+ type: "button",
6381
+ className: "db-workspace-empty-secondary",
6382
+ onClick: () => void handleOpenFolder(),
6383
+ children: "Open a folder"
6384
+ }
6385
+ )
6386
+ ] }),
6387
+ /* @__PURE__ */ jsx12("p", { className: "db-workspace-empty-hint", children: "Browser workspaces stay on this device. Use the sidebar backup action to keep a portable copy." })
6388
+ ] })
6104
6389
  ] }),
6105
6390
  /* @__PURE__ */ jsx12(
6106
6391
  UpdateAvailableNotice,
@@ -6119,34 +6404,6 @@ function DocBlocksShell({
6119
6404
  }
6120
6405
  );
6121
6406
  }
6122
-
6123
- // src/Export/export-destination.ts
6124
- function updateExportTargetExtension(targetPath, suggestedFilename) {
6125
- const nextExtension = extensionOf(suggestedFilename);
6126
- if (!nextExtension) return targetPath;
6127
- if (!targetPath.trim()) return suggestedFilename;
6128
- const suffixIndex = firstSuffixIndex(targetPath);
6129
- const pathPart = suffixIndex === -1 ? targetPath : targetPath.slice(0, suffixIndex);
6130
- const suffix = suffixIndex === -1 ? "" : targetPath.slice(suffixIndex);
6131
- const separatorIndex = Math.max(pathPart.lastIndexOf("/"), pathPart.lastIndexOf("\\"));
6132
- const dotIndex = pathPart.lastIndexOf(".");
6133
- const stem = dotIndex > separatorIndex ? pathPart.slice(0, dotIndex) : pathPart;
6134
- return `${stem}${nextExtension}${suffix}`;
6135
- }
6136
- function extensionOf(value) {
6137
- const suffixIndex = firstSuffixIndex(value);
6138
- const pathPart = suffixIndex === -1 ? value : value.slice(0, suffixIndex);
6139
- const separatorIndex = Math.max(pathPart.lastIndexOf("/"), pathPart.lastIndexOf("\\"));
6140
- const dotIndex = pathPart.lastIndexOf(".");
6141
- return dotIndex > separatorIndex ? pathPart.slice(dotIndex) : "";
6142
- }
6143
- function firstSuffixIndex(value) {
6144
- const query = value.indexOf("?");
6145
- const fragment = value.indexOf("#");
6146
- if (query === -1) return fragment;
6147
- if (fragment === -1) return query;
6148
- return Math.min(query, fragment);
6149
- }
6150
6407
  export {
6151
6408
  AccentColorSettings,
6152
6409
  AppMenu,
@@ -6167,4 +6424,3 @@ export {
6167
6424
  useDocumentSession,
6168
6425
  useFileTree
6169
6426
  };
6170
- //# sourceMappingURL=index.js.map