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