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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.cjs CHANGED
@@ -42,6 +42,7 @@ function _interopNamespace(e) {
42
42
  var DOMPurify6__default = /*#__PURE__*/_interopDefault(DOMPurify6);
43
43
  var pdfjsLib__namespace = /*#__PURE__*/_interopNamespace(pdfjsLib);
44
44
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
45
+ var fflate__namespace = /*#__PURE__*/_interopNamespace(fflate);
45
46
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
46
47
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
47
48
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
@@ -527,8 +528,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
527
528
  var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
528
529
  var ICON_FAST_FORWARD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
529
530
  var ICON_REWIND = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
530
- var ICON_SPEED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
531
- var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`;
531
+ var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><line x1="3" y1="8" x2="21" y2="8"></line><circle cx="6" cy="5.5" r="0.75" fill="currentColor"></circle><circle cx="8.5" cy="5.5" r="0.75" fill="currentColor"></circle><polyline points="14 13 18 13 18 17"></polyline><line x1="11" y1="20" x2="18" y2="13"></line></svg>`;
532
532
  var ICON_MAP = {
533
533
  "zoom-in": ICON_ZOOM_IN,
534
534
  "zoom-out": ICON_ZOOM_OUT,
@@ -555,28 +555,177 @@ var ICON_MAP = {
555
555
  "forward-10": ICON_FAST_FORWARD,
556
556
  "rewind": ICON_REWIND,
557
557
  "replay-10": ICON_REWIND,
558
- "speed": ICON_SPEED,
559
558
  "open-window": ICON_EXTERNAL_WINDOW,
560
- "external-window": ICON_EXTERNAL_WINDOW
559
+ "external-window": ICON_EXTERNAL_WINDOW,
560
+ "openWindow": ICON_EXTERNAL_WINDOW,
561
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
561
562
  };
562
563
  var ToolbarController = class {
563
564
  el;
564
565
  toolbarEl;
565
566
  actions = [];
567
+ config = {};
566
568
  pageInputEl = null;
567
569
  pageLabelEl = null;
568
- constructor(container) {
570
+ prevPageBtn = null;
571
+ nextPageBtn = null;
572
+ constructor(container, config) {
569
573
  this.el = container;
574
+ if (config) this.config = { ...config };
570
575
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
571
576
  this.el.appendChild(this.toolbarEl);
572
577
  }
578
+ setConfig(config) {
579
+ this.config = { ...config };
580
+ this.render();
581
+ }
582
+ getConfig() {
583
+ return { ...this.config };
584
+ }
585
+ hideAction(actionId) {
586
+ this.config[actionId] = false;
587
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
588
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
589
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
590
+ this.config.fitPage = false;
591
+ this.config.fitToPage = false;
592
+ this.config.fitWidth = false;
593
+ }
594
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
595
+ if (actionId === "fullscreen") this.config.fullscreen = false;
596
+ if (actionId === "download") this.config.download = false;
597
+ if (actionId === "print") this.config.print = false;
598
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
599
+ this.config.openWindow = false;
600
+ this.config.openSeparateWindow = false;
601
+ }
602
+ if (actionId === "copy") this.config.copy = false;
603
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
604
+ this.config.pageNav = false;
605
+ this.config.pagination = false;
606
+ }
607
+ this.render();
608
+ }
609
+ showAction(actionId) {
610
+ this.config[actionId] = true;
611
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
612
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
613
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
614
+ this.config.fitPage = true;
615
+ this.config.fitToPage = true;
616
+ this.config.fitWidth = true;
617
+ }
618
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
619
+ if (actionId === "fullscreen") this.config.fullscreen = true;
620
+ if (actionId === "download") this.config.download = true;
621
+ if (actionId === "print") this.config.print = true;
622
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
623
+ this.config.openWindow = true;
624
+ this.config.openSeparateWindow = true;
625
+ }
626
+ if (actionId === "copy") this.config.copy = true;
627
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
628
+ this.config.pageNav = true;
629
+ this.config.pagination = true;
630
+ }
631
+ this.render();
632
+ }
633
+ normalizeActionId(actionId) {
634
+ if (actionId === "zoomIn") return "zoom-in";
635
+ if (actionId === "zoomOut") return "zoom-out";
636
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
637
+ if (actionId === "rotateCW") return "rotate-cw";
638
+ if (actionId === "rotateCCW") return "rotate-ccw";
639
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
640
+ if (actionId === "pageNav") return "page-nav";
641
+ return actionId;
642
+ }
643
+ enableAction(actionId) {
644
+ const norm = this.normalizeActionId(actionId);
645
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
646
+ if (btn) {
647
+ btn.disabled = false;
648
+ btn.classList.remove("disabled");
649
+ }
650
+ }
651
+ disableAction(actionId) {
652
+ const norm = this.normalizeActionId(actionId);
653
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
654
+ if (btn) {
655
+ btn.disabled = true;
656
+ btn.classList.add("disabled");
657
+ }
658
+ }
659
+ setActionActive(actionId, active) {
660
+ const norm = this.normalizeActionId(actionId);
661
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
662
+ if (btn) {
663
+ if (active) {
664
+ btn.classList.add("active");
665
+ } else {
666
+ btn.classList.remove("active");
667
+ }
668
+ }
669
+ }
670
+ isActionEnabled(action) {
671
+ const c = this.config;
672
+ const id = action.id;
673
+ if (c[id] === false) return false;
674
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
675
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
676
+ if ((id === "fit-page" || id === "fit-width" || id === "fit-slide") && (c.fitPage === false || c.fitToPage === false || c.fitWidth === false || c["fit-page"] === false || c["fit-width"] === false)) {
677
+ return false;
678
+ }
679
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
680
+ return false;
681
+ }
682
+ if (id === "fullscreen" && c.fullscreen === false) return false;
683
+ if (id === "download" && c.download === false) return false;
684
+ if (id === "print" && c.print === false) return false;
685
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
686
+ return false;
687
+ }
688
+ if (id === "copy" && c.copy === false) return false;
689
+ if (id === "thumbnails" && c.thumbnails === false) return false;
690
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
691
+ return false;
692
+ }
693
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
694
+ return false;
695
+ }
696
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
697
+ return false;
698
+ }
699
+ if (id === "play" && c.play === false) return false;
700
+ if (id === "pause" && c.pause === false) return false;
701
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
702
+ return false;
703
+ }
704
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
705
+ return false;
706
+ }
707
+ if (id === "speed" && c.speed === false) return false;
708
+ return true;
709
+ }
573
710
  setPage(page, max) {
574
711
  if (this.pageInputEl) {
575
712
  this.pageInputEl.value = page.toString();
713
+ if (max !== void 0) {
714
+ this.pageInputEl.max = max.toString();
715
+ }
576
716
  }
577
717
  if (max !== void 0 && this.pageLabelEl) {
578
718
  this.pageLabelEl.textContent = ` / ${max}`;
579
719
  }
720
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
721
+ if (this.prevPageBtn) {
722
+ this.prevPageBtn.disabled = page <= 1;
723
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
724
+ }
725
+ if (this.nextPageBtn) {
726
+ this.nextPageBtn.disabled = page >= currentMax;
727
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
728
+ }
580
729
  }
581
730
  update(actions) {
582
731
  this.actions = actions;
@@ -599,8 +748,9 @@ var ToolbarController = class {
599
748
  view: [],
600
749
  actions: []
601
750
  };
602
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
603
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
751
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
752
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
753
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
604
754
  for (const action of effectiveActions) {
605
755
  if (groups[action.group]) {
606
756
  groups[action.group].push(action);
@@ -660,6 +810,13 @@ var ToolbarController = class {
660
810
  action.execute("go", val);
661
811
  });
662
812
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
813
+ const curVal = action.value ?? 1;
814
+ prevBtn.disabled = curVal <= 1;
815
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
816
+ nextBtn.disabled = curVal >= max;
817
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
818
+ this.prevPageBtn = prevBtn;
819
+ this.nextPageBtn = nextBtn;
663
820
  this.pageInputEl = input;
664
821
  this.pageLabelEl = label;
665
822
  groupEl.appendChild(prevBtn);
@@ -700,9 +857,11 @@ var ToolbarController = class {
700
857
  type: "button",
701
858
  "data-action-id": id
702
859
  });
703
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
704
- if (svg) {
705
- btn.innerHTML = sanitizeSVG(svg);
860
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
861
+ if (internalSvg) {
862
+ btn.innerHTML = internalSvg;
863
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
864
+ btn.innerHTML = sanitizeSVG(iconHtml);
706
865
  } else {
707
866
  btn.textContent = title || id;
708
867
  }
@@ -713,25 +872,63 @@ var ToolbarController = class {
713
872
  var ThumbnailPanel = class {
714
873
  el;
715
874
  panelEl;
875
+ listEl;
876
+ headerEl;
716
877
  thumbnails = [];
717
878
  onSelect;
879
+ onToggleCallback;
718
880
  activeIndex = 0;
719
- constructor(container) {
881
+ observer;
882
+ renderedIndices = /* @__PURE__ */ new Set();
883
+ constructor(container, onToggle) {
720
884
  this.el = container;
885
+ this.onToggleCallback = onToggle;
721
886
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
887
+ this.panelEl.style.width = "0px";
888
+ this.panelEl.style.minWidth = "0px";
889
+ this.panelEl.style.opacity = "0";
890
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
891
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
892
+ const closeBtn = createElement("button", {
893
+ className: "fp-thumbnail-close-btn",
894
+ title: "Close Thumbnails",
895
+ type: "button"
896
+ });
897
+ closeBtn.innerHTML = ICON_CLOSE;
898
+ closeBtn.addEventListener("click", (e) => {
899
+ e.stopPropagation();
900
+ this.hide();
901
+ });
902
+ this.headerEl.appendChild(title);
903
+ this.headerEl.appendChild(closeBtn);
904
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
905
+ this.panelEl.appendChild(this.headerEl);
906
+ this.panelEl.appendChild(this.listEl);
722
907
  this.el.appendChild(this.panelEl);
723
908
  }
909
+ isOpen() {
910
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
911
+ }
724
912
  update(thumbnails, onSelect) {
725
913
  this.thumbnails = thumbnails;
726
914
  this.onSelect = onSelect;
915
+ this.renderedIndices.clear();
916
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
917
+ if (titleEl) {
918
+ titleEl.textContent = `Pages (${thumbnails.length})`;
919
+ }
727
920
  this.render();
921
+ if (this.isOpen()) {
922
+ this.renderAllVisible();
923
+ }
728
924
  }
729
925
  setActive(index) {
730
926
  this.activeIndex = index;
731
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
927
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
732
928
  items.forEach((item, i) => {
733
929
  if (i === index) {
734
930
  item.classList.add("active");
931
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
735
932
  } else {
736
933
  item.classList.remove("active");
737
934
  }
@@ -739,37 +936,102 @@ var ThumbnailPanel = class {
739
936
  }
740
937
  show() {
741
938
  this.panelEl.classList.remove("hidden");
939
+ this.panelEl.style.width = "200px";
940
+ this.panelEl.style.minWidth = "200px";
941
+ this.panelEl.style.opacity = "1";
942
+ this.panelEl.style.display = "flex";
943
+ this.onToggleCallback?.(true);
944
+ this.renderAllVisible();
742
945
  }
743
946
  hide() {
744
947
  this.panelEl.classList.add("hidden");
948
+ this.panelEl.style.width = "0px";
949
+ this.panelEl.style.minWidth = "0px";
950
+ this.panelEl.style.opacity = "0";
951
+ this.onToggleCallback?.(false);
745
952
  }
746
953
  toggle() {
747
- this.panelEl.classList.toggle("hidden");
954
+ if (this.isOpen()) {
955
+ this.hide();
956
+ } else {
957
+ this.show();
958
+ }
748
959
  }
749
960
  destroy() {
961
+ if (this.observer) {
962
+ this.observer.disconnect();
963
+ this.observer = void 0;
964
+ }
965
+ this.renderedIndices.clear();
750
966
  this.el.innerHTML = "";
751
967
  }
752
- async render() {
753
- this.panelEl.innerHTML = "";
968
+ renderAllVisible() {
969
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
970
+ items.forEach((item) => {
971
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
972
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
973
+ this.renderThumbnailItem(idx, item);
974
+ }
975
+ });
976
+ }
977
+ renderThumbnailItem(idx, itemEl) {
978
+ if (this.renderedIndices.has(idx)) return;
979
+ this.renderedIndices.add(idx);
980
+ const canvas = itemEl.querySelector("canvas");
981
+ const thumb = this.thumbnails[idx];
982
+ if (canvas && thumb) {
983
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
984
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
985
+ });
986
+ }
987
+ }
988
+ render() {
989
+ if (this.observer) {
990
+ this.observer.disconnect();
991
+ }
992
+ this.listEl.innerHTML = "";
993
+ const useObserver = typeof IntersectionObserver !== "undefined";
994
+ if (useObserver) {
995
+ this.observer = new IntersectionObserver(
996
+ (entries) => {
997
+ for (const entry of entries) {
998
+ if (entry.isIntersecting) {
999
+ const target = entry.target;
1000
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
1001
+ if (idx >= 0) {
1002
+ this.renderThumbnailItem(idx, target);
1003
+ }
1004
+ }
1005
+ }
1006
+ },
1007
+ {
1008
+ root: this.listEl,
1009
+ rootMargin: "120px 0px 120px 0px"
1010
+ }
1011
+ );
1012
+ }
754
1013
  for (let i = 0; i < this.thumbnails.length; i++) {
755
1014
  const thumb = this.thumbnails[i];
756
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
757
- if (i === this.activeIndex) {
758
- itemEl.classList.add("active");
759
- }
1015
+ const itemEl = createElement("div", {
1016
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
1017
+ });
1018
+ itemEl.dataset.thumbIndex = i.toString();
760
1019
  itemEl.addEventListener("click", () => {
761
1020
  this.setActive(i);
762
1021
  this.onSelect?.(i);
763
1022
  });
764
- const canvas = createElement("canvas", { width: "150", height: "200" });
765
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
766
- itemEl.appendChild(canvas);
1023
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
1024
+ const canvas = createElement("canvas", { width: "130", height: "170" });
1025
+ canvasWrapper.appendChild(canvas);
1026
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
1027
+ itemEl.appendChild(canvasWrapper);
767
1028
  itemEl.appendChild(label);
768
- this.panelEl.appendChild(itemEl);
769
- try {
770
- await thumb.render(canvas);
771
- } catch (err) {
772
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
1029
+ this.listEl.appendChild(itemEl);
1030
+ if (this.observer) {
1031
+ this.observer.observe(itemEl);
1032
+ }
1033
+ if (i < 4) {
1034
+ this.renderThumbnailItem(i, itemEl);
773
1035
  }
774
1036
  }
775
1037
  }
@@ -790,6 +1052,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
790
1052
  currentOptions = {};
791
1053
  resizeObserver = null;
792
1054
  fullscreenHandler = null;
1055
+ titleBarEl = null;
793
1056
  /**
794
1057
  * Register a preview plugin.
795
1058
  */
@@ -826,6 +1089,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
826
1089
  }
827
1090
  this.currentBuffer = buffer.slice(0);
828
1091
  this.currentMetadata = metadata;
1092
+ this.updateTitleBar(options, metadata);
829
1093
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
830
1094
  const fileInfo = { metadata, buffer };
831
1095
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -849,15 +1113,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
849
1113
  if (event === "page-change" && payload && typeof payload.page === "number") {
850
1114
  const total = payload.total ?? payload.totalPages;
851
1115
  this.toolbar?.setPage(payload.page, total);
1116
+ this.thumbnailPanel?.setActive(payload.page - 1);
852
1117
  }
853
1118
  this.eventEmitter.emit(event, payload);
854
- }
1119
+ },
1120
+ toggleThumbnails: () => this.toggleThumbnails()
855
1121
  });
856
1122
  this.activeInstance = instance;
857
1123
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1124
+ instance.toggleThumbnails = () => this.toggleThumbnails();
858
1125
  this.hideLoading();
859
1126
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
860
- if (options.showToolbar !== false && this.toolbar) {
1127
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1128
+ if (isToolbarVisible && this.toolbar) {
1129
+ const toolbarConfig = this.extractToolbarConfig(options);
1130
+ this.toolbar.setConfig(toolbarConfig);
861
1131
  const actions = matchedPlugin.getToolbarActions(instance);
862
1132
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
863
1133
  if (!hasFullscreen && this.wrapperEl) {
@@ -907,6 +1177,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
907
1177
  }
908
1178
  });
909
1179
  }
1180
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1181
+ if (thumbAction) {
1182
+ thumbAction.execute = () => {
1183
+ this.toggleThumbnails();
1184
+ };
1185
+ }
910
1186
  this.toolbar.update(actions);
911
1187
  this.toolbar.show();
912
1188
  }
