@bendyline/docblocks-react 2.7.0 → 2.7.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 (43) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.txt +98 -48
  3. package/dist/{AiDiagrams-XWOM3YZX.js → AiDiagrams-LFV4YU6W.js} +2 -2
  4. package/dist/{AiEditorAssistant-M4LI2CRY.js → AiEditorAssistant-GN2KXKZD.js} +21 -6
  5. package/dist/{ExportDialog-L6FG6W3H.js → ExportDialog-JRVZCG3J.js} +2 -2
  6. package/dist/{ExportToolbarControls-BRCPZ7H3.js → ExportToolbarControls-2FEIHNMW.js} +28 -18
  7. package/dist/{GitToolbarControl-J4UYZDV6.js → GitToolbarControl-B7KG4MFP.js} +4 -4
  8. package/dist/{GitUI-LZW3IUXO.js → GitUI-RCWUZVS6.js} +2 -2
  9. package/dist/KeyboardShortcutsDialog-NOD4P25G.js +145 -0
  10. package/dist/{PresentationMaker-622NHMIN.js → PresentationMaker-ITNP5SDO.js} +2 -2
  11. package/dist/{Settings-DuXk6baH.d.ts → Settings-RDYvDDI0.d.ts} +13 -11
  12. package/dist/ShareDialog-XUA2OVGI.js +292 -0
  13. package/dist/{SpeechToolbarControl-UZQKGQWD.js → SpeechToolbarControl-CLU7RB2G.js} +2 -2
  14. package/dist/{TextFromNarration-XYRAB6O2.js → TextFromNarration-NITJQ4IB.js} +1 -1
  15. package/dist/{WorkspaceSearchPanel-OUQQ5DOM.js → WorkspaceSearchPanel-J6BKEZQJ.js} +8 -2
  16. package/dist/{chunk-UG4G7PJ4.js → chunk-5O5YTOXG.js} +1 -1
  17. package/dist/{chunk-VO2JYYQZ.js → chunk-6JJOGV6M.js} +5 -13
  18. package/dist/{chunk-X437UAF7.js → chunk-6MSHXQNB.js} +7 -2
  19. package/dist/{chunk-Q5ZVP5LV.js → chunk-BLUEZG7T.js} +40 -32
  20. package/dist/chunk-DICE7VBJ.js +77 -0
  21. package/dist/{chunk-MRUK56JS.js → chunk-GMA4KGGO.js} +7 -3
  22. package/dist/{chunk-BCQZTWE5.js → chunk-HRWEGPTY.js} +18 -4
  23. package/dist/chunk-K7XJDJUJ.js +126 -0
  24. package/dist/{chunk-5OZBGIIT.js → chunk-NAH52736.js} +86 -28
  25. package/dist/{chunk-YS4HVWIO.js → chunk-SPUPWCSI.js} +49 -1
  26. package/dist/{chunk-MMOTEQK6.js → chunk-SSB43524.js} +165 -70
  27. package/dist/{chunk-G4MFB3IO.js → chunk-VG434562.js} +2 -70
  28. package/dist/{chunk-OINKNX26.js → chunk-X3DDTJKR.js} +1 -1
  29. package/dist/{chunk-SGQKTKDX.js → chunk-Y5OE3HJC.js} +14 -4
  30. package/dist/editor.js +7 -5
  31. package/dist/export/index.d.ts +17 -6
  32. package/dist/export/index.js +4 -4
  33. package/dist/index.d.ts +24 -6
  34. package/dist/index.js +452 -247
  35. package/dist/proofing/index.js +11 -1
  36. package/dist/run-export-CS3HRNAZ.js +13 -0
  37. package/dist/settings/index.d.ts +1 -1
  38. package/dist/settings/index.js +1 -1
  39. package/package.json +9 -8
  40. package/src/styles/docblocks.css +481 -271
  41. package/dist/ShareDialog-N7GPM4SB.js +0 -310
  42. package/dist/chunk-EA4BO6E4.js +0 -73
  43. package/dist/run-export-47X5FTYZ.js +0 -11
