collabmd 0.1.47 → 0.1.48

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 (100) hide show
  1. package/README.md +9 -6
  2. package/dist/client/assets/JetBrainsMono-Regular-BQaDgvhP.woff2 +0 -0
  3. package/dist/client/assets/babel-CY5KOv-I.js +16 -0
  4. package/dist/client/assets/comment-ui-shared-DuoyznXW.js +7 -0
  5. package/dist/client/assets/dist-ChRoo3IB.js +9 -0
  6. package/dist/client/assets/document-formatter-Dxl_a8XO.js +2 -0
  7. package/dist/client/assets/{drawio-editor-BjrxTe3l.js → drawio-editor-BVaNNtYp.js} +2 -2
  8. package/dist/client/assets/{drawioEditor-DUVZjSSr.js → drawioEditor-BkIBlXx0.js} +2 -2
  9. package/dist/client/assets/editor-session-DMyVdHLj.js +54 -0
  10. package/dist/client/assets/embedded-editor-base-B3l6Xy65.css +1 -0
  11. package/dist/client/assets/estree-CuT_pB8K.js +44 -0
  12. package/dist/client/assets/{excalidraw-editor-BlcN6umJ.css → excalidraw-editor-BbSbI80a.css} +1 -1
  13. package/dist/client/assets/excalidraw-editor-BjYaNEMY.js +9 -0
  14. package/dist/client/assets/{excalidrawEditor-B0jOK3lv.js → excalidrawEditor-ChqF_d82.js} +2 -2
  15. package/dist/client/assets/export-page-Dyu0XCbu.js +57 -0
  16. package/dist/client/assets/exportDocument-CJBx4Xjx.css +1 -0
  17. package/dist/client/assets/{exportDocument-Dg9MjkIk.js → exportDocument-Dgvfa2Sd.js} +2 -2
  18. package/dist/client/assets/{file-icon-svg-CQAjlWjK.js → file-icon-svg-Y_kpF957.js} +1 -1
  19. package/dist/client/assets/file-kind-BwMIcZbi.js +1 -0
  20. package/dist/client/assets/file-search-DDLyyLDU.js +1 -0
  21. package/dist/client/assets/html-Cdnr2qzU.js +25 -0
  22. package/dist/client/assets/index-CSNIMyOP.css +1 -0
  23. package/dist/client/assets/index-DG3N2Ps-.js +2 -0
  24. package/dist/client/assets/main-D0wCxpVn.js +903 -0
  25. package/dist/client/assets/markdown-CByV-4CU.js +70 -0
  26. package/dist/client/assets/mermaid.core-BJRLUQZE.js +11 -0
  27. package/dist/client/assets/{preview-render-compiler-BApvlLLo.js → preview-render-compiler-CrJIooVD.js} +2 -2
  28. package/dist/client/assets/preview-render-compiler-O0JxBVNF.js +1 -0
  29. package/dist/client/assets/{preview-render-profile-CE-5rVTC.js → preview-render-profile-B4RWYWHt.js} +1 -1
  30. package/dist/client/assets/{preview-render-worker-BmN79bVc.js → preview-render-worker-_QTxqKvP.js} +22 -22
  31. package/dist/client/assets/quick-switcher-controller-CaEjEtq_.js +1 -0
  32. package/dist/client/assets/standalone-6z5x8CWe.js +29 -0
  33. package/dist/client/assets/{vault-api-client-_LpV1aIn.js → vault-api-client-BkwdFjx0.js} +2 -2
  34. package/dist/client/assets/{es-CAsWR6hG.js → vault-utils-DvoSftJT.js} +1 -1
  35. package/dist/client/assets/yaml-9ltHPDNO.js +154 -0
  36. package/dist/client/drawio-editor.html +1 -1
  37. package/dist/client/excalidraw-editor.html +1 -1
  38. package/dist/client/export-document.html +2 -2
  39. package/dist/client/index.html +41 -19
  40. package/package.json +4 -1
  41. package/src/client/app/index.html +39 -17
  42. package/src/client/application/app-shell/export-feature.js +17 -1
  43. package/src/client/application/app-shell/git-feature.js +28 -9
  44. package/src/client/application/app-shell/ui-feature-identity.js +13 -0
  45. package/src/client/application/app-shell/ui-feature-shell.js +164 -9
  46. package/src/client/application/app-shell-elements.js +4 -0
  47. package/src/client/application/workspace-coordinator.js +21 -4
  48. package/src/client/application/workspace-preview-controller.js +120 -2
  49. package/src/client/application/workspace-route-controller.js +4 -1
  50. package/src/client/assets/vendor/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
  51. package/src/client/assets/vendor/jetbrains-mono/OFL.txt +93 -0
  52. package/src/client/bootstrap/collabmd-app-shell.js +21 -5
  53. package/src/client/domain/document-formatter.js +193 -0
  54. package/src/client/domain/file-search.js +73 -0
  55. package/src/client/domain/git-labels.js +5 -0
  56. package/src/client/domain/vault-paths.js +6 -4
  57. package/src/client/domain/wiki-link-completions.js +20 -20
  58. package/src/client/drawio-editor.js +8 -3
  59. package/src/client/excalidraw-editor.js +152 -6
  60. package/src/client/export/export-host.js +56 -35
  61. package/src/client/export/export-page.js +14 -4
  62. package/src/client/export/export-pipeline.js +266 -25
  63. package/src/client/infrastructure/editor-session.js +4 -0
  64. package/src/client/infrastructure/editor-view-adapter.js +60 -14
  65. package/src/client/presentation/bases-preview-controller.js +35 -11
  66. package/src/client/presentation/file-action-controller.js +15 -2
  67. package/src/client/presentation/file-explorer-controller.js +3 -0
  68. package/src/client/presentation/file-explorer-view.js +2 -1
  69. package/src/client/presentation/file-history-view-controller.js +6 -1
  70. package/src/client/presentation/file-tree-state.js +1 -11
  71. package/src/client/presentation/git-diff-view-controller.js +36 -11
  72. package/src/client/presentation/git-panel-controller.js +10 -6
  73. package/src/client/presentation/quick-switcher-controller.js +54 -154
  74. package/src/client/styles/components/scrollbars.css +10 -0
  75. package/src/client/styles/export-document.css +44 -29
  76. package/src/client/styles/features/collaboration-presence.css +13 -1
  77. package/src/client/styles/features/preview-markdown.css +54 -0
  78. package/src/client/styles/foundation/tokens.css +8 -0
  79. package/src/client/styles/layout/shell.css +76 -24
  80. package/src/client/styles/layout/view-modes.css +2 -0
  81. package/src/client/styles/overrides/codemirror.css +31 -19
  82. package/src/client/styles/surfaces/excalidraw-editor.css +42 -0
  83. package/src/domain/file-kind.js +12 -1
  84. package/src/server/domain/ripgrep-search-service.js +110 -37
  85. package/src/server/infrastructure/persistence/path-utils.js +10 -4
  86. package/src/server/infrastructure/persistence/vault-file-store.js +2 -0
  87. package/dist/client/assets/comment-ui-shared-DWkk9Vh3.js +0 -7
  88. package/dist/client/assets/editor-session-DfZuRgB_.js +0 -54
  89. package/dist/client/assets/embedded-editor-base-DvMynhKu.css +0 -1
  90. package/dist/client/assets/excalidraw-editor-D7pnUCLH.js +0 -9
  91. package/dist/client/assets/export-page-Bs3xE1fg.js +0 -57
  92. package/dist/client/assets/exportDocument-PnYU3v1s.css +0 -1
  93. package/dist/client/assets/file-kind-BbqgGzpc.js +0 -1
  94. package/dist/client/assets/index-BIpgxyGZ.css +0 -1
  95. package/dist/client/assets/index-Z5sI0Bj-.js +0 -2
  96. package/dist/client/assets/main-Dwnj9Gaq.js +0 -903
  97. package/dist/client/assets/mermaid.core-CLZBlpIW.js +0 -11
  98. package/dist/client/assets/preview-render-compiler-BQ_3h9vD.js +0 -1
  99. package/dist/client/assets/quick-switcher-controller-ButWPmCI.js +0 -12
  100. package/dist/client/assets/vault-utils-wdQ0RNR9.js +0 -1
@@ -469,6 +469,32 @@ function buildExcalidrawProps({ initialData, renderTopRightUI, viewModeEnabled }
469
469
  }
470
470
 
471
471
  const diagramCommentsContext = React.createContext(null);
472
+ const presentationContext = React.createContext(null);
473
+
474
+ function PresentationToolbar() {
475
+ const context = React.useContext(presentationContext);
476
+ if (!context) {
477
+ return null;
478
+ }
479
+
480
+ const label = context.active ? 'Exit presentation' : 'Start presentation';
481
+ const icon = context.active ? '×' : '▶';
482
+ let title = context.active ? 'Exit presentation' : 'Present frames';
483
+ if (context.frameCount === 0) {
484
+ title = 'Add a frame to create slides';
485
+ }
486
+
487
+ return React.createElement('button', {
488
+ 'aria-label': label,
489
+ 'aria-pressed': context.active,
490
+ className: `diagram-comment-button excalidraw-presentation-toggle sidebar-trigger${context.active ? ' is-active' : ''}`,
491
+ 'data-testid': 'excalidraw-presentation-toggle',
492
+ disabled: context.frameCount === 0,
493
+ onClick: context.toggle,
494
+ title,
495
+ type: 'button',
496
+ }, React.createElement('span', { 'aria-hidden': 'true' }, icon));
497
+ }
472
498
 
473
499
  function DiagramCommentsToolbar() {
474
500
  const context = React.useContext(diagramCommentsContext);
@@ -518,7 +544,12 @@ function DiagramCommentsToolbar() {
518
544
 
519
545
  const DiagramCommentsExcalidraw = React.memo(function DiagramCommentsExcalidraw({ initialData = null, renderKey = 0, viewModeEnabled }) {
520
546
  const renderTopRightUI = React.useCallback(
521
- () => React.createElement(DiagramCommentsToolbar),
547
+ () => React.createElement('div', {
548
+ className: 'excalidraw-editor-toolbar',
549
+ }, [
550
+ React.createElement(PresentationToolbar, { key: 'presentation' }),
551
+ React.createElement(DiagramCommentsToolbar, { key: 'comments' }),
552
+ ]),
522
553
  [],
523
554
  );
524
555
 
@@ -566,7 +597,13 @@ function DiagramCommentsEditor({ apiId = '', canWrite = false, focusRequest = nu
566
597
  const [drawerOpen, setDrawerOpen] = React.useState(false);
567
598
  const [draft, setDraft] = React.useState('');
568
599
  const [replyDraft, setReplyDraft] = React.useState('');
600
+ const [presentationFrameId, setPresentationFrameId] = React.useState(null);
569
601
  const [, setViewportRevision] = React.useState(0);
602
+ const presentationFrames = (
603
+ getMountedExcalidrawAPI()?.getSceneElementsIncludingDeleted?.() || []
604
+ ).filter((element) => element?.type === 'frame' && !element.isDeleted);
605
+ const presentationIndex = presentationFrames.findIndex((frame) => frame.id === presentationFrameId);
606
+ const presentationMode = presentationFrameId !== null;
570
607
 
571
608
  React.useEffect(() => {
572
609
  const api = getMountedExcalidrawAPI();
@@ -588,6 +625,76 @@ function DiagramCommentsEditor({ apiId = '', canWrite = false, focusRequest = nu
588
625
 
589
626
  useCloseDiagramCommentsOnCanvas(drawerOpen, setDrawerOpen);
590
627
 
628
+ const goToPresentationFrame = React.useCallback((index) => {
629
+ const targetIndex = Math.min(Math.max(index, 0), presentationFrames.length - 1);
630
+ const frame = presentationFrames[targetIndex];
631
+ const api = getMountedExcalidrawAPI();
632
+ if (!api || !frame) {
633
+ return;
634
+ }
635
+
636
+ setPresentationFrameId(frame.id);
637
+ api.setViewport({
638
+ animation: true,
639
+ fit: 'contain',
640
+ offsets: { ui: true },
641
+ target: frame,
642
+ });
643
+ }, [presentationFrames]);
644
+
645
+ const togglePresentation = React.useCallback(() => {
646
+ if (presentationMode) {
647
+ setPresentationFrameId(null);
648
+ return;
649
+ }
650
+
651
+ setDrawerOpen(false);
652
+ setComposerOpen(false);
653
+ setActiveThreadId(null);
654
+ goToPresentationFrame(0);
655
+ }, [goToPresentationFrame, presentationMode]);
656
+
657
+ React.useEffect(() => {
658
+ if (!presentationFrameId || presentationIndex >= 0) {
659
+ return;
660
+ }
661
+
662
+ if (presentationFrames.length > 0) {
663
+ goToPresentationFrame(0);
664
+ } else {
665
+ setPresentationFrameId(null);
666
+ }
667
+ }, [goToPresentationFrame, presentationFrameId, presentationFrames.length, presentationIndex]);
668
+
669
+ React.useEffect(() => {
670
+ if (!presentationMode) {
671
+ return undefined;
672
+ }
673
+
674
+ const handlePresentationKeyDown = (event) => {
675
+ if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.altKey) {
676
+ return;
677
+ }
678
+
679
+ if (event.key === 'Escape') {
680
+ event.preventDefault();
681
+ event.stopPropagation();
682
+ setPresentationFrameId(null);
683
+ } else if (event.key === 'ArrowRight') {
684
+ event.preventDefault();
685
+ event.stopPropagation();
686
+ goToPresentationFrame(presentationIndex + 1);
687
+ } else if (event.key === 'ArrowLeft') {
688
+ event.preventDefault();
689
+ event.stopPropagation();
690
+ goToPresentationFrame(presentationIndex - 1);
691
+ }
692
+ };
693
+
694
+ document.addEventListener('keydown', handlePresentationKeyDown, true);
695
+ return () => document.removeEventListener('keydown', handlePresentationKeyDown, true);
696
+ }, [goToPresentationFrame, presentationIndex, presentationMode]);
697
+
591
698
  React.useEffect(() => {
592
699
  if (activeThreadId && !threads.some((thread) => thread.id === activeThreadId)) {
593
700
  setActiveThreadId(null);
@@ -833,7 +940,8 @@ function DiagramCommentsEditor({ apiId = '', canWrite = false, focusRequest = nu
833
940
  ]);
834
941
  };
835
942
 
836
- const renderCommentsOverlay = visible && room
943
+ const commentsVisible = visible && !presentationMode;
944
+ const renderCommentsOverlay = commentsVisible && room
837
945
  ? React.createElement('div', {
838
946
  'aria-label': 'Diagram comments overlay',
839
947
  className: 'diagram-comments-overlay',
@@ -865,7 +973,44 @@ function DiagramCommentsEditor({ apiId = '', canWrite = false, focusRequest = nu
865
973
  ])
866
974
  : null;
867
975
 
868
- return React.createElement(diagramCommentsContext.Provider, {
976
+ const renderPresentationNavigation = presentationMode
977
+ ? React.createElement('div', {
978
+ 'aria-label': 'Presentation navigation',
979
+ className: 'excalidraw-presentation-navigation',
980
+ key: 'presentation-navigation',
981
+ role: 'toolbar',
982
+ }, [
983
+ React.createElement('button', {
984
+ 'aria-label': 'Previous slide',
985
+ className: 'diagram-comment-button',
986
+ disabled: presentationIndex <= 0,
987
+ key: 'previous',
988
+ onClick: () => goToPresentationFrame(presentationIndex - 1),
989
+ type: 'button',
990
+ }, '←'),
991
+ React.createElement('span', {
992
+ 'aria-live': 'polite',
993
+ className: 'excalidraw-presentation-status',
994
+ key: 'status',
995
+ }, `${presentationFrames[presentationIndex]?.name || `Slide ${presentationIndex + 1}`} · ${presentationIndex + 1} / ${presentationFrames.length}`),
996
+ React.createElement('button', {
997
+ 'aria-label': 'Next slide',
998
+ className: 'diagram-comment-button',
999
+ disabled: presentationIndex >= presentationFrames.length - 1,
1000
+ key: 'next',
1001
+ onClick: () => goToPresentationFrame(presentationIndex + 1),
1002
+ type: 'button',
1003
+ }, '→'),
1004
+ ])
1005
+ : null;
1006
+
1007
+ return React.createElement(presentationContext.Provider, {
1008
+ value: {
1009
+ active: presentationMode,
1010
+ frameCount: presentationFrames.length,
1011
+ toggle: togglePresentation,
1012
+ },
1013
+ }, React.createElement(diagramCommentsContext.Provider, {
869
1014
  value: {
870
1015
  canWrite,
871
1016
  drawerOpen,
@@ -874,17 +1019,18 @@ function DiagramCommentsEditor({ apiId = '', canWrite = false, focusRequest = nu
874
1019
  selectedElement,
875
1020
  threads,
876
1021
  toggleDrawer: () => setDrawerOpen((open) => !open),
877
- visible,
1022
+ visible: commentsVisible,
878
1023
  },
879
1024
  }, [
880
1025
  React.createElement(DiagramCommentsExcalidraw, {
881
1026
  initialData,
882
1027
  key: 'excalidraw',
883
1028
  renderKey: editorRenderKey,
884
- viewModeEnabled: getDocumentViewState().viewModeEnabled,
1029
+ viewModeEnabled: presentationMode || getDocumentViewState().viewModeEnabled,
885
1030
  }),
886
1031
  renderCommentsOverlay,
887
- ]);
1032
+ renderPresentationNavigation,
1033
+ ]));
888
1034
  }
889
1035
 
890
1036
  function renderExcalidrawApp({ initialData } = {}) {
@@ -3,7 +3,7 @@ import { resolveAppUrl } from '../infrastructure/runtime-config.js';
3
3
  const EXPORT_PAGE_SOURCE = 'collabmd-export-page';
4
4
  const EXPORT_HOST_SOURCE = 'collabmd-export-host';
5
5
  const EXPORT_JOB_CLOSE_POLL_MS = 500;
6
- const EXPORT_JOB_TIMEOUT_MS = 60_000;
6
+ const EXPORT_JOB_TIMEOUT_MS = 300_000;
7
7
  const pendingJobs = new Map();
8
8
  let exportBridgeErrorHandler = null;
9
9
 
@@ -15,31 +15,29 @@ function normalizeFormat(format) {
15
15
  return ['html', 'pdf'].includes(format) ? format : 'docx';
16
16
  }
17
17
 
18
- function buildExportUrl({ filePath, format, jobId }) {
19
- const url = new URL(resolveAppUrl('/export-document.html'), window.location.origin);
20
- url.searchParams.set('action', normalizeFormat(format));
21
- url.searchParams.set('job', jobId);
22
- if (filePath) {
23
- url.searchParams.set('file', filePath);
24
- }
25
- return url.toString();
26
- }
27
-
28
18
  function createBootstrapPayload({
19
+ currentFilePath = '',
20
+ currentMarkdownText = '',
21
+ directoryPath = '',
29
22
  fileList = [],
30
23
  filePath,
31
24
  format,
32
25
  jobId,
33
26
  markdownText = '',
27
+ theme = 'light',
34
28
  title = '',
35
29
  }) {
36
30
  return {
37
31
  action: normalizeFormat(format),
32
+ currentFilePath: String(currentFilePath ?? ''),
33
+ currentMarkdownText: String(currentMarkdownText ?? ''),
34
+ directoryPath: String(directoryPath ?? ''),
38
35
  fileList: Array.isArray(fileList) ? fileList.slice() : [],
39
36
  filePath: String(filePath ?? ''),
40
37
  jobId,
41
38
  markdownText: String(markdownText ?? ''),
42
39
  source: EXPORT_HOST_SOURCE,
40
+ theme: theme === 'dark' ? 'dark' : 'light',
43
41
  title: String(title ?? ''),
44
42
  type: 'bootstrap',
45
43
  };
@@ -136,26 +134,9 @@ export function initializeExportBridge({
136
134
  window.__collabmdExportBridgeInitialized = true;
137
135
  }
138
136
 
139
- export async function exportDocument({
140
- fileList = [],
141
- filePath,
142
- format,
143
- markdownText = '',
144
- title = '',
145
- } = {}) {
146
- const normalizedFilePath = String(filePath ?? '').trim();
147
- if (!normalizedFilePath) {
148
- throw new Error('No markdown note is open');
149
- }
150
-
137
+ async function startExport(payload) {
151
138
  const jobId = createJobId();
152
- const url = buildExportUrl({
153
- filePath: normalizedFilePath,
154
- format,
155
- jobId,
156
- });
157
-
158
- const exportWindow = window.open(url, '_blank');
139
+ const exportWindow = globalThis.open('', jobId);
159
140
  if (!exportWindow) {
160
141
  throw new Error('Export popup was blocked');
161
142
  }
@@ -172,12 +153,9 @@ export async function exportDocument({
172
153
  });
173
154
  }, EXPORT_JOB_CLOSE_POLL_MS),
174
155
  payload: createBootstrapPayload({
175
- fileList,
176
- filePath: normalizedFilePath,
177
- format,
156
+ ...payload,
178
157
  jobId,
179
- markdownText,
180
- title,
158
+ theme: document.documentElement.dataset.theme,
181
159
  }),
182
160
  timeoutId: window.setTimeout(() => {
183
161
  finishPendingJob(jobId, {
@@ -188,6 +166,49 @@ export async function exportDocument({
188
166
  window: exportWindow,
189
167
  });
190
168
 
169
+ exportWindow.location.replace(resolveAppUrl('/export-document.html'));
191
170
  exportWindow.focus?.();
192
171
  return jobId;
193
172
  }
173
+
174
+ export async function exportDirectory({
175
+ currentFilePath = '',
176
+ currentMarkdownText = '',
177
+ directoryPath,
178
+ fileList = [],
179
+ format,
180
+ } = {}) {
181
+ const normalizedDirectoryPath = String(directoryPath ?? '').replace(/\\/gu, '/').replace(/^\/+|\/+$/gu, '').trim();
182
+ if (!normalizedDirectoryPath) {
183
+ throw new Error('No folder was selected');
184
+ }
185
+
186
+ return startExport({
187
+ currentFilePath,
188
+ currentMarkdownText,
189
+ directoryPath: normalizedDirectoryPath,
190
+ fileList,
191
+ format,
192
+ });
193
+ }
194
+
195
+ export async function exportDocument({
196
+ fileList = [],
197
+ filePath,
198
+ format,
199
+ markdownText = '',
200
+ title = '',
201
+ } = {}) {
202
+ const normalizedFilePath = String(filePath ?? '').trim();
203
+ if (!normalizedFilePath) {
204
+ throw new Error('No markdown note is open');
205
+ }
206
+
207
+ return startExport({
208
+ fileList,
209
+ filePath: normalizedFilePath,
210
+ format,
211
+ markdownText,
212
+ title,
213
+ });
214
+ }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  postExportPageMessage,
3
+ prepareDirectoryExportSnapshot,
3
4
  prepareExportSnapshot,
4
5
  runExportAdapter,
5
6
  waitForBootstrapPayload,
@@ -7,6 +8,10 @@ import {
7
8
  } from './export-pipeline.js';
8
9
  import { groupHeadingWithFollowingBlock } from './export-print-layout.js';
9
10
 
11
+ function getExportJobId() {
12
+ return new URLSearchParams(window.location.search).get('job') || window.name || '';
13
+ }
14
+
10
15
  function setStatus(message) {
11
16
  const status = document.getElementById('exportStatus');
12
17
  if (status) {
@@ -38,7 +43,8 @@ function renderSnapshot(snapshot) {
38
43
  }
39
44
 
40
45
  document.title = `${snapshot.title} — Export`;
41
- mount.innerHTML = snapshot.html;
46
+ const parsed = new DOMParser().parseFromString(`<body>${snapshot.html}</body>`, 'text/html');
47
+ mount.replaceChildren(...parsed.body.childNodes);
42
48
  groupHeadingWithFollowingBlock(mount);
43
49
  renderWarnings(snapshot);
44
50
  return mount;
@@ -48,7 +54,11 @@ async function bootstrap() {
48
54
  try {
49
55
  setStatus('Loading export content…');
50
56
  const payload = await waitForBootstrapPayload();
51
- const snapshot = await prepareExportSnapshot(payload);
57
+ document.documentElement.dataset.theme = payload.theme;
58
+ document.body.dataset.theme = payload.theme;
59
+ const snapshot = payload.directoryPath
60
+ ? await prepareDirectoryExportSnapshot(payload)
61
+ : await prepareExportSnapshot(payload);
52
62
  const mount = renderSnapshot(snapshot);
53
63
  snapshot.html = await waitForRenderedExportContent(mount);
54
64
  setStatus(payload.action === 'pdf'
@@ -58,12 +68,12 @@ async function bootstrap() {
58
68
  setStatus(payload.action === 'pdf'
59
69
  ? 'Print dialog opened.'
60
70
  : `${payload.action.toUpperCase()} download started.`);
61
- postExportPageMessage('complete', { jobId: new URL(window.location.href).searchParams.get('job') || '' });
71
+ postExportPageMessage('complete', { jobId: getExportJobId() });
62
72
  } catch (error) {
63
73
  const message = error instanceof Error ? error.message : 'Export failed';
64
74
  setStatus(message);
65
75
  postExportPageMessage('error', {
66
- jobId: new URL(window.location.href).searchParams.get('job') || '',
76
+ jobId: getExportJobId(),
67
77
  message,
68
78
  });
69
79
  }