@files-preview-app/preview-file 1.2.4 → 1.2.5

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
@@ -1,5 +1,6 @@
1
1
  import { defineComponent, ref, onMounted, watch, onBeforeUnmount, h } from 'vue';
2
2
  import DOMPurify2 from 'dompurify';
3
+ import * as pdfjsLib from 'pdfjs-dist';
3
4
  import * as docx from 'docx-preview';
4
5
  import { unzipSync, strFromU8, unzip } from 'fflate';
5
6
  import * as XLSX from 'xlsx';
@@ -479,11 +480,21 @@ var ToolbarController = class {
479
480
  el;
480
481
  toolbarEl;
481
482
  actions = [];
483
+ pageInputEl = null;
484
+ pageLabelEl = null;
482
485
  constructor(container) {
483
486
  this.el = container;
484
487
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
485
488
  this.el.appendChild(this.toolbarEl);
486
489
  }
490
+ setPage(page, max) {
491
+ if (this.pageInputEl) {
492
+ this.pageInputEl.value = page.toString();
493
+ }
494
+ if (max !== void 0 && this.pageLabelEl) {
495
+ this.pageLabelEl.textContent = ` / ${max}`;
496
+ }
497
+ }
487
498
  update(actions) {
488
499
  this.actions = actions;
489
500
  this.render();
@@ -526,6 +537,7 @@ var ToolbarController = class {
526
537
  if (action.type === "separator") {
527
538
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
528
539
  } else if (action.type === "page-nav") {
540
+ const max = action.max ?? 1;
529
541
  const prevBtn = this.createButton(
530
542
  "prev",
531
543
  ICON_PAGE_PREV,
@@ -544,7 +556,6 @@ var ToolbarController = class {
544
556
  "Next Page",
545
557
  () => {
546
558
  const cur = parseInt(input.value, 10) || 1;
547
- const max = action.max ?? 1;
548
559
  if (cur < max) {
549
560
  input.value = (cur + 1).toString();
550
561
  action.execute("next", cur + 1);
@@ -556,15 +567,18 @@ var ToolbarController = class {
556
567
  type: "number",
557
568
  value: (action.value ?? 1).toString(),
558
569
  min: "1",
559
- max: (action.max ?? 1).toString()
570
+ max: max.toString()
560
571
  });
561
572
  input.addEventListener("change", () => {
562
- const val = parseInt(input.value, 10);
563
- if (!isNaN(val)) {
564
- action.execute("go", val);
565
- }
573
+ let val = parseInt(input.value, 10);
574
+ if (isNaN(val)) val = 1;
575
+ val = Math.max(1, Math.min(max, val));
576
+ input.value = val.toString();
577
+ action.execute("go", val);
566
578
  });
567
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
579
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
580
+ this.pageInputEl = input;
581
+ this.pageLabelEl = label;
568
582
  groupEl.appendChild(prevBtn);
569
583
  groupEl.appendChild(input);
570
584
  groupEl.appendChild(label);
@@ -742,7 +756,13 @@ var FilePreviewViewer = class {
742
756
  buffer,
743
757
  options,
744
758
  signal,
745
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
759
+ emit: (event, payload) => {
760
+ if (event === "page-change" && payload && typeof payload.page === "number") {
761
+ const total = payload.total ?? payload.totalPages;
762
+ this.toolbar?.setPage(payload.page, total);
763
+ }
764
+ this.eventEmitter.emit(event, payload);
765
+ }
746
766
  });
747
767
  this.activeInstance = instance;
748
768
  this.hideLoading();
@@ -776,6 +796,7 @@ var FilePreviewViewer = class {
776
796
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
777
797
  this.thumbnailPanel.update(thumbnails, (index) => {
778
798
  instance.goToPage?.(index + 1);
799
+ this.toolbar?.setPage(index + 1);
779
800
  });
780
801
  if (options.showThumbnails) {
781
802
  this.thumbnailPanel.show();
@@ -901,9 +922,13 @@ var FilePreviewViewer = class {
901
922
  if (e.key === "ArrowRight" || e.key === "PageDown") {
902
923
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
903
924
  this.activeInstance.goToPage?.(cur + 1);
925
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
926
+ this.toolbar?.setPage(nextCur);
904
927
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
905
928
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
906
929
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
930
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
931
+ this.toolbar?.setPage(prevCur);
907
932
  } else if (e.key === "+" || e.key === "=") {
908
933
  this.activeInstance.zoomIn?.();
909
934
  } else if (e.key === "-" || e.key === "_") {
@@ -1169,30 +1194,62 @@ var CfbfReader = class {
1169
1194
  return result.subarray(0, targetSize);
1170
1195
  }
1171
1196
  };
1172
-
1173
- // ../plugins/pdf/dist/index.js
1197
+ if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1198
+ if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1199
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1200
+ }
1201
+ }
1174
1202
  var PdfPlugin = class {
1175
1203
  id = "pdf";
1176
- name = "PDF Preview";
1204
+ name = "PDF Document Preview";
1177
1205
  extensions = [".pdf"];
1178
1206
  mimeTypes = ["application/pdf"];
1179
1207
  weight = 100;
1180
1208
  supports(file) {
1181
1209
  const ext = file.metadata.extension?.toLowerCase();
1182
1210
  const mime = file.metadata.mimeType?.toLowerCase();
1183
- return ext === ".pdf" || mime === "application/pdf";
1211
+ if (ext) return this.extensions.includes(ext);
1212
+ return this.mimeTypes.includes(mime || "");
1184
1213
  }
1185
1214
  getToolbarActions(instance) {
1215
+ const totalPages = instance.getPageCount?.() ?? 1;
1216
+ const curPage = instance.getCurrentPage?.() ?? 1;
1186
1217
  return [
1218
+ {
1219
+ id: "thumbnails",
1220
+ icon: "thumbnails",
1221
+ label: "Page Thumbnails",
1222
+ type: "button",
1223
+ group: "navigation",
1224
+ execute: () => instance.toggleThumbnails?.()
1225
+ },
1226
+ {
1227
+ id: "page-nav",
1228
+ icon: "",
1229
+ label: "Page Navigation",
1230
+ type: "page-nav",
1231
+ group: "navigation",
1232
+ value: curPage,
1233
+ max: totalPages,
1234
+ execute: (action, page) => {
1235
+ const cur = instance.getCurrentPage?.() ?? 1;
1236
+ const max = instance.getPageCount?.() ?? 1;
1237
+ if (action === "prev") {
1238
+ if (cur > 1) instance.goToPage?.(cur - 1);
1239
+ } else if (action === "next") {
1240
+ if (cur < max) instance.goToPage?.(cur + 1);
1241
+ } else if (typeof page === "number") {
1242
+ instance.goToPage?.(page);
1243
+ }
1244
+ }
1245
+ },
1187
1246
  {
1188
1247
  id: "zoom-out",
1189
1248
  icon: "zoom-out",
1190
1249
  label: "Zoom Out",
1191
1250
  type: "button",
1192
1251
  group: "zoom",
1193
- execute: () => {
1194
- instance.zoomOut?.();
1195
- }
1252
+ execute: () => instance.zoomOut?.()
1196
1253
  },
1197
1254
  {
1198
1255
  id: "zoom-in",
@@ -1200,9 +1257,7 @@ var PdfPlugin = class {
1200
1257
  label: "Zoom In",
1201
1258
  type: "button",
1202
1259
  group: "zoom",
1203
- execute: () => {
1204
- instance.zoomIn?.();
1205
- }
1260
+ execute: () => instance.zoomIn?.()
1206
1261
  },
1207
1262
  {
1208
1263
  id: "fit-page",
@@ -1210,39 +1265,23 @@ var PdfPlugin = class {
1210
1265
  label: "Fit to Page",
1211
1266
  type: "button",
1212
1267
  group: "zoom",
1213
- execute: () => {
1214
- instance.fitToPage?.();
1215
- }
1268
+ execute: () => instance.fitToPage?.()
1216
1269
  },
1217
1270
  {
1218
1271
  id: "rotate-cw",
1219
1272
  icon: "rotate-cw",
1220
- label: "Rotate",
1273
+ label: "Rotate Clockwise",
1221
1274
  type: "button",
1222
1275
  group: "view",
1223
- execute: () => {
1224
- instance.rotateCW?.();
1225
- }
1226
- },
1227
- {
1228
- id: "page-nav",
1229
- icon: "page-nav",
1230
- label: "Page Navigation",
1231
- type: "page-nav",
1232
- group: "navigation",
1233
- execute: (page) => {
1234
- if (typeof page === "number") instance.goToPage?.(page);
1235
- }
1276
+ execute: () => instance.rotateCW?.()
1236
1277
  },
1237
1278
  {
1238
1279
  id: "download",
1239
1280
  icon: "download",
1240
- label: "Download",
1281
+ label: "Download PDF",
1241
1282
  type: "button",
1242
1283
  group: "actions",
1243
- execute: () => {
1244
- instance.download?.();
1245
- }
1284
+ execute: () => instance.download?.()
1246
1285
  },
1247
1286
  {
1248
1287
  id: "print",
@@ -1250,99 +1289,213 @@ var PdfPlugin = class {
1250
1289
  label: "Print",
1251
1290
  type: "button",
1252
1291
  group: "actions",
1253
- execute: () => {
1254
- instance.print?.();
1255
- }
1292
+ execute: () => instance.print?.()
1256
1293
  }
1257
1294
  ];
1258
1295
  }
1259
1296
  async render(ctx) {
1260
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1261
- const url = URL.createObjectURL(blob);
1262
- const wrapper = document.createElement("div");
1263
- wrapper.style.width = "100%";
1264
- wrapper.style.height = "100%";
1265
- wrapper.style.overflow = "hidden";
1266
- wrapper.style.display = "flex";
1267
- wrapper.style.justifyContent = "center";
1268
- wrapper.style.alignItems = "center";
1269
- const iframe = document.createElement("iframe");
1270
- iframe.src = url;
1271
- iframe.style.width = "100%";
1272
- iframe.style.height = "100%";
1273
- iframe.style.border = "none";
1274
- wrapper.appendChild(iframe);
1275
- ctx.container.appendChild(wrapper);
1297
+ const container = document.createElement("div");
1298
+ container.className = "fp-pdf-container";
1299
+ container.style.width = "100%";
1300
+ container.style.height = "100%";
1301
+ container.style.overflow = "auto";
1302
+ container.style.display = "flex";
1303
+ container.style.flexDirection = "column";
1304
+ container.style.alignItems = "center";
1305
+ container.style.padding = "24px 16px";
1306
+ container.style.backgroundColor = "#0f172a";
1307
+ container.style.boxSizing = "border-box";
1308
+ container.style.position = "relative";
1309
+ const pageCard = document.createElement("div");
1310
+ pageCard.className = "fp-pdf-page-card";
1311
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1312
+ pageCard.style.backgroundColor = "#ffffff";
1313
+ pageCard.style.borderRadius = "4px";
1314
+ pageCard.style.overflow = "hidden";
1315
+ pageCard.style.lineHeight = "0";
1316
+ pageCard.style.transition = "transform 0.15s ease";
1317
+ pageCard.style.position = "relative";
1318
+ const canvas = document.createElement("canvas");
1319
+ pageCard.appendChild(canvas);
1320
+ container.appendChild(pageCard);
1321
+ const indicator = document.createElement("div");
1322
+ indicator.className = "fp-pdf-page-indicator";
1323
+ indicator.style.position = "sticky";
1324
+ indicator.style.bottom = "16px";
1325
+ indicator.style.marginTop = "16px";
1326
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1327
+ indicator.style.backdropFilter = "blur(8px)";
1328
+ indicator.style.color = "#f8fafc";
1329
+ indicator.style.fontSize = "12px";
1330
+ indicator.style.fontWeight = "600";
1331
+ indicator.style.padding = "5px 14px";
1332
+ indicator.style.borderRadius = "20px";
1333
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1334
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1335
+ indicator.style.zIndex = "10";
1336
+ indicator.style.userSelect = "none";
1337
+ indicator.style.pointerEvents = "none";
1338
+ container.appendChild(indicator);
1339
+ ctx.container.appendChild(container);
1340
+ const loadingTask = pdfjsLib.getDocument({
1341
+ data: new Uint8Array(ctx.buffer),
1342
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1343
+ cMapPacked: true,
1344
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1345
+ });
1346
+ const pdfDoc = await loadingTask.promise;
1347
+ const totalPages = Math.max(1, pdfDoc.numPages);
1276
1348
  let currentPage = 1;
1277
- let currentZoom = 1;
1349
+ let zoomScale = 1;
1278
1350
  let rotation = 0;
1351
+ let currentRenderTask = null;
1352
+ const renderPage = async (pageNum) => {
1353
+ if (currentRenderTask) {
1354
+ try {
1355
+ currentRenderTask.cancel();
1356
+ } catch {
1357
+ }
1358
+ currentRenderTask = null;
1359
+ }
1360
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1361
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1362
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1363
+ const page = await pdfDoc.getPage(currentPage);
1364
+ const containerWidth = container.clientWidth || 900;
1365
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1366
+ const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1367
+ const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1368
+ const pixelRatio = window.devicePixelRatio || 1;
1369
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1370
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1371
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1372
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1373
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1374
+ const canvasCtx = canvas.getContext("2d");
1375
+ if (!canvasCtx) return;
1376
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1377
+ currentRenderTask = page.render({
1378
+ canvasContext: canvasCtx,
1379
+ viewport
1380
+ });
1381
+ try {
1382
+ await currentRenderTask.promise;
1383
+ } catch (err) {
1384
+ if (err?.name !== "RenderingCancelledException") {
1385
+ console.warn("[PdfPlugin] Page render warning:", err);
1386
+ }
1387
+ } finally {
1388
+ currentRenderTask = null;
1389
+ }
1390
+ };
1391
+ await renderPage(1);
1279
1392
  const cleanup = () => {
1280
- URL.revokeObjectURL(url);
1281
- wrapper.remove();
1393
+ if (currentRenderTask) {
1394
+ try {
1395
+ currentRenderTask.cancel();
1396
+ } catch {
1397
+ }
1398
+ }
1399
+ try {
1400
+ pdfDoc.destroy();
1401
+ } catch {
1402
+ }
1403
+ container.remove();
1282
1404
  ctx.container.innerHTML = "";
1283
1405
  };
1284
1406
  ctx.signal.addEventListener("abort", cleanup);
1285
- return {
1407
+ const instance = {
1286
1408
  destroy: cleanup,
1287
1409
  zoomIn: () => {
1288
- currentZoom += 0.1;
1289
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1410
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1411
+ renderPage(currentPage);
1290
1412
  },
1291
1413
  zoomOut: () => {
1292
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1293
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1414
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1415
+ renderPage(currentPage);
1294
1416
  },
1295
- getZoom: () => currentZoom,
1417
+ getZoom: () => zoomScale,
1296
1418
  setZoom: (level) => {
1297
- currentZoom = level;
1298
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1419
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1420
+ renderPage(currentPage);
1299
1421
  },
1300
1422
  fitToPage: () => {
1301
- currentZoom = 1;
1302
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1423
+ zoomScale = 1;
1424
+ renderPage(currentPage);
1303
1425
  },
1304
1426
  rotateCW: () => {
1305
1427
  rotation = (rotation + 90) % 360;
1306
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1428
+ renderPage(currentPage);
1307
1429
  },
1308
1430
  rotateCCW: () => {
1309
1431
  rotation = (rotation - 90 + 360) % 360;
1310
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1432
+ renderPage(currentPage);
1311
1433
  },
1312
1434
  getRotation: () => rotation,
1435
+ getPageCount: () => totalPages,
1436
+ getCurrentPage: () => currentPage,
1313
1437
  goToPage: (page) => {
1314
- currentPage = page;
1315
- iframe.src = `${url}#page=${page}`;
1438
+ renderPage(page);
1316
1439
  },
1317
- getCurrentPage: () => currentPage,
1318
1440
  download: () => {
1441
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1442
+ const url = URL.createObjectURL(blob);
1319
1443
  const a = document.createElement("a");
1320
1444
  a.href = url;
1321
1445
  a.download = ctx.metadata.name || "document.pdf";
1322
1446
  a.click();
1447
+ URL.revokeObjectURL(url);
1323
1448
  },
1324
1449
  print: () => {
1325
- iframe.contentWindow?.print();
1450
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1451
+ const url = URL.createObjectURL(blob);
1452
+ const hiddenIframe = document.createElement("iframe");
1453
+ hiddenIframe.style.position = "fixed";
1454
+ hiddenIframe.style.right = "0";
1455
+ hiddenIframe.style.bottom = "0";
1456
+ hiddenIframe.style.width = "0";
1457
+ hiddenIframe.style.height = "0";
1458
+ hiddenIframe.style.border = "0";
1459
+ document.body.appendChild(hiddenIframe);
1460
+ hiddenIframe.src = url;
1461
+ hiddenIframe.onload = () => {
1462
+ setTimeout(() => {
1463
+ hiddenIframe.contentWindow?.print();
1464
+ setTimeout(() => {
1465
+ hiddenIframe.remove();
1466
+ URL.revokeObjectURL(url);
1467
+ }, 1e3);
1468
+ }, 300);
1469
+ };
1326
1470
  },
1327
1471
  getThumbnails: async () => {
1328
- return [
1329
- {
1330
- index: 1,
1331
- label: "Page 1",
1332
- render: async (canvas) => {
1333
- const context = canvas.getContext("2d");
1334
- if (context) {
1335
- context.fillStyle = "#fff";
1336
- context.fillRect(0, 0, canvas.width, canvas.height);
1337
- context.fillStyle = "#333";
1338
- context.font = "12px sans-serif";
1339
- context.fillText("PDF Preview", 10, 20);
1472
+ const thumbnails = [];
1473
+ const count = Math.min(totalPages, 50);
1474
+ for (let i = 1; i <= count; i++) {
1475
+ thumbnails.push({
1476
+ index: i,
1477
+ label: `Page ${i}`,
1478
+ render: async (thumbCanvas) => {
1479
+ try {
1480
+ const p = await pdfDoc.getPage(i);
1481
+ const baseVp = p.getViewport({ scale: 1 });
1482
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1483
+ const thumbVp = p.getViewport({ scale: thumbScale });
1484
+ thumbCanvas.height = Math.floor(thumbVp.height);
1485
+ const tCtx = thumbCanvas.getContext("2d");
1486
+ if (tCtx) {
1487
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1488
+ }
1489
+ } catch (e) {
1490
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1340
1491
  }
1341
1492
  }
1342
- }
1343
- ];
1493
+ });
1494
+ }
1495
+ return thumbnails;
1344
1496
  }
1345
1497
  };
1498
+ return instance;
1346
1499
  }
1347
1500
  };
1348
1501
  function pdfPlugin() {
@@ -1669,7 +1822,31 @@ var DocxPlugin = class {
1669
1822
  return this.mimeTypes.includes(mime || "");
1670
1823
  }
1671
1824
  getToolbarActions(instance) {
1672
- return [
1825
+ const totalPages = instance.getPageCount?.() ?? 1;
1826
+ const actions = [];
1827
+ if (totalPages > 1) {
1828
+ actions.push({
1829
+ id: "page-nav",
1830
+ icon: "",
1831
+ label: "Page Navigation",
1832
+ type: "page-nav",
1833
+ group: "navigation",
1834
+ value: instance.getCurrentPage?.() ?? 1,
1835
+ max: totalPages,
1836
+ execute: (action, page) => {
1837
+ const cur = instance.getCurrentPage?.() ?? 1;
1838
+ const max = instance.getPageCount?.() ?? 1;
1839
+ if (action === "prev") {
1840
+ if (cur > 1) instance.goToPage?.(cur - 1);
1841
+ } else if (action === "next") {
1842
+ if (cur < max) instance.goToPage?.(cur + 1);
1843
+ } else if (typeof page === "number") {
1844
+ instance.goToPage?.(page);
1845
+ }
1846
+ }
1847
+ });
1848
+ }
1849
+ actions.push(
1673
1850
  {
1674
1851
  id: "zoom-out",
1675
1852
  icon: "zoom-out",
@@ -1710,7 +1887,8 @@ var DocxPlugin = class {
1710
1887
  group: "actions",
1711
1888
  execute: () => instance.print?.()
1712
1889
  }
1713
- ];
1890
+ );
1891
+ return actions;
1714
1892
  }
1715
1893
  async render(ctx) {
1716
1894
  const wrapper = document.createElement("div");
@@ -1786,7 +1964,51 @@ var DocxPlugin = class {
1786
1964
  }
1787
1965
  }
1788
1966
  }
1967
+ const sections = wrapper.querySelectorAll("section.docx");
1968
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
1969
+ const pageElements = sections.length > 0 ? sections : cards;
1970
+ const totalPages = Math.max(1, pageElements.length);
1971
+ let currentPage = 1;
1972
+ let indicator = null;
1973
+ if (totalPages > 1) {
1974
+ indicator = document.createElement("div");
1975
+ indicator.className = "fp-docx-page-indicator";
1976
+ indicator.style.position = "sticky";
1977
+ indicator.style.bottom = "16px";
1978
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1979
+ indicator.style.backdropFilter = "blur(8px)";
1980
+ indicator.style.color = "#f8fafc";
1981
+ indicator.style.fontSize = "12px";
1982
+ indicator.style.fontWeight = "600";
1983
+ indicator.style.padding = "5px 14px";
1984
+ indicator.style.borderRadius = "20px";
1985
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1986
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1987
+ indicator.style.zIndex = "10";
1988
+ indicator.style.userSelect = "none";
1989
+ indicator.style.pointerEvents = "none";
1990
+ indicator.style.textAlign = "center";
1991
+ indicator.style.width = "fit-content";
1992
+ indicator.style.margin = "16px auto 0";
1993
+ ctx.container.appendChild(indicator);
1994
+ }
1995
+ const showPage = (pageNum) => {
1996
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1997
+ if (pageElements.length > 1) {
1998
+ pageElements.forEach((sec, idx) => {
1999
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2000
+ });
2001
+ }
2002
+ if (indicator) {
2003
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2004
+ }
2005
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2006
+ };
2007
+ if (totalPages > 1) {
2008
+ showPage(1);
2009
+ }
1789
2010
  const cleanup = () => {
2011
+ indicator?.remove();
1790
2012
  for (const url of createdBlobUrls) {
1791
2013
  URL.revokeObjectURL(url);
1792
2014
  }
@@ -1797,6 +2019,11 @@ var DocxPlugin = class {
1797
2019
  ctx.signal.addEventListener("abort", cleanup);
1798
2020
  return {
1799
2021
  destroy: cleanup,
2022
+ getPageCount: () => totalPages,
2023
+ getCurrentPage: () => currentPage,
2024
+ goToPage: (page) => {
2025
+ showPage(page);
2026
+ },
1800
2027
  zoomIn: () => {
1801
2028
  scale += 0.1;
1802
2029
  wrapper.style.transform = `scale(${scale})`;
@@ -2061,7 +2288,31 @@ var ExcelPlugin = class {
2061
2288
  return this.mimeTypes.includes(mime || "");
2062
2289
  }
2063
2290
  getToolbarActions(instance) {
2064
- return [
2291
+ const totalSheets = instance.getPageCount?.() ?? 1;
2292
+ const actions = [];
2293
+ if (totalSheets > 1) {
2294
+ actions.push({
2295
+ id: "page-nav",
2296
+ icon: "",
2297
+ label: "Sheet Navigation",
2298
+ type: "page-nav",
2299
+ group: "navigation",
2300
+ value: instance.getCurrentPage?.() ?? 1,
2301
+ max: totalSheets,
2302
+ execute: (action, sheet) => {
2303
+ const cur = instance.getCurrentPage?.() ?? 1;
2304
+ const max = instance.getPageCount?.() ?? 1;
2305
+ if (action === "prev") {
2306
+ if (cur > 1) instance.goToPage?.(cur - 1);
2307
+ } else if (action === "next") {
2308
+ if (cur < max) instance.goToPage?.(cur + 1);
2309
+ } else if (typeof sheet === "number") {
2310
+ instance.goToPage?.(sheet);
2311
+ }
2312
+ }
2313
+ });
2314
+ }
2315
+ actions.push(
2065
2316
  {
2066
2317
  id: "zoom-out",
2067
2318
  icon: "zoom-out",
@@ -2082,16 +2333,6 @@ var ExcelPlugin = class {
2082
2333
  instance.zoomIn?.();
2083
2334
  }
2084
2335
  },
2085
- {
2086
- id: "page-nav",
2087
- icon: "page-nav",
2088
- label: "Sheet Navigation",
2089
- type: "page-nav",
2090
- group: "navigation",
2091
- execute: (sheet) => {
2092
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2093
- }
2094
- },
2095
2336
  {
2096
2337
  id: "download",
2097
2338
  icon: "download",
@@ -2112,7 +2353,8 @@ var ExcelPlugin = class {
2112
2353
  instance.print?.();
2113
2354
  }
2114
2355
  }
2115
- ];
2356
+ );
2357
+ return actions;
2116
2358
  }
2117
2359
  async render(ctx) {
2118
2360
  const container = document.createElement("div");
@@ -2196,6 +2438,7 @@ var ExcelPlugin = class {
2196
2438
  b.style.fontWeight = "normal";
2197
2439
  }
2198
2440
  });
2441
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2199
2442
  };
2200
2443
  if (sheetNames.length > 0) {
2201
2444
  sheetNames.forEach((name, idx) => {
@@ -2445,7 +2688,31 @@ var CodePlugin = class {
2445
2688
  return false;
2446
2689
  }
2447
2690
  getToolbarActions(instance) {
2448
- return [
2691
+ const totalPages = instance.getPageCount?.() ?? 1;
2692
+ const actions = [];
2693
+ if (totalPages > 1) {
2694
+ actions.push({
2695
+ id: "page-nav",
2696
+ icon: "",
2697
+ label: "Page Navigation",
2698
+ type: "page-nav",
2699
+ group: "navigation",
2700
+ value: instance.getCurrentPage?.() ?? 1,
2701
+ max: totalPages,
2702
+ execute: (action, page) => {
2703
+ const cur = instance.getCurrentPage?.() ?? 1;
2704
+ const max = instance.getPageCount?.() ?? 1;
2705
+ if (action === "prev") {
2706
+ if (cur > 1) instance.goToPage?.(cur - 1);
2707
+ } else if (action === "next") {
2708
+ if (cur < max) instance.goToPage?.(cur + 1);
2709
+ } else if (typeof page === "number") {
2710
+ instance.goToPage?.(page);
2711
+ }
2712
+ }
2713
+ });
2714
+ }
2715
+ actions.push(
2449
2716
  {
2450
2717
  id: "zoom-out",
2451
2718
  icon: "zoom-out",
@@ -2496,11 +2763,16 @@ var CodePlugin = class {
2496
2763
  instance.print?.();
2497
2764
  }
2498
2765
  }
2499
- ];
2766
+ );
2767
+ return actions;
2500
2768
  }
2501
2769
  async render(ctx) {
2502
2770
  const decoder = new TextDecoder("utf-8");
2503
- const text = decoder.decode(ctx.buffer);
2771
+ const fullText = decoder.decode(ctx.buffer);
2772
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2773
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
2774
+ const totalPages = Math.max(1, rawPages.length);
2775
+ let currentPage = 1;
2504
2776
  const container = document.createElement("div");
2505
2777
  container.style.width = "100%";
2506
2778
  container.style.height = "100%";
@@ -2519,25 +2791,66 @@ var CodePlugin = class {
2519
2791
  pre.style.wordBreak = "break-all";
2520
2792
  const code = document.createElement("code");
2521
2793
  const ext = (ctx.metadata.extension || "").replace(".", "");
2522
- try {
2523
- if (ext && hljs.getLanguage(ext)) {
2524
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2525
- } else {
2526
- code.innerHTML = hljs.highlightAuto(text).value;
2794
+ const renderCodePage = (text) => {
2795
+ try {
2796
+ if (ext && hljs.getLanguage(ext)) {
2797
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
2798
+ } else {
2799
+ code.innerHTML = hljs.highlightAuto(text).value;
2800
+ }
2801
+ } catch {
2802
+ code.textContent = text;
2527
2803
  }
2528
- } catch {
2529
- code.textContent = text;
2530
- }
2804
+ };
2805
+ renderCodePage(rawPages[0] || fullText);
2531
2806
  pre.appendChild(code);
2532
2807
  container.appendChild(pre);
2808
+ let indicator = null;
2809
+ if (totalPages > 1) {
2810
+ indicator = document.createElement("div");
2811
+ indicator.className = "fp-code-page-indicator";
2812
+ indicator.style.position = "sticky";
2813
+ indicator.style.bottom = "16px";
2814
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2815
+ indicator.style.backdropFilter = "blur(8px)";
2816
+ indicator.style.color = "#f8fafc";
2817
+ indicator.style.fontSize = "12px";
2818
+ indicator.style.fontWeight = "600";
2819
+ indicator.style.padding = "5px 14px";
2820
+ indicator.style.borderRadius = "20px";
2821
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2822
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2823
+ indicator.style.zIndex = "10";
2824
+ indicator.style.userSelect = "none";
2825
+ indicator.style.pointerEvents = "none";
2826
+ indicator.style.textAlign = "center";
2827
+ indicator.style.width = "fit-content";
2828
+ indicator.style.margin = "16px auto 0";
2829
+ container.appendChild(indicator);
2830
+ }
2831
+ const showPage = (pageNum) => {
2832
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2833
+ renderCodePage(rawPages[currentPage - 1] || fullText);
2834
+ if (indicator) {
2835
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2836
+ }
2837
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2838
+ };
2839
+ if (totalPages > 1) {
2840
+ showPage(1);
2841
+ }
2533
2842
  ctx.container.appendChild(container);
2534
2843
  const cleanup = () => {
2844
+ indicator?.remove();
2535
2845
  container.remove();
2536
2846
  ctx.container.innerHTML = "";
2537
2847
  };
2538
2848
  ctx.signal.addEventListener("abort", cleanup);
2539
2849
  return {
2540
2850
  destroy: cleanup,
2851
+ getPageCount: () => totalPages,
2852
+ getCurrentPage: () => currentPage,
2853
+ goToPage: (page) => showPage(page),
2541
2854
  zoomIn: () => {
2542
2855
  fontSize = Math.min(32, fontSize + 2);
2543
2856
  pre.style.fontSize = `${fontSize}px`;
@@ -2565,7 +2878,7 @@ var CodePlugin = class {
2565
2878
  window.print();
2566
2879
  },
2567
2880
  copy: () => {
2568
- navigator.clipboard?.writeText(text);
2881
+ navigator.clipboard?.writeText(fullText);
2569
2882
  }
2570
2883
  };
2571
2884
  }
@@ -3387,7 +3700,31 @@ var RtfPlugin = class {
3387
3700
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3388
3701
  }
3389
3702
  getToolbarActions(instance) {
3390
- return [
3703
+ const totalPages = instance.getPageCount?.() ?? 1;
3704
+ const actions = [];
3705
+ if (totalPages > 1) {
3706
+ actions.push({
3707
+ id: "page-nav",
3708
+ icon: "",
3709
+ label: "Page Navigation",
3710
+ type: "page-nav",
3711
+ group: "navigation",
3712
+ value: instance.getCurrentPage?.() ?? 1,
3713
+ max: totalPages,
3714
+ execute: (action, page) => {
3715
+ const cur = instance.getCurrentPage?.() ?? 1;
3716
+ const max = instance.getPageCount?.() ?? 1;
3717
+ if (action === "prev") {
3718
+ if (cur > 1) instance.goToPage?.(cur - 1);
3719
+ } else if (action === "next") {
3720
+ if (cur < max) instance.goToPage?.(cur + 1);
3721
+ } else if (typeof page === "number") {
3722
+ instance.goToPage?.(page);
3723
+ }
3724
+ }
3725
+ });
3726
+ }
3727
+ actions.push(
3391
3728
  {
3392
3729
  id: "zoom-out",
3393
3730
  icon: "zoom-out",
@@ -3428,7 +3765,8 @@ var RtfPlugin = class {
3428
3765
  group: "actions",
3429
3766
  execute: () => instance.print?.()
3430
3767
  }
3431
- ];
3768
+ );
3769
+ return actions;
3432
3770
  }
3433
3771
  async render(ctx) {
3434
3772
  const wrapper = document.createElement("div");
@@ -3447,28 +3785,82 @@ var RtfPlugin = class {
3447
3785
  ctx.container.style.backgroundColor = "#f1f5f9";
3448
3786
  ctx.container.appendChild(wrapper);
3449
3787
  let scale = 1;
3788
+ let pageElements = [];
3450
3789
  try {
3451
3790
  if (typeof RTFJS.loggingEnabled === "function") {
3452
3791
  RTFJS.loggingEnabled(false);
3453
3792
  }
3454
3793
  const doc = new RTFJS.Document(ctx.buffer, {});
3455
3794
  const htmlElements = await doc.render();
3456
- for (const el of htmlElements) {
3795
+ pageElements = htmlElements;
3796
+ for (let i = 0; i < htmlElements.length; i++) {
3797
+ const el = htmlElements[i];
3798
+ el.style.display = i === 0 ? "block" : "none";
3457
3799
  wrapper.appendChild(el);
3458
3800
  }
3459
3801
  } catch (err) {
3460
3802
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3461
3803
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3462
3804
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3463
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
3805
+ const pre = document.createElement("pre");
3806
+ pre.style.whiteSpace = "pre-wrap";
3807
+ pre.style.fontFamily = "serif";
3808
+ pre.style.color = "#333";
3809
+ pre.textContent = clean;
3810
+ wrapper.appendChild(pre);
3811
+ pageElements = [pre];
3812
+ }
3813
+ const totalPages = Math.max(1, pageElements.length);
3814
+ let currentPage = 1;
3815
+ let indicator = null;
3816
+ if (totalPages > 1) {
3817
+ indicator = document.createElement("div");
3818
+ indicator.className = "fp-rtf-page-indicator";
3819
+ indicator.style.position = "sticky";
3820
+ indicator.style.bottom = "16px";
3821
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3822
+ indicator.style.backdropFilter = "blur(8px)";
3823
+ indicator.style.color = "#f8fafc";
3824
+ indicator.style.fontSize = "12px";
3825
+ indicator.style.fontWeight = "600";
3826
+ indicator.style.padding = "5px 14px";
3827
+ indicator.style.borderRadius = "20px";
3828
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3829
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3830
+ indicator.style.zIndex = "10";
3831
+ indicator.style.userSelect = "none";
3832
+ indicator.style.pointerEvents = "none";
3833
+ indicator.style.textAlign = "center";
3834
+ indicator.style.width = "fit-content";
3835
+ indicator.style.margin = "16px auto 0";
3836
+ ctx.container.appendChild(indicator);
3837
+ }
3838
+ const showPage = (pageNum) => {
3839
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3840
+ if (totalPages > 1) {
3841
+ pageElements.forEach((el, idx) => {
3842
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
3843
+ });
3844
+ }
3845
+ if (indicator) {
3846
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3847
+ }
3848
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3849
+ };
3850
+ if (totalPages > 1) {
3851
+ showPage(1);
3464
3852
  }
3465
3853
  const cleanup = () => {
3854
+ indicator?.remove();
3466
3855
  wrapper.remove();
3467
3856
  ctx.container.innerHTML = "";
3468
3857
  };
3469
3858
  ctx.signal.addEventListener("abort", cleanup);
3470
3859
  return {
3471
3860
  destroy: cleanup,
3861
+ getPageCount: () => totalPages,
3862
+ getCurrentPage: () => currentPage,
3863
+ goToPage: (page) => showPage(page),
3472
3864
  zoomIn: () => {
3473
3865
  scale += 0.1;
3474
3866
  wrapper.style.transform = `scale(${scale})`;
@@ -3656,41 +4048,41 @@ var OpenDocumentPlugin = class {
3656
4048
  }
3657
4049
  getToolbarActions(instance) {
3658
4050
  const isPresentation = instance.isPresentation;
4051
+ const totalPages = instance.getPageCount?.() ?? 1;
3659
4052
  const actions = [];
3660
- if (isPresentation) {
3661
- actions.push(
3662
- {
4053
+ if (isPresentation || totalPages > 1) {
4054
+ if (isPresentation) {
4055
+ actions.push({
3663
4056
  id: "thumbnails",
3664
4057
  icon: "thumbnails",
3665
4058
  label: "Slide Thumbnails",
3666
4059
  type: "button",
3667
4060
  group: "navigation",
3668
4061
  execute: () => instance.toggleThumbnails?.()
3669
- },
3670
- {
3671
- id: "page-nav",
3672
- icon: "",
3673
- label: "Slide Navigation",
3674
- type: "page-nav",
3675
- group: "navigation",
3676
- value: instance.getCurrentPage?.() ?? 1,
3677
- max: instance.getPageCount?.() ?? 1,
3678
- execute: (action, page) => {
3679
- if (action === "prev") {
3680
- const cur = instance.getCurrentPage?.() ?? 1;
3681
- if (cur > 1) instance.goToPage?.(cur - 1);
3682
- } else if (action === "next") {
3683
- const cur = instance.getCurrentPage?.() ?? 1;
3684
- const total = instance.getPageCount?.() ?? 1;
3685
- if (cur < total) instance.goToPage?.(cur + 1);
3686
- } else if (typeof page === "number") {
3687
- instance.goToPage?.(page);
3688
- } else if (typeof action === "number") {
3689
- instance.goToPage?.(action);
3690
- }
4062
+ });
4063
+ }
4064
+ actions.push({
4065
+ id: "page-nav",
4066
+ icon: "",
4067
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4068
+ type: "page-nav",
4069
+ group: "navigation",
4070
+ value: instance.getCurrentPage?.() ?? 1,
4071
+ max: totalPages,
4072
+ execute: (action, page) => {
4073
+ const cur = instance.getCurrentPage?.() ?? 1;
4074
+ const max = instance.getPageCount?.() ?? 1;
4075
+ if (action === "prev") {
4076
+ if (cur > 1) instance.goToPage?.(cur - 1);
4077
+ } else if (action === "next") {
4078
+ if (cur < max) instance.goToPage?.(cur + 1);
4079
+ } else if (typeof page === "number") {
4080
+ instance.goToPage?.(page);
4081
+ } else if (typeof action === "number") {
4082
+ instance.goToPage?.(action);
3691
4083
  }
3692
4084
  }
3693
- );
4085
+ });
3694
4086
  }
3695
4087
  actions.push(
3696
4088
  {
@@ -4073,7 +4465,31 @@ var DocPlugin = class {
4073
4465
  return this.mimeTypes.includes(mime || "");
4074
4466
  }
4075
4467
  getToolbarActions(instance) {
4076
- return [
4468
+ const totalPages = instance.getPageCount?.() ?? 1;
4469
+ const actions = [];
4470
+ if (totalPages > 1) {
4471
+ actions.push({
4472
+ id: "page-nav",
4473
+ icon: "",
4474
+ label: "Page Navigation",
4475
+ type: "page-nav",
4476
+ group: "navigation",
4477
+ value: instance.getCurrentPage?.() ?? 1,
4478
+ max: totalPages,
4479
+ execute: (action, page) => {
4480
+ const cur = instance.getCurrentPage?.() ?? 1;
4481
+ const max = instance.getPageCount?.() ?? 1;
4482
+ if (action === "prev") {
4483
+ if (cur > 1) instance.goToPage?.(cur - 1);
4484
+ } else if (action === "next") {
4485
+ if (cur < max) instance.goToPage?.(cur + 1);
4486
+ } else if (typeof page === "number") {
4487
+ instance.goToPage?.(page);
4488
+ }
4489
+ }
4490
+ });
4491
+ }
4492
+ actions.push(
4077
4493
  {
4078
4494
  id: "zoom-out",
4079
4495
  icon: "zoom-out",
@@ -4122,7 +4538,8 @@ var DocPlugin = class {
4122
4538
  group: "actions",
4123
4539
  execute: () => instance.print?.()
4124
4540
  }
4125
- ];
4541
+ );
4542
+ return actions;
4126
4543
  }
4127
4544
  async render(ctx) {
4128
4545
  const container = document.createElement("div");
@@ -4149,6 +4566,7 @@ var DocPlugin = class {
4149
4566
  ctx.container.appendChild(container);
4150
4567
  let scale = 1;
4151
4568
  let extractedRawText = "";
4569
+ let isFallback = false;
4152
4570
  try {
4153
4571
  const cfbf = new CfbfReader(ctx.buffer);
4154
4572
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4162,26 +4580,89 @@ var DocPlugin = class {
4162
4580
  const tableStream = cfbf.readStream(tableName);
4163
4581
  const text = this.extractDocText(wordDocStream, tableStream);
4164
4582
  extractedRawText = text;
4165
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4166
4583
  } catch (err) {
4167
4584
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4168
4585
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4169
4586
  extractedRawText = fallback;
4170
- wrapper.innerHTML = `
4171
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4172
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4173
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4174
- </div>
4175
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4176
- `;
4587
+ isFallback = true;
4588
+ }
4589
+ const rawPages = this.splitIntoPages(extractedRawText);
4590
+ const totalPages = Math.max(1, rawPages.length);
4591
+ let currentPage = 1;
4592
+ wrapper.innerHTML = "";
4593
+ const pageCards = [];
4594
+ for (let i = 0; i < totalPages; i++) {
4595
+ const pageCard = document.createElement("div");
4596
+ pageCard.className = "fp-doc-page-card";
4597
+ pageCard.style.backgroundColor = "#ffffff";
4598
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4599
+ pageCard.style.borderRadius = "4px";
4600
+ pageCard.style.padding = "56px 48px";
4601
+ pageCard.style.minHeight = "100%";
4602
+ pageCard.style.display = i === 0 ? "block" : "none";
4603
+ if (isFallback && i === 0) {
4604
+ pageCard.innerHTML = `
4605
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4606
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4607
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4608
+ </div>
4609
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4610
+ `;
4611
+ } else {
4612
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4613
+ }
4614
+ wrapper.appendChild(pageCard);
4615
+ pageCards.push(pageCard);
4616
+ }
4617
+ let indicator = null;
4618
+ if (totalPages > 1) {
4619
+ indicator = document.createElement("div");
4620
+ indicator.className = "fp-doc-page-indicator";
4621
+ indicator.style.position = "sticky";
4622
+ indicator.style.bottom = "16px";
4623
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4624
+ indicator.style.backdropFilter = "blur(8px)";
4625
+ indicator.style.color = "#f8fafc";
4626
+ indicator.style.fontSize = "12px";
4627
+ indicator.style.fontWeight = "600";
4628
+ indicator.style.padding = "5px 14px";
4629
+ indicator.style.borderRadius = "20px";
4630
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4631
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4632
+ indicator.style.zIndex = "10";
4633
+ indicator.style.userSelect = "none";
4634
+ indicator.style.pointerEvents = "none";
4635
+ indicator.style.textAlign = "center";
4636
+ indicator.style.width = "fit-content";
4637
+ indicator.style.margin = "16px auto 0";
4638
+ container.appendChild(indicator);
4639
+ }
4640
+ const showPage = (pageNum) => {
4641
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4642
+ if (totalPages > 1) {
4643
+ pageCards.forEach((card, idx) => {
4644
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
4645
+ });
4646
+ }
4647
+ if (indicator) {
4648
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4649
+ }
4650
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4651
+ };
4652
+ if (totalPages > 1) {
4653
+ showPage(1);
4177
4654
  }
4178
4655
  const cleanup = () => {
4656
+ indicator?.remove();
4179
4657
  container.remove();
4180
4658
  ctx.container.innerHTML = "";
4181
4659
  };
4182
4660
  ctx.signal.addEventListener("abort", cleanup);
4183
4661
  return {
4184
4662
  destroy: cleanup,
4663
+ getPageCount: () => totalPages,
4664
+ getCurrentPage: () => currentPage,
4665
+ goToPage: (page) => showPage(page),
4185
4666
  zoomIn: () => {
4186
4667
  scale += 0.1;
4187
4668
  wrapper.style.transform = `scale(${scale})`;
@@ -4313,11 +4794,16 @@ var DocPlugin = class {
4313
4794
  heuristicTextExtraction(buffer) {
4314
4795
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4315
4796
  }
4797
+ splitIntoPages(text) {
4798
+ if (!text) return [""];
4799
+ const parts = text.split(/[\x0C\f]|\r?\n\s*[-=_]{3,}\s*(?:PAGE|Page|page break)[\s\d\w-]*[-=_]{3,}\s*\r?\n/i).map((p) => p.trim()).filter((p) => p.length > 0);
4800
+ return parts.length > 0 ? parts : [text];
4801
+ }
4316
4802
  /**
4317
4803
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4318
4804
  */
4319
4805
  formatDocToHtml(text, filename) {
4320
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
4806
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4321
4807
  let html = "";
4322
4808
  let inList = false;
4323
4809
  for (const rawLine of lines) {