@@ -991,7 +1267,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
991
1267
  if (targetUrl) {
992
1268
  const newWin2 = window.open(targetUrl, "_blank");
993
1269
  if (!newWin2) {
994
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1270
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
995
1271
  return null;
996
1272
  }
997
1273
  return newWin2;
@@ -999,7 +1275,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
999
1275
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1000
1276
  const newWin = window.open("", "_blank");
1001
1277
  if (!newWin) {
1002
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1278
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1003
1279
  return null;
1004
1280
  }
1005
1281
  newWin.document.title = title;
@@ -1076,6 +1352,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1076
1352
  this.currentBuffer = null;
1077
1353
  this.currentMetadata = null;
1078
1354
  this.currentContainer = null;
1355
+ this.titleBarEl = null;
1079
1356
  }
1080
1357
  /**
1081
1358
  * Get the currently active preview instance.
@@ -1083,7 +1360,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1083
1360
  getInstance() {
1084
1361
  return this.activeInstance;
1085
1362
  }
1363
+ // --- Toolbar Configuration & Feature Toggles ---
1364
+ /**
1365
+ * Configure toolbar features dynamically through options or methods.
1366
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1367
+ */
1368
+ setToolbarConfig(config) {
1369
+ this.toolbar?.setConfig(config);
1370
+ }
1371
+ /**
1372
+ * Get current toolbar configuration.
1373
+ */
1374
+ getToolbarConfig() {
1375
+ return this.toolbar?.getConfig() ?? {};
1376
+ }
1377
+ /**
1378
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1379
+ */
1380
+ hideToolbarAction(actionId) {
1381
+ this.toolbar?.hideAction(actionId);
1382
+ }
1383
+ /**
1384
+ * Show a specific toolbar action by ID or alias.
1385
+ */
1386
+ showToolbarAction(actionId) {
1387
+ this.toolbar?.showAction(actionId);
1388
+ }
1389
+ /**
1390
+ * Enable a specific toolbar action button.
1391
+ */
1392
+ enableToolbarAction(actionId) {
1393
+ this.toolbar?.enableAction(actionId);
1394
+ }
1395
+ /**
1396
+ * Disable a specific toolbar action button.
1397
+ */
1398
+ disableToolbarAction(actionId) {
1399
+ this.toolbar?.disableAction(actionId);
1400
+ }
1401
+ // --- Programmatic Toolbar Action Methods ---
1402
+ /**
1403
+ * Fit document to page so it fills the frame width with minimal margins.
1404
+ */
1405
+ fitToPage() {
1406
+ this.activeInstance?.fitToPage?.();
1407
+ }
1408
+ /**
1409
+ * Zoom in.
1410
+ */
1411
+ zoomIn() {
1412
+ this.activeInstance?.zoomIn?.();
1413
+ }
1414
+ /**
1415
+ * Zoom out.
1416
+ */
1417
+ zoomOut() {
1418
+ this.activeInstance?.zoomOut?.();
1419
+ }
1420
+ /**
1421
+ * Set specific zoom level.
1422
+ */
1423
+ setZoom(level) {
1424
+ this.activeInstance?.setZoom?.(level);
1425
+ }
1426
+ /**
1427
+ * Get current zoom level.
1428
+ */
1429
+ getZoom() {
1430
+ return this.activeInstance?.getZoom?.() ?? 1;
1431
+ }
1432
+ /**
1433
+ * Rotate 90 degrees clockwise.
1434
+ */
1435
+ rotateCW() {
1436
+ this.activeInstance?.rotateCW?.();
1437
+ }
1438
+ /**
1439
+ * Rotate 90 degrees counter-clockwise.
1440
+ */
1441
+ rotateCCW() {
1442
+ this.activeInstance?.rotateCCW?.();
1443
+ }
1444
+ /**
1445
+ * Navigate to a specific page number.
1446
+ */
1447
+ goToPage(page) {
1448
+ this.activeInstance?.goToPage?.(page);
1449
+ this.toolbar?.setPage(page);
1450
+ }
1451
+ /**
1452
+ * Navigate to next page.
1453
+ */
1454
+ nextPage() {
1455
+ const cur = this.getCurrentPage();
1456
+ const max = this.getPageCount();
1457
+ if (cur < max) {
1458
+ this.goToPage(cur + 1);
1459
+ }
1460
+ }
1461
+ /**
1462
+ * Navigate to previous page.
1463
+ */
1464
+ prevPage() {
1465
+ const cur = this.getCurrentPage();
1466
+ if (cur > 1) {
1467
+ this.goToPage(cur - 1);
1468
+ }
1469
+ }
1470
+ /**
1471
+ * Get total page count.
1472
+ */
1473
+ getPageCount() {
1474
+ return this.activeInstance?.getPageCount?.() ?? 1;
1475
+ }
1476
+ /**
1477
+ * Get current page number.
1478
+ */
1479
+ getCurrentPage() {
1480
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1481
+ }
1482
+ /**
1483
+ * Download the current document.
1484
+ */
1485
+ download() {
1486
+ this.activeInstance?.download?.();
1487
+ }
1488
+ /**
1489
+ * Print the current document.
1490
+ */
1491
+ print() {
1492
+ this.activeInstance?.print?.();
1493
+ }
1494
+ /**
1495
+ * Toggle fullscreen mode.
1496
+ */
1497
+ toggleFullscreen() {
1498
+ try {
1499
+ if (!document.fullscreenElement) {
1500
+ this.wrapperEl?.requestFullscreen?.();
1501
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1502
+ } else {
1503
+ document.exitFullscreen?.();
1504
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1505
+ }
1506
+ } catch {
1507
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1508
+ }
1509
+ setTimeout(() => {
1510
+ this.activeInstance?.fitToPage?.();
1511
+ }, 120);
1512
+ }
1513
+ /**
1514
+ * Toggle thumbnails sidebar panel.
1515
+ */
1516
+ toggleThumbnails() {
1517
+ if (!this.thumbnailPanel) return;
1518
+ this.thumbnailPanel.toggle();
1519
+ const isOpen = this.thumbnailPanel.isOpen();
1520
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1521
+ setTimeout(() => {
1522
+ this.activeInstance?.fitToPage?.();
1523
+ }, 260);
1524
+ }
1525
+ /**
1526
+ * Set whether to display the file name / title bar above the toolbar.
1527
+ */
1528
+ setShowFileName(show) {
1529
+ if (this.currentOptions) {
1530
+ this.currentOptions.showFileName = show;
1531
+ }
1532
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1533
+ }
1534
+ /**
1535
+ * Set or override the displayed file name in the preview panel.
1536
+ */
1537
+ setFileName(name) {
1538
+ if (this.currentOptions) {
1539
+ this.currentOptions.fileName = name;
1540
+ }
1541
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1542
+ }
1086
1543
  // --- Private methods ---
1544
+ updateTitleBar(options, metadata) {
1545
+ if (!this.titleBarEl) return;
1546
+ const show = options?.showFileName !== false;
1547
+ if (!show) {
1548
+ this.titleBarEl.style.display = "none";
1549
+ return;
1550
+ }
1551
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1552
+ if (!name) {
1553
+ this.titleBarEl.style.display = "none";
1554
+ return;
1555
+ }
1556
+ this.titleBarEl.style.display = "flex";
1557
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1558
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1559
+ this.titleBarEl.innerHTML = `
1560
+ <div class="fp-titlebar-info">
1561
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1562
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1563
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1564
+ </div>
1565
+ `;
1566
+ }
1567
+ extractToolbarConfig(options) {
1568
+ if (!options) return {};
1569
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1570
+ return {
1571
+ ...options,
1572
+ ...nested
1573
+ };
1574
+ }
1087
1575
  abort() {
1088
1576
  if (this.abortController) {
1089
1577
  this.abortController.abort();
@@ -1126,17 +1614,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1126
1614
  bodyEl.className = "fp-body";
1127
1615
  bodyEl.appendChild(thumbnailEl);
1128
1616
  bodyEl.appendChild(this.contentEl);
1617
+ const titleBarEl = document.createElement("div");
1618
+ titleBarEl.className = "fp-titlebar";
1619
+ this.titleBarEl = titleBarEl;
1620
+ this.updateTitleBar(options, this.currentMetadata);
1129
1621
  if (toolbarPos === "top") {
1622
+ this.wrapperEl.appendChild(titleBarEl);
1130
1623
  this.wrapperEl.appendChild(toolbarEl);
1131
1624
  this.wrapperEl.appendChild(bodyEl);
1132
1625
  } else {
1626
+ this.wrapperEl.appendChild(titleBarEl);
1133
1627
  this.wrapperEl.appendChild(bodyEl);
1134
1628
  this.wrapperEl.appendChild(toolbarEl);
1135
1629
  }
1136
1630
  container.innerHTML = "";
1137
1631
  container.appendChild(this.wrapperEl);
1138
- this.toolbar = new ToolbarController(toolbarEl);
1139
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1632
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1633
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1634
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1635
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1636
+ setTimeout(() => {
1637
+ this.activeInstance?.fitToPage?.();
1638
+ }, 260);
1639
+ });
1140
1640
  this.setupKeyboardShortcuts();
1141
1641
  this.setupDragAndDrop(container, options);
1142
1642
  this.setupResizeAndFullscreenListeners();
@@ -1526,6 +2026,14 @@ var PdfPlugin = class {
1526
2026
  group: "zoom",
1527
2027
  execute: () => instance.fitToPage?.()
1528
2028
  },
