@bendyline/docblocks-react 2.7.6 → 2.7.8

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/THIRD_PARTY_NOTICES.txt +205 -35
  2. package/dist/{AiDiagrams-VOCEZS6U.js → AiDiagrams-2IKKSBAU.js} +6 -4
  3. package/dist/{AiEditorAssistant-2MW6UYNI.js → AiEditorAssistant-ANYGVG7P.js} +128 -12
  4. package/dist/AiImagePanel-OXWEFOBC.js +314 -0
  5. package/dist/{AiStudyDialog-DQZVTBI7.js → AiStudyDialog-T3FY74HY.js} +3 -3
  6. package/dist/{AudioExportDialog-EICCBSSE.js → AudioExportDialog-CBMSLQBS.js} +1 -1
  7. package/dist/DocumentConflictDialog-4R4QMKBT.js +217 -0
  8. package/dist/{ExportDialog-LGWBXIWN.js → ExportDialog-FNFD53SY.js} +2 -2
  9. package/dist/{ExportToolbarControls-4LYTXGT3.js → ExportToolbarControls-ZOYH4I36.js} +146 -68
  10. package/dist/{GitUI-F3CNBURE.js → GitUI-3FANM7I3.js} +88 -155
  11. package/dist/{KeyboardShortcutsDialog-HVKDFP5Z.js → KeyboardShortcutsDialog-7XJTDIX2.js} +1 -1
  12. package/dist/{PresentationMaker-T526TBRJ.js → PresentationMaker-COLTVMTS.js} +3 -3
  13. package/dist/{QuickOpenDialog-3QEX7RZG.js → QuickOpenDialog-U2FWCEPA.js} +1 -1
  14. package/dist/{Settings-CfhY489G.d.ts → Settings-ButIB78y.d.ts} +20 -4
  15. package/dist/{ShareDialog-PF7D7ORP.js → ShareDialog-WOWHI6YI.js} +1 -1
  16. package/dist/{SpeechToolbarControl-E6OSJGHL.js → SpeechToolbarControl-BZW6IVZM.js} +1 -1
  17. package/dist/{TextFromNarration-ME6FAYWO.js → TextFromNarration-6BYTUKVZ.js} +2 -2
  18. package/dist/chunk-27E2OON2.js +90 -0
  19. package/dist/{chunk-5O5YTOXG.js → chunk-5YMULR6A.js} +13 -1
  20. package/dist/{chunk-NOYDVL6M.js → chunk-6QHZNL6C.js} +45 -17
  21. package/dist/{chunk-45YTIVYS.js → chunk-A7BK6QFL.js} +363 -26
  22. package/dist/{chunk-PRUEYW3G.js → chunk-BGA5UVKL.js} +58 -17
  23. package/dist/{chunk-T42BH6YX.js → chunk-CXLXGFG2.js} +37 -15
  24. package/dist/{chunk-7CSBRAMT.js → chunk-F7TR5EKX.js} +8 -6
  25. package/dist/chunk-HIKMOKBQ.js +37 -0
  26. package/dist/chunk-OW6JWRAA.js +66 -0
  27. package/dist/{chunk-BNKIPCXO.js → chunk-QXBV3G6T.js} +5 -3
  28. package/dist/{chunk-7HBKKXRV.js → chunk-TN7BY5N3.js} +722 -281
  29. package/dist/{chunk-VJPGZCFS.js → chunk-UYJCL6OW.js} +4 -2
  30. package/dist/{chunk-GFK4TCKN.js → chunk-WDNE5TI3.js} +21 -7
  31. package/dist/{chunk-KBUHWZCF.js → chunk-YX275NPE.js} +1 -1
  32. package/dist/editor.js +3 -3
  33. package/dist/export/index.d.ts +16 -162
  34. package/dist/export/index.js +6 -4
  35. package/dist/image-save-D18dJrUr.d.ts +182 -0
  36. package/dist/index.d.ts +15 -11
  37. package/dist/index.js +1365 -1078
  38. package/dist/{run-export-SPNNH32M.js → run-export-C3PLLWLS.js} +2 -2
  39. package/dist/settings/index.d.ts +1 -1
  40. package/dist/settings/index.js +3 -2
  41. package/package.json +8 -8
  42. package/src/styles/docblocks.css +349 -11
  43. package/dist/DocumentConflictDialog-DZ2BV2IH.js +0 -111
