@bendyline/docblocks-react 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +12 -3
  2. package/dist/ExportDialog-VYATD5UC.js +9 -0
  3. package/dist/{ExportToolbarControls-GHAOJMH6.js → ExportToolbarControls-5HUE3LSX.js} +69 -40
  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-EF2BWRXU.js → LazyEditorShell-RF54B5UD.js} +0 -1
  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 +346 -97
  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 +448 -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.map +0 -1
  33. package/dist/ShareDialog-63ENBEDZ.js.map +0 -1
  34. package/dist/chunk-4XXSWN3T.js.map +0 -1
  35. package/dist/chunk-57ZJJOC5.js.map +0 -1
  36. package/dist/chunk-5A4KEWKT.js.map +0 -1
  37. package/dist/chunk-DD4VFJQI.js.map +0 -1
  38. package/dist/chunk-FEWQUQML.js.map +0 -1
  39. package/dist/chunk-FMGTHSTX.js.map +0 -1
  40. package/dist/chunk-UNYZQXWR.js.map +0 -1
  41. package/dist/chunk-VHDP4IPL.js.map +0 -1
  42. package/dist/chunk-ZI56BNB7.js.map +0 -1
  43. package/dist/editor.js.map +0 -1
  44. package/dist/export/index.js.map +0 -1
  45. package/dist/index.js.map +0 -1
  46. package/dist/run-export-ICTZITXC.js +0 -10
  47. package/dist/run-export-ICTZITXC.js.map +0 -1
  48. 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";
@@ -1291,11 +1294,11 @@ function FileExplorer({
1291
1294
  disabled: newItemCreationPending,
1292
1295
  onClick: () => {
1293
1296
  setNewItemError(null);
1294
- setNewItemType("file");
1297
+ setNewItemType("directory");
1295
1298
  },
1296
- title: "New File",
1297
- "aria-label": "New File",
1298
- children: /* @__PURE__ */ jsx3(NewFileIcon, {})
1299
+ title: "New Folder",
1300
+ "aria-label": "New Folder",
1301
+ children: /* @__PURE__ */ jsx3(NewFolderIcon, {})
1299
1302
  }
1300
1303
  ),
1301
1304
  /* @__PURE__ */ jsx3(
@@ -1305,11 +1308,11 @@ function FileExplorer({
1305
1308
  disabled: newItemCreationPending,
1306
1309
  onClick: () => {
1307
1310
  setNewItemError(null);
1308
- setNewItemType("directory");
1311
+ setNewItemType("file");
1309
1312
  },
1310
- title: "New Folder",
1311
- "aria-label": "New Folder",
1312
- children: /* @__PURE__ */ jsx3(NewFolderIcon, {})
1313
+ title: "New File",
1314
+ "aria-label": "New File",
1315
+ children: /* @__PURE__ */ jsx3(NewFileIcon, {})
1313
1316
  }
1314
1317
  )
1315
1318
  ] })
