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