@@ -0,0 +1,217 @@
1
+ import {
2
+ GitDiffView
3
+ } from "./chunk-27E2OON2.js";
4
+ import {
5
+ Dialog
6
+ } from "./chunk-UYJCL6OW.js";
7
+
8
+ // src/DocBlocksShell/DocumentConflictDialog.tsx
9
+ import { Component, useEffect, useMemo, useState } from "react";
10
+ import { resolveFileKind } from "@bendyline/squisq-editor-react";
11
+ import { MediaContext } from "@bendyline/squisq-react";
12
+ import { LinearDocView } from "@bendyline/squisq-react/page";
13
+ import { markdownToDoc, resolveThemeForDoc } from "@bendyline/squisq/doc";
14
+ import { parseMarkdown } from "@bendyline/squisq/markdown";
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ var DIFF_VIEWS = [
17
+ { value: "inline", label: "Inline" },
18
+ { value: "side-by-side", label: "Side by side" },
19
+ { value: "changes", label: "Changes only" }
20
+ ];
21
+ function formattedTime(value) {
22
+ if (value === null || value === void 0) return null;
23
+ const date = new Date(value);
24
+ return Number.isFinite(date.getTime()) ? date.toLocaleString() : null;
25
+ }
26
+ function VersionText({ label, content }) {
27
+ return /* @__PURE__ */ jsx(
28
+ "textarea",
29
+ {
30
+ className: "db-conflict-preview",
31
+ "aria-label": `${label} preview`,
32
+ value: content,
33
+ readOnly: true,
34
+ spellCheck: false
35
+ }
36
+ );
37
+ }
38
+ var RenderBoundary = class extends Component {
39
+ constructor() {
40
+ super(...arguments);
41
+ this.state = { failed: false, resetKey: this.props.resetKey };
42
+ }
43
+ static getDerivedStateFromError() {
44
+ return { failed: true };
45
+ }
46
+ static getDerivedStateFromProps(props, state) {
47
+ return props.resetKey === state.resetKey ? null : { failed: false, resetKey: props.resetKey };
48
+ }
49
+ render() {
50
+ return this.state.failed ? this.props.fallback : this.props.children;
51
+ }
52
+ };
53
+ function RenderedVersion({
54
+ label,
55
+ content,
56
+ defaultThemeId
57
+ }) {
58
+ const rendered = useMemo(() => {
59
+ try {
60
+ const doc = markdownToDoc(parseMarkdown(content));
61
+ return { doc, theme: resolveThemeForDoc(doc, doc.themeId ? void 0 : defaultThemeId) };
62
+ } catch {
63
+ return null;
64
+ }
65
+ }, [content, defaultThemeId]);
66
+ const text = /* @__PURE__ */ jsx(VersionText, { label, content });
67
+ if (!rendered) return text;
68
+ return /* @__PURE__ */ jsx(RenderBoundary, { resetKey: content, fallback: text, children: /* @__PURE__ */ jsx(
69
+ "div",
70
+ {
71
+ className: "db-conflict-preview db-conflict-preview--rendered",
72
+ role: "region",
73
+ "aria-label": `${label} preview`,
74
+ tabIndex: 0,
75
+ style: { background: rendered.theme.colors.background },
76
+ children: /* @__PURE__ */ jsx(
77
+ LinearDocView,
78
+ {
79
+ doc: rendered.doc,
80
+ theme: rendered.theme,
81
+ variant: "document",
82
+ showCover: false,
83
+ thinMargins: true,
84
+ animationsEnabled: false
85
+ }
86
+ )
87
+ }
88
+ ) });
89
+ }
90
+ function DocumentConflictDialog({
91
+ conflict,
92
+ fileName,
93
+ theme,
94
+ defaultThemeId,
95
+ mediaProvider,
96
+ readSavedFile,
97
+ onUseLocal,
98
+ onUseSaved,
99
+ onClose
100
+ }) {
101
+ const [savedFile, setSavedFile] = useState(null);
102
+ const [busy, setBusy] = useState(false);
103
+ const [error, setError] = useState(null);
104
+ const [diffView, setDiffView] = useState("inline");
105
+ useEffect(() => {
106
+ let disposed = false;
107
+ void readSavedFile().then(
108
+ (file) => {
109
+ if (!disposed) setSavedFile(file);
110
+ },
111
+ () => {
112
+ if (!disposed) setSavedFile(null);
113
+ }
114
+ );
115
+ return () => {
116
+ disposed = true;
117
+ };
118
+ }, [readSavedFile, conflict.targetKey, conflict.externalContent, conflict.externalVersion]);
119
+ const capturedAt = formattedTime(conflict.recoveredDraftCapturedAt);
120
+ const savedAt = savedFile?.content === conflict.externalContent ? formattedTime(savedFile?.lastModified) : null;
121
+ const localLabel = conflict.recoveredDraft ? "Recovery draft" : "Your unsaved version";
122
+ const fileKind = resolveFileKind(fileName);
123
+ const version = (label, content) => fileKind.mode === "markdown" ? /* @__PURE__ */ jsx(RenderedVersion, { label, content, defaultThemeId }) : /* @__PURE__ */ jsx(VersionText, { label, content });
124
+ const choose = async (resolve) => {
125
+ if (busy) return;
126
+ setBusy(true);
127
+ setError(null);
128
+ try {
129
+ if (await resolve()) onClose();
130
+ else setError("Could not resolve this conflict. Both versions are still available.");
131
+ } catch (cause) {
132
+ setError(cause instanceof Error ? cause.message : "Could not resolve this conflict.");
133
+ } finally {
134
+ setBusy(false);
135
+ }
136
+ };
137
+ return /* @__PURE__ */ jsxs(
138
+ Dialog,
139
+ {
140
+ title: "Compare document versions",
141
+ size: "full",
142
+ className: "db-conflict-dialog",
143
+ onClose: () => {
144
+ if (!busy) onClose();
145
+ },
146
+ closeOnBackdrop: !busy,
147
+ footer: /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: onClose, children: "Decide later" }),
148
+ children: [
149
+ /* @__PURE__ */ jsxs("p", { children: [
150
+ conflict.recoveredDraft ? "This draft was recovered from a previous session. The saved file may contain newer edits." : "The saved file changed while you had unsaved edits.",
151
+ " ",
152
+ "Compare the two versions before choosing. Neither version changes until you choose."
153
+ ] }),
154
+ /* @__PURE__ */ jsx(MediaContext.Provider, { value: mediaProvider ?? null, children: /* @__PURE__ */ jsxs(
155
+ "div",
156
+ {
157
+ className: diffView === "side-by-side" ? "db-conflict-columns db-conflict-columns--wide-diff" : "db-conflict-columns",
158
+ "aria-busy": busy,
159
+ children: [
160
+ /* @__PURE__ */ jsxs("section", { className: "db-conflict-version", "aria-label": localLabel, children: [
161
+ /* @__PURE__ */ jsx("h3", { children: localLabel }),
162
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-metadata", children: conflict.recoveredDraft ? capturedAt ? `Recovery copy captured: ${capturedAt}` : "Recovery capture time unavailable" : "Not saved in this session" }),
163
+ version(localLabel, conflict.localContent),
164
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-consequence", children: "Replaces the saved file with the draft shown above." }),
165
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => void choose(onUseLocal), children: conflict.recoveredDraft ? "Replace file with draft" : "Keep mine" })
166
+ ] }),
167
+ /* @__PURE__ */ jsxs("section", { className: "db-conflict-diff", "aria-label": "Differences", children: [
168
+ /* @__PURE__ */ jsxs("div", { className: "db-conflict-diff-header", children: [
169
+ /* @__PURE__ */ jsx("h3", { children: "Differences" }),
170
+ /* @__PURE__ */ jsxs("label", { className: "db-conflict-diff-view", children: [
171
+ /* @__PURE__ */ jsx("span", { children: "View" }),
172
+ /* @__PURE__ */ jsx(
173
+ "select",
174
+ {
175
+ value: diffView,
176
+ onChange: (event) => setDiffView(event.currentTarget.value),
177
+ children: DIFF_VIEWS.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
178
+ }
179
+ )
180
+ ] })
181
+ ] }),
182
+ /* @__PURE__ */ jsxs("p", { className: "db-conflict-metadata", children: [
183
+ "\u2212 Only in ",
184
+ localLabel.toLowerCase(),
185
+ " \xB7 + Only in the saved file"
186
+ ] }),
187
+ /* @__PURE__ */ jsx(
188
+ GitDiffView,
189
+ {
190
+ original: conflict.localContent,
191
+ modified: conflict.externalContent ?? "",
192
+ fileName,
193
+ language: fileKind.language,
194
+ theme,
195
+ layout: diffView === "side-by-side" ? "side-by-side" : "inline",
196
+ changesOnly: diffView === "changes"
197
+ }
198
+ )
199
+ ] }),
200
+ /* @__PURE__ */ jsxs("section", { className: "db-conflict-version", "aria-label": "Saved file", children: [
201
+ /* @__PURE__ */ jsx("h3", { children: "Saved file" }),
202
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-metadata", children: conflict.externalContent === null ? "File was deleted outside DocBlocks" : savedAt ? `File last modified: ${savedAt}` : "File modification time unavailable" }),
203
+ conflict.externalContent === null ? /* @__PURE__ */ jsx("div", { className: "db-conflict-preview", children: "The saved file no longer exists." }) : version("Saved file", conflict.externalContent),
204
+ /* @__PURE__ */ jsx("p", { className: "db-conflict-consequence", children: conflict.externalContent === null ? "Discards the draft and closes this document." : "Discards the draft and opens the saved file shown above." }),
205
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => void choose(onUseSaved), children: conflict.externalContent === null ? "Accept deletion" : "Use saved file" })
206
+ ] })
207
+ ]
208
+ }
209
+ ) }),
210
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
211
+ ]
212
+ }
213
+ );
214
+ }
215
+ export {
216
+ DocumentConflictDialog as default
217
+ };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  ExportDialog
3
- } from "./chunk-GFK4TCKN.js";
3
+ } from "./chunk-WDNE5TI3.js";
4
4
  import "./chunk-H3BK5MK6.js";
