@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/index.cjs CHANGED
@@ -609,8 +609,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
609
609
  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>`;
610
610
  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>`;
611
611
  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>`;
612
- 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>`;
613
- 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>`;
612
+ 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>`;
614
613
  var ICON_MAP = {
615
614
  "zoom-in": ICON_ZOOM_IN,
616
615
  "zoom-out": ICON_ZOOM_OUT,
@@ -637,28 +636,177 @@ var ICON_MAP = {
637
636
  "forward-10": ICON_FAST_FORWARD,
638
637
  "rewind": ICON_REWIND,
639
638
  "replay-10": ICON_REWIND,
640
- "speed": ICON_SPEED,
641
639
  "open-window": ICON_EXTERNAL_WINDOW,
642
- "external-window": ICON_EXTERNAL_WINDOW
640
+ "external-window": ICON_EXTERNAL_WINDOW,
641
+ "openWindow": ICON_EXTERNAL_WINDOW,
642
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
643
643
  };
644
644
  var ToolbarController = class {
645
645
  el;
646
646
  toolbarEl;
647
647
  actions = [];
648
+ config = {};
648
649
  pageInputEl = null;
649
650
  pageLabelEl = null;
650
- constructor(container) {
651
+ prevPageBtn = null;
652
+ nextPageBtn = null;
653
+ constructor(container, config) {
651
654
  this.el = container;
655
+ if (config) this.config = { ...config };
652
656
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
653
657
  this.el.appendChild(this.toolbarEl);
654
658
  }
659
+ setConfig(config) {
660
+ this.config = { ...config };
661
+ this.render();
662
+ }
663
+ getConfig() {
664
+ return { ...this.config };
665
+ }
666
+ hideAction(actionId) {
667
+ this.config[actionId] = false;
668
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
669
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
670
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
671
+ this.config.fitPage = false;
672
+ this.config.fitToPage = false;
673
+ this.config.fitWidth = false;
674
+ }
675
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
676
+ if (actionId === "fullscreen") this.config.fullscreen = false;
677
+ if (actionId === "download") this.config.download = false;
678
+ if (actionId === "print") this.config.print = false;
679
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
680
+ this.config.openWindow = false;
681
+ this.config.openSeparateWindow = false;
682
+ }
683
+ if (actionId === "copy") this.config.copy = false;
684
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
685
+ this.config.pageNav = false;
686
+ this.config.pagination = false;
687
+ }
688
+ this.render();
689
+ }
690
+ showAction(actionId) {
691
+ this.config[actionId] = true;
692
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
693
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
694
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
695
+ this.config.fitPage = true;
696
+ this.config.fitToPage = true;
697
+ this.config.fitWidth = true;
698
+ }
699
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
700
+ if (actionId === "fullscreen") this.config.fullscreen = true;
701
+ if (actionId === "download") this.config.download = true;
702
+ if (actionId === "print") this.config.print = true;
703
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
704
+ this.config.openWindow = true;
705
+ this.config.openSeparateWindow = true;
706
+ }
707
+ if (actionId === "copy") this.config.copy = true;
708
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
709
+ this.config.pageNav = true;
710
+ this.config.pagination = true;
711
+ }
712
+ this.render();
713
+ }
714
+ normalizeActionId(actionId) {
715
+ if (actionId === "zoomIn") return "zoom-in";
716
+ if (actionId === "zoomOut") return "zoom-out";
717
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
718
+ if (actionId === "rotateCW") return "rotate-cw";
719
+ if (actionId === "rotateCCW") return "rotate-ccw";
720
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
721
+ if (actionId === "pageNav") return "page-nav";
722
+ return actionId;
723
+ }
724
+ enableAction(actionId) {
725
+ const norm = this.normalizeActionId(actionId);
726
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
727
+ if (btn) {
728
+ btn.disabled = false;
729
+ btn.classList.remove("disabled");
730
+ }
731
+ }
732
+ disableAction(actionId) {
733
+ const norm = this.normalizeActionId(actionId);
734
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
735
+ if (btn) {
736
+ btn.disabled = true;
737
+ btn.classList.add("disabled");
738
+ }
739
+ }
740
+ setActionActive(actionId, active) {
741
+ const norm = this.normalizeActionId(actionId);
742
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
743
+ if (btn) {
744
+ if (active) {
745
+ btn.classList.add("active");
746
+ } else {
747
+ btn.classList.remove("active");
748
+ }
749
+ }
750
+ }
751
+ isActionEnabled(action) {
752
+ const c = this.config;
753
+ const id = action.id;
754
+ if (c[id] === false) return false;
755
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
756
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
757
+ 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)) {
758
+ return false;
759
+ }
760
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
761
+ return false;
762
+ }
763
+ if (id === "fullscreen" && c.fullscreen === false) return false;
764
+ if (id === "download" && c.download === false) return false;
765
+ if (id === "print" && c.print === false) return false;
766
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
767
+ return false;
768
+ }
769
+ if (id === "copy" && c.copy === false) return false;
770
+ if (id === "thumbnails" && c.thumbnails === false) return false;
771
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
772
+ return false;
773
+ }
774
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
775
+ return false;
776
+ }
777
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
778
+ return false;
779
+ }
780
+ if (id === "play" && c.play === false) return false;
781
+ if (id === "pause" && c.pause === false) return false;
782
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
783
+ return false;
784
+ }
785
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
786
+ return false;
787
+ }
788
+ if (id === "speed" && c.speed === false) return false;
789
+ return true;
790
+ }
655
791
  setPage(page, max) {
656
792
  if (this.pageInputEl) {
657
793
  this.pageInputEl.value = page.toString();
794
+ if (max !== void 0) {
795
+ this.pageInputEl.max = max.toString();
796
+ }
658
797
  }
659
798
  if (max !== void 0 && this.pageLabelEl) {
660
799
  this.pageLabelEl.textContent = ` / ${max}`;
661
800
  }
801
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
802
+ if (this.prevPageBtn) {
803
+ this.prevPageBtn.disabled = page <= 1;
804
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
805
+ }
806
+ if (this.nextPageBtn) {
807
+ this.nextPageBtn.disabled = page >= currentMax;
808
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
809
+ }
662
810
  }
663
811
  update(actions) {
664
812
  this.actions = actions;
@@ -681,8 +829,9 @@ var ToolbarController = class {
681
829
  view: [],
682
830
  actions: []
683
831
  };
684
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
685
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
832
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
833
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
834
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
686
835
  for (const action of effectiveActions) {
687
836
  if (groups[action.group]) {
688
837
  groups[action.group].push(action);
@@ -742,6 +891,13 @@ var ToolbarController = class {
742
891
  action.execute("go", val);
743
892
  });
744
893
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
894
+ const curVal = action.value ?? 1;
895
+ prevBtn.disabled = curVal <= 1;
896
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
897
+ nextBtn.disabled = curVal >= max;
898
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
899
+ this.prevPageBtn = prevBtn;
900
+ this.nextPageBtn = nextBtn;
745
901
  this.pageInputEl = input;
746
902
  this.pageLabelEl = label;
747
903
  groupEl.appendChild(prevBtn);
@@ -797,25 +953,63 @@ var ToolbarController = class {
797
953
  var ThumbnailPanel = class {
798
954
  el;
799
955
  panelEl;
956
+ listEl;
957
+ headerEl;
800
958
  thumbnails = [];
801
959
  onSelect;
960
+ onToggleCallback;
802
961
  activeIndex = 0;
803
- constructor(container) {
962
+ observer;
963
+ renderedIndices = /* @__PURE__ */ new Set();
964
+ constructor(container, onToggle) {
804
965
  this.el = container;
966
+ this.onToggleCallback = onToggle;
805
967
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
968
+ this.panelEl.style.width = "0px";
969
+ this.panelEl.style.minWidth = "0px";
970
+ this.panelEl.style.opacity = "0";
971
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
972
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
973
+ const closeBtn = createElement("button", {
974
+ className: "fp-thumbnail-close-btn",
975
+ title: "Close Thumbnails",
976
+ type: "button"
977
+ });
978
+ closeBtn.innerHTML = ICON_CLOSE;
979
+ closeBtn.addEventListener("click", (e) => {
980
+ e.stopPropagation();
981
+ this.hide();
982
+ });
983
+ this.headerEl.appendChild(title);
984
+ this.headerEl.appendChild(closeBtn);
985
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
986
+ this.panelEl.appendChild(this.headerEl);
987
+ this.panelEl.appendChild(this.listEl);
806
988
  this.el.appendChild(this.panelEl);
807
989
  }
990
+ isOpen() {
991
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
992
+ }
808
993
  update(thumbnails, onSelect) {
809
994
  this.thumbnails = thumbnails;
810
995
  this.onSelect = onSelect;
996
+ this.renderedIndices.clear();
997
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
998
+ if (titleEl) {
999
+ titleEl.textContent = `Pages (${thumbnails.length})`;
1000
+ }
811
1001
  this.render();
1002
+ if (this.isOpen()) {
1003
+ this.renderAllVisible();
1004
+ }
812
1005
  }
813
1006
  setActive(index) {
814
1007
  this.activeIndex = index;
815
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
1008
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
816
1009
  items.forEach((item, i) => {
817
1010
  if (i === index) {
818
1011
  item.classList.add("active");
1012
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
819
1013
  } else {
820
1014
  item.classList.remove("active");
821
1015
  }
@@ -823,37 +1017,102 @@ var ThumbnailPanel = class {
823
1017
  }
824
1018
  show() {
825
1019
  this.panelEl.classList.remove("hidden");
1020
+ this.panelEl.style.width = "200px";
1021
+ this.panelEl.style.minWidth = "200px";
1022
+ this.panelEl.style.opacity = "1";
1023
+ this.panelEl.style.display = "flex";
1024
+ this.onToggleCallback?.(true);
1025
+ this.renderAllVisible();
826
1026
  }
827
1027
  hide() {
828
1028
  this.panelEl.classList.add("hidden");
1029
+ this.panelEl.style.width = "0px";
1030
+ this.panelEl.style.minWidth = "0px";
1031
+ this.panelEl.style.opacity = "0";
1032
+ this.onToggleCallback?.(false);
829
1033
  }
830
1034
  toggle() {
831
- this.panelEl.classList.toggle("hidden");
1035
+ if (this.isOpen()) {
1036
+ this.hide();
1037
+ } else {
1038
+ this.show();
1039
+ }
832
1040
  }
833
1041
  destroy() {
1042
+ if (this.observer) {
1043
+ this.observer.disconnect();
1044
+ this.observer = void 0;
1045
+ }
1046
+ this.renderedIndices.clear();
834
1047
  this.el.innerHTML = "";
835
1048
  }
836
- async render() {
837
- this.panelEl.innerHTML = "";
1049
+ renderAllVisible() {
1050
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
1051
+ items.forEach((item) => {
1052
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
1053
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
1054
+ this.renderThumbnailItem(idx, item);
1055
+ }
1056
+ });
1057
+ }
1058
+ renderThumbnailItem(idx, itemEl) {
1059
+ if (this.renderedIndices.has(idx)) return;
1060
+ this.renderedIndices.add(idx);
1061
+ const canvas = itemEl.querySelector("canvas");
1062
+ const thumb = this.thumbnails[idx];
1063
+ if (canvas && thumb) {
1064
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
1065
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
1066
+ });
1067
+ }
1068
+ }
1069
+ render() {
1070
+ if (this.observer) {
1071
+ this.observer.disconnect();
1072
+ }
1073
+ this.listEl.innerHTML = "";
1074
+ const useObserver = typeof IntersectionObserver !== "undefined";
1075
+ if (useObserver) {
1076
+ this.observer = new IntersectionObserver(
1077
+ (entries) => {
1078
+ for (const entry of entries) {
1079
+ if (entry.isIntersecting) {
1080
+ const target = entry.target;
1081
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
1082
+ if (idx >= 0) {
1083
+ this.renderThumbnailItem(idx, target);
1084
+ }
1085
+ }
1086
+ }
1087
+ },
1088
+ {
1089
+ root: this.listEl,
1090
+ rootMargin: "120px 0px 120px 0px"
1091
+ }
1092
+ );
1093
+ }
838
1094
  for (let i = 0; i < this.thumbnails.length; i++) {
839
1095
  const thumb = this.thumbnails[i];
840
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
841
- if (i === this.activeIndex) {
842
- itemEl.classList.add("active");
843
- }
1096
+ const itemEl = createElement("div", {
1097
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
1098
+ });
1099
+ itemEl.dataset.thumbIndex = i.toString();
844
1100
  itemEl.addEventListener("click", () => {
845
1101
  this.setActive(i);
846
1102
  this.onSelect?.(i);
847
1103
  });
848
- const canvas = createElement("canvas", { width: "150", height: "200" });
849
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
850
- itemEl.appendChild(canvas);
1104
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
1105
+ const canvas = createElement("canvas", { width: "130", height: "170" });
1106
+ canvasWrapper.appendChild(canvas);
1107
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
1108
+ itemEl.appendChild(canvasWrapper);
851
1109
  itemEl.appendChild(label);
852
- this.panelEl.appendChild(itemEl);
853
- try {
854
- await thumb.render(canvas);
855
- } catch (err) {
856
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
1110
+ this.listEl.appendChild(itemEl);
1111
+ if (this.observer) {
1112
+ this.observer.observe(itemEl);
1113
+ }
1114
+ if (i < 4) {
1115
+ this.renderThumbnailItem(i, itemEl);
857
1116
  }
858
1117
  }
859
1118
  }
@@ -874,6 +1133,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
874
1133
  currentOptions = {};
875
1134
  resizeObserver = null;
876
1135
  fullscreenHandler = null;
1136
+ titleBarEl = null;
877
1137
  /**
878
1138
  * Register a preview plugin.
879
1139
  */
@@ -910,6 +1170,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
910
1170
  }
911
1171
  this.currentBuffer = buffer.slice(0);
912
1172
  this.currentMetadata = metadata;
1173
+ this.updateTitleBar(options, metadata);
913
1174
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
914
1175
  const fileInfo = { metadata, buffer };
915
1176
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -933,15 +1194,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
933
1194
  if (event === "page-change" && payload && typeof payload.page === "number") {
934
1195
  const total = payload.total ?? payload.totalPages;
935
1196
  this.toolbar?.setPage(payload.page, total);
1197
+ this.thumbnailPanel?.setActive(payload.page - 1);
936
1198
  }
937
1199
  this.eventEmitter.emit(event, payload);
938
- }
1200
+ },
1201
+ toggleThumbnails: () => this.toggleThumbnails()
939
1202
  });
940
1203
  this.activeInstance = instance;
941
1204
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1205
+ instance.toggleThumbnails = () => this.toggleThumbnails();
942
1206
  this.hideLoading();
943
1207
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
944
- if (options.showToolbar !== false && this.toolbar) {
1208
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1209
+ if (isToolbarVisible && this.toolbar) {
1210
+ const toolbarConfig = this.extractToolbarConfig(options);
1211
+ this.toolbar.setConfig(toolbarConfig);
945
1212
  const actions = matchedPlugin.getToolbarActions(instance);
946
1213
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
947
1214
  if (!hasFullscreen && this.wrapperEl) {
@@ -991,6 +1258,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
991
1258
  }
992
1259
  });
993
1260
  }
1261
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1262
+ if (thumbAction) {
1263
+ thumbAction.execute = () => {
1264
+ this.toggleThumbnails();
1265
+ };
1266
+ }
994
1267
  this.toolbar.update(actions);
995
1268
  this.toolbar.show();
996
1269
  }
