@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/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import DOMPurify2 from 'dompurify';
2
+ import * as pdfjsLib from 'pdfjs-dist';
2
3
  import * as docx from 'docx-preview';
3
4
  import { unzipSync, strFromU8, unzip } from 'fflate';
4
5
  import * as XLSX from 'xlsx';
@@ -522,11 +523,21 @@ var ToolbarController = class {
522
523
  el;
523
524
  toolbarEl;
524
525
  actions = [];
526
+ pageInputEl = null;
527
+ pageLabelEl = null;
525
528
  constructor(container) {
526
529
  this.el = container;
527
530
  this.toolbarEl = createElement("div", { className: "fp-toolbar" });
528
531
  this.el.appendChild(this.toolbarEl);
529
532
  }
533
+ setPage(page, max) {
534
+ if (this.pageInputEl) {
535
+ this.pageInputEl.value = page.toString();
536
+ }
537
+ if (max !== void 0 && this.pageLabelEl) {
538
+ this.pageLabelEl.textContent = ` / ${max}`;
539
+ }
540
+ }
530
541
  update(actions) {
531
542
  this.actions = actions;
532
543
  this.render();
@@ -569,6 +580,7 @@ var ToolbarController = class {
569
580
  if (action.type === "separator") {
570
581
  groupEl.appendChild(createElement("div", { className: "fp-toolbar-separator" }));
571
582
  } else if (action.type === "page-nav") {
583
+ const max = action.max ?? 1;
572
584
  const prevBtn = this.createButton(
573
585
  "prev",
574
586
  ICON_PAGE_PREV,
@@ -587,7 +599,6 @@ var ToolbarController = class {
587
599
  "Next Page",
588
600
  () => {
589
601
  const cur = parseInt(input.value, 10) || 1;
590
- const max = action.max ?? 1;
591
602
  if (cur < max) {
592
603
  input.value = (cur + 1).toString();
593
604
  action.execute("next", cur + 1);
@@ -599,15 +610,18 @@ var ToolbarController = class {
599
610
  type: "number",
600
611
  value: (action.value ?? 1).toString(),
601
612
  min: "1",
602
- max: (action.max ?? 1).toString()
613
+ max: max.toString()
603
614
  });
604
615
  input.addEventListener("change", () => {
605
- const val = parseInt(input.value, 10);
606
- if (!isNaN(val)) {
607
- action.execute("go", val);
608
- }
616
+ let val = parseInt(input.value, 10);
617
+ if (isNaN(val)) val = 1;
618
+ val = Math.max(1, Math.min(max, val));
619
+ input.value = val.toString();
620
+ action.execute("go", val);
609
621
  });
610
- const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
622
+ const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${max}`);
623
+ this.pageInputEl = input;
624
+ this.pageLabelEl = label;
611
625
  groupEl.appendChild(prevBtn);
612
626
  groupEl.appendChild(input);
613
627
  groupEl.appendChild(label);
@@ -785,7 +799,13 @@ var FilePreviewViewer = class {
785
799
  buffer,
786
800
  options,
787
801
  signal,
788
- emit: (event, payload) => this.eventEmitter.emit(event, payload)
802
+ emit: (event, payload) => {
803
+ if (event === "page-change" && payload && typeof payload.page === "number") {
804
+ const total = payload.total ?? payload.totalPages;
805
+ this.toolbar?.setPage(payload.page, total);
806
+ }
807
+ this.eventEmitter.emit(event, payload);
808
+ }
789
809
  });
790
810
  this.activeInstance = instance;
791
811
  this.hideLoading();
@@ -819,6 +839,7 @@ var FilePreviewViewer = class {
819
839
  if (thumbnails && thumbnails.length > 0 && this.thumbnailPanel) {
820
840
  this.thumbnailPanel.update(thumbnails, (index) => {
821
841
  instance.goToPage?.(index + 1);
842
+ this.toolbar?.setPage(index + 1);
822
843
  });
823
844
  if (options.showThumbnails) {
824
845
  this.thumbnailPanel.show();
@@ -944,9 +965,13 @@ var FilePreviewViewer = class {
944
965
  if (e.key === "ArrowRight" || e.key === "PageDown") {
945
966
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
946
967
  this.activeInstance.goToPage?.(cur + 1);
968
+ const nextCur = this.activeInstance.getCurrentPage?.() ?? cur + 1;
969
+ this.toolbar?.setPage(nextCur);
947
970
  } else if (e.key === "ArrowLeft" || e.key === "PageUp") {
948
971
  const cur = this.activeInstance.getCurrentPage?.() ?? 1;
949
972
  this.activeInstance.goToPage?.(Math.max(1, cur - 1));
973
+ const prevCur = this.activeInstance.getCurrentPage?.() ?? Math.max(1, cur - 1);
974
+ this.toolbar?.setPage(prevCur);
950
975
  } else if (e.key === "+" || e.key === "=") {
951
976
  this.activeInstance.zoomIn?.();
952
977
  } else if (e.key === "-" || e.key === "_") {
@@ -1212,30 +1237,62 @@ var CfbfReader = class {
1212
1237
  return result.subarray(0, targetSize);
1213
1238
  }
1214
1239
  };
1215
-
1216
- // ../plugins/pdf/dist/index.js
1240
+ if (typeof window !== "undefined" && pdfjsLib.GlobalWorkerOptions) {
1241
+ if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
1242
+ pdfjsLib.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/build/pdf.worker.min.mjs`;
1243
+ }
1244
+ }
1217
1245
  var PdfPlugin = class {
1218
1246
  id = "pdf";
1219
- name = "PDF Preview";
1247
+ name = "PDF Document Preview";
1220
1248
  extensions = [".pdf"];
1221
1249
  mimeTypes = ["application/pdf"];
1222
1250
  weight = 100;
1223
1251
  supports(file) {
1224
1252
  const ext = file.metadata.extension?.toLowerCase();
1225
1253
  const mime = file.metadata.mimeType?.toLowerCase();
1226
- return ext === ".pdf" || mime === "application/pdf";
1254
+ if (ext) return this.extensions.includes(ext);
1255
+ return this.mimeTypes.includes(mime || "");
1227
1256
  }
1228
1257
  getToolbarActions(instance) {
1258
+ const totalPages = instance.getPageCount?.() ?? 1;
1259
+ const curPage = instance.getCurrentPage?.() ?? 1;
1229
1260
  return [
1261
+ {
1262
+ id: "thumbnails",
1263
+ icon: "thumbnails",
1264
+ label: "Page Thumbnails",
1265
+ type: "button",
1266
+ group: "navigation",
1267
+ execute: () => instance.toggleThumbnails?.()
1268
+ },
1269
+ {
1270
+ id: "page-nav",
1271
+ icon: "",
1272
+ label: "Page Navigation",
1273
+ type: "page-nav",
1274
+ group: "navigation",
1275
+ value: curPage,
1276
+ max: totalPages,
1277
+ execute: (action, page) => {
1278
+ const cur = instance.getCurrentPage?.() ?? 1;
1279
+ const max = instance.getPageCount?.() ?? 1;
1280
+ if (action === "prev") {
1281
+ if (cur > 1) instance.goToPage?.(cur - 1);
1282
+ } else if (action === "next") {
1283
+ if (cur < max) instance.goToPage?.(cur + 1);
1284
+ } else if (typeof page === "number") {
1285
+ instance.goToPage?.(page);
1286
+ }
1287
+ }
1288
+ },
1230
1289
  {
1231
1290
  id: "zoom-out",
1232
1291
  icon: "zoom-out",
1233
1292
  label: "Zoom Out",
1234
1293
  type: "button",
1235
1294
  group: "zoom",
1236
- execute: () => {
1237
- instance.zoomOut?.();
1238
- }
1295
+ execute: () => instance.zoomOut?.()
1239
1296
  },
1240
1297
  {
1241
1298
  id: "zoom-in",
@@ -1243,9 +1300,7 @@ var PdfPlugin = class {
1243
1300
  label: "Zoom In",
1244
1301
  type: "button",
1245
1302
  group: "zoom",
1246
- execute: () => {
1247
- instance.zoomIn?.();
1248
- }
1303
+ execute: () => instance.zoomIn?.()
1249
1304
  },
1250
1305
  {
1251
1306
  id: "fit-page",
@@ -1253,39 +1308,23 @@ var PdfPlugin = class {
1253
1308
  label: "Fit to Page",
1254
1309
  type: "button",
1255
1310
  group: "zoom",
1256
- execute: () => {
1257
- instance.fitToPage?.();
1258
- }
1311
+ execute: () => instance.fitToPage?.()
1259
1312
  },
1260
1313
  {
1261
1314
  id: "rotate-cw",
1262
1315
  icon: "rotate-cw",
1263
- label: "Rotate",
1316
+ label: "Rotate Clockwise",
1264
1317
  type: "button",
1265
1318
  group: "view",
1266
- execute: () => {
1267
- instance.rotateCW?.();
1268
- }
1269
- },
1270
- {
1271
- id: "page-nav",
1272
- icon: "page-nav",
1273
- label: "Page Navigation",
1274
- type: "page-nav",
1275
- group: "navigation",
1276
- execute: (page) => {
1277
- if (typeof page === "number") instance.goToPage?.(page);
1278
- }
1319
+ execute: () => instance.rotateCW?.()
1279
1320
  },
1280
1321
  {
1281
1322
  id: "download",
1282
1323
  icon: "download",
1283
- label: "Download",
1324
+ label: "Download PDF",
1284
1325
  type: "button",
1285
1326
  group: "actions",
1286
- execute: () => {
1287
- instance.download?.();
1288
- }
1327
+ execute: () => instance.download?.()
1289
1328
  },
1290
1329
  {
1291
1330
  id: "print",
@@ -1293,99 +1332,213 @@ var PdfPlugin = class {
1293
1332
  label: "Print",
1294
1333
  type: "button",
1295
1334
  group: "actions",
1296
- execute: () => {
1297
- instance.print?.();
1298
- }
1335
+ execute: () => instance.print?.()
1299
1336
  }
1300
1337
  ];
1301
1338
  }
1302
1339
  async render(ctx) {
1303
- const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1304
- const url = URL.createObjectURL(blob);
1305
- const wrapper = document.createElement("div");
1306
- wrapper.style.width = "100%";
1307
- wrapper.style.height = "100%";
1308
- wrapper.style.overflow = "hidden";
1309
- wrapper.style.display = "flex";
1310
- wrapper.style.justifyContent = "center";
1311
- wrapper.style.alignItems = "center";
1312
- const iframe = document.createElement("iframe");
1313
- iframe.src = url;
1314
- iframe.style.width = "100%";
1315
- iframe.style.height = "100%";
1316
- iframe.style.border = "none";
1317
- wrapper.appendChild(iframe);
1318
- ctx.container.appendChild(wrapper);
1340
+ const container = document.createElement("div");
1341
+ container.className = "fp-pdf-container";
1342
+ container.style.width = "100%";
1343
+ container.style.height = "100%";
1344
+ container.style.overflow = "auto";
1345
+ container.style.display = "flex";
1346
+ container.style.flexDirection = "column";
1347
+ container.style.alignItems = "center";
1348
+ container.style.padding = "24px 16px";
1349
+ container.style.backgroundColor = "#0f172a";
1350
+ container.style.boxSizing = "border-box";
1351
+ container.style.position = "relative";
1352
+ const pageCard = document.createElement("div");
1353
+ pageCard.className = "fp-pdf-page-card";
1354
+ pageCard.style.boxShadow = "0 10px 35px rgba(0, 0, 0, 0.5)";
1355
+ pageCard.style.backgroundColor = "#ffffff";
1356
+ pageCard.style.borderRadius = "4px";
1357
+ pageCard.style.overflow = "hidden";
1358
+ pageCard.style.lineHeight = "0";
1359
+ pageCard.style.transition = "transform 0.15s ease";
1360
+ pageCard.style.position = "relative";
1361
+ const canvas = document.createElement("canvas");
1362
+ pageCard.appendChild(canvas);
1363
+ container.appendChild(pageCard);
1364
+ const indicator = document.createElement("div");
1365
+ indicator.className = "fp-pdf-page-indicator";
1366
+ indicator.style.position = "sticky";
1367
+ indicator.style.bottom = "16px";
1368
+ indicator.style.marginTop = "16px";
1369
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
1370
+ indicator.style.backdropFilter = "blur(8px)";
1371
+ indicator.style.color = "#f8fafc";
1372
+ indicator.style.fontSize = "12px";
1373
+ indicator.style.fontWeight = "600";
1374
+ indicator.style.padding = "5px 14px";
1375
+ indicator.style.borderRadius = "20px";
1376
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
1377
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
1378
+ indicator.style.zIndex = "10";
1379
+ indicator.style.userSelect = "none";
1380
+ indicator.style.pointerEvents = "none";
1381
+ container.appendChild(indicator);
1382
+ ctx.container.appendChild(container);
1383
+ const loadingTask = pdfjsLib.getDocument({
1384
+ data: new Uint8Array(ctx.buffer),
1385
+ cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/cmaps/`,
1386
+ cMapPacked: true,
1387
+ standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjsLib.version || "4.10.38"}/standard_fonts/`
1388
+ });
1389
+ const pdfDoc = await loadingTask.promise;
1390
+ const totalPages = Math.max(1, pdfDoc.numPages);
1319
1391
  let currentPage = 1;
1320
- let currentZoom = 1;
1392
+ let zoomScale = 1;
1321
1393
  let rotation = 0;
1394
+ let currentRenderTask = null;
1395
+ const renderPage = async (pageNum) => {
1396
+ if (currentRenderTask) {
1397
+ try {
1398
+ currentRenderTask.cancel();
1399
+ } catch {
1400
+ }
1401
+ currentRenderTask = null;
1402
+ }
1403
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
1404
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
1405
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
1406
+ const page = await pdfDoc.getPage(currentPage);
1407
+ const containerWidth = container.clientWidth || 900;
1408
+ const unscaledVp = page.getViewport({ scale: 1, rotation });
1409
+ const baseScale = Math.min((containerWidth - 64) / unscaledVp.width, 1.6);
1410
+ const effectiveScale = (baseScale > 0 ? baseScale : 1) * zoomScale;
1411
+ const pixelRatio = window.devicePixelRatio || 1;
1412
+ const viewport = page.getViewport({ scale: effectiveScale, rotation });
1413
+ canvas.width = Math.floor(viewport.width * pixelRatio);
1414
+ canvas.height = Math.floor(viewport.height * pixelRatio);
1415
+ canvas.style.width = `${Math.floor(viewport.width)}px`;
1416
+ canvas.style.height = `${Math.floor(viewport.height)}px`;
1417
+ const canvasCtx = canvas.getContext("2d");
1418
+ if (!canvasCtx) return;
1419
+ canvasCtx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
1420
+ currentRenderTask = page.render({
1421
+ canvasContext: canvasCtx,
1422
+ viewport
1423
+ });
1424
+ try {
1425
+ await currentRenderTask.promise;
1426
+ } catch (err) {
1427
+ if (err?.name !== "RenderingCancelledException") {
1428
+ console.warn("[PdfPlugin] Page render warning:", err);
1429
+ }
1430
+ } finally {
1431
+ currentRenderTask = null;
1432
+ }
1433
+ };
1434
+ await renderPage(1);
1322
1435
  const cleanup = () => {
1323
- URL.revokeObjectURL(url);
1324
- wrapper.remove();
1436
+ if (currentRenderTask) {
1437
+ try {
1438
+ currentRenderTask.cancel();
1439
+ } catch {
1440
+ }
1441
+ }
1442
+ try {
1443
+ pdfDoc.destroy();
1444
+ } catch {
1445
+ }
1446
+ container.remove();
1325
1447
  ctx.container.innerHTML = "";
1326
1448
  };
1327
1449
  ctx.signal.addEventListener("abort", cleanup);
1328
- return {
1450
+ const instance = {
1329
1451
  destroy: cleanup,
1330
1452
  zoomIn: () => {
1331
- currentZoom += 0.1;
1332
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1453
+ zoomScale = Math.min(3.5, zoomScale + 0.2);
1454
+ renderPage(currentPage);
1333
1455
  },
1334
1456
  zoomOut: () => {
1335
- currentZoom = Math.max(0.2, currentZoom - 0.1);
1336
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1457
+ zoomScale = Math.max(0.3, zoomScale - 0.2);
1458
+ renderPage(currentPage);
1337
1459
  },
1338
- getZoom: () => currentZoom,
1460
+ getZoom: () => zoomScale,
1339
1461
  setZoom: (level) => {
1340
- currentZoom = level;
1341
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1462
+ zoomScale = Math.max(0.3, Math.min(3.5, level));
1463
+ renderPage(currentPage);
1342
1464
  },
1343
1465
  fitToPage: () => {
1344
- currentZoom = 1;
1345
- iframe.style.transform = `scale(1) rotate(${rotation}deg)`;
1466
+ zoomScale = 1;
1467
+ renderPage(currentPage);
1346
1468
  },
1347
1469
  rotateCW: () => {
1348
1470
  rotation = (rotation + 90) % 360;
1349
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1471
+ renderPage(currentPage);
1350
1472
  },
1351
1473
  rotateCCW: () => {
1352
1474
  rotation = (rotation - 90 + 360) % 360;
1353
- iframe.style.transform = `scale(${currentZoom}) rotate(${rotation}deg)`;
1475
+ renderPage(currentPage);
1354
1476
  },
1355
1477
  getRotation: () => rotation,
1478
+ getPageCount: () => totalPages,
1479
+ getCurrentPage: () => currentPage,
1356
1480
  goToPage: (page) => {
1357
- currentPage = page;
1358
- iframe.src = `${url}#page=${page}`;
1481
+ renderPage(page);
1359
1482
  },
1360
- getCurrentPage: () => currentPage,
1361
1483
  download: () => {
1484
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1485
+ const url = URL.createObjectURL(blob);
1362
1486
  const a = document.createElement("a");
1363
1487
  a.href = url;
1364
1488
  a.download = ctx.metadata.name || "document.pdf";
1365
1489
  a.click();
1490
+ URL.revokeObjectURL(url);
1366
1491
  },
1367
1492
  print: () => {
1368
- iframe.contentWindow?.print();
1493
+ const blob = new Blob([ctx.buffer], { type: "application/pdf" });
1494
+ const url = URL.createObjectURL(blob);
1495
+ const hiddenIframe = document.createElement("iframe");
1496
+ hiddenIframe.style.position = "fixed";
1497
+ hiddenIframe.style.right = "0";
1498
+ hiddenIframe.style.bottom = "0";
1499
+ hiddenIframe.style.width = "0";
1500
+ hiddenIframe.style.height = "0";
1501
+ hiddenIframe.style.border = "0";
1502
+ document.body.appendChild(hiddenIframe);
1503
+ hiddenIframe.src = url;
1504
+ hiddenIframe.onload = () => {
1505
+ setTimeout(() => {
1506
+ hiddenIframe.contentWindow?.print();
1507
+ setTimeout(() => {
1508
+ hiddenIframe.remove();
1509
+ URL.revokeObjectURL(url);
1510
+ }, 1e3);
1511
+ }, 300);
1512
+ };
1369
1513
  },
1370
1514
  getThumbnails: async () => {
1371
- return [
1372
- {
1373
- index: 1,
1374
- label: "Page 1",
1375
- render: async (canvas) => {
1376
- const context = canvas.getContext("2d");
1377
- if (context) {
1378
- context.fillStyle = "#fff";
1379
- context.fillRect(0, 0, canvas.width, canvas.height);
1380
- context.fillStyle = "#333";
1381
- context.font = "12px sans-serif";
1382
- context.fillText("PDF Preview", 10, 20);
1515
+ const thumbnails = [];
1516
+ const count = Math.min(totalPages, 50);
1517
+ for (let i = 1; i <= count; i++) {
1518
+ thumbnails.push({
1519
+ index: i,
1520
+ label: `Page ${i}`,
1521
+ render: async (thumbCanvas) => {
1522
+ try {
1523
+ const p = await pdfDoc.getPage(i);
1524
+ const baseVp = p.getViewport({ scale: 1 });
1525
+ const thumbScale = (thumbCanvas.width || 120) / baseVp.width;
1526
+ const thumbVp = p.getViewport({ scale: thumbScale });
1527
+ thumbCanvas.height = Math.floor(thumbVp.height);
1528
+ const tCtx = thumbCanvas.getContext("2d");
1529
+ if (tCtx) {
1530
+ await p.render({ canvasContext: tCtx, viewport: thumbVp }).promise;
1531
+ }
1532
+ } catch (e) {
1533
+ console.warn(`[PdfPlugin] Error generating thumbnail for page ${i}:`, e);
1383
1534
  }
1384
1535
  }
1385
- }
1386
- ];
1536
+ });
1537
+ }
1538
+ return thumbnails;
1387
1539
  }
1388
1540
  };
1541
+ return instance;
1389
1542
  }
1390
1543
  };
1391
1544
  function pdfPlugin() {
@@ -1712,7 +1865,31 @@ var DocxPlugin = class {
1712
1865
  return this.mimeTypes.includes(mime || "");
1713
1866
  }
1714
1867
  getToolbarActions(instance) {
1715
- return [
1868
+ const totalPages = instance.getPageCount?.() ?? 1;
1869
+ const actions = [];
1870
+ if (totalPages > 1) {
1871
+ actions.push({
1872
+ id: "page-nav",
1873
+ icon: "",
1874
+ label: "Page Navigation",
1875
+ type: "page-nav",
1876
+ group: "navigation",
1877
+ value: instance.getCurrentPage?.() ?? 1,
1878
+ max: totalPages,
1879
+ execute: (action, page) => {
1880
+ const cur = instance.getCurrentPage?.() ?? 1;
1881
+ const max = instance.getPageCount?.() ?? 1;
1882
+ if (action === "prev") {
1883
+ if (cur > 1) instance.goToPage?.(cur - 1);
1884
+ } else if (action === "next") {
1885
+ if (cur < max) instance.goToPage?.(cur + 1);
1886
+ } else if (typeof page === "number") {
1887
+ instance.goToPage?.(page);
1888
+ }
1889
+ }
1890
+ });
1891
+ }
1892
+ actions.push(
1716
1893
  {
1717
1894
  id: "zoom-out",
1718
1895
  icon: "zoom-out",
@@ -1753,7 +1930,8 @@ var DocxPlugin = class {
1753
1930
  group: "actions",
1754
1931
  execute: () => instance.print?.()
1755
1932
  }
1756
- ];
1933
+ );
1934
+ return actions;
1757
1935
  }
1758
1936
  async render(ctx) {
1759
1937
  const wrapper = document.createElement("div");
@@ -1829,7 +2007,51 @@ var DocxPlugin = class {
1829
2007
  }
1830
2008
  }
1831
2009
  }
2010
+ const sections = wrapper.querySelectorAll("section.docx");
2011
+ const cards = wrapper.querySelectorAll(".fp-docx-page-card");
2012
+ const pageElements = sections.length > 0 ? sections : cards;
2013
+ const totalPages = Math.max(1, pageElements.length);
2014
+ let currentPage = 1;
2015
+ let indicator = null;
2016
+ if (totalPages > 1) {
2017
+ indicator = document.createElement("div");
2018
+ indicator.className = "fp-docx-page-indicator";
2019
+ indicator.style.position = "sticky";
2020
+ indicator.style.bottom = "16px";
2021
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2022
+ indicator.style.backdropFilter = "blur(8px)";
2023
+ indicator.style.color = "#f8fafc";
2024
+ indicator.style.fontSize = "12px";
2025
+ indicator.style.fontWeight = "600";
2026
+ indicator.style.padding = "5px 14px";
2027
+ indicator.style.borderRadius = "20px";
2028
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2029
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2030
+ indicator.style.zIndex = "10";
2031
+ indicator.style.userSelect = "none";
2032
+ indicator.style.pointerEvents = "none";
2033
+ indicator.style.textAlign = "center";
2034
+ indicator.style.width = "fit-content";
2035
+ indicator.style.margin = "16px auto 0";
2036
+ ctx.container.appendChild(indicator);
2037
+ }
2038
+ const showPage = (pageNum) => {
2039
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2040
+ if (pageElements.length > 1) {
2041
+ pageElements.forEach((sec, idx) => {
2042
+ sec.style.display = idx + 1 === currentPage ? "block" : "none";
2043
+ });
2044
+ }
2045
+ if (indicator) {
2046
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2047
+ }
2048
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2049
+ };
2050
+ if (totalPages > 1) {
2051
+ showPage(1);
2052
+ }
1832
2053
  const cleanup = () => {
2054
+ indicator?.remove();
1833
2055
  for (const url of createdBlobUrls) {
1834
2056
  URL.revokeObjectURL(url);
1835
2057
  }
@@ -1840,6 +2062,11 @@ var DocxPlugin = class {
1840
2062
  ctx.signal.addEventListener("abort", cleanup);
1841
2063
  return {
1842
2064
  destroy: cleanup,
2065
+ getPageCount: () => totalPages,
2066
+ getCurrentPage: () => currentPage,
2067
+ goToPage: (page) => {
2068
+ showPage(page);
2069
+ },
1843
2070
  zoomIn: () => {
1844
2071
  scale += 0.1;
1845
2072
  wrapper.style.transform = `scale(${scale})`;
@@ -2104,7 +2331,31 @@ var ExcelPlugin = class {
2104
2331
  return this.mimeTypes.includes(mime || "");
2105
2332
  }
2106
2333
  getToolbarActions(instance) {
2107
- return [
2334
+ const totalSheets = instance.getPageCount?.() ?? 1;
2335
+ const actions = [];
2336
+ if (totalSheets > 1) {
2337
+ actions.push({
2338
+ id: "page-nav",
2339
+ icon: "",
2340
+ label: "Sheet Navigation",
2341
+ type: "page-nav",
2342
+ group: "navigation",
2343
+ value: instance.getCurrentPage?.() ?? 1,
2344
+ max: totalSheets,
2345
+ execute: (action, sheet) => {
2346
+ const cur = instance.getCurrentPage?.() ?? 1;
2347
+ const max = instance.getPageCount?.() ?? 1;
2348
+ if (action === "prev") {
2349
+ if (cur > 1) instance.goToPage?.(cur - 1);
2350
+ } else if (action === "next") {
2351
+ if (cur < max) instance.goToPage?.(cur + 1);
2352
+ } else if (typeof sheet === "number") {
2353
+ instance.goToPage?.(sheet);
2354
+ }
2355
+ }
2356
+ });
2357
+ }
2358
+ actions.push(
2108
2359
  {
2109
2360
  id: "zoom-out",
2110
2361
  icon: "zoom-out",
@@ -2125,16 +2376,6 @@ var ExcelPlugin = class {
2125
2376
  instance.zoomIn?.();
2126
2377
  }
2127
2378
  },
2128
- {
2129
- id: "page-nav",
2130
- icon: "page-nav",
2131
- label: "Sheet Navigation",
2132
- type: "page-nav",
2133
- group: "navigation",
2134
- execute: (sheet) => {
2135
- if (typeof sheet === "number") instance.goToPage?.(sheet);
2136
- }
2137
- },
2138
2379
  {
2139
2380
  id: "download",
2140
2381
  icon: "download",
@@ -2155,7 +2396,8 @@ var ExcelPlugin = class {
2155
2396
  instance.print?.();
2156
2397
  }
2157
2398
  }
2158
- ];
2399
+ );
2400
+ return actions;
2159
2401
  }
2160
2402
  async render(ctx) {
2161
2403
  const container = document.createElement("div");
@@ -2239,6 +2481,7 @@ var ExcelPlugin = class {
2239
2481
  b.style.fontWeight = "normal";
2240
2482
  }
2241
2483
  });
2484
+ ctx.emit("page-change", { page: currentSheetIndex, total: sheetNames.length });
2242
2485
  };
2243
2486
  if (sheetNames.length > 0) {
2244
2487
  sheetNames.forEach((name, idx) => {
@@ -2488,7 +2731,31 @@ var CodePlugin = class {
2488
2731
  return false;
2489
2732
  }
2490
2733
  getToolbarActions(instance) {
2491
- return [
2734
+ const totalPages = instance.getPageCount?.() ?? 1;
2735
+ const actions = [];
2736
+ if (totalPages > 1) {
2737
+ actions.push({
2738
+ id: "page-nav",
2739
+ icon: "",
2740
+ label: "Page Navigation",
2741
+ type: "page-nav",
2742
+ group: "navigation",
2743
+ value: instance.getCurrentPage?.() ?? 1,
2744
+ max: totalPages,
2745
+ execute: (action, page) => {
2746
+ const cur = instance.getCurrentPage?.() ?? 1;
2747
+ const max = instance.getPageCount?.() ?? 1;
2748
+ if (action === "prev") {
2749
+ if (cur > 1) instance.goToPage?.(cur - 1);
2750
+ } else if (action === "next") {
2751
+ if (cur < max) instance.goToPage?.(cur + 1);
2752
+ } else if (typeof page === "number") {
2753
+ instance.goToPage?.(page);
2754
+ }
2755
+ }
2756
+ });
2757
+ }
2758
+ actions.push(
2492
2759
  {
2493
2760
  id: "zoom-out",
2494
2761
  icon: "zoom-out",
@@ -2539,11 +2806,16 @@ var CodePlugin = class {
2539
2806
  instance.print?.();
2540
2807
  }
2541
2808
  }
2542
- ];
2809
+ );
2810
+ return actions;
2543
2811
  }
2544
2812
  async render(ctx) {
2545
2813
  const decoder = new TextDecoder("utf-8");
2546
- const text = decoder.decode(ctx.buffer);
2814
+ const fullText = decoder.decode(ctx.buffer);
2815
+ const pageSplitRegex = /(?:\f|\x0C|(?:\r?\n|^)\s*[-=_]{3,}\s*(?:PAGE|Page|page break|Page Break)[\s\d\w-]*[-=_]{3,}\s*(?:\r?\n|$))/i;
2816
+ const rawPages = fullText.split(pageSplitRegex).map((p) => p.trim()).filter((p) => p.length > 0);
2817
+ const totalPages = Math.max(1, rawPages.length);
2818
+ let currentPage = 1;
2547
2819
  const container = document.createElement("div");
2548
2820
  container.style.width = "100%";
2549
2821
  container.style.height = "100%";
@@ -2562,25 +2834,66 @@ var CodePlugin = class {
2562
2834
  pre.style.wordBreak = "break-all";
2563
2835
  const code = document.createElement("code");
2564
2836
  const ext = (ctx.metadata.extension || "").replace(".", "");
2565
- try {
2566
- if (ext && hljs.getLanguage(ext)) {
2567
- code.innerHTML = hljs.highlight(text, { language: ext }).value;
2568
- } else {
2569
- code.innerHTML = hljs.highlightAuto(text).value;
2837
+ const renderCodePage = (text) => {
2838
+ try {
2839
+ if (ext && hljs.getLanguage(ext)) {
2840
+ code.innerHTML = hljs.highlight(text, { language: ext }).value;
2841
+ } else {
2842
+ code.innerHTML = hljs.highlightAuto(text).value;
2843
+ }
2844
+ } catch {
2845
+ code.textContent = text;
2570
2846
  }
2571
- } catch {
2572
- code.textContent = text;
2573
- }
2847
+ };
2848
+ renderCodePage(rawPages[0] || fullText);
2574
2849
  pre.appendChild(code);
2575
2850
  container.appendChild(pre);
2851
+ let indicator = null;
2852
+ if (totalPages > 1) {
2853
+ indicator = document.createElement("div");
2854
+ indicator.className = "fp-code-page-indicator";
2855
+ indicator.style.position = "sticky";
2856
+ indicator.style.bottom = "16px";
2857
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
2858
+ indicator.style.backdropFilter = "blur(8px)";
2859
+ indicator.style.color = "#f8fafc";
2860
+ indicator.style.fontSize = "12px";
2861
+ indicator.style.fontWeight = "600";
2862
+ indicator.style.padding = "5px 14px";
2863
+ indicator.style.borderRadius = "20px";
2864
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
2865
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
2866
+ indicator.style.zIndex = "10";
2867
+ indicator.style.userSelect = "none";
2868
+ indicator.style.pointerEvents = "none";
2869
+ indicator.style.textAlign = "center";
2870
+ indicator.style.width = "fit-content";
2871
+ indicator.style.margin = "16px auto 0";
2872
+ container.appendChild(indicator);
2873
+ }
2874
+ const showPage = (pageNum) => {
2875
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
2876
+ renderCodePage(rawPages[currentPage - 1] || fullText);
2877
+ if (indicator) {
2878
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
2879
+ }
2880
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
2881
+ };
2882
+ if (totalPages > 1) {
2883
+ showPage(1);
2884
+ }
2576
2885
  ctx.container.appendChild(container);
2577
2886
  const cleanup = () => {
2887
+ indicator?.remove();
2578
2888
  container.remove();
2579
2889
  ctx.container.innerHTML = "";
2580
2890
  };
2581
2891
  ctx.signal.addEventListener("abort", cleanup);
2582
2892
  return {
2583
2893
  destroy: cleanup,
2894
+ getPageCount: () => totalPages,
2895
+ getCurrentPage: () => currentPage,
2896
+ goToPage: (page) => showPage(page),
2584
2897
  zoomIn: () => {
2585
2898
  fontSize = Math.min(32, fontSize + 2);
2586
2899
  pre.style.fontSize = `${fontSize}px`;
@@ -2608,7 +2921,7 @@ var CodePlugin = class {
2608
2921
  window.print();
2609
2922
  },
2610
2923
  copy: () => {
2611
- navigator.clipboard?.writeText(text);
2924
+ navigator.clipboard?.writeText(fullText);
2612
2925
  }
2613
2926
  };
2614
2927
  }
@@ -3430,7 +3743,31 @@ var RtfPlugin = class {
3430
3743
  return this.extensions.includes(ext || "") || this.mimeTypes.includes(mime || "");
3431
3744
  }
3432
3745
  getToolbarActions(instance) {
3433
- return [
3746
+ const totalPages = instance.getPageCount?.() ?? 1;
3747
+ const actions = [];
3748
+ if (totalPages > 1) {
3749
+ actions.push({
3750
+ id: "page-nav",
3751
+ icon: "",
3752
+ label: "Page Navigation",
3753
+ type: "page-nav",
3754
+ group: "navigation",
3755
+ value: instance.getCurrentPage?.() ?? 1,
3756
+ max: totalPages,
3757
+ execute: (action, page) => {
3758
+ const cur = instance.getCurrentPage?.() ?? 1;
3759
+ const max = instance.getPageCount?.() ?? 1;
3760
+ if (action === "prev") {
3761
+ if (cur > 1) instance.goToPage?.(cur - 1);
3762
+ } else if (action === "next") {
3763
+ if (cur < max) instance.goToPage?.(cur + 1);
3764
+ } else if (typeof page === "number") {
3765
+ instance.goToPage?.(page);
3766
+ }
3767
+ }
3768
+ });
3769
+ }
3770
+ actions.push(
3434
3771
  {
3435
3772
  id: "zoom-out",
3436
3773
  icon: "zoom-out",
@@ -3471,7 +3808,8 @@ var RtfPlugin = class {
3471
3808
  group: "actions",
3472
3809
  execute: () => instance.print?.()
3473
3810
  }
3474
- ];
3811
+ );
3812
+ return actions;
3475
3813
  }
3476
3814
  async render(ctx) {
3477
3815
  const wrapper = document.createElement("div");
@@ -3490,28 +3828,82 @@ var RtfPlugin = class {
3490
3828
  ctx.container.style.backgroundColor = "#f1f5f9";
3491
3829
  ctx.container.appendChild(wrapper);
3492
3830
  let scale = 1;
3831
+ let pageElements = [];
3493
3832
  try {
3494
3833
  if (typeof RTFJS.loggingEnabled === "function") {
3495
3834
  RTFJS.loggingEnabled(false);
3496
3835
  }
3497
3836
  const doc = new RTFJS.Document(ctx.buffer, {});
3498
3837
  const htmlElements = await doc.render();
3499
- for (const el of htmlElements) {
3838
+ pageElements = htmlElements;
3839
+ for (let i = 0; i < htmlElements.length; i++) {
3840
+ const el = htmlElements[i];
3841
+ el.style.display = i === 0 ? "block" : "none";
3500
3842
  wrapper.appendChild(el);
3501
3843
  }
3502
3844
  } catch (err) {
3503
3845
  console.warn("[RtfPlugin] RTF render error, fallback text:", err);
3504
3846
  const text = new TextDecoder("latin1").decode(ctx.buffer);
3505
3847
  const clean = text.replace(/\\par[d]?/g, "\n").replace(/\\[a-zA-Z0-9\-]+/g, "").replace(/[{}]/g, "");
3506
- wrapper.innerHTML = `<pre style="white-space: pre-wrap; font-family: serif; color: #333;">${clean}</pre>`;
3848
+ const pre = document.createElement("pre");
3849
+ pre.style.whiteSpace = "pre-wrap";
3850
+ pre.style.fontFamily = "serif";
3851
+ pre.style.color = "#333";
3852
+ pre.textContent = clean;
3853
+ wrapper.appendChild(pre);
3854
+ pageElements = [pre];
3855
+ }
3856
+ const totalPages = Math.max(1, pageElements.length);
3857
+ let currentPage = 1;
3858
+ let indicator = null;
3859
+ if (totalPages > 1) {
3860
+ indicator = document.createElement("div");
3861
+ indicator.className = "fp-rtf-page-indicator";
3862
+ indicator.style.position = "sticky";
3863
+ indicator.style.bottom = "16px";
3864
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
3865
+ indicator.style.backdropFilter = "blur(8px)";
3866
+ indicator.style.color = "#f8fafc";
3867
+ indicator.style.fontSize = "12px";
3868
+ indicator.style.fontWeight = "600";
3869
+ indicator.style.padding = "5px 14px";
3870
+ indicator.style.borderRadius = "20px";
3871
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
3872
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
3873
+ indicator.style.zIndex = "10";
3874
+ indicator.style.userSelect = "none";
3875
+ indicator.style.pointerEvents = "none";
3876
+ indicator.style.textAlign = "center";
3877
+ indicator.style.width = "fit-content";
3878
+ indicator.style.margin = "16px auto 0";
3879
+ ctx.container.appendChild(indicator);
3880
+ }
3881
+ const showPage = (pageNum) => {
3882
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
3883
+ if (totalPages > 1) {
3884
+ pageElements.forEach((el, idx) => {
3885
+ el.style.display = idx + 1 === currentPage ? "block" : "none";
3886
+ });
3887
+ }
3888
+ if (indicator) {
3889
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
3890
+ }
3891
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
3892
+ };
3893
+ if (totalPages > 1) {
3894
+ showPage(1);
3507
3895
  }
3508
3896
  const cleanup = () => {
3897
+ indicator?.remove();
3509
3898
  wrapper.remove();
3510
3899
  ctx.container.innerHTML = "";
3511
3900
  };
3512
3901
  ctx.signal.addEventListener("abort", cleanup);
3513
3902
  return {
3514
3903
  destroy: cleanup,
3904
+ getPageCount: () => totalPages,
3905
+ getCurrentPage: () => currentPage,
3906
+ goToPage: (page) => showPage(page),
3515
3907
  zoomIn: () => {
3516
3908
  scale += 0.1;
3517
3909
  wrapper.style.transform = `scale(${scale})`;
@@ -3699,41 +4091,41 @@ var OpenDocumentPlugin = class {
3699
4091
  }
3700
4092
  getToolbarActions(instance) {
3701
4093
  const isPresentation = instance.isPresentation;
4094
+ const totalPages = instance.getPageCount?.() ?? 1;
3702
4095
  const actions = [];
3703
- if (isPresentation) {
3704
- actions.push(
3705
- {
4096
+ if (isPresentation || totalPages > 1) {
4097
+ if (isPresentation) {
4098
+ actions.push({
3706
4099
  id: "thumbnails",
3707
4100
  icon: "thumbnails",
3708
4101
  label: "Slide Thumbnails",
3709
4102
  type: "button",
3710
4103
  group: "navigation",
3711
4104
  execute: () => instance.toggleThumbnails?.()
3712
- },
3713
- {
3714
- id: "page-nav",
3715
- icon: "",
3716
- label: "Slide Navigation",
3717
- type: "page-nav",
3718
- group: "navigation",
3719
- value: instance.getCurrentPage?.() ?? 1,
3720
- max: instance.getPageCount?.() ?? 1,
3721
- execute: (action, page) => {
3722
- if (action === "prev") {
3723
- const cur = instance.getCurrentPage?.() ?? 1;
3724
- if (cur > 1) instance.goToPage?.(cur - 1);
3725
- } else if (action === "next") {
3726
- const cur = instance.getCurrentPage?.() ?? 1;
3727
- const total = instance.getPageCount?.() ?? 1;
3728
- if (cur < total) instance.goToPage?.(cur + 1);
3729
- } else if (typeof page === "number") {
3730
- instance.goToPage?.(page);
3731
- } else if (typeof action === "number") {
3732
- instance.goToPage?.(action);
3733
- }
4105
+ });
4106
+ }
4107
+ actions.push({
4108
+ id: "page-nav",
4109
+ icon: "",
4110
+ label: isPresentation ? "Slide Navigation" : "Page Navigation",
4111
+ type: "page-nav",
4112
+ group: "navigation",
4113
+ value: instance.getCurrentPage?.() ?? 1,
4114
+ max: totalPages,
4115
+ execute: (action, page) => {
4116
+ const cur = instance.getCurrentPage?.() ?? 1;
4117
+ const max = instance.getPageCount?.() ?? 1;
4118
+ if (action === "prev") {
4119
+ if (cur > 1) instance.goToPage?.(cur - 1);
4120
+ } else if (action === "next") {
4121
+ if (cur < max) instance.goToPage?.(cur + 1);
4122
+ } else if (typeof page === "number") {
4123
+ instance.goToPage?.(page);
4124
+ } else if (typeof action === "number") {
4125
+ instance.goToPage?.(action);
3734
4126
  }
3735
4127
  }
3736
- );
4128
+ });
3737
4129
  }
3738
4130
  actions.push(
3739
4131
  {
@@ -4116,7 +4508,31 @@ var DocPlugin = class {
4116
4508
  return this.mimeTypes.includes(mime || "");
4117
4509
  }
4118
4510
  getToolbarActions(instance) {
4119
- return [
4511
+ const totalPages = instance.getPageCount?.() ?? 1;
4512
+ const actions = [];
4513
+ if (totalPages > 1) {
4514
+ actions.push({
4515
+ id: "page-nav",
4516
+ icon: "",
4517
+ label: "Page Navigation",
4518
+ type: "page-nav",
4519
+ group: "navigation",
4520
+ value: instance.getCurrentPage?.() ?? 1,
4521
+ max: totalPages,
4522
+ execute: (action, page) => {
4523
+ const cur = instance.getCurrentPage?.() ?? 1;
4524
+ const max = instance.getPageCount?.() ?? 1;
4525
+ if (action === "prev") {
4526
+ if (cur > 1) instance.goToPage?.(cur - 1);
4527
+ } else if (action === "next") {
4528
+ if (cur < max) instance.goToPage?.(cur + 1);
4529
+ } else if (typeof page === "number") {
4530
+ instance.goToPage?.(page);
4531
+ }
4532
+ }
4533
+ });
4534
+ }
4535
+ actions.push(
4120
4536
  {
4121
4537
  id: "zoom-out",
4122
4538
  icon: "zoom-out",
@@ -4165,7 +4581,8 @@ var DocPlugin = class {
4165
4581
  group: "actions",
4166
4582
  execute: () => instance.print?.()
4167
4583
  }
4168
- ];
4584
+ );
4585
+ return actions;
4169
4586
  }
4170
4587
  async render(ctx) {
4171
4588
  const container = document.createElement("div");
@@ -4192,6 +4609,7 @@ var DocPlugin = class {
4192
4609
  ctx.container.appendChild(container);
4193
4610
  let scale = 1;
4194
4611
  let extractedRawText = "";
4612
+ let isFallback = false;
4195
4613
  try {
4196
4614
  const cfbf = new CfbfReader(ctx.buffer);
4197
4615
  const wordDocStream = cfbf.readStream("WordDocument");
@@ -4205,26 +4623,89 @@ var DocPlugin = class {
4205
4623
  const tableStream = cfbf.readStream(tableName);
4206
4624
  const text = this.extractDocText(wordDocStream, tableStream);
4207
4625
  extractedRawText = text;
4208
- wrapper.innerHTML = this.formatDocToHtml(text, ctx.metadata.name || "Document");
4209
4626
  } catch (err) {
4210
4627
  console.warn("[DocPlugin] Binary parsing error, fallback text:", err);
4211
4628
  const fallback = this.heuristicTextExtraction(ctx.buffer);
4212
4629
  extractedRawText = fallback;
4213
- wrapper.innerHTML = `
4214
- <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4215
- <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${ctx.metadata.name || "Word Document (.doc)"}</h2>
4216
- <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4217
- </div>
4218
- ${this.formatDocToHtml(fallback, ctx.metadata.name || "Document")}
4219
- `;
4630
+ isFallback = true;
4631
+ }
4632
+ const rawPages = this.splitIntoPages(extractedRawText);
4633
+ const totalPages = Math.max(1, rawPages.length);
4634
+ let currentPage = 1;
4635
+ wrapper.innerHTML = "";
4636
+ const pageCards = [];
4637
+ for (let i = 0; i < totalPages; i++) {
4638
+ const pageCard = document.createElement("div");
4639
+ pageCard.className = "fp-doc-page-card";
4640
+ pageCard.style.backgroundColor = "#ffffff";
4641
+ pageCard.style.boxShadow = "0 2px 12px rgba(0,0,0,0.08)";
4642
+ pageCard.style.borderRadius = "4px";
4643
+ pageCard.style.padding = "56px 48px";
4644
+ pageCard.style.minHeight = "100%";
4645
+ pageCard.style.display = i === 0 ? "block" : "none";
4646
+ if (isFallback && i === 0) {
4647
+ pageCard.innerHTML = `
4648
+ <div style="border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 24px;">
4649
+ <h2 style="margin: 0 0 6px; font-size: 20px; color: #334155;">${DOMPurify2.sanitize(ctx.metadata.name || "Word Document (.doc)")}</h2>
4650
+ <span style="font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">Legacy Word 97-2003 Binary Preview</span>
4651
+ </div>
4652
+ ${this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document")}
4653
+ `;
4654
+ } else {
4655
+ pageCard.innerHTML = this.formatDocToHtml(rawPages[i], ctx.metadata.name || "Document");
4656
+ }
4657
+ wrapper.appendChild(pageCard);
4658
+ pageCards.push(pageCard);
4659
+ }
4660
+ let indicator = null;
4661
+ if (totalPages > 1) {
4662
+ indicator = document.createElement("div");
4663
+ indicator.className = "fp-doc-page-indicator";
4664
+ indicator.style.position = "sticky";
4665
+ indicator.style.bottom = "16px";
4666
+ indicator.style.backgroundColor = "rgba(15, 23, 42, 0.85)";
4667
+ indicator.style.backdropFilter = "blur(8px)";
4668
+ indicator.style.color = "#f8fafc";
4669
+ indicator.style.fontSize = "12px";
4670
+ indicator.style.fontWeight = "600";
4671
+ indicator.style.padding = "5px 14px";
4672
+ indicator.style.borderRadius = "20px";
4673
+ indicator.style.border = "1px solid rgba(255, 255, 255, 0.15)";
4674
+ indicator.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.3)";
4675
+ indicator.style.zIndex = "10";
4676
+ indicator.style.userSelect = "none";
4677
+ indicator.style.pointerEvents = "none";
4678
+ indicator.style.textAlign = "center";
4679
+ indicator.style.width = "fit-content";
4680
+ indicator.style.margin = "16px auto 0";
4681
+ container.appendChild(indicator);
4682
+ }
4683
+ const showPage = (pageNum) => {
4684
+ currentPage = Math.max(1, Math.min(totalPages, pageNum));
4685
+ if (totalPages > 1) {
4686
+ pageCards.forEach((card, idx) => {
4687
+ card.style.display = idx + 1 === currentPage ? "block" : "none";
4688
+ });
4689
+ }
4690
+ if (indicator) {
4691
+ indicator.textContent = `Page ${currentPage} of ${totalPages}`;
4692
+ }
4693
+ ctx.emit("page-change", { page: currentPage, total: totalPages });
4694
+ };
4695
+ if (totalPages > 1) {
4696
+ showPage(1);
4220
4697
  }
4221
4698
  const cleanup = () => {
4699
+ indicator?.remove();
4222
4700
  container.remove();
4223
4701
  ctx.container.innerHTML = "";
4224
4702
  };
4225
4703
  ctx.signal.addEventListener("abort", cleanup);
4226
4704
  return {
4227
4705
  destroy: cleanup,
4706
+ getPageCount: () => totalPages,
4707
+ getCurrentPage: () => currentPage,
4708
+ goToPage: (page) => showPage(page),
4228
4709
  zoomIn: () => {
4229
4710
  scale += 0.1;
4230
4711
  wrapper.style.transform = `scale(${scale})`;
@@ -4356,11 +4837,16 @@ var DocPlugin = class {
4356
4837
  heuristicTextExtraction(buffer) {
4357
4838
  return this.extractStringsFromBytes(new Uint8Array(buffer));
4358
4839
  }
4840
+ splitIntoPages(text) {
4841
+ if (!text) return [""];
4842
+ 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);
4843
+ return parts.length > 0 ? parts : [text];
4844
+ }
4359
4845
  /**
4360
4846
  * Format cleaned extracted text into attractive HTML paragraphs, headings, and lists
4361
4847
  */
4362
4848
  formatDocToHtml(text, filename) {
4363
- const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").replace(/\x0C/g, "\n\n").split("\n");
4849
+ const lines = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\x0B/g, "\n").split("\n");
4364
4850
  let html = "";
4365
4851
  let inList = false;
4366
4852
  for (const rawLine of lines) {