@files-preview-app/preview-file 1.1.4 → 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.js CHANGED
@@ -384,8 +384,39 @@ function createElement(tag, attrs, ...children) {
384
384
  }
385
385
  return el;
386
386
  }
387
+ 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>`;
388
+ 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>`;
389
+ 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>`;
390
+ 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>`;
391
+ 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>`;
392
+ 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>`;
393
+ 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>`;
387
394
  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>`;
388
395
  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>`;
396
+ 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>`;
397
+ 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>`;
398
+ 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>`;
399
+ 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>`;
400
+ 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>`;
401
+ 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>`;
402
+ var ICON_MAP = {
403
+ "zoom-in": ICON_ZOOM_IN,
404
+ "zoom-out": ICON_ZOOM_OUT,
405
+ "fit-page": ICON_FIT_PAGE,
406
+ "fit-slide": ICON_FIT_PAGE,
407
+ "rotate-cw": ICON_ROTATE_CW,
408
+ "rotate-ccw": ICON_ROTATE_CCW,
409
+ "download": ICON_DOWNLOAD,
410
+ "print": ICON_PRINT,
411
+ "thumbnails": ICON_THUMBNAILS,
412
+ "fullscreen": ICON_FULLSCREEN,
413
+ "play": ICON_PLAY,
414
+ "pause": ICON_PAUSE,
415
+ "close": ICON_CLOSE,
416
+ "copy": ICON_COPY,
417
+ "prev": ICON_PAGE_PREV,
418
+ "next": ICON_PAGE_NEXT
419
+ };
389
420
  var ToolbarController = class {
390
421
  el;
391
422
  toolbarEl;
@@ -496,10 +527,11 @@ var ToolbarController = class {
496
527
  type: "button",
497
528
  "data-action-id": id
498
529
  });
499
- if (iconHtml.startsWith("<svg")) {
500
- btn.innerHTML = sanitizeSVG(iconHtml);
530
+ const svg = ICON_MAP[iconHtml] || iconHtml;
531
+ if (svg && svg.startsWith("<svg")) {
532
+ btn.innerHTML = sanitizeSVG(svg);
501
533
  } else {
502
- btn.textContent = iconHtml;
534
+ btn.textContent = svg || title;
503
535
  }
504
536
  btn.addEventListener("click", onClick);
505
537
  return btn;
@@ -580,6 +612,9 @@ var FilePreviewViewer = class {
580
612
  contentEl = null;
581
613
  currentBuffer = null;
582
614
  currentMetadata = null;
615
+ keyHandler = null;
616
+ currentContainer = null;
617
+ currentOptions = {};
583
618
  /**
584
619
  * Register a preview plugin.
585
620
  */
@@ -634,24 +669,47 @@ var FilePreviewViewer = class {
634
669
  emit: (event, payload) => this.eventEmitter.emit(event, payload)
635
670
  });
636
671
  this.activeInstance = instance;
672
+ this.hideLoading();
673
+ this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
637
674
  if (options.showToolbar !== false && this.toolbar) {
638
675
  const actions = matchedPlugin.getToolbarActions(instance);
676
+ const hasFullscreen = actions.some((a) => a.id === "fullscreen");
677
+ if (!hasFullscreen && this.wrapperEl) {
678
+ actions.push({
679
+ id: "fullscreen",
680
+ icon: "fullscreen",
681
+ label: "Toggle Fullscreen",
682
+ type: "button",
683
+ group: "view",
684
+ execute: () => {
685
+ if (!document.fullscreenElement) {
686
+ this.wrapperEl?.requestFullscreen?.();
687
+ } else {
688
+ document.exitFullscreen?.();
689
+ }
690
+ }
691
+ });
692
+ }
639
693
  this.toolbar.update(actions);
640
694
  this.toolbar.show();
641
695
  }
642
696
  if (instance.getThumbnails && this.thumbnailPanel) {
643
- const thumbnails = await Promise.resolve(instance.getThumbnails());
644
- if (thumbnails && thumbnails.length > 0) {
645
- this.thumbnailPanel.update(thumbnails, (index) => {
646
- instance.goToPage?.(index + 1);
647
- });
648
- if (options.showThumbnails) {
649
- this.thumbnailPanel.show();
697
+ Promise.resolve().then(async () => {
698
+ try {
699
+ const thumbnails = await Promise.resolve(instance.getThumbnails());
700
+ if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
701
+ this.thumbnailPanel.update(thumbnails, (index) => {
702
+ instance.goToPage?.(index + 1);
703
+ });
704
+ if (options.showThumbnails) {
705
+ this.thumbnailPanel.show();
706
+ }
707
+ }
708
+ } catch (e) {
709
+ console.warn("[FilePreview] Non-critical thumbnail load error:", e);
650
710
  }
651
- }
711
+ });
652
712
  }
653
- this.hideLoading();
654
- this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
655
713
  return instance;
656
714
  } catch (error) {
657
715
  this.hideLoading();
@@ -673,6 +731,10 @@ var FilePreviewViewer = class {
673
731
  * Destroy the viewer and clean up all resources.
674
732
  */
675
733
  destroy() {
734
+ if (this.keyHandler) {
735
+ window.removeEventListener("keydown", this.keyHandler);
736
+ this.keyHandler = null;
737
+ }
676
738
  this.abort();
677
739
  this.destroyInstance();
678
740
  this.toolbar?.destroy();
@@ -688,6 +750,7 @@ var FilePreviewViewer = class {
688
750
  this.thumbnailPanel = null;
689
751
  this.currentBuffer = null;
690
752
  this.currentMetadata = null;
753
+ this.currentContainer = null;
691
754
  }
692
755
  /**
693
756
  * Get the currently active preview instance.
@@ -717,6 +780,8 @@ var FilePreviewViewer = class {
717
780
  return null;
718
781
  }
719
782
  setupDOM(container, options) {
783
+ this.currentContainer = container;
784
+ this.currentOptions = options;
720
785
  if (this.wrapperEl?.parentNode === container) return;
721
786
  if (this.wrapperEl?.parentNode) {
722
787
  this.wrapperEl.parentNode.removeChild(this.wrapperEl);
@@ -725,6 +790,7 @@ var FilePreviewViewer = class {
725
790
  const toolbarPos = options.toolbarPosition ?? "top";
726
791
  this.wrapperEl = document.createElement("div");
727
792
  this.wrapperEl.className = `fp-viewer ${themeClass} ${options.className ?? ""}`.trim();
793
+ this.wrapperEl.tabIndex = 0;
728
794
  const toolbarEl = document.createElement("div");
729
795
  toolbarEl.className = "fp-toolbar-container";
730
796
  this.contentEl = document.createElement("div");
@@ -746,6 +812,59 @@ var FilePreviewViewer = class {
746
812
  container.appendChild(this.wrapperEl);
747
813
  this.toolbar = new ToolbarController(toolbarEl);
748
814
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
815
+ this.setupKeyboardShortcuts();
816
+ this.setupDragAndDrop(container, options);
817
+ }
818
+ setupKeyboardShortcuts() {
819
+ if (this.keyHandler) return;
820
+ this.keyHandler = (e) => {
821
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target?.isContentEditable) {
822
+ return;
823
+ }
824
+ if (!this.activeInstance) return;
825
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
826
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
827
+ this.activeInstance.goToPage?.(cur + 1);
828
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
829
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
830
+ this.activeInstance.goToPage?.(Math.max(1, cur - 1));
831
+ } else if (e.key === "+" || e.key === "=") {
832
+ this.activeInstance.zoomIn?.();
833
+ } else if (e.key === "-" || e.key === "_") {
834
+ this.activeInstance.zoomOut?.();
835
+ } else if (e.key === "0") {
836
+ this.activeInstance.fitToPage?.();
837
+ } else if (e.key === "r" || e.key === "R") {
838
+ this.activeInstance.rotateCW?.();
839
+ } else if (e.key === "f" || e.key === "F") {
840
+ if (!document.fullscreenElement) {
841
+ this.wrapperEl?.requestFullscreen?.();
842
+ } else {
843
+ document.exitFullscreen?.();
844
+ }
845
+ }
846
+ };
847
+ window.addEventListener("keydown", this.keyHandler);
848
+ }
849
+ setupDragAndDrop(container, options) {
850
+ if (!this.wrapperEl) return;
851
+ this.wrapperEl.addEventListener("dragover", (e) => {
852
+ e.preventDefault();
853
+ this.wrapperEl?.classList.add("fp-dragover");
854
+ });
855
+ this.wrapperEl.addEventListener("dragleave", (e) => {
856
+ if (e.relatedTarget === null || !this.wrapperEl?.contains(e.relatedTarget)) {
857
+ this.wrapperEl?.classList.remove("fp-dragover");
858
+ }
859
+ });
860
+ this.wrapperEl.addEventListener("drop", (e) => {
861
+ e.preventDefault();
862
+ this.wrapperEl?.classList.remove("fp-dragover");
863
+ const file = e.dataTransfer?.files?.[0];
864
+ if (file) {
865
+ this.preview(container, file, options);
866
+ }
867
+ });
749
868
  }
750
869
  showLoading() {
751
870
  if (!this.contentEl) return;
@@ -764,10 +883,27 @@ var FilePreviewViewer = class {
764
883
  this.contentEl.innerHTML = "";
765
884
  const errorEl = document.createElement("div");
766
885
  errorEl.className = "fp-error";
886
+ errorEl.style.display = "flex";
887
+ errorEl.style.flexDirection = "column";
888
+ errorEl.style.alignItems = "center";
889
+ errorEl.style.justifyContent = "center";
890
+ errorEl.style.padding = "32px";
891
+ errorEl.style.textAlign = "center";
892
+ const hasDownload = !!(this.currentBuffer && this.currentMetadata);
767
893
  errorEl.innerHTML = `