@@ -1075,7 +1348,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1075
1348
  if (targetUrl) {
1076
1349
  const newWin2 = window.open(targetUrl, "_blank");
1077
1350
  if (!newWin2) {
1078
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1351
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1079
1352
  return null;
1080
1353
  }
1081
1354
  return newWin2;
@@ -1083,7 +1356,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1083
1356
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1084
1357
  const newWin = window.open("", "_blank");
1085
1358
  if (!newWin) {
1086
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1359
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1087
1360
  return null;
1088
1361
  }
1089
1362
  newWin.document.title = title;
@@ -1160,6 +1433,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1160
1433
  this.currentBuffer = null;
1161
1434
  this.currentMetadata = null;
1162
1435
  this.currentContainer = null;
1436
+ this.titleBarEl = null;
1163
1437
  }
1164
1438
  /**
1165
1439
  * Get the currently active preview instance.
@@ -1167,7 +1441,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1167
1441
  getInstance() {
1168
1442
  return this.activeInstance;
1169
1443
  }
1444
+ // --- Toolbar Configuration & Feature Toggles ---
1445
+ /**
1446
+ * Configure toolbar features dynamically through options or methods.
1447
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1448
+ */
1449
+ setToolbarConfig(config) {
1450
+ this.toolbar?.setConfig(config);
1451
+ }
1452
+ /**
1453
+ * Get current toolbar configuration.
1454
+ */
1455
+ getToolbarConfig() {
1456
+ return this.toolbar?.getConfig() ?? {};
1457
+ }
1458
+ /**
1459
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1460
+ */
1461
+ hideToolbarAction(actionId) {
1462
+ this.toolbar?.hideAction(actionId);
1463
+ }
1464
+ /**
1465
+ * Show a specific toolbar action by ID or alias.
1466
+ */
1467
+ showToolbarAction(actionId) {
1468
+ this.toolbar?.showAction(actionId);
1469
+ }
1470
+ /**
1471
+ * Enable a specific toolbar action button.
1472
+ */
1473
+ enableToolbarAction(actionId) {
1474
+ this.toolbar?.enableAction(actionId);
1475
+ }
1476
+ /**
1477
+ * Disable a specific toolbar action button.
1478
+ */
1479
+ disableToolbarAction(actionId) {
1480
+ this.toolbar?.disableAction(actionId);
1481
+ }
1482
+ // --- Programmatic Toolbar Action Methods ---
1483
+ /**
1484
+ * Fit document to page so it fills the frame width with minimal margins.
1485
+ */
1486
+ fitToPage() {
1487
+ this.activeInstance?.fitToPage?.();
1488
+ }
1489
+ /**
1490
+ * Zoom in.
1491
+ */
1492
+ zoomIn() {
1493
+ this.activeInstance?.zoomIn?.();
1494
+ }
1495
+ /**
1496
+ * Zoom out.
1497
+ */
1498
+ zoomOut() {
1499
+ this.activeInstance?.zoomOut?.();
1500
+ }
1501
+ /**
1502
+ * Set specific zoom level.
1503
+ */
1504
+ setZoom(level) {
1505
+ this.activeInstance?.setZoom?.(level);
1506
+ }
1507
+ /**
1508
+ * Get current zoom level.
1509
+ */
1510
+ getZoom() {
1511
+ return this.activeInstance?.getZoom?.() ?? 1;
1512
+ }
1513
+ /**
1514
+ * Rotate 90 degrees clockwise.
1515
+ */
1516
+ rotateCW() {
1517
+ this.activeInstance?.rotateCW?.();
1518
+ }
1519
+ /**
1520
+ * Rotate 90 degrees counter-clockwise.
1521
+ */
1522
+ rotateCCW() {
1523
+ this.activeInstance?.rotateCCW?.();
1524
+ }
1525
+ /**
1526
+ * Navigate to a specific page number.
1527
+ */
1528
+ goToPage(page) {
1529
+ this.activeInstance?.goToPage?.(page);
1530
+ this.toolbar?.setPage(page);
1531
+ }
1532
+ /**
1533
+ * Navigate to next page.
1534
+ */
1535
+ nextPage() {
1536
+ const cur = this.getCurrentPage();
1537
+ const max = this.getPageCount();
1538
+ if (cur < max) {
1539
+ this.goToPage(cur + 1);
1540
+ }
1541
+ }
1542
+ /**
1543
+ * Navigate to previous page.
1544
+ */
1545
+ prevPage() {
1546
+ const cur = this.getCurrentPage();
1547
+ if (cur > 1) {
1548
+ this.goToPage(cur - 1);
1549
+ }
1550
+ }
1551
+ /**
1552
+ * Get total page count.
1553
+ */
1554
+ getPageCount() {
1555
+ return this.activeInstance?.getPageCount?.() ?? 1;
1556
+ }
1557
+ /**
1558
+ * Get current page number.
1559
+ */
1560
+ getCurrentPage() {
1561
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1562
+ }
1563
+ /**
1564
+ * Download the current document.
1565
+ */
1566
+ download() {
1567
+ this.activeInstance?.download?.();
1568
+ }
1569
+ /**
1570
+ * Print the current document.
1571
+ */
1572
+ print() {
1573
+ this.activeInstance?.print?.();
1574
+ }
1575
+ /**
1576
+ * Toggle fullscreen mode.
1577
+ */
1578
+ toggleFullscreen() {
1579
+ try {
1580
+ if (!document.fullscreenElement) {
1581
+ this.wrapperEl?.requestFullscreen?.();
1582
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1583
+ } else {
1584
+ document.exitFullscreen?.();
1585
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1586
+ }
1587
+ } catch {
1588
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1589
+ }
1590
+ setTimeout(() => {
1591
+ this.activeInstance?.fitToPage?.();
1592
+ }, 120);
1593
+ }
1594
+ /**
1595
+ * Toggle thumbnails sidebar panel.
1596
+ */
1597
+ toggleThumbnails() {
1598
+ if (!this.thumbnailPanel) return;
1599
+ this.thumbnailPanel.toggle();
1600
+ const isOpen = this.thumbnailPanel.isOpen();
1601
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1602
+ setTimeout(() => {
1603
+ this.activeInstance?.fitToPage?.();
1604
+ }, 260);
1605
+ }
1606
+ /**
1607
+ * Set whether to display the file name / title bar above the toolbar.
1608
+ */
1609
+ setShowFileName(show) {
1610
+ if (this.currentOptions) {
1611
+ this.currentOptions.showFileName = show;
1612
+ }
1613
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1614
+ }
1615
+ /**
1616
+ * Set or override the displayed file name in the preview panel.
1617
+ */
1618
+ setFileName(name) {
1619
+ if (this.currentOptions) {
1620
+ this.currentOptions.fileName = name;
1621
+ }
1622
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1623
+ }
1170
1624
  // --- Private methods ---
1625
+ updateTitleBar(options, metadata) {
1626
+ if (!this.titleBarEl) return;
1627
+ const show = options?.showFileName !== false;
1628
+ if (!show) {
1629
+ this.titleBarEl.style.display = "none";
1630
+ return;
1631
+ }
1632
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1633
+ if (!name) {
1634
+ this.titleBarEl.style.display = "none";
1635
+ return;
1636
+ }
1637
+ this.titleBarEl.style.display = "flex";
1638
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1639
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1640
+ this.titleBarEl.innerHTML = `
1641
+ <div class="fp-titlebar-info">
1642
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1643
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1644
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1645
+ </div>
1646
+ `;
1647
+ }
1648
+ extractToolbarConfig(options) {
1649
+ if (!options) return {};
1650
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1651
+ return {
1652
+ ...options,
1653
+ ...nested
1654
+ };
1655
+ }
1171
1656
  abort() {
1172
1657
  if (this.abortController) {
1173
1658
  this.abortController.abort();
@@ -1210,17 +1695,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1210
1695
  bodyEl.className = "fp-body";
1211
1696
  bodyEl.appendChild(thumbnailEl);
1212
1697
  bodyEl.appendChild(this.contentEl);
1698
+ const titleBarEl = document.createElement("div");
1699
+ titleBarEl.className = "fp-titlebar";
1700
+ this.titleBarEl = titleBarEl;
1701
+ this.updateTitleBar(options, this.currentMetadata);
1213
1702
  if (toolbarPos === "top") {
1703
+ this.wrapperEl.appendChild(titleBarEl);
1214
1704
  this.wrapperEl.appendChild(toolbarEl);
1215
1705
  this.wrapperEl.appendChild(bodyEl);
1216
1706
  } else {
1707
+ this.wrapperEl.appendChild(titleBarEl);
1217
1708
  this.wrapperEl.appendChild(bodyEl);
1218
1709
  this.wrapperEl.appendChild(toolbarEl);
1219
1710
  }
1220
1711
  container.innerHTML = "";
1221
1712
  container.appendChild(this.wrapperEl);
1222
- this.toolbar = new ToolbarController(toolbarEl);
1223
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1713
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1714
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1715
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1716
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1717
+ setTimeout(() => {
1718
+ this.activeInstance?.fitToPage?.();
1719
+ }, 260);
1720
+ });
1224
1721
  this.setupKeyboardShortcuts();
1225
1722
  this.setupDragAndDrop(container, options);
1226
1723
  this.setupResizeAndFullscreenListeners();
@@ -1610,6 +2107,14 @@ var PdfPlugin = class {
1610
2107
  group: "zoom",
1611
2108
  execute: () => instance.fitToPage?.()
1612
2109
  },
