@bendyline/docblocks-react 2.6.2 → 2.7.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.
- package/README.md +3 -10
- package/THIRD_PARTY_NOTICES.txt +4284 -3651
- package/dist/AiDiagrams-LFV4YU6W.js +13 -0
- package/dist/AiEditorAssistant-GN2KXKZD.js +786 -0
- package/dist/AudioExportDialog-F23QXNPB.js +253 -0
- package/dist/DocumentConflictDialog-5LRVIGXS.js +111 -0
- package/dist/ExportDialog-IMBVGRYG.js +9 -0
- package/dist/{ExportToolbarControls-QYLBRWM7.js → ExportToolbarControls-MUM6HM3K.js} +117 -49
- package/dist/{GitToolbarControl-O5AXBC6T.js → GitToolbarControl-B7KG4MFP.js} +6 -6
- package/dist/{GitUI-L5T3XHZG.js → GitUI-RCWUZVS6.js} +6 -6
- package/dist/PresentationMaker-W2JIBHRN.js +9 -0
- package/dist/QuickOpenDialog-2KK4SEBU.js +356 -0
- package/dist/Settings-DuXk6baH.d.ts +144 -0
- package/dist/ShareDialog-XUA2OVGI.js +292 -0
- package/dist/SpeechToolbarControl-OSQS7NVY.js +434 -0
- package/dist/TextFromNarration-XYRAB6O2.js +598 -0
- package/dist/WorkspaceSearchPanel-BKZJRPMY.js +334 -0
- package/dist/chunk-2IYBBFQX.js +201 -0
- package/dist/{chunk-LG6HAWCK.js → chunk-56KO2LYN.js} +35 -4
- package/dist/chunk-6B7G33D5.js +88 -0
- package/dist/chunk-ARATARVJ.js +40 -0
- package/dist/{chunk-ODYNOPC3.js → chunk-BCQZTWE5.js} +57 -17
- package/dist/{chunk-FMDLZ4ZG.js → chunk-BD54Z5XP.js} +12 -3
- package/dist/chunk-EA4BO6E4.js +73 -0
- package/dist/chunk-EPOLZNFD.js +518 -0
- package/dist/chunk-G4MFB3IO.js +208 -0
- package/dist/{chunk-MRUK56JS.js → chunk-GMA4KGGO.js} +7 -3
- package/dist/chunk-I44HKDQX.js +314 -0
- package/dist/chunk-K4I3NJFL.js +49 -0
- package/dist/chunk-KBDZDFPU.js +61 -0
- package/dist/{chunk-FNSRCCBC.js → chunk-KWG6UP5O.js} +0 -10
- package/dist/chunk-NAH52736.js +1884 -0
- package/dist/chunk-RP3DVNTI.js +62 -0
- package/dist/chunk-SPUPWCSI.js +111 -0
- package/dist/chunk-TBLZBNQF.js +25 -0
- package/dist/{chunk-M5Y5WO7Z.js → chunk-UG4G7PJ4.js} +1 -1
- package/dist/chunk-UONFWKJ7.js +22 -0
- package/dist/chunk-VO2JYYQZ.js +40 -0
- package/dist/chunk-W55E6TE2.js +1714 -0
- package/dist/chunk-X3DDTJKR.js +574 -0
- package/dist/{chunk-EBWYGTN7.js → chunk-X437UAF7.js} +6 -0
- package/dist/editor.d.ts +36 -1
- package/dist/editor.js +16 -1
- package/dist/export/index.d.ts +27 -11
- package/dist/export/index.js +11 -7
- package/dist/index.d.ts +100 -13
- package/dist/index.js +3927 -1638
- package/dist/proofing/index.js +11 -1
- package/dist/{run-export-I2XOVGG4.js → run-export-47X5FTYZ.js} +3 -3
- package/dist/settings/index.d.ts +32 -101
- package/dist/settings/index.js +14 -2
- package/package.json +9 -9
- package/src/styles/docblocks.css +2344 -657
- package/dist/ExportDialog-Q4VWI43H.js +0 -9
- package/dist/ShareDialog-4OX3UPJ4.js +0 -359
- package/dist/chunk-JDJVRDOP.js +0 -92
- package/dist/chunk-Q6XNIKDG.js +0 -669
|
@@ -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-GMA4KGGO.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
|
+
};
|