@@ -1469,12 +1472,18 @@ function WorkspacePicker({
1469
1472
  }) {
1470
1473
  const [workspaces, setWorkspaces] = useState4([]);
1471
1474
  const [isOpen, setIsOpen] = useState4(false);
1475
+ const [creatingNew, setCreatingNew] = useState4(false);
1476
+ const [newWorkspaceName, setNewWorkspaceName] = useState4("");
1477
+ const [newWorkspaceError, setNewWorkspaceError] = useState4(null);
1478
+ const [newWorkspacePending, setNewWorkspacePending] = useState4(false);
1472
1479
  const pickerRef = useRef4(null);
1473
1480
  useEffect4(() => {
1474
1481
  if (!isOpen) return;
1475
1482
  function handleOutsideClick(e) {
1476
1483
  if (pickerRef.current && !pickerRef.current.contains(e.target)) {
1477
1484
  setIsOpen(false);
1485
+ setCreatingNew(false);
1486
+ setNewWorkspaceError(null);
1478
1487
  }
1479
1488
  }
1480
1489
  document.addEventListener("mousedown", handleOutsideClick);
@@ -1497,8 +1506,34 @@ function WorkspacePicker({
1497
1506
  },
1498
1507
  [onSelect]
1499
1508
  );
1509
+ const handleStartCreateNew = useCallback4(() => {
1510
+ const existingNames = new Set(workspaces.map((workspace) => workspace.name.toLowerCase()));
1511
+ let suffix = workspaces.length + 1;
1512
+ while (existingNames.has(`workspace ${suffix}`.toLowerCase())) suffix += 1;
1513
+ setNewWorkspaceName(`Workspace ${suffix}`);
1514
+ setNewWorkspaceError(null);
1515
+ setCreatingNew(true);
1516
+ }, [workspaces]);
1517
+ const handleCancelCreateNew = useCallback4(() => {
1518
+ setCreatingNew(false);
1519
+ setNewWorkspaceName("");
1520
+ setNewWorkspaceError(null);
1521
+ }, []);
1500
1522
  const handleCreateNew = useCallback4(async () => {
1501
- const name = `Workspace ${workspaces.length + 1}`;
1523
+ if (newWorkspacePending) return;
1524
+ const name = newWorkspaceName.trim();
1525
+ if (!name) {
1526
+ setNewWorkspaceError("Enter a workspace name.");
1527
+ return;
1528
+ }
1529
+ if (name.length > 80) {
1530
+ setNewWorkspaceError("Workspace names must be 80 characters or fewer.");
1531
+ return;
1532
+ }
1533
+ if (workspaces.some((workspace) => workspace.name.toLowerCase() === name.toLowerCase())) {
1534
+ setNewWorkspaceError("A workspace with that name already exists.");
1535
+ return;
1536
+ }
1502
1537
  const id = `ws-${Date.now()}`;
1503
1538
  const descriptor = {
1504
1539
  id,
@@ -1506,11 +1541,21 @@ function WorkspacePicker({
1506
1541
  type: "indexeddb",
1507
1542
  lastOpened: (/* @__PURE__ */ new Date()).toISOString()
1508
1543
  };
1509
- await saveWorkspace(descriptor);
1510
- await refresh();
1511
- onSelect(descriptor);
1512
- setIsOpen(false);
1513
- }, [workspaces.length, onSelect, refresh]);
1544
+ setNewWorkspacePending(true);
1545
+ setNewWorkspaceError(null);
1546
+ try {
1547
+ await saveWorkspace(descriptor);
1548
+ await refresh();
1549
+ onSelect(descriptor);
1550
+ setCreatingNew(false);
1551
+ setNewWorkspaceName("");
1552
+ setIsOpen(false);
1553
+ } catch {
1554
+ setNewWorkspaceError("The workspace could not be created. Try again.");
1555
+ } finally {
1556
+ setNewWorkspacePending(false);
1557
+ }
1558
+ }, [newWorkspaceName, newWorkspacePending, onSelect, refresh, workspaces]);
1514
1559
  const activeWs = workspaces.find((w) => w.id === activeWorkspaceId);
1515
1560
  const activeWorkspaceName = activeWs?.name ?? "No workspace";
1516
1561
  return /* @__PURE__ */ jsxs4("div", { ref: pickerRef, className: `db-workspace-picker ${className ?? ""}`, children: [
@@ -1518,7 +1563,11 @@ function WorkspacePicker({
1518
1563
  "button",
1519
1564
  {
1520
1565
  className: "db-workspace-picker-btn",
1521
- onClick: () => setIsOpen(!isOpen),
1566
+ onClick: () => {
1567
+ const nextOpen = !isOpen;
1568
+ setIsOpen(nextOpen);
1569
+ if (!nextOpen) handleCancelCreateNew();
1570
+ },
1522
1571
  title: "Switch workspace",
1523
1572
  "aria-label": `Switch workspace, current: ${activeWorkspaceName}`,
1524
1573
  children: [
@@ -1551,10 +1600,57 @@ function WorkspacePicker({
1551
1600
  ws.id
1552
1601
  )),
1553
1602
  /* @__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: [
1603
+ !electron && (creatingNew ? /* @__PURE__ */ jsxs4(
1604
+ "form",
1605
+ {
1606
+ className: "db-workspace-create",
1607
+ "aria-busy": newWorkspacePending,
1608
+ onSubmit: (event) => {
1609
+ event.preventDefault();
1610
+ void handleCreateNew();
1611
+ },
1612
+ children: [
1613
+ /* @__PURE__ */ jsx4("label", { className: "db-workspace-create-label", htmlFor: "db-new-workspace-name", children: "Workspace name" }),
1614
+ /* @__PURE__ */ jsx4(
1615
+ "input",
1616
+ {
1617
+ id: "db-new-workspace-name",
1618
+ className: "db-workspace-create-input",
1619
+ value: newWorkspaceName,
1620
+ maxLength: 80,
1621
+ disabled: newWorkspacePending,
1622
+ "aria-invalid": newWorkspaceError !== null,
1623
+ "aria-describedby": newWorkspaceError ? "db-new-workspace-error" : void 0,
1624
+ autoFocus: true,
1625
+ onChange: (event) => {
1626
+ setNewWorkspaceName(event.target.value);
1627
+ setNewWorkspaceError(null);
1628
+ },
1629
+ onKeyDown: (event) => {
1630
+ if (event.key === "Escape") handleCancelCreateNew();
1631
+ }
1632
+ }
1633
+ ),
1634
+ newWorkspaceError && /* @__PURE__ */ jsx4("p", { id: "db-new-workspace-error", className: "db-workspace-create-error", role: "alert", children: newWorkspaceError }),
1635
+ /* @__PURE__ */ jsxs4("div", { className: "db-workspace-create-actions", children: [
1636
+ /* @__PURE__ */ jsx4(
1637
+ "button",
1638
+ {
1639
+ type: "button",
1640
+ className: "db-workspace-create-cancel",
1641
+ disabled: newWorkspacePending,
1642
+ onClick: handleCancelCreateNew,
1643
+ children: "Cancel"
1644
+ }
1645
+ ),
1646
+ /* @__PURE__ */ jsx4("button", { type: "submit", disabled: newWorkspacePending, children: newWorkspacePending ? "Creating\xE2\u20AC\xA6" : "Create" })
1647
+ ] })
1648
+ ]
1649
+ }
1650
+ ) : /* @__PURE__ */ jsx4("button", { className: "db-workspace-dropdown-item", onClick: handleStartCreateNew, children: /* @__PURE__ */ jsxs4("span", { className: "db-workspace-dropdown-action-label", children: [
1555
1651
  /* @__PURE__ */ jsx4(NewFolderIcon, {}),
1556
1652
  /* @__PURE__ */ jsx4("span", { children: "New Workspace" })
1557
- ] }) }),
1653
+ ] }) })),
1558
1654
  (electron || isNativeFileSystemSupported()) && /* @__PURE__ */ jsx4(
1559
1655
  "button",
1560
1656
  {
@@ -1583,7 +1679,6 @@ function WorkspacePicker({
1583
1679
 
1584
1680
  // src/DocBlocksShell/DocBlocksShell.tsx
1585
1681
  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
1682
  import { MediaContext } from "@bendyline/squisq-react";
1588
1683
  import {
1589
1684
  DocumentVersionManager
@@ -1648,7 +1743,9 @@ function AppMenu({
1648
1743
  onKeepBrowserData,
1649
1744
  onInstallApp,
1650
1745
  getStorageEstimate,
1651
- storagePersistent
1746
+ storagePersistent,
1747
+ appVersion,
1748
+ appBuildDate
1652
1749
  }) {
1653
1750
  const [isOpen, setIsOpen] = useState5(false);
1654
1751
  const [showAbout, setShowAbout] = useState5(false);
@@ -1745,8 +1842,8 @@ function AppMenu({
1745
1842
  {
1746
1843
  className: "db-app-menu-item",
1747
1844
  role: "menuitem",
1748
- onClick: () => handleAction(() => void onKeepBrowserData()),
1749
- children: "Keep data in browser for longer"
1845
+ onClick: () => handleAction(() => setShowSettings(true)),
1846
+ children: "Storage protection\u2026"
1750
1847
  }
1751
1848
  ),
1752
1849
  onDownloadAllWorkspaces && /* @__PURE__ */ jsx5(
@@ -1799,7 +1896,7 @@ function AppMenu({
1799
1896
  )} of the ${formatBytes(
1800
1897
  storageEstimate.quota
1801
1898
  )} 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." }),
1899
+ 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
1900
  onKeepBrowserData && /* @__PURE__ */ jsx5(
1804
1901
  "button",
1805
1902
  {
@@ -1807,7 +1904,7 @@ function AppMenu({
1807
1904
  className: "db-settings-action",
1808
1905
  disabled: requestingPersistentStorage,
1809
1906
  onClick: () => void handleKeepBrowserDataFromSettings(),
1810
- children: requestingPersistentStorage ? "Requesting persistent storage\u2026" : "Keep data in browser for longer"
1907
+ children: requestingPersistentStorage ? "Requesting storage protection\u2026" : "Protect data from browser cleanup"
1811
1908
  }
1812
1909
  )
1813
1910
  ] }),
@@ -1863,7 +1960,15 @@ function AppMenu({
1863
1960
  showAbout && /* @__PURE__ */ jsxs5(Dialog, { title: "About DocBlocks", onClose: () => setShowAbout(false), children: [
1864
1961
  /* @__PURE__ */ jsxs5("p", { children: [
1865
1962
  /* @__PURE__ */ jsx5("strong", { children: "DocBlocks" }),
1866
- " is a markdown document editor that runs entirely in your browser. Your files stay on your device."
1963
+ " is a local-first Markdown document editor. Your files stay under your control."
1964
+ ] }),
1965
+ appVersion && /* @__PURE__ */ jsxs5("p", { className: "db-about-version", children: [
1966
+ /* @__PURE__ */ jsx5("span", { children: "Version" }),
1967
+ /* @__PURE__ */ jsx5("code", { "aria-label": `DocBlocks version ${appVersion}`, children: appVersion })
1968
+ ] }),
1969
+ appBuildDate && /* @__PURE__ */ jsxs5("p", { className: "db-about-version", children: [
1970
+ /* @__PURE__ */ jsx5("span", { children: "Build" }),
1971
+ /* @__PURE__ */ jsx5("time", { dateTime: appBuildDate, children: appBuildDate })
1867
1972
  ] }),
1868
1973
  /* @__PURE__ */ jsxs5("p", { children: [
1869
1974
  "Built with",
@@ -1886,6 +1991,26 @@ function AppMenu({
1886
1991
  }
1887
1992
  ),
1888
1993
  /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
1994
+ /* @__PURE__ */ jsx5(
1995
+ "a",
1996
+ {
1997
+ href: "https://github.com/bendyline/docblocks/releases",
1998
+ target: "_blank",
1999
+ rel: "noopener noreferrer",
2000
+ children: "Release notes"
2001
+ }
2002
+ ),
2003
+ /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
2004
+ /* @__PURE__ */ jsx5(
2005
+ "a",
2006
+ {
2007
+ href: "https://github.com/bendyline/docblocks/issues",
2008
+ target: "_blank",
2009
+ rel: "noopener noreferrer",
2010
+ children: "Support"
2011
+ }
2012
+ ),
2013
+ /* @__PURE__ */ jsx5("span", { className: "db-dialog-sep", children: "\xB7" }),
1889
2014
  /* @__PURE__ */ jsx5(
1890
2015
  "a",
1891
2016
  {
@@ -2129,7 +2254,7 @@ function useDocumentSession(autoSaveDelayMs = 500) {
2129
2254
  import { lazy, Suspense } from "react";
2130
2255
  import { jsx as jsx8 } from "react/jsx-runtime";
2131
2256
  var ExportToolbarControlsImplementation = lazy(
2132
- () => import("./ExportToolbarControls-GHAOJMH6.js").then((module) => ({
2257
+ () => import("./ExportToolbarControls-5HUE3LSX.js").then((module) => ({
2133
2258
  default: module.ExportToolbarControls
2134
2259
  }))
2135
2260
  );
@@ -2514,17 +2639,45 @@ function retainFileSystemProvider(provider) {
2514
2639
  // src/DocBlocksShell/document-title.ts
2515
2640
  import { useEffect as useEffect9 } from "react";
2516
2641
  var APP_TITLE = "DocBlocks";
2517
- function titleForSelectedFile(selectedFile, homeDocumentTitle = APP_TITLE, homeDocumentPath) {
2518
- if (!selectedFile || selectedFile === homeDocumentPath) return homeDocumentTitle;
2642
+ var INSTALLED_DISPLAY_QUERIES = [
2643
+ "(display-mode: window-controls-overlay)",
2644
+ "(display-mode: standalone)"
2645
+ ];
2646
+ function unbrandedHomeTitle(homeDocumentTitle) {
2647
+ if (homeDocumentTitle === APP_TITLE) return "";
2648
+ for (const separator of [" \u2014 ", " - ", ": ", " | "]) {
2649
+ const prefix = `${APP_TITLE}${separator}`;
2650
+ if (homeDocumentTitle.startsWith(prefix)) return homeDocumentTitle.slice(prefix.length);
2651
+ }
2652
+ return homeDocumentTitle;
2653
+ }
2654
+ function titleForSelectedFile(selectedFile, homeDocumentTitle = APP_TITLE, homeDocumentPath, includeAppName = true) {
2655
+ if (!selectedFile || selectedFile === homeDocumentPath) {
2656
+ return includeAppName ? homeDocumentTitle : unbrandedHomeTitle(homeDocumentTitle);
2657
+ }
2519
2658
  const filename = selectedFile.replace(/^\/+/, "").split("/").pop();
2520
2659
  const name = filename?.replace(/\.[^.]+$/, "");
2521
- return name ? `${name} - ${APP_TITLE}` : APP_TITLE;
2660
+ if (!name) return includeAppName ? APP_TITLE : "";
2661
+ return includeAppName ? `${name} - ${APP_TITLE}` : name;
2522
2662
  }
2523
2663
  function useDocumentTitle(selectedFile, homeDocumentTitle, homeDocumentPath) {
2524
2664
  useEffect9(() => {
2525
- if (typeof document !== "undefined") {
2526
- document.title = titleForSelectedFile(selectedFile, homeDocumentTitle, homeDocumentPath);
2527
- }
2665
+ if (typeof document === "undefined") return;
2666
+ const displayQueries = typeof globalThis.matchMedia === "function" ? INSTALLED_DISPLAY_QUERIES.map((query) => globalThis.matchMedia(query)) : [];
2667
+ const updateTitle = () => {
2668
+ const includeAppName = !displayQueries.some((query) => query.matches);
2669
+ document.title = titleForSelectedFile(
2670
+ selectedFile,
2671
+ homeDocumentTitle,
2672
+ homeDocumentPath,
2673
+ includeAppName
2674
+ );
2675
+ };
2676
+ updateTitle();
2677
+ for (const query of displayQueries) query.addEventListener("change", updateTitle);
2678
+ return () => {
2679
+ for (const query of displayQueries) query.removeEventListener("change", updateTitle);
2680
+ };
2528
2681
  }, [homeDocumentPath, homeDocumentTitle, selectedFile]);
2529
2682
  }
2530
2683
 
@@ -3055,7 +3208,7 @@ function UpdateAvailableNotice({
3055
3208
  blocked = false,
3056
3209
  statusBarVisible
3057
3210
  }) {
3058
- const [promptOpen, setPromptOpen] = useState13(false);
3211
+ const [promptOpen, setPromptOpen] = useState13(true);
3059
3212
  if (!available || !onApplyUpdate) return null;
3060
3213
  const promptVisible = promptOpen && !blocked;
3061
3214
  return /* @__PURE__ */ jsxs10(Fragment6, { children: [
@@ -3074,9 +3227,9 @@ function UpdateAvailableNotice({
3074
3227
  }
3075
3228
  ),
3076
3229
  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." }),
3230
+ /* @__PURE__ */ jsx11("span", { children: "A new version of DocBlocks is available. Reload to update the editor and site pages." }),
3078
3231
  /* @__PURE__ */ jsxs10("div", { className: "db-update-banner-actions", children: [
3079
- /* @__PURE__ */ jsx11("button", { type: "button", onClick: onApplyUpdate, children: "Reload" }),
3232
+ /* @__PURE__ */ jsx11("button", { type: "button", onClick: onApplyUpdate, disabled: blocked, children: "Reload" }),
3080
3233
  /* @__PURE__ */ jsx11("button", { type: "button", onClick: () => setPromptOpen(false), children: "Later" })
3081
3234
  ] })
3082
3235
  ] })
@@ -3392,14 +3545,19 @@ async function copyTransientWorkspaceContents(source, destination) {
3392
3545
 
3393
3546
  // src/DocBlocksShell/DocBlocksShell.tsx
3394
3547
  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 })));
3548
+ var editorShellModulePromise = null;
3549
+ function loadEditorShell() {
3550
+ editorShellModulePromise ?? (editorShellModulePromise = (async () => {
3551
+ const workersReady = globalThis.docBlocksMonacoWorkersReady;
3552
+ await workersReady?.catch(() => void 0);
3553
+ return import("./LazyEditorShell-RF54B5UD.js");
3554
+ })());
3555
+ return editorShellModulePromise;
3556
+ }
3557
+ var EditorShell = lazy2(loadEditorShell);
3558
+ var GitUI = lazy2(() => import("./GitUI-GMH3PAYL.js").then((m) => ({ default: m.GitUI })));
3401
3559
  var GitToolbarControl = lazy2(
3402
- () => import("./GitToolbarControl-GNMRVKWQ.js").then((m) => ({ default: m.GitToolbarControl }))
3560
+ () => import("./GitToolbarControl-FW3BZFXV.js").then((m) => ({ default: m.GitToolbarControl }))
3403
3561
  );
3404
3562
  var DOCBLOCKS_VIDEO_EXPORT_PALETTE = Object.freeze({
3405
3563
  overlay: "rgba(0, 0, 0, 0.72)",
@@ -3679,6 +3837,8 @@ function DocBlocksShell({
3679
3837
  theme: hostTheme = "auto",
3680
3838
  logoUrl,
3681
3839
  issueReportVersion,
3840
+ appBuildDate,
3841
+ ffmpegWasm,
3682
3842
  homeDocumentTitle,
3683
3843
  homeDocumentPath,
3684
3844
  allowVersioning = true,
@@ -3723,14 +3883,20 @@ function DocBlocksShell({
3723
3883
  saveViewPreferences(prefs);
3724
3884
  }, []);
3725
3885
  const isMobile = useIsMobile();
3726
- const [mobileShowEditor, setMobileShowEditor] = useState14(false);
3886
+ const [mobileShowEditor, setMobileShowEditor] = useState14(
3887
+ () => isMobile && isWelcomeGatewayDismissed()
3888
+ );
3889
+ useEffect14(() => {
3890
+ if (isMobile) void loadEditorShell();
3891
+ }, [isMobile]);
3727
3892
  const [sidebarWidth, setSidebarWidth] = useState14(loadSidebarWidth);
3728
3893
  const [compactLayout, setCompactLayout] = useState14(false);
3729
3894
  const effectiveCompact = isMobile || compactLayout;
3730
3895
  const showBrowserStorageWarning = !isElectronHost2();
3896
+ const appVersion = isElectronHost2() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web";
3731
3897
  const issueReportUrl = buildIssueReportUrl({
3732
3898
  reportedAt: /* @__PURE__ */ new Date(),
3733
- version: isElectronHost2() ? `${getDocBlocksHost().env.appVersion} desktop` : issueReportVersion ?? "web",
3899
+ version: appVersion,
3734
3900
  userAgent: typeof navigator === "undefined" ? "" : navigator.userAgent
3735
3901
  });
3736
3902
  const [browserStoragePersistent, setBrowserStoragePersistent] = useState14(false);
@@ -4259,25 +4425,25 @@ function DocBlocksShell({
4259
4425
  "",
4260
4426
  "Your markdown can do anything.",
4261
4427
  "",
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.",
4428
+ "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
4429
  "",
4264
4430
  "Simple to write. Beautiful wherever it goes.",
4265
4431
  "",
4266
4432
  "## Features",
4267
4433
  "",
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",
4434
+ "- **Rich Markdown editing** \u2014 Write visually or switch to raw Markdown source at any time. Section annotations can change how individual blocks are presented.",
4435
+ "- **Workspaces** \u2014 Organize documents into separate browser workspaces or folders on your device.",
4436
+ "- **Useful everywhere** \u2014 Export Word, PowerPoint, PDF, HTML, and Markdown in the app, with EPUB and additional formats available through the CLI.",
4437
+ "- **Playback and video** \u2014 Preview a document as a rich presentation and export it as an MP4 video.",
4438
+ "- **Private by default** \u2014 No ads, accounts, or tracking. Your browser workspace stays on this device.",
4273
4439
  "",
4274
4440
  "## Getting Started",
4275
4441
  "",
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",
4442
+ "1. Create a document with the **New file** button in the file pane.",
4443
+ "2. Start writing in Markdown \u2014 the editor supports headings, lists, links, images, and more.",
4444
+ "3. Your work is saved automatically.",
4279
4445
  "",
4280
- "Built on [Squiggly Square markdown extensions](https://github.com/bendyline/squisq) by [Bendyline](https://bendyline.com).",
4446
+ "Built with [squisq](https://github.com/bendyline/squisq) by [Bendyline](https://bendyline.com).",
4281
4447
  "",
4282
4448
  "## Explore DocBlocks",
4283
4449
  "",
@@ -5179,6 +5345,52 @@ function DocBlocksShell({
5179
5345
  promptForText,
5180
5346
  showToast
5181
5347
  ]);
5348
+ const handleNewFolder = useCallback14(async () => {
5349
+ if (!provider) return;
5350
+ const name = await promptForText({
5351
+ title: "New folder",
5352
+ label: "Folder name",
5353
+ initialValue: "Untitled folder",
5354
+ confirmLabel: "Create"
5355
+ });
5356
+ if (!name) return;
5357
+ const folderName = name.trim();
5358
+ if (!folderName || /[\\/]/.test(folderName)) {
5359
+ showToast("error", "Use a folder name without slashes.");
5360
+ return;
5361
+ }
5362
+ const path = `/${folderName}`;
5363
+ try {
5364
+ const providerV2 = getFileSystemProviderV25(provider);
5365
+ if (providerV2) {
5366
+ await providerV2.createDirectory(parseWorkspacePath5(path), { mode: "create" });
5367
+ } else {
5368
+ if (await providerEntryExists(provider, path)) {
5369
+ throw new Error("A file or folder with that name already exists.");
5370
+ }
5371
+ await provider.createDirectory(path);
5372
+ }
5373
+ } catch (error) {
5374
+ 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.";
5375
+ showToast("error", message);
5376
+ return;
5377
+ }
5378
+ setSelectedFile(null);
5379
+ setSelectedFolder(path);
5380
+ setFolderEntries([]);
5381
+ setExplorerKey((key) => key + 1);
5382
+ closeWelcomeGateway();
5383
+ if (activeWorkspaceId) pushHash(activeWorkspaceId, null);
5384
+ if (effectiveCompact) setMobileShowEditor(true);
5385
+ }, [
5386
+ provider,
5387
+ promptForText,
5388
+ showToast,
5389
+ closeWelcomeGateway,
5390
+ activeWorkspaceId,
5391
+ pushHash,
5392
+ effectiveCompact
5393
+ ]);
5182
5394
  const actionNewHandledRef = useRef14(false);
5183
5395
  useEffect14(() => {
5184
5396
  if (!provider || actionNewHandledRef.current) return;
@@ -5867,10 +6079,11 @@ function DocBlocksShell({
5867
6079
  ),
5868
6080
  /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flex: 1, overflow: "hidden" }, children: [
5869
6081
  (!effectiveCompact || !mobileShowEditor) && /* @__PURE__ */ jsxs11(
5870
- "div",
6082
+ "aside",
5871
6083
  {
5872
6084
  ref: sidebarRef,
5873
6085
  className: "db-shell-sidebar",
6086
+ "aria-label": "Workspace and files",
5874
6087
  style: effectiveCompact ? void 0 : { width: `${sidebarWidth}px` },
5875
6088
  children: [
5876
6089
  /* @__PURE__ */ jsxs11("div", { className: "db-shell-sidebar-header", children: [
@@ -5890,7 +6103,9 @@ function DocBlocksShell({
5890
6103
  onKeepBrowserData: showBrowserStorageWarning && !browserStoragePersistent ? handleKeepBrowserData : void 0,
5891
6104
  onInstallApp: installPromptEvent ? handleInstallApp : void 0,
5892
6105
  getStorageEstimate: showBrowserStorageWarning ? getBrowserStorageEstimate : void 0,
5893
- storagePersistent: showBrowserStorageWarning ? browserStoragePersistent : void 0
6106
+ storagePersistent: showBrowserStorageWarning ? browserStoragePersistent : void 0,
6107
+ appVersion,
6108
+ appBuildDate
5894
6109
  }
5895
6110
  ),
5896
6111
  /* @__PURE__ */ jsx12(
@@ -5945,6 +6160,40 @@ function DocBlocksShell({
5945
6160
  },
5946
6161
  explorerKey
5947
6162
  ),
6163
+ isMobile && showWelcomeGateway && /* @__PURE__ */ jsxs11(
6164
+ "section",
6165
+ {
6166
+ className: "db-mobile-first-run",
6167
+ "aria-labelledby": "db-mobile-first-run-title",
6168
+ children: [
6169
+ /* @__PURE__ */ jsx12("p", { className: "db-mobile-first-run-eyebrow", children: "Local-first Markdown editor" }),
6170
+ /* @__PURE__ */ jsx12("h1", { id: "db-mobile-first-run-title", children: "Welcome to DocBlocks" }),
6171
+ /* @__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." }),
6172
+ /* @__PURE__ */ jsxs11("div", { className: "db-mobile-first-run-actions", children: [
6173
+ /* @__PURE__ */ jsx12(
6174
+ "button",
6175
+ {
6176
+ type: "button",
6177
+ onClick: () => {
6178
+ closeWelcomeGateway();
6179
+ setMobileShowEditor(true);
6180
+ },
6181
+ children: "Tour the welcome document"
6182
+ }
6183
+ ),
6184
+ /* @__PURE__ */ jsx12(
6185
+ "button",
6186
+ {
6187
+ type: "button",
6188
+ className: "db-mobile-first-run-secondary",
6189
+ onClick: () => void handleNewFile(),
6190
+ children: "Create a document"
6191
+ }
6192
+ )
6193
+ ] })
6194
+ ]
6195
+ }
6196
+ ),
5948
6197
  /* @__PURE__ */ jsxs11("div", { className: "db-shell-sidebar-footer", children: [
5949
6198
  /* @__PURE__ */ jsx12("a", { href: "https://docblocks.com/docs/", target: "_blank", rel: "noopener noreferrer", children: "Docs" }),
5950
6199
  /* @__PURE__ */ jsx12("span", { className: "db-shell-sidebar-footer-separator", "aria-hidden": "true", children: "\u2022" }),
@@ -5981,6 +6230,7 @@ function DocBlocksShell({
5981
6230
  (!effectiveCompact || mobileShowEditor) && /* @__PURE__ */ jsxs11(
5982
6231
  "main",
5983
6232
  {
6233
+ "aria-label": "Document editor",
5984
6234
  className: updateAvailable && onApplyUpdate && updateStatusBarVisible ? "db-shell-editor-area db-shell-editor-area--has-update" : "db-shell-editor-area",
5985
6235
  style: {
5986
6236
  flex: 1,
@@ -5994,7 +6244,7 @@ function DocBlocksShell({
5994
6244
  /* @__PURE__ */ jsx12(
5995
6245
  Suspense2,
5996
6246
  {
5997
- fallback: /* @__PURE__ */ jsx12("div", { className: "db-shell-empty", role: "status", children: "Loading editor\u2026" }),
6247
+ fallback: /* @__PURE__ */ jsx12("div", { className: "db-shell-empty", role: "status", children: "Loading your document\u2026" }),
5998
6248
  children: /* @__PURE__ */ jsx12(
5999
6249
  EditorShell,
6000
6250
  {
@@ -6047,6 +6297,7 @@ function DocBlocksShell({
6047
6297
  destinationAdapter: exportDestinationAdapter,
6048
6298
  colorScheme: resolvedTheme,
6049
6299
  videoExportPalette: DOCBLOCKS_VIDEO_EXPORT_PALETTE,
6300
+ ffmpegWasm,
6050
6301
  initialSharedMode
6051
6302
  }
6052
6303
  )
@@ -6056,11 +6307,12 @@ function DocBlocksShell({
6056
6307
  )
6057
6308
  }
6058
6309
  ),
6059
- showWelcomeGateway && /* @__PURE__ */ jsxs11("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
6310
+ showWelcomeGateway && !isMobile && /* @__PURE__ */ jsxs11("div", { className: "db-welcome-gateway", role: "note", "aria-label": "Welcome tip", children: [
6060
6311
  /* @__PURE__ */ jsxs11("span", { className: "db-welcome-gateway-text", children: [
6061
6312
  "You\u2019re watching this welcome doc in ",
6062
6313
  /* @__PURE__ */ jsx12("strong", { children: "Slideshow" }),
6063
- " view -- it\u2019s a regular markdown file, and so is everything you\u2019ll write."
6314
+ " ",
6315
+ "view\u2014 it\u2019s a regular markdown file, and so is everything you\u2019ll write."
6064
6316
  ] }),
6065
6317
  /* @__PURE__ */ jsx12("button", { className: "db-welcome-gateway-cta", onClick: handleStartWriting, children: "Start writing" }),
6066
6318
  /* @__PURE__ */ jsx12(
@@ -6095,12 +6347,38 @@ function DocBlocksShell({
6095
6347
  },
6096
6348
  entry.path
6097
6349
  )) })
6098
- ] }) : /* @__PURE__ */ jsxs11("div", { className: "db-shell-empty", children: [
6350
+ ] }) : /* @__PURE__ */ jsxs11("div", { className: "db-shell-empty db-shell-empty--workspace", children: [
6099
6351
  effectiveCompact && /* @__PURE__ */ jsxs11("button", { className: "db-mobile-back", onClick: () => setMobileShowEditor(false), children: [
6100
6352
  /* @__PURE__ */ jsx12("span", { className: "db-mobile-files-icon", children: /* @__PURE__ */ jsx12(FolderGlyph, {}) }),
6101
6353
  "Back to files"
6102
6354
  ] }),
6103
- /* @__PURE__ */ jsx12("p", { children: "Select a file to start editing, or create a new one." })
6355
+ /* @__PURE__ */ jsxs11("div", { className: "db-workspace-empty-content", children: [
6356
+ /* @__PURE__ */ jsx12("span", { className: "db-workspace-empty-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx12(FileGlyph, {}) }),
6357
+ /* @__PURE__ */ jsx12("h1", { children: activeWorkspaceDescriptor?.name ? `${activeWorkspaceDescriptor.name} is ready` : "Your workspace is ready" }),
6358
+ /* @__PURE__ */ jsx12("p", { children: "Create your first Markdown document, or open a folder you already use." }),
6359
+ /* @__PURE__ */ jsxs11("div", { className: "db-workspace-empty-actions", children: [
6360
+ /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => void handleNewFile(), children: "Create your first document" }),
6361
+ /* @__PURE__ */ jsx12(
6362
+ "button",
6363
+ {
6364
+ type: "button",
6365
+ className: "db-workspace-empty-secondary",
6366
+ onClick: () => void handleNewFolder(),
6367
+ children: "New folder"
6368
+ }
6369
+ ),
6370
+ (isElectronHost2() || typeof globalThis.showDirectoryPicker === "function") && /* @__PURE__ */ jsx12(
6371
+ "button",
6372
+ {
6373
+ type: "button",
6374
+ className: "db-workspace-empty-secondary",
6375
+ onClick: () => void handleOpenFolder(),
6376
+ children: "Open a folder"
6377
+ }
6378
+ )
6379
+ ] }),
6380
+ /* @__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." })
6381
+ ] })
6104
6382
  ] }),
6105
6383
  /* @__PURE__ */ jsx12(
6106
6384
  UpdateAvailableNotice,
@@ -6119,34 +6397,6 @@ function DocBlocksShell({
6119
6397
  }
6120
6398
  );
6121
6399
  }
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
6400
  export {
6151
6401
  AccentColorSettings,
6152
6402
  AppMenu,
@@ -6167,4 +6417,3 @@ export {
6167
6417
  useDocumentSession,
6168
6418
  useFileTree
6169
6419
  };
6170
- //# sourceMappingURL=index.js.map