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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -578,8 +578,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
578
578
  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>`;
579
579
  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>`;
580
580
  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>`;
581
- 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>`;
582
- 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>`;
581
+ 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>`;
583
582
  var ICON_MAP = {
584
583
  "zoom-in": ICON_ZOOM_IN,
585
584
  "zoom-out": ICON_ZOOM_OUT,
@@ -606,28 +605,177 @@ var ICON_MAP = {
606
605
  "forward-10": ICON_FAST_FORWARD,
607
606
  "rewind": ICON_REWIND,
608
607
  "replay-10": ICON_REWIND,
609
- "speed": ICON_SPEED,
610
608
  "open-window": ICON_EXTERNAL_WINDOW,
611
- "external-window": ICON_EXTERNAL_WINDOW
609
+ "external-window": ICON_EXTERNAL_WINDOW,
610
+ "openWindow": ICON_EXTERNAL_WINDOW,
611
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
612
612
  };
613
613
  var ToolbarController = class {
614
614
  el;
615
615
  toolbarEl;
616
616
  actions = [];
617
+ config = {};
617
618
  pageInputEl = null;
618
619
  pageLabelEl = null;
619
- constructor(container) {
620
+ prevPageBtn = null;
621
+ nextPageBtn = null;
622
+ constructor(container, config) {
620
623
  this.el = container;
624
+ if (config) this.config = { ...config };
621
625
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
622
626
  this.el.appendChild(this.toolbarEl);
623
627
  }
628
+ setConfig(config) {
629
+ this.config = { ...config };
630
+ this.render();
631
+ }
632
+ getConfig() {
633
+ return { ...this.config };
634
+ }
635
+ hideAction(actionId) {
636
+ this.config[actionId] = false;
637
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
638
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
639
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
640
+ this.config.fitPage = false;
641
+ this.config.fitToPage = false;
642
+ this.config.fitWidth = false;
643
+ }
644
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
645
+ if (actionId === "fullscreen") this.config.fullscreen = false;
646
+ if (actionId === "download") this.config.download = false;
647
+ if (actionId === "print") this.config.print = false;
648
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
649
+ this.config.openWindow = false;
650
+ this.config.openSeparateWindow = false;
651
+ }
652
+ if (actionId === "copy") this.config.copy = false;
653
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
654
+ this.config.pageNav = false;
655
+ this.config.pagination = false;
656
+ }
657
+ this.render();
658
+ }
659
+ showAction(actionId) {
660
+ this.config[actionId] = true;
661
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
662
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
663
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
664
+ this.config.fitPage = true;
665
+ this.config.fitToPage = true;
666
+ this.config.fitWidth = true;
667
+ }
668
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
669
+ if (actionId === "fullscreen") this.config.fullscreen = true;
670
+ if (actionId === "download") this.config.download = true;
671
+ if (actionId === "print") this.config.print = true;
672
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
673
+ this.config.openWindow = true;
674
+ this.config.openSeparateWindow = true;
675
+ }
676
+ if (actionId === "copy") this.config.copy = true;
677
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
678
+ this.config.pageNav = true;
679
+ this.config.pagination = true;
680
+ }
681
+ this.render();
682
+ }
683
+ normalizeActionId(actionId) {
684
+ if (actionId === "zoomIn") return "zoom-in";
685
+ if (actionId === "zoomOut") return "zoom-out";
686
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
687
+ if (actionId === "rotateCW") return "rotate-cw";
688
+ if (actionId === "rotateCCW") return "rotate-ccw";
689
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
690
+ if (actionId === "pageNav") return "page-nav";
691
+ return actionId;
692
+ }
693
+ enableAction(actionId) {
694
+ const norm = this.normalizeActionId(actionId);
695
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
696
+ if (btn) {
697
+ btn.disabled = false;
698
+ btn.classList.remove("disabled");
699
+ }
700
+ }
701
+ disableAction(actionId) {
702
+ const norm = this.normalizeActionId(actionId);
703
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
704
+ if (btn) {
705
+ btn.disabled = true;
706
+ btn.classList.add("disabled");
707
+ }
708
+ }
709
+ setActionActive(actionId, active) {
710
+ const norm = this.normalizeActionId(actionId);
711
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
712
+ if (btn) {
713
+ if (active) {
714
+ btn.classList.add("active");
715
+ } else {
716
+ btn.classList.remove("active");
717
+ }
718
+ }
719
+ }
720
+ isActionEnabled(action) {
721
+ const c = this.config;
722
+ const id = action.id;
723
+ if (c[id] === false) return false;
724
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
725
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
726
+ 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)) {
727
+ return false;
728
+ }
729
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
730
+ return false;
731
+ }
732
+ if (id === "fullscreen" && c.fullscreen === false) return false;
733
+ if (id === "download" && c.download === false) return false;
734
+ if (id === "print" && c.print === false) return false;
735
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
736
+ return false;
737
+ }
738
+ if (id === "copy" && c.copy === false) return false;
739
+ if (id === "thumbnails" && c.thumbnails === false) return false;
740
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
741
+ return false;
742
+ }
743
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
744
+ return false;
745
+ }
746
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
747
+ return false;
748
+ }
749
+ if (id === "play" && c.play === false) return false;
750
+ if (id === "pause" && c.pause === false) return false;
751
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
752
+ return false;
753
+ }
754
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
755
+ return false;
756
+ }
757
+ if (id === "speed" && c.speed === false) return false;
758
+ return true;
759
+ }
624
760
  setPage(page, max) {
625
761
  if (this.pageInputEl) {
626
762
  this.pageInputEl.value = page.toString();
763
+ if (max !== void 0) {
764
+ this.pageInputEl.max = max.toString();
765
+ }
627
766
  }
628
767
  if (max !== void 0 && this.pageLabelEl) {
629
768
  this.pageLabelEl.textContent = ` / ${max}`;
630
769
  }
770
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
771
+ if (this.prevPageBtn) {
772
+ this.prevPageBtn.disabled = page <= 1;
773
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
774
+ }
775
+ if (this.nextPageBtn) {
776
+ this.nextPageBtn.disabled = page >= currentMax;
777
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
778
+ }
631
779
  }
632
780
  update(actions) {
633
781
  this.actions = actions;
@@ -650,8 +798,9 @@ var ToolbarController = class {
650
798
  view: [],
651
799
  actions: []
652
800
  };
653
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
654
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
801
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
802
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
803
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
655
804
  for (const action of effectiveActions) {
656
805
  if (groups[action.group]) {
657
806
  groups[action.group].push(action);
@@ -711,6 +860,13 @@ var ToolbarController = class {
711
860
  action.execute("go", val);
712
861
  });
713
862
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
863
+ const curVal = action.value ?? 1;
864
+ prevBtn.disabled = curVal <= 1;
865
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
866
+ nextBtn.disabled = curVal >= max;
867
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
868
+ this.prevPageBtn = prevBtn;
869
+ this.nextPageBtn = nextBtn;
714
870
  this.pageInputEl = input;
715
871
  this.pageLabelEl = label;
716
872
  groupEl.appendChild(prevBtn);
@@ -766,25 +922,63 @@ var ToolbarController = class {
766
922
  var ThumbnailPanel = class {
767
923
  el;
768
924
  panelEl;
925
+ listEl;
926
+ headerEl;
769
927
  thumbnails = [];
770
928
  onSelect;
929
+ onToggleCallback;
771
930
  activeIndex = 0;
772
- constructor(container) {
931
+ observer;
932
+ renderedIndices = /* @__PURE__ */ new Set();
933
+ constructor(container, onToggle) {
773
934
  this.el = container;
935
+ this.onToggleCallback = onToggle;
774
936
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
937
+ this.panelEl.style.width = "0px";
938
+ this.panelEl.style.minWidth = "0px";
939
+ this.panelEl.style.opacity = "0";
940
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
941
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
942
+ const closeBtn = createElement("button", {
943
+ className: "fp-thumbnail-close-btn",
944
+ title: "Close Thumbnails",
945
+ type: "button"
946
+ });
947
+ closeBtn.innerHTML = ICON_CLOSE;
948
+ closeBtn.addEventListener("click", (e) => {
949
+ e.stopPropagation();
950
+ this.hide();
951
+ });
952
+ this.headerEl.appendChild(title);
953
+ this.headerEl.appendChild(closeBtn);
954
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
955
+ this.panelEl.appendChild(this.headerEl);
956
+ this.panelEl.appendChild(this.listEl);
775
957
  this.el.appendChild(this.panelEl);
776
958
  }
959
+ isOpen() {
960
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
961
+ }
777
962
  update(thumbnails, onSelect) {
778
963
  this.thumbnails = thumbnails;
779
964
  this.onSelect = onSelect;
965
+ this.renderedIndices.clear();
966
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
967
+ if (titleEl) {
968
+ titleEl.textContent = `Pages (${thumbnails.length})`;
969
+ }
780
970
  this.render();
971
+ if (this.isOpen()) {
972
+ this.renderAllVisible();
973
+ }
781
974
  }
782
975
  setActive(index) {
783
976
  this.activeIndex = index;
784
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
977
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
785
978
  items.forEach((item, i) => {
786
979
  if (i === index) {
787
980
  item.classList.add("active");
981
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
788
982
  } else {
789
983
  item.classList.remove("active");
790
984
  }
@@ -792,37 +986,102 @@ var ThumbnailPanel = class {
792
986
  }
793
987
  show() {
794
988
  this.panelEl.classList.remove("hidden");
989
+ this.panelEl.style.width = "200px";
990
+ this.panelEl.style.minWidth = "200px";
991
+ this.panelEl.style.opacity = "1";
992
+ this.panelEl.style.display = "flex";
993
+ this.onToggleCallback?.(true);
994
+ this.renderAllVisible();
795
995
  }
796
996
  hide() {
797
997
  this.panelEl.classList.add("hidden");
998
+ this.panelEl.style.width = "0px";
999
+ this.panelEl.style.minWidth = "0px";
1000
+ this.panelEl.style.opacity = "0";
1001
+ this.onToggleCallback?.(false);
798
1002
  }
799
1003
  toggle() {
800
- this.panelEl.classList.toggle("hidden");
1004
+ if (this.isOpen()) {
1005
+ this.hide();
1006
+ } else {
1007
+ this.show();
1008
+ }
801
1009
  }
802
1010
  destroy() {
1011
+ if (this.observer) {
1012
+ this.observer.disconnect();
1013
+ this.observer = void 0;
1014
+ }
1015
+ this.renderedIndices.clear();
803
1016
  this.el.innerHTML = "";
804
1017
  }
805
- async render() {
806
- this.panelEl.innerHTML = "";
1018
+ renderAllVisible() {
1019
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
1020
+ items.forEach((item) => {
1021
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
1022
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
1023
+ this.renderThumbnailItem(idx, item);
1024
+ }
1025
+ });
1026
+ }
1027
+ renderThumbnailItem(idx, itemEl) {
1028
+ if (this.renderedIndices.has(idx)) return;
1029
+ this.renderedIndices.add(idx);
1030
+ const canvas = itemEl.querySelector("canvas");
1031
+ const thumb = this.thumbnails[idx];
1032
+ if (canvas && thumb) {
1033
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
1034
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
1035
+ });
1036
+ }
1037
+ }
1038
+ render() {
1039
+ if (this.observer) {
1040
+ this.observer.disconnect();
1041
+ }
1042
+ this.listEl.innerHTML = "";
1043
+ const useObserver = typeof IntersectionObserver !== "undefined";
1044
+ if (useObserver) {
1045
+ this.observer = new IntersectionObserver(
1046
+ (entries) => {
1047
+ for (const entry of entries) {
1048
+ if (entry.isIntersecting) {
1049
+ const target = entry.target;
1050
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
1051
+ if (idx >= 0) {
1052
+ this.renderThumbnailItem(idx, target);
1053
+ }
1054
+ }
1055
+ }
1056
+ },
1057
+ {
1058
+ root: this.listEl,
1059
+ rootMargin: "120px 0px 120px 0px"
1060
+ }
1061
+ );
1062
+ }
807
1063
  for (let i = 0; i < this.thumbnails.length; i++) {
808
1064
  const thumb = this.thumbnails[i];
809
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
810
- if (i === this.activeIndex) {
811
- itemEl.classList.add("active");
812
- }
1065
+ const itemEl = createElement("div", {
1066
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
1067
+ });
1068
+ itemEl.dataset.thumbIndex = i.toString();
813
1069
  itemEl.addEventListener("click", () => {
814
1070
  this.setActive(i);
815
1071
  this.onSelect?.(i);
816
1072
  });
817
- const canvas = createElement("canvas", { width: "150", height: "200" });
818
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
819
- itemEl.appendChild(canvas);
1073
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
1074
+ const canvas = createElement("canvas", { width: "130", height: "170" });
1075
+ canvasWrapper.appendChild(canvas);
1076
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
1077
+ itemEl.appendChild(canvasWrapper);
820
1078
  itemEl.appendChild(label);
821
- this.panelEl.appendChild(itemEl);
822
- try {
823
- await thumb.render(canvas);
824
- } catch (err) {
825
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
1079
+ this.listEl.appendChild(itemEl);
1080
+ if (this.observer) {
1081
+ this.observer.observe(itemEl);
1082
+ }
1083
+ if (i < 4) {
1084
+ this.renderThumbnailItem(i, itemEl);
826
1085
  }
827
1086
  }
828
1087
  }
@@ -843,6 +1102,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
843
1102
  currentOptions = {};
844
1103
  resizeObserver = null;
845
1104
  fullscreenHandler = null;
1105
+ titleBarEl = null;
846
1106
  /**
847
1107
  * Register a preview plugin.
848
1108
  */
@@ -879,6 +1139,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
879
1139
  }
880
1140
  this.currentBuffer = buffer.slice(0);
881
1141
  this.currentMetadata = metadata;
1142
+ this.updateTitleBar(options, metadata);
882
1143
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
883
1144
  const fileInfo = { metadata, buffer };
884
1145
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -902,15 +1163,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
902
1163
  if (event === "page-change" && payload && typeof payload.page === "number") {
903
1164
  const total = payload.total ?? payload.totalPages;
904
1165
  this.toolbar?.setPage(payload.page, total);
1166
+ this.thumbnailPanel?.setActive(payload.page - 1);
905
1167
  }
906
1168
  this.eventEmitter.emit(event, payload);
907
- }
1169
+ },
1170
+ toggleThumbnails: () => this.toggleThumbnails()
908
1171
  });
909
1172
  this.activeInstance = instance;
910
1173
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1174
+ instance.toggleThumbnails = () => this.toggleThumbnails();
911
1175
  this.hideLoading();
912
1176
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
913
- if (options.showToolbar !== false && this.toolbar) {
1177
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1178
+ if (isToolbarVisible && this.toolbar) {
1179
+ const toolbarConfig = this.extractToolbarConfig(options);
1180
+ this.toolbar.setConfig(toolbarConfig);
914
1181
  const actions = matchedPlugin.getToolbarActions(instance);
915
1182
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
916
1183
  if (!hasFullscreen && this.wrapperEl) {
@@ -960,6 +1227,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
960
1227
  }
961
1228
  });
962
1229
  }
1230
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1231
+ if (thumbAction) {
1232
+ thumbAction.execute = () => {
1233
+ this.toggleThumbnails();
1234
+ };
1235
+ }
963
1236
  this.toolbar.update(actions);
964
1237
  this.toolbar.show();
965
1238
  }
