@arbidocs/react 0.5.119 → 0.5.121

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 (86) hide show
  1. package/dist/PdfViewerImpl-ZVJZ4KNW.cjs +22 -0
  2. package/dist/PdfViewerImpl-ZVJZ4KNW.cjs.map +1 -0
  3. package/dist/{PdfViewerImpl.d-B28hLEuj.d.ts → PdfViewerImpl.d-CqnEzQO7.d.ts} +9 -1
  4. package/dist/annotations/freeze/index.d.ts +1 -1
  5. package/dist/artifacts/artifact-kind.js +1 -0
  6. package/dist/artifacts/artifact-kind.js.map +2 -2
  7. package/dist/artifacts/index.cjs +3 -2
  8. package/dist/artifacts/index.cjs.map +1 -1
  9. package/dist/artifacts/index.d.ts +3 -2
  10. package/dist/calls/livekit/index.cjs +41 -41
  11. package/dist/chat/turn-flush.js +16 -0
  12. package/dist/chat/turn-flush.js.map +7 -0
  13. package/dist/chat/useConversationActions.js +3 -0
  14. package/dist/chat/useConversationActions.js.map +2 -2
  15. package/dist/{chunk-QP3WQSAA.cjs → chunk-4B2GW3AY.cjs} +3 -3
  16. package/dist/{chunk-QP3WQSAA.cjs.map → chunk-4B2GW3AY.cjs.map} +1 -1
  17. package/dist/{chunk-YMB6XUR2.cjs → chunk-7L7QRIQ2.cjs} +11 -7
  18. package/dist/chunk-7L7QRIQ2.cjs.map +1 -0
  19. package/dist/{chunk-LBAG7KL7.cjs → chunk-DLEHM4IA.cjs} +328 -180
  20. package/dist/chunk-DLEHM4IA.cjs.map +1 -0
  21. package/dist/{PdfViewerImpl-QUSDLNNM.cjs → chunk-ICBT2XGF.cjs} +5 -2
  22. package/dist/chunk-ICBT2XGF.cjs.map +1 -0
  23. package/dist/chunk-MAOGK7IC.cjs +61 -0
  24. package/dist/chunk-MAOGK7IC.cjs.map +1 -0
  25. package/dist/{chunk-5TRUFWKT.cjs → chunk-X5OZRLFZ.cjs} +5 -5
  26. package/dist/{chunk-5TRUFWKT.cjs.map → chunk-X5OZRLFZ.cjs.map} +1 -1
  27. package/dist/datasets/sources/documents.js +1 -1
  28. package/dist/datasets/sources/documents.js.map +2 -2
  29. package/dist/document-tools/images-to-pdf.js +43 -0
  30. package/dist/document-tools/images-to-pdf.js.map +7 -0
  31. package/dist/document-tools/index.cjs +104 -0
  32. package/dist/document-tools/index.cjs.map +1 -1
  33. package/dist/document-tools/index.d.ts +41 -1
  34. package/dist/document-tools/index.js +6 -1
  35. package/dist/document-tools/index.js.map +2 -2
  36. package/dist/document-tools/pdf-attachments.js +63 -0
  37. package/dist/document-tools/pdf-attachments.js.map +7 -0
  38. package/dist/entities/DocumentEntity.js +7 -2
  39. package/dist/entities/DocumentEntity.js.map +2 -2
  40. package/dist/entities/SharingEntity.js +85 -0
  41. package/dist/entities/SharingEntity.js.map +7 -0
  42. package/dist/entities/file-type.js +14 -2
  43. package/dist/entities/file-type.js.map +2 -2
  44. package/dist/entities/index.js +17 -1
  45. package/dist/entities/index.js.map +2 -2
  46. package/dist/icons/PresIcon.js +53 -0
  47. package/dist/icons/PresIcon.js.map +7 -0
  48. package/dist/icons/colors.js +2 -0
  49. package/dist/icons/colors.js.map +2 -2
  50. package/dist/icons/file-type.js +3 -0
  51. package/dist/icons/file-type.js.map +2 -2
  52. package/dist/icons/index.js +2 -0
  53. package/dist/icons/index.js.map +2 -2
  54. package/dist/index.cjs +834 -789
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/{index.d-CYXcTKYt.d.ts → index.d-D2puqWeN.d.ts} +47 -3
  57. package/dist/index.d.ts +50 -8
  58. package/dist/index.js +1 -0
  59. package/dist/index.js.map +2 -2
  60. package/dist/pdf-region-impl-ZG5G2OX2.cjs +98 -0
  61. package/dist/pdf-region-impl-ZG5G2OX2.cjs.map +1 -0
  62. package/dist/session/documents.js +9 -5
  63. package/dist/session/documents.js.map +2 -2
  64. package/dist/ui/index.cjs +182 -158
  65. package/dist/ui/index.d.ts +1 -1
  66. package/dist/ui/slider.js +7 -1
  67. package/dist/ui/slider.js.map +2 -2
  68. package/dist/viewers/PdfViewer.cjs +3 -3
  69. package/dist/viewers/PdfViewer.d.ts +2 -2
  70. package/dist/viewers/PdfViewerImpl.js +4 -1
  71. package/dist/viewers/PdfViewerImpl.js.map +2 -2
  72. package/dist/viewers/index.cjs +30 -4
  73. package/dist/viewers/index.cjs.map +1 -1
  74. package/dist/viewers/index.d.ts +72 -2
  75. package/dist/viewers/index.js +14 -0
  76. package/dist/viewers/index.js.map +2 -2
  77. package/dist/viewers/pdf-region-impl.js +96 -0
  78. package/dist/viewers/pdf-region-impl.js.map +7 -0
  79. package/dist/viewers/pdf-region.js +57 -0
  80. package/dist/viewers/pdf-region.js.map +7 -0
  81. package/dist/viewers/useProcessedChunks.js +2 -1
  82. package/dist/viewers/useProcessedChunks.js.map +2 -2
  83. package/package.json +3 -3
  84. package/dist/PdfViewerImpl-QUSDLNNM.cjs.map +0 -1
  85. package/dist/chunk-LBAG7KL7.cjs.map +0 -1
  86. package/dist/chunk-YMB6XUR2.cjs.map +0 -1