@@ -0,0 +1,145 @@
1
+ import {
2
+ SHELL_SHORTCUTS,
3
+ isApplePlatform
4
+ } from "./chunk-K7XJDJUJ.js";
5
+ import {
6
+ Dialog
7
+ } from "./chunk-56KO2LYN.js";
8
+
9
+ // src/DocBlocksShell/KeyboardShortcutsDialog.tsx
10
+ import { Fragment, useId, useMemo } from "react";
11
+ import { getHostEnvironment } from "@bendyline/docblocks/host";
12
+
13
+ // src/DocBlocksShell/shortcut-sheet.ts
14
+ var VIEW_KEYS = {
15
+ title: "Views",
16
+ entries: [
17
+ { action: "Write", chords: [["Mod", "Shift", "1"]] },
18
+ { action: "Source", chords: [["Mod", "Shift", "2"]] },
19
+ { action: "Use (preview, slideshow, video)", chords: [["Mod", "Shift", "3"]] }
20
+ ]
21
+ };
22
+ var EDITING_KEYS = {
23
+ title: "Formatting in Write",
24
+ entries: [
25
+ { action: "Bold", chords: [["Mod", "B"]] },
26
+ { action: "Italic", chords: [["Mod", "I"]] },
27
+ { action: "Strikethrough", chords: [["Mod", "Shift", "S"]] },
28
+ { action: "Inline code", chords: [["Mod", "E"]] },
29
+ { action: "Link", chords: [["Mod", "K"]] },
30
+ { action: "Heading 1\u20136", chords: [["Mod", "Alt", "1\u20136"]] },
31
+ { action: "Bulleted list", chords: [["Mod", "Shift", "8"]] },
32
+ { action: "Numbered list", chords: [["Mod", "Shift", "7"]] },
33
+ { action: "Quote", chords: [["Mod", "Shift", "B"]] },
34
+ { action: "Superscript", chords: [["Mod", "."]] },
35
+ { action: "Subscript", chords: [["Mod", ","]] },
36
+ { action: "Line break", chords: [["Shift", "Enter"]] },
37
+ { action: "Undo", chords: [["Mod", "Z"]] },
38
+ { action: "Redo", chords: [["Mod", "Shift", "Z"]] }
39
+ ]
40
+ };
41
+ var PRESENTING_KEYS = {
42
+ title: "Slideshow",
43
+ entries: [
44
+ { action: "Next slide or step", chords: [["\u2192"], ["Space"]] },
45
+ { action: "Previous slide or step", chords: [["\u2190"]] },
46
+ { action: "First or last slide", chords: [["Home"], ["End"]] },
47
+ { action: "Pick a slide", chords: [["\u2191"]] },
48
+ { action: "Leave the presentation", chords: [["Esc"]] }
49
+ ]
50
+ };
51
+ function labelKeys(label) {
52
+ return label.split("+").map((key) => key === "Ctrl" ? "Mod" : key);
53
+ }
54
+ function shellEntries({ nativeHost }) {
55
+ const entries = /* @__PURE__ */ new Map();
56
+ for (const binding of SHELL_SHORTCUTS) {
57
+ if (binding.browserReserved && !nativeHost) continue;
58
+ const entry = entries.get(binding.id) ?? { chords: [] };
59
+ entry.action ?? (entry.action = binding.action);
60
+ entry.chords.push(labelKeys(binding.label));
61
+ entries.set(binding.id, entry);
62
+ }
63
+ return [...entries.values()].flatMap(
64
+ (entry) => entry.action ? [{ action: entry.action, chords: entry.chords }] : []
65
+ );
66
+ }
67
+ function shortcutSheet(options) {
68
+ return [
69
+ { title: "Documents and files", entries: shellEntries(options) },
70
+ VIEW_KEYS,
71
+ EDITING_KEYS,
72
+ PRESENTING_KEYS
73
+ ];
74
+ }
75
+ var APPLE_MODIFIERS = {
76
+ Mod: "\u2318",
77
+ Alt: "\u2325",
78
+ Shift: "\u21E7"
79
+ };
80
+ var APPLE_ORDER = ["Alt", "Shift", "Mod"];
81
+ function displayKeys(chord, apple) {
82
+ if (!apple) return chord.map((key) => key === "Mod" ? "Ctrl" : key);
83
+ const modifiers = APPLE_ORDER.filter((key) => chord.includes(key));
84
+ const rest = chord.filter((key) => !(key in APPLE_MODIFIERS));
85
+ return [...modifiers.map((key) => APPLE_MODIFIERS[key]), ...rest];
86
+ }
87
+ var SPOKEN_KEYS = {
88
+ "\u2192": "Right arrow",
89
+ "\u2190": "Left arrow",
90
+ "\u2191": "Up arrow",
91
+ "?": "Question mark",
92
+ "\\": "Backslash",
93
+ ".": "Period",
94
+ ",": "Comma",
95
+ "1\u20136": "1 to 6"
96
+ };
97
+ function spokenChord(chord, apple) {
98
+ return chord.map(
99
+ (key) => key === "Mod" ? apple ? "Command" : "Control" : key === "Alt" && apple ? "Option" : key
100
+ ).map((key) => SPOKEN_KEYS[key] ?? key).join(" ");
101
+ }
102
+
103
+ // src/DocBlocksShell/KeyboardShortcutsDialog.tsx
104
+ import { jsx, jsxs } from "react/jsx-runtime";
105
+ function KeyboardShortcutsDialog({ onClose }) {
106
+ const idPrefix = useId();
107
+ const apple = isApplePlatform();
108
+ const sections = useMemo(() => shortcutSheet({ nativeHost: getHostEnvironment() !== null }), []);
109
+ return /* @__PURE__ */ jsx(
110
+ Dialog,
111
+ {
112
+ title: "Keyboard shortcuts",
113
+ onClose,
114
+ size: "wide",
115
+ className: "db-shortcuts-dialog",
116
+ children: /* @__PURE__ */ jsx("div", { className: "db-shortcuts", role: "region", "aria-label": "Shortcut list", tabIndex: 0, children: sections.map((section, sectionIndex) => {
117
+ const headingId = `${idPrefix}-${sectionIndex}`;
118
+ return /* @__PURE__ */ jsxs(
119
+ "section",
120
+ {
121
+ className: "db-shortcuts-section",
122
+ "aria-labelledby": headingId,
123
+ children: [
124
+ /* @__PURE__ */ jsx("h3", { id: headingId, className: "db-shortcuts-heading", children: section.title }),
125
+ /* @__PURE__ */ jsx("dl", { className: "db-shortcuts-list", children: section.entries.map((entry) => /* @__PURE__ */ jsxs("div", { className: "db-shortcuts-row", children: [
126
+ /* @__PURE__ */ jsx("dt", { className: "db-shortcuts-action", children: entry.action }),
127
+ /* @__PURE__ */ jsx("dd", { className: "db-shortcuts-keys", children: entry.chords.map((chord, chordIndex) => /* @__PURE__ */ jsxs(Fragment, { children: [
128
+ chordIndex > 0 && /* @__PURE__ */ jsx("span", { className: "db-shortcuts-or", children: "or" }),
129
+ /* @__PURE__ */ jsxs("span", { className: "db-shortcuts-chord", children: [
130
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: displayKeys(chord, apple).map((key) => /* @__PURE__ */ jsx("kbd", { children: key }, key)) }),
131
+ /* @__PURE__ */ jsx("span", { className: "db-visually-hidden", children: spokenChord(chord, apple) })
132
+ ] })
133
+ ] }, chord.join("+"))) })
134
+ ] }, entry.action)) })
135
+ ]
136
+ },
137
+ section.title
138
+ );
139
+ }) })
140
+ }
141
+ );
142
+ }
143
+ export {
144
+ KeyboardShortcutsDialog
145
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PresentationMaker
3
- } from "./chunk-MMOTEQK6.js";
4
- import "./chunk-5OZBGIIT.js";
3
+ } from "./chunk-SSB43524.js";
4
+ import "./chunk-NAH52736.js";
5
5
  import "./chunk-K4I3NJFL.js";
