@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/index.cjs CHANGED
@@ -412,8 +412,39 @@ function createElement(tag, attrs, ...children) {
412
412
  }
413
413
  return el;
414
414
  }
415
+ 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>`;
416
+ 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>`;
417
+ 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>`;
418
+ 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>`;
419
+ 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>`;
420
+ 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>`;
421
+ 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>`;
415
422
  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>`;
416
423
  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>`;
424
+ 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>`;
425
+ 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>`;
426
+ 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>`;
427
+ 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>`;
428
+ 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>`;
429
+ 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>`;
430
+ var ICON_MAP = {
431
+ "zoom-in": ICON_ZOOM_IN,
432
+ "zoom-out": ICON_ZOOM_OUT,
433
+ "fit-page": ICON_FIT_PAGE,
434
+ "fit-slide": ICON_FIT_PAGE,
435
+ "rotate-cw": ICON_ROTATE_CW,
436
+ "rotate-ccw": ICON_ROTATE_CCW,
437
+ "download": ICON_DOWNLOAD,
438
+ "print": ICON_PRINT,
439
+ "thumbnails": ICON_THUMBNAILS,
440
+ "fullscreen": ICON_FULLSCREEN,
441
+ "play": ICON_PLAY,
442
+ "pause": ICON_PAUSE,
443
+ "close": ICON_CLOSE,
444
+ "copy": ICON_COPY,
445
+ "prev": ICON_PAGE_PREV,
446
+ "next": ICON_PAGE_NEXT
447
+ };
417
448
  var ToolbarController = class {
418
449
  el;
419
450
  toolbarEl;
@@ -524,10 +555,11 @@ var ToolbarController = class {
524
555
  type: "button",
525
556
  "data-action-id": id
526
557
  });
527
- if (iconHtml.startsWith("<svg")) {
528
- btn.innerHTML = sanitizeSVG(iconHtml);
558
+ const svg = ICON_MAP[iconHtml] || iconHtml;
559
+ if (svg && svg.startsWith("<svg")) {
560
+ btn.innerHTML = sanitizeSVG(svg);
529
561
  } else {
530
- btn.textContent = iconHtml;
562
+ btn.textContent = svg || title;
531
563
  }
532
564
  btn.addEventListener("click", onClick);
533
565
  return btn;
@@ -608,6 +640,9 @@ var FilePreviewViewer = class {
608
640
  contentEl = null;
609
641
  currentBuffer = null;
610
642
  currentMetadata = null;
643
+ keyHandler = null;
644
+ currentContainer = null;
645
+ currentOptions = {};
611
646
  /**
612
647
  * Register a preview plugin.
613
648
  */
@@ -662,24 +697,47 @@ var FilePreviewViewer = class {
662
697
  emit: (event, payload) => this.eventEmitter.emit(event, payload)
663
698
  });
664
699
  this.activeInstance = instance;
700
+ this.hideLoading();
701
+ this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
665
702
  if (options.showToolbar !== false && this.toolbar) {
666
703
  const actions = matchedPlugin.getToolbarActions(instance);
704
+ const hasFullscreen = actions.some((a) => a.id === "fullscreen");
705
+ if (!hasFullscreen && this.wrapperEl) {
706
+ actions.push({
707
+ id: "fullscreen",
708
+ icon: "fullscreen",
709
+ label: "Toggle Fullscreen",
710
+ type: "button",
711
+ group: "view",
712
+ execute: () => {
713
+ if (!document.fullscreenElement) {
714
+ this.wrapperEl?.requestFullscreen?.();
715
+ } else {
716
+ document.exitFullscreen?.();
717
+ }
718
+ }
719
+ });
720
+ }
667
721
  this.toolbar.update(actions);
668
722
  this.toolbar.show();
669
723
  }
670
724
  if (instance.getThumbnails && this.thumbnailPanel) {
671
- const thumbnails = await Promise.resolve(instance.getThumbnails());
672
- if (thumbnails && thumbnails.length > 0) {
673
- this.thumbnailPanel.update(thumbnails, (index) => {
674
- instance.goToPage?.(index + 1);
675
- });
676
- if (options.showThumbnails) {
677
- this.thumbnailPanel.show();
725
+ Promise.resolve().then(async () => {
726
+ try {
727
+ const thumbnails = await Promise.resolve(instance.getThumbnails());
728
+ if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
729
+ this.thumbnailPanel.update(thumbnails, (index) => {
730
+ instance.goToPage?.(index + 1);
731
+ });
732
+ if (options.showThumbnails) {
733
+ this.thumbnailPanel.show();
734
+ }
735
+ }
736
+ } catch (e) {
737
+ console.warn("[FilePreview] Non-critical thumbnail load error:", e);
678
738
  }
679
- }
739
+ });
680
740
  }
681
- this.hideLoading();
682
- this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
683
741
  return instance;
684
742
  } catch (error) {
685
743
  this.hideLoading();
@@ -701,6 +759,10 @@ var FilePreviewViewer = class {
701
759
  * Destroy the viewer and clean up all resources.
702
760
  */
703
761
  destroy() {
762
+ if (this.keyHandler) {
763
+ window.removeEventListener("keydown", this.keyHandler);
764
+ this.keyHandler = null;
765
+ }
704
766
  this.abort();
705
767
  this.destroyInstance();
706
768
  this.toolbar?.destroy();
@@ -716,6 +778,7 @@ var FilePreviewViewer = class {
716
778
  this.thumbnailPanel = null;
717
779
  this.currentBuffer = null;
718
780
  this.currentMetadata = null;
781
+ this.currentContainer = null;
719
782
  }
720
783
  /**
721
784
  * Get the currently active preview instance.
@@ -745,6 +808,8 @@ var FilePreviewViewer = class {
745
808
  return null;
746
809
  }
747
810
  setupDOM(container, options) {
811
+ this.currentContainer = container;
812
+ this.currentOptions = options;
748
813
  if (this.wrapperEl?.parentNode === container) return;
749
814
  if (this.wrapperEl?.parentNode) {
750
815
  this.wrapperEl.parentNode.removeChild(this.wrapperEl);
@@ -753,6 +818,7 @@ var FilePreviewViewer = class {
753
818
  const toolbarPos = options.toolbarPosition ?? "top";
754
819
  this.wrapperEl = document.createElement("div");
755
820
  this.wrapperEl.className = `fp-viewer ${themeClass} ${options.className ?? ""}`.trim();
821
+ this.wrapperEl.tabIndex = 0;
756
822
  const toolbarEl = document.createElement("div");
757
823
  toolbarEl.className = "fp-toolbar-container";
758
824
  this.contentEl = document.createElement("div");
@@ -774,6 +840,59 @@ var FilePreviewViewer = class {
774
840
  container.appendChild(this.wrapperEl);
775
841
  this.toolbar = new ToolbarController(toolbarEl);
776
842
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
843
+ this.setupKeyboardShortcuts();
844
+ this.setupDragAndDrop(container, options);
845
+ }
846
+ setupKeyboardShortcuts() {
847
+ if (this.keyHandler) return;
848
+ this.keyHandler = (e) => {
849
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target?.isContentEditable) {
850
+ return;
851
+ }
852
+ if (!this.activeInstance) return;
853
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
854
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
855
+ this.activeInstance.goToPage?.(cur + 1);
856
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
857
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
858
+ this.activeInstance.goToPage?.(Math.max(1, cur - 1));
859
+ } else if (e.key === "+" || e.key === "=") {
860
+ this.activeInstance.zoomIn?.();
861
+ } else if (e.key === "-" || e.key === "_") {
862
+ this.activeInstance.zoomOut?.();
863
+ } else if (e.key === "0") {
864
+ this.activeInstance.fitToPage?.();
865
+ } else if (e.key === "r" || e.key === "R") {
866
+ this.activeInstance.rotateCW?.();
867
+ } else if (e.key === "f" || e.key === "F") {
868
+ if (!document.fullscreenElement) {
869
+ this.wrapperEl?.requestFullscreen?.();
870
+ } else {
871
+ document.exitFullscreen?.();
872
+ }
873
+ }
874
+ };
875
+ window.addEventListener("keydown", this.keyHandler);
876
+ }
877
+ setupDragAndDrop(container, options) {
878
+ if (!this.wrapperEl) return;
879
+ this.wrapperEl.addEventListener("dragover", (e) => {
880
+ e.preventDefault();
881
+ this.wrapperEl?.classList.add("fp-dragover");
882
+ });
883
+ this.wrapperEl.addEventListener("dragleave", (e) => {
884
+ if (e.relatedTarget === null || !this.wrapperEl?.contains(e.relatedTarget)) {
885
+ this.wrapperEl?.classList.remove("fp-dragover");
886
+ }
887
+ });
888
+ this.wrapperEl.addEventListener("drop", (e) => {
889
+ e.preventDefault();
890
+ this.wrapperEl?.classList.remove("fp-dragover");
891
+ const file = e.dataTransfer?.files?.[0];
892
+ if (file) {
893
+ this.preview(container, file, options);
894
+ }
895
+ });
777
896
  }
778
897
  showLoading() {
779
898
  if (!this.contentEl) return;
@@ -792,10 +911,27 @@ var FilePreviewViewer = class {
792
911
  this.contentEl.innerHTML = "";
793
912
  const errorEl = document.createElement("div");
794
913
  errorEl.className = "fp-error";
914
+ errorEl.style.display = "flex";
915
+ errorEl.style.flexDirection = "column";
916
+ errorEl.style.alignItems = "center";
917
+ errorEl.style.justifyContent = "center";
918
+ errorEl.style.padding = "32px";
919
+ errorEl.style.textAlign = "center";
920
+ const hasDownload = !!(this.currentBuffer && this.currentMetadata);
795
921
  errorEl.innerHTML = `