@@ -71,7 +71,7 @@ function createDocumentsSource(labels) {
71
71
  updated: d.updated_at ?? null,
72
72
  documentDate: d.doc_metadata?.doc_date ?? null,
73
73
  status: d.status ? l.statuses[d.status] ?? d.status : null,
74
- type: friendlyFileType(d.file_type, d.file_name) || null,
74
+ type: friendlyFileType(d.file_type, d.file_name, d.folder) || null,
75
75
  folder: d.folder || null,
76
76
  nature: d.doc_metadata?.doc_nature ?? null,
77
77
  author: d.doc_metadata?.doc_author ?? null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/datasets/sources/documents.ts"],
4
- "sourcesContent": ["/**\n * The documents of a workspace as a dataset (default: the open one; `params.workspaceId` names\n * another): counts and status by type, folder, author or time. Reads the shared documents list\n * (`useDocuments`), which live updates keep current, so a panel follows uploads as they process.\n */\nimport { useMemo } from 'react'\nimport { useDocuments } from '../../documents/documents-data'\nimport { friendlyFileType } from '../../entities/file-type'\nimport { useNames } from '../members'\nimport { defineRowsSource, staticFields } from '../rows-source'\nimport type { DataSource, DataSourceParams, DatasetField, DatasetRow } from '../types'\n\n/** The field labels (a site's words). */\nexport interface DocumentsSourceLabels {\n source: string\n description: string\n created: string\n updated: string\n documentDate: string\n status: string\n /** A processing status by the server's name; one not listed is shown as it comes. */\n statuses: Readonly<Record<string, string>>\n type: string\n folder: string\n nature: string\n author: string\n createdBy: string\n pages: string\n size: string\n shared: string\n}\n\nexport const DEFAULT_DOCUMENTS_SOURCE_LABELS: DocumentsSourceLabels = {\n source: 'Documents',\n description: 'The documents of the open workspace: type, folder, status, dates.',\n created: 'Added',\n updated: 'Updated',\n documentDate: 'Document date',\n status: 'Status',\n statuses: {\n completed: 'Ready',\n 'low-content': 'Little text',\n failed: 'Failed',\n skipped: 'Skipped',\n empty: 'Empty',\n queued: 'Queued',\n uploading: 'Uploading',\n parsing: 'Processing',\n encrypting: 'Processing',\n indexing: 'Processing',\n analysing: 'Processing',\n },\n type: 'Type',\n folder: 'Folder',\n nature: 'Kind of document',\n author: 'Author',\n createdBy: 'Added by',\n pages: 'Pages',\n size: 'Size',\n shared: 'Shared',\n}\n\ninterface DocumentLike {\n external_id: string\n file_name?: string | null\n file_type?: string | null\n file_size?: number | null\n status?: string | null\n folder?: string | null\n n_pages?: number | null\n shared?: boolean | null\n created_by_ext_id?: string | null\n created_at?: string | null\n updated_at?: string | null\n doc_metadata?: {\n doc_nature?: string | null\n doc_author?: string | null\n doc_date?: string | null\n title?: string | null\n } | null\n}\n\nconst workspaceOf = (params: DataSourceParams) =>\n typeof params.workspaceId === 'string' && params.workspaceId ? params.workspaceId : undefined\n\nexport function createDocumentsSource(labels?: Partial<DocumentsSourceLabels>): DataSource {\n const l = { ...DEFAULT_DOCUMENTS_SOURCE_LABELS, ...labels }\n const fields: DatasetField[] = [\n { id: 'name', label: l.source, type: 'string' },\n { id: 'created', label: l.created, type: 'date' },\n { id: 'updated', label: l.updated, type: 'date' },\n { id: 'documentDate', label: l.documentDate, type: 'date' },\n {\n id: 'status',\n label: l.status,\n type: 'string',\n options: [...new Set(Object.values(l.statuses))],\n },\n { id: 'type', label: l.type, type: 'string' },\n { id: 'folder', label: l.folder, type: 'string' },\n { id: 'nature', label: l.nature, type: 'string' },\n { id: 'author', label: l.author, type: 'string' },\n { id: 'createdBy', label: l.createdBy, type: 'string' },\n { id: 'pages', label: l.pages, type: 'number' },\n { id: 'size', label: l.size, type: 'number', unit: 'bytes' },\n { id: 'shared', label: l.shared, type: 'boolean' },\n ]\n return defineRowsSource({\n id: 'documents',\n scope: 'workspace',\n label: l.source,\n description: l.description,\n useFields: staticFields(fields),\n useRows(params, enabled) {\n const query = useDocuments(workspaceOf(params), { enabled })\n const nameOf = useNames(enabled)\n const rows = useMemo(\n () =>\n (query.data as DocumentLike[] | undefined)?.map((d): DatasetRow => ({\n name: d.doc_metadata?.title || d.file_name || d.external_id,\n created: d.created_at ?? null,\n updated: d.updated_at ?? null,\n documentDate: d.doc_metadata?.doc_date ?? null,\n status: d.status ? (l.statuses[d.status] ?? d.status) : null,\n type: friendlyFileType(d.file_type, d.file_name) || null,\n folder: d.folder || null,\n nature: d.doc_metadata?.doc_nature ?? null,\n author: d.doc_metadata?.doc_author ?? null,\n createdBy: nameOf(d.created_by_ext_id),\n pages: d.n_pages ?? null,\n size: d.file_size ?? null,\n shared: d.shared ?? null,\n })),\n [query.data, nameOf]\n )\n return { rows, isLoading: query.isLoading, error: query.error }\n },\n })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * The documents of a workspace as a dataset (default: the open one; `params.workspaceId` names\n * another): counts and status by type, folder, author or time. Reads the shared documents list\n * (`useDocuments`), which live updates keep current, so a panel follows uploads as they process.\n */\nimport { useMemo } from 'react'\nimport { useDocuments } from '../../documents/documents-data'\nimport { friendlyFileType } from '../../entities/file-type'\nimport { useNames } from '../members'\nimport { defineRowsSource, staticFields } from '../rows-source'\nimport type { DataSource, DataSourceParams, DatasetField, DatasetRow } from '../types'\n\n/** The field labels (a site's words). */\nexport interface DocumentsSourceLabels {\n source: string\n description: string\n created: string\n updated: string\n documentDate: string\n status: string\n /** A processing status by the server's name; one not listed is shown as it comes. */\n statuses: Readonly<Record<string, string>>\n type: string\n folder: string\n nature: string\n author: string\n createdBy: string\n pages: string\n size: string\n shared: string\n}\n\nexport const DEFAULT_DOCUMENTS_SOURCE_LABELS: DocumentsSourceLabels = {\n source: 'Documents',\n description: 'The documents of the open workspace: type, folder, status, dates.',\n created: 'Added',\n updated: 'Updated',\n documentDate: 'Document date',\n status: 'Status',\n statuses: {\n completed: 'Ready',\n 'low-content': 'Little text',\n failed: 'Failed',\n skipped: 'Skipped',\n empty: 'Empty',\n queued: 'Queued',\n uploading: 'Uploading',\n parsing: 'Processing',\n encrypting: 'Processing',\n indexing: 'Processing',\n analysing: 'Processing',\n },\n type: 'Type',\n folder: 'Folder',\n nature: 'Kind of document',\n author: 'Author',\n createdBy: 'Added by',\n pages: 'Pages',\n size: 'Size',\n shared: 'Shared',\n}\n\ninterface DocumentLike {\n external_id: string\n file_name?: string | null\n file_type?: string | null\n file_size?: number | null\n status?: string | null\n folder?: string | null\n n_pages?: number | null\n shared?: boolean | null\n created_by_ext_id?: string | null\n created_at?: string | null\n updated_at?: string | null\n doc_metadata?: {\n doc_nature?: string | null\n doc_author?: string | null\n doc_date?: string | null\n title?: string | null\n } | null\n}\n\nconst workspaceOf = (params: DataSourceParams) =>\n typeof params.workspaceId === 'string' && params.workspaceId ? params.workspaceId : undefined\n\nexport function createDocumentsSource(labels?: Partial<DocumentsSourceLabels>): DataSource {\n const l = { ...DEFAULT_DOCUMENTS_SOURCE_LABELS, ...labels }\n const fields: DatasetField[] = [\n { id: 'name', label: l.source, type: 'string' },\n { id: 'created', label: l.created, type: 'date' },\n { id: 'updated', label: l.updated, type: 'date' },\n { id: 'documentDate', label: l.documentDate, type: 'date' },\n {\n id: 'status',\n label: l.status,\n type: 'string',\n options: [...new Set(Object.values(l.statuses))],\n },\n { id: 'type', label: l.type, type: 'string' },\n { id: 'folder', label: l.folder, type: 'string' },\n { id: 'nature', label: l.nature, type: 'string' },\n { id: 'author', label: l.author, type: 'string' },\n { id: 'createdBy', label: l.createdBy, type: 'string' },\n { id: 'pages', label: l.pages, type: 'number' },\n { id: 'size', label: l.size, type: 'number', unit: 'bytes' },\n { id: 'shared', label: l.shared, type: 'boolean' },\n ]\n return defineRowsSource({\n id: 'documents',\n scope: 'workspace',\n label: l.source,\n description: l.description,\n useFields: staticFields(fields),\n useRows(params, enabled) {\n const query = useDocuments(workspaceOf(params), { enabled })\n const nameOf = useNames(enabled)\n const rows = useMemo(\n () =>\n (query.data as DocumentLike[] | undefined)?.map((d): DatasetRow => ({\n name: d.doc_metadata?.title || d.file_name || d.external_id,\n created: d.created_at ?? null,\n updated: d.updated_at ?? null,\n documentDate: d.doc_metadata?.doc_date ?? null,\n status: d.status ? (l.statuses[d.status] ?? d.status) : null,\n type: friendlyFileType(d.file_type, d.file_name, d.folder) || null,\n folder: d.folder || null,\n nature: d.doc_metadata?.doc_nature ?? null,\n author: d.doc_metadata?.doc_author ?? null,\n createdBy: nameOf(d.created_by_ext_id),\n pages: d.n_pages ?? null,\n size: d.file_size ?? null,\n shared: d.shared ?? null,\n })),\n [query.data, nameOf]\n )\n return { rows, isLoading: query.isLoading, error: query.error }\n },\n })\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,43 @@
1
+ async function imagesToPdf(pages, meta = {}) {
2
+ const { PDFDocument, StandardFonts } = await import("pdf-lib");
3
+ const pdf = await PDFDocument.create();
4
+ if (meta.title) pdf.setTitle(meta.title);
5
+ const now = /* @__PURE__ */ new Date();
6
+ for (const attachment of meta.attachments ?? []) {
7
+ await pdf.attach(new Uint8Array(attachment.data), attachment.name, {
8
+ mimeType: attachment.mimeType,
9
+ description: attachment.description,
10
+ creationDate: now,
11
+ modificationDate: now
12
+ });
13
+ }
14
+ const font = await pdf.embedFont(StandardFonts.Helvetica);
15
+ const encodable = (line) => [...line].filter((ch) => {
16
+ try {
17
+ font.encodeText(ch);
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }).join("");
23
+ for (const entry of pages) {
24
+ const bytes = new Uint8Array(await entry.image.arrayBuffer());
25
+ const image = entry.image.type === "image/png" ? await pdf.embedPng(bytes) : await pdf.embedJpg(bytes);
26
+ const page = pdf.addPage([entry.width, entry.height]);
27
+ page.drawImage(image, { x: 0, y: 0, width: entry.width, height: entry.height });
28
+ let y = entry.height - 14;
29
+ for (const line of entry.text ?? []) {
30
+ const text = encodable(line).trim();
31
+ if (!text) continue;
32
+ page.drawText(text, { x: 8, y, size: 10, font, opacity: 0 });
33
+ y -= 12;
34
+ if (y < 4) break;
35
+ }
36
+ }
37
+ const saved = await pdf.save();
38
+ return new Blob([saved], { type: "application/pdf" });
39
+ }
40
+ export {
41
+ imagesToPdf
42
+ };
43
+ //# sourceMappingURL=images-to-pdf.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/document-tools/images-to-pdf.ts"],
4
+ "sourcesContent": ["/**\n * Page images to a PDF: one page per image, the image filling it, and the page's text laid on it\n * invisibly so the PDF is searchable (a presentation's slides: what shows is exactly the image,\n * redactions burnt in, while their titles and labels can still be found). pdf-lib is loaded on\n * first use. Files can be attached to it (`meta.attachments`; see `pdf-attachments`).\n */\n\nimport type { PdfAttachment } from './pdf-attachments'\n\nexport interface PdfImagePage {\n /** A JPEG or PNG. */\n image: Blob\n /** The page size, in PDF points. */\n width: number\n height: number\n /** Text on the page, laid on it invisibly for search. */\n text?: readonly string[]\n}\n\nexport async function imagesToPdf(\n pages: readonly PdfImagePage[],\n meta: { title?: string; attachments?: readonly PdfAttachment[] } = {}\n): Promise<Blob> {\n const { PDFDocument, StandardFonts } = await import('pdf-lib')\n const pdf = await PDFDocument.create()\n if (meta.title) pdf.setTitle(meta.title)\n const now = new Date()\n for (const attachment of meta.attachments ?? []) {\n // Copied: bytes made in another realm (Node's TextEncoder under jsdom) fail pdf-lib's type check.\n await pdf.attach(new Uint8Array(attachment.data), attachment.name, {\n mimeType: attachment.mimeType,\n description: attachment.description,\n creationDate: now,\n modificationDate: now,\n })\n }\n const font = await pdf.embedFont(StandardFonts.Helvetica)\n // The standard font covers Latin text; characters it cannot encode are left out of the hidden\n // layer rather than failing the export.\n const encodable = (line: string) =>\n [...line]\n .filter((ch) => {\n try {\n font.encodeText(ch)\n return true\n } catch {\n return false\n }\n })\n .join('')\n\n for (const entry of pages) {\n const bytes = new Uint8Array(await entry.image.arrayBuffer())\n const image =\n entry.image.type === 'image/png' ? await pdf.embedPng(bytes) : await pdf.embedJpg(bytes)\n const page = pdf.addPage([entry.width, entry.height])\n page.drawImage(image, { x: 0, y: 0, width: entry.width, height: entry.height })\n let y = entry.height - 14\n for (const line of entry.text ?? []) {\n const text = encodable(line).trim()\n if (!text) continue\n page.drawText(text, { x: 8, y, size: 10, font, opacity: 0 })\n y -= 12\n if (y < 4) break\n }\n }\n const saved = await pdf.save()\n return new Blob([saved as BlobPart], { type: 'application/pdf' })\n}\n"],
5
+ "mappings": "AAmBA,eAAsB,YACpB,OACA,OAAmE,CAAC,GACrD;AACf,QAAM,EAAE,aAAa,cAAc,IAAI,MAAM,OAAO,SAAS;AAC7D,QAAM,MAAM,MAAM,YAAY,OAAO;AACrC,MAAI,KAAK,MAAO,KAAI,SAAS,KAAK,KAAK;AACvC,QAAM,MAAM,oBAAI,KAAK;AACrB,aAAW,cAAc,KAAK,eAAe,CAAC,GAAG;AAE/C,UAAM,IAAI,OAAO,IAAI,WAAW,WAAW,IAAI,GAAG,WAAW,MAAM;AAAA,MACjE,UAAU,WAAW;AAAA,MACrB,aAAa,WAAW;AAAA,MACxB,cAAc;AAAA,MACd,kBAAkB;AAAA,IACpB,CAAC;AAAA,EACH;AACA,QAAM,OAAO,MAAM,IAAI,UAAU,cAAc,SAAS;AAGxD,QAAM,YAAY,CAAC,SACjB,CAAC,GAAG,IAAI,EACL,OAAO,CAAC,OAAO;AACd,QAAI;AACF,WAAK,WAAW,EAAE;AAClB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,CAAC,EACA,KAAK,EAAE;AAEZ,aAAW,SAAS,OAAO;AACzB,UAAM,QAAQ,IAAI,WAAW,MAAM,MAAM,MAAM,YAAY,CAAC;AAC5D,UAAM,QACJ,MAAM,MAAM,SAAS,cAAc,MAAM,IAAI,SAAS,KAAK,IAAI,MAAM,IAAI,SAAS,KAAK;AACzF,UAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,OAAO,MAAM,MAAM,CAAC;AACpD,SAAK,UAAU,OAAO,EAAE,GAAG,GAAG,GAAG,GAAG,OAAO,MAAM,OAAO,QAAQ,MAAM,OAAO,CAAC;AAC9E,QAAI,IAAI,MAAM,SAAS;AACvB,eAAW,QAAQ,MAAM,QAAQ,CAAC,GAAG;AACnC,YAAM,OAAO,UAAU,IAAI,EAAE,KAAK;AAClC,UAAI,CAAC,KAAM;AACX,WAAK,SAAS,MAAM,EAAE,GAAG,GAAG,GAAG,MAAM,IAAI,MAAM,SAAS,EAAE,CAAC;AAC3D,WAAK;AACL,UAAI,IAAI,EAAG;AAAA,IACb;AAAA,EACF;AACA,QAAM,QAAQ,MAAM,IAAI,KAAK;AAC7B,SAAO,IAAI,KAAK,CAAC,KAAiB,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAClE;",
6
+ "names": []
7
+ }
@@ -146,6 +146,107 @@ async function buildProduction(client, documents, choices, options) {
146
146
  };
147
147
  }
148
148
 
149
+ // src/document-tools/images-to-pdf.ts
150
+ async function imagesToPdf(pages, meta = {}) {
151
+ const { PDFDocument, StandardFonts } = await import('pdf-lib');
152
+ const pdf = await PDFDocument.create();
153
+ if (meta.title) pdf.setTitle(meta.title);
154
+ const now = /* @__PURE__ */ new Date();
155
+ for (const attachment of meta.attachments ?? []) {
156
+ await pdf.attach(new Uint8Array(attachment.data), attachment.name, {
157
+ mimeType: attachment.mimeType,
158
+ description: attachment.description,
159
+ creationDate: now,
160
+ modificationDate: now
161
+ });
162
+ }
163
+ const font = await pdf.embedFont(StandardFonts.Helvetica);
164
+ const encodable = (line) => [...line].filter((ch) => {
165
+ try {
166
+ font.encodeText(ch);
167
+ return true;
168
+ } catch {
169
+ return false;
170
+ }
171
+ }).join("");
172
+ for (const entry of pages) {
173
+ const bytes = new Uint8Array(await entry.image.arrayBuffer());
174
+ const image = entry.image.type === "image/png" ? await pdf.embedPng(bytes) : await pdf.embedJpg(bytes);
175
+ const page = pdf.addPage([entry.width, entry.height]);
176
+ page.drawImage(image, { x: 0, y: 0, width: entry.width, height: entry.height });
177
+ let y = entry.height - 14;
178
+ for (const line of entry.text ?? []) {
179
+ const text = encodable(line).trim();
180
+ if (!text) continue;
181
+ page.drawText(text, { x: 8, y, size: 10, font, opacity: 0 });
182
+ y -= 12;
183
+ if (y < 4) break;
184
+ }
185
+ }
186
+ const saved = await pdf.save();
187
+ return new Blob([saved], { type: "application/pdf" });
188
+ }
189
+
190
+ // src/document-tools/pdf-attachments.ts
191
+ var asBytes = (bytes) => bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
192
+ async function readPdfAttachment(pdf, name) {
193
+ const lib = await import('pdf-lib');
194
+ const { PDFDocument, PDFName, PDFDict, PDFArray, PDFString, PDFHexString, decodePDFRawStream } = lib;
195
+ const doc = await PDFDocument.load(asBytes(pdf), { updateMetadata: false });
196
+ const names = doc.catalog.lookupMaybe(PDFName.of("Names"), PDFDict);
197
+ const root = names?.lookupMaybe(PDFName.of("EmbeddedFiles"), PDFDict);
198
+ if (!root) return null;
199
+ const find = (node) => {
200
+ const pairs = node.lookupMaybe(PDFName.of("Names"), PDFArray);
201
+ if (pairs) {
202
+ for (let i = 0; i + 1 < pairs.size(); i += 2) {
203
+ const key = pairs.lookup(i);
204
+ const text = key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : void 0;
205
+ if (text !== name) continue;
206
+ const spec = pairs.lookup(i + 1, PDFDict);
207
+ const files = spec.lookupMaybe(PDFName.of("EF"), PDFDict);
208
+ const stream = files?.lookup(PDFName.of("F"));
209
+ if (!stream || !(stream instanceof lib.PDFRawStream)) return null;
210
+ return decodePDFRawStream(stream).decode();
211
+ }
212
+ }
213
+ const kids = node.lookupMaybe(PDFName.of("Kids"), PDFArray);
214
+ for (let i = 0; kids && i < kids.size(); i++) {
215
+ const found = find(kids.lookup(i, PDFDict));
216
+ if (found) return found;
217
+ }
218
+ return null;
219
+ };
220
+ return find(root);
221
+ }
222
+ function removeEntries(lib, doc, name) {
223
+ const { PDFName, PDFDict, PDFArray, PDFString, PDFHexString } = lib;
224
+ const root = doc.catalog.lookupMaybe(PDFName.of("Names"), PDFDict)?.lookupMaybe(PDFName.of("EmbeddedFiles"), PDFDict);
225
+ const pairs = root?.lookupMaybe(PDFName.of("Names"), PDFArray);
226
+ if (!pairs) return;
227
+ for (let i = pairs.size() - 2; i >= 0; i -= 2) {
228
+ const key = pairs.lookup(i);
229
+ const text = key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : void 0;
230
+ if (text === name) {
231
+ pairs.remove(i + 1);
232
+ pairs.remove(i);
233
+ }
234
+ }
235
+ }
236
+ async function setPdfAttachment(pdf, attachment) {
237
+ const lib = await import('pdf-lib');
238
+ const doc = await lib.PDFDocument.load(asBytes(pdf), { updateMetadata: false });
239
+ removeEntries(lib, doc, attachment.name);
240
+ const now = /* @__PURE__ */ new Date();
241
+ await doc.attach(new Uint8Array(attachment.data), attachment.name, {
242
+ mimeType: attachment.mimeType,
243
+ description: attachment.description,
244
+ creationDate: now,
245
+ modificationDate: now
246
+ });
247
+ return doc.save();
248
+ }
249
+
149
250
  Object.defineProperty(exports, "DEFAULT_FIELD_SIZE", {
150
251
  enumerable: true,
151
252
  get: function () { return chunkK6ELFSG6_cjs.DEFAULT_FIELD_SIZE; }
@@ -530,12 +631,15 @@ exports.PRODUCTIONS_FOLDER = PRODUCTIONS_FOLDER;
530
631
  exports.buildProduction = buildProduction;
531
632
  exports.fetchProductionPdf = fetchProductionPdf;
532
633
  exports.hasNoRendition = hasNoRendition;
634
+ exports.imagesToPdf = imagesToPdf;
533
635
  exports.isPdfDocument = isPdfDocument;
534
636
  exports.productionDocumentName = productionDocumentName;
535
637
  exports.productionFileName = productionFileName;
536
638
  exports.productionFolder = productionFolder;
537
639
  exports.productionIndexCsv = productionIndexCsv;
538
640
  exports.productionTitle = productionTitle;
641
+ exports.readPdfAttachment = readPdfAttachment;
539
642
  exports.saveProductionFiles = saveProductionFiles;
643
+ exports.setPdfAttachment = setPdfAttachment;
540
644
  //# sourceMappingURL=index.cjs.map
541
645
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/document-tools/production.ts"],"names":["fetchDocumentFile","fetchDocumentPdf","DocumentFetchError"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Production sets: number the selected documents with Bates labels (optionally a QR code back to\n * each document and an index page) and save the result back to the workspace. The dialog is\n * `ProductionSetDialog` in `@arbidocs/blocks/document-tools`; this is its data side.\n *\n * A PDF is numbered as stored (`/download`). Any other document is numbered through its PDF\n * rendition (`/view`, what the viewer shows for a Word or text file); one without a rendition is\n * left out. Saved results are new documents derived from their sources (role `production`) in a\n * Productions folder, shared with the workspace only if every source is. The originals never\n * change.\n *\n * pdf-lib (the numbering) is loaded only when a production is built, so importing this module\n * stays as light as the rest of the document tools.\n */\n\nimport type { components } from '@arbidocs/client'\nimport type { ProductionChoices } from '../annotations/ProductionDialog'\nimport type { BundleFile, BundleIndexEntry, BundleItem } from '../annotations/freeze/bundle'\nimport { DocumentFetchError, fetchDocumentFile, fetchDocumentPdf } from './document-pdf'\nimport type {\n DocumentToolsClient,\n DocumentToolsEnvironment,\n DocumentUploadQuery,\n} from './environment'\n\n/** The fields of a document record (`DocResponse`) a production uses. */\nexport type ProductionSource = Pick<\n components['schemas']['DocResponse'],\n 'external_id' | 'file_name' | 'file_type' | 'shared'\n> & {\n doc_metadata?: { title?: string | null } | null\n /** The document's workspace: the produced files are saved there, named on the upload. */\n workspace_ext_id?: string | null\n}\n\nexport const PRODUCTIONS_FOLDER = 'Productions'\n\nconst extensionOf = (fileName: string | null | undefined) => {\n const name = (fileName ?? '').toLowerCase()\n const dot = name.lastIndexOf('.')\n return dot < 0 ? '' : name.slice(dot + 1)\n}\n\n/** Media, archives and design files: no PDF version to number. */\nconst NO_RENDITION_EXTENSIONS = new Set(\n [\n // images\n 'jpg jpeg png gif bmp tiff tif webp heic',\n // video and audio\n 'mp4 mpeg mpg webm mov avi mkv mp3 wav m4a aac ogg flac',\n // archives, binaries, design files\n 'zip rar 7z tar gz exe bin dmg iso apk msi dll so psd ai sketch',\n ]\n .join(' ')\n .split(' ')\n)\n\nexport const isPdfDocument = (doc: ProductionSource) =>\n /pdf/i.test(doc.file_type ?? '') || extensionOf(doc.file_name) === 'pdf'\n\n/** True for a document with no PDF version to number (media, archives and the like). */\nexport const hasNoRendition = (doc: ProductionSource) =>\n !isPdfDocument(doc) && NO_RENDITION_EXTENSIONS.has(extensionOf(doc.file_name))\n\n/** The name a document goes by in the dialog and the index: its title, else its file name. */\nexport const productionDocumentName = (doc: ProductionSource) =>\n doc.doc_metadata?.title || doc.file_name || doc.external_id\n\nconst isPdfBytes = (bytes: Uint8Array) =>\n bytes.length > 4 && String.fromCharCode(...bytes.subarray(0, 5)) === '%PDF-'\n\n/**\n * The PDF to number for `doc`: the file itself, or the PDF version of anything else. `null` when a\n * non-PDF has no PDF version (yet), so it is left out; a PDF that cannot be downloaded throws.\n */\nexport async function fetchProductionPdf(\n client: DocumentToolsClient,\n doc: ProductionSource\n): Promise<Uint8Array | null> {\n const pdf = isPdfDocument(doc)\n let blob: Blob\n try {\n blob = await (pdf ? fetchDocumentFile : fetchDocumentPdf)(client, doc.external_id)\n } catch (e) {\n if (!pdf && e instanceof DocumentFetchError) return null\n throw e\n }\n const bytes = new Uint8Array(await blob.arrayBuffer())\n return pdf || isPdfBytes(bytes) ? bytes : null\n}\n\nconst safeName = (name: string) => name.replace(/[\\\\/:*?\"<>|]+/g, '_').trim()\n\n/** \"Production ACME-000001-ACME-000042\": names the files and folder of one production. */\nexport function productionTitle(firstLabel: string, lastLabel: string): string {\n return safeName(`Production ${firstLabel}-${lastLabel}`)\n}\n\n/** Where a production is saved: the Productions folder, or a folder of its own per document. */\nexport function productionFolder(title: string, merged: boolean): string {\n return merged ? PRODUCTIONS_FOLDER : `${PRODUCTIONS_FOLDER}/${title}`\n}\n\n/** In the workspace a per-document file says which range it holds, beside its original. */\nexport function productionFileName(file: BundleFile, title: string): string {\n if (!file.firstLabel) return `${title} index.pdf`\n return `${file.name.replace(/\\.pdf$/i, '')} (${file.firstLabel}-${file.lastLabel}).pdf`\n}\n\nconst csvCell = (value: string | number) => `\"${String(value).replace(/\"/g, '\"\"')}\"`\n\n/** The `index.csv` of a per-document production: every document with its file and range. */\nexport function productionIndexCsv(index: readonly BundleIndexEntry[]): string {\n const rows = index.map((e) =>\n [e.documentId, e.name, e.file, e.pages, e.firstLabel, e.lastLabel].map(csvCell).join(',')\n )\n return ['document_id,name,file,pages,first_label,last_label', ...rows].join('\\n')\n}\n\nexport interface ProductionUpload {\n name: string\n bytes: Uint8Array\n /** The documents this file was made from. */\n sources: ProductionSource[]\n}\n\n/** Save each file as a new workspace document derived from its sources; returns the new ids. */\nexport async function saveProductionFiles(\n upload: NonNullable<DocumentToolsEnvironment['uploadDocument']>,\n files: readonly ProductionUpload[],\n folder: string\n): Promise<string[]> {\n const ids: string[] = []\n for (const file of files) {\n const query: DocumentUploadQuery = {\n derived_from: file.sources.map((doc) => doc.external_id),\n derived_role: 'production',\n shared: file.sources.every((doc) => doc.shared),\n folder,\n }\n // Saved in the sources' workspace, named on the upload (never whichever one is open now).\n const workspaces = new Set(file.sources.map((doc) => doc.workspace_ext_id).filter(Boolean))\n if (workspaces.size === 1) query.workspace_ext_id = [...workspaces][0]\n const response = await upload({\n file: new File([file.bytes as BlobPart], file.name, { type: 'application/pdf' }),\n query,\n })\n ids.push(...(response?.doc_ext_ids ?? []))\n }\n return ids\n}\n\nexport interface BuildProductionOptions {\n /** The link a document's QR code opens (the document in the host app). */\n documentUrl: (documentId: string) => string\n /** Who produced the set; recorded in a merged file's metadata. */\n author?: string\n onProgress?: (done: number, total: number) => void\n}\n\nexport interface BuiltProduction {\n title: string\n merged: boolean\n /** The numbered files: one merged PDF (named after its range), or one per document (+ index). */\n files: BundleFile[]\n index: BundleIndexEntry[]\n /** The documents numbered, in order. */\n included: ProductionSource[]\n /** Documents left out because they have no PDF version yet. */\n noRendition: ProductionSource[]\n /** The files as they are saved to the workspace, each with the documents it was made from. */\n uploads: ProductionUpload[]\n folder: string\n}\n\n/**\n * Fetch each document's PDF (in `choices.order`) and number the set. Throws when none of the\n * documents has a PDF version, or when a PDF cannot be fetched.\n */\nexport async function buildProduction(\n client: DocumentToolsClient,\n documents: readonly ProductionSource[],\n choices: ProductionChoices,\n options: BuildProductionOptions\n): Promise<BuiltProduction> {\n const byId = new Map(documents.map((doc) => [doc.external_id, doc]))\n const included: ProductionSource[] = []\n const noRendition: ProductionSource[] = []\n const items: BundleItem[] = []\n for (const doc of choices.order.flatMap((id) => byId.get(id) ?? [])) {\n const bytes = await fetchProductionPdf(client, doc)\n if (!bytes) {\n noRendition.push(doc)\n continue\n }\n included.push(doc)\n items.push({\n name: productionDocumentName(doc),\n documentId: doc.external_id,\n bytes,\n qrUrl: options.documentUrl(doc.external_id),\n })\n }\n if (items.length === 0) {\n throw new Error('None of these documents has a PDF version to number yet.')\n }\n\n const { buildBundle } = await import('../annotations/freeze/bundle')\n const result = await buildBundle(items, {\n layout: choices.layout,\n bates: {\n prefix: choices.prefix,\n start: choices.start,\n digits: choices.digits,\n corner: choices.corner,\n },\n qr: choices.qr,\n // The QR code sits in the bottom corner the label is not in.\n qrCorner: choices.corner.endsWith('left') ? 'bottom-right' : 'bottom-left',\n indexPage: choices.indexPage,\n author: options.author,\n onProgress: options.onProgress,\n })\n\n const title = productionTitle(\n result.index[0].firstLabel,\n result.index[result.index.length - 1].lastLabel\n )\n const merged = choices.layout === 'merged'\n // A merged file is named after its range; per-document files keep their document names.\n const files = merged ? [{ ...result.files[0], name: `${title}.pdf` }] : result.files\n const sourceOf = new Map(result.index.map((e) => [e.file, byId.get(e.documentId)!]))\n const uploads = files.map((file) => ({\n name: merged ? file.name : productionFileName(file, title),\n bytes: file.bytes,\n // The merged file (and a separate index) is made from every document; each per-document\n // file from its own.\n sources: merged || !sourceOf.has(file.name) ? included : [sourceOf.get(file.name)!],\n }))\n return {\n title,\n merged,\n files,\n index: result.index,\n included,\n noRendition,\n uploads,\n folder: productionFolder(title, merged),\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/document-tools/production.ts","../../src/document-tools/images-to-pdf.ts","../../src/document-tools/pdf-attachments.ts"],"names":["fetchDocumentFile","fetchDocumentPdf","DocumentFetchError"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Production sets: number the selected documents with Bates labels (optionally a QR code back to\n * each document and an index page) and save the result back to the workspace. The dialog is\n * `ProductionSetDialog` in `@arbidocs/blocks/document-tools`; this is its data side.\n *\n * A PDF is numbered as stored (`/download`). Any other document is numbered through its PDF\n * rendition (`/view`, what the viewer shows for a Word or text file); one without a rendition is\n * left out. Saved results are new documents derived from their sources (role `production`) in a\n * Productions folder, shared with the workspace only if every source is. The originals never\n * change.\n *\n * pdf-lib (the numbering) is loaded only when a production is built, so importing this module\n * stays as light as the rest of the document tools.\n */\n\nimport type { components } from '@arbidocs/client'\nimport type { ProductionChoices } from '../annotations/ProductionDialog'\nimport type { BundleFile, BundleIndexEntry, BundleItem } from '../annotations/freeze/bundle'\nimport { DocumentFetchError, fetchDocumentFile, fetchDocumentPdf } from './document-pdf'\nimport type {\n DocumentToolsClient,\n DocumentToolsEnvironment,\n DocumentUploadQuery,\n} from './environment'\n\n/** The fields of a document record (`DocResponse`) a production uses. */\nexport type ProductionSource = Pick<\n components['schemas']['DocResponse'],\n 'external_id' | 'file_name' | 'file_type' | 'shared'\n> & {\n doc_metadata?: { title?: string | null } | null\n /** The document's workspace: the produced files are saved there, named on the upload. */\n workspace_ext_id?: string | null\n}\n\nexport const PRODUCTIONS_FOLDER = 'Productions'\n\nconst extensionOf = (fileName: string | null | undefined) => {\n const name = (fileName ?? '').toLowerCase()\n const dot = name.lastIndexOf('.')\n return dot < 0 ? '' : name.slice(dot + 1)\n}\n\n/** Media, archives and design files: no PDF version to number. */\nconst NO_RENDITION_EXTENSIONS = new Set(\n [\n // images\n 'jpg jpeg png gif bmp tiff tif webp heic',\n // video and audio\n 'mp4 mpeg mpg webm mov avi mkv mp3 wav m4a aac ogg flac',\n // archives, binaries, design files\n 'zip rar 7z tar gz exe bin dmg iso apk msi dll so psd ai sketch',\n ]\n .join(' ')\n .split(' ')\n)\n\nexport const isPdfDocument = (doc: ProductionSource) =>\n /pdf/i.test(doc.file_type ?? '') || extensionOf(doc.file_name) === 'pdf'\n\n/** True for a document with no PDF version to number (media, archives and the like). */\nexport const hasNoRendition = (doc: ProductionSource) =>\n !isPdfDocument(doc) && NO_RENDITION_EXTENSIONS.has(extensionOf(doc.file_name))\n\n/** The name a document goes by in the dialog and the index: its title, else its file name. */\nexport const productionDocumentName = (doc: ProductionSource) =>\n doc.doc_metadata?.title || doc.file_name || doc.external_id\n\nconst isPdfBytes = (bytes: Uint8Array) =>\n bytes.length > 4 && String.fromCharCode(...bytes.subarray(0, 5)) === '%PDF-'\n\n/**\n * The PDF to number for `doc`: the file itself, or the PDF version of anything else. `null` when a\n * non-PDF has no PDF version (yet), so it is left out; a PDF that cannot be downloaded throws.\n */\nexport async function fetchProductionPdf(\n client: DocumentToolsClient,\n doc: ProductionSource\n): Promise<Uint8Array | null> {\n const pdf = isPdfDocument(doc)\n let blob: Blob\n try {\n blob = await (pdf ? fetchDocumentFile : fetchDocumentPdf)(client, doc.external_id)\n } catch (e) {\n if (!pdf && e instanceof DocumentFetchError) return null\n throw e\n }\n const bytes = new Uint8Array(await blob.arrayBuffer())\n return pdf || isPdfBytes(bytes) ? bytes : null\n}\n\nconst safeName = (name: string) => name.replace(/[\\\\/:*?\"<>|]+/g, '_').trim()\n\n/** \"Production ACME-000001-ACME-000042\": names the files and folder of one production. */\nexport function productionTitle(firstLabel: string, lastLabel: string): string {\n return safeName(`Production ${firstLabel}-${lastLabel}`)\n}\n\n/** Where a production is saved: the Productions folder, or a folder of its own per document. */\nexport function productionFolder(title: string, merged: boolean): string {\n return merged ? PRODUCTIONS_FOLDER : `${PRODUCTIONS_FOLDER}/${title}`\n}\n\n/** In the workspace a per-document file says which range it holds, beside its original. */\nexport function productionFileName(file: BundleFile, title: string): string {\n if (!file.firstLabel) return `${title} index.pdf`\n return `${file.name.replace(/\\.pdf$/i, '')} (${file.firstLabel}-${file.lastLabel}).pdf`\n}\n\nconst csvCell = (value: string | number) => `\"${String(value).replace(/\"/g, '\"\"')}\"`\n\n/** The `index.csv` of a per-document production: every document with its file and range. */\nexport function productionIndexCsv(index: readonly BundleIndexEntry[]): string {\n const rows = index.map((e) =>\n [e.documentId, e.name, e.file, e.pages, e.firstLabel, e.lastLabel].map(csvCell).join(',')\n )\n return ['document_id,name,file,pages,first_label,last_label', ...rows].join('\\n')\n}\n\nexport interface ProductionUpload {\n name: string\n bytes: Uint8Array\n /** The documents this file was made from. */\n sources: ProductionSource[]\n}\n\n/** Save each file as a new workspace document derived from its sources; returns the new ids. */\nexport async function saveProductionFiles(\n upload: NonNullable<DocumentToolsEnvironment['uploadDocument']>,\n files: readonly ProductionUpload[],\n folder: string\n): Promise<string[]> {\n const ids: string[] = []\n for (const file of files) {\n const query: DocumentUploadQuery = {\n derived_from: file.sources.map((doc) => doc.external_id),\n derived_role: 'production',\n shared: file.sources.every((doc) => doc.shared),\n folder,\n }\n // Saved in the sources' workspace, named on the upload (never whichever one is open now).\n const workspaces = new Set(file.sources.map((doc) => doc.workspace_ext_id).filter(Boolean))\n if (workspaces.size === 1) query.workspace_ext_id = [...workspaces][0]\n const response = await upload({\n file: new File([file.bytes as BlobPart], file.name, { type: 'application/pdf' }),\n query,\n })\n ids.push(...(response?.doc_ext_ids ?? []))\n }\n return ids\n}\n\nexport interface BuildProductionOptions {\n /** The link a document's QR code opens (the document in the host app). */\n documentUrl: (documentId: string) => string\n /** Who produced the set; recorded in a merged file's metadata. */\n author?: string\n onProgress?: (done: number, total: number) => void\n}\n\nexport interface BuiltProduction {\n title: string\n merged: boolean\n /** The numbered files: one merged PDF (named after its range), or one per document (+ index). */\n files: BundleFile[]\n index: BundleIndexEntry[]\n /** The documents numbered, in order. */\n included: ProductionSource[]\n /** Documents left out because they have no PDF version yet. */\n noRendition: ProductionSource[]\n /** The files as they are saved to the workspace, each with the documents it was made from. */\n uploads: ProductionUpload[]\n folder: string\n}\n\n/**\n * Fetch each document's PDF (in `choices.order`) and number the set. Throws when none of the\n * documents has a PDF version, or when a PDF cannot be fetched.\n */\nexport async function buildProduction(\n client: DocumentToolsClient,\n documents: readonly ProductionSource[],\n choices: ProductionChoices,\n options: BuildProductionOptions\n): Promise<BuiltProduction> {\n const byId = new Map(documents.map((doc) => [doc.external_id, doc]))\n const included: ProductionSource[] = []\n const noRendition: ProductionSource[] = []\n const items: BundleItem[] = []\n for (const doc of choices.order.flatMap((id) => byId.get(id) ?? [])) {\n const bytes = await fetchProductionPdf(client, doc)\n if (!bytes) {\n noRendition.push(doc)\n continue\n }\n included.push(doc)\n items.push({\n name: productionDocumentName(doc),\n documentId: doc.external_id,\n bytes,\n qrUrl: options.documentUrl(doc.external_id),\n })\n }\n if (items.length === 0) {\n throw new Error('None of these documents has a PDF version to number yet.')\n }\n\n const { buildBundle } = await import('../annotations/freeze/bundle')\n const result = await buildBundle(items, {\n layout: choices.layout,\n bates: {\n prefix: choices.prefix,\n start: choices.start,\n digits: choices.digits,\n corner: choices.corner,\n },\n qr: choices.qr,\n // The QR code sits in the bottom corner the label is not in.\n qrCorner: choices.corner.endsWith('left') ? 'bottom-right' : 'bottom-left',\n indexPage: choices.indexPage,\n author: options.author,\n onProgress: options.onProgress,\n })\n\n const title = productionTitle(\n result.index[0].firstLabel,\n result.index[result.index.length - 1].lastLabel\n )\n const merged = choices.layout === 'merged'\n // A merged file is named after its range; per-document files keep their document names.\n const files = merged ? [{ ...result.files[0], name: `${title}.pdf` }] : result.files\n const sourceOf = new Map(result.index.map((e) => [e.file, byId.get(e.documentId)!]))\n const uploads = files.map((file) => ({\n name: merged ? file.name : productionFileName(file, title),\n bytes: file.bytes,\n // The merged file (and a separate index) is made from every document; each per-document\n // file from its own.\n sources: merged || !sourceOf.has(file.name) ? included : [sourceOf.get(file.name)!],\n }))\n return {\n title,\n merged,\n files,\n index: result.index,\n included,\n noRendition,\n uploads,\n folder: productionFolder(title, merged),\n }\n}\n","/**\n * Page images to a PDF: one page per image, the image filling it, and the page's text laid on it\n * invisibly so the PDF is searchable (a presentation's slides: what shows is exactly the image,\n * redactions burnt in, while their titles and labels can still be found). pdf-lib is loaded on\n * first use. Files can be attached to it (`meta.attachments`; see `pdf-attachments`).\n */\n\nimport type { PdfAttachment } from './pdf-attachments'\n\nexport interface PdfImagePage {\n /** A JPEG or PNG. */\n image: Blob\n /** The page size, in PDF points. */\n width: number\n height: number\n /** Text on the page, laid on it invisibly for search. */\n text?: readonly string[]\n}\n\nexport async function imagesToPdf(\n pages: readonly PdfImagePage[],\n meta: { title?: string; attachments?: readonly PdfAttachment[] } = {}\n): Promise<Blob> {\n const { PDFDocument, StandardFonts } = await import('pdf-lib')\n const pdf = await PDFDocument.create()\n if (meta.title) pdf.setTitle(meta.title)\n const now = new Date()\n for (const attachment of meta.attachments ?? []) {\n // Copied: bytes made in another realm (Node's TextEncoder under jsdom) fail pdf-lib's type check.\n await pdf.attach(new Uint8Array(attachment.data), attachment.name, {\n mimeType: attachment.mimeType,\n description: attachment.description,\n creationDate: now,\n modificationDate: now,\n })\n }\n const font = await pdf.embedFont(StandardFonts.Helvetica)\n // The standard font covers Latin text; characters it cannot encode are left out of the hidden\n // layer rather than failing the export.\n const encodable = (line: string) =>\n [...line]\n .filter((ch) => {\n try {\n font.encodeText(ch)\n return true\n } catch {\n return false\n }\n })\n .join('')\n\n for (const entry of pages) {\n const bytes = new Uint8Array(await entry.image.arrayBuffer())\n const image =\n entry.image.type === 'image/png' ? await pdf.embedPng(bytes) : await pdf.embedJpg(bytes)\n const page = pdf.addPage([entry.width, entry.height])\n page.drawImage(image, { x: 0, y: 0, width: entry.width, height: entry.height })\n let y = entry.height - 14\n for (const line of entry.text ?? []) {\n const text = encodable(line).trim()\n if (!text) continue\n page.drawText(text, { x: 8, y, size: 10, font, opacity: 0 })\n y -= 12\n if (y < 4) break\n }\n }\n const saved = await pdf.save()\n return new Blob([saved as BlobPart], { type: 'application/pdf' })\n}\n","/**\n * Files attached to a PDF (the standard /EmbeddedFiles name tree, what a PDF reader lists under\n * \"Attachments\"): read one by name, or put one in (replacing any of that name). Plain functions\n * over bytes, for the browser and Node alike: a saved presentation is a PDF of its slides with its\n * editable data attached (`arbi-presentation.json`), and anything that can read and write a PDF's\n * attachments (these, pypdf / PyMuPDF on a server) can read and edit it. pdf-lib is loaded on use.\n */\n\nimport type { PDFDict as PDFDictType, PDFDocument as PDFDocumentType } from 'pdf-lib'\n\nexport interface PdfAttachment {\n name: string\n data: Uint8Array\n mimeType: string\n description?: string\n}\n\ntype Bytes = Uint8Array | ArrayBuffer\nconst asBytes = (bytes: Bytes) => (bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes))\n\n/** The attachment called `name`, or null when the PDF has none of that name. */\nexport async function readPdfAttachment(pdf: Bytes, name: string): Promise<Uint8Array | null> {\n const lib = await import('pdf-lib')\n const { PDFDocument, PDFName, PDFDict, PDFArray, PDFString, PDFHexString, decodePDFRawStream } =\n lib\n const doc = await PDFDocument.load(asBytes(pdf), { updateMetadata: false })\n const names = doc.catalog.lookupMaybe(PDFName.of('Names'), PDFDict)\n const root = names?.lookupMaybe(PDFName.of('EmbeddedFiles'), PDFDict)\n if (!root) return null\n\n // A name tree: pairs [name, file spec] in /Names, or nested nodes under /Kids.\n const find = (node: PDFDictType): Uint8Array | null => {\n const pairs = node.lookupMaybe(PDFName.of('Names'), PDFArray)\n if (pairs) {\n for (let i = 0; i + 1 < pairs.size(); i += 2) {\n const key = pairs.lookup(i)\n const text =\n key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : undefined\n if (text !== name) continue\n const spec = pairs.lookup(i + 1, PDFDict)\n const files = spec.lookupMaybe(PDFName.of('EF'), PDFDict)\n const stream = files?.lookup(PDFName.of('F'))\n if (!stream || !(stream instanceof lib.PDFRawStream)) return null\n return decodePDFRawStream(stream).decode()\n }\n }\n const kids = node.lookupMaybe(PDFName.of('Kids'), PDFArray)\n for (let i = 0; kids && i < kids.size(); i++) {\n const found = find(kids.lookup(i, PDFDict))\n if (found) return found\n }\n return null\n }\n return find(root)\n}\n\n/** Remove the name-tree entries called `name` (flat /Names arrays; the shape pdf-lib writes). */\nfunction removeEntries(lib: typeof import('pdf-lib'), doc: PDFDocumentType, name: string) {\n const { PDFName, PDFDict, PDFArray, PDFString, PDFHexString } = lib\n const root = doc.catalog\n .lookupMaybe(PDFName.of('Names'), PDFDict)\n ?.lookupMaybe(PDFName.of('EmbeddedFiles'), PDFDict)\n const pairs = root?.lookupMaybe(PDFName.of('Names'), PDFArray)\n if (!pairs) return\n for (let i = pairs.size() - 2; i >= 0; i -= 2) {\n const key = pairs.lookup(i)\n const text =\n key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : undefined\n if (text === name) {\n pairs.remove(i + 1)\n pairs.remove(i)\n }\n }\n}\n\n/** The PDF with `attachment` in it, replacing any attachment of the same name. */\nexport async function setPdfAttachment(pdf: Bytes, attachment: PdfAttachment): Promise<Uint8Array> {\n const lib = await import('pdf-lib')\n const doc = await lib.PDFDocument.load(asBytes(pdf), { updateMetadata: false })\n removeEntries(lib, doc, attachment.name)\n const now = new Date()\n await doc.attach(new Uint8Array(attachment.data), attachment.name, {\n mimeType: attachment.mimeType,\n description: attachment.description,\n creationDate: now,\n modificationDate: now,\n })\n return doc.save()\n}\n"]}
@@ -87,4 +87,44 @@ interface BuiltProduction {
87
87
  */
88
88
  declare function buildProduction(client: DocumentToolsClient, documents: readonly ProductionSource[], choices: ProductionChoices, options: BuildProductionOptions): Promise<BuiltProduction>;
89
89
 
90
- export { type BuildProductionOptions, type BuiltProduction, DocumentToolsClient, DocumentToolsEnvironment, PRODUCTIONS_FOLDER, type ProductionSource, type ProductionUpload, buildProduction, fetchProductionPdf, hasNoRendition, isPdfDocument, productionDocumentName, productionFileName, productionFolder, productionIndexCsv, productionTitle, saveProductionFiles };
90
+ /**
91
+ * Files attached to a PDF (the standard /EmbeddedFiles name tree, what a PDF reader lists under
92
+ * "Attachments"): read one by name, or put one in (replacing any of that name). Plain functions
93
+ * over bytes, for the browser and Node alike: a saved presentation is a PDF of its slides with its
94
+ * editable data attached (`arbi-presentation.json`), and anything that can read and write a PDF's
95
+ * attachments (these, pypdf / PyMuPDF on a server) can read and edit it. pdf-lib is loaded on use.
96
+ */
97
+ interface PdfAttachment {
98
+ name: string;
99
+ data: Uint8Array;
100
+ mimeType: string;
101
+ description?: string;
102
+ }
103
+ type Bytes = Uint8Array | ArrayBuffer;
104
+ /** The attachment called `name`, or null when the PDF has none of that name. */
105
+ declare function readPdfAttachment(pdf: Bytes, name: string): Promise<Uint8Array | null>;
106
+ /** The PDF with `attachment` in it, replacing any attachment of the same name. */
107
+ declare function setPdfAttachment(pdf: Bytes, attachment: PdfAttachment): Promise<Uint8Array>;
108
+
109
+ /**
110
+ * Page images to a PDF: one page per image, the image filling it, and the page's text laid on it
111
+ * invisibly so the PDF is searchable (a presentation's slides: what shows is exactly the image,
112
+ * redactions burnt in, while their titles and labels can still be found). pdf-lib is loaded on
113
+ * first use. Files can be attached to it (`meta.attachments`; see `pdf-attachments`).
114
+ */
115
+
116
+ interface PdfImagePage {
117
+ /** A JPEG or PNG. */
118
+ image: Blob;
119
+ /** The page size, in PDF points. */
120
+ width: number;
121
+ height: number;
122
+ /** Text on the page, laid on it invisibly for search. */
123
+ text?: readonly string[];
124
+ }
125
+ declare function imagesToPdf(pages: readonly PdfImagePage[], meta?: {
126
+ title?: string;
127
+ attachments?: readonly PdfAttachment[];
128
+ }): Promise<Blob>;
129
+
130
+ export { type BuildProductionOptions, type BuiltProduction, DocumentToolsClient, DocumentToolsEnvironment, PRODUCTIONS_FOLDER, type PdfAttachment, type PdfImagePage, type ProductionSource, type ProductionUpload, buildProduction, fetchProductionPdf, hasNoRendition, imagesToPdf, isPdfDocument, productionDocumentName, productionFileName, productionFolder, productionIndexCsv, productionTitle, readPdfAttachment, saveProductionFiles, setPdfAttachment };
@@ -12,17 +12,22 @@ import {
12
12
  productionTitle,
13
13
  saveProductionFiles
14
14
  } from "./production.js";
15
+ import { imagesToPdf } from "./images-to-pdf.js";
16
+ import { readPdfAttachment, setPdfAttachment } from "./pdf-attachments.js";
15
17
  export {
16
18
  PRODUCTIONS_FOLDER,
17
19
  buildProduction,
18
20
  fetchProductionPdf,
19
21
  hasNoRendition,
22
+ imagesToPdf,
20
23
  isPdfDocument,
21
24
  productionDocumentName,
22
25
  productionFileName,
23
26
  productionFolder,
24
27
  productionIndexCsv,
25
28
  productionTitle,
26
- saveProductionFiles
29
+ readPdfAttachment,
30
+ saveProductionFiles,
31
+ setPdfAttachment
27
32
  };
28
33
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/document-tools/index.ts"],
4
- "sourcesContent": ["/** Existing document-tools surface. PDF production is optional; lightweight consumers use /core. */\nexport * from './core'\nexport {\n PRODUCTIONS_FOLDER,\n buildProduction,\n fetchProductionPdf,\n hasNoRendition,\n isPdfDocument,\n productionDocumentName,\n productionFileName,\n productionFolder,\n productionIndexCsv,\n productionTitle,\n saveProductionFiles,\n type BuildProductionOptions,\n type BuiltProduction,\n type ProductionSource,\n type ProductionUpload,\n} from './production'\n"],
5
- "mappings": "AACA,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;",
4
+ "sourcesContent": ["/** Existing document-tools surface. PDF production is optional; lightweight consumers use /core. */\nexport * from './core'\nexport {\n PRODUCTIONS_FOLDER,\n buildProduction,\n fetchProductionPdf,\n hasNoRendition,\n isPdfDocument,\n productionDocumentName,\n productionFileName,\n productionFolder,\n productionIndexCsv,\n productionTitle,\n saveProductionFiles,\n type BuildProductionOptions,\n type BuiltProduction,\n type ProductionSource,\n type ProductionUpload,\n} from './production'\nexport { imagesToPdf, type PdfImagePage } from './images-to-pdf'\nexport { readPdfAttachment, setPdfAttachment, type PdfAttachment } from './pdf-attachments'\n"],
5
+ "mappings": "AACA,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,mBAAsC;AAC/C,SAAS,mBAAmB,wBAA4C;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,63 @@
1
+ const asBytes = (bytes) => bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
2
+ async function readPdfAttachment(pdf, name) {
3
+ const lib = await import("pdf-lib");
4
+ const { PDFDocument, PDFName, PDFDict, PDFArray, PDFString, PDFHexString, decodePDFRawStream } = lib;
5
+ const doc = await PDFDocument.load(asBytes(pdf), { updateMetadata: false });
6
+ const names = doc.catalog.lookupMaybe(PDFName.of("Names"), PDFDict);
7
+ const root = names?.lookupMaybe(PDFName.of("EmbeddedFiles"), PDFDict);
8
+ if (!root) return null;
9
+ const find = (node) => {
10
+ const pairs = node.lookupMaybe(PDFName.of("Names"), PDFArray);
11
+ if (pairs) {
12
+ for (let i = 0; i + 1 < pairs.size(); i += 2) {
13
+ const key = pairs.lookup(i);
14
+ const text = key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : void 0;
15
+ if (text !== name) continue;
16
+ const spec = pairs.lookup(i + 1, PDFDict);
17
+ const files = spec.lookupMaybe(PDFName.of("EF"), PDFDict);
18
+ const stream = files?.lookup(PDFName.of("F"));
19
+ if (!stream || !(stream instanceof lib.PDFRawStream)) return null;
20
+ return decodePDFRawStream(stream).decode();
21
+ }
22
+ }
23
+ const kids = node.lookupMaybe(PDFName.of("Kids"), PDFArray);
24
+ for (let i = 0; kids && i < kids.size(); i++) {
25
+ const found = find(kids.lookup(i, PDFDict));
26
+ if (found) return found;
27
+ }
28
+ return null;
29
+ };
30
+ return find(root);
31
+ }
32
+ function removeEntries(lib, doc, name) {
33
+ const { PDFName, PDFDict, PDFArray, PDFString, PDFHexString } = lib;
34
+ const root = doc.catalog.lookupMaybe(PDFName.of("Names"), PDFDict)?.lookupMaybe(PDFName.of("EmbeddedFiles"), PDFDict);
35
+ const pairs = root?.lookupMaybe(PDFName.of("Names"), PDFArray);
36
+ if (!pairs) return;
37
+ for (let i = pairs.size() - 2; i >= 0; i -= 2) {
38
+ const key = pairs.lookup(i);
39
+ const text = key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : void 0;
40
+ if (text === name) {
41
+ pairs.remove(i + 1);
42
+ pairs.remove(i);
43
+ }
44
+ }
45
+ }
46
+ async function setPdfAttachment(pdf, attachment) {
47
+ const lib = await import("pdf-lib");
48
+ const doc = await lib.PDFDocument.load(asBytes(pdf), { updateMetadata: false });
49
+ removeEntries(lib, doc, attachment.name);
50
+ const now = /* @__PURE__ */ new Date();
51
+ await doc.attach(new Uint8Array(attachment.data), attachment.name, {
52
+ mimeType: attachment.mimeType,
53
+ description: attachment.description,
54
+ creationDate: now,
55
+ modificationDate: now
56
+ });
57
+ return doc.save();
58
+ }
59
+ export {
60
+ readPdfAttachment,
61
+ setPdfAttachment
62
+ };
63
+ //# sourceMappingURL=pdf-attachments.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/document-tools/pdf-attachments.ts"],
4
+ "sourcesContent": ["/**\n * Files attached to a PDF (the standard /EmbeddedFiles name tree, what a PDF reader lists under\n * \"Attachments\"): read one by name, or put one in (replacing any of that name). Plain functions\n * over bytes, for the browser and Node alike: a saved presentation is a PDF of its slides with its\n * editable data attached (`arbi-presentation.json`), and anything that can read and write a PDF's\n * attachments (these, pypdf / PyMuPDF on a server) can read and edit it. pdf-lib is loaded on use.\n */\n\nimport type { PDFDict as PDFDictType, PDFDocument as PDFDocumentType } from 'pdf-lib'\n\nexport interface PdfAttachment {\n name: string\n data: Uint8Array\n mimeType: string\n description?: string\n}\n\ntype Bytes = Uint8Array | ArrayBuffer\nconst asBytes = (bytes: Bytes) => (bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes))\n\n/** The attachment called `name`, or null when the PDF has none of that name. */\nexport async function readPdfAttachment(pdf: Bytes, name: string): Promise<Uint8Array | null> {\n const lib = await import('pdf-lib')\n const { PDFDocument, PDFName, PDFDict, PDFArray, PDFString, PDFHexString, decodePDFRawStream } =\n lib\n const doc = await PDFDocument.load(asBytes(pdf), { updateMetadata: false })\n const names = doc.catalog.lookupMaybe(PDFName.of('Names'), PDFDict)\n const root = names?.lookupMaybe(PDFName.of('EmbeddedFiles'), PDFDict)\n if (!root) return null\n\n // A name tree: pairs [name, file spec] in /Names, or nested nodes under /Kids.\n const find = (node: PDFDictType): Uint8Array | null => {\n const pairs = node.lookupMaybe(PDFName.of('Names'), PDFArray)\n if (pairs) {\n for (let i = 0; i + 1 < pairs.size(); i += 2) {\n const key = pairs.lookup(i)\n const text =\n key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : undefined\n if (text !== name) continue\n const spec = pairs.lookup(i + 1, PDFDict)\n const files = spec.lookupMaybe(PDFName.of('EF'), PDFDict)\n const stream = files?.lookup(PDFName.of('F'))\n if (!stream || !(stream instanceof lib.PDFRawStream)) return null\n return decodePDFRawStream(stream).decode()\n }\n }\n const kids = node.lookupMaybe(PDFName.of('Kids'), PDFArray)\n for (let i = 0; kids && i < kids.size(); i++) {\n const found = find(kids.lookup(i, PDFDict))\n if (found) return found\n }\n return null\n }\n return find(root)\n}\n\n/** Remove the name-tree entries called `name` (flat /Names arrays; the shape pdf-lib writes). */\nfunction removeEntries(lib: typeof import('pdf-lib'), doc: PDFDocumentType, name: string) {\n const { PDFName, PDFDict, PDFArray, PDFString, PDFHexString } = lib\n const root = doc.catalog\n .lookupMaybe(PDFName.of('Names'), PDFDict)\n ?.lookupMaybe(PDFName.of('EmbeddedFiles'), PDFDict)\n const pairs = root?.lookupMaybe(PDFName.of('Names'), PDFArray)\n if (!pairs) return\n for (let i = pairs.size() - 2; i >= 0; i -= 2) {\n const key = pairs.lookup(i)\n const text =\n key instanceof PDFString || key instanceof PDFHexString ? key.decodeText() : undefined\n if (text === name) {\n pairs.remove(i + 1)\n pairs.remove(i)\n }\n }\n}\n\n/** The PDF with `attachment` in it, replacing any attachment of the same name. */\nexport async function setPdfAttachment(pdf: Bytes, attachment: PdfAttachment): Promise<Uint8Array> {\n const lib = await import('pdf-lib')\n const doc = await lib.PDFDocument.load(asBytes(pdf), { updateMetadata: false })\n removeEntries(lib, doc, attachment.name)\n const now = new Date()\n await doc.attach(new Uint8Array(attachment.data), attachment.name, {\n mimeType: attachment.mimeType,\n description: attachment.description,\n creationDate: now,\n modificationDate: now,\n })\n return doc.save()\n}\n"],
5
+ "mappings": "AAkBA,MAAM,UAAU,CAAC,UAAkB,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;AAG7F,eAAsB,kBAAkB,KAAY,MAA0C;AAC5F,QAAM,MAAM,MAAM,OAAO,SAAS;AAClC,QAAM,EAAE,aAAa,SAAS,SAAS,UAAU,WAAW,cAAc,mBAAmB,IAC3F;AACF,QAAM,MAAM,MAAM,YAAY,KAAK,QAAQ,GAAG,GAAG,EAAE,gBAAgB,MAAM,CAAC;AAC1E,QAAM,QAAQ,IAAI,QAAQ,YAAY,QAAQ,GAAG,OAAO,GAAG,OAAO;AAClE,QAAM,OAAO,OAAO,YAAY,QAAQ,GAAG,eAAe,GAAG,OAAO;AACpE,MAAI,CAAC,KAAM,QAAO;AAGlB,QAAM,OAAO,CAAC,SAAyC;AACrD,UAAM,QAAQ,KAAK,YAAY,QAAQ,GAAG,OAAO,GAAG,QAAQ;AAC5D,QAAI,OAAO;AACT,eAAS,IAAI,GAAG,IAAI,IAAI,MAAM,KAAK,GAAG,KAAK,GAAG;AAC5C,cAAM,MAAM,MAAM,OAAO,CAAC;AAC1B,cAAM,OACJ,eAAe,aAAa,eAAe,eAAe,IAAI,WAAW,IAAI;AAC/E,YAAI,SAAS,KAAM;AACnB,cAAM,OAAO,MAAM,OAAO,IAAI,GAAG,OAAO;AACxC,cAAM,QAAQ,KAAK,YAAY,QAAQ,GAAG,IAAI,GAAG,OAAO;AACxD,cAAM,SAAS,OAAO,OAAO,QAAQ,GAAG,GAAG,CAAC;AAC5C,YAAI,CAAC,UAAU,EAAE,kBAAkB,IAAI,cAAe,QAAO;AAC7D,eAAO,mBAAmB,MAAM,EAAE,OAAO;AAAA,MAC3C;AAAA,IACF;AACA,UAAM,OAAO,KAAK,YAAY,QAAQ,GAAG,MAAM,GAAG,QAAQ;AAC1D,aAAS,IAAI,GAAG,QAAQ,IAAI,KAAK,KAAK,GAAG,KAAK;AAC5C,YAAM,QAAQ,KAAK,KAAK,OAAO,GAAG,OAAO,CAAC;AAC1C,UAAI,MAAO,QAAO;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI;AAClB;AAGA,SAAS,cAAc,KAA+B,KAAsB,MAAc;AACxF,QAAM,EAAE,SAAS,SAAS,UAAU,WAAW,aAAa,IAAI;AAChE,QAAM,OAAO,IAAI,QACd,YAAY,QAAQ,GAAG,OAAO,GAAG,OAAO,GACvC,YAAY,QAAQ,GAAG,eAAe,GAAG,OAAO;AACpD,QAAM,QAAQ,MAAM,YAAY,QAAQ,GAAG,OAAO,GAAG,QAAQ;AAC7D,MAAI,CAAC,MAAO;AACZ,WAAS,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG;AAC7C,UAAM,MAAM,MAAM,OAAO,CAAC;AAC1B,UAAM,OACJ,eAAe,aAAa,eAAe,eAAe,IAAI,WAAW,IAAI;AAC/E,QAAI,SAAS,MAAM;AACjB,YAAM,OAAO,IAAI,CAAC;AAClB,YAAM,OAAO,CAAC;AAAA,IAChB;AAAA,EACF;AACF;AAGA,eAAsB,iBAAiB,KAAY,YAAgD;AACjG,QAAM,MAAM,MAAM,OAAO,SAAS;AAClC,QAAM,MAAM,MAAM,IAAI,YAAY,KAAK,QAAQ,GAAG,GAAG,EAAE,gBAAgB,MAAM,CAAC;AAC9E,gBAAc,KAAK,KAAK,WAAW,IAAI;AACvC,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,IAAI,OAAO,IAAI,WAAW,WAAW,IAAI,GAAG,WAAW,MAAM;AAAA,IACjE,UAAU,WAAW;AAAA,IACrB,aAAa,WAAW;AAAA,IACxB,cAAc;AAAA,IACd,kBAAkB;AAAA,EACpB,CAAC;AACD,SAAO,IAAI,KAAK;AAClB;",
6
+ "names": []
7
+ }
@@ -7,10 +7,12 @@ import { EmlIcon } from "../icons/EmlIcon.js";
7
7
  import { HtmlIcon } from "../icons/HtmlIcon.js";
8
8
  import { ImgIcon } from "../icons/ImgIcon.js";
9
9
  import { PdfIcon } from "../icons/PdfIcon.js";
10
+ import { PresIcon } from "../icons/PresIcon.js";
10
11
  import { TxtIcon } from "../icons/TxtIcon.js";
11
12
  import { VidIcon } from "../icons/VidIcon.js";
12
13
  import { XlsIcon } from "../icons/XlsIcon.js";
13
14
  import { cn } from "../lib/utils.js";
15
+ import { isPresentationFileName } from "./file-type.js";
14
16
  function getDocumentTitle(document, defaultValue = "") {
15
17
  if (!document) return defaultValue;
16
18
  return document.doc_metadata?.title || document.file_name || defaultValue;
@@ -41,7 +43,10 @@ const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
41
43
  "yaml",
42
44
  "yml"
43
45
  ]);
44
- function getDocIcon(fileName, iconSize = 14) {
46
+ function getDocIcon(fileName, iconSize = 14, folder) {
47
+ if (isPresentationFileName(fileName, folder)) {
48
+ return /* @__PURE__ */ jsx(PresIcon, { size: iconSize, className: "flex-shrink-0" });
49
+ }
45
50
  const ext = fileName?.split(".").pop()?.toLowerCase();
46
51
  if (ext && VIDEO_EXTENSIONS.has(ext)) return /* @__PURE__ */ jsx(VidIcon, { size: iconSize, className: "flex-shrink-0" });
47
52
  if (ext && AUDIO_EXTENSIONS.has(ext)) return /* @__PURE__ */ jsx(AudIcon, { size: iconSize, className: "flex-shrink-0" });
@@ -75,7 +80,7 @@ function DocumentEntity({
75
80
  const ext = doc.file_name?.split(".").pop()?.toLowerCase();
76
81
  const iconSize = size === "md" ? 16 : 14;
77
82
  const eyeSizeClass = size === "md" ? "h-4 w-4" : "h-3.5 w-3.5";
78
- const baseIcon = icon ?? getDocIcon(doc.file_name, iconSize);
83
+ const baseIcon = icon ?? getDocIcon(doc.file_name, iconSize, doc.folder);
79
84
  const displayIcon = viewing || iconBadge ? /* @__PURE__ */ jsxs("span", { className: "relative inline-flex flex-shrink-0 items-center justify-center", children: [
80
85
  viewing ? /* @__PURE__ */ jsx(Eye, { className: cn("text-primary-foreground", eyeSizeClass) }) : baseIcon,
81
86
  iconBadge
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/entities/DocumentEntity.tsx"],
4
- "sourcesContent": ["/**\n * DocumentEntity Component\n *\n * Displays a document as a compact entity pill with file-type icon and name.\n * Promoted from the ARBI app and DECOUPLED from the app store/API: it renders\n * purely from the `doc` prop and optional `onClick`/`onIconClick` callbacks.\n */\n\nimport React from 'react'\nimport { Eye, FolderOpen } from 'lucide-react'\nimport { EntityPill, type EntitySize } from './EntityPill'\nimport { AudIcon } from '../icons/AudIcon'\nimport { DocIcon } from '../icons/DocIcon'\nimport { EmlIcon } from '../icons/EmlIcon'\nimport { HtmlIcon } from '../icons/HtmlIcon'\nimport { ImgIcon } from '../icons/ImgIcon'\nimport { PdfIcon } from '../icons/PdfIcon'\nimport { TxtIcon } from '../icons/TxtIcon'\nimport { VidIcon } from '../icons/VidIcon'\nimport { XlsIcon } from '../icons/XlsIcon'\nimport { cn } from '../lib/utils'\n\n/** Minimal, decoupled document shape the pill needs. Structurally compatible\n * with the SDK's `DocResponse` (from `useDocuments`), but no dependency on it \u2014\n * a consumer can pass any object with these fields. */\nexport interface DocumentLike {\n file_name?: string | null\n folder?: string | null\n doc_metadata?: { title?: string | null } | null\n}\n\n/** Title of a document: `doc_metadata.title`, falling back to `file_name`. */\nexport function getDocumentTitle(\n document: DocumentLike | null | undefined,\n defaultValue = ''\n): string {\n if (!document) return defaultValue\n return document.doc_metadata?.title || document.file_name || defaultValue\n}\n\nconst IMAGE_FILE_TYPES = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp'])\n\n/**\n * Whether a document is an image (upload or AI-generated): pages and tokens are text-document\n * concepts, never meaningful for an image, whatever the stored value.\n */\nexport function isImageDocument(\n document: { file_type?: string | null } | null | undefined\n): boolean {\n return !!document?.file_type && IMAGE_FILE_TYPES.has(document.file_type.toLowerCase())\n}\n\n/** Hard cap on the pill's visible label. The full name still shows in the hover\n * tooltip; the pill stays compact so the trailing \u22EE menu never gets pushed. */\nconst MAX_LABEL_CHARS = 30\n\nfunction clampLabel(text: string): string {\n return text.length > MAX_LABEL_CHARS ? `${text.slice(0, MAX_LABEL_CHARS).trimEnd()}\u2026` : text\n}\n\nconst IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'tif', 'webp'])\nconst VIDEO_EXTENSIONS = new Set(['mp4', 'mpeg', 'mpg', 'webm', 'mov', 'avi', 'mkv'])\nconst AUDIO_EXTENSIONS = new Set(['mp3', 'wav', 'm4a', 'aac', 'ogg', 'flac'])\nconst EMAIL_EXTENSIONS = new Set(['eml', 'msg'])\nconst DOCX_EXTENSIONS = new Set(['doc', 'docx', 'odt', 'rtf'])\nconst XLSX_EXTENSIONS = new Set(['xls', 'xlsx', 'csv', 'ods'])\nconst HTML_EXTENSIONS = new Set(['html', 'htm', 'mhtml'])\nconst TEXT_EXTENSIONS = new Set([\n 'txt',\n 'text',\n 'md',\n 'markdown',\n 'log',\n 'json',\n 'xml',\n 'yaml',\n 'yml',\n])\n\n/**\n * The file-type glyph for a document, by extension.\n *\n * Exported because a document is recognisable by its icon before its name \u2014 any\n * surface that lists documents (the entity pill here, the workspace home's\n * Documents card) has to draw the same PDF/image/audio/video/email glyph, or\n * the same file looks like two different things in two places.\n */\nexport function getDocIcon(fileName?: string | null, iconSize = 14): React.ReactNode {\n const ext = fileName?.split('.').pop()?.toLowerCase()\n if (ext && VIDEO_EXTENSIONS.has(ext)) return <VidIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && AUDIO_EXTENSIONS.has(ext)) return <AudIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && IMAGE_EXTENSIONS.has(ext)) return <ImgIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && EMAIL_EXTENSIONS.has(ext)) return <EmlIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && DOCX_EXTENSIONS.has(ext)) return <DocIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && XLSX_EXTENSIONS.has(ext)) return <XlsIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && HTML_EXTENSIONS.has(ext)) return <HtmlIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && TEXT_EXTENSIONS.has(ext)) return <TxtIcon size={iconSize} className=\"flex-shrink-0\" />\n // Unrecognised or missing extension (e.g. a display title): most uploads are PDFs.\n return <PdfIcon size={iconSize} className=\"flex-shrink-0\" />\n}\n\nexport interface DocumentEntityProps {\n doc: DocumentLike\n /** Override the default file-type icon */\n icon?: React.ReactNode\n /** `sm` (default) for inline/compact contexts, `md` for a larger pill (e.g. the documents\n * grid's primary name column). */\n size?: Extract<EntitySize, 'sm' | 'md'>\n /** Override default label max width */\n labelMaxWidth?: string\n /** Extra classes on the label text (e.g. a heavier weight on a card face). */\n labelClassName?: string\n /** Click handler for the whole pill */\n onClick?: (e: React.MouseEvent) => void\n /** Click handler for the icon only */\n onIconClick?: (e: React.MouseEvent) => void\n /** When true, the file-type icon is replaced by a pinned Eye \u2014 marks the\n * document that is currently open/being viewed. */\n viewing?: boolean\n /** Small overlay pinned to the icon's corner (e.g. an \"available offline\" pip). */\n iconBadge?: React.ReactNode\n /** Small content right after the name, inside the click area (e.g. a status glyph). */\n status?: React.ReactNode\n /** Trailing control rendered outside the click area (e.g. a \u22EF actions menu). */\n actions?: React.ReactNode\n /** When true, the pill fills the available width and the name truncates via\n * CSS so the trailing \u22EF menu pins to the right edge. */\n fullWidth?: boolean\n /** Render the WHOLE title (skip the 30-char `clampLabel` string cut) while\n * keeping the normal content-width layout \u2014 for callers that want the full\n * name to show/wrap (via a `labelClassName`) without `fullWidth`'s w-full.\n * `fullWidth` already implies this. */\n noClamp?: boolean\n /** Override the default folder/title hover tooltip. */\n tooltipContent?: React.ReactNode\n className?: string\n /** Forwarded to the pill so a surface can address this document's chip. */\n 'data-testid'?: string\n}\n\nexport function DocumentEntity({\n doc,\n icon,\n size = 'sm',\n labelMaxWidth,\n labelClassName,\n onClick,\n onIconClick,\n viewing,\n iconBadge,\n status,\n actions,\n tooltipContent,\n className,\n fullWidth,\n noClamp,\n 'data-testid': testId,\n}: DocumentEntityProps) {\n const title = getDocumentTitle(doc)\n const ext = doc.file_name?.split('.').pop()?.toLowerCase()\n const iconSize = size === 'md' ? 16 : 14\n const eyeSizeClass = size === 'md' ? 'h-4 w-4' : 'h-3.5 w-3.5'\n\n const baseIcon = icon ?? getDocIcon(doc.file_name, iconSize)\n const displayIcon =\n viewing || iconBadge ? (\n <span className=\"relative inline-flex flex-shrink-0 items-center justify-center\">\n {viewing ? <Eye className={cn('text-primary-foreground', eyeSizeClass)} /> : baseIcon}\n {iconBadge}\n </span>\n ) : (\n baseIcon\n )\n\n const defaultTooltip = (\n <div className=\"flex flex-col gap-1 text-xs max-w-[440px]\">\n {doc.folder && (\n // `flex-wrap` so a deep folder path wraps WITHIN the balloon instead of\n // spilling out of it (each path segment is atomic and wraps as a unit;\n // `min-w-0` + `break-all` lets a single over-long segment break too).\n <span className=\"flex flex-wrap items-center gap-0.5 text-muted-foreground\">\n <FolderOpen className=\"h-3 w-3 text-yellow-600 shrink-0\" />\n {doc.folder.split('/').map((part, i, arr) => (\n <span key={i} className=\"flex min-w-0 items-center gap-0.5\">\n {i > 0 && <span className=\"text-muted-foreground/50 mx-0.5\">/</span>}\n <span className={`break-all ${i < arr.length - 1 ? 'text-muted-foreground/70' : ''}`}>\n {part}\n </span>\n </span>\n ))}\n </span>\n )}\n <span className=\"font-medium break-words\">\n {title}\n {ext ? `.${ext}` : ''}\n </span>\n </div>\n )\n\n return (\n <EntityPill\n icon={displayIcon}\n size={size}\n label={fullWidth || noClamp ? title : clampLabel(title)}\n labelMaxWidth={labelMaxWidth ?? 'max-w-[140px]'}\n labelClassName={labelClassName}\n secondaryText={status}\n fullWidth={fullWidth}\n onClick={onClick}\n onIconClick={onIconClick}\n moreContent={actions}\n className={className}\n highlight={viewing}\n data-testid={testId}\n tooltipContent={tooltipContent ?? defaultTooltip}\n />\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * DocumentEntity Component\n *\n * Displays a document as a compact entity pill with file-type icon and name.\n * Promoted from the ARBI app and DECOUPLED from the app store/API: it renders\n * purely from the `doc` prop and optional `onClick`/`onIconClick` callbacks.\n */\n\nimport React from 'react'\nimport { Eye, FolderOpen } from 'lucide-react'\nimport { EntityPill, type EntitySize } from './EntityPill'\nimport { AudIcon } from '../icons/AudIcon'\nimport { DocIcon } from '../icons/DocIcon'\nimport { EmlIcon } from '../icons/EmlIcon'\nimport { HtmlIcon } from '../icons/HtmlIcon'\nimport { ImgIcon } from '../icons/ImgIcon'\nimport { PdfIcon } from '../icons/PdfIcon'\nimport { PresIcon } from '../icons/PresIcon'\nimport { TxtIcon } from '../icons/TxtIcon'\nimport { VidIcon } from '../icons/VidIcon'\nimport { XlsIcon } from '../icons/XlsIcon'\nimport { cn } from '../lib/utils'\nimport { isPresentationFileName } from './file-type'\n\n/** Minimal, decoupled document shape the pill needs. Structurally compatible\n * with the SDK's `DocResponse` (from `useDocuments`), but no dependency on it \u2014\n * a consumer can pass any object with these fields. */\nexport interface DocumentLike {\n file_name?: string | null\n folder?: string | null\n doc_metadata?: { title?: string | null } | null\n}\n\n/** Title of a document: `doc_metadata.title`, falling back to `file_name`. */\nexport function getDocumentTitle(\n document: DocumentLike | null | undefined,\n defaultValue = ''\n): string {\n if (!document) return defaultValue\n return document.doc_metadata?.title || document.file_name || defaultValue\n}\n\nconst IMAGE_FILE_TYPES = new Set(['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp'])\n\n/**\n * Whether a document is an image (upload or AI-generated): pages and tokens are text-document\n * concepts, never meaningful for an image, whatever the stored value.\n */\nexport function isImageDocument(\n document: { file_type?: string | null } | null | undefined\n): boolean {\n return !!document?.file_type && IMAGE_FILE_TYPES.has(document.file_type.toLowerCase())\n}\n\n/** Hard cap on the pill's visible label. The full name still shows in the hover\n * tooltip; the pill stays compact so the trailing \u22EE menu never gets pushed. */\nconst MAX_LABEL_CHARS = 30\n\nfunction clampLabel(text: string): string {\n return text.length > MAX_LABEL_CHARS ? `${text.slice(0, MAX_LABEL_CHARS).trimEnd()}\u2026` : text\n}\n\nconst IMAGE_EXTENSIONS = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'tif', 'webp'])\nconst VIDEO_EXTENSIONS = new Set(['mp4', 'mpeg', 'mpg', 'webm', 'mov', 'avi', 'mkv'])\nconst AUDIO_EXTENSIONS = new Set(['mp3', 'wav', 'm4a', 'aac', 'ogg', 'flac'])\nconst EMAIL_EXTENSIONS = new Set(['eml', 'msg'])\nconst DOCX_EXTENSIONS = new Set(['doc', 'docx', 'odt', 'rtf'])\nconst XLSX_EXTENSIONS = new Set(['xls', 'xlsx', 'csv', 'ods'])\nconst HTML_EXTENSIONS = new Set(['html', 'htm', 'mhtml'])\nconst TEXT_EXTENSIONS = new Set([\n 'txt',\n 'text',\n 'md',\n 'markdown',\n 'log',\n 'json',\n 'xml',\n 'yaml',\n 'yml',\n])\n\n/**\n * The file-type glyph for a document, by extension.\n *\n * Exported because a document is recognisable by its icon before its name \u2014 any\n * surface that lists documents (the entity pill here, the workspace home's\n * Documents card) has to draw the same PDF/image/audio/video/email glyph, or\n * the same file looks like two different things in two places.\n */\nexport function getDocIcon(\n fileName?: string | null,\n iconSize = 14,\n /** The document's folder: a PDF in Presentations is a presentation. */\n folder?: string | null\n): React.ReactNode {\n if (isPresentationFileName(fileName, folder)) {\n return <PresIcon size={iconSize} className=\"flex-shrink-0\" />\n }\n const ext = fileName?.split('.').pop()?.toLowerCase()\n if (ext && VIDEO_EXTENSIONS.has(ext)) return <VidIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && AUDIO_EXTENSIONS.has(ext)) return <AudIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && IMAGE_EXTENSIONS.has(ext)) return <ImgIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && EMAIL_EXTENSIONS.has(ext)) return <EmlIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && DOCX_EXTENSIONS.has(ext)) return <DocIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && XLSX_EXTENSIONS.has(ext)) return <XlsIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && HTML_EXTENSIONS.has(ext)) return <HtmlIcon size={iconSize} className=\"flex-shrink-0\" />\n if (ext && TEXT_EXTENSIONS.has(ext)) return <TxtIcon size={iconSize} className=\"flex-shrink-0\" />\n // Unrecognised or missing extension (e.g. a display title): most uploads are PDFs.\n return <PdfIcon size={iconSize} className=\"flex-shrink-0\" />\n}\n\nexport interface DocumentEntityProps {\n doc: DocumentLike\n /** Override the default file-type icon */\n icon?: React.ReactNode\n /** `sm` (default) for inline/compact contexts, `md` for a larger pill (e.g. the documents\n * grid's primary name column). */\n size?: Extract<EntitySize, 'sm' | 'md'>\n /** Override default label max width */\n labelMaxWidth?: string\n /** Extra classes on the label text (e.g. a heavier weight on a card face). */\n labelClassName?: string\n /** Click handler for the whole pill */\n onClick?: (e: React.MouseEvent) => void\n /** Click handler for the icon only */\n onIconClick?: (e: React.MouseEvent) => void\n /** When true, the file-type icon is replaced by a pinned Eye \u2014 marks the\n * document that is currently open/being viewed. */\n viewing?: boolean\n /** Small overlay pinned to the icon's corner (e.g. an \"available offline\" pip). */\n iconBadge?: React.ReactNode\n /** Small content right after the name, inside the click area (e.g. a status glyph). */\n status?: React.ReactNode\n /** Trailing control rendered outside the click area (e.g. a \u22EF actions menu). */\n actions?: React.ReactNode\n /** When true, the pill fills the available width and the name truncates via\n * CSS so the trailing \u22EF menu pins to the right edge. */\n fullWidth?: boolean\n /** Render the WHOLE title (skip the 30-char `clampLabel` string cut) while\n * keeping the normal content-width layout \u2014 for callers that want the full\n * name to show/wrap (via a `labelClassName`) without `fullWidth`'s w-full.\n * `fullWidth` already implies this. */\n noClamp?: boolean\n /** Override the default folder/title hover tooltip. */\n tooltipContent?: React.ReactNode\n className?: string\n /** Forwarded to the pill so a surface can address this document's chip. */\n 'data-testid'?: string\n}\n\nexport function DocumentEntity({\n doc,\n icon,\n size = 'sm',\n labelMaxWidth,\n labelClassName,\n onClick,\n onIconClick,\n viewing,\n iconBadge,\n status,\n actions,\n tooltipContent,\n className,\n fullWidth,\n noClamp,\n 'data-testid': testId,\n}: DocumentEntityProps) {\n const title = getDocumentTitle(doc)\n const ext = doc.file_name?.split('.').pop()?.toLowerCase()\n const iconSize = size === 'md' ? 16 : 14\n const eyeSizeClass = size === 'md' ? 'h-4 w-4' : 'h-3.5 w-3.5'\n\n const baseIcon = icon ?? getDocIcon(doc.file_name, iconSize, doc.folder)\n const displayIcon =\n viewing || iconBadge ? (\n <span className=\"relative inline-flex flex-shrink-0 items-center justify-center\">\n {viewing ? <Eye className={cn('text-primary-foreground', eyeSizeClass)} /> : baseIcon}\n {iconBadge}\n </span>\n ) : (\n baseIcon\n )\n\n const defaultTooltip = (\n <div className=\"flex flex-col gap-1 text-xs max-w-[440px]\">\n {doc.folder && (\n // `flex-wrap` so a deep folder path wraps WITHIN the balloon instead of\n // spilling out of it (each path segment is atomic and wraps as a unit;\n // `min-w-0` + `break-all` lets a single over-long segment break too).\n <span className=\"flex flex-wrap items-center gap-0.5 text-muted-foreground\">\n <FolderOpen className=\"h-3 w-3 text-yellow-600 shrink-0\" />\n {doc.folder.split('/').map((part, i, arr) => (\n <span key={i} className=\"flex min-w-0 items-center gap-0.5\">\n {i > 0 && <span className=\"text-muted-foreground/50 mx-0.5\">/</span>}\n <span className={`break-all ${i < arr.length - 1 ? 'text-muted-foreground/70' : ''}`}>\n {part}\n </span>\n </span>\n ))}\n </span>\n )}\n <span className=\"font-medium break-words\">\n {title}\n {ext ? `.${ext}` : ''}\n </span>\n </div>\n )\n\n return (\n <EntityPill\n icon={displayIcon}\n size={size}\n label={fullWidth || noClamp ? title : clampLabel(title)}\n labelMaxWidth={labelMaxWidth ?? 'max-w-[140px]'}\n labelClassName={labelClassName}\n secondaryText={status}\n fullWidth={fullWidth}\n onClick={onClick}\n onIconClick={onIconClick}\n moreContent={actions}\n className={className}\n highlight={viewing}\n data-testid={testId}\n tooltipContent={tooltipContent ?? defaultTooltip}\n />\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }