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