@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,253 @@
1
+ import {
2
+ hasDocumentNarration,
3
+ planSpeech
4
+ } from "./chunk-I44HKDQX.js";
5
+ import {
6
+ buildVideoExportDoc
7
+ } from "./chunk-RP3DVNTI.js";
8
+ import {
9
+ Dialog
10
+ } from "./chunk-56KO2LYN.js";
11
+
12
+ // src/Speech/AudioExportDialog.tsx
13
+ import { useEffect, useId, useRef, useState } from "react";
14
+ import { useEditorContext } from "@bendyline/squisq-editor-react";
15
+
16
+ // src/Speech/export-audio.ts
17
+ var AUDIO_FORMAT_LABELS = {
18
+ m4a: "M4A (AAC)",
19
+ "opus-webm": "WebM (Opus)",
20
+ wav: "WAV (uncompressed)"
21
+ };
22
+ var LONG_DOCUMENT_SECONDS = 10 * 60;
23
+ var APPEND_SECONDS = 2;
24
+ function audioExportFilename(selectedFile, extension) {
25
+ const base = (selectedFile ?? "document").split("/").pop() ?? "document";
26
+ return `${base.replace(/\.[^.]+$/u, "") || "document"}.${extension}`;
27
+ }
28
+ async function encoderFor(format, sampleRate, channels) {
29
+ const { createAudioFileEncoder } = await import("@bendyline/squisq-video-react/encoder");
30
+ return createAudioFileEncoder({ format, sampleRate, channels });
31
+ }
32
+ async function finishOrCancel(encoder, signal) {
33
+ if (signal?.aborted) {
34
+ await encoder.cancel().catch(() => void 0);
35
+ throw new DOMException("Audio export cancelled.", "AbortError");
36
+ }
37
+ return encoder.finish();
38
+ }
39
+ async function exportNarrationAudio(input) {
40
+ const [{ renderDocumentAudio }, doc] = await Promise.all([
41
+ import("@bendyline/squisq-video-react/encoder"),
42
+ buildVideoExportDoc(input.markdown, {
43
+ fileName: input.fileName ?? void 0,
44
+ workspaceContainer: input.workspaceContainer
45
+ })
46
+ ]);
47
+ const rendered = await renderDocumentAudio(doc, {
48
+ sampleRate: (doc.duration ?? 0) > LONG_DOCUMENT_SECONDS ? 24e3 : 48e3,
49
+ ...input.processedAudio ? { processedAudio: input.processedAudio } : {},
50
+ async readMedia(src) {
51
+ const url = await input.mediaProvider.resolveUrl(src);
52
+ const response = await fetch(url);
53
+ return response.ok ? response.arrayBuffer() : null;
54
+ },
55
+ ...input.signal ? { signal: input.signal } : {}
56
+ });
57
+ if (!rendered) return null;
58
+ const channels = rendered.numberOfChannels > 1 ? 2 : 1;
59
+ const encoder = await encoderFor(input.format, rendered.sampleRate, channels);
60
+ try {
61
+ const step = Math.round(rendered.sampleRate * APPEND_SECONDS);
62
+ for (let offset = 0; offset < rendered.length; offset += step) {
63
+ if (input.signal?.aborted) break;
64
+ const end = Math.min(rendered.length, offset + step);
65
+ const planar = Array.from(
66
+ { length: channels },
67
+ (_, channel) => rendered.getChannelData(channel).slice(offset, end)
68
+ );
69
+ await encoder.append(planar);
70
+ }
71
+ return { blob: await finishOrCancel(encoder, input.signal), extension: encoder.extension };
72
+ } catch (error) {
73
+ await encoder.cancel().catch(() => void 0);
74
+ throw error;
75
+ }
76
+ }
77
+ async function exportSpokenAudio(input) {
78
+ const synthesize = input.speech.synthesize;
79
+ if (!synthesize) throw new Error("Narration is not available.");
80
+ const plan = await planSpeech(input.markdown);
81
+ if (plan.segments.length === 0) throw new Error("There is nothing to read aloud.");
82
+ const encoder = await encoderFor(input.format, 24e3, 1);
83
+ try {
84
+ let appending = Promise.resolve();
85
+ for (const [index, segment] of plan.segments.entries()) {
86
+ if (input.signal?.aborted) break;
87
+ input.onProgress?.(index, plan.segments.length);
88
+ const handle = synthesize({ text: segment.text }, (event) => {
89
+ if (event.kind !== "chunk") return;
90
+ const samples = new Float32Array(event.chunk.pcm);
91
+ appending = appending.then(() => encoder.append([samples]));
92
+ });
93
+ const onAbort = () => handle.cancel();
94
+ input.signal?.addEventListener("abort", onAbort, { once: true });
95
+ const result = await handle.done;
96
+ input.signal?.removeEventListener("abort", onAbort);
97
+ if (!result.ok) {
98
+ if (result.error.code === "cancelled") break;
99
+ throw new Error(result.error.message);
100
+ }
101
+ }
102
+ await appending;
103
+ input.onProgress?.(plan.segments.length, plan.segments.length);
104
+ return { blob: await finishOrCancel(encoder, input.signal), extension: encoder.extension };
105
+ } catch (error) {
106
+ await encoder.cancel().catch(() => void 0);
107
+ throw error;
108
+ }
109
+ }
110
+
111
+ // src/Speech/AudioExportDialog.tsx
112
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
113
+ function AudioExportDialog({
114
+ speech,
115
+ selectedFile,
116
+ workspaceContainer,
117
+ onSave,
118
+ onClose
119
+ }) {
120
+ const { markdownSource, mediaProvider, mediaEditRenders } = useEditorContext();
121
+ const [formats, setFormats] = useState([]);
122
+ const [format, setFormat] = useState("wav");
123
+ const [state, setState] = useState({ phase: "idle" });
124
+ const abort = useRef(null);
125
+ const formatId = useId();
126
+ const narrated = hasDocumentNarration(markdownSource);
127
+ const canSpeak = Boolean(speech?.synthesize);
128
+ useEffect(() => {
129
+ let live = true;
130
+ void import("@bendyline/squisq-video-react/encoder").then(({ supportedAudioFileFormats }) => supportedAudioFileFormats()).then((supported) => {
131
+ if (!live) return;
132
+ setFormats(supported);
133
+ setFormat(supported.includes("m4a") ? "m4a" : supported[0] ?? "wav");
134
+ });
135
+ return () => {
136
+ live = false;
137
+ abort.current?.abort();
138
+ };
139
+ }, []);
140
+ const busy = state.phase === "working";
141
+ const run = async () => {
142
+ const controller = new AbortController();
143
+ abort.current = controller;
144
+ try {
145
+ let result;
146
+ if (narrated && mediaProvider) {
147
+ setState({ phase: "working", message: "Mixing the narration\u2026", progress: null });
148
+ result = await exportNarrationAudio({
149
+ markdown: markdownSource,
150
+ fileName: selectedFile,
151
+ workspaceContainer,
152
+ mediaProvider,
153
+ ...mediaEditRenders ? { processedAudio: (clip) => mediaEditRenders.processedAudio(clip) } : {},
154
+ format,
155
+ signal: controller.signal
156
+ });
157
+ if (!result) throw new Error("This document has no audio to export.");
158
+ } else {
159
+ if (!speech) throw new Error("Narration is not available.");
160
+ result = await exportSpokenAudio({
161
+ markdown: markdownSource,
162
+ speech,
163
+ format,
164
+ signal: controller.signal,
165
+ onProgress: (done, total) => setState({
166
+ phase: "working",
167
+ message: `Reading part ${Math.min(done + 1, total)} of ${total}\u2026`,
168
+ progress: total > 0 ? done / total : null
169
+ })
170
+ });
171
+ }
172
+ if (controller.signal.aborted) return;
173
+ const filename = audioExportFilename(selectedFile, result.extension);
174
+ setState({ phase: "working", message: "Saving\u2026", progress: null });
175
+ if (!await onSave(result.blob, filename)) {
176
+ setState({ phase: "idle" });
177
+ return;
178
+ }
179
+ setState({ phase: "done", filename });
180
+ } catch (error) {
181
+ if (controller.signal.aborted) {
182
+ setState({ phase: "idle" });
183
+ return;
184
+ }
185
+ setState({
186
+ phase: "error",
187
+ message: error instanceof Error ? error.message : "Audio export failed."
188
+ });
189
+ } finally {
190
+ if (abort.current === controller) abort.current = null;
191
+ }
192
+ };
193
+ const close = () => {
194
+ abort.current?.abort();
195
+ onClose();
196
+ };
197
+ return /* @__PURE__ */ jsx(
198
+ Dialog,
199
+ {
200
+ title: "Export audio",
201
+ onClose: close,
202
+ closeOnBackdrop: !busy,
203
+ footer: state.phase === "done" ? /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, children: "Done" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
204
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: close, children: busy ? "Stop" : "Cancel" }),
205
+ /* @__PURE__ */ jsx(
206
+ "button",
207
+ {
208
+ type: "button",
209
+ disabled: busy || formats.length === 0 || !narrated && !canSpeak,
210
+ onClick: () => void run(),
211
+ children: "Export"
212
+ }
213
+ )
214
+ ] }),
215
+ children: state.phase === "done" ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
216
+ "Saved ",
217
+ state.filename,
218
+ "."
219
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
220
+ /* @__PURE__ */ jsx("p", { children: narrated ? "Exports the document\u2019s narration track, mixed with its other audio, exactly as Play mode plays it." : canSpeak ? "This document has no narration track, so DocBlocks reads it aloud with your narration voice." : "This document has no narration track. Download the narration voices in Settings to export it as speech." }),
221
+ /* @__PURE__ */ jsxs("label", { className: "db-settings-select", htmlFor: formatId, children: [
222
+ /* @__PURE__ */ jsx("span", { className: "db-settings-select-header", children: "Format" }),
223
+ /* @__PURE__ */ jsx(
224
+ "select",
225
+ {
226
+ id: formatId,
227
+ className: "db-settings-select-input",
228
+ value: format,
229
+ disabled: busy,
230
+ onChange: (event) => setFormat(event.currentTarget.value),
231
+ children: formats.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: AUDIO_FORMAT_LABELS[option] }, option))
232
+ }
233
+ )
234
+ ] }),
235
+ state.phase === "working" && /* @__PURE__ */ jsxs("div", { className: "db-settings-ai-download-progress", role: "status", children: [
236
+ /* @__PURE__ */ jsx(
237
+ "progress",
238
+ {
239
+ max: 1,
240
+ value: state.progress ?? void 0,
241
+ "aria-label": "Audio export progress"
242
+ }
243
+ ),
244
+ /* @__PURE__ */ jsx("span", { children: state.message })
245
+ ] }),
246
+ state.phase === "error" && /* @__PURE__ */ jsx("p", { className: "db-settings-ai-error", role: "alert", children: state.message })
247
+ ] })
248
+ }
249
+ );
250
+ }
251
+ export {
252
+ AudioExportDialog
253
+ };
@@ -0,0 +1,111 @@
1
+ import {
2
+ Dialog
3
+ } from "./chunk-56KO2LYN.js";
4
+
5
+ // src/DocBlocksShell/DocumentConflictDialog.tsx
6
+ import { useEffect, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ function formattedTime(value) {
9
+ if (value === null || value === void 0) return null;
10
+ const date = new Date(value);
11
+ return Number.isFinite(date.getTime()) ? date.toLocaleString() : null;
12
+ }
13
+ function DocumentConflictDialog({
14
+ conflict,
15
+ readSavedFile,
16
+ onUseLocal,
17
+ onUseSaved,
18
+ onClose
19
+ }) {
20
+ const [savedFile, setSavedFile] = useState(null);
21
+ const [busy, setBusy] = useState(false);
22
+ const [error, setError] = useState(null);
23
+ useEffect(() => {
24
+ let disposed = false;
25
+ void readSavedFile().then(
26
+ (file) => {
27
+ if (!disposed) setSavedFile(file);
28
+ },
29
+ () => {
30
+ if (!disposed) setSavedFile(null);
31
+ }
32
+ );
33
+ return () => {
34
+ disposed = true;
35
+ };
36
+ }, [readSavedFile, conflict.targetKey, conflict.externalContent, conflict.externalVersion]);
37
+ const capturedAt = formattedTime(conflict.recoveredDraftCapturedAt);
38
+ const savedAt = savedFile?.content === conflict.externalContent ? formattedTime(savedFile?.lastModified) : null;
39
+ const localLabel = conflict.recoveredDraft ? "Recovery draft" : "Your unsaved version";
40
+ const choose = async (resolve) => {
41
+ if (busy) return;
42
+ setBusy(true);
43
+ setError(null);
44
+ try {
45
+ if (await resolve()) onClose();
46
+ else setError("Could not resolve this conflict. Both versions are still available.");
47
+ } catch (cause) {
48
+ setError(cause instanceof Error ? cause.message : "Could not resolve this conflict.");
49
+ } finally {
50
+ setBusy(false);
51
+ }
52
+ };
53
+ return /* @__PURE__ */ jsxs(
54
+ Dialog,
55
+ {
56
+ title: "Compare document versions",
57
+ size: "full",
58
+ className: "db-conflict-dialog",
59
+ onClose: () => {
60
+ if (!busy) onClose();
61
+ },
62
+ closeOnBackdrop: !busy,
63
+ footer: /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: onClose, children: "Decide later" }),
64
+ children: [
65
+ /* @__PURE__ */ jsxs("p", { children: [
66
+ conflict.recoveredDraft ? "This draft was recovered from a previous session. The saved file may contain newer edits." : "The saved file changed while you had unsaved edits.",
67
+ " ",
68
+ "Compare the document text before choosing. Neither version changes until you choose."
69
+ ] }),
70
+ /* @__PURE__ */ jsxs("div", { className: "db-conflict-columns", "aria-busy": busy, children: [
71
+ /* @__PURE__ */ jsxs("section", { className: "db-conflict-version", "aria-label": localLabel, children: [
72
+ /* @__PURE__ */ jsx("h3", { children: localLabel }),
73
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-metadata", children: conflict.recoveredDraft ? capturedAt ? `Recovery copy captured: ${capturedAt}` : "Recovery capture time unavailable" : "Not saved in this session" }),
74
+ /* @__PURE__ */ jsx(
75
+ "textarea",
76
+ {
77
+ className: "db-conflict-preview",
78
+ "aria-label": `${localLabel} preview`,
79
+ value: conflict.localContent,
80
+ readOnly: true,
81
+ spellCheck: false
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-consequence", children: "Replaces the saved file with the draft shown above." }),
85
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => void choose(onUseLocal), children: conflict.recoveredDraft ? "Replace file with draft" : "Keep mine" })
86
+ ] }),
87
+ /* @__PURE__ */ jsxs("section", { className: "db-conflict-version", "aria-label": "Saved file", children: [
88
+ /* @__PURE__ */ jsx("h3", { children: "Saved file" }),
89
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-metadata", children: conflict.externalContent === null ? "File was deleted outside DocBlocks" : savedAt ? `File last modified: ${savedAt}` : "File modification time unavailable" }),
90
+ conflict.externalContent === null ? /* @__PURE__ */ jsx("div", { className: "db-conflict-preview", children: "The saved file no longer exists." }) : /* @__PURE__ */ jsx(
91
+ "textarea",
92
+ {
93
+ className: "db-conflict-preview",
94
+ "aria-label": "Saved file preview",
95
+ value: conflict.externalContent,
96
+ readOnly: true,
97
+ spellCheck: false
98
+ }
99
+ ),
100
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-consequence", children: conflict.externalContent === null ? "Discards the draft and closes this document." : "Discards the draft and opens the saved file shown above." }),
101
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => void choose(onUseSaved), children: conflict.externalContent === null ? "Accept deletion" : "Use saved file" })
102
+ ] })
103
+ ] }),
104
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
105
+ ]
106
+ }
107
+ );
108
+ }
109
+ export {
110
+ DocumentConflictDialog as default
111
+ };
@@ -0,0 +1,9 @@
1
+ import {
2
+ ExportDialog
3
+ } from "./chunk-SGQKTKDX.js";
4
+ import "./chunk-YBEYTVU2.js";
5
+ import "./chunk-X437UAF7.js";
6
+ import "./chunk-56KO2LYN.js";
7
+ export {
8
+ ExportDialog
9
+ };