@arbidocs/blocks 0.5.124 → 0.5.126

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 (152) hide show
  1. package/dist/{ClippingMediaViewer-ZFEX5A3U.cjs → ClippingMediaViewer-47BAXNLO.cjs} +10 -11
  2. package/dist/ClippingMediaViewer-47BAXNLO.cjs.map +1 -0
  3. package/dist/{ClippingPdfViewer-3LRTEPHV.cjs → ClippingPdfViewer-D5BYHEAH.cjs} +16 -14
  4. package/dist/ClippingPdfViewer-D5BYHEAH.cjs.map +1 -0
  5. package/dist/{ConnectedCallDock-XJOYVVAH.cjs → ConnectedCallDock-733O5M36.cjs} +5 -5
  6. package/dist/{ConnectedCallDock-XJOYVVAH.cjs.map → ConnectedCallDock-733O5M36.cjs.map} +1 -1
  7. package/dist/{ConnectedCallRoom-CUDYAPQK.cjs → ConnectedCallRoom-TAUGSRGV.cjs} +5 -5
  8. package/dist/{ConnectedCallRoom-CUDYAPQK.cjs.map → ConnectedCallRoom-TAUGSRGV.cjs.map} +1 -1
  9. package/dist/{ConnectedNativeDocxComparison-QKHCYD6W.cjs → ConnectedNativeDocxComparison-HDE522YC.cjs} +9 -9
  10. package/dist/{ConnectedNativeDocxComparison-QKHCYD6W.cjs.map → ConnectedNativeDocxComparison-HDE522YC.cjs.map} +1 -1
  11. package/dist/{ConnectedPresentation-OV2KBOLK.cjs → ConnectedPresentation-SI3L77J4.cjs} +33 -34
  12. package/dist/ConnectedPresentation-SI3L77J4.cjs.map +1 -0
  13. package/dist/{ConnectedSlidesTab-LUAGZ6FD.cjs → ConnectedSlidesTab-JSPU6EY5.cjs} +37 -43
  14. package/dist/ConnectedSlidesTab-JSPU6EY5.cjs.map +1 -0
  15. package/dist/{ConnectedStreamedWordArtifact-AG62232A.cjs → ConnectedStreamedWordArtifact-4FHPOAFZ.cjs} +9 -9
  16. package/dist/{ConnectedStreamedWordArtifact-AG62232A.cjs.map → ConnectedStreamedWordArtifact-4FHPOAFZ.cjs.map} +1 -1
  17. package/dist/{DocumentReviewCard-M53XFLI2.cjs → DocumentReviewCard-BDNXP3D4.cjs} +4 -4
  18. package/dist/{DocumentReviewCard-M53XFLI2.cjs.map → DocumentReviewCard-BDNXP3D4.cjs.map} +1 -1
  19. package/dist/{FilesViewerDialog-JWKNJLWG.cjs → FilesViewerDialog-WQRNSKWM.cjs} +10 -11
  20. package/dist/FilesViewerDialog-WQRNSKWM.cjs.map +1 -0
  21. package/dist/{NativeDocxEditor-2LQY2CTV.cjs → NativeDocxEditor-SUDULUN3.cjs} +6 -6
  22. package/dist/{NativeDocxEditor-2LQY2CTV.cjs.map → NativeDocxEditor-SUDULUN3.cjs.map} +1 -1
  23. package/dist/{SpotlightStrip-ED22UYTM.cjs → SpotlightStrip-QQ2WUI32.cjs} +28 -29
  24. package/dist/SpotlightStrip-QQ2WUI32.cjs.map +1 -0
  25. package/dist/WorkspaceTableView-3XYAV4BV.cjs +1 -1
  26. package/dist/agents/index.cjs +1 -1
  27. package/dist/ai-IEIR7SC2.cjs +1 -1
  28. package/dist/calls/index.cjs +2 -2
  29. package/dist/{chunk-ZZZITHGK.cjs → chunk-26SFI2BH.cjs} +16 -3
  30. package/dist/chunk-26SFI2BH.cjs.map +1 -0
  31. package/dist/{chunk-NG2EE7HR.cjs → chunk-463QM764.cjs} +3 -3
  32. package/dist/{chunk-NG2EE7HR.cjs.map → chunk-463QM764.cjs.map} +1 -1
  33. package/dist/{chunk-XGM4VVNV.cjs → chunk-4KEQ62SL.cjs} +6 -6
  34. package/dist/{chunk-XGM4VVNV.cjs.map → chunk-4KEQ62SL.cjs.map} +1 -1
  35. package/dist/{chunk-YGBHY67W.cjs → chunk-6B4CCDDB.cjs} +5 -5
  36. package/dist/{chunk-YGBHY67W.cjs.map → chunk-6B4CCDDB.cjs.map} +1 -1
  37. package/dist/{chunk-JY7UUZQG.cjs → chunk-7Q46YAX4.cjs} +74 -71
  38. package/dist/chunk-7Q46YAX4.cjs.map +1 -0
  39. package/dist/{chunk-3ZZZO4F7.cjs → chunk-BY3QA3S5.cjs} +9 -9
  40. package/dist/{chunk-3ZZZO4F7.cjs.map → chunk-BY3QA3S5.cjs.map} +1 -1
  41. package/dist/{chunk-VKH3BEZE.cjs → chunk-CMPHJRVV.cjs} +3 -3
  42. package/dist/{chunk-VKH3BEZE.cjs.map → chunk-CMPHJRVV.cjs.map} +1 -1
  43. package/dist/{chunk-BX6G2ZCI.cjs → chunk-FB3JXSQT.cjs} +71 -43
  44. package/dist/chunk-FB3JXSQT.cjs.map +1 -0
  45. package/dist/{chunk-EMRY62HS.cjs → chunk-FGOJBOZX.cjs} +4 -4
  46. package/dist/{chunk-EMRY62HS.cjs.map → chunk-FGOJBOZX.cjs.map} +1 -1
  47. package/dist/{chunk-V6MW7B4B.cjs → chunk-I4X72WUT.cjs} +6 -6
  48. package/dist/{chunk-V6MW7B4B.cjs.map → chunk-I4X72WUT.cjs.map} +1 -1
  49. package/dist/{chunk-ORNLCBNE.cjs → chunk-JVEDKKNO.cjs} +43 -4
  50. package/dist/chunk-JVEDKKNO.cjs.map +1 -0
  51. package/dist/{chunk-7557LDAN.cjs → chunk-LOB2ATS5.cjs} +23 -10
  52. package/dist/chunk-LOB2ATS5.cjs.map +1 -0
  53. package/dist/{chunk-MXBXZXXQ.cjs → chunk-LXM5QXII.cjs} +3 -3
  54. package/dist/{chunk-MXBXZXXQ.cjs.map → chunk-LXM5QXII.cjs.map} +1 -1
  55. package/dist/chunk-OALQGO5C.cjs +18 -0
  56. package/dist/{chunk-SDRYSLOX.cjs.map → chunk-OALQGO5C.cjs.map} +1 -1
  57. package/dist/{chunk-2CSLFAGF.cjs → chunk-Q6N5D3R5.cjs} +34 -8
  58. package/dist/chunk-Q6N5D3R5.cjs.map +1 -0
  59. package/dist/{chunk-MVXHTAJ4.cjs → chunk-QWVUBTW3.cjs} +9 -9
  60. package/dist/{chunk-MVXHTAJ4.cjs.map → chunk-QWVUBTW3.cjs.map} +1 -1
  61. package/dist/{chunk-ADPRIBSH.cjs → chunk-X7ALF4MP.cjs} +7 -7
  62. package/dist/{chunk-ADPRIBSH.cjs.map → chunk-X7ALF4MP.cjs.map} +1 -1
  63. package/dist/{chunk-COD5ENHK.cjs → chunk-XNB27POJ.cjs} +4 -4
  64. package/dist/{chunk-COD5ENHK.cjs.map → chunk-XNB27POJ.cjs.map} +1 -1
  65. package/dist/{chunk-H236SNEV.cjs → chunk-ZCU72Q76.cjs} +191 -165
  66. package/dist/chunk-ZCU72Q76.cjs.map +1 -0
  67. package/dist/connect/docx.cjs +13 -13
  68. package/dist/connect/index.cjs +198 -163
  69. package/dist/connect/index.cjs.map +1 -1
  70. package/dist/connect/viewer.cjs +8 -9
  71. package/dist/connection/ai.d.ts +1 -0
  72. package/dist/connection/docx/ConnectedNativeDocxEditor.js +4 -5
  73. package/dist/connection/docx/ConnectedNativeDocxEditor.js.map +2 -2
  74. package/dist/connection/docx/save-native-version.d.ts +3 -0
  75. package/dist/connection/docx/save-native-version.js +19 -0
  76. package/dist/connection/docx/save-native-version.js.map +7 -0
  77. package/dist/connection/presentation/ClippingPdfViewer.js +8 -5
  78. package/dist/connection/presentation/ClippingPdfViewer.js.map +2 -2
  79. package/dist/connection/presentation/ConnectedSlidesTab.js +8 -13
  80. package/dist/connection/presentation/ConnectedSlidesTab.js.map +2 -2
  81. package/dist/connection/presentation/usePresentationController.js +24 -15
  82. package/dist/connection/presentation/usePresentationController.js.map +2 -2
  83. package/dist/connection/presentation/usePresentationFiles.d.ts +2 -1
  84. package/dist/connection/presentation/usePresentationFiles.js +15 -6
  85. package/dist/connection/presentation/usePresentationFiles.js.map +2 -2
  86. package/dist/connection/search/ConnectedSearchPage.d.ts +0 -11
  87. package/dist/connection/search/ConnectedSearchPage.js +3 -0
  88. package/dist/connection/search/ConnectedSearchPage.js.map +2 -2
  89. package/dist/connection/search/SearchResultRows.js +75 -43
  90. package/dist/connection/search/SearchResultRows.js.map +2 -2
  91. package/dist/connection/viewer/CleanViewControl.d.ts +3 -1
  92. package/dist/connection/viewer/CleanViewControl.js +5 -1
  93. package/dist/connection/viewer/CleanViewControl.js.map +2 -2
  94. package/dist/connection/viewer/ConnectedDocumentViewer.js +1 -1
  95. package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
  96. package/dist/connection/viewer/VersionDropdown.js +1 -1
  97. package/dist/connection/viewer/VersionDropdown.js.map +1 -1
  98. package/dist/connection/viewer/ViewerPdfPane.js +2 -0
  99. package/dist/connection/viewer/ViewerPdfPane.js.map +2 -2
  100. package/dist/connection/viewer/pdf/usePassageTextFallback.d.ts +12 -0
  101. package/dist/connection/viewer/pdf/usePassageTextFallback.js +30 -0
  102. package/dist/connection/viewer/pdf/usePassageTextFallback.js.map +7 -0
  103. package/dist/connection/viewer/pdf/usePdfPassageHighlights.js +2 -11
  104. package/dist/connection/viewer/pdf/usePdfPassageHighlights.js.map +2 -2
  105. package/dist/connection/viewer/useCleanView.d.ts +1 -1
  106. package/dist/connection/viewer/useCleanView.js +2 -2
  107. package/dist/connection/viewer/useCleanView.js.map +2 -2
  108. package/dist/connection/viewer/useViewedDocument.d.ts +1 -0
  109. package/dist/connection/viewer/useViewerTabs.js +13 -1
  110. package/dist/connection/viewer/useViewerTabs.js.map +2 -2
  111. package/dist/docx/DocxCommandMenu.js +2 -1
  112. package/dist/docx/DocxCommandMenu.js.map +2 -2
  113. package/dist/docx/NativeDocxEditor.d.ts +2 -1
  114. package/dist/docx/NativeDocxEditor.js +30 -5
  115. package/dist/docx/NativeDocxEditor.js.map +2 -2
  116. package/dist/docx/index.cjs +38 -38
  117. package/dist/embed/index.cjs +2 -2
  118. package/dist/facts/index.cjs +43 -43
  119. package/dist/index.cjs +73 -73
  120. package/dist/presentation/ClipCaptureOverlay.js +14 -1
  121. package/dist/presentation/ClipCaptureOverlay.js.map +2 -2
  122. package/dist/presentation/ClipImage.js +7 -4
  123. package/dist/presentation/ClipImage.js.map +2 -2
  124. package/dist/presentation/ClipTray.js +11 -7
  125. package/dist/presentation/ClipTray.js.map +2 -2
  126. package/dist/presentation/SlidesEditor.js +6 -2
  127. package/dist/presentation/SlidesEditor.js.map +2 -2
  128. package/dist/presentation/store.d.ts +2 -0
  129. package/dist/presentation/store.js +15 -0
  130. package/dist/presentation/store.js.map +3 -3
  131. package/dist/settings/index.cjs +1 -1
  132. package/dist/studio/index.cjs +1 -1
  133. package/dist/styles.css +1 -1
  134. package/dist/workflow-canvas/index.cjs +12 -12
  135. package/node_modules/@arbidocs/coedit/package.json +1 -1
  136. package/package.json +5 -5
  137. package/dist/ClippingMediaViewer-ZFEX5A3U.cjs.map +0 -1
  138. package/dist/ClippingPdfViewer-3LRTEPHV.cjs.map +0 -1
  139. package/dist/ConnectedPresentation-OV2KBOLK.cjs.map +0 -1
  140. package/dist/ConnectedSlidesTab-LUAGZ6FD.cjs.map +0 -1
  141. package/dist/FilesViewerDialog-JWKNJLWG.cjs.map +0 -1
  142. package/dist/SpotlightStrip-ED22UYTM.cjs.map +0 -1
  143. package/dist/chunk-2CSLFAGF.cjs.map +0 -1
  144. package/dist/chunk-7557LDAN.cjs.map +0 -1
  145. package/dist/chunk-BX6G2ZCI.cjs.map +0 -1
  146. package/dist/chunk-GFUOCGY6.cjs +0 -32
  147. package/dist/chunk-GFUOCGY6.cjs.map +0 -1
  148. package/dist/chunk-H236SNEV.cjs.map +0 -1
  149. package/dist/chunk-JY7UUZQG.cjs.map +0 -1
  150. package/dist/chunk-ORNLCBNE.cjs.map +0 -1
  151. package/dist/chunk-SDRYSLOX.cjs +0 -18
  152. package/dist/chunk-ZZZITHGK.cjs.map +0 -1
