@relevaince/document-viewer 0.1.1 → 0.2.0

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.
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __esm = (fn, res) => function __init() {
7
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
@@ -18,6 +20,14 @@ var __copyProps = (to, from, except, desc) => {
18
20
  }
19
21
  return to;
20
22
  };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
21
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
32
 
23
33
  // src/components/renderers/PdfRenderer.tsx
@@ -50,7 +60,7 @@ function bboxToHighlight(target) {
50
60
  }
51
61
  function CitationHighlightRenderer() {
52
62
  const { highlight, isScrolledTo } = (0, import_react_pdf_highlighter_extended.useHighlightContainerContext)();
53
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
63
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
54
64
  import_react_pdf_highlighter_extended.AreaHighlight,
55
65
  {
56
66
  highlight,
@@ -72,28 +82,28 @@ function PdfRenderer({
72
82
  onLoadSuccess,
73
83
  onLoadError
74
84
  }) {
75
- const highlighterRef = (0, import_react.useRef)(null);
76
- const loadNotifiedRef = (0, import_react.useRef)(false);
77
- const highlights = (0, import_react.useMemo)(() => {
85
+ const highlighterRef = (0, import_react3.useRef)(null);
86
+ const loadNotifiedRef = (0, import_react3.useRef)(false);
87
+ const highlights = (0, import_react3.useMemo)(() => {
78
88
  const h = bboxToHighlight(target);
79
89
  return h ? [h] : [];
80
90
  }, [target]);
81
- (0, import_react.useEffect)(() => {
91
+ (0, import_react3.useEffect)(() => {
82
92
  if (highlights.length === 0) return;
83
93
  const timer = setTimeout(() => {
84
94
  highlighterRef.current?.scrollToHighlight(highlights[0]);
85
95
  }, 400);
86
96
  return () => clearTimeout(timer);
87
97
  }, [highlights]);
88
- const handleUtilsRef = (0, import_react.useCallback)((utils) => {
98
+ const handleUtilsRef = (0, import_react3.useCallback)((utils) => {
89
99
  highlighterRef.current = utils;
90
100
  }, []);
91
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
101
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: "100%", height: "100%", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
92
102
  import_react_pdf_highlighter_extended.PdfLoader,
93
103
  {
94
104
  document: target.url,
95
105
  workerSrc: workerUrl ?? DEFAULT_WORKER_URL,
96
- beforeLoad: () => loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
106
+ beforeLoad: () => loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
97
107
  "div",
98
108
  {
99
109
  style: {
@@ -111,7 +121,7 @@ function PdfRenderer({
111
121
  ),
112
122
  errorMessage: (error) => {
113
123
  onLoadError?.(error);
114
- return errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
124
+ return errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
115
125
  "div",
116
126
  {
117
127
  style: {
@@ -133,7 +143,7 @@ function PdfRenderer({
133
143
  loadNotifiedRef.current = true;
134
144
  onLoadSuccess?.();
135
145
  }
136
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
146
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
137
147
  import_react_pdf_highlighter_extended.PdfHighlighter,
138
148
  {
139
149
  pdfDocument,
@@ -143,25 +153,137 @@ function PdfRenderer({
143
153
  width: "100%",
144
154
  height: "100%"
145
155
  },
146
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CitationHighlightRenderer, {})
156
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CitationHighlightRenderer, {})
147
157
  }
148
158
  );
149
159
  }
150
160
  }
151
161
  ) });
152
162
  }
153
- var import_react, import_react_pdf_highlighter_extended, import_jsx_runtime2, CITATION_ID, DEFAULT_WORKER_URL;
163
+ var import_react3, import_react_pdf_highlighter_extended, import_jsx_runtime4, CITATION_ID, DEFAULT_WORKER_URL;
154
164
  var init_PdfRenderer = __esm({
155
165
  "src/components/renderers/PdfRenderer.tsx"() {
156
166
  "use strict";
157
- import_react = require("react");
167
+ import_react3 = require("react");
158
168
  import_react_pdf_highlighter_extended = require("react-pdf-highlighter-extended");
159
- import_jsx_runtime2 = require("react/jsx-runtime");
169
+ import_jsx_runtime4 = require("react/jsx-runtime");
160
170
  CITATION_ID = "citation-highlight";
161
171
  DEFAULT_WORKER_URL = "https://unpkg.com/pdfjs-dist@4.4.168/build/pdf.worker.min.mjs";
162
172
  }
163
173
  });
164
174
 
175
+ // src/components/renderers/ImageRenderer.tsx
176
+ var ImageRenderer_exports = {};
177
+ __export(ImageRenderer_exports, {
178
+ ImageRenderer: () => ImageRenderer
179
+ });
180
+ function ImageRenderer({
181
+ target,
182
+ loadingComponent,
183
+ errorComponent,
184
+ onLoadSuccess,
185
+ onLoadError
186
+ }) {
187
+ const [status, setStatus] = (0, import_react4.useState)("loading");
188
+ const [error, setError] = (0, import_react4.useState)(null);
189
+ const handleLoad = (0, import_react4.useCallback)(() => {
190
+ setStatus("success");
191
+ onLoadSuccess?.();
192
+ }, [onLoadSuccess]);
193
+ const handleError = (0, import_react4.useCallback)(() => {
194
+ const err = new Error("Failed to load image");
195
+ setError(err);
196
+ setStatus("error");
197
+ onLoadError?.(err);
198
+ }, [onLoadError]);
199
+ if (status === "error") {
200
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: containerStyle3, children: errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
201
+ "div",
202
+ {
203
+ style: {
204
+ color: "#ef4444",
205
+ fontSize: 14,
206
+ fontFamily: 'system-ui, "Segoe UI", Roboto, sans-serif'
207
+ },
208
+ children: "Failed to load image"
209
+ }
210
+ ) });
211
+ }
212
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: containerStyle3, children: [
213
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
214
+ import_react_zoom_pan_pinch.TransformWrapper,
215
+ {
216
+ initialScale: 1,
217
+ minScale: 0.5,
218
+ maxScale: 8,
219
+ centerOnInit: true,
220
+ limitToBounds: false,
221
+ panning: { velocityDisabled: true },
222
+ doubleClick: { mode: "reset" },
223
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
224
+ import_react_zoom_pan_pinch.TransformComponent,
225
+ {
226
+ wrapperStyle: { width: "100%", height: "100%", cursor: "grab" },
227
+ contentStyle: containerStyle3,
228
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
229
+ "img",
230
+ {
231
+ src: target.url,
232
+ alt: target.title ?? "Document",
233
+ style: imageStyle,
234
+ onLoad: handleLoad,
235
+ onError: handleError,
236
+ draggable: false
237
+ }
238
+ )
239
+ }
240
+ )
241
+ }
242
+ ),
243
+ status === "loading" && (loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
244
+ "div",
245
+ {
246
+ style: {
247
+ position: "absolute",
248
+ inset: 0,
249
+ display: "flex",
250
+ alignItems: "center",
251
+ justifyContent: "center",
252
+ color: "#888",
253
+ fontSize: 14,
254
+ fontFamily: 'system-ui, "Segoe UI", Roboto, sans-serif'
255
+ },
256
+ children: "Loading image\u2026"
257
+ }
258
+ ))
259
+ ] });
260
+ }
261
+ var import_react4, import_react_zoom_pan_pinch, import_jsx_runtime5, containerStyle3, imageStyle;
262
+ var init_ImageRenderer = __esm({
263
+ "src/components/renderers/ImageRenderer.tsx"() {
264
+ "use strict";
265
+ import_react4 = require("react");
266
+ import_react_zoom_pan_pinch = require("react-zoom-pan-pinch");
267
+ import_jsx_runtime5 = require("react/jsx-runtime");
268
+ containerStyle3 = {
269
+ position: "relative",
270
+ width: "100%",
271
+ height: "100%",
272
+ display: "flex",
273
+ alignItems: "center",
274
+ justifyContent: "center",
275
+ overflow: "hidden",
276
+ background: "#1a1a1a"
277
+ };
278
+ imageStyle = {
279
+ maxWidth: "100%",
280
+ maxHeight: "100%",
281
+ objectFit: "contain",
282
+ display: "block"
283
+ };
284
+ }
285
+ });
286
+
165
287
  // src/index.ts
