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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue.js CHANGED
@@ -494,8 +494,7 @@ var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
494
494
  var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
495
495
  var ICON_FAST_FORWARD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
496
496
  var ICON_REWIND = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
497
- var ICON_SPEED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
498
- var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>`;
497
+ var ICON_EXTERNAL_WINDOW = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"></rect><line x1="3" y1="8" x2="21" y2="8"></line><circle cx="6" cy="5.5" r="0.75" fill="currentColor"></circle><circle cx="8.5" cy="5.5" r="0.75" fill="currentColor"></circle><polyline points="14 13 18 13 18 17"></polyline><line x1="11" y1="20" x2="18" y2="13"></line></svg>`;
499
498
  var ICON_MAP = {
500
499
  "zoom-in": ICON_ZOOM_IN,
501
500
  "zoom-out": ICON_ZOOM_OUT,
@@ -522,28 +521,177 @@ var ICON_MAP = {
522
521
  "forward-10": ICON_FAST_FORWARD,
523
522
  "rewind": ICON_REWIND,
524
523
  "replay-10": ICON_REWIND,
525
- "speed": ICON_SPEED,
526
524
  "open-window": ICON_EXTERNAL_WINDOW,
527
- "external-window": ICON_EXTERNAL_WINDOW
525
+ "external-window": ICON_EXTERNAL_WINDOW,
526
+ "openWindow": ICON_EXTERNAL_WINDOW,
527
+ "openSeparateWindow": ICON_EXTERNAL_WINDOW
528
528
  };
529
529
  var ToolbarController = class {
530
530
  el;
531
531
  toolbarEl;
532
532
  actions = [];
533
+ config = {};
533
534
  pageInputEl = null;
534
535
  pageLabelEl = null;
535
- constructor(container) {
536
+ prevPageBtn = null;
537
+ nextPageBtn = null;
538
+ constructor(container, config) {
536
539
  this.el = container;
540
+ if (config) this.config = { ...config };
537
541
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
538
542
  this.el.appendChild(this.toolbarEl);
539
543
  }
544
+ setConfig(config) {
545
+ this.config = { ...config };
546
+ this.render();
547
+ }
548
+ getConfig() {
549
+ return { ...this.config };
550
+ }
551
+ hideAction(actionId) {
552
+ this.config[actionId] = false;
553
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = false;
554
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = false;
555
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
556
+ this.config.fitPage = false;
557
+ this.config.fitToPage = false;
558
+ this.config.fitWidth = false;
559
+ }
560
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = false;
561
+ if (actionId === "fullscreen") this.config.fullscreen = false;
562
+ if (actionId === "download") this.config.download = false;
563
+ if (actionId === "print") this.config.print = false;
564
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
565
+ this.config.openWindow = false;
566
+ this.config.openSeparateWindow = false;
567
+ }
568
+ if (actionId === "copy") this.config.copy = false;
569
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
570
+ this.config.pageNav = false;
571
+ this.config.pagination = false;
572
+ }
573
+ this.render();
574
+ }
575
+ showAction(actionId) {
576
+ this.config[actionId] = true;
577
+ if (actionId === "zoom-in" || actionId === "zoomIn") this.config.zoomIn = true;
578
+ if (actionId === "zoom-out" || actionId === "zoomOut") this.config.zoomOut = true;
579
+ if (actionId === "fit-page" || actionId === "fitPage" || actionId === "fitToPage" || actionId === "fit-width") {
580
+ this.config.fitPage = true;
581
+ this.config.fitToPage = true;
582
+ this.config.fitWidth = true;
583
+ }
584
+ if (actionId === "rotate-cw" || actionId === "rotate" || actionId === "rotateCW") this.config.rotate = true;
585
+ if (actionId === "fullscreen") this.config.fullscreen = true;
586
+ if (actionId === "download") this.config.download = true;
587
+ if (actionId === "print") this.config.print = true;
588
+ if (actionId === "open-window" || actionId === "openWindow" || actionId === "openSeparateWindow") {
589
+ this.config.openWindow = true;
590
+ this.config.openSeparateWindow = true;
591
+ }
592
+ if (actionId === "copy") this.config.copy = true;
593
+ if (actionId === "page-nav" || actionId === "pageNav" || actionId === "pagination") {
594
+ this.config.pageNav = true;
595
+ this.config.pagination = true;
596
+ }
597
+ this.render();
598
+ }
599
+ normalizeActionId(actionId) {
600
+ if (actionId === "zoomIn") return "zoom-in";
601
+ if (actionId === "zoomOut") return "zoom-out";
602
+ if (actionId === "fitPage" || actionId === "fitToPage" || actionId === "fitWidth") return "fit-page";
603
+ if (actionId === "rotateCW") return "rotate-cw";
604
+ if (actionId === "rotateCCW") return "rotate-ccw";
605
+ if (actionId === "openWindow" || actionId === "openSeparateWindow") return "open-window";
606
+ if (actionId === "pageNav") return "page-nav";
607
+ return actionId;
608
+ }
609
+ enableAction(actionId) {
610
+ const norm = this.normalizeActionId(actionId);
611
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
612
+ if (btn) {
613
+ btn.disabled = false;
614
+ btn.classList.remove("disabled");
615
+ }
616
+ }
617
+ disableAction(actionId) {
618
+ const norm = this.normalizeActionId(actionId);
619
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
620
+ if (btn) {
621
+ btn.disabled = true;
622
+ btn.classList.add("disabled");
623
+ }
624
+ }
625
+ setActionActive(actionId, active) {
626
+ const norm = this.normalizeActionId(actionId);
627
+ const btn = this.toolbarEl.querySelector(`button[data-action-id="${actionId}"], button[data-action-id="${norm}"]`);
628
+ if (btn) {
629
+ if (active) {
630
+ btn.classList.add("active");
631
+ } else {
632
+ btn.classList.remove("active");
633
+ }
634
+ }
635
+ }
636
+ isActionEnabled(action) {
637
+ const c = this.config;
638
+ const id = action.id;
639
+ if (c[id] === false) return false;
640
+ if (id === "zoom-in" && (c.zoomIn === false || c["zoom-in"] === false)) return false;
641
+ if (id === "zoom-out" && (c.zoomOut === false || c["zoom-out"] === false)) return false;
642
+ if ((id === "fit-page" || id === "fit-width" || id === "fit-slide") && (c.fitPage === false || c.fitToPage === false || c.fitWidth === false || c["fit-page"] === false || c["fit-width"] === false)) {
643
+ return false;
644
+ }
645
+ if ((id === "rotate-cw" || id === "rotate-ccw" || id === "rotate") && (c.rotate === false || c.rotateCW === false || c.rotateCCW === false || c["rotate-cw"] === false)) {
646
+ return false;
647
+ }
648
+ if (id === "fullscreen" && c.fullscreen === false) return false;
649
+ if (id === "download" && c.download === false) return false;
650
+ if (id === "print" && c.print === false) return false;
651
+ if ((id === "open-window" || id === "external-window") && (c.openWindow === false || c.openSeparateWindow === false || c["open-window"] === false)) {
652
+ return false;
653
+ }
654
+ if (id === "copy" && c.copy === false) return false;
655
+ if (id === "thumbnails" && c.thumbnails === false) return false;
656
+ if (id === "page-nav" && (c.pageNav === false || c.pagination === false || c["page-nav"] === false)) {
657
+ return false;
658
+ }
659
+ if ((id === "prev" || id === "page-prev") && (c.prevPage === false || c.prev === false || c["prev"] === false || c.pageNav === false || c.pagination === false)) {
660
+ return false;
661
+ }
662
+ if ((id === "next" || id === "page-next") && (c.nextPage === false || c.next === false || c["next"] === false || c.pageNav === false || c.pagination === false)) {
663
+ return false;
664
+ }
665
+ if (id === "play" && c.play === false) return false;
666
+ if (id === "pause" && c.pause === false) return false;
667
+ if ((id === "fast-forward" || id === "forward-10") && (c.fastForward === false || c["fast-forward"] === false)) {
668
+ return false;
669
+ }
670
+ if ((id === "rewind" || id === "replay-10") && (c.rewind === false || c["rewind"] === false)) {
671
+ return false;
672
+ }
673
+ if (id === "speed" && c.speed === false) return false;
674
+ return true;
675
+ }
540
676
  setPage(page, max) {
541
677
  if (this.pageInputEl) {
542
678
  this.pageInputEl.value = page.toString();
679
+ if (max !== void 0) {
680
+ this.pageInputEl.max = max.toString();
681
+ }
543
682
  }
544
683
  if (max !== void 0 && this.pageLabelEl) {
545
684
  this.pageLabelEl.textContent = ` / ${max}`;
546
685
  }
686
+ const currentMax = max !== void 0 ? max : parseInt(this.pageInputEl?.max || "1", 10) || 1;
687
+ if (this.prevPageBtn) {
688
+ this.prevPageBtn.disabled = page <= 1;
689
+ this.prevPageBtn.style.opacity = page <= 1 ? "0.4" : "1";
690
+ }
691
+ if (this.nextPageBtn) {
692
+ this.nextPageBtn.disabled = page >= currentMax;
693
+ this.nextPageBtn.style.opacity = page >= currentMax ? "0.4" : "1";
694
+ }
547
695
  }
