@bendyline/docblocks-react 2.7.5 → 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.
Files changed (26) hide show
  1. package/THIRD_PARTY_NOTICES.txt +14 -14
  2. package/dist/{AiDiagrams-5P657GFC.js → AiDiagrams-VOCEZS6U.js} +4 -2
  3. package/dist/{AiEditorAssistant-V4MZRC6Q.js → AiEditorAssistant-2MW6UYNI.js} +51 -11
  4. package/dist/{AiStudyDialog-GSCFTG3G.js → AiStudyDialog-DQZVTBI7.js} +14 -4
  5. package/dist/{AudioExportDialog-IOYIEAER.js → AudioExportDialog-EICCBSSE.js} +1 -1
  6. package/dist/{ExportToolbarControls-HOV424TO.js → ExportToolbarControls-4LYTXGT3.js} +1 -1
  7. package/dist/{PresentationMaker-H5B44Q7P.js → PresentationMaker-T526TBRJ.js} +2 -2
  8. package/dist/{SpeechToolbarControl-KNI2C4JJ.js → SpeechToolbarControl-E6OSJGHL.js} +217 -71
  9. package/dist/{TextFromNarration-EYOMW2IW.js → TextFromNarration-ME6FAYWO.js} +1 -1
  10. package/dist/{WorkspaceSearchPanel-J6BKEZQJ.js → WorkspaceSearchPanel-MEBBTK7F.js} +3 -3
  11. package/dist/{chunk-I44HKDQX.js → chunk-3TSJQESW.js} +16 -3
  12. package/dist/chunk-45YTIVYS.js +1162 -0
  13. package/dist/{chunk-FBPCURCS.js → chunk-7HBKKXRV.js} +291 -82
  14. package/dist/{chunk-KBDZDFPU.js → chunk-BNKIPCXO.js} +4 -1
  15. package/dist/{chunk-2IYBBFQX.js → chunk-BZRYQENB.js} +59 -16
  16. package/dist/{chunk-DICE7VBJ.js → chunk-KHQ44ZFA.js} +16 -1
  17. package/dist/chunk-NOYDVL6M.js +180 -0
  18. package/dist/{chunk-USKX66SA.js → chunk-PRUEYW3G.js} +1 -1
  19. package/dist/{chunk-CUCWCZRO.js → chunk-T42BH6YX.js} +122 -91
  20. package/dist/editor.js +2 -2
  21. package/dist/index.js +20 -19
  22. package/dist/settings/index.js +2 -1
  23. package/package.json +5 -5
  24. package/src/styles/docblocks.css +501 -11
  25. package/dist/chunk-I4JURPYS.js +0 -812
  26. 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-I44HKDQX.js";
8
+ } from "./chunk-3TSJQESW.js";
9
9
  import {
10
10
  AutoPlayGate,
11
11
  ProgressivePlayer
12
- } from "./chunk-2IYBBFQX.js";
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 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);
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
- chunkSegments: [],
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
- stop();
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
- 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();
265
+ await synthesizeFrom(current, 0, synthesize);
187
266
  })().catch((error) => {
188
267
  if (current.cancelled) return;
189
- stop();
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
- return { state, start, pause, resume, stop };
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
  }
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-6JJOGV6M.js";
4
4
  import {
5
5
  createSpeechInputProvider
6
- } from "./chunk-KBDZDFPU.js";
6
+ } from "./chunk-BNKIPCXO.js";
7
7
  import {
8
8
  unavailableSentence,
9
9
  useAiStatus
@@ -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 doc = markdownToDoc(parseMarkdown(stripFencedCode(markdown)));
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 = script.sourceText.slice(block.charStart, block.charEnd);
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 = script.sourceText.slice(bodyStart, block.charEnd);
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({