@bendyline/docblocks-react 2.7.4 → 2.7.6
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 +59 -28
- package/dist/{AiDiagrams-QZMJNGDT.js → AiDiagrams-VOCEZS6U.js} +4 -2
- package/dist/{AiEditorAssistant-4HFFGVU4.js → AiEditorAssistant-2MW6UYNI.js} +51 -11
- package/dist/{AiStudyDialog-P3E7PJV5.js → AiStudyDialog-DQZVTBI7.js} +14 -4
- package/dist/{AudioExportDialog-IOYIEAER.js → AudioExportDialog-EICCBSSE.js} +1 -1
- package/dist/{ExportToolbarControls-HOV424TO.js → ExportToolbarControls-4LYTXGT3.js} +1 -1
- package/dist/{PresentationMaker-MUMFAXEI.js → PresentationMaker-T526TBRJ.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-45YTIVYS.js +1162 -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-DICE7VBJ.js → chunk-KHQ44ZFA.js} +16 -1
- package/dist/chunk-NOYDVL6M.js +180 -0
- package/dist/{chunk-PNZFIIWW.js → chunk-PRUEYW3G.js} +23 -5
- package/dist/{chunk-7V34VUK6.js → chunk-T42BH6YX.js} +128 -103
- package/dist/editor.js +2 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +817 -710
- package/dist/settings/index.js +2 -1
- package/package.json +8 -8
- package/src/styles/docblocks.css +542 -16
- package/dist/chunk-D4HIOS7E.js +0 -812
- package/dist/{QuickOpenDialog-ENVFGIHS.js → QuickOpenDialog-3QEX7RZG.js} +3 -3
|
@@ -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({
|