@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/vue.cjs CHANGED
@@ -371,8 +371,39 @@ function createElement(tag, attrs, ...children) {
371
371
  }
372
372
  return el;
373
373
  }
374
+ 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>`;
375
+ 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>`;
376
+ 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>`;
377
+ 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>`;
378
+ 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>`;
379
+ 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>`;
380
+ 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>`;
374
381
  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>`;
375
382
  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>`;
383
+ 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>`;
384
+ 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>`;
385
+ 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>`;
386
+ 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>`;
387
+ 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>`;
388
+ 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>`;
389
+ var ICON_MAP = {
390
+ "zoom-in": ICON_ZOOM_IN,
391
+ "zoom-out": ICON_ZOOM_OUT,
392
+ "fit-page": ICON_FIT_PAGE,
393
+ "fit-slide": ICON_FIT_PAGE,
394
+ "rotate-cw": ICON_ROTATE_CW,
395
+ "rotate-ccw": ICON_ROTATE_CCW,
396
+ "download": ICON_DOWNLOAD,
397
+ "print": ICON_PRINT,
398
+ "thumbnails": ICON_THUMBNAILS,
399
+ "fullscreen": ICON_FULLSCREEN,
400
+ "play": ICON_PLAY,
401
+ "pause": ICON_PAUSE,
402
+ "close": ICON_CLOSE,
403
+ "copy": ICON_COPY,
404
+ "prev": ICON_PAGE_PREV,
405
+ "next": ICON_PAGE_NEXT
406
+ };
376
407
  var ToolbarController = class {
377
408
  el;
378
409
  toolbarEl;
@@ -483,10 +514,11 @@ var ToolbarController = class {
483
514
  type: "button",
484
515
  "data-action-id": id
485
516
  });
486
- if (iconHtml.startsWith("<svg")) {
487
- btn.innerHTML = sanitizeSVG(iconHtml);
517
+ const svg = ICON_MAP[iconHtml] || iconHtml;
518
+ if (svg && svg.startsWith("<svg")) {
519
+ btn.innerHTML = sanitizeSVG(svg);
488
520
  } else {
489
- btn.textContent = iconHtml;
521
+ btn.textContent = svg || title;
490
522
  }
491
523
  btn.addEventListener("click", onClick);
492
524
  return btn;
@@ -567,6 +599,9 @@ var FilePreviewViewer = class {
567
599
  contentEl = null;
568
600
  currentBuffer = null;
569
601
  currentMetadata = null;
602
+ keyHandler = null;
603
+ currentContainer = null;
604
+ currentOptions = {};
570
605
  /**
571
606
  * Register a preview plugin.
572
607
  */
@@ -621,24 +656,47 @@ var FilePreviewViewer = class {
621
656
  emit: (event, payload) => this.eventEmitter.emit(event, payload)
622
657
  });
623
658
  this.activeInstance = instance;
659
+ this.hideLoading();
660
+ this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
624
661
  if (options.showToolbar !== false && this.toolbar) {
625
662
  const actions = matchedPlugin.getToolbarActions(instance);
663
+ const hasFullscreen = actions.some((a) => a.id === "fullscreen");
664
+ if (!hasFullscreen && this.wrapperEl) {
665
+ actions.push({
666
+ id: "fullscreen",
667
+ icon: "fullscreen",
668
+ label: "Toggle Fullscreen",
669
+ type: "button",
670
+ group: "view",
671
+ execute: () => {
672
+ if (!document.fullscreenElement) {
673
+ this.wrapperEl?.requestFullscreen?.();
674
+ } else {
675
+ document.exitFullscreen?.();
676
+ }
677
+ }
678
+ });
679
+ }
626
680
  this.toolbar.update(actions);
627
681
  this.toolbar.show();
628
682
  }
629
683
  if (instance.getThumbnails && this.thumbnailPanel) {
630
- const thumbnails = await Promise.resolve(instance.getThumbnails());
631
- if (thumbnails && thumbnails.length > 0) {
632
- this.thumbnailPanel.update(thumbnails, (index) => {
633
- instance.goToPage?.(index + 1);
634
- });
635
- if (options.showThumbnails) {
636
- this.thumbnailPanel.show();
684
+ Promise.resolve().then(async () => {
685
+ try {
686
+ const thumbnails = await Promise.resolve(instance.getThumbnails());
687
+ if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
688
+ this.thumbnailPanel.update(thumbnails, (index) => {
689
+ instance.goToPage?.(index + 1);
690
+ });
691
+ if (options.showThumbnails) {
692
+ this.thumbnailPanel.show();
693
+ }
694
+ }
695
+ } catch (e) {
696
+ console.warn("[FilePreview] Non-critical thumbnail load error:", e);
637
697
  }
638
- }
698
+ });
639
699
  }
640
- this.hideLoading();
641
- this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
642
700
  return instance;
643
701
  } catch (error) {
644
702
  this.hideLoading();
@@ -660,6 +718,10 @@ var FilePreviewViewer = class {
660
718
  * Destroy the viewer and clean up all resources.
661
719
  */
662
720
  destroy() {
721
+ if (this.keyHandler) {
722
+ window.removeEventListener("keydown", this.keyHandler);
723
+ this.keyHandler = null;
724
+ }
663
725
  this.abort();
664
726
  this.destroyInstance();
665
727
  this.toolbar?.destroy();
@@ -675,6 +737,7 @@ var FilePreviewViewer = class {
675
737
  this.thumbnailPanel = null;
676
738
  this.currentBuffer = null;
677
739
  this.currentMetadata = null;
740
+ this.currentContainer = null;
678
741
  }
679
742
  /**
680
743
  * Get the currently active preview instance.
@@ -704,6 +767,8 @@ var FilePreviewViewer = class {
704
767
  return null;
705
768
  }
706
769
  setupDOM(container, options) {
770
+ this.currentContainer = container;
771
+ this.currentOptions = options;
707
772
  if (this.wrapperEl?.parentNode === container) return;
708
773
  if (this.wrapperEl?.parentNode) {
709
774
  this.wrapperEl.parentNode.removeChild(this.wrapperEl);
@@ -712,6 +777,7 @@ var FilePreviewViewer = class {
712
777
  const toolbarPos = options.toolbarPosition ?? "top";
713
778
  this.wrapperEl = document.createElement("div");
714
779
  this.wrapperEl.className = `fp-viewer ${themeClass} ${options.className ?? ""}`.trim();
780
+ this.wrapperEl.tabIndex = 0;
715
781
  const toolbarEl = document.createElement("div");
716
782
  toolbarEl.className = "fp-toolbar-container";
717
783
  this.contentEl = document.createElement("div");
@@ -733,6 +799,59 @@ var FilePreviewViewer = class {
733
799
  container.appendChild(this.wrapperEl);
734
800
  this.toolbar = new ToolbarController(toolbarEl);
735
801
  this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
802
+ this.setupKeyboardShortcuts();
803
+ this.setupDragAndDrop(container, options);
804
+ }
805
+ setupKeyboardShortcuts() {
806
+ if (this.keyHandler) return;
807
+ this.keyHandler = (e) => {
808
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target?.isContentEditable) {
809
+ return;
810
+ }
811
+ if (!this.activeInstance) return;
812
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
813
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
814
+ this.activeInstance.goToPage?.(cur + 1);
815
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
816
+ const cur = this.activeInstance.getCurrentPage?.() ?? 1;
817
+ this.activeInstance.goToPage?.(Math.max(1, cur - 1));
818
+ } else if (e.key === "+" || e.key === "=") {
819
+ this.activeInstance.zoomIn?.();
820
+ } else if (e.key === "-" || e.key === "_") {
821
+ this.activeInstance.zoomOut?.();
822
+ } else if (e.key === "0") {
823
+ this.activeInstance.fitToPage?.();
824
+ } else if (e.key === "r" || e.key === "R") {
825
+ this.activeInstance.rotateCW?.();
826
+ } else if (e.key === "f" || e.key === "F") {
827
+ if (!document.fullscreenElement) {
828
+ this.wrapperEl?.requestFullscreen?.();
829
+ } else {
830
+ document.exitFullscreen?.();
831
+ }
832
+ }
833
+ };
834
+ window.addEventListener("keydown", this.keyHandler);
835
+ }
836
+ setupDragAndDrop(container, options) {
837
+ if (!this.wrapperEl) return;
838
+ this.wrapperEl.addEventListener("dragover", (e) => {
839
+ e.preventDefault();
840
+ this.wrapperEl?.classList.add("fp-dragover");
841
+ });
842
+ this.wrapperEl.addEventListener("dragleave", (e) => {
843
+ if (e.relatedTarget === null || !this.wrapperEl?.contains(e.relatedTarget)) {
844
+ this.wrapperEl?.classList.remove("fp-dragover");
845
+ }
846
+ });
847
+ this.wrapperEl.addEventListener("drop", (e) => {
848
+ e.preventDefault();
849
+ this.wrapperEl?.classList.remove("fp-dragover");
850
+ const file = e.dataTransfer?.files?.[0];
851
+ if (file) {
852
+ this.preview(container, file, options);
853
+ }
854
+ });
736
855
  }
737
856
  showLoading() {
738
857
  if (!this.contentEl) return;
@@ -751,10 +870,27 @@ var FilePreviewViewer = class {
751
870
  this.contentEl.innerHTML = "";
752
871
  const errorEl = document.createElement("div");
753
872
  errorEl.className = "fp-error";
873
+ errorEl.style.display = "flex";
874
+ errorEl.style.flexDirection = "column";
875
+ errorEl.style.alignItems = "center";
876
+ errorEl.style.justifyContent = "center";
877
+ errorEl.style.padding = "32px";
878
+ errorEl.style.textAlign = "center";
879
+ const hasDownload = !!(this.currentBuffer && this.currentMetadata);
754
880
  errorEl.innerHTML = `