5
5
  import "./chunk-6MSHXQNB.js";
6
- import "./chunk-VJPGZCFS.js";
6
+ import "./chunk-UYJCL6OW.js";
7
7
  export {
8
8
  ExportDialog
9
9
  };
@@ -13,8 +13,9 @@ import {
13
13
  hostErrorDetail
14
14
  } from "./chunk-UONFWKJ7.js";
15
15
  import {
16
+ PdfReplacementsError,
16
17
  buildExportFilename
17
- } from "./chunk-5O5YTOXG.js";
18
+ } from "./chunk-5YMULR6A.js";
18
19
  import {
19
20
  OpenAfterSaveCheckbox,
20
21
  loadTransformStyleSummaries
@@ -27,7 +28,7 @@ import {
27
28
  } from "./chunk-6MSHXQNB.js";
28
29
  import {
29
30
  Dialog
30
- } from "./chunk-VJPGZCFS.js";
31
+ } from "./chunk-UYJCL6OW.js";
31
32
 
32
33
  // src/Export/ExportToolbarControls.tsx
33
34
  import {
@@ -39,6 +40,7 @@ import {
39
40
  useRef,
40
41
  useMemo
41
42
  } from "react";
43
+ import { createPortal } from "react-dom";
42
44
  import {
43
45
  useEditorContext,
44
46
  usePreviewSettings,
@@ -66,20 +68,20 @@ function saveOpenAfterExport(value) {
66
68
  // src/Export/ExportToolbarControls.tsx
67
69
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
68
70
  var ExportDialog = lazy(
69
- () => import("./ExportDialog-LGWBXIWN.js").then((module) => ({ default: module.ExportDialog }))
71
+ () => import("./ExportDialog-FNFD53SY.js").then((module) => ({ default: module.ExportDialog }))
70
72
  );
71
73
  var ShareDialog = lazy(
72
- () => import("./ShareDialog-PF7D7ORP.js").then((module) => ({ default: module.ShareDialog }))
74
+ () => import("./ShareDialog-WOWHI6YI.js").then((module) => ({ default: module.ShareDialog }))
73
75
  );
74
76
  var runExportModulePromise = null;
75
77
  function loadRunExportModule() {
76
- runExportModulePromise ?? (runExportModulePromise = import("./run-export-SPNNH32M.js"));
78
+ runExportModulePromise ?? (runExportModulePromise = import("./run-export-C3PLLWLS.js"));
77
79
  return runExportModulePromise;
78
80
  }
79
81
  var ExportCancelledError = class extends Error {
80
82
  };
81
83
  var AudioExportDialog = lazy(
82
- () => import("./AudioExportDialog-EICCBSSE.js").then((module) => ({
84
+ () => import("./AudioExportDialog-CBMSLQBS.js").then((module) => ({
83
85
  default: module.AudioExportDialog
84
86
  }))
85
87
  );
@@ -114,6 +116,7 @@ function loadVideoExportModules() {
114
116
  return videoExportModulesPromise;
115
117
  }
116
118
  function exportErrorMessage(caught) {
119
+ if (caught instanceof PdfReplacementsError) return caught.message;
117
120
  const detail = hostErrorDetail(caught);
118
121
  return detail ? `Export failed: ${detail}` : "Export failed. The document could not be exported.";
119
122
  }
@@ -180,11 +183,14 @@ function ExportToolbarControls({
180
183
  const [exportError, setExportError] = useState(null);
181
184
  const [openAfterSave, setOpenAfterSave] = useState(loadOpenAfterExport);
182
185
  const [openError, setOpenError] = useState(null);
186
+ const [exportCompletion, setExportCompletion] = useState(null);
187
+ useEffect(() => setExportCompletion(null), [selectedFile]);
183
188
  const handleOpenAfterSaveChange = useCallback((checked) => {
184
189
  setOpenAfterSave(checked);
185
190
  saveOpenAfterExport(checked);
186
191
  }, []);
187
192
  const [exportNotice, setExportNotice] = useState(null);
193
+ const [pdfReplacements, setPdfReplacements] = useState(null);
188
194
  const [destinationTarget, setDestinationTarget] = useState(null);
189
195
  const [quickDestination, setQuickDestination] = useState(null);
190
196
  const [quickDestinationPending, setQuickDestinationPending] = useState(false);
@@ -359,8 +365,8 @@ function ExportToolbarControls({
359
365
  [destinationAdapter]
360
366
  );
361
367
  const openSaved = useCallback(
362
- async (target) => {
363
- if (!openAfterSave || !destinationAdapter?.openSaved) return;
368
+ async (target, requested = false) => {
369
+ if (!requested && !openAfterSave || !destinationAdapter?.openSaved) return;
364
370
  try {
365
371
  await destinationAdapter.openSaved(target);
366
372
  } catch (caught) {
@@ -371,11 +377,13 @@ function ExportToolbarControls({
371
377
  [destinationAdapter, openAfterSave]
372
378
  );
373
379
  const handleExport = useCallback(
374
- async (opts) => {
380
+ async (opts, allowPdfReplacements = false) => {
375
381
  setExporting(true);
376
382
  setExportError(null);
383
+ setPdfReplacements(null);
377
384
  setOpenError(null);
378
385
  setExportNotice(null);
386
+ setExportCompletion(null);
379
387
  try {
380
388
  let exportTarget = destinationTarget;
381
389
  if (!fileShare && destinationAdapter?.pickBeforeSave) {
@@ -396,12 +404,23 @@ function ExportToolbarControls({
396
404
  if (outcome === "cancelled") throw new ExportCancelledError();
397
405
  } : destinationAdapter ? async (blob, filename) => {
398
406
  savedTarget = await saveToDestination(blob, filename, exportTarget);
399
- } : saveBlob
407
+ } : saveBlob,
408
+ void 0,
409
+ { allowPdfReplacements }
400
410
  );
401
411
  setDialogOpen(false);
412
+ if (!fileShare) {
413
+ setExportCompletion({
414
+ filename: buildExportFilename(selectedFile, opts),
415
+ target: savedTarget
416
+ });
417
+ }
402
418
  if (result.warnings.length > 0) setExportNotice(result.warnings);
403
419
  if (savedTarget) await openSaved(savedTarget);
404
420
  } catch (caught) {
421
+ if (caught instanceof PdfReplacementsError) {
422
+ setPdfReplacements({ options: opts, quick: false });
423
+ }
405
424
  if (!(caught instanceof ExportCancelledError)) setExportError(exportErrorMessage(caught));
406
425
  } finally {
407
426
  setExporting(false);
@@ -419,59 +438,79 @@ function ExportToolbarControls({
419
438
  openSaved
420
439
  ]
421
440
  );
422
- const handleQuickExport = useCallback(async () => {
423
- if (!lastOptions) return;
424
- closeMenu(true);
425
- setExporting(true);
426
- setExportError(null);
427
- setOpenError(null);
428
- setExportNotice(null);
429
- try {
430
- saveExportOptions(lastOptions);
431
- let quickTarget = null;
432
- if (destinationAdapter) {
433
- if (destinationAdapter.pickBeforeSave) {
434
- const filename = buildExportFilename(selectedFile, lastOptions);
435
- quickTarget = await destinationAdapter.pickTarget(filename, quickDestinationTarget);
436
- if (!quickTarget) return;
437
- } else {
438
- if (!quickDestinationTarget) return;
439
- quickTarget = quickDestinationTarget;
441
+ const handleQuickExport = useCallback(
442
+ async (allowPdfReplacements = false) => {
443
+ if (!lastOptions) return;
444
+ closeMenu(true);
445
+ setExporting(true);
446
+ setExportError(null);
447
+ setPdfReplacements(null);
448
+ setOpenError(null);
449
+ setExportNotice(null);
450
+ setExportCompletion(null);
451
+ try {
452
+ saveExportOptions(lastOptions);
453
+ let quickTarget = null;
454
+ if (destinationAdapter) {
455
+ if (destinationAdapter.pickBeforeSave) {
456
+ const filename = buildExportFilename(selectedFile, lastOptions);
457
+ quickTarget = await destinationAdapter.pickTarget(filename, quickDestinationTarget);
458
+ if (!quickTarget) return;
459
+ } else {
460
+ if (!quickDestinationTarget) return;
461
+ quickTarget = quickDestinationTarget;
462
+ }
440
463
  }
464
+ const { runExport } = await loadRunExportModule();
465
+ let savedTarget;
466
+ const result = await runExport(
467
+ markdownSource,
468
+ selectedFile,
469
+ lastOptions,
470
+ mediaContainer,
471
+ destinationAdapter ? async (blob, filename) => {
472
+ savedTarget = await saveToDestination(blob, filename, quickTarget);
473
+ } : saveBlob,
474
+ void 0,
475
+ { allowPdfReplacements }
476
+ );
477
+ setExportCompletion({
478
+ filename: buildExportFilename(selectedFile, lastOptions),
479
+ target: savedTarget
480
+ });
481
+ if (result.warnings.length > 0) setExportNotice(result.warnings);
482
+ if (savedTarget) await openSaved(savedTarget);
483
+ } catch (caught) {
484
+ if (caught instanceof PdfReplacementsError) {
485
+ setPdfReplacements({ options: lastOptions, quick: true });
486
+ }
487
+ if (!(caught instanceof ExportCancelledError)) setExportError(exportErrorMessage(caught));
488
+ } finally {
489
+ setExporting(false);
441
490
  }
442
- const { runExport } = await loadRunExportModule();
443
- let savedTarget;
444
- const result = await runExport(
445
- markdownSource,
446
- selectedFile,
447
- lastOptions,
448
- mediaContainer,
449
- destinationAdapter ? async (blob, filename) => {
450
- savedTarget = await saveToDestination(blob, filename, quickTarget);
451
- } : saveBlob
452
- );
453
- if (result.warnings.length > 0) setExportNotice(result.warnings);
454
- if (savedTarget) await openSaved(savedTarget);
455
- } catch (caught) {
456
- if (!(caught instanceof ExportCancelledError)) setExportError(exportErrorMessage(caught));
457
- } finally {
458
- setExporting(false);
459
- }
460
- }, [
461
- closeMenu,
462
- destinationAdapter,
463
- lastOptions,
464
- markdownSource,
465
- mediaContainer,
466
- saveBlob,
467
- saveToDestination,
468
- selectedFile,
469
- quickDestinationTarget,
470
- openSaved
471
- ]);
491
+ },
492
+ [
493
+ closeMenu,
494
+ destinationAdapter,
495
+ lastOptions,
496
+ markdownSource,
497
+ mediaContainer,
498
+ saveBlob,
499
+ saveToDestination,
500
+ selectedFile,
501
+ quickDestinationTarget,
502
+ openSaved
503
+ ]
504
+ );
472
505
  const handleDismissExportError = useCallback(() => {
473
506
  setExportError(null);
507
+ setPdfReplacements(null);
474
508
  }, []);
509
+ const handleSaveWithReplacements = useCallback(() => {
510
+ if (!pdfReplacements) return;
511
+ if (pdfReplacements.quick) void handleQuickExport(true);
512
+ else void handleExport(pdfReplacements.options, true);
513
+ }, [handleExport, handleQuickExport, pdfReplacements]);
475
514
  const handleDismissExportNotice = useCallback(() => {
476
515
  setExportNotice(null);
477
516
  }, []);
@@ -538,7 +577,7 @@ function ExportToolbarControls({
538
577
  role: "menuitem",
539
578
  tabIndex: -1,
540
579
  className: "db-toolbar-menu-item",
541
- onClick: handleQuickExport,
580
+ onClick: () => void handleQuickExport(),
542
581
  disabled: exporting || Boolean(destinationAdapter) && !quickDestinationTarget,
543
582
  title: quickDestinationTarget ? quickLabel(
544
583
  lastOptions,
@@ -665,6 +704,7 @@ function ExportToolbarControls({
665
704
  documentThemes,
666
705
  exporting,
667
706
  error: exportError,
707
+ errorAction: pdfReplacements ? { label: "Save PDF with replacements", onClick: handleSaveWithReplacements } : null,
668
708
  openAfterSave: !fileShare && destinationAdapter?.openSaved ? { checked: openAfterSave, onChange: handleOpenAfterSaveChange } : void 0,
669
709
  destination: destinationAdapter && destinationAdapter.showDestination !== false ? {
670
710
  value: destinationTarget?.displayPath ?? "",
@@ -692,19 +732,57 @@ function ExportToolbarControls({
692
732
  {
693
733
  title: "Export Document",
694
734
  onClose: handleDismissExportError,
695
- footer: /* @__PURE__ */ jsx(
696
- "button",
697
- {
698
- type: "button",
699
- className: "db-export-btn db-export-btn--secondary",
700
- onClick: handleDismissExportError,
701
- children: "Close"
702
- }
703
- ),
735
+ footer: /* @__PURE__ */ jsxs(Fragment, { children: [
736
+ pdfReplacements && /* @__PURE__ */ jsx(
737
+ "button",
738
+ {
739
+ type: "button",
740
+ className: "db-export-btn db-export-btn--secondary",
741
+ onClick: handleSaveWithReplacements,
742
+ disabled: exporting,
743
+ children: "Save PDF with replacements"
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsx(
747
+ "button",
748
+ {
749
+ type: "button",
750
+ className: "db-export-btn db-export-btn--secondary",
751
+ onClick: handleDismissExportError,
752
+ children: "Close"
753
+ }
754
+ )
755
+ ] }),
704
756
  children: /* @__PURE__ */ jsx("p", { className: "db-export-error", role: "alert", children: exportError })
705
757
  }
706
758
  ),
707
759
  openError && !videoModalOpen && /* @__PURE__ */ jsx(Dialog, { title: "Open saved export", onClose: () => setOpenError(null), children: /* @__PURE__ */ jsx("p", { role: "alert", children: openError }) }),
760
+ exportCompletion && !dialogOpen && !exportError && !openError && !exportNotice && createPortal(
761
+ /* @__PURE__ */ jsxs("aside", { className: "db-export-completion", "aria-label": "Export result", children: [
762
+ /* @__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}` }),
763
+ /* @__PURE__ */ jsxs("div", { className: "db-export-completion-actions", children: [
764
+ exportCompletion.target && destinationAdapter?.openSaved && /* @__PURE__ */ jsx(
765
+ "button",
766
+ {
767
+ type: "button",
768
+ className: "db-export-btn db-export-btn--primary",
769
+ onClick: () => void openSaved(exportCompletion.target, true),
770
+ children: "Open exported file"
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsx(
774
+ "button",
775
+ {
776
+ type: "button",
777
+ className: "db-export-btn db-export-btn--secondary",
778
+ onClick: () => setExportCompletion(null),
779
+ children: "Dismiss"
780
+ }
781
+ )
782
+ ] })
783
+ ] }),
784
+ document.body
785
+ ),
708
786
  exportNotice && !dialogOpen && !exportError && !openError && /* @__PURE__ */ jsx(
709
787
  Dialog,
710
788
  {