768
- <div class="fp-error-icon">\u26A0\uFE0F</div>
769
- <div class="fp-error-message">${message}</div>
894
+ <div class="fp-error-icon" style="font-size:36px;margin-bottom:8px;">\u26A0\uFE0F</div>
895
+ <div class="fp-error-message" style="font-size:16px;font-weight:600;margin-bottom:6px;">Cannot preview file</div>
896
+ <div class="fp-error-sub" style="font-size:13px;color:#64748b;max-width:420px;line-height:1.5;margin-bottom:16px;">${message}</div>
897
+ ${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>' : ""}
770
898
  `;
899
+ if (hasDownload && this.currentBuffer) {
900
+ const btn = errorEl.querySelector(".fp-error-download-btn");
901
+ if (btn) {
902
+ btn.addEventListener("click", () => {
903
+ downloadFile(this.currentBuffer, this.currentMetadata?.name || "download", this.currentMetadata?.mimeType);
904
+ });
905
+ }
906
+ }
771
907
  this.contentEl.appendChild(errorEl);
772
908
  }
773
909
  };
@@ -1688,6 +1824,16 @@ var CodePlugin = class {
1688
1824
  instance.zoomIn?.();
1689
1825
  }
1690
1826
  },
1827
+ {
1828
+ id: "copy",
1829
+ icon: "copy",
1830
+ label: "Copy Content",
1831
+ type: "button",
1832
+ group: "actions",
1833
+ execute: () => {
1834
+ instance.copy?.();
1835
+ }
1836
+ },
1691
1837
  {
1692
1838
  id: "download",
1693
1839
  icon: "download",
@@ -1775,6 +1921,9 @@ var CodePlugin = class {
1775
1921
  },
1776
1922
  print: () => {
1777
1923
  window.print();
1924
+ },
1925
+ copy: () => {
1926
+ navigator.clipboard?.writeText(text);
1778
1927
  }
1779
1928
  };
1780
1929
  }
@@ -2025,6 +2174,14 @@ var MarkdownPlugin = class {
2025
2174
  group: "zoom",
2026
2175
  execute: () => instance.fitToPage?.()
2027
2176
  },
2177
+ {
2178
+ id: "copy",
2179
+ icon: "copy",
2180
+ label: "Copy Markdown",
2181
+ type: "button",
2182
+ group: "actions",
2183
+ execute: () => instance.copy?.()
2184
+ },
2028
2185
  {
2029
2186
  id: "download",
2030
2187
  icon: "download",
@@ -2175,6 +2332,9 @@ var MarkdownPlugin = class {
2175
2332
  },
2176
2333
  print: () => {
2177
2334
  window.print();
2335
+ },
2336
+ copy: () => {
2337
+ navigator.clipboard?.writeText(text);
2178
2338
  }
2179
2339
  };
2180
2340
  }