6
6
  import "./chunk-56KO2LYN.js";
7
7
  export {
@@ -5,22 +5,24 @@ import { WriteCanvasSettings } from '@bendyline/squisq-editor-react';
5
5
 
6
6
  /** Appearance preferences shared by the shell and settings UI. */
7
7
  type ThemePreference = 'auto' | 'light' | 'dark';
8
- declare const ACCENT_COLORS: readonly ["brown", "green", "blue", "purple", "maroon", "orange", "gray"];
8
+ declare const ACCENT_COLORS: readonly ["rust", "green", "blue", "purple", "maroon", "orange", "gray"];
9
9
  type AccentColor = (typeof ACCENT_COLORS)[number];
10
10
  /**
11
11
  * Which typeface the shell chrome uses.
12
12
  *
13
- * `system` resolves `system-ui`, so DocBlocks looks native on each platform —
14
- * the right default, and what almost everyone should keep. `fixed` pins the
15
- * chrome to the bundled `DocBlocks Fixed UI` face (the Roboto variable binary
16
- * already shipped for the document theme of that name) so the interface renders
17
- * identically everywhere.
13
+ * `fixed` — the default — pins the chrome to the bundled `DocBlocks Fixed UI`
14
+ * face: the Hanken Grotesk variable binary every surface already ships for
15
+ * documents set in it, and the sans docblocks.com uses. The app and the site
16
+ * then read as one product, and the interface renders identically on every
17
+ * OS — which is also what lets the visual-regression suite compare one set of
18
+ * baselines across platforms at all.
18
19
  *
19
- * Two things want that. A user working across machines gets typography that
20
- * does not shift between them. And the visual-regression suite gets a rendering
21
- * it can compare across operating systems at all: under `system` the same
22
- * screenshot differs on every platform, which is why those baselines are
23
- * otherwise captured on one OS and compared only there.
20
+ * `system` resolves `system-ui`, for anyone who would rather DocBlocks looked
21
+ * native on each platform; the chrome then fetches no font.
22
+ *
23
+ * The stored values kept their names when the default changed, so a saved
24
+ * `system` still opts out. A saved `fixed` now gets Hanken Grotesk; it used to
25
+ * get Roboto.
24
26
  */
25
27
  type InterfaceFontPreference = 'system' | 'fixed';
26
28
  declare const DEFAULT_INTERFACE_FONT: InterfaceFontPreference;
@@ -0,0 +1,292 @@
1
+ import {
2
+ buildSharedDocumentQrUrl,
3
+ buildSharedDocumentUrl,
4
+ createSharedDocumentArchive,
5
+ sharedDocumentQrFilename
6
+ } from "./chunk-SPUPWCSI.js";
7
+ import {
8
+ Dialog
9
+ } from "./chunk-56KO2LYN.js";
10
+
11
+ // src/Export/ShareDialog.tsx
12
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
13
+ import { SHARED_DOCUMENT_LIMITS } from "@bendyline/docblocks/share";
14
+
15
+ // src/Export/share-qr.ts
16
+ import QRCode from "qrcode";
17
+ async function renderSharedDocumentQrPng(url) {
18
+ return QRCode.toDataURL(url, {
19
+ type: "image/png",
20
+ errorCorrectionLevel: "M",
21
+ margin: 4,
22
+ width: 1024,
23
+ color: {
24
+ dark: "#111111ff",
25
+ light: "#ffffffff"
26
+ }
27
+ });
28
+ }
29
+ function qrPngDataUrlToBlob(dataUrl) {
30
+ const prefix = "data:image/png;base64,";
31
+ if (!dataUrl.startsWith(prefix)) {
32
+ throw new Error("The QR code renderer returned an unsupported image format.");
33
+ }
34
+ const binary = atob(dataUrl.slice(prefix.length));
35
+ const bytes = new Uint8Array(binary.length);
36
+ for (let index = 0; index < binary.length; index += 1) {
37
+ bytes[index] = binary.charCodeAt(index);
38
+ }
39
+ return new Blob([bytes], { type: "image/png" });
40
+ }
41
+
42
+ // src/Export/ShareDialog.tsx
43
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
44
+ var MODES = [
45
+ { value: "", label: "Write (no default)" },
46
+ { value: "slideshow", label: "Slideshow" },
47
+ { value: "video", label: "Video" },
48
+ { value: "page", label: "Page" },
49
+ { value: "document", label: "Document" },
50
+ { value: "narrate", label: "Narrate" }
51
+ ];
52
+ function ShareDialog({
53
+ markdown,
54
+ selectedFile,
55
+ baseUrl,
56
+ onClose
57
+ }) {
58
+ const [mode, setMode] = useState("");
59
+ const [archive, setArchive] = useState(null);
60
+ const [archiveError, setArchiveError] = useState(null);
61
+ const [copyStatus, setCopyStatus] = useState("idle");
62
+ const [qrDataUrl, setQrDataUrl] = useState(null);
63
+ const [qrRenderError, setQrRenderError] = useState(null);
64
+ const [qrCopyStatus, setQrCopyStatus] = useState("idle");
65
+ const linkRef = useRef(null);
66
+ useEffect(() => {
67
+ let cancelled = false;
68
+ setArchive(null);
69
+ setArchiveError(null);
70
+ void createSharedDocumentArchive(markdown, selectedFile).then((nextArchive) => {
71
+ if (!cancelled) setArchive(nextArchive);
72
+ }).catch((error) => {
73
+ if (!cancelled) {
74
+ setArchiveError(
75
+ error instanceof Error ? error.message : "DocBlocks could not create the shared link."
76
+ );
77
+ }
78
+ });
79
+ return () => {
80
+ cancelled = true;
81
+ };
82
+ }, [markdown, selectedFile]);
83
+ const linkResult = useMemo(() => {
84
+ if (!archive) return { url: "", error: archiveError };
85
+ try {
86
+ return { url: buildSharedDocumentUrl(baseUrl, archive, mode || null), error: null };
87
+ } catch (error) {
88
+ return {
89
+ url: "",
90
+ error: error instanceof Error ? error.message : "DocBlocks could not create the shared link."
91
+ };
92
+ }
93
+ }, [archive, archiveError, baseUrl, mode]);
94
+ const qrLinkResult = useMemo(() => {
95
+ if (!archive) return { url: "", error: archiveError };
96
+ try {
97
+ return { url: buildSharedDocumentQrUrl(archive, mode || null), error: null };
98
+ } catch (error) {
99
+ return {
100
+ url: "",
101
+ error: error instanceof Error ? error.message : "DocBlocks could not create the QR code."
102
+ };
103
+ }
104
+ }, [archive, archiveError, mode]);
105
+ useEffect(() => {
106
+ let cancelled = false;
107
+ setQrDataUrl(null);
108
+ setQrRenderError(null);
109
+ setQrCopyStatus("idle");
110
+ if (!qrLinkResult.url) return;
111
+ void renderSharedDocumentQrPng(qrLinkResult.url).then((dataUrl) => {
112
+ if (!cancelled) setQrDataUrl(dataUrl);
113
+ }).catch((error) => {
114
+ if (!cancelled) {
115
+ setQrRenderError(
116
+ error instanceof Error ? error.message : "DocBlocks could not render the QR code."
117
+ );
118
+ }
119
+ });
120
+ return () => {
121
+ cancelled = true;
122
+ };
123
+ }, [qrLinkResult.url]);
124
+ useEffect(() => setCopyStatus("idle"), [linkResult.url]);
125
+ const copyLink = useCallback(async () => {
126
+ if (!linkResult.url) return;
127
+ try {
128
+ if (navigator.clipboard?.writeText) {
129
+ await navigator.clipboard.writeText(linkResult.url);
130
+ } else {
131
+ linkRef.current?.focus();
132
+ linkRef.current?.select();
133
+ if (typeof document.execCommand !== "function" || !document.execCommand("copy")) {
134
+ throw new Error("Clipboard access is unavailable.");
135
+ }
136
+ }
137
+ setCopyStatus("copied");
138
+ } catch {
139
+ linkRef.current?.focus();
140
+ linkRef.current?.select();
141
+ setCopyStatus("failed");
142
+ }
143
+ }, [linkResult.url]);
144
+ const copyQrImage = useCallback(async () => {
145
+ if (!qrDataUrl) return;
146
+ try {
147
+ if (!navigator.clipboard?.write || typeof ClipboardItem === "undefined") {
148
+ throw new Error("Image clipboard access is unavailable.");
149
+ }
150
+ const png = qrPngDataUrlToBlob(qrDataUrl);
151
+ await navigator.clipboard.write([new ClipboardItem({ "image/png": png })]);
152
+ setQrCopyStatus("copied");
153
+ } catch {
154
+ setQrCopyStatus("failed");
155
+ }
156
+ }, [qrDataUrl]);
157
+ const saveQrImage = useCallback(() => {
158
+ if (!qrDataUrl) return;
159
+ const download = document.createElement("a");
160
+ download.href = qrDataUrl;
161
+ download.download = sharedDocumentQrFilename(selectedFile);
162
+ download.style.display = "none";
163
+ document.body.append(download);
164
+ download.click();
165
+ download.remove();
166
+ }, [qrDataUrl, selectedFile]);
167
+ const isLong = linkResult.url.length > SHARED_DOCUMENT_LIMITS.portableUrlCharacters;
168
+ const qrError = qrLinkResult.error ?? qrRenderError;
169
+ return /* @__PURE__ */ jsxs(
170
+ Dialog,
171
+ {
172
+ title: "Share document",
173
+ onClose,
174
+ className: "db-export-dialog db-share-dialog",
175
+ footerClassName: "db-export-footer",
176
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
177
+ /* @__PURE__ */ jsx(
178
+ "button",
179
+ {
180
+ className: "db-export-btn db-export-btn--secondary",
181
+ type: "button",
182
+ onClick: onClose,
183
+ children: "Close"
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsx(
187
+ "button",
188
+ {
189
+ className: "db-export-btn db-export-btn--primary",
190
+ type: "button",
191
+ onClick: () => void copyLink(),
192
+ disabled: !linkResult.url,
193
+ children: copyStatus === "copied" ? "Copied" : copyStatus === "failed" ? "Select and copy" : "Copy link"
194
+ }
195
+ )
196
+ ] }),
197
+ children: [
198
+ /* @__PURE__ */ jsx("div", { className: "db-share-warning", role: "note", children: "The text and formatting of this document are embedded in the URL. Anyone with the link can read the shared copy, so do not use it for secrets." }),
199
+ /* @__PURE__ */ jsx("p", { className: "db-export-hint", role: "note", children: "Images are excluded from share links. Image descriptions are kept when available. Your original document is unchanged." }),
200
+ /* @__PURE__ */ jsxs("div", { className: "db-export-field", children: [
201
+ /* @__PURE__ */ jsx("label", { className: "db-export-label", htmlFor: "db-share-mode", children: "Default mode (optional)" }),
202
+ /* @__PURE__ */ jsx(
203
+ "select",
204
+ {
205
+ id: "db-share-mode",
206
+ className: "db-export-select",
207
+ value: mode,
208
+ onChange: (event) => setMode(event.target.value),
209
+ children: MODES.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value || "write"))
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsx("span", { className: "db-export-hint", children: "Without a default, the copied document opens in Write. Other choices open directly in Use." })
213
+ ] }),
214
+ /* @__PURE__ */ jsxs("div", { className: "db-export-field", children: [
215
+ /* @__PURE__ */ jsx("label", { className: "db-export-label", htmlFor: "db-share-link", children: "Share link" }),
216
+ /* @__PURE__ */ jsx(
217
+ "textarea",
218
+ {
219
+ ref: linkRef,
220
+ id: "db-share-link",
221
+ className: "db-share-link",
222
+ value: linkResult.url,
223
+ readOnly: true,
224
+ rows: 3,
225
+ spellCheck: false,
226
+ "aria-describedby": "db-share-link-status",
227
+ placeholder: linkResult.error ? "" : "Creating link..."
228
+ }
229
+ ),
230
+ /* @__PURE__ */ jsx(
231
+ "span",
232
+ {
233
+ id: "db-share-link-status",
234
+ className: `db-export-hint${isLong ? " db-share-length-warning" : ""}`,
235
+ children: linkResult.error ? linkResult.error : linkResult.url ? isLong ? `${linkResult.url.length.toLocaleString()} characters. Links longer than 4,096 characters may be truncated by messaging or email tools.` : `${linkResult.url.length.toLocaleString()} characters.` : "Creating a link with the shared text and formatting..."
236
+ }
237
+ )
238
+ ] }),
239
+ /* @__PURE__ */ jsxs("section", { className: "db-share-qr", "aria-labelledby": "db-share-qr-title", children: [
240
+ /* @__PURE__ */ jsxs("div", { className: "db-share-qr-heading", children: [
241
+ /* @__PURE__ */ jsxs("div", { children: [
242
+ /* @__PURE__ */ jsx("h3", { id: "db-share-qr-title", children: "QR code" }),
243
+ /* @__PURE__ */ jsx("span", { className: "db-export-hint", children: "A self-contained docblocks.com link to the shared text. No document content is uploaded." })
244
+ ] }),
245
+ /* @__PURE__ */ jsxs("div", { className: "db-share-qr-actions", children: [
246
+ /* @__PURE__ */ jsx(
247
+ "button",
248
+ {
249
+ className: "db-export-btn db-export-btn--secondary",
250
+ type: "button",
251
+ onClick: () => void copyQrImage(),
252
+ disabled: !qrDataUrl,
253
+ children: qrCopyStatus === "copied" ? "Copied QR" : qrCopyStatus === "failed" ? "Copy unavailable" : "Copy QR image"
254
+ }
255
+ ),
256
+ /* @__PURE__ */ jsx(
257
+ "button",
258
+ {
259
+ className: "db-export-btn db-export-btn--secondary",
260
+ type: "button",
261
+ onClick: saveQrImage,
262
+ disabled: !qrDataUrl,
263
+ children: "Save QR PNG"
264
+ }
265
+ )
266
+ ] })
267
+ ] }),
268
+ qrDataUrl ? /* @__PURE__ */ jsx(
269
+ "img",
270
+ {
271
+ className: "db-share-qr-image",
272
+ src: qrDataUrl,
273
+ alt: "QR code for this shared DocBlocks document"
274
+ }
275
+ ) : /* @__PURE__ */ jsx(
276
+ "div",
277
+ {
278
+ className: `db-share-qr-placeholder${qrError ? " db-share-qr-placeholder--error" : ""}`,
279
+ role: qrError ? "note" : "status",
280
+ children: qrError ?? "Rendering QR code..."
281
+ }
282
+ ),
283
+ /* @__PURE__ */ jsx("span", { className: "db-export-hint db-share-qr-status", "aria-live": "polite", children: qrCopyStatus === "failed" ? "This browser could not copy the PNG. Save it instead." : qrDataUrl ? `${qrLinkResult.url.length.toLocaleString()} of ${SHARED_DOCUMENT_LIMITS.qrUrlCharacters.toLocaleString()} supported QR characters.` : "" })
284
+ ] }),
285
+ /* @__PURE__ */ jsx("p", { className: "db-share-temporary-note", children: "Opening the link creates a temporary workspace. Changes there do not alter this document or the link." })
286
+ ]
287
+ }
288
+ );
289
+ }
290
+ export {
291
+ ShareDialog
292
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useSpeechStatus
3
- } from "./chunk-VO2JYYQZ.js";
3
+ } from "./chunk-6JJOGV6M.js";
4
4
  import {
5
5
  hasDocumentNarration,
6
6
  planSpeech,
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-2IYBBFQX.js";
13
13
  import {
14
14
  useMenuKeyboard
15
- } from "./chunk-MRUK56JS.js";
15
+ } from "./chunk-GMA4KGGO.js";
16
16
  import {
17
17
  Dialog
18
18
  } from "./chunk-56KO2LYN.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useSpeechStatus
3
- } from "./chunk-VO2JYYQZ.js";
3
+ } from "./chunk-6JJOGV6M.js";
4
4
  import {
5
5
  createSpeechInputProvider
6
6
  } from "./chunk-KBDZDFPU.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-TBLZBNQF.js";
4
4
  import {
5
5
  FilesIcon
6
- } from "./chunk-EA4BO6E4.js";
6
+ } from "./chunk-DICE7VBJ.js";
7
7
 
8
8
  // src/WorkspaceSearch/WorkspaceSearchPanel.tsx
9
9
  import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
@@ -24,6 +24,10 @@ function useWorkspaceSearch(index, generation, query) {
24
24
  useEffect(() => {
25
25
  controllerRef.current?.abort();
26
26
  controllerRef.current = null;
27
+ if (refreshTimerRef.current !== null) {
28
+ clearTimeout(refreshTimerRef.current);
29
+ refreshTimerRef.current = null;
30
+ }
27
31
  if (!index || !text.trim()) {
28
32
  runRef.current = null;
29
33
  setState(EMPTY);
@@ -45,7 +49,7 @@ function useWorkspaceSearch(index, generation, query) {
45
49
  );
46
50
  };
47
51
  runRef.current = run;
48
- setState((current) => ({ ...current, searching: true }));
52
+ setState({ result: null, searching: true });
49
53
  const timer = setTimeout(run, QUERY_DEBOUNCE_MS);
50
54
  return () => clearTimeout(timer);
51
55
  }, [index, text, matchCase, wholeWord]);
@@ -59,6 +63,8 @@ function useWorkspaceSearch(index, generation, query) {
59
63
  useEffect(
60
64
  () => () => {
61
65
  controllerRef.current?.abort();
66
+ controllerRef.current = null;
67
+ runRef.current = null;
62
68
  if (refreshTimerRef.current !== null) clearTimeout(refreshTimerRef.current);
63
69
  },
64
70
  []
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FORMAT_EXTENSIONS
3
- } from "./chunk-X437UAF7.js";
3
+ } from "./chunk-6MSHXQNB.js";
4
4
 
5
5
  // src/Export/export-filename.ts
6
6
  function buildExportFilename(selectedFile, options) {
@@ -12,19 +12,11 @@ function useSpeechStatus(speech) {
12
12
  if (live) setStatus((current) => current ?? initial);
13
13
  },
14
14
  () => {
15
- if (live)
16
- setStatus(
17
- (current) => current ?? {
18
- stt: {
19
- state: "unavailable",
20
- reason: "Speech status is unavailable. Try opening Settings."
21
- },
22
- tts: {
23
- state: "unavailable",
24
- reason: "Speech status is unavailable. Try opening Settings."
25
- }
26
- }
27
- );
15
+ const readiness = {
16
+ state: "unavailable",
17
+ reason: "DocBlocks could not check speech on this computer."
18
+ };
19
+ if (live) setStatus((current) => current ?? { stt: readiness, tts: readiness });
28
20
  }
29
21
  );
30
22
  return () => {
@@ -1,4 +1,5 @@
1
1
  // src/Export/export-options.ts
2
+ import { getThemeSummaries } from "@bendyline/squisq/schemas";
2
3
  var FORMAT_LABELS = {
3
4
  docx: "Word Document (.docx)",
4
5
  pdf: "PDF (.pdf)",
@@ -28,8 +29,11 @@ var DEFAULT_OPTIONS = {
28
29
  };
29
30
  function resolveExportDialogInitial(lastOptions, docThemeId, workspaceThemeId) {
30
31
  const base = lastOptions ?? DEFAULT_OPTIONS;
31
- const themeId = docThemeId || workspaceThemeId;
32
- return themeId ? { ...base, themeId } : base;
32
+ return { ...base, themeId: docThemeId || workspaceThemeId || DEFAULT_OPTIONS.themeId };
33
+ }
34
+ function exportThemeChoices(documentThemes = []) {
35
+ const ids = new Set(documentThemes.map((theme) => theme.id));
36
+ return [...documentThemes, ...getThemeSummaries().filter((theme) => !ids.has(theme.id))];
33
37
  }
34
38
  function loadLastExportOptions() {
35
39
  try {
@@ -53,6 +57,7 @@ export {
53
57
  FORMAT_EXTENSIONS,
54
58
  DEFAULT_OPTIONS,
55
59
  resolveExportDialogInitial,
60
+ exportThemeChoices,
56
61
  loadLastExportOptions,
57
62
  saveExportOptions
58
63
  };