@@ -1044,7 +1317,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1044
1317
  if (targetUrl) {
1045
1318
  const newWin2 = window.open(targetUrl, "_blank");
1046
1319
  if (!newWin2) {
1047
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1320
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1048
1321
  return null;
1049
1322
  }
1050
1323
  return newWin2;
@@ -1052,7 +1325,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1052
1325
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1053
1326
  const newWin = window.open("", "_blank");
1054
1327
  if (!newWin) {
1055
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1328
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1056
1329
  return null;
1057
1330
  }
1058
1331
  newWin.document.title = title;
@@ -1129,6 +1402,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1129
1402
  this.currentBuffer = null;
1130
1403
  this.currentMetadata = null;
1131
1404
  this.currentContainer = null;
1405
+ this.titleBarEl = null;
1132
1406
  }
1133
1407
  /**
1134
1408
  * Get the currently active preview instance.
@@ -1136,7 +1410,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1136
1410
  getInstance() {
1137
1411
  return this.activeInstance;
1138
1412
  }
1413
+ // --- Toolbar Configuration & Feature Toggles ---
1414
+ /**
1415
+ * Configure toolbar features dynamically through options or methods.
1416
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1417
+ */
1418
+ setToolbarConfig(config) {
1419
+ this.toolbar?.setConfig(config);
1420
+ }
1421
+ /**
1422
+ * Get current toolbar configuration.
1423
+ */
1424
+ getToolbarConfig() {
1425
+ return this.toolbar?.getConfig() ?? {};
1426
+ }
1427
+ /**
1428
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1429
+ */
1430
+ hideToolbarAction(actionId) {
1431
+ this.toolbar?.hideAction(actionId);
1432
+ }
1433
+ /**
1434
+ * Show a specific toolbar action by ID or alias.
1435
+ */
1436
+ showToolbarAction(actionId) {
1437
+ this.toolbar?.showAction(actionId);
1438
+ }
1439
+ /**
1440
+ * Enable a specific toolbar action button.
1441
+ */
1442
+ enableToolbarAction(actionId) {
1443
+ this.toolbar?.enableAction(actionId);
1444
+ }
1445
+ /**
1446
+ * Disable a specific toolbar action button.
1447
+ */
1448
+ disableToolbarAction(actionId) {
1449
+ this.toolbar?.disableAction(actionId);
1450
+ }
1451
+ // --- Programmatic Toolbar Action Methods ---
1452
+ /**
1453
+ * Fit document to page so it fills the frame width with minimal margins.
1454
+ */
1455
+ fitToPage() {
1456
+ this.activeInstance?.fitToPage?.();
1457
+ }
1458
+ /**
1459
+ * Zoom in.
1460
+ */
1461
+ zoomIn() {
1462
+ this.activeInstance?.zoomIn?.();
1463
+ }
1464
+ /**
1465
+ * Zoom out.
1466
+ */
1467
+ zoomOut() {
1468
+ this.activeInstance?.zoomOut?.();
1469
+ }
1470
+ /**
1471
+ * Set specific zoom level.
1472
+ */
1473
+ setZoom(level) {
1474
+ this.activeInstance?.setZoom?.(level);
1475
+ }
1476
+ /**
1477
+ * Get current zoom level.
1478
+ */
1479
+ getZoom() {
1480
+ return this.activeInstance?.getZoom?.() ?? 1;
1481
+ }
1482
+ /**
1483
+ * Rotate 90 degrees clockwise.
1484
+ */
1485
+ rotateCW() {
1486
+ this.activeInstance?.rotateCW?.();
1487
+ }
1488
+ /**
1489
+ * Rotate 90 degrees counter-clockwise.
1490
+ */
1491
+ rotateCCW() {
1492
+ this.activeInstance?.rotateCCW?.();
1493
+ }
1494
+ /**
1495
+ * Navigate to a specific page number.
1496
+ */
1497
+ goToPage(page) {
1498
+ this.activeInstance?.goToPage?.(page);
1499
+ this.toolbar?.setPage(page);
1500
+ }
1501
+ /**
1502
+ * Navigate to next page.
1503
+ */
1504
+ nextPage() {
1505
+ const cur = this.getCurrentPage();
1506
+ const max = this.getPageCount();
1507
+ if (cur < max) {
1508
+ this.goToPage(cur + 1);
1509
+ }
1510
+ }
1511
+ /**
1512
+ * Navigate to previous page.
1513
+ */
1514
+ prevPage() {
1515
+ const cur = this.getCurrentPage();
1516
+ if (cur > 1) {
1517
+ this.goToPage(cur - 1);
1518
+ }
1519
+ }
1520
+ /**
1521
+ * Get total page count.
1522
+ */
1523
+ getPageCount() {
1524
+ return this.activeInstance?.getPageCount?.() ?? 1;
1525
+ }
1526
+ /**
1527
+ * Get current page number.
1528
+ */
1529
+ getCurrentPage() {
1530
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1531
+ }
1532
+ /**
1533
+ * Download the current document.
1534
+ */
1535
+ download() {
1536
+ this.activeInstance?.download?.();
1537
+ }
1538
+ /**
1539
+ * Print the current document.
1540
+ */
1541
+ print() {
1542
+ this.activeInstance?.print?.();
1543
+ }
1544
+ /**
1545
+ * Toggle fullscreen mode.
1546
+ */
1547
+ toggleFullscreen() {
1548
+ try {
1549
+ if (!document.fullscreenElement) {
1550
+ this.wrapperEl?.requestFullscreen?.();
1551
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1552
+ } else {
1553
+ document.exitFullscreen?.();
1554
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1555
+ }
1556
+ } catch {
1557
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1558
+ }
1559
+ setTimeout(() => {
1560
+ this.activeInstance?.fitToPage?.();
1561
+ }, 120);
1562
+ }
1563
+ /**
1564
+ * Toggle thumbnails sidebar panel.
1565
+ */
1566
+ toggleThumbnails() {
1567
+ if (!this.thumbnailPanel) return;
1568
+ this.thumbnailPanel.toggle();
1569
+ const isOpen = this.thumbnailPanel.isOpen();
1570
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1571
+ setTimeout(() => {
1572
+ this.activeInstance?.fitToPage?.();
1573
+ }, 260);
1574
+ }
1575
+ /**
1576
+ * Set whether to display the file name / title bar above the toolbar.
1577
+ */
1578
+ setShowFileName(show) {
1579
+ if (this.currentOptions) {
1580
+ this.currentOptions.showFileName = show;
1581
+ }
1582
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1583
+ }
1584
+ /**
1585
+ * Set or override the displayed file name in the preview panel.
1586
+ */
1587
+ setFileName(name) {
1588
+ if (this.currentOptions) {
1589
+ this.currentOptions.fileName = name;
1590
+ }
1591
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1592
+ }
1139
1593
  // --- Private methods ---
1594
+ updateTitleBar(options, metadata) {
1595
+ if (!this.titleBarEl) return;
1596
+ const show = options?.showFileName !== false;
1597
+ if (!show) {
1598
+ this.titleBarEl.style.display = "none";
1599
+ return;
1600
+ }
1601
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1602
+ if (!name) {
1603
+ this.titleBarEl.style.display = "none";
1604
+ return;
1605
+ }
1606
+ this.titleBarEl.style.display = "flex";
1607
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1608
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1609
+ this.titleBarEl.innerHTML = `
1610
+ <div class="fp-titlebar-info">
1611
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1612
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1613
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1614
+ </div>
1615
+ `;
1616
+ }
1617
+ extractToolbarConfig(options) {
1618
+ if (!options) return {};
1619
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1620
+ return {
1621
+ ...options,
1622
+ ...nested
1623
+ };
1624
+ }
1140
1625
  abort() {
1141
1626
  if (this.abortController) {
1142
1627
  this.abortController.abort();
@@ -1179,17 +1664,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1179
1664
  bodyEl.className = "fp-body";
1180
1665
  bodyEl.appendChild(thumbnailEl);
1181
1666
  bodyEl.appendChild(this.contentEl);
1667
+ const titleBarEl = document.createElement("div");
1668
+ titleBarEl.className = "fp-titlebar";
1669
+ this.titleBarEl = titleBarEl;
1670
+ this.updateTitleBar(options, this.currentMetadata);
1182
1671
  if (toolbarPos === "top") {
1672
+ this.wrapperEl.appendChild(titleBarEl);
1183
1673
  this.wrapperEl.appendChild(toolbarEl);
1184
1674
  this.wrapperEl.appendChild(bodyEl);
1185
1675
  } else {
1676
+ this.wrapperEl.appendChild(titleBarEl);
1186
1677
  this.wrapperEl.appendChild(bodyEl);
1187
1678
  this.wrapperEl.appendChild(toolbarEl);
1188
1679
  }
1189
1680
  container.innerHTML = "";
1190
1681
  container.appendChild(this.wrapperEl);
1191
- this.toolbar = new ToolbarController(toolbarEl);
1192
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1682
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1683
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1684
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1685
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1686
+ setTimeout(() => {
1687
+ this.activeInstance?.fitToPage?.();
1688
+ }, 260);
1689
+ });
1193
1690
  this.setupKeyboardShortcuts();
1194
1691
  this.setupDragAndDrop(container, options);
1195
1692
  this.setupResizeAndFullscreenListeners();
@@ -1579,6 +2076,14 @@ var PdfPlugin = class {
1579
2076
  group: "zoom",
1580
2077
  execute: () => instance.fitToPage?.()
1581
2078
  },