166
288
  var index_exports = {};
167
289
  __export(index_exports, {
@@ -171,13 +293,19 @@ __export(index_exports, {
171
293
  module.exports = __toCommonJS(index_exports);
172
294
 
173
295
  // src/components/DocumentViewer.tsx
174
- var import_react2 = require("react");
296
+ var import_react5 = require("react");
175
297
 
176
298
  // src/utils/getFileType.ts
299
+ var IMAGE_EXT = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".avif", ".svg"];
300
+ var JSON_EXT = [".json"];
301
+ var EML_EXT = [".eml"];
177
302
  function getFileType(url) {
178
303
  const clean = url.split("?")[0].split("#")[0].toLowerCase();
179
304
  if (clean.endsWith(".pdf")) return "pdf";
180
305
  if (clean.endsWith(".docx") || clean.endsWith(".doc")) return "docx";
306
+ if (IMAGE_EXT.some((e) => clean.endsWith(e))) return "image";
307
+ if (JSON_EXT.some((e) => clean.endsWith(e))) return "json";
308
+ if (EML_EXT.some((e) => clean.endsWith(e))) return "eml";
181
309
  return "unknown";
182
310
  }
183
311
 
@@ -203,21 +331,397 @@ function DocxRenderer({ target }) {
203
331
  );
204
332
  }
205
333
 
206
- // src/components/DocumentViewer.tsx
334
+ // src/components/renderers/JsonRenderer.tsx
335
+ var import_react = require("react");
336
+ var import_jsx_runtime2 = require("react/jsx-runtime");
337
+ var containerStyle = {
338
+ width: "100%",
339
+ height: "100%",
340
+ overflow: "auto",
341
+ background: "#0d1117",
342
+ padding: 16,
343
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
344
+ fontSize: 13,
345
+ lineHeight: 1.5
346
+ };
347
+ function formatKey(key) {
348
+ return JSON.stringify(key);
349
+ }
350
+ function JsonTree({ node, depth = 0 }) {
351
+ const [open, setOpen] = (0, import_react.useState)(
352
+ node.type === "array" || node.type === "object" ? node.open : true
353
+ );
354
+ const isOpen = node.type === "array" || node.type === "object" ? open : true;
355
+ const indent = depth * 18;
356
+ if (node.type === "string") {
357
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
358
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#79c0ff" }, children: formatKey(node.key) }),
359
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#6e7681" }, children: ": " }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#a5d6ff" }, children: JSON.stringify(node.value) })
361
+ ] });
362
+ }
363
+ if (node.type === "number") {
364
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
365
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#79c0ff" }, children: formatKey(node.key) }),
366
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#6e7681" }, children: ": " }),
367
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#79c0ff" }, children: node.value })
368
+ ] });
369
+ }
370
+ if (node.type === "boolean") {
371
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
372
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#79c0ff" }, children: formatKey(node.key) }),
373
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#6e7681" }, children: ": " }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#ff7b72" }, children: String(node.value) })
375
+ ] });
376
+ }
377
+ if (node.type === "null") {
378
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
379
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#79c0ff" }, children: formatKey(node.key) }),
380
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#6e7681" }, children: ": " }),
381
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#6e7681" }, children: "null" })
382
+ ] });
383
+ }
384
+ if (node.type === "array") {
385
+ const bracket = isOpen ? "[" : "[ \u2026 ]";
386
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginLeft: indent }, children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
388
+ "span",
389
+ {
390
+ onClick: () => node.children.length > 0 ? setOpen(!open) : null,
391
+ style: {
392
+ cursor: node.children.length > 0 ? "pointer" : "default",
393
+ color: "#ffa657"
394
+ },
395
+ children: [
396
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
397
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#79c0ff" }, children: formatKey(node.key) }),
398
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#6e7681" }, children: ": " })
399
+ ] }),
400
+ bracket
401
+ ]
402
+ }
403
+ ),
404
+ isOpen && node.children.map((child, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
405
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JsonTree, { node: child, depth: depth + 1 }),
406
+ i < node.children.length - 1 && ","
407
+ ] }, i)),
408
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#ffa657" }, children: "]" })
409
+ ] });
410
+ }
411
+ if (node.type === "object") {
412
+ const brace = isOpen ? "{" : "{ \u2026 }";
413
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginLeft: indent }, children: [
414
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
415
+ "span",
416
+ {
417
+ onClick: () => node.children.length > 0 ? setOpen(!open) : null,
418
+ style: {
419
+ cursor: node.children.length > 0 ? "pointer" : "default",
420
+ color: "#ffa657"
421
+ },
422
+ children: [
423
+ node.key != null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
424
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#79c0ff" }, children: formatKey(node.key) }),
425
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#6e7681" }, children: ": " })
426
+ ] }),
427
+ brace
428
+ ]
429
+ }
430
+ ),
431
+ isOpen && node.children.map(({ key, value }, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JsonTree, { node: { ...value, key }, depth: depth + 1 }),
433
+ i < node.children.length - 1 && ","
434
+ ] }, key)),
435
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#ffa657" }, children: "}" })
436
+ ] });
437
+ }
438
+ return null;
439
+ }
440
+ function parseToTree(value) {
441
+ if (value === null) return { type: "null" };
442
+ if (typeof value === "string") return { type: "string", value };
443
+ if (typeof value === "number") return { type: "number", value };
444
+ if (typeof value === "boolean") return { type: "boolean", value };
445
+ if (Array.isArray(value)) {
446
+ return {
447
+ type: "array",
448
+ children: value.map((v) => parseToTree(v)),
449
+ open: value.length <= 3
450
+ };
451
+ }
452
+ if (typeof value === "object" && value !== null) {
453
+ const obj = value;
454
+ return {
455
+ type: "object",
456
+ children: Object.entries(obj).map(([k, v]) => ({ key: k, value: parseToTree(v) })),
457
+ open: Object.keys(obj).length <= 5
458
+ };
459
+ }
460
+ return { type: "null" };
461
+ }
462
+ function JsonRenderer({
463
+ target,
464
+ loadingComponent,
465
+ errorComponent,
466
+ onLoadSuccess,
467
+ onLoadError
468
+ }) {
469
+ const [state, setState] = (0, import_react.useState)({ status: "loading" });
470
+ const load = (0, import_react.useCallback)(async () => {
471
+ try {
472
+ const res = await fetch(target.url, { credentials: "omit" });
473
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
474
+ const raw = await res.text();
475
+ const data = JSON.parse(raw);
476
+ setState({ status: "success", tree: parseToTree(data) });
477
+ onLoadSuccess?.();
478
+ } catch (err) {
479
+ const error = err instanceof Error ? err : new Error(String(err));
480
+ setState({ status: "error", error });
481
+ onLoadError?.(error);
482
+ }
483
+ }, [target.url, onLoadSuccess, onLoadError]);
484
+ (0, import_react.useEffect)(() => {
485
+ load();
486
+ }, [load]);
487
+ if (state.status === "loading") {
488
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: containerStyle, children: loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "#8b949e" }, children: "Loading JSON\u2026" }) });
489
+ }
490
+ if (state.status === "error") {
491
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: containerStyle, children: errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: "#f85149" }, children: [
492
+ "Failed to load JSON: ",
493
+ state.error.message
494
+ ] }) });
495
+ }
496
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: containerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(JsonTree, { node: state.tree }) });
497
+ }
498
+
499
+ // src/components/renderers/EmlRenderer.tsx
500
+ var import_react2 = require("react");
501
+ var import_dompurify = __toESM(require("dompurify"));
207
502
  var import_jsx_runtime3 = require("react/jsx-runtime");
