@bendyline/docblocks-react 2.6.2 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +3 -10
  2. package/THIRD_PARTY_NOTICES.txt +4284 -3651
  3. package/dist/AiDiagrams-XWOM3YZX.js +13 -0
  4. package/dist/AiEditorAssistant-M4LI2CRY.js +771 -0
  5. package/dist/AudioExportDialog-F23QXNPB.js +253 -0
  6. package/dist/DocumentConflictDialog-5LRVIGXS.js +111 -0
  7. package/dist/ExportDialog-L6FG6W3H.js +9 -0
  8. package/dist/{ExportToolbarControls-QYLBRWM7.js → ExportToolbarControls-BRCPZ7H3.js} +108 -42
  9. package/dist/{GitUI-L5T3XHZG.js → GitUI-LZW3IUXO.js} +5 -5
  10. package/dist/PresentationMaker-622NHMIN.js +9 -0
  11. package/dist/QuickOpenDialog-2KK4SEBU.js +356 -0
  12. package/dist/Settings-DuXk6baH.d.ts +144 -0
  13. package/dist/{ShareDialog-4OX3UPJ4.js → ShareDialog-N7GPM4SB.js} +10 -59
  14. package/dist/SpeechToolbarControl-UZQKGQWD.js +434 -0
  15. package/dist/TextFromNarration-XYRAB6O2.js +598 -0
  16. package/dist/WorkspaceSearchPanel-OUQQ5DOM.js +328 -0
  17. package/dist/chunk-2IYBBFQX.js +201 -0
  18. package/dist/{chunk-LG6HAWCK.js → chunk-56KO2LYN.js} +35 -4
  19. package/dist/chunk-5OZBGIIT.js +1826 -0
  20. package/dist/chunk-6B7G33D5.js +88 -0
  21. package/dist/chunk-ARATARVJ.js +40 -0
  22. package/dist/{chunk-ODYNOPC3.js → chunk-BCQZTWE5.js} +57 -17
  23. package/dist/chunk-EA4BO6E4.js +73 -0
  24. package/dist/chunk-G4MFB3IO.js +208 -0
  25. package/dist/chunk-I44HKDQX.js +314 -0
  26. package/dist/chunk-K4I3NJFL.js +49 -0
  27. package/dist/chunk-KBDZDFPU.js +61 -0
  28. package/dist/{chunk-FNSRCCBC.js → chunk-KWG6UP5O.js} +0 -10
  29. package/dist/chunk-MMOTEQK6.js +518 -0
  30. package/dist/chunk-OINKNX26.js +574 -0
  31. package/dist/chunk-Q5ZVP5LV.js +1711 -0
  32. package/dist/chunk-RP3DVNTI.js +62 -0
  33. package/dist/{chunk-FMDLZ4ZG.js → chunk-SGQKTKDX.js} +2 -2
  34. package/dist/chunk-TBLZBNQF.js +25 -0
  35. package/dist/{chunk-M5Y5WO7Z.js → chunk-UG4G7PJ4.js} +1 -1
  36. package/dist/chunk-UONFWKJ7.js +22 -0
  37. package/dist/chunk-VO2JYYQZ.js +40 -0
  38. package/dist/{chunk-EBWYGTN7.js → chunk-X437UAF7.js} +6 -0
  39. package/dist/chunk-YS4HVWIO.js +63 -0
  40. package/dist/editor.d.ts +36 -1
  41. package/dist/editor.js +16 -1
  42. package/dist/export/index.d.ts +27 -11
  43. package/dist/export/index.js +11 -7
  44. package/dist/index.d.ts +100 -13
  45. package/dist/index.js +3916 -1633
  46. package/dist/{run-export-I2XOVGG4.js → run-export-47X5FTYZ.js} +3 -3
  47. package/dist/settings/index.d.ts +32 -101
  48. package/dist/settings/index.js +14 -2
  49. package/package.json +9 -9
  50. package/src/styles/docblocks.css +2332 -656
  51. package/dist/ExportDialog-Q4VWI43H.js +0 -9
  52. package/dist/chunk-JDJVRDOP.js +0 -92
  53. package/dist/chunk-Q6XNIKDG.js +0 -669
  54. package/dist/{GitToolbarControl-O5AXBC6T.js → GitToolbarControl-J4UYZDV6.js} +3 -3
@@ -0,0 +1,434 @@
1
+ import {
2
+ useSpeechStatus
3
+ } from "./chunk-VO2JYYQZ.js";
4
+ import {
5
+ hasDocumentNarration,
6
+ planSpeech,
7
+ useGenerateNarration
8
+ } from "./chunk-I44HKDQX.js";
9
+ import {
10
+ AutoPlayGate,
11
+ ProgressivePlayer
12
+ } from "./chunk-2IYBBFQX.js";
13
+ import {
14
+ useMenuKeyboard
15
+ } from "./chunk-MRUK56JS.js";
16
+ import {
17
+ Dialog
18
+ } from "./chunk-56KO2LYN.js";
19
+
20
+ // src/Speech/SpeechToolbarControl.tsx
21
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
22
+ import { useEditorContext as useEditorContext2 } from "@bendyline/squisq-editor-react";
23
+ import {
24
+ hostSupports,
25
+ maybeGetDocBlocksHost
26
+ } from "@bendyline/docblocks/host";
27
+
28
+ // src/Speech/GenerateNarrationDialog.tsx
29
+ import { useEditorContext } from "@bendyline/squisq-editor-react";
30
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
31
+ function formatDuration(seconds) {
32
+ const whole = Math.round(seconds);
33
+ const minutes = Math.floor(whole / 60);
34
+ const rest = whole % 60;
35
+ return minutes > 0 ? `${minutes} min ${rest} s` : `${rest} s`;
36
+ }
37
+ function GenerateNarrationDialog({ speech, onClose }) {
38
+ const { markdownSource } = useEditorContext();
39
+ const narration = useGenerateNarration(speech);
40
+ const { state } = narration;
41
+ const busy = state.phase === "generating" || state.phase === "saving";
42
+ const replacing = hasDocumentNarration(markdownSource);
43
+ const close = () => {
44
+ if (busy) narration.cancel();
45
+ onClose();
46
+ };
47
+ return /* @__PURE__ */ jsx(
48
+ Dialog,
49
+ {
50
+ title: "Generate narration",
51
+ onClose: close,
52
+ closeOnBackdrop: !busy,
53
+ className: "db-speech-narration-dialog",
54
+ footer: state.phase === "done" ? /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, children: "Done" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
55
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: close, children: busy ? "Stop" : "Cancel" }),
56
+ /* @__PURE__ */ jsx(
57
+ "button",
58
+ {
59
+ type: "button",
60
+ disabled: busy || !narration.available,
61
+ onClick: () => narration.generate(),
62
+ children: replacing ? "Replace narration" : "Generate"
63
+ }
64
+ )
65
+ ] }),
66
+ children: state.phase === "done" ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
67
+ "Added ",
68
+ formatDuration(state.durationSec),
69
+ " of narration. Play mode and video export now follow it, section by section."
70
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
71
+ /* @__PURE__ */ jsx("p", { className: "db-speech-narration-intro", children: "DocBlocks reads the document aloud with your narration voice and saves the audio beside it, timed to each section. Change the voice and speed in Settings." }),
72
+ replacing && /* @__PURE__ */ jsx("p", { className: "db-speech-narration-warning", role: "note", children: "This document already has narration. Generating replaces it; the old audio file stays in the document\u2019s folder." }),
73
+ state.phase === "generating" && /* @__PURE__ */ jsxs("div", { className: "db-settings-ai-download-progress", role: "status", children: [
74
+ /* @__PURE__ */ jsx("progress", { max: state.total, value: state.done, "aria-label": "Narration progress" }),
75
+ /* @__PURE__ */ jsxs("span", { children: [
76
+ "Generating part ",
77
+ Math.min(state.done + 1, state.total),
78
+ " of ",
79
+ state.total,
80
+ "\u2026"
81
+ ] })
82
+ ] }),
83
+ state.phase === "saving" && /* @__PURE__ */ jsx("p", { role: "status", children: "Saving the narration\u2026" }),
84
+ state.phase === "error" && /* @__PURE__ */ jsx("p", { className: "db-settings-ai-error", role: "alert", children: state.message })
85
+ ] })
86
+ }
87
+ );
88
+ }
89
+
90
+ // src/Speech/useReadAloud.ts
91
+ import { useCallback, useEffect, useRef, useState } from "react";
92
+ function useReadAloud(speech) {
93
+ const [state, setState] = useState({ phase: "idle" });
94
+ const session = useRef(null);
95
+ const stop = useCallback(() => {
96
+ const current = session.current;
97
+ session.current = null;
98
+ if (current) {
99
+ current.cancelled = true;
100
+ current.handle?.cancel();
101
+ current.player.dispose();
102
+ }
103
+ setState({ phase: "idle" });
104
+ }, []);
105
+ useEffect(() => stop, [stop]);
106
+ const start = useCallback(
107
+ (markdown) => {
108
+ const synthesize = speech?.synthesize;
109
+ if (!synthesize) return;
110
+ stop();
111
+ const onChange = (snapshot) => {
112
+ const current2 = session.current;
113
+ if (!current2 || current2.player !== player) return;
114
+ if (snapshot.complete && !snapshot.isPlaying && snapshot.currentTime >= snapshot.bufferedDuration - 0.05) {
115
+ session.current = null;
116
+ player.dispose();
117
+ setState({ phase: "idle" });
118
+ return;
119
+ }
120
+ const segment = snapshot.currentChunk === null ? 0 : current2.chunkSegments[snapshot.currentChunk] ?? 0;
121
+ setState({
122
+ phase: "reading",
123
+ playing: snapshot.isPlaying,
124
+ waiting: snapshot.waitingForAudio,
125
+ segment,
126
+ segments: current2.segments.length
127
+ });
128
+ };
129
+ const player = ProgressivePlayer.create(onChange);
130
+ if (!player) {
131
+ setState({ phase: "error", message: "Audio playback is not available." });
132
+ return;
133
+ }
134
+ player.prime();
135
+ const current = {
136
+ player,
137
+ chunkSegments: [],
138
+ segments: [],
139
+ handle: null,
140
+ cancelled: false,
141
+ userPaused: false
142
+ };
143
+ session.current = current;
144
+ setState({ phase: "loading" });
145
+ void (async () => {
146
+ const plan = await planSpeech(markdown);
147
+ if (current.cancelled) return;
148
+ current.segments = plan.segments;
149
+ if (plan.segments.length === 0) {
150
+ stop();
151
+ setState({ phase: "error", message: "There is nothing to read aloud." });
152
+ return;
153
+ }
154
+ const totalCharacters = plan.segments.reduce((sum, s) => sum + s.text.length, 0);
155
+ const gate = new AutoPlayGate();
156
+ let spokenCharacters = 0;
157
+ let chunkIndex = 0;
158
+ for (const [segmentIndex, segment] of plan.segments.entries()) {
159
+ if (current.cancelled) return;
160
+ const handle = synthesize({ text: segment.text }, (event) => {
161
+ if (current.cancelled || event.kind !== "chunk") return;
162
+ current.chunkSegments[chunkIndex] = segmentIndex;
163
+ player.append({ ...event.chunk, index: chunkIndex });
164
+ chunkIndex += 1;
165
+ const snapshot = player.snapshot();
166
+ const progress = {
167
+ completedCharacters: spokenCharacters + event.chunk.textEnd,
168
+ totalCharacters
169
+ };
170
+ if (!snapshot.isPlaying && !current.userPaused && gate.ready(snapshot, progress, performance.now())) {
171
+ void player.play();
172
+ }
173
+ });
174
+ current.handle = handle;
175
+ const result = await handle.done;
176
+ if (current.cancelled) return;
177
+ if (!result.ok) {
178
+ stop();
179
+ setState({ phase: "error", message: result.error.message });
180
+ return;
181
+ }
182
+ spokenCharacters += segment.text.length;
183
+ }
184
+ current.handle = null;
185
+ player.finish();
186
+ if (!player.snapshot().isPlaying && !current.userPaused) void player.play();
187
+ })().catch((error) => {
188
+ if (current.cancelled) return;
189
+ stop();
190
+ setState({
191
+ phase: "error",
192
+ message: error instanceof Error ? error.message : "Reading aloud failed."
193
+ });
194
+ });
195
+ },
196
+ [speech, stop]
197
+ );
198
+ const pause = useCallback(() => {
199
+ const current = session.current;
200
+ if (!current) return;
201
+ current.userPaused = true;
202
+ current.player.pause();
203
+ }, []);
204
+ const resume = useCallback(() => {
205
+ const current = session.current;
206
+ if (!current) return;
207
+ current.userPaused = false;
208
+ void current.player.play();
209
+ }, []);
210
+ return { state, start, pause, resume, stop };
211
+ }
212
+
213
+ // src/Speech/SpeechToolbarControl.tsx
214
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
215
+ function useSpeechMenuCommands(readAloud, readCurrent) {
216
+ const { dictation } = useEditorContext2();
217
+ const latest = useRef2({ dictation, readAloud, readCurrent });
218
+ latest.current = { dictation, readAloud, readCurrent };
219
+ useEffect2(() => {
220
+ const host = maybeGetDocBlocksHost();
221
+ if (!hostSupports("menuCommands") || !host?.onMenuCommand) return;
222
+ return host.onMenuCommand((command) => {
223
+ const { dictation: control, readAloud: reader, readCurrent: read } = latest.current;
224
+ if (command === "edit:toggleDictation") control?.toggle();
225
+ if (command === "edit:readAloud" && reader) {
226
+ if (reader.state.phase === "idle" || reader.state.phase === "error") read();
227
+ else reader.stop();
228
+ }
229
+ });
230
+ }, []);
231
+ }
232
+ function SpeechMenuBridge() {
233
+ useSpeechMenuCommands(null, () => void 0);
234
+ return null;
235
+ }
236
+ function SpeechToolbarControl({
237
+ speech,
238
+ onOpenSettings,
239
+ readOnly = false
240
+ }) {
241
+ const { editorMode, getSelection, markdownSource, selectionVersion, activeView } = useEditorContext2();
242
+ const status = useSpeechStatus(speech);
243
+ const readAloud = useReadAloud(speech);
244
+ const [menuOpen, setMenuOpen] = useState2(false);
245
+ const [narrationOpen, setNarrationOpen] = useState2(false);
246
+ const rootRef = useRef2(null);
247
+ const { menuRef, triggerRef, handleMenuKeyDown, handleTriggerKeyDown, closeMenu } = useMenuKeyboard(menuOpen, setMenuOpen);
248
+ const sourceRef = useRef2(markdownSource);
249
+ sourceRef.current = markdownSource;
250
+ void activeView;
251
+ void selectionVersion;
252
+ const selection = getSelection();
253
+ const hasSelection = Boolean(selection && !selection.empty && selection.text.trim());
254
+ const readCurrent = () => {
255
+ const current = getSelection();
256
+ const text = current && !current.empty && current.text.trim() ? current.text : sourceRef.current;
257
+ readAloud.start(text);
258
+ };
259
+ useSpeechMenuCommands(readAloud, readCurrent);
260
+ useEffect2(() => {
261
+ if (!menuOpen) return;
262
+ const onPointerDown = (event) => {
263
+ if (rootRef.current && !rootRef.current.contains(event.target)) setMenuOpen(false);
264
+ };
265
+ document.addEventListener("pointerdown", onPointerDown);
266
+ return () => document.removeEventListener("pointerdown", onPointerDown);
267
+ }, [menuOpen]);
268
+ if (editorMode !== "markdown" || !speech.synthesize) return null;
269
+ const ttsReady = status?.tts.state === "ready";
270
+ const { state } = readAloud;
271
+ const active = state.phase === "loading" || state.phase === "reading";
272
+ return /* @__PURE__ */ jsxs2("div", { className: "db-toolbar-menu db-speech-toolbar", ref: rootRef, children: [
273
+ /* @__PURE__ */ jsx2(
274
+ "button",
275
+ {
276
+ ref: triggerRef,
277
+ type: "button",
278
+ className: `squisq-toolbar-button${active ? " squisq-toolbar-button--active" : ""}`,
279
+ onClick: () => setMenuOpen((open) => !open),
280
+ onKeyDown: handleTriggerKeyDown,
281
+ "aria-label": "Speech",
282
+ "aria-haspopup": "menu",
283
+ "aria-expanded": menuOpen,
284
+ "data-tooltip": "Speech",
285
+ children: /* @__PURE__ */ jsx2(SpeakerGlyph, {})
286
+ }
287
+ ),
288
+ active && /* @__PURE__ */ jsxs2("div", { className: "db-speech-transport", role: "group", "aria-label": "Reading aloud", children: [
289
+ state.phase === "reading" && state.playing ? /* @__PURE__ */ jsx2(
290
+ "button",
291
+ {
292
+ type: "button",
293
+ className: "squisq-toolbar-button",
294
+ "aria-label": "Pause reading",
295
+ "data-tooltip": "Pause",
296
+ onClick: () => readAloud.pause(),
297
+ children: /* @__PURE__ */ jsx2(PauseGlyph, {})
298
+ }
299
+ ) : /* @__PURE__ */ jsx2(
300
+ "button",
301
+ {
302
+ type: "button",
303
+ className: "squisq-toolbar-button",
304
+ "aria-label": "Resume reading",
305
+ "data-tooltip": "Resume",
306
+ disabled: state.phase !== "reading",
307
+ onClick: () => readAloud.resume(),
308
+ children: /* @__PURE__ */ jsx2(PlayGlyph, {})
309
+ }
310
+ ),
311
+ /* @__PURE__ */ jsx2(
312
+ "button",
313
+ {
314
+ type: "button",
315
+ className: "squisq-toolbar-button",
316
+ "aria-label": "Stop reading",
317
+ "data-tooltip": "Stop",
318
+ onClick: () => readAloud.stop(),
319
+ children: /* @__PURE__ */ jsx2(StopGlyph, {})
320
+ }
321
+ ),
322
+ /* @__PURE__ */ jsx2("span", { className: "db-speech-transport-label", role: "status", "aria-live": "polite", children: state.phase === "loading" ? "Preparing voice\u2026" : state.waiting ? "Generating\u2026" : `${state.segment + 1} / ${state.segments}` })
323
+ ] }),
324
+ menuOpen && /* @__PURE__ */ jsxs2(
325
+ "div",
326
+ {
327
+ ref: menuRef,
328
+ className: "db-toolbar-menu-dropdown db-speech-toolbar-menu",
329
+ role: "menu",
330
+ "aria-label": "Speech",
331
+ onKeyDown: handleMenuKeyDown,
332
+ children: [
333
+ status && !ttsReady && /* @__PURE__ */ jsx2("div", { className: "db-ai-toolbar-status", role: "status", children: status.tts.reason ?? "Narration is not ready." }),
334
+ active ? /* @__PURE__ */ jsx2(
335
+ "button",
336
+ {
337
+ type: "button",
338
+ role: "menuitem",
339
+ tabIndex: -1,
340
+ className: "db-toolbar-menu-item",
341
+ onClick: () => {
342
+ closeMenu(false);
343
+ readAloud.stop();
344
+ },
345
+ children: "Stop reading"
346
+ }
347
+ ) : /* @__PURE__ */ jsx2(
348
+ "button",
349
+ {
350
+ type: "button",
351
+ role: "menuitem",
352
+ tabIndex: -1,
353
+ className: "db-toolbar-menu-item",
354
+ disabled: !ttsReady,
355
+ onClick: () => {
356
+ closeMenu(false);
357
+ readCurrent();
358
+ },
359
+ children: hasSelection ? "Read selection aloud" : "Read aloud"
360
+ }
361
+ ),
362
+ /* @__PURE__ */ jsx2(
363
+ "button",
364
+ {
365
+ type: "button",
366
+ role: "menuitem",
367
+ tabIndex: -1,
368
+ className: "db-toolbar-menu-item",
369
+ disabled: !ttsReady || readOnly,
370
+ onClick: () => {
371
+ closeMenu(false);
372
+ setNarrationOpen(true);
373
+ },
374
+ children: "Generate narration\u2026"
375
+ }
376
+ ),
377
+ status && status.tts.state === "download-required" && /* @__PURE__ */ jsx2(
378
+ "button",
379
+ {
380
+ type: "button",
381
+ role: "menuitem",
382
+ tabIndex: -1,
383
+ className: "db-toolbar-menu-item",
384
+ onClick: () => {
385
+ closeMenu(false);
386
+ onOpenSettings();
387
+ },
388
+ children: "Download voices\u2026"
389
+ }
390
+ ),
391
+ state.phase === "error" && /* @__PURE__ */ jsx2("div", { className: "db-ai-toolbar-status db-settings-ai-error", role: "alert", children: state.message })
392
+ ]
393
+ }
394
+ ),
395
+ narrationOpen && /* @__PURE__ */ jsx2(GenerateNarrationDialog, { speech, onClose: () => setNarrationOpen(false) })
396
+ ] });
397
+ }
398
+ function Glyph({ children }) {
399
+ return /* @__PURE__ */ jsx2(
400
+ "svg",
401
+ {
402
+ viewBox: "0 0 18 18",
403
+ width: "18",
404
+ height: "18",
405
+ fill: "none",
406
+ stroke: "currentColor",
407
+ strokeWidth: "1.35",
408
+ strokeLinecap: "round",
409
+ strokeLinejoin: "round",
410
+ "aria-hidden": "true",
411
+ children
412
+ }
413
+ );
414
+ }
415
+ function SpeakerGlyph() {
416
+ return /* @__PURE__ */ jsxs2(Glyph, { children: [
417
+ /* @__PURE__ */ jsx2("path", { d: "M3 7h2.5L9 4v10l-3.5-3H3z" }),
418
+ /* @__PURE__ */ jsx2("path", { d: "M11.5 6.5a3.5 3.5 0 0 1 0 5" }),
419
+ /* @__PURE__ */ jsx2("path", { d: "M13.5 4.5a6.4 6.4 0 0 1 0 9" })
420
+ ] });
421
+ }
422
+ function PlayGlyph() {
423
+ return /* @__PURE__ */ jsx2(Glyph, { children: /* @__PURE__ */ jsx2("path", { d: "M6 4.5v9l7-4.5z" }) });
424
+ }
425
+ function PauseGlyph() {
426
+ return /* @__PURE__ */ jsx2(Glyph, { children: /* @__PURE__ */ jsx2("path", { d: "M6.5 4.5v9M11.5 4.5v9" }) });
427
+ }
428
+ function StopGlyph() {
429
+ return /* @__PURE__ */ jsx2(Glyph, { children: /* @__PURE__ */ jsx2("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1" }) });
430
+ }
431
+ export {
432
+ SpeechMenuBridge,
433
+ SpeechToolbarControl
434
+ };