@files-preview-app/preview-file 1.1.3 → 1.1.5

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/react.js CHANGED
@@ -342,8 +342,39 @@ function createElement(tag, attrs, ...children) {
342
342
  }
343
343
  return el;
344
344
  }
345
+ var ICON_ZOOM_IN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>`;
346
+ var ICON_ZOOM_OUT = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>`;
347
+ var ICON_FIT_PAGE = `<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 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line></svg>`;
348
+ var ICON_ROTATE_CW = `<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="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path></svg>`;
349
+ var ICON_ROTATE_CCW = `<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="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>`;
350
+ var ICON_DOWNLOAD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>`;
351
+ var ICON_PRINT = `<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="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg>`;
345
352
  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
353
  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>`;
354
+ var ICON_PLAY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>`;
355
+ var ICON_PAUSE = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>`;
356
+ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>`;
357
+ var ICON_CLOSE = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
358
+ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>`;
359
+ var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
360
+ var ICON_MAP = {
361
+ "zoom-in": ICON_ZOOM_IN,
362
+ "zoom-out": ICON_ZOOM_OUT,
363
+ "fit-page": ICON_FIT_PAGE,
364
+ "fit-slide": ICON_FIT_PAGE,
365
+ "rotate-cw": ICON_ROTATE_CW,
366
+ "rotate-ccw": ICON_ROTATE_CCW,
367
+ "download": ICON_DOWNLOAD,
368
+ "print": ICON_PRINT,
369
+ "thumbnails": ICON_THUMBNAILS,
370
+ "fullscreen": ICON_FULLSCREEN,
371
+ "play": ICON_PLAY,
372
+ "pause": ICON_PAUSE,
373
+ "close": ICON_CLOSE,
374
+ "copy": ICON_COPY,
375
+ "prev": ICON_PAGE_PREV,
376
+ "next": ICON_PAGE_NEXT
377
+ };
347
378
  var ToolbarController = class {
348
379
  el;
349
380
  toolbarEl;
@@ -454,10 +485,11 @@ var ToolbarController = class {
454
485
  type: "button",
455
486
  "data-action-id": id
456
487
  });
457
- if (iconHtml.startsWith("<svg")) {
458
- btn.innerHTML = sanitizeSVG(iconHtml);
488
+ const svg = ICON_MAP[iconHtml] || iconHtml;
489
+ if (svg && svg.startsWith("<svg")) {
490
+ btn.innerHTML = sanitizeSVG(svg);
459
491
  } else {
460
- btn.textContent = iconHtml;
492
+ btn.textContent = svg || title;
461
493
  }
462
494
  btn.addEventListener("click", onClick);
463
495
  return btn;
@@ -538,6 +570,9 @@ var FilePreviewViewer = class {
538
570
  contentEl = null;
539
571
  currentBuffer = null;
540
572
  currentMetadata = null;
573
+ keyHandler = null;
574
+ currentContainer = null;
575
+ currentOptions = {};
541
576
  /**
542
577
  * Register a preview plugin.
543
578
  */
@@ -592,24 +627,47 @@ var FilePreviewViewer = class {
592
627
  emit: (event, payload) => this.eventEmitter.emit(event, payload)
593
628
  });
594
629
  this.activeInstance = instance;
630
+ this.hideLoading();
631
+ this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
595
632
  if (options.showToolbar !== false && this.toolbar) {
596
633
  const actions = matchedPlugin.getToolbarActions(instance);
634
+ const hasFullscreen = actions.some((a) => a.id === "fullscreen");
635
+ if (!hasFullscreen && this.wrapperEl) {
636
+ actions.push({
637
+ id: "fullscreen",
638
+ icon: "fullscreen",
639
+ label: "Toggle Fullscreen",
640
+ type: "button",
641
+ group: "view",
642
+ execute: () => {
643
+ if (!document.fullscreenElement) {
644
+ this.wrapperEl?.requestFullscreen?.();
645
+ } else {
646
+ document.exitFullscreen?.();
647
+ }
648
+ }
649
+ });
650
+ }
597
651
  this.toolbar.update(actions);
598
652
  this.toolbar.show();
599
653
  }
600
654
  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();
655
+ Promise.resolve().then(async () => {
656
+ try {
657
+ const thumbnails = await Promise.resolve(instance.getThumbnails());
658
+ if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
659
+ this.thumbnailPanel.update(thumbnails, (index) => {
660
+ instance.goToPage?.(index + 1);
661
+ });
662
+ if (options.showThumbnails) {
663
+ this.thumbnailPanel.show();
664
+ }
665
+ }
666
+ } catch (e) {
667
+ console.warn("[FilePreview] Non-critical thumbnail load error:", e);
608
668
  }
609
- }
669
+ });
610
670
  }
611
- this.hideLoading();
612
- this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
613
671
  return instance;
614
672
  } catch (error) {
615
673
  this.hideLoading();
@@ -631,6 +689,10 @@ var FilePreviewViewer = class {
631
689
  * Destroy the viewer and clean up all resources.
632
690
  */
633
691
  destroy() {
692
+ if (this.keyHandler) {
693
+ window.removeEventListener("keydown", this.keyHandler);
694
+ this.keyHandler = null;
695
+ }
634
696
  this.abort();
635
697
  this.destroyInstance();
636
698
  this.toolbar?.destroy();
@@ -646,6 +708,7 @@ var FilePreviewViewer = class {
646
708
  this.thumbnailPanel = null;
647
709
  this.currentBuffer = null;
648
710
  this.currentMetadata = null;
711
+ this.currentContainer = null;
649
712
  }
650
713
  /**
651
714
  * Get the currently active preview instance.
@@ -675,6 +738,8 @@ var FilePreviewViewer = class {
675
738
  return null;
676
739
  }
677
740
  setupDOM(container, options) {
741
+ this.currentContainer = container;
742
+ this.currentOptions = options;
678
743
  if (this.wrapperEl?.parentNode === container) return;
679
744
  if (this.wrapperEl?.parentNode) {
680
745
  this.wrapperEl.parentNode.removeChild(this.wrapperEl);
@@ -683,6 +748,7 @@ var FilePreviewViewer = class {
683
748
  const toolbarPos = options.toolbarPosition ?? "top";
684
749
  this.wrapperEl = document.createElement("div");
685
750
  this.wrapperEl.className = `fp-viewer ${themeClass} ${options.className ?? ""}`.trim();
751
+ this.wrapperEl.tabIndex = 0;
686
752
  const toolbarEl = document.createElement("div");
687
753
  toolbarEl.className = "fp-toolbar-container";
688
754
  this.contentEl = document.createElement("div");
@@ -704,6 +770,59 @@ var FilePreviewViewer = class {
704
770
  container.appendChild(this.wrapperEl);
705
771
  this.toolbar = new ToolbarController(toolbarEl);
706
772
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
773
+ this.setupKeyboardShortcuts();
774
+ this.setupDragAndDrop(container, options);
775
+ }
776
+ setupKeyboardShortcuts() {
777
+ if (this.keyHandler) return;
778
+ this.keyHandler = (e) => {
779
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target?.isContentEditable) {
780
+ return;
781
+ }
782
+ if (!this.activeInstance) return;
783
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
784
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
785
+ this.activeInstance.goToPage?.(cur + 1);
786
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
787
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
788
+ this.activeInstance.goToPage?.(Math.max(1, cur - 1));
789
+ } else if (e.key === "+" || e.key === "=") {
790
+ this.activeInstance.zoomIn?.();
791
+ } else if (e.key === "-" || e.key === "_") {
792
+ this.activeInstance.zoomOut?.();
793
+ } else if (e.key === "0") {
794
+ this.activeInstance.fitToPage?.();
795
+ } else if (e.key === "r" || e.key === "R") {
796
+ this.activeInstance.rotateCW?.();
797
+ } else if (e.key === "f" || e.key === "F") {
798
+ if (!document.fullscreenElement) {
799
+ this.wrapperEl?.requestFullscreen?.();
800
+ } else {
801
+ document.exitFullscreen?.();
802
+ }
803
+ }
804
+ };
805
+ window.addEventListener("keydown", this.keyHandler);
806
+ }
807
+ setupDragAndDrop(container, options) {
808
+ if (!this.wrapperEl) return;
809
+ this.wrapperEl.addEventListener("dragover", (e) => {
810
+ e.preventDefault();
811
+ this.wrapperEl?.classList.add("fp-dragover");
812
+ });
813
+ this.wrapperEl.addEventListener("dragleave", (e) => {
814
+ if (e.relatedTarget === null || !this.wrapperEl?.contains(e.relatedTarget)) {
815
+ this.wrapperEl?.classList.remove("fp-dragover");
816
+ }
817
+ });
818
+ this.wrapperEl.addEventListener("drop", (e) => {
819
+ e.preventDefault();
820
+ this.wrapperEl?.classList.remove("fp-dragover");
821
+ const file = e.dataTransfer?.files?.[0];
822
+ if (file) {
823
+ this.preview(container, file, options);
824
+ }
825
+ });
707
826
  }
708
827
  showLoading() {
709
828
  if (!this.contentEl) return;
@@ -722,10 +841,27 @@ var FilePreviewViewer = class {
722
841
  this.contentEl.innerHTML = "";
723
842
  const errorEl = document.createElement("div");
724
843
  errorEl.className = "fp-error";
844
+ errorEl.style.display = "flex";
845
+ errorEl.style.flexDirection = "column";
846
+ errorEl.style.alignItems = "center";
847
+ errorEl.style.justifyContent = "center";
848
+ errorEl.style.padding = "32px";
849
+ errorEl.style.textAlign = "center";
850
+ const hasDownload = !!(this.currentBuffer && this.currentMetadata);
725
851
  errorEl.innerHTML = `
