@files-preview-app/preview-file 1.3.0 → 1.3.2

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
@@ -38,6 +38,7 @@ function _interopNamespace(e) {
38
38
  var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
39
39
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
40
40
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
41
+ var fflate__namespace = /*#__PURE__*/_interopNamespace(fflate);
41
42
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
42
43
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
43
44
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
@@ -608,8 +609,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
608
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>`;
609
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>`;
610
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>`;
611
- 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>`;
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"><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>`;
613
613
  var ICON_MAP = {
614
614
  "zoom-in": ICON_ZOOM_IN,
615
615
  "zoom-out": ICON_ZOOM_OUT,
@@ -636,28 +636,177 @@ var ICON_MAP = {
636
636
  "forward-10": ICON_FAST_FORWARD,
637
637
  "rewind": ICON_REWIND,
638
638
  "replay-10": ICON_REWIND,
639
- "speed": ICON_SPEED,
640
639
  "open-window": ICON_EXTERNAL_WINDOW,
641
- "external-window": ICON_EXTERNAL_WINDOW
640
+ "external-window": ICON_EXTERNAL_WINDOW,
641
+ "openWindow": ICON_EXTERNAL_WINDOW,
642
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
642
643
  };
643
644
  var ToolbarController = class {
644
645
  el;
645
646
  toolbarEl;
646
647
  actions = [];
648
+ config = {};
647
649
  pageInputEl = null;
648
650
  pageLabelEl = null;
649
- constructor(container) {
651
+ prevPageBtn = null;
652
+ nextPageBtn = null;
653
+ constructor(container, config) {
650
654
  this.el = container;
655
+ if (config) this.config = { ...config };
651
656
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
652
657
  this.el.appendChild(this.toolbarEl);
653
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
+ }
654
791
  setPage(page, max) {
655
792
  if (this.pageInputEl) {
656
793
  this.pageInputEl.value = page.toString();
794
+ if (max !== void 0) {
795
+ this.pageInputEl.max = max.toString();
796
+ }
657
797
  }
658
798
  if (max !== void 0 && this.pageLabelEl) {
659
799
  this.pageLabelEl.textContent = ` / ${max}`;
660
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
+ }
661
810
  }
662
811
  update(actions) {
663
812
  this.actions = actions;
@@ -680,8 +829,9 @@ var ToolbarController = class {
680
829
  view: [],
681
830
  actions: []
682
831
  };
683
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
684
- 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;
685
835
  for (const action of effectiveActions) {
686
836
  if (groups[action.group]) {
687
837
  groups[action.group].push(action);
@@ -741,6 +891,13 @@ var ToolbarController = class {
741
891
  action.execute("go", val);
742
892
  });
743
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;
744
901
  this.pageInputEl = input;
745
902
  this.pageLabelEl = label;
746
903
  groupEl.appendChild(prevBtn);
@@ -781,9 +938,11 @@ var ToolbarController = class {
781
938
  type: "button",
782
939
  "data-action-id": id
783
940
  });
784
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
785
- if (svg) {
786
- btn.innerHTML = sanitizeSVG(svg);
941
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
942
+ if (internalSvg) {
943
+ btn.innerHTML = internalSvg;
944
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
945
+ btn.innerHTML = sanitizeSVG(iconHtml);
787
946
  } else {
788
947
  btn.textContent = title || id;
789
948
  }
@@ -794,25 +953,63 @@ var ToolbarController = class {
794
953
  var ThumbnailPanel = class {
795
954
  el;
796
955
  panelEl;
956
+ listEl;
957
+ headerEl;
797
958
  thumbnails = [];
798
959
  onSelect;
960
+ onToggleCallback;
799
961
  activeIndex = 0;
800
- constructor(container) {
962
+ observer;
963
+ renderedIndices = /* @__PURE__ */ new Set();
964
+ constructor(container, onToggle) {
801
965
  this.el = container;
966
+ this.onToggleCallback = onToggle;
802
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);
803
988
  this.el.appendChild(this.panelEl);
804
989
  }
990
+ isOpen() {
991
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
992
+ }
805
993
  update(thumbnails, onSelect) {
806
994
  this.thumbnails = thumbnails;
807
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
+ }
808
1001
  this.render();
1002
+ if (this.isOpen()) {
1003
+ this.renderAllVisible();
1004
+ }
809
1005
  }
810
1006
  setActive(index) {
811
1007
  this.activeIndex = index;
812
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
1008
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
813
1009
  items.forEach((item, i) => {
814
1010
  if (i === index) {
815
1011
  item.classList.add("active");
1012
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
816
1013
  } else {
817
1014
  item.classList.remove("active");
818
1015
  }
@@ -820,37 +1017,102 @@ var ThumbnailPanel = class {
820
1017
  }
821
1018
  show() {
822
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();
823
1026
  }
824
1027
  hide() {
825
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);
826
1033
  }
827
1034
  toggle() {
828
- this.panelEl.classList.toggle("hidden");
1035
+ if (this.isOpen()) {
1036
+ this.hide();
1037
+ } else {
1038
+ this.show();
1039
+ }
829
1040
  }
830
1041
  destroy() {
1042
+ if (this.observer) {
1043
+ this.observer.disconnect();
1044
+ this.observer = void 0;
1045
+ }
1046
+ this.renderedIndices.clear();
831
1047
  this.el.innerHTML = "";
832
1048
  }
833
- async render() {
834
- 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
+ }
835
1094
  for (let i = 0; i < this.thumbnails.length; i++) {
836
1095
  const thumb = this.thumbnails[i];
837
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
838
- if (i === this.activeIndex) {
839
- itemEl.classList.add("active");
840
- }
1096
+ const itemEl = createElement("div", {
1097
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
1098
+ });
1099
+ itemEl.dataset.thumbIndex = i.toString();
841
1100
  itemEl.addEventListener("click", () => {
842
1101
  this.setActive(i);
843
1102
  this.onSelect?.(i);
844
1103
  });
845
- const canvas = createElement("canvas", { width: "150", height: "200" });
846
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
847
- 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);
848
1109
  itemEl.appendChild(label);
849
- this.panelEl.appendChild(itemEl);
850
- try {
851
- await thumb.render(canvas);
852
- } catch (err) {
853
- 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);
854
1116
  }
855
1117
  }
856
1118
  }
@@ -871,6 +1133,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
871
1133
  currentOptions = {};
872
1134
  resizeObserver = null;
873
1135
  fullscreenHandler = null;
1136
+ titleBarEl = null;
874
1137
  /**
875
1138
  * Register a preview plugin.
876
1139
  */
@@ -907,6 +1170,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
907
1170
  }
908
1171
  this.currentBuffer = buffer.slice(0);
909
1172
  this.currentMetadata = metadata;
1173
+ this.updateTitleBar(options, metadata);
910
1174
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
911
1175
  const fileInfo = { metadata, buffer };
912
1176
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -930,15 +1194,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
930
1194
  if (event === "page-change" && payload && typeof payload.page === "number") {
931
1195
  const total = payload.total ?? payload.totalPages;
932
1196
  this.toolbar?.setPage(payload.page, total);
1197
+ this.thumbnailPanel?.setActive(payload.page - 1);
933
1198
  }
934
1199
  this.eventEmitter.emit(event, payload);
935
- }
1200
+ },
1201
+ toggleThumbnails: () => this.toggleThumbnails()
936
1202
  });
937
1203
  this.activeInstance = instance;
938
1204
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1205
+ instance.toggleThumbnails = () => this.toggleThumbnails();
939
1206
  this.hideLoading();
940
1207
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
941
- 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);
942
1212
  const actions = matchedPlugin.getToolbarActions(instance);
943
1213
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
944
1214
  if (!hasFullscreen && this.wrapperEl) {
@@ -988,6 +1258,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
988
1258
  }
989
1259
  });
990
1260
  }
1261
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1262
+ if (thumbAction) {
1263
+ thumbAction.execute = () => {
1264
+ this.toggleThumbnails();
1265
+ };
1266
+ }
991
1267
  this.toolbar.update(actions);
992
1268
  this.toolbar.show();
993
1269
  }
@@ -1072,7 +1348,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1072
1348
  if (targetUrl) {
1073
1349
  const newWin2 = window.open(targetUrl, "_blank");
1074
1350
  if (!newWin2) {
1075
- 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.");
1076
1352
  return null;
1077
1353
  }
1078
1354
  return newWin2;
@@ -1080,7 +1356,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1080
1356
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1081
1357
  const newWin = window.open("", "_blank");
1082
1358
  if (!newWin) {
1083
- 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.");
1084
1360
  return null;
1085
1361
  }
1086
1362
  newWin.document.title = title;
@@ -1157,6 +1433,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1157
1433
  this.currentBuffer = null;
1158
1434
  this.currentMetadata = null;
1159
1435
  this.currentContainer = null;
1436
+ this.titleBarEl = null;
1160
1437
  }
