@files-preview-app/preview-file 1.0.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/vue.cjs ADDED
@@ -0,0 +1,1832 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var vue = require('vue');
6
+ var DOMPurify = require('dompurify');
7
+ var docx = require('docx-preview');
8
+ var ExcelJS = require('exceljs');
9
+ var hljs = require('highlight.js');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ function _interopNamespace(e) {
14
+ if (e && e.__esModule) return e;
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
32
+ var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
33
+ var ExcelJS__default = /*#__PURE__*/_interopDefault(ExcelJS);
34
+ var hljs__default = /*#__PURE__*/_interopDefault(hljs);
35
+
36
+ // src/vue.ts
37
+ var EventEmitter = class {
38
+ listeners = /* @__PURE__ */ new Map();
39
+ /**
40
+ * Subscribe to an event.
41
+ * @returns An unsubscribe function.
42
+ */
43
+ on(event, handler) {
44
+ if (!this.listeners.has(event)) {
45
+ this.listeners.set(event, /* @__PURE__ */ new Set());
46
+ }
47
+ this.listeners.get(event).add(handler);
48
+ return () => this.off(event, handler);
49
+ }
50
+ /**
51
+ * Unsubscribe from an event.
52
+ */
53
+ off(event, handler) {
54
+ const handlers = this.listeners.get(event);
55
+ if (handlers) {
56
+ handlers.delete(handler);
57
+ if (handlers.size === 0) {
58
+ this.listeners.delete(event);
59
+ }
60
+ }
61
+ }
62
+ /**
63
+ * Emit an event with optional payload.
64
+ */
65
+ emit(event, data) {
66
+ const handlers = this.listeners.get(event);
67
+ if (handlers) {
68
+ for (const handler of handlers) {
69
+ try {
70
+ handler(data);
71
+ } catch (error) {
72
+ console.error(`[FilePreview] Error in '${event}' handler:`, error);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Remove all listeners, optionally for a specific event.
79
+ */
80
+ removeAll(event) {
81
+ if (event) {
82
+ this.listeners.delete(event);
83
+ } else {
84
+ this.listeners.clear();
85
+ }
86
+ }
87
+ /**
88
+ * Get the count of listeners for a specific event.
89
+ */
90
+ listenerCount(event) {
91
+ return this.listeners.get(event)?.size ?? 0;
92
+ }
93
+ };
94
+ var MAGIC_NUMBERS = [
95
+ { bytes: [37, 80, 68, 70], mime: "application/pdf" },
96
+ // %PDF
97
+ { bytes: [80, 75, 3, 4], mime: "application/zip" },
98
+ // PK.. (ZIP/DOCX/XLSX/PPTX)
99
+ { bytes: [137, 80, 78, 71, 13, 10, 26, 10], mime: "image/png" },
100
+ // PNG
101
+ { bytes: [255, 216, 255], mime: "image/jpeg" },
102
+ // JPEG
103
+ { bytes: [71, 73, 70, 56], mime: "image/gif" },
104
+ // GIF87a/GIF89a
105
+ { bytes: [66, 77], mime: "image/bmp" },
106
+ // BMP
107
+ { bytes: [0, 0, 1, 0], mime: "image/x-icon" },
108
+ // ICO
109
+ { bytes: [73, 73, 42, 0], mime: "image/tiff" },
110
+ // TIFF (LE)
111
+ { bytes: [77, 77, 0, 42], mime: "image/tiff" },
112
+ // TIFF (BE)
113
+ { bytes: [26, 69, 223, 163], mime: "video/webm" },
114
+ // WebM/MKV
115
+ { bytes: [102, 116, 121, 112], mime: "video/mp4", offset: 4 },
116
+ // MP4 (ftyp)
117
+ { bytes: [73, 68, 51], mime: "audio/mpeg" },
118
+ // MP3 (ID3)
119
+ { bytes: [255, 251], mime: "audio/mpeg" },
120
+ // MP3 (sync)
121
+ { bytes: [255, 243], mime: "audio/mpeg" },
122
+ // MP3 (sync)
123
+ { bytes: [79, 103, 103, 83], mime: "audio/ogg" },
124
+ // OGG
125
+ { bytes: [82, 73, 70, 70], mime: "audio/wav" },
126
+ // WAV/RIFF (also WebP)
127
+ { bytes: [102, 76, 97, 67], mime: "audio/flac" },
128
+ // FLAC
129
+ { bytes: [123, 92, 114, 116, 102], mime: "text/rtf" }
130
+ // RTF
131
+ ];
132
+ var EXTENSION_MIME_MAP = {
133
+ // Documents
134
+ ".pdf": "application/pdf",
135
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
136
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
137
+ ".xls": "application/vnd.ms-excel",
138
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
139
+ ".csv": "text/csv",
140
+ ".tsv": "text/tab-separated-values",
141
+ // Images
142
+ ".png": "image/png",
143
+ ".jpg": "image/jpeg",
144
+ ".jpeg": "image/jpeg",
145
+ ".gif": "image/gif",
146
+ ".webp": "image/webp",
147
+ ".bmp": "image/bmp",
148
+ ".svg": "image/svg+xml",
149
+ ".ico": "image/x-icon",
150
+ ".tiff": "image/tiff",
151
+ ".tif": "image/tiff",
152
+ // Media
153
+ ".mp4": "video/mp4",
154
+ ".webm": "video/webm",
155
+ ".ogg": "audio/ogg",
156
+ ".ogv": "video/ogg",
157
+ ".mov": "video/quicktime",
158
+ ".mp3": "audio/mpeg",
159
+ ".wav": "audio/wav",
160
+ ".aac": "audio/aac",
161
+ ".flac": "audio/flac",
162
+ // Code/Text
163
+ ".txt": "text/plain",
164
+ ".log": "text/plain",
165
+ ".json": "application/json",
166
+ ".xml": "application/xml",
167
+ ".html": "text/html",
168
+ ".htm": "text/html",
169
+ ".css": "text/css",
170
+ ".js": "text/javascript",
171
+ ".ts": "text/typescript",
172
+ ".jsx": "text/jsx",
173
+ ".tsx": "text/tsx",
174
+ ".py": "text/x-python",
175
+ ".java": "text/x-java",
176
+ ".c": "text/x-c",
177
+ ".cpp": "text/x-c++",
178
+ ".h": "text/x-c",
179
+ ".go": "text/x-go",
180
+ ".rs": "text/x-rust",
181
+ ".rb": "text/x-ruby",
182
+ ".php": "text/x-php",
183
+ ".sh": "text/x-shellscript",
184
+ ".bash": "text/x-shellscript",
185
+ ".sql": "text/x-sql",
186
+ ".yaml": "text/yaml",
187
+ ".yml": "text/yaml",
188
+ ".toml": "text/toml",
189
+ ".ini": "text/ini",
190
+ ".conf": "text/plain",
191
+ ".env": "text/plain",
192
+ ".md": "text/markdown",
193
+ ".scss": "text/x-scss",
194
+ ".less": "text/x-less",
195
+ ".vue": "text/x-vue",
196
+ ".svelte": "text/x-svelte",
197
+ ".dart": "text/x-dart",
198
+ ".kt": "text/x-kotlin",
199
+ ".swift": "text/x-swift",
200
+ ".r": "text/x-r",
201
+ ".scala": "text/x-scala",
202
+ ".lua": "text/x-lua",
203
+ ".perl": "text/x-perl",
204
+ ".pl": "text/x-perl"
205
+ };
206
+ function detectMagicBytes(buffer) {
207
+ const bytes = new Uint8Array(buffer.slice(0, 16));
208
+ for (const sig of MAGIC_NUMBERS) {
209
+ const offset = sig.offset ?? 0;
210
+ let match = true;
211
+ for (let i = 0; i < sig.bytes.length; i++) {
212
+ const actual = bytes[offset + i];
213
+ const expected = sig.bytes[i];
214
+ const mask = sig.mask?.[i] ?? 255;
215
+ if ((actual & mask) !== expected) {
216
+ match = false;
217
+ break;
218
+ }
219
+ }
220
+ if (match) {
221
+ if (sig.mime === "audio/wav" && bytes.length >= 12) {
222
+ const format = String.fromCharCode(bytes[8], bytes[9], bytes[10], bytes[11]);
223
+ if (format === "WEBP") return "image/webp";
224
+ if (format === "AVI ") return "video/x-msvideo";
225
+ return "audio/wav";
226
+ }
227
+ return sig.mime;
228
+ }
229
+ }
230
+ return null;
231
+ }
232
+ function detectOoxmlType(buffer) {
233
+ const text = new TextDecoder("ascii", { fatal: false }).decode(
234
+ new Uint8Array(buffer.slice(0, 4e3))
235
+ );
236
+ if (text.includes("word/")) {
237
+ return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
238
+ }
239
+ if (text.includes("xl/")) {
240
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
241
+ }
242
+ if (text.includes("ppt/")) {
243
+ return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
244
+ }
245
+ return "application/zip";
246
+ }
247
+ function extractExtension(nameOrUrl) {
248
+ try {
249
+ const url = new URL(nameOrUrl);
250
+ const pathname = url.pathname;
251
+ const dotIndex = pathname.lastIndexOf(".");
252
+ if (dotIndex !== -1) {
253
+ return pathname.slice(dotIndex).toLowerCase().split("?")[0];
254
+ }
255
+ } catch {
256
+ const dotIndex = nameOrUrl.lastIndexOf(".");
257
+ if (dotIndex !== -1) {
258
+ return nameOrUrl.slice(dotIndex).toLowerCase();
259
+ }
260
+ }
261
+ return void 0;
262
+ }
263
+ function mimeFromExtension(extension) {
264
+ return EXTENSION_MIME_MAP[extension.toLowerCase()];
265
+ }
266
+ async function sourceToArrayBuffer(source, signal) {
267
+ let buffer;
268
+ const metadata = {};
269
+ if (typeof source === "string") {
270
+ metadata.name = source.split("/").pop()?.split("?")[0];
271
+ metadata.extension = extractExtension(source);
272
+ const response = await fetch(source, { signal });
273
+ if (!response.ok) {
274
+ throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`);
275
+ }
276
+ metadata.mimeType = response.headers.get("content-type") ?? void 0;
277
+ buffer = await response.arrayBuffer();
278
+ } else if (source instanceof File) {
279
+ metadata.name = source.name;
280
+ metadata.size = source.size;
281
+ metadata.mimeType = source.type || void 0;
282
+ metadata.extension = extractExtension(source.name);
283
+ buffer = await source.arrayBuffer();
284
+ } else if (source instanceof Blob) {
285
+ metadata.size = source.size;
286
+ metadata.mimeType = source.type || void 0;
287
+ buffer = await source.arrayBuffer();
288
+ } else if (source instanceof ArrayBuffer) {
289
+ buffer = source;
290
+ } else if (source instanceof Uint8Array) {
291
+ buffer = source.buffer.slice(
292
+ source.byteOffset,
293
+ source.byteOffset + source.byteLength
294
+ );
295
+ } else {
296
+ throw new Error("Unsupported file source type");
297
+ }
298
+ metadata.size = metadata.size ?? buffer.byteLength;
299
+ const magicMime = detectMagicBytes(buffer);
300
+ if (magicMime) {
301
+ if (magicMime === "application/zip") {
302
+ const ooxmlMime = detectOoxmlType(buffer);
303
+ if (ooxmlMime && ooxmlMime !== "application/zip") {
304
+ metadata.mimeType = ooxmlMime;
305
+ if (ooxmlMime.includes("wordprocessing")) metadata.extension = metadata.extension ?? ".docx";
306
+ else if (ooxmlMime.includes("spreadsheet")) metadata.extension = metadata.extension ?? ".xlsx";
307
+ else if (ooxmlMime.includes("presentation")) metadata.extension = metadata.extension ?? ".pptx";
308
+ } else {
309
+ metadata.mimeType = metadata.mimeType ?? magicMime;
310
+ }
311
+ } else {
312
+ metadata.mimeType = magicMime;
313
+ }
314
+ }
315
+ if (!metadata.mimeType && metadata.extension) {
316
+ metadata.mimeType = mimeFromExtension(metadata.extension);
317
+ }
318
+ return { buffer, metadata };
319
+ }
320
+ function sanitizeSVG(svg) {
321
+ return DOMPurify__default.default.sanitize(svg, {
322
+ USE_PROFILES: { svg: true, svgFilters: true }
323
+ });
324
+ }
325
+ function createElement(tag, attrs, ...children) {
326
+ const el = document.createElement(tag);
327
+ if (attrs) {
328
+ for (const [key, value] of Object.entries(attrs)) {
329
+ if (key === "className") {
330
+ el.className = value;
331
+ } else {
332
+ el.setAttribute(key, value);
333
+ }
334
+ }
335
+ }
336
+ for (const child of children) {
337
+ if (typeof child === "string") {
338
+ el.appendChild(document.createTextNode(child));
339
+ } else {
340
+ el.appendChild(child);
341
+ }
342
+ }
343
+ return el;
344
+ }
345
+ var ICON_PAGE_PREV = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>`;
346
+ var ICON_PAGE_NEXT = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>`;
347
+ var ToolbarController = class {
348
+ el;
349
+ toolbarEl;
350
+ actions = [];
351
+ constructor(container) {
352
+ this.el = container;
353
+ this.toolbarEl = createElement("div", { className: "fp-toolbar" });
354
+ this.el.appendChild(this.toolbarEl);
355
+ }
356
+ update(actions) {
357
+ this.actions = actions;
358
+ this.render();
359
+ }
360
+ show() {
361
+ this.el.style.display = "block";
362
+ }
363
+ hide() {
364
+ this.el.style.display = "none";
365
+ }
366
+ destroy() {
367
+ this.el.innerHTML = "";
368
+ }
369
+ render() {
370
+ this.toolbarEl.innerHTML = "";
371
+ const groups = {
372
+ navigation: [],
373
+ zoom: [],
374
+ view: [],
375
+ actions: []
376
+ };
377
+ for (const action of this.actions) {
378
+ if (groups[action.group]) {
379
+ groups[action.group].push(action);
380
+ }
381
+ }
382
+ const order = ["navigation", "zoom", "view", "actions"];
383
+ let isFirstGroup = true;
384
+ for (const group of order) {
385
+ const groupActions = groups[group];
386
+ if (groupActions.length === 0) continue;
387
+ if (!isFirstGroup) {
388
+ this.toolbarEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
389
+ }
390
+ isFirstGroup = false;
391
+ const groupEl = createElement("div", { className: "fp-toolbar-group" });
392
+ for (const action of groupActions) {
393
+ if (action.type === "separator") {
394
+ groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
395
+ } else if (action.type === "page-nav") {
396
+ const prevBtn = this.createButton(
397
+ "prev",
398
+ ICON_PAGE_PREV,
399
+ "Previous Page",
400
+ () => action.execute("prev")
401
+ );
402
+ const nextBtn = this.createButton(
403
+ "next",
404
+ ICON_PAGE_NEXT,
405
+ "Next Page",
406
+ () => action.execute("next")
407
+ );
408
+ const input = createElement("input", {
409
+ className: "fp-toolbar-input",
410
+ type: "number",
411
+ value: (action.value ?? 1).toString(),
412
+ min: "1",
413
+ max: (action.max ?? 1).toString()
414
+ });
415
+ input.addEventListener("change", () => {
416
+ action.execute("go", parseInt(input.value, 10));
417
+ });
418
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
419
+ groupEl.appendChild(prevBtn);
420
+ groupEl.appendChild(input);
421
+ groupEl.appendChild(label);
422
+ groupEl.appendChild(nextBtn);
423
+ } else if (action.type === "range") {
424
+ const input = createElement("input", {
425
+ type: "range",
426
+ min: (action.min ?? 0).toString(),
427
+ max: (action.max ?? 100).toString(),
428
+ step: (action.step ?? 1).toString(),
429
+ value: (action.value ?? 50).toString()
430
+ });
431
+ input.addEventListener("input", () => {
432
+ action.execute(parseFloat(input.value));
433
+ });
434
+ groupEl.appendChild(input);
435
+ } else {
436
+ const btn = this.createButton(
437
+ action.id,
438
+ action.icon,
439
+ action.label,
440
+ () => action.execute()
441
+ );
442
+ if (action.enabled === false) btn.disabled = true;
443
+ if (action.active) btn.classList.add("active");
444
+ groupEl.appendChild(btn);
445
+ }
446
+ }
447
+ this.toolbarEl.appendChild(groupEl);
448
+ }
449
+ }
450
+ createButton(id, iconHtml, title, onClick) {
451
+ const btn = createElement("button", {
452
+ className: "fp-toolbar-btn",
453
+ title,
454
+ type: "button",
455
+ "data-action-id": id
456
+ });
457
+ if (iconHtml.startsWith("<svg")) {
458
+ btn.innerHTML = sanitizeSVG(iconHtml);
459
+ } else {
460
+ btn.textContent = iconHtml;
461
+ }
462
+ btn.addEventListener("click", onClick);
463
+ return btn;
464
+ }
465
+ };
466
+ var ThumbnailPanel = class {
467
+ el;
468
+ panelEl;
469
+ thumbnails = [];
470
+ onSelect;
471
+ activeIndex = 0;
472
+ constructor(container) {
473
+ this.el = container;
474
+ this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
475
+ this.el.appendChild(this.panelEl);
476
+ }
477
+ update(thumbnails, onSelect) {
478
+ this.thumbnails = thumbnails;
479
+ this.onSelect = onSelect;
480
+ this.render();
481
+ }
482
+ setActive(index) {
483
+ this.activeIndex = index;
484
+ const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
485
+ items.forEach((item, i) => {
486
+ if (i === index) {
487
+ item.classList.add("active");
488
+ } else {
489
+ item.classList.remove("active");
490
+ }
491
+ });
492
+ }
493
+ show() {
494
+ this.panelEl.classList.remove("hidden");
495
+ }
496
+ hide() {
497
+ this.panelEl.classList.add("hidden");
498
+ }
499
+ toggle() {
500
+ this.panelEl.classList.toggle("hidden");
501
+ }
502
+ destroy() {
503
+ this.el.innerHTML = "";
504
+ }
505
+ async render() {
506
+ this.panelEl.innerHTML = "";
507
+ for (let i = 0; i < this.thumbnails.length; i++) {
508
+ const thumb = this.thumbnails[i];
509
+ const itemEl = createElement("div", { className: "fp-thumbnail-item" });
510
+ if (i === this.activeIndex) {
511
+ itemEl.classList.add("active");
512
+ }
513
+ itemEl.addEventListener("click", () => {
514
+ this.setActive(i);
515
+ this.onSelect?.(i);
516
+ });
517
+ const canvas = createElement("canvas", { width: "150", height: "200" });
518
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
519
+ itemEl.appendChild(canvas);
520
+ itemEl.appendChild(label);
521
+ this.panelEl.appendChild(itemEl);
522
+ try {
523
+ await thumb.render(canvas);
524
+ } catch (err) {
525
+ console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
526
+ }
527
+ }
528
+ }
529
+ };
530
+ var FilePreviewViewer = class {
531
+ plugins = [];
532
+ activeInstance = null;
533
+ abortController = null;
534
+ eventEmitter = new EventEmitter();
535
+ toolbar = null;
536
+ thumbnailPanel = null;
537
+ wrapperEl = null;
538
+ contentEl = null;
539
+ currentBuffer = null;
540
+ currentMetadata = null;
541
+ /**
542
+ * Register a preview plugin.
543
+ */
544
+ registerPlugin(plugin) {
545
+ this.plugins.push(plugin);
546
+ this.plugins.sort((a, b) => (b.weight ?? 0) - (a.weight ?? 0));
547
+ return this;
548
+ }
549
+ /**
550
+ * Register multiple plugins at once.
551
+ */
552
+ registerPlugins(plugins) {
553
+ for (const plugin of plugins) {
554
+ this.registerPlugin(plugin);
555
+ }
556
+ return this;
557
+ }
558
+ /**
559
+ * Preview a file in the given container element.
560
+ */
561
+ async preview(container, source, options = {}) {
562
+ this.abort();
563
+ this.abortController = new AbortController();
564
+ const { signal } = this.abortController;
565
+ this.destroyInstance();
566
+ this.setupDOM(container, options);
567
+ this.eventEmitter.emit("loading", { source });
568
+ this.showLoading();
569
+ try {
570
+ const { buffer, metadata } = await sourceToArrayBuffer(source, signal);
571
+ this.currentBuffer = buffer;
572
+ this.currentMetadata = metadata;
573
+ if (signal.aborted) throw new DOMException("Aborted", "AbortError");
574
+ const fileInfo = { metadata, buffer };
575
+ const matchedPlugin = await this.findPlugin(fileInfo);
576
+ if (!matchedPlugin) {
577
+ throw new Error(
578
+ `Unsupported file type: ${metadata.extension ?? metadata.mimeType ?? "unknown"}`
579
+ );
580
+ }
581
+ if (signal.aborted) throw new DOMException("Aborted", "AbortError");
582
+ if (this.contentEl) {
583
+ this.contentEl.innerHTML = "";
584
+ }
585
+ const instance = await matchedPlugin.render({
586
+ container: this.contentEl,
587
+ source,
588
+ metadata,
589
+ buffer,
590
+ options,
591
+ signal,
592
+ emit: (event, payload) => this.eventEmitter.emit(event, payload)
593
+ });
594
+ this.activeInstance = instance;
595
+ if (options.showToolbar !== false && this.toolbar) {
596
+ const actions = matchedPlugin.getToolbarActions(instance);
597
+ this.toolbar.update(actions);
598
+ this.toolbar.show();
599
+ }
600
+ if (instance.getThumbnails && this.thumbnailPanel) {
601
+ const thumbnails = await Promise.resolve(instance.getThumbnails());
602
+ if (thumbnails && thumbnails.length > 0) {
603
+ this.thumbnailPanel.update(thumbnails, (index) => {
604
+ instance.goToPage?.(index + 1);
605
+ });
606
+ if (options.showThumbnails) {
607
+ this.thumbnailPanel.show();
608
+ }
609
+ }
610
+ }
611
+ this.hideLoading();
612
+ this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
613
+ return instance;
614
+ } catch (error) {
615
+ this.hideLoading();
616
+ if (error instanceof DOMException && error.name === "AbortError") {
617
+ throw error;
618
+ }
619
+ this.showError(error instanceof Error ? error.message : "Failed to preview file");
620
+ this.eventEmitter.emit("error", error);
621
+ throw error;
622
+ }
623
+ }
624
+ /**
625
+ * Subscribe to viewer events.
626
+ */
627
+ on(event, handler) {
628
+ return this.eventEmitter.on(event, handler);
629
+ }
630
+ /**
631
+ * Destroy the viewer and clean up all resources.
632
+ */
633
+ destroy() {
634
+ this.abort();
635
+ this.destroyInstance();
636
+ this.toolbar?.destroy();
637
+ this.thumbnailPanel?.destroy();
638
+ this.eventEmitter.emit("destroy", null);
639
+ this.eventEmitter.removeAll();
640
+ if (this.wrapperEl?.parentNode) {
641
+ this.wrapperEl.parentNode.removeChild(this.wrapperEl);
642
+ }
643
+ this.wrapperEl = null;
644
+ this.contentEl = null;
645
+ this.toolbar = null;
646
+ this.thumbnailPanel = null;
647
+ this.currentBuffer = null;
648
+ this.currentMetadata = null;
649
+ }
650
+ /**
651
+ * Get the currently active preview instance.
652
+ */
653
+ getInstance() {
654
+ return this.activeInstance;
655
+ }
656
+ // --- Private methods ---
657
+ abort() {
658
+ if (this.abortController) {
659
+ this.abortController.abort();
660
+ this.abortController = null;
661
+ }
662
+ }
663
+ destroyInstance() {
664
+ if (this.activeInstance) {
665
+ this.activeInstance.destroy();
666
+ this.activeInstance = null;
667
+ }
668
+ }
669
+ async findPlugin(fileInfo) {
670
+ for (const plugin of this.plugins) {
671
+ const result = plugin.supports(fileInfo);
672
+ const supports = result instanceof Promise ? await result : result;
673
+ if (supports) return plugin;
674
+ }
675
+ return null;
676
+ }
677
+ setupDOM(container, options) {
678
+ if (this.wrapperEl?.parentNode === container) return;
679
+ if (this.wrapperEl?.parentNode) {
680
+ this.wrapperEl.parentNode.removeChild(this.wrapperEl);
681
+ }
682
+ const themeClass = options.theme === "dark" ? "fp-theme-dark" : "";
683
+ const toolbarPos = options.toolbarPosition ?? "top";
684
+ this.wrapperEl = document.createElement("div");
685
+ this.wrapperEl.className = `fp-viewer ${themeClass} ${options.className ?? ""}`.trim();
686
+ const toolbarEl = document.createElement("div");
687
+ toolbarEl.className = "fp-toolbar-container";
688
+ this.contentEl = document.createElement("div");
689
+ this.contentEl.className = "fp-content";
690
+ const thumbnailEl = document.createElement("div");
691
+ thumbnailEl.className = "fp-thumbnail-container";
692
+ const bodyEl = document.createElement("div");
693
+ bodyEl.className = "fp-body";
694
+ bodyEl.appendChild(thumbnailEl);
695
+ bodyEl.appendChild(this.contentEl);
696
+ if (toolbarPos === "top") {
697
+ this.wrapperEl.appendChild(toolbarEl);
698
+ this.wrapperEl.appendChild(bodyEl);
699
+ } else {
700
+ this.wrapperEl.appendChild(bodyEl);
701
+ this.wrapperEl.appendChild(toolbarEl);
702
+ }
703
+ container.innerHTML = "";
704
+ container.appendChild(this.wrapperEl);
705
+ this.toolbar = new ToolbarController(toolbarEl);
706
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
707
+ }
708
+ showLoading() {
709
+ if (!this.contentEl) return;
710
+ const loader = document.createElement("div");
711
+ loader.className = "fp-loading";
712
+ loader.innerHTML = '<div class="fp-spinner"></div><span>Loading preview...</span>';
713
+ this.contentEl.appendChild(loader);
714
+ }
715
+ hideLoading() {
716
+ if (!this.contentEl) return;
717
+ const loader = this.contentEl.querySelector(".fp-loading");
718
+ if (loader) loader.remove();
719
+ }
720
+ showError(message) {
721
+ if (!this.contentEl) return;
722
+ this.contentEl.innerHTML = "";
723
+ const errorEl = document.createElement("div");
724
+ errorEl.className = "fp-error";
725
+ errorEl.innerHTML = `
726
+ <div class="fp-error-icon">\u26A0\uFE0F</div>
727
+ <div class="fp-error-message">${message}</div>
728
+ `;
729
+ this.contentEl.appendChild(errorEl);
730
+ }
731
+ };
732
+
733
+ // ../plugins/pdf/dist/index.js
734
+ var PdfPlugin = class {
735
+ id = "pdf";
736
+ name = "PDF Preview";
737
+ extensions = [".pdf"];
738
+ mimeTypes = ["application/pdf"];
739
+ weight = 100;
740
+ supports(file) {
741
+ const ext = file.metadata.extension?.toLowerCase();
742
+ const mime = file.metadata.mimeType?.toLowerCase();
743
+ return ext === ".pdf" || mime === "application/pdf";
744
+ }
745
+ getToolbarActions(instance) {
746
+ return [
747
+ {
748
+ id: "zoom-out",
749
+ icon: "zoom-out",
750
+ label: "Zoom Out",
751
+ type: "button",
752
+ group: "zoom",
753
+ execute: () => {
754
+ instance.zoomOut?.();
755
+ }
756
+ },
757
+ {
758
+ id: "zoom-in",
759
+ icon: "zoom-in",
760
+ label: "Zoom In",
761
+ type: "button",
762
+ group: "zoom",
763
+ execute: () => {
764
+ instance.zoomIn?.();
765
+ }
766
+ },
767
+ {
768
+ id: "fit-page",
769
+ icon: "fit-page",
770
+ label: "Fit to Page",
771
+ type: "button",
772
+ group: "zoom",
773
+ execute: () => {
774
+ instance.fitToPage?.();
775
+ }
776
+ },
777
+ {
778
+ id: "rotate-cw",
779
+ icon: "rotate-cw",
780
+ label: "Rotate",
781
+ type: "button",
782
+ group: "view",
783
+ execute: () => {
784
+ instance.rotateCW?.();
785
+ }
786
+ },
787
+ {
788
+ id: "page-nav",
789
+ icon: "page-nav",
790
+ label: "Page Navigation",
791
+ type: "page-nav",
792
+ group: "navigation",
793
+ execute: (page) => {
794
+ if (typeof page === "number") instance.goToPage?.(page);
795
+ }
796
+ },
797
+ {
798
+ id: "download",
799
+ icon: "download",
800
+ label: "Download",
801
+ type: "button",
802
+ group: "actions",
803
+ execute: () => {
804
+ instance.download?.();
805
+ }
806
+ },
807
+ {
808
+ id: "print",
809
+ icon: "print",
810
+ label: "Print",
811
+ type: "button",
812
+ group: "actions",
813
+ execute: () => {
814
+ instance.print?.();
815
+ }
816
+ }
817
+ ];
818
+ }
819
+ async render(ctx) {
820
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
821
+ const url = URL.createObjectURL(blob);
822
+ const wrapper = document.createElement("div");
823
+ wrapper.style.width = "100%";
824
+ wrapper.style.height = "100%";
825
+ wrapper.style.overflow = "hidden";
826
+ wrapper.style.display = "flex";
827
+ wrapper.style.justifyContent = "center";
828
+ wrapper.style.alignItems = "center";
829
+ const iframe = document.createElement("iframe");
830
+ iframe.src = url;
831
+ iframe.style.width = "100%";
832
+ iframe.style.height = "100%";
833
+ iframe.style.border = "none";
834
+ wrapper.appendChild(iframe);
835
+ ctx.container.appendChild(wrapper);
836
+ let currentPage = 1;
837
+ let currentZoom = 1;
838
+ let rotation = 0;
839
+ const cleanup = () => {
840
+ URL.revokeObjectURL(url);
841
+ wrapper.remove();
842
+ ctx.container.innerHTML = "";
843
+ };
844
+ ctx.signal.addEventListener("abort", cleanup);
845
+ return {
846
+ destroy: cleanup,
847
+ zoomIn: () => {
848
+ currentZoom += 0.1;
849
+ iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
850
+ },
851
+ zoomOut: () => {
852
+ currentZoom = Math.max(0.2, currentZoom - 0.1);
853
+ iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
854
+ },
855
+ getZoom: () => currentZoom,
856
+ setZoom: (level) => {
857
+ currentZoom = level;
858
+ iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
859
+ },
860
+ fitToPage: () => {
861
+ currentZoom = 1;
862
+ iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
863
+ },
864
+ rotateCW: () => {
865
+ rotation = (rotation + 90) % 360;
866
+ iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
867
+ },
868
+ rotateCCW: () => {
869
+ rotation = (rotation - 90 + 360) % 360;
870
+ iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
871
+ },
872
+ getRotation: () => rotation,
873
+ goToPage: (page) => {
874
+ currentPage = page;
875
+ iframe.src = `${url}#page=${page}`;
876
+ },
877
+ getCurrentPage: () => currentPage,
878
+ download: () => {
879
+ const a = document.createElement("a");
880
+ a.href = url;
881
+ a.download = ctx.metadata.name || "document.pdf";
882
+ a.click();
883
+ },
884
+ print: () => {
885
+ iframe.contentWindow?.print();
886
+ },
887
+ getThumbnails: async () => {
888
+ return [
889
+ {
890
+ index: 1,
891
+ label: "Page 1",
892
+ render: async (canvas) => {
893
+ const context = canvas.getContext("2d");
894
+ if (context) {
895
+ context.fillStyle = "#fff";
896
+ context.fillRect(0, 0, canvas.width, canvas.height);
897
+ context.fillStyle = "#333";
898
+ context.font = "12px sans-serif";
899
+ context.fillText("PDF Preview", 10, 20);
900
+ }
901
+ }
902
+ }
903
+ ];
904
+ }
905
+ };
906
+ }
907
+ };
908
+ function pdfPlugin() {
909
+ return new PdfPlugin();
910
+ }
911
+
912
+ // ../plugins/media/dist/index.js
913
+ var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"];
914
+ var VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
915
+ var AUDIO_EXTS = [".mp3", ".wav", ".ogg"];
916
+ var MediaPlugin = class {
917
+ id = "media";
918
+ name = "Media Preview";
919
+ extensions = [...IMAGE_EXTS, ...VIDEO_EXTS, ...AUDIO_EXTS];
920
+ mimeTypes = [
921
+ "image/jpeg",
922
+ "image/png",
923
+ "image/gif",
924
+ "image/webp",
925
+ "image/svg+xml",
926
+ "video/mp4",
927
+ "video/webm",
928
+ "video/ogg",
929
+ "audio/mpeg",
930
+ "audio/wav",
931
+ "audio/ogg"
932
+ ];
933
+ weight = 90;
934
+ supports(file) {
935
+ const ext = file.metadata.extension?.toLowerCase() || "";
936
+ const mime = file.metadata.mimeType?.toLowerCase() || "";
937
+ return this.extensions.includes(ext) || this.mimeTypes.some((m) => mime.startsWith(m.split("/")[0]));
938
+ }
939
+ getToolbarActions(instance) {
940
+ const actions = [];
941
+ if (instance.zoomIn) {
942
+ actions.push(
943
+ {
944
+ id: "zoom-out",
945
+ icon: "zoom-out",
946
+ label: "Zoom Out",
947
+ type: "button",
948
+ group: "zoom",
949
+ execute: () => {
950
+ instance.zoomOut?.();
951
+ }
952
+ },
953
+ {
954
+ id: "zoom-in",
955
+ icon: "zoom-in",
956
+ label: "Zoom In",
957
+ type: "button",
958
+ group: "zoom",
959
+ execute: () => {
960
+ instance.zoomIn?.();
961
+ }
962
+ },
963
+ {
964
+ id: "fit-page",
965
+ icon: "fit-page",
966
+ label: "Fit to Page",
967
+ type: "button",
968
+ group: "zoom",
969
+ execute: () => {
970
+ instance.fitToPage?.();
971
+ }
972
+ },
973
+ {
974
+ id: "rotate-cw",
975
+ icon: "rotate-cw",
976
+ label: "Rotate",
977
+ type: "button",
978
+ group: "view",
979
+ execute: () => {
980
+ instance.rotateCW?.();
981
+ }
982
+ }
983
+ );
984
+ }
985
+ if (instance.play) {
986
+ actions.push(
987
+ {
988
+ id: "play",
989
+ icon: "play",
990
+ label: "Play",
991
+ type: "button",
992
+ group: "actions",
993
+ execute: () => {
994
+ instance.play?.();
995
+ }
996
+ },
997
+ {
998
+ id: "pause",
999
+ icon: "pause",
1000
+ label: "Pause",
1001
+ type: "button",
1002
+ group: "actions",
1003
+ execute: () => {
1004
+ instance.pause?.();
1005
+ }
1006
+ }
1007
+ );
1008
+ }
1009
+ actions.push({
1010
+ id: "download",
1011
+ icon: "download",
1012
+ label: "Download",
1013
+ type: "button",
1014
+ group: "actions",
1015
+ execute: () => {
1016
+ instance.download?.();
1017
+ }
1018
+ });
1019
+ if (instance.print) {
1020
+ actions.push({
1021
+ id: "print",
1022
+ icon: "print",
1023
+ label: "Print",
1024
+ type: "button",
1025
+ group: "actions",
1026
+ execute: () => {
1027
+ instance.print?.();
1028
+ }
1029
+ });
1030
+ }
1031
+ return actions;
1032
+ }
1033
+ async render(ctx) {
1034
+ const mimeType = ctx.metadata.mimeType || "application/octet-stream";
1035
+ const isImage = mimeType.startsWith("image/") || IMAGE_EXTS.includes(ctx.metadata.extension || "");
1036
+ const isVideo = mimeType.startsWith("video/") || VIDEO_EXTS.includes(ctx.metadata.extension || "");
1037
+ const isAudio = mimeType.startsWith("audio/") || AUDIO_EXTS.includes(ctx.metadata.extension || "");
1038
+ let url = "";
1039
+ if (ctx.metadata.extension === ".svg" || mimeType === "image/svg+xml") {
1040
+ const decoder = new TextDecoder("utf-8");
1041
+ const svgText = decoder.decode(ctx.buffer);
1042
+ const blob = new Blob([svgText], { type: "image/svg+xml" });
1043
+ url = URL.createObjectURL(blob);
1044
+ } else {
1045
+ const blob = new Blob([ctx.buffer], { type: mimeType });
1046
+ url = URL.createObjectURL(blob);
1047
+ }
1048
+ let element;
1049
+ let currentZoom = 1;
1050
+ let rotation = 0;
1051
+ let mediaElement = null;
1052
+ if (isImage) {
1053
+ const img = document.createElement("img");
1054
+ img.src = url;
1055
+ img.style.maxWidth = "100%";
1056
+ img.style.maxHeight = "100%";
1057
+ img.style.objectFit = "contain";
1058
+ img.style.transition = "transform 0.2s ease";
1059
+ element = img;
1060
+ } else if (isVideo) {
1061
+ const video = document.createElement("video");
1062
+ video.src = url;
1063
+ video.controls = true;
1064
+ video.style.maxWidth = "100%";
1065
+ video.style.maxHeight = "100%";
1066
+ element = video;
1067
+ mediaElement = video;
1068
+ } else if (isAudio) {
1069
+ const audio = document.createElement("audio");
1070
+ audio.src = url;
1071
+ audio.controls = true;
1072
+ element = audio;
1073
+ mediaElement = audio;
1074
+ } else {
1075
+ element = document.createElement("div");
1076
+ element.textContent = "Unsupported media type";
1077
+ }
1078
+ ctx.container.style.display = "flex";
1079
+ ctx.container.style.alignItems = "center";
1080
+ ctx.container.style.justifyContent = "center";
1081
+ ctx.container.style.overflow = "hidden";
1082
+ ctx.container.appendChild(element);
1083
+ const applyTransform = () => {
1084
+ element.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1085
+ };
1086
+ const cleanup = () => {
1087
+ if (url) URL.revokeObjectURL(url);
1088
+ element.remove();
1089
+ ctx.container.innerHTML = "";
1090
+ };
1091
+ ctx.signal.addEventListener("abort", cleanup);
1092
+ return {
1093
+ destroy: cleanup,
1094
+ zoomIn: isImage ? () => {
1095
+ currentZoom += 0.1;
1096
+ applyTransform();
1097
+ } : void 0,
1098
+ zoomOut: isImage ? () => {
1099
+ currentZoom = Math.max(0.1, currentZoom - 0.1);
1100
+ applyTransform();
1101
+ } : void 0,
1102
+ getZoom: isImage ? () => currentZoom : void 0,
1103
+ setZoom: isImage ? (level) => {
1104
+ currentZoom = level;
1105
+ applyTransform();
1106
+ } : void 0,
1107
+ fitToPage: isImage ? () => {
1108
+ currentZoom = 1;
1109
+ rotation = 0;
1110
+ applyTransform();
1111
+ } : void 0,
1112
+ rotateCW: isImage || isVideo ? () => {
1113
+ rotation = (rotation + 90) % 360;
1114
+ applyTransform();
1115
+ } : void 0,
1116
+ rotateCCW: isImage || isVideo ? () => {
1117
+ rotation = (rotation - 90 + 360) % 360;
1118
+ applyTransform();
1119
+ } : void 0,
1120
+ getRotation: () => rotation,
1121
+ play: mediaElement ? () => mediaElement?.play() : void 0,
1122
+ pause: mediaElement ? () => mediaElement?.pause() : void 0,
1123
+ isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1124
+ download: () => {
1125
+ const a = document.createElement("a");
1126
+ a.href = url;
1127
+ a.download = ctx.metadata.name || "media";
1128
+ a.click();
1129
+ },
1130
+ print: isImage ? () => {
1131
+ window.print();
1132
+ } : void 0
1133
+ };
1134
+ }
1135
+ };
1136
+ function mediaPlugin() {
1137
+ return new MediaPlugin();
1138
+ }
1139
+ var DocxPlugin = class {
1140
+ id = "docx";
1141
+ name = "Word Document Preview";
1142
+ extensions = [".docx"];
1143
+ mimeTypes = ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"];
1144
+ weight = 80;
1145
+ supports(file) {
1146
+ const ext = file.metadata.extension?.toLowerCase();
1147
+ const mime = file.metadata.mimeType?.toLowerCase();
1148
+ return ext === ".docx" || this.mimeTypes.includes(mime || "");
1149
+ }
1150
+ getToolbarActions(instance) {
1151
+ return [
1152
+ {
1153
+ id: "zoom-out",
1154
+ icon: "zoom-out",
1155
+ label: "Zoom Out",
1156
+ type: "button",
1157
+ group: "zoom",
1158
+ execute: () => {
1159
+ instance.zoomOut?.();
1160
+ }
1161
+ },
1162
+ {
1163
+ id: "zoom-in",
1164
+ icon: "zoom-in",
1165
+ label: "Zoom In",
1166
+ type: "button",
1167
+ group: "zoom",
1168
+ execute: () => {
1169
+ instance.zoomIn?.();
1170
+ }
1171
+ },
1172
+ {
1173
+ id: "fit-page",
1174
+ icon: "fit-page",
1175
+ label: "Fit to Page",
1176
+ type: "button",
1177
+ group: "zoom",
1178
+ execute: () => {
1179
+ instance.fitToPage?.();
1180
+ }
1181
+ },
1182
+ {
1183
+ id: "download",
1184
+ icon: "download",
1185
+ label: "Download",
1186
+ type: "button",
1187
+ group: "actions",
1188
+ execute: () => {
1189
+ instance.download?.();
1190
+ }
1191
+ },
1192
+ {
1193
+ id: "print",
1194
+ icon: "print",
1195
+ label: "Print",
1196
+ type: "button",
1197
+ group: "actions",
1198
+ execute: () => {
1199
+ instance.print?.();
1200
+ }
1201
+ }
1202
+ ];
1203
+ }
1204
+ async render(ctx) {
1205
+ const wrapper = document.createElement("div");
1206
+ wrapper.className = "fp-docx-wrapper";
1207
+ wrapper.style.transformOrigin = "top center";
1208
+ wrapper.style.transition = "transform 0.2s ease";
1209
+ wrapper.style.padding = "16px";
1210
+ ctx.container.style.overflow = "auto";
1211
+ ctx.container.appendChild(wrapper);
1212
+ let scale = 1;
1213
+ try {
1214
+ await docx__namespace.renderAsync(ctx.buffer, wrapper, ctx.container, {
1215
+ inWrapper: true,
1216
+ ignoreWidth: false,
1217
+ ignoreHeight: false
1218
+ });
1219
+ } catch {
1220
+ wrapper.innerHTML = `
1221
+ <div style="text-align:center; padding: 40px; color: #666;">
1222
+ <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1223
+ <h3>${ctx.metadata.name || "Word Document"}</h3>
1224
+ <p>DOCX render preview</p>
1225
+ </div>
1226
+ `;
1227
+ }
1228
+ const cleanup = () => {
1229
+ wrapper.remove();
1230
+ ctx.container.innerHTML = "";
1231
+ };
1232
+ ctx.signal.addEventListener("abort", cleanup);
1233
+ return {
1234
+ destroy: cleanup,
1235
+ zoomIn: () => {
1236
+ scale += 0.1;
1237
+ wrapper.style.transform = `scale(${scale})`;
1238
+ },
1239
+ zoomOut: () => {
1240
+ scale = Math.max(0.2, scale - 0.1);
1241
+ wrapper.style.transform = `scale(${scale})`;
1242
+ },
1243
+ getZoom: () => scale,
1244
+ setZoom: (level) => {
1245
+ scale = level;
1246
+ wrapper.style.transform = `scale(${scale})`;
1247
+ },
1248
+ fitToPage: () => {
1249
+ scale = 1;
1250
+ wrapper.style.transform = `scale(1)`;
1251
+ },
1252
+ download: () => {
1253
+ const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
1254
+ const url = URL.createObjectURL(blob);
1255
+ const a = document.createElement("a");
1256
+ a.href = url;
1257
+ a.download = ctx.metadata.name || "document.docx";
1258
+ a.click();
1259
+ URL.revokeObjectURL(url);
1260
+ },
1261
+ print: () => {
1262
+ window.print();
1263
+ }
1264
+ };
1265
+ }
1266
+ };
1267
+ function docxPlugin() {
1268
+ return new DocxPlugin();
1269
+ }
1270
+ var ExcelPlugin = class {
1271
+ id = "excel";
1272
+ name = "Excel Spreadsheet Preview";
1273
+ extensions = [".xlsx", ".xls"];
1274
+ mimeTypes = [
1275
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1276
+ "application/vnd.ms-excel"
1277
+ ];
1278
+ weight = 80;
1279
+ supports(file) {
1280
+ const ext = file.metadata.extension?.toLowerCase();
1281
+ const mime = file.metadata.mimeType?.toLowerCase();
1282
+ return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
1283
+ }
1284
+ getToolbarActions(instance) {
1285
+ return [
1286
+ {
1287
+ id: "zoom-out",
1288
+ icon: "zoom-out",
1289
+ label: "Zoom Out",
1290
+ type: "button",
1291
+ group: "zoom",
1292
+ execute: () => {
1293
+ instance.zoomOut?.();
1294
+ }
1295
+ },
1296
+ {
1297
+ id: "zoom-in",
1298
+ icon: "zoom-in",
1299
+ label: "Zoom In",
1300
+ type: "button",
1301
+ group: "zoom",
1302
+ execute: () => {
1303
+ instance.zoomIn?.();
1304
+ }
1305
+ },
1306
+ {
1307
+ id: "page-nav",
1308
+ icon: "page-nav",
1309
+ label: "Sheet Navigation",
1310
+ type: "page-nav",
1311
+ group: "navigation",
1312
+ execute: (sheet) => {
1313
+ if (typeof sheet === "number") instance.goToPage?.(sheet);
1314
+ }
1315
+ },
1316
+ {
1317
+ id: "download",
1318
+ icon: "download",
1319
+ label: "Download",
1320
+ type: "button",
1321
+ group: "actions",
1322
+ execute: () => {
1323
+ instance.download?.();
1324
+ }
1325
+ },
1326
+ {
1327
+ id: "print",
1328
+ icon: "print",
1329
+ label: "Print",
1330
+ type: "button",
1331
+ group: "actions",
1332
+ execute: () => {
1333
+ instance.print?.();
1334
+ }
1335
+ }
1336
+ ];
1337
+ }
1338
+ async render(ctx) {
1339
+ const container = document.createElement("div");
1340
+ container.style.display = "flex";
1341
+ container.style.flexDirection = "column";
1342
+ container.style.width = "100%";
1343
+ container.style.height = "100%";
1344
+ container.style.overflow = "hidden";
1345
+ const contentArea = document.createElement("div");
1346
+ contentArea.style.flex = "1";
1347
+ contentArea.style.overflow = "auto";
1348
+ contentArea.style.padding = "16px";
1349
+ contentArea.style.transformOrigin = "top left";
1350
+ const tabsArea = document.createElement("div");
1351
+ tabsArea.style.display = "flex";
1352
+ tabsArea.style.gap = "8px";
1353
+ tabsArea.style.padding = "8px 16px";
1354
+ tabsArea.style.borderTop = "1px solid #e0e0e0";
1355
+ tabsArea.style.backgroundColor = "#fafafa";
1356
+ tabsArea.style.overflowX = "auto";
1357
+ container.appendChild(contentArea);
1358
+ container.appendChild(tabsArea);
1359
+ ctx.container.appendChild(container);
1360
+ let scale = 1;
1361
+ let currentSheetIndex = 1;
1362
+ const workbook = new ExcelJS__default.default.Workbook();
1363
+ try {
1364
+ await workbook.xlsx.load(ctx.buffer);
1365
+ } catch {
1366
+ }
1367
+ const renderSheet = (index) => {
1368
+ contentArea.innerHTML = "";
1369
+ const sheet = workbook.getWorksheet(index);
1370
+ if (!sheet) return;
1371
+ const table = document.createElement("table");
1372
+ table.style.borderCollapse = "collapse";
1373
+ table.style.fontFamily = "sans-serif";
1374
+ table.style.fontSize = "13px";
1375
+ table.style.minWidth = "100%";
1376
+ sheet.eachRow({ includeEmpty: false }, (row) => {
1377
+ const tr = document.createElement("tr");
1378
+ row.eachCell({ includeEmpty: true }, (cell) => {
1379
+ const td = document.createElement("td");
1380
+ td.style.border = "1px solid #d0d7de";
1381
+ td.style.padding = "6px 12px";
1382
+ td.textContent = cell.text || "";
1383
+ tr.appendChild(td);
1384
+ });
1385
+ table.appendChild(tr);
1386
+ });
1387
+ contentArea.appendChild(table);
1388
+ currentSheetIndex = index;
1389
+ };
1390
+ workbook.worksheets.forEach((sheet, idx) => {
1391
+ const btn = document.createElement("button");
1392
+ btn.textContent = sheet.name;
1393
+ btn.style.padding = "4px 12px";
1394
+ btn.style.fontSize = "12px";
1395
+ btn.style.cursor = "pointer";
1396
+ btn.style.border = "1px solid #ccc";
1397
+ btn.style.borderRadius = "4px";
1398
+ btn.style.backgroundColor = "#fff";
1399
+ btn.onclick = () => renderSheet(idx + 1);
1400
+ tabsArea.appendChild(btn);
1401
+ });
1402
+ if (workbook.worksheets.length > 0) {
1403
+ renderSheet(1);
1404
+ }
1405
+ const cleanup = () => {
1406
+ container.remove();
1407
+ ctx.container.innerHTML = "";
1408
+ };
1409
+ ctx.signal.addEventListener("abort", cleanup);
1410
+ return {
1411
+ destroy: cleanup,
1412
+ zoomIn: () => {
1413
+ scale += 0.1;
1414
+ contentArea.style.transform = `scale(${scale})`;
1415
+ },
1416
+ zoomOut: () => {
1417
+ scale = Math.max(0.2, scale - 0.1);
1418
+ contentArea.style.transform = `scale(${scale})`;
1419
+ },
1420
+ getZoom: () => scale,
1421
+ setZoom: (level) => {
1422
+ scale = level;
1423
+ contentArea.style.transform = `scale(${scale})`;
1424
+ },
1425
+ goToPage: (page) => {
1426
+ if (page > 0 && page <= workbook.worksheets.length) {
1427
+ renderSheet(page);
1428
+ }
1429
+ },
1430
+ getPageCount: () => workbook.worksheets.length,
1431
+ getCurrentPage: () => currentSheetIndex,
1432
+ download: () => {
1433
+ const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
1434
+ const url = URL.createObjectURL(blob);
1435
+ const a = document.createElement("a");
1436
+ a.href = url;
1437
+ a.download = ctx.metadata.name || "spreadsheet.xlsx";
1438
+ a.click();
1439
+ URL.revokeObjectURL(url);
1440
+ },
1441
+ print: () => {
1442
+ window.print();
1443
+ }
1444
+ };
1445
+ }
1446
+ };
1447
+ function excelPlugin() {
1448
+ return new ExcelPlugin();
1449
+ }
1450
+
1451
+ // ../plugins/csv/dist/index.js
1452
+ var CsvPlugin = class {
1453
+ id = "csv";
1454
+ name = "CSV Preview";
1455
+ extensions = [".csv", ".tsv"];
1456
+ mimeTypes = ["text/csv", "text/tab-separated-values"];
1457
+ weight = 80;
1458
+ supports(file) {
1459
+ const ext = file.metadata.extension?.toLowerCase();
1460
+ const mime = file.metadata.mimeType?.toLowerCase();
1461
+ return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
1462
+ }
1463
+ getToolbarActions(instance) {
1464
+ return [
1465
+ {
1466
+ id: "zoom-out",
1467
+ icon: "zoom-out",
1468
+ label: "Zoom Out",
1469
+ type: "button",
1470
+ group: "zoom",
1471
+ execute: () => {
1472
+ instance.zoomOut?.();
1473
+ }
1474
+ },
1475
+ {
1476
+ id: "zoom-in",
1477
+ icon: "zoom-in",
1478
+ label: "Zoom In",
1479
+ type: "button",
1480
+ group: "zoom",
1481
+ execute: () => {
1482
+ instance.zoomIn?.();
1483
+ }
1484
+ },
1485
+ {
1486
+ id: "download",
1487
+ icon: "download",
1488
+ label: "Download",
1489
+ type: "button",
1490
+ group: "actions",
1491
+ execute: () => {
1492
+ instance.download?.();
1493
+ }
1494
+ },
1495
+ {
1496
+ id: "print",
1497
+ icon: "print",
1498
+ label: "Print",
1499
+ type: "button",
1500
+ group: "actions",
1501
+ execute: () => {
1502
+ instance.print?.();
1503
+ }
1504
+ }
1505
+ ];
1506
+ }
1507
+ async render(ctx) {
1508
+ const decoder = new TextDecoder("utf-8");
1509
+ const text = decoder.decode(ctx.buffer);
1510
+ const container = document.createElement("div");
1511
+ container.style.width = "100%";
1512
+ container.style.height = "100%";
1513
+ container.style.overflow = "auto";
1514
+ container.style.padding = "16px";
1515
+ container.style.boxSizing = "border-box";
1516
+ container.style.transformOrigin = "top left";
1517
+ const table = document.createElement("table");
1518
+ table.style.borderCollapse = "collapse";
1519
+ table.style.width = "100%";
1520
+ table.style.fontFamily = "monospace";
1521
+ table.style.fontSize = "13px";
1522
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
1523
+ const rows = text.split(/\r?\n/).slice(0, 500);
1524
+ rows.forEach((row, rowIndex) => {
1525
+ if (!row.trim()) return;
1526
+ const tr = document.createElement("tr");
1527
+ const cells = row.split(delimiter);
1528
+ cells.forEach((cell) => {
1529
+ const td = document.createElement(rowIndex === 0 ? "th" : "td");
1530
+ td.textContent = cell.trim();
1531
+ td.style.border = "1px solid #d0d7de";
1532
+ td.style.padding = "6px 12px";
1533
+ if (rowIndex === 0) {
1534
+ td.style.backgroundColor = "#f6f8fa";
1535
+ td.style.fontWeight = "bold";
1536
+ }
1537
+ tr.appendChild(td);
1538
+ });
1539
+ table.appendChild(tr);
1540
+ });
1541
+ container.appendChild(table);
1542
+ ctx.container.appendChild(container);
1543
+ let scale = 1;
1544
+ const cleanup = () => {
1545
+ container.remove();
1546
+ ctx.container.innerHTML = "";
1547
+ };
1548
+ ctx.signal.addEventListener("abort", cleanup);
1549
+ return {
1550
+ destroy: cleanup,
1551
+ zoomIn: () => {
1552
+ scale += 0.1;
1553
+ container.style.transform = `scale(${scale})`;
1554
+ },
1555
+ zoomOut: () => {
1556
+ scale = Math.max(0.2, scale - 0.1);
1557
+ container.style.transform = `scale(${scale})`;
1558
+ },
1559
+ getZoom: () => scale,
1560
+ setZoom: (level) => {
1561
+ scale = level;
1562
+ container.style.transform = `scale(${scale})`;
1563
+ },
1564
+ fitToPage: () => {
1565
+ scale = 1;
1566
+ container.style.transform = `scale(1)`;
1567
+ },
1568
+ download: () => {
1569
+ const blob = new Blob([ctx.buffer], { type: "text/csv" });
1570
+ const url = URL.createObjectURL(blob);
1571
+ const a = document.createElement("a");
1572
+ a.href = url;
1573
+ a.download = ctx.metadata.name || "data.csv";
1574
+ a.click();
1575
+ URL.revokeObjectURL(url);
1576
+ },
1577
+ print: () => {
1578
+ window.print();
1579
+ }
1580
+ };
1581
+ }
1582
+ };
1583
+ function csvPlugin() {
1584
+ return new CsvPlugin();
1585
+ }
1586
+ var CODE_EXTENSIONS = [
1587
+ ".txt",
1588
+ ".json",
1589
+ ".js",
1590
+ ".ts",
1591
+ ".jsx",
1592
+ ".tsx",
1593
+ ".html",
1594
+ ".css",
1595
+ ".scss",
1596
+ ".less",
1597
+ ".md",
1598
+ ".xml",
1599
+ ".yml",
1600
+ ".yaml",
1601
+ ".sh",
1602
+ ".bash",
1603
+ ".py",
1604
+ ".java",
1605
+ ".c",
1606
+ ".cpp",
1607
+ ".h",
1608
+ ".cs",
1609
+ ".go",
1610
+ ".rs",
1611
+ ".sql",
1612
+ ".php"
1613
+ ];
1614
+ var CodePlugin = class {
1615
+ id = "code";
1616
+ name = "Code/Text Preview";
1617
+ extensions = CODE_EXTENSIONS;
1618
+ mimeTypes = ["text/plain", "application/json", "text/javascript", "text/html", "text/css"];
1619
+ weight = 50;
1620
+ supports(file) {
1621
+ const ext = file.metadata.extension?.toLowerCase();
1622
+ const mime = file.metadata.mimeType?.toLowerCase();
1623
+ if (ext && this.extensions.includes(ext)) return true;
1624
+ if (mime && (mime.startsWith("text/") || this.mimeTypes.includes(mime))) return true;
1625
+ return false;
1626
+ }
1627
+ getToolbarActions(instance) {
1628
+ return [
1629
+ {
1630
+ id: "zoom-out",
1631
+ icon: "zoom-out",
1632
+ label: "Zoom Out",
1633
+ type: "button",
1634
+ group: "zoom",
1635
+ execute: () => {
1636
+ instance.zoomOut?.();
1637
+ }
1638
+ },
1639
+ {
1640
+ id: "zoom-in",
1641
+ icon: "zoom-in",
1642
+ label: "Zoom In",
1643
+ type: "button",
1644
+ group: "zoom",
1645
+ execute: () => {
1646
+ instance.zoomIn?.();
1647
+ }
1648
+ },
1649
+ {
1650
+ id: "download",
1651
+ icon: "download",
1652
+ label: "Download",
1653
+ type: "button",
1654
+ group: "actions",
1655
+ execute: () => {
1656
+ instance.download?.();
1657
+ }
1658
+ },
1659
+ {
1660
+ id: "print",
1661
+ icon: "print",
1662
+ label: "Print",
1663
+ type: "button",
1664
+ group: "actions",
1665
+ execute: () => {
1666
+ instance.print?.();
1667
+ }
1668
+ }
1669
+ ];
1670
+ }
1671
+ async render(ctx) {
1672
+ const decoder = new TextDecoder("utf-8");
1673
+ const text = decoder.decode(ctx.buffer);
1674
+ const container = document.createElement("div");
1675
+ container.style.width = "100%";
1676
+ container.style.height = "100%";
1677
+ container.style.overflow = "auto";
1678
+ container.style.backgroundColor = "#1e1e1e";
1679
+ container.style.color = "#d4d4d4";
1680
+ container.style.padding = "16px";
1681
+ container.style.boxSizing = "border-box";
1682
+ let fontSize = 13;
1683
+ const pre = document.createElement("pre");
1684
+ pre.style.margin = "0";
1685
+ pre.style.fontFamily = "Consolas, Menlo, Monaco, monospace";
1686
+ pre.style.fontSize = `${fontSize}px`;
1687
+ pre.style.lineHeight = "1.5";
1688
+ pre.style.whiteSpace = "pre-wrap";
1689
+ pre.style.wordBreak = "break-all";
1690
+ const code = document.createElement("code");
1691
+ const ext = (ctx.metadata.extension || "").replace(".", "");
1692
+ try {
1693
+ if (ext && hljs__default.default.getLanguage(ext)) {
1694
+ code.innerHTML = hljs__default.default.highlight(text, { language: ext }).value;
1695
+ } else {
1696
+ code.innerHTML = hljs__default.default.highlightAuto(text).value;
1697
+ }
1698
+ } catch {
1699
+ code.textContent = text;
1700
+ }
1701
+ pre.appendChild(code);
1702
+ container.appendChild(pre);
1703
+ ctx.container.appendChild(container);
1704
+ const cleanup = () => {
1705
+ container.remove();
1706
+ ctx.container.innerHTML = "";
1707
+ };
1708
+ ctx.signal.addEventListener("abort", cleanup);
1709
+ return {
1710
+ destroy: cleanup,
1711
+ zoomIn: () => {
1712
+ fontSize = Math.min(32, fontSize + 2);
1713
+ pre.style.fontSize = `${fontSize}px`;
1714
+ },
1715
+ zoomOut: () => {
1716
+ fontSize = Math.max(8, fontSize - 2);
1717
+ pre.style.fontSize = `${fontSize}px`;
1718
+ },
1719
+ getZoom: () => fontSize / 13,
1720
+ setZoom: (level) => {
1721
+ fontSize = Math.round(13 * level);
1722
+ pre.style.fontSize = `${fontSize}px`;
1723
+ },
1724
+ download: () => {
1725
+ const mimeType = ctx.metadata.mimeType || "text/plain";
1726
+ const blob = new Blob([ctx.buffer], { type: mimeType });
1727
+ const url = URL.createObjectURL(blob);
1728
+ const a = document.createElement("a");
1729
+ a.href = url;
1730
+ a.download = ctx.metadata.name || "code.txt";
1731
+ a.click();
1732
+ URL.revokeObjectURL(url);
1733
+ },
1734
+ print: () => {
1735
+ window.print();
1736
+ }
1737
+ };
1738
+ }
1739
+ };
1740
+ function codePlugin() {
1741
+ return new CodePlugin();
1742
+ }
1743
+
1744
+ // src/index.ts
1745
+ function getDefaultPlugins() {
1746
+ return [
1747
+ pdfPlugin(),
1748
+ mediaPlugin(),
1749
+ docxPlugin(),
1750
+ excelPlugin(),
1751
+ csvPlugin(),
1752
+ codePlugin()
1753
+ ];
1754
+ }
1755
+ var FilePreviewViewer2 = class extends FilePreviewViewer {
1756
+ constructor(options) {
1757
+ super();
1758
+ if (options?.autoRegisterDefaults !== false) {
1759
+ this.registerPlugins(getDefaultPlugins());
1760
+ }
1761
+ }
1762
+ };
1763
+
1764
+ // src/vue.ts
1765
+ var FilePreview = vue.defineComponent({
1766
+ name: "FilePreview",
1767
+ props: {
1768
+ src: {
1769
+ type: [String, Object],
1770
+ required: true
1771
+ },
1772
+ plugins: {
1773
+ type: Array,
1774
+ default: void 0
1775
+ },
1776
+ options: {
1777
+ type: Object,
1778
+ default: () => ({})
1779
+ }
1780
+ },
1781
+ emits: ["loading", "loaded", "error", "page-change", "zoom-change"],
1782
+ setup(props, { emit, expose }) {
1783
+ const containerRef = vue.ref(null);
1784
+ let viewer = null;
1785
+ let instance = null;
1786
+ const renderPreview = async () => {
1787
+ const el = containerRef.value;
1788
+ if (!viewer || !el || !props.src) return;
1789
+ try {
1790
+ instance = await viewer.preview(el, props.src, props.options);
1791
+ } catch (error) {
1792
+ if (error instanceof DOMException && error.name === "AbortError") return;
1793
+ emit("error", error);
1794
+ }
1795
+ };
1796
+ vue.onMounted(() => {
1797
+ viewer = new FilePreviewViewer2({ autoRegisterDefaults: false });
1798
+ const activePlugins = props.plugins && props.plugins.length > 0 ? props.plugins : getDefaultPlugins();
1799
+ viewer.registerPlugins(activePlugins);
1800
+ viewer.on("loading", (data) => emit("loading", data));
1801
+ viewer.on("loaded", (data) => emit("loaded", data));
1802
+ viewer.on("error", (data) => emit("error", data));
1803
+ viewer.on("page-change", (data) => emit("page-change", data));
1804
+ viewer.on("zoom-change", (data) => emit("zoom-change", data));
1805
+ renderPreview();
1806
+ });
1807
+ vue.watch(() => props.src, renderPreview);
1808
+ vue.watch(() => props.options, renderPreview, { deep: true });
1809
+ vue.onBeforeUnmount(() => {
1810
+ viewer?.destroy();
1811
+ viewer = null;
1812
+ instance = null;
1813
+ });
1814
+ expose({
1815
+ getInstance: () => instance,
1816
+ getViewer: () => viewer,
1817
+ destroy: () => viewer?.destroy()
1818
+ });
1819
+ return () => {
1820
+ return vue.h("div", {
1821
+ ref: containerRef,
1822
+ style: { width: "100%", height: "100%", position: "relative" }
1823
+ });
1824
+ };
1825
+ }
1826
+ });
1827
+ var vue_default = FilePreview;
1828
+
1829
+ exports.FilePreview = FilePreview;
1830
+ exports.default = vue_default;
1831
+ //# sourceMappingURL=vue.cjs.map
1832
+ //# sourceMappingURL=vue.cjs.map