@files-preview-app/preview-file 1.3.1 → 1.3.3

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
@@ -527,8 +527,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
527
527
  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>`;
528
528
  var ICON_FAST_FORWARD = `<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="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
529
529
  var ICON_REWIND = `<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="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
530
- var ICON_SPEED = `<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="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
531
- var ICON_EXTERNAL_WINDOW = `<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="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`;
530
+ var ICON_EXTERNAL_WINDOW = `<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="18" height="18" rx="2"></rect><line x1="3" y1="8" x2="21" y2="8"></line><circle cx="6" cy="5.5" r="0.75" fill="currentColor"></circle><circle cx="8.5" cy="5.5" r="0.75" fill="currentColor"></circle><polyline points="14 13 18 13 18 17"></polyline><line x1="11" y1="20" x2="18" y2="13"></line></svg>`;
532
531
  var ICON_MAP = {
533
532
  "zoom-in": ICON_ZOOM_IN,
534
533
  "zoom-out": ICON_ZOOM_OUT,
@@ -555,28 +554,177 @@ var ICON_MAP = {
555
554
  "forward-10": ICON_FAST_FORWARD,
556
555
  "rewind": ICON_REWIND,
557
556
  "replay-10": ICON_REWIND,
558
- "speed": ICON_SPEED,
559
557
  "open-window": ICON_EXTERNAL_WINDOW,
560
- "external-window": ICON_EXTERNAL_WINDOW
558
+ "external-window": ICON_EXTERNAL_WINDOW,
559
+ "openWindow": ICON_EXTERNAL_WINDOW,
560
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
561
561
  };
562
562
  var ToolbarController = class {
563
563
  el;
564
564
  toolbarEl;
565
565
  actions = [];
566
+ config = {};
566
567
  pageInputEl = null;
567
568
  pageLabelEl = null;
568
- constructor(container) {
569
+ prevPageBtn = null;
570
+ nextPageBtn = null;
571
+ constructor(container, config) {
569
572
  this.el = container;
573
+ if (config) this.config = { ...config };
570
574
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
571
575
  this.el.appendChild(this.toolbarEl);
572
576
  }
577
+ setConfig(config) {
578
+ this.config = { ...config };
579
+ this.render();
580
+ }
581
+ getConfig() {
582
+ return { ...this.config };
583
+ }
584
+ hideAction(actionId) {
585
+ this.config[actionId] = false;
586
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
587
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
588
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
589
+ this.config.fitPage = false;
590
+ this.config.fitToPage = false;
591
+ this.config.fitWidth = false;
592
+ }
593
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
594
+ if (actionId === "fullscreen") this.config.fullscreen = false;
595
+ if (actionId === "download") this.config.download = false;
596
+ if (actionId === "print") this.config.print = false;
597
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
598
+ this.config.openWindow = false;
599
+ this.config.openSeparateWindow = false;
600
+ }
601
+ if (actionId === "copy") this.config.copy = false;
602
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
603
+ this.config.pageNav = false;
604
+ this.config.pagination = false;
605
+ }
606
+ this.render();
607
+ }
608
+ showAction(actionId) {
609
+ this.config[actionId] = true;
610
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
611
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
612
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
613
+ this.config.fitPage = true;
614
+ this.config.fitToPage = true;
615
+ this.config.fitWidth = true;
616
+ }
617
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
618
+ if (actionId === "fullscreen") this.config.fullscreen = true;
619
+ if (actionId === "download") this.config.download = true;
620
+ if (actionId === "print") this.config.print = true;
621
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
622
+ this.config.openWindow = true;
623
+ this.config.openSeparateWindow = true;
624
+ }
625
+ if (actionId === "copy") this.config.copy = true;
626
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
627
+ this.config.pageNav = true;
628
+ this.config.pagination = true;
629
+ }
630
+ this.render();
631
+ }
632
+ normalizeActionId(actionId) {
633
+ if (actionId === "zoomIn") return "zoom-in";
634
+ if (actionId === "zoomOut") return "zoom-out";
635
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
636
+ if (actionId === "rotateCW") return "rotate-cw";
637
+ if (actionId === "rotateCCW") return "rotate-ccw";
638
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
639
+ if (actionId === "pageNav") return "page-nav";
640
+ return actionId;
641
+ }
642
+ enableAction(actionId) {
643
+ const norm = this.normalizeActionId(actionId);
644
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
645
+ if (btn) {
646
+ btn.disabled = false;
647
+ btn.classList.remove("disabled");
648
+ }
649
+ }
650
+ disableAction(actionId) {
651
+ const norm = this.normalizeActionId(actionId);
652
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
653
+ if (btn) {
654
+ btn.disabled = true;
655
+ btn.classList.add("disabled");
656
+ }
657
+ }
658
+ setActionActive(actionId, active) {
659
+ const norm = this.normalizeActionId(actionId);
660
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
661
+ if (btn) {
662
+ if (active) {
663
+ btn.classList.add("active");
664
+ } else {
665
+ btn.classList.remove("active");
666
+ }
667
+ }
668
+ }
669
+ isActionEnabled(action) {
670
+ const c = this.config;
671
+ const id = action.id;
672
+ if (c[id] === false) return false;
673
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
674
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
675
+ if ((id === "fit-page" || id === "fit-width" || id === "fit-slide") && (c.fitPage === false || c.fitToPage === false || c.fitWidth === false || c["fit-page"] === false || c["fit-width"] === false)) {
676
+ return false;
677
+ }
678
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
679
+ return false;
680
+ }
681
+ if (id === "fullscreen" && c.fullscreen === false) return false;
682
+ if (id === "download" && c.download === false) return false;
683
+ if (id === "print" && c.print === false) return false;
684
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
685
+ return false;
686
+ }
687
+ if (id === "copy" && c.copy === false) return false;
688
+ if (id === "thumbnails" && c.thumbnails === false) return false;
689
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
690
+ return false;
691
+ }
692
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
693
+ return false;
694
+ }
695
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
696
+ return false;
697
+ }
698
+ if (id === "play" && c.play === false) return false;
699
+ if (id === "pause" && c.pause === false) return false;
700
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
701
+ return false;
702
+ }
703
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
704
+ return false;
705
+ }
706
+ if (id === "speed" && c.speed === false) return false;
707
+ return true;
708
+ }
573
709
  setPage(page, max) {
574
710
  if (this.pageInputEl) {
575
711
  this.pageInputEl.value = page.toString();
712
+ if (max !== void 0) {
713
+ this.pageInputEl.max = max.toString();
714
+ }
576
715
  }
577
716
  if (max !== void 0 && this.pageLabelEl) {
578
717
  this.pageLabelEl.textContent = ` / ${max}`;
579
718
  }
719
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
720
+ if (this.prevPageBtn) {
721
+ this.prevPageBtn.disabled = page <= 1;
722
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
723
+ }
724
+ if (this.nextPageBtn) {
725
+ this.nextPageBtn.disabled = page >= currentMax;
726
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
727
+ }
580
728
  }
581
729
  update(actions) {
582
730
  this.actions = actions;
@@ -599,8 +747,9 @@ var ToolbarController = class {
599
747
  view: [],
600
748
  actions: []
601
749
  };
602
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
603
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
750
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
751
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
752
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
604
753
  for (const action of effectiveActions) {
605
754
  if (groups[action.group]) {
606
755
  groups[action.group].push(action);
@@ -660,6 +809,13 @@ var ToolbarController = class {
660
809
  action.execute("go", val);
661
810
  });
662
811
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
812
+ const curVal = action.value ?? 1;
813
+ prevBtn.disabled = curVal <= 1;
814
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
815
+ nextBtn.disabled = curVal >= max;
816
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
817
+ this.prevPageBtn = prevBtn;
818
+ this.nextPageBtn = nextBtn;
663
819
  this.pageInputEl = input;
664
820
  this.pageLabelEl = label;
665
821
  groupEl.appendChild(prevBtn);
@@ -715,25 +871,63 @@ var ToolbarController = class {
715
871
  var ThumbnailPanel = class {
716
872
  el;
717
873
  panelEl;
874
+ listEl;
875
+ headerEl;
718
876
  thumbnails = [];
719
877
  onSelect;
878
+ onToggleCallback;
720
879
  activeIndex = 0;
721
- constructor(container) {
880
+ observer;
881
+ renderedIndices = /* @__PURE__ */ new Set();
882
+ constructor(container, onToggle) {
722
883
  this.el = container;
884
+ this.onToggleCallback = onToggle;
723
885
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
886
+ this.panelEl.style.width = "0px";
887
+ this.panelEl.style.minWidth = "0px";
888
+ this.panelEl.style.opacity = "0";
889
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
890
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
891
+ const closeBtn = createElement("button", {
892
+ className: "fp-thumbnail-close-btn",
893
+ title: "Close Thumbnails",
894
+ type: "button"
895
+ });
896
+ closeBtn.innerHTML = ICON_CLOSE;
897
+ closeBtn.addEventListener("click", (e) => {
898
+ e.stopPropagation();
899
+ this.hide();
900
+ });
901
+ this.headerEl.appendChild(title);
902
+ this.headerEl.appendChild(closeBtn);
903
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
904
+ this.panelEl.appendChild(this.headerEl);
905
+ this.panelEl.appendChild(this.listEl);
724
906
  this.el.appendChild(this.panelEl);
725
907
  }
908
+ isOpen() {
909
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
910
+ }
726
911
  update(thumbnails, onSelect) {
727
912
  this.thumbnails = thumbnails;
728
913
  this.onSelect = onSelect;
914
+ this.renderedIndices.clear();
915
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
916
+ if (titleEl) {
917
+ titleEl.textContent = `Pages (${thumbnails.length})`;
918
+ }
729
919
  this.render();
920
+ if (this.isOpen()) {
921
+ this.renderAllVisible();
922
+ }
730
923
  }
731
924
  setActive(index) {
732
925
  this.activeIndex = index;
733
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
926
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
734
927
  items.forEach((item, i) => {
735
928
  if (i === index) {
736
929
  item.classList.add("active");
930
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
737
931
  } else {
738
932
  item.classList.remove("active");
739
933
  }
@@ -741,37 +935,102 @@ var ThumbnailPanel = class {
741
935
  }
742
936
  show() {
743
937
  this.panelEl.classList.remove("hidden");
938
+ this.panelEl.style.width = "200px";
939
+ this.panelEl.style.minWidth = "200px";
940
+ this.panelEl.style.opacity = "1";
941
+ this.panelEl.style.display = "flex";
942
+ this.onToggleCallback?.(true);
943
+ this.renderAllVisible();
744
944
  }
745
945
  hide() {
746
946
  this.panelEl.classList.add("hidden");
947
+ this.panelEl.style.width = "0px";
948
+ this.panelEl.style.minWidth = "0px";
949
+ this.panelEl.style.opacity = "0";
950
+ this.onToggleCallback?.(false);
747
951
  }
748
952
  toggle() {
749
- this.panelEl.classList.toggle("hidden");
953
+ if (this.isOpen()) {
954
+ this.hide();
955
+ } else {
956
+ this.show();
957
+ }
750
958
  }
751
959
  destroy() {
960
+ if (this.observer) {
961
+ this.observer.disconnect();
962
+ this.observer = void 0;
963
+ }
964
+ this.renderedIndices.clear();
752
965
  this.el.innerHTML = "";
753
966
  }
754
- async render() {
755
- this.panelEl.innerHTML = "";
967
+ renderAllVisible() {
968
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
969
+ items.forEach((item) => {
970
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
971
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
972
+ this.renderThumbnailItem(idx, item);
973
+ }
974
+ });
975
+ }
976
+ renderThumbnailItem(idx, itemEl) {
977
+ if (this.renderedIndices.has(idx)) return;
978
+ this.renderedIndices.add(idx);
979
+ const canvas = itemEl.querySelector("canvas");
980
+ const thumb = this.thumbnails[idx];
981
+ if (canvas && thumb) {
982
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
983
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
984
+ });
985
+ }
986
+ }
987
+ render() {
988
+ if (this.observer) {
989
+ this.observer.disconnect();
990
+ }
991
+ this.listEl.innerHTML = "";
992
+ const useObserver = typeof IntersectionObserver !== "undefined";
993
+ if (useObserver) {
994
+ this.observer = new IntersectionObserver(
995
+ (entries) => {
996
+ for (const entry of entries) {
997
+ if (entry.isIntersecting) {
998
+ const target = entry.target;
999
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
1000
+ if (idx >= 0) {
1001
+ this.renderThumbnailItem(idx, target);
1002
+ }
1003
+ }
1004
+ }
1005
+ },
1006
+ {
1007
+ root: this.listEl,
1008
+ rootMargin: "120px 0px 120px 0px"
1009
+ }
1010
+ );
1011
+ }
756
1012
  for (let i = 0; i < this.thumbnails.length; i++) {
757
1013
  const thumb = this.thumbnails[i];
758
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
759
- if (i === this.activeIndex) {
760
- itemEl.classList.add("active");
761
- }
1014
+ const itemEl = createElement("div", {
1015
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
1016
+ });
1017
+ itemEl.dataset.thumbIndex = i.toString();
762
1018
  itemEl.addEventListener("click", () => {
763
1019
  this.setActive(i);
764
1020
  this.onSelect?.(i);
765
1021
  });
766
- const canvas = createElement("canvas", { width: "150", height: "200" });
767
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
768
- itemEl.appendChild(canvas);
1022
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
1023
+ const canvas = createElement("canvas", { width: "130", height: "170" });
1024
+ canvasWrapper.appendChild(canvas);
1025
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
1026
+ itemEl.appendChild(canvasWrapper);
769
1027
  itemEl.appendChild(label);
770
- this.panelEl.appendChild(itemEl);
771
- try {
772
- await thumb.render(canvas);
773
- } catch (err) {
774
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
1028
+ this.listEl.appendChild(itemEl);
1029
+ if (this.observer) {
1030
+ this.observer.observe(itemEl);
1031
+ }
1032
+ if (i < 4) {
1033
+ this.renderThumbnailItem(i, itemEl);
775
1034
  }
776
1035
  }
777
1036
  }
@@ -792,6 +1051,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
792
1051
  currentOptions = {};
793
1052
  resizeObserver = null;
794
1053
  fullscreenHandler = null;
1054
+ titleBarEl = null;
795
1055
  /**
796
1056
  * Register a preview plugin.
797
1057
  */
@@ -828,6 +1088,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
828
1088
  }
829
1089
  this.currentBuffer = buffer.slice(0);
830
1090
  this.currentMetadata = metadata;
1091
+ this.updateTitleBar(options, metadata);
831
1092
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
832
1093
  const fileInfo = { metadata, buffer };
833
1094
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -851,15 +1112,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
851
1112
  if (event === "page-change" && payload && typeof payload.page === "number") {
852
1113
  const total = payload.total ?? payload.totalPages;
853
1114
  this.toolbar?.setPage(payload.page, total);
1115
+ this.thumbnailPanel?.setActive(payload.page - 1);
854
1116
  }
855
1117
  this.eventEmitter.emit(event, payload);
856
- }
1118
+ },
1119
+ toggleThumbnails: () => this.toggleThumbnails()
857
1120
  });
858
1121
  this.activeInstance = instance;
859
1122
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1123
+ instance.toggleThumbnails = () => this.toggleThumbnails();
860
1124
  this.hideLoading();
861
1125
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
862
- if (options.showToolbar !== false && this.toolbar) {
1126
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1127
+ if (isToolbarVisible && this.toolbar) {
1128
+ const toolbarConfig = this.extractToolbarConfig(options);
1129
+ this.toolbar.setConfig(toolbarConfig);
863
1130
  const actions = matchedPlugin.getToolbarActions(instance);
864
1131
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
865
1132
  if (!hasFullscreen && this.wrapperEl) {
@@ -909,6 +1176,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
909
1176
  }
910
1177
  });
911
1178
  }
1179
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1180
+ if (thumbAction) {
1181
+ thumbAction.execute = () => {
1182
+ this.toggleThumbnails();
1183
+ };
1184
+ }
912
1185
  this.toolbar.update(actions);
913
1186
  this.toolbar.show();
914
1187
  }
