@bendyline/docblocks-react 2.7.0 → 2.7.2

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 (43) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.txt +98 -48
  3. package/dist/{AiDiagrams-XWOM3YZX.js → AiDiagrams-LFV4YU6W.js} +2 -2
  4. package/dist/{AiEditorAssistant-M4LI2CRY.js → AiEditorAssistant-GN2KXKZD.js} +21 -6
  5. package/dist/{ExportDialog-L6FG6W3H.js → ExportDialog-JRVZCG3J.js} +2 -2
  6. package/dist/{ExportToolbarControls-BRCPZ7H3.js → ExportToolbarControls-2FEIHNMW.js} +28 -18
  7. package/dist/{GitToolbarControl-J4UYZDV6.js → GitToolbarControl-B7KG4MFP.js} +4 -4
  8. package/dist/{GitUI-LZW3IUXO.js → GitUI-RCWUZVS6.js} +2 -2
  9. package/dist/KeyboardShortcutsDialog-NOD4P25G.js +145 -0
  10. package/dist/{PresentationMaker-622NHMIN.js → PresentationMaker-ITNP5SDO.js} +2 -2
  11. package/dist/{Settings-DuXk6baH.d.ts → Settings-RDYvDDI0.d.ts} +13 -11
  12. package/dist/ShareDialog-XUA2OVGI.js +292 -0
  13. package/dist/{SpeechToolbarControl-UZQKGQWD.js → SpeechToolbarControl-CLU7RB2G.js} +2 -2
  14. package/dist/{TextFromNarration-XYRAB6O2.js → TextFromNarration-NITJQ4IB.js} +1 -1
  15. package/dist/{WorkspaceSearchPanel-OUQQ5DOM.js → WorkspaceSearchPanel-J6BKEZQJ.js} +8 -2
  16. package/dist/{chunk-UG4G7PJ4.js → chunk-5O5YTOXG.js} +1 -1
  17. package/dist/{chunk-VO2JYYQZ.js → chunk-6JJOGV6M.js} +5 -13
  18. package/dist/{chunk-X437UAF7.js → chunk-6MSHXQNB.js} +7 -2
  19. package/dist/{chunk-Q5ZVP5LV.js → chunk-BLUEZG7T.js} +40 -32
  20. package/dist/chunk-DICE7VBJ.js +77 -0
  21. package/dist/{chunk-MRUK56JS.js → chunk-GMA4KGGO.js} +7 -3
  22. package/dist/{chunk-BCQZTWE5.js → chunk-HRWEGPTY.js} +18 -4
  23. package/dist/chunk-K7XJDJUJ.js +126 -0
  24. package/dist/{chunk-5OZBGIIT.js → chunk-NAH52736.js} +86 -28
  25. package/dist/{chunk-YS4HVWIO.js → chunk-SPUPWCSI.js} +49 -1
  26. package/dist/{chunk-MMOTEQK6.js → chunk-SSB43524.js} +165 -70
  27. package/dist/{chunk-G4MFB3IO.js → chunk-VG434562.js} +2 -70
  28. package/dist/{chunk-OINKNX26.js → chunk-X3DDTJKR.js} +1 -1
  29. package/dist/{chunk-SGQKTKDX.js → chunk-Y5OE3HJC.js} +14 -4
  30. package/dist/editor.js +7 -5
  31. package/dist/export/index.d.ts +17 -6
  32. package/dist/export/index.js +4 -4
  33. package/dist/index.d.ts +24 -6
  34. package/dist/index.js +452 -247
  35. package/dist/proofing/index.js +11 -1
  36. package/dist/run-export-CS3HRNAZ.js +13 -0
  37. package/dist/settings/index.d.ts +1 -1
  38. package/dist/settings/index.js +1 -1
  39. package/package.json +9 -8
  40. package/src/styles/docblocks.css +481 -271
  41. package/dist/ShareDialog-N7GPM4SB.js +0 -310
  42. package/dist/chunk-EA4BO6E4.js +0 -73
  43. package/dist/run-export-47X5FTYZ.js +0 -11
@@ -22,8 +22,56 @@ function sharedDocumentFilename(selectedFile) {
22
22
  const bounded = stem.replace(/^-+|-+$/gu, "").slice(0, 80) || "shared";
23
23
  return `${bounded}.md`;
24
24
  }
