@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/angular.js CHANGED
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
3
3
  import DOMPurify6 from 'dompurify';
4
4
  import * as pdfjsLib from 'pdfjs-dist';
5
5
  import * as docx from 'docx-preview';
6
+ import * as fflate from 'fflate';
6
7
  import { unzipSync, strFromU8, unzip } from 'fflate';
7
8
  import * as XLSX from 'xlsx';
8
9
  import hljs from 'highlight.js';
@@ -541,8 +542,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
541
542
  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>`;
542
543
  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>`;
543
544
  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>`;
544
- 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>`;
545
- 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>`;
545
+ 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>`;
546
546
  var ICON_MAP = {
547
547
  "zoom-in": ICON_ZOOM_IN,
548
548
  "zoom-out": ICON_ZOOM_OUT,
@@ -569,28 +569,177 @@ var ICON_MAP = {
569
569
  "forward-10": ICON_FAST_FORWARD,
570
570
  "rewind": ICON_REWIND,
571
571
  "replay-10": ICON_REWIND,
572
- "speed": ICON_SPEED,
573
572
  "open-window": ICON_EXTERNAL_WINDOW,
574
- "external-window": ICON_EXTERNAL_WINDOW
573
+ "external-window": ICON_EXTERNAL_WINDOW,
574
+ "openWindow": ICON_EXTERNAL_WINDOW,
575
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
575
576
  };
576
577
  var ToolbarController = class {
577
578
  el;
578
579
  toolbarEl;
579
580
  actions = [];
581
+ config = {};
580
582
  pageInputEl = null;
581
583
  pageLabelEl = null;
582
- constructor(container) {
584
+ prevPageBtn = null;
585
+ nextPageBtn = null;
586
+ constructor(container, config) {
583
587
  this.el = container;
588
+ if (config) this.config = { ...config };
584
589
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
585
590
  this.el.appendChild(this.toolbarEl);
586
591
  }
592
+ setConfig(config) {
593
+ this.config = { ...config };
594
+ this.render();
595
+ }
596
+ getConfig() {
597
+ return { ...this.config };
598
+ }
599
+ hideAction(actionId) {
600
+ this.config[actionId] = false;
601
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
602
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
603
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
604
+ this.config.fitPage = false;
605
+ this.config.fitToPage = false;
606
+ this.config.fitWidth = false;
607
+ }
608
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
609
+ if (actionId === "fullscreen") this.config.fullscreen = false;
610
+ if (actionId === "download") this.config.download = false;
611
+ if (actionId === "print") this.config.print = false;
612
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
613
+ this.config.openWindow = false;
614
+ this.config.openSeparateWindow = false;
615
+ }
616
+ if (actionId === "copy") this.config.copy = false;
617
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
618
+ this.config.pageNav = false;
619
+ this.config.pagination = false;
620
+ }
621
+ this.render();
622
+ }
623
+ showAction(actionId) {
624
+ this.config[actionId] = true;
625
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
626
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
627
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
628
+ this.config.fitPage = true;
629
+ this.config.fitToPage = true;
630
+ this.config.fitWidth = true;
631
+ }
632
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
633
+ if (actionId === "fullscreen") this.config.fullscreen = true;
634
+ if (actionId === "download") this.config.download = true;
635
+ if (actionId === "print") this.config.print = true;
636
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
637
+ this.config.openWindow = true;
638
+ this.config.openSeparateWindow = true;
639
+ }
640
+ if (actionId === "copy") this.config.copy = true;
641
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
642
+ this.config.pageNav = true;
643
+ this.config.pagination = true;
644
+ }
645
+ this.render();
646
+ }
647
+ normalizeActionId(actionId) {
648
+ if (actionId === "zoomIn") return "zoom-in";
649
+ if (actionId === "zoomOut") return "zoom-out";
650
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
651
+ if (actionId === "rotateCW") return "rotate-cw";
652
+ if (actionId === "rotateCCW") return "rotate-ccw";
653
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
654
+ if (actionId === "pageNav") return "page-nav";
655
+ return actionId;
656
+ }
657
+ enableAction(actionId) {
658
+ const norm = this.normalizeActionId(actionId);
659
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
660
+ if (btn) {
661
+ btn.disabled = false;
662
+ btn.classList.remove("disabled");
663
+ }
664
+ }
665
+ disableAction(actionId) {
666
+ const norm = this.normalizeActionId(actionId);
667
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
668
+ if (btn) {
669
+ btn.disabled = true;
670
+ btn.classList.add("disabled");
671
+ }
672
+ }
673
+ setActionActive(actionId, active) {
674
+ const norm = this.normalizeActionId(actionId);
675
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
676
+ if (btn) {
677
+ if (active) {
678
+ btn.classList.add("active");
679
+ } else {
680
+ btn.classList.remove("active");
681
+ }
682
+ }
683
+ }
684
+ isActionEnabled(action) {
685
+ const c = this.config;
686
+ const id = action.id;
687
+ if (c[id] === false) return false;
688
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
689
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
690
+ 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)) {
691
+ return false;
692
+ }
693
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
694
+ return false;
695
+ }
696
+ if (id === "fullscreen" && c.fullscreen === false) return false;
697
+ if (id === "download" && c.download === false) return false;
698
+ if (id === "print" && c.print === false) return false;
699
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
700
+ return false;
701
+ }
702
+ if (id === "copy" && c.copy === false) return false;
703
+ if (id === "thumbnails" && c.thumbnails === false) return false;
704
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
705
+ return false;
706
+ }
707
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
708
+ return false;
709
+ }
710
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
711
+ return false;
712
+ }
713
+ if (id === "play" && c.play === false) return false;
714
+ if (id === "pause" && c.pause === false) return false;
715
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
716
+ return false;
717
+ }
718
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
719
+ return false;
720
+ }
721
+ if (id === "speed" && c.speed === false) return false;
722
+ return true;
723
+ }
587
724
  setPage(page, max) {
588
725
  if (this.pageInputEl) {
589
726
  this.pageInputEl.value = page.toString();
727
+ if (max !== void 0) {
728
+ this.pageInputEl.max = max.toString();
729
+ }
590
730
  }
591
731
  if (max !== void 0 && this.pageLabelEl) {
592
732
  this.pageLabelEl.textContent = ` / ${max}`;
593
733
  }
734
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
735
+ if (this.prevPageBtn) {
736
+ this.prevPageBtn.disabled = page <= 1;
737
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
738
+ }
739
+ if (this.nextPageBtn) {
740
+ this.nextPageBtn.disabled = page >= currentMax;
741
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
742
+ }
594
743
  }
595
744
  update(actions) {
596
745
  this.actions = actions;
@@ -613,8 +762,9 @@ var ToolbarController = class {
613
762
  view: [],
614
763
  actions: []
615
764
  };
616
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
617
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
765
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
766
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
767
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
618
768
  for (const action of effectiveActions) {
619
769
  if (groups[action.group]) {
620
770
  groups[action.group].push(action);
@@ -674,6 +824,13 @@ var ToolbarController = class {
674
824
  action.execute("go", val);
675
825
  });
676
826
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
827
+ const curVal = action.value ?? 1;
828
+ prevBtn.disabled = curVal <= 1;
829
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
830
+ nextBtn.disabled = curVal >= max;
831
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
832
+ this.prevPageBtn = prevBtn;
833
+ this.nextPageBtn = nextBtn;
677
834
  this.pageInputEl = input;
678
835
  this.pageLabelEl = label;
679
836
  groupEl.appendChild(prevBtn);
@@ -714,9 +871,11 @@ var ToolbarController = class {
714
871
  type: "button",
715
872
  "data-action-id": id
716
873
  });
717
- const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
718
- if (svg) {
719
- btn.innerHTML = sanitizeSVG(svg);
874
+ const internalSvg = ICON_MAP[iconHtml] || ICON_MAP[id];
875
+ if (internalSvg) {
876
+ btn.innerHTML = internalSvg;
877
+ } else if (iconHtml && iconHtml.startsWith("<svg")) {
878
+ btn.innerHTML = sanitizeSVG(iconHtml);
720
879
  } else {
721
880
  btn.textContent = title || id;
722
881
  }
@@ -727,25 +886,63 @@ var ToolbarController = class {
727
886
  var ThumbnailPanel = class {
728
887
  el;
729
888
  panelEl;
889
+ listEl;
890
+ headerEl;
730
891
  thumbnails = [];
731
892
  onSelect;
893
+ onToggleCallback;
732
894
  activeIndex = 0;
733
- constructor(container) {
895
+ observer;
896
+ renderedIndices = /* @__PURE__ */ new Set();
897
+ constructor(container, onToggle) {
734
898
  this.el = container;
899
+ this.onToggleCallback = onToggle;
735
900
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
901
+ this.panelEl.style.width = "0px";
902
+ this.panelEl.style.minWidth = "0px";
903
+ this.panelEl.style.opacity = "0";
904
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
905
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
906
+ const closeBtn = createElement("button", {
907
+ className: "fp-thumbnail-close-btn",
908
+ title: "Close Thumbnails",
909
+ type: "button"
910
+ });
911
+ closeBtn.innerHTML = ICON_CLOSE;
912
+ closeBtn.addEventListener("click", (e) => {
913
+ e.stopPropagation();
914
+ this.hide();
915
+ });
916
+ this.headerEl.appendChild(title);
917
+ this.headerEl.appendChild(closeBtn);
918
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
919
+ this.panelEl.appendChild(this.headerEl);
920
+ this.panelEl.appendChild(this.listEl);
736
921
  this.el.appendChild(this.panelEl);
737
922
  }
923
+ isOpen() {
924
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
925
+ }
738
926
  update(thumbnails, onSelect) {
739
927
  this.thumbnails = thumbnails;
740
928
  this.onSelect = onSelect;
929
+ this.renderedIndices.clear();
930
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
931
+ if (titleEl) {
932
+ titleEl.textContent = `Pages (${thumbnails.length})`;
933
+ }
741
934
  this.render();
935
+ if (this.isOpen()) {
936
+ this.renderAllVisible();
937
+ }
742
938
  }
743
939
  setActive(index) {
744
940
  this.activeIndex = index;
745
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
941
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
746
942
  items.forEach((item, i) => {
747
943
  if (i === index) {
748
944
  item.classList.add("active");
945
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
749
946
  } else {
750
947
  item.classList.remove("active");
751
948
  }
@@ -753,37 +950,102 @@ var ThumbnailPanel = class {
753
950
  }
754
951
  show() {
755
952
  this.panelEl.classList.remove("hidden");
953
+ this.panelEl.style.width = "200px";
954
+ this.panelEl.style.minWidth = "200px";
955
+ this.panelEl.style.opacity = "1";
956
+ this.panelEl.style.display = "flex";
957
+ this.onToggleCallback?.(true);
958
+ this.renderAllVisible();
756
959
  }
757
960
  hide() {
758
961
  this.panelEl.classList.add("hidden");
962
+ this.panelEl.style.width = "0px";
963
+ this.panelEl.style.minWidth = "0px";
964
+ this.panelEl.style.opacity = "0";
965
+ this.onToggleCallback?.(false);
759
966
  }
760
967
  toggle() {
761
- this.panelEl.classList.toggle("hidden");
968
+ if (this.isOpen()) {
969
+ this.hide();
970
+ } else {
971
+ this.show();
972
+ }
762
973
  }
763
974
  destroy() {
975
+ if (this.observer) {
976
+ this.observer.disconnect();
977
+ this.observer = void 0;
978
+ }
979
+ this.renderedIndices.clear();
764
980
  this.el.innerHTML = "";
765
981
  }
766
- async render() {
767
- this.panelEl.innerHTML = "";
982
+ renderAllVisible() {
983
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
984
+ items.forEach((item) => {
985
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
986
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
987
+ this.renderThumbnailItem(idx, item);
988
+ }
989
+ });
990
+ }
991
+ renderThumbnailItem(idx, itemEl) {
992
+ if (this.renderedIndices.has(idx)) return;
993
+ this.renderedIndices.add(idx);
994
+ const canvas = itemEl.querySelector("canvas");
995
+ const thumb = this.thumbnails[idx];
996
+ if (canvas && thumb) {
997
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
998
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
999
+ });
1000
+ }
1001
+ }
1002
+ render() {
1003
+ if (this.observer) {
1004
+ this.observer.disconnect();
1005
+ }
1006
+ this.listEl.innerHTML = "";
1007
+ const useObserver = typeof IntersectionObserver !== "undefined";
1008
+ if (useObserver) {
1009
+ this.observer = new IntersectionObserver(
1010
+ (entries) => {
1011
+ for (const entry of entries) {
1012
+ if (entry.isIntersecting) {
1013
+ const target = entry.target;
1014
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
1015
+ if (idx >= 0) {
1016
+ this.renderThumbnailItem(idx, target);
1017
+ }
1018
+ }
1019
+ }
1020
+ },
1021
+ {
1022
+ root: this.listEl,
1023
+ rootMargin: "120px 0px 120px 0px"
1024
+ }
1025
+ );
1026
+ }
768
1027
  for (let i = 0; i < this.thumbnails.length; i++) {
769
1028
  const thumb = this.thumbnails[i];
770
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
771
- if (i === this.activeIndex) {
772
- itemEl.classList.add("active");
773
- }
1029
+ const itemEl = createElement("div", {
1030
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
1031
+ });
1032
+ itemEl.dataset.thumbIndex = i.toString();
774
1033
  itemEl.addEventListener("click", () => {
775
1034
  this.setActive(i);
776
1035
  this.onSelect?.(i);
777
1036
  });
778
- const canvas = createElement("canvas", { width: "150", height: "200" });
779
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
780
- itemEl.appendChild(canvas);
1037
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
1038
+ const canvas = createElement("canvas", { width: "130", height: "170" });
1039
+ canvasWrapper.appendChild(canvas);
1040
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
1041
+ itemEl.appendChild(canvasWrapper);
781
1042
  itemEl.appendChild(label);
782
- this.panelEl.appendChild(itemEl);
783
- try {
784
- await thumb.render(canvas);
785
- } catch (err) {
786
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
1043
+ this.listEl.appendChild(itemEl);
1044
+ if (this.observer) {
1045
+ this.observer.observe(itemEl);
1046
+ }
1047
+ if (i < 4) {
1048
+ this.renderThumbnailItem(i, itemEl);
787
1049
  }
788
1050
  }
789
1051
  }
@@ -804,6 +1066,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
804
1066
  currentOptions = {};
805
1067
  resizeObserver = null;
806
1068
  fullscreenHandler = null;
1069
+ titleBarEl = null;
807
1070
  /**
808
1071
  * Register a preview plugin.
809
1072
  */
@@ -840,6 +1103,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
840
1103
  }
841
1104
  this.currentBuffer = buffer.slice(0);
842
1105
  this.currentMetadata = metadata;
1106
+ this.updateTitleBar(options, metadata);
843
1107
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
844
1108
  const fileInfo = { metadata, buffer };
845
1109
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -863,15 +1127,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
863
1127
  if (event === "page-change" && payload && typeof payload.page === "number") {
864
1128
  const total = payload.total ?? payload.totalPages;
865
1129
  this.toolbar?.setPage(payload.page, total);
1130
+ this.thumbnailPanel?.setActive(payload.page - 1);
866
1131
  }
867
1132
  this.eventEmitter.emit(event, payload);
868
- }
1133
+ },
1134
+ toggleThumbnails: () => this.toggleThumbnails()
869
1135
  });
870
1136
  this.activeInstance = instance;
871
1137
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1138
+ instance.toggleThumbnails = () => this.toggleThumbnails();
872
1139
  this.hideLoading();
873
1140
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
874
- if (options.showToolbar !== false && this.toolbar) {
1141
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1142
+ if (isToolbarVisible && this.toolbar) {
1143
+ const toolbarConfig = this.extractToolbarConfig(options);
1144
+ this.toolbar.setConfig(toolbarConfig);
875
1145
  const actions = matchedPlugin.getToolbarActions(instance);
876
1146
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
877
1147
  if (!hasFullscreen && this.wrapperEl) {
@@ -921,6 +1191,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
921
1191
  }
922
1192
  });
923
1193
  }
1194
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1195
+ if (thumbAction) {
1196
+ thumbAction.execute = () => {
1197
+ this.toggleThumbnails();
1198
+ };
1199
+ }
924
1200
  this.toolbar.update(actions);
925
1201
  this.toolbar.show();
926
1202
  }