@@ -993,7 +1266,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
993
1266
  if (targetUrl) {
994
1267
  const newWin2 = window.open(targetUrl, "_blank");
995
1268
  if (!newWin2) {
996
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1269
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
997
1270
  return null;
998
1271
  }
999
1272
  return newWin2;
@@ -1001,7 +1274,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1001
1274
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1002
1275
  const newWin = window.open("", "_blank");
1003
1276
  if (!newWin) {
1004
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1277
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1005
1278
  return null;
1006
1279
  }
1007
1280
  newWin.document.title = title;
@@ -1078,6 +1351,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1078
1351
  this.currentBuffer = null;
1079
1352
  this.currentMetadata = null;
1080
1353
  this.currentContainer = null;
1354
+ this.titleBarEl = null;
1081
1355
  }
1082
1356
  /**
1083
1357
  * Get the currently active preview instance.
@@ -1085,7 +1359,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1085
1359
  getInstance() {
1086
1360
  return this.activeInstance;
1087
1361
  }
1362
+ // --- Toolbar Configuration & Feature Toggles ---
1363
+ /**
1364
+ * Configure toolbar features dynamically through options or methods.
1365
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1366
+ */
1367
+ setToolbarConfig(config) {
1368
+ this.toolbar?.setConfig(config);
1369
+ }
1370
+ /**
1371
+ * Get current toolbar configuration.
1372
+ */
1373
+ getToolbarConfig() {
1374
+ return this.toolbar?.getConfig() ?? {};
1375
+ }
1376
+ /**
1377
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1378
+ */
1379
+ hideToolbarAction(actionId) {
1380
+ this.toolbar?.hideAction(actionId);
1381
+ }
1382
+ /**
1383
+ * Show a specific toolbar action by ID or alias.
1384
+ */
1385
+ showToolbarAction(actionId) {
1386
+ this.toolbar?.showAction(actionId);
1387
+ }
1388
+ /**
1389
+ * Enable a specific toolbar action button.
1390
+ */
1391
+ enableToolbarAction(actionId) {
1392
+ this.toolbar?.enableAction(actionId);
1393
+ }
1394
+ /**
1395
+ * Disable a specific toolbar action button.
1396
+ */
1397
+ disableToolbarAction(actionId) {
1398
+ this.toolbar?.disableAction(actionId);
1399
+ }
1400
+ // --- Programmatic Toolbar Action Methods ---
1401
+ /**
1402
+ * Fit document to page so it fills the frame width with minimal margins.
1403
+ */
1404
+ fitToPage() {
1405
+ this.activeInstance?.fitToPage?.();
1406
+ }
1407
+ /**
1408
+ * Zoom in.
1409
+ */
1410
+ zoomIn() {
1411
+ this.activeInstance?.zoomIn?.();
1412
+ }
1413
+ /**
1414
+ * Zoom out.
1415
+ */
1416
+ zoomOut() {
1417
+ this.activeInstance?.zoomOut?.();
1418
+ }
1419
+ /**
1420
+ * Set specific zoom level.
1421
+ */
1422
+ setZoom(level) {
1423
+ this.activeInstance?.setZoom?.(level);
1424
+ }
1425
+ /**
1426
+ * Get current zoom level.
1427
+ */
1428
+ getZoom() {
1429
+ return this.activeInstance?.getZoom?.() ?? 1;
1430
+ }
1431
+ /**
1432
+ * Rotate 90 degrees clockwise.
1433
+ */
1434
+ rotateCW() {
1435
+ this.activeInstance?.rotateCW?.();
1436
+ }
1437
+ /**
1438
+ * Rotate 90 degrees counter-clockwise.
1439
+ */
1440
+ rotateCCW() {
1441
+ this.activeInstance?.rotateCCW?.();
1442
+ }
1443
+ /**
1444
+ * Navigate to a specific page number.
1445
+ */
1446
+ goToPage(page) {
1447
+ this.activeInstance?.goToPage?.(page);
1448
+ this.toolbar?.setPage(page);
1449
+ }
1450
+ /**
1451
+ * Navigate to next page.
1452
+ */
1453
+ nextPage() {
1454
+ const cur = this.getCurrentPage();
1455
+ const max = this.getPageCount();
1456
+ if (cur < max) {
1457
+ this.goToPage(cur + 1);
1458
+ }
1459
+ }
1460
+ /**
1461
+ * Navigate to previous page.
1462
+ */
1463
+ prevPage() {
1464
+ const cur = this.getCurrentPage();
1465
+ if (cur > 1) {
1466
+ this.goToPage(cur - 1);
1467
+ }
1468
+ }
1469
+ /**
1470
+ * Get total page count.
1471
+ */
1472
+ getPageCount() {
1473
+ return this.activeInstance?.getPageCount?.() ?? 1;
1474
+ }
1475
+ /**
1476
+ * Get current page number.
1477
+ */
1478
+ getCurrentPage() {
1479
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1480
+ }
1481
+ /**
1482
+ * Download the current document.
1483
+ */
1484
+ download() {
1485
+ this.activeInstance?.download?.();
1486
+ }
1487
+ /**
1488
+ * Print the current document.
1489
+ */
1490
+ print() {
1491
+ this.activeInstance?.print?.();
1492
+ }
1493
+ /**
1494
+ * Toggle fullscreen mode.
1495
+ */
1496
+ toggleFullscreen() {
1497
+ try {
1498
+ if (!document.fullscreenElement) {
1499
+ this.wrapperEl?.requestFullscreen?.();
1500
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1501
+ } else {
1502
+ document.exitFullscreen?.();
1503
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1504
+ }
1505
+ } catch {
1506
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1507
+ }
1508
+ setTimeout(() => {
1509
+ this.activeInstance?.fitToPage?.();
1510
+ }, 120);
1511
+ }
1512
+ /**
1513
+ * Toggle thumbnails sidebar panel.
1514
+ */
1515
+ toggleThumbnails() {
1516
+ if (!this.thumbnailPanel) return;
1517
+ this.thumbnailPanel.toggle();
1518
+ const isOpen = this.thumbnailPanel.isOpen();
1519
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1520
+ setTimeout(() => {
1521
+ this.activeInstance?.fitToPage?.();
1522
+ }, 260);
1523
+ }
1524
+ /**
1525
+ * Set whether to display the file name / title bar above the toolbar.
1526
+ */
1527
+ setShowFileName(show) {
1528
+ if (this.currentOptions) {
1529
+ this.currentOptions.showFileName = show;
1530
+ }
1531
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1532
+ }
1533
+ /**
1534
+ * Set or override the displayed file name in the preview panel.
1535
+ */
1536
+ setFileName(name) {
1537
+ if (this.currentOptions) {
1538
+ this.currentOptions.fileName = name;
1539
+ }
1540
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1541
+ }
1088
1542
  // --- Private methods ---
1543
+ updateTitleBar(options, metadata) {
1544
+ if (!this.titleBarEl) return;
1545
+ const show = options?.showFileName !== false;
1546
+ if (!show) {
1547
+ this.titleBarEl.style.display = "none";
1548
+ return;
1549
+ }
1550
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1551
+ if (!name) {
1552
+ this.titleBarEl.style.display = "none";
1553
+ return;
1554
+ }
1555
+ this.titleBarEl.style.display = "flex";
1556
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1557
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1558
+ this.titleBarEl.innerHTML = `
1559
+ <div class="fp-titlebar-info">
1560
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1561
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1562
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1563
+ </div>
1564
+ `;
1565
+ }
1566
+ extractToolbarConfig(options) {
1567
+ if (!options) return {};
1568
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1569
+ return {
1570
+ ...options,
1571
+ ...nested
1572
+ };
1573
+ }
1089
1574
  abort() {
1090
1575
  if (this.abortController) {
1091
1576
  this.abortController.abort();
@@ -1128,17 +1613,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1128
1613
  bodyEl.className = "fp-body";
1129
1614
  bodyEl.appendChild(thumbnailEl);
1130
1615
  bodyEl.appendChild(this.contentEl);
1616
+ const titleBarEl = document.createElement("div");
1617
+ titleBarEl.className = "fp-titlebar";
1618
+ this.titleBarEl = titleBarEl;
1619
+ this.updateTitleBar(options, this.currentMetadata);
1131
1620
  if (toolbarPos === "top") {
1621
+ this.wrapperEl.appendChild(titleBarEl);
1132
1622
  this.wrapperEl.appendChild(toolbarEl);
1133
1623
  this.wrapperEl.appendChild(bodyEl);
1134
1624
  } else {
1625
+ this.wrapperEl.appendChild(titleBarEl);
1135
1626
  this.wrapperEl.appendChild(bodyEl);
1136
1627
  this.wrapperEl.appendChild(toolbarEl);
1137
1628
  }
1138
1629
  container.innerHTML = "";
1139
1630
  container.appendChild(this.wrapperEl);
1140
- this.toolbar = new ToolbarController(toolbarEl);
1141
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1631
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1632
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1633
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1634
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1635
+ setTimeout(() => {
1636
+ this.activeInstance?.fitToPage?.();
1637
+ }, 260);
1638
+ });
1142
1639
  this.setupKeyboardShortcuts();
1143
1640
  this.setupDragAndDrop(container, options);
1144
1641
  this.setupResizeAndFullscreenListeners();
@@ -1528,6 +2025,14 @@ var PdfPlugin = class {
1528
2025
  group: "zoom",
1529
2026
  execute: () => instance.fitToPage?.()
1530
2027
  },
