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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue.js CHANGED
@@ -494,8 +494,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
494
494
  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>`;
495
495
  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>`;
496
496
  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>`;
497
- 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>`;
498
- 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>`;
497
+ 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>`;
499
498
  var ICON_MAP = {
500
499
  "zoom-in": ICON_ZOOM_IN,
501
500
  "zoom-out": ICON_ZOOM_OUT,
@@ -522,28 +521,177 @@ var ICON_MAP = {
522
521
  "forward-10": ICON_FAST_FORWARD,
523
522
  "rewind": ICON_REWIND,
524
523
  "replay-10": ICON_REWIND,
525
- "speed": ICON_SPEED,
526
524
  "open-window": ICON_EXTERNAL_WINDOW,
527
- "external-window": ICON_EXTERNAL_WINDOW
525
+ "external-window": ICON_EXTERNAL_WINDOW,
526
+ "openWindow": ICON_EXTERNAL_WINDOW,
527
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
528
528
  };
529
529
  var ToolbarController = class {
530
530
  el;
531
531
  toolbarEl;
532
532
  actions = [];
533
+ config = {};
533
534
  pageInputEl = null;
534
535
  pageLabelEl = null;
535
- constructor(container) {
536
+ prevPageBtn = null;
537
+ nextPageBtn = null;
538
+ constructor(container, config) {
536
539
  this.el = container;
540
+ if (config) this.config = { ...config };
537
541
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
538
542
  this.el.appendChild(this.toolbarEl);
539
543
  }
544
+ setConfig(config) {
545
+ this.config = { ...config };
546
+ this.render();
547
+ }
548
+ getConfig() {
549
+ return { ...this.config };
550
+ }
551
+ hideAction(actionId) {
552
+ this.config[actionId] = false;
553
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
554
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
555
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
556
+ this.config.fitPage = false;
557
+ this.config.fitToPage = false;
558
+ this.config.fitWidth = false;
559
+ }
560
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
561
+ if (actionId === "fullscreen") this.config.fullscreen = false;
562
+ if (actionId === "download") this.config.download = false;
563
+ if (actionId === "print") this.config.print = false;
564
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
565
+ this.config.openWindow = false;
566
+ this.config.openSeparateWindow = false;
567
+ }
568
+ if (actionId === "copy") this.config.copy = false;
569
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
570
+ this.config.pageNav = false;
571
+ this.config.pagination = false;
572
+ }
573
+ this.render();
574
+ }
575
+ showAction(actionId) {
576
+ this.config[actionId] = true;
577
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
578
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
579
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
580
+ this.config.fitPage = true;
581
+ this.config.fitToPage = true;
582
+ this.config.fitWidth = true;
583
+ }
584
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
585
+ if (actionId === "fullscreen") this.config.fullscreen = true;
586
+ if (actionId === "download") this.config.download = true;
587
+ if (actionId === "print") this.config.print = true;
588
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
589
+ this.config.openWindow = true;
590
+ this.config.openSeparateWindow = true;
591
+ }
592
+ if (actionId === "copy") this.config.copy = true;
593
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
594
+ this.config.pageNav = true;
595
+ this.config.pagination = true;
596
+ }
597
+ this.render();
598
+ }
599
+ normalizeActionId(actionId) {
600
+ if (actionId === "zoomIn") return "zoom-in";
601
+ if (actionId === "zoomOut") return "zoom-out";
602
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
603
+ if (actionId === "rotateCW") return "rotate-cw";
604
+ if (actionId === "rotateCCW") return "rotate-ccw";
605
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
606
+ if (actionId === "pageNav") return "page-nav";
607
+ return actionId;
608
+ }
609
+ enableAction(actionId) {
610
+ const norm = this.normalizeActionId(actionId);
611
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
612
+ if (btn) {
613
+ btn.disabled = false;
614
+ btn.classList.remove("disabled");
615
+ }
616
+ }
617
+ disableAction(actionId) {
618
+ const norm = this.normalizeActionId(actionId);
619
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
620
+ if (btn) {
621
+ btn.disabled = true;
622
+ btn.classList.add("disabled");
623
+ }
624
+ }
625
+ setActionActive(actionId, active) {
626
+ const norm = this.normalizeActionId(actionId);
627
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
628
+ if (btn) {
629
+ if (active) {
630
+ btn.classList.add("active");
631
+ } else {
632
+ btn.classList.remove("active");
633
+ }
634
+ }
635
+ }
636
+ isActionEnabled(action) {
637
+ const c = this.config;
638
+ const id = action.id;
639
+ if (c[id] === false) return false;
640
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
641
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
642
+ 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)) {
643
+ return false;
644
+ }
645
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
646
+ return false;
647
+ }
648
+ if (id === "fullscreen" && c.fullscreen === false) return false;
649
+ if (id === "download" && c.download === false) return false;
650
+ if (id === "print" && c.print === false) return false;
651
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
652
+ return false;
653
+ }
654
+ if (id === "copy" && c.copy === false) return false;
655
+ if (id === "thumbnails" && c.thumbnails === false) return false;
656
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
657
+ return false;
658
+ }
659
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
660
+ return false;
661
+ }
662
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
663
+ return false;
664
+ }
665
+ if (id === "play" && c.play === false) return false;
666
+ if (id === "pause" && c.pause === false) return false;
667
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
668
+ return false;
669
+ }
670
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
671
+ return false;
672
+ }
673
+ if (id === "speed" && c.speed === false) return false;
674
+ return true;
675
+ }
540
676
  setPage(page, max) {
541
677
  if (this.pageInputEl) {
542
678
  this.pageInputEl.value = page.toString();
679
+ if (max !== void 0) {
680
+ this.pageInputEl.max = max.toString();
681
+ }
543
682
  }
544
683
  if (max !== void 0 && this.pageLabelEl) {
545
684
  this.pageLabelEl.textContent = ` / ${max}`;
546
685
  }
686
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
687
+ if (this.prevPageBtn) {
688
+ this.prevPageBtn.disabled = page <= 1;
689
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
690
+ }
691
+ if (this.nextPageBtn) {
692
+ this.nextPageBtn.disabled = page >= currentMax;
693
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
694
+ }
547
695
  }
548
696
  update(actions) {
549
697
  this.actions = actions;
@@ -566,8 +714,9 @@ var ToolbarController = class {
566
714
  view: [],
567
715
  actions: []
568
716
  };
569
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
570
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
717
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
718
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
719
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
571
720
  for (const action of effectiveActions) {
572
721
  if (groups[action.group]) {
573
722
  groups[action.group].push(action);
@@ -627,6 +776,13 @@ var ToolbarController = class {
627
776
  action.execute("go", val);
628
777
  });
629
778
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
779
+ const curVal = action.value ?? 1;
780
+ prevBtn.disabled = curVal <= 1;
781
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
782
+ nextBtn.disabled = curVal >= max;
783
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
784
+ this.prevPageBtn = prevBtn;
785
+ this.nextPageBtn = nextBtn;
630
786
  this.pageInputEl = input;
631
787
  this.pageLabelEl = label;
632
788
  groupEl.appendChild(prevBtn);
@@ -682,25 +838,63 @@ var ToolbarController = class {
682
838
  var ThumbnailPanel = class {
683
839
  el;
684
840
  panelEl;
841
+ listEl;
842
+ headerEl;
685
843
  thumbnails = [];
686
844
  onSelect;
845
+ onToggleCallback;
687
846
  activeIndex = 0;
688
- constructor(container) {
847
+ observer;
848
+ renderedIndices = /* @__PURE__ */ new Set();
849
+ constructor(container, onToggle) {
689
850
  this.el = container;
851
+ this.onToggleCallback = onToggle;
690
852
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
853
+ this.panelEl.style.width = "0px";
854
+ this.panelEl.style.minWidth = "0px";
855
+ this.panelEl.style.opacity = "0";
856
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
857
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
858
+ const closeBtn = createElement("button", {
859
+ className: "fp-thumbnail-close-btn",
860
+ title: "Close Thumbnails",
861
+ type: "button"
862
+ });
863
+ closeBtn.innerHTML = ICON_CLOSE;
864
+ closeBtn.addEventListener("click", (e) => {
865
+ e.stopPropagation();
866
+ this.hide();
867
+ });
868
+ this.headerEl.appendChild(title);
869
+ this.headerEl.appendChild(closeBtn);
870
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
871
+ this.panelEl.appendChild(this.headerEl);
872
+ this.panelEl.appendChild(this.listEl);
691
873
  this.el.appendChild(this.panelEl);
692
874
  }
875
+ isOpen() {
876
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
877
+ }
693
878
  update(thumbnails, onSelect) {
694
879
  this.thumbnails = thumbnails;
695
880
  this.onSelect = onSelect;
881
+ this.renderedIndices.clear();
882
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
883
+ if (titleEl) {
884
+ titleEl.textContent = `Pages (${thumbnails.length})`;
885
+ }
696
886
  this.render();
887
+ if (this.isOpen()) {
888
+ this.renderAllVisible();
889
+ }
697
890
  }
698
891
  setActive(index) {
699
892
  this.activeIndex = index;
700
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
893
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
701
894
  items.forEach((item, i) => {
702
895
  if (i === index) {
703
896
  item.classList.add("active");
897
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
704
898
  } else {
705
899
  item.classList.remove("active");
706
900
  }
@@ -708,37 +902,102 @@ var ThumbnailPanel = class {
708
902
  }
709
903
  show() {
710
904
  this.panelEl.classList.remove("hidden");
905
+ this.panelEl.style.width = "200px";
906
+ this.panelEl.style.minWidth = "200px";
907
+ this.panelEl.style.opacity = "1";
908
+ this.panelEl.style.display = "flex";
909
+ this.onToggleCallback?.(true);
910
+ this.renderAllVisible();
711
911
  }
712
912
  hide() {
713
913
  this.panelEl.classList.add("hidden");
914
+ this.panelEl.style.width = "0px";
915
+ this.panelEl.style.minWidth = "0px";
916
+ this.panelEl.style.opacity = "0";
917
+ this.onToggleCallback?.(false);
714
918
  }
715
919
  toggle() {
716
- this.panelEl.classList.toggle("hidden");
920
+ if (this.isOpen()) {
921
+ this.hide();
922
+ } else {
923
+ this.show();
924
+ }
717
925
  }
718
926
  destroy() {
927
+ if (this.observer) {
928
+ this.observer.disconnect();
929
+ this.observer = void 0;
930
+ }
931
+ this.renderedIndices.clear();
719
932
  this.el.innerHTML = "";
720
933
  }
721
- async render() {
722
- this.panelEl.innerHTML = "";
934
+ renderAllVisible() {
935
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
936
+ items.forEach((item) => {
937
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
938
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
939
+ this.renderThumbnailItem(idx, item);
940
+ }
941
+ });
942
+ }
943
+ renderThumbnailItem(idx, itemEl) {
944
+ if (this.renderedIndices.has(idx)) return;
945
+ this.renderedIndices.add(idx);
946
+ const canvas = itemEl.querySelector("canvas");
947
+ const thumb = this.thumbnails[idx];
948
+ if (canvas && thumb) {
949
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
950
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
951
+ });
952
+ }
953
+ }
954
+ render() {
955
+ if (this.observer) {
956
+ this.observer.disconnect();
957
+ }
958
+ this.listEl.innerHTML = "";
959
+ const useObserver = typeof IntersectionObserver !== "undefined";
960
+ if (useObserver) {
961
+ this.observer = new IntersectionObserver(
962
+ (entries) => {
963
+ for (const entry of entries) {
964
+ if (entry.isIntersecting) {
965
+ const target = entry.target;
966
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
967
+ if (idx >= 0) {
968
+ this.renderThumbnailItem(idx, target);
969
+ }
970
+ }
971
+ }
972
+ },
973
+ {
974
+ root: this.listEl,
975
+ rootMargin: "120px 0px 120px 0px"
976
+ }
977
+ );
978
+ }
723
979
  for (let i = 0; i < this.thumbnails.length; i++) {
724
980
  const thumb = this.thumbnails[i];
725
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
726
- if (i === this.activeIndex) {
727
- itemEl.classList.add("active");
728
- }
981
+ const itemEl = createElement("div", {
982
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
983
+ });
984
+ itemEl.dataset.thumbIndex = i.toString();
729
985
  itemEl.addEventListener("click", () => {
730
986
  this.setActive(i);
731
987
  this.onSelect?.(i);
732
988
  });
733
- const canvas = createElement("canvas", { width: "150", height: "200" });
734
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
735
- itemEl.appendChild(canvas);
989
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
990
+ const canvas = createElement("canvas", { width: "130", height: "170" });
991
+ canvasWrapper.appendChild(canvas);
992
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
993
+ itemEl.appendChild(canvasWrapper);
736
994
  itemEl.appendChild(label);
737
- this.panelEl.appendChild(itemEl);
738
- try {
739
- await thumb.render(canvas);
740
- } catch (err) {
741
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
995
+ this.listEl.appendChild(itemEl);
996
+ if (this.observer) {
997
+ this.observer.observe(itemEl);
998
+ }
999
+ if (i < 4) {
1000
+ this.renderThumbnailItem(i, itemEl);
742
1001
  }
743
1002
  }
744
1003
  }
@@ -759,6 +1018,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
759
1018
  currentOptions = {};
760
1019
  resizeObserver = null;
761
1020
  fullscreenHandler = null;
1021
+ titleBarEl = null;
762
1022
  /**
763
1023
  * Register a preview plugin.
764
1024
  */
@@ -795,6 +1055,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
795
1055
  }
796
1056
  this.currentBuffer = buffer.slice(0);
797
1057
  this.currentMetadata = metadata;
1058
+ this.updateTitleBar(options, metadata);
798
1059
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
799
1060
  const fileInfo = { metadata, buffer };
800
1061
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -818,15 +1079,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
818
1079
  if (event === "page-change" && payload && typeof payload.page === "number") {
819
1080
  const total = payload.total ?? payload.totalPages;
820
1081
  this.toolbar?.setPage(payload.page, total);
1082
+ this.thumbnailPanel?.setActive(payload.page - 1);
821
1083
  }
822
1084
  this.eventEmitter.emit(event, payload);
823
- }
1085
+ },
1086
+ toggleThumbnails: () => this.toggleThumbnails()
824
1087
  });
825
1088
  this.activeInstance = instance;
826
1089
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1090
+ instance.toggleThumbnails = () => this.toggleThumbnails();
827
1091
  this.hideLoading();
828
1092
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
829
- if (options.showToolbar !== false && this.toolbar) {
1093
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1094
+ if (isToolbarVisible && this.toolbar) {
1095
+ const toolbarConfig = this.extractToolbarConfig(options);
1096
+ this.toolbar.setConfig(toolbarConfig);
830
1097
  const actions = matchedPlugin.getToolbarActions(instance);
831
1098
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
832
1099
  if (!hasFullscreen && this.wrapperEl) {
@@ -876,6 +1143,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
876
1143
  }
877
1144
  });
878
1145
  }
1146
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1147
+ if (thumbAction) {
1148
+ thumbAction.execute = () => {
1149
+ this.toggleThumbnails();
1150
+ };
1151
+ }
879
1152
  this.toolbar.update(actions);
880
1153
  this.toolbar.show();
881
1154
  }