@@ -1005,7 +1281,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1005
1281
  if (targetUrl) {
1006
1282
  const newWin2 = window.open(targetUrl, "_blank");
1007
1283
  if (!newWin2) {
1008
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1284
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1009
1285
  return null;
1010
1286
  }
1011
1287
  return newWin2;
@@ -1013,7 +1289,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1013
1289
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
1014
1290
  const newWin = window.open("", "_blank");
1015
1291
  if (!newWin) {
1016
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1292
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
1017
1293
  return null;
1018
1294
  }
1019
1295
  newWin.document.title = title;
@@ -1090,6 +1366,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1090
1366
  this.currentBuffer = null;
1091
1367
  this.currentMetadata = null;
1092
1368
  this.currentContainer = null;
1369
+ this.titleBarEl = null;
1093
1370
  }
1094
1371
  /**
1095
1372
  * Get the currently active preview instance.
@@ -1097,7 +1374,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1097
1374
  getInstance() {
1098
1375
  return this.activeInstance;
1099
1376
  }
1377
+ // --- Toolbar Configuration & Feature Toggles ---
1378
+ /**
1379
+ * Configure toolbar features dynamically through options or methods.
1380
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1381
+ */
1382
+ setToolbarConfig(config) {
1383
+ this.toolbar?.setConfig(config);
1384
+ }
1385
+ /**
1386
+ * Get current toolbar configuration.
1387
+ */
1388
+ getToolbarConfig() {
1389
+ return this.toolbar?.getConfig() ?? {};
1390
+ }
1391
+ /**
1392
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1393
+ */
1394
+ hideToolbarAction(actionId) {
1395
+ this.toolbar?.hideAction(actionId);
1396
+ }
1397
+ /**
1398
+ * Show a specific toolbar action by ID or alias.
1399
+ */
1400
+ showToolbarAction(actionId) {
1401
+ this.toolbar?.showAction(actionId);
1402
+ }
1403
+ /**
1404
+ * Enable a specific toolbar action button.
1405
+ */
1406
+ enableToolbarAction(actionId) {
1407
+ this.toolbar?.enableAction(actionId);
1408
+ }
1409
+ /**
1410
+ * Disable a specific toolbar action button.
1411
+ */
1412
+ disableToolbarAction(actionId) {
1413
+ this.toolbar?.disableAction(actionId);
1414
+ }
1415
+ // --- Programmatic Toolbar Action Methods ---
1416
+ /**
1417
+ * Fit document to page so it fills the frame width with minimal margins.
1418
+ */
1419
+ fitToPage() {
1420
+ this.activeInstance?.fitToPage?.();
1421
+ }
1422
+ /**
1423
+ * Zoom in.
1424
+ */
1425
+ zoomIn() {
1426
+ this.activeInstance?.zoomIn?.();
1427
+ }
1428
+ /**
1429
+ * Zoom out.
1430
+ */
1431
+ zoomOut() {
1432
+ this.activeInstance?.zoomOut?.();
1433
+ }
1434
+ /**
1435
+ * Set specific zoom level.
1436
+ */
1437
+ setZoom(level) {
1438
+ this.activeInstance?.setZoom?.(level);
1439
+ }
1440
+ /**
1441
+ * Get current zoom level.
1442
+ */
1443
+ getZoom() {
1444
+ return this.activeInstance?.getZoom?.() ?? 1;
1445
+ }
1446
+ /**
1447
+ * Rotate 90 degrees clockwise.
1448
+ */
1449
+ rotateCW() {
1450
+ this.activeInstance?.rotateCW?.();
1451
+ }
1452
+ /**
1453
+ * Rotate 90 degrees counter-clockwise.
1454
+ */
1455
+ rotateCCW() {
1456
+ this.activeInstance?.rotateCCW?.();
1457
+ }
1458
+ /**
1459
+ * Navigate to a specific page number.
1460
+ */
1461
+ goToPage(page) {
1462
+ this.activeInstance?.goToPage?.(page);
1463
+ this.toolbar?.setPage(page);
1464
+ }
1465
+ /**
1466
+ * Navigate to next page.
1467
+ */
1468
+ nextPage() {
1469
+ const cur = this.getCurrentPage();
1470
+ const max = this.getPageCount();
1471
+ if (cur < max) {
1472
+ this.goToPage(cur + 1);
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Navigate to previous page.
1477
+ */
1478
+ prevPage() {
1479
+ const cur = this.getCurrentPage();
1480
+ if (cur > 1) {
1481
+ this.goToPage(cur - 1);
1482
+ }
1483
+ }
1484
+ /**
1485
+ * Get total page count.
1486
+ */
1487
+ getPageCount() {
1488
+ return this.activeInstance?.getPageCount?.() ?? 1;
1489
+ }
1490
+ /**
1491
+ * Get current page number.
1492
+ */
1493
+ getCurrentPage() {
1494
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1495
+ }
1496
+ /**
1497
+ * Download the current document.
1498
+ */
1499
+ download() {
1500
+ this.activeInstance?.download?.();
1501
+ }
1502
+ /**
1503
+ * Print the current document.
1504
+ */
1505
+ print() {
1506
+ this.activeInstance?.print?.();
1507
+ }
1508
+ /**
1509
+ * Toggle fullscreen mode.
1510
+ */
1511
+ toggleFullscreen() {
1512
+ try {
1513
+ if (!document.fullscreenElement) {
1514
+ this.wrapperEl?.requestFullscreen?.();
1515
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1516
+ } else {
1517
+ document.exitFullscreen?.();
1518
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1519
+ }
1520
+ } catch {
1521
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1522
+ }
1523
+ setTimeout(() => {
1524
+ this.activeInstance?.fitToPage?.();
1525
+ }, 120);
1526
+ }
1527
+ /**
1528
+ * Toggle thumbnails sidebar panel.
1529
+ */
1530
+ toggleThumbnails() {
1531
+ if (!this.thumbnailPanel) return;
1532
+ this.thumbnailPanel.toggle();
1533
+ const isOpen = this.thumbnailPanel.isOpen();
1534
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1535
+ setTimeout(() => {
1536
+ this.activeInstance?.fitToPage?.();
1537
+ }, 260);
1538
+ }
1539
+ /**
1540
+ * Set whether to display the file name / title bar above the toolbar.
1541
+ */
1542
+ setShowFileName(show) {
1543
+ if (this.currentOptions) {
1544
+ this.currentOptions.showFileName = show;
1545
+ }
1546
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1547
+ }
1548
+ /**
1549
+ * Set or override the displayed file name in the preview panel.
1550
+ */
1551
+ setFileName(name) {
1552
+ if (this.currentOptions) {
1553
+ this.currentOptions.fileName = name;
1554
+ }
1555
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1556
+ }
1100
1557
  // --- Private methods ---
1558
+ updateTitleBar(options, metadata) {
1559
+ if (!this.titleBarEl) return;
1560
+ const show = options?.showFileName !== false;
1561
+ if (!show) {
1562
+ this.titleBarEl.style.display = "none";
1563
+ return;
1564
+ }
1565
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1566
+ if (!name) {
1567
+ this.titleBarEl.style.display = "none";
1568
+ return;
1569
+ }
1570
+ this.titleBarEl.style.display = "flex";
1571
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1572
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1573
+ this.titleBarEl.innerHTML = `
1574
+ <div class="fp-titlebar-info">
1575
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1576
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1577
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1578
+ </div>
1579
+ `;
1580
+ }
1581
+ extractToolbarConfig(options) {
1582
+ if (!options) return {};
1583
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1584
+ return {
1585
+ ...options,
1586
+ ...nested
1587
+ };
1588
+ }
1101
1589
  abort() {
1102
1590
  if (this.abortController) {
1103
1591
  this.abortController.abort();
@@ -1140,17 +1628,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1140
1628
  bodyEl.className = "fp-body";
1141
1629
  bodyEl.appendChild(thumbnailEl);
1142
1630
  bodyEl.appendChild(this.contentEl);
1631
+ const titleBarEl = document.createElement("div");
1632
+ titleBarEl.className = "fp-titlebar";
1633
+ this.titleBarEl = titleBarEl;
1634
+ this.updateTitleBar(options, this.currentMetadata);
1143
1635
  if (toolbarPos === "top") {
1636
+ this.wrapperEl.appendChild(titleBarEl);
1144
1637
  this.wrapperEl.appendChild(toolbarEl);
1145
1638
  this.wrapperEl.appendChild(bodyEl);
1146
1639
  } else {
1640
+ this.wrapperEl.appendChild(titleBarEl);
1147
1641
  this.wrapperEl.appendChild(bodyEl);
1148
1642
  this.wrapperEl.appendChild(toolbarEl);
1149
1643
  }
1150
1644
  container.innerHTML = "";
1151
1645
  container.appendChild(this.wrapperEl);
1152
- this.toolbar = new ToolbarController(toolbarEl);
1153
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1646
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1647
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1648
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1649
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1650
+ setTimeout(() => {
1651
+ this.activeInstance?.fitToPage?.();
1652
+ }, 260);
1653
+ });
1154
1654
  this.setupKeyboardShortcuts();
1155
1655
  this.setupDragAndDrop(container, options);
1156
1656
  this.setupResizeAndFullscreenListeners();
@@ -1540,6 +2040,14 @@ var PdfPlugin = class {
1540
2040
  group: "zoom",
1541
2041
  execute: () => instance.fitToPage?.()
1542
2042
  },
2043
+ {
2044
+ id: "fit-width",
2045
+ icon: "fit-width",
2046
+ label: "Fit to Width",
2047
+ type: "button",
2048
+ group: "zoom",
2049
+ execute: () => instance.fitToWidth?.()
2050
+ },
1543
2051
  {
1544
2052
  id: "rotate-cw",
1545
2053
  icon: "rotate-cw",
@@ -1548,6 +2056,14 @@ var PdfPlugin = class {
1548
2056
  group: "view",
1549
2057
  execute: () => instance.rotateCW?.()
1550
2058
  },
2059
+ {
2060
+ id: "rotate-ccw",
2061
+ icon: "rotate-ccw",
2062
+ label: "Rotate Counter-Clockwise",
2063
+ type: "button",
2064
+ group: "view",
2065
+ execute: () => instance.rotateCCW?.()
2066
+ },
1551
2067
  {
1552
2068
  id: "download",
1553
2069
  icon: "download",
@@ -1579,12 +2095,13 @@ var PdfPlugin = class {
1579
2095
  container.className = "fp-pdf-container";
1580
2096
  container.style.width = "100%";
1581
2097
  container.style.height = "100%";
1582
- container.style.overflow = "auto";
2098
+ container.style.overflowX = "hidden";
2099
+ container.style.overflowY = "auto";
1583
2100
  container.style.display = "flex";
1584
2101
  container.style.flexDirection = "column";
1585
2102
  container.style.alignItems = "center";
1586
2103
  container.style.justifyContent = "flex-start";
1587
- container.style.padding = "20px 16px";
2104
+ container.style.padding = "16px 8px";
1588
2105
  container.style.backgroundColor = "#0f172a";
1589
2106
  container.style.boxSizing = "border-box";
1590
2107
  container.style.position = "relative";
@@ -1595,30 +2112,12 @@ var PdfPlugin = class {
1595
2112
  pageCard.style.borderRadius = "4px";
1596
2113
  pageCard.style.overflow = "hidden";
1597
2114
  pageCard.style.lineHeight = "0";
1598
- pageCard.style.transition = "transform 0.15s ease";
1599
2115
  pageCard.style.position = "relative";
1600
2116
  pageCard.style.flexShrink = "0";
2117
+ pageCard.style.transition = "box-shadow 0.2s ease";
1601
2118
  let canvas = document.createElement("canvas");
1602
2119
  pageCard.appendChild(canvas);
1603
2120
  container.appendChild(pageCard);
1604
- const indicator = document.createElement("div");
1605
- indicator.className = "fp-pdf-page-indicator";
1606
- indicator.style.position = "sticky";
1607
- indicator.style.bottom = "16px";
1608
- indicator.style.marginTop = "16px";
1609
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1610
- indicator.style.backdropFilter = "blur(8px)";
1611
- indicator.style.color = "#f8fafc";
1612
- indicator.style.fontSize = "12px";
1613
- indicator.style.fontWeight = "600";
1614
- indicator.style.padding = "5px 14px";
1615
- indicator.style.borderRadius = "20px";
1616
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1617
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1618
- indicator.style.zIndex = "10";
1619
- indicator.style.userSelect = "none";
1620
- indicator.style.pointerEvents = "none";
1621
- container.appendChild(indicator);
1622
2121
  ctx.container.appendChild(container);
1623
2122
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1624
2123
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1634,8 +2133,9 @@ var PdfPlugin = class {
1634
2133
  let currentPage = 1;
1635
2134
  let zoomScale = 1;
1636
2135
  let rotation = 0;
1637
- let fitMode = "page";
2136
+ let fitMode = ctx?.options?.fitMode || "width";
1638
2137
  let currentRenderTask = null;
2138
+ let textLayerDiv = null;
1639
2139
  const renderPage = async (pageNum) => {
1640
2140
  if (currentRenderTask) {
1641
2141
  try {
@@ -1647,30 +2147,37 @@ var PdfPlugin = class {
1647
2147
  const newCanvas = document.createElement("canvas");
1648
2148
  pageCard.replaceChild(newCanvas, canvas);
1649
2149
  canvas = newCanvas;
2150
+ if (textLayerDiv) {
2151
+ textLayerDiv.remove();
2152
+ textLayerDiv = null;
2153
+ }
1650
2154
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1651
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1652
2155
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1653
2156
  const page = await pdfDoc.getPage(currentPage);
1654
2157
  const containerWidth = container.clientWidth || 900;
1655
2158
  const containerHeight = container.clientHeight || 700;
1656
2159
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1657
- const availWidth = Math.max(320, containerWidth - 48);
1658
- const availHeight = Math.max(550, containerHeight - 88);
2160
+ const availWidth = Math.max(280, containerWidth - 36);
2161
+ const availHeight = Math.max(280, containerHeight - 32);
1659
2162
  const scaleW = availWidth / unscaledVp.width;
1660
2163
  const scaleH = availHeight / unscaledVp.height;
1661
2164
  let fitScale;
1662
2165
  if (fitMode === "page") {
1663
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2166
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1664
2167
  } else {
1665
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2168
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1666
2169
  }
1667
2170
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1668
2171
  const pixelRatio = window.devicePixelRatio || 1;
1669
2172
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2173
+ const displayWidth = Math.floor(viewport.width);
2174
+ const displayHeight = Math.floor(viewport.height);
1670
2175
  canvas.width = Math.floor(viewport.width * pixelRatio);
1671
2176
  canvas.height = Math.floor(viewport.height * pixelRatio);
1672
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1673
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2177
+ canvas.style.width = `${displayWidth}px`;
2178
+ canvas.style.height = `${displayHeight}px`;
2179
+ pageCard.style.width = `${displayWidth}px`;
2180
+ pageCard.style.height = `${displayHeight}px`;
1674
2181
  const canvasCtx = canvas.getContext("2d");
1675
2182
  if (!canvasCtx) return;
1676
2183
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1684,9 +2191,31 @@ var PdfPlugin = class {
1684
2191
  if (err?.name !== "RenderingCancelledException") {
1685
2192
  console.warn("[PdfPlugin] Page render warning:", err);
1686
2193
  }
2194
+ return;
1687
2195
  } finally {
1688
2196
  currentRenderTask = null;
1689
2197
  }
2198
+ try {
2199
+ textLayerDiv = document.createElement("div");
2200
+ textLayerDiv.className = "textLayer";
2201
+ textLayerDiv.style.width = `${displayWidth}px`;
2202
+ textLayerDiv.style.height = `${displayHeight}px`;
2203
+ textLayerDiv.style.position = "absolute";
2204
+ textLayerDiv.style.top = "0";
2205
+ textLayerDiv.style.left = "0";
2206
+ textLayerDiv.style.lineHeight = "1";
2207
+ pageCard.appendChild(textLayerDiv);
2208
+ if (pdfjsLib.TextLayer) {
2209
+ const textLayer = new pdfjsLib.TextLayer({
2210
+ textContentSource: page.streamTextContent(),
2211
+ container: textLayerDiv,
2212
+ viewport
2213
+ });
2214
+ await textLayer.render();
2215
+ }
2216
+ } catch (err) {
2217
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2218
+ }
1690
2219
  };
1691
2220
  renderPage(1);
1692
2221
  let resizeTimer = null;
@@ -1696,11 +2225,48 @@ var PdfPlugin = class {
1696
2225
  if (Math.abs(zoomScale - 1) < 0.05) {
1697
2226
  renderPage(currentPage);
1698
2227
  }
1699
- }, 150);
2228
+ }, 120);
1700
2229
  });
1701
2230
  resizeObserver.observe(container);
2231
+ const onWheel = (e) => {
2232
+ if (e.ctrlKey || e.metaKey) {
2233
+ e.preventDefault();
2234
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2235
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2236
+ renderPage(currentPage);
2237
+ }
2238
+ };
2239
+ container.addEventListener("wheel", onWheel, { passive: false });
2240
+ const onKeyDown = (e) => {
2241
+ const tag = e.target?.tagName?.toLowerCase();
2242
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2243
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2244
+ if (currentPage < totalPages) {
2245
+ e.preventDefault();
2246
+ container.scrollTop = 0;
2247
+ renderPage(currentPage + 1);
2248
+ }
2249
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2250
+ if (currentPage > 1) {
2251
+ e.preventDefault();
2252
+ container.scrollTop = 0;
2253
+ renderPage(currentPage - 1);
2254
+ }
2255
+ } else if (e.key === "Home") {
2256
+ e.preventDefault();
2257
+ container.scrollTop = 0;
2258
+ renderPage(1);
2259
+ } else if (e.key === "End") {
2260
+ e.preventDefault();
2261
+ container.scrollTop = 0;
2262
+ renderPage(totalPages);
2263
+ }
2264
+ };
2265
+ window.addEventListener("keydown", onKeyDown);
1702
2266
  const cleanup = () => {
1703
2267
  resizeObserver.disconnect();
2268
+ window.removeEventListener("keydown", onKeyDown);
2269
+ container.removeEventListener("wheel", onWheel);
1704
2270
  if (resizeTimer) clearTimeout(resizeTimer);
1705
2271
  if (currentRenderTask) {
1706
2272
  try {
@@ -1719,22 +2285,26 @@ var PdfPlugin = class {
1719
2285
  const instance = {
1720
2286
  destroy: cleanup,
1721
2287
  zoomIn: () => {
1722
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2288
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1723
2289
  renderPage(currentPage);
1724
2290
  },
1725
2291
  zoomOut: () => {
1726
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2292
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1727
2293
  renderPage(currentPage);
1728
2294
  },
1729
2295
  getZoom: () => zoomScale,
1730
2296
  setZoom: (level) => {
1731
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2297
+ zoomScale = Math.max(0.25, Math.min(4, level));
1732
2298
  renderPage(currentPage);
1733
2299
  },
1734
2300
  fitToPage: () => {
1735
- fitMode = fitMode === "page" ? "width" : "page";
2301
+ fitMode = "page";
2302
+ zoomScale = 1;
2303
+ renderPage(currentPage);
2304
+ },
2305
+ fitToWidth: () => {
2306
+ fitMode = "width";
1736
2307
  zoomScale = 1;
1737
- rotation = 0;
1738
2308
  renderPage(currentPage);
1739
2309
  },
1740
2310
  rotateCW: () => {
@@ -1752,6 +2322,9 @@ var PdfPlugin = class {
1752
2322
  container.scrollTop = 0;
1753
2323
  renderPage(page);
1754
2324
  },
2325
+ toggleThumbnails: () => {
2326
+ ctx?.toggleThumbnails?.();
2327
+ },
1755
2328
  download: () => {
1756
2329
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1757
2330
  const url = URL.createObjectURL(blob);
@@ -1785,7 +2358,7 @@ var PdfPlugin = class {
1785
2358
  },
1786
2359
  getThumbnails: async () => {
1787
2360
  const thumbnails = [];
1788
- const count = Math.min(totalPages, 50);
2361
+ const count = totalPages;
1789
2362
  for (let i = 1; i <= count; i++) {
1790
2363
  thumbnails.push({
1791
2364
  index: i,
@@ -1793,9 +2366,10 @@ var PdfPlugin = class {
1793
2366
  render: async (thumbCanvas) => {
1794
2367
  try {
1795
2368
  const p = await pdfDoc.getPage(i);
1796
- const baseVp = p.getViewport({ scale: 1 });
1797
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1798
- const thumbVp = p.getViewport({ scale: thumbScale });
2369
+ const baseVp = p.getViewport({ scale: 1, rotation });
2370
+ const targetW = thumbCanvas.width || 130;
2371
+ const thumbScale = targetW / baseVp.width;
2372
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1799
2373
  thumbCanvas.height = Math.floor(thumbVp.height);
1800
2374
  const tCtx = thumbCanvas.getContext("2d");
1801
2375
  if (tCtx) {
@@ -2139,28 +2713,26 @@ var DocxPlugin = class {
2139
2713
  getToolbarActions(instance) {
2140
2714
  const totalPages = instance.getPageCount?.() ?? 1;
2141
2715
  const actions = [];
2142
- if (totalPages > 1) {
2143
- actions.push({
2144
- id: "page-nav",
2145
- icon: "",
2146
- label: "Page Navigation",
2147
- type: "page-nav",
2148
- group: "navigation",
2149
- value: instance.getCurrentPage?.() ?? 1,
2150
- max: totalPages,
2151
- execute: (action, page) => {
2152
- const cur = instance.getCurrentPage?.() ?? 1;
2153
- const max = instance.getPageCount?.() ?? 1;
2154
- if (action === "prev") {
2155
- if (cur > 1) instance.goToPage?.(cur - 1);
2156
- } else if (action === "next") {
2157
- if (cur < max) instance.goToPage?.(cur + 1);
2158
- } else if (typeof page === "number") {
2159
- instance.goToPage?.(page);
2160
- }
2716
+ actions.push({
2717
+ id: "page-nav",
2718
+ icon: "",
2719
+ label: "Page Navigation",
2720
+ type: "page-nav",
2721
+ group: "navigation",
2722
+ value: instance.getCurrentPage?.() ?? 1,
2723
+ max: totalPages,
2724
+ execute: (action, page) => {
2725
+ const cur = instance.getCurrentPage?.() ?? 1;
2726
+ const max = instance.getPageCount?.() ?? 1;
2727
+ if (action === "prev") {
2728
+ if (cur > 1) instance.goToPage?.(cur - 1);
2729
+ } else if (action === "next") {
2730
+ if (cur < max) instance.goToPage?.(cur + 1);
2731
+ } else if (typeof page === "number") {
2732
+ instance.goToPage?.(page);
2161
2733
  }
2162
- });
2163
- }
2734
+ }
2735
+ });
2164
2736
  actions.push(
2165
2737
  {
2166
2738
  id: "zoom-out",
@@ -2226,11 +2798,17 @@ var DocxPlugin = class {
2226
2798
  wrapper.className = "fp-docx-wrapper";
2227
2799
  wrapper.style.transformOrigin = "top center";
2228
2800
  wrapper.style.transition = "transform 0.2s ease";
2229
- wrapper.style.padding = "24px 16px";
2230
- wrapper.style.maxWidth = "950px";
2801
+ wrapper.style.padding = "0";
2802
+ wrapper.style.maxWidth = "none";
2803
+ wrapper.style.width = "816px";
2231
2804
  wrapper.style.margin = "0 auto";
2805
+ wrapper.style.display = "flex";
2806
+ wrapper.style.flexDirection = "column";
2807
+ wrapper.style.alignItems = "center";
2232
2808
  wrapper.style.boxSizing = "border-box";
2233
- ctx.container.style.overflow = "auto";
2809
+ ctx.container.style.overflowX = "hidden";
2810
+ ctx.container.style.overflowY = "auto";
2811
+ ctx.container.style.padding = "16px 8px";
2234
2812
  ctx.container.style.backgroundColor = "#f1f5f9";
2235
2813
  ctx.container.appendChild(wrapper);
2236
2814
  const styleOverride = document.createElement("style");
@@ -2404,39 +2982,13 @@ var DocxPlugin = class {
2404
2982
  const pageElements = sections.length > 0 ? sections : cards;
2405
2983
  const totalPages = Math.max(1, pageElements.length);
2406
2984
  let currentPage = 1;
2407
- let indicator = null;
2408
- if (totalPages > 1) {
2409
- indicator = document.createElement("div");
2410
- indicator.className = "fp-docx-page-indicator";
2411
- indicator.style.position = "sticky";
2412
- indicator.style.bottom = "16px";
2413
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2414
- indicator.style.backdropFilter = "blur(8px)";
2415
- indicator.style.color = "#f8fafc";
2416
- indicator.style.fontSize = "12px";
2417
- indicator.style.fontWeight = "600";
2418
- indicator.style.padding = "5px 14px";
2419
- indicator.style.borderRadius = "20px";
2420
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2421
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2422
- indicator.style.zIndex = "10";
2423
- indicator.style.userSelect = "none";
2424
- indicator.style.pointerEvents = "none";
2425
- indicator.style.textAlign = "center";
2426
- indicator.style.width = "fit-content";
2427
- indicator.style.margin = "16px auto 0";
2428
- ctx.container.appendChild(indicator);
2429
- }
2430
2985
  const showPage = (pageNum) => {
2431
2986
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2432
2987
  if (pageElements.length > 1) {
2433
2988
  pageElements.forEach((sec, idx) => {
2434
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
2989
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2435
2990
  });
2436
2991
  }
2437
- if (indicator) {
2438
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2439
- }
2440
2992
  ctx.container.scrollTop = 0;
2441
2993
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2442
2994
  };
@@ -2445,31 +2997,46 @@ var DocxPlugin = class {
2445
2997
  }
2446
2998
  scale = 1;
2447
2999
  let rotation = 0;
2448
- let fitMode = "width";
2449
- const calculateFitScale = (mode = fitMode) => {
2450
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
3000
+ let fitMode = "page";
3001
+ let isUserZoomed = false;
3002
+ const calculateFitScale = (_mode = fitMode) => {
3003
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2451
3004
  const elW = activeEl.offsetWidth || 816;
2452
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2453
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2454
- const availH = Math.max(280, ctx.container.clientHeight - 80);
3005
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2455
3006
  const sW = availW / elW;
2456
- const sH = availH / singlePageH;
2457
- if (mode === "page") {
2458
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2459
- }
2460
- return Math.max(0.65, Math.min(1.05, sW));
3007
+ return Math.max(0.35, Math.min(3, sW));
2461
3008
  };
2462
3009
  const applyTransform = () => {
2463
3010
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2464
3011
  wrapper.style.transformOrigin = "top center";
3012
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
3013
+ const elH = activeEl.offsetHeight || 1056;
3014
+ if (scale > 1) {
3015
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
3016
+ } else {
3017
+ wrapper.style.marginBottom = "32px";
3018
+ }
2465
3019
  };
3020
+ let resizeObserver = null;
3021
+ if (typeof ResizeObserver !== "undefined") {
3022
+ resizeObserver = new ResizeObserver(() => {
3023
+ if (!isUserZoomed) {
3024
+ const newFit = calculateFitScale("page");
3025
+ if (Math.abs(scale - newFit) > 0.015) {
3026
+ scale = newFit;
3027
+ applyTransform();
3028
+ }
3029
+ }
3030
+ });
3031
+ resizeObserver.observe(ctx.container);
3032
+ }
2466
3033
  setTimeout(() => {
2467
- fitMode = "width";
2468
- scale = calculateFitScale("width");
3034
+ scale = calculateFitScale("page");
2469
3035
  applyTransform();
2470
- }, 60);
3036
+ }, 40);
2471
3037
  const cleanup = () => {
2472
- indicator?.remove();
3038
+ resizeObserver?.disconnect();
3039
+ resizeObserver = null;
2473
3040
  for (const url of createdBlobUrls) {
2474
3041
  URL.revokeObjectURL(url);
2475
3042
  }
@@ -2486,21 +3053,24 @@ var DocxPlugin = class {
2486
3053
  showPage(page);
2487
3054
  },
2488
3055
  zoomIn: () => {
2489
- scale += 0.15;
3056
+ isUserZoomed = true;
3057
+ scale = Math.min(3.5, scale + 0.15);
2490
3058
  applyTransform();
2491
3059
  },
2492
3060
  zoomOut: () => {
3061
+ isUserZoomed = true;
2493
3062
  scale = Math.max(0.2, scale - 0.15);
2494
3063
  applyTransform();
2495
3064
  },
2496
3065
  getZoom: () => scale,
2497
3066
  setZoom: (level) => {
3067
+ isUserZoomed = true;
2498
3068
  scale = level;
2499
3069
  applyTransform();
2500
3070
  },
2501
3071
  fitToPage: () => {
2502
- fitMode = fitMode === "width" ? "page" : "width";
2503
- scale = calculateFitScale(fitMode);
3072
+ isUserZoomed = false;
3073
+ scale = calculateFitScale("page");
2504
3074
  rotation = 0;
2505
3075
  applyTransform();
2506
3076
  },
@@ -2890,7 +3460,7 @@ var ExcelPlugin = class {
2890
3460
  "application/vnd.ms-excel.template.macroEnabled.12",
2891
3461
  "application/vnd.oasis.opendocument.spreadsheet"
2892
3462
  ];
2893
- weight = 80;
3463
+ weight = 85;
2894
3464
  supports(file) {
2895
3465
  const ext = file.metadata.extension?.toLowerCase();
2896
3466
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2902,6 +3472,14 @@ var ExcelPlugin = class {
2902
3472
  getToolbarActions(instance) {
2903
3473
  const totalSheets = instance.getPageCount?.() ?? 1;
2904
3474
  const actions = [];
3475
+ actions.push({
3476
+ id: "thumbnails",
3477
+ icon: "thumbnails",
3478
+ label: "Sheet Thumbnails",
3479
+ type: "button",
3480
+ group: "navigation",
3481
+ execute: () => instance.toggleThumbnails?.()
3482
+ });
2905
3483
  if (totalSheets > 1) {
2906
3484
  actions.push({
2907
3485
  id: "page-nav",
@@ -2984,6 +3562,16 @@ var ExcelPlugin = class {
2984
3562
  execute: () => {
2985
3563
  instance.print?.();
2986
3564
  }
3565
+ },
3566
+ {
3567
+ id: "open-window",
3568
+ icon: "open-window",
3569
+ label: "Open in Separate Full Window",
3570
+ type: "button",
3571
+ group: "actions",
3572
+ execute: () => {
3573
+ instance.openInSeparateWindow?.();
3574
+ }
2987
3575
  }
2988
3576
  );
2989
3577
  return actions;
@@ -3149,6 +3737,63 @@ var ExcelPlugin = class {
3149
3737
  },
3150
3738
  getPageCount: () => sheetNames.length,
3151
3739
  getCurrentPage: () => currentSheetIndex,
3740
+ getThumbnails: () => {
3741
+ return sheetNames.map((name, idx) => ({
3742
+ index: idx,
3743
+ label: name,
3744
+ render: async (canvas) => {
3745
+ canvas.width = 140;
3746
+ canvas.height = 100;
3747
+ const c = canvas.getContext("2d");
3748
+ if (!c) return;
3749
+ c.fillStyle = "#ffffff";
3750
+ c.fillRect(0, 0, canvas.width, canvas.height);
3751
+ c.fillStyle = "#107c41";
3752
+ c.fillRect(0, 0, canvas.width, 16);
3753
+ c.fillStyle = "#ffffff";
3754
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3755
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3756
+ const startY = 17;
3757
+ const rowH = 13;
3758
+ const colW = 30;
3759
+ const colHeaders = ["A", "B", "C", "D"];
3760
+ c.fillStyle = "#f1f5f9";
3761
+ c.fillRect(0, startY, canvas.width, rowH);
3762
+ c.strokeStyle = "#cbd5e1";
3763
+ c.lineWidth = 0.8;
3764
+ c.strokeRect(0, startY, canvas.width, rowH);
3765
+ c.fillStyle = "#64748b";
3766
+ c.font = "bold 7px sans-serif";
3767
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3768
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3769
+ }
3770
+ const ws = wb?.Sheets[name];
3771
+ for (let ri = 1; ri <= 5; ri++) {
3772
+ const y = startY + ri * rowH;
3773
+ if (y > canvas.height - 2) break;
3774
+ c.fillStyle = "#f8fafc";
3775
+ c.fillRect(0, y, 12, rowH);
3776
+ c.fillStyle = "#94a3b8";
3777
+ c.font = "6px sans-serif";
3778
+ c.fillText(String(ri), 3, y + 9);
3779
+ c.strokeStyle = "#e2e8f0";
3780
+ c.strokeRect(0, y, canvas.width, rowH);
3781
+ c.fillStyle = "#334155";
3782
+ c.font = "6px sans-serif";
3783
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3784
+ const cellRef = `${colHeaders[ci]}${ri}`;
3785
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3786
+ if (cellVal !== void 0) {
3787
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3788
+ }
3789
+ }
3790
+ }
3791
+ c.strokeStyle = "#cbd5e1";
3792
+ c.lineWidth = 1;
3793
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3794
+ }
3795
+ }));
3796
+ },
3152
3797
  download: () => {
3153
3798
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3154
3799
  const url = URL.createObjectURL(blob);
@@ -3181,7 +3826,39 @@ var CsvPlugin = class {
3181
3826
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3182
3827
  }
3183
3828
  getToolbarActions(instance) {
3184
- return [
3829
+ const totalPages = instance.getPageCount?.() ?? 1;
3830
+ const actions = [];
3831
+ actions.push({
3832
+ id: "thumbnails",
3833
+ icon: "thumbnails",
3834
+ label: "Page Thumbnails",
3835
+ type: "button",
3836
+ group: "navigation",
3837
+ execute: () => instance.toggleThumbnails?.()
3838
+ });
3839
+ if (totalPages > 1) {
3840
+ actions.push({
3841
+ id: "page-nav",
3842
+ icon: "",
3843
+ label: "Page Navigation",
3844
+ type: "page-nav",
3845
+ group: "navigation",
3846
+ value: instance.getCurrentPage?.() ?? 1,
3847
+ max: totalPages,
3848
+ execute: (action, page) => {
3849
+ const cur = instance.getCurrentPage?.() ?? 1;
3850
+ const max = instance.getPageCount?.() ?? 1;
3851
+ if (action === "prev") {
3852
+ if (cur > 1) instance.goToPage?.(cur - 1);
3853
+ } else if (action === "next") {
3854
+ if (cur < max) instance.goToPage?.(cur + 1);
3855
+ } else if (typeof page === "number") {
3856
+ instance.goToPage?.(page);
3857
+ }
3858
+ }
3859
+ });
3860
+ }
3861
+ actions.push(
3185
3862
  {
3186
3863
  id: "zoom-out",
3187
3864
  icon: "zoom-out",
@@ -3202,6 +3879,16 @@ var CsvPlugin = class {
3202
3879
  instance.zoomIn?.();
3203
3880
  }
3204
3881
  },
3882
+ {
3883
+ id: "fit-page",
3884
+ icon: "fit-page",
3885
+ label: "Fit to View",
3886
+ type: "button",
3887
+ group: "zoom",
3888
+ execute: () => {
3889
+ instance.fitToPage?.();
3890
+ }
3891
+ },
3205
3892
  {
3206
3893
  id: "download",
3207
3894
  icon: "download",
@@ -3219,10 +3906,21 @@ var CsvPlugin = class {
3219
3906
  type: "button",
3220
3907
  group: "actions",
3221
3908
  execute: () => {
3222
- instance.print?.();
3909
+ instance.print?.();
3910
+ }
3911
+ },
3912
+ {
3913
+ id: "open-window",
3914
+ icon: "open-window",
3915
+ label: "Open in Separate Full Window",
3916
+ type: "button",
3917
+ group: "actions",
3918
+ execute: () => {
3919
+ instance.openInSeparateWindow?.();
3223
3920
  }
3224
3921
  }
3225
- ];
3922
+ );
3923
+ return actions;
3226
3924
  }
3227
3925
  async render(ctx) {
3228
3926
  const decoder = new TextDecoder("utf-8");
@@ -3234,33 +3932,62 @@ var CsvPlugin = class {
3234
3932
  container.style.padding = "16px";
3235
3933
  container.style.boxSizing = "border-box";
3236
3934
  container.style.transformOrigin = "top left";
3935
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3936
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3937
+ const headerLine = allLines[0] || "";
3938
+ const headerCells = headerLine.split(delimiter);
3939
+ const dataLines = allLines.slice(1);
3940
+ const ROWS_PER_PAGE = 100;
3941
+ const totalPages = Math.max(1, Math.ceil(dataLines.length / ROWS_PER_PAGE));
3942
+ let currentPage = 1;
3237
3943
  const table = document.createElement("table");
3238
3944
  table.style.borderCollapse = "collapse";
3239
3945
  table.style.width = "100%";
3240
- table.style.fontFamily = "monospace";
3946
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3241
3947
  table.style.fontSize = "13px";
3242
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3243
- const rows = text.split(/\r?\n/).slice(0, 500);
3244
- rows.forEach((row, rowIndex) => {
3245
- if (!row.trim()) return;
3246
- const tr = document.createElement("tr");
3247
- const cells = row.split(delimiter);
3248
- cells.forEach((cell) => {
3249
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3250
- td.textContent = cell.trim();
3251
- td.style.border = "1px solid #d0d7de";
3252
- td.style.padding = "6px 12px";
3253
- if (rowIndex === 0) {
3254
- td.style.backgroundColor = "#f6f8fa";
3255
- td.style.fontWeight = "bold";
3256
- }
3257
- tr.appendChild(td);
3948
+ const renderPage = (pageNum) => {
3949
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3950
+ table.innerHTML = "";
3951
+ const headerTr = document.createElement("tr");
3952
+ headerCells.forEach((cell) => {
3953
+ const th = document.createElement("th");
3954
+ th.textContent = cell.trim();
3955
+ th.style.border = "1px solid #d0d7de";
3956
+ th.style.padding = "8px 12px";
3957
+ th.style.backgroundColor = "#f6f8fa";
3958
+ th.style.fontWeight = "600";
3959
+ th.style.whiteSpace = "nowrap";
3960
+ headerTr.appendChild(th);
3258
3961
  });
3259
- table.appendChild(tr);
3260
- });
3962
+ table.appendChild(headerTr);
3963
+ const start = (currentPage - 1) * ROWS_PER_PAGE;
3964
+ const end = Math.min(dataLines.length, start + ROWS_PER_PAGE);
3965
+ const pageRows = dataLines.slice(start, end);
3966
+ pageRows.forEach((row, idx) => {
3967
+ const tr = document.createElement("tr");
3968
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3969
+ const cells = row.split(delimiter);
3970
+ cells.forEach((cell) => {
3971
+ const td = document.createElement("td");
3972
+ td.textContent = cell.trim();
3973
+ td.style.border = "1px solid #d0d7de";
3974
+ td.style.padding = "6px 12px";
3975
+ td.style.whiteSpace = "nowrap";
3976
+ tr.appendChild(td);
3977
+ });
3978
+ table.appendChild(tr);
3979
+ });
3980
+ container.scrollTop = 0;
3981
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3982
+ };
3983
+ renderPage(1);
3261
3984
  container.appendChild(table);
3262
3985
  ctx.container.appendChild(container);
3263
3986
  let scale = 1;
3987
+ const applyScale = () => {
3988
+ container.style.transform = `scale(${scale})`;
3989
+ container.style.transformOrigin = "top left";
3990
+ };
3264
3991
  const cleanup = () => {
3265
3992
  container.remove();
3266
3993
  ctx.container.innerHTML = "";
@@ -3268,22 +3995,86 @@ var CsvPlugin = class {
3268
3995
  ctx.signal.addEventListener("abort", cleanup);
3269
3996
  return {
3270
3997
  destroy: cleanup,
3998
+ getPageCount: () => totalPages,
3999
+ getCurrentPage: () => currentPage,
4000
+ goToPage: (page) => renderPage(page),
4001
+ getThumbnails: () => {
4002
+ const thumbnails = [];
4003
+ for (let i = 0; i < totalPages; i++) {
4004
+ const pageIdx = i;
4005
+ const startRow = pageIdx * ROWS_PER_PAGE + 1;
4006
+ const endRow = Math.min(dataLines.length, (pageIdx + 1) * ROWS_PER_PAGE);
4007
+ const label = totalPages === 1 ? "Table" : `Page ${pageIdx + 1} (${startRow}-${endRow})`;
4008
+ thumbnails.push({
4009
+ index: pageIdx,
4010
+ label,
4011
+ render: async (canvas) => {
4012
+ canvas.width = 140;
4013
+ canvas.height = 100;
4014
+ const c = canvas.getContext("2d");
4015
+ if (!c) return;
4016
+ c.fillStyle = "#ffffff";
4017
+ c.fillRect(0, 0, canvas.width, canvas.height);
4018
+ c.fillStyle = "#2563eb";
4019
+ c.fillRect(0, 0, canvas.width, 16);
4020
+ c.fillStyle = "#ffffff";
4021
+ c.font = "bold 8px sans-serif";
4022
+ c.fillText(`\u{1F4C8} ${label.slice(0, 18)}`, 6, 11);
4023
+ const startY = 17;
4024
+ const rowH = 13;
4025
+ const colW = 28;
4026
+ const cols = Math.min(4, headerCells.length || 4);
4027
+ c.fillStyle = "#f1f5f9";
4028
+ c.fillRect(0, startY, canvas.width, rowH);
4029
+ c.strokeStyle = "#cbd5e1";
4030
+ c.lineWidth = 0.8;
4031
+ c.strokeRect(0, startY, canvas.width, rowH);
4032
+ c.fillStyle = "#64748b";
4033
+ c.font = "bold 7px sans-serif";
4034
+ for (let ci = 0; ci < cols; ci++) {
4035
+ const hName = headerCells[ci] || `Col ${ci + 1}`;
4036
+ c.fillText(hName.slice(0, 5), 8 + ci * colW, startY + 9);
4037
+ }
4038
+ const rowsToPreview = dataLines.slice(pageIdx * ROWS_PER_PAGE, pageIdx * ROWS_PER_PAGE + 5);
4039
+ for (let ri = 0; ri < rowsToPreview.length; ri++) {
4040
+ const y = startY + (ri + 1) * rowH;
4041
+ if (y > canvas.height - 2) break;
4042
+ c.strokeStyle = "#e2e8f0";
4043
+ c.strokeRect(0, y, canvas.width, rowH);
4044
+ const cVals = rowsToPreview[ri].split(delimiter);
4045
+ c.fillStyle = "#334155";
4046
+ c.font = "6px sans-serif";
4047
+ for (let ci = 0; ci < cols; ci++) {
4048
+ const val = cVals[ci] || "";
4049
+ c.fillText(val.trim().slice(0, 6), 8 + ci * colW, y + 9);
4050
+ }
4051
+ }
4052
+ c.strokeStyle = "#cbd5e1";
4053
+ c.lineWidth = 1;
4054
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4055
+ }
4056
+ });
4057
+ }
4058
+ return thumbnails;
4059
+ },
3271
4060
  zoomIn: () => {
3272
4061
  scale += 0.1;
3273
- container.style.transform = `scale(${scale})`;
4062
+ applyScale();
3274
4063
  },
3275
4064
  zoomOut: () => {
3276
4065
  scale = Math.max(0.2, scale - 0.1);
3277
- container.style.transform = `scale(${scale})`;
4066
+ applyScale();
3278
4067
  },
3279
4068
  getZoom: () => scale,
3280
4069
  setZoom: (level) => {
3281
4070
  scale = level;
3282
- container.style.transform = `scale(${scale})`;
4071
+ applyScale();
3283
4072
  },
3284
4073
  fitToPage: () => {
3285
- scale = 1;
3286
- container.style.transform = `scale(1)`;
4074
+ const availW = Math.max(280, (ctx.container.clientWidth || window.innerWidth) - 48);
4075
+ const tW = table.offsetWidth || 600;
4076
+ scale = Math.max(0.35, Math.min(1, availW / tW));
4077
+ applyScale();
3287
4078
  },
3288
4079
  download: () => {
3289
4080
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3347,28 +4138,34 @@ var CodePlugin = class {
3347
4138
  getToolbarActions(instance) {
3348
4139
  const totalPages = instance.getPageCount?.() ?? 1;
3349
4140
  const actions = [];
3350
- if (totalPages > 1) {
3351
- actions.push({
3352
- id: "page-nav",
3353
- icon: "",
3354
- label: "Page Navigation",
3355
- type: "page-nav",
3356
- group: "navigation",
3357
- value: instance.getCurrentPage?.() ?? 1,
3358
- max: totalPages,
3359
- execute: (action, page) => {
3360
- const cur = instance.getCurrentPage?.() ?? 1;
3361
- const max = instance.getPageCount?.() ?? 1;
3362
- if (action === "prev") {
3363
- if (cur > 1) instance.goToPage?.(cur - 1);
3364
- } else if (action === "next") {
3365
- if (cur < max) instance.goToPage?.(cur + 1);
3366
- } else if (typeof page === "number") {
3367
- instance.goToPage?.(page);
3368
- }
4141
+ actions.push({
4142
+ id: "thumbnails",
4143
+ icon: "thumbnails",
4144
+ label: "Page Thumbnails",
4145
+ type: "button",
4146
+ group: "navigation",
4147
+ execute: () => instance.toggleThumbnails?.()
4148
+ });
4149
+ actions.push({
4150
+ id: "page-nav",
4151
+ icon: "",
4152
+ label: "Page Navigation",
4153
+ type: "page-nav",
4154
+ group: "navigation",
4155
+ value: instance.getCurrentPage?.() ?? 1,
4156
+ max: totalPages,
4157
+ execute: (action, page) => {
4158
+ const cur = instance.getCurrentPage?.() ?? 1;
4159
+ const max = instance.getPageCount?.() ?? 1;
4160
+ if (action === "prev") {
4161
+ if (cur > 1) instance.goToPage?.(cur - 1);
4162
+ } else if (action === "next") {
4163
+ if (cur < max) instance.goToPage?.(cur + 1);
4164
+ } else if (typeof page === "number") {
4165
+ instance.goToPage?.(page);
3369
4166
  }
3370
- });
3371
- }
4167
+ }
4168
+ });
3372
4169
  actions.push(
3373
4170
  {
3374
4171
  id: "zoom-out",
@@ -3512,16 +4309,19 @@ var CodePlugin = class {
3512
4309
  let fontSize = 13;
3513
4310
  let rotation = 0;
3514
4311
  let zoomLevel = 1;
4312
+ let isUserZoomed = false;
3515
4313
  const pre = document.createElement("pre");
3516
4314
  const code = document.createElement("code");
3517
4315
  if (isTxt) {
4316
+ container.style.overflowX = "hidden";
4317
+ container.style.overflowY = "auto";
3518
4318
  container.style.backgroundColor = "#f1f5f9";
3519
- container.style.padding = "32px";
4319
+ container.style.padding = "16px 8px";
3520
4320
  container.style.boxSizing = "border-box";
3521
4321
  container.style.display = "flex";
3522
4322
  container.style.flexDirection = "column";
3523
4323
  container.style.alignItems = "center";
3524
- pre.style.margin = "0";
4324
+ pre.style.margin = "0 auto";
3525
4325
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3526
4326
  pre.style.fontSize = "13px";
3527
4327
  pre.style.color = "#1e293b";
@@ -3533,10 +4333,12 @@ var CodePlugin = class {
3533
4333
  pre.style.minHeight = "1056px";
3534
4334
  pre.style.padding = "72px 56px";
3535
4335
  pre.style.boxSizing = "border-box";
3536
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4336
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3537
4337
  pre.style.borderRadius = "4px";
3538
4338
  pre.style.transformOrigin = "top center";
4339
+ pre.style.transition = "transform 0.15s ease";
3539
4340
  } else {
4341
+ container.style.overflow = "auto";
3540
4342
  container.style.backgroundColor = "#1e1e1e";
3541
4343
  container.style.color = "#d4d4d4";
3542
4344
  container.style.padding = "16px";
@@ -3568,42 +4370,9 @@ var CodePlugin = class {
3568
4370
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3569
4371
  pre.appendChild(code);
3570
4372
  container.appendChild(pre);
3571
- let indicator = null;
3572
- if (totalPages > 1) {
3573
- indicator = document.createElement("div");
3574
- indicator.className = "fp-code-page-indicator";
3575
- indicator.style.position = "sticky";
3576
- indicator.style.bottom = "16px";
3577
- if (isTxt) {
3578
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3579
- indicator.style.color = "#334155";
3580
- indicator.style.border = "1px solid #e2e8f0";
3581
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3582
- } else {
3583
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3584
- indicator.style.color = "#f8fafc";
3585
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3586
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3587
- indicator.style.backdropFilter = "blur(8px)";
3588
- }
3589
- indicator.style.fontSize = "12px";
3590
- indicator.style.fontWeight = "600";
3591
- indicator.style.padding = "5px 14px";
3592
- indicator.style.borderRadius = "20px";
3593
- indicator.style.zIndex = "10";
3594
- indicator.style.userSelect = "none";
3595
- indicator.style.pointerEvents = "none";
3596
- indicator.style.textAlign = "center";
3597
- indicator.style.width = "fit-content";
3598
- indicator.style.margin = "16px auto 0";
3599
- container.appendChild(indicator);
3600
- }
3601
4373
  const showPage = (pageNum) => {
3602
4374
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3603
4375
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3604
- if (indicator) {
3605
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3606
- }
3607
4376
  container.scrollTop = 0;
3608
4377
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3609
4378
  };
@@ -3611,36 +4380,90 @@ var CodePlugin = class {
3611
4380
  showPage(1);
3612
4381
  }
3613
4382
  ctx.container.appendChild(container);
3614
- const cleanup = () => {
3615
- indicator?.remove();
3616
- container.remove();
3617
- ctx.container.innerHTML = "";
4383
+ const calculateTxtFit = () => {
4384
+ const availW = Math.max(280, container.clientWidth - 32);
4385
+ return Math.max(0.4, Math.min(3, availW / 816));
3618
4386
  };
3619
- ctx.signal.addEventListener("abort", cleanup);
3620
4387
  const updateTransform = () => {
3621
4388
  if (isTxt) {
3622
4389
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4390
+ const scaledH = 1056 * zoomLevel;
4391
+ const extraH = Math.max(0, scaledH - 1056);
4392
+ pre.style.marginBottom = `${extraH + 32}px`;
3623
4393
  } else {
3624
4394
  pre.style.transform = `rotate(${rotation}deg)`;
3625
4395
  pre.style.fontSize = `${fontSize}px`;
3626
4396
  }
3627
4397
  };
4398
+ let resizeObserver = null;
4399
+ if (isTxt) {
4400
+ setTimeout(() => {
4401
+ zoomLevel = calculateTxtFit();
4402
+ updateTransform();
4403
+ }, 60);
4404
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4405
+ if (!isUserZoomed) {
4406
+ zoomLevel = calculateTxtFit();
4407
+ updateTransform();
4408
+ }
4409
+ }) : null;
4410
+ resizeObserver?.observe(container);
4411
+ }
4412
+ const cleanup = () => {
4413
+ resizeObserver?.disconnect();
4414
+ container.remove();
4415
+ ctx.container.innerHTML = "";
4416
+ };
4417
+ ctx.signal.addEventListener("abort", cleanup);
3628
4418
  return {
3629
4419
  destroy: cleanup,
3630
4420
  getPageCount: () => totalPages,
3631
4421
  getCurrentPage: () => currentPage,
3632
4422
  goToPage: (page) => showPage(page),
4423
+ getThumbnails: () => {
4424
+ return rawPages.map((pageText, idx) => ({
4425
+ index: idx,
4426
+ label: `Page ${idx + 1}`,
4427
+ render: async (canvas) => {
4428
+ canvas.width = 140;
4429
+ canvas.height = 180;
4430
+ const c = canvas.getContext("2d");
4431
+ if (!c) return;
4432
+ c.fillStyle = "#ffffff";
4433
+ c.fillRect(0, 0, canvas.width, canvas.height);
4434
+ c.strokeStyle = "#cbd5e1";
4435
+ c.lineWidth = 1;
4436
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4437
+ c.fillStyle = "#64748b";
4438
+ const lines = (pageText || "").split("\n").slice(0, 24);
4439
+ const lineH = 6;
4440
+ const startY = 14;
4441
+ const startX = 12;
4442
+ const maxW = canvas.width - 24;
4443
+ c.font = "5px monospace";
4444
+ for (let li = 0; li < lines.length; li++) {
4445
+ const l = lines[li].trim();
4446
+ if (!l) continue;
4447
+ const y = startY + li * lineH;
4448
+ if (y > canvas.height - 12) break;
4449
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4450
+ }
4451
+ }
4452
+ }));
4453
+ },
3633
4454
  zoomIn: () => {
4455
+ isUserZoomed = true;
3634
4456
  if (isTxt) {
3635
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4457
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3636
4458
  } else {
3637
4459
  fontSize = Math.min(32, fontSize + 2);
3638
4460
  }
3639
4461
  updateTransform();
3640
4462
  },
3641
4463
  zoomOut: () => {
4464
+ isUserZoomed = true;
3642
4465
  if (isTxt) {
3643
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4466
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3644
4467
  } else {
3645
4468
  fontSize = Math.max(8, fontSize - 2);
3646
4469
  }
@@ -3648,6 +4471,7 @@ var CodePlugin = class {
3648
4471
  },
3649
4472
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3650
4473
  setZoom: (level) => {
4474
+ isUserZoomed = true;
3651
4475
  if (isTxt) {
3652
4476
  zoomLevel = level;
3653
4477
  } else {
@@ -3656,9 +4480,10 @@ var CodePlugin = class {
3656
4480
  updateTransform();
3657
4481
  },
3658
4482
  fitToPage: () => {
4483
+ isUserZoomed = false;
3659
4484
  fontSize = 13;
3660
4485
  rotation = 0;
3661
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4486
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3662
4487
  updateTransform();
3663
4488
  },
3664
4489
  rotateCW: () => {
@@ -4539,28 +5364,26 @@ var RtfPlugin = class {
4539
5364
  getToolbarActions(instance) {
4540
5365
  const totalPages = instance.getPageCount?.() ?? 1;
4541
5366
  const actions = [];
4542
- if (totalPages > 1) {
4543
- actions.push({
4544
- id: "page-nav",
4545
- icon: "",
4546
- label: "Page Navigation",
4547
- type: "page-nav",
4548
- group: "navigation",
4549
- value: instance.getCurrentPage?.() ?? 1,
4550
- max: totalPages,
4551
- execute: (action, page) => {
4552
- const cur = instance.getCurrentPage?.() ?? 1;
4553
- const max = instance.getPageCount?.() ?? 1;
4554
- if (action === "prev") {
4555
- if (cur > 1) instance.goToPage?.(cur - 1);
4556
- } else if (action === "next") {
4557
- if (cur < max) instance.goToPage?.(cur + 1);
4558
- } else if (typeof page === "number") {
4559
- instance.goToPage?.(page);
4560
- }
5367
+ actions.push({
5368
+ id: "page-nav",
5369
+ icon: "",
5370
+ label: "Page Navigation",
5371
+ type: "page-nav",
5372
+ group: "navigation",
5373
+ value: instance.getCurrentPage?.() ?? 1,
5374
+ max: totalPages,
5375
+ execute: (action, page) => {
5376
+ const cur = instance.getCurrentPage?.() ?? 1;
5377
+ const max = instance.getPageCount?.() ?? 1;
5378
+ if (action === "prev") {
5379
+ if (cur > 1) instance.goToPage?.(cur - 1);
5380
+ } else if (action === "next") {
5381
+ if (cur < max) instance.goToPage?.(cur + 1);
5382
+ } else if (typeof page === "number") {
5383
+ instance.goToPage?.(page);
4561
5384
  }
4562
- });
4563
- }
5385
+ }
5386
+ });
4564
5387
  actions.push(
4565
5388
  {
4566
5389
  id: "zoom-out",
@@ -4632,43 +5455,57 @@ var RtfPlugin = class {
4632
5455
  async render(ctx) {
4633
5456
  const wrapper = document.createElement("div");
4634
5457
  wrapper.className = "fp-rtf-wrapper";
4635
- wrapper.style.padding = "32px";
4636
- wrapper.style.maxWidth = "850px";
5458
+ wrapper.style.padding = "0";
5459
+ wrapper.style.width = "816px";
4637
5460
  wrapper.style.margin = "0 auto";
4638
- wrapper.style.backgroundColor = "#fff";
4639
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4640
- wrapper.style.borderRadius = "4px";
4641
- wrapper.style.minHeight = "100%";
5461
+ wrapper.style.boxSizing = "border-box";
5462
+ wrapper.style.display = "flex";
5463
+ wrapper.style.flexDirection = "column";
5464
+ wrapper.style.alignItems = "center";
5465
+ wrapper.style.backgroundColor = "transparent";
4642
5466
  wrapper.style.transformOrigin = "top center";
4643
- wrapper.style.transition = "transform 0.2s ease";
4644
- ctx.container.style.overflow = "auto";
4645
- ctx.container.style.padding = "24px";
5467
+ wrapper.style.transition = "transform 0.15s ease";
5468
+ ctx.container.style.overflowX = "hidden";
5469
+ ctx.container.style.overflowY = "auto";
5470
+ ctx.container.style.padding = "16px 8px";
4646
5471
  ctx.container.style.backgroundColor = "#f1f5f9";
4647
5472
  ctx.container.appendChild(wrapper);
4648
5473
  let scale = 1;
4649
5474
  let rotation = 0;
4650
5475
  let pageElements = [];
4651
- let fitMode = "width";
5476
+ let isUserZoomed = false;
5477
+ let fitMode = ctx?.options?.fitMode || "width";
4652
5478
  const calculateFitScale = (mode = fitMode) => {
4653
5479
  const activeEl = pageElements[currentPage - 1] || wrapper;
4654
- const elW = activeEl.offsetWidth || 850;
4655
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4656
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4657
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5480
+ const elW = 816;
5481
+ const singlePageH = activeEl?.offsetHeight || 1056;
5482
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5483
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4658
5484
  const sW = availW / elW;
4659
5485
  const sH = availH / singlePageH;
4660
5486
  if (mode === "page") {
4661
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5487
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4662
5488
  }
4663
- return Math.max(0.65, Math.min(1.05, sW));
5489
+ return Math.max(0.4, Math.min(3, sW));
4664
5490
  };
4665
5491
  const applyTransform = () => {
4666
5492
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4667
5493
  wrapper.style.transformOrigin = "top center";
5494
+ const activeEl = pageElements[currentPage - 1];
5495
+ const baseH = activeEl?.offsetHeight || 1056;
5496
+ const scaledH = baseH * scale;
5497
+ const extraH = Math.max(0, scaledH - baseH);
5498
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4668
5499
  };
5500
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5501
+ if (!isUserZoomed) {
5502
+ scale = calculateFitScale(fitMode);
5503
+ applyTransform();
5504
+ }
5505
+ }) : null;
5506
+ resizeObserver?.observe(ctx.container);
4669
5507
  setTimeout(() => {
4670
- fitMode = "width";
4671
- scale = calculateFitScale("width");
5508
+ scale = calculateFitScale(fitMode);
4672
5509
  applyTransform();
4673
5510
  }, 60);
4674
5511
  try {
@@ -4678,20 +5515,34 @@ var RtfPlugin = class {
4678
5515
  const doc = new RTFJS.Document(ctx.buffer, {});
4679
5516
  const htmlElements = await doc.render();
4680
5517
  const contentNodes = [];
4681
- for (const item of htmlElements) {
4682
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4683
- contentNodes.push(...Array.from(item.children));
4684
- } else {
4685
- contentNodes.push(item);
5518
+ const extractBlocks = (nodes) => {
5519
+ for (const item of nodes) {
5520
+ if (!item || !(item instanceof HTMLElement)) continue;
5521
+ const tag = item.tagName.toLowerCase();
5522
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5523
+ const ct = c.tagName.toLowerCase();
5524
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5525
+ });
5526
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5527
+ extractBlocks(Array.from(item.children));
5528
+ } else {
5529
+ if (tag === "p" || tag === "div") {
5530
+ item.style.display = "block";
5531
+ item.style.marginBottom = "12px";
5532
+ item.style.lineHeight = "1.6";
5533
+ }
5534
+ contentNodes.push(item);
5535
+ }
4686
5536
  }
4687
- }
5537
+ };
5538
+ extractBlocks(htmlElements);
4688
5539
  wrapper.innerHTML = "";
4689
5540
  contentNodes.forEach((node) => wrapper.appendChild(node));
4690
5541
  const childHeights = contentNodes.map((c) => {
4691
5542
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4692
5543
  const offH = c.offsetHeight || 0;
4693
5544
  const textLen = c.textContent?.trim().length || 0;
4694
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
5545
+ const estH = Math.max(28, Math.ceil(textLen / 75) * 24 + 16);
4695
5546
  return Math.max(rectH, offH, estH);
4696
5547
  });
4697
5548
  wrapper.innerHTML = "";
@@ -4708,6 +5559,7 @@ var RtfPlugin = class {
4708
5559
  card.style.marginBottom = "24px";
4709
5560
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4710
5561
  card.style.lineHeight = "1.6";
5562
+ card.style.color = "#1e293b";
4711
5563
  return card;
4712
5564
  };
4713
5565
  let curCard = createRtfCard();
@@ -4743,9 +5595,8 @@ var RtfPlugin = class {
4743
5595
  pageCard.style.padding = "72px 56px";
4744
5596
  pageCard.style.width = "816px";
4745
5597
  pageCard.style.minHeight = "1056px";
4746
- pageCard.style.maxWidth = "100%";
4747
5598
  pageCard.style.boxSizing = "border-box";
4748
- pageCard.style.fontFamily = "serif";
5599
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4749
5600
  pageCard.style.fontSize = "12pt";
4750
5601
  pageCard.style.lineHeight = "1.6";
4751
5602
  pageCard.style.color = "#1e293b";
@@ -4758,29 +5609,6 @@ var RtfPlugin = class {
4758
5609
  }
4759
5610
  const totalPages = Math.max(1, pageElements.length);
4760
5611
  let currentPage = 1;
4761
- let indicator = null;
4762
- if (totalPages > 1) {
4763
- indicator = document.createElement("div");
4764
- indicator.className = "fp-rtf-page-indicator";
4765
- indicator.style.position = "sticky";
4766
- indicator.style.bottom = "16px";
4767
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4768
- indicator.style.backdropFilter = "blur(8px)";
4769
- indicator.style.color = "#f8fafc";
4770
- indicator.style.fontSize = "12px";
4771
- indicator.style.fontWeight = "600";
4772
- indicator.style.padding = "5px 14px";
4773
- indicator.style.borderRadius = "20px";
4774
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4775
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4776
- indicator.style.zIndex = "10";
4777
- indicator.style.userSelect = "none";
4778
- indicator.style.pointerEvents = "none";
4779
- indicator.style.textAlign = "center";
4780
- indicator.style.width = "fit-content";
4781
- indicator.style.margin = "16px auto 0";
4782
- ctx.container.appendChild(indicator);
4783
- }
4784
5612
  const showPage = (pageNum) => {
4785
5613
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4786
5614
  if (totalPages > 1) {
@@ -4788,9 +5616,6 @@ var RtfPlugin = class {
4788
5616
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4789
5617
  });
4790
5618
  }
4791
- if (indicator) {
4792
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4793
- }
4794
5619
  ctx.container.scrollTop = 0;
4795
5620
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4796
5621
  };
@@ -4798,7 +5623,7 @@ var RtfPlugin = class {
4798
5623
  showPage(1);
4799
5624
  }
4800
5625
  const cleanup = () => {
4801
- indicator?.remove();
5626
+ resizeObserver?.disconnect();
4802
5627
  wrapper.remove();
4803
5628
  ctx.container.innerHTML = "";
4804
5629
  };
@@ -4809,21 +5634,25 @@ var RtfPlugin = class {
4809
5634
  getCurrentPage: () => currentPage,
4810
5635
  goToPage: (page) => showPage(page),
4811
5636
  zoomIn: () => {
5637
+ isUserZoomed = true;
4812
5638
  scale += 0.15;
4813
5639
  applyTransform();
4814
5640
  },
4815
5641
  zoomOut: () => {
5642
+ isUserZoomed = true;
4816
5643
  scale = Math.max(0.2, scale - 0.15);
4817
5644
  applyTransform();
4818
5645
  },
4819
5646
  getZoom: () => scale,
4820
5647
  setZoom: (level) => {
5648
+ isUserZoomed = true;
4821
5649
  scale = level;
4822
5650
  applyTransform();
4823
5651
  },
4824
5652
  fitToPage: () => {
4825
- fitMode = fitMode === "width" ? "page" : "width";
4826
- scale = calculateFitScale(fitMode);
5653
+ isUserZoomed = false;
5654
+ fitMode = "width";
5655
+ scale = calculateFitScale("width");
4827
5656
  rotation = 0;
4828
5657
  applyTransform();
4829
5658
  },
@@ -5011,40 +5840,36 @@ var OpenDocumentPlugin = class {
5011
5840
  const isPresentation = instance.isPresentation;
5012
5841
  const totalPages = instance.getPageCount?.() ?? 1;
5013
5842
  const actions = [];
5014
- if (isPresentation || totalPages > 1) {
5015
- if (isPresentation) {
5016
- actions.push({
5017
- id: "thumbnails",
5018
- icon: "thumbnails",
5019
- label: "Slide Thumbnails",
5020
- type: "button",
5021
- group: "navigation",
5022
- execute: () => instance.toggleThumbnails?.()
5023
- });
5024
- }
5025
- actions.push({
5026
- id: "page-nav",
5027
- icon: "",
5028
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
5029
- type: "page-nav",
5030
- group: "navigation",
5031
- value: instance.getCurrentPage?.() ?? 1,
5032
- max: totalPages,
5033
- execute: (action, page) => {
5034
- const cur = instance.getCurrentPage?.() ?? 1;
5035
- const max = instance.getPageCount?.() ?? 1;
5036
- if (action === "prev") {
5037
- if (cur > 1) instance.goToPage?.(cur - 1);
5038
- } else if (action === "next") {
5039
- if (cur < max) instance.goToPage?.(cur + 1);
5040
- } else if (typeof page === "number") {
5041
- instance.goToPage?.(page);
5042
- } else if (typeof action === "number") {
5043
- instance.goToPage?.(action);
5044
- }
5843
+ actions.push({
5844
+ id: "thumbnails",
5845
+ icon: "thumbnails",
5846
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
5847
+ type: "button",
5848
+ group: "navigation",
5849
+ execute: () => instance.toggleThumbnails?.()
5850
+ });
5851
+ actions.push({
5852
+ id: "page-nav",
5853
+ icon: "",
5854
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
5855
+ type: "page-nav",
5856
+ group: "navigation",
5857
+ value: instance.getCurrentPage?.() ?? 1,
5858
+ max: totalPages,
5859
+ execute: (action, page) => {
5860
+ const cur = instance.getCurrentPage?.() ?? 1;
5861
+ const max = instance.getPageCount?.() ?? 1;
5862
+ if (action === "prev") {
5863
+ if (cur > 1) instance.goToPage?.(cur - 1);
5864
+ } else if (action === "next") {
5865
+ if (cur < max) instance.goToPage?.(cur + 1);
5866
+ } else if (typeof page === "number") {
5867
+ instance.goToPage?.(page);
5868
+ } else if (typeof action === "number") {
5869
+ instance.goToPage?.(action);
5045
5870
  }
5046
- });
5047
- }
5871
+ }
5872
+ });
5048
5873
  actions.push(
5049
5874
  {
5050
5875
  id: "zoom-out",
@@ -5135,49 +5960,65 @@ var OpenDocumentPlugin = class {
5135
5960
  container.className = "fp-odf-container";
5136
5961
  container.style.width = "100%";
5137
5962
  container.style.height = "100%";
5138
- container.style.overflow = "auto";
5139
- container.style.padding = "24px";
5963
+ container.style.overflowX = "hidden";
5964
+ container.style.overflowY = "auto";
5965
+ container.style.padding = "16px 8px";
5140
5966
  container.style.backgroundColor = "#f1f5f9";
5141
5967
  const wrapper = document.createElement("div");
5142
5968
  wrapper.className = "fp-odf-wrapper";
5143
5969
  wrapper.style.margin = "0 auto";
5970
+ wrapper.style.width = isPresentation ? "960px" : "816px";
5971
+ wrapper.style.boxSizing = "border-box";
5144
5972
  wrapper.style.backgroundColor = "#ffffff";
5145
5973
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5146
5974
  wrapper.style.borderRadius = "4px";
5147
5975
  wrapper.style.transformOrigin = "top center";
5148
- wrapper.style.transition = "transform 0.2s ease";
5976
+ wrapper.style.transition = "transform 0.15s ease";
5149
5977
  container.appendChild(wrapper);
5150
5978
  ctx.container.appendChild(container);
5151
5979
  let scale = 1;
5152
5980
  let rotation = 0;
5153
- let fitMode = "width";
5981
+ let isUserZoomed = false;
5982
+ let fitMode = ctx?.options?.fitMode || "width";
5983
+ let currentPage = 1;
5984
+ let totalPages = 1;
5985
+ let slides = [];
5154
5986
  const calculateFitScale = (mode = fitMode) => {
5155
- const elW = wrapper.offsetWidth || 850;
5156
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5157
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5158
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5987
+ const activeSlide = slides[currentPage - 1];
5988
+ const elW = isPresentation ? 960 : 816;
5989
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5990
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5991
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5159
5992
  const sW = availW / elW;
5160
5993
  const sH = availH / (isPresentation ? 540 : singlePageH);
5161
5994
  if (isPresentation) {
5162
- return Math.min(1, Math.min(sW, sH));
5995
+ return Math.min(2.5, Math.min(sW, sH));
5163
5996
  }
5164
5997
  if (mode === "page") {
5165
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5998
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5166
5999
  }
5167
- return Math.max(0.65, Math.min(1.05, sW));
6000
+ return Math.max(0.4, Math.min(3, sW));
5168
6001
  };
5169
6002
  const applyTransform = () => {
5170
6003
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5171
6004
  wrapper.style.transformOrigin = "top center";
6005
+ const activeSlide = slides[currentPage - 1];
6006
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6007
+ const scaledH = baseH * scale;
6008
+ const extraH = Math.max(0, scaledH - baseH);
6009
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5172
6010
  };
6011
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6012
+ if (!isUserZoomed) {
6013
+ scale = calculateFitScale(fitMode);
6014
+ applyTransform();
6015
+ }
6016
+ }) : null;
6017
+ resizeObserver?.observe(ctx.container);
5173
6018
  setTimeout(() => {
5174
- fitMode = "width";
5175
- scale = calculateFitScale("width");
6019
+ scale = calculateFitScale(fitMode);
5176
6020
  applyTransform();
5177
6021
  }, 60);
5178
- let currentPage = 1;
5179
- let totalPages = 1;
5180
- let slides = [];
5181
6022
  if (isPresentation) {
5182
6023
  wrapper.style.maxWidth = "960px";
5183
6024
  wrapper.style.aspectRatio = "16 / 9";
@@ -5278,24 +6119,9 @@ var OpenDocumentPlugin = class {
5278
6119
  wrapper.appendChild(page);
5279
6120
  slides.push(page);
5280
6121
  });
5281
- const pageIndicator = document.createElement("div");
5282
- pageIndicator.className = "fp-odt-page-indicator";
5283
- pageIndicator.style.position = "sticky";
5284
- pageIndicator.style.bottom = "16px";
5285
- pageIndicator.style.left = "50%";
5286
- pageIndicator.style.transform = "translateX(-50%)";
5287
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5288
- pageIndicator.style.color = "#fff";
5289
- pageIndicator.style.padding = "6px 12px";
5290
- pageIndicator.style.borderRadius = "16px";
5291
- pageIndicator.style.fontSize = "12px";
5292
- pageIndicator.style.zIndex = "100";
5293
- pageIndicator.style.display = "inline-block";
5294
- pageIndicator.style.width = "fit-content";
5295
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5296
- container.appendChild(pageIndicator);
5297
6122
  }
5298
6123
  const cleanup = () => {
6124
+ resizeObserver?.disconnect();
5299
6125
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5300
6126
  container.remove();
5301
6127
  ctx.container.innerHTML = "";
@@ -5307,10 +6133,6 @@ var OpenDocumentPlugin = class {
5307
6133
  slides.forEach((s, idx) => {
5308
6134
  s.style.display = idx === page - 1 ? "block" : "none";
5309
6135
  });
5310
- const indicator = container.querySelector(".fp-odt-page-indicator");
5311
- if (indicator) {
5312
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5313
- }
5314
6136
  container.scrollTop = 0;
5315
6137
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5316
6138
  };
@@ -5318,21 +6140,25 @@ var OpenDocumentPlugin = class {
5318
6140
  destroy: cleanup,
5319
6141
  isPresentation,
5320
6142
  zoomIn: () => {
6143
+ isUserZoomed = true;
5321
6144
  scale += 0.15;
5322
6145
  applyTransform();
5323
6146
  },
5324
6147
  zoomOut: () => {
6148
+ isUserZoomed = true;
5325
6149
  scale = Math.max(0.2, scale - 0.15);
5326
6150
  applyTransform();
5327
6151
  },
5328
6152
  getZoom: () => scale,
5329
6153
  setZoom: (level) => {
6154
+ isUserZoomed = true;
5330
6155
  scale = level;
5331
6156
  applyTransform();
5332
6157
  },
5333
6158
  fitToPage: () => {
5334
- fitMode = fitMode === "width" ? "page" : "width";
5335
- scale = calculateFitScale(fitMode);
6159
+ isUserZoomed = false;
6160
+ fitMode = "width";
6161
+ scale = calculateFitScale("width");
5336
6162
  rotation = 0;
5337
6163
  applyTransform();
5338
6164
  },
@@ -5348,23 +6174,45 @@ var OpenDocumentPlugin = class {
5348
6174
  getPageCount: () => totalPages,
5349
6175
  getCurrentPage: () => currentPage,
5350
6176
  getThumbnails: async () => {
5351
- if (!isPresentation) return [];
5352
- return slides.map((_, idx) => ({
5353
- index: idx,
5354
- label: `Slide ${idx + 1}`,
5355
- render: async (canvas) => {
5356
- const ctx2d = canvas.getContext("2d");
5357
- if (!ctx2d) return;
5358
- canvas.width = 160;
5359
- canvas.height = 90;
5360
- ctx2d.fillStyle = "#f8fafc";
5361
- ctx2d.fillRect(0, 0, 160, 90);
5362
- ctx2d.fillStyle = "#334155";
5363
- ctx2d.font = "bold 12px sans-serif";
5364
- ctx2d.textAlign = "center";
5365
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5366
- }
5367
- }));
6177
+ const count = totalPages || slides.length || 1;
6178
+ const items = [];
6179
+ for (let idx = 0; idx < count; idx++) {
6180
+ const itemIdx = idx;
6181
+ items.push({
6182
+ index: itemIdx,
6183
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6184
+ render: async (canvas) => {
6185
+ const ctx2d = canvas.getContext("2d");
6186
+ if (!ctx2d) return;
6187
+ if (isPresentation) {
6188
+ canvas.width = 160;
6189
+ canvas.height = 90;
6190
+ ctx2d.fillStyle = "#f8fafc";
6191
+ ctx2d.fillRect(0, 0, 160, 90);
6192
+ ctx2d.strokeStyle = "#cbd5e1";
6193
+ ctx2d.lineWidth = 1;
6194
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6195
+ ctx2d.fillStyle = "#334155";
6196
+ ctx2d.font = "bold 11px sans-serif";
6197
+ ctx2d.textAlign = "center";
6198
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6199
+ } else {
6200
+ canvas.width = 140;
6201
+ canvas.height = 180;
6202
+ ctx2d.fillStyle = "#ffffff";
6203
+ ctx2d.fillRect(0, 0, 140, 180);
6204
+ ctx2d.strokeStyle = "#cbd5e1";
6205
+ ctx2d.lineWidth = 1;
6206
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6207
+ ctx2d.fillStyle = "#64748b";
6208
+ ctx2d.font = "bold 10px sans-serif";
6209
+ ctx2d.textAlign = "center";
6210
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6211
+ }
6212
+ }
6213
+ });
6214
+ }
6215
+ return items;
5368
6216
  },
5369
6217
  download: () => {
5370
6218
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5469,23 +6317,24 @@ var OpenDocumentPlugin = class {
5469
6317
  case "h": {
5470
6318
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5471
6319
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5472
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6320
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5473
6321
  break;
5474
6322
  }
5475
6323
  case "p": {
5476
6324
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5477
6325
  const inner = this.renderSpans(node, styles, images);
5478
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6326
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6327
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5479
6328
  break;
5480
6329
  }
5481
6330
  case "list": {
5482
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6331
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5483
6332
  Array.from(node.children).forEach((c) => walk(c));
5484
6333
  html += "</ul>";
5485
6334
  break;
5486
6335
  }
5487
6336
  case "list-item": {
5488
- html += "<li>";
6337
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5489
6338
  Array.from(node.children).forEach((c) => walk(c));
5490
6339
  html += "</li>";
5491
6340
  break;
@@ -5611,28 +6460,26 @@ var DocPlugin = class {
5611
6460
  getToolbarActions(instance) {
5612
6461
  const totalPages = instance.getPageCount?.() ?? 1;
5613
6462
  const actions = [];
5614
- if (totalPages > 1) {
5615
- actions.push({
5616
- id: "page-nav",
5617
- icon: "",
5618
- label: "Page Navigation",
5619
- type: "page-nav",
5620
- group: "navigation",
5621
- value: instance.getCurrentPage?.() ?? 1,
5622
- max: totalPages,
5623
- execute: (action, page) => {
5624
- const cur = instance.getCurrentPage?.() ?? 1;
5625
- const max = instance.getPageCount?.() ?? 1;
5626
- if (action === "prev") {
5627
- if (cur > 1) instance.goToPage?.(cur - 1);
5628
- } else if (action === "next") {
5629
- if (cur < max) instance.goToPage?.(cur + 1);
5630
- } else if (typeof page === "number") {
5631
- instance.goToPage?.(page);
5632
- }
6463
+ actions.push({
6464
+ id: "page-nav",
6465
+ icon: "",
6466
+ label: "Page Navigation",
6467
+ type: "page-nav",
6468
+ group: "navigation",
6469
+ value: instance.getCurrentPage?.() ?? 1,
6470
+ max: totalPages,
6471
+ execute: (action, page) => {
6472
+ const cur = instance.getCurrentPage?.() ?? 1;
6473
+ const max = instance.getPageCount?.() ?? 1;
6474
+ if (action === "prev") {
6475
+ if (cur > 1) instance.goToPage?.(cur - 1);
6476
+ } else if (action === "next") {
6477
+ if (cur < max) instance.goToPage?.(cur + 1);
6478
+ } else if (typeof page === "number") {
6479
+ instance.goToPage?.(page);
5633
6480
  }
5634
- });
5635
- }
6481
+ }
6482
+ });
5636
6483
  actions.push(
5637
6484
  {
5638
6485
  id: "zoom-out",
@@ -5706,8 +6553,9 @@ var DocPlugin = class {
5706
6553
  container.className = "fp-doc-container";
5707
6554
  container.style.width = "100%";
5708
6555
  container.style.height = "100%";
5709
- container.style.overflow = "auto";
5710
- container.style.padding = "32px 16px";
6556
+ container.style.overflowX = "hidden";
6557
+ container.style.overflowY = "auto";
6558
+ container.style.padding = "16px 8px";
5711
6559
  container.style.backgroundColor = "#f1f5f9";
5712
6560
  container.style.display = "flex";
5713
6561
  container.style.justifyContent = "center";
@@ -5716,8 +6564,17 @@ var DocPlugin = class {
5716
6564
  let scale = 1;
5717
6565
  let extractedRawText = "";
5718
6566
  let isFallback = false;
6567
+ let chartSvg = "";
5719
6568
  try {
5720
6569
  const cfbf = new CfbfReader(ctx.buffer);
6570
+ try {
6571
+ const pkg = cfbf.readStream("package_stream");
6572
+ if (pkg && pkg.length > 100) {
6573
+ chartSvg = this.parseOdfChartToSvg(pkg);
6574
+ }
6575
+ } catch (chartErr) {
6576
+ console.warn("[DocPlugin] Chart stream parsing info:", chartErr);
6577
+ }
5721
6578
  const wordDocStream = cfbf.readStream("WordDocument");
5722
6579
  if (!wordDocStream || wordDocStream.length < 512) {
5723
6580
  throw new Error("WordDocument stream not found or invalid in CFBF archive");
@@ -5735,7 +6592,7 @@ var DocPlugin = class {
5735
6592
  extractedRawText = fallback;
5736
6593
  isFallback = true;
5737
6594
  }
5738
- const rawPages = this.splitIntoPages(extractedRawText);
6595
+ const rawPages = this.splitIntoPages(extractedRawText, chartSvg);
5739
6596
  const totalPages = Math.max(1, rawPages.length);
5740
6597
  let currentPage = 1;
5741
6598
  const pageCards = [];
@@ -5743,16 +6600,15 @@ var DocPlugin = class {
5743
6600
  const pageCard = document.createElement("div");
5744
6601
  pageCard.className = "fp-doc-page-card";
5745
6602
  pageCard.style.width = "816px";
5746
- pageCard.style.height = "1056px";
6603
+ pageCard.style.minHeight = "1056px";
5747
6604
  pageCard.style.backgroundColor = "#ffffff";
5748
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6605
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5749
6606
  pageCard.style.borderRadius = "4px";
5750
- pageCard.style.padding = "96px 72px";
6607
+ pageCard.style.padding = "72px 56px";
5751
6608
  pageCard.style.boxSizing = "border-box";
5752
- pageCard.style.overflow = "hidden";
5753
6609
  pageCard.style.display = i === 0 ? "block" : "none";
5754
6610
  pageCard.style.transformOrigin = "top center";
5755
- pageCard.style.transition = "transform 0.2s ease";
6611
+ pageCard.style.transition = "transform 0.15s ease";
5756
6612
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5757
6613
  pageCard.style.color = "#1e293b";
5758
6614
  if (isFallback && i === 0) {
@@ -5769,34 +6625,15 @@ var DocPlugin = class {
5769
6625
  container.appendChild(pageCard);
5770
6626
  pageCards.push(pageCard);
5771
6627
  }
5772
- let indicator = null;
5773
- if (totalPages > 1) {
5774
- indicator = document.createElement("div");
5775
- indicator.className = "fp-doc-page-indicator";
5776
- indicator.style.position = "fixed";
5777
- indicator.style.bottom = "16px";
5778
- indicator.style.left = "50%";
5779
- indicator.style.transform = "translateX(-50%)";
5780
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5781
- indicator.style.backdropFilter = "blur(8px)";
5782
- indicator.style.color = "#f8fafc";
5783
- indicator.style.fontSize = "12px";
5784
- indicator.style.fontWeight = "600";
5785
- indicator.style.padding = "5px 14px";
5786
- indicator.style.borderRadius = "20px";
5787
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5788
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5789
- indicator.style.zIndex = "10";
5790
- indicator.style.userSelect = "none";
5791
- indicator.style.pointerEvents = "none";
5792
- indicator.style.textAlign = "center";
5793
- container.appendChild(indicator);
5794
- }
5795
6628
  let rotation = 0;
5796
6629
  const applyTransform = () => {
5797
6630
  const activeCard = pageCards[currentPage - 1];
5798
6631
  if (activeCard) {
5799
6632
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6633
+ const baseH = activeCard.offsetHeight || 1056;
6634
+ const scaledH = baseH * scale;
6635
+ const extraH = Math.max(0, scaledH - baseH);
6636
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5800
6637
  }
5801
6638
  };
5802
6639
  const showPage = (pageNum) => {
@@ -5809,35 +6646,39 @@ var DocPlugin = class {
5809
6646
  card.style.display = "none";
5810
6647
  }
5811
6648
  });
5812
- if (indicator) {
5813
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5814
- }
5815
6649
  container.scrollTop = 0;
5816
6650
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5817
6651
  };
5818
6652
  if (totalPages > 1) {
5819
6653
  showPage(1);
5820
6654
  }
5821
- let fitMode = "width";
6655
+ let fitMode = ctx?.options?.fitMode || "width";
6656
+ let isUserZoomed = false;
5822
6657
  const calculateFitScale = (mode = fitMode) => {
5823
6658
  const elW = 816;
5824
6659
  const elH = 1056;
5825
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5826
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6660
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6661
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5827
6662
  const sW = availW / elW;
5828
6663
  const sH = availH / elH;
5829
6664
  if (mode === "page") {
5830
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6665
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5831
6666
  }
5832
- return Math.max(0.65, Math.min(1.05, sW));
6667
+ return Math.max(0.4, Math.min(3, sW));
5833
6668
  };
6669
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6670
+ if (!isUserZoomed) {
6671
+ scale = calculateFitScale(fitMode);
6672
+ applyTransform();
6673
+ }
6674
+ }) : null;
6675
+ resizeObserver?.observe(ctx.container);
5834
6676
  setTimeout(() => {
5835
- fitMode = "width";
5836
- scale = calculateFitScale("width");
6677
+ scale = calculateFitScale(fitMode);
5837
6678
  applyTransform();
5838
6679
  }, 60);
5839
6680
  const cleanup = () => {
5840
- indicator?.remove();
6681
+ resizeObserver?.disconnect();
5841
6682
  container.remove();
5842
6683
  ctx.container.innerHTML = "";
5843
6684
  };
@@ -5848,21 +6689,25 @@ var DocPlugin = class {
5848
6689
  getCurrentPage: () => currentPage,
5849
6690
  goToPage: (page) => showPage(page),
5850
6691
  zoomIn: () => {
6692
+ isUserZoomed = true;
5851
6693
  scale += 0.15;
5852
6694
  applyTransform();
5853
6695
  },
5854
6696
  zoomOut: () => {
6697
+ isUserZoomed = true;
5855
6698
  scale = Math.max(0.2, scale - 0.15);
5856
6699
  applyTransform();
5857
6700
  },
5858
6701
  getZoom: () => scale,
5859
6702
  setZoom: (level) => {
6703
+ isUserZoomed = true;
5860
6704
  scale = level;
5861
6705
  applyTransform();
5862
6706
  },
5863
6707
  fitToPage: () => {
5864
- fitMode = fitMode === "width" ? "page" : "width";
5865
- scale = calculateFitScale(fitMode);
6708
+ isUserZoomed = false;
6709
+ fitMode = "width";
6710
+ scale = calculateFitScale("width");
5866
6711
  rotation = 0;
5867
6712
  applyTransform();
5868
6713
  },
@@ -5977,7 +6822,7 @@ var DocPlugin = class {
5977
6822
  for (const run of [...ansiRuns, ...utf16Runs]) {
5978
6823
  const trimmed = run.trim();
5979
6824
  if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
5980
- 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)) {
6825
+ 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)) {
5981
6826
  seen.add(trimmed);
5982
6827
  candidateLines.push(trimmed);
5983
6828
  }
@@ -5988,9 +6833,114 @@ var DocPlugin = class {
5988
6833
  heuristicTextExtraction(buffer) {
5989
6834
  return this.extractStringsFromBytes(new Uint8Array(buffer));
5990
6835
  }
5991
- cleanWordDocFields(text) {
6836
+ /**
6837
+ * Parses an embedded OpenDocument Chart package into a vector SVG bar/column chart
6838
+ */
6839
+ parseOdfChartToSvg(zipBytes) {
6840
+ try {
6841
+ const unzipped = fflate.unzipSync(zipBytes);
6842
+ const contentXml = unzipped["content.xml"] ? new TextDecoder("utf-8").decode(unzipped["content.xml"]) : "";
6843
+ if (!contentXml) return "";
6844
+ const rowsMatch = contentXml.match(/<table:table-row[\s\S]*?<\/table:table-row>/g) || [];
6845
+ if (rowsMatch.length < 2) return "";
6846
+ const headers = [];
6847
+ const firstRow = rowsMatch[0];
6848
+ const headerCells = firstRow ? firstRow.match(/<text:p>([^<]+)<\/text:p>/g) || [] : [];
6849
+ for (const h of headerCells) {
6850
+ headers.push(h.replace(/<\/?text:p>/g, "").trim());
6851
+ }
6852
+ const categories = [];
6853
+ const seriesValues = headers.map(() => []);
6854
+ for (let r = 1; r < rowsMatch.length; r++) {
6855
+ const rowStr = rowsMatch[r];
6856
+ if (!rowStr) continue;
6857
+ const cells = rowStr.match(/<table:table-cell[\s\S]*?<\/table:table-cell>/g) || [];
6858
+ if (cells.length > 0 && cells[0]) {
6859
+ const catMatch = cells[0].match(/<text:p>([^<]+)<\/text:p>/);
6860
+ categories.push(catMatch ? catMatch[1] : "Row " + r);
6861
+ for (let c = 1; c < cells.length && c - 1 < headers.length; c++) {
6862
+ const cellStr = cells[c];
6863
+ if (!cellStr) continue;
6864
+ const valMatch = cellStr.match(/office:value="([0-9.]+)"/) || cellStr.match(/<text:p>([0-9.]+)<\/text:p>/);
6865
+ const series = seriesValues[c - 1];
6866
+ if (series) {
6867
+ series.push(valMatch ? parseFloat(valMatch[1]) : 0);
6868
+ }
6869
+ }
6870
+ }
6871
+ }
6872
+ const colors = ["#004586", "#ff420e", "#ffd320", "#579d1c", "#7e0021"];
6873
+ const colorMatches = contentXml.matchAll(/draw:fill-color="(#[0-9a-fA-F]{6})"/g);
6874
+ let cIdx = 0;
6875
+ for (const cm of colorMatches) {
6876
+ if (cIdx < colors.length) colors[cIdx] = cm[1];
6877
+ cIdx++;
6878
+ }
6879
+ let maxVal = 10;
6880
+ for (const s of seriesValues) {
6881
+ for (const v of s) {
6882
+ if (v > maxVal) maxVal = v;
6883
+ }
6884
+ }
6885
+ maxVal = Math.ceil(maxVal * 1.15);
6886
+ const width = 560;
6887
+ const height = 280;
6888
+ const padLeft = 45;
6889
+ const padRight = 100;
6890
+ const padTop = 20;
6891
+ const padBottom = 40;
6892
+ const chartW = width - padLeft - padRight;
6893
+ const chartH = height - padTop - padBottom;
6894
+ 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);">`;
6895
+ for (let step = 0; step <= 4; step++) {
6896
+ const yVal = (maxVal / 4 * step).toFixed(1);
6897
+ const yPos = padTop + chartH - step / 4 * chartH;
6898
+ svg += `<line x1="${padLeft}" y1="${yPos}" x2="${padLeft + chartW}" y2="${yPos}" stroke="#e2e8f0" stroke-dasharray="2,2" />`;
6899
+ svg += `<text x="${padLeft - 8}" y="${yPos + 4}" font-size="11" fill="#64748b" text-anchor="end">${yVal}</text>`;
6900
+ }
6901
+ const numCats = categories.length;
6902
+ const numSeries = headers.length;
6903
+ const groupW = chartW / numCats;
6904
+ const barW = Math.max(8, groupW * 0.7 / numSeries);
6905
+ const groupPad = (groupW - barW * numSeries) / 2;
6906
+ for (let catIdx = 0; catIdx < numCats; catIdx++) {
6907
+ const groupX = padLeft + catIdx * groupW + groupPad;
6908
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6909
+ const val = seriesValues[sIdx][catIdx] || 0;
6910
+ const barH = val / maxVal * chartH;
6911
+ const barX = groupX + sIdx * barW;
6912
+ const barY = padTop + chartH - barH;
6913
+ const col = colors[sIdx % colors.length];
6914
+ svg += `<rect x="${barX}" y="${barY}" width="${barW - 2}" height="${barH}" fill="${col}" rx="2"><title>${headers[sIdx]}: ${val}</title></rect>`;
6915
+ }
6916
+ const catX = padLeft + catIdx * groupW + groupW / 2;
6917
+ svg += `<text x="${catX}" y="${padTop + chartH + 18}" font-size="11" fill="#475569" text-anchor="middle">${categories[catIdx]}</text>`;
6918
+ }
6919
+ let legendY = padTop + 20;
6920
+ for (let sIdx = 0; sIdx < numSeries; sIdx++) {
6921
+ const col = colors[sIdx % colors.length];
6922
+ svg += `<rect x="${padLeft + chartW + 15}" y="${legendY}" width="12" height="12" fill="${col}" rx="2" />`;
6923
+ svg += `<text x="${padLeft + chartW + 32}" y="${legendY + 10}" font-size="11" fill="#334155">${headers[sIdx]}</text>`;
6924
+ legendY += 20;
6925
+ }
6926
+ svg += "</svg>";
6927
+ return svg;
6928
+ } catch (e) {
6929
+ console.warn("[DocPlugin] Error generating chart SVG:", e);
6930
+ return "";
6931
+ }
6932
+ }
6933
+ cleanWordDocFields(text, chartSvg = "") {
5992
6934
  if (!text) return "";
5993
6935
  let cleaned = text.replace(
6936
+ /\x13\s*EMBED\b[\s\S]*?\x15/gi,
6937
+ () => chartSvg ? `
6938
+
6939
+ ${chartSvg}
6940
+
6941
+ ` : ""
6942
+ );
6943
+ cleaned = cleaned.replace(
5994
6944
  /\x13\s*HYPERLINK\s*"?([^"\x14]+)"?\s*\x14([\s\S]*?)\x15/gi,
5995
6945
  (_match, url, label) => {
5996
6946
  const cleanUrl = url.trim();
@@ -5998,18 +6948,23 @@ var DocPlugin = class {
5998
6948
  return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${cleanLabel}</a>`;
5999
6949
  }
6000
6950
  );
6001
- cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, "$1");
6951
+ cleaned = cleaned.replace(/\x13[^\x14\x15]*\x14([^\x15]*)\x15/g, (_m, res) => {
6952
+ if (/[\x00-\x1F]/.test(res)) return "";
6953
+ return res.trim();
6954
+ });
6002
6955
  cleaned = cleaned.replace(/\x13[^\x15]*\x15/g, "");
6003
6956
  cleaned = cleaned.replace(/[\x13\x14\x15]/g, "");
6957
+ cleaned = cleaned.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, "");
6958
+ cleaned = cleaned.replace(/EMBED\s+LibreOffice\.ChartDocument\.[0-9]+/gi, chartSvg || "");
6004
6959
  return cleaned;
6005
6960
  }
6006
- splitIntoPages(text) {
6961
+ splitIntoPages(text, chartSvg = "") {
6007
6962
  if (!text) return [""];
6008
- const cleanedText = this.cleanWordDocFields(text);
6963
+ const cleanedText = this.cleanWordDocFields(text, chartSvg);
6009
6964
  const normalized = cleanedText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x07\n/g, "\n").replace(/\x07/g, " ");
6010
6965
  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);
6011
6966
  if (explicitParts.length === 0) explicitParts.push(normalized);
6012
- const maxLinesPerPage = 32;
6967
+ const maxLinesPerPage = 34;
6013
6968
  const charsPerLine = 80;
6014
6969
  const finalPages = [];
6015
6970
  for (const part of explicitParts) {
@@ -6017,8 +6972,8 @@ var DocPlugin = class {
6017
6972
  let currentLines = [];
6018
6973
  let count = 0;
6019
6974
  for (const line of lines) {
6020
- const plainLine = line.replace(/<[^>]+>/g, "");
6021
- const vLines = Math.max(1, Math.ceil((plainLine.length || 1) / charsPerLine));
6975
+ const isSvg = line.includes("<svg");
6976
+ const vLines = isSvg ? 12 : Math.max(1, Math.ceil((line.replace(/<[^>]+>/g, "").length || 1) / charsPerLine));
6022
6977
  if (count + vLines > maxLinesPerPage && currentLines.length > 0) {
6023
6978
  finalPages.push(currentLines.join("\n"));
6024
6979
  currentLines = [];
@@ -6058,9 +7013,44 @@ var DocPlugin = class {
6058
7013
  tableLines = [];
6059
7014
  }
6060
7015
  };
7016
+ const sanitizeOptions = {
7017
+ ADD_TAGS: ["a", "svg", "g", "path", "line", "rect", "circle", "text", "title"],
7018
+ ADD_ATTR: [
7019
+ "href",
7020
+ "target",
7021
+ "rel",
7022
+ "style",
7023
+ "viewBox",
7024
+ "width",
7025
+ "height",
7026
+ "x",
7027
+ "y",
7028
+ "x1",
7029
+ "y1",
7030
+ "x2",
7031
+ "y2",
7032
+ "fill",
7033
+ "stroke",
7034
+ "stroke-width",
7035
+ "stroke-dasharray",
7036
+ "rx",
7037
+ "font-size",
7038
+ "text-anchor"
7039
+ ]
7040
+ };
6061
7041
  let i = 0;
6062
7042
  while (i < lines.length) {
6063
7043
  let line = lines[i];
7044
+ if (line.includes("<svg")) {
7045
+ if (inList) {
7046
+ html += "</ul>";
7047
+ inList = false;
7048
+ }
7049
+ flushTable();
7050
+ html += line;
7051
+ i++;
7052
+ continue;
7053
+ }
6064
7054
  let tabCount = (line.match(/\t/g) || []).length;
6065
7055
  if (tabCount > 0) {
6066
7056
  let j = i;
@@ -6094,26 +7084,31 @@ var DocPlugin = class {
6094
7084
  i++;
6095
7085
  continue;
6096
7086
  }
6097
- const sanitizeOptions = { ADD_TAGS: ["a"], ADD_ATTR: ["href", "target", "rel", "style"] };
6098
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7087
+ if (/^\d{1,2}$/.test(line.trim())) {
7088
+ i++;
7089
+ continue;
7090
+ }
7091
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7092
+ 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));
7093
+ if (isTitleLike) {
6099
7094
  if (inList) {
6100
7095
  html += "</ul>";
6101
7096
  inList = false;
6102
7097
  }
6103
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
7098
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 18px 0 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
6104
7099
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6105
7100
  if (!inList) {
6106
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7101
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6107
7102
  inList = true;
6108
7103
  }
6109
7104
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6110
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
7105
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
6111
7106
  } else {
6112
7107
  if (inList) {
6113
7108
  html += "</ul>";
6114
7109
  inList = false;
6115
7110
  }
6116
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
7111
+ html += `<p style="line-height: 1.5; margin: 0 0 12px 0; font-size: 11pt; color: #1e293b; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
6117
7112
  }
6118
7113
  i++;
6119
7114
  }