2110
+ {
2111
+ id: "fit-width",
2112
+ icon: "fit-width",
2113
+ label: "Fit to Width",
2114
+ type: "button",
2115
+ group: "zoom",
2116
+ execute: () => instance.fitToWidth?.()
2117
+ },
1613
2118
  {
1614
2119
  id: "rotate-cw",
1615
2120
  icon: "rotate-cw",
@@ -1618,6 +2123,14 @@ var PdfPlugin = class {
1618
2123
  group: "view",
1619
2124
  execute: () => instance.rotateCW?.()
1620
2125
  },
2126
+ {
2127
+ id: "rotate-ccw",
2128
+ icon: "rotate-ccw",
2129
+ label: "Rotate Counter-Clockwise",
2130
+ type: "button",
2131
+ group: "view",
2132
+ execute: () => instance.rotateCCW?.()
2133
+ },
1621
2134
  {
1622
2135
  id: "download",
1623
2136
  icon: "download",
@@ -1649,12 +2162,13 @@ var PdfPlugin = class {
1649
2162
  container.className = "fp-pdf-container";
1650
2163
  container.style.width = "100%";
1651
2164
  container.style.height = "100%";
1652
- container.style.overflow = "auto";
2165
+ container.style.overflowX = "hidden";
2166
+ container.style.overflowY = "auto";
1653
2167
  container.style.display = "flex";
1654
2168
  container.style.flexDirection = "column";
1655
2169
  container.style.alignItems = "center";
1656
2170
  container.style.justifyContent = "flex-start";
1657
- container.style.padding = "20px 16px";
2171
+ container.style.padding = "16px 8px";
1658
2172
  container.style.backgroundColor = "#0f172a";
1659
2173
  container.style.boxSizing = "border-box";
1660
2174
  container.style.position = "relative";
@@ -1665,30 +2179,12 @@ var PdfPlugin = class {
1665
2179
  pageCard.style.borderRadius = "4px";
1666
2180
  pageCard.style.overflow = "hidden";
1667
2181
  pageCard.style.lineHeight = "0";
1668
- pageCard.style.transition = "transform 0.15s ease";
1669
2182
  pageCard.style.position = "relative";
1670
2183
  pageCard.style.flexShrink = "0";
2184
+ pageCard.style.transition = "box-shadow 0.2s ease";
1671
2185
  let canvas = document.createElement("canvas");
1672
2186
  pageCard.appendChild(canvas);
1673
2187
  container.appendChild(pageCard);
1674
- const indicator = document.createElement("div");
1675
- indicator.className = "fp-pdf-page-indicator";
1676
- indicator.style.position = "sticky";
1677
- indicator.style.bottom = "16px";
1678
- indicator.style.marginTop = "16px";
1679
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1680
- indicator.style.backdropFilter = "blur(8px)";
1681
- indicator.style.color = "#f8fafc";
1682
- indicator.style.fontSize = "12px";
1683
- indicator.style.fontWeight = "600";
1684
- indicator.style.padding = "5px 14px";
1685
- indicator.style.borderRadius = "20px";
1686
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1687
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1688
- indicator.style.zIndex = "10";
1689
- indicator.style.userSelect = "none";
1690
- indicator.style.pointerEvents = "none";
1691
- container.appendChild(indicator);
1692
2188
  ctx.container.appendChild(container);
1693
2189
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1694
2190
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1704,8 +2200,9 @@ var PdfPlugin = class {
1704
2200
  let currentPage = 1;
1705
2201
  let zoomScale = 1;
1706
2202
  let rotation = 0;
1707
- let fitMode = "page";
2203
+ let fitMode = ctx?.options?.fitMode || "width";
1708
2204
  let currentRenderTask = null;
2205
+ let textLayerDiv = null;
1709
2206
  const renderPage = async (pageNum) => {
1710
2207
  if (currentRenderTask) {
1711
2208
  try {
@@ -1717,30 +2214,37 @@ var PdfPlugin = class {
1717
2214
  const newCanvas = document.createElement("canvas");
1718
2215
  pageCard.replaceChild(newCanvas, canvas);
1719
2216
  canvas = newCanvas;
2217
+ if (textLayerDiv) {
2218
+ textLayerDiv.remove();
2219
+ textLayerDiv = null;
2220
+ }
1720
2221
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1721
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1722
2222
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1723
2223
  const page = await pdfDoc.getPage(currentPage);
1724
2224
  const containerWidth = container.clientWidth || 900;
1725
2225
  const containerHeight = container.clientHeight || 700;
1726
2226
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1727
- const availWidth = Math.max(320, containerWidth - 48);
1728
- const availHeight = Math.max(550, containerHeight - 88);
2227
+ const availWidth = Math.max(280, containerWidth - 36);
2228
+ const availHeight = Math.max(280, containerHeight - 32);
1729
2229
  const scaleW = availWidth / unscaledVp.width;
1730
2230
  const scaleH = availHeight / unscaledVp.height;
1731
2231
  let fitScale;
1732
2232
  if (fitMode === "page") {
1733
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2233
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1734
2234
  } else {
1735
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2235
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1736
2236
  }
1737
2237
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1738
2238
  const pixelRatio = window.devicePixelRatio || 1;
1739
2239
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2240
+ const displayWidth = Math.floor(viewport.width);
2241
+ const displayHeight = Math.floor(viewport.height);
1740
2242
  canvas.width = Math.floor(viewport.width * pixelRatio);
1741
2243
  canvas.height = Math.floor(viewport.height * pixelRatio);
1742
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1743
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2244
+ canvas.style.width = `${displayWidth}px`;
2245
+ canvas.style.height = `${displayHeight}px`;
2246
+ pageCard.style.width = `${displayWidth}px`;
2247
+ pageCard.style.height = `${displayHeight}px`;
1744
2248
  const canvasCtx = canvas.getContext("2d");
1745
2249
  if (!canvasCtx) return;
1746
2250
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1754,9 +2258,31 @@ var PdfPlugin = class {
1754
2258
  if (err?.name !== "RenderingCancelledException") {
1755
2259
  console.warn("[PdfPlugin] Page render warning:", err);
1756
2260
  }
2261
+ return;
1757
2262
  } finally {
1758
2263
  currentRenderTask = null;
1759
2264
  }
2265
+ try {
2266
+ textLayerDiv = document.createElement("div");
2267
+ textLayerDiv.className = "textLayer";
2268
+ textLayerDiv.style.width = `${displayWidth}px`;
2269
+ textLayerDiv.style.height = `${displayHeight}px`;
2270
+ textLayerDiv.style.position = "absolute";
2271
+ textLayerDiv.style.top = "0";
2272
+ textLayerDiv.style.left = "0";
2273
+ textLayerDiv.style.lineHeight = "1";
2274
+ pageCard.appendChild(textLayerDiv);
2275
+ if (pdfjsLib__namespace.TextLayer) {
2276
+ const textLayer = new pdfjsLib__namespace.TextLayer({
2277
+ textContentSource: page.streamTextContent(),
2278
+ container: textLayerDiv,
2279
+ viewport
2280
+ });
2281
+ await textLayer.render();
2282
+ }
2283
+ } catch (err) {
2284
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2285
+ }
1760
2286
  };
1761
2287
  renderPage(1);
1762
2288
  let resizeTimer = null;
@@ -1766,11 +2292,48 @@ var PdfPlugin = class {
1766
2292
  if (Math.abs(zoomScale - 1) < 0.05) {
1767
2293
  renderPage(currentPage);
1768
2294
  }
1769
- }, 150);
2295
+ }, 120);
1770
2296
  });
1771
2297
  resizeObserver.observe(container);
2298
+ const onWheel = (e) => {
2299
+ if (e.ctrlKey || e.metaKey) {
2300
+ e.preventDefault();
2301
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2302
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2303
+ renderPage(currentPage);
2304
+ }
2305
+ };
2306
+ container.addEventListener("wheel", onWheel, { passive: false });
2307
+ const onKeyDown = (e) => {
2308
+ const tag = e.target?.tagName?.toLowerCase();
2309
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2310
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2311
+ if (currentPage < totalPages) {
2312
+ e.preventDefault();
2313
+ container.scrollTop = 0;
2314
+ renderPage(currentPage + 1);
2315
+ }
2316
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2317
+ if (currentPage > 1) {
2318
+ e.preventDefault();
2319
+ container.scrollTop = 0;
2320
+ renderPage(currentPage - 1);
2321
+ }
2322
+ } else if (e.key === "Home") {
2323
+ e.preventDefault();
2324
+ container.scrollTop = 0;
2325
+ renderPage(1);
2326
+ } else if (e.key === "End") {
2327
+ e.preventDefault();
2328
+ container.scrollTop = 0;
2329
+ renderPage(totalPages);
2330
+ }
2331
+ };
2332
+ window.addEventListener("keydown", onKeyDown);
1772
2333
  const cleanup = () => {
1773
2334
  resizeObserver.disconnect();
2335
+ window.removeEventListener("keydown", onKeyDown);
2336
+ container.removeEventListener("wheel", onWheel);
1774
2337
  if (resizeTimer) clearTimeout(resizeTimer);
1775
2338
  if (currentRenderTask) {
1776
2339
  try {
@@ -1789,22 +2352,26 @@ var PdfPlugin = class {
1789
2352
  const instance = {
1790
2353
  destroy: cleanup,
1791
2354
  zoomIn: () => {
1792
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2355
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1793
2356
  renderPage(currentPage);
1794
2357
  },
1795
2358
  zoomOut: () => {
1796
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2359
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1797
2360
  renderPage(currentPage);
1798
2361
  },
1799
2362
  getZoom: () => zoomScale,
1800
2363
  setZoom: (level) => {
1801
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2364
+ zoomScale = Math.max(0.25, Math.min(4, level));
1802
2365
  renderPage(currentPage);
1803
2366
  },
1804
2367
  fitToPage: () => {
1805
- fitMode = fitMode === "page" ? "width" : "page";
2368
+ fitMode = "page";
2369
+ zoomScale = 1;
2370
+ renderPage(currentPage);
2371
+ },
2372
+ fitToWidth: () => {
2373
+ fitMode = "width";
1806
2374
  zoomScale = 1;
1807
- rotation = 0;
1808
2375
  renderPage(currentPage);
1809
2376
  },
1810
2377
  rotateCW: () => {
@@ -1822,6 +2389,9 @@ var PdfPlugin = class {
1822
2389
  container.scrollTop = 0;
1823
2390
  renderPage(page);
1824
2391
  },
2392
+ toggleThumbnails: () => {
2393
+ ctx?.toggleThumbnails?.();
2394
+ },
1825
2395
  download: () => {
1826
2396
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1827
2397
  const url = URL.createObjectURL(blob);
@@ -1855,7 +2425,7 @@ var PdfPlugin = class {
1855
2425
  },
1856
2426
  getThumbnails: async () => {
1857
2427
  const thumbnails = [];
1858
- const count = Math.min(totalPages, 50);
2428
+ const count = totalPages;
1859
2429
  for (let i = 1; i <= count; i++) {
1860
2430
  thumbnails.push({
1861
2431
  index: i,
@@ -1863,9 +2433,10 @@ var PdfPlugin = class {
1863
2433
  render: async (thumbCanvas) => {
1864
2434
  try {
1865
2435
  const p = await pdfDoc.getPage(i);
1866
- const baseVp = p.getViewport({ scale: 1 });
1867
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1868
- const thumbVp = p.getViewport({ scale: thumbScale });
2436
+ const baseVp = p.getViewport({ scale: 1, rotation });
2437
+ const targetW = thumbCanvas.width || 130;
2438
+ const thumbScale = targetW / baseVp.width;
2439
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1869
2440
  thumbCanvas.height = Math.floor(thumbVp.height);
1870
2441
  const tCtx = thumbCanvas.getContext("2d");
1871
2442
  if (tCtx) {
@@ -2209,28 +2780,26 @@ var DocxPlugin = class {
2209
2780
  getToolbarActions(instance) {
2210
2781
  const totalPages = instance.getPageCount?.() ?? 1;
2211
2782
  const actions = [];
2212
- if (totalPages > 1) {
2213
- actions.push({
2214
- id: "page-nav",
2215
- icon: "",
2216
- label: "Page Navigation",
2217
- type: "page-nav",
2218
- group: "navigation",
2219
- value: instance.getCurrentPage?.() ?? 1,
2220
- max: totalPages,
2221
- execute: (action, page) => {
2222
- const cur = instance.getCurrentPage?.() ?? 1;
2223
- const max = instance.getPageCount?.() ?? 1;
2224
- if (action === "prev") {
2225
- if (cur > 1) instance.goToPage?.(cur - 1);
2226
- } else if (action === "next") {
2227
- if (cur < max) instance.goToPage?.(cur + 1);
2228
- } else if (typeof page === "number") {
2229
- instance.goToPage?.(page);
2230
- }
2783
+ actions.push({
2784
+ id: "page-nav",
2785
+ icon: "",
2786
+ label: "Page Navigation",
2787
+ type: "page-nav",
2788
+ group: "navigation",
2789
+ value: instance.getCurrentPage?.() ?? 1,
2790
+ max: totalPages,
2791
+ execute: (action, page) => {
2792
+ const cur = instance.getCurrentPage?.() ?? 1;
2793
+ const max = instance.getPageCount?.() ?? 1;
2794
+ if (action === "prev") {
2795
+ if (cur > 1) instance.goToPage?.(cur - 1);
2796
+ } else if (action === "next") {
2797
+ if (cur < max) instance.goToPage?.(cur + 1);
2798
+ } else if (typeof page === "number") {
2799
+ instance.goToPage?.(page);
2231
2800
  }
2232
- });
2233
- }
2801
+ }
2802
+ });
2234
2803
  actions.push(
2235
2804
  {
2236
2805
  id: "zoom-out",
@@ -2296,11 +2865,17 @@ var DocxPlugin = class {
2296
2865
  wrapper.className = "fp-docx-wrapper";
2297
2866
  wrapper.style.transformOrigin = "top center";
2298
2867
  wrapper.style.transition = "transform 0.2s ease";
2299
- wrapper.style.padding = "24px 16px";
2300
- wrapper.style.maxWidth = "950px";
2868
+ wrapper.style.padding = "0";
2869
+ wrapper.style.maxWidth = "none";
2870
+ wrapper.style.width = "816px";
2301
2871
  wrapper.style.margin = "0 auto";
2872
+ wrapper.style.display = "flex";
2873
+ wrapper.style.flexDirection = "column";
2874
+ wrapper.style.alignItems = "center";
2302
2875
  wrapper.style.boxSizing = "border-box";
2303
- ctx.container.style.overflow = "auto";
2876
+ ctx.container.style.overflowX = "hidden";
2877
+ ctx.container.style.overflowY = "auto";
2878
+ ctx.container.style.padding = "16px 8px";
2304
2879
  ctx.container.style.backgroundColor = "#f1f5f9";
2305
2880
  ctx.container.appendChild(wrapper);
2306
2881
  const styleOverride = document.createElement("style");
@@ -2474,39 +3049,13 @@ var DocxPlugin = class {
2474
3049
  const pageElements = sections.length > 0 ? sections : cards;
2475
3050
  const totalPages = Math.max(1, pageElements.length);
2476
3051
  let currentPage = 1;
2477
- let indicator = null;
2478
- if (totalPages > 1) {
2479
- indicator = document.createElement("div");
2480
- indicator.className = "fp-docx-page-indicator";
2481
- indicator.style.position = "sticky";
2482
- indicator.style.bottom = "16px";
2483
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2484
- indicator.style.backdropFilter = "blur(8px)";
2485
- indicator.style.color = "#f8fafc";
2486
- indicator.style.fontSize = "12px";
2487
- indicator.style.fontWeight = "600";
2488
- indicator.style.padding = "5px 14px";
2489
- indicator.style.borderRadius = "20px";
2490
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2491
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2492
- indicator.style.zIndex = "10";
2493
- indicator.style.userSelect = "none";
2494
- indicator.style.pointerEvents = "none";
2495
- indicator.style.textAlign = "center";
2496
- indicator.style.width = "fit-content";
2497
- indicator.style.margin = "16px auto 0";
2498
- ctx.container.appendChild(indicator);
2499
- }
2500
3052
  const showPage = (pageNum) => {
2501
3053
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2502
3054
  if (pageElements.length > 1) {
2503
3055
  pageElements.forEach((sec, idx) => {
2504
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
3056
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2505
3057
  });
2506
3058
  }
2507
- if (indicator) {
2508
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2509
- }
2510
3059
  ctx.container.scrollTop = 0;
2511
3060
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2512
3061
  };
@@ -2515,31 +3064,46 @@ var DocxPlugin = class {
2515
3064
  }
2516
3065
  scale = 1;
2517
3066
  let rotation = 0;
2518
- let fitMode = "width";
2519
- const calculateFitScale = (mode = fitMode) => {
2520
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
3067
+ let fitMode = "page";
3068
+ let isUserZoomed = false;
3069
+ const calculateFitScale = (_mode = fitMode) => {
3070
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2521
3071
  const elW = activeEl.offsetWidth || 816;
2522
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2523
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2524
- const availH = Math.max(280, ctx.container.clientHeight - 80);
3072
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2525
3073
  const sW = availW / elW;
2526
- const sH = availH / singlePageH;
2527
- if (mode === "page") {
2528
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2529
- }
2530
- return Math.max(0.65, Math.min(1.05, sW));
3074
+ return Math.max(0.35, Math.min(3, sW));
2531
3075
  };
2532
3076
  const applyTransform = () => {
2533
3077
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2534
3078
  wrapper.style.transformOrigin = "top center";
3079
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3080
+ const elH = activeEl.offsetHeight || 1056;
3081
+ if (scale > 1) {
3082
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3083
+ } else {
3084
+ wrapper.style.marginBottom = "32px";
3085
+ }
2535
3086
  };
3087
+ let resizeObserver = null;
3088
+ if (typeof ResizeObserver !== "undefined") {
3089
+ resizeObserver = new ResizeObserver(() => {
3090
+ if (!isUserZoomed) {
3091
+ const newFit = calculateFitScale("page");
3092
+ if (Math.abs(scale - newFit) > 0.015) {
3093
+ scale = newFit;
3094
+ applyTransform();
3095
+ }
3096
+ }
3097
+ });
3098
+ resizeObserver.observe(ctx.container);
3099
+ }
2536
3100
  setTimeout(() => {
2537
- fitMode = "width";
2538
- scale = calculateFitScale("width");
3101
+ scale = calculateFitScale("page");
2539
3102
  applyTransform();
2540
- }, 60);
3103
+ }, 40);
2541
3104
  const cleanup = () => {
2542
- indicator?.remove();
3105
+ resizeObserver?.disconnect();
3106
+ resizeObserver = null;
2543
3107
  for (const url of createdBlobUrls) {
2544
3108
  URL.revokeObjectURL(url);
2545
3109
  }
@@ -2556,21 +3120,24 @@ var DocxPlugin = class {
2556
3120
  showPage(page);
2557
3121
  },
2558
3122
  zoomIn: () => {
2559
- scale += 0.15;
3123
+ isUserZoomed = true;
3124
+ scale = Math.min(3.5, scale + 0.15);
2560
3125
  applyTransform();
2561
3126
  },
2562
3127
  zoomOut: () => {
3128
+ isUserZoomed = true;
2563
3129
  scale = Math.max(0.2, scale - 0.15);
2564
3130
  applyTransform();
2565
3131
  },
2566
3132
  getZoom: () => scale,
2567
3133
  setZoom: (level) => {
3134
+ isUserZoomed = true;
2568
3135
  scale = level;
2569
3136
  applyTransform();
2570
3137
  },
2571
3138
  fitToPage: () => {
2572
- fitMode = fitMode === "width" ? "page" : "width";
2573
- scale = calculateFitScale(fitMode);
3139
+ isUserZoomed = false;
3140
+ scale = calculateFitScale("page");
2574
3141
  rotation = 0;
2575
3142
  applyTransform();
2576
3143
  },
@@ -2960,7 +3527,7 @@ var ExcelPlugin = class {
2960
3527
  "application/vnd.ms-excel.template.macroEnabled.12",
2961
3528
  "application/vnd.oasis.opendocument.spreadsheet"
2962
3529
  ];
2963
- weight = 80;
3530
+ weight = 85;
2964
3531
  supports(file) {
2965
3532
  const ext = file.metadata.extension?.toLowerCase();
2966
3533
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2972,6 +3539,14 @@ var ExcelPlugin = class {
2972
3539
  getToolbarActions(instance) {
2973
3540
  const totalSheets = instance.getPageCount?.() ?? 1;
2974
3541
  const actions = [];
3542
+ actions.push({
3543
+ id: "thumbnails",
3544
+ icon: "thumbnails",
3545
+ label: "Sheet Thumbnails",
3546
+ type: "button",
3547
+ group: "navigation",
3548
+ execute: () => instance.toggleThumbnails?.()
3549
+ });
2975
3550
  if (totalSheets > 1) {
2976
3551
  actions.push({
2977
3552
  id: "page-nav",
@@ -3015,26 +3590,6 @@ var ExcelPlugin = class {
3015
3590
  instance.zoomIn?.();
3016
3591
  }
3017
3592
  },
3018
- {
3019
- id: "fit-page",
3020
- icon: "fit-page",
3021
- label: "Fit to View",
3022
- type: "button",
3023
- group: "zoom",
3024
- execute: () => {
3025
- instance.fitToPage?.();
3026
- }
3027
- },
3028
- {
3029
- id: "rotate-cw",
3030
- icon: "rotate-cw",
3031
- label: "Rotate",
3032
- type: "button",
3033
- group: "view",
3034
- execute: () => {
3035
- instance.rotateCW?.();
3036
- }
3037
- },
3038
3593
  {
3039
3594
  id: "download",
3040
3595
  icon: "download",
@@ -3054,6 +3609,16 @@ var ExcelPlugin = class {
3054
3609
  execute: () => {
3055
3610
  instance.print?.();
3056
3611
  }
3612
+ },
3613
+ {
3614
+ id: "open-window",
3615
+ icon: "open-window",
3616
+ label: "Open in Separate Full Window",
3617
+ type: "button",
3618
+ group: "actions",
3619
+ execute: () => {
3620
+ instance.openInSeparateWindow?.();
3621
+ }
3057
3622
  }
3058
3623
  );
3059
3624
  return actions;
@@ -3085,12 +3650,11 @@ var ExcelPlugin = class {
3085
3650
  container.appendChild(tabsArea);
3086
3651
  ctx.container.appendChild(container);
3087
3652
  let scale = 1;
3088
- let rotation = 0;
3089
3653
  let currentSheetIndex = 1;
3090
3654
  let sheetNames = [];
3091
3655
  let wb = null;
3092
3656
  const applyTransform = () => {
3093
- contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3657
+ contentArea.style.transform = `scale(${scale})`;
3094
3658
  contentArea.style.transformOrigin = "top left";
3095
3659
  };
3096
3660
  const calculateFitScale = () => {
@@ -3098,7 +3662,7 @@ var ExcelPlugin = class {
3098
3662
  if (!table) return 1;
3099
3663
  const availW = Math.max(280, container.clientWidth - 48);
3100
3664
  const tW = table.offsetWidth || 800;
3101
- return Math.max(0.65, Math.min(1, availW / tW));
3665
+ return Math.max(0.4, Math.min(1, availW / tW));
3102
3666
  };
3103
3667
  try {
3104
3668
  wb = XLSX__namespace.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
@@ -3153,7 +3717,16 @@ var ExcelPlugin = class {
3153
3717
  }
3154
3718
  });
3155
3719
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3720
+ requestAnimationFrame(() => {
3721
+ scale = calculateFitScale();
3722
+ applyTransform();
3723
+ });
3156
3724
  };
3725
+ const ro = new ResizeObserver(() => {
3726
+ scale = calculateFitScale();
3727
+ applyTransform();
3728
+ });
3729
+ ro.observe(container);
3157
3730
  if (sheetNames.length > 0) {
3158
3731
  sheetNames.forEach((name, idx) => {
3159
3732
  const btn = document.createElement("button");
@@ -3180,6 +3753,7 @@ var ExcelPlugin = class {
3180
3753
  `;
3181
3754
  }
3182
3755
  const cleanup = () => {
3756
+ ro.disconnect();
3183
3757
  container.remove();
3184
3758
  ctx.container.innerHTML = "";
3185
3759
  };
@@ -3201,16 +3775,11 @@ var ExcelPlugin = class {
3201
3775
  },
3202
3776
  fitToPage: () => {
3203
3777
  scale = calculateFitScale();
3204
- rotation = 0;
3205
3778
  applyTransform();
3206
3779
  },
3207
3780
  rotateCW: () => {
3208
- rotation = (rotation + 90) % 360;
3209
- applyTransform();
3210
3781
  },
3211
3782
  rotateCCW: () => {
3212
- rotation = (rotation - 90 + 360) % 360;
3213
- applyTransform();
3214
3783
  },
3215
3784
  goToPage: (page) => {
3216
3785
  if (page > 0 && page <= sheetNames.length) {
@@ -3219,6 +3788,63 @@ var ExcelPlugin = class {
3219
3788
  },
3220
3789
  getPageCount: () => sheetNames.length,
3221
3790
  getCurrentPage: () => currentSheetIndex,
3791
+ getThumbnails: () => {
3792
+ return sheetNames.map((name, idx) => ({
3793
+ index: idx,
3794
+ label: name,
3795
+ render: async (canvas) => {
3796
+ canvas.width = 140;
3797
+ canvas.height = 100;
3798
+ const c = canvas.getContext("2d");
3799
+ if (!c) return;
3800
+ c.fillStyle = "#ffffff";
3801
+ c.fillRect(0, 0, canvas.width, canvas.height);
3802
+ c.fillStyle = "#107c41";
3803
+ c.fillRect(0, 0, canvas.width, 16);
3804
+ c.fillStyle = "#ffffff";
3805
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3806
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3807
+ const startY = 17;
3808
+ const rowH = 13;
3809
+ const colW = 30;
3810
+ const colHeaders = ["A", "B", "C", "D"];
3811
+ c.fillStyle = "#f1f5f9";
3812
+ c.fillRect(0, startY, canvas.width, rowH);
3813
+ c.strokeStyle = "#cbd5e1";
3814
+ c.lineWidth = 0.8;
3815
+ c.strokeRect(0, startY, canvas.width, rowH);
3816
+ c.fillStyle = "#64748b";
3817
+ c.font = "bold 7px sans-serif";
3818
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3819
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3820
+ }
3821
+ const ws = wb?.Sheets[name];
3822
+ for (let ri = 1; ri <= 5; ri++) {
3823
+ const y = startY + ri * rowH;
3824
+ if (y > canvas.height - 2) break;
3825
+ c.fillStyle = "#f8fafc";
3826
+ c.fillRect(0, y, 12, rowH);
3827
+ c.fillStyle = "#94a3b8";
3828
+ c.font = "6px sans-serif";
3829
+ c.fillText(String(ri), 3, y + 9);
3830
+ c.strokeStyle = "#e2e8f0";
3831
+ c.strokeRect(0, y, canvas.width, rowH);
3832
+ c.fillStyle = "#334155";
3833
+ c.font = "6px sans-serif";
3834
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3835
+ const cellRef = `${colHeaders[ci]}${ri}`;
3836
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3837
+ if (cellVal !== void 0) {
3838
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3839
+ }
3840
+ }
3841
+ }
3842
+ c.strokeStyle = "#cbd5e1";
3843
+ c.lineWidth = 1;
3844
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3845
+ }
3846
+ }));
3847
+ },
3222
3848
  download: () => {
3223
3849
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3224
3850
  const url = URL.createObjectURL(blob);
@@ -3251,7 +3877,16 @@ var CsvPlugin = class {
3251
3877
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3252
3878
  }
3253
3879
  getToolbarActions(instance) {
3254
- return [
3880
+ const actions = [];
3881
+ actions.push({
3882
+ id: "thumbnails",
3883
+ icon: "thumbnails",
3884
+ label: "Sheet Thumbnails",
3885
+ type: "button",
3886
+ group: "navigation",
3887
+ execute: () => instance.toggleThumbnails?.()
3888
+ });
3889
+ actions.push(
3255
3890
  {
3256
3891
  id: "zoom-out",
3257
3892
  icon: "zoom-out",
@@ -3291,8 +3926,19 @@ var CsvPlugin = class {
3291
3926
  execute: () => {
3292
3927
  instance.print?.();
3293
3928
  }
3929
+ },
3930
+ {
3931
+ id: "open-window",
3932
+ icon: "open-window",
3933
+ label: "Open in Separate Full Window",
3934
+ type: "button",
3935
+ group: "actions",
3936
+ execute: () => {
3937
+ instance.openInSeparateWindow?.();
3938
+ }
3294
3939
  }
3295
- ];
3940
+ );
3941
+ return actions;
3296
3942
  }
3297
3943
  async render(ctx) {
3298
3944
  const decoder = new TextDecoder("utf-8");
@@ -3303,57 +3949,156 @@ var CsvPlugin = class {
3303
3949
  container.style.overflow = "auto";
3304
3950
  container.style.padding = "16px";
3305
3951
  container.style.boxSizing = "border-box";
3306
- container.style.transformOrigin = "top left";
3952
+ const tableWrapper = document.createElement("div");
3953
+ tableWrapper.style.transformOrigin = "top left";
3954
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3955
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3956
+ const headerLine = allLines[0] || "";
3957
+ const headerCells = headerLine.split(delimiter);
3958
+ const dataLines = allLines.slice(1);
3307
3959
  const table = document.createElement("table");
3308
3960
  table.style.borderCollapse = "collapse";
3309
- table.style.width = "100%";
3310
- table.style.fontFamily = "monospace";
3961
+ table.style.minWidth = "100%";
3962
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3311
3963
  table.style.fontSize = "13px";
3312
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3313
- const rows = text.split(/\r?\n/).slice(0, 500);
3314
- rows.forEach((row, rowIndex) => {
3315
- if (!row.trim()) return;
3964
+ table.style.background = "#ffffff";
3965
+ table.style.border = "1px solid #d0d7de";
3966
+ const headerTr = document.createElement("tr");
3967
+ headerCells.forEach((cell) => {
3968
+ const th = document.createElement("th");
3969
+ th.textContent = cell.trim();
3970
+ th.style.border = "1px solid #d0d7de";
3971
+ th.style.padding = "8px 12px";
3972
+ th.style.backgroundColor = "#f6f8fa";
3973
+ th.style.fontWeight = "600";
3974
+ th.style.whiteSpace = "nowrap";
3975
+ headerTr.appendChild(th);
3976
+ });
3977
+ table.appendChild(headerTr);
3978
+ dataLines.forEach((row, idx) => {
3316
3979
  const tr = document.createElement("tr");
3980
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3317
3981
  const cells = row.split(delimiter);
3318
3982
  cells.forEach((cell) => {
3319
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3983
+ const td = document.createElement("td");
3320
3984
  td.textContent = cell.trim();
3321
3985
  td.style.border = "1px solid #d0d7de";
3322
3986
  td.style.padding = "6px 12px";
3323
- if (rowIndex === 0) {
3324
- td.style.backgroundColor = "#f6f8fa";
3325
- td.style.fontWeight = "bold";
3326
- }
3987
+ td.style.whiteSpace = "nowrap";
3327
3988
  tr.appendChild(td);
3328
3989
  });
3329
3990
  table.appendChild(tr);
3330
3991
  });
3331
- container.appendChild(table);
3992
+ tableWrapper.appendChild(table);
3993
+ container.appendChild(tableWrapper);
3332
3994
  ctx.container.appendChild(container);
3333
3995
  let scale = 1;
3996
+ const calculateFitScale = () => {
3997
+ const availW = Math.max(280, container.clientWidth - 48);
3998
+ const tW = table.offsetWidth || 800;
3999
+ return Math.max(0.4, Math.min(1, availW / tW));
4000
+ };
4001
+ const applyScale = () => {
4002
+ tableWrapper.style.transform = `scale(${scale})`;
4003
+ };
4004
+ requestAnimationFrame(() => {
4005
+ scale = calculateFitScale();
4006
+ applyScale();
4007
+ });
4008
+ const ro = new ResizeObserver(() => {
4009
+ scale = calculateFitScale();
4010
+ applyScale();
4011
+ });
4012
+ ro.observe(container);
3334
4013
  const cleanup = () => {
4014
+ ro.disconnect();
3335
4015
  container.remove();
3336
4016
  ctx.container.innerHTML = "";
3337
4017
  };
3338
4018
  ctx.signal.addEventListener("abort", cleanup);
3339
4019
  return {
3340
4020
  destroy: cleanup,
4021
+ getPageCount: () => 1,
4022
+ getCurrentPage: () => 1,
4023
+ goToPage: () => {
4024
+ },
4025
+ getThumbnails: () => {
4026
+ const sheetName = ctx.metadata.name?.replace(/\.[^/.]+$/, "") || "Sheet1";
4027
+ return [{
4028
+ index: 0,
4029
+ label: sheetName,
4030
+ render: async (canvas) => {
4031
+ canvas.width = 140;
4032
+ canvas.height = 100;
4033
+ const c = canvas.getContext("2d");
4034
+ if (!c) return;
4035
+ c.fillStyle = "#ffffff";
4036
+ c.fillRect(0, 0, canvas.width, canvas.height);
4037
+ c.fillStyle = "#107c41";
4038
+ c.fillRect(0, 0, canvas.width, 16);
4039
+ c.fillStyle = "#ffffff";
4040
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
4041
+ c.fillText(`\u{1F4CA} ${sheetName.slice(0, 16)}`, 6, 11);
4042
+ const startY = 17;
4043
+ const rowH = 13;
4044
+ const colW = 30;
4045
+ const cols = headerCells.slice(0, 4).map((h) => h.trim() || "Col");
4046
+ c.fillStyle = "#f1f5f9";
4047
+ c.fillRect(0, startY, canvas.width, rowH);
4048
+ c.strokeStyle = "#cbd5e1";
4049
+ c.lineWidth = 0.8;
4050
+ c.strokeRect(0, startY, canvas.width, rowH);
4051
+ c.fillStyle = "#64748b";
4052
+ c.font = "bold 7px sans-serif";
4053
+ for (let ci = 0; ci < cols.length; ci++) {
4054
+ c.fillText(cols[ci].slice(0, 5), 14 + ci * colW, startY + 9);
4055
+ }
4056
+ for (let ri = 0; ri < Math.min(5, dataLines.length); ri++) {
4057
+ const y = startY + (ri + 1) * rowH;
4058
+ if (y > canvas.height - 2) break;
4059
+ c.fillStyle = "#f8fafc";
4060
+ c.fillRect(0, y, 12, rowH);
4061
+ c.fillStyle = "#94a3b8";
4062
+ c.font = "6px sans-serif";
4063
+ c.fillText(String(ri + 1), 3, y + 9);
4064
+ c.strokeStyle = "#e2e8f0";
4065
+ c.strokeRect(0, y, canvas.width, rowH);
4066
+ const rowCells = dataLines[ri].split(delimiter);
4067
+ c.fillStyle = "#334155";
4068
+ c.font = "6px sans-serif";
4069
+ for (let ci = 0; ci < cols.length; ci++) {
4070
+ const val = (rowCells[ci] || "").trim();
4071
+ if (val) {
4072
+ c.fillText(val.slice(0, 6), 14 + ci * colW, y + 9);
4073
+ }
4074
+ }
4075
+ }
4076
+ c.strokeStyle = "#cbd5e1";
4077
+ c.lineWidth = 1;
4078
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4079
+ }
4080
+ }];
4081
+ },
3341
4082
  zoomIn: () => {
3342
4083
  scale += 0.1;
3343
- container.style.transform = `scale(${scale})`;
4084
+ applyScale();
3344
4085
  },
3345
4086
  zoomOut: () => {
3346
4087
  scale = Math.max(0.2, scale - 0.1);
3347
- container.style.transform = `scale(${scale})`;
4088
+ applyScale();
3348
4089
  },
3349
4090
  getZoom: () => scale,
3350
4091
  setZoom: (level) => {
3351
4092
  scale = level;
3352
- container.style.transform = `scale(${scale})`;
4093
+ applyScale();
3353
4094
  },
3354
4095
  fitToPage: () => {
3355
- scale = 1;
3356
- container.style.transform = `scale(1)`;
4096
+ scale = calculateFitScale();
4097
+ applyScale();
4098
+ },
4099
+ rotateCW: () => {
4100
+ },
4101
+ rotateCCW: () => {
3357
4102
  },
3358
4103
  download: () => {
3359
4104
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3415,30 +4160,36 @@ var CodePlugin = class {
3415
4160
  return false;
3416
4161
  }
3417
4162
  getToolbarActions(instance) {
3418
- const totalPages = instance.getPageCount?.() ?? 1;
3419
- const actions = [];
3420
- if (totalPages > 1) {
3421
- actions.push({
3422
- id: "page-nav",
3423
- icon: "",
3424
- label: "Page Navigation",
3425
- type: "page-nav",
3426
- group: "navigation",
3427
- value: instance.getCurrentPage?.() ?? 1,
3428
- max: totalPages,
3429
- execute: (action, page) => {
3430
- const cur = instance.getCurrentPage?.() ?? 1;
3431
- const max = instance.getPageCount?.() ?? 1;
3432
- if (action === "prev") {
3433
- if (cur > 1) instance.goToPage?.(cur - 1);
3434
- } else if (action === "next") {
3435
- if (cur < max) instance.goToPage?.(cur + 1);
3436
- } else if (typeof page === "number") {
3437
- instance.goToPage?.(page);
3438
- }
4163
+ const totalPages = instance.getPageCount?.() ?? 1;
4164
+ const actions = [];
4165
+ actions.push({
4166
+ id: "thumbnails",
4167
+ icon: "thumbnails",
4168
+ label: "Page Thumbnails",
4169
+ type: "button",
4170
+ group: "navigation",
4171
+ execute: () => instance.toggleThumbnails?.()
4172
+ });
4173
+ actions.push({
4174
+ id: "page-nav",
4175
+ icon: "",
4176
+ label: "Page Navigation",
4177
+ type: "page-nav",
4178
+ group: "navigation",
4179
+ value: instance.getCurrentPage?.() ?? 1,
4180
+ max: totalPages,
4181
+ execute: (action, page) => {
4182
+ const cur = instance.getCurrentPage?.() ?? 1;
4183
+ const max = instance.getPageCount?.() ?? 1;
4184
+ if (action === "prev") {
4185
+ if (cur > 1) instance.goToPage?.(cur - 1);
4186
+ } else if (action === "next") {
4187
+ if (cur < max) instance.goToPage?.(cur + 1);
4188
+ } else if (typeof page === "number") {
4189
+ instance.goToPage?.(page);
3439
4190
  }
3440
- });
3441
- }
4191
+ }
4192
+ });
3442
4193
  actions.push(
3443
4194
  {
3444
4195
  id: "zoom-out",
@@ -3460,26 +4211,6 @@ var CodePlugin = class {
3460
4211
  instance.zoomIn?.();
3461
4212
  }
3462
4213
  },
3463
- {
3464
- id: "fit-page",
3465
- icon: "fit-page",
3466
- label: "Fit to View",
3467
- type: "button",
3468
- group: "zoom",
3469
- execute: () => {
3470
- instance.fitToPage?.();
3471
- }
3472
- },
3473
- {
3474
- id: "rotate-cw",
3475
- icon: "rotate-cw",
3476
- label: "Rotate",
3477
- type: "button",
3478
- group: "view",
3479
- execute: () => {
3480
- instance.rotateCW?.();
3481
- }
3482
- },
3483
4214
  {
3484
4215
  id: "copy",
3485
4216
  icon: "copy",
@@ -3582,16 +4313,19 @@ var CodePlugin = class {
3582
4313
  let fontSize = 13;
3583
4314
  let rotation = 0;
3584
4315
  let zoomLevel = 1;
4316
+ let isUserZoomed = false;
3585
4317
  const pre = document.createElement("pre");
3586
4318
  const code = document.createElement("code");
3587
4319
  if (isTxt) {
4320
+ container.style.overflowX = "hidden";
4321
+ container.style.overflowY = "auto";
3588
4322
  container.style.backgroundColor = "#f1f5f9";
3589
- container.style.padding = "32px";
4323
+ container.style.padding = "16px 8px";
3590
4324
  container.style.boxSizing = "border-box";
3591
4325
  container.style.display = "flex";
3592
4326
  container.style.flexDirection = "column";
3593
4327
  container.style.alignItems = "center";
3594
- pre.style.margin = "0";
4328
+ pre.style.margin = "0 auto";
3595
4329
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3596
4330
  pre.style.fontSize = "13px";
3597
4331
  pre.style.color = "#1e293b";
@@ -3603,10 +4337,12 @@ var CodePlugin = class {
3603
4337
  pre.style.minHeight = "1056px";
3604
4338
  pre.style.padding = "72px 56px";
3605
4339
  pre.style.boxSizing = "border-box";
3606
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4340
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3607
4341
  pre.style.borderRadius = "4px";
3608
4342
  pre.style.transformOrigin = "top center";
4343
+ pre.style.transition = "transform 0.15s ease";
3609
4344
  } else {
4345
+ container.style.overflow = "auto";
3610
4346
  container.style.backgroundColor = "#1e1e1e";
3611
4347
  container.style.color = "#d4d4d4";
3612
4348
  container.style.padding = "16px";
@@ -3638,42 +4374,9 @@ var CodePlugin = class {
3638
4374
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3639
4375
  pre.appendChild(code);
3640
4376
  container.appendChild(pre);
3641
- let indicator = null;
3642
- if (totalPages > 1) {
3643
- indicator = document.createElement("div");
3644
- indicator.className = "fp-code-page-indicator";
3645
- indicator.style.position = "sticky";
3646
- indicator.style.bottom = "16px";
3647
- if (isTxt) {
3648
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3649
- indicator.style.color = "#334155";
3650
- indicator.style.border = "1px solid #e2e8f0";
3651
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3652
- } else {
3653
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3654
- indicator.style.color = "#f8fafc";
3655
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3656
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3657
- indicator.style.backdropFilter = "blur(8px)";
3658
- }
3659
- indicator.style.fontSize = "12px";
3660
- indicator.style.fontWeight = "600";
3661
- indicator.style.padding = "5px 14px";
3662
- indicator.style.borderRadius = "20px";
3663
- indicator.style.zIndex = "10";
3664
- indicator.style.userSelect = "none";
3665
- indicator.style.pointerEvents = "none";
3666
- indicator.style.textAlign = "center";
3667
- indicator.style.width = "fit-content";
3668
- indicator.style.margin = "16px auto 0";
3669
- container.appendChild(indicator);
3670
- }
3671
4377
  const showPage = (pageNum) => {
3672
4378
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3673
4379
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3674
- if (indicator) {
3675
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3676
- }
3677
4380
  container.scrollTop = 0;
3678
4381
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3679
4382
  };
@@ -3681,36 +4384,90 @@ var CodePlugin = class {
3681
4384
  showPage(1);
3682
4385
  }
3683
4386
  ctx.container.appendChild(container);
3684
- const cleanup = () => {
3685
- indicator?.remove();
3686
- container.remove();
3687
- ctx.container.innerHTML = "";
4387
+ const calculateTxtFit = () => {
4388
+ const availW = Math.max(280, container.clientWidth - 32);
4389
+ return Math.max(0.4, Math.min(3, availW / 816));
3688
4390
  };
3689
- ctx.signal.addEventListener("abort", cleanup);
3690
4391
  const updateTransform = () => {
3691
4392
  if (isTxt) {
3692
4393
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4394
+ const scaledH = 1056 * zoomLevel;
4395
+ const extraH = Math.max(0, scaledH - 1056);
4396
+ pre.style.marginBottom = `${extraH + 32}px`;
3693
4397
  } else {
3694
4398
  pre.style.transform = `rotate(${rotation}deg)`;
3695
4399
  pre.style.fontSize = `${fontSize}px`;
3696
4400
  }
3697
4401
  };
4402
+ let resizeObserver = null;
4403
+ if (isTxt) {
4404
+ setTimeout(() => {
4405
+ zoomLevel = calculateTxtFit();
4406
+ updateTransform();
4407
+ }, 60);
4408
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4409
+ if (!isUserZoomed) {
4410
+ zoomLevel = calculateTxtFit();
4411
+ updateTransform();
4412
+ }
4413
+ }) : null;
4414
+ resizeObserver?.observe(container);
4415
+ }
4416
+ const cleanup = () => {
4417
+ resizeObserver?.disconnect();
4418
+ container.remove();
4419
+ ctx.container.innerHTML = "";
4420
+ };
4421
+ ctx.signal.addEventListener("abort", cleanup);
3698
4422
  return {
3699
4423
  destroy: cleanup,
3700
4424
  getPageCount: () => totalPages,
3701
4425
  getCurrentPage: () => currentPage,
3702
4426
  goToPage: (page) => showPage(page),
4427
+ getThumbnails: () => {
4428
+ return rawPages.map((pageText, idx) => ({
4429
+ index: idx,
4430
+ label: `Page ${idx + 1}`,
4431
+ render: async (canvas) => {
4432
+ canvas.width = 140;
4433
+ canvas.height = 180;
4434
+ const c = canvas.getContext("2d");
4435
+ if (!c) return;
4436
+ c.fillStyle = "#ffffff";
4437
+ c.fillRect(0, 0, canvas.width, canvas.height);
4438
+ c.strokeStyle = "#cbd5e1";
4439
+ c.lineWidth = 1;
4440
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4441
+ c.fillStyle = "#64748b";
4442
+ const lines = (pageText || "").split("\n").slice(0, 24);
4443
+ const lineH = 6;
4444
+ const startY = 14;
4445
+ const startX = 12;
4446
+ const maxW = canvas.width - 24;
4447
+ c.font = "5px monospace";
4448
+ for (let li = 0; li < lines.length; li++) {
4449
+ const l = lines[li].trim();
4450
+ if (!l) continue;
4451
+ const y = startY + li * lineH;
4452
+ if (y > canvas.height - 12) break;
4453
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4454
+ }
4455
+ }
4456
+ }));
4457
+ },
3703
4458
  zoomIn: () => {
4459
+ isUserZoomed = true;
3704
4460
  if (isTxt) {
3705
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4461
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3706
4462
  } else {
3707
4463
  fontSize = Math.min(32, fontSize + 2);
3708
4464
  }
3709
4465
  updateTransform();
3710
4466
  },
3711
4467
  zoomOut: () => {
4468
+ isUserZoomed = true;
3712
4469
  if (isTxt) {
3713
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4470
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3714
4471
  } else {
3715
4472
  fontSize = Math.max(8, fontSize - 2);
3716
4473
  }
@@ -3718,6 +4475,7 @@ var CodePlugin = class {
3718
4475
  },
3719
4476
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3720
4477
  setZoom: (level) => {
4478
+ isUserZoomed = true;
3721
4479
  if (isTxt) {
3722
4480
  zoomLevel = level;
3723
4481
  } else {
@@ -3726,9 +4484,10 @@ var CodePlugin = class {
3726
4484
  updateTransform();
3727
4485
  },
3728
4486
  fitToPage: () => {
4487
+ isUserZoomed = false;
3729
4488
  fontSize = 13;
3730
4489
  rotation = 0;
3731
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4490
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3732
4491
  updateTransform();
3733
4492
  },
3734
4493
  rotateCW: () => {
@@ -4595,6 +5354,148 @@ var ThreeDPlugin = class {
4595
5354
  function threeDPlugin() {
4596
5355
  return new ThreeDPlugin();
4597
5356
  }
5357
+ function cleanRtfText(raw) {
5358
+ return raw.replace(/\\bin\d+\s/g, "").replace(/{\\\*[^}]+}/g, "").replace(/\\u(-?\d+)\??/g, (_, code) => {
5359
+ let num = parseInt(code, 10);
5360
+ if (num < 0) num += 65536;
5361
+ return String.fromCharCode(num);
5362
+ }).replace(/\\'([0-9a-fA-F]{2})/g, (_, hex) => {
5363
+ return String.fromCharCode(parseInt(hex, 16));
5364
+ }).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();
5365
+ }
5366
+ function parseRtfTables(rtfText) {
5367
+ const rowSegments = rtfText.split(/\\row\b/);
5368
+ if (rowSegments.length <= 1) return [];
5369
+ const rawTables = [];
5370
+ let currentTable = null;
5371
+ let charOffset = 0;
5372
+ for (let i = 0; i < rowSegments.length - 1; i++) {
5373
+ const segment = rowSegments[i];
5374
+ const segmentStart = charOffset;
5375
+ const segmentEnd = charOffset + segment.length + 4;
5376
+ const cellParts = segment.split(/\\cell\b/);
5377
+ if (cellParts.length > 1) {
5378
+ const cellxMatches = [...segment.matchAll(/\\cellx(\d+)/g)].map((m) => parseInt(m[1], 10));
5379
+ let cellWidths = [];
5380
+ if (cellxMatches.length > 0) {
5381
+ let seq = [cellxMatches[0]];
5382
+ for (let k = 1; k < cellxMatches.length; k++) {
5383
+ if (cellxMatches[k] > cellxMatches[k - 1]) seq.push(cellxMatches[k]);
5384
+ else break;
5385
+ }
5386
+ let prev = 0;
5387
+ cellWidths = seq.map((x) => {
5388
+ const w = x - prev;
5389
+ prev = x;
5390
+ return w;
5391
+ });
5392
+ }
5393
+ const cells = [];
5394
+ for (let c = 0; c < cellParts.length - 1; c++) {
5395
+ let rawCell = cellParts[c];
5396
+ if (c === 0) {
5397
+ const lastTrowd = rawCell.lastIndexOf("\\trowd");
5398
+ if (lastTrowd !== -1) rawCell = rawCell.slice(lastTrowd);
5399
+ }
5400
+ cells.push(cleanRtfText(rawCell));
5401
+ }
5402
+ const isNewTable = !currentTable || segmentStart - currentTable.lastEnd > 2500;
5403
+ if (isNewTable) {
5404
+ const prevTextSlice = rtfText.slice(Math.max(0, segmentStart - 4e3), segmentStart);
5405
+ const cleanPrev = cleanRtfText(prevTextSlice);
5406
+ const anchorBefore = cleanPrev.slice(-80).trim();
5407
+ currentTable = {
5408
+ id: `RTF_TABLE_${rawTables.length}`,
5409
+ startPos: segmentStart,
5410
+ lastEnd: segmentEnd,
5411
+ rows: [],
5412
+ columnWidths: cellWidths,
5413
+ anchorBefore,
5414
+ anchorAfter: ""
5415
+ };
5416
+ rawTables.push(currentTable);
5417
+ }
5418
+ if (currentTable) {
5419
+ currentTable.rows.push({
5420
+ cells,
5421
+ widths: cellWidths.length === cells.length ? cellWidths : currentTable.columnWidths
5422
+ });
5423
+ currentTable.lastEnd = segmentEnd;
5424
+ }
5425
+ }
5426
+ charOffset += segment.length + 4;
5427
+ }
5428
+ rawTables.forEach((tbl) => {
5429
+ const afterSlice = rtfText.slice(tbl.lastEnd, Math.min(rtfText.length, tbl.lastEnd + 2e3));
5430
+ const cleanAfter = cleanRtfText(afterSlice);
5431
+ tbl.anchorAfter = cleanAfter.slice(0, 80).trim();
5432
+ });
5433
+ return rawTables.map((tbl) => {
5434
+ const totalW = (tbl.columnWidths || []).reduce((a, b) => a + b, 0);
5435
+ const colPercents = totalW > 0 ? tbl.columnWidths.map((w) => Math.round(w / totalW * 100)) : [];
5436
+ return {
5437
+ id: tbl.id,
5438
+ rows: tbl.rows.map((r) => r.cells),
5439
+ colPercents,
5440
+ anchorBefore: tbl.anchorBefore,
5441
+ anchorAfter: tbl.anchorAfter
5442
+ };
5443
+ });
5444
+ }
5445
+ function renderRtfTableElement(table) {
5446
+ const tableEl = document.createElement("table");
5447
+ tableEl.className = "fp-rtf-table";
5448
+ tableEl.style.width = "100%";
5449
+ tableEl.style.borderCollapse = "collapse";
5450
+ tableEl.style.margin = "16px 0 20px";
5451
+ tableEl.style.fontSize = "10pt";
5452
+ tableEl.style.lineHeight = "1.5";
5453
+ tableEl.style.border = "1px solid #cbd5e1";
5454
+ tableEl.style.borderRadius = "4px";
5455
+ tableEl.style.boxSizing = "border-box";
5456
+ tableEl.style.backgroundColor = "#ffffff";
5457
+ const thead = document.createElement("thead");
5458
+ const tbody = document.createElement("tbody");
5459
+ table.rows.forEach((rowCells, rIdx) => {
5460
+ const tr = document.createElement("tr");
5461
+ const isHeader = rIdx === 0;
5462
+ if (isHeader) {
5463
+ tr.style.backgroundColor = "#f8fafc";
5464
+ tr.style.borderBottom = "2px solid #cbd5e1";
5465
+ } else {
5466
+ tr.style.borderBottom = "1px solid #e2e8f0";
5467
+ if (rIdx % 2 === 0) {
5468
+ tr.style.backgroundColor = "#fcfdfe";
5469
+ }
5470
+ }
5471
+ rowCells.forEach((cellText, cIdx) => {
5472
+ const cellEl = document.createElement(isHeader ? "th" : "td");
5473
+ cellEl.style.padding = "8px 12px";
5474
+ cellEl.style.border = "1px solid #cbd5e1";
5475
+ cellEl.style.verticalAlign = "top";
5476
+ cellEl.style.color = isHeader ? "#0f172a" : "#1e293b";
5477
+ cellEl.style.fontWeight = isHeader ? "600" : "normal";
5478
+ if (table.colPercents && table.colPercents[cIdx]) {
5479
+ cellEl.style.width = `${table.colPercents[cIdx]}%`;
5480
+ }
5481
+ if (/^\d+$/.test(cellText.trim()) || cellText.trim() === "#") {
5482
+ cellEl.style.textAlign = "center";
5483
+ } else {
5484
+ cellEl.style.textAlign = "left";
5485
+ }
5486
+ cellEl.textContent = cellText;
5487
+ tr.appendChild(cellEl);
5488
+ });
5489
+ if (isHeader) {
5490
+ thead.appendChild(tr);
5491
+ } else {
5492
+ tbody.appendChild(tr);
5493
+ }
5494
+ });
5495
+ tableEl.appendChild(thead);
5496
+ tableEl.appendChild(tbody);
5497
+ return tableEl;
5498
+ }
4598
5499
  var RtfPlugin = class {
4599
5500
  id = "rtf";
4600
5501
  name = "Rich Text Format (RTF)";
@@ -4609,29 +5510,35 @@ var RtfPlugin = class {
4609
5510
  getToolbarActions(instance) {
4610
5511
  const totalPages = instance.getPageCount?.() ?? 1;
4611
5512
  const actions = [];
4612
- if (totalPages > 1) {
4613
- actions.push({
4614
- id: "page-nav",
4615
- icon: "",
4616
- label: "Page Navigation",
4617
- type: "page-nav",
4618
- group: "navigation",
4619
- value: instance.getCurrentPage?.() ?? 1,
4620
- max: totalPages,
4621
- execute: (action, page) => {
4622
- const cur = instance.getCurrentPage?.() ?? 1;
4623
- const max = instance.getPageCount?.() ?? 1;
4624
- if (action === "prev") {
4625
- if (cur > 1) instance.goToPage?.(cur - 1);
4626
- } else if (action === "next") {
4627
- if (cur < max) instance.goToPage?.(cur + 1);
4628
- } else if (typeof page === "number") {
4629
- instance.goToPage?.(page);
4630
- }
5513
+ actions.push({
5514
+ id: "page-nav",
5515
+ icon: "",
5516
+ label: "Page Navigation",
5517
+ type: "page-nav",
5518
+ group: "navigation",
5519
+ value: instance.getCurrentPage?.() ?? 1,
5520
+ max: totalPages,
5521
+ execute: (action, page) => {
5522
+ const cur = instance.getCurrentPage?.() ?? 1;
5523
+ const max = instance.getPageCount?.() ?? 1;
5524
+ if (action === "prev") {
5525
+ if (cur > 1) instance.goToPage?.(cur - 1);
5526
+ } else if (action === "next") {
5527
+ if (cur < max) instance.goToPage?.(cur + 1);
5528
+ } else if (typeof page === "number") {
5529
+ instance.goToPage?.(page);
4631
5530
  }
4632
- });
4633
- }
5531
+ }
5532
+ });
4634
5533
  actions.push(
5534
+ {
5535
+ id: "thumbnails",
5536
+ icon: "thumbnails",
5537
+ label: "Thumbnails",
5538
+ type: "button",
5539
+ group: "view",
5540
+ execute: () => instance.toggleThumbnails?.()
5541
+ },
4635
5542
  {
4636
5543
  id: "zoom-out",
4637
5544
  icon: "zoom-out",
@@ -4702,67 +5609,143 @@ var RtfPlugin = class {
4702
5609
  async render(ctx) {
4703
5610
  const wrapper = document.createElement("div");
4704
5611
  wrapper.className = "fp-rtf-wrapper";
4705
- wrapper.style.padding = "32px";
4706
- wrapper.style.maxWidth = "850px";
5612
+ wrapper.style.padding = "0";
5613
+ wrapper.style.width = "816px";
4707
5614
  wrapper.style.margin = "0 auto";
4708
- wrapper.style.backgroundColor = "#fff";
4709
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4710
- wrapper.style.borderRadius = "4px";
4711
- wrapper.style.minHeight = "100%";
5615
+ wrapper.style.boxSizing = "border-box";
5616
+ wrapper.style.display = "flex";
5617
+ wrapper.style.flexDirection = "column";
5618
+ wrapper.style.alignItems = "center";
5619
+ wrapper.style.backgroundColor = "transparent";
4712
5620
  wrapper.style.transformOrigin = "top center";
4713
- wrapper.style.transition = "transform 0.2s ease";
4714
- ctx.container.style.overflow = "auto";
4715
- ctx.container.style.padding = "24px";
5621
+ wrapper.style.transition = "transform 0.15s ease";
5622
+ ctx.container.style.overflowX = "hidden";
5623
+ ctx.container.style.overflowY = "auto";
5624
+ ctx.container.style.padding = "16px 8px";
4716
5625
  ctx.container.style.backgroundColor = "#f1f5f9";
4717
5626
  ctx.container.appendChild(wrapper);
4718
5627
  let scale = 1;
4719
5628
  let rotation = 0;
4720
5629
  let pageElements = [];
4721
- let fitMode = "width";
5630
+ let isUserZoomed = false;
5631
+ let fitMode = ctx?.options?.fitMode || "width";
4722
5632
  const calculateFitScale = (mode = fitMode) => {
4723
5633
  const activeEl = pageElements[currentPage - 1] || wrapper;
4724
- const elW = activeEl.offsetWidth || 850;
4725
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4726
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4727
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5634
+ const elW = 816;
5635
+ const singlePageH = activeEl?.offsetHeight || 1056;
5636
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5637
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4728
5638
  const sW = availW / elW;
4729
5639
  const sH = availH / singlePageH;
4730
5640
  if (mode === "page") {
4731
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5641
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4732
5642
  }
4733
- return Math.max(0.65, Math.min(1.05, sW));
5643
+ return Math.max(0.4, Math.min(3, sW));
4734
5644
  };
4735
5645
  const applyTransform = () => {
4736
5646
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4737
5647
  wrapper.style.transformOrigin = "top center";
5648
+ const activeEl = pageElements[currentPage - 1];
5649
+ const baseH = activeEl?.offsetHeight || 1056;
5650
+ const scaledH = baseH * scale;
5651
+ const extraH = Math.max(0, scaledH - baseH);
5652
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4738
5653
  };
5654
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5655
+ if (!isUserZoomed) {
5656
+ scale = calculateFitScale(fitMode);
5657
+ applyTransform();
5658
+ }
5659
+ }) : null;
5660
+ resizeObserver?.observe(ctx.container);
4739
5661
  setTimeout(() => {
4740
- fitMode = "width";
4741
- scale = calculateFitScale("width");
5662
+ scale = calculateFitScale(fitMode);
4742
5663
  applyTransform();
4743
5664
  }, 60);
4744
5665
  try {
4745
5666
  if (typeof RTFJS__namespace.loggingEnabled === "function") {
4746
5667
  RTFJS__namespace.loggingEnabled(false);
4747
5668
  }
5669
+ const rawText = new TextDecoder("latin1").decode(ctx.buffer);
5670
+ const tables = parseRtfTables(rawText);
4748
5671
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4749
5672
  const htmlElements = await doc.render();
4750
5673
  const contentNodes = [];
4751
- for (const item of htmlElements) {
4752
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4753
- contentNodes.push(...Array.from(item.children));
4754
- } else {
4755
- contentNodes.push(item);
5674
+ const extractBlocks = (nodes) => {
5675
+ for (const item of nodes) {
5676
+ if (!item || !(item instanceof HTMLElement)) continue;
5677
+ const tag = item.tagName.toLowerCase();
5678
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5679
+ const ct = c.tagName.toLowerCase();
5680
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5681
+ });
5682
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5683
+ extractBlocks(Array.from(item.children));
5684
+ } else {
5685
+ const svgs = item.querySelectorAll("svg, img");
5686
+ svgs.forEach((s) => {
5687
+ const el = s;
5688
+ el.style.maxWidth = "100%";
5689
+ el.style.maxHeight = "360px";
5690
+ el.style.display = "block";
5691
+ el.style.margin = "12px auto";
5692
+ if (s.tagName.toLowerCase() === "svg") {
5693
+ s.removeAttribute("width");
5694
+ s.removeAttribute("height");
5695
+ el.style.width = "100%";
5696
+ el.style.maxHeight = "360px";
5697
+ }
5698
+ });
5699
+ if (tag === "p" || tag === "div") {
5700
+ item.style.display = "block";
5701
+ item.style.marginBottom = "12px";
5702
+ item.style.lineHeight = "1.6";
5703
+ }
5704
+ contentNodes.push(item);
5705
+ }
4756
5706
  }
4757
- }
5707
+ };
5708
+ extractBlocks(htmlElements);
5709
+ tables.forEach((table) => {
5710
+ const tableEl = renderRtfTableElement(table);
5711
+ let inserted = false;
5712
+ if (table.anchorBefore && table.anchorBefore.length > 10) {
5713
+ const keyword = table.anchorBefore.slice(-35).trim();
5714
+ for (let i = 0; i < contentNodes.length; i++) {
5715
+ if (contentNodes[i].textContent?.includes(keyword)) {
5716
+ contentNodes.splice(i + 1, 0, tableEl);
5717
+ inserted = true;
5718
+ break;
5719
+ }
5720
+ }
5721
+ }
5722
+ if (!inserted && table.anchorAfter && table.anchorAfter.length > 10) {
5723
+ const keyword = table.anchorAfter.slice(0, 35).trim();
5724
+ for (let i = 0; i < contentNodes.length; i++) {
5725
+ if (contentNodes[i].textContent?.includes(keyword)) {
5726
+ contentNodes.splice(i, 0, tableEl);
5727
+ inserted = true;
5728
+ break;
5729
+ }
5730
+ }
5731
+ }
5732
+ if (!inserted) {
5733
+ if (contentNodes.length > 2) {
5734
+ contentNodes.splice(2, 0, tableEl);
5735
+ } else {
5736
+ contentNodes.push(tableEl);
5737
+ }
5738
+ }
5739
+ });
4758
5740
  wrapper.innerHTML = "";
4759
5741
  contentNodes.forEach((node) => wrapper.appendChild(node));
4760
5742
  const childHeights = contentNodes.map((c) => {
4761
5743
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4762
5744
  const offH = c.offsetHeight || 0;
5745
+ const realH = Math.max(rectH, offH);
5746
+ if (realH > 0) return realH;
4763
5747
  const textLen = c.textContent?.trim().length || 0;
4764
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4765
- return Math.max(rectH, offH, estH);
5748
+ return Math.max(24, Math.ceil(textLen / 95) * 22 + 12);
4766
5749
  });
4767
5750
  wrapper.innerHTML = "";
4768
5751
  const createRtfCard = () => {
@@ -4771,35 +5754,49 @@ var RtfPlugin = class {
4771
5754
  card.style.backgroundColor = "#ffffff";
4772
5755
  card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4773
5756
  card.style.borderRadius = "4px";
4774
- card.style.padding = "72px 56px";
5757
+ card.style.padding = "64px 56px";
4775
5758
  card.style.width = "816px";
4776
5759
  card.style.minHeight = "1056px";
4777
5760
  card.style.boxSizing = "border-box";
4778
5761
  card.style.marginBottom = "24px";
4779
5762
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4780
5763
  card.style.lineHeight = "1.6";
5764
+ card.style.color = "#1e293b";
4781
5765
  return card;
4782
5766
  };
4783
5767
  let curCard = createRtfCard();
4784
5768
  wrapper.appendChild(curCard);
4785
5769
  pageElements = [curCard];
4786
5770
  let curH = 0;
4787
- const maxH = 912;
5771
+ const maxH = 928;
4788
5772
  for (let i = 0; i < contentNodes.length; i++) {
4789
5773
  const child = contentNodes[i];
4790
5774
  const chH = childHeights[i];
4791
- curCard.appendChild(child);
4792
- curH += chH;
4793
- if (curH >= maxH && i < contentNodes.length - 1) {
5775
+ const isChildEmpty = (child.textContent || "").trim().length === 0 && !child.querySelector("table, img, svg, canvas");
5776
+ if (curH === 0 && isChildEmpty) {
5777
+ continue;
5778
+ }
5779
+ if (curH + chH > maxH && curCard.childNodes.length > 0) {
4794
5780
  curCard = createRtfCard();
4795
5781
  wrapper.appendChild(curCard);
4796
5782
  pageElements.push(curCard);
4797
5783
  curH = 0;
4798
5784
  }
5785
+ curCard.appendChild(child);
5786
+ curH += chH;
5787
+ }
5788
+ pageElements = pageElements.filter((card) => {
5789
+ const hasText = (card.textContent || "").trim().length > 0;
5790
+ const hasMedia = card.querySelector("table, img, svg, canvas") !== null;
5791
+ return hasText || hasMedia;
5792
+ });
5793
+ if (pageElements.length === 0) {
5794
+ pageElements = [curCard];
4799
5795
  }
4800
5796
  } catch (err) {
4801
5797
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
4802
5798
  const text = new TextDecoder("latin1").decode(ctx.buffer);
5799
+ const tables = parseRtfTables(text);
4803
5800
  const rawPages = text.split(/\\page\b/).map((segment) => {
4804
5801
  return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4805
5802
  }).filter((p) => p.length > 0);
@@ -4810,47 +5807,26 @@ var RtfPlugin = class {
4810
5807
  pageCard.style.backgroundColor = "#ffffff";
4811
5808
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4812
5809
  pageCard.style.borderRadius = "4px";
4813
- pageCard.style.padding = "72px 56px";
5810
+ pageCard.style.padding = "64px 56px";
4814
5811
  pageCard.style.width = "816px";
4815
5812
  pageCard.style.minHeight = "1056px";
4816
- pageCard.style.maxWidth = "100%";
4817
5813
  pageCard.style.boxSizing = "border-box";
4818
- pageCard.style.fontFamily = "serif";
5814
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4819
5815
  pageCard.style.fontSize = "12pt";
4820
5816
  pageCard.style.lineHeight = "1.6";
4821
5817
  pageCard.style.color = "#1e293b";
4822
5818
  pageCard.style.whiteSpace = "pre-wrap";
4823
5819
  pageCard.style.display = i === 0 ? "block" : "none";
4824
5820
  pageCard.textContent = pages[i];
5821
+ if (i === 1 && tables.length > 0) {
5822
+ tables.forEach((tbl) => pageCard.appendChild(renderRtfTableElement(tbl)));
5823
+ }
4825
5824
  wrapper.appendChild(pageCard);
4826
5825
  pageElements.push(pageCard);
4827
5826
  }
4828
5827
  }
4829
5828
  const totalPages = Math.max(1, pageElements.length);
4830
5829
  let currentPage = 1;
4831
- let indicator = null;
4832
- if (totalPages > 1) {
4833
- indicator = document.createElement("div");
4834
- indicator.className = "fp-rtf-page-indicator";
4835
- indicator.style.position = "sticky";
4836
- indicator.style.bottom = "16px";
4837
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4838
- indicator.style.backdropFilter = "blur(8px)";
4839
- indicator.style.color = "#f8fafc";
4840
- indicator.style.fontSize = "12px";
4841
- indicator.style.fontWeight = "600";
4842
- indicator.style.padding = "5px 14px";
4843
- indicator.style.borderRadius = "20px";
4844
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4845
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4846
- indicator.style.zIndex = "10";
4847
- indicator.style.userSelect = "none";
4848
- indicator.style.pointerEvents = "none";
4849
- indicator.style.textAlign = "center";
4850
- indicator.style.width = "fit-content";
4851
- indicator.style.margin = "16px auto 0";
4852
- ctx.container.appendChild(indicator);
4853
- }
4854
5830
  const showPage = (pageNum) => {
4855
5831
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4856
5832
  if (totalPages > 1) {
@@ -4858,9 +5834,6 @@ var RtfPlugin = class {
4858
5834
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4859
5835
  });
4860
5836
  }
4861
- if (indicator) {
4862
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4863
- }
4864
5837
  ctx.container.scrollTop = 0;
4865
5838
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4866
5839
  };
@@ -4868,7 +5841,7 @@ var RtfPlugin = class {
4868
5841
  showPage(1);
4869
5842
  }
4870
5843
  const cleanup = () => {
4871
- indicator?.remove();
5844
+ resizeObserver?.disconnect();
4872
5845
  wrapper.remove();
4873
5846
  ctx.container.innerHTML = "";
4874
5847
  };
@@ -4878,22 +5851,52 @@ var RtfPlugin = class {
4878
5851
  getPageCount: () => totalPages,
4879
5852
  getCurrentPage: () => currentPage,
4880
5853
  goToPage: (page) => showPage(page),
5854
+ getThumbnails: async () => {
5855
+ const items = [];
5856
+ for (let idx = 0; idx < totalPages; idx++) {
5857
+ const pageIdx = idx;
5858
+ items.push({
5859
+ index: pageIdx,
5860
+ label: `Page ${pageIdx + 1}`,
5861
+ render: async (canvas) => {
5862
+ const ctx2d = canvas.getContext("2d");
5863
+ if (!ctx2d) return;
5864
+ canvas.width = 140;
5865
+ canvas.height = 180;
5866
+ ctx2d.fillStyle = "#ffffff";
5867
+ ctx2d.fillRect(0, 0, 140, 180);
5868
+ ctx2d.strokeStyle = "#cbd5e1";
5869
+ ctx2d.lineWidth = 1;
5870
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
5871
+ ctx2d.fillStyle = "#334155";
5872
+ ctx2d.font = 'bold 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
5873
+ ctx2d.textAlign = "center";
5874
+ ctx2d.fillText(`Page ${pageIdx + 1}`, 70, 95);
5875
+ }
5876
+ });
5877
+ }
5878
+ return items;
5879
+ },
4881
5880
  zoomIn: () => {
5881
+ isUserZoomed = true;
4882
5882
  scale += 0.15;
4883
5883
  applyTransform();
4884
5884
  },
4885
5885
  zoomOut: () => {
5886
+ isUserZoomed = true;
4886
5887
  scale = Math.max(0.2, scale - 0.15);
4887
5888
  applyTransform();
4888
5889
  },
4889
5890
  getZoom: () => scale,
4890
5891
  setZoom: (level) => {
5892
+ isUserZoomed = true;
4891
5893
  scale = level;
4892
5894
  applyTransform();
4893
5895
  },
4894
5896
  fitToPage: () => {
4895
- fitMode = fitMode === "width" ? "page" : "width";
4896
- scale = calculateFitScale(fitMode);
5897
+ isUserZoomed = false;
5898
+ fitMode = "width";
5899
+ scale = calculateFitScale("width");
4897
5900
  rotation = 0;
4898
5901
  applyTransform();
4899
5902
  },
@@ -5081,40 +6084,36 @@ var OpenDocumentPlugin = class {
5081
6084
  const isPresentation = instance.isPresentation;
5082
6085
  const totalPages = instance.getPageCount?.() ?? 1;
5083
6086
  const actions = [];
5084
- if (isPresentation || totalPages > 1) {
5085
- if (isPresentation) {
5086
- actions.push({
5087
- id: "thumbnails",
5088
- icon: "thumbnails",
5089
- label: "Slide Thumbnails",
5090
- type: "button",
5091
- group: "navigation",
5092
- execute: () => instance.toggleThumbnails?.()
5093
- });
5094
- }
5095
- actions.push({
5096
- id: "page-nav",
5097
- icon: "",
5098
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
5099
- type: "page-nav",
5100
- group: "navigation",
5101
- value: instance.getCurrentPage?.() ?? 1,
5102
- max: totalPages,
5103
- execute: (action, page) => {
5104
- const cur = instance.getCurrentPage?.() ?? 1;
5105
- const max = instance.getPageCount?.() ?? 1;
5106
- if (action === "prev") {
5107
- if (cur > 1) instance.goToPage?.(cur - 1);
5108
- } else if (action === "next") {
5109
- if (cur < max) instance.goToPage?.(cur + 1);
5110
- } else if (typeof page === "number") {
5111
- instance.goToPage?.(page);
5112
- } else if (typeof action === "number") {
5113
- instance.goToPage?.(action);
5114
- }
6087
+ actions.push({
6088
+ id: "thumbnails",
6089
+ icon: "thumbnails",
6090
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
6091
+ type: "button",
6092
+ group: "navigation",
6093
+ execute: () => instance.toggleThumbnails?.()
6094
+ });
6095
+ actions.push({
6096
+ id: "page-nav",
6097
+ icon: "",
6098
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
6099
+ type: "page-nav",
6100
+ group: "navigation",
6101
+ value: instance.getCurrentPage?.() ?? 1,
6102
+ max: totalPages,
6103
+ execute: (action, page) => {
6104
+ const cur = instance.getCurrentPage?.() ?? 1;
6105
+ const max = instance.getPageCount?.() ?? 1;
6106
+ if (action === "prev") {
6107
+ if (cur > 1) instance.goToPage?.(cur - 1);
6108
+ } else if (action === "next") {
6109
+ if (cur < max) instance.goToPage?.(cur + 1);
6110
+ } else if (typeof page === "number") {
6111
+ instance.goToPage?.(page);
6112
+ } else if (typeof action === "number") {
6113
+ instance.goToPage?.(action);
5115
6114
  }
5116
- });
5117
- }
6115
+ }
6116
+ });
5118
6117
  actions.push(
5119
6118
  {
5120
6119
  id: "zoom-out",
@@ -5205,49 +6204,65 @@ var OpenDocumentPlugin = class {
5205
6204
  container.className = "fp-odf-container";
5206
6205
  container.style.width = "100%";
5207
6206
  container.style.height = "100%";
5208
- container.style.overflow = "auto";
5209
- container.style.padding = "24px";
6207
+ container.style.overflowX = "hidden";
6208
+ container.style.overflowY = "auto";
6209
+ container.style.padding = "16px 8px";
5210
6210
  container.style.backgroundColor = "#f1f5f9";
5211
6211
  const wrapper = document.createElement("div");
5212
6212
  wrapper.className = "fp-odf-wrapper";
5213
6213
  wrapper.style.margin = "0 auto";
6214
+ wrapper.style.width = isPresentation ? "960px" : "816px";
6215
+ wrapper.style.boxSizing = "border-box";
5214
6216
  wrapper.style.backgroundColor = "#ffffff";
5215
6217
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5216
6218
  wrapper.style.borderRadius = "4px";
5217
6219
  wrapper.style.transformOrigin = "top center";
5218
- wrapper.style.transition = "transform 0.2s ease";
6220
+ wrapper.style.transition = "transform 0.15s ease";
5219
6221
  container.appendChild(wrapper);
5220
6222
  ctx.container.appendChild(container);
5221
6223
  let scale = 1;
5222
6224
  let rotation = 0;
5223
- let fitMode = "width";
6225
+ let isUserZoomed = false;
6226
+ let fitMode = ctx?.options?.fitMode || "width";
6227
+ let currentPage = 1;
6228
+ let totalPages = 1;
6229
+ let slides = [];
5224
6230
  const calculateFitScale = (mode = fitMode) => {
5225
- const elW = wrapper.offsetWidth || 850;
5226
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5227
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5228
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6231
+ const activeSlide = slides[currentPage - 1];
6232
+ const elW = isPresentation ? 960 : 816;
6233
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
6234
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6235
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5229
6236
  const sW = availW / elW;
5230
6237
  const sH = availH / (isPresentation ? 540 : singlePageH);
5231
6238
  if (isPresentation) {
5232
- return Math.min(1, Math.min(sW, sH));
6239
+ return Math.min(2.5, Math.min(sW, sH));
5233
6240
  }
5234
6241
  if (mode === "page") {
5235
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6242
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5236
6243
  }
5237
- return Math.max(0.65, Math.min(1.05, sW));
6244
+ return Math.max(0.4, Math.min(3, sW));
5238
6245
  };
5239
6246
  const applyTransform = () => {
5240
6247
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5241
6248
  wrapper.style.transformOrigin = "top center";
6249
+ const activeSlide = slides[currentPage - 1];
6250
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6251
+ const scaledH = baseH * scale;
6252
+ const extraH = Math.max(0, scaledH - baseH);
6253
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5242
6254
  };
6255
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6256
+ if (!isUserZoomed) {
6257
+ scale = calculateFitScale(fitMode);
6258
+ applyTransform();
6259
+ }
6260
+ }) : null;
6261
+ resizeObserver?.observe(ctx.container);
5243
6262
  setTimeout(() => {
5244
- fitMode = "width";
5245
- scale = calculateFitScale("width");
6263
+ scale = calculateFitScale(fitMode);
5246
6264
  applyTransform();
5247
6265
  }, 60);
5248
- let currentPage = 1;
5249
- let totalPages = 1;
5250
- let slides = [];
5251
6266
  if (isPresentation) {
5252
6267
  wrapper.style.maxWidth = "960px";
5253
6268
  wrapper.style.aspectRatio = "16 / 9";
@@ -5348,24 +6363,9 @@ var OpenDocumentPlugin = class {
5348
6363
  wrapper.appendChild(page);
5349
6364
  slides.push(page);
5350
6365
  });
5351
- const pageIndicator = document.createElement("div");
5352
- pageIndicator.className = "fp-odt-page-indicator";
5353
- pageIndicator.style.position = "sticky";
5354
- pageIndicator.style.bottom = "16px";
5355
- pageIndicator.style.left = "50%";
5356
- pageIndicator.style.transform = "translateX(-50%)";
5357
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5358
- pageIndicator.style.color = "#fff";
5359
- pageIndicator.style.padding = "6px 12px";
5360
- pageIndicator.style.borderRadius = "16px";
5361
- pageIndicator.style.fontSize = "12px";
5362
- pageIndicator.style.zIndex = "100";
5363
- pageIndicator.style.display = "inline-block";
5364
- pageIndicator.style.width = "fit-content";
5365
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5366
- container.appendChild(pageIndicator);
5367
6366
  }
5368
6367
  const cleanup = () => {
6368
+ resizeObserver?.disconnect();
5369
6369
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5370
6370
  container.remove();
5371
6371
  ctx.container.innerHTML = "";
@@ -5377,10 +6377,6 @@ var OpenDocumentPlugin = class {
5377
6377
  slides.forEach((s, idx) => {
5378
6378
  s.style.display = idx === page - 1 ? "block" : "none";
5379
6379
  });
5380
- const indicator = container.querySelector(".fp-odt-page-indicator");
5381
- if (indicator) {
5382
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5383
- }
5384
6380
  container.scrollTop = 0;
5385
6381
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5386
6382
  };
@@ -5388,21 +6384,25 @@ var OpenDocumentPlugin = class {
5388
6384
  destroy: cleanup,
5389
6385
  isPresentation,
5390
6386
  zoomIn: () => {
6387
+ isUserZoomed = true;
5391
6388
  scale += 0.15;
5392
6389
  applyTransform();
5393
6390
  },
5394
6391
  zoomOut: () => {
6392
+ isUserZoomed = true;
5395
6393
  scale = Math.max(0.2, scale - 0.15);
5396
6394
  applyTransform();
5397
6395
  },
5398
6396
  getZoom: () => scale,
5399
6397
  setZoom: (level) => {
6398
+ isUserZoomed = true;
5400
6399
  scale = level;
5401
6400
  applyTransform();
5402
6401
  },
5403
6402
  fitToPage: () => {
5404
- fitMode = fitMode === "width" ? "page" : "width";
5405
- scale = calculateFitScale(fitMode);
6403
+ isUserZoomed = false;
6404
+ fitMode = "width";
6405
+ scale = calculateFitScale("width");
5406
6406
  rotation = 0;
5407
6407
  applyTransform();
5408
6408
  },
@@ -5418,23 +6418,45 @@ var OpenDocumentPlugin = class {
5418
6418
  getPageCount: () => totalPages,
5419
6419
  getCurrentPage: () => currentPage,
5420
6420
  getThumbnails: async () => {
5421
- if (!isPresentation) return [];
5422
- return slides.map((_, idx) => ({
5423
- index: idx,
5424
- label: `Slide ${idx + 1}`,
5425
- render: async (canvas) => {
5426
- const ctx2d = canvas.getContext("2d");
5427
- if (!ctx2d) return;
5428
- canvas.width = 160;
5429
- canvas.height = 90;
5430
- ctx2d.fillStyle = "#f8fafc";
5431
- ctx2d.fillRect(0, 0, 160, 90);
5432
- ctx2d.fillStyle = "#334155";
5433
- ctx2d.font = "bold 12px sans-serif";
5434
- ctx2d.textAlign = "center";
5435
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5436
- }
5437
- }));
6421
+ const count = totalPages || slides.length || 1;
6422
+ const items = [];
6423
+ for (let idx = 0; idx < count; idx++) {
6424
+ const itemIdx = idx;
6425
+ items.push({
6426
+ index: itemIdx,
6427
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6428
+ render: async (canvas) => {
6429
+ const ctx2d = canvas.getContext("2d");
6430
+ if (!ctx2d) return;
6431
+ if (isPresentation) {
6432
+ canvas.width = 160;
6433
+ canvas.height = 90;
6434
+ ctx2d.fillStyle = "#f8fafc";
6435
+ ctx2d.fillRect(0, 0, 160, 90);
6436
+ ctx2d.strokeStyle = "#cbd5e1";
6437
+ ctx2d.lineWidth = 1;
6438
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6439
+ ctx2d.fillStyle = "#334155";
6440
+ ctx2d.font = "bold 11px sans-serif";
6441
+ ctx2d.textAlign = "center";
6442
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6443
+ } else {
6444
+ canvas.width = 140;
6445
+ canvas.height = 180;
6446
+ ctx2d.fillStyle = "#ffffff";
6447
+ ctx2d.fillRect(0, 0, 140, 180);
6448
+ ctx2d.strokeStyle = "#cbd5e1";
6449
+ ctx2d.lineWidth = 1;
6450
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6451
+ ctx2d.fillStyle = "#64748b";
6452
+ ctx2d.font = "bold 10px sans-serif";
6453
+ ctx2d.textAlign = "center";
6454
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6455
+ }
6456
+ }
6457
+ });
6458
+ }
6459
+ return items;
5438
6460
  },
5439
6461
  download: () => {
5440
6462
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5539,23 +6561,24 @@ var OpenDocumentPlugin = class {
5539
6561
  case "h": {
5540
6562
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5541
6563
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5542
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6564
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5543
6565
  break;
5544
6566
  }
5545
6567
  case "p": {
5546
6568
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5547
6569
  const inner = this.renderSpans(node, styles, images);
5548
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6570
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6571
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5549
6572
  break;
5550
6573
  }
5551
6574
  case "list": {
5552
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6575
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5553
6576
  Array.from(node.children).forEach((c) => walk(c));
5554
6577
  html += "</ul>";
5555
6578
  break;
5556
6579
  }
5557
6580
  case "list-item": {
5558
- html += "<li>";
6581
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5559
6582
  Array.from(node.children).forEach((c) => walk(c));
5560
6583
  html += "</li>";
5561
6584
  break;
@@ -5681,28 +6704,26 @@ var DocPlugin = class {
5681
6704
  getToolbarActions(instance) {
5682
6705
  const totalPages = instance.getPageCount?.() ?? 1;
5683
6706
  const actions = [];
5684
- if (totalPages > 1) {
5685
- actions.push({
5686
- id: "page-nav",
5687
- icon: "",
5688
- label: "Page Navigation",
5689
- type: "page-nav",
5690
- group: "navigation",
5691
- value: instance.getCurrentPage?.() ?? 1,
5692
- max: totalPages,
5693
- execute: (action, page) => {
5694
- const cur = instance.getCurrentPage?.() ?? 1;
5695
- const max = instance.getPageCount?.() ?? 1;
5696
- if (action === "prev") {
5697
- if (cur > 1) instance.goToPage?.(cur - 1);
5698
- } else if (action === "next") {
5699
- if (cur < max) instance.goToPage?.(cur + 1);
5700
- } else if (typeof page === "number") {
5701
- instance.goToPage?.(page);
5702
- }
6707
+ actions.push({
6708
+ id: "page-nav",
6709
+ icon: "",
6710
+ label: "Page Navigation",
6711
+ type: "page-nav",
6712
+ group: "navigation",
6713
+ value: instance.getCurrentPage?.() ?? 1,
6714
+ max: totalPages,
6715
+ execute: (action, page) => {
6716
+ const cur = instance.getCurrentPage?.() ?? 1;
6717
+ const max = instance.getPageCount?.() ?? 1;
6718
+ if (action === "prev") {
6719
+ if (cur > 1) instance.goToPage?.(cur - 1);
6720
+ } else if (action === "next") {
6721
+ if (cur < max) instance.goToPage?.(cur + 1);
6722
+ } else if (typeof page === "number") {
6723
+ instance.goToPage?.(page);
5703
6724
  }
5704
- });
5705
- }
6725
+ }
6726
+ });
5706
6727
  actions.push(
5707
6728
  {
5708
6729
  id: "zoom-out",
@@ -5776,8 +6797,9 @@ var DocPlugin = class {
5776
6797
  container.className = "fp-doc-container";
5777
6798
  container.style.width = "100%";
5778
6799
  container.style.height = "100%";
5779
- container.style.overflow = "auto";
5780
- container.style.padding = "32px 16px";
6800
+ container.style.overflowX = "hidden";
6801
+ container.style.overflowY = "auto";
6802
+ container.style.padding = "16px 8px";
5781
6803
  container.style.backgroundColor = "#f1f5f9";
5782
6804
  container.style.display = "flex";
5783
6805
  container.style.justifyContent = "center";
@@ -5822,16 +6844,15 @@ var DocPlugin = class {
5822
6844
  const pageCard = document.createElement("div");
5823
6845
  pageCard.className = "fp-doc-page-card";
5824
6846
  pageCard.style.width = "816px";
5825
- pageCard.style.height = "1056px";
6847
+ pageCard.style.minHeight = "1056px";
5826
6848
  pageCard.style.backgroundColor = "#ffffff";
5827
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6849
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5828
6850
  pageCard.style.borderRadius = "4px";
5829
- pageCard.style.padding = "96px 72px";
6851
+ pageCard.style.padding = "72px 56px";
5830
6852
  pageCard.style.boxSizing = "border-box";
5831
- pageCard.style.overflow = "hidden";
5832
6853
  pageCard.style.display = i === 0 ? "block" : "none";
5833
6854
  pageCard.style.transformOrigin = "top center";
5834
- pageCard.style.transition = "transform 0.2s ease";
6855
+ pageCard.style.transition = "transform 0.15s ease";
5835
6856
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5836
6857
  pageCard.style.color = "#1e293b";
5837
6858
  if (isFallback && i === 0) {
@@ -5848,34 +6869,15 @@ var DocPlugin = class {
5848
6869
  container.appendChild(pageCard);
5849
6870
  pageCards.push(pageCard);
5850
6871
  }
5851
- let indicator = null;
5852
- if (totalPages > 1) {
5853
- indicator = document.createElement("div");
5854
- indicator.className = "fp-doc-page-indicator";
5855
- indicator.style.position = "fixed";
5856
- indicator.style.bottom = "16px";
5857
- indicator.style.left = "50%";
5858
- indicator.style.transform = "translateX(-50%)";
5859
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5860
- indicator.style.backdropFilter = "blur(8px)";
5861
- indicator.style.color = "#f8fafc";
5862
- indicator.style.fontSize = "12px";
5863
- indicator.style.fontWeight = "600";
5864
- indicator.style.padding = "5px 14px";
5865
- indicator.style.borderRadius = "20px";
5866
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5867
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5868
- indicator.style.zIndex = "10";
5869
- indicator.style.userSelect = "none";
5870
- indicator.style.pointerEvents = "none";
5871
- indicator.style.textAlign = "center";
5872
- container.appendChild(indicator);
5873
- }
5874
6872
  let rotation = 0;
5875
6873
  const applyTransform = () => {
5876
6874
  const activeCard = pageCards[currentPage - 1];
5877
6875
  if (activeCard) {
5878
6876
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6877
+ const baseH = activeCard.offsetHeight || 1056;
6878
+ const scaledH = baseH * scale;
6879
+ const extraH = Math.max(0, scaledH - baseH);
6880
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5879
6881
  }
5880
6882
  };
5881
6883
  const showPage = (pageNum) => {
@@ -5888,35 +6890,39 @@ var DocPlugin = class {
5888
6890
  card.style.display = "none";
5889
6891
  }
5890
6892
  });
5891
- if (indicator) {
5892
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5893
- }
5894
6893
  container.scrollTop = 0;
5895
6894
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5896
6895
  };
5897
6896
  if (totalPages > 1) {
5898
6897
  showPage(1);
5899
6898
  }
5900
- let fitMode = "width";
6899
+ let fitMode = ctx?.options?.fitMode || "width";
6900
+ let isUserZoomed = false;
5901
6901
  const calculateFitScale = (mode = fitMode) => {
5902
6902
  const elW = 816;
5903
6903
  const elH = 1056;
5904
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5905
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6904
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6905
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5906
6906
  const sW = availW / elW;
5907
6907
  const sH = availH / elH;
5908
6908
  if (mode === "page") {
5909
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6909
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5910
6910
  }
5911
- return Math.max(0.65, Math.min(1.05, sW));
6911
+ return Math.max(0.4, Math.min(3, sW));
5912
6912
  };
6913
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6914
+ if (!isUserZoomed) {
6915
+ scale = calculateFitScale(fitMode);
6916
+ applyTransform();
6917
+ }
6918
+ }) : null;
6919
+ resizeObserver?.observe(ctx.container);
5913
6920
  setTimeout(() => {
5914
- fitMode = "width";
5915
- scale = calculateFitScale("width");
6921
+ scale = calculateFitScale(fitMode);
5916
6922
  applyTransform();
5917
6923
  }, 60);
5918
6924
  const cleanup = () => {
5919
- indicator?.remove();
6925
+ resizeObserver?.disconnect();
5920
6926
  container.remove();
5921
6927
  ctx.container.innerHTML = "";
5922
6928
  };
@@ -5927,21 +6933,25 @@ var DocPlugin = class {
5927
6933
  getCurrentPage: () => currentPage,
5928
6934
  goToPage: (page) => showPage(page),
5929
6935
  zoomIn: () => {
6936
+ isUserZoomed = true;
5930
6937
  scale += 0.15;
5931
6938
  applyTransform();
5932
6939
  },
5933
6940
  zoomOut: () => {
6941
+ isUserZoomed = true;
5934
6942
  scale = Math.max(0.2, scale - 0.15);
5935
6943
  applyTransform();
5936
6944
  },
5937
6945
  getZoom: () => scale,
5938
6946
  setZoom: (level) => {
6947
+ isUserZoomed = true;
5939
6948
  scale = level;
5940
6949
  applyTransform();
5941
6950
  },
5942
6951
  fitToPage: () => {
5943
- fitMode = fitMode === "width" ? "page" : "width";
5944
- scale = calculateFitScale(fitMode);
6952
+ isUserZoomed = false;
6953
+ fitMode = "width";
6954
+ scale = calculateFitScale("width");
5945
6955
  rotation = 0;
5946
6956
  applyTransform();
5947
6957
  },
@@ -6318,25 +7328,31 @@ ${chartSvg}
6318
7328
  i++;
6319
7329
  continue;
6320
7330
  }
6321
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7331
+ if (/^\d{1,2}$/.test(line.trim())) {
7332
+ i++;
7333
+ continue;
7334
+ }
7335
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7336
+ 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));
7337
+ if (isTitleLike) {
6322
7338
  if (inList) {
6323
7339
  html += "</ul>";
6324
7340
  inList = false;
6325
7341
  }
6326
- 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>`;
7342
+ 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>`;
6327
7343
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6328
7344
  if (!inList) {
6329
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7345
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6330
7346
  inList = true;
6331
7347
  }
6332
7348
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6333
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
7349
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
6334
7350
  } else {
6335
7351
  if (inList) {
6336
7352
  html += "</ul>";
6337
7353
  inList = false;
6338
7354
  }
6339
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
7355
+ 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>`;
6340
7356
  }
6341
7357
  i++;
6342
7358
  }