208
- var LazyPdfRenderer = (0, import_react2.lazy)(
503
+ var containerStyle2 = {
504
+ width: "100%",
505
+ height: "100%",
506
+ overflow: "auto",
507
+ background: "#fff",
508
+ fontFamily: 'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
509
+ fontSize: 14,
510
+ lineHeight: 1.5
511
+ };
512
+ function parseEml(raw) {
513
+ const lines = raw.split(/\r?\n/);
514
+ const headers = [];
515
+ let i = 0;
516
+ let currentKey = null;
517
+ let currentValue = [];
518
+ while (i < lines.length) {
519
+ const line = lines[i];
520
+ if (line === "") {
521
+ i++;
522
+ break;
523
+ }
524
+ const match = line.match(/^([\w-]+):\s*(.*)$/);
525
+ if (match) {
526
+ if (currentKey) {
527
+ headers.push({ key: currentKey, value: currentValue.join(" ").trim() });
528
+ }
529
+ currentKey = match[1];
530
+ currentValue = [match[2]];
531
+ } else if (currentKey && /^\s/.test(line)) {
532
+ currentValue.push(line.trim());
533
+ }
534
+ i++;
535
+ }
536
+ if (currentKey) {
537
+ headers.push({ key: currentKey, value: currentValue.join(" ").trim() });
538
+ }
539
+ const rest = lines.slice(i).join("\n");
540
+ const contentType = headers.find(
541
+ (h) => h.key.toLowerCase() === "content-type"
542
+ )?.value ?? "text/plain";
543
+ const isMultipart = contentType.toLowerCase().includes("multipart");
544
+ let bodyText = null;
545
+ let bodyHtml = null;
546
+ if (!isMultipart) {
547
+ bodyText = rest.trim() || null;
548
+ if (contentType.toLowerCase().includes("text/html")) {
549
+ bodyHtml = bodyText;
550
+ bodyText = null;
551
+ }
552
+ return { headers, bodyText, bodyHtml };
553
+ }
554
+ const boundaryMatch = contentType.match(/boundary\s*=\s*["']?([^"'\s;]+)["']?/i);
555
+ const boundary = boundaryMatch?.[1]?.trim();
556
+ if (!boundary) {
557
+ bodyText = rest.trim() || null;
558
+ return { headers, bodyText, bodyHtml: null };
559
+ }
560
+ const parts = rest.split(new RegExp(`\\r?\\n--${escapeRegExp(boundary)}(?:\\r?\\n|--)`));
561
+ for (const part of parts) {
562
+ if (!part.trim()) continue;
563
+ const [head, ...bodyLines] = part.split(/\r?\n\r?\n/);
564
+ const body = bodyLines.join("\n\n").trim();
565
+ const partType = (head.match(/Content-Type:\s*([^;\s]+)/i) ?? [])[1]?.toLowerCase();
566
+ if (partType?.includes("text/plain") && !bodyText) bodyText = body;
567
+ if (partType?.includes("text/html") && !bodyHtml) bodyHtml = body;
568
+ }
569
+ if (!bodyText && !bodyHtml && parts[1]) {
570
+ const [head, ...bodyLines] = parts[1].split(/\r?\n\r?\n/);
571
+ const body = bodyLines.join("\n\n").trim();
572
+ const partType = (head.match(/Content-Type:\s*([^;\s]+)/i) ?? [])[1]?.toLowerCase();
573
+ if (partType?.includes("text/html")) bodyHtml = body;
574
+ else bodyText = body;
575
+ }
576
+ return { headers, bodyText, bodyHtml };
577
+ }
578
+ function escapeRegExp(s) {
579
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
580
+ }
581
+ var HEADER_KEYS = ["from", "to", "cc", "bcc", "subject", "date", "reply-to"];
582
+ function EmlRenderer({
583
+ target,
584
+ loadingComponent,
585
+ errorComponent,
586
+ onLoadSuccess,
587
+ onLoadError
588
+ }) {
589
+ const [state, setState] = (0, import_react2.useState)({ status: "loading" });
590
+ const load = (0, import_react2.useCallback)(async () => {
591
+ try {
592
+ const res = await fetch(target.url, { credentials: "omit" });
593
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
594
+ const raw = await res.text();
595
+ const data = parseEml(raw);
596
+ setState({ status: "success", data });
597
+ onLoadSuccess?.();
598
+ } catch (err) {
599
+ const error = err instanceof Error ? err : new Error(String(err));
600
+ setState({ status: "error", error });
601
+ onLoadError?.(error);
602
+ }
603
+ }, [target.url, onLoadSuccess, onLoadError]);
604
+ (0, import_react2.useEffect)(() => {
605
+ load();
606
+ }, [load]);
607
+ if (state.status === "loading") {
608
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: containerStyle2, children: loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 24, color: "#666" }, children: "Loading email\u2026" }) });
609
+ }
610
+ if (state.status === "error") {
611
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: containerStyle2, children: errorComponent ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: 24, color: "#dc2626" }, children: [
612
+ "Failed to load email: ",
613
+ state.error.message
614
+ ] }) });
615
+ }
616
+ const { headers, bodyText, bodyHtml } = state.data;
617
+ const preferredHeaders = HEADER_KEYS.filter(
618
+ (k) => headers.some((h) => h.key.toLowerCase() === k)
619
+ );
620
+ const otherHeaders = headers.filter(
621
+ (h) => !HEADER_KEYS.includes(h.key.toLowerCase())
622
+ );
623
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: containerStyle2, children: [
624
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { padding: 24, borderBottom: "1px solid #e5e7eb" }, children: [
625
+ preferredHeaders.map((key) => {
626
+ const h = headers.find((x) => x.key.toLowerCase() === key);
627
+ if (!h) return null;
628
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: 8 }, children: [
629
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { color: "#6b7280", fontWeight: 600, marginRight: 8 }, children: [
630
+ h.key,
631
+ ":"
632
+ ] }),
633
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#111" }, children: h.value })
634
+ ] }, h.key);
635
+ }),
636
+ otherHeaders.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("details", { style: { marginTop: 12 }, children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("summary", { style: { color: "#6b7280", cursor: "pointer", fontSize: 13 }, children: "Other headers" }),
638
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginTop: 8 }, children: otherHeaders.map((h) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { marginBottom: 4, fontSize: 13 }, children: [
639
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { style: { color: "#6b7280" }, children: [
640
+ h.key,
641
+ ": "
642
+ ] }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#374151" }, children: h.value })
644
+ ] }, h.key)) })
645
+ ] })
646
+ ] }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 24 }, children: bodyHtml ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
648
+ "div",
649
+ {
650
+ className: "document-viewer-eml-body",
651
+ style: { maxWidth: 720, overflow: "auto" },
652
+ dangerouslySetInnerHTML: {
653
+ __html: import_dompurify.default.sanitize(bodyHtml, {
654
+ ALLOWED_TAGS: [
655
+ "p",
656
+ "br",
657
+ "div",
658
+ "span",
659
+ "a",
660
+ "strong",
661
+ "em",
662
+ "u",
663
+ "b",
664
+ "i",
665
+ "ul",
666
+ "ol",
667
+ "li",
668
+ "h1",
669
+ "h2",
670
+ "h3",
671
+ "h4",
672
+ "table",
673
+ "tr",
674
+ "td",
675
+ "th",
676
+ "tbody",
677
+ "thead",
678
+ "img",
679
+ "blockquote",
680
+ "hr",
681
+ "pre",
682
+ "code"
683
+ ],
684
+ ALLOWED_ATTR: ["href", "src", "alt", "title", "target"]
685
+ })
686
+ }
687
+ }
688
+ ) : bodyText ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
689
+ "pre",
690
+ {
691
+ style: {
692
+ margin: 0,
693
+ whiteSpace: "pre-wrap",
694
+ wordBreak: "break-word",
695
+ color: "#374151",
696
+ fontFamily: "inherit",
697
+ fontSize: "inherit"
698
+ },
699
+ children: bodyText
700
+ }
701
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { color: "#6b7280" }, children: "No body content" }) })
702
+ ] });
703
+ }
704
+
705
+ // src/components/DocumentViewer.tsx
706
+ var import_jsx_runtime6 = require("react/jsx-runtime");
707
+ var LazyPdfRenderer = (0, import_react5.lazy)(
209
708
  () => Promise.resolve().then(() => (init_PdfRenderer(), PdfRenderer_exports)).then((mod) => ({
210
709
  default: mod.PdfRenderer
211
710
  }))
212
711
  );