@@ -80,7 +80,11 @@ function usePresentationFiles() {
80
80
  return writePptxPresentationData(deck, JSON.stringify(data, null, 2));
81
81
  }, []);
82
82
  const save = useCallback(
83
- async (title, { asNew = false, template = false } = {}) => {
83
+ async (title, {
84
+ asNew = false,
85
+ template = false,
86
+ autosave = false
87
+ } = {}) => {
84
88
  if (!arbi || !workspaceId) throw new Error("Open a workspace to save the presentation.");
85
89
  const state = usePresentation.getState();
86
90
  const versionOf = asNew || template ? null : state.savedId;
@@ -89,13 +93,14 @@ function usePresentationFiles() {
89
93
  const result = await arbi.documents.upload([file], {
90
94
  folder: template ? TEMPLATES_FOLDER : PRESENTATIONS_FOLDER,
91
95
  workspaceExtId: workspaceId,
92
- ...versionOf ? { versionOf } : {}
96
+ ...versionOf ? { versionOf, autosave } : {}
93
97
  });
94
98
  const id = versionOf ?? result.doc_ext_ids?.[0];
95
99
  if (!id) throw new Error("The presentation could not be saved.");
96
100
  if (!template) {
97
101
  const [doc] = await arbi.documents.get([id]).catch(() => []);
98
- state.setSaved(id, title, doc?.current_version ?? null);
102
+ state.setSaved(id, title, result.version ?? doc?.current_version ?? null);
103
+ usePresentation.setState({ autosave: !!versionOf && autosave });
99
104
  }
100
105
  return id;
101
106
  },
@@ -145,6 +150,7 @@ function usePresentationFiles() {
145
150
  const text = await presentationData(bytes);
146
151
  const [doc] = await arbi.documents.get([id]).catch(() => []);
147
152
  const openedVersion = version ?? doc?.current_version ?? null;
153
+ const autosave = !!(version ? doc?.versions?.find((entry) => entry.n === version)?.autosave : doc?.autosave);
148
154
  if (text === null) {
149
155
  const imported = await importPptx(bytes, {
150
156
  title: presentationTitle(doc?.file_name ?? "Presentation.pptx"),
@@ -156,7 +162,8 @@ function usePresentationFiles() {
156
162
  saved: imported.saved,
157
163
  legacy: false,
158
164
  imported: { notes: imported.notes },
159
- openedVersion
165
+ openedVersion,
166
+ autosave
160
167
  };
161
168
  }
162
169
  let saved = parsePresentation(text);
@@ -182,7 +189,8 @@ function usePresentationFiles() {
182
189
  // file, not a version of this one (a document's versions keep one format).
183
190
  legacy: !isZipBytes(bytes),
184
191
  changedOutside,
185
- openedVersion
192
+ openedVersion,
193
+ autosave
186
194
  };
187
195
  },
188
196
  [arbi]
@@ -213,7 +221,7 @@ function usePresentationFiles() {
213
221
  const open = useCallback(
214
222
  async (id, version) => {
215
223
  if (!arbi) throw new Error("Sign in to open a presentation.");
216
- const { saved, legacy, imported, changedOutside, openedVersion } = await readSaved(
224
+ const { saved, legacy, imported, changedOutside, openedVersion, autosave } = await readSaved(
217
225
  id,
218
226
  version
219
227
  );
@@ -271,6 +279,7 @@ function usePresentationFiles() {
271
279
  title: saved.title,
272
280
  savedId: legacy ? null : id,
273
281
  version: legacy ? null : openedVersion,
282
+ autosave,
274
283
  boundTo: id,
275
284
  captions: saved.captions,
276
285
  labelColumn: saved.labelColumn ?? null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/presentation/usePresentationFiles.ts"],
4
- "sourcesContent": ["/**\n * Saving and opening presentations as documents of the open workspace's `Presentations` folder:\n * a PowerPoint file (`<title>.pptx`: its slides as editable PowerPoint, the presentation's own data\n * inside it, `pptx-data`). Saving again adds a version to the same document; Export PDF saves a\n * PDF of the slides.\n *\n * Opening fetches each clip's source again: the PDF the viewer shows for a page clip (so Word and\n * text documents work too), the original file for an image or a video. A page clip's redactions\n * are read again from the markup; a clip whose source or redactions cannot be read is left out\n * (never shown unredacted), and how many were is reported.\n *\n * Presentations saved before they were PowerPoint files (a PDF with the data attached, or the data\n * alone) still open; their first save makes a PowerPoint file of them.\n */\n\nimport { useCallback } from 'react'\nimport { useArbi, useArbiSessionState } from '@arbidocs/react'\nimport { isPresentationFileName } from '@arbidocs/react/ui'\nimport {\n createMarkupStore,\n fetchDocumentPdf,\n imagesToPdf,\n MarkupStoreError,\n readPdfAttachment,\n useDocumentTools,\n} from '@arbidocs/react/document-tools'\nimport type { Clip, Rect, Slide } from '../../presentation/model'\nimport {\n EXPORTS_FOLDER,\n PRESENTATIONS_FOLDER,\n PRESENTATION_ATTACHMENT,\n TEMPLATES_FOLDER,\n parsePresentation,\n presentationFileName,\n presentationTitle,\n safeFileStem,\n serializePresentation,\n type SavedClip,\n} from '../../presentation/persistence'\nimport { usePresentation } from '../../presentation/store'\nimport { renderSlide } from '../../presentation/render-slides'\nimport { buildPptx } from '../../presentation/export-pptx'\nimport {\n isZipBytes,\n readPptxPresentationData,\n writePptxPresentationData,\n} from '../../presentation/pptx-data'\nimport { importPptx, slideFingerprints } from '../../presentation/import-pptx'\nimport { mergeOutsideEdits } from '../../presentation/merge-outside-edits'\nimport type { SavedPresentation } from '../../presentation/persistence'\n\nconst PPTX_TYPE = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'\n\n/** A slide's page in the exported PDF, in points: 16:9. */\nconst SLIDE_PAGE = { width: 1280, height: 720 }\n\n/** An image embedded as a data URL, as a file. */\nfunction dataUrlBlob(url: string): Blob {\n const [head, body] = url.split(',', 2)\n const type = /^data:([^;]+)/.exec(head)?.[1] ?? 'application/octet-stream'\n const binary = atob(body ?? '')\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return new Blob([bytes], { type })\n}\n\n/** What opening a presentation found besides its content. */\nexport interface OpenedPresentation {\n /** Clips whose source could not be opened (left out). */\n missing: number\n /** Changed outside the editor (an agent): the slides are drawn again on the next save. */\n stale: boolean\n /** A PowerPoint made elsewhere, imported: what did not come across as it was. */\n imported?: { notes: string[] }\n /** Slides (1-based) changed in PowerPoint since the editor saved it, brought in. */\n changedOutside?: number[]\n}\n\n/**\n * The editable data of a saved presentation: inside its PowerPoint file; or, saved before that,\n * attached to its PDF, or the file itself.\n */\nasync function presentationData(bytes: Uint8Array): Promise<string | null> {\n if (isZipBytes(bytes)) return readPptxPresentationData(bytes)\n const isPdf = bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46\n if (!isPdf) return new TextDecoder().decode(bytes)\n const data = await readPdfAttachment(bytes, PRESENTATION_ATTACHMENT)\n if (!data) {\n throw new Error(\n 'This PDF has no editable presentation in it (it may have been changed outside ARBI).'\n )\n }\n return new TextDecoder().decode(data)\n}\n\nexport interface SavedPresentationEntry {\n id: string\n title: string\n updatedAt: string | null\n}\n\n/** The presentation as it is now, or (`withoutClips`) its look and slides only: a template. */\nfunction current(withoutClips: boolean) {\n const state = usePresentation.getState()\n return withoutClips\n ? {\n ...state,\n clips: [],\n slides: state.slides.map((slide): Slide => ({ ...slide, items: [] })),\n }\n : state\n}\n\nconst lookOf = (state: ReturnType<typeof current>) => ({\n labels: state.captions,\n columnValues: state.columnValues,\n theme: state.theme,\n logo: state.logoImage,\n media: state.media,\n})\n\nexport function usePresentationFiles() {\n const arbi = useArbi()\n const { client } = useDocumentTools()\n const workspaceId = useArbiSessionState((s) => s.workspaceId)\n\n /** The presentation as its PowerPoint file, its editable data inside. */\n const buildFile = useCallback(async (title: string, withoutClips: boolean): Promise<Blob> => {\n const state = current(withoutClips)\n const deck = await buildPptx({ title, slides: state.slides, clips: state.clips }, lookOf(state))\n // Drawn just now from the presentation itself: never stale. Each slide's fingerprint tells,\n // when it is opened again, which slides were changed in PowerPoint meanwhile.\n const fingerprints = await slideFingerprints(deck)\n const data = serializePresentation({ ...state, title, fingerprints })\n return writePptxPresentationData(deck, JSON.stringify(data, null, 2))\n }, [])\n\n /**\n * Save the presentation: `<title>.pptx` in Presentations, through the ordinary upload, so it is\n * viewed, searched and downloaded like any document (and opens in PowerPoint). A new version of\n * the document it was saved as, or (never saved, or `asNew`: Save as) a new document named\n * `title`; a template (its look and slides, no clips) goes to Presentations/Templates. Resolves\n * with its id.\n */\n const save = useCallback(\n async (\n title: string,\n { asNew = false, template = false }: { asNew?: boolean; template?: boolean } = {}\n ): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to save the presentation.')\n const state = usePresentation.getState()\n // A template is always a new document; the open presentation stays what it was.\n const versionOf = asNew || template ? null : state.savedId\n const deck = await buildFile(title, template)\n const file = new File([deck], presentationFileName(title), { type: PPTX_TYPE })\n const result = await arbi.documents.upload([file], {\n folder: template ? TEMPLATES_FOLDER : PRESENTATIONS_FOLDER,\n workspaceExtId: workspaceId,\n ...(versionOf ? { versionOf } : {}),\n })\n const id = versionOf ?? result.doc_ext_ids?.[0]\n if (!id) throw new Error('The presentation could not be saved.')\n if (!template) {\n // The version just saved: a newer one (saved by ARBI, or someone else) is then told apart.\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n state.setSaved(id, title, doc?.current_version ?? null)\n }\n return id\n },\n [arbi, workspaceId, buildFile]\n )\n\n /**\n * Export the slides as a PDF named `title` (in Presentations/Exports): to send outside or file\n * in a bundle, each slide a page as presented, redactions burnt in.\n */\n const exportPdf = useCallback(\n async (title: string): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to export the PDF.')\n const state = current(false)\n const pages = []\n for (const slide of state.slides) {\n const { image, text } = await renderSlide(slide, state.clips, lookOf(state))\n pages.push({ image, text, width: SLIDE_PAGE.width, height: SLIDE_PAGE.height })\n }\n const pdf = await imagesToPdf(pages, { title })\n const result = await arbi.documents.upload(\n [new File([pdf], `${safeFileStem(title)}.pdf`, { type: 'application/pdf' })],\n { folder: EXPORTS_FOLDER, workspaceExtId: workspaceId }\n )\n const id = result.doc_ext_ids?.[0]\n if (!id) throw new Error('The PDF could not be saved.')\n return id\n },\n [arbi, workspaceId]\n )\n\n /** The presentations saved in the open workspace (or its templates), newest first. */\n const list = useCallback(\n async (\n scope: 'presentations' | 'templates' = 'presentations'\n ): Promise<SavedPresentationEntry[]> => {\n if (!arbi || !workspaceId) return []\n const docs = await arbi.documents.list({ workspaceId })\n return docs\n .filter((doc) => {\n const folder = doc.folder ?? ''\n if (!isPresentationFileName(doc.file_name, folder)) return false\n return scope === 'templates'\n ? folder === TEMPLATES_FOLDER\n : folder.startsWith(PRESENTATIONS_FOLDER) &&\n folder !== TEMPLATES_FOLDER &&\n !folder.startsWith(EXPORTS_FOLDER)\n })\n .map((doc) => ({\n id: doc.external_id,\n title: presentationTitle(doc.file_name ?? ''),\n updatedAt: doc.updated_at ?? doc.created_at ?? null,\n }))\n .sort((a, b) => ((a.updatedAt ?? '') < (b.updatedAt ?? '') ? 1 : -1))\n },\n [arbi, workspaceId]\n )\n\n /**\n * A saved presentation's data: from its PowerPoint file (slides changed in PowerPoint since\n * brought in), its PDF's attachment or a file saved before that; a PowerPoint made elsewhere is\n * imported.\n */\n const readSaved = useCallback(\n async (id: string, version?: number) => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const response = await arbi.documents.download(id, version)\n const bytes = new Uint8Array(await response.arrayBuffer())\n const text = await presentationData(bytes)\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n const openedVersion = version ?? doc?.current_version ?? null\n if (text === null) {\n // A PowerPoint made elsewhere: imported, the version imported pinned (what cannot be edited\n // here is shown from that version's PDF).\n const imported = await importPptx(bytes, {\n title: presentationTitle(doc?.file_name ?? 'Presentation.pptx'),\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n return {\n saved: imported.saved,\n legacy: false,\n imported: { notes: imported.notes },\n openedVersion,\n }\n }\n let saved: SavedPresentation = parsePresentation(text)\n let changedOutside: number[] | undefined\n if (isZipBytes(bytes) && saved.slides.some((slide) => slide.fingerprint)) {\n const now = await slideFingerprints(bytes)\n const unchanged =\n now.length === saved.slides.length &&\n now.every((fingerprint, i) => fingerprint === saved.slides[i].fingerprint)\n if (!unchanged) {\n const imported = await importPptx(bytes, {\n title: saved.title,\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n const merged = mergeOutsideEdits(saved, imported.saved, now)\n saved = merged.saved\n changedOutside = merged.changed\n }\n }\n return {\n saved,\n // Saved before presentations were PowerPoint files: its next save is a new PowerPoint\n // file, not a version of this one (a document's versions keep one format).\n legacy: !isZipBytes(bytes),\n changedOutside,\n openedVersion,\n }\n },\n [arbi]\n )\n\n /** A new presentation from a template: its look and slides, without clips, unsaved. */\n const startFromTemplate = useCallback(\n async (id: string) => {\n const { saved } = await readSaved(id)\n usePresentation.getState().load({\n title: 'Untitled presentation',\n savedId: null,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: [],\n slides: saved.slides.map((slide) => ({ ...slide, items: [] })),\n media: saved.media,\n })\n },\n [readSaved]\n )\n\n /** A file of the workspace (e.g. the theme's logo). */\n const downloadFile = useCallback(\n async (id: string): Promise<Blob> => {\n if (!arbi) throw new Error('Sign in to read the file.')\n return (await arbi.documents.download(id)).blob()\n },\n [arbi]\n )\n\n /**\n * Open a saved presentation (as of `version`; default its current one) in place of this one.\n * Resolves with how many clips were left out, and whether its slides are stale (its data was\n * changed outside the editor: the next save draws them again).\n */\n const open = useCallback(\n async (id: string, version?: number): Promise<OpenedPresentation> => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const { saved, legacy, imported, changedOutside, openedVersion } = await readSaved(\n id,\n version\n )\n const media = saved.media ?? {}\n\n // Each source fetched once, however many clips come from it.\n const sources = new Map<string, Promise<Blob>>()\n const sourceOf = (clip: SavedClip) => {\n if (clip.media) {\n const url = media[clip.media]\n return url ? Promise.resolve(dataUrlBlob(url)) : Promise.reject(new Error('No image'))\n }\n const key = `${clip.kind === 'pdf' ? 'pdf' : 'file'}:${clip.documentId}@${clip.version ?? ''}`\n let pending = sources.get(key)\n if (!pending) {\n pending =\n clip.kind === 'pdf'\n ? fetchDocumentPdf(client, clip.documentId, { version: clip.version ?? null })\n : arbi.documents.download(clip.documentId, clip.version).then((r) => r.blob())\n sources.set(key, pending)\n }\n return pending\n }\n // Redactions per page, read once per source version.\n const markups = new Map<string, Promise<Map<number, Rect[]>>>()\n const masksOf = (clip: SavedClip) => {\n const key = `${clip.documentId}@${clip.version ?? ''}`\n let pending = markups.get(key)\n if (!pending) {\n pending = createMarkupStore({ version: clip.version, client })\n .load(clip.documentId)\n .then((layer) => {\n const byPage = new Map<number, Rect[]>()\n for (const mark of layer.annotations) {\n if (mark.kind !== 'redaction' || mark.status === 'rejected') continue\n byPage.set(mark.page, [...(byPage.get(mark.page) ?? []), ...mark.rects])\n }\n return byPage\n })\n .catch((error: unknown) => {\n // No markup at all is no redaction; anything else is a reason not to show it.\n if (error instanceof MarkupStoreError && error.status === 404) return new Map()\n throw error\n })\n markups.set(key, pending)\n }\n return pending\n }\n\n const opened = await Promise.all(\n saved.clips.map(async (clip): Promise<Omit<Clip, 'id'> | null> => {\n try {\n const [source, masks] = await Promise.all([\n sourceOf(clip),\n clip.kind === 'pdf' && !clip.media\n ? masksOf(clip)\n : Promise.resolve(new Map<number, Rect[]>()),\n ])\n return { ...clip, source, masks: masks.get(clip.page) ?? [] }\n } catch {\n return null\n }\n })\n )\n // Keep slide items pointing at the right clips once the missing ones are dropped.\n const kept: Omit<Clip, 'id'>[] = []\n const newIndex = opened.map((clip) => (clip ? kept.push(clip) - 1 : -1))\n usePresentation.getState().load({\n title: saved.title,\n savedId: legacy ? null : id,\n version: legacy ? null : openedVersion,\n boundTo: id,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: kept,\n slides: saved.slides.map((slide) => ({\n ...slide,\n items: slide.items\n .filter((item) => newIndex[item.clip] >= 0)\n .map((item) => ({ ...item, clip: newIndex[item.clip] })),\n })),\n media,\n })\n return {\n missing: opened.length - kept.length,\n stale: saved.slidesStale === true,\n ...(imported ? { imported } : {}),\n ...(changedOutside?.length ? { changedOutside } : {}),\n }\n },\n [arbi, client, readSaved]\n )\n\n return {\n save,\n exportPdf,\n list,\n open,\n startFromTemplate,\n downloadFile,\n canSave: !!arbi && !!workspaceId,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Saving and opening presentations as documents of the open workspace's `Presentations` folder:\n * a PowerPoint file (`<title>.pptx`: its slides as editable PowerPoint, the presentation's own data\n * inside it, `pptx-data`). Saving again adds a version to the same document; Export PDF saves a\n * PDF of the slides.\n *\n * Opening fetches each clip's source again: the PDF the viewer shows for a page clip (so Word and\n * text documents work too), the original file for an image or a video. A page clip's redactions\n * are read again from the markup; a clip whose source or redactions cannot be read is left out\n * (never shown unredacted), and how many were is reported.\n *\n * Presentations saved before they were PowerPoint files (a PDF with the data attached, or the data\n * alone) still open; their first save makes a PowerPoint file of them.\n */\n\nimport { useCallback } from 'react'\nimport { useArbi, useArbiSessionState } from '@arbidocs/react'\nimport { isPresentationFileName } from '@arbidocs/react/ui'\nimport {\n createMarkupStore,\n fetchDocumentPdf,\n imagesToPdf,\n MarkupStoreError,\n readPdfAttachment,\n useDocumentTools,\n} from '@arbidocs/react/document-tools'\nimport type { Clip, Rect, Slide } from '../../presentation/model'\nimport {\n EXPORTS_FOLDER,\n PRESENTATIONS_FOLDER,\n PRESENTATION_ATTACHMENT,\n TEMPLATES_FOLDER,\n parsePresentation,\n presentationFileName,\n presentationTitle,\n safeFileStem,\n serializePresentation,\n type SavedClip,\n} from '../../presentation/persistence'\nimport { usePresentation } from '../../presentation/store'\nimport { renderSlide } from '../../presentation/render-slides'\nimport { buildPptx } from '../../presentation/export-pptx'\nimport {\n isZipBytes,\n readPptxPresentationData,\n writePptxPresentationData,\n} from '../../presentation/pptx-data'\nimport { importPptx, slideFingerprints } from '../../presentation/import-pptx'\nimport { mergeOutsideEdits } from '../../presentation/merge-outside-edits'\nimport type { SavedPresentation } from '../../presentation/persistence'\n\nconst PPTX_TYPE = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'\n\n/** A slide's page in the exported PDF, in points: 16:9. */\nconst SLIDE_PAGE = { width: 1280, height: 720 }\n\n/** An image embedded as a data URL, as a file. */\nfunction dataUrlBlob(url: string): Blob {\n const [head, body] = url.split(',', 2)\n const type = /^data:([^;]+)/.exec(head)?.[1] ?? 'application/octet-stream'\n const binary = atob(body ?? '')\n const bytes = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i)\n return new Blob([bytes], { type })\n}\n\n/** What opening a presentation found besides its content. */\nexport interface OpenedPresentation {\n /** Clips whose source could not be opened (left out). */\n missing: number\n /** Changed outside the editor (an agent): the slides are drawn again on the next save. */\n stale: boolean\n /** A PowerPoint made elsewhere, imported: what did not come across as it was. */\n imported?: { notes: string[] }\n /** Slides (1-based) changed in PowerPoint since the editor saved it, brought in. */\n changedOutside?: number[]\n}\n\n/**\n * The editable data of a saved presentation: inside its PowerPoint file; or, saved before that,\n * attached to its PDF, or the file itself.\n */\nasync function presentationData(bytes: Uint8Array): Promise<string | null> {\n if (isZipBytes(bytes)) return readPptxPresentationData(bytes)\n const isPdf = bytes[0] === 0x25 && bytes[1] === 0x50 && bytes[2] === 0x44 && bytes[3] === 0x46\n if (!isPdf) return new TextDecoder().decode(bytes)\n const data = await readPdfAttachment(bytes, PRESENTATION_ATTACHMENT)\n if (!data) {\n throw new Error(\n 'This PDF has no editable presentation in it (it may have been changed outside ARBI).'\n )\n }\n return new TextDecoder().decode(data)\n}\n\nexport interface SavedPresentationEntry {\n id: string\n title: string\n updatedAt: string | null\n}\n\n/** The presentation as it is now, or (`withoutClips`) its look and slides only: a template. */\nfunction current(withoutClips: boolean) {\n const state = usePresentation.getState()\n return withoutClips\n ? {\n ...state,\n clips: [],\n slides: state.slides.map((slide): Slide => ({ ...slide, items: [] })),\n }\n : state\n}\n\nconst lookOf = (state: ReturnType<typeof current>) => ({\n labels: state.captions,\n columnValues: state.columnValues,\n theme: state.theme,\n logo: state.logoImage,\n media: state.media,\n})\n\nexport function usePresentationFiles() {\n const arbi = useArbi()\n const { client } = useDocumentTools()\n const workspaceId = useArbiSessionState((s) => s.workspaceId)\n\n /** The presentation as its PowerPoint file, its editable data inside. */\n const buildFile = useCallback(async (title: string, withoutClips: boolean): Promise<Blob> => {\n const state = current(withoutClips)\n const deck = await buildPptx({ title, slides: state.slides, clips: state.clips }, lookOf(state))\n // Drawn just now from the presentation itself: never stale. Each slide's fingerprint tells,\n // when it is opened again, which slides were changed in PowerPoint meanwhile.\n const fingerprints = await slideFingerprints(deck)\n const data = serializePresentation({ ...state, title, fingerprints })\n return writePptxPresentationData(deck, JSON.stringify(data, null, 2))\n }, [])\n\n /**\n * Save the presentation: `<title>.pptx` in Presentations, through the ordinary upload, so it is\n * viewed, searched and downloaded like any document (and opens in PowerPoint). A new version of\n * the document it was saved as, or (never saved, or `asNew`: Save as) a new document named\n * `title`; a template (its look and slides, no clips) goes to Presentations/Templates. Resolves\n * with its id.\n */\n const save = useCallback(\n async (\n title: string,\n {\n asNew = false,\n template = false,\n autosave = false,\n }: { asNew?: boolean; template?: boolean; autosave?: boolean } = {}\n ): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to save the presentation.')\n const state = usePresentation.getState()\n // A template is always a new document; the open presentation stays what it was.\n const versionOf = asNew || template ? null : state.savedId\n const deck = await buildFile(title, template)\n const file = new File([deck], presentationFileName(title), { type: PPTX_TYPE })\n const result = await arbi.documents.upload([file], {\n folder: template ? TEMPLATES_FOLDER : PRESENTATIONS_FOLDER,\n workspaceExtId: workspaceId,\n ...(versionOf ? { versionOf, autosave } : {}),\n })\n const id = versionOf ?? result.doc_ext_ids?.[0]\n if (!id) throw new Error('The presentation could not be saved.')\n if (!template) {\n // The version just saved: a newer one (saved by ARBI, or someone else) is then told apart.\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n state.setSaved(id, title, result.version ?? doc?.current_version ?? null)\n usePresentation.setState({ autosave: !!versionOf && autosave })\n }\n return id\n },\n [arbi, workspaceId, buildFile]\n )\n\n /**\n * Export the slides as a PDF named `title` (in Presentations/Exports): to send outside or file\n * in a bundle, each slide a page as presented, redactions burnt in.\n */\n const exportPdf = useCallback(\n async (title: string): Promise<string> => {\n if (!arbi || !workspaceId) throw new Error('Open a workspace to export the PDF.')\n const state = current(false)\n const pages = []\n for (const slide of state.slides) {\n const { image, text } = await renderSlide(slide, state.clips, lookOf(state))\n pages.push({ image, text, width: SLIDE_PAGE.width, height: SLIDE_PAGE.height })\n }\n const pdf = await imagesToPdf(pages, { title })\n const result = await arbi.documents.upload(\n [new File([pdf], `${safeFileStem(title)}.pdf`, { type: 'application/pdf' })],\n { folder: EXPORTS_FOLDER, workspaceExtId: workspaceId }\n )\n const id = result.doc_ext_ids?.[0]\n if (!id) throw new Error('The PDF could not be saved.')\n return id\n },\n [arbi, workspaceId]\n )\n\n /** The presentations saved in the open workspace (or its templates), newest first. */\n const list = useCallback(\n async (\n scope: 'presentations' | 'templates' = 'presentations'\n ): Promise<SavedPresentationEntry[]> => {\n if (!arbi || !workspaceId) return []\n const docs = await arbi.documents.list({ workspaceId })\n return docs\n .filter((doc) => {\n const folder = doc.folder ?? ''\n if (!isPresentationFileName(doc.file_name, folder)) return false\n return scope === 'templates'\n ? folder === TEMPLATES_FOLDER\n : folder.startsWith(PRESENTATIONS_FOLDER) &&\n folder !== TEMPLATES_FOLDER &&\n !folder.startsWith(EXPORTS_FOLDER)\n })\n .map((doc) => ({\n id: doc.external_id,\n title: presentationTitle(doc.file_name ?? ''),\n updatedAt: doc.updated_at ?? doc.created_at ?? null,\n }))\n .sort((a, b) => ((a.updatedAt ?? '') < (b.updatedAt ?? '') ? 1 : -1))\n },\n [arbi, workspaceId]\n )\n\n /**\n * A saved presentation's data: from its PowerPoint file (slides changed in PowerPoint since\n * brought in), its PDF's attachment or a file saved before that; a PowerPoint made elsewhere is\n * imported.\n */\n const readSaved = useCallback(\n async (id: string, version?: number) => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const response = await arbi.documents.download(id, version)\n const bytes = new Uint8Array(await response.arrayBuffer())\n const text = await presentationData(bytes)\n const [doc] = await arbi.documents.get([id]).catch(() => [])\n const openedVersion = version ?? doc?.current_version ?? null\n const autosave = !!(version\n ? doc?.versions?.find((entry) => entry.n === version)?.autosave\n : doc?.autosave)\n if (text === null) {\n // A PowerPoint made elsewhere: imported, the version imported pinned (what cannot be edited\n // here is shown from that version's PDF).\n const imported = await importPptx(bytes, {\n title: presentationTitle(doc?.file_name ?? 'Presentation.pptx'),\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n return {\n saved: imported.saved,\n legacy: false,\n imported: { notes: imported.notes },\n openedVersion,\n autosave,\n }\n }\n let saved: SavedPresentation = parsePresentation(text)\n let changedOutside: number[] | undefined\n if (isZipBytes(bytes) && saved.slides.some((slide) => slide.fingerprint)) {\n const now = await slideFingerprints(bytes)\n const unchanged =\n now.length === saved.slides.length &&\n now.every((fingerprint, i) => fingerprint === saved.slides[i].fingerprint)\n if (!unchanged) {\n const imported = await importPptx(bytes, {\n title: saved.title,\n documentId: id,\n version: version ?? doc?.current_version,\n fileName: doc?.file_name ?? undefined,\n })\n const merged = mergeOutsideEdits(saved, imported.saved, now)\n saved = merged.saved\n changedOutside = merged.changed\n }\n }\n return {\n saved,\n // Saved before presentations were PowerPoint files: its next save is a new PowerPoint\n // file, not a version of this one (a document's versions keep one format).\n legacy: !isZipBytes(bytes),\n changedOutside,\n openedVersion,\n autosave,\n }\n },\n [arbi]\n )\n\n /** A new presentation from a template: its look and slides, without clips, unsaved. */\n const startFromTemplate = useCallback(\n async (id: string) => {\n const { saved } = await readSaved(id)\n usePresentation.getState().load({\n title: 'Untitled presentation',\n savedId: null,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: [],\n slides: saved.slides.map((slide) => ({ ...slide, items: [] })),\n media: saved.media,\n })\n },\n [readSaved]\n )\n\n /** A file of the workspace (e.g. the theme's logo). */\n const downloadFile = useCallback(\n async (id: string): Promise<Blob> => {\n if (!arbi) throw new Error('Sign in to read the file.')\n return (await arbi.documents.download(id)).blob()\n },\n [arbi]\n )\n\n /**\n * Open a saved presentation (as of `version`; default its current one) in place of this one.\n * Resolves with how many clips were left out, and whether its slides are stale (its data was\n * changed outside the editor: the next save draws them again).\n */\n const open = useCallback(\n async (id: string, version?: number): Promise<OpenedPresentation> => {\n if (!arbi) throw new Error('Sign in to open a presentation.')\n const { saved, legacy, imported, changedOutside, openedVersion, autosave } = await readSaved(\n id,\n version\n )\n const media = saved.media ?? {}\n\n // Each source fetched once, however many clips come from it.\n const sources = new Map<string, Promise<Blob>>()\n const sourceOf = (clip: SavedClip) => {\n if (clip.media) {\n const url = media[clip.media]\n return url ? Promise.resolve(dataUrlBlob(url)) : Promise.reject(new Error('No image'))\n }\n const key = `${clip.kind === 'pdf' ? 'pdf' : 'file'}:${clip.documentId}@${clip.version ?? ''}`\n let pending = sources.get(key)\n if (!pending) {\n pending =\n clip.kind === 'pdf'\n ? fetchDocumentPdf(client, clip.documentId, { version: clip.version ?? null })\n : arbi.documents.download(clip.documentId, clip.version).then((r) => r.blob())\n sources.set(key, pending)\n }\n return pending\n }\n // Redactions per page, read once per source version.\n const markups = new Map<string, Promise<Map<number, Rect[]>>>()\n const masksOf = (clip: SavedClip) => {\n const key = `${clip.documentId}@${clip.version ?? ''}`\n let pending = markups.get(key)\n if (!pending) {\n pending = createMarkupStore({ version: clip.version, client })\n .load(clip.documentId)\n .then((layer) => {\n const byPage = new Map<number, Rect[]>()\n for (const mark of layer.annotations) {\n if (mark.kind !== 'redaction' || mark.status === 'rejected') continue\n byPage.set(mark.page, [...(byPage.get(mark.page) ?? []), ...mark.rects])\n }\n return byPage\n })\n .catch((error: unknown) => {\n // No markup at all is no redaction; anything else is a reason not to show it.\n if (error instanceof MarkupStoreError && error.status === 404) return new Map()\n throw error\n })\n markups.set(key, pending)\n }\n return pending\n }\n\n const opened = await Promise.all(\n saved.clips.map(async (clip): Promise<Omit<Clip, 'id'> | null> => {\n try {\n const [source, masks] = await Promise.all([\n sourceOf(clip),\n clip.kind === 'pdf' && !clip.media\n ? masksOf(clip)\n : Promise.resolve(new Map<number, Rect[]>()),\n ])\n return { ...clip, source, masks: masks.get(clip.page) ?? [] }\n } catch {\n return null\n }\n })\n )\n // Keep slide items pointing at the right clips once the missing ones are dropped.\n const kept: Omit<Clip, 'id'>[] = []\n const newIndex = opened.map((clip) => (clip ? kept.push(clip) - 1 : -1))\n usePresentation.getState().load({\n title: saved.title,\n savedId: legacy ? null : id,\n version: legacy ? null : openedVersion,\n autosave,\n boundTo: id,\n captions: saved.captions,\n labelColumn: saved.labelColumn ?? null,\n theme: saved.theme,\n clips: kept,\n slides: saved.slides.map((slide) => ({\n ...slide,\n items: slide.items\n .filter((item) => newIndex[item.clip] >= 0)\n .map((item) => ({ ...item, clip: newIndex[item.clip] })),\n })),\n media,\n })\n return {\n missing: opened.length - kept.length,\n stale: saved.slidesStale === true,\n ...(imported ? { imported } : {}),\n ...(changedOutside?.length ? { changedOutside } : {}),\n }\n },\n [arbi, client, readSaved]\n )\n\n return {\n save,\n exportPdf,\n list,\n open,\n startFromTemplate,\n downloadFile,\n canSave: !!arbi && !!workspaceId,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,14 +1,3 @@
1
- /**
2
- * The Smart Search page, connected: search the open workspace's documents for passages
3
- * (`useSemanticSearch` over the shared search session, so results outlive the page), group them
4
- * by document or list them by relevance, cut them by a relevance threshold, open a passage in the
5
- * host's viewer (`BlocksEnvironment.openDocument`), and label every listed document.
6
- *
7
- * Runs under any host (`useConnectedWorkspace`). It searches what the Files filter lets through
8
- * (`useSearchScope`: the same filter state as the Files page; none, the whole workspace). A group's
9
- * document is its `ConnectedDocumentPill`. An empty workspace offers the upload surface of the
10
- * Files views, through the host's `openUpload`.
11
- */
12
1
  import { type SearchRowLabels } from './SearchResultRows';
13
2
  export interface ConnectedSearchPageLabels extends SearchRowLabels {
14
3
  tab: string;
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useSpotlight } from "../../presentation/spotlight.js";
3
4
  import {
4
5
  useCallback,
5
6
  useEffect,
@@ -163,6 +164,8 @@ function ConnectedSearchPage({ embedded = false, labels }) {
163
164
  (e, hit) => {
164
165
  e.preventDefault();
165
166
  e.stopPropagation();
167
+ if (e.currentTarget.dataset.clip === "true")
168
+ useSpotlight.getState().setClipping(true);
166
169
  openDocument(hit.docId, { page: hit.page, passageId: hit.chunkExtId });
167
170
  },
168
171
  [openDocument]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/search/ConnectedSearchPage.tsx"],
4
- "sourcesContent": ["/**\n * The Smart Search page, connected: search the open workspace's documents for passages\n * (`useSemanticSearch` over the shared search session, so results outlive the page), group them\n * by document or list them by relevance, cut them by a relevance threshold, open a passage in the\n * host's viewer (`BlocksEnvironment.openDocument`), and label every listed document.\n *\n * Runs under any host (`useConnectedWorkspace`). It searches what the Files filter lets through\n * (`useSearchScope`: the same filter state as the Files page; none, the whole workspace). A group's\n * document is its `ConnectedDocumentPill`. An empty workspace offers the upload surface of the\n * Files views, through the host's `openUpload`.\n */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type MouseEvent,\n} from 'react'\nimport { FileText, Search, Tag as List } from 'lucide-react'\nimport {\n MAX_RETRIEVE_DOC_IDS,\n useAssignDocTags,\n useCreateTag,\n useSemanticSearch,\n useUserSettings,\n type SearchChunk,\n} from '@arbidocs/react'\nimport { sharedSearchSession, takePendingSearch } from '@arbidocs/react/shell'\nimport { useIsOnline } from '@arbidocs/react/document-tools'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { TabHeader, type Tab } from '../../shell/TabHeader'\nimport { SearchEmptyState } from '../../retrieval/SearchEmptyState'\nimport { SearchPagination } from '../../retrieval/SearchPagination'\nimport {\n ApplyLabelButton,\n ApplyLabelDialog,\n RelevanceThreshold,\n SearchBar,\n SearchControlsBar,\n SearchErrorBanner,\n SearchGroupCard,\n SearchNoResults,\n SearchPageBody,\n SearchPageFrame,\n SearchPageHeader,\n SearchPillToggle,\n SearchResultList,\n SearchResultsInner,\n SearchResultsMain,\n SearchToolbarRow,\n SearchTotalsHeadline,\n} from '../../retrieval/SearchPage'\nimport {\n extractHighlightTerms,\n getSearchMatchHint,\n groupSearchResults,\n isMeaningMatch,\n passesRelevanceCutoff,\n type SearchDocumentGroup,\n} from '../../retrieval/search-results'\nimport { useConnectedWorkspace } from '../ConnectionProvider'\nimport { ConnectedDocumentPill } from '../entities/ConnectedDocumentPill'\nimport { EmptyWorkspaceUpload } from '../files/upload/EmptyWorkspaceUpload'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { useConnectedDocuments } from '../workspaceData'\nimport { useSearchScope } from './useSearchScope'\nimport {\n DEFAULT_SEARCH_ROW_LABELS,\n FlatResultRow,\n GroupedResultRow,\n SearchGroupRowHeader,\n type SearchRowLabels,\n} from './SearchResultRows'\n\n/**\n * Hits on one page. The user always sees the true totals (\"X hits across Y documents\"); only\n * this many rows mount at a time.\n */\nconst PAGE_SIZE_FLAT = 50\nconst PAGE_SIZE_GROUPED = 20\n\ntype ViewMode = 'grouped' | 'flat'\n\nexport interface ConnectedSearchPageLabels extends SearchRowLabels {\n tab: string\n /** An example query rather than an instruction: quoted phrase plus free words. */\n placeholder: string\n byDoc: string\n byDocTitle: string\n byPassage: string\n byPassageTitle: string\n grouping: string\n offline: string\n tooManyDocuments: (count: number, max: number) => string\n /** The default name of the label made from a search. */\n labelName: (query: string) => string\n labelled: (count: number, name: string) => string\n labelFailed: string\n}\n\nexport const DEFAULT_CONNECTED_SEARCH_PAGE_LABELS: ConnectedSearchPageLabels = {\n ...DEFAULT_SEARCH_ROW_LABELS,\n tab: 'Smart Search',\n placeholder: 'e.g. \"payment terms\" invoice',\n byDoc: 'By Doc',\n byDocTitle: 'Group by document',\n byPassage: 'By Passage',\n byPassageTitle: 'All passages by relevance',\n grouping: 'Result grouping',\n offline: \"You're offline \u2014 reconnect to search.\",\n tooManyDocuments: (count, max) =>\n `You selected ${count.toLocaleString()} documents. Maximum for individual selection is ${max.toLocaleString()}. Narrow your filter, or clear it to search the whole workspace.`,\n // `Search(\u2026)` tells a saved search apart from a hand-made label at a glance.\n labelName: (query) => `Search(${query})`,\n labelled: (count, name) =>\n `Labeled ${count} ${count === 1 ? 'document' : 'documents'} as \"${name}\"`,\n labelFailed: 'Failed to save search',\n}\n\nexport interface ConnectedSearchPageProps {\n /**\n * Shown inside a host's Files view: no tab header of its own (the host has it and the filter),\n * and the grouping toggle on a slim row above the search box.\n */\n embedded?: boolean\n labels?: Partial<ConnectedSearchPageLabels>\n}\n\nconst readPending = () => sharedSearchSession.getState().pending\n\nexport function ConnectedSearchPage({ embedded = false, labels }: ConnectedSearchPageProps) {\n const l = useMemo(() => ({ ...DEFAULT_CONNECTED_SEARCH_PAGE_LABELS, ...labels }), [labels])\n const { notify, openDocument, openUpload } = useBlocksEnvironment()\n const { workspaceId, live } = useConnectedWorkspace()\n const { data: documents } = useConnectedDocuments()\n const { data: settings } = useUserSettings({ enabled: live })\n const isOnline = useIsOnline()\n\n const scope = useSearchScope(documents)\n const scopeDocIds = scope.docIds\n const scopeCount = scope.count\n const tooMany = !!scopeDocIds && scopeDocIds.length > MAX_RETRIEVE_DOC_IDS\n const tooManyMessage = tooMany ? l.tooManyDocuments(scopeCount, MAX_RETRIEVE_DOC_IDS) : undefined\n\n // An empty workspace's links: a zip is unpacked by the upload, the rest added here.\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openUpload?.(files, { source: 'computer' }),\n })\n\n const search = useSemanticSearch(scopeDocIds ?? [], {\n session: sharedSearchSession,\n workspaceId,\n model: settings?.last_config || 'default',\n })\n const { results, searched, status, error, query: submittedQuery } = search\n const isSearching = status === 'running'\n\n // The box opens on the last query, so a search is not retyped after a reload or a page switch.\n const [draft, setDraft] = useState(submittedQuery)\n const inputRef = useRef<HTMLTextAreaElement>(null)\n const [expanded, setExpanded] = useState<Set<string>>(new Set())\n const [cutoff, setCutoff] = useState(0)\n const [viewMode, setViewMode] = useState<ViewMode>('grouped')\n const [page, setPage] = useState(1)\n const [labelOpen, setLabelOpen] = useState(false)\n const [labelName, setLabelName] = useState('')\n const createTag = useCreateTag()\n const assignDocTags = useAssignDocTags()\n const [isLabelling, setIsLabelling] = useState(false)\n\n // Highlight quoted phrases and the query's significant words.\n const terms = useMemo(() => extractHighlightTerms(submittedQuery), [submittedQuery])\n\n // The threshold cuts what is shown; the headline's totals stay those of the whole result set.\n const shown = useMemo(\n () => results.filter((hit) => passesRelevanceCutoff(hit, cutoff, terms)),\n [results, cutoff, terms]\n )\n const meaningHits = useMemo(\n () => shown.filter((hit) => isMeaningMatch(hit.content, terms)).length,\n [shown, terms]\n )\n const { groups, docIds: listedDocIds } = useMemo(\n () => groupSearchResults(shown, terms),\n [shown, terms]\n )\n const totalDocuments = useMemo(() => new Set(results.map((hit) => hit.docId)).size, [results])\n const docsById = useMemo(\n () => new Map(documents.map((doc) => [doc.external_id, doc])),\n [documents]\n )\n\n // A new result set opens every group; the threshold, the view and paging keep the user's\n // collapsed groups.\n const lastResults = useRef<SearchChunk[] | null>(null)\n useEffect(() => {\n if (results === lastResults.current) return\n lastResults.current = results\n setExpanded(new Set(results.map((hit) => hit.docId)))\n }, [results])\n\n useEffect(() => setPage(1), [results, cutoff, viewMode])\n\n const pageData = useMemo(() => {\n const size = viewMode === 'flat' ? PAGE_SIZE_FLAT : PAGE_SIZE_GROUPED\n const total = viewMode === 'flat' ? shown.length : groups.length\n const totalPages = Math.max(1, Math.ceil(total / size))\n const start = (Math.min(page, totalPages) - 1) * size\n const end = Math.min(start + size, total)\n return {\n totalPages,\n start,\n flat: viewMode === 'flat' ? shown.slice(start, end) : [],\n grouped: viewMode === 'grouped' ? groups.slice(start, end) : ([] as SearchDocumentGroup[]),\n range: { start: total === 0 ? 0 : start + 1, end, total },\n }\n }, [viewMode, page, shown, groups])\n\n const toggleGroup = useCallback((docId: string) => {\n setExpanded((prev) => {\n const next = new Set(prev)\n if (next.has(docId)) next.delete(docId)\n else next.add(docId)\n return next\n })\n }, [])\n\n const openHit = useCallback(\n (e: MouseEvent, hit: SearchChunk) => {\n e.preventDefault()\n e.stopPropagation()\n openDocument(hit.docId, { page: hit.page, passageId: hit.chunkExtId })\n },\n [openDocument]\n )\n\n const runSearch = (text: string) => {\n const query = text.trim()\n if (!query || !workspaceId || tooMany) return\n // Search runs on the server: nothing to do offline.\n if (!isOnline) {\n notify(l.offline, 'warning')\n return\n }\n setDraft(text)\n // Every new search starts at Low, a step the threshold buttons show as selected.\n setCutoff(0)\n void search.search(query)\n }\n\n // A query handed over by another surface (the workspace home's ask bar) runs on arrival: the\n // user already pressed Search once. Taken once, so a failed search never retries itself.\n const pending = useSyncExternalStore(sharedSearchSession.subscribe, readPending, readPending)\n useEffect(() => {\n if (pending === null || !workspaceId) return\n const query = takePendingSearch(sharedSearchSession)\n if (query?.trim()) runSearch(query)\n // runSearch is redefined per render; the pending query is the one-shot trigger.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pending, workspaceId])\n\n // The box's clear empties everything and returns to the intro.\n const clear = () => {\n setDraft('')\n search.clear()\n setExpanded(new Set())\n setCutoff(0)\n setPage(1)\n if (inputRef.current) inputRef.current.style.height = 'auto'\n inputRef.current?.focus()\n }\n\n const openLabelDialog = () => {\n setLabelName(l.labelName(submittedQuery.trim()))\n setLabelOpen(true)\n }\n\n const applyLabel = async (name: string) => {\n const trimmed = name.trim()\n if (!workspaceId || listedDocIds.length === 0 || !trimmed) return\n setIsLabelling(true)\n try {\n // A private label; one request then tags every listed document in a single transaction.\n const tag = await createTag.mutateAsync({\n name: trimmed,\n tagType: { type: 'checkbox', options: [] },\n shared: false,\n })\n await assignDocTags.mutateAsync({ tagId: tag.external_id, docIds: listedDocIds })\n notify(l.labelled(listedDocIds.length, trimmed), 'success')\n setLabelOpen(false)\n } catch (err) {\n // The server's reason, so a failed save is something the user can act on.\n notify(err instanceof Error ? err.message : l.labelFailed, 'error')\n } finally {\n setIsLabelling(false)\n }\n }\n\n const viewOptions = useMemo(\n () => [\n {\n value: 'grouped',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <FileText className=\"size-3.5\" />\n {l.byDoc}\n </span>\n ),\n title: l.byDocTitle,\n },\n {\n value: 'flat',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <List className=\"size-3.5\" />\n {l.byPassage}\n </span>\n ),\n title: l.byPassageTitle,\n },\n ],\n [l]\n )\n const viewToggle = (\n <SearchPillToggle\n options={viewOptions}\n value={viewMode}\n onChange={(value) => setViewMode(value as ViewMode)}\n ariaLabel={l.grouping}\n />\n )\n\n const tabs: Tab[] = [\n {\n id: 'search',\n label: l.tab,\n icon: <Search className=\"size-4\" />,\n count: scopeCount,\n hasActiveFilters: !!scopeDocIds,\n actions: scope.filter,\n actionsHoverHandlers: scope.filterHoverHandlers,\n },\n ]\n\n const pagination = (\n <SearchPagination\n currentPage={Math.min(page, pageData.totalPages)}\n totalPages={pageData.totalPages}\n onPageChange={setPage}\n />\n )\n const hasResults = searched && results.length > 0\n\n return (\n <SearchPageFrame isEmbedded={embedded}>\n {!embedded && (\n <SearchPageHeader>\n <TabHeader tabs={tabs} activeTabId=\"search\" onTabChange={() => {}} variant=\"underline\" />\n </SearchPageHeader>\n )}\n\n <ApplyLabelDialog\n open={labelOpen}\n onOpenChange={setLabelOpen}\n name={labelName}\n onNameChange={setLabelName}\n onApply={applyLabel}\n isApplying={isLabelling}\n documentCount={listedDocIds.length}\n passageCount={shown.length}\n />\n\n <SearchPageBody>\n {/* Embedded: the grouping toggle sits on the folder line, visible before any search. */}\n {embedded && <SearchToolbarRow>{viewToggle}</SearchToolbarRow>}\n <SearchBar\n query={draft}\n onQueryChange={setDraft}\n onSubmit={() => runSearch(draft)}\n onClear={clear}\n canSubmit={!!draft.trim() && !!workspaceId && !isSearching && !tooMany}\n isSearching={isSearching}\n placeholder={l.placeholder}\n submitTitle={tooManyMessage}\n notice={tooManyMessage}\n inputRef={inputRef}\n isEmbedded={embedded}\n />\n\n {/* Pinned between the box and the scrolling results. */}\n {hasResults && (\n <SearchControlsBar\n start={\n <SearchTotalsHeadline\n shownHits={shown.length}\n totalHits={results.length}\n shownDocuments={groups.length}\n totalDocuments={totalDocuments}\n meaningHits={meaningHits}\n range={\n pageData.totalPages > 1\n ? { ...pageData.range, unit: viewMode === 'flat' ? 'passages' : 'documents' }\n : null\n }\n action={\n listedDocIds.length > 0 ? (\n <ApplyLabelButton onClick={openLabelDialog} />\n ) : undefined\n }\n />\n }\n end={\n <>\n <RelevanceThreshold value={cutoff} onChange={setCutoff} />\n {!embedded && viewToggle}\n </>\n }\n />\n )}\n\n {hasResults && (\n <p\n className=\"pb-3 pl-6 pr-4 text-xs text-muted-foreground\"\n data-testid=\"smart-search-match-hint\"\n >\n {getSearchMatchHint(submittedQuery)}\n </p>\n )}\n\n <SearchResultsMain>\n {searched || error ? (\n <SearchResultsInner>\n {error ? (\n <SearchErrorBanner message={error} />\n ) : shown.length > 0 ? (\n <>\n {/* The same pager above and below a long page. */}\n {pagination}\n {viewMode === 'flat' ? (\n <SearchResultList testId=\"smart-search-results-flat\">\n {pageData.flat.map((hit, i) => (\n <FlatResultRow\n key={hit.id}\n hit={hit}\n position={pageData.start + i}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchResultList>\n ) : (\n <SearchResultList testId=\"smart-search-results\">\n {pageData.grouped.map((group) => (\n <SearchGroupCard\n key={group.docId}\n testId={`search-result-group-${group.docId}`}\n isExpanded={expanded.has(group.docId)}\n header={\n <SearchGroupRowHeader\n group={group}\n source={\n <ConnectedDocumentPill\n docId={group.docId}\n doc={docsById.get(group.docId)}\n labelMaxWidth=\"max-w-none\"\n />\n }\n isExpanded={expanded.has(group.docId)}\n onToggle={toggleGroup}\n />\n }\n >\n {group.results.map((hit, i) => (\n <GroupedResultRow\n key={hit.id}\n hit={hit}\n testId={`search-result-${group.docId}-${i}`}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchGroupCard>\n ))}\n </SearchResultList>\n )}\n {pagination}\n </>\n ) : (\n <SearchNoResults\n hiddenHits={results.length}\n onLowerThreshold={() => setCutoff(0)}\n />\n )}\n </SearchResultsInner>\n ) : documents.length === 0 && openUpload ? (\n <EmptyWorkspaceUpload\n onFilesSelected={(files) => openUpload(files, { source: 'computer' })}\n onUrlsSubmit={uploadUrls}\n onImportFromEmail={() => openUpload(undefined, { source: 'email' })}\n />\n ) : (\n <SearchEmptyState count={scopeCount} />\n )}\n </SearchResultsMain>\n </SearchPageBody>\n </SearchPageFrame>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { useSpotlight } from '../../presentation/spotlight'\n/**\n * The Smart Search page, connected: search the open workspace's documents for passages\n * (`useSemanticSearch` over the shared search session, so results outlive the page), group them\n * by document or list them by relevance, cut them by a relevance threshold, open a passage in the\n * host's viewer (`BlocksEnvironment.openDocument`), and label every listed document.\n *\n * Runs under any host (`useConnectedWorkspace`). It searches what the Files filter lets through\n * (`useSearchScope`: the same filter state as the Files page; none, the whole workspace). A group's\n * document is its `ConnectedDocumentPill`. An empty workspace offers the upload surface of the\n * Files views, through the host's `openUpload`.\n */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type MouseEvent,\n} from 'react'\nimport { FileText, Search, Tag as List } from 'lucide-react'\nimport {\n MAX_RETRIEVE_DOC_IDS,\n useAssignDocTags,\n useCreateTag,\n useSemanticSearch,\n useUserSettings,\n type SearchChunk,\n} from '@arbidocs/react'\nimport { sharedSearchSession, takePendingSearch } from '@arbidocs/react/shell'\nimport { useIsOnline } from '@arbidocs/react/document-tools'\nimport { useBlocksEnvironment } from '../../provider/environment'\nimport { TabHeader, type Tab } from '../../shell/TabHeader'\nimport { SearchEmptyState } from '../../retrieval/SearchEmptyState'\nimport { SearchPagination } from '../../retrieval/SearchPagination'\nimport {\n ApplyLabelButton,\n ApplyLabelDialog,\n RelevanceThreshold,\n SearchBar,\n SearchControlsBar,\n SearchErrorBanner,\n SearchGroupCard,\n SearchNoResults,\n SearchPageBody,\n SearchPageFrame,\n SearchPageHeader,\n SearchPillToggle,\n SearchResultList,\n SearchResultsInner,\n SearchResultsMain,\n SearchToolbarRow,\n SearchTotalsHeadline,\n} from '../../retrieval/SearchPage'\nimport {\n extractHighlightTerms,\n getSearchMatchHint,\n groupSearchResults,\n isMeaningMatch,\n passesRelevanceCutoff,\n type SearchDocumentGroup,\n} from '../../retrieval/search-results'\nimport { useConnectedWorkspace } from '../ConnectionProvider'\nimport { ConnectedDocumentPill } from '../entities/ConnectedDocumentPill'\nimport { EmptyWorkspaceUpload } from '../files/upload/EmptyWorkspaceUpload'\nimport { useAddFromUrls } from '../files/upload/useAddFromUrls'\nimport { useConnectedDocuments } from '../workspaceData'\nimport { useSearchScope } from './useSearchScope'\nimport {\n DEFAULT_SEARCH_ROW_LABELS,\n FlatResultRow,\n GroupedResultRow,\n SearchGroupRowHeader,\n type SearchRowLabels,\n} from './SearchResultRows'\n\n/**\n * Hits on one page. The user always sees the true totals (\"X hits across Y documents\"); only\n * this many rows mount at a time.\n */\nconst PAGE_SIZE_FLAT = 50\nconst PAGE_SIZE_GROUPED = 20\n\ntype ViewMode = 'grouped' | 'flat'\n\nexport interface ConnectedSearchPageLabels extends SearchRowLabels {\n tab: string\n /** An example query rather than an instruction: quoted phrase plus free words. */\n placeholder: string\n byDoc: string\n byDocTitle: string\n byPassage: string\n byPassageTitle: string\n grouping: string\n offline: string\n tooManyDocuments: (count: number, max: number) => string\n /** The default name of the label made from a search. */\n labelName: (query: string) => string\n labelled: (count: number, name: string) => string\n labelFailed: string\n}\n\nexport const DEFAULT_CONNECTED_SEARCH_PAGE_LABELS: ConnectedSearchPageLabels = {\n ...DEFAULT_SEARCH_ROW_LABELS,\n tab: 'Smart Search',\n placeholder: 'e.g. \"payment terms\" invoice',\n byDoc: 'By Doc',\n byDocTitle: 'Group by document',\n byPassage: 'By Passage',\n byPassageTitle: 'All passages by relevance',\n grouping: 'Result grouping',\n offline: \"You're offline \u2014 reconnect to search.\",\n tooManyDocuments: (count, max) =>\n `You selected ${count.toLocaleString()} documents. Maximum for individual selection is ${max.toLocaleString()}. Narrow your filter, or clear it to search the whole workspace.`,\n // `Search(\u2026)` tells a saved search apart from a hand-made label at a glance.\n labelName: (query) => `Search(${query})`,\n labelled: (count, name) =>\n `Labeled ${count} ${count === 1 ? 'document' : 'documents'} as \"${name}\"`,\n labelFailed: 'Failed to save search',\n}\n\nexport interface ConnectedSearchPageProps {\n /**\n * Shown inside a host's Files view: no tab header of its own (the host has it and the filter),\n * and the grouping toggle on a slim row above the search box.\n */\n embedded?: boolean\n labels?: Partial<ConnectedSearchPageLabels>\n}\n\nconst readPending = () => sharedSearchSession.getState().pending\n\nexport function ConnectedSearchPage({ embedded = false, labels }: ConnectedSearchPageProps) {\n const l = useMemo(() => ({ ...DEFAULT_CONNECTED_SEARCH_PAGE_LABELS, ...labels }), [labels])\n const { notify, openDocument, openUpload } = useBlocksEnvironment()\n const { workspaceId, live } = useConnectedWorkspace()\n const { data: documents } = useConnectedDocuments()\n const { data: settings } = useUserSettings({ enabled: live })\n const isOnline = useIsOnline()\n\n const scope = useSearchScope(documents)\n const scopeDocIds = scope.docIds\n const scopeCount = scope.count\n const tooMany = !!scopeDocIds && scopeDocIds.length > MAX_RETRIEVE_DOC_IDS\n const tooManyMessage = tooMany ? l.tooManyDocuments(scopeCount, MAX_RETRIEVE_DOC_IDS) : undefined\n\n // An empty workspace's links: a zip is unpacked by the upload, the rest added here.\n const uploadUrls = useAddFromUrls({\n onZipFiles: (files) => openUpload?.(files, { source: 'computer' }),\n })\n\n const search = useSemanticSearch(scopeDocIds ?? [], {\n session: sharedSearchSession,\n workspaceId,\n model: settings?.last_config || 'default',\n })\n const { results, searched, status, error, query: submittedQuery } = search\n const isSearching = status === 'running'\n\n // The box opens on the last query, so a search is not retyped after a reload or a page switch.\n const [draft, setDraft] = useState(submittedQuery)\n const inputRef = useRef<HTMLTextAreaElement>(null)\n const [expanded, setExpanded] = useState<Set<string>>(new Set())\n const [cutoff, setCutoff] = useState(0)\n const [viewMode, setViewMode] = useState<ViewMode>('grouped')\n const [page, setPage] = useState(1)\n const [labelOpen, setLabelOpen] = useState(false)\n const [labelName, setLabelName] = useState('')\n const createTag = useCreateTag()\n const assignDocTags = useAssignDocTags()\n const [isLabelling, setIsLabelling] = useState(false)\n\n // Highlight quoted phrases and the query's significant words.\n const terms = useMemo(() => extractHighlightTerms(submittedQuery), [submittedQuery])\n\n // The threshold cuts what is shown; the headline's totals stay those of the whole result set.\n const shown = useMemo(\n () => results.filter((hit) => passesRelevanceCutoff(hit, cutoff, terms)),\n [results, cutoff, terms]\n )\n const meaningHits = useMemo(\n () => shown.filter((hit) => isMeaningMatch(hit.content, terms)).length,\n [shown, terms]\n )\n const { groups, docIds: listedDocIds } = useMemo(\n () => groupSearchResults(shown, terms),\n [shown, terms]\n )\n const totalDocuments = useMemo(() => new Set(results.map((hit) => hit.docId)).size, [results])\n const docsById = useMemo(\n () => new Map(documents.map((doc) => [doc.external_id, doc])),\n [documents]\n )\n\n // A new result set opens every group; the threshold, the view and paging keep the user's\n // collapsed groups.\n const lastResults = useRef<SearchChunk[] | null>(null)\n useEffect(() => {\n if (results === lastResults.current) return\n lastResults.current = results\n setExpanded(new Set(results.map((hit) => hit.docId)))\n }, [results])\n\n useEffect(() => setPage(1), [results, cutoff, viewMode])\n\n const pageData = useMemo(() => {\n const size = viewMode === 'flat' ? PAGE_SIZE_FLAT : PAGE_SIZE_GROUPED\n const total = viewMode === 'flat' ? shown.length : groups.length\n const totalPages = Math.max(1, Math.ceil(total / size))\n const start = (Math.min(page, totalPages) - 1) * size\n const end = Math.min(start + size, total)\n return {\n totalPages,\n start,\n flat: viewMode === 'flat' ? shown.slice(start, end) : [],\n grouped: viewMode === 'grouped' ? groups.slice(start, end) : ([] as SearchDocumentGroup[]),\n range: { start: total === 0 ? 0 : start + 1, end, total },\n }\n }, [viewMode, page, shown, groups])\n\n const toggleGroup = useCallback((docId: string) => {\n setExpanded((prev) => {\n const next = new Set(prev)\n if (next.has(docId)) next.delete(docId)\n else next.add(docId)\n return next\n })\n }, [])\n\n const openHit = useCallback(\n (e: MouseEvent, hit: SearchChunk) => {\n e.preventDefault()\n e.stopPropagation()\n if ((e.currentTarget as HTMLElement).dataset.clip === 'true')\n useSpotlight.getState().setClipping(true)\n openDocument(hit.docId, { page: hit.page, passageId: hit.chunkExtId })\n },\n [openDocument]\n )\n\n const runSearch = (text: string) => {\n const query = text.trim()\n if (!query || !workspaceId || tooMany) return\n // Search runs on the server: nothing to do offline.\n if (!isOnline) {\n notify(l.offline, 'warning')\n return\n }\n setDraft(text)\n // Every new search starts at Low, a step the threshold buttons show as selected.\n setCutoff(0)\n void search.search(query)\n }\n\n // A query handed over by another surface (the workspace home's ask bar) runs on arrival: the\n // user already pressed Search once. Taken once, so a failed search never retries itself.\n const pending = useSyncExternalStore(sharedSearchSession.subscribe, readPending, readPending)\n useEffect(() => {\n if (pending === null || !workspaceId) return\n const query = takePendingSearch(sharedSearchSession)\n if (query?.trim()) runSearch(query)\n // runSearch is redefined per render; the pending query is the one-shot trigger.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pending, workspaceId])\n\n // The box's clear empties everything and returns to the intro.\n const clear = () => {\n setDraft('')\n search.clear()\n setExpanded(new Set())\n setCutoff(0)\n setPage(1)\n if (inputRef.current) inputRef.current.style.height = 'auto'\n inputRef.current?.focus()\n }\n\n const openLabelDialog = () => {\n setLabelName(l.labelName(submittedQuery.trim()))\n setLabelOpen(true)\n }\n\n const applyLabel = async (name: string) => {\n const trimmed = name.trim()\n if (!workspaceId || listedDocIds.length === 0 || !trimmed) return\n setIsLabelling(true)\n try {\n // A private label; one request then tags every listed document in a single transaction.\n const tag = await createTag.mutateAsync({\n name: trimmed,\n tagType: { type: 'checkbox', options: [] },\n shared: false,\n })\n await assignDocTags.mutateAsync({ tagId: tag.external_id, docIds: listedDocIds })\n notify(l.labelled(listedDocIds.length, trimmed), 'success')\n setLabelOpen(false)\n } catch (err) {\n // The server's reason, so a failed save is something the user can act on.\n notify(err instanceof Error ? err.message : l.labelFailed, 'error')\n } finally {\n setIsLabelling(false)\n }\n }\n\n const viewOptions = useMemo(\n () => [\n {\n value: 'grouped',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <FileText className=\"size-3.5\" />\n {l.byDoc}\n </span>\n ),\n title: l.byDocTitle,\n },\n {\n value: 'flat',\n content: (\n <span className=\"flex items-center gap-1 px-0.5 text-xs\">\n <List className=\"size-3.5\" />\n {l.byPassage}\n </span>\n ),\n title: l.byPassageTitle,\n },\n ],\n [l]\n )\n const viewToggle = (\n <SearchPillToggle\n options={viewOptions}\n value={viewMode}\n onChange={(value) => setViewMode(value as ViewMode)}\n ariaLabel={l.grouping}\n />\n )\n\n const tabs: Tab[] = [\n {\n id: 'search',\n label: l.tab,\n icon: <Search className=\"size-4\" />,\n count: scopeCount,\n hasActiveFilters: !!scopeDocIds,\n actions: scope.filter,\n actionsHoverHandlers: scope.filterHoverHandlers,\n },\n ]\n\n const pagination = (\n <SearchPagination\n currentPage={Math.min(page, pageData.totalPages)}\n totalPages={pageData.totalPages}\n onPageChange={setPage}\n />\n )\n const hasResults = searched && results.length > 0\n\n return (\n <SearchPageFrame isEmbedded={embedded}>\n {!embedded && (\n <SearchPageHeader>\n <TabHeader tabs={tabs} activeTabId=\"search\" onTabChange={() => {}} variant=\"underline\" />\n </SearchPageHeader>\n )}\n\n <ApplyLabelDialog\n open={labelOpen}\n onOpenChange={setLabelOpen}\n name={labelName}\n onNameChange={setLabelName}\n onApply={applyLabel}\n isApplying={isLabelling}\n documentCount={listedDocIds.length}\n passageCount={shown.length}\n />\n\n <SearchPageBody>\n {/* Embedded: the grouping toggle sits on the folder line, visible before any search. */}\n {embedded && <SearchToolbarRow>{viewToggle}</SearchToolbarRow>}\n <SearchBar\n query={draft}\n onQueryChange={setDraft}\n onSubmit={() => runSearch(draft)}\n onClear={clear}\n canSubmit={!!draft.trim() && !!workspaceId && !isSearching && !tooMany}\n isSearching={isSearching}\n placeholder={l.placeholder}\n submitTitle={tooManyMessage}\n notice={tooManyMessage}\n inputRef={inputRef}\n isEmbedded={embedded}\n />\n\n {/* Pinned between the box and the scrolling results. */}\n {hasResults && (\n <SearchControlsBar\n start={\n <SearchTotalsHeadline\n shownHits={shown.length}\n totalHits={results.length}\n shownDocuments={groups.length}\n totalDocuments={totalDocuments}\n meaningHits={meaningHits}\n range={\n pageData.totalPages > 1\n ? { ...pageData.range, unit: viewMode === 'flat' ? 'passages' : 'documents' }\n : null\n }\n action={\n listedDocIds.length > 0 ? (\n <ApplyLabelButton onClick={openLabelDialog} />\n ) : undefined\n }\n />\n }\n end={\n <>\n <RelevanceThreshold value={cutoff} onChange={setCutoff} />\n {!embedded && viewToggle}\n </>\n }\n />\n )}\n\n {hasResults && (\n <p\n className=\"pb-3 pl-6 pr-4 text-xs text-muted-foreground\"\n data-testid=\"smart-search-match-hint\"\n >\n {getSearchMatchHint(submittedQuery)}\n </p>\n )}\n\n <SearchResultsMain>\n {searched || error ? (\n <SearchResultsInner>\n {error ? (\n <SearchErrorBanner message={error} />\n ) : shown.length > 0 ? (\n <>\n {/* The same pager above and below a long page. */}\n {pagination}\n {viewMode === 'flat' ? (\n <SearchResultList testId=\"smart-search-results-flat\">\n {pageData.flat.map((hit, i) => (\n <FlatResultRow\n key={hit.id}\n hit={hit}\n position={pageData.start + i}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchResultList>\n ) : (\n <SearchResultList testId=\"smart-search-results\">\n {pageData.grouped.map((group) => (\n <SearchGroupCard\n key={group.docId}\n testId={`search-result-group-${group.docId}`}\n isExpanded={expanded.has(group.docId)}\n header={\n <SearchGroupRowHeader\n group={group}\n source={\n <ConnectedDocumentPill\n docId={group.docId}\n doc={docsById.get(group.docId)}\n labelMaxWidth=\"max-w-none\"\n />\n }\n isExpanded={expanded.has(group.docId)}\n onToggle={toggleGroup}\n />\n }\n >\n {group.results.map((hit, i) => (\n <GroupedResultRow\n key={hit.id}\n hit={hit}\n testId={`search-result-${group.docId}-${i}`}\n terms={terms}\n onOpen={openHit}\n labels={l}\n />\n ))}\n </SearchGroupCard>\n ))}\n </SearchResultList>\n )}\n {pagination}\n </>\n ) : (\n <SearchNoResults\n hiddenHits={results.length}\n onLowerThreshold={() => setCutoff(0)}\n />\n )}\n </SearchResultsInner>\n ) : documents.length === 0 && openUpload ? (\n <EmptyWorkspaceUpload\n onFilesSelected={(files) => openUpload(files, { source: 'computer' })}\n onUrlsSubmit={uploadUrls}\n onImportFromEmail={() => openUpload(undefined, { source: 'email' })}\n />\n ) : (\n <SearchEmptyState count={scopeCount} />\n )}\n </SearchResultsMain>\n </SearchPageBody>\n </SearchPageFrame>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -32,34 +32,50 @@ const FlatResultRow = memo(function FlatResultRow2({
32
32
  labels
33
33
  }) {
34
34
  const handleClick = useCallback((e) => onOpen(e, hit), [onOpen, hit]);
35
- return /* @__PURE__ */ jsx(
36
- SearchHit,
37
- {
38
- active: useIsOpenPassage(hit.chunkExtId),
39
- header: (
40
- // No `onClick` on the pill: it would render a real <button> inside the card's
41
- // role="button" target (invalid nesting). The card already opens the passage.
42
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
43
- /* @__PURE__ */ jsx(
44
- PassagePill,
45
- {
46
- icon: /* @__PURE__ */ jsx(HtmlIcon, { size: 16, className: "flex-shrink-0" }),
47
- documentTitle: hit.docTitle,
48
- pageNumber: hit.page,
49
- passageIndex: hit.index,
50
- labelMaxWidth: "max-w-none"
51
- }
52
- ),
53
- /* @__PURE__ */ jsx(MeaningMatchBadge, { content: hit.content, terms, labels })
54
- ] })
55
- ),
56
- content: hit.content,
57
- terms,
58
- renderSnippet,
59
- onClick: handleClick,
60
- testId: `search-result-flat-${position}`
61
- }
62
- );
35
+ return /* @__PURE__ */ jsxs("div", { children: [
36
+ /* @__PURE__ */ jsx(
37
+ SearchHit,
38
+ {
39
+ active: useIsOpenPassage(hit.chunkExtId),
40
+ header: (
41
+ // No `onClick` on the pill: it would render a real <button> inside the card's
42
+ // role="button" target (invalid nesting). The card already opens the passage.
43
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
44
+ /* @__PURE__ */ jsx(
45
+ PassagePill,
46
+ {
47
+ icon: /* @__PURE__ */ jsx(HtmlIcon, { size: 16, className: "flex-shrink-0" }),
48
+ documentTitle: hit.docTitle,
49
+ pageNumber: hit.page,
50
+ passageIndex: hit.index,
51
+ labelMaxWidth: "max-w-none"
52
+ }
53
+ ),
54
+ /* @__PURE__ */ jsx(MeaningMatchBadge, { content: hit.content, terms, labels })
55
+ ] })
56
+ ),
57
+ content: hit.content,
58
+ terms,
59
+ renderSnippet,
60
+ onClick: handleClick,
61
+ testId: `search-result-flat-${position}`
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsx(
65
+ "button",
66
+ {
67
+ type: "button",
68
+ className: "rounded border px-2 py-0.5 text-xs",
69
+ onClick: (event) => {
70
+ event.preventDefault();
71
+ event.stopPropagation();
72
+ event.currentTarget.dataset.clip = "true";
73
+ onOpen(event, hit);
74
+ },
75
+ children: "Clip"
76
+ }
77
+ )
78
+ ] });
63
79
  });
64
80
  const GroupedResultRow = memo(function GroupedResultRow2({
65
81
  hit,
@@ -69,21 +85,37 @@ const GroupedResultRow = memo(function GroupedResultRow2({
69
85
  labels
70
86
  }) {
71
87
  const handleClick = useCallback((e) => onOpen(e, hit), [onOpen, hit]);
72
- return /* @__PURE__ */ jsx(
73
- SearchHit,
74
- {
75
- active: useIsOpenPassage(hit.chunkExtId ?? hit.id),
76
- header: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
77
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: hit.page ? labels.page(hit.page) : labels.passage }),
78
- /* @__PURE__ */ jsx(MeaningMatchBadge, { content: hit.content, terms, labels })
79
- ] }),
80
- content: hit.content,
81
- terms,
82
- renderSnippet,
83
- onClick: handleClick,
84
- testId
85
- }
86
- );
88
+ return /* @__PURE__ */ jsxs("div", { children: [
89
+ /* @__PURE__ */ jsx(
90
+ SearchHit,
91
+ {
92
+ active: useIsOpenPassage(hit.chunkExtId ?? hit.id),
93
+ header: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
94
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: hit.page ? labels.page(hit.page) : labels.passage }),
95
+ /* @__PURE__ */ jsx(MeaningMatchBadge, { content: hit.content, terms, labels })
96
+ ] }),
97
+ content: hit.content,
98
+ terms,
99
+ renderSnippet,
100
+ onClick: handleClick,
101
+ testId
102
+ }
103
+ ),
104
+ /* @__PURE__ */ jsx(
105
+ "button",
106
+ {
107
+ type: "button",
108
+ className: "rounded border px-2 py-0.5 text-xs",
109
+ onClick: (event) => {
110
+ event.preventDefault();
111
+ event.stopPropagation();
112
+ event.currentTarget.dataset.clip = "true";
113
+ onOpen(event, hit);
114
+ },
115
+ children: "Clip"
116
+ }
117
+ )
118
+ ] });
87
119
  });