1161
1438
  /**
1162
1439
  * Get the currently active preview instance.
@@ -1164,7 +1441,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1164
1441
  getInstance() {
1165
1442
  return this.activeInstance;
1166
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
+ }
1167
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
+ }
1168
1656
  abort() {
1169
1657
  if (this.abortController) {
1170
1658
  this.abortController.abort();
@@ -1207,17 +1695,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1207
1695
  bodyEl.className = "fp-body";
1208
1696
  bodyEl.appendChild(thumbnailEl);
1209
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);
1210
1702
  if (toolbarPos === "top") {
1703
+ this.wrapperEl.appendChild(titleBarEl);
1211
1704
  this.wrapperEl.appendChild(toolbarEl);
1212
1705
  this.wrapperEl.appendChild(bodyEl);
1213
1706
  } else {
1707
+ this.wrapperEl.appendChild(titleBarEl);
1214
1708
  this.wrapperEl.appendChild(bodyEl);
1215
1709
  this.wrapperEl.appendChild(toolbarEl);
1216
1710
  }
1217
1711
  container.innerHTML = "";
1218
1712
  container.appendChild(this.wrapperEl);
1219
- this.toolbar = new ToolbarController(toolbarEl);
1220
- 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
+ });
1221
1721
  this.setupKeyboardShortcuts();
1222
1722
  this.setupDragAndDrop(container, options);
1223
1723
  this.setupResizeAndFullscreenListeners();
@@ -1607,6 +2107,14 @@ var PdfPlugin = class {
1607
2107
  group: "zoom",
1608
2108
  execute: () => instance.fitToPage?.()
1609
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
+ },
1610
2118
  {
1611
2119
  id: "rotate-cw",
1612
2120
  icon: "rotate-cw",
@@ -1615,6 +2123,14 @@ var PdfPlugin = class {
1615
2123
  group: "view",
1616
2124
  execute: () => instance.rotateCW?.()
1617
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
+ },
1618
2134
  {
1619
2135
  id: "download",
1620
2136
  icon: "download",
@@ -1646,12 +2162,13 @@ var PdfPlugin = class {
1646
2162
  container.className = "fp-pdf-container";
1647
2163
  container.style.width = "100%";
1648
2164
  container.style.height = "100%";
1649
- container.style.overflow = "auto";
2165
+ container.style.overflowX = "hidden";
2166
+ container.style.overflowY = "auto";
1650
2167
  container.style.display = "flex";
1651
2168
  container.style.flexDirection = "column";
1652
2169
  container.style.alignItems = "center";
1653
2170
  container.style.justifyContent = "flex-start";
1654
- container.style.padding = "20px 16px";
2171
+ container.style.padding = "16px 8px";
1655
2172
  container.style.backgroundColor = "#0f172a";
1656
2173
  container.style.boxSizing = "border-box";
1657
2174
  container.style.position = "relative";
@@ -1662,30 +2179,12 @@ var PdfPlugin = class {
1662
2179
  pageCard.style.borderRadius = "4px";
1663
2180
  pageCard.style.overflow = "hidden";
1664
2181
  pageCard.style.lineHeight = "0";
1665
- pageCard.style.transition = "transform 0.15s ease";
1666
2182
  pageCard.style.position = "relative";
1667
2183
  pageCard.style.flexShrink = "0";
2184
+ pageCard.style.transition = "box-shadow 0.2s ease";
1668
2185
  let canvas = document.createElement("canvas");
1669
2186
  pageCard.appendChild(canvas);
1670
2187
  container.appendChild(pageCard);
1671
- const indicator = document.createElement("div");
1672
- indicator.className = "fp-pdf-page-indicator";
1673
- indicator.style.position = "sticky";
1674
- indicator.style.bottom = "16px";
1675
- indicator.style.marginTop = "16px";
1676
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1677
- indicator.style.backdropFilter = "blur(8px)";
1678
- indicator.style.color = "#f8fafc";
1679
- indicator.style.fontSize = "12px";
1680
- indicator.style.fontWeight = "600";
1681
- indicator.style.padding = "5px 14px";
1682
- indicator.style.borderRadius = "20px";
1683
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1684
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1685
- indicator.style.zIndex = "10";
1686
- indicator.style.userSelect = "none";
1687
- indicator.style.pointerEvents = "none";
1688
- container.appendChild(indicator);
1689
2188
  ctx.container.appendChild(container);
1690
2189
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1691
2190
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1701,8 +2200,9 @@ var PdfPlugin = class {
1701
2200
  let currentPage = 1;
1702
2201
  let zoomScale = 1;
1703
2202
  let rotation = 0;
1704
- let fitMode = "page";
2203
+ let fitMode = ctx?.options?.fitMode || "width";
1705
2204
  let currentRenderTask = null;
2205
+ let textLayerDiv = null;
1706
2206
  const renderPage = async (pageNum) => {
1707
2207
  if (currentRenderTask) {
1708
2208
  try {
@@ -1714,30 +2214,37 @@ var PdfPlugin = class {
1714
2214
  const newCanvas = document.createElement("canvas");
1715
2215
  pageCard.replaceChild(newCanvas, canvas);
1716
2216
  canvas = newCanvas;
2217
+ if (textLayerDiv) {
2218
+ textLayerDiv.remove();
2219
+ textLayerDiv = null;
2220
+ }
1717
2221
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1718
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1719
2222
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1720
2223
  const page = await pdfDoc.getPage(currentPage);
1721
2224
  const containerWidth = container.clientWidth || 900;
1722
2225
  const containerHeight = container.clientHeight || 700;
1723
2226
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1724
- const availWidth = Math.max(320, containerWidth - 48);
1725
- const availHeight = Math.max(550, containerHeight - 88);
2227
+ const availWidth = Math.max(280, containerWidth - 36);
2228
+ const availHeight = Math.max(280, containerHeight - 32);
1726
2229
  const scaleW = availWidth / unscaledVp.width;
1727
2230
  const scaleH = availHeight / unscaledVp.height;
1728
2231
  let fitScale;
1729
2232
  if (fitMode === "page") {
1730
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2233
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1731
2234
  } else {
1732
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2235
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1733
2236
  }
1734
2237
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1735
2238
  const pixelRatio = window.devicePixelRatio || 1;
1736
2239
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2240
+ const displayWidth = Math.floor(viewport.width);
2241
+ const displayHeight = Math.floor(viewport.height);
1737
2242
  canvas.width = Math.floor(viewport.width * pixelRatio);
1738
2243
  canvas.height = Math.floor(viewport.height * pixelRatio);
1739
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1740
- 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`;
1741
2248
  const canvasCtx = canvas.getContext("2d");
1742
2249
  if (!canvasCtx) return;
1743
2250
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1751,9 +2258,31 @@ var PdfPlugin = class {
1751
2258
  if (err?.name !== "RenderingCancelledException") {
1752
2259
  console.warn("[PdfPlugin] Page render warning:", err);
1753
2260
  }
2261
+ return;
1754
2262
  } finally {
1755
2263
  currentRenderTask = null;
1756
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
+ }
1757
2286
  };
1758
2287
  renderPage(1);
1759
2288
  let resizeTimer = null;
@@ -1763,11 +2292,48 @@ var PdfPlugin = class {
1763
2292
  if (Math.abs(zoomScale - 1) < 0.05) {
1764
2293
  renderPage(currentPage);
1765
2294
  }
1766
- }, 150);
2295
+ }, 120);
1767
2296
  });
1768
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);
1769
2333
  const cleanup = () => {
1770
2334
  resizeObserver.disconnect();
2335
+ window.removeEventListener("keydown", onKeyDown);
2336
+ container.removeEventListener("wheel", onWheel);
1771
2337
  if (resizeTimer) clearTimeout(resizeTimer);
1772
2338
  if (currentRenderTask) {
1773
2339
  try {
@@ -1786,22 +2352,26 @@ var PdfPlugin = class {
1786
2352
  const instance = {
1787
2353
  destroy: cleanup,
1788
2354
  zoomIn: () => {
1789
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2355
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1790
2356
  renderPage(currentPage);
1791
2357
  },
1792
2358
  zoomOut: () => {
1793
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2359
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1794
2360
  renderPage(currentPage);
1795
2361
  },
1796
2362
  getZoom: () => zoomScale,
1797
2363
  setZoom: (level) => {
1798
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2364
+ zoomScale = Math.max(0.25, Math.min(4, level));
1799
2365
  renderPage(currentPage);
1800
2366
  },
1801
2367
  fitToPage: () => {
1802
- fitMode = fitMode === "page" ? "width" : "page";
2368
+ fitMode = "page";
2369
+ zoomScale = 1;
2370
+ renderPage(currentPage);
2371
+ },
2372
+ fitToWidth: () => {
2373
+ fitMode = "width";
1803
2374
  zoomScale = 1;
1804
- rotation = 0;
1805
2375
  renderPage(currentPage);
1806
2376
  },
1807
2377
  rotateCW: () => {
@@ -1819,6 +2389,9 @@ var PdfPlugin = class {
1819
2389
  container.scrollTop = 0;
1820
2390
  renderPage(page);
1821
2391
  },
2392
+ toggleThumbnails: () => {
2393
+ ctx?.toggleThumbnails?.();
2394
+ },
1822
2395
  download: () => {
1823
2396
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1824
2397
  const url = URL.createObjectURL(blob);
@@ -1852,7 +2425,7 @@ var PdfPlugin = class {
1852
2425
  },
1853
2426
  getThumbnails: async () => {
1854
2427
  const thumbnails = [];
1855
- const count = Math.min(totalPages, 50);
2428
+ const count = totalPages;
1856
2429
  for (let i = 1; i <= count; i++) {
1857
2430
  thumbnails.push({
1858
2431
  index: i,
@@ -1860,9 +2433,10 @@ var PdfPlugin = class {
1860
2433
  render: async (thumbCanvas) => {
1861
2434
  try {
1862
2435
  const p = await pdfDoc.getPage(i);
1863
- const baseVp = p.getViewport({ scale: 1 });
1864
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1865
- 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 });
1866
2440
  thumbCanvas.height = Math.floor(thumbVp.height);
1867
2441
  const tCtx = thumbCanvas.getContext("2d");
1868
2442
  if (tCtx) {
@@ -2206,28 +2780,26 @@ var DocxPlugin = class {
2206
2780
  getToolbarActions(instance) {
2207
2781
  const totalPages = instance.getPageCount?.() ?? 1;
2208
2782
  const actions = [];
2209
- if (totalPages > 1) {
2210
- actions.push({
2211
- id: "page-nav",
2212
- icon: "",
2213
- label: "Page Navigation",
2214
- type: "page-nav",
2215
- group: "navigation",
2216
- value: instance.getCurrentPage?.() ?? 1,
2217
- max: totalPages,
2218
- execute: (action, page) => {
2219
- const cur = instance.getCurrentPage?.() ?? 1;
2220
- const max = instance.getPageCount?.() ?? 1;
2221
- if (action === "prev") {
2222
- if (cur > 1) instance.goToPage?.(cur - 1);
2223
- } else if (action === "next") {
2224
- if (cur < max) instance.goToPage?.(cur + 1);
2225
- } else if (typeof page === "number") {
2226
- instance.goToPage?.(page);
2227
- }
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);
2228
2800
  }
2229
- });
2230
- }
2801
+ }
2802
+ });
2231
2803
  actions.push(
2232
2804
  {
2233
2805
  id: "zoom-out",
@@ -2293,11 +2865,17 @@ var DocxPlugin = class {
2293
2865
  wrapper.className = "fp-docx-wrapper";
2294
2866
  wrapper.style.transformOrigin = "top center";
2295
2867
  wrapper.style.transition = "transform 0.2s ease";
2296
- wrapper.style.padding = "24px 16px";
2297
- wrapper.style.maxWidth = "950px";
2868
+ wrapper.style.padding = "0";
2869
+ wrapper.style.maxWidth = "none";
2870
+ wrapper.style.width = "816px";
2298
2871
  wrapper.style.margin = "0 auto";
2872
+ wrapper.style.display = "flex";
2873
+ wrapper.style.flexDirection = "column";
2874
+ wrapper.style.alignItems = "center";
2299
2875
  wrapper.style.boxSizing = "border-box";
2300
- ctx.container.style.overflow = "auto";
2876
+ ctx.container.style.overflowX = "hidden";
2877
+ ctx.container.style.overflowY = "auto";
2878
+ ctx.container.style.padding = "16px 8px";
2301
2879
  ctx.container.style.backgroundColor = "#f1f5f9";
2302
2880
  ctx.container.appendChild(wrapper);
2303
2881
  const styleOverride = document.createElement("style");
@@ -2471,39 +3049,13 @@ var DocxPlugin = class {
2471
3049
  const pageElements = sections.length > 0 ? sections : cards;
2472
3050
  const totalPages = Math.max(1, pageElements.length);
2473
3051
  let currentPage = 1;
2474
- let indicator = null;
2475
- if (totalPages > 1) {
2476
- indicator = document.createElement("div");
2477
- indicator.className = "fp-docx-page-indicator";
2478
- indicator.style.position = "sticky";
2479
- indicator.style.bottom = "16px";
2480
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2481
- indicator.style.backdropFilter = "blur(8px)";
2482
- indicator.style.color = "#f8fafc";
2483
- indicator.style.fontSize = "12px";
2484
- indicator.style.fontWeight = "600";
2485
- indicator.style.padding = "5px 14px";
2486
- indicator.style.borderRadius = "20px";
2487
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2488
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2489
- indicator.style.zIndex = "10";
2490
- indicator.style.userSelect = "none";
2491
- indicator.style.pointerEvents = "none";
2492
- indicator.style.textAlign = "center";
2493
- indicator.style.width = "fit-content";
2494
- indicator.style.margin = "16px auto 0";
2495
- ctx.container.appendChild(indicator);
2496
- }
2497
3052
  const showPage = (pageNum) => {
2498
3053
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2499
3054
  if (pageElements.length > 1) {
2500
3055
  pageElements.forEach((sec, idx) => {
2501
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
3056
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2502
3057
  });
2503
3058
  }
2504
- if (indicator) {
2505
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2506
- }
2507
3059
  ctx.container.scrollTop = 0;
2508
3060
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2509
3061
  };
@@ -2512,31 +3064,46 @@ var DocxPlugin = class {
2512
3064
  }
2513
3065
  scale = 1;
2514
3066
  let rotation = 0;
2515
- let fitMode = "width";
2516
- const calculateFitScale = (mode = fitMode) => {
2517
- 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;
2518
3071
  const elW = activeEl.offsetWidth || 816;
2519
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2520
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2521
- const availH = Math.max(280, ctx.container.clientHeight - 80);
3072
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2522
3073
  const sW = availW / elW;
2523
- const sH = availH / singlePageH;
2524
- if (mode === "page") {
2525
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2526
- }
2527
- return Math.max(0.65, Math.min(1.05, sW));
3074
+ return Math.max(0.35, Math.min(3, sW));
2528
3075
  };
2529
3076
  const applyTransform = () => {
2530
3077
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2531
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
+ }
2532
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
+ }
2533
3100
  setTimeout(() => {
2534
- fitMode = "width";
2535
- scale = calculateFitScale("width");
3101
+ scale = calculateFitScale("page");
2536
3102
  applyTransform();
2537
- }, 60);
3103
+ }, 40);
2538
3104
  const cleanup = () => {
2539
- indicator?.remove();
3105
+ resizeObserver?.disconnect();
3106
+ resizeObserver = null;
2540
3107
  for (const url of createdBlobUrls) {
2541
3108
  URL.revokeObjectURL(url);
2542
3109
  }
@@ -2553,21 +3120,24 @@ var DocxPlugin = class {
2553
3120
  showPage(page);
2554
3121
  },
2555
3122
  zoomIn: () => {
2556
- scale += 0.15;
3123
+ isUserZoomed = true;
3124
+ scale = Math.min(3.5, scale + 0.15);
2557
3125
  applyTransform();
2558
3126
  },
2559
3127
  zoomOut: () => {
3128
+ isUserZoomed = true;
2560
3129
  scale = Math.max(0.2, scale - 0.15);
2561
3130
  applyTransform();
2562
3131
  },
2563
3132
  getZoom: () => scale,
2564
3133
  setZoom: (level) => {
3134
+ isUserZoomed = true;
2565
3135
  scale = level;
2566
3136
  applyTransform();
2567
3137
  },
2568
3138
  fitToPage: () => {
2569
- fitMode = fitMode === "width" ? "page" : "width";
2570
- scale = calculateFitScale(fitMode);
3139
+ isUserZoomed = false;
3140
+ scale = calculateFitScale("page");
2571
3141
  rotation = 0;
2572
3142
  applyTransform();
2573
3143
  },
@@ -2957,7 +3527,7 @@ var ExcelPlugin = class {
2957
3527
  "application/vnd.ms-excel.template.macroEnabled.12",
2958
3528
  "application/vnd.oasis.opendocument.spreadsheet"
2959
3529
  ];
2960
- weight = 80;
3530
+ weight = 85;
2961
3531
  supports(file) {
2962
3532
  const ext = file.metadata.extension?.toLowerCase();
2963
3533
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2969,6 +3539,14 @@ var ExcelPlugin = class {
2969
3539
  getToolbarActions(instance) {
2970
3540
  const totalSheets = instance.getPageCount?.() ?? 1;
2971
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
+ });
2972
3550
  if (totalSheets > 1) {
2973
3551
  actions.push({
2974
3552
  id: "page-nav",
@@ -3051,6 +3629,16 @@ var ExcelPlugin = class {
3051
3629
  execute: () => {
3052
3630
  instance.print?.();
3053
3631
  }
3632
+ },
3633
+ {
3634
+ id: "open-window",
3635
+ icon: "open-window",
3636
+ label: "Open in Separate Full Window",
3637
+ type: "button",
3638
+ group: "actions",
3639
+ execute: () => {
3640
+ instance.openInSeparateWindow?.();
3641
+ }
3054
3642
  }
3055
3643
  );
3056
3644
  return actions;
@@ -3216,6 +3804,63 @@ var ExcelPlugin = class {
3216
3804
  },
3217
3805
  getPageCount: () => sheetNames.length,
3218
3806
  getCurrentPage: () => currentSheetIndex,
3807
+ getThumbnails: () => {
3808
+ return sheetNames.map((name, idx) => ({
3809
+ index: idx,
3810
+ label: name,
3811
+ render: async (canvas) => {
3812
+ canvas.width = 140;
3813
+ canvas.height = 100;
3814
+ const c = canvas.getContext("2d");
3815
+ if (!c) return;
3816
+ c.fillStyle = "#ffffff";
3817
+ c.fillRect(0, 0, canvas.width, canvas.height);
3818
+ c.fillStyle = "#107c41";
3819
+ c.fillRect(0, 0, canvas.width, 16);
3820
+ c.fillStyle = "#ffffff";
3821
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3822
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3823
+ const startY = 17;
3824
+ const rowH = 13;
3825
+ const colW = 30;
3826
+ const colHeaders = ["A", "B", "C", "D"];
3827
+ c.fillStyle = "#f1f5f9";
3828
+ c.fillRect(0, startY, canvas.width, rowH);
3829
+ c.strokeStyle = "#cbd5e1";
3830
+ c.lineWidth = 0.8;
3831
+ c.strokeRect(0, startY, canvas.width, rowH);
3832
+ c.fillStyle = "#64748b";
3833
+ c.font = "bold 7px sans-serif";
3834
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3835
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3836
+ }
3837
+ const ws = wb?.Sheets[name];
3838
+ for (let ri = 1; ri <= 5; ri++) {
3839
+ const y = startY + ri * rowH;
3840
+ if (y > canvas.height - 2) break;
3841
+ c.fillStyle = "#f8fafc";
3842
+ c.fillRect(0, y, 12, rowH);
3843
+ c.fillStyle = "#94a3b8";
3844
+ c.font = "6px sans-serif";
3845
+ c.fillText(String(ri), 3, y + 9);
3846
+ c.strokeStyle = "#e2e8f0";
3847
+ c.strokeRect(0, y, canvas.width, rowH);
3848
+ c.fillStyle = "#334155";
3849
+ c.font = "6px sans-serif";
3850
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3851
+ const cellRef = `${colHeaders[ci]}${ri}`;
3852
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3853
+ if (cellVal !== void 0) {
3854
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3855
+ }
3856
+ }
3857
+ }
3858
+ c.strokeStyle = "#cbd5e1";
3859
+ c.lineWidth = 1;
3860
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3861
+ }
3862
+ }));
3863
+ },
3219
3864
  download: () => {
3220
3865
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3221
3866
  const url = URL.createObjectURL(blob);
@@ -3248,7 +3893,39 @@ var CsvPlugin = class {
3248
3893
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3249
3894
  }
3250
3895
  getToolbarActions(instance) {
3251
- return [
3896
+ const totalPages = instance.getPageCount?.() ?? 1;
3897
+ const actions = [];
3898
+ actions.push({
3899
+ id: "thumbnails",
3900
+ icon: "thumbnails",
3901
+ label: "Page Thumbnails",
3902
+ type: "button",
3903
+ group: "navigation",
3904
+ execute: () => instance.toggleThumbnails?.()
3905
+ });
3906
+ if (totalPages > 1) {
3907
+ actions.push({
3908
+ id: "page-nav",
3909
+ icon: "",
3910
+ label: "Page Navigation",
3911
+ type: "page-nav",
3912
+ group: "navigation",
3913
+ value: instance.getCurrentPage?.() ?? 1,
3914
+ max: totalPages,
3915
+ execute: (action, page) => {
3916
+ const cur = instance.getCurrentPage?.() ?? 1;
3917
+ const max = instance.getPageCount?.() ?? 1;
3918
+ if (action === "prev") {
3919
+ if (cur > 1) instance.goToPage?.(cur - 1);
3920
+ } else if (action === "next") {
3921
+ if (cur < max) instance.goToPage?.(cur + 1);
3922
+ } else if (typeof page === "number") {
3923
+ instance.goToPage?.(page);
3924
+ }
3925
+ }
3926
+ });
3927
+ }
3928
+ actions.push(
3252
3929
  {
3253
3930
  id: "zoom-out",
3254
3931
  icon: "zoom-out",
@@ -3269,6 +3946,16 @@ var CsvPlugin = class {
3269
3946
  instance.zoomIn?.();
3270
3947
  }
3271
3948
  },
3949
+ {
3950
+ id: "fit-page",
3951
+ icon: "fit-page",
3952
+ label: "Fit to View",
3953
+ type: "button",
3954
+ group: "zoom",
3955
+ execute: () => {
3956
+ instance.fitToPage?.();
3957
+ }
3958
+ },
3272
3959
  {
3273
3960
  id: "download",
3274
3961
  icon: "download",
@@ -3286,10 +3973,21 @@ var CsvPlugin = class {
3286
3973
  type: "button",
3287
3974
  group: "actions",
3288
3975
  execute: () => {
3289
- instance.print?.();
3976
+ instance.print?.();
3977
+ }
3978
+ },
3979
+ {
3980
+ id: "open-window",
3981
+ icon: "open-window",
3982
+ label: "Open in Separate Full Window",
3983
+ type: "button",
3984
+ group: "actions",
3985
+ execute: () => {
3986
+ instance.openInSeparateWindow?.();
3290
3987
  }
3291
3988
  }
3292
- ];
3989
+ );
3990
+ return actions;
3293
3991
  }
3294
3992
  async render(ctx) {
3295
3993
  const decoder = new TextDecoder("utf-8");
@@ -3301,33 +3999,62 @@ var CsvPlugin = class {
3301
3999
  container.style.padding = "16px";
3302
4000
  container.style.boxSizing = "border-box";
3303
4001
  container.style.transformOrigin = "top left";
4002
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
4003
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
4004
+ const headerLine = allLines[0] || "";
4005
+ const headerCells = headerLine.split(delimiter);
4006
+ const dataLines = allLines.slice(1);
4007
+ const ROWS_PER_PAGE = 100;
4008
+ const totalPages = Math.max(1, Math.ceil(dataLines.length / ROWS_PER_PAGE));
4009
+ let currentPage = 1;
3304
4010
  const table = document.createElement("table");
3305
4011
  table.style.borderCollapse = "collapse";
3306
4012
  table.style.width = "100%";
3307
- table.style.fontFamily = "monospace";
4013
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3308
4014
  table.style.fontSize = "13px";
3309
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3310
- const rows = text.split(/\r?\n/).slice(0, 500);
3311
- rows.forEach((row, rowIndex) => {
3312
- if (!row.trim()) return;
3313
- const tr = document.createElement("tr");
3314
- const cells = row.split(delimiter);
3315
- cells.forEach((cell) => {
3316
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3317
- td.textContent = cell.trim();
3318
- td.style.border = "1px solid #d0d7de";
3319
- td.style.padding = "6px 12px";
3320
- if (rowIndex === 0) {
3321
- td.style.backgroundColor = "#f6f8fa";
3322
- td.style.fontWeight = "bold";
3323
- }
3324
- tr.appendChild(td);
4015
+ const renderPage = (pageNum) => {
4016
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4017
+ table.innerHTML = "";
4018
+ const headerTr = document.createElement("tr");
4019
+ headerCells.forEach((cell) => {
4020
+ const th = document.createElement("th");
4021
+ th.textContent = cell.trim();
4022
+ th.style.border = "1px solid #d0d7de";
4023
+ th.style.padding = "8px 12px";
4024
+ th.style.backgroundColor = "#f6f8fa";
4025
+ th.style.fontWeight = "600";
4026
+ th.style.whiteSpace = "nowrap";
4027
+ headerTr.appendChild(th);
3325
4028
  });
3326
- table.appendChild(tr);
3327
- });
4029
+ table.appendChild(headerTr);
4030
+ const start = (currentPage - 1) * ROWS_PER_PAGE;
4031
+ const end = Math.min(dataLines.length, start + ROWS_PER_PAGE);
4032
+ const pageRows = dataLines.slice(start, end);
4033
+ pageRows.forEach((row, idx) => {
4034
+ const tr = document.createElement("tr");
4035
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
4036
+ const cells = row.split(delimiter);
4037
+ cells.forEach((cell) => {
4038
+ const td = document.createElement("td");
4039
+ td.textContent = cell.trim();
4040
+ td.style.border = "1px solid #d0d7de";
4041
+ td.style.padding = "6px 12px";
4042
+ td.style.whiteSpace = "nowrap";
4043
+ tr.appendChild(td);
4044
+ });
4045
+ table.appendChild(tr);
4046
+ });
4047
+ container.scrollTop = 0;
4048
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4049
+ };
4050
+ renderPage(1);
3328
4051
  container.appendChild(table);
3329
4052
  ctx.container.appendChild(container);
3330
4053
  let scale = 1;
4054
+ const applyScale = () => {
4055
+ container.style.transform = `scale(${scale})`;
4056
+ container.style.transformOrigin = "top left";
4057
+ };
3331
4058
  const cleanup = () => {
3332
4059
  container.remove();
3333
4060
  ctx.container.innerHTML = "";
@@ -3335,22 +4062,86 @@ var CsvPlugin = class {
3335
4062
  ctx.signal.addEventListener("abort", cleanup);
3336
4063
  return {
3337
4064
  destroy: cleanup,
4065
+ getPageCount: () => totalPages,
4066
+ getCurrentPage: () => currentPage,
4067
+ goToPage: (page) => renderPage(page),
4068
+ getThumbnails: () => {
4069
+ const thumbnails = [];
4070
+ for (let i = 0; i < totalPages; i++) {
4071
+ const pageIdx = i;
4072
+ const startRow = pageIdx * ROWS_PER_PAGE + 1;
4073
+ const endRow = Math.min(dataLines.length, (pageIdx + 1) * ROWS_PER_PAGE);
4074
+ const label = totalPages === 1 ? "Table" : `Page ${pageIdx + 1} (${startRow}-${endRow})`;
4075
+ thumbnails.push({
4076
+ index: pageIdx,
4077
+ label,
4078
+ render: async (canvas) => {
4079
+ canvas.width = 140;
4080
+ canvas.height = 100;
4081
+ const c = canvas.getContext("2d");
4082
+ if (!c) return;
4083
+ c.fillStyle = "#ffffff";
4084
+ c.fillRect(0, 0, canvas.width, canvas.height);
4085
+ c.fillStyle = "#2563eb";
4086
+ c.fillRect(0, 0, canvas.width, 16);
4087
+ c.fillStyle = "#ffffff";
4088
+ c.font = "bold 8px sans-serif";
4089
+ c.fillText(`\u{1F4C8} ${label.slice(0, 18)}`, 6, 11);
4090
+ const startY = 17;
4091
+ const rowH = 13;
4092
+ const colW = 28;
4093
+ const cols = Math.min(4, headerCells.length || 4);
4094
+ c.fillStyle = "#f1f5f9";
4095
+ c.fillRect(0, startY, canvas.width, rowH);
4096
+ c.strokeStyle = "#cbd5e1";
4097
+ c.lineWidth = 0.8;
4098
+ c.strokeRect(0, startY, canvas.width, rowH);
4099
+ c.fillStyle = "#64748b";
4100
+ c.font = "bold 7px sans-serif";
4101
+ for (let ci = 0; ci < cols; ci++) {
4102
+ const hName = headerCells[ci] || `Col ${ci + 1}`;
4103
+ c.fillText(hName.slice(0, 5), 8 + ci * colW, startY + 9);
4104
+ }
4105
+ const rowsToPreview = dataLines.slice(pageIdx * ROWS_PER_PAGE, pageIdx * ROWS_PER_PAGE + 5);
4106
+ for (let ri = 0; ri < rowsToPreview.length; ri++) {
4107
+ const y = startY + (ri + 1) * rowH;
4108
+ if (y > canvas.height - 2) break;
4109
+ c.strokeStyle = "#e2e8f0";
4110
+ c.strokeRect(0, y, canvas.width, rowH);
4111
+ const cVals = rowsToPreview[ri].split(delimiter);
4112
+ c.fillStyle = "#334155";
4113
+ c.font = "6px sans-serif";
4114
+ for (let ci = 0; ci < cols; ci++) {
4115
+ const val = cVals[ci] || "";
4116
+ c.fillText(val.trim().slice(0, 6), 8 + ci * colW, y + 9);
4117
+ }
4118
+ }
4119
+ c.strokeStyle = "#cbd5e1";
4120
+ c.lineWidth = 1;
4121
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4122
+ }
4123
+ });
4124
+ }
4125
+ return thumbnails;
4126
+ },
3338
4127
  zoomIn: () => {
3339
4128
  scale += 0.1;
3340
- container.style.transform = `scale(${scale})`;
4129
+ applyScale();
3341
4130
  },
3342
4131
  zoomOut: () => {
3343
4132
  scale = Math.max(0.2, scale - 0.1);
3344
- container.style.transform = `scale(${scale})`;
4133
+ applyScale();
3345
4134
  },
3346
4135
  getZoom: () => scale,
3347
4136
  setZoom: (level) => {
3348
4137
  scale = level;
3349
- container.style.transform = `scale(${scale})`;
4138
+ applyScale();
3350
4139
  },
3351
4140
  fitToPage: () => {
3352
- scale = 1;
3353
- container.style.transform = `scale(1)`;
4141
+ const availW = Math.max(280, (ctx.container.clientWidth || window.innerWidth) - 48);
4142
+ const tW = table.offsetWidth || 600;
4143
+ scale = Math.max(0.35, Math.min(1, availW / tW));
4144
+ applyScale();
3354
4145
  },
3355
4146
  download: () => {
3356
4147
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3414,28 +4205,34 @@ var CodePlugin = class {
3414
4205
  getToolbarActions(instance) {
3415
4206
  const totalPages = instance.getPageCount?.() ?? 1;
3416
4207
  const actions = [];
3417
- if (totalPages > 1) {
3418
- actions.push({
3419
- id: "page-nav",
3420
- icon: "",
3421
- label: "Page Navigation",
3422
- type: "page-nav",
3423
- group: "navigation",
3424
- value: instance.getCurrentPage?.() ?? 1,
3425
- max: totalPages,
3426
- execute: (action, page) => {
3427
- const cur = instance.getCurrentPage?.() ?? 1;
3428
- const max = instance.getPageCount?.() ?? 1;
3429
- if (action === "prev") {
3430
- if (cur > 1) instance.goToPage?.(cur - 1);
3431
- } else if (action === "next") {
3432
- if (cur < max) instance.goToPage?.(cur + 1);
3433
- } else if (typeof page === "number") {
3434
- instance.goToPage?.(page);
3435
- }
4208
+ actions.push({
4209
+ id: "thumbnails",
4210
+ icon: "thumbnails",
4211
+ label: "Page Thumbnails",
4212
+ type: "button",
4213
+ group: "navigation",
4214
+ execute: () => instance.toggleThumbnails?.()
4215
+ });
4216
+ actions.push({
4217
+ id: "page-nav",
4218
+ icon: "",
4219
+ label: "Page Navigation",
4220
+ type: "page-nav",
4221
+ group: "navigation",
4222
+ value: instance.getCurrentPage?.() ?? 1,
4223
+ max: totalPages,
4224
+ execute: (action, page) => {
4225
+ const cur = instance.getCurrentPage?.() ?? 1;
4226
+ const max = instance.getPageCount?.() ?? 1;
4227
+ if (action === "prev") {
4228
+ if (cur > 1) instance.goToPage?.(cur - 1);
4229
+ } else if (action === "next") {
4230
+ if (cur < max) instance.goToPage?.(cur + 1);
4231
+ } else if (typeof page === "number") {
4232
+ instance.goToPage?.(page);
3436
4233
  }
3437
- });
3438
- }
4234
+ }
4235
+ });
3439
4236
  actions.push(
3440
4237
  {
3441
4238
  id: "zoom-out",
@@ -3579,16 +4376,19 @@ var CodePlugin = class {
3579
4376
  let fontSize = 13;
3580
4377
  let rotation = 0;
3581
4378
  let zoomLevel = 1;
4379
+ let isUserZoomed = false;
3582
4380
  const pre = document.createElement("pre");
3583
4381
  const code = document.createElement("code");
3584
4382
  if (isTxt) {
4383
+ container.style.overflowX = "hidden";
4384
+ container.style.overflowY = "auto";
3585
4385
  container.style.backgroundColor = "#f1f5f9";
3586
- container.style.padding = "32px";
4386
+ container.style.padding = "16px 8px";
3587
4387
  container.style.boxSizing = "border-box";
3588
4388
  container.style.display = "flex";
3589
4389
  container.style.flexDirection = "column";
3590
4390
  container.style.alignItems = "center";
3591
- pre.style.margin = "0";
4391
+ pre.style.margin = "0 auto";
3592
4392
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3593
4393
  pre.style.fontSize = "13px";
3594
4394
  pre.style.color = "#1e293b";
@@ -3600,10 +4400,12 @@ var CodePlugin = class {
3600
4400
  pre.style.minHeight = "1056px";
3601
4401
  pre.style.padding = "72px 56px";
3602
4402
  pre.style.boxSizing = "border-box";
3603
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4403
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3604
4404
  pre.style.borderRadius = "4px";
3605
4405
  pre.style.transformOrigin = "top center";
4406
+ pre.style.transition = "transform 0.15s ease";
3606
4407
  } else {
4408
+ container.style.overflow = "auto";
3607
4409
  container.style.backgroundColor = "#1e1e1e";
3608
4410
  container.style.color = "#d4d4d4";
3609
4411
  container.style.padding = "16px";
@@ -3635,42 +4437,9 @@ var CodePlugin = class {
3635
4437
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3636
4438
  pre.appendChild(code);
3637
4439
  container.appendChild(pre);
3638
- let indicator = null;
3639
- if (totalPages > 1) {
3640
- indicator = document.createElement("div");
3641
- indicator.className = "fp-code-page-indicator";
3642
- indicator.style.position = "sticky";
3643
- indicator.style.bottom = "16px";
3644
- if (isTxt) {
3645
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3646
- indicator.style.color = "#334155";
3647
- indicator.style.border = "1px solid #e2e8f0";
3648
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3649
- } else {
3650
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3651
- indicator.style.color = "#f8fafc";
3652
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3653
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3654
- indicator.style.backdropFilter = "blur(8px)";
3655
- }
3656
- indicator.style.fontSize = "12px";
3657
- indicator.style.fontWeight = "600";
3658
- indicator.style.padding = "5px 14px";
3659
- indicator.style.borderRadius = "20px";
3660
- indicator.style.zIndex = "10";
3661
- indicator.style.userSelect = "none";
3662
- indicator.style.pointerEvents = "none";
3663
- indicator.style.textAlign = "center";
3664
- indicator.style.width = "fit-content";
3665
- indicator.style.margin = "16px auto 0";
3666
- container.appendChild(indicator);
3667
- }
3668
4440
  const showPage = (pageNum) => {
3669
4441
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3670
4442
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3671
- if (indicator) {
3672
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3673
- }
3674
4443
  container.scrollTop = 0;
3675
4444
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3676
4445
  };
@@ -3678,36 +4447,90 @@ var CodePlugin = class {
3678
4447
  showPage(1);
3679
4448
  }
3680
4449
  ctx.container.appendChild(container);
3681
- const cleanup = () => {
3682
- indicator?.remove();
3683
- container.remove();
3684
- ctx.container.innerHTML = "";
4450
+ const calculateTxtFit = () => {
4451
+ const availW = Math.max(280, container.clientWidth - 32);
4452
+ return Math.max(0.4, Math.min(3, availW / 816));
3685
4453
  };
3686
- ctx.signal.addEventListener("abort", cleanup);
3687
4454
  const updateTransform = () => {
3688
4455
  if (isTxt) {
3689
4456
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4457
+ const scaledH = 1056 * zoomLevel;
4458
+ const extraH = Math.max(0, scaledH - 1056);
4459
+ pre.style.marginBottom = `${extraH + 32}px`;
3690
4460
  } else {
3691
4461
  pre.style.transform = `rotate(${rotation}deg)`;
3692
4462
  pre.style.fontSize = `${fontSize}px`;
3693
4463
  }
3694
4464
  };
4465
+ let resizeObserver = null;
4466
+ if (isTxt) {
4467
+ setTimeout(() => {
4468
+ zoomLevel = calculateTxtFit();
4469
+ updateTransform();
4470
+ }, 60);
4471
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4472
+ if (!isUserZoomed) {
4473
+ zoomLevel = calculateTxtFit();
4474
+ updateTransform();
4475
+ }
4476
+ }) : null;
4477
+ resizeObserver?.observe(container);
4478
+ }
4479
+ const cleanup = () => {
4480
+ resizeObserver?.disconnect();
4481
+ container.remove();
4482
+ ctx.container.innerHTML = "";
4483
+ };
4484
+ ctx.signal.addEventListener("abort", cleanup);
3695
4485
  return {
3696
4486
  destroy: cleanup,
3697
4487
  getPageCount: () => totalPages,
3698
4488
  getCurrentPage: () => currentPage,
3699
4489
  goToPage: (page) => showPage(page),
4490
+ getThumbnails: () => {
4491
+ return rawPages.map((pageText, idx) => ({
4492
+ index: idx,
4493
+ label: `Page ${idx + 1}`,
4494
+ render: async (canvas) => {
4495
+ canvas.width = 140;
4496
+ canvas.height = 180;
4497
+ const c = canvas.getContext("2d");
4498
+ if (!c) return;
4499
+ c.fillStyle = "#ffffff";
4500
+ c.fillRect(0, 0, canvas.width, canvas.height);
4501
+ c.strokeStyle = "#cbd5e1";
4502
+ c.lineWidth = 1;
4503
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4504
+ c.fillStyle = "#64748b";
4505
+ const lines = (pageText || "").split("\n").slice(0, 24);
4506
+ const lineH = 6;
4507
+ const startY = 14;
4508
+ const startX = 12;
4509
+ const maxW = canvas.width - 24;
4510
+ c.font = "5px monospace";
4511
+ for (let li = 0; li < lines.length; li++) {
4512
+ const l = lines[li].trim();
4513
+ if (!l) continue;
4514
+ const y = startY + li * lineH;
4515
+ if (y > canvas.height - 12) break;
4516
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4517
+ }
4518
+ }
4519
+ }));
4520
+ },
3700
4521
  zoomIn: () => {
4522
+ isUserZoomed = true;
3701
4523
  if (isTxt) {
3702
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4524
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3703
4525
  } else {
3704
4526
  fontSize = Math.min(32, fontSize + 2);
3705
4527
  }
3706
4528
  updateTransform();
3707
4529
  },
3708
4530
  zoomOut: () => {
4531
+ isUserZoomed = true;
3709
4532
  if (isTxt) {
3710
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4533
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3711
4534
  } else {
3712
4535
  fontSize = Math.max(8, fontSize - 2);
3713
4536
  }
@@ -3715,6 +4538,7 @@ var CodePlugin = class {
3715
4538
  },
3716
4539
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3717
4540
  setZoom: (level) => {
4541
+ isUserZoomed = true;
3718
4542
  if (isTxt) {
3719
4543
  zoomLevel = level;
3720
4544
  } else {
@@ -3723,9 +4547,10 @@ var CodePlugin = class {
3723
4547
  updateTransform();
3724
4548
  },
3725
4549
  fitToPage: () => {
4550
+ isUserZoomed = false;
3726
4551
  fontSize = 13;
3727
4552
  rotation = 0;
3728
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4553
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3729
4554
  updateTransform();
3730
4555
  },
3731
4556
  rotateCW: () => {
@@ -4606,28 +5431,26 @@ var RtfPlugin = class {
4606
5431
  getToolbarActions(instance) {
4607
5432
  const totalPages = instance.getPageCount?.() ?? 1;
4608
5433
  const actions = [];
4609
- if (totalPages > 1) {
4610
- actions.push({
4611
- id: "page-nav",
4612
- icon: "",
4613
- label: "Page Navigation",
4614
- type: "page-nav",
4615
- group: "navigation",
4616
- value: instance.getCurrentPage?.() ?? 1,
4617
- max: totalPages,
4618
- execute: (action, page) => {
4619
- const cur = instance.getCurrentPage?.() ?? 1;
4620
- const max = instance.getPageCount?.() ?? 1;
4621
- if (action === "prev") {
4622
- if (cur > 1) instance.goToPage?.(cur - 1);
4623
- } else if (action === "next") {
4624
- if (cur < max) instance.goToPage?.(cur + 1);
4625
- } else if (typeof page === "number") {
4626
- instance.goToPage?.(page);
4627
- }
5434
+ actions.push({
5435
+ id: "page-nav",
5436
+ icon: "",
5437
+ label: "Page Navigation",
5438
+ type: "page-nav",
5439
+ group: "navigation",
5440
+ value: instance.getCurrentPage?.() ?? 1,
5441
+ max: totalPages,
5442
+ execute: (action, page) => {
5443
+ const cur = instance.getCurrentPage?.() ?? 1;
5444
+ const max = instance.getPageCount?.() ?? 1;
5445
+ if (action === "prev") {
5446
+ if (cur > 1) instance.goToPage?.(cur - 1);
5447
+ } else if (action === "next") {
5448
+ if (cur < max) instance.goToPage?.(cur + 1);
5449
+ } else if (typeof page === "number") {
5450
+ instance.goToPage?.(page);
4628
5451
  }
4629
- });
4630
- }
5452
+ }
5453
+ });
4631
5454
  actions.push(
4632
5455
  {
4633
5456
  id: "zoom-out",
@@ -4699,43 +5522,57 @@ var RtfPlugin = class {
4699
5522
  async render(ctx) {
4700
5523
  const wrapper = document.createElement("div");
4701
5524
  wrapper.className = "fp-rtf-wrapper";
4702
- wrapper.style.padding = "32px";
4703
- wrapper.style.maxWidth = "850px";
5525
+ wrapper.style.padding = "0";
5526
+ wrapper.style.width = "816px";
4704
5527
  wrapper.style.margin = "0 auto";
4705
- wrapper.style.backgroundColor = "#fff";
4706
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4707
- wrapper.style.borderRadius = "4px";
4708
- wrapper.style.minHeight = "100%";
5528
+ wrapper.style.boxSizing = "border-box";
5529
+ wrapper.style.display = "flex";
5530
+ wrapper.style.flexDirection = "column";
5531
+ wrapper.style.alignItems = "center";
5532
+ wrapper.style.backgroundColor = "transparent";
4709
5533
  wrapper.style.transformOrigin = "top center";
4710
- wrapper.style.transition = "transform 0.2s ease";
4711
- ctx.container.style.overflow = "auto";
4712
- ctx.container.style.padding = "24px";
5534
+ wrapper.style.transition = "transform 0.15s ease";
5535
+ ctx.container.style.overflowX = "hidden";
5536
+ ctx.container.style.overflowY = "auto";
5537
+ ctx.container.style.padding = "16px 8px";
4713
5538
  ctx.container.style.backgroundColor = "#f1f5f9";
4714
5539
  ctx.container.appendChild(wrapper);
4715
5540
  let scale = 1;
4716
5541
  let rotation = 0;
4717
5542
  let pageElements = [];
4718
- let fitMode = "width";
5543
+ let isUserZoomed = false;
5544
+ let fitMode = ctx?.options?.fitMode || "width";
4719
5545
  const calculateFitScale = (mode = fitMode) => {
4720
5546
  const activeEl = pageElements[currentPage - 1] || wrapper;
4721
- const elW = activeEl.offsetWidth || 850;
4722
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4723
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4724
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5547
+ const elW = 816;
5548
+ const singlePageH = activeEl?.offsetHeight || 1056;
5549
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5550
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4725
5551
  const sW = availW / elW;
4726
5552
  const sH = availH / singlePageH;
4727
5553
  if (mode === "page") {
4728
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5554
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4729
5555
  }
4730
- return Math.max(0.65, Math.min(1.05, sW));
5556
+ return Math.max(0.4, Math.min(3, sW));
4731
5557
  };
4732
5558
  const applyTransform = () => {
4733
5559
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4734
5560
  wrapper.style.transformOrigin = "top center";
5561
+ const activeEl = pageElements[currentPage - 1];
5562
+ const baseH = activeEl?.offsetHeight || 1056;
5563
+ const scaledH = baseH * scale;
5564
+ const extraH = Math.max(0, scaledH - baseH);
5565
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4735
5566
  };
5567
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5568
+ if (!isUserZoomed) {
5569
+ scale = calculateFitScale(fitMode);
5570
+ applyTransform();
5571
+ }
5572
+ }) : null;
5573
+ resizeObserver?.observe(ctx.container);
4736
5574
  setTimeout(() => {
4737
- fitMode = "width";
4738
- scale = calculateFitScale("width");
5575
+ scale = calculateFitScale(fitMode);
4739
5576
  applyTransform();
4740
5577
  }, 60);
4741
5578
  try {
@@ -4745,20 +5582,34 @@ var RtfPlugin = class {
4745
5582
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4746
5583
  const htmlElements = await doc.render();
4747
5584
  const contentNodes = [];
4748
- for (const item of htmlElements) {
4749
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4750
- contentNodes.push(...Array.from(item.children));
4751
- } else {
4752
- contentNodes.push(item);
5585
+ const extractBlocks = (nodes) => {
5586
+ for (const item of nodes) {
5587
+ if (!item || !(item instanceof HTMLElement)) continue;
5588
+ const tag = item.tagName.toLowerCase();
5589
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5590
+ const ct = c.tagName.toLowerCase();
5591
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5592
+ });
5593
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5594
+ extractBlocks(Array.from(item.children));
5595
+ } else {
5596
+ if (tag === "p" || tag === "div") {
5597
+ item.style.display = "block";
5598
+ item.style.marginBottom = "12px";
5599
+ item.style.lineHeight = "1.6";
5600
+ }
5601
+ contentNodes.push(item);
5602
+ }
4753
5603
  }
4754
- }
5604
+ };
5605
+ extractBlocks(htmlElements);
4755
5606
  wrapper.innerHTML = "";
4756
5607
  contentNodes.forEach((node) => wrapper.appendChild(node));
4757
5608
  const childHeights = contentNodes.map((c) => {
4758
5609
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4759
5610
  const offH = c.offsetHeight || 0;
4760
5611
  const textLen = c.textContent?.trim().length || 0;
4761
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
5612
+ const estH = Math.max(28, Math.ceil(textLen / 75) * 24 + 16);
4762
5613
  return Math.max(rectH, offH, estH);
4763
5614
  });
4764
5615
  wrapper.innerHTML = "";
@@ -4775,6 +5626,7 @@ var RtfPlugin = class {
4775
5626
  card.style.marginBottom = "24px";
4776
5627
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4777
5628
  card.style.lineHeight = "1.6";
5629
+ card.style.color = "#1e293b";
4778
5630
  return card;
4779
5631
  };
4780
5632
  let curCard = createRtfCard();
@@ -4810,9 +5662,8 @@ var RtfPlugin = class {
4810
5662
  pageCard.style.padding = "72px 56px";
4811
5663
  pageCard.style.width = "816px";
4812
5664
  pageCard.style.minHeight = "1056px";
4813
- pageCard.style.maxWidth = "100%";
4814
5665
  pageCard.style.boxSizing = "border-box";
4815
- pageCard.style.fontFamily = "serif";
5666
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4816
5667
  pageCard.style.fontSize = "12pt";
4817
5668
  pageCard.style.lineHeight = "1.6";
4818
5669
  pageCard.style.color = "#1e293b";
@@ -4825,29 +5676,6 @@ var RtfPlugin = class {
4825
5676
  }
4826
5677
  const totalPages = Math.max(1, pageElements.length);
4827
5678
  let currentPage = 1;
4828
- let indicator = null;
4829
- if (totalPages > 1) {
4830
- indicator = document.createElement("div");
4831
- indicator.className = "fp-rtf-page-indicator";
4832
- indicator.style.position = "sticky";
4833
- indicator.style.bottom = "16px";
4834
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4835
- indicator.style.backdropFilter = "blur(8px)";
4836
- indicator.style.color = "#f8fafc";
4837
- indicator.style.fontSize = "12px";
4838
- indicator.style.fontWeight = "600";
4839
- indicator.style.padding = "5px 14px";
4840
- indicator.style.borderRadius = "20px";
4841
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4842
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4843
- indicator.style.zIndex = "10";
4844
- indicator.style.userSelect = "none";
4845
- indicator.style.pointerEvents = "none";
4846
- indicator.style.textAlign = "center";
4847
- indicator.style.width = "fit-content";
4848
- indicator.style.margin = "16px auto 0";
4849
- ctx.container.appendChild(indicator);
4850
- }
4851
5679
  const showPage = (pageNum) => {
4852
5680
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4853
5681
  if (totalPages > 1) {
@@ -4855,9 +5683,6 @@ var RtfPlugin = class {
4855
5683
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4856
5684
  });
4857
5685
  }
4858
- if (indicator) {
4859
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4860
- }
4861
5686
  ctx.container.scrollTop = 0;
4862
5687
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4863
5688
  };
@@ -4865,7 +5690,7 @@ var RtfPlugin = class {
4865
5690
  showPage(1);
4866
5691
  }
4867
5692
  const cleanup = () => {
4868
- indicator?.remove();
5693
+ resizeObserver?.disconnect();
4869
5694
  wrapper.remove();
4870
5695
  ctx.container.innerHTML = "";
4871
5696
  };
@@ -4876,21 +5701,25 @@ var RtfPlugin = class {
4876
5701
  getCurrentPage: () => currentPage,
4877
5702
  goToPage: (page) => showPage(page),
4878
5703
  zoomIn: () => {
5704
+ isUserZoomed = true;
4879
5705
  scale += 0.15;
4880
5706
  applyTransform();
4881
5707
  },
4882
5708
  zoomOut: () => {
5709
+ isUserZoomed = true;
4883
5710
  scale = Math.max(0.2, scale - 0.15);
4884
5711
  applyTransform();
4885
5712
  },
4886
5713
  getZoom: () => scale,
4887
5714
  setZoom: (level) => {
5715
+ isUserZoomed = true;
4888
5716
  scale = level;
4889
5717
  applyTransform();
4890
5718
  },
4891
5719
  fitToPage: () => {
4892
- fitMode = fitMode === "width" ? "page" : "width";
4893
- scale = calculateFitScale(fitMode);
5720
+ isUserZoomed = false;
5721
+ fitMode = "width";
5722
+ scale = calculateFitScale("width");
4894
5723
  rotation = 0;
4895
5724
  applyTransform();
4896
5725
  },
@@ -5078,40 +5907,36 @@ var OpenDocumentPlugin = class {
5078
5907
  const isPresentation = instance.isPresentation;
5079
5908
  const totalPages = instance.getPageCount?.() ?? 1;
5080
5909
  const actions = [];
5081
- if (isPresentation || totalPages > 1) {
5082
- if (isPresentation) {
5083
- actions.push({
5084
- id: "thumbnails",
5085
- icon: "thumbnails",
5086
- label: "Slide Thumbnails",
5087
- type: "button",
5088
- group: "navigation",
5089
- execute: () => instance.toggleThumbnails?.()
5090
- });
5091
- }
5092
- actions.push({
5093
- id: "page-nav",
5094
- icon: "",
5095
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
5096
- type: "page-nav",
5097
- group: "navigation",
5098
- value: instance.getCurrentPage?.() ?? 1,
5099
- max: totalPages,
5100
- execute: (action, page) => {
5101
- const cur = instance.getCurrentPage?.() ?? 1;
5102
- const max = instance.getPageCount?.() ?? 1;
5103
- if (action === "prev") {
5104
- if (cur > 1) instance.goToPage?.(cur - 1);
5105
- } else if (action === "next") {
5106
- if (cur < max) instance.goToPage?.(cur + 1);
5107
- } else if (typeof page === "number") {
5108
- instance.goToPage?.(page);
5109
- } else if (typeof action === "number") {
5110
- instance.goToPage?.(action);
5111
- }
5910
+ actions.push({
5911
+ id: "thumbnails",
5912
+ icon: "thumbnails",
5913
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
5914
+ type: "button",
5915
+ group: "navigation",
5916
+ execute: () => instance.toggleThumbnails?.()
5917
+ });
5918
+ actions.push({
5919
+ id: "page-nav",
5920
+ icon: "",
5921
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
5922
+ type: "page-nav",
5923
+ group: "navigation",
5924
+ value: instance.getCurrentPage?.() ?? 1,
5925
+ max: totalPages,
5926
+ execute: (action, page) => {
5927
+ const cur = instance.getCurrentPage?.() ?? 1;
5928
+ const max = instance.getPageCount?.() ?? 1;
5929
+ if (action === "prev") {
5930
+ if (cur > 1) instance.goToPage?.(cur - 1);
5931
+ } else if (action === "next") {
5932
+ if (cur < max) instance.goToPage?.(cur + 1);
5933
+ } else if (typeof page === "number") {
5934
+ instance.goToPage?.(page);
5935
+ } else if (typeof action === "number") {
5936
+ instance.goToPage?.(action);
5112
5937
  }
5113
- });
5114
- }
5938
+ }
5939
+ });
5115
5940
  actions.push(
5116
5941
  {
5117
5942
  id: "zoom-out",
@@ -5202,49 +6027,65 @@ var OpenDocumentPlugin = class {
5202
6027
  container.className = "fp-odf-container";
5203
6028
  container.style.width = "100%";
5204
6029
  container.style.height = "100%";
5205
- container.style.overflow = "auto";
5206
- container.style.padding = "24px";
6030
+ container.style.overflowX = "hidden";
6031
+ container.style.overflowY = "auto";
6032
+ container.style.padding = "16px 8px";
5207
6033
  container.style.backgroundColor = "#f1f5f9";
5208
6034
  const wrapper = document.createElement("div");
5209
6035
  wrapper.className = "fp-odf-wrapper";
5210
6036
  wrapper.style.margin = "0 auto";
6037
+ wrapper.style.width = isPresentation ? "960px" : "816px";
6038
+ wrapper.style.boxSizing = "border-box";
5211
6039
  wrapper.style.backgroundColor = "#ffffff";
5212
6040
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5213
6041
  wrapper.style.borderRadius = "4px";
5214
6042
  wrapper.style.transformOrigin = "top center";
5215
- wrapper.style.transition = "transform 0.2s ease";
6043
+ wrapper.style.transition = "transform 0.15s ease";
5216
6044
  container.appendChild(wrapper);
5217
6045
  ctx.container.appendChild(container);
5218
6046
  let scale = 1;
5219
6047
  let rotation = 0;
5220
- let fitMode = "width";
6048
+ let isUserZoomed = false;
6049
+ let fitMode = ctx?.options?.fitMode || "width";
6050
+ let currentPage = 1;
6051
+ let totalPages = 1;
6052
+ let slides = [];
5221
6053
  const calculateFitScale = (mode = fitMode) => {
5222
- const elW = wrapper.offsetWidth || 850;
5223
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5224
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5225
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6054
+ const activeSlide = slides[currentPage - 1];
6055
+ const elW = isPresentation ? 960 : 816;
6056
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
6057
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6058
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5226
6059
  const sW = availW / elW;
5227
6060
  const sH = availH / (isPresentation ? 540 : singlePageH);
5228
6061
  if (isPresentation) {
5229
- return Math.min(1, Math.min(sW, sH));
6062
+ return Math.min(2.5, Math.min(sW, sH));
5230
6063
  }
5231
6064
  if (mode === "page") {
5232
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6065
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5233
6066
  }
5234
- return Math.max(0.65, Math.min(1.05, sW));
6067
+ return Math.max(0.4, Math.min(3, sW));
5235
6068
  };
5236
6069
  const applyTransform = () => {
5237
6070
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5238
6071
  wrapper.style.transformOrigin = "top center";
6072
+ const activeSlide = slides[currentPage - 1];
6073
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6074
+ const scaledH = baseH * scale;
6075
+ const extraH = Math.max(0, scaledH - baseH);
6076
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5239
6077
  };
6078
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6079
+ if (!isUserZoomed) {
6080
+ scale = calculateFitScale(fitMode);
6081
+ applyTransform();
6082
+ }
6083
+ }) : null;
6084
+ resizeObserver?.observe(ctx.container);
5240
6085
  setTimeout(() => {
5241
- fitMode = "width";
5242
- scale = calculateFitScale("width");
6086
+ scale = calculateFitScale(fitMode);
5243
6087
  applyTransform();
5244
6088
  }, 60);
5245
- let currentPage = 1;
5246
- let totalPages = 1;
5247
- let slides = [];
5248
6089
  if (isPresentation) {
5249
6090
  wrapper.style.maxWidth = "960px";
5250
6091
  wrapper.style.aspectRatio = "16 / 9";
@@ -5345,24 +6186,9 @@ var OpenDocumentPlugin = class {
5345
6186
  wrapper.appendChild(page);
5346
6187
  slides.push(page);
5347
6188
  });
5348
- const pageIndicator = document.createElement("div");
5349
- pageIndicator.className = "fp-odt-page-indicator";
5350
- pageIndicator.style.position = "sticky";
5351
- pageIndicator.style.bottom = "16px";
5352
- pageIndicator.style.left = "50%";
5353
- pageIndicator.style.transform = "translateX(-50%)";
5354
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5355
- pageIndicator.style.color = "#fff";
5356
- pageIndicator.style.padding = "6px 12px";
5357
- pageIndicator.style.borderRadius = "16px";
5358
- pageIndicator.style.fontSize = "12px";
5359
- pageIndicator.style.zIndex = "100";
5360
- pageIndicator.style.display = "inline-block";
5361
- pageIndicator.style.width = "fit-content";
5362
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5363
- container.appendChild(pageIndicator);
5364
6189
  }
5365
6190
  const cleanup = () => {
6191
+ resizeObserver?.disconnect();
5366
6192
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5367
6193
  container.remove();
5368
6194
  ctx.container.innerHTML = "";
@@ -5374,10 +6200,6 @@ var OpenDocumentPlugin = class {
5374
6200
  slides.forEach((s, idx) => {
5375
6201
  s.style.display = idx === page - 1 ? "block" : "none";
5376
6202
  });
5377
- const indicator = container.querySelector(".fp-odt-page-indicator");
5378
- if (indicator) {
5379
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5380
- }
5381
6203
  container.scrollTop = 0;
5382
6204
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5383
6205
  };
@@ -5385,21 +6207,25 @@ var OpenDocumentPlugin = class {
5385
6207
  destroy: cleanup,
5386
6208
  isPresentation,
5387
6209
  zoomIn: () => {
6210
+ isUserZoomed = true;
5388
6211
  scale += 0.15;
5389
6212
  applyTransform();
5390
6213
  },
5391
6214
  zoomOut: () => {
6215
+ isUserZoomed = true;
5392
6216
  scale = Math.max(0.2, scale - 0.15);
5393
6217
  applyTransform();
5394
6218
  },
5395
6219
  getZoom: () => scale,
5396
6220
  setZoom: (level) => {
6221
+ isUserZoomed = true;
5397
6222
  scale = level;
5398
6223
  applyTransform();
5399
6224
  },
5400
6225
  fitToPage: () => {
5401
- fitMode = fitMode === "width" ? "page" : "width";
5402
- scale = calculateFitScale(fitMode);
6226
+ isUserZoomed = false;
6227
+ fitMode = "width";
6228
+ scale = calculateFitScale("width");
5403
6229
  rotation = 0;
5404
6230
  applyTransform();
5405
6231
  },
@@ -5415,23 +6241,45 @@ var OpenDocumentPlugin = class {
5415
6241
  getPageCount: () => totalPages,
5416
6242
  getCurrentPage: () => currentPage,
5417
6243
  getThumbnails: async () => {
5418
- if (!isPresentation) return [];
5419
- return slides.map((_, idx) => ({
5420
- index: idx,
5421
- label: `Slide ${idx + 1}`,
5422
- render: async (canvas) => {
5423
- const ctx2d = canvas.getContext("2d");
5424
- if (!ctx2d) return;
5425
- canvas.width = 160;
5426
- canvas.height = 90;
5427
- ctx2d.fillStyle = "#f8fafc";
5428
- ctx2d.fillRect(0, 0, 160, 90);
5429
- ctx2d.fillStyle = "#334155";
5430
- ctx2d.font = "bold 12px sans-serif";
5431
- ctx2d.textAlign = "center";
5432
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5433
- }
5434
- }));
6244
+ const count = totalPages || slides.length || 1;
6245
+ const items = [];
6246
+ for (let idx = 0; idx < count; idx++) {
6247
+ const itemIdx = idx;
6248
+ items.push({
6249
+ index: itemIdx,
6250
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6251
+ render: async (canvas) => {
6252
+ const ctx2d = canvas.getContext("2d");
6253
+ if (!ctx2d) return;
6254
+ if (isPresentation) {
6255
+ canvas.width = 160;
6256
+ canvas.height = 90;
6257
+ ctx2d.fillStyle = "#f8fafc";
6258
+ ctx2d.fillRect(0, 0, 160, 90);
6259
+ ctx2d.strokeStyle = "#cbd5e1";
6260
+ ctx2d.lineWidth = 1;
6261
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6262
+ ctx2d.fillStyle = "#334155";
6263
+ ctx2d.font = "bold 11px sans-serif";
6264
+ ctx2d.textAlign = "center";
6265
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6266
+ } else {
6267
+ canvas.width = 140;
6268
+ canvas.height = 180;
6269
+ ctx2d.fillStyle = "#ffffff";
6270
+ ctx2d.fillRect(0, 0, 140, 180);
6271
+ ctx2d.strokeStyle = "#cbd5e1";
6272
+ ctx2d.lineWidth = 1;
6273
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6274
+ ctx2d.fillStyle = "#64748b";
6275
+ ctx2d.font = "bold 10px sans-serif";
6276
+ ctx2d.textAlign = "center";
6277
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6278
+ }
6279
+ }
6280
+ });
6281
+ }
6282
+ return items;
5435
6283
  },
5436
6284
  download: () => {
5437
6285
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5536,23 +6384,24 @@ var OpenDocumentPlugin = class {
5536
6384
  case "h": {
5537
6385
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5538
6386
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5539
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6387
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5540
6388
  break;
5541
6389
  }
5542
6390
  case "p": {
5543
6391
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5544
6392
  const inner = this.renderSpans(node, styles, images);
5545
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6393
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6394
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5546
6395
  break;
5547
6396
  }
5548
6397
  case "list": {
5549
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6398
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5550
6399
  Array.from(node.children).forEach((c) => walk(c));
5551
6400
  html += "</ul>";
5552
6401
  break;
5553
6402
  }
5554
6403
  case "list-item": {
5555
- html += "<li>";
6404
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5556
6405
  Array.from(node.children).forEach((c) => walk(c));
5557
6406
  html += "</li>";
5558
6407
  break;
@@ -5678,28 +6527,26 @@ var DocPlugin = class {
5678
6527
  getToolbarActions(instance) {
5679
6528
  const totalPages = instance.getPageCount?.() ?? 1;
5680
6529
  const actions = [];
5681
- if (totalPages > 1) {
5682
- actions.push({
5683
- id: "page-nav",
5684
- icon: "",
5685
- label: "Page Navigation",
5686
- type: "page-nav",
5687
- group: "navigation",
5688
- value: instance.getCurrentPage?.() ?? 1,
5689
- max: totalPages,
5690
- execute: (action, page) => {
5691
- const cur = instance.getCurrentPage?.() ?? 1;
5692
- const max = instance.getPageCount?.() ?? 1;
5693
- if (action === "prev") {
5694
- if (cur > 1) instance.goToPage?.(cur - 1);
5695
- } else if (action === "next") {
5696
- if (cur < max) instance.goToPage?.(cur + 1);
5697
- } else if (typeof page === "number") {
5698
- instance.goToPage?.(page);
5699
- }
6530
+ actions.push({
6531
+ id: "page-nav",
6532
+ icon: "",
6533
+ label: "Page Navigation",
6534
+ type: "page-nav",
6535
+ group: "navigation",
6536
+ value: instance.getCurrentPage?.() ?? 1,
6537
+ max: totalPages,
6538
+ execute: (action, page) => {
6539
+ const cur = instance.getCurrentPage?.() ?? 1;
6540
+ const max = instance.getPageCount?.() ?? 1;
6541
+ if (action === "prev") {
6542
+ if (cur > 1) instance.goToPage?.(cur - 1);
6543
+ } else if (action === "next") {
6544
+ if (cur < max) instance.goToPage?.(cur + 1);
6545
+ } else if (typeof page === "number") {
6546
+ instance.goToPage?.(page);
5700
6547
  }
5701
- });
5702
- }
6548
+ }
6549
+ });
5703
6550
  actions.push(
5704
6551
  {
5705
6552
  id: "zoom-out",
@@ -5773,8 +6620,9 @@ var DocPlugin = class {
5773
6620
  container.className = "fp-doc-container";
5774
6621
  container.style.width = "100%";
5775
6622
  container.style.height = "100%";
5776
- container.style.overflow = "auto";
5777
- container.style.padding = "32px 16px";
6623
+ container.style.overflowX = "hidden";
6624
+ container.style.overflowY = "auto";
6625
+ container.style.padding = "16px 8px";
5778
6626
  container.style.backgroundColor = "#f1f5f9";
5779
6627
  container.style.display = "flex";
5780
6628
  container.style.justifyContent = "center";
@@ -5783,8 +6631,17 @@ var DocPlugin = class {
5783
6631
  let scale = 1;
5784
6632
  let extractedRawText = "";
5785
6633
  let isFallback = false;
6634
+ let chartSvg = "";
5786
6635
  try {
5787
6636
  const cfbf = new CfbfReader(ctx.buffer);
6637
+ try {
6638
+ const pkg = cfbf.readStream("package_stream");
6639
+ if (pkg && pkg.length > 100) {
6640
+ chartSvg = this.parseOdfChartToSvg(pkg);
6641
+ }
6642
+ } catch (chartErr) {
6643
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
6644
+ }
5788
6645
  const wordDocStream = cfbf.readStream("WordDocument");
5789
6646
  if (!wordDocStream || wordDocStream.length < 512) {
5790
6647
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5802,7 +6659,7 @@ var DocPlugin = class {
5802
6659
  extractedRawText = fallback;
5803
6660
  isFallback = true;
5804
6661
  }
5805
- const rawPages = this.splitIntoPages(extractedRawText);
6662
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5806
6663
  const totalPages = Math.max(1, rawPages.length);
5807
6664
  let currentPage = 1;
5808
6665
  const pageCards = [];
@@ -5810,16 +6667,15 @@ var DocPlugin = class {
5810
6667
  const pageCard = document.createElement("div");
5811
6668
  pageCard.className = "fp-doc-page-card";
5812
6669
  pageCard.style.width = "816px";
5813
- pageCard.style.height = "1056px";
6670
+ pageCard.style.minHeight = "1056px";
5814
6671
  pageCard.style.backgroundColor = "#ffffff";
5815
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6672
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5816
6673
  pageCard.style.borderRadius = "4px";
5817
- pageCard.style.padding = "96px 72px";
6674
+ pageCard.style.padding = "72px 56px";
5818
6675
  pageCard.style.boxSizing = "border-box";
5819
- pageCard.style.overflow = "hidden";
5820
6676
  pageCard.style.display = i === 0 ? "block" : "none";
5821
6677
  pageCard.style.transformOrigin = "top center";
5822
- pageCard.style.transition = "transform 0.2s ease";
6678
+ pageCard.style.transition = "transform 0.15s ease";
5823
6679
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5824
6680
  pageCard.style.color = "#1e293b";
5825
6681
  if (isFallback && i === 0) {
@@ -5836,34 +6692,15 @@ var DocPlugin = class {
5836
6692
  container.appendChild(pageCard);
5837
6693
  pageCards.push(pageCard);
5838
6694
  }
5839
- let indicator = null;
5840
- if (totalPages > 1) {
5841
- indicator = document.createElement("div");
5842
- indicator.className = "fp-doc-page-indicator";
5843
- indicator.style.position = "fixed";
5844
- indicator.style.bottom = "16px";
5845
- indicator.style.left = "50%";
5846
- indicator.style.transform = "translateX(-50%)";
5847
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5848
- indicator.style.backdropFilter = "blur(8px)";
5849
- indicator.style.color = "#f8fafc";
5850
- indicator.style.fontSize = "12px";
5851
- indicator.style.fontWeight = "600";
5852
- indicator.style.padding = "5px 14px";
5853
- indicator.style.borderRadius = "20px";
5854
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5855
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5856
- indicator.style.zIndex = "10";
5857
- indicator.style.userSelect = "none";
5858
- indicator.style.pointerEvents = "none";
5859
- indicator.style.textAlign = "center";
5860
- container.appendChild(indicator);
5861
- }
5862
6695
  let rotation = 0;
5863
6696
  const applyTransform = () => {
5864
6697
  const activeCard = pageCards[currentPage - 1];
5865
6698
  if (activeCard) {
5866
6699
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6700
+ const baseH = activeCard.offsetHeight || 1056;
6701
+ const scaledH = baseH * scale;
6702
+ const extraH = Math.max(0, scaledH - baseH);
6703
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5867
6704
  }
5868
6705
  };
5869
6706
  const showPage = (pageNum) => {
@@ -5876,35 +6713,39 @@ var DocPlugin = class {
5876
6713
  card.style.display = "none";
5877
6714
  }
5878
6715
  });
5879
- if (indicator) {
5880
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5881
- }
5882
6716
  container.scrollTop = 0;
5883
6717
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5884
6718
  };
5885
6719
  if (totalPages > 1) {
5886
6720
  showPage(1);
5887
6721
  }
5888
- let fitMode = "width";
6722
+ let fitMode = ctx?.options?.fitMode || "width";
6723
+ let isUserZoomed = false;
5889
6724
  const calculateFitScale = (mode = fitMode) => {
5890
6725
  const elW = 816;
5891
6726
  const elH = 1056;
5892
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5893
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6727
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6728
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5894
6729
  const sW = availW / elW;
5895
6730
  const sH = availH / elH;
5896
6731
  if (mode === "page") {
5897
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6732
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5898
6733
  }
5899
- return Math.max(0.65, Math.min(1.05, sW));
6734
+ return Math.max(0.4, Math.min(3, sW));
5900
6735
  };
6736
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6737
+ if (!isUserZoomed) {
6738
+ scale = calculateFitScale(fitMode);
6739
+ applyTransform();
6740
+ }
6741
+ }) : null;
6742
+ resizeObserver?.observe(ctx.container);
5901
6743
  setTimeout(() => {
5902
- fitMode = "width";
5903
- scale = calculateFitScale("width");
6744
+ scale = calculateFitScale(fitMode);
5904
6745
  applyTransform();
5905
6746
  }, 60);
5906
6747
  const cleanup = () => {
5907
- indicator?.remove();
6748
+ resizeObserver?.disconnect();
5908
6749
  container.remove();
5909
6750
  ctx.container.innerHTML = "";
5910
6751
  };
@@ -5915,21 +6756,25 @@ var DocPlugin = class {
5915
6756
  getCurrentPage: () => currentPage,
5916
6757
  goToPage: (page) => showPage(page),
5917
6758
  zoomIn: () => {
6759
+ isUserZoomed = true;
5918
6760
  scale += 0.15;
5919
6761
  applyTransform();
5920
6762
  },
5921
6763
  zoomOut: () => {
6764
+ isUserZoomed = true;
5922
6765
  scale = Math.max(0.2, scale - 0.15);
5923
6766
  applyTransform();
5924
6767
  },
5925
6768
  getZoom: () => scale,
5926
6769
  setZoom: (level) => {
6770
+ isUserZoomed = true;
5927
6771
  scale = level;
5928
6772
  applyTransform();
5929
6773
  },
5930
6774
  fitToPage: () => {
5931
- fitMode = fitMode === "width" ? "page" : "width";
5932
- scale = calculateFitScale(fitMode);
6775
+ isUserZoomed = false;
6776
+ fitMode = "width";
6777
+ scale = calculateFitScale("width");
5933
6778
  rotation = 0;
5934
6779
  applyTransform();
5935
6780
  },
@@ -6044,7 +6889,7 @@ var DocPlugin = class {
6044
6889
  for (const run of [...ansiRuns, ...utf16Runs]) {
6045
6890
  const trimmed = run.trim();
6046
6891
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
6047
- if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
6892
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^EMBED\b/i.test(trimmed) && !trimmed.includes("ChartDocument") && !/^[\W_0-9]+$/.test(trimmed)) {
6048
6893
  seen.add(trimmed);
6049
6894
  candidateLines.push(trimmed);
6050
6895
  }
@@ -6055,9 +6900,114 @@ var DocPlugin = class {
6055
6900
  heuristicTextExtraction(buffer) {
6056
6901
  return this.extractStringsFromBytes(new Uint8Array(buffer));
6057
6902
  }
6058
- cleanWordDocFields(text) {
6903
+ /**
6904
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
6905
+ */
6906
+ parseOdfChartToSvg(zipBytes) {
6907
+ try {
6908
+ const unzipped = fflate__namespace.unzipSync(zipBytes);
6909
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
6910
+ if (!contentXml) return "";
6911
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
6912
+ if (rowsMatch.length < 2) return "";
6913
+ const headers = [];
6914
+ const firstRow = rowsMatch[0];
6915
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6916
+ for (const h of headerCells) {
6917
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6918
+ }
6919
+ const categories = [];
6920
+ const seriesValues = headers.map(() => []);
6921
+ for (let r = 1; r < rowsMatch.length; r++) {
6922
+ const rowStr = rowsMatch[r];
6923
+ if (!rowStr) continue;
6924
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6925
+ if (cells.length > 0 && cells[0]) {
6926
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6927
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6928
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6929
+ const cellStr = cells[c];
6930
+ if (!cellStr) continue;
6931
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6932
+ const series = seriesValues[c - 1];
6933
+ if (series) {
6934
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6935
+ }
6936
+ }
6937
+ }
6938
+ }
6939
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6940
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6941
+ let cIdx = 0;
6942
+ for (const cm of colorMatches) {
6943
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6944
+ cIdx++;
6945
+ }
6946
+ let maxVal = 10;
6947
+ for (const s of seriesValues) {
6948
+ for (const v of s) {
6949
+ if (v > maxVal) maxVal = v;
6950
+ }
6951
+ }
6952
+ maxVal = Math.ceil(maxVal * 1.15);
6953
+ const width = 560;
6954
+ const height = 280;
6955
+ const padLeft = 45;
6956
+ const padRight = 100;
6957
+ const padTop = 20;
6958
+ const padBottom = 40;
6959
+ const chartW = width - padLeft - padRight;
6960
+ const chartH = height - padTop - padBottom;
6961
+ let svg = `<svg viewBox="0 0 ${width} ${height}" width="100%" height="auto" style="max-width: 560px; height: 280px; margin: 16px auto; display: block; font-family: Calibri, sans-serif; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 1px 4px rgba(0,0,0,0.05);">`;
6962
+ for (let step = 0; step <= 4; step++) {
6963
+ const yVal = (maxVal / 4 * step).toFixed(1);
6964
+ const yPos = padTop + chartH - step / 4 * chartH;
6965
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6966
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6967
+ }
6968
+ const numCats = categories.length;
6969
+ const numSeries = headers.length;
6970
+ const groupW = chartW / numCats;
6971
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6972
+ const groupPad = (groupW - barW * numSeries) / 2;
6973
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6974
+ const groupX = padLeft + catIdx * groupW + groupPad;
6975
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6976
+ const val = seriesValues[sIdx][catIdx] || 0;
6977
+ const barH = val / maxVal * chartH;
6978
+ const barX = groupX + sIdx * barW;
6979
+ const barY = padTop + chartH - barH;
6980
+ const col = colors[sIdx % colors.length];
6981
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6982
+ }
6983
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6984
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6985
+ }
6986
+ let legendY = padTop + 20;
6987
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6988
+ const col = colors[sIdx % colors.length];
6989
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6990
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6991
+ legendY += 20;
6992
+ }
6993
+ svg += "</svg>";
6994
+ return svg;
6995
+ } catch (e) {
6996
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6997
+ return "";
6998
+ }
6999
+ }
7000
+ cleanWordDocFields(text, chartSvg = "") {
6059
7001
  if (!text) return "";
6060
7002
  let cleaned = text.replace(
7003
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
7004
+ () => chartSvg ? `
7005
+
7006
+ ${chartSvg}
7007
+
7008
+ ` : ""
7009
+ );
7010
+ cleaned = cleaned.replace(
6061
7011
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
6062
7012
  (_match, url, label) => {
6063
7013
  const cleanUrl = url.trim();
@@ -6065,18 +7015,23 @@ var DocPlugin = class {
6065
7015
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
6066
7016
  }
6067
7017
  );
6068
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
7018
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
7019
+ if (/[\x00-\x1F]/.test(res)) return "";
7020
+ return res.trim();
7021
+ });
6069
7022
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6070
7023
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
7024
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
7025
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
6071
7026
  return cleaned;
