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