2028
+ {
2029
+ id: "fit-width",
2030
+ icon: "fit-width",
2031
+ label: "Fit to Width",
2032
+ type: "button",
2033
+ group: "zoom",
2034
+ execute: () => instance.fitToWidth?.()
2035
+ },
1531
2036
  {
1532
2037
  id: "rotate-cw",
1533
2038
  icon: "rotate-cw",
@@ -1536,6 +2041,14 @@ var PdfPlugin = class {
1536
2041
  group: "view",
1537
2042
  execute: () => instance.rotateCW?.()
1538
2043
  },
2044
+ {
2045
+ id: "rotate-ccw",
2046
+ icon: "rotate-ccw",
2047
+ label: "Rotate Counter-Clockwise",
2048
+ type: "button",
2049
+ group: "view",
2050
+ execute: () => instance.rotateCCW?.()
2051
+ },
1539
2052
  {
1540
2053
  id: "download",
1541
2054
  icon: "download",
@@ -1567,12 +2080,13 @@ var PdfPlugin = class {
1567
2080
  container.className = "fp-pdf-container";
1568
2081
  container.style.width = "100%";
1569
2082
  container.style.height = "100%";
1570
- container.style.overflow = "auto";
2083
+ container.style.overflowX = "hidden";
2084
+ container.style.overflowY = "auto";
1571
2085
  container.style.display = "flex";
1572
2086
  container.style.flexDirection = "column";
1573
2087
  container.style.alignItems = "center";
1574
2088
  container.style.justifyContent = "flex-start";
1575
- container.style.padding = "20px 16px";
2089
+ container.style.padding = "16px 8px";
1576
2090
  container.style.backgroundColor = "#0f172a";
1577
2091
  container.style.boxSizing = "border-box";
1578
2092
  container.style.position = "relative";
@@ -1583,30 +2097,12 @@ var PdfPlugin = class {
1583
2097
  pageCard.style.borderRadius = "4px";
1584
2098
  pageCard.style.overflow = "hidden";
1585
2099
  pageCard.style.lineHeight = "0";
1586
- pageCard.style.transition = "transform 0.15s ease";
1587
2100
  pageCard.style.position = "relative";
1588
2101
  pageCard.style.flexShrink = "0";
2102
+ pageCard.style.transition = "box-shadow 0.2s ease";
1589
2103
  let canvas = document.createElement("canvas");
1590
2104
  pageCard.appendChild(canvas);
1591
2105
  container.appendChild(pageCard);
1592
- const indicator = document.createElement("div");
1593
- indicator.className = "fp-pdf-page-indicator";
1594
- indicator.style.position = "sticky";
1595
- indicator.style.bottom = "16px";
1596
- indicator.style.marginTop = "16px";
1597
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1598
- indicator.style.backdropFilter = "blur(8px)";
1599
- indicator.style.color = "#f8fafc";
1600
- indicator.style.fontSize = "12px";
1601
- indicator.style.fontWeight = "600";
1602
- indicator.style.padding = "5px 14px";
1603
- indicator.style.borderRadius = "20px";
1604
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1605
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1606
- indicator.style.zIndex = "10";
1607
- indicator.style.userSelect = "none";
1608
- indicator.style.pointerEvents = "none";
1609
- container.appendChild(indicator);
1610
2106
  ctx.container.appendChild(container);
1611
2107
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1612
2108
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1622,8 +2118,9 @@ var PdfPlugin = class {
1622
2118
  let currentPage = 1;
1623
2119
  let zoomScale = 1;
1624
2120
  let rotation = 0;
1625
- let fitMode = "page";
2121
+ let fitMode = ctx?.options?.fitMode || "width";
1626
2122
  let currentRenderTask = null;
2123
+ let textLayerDiv = null;
1627
2124
  const renderPage = async (pageNum) => {
1628
2125
  if (currentRenderTask) {
1629
2126
  try {
@@ -1635,30 +2132,37 @@ var PdfPlugin = class {
1635
2132
  const newCanvas = document.createElement("canvas");
1636
2133
  pageCard.replaceChild(newCanvas, canvas);
1637
2134
  canvas = newCanvas;
2135
+ if (textLayerDiv) {
2136
+ textLayerDiv.remove();
2137
+ textLayerDiv = null;
2138
+ }
1638
2139
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1639
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1640
2140
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1641
2141
  const page = await pdfDoc.getPage(currentPage);
1642
2142
  const containerWidth = container.clientWidth || 900;
1643
2143
  const containerHeight = container.clientHeight || 700;
1644
2144
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1645
- const availWidth = Math.max(320, containerWidth - 48);
1646
- const availHeight = Math.max(550, containerHeight - 88);
2145
+ const availWidth = Math.max(280, containerWidth - 36);
2146
+ const availHeight = Math.max(280, containerHeight - 32);
1647
2147
  const scaleW = availWidth / unscaledVp.width;
1648
2148
  const scaleH = availHeight / unscaledVp.height;
1649
2149
  let fitScale;
1650
2150
  if (fitMode === "page") {
1651
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2151
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1652
2152
  } else {
1653
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2153
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1654
2154
  }
1655
2155
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1656
2156
  const pixelRatio = window.devicePixelRatio || 1;
1657
2157
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2158
+ const displayWidth = Math.floor(viewport.width);
2159
+ const displayHeight = Math.floor(viewport.height);
1658
2160
  canvas.width = Math.floor(viewport.width * pixelRatio);
1659
2161
  canvas.height = Math.floor(viewport.height * pixelRatio);
1660
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1661
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2162
+ canvas.style.width = `${displayWidth}px`;
2163
+ canvas.style.height = `${displayHeight}px`;
2164
+ pageCard.style.width = `${displayWidth}px`;
2165
+ pageCard.style.height = `${displayHeight}px`;
1662
2166
  const canvasCtx = canvas.getContext("2d");
1663
2167
  if (!canvasCtx) return;
1664
2168
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1672,9 +2176,31 @@ var PdfPlugin = class {
1672
2176
  if (err?.name !== "RenderingCancelledException") {
1673
2177
  console.warn("[PdfPlugin] Page render warning:", err);
1674
2178
  }
2179
+ return;
1675
2180
  } finally {
1676
2181
  currentRenderTask = null;
1677
2182
  }
2183
+ try {
2184
+ textLayerDiv = document.createElement("div");
2185
+ textLayerDiv.className = "textLayer";
2186
+ textLayerDiv.style.width = `${displayWidth}px`;
2187
+ textLayerDiv.style.height = `${displayHeight}px`;
2188
+ textLayerDiv.style.position = "absolute";
2189
+ textLayerDiv.style.top = "0";
2190
+ textLayerDiv.style.left = "0";
2191
+ textLayerDiv.style.lineHeight = "1";
2192
+ pageCard.appendChild(textLayerDiv);
2193
+ if (pdfjsLib__namespace.TextLayer) {
2194
+ const textLayer = new pdfjsLib__namespace.TextLayer({
2195
+ textContentSource: page.streamTextContent(),
2196
+ container: textLayerDiv,
2197
+ viewport
2198
+ });
2199
+ await textLayer.render();
2200
+ }
2201
+ } catch (err) {
2202
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2203
+ }
1678
2204
  };
1679
2205
  renderPage(1);
1680
2206
  let resizeTimer = null;
@@ -1684,11 +2210,48 @@ var PdfPlugin = class {
1684
2210
  if (Math.abs(zoomScale - 1) < 0.05) {
1685
2211
  renderPage(currentPage);
1686
2212
  }
1687
- }, 150);
2213
+ }, 120);
1688
2214
  });
1689
2215
  resizeObserver.observe(container);
2216
+ const onWheel = (e) => {
2217
+ if (e.ctrlKey || e.metaKey) {
2218
+ e.preventDefault();
2219
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2220
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2221
+ renderPage(currentPage);
2222
+ }
2223
+ };
2224
+ container.addEventListener("wheel", onWheel, { passive: false });
2225
+ const onKeyDown = (e) => {
2226
+ const tag = e.target?.tagName?.toLowerCase();
2227
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2228
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2229
+ if (currentPage < totalPages) {
2230
+ e.preventDefault();
2231
+ container.scrollTop = 0;
2232
+ renderPage(currentPage + 1);
2233
+ }
2234
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2235
+ if (currentPage > 1) {
2236
+ e.preventDefault();
2237
+ container.scrollTop = 0;
2238
+ renderPage(currentPage - 1);
2239
+ }
2240
+ } else if (e.key === "Home") {
2241
+ e.preventDefault();
2242
+ container.scrollTop = 0;
2243
+ renderPage(1);
2244
+ } else if (e.key === "End") {
2245
+ e.preventDefault();
2246
+ container.scrollTop = 0;
2247
+ renderPage(totalPages);
2248
+ }
2249
+ };
2250
+ window.addEventListener("keydown", onKeyDown);
1690
2251
  const cleanup = () => {
1691
2252
  resizeObserver.disconnect();
2253
+ window.removeEventListener("keydown", onKeyDown);
2254
+ container.removeEventListener("wheel", onWheel);
1692
2255
  if (resizeTimer) clearTimeout(resizeTimer);
1693
2256
  if (currentRenderTask) {
1694
2257
  try {
@@ -1707,22 +2270,26 @@ var PdfPlugin = class {
1707
2270
  const instance = {
1708
2271
  destroy: cleanup,
1709
2272
  zoomIn: () => {
1710
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2273
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1711
2274
  renderPage(currentPage);
1712
2275
  },
1713
2276
  zoomOut: () => {
1714
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2277
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1715
2278
  renderPage(currentPage);
1716
2279
  },
1717
2280
  getZoom: () => zoomScale,
1718
2281
  setZoom: (level) => {
1719
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2282
+ zoomScale = Math.max(0.25, Math.min(4, level));
1720
2283
  renderPage(currentPage);
1721
2284
  },
1722
2285
  fitToPage: () => {
1723
- fitMode = fitMode === "page" ? "width" : "page";
2286
+ fitMode = "page";
2287
+ zoomScale = 1;
2288
+ renderPage(currentPage);
2289
+ },
2290
+ fitToWidth: () => {
2291
+ fitMode = "width";
1724
2292
  zoomScale = 1;
1725
- rotation = 0;
1726
2293
  renderPage(currentPage);
1727
2294
  },
1728
2295
  rotateCW: () => {
@@ -1740,6 +2307,9 @@ var PdfPlugin = class {
1740
2307
  container.scrollTop = 0;
1741
2308
  renderPage(page);
1742
2309
  },
2310
+ toggleThumbnails: () => {
2311
+ ctx?.toggleThumbnails?.();
2312
+ },
1743
2313
  download: () => {
1744
2314
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1745
2315
  const url = URL.createObjectURL(blob);
@@ -1773,7 +2343,7 @@ var PdfPlugin = class {
1773
2343
  },
1774
2344
  getThumbnails: async () => {
1775
2345
  const thumbnails = [];
1776
- const count = Math.min(totalPages, 50);
2346
+ const count = totalPages;
1777
2347
  for (let i = 1; i <= count; i++) {
1778
2348
  thumbnails.push({
1779
2349
  index: i,
@@ -1781,9 +2351,10 @@ var PdfPlugin = class {
1781
2351
  render: async (thumbCanvas) => {
1782
2352
  try {
1783
2353
  const p = await pdfDoc.getPage(i);
1784
- const baseVp = p.getViewport({ scale: 1 });
1785
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1786
- const thumbVp = p.getViewport({ scale: thumbScale });
2354
+ const baseVp = p.getViewport({ scale: 1, rotation });
2355
+ const targetW = thumbCanvas.width || 130;
2356
+ const thumbScale = targetW / baseVp.width;
2357
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1787
2358
  thumbCanvas.height = Math.floor(thumbVp.height);
1788
2359
  const tCtx = thumbCanvas.getContext("2d");
1789
2360
  if (tCtx) {
@@ -2127,28 +2698,26 @@ var DocxPlugin = class {
2127
2698
  getToolbarActions(instance) {
2128
2699
  const totalPages = instance.getPageCount?.() ?? 1;
2129
2700
  const actions = [];
2130
- if (totalPages > 1) {
2131
- actions.push({
2132
- id: "page-nav",
2133
- icon: "",
2134
- label: "Page Navigation",
2135
- type: "page-nav",
2136
- group: "navigation",
2137
- value: instance.getCurrentPage?.() ?? 1,
2138
- max: totalPages,
2139
- execute: (action, page) => {
2140
- const cur = instance.getCurrentPage?.() ?? 1;
2141
- const max = instance.getPageCount?.() ?? 1;
2142
- if (action === "prev") {
2143
- if (cur > 1) instance.goToPage?.(cur - 1);
2144
- } else if (action === "next") {
2145
- if (cur < max) instance.goToPage?.(cur + 1);
2146
- } else if (typeof page === "number") {
2147
- instance.goToPage?.(page);
2148
- }
2701
+ actions.push({
2702
+ id: "page-nav",
2703
+ icon: "",
2704
+ label: "Page Navigation",
2705
+ type: "page-nav",
2706
+ group: "navigation",
2707
+ value: instance.getCurrentPage?.() ?? 1,
2708
+ max: totalPages,
2709
+ execute: (action, page) => {
2710
+ const cur = instance.getCurrentPage?.() ?? 1;
2711
+ const max = instance.getPageCount?.() ?? 1;
2712
+ if (action === "prev") {
2713
+ if (cur > 1) instance.goToPage?.(cur - 1);
2714
+ } else if (action === "next") {
2715
+ if (cur < max) instance.goToPage?.(cur + 1);
2716
+ } else if (typeof page === "number") {
2717
+ instance.goToPage?.(page);
2149
2718
  }
2150
- });
2151
- }
2719
+ }
2720
+ });
2152
2721
  actions.push(
2153
2722
  {
2154
2723
  id: "zoom-out",
@@ -2214,11 +2783,17 @@ var DocxPlugin = class {
2214
2783
  wrapper.className = "fp-docx-wrapper";
2215
2784
  wrapper.style.transformOrigin = "top center";
2216
2785
  wrapper.style.transition = "transform 0.2s ease";
2217
- wrapper.style.padding = "24px 16px";
2218
- wrapper.style.maxWidth = "950px";
2786
+ wrapper.style.padding = "0";
2787
+ wrapper.style.maxWidth = "none";
2788
+ wrapper.style.width = "816px";
2219
2789
  wrapper.style.margin = "0 auto";
2790
+ wrapper.style.display = "flex";
2791
+ wrapper.style.flexDirection = "column";
2792
+ wrapper.style.alignItems = "center";
2220
2793
  wrapper.style.boxSizing = "border-box";
2221
- ctx.container.style.overflow = "auto";
2794
+ ctx.container.style.overflowX = "hidden";
2795
+ ctx.container.style.overflowY = "auto";
2796
+ ctx.container.style.padding = "16px 8px";
2222
2797
  ctx.container.style.backgroundColor = "#f1f5f9";
2223
2798
  ctx.container.appendChild(wrapper);
2224
2799
  const styleOverride = document.createElement("style");
@@ -2392,39 +2967,13 @@ var DocxPlugin = class {
2392
2967
  const pageElements = sections.length > 0 ? sections : cards;
2393
2968
  const totalPages = Math.max(1, pageElements.length);
2394
2969
  let currentPage = 1;
2395
- let indicator = null;
2396
- if (totalPages > 1) {
2397
- indicator = document.createElement("div");
2398
- indicator.className = "fp-docx-page-indicator";
2399
- indicator.style.position = "sticky";
2400
- indicator.style.bottom = "16px";
2401
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2402
- indicator.style.backdropFilter = "blur(8px)";
2403
- indicator.style.color = "#f8fafc";
2404
- indicator.style.fontSize = "12px";
2405
- indicator.style.fontWeight = "600";
2406
- indicator.style.padding = "5px 14px";
2407
- indicator.style.borderRadius = "20px";
2408
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2409
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2410
- indicator.style.zIndex = "10";
2411
- indicator.style.userSelect = "none";
2412
- indicator.style.pointerEvents = "none";
2413
- indicator.style.textAlign = "center";
2414
- indicator.style.width = "fit-content";
2415
- indicator.style.margin = "16px auto 0";
2416
- ctx.container.appendChild(indicator);
2417
- }
2418
2970
  const showPage = (pageNum) => {
2419
2971
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2420
2972
  if (pageElements.length > 1) {
2421
2973
  pageElements.forEach((sec, idx) => {
2422
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
2974
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2423
2975
  });
2424
2976
  }
2425
- if (indicator) {
2426
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2427
- }
2428
2977
  ctx.container.scrollTop = 0;
2429
2978
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2430
2979
  };
@@ -2433,31 +2982,46 @@ var DocxPlugin = class {
2433
2982
  }
2434
2983
  scale = 1;
2435
2984
  let rotation = 0;
2436
- let fitMode = "width";
2437
- const calculateFitScale = (mode = fitMode) => {
2438
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2985
+ let fitMode = "page";
2986
+ let isUserZoomed = false;
2987
+ const calculateFitScale = (_mode = fitMode) => {
2988
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2439
2989
  const elW = activeEl.offsetWidth || 816;
2440
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2441
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2442
- const availH = Math.max(280, ctx.container.clientHeight - 80);
2990
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2443
2991
  const sW = availW / elW;
2444
- const sH = availH / singlePageH;
2445
- if (mode === "page") {
2446
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2447
- }
2448
- return Math.max(0.65, Math.min(1.05, sW));
2992
+ return Math.max(0.35, Math.min(3, sW));
2449
2993
  };
2450
2994
  const applyTransform = () => {
2451
2995
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2452
2996
  wrapper.style.transformOrigin = "top center";
2997
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2998
+ const elH = activeEl.offsetHeight || 1056;
2999
+ if (scale > 1) {
3000
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3001
+ } else {
3002
+ wrapper.style.marginBottom = "32px";
3003
+ }
2453
3004
  };
3005
+ let resizeObserver = null;
3006
+ if (typeof ResizeObserver !== "undefined") {
3007
+ resizeObserver = new ResizeObserver(() => {
3008
+ if (!isUserZoomed) {
3009
+ const newFit = calculateFitScale("page");
3010
+ if (Math.abs(scale - newFit) > 0.015) {
3011
+ scale = newFit;
3012
+ applyTransform();
3013
+ }
3014
+ }
3015
+ });
3016
+ resizeObserver.observe(ctx.container);
3017
+ }
2454
3018
  setTimeout(() => {
2455
- fitMode = "width";
2456
- scale = calculateFitScale("width");
3019
+ scale = calculateFitScale("page");
2457
3020
  applyTransform();
2458
- }, 60);
3021
+ }, 40);
2459
3022
  const cleanup = () => {
2460
- indicator?.remove();
3023
+ resizeObserver?.disconnect();
3024
+ resizeObserver = null;
2461
3025
  for (const url of createdBlobUrls) {
2462
3026
  URL.revokeObjectURL(url);
2463
3027
  }
@@ -2474,21 +3038,24 @@ var DocxPlugin = class {
2474
3038
  showPage(page);
2475
3039
  },
2476
3040
  zoomIn: () => {
2477
- scale += 0.15;
3041
+ isUserZoomed = true;
3042
+ scale = Math.min(3.5, scale + 0.15);
2478
3043
  applyTransform();
2479
3044
  },
2480
3045
  zoomOut: () => {
3046
+ isUserZoomed = true;
2481
3047
  scale = Math.max(0.2, scale - 0.15);
2482
3048
  applyTransform();
2483
3049
  },
2484
3050
  getZoom: () => scale,
2485
3051
  setZoom: (level) => {
3052
+ isUserZoomed = true;
2486
3053
  scale = level;
2487
3054
  applyTransform();
2488
3055
  },
2489
3056
  fitToPage: () => {
2490
- fitMode = fitMode === "width" ? "page" : "width";
2491
- scale = calculateFitScale(fitMode);
3057
+ isUserZoomed = false;
3058
+ scale = calculateFitScale("page");
2492
3059
  rotation = 0;
2493
3060
  applyTransform();
2494
3061
  },
@@ -2878,7 +3445,7 @@ var ExcelPlugin = class {
2878
3445
  "application/vnd.ms-excel.template.macroEnabled.12",
2879
3446
  "application/vnd.oasis.opendocument.spreadsheet"
2880
3447
  ];
2881
- weight = 80;
3448
+ weight = 85;
2882
3449
  supports(file) {
2883
3450
  const ext = file.metadata.extension?.toLowerCase();
2884
3451
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2890,6 +3457,14 @@ var ExcelPlugin = class {
2890
3457
  getToolbarActions(instance) {
2891
3458
  const totalSheets = instance.getPageCount?.() ?? 1;
2892
3459
  const actions = [];
3460
+ actions.push({
3461
+ id: "thumbnails",
3462
+ icon: "thumbnails",
3463
+ label: "Sheet Thumbnails",
3464
+ type: "button",
3465
+ group: "navigation",
3466
+ execute: () => instance.toggleThumbnails?.()
3467
+ });
2893
3468
  if (totalSheets > 1) {
2894
3469
  actions.push({
2895
3470
  id: "page-nav",
@@ -2933,26 +3508,6 @@ var ExcelPlugin = class {
2933
3508
  instance.zoomIn?.();
2934
3509
  }
2935
3510
  },
2936
- {
2937
- id: "fit-page",
2938
- icon: "fit-page",
2939
- label: "Fit to View",
2940
- type: "button",
2941
- group: "zoom",
2942
- execute: () => {
2943
- instance.fitToPage?.();
2944
- }
2945
- },
2946
- {
2947
- id: "rotate-cw",
2948
- icon: "rotate-cw",
2949
- label: "Rotate",
2950
- type: "button",
2951
- group: "view",
2952
- execute: () => {
2953
- instance.rotateCW?.();
2954
- }
2955
- },
2956
3511
  {
2957
3512
  id: "download",
2958
3513
  icon: "download",
@@ -2972,6 +3527,16 @@ var ExcelPlugin = class {
2972
3527
  execute: () => {
2973
3528
  instance.print?.();
2974
3529
  }
3530
+ },
3531
+ {
3532
+ id: "open-window",
3533
+ icon: "open-window",
3534
+ label: "Open in Separate Full Window",
3535
+ type: "button",
3536
+ group: "actions",
3537
+ execute: () => {
3538
+ instance.openInSeparateWindow?.();
3539
+ }
2975
3540
  }
2976
3541
  );
2977
3542
  return actions;
@@ -3003,12 +3568,11 @@ var ExcelPlugin = class {
3003
3568
  container.appendChild(tabsArea);
3004
3569
  ctx.container.appendChild(container);
3005
3570
  let scale = 1;
3006
- let rotation = 0;
3007
3571
  let currentSheetIndex = 1;
3008
3572
  let sheetNames = [];
3009
3573
  let wb = null;
3010
3574
  const applyTransform = () => {
3011
- contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3575
+ contentArea.style.transform = `scale(${scale})`;
3012
3576
  contentArea.style.transformOrigin = "top left";
3013
3577
  };
3014
3578
  const calculateFitScale = () => {
@@ -3016,7 +3580,7 @@ var ExcelPlugin = class {
3016
3580
  if (!table) return 1;
3017
3581
  const availW = Math.max(280, container.clientWidth - 48);
3018
3582
  const tW = table.offsetWidth || 800;
3019
- return Math.max(0.65, Math.min(1, availW / tW));
3583
+ return Math.max(0.4, Math.min(1, availW / tW));
3020
3584
  };
3021
3585
  try {
3022
3586
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
@@ -3071,7 +3635,16 @@ var ExcelPlugin = class {
3071
3635
  }
3072
3636
  });
3073
3637
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3638
+ requestAnimationFrame(() => {
3639
+ scale = calculateFitScale();
3640
+ applyTransform();
3641
+ });
3074
3642
  };
3643
+ const ro = new ResizeObserver(() => {
3644
+ scale = calculateFitScale();
3645
+ applyTransform();
3646
+ });
3647
+ ro.observe(container);
3075
3648
  if (sheetNames.length > 0) {
3076
3649
  sheetNames.forEach((name, idx) => {
3077
3650
  const btn = document.createElement("button");
@@ -3098,6 +3671,7 @@ var ExcelPlugin = class {
3098
3671
  `;
3099
3672
  }
3100
3673
  const cleanup = () => {
3674
+ ro.disconnect();
3101
3675
  container.remove();
3102
3676
  ctx.container.innerHTML = "";
3103
3677
  };
@@ -3119,16 +3693,11 @@ var ExcelPlugin = class {
3119
3693
  },
3120
3694
  fitToPage: () => {
3121
3695
  scale = calculateFitScale();
3122
- rotation = 0;
3123
3696
  applyTransform();
3124
3697
  },
3125
3698
  rotateCW: () => {
3126
- rotation = (rotation + 90) % 360;
3127
- applyTransform();
3128
3699
  },
3129
3700
  rotateCCW: () => {
3130
- rotation = (rotation - 90 + 360) % 360;
3131
- applyTransform();
3132
3701
  },
3133
3702
  goToPage: (page) => {
3134
3703
  if (page > 0 && page <= sheetNames.length) {
@@ -3137,6 +3706,63 @@ var ExcelPlugin = class {
3137
3706
  },
3138
3707
  getPageCount: () => sheetNames.length,
3139
3708
  getCurrentPage: () => currentSheetIndex,
3709
+ getThumbnails: () => {
3710
+ return sheetNames.map((name, idx) => ({
3711
+ index: idx,
3712
+ label: name,
3713
+ render: async (canvas) => {
3714
+ canvas.width = 140;
3715
+ canvas.height = 100;
3716
+ const c = canvas.getContext("2d");
3717
+ if (!c) return;
3718
+ c.fillStyle = "#ffffff";
3719
+ c.fillRect(0, 0, canvas.width, canvas.height);
3720
+ c.fillStyle = "#107c41";
3721
+ c.fillRect(0, 0, canvas.width, 16);
3722
+ c.fillStyle = "#ffffff";
3723
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3724
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3725
+ const startY = 17;
3726
+ const rowH = 13;
3727
+ const colW = 30;
3728
+ const colHeaders = ["A", "B", "C", "D"];
3729
+ c.fillStyle = "#f1f5f9";
3730
+ c.fillRect(0, startY, canvas.width, rowH);
3731
+ c.strokeStyle = "#cbd5e1";
3732
+ c.lineWidth = 0.8;
3733
+ c.strokeRect(0, startY, canvas.width, rowH);
3734
+ c.fillStyle = "#64748b";
3735
+ c.font = "bold 7px sans-serif";
3736
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3737
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3738
+ }
3739
+ const ws = wb?.Sheets[name];
3740
+ for (let ri = 1; ri <= 5; ri++) {
3741
+ const y = startY + ri * rowH;
3742
+ if (y > canvas.height - 2) break;
3743
+ c.fillStyle = "#f8fafc";
3744
+ c.fillRect(0, y, 12, rowH);
3745
+ c.fillStyle = "#94a3b8";
3746
+ c.font = "6px sans-serif";
3747
+ c.fillText(String(ri), 3, y + 9);
3748
+ c.strokeStyle = "#e2e8f0";
3749
+ c.strokeRect(0, y, canvas.width, rowH);
3750
+ c.fillStyle = "#334155";
3751
+ c.font = "6px sans-serif";
3752
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3753
+ const cellRef = `${colHeaders[ci]}${ri}`;
3754
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3755
+ if (cellVal !== void 0) {
3756
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3757
+ }
3758
+ }
3759
+ }
3760
+ c.strokeStyle = "#cbd5e1";
3761
+ c.lineWidth = 1;
3762
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3763
+ }
3764
+ }));
3765
+ },
3140
3766
  download: () => {
3141
3767
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3142
3768
  const url = URL.createObjectURL(blob);
@@ -3169,7 +3795,16 @@ var CsvPlugin = class {
3169
3795
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3170
3796
  }
3171
3797
  getToolbarActions(instance) {
3172
- return [
3798
+ const actions = [];
3799
+ actions.push({
3800
+ id: "thumbnails",
3801
+ icon: "thumbnails",
3802
+ label: "Sheet Thumbnails",
3803
+ type: "button",
3804
+ group: "navigation",
3805
+ execute: () => instance.toggleThumbnails?.()
3806
+ });
3807
+ actions.push(
3173
3808
  {
3174
3809
  id: "zoom-out",
3175
3810
  icon: "zoom-out",
@@ -3209,8 +3844,19 @@ var CsvPlugin = class {
3209
3844
  execute: () => {
3210
3845
  instance.print?.();
3211
3846
  }
3847
+ },
3848
+ {
3849
+ id: "open-window",
3850
+ icon: "open-window",
3851
+ label: "Open in Separate Full Window",
3852
+ type: "button",
3853
+ group: "actions",
3854
+ execute: () => {
3855
+ instance.openInSeparateWindow?.();
3856
+ }
3212
3857
  }
3213
- ];
3858
+ );
3859
+ return actions;
3214
3860
  }
3215
3861
  async render(ctx) {
3216
3862
  const decoder = new TextDecoder("utf-8");
@@ -3221,57 +3867,156 @@ var CsvPlugin = class {
3221
3867
  container.style.overflow = "auto";
3222
3868
  container.style.padding = "16px";
3223
3869
  container.style.boxSizing = "border-box";
3224
- container.style.transformOrigin = "top left";
3870
+ const tableWrapper = document.createElement("div");
3871
+ tableWrapper.style.transformOrigin = "top left";
3872
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3873
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3874
+ const headerLine = allLines[0] || "";
3875
+ const headerCells = headerLine.split(delimiter);
3876
+ const dataLines = allLines.slice(1);
3225
3877
  const table = document.createElement("table");
3226
3878
  table.style.borderCollapse = "collapse";
3227
- table.style.width = "100%";
3228
- table.style.fontFamily = "monospace";
3879
+ table.style.minWidth = "100%";
3880
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3229
3881
  table.style.fontSize = "13px";
3230
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3231
- const rows = text.split(/\r?\n/).slice(0, 500);
3232
- rows.forEach((row, rowIndex) => {
3233
- if (!row.trim()) return;
3882
+ table.style.background = "#ffffff";
3883
+ table.style.border = "1px solid #d0d7de";
3884
+ const headerTr = document.createElement("tr");
3885
+ headerCells.forEach((cell) => {
3886
+ const th = document.createElement("th");
3887
+ th.textContent = cell.trim();
3888
+ th.style.border = "1px solid #d0d7de";
3889
+ th.style.padding = "8px 12px";
3890
+ th.style.backgroundColor = "#f6f8fa";
3891
+ th.style.fontWeight = "600";
3892
+ th.style.whiteSpace = "nowrap";
3893
+ headerTr.appendChild(th);
3894
+ });
3895
+ table.appendChild(headerTr);
3896
+ dataLines.forEach((row, idx) => {
3234
3897
  const tr = document.createElement("tr");
3898
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3235
3899
  const cells = row.split(delimiter);
3236
3900
  cells.forEach((cell) => {
3237
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3901
+ const td = document.createElement("td");
3238
3902
  td.textContent = cell.trim();
3239
3903
  td.style.border = "1px solid #d0d7de";
3240
3904
  td.style.padding = "6px 12px";
3241
- if (rowIndex === 0) {
3242
- td.style.backgroundColor = "#f6f8fa";
3243
- td.style.fontWeight = "bold";
3244
- }
3905
+ td.style.whiteSpace = "nowrap";
3245
3906
  tr.appendChild(td);
3246
3907
  });
3247
3908
  table.appendChild(tr);
3248
3909
  });
3249
- container.appendChild(table);
3910
+ tableWrapper.appendChild(table);
3911
+ container.appendChild(tableWrapper);
3250
3912
  ctx.container.appendChild(container);
3251
3913
  let scale = 1;
3914
+ const calculateFitScale = () => {
3915
+ const availW = Math.max(280, container.clientWidth - 48);
3916
+ const tW = table.offsetWidth || 800;
3917
+ return Math.max(0.4, Math.min(1, availW / tW));
3918
+ };
3919
+ const applyScale = () => {
3920
+ tableWrapper.style.transform = `scale(${scale})`;
3921
+ };
3922
+ requestAnimationFrame(() => {
3923
+ scale = calculateFitScale();
3924
+ applyScale();
3925
+ });
3926
+ const ro = new ResizeObserver(() => {
3927
+ scale = calculateFitScale();
3928
+ applyScale();
3929
+ });
3930
+ ro.observe(container);
3252
3931
  const cleanup = () => {
3932
+ ro.disconnect();
3253
3933
  container.remove();
3254
3934
  ctx.container.innerHTML = "";
3255
3935
  };
3256
3936
  ctx.signal.addEventListener("abort", cleanup);
3257
3937
  return {
3258
3938
  destroy: cleanup,
3939
+ getPageCount: () => 1,
3940
+ getCurrentPage: () => 1,
3941
+ goToPage: () => {
3942
+ },
3943
+ getThumbnails: () => {
3944
+ const sheetName = ctx.metadata.name?.replace(/\.[^/.]+$/, "") || "Sheet1";
3945
+ return [{
3946
+ index: 0,
3947
+ label: sheetName,
3948
+ render: async (canvas) => {
3949
+ canvas.width = 140;
3950
+ canvas.height = 100;
3951
+ const c = canvas.getContext("2d");
3952
+ if (!c) return;
3953
+ c.fillStyle = "#ffffff";
3954
+ c.fillRect(0, 0, canvas.width, canvas.height);
3955
+ c.fillStyle = "#107c41";
3956
+ c.fillRect(0, 0, canvas.width, 16);
3957
+ c.fillStyle = "#ffffff";
3958
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3959
+ c.fillText(`\u{1F4CA} ${sheetName.slice(0, 16)}`, 6, 11);
3960
+ const startY = 17;
3961
+ const rowH = 13;
3962
+ const colW = 30;
3963
+ const cols = headerCells.slice(0, 4).map((h2) => h2.trim() || "Col");
3964
+ c.fillStyle = "#f1f5f9";
3965
+ c.fillRect(0, startY, canvas.width, rowH);
3966
+ c.strokeStyle = "#cbd5e1";
3967
+ c.lineWidth = 0.8;
3968
+ c.strokeRect(0, startY, canvas.width, rowH);
3969
+ c.fillStyle = "#64748b";
3970
+ c.font = "bold 7px sans-serif";
3971
+ for (let ci = 0; ci < cols.length; ci++) {
3972
+ c.fillText(cols[ci].slice(0, 5), 14 + ci * colW, startY + 9);
3973
+ }
3974
+ for (let ri = 0; ri < Math.min(5, dataLines.length); ri++) {
3975
+ const y = startY + (ri + 1) * rowH;
3976
+ if (y > canvas.height - 2) break;
3977
+ c.fillStyle = "#f8fafc";
3978
+ c.fillRect(0, y, 12, rowH);
3979
+ c.fillStyle = "#94a3b8";
3980
+ c.font = "6px sans-serif";
3981
+ c.fillText(String(ri + 1), 3, y + 9);
3982
+ c.strokeStyle = "#e2e8f0";
3983
+ c.strokeRect(0, y, canvas.width, rowH);
3984
+ const rowCells = dataLines[ri].split(delimiter);
3985
+ c.fillStyle = "#334155";
3986
+ c.font = "6px sans-serif";
3987
+ for (let ci = 0; ci < cols.length; ci++) {
3988
+ const val = (rowCells[ci] || "").trim();
3989
+ if (val) {
3990
+ c.fillText(val.slice(0, 6), 14 + ci * colW, y + 9);
3991
+ }
3992
+ }
3993
+ }
3994
+ c.strokeStyle = "#cbd5e1";
3995
+ c.lineWidth = 1;
3996
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3997
+ }
3998
+ }];
3999
+ },
3259
4000
  zoomIn: () => {
3260
4001
  scale += 0.1;
3261
- container.style.transform = `scale(${scale})`;
4002
+ applyScale();
3262
4003
  },
3263
4004
  zoomOut: () => {
3264
4005
  scale = Math.max(0.2, scale - 0.1);
3265
- container.style.transform = `scale(${scale})`;
4006
+ applyScale();
3266
4007
  },
3267
4008
  getZoom: () => scale,
3268
4009
  setZoom: (level) => {
3269
4010
  scale = level;
3270
- container.style.transform = `scale(${scale})`;
4011
+ applyScale();
3271
4012
  },
3272
4013
  fitToPage: () => {
3273
- scale = 1;
3274
- container.style.transform = `scale(1)`;
4014
+ scale = calculateFitScale();
4015
+ applyScale();
4016
+ },
4017
+ rotateCW: () => {
4018
+ },
4019
+ rotateCCW: () => {
3275
4020
  },
3276
4021
  download: () => {
3277
4022
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3333,30 +4078,36 @@ var CodePlugin = class {
3333
4078
  return false;
3334
4079
  }
3335
4080
  getToolbarActions(instance) {
3336
- const totalPages = instance.getPageCount?.() ?? 1;
3337
- const actions = [];
3338
- if (totalPages > 1) {
3339
- actions.push({
3340
- id: "page-nav",
3341
- icon: "",
3342
- label: "Page Navigation",
3343
- type: "page-nav",
3344
- group: "navigation",
3345
- value: instance.getCurrentPage?.() ?? 1,
3346
- max: totalPages,
3347
- execute: (action, page) => {
3348
- const cur = instance.getCurrentPage?.() ?? 1;
3349
- const max = instance.getPageCount?.() ?? 1;
3350
- if (action === "prev") {
3351
- if (cur > 1) instance.goToPage?.(cur - 1);
3352
- } else if (action === "next") {
3353
- if (cur < max) instance.goToPage?.(cur + 1);
3354
- } else if (typeof page === "number") {
3355
- instance.goToPage?.(page);
3356
- }
4081
+ const totalPages = instance.getPageCount?.() ?? 1;
4082
+ const actions = [];
4083
+ actions.push({
4084
+ id: "thumbnails",
4085
+ icon: "thumbnails",
4086
+ label: "Page Thumbnails",
4087
+ type: "button",
4088
+ group: "navigation",
4089
+ execute: () => instance.toggleThumbnails?.()
4090
+ });
4091
+ actions.push({
4092
+ id: "page-nav",
4093
+ icon: "",
4094
+ label: "Page Navigation",
4095
+ type: "page-nav",
4096
+ group: "navigation",
4097
+ value: instance.getCurrentPage?.() ?? 1,
4098
+ max: totalPages,
4099
+ execute: (action, page) => {
4100
+ const cur = instance.getCurrentPage?.() ?? 1;
4101
+ const max = instance.getPageCount?.() ?? 1;
4102
+ if (action === "prev") {
4103
+ if (cur > 1) instance.goToPage?.(cur - 1);
4104
+ } else if (action === "next") {
4105
+ if (cur < max) instance.goToPage?.(cur + 1);
4106
+ } else if (typeof page === "number") {
4107
+ instance.goToPage?.(page);
3357
4108
  }
3358
- });
3359
- }
4109
+ }
4110
+ });
3360
4111
  actions.push(
3361
4112
  {
3362
4113
  id: "zoom-out",
@@ -3378,26 +4129,6 @@ var CodePlugin = class {
3378
4129
  instance.zoomIn?.();
3379
4130
  }
3380
4131
  },
3381
- {
3382
- id: "fit-page",
3383
- icon: "fit-page",
3384
- label: "Fit to View",
3385
- type: "button",
3386
- group: "zoom",
3387
- execute: () => {
3388
- instance.fitToPage?.();
3389
- }
3390
- },
3391
- {
3392
- id: "rotate-cw",
3393
- icon: "rotate-cw",
3394
- label: "Rotate",
3395
- type: "button",
3396
- group: "view",
3397
- execute: () => {
3398
- instance.rotateCW?.();
3399
- }
3400
- },
3401
4132
  {
3402
4133
  id: "copy",
3403
4134
  icon: "copy",
@@ -3500,16 +4231,19 @@ var CodePlugin = class {
3500
4231
  let fontSize = 13;
3501
4232
  let rotation = 0;
3502
4233
  let zoomLevel = 1;
4234
+ let isUserZoomed = false;
3503
4235
  const pre = document.createElement("pre");
3504
4236
  const code = document.createElement("code");
3505
4237
  if (isTxt) {
4238
+ container.style.overflowX = "hidden";
4239
+ container.style.overflowY = "auto";
3506
4240
  container.style.backgroundColor = "#f1f5f9";
3507
- container.style.padding = "32px";
4241
+ container.style.padding = "16px 8px";
3508
4242
  container.style.boxSizing = "border-box";
3509
4243
  container.style.display = "flex";
3510
4244
  container.style.flexDirection = "column";
3511
4245
  container.style.alignItems = "center";
3512
- pre.style.margin = "0";
4246
+ pre.style.margin = "0 auto";
3513
4247
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3514
4248
  pre.style.fontSize = "13px";
3515
4249
  pre.style.color = "#1e293b";
@@ -3521,10 +4255,12 @@ var CodePlugin = class {
3521
4255
  pre.style.minHeight = "1056px";
3522
4256
  pre.style.padding = "72px 56px";
3523
4257
  pre.style.boxSizing = "border-box";
3524
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4258
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3525
4259
  pre.style.borderRadius = "4px";
3526
4260
  pre.style.transformOrigin = "top center";
4261
+ pre.style.transition = "transform 0.15s ease";
3527
4262
  } else {
4263
+ container.style.overflow = "auto";
3528
4264
  container.style.backgroundColor = "#1e1e1e";
3529
4265
  container.style.color = "#d4d4d4";
3530
4266
  container.style.padding = "16px";
@@ -3556,42 +4292,9 @@ var CodePlugin = class {
3556
4292
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3557
4293
  pre.appendChild(code);
3558
4294
  container.appendChild(pre);
3559
- let indicator = null;
3560
- if (totalPages > 1) {
3561
- indicator = document.createElement("div");
3562
- indicator.className = "fp-code-page-indicator";
3563
- indicator.style.position = "sticky";
3564
- indicator.style.bottom = "16px";
3565
- if (isTxt) {
3566
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3567
- indicator.style.color = "#334155";
3568
- indicator.style.border = "1px solid #e2e8f0";
3569
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3570
- } else {
3571
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3572
- indicator.style.color = "#f8fafc";
3573
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3574
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3575
- indicator.style.backdropFilter = "blur(8px)";
3576
- }
3577
- indicator.style.fontSize = "12px";
3578
- indicator.style.fontWeight = "600";
3579
- indicator.style.padding = "5px 14px";
3580
- indicator.style.borderRadius = "20px";
3581
- indicator.style.zIndex = "10";
3582
- indicator.style.userSelect = "none";
3583
- indicator.style.pointerEvents = "none";
3584
- indicator.style.textAlign = "center";
3585
- indicator.style.width = "fit-content";
3586
- indicator.style.margin = "16px auto 0";
3587
- container.appendChild(indicator);
3588
- }
3589
4295
  const showPage = (pageNum) => {
3590
4296
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3591
4297
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3592
- if (indicator) {
3593
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3594
- }
3595
4298
  container.scrollTop = 0;
3596
4299
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3597
4300
  };
@@ -3599,36 +4302,90 @@ var CodePlugin = class {
3599
4302
  showPage(1);
3600
4303
  }
3601
4304
  ctx.container.appendChild(container);
3602
- const cleanup = () => {
3603
- indicator?.remove();
3604
- container.remove();
3605
- ctx.container.innerHTML = "";
4305
+ const calculateTxtFit = () => {
4306
+ const availW = Math.max(280, container.clientWidth - 32);
4307
+ return Math.max(0.4, Math.min(3, availW / 816));
3606
4308
  };
3607
- ctx.signal.addEventListener("abort", cleanup);
3608
4309
  const updateTransform = () => {
3609
4310
  if (isTxt) {
3610
4311
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4312
+ const scaledH = 1056 * zoomLevel;
4313
+ const extraH = Math.max(0, scaledH - 1056);
4314
+ pre.style.marginBottom = `${extraH + 32}px`;
3611
4315
  } else {
3612
4316
  pre.style.transform = `rotate(${rotation}deg)`;
3613
4317
  pre.style.fontSize = `${fontSize}px`;
3614
4318
  }
3615
4319
  };
4320
+ let resizeObserver = null;
4321
+ if (isTxt) {
4322
+ setTimeout(() => {
4323
+ zoomLevel = calculateTxtFit();
4324
+ updateTransform();
4325
+ }, 60);
4326
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4327
+ if (!isUserZoomed) {
4328
+ zoomLevel = calculateTxtFit();
4329
+ updateTransform();
4330
+ }
4331
+ }) : null;
4332
+ resizeObserver?.observe(container);
4333
+ }
4334
+ const cleanup = () => {
4335
+ resizeObserver?.disconnect();
4336
+ container.remove();
4337
+ ctx.container.innerHTML = "";
4338
+ };
4339
+ ctx.signal.addEventListener("abort", cleanup);
3616
4340
  return {
3617
4341
  destroy: cleanup,
3618
4342
  getPageCount: () => totalPages,
3619
4343
  getCurrentPage: () => currentPage,
3620
4344
  goToPage: (page) => showPage(page),
4345
+ getThumbnails: () => {
4346
+ return rawPages.map((pageText, idx) => ({
4347
+ index: idx,
4348
+ label: `Page ${idx + 1}`,
4349
+ render: async (canvas) => {
4350
+ canvas.width = 140;
4351
+ canvas.height = 180;
4352
+ const c = canvas.getContext("2d");
4353
+ if (!c) return;
4354
+ c.fillStyle = "#ffffff";
4355
+ c.fillRect(0, 0, canvas.width, canvas.height);
4356
+ c.strokeStyle = "#cbd5e1";
4357
+ c.lineWidth = 1;
4358
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4359
+ c.fillStyle = "#64748b";
4360
+ const lines = (pageText || "").split("\n").slice(0, 24);
4361
+ const lineH = 6;
4362
+ const startY = 14;
4363
+ const startX = 12;
4364
+ const maxW = canvas.width - 24;
4365
+ c.font = "5px monospace";
4366
+ for (let li = 0; li < lines.length; li++) {
4367
+ const l = lines[li].trim();
4368
+ if (!l) continue;
4369
+ const y = startY + li * lineH;
4370
+ if (y > canvas.height - 12) break;
4371
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4372
+ }
4373
+ }
4374
+ }));
4375
+ },
3621
4376
  zoomIn: () => {
4377
+ isUserZoomed = true;
3622
4378
  if (isTxt) {
3623
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4379
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3624
4380
  } else {
3625
4381
  fontSize = Math.min(32, fontSize + 2);
3626
4382
  }
3627
4383
  updateTransform();
3628
4384
  },
3629
4385
  zoomOut: () => {
4386
+ isUserZoomed = true;
3630
4387
  if (isTxt) {
3631
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4388
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3632
4389
  } else {
3633
4390
  fontSize = Math.max(8, fontSize - 2);
3634
4391
  }
@@ -3636,6 +4393,7 @@ var CodePlugin = class {
3636
4393
  },
3637
4394
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3638
4395
  setZoom: (level) => {
4396
+ isUserZoomed = true;
3639
4397
  if (isTxt) {
3640
4398
  zoomLevel = level;
3641
4399
  } else {
@@ -3644,9 +4402,10 @@ var CodePlugin = class {
3644
4402
  updateTransform();
3645
4403
  },
3646
4404
  fitToPage: () => {
4405
+ isUserZoomed = false;
3647
4406
  fontSize = 13;
3648
4407
  rotation = 0;
3649
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4408
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3650
4409
  updateTransform();
3651
4410
  },
3652
4411
  rotateCW: () => {
@@ -4513,6 +5272,148 @@ var ThreeDPlugin = class {
4513
5272
  function threeDPlugin() {
4514
5273
  return new ThreeDPlugin();
4515
5274
  }
5275
+ function cleanRtfText(raw) {
5276
+ return raw.replace(/\\bin\d+\s/g, "").replace(/{\\\*[^}]+}/g, "").replace(/\\u(-?\d+)\??/g, (_, code) => {
5277
+ let num = parseInt(code, 10);
5278
+ if (num < 0) num += 65536;
5279
+ return String.fromCharCode(num);
5280
+ }).replace(/\\'([0-9a-fA-F]{2})/g, (_, hex) => {
5281
+ return String.fromCharCode(parseInt(hex, 16));
5282
+ }).replace(/\\tab\b/g, " ").replace(/\\emdash\b/g, "\u2014").replace(/\\endash\b/g, "\u2013").replace(/\\bullet\b/g, "\u2022").replace(/\\lquote\b/g, "\u2018").replace(/\\rquote\b/g, "\u2019").replace(/\\ldblquote\b/g, "\u201C").replace(/\\rdblquote\b/g, "\u201D").replace(/\\\w+(?:-?\d+)?\s?/g, " ").replace(/[{}\r\n]/g, " ").replace(/\s+/g, " ").trim();
5283
+ }
5284
+ function parseRtfTables(rtfText) {
5285
+ const rowSegments = rtfText.split(/\\row\b/);
5286
+ if (rowSegments.length <= 1) return [];
5287
+ const rawTables = [];
5288
+ let currentTable = null;
5289
+ let charOffset = 0;
5290
+ for (let i = 0; i < rowSegments.length - 1; i++) {
5291
+ const segment = rowSegments[i];
5292
+ const segmentStart = charOffset;
5293
+ const segmentEnd = charOffset + segment.length + 4;
5294
+ const cellParts = segment.split(/\\cell\b/);
5295
+ if (cellParts.length > 1) {
5296
+ const cellxMatches = [...segment.matchAll(/\\cellx(\d+)/g)].map((m) => parseInt(m[1], 10));
5297
+ let cellWidths = [];
5298
+ if (cellxMatches.length > 0) {
5299
+ let seq = [cellxMatches[0]];
5300
+ for (let k = 1; k < cellxMatches.length; k++) {
5301
+ if (cellxMatches[k] > cellxMatches[k - 1]) seq.push(cellxMatches[k]);
5302
+ else break;
5303
+ }
5304
+ let prev = 0;
5305
+ cellWidths = seq.map((x) => {
5306
+ const w = x - prev;
5307
+ prev = x;
5308
+ return w;
5309
+ });
5310
+ }
5311
+ const cells = [];
5312
+ for (let c = 0; c < cellParts.length - 1; c++) {
5313
+ let rawCell = cellParts[c];
5314
+ if (c === 0) {
5315
+ const lastTrowd = rawCell.lastIndexOf("\\trowd");
5316
+ if (lastTrowd !== -1) rawCell = rawCell.slice(lastTrowd);
5317
+ }
5318
+ cells.push(cleanRtfText(rawCell));
5319
+ }
5320
+ const isNewTable = !currentTable || segmentStart - currentTable.lastEnd > 2500;
5321
+ if (isNewTable) {
5322
+ const prevTextSlice = rtfText.slice(Math.max(0, segmentStart - 4e3), segmentStart);
5323
+ const cleanPrev = cleanRtfText(prevTextSlice);
5324
+ const anchorBefore = cleanPrev.slice(-80).trim();
5325
+ currentTable = {
5326
+ id: `RTF_TABLE_${rawTables.length}`,
5327
+ startPos: segmentStart,
5328
+ lastEnd: segmentEnd,
5329
+ rows: [],
5330
+ columnWidths: cellWidths,
5331
+ anchorBefore,
5332
+ anchorAfter: ""
5333
+ };
5334
+ rawTables.push(currentTable);
5335
+ }
5336
+ if (currentTable) {
5337
+ currentTable.rows.push({
5338
+ cells,
5339
+ widths: cellWidths.length === cells.length ? cellWidths : currentTable.columnWidths
5340
+ });
5341
+ currentTable.lastEnd = segmentEnd;
5342
+ }
5343
+ }
5344
+ charOffset += segment.length + 4;
5345
+ }
5346
+ rawTables.forEach((tbl) => {
5347
+ const afterSlice = rtfText.slice(tbl.lastEnd, Math.min(rtfText.length, tbl.lastEnd + 2e3));
5348
+ const cleanAfter = cleanRtfText(afterSlice);
5349
+ tbl.anchorAfter = cleanAfter.slice(0, 80).trim();
5350
+ });
5351
+ return rawTables.map((tbl) => {
5352
+ const totalW = (tbl.columnWidths || []).reduce((a, b) => a + b, 0);
5353
+ const colPercents = totalW > 0 ? tbl.columnWidths.map((w) => Math.round(w / totalW * 100)) : [];
5354
+ return {
5355
+ id: tbl.id,
5356
+ rows: tbl.rows.map((r) => r.cells),
5357
+ colPercents,
5358
+ anchorBefore: tbl.anchorBefore,
5359
+ anchorAfter: tbl.anchorAfter
5360
+ };
5361
+ });
5362
+ }
5363
+ function renderRtfTableElement(table) {
5364
+ const tableEl = document.createElement("table");
5365
+ tableEl.className = "fp-rtf-table";
5366
+ tableEl.style.width = "100%";
5367
+ tableEl.style.borderCollapse = "collapse";
5368
+ tableEl.style.margin = "16px 0 20px";
5369
+ tableEl.style.fontSize = "10pt";
5370
+ tableEl.style.lineHeight = "1.5";
5371
+ tableEl.style.border = "1px solid #cbd5e1";
5372
+ tableEl.style.borderRadius = "4px";
5373
+ tableEl.style.boxSizing = "border-box";
5374
+ tableEl.style.backgroundColor = "#ffffff";
5375
+ const thead = document.createElement("thead");
5376
+ const tbody = document.createElement("tbody");
5377
+ table.rows.forEach((rowCells, rIdx) => {
5378
+ const tr = document.createElement("tr");
5379
+ const isHeader = rIdx === 0;
5380
+ if (isHeader) {
5381
+ tr.style.backgroundColor = "#f8fafc";
5382
+ tr.style.borderBottom = "2px solid #cbd5e1";
5383
+ } else {
5384
+ tr.style.borderBottom = "1px solid #e2e8f0";
5385
+ if (rIdx % 2 === 0) {
5386
+ tr.style.backgroundColor = "#fcfdfe";
5387
+ }
5388
+ }
5389
+ rowCells.forEach((cellText, cIdx) => {
5390
+ const cellEl = document.createElement(isHeader ? "th" : "td");
5391
+ cellEl.style.padding = "8px 12px";
5392
+ cellEl.style.border = "1px solid #cbd5e1";
5393
+ cellEl.style.verticalAlign = "top";
5394
+ cellEl.style.color = isHeader ? "#0f172a" : "#1e293b";
5395
+ cellEl.style.fontWeight = isHeader ? "600" : "normal";
5396
+ if (table.colPercents && table.colPercents[cIdx]) {
5397
+ cellEl.style.width = `${table.colPercents[cIdx]}%`;
5398
+ }
5399
+ if (/^\d+$/.test(cellText.trim()) || cellText.trim() === "#") {
5400
+ cellEl.style.textAlign = "center";
5401
+ } else {
5402
+ cellEl.style.textAlign = "left";
5403
+ }
5404
+ cellEl.textContent = cellText;
5405
+ tr.appendChild(cellEl);
5406
+ });
5407
+ if (isHeader) {
5408
+ thead.appendChild(tr);
5409
+ } else {
5410
+ tbody.appendChild(tr);
5411
+ }
5412
+ });
5413
+ tableEl.appendChild(thead);
5414
+ tableEl.appendChild(tbody);
5415
+ return tableEl;
5416
+ }
4516
5417
  var RtfPlugin = class {
4517
5418
  id = "rtf";
4518
5419
  name = "Rich Text Format (RTF)";
@@ -4527,29 +5428,35 @@ var RtfPlugin = class {
4527
5428
  getToolbarActions(instance) {
4528
5429
  const totalPages = instance.getPageCount?.() ?? 1;
4529
5430
  const actions = [];
4530
- if (totalPages > 1) {
4531
- actions.push({
4532
- id: "page-nav",
4533
- icon: "",
4534
- label: "Page Navigation",
4535
- type: "page-nav",
4536
- group: "navigation",
4537
- value: instance.getCurrentPage?.() ?? 1,
4538
- max: totalPages,
4539
- execute: (action, page) => {
4540
- const cur = instance.getCurrentPage?.() ?? 1;
4541
- const max = instance.getPageCount?.() ?? 1;
4542
- if (action === "prev") {
4543
- if (cur > 1) instance.goToPage?.(cur - 1);
4544
- } else if (action === "next") {
4545
- if (cur < max) instance.goToPage?.(cur + 1);
4546
- } else if (typeof page === "number") {
4547
- instance.goToPage?.(page);
4548
- }
5431
+ actions.push({
5432
+ id: "page-nav",
5433
+ icon: "",
5434
+ label: "Page Navigation",
5435
+ type: "page-nav",
5436
+ group: "navigation",
5437
+ value: instance.getCurrentPage?.() ?? 1,
5438
+ max: totalPages,
5439
+ execute: (action, page) => {
5440
+ const cur = instance.getCurrentPage?.() ?? 1;
5441
+ const max = instance.getPageCount?.() ?? 1;
5442
+ if (action === "prev") {
5443
+ if (cur > 1) instance.goToPage?.(cur - 1);
5444
+ } else if (action === "next") {
5445
+ if (cur < max) instance.goToPage?.(cur + 1);
5446
+ } else if (typeof page === "number") {
5447
+ instance.goToPage?.(page);
4549
5448
  }
4550
- });
4551
- }
5449
+ }
5450
+ });
4552
5451
  actions.push(
5452
+ {
5453
+ id: "thumbnails",
5454
+ icon: "thumbnails",
5455
+ label: "Thumbnails",
5456
+ type: "button",
5457
+ group: "view",
5458
+ execute: () => instance.toggleThumbnails?.()
5459
+ },
4553
5460
  {
4554
5461
  id: "zoom-out",
4555
5462
  icon: "zoom-out",
@@ -4620,67 +5527,143 @@ var RtfPlugin = class {
4620
5527
  async render(ctx) {
4621
5528
  const wrapper = document.createElement("div");
4622
5529
  wrapper.className = "fp-rtf-wrapper";
4623
- wrapper.style.padding = "32px";
4624
- wrapper.style.maxWidth = "850px";
5530
+ wrapper.style.padding = "0";
5531
+ wrapper.style.width = "816px";
4625
5532
  wrapper.style.margin = "0 auto";
4626
- wrapper.style.backgroundColor = "#fff";
4627
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4628
- wrapper.style.borderRadius = "4px";
4629
- wrapper.style.minHeight = "100%";
5533
+ wrapper.style.boxSizing = "border-box";
5534
+ wrapper.style.display = "flex";
5535
+ wrapper.style.flexDirection = "column";
5536
+ wrapper.style.alignItems = "center";
5537
+ wrapper.style.backgroundColor = "transparent";
4630
5538
  wrapper.style.transformOrigin = "top center";
4631
- wrapper.style.transition = "transform 0.2s ease";
4632
- ctx.container.style.overflow = "auto";
4633
- ctx.container.style.padding = "24px";
5539
+ wrapper.style.transition = "transform 0.15s ease";
5540
+ ctx.container.style.overflowX = "hidden";
5541
+ ctx.container.style.overflowY = "auto";
5542
+ ctx.container.style.padding = "16px 8px";
4634
5543
  ctx.container.style.backgroundColor = "#f1f5f9";
4635
5544
  ctx.container.appendChild(wrapper);
4636
5545
  let scale = 1;
4637
5546
  let rotation = 0;
4638
5547
  let pageElements = [];
4639
- let fitMode = "width";
5548
+ let isUserZoomed = false;
5549
+ let fitMode = ctx?.options?.fitMode || "width";
4640
5550
  const calculateFitScale = (mode = fitMode) => {
4641
5551
  const activeEl = pageElements[currentPage - 1] || wrapper;
4642
- const elW = activeEl.offsetWidth || 850;
4643
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4644
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4645
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5552
+ const elW = 816;
5553
+ const singlePageH = activeEl?.offsetHeight || 1056;
5554
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5555
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4646
5556
  const sW = availW / elW;
4647
5557
  const sH = availH / singlePageH;
4648
5558
  if (mode === "page") {
4649
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5559
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4650
5560
  }
4651
- return Math.max(0.65, Math.min(1.05, sW));
5561
+ return Math.max(0.4, Math.min(3, sW));
4652
5562
  };
4653
5563
  const applyTransform = () => {
4654
5564
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4655
5565
  wrapper.style.transformOrigin = "top center";
5566
+ const activeEl = pageElements[currentPage - 1];
5567
+ const baseH = activeEl?.offsetHeight || 1056;
5568
+ const scaledH = baseH * scale;
5569
+ const extraH = Math.max(0, scaledH - baseH);
5570
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4656
5571
  };
5572
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5573
+ if (!isUserZoomed) {
5574
+ scale = calculateFitScale(fitMode);
5575
+ applyTransform();
5576
+ }
5577
+ }) : null;
5578
+ resizeObserver?.observe(ctx.container);
4657
5579
  setTimeout(() => {
4658
- fitMode = "width";
4659
- scale = calculateFitScale("width");
5580
+ scale = calculateFitScale(fitMode);
4660
5581
  applyTransform();
4661
5582
  }, 60);
4662
5583
  try {
4663
5584
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
4664
5585
  RTFJS__namespace.loggingEnabled(false);
4665
5586
  }
5587
+ const rawText = new TextDecoder("latin1").decode(ctx.buffer);
5588
+ const tables = parseRtfTables(rawText);
4666
5589
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4667
5590
  const htmlElements = await doc.render();
4668
5591
  const contentNodes = [];
4669
- for (const item of htmlElements) {
4670
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4671
- contentNodes.push(...Array.from(item.children));
4672
- } else {
4673
- contentNodes.push(item);
5592
+ const extractBlocks = (nodes) => {
5593
+ for (const item of nodes) {
5594
+ if (!item || !(item instanceof HTMLElement)) continue;
5595
+ const tag = item.tagName.toLowerCase();
5596
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5597
+ const ct = c.tagName.toLowerCase();
5598
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5599
+ });
5600
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5601
+ extractBlocks(Array.from(item.children));
5602
+ } else {
5603
+ const svgs = item.querySelectorAll("svg, img");
5604
+ svgs.forEach((s) => {
5605
+ const el = s;
5606
+ el.style.maxWidth = "100%";
5607
+ el.style.maxHeight = "360px";
5608
+ el.style.display = "block";
5609
+ el.style.margin = "12px auto";
5610
+ if (s.tagName.toLowerCase() === "svg") {
5611
+ s.removeAttribute("width");
5612
+ s.removeAttribute("height");
5613
+ el.style.width = "100%";
5614
+ el.style.maxHeight = "360px";
5615
+ }
5616
+ });
5617
+ if (tag === "p" || tag === "div") {
5618
+ item.style.display = "block";
5619
+ item.style.marginBottom = "12px";
5620
+ item.style.lineHeight = "1.6";
5621
+ }
5622
+ contentNodes.push(item);
5623
+ }
4674
5624
  }
4675
- }
5625
+ };
5626
+ extractBlocks(htmlElements);
5627
+ tables.forEach((table) => {
5628
+ const tableEl = renderRtfTableElement(table);
5629
+ let inserted = false;
5630
+ if (table.anchorBefore && table.anchorBefore.length > 10) {
5631
+ const keyword = table.anchorBefore.slice(-35).trim();
5632
+ for (let i = 0; i < contentNodes.length; i++) {
5633
+ if (contentNodes[i].textContent?.includes(keyword)) {
5634
+ contentNodes.splice(i + 1, 0, tableEl);
5635
+ inserted = true;
5636
+ break;
5637
+ }
5638
+ }
5639
+ }
5640
+ if (!inserted && table.anchorAfter && table.anchorAfter.length > 10) {
5641
+ const keyword = table.anchorAfter.slice(0, 35).trim();
5642
+ for (let i = 0; i < contentNodes.length; i++) {
5643
+ if (contentNodes[i].textContent?.includes(keyword)) {
5644
+ contentNodes.splice(i, 0, tableEl);
5645
+ inserted = true;
5646
+ break;
5647
+ }
5648
+ }
5649
+ }
5650
+ if (!inserted) {
5651
+ if (contentNodes.length > 2) {
5652
+ contentNodes.splice(2, 0, tableEl);
5653
+ } else {
5654
+ contentNodes.push(tableEl);
5655
+ }
5656
+ }
5657
+ });
4676
5658
  wrapper.innerHTML = "";
4677
5659
  contentNodes.forEach((node) => wrapper.appendChild(node));
4678
5660
  const childHeights = contentNodes.map((c) => {
4679
5661
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4680
5662
  const offH = c.offsetHeight || 0;
5663
+ const realH = Math.max(rectH, offH);
5664
+ if (realH > 0) return realH;
4681
5665
  const textLen = c.textContent?.trim().length || 0;
4682
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4683
- return Math.max(rectH, offH, estH);
5666
+ return Math.max(24, Math.ceil(textLen / 95) * 22 + 12);
4684
5667
  });
4685
5668
  wrapper.innerHTML = "";
4686
5669
  const createRtfCard = () => {
@@ -4689,35 +5672,49 @@ var RtfPlugin = class {
4689
5672
  card.style.backgroundColor = "#ffffff";
4690
5673
  card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4691
5674
  card.style.borderRadius = "4px";
4692
- card.style.padding = "72px 56px";
5675
+ card.style.padding = "64px 56px";
4693
5676
  card.style.width = "816px";
4694
5677
  card.style.minHeight = "1056px";
4695
5678
  card.style.boxSizing = "border-box";
4696
5679
  card.style.marginBottom = "24px";
4697
5680
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4698
5681
  card.style.lineHeight = "1.6";
5682
+ card.style.color = "#1e293b";
4699
5683
  return card;
4700
5684
  };
4701
5685
  let curCard = createRtfCard();
4702
5686
  wrapper.appendChild(curCard);
4703
5687
  pageElements = [curCard];
4704
5688
  let curH = 0;
4705
- const maxH = 912;
5689
+ const maxH = 928;
4706
5690
  for (let i = 0; i < contentNodes.length; i++) {
4707
5691
  const child = contentNodes[i];
4708
5692
  const chH = childHeights[i];
4709
- curCard.appendChild(child);
4710
- curH += chH;
4711
- if (curH >= maxH && i < contentNodes.length - 1) {
5693
+ const isChildEmpty = (child.textContent || "").trim().length === 0 && !child.querySelector("table, img, svg, canvas");
5694
+ if (curH === 0 && isChildEmpty) {
5695
+ continue;
5696
+ }
5697
+ if (curH + chH > maxH && curCard.childNodes.length > 0) {
4712
5698
  curCard = createRtfCard();
4713
5699
  wrapper.appendChild(curCard);
4714
5700
  pageElements.push(curCard);
4715
5701
  curH = 0;
4716
5702
  }
5703
+ curCard.appendChild(child);
5704
+ curH += chH;
5705
+ }
5706
+ pageElements = pageElements.filter((card) => {
5707
+ const hasText = (card.textContent || "").trim().length > 0;
5708
+ const hasMedia = card.querySelector("table, img, svg, canvas") !== null;
5709
+ return hasText || hasMedia;
5710
+ });
5711
+ if (pageElements.length === 0) {
5712
+ pageElements = [curCard];
4717
5713
  }
4718
5714
  } catch (err) {
4719
5715
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
4720
5716
  const text = new TextDecoder("latin1").decode(ctx.buffer);
5717
+ const tables = parseRtfTables(text);
4721
5718
  const rawPages = text.split(/\\page\b/).map((segment) => {
4722
5719
  return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4723
5720
  }).filter((p) => p.length > 0);
@@ -4728,47 +5725,26 @@ var RtfPlugin = class {
4728
5725
  pageCard.style.backgroundColor = "#ffffff";
4729
5726
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4730
5727
  pageCard.style.borderRadius = "4px";
4731
- pageCard.style.padding = "72px 56px";
5728
+ pageCard.style.padding = "64px 56px";
4732
5729
  pageCard.style.width = "816px";
4733
5730
  pageCard.style.minHeight = "1056px";
4734
- pageCard.style.maxWidth = "100%";
4735
5731
  pageCard.style.boxSizing = "border-box";
4736
- pageCard.style.fontFamily = "serif";
5732
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4737
5733
  pageCard.style.fontSize = "12pt";
4738
5734
  pageCard.style.lineHeight = "1.6";
4739
5735
  pageCard.style.color = "#1e293b";
4740
5736
  pageCard.style.whiteSpace = "pre-wrap";
4741
5737
  pageCard.style.display = i === 0 ? "block" : "none";
4742
5738
  pageCard.textContent = pages[i];
5739
+ if (i === 1 && tables.length > 0) {
5740
+ tables.forEach((tbl) => pageCard.appendChild(renderRtfTableElement(tbl)));
5741
+ }
4743
5742
  wrapper.appendChild(pageCard);
4744
5743
  pageElements.push(pageCard);
4745
5744
  }
4746
5745
  }
4747
5746
  const totalPages = Math.max(1, pageElements.length);
4748
5747
  let currentPage = 1;
4749
- let indicator = null;
4750
- if (totalPages > 1) {
4751
- indicator = document.createElement("div");
4752
- indicator.className = "fp-rtf-page-indicator";
4753
- indicator.style.position = "sticky";
4754
- indicator.style.bottom = "16px";
4755
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4756
- indicator.style.backdropFilter = "blur(8px)";
4757
- indicator.style.color = "#f8fafc";
4758
- indicator.style.fontSize = "12px";
4759
- indicator.style.fontWeight = "600";
4760
- indicator.style.padding = "5px 14px";
4761
- indicator.style.borderRadius = "20px";
4762
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4763
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4764
- indicator.style.zIndex = "10";
4765
- indicator.style.userSelect = "none";
4766
- indicator.style.pointerEvents = "none";
4767
- indicator.style.textAlign = "center";
4768
- indicator.style.width = "fit-content";
4769
- indicator.style.margin = "16px auto 0";
4770
- ctx.container.appendChild(indicator);
4771
- }
4772
5748
  const showPage = (pageNum) => {
4773
5749
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4774
5750
  if (totalPages > 1) {
@@ -4776,9 +5752,6 @@ var RtfPlugin = class {
4776
5752
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4777
5753
  });
4778
5754
  }
4779
- if (indicator) {
4780
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4781
- }
4782
5755
  ctx.container.scrollTop = 0;
4783
5756
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4784
5757
  };
@@ -4786,7 +5759,7 @@ var RtfPlugin = class {
4786
5759
  showPage(1);
4787
5760
  }
4788
5761
  const cleanup = () => {
4789
- indicator?.remove();
5762
+ resizeObserver?.disconnect();
4790
5763
  wrapper.remove();
4791
5764
  ctx.container.innerHTML = "";
4792
5765
  };
@@ -4796,22 +5769,52 @@ var RtfPlugin = class {
4796
5769
  getPageCount: () => totalPages,
4797
5770
  getCurrentPage: () => currentPage,
4798
5771
  goToPage: (page) => showPage(page),
5772
+ getThumbnails: async () => {
5773
+ const items = [];
5774
+ for (let idx = 0; idx < totalPages; idx++) {
5775
+ const pageIdx = idx;
5776
+ items.push({
5777
+ index: pageIdx,
5778
+ label: `Page ${pageIdx + 1}`,
5779
+ render: async (canvas) => {
5780
+ const ctx2d = canvas.getContext("2d");
5781
+ if (!ctx2d) return;
5782
+ canvas.width = 140;
5783
+ canvas.height = 180;
5784
+ ctx2d.fillStyle = "#ffffff";
5785
+ ctx2d.fillRect(0, 0, 140, 180);
5786
+ ctx2d.strokeStyle = "#cbd5e1";
5787
+ ctx2d.lineWidth = 1;
5788
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
5789
+ ctx2d.fillStyle = "#334155";
5790
+ ctx2d.font = 'bold 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
5791
+ ctx2d.textAlign = "center";
5792
+ ctx2d.fillText(`Page ${pageIdx + 1}`, 70, 95);
5793
+ }
5794
+ });
5795
+ }
5796
+ return items;
5797
+ },
4799
5798
  zoomIn: () => {
5799
+ isUserZoomed = true;
4800
5800
  scale += 0.15;
4801
5801
  applyTransform();
4802
5802
  },
4803
5803
  zoomOut: () => {
5804
+ isUserZoomed = true;
4804
5805
  scale = Math.max(0.2, scale - 0.15);
4805
5806
  applyTransform();
4806
5807
  },
4807
5808
  getZoom: () => scale,
4808
5809
  setZoom: (level) => {
5810
+ isUserZoomed = true;
4809
5811
  scale = level;
4810
5812
  applyTransform();
4811
5813
  },
4812
5814
  fitToPage: () => {
4813
- fitMode = fitMode === "width" ? "page" : "width";
4814
- scale = calculateFitScale(fitMode);
5815
+ isUserZoomed = false;
5816
+ fitMode = "width";
5817
+ scale = calculateFitScale("width");
4815
5818
  rotation = 0;
4816
5819
  applyTransform();
4817
5820
  },
@@ -4999,40 +6002,36 @@ var OpenDocumentPlugin = class {
4999
6002
  const isPresentation = instance.isPresentation;
5000
6003
  const totalPages = instance.getPageCount?.() ?? 1;
5001
6004
  const actions = [];
5002
- if (isPresentation || totalPages > 1) {
5003
- if (isPresentation) {
5004
- actions.push({
5005
- id: "thumbnails",
5006
- icon: "thumbnails",
5007
- label: "Slide Thumbnails",
5008
- type: "button",
5009
- group: "navigation",
5010
- execute: () => instance.toggleThumbnails?.()
5011
- });
5012
- }
5013
- actions.push({
5014
- id: "page-nav",
5015
- icon: "",
5016
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
5017
- type: "page-nav",
5018
- group: "navigation",
5019
- value: instance.getCurrentPage?.() ?? 1,
5020
- max: totalPages,
5021
- execute: (action, page) => {
5022
- const cur = instance.getCurrentPage?.() ?? 1;
5023
- const max = instance.getPageCount?.() ?? 1;
5024
- if (action === "prev") {
5025
- if (cur > 1) instance.goToPage?.(cur - 1);
5026
- } else if (action === "next") {
5027
- if (cur < max) instance.goToPage?.(cur + 1);
5028
- } else if (typeof page === "number") {
5029
- instance.goToPage?.(page);
5030
- } else if (typeof action === "number") {
5031
- instance.goToPage?.(action);
5032
- }
6005
+ actions.push({
6006
+ id: "thumbnails",
6007
+ icon: "thumbnails",
6008
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
6009
+ type: "button",
6010
+ group: "navigation",
6011
+ execute: () => instance.toggleThumbnails?.()
6012
+ });
6013
+ actions.push({
6014
+ id: "page-nav",
6015
+ icon: "",
6016
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
6017
+ type: "page-nav",
6018
+ group: "navigation",
6019
+ value: instance.getCurrentPage?.() ?? 1,
6020
+ max: totalPages,
6021
+ execute: (action, page) => {
6022
+ const cur = instance.getCurrentPage?.() ?? 1;
6023
+ const max = instance.getPageCount?.() ?? 1;
6024
+ if (action === "prev") {
6025
+ if (cur > 1) instance.goToPage?.(cur - 1);
6026
+ } else if (action === "next") {
6027
+ if (cur < max) instance.goToPage?.(cur + 1);
6028
+ } else if (typeof page === "number") {
6029
+ instance.goToPage?.(page);
6030
+ } else if (typeof action === "number") {
6031
+ instance.goToPage?.(action);
5033
6032
  }
5034
- });
5035
- }
6033
+ }
6034
+ });
5036
6035
  actions.push(
5037
6036
  {
5038
6037
  id: "zoom-out",
@@ -5123,49 +6122,65 @@ var OpenDocumentPlugin = class {
5123
6122
  container.className = "fp-odf-container";
5124
6123
  container.style.width = "100%";
5125
6124
  container.style.height = "100%";
5126
- container.style.overflow = "auto";
5127
- container.style.padding = "24px";
6125
+ container.style.overflowX = "hidden";
6126
+ container.style.overflowY = "auto";
6127
+ container.style.padding = "16px 8px";
5128
6128
  container.style.backgroundColor = "#f1f5f9";
5129
6129
  const wrapper = document.createElement("div");
5130
6130
  wrapper.className = "fp-odf-wrapper";
5131
6131
  wrapper.style.margin = "0 auto";
6132
+ wrapper.style.width = isPresentation ? "960px" : "816px";
6133
+ wrapper.style.boxSizing = "border-box";
5132
6134
  wrapper.style.backgroundColor = "#ffffff";
5133
6135
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5134
6136
  wrapper.style.borderRadius = "4px";
5135
6137
  wrapper.style.transformOrigin = "top center";
5136
- wrapper.style.transition = "transform 0.2s ease";
6138
+ wrapper.style.transition = "transform 0.15s ease";
5137
6139
  container.appendChild(wrapper);
5138
6140
  ctx.container.appendChild(container);
5139
6141
  let scale = 1;
5140
6142
  let rotation = 0;
5141
- let fitMode = "width";
6143
+ let isUserZoomed = false;
6144
+ let fitMode = ctx?.options?.fitMode || "width";
6145
+ let currentPage = 1;
6146
+ let totalPages = 1;
6147
+ let slides = [];
5142
6148
  const calculateFitScale = (mode = fitMode) => {
5143
- const elW = wrapper.offsetWidth || 850;
5144
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5145
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5146
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6149
+ const activeSlide = slides[currentPage - 1];
6150
+ const elW = isPresentation ? 960 : 816;
6151
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
6152
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6153
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5147
6154
  const sW = availW / elW;
5148
6155
  const sH = availH / (isPresentation ? 540 : singlePageH);
5149
6156
  if (isPresentation) {
5150
- return Math.min(1, Math.min(sW, sH));
6157
+ return Math.min(2.5, Math.min(sW, sH));
5151
6158
  }
5152
6159
  if (mode === "page") {
5153
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6160
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5154
6161
  }
5155
- return Math.max(0.65, Math.min(1.05, sW));
6162
+ return Math.max(0.4, Math.min(3, sW));
5156
6163
  };
5157
6164
  const applyTransform = () => {
5158
6165
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5159
6166
  wrapper.style.transformOrigin = "top center";
6167
+ const activeSlide = slides[currentPage - 1];
6168
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6169
+ const scaledH = baseH * scale;
6170
+ const extraH = Math.max(0, scaledH - baseH);
6171
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5160
6172
  };
6173
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6174
+ if (!isUserZoomed) {
6175
+ scale = calculateFitScale(fitMode);
6176
+ applyTransform();
6177
+ }
6178
+ }) : null;
6179
+ resizeObserver?.observe(ctx.container);
5161
6180
  setTimeout(() => {
5162
- fitMode = "width";
5163
- scale = calculateFitScale("width");
6181
+ scale = calculateFitScale(fitMode);
5164
6182
  applyTransform();
5165
6183
  }, 60);
5166
- let currentPage = 1;
5167
- let totalPages = 1;
5168
- let slides = [];
5169
6184
  if (isPresentation) {
5170
6185
  wrapper.style.maxWidth = "960px";
5171
6186
  wrapper.style.aspectRatio = "16 / 9";
@@ -5266,24 +6281,9 @@ var OpenDocumentPlugin = class {
5266
6281
  wrapper.appendChild(page);
5267
6282
  slides.push(page);
5268
6283
  });
5269
- const pageIndicator = document.createElement("div");
5270
- pageIndicator.className = "fp-odt-page-indicator";
5271
- pageIndicator.style.position = "sticky";
5272
- pageIndicator.style.bottom = "16px";
5273
- pageIndicator.style.left = "50%";
5274
- pageIndicator.style.transform = "translateX(-50%)";
5275
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5276
- pageIndicator.style.color = "#fff";
5277
- pageIndicator.style.padding = "6px 12px";
5278
- pageIndicator.style.borderRadius = "16px";
5279
- pageIndicator.style.fontSize = "12px";
5280
- pageIndicator.style.zIndex = "100";
5281
- pageIndicator.style.display = "inline-block";
5282
- pageIndicator.style.width = "fit-content";
5283
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5284
- container.appendChild(pageIndicator);
5285
6284
  }
5286
6285
  const cleanup = () => {
6286
+ resizeObserver?.disconnect();
5287
6287
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5288
6288
  container.remove();
5289
6289
  ctx.container.innerHTML = "";
@@ -5295,10 +6295,6 @@ var OpenDocumentPlugin = class {
5295
6295
  slides.forEach((s, idx) => {
5296
6296
  s.style.display = idx === page - 1 ? "block" : "none";
5297
6297
  });
5298
- const indicator = container.querySelector(".fp-odt-page-indicator");
5299
- if (indicator) {
5300
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5301
- }
5302
6298
  container.scrollTop = 0;
5303
6299
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5304
6300
  };
@@ -5306,21 +6302,25 @@ var OpenDocumentPlugin = class {
5306
6302
  destroy: cleanup,
5307
6303
  isPresentation,
5308
6304
  zoomIn: () => {
6305
+ isUserZoomed = true;
5309
6306
  scale += 0.15;
5310
6307
  applyTransform();
5311
6308
  },
5312
6309
  zoomOut: () => {
6310
+ isUserZoomed = true;
5313
6311
  scale = Math.max(0.2, scale - 0.15);
5314
6312
  applyTransform();
5315
6313
  },
5316
6314
  getZoom: () => scale,
5317
6315
  setZoom: (level) => {
6316
+ isUserZoomed = true;
5318
6317
  scale = level;
5319
6318
  applyTransform();
5320
6319
  },
5321
6320
  fitToPage: () => {
5322
- fitMode = fitMode === "width" ? "page" : "width";
5323
- scale = calculateFitScale(fitMode);
6321
+ isUserZoomed = false;
6322
+ fitMode = "width";
6323
+ scale = calculateFitScale("width");
5324
6324
  rotation = 0;
5325
6325
  applyTransform();
5326
6326
  },
@@ -5336,23 +6336,45 @@ var OpenDocumentPlugin = class {
5336
6336
  getPageCount: () => totalPages,
5337
6337
  getCurrentPage: () => currentPage,
5338
6338
  getThumbnails: async () => {
5339
- if (!isPresentation) return [];
5340
- return slides.map((_, idx) => ({
5341
- index: idx,
5342
- label: `Slide ${idx + 1}`,
5343
- render: async (canvas) => {
5344
- const ctx2d = canvas.getContext("2d");
5345
- if (!ctx2d) return;
5346
- canvas.width = 160;
5347
- canvas.height = 90;
5348
- ctx2d.fillStyle = "#f8fafc";
5349
- ctx2d.fillRect(0, 0, 160, 90);
5350
- ctx2d.fillStyle = "#334155";
5351
- ctx2d.font = "bold 12px sans-serif";
5352
- ctx2d.textAlign = "center";
5353
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5354
- }
5355
- }));
6339
+ const count = totalPages || slides.length || 1;
6340
+ const items = [];
6341
+ for (let idx = 0; idx < count; idx++) {
6342
+ const itemIdx = idx;
6343
+ items.push({
6344
+ index: itemIdx,
6345
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6346
+ render: async (canvas) => {
6347
+ const ctx2d = canvas.getContext("2d");
6348
+ if (!ctx2d) return;
6349
+ if (isPresentation) {
6350
+ canvas.width = 160;
6351
+ canvas.height = 90;
6352
+ ctx2d.fillStyle = "#f8fafc";
6353
+ ctx2d.fillRect(0, 0, 160, 90);
6354
+ ctx2d.strokeStyle = "#cbd5e1";
6355
+ ctx2d.lineWidth = 1;
6356
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6357
+ ctx2d.fillStyle = "#334155";
6358
+ ctx2d.font = "bold 11px sans-serif";
6359
+ ctx2d.textAlign = "center";
6360
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6361
+ } else {
6362
+ canvas.width = 140;
6363
+ canvas.height = 180;
6364
+ ctx2d.fillStyle = "#ffffff";
6365
+ ctx2d.fillRect(0, 0, 140, 180);
6366
+ ctx2d.strokeStyle = "#cbd5e1";
6367
+ ctx2d.lineWidth = 1;
6368
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6369
+ ctx2d.fillStyle = "#64748b";
6370
+ ctx2d.font = "bold 10px sans-serif";
6371
+ ctx2d.textAlign = "center";
6372
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6373
+ }
6374
+ }
6375
+ });
6376
+ }
6377
+ return items;
5356
6378
  },
5357
6379
  download: () => {
5358
6380
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5457,23 +6479,24 @@ var OpenDocumentPlugin = class {
5457
6479
  case "h": {
5458
6480
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5459
6481
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5460
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6482
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5461
6483
  break;
5462
6484
  }
5463
6485
  case "p": {
5464
6486
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5465
6487
  const inner = this.renderSpans(node, styles, images);
5466
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6488
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6489
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5467
6490
  break;
5468
6491
  }
5469
6492
  case "list": {
5470
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6493
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5471
6494
  Array.from(node.children).forEach((c) => walk(c));
5472
6495
  html += "</ul>";
5473
6496
  break;
5474
6497
  }
5475
6498
  case "list-item": {
5476
- html += "<li>";
6499
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5477
6500
  Array.from(node.children).forEach((c) => walk(c));
5478
6501
  html += "</li>";
5479
6502
  break;
@@ -5599,28 +6622,26 @@ var DocPlugin = class {
5599
6622
  getToolbarActions(instance) {
5600
6623
  const totalPages = instance.getPageCount?.() ?? 1;
5601
6624
  const actions = [];
5602
- if (totalPages > 1) {
5603
- actions.push({
5604
- id: "page-nav",
5605
- icon: "",
5606
- label: "Page Navigation",
5607
- type: "page-nav",
5608
- group: "navigation",
5609
- value: instance.getCurrentPage?.() ?? 1,
5610
- max: totalPages,
5611
- execute: (action, page) => {
5612
- const cur = instance.getCurrentPage?.() ?? 1;
5613
- const max = instance.getPageCount?.() ?? 1;
5614
- if (action === "prev") {
5615
- if (cur > 1) instance.goToPage?.(cur - 1);
5616
- } else if (action === "next") {
5617
- if (cur < max) instance.goToPage?.(cur + 1);
5618
- } else if (typeof page === "number") {
5619
- instance.goToPage?.(page);
5620
- }
6625
+ actions.push({
6626
+ id: "page-nav",
6627
+ icon: "",
6628
+ label: "Page Navigation",
6629
+ type: "page-nav",
6630
+ group: "navigation",
6631
+ value: instance.getCurrentPage?.() ?? 1,
6632
+ max: totalPages,
6633
+ execute: (action, page) => {
6634
+ const cur = instance.getCurrentPage?.() ?? 1;
6635
+ const max = instance.getPageCount?.() ?? 1;
6636
+ if (action === "prev") {
6637
+ if (cur > 1) instance.goToPage?.(cur - 1);
6638
+ } else if (action === "next") {
6639
+ if (cur < max) instance.goToPage?.(cur + 1);
6640
+ } else if (typeof page === "number") {
6641
+ instance.goToPage?.(page);
5621
6642
  }
5622
- });
5623
- }
6643
+ }
6644
+ });
5624
6645
  actions.push(
5625
6646
  {
5626
6647
  id: "zoom-out",
@@ -5694,8 +6715,9 @@ var DocPlugin = class {
5694
6715
  container.className = "fp-doc-container";
5695
6716
  container.style.width = "100%";
5696
6717
  container.style.height = "100%";
5697
- container.style.overflow = "auto";
5698
- container.style.padding = "32px 16px";
6718
+ container.style.overflowX = "hidden";
6719
+ container.style.overflowY = "auto";
6720
+ container.style.padding = "16px 8px";
5699
6721
  container.style.backgroundColor = "#f1f5f9";
5700
6722
  container.style.display = "flex";
5701
6723
  container.style.justifyContent = "center";
@@ -5740,16 +6762,15 @@ var DocPlugin = class {
5740
6762
  const pageCard = document.createElement("div");
5741
6763
  pageCard.className = "fp-doc-page-card";
5742
6764
  pageCard.style.width = "816px";
5743
- pageCard.style.height = "1056px";
6765
+ pageCard.style.minHeight = "1056px";
5744
6766
  pageCard.style.backgroundColor = "#ffffff";
5745
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6767
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5746
6768
  pageCard.style.borderRadius = "4px";
5747
- pageCard.style.padding = "96px 72px";
6769
+ pageCard.style.padding = "72px 56px";
5748
6770
  pageCard.style.boxSizing = "border-box";
5749
- pageCard.style.overflow = "hidden";
5750
6771
  pageCard.style.display = i === 0 ? "block" : "none";
5751
6772
  pageCard.style.transformOrigin = "top center";
5752
- pageCard.style.transition = "transform 0.2s ease";
6773
+ pageCard.style.transition = "transform 0.15s ease";
5753
6774
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5754
6775
  pageCard.style.color = "#1e293b";
5755
6776
  if (isFallback && i === 0) {
@@ -5766,34 +6787,15 @@ var DocPlugin = class {
5766
6787
  container.appendChild(pageCard);
5767
6788
  pageCards.push(pageCard);
5768
6789
  }
5769
- let indicator = null;
5770
- if (totalPages > 1) {
5771
- indicator = document.createElement("div");
5772
- indicator.className = "fp-doc-page-indicator";
5773
- indicator.style.position = "fixed";
5774
- indicator.style.bottom = "16px";
5775
- indicator.style.left = "50%";
5776
- indicator.style.transform = "translateX(-50%)";
5777
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5778
- indicator.style.backdropFilter = "blur(8px)";
5779
- indicator.style.color = "#f8fafc";
5780
- indicator.style.fontSize = "12px";
5781
- indicator.style.fontWeight = "600";
5782
- indicator.style.padding = "5px 14px";
5783
- indicator.style.borderRadius = "20px";
5784
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5785
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5786
- indicator.style.zIndex = "10";
5787
- indicator.style.userSelect = "none";
5788
- indicator.style.pointerEvents = "none";
5789
- indicator.style.textAlign = "center";
5790
- container.appendChild(indicator);
5791
- }
5792
6790
  let rotation = 0;
5793
6791
  const applyTransform = () => {
5794
6792
  const activeCard = pageCards[currentPage - 1];
5795
6793
  if (activeCard) {
5796
6794
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6795
+ const baseH = activeCard.offsetHeight || 1056;
6796
+ const scaledH = baseH * scale;
6797
+ const extraH = Math.max(0, scaledH - baseH);
6798
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5797
6799
  }
5798
6800
  };
5799
6801
  const showPage = (pageNum) => {
@@ -5806,35 +6808,39 @@ var DocPlugin = class {
5806
6808
  card.style.display = "none";
5807
6809
  }
5808
6810
  });
5809
- if (indicator) {
5810
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5811
- }
5812
6811
  container.scrollTop = 0;
5813
6812
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5814
6813
  };
5815
6814
  if (totalPages > 1) {
5816
6815
  showPage(1);
5817
6816
  }
5818
- let fitMode = "width";
6817
+ let fitMode = ctx?.options?.fitMode || "width";
6818
+ let isUserZoomed = false;
5819
6819
  const calculateFitScale = (mode = fitMode) => {
5820
6820
  const elW = 816;
5821
6821
  const elH = 1056;
5822
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5823
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6822
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6823
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5824
6824
  const sW = availW / elW;
5825
6825
  const sH = availH / elH;
5826
6826
  if (mode === "page") {
5827
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6827
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5828
6828
  }
5829
- return Math.max(0.65, Math.min(1.05, sW));
6829
+ return Math.max(0.4, Math.min(3, sW));
5830
6830
  };
6831
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6832
+ if (!isUserZoomed) {
6833
+ scale = calculateFitScale(fitMode);
6834
+ applyTransform();
6835
+ }
6836
+ }) : null;
6837
+ resizeObserver?.observe(ctx.container);
5831
6838
  setTimeout(() => {
5832
- fitMode = "width";
5833
- scale = calculateFitScale("width");
6839
+ scale = calculateFitScale(fitMode);
5834
6840
  applyTransform();
5835
6841
  }, 60);
5836
6842
  const cleanup = () => {
5837
- indicator?.remove();
6843
+ resizeObserver?.disconnect();
5838
6844
  container.remove();
5839
6845
  ctx.container.innerHTML = "";
5840
6846
  };
@@ -5845,21 +6851,25 @@ var DocPlugin = class {
5845
6851
  getCurrentPage: () => currentPage,
5846
6852
  goToPage: (page) => showPage(page),
5847
6853
  zoomIn: () => {
6854
+ isUserZoomed = true;
5848
6855
  scale += 0.15;
5849
6856
  applyTransform();
5850
6857
  },
5851
6858
  zoomOut: () => {
6859
+ isUserZoomed = true;
5852
6860
  scale = Math.max(0.2, scale - 0.15);
5853
6861
  applyTransform();
5854
6862
  },
5855
6863
  getZoom: () => scale,
5856
6864
  setZoom: (level) => {
6865
+ isUserZoomed = true;
5857
6866
  scale = level;
5858
6867
  applyTransform();
5859
6868
  },
5860
6869
  fitToPage: () => {
5861
- fitMode = fitMode === "width" ? "page" : "width";
5862
- scale = calculateFitScale(fitMode);
6870
+ isUserZoomed = false;
6871
+ fitMode = "width";
6872
+ scale = calculateFitScale("width");
5863
6873
  rotation = 0;
5864
6874
  applyTransform();
5865
6875
  },
@@ -6236,25 +7246,31 @@ ${chartSvg}
6236
7246
  i++;
6237
7247
  continue;
6238
7248
  }
6239
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7249
+ if (/^\d{1,2}$/.test(line.trim())) {
7250
+ i++;
7251
+ continue;
7252
+ }
7253
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7254
+ const isTitleLike = isShortLine && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#") || /^(?:Chapter|Section|Part|\d+\.)\b/i.test(line) || /^[A-Z][a-zA-Z0-9\s#:-]{2,45}$/.test(line));
7255
+ if (isTitleLike) {
6240
7256
  if (inList) {
6241
7257
  html += "</ul>";
6242
7258
  inList = false;
6243
7259
  }
6244
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</h2>`;
7260
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 18px 0 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</h2>`;
6245
7261
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6246
7262
  if (!inList) {
6247
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7263
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6248
7264
  inList = true;
6249
7265
  }
6250
7266
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6251
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
7267
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
6252
7268
  } else {
6253
7269
  if (inList) {
6254
7270
  html += "</ul>";
6255
7271
  inList = false;
6256
7272
  }
6257
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
7273
+ html += `<p style="line-height: 1.5; margin: 0 0 12px 0; font-size: 11pt; color: #1e293b; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
6258
7274
  }
6259
7275
  i++;
6260
7276
  }
@@ -6824,7 +7840,7 @@ var FilePreview = vue.defineComponent({
6824
7840
  default: () => ({})
6825
7841
  }
6826
7842
  },
6827
- emits: ["loading", "loaded", "error", "page-change", "zoom-change"],
7843
+ emits: ["loading", "loaded", "error", "page-change", "zoom-change", "rotate", "destroy"],
6828
7844
  setup(props, { emit, expose }) {
6829
7845
  const containerRef = vue.ref(null);
6830
7846
  let viewer = null;
@@ -6848,6 +7864,8 @@ var FilePreview = vue.defineComponent({
6848
7864
  viewer.on("error", (data) => emit("error", data));
6849
7865
  viewer.on("page-change", (data) => emit("page-change", data));
6850
7866
  viewer.on("zoom-change", (data) => emit("zoom-change", data));
7867
+ viewer.on("rotate", (data) => emit("rotate", data));
7868
+ viewer.on("destroy", (data) => emit("destroy", data));
6851
7869
  renderPreview();
6852
7870
  });
6853
7871
  vue.watch(() => props.src, renderPreview);