88
120
  const SearchGroupRowHeader = memo(function SearchGroupRowHeader2({
89
121
  group,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/connection/search/SearchResultRows.tsx"],
4
- "sourcesContent": ["/**\n * The search page's rows: a passage in the flat list, a passage inside its document's group, and\n * the group's header. Each is memoized against stable props from the page, so paging or moving\n * the threshold doesn't re-render rows.\n */\n\nimport { memo, useCallback, type MouseEvent, type ReactNode } from 'react'\nimport { HtmlIcon, useArbiSessionState, type SearchChunk } from '@arbidocs/react'\nimport { Badge } from '@arbidocs/react/ui'\nimport { PassagePill } from '../../citations/PassagePill'\nimport { SearchGroupHeader } from '../../retrieval/SearchGroupHeader'\nimport { SearchHit } from '../../retrieval/SearchHit'\nimport { renderSearchSnippet as renderSnippet } from '../../retrieval/SearchSnippet'\nimport { isMeaningMatch, type SearchDocumentGroup } from '../../retrieval/search-results'\n\nexport interface SearchRowLabels {\n /** Badge on a passage that contains none of the query's words. */\n meaningMatch: string\n meaningMatchTitle: string\n /** The page a grouped passage is on. */\n page: (page: number) => string\n /** A grouped passage without a page. */\n passage: string\n}\n\nexport const DEFAULT_SEARCH_ROW_LABELS: SearchRowLabels = {\n meaningMatch: 'Matched by meaning',\n meaningMatchTitle: 'None of your search words appear in this passage. It matched by meaning.',\n page: (page) => `p.${page}`,\n passage: 'Passage',\n}\n\n/** Whether the passage is the one open in the document viewer. */\nfunction useIsOpenPassage(chunkExtId: string | undefined): boolean {\n return useArbiSessionState((s) => !!chunkExtId && s.viewingDocument?.passageId === chunkExtId)\n}\n\nfunction MeaningMatchBadge({\n content,\n terms,\n labels,\n}: {\n content: string\n terms: string[]\n labels: SearchRowLabels\n}) {\n return isMeaningMatch(content, terms) ? (\n <Badge variant=\"outline\" className=\"text-muted-foreground\" title={labels.meaningMatchTitle}>\n {labels.meaningMatch}\n </Badge>\n ) : null\n}\n\ninterface RowProps {\n hit: SearchChunk\n terms: string[]\n onOpen: (e: MouseEvent, hit: SearchChunk) => void\n labels: SearchRowLabels\n}\n\nexport const FlatResultRow = memo(function FlatResultRow({\n hit,\n position,\n terms,\n onOpen,\n labels,\n}: RowProps & { position: number }) {\n const handleClick = useCallback((e: MouseEvent) => onOpen(e, hit), [onOpen, hit])\n return (\n <SearchHit\n active={useIsOpenPassage(hit.chunkExtId)}\n header={\n // No `onClick` on the pill: it would render a real <button> inside the card's\n // role=\"button\" target (invalid nesting). The card already opens the passage.\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <PassagePill\n icon={<HtmlIcon size={16} className=\"flex-shrink-0\" />}\n documentTitle={hit.docTitle}\n pageNumber={hit.page}\n passageIndex={hit.index}\n labelMaxWidth=\"max-w-none\"\n />\n <MeaningMatchBadge content={hit.content} terms={terms} labels={labels} />\n </div>\n }\n content={hit.content}\n terms={terms}\n renderSnippet={renderSnippet}\n onClick={handleClick}\n testId={`search-result-flat-${position}`}\n />\n )\n})\n\nexport const GroupedResultRow = memo(function GroupedResultRow({\n hit,\n testId,\n terms,\n onOpen,\n labels,\n}: RowProps & { testId: string }) {\n const handleClick = useCallback((e: MouseEvent) => onOpen(e, hit), [onOpen, hit])\n return (\n <SearchHit\n active={useIsOpenPassage(hit.chunkExtId ?? hit.id)}\n header={\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {hit.page ? labels.page(hit.page) : labels.passage}\n </span>\n <MeaningMatchBadge content={hit.content} terms={terms} labels={labels} />\n </div>\n }\n content={hit.content}\n terms={terms}\n renderSnippet={renderSnippet}\n onClick={handleClick}\n testId={testId}\n />\n )\n})\n\nexport const SearchGroupRowHeader = memo(function SearchGroupRowHeader({\n group,\n source,\n isExpanded,\n onToggle,\n}: {\n group: SearchDocumentGroup\n /** The document's chip. */\n source: ReactNode\n isExpanded: boolean\n onToggle: (docId: string) => void\n}) {\n return (\n <SearchGroupHeader\n groupId={group.docId}\n hitCount={group.results.length}\n isExpanded={isExpanded}\n onToggle={onToggle}\n source={source}\n />\n )\n})\n"],
5
- "mappings": ";AA+CI,cA2BI,YA3BJ;AAzCJ,SAAS,MAAM,mBAAoD;AACnE,SAAS,UAAU,2BAA6C;AAChE,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,uBAAuB,qBAAqB;AACrD,SAAS,sBAAgD;AAYlD,MAAM,4BAA6C;AAAA,EACxD,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,MAAM,CAAC,SAAS,KAAK,IAAI;AAAA,EACzB,SAAS;AACX;AAGA,SAAS,iBAAiB,YAAyC;AACjE,SAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC,cAAc,EAAE,iBAAiB,cAAc,UAAU;AAC/F;AAEA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SAAO,eAAe,SAAS,KAAK,IAClC,oBAAC,SAAM,SAAQ,WAAU,WAAU,yBAAwB,OAAO,OAAO,mBACtE,iBAAO,cACV,IACE;AACN;AASO,MAAM,gBAAgB,KAAK,SAASA,eAAc;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAoC;AAClC,QAAM,cAAc,YAAY,CAAC,MAAkB,OAAO,GAAG,GAAG,GAAG,CAAC,QAAQ,GAAG,CAAC;AAChF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ,iBAAiB,IAAI,UAAU;AAAA,MACvC;AAAA;AAAA;AAAA,QAGE,qBAAC,SAAI,WAAU,6CACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,oBAAC,YAAS,MAAM,IAAI,WAAU,iBAAgB;AAAA,cACpD,eAAe,IAAI;AAAA,cACnB,YAAY,IAAI;AAAA,cAChB,cAAc,IAAI;AAAA,cAClB,eAAc;AAAA;AAAA,UAChB;AAAA,UACA,oBAAC,qBAAkB,SAAS,IAAI,SAAS,OAAc,QAAgB;AAAA,WACzE;AAAA;AAAA,MAEF,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ,sBAAsB,QAAQ;AAAA;AAAA,EACxC;AAEJ,CAAC;AAEM,MAAM,mBAAmB,KAAK,SAASC,kBAAiB;AAAA,EAC7D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkC;AAChC,QAAM,cAAc,YAAY,CAAC,MAAkB,OAAO,GAAG,GAAG,GAAG,CAAC,QAAQ,GAAG,CAAC;AAChF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ,iBAAiB,IAAI,cAAc,IAAI,EAAE;AAAA,MACjD,QACE,qBAAC,SAAI,WAAU,6CACb;AAAA,4BAAC,UAAK,WAAU,6CACb,cAAI,OAAO,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,SAC7C;AAAA,QACA,oBAAC,qBAAkB,SAAS,IAAI,SAAS,OAAc,QAAgB;AAAA,SACzE;AAAA,MAEF,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA;AAAA,EACF;AAEJ,CAAC;AAEM,MAAM,uBAAuB,KAAK,SAASC,sBAAqB;AAAA,EACrE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,MAAM;AAAA,MACf,UAAU,MAAM,QAAQ;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,EACF;AAEJ,CAAC;",
4
+ "sourcesContent": ["/**\n * The search page's rows: a passage in the flat list, a passage inside its document's group, and\n * the group's header. Each is memoized against stable props from the page, so paging or moving\n * the threshold doesn't re-render rows.\n */\n\nimport { memo, useCallback, type MouseEvent, type ReactNode } from 'react'\nimport { HtmlIcon, useArbiSessionState, type SearchChunk } from '@arbidocs/react'\nimport { Badge } from '@arbidocs/react/ui'\nimport { PassagePill } from '../../citations/PassagePill'\nimport { SearchGroupHeader } from '../../retrieval/SearchGroupHeader'\nimport { SearchHit } from '../../retrieval/SearchHit'\nimport { renderSearchSnippet as renderSnippet } from '../../retrieval/SearchSnippet'\nimport { isMeaningMatch, type SearchDocumentGroup } from '../../retrieval/search-results'\n\nexport interface SearchRowLabels {\n /** Badge on a passage that contains none of the query's words. */\n meaningMatch: string\n meaningMatchTitle: string\n /** The page a grouped passage is on. */\n page: (page: number) => string\n /** A grouped passage without a page. */\n passage: string\n}\n\nexport const DEFAULT_SEARCH_ROW_LABELS: SearchRowLabels = {\n meaningMatch: 'Matched by meaning',\n meaningMatchTitle: 'None of your search words appear in this passage. It matched by meaning.',\n page: (page) => `p.${page}`,\n passage: 'Passage',\n}\n\n/** Whether the passage is the one open in the document viewer. */\nfunction useIsOpenPassage(chunkExtId: string | undefined): boolean {\n return useArbiSessionState((s) => !!chunkExtId && s.viewingDocument?.passageId === chunkExtId)\n}\n\nfunction MeaningMatchBadge({\n content,\n terms,\n labels,\n}: {\n content: string\n terms: string[]\n labels: SearchRowLabels\n}) {\n return isMeaningMatch(content, terms) ? (\n <Badge variant=\"outline\" className=\"text-muted-foreground\" title={labels.meaningMatchTitle}>\n {labels.meaningMatch}\n </Badge>\n ) : null\n}\n\ninterface RowProps {\n hit: SearchChunk\n terms: string[]\n onOpen: (e: MouseEvent, hit: SearchChunk) => void\n labels: SearchRowLabels\n}\n\nexport const FlatResultRow = memo(function FlatResultRow({\n hit,\n position,\n terms,\n onOpen,\n labels,\n}: RowProps & { position: number }) {\n const handleClick = useCallback((e: MouseEvent) => onOpen(e, hit), [onOpen, hit])\n return (\n <div>\n <SearchHit\n active={useIsOpenPassage(hit.chunkExtId)}\n header={\n // No `onClick` on the pill: it would render a real <button> inside the card's\n // role=\"button\" target (invalid nesting). The card already opens the passage.\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <PassagePill\n icon={<HtmlIcon size={16} className=\"flex-shrink-0\" />}\n documentTitle={hit.docTitle}\n pageNumber={hit.page}\n passageIndex={hit.index}\n labelMaxWidth=\"max-w-none\"\n />\n <MeaningMatchBadge content={hit.content} terms={terms} labels={labels} />\n </div>\n }\n content={hit.content}\n terms={terms}\n renderSnippet={renderSnippet}\n onClick={handleClick}\n testId={`search-result-flat-${position}`}\n />\n <button\n type=\"button\"\n className=\"rounded border px-2 py-0.5 text-xs\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n event.currentTarget.dataset.clip = 'true'\n onOpen(event, hit)\n }}\n >\n Clip\n </button>\n </div>\n )\n})\n\nexport const GroupedResultRow = memo(function GroupedResultRow({\n hit,\n testId,\n terms,\n onOpen,\n labels,\n}: RowProps & { testId: string }) {\n const handleClick = useCallback((e: MouseEvent) => onOpen(e, hit), [onOpen, hit])\n return (\n <div>\n <SearchHit\n active={useIsOpenPassage(hit.chunkExtId ?? hit.id)}\n header={\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {hit.page ? labels.page(hit.page) : labels.passage}\n </span>\n <MeaningMatchBadge content={hit.content} terms={terms} labels={labels} />\n </div>\n }\n content={hit.content}\n terms={terms}\n renderSnippet={renderSnippet}\n onClick={handleClick}\n testId={testId}\n />\n <button\n type=\"button\"\n className=\"rounded border px-2 py-0.5 text-xs\"\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n event.currentTarget.dataset.clip = 'true'\n onOpen(event, hit)\n }}\n >\n Clip\n </button>\n </div>\n )\n})\n\nexport const SearchGroupRowHeader = memo(function SearchGroupRowHeader({\n group,\n source,\n isExpanded,\n onToggle,\n}: {\n group: SearchDocumentGroup\n /** The document's chip. */\n source: ReactNode\n isExpanded: boolean\n onToggle: (docId: string) => void\n}) {\n return (\n <SearchGroupHeader\n groupId={group.docId}\n hitCount={group.results.length}\n isExpanded={isExpanded}\n onToggle={onToggle}\n source={source}\n />\n )\n})\n"],
5
+ "mappings": "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",
6
6
  "names": ["FlatResultRow", "GroupedResultRow", "SearchGroupRowHeader"]
7
7
  }
@@ -1,8 +1,10 @@
1
+ import { type ReactNode } from 'react';
1
2
  /**
2
3
  * Over the viewer's clean view: Full screen, and Show controls (leave the clean view). It shows
3
4
  * while the pointer moves or a key is pressed and fades after a moment, so a share rarely
4
5
  * catches it.
5
6
  */
6
- export declare function CleanViewControl({ onExit }: {
7
+ export declare function CleanViewControl({ onExit, children, }: {
7
8
  onExit: () => void;
9
+ children?: ReactNode;
8
10
  }): import("react").JSX.Element;
@@ -5,7 +5,10 @@ import { Maximize, PanelTop } from "lucide-react";
5
5
  import { Button, cn } from "@arbidocs/react/ui";
6
6
  import { toggleFullscreen } from "../../presentation/usePresenterKeys.js";
7
7
  const IDLE_MS = 2e3;
8
- function CleanViewControl({ onExit }) {
8
+ function CleanViewControl({
9
+ onExit,
10
+ children
11
+ }) {
9
12
  const [view, setView] = useState(null);
10
13
  const [shown, setShown] = useState(true);
11
14
  const ref = useCallback((node) => {
@@ -37,6 +40,7 @@ function CleanViewControl({ onExit }) {
37
40
  ),
38
41
  "data-testid": "viewer-clean-control",
39
42
  children: [
43
+ children,
40
44
  /* @__PURE__ */ jsxs(
41
45
  Button,
42
46
  {