@@ -960,7 +1233,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
960
1233
  if (targetUrl) {
961
1234
  const newWin2 = window.open(targetUrl, "_blank");
962
1235
  if (!newWin2) {
963
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1236
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
964
1237
  return null;
965
1238
  }
966
1239
  return newWin2;
@@ -968,7 +1241,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
968
1241
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
969
1242
  const newWin = window.open("", "_blank");
970
1243
  if (!newWin) {
971
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1244
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
972
1245
  return null;
973
1246
  }
974
1247
  newWin.document.title = title;
@@ -1045,6 +1318,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1045
1318
  this.currentBuffer = null;
1046
1319
  this.currentMetadata = null;
1047
1320
  this.currentContainer = null;
1321
+ this.titleBarEl = null;
1048
1322
  }
1049
1323
  /**
1050
1324
  * Get the currently active preview instance.
@@ -1052,7 +1326,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1052
1326
  getInstance() {
1053
1327
  return this.activeInstance;
1054
1328
  }
1329
+ // --- Toolbar Configuration & Feature Toggles ---
1330
+ /**
1331
+ * Configure toolbar features dynamically through options or methods.
1332
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1333
+ */
1334
+ setToolbarConfig(config) {
1335
+ this.toolbar?.setConfig(config);
1336
+ }
1337
+ /**
1338
+ * Get current toolbar configuration.
1339
+ */
1340
+ getToolbarConfig() {
1341
+ return this.toolbar?.getConfig() ?? {};
1342
+ }
1343
+ /**
1344
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1345
+ */
1346
+ hideToolbarAction(actionId) {
1347
+ this.toolbar?.hideAction(actionId);
1348
+ }
1349
+ /**
1350
+ * Show a specific toolbar action by ID or alias.
1351
+ */
1352
+ showToolbarAction(actionId) {
1353
+ this.toolbar?.showAction(actionId);
1354
+ }
1355
+ /**
1356
+ * Enable a specific toolbar action button.
1357
+ */
1358
+ enableToolbarAction(actionId) {
1359
+ this.toolbar?.enableAction(actionId);
1360
+ }
1361
+ /**
1362
+ * Disable a specific toolbar action button.
1363
+ */
1364
+ disableToolbarAction(actionId) {
1365
+ this.toolbar?.disableAction(actionId);
1366
+ }
1367
+ // --- Programmatic Toolbar Action Methods ---
1368
+ /**
1369
+ * Fit document to page so it fills the frame width with minimal margins.
1370
+ */
1371
+ fitToPage() {
1372
+ this.activeInstance?.fitToPage?.();
1373
+ }
1374
+ /**
1375
+ * Zoom in.
1376
+ */
1377
+ zoomIn() {
1378
+ this.activeInstance?.zoomIn?.();
1379
+ }
1380
+ /**
1381
+ * Zoom out.
1382
+ */
1383
+ zoomOut() {
1384
+ this.activeInstance?.zoomOut?.();
1385
+ }
1386
+ /**
1387
+ * Set specific zoom level.
1388
+ */
1389
+ setZoom(level) {
1390
+ this.activeInstance?.setZoom?.(level);
1391
+ }
1392
+ /**
1393
+ * Get current zoom level.
1394
+ */
1395
+ getZoom() {
1396
+ return this.activeInstance?.getZoom?.() ?? 1;
1397
+ }
1398
+ /**
1399
+ * Rotate 90 degrees clockwise.
1400
+ */
1401
+ rotateCW() {
1402
+ this.activeInstance?.rotateCW?.();
1403
+ }
1404
+ /**
1405
+ * Rotate 90 degrees counter-clockwise.
1406
+ */
1407
+ rotateCCW() {
1408
+ this.activeInstance?.rotateCCW?.();
1409
+ }
1410
+ /**
1411
+ * Navigate to a specific page number.
1412
+ */
1413
+ goToPage(page) {
1414
+ this.activeInstance?.goToPage?.(page);
1415
+ this.toolbar?.setPage(page);
1416
+ }
1417
+ /**
1418
+ * Navigate to next page.
1419
+ */
1420
+ nextPage() {
1421
+ const cur = this.getCurrentPage();
1422
+ const max = this.getPageCount();
1423
+ if (cur < max) {
1424
+ this.goToPage(cur + 1);
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Navigate to previous page.
1429
+ */
1430
+ prevPage() {
1431
+ const cur = this.getCurrentPage();
1432
+ if (cur > 1) {
1433
+ this.goToPage(cur - 1);
1434
+ }
1435
+ }
1436
+ /**
1437
+ * Get total page count.
1438
+ */
1439
+ getPageCount() {
1440
+ return this.activeInstance?.getPageCount?.() ?? 1;
1441
+ }
1442
+ /**
1443
+ * Get current page number.
1444
+ */
1445
+ getCurrentPage() {
1446
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1447
+ }
1448
+ /**
1449
+ * Download the current document.
1450
+ */
1451
+ download() {
1452
+ this.activeInstance?.download?.();
1453
+ }
1454
+ /**
1455
+ * Print the current document.
1456
+ */
1457
+ print() {
1458
+ this.activeInstance?.print?.();
1459
+ }
1460
+ /**
1461
+ * Toggle fullscreen mode.
1462
+ */
1463
+ toggleFullscreen() {
1464
+ try {
1465
+ if (!document.fullscreenElement) {
1466
+ this.wrapperEl?.requestFullscreen?.();
1467
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1468
+ } else {
1469
+ document.exitFullscreen?.();
1470
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1471
+ }
1472
+ } catch {
1473
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1474
+ }
1475
+ setTimeout(() => {
1476
+ this.activeInstance?.fitToPage?.();
1477
+ }, 120);
1478
+ }
1479
+ /**
1480
+ * Toggle thumbnails sidebar panel.
1481
+ */
1482
+ toggleThumbnails() {
1483
+ if (!this.thumbnailPanel) return;
1484
+ this.thumbnailPanel.toggle();
1485
+ const isOpen = this.thumbnailPanel.isOpen();
1486
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1487
+ setTimeout(() => {
1488
+ this.activeInstance?.fitToPage?.();
1489
+ }, 260);
1490
+ }
1491
+ /**
1492
+ * Set whether to display the file name / title bar above the toolbar.
1493
+ */
1494
+ setShowFileName(show) {
1495
+ if (this.currentOptions) {
1496
+ this.currentOptions.showFileName = show;
1497
+ }
1498
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1499
+ }
1500
+ /**
1501
+ * Set or override the displayed file name in the preview panel.
1502
+ */
1503
+ setFileName(name) {
1504
+ if (this.currentOptions) {
1505
+ this.currentOptions.fileName = name;
1506
+ }
1507
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1508
+ }
1055
1509
  // --- Private methods ---
1510
+ updateTitleBar(options, metadata) {
1511
+ if (!this.titleBarEl) return;
1512
+ const show = options?.showFileName !== false;
1513
+ if (!show) {
1514
+ this.titleBarEl.style.display = "none";
1515
+ return;
1516
+ }
1517
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1518
+ if (!name) {
1519
+ this.titleBarEl.style.display = "none";
1520
+ return;
1521
+ }
1522
+ this.titleBarEl.style.display = "flex";
1523
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1524
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1525
+ this.titleBarEl.innerHTML = `
1526
+ <div class="fp-titlebar-info">
1527
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1528
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1529
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1530
+ </div>
1531
+ `;
1532
+ }
1533
+ extractToolbarConfig(options) {
1534
+ if (!options) return {};
1535
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1536
+ return {
1537
+ ...options,
1538
+ ...nested
1539
+ };
1540
+ }
1056
1541
  abort() {
1057
1542
  if (this.abortController) {
1058
1543
  this.abortController.abort();
@@ -1095,17 +1580,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1095
1580
  bodyEl.className = "fp-body";
1096
1581
  bodyEl.appendChild(thumbnailEl);
1097
1582
  bodyEl.appendChild(this.contentEl);
1583
+ const titleBarEl = document.createElement("div");
1584
+ titleBarEl.className = "fp-titlebar";
1585
+ this.titleBarEl = titleBarEl;
1586
+ this.updateTitleBar(options, this.currentMetadata);
1098
1587
  if (toolbarPos === "top") {
1588
+ this.wrapperEl.appendChild(titleBarEl);
1099
1589
  this.wrapperEl.appendChild(toolbarEl);
1100
1590
  this.wrapperEl.appendChild(bodyEl);
1101
1591
  } else {
1592
+ this.wrapperEl.appendChild(titleBarEl);
1102
1593
  this.wrapperEl.appendChild(bodyEl);
1103
1594
  this.wrapperEl.appendChild(toolbarEl);
1104
1595
  }
1105
1596
  container.innerHTML = "";
1106
1597
  container.appendChild(this.wrapperEl);
1107
- this.toolbar = new ToolbarController(toolbarEl);
1108
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1598
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1599
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1600
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1601
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1602
+ setTimeout(() => {
1603
+ this.activeInstance?.fitToPage?.();
1604
+ }, 260);
1605
+ });
1109
1606
  this.setupKeyboardShortcuts();
1110
1607
  this.setupDragAndDrop(container, options);
1111
1608
  this.setupResizeAndFullscreenListeners();
@@ -1495,6 +1992,14 @@ var PdfPlugin = class {
1495
1992
  group: "zoom",
1496
1993
  execute: () => instance.fitToPage?.()
1497
1994
  },
1995
+ {
1996
+ id: "fit-width",
1997
+ icon: "fit-width",
1998
+ label: "Fit to Width",
1999
+ type: "button",
2000
+ group: "zoom",
2001
+ execute: () => instance.fitToWidth?.()
2002
+ },
1498
2003
  {
1499
2004
  id: "rotate-cw",
1500
2005
  icon: "rotate-cw",
@@ -1503,6 +2008,14 @@ var PdfPlugin = class {
1503
2008
  group: "view",
1504
2009
  execute: () => instance.rotateCW?.()
1505
2010
  },
2011
+ {
2012
+ id: "rotate-ccw",
2013
+ icon: "rotate-ccw",
2014
+ label: "Rotate Counter-Clockwise",
2015
+ type: "button",
2016
+ group: "view",
2017
+ execute: () => instance.rotateCCW?.()
2018
+ },
1506
2019
  {
1507
2020
  id: "download",
1508
2021
  icon: "download",
@@ -1534,12 +2047,13 @@ var PdfPlugin = class {
1534
2047
  container.className = "fp-pdf-container";
1535
2048
  container.style.width = "100%";
1536
2049
  container.style.height = "100%";
1537
- container.style.overflow = "auto";
2050
+ container.style.overflowX = "hidden";
2051
+ container.style.overflowY = "auto";
1538
2052
  container.style.display = "flex";
1539
2053
  container.style.flexDirection = "column";
1540
2054
  container.style.alignItems = "center";
1541
2055
  container.style.justifyContent = "flex-start";
1542
- container.style.padding = "20px 16px";
2056
+ container.style.padding = "16px 8px";
1543
2057
  container.style.backgroundColor = "#0f172a";
1544
2058
  container.style.boxSizing = "border-box";
1545
2059
  container.style.position = "relative";
@@ -1550,30 +2064,12 @@ var PdfPlugin = class {
1550
2064
  pageCard.style.borderRadius = "4px";
1551
2065
  pageCard.style.overflow = "hidden";
1552
2066
  pageCard.style.lineHeight = "0";
1553
- pageCard.style.transition = "transform 0.15s ease";
1554
2067
  pageCard.style.position = "relative";
1555
2068
  pageCard.style.flexShrink = "0";
2069
+ pageCard.style.transition = "box-shadow 0.2s ease";
1556
2070
  let canvas = document.createElement("canvas");
1557
2071
  pageCard.appendChild(canvas);
1558
2072
  container.appendChild(pageCard);
1559
- const indicator = document.createElement("div");
1560
- indicator.className = "fp-pdf-page-indicator";
1561
- indicator.style.position = "sticky";
1562
- indicator.style.bottom = "16px";
1563
- indicator.style.marginTop = "16px";
1564
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1565
- indicator.style.backdropFilter = "blur(8px)";
1566
- indicator.style.color = "#f8fafc";
1567
- indicator.style.fontSize = "12px";
1568
- indicator.style.fontWeight = "600";
1569
- indicator.style.padding = "5px 14px";
1570
- indicator.style.borderRadius = "20px";
1571
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1572
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1573
- indicator.style.zIndex = "10";
1574
- indicator.style.userSelect = "none";
1575
- indicator.style.pointerEvents = "none";
1576
- container.appendChild(indicator);
1577
2073
  ctx.container.appendChild(container);
1578
2074
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1579
2075
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1589,8 +2085,9 @@ var PdfPlugin = class {
1589
2085
  let currentPage = 1;
1590
2086
  let zoomScale = 1;
1591
2087
  let rotation = 0;
1592
- let fitMode = "page";
2088
+ let fitMode = ctx?.options?.fitMode || "width";
1593
2089
  let currentRenderTask = null;
2090
+ let textLayerDiv = null;
1594
2091
  const renderPage = async (pageNum) => {
1595
2092
  if (currentRenderTask) {
1596
2093
  try {
@@ -1602,30 +2099,37 @@ var PdfPlugin = class {
1602
2099
  const newCanvas = document.createElement("canvas");
1603
2100
  pageCard.replaceChild(newCanvas, canvas);
1604
2101
  canvas = newCanvas;
2102
+ if (textLayerDiv) {
2103
+ textLayerDiv.remove();
2104
+ textLayerDiv = null;
2105
+ }
1605
2106
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1606
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1607
2107
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1608
2108
  const page = await pdfDoc.getPage(currentPage);
1609
2109
  const containerWidth = container.clientWidth || 900;
1610
2110
  const containerHeight = container.clientHeight || 700;
1611
2111
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1612
- const availWidth = Math.max(320, containerWidth - 48);
1613
- const availHeight = Math.max(550, containerHeight - 88);
2112
+ const availWidth = Math.max(280, containerWidth - 36);
2113
+ const availHeight = Math.max(280, containerHeight - 32);
1614
2114
  const scaleW = availWidth / unscaledVp.width;
1615
2115
  const scaleH = availHeight / unscaledVp.height;
1616
2116
  let fitScale;
1617
2117
  if (fitMode === "page") {
1618
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2118
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1619
2119
  } else {
1620
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2120
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1621
2121
  }
1622
2122
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1623
2123
  const pixelRatio = window.devicePixelRatio || 1;
1624
2124
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2125
+ const displayWidth = Math.floor(viewport.width);
2126
+ const displayHeight = Math.floor(viewport.height);
1625
2127
  canvas.width = Math.floor(viewport.width * pixelRatio);
1626
2128
  canvas.height = Math.floor(viewport.height * pixelRatio);
1627
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1628
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2129
+ canvas.style.width = `${displayWidth}px`;
2130
+ canvas.style.height = `${displayHeight}px`;
2131
+ pageCard.style.width = `${displayWidth}px`;
2132
+ pageCard.style.height = `${displayHeight}px`;
1629
2133
  const canvasCtx = canvas.getContext("2d");
1630
2134
  if (!canvasCtx) return;
1631
2135
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1639,9 +2143,31 @@ var PdfPlugin = class {
1639
2143
  if (err?.name !== "RenderingCancelledException") {
1640
2144
  console.warn("[PdfPlugin] Page render warning:", err);
1641
2145
  }
2146
+ return;
1642
2147
  } finally {
1643
2148
  currentRenderTask = null;
1644
2149
  }
2150
+ try {
2151
+ textLayerDiv = document.createElement("div");
2152
+ textLayerDiv.className = "textLayer";
2153
+ textLayerDiv.style.width = `${displayWidth}px`;
2154
+ textLayerDiv.style.height = `${displayHeight}px`;
2155
+ textLayerDiv.style.position = "absolute";
2156
+ textLayerDiv.style.top = "0";
2157
+ textLayerDiv.style.left = "0";
2158
+ textLayerDiv.style.lineHeight = "1";
2159
+ pageCard.appendChild(textLayerDiv);
2160
+ if (pdfjsLib.TextLayer) {
2161
+ const textLayer = new pdfjsLib.TextLayer({
2162
+ textContentSource: page.streamTextContent(),
2163
+ container: textLayerDiv,
2164
+ viewport
2165
+ });
2166
+ await textLayer.render();
2167
+ }
2168
+ } catch (err) {
2169
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2170
+ }
1645
2171
  };
1646
2172
  renderPage(1);
1647
2173
  let resizeTimer = null;
@@ -1651,11 +2177,48 @@ var PdfPlugin = class {
1651
2177
  if (Math.abs(zoomScale - 1) < 0.05) {
1652
2178
  renderPage(currentPage);
1653
2179
  }
1654
- }, 150);
2180
+ }, 120);
1655
2181
  });
1656
2182
  resizeObserver.observe(container);
2183
+ const onWheel = (e) => {
2184
+ if (e.ctrlKey || e.metaKey) {
2185
+ e.preventDefault();
2186
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2187
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2188
+ renderPage(currentPage);
2189
+ }
2190
+ };
2191
+ container.addEventListener("wheel", onWheel, { passive: false });
2192
+ const onKeyDown = (e) => {
2193
+ const tag = e.target?.tagName?.toLowerCase();
2194
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2195
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2196
+ if (currentPage < totalPages) {
2197
+ e.preventDefault();
2198
+ container.scrollTop = 0;
2199
+ renderPage(currentPage + 1);
2200
+ }
2201
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2202
+ if (currentPage > 1) {
2203
+ e.preventDefault();
2204
+ container.scrollTop = 0;
2205
+ renderPage(currentPage - 1);
2206
+ }
2207
+ } else if (e.key === "Home") {
2208
+ e.preventDefault();
2209
+ container.scrollTop = 0;
2210
+ renderPage(1);
2211
+ } else if (e.key === "End") {
2212
+ e.preventDefault();
2213
+ container.scrollTop = 0;
2214
+ renderPage(totalPages);
2215
+ }
2216
+ };
2217
+ window.addEventListener("keydown", onKeyDown);
1657
2218
  const cleanup = () => {
1658
2219
  resizeObserver.disconnect();
2220
+ window.removeEventListener("keydown", onKeyDown);
2221
+ container.removeEventListener("wheel", onWheel);
1659
2222
  if (resizeTimer) clearTimeout(resizeTimer);
1660
2223
  if (currentRenderTask) {
1661
2224
  try {
@@ -1674,22 +2237,26 @@ var PdfPlugin = class {
1674
2237
  const instance = {
1675
2238
  destroy: cleanup,
1676
2239
  zoomIn: () => {
1677
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2240
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1678
2241
  renderPage(currentPage);
1679
2242
  },
1680
2243
  zoomOut: () => {
1681
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2244
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1682
2245
  renderPage(currentPage);
1683
2246
  },
1684
2247
  getZoom: () => zoomScale,
1685
2248
  setZoom: (level) => {
1686
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2249
+ zoomScale = Math.max(0.25, Math.min(4, level));
1687
2250
  renderPage(currentPage);
1688
2251
  },
1689
2252
  fitToPage: () => {
1690
- fitMode = fitMode === "page" ? "width" : "page";
2253
+ fitMode = "page";
2254
+ zoomScale = 1;
2255
+ renderPage(currentPage);
2256
+ },
2257
+ fitToWidth: () => {
2258
+ fitMode = "width";
1691
2259
  zoomScale = 1;
1692
- rotation = 0;
1693
2260
  renderPage(currentPage);
1694
2261
  },
1695
2262
  rotateCW: () => {
@@ -1707,6 +2274,9 @@ var PdfPlugin = class {
1707
2274
  container.scrollTop = 0;
1708
2275
  renderPage(page);
1709
2276
  },
2277
+ toggleThumbnails: () => {
2278
+ ctx?.toggleThumbnails?.();
2279
+ },
1710
2280
  download: () => {
1711
2281
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1712
2282
  const url = URL.createObjectURL(blob);
@@ -1740,7 +2310,7 @@ var PdfPlugin = class {
1740
2310
  },
1741
2311
  getThumbnails: async () => {
1742
2312
  const thumbnails = [];
1743
- const count = Math.min(totalPages, 50);
2313
+ const count = totalPages;
1744
2314
  for (let i = 1; i <= count; i++) {
1745
2315
  thumbnails.push({
1746
2316
  index: i,
@@ -1748,9 +2318,10 @@ var PdfPlugin = class {
1748
2318
  render: async (thumbCanvas) => {
1749
2319
  try {
1750
2320
  const p = await pdfDoc.getPage(i);
1751
- const baseVp = p.getViewport({ scale: 1 });
1752
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1753
- const thumbVp = p.getViewport({ scale: thumbScale });
2321
+ const baseVp = p.getViewport({ scale: 1, rotation });
2322
+ const targetW = thumbCanvas.width || 130;
2323
+ const thumbScale = targetW / baseVp.width;
2324
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1754
2325
  thumbCanvas.height = Math.floor(thumbVp.height);
1755
2326
  const tCtx = thumbCanvas.getContext("2d");
1756
2327
  if (tCtx) {
@@ -2094,28 +2665,26 @@ var DocxPlugin = class {
2094
2665
  getToolbarActions(instance) {
2095
2666
  const totalPages = instance.getPageCount?.() ?? 1;
2096
2667
  const actions = [];
2097
- if (totalPages > 1) {
2098
- actions.push({
2099
- id: "page-nav",
2100
- icon: "",
2101
- label: "Page Navigation",
2102
- type: "page-nav",
2103
- group: "navigation",
2104
- value: instance.getCurrentPage?.() ?? 1,
2105
- max: totalPages,
2106
- execute: (action, page) => {
2107
- const cur = instance.getCurrentPage?.() ?? 1;
2108
- const max = instance.getPageCount?.() ?? 1;
2109
- if (action === "prev") {
2110
- if (cur > 1) instance.goToPage?.(cur - 1);
2111
- } else if (action === "next") {
2112
- if (cur < max) instance.goToPage?.(cur + 1);
2113
- } else if (typeof page === "number") {
2114
- instance.goToPage?.(page);
2115
- }
2668
+ actions.push({
2669
+ id: "page-nav",
2670
+ icon: "",
2671
+ label: "Page Navigation",
2672
+ type: "page-nav",
2673
+ group: "navigation",
2674
+ value: instance.getCurrentPage?.() ?? 1,
2675
+ max: totalPages,
2676
+ execute: (action, page) => {
2677
+ const cur = instance.getCurrentPage?.() ?? 1;
2678
+ const max = instance.getPageCount?.() ?? 1;
2679
+ if (action === "prev") {
2680
+ if (cur > 1) instance.goToPage?.(cur - 1);
2681
+ } else if (action === "next") {
2682
+ if (cur < max) instance.goToPage?.(cur + 1);
2683
+ } else if (typeof page === "number") {
2684
+ instance.goToPage?.(page);
2116
2685
  }
2117
- });
2118
- }
2686
+ }
2687
+ });
2119
2688
  actions.push(
2120
2689
  {
2121
2690
  id: "zoom-out",
@@ -2181,11 +2750,17 @@ var DocxPlugin = class {
2181
2750
  wrapper.className = "fp-docx-wrapper";
2182
2751
  wrapper.style.transformOrigin = "top center";
2183
2752
  wrapper.style.transition = "transform 0.2s ease";
2184
- wrapper.style.padding = "24px 16px";
2185
- wrapper.style.maxWidth = "950px";
2753
+ wrapper.style.padding = "0";
2754
+ wrapper.style.maxWidth = "none";
2755
+ wrapper.style.width = "816px";
2186
2756
  wrapper.style.margin = "0 auto";
2757
+ wrapper.style.display = "flex";
2758
+ wrapper.style.flexDirection = "column";
2759
+ wrapper.style.alignItems = "center";
2187
2760
  wrapper.style.boxSizing = "border-box";
2188
- ctx.container.style.overflow = "auto";
2761
+ ctx.container.style.overflowX = "hidden";
2762
+ ctx.container.style.overflowY = "auto";
2763
+ ctx.container.style.padding = "16px 8px";
2189
2764
  ctx.container.style.backgroundColor = "#f1f5f9";
2190
2765
  ctx.container.appendChild(wrapper);
2191
2766
  const styleOverride = document.createElement("style");
@@ -2359,39 +2934,13 @@ var DocxPlugin = class {
2359
2934
  const pageElements = sections.length > 0 ? sections : cards;
2360
2935
  const totalPages = Math.max(1, pageElements.length);
2361
2936
  let currentPage = 1;
2362
- let indicator = null;
2363
- if (totalPages > 1) {
2364
- indicator = document.createElement("div");
2365
- indicator.className = "fp-docx-page-indicator";
2366
- indicator.style.position = "sticky";
2367
- indicator.style.bottom = "16px";
2368
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2369
- indicator.style.backdropFilter = "blur(8px)";
2370
- indicator.style.color = "#f8fafc";
2371
- indicator.style.fontSize = "12px";
2372
- indicator.style.fontWeight = "600";
2373
- indicator.style.padding = "5px 14px";
2374
- indicator.style.borderRadius = "20px";
2375
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2376
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2377
- indicator.style.zIndex = "10";
2378
- indicator.style.userSelect = "none";
2379
- indicator.style.pointerEvents = "none";
2380
- indicator.style.textAlign = "center";
2381
- indicator.style.width = "fit-content";
2382
- indicator.style.margin = "16px auto 0";
2383
- ctx.container.appendChild(indicator);
2384
- }
2385
2937
  const showPage = (pageNum) => {
2386
2938
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2387
2939
  if (pageElements.length > 1) {
2388
2940
  pageElements.forEach((sec, idx) => {
2389
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
2941
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2390
2942
  });
2391
2943
  }
2392
- if (indicator) {
2393
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2394
- }
2395
2944
  ctx.container.scrollTop = 0;
2396
2945
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2397
2946
  };
@@ -2400,31 +2949,46 @@ var DocxPlugin = class {
2400
2949
  }
2401
2950
  scale = 1;
2402
2951
  let rotation = 0;
2403
- let fitMode = "width";
2404
- const calculateFitScale = (mode = fitMode) => {
2405
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2952
+ let fitMode = "page";
2953
+ let isUserZoomed = false;
2954
+ const calculateFitScale = (_mode = fitMode) => {
2955
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2406
2956
  const elW = activeEl.offsetWidth || 816;
2407
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2408
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2409
- const availH = Math.max(280, ctx.container.clientHeight - 80);
2957
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2410
2958
  const sW = availW / elW;
2411
- const sH = availH / singlePageH;
2412
- if (mode === "page") {
2413
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2414
- }
2415
- return Math.max(0.65, Math.min(1.05, sW));
2959
+ return Math.max(0.35, Math.min(3, sW));
2416
2960
  };
2417
2961
  const applyTransform = () => {
2418
2962
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2419
2963
  wrapper.style.transformOrigin = "top center";
2964
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2965
+ const elH = activeEl.offsetHeight || 1056;
2966
+ if (scale > 1) {
2967
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
2968
+ } else {
2969
+ wrapper.style.marginBottom = "32px";
2970
+ }
2420
2971
  };
2972
+ let resizeObserver = null;
2973
+ if (typeof ResizeObserver !== "undefined") {
2974
+ resizeObserver = new ResizeObserver(() => {
2975
+ if (!isUserZoomed) {
2976
+ const newFit = calculateFitScale("page");
2977
+ if (Math.abs(scale - newFit) > 0.015) {
2978
+ scale = newFit;
2979
+ applyTransform();
2980
+ }
2981
+ }
2982
+ });
2983
+ resizeObserver.observe(ctx.container);
2984
+ }
2421
2985
  setTimeout(() => {
2422
- fitMode = "width";
2423
- scale = calculateFitScale("width");
2986
+ scale = calculateFitScale("page");
2424
2987
  applyTransform();
2425
- }, 60);
2988
+ }, 40);
2426
2989
  const cleanup = () => {
2427
- indicator?.remove();
2990
+ resizeObserver?.disconnect();
2991
+ resizeObserver = null;
2428
2992
  for (const url of createdBlobUrls) {
2429
2993
  URL.revokeObjectURL(url);
2430
2994
  }
@@ -2441,21 +3005,24 @@ var DocxPlugin = class {
2441
3005
  showPage(page);
2442
3006
  },
2443
3007
  zoomIn: () => {
2444
- scale += 0.15;
3008
+ isUserZoomed = true;
3009
+ scale = Math.min(3.5, scale + 0.15);
2445
3010
  applyTransform();
2446
3011
  },
2447
3012
  zoomOut: () => {
3013
+ isUserZoomed = true;
2448
3014
  scale = Math.max(0.2, scale - 0.15);
2449
3015
  applyTransform();
2450
3016
  },
2451
3017
  getZoom: () => scale,
2452
3018
  setZoom: (level) => {
3019
+ isUserZoomed = true;
2453
3020
  scale = level;
2454
3021
  applyTransform();
2455
3022
  },
2456
3023
  fitToPage: () => {
2457
- fitMode = fitMode === "width" ? "page" : "width";
2458
- scale = calculateFitScale(fitMode);
3024
+ isUserZoomed = false;
3025
+ scale = calculateFitScale("page");
2459
3026
  rotation = 0;
2460
3027
  applyTransform();
2461
3028
  },
@@ -2845,7 +3412,7 @@ var ExcelPlugin = class {
2845
3412
  "application/vnd.ms-excel.template.macroEnabled.12",
2846
3413
  "application/vnd.oasis.opendocument.spreadsheet"
2847
3414
  ];
2848
- weight = 80;
3415
+ weight = 85;
2849
3416
  supports(file) {
2850
3417
  const ext = file.metadata.extension?.toLowerCase();
2851
3418
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2857,6 +3424,14 @@ var ExcelPlugin = class {
2857
3424
  getToolbarActions(instance) {
2858
3425
  const totalSheets = instance.getPageCount?.() ?? 1;
2859
3426
  const actions = [];
3427
+ actions.push({
3428
+ id: "thumbnails",
3429
+ icon: "thumbnails",
3430
+ label: "Sheet Thumbnails",
3431
+ type: "button",
3432
+ group: "navigation",
3433
+ execute: () => instance.toggleThumbnails?.()
3434
+ });
2860
3435
  if (totalSheets > 1) {
2861
3436
  actions.push({
2862
3437
  id: "page-nav",
@@ -2939,6 +3514,16 @@ var ExcelPlugin = class {
2939
3514
  execute: () => {
2940
3515
  instance.print?.();
2941
3516
  }
3517
+ },
3518
+ {
3519
+ id: "open-window",
3520
+ icon: "open-window",
3521
+ label: "Open in Separate Full Window",
3522
+ type: "button",
3523
+ group: "actions",
3524
+ execute: () => {
3525
+ instance.openInSeparateWindow?.();
3526
+ }
2942
3527
  }
2943
3528
  );
2944
3529
  return actions;
@@ -3104,6 +3689,63 @@ var ExcelPlugin = class {
3104
3689
  },
3105
3690
  getPageCount: () => sheetNames.length,
3106
3691
  getCurrentPage: () => currentSheetIndex,
3692
+ getThumbnails: () => {
3693
+ return sheetNames.map((name, idx) => ({
3694
+ index: idx,
3695
+ label: name,
3696
+ render: async (canvas) => {
3697
+ canvas.width = 140;
3698
+ canvas.height = 100;
3699
+ const c = canvas.getContext("2d");
3700
+ if (!c) return;
3701
+ c.fillStyle = "#ffffff";
3702
+ c.fillRect(0, 0, canvas.width, canvas.height);
3703
+ c.fillStyle = "#107c41";
3704
+ c.fillRect(0, 0, canvas.width, 16);
3705
+ c.fillStyle = "#ffffff";
3706
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3707
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3708
+ const startY = 17;
3709
+ const rowH = 13;
3710
+ const colW = 30;
3711
+ const colHeaders = ["A", "B", "C", "D"];
3712
+ c.fillStyle = "#f1f5f9";
3713
+ c.fillRect(0, startY, canvas.width, rowH);
3714
+ c.strokeStyle = "#cbd5e1";
3715
+ c.lineWidth = 0.8;
3716
+ c.strokeRect(0, startY, canvas.width, rowH);
3717
+ c.fillStyle = "#64748b";
3718
+ c.font = "bold 7px sans-serif";
3719
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3720
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3721
+ }
3722
+ const ws = wb?.Sheets[name];
3723
+ for (let ri = 1; ri <= 5; ri++) {
3724
+ const y = startY + ri * rowH;
3725
+ if (y > canvas.height - 2) break;
3726
+ c.fillStyle = "#f8fafc";
3727
+ c.fillRect(0, y, 12, rowH);
3728
+ c.fillStyle = "#94a3b8";
3729
+ c.font = "6px sans-serif";
3730
+ c.fillText(String(ri), 3, y + 9);
3731
+ c.strokeStyle = "#e2e8f0";
3732
+ c.strokeRect(0, y, canvas.width, rowH);
3733
+ c.fillStyle = "#334155";
3734
+ c.font = "6px sans-serif";
3735
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3736
+ const cellRef = `${colHeaders[ci]}${ri}`;
3737
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3738
+ if (cellVal !== void 0) {
3739
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3740
+ }
3741
+ }
3742
+ }
3743
+ c.strokeStyle = "#cbd5e1";
3744
+ c.lineWidth = 1;
3745
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3746
+ }
3747
+ }));
3748
+ },
3107
3749
  download: () => {
3108
3750
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3109
3751
  const url = URL.createObjectURL(blob);
@@ -3136,7 +3778,39 @@ var CsvPlugin = class {
3136
3778
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3137
3779
  }
3138
3780
  getToolbarActions(instance) {
3139
- return [
3781
+ const totalPages = instance.getPageCount?.() ?? 1;
3782
+ const actions = [];
3783
+ actions.push({
3784
+ id: "thumbnails",
3785
+ icon: "thumbnails",
3786
+ label: "Page Thumbnails",
3787
+ type: "button",
3788
+ group: "navigation",
3789
+ execute: () => instance.toggleThumbnails?.()
3790
+ });
3791
+ if (totalPages > 1) {
3792
+ actions.push({
3793
+ id: "page-nav",
3794
+ icon: "",
3795
+ label: "Page Navigation",
3796
+ type: "page-nav",
3797
+ group: "navigation",
3798
+ value: instance.getCurrentPage?.() ?? 1,
3799
+ max: totalPages,
3800
+ execute: (action, page) => {
3801
+ const cur = instance.getCurrentPage?.() ?? 1;
3802
+ const max = instance.getPageCount?.() ?? 1;
3803
+ if (action === "prev") {
3804
+ if (cur > 1) instance.goToPage?.(cur - 1);
3805
+ } else if (action === "next") {
3806
+ if (cur < max) instance.goToPage?.(cur + 1);
3807
+ } else if (typeof page === "number") {
3808
+ instance.goToPage?.(page);
3809
+ }
3810
+ }
3811
+ });
3812
+ }
3813
+ actions.push(
3140
3814
  {
3141
3815
  id: "zoom-out",
3142
3816
  icon: "zoom-out",
@@ -3157,6 +3831,16 @@ var CsvPlugin = class {
3157
3831
  instance.zoomIn?.();
3158
3832
  }
3159
3833
  },
3834
+ {
3835
+ id: "fit-page",
3836
+ icon: "fit-page",
3837
+ label: "Fit to View",
3838
+ type: "button",
3839
+ group: "zoom",
3840
+ execute: () => {
3841
+ instance.fitToPage?.();
3842
+ }
3843
+ },
3160
3844
  {
3161
3845
  id: "download",
3162
3846
  icon: "download",
@@ -3176,8 +3860,19 @@ var CsvPlugin = class {
3176
3860
  execute: () => {
3177
3861
  instance.print?.();
3178
3862
  }
3863
+ },
3864
+ {
3865
+ id: "open-window",
3866
+ icon: "open-window",
3867
+ label: "Open in Separate Full Window",
3868
+ type: "button",
3869
+ group: "actions",
3870
+ execute: () => {
3871
+ instance.openInSeparateWindow?.();
3872
+ }
3179
3873
  }
3180
- ];
3874
+ );
3875
+ return actions;
3181
3876
  }
3182
3877
  async render(ctx) {
3183
3878
  const decoder = new TextDecoder("utf-8");
@@ -3189,33 +3884,62 @@ var CsvPlugin = class {
3189
3884
  container.style.padding = "16px";
3190
3885
  container.style.boxSizing = "border-box";
3191
3886
  container.style.transformOrigin = "top left";
3887
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3888
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3889
+ const headerLine = allLines[0] || "";
3890
+ const headerCells = headerLine.split(delimiter);
3891
+ const dataLines = allLines.slice(1);
3892
+ const ROWS_PER_PAGE = 100;
3893
+ const totalPages = Math.max(1, Math.ceil(dataLines.length / ROWS_PER_PAGE));
3894
+ let currentPage = 1;
3192
3895
  const table = document.createElement("table");
3193
3896
  table.style.borderCollapse = "collapse";
3194
3897
  table.style.width = "100%";
3195
- table.style.fontFamily = "monospace";
3898
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3196
3899
  table.style.fontSize = "13px";
3197
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3198
- const rows = text.split(/\r?\n/).slice(0, 500);
3199
- rows.forEach((row, rowIndex) => {
3200
- if (!row.trim()) return;
3201
- const tr = document.createElement("tr");
3202
- const cells = row.split(delimiter);
3203
- cells.forEach((cell) => {
3204
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3205
- td.textContent = cell.trim();
3206
- td.style.border = "1px solid #d0d7de";
3207
- td.style.padding = "6px 12px";
3208
- if (rowIndex === 0) {
3209
- td.style.backgroundColor = "#f6f8fa";
3210
- td.style.fontWeight = "bold";
3211
- }
3212
- tr.appendChild(td);
3900
+ const renderPage = (pageNum) => {
3901
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3902
+ table.innerHTML = "";
3903
+ const headerTr = document.createElement("tr");
3904
+ headerCells.forEach((cell) => {
3905
+ const th = document.createElement("th");
3906
+ th.textContent = cell.trim();
3907
+ th.style.border = "1px solid #d0d7de";
3908
+ th.style.padding = "8px 12px";
3909
+ th.style.backgroundColor = "#f6f8fa";
3910
+ th.style.fontWeight = "600";
3911
+ th.style.whiteSpace = "nowrap";
3912
+ headerTr.appendChild(th);
3213
3913
  });
3214
- table.appendChild(tr);
3215
- });
3914
+ table.appendChild(headerTr);
3915
+ const start = (currentPage - 1) * ROWS_PER_PAGE;
3916
+ const end = Math.min(dataLines.length, start + ROWS_PER_PAGE);
3917
+ const pageRows = dataLines.slice(start, end);
3918
+ pageRows.forEach((row, idx) => {
3919
+ const tr = document.createElement("tr");
3920
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3921
+ const cells = row.split(delimiter);
3922
+ cells.forEach((cell) => {
3923
+ const td = document.createElement("td");
3924
+ td.textContent = cell.trim();
3925
+ td.style.border = "1px solid #d0d7de";
3926
+ td.style.padding = "6px 12px";
3927
+ td.style.whiteSpace = "nowrap";
3928
+ tr.appendChild(td);
3929
+ });
3930
+ table.appendChild(tr);
3931
+ });
3932
+ container.scrollTop = 0;
3933
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3934
+ };
3935
+ renderPage(1);
3216
3936
  container.appendChild(table);
3217
3937
  ctx.container.appendChild(container);
3218
3938
  let scale = 1;
3939
+ const applyScale = () => {
3940
+ container.style.transform = `scale(${scale})`;
3941
+ container.style.transformOrigin = "top left";
3942
+ };
3219
3943
  const cleanup = () => {
3220
3944
  container.remove();
3221
3945
  ctx.container.innerHTML = "";
@@ -3223,22 +3947,86 @@ var CsvPlugin = class {
3223
3947
  ctx.signal.addEventListener("abort", cleanup);
3224
3948
  return {
3225
3949
  destroy: cleanup,
3950
+ getPageCount: () => totalPages,
3951
+ getCurrentPage: () => currentPage,
3952
+ goToPage: (page) => renderPage(page),
3953
+ getThumbnails: () => {
3954
+ const thumbnails = [];
3955
+ for (let i = 0; i < totalPages; i++) {
3956
+ const pageIdx = i;
3957
+ const startRow = pageIdx * ROWS_PER_PAGE + 1;
3958
+ const endRow = Math.min(dataLines.length, (pageIdx + 1) * ROWS_PER_PAGE);
3959
+ const label = totalPages === 1 ? "Table" : `Page ${pageIdx + 1} (${startRow}-${endRow})`;
3960
+ thumbnails.push({
3961
+ index: pageIdx,
3962
+ label,
3963
+ render: async (canvas) => {
3964
+ canvas.width = 140;
3965
+ canvas.height = 100;
3966
+ const c = canvas.getContext("2d");
3967
+ if (!c) return;
3968
+ c.fillStyle = "#ffffff";
3969
+ c.fillRect(0, 0, canvas.width, canvas.height);
3970
+ c.fillStyle = "#2563eb";
3971
+ c.fillRect(0, 0, canvas.width, 16);
3972
+ c.fillStyle = "#ffffff";
3973
+ c.font = "bold 8px sans-serif";
3974
+ c.fillText(`\u{1F4C8} ${label.slice(0, 18)}`, 6, 11);
3975
+ const startY = 17;
3976
+ const rowH = 13;
3977
+ const colW = 28;
3978
+ const cols = Math.min(4, headerCells.length || 4);
3979
+ c.fillStyle = "#f1f5f9";
3980
+ c.fillRect(0, startY, canvas.width, rowH);
3981
+ c.strokeStyle = "#cbd5e1";
3982
+ c.lineWidth = 0.8;
3983
+ c.strokeRect(0, startY, canvas.width, rowH);
3984
+ c.fillStyle = "#64748b";
3985
+ c.font = "bold 7px sans-serif";
3986
+ for (let ci = 0; ci < cols; ci++) {
3987
+ const hName = headerCells[ci] || `Col ${ci + 1}`;
3988
+ c.fillText(hName.slice(0, 5), 8 + ci * colW, startY + 9);
3989
+ }
3990
+ const rowsToPreview = dataLines.slice(pageIdx * ROWS_PER_PAGE, pageIdx * ROWS_PER_PAGE + 5);
3991
+ for (let ri = 0; ri < rowsToPreview.length; ri++) {
3992
+ const y = startY + (ri + 1) * rowH;
3993
+ if (y > canvas.height - 2) break;
3994
+ c.strokeStyle = "#e2e8f0";
3995
+ c.strokeRect(0, y, canvas.width, rowH);
3996
+ const cVals = rowsToPreview[ri].split(delimiter);
3997
+ c.fillStyle = "#334155";
3998
+ c.font = "6px sans-serif";
3999
+ for (let ci = 0; ci < cols; ci++) {
4000
+ const val = cVals[ci] || "";
4001
+ c.fillText(val.trim().slice(0, 6), 8 + ci * colW, y + 9);
4002
+ }
4003
+ }
4004
+ c.strokeStyle = "#cbd5e1";
4005
+ c.lineWidth = 1;
4006
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4007
+ }
4008
+ });
4009
+ }
4010
+ return thumbnails;
4011
+ },
3226
4012
  zoomIn: () => {
3227
4013
  scale += 0.1;
3228
- container.style.transform = `scale(${scale})`;
4014
+ applyScale();
3229
4015
  },
3230
4016
  zoomOut: () => {
3231
4017
  scale = Math.max(0.2, scale - 0.1);
3232
- container.style.transform = `scale(${scale})`;
4018
+ applyScale();
3233
4019
  },
3234
4020
  getZoom: () => scale,
3235
4021
  setZoom: (level) => {
3236
4022
  scale = level;
3237
- container.style.transform = `scale(${scale})`;
4023
+ applyScale();
3238
4024
  },
3239
4025
  fitToPage: () => {
3240
- scale = 1;
3241
- container.style.transform = `scale(1)`;
4026
+ const availW = Math.max(280, (ctx.container.clientWidth || window.innerWidth) - 48);
4027
+ const tW = table.offsetWidth || 600;
4028
+ scale = Math.max(0.35, Math.min(1, availW / tW));
4029
+ applyScale();
3242
4030
  },
3243
4031
  download: () => {
3244
4032
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3302,28 +4090,34 @@ var CodePlugin = class {
3302
4090
  getToolbarActions(instance) {
3303
4091
  const totalPages = instance.getPageCount?.() ?? 1;
3304
4092
  const actions = [];
3305
- if (totalPages > 1) {
3306
- actions.push({
3307
- id: "page-nav",
3308
- icon: "",
3309
- label: "Page Navigation",
3310
- type: "page-nav",
3311
- group: "navigation",
3312
- value: instance.getCurrentPage?.() ?? 1,
3313
- max: totalPages,
3314
- execute: (action, page) => {
3315
- const cur = instance.getCurrentPage?.() ?? 1;
3316
- const max = instance.getPageCount?.() ?? 1;
3317
- if (action === "prev") {
3318
- if (cur > 1) instance.goToPage?.(cur - 1);
3319
- } else if (action === "next") {
3320
- if (cur < max) instance.goToPage?.(cur + 1);
3321
- } else if (typeof page === "number") {
3322
- instance.goToPage?.(page);
3323
- }
4093
+ actions.push({
4094
+ id: "thumbnails",
4095
+ icon: "thumbnails",
4096
+ label: "Page Thumbnails",
4097
+ type: "button",
4098
+ group: "navigation",
4099
+ execute: () => instance.toggleThumbnails?.()
4100
+ });
4101
+ actions.push({
4102
+ id: "page-nav",
4103
+ icon: "",
4104
+ label: "Page Navigation",
4105
+ type: "page-nav",
4106
+ group: "navigation",
4107
+ value: instance.getCurrentPage?.() ?? 1,
4108
+ max: totalPages,
4109
+ execute: (action, page) => {
4110
+ const cur = instance.getCurrentPage?.() ?? 1;
4111
+ const max = instance.getPageCount?.() ?? 1;
4112
+ if (action === "prev") {
4113
+ if (cur > 1) instance.goToPage?.(cur - 1);
4114
+ } else if (action === "next") {
4115
+ if (cur < max) instance.goToPage?.(cur + 1);
4116
+ } else if (typeof page === "number") {
4117
+ instance.goToPage?.(page);
3324
4118
  }
3325
- });
3326
- }
4119
+ }
4120
+ });
3327
4121
  actions.push(
3328
4122
  {
3329
4123
  id: "zoom-out",
@@ -3467,16 +4261,19 @@ var CodePlugin = class {
3467
4261
  let fontSize = 13;
3468
4262
  let rotation = 0;
3469
4263
  let zoomLevel = 1;
4264
+ let isUserZoomed = false;
3470
4265
  const pre = document.createElement("pre");
3471
4266
  const code = document.createElement("code");
3472
4267
  if (isTxt) {
4268
+ container.style.overflowX = "hidden";
4269
+ container.style.overflowY = "auto";
3473
4270
  container.style.backgroundColor = "#f1f5f9";
3474
- container.style.padding = "32px";
4271
+ container.style.padding = "16px 8px";
3475
4272
  container.style.boxSizing = "border-box";
3476
4273
  container.style.display = "flex";
3477
4274
  container.style.flexDirection = "column";
3478
4275
  container.style.alignItems = "center";
3479
- pre.style.margin = "0";
4276
+ pre.style.margin = "0 auto";
3480
4277
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3481
4278
  pre.style.fontSize = "13px";
3482
4279
  pre.style.color = "#1e293b";
@@ -3488,10 +4285,12 @@ var CodePlugin = class {
3488
4285
  pre.style.minHeight = "1056px";
3489
4286
  pre.style.padding = "72px 56px";
3490
4287
  pre.style.boxSizing = "border-box";
3491
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4288
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3492
4289
  pre.style.borderRadius = "4px";
3493
4290
  pre.style.transformOrigin = "top center";
4291
+ pre.style.transition = "transform 0.15s ease";
3494
4292
  } else {
4293
+ container.style.overflow = "auto";
3495
4294
  container.style.backgroundColor = "#1e1e1e";
3496
4295
  container.style.color = "#d4d4d4";
3497
4296
  container.style.padding = "16px";
@@ -3523,42 +4322,9 @@ var CodePlugin = class {
3523
4322
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3524
4323
  pre.appendChild(code);
3525
4324
  container.appendChild(pre);
3526
- let indicator = null;
3527
- if (totalPages > 1) {
3528
- indicator = document.createElement("div");
3529
- indicator.className = "fp-code-page-indicator";
3530
- indicator.style.position = "sticky";
3531
- indicator.style.bottom = "16px";
3532
- if (isTxt) {
3533
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3534
- indicator.style.color = "#334155";
3535
- indicator.style.border = "1px solid #e2e8f0";
3536
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3537
- } else {
3538
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3539
- indicator.style.color = "#f8fafc";
3540
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3541
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3542
- indicator.style.backdropFilter = "blur(8px)";
3543
- }
3544
- indicator.style.fontSize = "12px";
3545
- indicator.style.fontWeight = "600";
3546
- indicator.style.padding = "5px 14px";
3547
- indicator.style.borderRadius = "20px";
3548
- indicator.style.zIndex = "10";
3549
- indicator.style.userSelect = "none";
3550
- indicator.style.pointerEvents = "none";
3551
- indicator.style.textAlign = "center";
3552
- indicator.style.width = "fit-content";
3553
- indicator.style.margin = "16px auto 0";
3554
- container.appendChild(indicator);
3555
- }
3556
4325
  const showPage = (pageNum) => {
3557
4326
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3558
4327
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3559
- if (indicator) {
3560
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3561
- }
3562
4328
  container.scrollTop = 0;
3563
4329
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3564
4330
  };
@@ -3566,36 +4332,90 @@ var CodePlugin = class {
3566
4332
  showPage(1);
3567
4333
  }
3568
4334
  ctx.container.appendChild(container);
3569
- const cleanup = () => {
3570
- indicator?.remove();
3571
- container.remove();
3572
- ctx.container.innerHTML = "";
4335
+ const calculateTxtFit = () => {
4336
+ const availW = Math.max(280, container.clientWidth - 32);
4337
+ return Math.max(0.4, Math.min(3, availW / 816));
3573
4338
  };
3574
- ctx.signal.addEventListener("abort", cleanup);
3575
4339
  const updateTransform = () => {
3576
4340
  if (isTxt) {
3577
4341
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4342
+ const scaledH = 1056 * zoomLevel;
4343
+ const extraH = Math.max(0, scaledH - 1056);
4344
+ pre.style.marginBottom = `${extraH + 32}px`;
3578
4345
  } else {
3579
4346
  pre.style.transform = `rotate(${rotation}deg)`;
3580
4347
  pre.style.fontSize = `${fontSize}px`;
3581
4348
  }
3582
4349
  };
4350
+ let resizeObserver = null;
4351
+ if (isTxt) {
4352
+ setTimeout(() => {
4353
+ zoomLevel = calculateTxtFit();
4354
+ updateTransform();
4355
+ }, 60);
4356
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4357
+ if (!isUserZoomed) {
4358
+ zoomLevel = calculateTxtFit();
4359
+ updateTransform();
4360
+ }
4361
+ }) : null;
4362
+ resizeObserver?.observe(container);
4363
+ }
4364
+ const cleanup = () => {
4365
+ resizeObserver?.disconnect();
4366
+ container.remove();
4367
+ ctx.container.innerHTML = "";
4368
+ };
4369
+ ctx.signal.addEventListener("abort", cleanup);
3583
4370
  return {
3584
4371
  destroy: cleanup,
3585
4372
  getPageCount: () => totalPages,
3586
4373
  getCurrentPage: () => currentPage,
3587
4374
  goToPage: (page) => showPage(page),
4375
+ getThumbnails: () => {
4376
+ return rawPages.map((pageText, idx) => ({
4377
+ index: idx,
4378
+ label: `Page ${idx + 1}`,
4379
+ render: async (canvas) => {
4380
+ canvas.width = 140;
4381
+ canvas.height = 180;
4382
+ const c = canvas.getContext("2d");
4383
+ if (!c) return;
4384
+ c.fillStyle = "#ffffff";
4385
+ c.fillRect(0, 0, canvas.width, canvas.height);
4386
+ c.strokeStyle = "#cbd5e1";
4387
+ c.lineWidth = 1;
4388
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4389
+ c.fillStyle = "#64748b";
4390
+ const lines = (pageText || "").split("\n").slice(0, 24);
4391
+ const lineH = 6;
4392
+ const startY = 14;
4393
+ const startX = 12;
4394
+ const maxW = canvas.width - 24;
4395
+ c.font = "5px monospace";
4396
+ for (let li = 0; li < lines.length; li++) {
4397
+ const l = lines[li].trim();
4398
+ if (!l) continue;
4399
+ const y = startY + li * lineH;
4400
+ if (y > canvas.height - 12) break;
4401
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4402
+ }
4403
+ }
4404
+ }));
4405
+ },
3588
4406
  zoomIn: () => {
4407
+ isUserZoomed = true;
3589
4408
  if (isTxt) {
3590
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4409
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3591
4410
  } else {
3592
4411
  fontSize = Math.min(32, fontSize + 2);
3593
4412
  }
3594
4413
  updateTransform();
3595
4414
  },
3596
4415
  zoomOut: () => {
4416
+ isUserZoomed = true;
3597
4417
  if (isTxt) {
3598
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4418
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3599
4419
  } else {
3600
4420
  fontSize = Math.max(8, fontSize - 2);
3601
4421
  }
@@ -3603,6 +4423,7 @@ var CodePlugin = class {
3603
4423
  },
3604
4424
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3605
4425
  setZoom: (level) => {
4426
+ isUserZoomed = true;
3606
4427
  if (isTxt) {
3607
4428
  zoomLevel = level;
3608
4429
  } else {
@@ -3611,9 +4432,10 @@ var CodePlugin = class {
3611
4432
  updateTransform();
3612
4433
  },
3613
4434
  fitToPage: () => {
4435
+ isUserZoomed = false;
3614
4436
  fontSize = 13;
3615
4437
  rotation = 0;
3616
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4438
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3617
4439
  updateTransform();
3618
4440
  },
3619
4441
  rotateCW: () => {
@@ -4494,28 +5316,26 @@ var RtfPlugin = class {
4494
5316
  getToolbarActions(instance) {
4495
5317
  const totalPages = instance.getPageCount?.() ?? 1;
4496
5318
  const actions = [];
4497
- if (totalPages > 1) {
4498
- actions.push({
4499
- id: "page-nav",
4500
- icon: "",
4501
- label: "Page Navigation",
4502
- type: "page-nav",
4503
- group: "navigation",
4504
- value: instance.getCurrentPage?.() ?? 1,
4505
- max: totalPages,
4506
- execute: (action, page) => {
4507
- const cur = instance.getCurrentPage?.() ?? 1;
4508
- const max = instance.getPageCount?.() ?? 1;
4509
- if (action === "prev") {
4510
- if (cur > 1) instance.goToPage?.(cur - 1);
4511
- } else if (action === "next") {
4512
- if (cur < max) instance.goToPage?.(cur + 1);
4513
- } else if (typeof page === "number") {
4514
- instance.goToPage?.(page);
4515
- }
5319
+ actions.push({
5320
+ id: "page-nav",
5321
+ icon: "",
5322
+ label: "Page Navigation",
5323
+ type: "page-nav",
5324
+ group: "navigation",
5325
+ value: instance.getCurrentPage?.() ?? 1,
5326
+ max: totalPages,
5327
+ execute: (action, page) => {
5328
+ const cur = instance.getCurrentPage?.() ?? 1;
5329
+ const max = instance.getPageCount?.() ?? 1;
5330
+ if (action === "prev") {
5331
+ if (cur > 1) instance.goToPage?.(cur - 1);
5332
+ } else if (action === "next") {
5333
+ if (cur < max) instance.goToPage?.(cur + 1);
5334
+ } else if (typeof page === "number") {
5335
+ instance.goToPage?.(page);
4516
5336
  }
4517
- });
4518
- }
5337
+ }
5338
+ });
4519
5339
  actions.push(
4520
5340
  {
4521
5341
  id: "zoom-out",
@@ -4587,43 +5407,57 @@ var RtfPlugin = class {
4587
5407
  async render(ctx) {
4588
5408
  const wrapper = document.createElement("div");
4589
5409
  wrapper.className = "fp-rtf-wrapper";
4590
- wrapper.style.padding = "32px";
4591
- wrapper.style.maxWidth = "850px";
5410
+ wrapper.style.padding = "0";
5411
+ wrapper.style.width = "816px";
4592
5412
  wrapper.style.margin = "0 auto";
4593
- wrapper.style.backgroundColor = "#fff";
4594
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4595
- wrapper.style.borderRadius = "4px";
4596
- wrapper.style.minHeight = "100%";
5413
+ wrapper.style.boxSizing = "border-box";
5414
+ wrapper.style.display = "flex";
5415
+ wrapper.style.flexDirection = "column";
5416
+ wrapper.style.alignItems = "center";
5417
+ wrapper.style.backgroundColor = "transparent";
4597
5418
  wrapper.style.transformOrigin = "top center";
4598
- wrapper.style.transition = "transform 0.2s ease";
4599
- ctx.container.style.overflow = "auto";
4600
- ctx.container.style.padding = "24px";
5419
+ wrapper.style.transition = "transform 0.15s ease";
5420
+ ctx.container.style.overflowX = "hidden";
5421
+ ctx.container.style.overflowY = "auto";
5422
+ ctx.container.style.padding = "16px 8px";
4601
5423
  ctx.container.style.backgroundColor = "#f1f5f9";
4602
5424
  ctx.container.appendChild(wrapper);
4603
5425
  let scale = 1;
4604
5426
  let rotation = 0;
4605
5427
  let pageElements = [];
4606
- let fitMode = "width";
5428
+ let isUserZoomed = false;
5429
+ let fitMode = ctx?.options?.fitMode || "width";
4607
5430
  const calculateFitScale = (mode = fitMode) => {
4608
5431
  const activeEl = pageElements[currentPage - 1] || wrapper;
4609
- const elW = activeEl.offsetWidth || 850;
4610
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4611
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4612
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5432
+ const elW = 816;
5433
+ const singlePageH = activeEl?.offsetHeight || 1056;
5434
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5435
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4613
5436
  const sW = availW / elW;
4614
5437
  const sH = availH / singlePageH;
4615
5438
  if (mode === "page") {
4616
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5439
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4617
5440
  }
4618
- return Math.max(0.65, Math.min(1.05, sW));
5441
+ return Math.max(0.4, Math.min(3, sW));
4619
5442
  };
4620
5443
  const applyTransform = () => {
4621
5444
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4622
5445
  wrapper.style.transformOrigin = "top center";
5446
+ const activeEl = pageElements[currentPage - 1];
5447
+ const baseH = activeEl?.offsetHeight || 1056;
5448
+ const scaledH = baseH * scale;
5449
+ const extraH = Math.max(0, scaledH - baseH);
5450
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4623
5451
  };
5452
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5453
+ if (!isUserZoomed) {
5454
+ scale = calculateFitScale(fitMode);
5455
+ applyTransform();
5456
+ }
5457
+ }) : null;
5458
+ resizeObserver?.observe(ctx.container);
4624
5459
  setTimeout(() => {
4625
- fitMode = "width";
4626
- scale = calculateFitScale("width");
5460
+ scale = calculateFitScale(fitMode);
4627
5461
  applyTransform();
4628
5462
  }, 60);
4629
5463
  try {
@@ -4633,20 +5467,34 @@ var RtfPlugin = class {
4633
5467
  const doc = new RTFJS.Document(ctx.buffer, {});
4634
5468
  const htmlElements = await doc.render();
4635
5469
  const contentNodes = [];
4636
- for (const item of htmlElements) {
4637
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4638
- contentNodes.push(...Array.from(item.children));
4639
- } else {
4640
- contentNodes.push(item);
5470
+ const extractBlocks = (nodes) => {
5471
+ for (const item of nodes) {
5472
+ if (!item || !(item instanceof HTMLElement)) continue;
5473
+ const tag = item.tagName.toLowerCase();
5474
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5475
+ const ct = c.tagName.toLowerCase();
5476
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5477
+ });
5478
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5479
+ extractBlocks(Array.from(item.children));
5480
+ } else {
5481
+ if (tag === "p" || tag === "div") {
5482
+ item.style.display = "block";
5483
+ item.style.marginBottom = "12px";
5484
+ item.style.lineHeight = "1.6";
5485
+ }
5486
+ contentNodes.push(item);
5487
+ }
4641
5488
  }
4642
- }
5489
+ };
5490
+ extractBlocks(htmlElements);
4643
5491
  wrapper.innerHTML = "";
4644
5492
  contentNodes.forEach((node) => wrapper.appendChild(node));
4645
5493
  const childHeights = contentNodes.map((c) => {
4646
5494
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4647
5495
  const offH = c.offsetHeight || 0;
4648
5496
  const textLen = c.textContent?.trim().length || 0;
4649
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
5497
+ const estH = Math.max(28, Math.ceil(textLen / 75) * 24 + 16);
4650
5498
  return Math.max(rectH, offH, estH);
4651
5499
  });
4652
5500
  wrapper.innerHTML = "";
@@ -4663,6 +5511,7 @@ var RtfPlugin = class {
4663
5511
  card.style.marginBottom = "24px";
4664
5512
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4665
5513
  card.style.lineHeight = "1.6";
5514
+ card.style.color = "#1e293b";
4666
5515
  return card;
4667
5516
  };
4668
5517
  let curCard = createRtfCard();
@@ -4698,9 +5547,8 @@ var RtfPlugin = class {
4698
5547
  pageCard.style.padding = "72px 56px";
4699
5548
  pageCard.style.width = "816px";
4700
5549
  pageCard.style.minHeight = "1056px";
4701
- pageCard.style.maxWidth = "100%";
4702
5550
  pageCard.style.boxSizing = "border-box";
4703
- pageCard.style.fontFamily = "serif";
5551
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4704
5552
  pageCard.style.fontSize = "12pt";
4705
5553
  pageCard.style.lineHeight = "1.6";
4706
5554
  pageCard.style.color = "#1e293b";
@@ -4713,29 +5561,6 @@ var RtfPlugin = class {
4713
5561
  }
4714
5562
  const totalPages = Math.max(1, pageElements.length);
4715
5563
  let currentPage = 1;
4716
- let indicator = null;
4717
- if (totalPages > 1) {
4718
- indicator = document.createElement("div");
4719
- indicator.className = "fp-rtf-page-indicator";
4720
- indicator.style.position = "sticky";
4721
- indicator.style.bottom = "16px";
4722
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4723
- indicator.style.backdropFilter = "blur(8px)";
4724
- indicator.style.color = "#f8fafc";
4725
- indicator.style.fontSize = "12px";
4726
- indicator.style.fontWeight = "600";
4727
- indicator.style.padding = "5px 14px";
4728
- indicator.style.borderRadius = "20px";
4729
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4730
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4731
- indicator.style.zIndex = "10";
4732
- indicator.style.userSelect = "none";
4733
- indicator.style.pointerEvents = "none";
4734
- indicator.style.textAlign = "center";
4735
- indicator.style.width = "fit-content";
4736
- indicator.style.margin = "16px auto 0";
4737
- ctx.container.appendChild(indicator);
4738
- }
4739
5564
  const showPage = (pageNum) => {
4740
5565
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4741
5566
  if (totalPages > 1) {
@@ -4743,9 +5568,6 @@ var RtfPlugin = class {
4743
5568
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4744
5569
  });
4745
5570
  }
4746
- if (indicator) {
4747
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4748
- }
4749
5571
  ctx.container.scrollTop = 0;
4750
5572
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4751
5573
  };
@@ -4753,7 +5575,7 @@ var RtfPlugin = class {
4753
5575
  showPage(1);
4754
5576
  }
4755
5577
  const cleanup = () => {
4756
- indicator?.remove();
5578
+ resizeObserver?.disconnect();
4757
5579
  wrapper.remove();
4758
5580
  ctx.container.innerHTML = "";
4759
5581
  };
@@ -4764,21 +5586,25 @@ var RtfPlugin = class {
4764
5586
  getCurrentPage: () => currentPage,
4765
5587
  goToPage: (page) => showPage(page),
4766
5588
  zoomIn: () => {
5589
+ isUserZoomed = true;
4767
5590
  scale += 0.15;
4768
5591
  applyTransform();
4769
5592
  },
4770
5593
  zoomOut: () => {
5594
+ isUserZoomed = true;
4771
5595
  scale = Math.max(0.2, scale - 0.15);
4772
5596
  applyTransform();
4773
5597
  },
4774
5598
  getZoom: () => scale,
4775
5599
  setZoom: (level) => {
5600
+ isUserZoomed = true;
4776
5601
  scale = level;
4777
5602
  applyTransform();
4778
5603
  },
4779
5604
  fitToPage: () => {
4780
- fitMode = fitMode === "width" ? "page" : "width";
4781
- scale = calculateFitScale(fitMode);
5605
+ isUserZoomed = false;
5606
+ fitMode = "width";
5607
+ scale = calculateFitScale("width");
4782
5608
  rotation = 0;
4783
5609
  applyTransform();
4784
5610
  },
@@ -4966,40 +5792,36 @@ var OpenDocumentPlugin = class {
4966
5792
  const isPresentation = instance.isPresentation;
4967
5793
  const totalPages = instance.getPageCount?.() ?? 1;
4968
5794
  const actions = [];
4969
- if (isPresentation || totalPages > 1) {
4970
- if (isPresentation) {
4971
- actions.push({
4972
- id: "thumbnails",
4973
- icon: "thumbnails",
4974
- label: "Slide Thumbnails",
4975
- type: "button",
4976
- group: "navigation",
4977
- execute: () => instance.toggleThumbnails?.()
4978
- });
4979
- }
4980
- actions.push({
4981
- id: "page-nav",
4982
- icon: "",
4983
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
4984
- type: "page-nav",
4985
- group: "navigation",
4986
- value: instance.getCurrentPage?.() ?? 1,
4987
- max: totalPages,
4988
- execute: (action, page) => {
4989
- const cur = instance.getCurrentPage?.() ?? 1;
4990
- const max = instance.getPageCount?.() ?? 1;
4991
- if (action === "prev") {
4992
- if (cur > 1) instance.goToPage?.(cur - 1);
4993
- } else if (action === "next") {
4994
- if (cur < max) instance.goToPage?.(cur + 1);
4995
- } else if (typeof page === "number") {
4996
- instance.goToPage?.(page);
4997
- } else if (typeof action === "number") {
4998
- instance.goToPage?.(action);
4999
- }
5795
+ actions.push({
5796
+ id: "thumbnails",
5797
+ icon: "thumbnails",
5798
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
5799
+ type: "button",
5800
+ group: "navigation",
5801
+ execute: () => instance.toggleThumbnails?.()
5802
+ });
5803
+ actions.push({
5804
+ id: "page-nav",
5805
+ icon: "",
5806
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
5807
+ type: "page-nav",
5808
+ group: "navigation",
5809
+ value: instance.getCurrentPage?.() ?? 1,
5810
+ max: totalPages,
5811
+ execute: (action, page) => {
5812
+ const cur = instance.getCurrentPage?.() ?? 1;
5813
+ const max = instance.getPageCount?.() ?? 1;
5814
+ if (action === "prev") {
5815
+ if (cur > 1) instance.goToPage?.(cur - 1);
5816
+ } else if (action === "next") {
5817
+ if (cur < max) instance.goToPage?.(cur + 1);
5818
+ } else if (typeof page === "number") {
5819
+ instance.goToPage?.(page);
5820
+ } else if (typeof action === "number") {
5821
+ instance.goToPage?.(action);
5000
5822
  }
5001
- });
5002
- }
5823
+ }
5824
+ });
5003
5825
  actions.push(
5004
5826
  {
5005
5827
  id: "zoom-out",
@@ -5090,49 +5912,65 @@ var OpenDocumentPlugin = class {
5090
5912
  container.className = "fp-odf-container";
5091
5913
  container.style.width = "100%";
5092
5914
  container.style.height = "100%";
5093
- container.style.overflow = "auto";
5094
- container.style.padding = "24px";
5915
+ container.style.overflowX = "hidden";
5916
+ container.style.overflowY = "auto";
5917
+ container.style.padding = "16px 8px";
5095
5918
  container.style.backgroundColor = "#f1f5f9";
5096
5919
  const wrapper = document.createElement("div");
5097
5920
  wrapper.className = "fp-odf-wrapper";
5098
5921
  wrapper.style.margin = "0 auto";
5922
+ wrapper.style.width = isPresentation ? "960px" : "816px";
5923
+ wrapper.style.boxSizing = "border-box";
5099
5924
  wrapper.style.backgroundColor = "#ffffff";
5100
5925
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5101
5926
  wrapper.style.borderRadius = "4px";
5102
5927
  wrapper.style.transformOrigin = "top center";
5103
- wrapper.style.transition = "transform 0.2s ease";
5928
+ wrapper.style.transition = "transform 0.15s ease";
5104
5929
  container.appendChild(wrapper);
5105
5930
  ctx.container.appendChild(container);
5106
5931
  let scale = 1;
5107
5932
  let rotation = 0;
5108
- let fitMode = "width";
5933
+ let isUserZoomed = false;
5934
+ let fitMode = ctx?.options?.fitMode || "width";
5935
+ let currentPage = 1;
5936
+ let totalPages = 1;
5937
+ let slides = [];
5109
5938
  const calculateFitScale = (mode = fitMode) => {
5110
- const elW = wrapper.offsetWidth || 850;
5111
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5112
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5113
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5939
+ const activeSlide = slides[currentPage - 1];
5940
+ const elW = isPresentation ? 960 : 816;
5941
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5942
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5943
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5114
5944
  const sW = availW / elW;
5115
5945
  const sH = availH / (isPresentation ? 540 : singlePageH);
5116
5946
  if (isPresentation) {
5117
- return Math.min(1, Math.min(sW, sH));
5947
+ return Math.min(2.5, Math.min(sW, sH));
5118
5948
  }
5119
5949
  if (mode === "page") {
5120
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5950
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5121
5951
  }
5122
- return Math.max(0.65, Math.min(1.05, sW));
5952
+ return Math.max(0.4, Math.min(3, sW));
5123
5953
  };
5124
5954
  const applyTransform = () => {
5125
5955
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5126
5956
  wrapper.style.transformOrigin = "top center";
5957
+ const activeSlide = slides[currentPage - 1];
5958
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
5959
+ const scaledH = baseH * scale;
5960
+ const extraH = Math.max(0, scaledH - baseH);
5961
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5127
5962
  };
5963
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5964
+ if (!isUserZoomed) {
5965
+ scale = calculateFitScale(fitMode);
5966
+ applyTransform();
5967
+ }
5968
+ }) : null;
5969
+ resizeObserver?.observe(ctx.container);
5128
5970
  setTimeout(() => {
5129
- fitMode = "width";
5130
- scale = calculateFitScale("width");
5971
+ scale = calculateFitScale(fitMode);
5131
5972
  applyTransform();
5132
5973
  }, 60);
5133
- let currentPage = 1;
5134
- let totalPages = 1;
5135
- let slides = [];
5136
5974
  if (isPresentation) {
5137
5975
  wrapper.style.maxWidth = "960px";
5138
5976
  wrapper.style.aspectRatio = "16 / 9";
@@ -5233,24 +6071,9 @@ var OpenDocumentPlugin = class {
5233
6071
  wrapper.appendChild(page);
5234
6072
  slides.push(page);
5235
6073
  });
5236
- const pageIndicator = document.createElement("div");
5237
- pageIndicator.className = "fp-odt-page-indicator";
5238
- pageIndicator.style.position = "sticky";
5239
- pageIndicator.style.bottom = "16px";
5240
- pageIndicator.style.left = "50%";
5241
- pageIndicator.style.transform = "translateX(-50%)";
5242
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5243
- pageIndicator.style.color = "#fff";
5244
- pageIndicator.style.padding = "6px 12px";
5245
- pageIndicator.style.borderRadius = "16px";
5246
- pageIndicator.style.fontSize = "12px";
5247
- pageIndicator.style.zIndex = "100";
5248
- pageIndicator.style.display = "inline-block";
5249
- pageIndicator.style.width = "fit-content";
5250
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5251
- container.appendChild(pageIndicator);
5252
6074
  }
5253
6075
  const cleanup = () => {
6076
+ resizeObserver?.disconnect();
5254
6077
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5255
6078
  container.remove();
5256
6079
  ctx.container.innerHTML = "";
@@ -5262,10 +6085,6 @@ var OpenDocumentPlugin = class {
5262
6085
  slides.forEach((s, idx) => {
5263
6086
  s.style.display = idx === page - 1 ? "block" : "none";
5264
6087
  });
5265
- const indicator = container.querySelector(".fp-odt-page-indicator");
5266
- if (indicator) {
5267
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5268
- }
5269
6088
  container.scrollTop = 0;
5270
6089
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5271
6090
  };
@@ -5273,21 +6092,25 @@ var OpenDocumentPlugin = class {
5273
6092
  destroy: cleanup,
5274
6093
  isPresentation,
5275
6094
  zoomIn: () => {
6095
+ isUserZoomed = true;
5276
6096
  scale += 0.15;
5277
6097
  applyTransform();
5278
6098
  },
5279
6099
  zoomOut: () => {
6100
+ isUserZoomed = true;
5280
6101
  scale = Math.max(0.2, scale - 0.15);
5281
6102
  applyTransform();
5282
6103
  },
5283
6104
  getZoom: () => scale,
5284
6105
  setZoom: (level) => {
6106
+ isUserZoomed = true;
5285
6107
  scale = level;
5286
6108
  applyTransform();
5287
6109
  },
5288
6110
  fitToPage: () => {
5289
- fitMode = fitMode === "width" ? "page" : "width";
5290
- scale = calculateFitScale(fitMode);
6111
+ isUserZoomed = false;
6112
+ fitMode = "width";
6113
+ scale = calculateFitScale("width");
5291
6114
  rotation = 0;
5292
6115
  applyTransform();
5293
6116
  },
@@ -5303,23 +6126,45 @@ var OpenDocumentPlugin = class {
5303
6126
  getPageCount: () => totalPages,
5304
6127
  getCurrentPage: () => currentPage,
5305
6128
  getThumbnails: async () => {
5306
- if (!isPresentation) return [];
5307
- return slides.map((_, idx) => ({
5308
- index: idx,
5309
- label: `Slide ${idx + 1}`,
5310
- render: async (canvas) => {
5311
- const ctx2d = canvas.getContext("2d");
5312
- if (!ctx2d) return;
5313
- canvas.width = 160;
5314
- canvas.height = 90;
5315
- ctx2d.fillStyle = "#f8fafc";
5316
- ctx2d.fillRect(0, 0, 160, 90);
5317
- ctx2d.fillStyle = "#334155";
5318
- ctx2d.font = "bold 12px sans-serif";
5319
- ctx2d.textAlign = "center";
5320
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5321
- }
5322
- }));
6129
+ const count = totalPages || slides.length || 1;
6130
+ const items = [];
6131
+ for (let idx = 0; idx < count; idx++) {
6132
+ const itemIdx = idx;
6133
+ items.push({
6134
+ index: itemIdx,
6135
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6136
+ render: async (canvas) => {
6137
+ const ctx2d = canvas.getContext("2d");
6138
+ if (!ctx2d) return;
6139
+ if (isPresentation) {
6140
+ canvas.width = 160;
6141
+ canvas.height = 90;
6142
+ ctx2d.fillStyle = "#f8fafc";
6143
+ ctx2d.fillRect(0, 0, 160, 90);
6144
+ ctx2d.strokeStyle = "#cbd5e1";
6145
+ ctx2d.lineWidth = 1;
6146
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6147
+ ctx2d.fillStyle = "#334155";
6148
+ ctx2d.font = "bold 11px sans-serif";
6149
+ ctx2d.textAlign = "center";
6150
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6151
+ } else {
6152
+ canvas.width = 140;
6153
+ canvas.height = 180;
6154
+ ctx2d.fillStyle = "#ffffff";
6155
+ ctx2d.fillRect(0, 0, 140, 180);
6156
+ ctx2d.strokeStyle = "#cbd5e1";
6157
+ ctx2d.lineWidth = 1;
6158
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6159
+ ctx2d.fillStyle = "#64748b";
6160
+ ctx2d.font = "bold 10px sans-serif";
6161
+ ctx2d.textAlign = "center";
6162
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6163
+ }
6164
+ }
6165
+ });
6166
+ }
6167
+ return items;
5323
6168
  },
5324
6169
  download: () => {
5325
6170
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5424,23 +6269,24 @@ var OpenDocumentPlugin = class {
5424
6269
  case "h": {
5425
6270
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5426
6271
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5427
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6272
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5428
6273
  break;
5429
6274
  }
5430
6275
  case "p": {
5431
6276
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5432
6277
  const inner = this.renderSpans(node, styles, images);
5433
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6278
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6279
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5434
6280
  break;
5435
6281
  }
5436
6282
  case "list": {
5437
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6283
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5438
6284
  Array.from(node.children).forEach((c) => walk(c));
5439
6285
  html += "</ul>";
5440
6286
  break;
5441
6287
  }
5442
6288
  case "list-item": {
5443
- html += "<li>";
6289
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5444
6290
  Array.from(node.children).forEach((c) => walk(c));
5445
6291
  html += "</li>";
5446
6292
  break;
@@ -5566,28 +6412,26 @@ var DocPlugin = class {
5566
6412
  getToolbarActions(instance) {
5567
6413
  const totalPages = instance.getPageCount?.() ?? 1;
5568
6414
  const actions = [];
5569
- if (totalPages > 1) {
5570
- actions.push({
5571
- id: "page-nav",
5572
- icon: "",
5573
- label: "Page Navigation",
5574
- type: "page-nav",
5575
- group: "navigation",
5576
- value: instance.getCurrentPage?.() ?? 1,
5577
- max: totalPages,
5578
- execute: (action, page) => {
5579
- const cur = instance.getCurrentPage?.() ?? 1;
5580
- const max = instance.getPageCount?.() ?? 1;
5581
- if (action === "prev") {
5582
- if (cur > 1) instance.goToPage?.(cur - 1);
5583
- } else if (action === "next") {
5584
- if (cur < max) instance.goToPage?.(cur + 1);
5585
- } else if (typeof page === "number") {
5586
- instance.goToPage?.(page);
5587
- }
6415
+ actions.push({
6416
+ id: "page-nav",
6417
+ icon: "",
6418
+ label: "Page Navigation",
6419
+ type: "page-nav",
6420
+ group: "navigation",
6421
+ value: instance.getCurrentPage?.() ?? 1,
6422
+ max: totalPages,
6423
+ execute: (action, page) => {
6424
+ const cur = instance.getCurrentPage?.() ?? 1;
6425
+ const max = instance.getPageCount?.() ?? 1;
6426
+ if (action === "prev") {
6427
+ if (cur > 1) instance.goToPage?.(cur - 1);
6428
+ } else if (action === "next") {
6429
+ if (cur < max) instance.goToPage?.(cur + 1);
6430
+ } else if (typeof page === "number") {
6431
+ instance.goToPage?.(page);
5588
6432
  }
5589
- });
5590
- }
6433
+ }
6434
+ });
5591
6435
  actions.push(
5592
6436
  {
5593
6437
  id: "zoom-out",
@@ -5661,8 +6505,9 @@ var DocPlugin = class {
5661
6505
  container.className = "fp-doc-container";
5662
6506
  container.style.width = "100%";
5663
6507
  container.style.height = "100%";
5664
- container.style.overflow = "auto";
5665
- container.style.padding = "32px 16px";
6508
+ container.style.overflowX = "hidden";
6509
+ container.style.overflowY = "auto";
6510
+ container.style.padding = "16px 8px";
5666
6511
  container.style.backgroundColor = "#f1f5f9";
5667
6512
  container.style.display = "flex";
5668
6513
  container.style.justifyContent = "center";
@@ -5707,16 +6552,15 @@ var DocPlugin = class {
5707
6552
  const pageCard = document.createElement("div");
5708
6553
  pageCard.className = "fp-doc-page-card";
5709
6554
  pageCard.style.width = "816px";
5710
- pageCard.style.height = "1056px";
6555
+ pageCard.style.minHeight = "1056px";
5711
6556
  pageCard.style.backgroundColor = "#ffffff";
5712
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6557
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5713
6558
  pageCard.style.borderRadius = "4px";
5714
- pageCard.style.padding = "96px 72px";
6559
+ pageCard.style.padding = "72px 56px";
5715
6560
  pageCard.style.boxSizing = "border-box";
5716
- pageCard.style.overflow = "hidden";
5717
6561
  pageCard.style.display = i === 0 ? "block" : "none";
5718
6562
  pageCard.style.transformOrigin = "top center";
5719
- pageCard.style.transition = "transform 0.2s ease";
6563
+ pageCard.style.transition = "transform 0.15s ease";
5720
6564
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5721
6565
  pageCard.style.color = "#1e293b";
5722
6566
  if (isFallback && i === 0) {
@@ -5733,34 +6577,15 @@ var DocPlugin = class {
5733
6577
  container.appendChild(pageCard);
5734
6578
  pageCards.push(pageCard);
5735
6579
  }
5736
- let indicator = null;
5737
- if (totalPages > 1) {
5738
- indicator = document.createElement("div");
5739
- indicator.className = "fp-doc-page-indicator";
5740
- indicator.style.position = "fixed";
5741
- indicator.style.bottom = "16px";
5742
- indicator.style.left = "50%";
5743
- indicator.style.transform = "translateX(-50%)";
5744
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5745
- indicator.style.backdropFilter = "blur(8px)";
5746
- indicator.style.color = "#f8fafc";
5747
- indicator.style.fontSize = "12px";
5748
- indicator.style.fontWeight = "600";
5749
- indicator.style.padding = "5px 14px";
5750
- indicator.style.borderRadius = "20px";
5751
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5752
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5753
- indicator.style.zIndex = "10";
5754
- indicator.style.userSelect = "none";
5755
- indicator.style.pointerEvents = "none";
5756
- indicator.style.textAlign = "center";
5757
- container.appendChild(indicator);
5758
- }
5759
6580
  let rotation = 0;
5760
6581
  const applyTransform = () => {
5761
6582
  const activeCard = pageCards[currentPage - 1];
5762
6583
  if (activeCard) {
5763
6584
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6585
+ const baseH = activeCard.offsetHeight || 1056;
6586
+ const scaledH = baseH * scale;
6587
+ const extraH = Math.max(0, scaledH - baseH);
6588
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5764
6589
  }
5765
6590
  };
5766
6591
  const showPage = (pageNum) => {
@@ -5773,35 +6598,39 @@ var DocPlugin = class {
5773
6598
  card.style.display = "none";
5774
6599
  }
5775
6600
  });
5776
- if (indicator) {
5777
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5778
- }
5779
6601
  container.scrollTop = 0;
5780
6602
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5781
6603
  };
5782
6604
  if (totalPages > 1) {
5783
6605
  showPage(1);
5784
6606
  }
5785
- let fitMode = "width";
6607
+ let fitMode = ctx?.options?.fitMode || "width";
6608
+ let isUserZoomed = false;
5786
6609
  const calculateFitScale = (mode = fitMode) => {
5787
6610
  const elW = 816;
5788
6611
  const elH = 1056;
5789
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5790
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6612
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6613
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5791
6614
  const sW = availW / elW;
5792
6615
  const sH = availH / elH;
5793
6616
  if (mode === "page") {
5794
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6617
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5795
6618
  }
5796
- return Math.max(0.65, Math.min(1.05, sW));
6619
+ return Math.max(0.4, Math.min(3, sW));
5797
6620
  };
6621
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6622
+ if (!isUserZoomed) {
6623
+ scale = calculateFitScale(fitMode);
6624
+ applyTransform();
6625
+ }
6626
+ }) : null;
6627
+ resizeObserver?.observe(ctx.container);
5798
6628
  setTimeout(() => {
5799
- fitMode = "width";
5800
- scale = calculateFitScale("width");
6629
+ scale = calculateFitScale(fitMode);
5801
6630
  applyTransform();
5802
6631
  }, 60);
5803
6632
  const cleanup = () => {
5804
- indicator?.remove();
6633
+ resizeObserver?.disconnect();
5805
6634
  container.remove();
5806
6635
  ctx.container.innerHTML = "";
5807
6636
  };
@@ -5812,21 +6641,25 @@ var DocPlugin = class {
5812
6641
  getCurrentPage: () => currentPage,
5813
6642
  goToPage: (page) => showPage(page),
5814
6643
  zoomIn: () => {
6644
+ isUserZoomed = true;
5815
6645
  scale += 0.15;
5816
6646
  applyTransform();
5817
6647
  },
5818
6648
  zoomOut: () => {
6649
+ isUserZoomed = true;
5819
6650
  scale = Math.max(0.2, scale - 0.15);
5820
6651
  applyTransform();
5821
6652
  },
5822
6653
  getZoom: () => scale,
5823
6654
  setZoom: (level) => {
6655
+ isUserZoomed = true;
5824
6656
  scale = level;
5825
6657
  applyTransform();
5826
6658
  },
5827
6659
  fitToPage: () => {
5828
- fitMode = fitMode === "width" ? "page" : "width";
5829
- scale = calculateFitScale(fitMode);
6660
+ isUserZoomed = false;
6661
+ fitMode = "width";
6662
+ scale = calculateFitScale("width");
5830
6663
  rotation = 0;
5831
6664
  applyTransform();
5832
6665
  },
@@ -6203,25 +7036,31 @@ ${chartSvg}
6203
7036
  i++;
6204
7037
  continue;
6205
7038
  }
6206
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7039
+ if (/^\d{1,2}$/.test(line.trim())) {
7040
+ i++;
7041
+ continue;
7042
+ }
7043
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7044
+ 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));
7045
+ if (isTitleLike) {
6207
7046
  if (inList) {
6208
7047
  html += "</ul>";
6209
7048
  inList = false;
6210
7049
  }
6211
- 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>`;
7050
+ 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>`;
6212
7051
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6213
7052
  if (!inList) {
6214
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7053
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6215
7054
  inList = true;
6216
7055
  }
6217
7056
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6218
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
7057
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
6219
7058
  } else {
6220
7059
  if (inList) {
6221
7060
  html += "</ul>";
6222
7061
  inList = false;
6223
7062
  }
6224
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
7063
+ 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>`;
6225
7064
  }
6226
7065
  i++;
6227
7066
  }