25
+ async function omitSharedImages(markdown) {
26
+ const { parseMarkdown, rewriteMarkdownReferences, splitFrontmatterBlock, walkMarkdownTree } = await import("@bendyline/squisq/markdown");
27
+ const { frontmatter, body } = splitFrontmatterBlock(markdown);
28
+ const imageReferences = /* @__PURE__ */ new Set();
29
+ const linkReferences = /* @__PURE__ */ new Set();
30
+ walkMarkdownTree(parseMarkdown(body, { frontmatter: false }), (node) => {
31
+ if (node.type === "imageReference") imageReferences.add(node.identifier);
32
+ if (node.type === "linkReference") linkReferences.add(node.identifier);
33
+ });
34
+ let sharedBody = rewriteMarkdownReferences(body, { images: "omit" });
35
+ let sharedDocument = parseMarkdown(sharedBody, { frontmatter: false });
36
+ let descriptionContainsImage = false;
37
+ walkMarkdownTree(sharedDocument, (node) => {
38
+ if (node.type === "image" || node.type === "imageReference") descriptionContainsImage = true;
39
+ if (node.type === "imageReference") imageReferences.add(node.identifier);
40
+ if (node.type === "linkReference") linkReferences.add(node.identifier);
41
+ });
42
+ if (descriptionContainsImage) {
43
+ sharedBody = rewriteMarkdownReferences(sharedBody, { images: "omit" });
44
+ sharedDocument = parseMarkdown(sharedBody, { frontmatter: false });
45
+ walkMarkdownTree(sharedDocument, (node) => {
46
+ if (node.type === "image" || node.type === "imageReference") {
47
+ throw new Error(
48
+ "An image description contains image markup that cannot be shared as text."
49
+ );
50
+ }
51
+ });
52
+ }
53
+ if (imageReferences.size > 0) {
54
+ const removals = [];
55
+ walkMarkdownTree(sharedDocument, (node) => {
56
+ if (node.type !== "definition" || !imageReferences.has(node.identifier) || linkReferences.has(node.identifier)) {
57
+ return;
58
+ }
59
+ const start = node.position?.start.offset;
60
+ const end = node.position?.end.offset;
61
+ if (start === void 0 || end === void 0) {
62
+ throw new Error("Cannot locate an image reference in the shared document.");
63
+ }
64
+ removals.push({ start, end });
65
+ });
66
+ for (const { start, end } of removals.sort((a, b) => b.start - a.start)) {
67
+ sharedBody = sharedBody.slice(0, start) + sharedBody.slice(end);
68
+ }
69
+ }
70
+ return (frontmatter ?? "") + sharedBody;
71
+ }
25
72
  async function createSharedDocumentArchive(markdown, selectedFile) {
26
- const encoded = new TextEncoder().encode(markdown);
73
+ const sharedMarkdown = await omitSharedImages(markdown);
74
+ const encoded = new TextEncoder().encode(sharedMarkdown);
27
75
  if (encoded.byteLength > SHARED_DOCUMENT_LIMITS.markdownBytes) {
28
76
  throw new Error(
29
77
  `This document is too large for a shared URL (maximum ${SHARED_DOCUMENT_LIMITS.markdownBytes.toLocaleString()} UTF-8 bytes).`
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  chatOnce
3
- } from "./chunk-5OZBGIIT.js";
3
+ } from "./chunk-NAH52736.js";
4
4
  import {
5
5
  unavailableSentence,
6
6
  useAiStatus
@@ -78,6 +78,7 @@ async function refinePresentationPlan(ai, plan, options = {}) {
78
78
  const passage = plan.sourceText.slice(beat.sourceStart, beat.sourceEnd);
79
79
  if (passage.length > 2400) {
80
80
  retained++;
81
+ options.onBeat?.(index, null);
81
82
  continue;
82
83
  }
83
84
  let replacement = null;
@@ -117,6 +118,7 @@ async function refinePresentationPlan(ai, plan, options = {}) {
117
118
  beats[index] = replacement;
118
119
  refined.push(index);
119
120
  } else retained++;
121
+ options.onBeat?.(index, replacement);
120
122
  }
121
123
  options.onProgress?.(plan.beats.length, plan.beats.length);
122
124
  return {
@@ -136,6 +138,26 @@ var LABELS = {
136
138
  steps: "Steps diagram",
137
139
  image: "Image"
138
140
  };
141
+ function sectionAt(sections, offset) {
142
+ return sections.find((item) => offset >= item.sourceStart && offset < item.sourceEnd);
143
+ }
144
+ function withSuggestion(current, sections, sourceText, item) {
145
+ const parent = sectionAt(sections, item.sourceStart);
146
+ if (!parent) return current;
147
+ const source = sourceText.slice(item.sourceStart, item.sourceEnd);
148
+ const existing = current.blocks.find((block) => block.blockId === parent.blockId);
149
+ const target = {
150
+ ...existing ?? { blockId: parent.blockId },
151
+ ai: [
152
+ ...(existing?.ai ?? []).filter((entry) => entry.source !== source),
153
+ { source, layout: item.layout, headline: item.headline, points: [...item.points] }
154
+ ].slice(-80)
155
+ };
156
+ return {
157
+ ...current,
158
+ blocks: [...current.blocks.filter((block) => block.blockId !== parent.blockId), target]
159
+ };
160
+ }
139
161
  function PresentationMaker({
140
162
  ai,
141
163
  onClose
@@ -145,6 +167,7 @@ function PresentationMaker({
145
167
  function AiRefineButton({
146
168
  ai,
147
169
  disabled,
170
+ buttonRef,
148
171
  onRefine
149
172
  }) {
150
173
  const status = useAiStatus(ai);
@@ -152,6 +175,7 @@ function AiRefineButton({
152
175
  return /* @__PURE__ */ jsx(
153
176
  "button",
154
177
  {
178
+ ref: buttonRef,
155
179
  type: "button",
156
180
  disabled: disabled || !ready,
157
181
  title: ready ? "Suggest layouts and shorter slide wording from the current text." : unavailableSentence(status),
@@ -173,6 +197,9 @@ function PresentationDialog({ ai, onClose }) {
173
197
  const [notice, setNotice] = useState("");
174
198
  const [progress, setProgress] = useState(null);
175
199
  const controller = useRef(null);
200
+ const follow = useRef(false);
201
+ const suggestButton = useRef(null);
202
+ const stopButton = useRef(null);
176
203
  useEffect(() => {
177
204
  let disposed = false;
178
205
  const parsed = markdownToDoc(parseMarkdown(markdownSource));
@@ -187,6 +214,14 @@ function PresentationDialog({ ai, onClose }) {
187
214
  controller.current?.abort();
188
215
  };
189
216
  }, [markdownSource, workspaceContainer]);
217
+ const running = progress !== null;
218
+ const wasRunning = useRef(false);
219
+ useEffect(() => {
220
+ if (wasRunning.current === running) return;
221
+ wasRunning.current = running;
222
+ if (document.activeElement === null || document.activeElement === document.body)
223
+ (running ? stopButton.current : suggestButton.current)?.focus();
224
+ }, [running]);
190
225
  const doc = loaded?.source === markdownSource ? loaded.doc : null;
191
226
  const generated = useMemo(() => {
192
227
  if (!doc) return null;
@@ -219,6 +254,15 @@ function PresentationDialog({ ai, onClose }) {
219
254
  };
220
255
  }
221
256
  }, [doc, plan, preview?.activeTheme]);
257
+ const aiEntries = useMemo(() => {
258
+ const entries = /* @__PURE__ */ new Map();
259
+ for (const block of hints.blocks)
260
+ entries.set(
261
+ block.blockId,
262
+ new Map((block.ai ?? []).map((entry) => [entry.source, entry.layout]))
263
+ );
264
+ return entries;
265
+ }, [hints]);
222
266
  const selectedIndex = Math.max(
223
267
  0,
224
268
  plan?.beats.findIndex(
@@ -226,9 +270,7 @@ function PresentationDialog({ ai, onClose }) {
226
270
  ) ?? -1
227
271
  );
228
272
  const beat = plan?.beats[selectedIndex];
229
- const section = sections.find(
230
- (item) => beat && beat.sourceStart >= item.sourceStart && beat.sourceStart < item.sourceEnd
231
- );
273
+ const section = beat && sectionAt(sections, beat.sourceStart);
232
274
  const hint = hints.blocks.find((item) => item.blockId === section?.blockId);
233
275
  const visual = projection?.blocks.find((block) => block.id === beat?.id);
234
276
  const updateHint = (patch) => {
@@ -250,52 +292,47 @@ function PresentationDialog({ ai, onClose }) {
250
292
  setHints(next);
251
293
  };
252
294
  const refine = async () => {
253
- if (!ai || !plan || progress) return;
295
+ if (!ai || !plan || controller.current) return;
254
296
  const run = new AbortController();
255
297
  controller.current = run;
298
+ follow.current = true;
299
+ const total = plan.beats.length;
300
+ const outcome = { done: 0, refined: 0, retained: 0 };
256
301
  setError(null);
257
302
  setNotice("");
258
303
  try {
259
- const result = await refinePresentationPlan(ai, plan, {
304
+ await refinePresentationPlan(ai, plan, {
260
305
  signal: run.signal,
261
- onProgress: (done, total) => setProgress(`Suggesting slide ${Math.min(done + 1, total)} of ${total}\u2026`)
262
- });
263
- if (!run.signal.aborted) {
264
- setHints((current) => {
265
- const blocks = current.blocks.map((item) => ({
266
- ...item,
267
- ai: item.ai ? [...item.ai] : void 0
268
- }));
269
- for (const index of result.refined) {
270
- const item = result.plan.beats[index];
271
- const parent = sections.find(
272
- (s) => item.sourceStart >= s.sourceStart && item.sourceStart < s.sourceEnd
273
- );
274
- if (!parent) continue;
275
- let target = blocks.find((b) => b.blockId === parent.blockId);
276
- if (!target) {
277
- target = { blockId: parent.blockId, ai: void 0 };
278
- blocks.push(target);
279
- }
280
- const source = plan.sourceText.slice(item.sourceStart, item.sourceEnd);
281
- target.ai = [
282
- ...(target.ai ?? []).filter((v) => v.source !== source),
283
- {
284
- source,
285
- layout: item.layout,
286
- headline: item.headline,
287
- points: item.points
288
- }
289
- ].slice(-80);
306
+ onProgress: (done) => {
307
+ outcome.done = done;
308
+ const next = plan.beats[done];
309
+ setProgress({
310
+ done,
311
+ total,
312
+ active: next ? { sourceStart: next.sourceStart, sourceEnd: next.sourceEnd } : null
313
+ });
314
+ if (next && follow.current) setSelectedSourceStart(next.sourceStart);
315
+ },
316
+ // Each accepted slide reaches the list and preview as soon as it
317
+ // resolves, so stopping early keeps everything suggested so far.
318
+ onBeat: (_index, item) => {
319
+ if (!item) {
320
+ outcome.retained++;
321
+ return;
290
322
  }
291
- return { ...current, blocks };
292
- });
293
- setNotice(
294
- result.retained ? `Kept automatic summaries for ${result.retained} slides because the AI suggestions could not be validated.` : "AI summaries are ready to review. They will be saved with their source passages."
295
- );
296
- }
323
+ outcome.refined++;
324
+ setHints((current) => withSuggestion(current, sections, plan.sourceText, item));
325
+ }
326
+ });
327
+ setNotice(
328
+ outcome.retained ? `Kept automatic summaries for ${outcome.retained} slides because the AI suggestions could not be validated.` : "AI summaries are ready to review. They will be saved with their source passages."
329
+ );
297
330
  } catch (cause) {
298
331
  if (!run.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
332
+ else if (controller.current === run)
333
+ setNotice(
334
+ `Stopped after ${outcome.done} of ${total} slides. ${outcome.refined === 0 ? "No slides changed." : outcome.refined === 1 ? "The one AI suggestion is kept." : `The ${outcome.refined} AI suggestions are kept.`}`
335
+ );
299
336
  } finally {
300
337
  if (controller.current === run) {
301
338
  controller.current = null;
@@ -304,7 +341,7 @@ function PresentationDialog({ ai, onClose }) {
304
341
  }
305
342
  };
306
343
  const apply = () => {
307
- if (!doc || !plan || progress) return;
344
+ if (!doc || !plan || running) return;
308
345
  try {
309
346
  const next = setFrontmatterValues(markdownSource, {
310
347
  [PRESENTATION_HINTS_KEY]: serializePresentationHints(hints),
@@ -328,10 +365,11 @@ function PresentationDialog({ ai, onClose }) {
328
365
  onClose,
329
366
  closeOnBackdrop: false,
330
367
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
331
- progress ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => controller.current?.abort(), children: "Cancel suggestions" }) : /* @__PURE__ */ jsx(
368
+ /* @__PURE__ */ jsx(
332
369
  "button",
333
370
  {
334
371
  type: "button",
372
+ disabled: running,
335
373
  onClick: () => {
336
374
  setHints(defaultPresentationHints());
337
375
  setSelectedSourceStart(0);
@@ -341,8 +379,46 @@ function PresentationDialog({ ai, onClose }) {
341
379
  children: "Reset design"
342
380
  }
343
381
  ),
344
- ai && /* @__PURE__ */ jsx(AiRefineButton, { ai, disabled: !plan || !!progress, onRefine: () => void refine() }),
345
- /* @__PURE__ */ jsx("button", { type: "button", disabled: !plan || !!progress || !!error, onClick: apply, children: "Use dynamic slides" })
382
+ ai && !progress && /* @__PURE__ */ jsx(
383
+ AiRefineButton,
384
+ {
385
+ ai,
386
+ buttonRef: suggestButton,
387
+ disabled: !plan,
388
+ onRefine: () => void refine()
389
+ }
390
+ ),
391
+ ai && progress && /* @__PURE__ */ jsxs(Fragment, { children: [
392
+ /* @__PURE__ */ jsxs("div", { className: "db-presentation-progress", role: "status", children: [
393
+ /* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
394
+ /* @__PURE__ */ jsxs("span", { children: [
395
+ "Suggesting slide ",
396
+ Math.min(progress.done + 1, progress.total),
397
+ " of ",
398
+ progress.total,
399
+ "\u2026"
400
+ ] }),
401
+ /* @__PURE__ */ jsx(
402
+ "progress",
403
+ {
404
+ max: progress.total,
405
+ value: progress.done,
406
+ "aria-label": "Slides suggested so far"
407
+ }
408
+ )
409
+ ] }),
410
+ /* @__PURE__ */ jsx(
411
+ "button",
412
+ {
413
+ ref: stopButton,
414
+ type: "button",
415
+ title: "Stop suggesting. Slides already suggested keep their new wording.",
416
+ onClick: () => controller.current?.abort(),
417
+ children: "Stop"
418
+ }
419
+ )
420
+ ] }),
421
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: !plan || running || !!error, onClick: apply, children: "Use dynamic slides" })
346
422
  ] }),
347
423
  children: [
348
424
  /* @__PURE__ */ jsx("p", { className: "db-presentation-intro", children: "Design how your text is summarized for presentation in slides and video. Note that your document text and narration will remain unchanged." }),
@@ -353,7 +429,7 @@ function PresentationDialog({ ai, onClose }) {
353
429
  "select",
354
430
  {
355
431
  value: hints.density,
356
- disabled: !!progress,
432
+ disabled: running,
357
433
  onChange: (event) => setHints({ ...hints, density: event.target.value }),
358
434
  children: [
359
435
  /* @__PURE__ */ jsx("option", { value: "concise", children: "Concise \u2014 shorter passages per slide" }),
@@ -369,7 +445,7 @@ function PresentationDialog({ ai, onClose }) {
369
445
  {
370
446
  type: "checkbox",
371
447
  checked: hints.inbetweens,
372
- disabled: !!progress,
448
+ disabled: running,
373
449
  onChange: (event) => setHints({ ...hints, inbetweens: event.target.checked })
374
450
  }
375
451
  ),
@@ -377,28 +453,47 @@ function PresentationDialog({ ai, onClose }) {
377
453
  "Add supporting visual slides where useful"
378
454
  ] })
379
455
  ] }),
380
- /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", children: progress ?? (plan && projection?.doc ? `${plan.beats.length} slides \xB7 ${projection.doc.duration.toFixed(1)} seconds \xB7 ${doc?.presentationNarration ? "Follows narration" : "Estimated pacing \u2014 add narration to synchronize"}` : "Preparing dynamic slides\u2026") }),
456
+ /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", children: plan && projection?.doc ? `${plan.beats.length} slides \xB7 ${projection.doc.duration.toFixed(1)} seconds \xB7 ${doc?.presentationNarration ? "Follows narration" : "Estimated pacing \u2014 add narration to synchronize"}` : "Preparing dynamic slides\u2026" }),
381
457
  (error || generated?.error || projection?.error) && /* @__PURE__ */ jsx("p", { role: "alert", children: error ?? generated?.error ?? projection?.error }),
382
458
  notice && /* @__PURE__ */ jsx("p", { role: "status", children: notice }),
383
459
  plan && beat && section && /* @__PURE__ */ jsxs("div", { className: "db-presentation-workspace", children: [
384
- /* @__PURE__ */ jsx("nav", { className: "db-presentation-beats", "aria-label": "Presentation slides", children: plan.beats.map((item, index) => /* @__PURE__ */ jsxs(
385
- "button",
386
- {
387
- type: "button",
388
- "aria-current": index === selectedIndex ? "step" : void 0,
389
- title: item.headline,
390
- onClick: () => setSelectedSourceStart(item.sourceStart),
391
- children: [
392
- /* @__PURE__ */ jsxs("span", { className: "db-presentation-beat-title", children: [
393
- index + 1,
394
- ". ",
395
- item.headline
396
- ] }),
397
- /* @__PURE__ */ jsx("small", { children: LABELS[item.layout] })
398
- ]
399
- },
400
- item.id
401
- )) }),
460
+ /* @__PURE__ */ jsx("nav", { className: "db-presentation-beats", "aria-label": "Presentation slides", children: plan.beats.map((item, index) => {
461
+ const owner = sectionAt(sections, item.sourceStart);
462
+ const layout = owner && aiEntries.get(owner.blockId)?.get(plan.sourceText.slice(item.sourceStart, item.sourceEnd));
463
+ const suggested = layout !== void 0 && (layout !== "image" || !!item.imageSrc);
464
+ const active = progress?.active;
465
+ const pending = !!active && active.sourceStart === item.sourceStart && active.sourceEnd === item.sourceEnd;
466
+ return /* @__PURE__ */ jsxs(
467
+ "button",
468
+ {
469
+ type: "button",
470
+ "aria-current": index === selectedIndex ? "step" : void 0,
471
+ title: item.headline,
472
+ onClick: () => {
473
+ follow.current = false;
474
+ setSelectedSourceStart(item.sourceStart);
475
+ },
476
+ children: [
477
+ /* @__PURE__ */ jsxs("span", { className: "db-presentation-beat-title", children: [
478
+ index + 1,
479
+ ". ",
480
+ item.headline
481
+ ] }),
482
+ /* @__PURE__ */ jsxs("span", { className: "db-presentation-beat-tags", children: [
483
+ /* @__PURE__ */ jsx("small", { children: LABELS[item.layout] }),
484
+ pending ? /* @__PURE__ */ jsxs("small", { className: "db-presentation-beat-ai", children: [
485
+ /* @__PURE__ */ jsx("span", { className: "db-ai-spinner", "aria-hidden": "true" }),
486
+ "Suggesting\u2026"
487
+ ] }) : suggested ? /* @__PURE__ */ jsxs("small", { className: "db-presentation-beat-ai", children: [
488
+ /* @__PURE__ */ jsx("span", { className: "db-visually-hidden", children: "Suggested by " }),
489
+ "AI"
490
+ ] }) : null
491
+ ] })
492
+ ]
493
+ },
494
+ item.id
495
+ );
496
+ }) }),
402
497
  /* @__PURE__ */ jsxs("div", { className: "db-presentation-detail", children: [
403
498
  /* @__PURE__ */ jsx(
404
499
  "div",
@@ -425,7 +520,7 @@ function PresentationDialog({ ai, onClose }) {
425
520
  "select",
426
521
  {
427
522
  value: hint?.layout ?? "",
428
- disabled: !!progress,
523
+ disabled: running,
429
524
  onChange: (event) => updateHint({
430
525
  layout: event.target.value || void 0
431
526
  }),
@@ -444,7 +539,7 @@ function PresentationDialog({ ai, onClose }) {
444
539
  "select",
445
540
  {
446
541
  value: hint?.inbetweens === void 0 ? "" : String(hint.inbetweens),
447
- disabled: !!progress,
542
+ disabled: running,
448
543
  onChange: (event) => updateHint({
449
544
  inbetweens: event.target.value === "" ? void 0 : event.target.value === "true"
450
545
  }),
@@ -474,7 +569,7 @@ function PresentationDialog({ ai, onClose }) {
474
569
  {
475
570
  defaultValue: beat.headline,
476
571
  maxLength: 110,
477
- disabled: !!progress,
572
+ disabled: running,
478
573
  onBlur: (event) => updateHint({
479
574
  wording: {
480
575
  ...hint?.wording?.source === section.text ? hint.wording : {},
@@ -493,7 +588,7 @@ function PresentationDialog({ ai, onClose }) {
493
588
  {
494
589
  rows: 3,
495
590
  defaultValue: beat.points.join("\n"),
496
- disabled: !!progress,
591
+ disabled: running,
497
592
  onBlur: (event) => updateHint({
498
593
  wording: {
499
594
  ...hint?.wording?.source === section.text ? hint.wording : {},
@@ -3,8 +3,8 @@ import { useEffect, useState } from "react";
3
3
 
4
4
  // src/components/FindButton.tsx
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
- function FindButton({ active, onActiveChange, shortcutLabel: shortcutLabel2 }) {
7
- const tooltip = shortcutLabel2 ? `Find in document (${shortcutLabel2})` : "Find in document";
6
+ function FindButton({ active, onActiveChange, shortcutLabel }) {
7
+ const tooltip = shortcutLabel ? `Find in document (${shortcutLabel})` : "Find in document";
8
8
  return /* @__PURE__ */ jsx(
9
9
  "button",
10
10
  {
@@ -45,69 +45,6 @@ function focusFindInput(scope = document) {
45
45
  input?.select();
46
46
  }
47
47
 
48
- // src/DocBlocksShell/keyboard-shortcuts.ts
49
- var SHELL_SHORTCUTS = Object.freeze([
50
- // Must work mid-sentence; that is the entire point of a save shortcut.
51
- { id: "save", key: "s", accel: true, label: "Ctrl+S", whileTyping: true },
52
- // Find is mostly invoked from inside the document being searched.
53
- { id: "find", key: "f", accel: true, label: "Ctrl+F", whileTyping: true },
54
- // Jumping elsewhere is as likely mid-sentence as from the tree.
55
- { id: "quick-open", key: "p", accel: true, label: "Ctrl+P", whileTyping: true },
56
- {
57
- id: "workspace-search",
58
- key: "f",
59
- accel: true,
60
- shift: true,
61
- label: "Ctrl+Shift+F",
62
- whileTyping: true
63
- },
64
- // Deliberately NOT Cmd+B — Squisq owns that for Bold. Cmd+\ is the VS Code
65
- // convention for toggling the side bar.
66
- { id: "toggle-sidebar", key: "\\", accel: true, label: "Ctrl+\\", whileTyping: true },
67
- { id: "focus-files", key: "e", accel: true, shift: true, label: "Ctrl+Shift+E" },
68
- { id: "new-document", key: "n", accel: true, label: "Ctrl+N" },
69
- { id: "new-folder", key: "n", accel: true, shift: true, label: "Ctrl+Shift+N" },
70
- // Ctrl+, (settings) is deliberately absent: AppMenu owns that dialog's state
71
- // internally and exposes no way to open it. Binding the key without first
72
- // giving AppMenu a controlled `settingsOpen` prop would ship a dead shortcut.
73
- { id: "close-overlay", key: "Escape", accel: false, label: "Esc", whileTyping: true }
74
- ]);
75
- function isTypingTarget(target) {
76
- if (!(target instanceof HTMLElement)) return false;
77
- if (target.isContentEditable) return true;
78
- const tag = target.tagName;
79
- if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
80
- return target.closest(".monaco-editor") !== null;
81
- }
82
- function matchShortcut(event, _layoutMode) {
83
- return SHELL_SHORTCUTS.find((binding) => bindingMatches(binding, event))?.id ?? null;
84
- }
85
- function bindingMatches(binding, event) {
86
- if (event.altKey) return false;
87
- const accel = event.ctrlKey || event.metaKey;
88
- const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
89
- if (binding.accel !== accel) return false;
90
- if ((binding.shift ?? false) !== event.shiftKey) return false;
91
- if (binding.key !== key) return false;
92
- return binding.whileTyping === true || !isTypingTarget(event.target);
93
- }
94
- function isFindShortcut(event) {
95
- const binding = SHELL_SHORTCUTS.find((candidate) => candidate.id === "find");
96
- return binding !== void 0 && bindingMatches(binding, event);
97
- }
98
- function findShortcutLabel() {
99
- return shortcutLabel("find", isApplePlatform());
100
- }
101
- function isApplePlatform() {
102
- if (typeof navigator === "undefined") return false;
103
- return /Mac|iPhone|iPad|iPod/u.test(navigator.platform || navigator.userAgent);
104
- }
105
- function shortcutLabel(id, isApple) {
106
- const binding = SHELL_SHORTCUTS.find((candidate) => candidate.id === id);
107
- if (!binding) return "";
108
- return isApple ? binding.label.replace("Ctrl+", "\u2318") : binding.label;
109
- }
110
-
111
48
  // src/editor.ts
112
49
  var PORTRAIT_FORM_FACTOR_QUERY = "(orientation: portrait)";
113
50
  function previewViewportPresetForOrientation(isPortrait) {
@@ -195,11 +132,6 @@ function pickEmptyDocumentPrompt(random = Math.random) {
195
132
  export {
196
133
  FindButton,
197
134
  focusFindInput,
198
- matchShortcut,
199
- isFindShortcut,
200
- findShortcutLabel,
201
- isApplePlatform,
202
- shortcutLabel,
203
135
  PORTRAIT_FORM_FACTOR_QUERY,
204
136
  previewViewportPresetForOrientation,
205
137
  useResponsivePreviewViewportPreset,
@@ -12,7 +12,7 @@ import {
12
12
  realizeDiagram,
13
13
  specSummaryForAvoid,
14
14
  startIllustrationRun
15
- } from "./chunk-5OZBGIIT.js";
15
+ } from "./chunk-NAH52736.js";
16
16
  import {
17
17
  unavailableSentence,
18
18
  useAiStatus
@@ -3,15 +3,15 @@ import {
3
3
  } from "./chunk-YBEYTVU2.js";
4
4
  import {
5
5
  FORMAT_LABELS,
6
+ exportThemeChoices,
6
7
  saveExportOptions
7
- } from "./chunk-X437UAF7.js";
8
+ } from "./chunk-6MSHXQNB.js";
8
9
  import {
9
10
  Dialog
10
11
  } from "./chunk-56KO2LYN.js";
11
12
 
12
13
  // src/Export/ExportDialog.tsx
13
14
  import { useState, useCallback, useEffect, useMemo } from "react";
14
- import { getThemeSummaries } from "@bendyline/squisq/schemas";
15
15
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
16
16
  var FORMATS = ["pdf", "docx", "pptx", "epub", "html", "md"];
17
17
  var FORMAT_CHIP_LABELS = {
@@ -52,7 +52,7 @@ function ChipRadioGroup({
52
52
  options,
53
53
  onChange
54
54
  }) {
55
- return /* @__PURE__ */ jsx("div", { className: "db-export-chips", role: "radiogroup", "aria-label": name, children: options.map((opt) => {
55
+ return /* @__PURE__ */ jsx("div", { className: "db-export-chips", role: "radiogroup", "aria-label": name, children: options.map((opt, index) => {
56
56
  const active = opt.key === value;
57
57
  return /* @__PURE__ */ jsx(
58
58
  "button",
@@ -60,8 +60,17 @@ function ChipRadioGroup({
60
60
  type: "button",
61
61
  role: "radio",
62
62
  "aria-checked": active,
63
+ tabIndex: active ? 0 : -1,
63
64
  className: `db-export-chip${active ? " db-export-chip--active" : ""}`,
64
65
  onClick: () => onChange(opt.key),
66
+ onKeyDown: (event) => {
67
+ const delta = event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : event.key === "ArrowLeft" || event.key === "ArrowUp" ? -1 : 0;
68
+ if (!delta) return;
69
+ event.preventDefault();
70
+ const next = (index + delta + options.length) % options.length;
71
+ onChange(options[next].key);
72
+ event.currentTarget.parentElement?.querySelectorAll('[role="radio"]')[next]?.focus();
73
+ },
65
74
  title: opt.title,
66
75
  children: opt.label
67
76
  },
@@ -71,6 +80,7 @@ function ChipRadioGroup({
71
80
  }
72
81
  function ExportDialog({
73
82
  initial,
83
+ documentThemes,
74
84
  exporting,
75
85
  error,
76
86
  destination,
@@ -88,7 +98,7 @@ function ExportDialog({
88
98
  const [includeLinkedDocs, setIncludeLinkedDocs] = useState(initial.includeLinkedDocs);
89
99
  const [entryAsIndex, setEntryAsIndex] = useState(initial.entryAsIndex);
90
100
  const [transforms, setTransforms] = useState([]);
91
- const themes = getThemeSummaries();
101
+ const themes = useMemo(() => exportThemeChoices(documentThemes), [documentThemes]);
92
102
  const showTheme = format === "docx" || format === "pdf" || format === "pptx" || format === "epub" || format === "html";
93
103
  const showTransform = format === "pptx";
94
104
  const showPageSize = format === "pdf";
package/dist/editor.js CHANGED
@@ -2,17 +2,19 @@ import {
2
2
  EMPTY_DOCUMENT_PROMPTS,
3
3
  FindButton,
4
4
  PORTRAIT_FORM_FACTOR_QUERY,
5
- findShortcutLabel,
6
5
  focusFindInput,
7
- isFindShortcut,
8
6
  pickEmptyDocumentPrompt,
9
7
  previewViewportPresetForOrientation,
10
8
  useResponsivePreviewViewportPreset
11
- } from "./chunk-G4MFB3IO.js";
9
+ } from "./chunk-VG434562.js";
10
+ import {
11
+ findShortcutLabel,
12
+ isFindShortcut
13
+ } from "./chunk-K7XJDJUJ.js";
12
14
  import {
13
15
  PresentationMaker
14
- } from "./chunk-MMOTEQK6.js";
15
- import "./chunk-5OZBGIIT.js";
16
+ } from "./chunk-SSB43524.js";
17
+ import "./chunk-NAH52736.js";
16
18
  import "./chunk-K4I3NJFL.js";
17
19
  import "./chunk-56KO2LYN.js";
18
20
  export {