796
- <div class="fp-error-icon">\u26A0\uFE0F</div>
797
- <div class="fp-error-message">${message}</div>
922
+ <div class="fp-error-icon" style="font-size:36px;margin-bottom:8px;">\u26A0\uFE0F</div>
923
+ <div class="fp-error-message" style="font-size:16px;font-weight:600;margin-bottom:6px;">Cannot preview file</div>
924
+ <div class="fp-error-sub" style="font-size:13px;color:#64748b;max-width:420px;line-height:1.5;margin-bottom:16px;">${message}</div>
925
+ ${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>' : ""}
798
926
  `;
927
+ if (hasDownload && this.currentBuffer) {
928
+ const btn = errorEl.querySelector(".fp-error-download-btn");
929
+ if (btn) {
930
+ btn.addEventListener("click", () => {
931
+ downloadFile(this.currentBuffer, this.currentMetadata?.name || "download", this.currentMetadata?.mimeType);
932
+ });
933
+ }
934
+ }
799
935
  this.contentEl.appendChild(errorEl);
800
936
  }
801
937
  };
@@ -1716,6 +1852,16 @@ var CodePlugin = class {
1716
1852
  instance.zoomIn?.();
1717
1853
  }
1718
1854
  },
1855
+ {
1856
+ id: "copy",
1857
+ icon: "copy",
1858
+ label: "Copy Content",
1859
+ type: "button",
1860
+ group: "actions",
1861
+ execute: () => {
1862
+ instance.copy?.();
1863
+ }
1864
+ },
1719
1865
  {
1720
1866
  id: "download",
1721
1867
  icon: "download",
@@ -1803,6 +1949,9 @@ var CodePlugin = class {
1803
1949
  },
1804
1950
  print: () => {
1805
1951
  window.print();
1952
+ },
1953
+ copy: () => {
1954
+ navigator.clipboard?.writeText(text);
1806
1955
  }
1807
1956
  };
1808
1957
  }
@@ -2053,6 +2202,14 @@ var MarkdownPlugin = class {
2053
2202
  group: "zoom",
2054
2203
  execute: () => instance.fitToPage?.()
2055
2204
  },
2205
+ {
2206
+ id: "copy",
2207
+ icon: "copy",
2208
+ label: "Copy Markdown",
2209
+ type: "button",
2210
+ group: "actions",
2211
+ execute: () => instance.copy?.()
2212
+ },
2056
2213
  {
2057
2214
  id: "download",
2058
2215
  icon: "download",
@@ -2203,6 +2360,9 @@ var MarkdownPlugin = class {
2203
2360
  },
2204
2361
  print: () => {
2205
2362
  window.print();
2363
+ },
2364
+ copy: () => {
2365
+ navigator.clipboard?.writeText(text);
2206
2366
  }
2207
2367
  };
2208
2368
  }