726
- <div class="fp-error-icon">\u26A0\uFE0F</div>
727
- <div class="fp-error-message">${message}</div>
852
+ <div class="fp-error-icon" style="font-size:36px;margin-bottom:8px;">\u26A0\uFE0F</div>
853
+ <div class="fp-error-message" style="font-size:16px;font-weight:600;margin-bottom:6px;">Cannot preview file</div>
854
+ <div class="fp-error-sub" style="font-size:13px;color:#64748b;max-width:420px;line-height:1.5;margin-bottom:16px;">${message}</div>
855
+ ${hasDownload ? '<button class="fp-error-download-btn" style="padding:8px 16px;background:#3b82f6;color:#ffffff;border:none;border-radius:6px;cursor:pointer;font-weight:500;font-size:13px;transition:background 0.2s;">Download Original File</button>' : ""}
728
856
  `;
857
+ if (hasDownload && this.currentBuffer) {
858
+ const btn = errorEl.querySelector(".fp-error-download-btn");
859
+ if (btn) {
860
+ btn.addEventListener("click", () => {
861
+ downloadFile(this.currentBuffer, this.currentMetadata?.name || "download", this.currentMetadata?.mimeType);
862
+ });
863
+ }
864
+ }
729
865
  this.contentEl.appendChild(errorEl);
730
866
  }
731
867
  };
@@ -1646,6 +1782,16 @@ var CodePlugin = class {
1646
1782
  instance.zoomIn?.();
1647
1783
  }
1648
1784
  },
1785
+ {
1786
+ id: "copy",
1787
+ icon: "copy",
1788
+ label: "Copy Content",
1789
+ type: "button",
1790
+ group: "actions",
1791
+ execute: () => {
1792
+ instance.copy?.();
1793
+ }
1794
+ },
1649
1795
  {
1650
1796
  id: "download",
1651
1797
  icon: "download",
@@ -1733,6 +1879,9 @@ var CodePlugin = class {
1733
1879
  },
1734
1880
  print: () => {
1735
1881
  window.print();
1882
+ },
1883
+ copy: () => {
1884
+ navigator.clipboard?.writeText(text);
1736
1885
  }
1737
1886
  };
1738
1887
  }
@@ -1983,6 +2132,14 @@ var MarkdownPlugin = class {
1983
2132
  group: "zoom",
1984
2133
  execute: () => instance.fitToPage?.()
1985
2134
  },
2135
+ {
2136
+ id: "copy",
2137
+ icon: "copy",
2138
+ label: "Copy Markdown",
2139
+ type: "button",
2140
+ group: "actions",
2141
+ execute: () => instance.copy?.()
2142
+ },
1986
2143
  {
1987
2144
  id: "download",
1988
2145
  icon: "download",
@@ -2133,6 +2290,9 @@ var MarkdownPlugin = class {
2133
2290
  },
2134
2291
  print: () => {
2135
2292
  window.print();
2293
+ },
2294
+ copy: () => {
2295
+ navigator.clipboard?.writeText(text);
2136
2296
  }
2137
2297
  };
2138
2298
  }