755
- <div class="fp-error-icon">\u26A0\uFE0F</div>
756
- <div class="fp-error-message">${message}</div>
881
+ <div class="fp-error-icon" style="font-size:36px;margin-bottom:8px;">\u26A0\uFE0F</div>
882
+ <div class="fp-error-message" style="font-size:16px;font-weight:600;margin-bottom:6px;">Cannot preview file</div>
883
+ <div class="fp-error-sub" style="font-size:13px;color:#64748b;max-width:420px;line-height:1.5;margin-bottom:16px;">${message}</div>
884
+ ${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>' : ""}
757
885
  `;
886
+ if (hasDownload && this.currentBuffer) {
887
+ const btn = errorEl.querySelector(".fp-error-download-btn");
888
+ if (btn) {
889
+ btn.addEventListener("click", () => {
890
+ downloadFile(this.currentBuffer, this.currentMetadata?.name || "download", this.currentMetadata?.mimeType);
891
+ });
892
+ }
893
+ }
758
894
  this.contentEl.appendChild(errorEl);
759
895
  }
760
896
  };
@@ -1675,6 +1811,16 @@ var CodePlugin = class {
1675
1811
  instance.zoomIn?.();
1676
1812
  }
1677
1813
  },
1814
+ {
1815
+ id: "copy",
1816
+ icon: "copy",
1817
+ label: "Copy Content",
1818
+ type: "button",
1819
+ group: "actions",
1820
+ execute: () => {
1821
+ instance.copy?.();
1822
+ }
1823
+ },
1678
1824
  {
1679
1825
  id: "download",
1680
1826
  icon: "download",
@@ -1762,6 +1908,9 @@ var CodePlugin = class {
1762
1908
  },
1763
1909
  print: () => {
1764
1910
  window.print();
1911
+ },
1912
+ copy: () => {
1913
+ navigator.clipboard?.writeText(text);
1765
1914
  }
1766
1915
  };
1767
1916
  }
@@ -2012,6 +2161,14 @@ var MarkdownPlugin = class {
2012
2161
  group: "zoom",
2013
2162
  execute: () => instance.fitToPage?.()
2014
2163
  },
2164
+ {
2165
+ id: "copy",
2166
+ icon: "copy",
2167
+ label: "Copy Markdown",
2168
+ type: "button",
2169
+ group: "actions",
2170
+ execute: () => instance.copy?.()
2171
+ },
2015
2172
  {
2016
2173
  id: "download",
2017
2174
  icon: "download",
@@ -2162,6 +2319,9 @@ var MarkdownPlugin = class {
2162
2319
  },
2163
2320
  print: () => {
2164
2321
  window.print();
2322
+ },
2323
+ copy: () => {
2324
+ navigator.clipboard?.writeText(text);
2165
2325
  }
2166
2326
  };
2167
2327
  }