2029
+ {
2030
+ id: "fit-width",
2031
+ icon: "fit-width",
2032
+ label: "Fit to Width",
2033
+ type: "button",
2034
+ group: "zoom",
2035
+ execute: () => instance.fitToWidth?.()
2036
+ },
1529
2037
  {
1530
2038
  id: "rotate-cw",
1531
2039
  icon: "rotate-cw",
@@ -1534,6 +2042,14 @@ var PdfPlugin = class {
1534
2042
  group: "view",
1535
2043
  execute: () => instance.rotateCW?.()
1536
2044
  },
2045
+ {
2046
+ id: "rotate-ccw",
2047
+ icon: "rotate-ccw",
2048
+ label: "Rotate Counter-Clockwise",
2049
+ type: "button",
2050
+ group: "view",
2051
+ execute: () => instance.rotateCCW?.()
2052
+ },
1537
2053
  {
1538
2054
  id: "download",
1539
2055
  icon: "download",
@@ -1565,12 +2081,13 @@ var PdfPlugin = class {
1565
2081
  container.className = "fp-pdf-container";
1566
2082
  container.style.width = "100%";
1567
2083
  container.style.height = "100%";
1568
- container.style.overflow = "auto";
2084
+ container.style.overflowX = "hidden";
2085
+ container.style.overflowY = "auto";
1569
2086
  container.style.display = "flex";
1570
2087
  container.style.flexDirection = "column";
1571
2088
  container.style.alignItems = "center";
1572
2089
  container.style.justifyContent = "flex-start";
1573
- container.style.padding = "20px 16px";
2090
+ container.style.padding = "16px 8px";
1574
2091
  container.style.backgroundColor = "#0f172a";
1575
2092
  container.style.boxSizing = "border-box";
1576
2093
  container.style.position = "relative";
@@ -1581,30 +2098,12 @@ var PdfPlugin = class {
1581
2098
  pageCard.style.borderRadius = "4px";
1582
2099
  pageCard.style.overflow = "hidden";
1583
2100
  pageCard.style.lineHeight = "0";
1584
- pageCard.style.transition = "transform 0.15s ease";
1585
2101
  pageCard.style.position = "relative";
1586
2102
  pageCard.style.flexShrink = "0";
2103
+ pageCard.style.transition = "box-shadow 0.2s ease";
1587
2104
  let canvas = document.createElement("canvas");
1588
2105
  pageCard.appendChild(canvas);
1589
2106
  container.appendChild(pageCard);
1590
- const indicator = document.createElement("div");
1591
- indicator.className = "fp-pdf-page-indicator";
1592
- indicator.style.position = "sticky";
1593
- indicator.style.bottom = "16px";
1594
- indicator.style.marginTop = "16px";
1595
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1596
- indicator.style.backdropFilter = "blur(8px)";
1597
- indicator.style.color = "#f8fafc";
1598
- indicator.style.fontSize = "12px";
1599
- indicator.style.fontWeight = "600";
1600
- indicator.style.padding = "5px 14px";
1601
- indicator.style.borderRadius = "20px";
1602
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1603
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1604
- indicator.style.zIndex = "10";
1605
- indicator.style.userSelect = "none";
1606
- indicator.style.pointerEvents = "none";
1607
- container.appendChild(indicator);
1608
2107
  ctx.container.appendChild(container);
1609
2108
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1610
2109
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1620,8 +2119,9 @@ var PdfPlugin = class {
1620
2119
  let currentPage = 1;
1621
2120
  let zoomScale = 1;
1622
2121
  let rotation = 0;
1623
- let fitMode = "page";
2122
+ let fitMode = ctx?.options?.fitMode || "width";
1624
2123
  let currentRenderTask = null;
2124
+ let textLayerDiv = null;
1625
2125
  const renderPage = async (pageNum) => {
1626
2126
  if (currentRenderTask) {
1627
2127
  try {
@@ -1633,30 +2133,37 @@ var PdfPlugin = class {
1633
2133
  const newCanvas = document.createElement("canvas");
1634
2134
  pageCard.replaceChild(newCanvas, canvas);
1635
2135
  canvas = newCanvas;
2136
+ if (textLayerDiv) {
2137
+ textLayerDiv.remove();
2138
+ textLayerDiv = null;
2139
+ }
1636
2140
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1637
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1638
2141
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1639
2142
  const page = await pdfDoc.getPage(currentPage);
1640
2143
  const containerWidth = container.clientWidth || 900;
1641
2144
  const containerHeight = container.clientHeight || 700;
1642
2145
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1643
- const availWidth = Math.max(320, containerWidth - 48);
1644
- const availHeight = Math.max(550, containerHeight - 88);
2146
+ const availWidth = Math.max(280, containerWidth - 36);
2147
+ const availHeight = Math.max(280, containerHeight - 32);
1645
2148
  const scaleW = availWidth / unscaledVp.width;
1646
2149
  const scaleH = availHeight / unscaledVp.height;
1647
2150
  let fitScale;
1648
2151
  if (fitMode === "page") {
1649
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2152
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1650
2153
  } else {
1651
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2154
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1652
2155
  }
1653
2156
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1654
2157
  const pixelRatio = window.devicePixelRatio || 1;
1655
2158
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2159
+ const displayWidth = Math.floor(viewport.width);
2160
+ const displayHeight = Math.floor(viewport.height);
1656
2161
  canvas.width = Math.floor(viewport.width * pixelRatio);
1657
2162
  canvas.height = Math.floor(viewport.height * pixelRatio);
1658
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1659
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2163
+ canvas.style.width = `${displayWidth}px`;
2164
+ canvas.style.height = `${displayHeight}px`;
2165
+ pageCard.style.width = `${displayWidth}px`;
2166
+ pageCard.style.height = `${displayHeight}px`;
1660
2167
  const canvasCtx = canvas.getContext("2d");
1661
2168
  if (!canvasCtx) return;
1662
2169
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1670,9 +2177,31 @@ var PdfPlugin = class {
1670
2177
  if (err?.name !== "RenderingCancelledException") {
1671
2178
  console.warn("[PdfPlugin] Page render warning:", err);
1672
2179
  }
2180
+ return;
1673
2181
  } finally {
1674
2182
  currentRenderTask = null;
1675
2183
  }
2184
+ try {
2185
+ textLayerDiv = document.createElement("div");
2186
+ textLayerDiv.className = "textLayer";
2187
+ textLayerDiv.style.width = `${displayWidth}px`;
2188
+ textLayerDiv.style.height = `${displayHeight}px`;
2189
+ textLayerDiv.style.position = "absolute";
2190
+ textLayerDiv.style.top = "0";
2191
+ textLayerDiv.style.left = "0";
2192
+ textLayerDiv.style.lineHeight = "1";
2193
+ pageCard.appendChild(textLayerDiv);
2194
+ if (pdfjsLib__namespace.TextLayer) {
2195
+ const textLayer = new pdfjsLib__namespace.TextLayer({
2196
+ textContentSource: page.streamTextContent(),
2197
+ container: textLayerDiv,
2198
+ viewport
2199
+ });
2200
+ await textLayer.render();
2201
+ }
2202
+ } catch (err) {
2203
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2204
+ }
1676
2205
  };
1677
2206
  renderPage(1);
1678
2207
  let resizeTimer = null;
@@ -1682,11 +2211,48 @@ var PdfPlugin = class {
1682
2211
  if (Math.abs(zoomScale - 1) < 0.05) {
1683
2212
  renderPage(currentPage);
1684
2213
  }
1685
- }, 150);
2214
+ }, 120);
1686
2215
  });
1687
2216
  resizeObserver.observe(container);
2217
+ const onWheel = (e) => {
2218
+ if (e.ctrlKey || e.metaKey) {
2219
+ e.preventDefault();
2220
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2221
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2222
+ renderPage(currentPage);
2223
+ }
2224
+ };
2225
+ container.addEventListener("wheel", onWheel, { passive: false });
2226
+ const onKeyDown = (e) => {
2227
+ const tag = e.target?.tagName?.toLowerCase();
2228
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2229
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2230
+ if (currentPage < totalPages) {
2231
+ e.preventDefault();
2232
+ container.scrollTop = 0;
2233
+ renderPage(currentPage + 1);
2234
+ }
2235
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2236
+ if (currentPage > 1) {
2237
+ e.preventDefault();
2238
+ container.scrollTop = 0;
2239
+ renderPage(currentPage - 1);
2240
+ }
2241
+ } else if (e.key === "Home") {
2242
+ e.preventDefault();
2243
+ container.scrollTop = 0;
2244
+ renderPage(1);
2245
+ } else if (e.key === "End") {
2246
+ e.preventDefault();
2247
+ container.scrollTop = 0;
2248
+ renderPage(totalPages);
2249
+ }
2250
+ };
2251
+ window.addEventListener("keydown", onKeyDown);
1688
2252
  const cleanup = () => {
1689
2253
  resizeObserver.disconnect();
2254
+ window.removeEventListener("keydown", onKeyDown);
2255
+ container.removeEventListener("wheel", onWheel);
1690
2256
  if (resizeTimer) clearTimeout(resizeTimer);
1691
2257
  if (currentRenderTask) {
1692
2258
  try {
@@ -1705,22 +2271,26 @@ var PdfPlugin = class {
1705
2271
  const instance = {
1706
2272
  destroy: cleanup,
1707
2273
  zoomIn: () => {
1708
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2274
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1709
2275
  renderPage(currentPage);
1710
2276
  },
1711
2277
  zoomOut: () => {
1712
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2278
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1713
2279
  renderPage(currentPage);
1714
2280
  },
1715
2281
  getZoom: () => zoomScale,
1716
2282
  setZoom: (level) => {
1717
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2283
+ zoomScale = Math.max(0.25, Math.min(4, level));
1718
2284
  renderPage(currentPage);
1719
2285
  },
1720
2286
  fitToPage: () => {
1721
- fitMode = fitMode === "page" ? "width" : "page";
2287
+ fitMode = "page";
2288
+ zoomScale = 1;
2289
+ renderPage(currentPage);
2290
+ },
2291
+ fitToWidth: () => {
2292
+ fitMode = "width";
1722
2293
  zoomScale = 1;
1723
- rotation = 0;
1724
2294
  renderPage(currentPage);
1725
2295
  },
1726
2296
  rotateCW: () => {
@@ -1738,6 +2308,9 @@ var PdfPlugin = class {
1738
2308
  container.scrollTop = 0;
1739
2309
  renderPage(page);
1740
2310
  },
2311
+ toggleThumbnails: () => {
2312
+ ctx?.toggleThumbnails?.();
2313
+ },
1741
2314
  download: () => {
1742
2315
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1743
2316
  const url = URL.createObjectURL(blob);
@@ -1771,7 +2344,7 @@ var PdfPlugin = class {
1771
2344
  },
1772
2345
  getThumbnails: async () => {
1773
2346
  const thumbnails = [];
1774
- const count = Math.min(totalPages, 50);
2347
+ const count = totalPages;
1775
2348
  for (let i = 1; i <= count; i++) {
1776
2349
  thumbnails.push({
1777
2350
  index: i,
@@ -1779,9 +2352,10 @@ var PdfPlugin = class {
1779
2352
  render: async (thumbCanvas) => {
1780
2353
  try {
1781
2354
  const p = await pdfDoc.getPage(i);
1782
- const baseVp = p.getViewport({ scale: 1 });
1783
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1784
- const thumbVp = p.getViewport({ scale: thumbScale });
2355
+ const baseVp = p.getViewport({ scale: 1, rotation });
2356
+ const targetW = thumbCanvas.width || 130;
2357
+ const thumbScale = targetW / baseVp.width;
2358
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1785
2359
  thumbCanvas.height = Math.floor(thumbVp.height);
1786
2360
  const tCtx = thumbCanvas.getContext("2d");
1787
2361
  if (tCtx) {
@@ -2125,28 +2699,26 @@ var DocxPlugin = class {
2125
2699
  getToolbarActions(instance) {
2126
2700
  const totalPages = instance.getPageCount?.() ?? 1;
2127
2701
  const actions = [];
2128
- if (totalPages > 1) {
2129
- actions.push({
2130
- id: "page-nav",
2131
- icon: "",
2132
- label: "Page Navigation",
2133
- type: "page-nav",
2134
- group: "navigation",
2135
- value: instance.getCurrentPage?.() ?? 1,
2136
- max: totalPages,
2137
- execute: (action, page) => {
2138
- const cur = instance.getCurrentPage?.() ?? 1;
2139
- const max = instance.getPageCount?.() ?? 1;
2140
- if (action === "prev") {
2141
- if (cur > 1) instance.goToPage?.(cur - 1);
2142
- } else if (action === "next") {
2143
- if (cur < max) instance.goToPage?.(cur + 1);
2144
- } else if (typeof page === "number") {
2145
- instance.goToPage?.(page);
2146
- }
2702
+ actions.push({
2703
+ id: "page-nav",
2704
+ icon: "",
2705
+ label: "Page Navigation",
2706
+ type: "page-nav",
2707
+ group: "navigation",
2708
+ value: instance.getCurrentPage?.() ?? 1,
2709
+ max: totalPages,
2710
+ execute: (action, page) => {
2711
+ const cur = instance.getCurrentPage?.() ?? 1;
2712
+ const max = instance.getPageCount?.() ?? 1;
2713
+ if (action === "prev") {
2714
+ if (cur > 1) instance.goToPage?.(cur - 1);
2715
+ } else if (action === "next") {
2716
+ if (cur < max) instance.goToPage?.(cur + 1);
2717
+ } else if (typeof page === "number") {
2718
+ instance.goToPage?.(page);
2147
2719
  }
2148
- });
2149
- }
2720
+ }
2721
+ });
2150
2722
  actions.push(
2151
2723
  {
2152
2724
  id: "zoom-out",
@@ -2212,11 +2784,17 @@ var DocxPlugin = class {
2212
2784
  wrapper.className = "fp-docx-wrapper";
2213
2785
  wrapper.style.transformOrigin = "top center";
2214
2786
  wrapper.style.transition = "transform 0.2s ease";
2215
- wrapper.style.padding = "24px 16px";
2216
- wrapper.style.maxWidth = "950px";
2787
+ wrapper.style.padding = "0";
2788
+ wrapper.style.maxWidth = "none";
2789
+ wrapper.style.width = "816px";
2217
2790
  wrapper.style.margin = "0 auto";
2791
+ wrapper.style.display = "flex";
2792
+ wrapper.style.flexDirection = "column";
2793
+ wrapper.style.alignItems = "center";
2218
2794
  wrapper.style.boxSizing = "border-box";
2219
- ctx.container.style.overflow = "auto";
2795
+ ctx.container.style.overflowX = "hidden";
2796
+ ctx.container.style.overflowY = "auto";
2797
+ ctx.container.style.padding = "16px 8px";
2220
2798
  ctx.container.style.backgroundColor = "#f1f5f9";
2221
2799
  ctx.container.appendChild(wrapper);
2222
2800
  const styleOverride = document.createElement("style");
@@ -2390,39 +2968,13 @@ var DocxPlugin = class {
2390
2968
  const pageElements = sections.length > 0 ? sections : cards;
2391
2969
  const totalPages = Math.max(1, pageElements.length);
2392
2970
  let currentPage = 1;
2393
- let indicator = null;
2394
- if (totalPages > 1) {
2395
- indicator = document.createElement("div");
2396
- indicator.className = "fp-docx-page-indicator";
2397
- indicator.style.position = "sticky";
2398
- indicator.style.bottom = "16px";
2399
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2400
- indicator.style.backdropFilter = "blur(8px)";
2401
- indicator.style.color = "#f8fafc";
2402
- indicator.style.fontSize = "12px";
2403
- indicator.style.fontWeight = "600";
2404
- indicator.style.padding = "5px 14px";
2405
- indicator.style.borderRadius = "20px";
2406
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2407
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2408
- indicator.style.zIndex = "10";
2409
- indicator.style.userSelect = "none";
2410
- indicator.style.pointerEvents = "none";
2411
- indicator.style.textAlign = "center";
2412
- indicator.style.width = "fit-content";
2413
- indicator.style.margin = "16px auto 0";
2414
- ctx.container.appendChild(indicator);
2415
- }
2416
2971
  const showPage = (pageNum) => {
2417
2972
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2418
2973
  if (pageElements.length > 1) {
2419
2974
  pageElements.forEach((sec, idx) => {
2420
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
2975
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2421
2976
  });
2422
2977
  }
2423
- if (indicator) {
2424
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2425
- }
2426
2978
  ctx.container.scrollTop = 0;
2427
2979
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2428
2980
  };
@@ -2431,31 +2983,46 @@ var DocxPlugin = class {
2431
2983
  }
2432
2984
  scale = 1;
2433
2985
  let rotation = 0;
2434
- let fitMode = "width";
2435
- const calculateFitScale = (mode = fitMode) => {
2436
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2986
+ let fitMode = "page";
2987
+ let isUserZoomed = false;
2988
+ const calculateFitScale = (_mode = fitMode) => {
2989
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2437
2990
  const elW = activeEl.offsetWidth || 816;
2438
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2439
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2440
- const availH = Math.max(280, ctx.container.clientHeight - 80);
2991
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2441
2992
  const sW = availW / elW;
2442
- const sH = availH / singlePageH;
2443
- if (mode === "page") {
2444
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2445
- }
2446
- return Math.max(0.65, Math.min(1.05, sW));
2993
+ return Math.max(0.35, Math.min(3, sW));
2447
2994
  };
2448
2995
  const applyTransform = () => {
2449
2996
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2450
2997
  wrapper.style.transformOrigin = "top center";
2998
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2999
+ const elH = activeEl.offsetHeight || 1056;
3000
+ if (scale > 1) {
3001
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3002
+ } else {
3003
+ wrapper.style.marginBottom = "32px";
3004
+ }
2451
3005
  };
3006
+ let resizeObserver = null;
3007
+ if (typeof ResizeObserver !== "undefined") {
3008
+ resizeObserver = new ResizeObserver(() => {
3009
+ if (!isUserZoomed) {
3010
+ const newFit = calculateFitScale("page");
3011
+ if (Math.abs(scale - newFit) > 0.015) {
3012
+ scale = newFit;
3013
+ applyTransform();
3014
+ }
3015
+ }
3016
+ });
3017
+ resizeObserver.observe(ctx.container);
3018
+ }
2452
3019
  setTimeout(() => {
2453
- fitMode = "width";
2454
- scale = calculateFitScale("width");
3020
+ scale = calculateFitScale("page");
2455
3021
  applyTransform();
2456
- }, 60);
3022
+ }, 40);
2457
3023
  const cleanup = () => {
2458
- indicator?.remove();
3024
+ resizeObserver?.disconnect();
3025
+ resizeObserver = null;
2459
3026
  for (const url of createdBlobUrls) {
2460
3027
  URL.revokeObjectURL(url);
2461
3028
  }
@@ -2472,21 +3039,24 @@ var DocxPlugin = class {
2472
3039
  showPage(page);
2473
3040
  },
2474
3041
  zoomIn: () => {
2475
- scale += 0.15;
3042
+ isUserZoomed = true;
3043
+ scale = Math.min(3.5, scale + 0.15);
2476
3044
  applyTransform();
2477
3045
  },
2478
3046
  zoomOut: () => {
3047
+ isUserZoomed = true;
2479
3048
  scale = Math.max(0.2, scale - 0.15);
2480
3049
  applyTransform();
2481
3050
  },
2482
3051
  getZoom: () => scale,
2483
3052
  setZoom: (level) => {
3053
+ isUserZoomed = true;
2484
3054
  scale = level;
2485
3055
  applyTransform();
2486
3056
  },
2487
3057
  fitToPage: () => {
2488
- fitMode = fitMode === "width" ? "page" : "width";
2489
- scale = calculateFitScale(fitMode);
3058
+ isUserZoomed = false;
3059
+ scale = calculateFitScale("page");
2490
3060
  rotation = 0;
2491
3061
  applyTransform();
2492
3062
  },
@@ -2876,7 +3446,7 @@ var ExcelPlugin = class {
2876
3446
  "application/vnd.ms-excel.template.macroEnabled.12",
2877
3447
  "application/vnd.oasis.opendocument.spreadsheet"
2878
3448
  ];
2879
- weight = 80;
3449
+ weight = 85;
2880
3450
  supports(file) {
2881
3451
  const ext = file.metadata.extension?.toLowerCase();
2882
3452
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2888,6 +3458,14 @@ var ExcelPlugin = class {
2888
3458
  getToolbarActions(instance) {
2889
3459
  const totalSheets = instance.getPageCount?.() ?? 1;
2890
3460
  const actions = [];
3461
+ actions.push({
3462
+ id: "thumbnails",
3463
+ icon: "thumbnails",
3464
+ label: "Sheet Thumbnails",
3465
+ type: "button",
3466
+ group: "navigation",
3467
+ execute: () => instance.toggleThumbnails?.()
3468
+ });
2891
3469
  if (totalSheets > 1) {
2892
3470
  actions.push({
2893
3471
  id: "page-nav",
@@ -2970,6 +3548,16 @@ var ExcelPlugin = class {
2970
3548
  execute: () => {
2971
3549
  instance.print?.();
2972
3550
  }
3551
+ },
3552
+ {
3553
+ id: "open-window",
3554
+ icon: "open-window",
3555
+ label: "Open in Separate Full Window",
3556
+ type: "button",
3557
+ group: "actions",
3558
+ execute: () => {
3559
+ instance.openInSeparateWindow?.();
3560
+ }
2973
3561
  }
2974
3562
  );
2975
3563
  return actions;
@@ -3135,6 +3723,63 @@ var ExcelPlugin = class {
3135
3723
  },
3136
3724
  getPageCount: () => sheetNames.length,
3137
3725
  getCurrentPage: () => currentSheetIndex,
3726
+ getThumbnails: () => {
3727
+ return sheetNames.map((name, idx) => ({
3728
+ index: idx,
3729
+ label: name,
3730
+ render: async (canvas) => {
3731
+ canvas.width = 140;
3732
+ canvas.height = 100;
3733
+ const c = canvas.getContext("2d");
3734
+ if (!c) return;
3735
+ c.fillStyle = "#ffffff";
3736
+ c.fillRect(0, 0, canvas.width, canvas.height);
3737
+ c.fillStyle = "#107c41";
3738
+ c.fillRect(0, 0, canvas.width, 16);
3739
+ c.fillStyle = "#ffffff";
3740
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3741
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3742
+ const startY = 17;
3743
+ const rowH = 13;
3744
+ const colW = 30;
3745
+ const colHeaders = ["A", "B", "C", "D"];
3746
+ c.fillStyle = "#f1f5f9";
3747
+ c.fillRect(0, startY, canvas.width, rowH);
3748
+ c.strokeStyle = "#cbd5e1";
3749
+ c.lineWidth = 0.8;
3750
+ c.strokeRect(0, startY, canvas.width, rowH);
3751
+ c.fillStyle = "#64748b";
3752
+ c.font = "bold 7px sans-serif";
3753
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3754
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3755
+ }
3756
+ const ws = wb?.Sheets[name];
3757
+ for (let ri = 1; ri <= 5; ri++) {
3758
+ const y = startY + ri * rowH;
3759
+ if (y > canvas.height - 2) break;
3760
+ c.fillStyle = "#f8fafc";
3761
+ c.fillRect(0, y, 12, rowH);
3762
+ c.fillStyle = "#94a3b8";
3763
+ c.font = "6px sans-serif";
3764
+ c.fillText(String(ri), 3, y + 9);
3765
+ c.strokeStyle = "#e2e8f0";
3766
+ c.strokeRect(0, y, canvas.width, rowH);
3767
+ c.fillStyle = "#334155";
3768
+ c.font = "6px sans-serif";
3769
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3770
+ const cellRef = `${colHeaders[ci]}${ri}`;
3771
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3772
+ if (cellVal !== void 0) {
3773
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3774
+ }
3775
+ }
3776
+ }
3777
+ c.strokeStyle = "#cbd5e1";
3778
+ c.lineWidth = 1;
3779
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3780
+ }
3781
+ }));
3782
+ },
3138
3783
  download: () => {
3139
3784
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3140
3785
  const url = URL.createObjectURL(blob);
@@ -3167,7 +3812,39 @@ var CsvPlugin = class {
3167
3812
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3168
3813
  }
3169
3814
  getToolbarActions(instance) {
3170
- return [
3815
+ const totalPages = instance.getPageCount?.() ?? 1;
3816
+ const actions = [];
3817
+ actions.push({
3818
+ id: "thumbnails",
3819
+ icon: "thumbnails",
3820
+ label: "Page Thumbnails",
3821
+ type: "button",
3822
+ group: "navigation",
3823
+ execute: () => instance.toggleThumbnails?.()
3824
+ });
3825
+ if (totalPages > 1) {
3826
+ actions.push({
3827
+ id: "page-nav",
3828
+ icon: "",
3829
+ label: "Page Navigation",
3830
+ type: "page-nav",
3831
+ group: "navigation",
3832
+ value: instance.getCurrentPage?.() ?? 1,
3833
+ max: totalPages,
3834
+ execute: (action, page) => {
3835
+ const cur = instance.getCurrentPage?.() ?? 1;
3836
+ const max = instance.getPageCount?.() ?? 1;
3837
+ if (action === "prev") {
3838
+ if (cur > 1) instance.goToPage?.(cur - 1);
3839
+ } else if (action === "next") {
3840
+ if (cur < max) instance.goToPage?.(cur + 1);
3841
+ } else if (typeof page === "number") {
3842
+ instance.goToPage?.(page);
3843
+ }
3844
+ }
3845
+ });
3846
+ }
3847
+ actions.push(
3171
3848
  {
3172
3849
  id: "zoom-out",
3173
3850
  icon: "zoom-out",
@@ -3188,6 +3865,16 @@ var CsvPlugin = class {
3188
3865
  instance.zoomIn?.();
3189
3866
  }
3190
3867
  },
3868
+ {
3869
+ id: "fit-page",
3870
+ icon: "fit-page",
3871
+ label: "Fit to View",
3872
+ type: "button",
3873
+ group: "zoom",
3874
+ execute: () => {
3875
+ instance.fitToPage?.();
3876
+ }
3877
+ },
3191
3878
  {
3192
3879
  id: "download",
3193
3880
  icon: "download",
@@ -3205,10 +3892,21 @@ var CsvPlugin = class {
3205
3892
  type: "button",
3206
3893
  group: "actions",
3207
3894
  execute: () => {
3208
- instance.print?.();
3895
+ instance.print?.();
3896
+ }
3897
+ },
3898
+ {
3899
+ id: "open-window",
3900
+ icon: "open-window",
3901
+ label: "Open in Separate Full Window",
3902
+ type: "button",
3903
+ group: "actions",
3904
+ execute: () => {
3905
+ instance.openInSeparateWindow?.();
3209
3906
  }
3210
3907
  }
3211
- ];
3908
+ );
3909
+ return actions;
3212
3910
  }
3213
3911
  async render(ctx) {
3214
3912
  const decoder = new TextDecoder("utf-8");
@@ -3220,33 +3918,62 @@ var CsvPlugin = class {
3220
3918
  container.style.padding = "16px";
3221
3919
  container.style.boxSizing = "border-box";
3222
3920
  container.style.transformOrigin = "top left";
3921
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3922
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3923
+ const headerLine = allLines[0] || "";
3924
+ const headerCells = headerLine.split(delimiter);
3925
+ const dataLines = allLines.slice(1);
3926
+ const ROWS_PER_PAGE = 100;
3927
+ const totalPages = Math.max(1, Math.ceil(dataLines.length / ROWS_PER_PAGE));
3928
+ let currentPage = 1;
3223
3929
  const table = document.createElement("table");
3224
3930
  table.style.borderCollapse = "collapse";
3225
3931
  table.style.width = "100%";
3226
- table.style.fontFamily = "monospace";
3932
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3227
3933
  table.style.fontSize = "13px";
3228
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3229
- const rows = text.split(/\r?\n/).slice(0, 500);
3230
- rows.forEach((row, rowIndex) => {
3231
- if (!row.trim()) return;
3232
- const tr = document.createElement("tr");
3233
- const cells = row.split(delimiter);
3234
- cells.forEach((cell) => {
3235
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3236
- td.textContent = cell.trim();
3237
- td.style.border = "1px solid #d0d7de";
3238
- td.style.padding = "6px 12px";
3239
- if (rowIndex === 0) {
3240
- td.style.backgroundColor = "#f6f8fa";
3241
- td.style.fontWeight = "bold";
3242
- }
3243
- tr.appendChild(td);
3934
+ const renderPage = (pageNum) => {
3935
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3936
+ table.innerHTML = "";
3937
+ const headerTr = document.createElement("tr");
3938
+ headerCells.forEach((cell) => {
3939
+ const th = document.createElement("th");
3940
+ th.textContent = cell.trim();
3941
+ th.style.border = "1px solid #d0d7de";
3942
+ th.style.padding = "8px 12px";
3943
+ th.style.backgroundColor = "#f6f8fa";
3944
+ th.style.fontWeight = "600";
3945
+ th.style.whiteSpace = "nowrap";
3946
+ headerTr.appendChild(th);
3244
3947
  });
3245
- table.appendChild(tr);
3246
- });
3948
+ table.appendChild(headerTr);
3949
+ const start = (currentPage - 1) * ROWS_PER_PAGE;
3950
+ const end = Math.min(dataLines.length, start + ROWS_PER_PAGE);
3951
+ const pageRows = dataLines.slice(start, end);
3952
+ pageRows.forEach((row, idx) => {
3953
+ const tr = document.createElement("tr");
3954
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3955
+ const cells = row.split(delimiter);
3956
+ cells.forEach((cell) => {
3957
+ const td = document.createElement("td");
3958
+ td.textContent = cell.trim();
3959
+ td.style.border = "1px solid #d0d7de";
3960
+ td.style.padding = "6px 12px";
3961
+ td.style.whiteSpace = "nowrap";
3962
+ tr.appendChild(td);
3963
+ });
3964
+ table.appendChild(tr);
3965
+ });
3966
+ container.scrollTop = 0;
3967
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3968
+ };
3969
+ renderPage(1);
3247
3970
  container.appendChild(table);
3248
3971
  ctx.container.appendChild(container);
3249
3972
  let scale = 1;
3973
+ const applyScale = () => {
3974
+ container.style.transform = `scale(${scale})`;
3975
+ container.style.transformOrigin = "top left";
3976
+ };
3250
3977
  const cleanup = () => {
3251
3978
  container.remove();
3252
3979
  ctx.container.innerHTML = "";
@@ -3254,22 +3981,86 @@ var CsvPlugin = class {
3254
3981
  ctx.signal.addEventListener("abort", cleanup);
3255
3982
  return {
3256
3983
  destroy: cleanup,
3984
+ getPageCount: () => totalPages,
3985
+ getCurrentPage: () => currentPage,
3986
+ goToPage: (page) => renderPage(page),
3987
+ getThumbnails: () => {
3988
+ const thumbnails = [];
3989
+ for (let i = 0; i < totalPages; i++) {
3990
+ const pageIdx = i;
3991
+ const startRow = pageIdx * ROWS_PER_PAGE + 1;
3992
+ const endRow = Math.min(dataLines.length, (pageIdx + 1) * ROWS_PER_PAGE);
3993
+ const label = totalPages === 1 ? "Table" : `Page ${pageIdx + 1} (${startRow}-${endRow})`;
3994
+ thumbnails.push({
3995
+ index: pageIdx,
3996
+ label,
3997
+ render: async (canvas) => {
3998
+ canvas.width = 140;
3999
+ canvas.height = 100;
4000
+ const c = canvas.getContext("2d");
4001
+ if (!c) return;
4002
+ c.fillStyle = "#ffffff";
4003
+ c.fillRect(0, 0, canvas.width, canvas.height);
4004
+ c.fillStyle = "#2563eb";
4005
+ c.fillRect(0, 0, canvas.width, 16);
4006
+ c.fillStyle = "#ffffff";
4007
+ c.font = "bold 8px sans-serif";
4008
+ c.fillText(`\u{1F4C8} ${label.slice(0, 18)}`, 6, 11);
4009
+ const startY = 17;
4010
+ const rowH = 13;
4011
+ const colW = 28;
4012
+ const cols = Math.min(4, headerCells.length || 4);
4013
+ c.fillStyle = "#f1f5f9";
4014
+ c.fillRect(0, startY, canvas.width, rowH);
4015
+ c.strokeStyle = "#cbd5e1";
4016
+ c.lineWidth = 0.8;
4017
+ c.strokeRect(0, startY, canvas.width, rowH);
4018
+ c.fillStyle = "#64748b";
4019
+ c.font = "bold 7px sans-serif";
4020
+ for (let ci = 0; ci < cols; ci++) {
4021
+ const hName = headerCells[ci] || `Col ${ci + 1}`;
4022
+ c.fillText(hName.slice(0, 5), 8 + ci * colW, startY + 9);
4023
+ }
4024
+ const rowsToPreview = dataLines.slice(pageIdx * ROWS_PER_PAGE, pageIdx * ROWS_PER_PAGE + 5);
4025
+ for (let ri = 0; ri < rowsToPreview.length; ri++) {
4026
+ const y = startY + (ri + 1) * rowH;
4027
+ if (y > canvas.height - 2) break;
4028
+ c.strokeStyle = "#e2e8f0";
4029
+ c.strokeRect(0, y, canvas.width, rowH);
4030
+ const cVals = rowsToPreview[ri].split(delimiter);
4031
+ c.fillStyle = "#334155";
4032
+ c.font = "6px sans-serif";
4033
+ for (let ci = 0; ci < cols; ci++) {
4034
+ const val = cVals[ci] || "";
4035
+ c.fillText(val.trim().slice(0, 6), 8 + ci * colW, y + 9);
4036
+ }
4037
+ }
4038
+ c.strokeStyle = "#cbd5e1";
4039
+ c.lineWidth = 1;
4040
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4041
+ }
4042
+ });
4043
+ }
4044
+ return thumbnails;
4045
+ },
3257
4046
  zoomIn: () => {
3258
4047
  scale += 0.1;
3259
- container.style.transform = `scale(${scale})`;
4048
+ applyScale();
3260
4049
  },
3261
4050
  zoomOut: () => {
3262
4051
  scale = Math.max(0.2, scale - 0.1);
3263
- container.style.transform = `scale(${scale})`;
4052
+ applyScale();
3264
4053
  },
3265
4054
  getZoom: () => scale,
3266
4055
  setZoom: (level) => {
3267
4056
  scale = level;
3268
- container.style.transform = `scale(${scale})`;
4057
+ applyScale();
3269
4058
  },
3270
4059
  fitToPage: () => {
3271
- scale = 1;
3272
- container.style.transform = `scale(1)`;
4060
+ const availW = Math.max(280, (ctx.container.clientWidth || window.innerWidth) - 48);
4061
+ const tW = table.offsetWidth || 600;
4062
+ scale = Math.max(0.35, Math.min(1, availW / tW));
4063
+ applyScale();
3273
4064
  },
3274
4065
  download: () => {
3275
4066
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3333,28 +4124,34 @@ var CodePlugin = class {
3333
4124
  getToolbarActions(instance) {
3334
4125
  const totalPages = instance.getPageCount?.() ?? 1;
3335
4126
  const actions = [];
3336
- if (totalPages > 1) {
3337
- actions.push({
3338
- id: "page-nav",
3339
- icon: "",
3340
- label: "Page Navigation",
3341
- type: "page-nav",
3342
- group: "navigation",
3343
- value: instance.getCurrentPage?.() ?? 1,
3344
- max: totalPages,
3345
- execute: (action, page) => {
3346
- const cur = instance.getCurrentPage?.() ?? 1;
3347
- const max = instance.getPageCount?.() ?? 1;
3348
- if (action === "prev") {
3349
- if (cur > 1) instance.goToPage?.(cur - 1);
3350
- } else if (action === "next") {
3351
- if (cur < max) instance.goToPage?.(cur + 1);
3352
- } else if (typeof page === "number") {
3353
- instance.goToPage?.(page);
3354
- }
4127
+ actions.push({
4128
+ id: "thumbnails",
4129
+ icon: "thumbnails",
4130
+ label: "Page Thumbnails",
4131
+ type: "button",
4132
+ group: "navigation",
4133
+ execute: () => instance.toggleThumbnails?.()
4134
+ });
4135
+ actions.push({
4136
+ id: "page-nav",
4137
+ icon: "",
4138
+ label: "Page Navigation",
4139
+ type: "page-nav",
4140
+ group: "navigation",
4141
+ value: instance.getCurrentPage?.() ?? 1,
4142
+ max: totalPages,
4143
+ execute: (action, page) => {
4144
+ const cur = instance.getCurrentPage?.() ?? 1;
4145
+ const max = instance.getPageCount?.() ?? 1;
4146
+ if (action === "prev") {
4147
+ if (cur > 1) instance.goToPage?.(cur - 1);
4148
+ } else if (action === "next") {
4149
+ if (cur < max) instance.goToPage?.(cur + 1);
4150
+ } else if (typeof page === "number") {
4151
+ instance.goToPage?.(page);
3355
4152
  }
3356
- });
3357
- }
4153
+ }
4154
+ });
3358
4155
  actions.push(
3359
4156
  {
3360
4157
  id: "zoom-out",
@@ -3498,16 +4295,19 @@ var CodePlugin = class {
3498
4295
  let fontSize = 13;
3499
4296
  let rotation = 0;
3500
4297
  let zoomLevel = 1;
4298
+ let isUserZoomed = false;
3501
4299
  const pre = document.createElement("pre");
3502
4300
  const code = document.createElement("code");
3503
4301
  if (isTxt) {
4302
+ container.style.overflowX = "hidden";
4303
+ container.style.overflowY = "auto";
3504
4304
  container.style.backgroundColor = "#f1f5f9";
3505
- container.style.padding = "32px";
4305
+ container.style.padding = "16px 8px";
3506
4306
  container.style.boxSizing = "border-box";
3507
4307
  container.style.display = "flex";
3508
4308
  container.style.flexDirection = "column";
3509
4309
  container.style.alignItems = "center";
3510
- pre.style.margin = "0";
4310
+ pre.style.margin = "0 auto";
3511
4311
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3512
4312
  pre.style.fontSize = "13px";
3513
4313
  pre.style.color = "#1e293b";
@@ -3519,10 +4319,12 @@ var CodePlugin = class {
3519
4319
  pre.style.minHeight = "1056px";
3520
4320
  pre.style.padding = "72px 56px";
3521
4321
  pre.style.boxSizing = "border-box";
3522
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4322
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3523
4323
  pre.style.borderRadius = "4px";
3524
4324
  pre.style.transformOrigin = "top center";
4325
+ pre.style.transition = "transform 0.15s ease";
3525
4326
  } else {
4327
+ container.style.overflow = "auto";
3526
4328
  container.style.backgroundColor = "#1e1e1e";
3527
4329
  container.style.color = "#d4d4d4";
3528
4330
  container.style.padding = "16px";
@@ -3554,42 +4356,9 @@ var CodePlugin = class {
3554
4356
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3555
4357
  pre.appendChild(code);
3556
4358
  container.appendChild(pre);
3557
- let indicator = null;
3558
- if (totalPages > 1) {
3559
- indicator = document.createElement("div");
3560
- indicator.className = "fp-code-page-indicator";
3561
- indicator.style.position = "sticky";
3562
- indicator.style.bottom = "16px";
3563
- if (isTxt) {
3564
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3565
- indicator.style.color = "#334155";
3566
- indicator.style.border = "1px solid #e2e8f0";
3567
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3568
- } else {
3569
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3570
- indicator.style.color = "#f8fafc";
3571
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3572
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3573
- indicator.style.backdropFilter = "blur(8px)";
3574
- }
3575
- indicator.style.fontSize = "12px";
3576
- indicator.style.fontWeight = "600";
3577
- indicator.style.padding = "5px 14px";
3578
- indicator.style.borderRadius = "20px";
3579
- indicator.style.zIndex = "10";
3580
- indicator.style.userSelect = "none";
3581
- indicator.style.pointerEvents = "none";
3582
- indicator.style.textAlign = "center";
3583
- indicator.style.width = "fit-content";
3584
- indicator.style.margin = "16px auto 0";
3585
- container.appendChild(indicator);
3586
- }
3587
4359
  const showPage = (pageNum) => {
3588
4360
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3589
4361
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3590
- if (indicator) {
3591
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3592
- }
3593
4362
  container.scrollTop = 0;
3594
4363
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3595
4364
  };
@@ -3597,36 +4366,90 @@ var CodePlugin = class {
3597
4366
  showPage(1);
3598
4367
  }
3599
4368
  ctx.container.appendChild(container);
3600
- const cleanup = () => {
3601
- indicator?.remove();
3602
- container.remove();
3603
- ctx.container.innerHTML = "";
4369
+ const calculateTxtFit = () => {
4370
+ const availW = Math.max(280, container.clientWidth - 32);
4371
+ return Math.max(0.4, Math.min(3, availW / 816));
3604
4372
  };
3605
- ctx.signal.addEventListener("abort", cleanup);
3606
4373
  const updateTransform = () => {
3607
4374
  if (isTxt) {
3608
4375
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4376
+ const scaledH = 1056 * zoomLevel;
4377
+ const extraH = Math.max(0, scaledH - 1056);
4378
+ pre.style.marginBottom = `${extraH + 32}px`;
3609
4379
  } else {
3610
4380
  pre.style.transform = `rotate(${rotation}deg)`;
3611
4381
  pre.style.fontSize = `${fontSize}px`;
3612
4382
  }
3613
4383
  };
4384
+ let resizeObserver = null;
4385
+ if (isTxt) {
4386
+ setTimeout(() => {
4387
+ zoomLevel = calculateTxtFit();
4388
+ updateTransform();
4389
+ }, 60);
4390
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4391
+ if (!isUserZoomed) {
4392
+ zoomLevel = calculateTxtFit();
4393
+ updateTransform();
4394
+ }
4395
+ }) : null;
4396
+ resizeObserver?.observe(container);
4397
+ }
4398
+ const cleanup = () => {
4399
+ resizeObserver?.disconnect();
4400
+ container.remove();
4401
+ ctx.container.innerHTML = "";
4402
+ };
4403
+ ctx.signal.addEventListener("abort", cleanup);
3614
4404
  return {
3615
4405
  destroy: cleanup,
3616
4406
  getPageCount: () => totalPages,
3617
4407
  getCurrentPage: () => currentPage,
3618
4408
  goToPage: (page) => showPage(page),
4409
+ getThumbnails: () => {
4410
+ return rawPages.map((pageText, idx) => ({
4411
+ index: idx,
4412
+ label: `Page ${idx + 1}`,
4413
+ render: async (canvas) => {
4414
+ canvas.width = 140;
4415
+ canvas.height = 180;
4416
+ const c = canvas.getContext("2d");
4417
+ if (!c) return;
4418
+ c.fillStyle = "#ffffff";
4419
+ c.fillRect(0, 0, canvas.width, canvas.height);
4420
+ c.strokeStyle = "#cbd5e1";
4421
+ c.lineWidth = 1;
4422
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4423
+ c.fillStyle = "#64748b";
4424
+ const lines = (pageText || "").split("\n").slice(0, 24);
4425
+ const lineH = 6;
4426
+ const startY = 14;
4427
+ const startX = 12;
4428
+ const maxW = canvas.width - 24;
4429
+ c.font = "5px monospace";
4430
+ for (let li = 0; li < lines.length; li++) {
4431
+ const l = lines[li].trim();
4432
+ if (!l) continue;
4433
+ const y = startY + li * lineH;
4434
+ if (y > canvas.height - 12) break;
4435
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4436
+ }
4437
+ }
4438
+ }));
4439
+ },
3619
4440
  zoomIn: () => {
4441
+ isUserZoomed = true;
3620
4442
  if (isTxt) {
3621
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4443
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3622
4444
  } else {
3623
4445
  fontSize = Math.min(32, fontSize + 2);
3624
4446
  }
3625
4447
  updateTransform();
3626
4448
  },
3627
4449
  zoomOut: () => {
4450
+ isUserZoomed = true;
3628
4451
  if (isTxt) {
3629
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4452
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3630
4453
  } else {
3631
4454
  fontSize = Math.max(8, fontSize - 2);
3632
4455
  }
@@ -3634,6 +4457,7 @@ var CodePlugin = class {
3634
4457
  },
3635
4458
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3636
4459
  setZoom: (level) => {
4460
+ isUserZoomed = true;
3637
4461
  if (isTxt) {
3638
4462
  zoomLevel = level;
3639
4463
  } else {
@@ -3642,9 +4466,10 @@ var CodePlugin = class {
3642
4466
  updateTransform();
3643
4467
  },
3644
4468
  fitToPage: () => {
4469
+ isUserZoomed = false;
3645
4470
  fontSize = 13;
3646
4471
  rotation = 0;
3647
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4472
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3648
4473
  updateTransform();
3649
4474
  },
3650
4475
  rotateCW: () => {
@@ -4525,28 +5350,26 @@ var RtfPlugin = class {
4525
5350
  getToolbarActions(instance) {
4526
5351
  const totalPages = instance.getPageCount?.() ?? 1;
4527
5352
  const actions = [];
4528
- if (totalPages > 1) {
4529
- actions.push({
4530
- id: "page-nav",
4531
- icon: "",
4532
- label: "Page Navigation",
4533
- type: "page-nav",
4534
- group: "navigation",
4535
- value: instance.getCurrentPage?.() ?? 1,
4536
- max: totalPages,
4537
- execute: (action, page) => {
4538
- const cur = instance.getCurrentPage?.() ?? 1;
4539
- const max = instance.getPageCount?.() ?? 1;
4540
- if (action === "prev") {
4541
- if (cur > 1) instance.goToPage?.(cur - 1);
4542
- } else if (action === "next") {
4543
- if (cur < max) instance.goToPage?.(cur + 1);
4544
- } else if (typeof page === "number") {
4545
- instance.goToPage?.(page);
4546
- }
5353
+ actions.push({
5354
+ id: "page-nav",
5355
+ icon: "",
5356
+ label: "Page Navigation",
5357
+ type: "page-nav",
5358
+ group: "navigation",
5359
+ value: instance.getCurrentPage?.() ?? 1,
5360
+ max: totalPages,
5361
+ execute: (action, page) => {
5362
+ const cur = instance.getCurrentPage?.() ?? 1;
5363
+ const max = instance.getPageCount?.() ?? 1;
5364
+ if (action === "prev") {
5365
+ if (cur > 1) instance.goToPage?.(cur - 1);
5366
+ } else if (action === "next") {
5367
+ if (cur < max) instance.goToPage?.(cur + 1);
5368
+ } else if (typeof page === "number") {
5369
+ instance.goToPage?.(page);
4547
5370
  }
4548
- });
4549
- }
5371
+ }
5372
+ });
4550
5373
  actions.push(
4551
5374
  {
4552
5375
  id: "zoom-out",
@@ -4618,43 +5441,57 @@ var RtfPlugin = class {
4618
5441
  async render(ctx) {
4619
5442
  const wrapper = document.createElement("div");
4620
5443
  wrapper.className = "fp-rtf-wrapper";
4621
- wrapper.style.padding = "32px";
4622
- wrapper.style.maxWidth = "850px";
5444
+ wrapper.style.padding = "0";
5445
+ wrapper.style.width = "816px";
4623
5446
  wrapper.style.margin = "0 auto";
4624
- wrapper.style.backgroundColor = "#fff";
4625
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4626
- wrapper.style.borderRadius = "4px";
4627
- wrapper.style.minHeight = "100%";
5447
+ wrapper.style.boxSizing = "border-box";
5448
+ wrapper.style.display = "flex";
5449
+ wrapper.style.flexDirection = "column";
5450
+ wrapper.style.alignItems = "center";
5451
+ wrapper.style.backgroundColor = "transparent";
4628
5452
  wrapper.style.transformOrigin = "top center";
4629
- wrapper.style.transition = "transform 0.2s ease";
4630
- ctx.container.style.overflow = "auto";
4631
- ctx.container.style.padding = "24px";
5453
+ wrapper.style.transition = "transform 0.15s ease";
5454
+ ctx.container.style.overflowX = "hidden";
5455
+ ctx.container.style.overflowY = "auto";
5456
+ ctx.container.style.padding = "16px 8px";
4632
5457
  ctx.container.style.backgroundColor = "#f1f5f9";
4633
5458
  ctx.container.appendChild(wrapper);
4634
5459
  let scale = 1;
4635
5460
  let rotation = 0;
4636
5461
  let pageElements = [];
4637
- let fitMode = "width";
5462
+ let isUserZoomed = false;
5463
+ let fitMode = ctx?.options?.fitMode || "width";
4638
5464
  const calculateFitScale = (mode = fitMode) => {
4639
5465
  const activeEl = pageElements[currentPage - 1] || wrapper;
4640
- const elW = activeEl.offsetWidth || 850;
4641
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4642
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4643
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5466
+ const elW = 816;
5467
+ const singlePageH = activeEl?.offsetHeight || 1056;
5468
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5469
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4644
5470
  const sW = availW / elW;
4645
5471
  const sH = availH / singlePageH;
4646
5472
  if (mode === "page") {
4647
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5473
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4648
5474
  }
4649
- return Math.max(0.65, Math.min(1.05, sW));
5475
+ return Math.max(0.4, Math.min(3, sW));
4650
5476
  };
4651
5477
  const applyTransform = () => {
4652
5478
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4653
5479
  wrapper.style.transformOrigin = "top center";
5480
+ const activeEl = pageElements[currentPage - 1];
5481
+ const baseH = activeEl?.offsetHeight || 1056;
5482
+ const scaledH = baseH * scale;
5483
+ const extraH = Math.max(0, scaledH - baseH);
5484
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4654
5485
  };
5486
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5487
+ if (!isUserZoomed) {
5488
+ scale = calculateFitScale(fitMode);
5489
+ applyTransform();
5490
+ }
5491
+ }) : null;
5492
+ resizeObserver?.observe(ctx.container);
4655
5493
  setTimeout(() => {
4656
- fitMode = "width";
4657
- scale = calculateFitScale("width");
5494
+ scale = calculateFitScale(fitMode);
4658
5495
  applyTransform();
4659
5496
  }, 60);
4660
5497
  try {
@@ -4664,20 +5501,34 @@ var RtfPlugin = class {
4664
5501
  const doc = new RTFJS__namespace.Document(ctx.buffer, {});
4665
5502
  const htmlElements = await doc.render();
4666
5503
  const contentNodes = [];
4667
- for (const item of htmlElements) {
4668
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4669
- contentNodes.push(...Array.from(item.children));
4670
- } else {
4671
- contentNodes.push(item);
5504
+ const extractBlocks = (nodes) => {
5505
+ for (const item of nodes) {
5506
+ if (!item || !(item instanceof HTMLElement)) continue;
5507
+ const tag = item.tagName.toLowerCase();
5508
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5509
+ const ct = c.tagName.toLowerCase();
5510
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5511
+ });
5512
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5513
+ extractBlocks(Array.from(item.children));
5514
+ } else {
5515
+ if (tag === "p" || tag === "div") {
5516
+ item.style.display = "block";
5517
+ item.style.marginBottom = "12px";
5518
+ item.style.lineHeight = "1.6";
5519
+ }
5520
+ contentNodes.push(item);
5521
+ }
4672
5522
  }
4673
- }
5523
+ };
5524
+ extractBlocks(htmlElements);
4674
5525
  wrapper.innerHTML = "";
4675
5526
  contentNodes.forEach((node) => wrapper.appendChild(node));
4676
5527
  const childHeights = contentNodes.map((c) => {
4677
5528
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4678
5529
  const offH = c.offsetHeight || 0;
4679
5530
  const textLen = c.textContent?.trim().length || 0;
4680
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
5531
+ const estH = Math.max(28, Math.ceil(textLen / 75) * 24 + 16);
4681
5532
  return Math.max(rectH, offH, estH);
4682
5533
  });
4683
5534
  wrapper.innerHTML = "";
@@ -4694,6 +5545,7 @@ var RtfPlugin = class {
4694
5545
  card.style.marginBottom = "24px";
4695
5546
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4696
5547
  card.style.lineHeight = "1.6";
5548
+ card.style.color = "#1e293b";
4697
5549
  return card;
4698
5550
  };
4699
5551
  let curCard = createRtfCard();
@@ -4729,9 +5581,8 @@ var RtfPlugin = class {
4729
5581
  pageCard.style.padding = "72px 56px";
4730
5582
  pageCard.style.width = "816px";
4731
5583
  pageCard.style.minHeight = "1056px";
4732
- pageCard.style.maxWidth = "100%";
4733
5584
  pageCard.style.boxSizing = "border-box";
4734
- pageCard.style.fontFamily = "serif";
5585
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4735
5586
  pageCard.style.fontSize = "12pt";
4736
5587
  pageCard.style.lineHeight = "1.6";
4737
5588
  pageCard.style.color = "#1e293b";
@@ -4744,29 +5595,6 @@ var RtfPlugin = class {
4744
5595
  }
4745
5596
  const totalPages = Math.max(1, pageElements.length);
4746
5597
  let currentPage = 1;
4747
- let indicator = null;
4748
- if (totalPages > 1) {
4749
- indicator = document.createElement("div");
4750
- indicator.className = "fp-rtf-page-indicator";
4751
- indicator.style.position = "sticky";
4752
- indicator.style.bottom = "16px";
4753
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4754
- indicator.style.backdropFilter = "blur(8px)";
4755
- indicator.style.color = "#f8fafc";
4756
- indicator.style.fontSize = "12px";
4757
- indicator.style.fontWeight = "600";
4758
- indicator.style.padding = "5px 14px";
4759
- indicator.style.borderRadius = "20px";
4760
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4761
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4762
- indicator.style.zIndex = "10";
4763
- indicator.style.userSelect = "none";
4764
- indicator.style.pointerEvents = "none";
4765
- indicator.style.textAlign = "center";
4766
- indicator.style.width = "fit-content";
4767
- indicator.style.margin = "16px auto 0";
4768
- ctx.container.appendChild(indicator);
4769
- }
4770
5598
  const showPage = (pageNum) => {
4771
5599
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4772
5600
  if (totalPages > 1) {
@@ -4774,9 +5602,6 @@ var RtfPlugin = class {
4774
5602
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4775
5603
  });
4776
5604
  }
4777
- if (indicator) {
4778
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4779
- }
4780
5605
  ctx.container.scrollTop = 0;
4781
5606
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4782
5607
  };
@@ -4784,7 +5609,7 @@ var RtfPlugin = class {
4784
5609
  showPage(1);
4785
5610
  }
4786
5611
  const cleanup = () => {
4787
- indicator?.remove();
5612
+ resizeObserver?.disconnect();
4788
5613
  wrapper.remove();
4789
5614
  ctx.container.innerHTML = "";
4790
5615
  };
@@ -4795,21 +5620,25 @@ var RtfPlugin = class {
4795
5620
  getCurrentPage: () => currentPage,
4796
5621
  goToPage: (page) => showPage(page),
4797
5622
  zoomIn: () => {
5623
+ isUserZoomed = true;
4798
5624
  scale += 0.15;
4799
5625
  applyTransform();
4800
5626
  },
4801
5627
  zoomOut: () => {
5628
+ isUserZoomed = true;
4802
5629
  scale = Math.max(0.2, scale - 0.15);
4803
5630
  applyTransform();
4804
5631
  },
4805
5632
  getZoom: () => scale,
4806
5633
  setZoom: (level) => {
5634
+ isUserZoomed = true;
4807
5635
  scale = level;
4808
5636
  applyTransform();
4809
5637
  },
4810
5638
  fitToPage: () => {
4811
- fitMode = fitMode === "width" ? "page" : "width";
4812
- scale = calculateFitScale(fitMode);
5639
+ isUserZoomed = false;
5640
+ fitMode = "width";
5641
+ scale = calculateFitScale("width");
4813
5642
  rotation = 0;
4814
5643
  applyTransform();
4815
5644
  },
@@ -4997,40 +5826,36 @@ var OpenDocumentPlugin = class {
4997
5826
  const isPresentation = instance.isPresentation;
4998
5827
  const totalPages = instance.getPageCount?.() ?? 1;
4999
5828
  const actions = [];
5000
- if (isPresentation || totalPages > 1) {
5001
- if (isPresentation) {
5002
- actions.push({
5003
- id: "thumbnails",
5004
- icon: "thumbnails",
5005
- label: "Slide Thumbnails",
5006
- type: "button",
5007
- group: "navigation",
5008
- execute: () => instance.toggleThumbnails?.()
5009
- });
5010
- }
5011
- actions.push({
5012
- id: "page-nav",
5013
- icon: "",
5014
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
5015
- type: "page-nav",
5016
- group: "navigation",
5017
- value: instance.getCurrentPage?.() ?? 1,
5018
- max: totalPages,
5019
- execute: (action, page) => {
5020
- const cur = instance.getCurrentPage?.() ?? 1;
5021
- const max = instance.getPageCount?.() ?? 1;
5022
- if (action === "prev") {
5023
- if (cur > 1) instance.goToPage?.(cur - 1);
5024
- } else if (action === "next") {
5025
- if (cur < max) instance.goToPage?.(cur + 1);
5026
- } else if (typeof page === "number") {
5027
- instance.goToPage?.(page);
5028
- } else if (typeof action === "number") {
5029
- instance.goToPage?.(action);
5030
- }
5829
+ actions.push({
5830
+ id: "thumbnails",
5831
+ icon: "thumbnails",
5832
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
5833
+ type: "button",
5834
+ group: "navigation",
5835
+ execute: () => instance.toggleThumbnails?.()
5836
+ });
5837
+ actions.push({
5838
+ id: "page-nav",
5839
+ icon: "",
5840
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
5841
+ type: "page-nav",
5842
+ group: "navigation",
5843
+ value: instance.getCurrentPage?.() ?? 1,
5844
+ max: totalPages,
5845
+ execute: (action, page) => {
5846
+ const cur = instance.getCurrentPage?.() ?? 1;
5847
+ const max = instance.getPageCount?.() ?? 1;
5848
+ if (action === "prev") {
5849
+ if (cur > 1) instance.goToPage?.(cur - 1);
5850
+ } else if (action === "next") {
5851
+ if (cur < max) instance.goToPage?.(cur + 1);
5852
+ } else if (typeof page === "number") {
5853
+ instance.goToPage?.(page);
5854
+ } else if (typeof action === "number") {
5855
+ instance.goToPage?.(action);
5031
5856
  }
5032
- });
5033
- }
5857
+ }
5858
+ });
5034
5859
  actions.push(
5035
5860
  {
5036
5861
  id: "zoom-out",
@@ -5121,49 +5946,65 @@ var OpenDocumentPlugin = class {
5121
5946
  container.className = "fp-odf-container";
5122
5947
  container.style.width = "100%";
5123
5948
  container.style.height = "100%";
5124
- container.style.overflow = "auto";
5125
- container.style.padding = "24px";
5949
+ container.style.overflowX = "hidden";
5950
+ container.style.overflowY = "auto";
5951
+ container.style.padding = "16px 8px";
5126
5952
  container.style.backgroundColor = "#f1f5f9";
5127
5953
  const wrapper = document.createElement("div");
5128
5954
  wrapper.className = "fp-odf-wrapper";
5129
5955
  wrapper.style.margin = "0 auto";
5956
+ wrapper.style.width = isPresentation ? "960px" : "816px";
5957
+ wrapper.style.boxSizing = "border-box";
5130
5958
  wrapper.style.backgroundColor = "#ffffff";
5131
5959
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5132
5960
  wrapper.style.borderRadius = "4px";
5133
5961
  wrapper.style.transformOrigin = "top center";
5134
- wrapper.style.transition = "transform 0.2s ease";
5962
+ wrapper.style.transition = "transform 0.15s ease";
5135
5963
  container.appendChild(wrapper);
5136
5964
  ctx.container.appendChild(container);
5137
5965
  let scale = 1;
5138
5966
  let rotation = 0;
5139
- let fitMode = "width";
5967
+ let isUserZoomed = false;
5968
+ let fitMode = ctx?.options?.fitMode || "width";
5969
+ let currentPage = 1;
5970
+ let totalPages = 1;
5971
+ let slides = [];
5140
5972
  const calculateFitScale = (mode = fitMode) => {
5141
- const elW = wrapper.offsetWidth || 850;
5142
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5143
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5144
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5973
+ const activeSlide = slides[currentPage - 1];
5974
+ const elW = isPresentation ? 960 : 816;
5975
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5976
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5977
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5145
5978
  const sW = availW / elW;
5146
5979
  const sH = availH / (isPresentation ? 540 : singlePageH);
5147
5980
  if (isPresentation) {
5148
- return Math.min(1, Math.min(sW, sH));
5981
+ return Math.min(2.5, Math.min(sW, sH));
5149
5982
  }
5150
5983
  if (mode === "page") {
5151
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5984
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5152
5985
  }
5153
- return Math.max(0.65, Math.min(1.05, sW));
5986
+ return Math.max(0.4, Math.min(3, sW));
5154
5987
  };
5155
5988
  const applyTransform = () => {
5156
5989
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5157
5990
  wrapper.style.transformOrigin = "top center";
5991
+ const activeSlide = slides[currentPage - 1];
5992
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
5993
+ const scaledH = baseH * scale;
5994
+ const extraH = Math.max(0, scaledH - baseH);
5995
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5158
5996
  };
5997
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5998
+ if (!isUserZoomed) {
5999
+ scale = calculateFitScale(fitMode);
6000
+ applyTransform();
6001
+ }
6002
+ }) : null;
6003
+ resizeObserver?.observe(ctx.container);
5159
6004
  setTimeout(() => {
5160
- fitMode = "width";
5161
- scale = calculateFitScale("width");
6005
+ scale = calculateFitScale(fitMode);
5162
6006
  applyTransform();
5163
6007
  }, 60);
5164
- let currentPage = 1;
5165
- let totalPages = 1;
5166
- let slides = [];
5167
6008
  if (isPresentation) {
5168
6009
  wrapper.style.maxWidth = "960px";
5169
6010
  wrapper.style.aspectRatio = "16 / 9";
@@ -5264,24 +6105,9 @@ var OpenDocumentPlugin = class {
5264
6105
  wrapper.appendChild(page);
5265
6106
  slides.push(page);
5266
6107
  });
5267
- const pageIndicator = document.createElement("div");
5268
- pageIndicator.className = "fp-odt-page-indicator";
5269
- pageIndicator.style.position = "sticky";
5270
- pageIndicator.style.bottom = "16px";
5271
- pageIndicator.style.left = "50%";
5272
- pageIndicator.style.transform = "translateX(-50%)";
5273
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5274
- pageIndicator.style.color = "#fff";
5275
- pageIndicator.style.padding = "6px 12px";
5276
- pageIndicator.style.borderRadius = "16px";
5277
- pageIndicator.style.fontSize = "12px";
5278
- pageIndicator.style.zIndex = "100";
5279
- pageIndicator.style.display = "inline-block";
5280
- pageIndicator.style.width = "fit-content";
5281
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5282
- container.appendChild(pageIndicator);
5283
6108
  }
5284
6109
  const cleanup = () => {
6110
+ resizeObserver?.disconnect();
5285
6111
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5286
6112
  container.remove();
5287
6113
  ctx.container.innerHTML = "";
@@ -5293,10 +6119,6 @@ var OpenDocumentPlugin = class {
5293
6119
  slides.forEach((s, idx) => {
5294
6120
  s.style.display = idx === page - 1 ? "block" : "none";
5295
6121
  });
5296
- const indicator = container.querySelector(".fp-odt-page-indicator");
5297
- if (indicator) {
5298
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5299
- }
5300
6122
  container.scrollTop = 0;
5301
6123
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5302
6124
  };
@@ -5304,21 +6126,25 @@ var OpenDocumentPlugin = class {
5304
6126
  destroy: cleanup,
5305
6127
  isPresentation,
5306
6128
  zoomIn: () => {
6129
+ isUserZoomed = true;
5307
6130
  scale += 0.15;
5308
6131
  applyTransform();
5309
6132
  },
5310
6133
  zoomOut: () => {
6134
+ isUserZoomed = true;
5311
6135
  scale = Math.max(0.2, scale - 0.15);
5312
6136
  applyTransform();
5313
6137
  },
5314
6138
  getZoom: () => scale,
5315
6139
  setZoom: (level) => {
6140
+ isUserZoomed = true;
5316
6141
  scale = level;
5317
6142
  applyTransform();
5318
6143
  },
5319
6144
  fitToPage: () => {
5320
- fitMode = fitMode === "width" ? "page" : "width";
5321
- scale = calculateFitScale(fitMode);
6145
+ isUserZoomed = false;
6146
+ fitMode = "width";
6147
+ scale = calculateFitScale("width");
5322
6148
  rotation = 0;
5323
6149
  applyTransform();
5324
6150
  },
@@ -5334,23 +6160,45 @@ var OpenDocumentPlugin = class {
5334
6160
  getPageCount: () => totalPages,
5335
6161
  getCurrentPage: () => currentPage,
5336
6162
  getThumbnails: async () => {
5337
- if (!isPresentation) return [];
5338
- return slides.map((_, idx) => ({
5339
- index: idx,
5340
- label: `Slide ${idx + 1}`,
5341
- render: async (canvas) => {
5342
- const ctx2d = canvas.getContext("2d");
5343
- if (!ctx2d) return;
5344
- canvas.width = 160;
5345
- canvas.height = 90;
5346
- ctx2d.fillStyle = "#f8fafc";
5347
- ctx2d.fillRect(0, 0, 160, 90);
5348
- ctx2d.fillStyle = "#334155";
5349
- ctx2d.font = "bold 12px sans-serif";
5350
- ctx2d.textAlign = "center";
5351
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5352
- }
5353
- }));
6163
+ const count = totalPages || slides.length || 1;
6164
+ const items = [];
6165
+ for (let idx = 0; idx < count; idx++) {
6166
+ const itemIdx = idx;
6167
+ items.push({
6168
+ index: itemIdx,
6169
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6170
+ render: async (canvas) => {
6171
+ const ctx2d = canvas.getContext("2d");
6172
+ if (!ctx2d) return;
6173
+ if (isPresentation) {
6174
+ canvas.width = 160;
6175
+ canvas.height = 90;
6176
+ ctx2d.fillStyle = "#f8fafc";
6177
+ ctx2d.fillRect(0, 0, 160, 90);
6178
+ ctx2d.strokeStyle = "#cbd5e1";
6179
+ ctx2d.lineWidth = 1;
6180
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6181
+ ctx2d.fillStyle = "#334155";
6182
+ ctx2d.font = "bold 11px sans-serif";
6183
+ ctx2d.textAlign = "center";
6184
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6185
+ } else {
6186
+ canvas.width = 140;
6187
+ canvas.height = 180;
6188
+ ctx2d.fillStyle = "#ffffff";
6189
+ ctx2d.fillRect(0, 0, 140, 180);
6190
+ ctx2d.strokeStyle = "#cbd5e1";
6191
+ ctx2d.lineWidth = 1;
6192
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6193
+ ctx2d.fillStyle = "#64748b";
6194
+ ctx2d.font = "bold 10px sans-serif";
6195
+ ctx2d.textAlign = "center";
6196
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6197
+ }
6198
+ }
6199
+ });
6200
+ }
6201
+ return items;
5354
6202
  },
5355
6203
  download: () => {
5356
6204
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5455,23 +6303,24 @@ var OpenDocumentPlugin = class {
5455
6303
  case "h": {
5456
6304
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5457
6305
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5458
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6306
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5459
6307
  break;
5460
6308
  }
5461
6309
  case "p": {
5462
6310
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5463
6311
  const inner = this.renderSpans(node, styles, images);
5464
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6312
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6313
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5465
6314
  break;
5466
6315
  }
5467
6316
  case "list": {
5468
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6317
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5469
6318
  Array.from(node.children).forEach((c) => walk(c));
5470
6319
  html += "</ul>";
5471
6320
  break;
5472
6321
  }
5473
6322
  case "list-item": {
5474
- html += "<li>";
6323
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5475
6324
  Array.from(node.children).forEach((c) => walk(c));
5476
6325
  html += "</li>";
5477
6326
  break;
@@ -5597,28 +6446,26 @@ var DocPlugin = class {
5597
6446
  getToolbarActions(instance) {
5598
6447
  const totalPages = instance.getPageCount?.() ?? 1;
5599
6448
  const actions = [];
5600
- if (totalPages > 1) {
5601
- actions.push({
5602
- id: "page-nav",
5603
- icon: "",
5604
- label: "Page Navigation",
5605
- type: "page-nav",
5606
- group: "navigation",
5607
- value: instance.getCurrentPage?.() ?? 1,
5608
- max: totalPages,
5609
- execute: (action, page) => {
5610
- const cur = instance.getCurrentPage?.() ?? 1;
5611
- const max = instance.getPageCount?.() ?? 1;
5612
- if (action === "prev") {
5613
- if (cur > 1) instance.goToPage?.(cur - 1);
5614
- } else if (action === "next") {
5615
- if (cur < max) instance.goToPage?.(cur + 1);
5616
- } else if (typeof page === "number") {
5617
- instance.goToPage?.(page);
5618
- }
6449
+ actions.push({
6450
+ id: "page-nav",
6451
+ icon: "",
6452
+ label: "Page Navigation",
6453
+ type: "page-nav",
6454
+ group: "navigation",
6455
+ value: instance.getCurrentPage?.() ?? 1,
6456
+ max: totalPages,
6457
+ execute: (action, page) => {
6458
+ const cur = instance.getCurrentPage?.() ?? 1;
6459
+ const max = instance.getPageCount?.() ?? 1;
6460
+ if (action === "prev") {
6461
+ if (cur > 1) instance.goToPage?.(cur - 1);
6462
+ } else if (action === "next") {
6463
+ if (cur < max) instance.goToPage?.(cur + 1);
6464
+ } else if (typeof page === "number") {
6465
+ instance.goToPage?.(page);
5619
6466
  }
5620
- });
5621
- }
6467
+ }
6468
+ });
5622
6469
  actions.push(
5623
6470
  {
5624
6471
  id: "zoom-out",
@@ -5692,8 +6539,9 @@ var DocPlugin = class {
5692
6539
  container.className = "fp-doc-container";
5693
6540
  container.style.width = "100%";
5694
6541
  container.style.height = "100%";
5695
- container.style.overflow = "auto";
5696
- container.style.padding = "32px 16px";
6542
+ container.style.overflowX = "hidden";
6543
+ container.style.overflowY = "auto";
6544
+ container.style.padding = "16px 8px";
5697
6545
  container.style.backgroundColor = "#f1f5f9";
5698
6546
  container.style.display = "flex";
5699
6547
  container.style.justifyContent = "center";
@@ -5702,8 +6550,17 @@ var DocPlugin = class {
5702
6550
  let scale = 1;
5703
6551
  let extractedRawText = "";
5704
6552
  let isFallback = false;
6553
+ let chartSvg = "";
5705
6554
  try {
5706
6555
  const cfbf = new CfbfReader(ctx.buffer);
6556
+ try {
6557
+ const pkg = cfbf.readStream("package_stream");
6558
+ if (pkg && pkg.length > 100) {
6559
+ chartSvg = this.parseOdfChartToSvg(pkg);
6560
+ }
6561
+ } catch (chartErr) {
6562
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
6563
+ }
5707
6564
  const wordDocStream = cfbf.readStream("WordDocument");
5708
6565
  if (!wordDocStream || wordDocStream.length < 512) {
5709
6566
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5721,7 +6578,7 @@ var DocPlugin = class {
5721
6578
  extractedRawText = fallback;
5722
6579
  isFallback = true;
5723
6580
  }
5724
- const rawPages = this.splitIntoPages(extractedRawText);
6581
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5725
6582
  const totalPages = Math.max(1, rawPages.length);
5726
6583
  let currentPage = 1;
5727
6584
  const pageCards = [];
@@ -5729,16 +6586,15 @@ var DocPlugin = class {
5729
6586
  const pageCard = document.createElement("div");
5730
6587
  pageCard.className = "fp-doc-page-card";
5731
6588
  pageCard.style.width = "816px";
5732
- pageCard.style.height = "1056px";
6589
+ pageCard.style.minHeight = "1056px";
5733
6590
  pageCard.style.backgroundColor = "#ffffff";
5734
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6591
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5735
6592
  pageCard.style.borderRadius = "4px";
5736
- pageCard.style.padding = "96px 72px";
6593
+ pageCard.style.padding = "72px 56px";
5737
6594
  pageCard.style.boxSizing = "border-box";
5738
- pageCard.style.overflow = "hidden";
5739
6595
  pageCard.style.display = i === 0 ? "block" : "none";
5740
6596
  pageCard.style.transformOrigin = "top center";
5741
- pageCard.style.transition = "transform 0.2s ease";
6597
+ pageCard.style.transition = "transform 0.15s ease";
5742
6598
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5743
6599
  pageCard.style.color = "#1e293b";
5744
6600
  if (isFallback && i === 0) {
@@ -5755,34 +6611,15 @@ var DocPlugin = class {
5755
6611
  container.appendChild(pageCard);
5756
6612
  pageCards.push(pageCard);
5757
6613
  }
5758
- let indicator = null;
5759
- if (totalPages > 1) {
5760
- indicator = document.createElement("div");
5761
- indicator.className = "fp-doc-page-indicator";
5762
- indicator.style.position = "fixed";
5763
- indicator.style.bottom = "16px";
5764
- indicator.style.left = "50%";
5765
- indicator.style.transform = "translateX(-50%)";
5766
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5767
- indicator.style.backdropFilter = "blur(8px)";
5768
- indicator.style.color = "#f8fafc";
5769
- indicator.style.fontSize = "12px";
5770
- indicator.style.fontWeight = "600";
5771
- indicator.style.padding = "5px 14px";
5772
- indicator.style.borderRadius = "20px";
5773
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5774
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5775
- indicator.style.zIndex = "10";
5776
- indicator.style.userSelect = "none";
5777
- indicator.style.pointerEvents = "none";
5778
- indicator.style.textAlign = "center";
5779
- container.appendChild(indicator);
5780
- }
5781
6614
  let rotation = 0;
5782
6615
  const applyTransform = () => {
5783
6616
  const activeCard = pageCards[currentPage - 1];
5784
6617
  if (activeCard) {
5785
6618
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6619
+ const baseH = activeCard.offsetHeight || 1056;
6620
+ const scaledH = baseH * scale;
6621
+ const extraH = Math.max(0, scaledH - baseH);
6622
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5786
6623
  }
5787
6624
  };
5788
6625
  const showPage = (pageNum) => {
@@ -5795,35 +6632,39 @@ var DocPlugin = class {
5795
6632
  card.style.display = "none";
5796
6633
  }
5797
6634
  });
5798
- if (indicator) {
5799
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5800
- }
5801
6635
  container.scrollTop = 0;
5802
6636
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5803
6637
  };
5804
6638
  if (totalPages > 1) {
5805
6639
  showPage(1);
5806
6640
  }
5807
- let fitMode = "width";
6641
+ let fitMode = ctx?.options?.fitMode || "width";
6642
+ let isUserZoomed = false;
5808
6643
  const calculateFitScale = (mode = fitMode) => {
5809
6644
  const elW = 816;
5810
6645
  const elH = 1056;
5811
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5812
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6646
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6647
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5813
6648
  const sW = availW / elW;
5814
6649
  const sH = availH / elH;
5815
6650
  if (mode === "page") {
5816
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6651
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5817
6652
  }
5818
- return Math.max(0.65, Math.min(1.05, sW));
6653
+ return Math.max(0.4, Math.min(3, sW));
5819
6654
  };
6655
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6656
+ if (!isUserZoomed) {
6657
+ scale = calculateFitScale(fitMode);
6658
+ applyTransform();
6659
+ }
6660
+ }) : null;
6661
+ resizeObserver?.observe(ctx.container);
5820
6662
  setTimeout(() => {
5821
- fitMode = "width";
5822
- scale = calculateFitScale("width");
6663
+ scale = calculateFitScale(fitMode);
5823
6664
  applyTransform();
5824
6665
  }, 60);
5825
6666
  const cleanup = () => {
5826
- indicator?.remove();
6667
+ resizeObserver?.disconnect();
5827
6668
  container.remove();
5828
6669
  ctx.container.innerHTML = "";
5829
6670
  };
@@ -5834,21 +6675,25 @@ var DocPlugin = class {
5834
6675
  getCurrentPage: () => currentPage,
5835
6676
  goToPage: (page) => showPage(page),
5836
6677
  zoomIn: () => {
6678
+ isUserZoomed = true;
5837
6679
  scale += 0.15;
5838
6680
  applyTransform();
5839
6681
  },
5840
6682
  zoomOut: () => {
6683
+ isUserZoomed = true;
5841
6684
  scale = Math.max(0.2, scale - 0.15);
5842
6685
  applyTransform();
5843
6686
  },
5844
6687
  getZoom: () => scale,
5845
6688
  setZoom: (level) => {
6689
+ isUserZoomed = true;
5846
6690
  scale = level;
5847
6691
  applyTransform();
5848
6692
  },
5849
6693
  fitToPage: () => {
5850
- fitMode = fitMode === "width" ? "page" : "width";
5851
- scale = calculateFitScale(fitMode);
6694
+ isUserZoomed = false;
6695
+ fitMode = "width";
6696
+ scale = calculateFitScale("width");
5852
6697
  rotation = 0;
5853
6698
  applyTransform();
5854
6699
  },
@@ -5963,7 +6808,7 @@ var DocPlugin = class {
5963
6808
  for (const run of [...ansiRuns, ...utf16Runs]) {
5964
6809
  const trimmed = run.trim();
5965
6810
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
5966
- if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
6811
+ if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^EMBED\b/i.test(trimmed) && !trimmed.includes("ChartDocument") && !/^[\W_0-9]+$/.test(trimmed)) {
5967
6812
  seen.add(trimmed);
5968
6813
  candidateLines.push(trimmed);
5969
6814
  }
@@ -5974,9 +6819,114 @@ var DocPlugin = class {
5974
6819
  heuristicTextExtraction(buffer) {
5975
6820
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5976
6821
  }
5977
- cleanWordDocFields(text) {
6822
+ /**
6823
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
6824
+ */
6825
+ parseOdfChartToSvg(zipBytes) {
6826
+ try {
6827
+ const unzipped = fflate__namespace.unzipSync(zipBytes);
6828
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
6829
+ if (!contentXml) return "";
6830
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
6831
+ if (rowsMatch.length < 2) return "";
6832
+ const headers = [];
6833
+ const firstRow = rowsMatch[0];
6834
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6835
+ for (const h of headerCells) {
6836
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6837
+ }
6838
+ const categories = [];
6839
+ const seriesValues = headers.map(() => []);
6840
+ for (let r = 1; r < rowsMatch.length; r++) {
6841
+ const rowStr = rowsMatch[r];
6842
+ if (!rowStr) continue;
6843
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6844
+ if (cells.length > 0 && cells[0]) {
6845
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6846
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6847
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6848
+ const cellStr = cells[c];
6849
+ if (!cellStr) continue;
6850
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6851
+ const series = seriesValues[c - 1];
6852
+ if (series) {
6853
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6854
+ }
6855
+ }
6856
+ }
6857
+ }
6858
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6859
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6860
+ let cIdx = 0;
6861
+ for (const cm of colorMatches) {
6862
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6863
+ cIdx++;
6864
+ }
6865
+ let maxVal = 10;
6866
+ for (const s of seriesValues) {
6867
+ for (const v of s) {
6868
+ if (v > maxVal) maxVal = v;
6869
+ }
6870
+ }
6871
+ maxVal = Math.ceil(maxVal * 1.15);
6872
+ const width = 560;
6873
+ const height = 280;
6874
+ const padLeft = 45;
6875
+ const padRight = 100;
6876
+ const padTop = 20;
6877
+ const padBottom = 40;
6878
+ const chartW = width - padLeft - padRight;
6879
+ const chartH = height - padTop - padBottom;
6880
+ let svg = `<svg viewBox="0 0 ${width} ${height}" width="100%" height="auto" style="max-width: 560px; height: 280px; margin: 16px auto; display: block; font-family: Calibri, sans-serif; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 6px; box-shadow: 0 1px 4px rgba(0,0,0,0.05);">`;
6881
+ for (let step = 0; step <= 4; step++) {
6882
+ const yVal = (maxVal / 4 * step).toFixed(1);
6883
+ const yPos = padTop + chartH - step / 4 * chartH;
6884
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6885
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6886
+ }
6887
+ const numCats = categories.length;
6888
+ const numSeries = headers.length;
6889
+ const groupW = chartW / numCats;
6890
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6891
+ const groupPad = (groupW - barW * numSeries) / 2;
6892
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6893
+ const groupX = padLeft + catIdx * groupW + groupPad;
6894
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6895
+ const val = seriesValues[sIdx][catIdx] || 0;
6896
+ const barH = val / maxVal * chartH;
6897
+ const barX = groupX + sIdx * barW;
6898
+ const barY = padTop + chartH - barH;
6899
+ const col = colors[sIdx % colors.length];
6900
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6901
+ }
6902
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6903
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6904
+ }
6905
+ let legendY = padTop + 20;
6906
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6907
+ const col = colors[sIdx % colors.length];
6908
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6909
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6910
+ legendY += 20;
6911
+ }
6912
+ svg += "</svg>";
6913
+ return svg;
6914
+ } catch (e) {
6915
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6916
+ return "";
6917
+ }
6918
+ }
6919
+ cleanWordDocFields(text, chartSvg = "") {
5978
6920
  if (!text) return "";
5979
6921
  let cleaned = text.replace(
6922
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6923
+ () => chartSvg ? `
6924
+
6925
+ ${chartSvg}
6926
+
6927
+ ` : ""
6928
+ );
6929
+ cleaned = cleaned.replace(
5980
6930
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5981
6931
  (_match, url, label) => {
5982
6932
  const cleanUrl = url.trim();
@@ -5984,18 +6934,23 @@ var DocPlugin = class {
5984
6934
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5985
6935
  }
5986
6936
  );
5987
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6937
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6938
+ if (/[\x00-\x1F]/.test(res)) return "";
6939
+ return res.trim();
6940
+ });
5988
6941
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
5989
6942
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6943
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6944
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
5990
6945
  return cleaned;
5991
6946
  }
5992
- splitIntoPages(text) {
6947
+ splitIntoPages(text, chartSvg = "") {
5993
6948
  if (!text) return [""];
5994
- const cleanedText = this.cleanWordDocFields(text);
6949
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
5995
6950
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
5996
6951
  const explicitParts = normalized.split(/[\x0C\f]|\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
5997
6952
  if (explicitParts.length === 0) explicitParts.push(normalized);
5998
- const maxLinesPerPage = 32;
6953
+ const maxLinesPerPage = 34;
5999
6954
  const charsPerLine = 80;
6000
6955
  const finalPages = [];
6001
6956
  for (const part of explicitParts) {
@@ -6003,8 +6958,8 @@ var DocPlugin = class {
6003
6958
  let currentLines = [];
6004
6959
  let count = 0;
6005
6960
  for (const line of lines) {
6006
- const plainLine = line.replace(/<[^>]+>/g, "");
6007
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6961
+ const isSvg = line.includes("<svg");
6962
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
6008
6963
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
6009
6964
  finalPages.push(currentLines.join("\n"));
6010
6965
  currentLines = [];
@@ -6044,9 +6999,44 @@ var DocPlugin = class {
6044
6999
  tableLines = [];
6045
7000
  }
6046
7001
  };
7002
+ const sanitizeOptions = {
7003
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
7004
+ ADD_ATTR: [
7005
+ "href",
7006
+ "target",
7007
+ "rel",
7008
+ "style",
7009
+ "viewBox",
7010
+ "width",
7011
+ "height",
7012
+ "x",
7013
+ "y",
7014
+ "x1",
7015
+ "y1",
7016
+ "x2",
7017
+ "y2",
7018
+ "fill",
7019
+ "stroke",
7020
+ "stroke-width",
7021
+ "stroke-dasharray",
7022
+ "rx",
7023
+ "font-size",
7024
+ "text-anchor"
7025
+ ]
7026
+ };
6047
7027
  let i = 0;
6048
7028
  while (i < lines.length) {
6049
7029
  let line = lines[i];
7030
+ if (line.includes("<svg")) {
7031
+ if (inList) {
7032
+ html += "</ul>";
7033
+ inList = false;
7034
+ }
7035
+ flushTable();
7036
+ html += line;
7037
+ i++;
7038
+ continue;
7039
+ }
6050
7040
  let tabCount = (line.match(/\t/g) || []).length;
6051
7041
  if (tabCount > 0) {
6052
7042
  let j = i;
@@ -6080,26 +7070,31 @@ var DocPlugin = class {
6080
7070
  i++;
6081
7071
  continue;
6082
7072
  }
6083
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6084
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7073
+ if (/^\d{1,2}$/.test(line.trim())) {
7074
+ i++;
7075
+ continue;
7076
+ }
7077
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7078
+ 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));
7079
+ if (isTitleLike) {
6085
7080
  if (inList) {
6086
7081
  html += "</ul>";
6087
7082
  inList = false;
6088
7083
  }
6089
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</h2>`;
7084
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 18px 0 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</h2>`;
6090
7085
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6091
7086
  if (!inList) {
6092
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7087
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6093
7088
  inList = true;
6094
7089
  }
6095
7090
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6096
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
7091
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6__default.default.sanitize(bulletText, sanitizeOptions)}</li>`;
6097
7092
  } else {
6098
7093
  if (inList) {
6099
7094
  html += "</ul>";
6100
7095
  inList = false;
6101
7096
  }
6102
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
7097
+ html += `<p style="line-height: 1.5; margin: 0 0 12px 0; font-size: 11pt; color: #1e293b; text-align: justify;">${DOMPurify6__default.default.sanitize(line, sanitizeOptions)}</p>`;
6103
7098
  }
6104
7099
  i++;
6105
7100
  }