2079
+ {
2080
+ id: "fit-width",
2081
+ icon: "fit-width",
2082
+ label: "Fit to Width",
2083
+ type: "button",
2084
+ group: "zoom",
2085
+ execute: () => instance.fitToWidth?.()
2086
+ },
1582
2087
  {
1583
2088
  id: "rotate-cw",
1584
2089
  icon: "rotate-cw",
@@ -1587,6 +2092,14 @@ var PdfPlugin = class {
1587
2092
  group: "view",
1588
2093
  execute: () => instance.rotateCW?.()
1589
2094
  },
2095
+ {
2096
+ id: "rotate-ccw",
2097
+ icon: "rotate-ccw",
2098
+ label: "Rotate Counter-Clockwise",
2099
+ type: "button",
2100
+ group: "view",
2101
+ execute: () => instance.rotateCCW?.()
2102
+ },
1590
2103
  {
1591
2104
  id: "download",
1592
2105
  icon: "download",
@@ -1618,12 +2131,13 @@ var PdfPlugin = class {
1618
2131
  container.className = "fp-pdf-container";
1619
2132
  container.style.width = "100%";
1620
2133
  container.style.height = "100%";
1621
- container.style.overflow = "auto";
2134
+ container.style.overflowX = "hidden";
2135
+ container.style.overflowY = "auto";
1622
2136
  container.style.display = "flex";
1623
2137
  container.style.flexDirection = "column";
1624
2138
  container.style.alignItems = "center";
1625
2139
  container.style.justifyContent = "flex-start";
1626
- container.style.padding = "20px 16px";
2140
+ container.style.padding = "16px 8px";
1627
2141
  container.style.backgroundColor = "#0f172a";
1628
2142
  container.style.boxSizing = "border-box";
1629
2143
  container.style.position = "relative";
@@ -1634,30 +2148,12 @@ var PdfPlugin = class {
1634
2148
  pageCard.style.borderRadius = "4px";
1635
2149
  pageCard.style.overflow = "hidden";
1636
2150
  pageCard.style.lineHeight = "0";
1637
- pageCard.style.transition = "transform 0.15s ease";
1638
2151
  pageCard.style.position = "relative";
1639
2152
  pageCard.style.flexShrink = "0";
2153
+ pageCard.style.transition = "box-shadow 0.2s ease";
1640
2154
  let canvas = document.createElement("canvas");
1641
2155
  pageCard.appendChild(canvas);
1642
2156
  container.appendChild(pageCard);
1643
- const indicator = document.createElement("div");
1644
- indicator.className = "fp-pdf-page-indicator";
1645
- indicator.style.position = "sticky";
1646
- indicator.style.bottom = "16px";
1647
- indicator.style.marginTop = "16px";
1648
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1649
- indicator.style.backdropFilter = "blur(8px)";
1650
- indicator.style.color = "#f8fafc";
1651
- indicator.style.fontSize = "12px";
1652
- indicator.style.fontWeight = "600";
1653
- indicator.style.padding = "5px 14px";
1654
- indicator.style.borderRadius = "20px";
1655
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1656
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1657
- indicator.style.zIndex = "10";
1658
- indicator.style.userSelect = "none";
1659
- indicator.style.pointerEvents = "none";
1660
- container.appendChild(indicator);
1661
2157
  ctx.container.appendChild(container);
1662
2158
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1663
2159
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1673,8 +2169,9 @@ var PdfPlugin = class {
1673
2169
  let currentPage = 1;
1674
2170
  let zoomScale = 1;
1675
2171
  let rotation = 0;
1676
- let fitMode = "page";
2172
+ let fitMode = ctx?.options?.fitMode || "width";
1677
2173
  let currentRenderTask = null;
2174
+ let textLayerDiv = null;
1678
2175
  const renderPage = async (pageNum) => {
1679
2176
  if (currentRenderTask) {
1680
2177
  try {
@@ -1686,30 +2183,37 @@ var PdfPlugin = class {
1686
2183
  const newCanvas = document.createElement("canvas");
1687
2184
  pageCard.replaceChild(newCanvas, canvas);
1688
2185
  canvas = newCanvas;
2186
+ if (textLayerDiv) {
2187
+ textLayerDiv.remove();
2188
+ textLayerDiv = null;
2189
+ }
1689
2190
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1690
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1691
2191
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1692
2192
  const page = await pdfDoc.getPage(currentPage);
1693
2193
  const containerWidth = container.clientWidth || 900;
1694
2194
  const containerHeight = container.clientHeight || 700;
1695
2195
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1696
- const availWidth = Math.max(320, containerWidth - 48);
1697
- const availHeight = Math.max(550, containerHeight - 88);
2196
+ const availWidth = Math.max(280, containerWidth - 36);
2197
+ const availHeight = Math.max(280, containerHeight - 32);
1698
2198
  const scaleW = availWidth / unscaledVp.width;
1699
2199
  const scaleH = availHeight / unscaledVp.height;
1700
2200
  let fitScale;
1701
2201
  if (fitMode === "page") {
1702
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2202
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1703
2203
  } else {
1704
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2204
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1705
2205
  }
1706
2206
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1707
2207
  const pixelRatio = window.devicePixelRatio || 1;
1708
2208
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2209
+ const displayWidth = Math.floor(viewport.width);
2210
+ const displayHeight = Math.floor(viewport.height);
1709
2211
  canvas.width = Math.floor(viewport.width * pixelRatio);
1710
2212
  canvas.height = Math.floor(viewport.height * pixelRatio);
1711
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1712
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2213
+ canvas.style.width = `${displayWidth}px`;
2214
+ canvas.style.height = `${displayHeight}px`;
2215
+ pageCard.style.width = `${displayWidth}px`;
2216
+ pageCard.style.height = `${displayHeight}px`;
1713
2217
  const canvasCtx = canvas.getContext("2d");
1714
2218
  if (!canvasCtx) return;
1715
2219
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1723,9 +2227,31 @@ var PdfPlugin = class {
1723
2227
  if (err?.name !== "RenderingCancelledException") {
1724
2228
  console.warn("[PdfPlugin] Page render warning:", err);
1725
2229
  }
2230
+ return;
1726
2231
  } finally {
1727
2232
  currentRenderTask = null;
1728
2233
  }
2234
+ try {
2235
+ textLayerDiv = document.createElement("div");
2236
+ textLayerDiv.className = "textLayer";
2237
+ textLayerDiv.style.width = `${displayWidth}px`;
2238
+ textLayerDiv.style.height = `${displayHeight}px`;
2239
+ textLayerDiv.style.position = "absolute";
2240
+ textLayerDiv.style.top = "0";
2241
+ textLayerDiv.style.left = "0";
2242
+ textLayerDiv.style.lineHeight = "1";
2243
+ pageCard.appendChild(textLayerDiv);
2244
+ if (pdfjsLib.TextLayer) {
2245
+ const textLayer = new pdfjsLib.TextLayer({
2246
+ textContentSource: page.streamTextContent(),
2247
+ container: textLayerDiv,
2248
+ viewport
2249
+ });
2250
+ await textLayer.render();
2251
+ }
2252
+ } catch (err) {
2253
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2254
+ }
1729
2255
  };
1730
2256
  renderPage(1);
1731
2257
  let resizeTimer = null;
@@ -1735,11 +2261,48 @@ var PdfPlugin = class {
1735
2261
  if (Math.abs(zoomScale - 1) < 0.05) {
1736
2262
  renderPage(currentPage);
1737
2263
  }
1738
- }, 150);
2264
+ }, 120);
1739
2265
  });
1740
2266
  resizeObserver.observe(container);
2267
+ const onWheel = (e) => {
2268
+ if (e.ctrlKey || e.metaKey) {
2269
+ e.preventDefault();
2270
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2271
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2272
+ renderPage(currentPage);
2273
+ }
2274
+ };
2275
+ container.addEventListener("wheel", onWheel, { passive: false });
2276
+ const onKeyDown = (e) => {
2277
+ const tag = e.target?.tagName?.toLowerCase();
2278
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2279
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2280
+ if (currentPage < totalPages) {
2281
+ e.preventDefault();
2282
+ container.scrollTop = 0;
2283
+ renderPage(currentPage + 1);
2284
+ }
2285
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2286
+ if (currentPage > 1) {
2287
+ e.preventDefault();
2288
+ container.scrollTop = 0;
2289
+ renderPage(currentPage - 1);
2290
+ }
2291
+ } else if (e.key === "Home") {
2292
+ e.preventDefault();
2293
+ container.scrollTop = 0;
2294
+ renderPage(1);
2295
+ } else if (e.key === "End") {
2296
+ e.preventDefault();
2297
+ container.scrollTop = 0;
2298
+ renderPage(totalPages);
2299
+ }
2300
+ };
2301
+ window.addEventListener("keydown", onKeyDown);
1741
2302
  const cleanup = () => {
1742
2303
  resizeObserver.disconnect();
2304
+ window.removeEventListener("keydown", onKeyDown);
2305
+ container.removeEventListener("wheel", onWheel);
1743
2306
  if (resizeTimer) clearTimeout(resizeTimer);
1744
2307
  if (currentRenderTask) {
1745
2308
  try {
@@ -1758,22 +2321,26 @@ var PdfPlugin = class {
1758
2321
  const instance = {
1759
2322
  destroy: cleanup,
1760
2323
  zoomIn: () => {
1761
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2324
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1762
2325
  renderPage(currentPage);
1763
2326
  },
1764
2327
  zoomOut: () => {
1765
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2328
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1766
2329
  renderPage(currentPage);
1767
2330
  },
1768
2331
  getZoom: () => zoomScale,
1769
2332
  setZoom: (level) => {
1770
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2333
+ zoomScale = Math.max(0.25, Math.min(4, level));
1771
2334
  renderPage(currentPage);
1772
2335
  },
1773
2336
  fitToPage: () => {
1774
- fitMode = fitMode === "page" ? "width" : "page";
2337
+ fitMode = "page";
2338
+ zoomScale = 1;
2339
+ renderPage(currentPage);
2340
+ },
2341
+ fitToWidth: () => {
2342
+ fitMode = "width";
1775
2343
  zoomScale = 1;
1776
- rotation = 0;
1777
2344
  renderPage(currentPage);
1778
2345
  },
1779
2346
  rotateCW: () => {
@@ -1791,6 +2358,9 @@ var PdfPlugin = class {
1791
2358
  container.scrollTop = 0;
1792
2359
  renderPage(page);
1793
2360
  },
2361
+ toggleThumbnails: () => {
2362
+ ctx?.toggleThumbnails?.();
2363
+ },
1794
2364
  download: () => {
1795
2365
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1796
2366
  const url = URL.createObjectURL(blob);
@@ -1824,7 +2394,7 @@ var PdfPlugin = class {
1824
2394
  },
1825
2395
  getThumbnails: async () => {
1826
2396
  const thumbnails = [];
1827
- const count = Math.min(totalPages, 50);
2397
+ const count = totalPages;
1828
2398
  for (let i = 1; i <= count; i++) {
1829
2399
  thumbnails.push({
1830
2400
  index: i,
@@ -1832,9 +2402,10 @@ var PdfPlugin = class {
1832
2402
  render: async (thumbCanvas) => {
1833
2403
  try {
1834
2404
  const p = await pdfDoc.getPage(i);
1835
- const baseVp = p.getViewport({ scale: 1 });
1836
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1837
- const thumbVp = p.getViewport({ scale: thumbScale });
2405
+ const baseVp = p.getViewport({ scale: 1, rotation });
2406
+ const targetW = thumbCanvas.width || 130;
2407
+ const thumbScale = targetW / baseVp.width;
2408
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1838
2409
  thumbCanvas.height = Math.floor(thumbVp.height);
1839
2410
  const tCtx = thumbCanvas.getContext("2d");
1840
2411
  if (tCtx) {
@@ -2178,28 +2749,26 @@ var DocxPlugin = class {
2178
2749
  getToolbarActions(instance) {
2179
2750
  const totalPages = instance.getPageCount?.() ?? 1;
2180
2751
  const actions = [];
2181
- if (totalPages > 1) {
2182
- actions.push({
2183
- id: "page-nav",
2184
- icon: "",
2185
- label: "Page Navigation",
2186
- type: "page-nav",
2187
- group: "navigation",
2188
- value: instance.getCurrentPage?.() ?? 1,
2189
- max: totalPages,
2190
- execute: (action, page) => {
2191
- const cur = instance.getCurrentPage?.() ?? 1;
2192
- const max = instance.getPageCount?.() ?? 1;
2193
- if (action === "prev") {
2194
- if (cur > 1) instance.goToPage?.(cur - 1);
2195
- } else if (action === "next") {
2196
- if (cur < max) instance.goToPage?.(cur + 1);
2197
- } else if (typeof page === "number") {
2198
- instance.goToPage?.(page);
2199
- }
2752
+ actions.push({
2753
+ id: "page-nav",
2754
+ icon: "",
2755
+ label: "Page Navigation",
2756
+ type: "page-nav",
2757
+ group: "navigation",
2758
+ value: instance.getCurrentPage?.() ?? 1,
2759
+ max: totalPages,
2760
+ execute: (action, page) => {
2761
+ const cur = instance.getCurrentPage?.() ?? 1;
2762
+ const max = instance.getPageCount?.() ?? 1;
2763
+ if (action === "prev") {
2764
+ if (cur > 1) instance.goToPage?.(cur - 1);
2765
+ } else if (action === "next") {
2766
+ if (cur < max) instance.goToPage?.(cur + 1);
2767
+ } else if (typeof page === "number") {
2768
+ instance.goToPage?.(page);
2200
2769
  }
2201
- });
2202
- }
2770
+ }
2771
+ });
2203
2772
  actions.push(
2204
2773
  {
2205
2774
  id: "zoom-out",
@@ -2265,11 +2834,17 @@ var DocxPlugin = class {
2265
2834
  wrapper.className = "fp-docx-wrapper";
2266
2835
  wrapper.style.transformOrigin = "top center";
2267
2836
  wrapper.style.transition = "transform 0.2s ease";
2268
- wrapper.style.padding = "24px 16px";
2269
- wrapper.style.maxWidth = "950px";
2837
+ wrapper.style.padding = "0";
2838
+ wrapper.style.maxWidth = "none";
2839
+ wrapper.style.width = "816px";
2270
2840
  wrapper.style.margin = "0 auto";
2841
+ wrapper.style.display = "flex";
2842
+ wrapper.style.flexDirection = "column";
2843
+ wrapper.style.alignItems = "center";
2271
2844
  wrapper.style.boxSizing = "border-box";
2272
- ctx.container.style.overflow = "auto";
2845
+ ctx.container.style.overflowX = "hidden";
2846
+ ctx.container.style.overflowY = "auto";
2847
+ ctx.container.style.padding = "16px 8px";
2273
2848
  ctx.container.style.backgroundColor = "#f1f5f9";
2274
2849
  ctx.container.appendChild(wrapper);
2275
2850
  const styleOverride = document.createElement("style");
@@ -2443,39 +3018,13 @@ var DocxPlugin = class {
2443
3018
  const pageElements = sections.length > 0 ? sections : cards;
2444
3019
  const totalPages = Math.max(1, pageElements.length);
2445
3020
  let currentPage = 1;
2446
- let indicator = null;
2447
- if (totalPages > 1) {
2448
- indicator = document.createElement("div");
2449
- indicator.className = "fp-docx-page-indicator";
2450
- indicator.style.position = "sticky";
2451
- indicator.style.bottom = "16px";
2452
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2453
- indicator.style.backdropFilter = "blur(8px)";
2454
- indicator.style.color = "#f8fafc";
2455
- indicator.style.fontSize = "12px";
2456
- indicator.style.fontWeight = "600";
2457
- indicator.style.padding = "5px 14px";
2458
- indicator.style.borderRadius = "20px";
2459
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2460
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2461
- indicator.style.zIndex = "10";
2462
- indicator.style.userSelect = "none";
2463
- indicator.style.pointerEvents = "none";
2464
- indicator.style.textAlign = "center";
2465
- indicator.style.width = "fit-content";
2466
- indicator.style.margin = "16px auto 0";
2467
- ctx.container.appendChild(indicator);
2468
- }
2469
3021
  const showPage = (pageNum) => {
2470
3022
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2471
3023
  if (pageElements.length > 1) {
2472
3024
  pageElements.forEach((sec, idx) => {
2473
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
3025
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2474
3026
  });
2475
3027
  }
2476
- if (indicator) {
2477
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2478
- }
2479
3028
  ctx.container.scrollTop = 0;
2480
3029
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2481
3030
  };
@@ -2484,31 +3033,46 @@ var DocxPlugin = class {
2484
3033
  }
2485
3034
  scale = 1;
2486
3035
  let rotation = 0;
2487
- let fitMode = "width";
2488
- const calculateFitScale = (mode = fitMode) => {
2489
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
3036
+ let fitMode = "page";
3037
+ let isUserZoomed = false;
3038
+ const calculateFitScale = (_mode = fitMode) => {
3039
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2490
3040
  const elW = activeEl.offsetWidth || 816;
2491
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2492
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2493
- const availH = Math.max(280, ctx.container.clientHeight - 80);
3041
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2494
3042
  const sW = availW / elW;
2495
- const sH = availH / singlePageH;
2496
- if (mode === "page") {
2497
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2498
- }
2499
- return Math.max(0.65, Math.min(1.05, sW));
3043
+ return Math.max(0.35, Math.min(3, sW));
2500
3044
  };
2501
3045
  const applyTransform = () => {
2502
3046
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2503
3047
  wrapper.style.transformOrigin = "top center";
3048
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3049
+ const elH = activeEl.offsetHeight || 1056;
3050
+ if (scale > 1) {
3051
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3052
+ } else {
3053
+ wrapper.style.marginBottom = "32px";
3054
+ }
2504
3055
  };
3056
+ let resizeObserver = null;
3057
+ if (typeof ResizeObserver !== "undefined") {
3058
+ resizeObserver = new ResizeObserver(() => {
3059
+ if (!isUserZoomed) {
3060
+ const newFit = calculateFitScale("page");
3061
+ if (Math.abs(scale - newFit) > 0.015) {
3062
+ scale = newFit;
3063
+ applyTransform();
3064
+ }
3065
+ }
3066
+ });
3067
+ resizeObserver.observe(ctx.container);
3068
+ }
2505
3069
  setTimeout(() => {
2506
- fitMode = "width";
2507
- scale = calculateFitScale("width");
3070
+ scale = calculateFitScale("page");
2508
3071
  applyTransform();
2509
- }, 60);
3072
+ }, 40);
2510
3073
  const cleanup = () => {
2511
- indicator?.remove();
3074
+ resizeObserver?.disconnect();
3075
+ resizeObserver = null;
2512
3076
  for (const url of createdBlobUrls) {
2513
3077
  URL.revokeObjectURL(url);
2514
3078
  }
@@ -2525,21 +3089,24 @@ var DocxPlugin = class {
2525
3089
  showPage(page);
2526
3090
  },
2527
3091
  zoomIn: () => {
2528
- scale += 0.15;
3092
+ isUserZoomed = true;
3093
+ scale = Math.min(3.5, scale + 0.15);
2529
3094
  applyTransform();
2530
3095
  },
2531
3096
  zoomOut: () => {
3097
+ isUserZoomed = true;
2532
3098
  scale = Math.max(0.2, scale - 0.15);
2533
3099
  applyTransform();
2534
3100
  },
2535
3101
  getZoom: () => scale,
2536
3102
  setZoom: (level) => {
3103
+ isUserZoomed = true;
2537
3104
  scale = level;
2538
3105
  applyTransform();
2539
3106
  },
2540
3107
  fitToPage: () => {
2541
- fitMode = fitMode === "width" ? "page" : "width";
2542
- scale = calculateFitScale(fitMode);
3108
+ isUserZoomed = false;
3109
+ scale = calculateFitScale("page");
2543
3110
  rotation = 0;
2544
3111
  applyTransform();
2545
3112
  },
@@ -2929,7 +3496,7 @@ var ExcelPlugin = class {
2929
3496
  "application/vnd.ms-excel.template.macroEnabled.12",
2930
3497
  "application/vnd.oasis.opendocument.spreadsheet"
2931
3498
  ];
2932
- weight = 80;
3499
+ weight = 85;
2933
3500
  supports(file) {
2934
3501
  const ext = file.metadata.extension?.toLowerCase();
2935
3502
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2941,6 +3508,14 @@ var ExcelPlugin = class {
2941
3508
  getToolbarActions(instance) {
2942
3509
  const totalSheets = instance.getPageCount?.() ?? 1;
2943
3510
  const actions = [];
3511
+ actions.push({
3512
+ id: "thumbnails",
3513
+ icon: "thumbnails",
3514
+ label: "Sheet Thumbnails",
3515
+ type: "button",
3516
+ group: "navigation",
3517
+ execute: () => instance.toggleThumbnails?.()
3518
+ });
2944
3519
  if (totalSheets > 1) {
2945
3520
  actions.push({
2946
3521
  id: "page-nav",
@@ -2984,26 +3559,6 @@ var ExcelPlugin = class {
2984
3559
  instance.zoomIn?.();
2985
3560
  }
2986
3561
  },
2987
- {
2988
- id: "fit-page",
2989
- icon: "fit-page",
2990
- label: "Fit to View",
2991
- type: "button",
2992
- group: "zoom",
2993
- execute: () => {
2994
- instance.fitToPage?.();
2995
- }
2996
- },
2997
- {
2998
- id: "rotate-cw",
2999
- icon: "rotate-cw",
3000
- label: "Rotate",
3001
- type: "button",
3002
- group: "view",
3003
- execute: () => {
3004
- instance.rotateCW?.();
3005
- }
3006
- },
3007
3562
  {
3008
3563
  id: "download",
3009
3564
  icon: "download",
@@ -3023,6 +3578,16 @@ var ExcelPlugin = class {
3023
3578
  execute: () => {
3024
3579
  instance.print?.();
3025
3580
  }
3581
+ },
3582
+ {
3583
+ id: "open-window",
3584
+ icon: "open-window",
3585
+ label: "Open in Separate Full Window",
3586
+ type: "button",
3587
+ group: "actions",
3588
+ execute: () => {
3589
+ instance.openInSeparateWindow?.();
3590
+ }
3026
3591
  }
3027
3592
  );
3028
3593
  return actions;
@@ -3054,12 +3619,11 @@ var ExcelPlugin = class {
3054
3619
  container.appendChild(tabsArea);
3055
3620
  ctx.container.appendChild(container);
3056
3621
  let scale = 1;
3057
- let rotation = 0;
3058
3622
  let currentSheetIndex = 1;
3059
3623
  let sheetNames = [];
3060
3624
  let wb = null;
3061
3625
  const applyTransform = () => {
3062
- contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3626
+ contentArea.style.transform = `scale(${scale})`;
3063
3627
  contentArea.style.transformOrigin = "top left";
3064
3628
  };
3065
3629
  const calculateFitScale = () => {
@@ -3067,7 +3631,7 @@ var ExcelPlugin = class {
3067
3631
  if (!table) return 1;
3068
3632
  const availW = Math.max(280, container.clientWidth - 48);
3069
3633
  const tW = table.offsetWidth || 800;
3070
- return Math.max(0.65, Math.min(1, availW / tW));
3634
+ return Math.max(0.4, Math.min(1, availW / tW));
3071
3635
  };
3072
3636
  try {
3073
3637
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
@@ -3122,7 +3686,16 @@ var ExcelPlugin = class {
3122
3686
  }
3123
3687
  });
3124
3688
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3689
+ requestAnimationFrame(() => {
3690
+ scale = calculateFitScale();
3691
+ applyTransform();
3692
+ });
3125
3693
  };
3694
+ const ro = new ResizeObserver(() => {
3695
+ scale = calculateFitScale();
3696
+ applyTransform();
3697
+ });
3698
+ ro.observe(container);
3126
3699
  if (sheetNames.length > 0) {
3127
3700
  sheetNames.forEach((name, idx) => {
3128
3701
  const btn = document.createElement("button");
@@ -3149,6 +3722,7 @@ var ExcelPlugin = class {
3149
3722
  `;
3150
3723
  }
3151
3724
  const cleanup = () => {
3725
+ ro.disconnect();
3152
3726
  container.remove();
3153
3727
  ctx.container.innerHTML = "";
3154
3728
  };
@@ -3170,16 +3744,11 @@ var ExcelPlugin = class {
3170
3744
  },
3171
3745
  fitToPage: () => {
3172
3746
  scale = calculateFitScale();
3173
- rotation = 0;
3174
3747
  applyTransform();
3175
3748
  },
3176
3749
  rotateCW: () => {
3177
- rotation = (rotation + 90) % 360;
3178
- applyTransform();
3179
3750
  },
3180
3751
  rotateCCW: () => {
3181
- rotation = (rotation - 90 + 360) % 360;
3182
- applyTransform();
3183
3752
  },
3184
3753
  goToPage: (page) => {
3185
3754
  if (page > 0 && page <= sheetNames.length) {
@@ -3188,6 +3757,63 @@ var ExcelPlugin = class {
3188
3757
  },
3189
3758
  getPageCount: () => sheetNames.length,
3190
3759
  getCurrentPage: () => currentSheetIndex,
3760
+ getThumbnails: () => {
3761
+ return sheetNames.map((name, idx) => ({
3762
+ index: idx,
3763
+ label: name,
3764
+ render: async (canvas) => {
3765
+ canvas.width = 140;
3766
+ canvas.height = 100;
3767
+ const c = canvas.getContext("2d");
3768
+ if (!c) return;
3769
+ c.fillStyle = "#ffffff";
3770
+ c.fillRect(0, 0, canvas.width, canvas.height);
3771
+ c.fillStyle = "#107c41";
3772
+ c.fillRect(0, 0, canvas.width, 16);
3773
+ c.fillStyle = "#ffffff";
3774
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3775
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3776
+ const startY = 17;
3777
+ const rowH = 13;
3778
+ const colW = 30;
3779
+ const colHeaders = ["A", "B", "C", "D"];
3780
+ c.fillStyle = "#f1f5f9";
3781
+ c.fillRect(0, startY, canvas.width, rowH);
3782
+ c.strokeStyle = "#cbd5e1";
3783
+ c.lineWidth = 0.8;
3784
+ c.strokeRect(0, startY, canvas.width, rowH);
3785
+ c.fillStyle = "#64748b";
3786
+ c.font = "bold 7px sans-serif";
3787
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3788
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3789
+ }
3790
+ const ws = wb?.Sheets[name];
3791
+ for (let ri = 1; ri <= 5; ri++) {
3792
+ const y = startY + ri * rowH;
3793
+ if (y > canvas.height - 2) break;
3794
+ c.fillStyle = "#f8fafc";
3795
+ c.fillRect(0, y, 12, rowH);
3796
+ c.fillStyle = "#94a3b8";
3797
+ c.font = "6px sans-serif";
3798
+ c.fillText(String(ri), 3, y + 9);
3799
+ c.strokeStyle = "#e2e8f0";
3800
+ c.strokeRect(0, y, canvas.width, rowH);
3801
+ c.fillStyle = "#334155";
3802
+ c.font = "6px sans-serif";
3803
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3804
+ const cellRef = `${colHeaders[ci]}${ri}`;
3805
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3806
+ if (cellVal !== void 0) {
3807
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3808
+ }
3809
+ }
3810
+ }
3811
+ c.strokeStyle = "#cbd5e1";
3812
+ c.lineWidth = 1;
3813
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3814
+ }
3815
+ }));
3816
+ },
3191
3817
  download: () => {
3192
3818
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3193
3819
  const url = URL.createObjectURL(blob);
@@ -3220,7 +3846,16 @@ var CsvPlugin = class {
3220
3846
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3221
3847
  }
3222
3848
  getToolbarActions(instance) {
3223
- return [
3849
+ const actions = [];
3850
+ actions.push({
3851
+ id: "thumbnails",
3852
+ icon: "thumbnails",
3853
+ label: "Sheet Thumbnails",
3854
+ type: "button",
3855
+ group: "navigation",
3856
+ execute: () => instance.toggleThumbnails?.()
3857
+ });
3858
+ actions.push(
3224
3859
  {
3225
3860
  id: "zoom-out",
3226
3861
  icon: "zoom-out",
@@ -3260,8 +3895,19 @@ var CsvPlugin = class {
3260
3895
  execute: () => {
3261
3896
  instance.print?.();
3262
3897
  }
3898
+ },
3899
+ {
3900
+ id: "open-window",
3901
+ icon: "open-window",
3902
+ label: "Open in Separate Full Window",
3903
+ type: "button",
3904
+ group: "actions",
3905
+ execute: () => {
3906
+ instance.openInSeparateWindow?.();
3907
+ }
3263
3908
  }
3264
- ];
3909
+ );
3910
+ return actions;
3265
3911
  }
3266
3912
  async render(ctx) {
3267
3913
  const decoder = new TextDecoder("utf-8");
@@ -3272,57 +3918,156 @@ var CsvPlugin = class {
3272
3918
  container.style.overflow = "auto";
3273
3919
  container.style.padding = "16px";
3274
3920
  container.style.boxSizing = "border-box";
3275
- container.style.transformOrigin = "top left";
3921
+ const tableWrapper = document.createElement("div");
3922
+ tableWrapper.style.transformOrigin = "top left";
3923
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3924
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3925
+ const headerLine = allLines[0] || "";
3926
+ const headerCells = headerLine.split(delimiter);
3927
+ const dataLines = allLines.slice(1);
3276
3928
  const table = document.createElement("table");
3277
3929
  table.style.borderCollapse = "collapse";
3278
- table.style.width = "100%";
3279
- table.style.fontFamily = "monospace";
3930
+ table.style.minWidth = "100%";
3931
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3280
3932
  table.style.fontSize = "13px";
3281
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3282
- const rows = text.split(/\r?\n/).slice(0, 500);
3283
- rows.forEach((row, rowIndex) => {
3284
- if (!row.trim()) return;
3933
+ table.style.background = "#ffffff";
3934
+ table.style.border = "1px solid #d0d7de";
3935
+ const headerTr = document.createElement("tr");
3936
+ headerCells.forEach((cell) => {
3937
+ const th = document.createElement("th");
3938
+ th.textContent = cell.trim();
3939
+ th.style.border = "1px solid #d0d7de";
3940
+ th.style.padding = "8px 12px";
3941
+ th.style.backgroundColor = "#f6f8fa";
3942
+ th.style.fontWeight = "600";
3943
+ th.style.whiteSpace = "nowrap";
3944
+ headerTr.appendChild(th);
3945
+ });
3946
+ table.appendChild(headerTr);
3947
+ dataLines.forEach((row, idx) => {
3285
3948
  const tr = document.createElement("tr");
3949
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3286
3950
  const cells = row.split(delimiter);
3287
3951
  cells.forEach((cell) => {
3288
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3952
+ const td = document.createElement("td");
3289
3953
  td.textContent = cell.trim();
3290
3954
  td.style.border = "1px solid #d0d7de";
3291
3955
  td.style.padding = "6px 12px";
3292
- if (rowIndex === 0) {
3293
- td.style.backgroundColor = "#f6f8fa";
3294
- td.style.fontWeight = "bold";
3295
- }
3956
+ td.style.whiteSpace = "nowrap";
3296
3957
  tr.appendChild(td);
3297
3958
  });
3298
3959
  table.appendChild(tr);
3299
3960
  });
3300
- container.appendChild(table);
3961
+ tableWrapper.appendChild(table);
3962
+ container.appendChild(tableWrapper);
3301
3963
  ctx.container.appendChild(container);
3302
3964
  let scale = 1;
3965
+ const calculateFitScale = () => {
3966
+ const availW = Math.max(280, container.clientWidth - 48);
3967
+ const tW = table.offsetWidth || 800;
3968
+ return Math.max(0.4, Math.min(1, availW / tW));
3969
+ };
3970
+ const applyScale = () => {
3971
+ tableWrapper.style.transform = `scale(${scale})`;
3972
+ };
3973
+ requestAnimationFrame(() => {
3974
+ scale = calculateFitScale();
3975
+ applyScale();
3976
+ });
3977
+ const ro = new ResizeObserver(() => {
3978
+ scale = calculateFitScale();
3979
+ applyScale();
3980
+ });
3981
+ ro.observe(container);
3303
3982
  const cleanup = () => {
3983
+ ro.disconnect();
3304
3984
  container.remove();
3305
3985
  ctx.container.innerHTML = "";
3306
3986
  };
3307
3987
  ctx.signal.addEventListener("abort", cleanup);
3308
3988
  return {
3309
3989
  destroy: cleanup,
3990
+ getPageCount: () => 1,
3991
+ getCurrentPage: () => 1,
3992
+ goToPage: () => {
3993
+ },
3994
+ getThumbnails: () => {
3995
+ const sheetName = ctx.metadata.name?.replace(/\.[^/.]+$/, "") || "Sheet1";
3996
+ return [{
3997
+ index: 0,
3998
+ label: sheetName,
3999
+ render: async (canvas) => {
4000
+ canvas.width = 140;
4001
+ canvas.height = 100;
4002
+ const c = canvas.getContext("2d");
4003
+ if (!c) return;
4004
+ c.fillStyle = "#ffffff";
4005
+ c.fillRect(0, 0, canvas.width, canvas.height);
4006
+ c.fillStyle = "#107c41";
4007
+ c.fillRect(0, 0, canvas.width, 16);
4008
+ c.fillStyle = "#ffffff";
4009
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
4010
+ c.fillText(`\u{1F4CA} ${sheetName.slice(0, 16)}`, 6, 11);
4011
+ const startY = 17;
4012
+ const rowH = 13;
4013
+ const colW = 30;
4014
+ const cols = headerCells.slice(0, 4).map((h) => h.trim() || "Col");
4015
+ c.fillStyle = "#f1f5f9";
4016
+ c.fillRect(0, startY, canvas.width, rowH);
4017
+ c.strokeStyle = "#cbd5e1";
4018
+ c.lineWidth = 0.8;
4019
+ c.strokeRect(0, startY, canvas.width, rowH);
4020
+ c.fillStyle = "#64748b";
4021
+ c.font = "bold 7px sans-serif";
4022
+ for (let ci = 0; ci < cols.length; ci++) {
4023
+ c.fillText(cols[ci].slice(0, 5), 14 + ci * colW, startY + 9);
4024
+ }
4025
+ for (let ri = 0; ri < Math.min(5, dataLines.length); ri++) {
4026
+ const y = startY + (ri + 1) * rowH;
4027
+ if (y > canvas.height - 2) break;
4028
+ c.fillStyle = "#f8fafc";
4029
+ c.fillRect(0, y, 12, rowH);
4030
+ c.fillStyle = "#94a3b8";
4031
+ c.font = "6px sans-serif";
4032
+ c.fillText(String(ri + 1), 3, y + 9);
4033
+ c.strokeStyle = "#e2e8f0";
4034
+ c.strokeRect(0, y, canvas.width, rowH);
4035
+ const rowCells = dataLines[ri].split(delimiter);
4036
+ c.fillStyle = "#334155";
4037
+ c.font = "6px sans-serif";
4038
+ for (let ci = 0; ci < cols.length; ci++) {
4039
+ const val = (rowCells[ci] || "").trim();
4040
+ if (val) {
4041
+ c.fillText(val.slice(0, 6), 14 + ci * colW, y + 9);
4042
+ }
4043
+ }
4044
+ }
4045
+ c.strokeStyle = "#cbd5e1";
4046
+ c.lineWidth = 1;
4047
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4048
+ }
4049
+ }];
4050
+ },
3310
4051
  zoomIn: () => {
3311
4052
  scale += 0.1;
3312
- container.style.transform = `scale(${scale})`;
4053
+ applyScale();
3313
4054
  },
3314
4055
  zoomOut: () => {
3315
4056
  scale = Math.max(0.2, scale - 0.1);
3316
- container.style.transform = `scale(${scale})`;
4057
+ applyScale();
3317
4058
  },
3318
4059
  getZoom: () => scale,
3319
4060
  setZoom: (level) => {
3320
4061
  scale = level;
3321
- container.style.transform = `scale(${scale})`;
4062
+ applyScale();
3322
4063
  },
3323
4064
  fitToPage: () => {
3324
- scale = 1;
3325
- container.style.transform = `scale(1)`;
4065
+ scale = calculateFitScale();
4066
+ applyScale();
4067
+ },
4068
+ rotateCW: () => {
4069
+ },
4070
+ rotateCCW: () => {
3326
4071
  },
3327
4072
  download: () => {
3328
4073
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3384,30 +4129,36 @@ var CodePlugin = class {
3384
4129
  return false;
3385
4130
  }
3386
4131
  getToolbarActions(instance) {
3387
- const totalPages = instance.getPageCount?.() ?? 1;
3388
- const actions = [];
3389
- if (totalPages > 1) {
3390
- actions.push({
3391
- id: "page-nav",
3392
- icon: "",
3393
- label: "Page Navigation",
3394
- type: "page-nav",
3395
- group: "navigation",
3396
- value: instance.getCurrentPage?.() ?? 1,
3397
- max: totalPages,
3398
- execute: (action, page) => {
3399
- const cur = instance.getCurrentPage?.() ?? 1;
3400
- const max = instance.getPageCount?.() ?? 1;
3401
- if (action === "prev") {
3402
- if (cur > 1) instance.goToPage?.(cur - 1);
3403
- } else if (action === "next") {
3404
- if (cur < max) instance.goToPage?.(cur + 1);
3405
- } else if (typeof page === "number") {
3406
- instance.goToPage?.(page);
3407
- }
4132
+ const totalPages = instance.getPageCount?.() ?? 1;
4133
+ const actions = [];
4134
+ actions.push({
4135
+ id: "thumbnails",
4136
+ icon: "thumbnails",
4137
+ label: "Page Thumbnails",
4138
+ type: "button",
4139
+ group: "navigation",
4140
+ execute: () => instance.toggleThumbnails?.()
4141
+ });
4142
+ actions.push({
4143
+ id: "page-nav",
4144
+ icon: "",
4145
+ label: "Page Navigation",
4146
+ type: "page-nav",
4147
+ group: "navigation",
4148
+ value: instance.getCurrentPage?.() ?? 1,
4149
+ max: totalPages,
4150
+ execute: (action, page) => {
4151
+ const cur = instance.getCurrentPage?.() ?? 1;
4152
+ const max = instance.getPageCount?.() ?? 1;
4153
+ if (action === "prev") {
4154
+ if (cur > 1) instance.goToPage?.(cur - 1);
4155
+ } else if (action === "next") {
4156
+ if (cur < max) instance.goToPage?.(cur + 1);
4157
+ } else if (typeof page === "number") {
4158
+ instance.goToPage?.(page);
3408
4159
  }
3409
- });
3410
- }
4160
+ }
4161
+ });
3411
4162
  actions.push(
3412
4163
  {
3413
4164
  id: "zoom-out",
@@ -3429,26 +4180,6 @@ var CodePlugin = class {
3429
4180
  instance.zoomIn?.();
3430
4181
  }
3431
4182
  },
3432
- {
3433
- id: "fit-page",
3434
- icon: "fit-page",
3435
- label: "Fit to View",
3436
- type: "button",
3437
- group: "zoom",
3438
- execute: () => {
3439
- instance.fitToPage?.();
3440
- }
3441
- },
3442
- {
3443
- id: "rotate-cw",
3444
- icon: "rotate-cw",
3445
- label: "Rotate",
3446
- type: "button",
3447
- group: "view",
3448
- execute: () => {
3449
- instance.rotateCW?.();
3450
- }
3451
- },
3452
4183
  {
3453
4184
  id: "copy",
3454
4185
  icon: "copy",
@@ -3551,16 +4282,19 @@ var CodePlugin = class {
3551
4282
  let fontSize = 13;
3552
4283
  let rotation = 0;
3553
4284
  let zoomLevel = 1;
4285
+ let isUserZoomed = false;
3554
4286
  const pre = document.createElement("pre");
3555
4287
  const code = document.createElement("code");
3556
4288
  if (isTxt) {
4289
+ container.style.overflowX = "hidden";
4290
+ container.style.overflowY = "auto";
3557
4291
  container.style.backgroundColor = "#f1f5f9";
3558
- container.style.padding = "32px";
4292
+ container.style.padding = "16px 8px";
3559
4293
  container.style.boxSizing = "border-box";
3560
4294
  container.style.display = "flex";
3561
4295
  container.style.flexDirection = "column";
3562
4296
  container.style.alignItems = "center";
3563
- pre.style.margin = "0";
4297
+ pre.style.margin = "0 auto";
3564
4298
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3565
4299
  pre.style.fontSize = "13px";
3566
4300
  pre.style.color = "#1e293b";
@@ -3572,10 +4306,12 @@ var CodePlugin = class {
3572
4306
  pre.style.minHeight = "1056px";
3573
4307
  pre.style.padding = "72px 56px";
3574
4308
  pre.style.boxSizing = "border-box";
3575
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4309
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3576
4310
  pre.style.borderRadius = "4px";
3577
4311
  pre.style.transformOrigin = "top center";
4312
+ pre.style.transition = "transform 0.15s ease";
3578
4313
  } else {
4314
+ container.style.overflow = "auto";
3579
4315
  container.style.backgroundColor = "#1e1e1e";
3580
4316
  container.style.color = "#d4d4d4";
3581
4317
  container.style.padding = "16px";
@@ -3607,42 +4343,9 @@ var CodePlugin = class {
3607
4343
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3608
4344
  pre.appendChild(code);
3609
4345
  container.appendChild(pre);
3610
- let indicator = null;
3611
- if (totalPages > 1) {
3612
- indicator = document.createElement("div");
3613
- indicator.className = "fp-code-page-indicator";
3614
- indicator.style.position = "sticky";
3615
- indicator.style.bottom = "16px";
3616
- if (isTxt) {
3617
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3618
- indicator.style.color = "#334155";
3619
- indicator.style.border = "1px solid #e2e8f0";
3620
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3621
- } else {
3622
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3623
- indicator.style.color = "#f8fafc";
3624
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3625
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3626
- indicator.style.backdropFilter = "blur(8px)";
3627
- }
3628
- indicator.style.fontSize = "12px";
3629
- indicator.style.fontWeight = "600";
3630
- indicator.style.padding = "5px 14px";
3631
- indicator.style.borderRadius = "20px";
3632
- indicator.style.zIndex = "10";
3633
- indicator.style.userSelect = "none";
3634
- indicator.style.pointerEvents = "none";
3635
- indicator.style.textAlign = "center";
3636
- indicator.style.width = "fit-content";
3637
- indicator.style.margin = "16px auto 0";
3638
- container.appendChild(indicator);
3639
- }
3640
4346
  const showPage = (pageNum) => {
3641
4347
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3642
4348
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3643
- if (indicator) {
3644
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3645
- }
3646
4349
  container.scrollTop = 0;
3647
4350
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3648
4351
  };
@@ -3650,36 +4353,90 @@ var CodePlugin = class {
3650
4353
  showPage(1);
3651
4354
  }
3652
4355
  ctx.container.appendChild(container);
3653
- const cleanup = () => {
3654
- indicator?.remove();
3655
- container.remove();
3656
- ctx.container.innerHTML = "";
4356
+ const calculateTxtFit = () => {
4357
+ const availW = Math.max(280, container.clientWidth - 32);
4358
+ return Math.max(0.4, Math.min(3, availW / 816));
3657
4359
  };
3658
- ctx.signal.addEventListener("abort", cleanup);
3659
4360
  const updateTransform = () => {
3660
4361
  if (isTxt) {
3661
4362
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4363
+ const scaledH = 1056 * zoomLevel;
4364
+ const extraH = Math.max(0, scaledH - 1056);
4365
+ pre.style.marginBottom = `${extraH + 32}px`;
3662
4366
  } else {
3663
4367
  pre.style.transform = `rotate(${rotation}deg)`;
3664
4368
  pre.style.fontSize = `${fontSize}px`;
3665
4369
  }
3666
4370
  };
4371
+ let resizeObserver = null;
4372
+ if (isTxt) {
4373
+ setTimeout(() => {
4374
+ zoomLevel = calculateTxtFit();
4375
+ updateTransform();
4376
+ }, 60);
4377
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4378
+ if (!isUserZoomed) {
4379
+ zoomLevel = calculateTxtFit();
4380
+ updateTransform();
4381
+ }
4382
+ }) : null;
4383
+ resizeObserver?.observe(container);
4384
+ }
4385
+ const cleanup = () => {
4386
+ resizeObserver?.disconnect();
4387
+ container.remove();
4388
+ ctx.container.innerHTML = "";
4389
+ };
4390
+ ctx.signal.addEventListener("abort", cleanup);
3667
4391
  return {
3668
4392
  destroy: cleanup,
3669
4393
  getPageCount: () => totalPages,
3670
4394
  getCurrentPage: () => currentPage,
3671
4395
  goToPage: (page) => showPage(page),
4396
+ getThumbnails: () => {
4397
+ return rawPages.map((pageText, idx) => ({
4398
+ index: idx,
4399
+ label: `Page ${idx + 1}`,
4400
+ render: async (canvas) => {
4401
+ canvas.width = 140;
4402
+ canvas.height = 180;
4403
+ const c = canvas.getContext("2d");
4404
+ if (!c) return;
4405
+ c.fillStyle = "#ffffff";
4406
+ c.fillRect(0, 0, canvas.width, canvas.height);
4407
+ c.strokeStyle = "#cbd5e1";
4408
+ c.lineWidth = 1;
4409
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4410
+ c.fillStyle = "#64748b";
4411
+ const lines = (pageText || "").split("\n").slice(0, 24);
4412
+ const lineH = 6;
4413
+ const startY = 14;
4414
+ const startX = 12;
4415
+ const maxW = canvas.width - 24;
4416
+ c.font = "5px monospace";
4417
+ for (let li = 0; li < lines.length; li++) {
4418
+ const l = lines[li].trim();
4419
+ if (!l) continue;
4420
+ const y = startY + li * lineH;
4421
+ if (y > canvas.height - 12) break;
4422
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4423
+ }
4424
+ }
4425
+ }));
4426
+ },
3672
4427
  zoomIn: () => {
4428
+ isUserZoomed = true;
3673
4429
  if (isTxt) {
3674
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4430
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3675
4431
  } else {
3676
4432
  fontSize = Math.min(32, fontSize + 2);
3677
4433
  }
3678
4434
  updateTransform();
3679
4435
  },
3680
4436
  zoomOut: () => {
4437
+ isUserZoomed = true;
3681
4438
  if (isTxt) {
3682
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4439
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3683
4440
  } else {
3684
4441
  fontSize = Math.max(8, fontSize - 2);
3685
4442
  }
@@ -3687,6 +4444,7 @@ var CodePlugin = class {
3687
4444
  },
3688
4445
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3689
4446
  setZoom: (level) => {
4447
+ isUserZoomed = true;
3690
4448
  if (isTxt) {
3691
4449
  zoomLevel = level;
3692
4450
  } else {
@@ -3695,9 +4453,10 @@ var CodePlugin = class {
3695
4453
  updateTransform();
3696
4454
  },
3697
4455
  fitToPage: () => {
4456
+ isUserZoomed = false;
3698
4457
  fontSize = 13;
3699
4458
  rotation = 0;
3700
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4459
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3701
4460
  updateTransform();
3702
4461
  },
3703
4462
  rotateCW: () => {
@@ -4564,6 +5323,148 @@ var ThreeDPlugin = class {
4564
5323
  function threeDPlugin() {
4565
5324
  return new ThreeDPlugin();
4566
5325
  }
5326
+ function cleanRtfText(raw) {
5327
+ return raw.replace(/\\bin\d+\s/g, "").replace(/{\\\*[^}]+}/g, "").replace(/\\u(-?\d+)\??/g, (_, code) => {
5328
+ let num = parseInt(code, 10);
5329
+ if (num < 0) num += 65536;
5330
+ return String.fromCharCode(num);
5331
+ }).replace(/\\'([0-9a-fA-F]{2})/g, (_, hex) => {
5332
+ return String.fromCharCode(parseInt(hex, 16));
5333
+ }).replace(/\\tab\b/g, " ").replace(/\\emdash\b/g, "\u2014").replace(/\\endash\b/g, "\u2013").replace(/\\bullet\b/g, "\u2022").replace(/\\lquote\b/g, "\u2018").replace(/\\rquote\b/g, "\u2019").replace(/\\ldblquote\b/g, "\u201C").replace(/\\rdblquote\b/g, "\u201D").replace(/\\\w+(?:-?\d+)?\s?/g, " ").replace(/[{}\r\n]/g, " ").replace(/\s+/g, " ").trim();
5334
+ }
5335
+ function parseRtfTables(rtfText) {
5336
+ const rowSegments = rtfText.split(/\\row\b/);
5337
+ if (rowSegments.length <= 1) return [];
5338
+ const rawTables = [];
5339
+ let currentTable = null;
5340
+ let charOffset = 0;
5341
+ for (let i = 0; i < rowSegments.length - 1; i++) {
5342
+ const segment = rowSegments[i];
5343
+ const segmentStart = charOffset;
5344
+ const segmentEnd = charOffset + segment.length + 4;
5345
+ const cellParts = segment.split(/\\cell\b/);
5346
+ if (cellParts.length > 1) {
5347
+ const cellxMatches = [...segment.matchAll(/\\cellx(\d+)/g)].map((m) => parseInt(m[1], 10));
5348
+ let cellWidths = [];
5349
+ if (cellxMatches.length > 0) {
5350
+ let seq = [cellxMatches[0]];
5351
+ for (let k = 1; k < cellxMatches.length; k++) {
5352
+ if (cellxMatches[k] > cellxMatches[k - 1]) seq.push(cellxMatches[k]);
5353
+ else break;
5354
+ }
5355
+ let prev = 0;
5356
+ cellWidths = seq.map((x) => {
5357
+ const w = x - prev;
5358
+ prev = x;
5359
+ return w;
5360
+ });
5361
+ }
5362
+ const cells = [];
5363
+ for (let c = 0; c < cellParts.length - 1; c++) {
5364
+ let rawCell = cellParts[c];
5365
+ if (c === 0) {
5366
+ const lastTrowd = rawCell.lastIndexOf("\\trowd");
5367
+ if (lastTrowd !== -1) rawCell = rawCell.slice(lastTrowd);
5368
+ }
5369
+ cells.push(cleanRtfText(rawCell));
5370
+ }
5371
+ const isNewTable = !currentTable || segmentStart - currentTable.lastEnd > 2500;
5372
+ if (isNewTable) {
5373
+ const prevTextSlice = rtfText.slice(Math.max(0, segmentStart - 4e3), segmentStart);
5374
+ const cleanPrev = cleanRtfText(prevTextSlice);
5375
+ const anchorBefore = cleanPrev.slice(-80).trim();
5376
+ currentTable = {
5377
+ id: `RTF_TABLE_${rawTables.length}`,
5378
+ startPos: segmentStart,
5379
+ lastEnd: segmentEnd,
5380
+ rows: [],
5381
+ columnWidths: cellWidths,
5382
+ anchorBefore,
5383
+ anchorAfter: ""
5384
+ };
5385
+ rawTables.push(currentTable);
5386
+ }
5387
+ if (currentTable) {
5388
+ currentTable.rows.push({
5389
+ cells,
5390
+ widths: cellWidths.length === cells.length ? cellWidths : currentTable.columnWidths
5391
+ });
5392
+ currentTable.lastEnd = segmentEnd;
5393
+ }
5394
+ }
5395
+ charOffset += segment.length + 4;
5396
+ }
5397
+ rawTables.forEach((tbl) => {
5398
+ const afterSlice = rtfText.slice(tbl.lastEnd, Math.min(rtfText.length, tbl.lastEnd + 2e3));
5399
+ const cleanAfter = cleanRtfText(afterSlice);
5400
+ tbl.anchorAfter = cleanAfter.slice(0, 80).trim();
5401
+ });
5402
+ return rawTables.map((tbl) => {
5403
+ const totalW = (tbl.columnWidths || []).reduce((a, b) => a + b, 0);
5404
+ const colPercents = totalW > 0 ? tbl.columnWidths.map((w) => Math.round(w / totalW * 100)) : [];
5405
+ return {
5406
+ id: tbl.id,
5407
+ rows: tbl.rows.map((r) => r.cells),
5408
+ colPercents,
5409
+ anchorBefore: tbl.anchorBefore,
5410
+ anchorAfter: tbl.anchorAfter
5411
+ };
5412
+ });
5413
+ }
5414
+ function renderRtfTableElement(table) {
5415
+ const tableEl = document.createElement("table");
5416
+ tableEl.className = "fp-rtf-table";
5417
+ tableEl.style.width = "100%";
5418
+ tableEl.style.borderCollapse = "collapse";
5419
+ tableEl.style.margin = "16px 0 20px";
5420
+ tableEl.style.fontSize = "10pt";
5421
+ tableEl.style.lineHeight = "1.5";
5422
+ tableEl.style.border = "1px solid #cbd5e1";
5423
+ tableEl.style.borderRadius = "4px";
5424
+ tableEl.style.boxSizing = "border-box";
5425
+ tableEl.style.backgroundColor = "#ffffff";
5426
+ const thead = document.createElement("thead");
5427
+ const tbody = document.createElement("tbody");
5428
+ table.rows.forEach((rowCells, rIdx) => {
5429
+ const tr = document.createElement("tr");
5430
+ const isHeader = rIdx === 0;
5431
+ if (isHeader) {
5432
+ tr.style.backgroundColor = "#f8fafc";
5433
+ tr.style.borderBottom = "2px solid #cbd5e1";
5434
+ } else {
5435
+ tr.style.borderBottom = "1px solid #e2e8f0";
5436
+ if (rIdx % 2 === 0) {
5437
+ tr.style.backgroundColor = "#fcfdfe";
5438
+ }
5439
+ }
5440
+ rowCells.forEach((cellText, cIdx) => {
5441
+ const cellEl = document.createElement(isHeader ? "th" : "td");
5442
+ cellEl.style.padding = "8px 12px";
5443
+ cellEl.style.border = "1px solid #cbd5e1";
5444
+ cellEl.style.verticalAlign = "top";
5445
+ cellEl.style.color = isHeader ? "#0f172a" : "#1e293b";
5446
+ cellEl.style.fontWeight = isHeader ? "600" : "normal";
5447
+ if (table.colPercents && table.colPercents[cIdx]) {
5448
+ cellEl.style.width = `${table.colPercents[cIdx]}%`;
5449
+ }
5450
+ if (/^\d+$/.test(cellText.trim()) || cellText.trim() === "#") {
5451
+ cellEl.style.textAlign = "center";
5452
+ } else {
5453
+ cellEl.style.textAlign = "left";
5454
+ }
5455
+ cellEl.textContent = cellText;
5456
+ tr.appendChild(cellEl);
5457
+ });
5458
+ if (isHeader) {
5459
+ thead.appendChild(tr);
5460
+ } else {
5461
+ tbody.appendChild(tr);
5462
+ }
5463
+ });
5464
+ tableEl.appendChild(thead);
5465
+ tableEl.appendChild(tbody);
5466
+ return tableEl;
5467
+ }
4567
5468
  var RtfPlugin = class {
4568
5469
  id = "rtf";
4569
5470
  name = "Rich Text Format (RTF)";
@@ -4578,29 +5479,35 @@ var RtfPlugin = class {
4578
5479
  getToolbarActions(instance) {
4579
5480
  const totalPages = instance.getPageCount?.() ?? 1;
4580
5481
  const actions = [];
4581
- if (totalPages > 1) {
4582
- actions.push({
4583
- id: "page-nav",
4584
- icon: "",
4585
- label: "Page Navigation",
4586
- type: "page-nav",
4587
- group: "navigation",
4588
- value: instance.getCurrentPage?.() ?? 1,
4589
- max: totalPages,
4590
- execute: (action, page) => {
4591
- const cur = instance.getCurrentPage?.() ?? 1;
4592
- const max = instance.getPageCount?.() ?? 1;
4593
- if (action === "prev") {
4594
- if (cur > 1) instance.goToPage?.(cur - 1);
4595
- } else if (action === "next") {
4596
- if (cur < max) instance.goToPage?.(cur + 1);
4597
- } else if (typeof page === "number") {
4598
- instance.goToPage?.(page);
4599
- }
5482
+ actions.push({
5483
+ id: "page-nav",
5484
+ icon: "",
5485
+ label: "Page Navigation",
5486
+ type: "page-nav",
5487
+ group: "navigation",
5488
+ value: instance.getCurrentPage?.() ?? 1,
5489
+ max: totalPages,
5490
+ execute: (action, page) => {
5491
+ const cur = instance.getCurrentPage?.() ?? 1;
5492
+ const max = instance.getPageCount?.() ?? 1;
5493
+ if (action === "prev") {
5494
+ if (cur > 1) instance.goToPage?.(cur - 1);
5495
+ } else if (action === "next") {
5496
+ if (cur < max) instance.goToPage?.(cur + 1);
5497
+ } else if (typeof page === "number") {
5498
+ instance.goToPage?.(page);
4600
5499
  }
4601
- });
4602
- }
5500
+ }
5501
+ });
4603
5502
  actions.push(
5503
+ {
5504
+ id: "thumbnails",
5505
+ icon: "thumbnails",
5506
+ label: "Thumbnails",
5507
+ type: "button",
5508
+ group: "view",
5509
+ execute: () => instance.toggleThumbnails?.()
5510
+ },
4604
5511
  {
4605
5512
  id: "zoom-out",
4606
5513
  icon: "zoom-out",
@@ -4671,67 +5578,143 @@ var RtfPlugin = class {
4671
5578
  async render(ctx) {
4672
5579
  const wrapper = document.createElement("div");
4673
5580
  wrapper.className = "fp-rtf-wrapper";
4674
- wrapper.style.padding = "32px";
4675
- wrapper.style.maxWidth = "850px";
5581
+ wrapper.style.padding = "0";
5582
+ wrapper.style.width = "816px";
4676
5583
  wrapper.style.margin = "0 auto";
4677
- wrapper.style.backgroundColor = "#fff";
4678
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4679
- wrapper.style.borderRadius = "4px";
4680
- wrapper.style.minHeight = "100%";
5584
+ wrapper.style.boxSizing = "border-box";
5585
+ wrapper.style.display = "flex";
5586
+ wrapper.style.flexDirection = "column";
5587
+ wrapper.style.alignItems = "center";
5588
+ wrapper.style.backgroundColor = "transparent";
4681
5589
  wrapper.style.transformOrigin = "top center";
4682
- wrapper.style.transition = "transform 0.2s ease";
4683
- ctx.container.style.overflow = "auto";
4684
- ctx.container.style.padding = "24px";
5590
+ wrapper.style.transition = "transform 0.15s ease";
5591
+ ctx.container.style.overflowX = "hidden";
5592
+ ctx.container.style.overflowY = "auto";
5593
+ ctx.container.style.padding = "16px 8px";
4685
5594
  ctx.container.style.backgroundColor = "#f1f5f9";
4686
5595
  ctx.container.appendChild(wrapper);
4687
5596
  let scale = 1;
4688
5597
  let rotation = 0;
4689
5598
  let pageElements = [];
4690
- let fitMode = "width";
5599
+ let isUserZoomed = false;
5600
+ let fitMode = ctx?.options?.fitMode || "width";
4691
5601
  const calculateFitScale = (mode = fitMode) => {
4692
5602
  const activeEl = pageElements[currentPage - 1] || wrapper;
4693
- const elW = activeEl.offsetWidth || 850;
4694
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4695
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4696
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5603
+ const elW = 816;
5604
+ const singlePageH = activeEl?.offsetHeight || 1056;
5605
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5606
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4697
5607
  const sW = availW / elW;
4698
5608
  const sH = availH / singlePageH;
4699
5609
  if (mode === "page") {
4700
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5610
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4701
5611
  }
4702
- return Math.max(0.65, Math.min(1.05, sW));
5612
+ return Math.max(0.4, Math.min(3, sW));
4703
5613
  };
4704
5614
  const applyTransform = () => {
4705
5615
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4706
5616
  wrapper.style.transformOrigin = "top center";
5617
+ const activeEl = pageElements[currentPage - 1];
5618
+ const baseH = activeEl?.offsetHeight || 1056;
5619
+ const scaledH = baseH * scale;
5620
+ const extraH = Math.max(0, scaledH - baseH);
5621
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4707
5622
  };
5623
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5624
+ if (!isUserZoomed) {
5625
+ scale = calculateFitScale(fitMode);
5626
+ applyTransform();
5627
+ }
5628
+ }) : null;
5629
+ resizeObserver?.observe(ctx.container);
4708
5630
  setTimeout(() => {
4709
- fitMode = "width";
4710
- scale = calculateFitScale("width");
5631
+ scale = calculateFitScale(fitMode);
4711
5632
  applyTransform();
4712
5633
  }, 60);
4713
5634
  try {
4714
5635
  if (typeof RTFJS.loggingEnabled === "function") {
4715
5636
  RTFJS.loggingEnabled(false);
4716
5637
  }
5638
+ const rawText = new TextDecoder("latin1").decode(ctx.buffer);
5639
+ const tables = parseRtfTables(rawText);
4717
5640
  const doc = new RTFJS.Document(ctx.buffer, {});
4718
5641
  const htmlElements = await doc.render();
4719
5642
  const contentNodes = [];
4720
- for (const item of htmlElements) {
4721
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4722
- contentNodes.push(...Array.from(item.children));
4723
- } else {
4724
- contentNodes.push(item);
5643
+ const extractBlocks = (nodes) => {
5644
+ for (const item of nodes) {
5645
+ if (!item || !(item instanceof HTMLElement)) continue;
5646
+ const tag = item.tagName.toLowerCase();
5647
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5648
+ const ct = c.tagName.toLowerCase();
5649
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5650
+ });
5651
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5652
+ extractBlocks(Array.from(item.children));
5653
+ } else {
5654
+ const svgs = item.querySelectorAll("svg, img");
5655
+ svgs.forEach((s) => {
5656
+ const el = s;
5657
+ el.style.maxWidth = "100%";
5658
+ el.style.maxHeight = "360px";
5659
+ el.style.display = "block";
5660
+ el.style.margin = "12px auto";
5661
+ if (s.tagName.toLowerCase() === "svg") {
5662
+ s.removeAttribute("width");
5663
+ s.removeAttribute("height");
5664
+ el.style.width = "100%";
5665
+ el.style.maxHeight = "360px";
5666
+ }
5667
+ });
5668
+ if (tag === "p" || tag === "div") {
5669
+ item.style.display = "block";
5670
+ item.style.marginBottom = "12px";
5671
+ item.style.lineHeight = "1.6";
5672
+ }
5673
+ contentNodes.push(item);
5674
+ }
4725
5675
  }
4726
- }
5676
+ };
5677
+ extractBlocks(htmlElements);
5678
+ tables.forEach((table) => {
5679
+ const tableEl = renderRtfTableElement(table);
5680
+ let inserted = false;
5681
+ if (table.anchorBefore && table.anchorBefore.length > 10) {
5682
+ const keyword = table.anchorBefore.slice(-35).trim();
5683
+ for (let i = 0; i < contentNodes.length; i++) {
5684
+ if (contentNodes[i].textContent?.includes(keyword)) {
5685
+ contentNodes.splice(i + 1, 0, tableEl);
5686
+ inserted = true;
5687
+ break;
5688
+ }
5689
+ }
5690
+ }
5691
+ if (!inserted && table.anchorAfter && table.anchorAfter.length > 10) {
5692
+ const keyword = table.anchorAfter.slice(0, 35).trim();
5693
+ for (let i = 0; i < contentNodes.length; i++) {
5694
+ if (contentNodes[i].textContent?.includes(keyword)) {
5695
+ contentNodes.splice(i, 0, tableEl);
5696
+ inserted = true;
5697
+ break;
5698
+ }
5699
+ }
5700
+ }
5701
+ if (!inserted) {
5702
+ if (contentNodes.length > 2) {
5703
+ contentNodes.splice(2, 0, tableEl);
5704
+ } else {
5705
+ contentNodes.push(tableEl);
5706
+ }
5707
+ }
5708
+ });
4727
5709
  wrapper.innerHTML = "";
4728
5710
  contentNodes.forEach((node) => wrapper.appendChild(node));
4729
5711
  const childHeights = contentNodes.map((c) => {
4730
5712
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4731
5713
  const offH = c.offsetHeight || 0;
5714
+ const realH = Math.max(rectH, offH);
5715
+ if (realH > 0) return realH;
4732
5716
  const textLen = c.textContent?.trim().length || 0;
4733
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4734
- return Math.max(rectH, offH, estH);
5717
+ return Math.max(24, Math.ceil(textLen / 95) * 22 + 12);
4735
5718
  });
4736
5719
  wrapper.innerHTML = "";
4737
5720
  const createRtfCard = () => {
@@ -4740,35 +5723,49 @@ var RtfPlugin = class {
4740
5723
  card.style.backgroundColor = "#ffffff";
4741
5724
  card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4742
5725
  card.style.borderRadius = "4px";
4743
- card.style.padding = "72px 56px";
5726
+ card.style.padding = "64px 56px";
4744
5727
  card.style.width = "816px";
4745
5728
  card.style.minHeight = "1056px";
4746
5729
  card.style.boxSizing = "border-box";
4747
5730
  card.style.marginBottom = "24px";
4748
5731
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4749
5732
  card.style.lineHeight = "1.6";
5733
+ card.style.color = "#1e293b";
4750
5734
  return card;
4751
5735
  };
4752
5736
  let curCard = createRtfCard();
4753
5737
  wrapper.appendChild(curCard);
4754
5738
  pageElements = [curCard];
4755
5739
  let curH = 0;
4756
- const maxH = 912;
5740
+ const maxH = 928;
4757
5741
  for (let i = 0; i < contentNodes.length; i++) {
4758
5742
  const child = contentNodes[i];
4759
5743
  const chH = childHeights[i];
4760
- curCard.appendChild(child);
4761
- curH += chH;
4762
- if (curH >= maxH && i < contentNodes.length - 1) {
5744
+ const isChildEmpty = (child.textContent || "").trim().length === 0 && !child.querySelector("table, img, svg, canvas");
5745
+ if (curH === 0 && isChildEmpty) {
5746
+ continue;
5747
+ }
5748
+ if (curH + chH > maxH && curCard.childNodes.length > 0) {
4763
5749
  curCard = createRtfCard();
4764
5750
  wrapper.appendChild(curCard);
4765
5751
  pageElements.push(curCard);
4766
5752
  curH = 0;
4767
5753
  }
5754
+ curCard.appendChild(child);
5755
+ curH += chH;
5756
+ }
5757
+ pageElements = pageElements.filter((card) => {
5758
+ const hasText = (card.textContent || "").trim().length > 0;
5759
+ const hasMedia = card.querySelector("table, img, svg, canvas") !== null;
5760
+ return hasText || hasMedia;
5761
+ });
5762
+ if (pageElements.length === 0) {
5763
+ pageElements = [curCard];
4768
5764
  }
4769
5765
  } catch (err) {
4770
5766
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
4771
5767
  const text = new TextDecoder("latin1").decode(ctx.buffer);
5768
+ const tables = parseRtfTables(text);
4772
5769
  const rawPages = text.split(/\\page\b/).map((segment) => {
4773
5770
  return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4774
5771
  }).filter((p) => p.length > 0);
@@ -4779,47 +5776,26 @@ var RtfPlugin = class {
4779
5776
  pageCard.style.backgroundColor = "#ffffff";
4780
5777
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4781
5778
  pageCard.style.borderRadius = "4px";
4782
- pageCard.style.padding = "72px 56px";
5779
+ pageCard.style.padding = "64px 56px";
4783
5780
  pageCard.style.width = "816px";
4784
5781
  pageCard.style.minHeight = "1056px";
4785
- pageCard.style.maxWidth = "100%";
4786
5782
  pageCard.style.boxSizing = "border-box";
4787
- pageCard.style.fontFamily = "serif";
5783
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4788
5784
  pageCard.style.fontSize = "12pt";
4789
5785
  pageCard.style.lineHeight = "1.6";
4790
5786
  pageCard.style.color = "#1e293b";
4791
5787
  pageCard.style.whiteSpace = "pre-wrap";
4792
5788
  pageCard.style.display = i === 0 ? "block" : "none";
4793
5789
  pageCard.textContent = pages[i];
5790
+ if (i === 1 && tables.length > 0) {
5791
+ tables.forEach((tbl) => pageCard.appendChild(renderRtfTableElement(tbl)));
5792
+ }
4794
5793
  wrapper.appendChild(pageCard);
4795
5794
  pageElements.push(pageCard);
4796
5795
  }
4797
5796
  }
4798
5797
  const totalPages = Math.max(1, pageElements.length);
4799
5798
  let currentPage = 1;
4800
- let indicator = null;
4801
- if (totalPages > 1) {
4802
- indicator = document.createElement("div");
4803
- indicator.className = "fp-rtf-page-indicator";
4804
- indicator.style.position = "sticky";
4805
- indicator.style.bottom = "16px";
4806
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4807
- indicator.style.backdropFilter = "blur(8px)";
4808
- indicator.style.color = "#f8fafc";
4809
- indicator.style.fontSize = "12px";
4810
- indicator.style.fontWeight = "600";
4811
- indicator.style.padding = "5px 14px";
4812
- indicator.style.borderRadius = "20px";
4813
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4814
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4815
- indicator.style.zIndex = "10";
4816
- indicator.style.userSelect = "none";
4817
- indicator.style.pointerEvents = "none";
4818
- indicator.style.textAlign = "center";
4819
- indicator.style.width = "fit-content";
4820
- indicator.style.margin = "16px auto 0";
4821
- ctx.container.appendChild(indicator);
4822
- }
4823
5799
  const showPage = (pageNum) => {
4824
5800
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4825
5801
  if (totalPages > 1) {
@@ -4827,9 +5803,6 @@ var RtfPlugin = class {
4827
5803
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4828
5804
  });
4829
5805
  }
4830
- if (indicator) {
4831
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4832
- }
4833
5806
  ctx.container.scrollTop = 0;
4834
5807
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4835
5808
  };
@@ -4837,7 +5810,7 @@ var RtfPlugin = class {
4837
5810
  showPage(1);
4838
5811
  }
4839
5812
  const cleanup = () => {
4840
- indicator?.remove();
5813
+ resizeObserver?.disconnect();
4841
5814
  wrapper.remove();
4842
5815
  ctx.container.innerHTML = "";
4843
5816
  };
@@ -4847,22 +5820,52 @@ var RtfPlugin = class {
4847
5820
  getPageCount: () => totalPages,
4848
5821
  getCurrentPage: () => currentPage,
4849
5822
  goToPage: (page) => showPage(page),
5823
+ getThumbnails: async () => {
5824
+ const items = [];
5825
+ for (let idx = 0; idx < totalPages; idx++) {
5826
+ const pageIdx = idx;
5827
+ items.push({
5828
+ index: pageIdx,
5829
+ label: `Page ${pageIdx + 1}`,
5830
+ render: async (canvas) => {
5831
+ const ctx2d = canvas.getContext("2d");
5832
+ if (!ctx2d) return;
5833
+ canvas.width = 140;
5834
+ canvas.height = 180;
5835
+ ctx2d.fillStyle = "#ffffff";
5836
+ ctx2d.fillRect(0, 0, 140, 180);
5837
+ ctx2d.strokeStyle = "#cbd5e1";
5838
+ ctx2d.lineWidth = 1;
5839
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
5840
+ ctx2d.fillStyle = "#334155";
5841
+ ctx2d.font = 'bold 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
5842
+ ctx2d.textAlign = "center";
5843
+ ctx2d.fillText(`Page ${pageIdx + 1}`, 70, 95);
5844
+ }
5845
+ });
5846
+ }
5847
+ return items;
5848
+ },
4850
5849
  zoomIn: () => {
5850
+ isUserZoomed = true;
4851
5851
  scale += 0.15;
4852
5852
  applyTransform();
4853
5853
  },
4854
5854
  zoomOut: () => {
5855
+ isUserZoomed = true;
4855
5856
  scale = Math.max(0.2, scale - 0.15);
4856
5857
  applyTransform();
4857
5858
  },
4858
5859
  getZoom: () => scale,
4859
5860
  setZoom: (level) => {
5861
+ isUserZoomed = true;
4860
5862
  scale = level;
4861
5863
  applyTransform();
4862
5864
  },
4863
5865
  fitToPage: () => {
4864
- fitMode = fitMode === "width" ? "page" : "width";
4865
- scale = calculateFitScale(fitMode);
5866
+ isUserZoomed = false;
5867
+ fitMode = "width";
5868
+ scale = calculateFitScale("width");
4866
5869
  rotation = 0;
4867
5870
  applyTransform();
4868
5871
  },
@@ -5050,40 +6053,36 @@ var OpenDocumentPlugin = class {
5050
6053
  const isPresentation = instance.isPresentation;
5051
6054
  const totalPages = instance.getPageCount?.() ?? 1;
5052
6055
  const actions = [];
5053
- if (isPresentation || totalPages > 1) {
5054
- if (isPresentation) {
5055
- actions.push({
5056
- id: "thumbnails",
5057
- icon: "thumbnails",
5058
- label: "Slide Thumbnails",
5059
- type: "button",
5060
- group: "navigation",
5061
- execute: () => instance.toggleThumbnails?.()
5062
- });
5063
- }
5064
- actions.push({
5065
- id: "page-nav",
5066
- icon: "",
5067
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
5068
- type: "page-nav",
5069
- group: "navigation",
5070
- value: instance.getCurrentPage?.() ?? 1,
5071
- max: totalPages,
5072
- execute: (action, page) => {
5073
- const cur = instance.getCurrentPage?.() ?? 1;
5074
- const max = instance.getPageCount?.() ?? 1;
5075
- if (action === "prev") {
5076
- if (cur > 1) instance.goToPage?.(cur - 1);
5077
- } else if (action === "next") {
5078
- if (cur < max) instance.goToPage?.(cur + 1);
5079
- } else if (typeof page === "number") {
5080
- instance.goToPage?.(page);
5081
- } else if (typeof action === "number") {
5082
- instance.goToPage?.(action);
5083
- }
6056
+ actions.push({
6057
+ id: "thumbnails",
6058
+ icon: "thumbnails",
6059
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
6060
+ type: "button",
6061
+ group: "navigation",
6062
+ execute: () => instance.toggleThumbnails?.()
6063
+ });
6064
+ actions.push({
6065
+ id: "page-nav",
6066
+ icon: "",
6067
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
6068
+ type: "page-nav",
6069
+ group: "navigation",
6070
+ value: instance.getCurrentPage?.() ?? 1,
6071
+ max: totalPages,
6072
+ execute: (action, page) => {
6073
+ const cur = instance.getCurrentPage?.() ?? 1;
6074
+ const max = instance.getPageCount?.() ?? 1;
6075
+ if (action === "prev") {
6076
+ if (cur > 1) instance.goToPage?.(cur - 1);
6077
+ } else if (action === "next") {
6078
+ if (cur < max) instance.goToPage?.(cur + 1);
6079
+ } else if (typeof page === "number") {
6080
+ instance.goToPage?.(page);
6081
+ } else if (typeof action === "number") {
6082
+ instance.goToPage?.(action);
5084
6083
  }
5085
- });
5086
- }
6084
+ }
6085
+ });
5087
6086
  actions.push(
5088
6087
  {
5089
6088
  id: "zoom-out",
@@ -5174,49 +6173,65 @@ var OpenDocumentPlugin = class {
5174
6173
  container.className = "fp-odf-container";
5175
6174
  container.style.width = "100%";
5176
6175
  container.style.height = "100%";
5177
- container.style.overflow = "auto";
5178
- container.style.padding = "24px";
6176
+ container.style.overflowX = "hidden";
6177
+ container.style.overflowY = "auto";
6178
+ container.style.padding = "16px 8px";
5179
6179
  container.style.backgroundColor = "#f1f5f9";
5180
6180
  const wrapper = document.createElement("div");
5181
6181
  wrapper.className = "fp-odf-wrapper";
5182
6182
  wrapper.style.margin = "0 auto";
6183
+ wrapper.style.width = isPresentation ? "960px" : "816px";
6184
+ wrapper.style.boxSizing = "border-box";
5183
6185
  wrapper.style.backgroundColor = "#ffffff";
5184
6186
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5185
6187
  wrapper.style.borderRadius = "4px";
5186
6188
  wrapper.style.transformOrigin = "top center";
5187
- wrapper.style.transition = "transform 0.2s ease";
6189
+ wrapper.style.transition = "transform 0.15s ease";
5188
6190
  container.appendChild(wrapper);
5189
6191
  ctx.container.appendChild(container);
5190
6192
  let scale = 1;
5191
6193
  let rotation = 0;
5192
- let fitMode = "width";
6194
+ let isUserZoomed = false;
6195
+ let fitMode = ctx?.options?.fitMode || "width";
6196
+ let currentPage = 1;
6197
+ let totalPages = 1;
6198
+ let slides = [];
5193
6199
  const calculateFitScale = (mode = fitMode) => {
5194
- const elW = wrapper.offsetWidth || 850;
5195
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5196
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5197
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6200
+ const activeSlide = slides[currentPage - 1];
6201
+ const elW = isPresentation ? 960 : 816;
6202
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
6203
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6204
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5198
6205
  const sW = availW / elW;
5199
6206
  const sH = availH / (isPresentation ? 540 : singlePageH);
5200
6207
  if (isPresentation) {
5201
- return Math.min(1, Math.min(sW, sH));
6208
+ return Math.min(2.5, Math.min(sW, sH));
5202
6209
  }
5203
6210
  if (mode === "page") {
5204
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6211
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5205
6212
  }
5206
- return Math.max(0.65, Math.min(1.05, sW));
6213
+ return Math.max(0.4, Math.min(3, sW));
5207
6214
  };
5208
6215
  const applyTransform = () => {
5209
6216
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5210
6217
  wrapper.style.transformOrigin = "top center";
6218
+ const activeSlide = slides[currentPage - 1];
6219
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6220
+ const scaledH = baseH * scale;
6221
+ const extraH = Math.max(0, scaledH - baseH);
6222
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5211
6223
  };
6224
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6225
+ if (!isUserZoomed) {
6226
+ scale = calculateFitScale(fitMode);
6227
+ applyTransform();
6228
+ }
6229
+ }) : null;
6230
+ resizeObserver?.observe(ctx.container);
5212
6231
  setTimeout(() => {
5213
- fitMode = "width";
5214
- scale = calculateFitScale("width");
6232
+ scale = calculateFitScale(fitMode);
5215
6233
  applyTransform();
5216
6234
  }, 60);
5217
- let currentPage = 1;
5218
- let totalPages = 1;
5219
- let slides = [];
5220
6235
  if (isPresentation) {
5221
6236
  wrapper.style.maxWidth = "960px";
5222
6237
  wrapper.style.aspectRatio = "16 / 9";
@@ -5317,24 +6332,9 @@ var OpenDocumentPlugin = class {
5317
6332
  wrapper.appendChild(page);
5318
6333
  slides.push(page);
5319
6334
  });
5320
- const pageIndicator = document.createElement("div");
5321
- pageIndicator.className = "fp-odt-page-indicator";
5322
- pageIndicator.style.position = "sticky";
5323
- pageIndicator.style.bottom = "16px";
5324
- pageIndicator.style.left = "50%";
5325
- pageIndicator.style.transform = "translateX(-50%)";
5326
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5327
- pageIndicator.style.color = "#fff";
5328
- pageIndicator.style.padding = "6px 12px";
5329
- pageIndicator.style.borderRadius = "16px";
5330
- pageIndicator.style.fontSize = "12px";
5331
- pageIndicator.style.zIndex = "100";
5332
- pageIndicator.style.display = "inline-block";
5333
- pageIndicator.style.width = "fit-content";
5334
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5335
- container.appendChild(pageIndicator);
5336
6335
  }
5337
6336
  const cleanup = () => {
6337
+ resizeObserver?.disconnect();
5338
6338
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5339
6339
  container.remove();
5340
6340
  ctx.container.innerHTML = "";
@@ -5346,10 +6346,6 @@ var OpenDocumentPlugin = class {
5346
6346
  slides.forEach((s, idx) => {
5347
6347
  s.style.display = idx === page - 1 ? "block" : "none";
5348
6348
  });
5349
- const indicator = container.querySelector(".fp-odt-page-indicator");
5350
- if (indicator) {
5351
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5352
- }
5353
6349
  container.scrollTop = 0;
5354
6350
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5355
6351
  };
@@ -5357,21 +6353,25 @@ var OpenDocumentPlugin = class {
5357
6353
  destroy: cleanup,
5358
6354
  isPresentation,
5359
6355
  zoomIn: () => {
6356
+ isUserZoomed = true;
5360
6357
  scale += 0.15;
5361
6358
  applyTransform();
5362
6359
  },
5363
6360
  zoomOut: () => {
6361
+ isUserZoomed = true;
5364
6362
  scale = Math.max(0.2, scale - 0.15);
5365
6363
  applyTransform();
5366
6364
  },
5367
6365
  getZoom: () => scale,
5368
6366
  setZoom: (level) => {
6367
+ isUserZoomed = true;
5369
6368
  scale = level;
5370
6369
  applyTransform();
5371
6370
  },
5372
6371
  fitToPage: () => {
5373
- fitMode = fitMode === "width" ? "page" : "width";
5374
- scale = calculateFitScale(fitMode);
6372
+ isUserZoomed = false;
6373
+ fitMode = "width";
6374
+ scale = calculateFitScale("width");
5375
6375
  rotation = 0;
5376
6376
  applyTransform();
5377
6377
  },
@@ -5387,23 +6387,45 @@ var OpenDocumentPlugin = class {
5387
6387
  getPageCount: () => totalPages,
5388
6388
  getCurrentPage: () => currentPage,
5389
6389
  getThumbnails: async () => {
5390
- if (!isPresentation) return [];
5391
- return slides.map((_, idx) => ({
5392
- index: idx,
5393
- label: `Slide ${idx + 1}`,
5394
- render: async (canvas) => {
5395
- const ctx2d = canvas.getContext("2d");
5396
- if (!ctx2d) return;
5397
- canvas.width = 160;
5398
- canvas.height = 90;
5399
- ctx2d.fillStyle = "#f8fafc";
5400
- ctx2d.fillRect(0, 0, 160, 90);
5401
- ctx2d.fillStyle = "#334155";
5402
- ctx2d.font = "bold 12px sans-serif";
5403
- ctx2d.textAlign = "center";
5404
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5405
- }
5406
- }));
6390
+ const count = totalPages || slides.length || 1;
6391
+ const items = [];
6392
+ for (let idx = 0; idx < count; idx++) {
6393
+ const itemIdx = idx;
6394
+ items.push({
6395
+ index: itemIdx,
6396
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6397
+ render: async (canvas) => {
6398
+ const ctx2d = canvas.getContext("2d");
6399
+ if (!ctx2d) return;
6400
+ if (isPresentation) {
6401
+ canvas.width = 160;
6402
+ canvas.height = 90;
6403
+ ctx2d.fillStyle = "#f8fafc";
6404
+ ctx2d.fillRect(0, 0, 160, 90);
6405
+ ctx2d.strokeStyle = "#cbd5e1";
6406
+ ctx2d.lineWidth = 1;
6407
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6408
+ ctx2d.fillStyle = "#334155";
6409
+ ctx2d.font = "bold 11px sans-serif";
6410
+ ctx2d.textAlign = "center";
6411
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6412
+ } else {
6413
+ canvas.width = 140;
6414
+ canvas.height = 180;
6415
+ ctx2d.fillStyle = "#ffffff";
6416
+ ctx2d.fillRect(0, 0, 140, 180);
6417
+ ctx2d.strokeStyle = "#cbd5e1";
6418
+ ctx2d.lineWidth = 1;
6419
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6420
+ ctx2d.fillStyle = "#64748b";
6421
+ ctx2d.font = "bold 10px sans-serif";
6422
+ ctx2d.textAlign = "center";
6423
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6424
+ }
6425
+ }
6426
+ });
6427
+ }
6428
+ return items;
5407
6429
  },
5408
6430
  download: () => {
5409
6431
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5508,23 +6530,24 @@ var OpenDocumentPlugin = class {
5508
6530
  case "h": {
5509
6531
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5510
6532
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5511
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6533
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5512
6534
  break;
5513
6535
  }
5514
6536
  case "p": {
5515
6537
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5516
6538
  const inner = this.renderSpans(node, styles, images);
5517
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6539
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6540
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5518
6541
  break;
5519
6542
  }
5520
6543
  case "list": {
5521
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6544
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5522
6545
  Array.from(node.children).forEach((c) => walk(c));
5523
6546
  html += "</ul>";
5524
6547
  break;
5525
6548
  }
5526
6549
  case "list-item": {
5527
- html += "<li>";
6550
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5528
6551
  Array.from(node.children).forEach((c) => walk(c));
5529
6552
  html += "</li>";
5530
6553
  break;
@@ -5650,28 +6673,26 @@ var DocPlugin = class {
5650
6673
  getToolbarActions(instance) {
5651
6674
  const totalPages = instance.getPageCount?.() ?? 1;
5652
6675
  const actions = [];
5653
- if (totalPages > 1) {
5654
- actions.push({
5655
- id: "page-nav",
5656
- icon: "",
5657
- label: "Page Navigation",
5658
- type: "page-nav",
5659
- group: "navigation",
5660
- value: instance.getCurrentPage?.() ?? 1,
5661
- max: totalPages,
5662
- execute: (action, page) => {
5663
- const cur = instance.getCurrentPage?.() ?? 1;
5664
- const max = instance.getPageCount?.() ?? 1;
5665
- if (action === "prev") {
5666
- if (cur > 1) instance.goToPage?.(cur - 1);
5667
- } else if (action === "next") {
5668
- if (cur < max) instance.goToPage?.(cur + 1);
5669
- } else if (typeof page === "number") {
5670
- instance.goToPage?.(page);
5671
- }
6676
+ actions.push({
6677
+ id: "page-nav",
6678
+ icon: "",
6679
+ label: "Page Navigation",
6680
+ type: "page-nav",
6681
+ group: "navigation",
6682
+ value: instance.getCurrentPage?.() ?? 1,
6683
+ max: totalPages,
6684
+ execute: (action, page) => {
6685
+ const cur = instance.getCurrentPage?.() ?? 1;
6686
+ const max = instance.getPageCount?.() ?? 1;
6687
+ if (action === "prev") {
6688
+ if (cur > 1) instance.goToPage?.(cur - 1);
6689
+ } else if (action === "next") {
6690
+ if (cur < max) instance.goToPage?.(cur + 1);
6691
+ } else if (typeof page === "number") {
6692
+ instance.goToPage?.(page);
5672
6693
  }
5673
- });
5674
- }
6694
+ }
6695
+ });
5675
6696
  actions.push(
5676
6697
  {
5677
6698
  id: "zoom-out",
@@ -5745,8 +6766,9 @@ var DocPlugin = class {
5745
6766
  container.className = "fp-doc-container";
5746
6767
  container.style.width = "100%";
5747
6768
  container.style.height = "100%";
5748
- container.style.overflow = "auto";
5749
- container.style.padding = "32px 16px";
6769
+ container.style.overflowX = "hidden";
6770
+ container.style.overflowY = "auto";
6771
+ container.style.padding = "16px 8px";
5750
6772
  container.style.backgroundColor = "#f1f5f9";
5751
6773
  container.style.display = "flex";
5752
6774
  container.style.justifyContent = "center";
@@ -5791,16 +6813,15 @@ var DocPlugin = class {
5791
6813
  const pageCard = document.createElement("div");
5792
6814
  pageCard.className = "fp-doc-page-card";
5793
6815
  pageCard.style.width = "816px";
5794
- pageCard.style.height = "1056px";
6816
+ pageCard.style.minHeight = "1056px";
5795
6817
  pageCard.style.backgroundColor = "#ffffff";
5796
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6818
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5797
6819
  pageCard.style.borderRadius = "4px";
5798
- pageCard.style.padding = "96px 72px";
6820
+ pageCard.style.padding = "72px 56px";
5799
6821
  pageCard.style.boxSizing = "border-box";
5800
- pageCard.style.overflow = "hidden";
5801
6822
  pageCard.style.display = i === 0 ? "block" : "none";
5802
6823
  pageCard.style.transformOrigin = "top center";
5803
- pageCard.style.transition = "transform 0.2s ease";
6824
+ pageCard.style.transition = "transform 0.15s ease";
5804
6825
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5805
6826
  pageCard.style.color = "#1e293b";
5806
6827
  if (isFallback && i === 0) {
@@ -5817,34 +6838,15 @@ var DocPlugin = class {
5817
6838
  container.appendChild(pageCard);
5818
6839
  pageCards.push(pageCard);
5819
6840
  }
5820
- let indicator = null;
5821
- if (totalPages > 1) {
5822
- indicator = document.createElement("div");
5823
- indicator.className = "fp-doc-page-indicator";
5824
- indicator.style.position = "fixed";
5825
- indicator.style.bottom = "16px";
5826
- indicator.style.left = "50%";
5827
- indicator.style.transform = "translateX(-50%)";
5828
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5829
- indicator.style.backdropFilter = "blur(8px)";
5830
- indicator.style.color = "#f8fafc";
5831
- indicator.style.fontSize = "12px";
5832
- indicator.style.fontWeight = "600";
5833
- indicator.style.padding = "5px 14px";
5834
- indicator.style.borderRadius = "20px";
5835
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5836
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5837
- indicator.style.zIndex = "10";
5838
- indicator.style.userSelect = "none";
5839
- indicator.style.pointerEvents = "none";
5840
- indicator.style.textAlign = "center";
5841
- container.appendChild(indicator);
5842
- }
5843
6841
  let rotation = 0;
5844
6842
  const applyTransform = () => {
5845
6843
  const activeCard = pageCards[currentPage - 1];
5846
6844
  if (activeCard) {
5847
6845
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6846
+ const baseH = activeCard.offsetHeight || 1056;
6847
+ const scaledH = baseH * scale;
6848
+ const extraH = Math.max(0, scaledH - baseH);
6849
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5848
6850
  }
5849
6851
  };
5850
6852
  const showPage = (pageNum) => {
@@ -5857,35 +6859,39 @@ var DocPlugin = class {
5857
6859
  card.style.display = "none";
5858
6860
  }
5859
6861
  });
5860
- if (indicator) {
5861
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5862
- }
5863
6862
  container.scrollTop = 0;
5864
6863
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5865
6864
  };
5866
6865
  if (totalPages > 1) {
5867
6866
  showPage(1);
5868
6867
  }
5869
- let fitMode = "width";
6868
+ let fitMode = ctx?.options?.fitMode || "width";
6869
+ let isUserZoomed = false;
5870
6870
  const calculateFitScale = (mode = fitMode) => {
5871
6871
  const elW = 816;
5872
6872
  const elH = 1056;
5873
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5874
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6873
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6874
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5875
6875
  const sW = availW / elW;
5876
6876
  const sH = availH / elH;
5877
6877
  if (mode === "page") {
5878
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6878
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5879
6879
  }
5880
- return Math.max(0.65, Math.min(1.05, sW));
6880
+ return Math.max(0.4, Math.min(3, sW));
5881
6881
  };
6882
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6883
+ if (!isUserZoomed) {
6884
+ scale = calculateFitScale(fitMode);
6885
+ applyTransform();
6886
+ }
6887
+ }) : null;
6888
+ resizeObserver?.observe(ctx.container);
5882
6889
  setTimeout(() => {
5883
- fitMode = "width";
5884
- scale = calculateFitScale("width");
6890
+ scale = calculateFitScale(fitMode);
5885
6891
  applyTransform();
5886
6892
  }, 60);
5887
6893
  const cleanup = () => {
5888
- indicator?.remove();
6894
+ resizeObserver?.disconnect();
5889
6895
  container.remove();
5890
6896
  ctx.container.innerHTML = "";
5891
6897
  };
@@ -5896,21 +6902,25 @@ var DocPlugin = class {
5896
6902
  getCurrentPage: () => currentPage,
5897
6903
  goToPage: (page) => showPage(page),
5898
6904
  zoomIn: () => {
6905
+ isUserZoomed = true;
5899
6906
  scale += 0.15;
5900
6907
  applyTransform();
5901
6908
  },
5902
6909
  zoomOut: () => {
6910
+ isUserZoomed = true;
5903
6911
  scale = Math.max(0.2, scale - 0.15);
5904
6912
  applyTransform();
5905
6913
  },
5906
6914
  getZoom: () => scale,
5907
6915
  setZoom: (level) => {
6916
+ isUserZoomed = true;
5908
6917
  scale = level;
5909
6918
  applyTransform();
5910
6919
  },
5911
6920
  fitToPage: () => {
5912
- fitMode = fitMode === "width" ? "page" : "width";
5913
- scale = calculateFitScale(fitMode);
6921
+ isUserZoomed = false;
6922
+ fitMode = "width";
6923
+ scale = calculateFitScale("width");
5914
6924
  rotation = 0;
5915
6925
  applyTransform();
5916
6926
  },
@@ -6287,25 +7297,31 @@ ${chartSvg}
6287
7297
  i++;
6288
7298
  continue;
6289
7299
  }
6290
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7300
+ if (/^\d{1,2}$/.test(line.trim())) {
7301
+ i++;
7302
+ continue;
7303
+ }
7304
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7305
+ 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));
7306
+ if (isTitleLike) {
6291
7307
  if (inList) {
6292
7308
  html += "</ul>";
6293
7309
  inList = false;
6294
7310
  }
6295
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
7311
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 18px 0 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
6296
7312
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6297
7313
  if (!inList) {
6298
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7314
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6299
7315
  inList = true;
6300
7316
  }
6301
7317
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6302
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
7318
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
6303
7319
  } else {
6304
7320
  if (inList) {
6305
7321
  html += "</ul>";
6306
7322
  inList = false;
6307
7323
  }
6308
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
7324
+ html += `<p style="line-height: 1.5; margin: 0 0 12px 0; font-size: 11pt; color: #1e293b; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
6309
7325
  }
6310
7326
  i++;
6311
7327
  }