@bendyline/docblocks-react 2.7.5 → 2.7.7
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/THIRD_PARTY_NOTICES.txt +26 -26
- package/dist/{AiDiagrams-5P657GFC.js → AiDiagrams-7KBAOXPB.js} +4 -2
- package/dist/{AiEditorAssistant-V4MZRC6Q.js → AiEditorAssistant-4B2PFFOT.js} +128 -12
- package/dist/{AiStudyDialog-GSCFTG3G.js → AiStudyDialog-JPVFFZBB.js} +14 -4
- package/dist/{AudioExportDialog-IOYIEAER.js → AudioExportDialog-EICCBSSE.js} +1 -1
- package/dist/{ExportDialog-LGWBXIWN.js → ExportDialog-EYDGTJ3S.js} +1 -1
- package/dist/{ExportToolbarControls-HOV424TO.js → ExportToolbarControls-2XY2CKGH.js} +45 -4
- package/dist/{PresentationMaker-H5B44Q7P.js → PresentationMaker-VYQKNL3L.js} +2 -2
- package/dist/{SpeechToolbarControl-KNI2C4JJ.js → SpeechToolbarControl-E6OSJGHL.js} +217 -71
- package/dist/{TextFromNarration-EYOMW2IW.js → TextFromNarration-ME6FAYWO.js} +1 -1
- package/dist/{WorkspaceSearchPanel-J6BKEZQJ.js → WorkspaceSearchPanel-MEBBTK7F.js} +3 -3
- package/dist/{chunk-I44HKDQX.js → chunk-3TSJQESW.js} +16 -3
- package/dist/chunk-5OIHB3SR.js +1162 -0
- package/dist/chunk-6QHZNL6C.js +208 -0
- package/dist/{chunk-FBPCURCS.js → chunk-7HBKKXRV.js} +291 -82
- package/dist/{chunk-KBDZDFPU.js → chunk-BNKIPCXO.js} +4 -1
- package/dist/{chunk-2IYBBFQX.js → chunk-BZRYQENB.js} +59 -16
- package/dist/{chunk-GFK4TCKN.js → chunk-G63YXKBC.js} +8 -5
- package/dist/{chunk-DICE7VBJ.js → chunk-KHQ44ZFA.js} +16 -1
- package/dist/{chunk-CUCWCZRO.js → chunk-N7AKLJPY.js} +143 -98
- package/dist/{chunk-USKX66SA.js → chunk-T2PA7QEF.js} +1 -1
- package/dist/editor.js +2 -2
- package/dist/export/index.js +1 -1
- package/dist/index.js +232 -134
- package/dist/settings/index.js +2 -1
- package/package.json +8 -8
- package/src/styles/docblocks.css +547 -11
- package/dist/chunk-I4JURPYS.js +0 -812
- package/dist/{QuickOpenDialog-ENVFGIHS.js → QuickOpenDialog-3QEX7RZG.js} +3 -3
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
useRef,
|
|
40
40
|
useMemo
|
|
41
41
|
} from "react";
|
|
42
|
+
import { createPortal } from "react-dom";
|
|
42
43
|
import {
|
|
43
44
|
useEditorContext,
|
|
44
45
|
usePreviewSettings,
|
|
@@ -66,7 +67,7 @@ function saveOpenAfterExport(value) {
|
|
|
66
67
|
// src/Export/ExportToolbarControls.tsx
|
|
67
68
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
68
69
|
var ExportDialog = lazy(
|
|
69
|
-
() => import("./ExportDialog-
|
|
70
|
+
() => import("./ExportDialog-EYDGTJ3S.js").then((module) => ({ default: module.ExportDialog }))
|
|
70
71
|
);
|
|
71
72
|
var ShareDialog = lazy(
|
|
72
73
|
() => import("./ShareDialog-PF7D7ORP.js").then((module) => ({ default: module.ShareDialog }))
|
|
@@ -79,7 +80,7 @@ function loadRunExportModule() {
|
|
|
79
80
|
var ExportCancelledError = class extends Error {
|
|
80
81
|
};
|
|
81
82
|
var AudioExportDialog = lazy(
|
|
82
|
-
() => import("./AudioExportDialog-
|
|
83
|
+
() => import("./AudioExportDialog-EICCBSSE.js").then((module) => ({
|
|
83
84
|
default: module.AudioExportDialog
|
|
84
85
|
}))
|
|
85
86
|
);
|
|
@@ -180,6 +181,8 @@ function ExportToolbarControls({
|
|
|
180
181
|
const [exportError, setExportError] = useState(null);
|
|
181
182
|
const [openAfterSave, setOpenAfterSave] = useState(loadOpenAfterExport);
|
|
182
183
|
const [openError, setOpenError] = useState(null);
|
|
184
|
+
const [exportCompletion, setExportCompletion] = useState(null);
|
|
185
|
+
useEffect(() => setExportCompletion(null), [selectedFile]);
|
|
183
186
|
const handleOpenAfterSaveChange = useCallback((checked) => {
|
|
184
187
|
setOpenAfterSave(checked);
|
|
185
188
|
saveOpenAfterExport(checked);
|
|
@@ -359,8 +362,8 @@ function ExportToolbarControls({
|
|
|
359
362
|
[destinationAdapter]
|
|
360
363
|
);
|
|
361
364
|
const openSaved = useCallback(
|
|
362
|
-
async (target) => {
|
|
363
|
-
if (!openAfterSave || !destinationAdapter?.openSaved) return;
|
|
365
|
+
async (target, requested = false) => {
|
|
366
|
+
if (!requested && !openAfterSave || !destinationAdapter?.openSaved) return;
|
|
364
367
|
try {
|
|
365
368
|
await destinationAdapter.openSaved(target);
|
|
366
369
|
} catch (caught) {
|
|
@@ -376,6 +379,7 @@ function ExportToolbarControls({
|
|
|
376
379
|
setExportError(null);
|
|
377
380
|
setOpenError(null);
|
|
378
381
|
setExportNotice(null);
|
|
382
|
+
setExportCompletion(null);
|
|
379
383
|
try {
|
|
380
384
|
let exportTarget = destinationTarget;
|
|
381
385
|
if (!fileShare && destinationAdapter?.pickBeforeSave) {
|
|
@@ -399,6 +403,12 @@ function ExportToolbarControls({
|
|
|
399
403
|
} : saveBlob
|
|
400
404
|
);
|
|
401
405
|
setDialogOpen(false);
|
|
406
|
+
if (!fileShare) {
|
|
407
|
+
setExportCompletion({
|
|
408
|
+
filename: buildExportFilename(selectedFile, opts),
|
|
409
|
+
target: savedTarget
|
|
410
|
+
});
|
|
411
|
+
}
|
|
402
412
|
if (result.warnings.length > 0) setExportNotice(result.warnings);
|
|
403
413
|
if (savedTarget) await openSaved(savedTarget);
|
|
404
414
|
} catch (caught) {
|
|
@@ -426,6 +436,7 @@ function ExportToolbarControls({
|
|
|
426
436
|
setExportError(null);
|
|
427
437
|
setOpenError(null);
|
|
428
438
|
setExportNotice(null);
|
|
439
|
+
setExportCompletion(null);
|
|
429
440
|
try {
|
|
430
441
|
saveExportOptions(lastOptions);
|
|
431
442
|
let quickTarget = null;
|
|
@@ -450,6 +461,10 @@ function ExportToolbarControls({
|
|
|
450
461
|
savedTarget = await saveToDestination(blob, filename, quickTarget);
|
|
451
462
|
} : saveBlob
|
|
452
463
|
);
|
|
464
|
+
setExportCompletion({
|
|
465
|
+
filename: buildExportFilename(selectedFile, lastOptions),
|
|
466
|
+
target: savedTarget
|
|
467
|
+
});
|
|
453
468
|
if (result.warnings.length > 0) setExportNotice(result.warnings);
|
|
454
469
|
if (savedTarget) await openSaved(savedTarget);
|
|
455
470
|
} catch (caught) {
|
|
@@ -705,6 +720,32 @@ function ExportToolbarControls({
|
|
|
705
720
|
}
|
|
706
721
|
),
|
|
707
722
|
openError && !videoModalOpen && /* @__PURE__ */ jsx(Dialog, { title: "Open saved export", onClose: () => setOpenError(null), children: /* @__PURE__ */ jsx("p", { role: "alert", children: openError }) }),
|
|
723
|
+
exportCompletion && !dialogOpen && !exportError && !openError && !exportNotice && createPortal(
|
|
724
|
+
/* @__PURE__ */ jsxs("aside", { className: "db-export-completion", "aria-label": "Export result", children: [
|
|
725
|
+
/* @__PURE__ */ jsx("p", { role: "status", children: exportCompletion.target ? `Saved ${exportCompletion.target.displayPath}` : !destinationAdapter && !saveBlob ? `Sent ${exportCompletion.filename} to your browser\u2019s downloads.` : `Exported ${exportCompletion.filename}` }),
|
|
726
|
+
/* @__PURE__ */ jsxs("div", { className: "db-export-completion-actions", children: [
|
|
727
|
+
exportCompletion.target && destinationAdapter?.openSaved && /* @__PURE__ */ jsx(
|
|
728
|
+
"button",
|
|
729
|
+
{
|
|
730
|
+
type: "button",
|
|
731
|
+
className: "db-export-btn db-export-btn--primary",
|
|
732
|
+
onClick: () => void openSaved(exportCompletion.target, true),
|
|
733
|
+
children: "Open exported file"
|
|
734
|
+
}
|
|
735
|
+
),
|
|
736
|
+
/* @__PURE__ */ jsx(
|
|
737
|
+
"button",
|
|
738
|
+
{
|
|
739
|
+
type: "button",
|
|
740
|
+
className: "db-export-btn db-export-btn--secondary",
|
|
741
|
+
onClick: () => setExportCompletion(null),
|
|
742
|
+
children: "Dismiss"
|
|
743
|
+
}
|
|
744
|
+
)
|
|
745
|
+
] })
|
|
746
|
+
] }),
|
|
747
|
+
document.body
|
|
748
|
+
),
|
|
708
749
|
exportNotice && !dialogOpen && !exportError && !openError && /* @__PURE__ */ jsx(
|
|
709
750
|
Dialog,
|
|
710
751
|
{
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
hasDocumentNarration,
|
|
6
6
|
planSpeech,
|
|
7
7
|
useGenerateNarration
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3TSJQESW.js";
|
|
9
9
|
import {
|
|
10
10
|
AutoPlayGate,
|
|
11
11
|
ProgressivePlayer
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BZRYQENB.js";
|
|
13
13
|
import {
|
|
14
14
|
useMenuKeyboard
|
|
15
15
|
} from "./chunk-GMA4KGGO.js";
|
|
@@ -89,44 +89,144 @@ function GenerateNarrationDialog({ speech, onClose }) {
|
|
|
89
89
|
|
|
90
90
|
// src/Speech/useReadAloud.ts
|
|
91
91
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
92
|
+
var RESTART_SEGMENT_SECONDS = 2;
|
|
93
|
+
var POLL_MS = 250;
|
|
94
|
+
var SENTENCE_BREAK = /(?<=[.!?…])\s+/u;
|
|
95
|
+
function* upcomingSentences(segments, segment, offset) {
|
|
96
|
+
for (let index = segment; index < segments.length; index += 1) {
|
|
97
|
+
const text = segments[index]?.text ?? "";
|
|
98
|
+
for (const sentence of (index === segment ? text.slice(offset) : text).split(SENTENCE_BREAK)) {
|
|
99
|
+
const length = sentence.trim().length;
|
|
100
|
+
if (length > 0) yield length;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function playIfReady(current) {
|
|
105
|
+
if (current.cancelled || current.userPaused || current.synthesizing === null) return;
|
|
106
|
+
const snapshot = current.player.snapshot();
|
|
107
|
+
if (snapshot.isPlaying) return;
|
|
108
|
+
const ready = current.gate.ready(
|
|
109
|
+
snapshot.bufferedDuration - snapshot.currentTime,
|
|
110
|
+
upcomingSentences(current.segments, current.synthesizing, current.synthesizedThrough),
|
|
111
|
+
performance.now()
|
|
112
|
+
);
|
|
113
|
+
if (ready) void current.player.play();
|
|
114
|
+
}
|
|
115
|
+
function endSession(current) {
|
|
116
|
+
current.cancelled = true;
|
|
117
|
+
current.handle?.cancel();
|
|
118
|
+
if (current.poll) clearInterval(current.poll);
|
|
119
|
+
current.player.dispose();
|
|
120
|
+
}
|
|
121
|
+
function sameState(a, b) {
|
|
122
|
+
if (a.phase === "reading" && b.phase === "reading") {
|
|
123
|
+
return a.playing === b.playing && a.waiting === b.waiting && a.segment === b.segment && a.segments === b.segments;
|
|
124
|
+
}
|
|
125
|
+
return a.phase === b.phase && a.phase !== "error";
|
|
126
|
+
}
|
|
127
|
+
function segmentAt(current, snapshot) {
|
|
128
|
+
const caughtUp = !snapshot.isPlaying && snapshot.currentTime >= snapshot.bufferedDuration - 0.05;
|
|
129
|
+
if (caughtUp && current.synthesizing !== null) return current.synthesizing;
|
|
130
|
+
if (snapshot.currentChunk === null) return current.firstSegment;
|
|
131
|
+
return current.chunkSegments[snapshot.currentChunk] ?? current.firstSegment;
|
|
132
|
+
}
|
|
92
133
|
function useReadAloud(speech) {
|
|
93
134
|
const [state, setState] = useState({ phase: "idle" });
|
|
94
135
|
const session = useRef(null);
|
|
95
136
|
const stop = useCallback(() => {
|
|
96
137
|
const current = session.current;
|
|
97
138
|
session.current = null;
|
|
98
|
-
if (current)
|
|
99
|
-
current.cancelled = true;
|
|
100
|
-
current.handle?.cancel();
|
|
101
|
-
current.player.dispose();
|
|
102
|
-
}
|
|
139
|
+
if (current) endSession(current);
|
|
103
140
|
setState({ phase: "idle" });
|
|
104
141
|
}, []);
|
|
105
142
|
useEffect(() => stop, [stop]);
|
|
143
|
+
const fail = useCallback(
|
|
144
|
+
(message) => {
|
|
145
|
+
stop();
|
|
146
|
+
setState({ phase: "error", message });
|
|
147
|
+
},
|
|
148
|
+
[stop]
|
|
149
|
+
);
|
|
150
|
+
const refresh = useCallback(() => {
|
|
151
|
+
const current = session.current;
|
|
152
|
+
if (!current) return;
|
|
153
|
+
const snapshot = current.player.snapshot();
|
|
154
|
+
if (snapshot.complete && !snapshot.isPlaying && snapshot.currentTime >= snapshot.bufferedDuration - 0.05) {
|
|
155
|
+
session.current = null;
|
|
156
|
+
endSession(current);
|
|
157
|
+
setState({ phase: "idle" });
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (snapshot.isPlaying) current.started = true;
|
|
161
|
+
const next2 = current.started ? {
|
|
162
|
+
phase: "reading",
|
|
163
|
+
playing: !current.userPaused,
|
|
164
|
+
waiting: !current.userPaused && !snapshot.isPlaying,
|
|
165
|
+
segment: segmentAt(current, snapshot),
|
|
166
|
+
segments: current.segments.length
|
|
167
|
+
} : { phase: "loading" };
|
|
168
|
+
setState((previous2) => sameState(previous2, next2) ? previous2 : next2);
|
|
169
|
+
}, []);
|
|
170
|
+
const synthesizeFrom = useCallback(
|
|
171
|
+
async (current, first, synthesize) => {
|
|
172
|
+
if (!synthesize) return;
|
|
173
|
+
current.run += 1;
|
|
174
|
+
const run = current.run;
|
|
175
|
+
const stale = () => current.cancelled || current.run !== run;
|
|
176
|
+
current.handle?.cancel();
|
|
177
|
+
current.handle = null;
|
|
178
|
+
current.firstSegment = first;
|
|
179
|
+
current.synthesizing = first;
|
|
180
|
+
current.segmentStarts.clear();
|
|
181
|
+
current.chunkSegments.length = 0;
|
|
182
|
+
current.gate.restart();
|
|
183
|
+
const { player, gate, segments } = current;
|
|
184
|
+
player.reset();
|
|
185
|
+
let chunkIndex = 0;
|
|
186
|
+
for (let index = first; index < segments.length; index += 1) {
|
|
187
|
+
if (stale()) return;
|
|
188
|
+
current.synthesizing = index;
|
|
189
|
+
current.synthesizedThrough = 0;
|
|
190
|
+
const handle = synthesize({ text: segments[index]?.text ?? "" }, (event) => {
|
|
191
|
+
if (stale() || event.kind !== "chunk") return;
|
|
192
|
+
const { chunk } = event;
|
|
193
|
+
if (!current.segmentStarts.has(index)) {
|
|
194
|
+
current.segmentStarts.set(index, player.snapshot().bufferedDuration);
|
|
195
|
+
}
|
|
196
|
+
current.chunkSegments[chunkIndex] = index;
|
|
197
|
+
player.append({ ...chunk, index: chunkIndex });
|
|
198
|
+
chunkIndex += 1;
|
|
199
|
+
gate.record(
|
|
200
|
+
chunk.textEnd - current.synthesizedThrough,
|
|
201
|
+
chunk.durationSec,
|
|
202
|
+
performance.now()
|
|
203
|
+
);
|
|
204
|
+
current.synthesizedThrough = chunk.textEnd;
|
|
205
|
+
playIfReady(current);
|
|
206
|
+
});
|
|
207
|
+
current.handle = handle;
|
|
208
|
+
const result = await handle.done;
|
|
209
|
+
if (stale()) return;
|
|
210
|
+
if (!result.ok) {
|
|
211
|
+
fail(result.error.message);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
current.handle = null;
|
|
216
|
+
current.synthesizing = null;
|
|
217
|
+
player.finish();
|
|
218
|
+
if (!player.snapshot().isPlaying && !current.userPaused) void player.play();
|
|
219
|
+
},
|
|
220
|
+
[fail]
|
|
221
|
+
);
|
|
106
222
|
const start = useCallback(
|
|
107
223
|
(markdown) => {
|
|
108
224
|
const synthesize = speech?.synthesize;
|
|
109
225
|
if (!synthesize) return;
|
|
110
226
|
stop();
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
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);
|
|
227
|
+
const player = ProgressivePlayer.create(() => {
|
|
228
|
+
if (session.current?.player === player) refresh();
|
|
229
|
+
});
|
|
130
230
|
if (!player) {
|
|
131
231
|
setState({ phase: "error", message: "Audio playback is not available." });
|
|
132
232
|
return;
|
|
@@ -134,12 +234,24 @@ function useReadAloud(speech) {
|
|
|
134
234
|
player.prime();
|
|
135
235
|
const current = {
|
|
136
236
|
player,
|
|
137
|
-
|
|
237
|
+
gate: new AutoPlayGate(),
|
|
138
238
|
segments: [],
|
|
239
|
+
run: 0,
|
|
240
|
+
firstSegment: 0,
|
|
241
|
+
synthesizing: null,
|
|
242
|
+
synthesizedThrough: 0,
|
|
243
|
+
poll: null,
|
|
244
|
+
segmentStarts: /* @__PURE__ */ new Map(),
|
|
245
|
+
chunkSegments: [],
|
|
139
246
|
handle: null,
|
|
247
|
+
started: false,
|
|
140
248
|
cancelled: false,
|
|
141
249
|
userPaused: false
|
|
142
250
|
};
|
|
251
|
+
current.poll = setInterval(() => {
|
|
252
|
+
playIfReady(current);
|
|
253
|
+
if (session.current === current) refresh();
|
|
254
|
+
}, POLL_MS);
|
|
143
255
|
session.current = current;
|
|
144
256
|
setState({ phase: "loading" });
|
|
145
257
|
void (async () => {
|
|
@@ -147,67 +259,65 @@ function useReadAloud(speech) {
|
|
|
147
259
|
if (current.cancelled) return;
|
|
148
260
|
current.segments = plan.segments;
|
|
149
261
|
if (plan.segments.length === 0) {
|
|
150
|
-
|
|
151
|
-
setState({ phase: "error", message: "There is nothing to read aloud." });
|
|
262
|
+
fail("There is nothing to read aloud.");
|
|
152
263
|
return;
|
|
153
264
|
}
|
|
154
|
-
|
|
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();
|
|
265
|
+
await synthesizeFrom(current, 0, synthesize);
|
|
187
266
|
})().catch((error) => {
|
|
188
267
|
if (current.cancelled) return;
|
|
189
|
-
|
|
190
|
-
setState({
|
|
191
|
-
phase: "error",
|
|
192
|
-
message: error instanceof Error ? error.message : "Reading aloud failed."
|
|
193
|
-
});
|
|
268
|
+
fail(error instanceof Error ? error.message : "Reading aloud failed.");
|
|
194
269
|
});
|
|
195
270
|
},
|
|
196
|
-
[speech, stop]
|
|
271
|
+
[speech, stop, fail, refresh, synthesizeFrom]
|
|
197
272
|
);
|
|
198
273
|
const pause = useCallback(() => {
|
|
199
274
|
const current = session.current;
|
|
200
275
|
if (!current) return;
|
|
201
276
|
current.userPaused = true;
|
|
202
277
|
current.player.pause();
|
|
203
|
-
|
|
278
|
+
refresh();
|
|
279
|
+
}, [refresh]);
|
|
204
280
|
const resume = useCallback(() => {
|
|
205
281
|
const current = session.current;
|
|
206
282
|
if (!current) return;
|
|
207
283
|
current.userPaused = false;
|
|
284
|
+
current.player.prime();
|
|
208
285
|
void current.player.play();
|
|
209
|
-
|
|
210
|
-
|
|
286
|
+
refresh();
|
|
287
|
+
}, [refresh]);
|
|
288
|
+
const skip = useCallback(
|
|
289
|
+
(direction) => {
|
|
290
|
+
const current = session.current;
|
|
291
|
+
if (!current || current.segments.length === 0) return;
|
|
292
|
+
current.player.prime();
|
|
293
|
+
const snapshot = current.player.snapshot();
|
|
294
|
+
const segment = segmentAt(current, snapshot);
|
|
295
|
+
const segmentStart = current.segmentStarts.get(segment);
|
|
296
|
+
const restart = direction < 0 && segmentStart !== void 0 && snapshot.currentTime - segmentStart > RESTART_SEGMENT_SECONDS;
|
|
297
|
+
const target = restart ? segment : segment + direction;
|
|
298
|
+
if (target < 0 || target >= current.segments.length) {
|
|
299
|
+
if (target < 0 && segmentStart !== void 0) current.player.seek(segmentStart);
|
|
300
|
+
refresh();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const start2 = current.segmentStarts.get(target);
|
|
304
|
+
if (start2 !== void 0) {
|
|
305
|
+
current.player.seek(start2);
|
|
306
|
+
} else if (target === current.synthesizing) {
|
|
307
|
+
current.player.seek(snapshot.bufferedDuration);
|
|
308
|
+
} else {
|
|
309
|
+
void synthesizeFrom(current, target, speech?.synthesize).catch((error) => {
|
|
310
|
+
if (current.cancelled) return;
|
|
311
|
+
fail(error instanceof Error ? error.message : "Reading aloud failed.");
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
refresh();
|
|
315
|
+
},
|
|
316
|
+
[speech, fail, refresh, synthesizeFrom]
|
|
317
|
+
);
|
|
318
|
+
const previous = useCallback(() => skip(-1), [skip]);
|
|
319
|
+
const next = useCallback(() => skip(1), [skip]);
|
|
320
|
+
return { state, start, pause, resume, previous, next, stop };
|
|
211
321
|
}
|
|
212
322
|
|
|
213
323
|
// src/Speech/SpeechToolbarControl.tsx
|
|
@@ -286,6 +396,18 @@ function SpeechToolbarControl({
|
|
|
286
396
|
}
|
|
287
397
|
),
|
|
288
398
|
active && /* @__PURE__ */ jsxs2("div", { className: "db-speech-transport", role: "group", "aria-label": "Reading aloud", children: [
|
|
399
|
+
/* @__PURE__ */ jsx2(
|
|
400
|
+
"button",
|
|
401
|
+
{
|
|
402
|
+
type: "button",
|
|
403
|
+
className: "squisq-toolbar-button",
|
|
404
|
+
"aria-label": "Previous segment",
|
|
405
|
+
"data-tooltip": "Previous segment",
|
|
406
|
+
disabled: state.phase !== "reading",
|
|
407
|
+
onClick: () => readAloud.previous(),
|
|
408
|
+
children: /* @__PURE__ */ jsx2(PreviousGlyph, {})
|
|
409
|
+
}
|
|
410
|
+
),
|
|
289
411
|
state.phase === "reading" && state.playing ? /* @__PURE__ */ jsx2(
|
|
290
412
|
"button",
|
|
291
413
|
{
|
|
@@ -308,6 +430,18 @@ function SpeechToolbarControl({
|
|
|
308
430
|
children: /* @__PURE__ */ jsx2(PlayGlyph, {})
|
|
309
431
|
}
|
|
310
432
|
),
|
|
433
|
+
/* @__PURE__ */ jsx2(
|
|
434
|
+
"button",
|
|
435
|
+
{
|
|
436
|
+
type: "button",
|
|
437
|
+
className: "squisq-toolbar-button",
|
|
438
|
+
"aria-label": "Next segment",
|
|
439
|
+
"data-tooltip": "Next segment",
|
|
440
|
+
disabled: state.phase !== "reading" || state.segment >= state.segments - 1,
|
|
441
|
+
onClick: () => readAloud.next(),
|
|
442
|
+
children: /* @__PURE__ */ jsx2(NextGlyph, {})
|
|
443
|
+
}
|
|
444
|
+
),
|
|
311
445
|
/* @__PURE__ */ jsx2(
|
|
312
446
|
"button",
|
|
313
447
|
{
|
|
@@ -425,6 +559,18 @@ function PlayGlyph() {
|
|
|
425
559
|
function PauseGlyph() {
|
|
426
560
|
return /* @__PURE__ */ jsx2(Glyph, { children: /* @__PURE__ */ jsx2("path", { d: "M6.5 4.5v9M11.5 4.5v9" }) });
|
|
427
561
|
}
|
|
562
|
+
function PreviousGlyph() {
|
|
563
|
+
return /* @__PURE__ */ jsxs2(Glyph, { children: [
|
|
564
|
+
/* @__PURE__ */ jsx2("path", { d: "M5 4.5v9" }),
|
|
565
|
+
/* @__PURE__ */ jsx2("path", { d: "M13 4.5v9L6.5 9z" })
|
|
566
|
+
] });
|
|
567
|
+
}
|
|
568
|
+
function NextGlyph() {
|
|
569
|
+
return /* @__PURE__ */ jsxs2(Glyph, { children: [
|
|
570
|
+
/* @__PURE__ */ jsx2("path", { d: "M13 4.5v9" }),
|
|
571
|
+
/* @__PURE__ */ jsx2("path", { d: "M5 4.5v9L11.5 9z" })
|
|
572
|
+
] });
|
|
573
|
+
}
|
|
428
574
|
function StopGlyph() {
|
|
429
575
|
return /* @__PURE__ */ jsx2(Glyph, { children: /* @__PURE__ */ jsx2("rect", { x: "5", y: "5", width: "8", height: "8", rx: "1" }) });
|
|
430
576
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FilesIcon
|
|
3
|
+
} from "./chunk-KHQ44ZFA.js";
|
|
1
4
|
import {
|
|
2
5
|
HighlightedText
|
|
3
6
|
} from "./chunk-TBLZBNQF.js";
|
|
4
|
-
import {
|
|
5
|
-
FilesIcon
|
|
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";
|
|
@@ -12,6 +12,14 @@ var FENCE = /^(?<indent> {0,3})(?<marker>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<indent>
|
|
|
12
12
|
function stripFencedCode(markdown) {
|
|
13
13
|
return markdown.replace(FENCE, "");
|
|
14
14
|
}
|
|
15
|
+
function joinSoftBreaks(node) {
|
|
16
|
+
if (typeof node !== "object" || node === null) return;
|
|
17
|
+
const { type, value, children } = node;
|
|
18
|
+
if (type === "text" && typeof value === "string") {
|
|
19
|
+
node.value = value.replace(/\n/gu, " ");
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(children)) for (const child of children) joinSoftBreaks(child);
|
|
22
|
+
}
|
|
15
23
|
function ensureSentenceEnd(text) {
|
|
16
24
|
const trimmed = text.trimEnd();
|
|
17
25
|
if (!trimmed) return trimmed;
|
|
@@ -54,11 +62,16 @@ async function planSpeech(markdown) {
|
|
|
54
62
|
import("@bendyline/squisq/doc"),
|
|
55
63
|
import("@bendyline/squisq/narration")
|
|
56
64
|
]);
|
|
57
|
-
const
|
|
65
|
+
const source = stripFencedCode(markdown);
|
|
66
|
+
const doc = markdownToDoc(parseMarkdown(source));
|
|
58
67
|
const script = buildNarrationScript(doc);
|
|
68
|
+
const joined = parseMarkdown(source);
|
|
69
|
+
joinSoftBreaks(joined);
|
|
70
|
+
const joinedText = buildNarrationScript(markdownToDoc(joined)).sourceText;
|
|
71
|
+
const spoken = joinedText.length === script.sourceText.length ? joinedText : script.sourceText;
|
|
59
72
|
const segments = [];
|
|
60
73
|
for (const block of script.blocks) {
|
|
61
|
-
const text =
|
|
74
|
+
const text = spoken.slice(block.charStart, block.charEnd);
|
|
62
75
|
let bodyStart = block.charStart;
|
|
63
76
|
const heading = block.heading?.trim();
|
|
64
77
|
if (heading && text.startsWith(heading)) {
|
|
@@ -72,7 +85,7 @@ async function planSpeech(markdown) {
|
|
|
72
85
|
bodyStart = block.charStart + heading.length;
|
|
73
86
|
}
|
|
74
87
|
const lines = /[^\n]+/gu;
|
|
75
|
-
const body =
|
|
88
|
+
const body = spoken.slice(bodyStart, block.charEnd);
|
|
76
89
|
for (const line of body.matchAll(lines)) {
|
|
77
90
|
for (const piece of splitLongText(line[0], bodyStart + line.index)) {
|
|
78
91
|
segments.push({
|