6072
7027
  }
6073
- splitIntoPages(text) {
7028
+ splitIntoPages(text, chartSvg = "") {
6074
7029
  if (!text) return [""];
6075
- const cleanedText = this.cleanWordDocFields(text);
7030
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
6076
7031
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6077
7032
  const explicitParts = normalized.split(/[\x0C\f]|\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
6078
7033
  if (explicitParts.length === 0) explicitParts.push(normalized);
6079
- const maxLinesPerPage = 32;
7034
+ const maxLinesPerPage = 34;
6080
7035
  const charsPerLine = 80;
6081
7036
  const finalPages = [];
6082
7037
  for (const part of explicitParts) {
@@ -6084,8 +7039,8 @@ var DocPlugin = class {
6084
7039
  let currentLines = [];
6085
7040
  let count = 0;
6086
7041
  for (const line of lines) {
6087
- const plainLine = line.replace(/<[^>]+>/g, "");
6088
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
7042
+ const isSvg = line.includes("<svg");
7043
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
6089
7044
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
6090
7045
  finalPages.push(currentLines.join("\n"));
6091
7046
  currentLines = [];
@@ -6125,9 +7080,44 @@ var DocPlugin = class {
6125
7080
  tableLines = [];
6126
7081
  }
6127
7082
  };
7083
+ const sanitizeOptions = {
7084
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
7085
+ ADD_ATTR: [
7086
+ "href",
7087
+ "target",
7088
+ "rel",
7089
+ "style",
7090
+ "viewBox",
7091
+ "width",
7092
+ "height",
7093
+ "x",
7094
+ "y",
7095
+ "x1",
7096
+ "y1",
7097
+ "x2",
7098
+ "y2",
7099
+ "fill",
7100
+ "stroke",
7101
+ "stroke-width",
7102
+ "stroke-dasharray",
7103
+ "rx",
7104
+ "font-size",
7105
+ "text-anchor"
7106
+ ]
7107
+ };
6128
7108
  let i = 0;
6129
7109
  while (i < lines.length) {
6130
7110
  let line = lines[i];
7111
+ if (line.includes("<svg")) {
7112
+ if (inList) {
7113
+ html += "</ul>";
7114
+ inList = false;
7115
+ }
7116
+ flushTable();
7117
+ html += line;
7118
+ i++;
7119
+ continue;
7120
+ }
6131
7121
  let tabCount = (line.match(/\t/g) || []).length;
6132
7122
  if (tabCount > 0) {
6133
7123
  let j = i;
@@ -6161,26 +7151,31 @@ var DocPlugin = class {
6161
7151
  i++;
6162
7152
  continue;
6163
7153
  }
6164
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6165
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7154
+ if (/^\d{1,2}$/.test(line.trim())) {
7155
+ i++;
7156
+ continue;
7157
+ }
7158
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7159
+ 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));
7160
+ if (isTitleLike) {
6166
7161
  if (inList) {
6167
7162
  html += "</ul>";
6168
7163
  inList = false;
6169
7164
  }
6170
- 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>`;
7165
+ 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>`;
6171
7166
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6172
7167
  if (!inList) {
6173
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7168
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6174
7169
  inList = true;
6175
7170
  }
6176
7171
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6177
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
7172
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
6178
7173
  } else {
6179
7174
  if (inList) {
6180
7175
  html += "</ul>";
6181
7176
  inList = false;
6182
7177
  }
6183
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
7178
+ 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>`;
6184
7179
  }
6185
7180
  i++;
6186
7181
  }