712
+ var LazyImageRenderer = (0, import_react5.lazy)(
713
+ () => Promise.resolve().then(() => (init_ImageRenderer(), ImageRenderer_exports)).then((mod) => ({
714
+ default: mod.ImageRenderer
715
+ }))
716
+ );
213
717
  function ClientOnly({
214
718
  children,
215
719
  fallback
216
720
  }) {
217
- const [mounted, setMounted] = (0, import_react2.useState)(false);
218
- (0, import_react2.useEffect)(() => setMounted(true), []);
219
- if (!mounted) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback ?? null });
220
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
721
+ const [mounted, setMounted] = (0, import_react5.useState)(false);
722
+ (0, import_react5.useEffect)(() => setMounted(true), []);
723
+ if (!mounted) return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: fallback ?? null });
724
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children });
221
725
  }
222
726
  function DocumentViewer({
223
727
  target,
@@ -239,7 +743,7 @@ function DocumentViewer({
239
743
  borderRadius: 8,
240
744
  background: "#fafafa"
241
745
  };
242
- const loadingFallback = loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
746
+ const loadingFallback = loadingComponent ?? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
243
747
  "div",
244
748
  {
245
749
  style: {
@@ -255,8 +759,8 @@ function DocumentViewer({
255
759
  children: "Loading\u2026"
256
760
  }
257
761
  );
258
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className, style: wrapperStyle, children: [
259
- type === "pdf" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ClientOnly, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react2.Suspense, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
762
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className, style: wrapperStyle, children: [
763
+ type === "pdf" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ClientOnly, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react5.Suspense, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
260
764
  LazyPdfRenderer,
261
765
  {
262
766
  target,
@@ -267,8 +771,38 @@ function DocumentViewer({
267
771
  onLoadError
268
772
  }
269
773
  ) }) }),
270
- type === "docx" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DocxRenderer, { target }),
271
- type === "unknown" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
774
+ type === "docx" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DocxRenderer, { target }),
775
+ type === "image" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ClientOnly, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react5.Suspense, { fallback: loadingFallback, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
776
+ LazyImageRenderer,
777
+ {
778
+ target,
779
+ loadingComponent,
780
+ errorComponent,
781
+ onLoadSuccess,
782
+ onLoadError
783
+ }
784
+ ) }) }),
785
+ type === "json" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
786
+ JsonRenderer,
787
+ {
788
+ target,
789
+ loadingComponent,
790
+ errorComponent,
791
+ onLoadSuccess,
792
+ onLoadError
793
+ }
794
+ ),
795
+ type === "eml" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
796
+ EmlRenderer,
797
+ {
798
+ target,
799
+ loadingComponent,
800
+ errorComponent,
801
+ onLoadSuccess,
802
+ onLoadError
803
+ }
804
+ ),
805
+ type === "unknown" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
272
806
  "div",
273
807
  {
274
808
  style: {