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