548
696
  update(actions) {
549
697
  this.actions = actions;
@@ -566,8 +714,9 @@ var ToolbarController = class {
566
714
  view: [],
567
715
  actions: []
568
716
  };
569
- const hasPageNav = this.actions.some((a) => a.type === "page-nav");
570
- const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
717
+ const enabledActions = this.actions.filter((a) => this.isActionEnabled(a));
718
+ const hasPageNav = enabledActions.some((a) => a.type === "page-nav");
719
+ const effectiveActions = hasPageNav ? enabledActions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : enabledActions;
571
720
  for (const action of effectiveActions) {
572
721
  if (groups[action.group]) {
573
722
  groups[action.group].push(action);
@@ -627,6 +776,13 @@ var ToolbarController = class {
627
776
  action.execute("go", val);
628
777
  });
629
778
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
779
+ const curVal = action.value ?? 1;
780
+ prevBtn.disabled = curVal <= 1;
781
+ prevBtn.style.opacity = curVal <= 1 ? "0.4" : "1";
782
+ nextBtn.disabled = curVal >= max;
783
+ nextBtn.style.opacity = curVal >= max ? "0.4" : "1";
784
+ this.prevPageBtn = prevBtn;
785
+ this.nextPageBtn = nextBtn;
630
786
  this.pageInputEl = input;
631
787
  this.pageLabelEl = label;
632
788
  groupEl.appendChild(prevBtn);
@@ -682,25 +838,63 @@ var ToolbarController = class {
682
838
  var ThumbnailPanel = class {
683
839
  el;
684
840
  panelEl;
841
+ listEl;
842
+ headerEl;
685
843
  thumbnails = [];
686
844
  onSelect;
845
+ onToggleCallback;
687
846
  activeIndex = 0;
688
- constructor(container) {
847
+ observer;
848
+ renderedIndices = /* @__PURE__ */ new Set();
849
+ constructor(container, onToggle) {
689
850
  this.el = container;
851
+ this.onToggleCallback = onToggle;
690
852
  this.panelEl = createElement("div", { className: "fp-thumbnail-panel hidden" });
853
+ this.panelEl.style.width = "0px";
854
+ this.panelEl.style.minWidth = "0px";
855
+ this.panelEl.style.opacity = "0";
856
+ this.headerEl = createElement("div", { className: "fp-thumbnail-header" });
857
+ const title = createElement("span", { className: "fp-thumbnail-title" }, "Pages");
858
+ const closeBtn = createElement("button", {
859
+ className: "fp-thumbnail-close-btn",
860
+ title: "Close Thumbnails",
861
+ type: "button"
862
+ });
863
+ closeBtn.innerHTML = ICON_CLOSE;
864
+ closeBtn.addEventListener("click", (e) => {
865
+ e.stopPropagation();
866
+ this.hide();
867
+ });
868
+ this.headerEl.appendChild(title);
869
+ this.headerEl.appendChild(closeBtn);
870
+ this.listEl = createElement("div", { className: "fp-thumbnail-list" });
871
+ this.panelEl.appendChild(this.headerEl);
872
+ this.panelEl.appendChild(this.listEl);
691
873
  this.el.appendChild(this.panelEl);
692
874
  }
875
+ isOpen() {
876
+ return !this.panelEl.classList.contains("hidden") && this.panelEl.style.width !== "0px";
877
+ }
693
878
  update(thumbnails, onSelect) {
694
879
  this.thumbnails = thumbnails;
695
880
  this.onSelect = onSelect;
881
+ this.renderedIndices.clear();
882
+ const titleEl = this.headerEl.querySelector(".fp-thumbnail-title");
883
+ if (titleEl) {
884
+ titleEl.textContent = `Pages (${thumbnails.length})`;
885
+ }
696
886
  this.render();
887
+ if (this.isOpen()) {
888
+ this.renderAllVisible();
889
+ }
697
890
  }
698
891
  setActive(index) {
699
892
  this.activeIndex = index;
700
- const items = this.panelEl.querySelectorAll(".fp-thumbnail-item");
893
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
701
894
  items.forEach((item, i) => {
702
895
  if (i === index) {
703
896
  item.classList.add("active");
897
+ item.scrollIntoView({ block: "nearest", behavior: "smooth" });
704
898
  } else {
705
899
  item.classList.remove("active");
706
900
  }
@@ -708,37 +902,102 @@ var ThumbnailPanel = class {
708
902
  }
709
903
  show() {
710
904
  this.panelEl.classList.remove("hidden");
905
+ this.panelEl.style.width = "200px";
906
+ this.panelEl.style.minWidth = "200px";
907
+ this.panelEl.style.opacity = "1";
908
+ this.panelEl.style.display = "flex";
909
+ this.onToggleCallback?.(true);
910
+ this.renderAllVisible();
711
911
  }
712
912
  hide() {
713
913
  this.panelEl.classList.add("hidden");
914
+ this.panelEl.style.width = "0px";
915
+ this.panelEl.style.minWidth = "0px";
916
+ this.panelEl.style.opacity = "0";
917
+ this.onToggleCallback?.(false);
714
918
  }
715
919
  toggle() {
716
- this.panelEl.classList.toggle("hidden");
920
+ if (this.isOpen()) {
921
+ this.hide();
922
+ } else {
923
+ this.show();
924
+ }
717
925
  }
718
926
  destroy() {
927
+ if (this.observer) {
928
+ this.observer.disconnect();
929
+ this.observer = void 0;
930
+ }
931
+ this.renderedIndices.clear();
719
932
  this.el.innerHTML = "";
720
933
  }
721
- async render() {
722
- this.panelEl.innerHTML = "";
934
+ renderAllVisible() {
935
+ const items = this.listEl.querySelectorAll(".fp-thumbnail-item");
936
+ items.forEach((item) => {
937
+ const idx = parseInt(item.dataset.thumbIndex || "-1", 10);
938
+ if (idx >= 0 && !this.renderedIndices.has(idx)) {
939
+ this.renderThumbnailItem(idx, item);
940
+ }
941
+ });
942
+ }
943
+ renderThumbnailItem(idx, itemEl) {
944
+ if (this.renderedIndices.has(idx)) return;
945
+ this.renderedIndices.add(idx);
946
+ const canvas = itemEl.querySelector("canvas");
947
+ const thumb = this.thumbnails[idx];
948
+ if (canvas && thumb) {
949
+ Promise.resolve(thumb.render(canvas)).catch((err) => {
950
+ console.warn(`[FilePreview] Error rendering thumbnail ${idx}:`, err);
951
+ });
952
+ }
953
+ }
954
+ render() {
955
+ if (this.observer) {
956
+ this.observer.disconnect();
957
+ }
958
+ this.listEl.innerHTML = "";
959
+ const useObserver = typeof IntersectionObserver !== "undefined";
960
+ if (useObserver) {
961
+ this.observer = new IntersectionObserver(
962
+ (entries) => {
963
+ for (const entry of entries) {
964
+ if (entry.isIntersecting) {
965
+ const target = entry.target;
966
+ const idx = parseInt(target.dataset.thumbIndex || "-1", 10);
967
+ if (idx >= 0) {
968
+ this.renderThumbnailItem(idx, target);
969
+ }
970
+ }
971
+ }
972
+ },
973
+ {
974
+ root: this.listEl,
975
+ rootMargin: "120px 0px 120px 0px"
976
+ }
977
+ );
978
+ }
723
979
  for (let i = 0; i < this.thumbnails.length; i++) {
724
980
  const thumb = this.thumbnails[i];
725
- const itemEl = createElement("div", { className: "fp-thumbnail-item" });
726
- if (i === this.activeIndex) {
727
- itemEl.classList.add("active");
728
- }
981
+ const itemEl = createElement("div", {
982
+ className: "fp-thumbnail-item" + (i === this.activeIndex ? " active" : "")
983
+ });
984
+ itemEl.dataset.thumbIndex = i.toString();
729
985
  itemEl.addEventListener("click", () => {
730
986
  this.setActive(i);
731
987
  this.onSelect?.(i);
732
988
  });
733
- const canvas = createElement("canvas", { width: "150", height: "200" });
734
- const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label);
735
- itemEl.appendChild(canvas);
989
+ const canvasWrapper = createElement("div", { className: "fp-thumbnail-canvas-wrapper" });
990
+ const canvas = createElement("canvas", { width: "130", height: "170" });
991
+ canvasWrapper.appendChild(canvas);
992
+ const label = createElement("span", { className: "fp-thumbnail-label" }, thumb.label || `Page ${i + 1}`);
993
+ itemEl.appendChild(canvasWrapper);
736
994
  itemEl.appendChild(label);
737
- this.panelEl.appendChild(itemEl);
738
- try {
739
- await thumb.render(canvas);
740
- } catch (err) {
741
- console.error(`[FilePreview] Error rendering thumbnail ${i}:`, err);
995
+ this.listEl.appendChild(itemEl);
996
+ if (this.observer) {
997
+ this.observer.observe(itemEl);
998
+ }
999
+ if (i < 4) {
1000
+ this.renderThumbnailItem(i, itemEl);
742
1001
  }
743
1002
  }
744
1003
  }
@@ -759,6 +1018,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
759
1018
  currentOptions = {};
760
1019
  resizeObserver = null;
761
1020
  fullscreenHandler = null;
1021
+ titleBarEl = null;
762
1022
  /**
763
1023
  * Register a preview plugin.
764
1024
  */
@@ -795,6 +1055,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
795
1055
  }
796
1056
  this.currentBuffer = buffer.slice(0);
797
1057
  this.currentMetadata = metadata;
1058
+ this.updateTitleBar(options, metadata);
798
1059
  if (signal.aborted) throw new DOMException("Aborted", "AbortError");
799
1060
  const fileInfo = { metadata, buffer };
800
1061
  const matchedPlugin = await this.findPlugin(fileInfo);
@@ -818,15 +1079,21 @@ var FilePreviewViewer = class _FilePreviewViewer {
818
1079
  if (event === "page-change" && payload && typeof payload.page === "number") {
819
1080
  const total = payload.total ?? payload.totalPages;
820
1081
  this.toolbar?.setPage(payload.page, total);
1082
+ this.thumbnailPanel?.setActive(payload.page - 1);
821
1083
  }
822
1084
  this.eventEmitter.emit(event, payload);
823
- }
1085
+ },
1086
+ toggleThumbnails: () => this.toggleThumbnails()
824
1087
  });
825
1088
  this.activeInstance = instance;
826
1089
  instance.openInSeparateWindow = () => this.openInSeparateWindow();
1090
+ instance.toggleThumbnails = () => this.toggleThumbnails();
827
1091
  this.hideLoading();
828
1092
  this.eventEmitter.emit("loaded", { metadata, plugin: matchedPlugin.id });
829
- if (options.showToolbar !== false && this.toolbar) {
1093
+ const isToolbarVisible = options.showToolbar !== false && options.toolbar !== false;
1094
+ if (isToolbarVisible && this.toolbar) {
1095
+ const toolbarConfig = this.extractToolbarConfig(options);
1096
+ this.toolbar.setConfig(toolbarConfig);
830
1097
  const actions = matchedPlugin.getToolbarActions(instance);
831
1098
  const hasFullscreen = actions.some((a) => a.id === "fullscreen");
832
1099
  if (!hasFullscreen && this.wrapperEl) {
@@ -876,6 +1143,12 @@ var FilePreviewViewer = class _FilePreviewViewer {
876
1143
  }
877
1144
  });
878
1145
  }
1146
+ const thumbAction = actions.find((a) => a.id === "thumbnails");
1147
+ if (thumbAction) {
1148
+ thumbAction.execute = () => {
1149
+ this.toggleThumbnails();
1150
+ };
1151
+ }
879
1152
  this.toolbar.update(actions);
880
1153
  this.toolbar.show();
881
1154
  }
@@ -960,7 +1233,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
960
1233
  if (targetUrl) {
961
1234
  const newWin2 = window.open(targetUrl, "_blank");
962
1235
  if (!newWin2) {
963
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1236
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
964
1237
  return null;
965
1238
  }
966
1239
  return newWin2;
@@ -968,7 +1241,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
968
1241
  const title = (this.currentMetadata?.name || "Document Preview") + " - Full Preview";
969
1242
  const newWin = window.open("", "_blank");
970
1243
  if (!newWin) {
971
- alert("Popup blocker prevented opening the preview in a separate window. Please allow popups for this site.");
1244
+ console.warn("[FilePreviewViewer] Popup blocker prevented opening the preview in a separate window.");
972
1245
  return null;
973
1246
  }
974
1247
  newWin.document.title = title;
@@ -1045,6 +1318,7 @@ var FilePreviewViewer = class _FilePreviewViewer {
1045
1318
  this.currentBuffer = null;
1046
1319
  this.currentMetadata = null;
1047
1320
  this.currentContainer = null;
1321
+ this.titleBarEl = null;
1048
1322
  }
1049
1323
  /**
1050
1324
  * Get the currently active preview instance.
@@ -1052,7 +1326,218 @@ var FilePreviewViewer = class _FilePreviewViewer {
1052
1326
  getInstance() {
1053
1327
  return this.activeInstance;
1054
1328
  }
1329
+ // --- Toolbar Configuration & Feature Toggles ---
1330
+ /**
1331
+ * Configure toolbar features dynamically through options or methods.
1332
+ * e.g. viewer.setToolbarConfig({ zoomIn: false, print: false })
1333
+ */
1334
+ setToolbarConfig(config) {
1335
+ this.toolbar?.setConfig(config);
1336
+ }
1337
+ /**
1338
+ * Get current toolbar configuration.
1339
+ */
1340
+ getToolbarConfig() {
1341
+ return this.toolbar?.getConfig() ?? {};
1342
+ }
1343
+ /**
1344
+ * Hide a specific toolbar action by ID or alias (e.g. 'zoom-in', 'print', 'download').
1345
+ */
1346
+ hideToolbarAction(actionId) {
1347
+ this.toolbar?.hideAction(actionId);
1348
+ }
1349
+ /**
1350
+ * Show a specific toolbar action by ID or alias.
1351
+ */
1352
+ showToolbarAction(actionId) {
1353
+ this.toolbar?.showAction(actionId);
1354
+ }
1355
+ /**
1356
+ * Enable a specific toolbar action button.
1357
+ */
1358
+ enableToolbarAction(actionId) {
1359
+ this.toolbar?.enableAction(actionId);
1360
+ }
1361
+ /**
1362
+ * Disable a specific toolbar action button.
1363
+ */
1364
+ disableToolbarAction(actionId) {
1365
+ this.toolbar?.disableAction(actionId);
1366
+ }
1367
+ // --- Programmatic Toolbar Action Methods ---
1368
+ /**
1369
+ * Fit document to page so it fills the frame width with minimal margins.
1370
+ */
1371
+ fitToPage() {
1372
+ this.activeInstance?.fitToPage?.();
1373
+ }
1374
+ /**
1375
+ * Zoom in.
1376
+ */
1377
+ zoomIn() {
1378
+ this.activeInstance?.zoomIn?.();
1379
+ }
1380
+ /**
1381
+ * Zoom out.
1382
+ */
1383
+ zoomOut() {
1384
+ this.activeInstance?.zoomOut?.();
1385
+ }
1386
+ /**
1387
+ * Set specific zoom level.
1388
+ */
1389
+ setZoom(level) {
1390
+ this.activeInstance?.setZoom?.(level);
1391
+ }
1392
+ /**
1393
+ * Get current zoom level.
1394
+ */
1395
+ getZoom() {
1396
+ return this.activeInstance?.getZoom?.() ?? 1;
1397
+ }
1398
+ /**
1399
+ * Rotate 90 degrees clockwise.
1400
+ */
1401
+ rotateCW() {
1402
+ this.activeInstance?.rotateCW?.();
1403
+ }
1404
+ /**
1405
+ * Rotate 90 degrees counter-clockwise.
1406
+ */
1407
+ rotateCCW() {
1408
+ this.activeInstance?.rotateCCW?.();
1409
+ }
1410
+ /**
1411
+ * Navigate to a specific page number.
1412
+ */
1413
+ goToPage(page) {
1414
+ this.activeInstance?.goToPage?.(page);
1415
+ this.toolbar?.setPage(page);
1416
+ }
1417
+ /**
1418
+ * Navigate to next page.
1419
+ */
1420
+ nextPage() {
1421
+ const cur = this.getCurrentPage();
1422
+ const max = this.getPageCount();
1423
+ if (cur < max) {
1424
+ this.goToPage(cur + 1);
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Navigate to previous page.
1429
+ */
1430
+ prevPage() {
1431
+ const cur = this.getCurrentPage();
1432
+ if (cur > 1) {
1433
+ this.goToPage(cur - 1);
1434
+ }
1435
+ }
1436
+ /**
1437
+ * Get total page count.
1438
+ */
1439
+ getPageCount() {
1440
+ return this.activeInstance?.getPageCount?.() ?? 1;
1441
+ }
1442
+ /**
1443
+ * Get current page number.
1444
+ */
1445
+ getCurrentPage() {
1446
+ return this.activeInstance?.getCurrentPage?.() ?? 1;
1447
+ }
1448
+ /**
1449
+ * Download the current document.
1450
+ */
1451
+ download() {
1452
+ this.activeInstance?.download?.();
1453
+ }
1454
+ /**
1455
+ * Print the current document.
1456
+ */
1457
+ print() {
1458
+ this.activeInstance?.print?.();
1459
+ }
1460
+ /**
1461
+ * Toggle fullscreen mode.
1462
+ */
1463
+ toggleFullscreen() {
1464
+ try {
1465
+ if (!document.fullscreenElement) {
1466
+ this.wrapperEl?.requestFullscreen?.();
1467
+ this.wrapperEl?.classList.add("fp-fullscreen-active");
1468
+ } else {
1469
+ document.exitFullscreen?.();
1470
+ this.wrapperEl?.classList.remove("fp-fullscreen-active");
1471
+ }
1472
+ } catch {
1473
+ this.wrapperEl?.classList.toggle("fp-fullscreen-active");
1474
+ }
1475
+ setTimeout(() => {
1476
+ this.activeInstance?.fitToPage?.();
1477
+ }, 120);
1478
+ }
1479
+ /**
1480
+ * Toggle thumbnails sidebar panel.
1481
+ */
1482
+ toggleThumbnails() {
1483
+ if (!this.thumbnailPanel) return;
1484
+ this.thumbnailPanel.toggle();
1485
+ const isOpen = this.thumbnailPanel.isOpen();
1486
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1487
+ setTimeout(() => {
1488
+ this.activeInstance?.fitToPage?.();
1489
+ }, 260);
1490
+ }
1491
+ /**
1492
+ * Set whether to display the file name / title bar above the toolbar.
1493
+ */
1494
+ setShowFileName(show) {
1495
+ if (this.currentOptions) {
1496
+ this.currentOptions.showFileName = show;
1497
+ }
1498
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1499
+ }
1500
+ /**
1501
+ * Set or override the displayed file name in the preview panel.
1502
+ */
1503
+ setFileName(name) {
1504
+ if (this.currentOptions) {
1505
+ this.currentOptions.fileName = name;
1506
+ }
1507
+ this.updateTitleBar(this.currentOptions, this.currentMetadata);
1508
+ }
1055
1509
  // --- Private methods ---
1510
+ updateTitleBar(options, metadata) {
1511
+ if (!this.titleBarEl) return;
1512
+ const show = options?.showFileName !== false;
1513
+ if (!show) {
1514
+ this.titleBarEl.style.display = "none";
1515
+ return;
1516
+ }
1517
+ const name = options?.fileName || metadata?.name || this.currentMetadata?.name || "";
1518
+ if (!name) {
1519
+ this.titleBarEl.style.display = "none";
1520
+ return;
1521
+ }
1522
+ this.titleBarEl.style.display = "flex";
1523
+ const ext = metadata?.extension || (name.includes(".") ? "." + name.split(".").pop() : "");
1524
+ const badge = ext ? ext.replace(".", "").toUpperCase() : "";
1525
+ this.titleBarEl.innerHTML = `
1526
+ <div class="fp-titlebar-info">
1527
+ <span class="fp-titlebar-icon">\u{1F4C4}</span>
1528
+ <span class="fp-titlebar-name" title="${name}">${name}</span>
1529
+ ${badge ? `<span class="fp-titlebar-badge">${badge}</span>` : ""}
1530
+ </div>
1531
+ `;
1532
+ }
1533
+ extractToolbarConfig(options) {
1534
+ if (!options) return {};
1535
+ const nested = typeof options.toolbar === "object" ? options.toolbar : {};
1536
+ return {
1537
+ ...options,
1538
+ ...nested
1539
+ };
1540
+ }
1056
1541
  abort() {
1057
1542
  if (this.abortController) {
1058
1543
  this.abortController.abort();
@@ -1095,17 +1580,29 @@ var FilePreviewViewer = class _FilePreviewViewer {
1095
1580
  bodyEl.className = "fp-body";
1096
1581
  bodyEl.appendChild(thumbnailEl);
1097
1582
  bodyEl.appendChild(this.contentEl);
1583
+ const titleBarEl = document.createElement("div");
1584
+ titleBarEl.className = "fp-titlebar";
1585
+ this.titleBarEl = titleBarEl;
1586
+ this.updateTitleBar(options, this.currentMetadata);
1098
1587
  if (toolbarPos === "top") {
1588
+ this.wrapperEl.appendChild(titleBarEl);
1099
1589
  this.wrapperEl.appendChild(toolbarEl);
1100
1590
  this.wrapperEl.appendChild(bodyEl);
1101
1591
  } else {
1592
+ this.wrapperEl.appendChild(titleBarEl);
1102
1593
  this.wrapperEl.appendChild(bodyEl);
1103
1594
  this.wrapperEl.appendChild(toolbarEl);
1104
1595
  }
1105
1596
  container.innerHTML = "";
1106
1597
  container.appendChild(this.wrapperEl);
1107
- this.toolbar = new ToolbarController(toolbarEl);
1108
- this.thumbnailPanel = new ThumbnailPanel(thumbnailEl);
1598
+ const initialToolbarConfig = this.extractToolbarConfig(options);
1599
+ this.toolbar = new ToolbarController(toolbarEl, initialToolbarConfig);
1600
+ this.thumbnailPanel = new ThumbnailPanel(thumbnailEl, (isOpen) => {
1601
+ this.toolbar?.setActionActive("thumbnails", isOpen);
1602
+ setTimeout(() => {
1603
+ this.activeInstance?.fitToPage?.();
1604
+ }, 260);
1605
+ });
1109
1606
  this.setupKeyboardShortcuts();
1110
1607
  this.setupDragAndDrop(container, options);
1111
1608
  this.setupResizeAndFullscreenListeners();
@@ -1495,6 +1992,14 @@ var PdfPlugin = class {
1495
1992
  group: "zoom",
1496
1993
  execute: () => instance.fitToPage?.()
1497
1994
  },
1995
+ {
1996
+ id: "fit-width",
1997
+ icon: "fit-width",
1998
+ label: "Fit to Width",
1999
+ type: "button",
2000
+ group: "zoom",
2001
+ execute: () => instance.fitToWidth?.()
2002
+ },
1498
2003
  {
1499
2004
  id: "rotate-cw",
1500
2005
  icon: "rotate-cw",
@@ -1503,6 +2008,14 @@ var PdfPlugin = class {
1503
2008
  group: "view",
1504
2009
  execute: () => instance.rotateCW?.()
1505
2010
  },
2011
+ {
2012
+ id: "rotate-ccw",
2013
+ icon: "rotate-ccw",
2014
+ label: "Rotate Counter-Clockwise",
2015
+ type: "button",
2016
+ group: "view",
2017
+ execute: () => instance.rotateCCW?.()
2018
+ },
1506
2019
  {
1507
2020
  id: "download",
1508
2021
  icon: "download",
@@ -1534,12 +2047,13 @@ var PdfPlugin = class {
1534
2047
  container.className = "fp-pdf-container";
1535
2048
  container.style.width = "100%";
1536
2049
  container.style.height = "100%";
1537
- container.style.overflow = "auto";
2050
+ container.style.overflowX = "hidden";
2051
+ container.style.overflowY = "auto";
1538
2052
  container.style.display = "flex";
1539
2053
  container.style.flexDirection = "column";
1540
2054
  container.style.alignItems = "center";
1541
2055
  container.style.justifyContent = "flex-start";
1542
- container.style.padding = "20px 16px";
2056
+ container.style.padding = "16px 8px";
1543
2057
  container.style.backgroundColor = "#0f172a";
1544
2058
  container.style.boxSizing = "border-box";
1545
2059
  container.style.position = "relative";
@@ -1550,30 +2064,12 @@ var PdfPlugin = class {
1550
2064
  pageCard.style.borderRadius = "4px";
1551
2065
  pageCard.style.overflow = "hidden";
1552
2066
  pageCard.style.lineHeight = "0";
1553
- pageCard.style.transition = "transform 0.15s ease";
1554
2067
  pageCard.style.position = "relative";
1555
2068
  pageCard.style.flexShrink = "0";
2069
+ pageCard.style.transition = "box-shadow 0.2s ease";
1556
2070
  let canvas = document.createElement("canvas");
1557
2071
  pageCard.appendChild(canvas);
1558
2072
  container.appendChild(pageCard);
1559
- const indicator = document.createElement("div");
1560
- indicator.className = "fp-pdf-page-indicator";
1561
- indicator.style.position = "sticky";
1562
- indicator.style.bottom = "16px";
1563
- indicator.style.marginTop = "16px";
1564
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1565
- indicator.style.backdropFilter = "blur(8px)";
1566
- indicator.style.color = "#f8fafc";
1567
- indicator.style.fontSize = "12px";
1568
- indicator.style.fontWeight = "600";
1569
- indicator.style.padding = "5px 14px";
1570
- indicator.style.borderRadius = "20px";
1571
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1572
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1573
- indicator.style.zIndex = "10";
1574
- indicator.style.userSelect = "none";
1575
- indicator.style.pointerEvents = "none";
1576
- container.appendChild(indicator);
1577
2073
  ctx.container.appendChild(container);
1578
2074
  const standardFontsUrl = typeof window !== "undefined" && window.__PDF_STANDARD_FONTS_URL__ || "./standard_fonts/";
1579
2075
  const cmapsUrl = typeof window !== "undefined" && window.__PDF_CMAPS_URL__ || "./cmaps/";
@@ -1589,8 +2085,9 @@ var PdfPlugin = class {
1589
2085
  let currentPage = 1;
1590
2086
  let zoomScale = 1;
1591
2087
  let rotation = 0;
1592
- let fitMode = "page";
2088
+ let fitMode = ctx?.options?.fitMode || "width";
1593
2089
  let currentRenderTask = null;
2090
+ let textLayerDiv = null;
1594
2091
  const renderPage = async (pageNum) => {
1595
2092
  if (currentRenderTask) {
1596
2093
  try {
@@ -1602,30 +2099,37 @@ var PdfPlugin = class {
1602
2099
  const newCanvas = document.createElement("canvas");
1603
2100
  pageCard.replaceChild(newCanvas, canvas);
1604
2101
  canvas = newCanvas;
2102
+ if (textLayerDiv) {
2103
+ textLayerDiv.remove();
2104
+ textLayerDiv = null;
2105
+ }
1605
2106
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
1606
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1607
2107
  ctx.emit("page-change", { page: currentPage, total: totalPages });
1608
2108
  const page = await pdfDoc.getPage(currentPage);
1609
2109
  const containerWidth = container.clientWidth || 900;
1610
2110
  const containerHeight = container.clientHeight || 700;
1611
2111
  const unscaledVp = page.getViewport({ scale: 1, rotation });
1612
- const availWidth = Math.max(320, containerWidth - 48);
1613
- const availHeight = Math.max(550, containerHeight - 88);
2112
+ const availWidth = Math.max(280, containerWidth - 36);
2113
+ const availHeight = Math.max(280, containerHeight - 32);
1614
2114
  const scaleW = availWidth / unscaledVp.width;
1615
2115
  const scaleH = availHeight / unscaledVp.height;
1616
2116
  let fitScale;
1617
2117
  if (fitMode === "page") {
1618
- fitScale = Math.max(0.4, Math.min(scaleW, scaleH));
2118
+ fitScale = Math.max(0.2, Math.min(4, Math.min(scaleW, scaleH)));
1619
2119
  } else {
1620
- fitScale = Math.max(0.65, Math.min(1.25, scaleW));
2120
+ fitScale = Math.max(0.2, Math.min(4, scaleW));
1621
2121
  }
1622
2122
  const effectiveScale = (fitScale > 0 ? fitScale : 1) * zoomScale;
1623
2123
  const pixelRatio = window.devicePixelRatio || 1;
1624
2124
  const viewport = page.getViewport({ scale: effectiveScale, rotation });
2125
+ const displayWidth = Math.floor(viewport.width);
2126
+ const displayHeight = Math.floor(viewport.height);
1625
2127
  canvas.width = Math.floor(viewport.width * pixelRatio);
1626
2128
  canvas.height = Math.floor(viewport.height * pixelRatio);
1627
- canvas.style.width = `${Math.floor(viewport.width)}px`;
1628
- canvas.style.height = `${Math.floor(viewport.height)}px`;
2129
+ canvas.style.width = `${displayWidth}px`;
2130
+ canvas.style.height = `${displayHeight}px`;
2131
+ pageCard.style.width = `${displayWidth}px`;
2132
+ pageCard.style.height = `${displayHeight}px`;
1629
2133
  const canvasCtx = canvas.getContext("2d");
1630
2134
  if (!canvasCtx) return;
1631
2135
  canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
@@ -1639,9 +2143,31 @@ var PdfPlugin = class {
1639
2143
  if (err?.name !== "RenderingCancelledException") {
1640
2144
  console.warn("[PdfPlugin] Page render warning:", err);
1641
2145
  }
2146
+ return;
1642
2147
  } finally {
1643
2148
  currentRenderTask = null;
1644
2149
  }
2150
+ try {
2151
+ textLayerDiv = document.createElement("div");
2152
+ textLayerDiv.className = "textLayer";
2153
+ textLayerDiv.style.width = `${displayWidth}px`;
2154
+ textLayerDiv.style.height = `${displayHeight}px`;
2155
+ textLayerDiv.style.position = "absolute";
2156
+ textLayerDiv.style.top = "0";
2157
+ textLayerDiv.style.left = "0";
2158
+ textLayerDiv.style.lineHeight = "1";
2159
+ pageCard.appendChild(textLayerDiv);
2160
+ if (pdfjsLib.TextLayer) {
2161
+ const textLayer = new pdfjsLib.TextLayer({
2162
+ textContentSource: page.streamTextContent(),
2163
+ container: textLayerDiv,
2164
+ viewport
2165
+ });
2166
+ await textLayer.render();
2167
+ }
2168
+ } catch (err) {
2169
+ console.debug("[PdfPlugin] TextLayer notice:", err);
2170
+ }
1645
2171
  };
1646
2172
  renderPage(1);
1647
2173
  let resizeTimer = null;
@@ -1651,11 +2177,48 @@ var PdfPlugin = class {
1651
2177
  if (Math.abs(zoomScale - 1) < 0.05) {
1652
2178
  renderPage(currentPage);
1653
2179
  }
1654
- }, 150);
2180
+ }, 120);
1655
2181
  });
1656
2182
  resizeObserver.observe(container);
2183
+ const onWheel = (e) => {
2184
+ if (e.ctrlKey || e.metaKey) {
2185
+ e.preventDefault();
2186
+ const delta = e.deltaY > 0 ? -0.15 : 0.15;
2187
+ zoomScale = Math.max(0.25, Math.min(4, Math.round((zoomScale + delta) * 100) / 100));
2188
+ renderPage(currentPage);
2189
+ }
2190
+ };
2191
+ container.addEventListener("wheel", onWheel, { passive: false });
2192
+ const onKeyDown = (e) => {
2193
+ const tag = e.target?.tagName?.toLowerCase();
2194
+ if (tag === "input" || tag === "textarea" || tag === "select") return;
2195
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
2196
+ if (currentPage < totalPages) {
2197
+ e.preventDefault();
2198
+ container.scrollTop = 0;
2199
+ renderPage(currentPage + 1);
2200
+ }
2201
+ } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
2202
+ if (currentPage > 1) {
2203
+ e.preventDefault();
2204
+ container.scrollTop = 0;
2205
+ renderPage(currentPage - 1);
2206
+ }
2207
+ } else if (e.key === "Home") {
2208
+ e.preventDefault();
2209
+ container.scrollTop = 0;
2210
+ renderPage(1);
2211
+ } else if (e.key === "End") {
2212
+ e.preventDefault();
2213
+ container.scrollTop = 0;
2214
+ renderPage(totalPages);
2215
+ }
2216
+ };
2217
+ window.addEventListener("keydown", onKeyDown);
1657
2218
  const cleanup = () => {
1658
2219
  resizeObserver.disconnect();
2220
+ window.removeEventListener("keydown", onKeyDown);
2221
+ container.removeEventListener("wheel", onWheel);
1659
2222
  if (resizeTimer) clearTimeout(resizeTimer);
1660
2223
  if (currentRenderTask) {
1661
2224
  try {
@@ -1674,22 +2237,26 @@ var PdfPlugin = class {
1674
2237
  const instance = {
1675
2238
  destroy: cleanup,
1676
2239
  zoomIn: () => {
1677
- zoomScale = Math.min(3.5, zoomScale + 0.2);
2240
+ zoomScale = Math.min(4, Math.round((zoomScale + 0.2) * 10) / 10);
1678
2241
  renderPage(currentPage);
1679
2242
  },
1680
2243
  zoomOut: () => {
1681
- zoomScale = Math.max(0.3, zoomScale - 0.2);
2244
+ zoomScale = Math.max(0.25, Math.round((zoomScale - 0.2) * 10) / 10);
1682
2245
  renderPage(currentPage);
1683
2246
  },
1684
2247
  getZoom: () => zoomScale,
1685
2248
  setZoom: (level) => {
1686
- zoomScale = Math.max(0.3, Math.min(3.5, level));
2249
+ zoomScale = Math.max(0.25, Math.min(4, level));
1687
2250
  renderPage(currentPage);
1688
2251
  },
1689
2252
  fitToPage: () => {
1690
- fitMode = fitMode === "page" ? "width" : "page";
2253
+ fitMode = "page";
2254
+ zoomScale = 1;
2255
+ renderPage(currentPage);
2256
+ },
2257
+ fitToWidth: () => {
2258
+ fitMode = "width";
1691
2259
  zoomScale = 1;
1692
- rotation = 0;
1693
2260
  renderPage(currentPage);
1694
2261
  },
1695
2262
  rotateCW: () => {
@@ -1707,6 +2274,9 @@ var PdfPlugin = class {
1707
2274
  container.scrollTop = 0;
1708
2275
  renderPage(page);
1709
2276
  },
2277
+ toggleThumbnails: () => {
2278
+ ctx?.toggleThumbnails?.();
2279
+ },
1710
2280
  download: () => {
1711
2281
  const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1712
2282
  const url = URL.createObjectURL(blob);
@@ -1740,7 +2310,7 @@ var PdfPlugin = class {
1740
2310
  },
1741
2311
  getThumbnails: async () => {
1742
2312
  const thumbnails = [];
1743
- const count = Math.min(totalPages, 50);
2313
+ const count = totalPages;
1744
2314
  for (let i = 1; i <= count; i++) {
1745
2315
  thumbnails.push({
1746
2316
  index: i,
@@ -1748,9 +2318,10 @@ var PdfPlugin = class {
1748
2318
  render: async (thumbCanvas) => {
1749
2319
  try {
1750
2320
  const p = await pdfDoc.getPage(i);
1751
- const baseVp = p.getViewport({ scale: 1 });
1752
- const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1753
- const thumbVp = p.getViewport({ scale: thumbScale });
2321
+ const baseVp = p.getViewport({ scale: 1, rotation });
2322
+ const targetW = thumbCanvas.width || 130;
2323
+ const thumbScale = targetW / baseVp.width;
2324
+ const thumbVp = p.getViewport({ scale: thumbScale, rotation });
1754
2325
  thumbCanvas.height = Math.floor(thumbVp.height);
1755
2326
  const tCtx = thumbCanvas.getContext("2d");
1756
2327
  if (tCtx) {
@@ -2094,28 +2665,26 @@ var DocxPlugin = class {
2094
2665
  getToolbarActions(instance) {
2095
2666
  const totalPages = instance.getPageCount?.() ?? 1;
2096
2667
  const actions = [];
2097
- if (totalPages > 1) {
2098
- actions.push({
2099
- id: "page-nav",
2100
- icon: "",
2101
- label: "Page Navigation",
2102
- type: "page-nav",
2103
- group: "navigation",
2104
- value: instance.getCurrentPage?.() ?? 1,
2105
- max: totalPages,
2106
- execute: (action, page) => {
2107
- const cur = instance.getCurrentPage?.() ?? 1;
2108
- const max = instance.getPageCount?.() ?? 1;
2109
- if (action === "prev") {
2110
- if (cur > 1) instance.goToPage?.(cur - 1);
2111
- } else if (action === "next") {
2112
- if (cur < max) instance.goToPage?.(cur + 1);
2113
- } else if (typeof page === "number") {
2114
- instance.goToPage?.(page);
2115
- }
2668
+ actions.push({
2669
+ id: "page-nav",
2670
+ icon: "",
2671
+ label: "Page Navigation",
2672
+ type: "page-nav",
2673
+ group: "navigation",
2674
+ value: instance.getCurrentPage?.() ?? 1,
2675
+ max: totalPages,
2676
+ execute: (action, page) => {
2677
+ const cur = instance.getCurrentPage?.() ?? 1;
2678
+ const max = instance.getPageCount?.() ?? 1;
2679
+ if (action === "prev") {
2680
+ if (cur > 1) instance.goToPage?.(cur - 1);
2681
+ } else if (action === "next") {
2682
+ if (cur < max) instance.goToPage?.(cur + 1);
2683
+ } else if (typeof page === "number") {
2684
+ instance.goToPage?.(page);
2116
2685
  }
2117
- });
2118
- }
2686
+ }
2687
+ });
2119
2688
  actions.push(
2120
2689
  {
2121
2690
  id: "zoom-out",
@@ -2181,11 +2750,17 @@ var DocxPlugin = class {
2181
2750
  wrapper.className = "fp-docx-wrapper";
2182
2751
  wrapper.style.transformOrigin = "top center";
2183
2752
  wrapper.style.transition = "transform 0.2s ease";
2184
- wrapper.style.padding = "24px 16px";
2185
- wrapper.style.maxWidth = "950px";
2753
+ wrapper.style.padding = "0";
2754
+ wrapper.style.maxWidth = "none";
2755
+ wrapper.style.width = "816px";
2186
2756
  wrapper.style.margin = "0 auto";
2757
+ wrapper.style.display = "flex";
2758
+ wrapper.style.flexDirection = "column";
2759
+ wrapper.style.alignItems = "center";
2187
2760
  wrapper.style.boxSizing = "border-box";
2188
- ctx.container.style.overflow = "auto";
2761
+ ctx.container.style.overflowX = "hidden";
2762
+ ctx.container.style.overflowY = "auto";
2763
+ ctx.container.style.padding = "16px 8px";
2189
2764
  ctx.container.style.backgroundColor = "#f1f5f9";
2190
2765
  ctx.container.appendChild(wrapper);
2191
2766
  const styleOverride = document.createElement("style");
@@ -2359,39 +2934,13 @@ var DocxPlugin = class {
2359
2934
  const pageElements = sections.length > 0 ? sections : cards;
2360
2935
  const totalPages = Math.max(1, pageElements.length);
2361
2936
  let currentPage = 1;
2362
- let indicator = null;
2363
- if (totalPages > 1) {
2364
- indicator = document.createElement("div");
2365
- indicator.className = "fp-docx-page-indicator";
2366
- indicator.style.position = "sticky";
2367
- indicator.style.bottom = "16px";
2368
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2369
- indicator.style.backdropFilter = "blur(8px)";
2370
- indicator.style.color = "#f8fafc";
2371
- indicator.style.fontSize = "12px";
2372
- indicator.style.fontWeight = "600";
2373
- indicator.style.padding = "5px 14px";
2374
- indicator.style.borderRadius = "20px";
2375
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2376
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2377
- indicator.style.zIndex = "10";
2378
- indicator.style.userSelect = "none";
2379
- indicator.style.pointerEvents = "none";
2380
- indicator.style.textAlign = "center";
2381
- indicator.style.width = "fit-content";
2382
- indicator.style.margin = "16px auto 0";
2383
- ctx.container.appendChild(indicator);
2384
- }
2385
2937
  const showPage = (pageNum) => {
2386
2938
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
2387
2939
  if (pageElements.length > 1) {
2388
2940
  pageElements.forEach((sec, idx) => {
2389
- sec.style.display = idx + 1 === currentPage ? "block" : "none";
2941
+ sec.style.display = idx + 1 === currentPage ? "" : "none";
2390
2942
  });
2391
2943
  }
2392
- if (indicator) {
2393
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2394
- }
2395
2944
  ctx.container.scrollTop = 0;
2396
2945
  ctx.emit("page-change", { page: currentPage, total: totalPages });
2397
2946
  };
@@ -2400,31 +2949,46 @@ var DocxPlugin = class {
2400
2949
  }
2401
2950
  scale = 1;
2402
2951
  let rotation = 0;
2403
- let fitMode = "width";
2404
- const calculateFitScale = (mode = fitMode) => {
2405
- const activeEl = pageElements[currentPage - 1] || wrapper.firstElementChild || wrapper;
2952
+ let fitMode = "page";
2953
+ let isUserZoomed = false;
2954
+ const calculateFitScale = (_mode = fitMode) => {
2955
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2406
2956
  const elW = activeEl.offsetWidth || 816;
2407
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
2408
- const availW = Math.max(280, ctx.container.clientWidth - 48);
2409
- const availH = Math.max(280, ctx.container.clientHeight - 80);
2957
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
2410
2958
  const sW = availW / elW;
2411
- const sH = availH / singlePageH;
2412
- if (mode === "page") {
2413
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
2414
- }
2415
- return Math.max(0.65, Math.min(1.05, sW));
2959
+ return Math.max(0.35, Math.min(3, sW));
2416
2960
  };
2417
2961
  const applyTransform = () => {
2418
2962
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
2419
2963
  wrapper.style.transformOrigin = "top center";
2964
+ const activeEl = pageElements[currentPage - 1] || wrapper.querySelector("section.docx") || wrapper;
2965
+ const elH = activeEl.offsetHeight || 1056;
2966
+ if (scale > 1) {
2967
+ wrapper.style.marginBottom = `${Math.round(elH * scale - elH + 32)}px`;
2968
+ } else {
2969
+ wrapper.style.marginBottom = "32px";
2970
+ }
2420
2971
  };
2972
+ let resizeObserver = null;
2973
+ if (typeof ResizeObserver !== "undefined") {
2974
+ resizeObserver = new ResizeObserver(() => {
2975
+ if (!isUserZoomed) {
2976
+ const newFit = calculateFitScale("page");
2977
+ if (Math.abs(scale - newFit) > 0.015) {
2978
+ scale = newFit;
2979
+ applyTransform();
2980
+ }
2981
+ }
2982
+ });
2983
+ resizeObserver.observe(ctx.container);
2984
+ }
2421
2985
  setTimeout(() => {
2422
- fitMode = "width";
2423
- scale = calculateFitScale("width");
2986
+ scale = calculateFitScale("page");
2424
2987
  applyTransform();
2425
- }, 60);
2988
+ }, 40);
2426
2989
  const cleanup = () => {
2427
- indicator?.remove();
2990
+ resizeObserver?.disconnect();
2991
+ resizeObserver = null;
2428
2992
  for (const url of createdBlobUrls) {
2429
2993
  URL.revokeObjectURL(url);
2430
2994
  }
@@ -2441,21 +3005,24 @@ var DocxPlugin = class {
2441
3005
  showPage(page);
2442
3006
  },
2443
3007
  zoomIn: () => {
2444
- scale += 0.15;
3008
+ isUserZoomed = true;
3009
+ scale = Math.min(3.5, scale + 0.15);
2445
3010
  applyTransform();
2446
3011
  },
2447
3012
  zoomOut: () => {
3013
+ isUserZoomed = true;
2448
3014
  scale = Math.max(0.2, scale - 0.15);
2449
3015
  applyTransform();
2450
3016
  },
2451
3017
  getZoom: () => scale,
2452
3018
  setZoom: (level) => {
3019
+ isUserZoomed = true;
2453
3020
  scale = level;
2454
3021
  applyTransform();
2455
3022
  },
2456
3023
  fitToPage: () => {
2457
- fitMode = fitMode === "width" ? "page" : "width";
2458
- scale = calculateFitScale(fitMode);
3024
+ isUserZoomed = false;
3025
+ scale = calculateFitScale("page");
2459
3026
  rotation = 0;
2460
3027
  applyTransform();
2461
3028
  },
@@ -2845,7 +3412,7 @@ var ExcelPlugin = class {
2845
3412
  "application/vnd.ms-excel.template.macroEnabled.12",
2846
3413
  "application/vnd.oasis.opendocument.spreadsheet"
2847
3414
  ];
2848
- weight = 80;
3415
+ weight = 85;
2849
3416
  supports(file) {
2850
3417
  const ext = file.metadata.extension?.toLowerCase();
2851
3418
  const mime = file.metadata.mimeType?.toLowerCase();
@@ -2857,6 +3424,14 @@ var ExcelPlugin = class {
2857
3424
  getToolbarActions(instance) {
2858
3425
  const totalSheets = instance.getPageCount?.() ?? 1;
2859
3426
  const actions = [];
3427
+ actions.push({
3428
+ id: "thumbnails",
3429
+ icon: "thumbnails",
3430
+ label: "Sheet Thumbnails",
3431
+ type: "button",
3432
+ group: "navigation",
3433
+ execute: () => instance.toggleThumbnails?.()
3434
+ });
2860
3435
  if (totalSheets > 1) {
2861
3436
  actions.push({
2862
3437
  id: "page-nav",
@@ -2900,26 +3475,6 @@ var ExcelPlugin = class {
2900
3475
  instance.zoomIn?.();
2901
3476
  }
2902
3477
  },
2903
- {
2904
- id: "fit-page",
2905
- icon: "fit-page",
2906
- label: "Fit to View",
2907
- type: "button",
2908
- group: "zoom",
2909
- execute: () => {
2910
- instance.fitToPage?.();
2911
- }
2912
- },
2913
- {
2914
- id: "rotate-cw",
2915
- icon: "rotate-cw",
2916
- label: "Rotate",
2917
- type: "button",
2918
- group: "view",
2919
- execute: () => {
2920
- instance.rotateCW?.();
2921
- }
2922
- },
2923
3478
  {
2924
3479
  id: "download",
2925
3480
  icon: "download",
@@ -2939,6 +3494,16 @@ var ExcelPlugin = class {
2939
3494
  execute: () => {
2940
3495
  instance.print?.();
2941
3496
  }
3497
+ },
3498
+ {
3499
+ id: "open-window",
3500
+ icon: "open-window",
3501
+ label: "Open in Separate Full Window",
3502
+ type: "button",
3503
+ group: "actions",
3504
+ execute: () => {
3505
+ instance.openInSeparateWindow?.();
3506
+ }
2942
3507
  }
2943
3508
  );
2944
3509
  return actions;
@@ -2970,12 +3535,11 @@ var ExcelPlugin = class {
2970
3535
  container.appendChild(tabsArea);
2971
3536
  ctx.container.appendChild(container);
2972
3537
  let scale = 1;
2973
- let rotation = 0;
2974
3538
  let currentSheetIndex = 1;
2975
3539
  let sheetNames = [];
2976
3540
  let wb = null;
2977
3541
  const applyTransform = () => {
2978
- contentArea.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
3542
+ contentArea.style.transform = `scale(${scale})`;
2979
3543
  contentArea.style.transformOrigin = "top left";
2980
3544
  };
2981
3545
  const calculateFitScale = () => {
@@ -2983,7 +3547,7 @@ var ExcelPlugin = class {
2983
3547
  if (!table) return 1;
2984
3548
  const availW = Math.max(280, container.clientWidth - 48);
2985
3549
  const tW = table.offsetWidth || 800;
2986
- return Math.max(0.65, Math.min(1, availW / tW));
3550
+ return Math.max(0.4, Math.min(1, availW / tW));
2987
3551
  };
2988
3552
  try {
2989
3553
  wb = XLSX.read(new Uint8Array(ctx.buffer), { type: "array", cellDates: true });
@@ -3038,7 +3602,16 @@ var ExcelPlugin = class {
3038
3602
  }
3039
3603
  });
3040
3604
  ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
3605
+ requestAnimationFrame(() => {
3606
+ scale = calculateFitScale();
3607
+ applyTransform();
3608
+ });
3041
3609
  };
3610
+ const ro = new ResizeObserver(() => {
3611
+ scale = calculateFitScale();
3612
+ applyTransform();
3613
+ });
3614
+ ro.observe(container);
3042
3615
  if (sheetNames.length > 0) {
3043
3616
  sheetNames.forEach((name, idx) => {
3044
3617
  const btn = document.createElement("button");
@@ -3065,6 +3638,7 @@ var ExcelPlugin = class {
3065
3638
  `;
3066
3639
  }
3067
3640
  const cleanup = () => {
3641
+ ro.disconnect();
3068
3642
  container.remove();
3069
3643
  ctx.container.innerHTML = "";
3070
3644
  };
@@ -3086,16 +3660,11 @@ var ExcelPlugin = class {
3086
3660
  },
3087
3661
  fitToPage: () => {
3088
3662
  scale = calculateFitScale();
3089
- rotation = 0;
3090
3663
  applyTransform();
3091
3664
  },
3092
3665
  rotateCW: () => {
3093
- rotation = (rotation + 90) % 360;
3094
- applyTransform();
3095
3666
  },
3096
3667
  rotateCCW: () => {
3097
- rotation = (rotation - 90 + 360) % 360;
3098
- applyTransform();
3099
3668
  },
3100
3669
  goToPage: (page) => {
3101
3670
  if (page > 0 && page <= sheetNames.length) {
@@ -3104,6 +3673,63 @@ var ExcelPlugin = class {
3104
3673
  },
3105
3674
  getPageCount: () => sheetNames.length,
3106
3675
  getCurrentPage: () => currentSheetIndex,
3676
+ getThumbnails: () => {
3677
+ return sheetNames.map((name, idx) => ({
3678
+ index: idx,
3679
+ label: name,
3680
+ render: async (canvas) => {
3681
+ canvas.width = 140;
3682
+ canvas.height = 100;
3683
+ const c = canvas.getContext("2d");
3684
+ if (!c) return;
3685
+ c.fillStyle = "#ffffff";
3686
+ c.fillRect(0, 0, canvas.width, canvas.height);
3687
+ c.fillStyle = "#107c41";
3688
+ c.fillRect(0, 0, canvas.width, 16);
3689
+ c.fillStyle = "#ffffff";
3690
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3691
+ c.fillText(`\u{1F4CA} ${name.slice(0, 16)}`, 6, 11);
3692
+ const startY = 17;
3693
+ const rowH = 13;
3694
+ const colW = 30;
3695
+ const colHeaders = ["A", "B", "C", "D"];
3696
+ c.fillStyle = "#f1f5f9";
3697
+ c.fillRect(0, startY, canvas.width, rowH);
3698
+ c.strokeStyle = "#cbd5e1";
3699
+ c.lineWidth = 0.8;
3700
+ c.strokeRect(0, startY, canvas.width, rowH);
3701
+ c.fillStyle = "#64748b";
3702
+ c.font = "bold 7px sans-serif";
3703
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3704
+ c.fillText(colHeaders[ci], 14 + ci * colW, startY + 9);
3705
+ }
3706
+ const ws = wb?.Sheets[name];
3707
+ for (let ri = 1; ri <= 5; ri++) {
3708
+ const y = startY + ri * rowH;
3709
+ if (y > canvas.height - 2) break;
3710
+ c.fillStyle = "#f8fafc";
3711
+ c.fillRect(0, y, 12, rowH);
3712
+ c.fillStyle = "#94a3b8";
3713
+ c.font = "6px sans-serif";
3714
+ c.fillText(String(ri), 3, y + 9);
3715
+ c.strokeStyle = "#e2e8f0";
3716
+ c.strokeRect(0, y, canvas.width, rowH);
3717
+ c.fillStyle = "#334155";
3718
+ c.font = "6px sans-serif";
3719
+ for (let ci = 0; ci < colHeaders.length; ci++) {
3720
+ const cellRef = `${colHeaders[ci]}${ri}`;
3721
+ const cellVal = ws?.[cellRef]?.w || ws?.[cellRef]?.v;
3722
+ if (cellVal !== void 0) {
3723
+ c.fillText(String(cellVal).slice(0, 6), 14 + ci * colW, y + 9);
3724
+ }
3725
+ }
3726
+ }
3727
+ c.strokeStyle = "#cbd5e1";
3728
+ c.lineWidth = 1;
3729
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3730
+ }
3731
+ }));
3732
+ },
3107
3733
  download: () => {
3108
3734
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
3109
3735
  const url = URL.createObjectURL(blob);
@@ -3136,7 +3762,16 @@ var CsvPlugin = class {
3136
3762
  return ext === ".csv" || ext === ".tsv" || mime === "text/csv" || mime === "text/tab-separated-values";
3137
3763
  }
3138
3764
  getToolbarActions(instance) {
3139
- return [
3765
+ const actions = [];
3766
+ actions.push({
3767
+ id: "thumbnails",
3768
+ icon: "thumbnails",
3769
+ label: "Sheet Thumbnails",
3770
+ type: "button",
3771
+ group: "navigation",
3772
+ execute: () => instance.toggleThumbnails?.()
3773
+ });
3774
+ actions.push(
3140
3775
  {
3141
3776
  id: "zoom-out",
3142
3777
  icon: "zoom-out",
@@ -3176,8 +3811,19 @@ var CsvPlugin = class {
3176
3811
  execute: () => {
3177
3812
  instance.print?.();
3178
3813
  }
3814
+ },
3815
+ {
3816
+ id: "open-window",
3817
+ icon: "open-window",
3818
+ label: "Open in Separate Full Window",
3819
+ type: "button",
3820
+ group: "actions",
3821
+ execute: () => {
3822
+ instance.openInSeparateWindow?.();
3823
+ }
3179
3824
  }
3180
- ];
3825
+ );
3826
+ return actions;
3181
3827
  }
3182
3828
  async render(ctx) {
3183
3829
  const decoder = new TextDecoder("utf-8");
@@ -3188,57 +3834,156 @@ var CsvPlugin = class {
3188
3834
  container.style.overflow = "auto";
3189
3835
  container.style.padding = "16px";
3190
3836
  container.style.boxSizing = "border-box";
3191
- container.style.transformOrigin = "top left";
3837
+ const tableWrapper = document.createElement("div");
3838
+ tableWrapper.style.transformOrigin = "top left";
3839
+ const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3840
+ const allLines = text.split(/\r?\n/).filter((r) => r.trim().length > 0);
3841
+ const headerLine = allLines[0] || "";
3842
+ const headerCells = headerLine.split(delimiter);
3843
+ const dataLines = allLines.slice(1);
3192
3844
  const table = document.createElement("table");
3193
3845
  table.style.borderCollapse = "collapse";
3194
- table.style.width = "100%";
3195
- table.style.fontFamily = "monospace";
3846
+ table.style.minWidth = "100%";
3847
+ table.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace';
3196
3848
  table.style.fontSize = "13px";
3197
- const delimiter = ctx.metadata.extension === ".tsv" ? " " : ",";
3198
- const rows = text.split(/\r?\n/).slice(0, 500);
3199
- rows.forEach((row, rowIndex) => {
3200
- if (!row.trim()) return;
3849
+ table.style.background = "#ffffff";
3850
+ table.style.border = "1px solid #d0d7de";
3851
+ const headerTr = document.createElement("tr");
3852
+ headerCells.forEach((cell) => {
3853
+ const th = document.createElement("th");
3854
+ th.textContent = cell.trim();
3855
+ th.style.border = "1px solid #d0d7de";
3856
+ th.style.padding = "8px 12px";
3857
+ th.style.backgroundColor = "#f6f8fa";
3858
+ th.style.fontWeight = "600";
3859
+ th.style.whiteSpace = "nowrap";
3860
+ headerTr.appendChild(th);
3861
+ });
3862
+ table.appendChild(headerTr);
3863
+ dataLines.forEach((row, idx) => {
3201
3864
  const tr = document.createElement("tr");
3865
+ if (idx % 2 === 1) tr.style.backgroundColor = "#f8fafc";
3202
3866
  const cells = row.split(delimiter);
3203
3867
  cells.forEach((cell) => {
3204
- const td = document.createElement(rowIndex === 0 ? "th" : "td");
3868
+ const td = document.createElement("td");
3205
3869
  td.textContent = cell.trim();
3206
3870
  td.style.border = "1px solid #d0d7de";
3207
3871
  td.style.padding = "6px 12px";
3208
- if (rowIndex === 0) {
3209
- td.style.backgroundColor = "#f6f8fa";
3210
- td.style.fontWeight = "bold";
3211
- }
3872
+ td.style.whiteSpace = "nowrap";
3212
3873
  tr.appendChild(td);
3213
3874
  });
3214
3875
  table.appendChild(tr);
3215
3876
  });
3216
- container.appendChild(table);
3877
+ tableWrapper.appendChild(table);
3878
+ container.appendChild(tableWrapper);
3217
3879
  ctx.container.appendChild(container);
3218
3880
  let scale = 1;
3881
+ const calculateFitScale = () => {
3882
+ const availW = Math.max(280, container.clientWidth - 48);
3883
+ const tW = table.offsetWidth || 800;
3884
+ return Math.max(0.4, Math.min(1, availW / tW));
3885
+ };
3886
+ const applyScale = () => {
3887
+ tableWrapper.style.transform = `scale(${scale})`;
3888
+ };
3889
+ requestAnimationFrame(() => {
3890
+ scale = calculateFitScale();
3891
+ applyScale();
3892
+ });
3893
+ const ro = new ResizeObserver(() => {
3894
+ scale = calculateFitScale();
3895
+ applyScale();
3896
+ });
3897
+ ro.observe(container);
3219
3898
  const cleanup = () => {
3899
+ ro.disconnect();
3220
3900
  container.remove();
3221
3901
  ctx.container.innerHTML = "";
3222
3902
  };
3223
3903
  ctx.signal.addEventListener("abort", cleanup);
3224
3904
  return {
3225
3905
  destroy: cleanup,
3906
+ getPageCount: () => 1,
3907
+ getCurrentPage: () => 1,
3908
+ goToPage: () => {
3909
+ },
3910
+ getThumbnails: () => {
3911
+ const sheetName = ctx.metadata.name?.replace(/\.[^/.]+$/, "") || "Sheet1";
3912
+ return [{
3913
+ index: 0,
3914
+ label: sheetName,
3915
+ render: async (canvas) => {
3916
+ canvas.width = 140;
3917
+ canvas.height = 100;
3918
+ const c = canvas.getContext("2d");
3919
+ if (!c) return;
3920
+ c.fillStyle = "#ffffff";
3921
+ c.fillRect(0, 0, canvas.width, canvas.height);
3922
+ c.fillStyle = "#107c41";
3923
+ c.fillRect(0, 0, canvas.width, 16);
3924
+ c.fillStyle = "#ffffff";
3925
+ c.font = 'bold 8px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
3926
+ c.fillText(`\u{1F4CA} ${sheetName.slice(0, 16)}`, 6, 11);
3927
+ const startY = 17;
3928
+ const rowH = 13;
3929
+ const colW = 30;
3930
+ const cols = headerCells.slice(0, 4).map((h2) => h2.trim() || "Col");
3931
+ c.fillStyle = "#f1f5f9";
3932
+ c.fillRect(0, startY, canvas.width, rowH);
3933
+ c.strokeStyle = "#cbd5e1";
3934
+ c.lineWidth = 0.8;
3935
+ c.strokeRect(0, startY, canvas.width, rowH);
3936
+ c.fillStyle = "#64748b";
3937
+ c.font = "bold 7px sans-serif";
3938
+ for (let ci = 0; ci < cols.length; ci++) {
3939
+ c.fillText(cols[ci].slice(0, 5), 14 + ci * colW, startY + 9);
3940
+ }
3941
+ for (let ri = 0; ri < Math.min(5, dataLines.length); ri++) {
3942
+ const y = startY + (ri + 1) * rowH;
3943
+ if (y > canvas.height - 2) break;
3944
+ c.fillStyle = "#f8fafc";
3945
+ c.fillRect(0, y, 12, rowH);
3946
+ c.fillStyle = "#94a3b8";
3947
+ c.font = "6px sans-serif";
3948
+ c.fillText(String(ri + 1), 3, y + 9);
3949
+ c.strokeStyle = "#e2e8f0";
3950
+ c.strokeRect(0, y, canvas.width, rowH);
3951
+ const rowCells = dataLines[ri].split(delimiter);
3952
+ c.fillStyle = "#334155";
3953
+ c.font = "6px sans-serif";
3954
+ for (let ci = 0; ci < cols.length; ci++) {
3955
+ const val = (rowCells[ci] || "").trim();
3956
+ if (val) {
3957
+ c.fillText(val.slice(0, 6), 14 + ci * colW, y + 9);
3958
+ }
3959
+ }
3960
+ }
3961
+ c.strokeStyle = "#cbd5e1";
3962
+ c.lineWidth = 1;
3963
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
3964
+ }
3965
+ }];
3966
+ },
3226
3967
  zoomIn: () => {
3227
3968
  scale += 0.1;
3228
- container.style.transform = `scale(${scale})`;
3969
+ applyScale();
3229
3970
  },
3230
3971
  zoomOut: () => {
3231
3972
  scale = Math.max(0.2, scale - 0.1);
3232
- container.style.transform = `scale(${scale})`;
3973
+ applyScale();
3233
3974
  },
3234
3975
  getZoom: () => scale,
3235
3976
  setZoom: (level) => {
3236
3977
  scale = level;
3237
- container.style.transform = `scale(${scale})`;
3978
+ applyScale();
3238
3979
  },
3239
3980
  fitToPage: () => {
3240
- scale = 1;
3241
- container.style.transform = `scale(1)`;
3981
+ scale = calculateFitScale();
3982
+ applyScale();
3983
+ },
3984
+ rotateCW: () => {
3985
+ },
3986
+ rotateCCW: () => {
3242
3987
  },
3243
3988
  download: () => {
3244
3989
  const blob = new Blob([ctx.buffer], { type: "text/csv" });
@@ -3300,30 +4045,36 @@ var CodePlugin = class {
3300
4045
  return false;
3301
4046
  }
3302
4047
  getToolbarActions(instance) {
3303
- const totalPages = instance.getPageCount?.() ?? 1;
3304
- const actions = [];
3305
- if (totalPages > 1) {
3306
- actions.push({
3307
- id: "page-nav",
3308
- icon: "",
3309
- label: "Page Navigation",
3310
- type: "page-nav",
3311
- group: "navigation",
3312
- value: instance.getCurrentPage?.() ?? 1,
3313
- max: totalPages,
3314
- execute: (action, page) => {
3315
- const cur = instance.getCurrentPage?.() ?? 1;
3316
- const max = instance.getPageCount?.() ?? 1;
3317
- if (action === "prev") {
3318
- if (cur > 1) instance.goToPage?.(cur - 1);
3319
- } else if (action === "next") {
3320
- if (cur < max) instance.goToPage?.(cur + 1);
3321
- } else if (typeof page === "number") {
3322
- instance.goToPage?.(page);
3323
- }
4048
+ const totalPages = instance.getPageCount?.() ?? 1;
4049
+ const actions = [];
4050
+ actions.push({
4051
+ id: "thumbnails",
4052
+ icon: "thumbnails",
4053
+ label: "Page Thumbnails",
4054
+ type: "button",
4055
+ group: "navigation",
4056
+ execute: () => instance.toggleThumbnails?.()
4057
+ });
4058
+ actions.push({
4059
+ id: "page-nav",
4060
+ icon: "",
4061
+ label: "Page Navigation",
4062
+ type: "page-nav",
4063
+ group: "navigation",
4064
+ value: instance.getCurrentPage?.() ?? 1,
4065
+ max: totalPages,
4066
+ execute: (action, page) => {
4067
+ const cur = instance.getCurrentPage?.() ?? 1;
4068
+ const max = instance.getPageCount?.() ?? 1;
4069
+ if (action === "prev") {
4070
+ if (cur > 1) instance.goToPage?.(cur - 1);
4071
+ } else if (action === "next") {
4072
+ if (cur < max) instance.goToPage?.(cur + 1);
4073
+ } else if (typeof page === "number") {
4074
+ instance.goToPage?.(page);
3324
4075
  }
3325
- });
3326
- }
4076
+ }
4077
+ });
3327
4078
  actions.push(
3328
4079
  {
3329
4080
  id: "zoom-out",
@@ -3345,26 +4096,6 @@ var CodePlugin = class {
3345
4096
  instance.zoomIn?.();
3346
4097
  }
3347
4098
  },
3348
- {
3349
- id: "fit-page",
3350
- icon: "fit-page",
3351
- label: "Fit to View",
3352
- type: "button",
3353
- group: "zoom",
3354
- execute: () => {
3355
- instance.fitToPage?.();
3356
- }
3357
- },
3358
- {
3359
- id: "rotate-cw",
3360
- icon: "rotate-cw",
3361
- label: "Rotate",
3362
- type: "button",
3363
- group: "view",
3364
- execute: () => {
3365
- instance.rotateCW?.();
3366
- }
3367
- },
3368
4099
  {
3369
4100
  id: "copy",
3370
4101
  icon: "copy",
@@ -3467,16 +4198,19 @@ var CodePlugin = class {
3467
4198
  let fontSize = 13;
3468
4199
  let rotation = 0;
3469
4200
  let zoomLevel = 1;
4201
+ let isUserZoomed = false;
3470
4202
  const pre = document.createElement("pre");
3471
4203
  const code = document.createElement("code");
3472
4204
  if (isTxt) {
4205
+ container.style.overflowX = "hidden";
4206
+ container.style.overflowY = "auto";
3473
4207
  container.style.backgroundColor = "#f1f5f9";
3474
- container.style.padding = "32px";
4208
+ container.style.padding = "16px 8px";
3475
4209
  container.style.boxSizing = "border-box";
3476
4210
  container.style.display = "flex";
3477
4211
  container.style.flexDirection = "column";
3478
4212
  container.style.alignItems = "center";
3479
- pre.style.margin = "0";
4213
+ pre.style.margin = "0 auto";
3480
4214
  pre.style.fontFamily = "Consolas, 'Courier New', monospace";
3481
4215
  pre.style.fontSize = "13px";
3482
4216
  pre.style.color = "#1e293b";
@@ -3488,10 +4222,12 @@ var CodePlugin = class {
3488
4222
  pre.style.minHeight = "1056px";
3489
4223
  pre.style.padding = "72px 56px";
3490
4224
  pre.style.boxSizing = "border-box";
3491
- pre.style.boxShadow = "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)";
4225
+ pre.style.boxShadow = "0 4px 24px rgba(0, 0, 0, 0.08)";
3492
4226
  pre.style.borderRadius = "4px";
3493
4227
  pre.style.transformOrigin = "top center";
4228
+ pre.style.transition = "transform 0.15s ease";
3494
4229
  } else {
4230
+ container.style.overflow = "auto";
3495
4231
  container.style.backgroundColor = "#1e1e1e";
3496
4232
  container.style.color = "#d4d4d4";
3497
4233
  container.style.padding = "16px";
@@ -3523,42 +4259,9 @@ var CodePlugin = class {
3523
4259
  renderCodePage(rawPages[0] || (isTxt ? "" : fullText));
3524
4260
  pre.appendChild(code);
3525
4261
  container.appendChild(pre);
3526
- let indicator = null;
3527
- if (totalPages > 1) {
3528
- indicator = document.createElement("div");
3529
- indicator.className = "fp-code-page-indicator";
3530
- indicator.style.position = "sticky";
3531
- indicator.style.bottom = "16px";
3532
- if (isTxt) {
3533
- indicator.style.backgroundColor = "rgba(255, 255, 255, 0.9)";
3534
- indicator.style.color = "#334155";
3535
- indicator.style.border = "1px solid #e2e8f0";
3536
- indicator.style.boxShadow = "0 1px 3px rgba(0,0,0,0.1)";
3537
- } else {
3538
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3539
- indicator.style.color = "#f8fafc";
3540
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3541
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3542
- indicator.style.backdropFilter = "blur(8px)";
3543
- }
3544
- indicator.style.fontSize = "12px";
3545
- indicator.style.fontWeight = "600";
3546
- indicator.style.padding = "5px 14px";
3547
- indicator.style.borderRadius = "20px";
3548
- indicator.style.zIndex = "10";
3549
- indicator.style.userSelect = "none";
3550
- indicator.style.pointerEvents = "none";
3551
- indicator.style.textAlign = "center";
3552
- indicator.style.width = "fit-content";
3553
- indicator.style.margin = "16px auto 0";
3554
- container.appendChild(indicator);
3555
- }
3556
4262
  const showPage = (pageNum) => {
3557
4263
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
3558
4264
  renderCodePage(rawPages[currentPage - 1] || (isTxt ? "" : fullText));
3559
- if (indicator) {
3560
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3561
- }
3562
4265
  container.scrollTop = 0;
3563
4266
  ctx.emit("page-change", { page: currentPage, total: totalPages });
3564
4267
  };
@@ -3566,36 +4269,90 @@ var CodePlugin = class {
3566
4269
  showPage(1);
3567
4270
  }
3568
4271
  ctx.container.appendChild(container);
3569
- const cleanup = () => {
3570
- indicator?.remove();
3571
- container.remove();
3572
- ctx.container.innerHTML = "";
4272
+ const calculateTxtFit = () => {
4273
+ const availW = Math.max(280, container.clientWidth - 32);
4274
+ return Math.max(0.4, Math.min(3, availW / 816));
3573
4275
  };
3574
- ctx.signal.addEventListener("abort", cleanup);
3575
4276
  const updateTransform = () => {
3576
4277
  if (isTxt) {
3577
4278
  pre.style.transform = `scale(${zoomLevel}) rotate(${rotation}deg)`;
4279
+ const scaledH = 1056 * zoomLevel;
4280
+ const extraH = Math.max(0, scaledH - 1056);
4281
+ pre.style.marginBottom = `${extraH + 32}px`;
3578
4282
  } else {
3579
4283
  pre.style.transform = `rotate(${rotation}deg)`;
3580
4284
  pre.style.fontSize = `${fontSize}px`;
3581
4285
  }
3582
4286
  };
4287
+ let resizeObserver = null;
4288
+ if (isTxt) {
4289
+ setTimeout(() => {
4290
+ zoomLevel = calculateTxtFit();
4291
+ updateTransform();
4292
+ }, 60);
4293
+ resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
4294
+ if (!isUserZoomed) {
4295
+ zoomLevel = calculateTxtFit();
4296
+ updateTransform();
4297
+ }
4298
+ }) : null;
4299
+ resizeObserver?.observe(container);
4300
+ }
4301
+ const cleanup = () => {
4302
+ resizeObserver?.disconnect();
4303
+ container.remove();
4304
+ ctx.container.innerHTML = "";
4305
+ };
4306
+ ctx.signal.addEventListener("abort", cleanup);
3583
4307
  return {
3584
4308
  destroy: cleanup,
3585
4309
  getPageCount: () => totalPages,
3586
4310
  getCurrentPage: () => currentPage,
3587
4311
  goToPage: (page) => showPage(page),
4312
+ getThumbnails: () => {
4313
+ return rawPages.map((pageText, idx) => ({
4314
+ index: idx,
4315
+ label: `Page ${idx + 1}`,
4316
+ render: async (canvas) => {
4317
+ canvas.width = 140;
4318
+ canvas.height = 180;
4319
+ const c = canvas.getContext("2d");
4320
+ if (!c) return;
4321
+ c.fillStyle = "#ffffff";
4322
+ c.fillRect(0, 0, canvas.width, canvas.height);
4323
+ c.strokeStyle = "#cbd5e1";
4324
+ c.lineWidth = 1;
4325
+ c.strokeRect(0.5, 0.5, canvas.width - 1, canvas.height - 1);
4326
+ c.fillStyle = "#64748b";
4327
+ const lines = (pageText || "").split("\n").slice(0, 24);
4328
+ const lineH = 6;
4329
+ const startY = 14;
4330
+ const startX = 12;
4331
+ const maxW = canvas.width - 24;
4332
+ c.font = "5px monospace";
4333
+ for (let li = 0; li < lines.length; li++) {
4334
+ const l = lines[li].trim();
4335
+ if (!l) continue;
4336
+ const y = startY + li * lineH;
4337
+ if (y > canvas.height - 12) break;
4338
+ c.fillText(l.slice(0, 30), startX, y, maxW);
4339
+ }
4340
+ }
4341
+ }));
4342
+ },
3588
4343
  zoomIn: () => {
4344
+ isUserZoomed = true;
3589
4345
  if (isTxt) {
3590
- zoomLevel = Math.min(3, zoomLevel + 0.1);
4346
+ zoomLevel = Math.min(3.5, zoomLevel + 0.15);
3591
4347
  } else {
3592
4348
  fontSize = Math.min(32, fontSize + 2);
3593
4349
  }
3594
4350
  updateTransform();
3595
4351
  },
3596
4352
  zoomOut: () => {
4353
+ isUserZoomed = true;
3597
4354
  if (isTxt) {
3598
- zoomLevel = Math.max(0.1, zoomLevel - 0.1);
4355
+ zoomLevel = Math.max(0.1, zoomLevel - 0.15);
3599
4356
  } else {
3600
4357
  fontSize = Math.max(8, fontSize - 2);
3601
4358
  }
@@ -3603,6 +4360,7 @@ var CodePlugin = class {
3603
4360
  },
3604
4361
  getZoom: () => isTxt ? zoomLevel : fontSize / 13,
3605
4362
  setZoom: (level) => {
4363
+ isUserZoomed = true;
3606
4364
  if (isTxt) {
3607
4365
  zoomLevel = level;
3608
4366
  } else {
@@ -3611,9 +4369,10 @@ var CodePlugin = class {
3611
4369
  updateTransform();
3612
4370
  },
3613
4371
  fitToPage: () => {
4372
+ isUserZoomed = false;
3614
4373
  fontSize = 13;
3615
4374
  rotation = 0;
3616
- zoomLevel = isTxt ? Math.max(0.65, Math.min(1.05, (container.clientWidth - 48) / 816)) : 1;
4375
+ zoomLevel = isTxt ? calculateTxtFit() : 1;
3617
4376
  updateTransform();
3618
4377
  },
3619
4378
  rotateCW: () => {
@@ -4480,6 +5239,148 @@ var ThreeDPlugin = class {
4480
5239
  function threeDPlugin() {
4481
5240
  return new ThreeDPlugin();
4482
5241
  }
5242
+ function cleanRtfText(raw) {
5243
+ return raw.replace(/\\bin\d+\s/g, "").replace(/{\\\*[^}]+}/g, "").replace(/\\u(-?\d+)\??/g, (_, code) => {
5244
+ let num = parseInt(code, 10);
5245
+ if (num < 0) num += 65536;
5246
+ return String.fromCharCode(num);
5247
+ }).replace(/\\'([0-9a-fA-F]{2})/g, (_, hex) => {
5248
+ return String.fromCharCode(parseInt(hex, 16));
5249
+ }).replace(/\\tab\b/g, " ").replace(/\\emdash\b/g, "\u2014").replace(/\\endash\b/g, "\u2013").replace(/\\bullet\b/g, "\u2022").replace(/\\lquote\b/g, "\u2018").replace(/\\rquote\b/g, "\u2019").replace(/\\ldblquote\b/g, "\u201C").replace(/\\rdblquote\b/g, "\u201D").replace(/\\\w+(?:-?\d+)?\s?/g, " ").replace(/[{}\r\n]/g, " ").replace(/\s+/g, " ").trim();
5250
+ }
5251
+ function parseRtfTables(rtfText) {
5252
+ const rowSegments = rtfText.split(/\\row\b/);
5253
+ if (rowSegments.length <= 1) return [];
5254
+ const rawTables = [];
5255
+ let currentTable = null;
5256
+ let charOffset = 0;
5257
+ for (let i = 0; i < rowSegments.length - 1; i++) {
5258
+ const segment = rowSegments[i];
5259
+ const segmentStart = charOffset;
5260
+ const segmentEnd = charOffset + segment.length + 4;
5261
+ const cellParts = segment.split(/\\cell\b/);
5262
+ if (cellParts.length > 1) {
5263
+ const cellxMatches = [...segment.matchAll(/\\cellx(\d+)/g)].map((m) => parseInt(m[1], 10));
5264
+ let cellWidths = [];
5265
+ if (cellxMatches.length > 0) {
5266
+ let seq = [cellxMatches[0]];
5267
+ for (let k = 1; k < cellxMatches.length; k++) {
5268
+ if (cellxMatches[k] > cellxMatches[k - 1]) seq.push(cellxMatches[k]);
5269
+ else break;
5270
+ }
5271
+ let prev = 0;
5272
+ cellWidths = seq.map((x) => {
5273
+ const w = x - prev;
5274
+ prev = x;
5275
+ return w;
5276
+ });
5277
+ }
5278
+ const cells = [];
5279
+ for (let c = 0; c < cellParts.length - 1; c++) {
5280
+ let rawCell = cellParts[c];
5281
+ if (c === 0) {
5282
+ const lastTrowd = rawCell.lastIndexOf("\\trowd");
5283
+ if (lastTrowd !== -1) rawCell = rawCell.slice(lastTrowd);
5284
+ }
5285
+ cells.push(cleanRtfText(rawCell));
5286
+ }
5287
+ const isNewTable = !currentTable || segmentStart - currentTable.lastEnd > 2500;
5288
+ if (isNewTable) {
5289
+ const prevTextSlice = rtfText.slice(Math.max(0, segmentStart - 4e3), segmentStart);
5290
+ const cleanPrev = cleanRtfText(prevTextSlice);
5291
+ const anchorBefore = cleanPrev.slice(-80).trim();
5292
+ currentTable = {
5293
+ id: `RTF_TABLE_${rawTables.length}`,
5294
+ startPos: segmentStart,
5295
+ lastEnd: segmentEnd,
5296
+ rows: [],
5297
+ columnWidths: cellWidths,
5298
+ anchorBefore,
5299
+ anchorAfter: ""
5300
+ };
5301
+ rawTables.push(currentTable);
5302
+ }
5303
+ if (currentTable) {
5304
+ currentTable.rows.push({
5305
+ cells,
5306
+ widths: cellWidths.length === cells.length ? cellWidths : currentTable.columnWidths
5307
+ });
5308
+ currentTable.lastEnd = segmentEnd;
5309
+ }
5310
+ }
5311
+ charOffset += segment.length + 4;
5312
+ }
5313
+ rawTables.forEach((tbl) => {
5314
+ const afterSlice = rtfText.slice(tbl.lastEnd, Math.min(rtfText.length, tbl.lastEnd + 2e3));
5315
+ const cleanAfter = cleanRtfText(afterSlice);
5316
+ tbl.anchorAfter = cleanAfter.slice(0, 80).trim();
5317
+ });
5318
+ return rawTables.map((tbl) => {
5319
+ const totalW = (tbl.columnWidths || []).reduce((a, b) => a + b, 0);
5320
+ const colPercents = totalW > 0 ? tbl.columnWidths.map((w) => Math.round(w / totalW * 100)) : [];
5321
+ return {
5322
+ id: tbl.id,
5323
+ rows: tbl.rows.map((r) => r.cells),
5324
+ colPercents,
5325
+ anchorBefore: tbl.anchorBefore,
5326
+ anchorAfter: tbl.anchorAfter
5327
+ };
5328
+ });
5329
+ }
5330
+ function renderRtfTableElement(table) {
5331
+ const tableEl = document.createElement("table");
5332
+ tableEl.className = "fp-rtf-table";
5333
+ tableEl.style.width = "100%";
5334
+ tableEl.style.borderCollapse = "collapse";
5335
+ tableEl.style.margin = "16px 0 20px";
5336
+ tableEl.style.fontSize = "10pt";
5337
+ tableEl.style.lineHeight = "1.5";
5338
+ tableEl.style.border = "1px solid #cbd5e1";
5339
+ tableEl.style.borderRadius = "4px";
5340
+ tableEl.style.boxSizing = "border-box";
5341
+ tableEl.style.backgroundColor = "#ffffff";
5342
+ const thead = document.createElement("thead");
5343
+ const tbody = document.createElement("tbody");
5344
+ table.rows.forEach((rowCells, rIdx) => {
5345
+ const tr = document.createElement("tr");
5346
+ const isHeader = rIdx === 0;
5347
+ if (isHeader) {
5348
+ tr.style.backgroundColor = "#f8fafc";
5349
+ tr.style.borderBottom = "2px solid #cbd5e1";
5350
+ } else {
5351
+ tr.style.borderBottom = "1px solid #e2e8f0";
5352
+ if (rIdx % 2 === 0) {
5353
+ tr.style.backgroundColor = "#fcfdfe";
5354
+ }
5355
+ }
5356
+ rowCells.forEach((cellText, cIdx) => {
5357
+ const cellEl = document.createElement(isHeader ? "th" : "td");
5358
+ cellEl.style.padding = "8px 12px";
5359
+ cellEl.style.border = "1px solid #cbd5e1";
5360
+ cellEl.style.verticalAlign = "top";
5361
+ cellEl.style.color = isHeader ? "#0f172a" : "#1e293b";
5362
+ cellEl.style.fontWeight = isHeader ? "600" : "normal";
5363
+ if (table.colPercents && table.colPercents[cIdx]) {
5364
+ cellEl.style.width = `${table.colPercents[cIdx]}%`;
5365
+ }
5366
+ if (/^\d+$/.test(cellText.trim()) || cellText.trim() === "#") {
5367
+ cellEl.style.textAlign = "center";
5368
+ } else {
5369
+ cellEl.style.textAlign = "left";
5370
+ }
5371
+ cellEl.textContent = cellText;
5372
+ tr.appendChild(cellEl);
5373
+ });
5374
+ if (isHeader) {
5375
+ thead.appendChild(tr);
5376
+ } else {
5377
+ tbody.appendChild(tr);
5378
+ }
5379
+ });
5380
+ tableEl.appendChild(thead);
5381
+ tableEl.appendChild(tbody);
5382
+ return tableEl;
5383
+ }
4483
5384
  var RtfPlugin = class {
4484
5385
  id = "rtf";
4485
5386
  name = "Rich Text Format (RTF)";
@@ -4494,29 +5395,35 @@ var RtfPlugin = class {
4494
5395
  getToolbarActions(instance) {
4495
5396
  const totalPages = instance.getPageCount?.() ?? 1;
4496
5397
  const actions = [];
4497
- if (totalPages > 1) {
4498
- actions.push({
4499
- id: "page-nav",
4500
- icon: "",
4501
- label: "Page Navigation",
4502
- type: "page-nav",
4503
- group: "navigation",
4504
- value: instance.getCurrentPage?.() ?? 1,
4505
- max: totalPages,
4506
- execute: (action, page) => {
4507
- const cur = instance.getCurrentPage?.() ?? 1;
4508
- const max = instance.getPageCount?.() ?? 1;
4509
- if (action === "prev") {
4510
- if (cur > 1) instance.goToPage?.(cur - 1);
4511
- } else if (action === "next") {
4512
- if (cur < max) instance.goToPage?.(cur + 1);
4513
- } else if (typeof page === "number") {
4514
- instance.goToPage?.(page);
4515
- }
5398
+ actions.push({
5399
+ id: "page-nav",
5400
+ icon: "",
5401
+ label: "Page Navigation",
5402
+ type: "page-nav",
5403
+ group: "navigation",
5404
+ value: instance.getCurrentPage?.() ?? 1,
5405
+ max: totalPages,
5406
+ execute: (action, page) => {
5407
+ const cur = instance.getCurrentPage?.() ?? 1;
5408
+ const max = instance.getPageCount?.() ?? 1;
5409
+ if (action === "prev") {
5410
+ if (cur > 1) instance.goToPage?.(cur - 1);
5411
+ } else if (action === "next") {
5412
+ if (cur < max) instance.goToPage?.(cur + 1);
5413
+ } else if (typeof page === "number") {
5414
+ instance.goToPage?.(page);
4516
5415
  }
4517
- });
4518
- }
5416
+ }
5417
+ });
4519
5418
  actions.push(
5419
+ {
5420
+ id: "thumbnails",
5421
+ icon: "thumbnails",
5422
+ label: "Thumbnails",
5423
+ type: "button",
5424
+ group: "view",
5425
+ execute: () => instance.toggleThumbnails?.()
5426
+ },
4520
5427
  {
4521
5428
  id: "zoom-out",
4522
5429
  icon: "zoom-out",
@@ -4587,67 +5494,143 @@ var RtfPlugin = class {
4587
5494
  async render(ctx) {
4588
5495
  const wrapper = document.createElement("div");
4589
5496
  wrapper.className = "fp-rtf-wrapper";
4590
- wrapper.style.padding = "32px";
4591
- wrapper.style.maxWidth = "850px";
5497
+ wrapper.style.padding = "0";
5498
+ wrapper.style.width = "816px";
4592
5499
  wrapper.style.margin = "0 auto";
4593
- wrapper.style.backgroundColor = "#fff";
4594
- wrapper.style.boxShadow = "0 2px 8px rgba(0,0,0,0.08)";
4595
- wrapper.style.borderRadius = "4px";
4596
- wrapper.style.minHeight = "100%";
5500
+ wrapper.style.boxSizing = "border-box";
5501
+ wrapper.style.display = "flex";
5502
+ wrapper.style.flexDirection = "column";
5503
+ wrapper.style.alignItems = "center";
5504
+ wrapper.style.backgroundColor = "transparent";
4597
5505
  wrapper.style.transformOrigin = "top center";
4598
- wrapper.style.transition = "transform 0.2s ease";
4599
- ctx.container.style.overflow = "auto";
4600
- ctx.container.style.padding = "24px";
5506
+ wrapper.style.transition = "transform 0.15s ease";
5507
+ ctx.container.style.overflowX = "hidden";
5508
+ ctx.container.style.overflowY = "auto";
5509
+ ctx.container.style.padding = "16px 8px";
4601
5510
  ctx.container.style.backgroundColor = "#f1f5f9";
4602
5511
  ctx.container.appendChild(wrapper);
4603
5512
  let scale = 1;
4604
5513
  let rotation = 0;
4605
5514
  let pageElements = [];
4606
- let fitMode = "width";
5515
+ let isUserZoomed = false;
5516
+ let fitMode = ctx?.options?.fitMode || "width";
4607
5517
  const calculateFitScale = (mode = fitMode) => {
4608
5518
  const activeEl = pageElements[currentPage - 1] || wrapper;
4609
- const elW = activeEl.offsetWidth || 850;
4610
- const singlePageH = Math.min(activeEl.offsetHeight || 1056, Math.round(elW * 1.32));
4611
- const availW = Math.max(280, ctx.container.clientWidth - 48);
4612
- const availH = Math.max(280, ctx.container.clientHeight - 80);
5519
+ const elW = 816;
5520
+ const singlePageH = activeEl?.offsetHeight || 1056;
5521
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
5522
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
4613
5523
  const sW = availW / elW;
4614
5524
  const sH = availH / singlePageH;
4615
5525
  if (mode === "page") {
4616
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
5526
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
4617
5527
  }
4618
- return Math.max(0.65, Math.min(1.05, sW));
5528
+ return Math.max(0.4, Math.min(3, sW));
4619
5529
  };
4620
5530
  const applyTransform = () => {
4621
5531
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
4622
5532
  wrapper.style.transformOrigin = "top center";
5533
+ const activeEl = pageElements[currentPage - 1];
5534
+ const baseH = activeEl?.offsetHeight || 1056;
5535
+ const scaledH = baseH * scale;
5536
+ const extraH = Math.max(0, scaledH - baseH);
5537
+ wrapper.style.marginBottom = `${extraH + 32}px`;
4623
5538
  };
5539
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
5540
+ if (!isUserZoomed) {
5541
+ scale = calculateFitScale(fitMode);
5542
+ applyTransform();
5543
+ }
5544
+ }) : null;
5545
+ resizeObserver?.observe(ctx.container);
4624
5546
  setTimeout(() => {
4625
- fitMode = "width";
4626
- scale = calculateFitScale("width");
5547
+ scale = calculateFitScale(fitMode);
4627
5548
  applyTransform();
4628
5549
  }, 60);
4629
5550
  try {
4630
5551
  if (typeof RTFJS.loggingEnabled === "function") {
4631
5552
  RTFJS.loggingEnabled(false);
4632
5553
  }
5554
+ const rawText = new TextDecoder("latin1").decode(ctx.buffer);
5555
+ const tables = parseRtfTables(rawText);
4633
5556
  const doc = new RTFJS.Document(ctx.buffer, {});
4634
5557
  const htmlElements = await doc.render();
4635
5558
  const contentNodes = [];
4636
- for (const item of htmlElements) {
4637
- if (item.children && item.children.length > 0 && !item.tagName.toLowerCase().startsWith("table")) {
4638
- contentNodes.push(...Array.from(item.children));
4639
- } else {
4640
- contentNodes.push(item);
5559
+ const extractBlocks = (nodes) => {
5560
+ for (const item of nodes) {
5561
+ if (!item || !(item instanceof HTMLElement)) continue;
5562
+ const tag = item.tagName.toLowerCase();
5563
+ const hasChildBlocks = Array.from(item.children).some((c) => {
5564
+ const ct = c.tagName.toLowerCase();
5565
+ return ["p", "div", "table", "h1", "h2", "h3", "h4", "h5", "h6", "section", "article", "ul", "ol"].includes(ct);
5566
+ });
5567
+ if (hasChildBlocks && !["table", "tr", "td", "th", "ul", "ol", "li"].includes(tag)) {
5568
+ extractBlocks(Array.from(item.children));
5569
+ } else {
5570
+ const svgs = item.querySelectorAll("svg, img");
5571
+ svgs.forEach((s) => {
5572
+ const el = s;
5573
+ el.style.maxWidth = "100%";
5574
+ el.style.maxHeight = "360px";
5575
+ el.style.display = "block";
5576
+ el.style.margin = "12px auto";
5577
+ if (s.tagName.toLowerCase() === "svg") {
5578
+ s.removeAttribute("width");
5579
+ s.removeAttribute("height");
5580
+ el.style.width = "100%";
5581
+ el.style.maxHeight = "360px";
5582
+ }
5583
+ });
5584
+ if (tag === "p" || tag === "div") {
5585
+ item.style.display = "block";
5586
+ item.style.marginBottom = "12px";
5587
+ item.style.lineHeight = "1.6";
5588
+ }
5589
+ contentNodes.push(item);
5590
+ }
4641
5591
  }
4642
- }
5592
+ };
5593
+ extractBlocks(htmlElements);
5594
+ tables.forEach((table) => {
5595
+ const tableEl = renderRtfTableElement(table);
5596
+ let inserted = false;
5597
+ if (table.anchorBefore && table.anchorBefore.length > 10) {
5598
+ const keyword = table.anchorBefore.slice(-35).trim();
5599
+ for (let i = 0; i < contentNodes.length; i++) {
5600
+ if (contentNodes[i].textContent?.includes(keyword)) {
5601
+ contentNodes.splice(i + 1, 0, tableEl);
5602
+ inserted = true;
5603
+ break;
5604
+ }
5605
+ }
5606
+ }
5607
+ if (!inserted && table.anchorAfter && table.anchorAfter.length > 10) {
5608
+ const keyword = table.anchorAfter.slice(0, 35).trim();
5609
+ for (let i = 0; i < contentNodes.length; i++) {
5610
+ if (contentNodes[i].textContent?.includes(keyword)) {
5611
+ contentNodes.splice(i, 0, tableEl);
5612
+ inserted = true;
5613
+ break;
5614
+ }
5615
+ }
5616
+ }
5617
+ if (!inserted) {
5618
+ if (contentNodes.length > 2) {
5619
+ contentNodes.splice(2, 0, tableEl);
5620
+ } else {
5621
+ contentNodes.push(tableEl);
5622
+ }
5623
+ }
5624
+ });
4643
5625
  wrapper.innerHTML = "";
4644
5626
  contentNodes.forEach((node) => wrapper.appendChild(node));
4645
5627
  const childHeights = contentNodes.map((c) => {
4646
5628
  const rectH = c.getBoundingClientRect ? c.getBoundingClientRect().height : 0;
4647
5629
  const offH = c.offsetHeight || 0;
5630
+ const realH = Math.max(rectH, offH);
5631
+ if (realH > 0) return realH;
4648
5632
  const textLen = c.textContent?.trim().length || 0;
4649
- const estH = Math.max(24, Math.ceil(textLen / 75) * 22 + 14);
4650
- return Math.max(rectH, offH, estH);
5633
+ return Math.max(24, Math.ceil(textLen / 95) * 22 + 12);
4651
5634
  });
4652
5635
  wrapper.innerHTML = "";
4653
5636
  const createRtfCard = () => {
@@ -4656,35 +5639,49 @@ var RtfPlugin = class {
4656
5639
  card.style.backgroundColor = "#ffffff";
4657
5640
  card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
4658
5641
  card.style.borderRadius = "4px";
4659
- card.style.padding = "72px 56px";
5642
+ card.style.padding = "64px 56px";
4660
5643
  card.style.width = "816px";
4661
5644
  card.style.minHeight = "1056px";
4662
5645
  card.style.boxSizing = "border-box";
4663
5646
  card.style.marginBottom = "24px";
4664
5647
  card.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4665
5648
  card.style.lineHeight = "1.6";
5649
+ card.style.color = "#1e293b";
4666
5650
  return card;
4667
5651
  };
4668
5652
  let curCard = createRtfCard();
4669
5653
  wrapper.appendChild(curCard);
4670
5654
  pageElements = [curCard];
4671
5655
  let curH = 0;
4672
- const maxH = 912;
5656
+ const maxH = 928;
4673
5657
  for (let i = 0; i < contentNodes.length; i++) {
4674
5658
  const child = contentNodes[i];
4675
5659
  const chH = childHeights[i];
4676
- curCard.appendChild(child);
4677
- curH += chH;
4678
- if (curH >= maxH && i < contentNodes.length - 1) {
5660
+ const isChildEmpty = (child.textContent || "").trim().length === 0 && !child.querySelector("table, img, svg, canvas");
5661
+ if (curH === 0 && isChildEmpty) {
5662
+ continue;
5663
+ }
5664
+ if (curH + chH > maxH && curCard.childNodes.length > 0) {
4679
5665
  curCard = createRtfCard();
4680
5666
  wrapper.appendChild(curCard);
4681
5667
  pageElements.push(curCard);
4682
5668
  curH = 0;
4683
5669
  }
5670
+ curCard.appendChild(child);
5671
+ curH += chH;
5672
+ }
5673
+ pageElements = pageElements.filter((card) => {
5674
+ const hasText = (card.textContent || "").trim().length > 0;
5675
+ const hasMedia = card.querySelector("table, img, svg, canvas") !== null;
5676
+ return hasText || hasMedia;
5677
+ });
5678
+ if (pageElements.length === 0) {
5679
+ pageElements = [curCard];
4684
5680
  }
4685
5681
  } catch (err) {
4686
5682
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
4687
5683
  const text = new TextDecoder("latin1").decode(ctx.buffer);
5684
+ const tables = parseRtfTables(text);
4688
5685
  const rawPages = text.split(/\\page\b/).map((segment) => {
4689
5686
  return segment.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "").trim();
4690
5687
  }).filter((p) => p.length > 0);
@@ -4695,47 +5692,26 @@ var RtfPlugin = class {
4695
5692
  pageCard.style.backgroundColor = "#ffffff";
4696
5693
  pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4697
5694
  pageCard.style.borderRadius = "4px";
4698
- pageCard.style.padding = "72px 56px";
5695
+ pageCard.style.padding = "64px 56px";
4699
5696
  pageCard.style.width = "816px";
4700
5697
  pageCard.style.minHeight = "1056px";
4701
- pageCard.style.maxWidth = "100%";
4702
5698
  pageCard.style.boxSizing = "border-box";
4703
- pageCard.style.fontFamily = "serif";
5699
+ pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
4704
5700
  pageCard.style.fontSize = "12pt";
4705
5701
  pageCard.style.lineHeight = "1.6";
4706
5702
  pageCard.style.color = "#1e293b";
4707
5703
  pageCard.style.whiteSpace = "pre-wrap";
4708
5704
  pageCard.style.display = i === 0 ? "block" : "none";
4709
5705
  pageCard.textContent = pages[i];
5706
+ if (i === 1 && tables.length > 0) {
5707
+ tables.forEach((tbl) => pageCard.appendChild(renderRtfTableElement(tbl)));
5708
+ }
4710
5709
  wrapper.appendChild(pageCard);
4711
5710
  pageElements.push(pageCard);
4712
5711
  }
4713
5712
  }
4714
5713
  const totalPages = Math.max(1, pageElements.length);
4715
5714
  let currentPage = 1;
4716
- let indicator = null;
4717
- if (totalPages > 1) {
4718
- indicator = document.createElement("div");
4719
- indicator.className = "fp-rtf-page-indicator";
4720
- indicator.style.position = "sticky";
4721
- indicator.style.bottom = "16px";
4722
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4723
- indicator.style.backdropFilter = "blur(8px)";
4724
- indicator.style.color = "#f8fafc";
4725
- indicator.style.fontSize = "12px";
4726
- indicator.style.fontWeight = "600";
4727
- indicator.style.padding = "5px 14px";
4728
- indicator.style.borderRadius = "20px";
4729
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4730
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4731
- indicator.style.zIndex = "10";
4732
- indicator.style.userSelect = "none";
4733
- indicator.style.pointerEvents = "none";
4734
- indicator.style.textAlign = "center";
4735
- indicator.style.width = "fit-content";
4736
- indicator.style.margin = "16px auto 0";
4737
- ctx.container.appendChild(indicator);
4738
- }
4739
5715
  const showPage = (pageNum) => {
4740
5716
  currentPage = Math.max(1, Math.min(totalPages, pageNum));
4741
5717
  if (totalPages > 1) {
@@ -4743,9 +5719,6 @@ var RtfPlugin = class {
4743
5719
  el.style.display = idx + 1 === currentPage ? "block" : "none";
4744
5720
  });
4745
5721
  }
4746
- if (indicator) {
4747
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4748
- }
4749
5722
  ctx.container.scrollTop = 0;
4750
5723
  ctx.emit("page-change", { page: currentPage, total: totalPages });
4751
5724
  };
@@ -4753,7 +5726,7 @@ var RtfPlugin = class {
4753
5726
  showPage(1);
4754
5727
  }
4755
5728
  const cleanup = () => {
4756
- indicator?.remove();
5729
+ resizeObserver?.disconnect();
4757
5730
  wrapper.remove();
4758
5731
  ctx.container.innerHTML = "";
4759
5732
  };
@@ -4763,22 +5736,52 @@ var RtfPlugin = class {
4763
5736
  getPageCount: () => totalPages,
4764
5737
  getCurrentPage: () => currentPage,
4765
5738
  goToPage: (page) => showPage(page),
5739
+ getThumbnails: async () => {
5740
+ const items = [];
5741
+ for (let idx = 0; idx < totalPages; idx++) {
5742
+ const pageIdx = idx;
5743
+ items.push({
5744
+ index: pageIdx,
5745
+ label: `Page ${pageIdx + 1}`,
5746
+ render: async (canvas) => {
5747
+ const ctx2d = canvas.getContext("2d");
5748
+ if (!ctx2d) return;
5749
+ canvas.width = 140;
5750
+ canvas.height = 180;
5751
+ ctx2d.fillStyle = "#ffffff";
5752
+ ctx2d.fillRect(0, 0, 140, 180);
5753
+ ctx2d.strokeStyle = "#cbd5e1";
5754
+ ctx2d.lineWidth = 1;
5755
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
5756
+ ctx2d.fillStyle = "#334155";
5757
+ ctx2d.font = 'bold 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
5758
+ ctx2d.textAlign = "center";
5759
+ ctx2d.fillText(`Page ${pageIdx + 1}`, 70, 95);
5760
+ }
5761
+ });
5762
+ }
5763
+ return items;
5764
+ },
4766
5765
  zoomIn: () => {
5766
+ isUserZoomed = true;
4767
5767
  scale += 0.15;
4768
5768
  applyTransform();
4769
5769
  },
4770
5770
  zoomOut: () => {
5771
+ isUserZoomed = true;
4771
5772
  scale = Math.max(0.2, scale - 0.15);
4772
5773
  applyTransform();
4773
5774
  },
4774
5775
  getZoom: () => scale,
4775
5776
  setZoom: (level) => {
5777
+ isUserZoomed = true;
4776
5778
  scale = level;
4777
5779
  applyTransform();
4778
5780
  },
4779
5781
  fitToPage: () => {
4780
- fitMode = fitMode === "width" ? "page" : "width";
4781
- scale = calculateFitScale(fitMode);
5782
+ isUserZoomed = false;
5783
+ fitMode = "width";
5784
+ scale = calculateFitScale("width");
4782
5785
  rotation = 0;
4783
5786
  applyTransform();
4784
5787
  },
@@ -4966,40 +5969,36 @@ var OpenDocumentPlugin = class {
4966
5969
  const isPresentation = instance.isPresentation;
4967
5970
  const totalPages = instance.getPageCount?.() ?? 1;
4968
5971
  const actions = [];
4969
- if (isPresentation || totalPages > 1) {
4970
- if (isPresentation) {
4971
- actions.push({
4972
- id: "thumbnails",
4973
- icon: "thumbnails",
4974
- label: "Slide Thumbnails",
4975
- type: "button",
4976
- group: "navigation",
4977
- execute: () => instance.toggleThumbnails?.()
4978
- });
4979
- }
4980
- actions.push({
4981
- id: "page-nav",
4982
- icon: "",
4983
- label: isPresentation ? "Slide Navigation" : "Page Navigation",
4984
- type: "page-nav",
4985
- group: "navigation",
4986
- value: instance.getCurrentPage?.() ?? 1,
4987
- max: totalPages,
4988
- execute: (action, page) => {
4989
- const cur = instance.getCurrentPage?.() ?? 1;
4990
- const max = instance.getPageCount?.() ?? 1;
4991
- if (action === "prev") {
4992
- if (cur > 1) instance.goToPage?.(cur - 1);
4993
- } else if (action === "next") {
4994
- if (cur < max) instance.goToPage?.(cur + 1);
4995
- } else if (typeof page === "number") {
4996
- instance.goToPage?.(page);
4997
- } else if (typeof action === "number") {
4998
- instance.goToPage?.(action);
4999
- }
5972
+ actions.push({
5973
+ id: "thumbnails",
5974
+ icon: "thumbnails",
5975
+ label: isPresentation ? "Slide Thumbnails" : "Page Thumbnails",
5976
+ type: "button",
5977
+ group: "navigation",
5978
+ execute: () => instance.toggleThumbnails?.()
5979
+ });
5980
+ actions.push({
5981
+ id: "page-nav",
5982
+ icon: "",
5983
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
5984
+ type: "page-nav",
5985
+ group: "navigation",
5986
+ value: instance.getCurrentPage?.() ?? 1,
5987
+ max: totalPages,
5988
+ execute: (action, page) => {
5989
+ const cur = instance.getCurrentPage?.() ?? 1;
5990
+ const max = instance.getPageCount?.() ?? 1;
5991
+ if (action === "prev") {
5992
+ if (cur > 1) instance.goToPage?.(cur - 1);
5993
+ } else if (action === "next") {
5994
+ if (cur < max) instance.goToPage?.(cur + 1);
5995
+ } else if (typeof page === "number") {
5996
+ instance.goToPage?.(page);
5997
+ } else if (typeof action === "number") {
5998
+ instance.goToPage?.(action);
5000
5999
  }
5001
- });
5002
- }
6000
+ }
6001
+ });
5003
6002
  actions.push(
5004
6003
  {
5005
6004
  id: "zoom-out",
@@ -5090,49 +6089,65 @@ var OpenDocumentPlugin = class {
5090
6089
  container.className = "fp-odf-container";
5091
6090
  container.style.width = "100%";
5092
6091
  container.style.height = "100%";
5093
- container.style.overflow = "auto";
5094
- container.style.padding = "24px";
6092
+ container.style.overflowX = "hidden";
6093
+ container.style.overflowY = "auto";
6094
+ container.style.padding = "16px 8px";
5095
6095
  container.style.backgroundColor = "#f1f5f9";
5096
6096
  const wrapper = document.createElement("div");
5097
6097
  wrapper.className = "fp-odf-wrapper";
5098
6098
  wrapper.style.margin = "0 auto";
6099
+ wrapper.style.width = isPresentation ? "960px" : "816px";
6100
+ wrapper.style.boxSizing = "border-box";
5099
6101
  wrapper.style.backgroundColor = "#ffffff";
5100
6102
  wrapper.style.boxShadow = "0 2px 10px rgba(0,0,0,0.08)";
5101
6103
  wrapper.style.borderRadius = "4px";
5102
6104
  wrapper.style.transformOrigin = "top center";
5103
- wrapper.style.transition = "transform 0.2s ease";
6105
+ wrapper.style.transition = "transform 0.15s ease";
5104
6106
  container.appendChild(wrapper);
5105
6107
  ctx.container.appendChild(container);
5106
6108
  let scale = 1;
5107
6109
  let rotation = 0;
5108
- let fitMode = "width";
6110
+ let isUserZoomed = false;
6111
+ let fitMode = ctx?.options?.fitMode || "width";
6112
+ let currentPage = 1;
6113
+ let totalPages = 1;
6114
+ let slides = [];
5109
6115
  const calculateFitScale = (mode = fitMode) => {
5110
- const elW = wrapper.offsetWidth || 850;
5111
- const singlePageH = Math.min(wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
5112
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5113
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6116
+ const activeSlide = slides[currentPage - 1];
6117
+ const elW = isPresentation ? 960 : 816;
6118
+ const singlePageH = Math.min(activeSlide?.offsetHeight || wrapper.offsetHeight || 1056, Math.round(elW * 1.32));
6119
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6120
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5114
6121
  const sW = availW / elW;
5115
6122
  const sH = availH / (isPresentation ? 540 : singlePageH);
5116
6123
  if (isPresentation) {
5117
- return Math.min(1, Math.min(sW, sH));
6124
+ return Math.min(2.5, Math.min(sW, sH));
5118
6125
  }
5119
6126
  if (mode === "page") {
5120
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6127
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5121
6128
  }
5122
- return Math.max(0.65, Math.min(1.05, sW));
6129
+ return Math.max(0.4, Math.min(3, sW));
5123
6130
  };
5124
6131
  const applyTransform = () => {
5125
6132
  wrapper.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
5126
6133
  wrapper.style.transformOrigin = "top center";
6134
+ const activeSlide = slides[currentPage - 1];
6135
+ const baseH = activeSlide?.offsetHeight || wrapper.offsetHeight || 1056;
6136
+ const scaledH = baseH * scale;
6137
+ const extraH = Math.max(0, scaledH - baseH);
6138
+ wrapper.style.marginBottom = `${extraH + 32}px`;
5127
6139
  };
6140
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6141
+ if (!isUserZoomed) {
6142
+ scale = calculateFitScale(fitMode);
6143
+ applyTransform();
6144
+ }
6145
+ }) : null;
6146
+ resizeObserver?.observe(ctx.container);
5128
6147
  setTimeout(() => {
5129
- fitMode = "width";
5130
- scale = calculateFitScale("width");
6148
+ scale = calculateFitScale(fitMode);
5131
6149
  applyTransform();
5132
6150
  }, 60);
5133
- let currentPage = 1;
5134
- let totalPages = 1;
5135
- let slides = [];
5136
6151
  if (isPresentation) {
5137
6152
  wrapper.style.maxWidth = "960px";
5138
6153
  wrapper.style.aspectRatio = "16 / 9";
@@ -5233,24 +6248,9 @@ var OpenDocumentPlugin = class {
5233
6248
  wrapper.appendChild(page);
5234
6249
  slides.push(page);
5235
6250
  });
5236
- const pageIndicator = document.createElement("div");
5237
- pageIndicator.className = "fp-odt-page-indicator";
5238
- pageIndicator.style.position = "sticky";
5239
- pageIndicator.style.bottom = "16px";
5240
- pageIndicator.style.left = "50%";
5241
- pageIndicator.style.transform = "translateX(-50%)";
5242
- pageIndicator.style.backgroundColor = "rgba(0, 0, 0, 0.6)";
5243
- pageIndicator.style.color = "#fff";
5244
- pageIndicator.style.padding = "6px 12px";
5245
- pageIndicator.style.borderRadius = "16px";
5246
- pageIndicator.style.fontSize = "12px";
5247
- pageIndicator.style.zIndex = "100";
5248
- pageIndicator.style.display = "inline-block";
5249
- pageIndicator.style.width = "fit-content";
5250
- pageIndicator.textContent = `Page 1 of ${totalPages}`;
5251
- container.appendChild(pageIndicator);
5252
6251
  }
5253
6252
  const cleanup = () => {
6253
+ resizeObserver?.disconnect();
5254
6254
  imageUrls.forEach((url) => URL.revokeObjectURL(url));
5255
6255
  container.remove();
5256
6256
  ctx.container.innerHTML = "";
@@ -5262,10 +6262,6 @@ var OpenDocumentPlugin = class {
5262
6262
  slides.forEach((s, idx) => {
5263
6263
  s.style.display = idx === page - 1 ? "block" : "none";
5264
6264
  });
5265
- const indicator = container.querySelector(".fp-odt-page-indicator");
5266
- if (indicator) {
5267
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5268
- }
5269
6265
  container.scrollTop = 0;
5270
6266
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5271
6267
  };
@@ -5273,21 +6269,25 @@ var OpenDocumentPlugin = class {
5273
6269
  destroy: cleanup,
5274
6270
  isPresentation,
5275
6271
  zoomIn: () => {
6272
+ isUserZoomed = true;
5276
6273
  scale += 0.15;
5277
6274
  applyTransform();
5278
6275
  },
5279
6276
  zoomOut: () => {
6277
+ isUserZoomed = true;
5280
6278
  scale = Math.max(0.2, scale - 0.15);
5281
6279
  applyTransform();
5282
6280
  },
5283
6281
  getZoom: () => scale,
5284
6282
  setZoom: (level) => {
6283
+ isUserZoomed = true;
5285
6284
  scale = level;
5286
6285
  applyTransform();
5287
6286
  },
5288
6287
  fitToPage: () => {
5289
- fitMode = fitMode === "width" ? "page" : "width";
5290
- scale = calculateFitScale(fitMode);
6288
+ isUserZoomed = false;
6289
+ fitMode = "width";
6290
+ scale = calculateFitScale("width");
5291
6291
  rotation = 0;
5292
6292
  applyTransform();
5293
6293
  },
@@ -5303,23 +6303,45 @@ var OpenDocumentPlugin = class {
5303
6303
  getPageCount: () => totalPages,
5304
6304
  getCurrentPage: () => currentPage,
5305
6305
  getThumbnails: async () => {
5306
- if (!isPresentation) return [];
5307
- return slides.map((_, idx) => ({
5308
- index: idx,
5309
- label: `Slide ${idx + 1}`,
5310
- render: async (canvas) => {
5311
- const ctx2d = canvas.getContext("2d");
5312
- if (!ctx2d) return;
5313
- canvas.width = 160;
5314
- canvas.height = 90;
5315
- ctx2d.fillStyle = "#f8fafc";
5316
- ctx2d.fillRect(0, 0, 160, 90);
5317
- ctx2d.fillStyle = "#334155";
5318
- ctx2d.font = "bold 12px sans-serif";
5319
- ctx2d.textAlign = "center";
5320
- ctx2d.fillText(`Slide ${idx + 1}`, 80, 50);
5321
- }
5322
- }));
6306
+ const count = totalPages || slides.length || 1;
6307
+ const items = [];
6308
+ for (let idx = 0; idx < count; idx++) {
6309
+ const itemIdx = idx;
6310
+ items.push({
6311
+ index: itemIdx,
6312
+ label: isPresentation ? `Slide ${itemIdx + 1}` : `Page ${itemIdx + 1}`,
6313
+ render: async (canvas) => {
6314
+ const ctx2d = canvas.getContext("2d");
6315
+ if (!ctx2d) return;
6316
+ if (isPresentation) {
6317
+ canvas.width = 160;
6318
+ canvas.height = 90;
6319
+ ctx2d.fillStyle = "#f8fafc";
6320
+ ctx2d.fillRect(0, 0, 160, 90);
6321
+ ctx2d.strokeStyle = "#cbd5e1";
6322
+ ctx2d.lineWidth = 1;
6323
+ ctx2d.strokeRect(0.5, 0.5, 159, 89);
6324
+ ctx2d.fillStyle = "#334155";
6325
+ ctx2d.font = "bold 11px sans-serif";
6326
+ ctx2d.textAlign = "center";
6327
+ ctx2d.fillText(`Slide ${itemIdx + 1}`, 80, 50);
6328
+ } else {
6329
+ canvas.width = 140;
6330
+ canvas.height = 180;
6331
+ ctx2d.fillStyle = "#ffffff";
6332
+ ctx2d.fillRect(0, 0, 140, 180);
6333
+ ctx2d.strokeStyle = "#cbd5e1";
6334
+ ctx2d.lineWidth = 1;
6335
+ ctx2d.strokeRect(0.5, 0.5, 139, 179);
6336
+ ctx2d.fillStyle = "#64748b";
6337
+ ctx2d.font = "bold 10px sans-serif";
6338
+ ctx2d.textAlign = "center";
6339
+ ctx2d.fillText(`Page ${itemIdx + 1}`, 70, 90);
6340
+ }
6341
+ }
6342
+ });
6343
+ }
6344
+ return items;
5323
6345
  },
5324
6346
  download: () => {
5325
6347
  const mimeType = this.mimeTypes.find((m) => m.includes(ext.replace(".", ""))) || "application/octet-stream";
@@ -5424,23 +6446,24 @@ var OpenDocumentPlugin = class {
5424
6446
  case "h": {
5425
6447
  const level = Math.min(6, Math.max(1, Number(node.getAttribute("text:outline-level") || 1)));
5426
6448
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5427
- html += `<h${level} style="${style}; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${node.textContent || ""}</h${level}>`;
6449
+ html += `<h${level} style="${style}; margin: 18px 0 8px; color: #1e3a8a; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${node.textContent || ""}</h${level}>`;
5428
6450
  break;
5429
6451
  }
5430
6452
  case "p": {
5431
6453
  const style = styles.get(node.getAttribute("text:style-name") || "") || "";
5432
6454
  const inner = this.renderSpans(node, styles, images);
5433
- html += `<p style="${style}; line-height: 1.6; margin: 8px 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${inner}</p>`;
6455
+ const isInsideLi = node.parentElement?.localName === "list-item" || node.parentElement?.tagName?.toLowerCase() === "list-item";
6456
+ html += `<p style="${style}; line-height: 1.5; margin: ${isInsideLi ? "0" : "8px 0 12px"}; font-family: Calibri, 'Segoe UI', Arial, sans-serif;">${inner}</p>`;
5434
6457
  break;
5435
6458
  }
5436
6459
  case "list": {
5437
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
6460
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
5438
6461
  Array.from(node.children).forEach((c) => walk(c));
5439
6462
  html += "</ul>";
5440
6463
  break;
5441
6464
  }
5442
6465
  case "list-item": {
5443
- html += "<li>";
6466
+ html += '<li style="margin: 4px 0; font-size: 11pt; line-height: 1.5; color: #1e293b;">';
5444
6467
  Array.from(node.children).forEach((c) => walk(c));
5445
6468
  html += "</li>";
5446
6469
  break;
@@ -5566,28 +6589,26 @@ var DocPlugin = class {
5566
6589
  getToolbarActions(instance) {
5567
6590
  const totalPages = instance.getPageCount?.() ?? 1;
5568
6591
  const actions = [];
5569
- if (totalPages > 1) {
5570
- actions.push({
5571
- id: "page-nav",
5572
- icon: "",
5573
- label: "Page Navigation",
5574
- type: "page-nav",
5575
- group: "navigation",
5576
- value: instance.getCurrentPage?.() ?? 1,
5577
- max: totalPages,
5578
- execute: (action, page) => {
5579
- const cur = instance.getCurrentPage?.() ?? 1;
5580
- const max = instance.getPageCount?.() ?? 1;
5581
- if (action === "prev") {
5582
- if (cur > 1) instance.goToPage?.(cur - 1);
5583
- } else if (action === "next") {
5584
- if (cur < max) instance.goToPage?.(cur + 1);
5585
- } else if (typeof page === "number") {
5586
- instance.goToPage?.(page);
5587
- }
6592
+ actions.push({
6593
+ id: "page-nav",
6594
+ icon: "",
6595
+ label: "Page Navigation",
6596
+ type: "page-nav",
6597
+ group: "navigation",
6598
+ value: instance.getCurrentPage?.() ?? 1,
6599
+ max: totalPages,
6600
+ execute: (action, page) => {
6601
+ const cur = instance.getCurrentPage?.() ?? 1;
6602
+ const max = instance.getPageCount?.() ?? 1;
6603
+ if (action === "prev") {
6604
+ if (cur > 1) instance.goToPage?.(cur - 1);
6605
+ } else if (action === "next") {
6606
+ if (cur < max) instance.goToPage?.(cur + 1);
6607
+ } else if (typeof page === "number") {
6608
+ instance.goToPage?.(page);
5588
6609
  }
5589
- });
5590
- }
6610
+ }
6611
+ });
5591
6612
  actions.push(
5592
6613
  {
5593
6614
  id: "zoom-out",
@@ -5661,8 +6682,9 @@ var DocPlugin = class {
5661
6682
  container.className = "fp-doc-container";
5662
6683
  container.style.width = "100%";
5663
6684
  container.style.height = "100%";
5664
- container.style.overflow = "auto";
5665
- container.style.padding = "32px 16px";
6685
+ container.style.overflowX = "hidden";
6686
+ container.style.overflowY = "auto";
6687
+ container.style.padding = "16px 8px";
5666
6688
  container.style.backgroundColor = "#f1f5f9";
5667
6689
  container.style.display = "flex";
5668
6690
  container.style.justifyContent = "center";
@@ -5707,16 +6729,15 @@ var DocPlugin = class {
5707
6729
  const pageCard = document.createElement("div");
5708
6730
  pageCard.className = "fp-doc-page-card";
5709
6731
  pageCard.style.width = "816px";
5710
- pageCard.style.height = "1056px";
6732
+ pageCard.style.minHeight = "1056px";
5711
6733
  pageCard.style.backgroundColor = "#ffffff";
5712
- pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
6734
+ pageCard.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
5713
6735
  pageCard.style.borderRadius = "4px";
5714
- pageCard.style.padding = "96px 72px";
6736
+ pageCard.style.padding = "72px 56px";
5715
6737
  pageCard.style.boxSizing = "border-box";
5716
- pageCard.style.overflow = "hidden";
5717
6738
  pageCard.style.display = i === 0 ? "block" : "none";
5718
6739
  pageCard.style.transformOrigin = "top center";
5719
- pageCard.style.transition = "transform 0.2s ease";
6740
+ pageCard.style.transition = "transform 0.15s ease";
5720
6741
  pageCard.style.fontFamily = 'Calibri, "Segoe UI", Arial, sans-serif';
5721
6742
  pageCard.style.color = "#1e293b";
5722
6743
  if (isFallback && i === 0) {
@@ -5733,34 +6754,15 @@ var DocPlugin = class {
5733
6754
  container.appendChild(pageCard);
5734
6755
  pageCards.push(pageCard);
5735
6756
  }
5736
- let indicator = null;
5737
- if (totalPages > 1) {
5738
- indicator = document.createElement("div");
5739
- indicator.className = "fp-doc-page-indicator";
5740
- indicator.style.position = "fixed";
5741
- indicator.style.bottom = "16px";
5742
- indicator.style.left = "50%";
5743
- indicator.style.transform = "translateX(-50%)";
5744
- indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
5745
- indicator.style.backdropFilter = "blur(8px)";
5746
- indicator.style.color = "#f8fafc";
5747
- indicator.style.fontSize = "12px";
5748
- indicator.style.fontWeight = "600";
5749
- indicator.style.padding = "5px 14px";
5750
- indicator.style.borderRadius = "20px";
5751
- indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
5752
- indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
5753
- indicator.style.zIndex = "10";
5754
- indicator.style.userSelect = "none";
5755
- indicator.style.pointerEvents = "none";
5756
- indicator.style.textAlign = "center";
5757
- container.appendChild(indicator);
5758
- }
5759
6757
  let rotation = 0;
5760
6758
  const applyTransform = () => {
5761
6759
  const activeCard = pageCards[currentPage - 1];
5762
6760
  if (activeCard) {
5763
6761
  activeCard.style.transform = `scale(${scale}) rotate(${rotation}deg)`;
6762
+ const baseH = activeCard.offsetHeight || 1056;
6763
+ const scaledH = baseH * scale;
6764
+ const extraH = Math.max(0, scaledH - baseH);
6765
+ activeCard.style.marginBottom = `${extraH + 32}px`;
5764
6766
  }
5765
6767
  };
5766
6768
  const showPage = (pageNum) => {
@@ -5773,35 +6775,39 @@ var DocPlugin = class {
5773
6775
  card.style.display = "none";
5774
6776
  }
5775
6777
  });
5776
- if (indicator) {
5777
- indicator.textContent = `Page ${currentPage} of ${totalPages}`;
5778
- }
5779
6778
  container.scrollTop = 0;
5780
6779
  ctx.emit("page-change", { page: currentPage, total: totalPages });
5781
6780
  };
5782
6781
  if (totalPages > 1) {
5783
6782
  showPage(1);
5784
6783
  }
5785
- let fitMode = "width";
6784
+ let fitMode = ctx?.options?.fitMode || "width";
6785
+ let isUserZoomed = false;
5786
6786
  const calculateFitScale = (mode = fitMode) => {
5787
6787
  const elW = 816;
5788
6788
  const elH = 1056;
5789
- const availW = Math.max(280, ctx.container.clientWidth - 48);
5790
- const availH = Math.max(280, ctx.container.clientHeight - 80);
6789
+ const availW = Math.max(280, ctx.container.clientWidth - 32);
6790
+ const availH = Math.max(280, ctx.container.clientHeight - 32);
5791
6791
  const sW = availW / elW;
5792
6792
  const sH = availH / elH;
5793
6793
  if (mode === "page") {
5794
- return Math.max(0.55, Math.min(1.15, Math.min(sW, sH)));
6794
+ return Math.max(0.35, Math.min(3, Math.min(sW, sH)));
5795
6795
  }
5796
- return Math.max(0.65, Math.min(1.05, sW));
6796
+ return Math.max(0.4, Math.min(3, sW));
5797
6797
  };
6798
+ const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
6799
+ if (!isUserZoomed) {
6800
+ scale = calculateFitScale(fitMode);
6801
+ applyTransform();
6802
+ }
6803
+ }) : null;
6804
+ resizeObserver?.observe(ctx.container);
5798
6805
  setTimeout(() => {
5799
- fitMode = "width";
5800
- scale = calculateFitScale("width");
6806
+ scale = calculateFitScale(fitMode);
5801
6807
  applyTransform();
5802
6808
  }, 60);
5803
6809
  const cleanup = () => {
5804
- indicator?.remove();
6810
+ resizeObserver?.disconnect();
5805
6811
  container.remove();
5806
6812
  ctx.container.innerHTML = "";
5807
6813
  };
@@ -5812,21 +6818,25 @@ var DocPlugin = class {
5812
6818
  getCurrentPage: () => currentPage,
5813
6819
  goToPage: (page) => showPage(page),
5814
6820
  zoomIn: () => {
6821
+ isUserZoomed = true;
5815
6822
  scale += 0.15;
5816
6823
  applyTransform();
5817
6824
  },
5818
6825
  zoomOut: () => {
6826
+ isUserZoomed = true;
5819
6827
  scale = Math.max(0.2, scale - 0.15);
5820
6828
  applyTransform();
5821
6829
  },
5822
6830
  getZoom: () => scale,
5823
6831
  setZoom: (level) => {
6832
+ isUserZoomed = true;
5824
6833
  scale = level;
5825
6834
  applyTransform();
5826
6835
  },
5827
6836
  fitToPage: () => {
5828
- fitMode = fitMode === "width" ? "page" : "width";
5829
- scale = calculateFitScale(fitMode);
6837
+ isUserZoomed = false;
6838
+ fitMode = "width";
6839
+ scale = calculateFitScale("width");
5830
6840
  rotation = 0;
5831
6841
  applyTransform();
5832
6842
  },
@@ -6203,25 +7213,31 @@ ${chartSvg}
6203
7213
  i++;
6204
7214
  continue;
6205
7215
  }
6206
- if (line.length < 60 && !line.endsWith(".") && (/^[A-Z0-9\s:_-]+$/.test(line) || line.startsWith("#"))) {
7216
+ if (/^\d{1,2}$/.test(line.trim())) {
7217
+ i++;
7218
+ continue;
7219
+ }
7220
+ const isShortLine = line.length < 60 && !line.endsWith(".") && !line.endsWith(",");
7221
+ 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));
7222
+ if (isTitleLike) {
6207
7223
  if (inList) {
6208
7224
  html += "</ul>";
6209
7225
  inList = false;
6210
7226
  }
6211
- html += `<h2 style="font-size: 16px; font-weight: 700; color: #1e3a8a; margin: 16px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
7227
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 18px 0 10px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify6.sanitize(line, sanitizeOptions)}</h2>`;
6212
7228
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
6213
7229
  if (!inList) {
6214
- html += '<ul style="margin: 8px 0; padding-left: 24px;">';
7230
+ html += '<ul style="margin: 8px 0 12px; padding-left: 24px;">';
6215
7231
  inList = true;
6216
7232
  }
6217
7233
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
6218
- html += `<li style="margin: 4px 0; line-height: 1.15; font-size: 12pt;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
7234
+ html += `<li style="margin: 4px 0; line-height: 1.5; font-size: 11pt; color: #1e293b;">${DOMPurify6.sanitize(bulletText, sanitizeOptions)}</li>`;
6219
7235
  } else {
6220
7236
  if (inList) {
6221
7237
  html += "</ul>";
6222
7238
  inList = false;
6223
7239
  }
6224
- html += `<p style="line-height: 1.15; margin: 0; font-size: 12pt; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
7240
+ html += `<p style="line-height: 1.5; margin: 0 0 12px 0; font-size: 11pt; color: #1e293b; text-align: justify;">${DOMPurify6.sanitize(line, sanitizeOptions)}</p>`;
6225
7241
  }
6226
7242
  i++;
6227
7243
  }
@@ -6791,7 +7807,7 @@ var FilePreview = defineComponent({
6791
7807
  default: () => ({})
6792
7808
  }
6793
7809
  },
6794
- emits: ["loading", "loaded", "error", "page-change", "zoom-change"],
7810
+ emits: ["loading", "loaded", "error", "page-change", "zoom-change", "rotate", "destroy"],
6795
7811
  setup(props, { emit, expose }) {
6796
7812
  const containerRef = ref(null);
6797
7813
  let viewer = null;
@@ -6815,6 +7831,8 @@ var FilePreview = defineComponent({
6815
7831
  viewer.on("error", (data) => emit("error", data));
6816
7832
  viewer.on("page-change", (data) => emit("page-change", data));
6817
7833
  viewer.on("zoom-change", (data) => emit("zoom-change", data));
7834
+ viewer.on("rotate", (data) => emit("rotate", data));
7835
+ viewer.on("destroy", (data) => emit("destroy", data));
6818
7836
  renderPreview();
6819
7837
  });
6820
7838
  watch(() => props.src, renderPreview);