@files-preview-app/preview-file 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { memo, forwardRef, useRef, useImperativeHandle, useEffect } from 'react';
2
- import DOMPurify5 from 'dompurify';
2
+ import DOMPurify2 from 'dompurify';
3
3
  import * as docx from 'docx-preview';
4
+ import { unzipSync, strFromU8, unzip } from 'fflate';
4
5
  import * as XLSX from 'xlsx';
5
6
  import hljs from 'highlight.js';
6
- import { unzipSync, strFromU8, unzip } from 'fflate';
7
7
  import { marked } from 'marked';
8
8
  import { PptxRenderer } from 'pptx-browser';
9
9
  import * as THREE from 'three';
@@ -341,7 +341,7 @@ async function sourceToArrayBuffer(source, signal) {
341
341
  return { buffer, metadata };
342
342
  }
343
343
  function sanitizeSVG(svg) {
344
- return DOMPurify5.sanitize(svg, {
344
+ return DOMPurify2.sanitize(svg, {
345
345
  USE_PROFILES: { svg: true, svgFilters: true }
346
346
  });
347
347
  }
@@ -401,6 +401,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
401
401
  var ICON_CLOSE = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
402
402
  var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>`;
403
403
  var ICON_COPY = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
404
+ var ICON_FAST_FORWARD = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon></svg>`;
405
+ var ICON_REWIND = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>`;
406
+ var ICON_SPEED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
404
407
  var ICON_MAP = {
405
408
  "zoom-in": ICON_ZOOM_IN,
406
409
  "zoom-out": ICON_ZOOM_OUT,
@@ -417,7 +420,16 @@ var ICON_MAP = {
417
420
  "close": ICON_CLOSE,
418
421
  "copy": ICON_COPY,
419
422
  "prev": ICON_PAGE_PREV,
420
- "next": ICON_PAGE_NEXT
423
+ "next": ICON_PAGE_NEXT,
424
+ "page-prev": ICON_PAGE_PREV,
425
+ "page-next": ICON_PAGE_NEXT,
426
+ "chevron-left": ICON_PAGE_PREV,
427
+ "chevron-right": ICON_PAGE_NEXT,
428
+ "fast-forward": ICON_FAST_FORWARD,
429
+ "forward-10": ICON_FAST_FORWARD,
430
+ "rewind": ICON_REWIND,
431
+ "replay-10": ICON_REWIND,
432
+ "speed": ICON_SPEED
421
433
  };
422
434
  var ToolbarController = class {
423
435
  el;
@@ -449,7 +461,9 @@ var ToolbarController = class {
449
461
  view: [],
450
462
  actions: []
451
463
  };
452
- for (const action of this.actions) {
464
+ const hasPageNav = this.actions.some((a) => a.type === "page-nav");
465
+ const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
466
+ for (const action of effectiveActions) {
453
467
  if (groups[action.group]) {
454
468
  groups[action.group].push(action);
455
469
  }
@@ -472,13 +486,26 @@ var ToolbarController = class {
472
486
  "prev",
473
487
  ICON_PAGE_PREV,
474
488
  "Previous Page",
475
- () => action.execute("prev")
489
+ () => {
490
+ const cur = parseInt(input.value, 10) || 1;
491
+ if (cur > 1) {
492
+ input.value = (cur - 1).toString();
493
+ action.execute("prev", cur - 1);
494
+ }
495
+ }
476
496
  );
477
497
  const nextBtn = this.createButton(
478
498
  "next",
479
499
  ICON_PAGE_NEXT,
480
500
  "Next Page",
481
- () => action.execute("next")
501
+ () => {
502
+ const cur = parseInt(input.value, 10) || 1;
503
+ const max = action.max ?? 1;
504
+ if (cur < max) {
505
+ input.value = (cur + 1).toString();
506
+ action.execute("next", cur + 1);
507
+ }
508
+ }
482
509
  );
483
510
  const input = createElement("input", {
484
511
  className: "fp-toolbar-input",
@@ -488,7 +515,10 @@ var ToolbarController = class {
488
515
  max: (action.max ?? 1).toString()
489
516
  });
490
517
  input.addEventListener("change", () => {
491
- action.execute("go", parseInt(input.value, 10));
518
+ const val = parseInt(input.value, 10);
519
+ if (!isNaN(val)) {
520
+ action.execute("go", val);
521
+ }
492
522
  });
493
523
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
494
524
  groupEl.appendChild(prevBtn);
@@ -529,11 +559,11 @@ var ToolbarController = class {
529
559
  type: "button",
530
560
  "data-action-id": id
531
561
  });
532
- const svg = ICON_MAP[iconHtml] || iconHtml;
533
- if (svg && svg.startsWith("<svg")) {
562
+ const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
563
+ if (svg) {
534
564
  btn.innerHTML = sanitizeSVG(svg);
535
565
  } else {
536
- btn.textContent = svg || title;
566
+ btn.textContent = title || id;
537
567
  }
538
568
  btn.addEventListener("click", onClick);
539
569
  return btn;
@@ -1276,12 +1306,12 @@ function pdfPlugin() {
1276
1306
  }
1277
1307
 
1278
1308
  // ../plugins/media/dist/index.js
1279
- var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"];
1280
- var VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
1281
- var AUDIO_EXTS = [".mp3", ".wav", ".ogg"];
1309
+ var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp", ".ico", ".tiff", ".tif", ".avif"];
1310
+ var VIDEO_EXTS = [".mp4", ".m4v", ".webm", ".ogv", ".ogg", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".3gp", ".mpg", ".mpeg"];
1311
+ var AUDIO_EXTS = [".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a", ".wma", ".opus", ".weba"];
1282
1312
  var MediaPlugin = class {
1283
1313
  id = "media";
1284
- name = "Media Preview";
1314
+ name = "Media Preview (Image, Video, Audio)";
1285
1315
  extensions = [...IMAGE_EXTS, ...VIDEO_EXTS, ...AUDIO_EXTS];
1286
1316
  mimeTypes = [
1287
1317
  "image/jpeg",
@@ -1289,12 +1319,29 @@ var MediaPlugin = class {
1289
1319
  "image/gif",
1290
1320
  "image/webp",
1291
1321
  "image/svg+xml",
1322
+ "image/bmp",
1323
+ "image/x-icon",
1324
+ "image/tiff",
1325
+ "image/avif",
1292
1326
  "video/mp4",
1293
1327
  "video/webm",
1294
1328
  "video/ogg",
1329
+ "video/quicktime",
1330
+ "video/x-msvideo",
1331
+ "video/x-matroska",
1332
+ "video/x-flv",
1333
+ "video/x-ms-wmv",
1334
+ "video/3gpp",
1335
+ "video/mpeg",
1295
1336
  "audio/mpeg",
1296
1337
  "audio/wav",
1297
- "audio/ogg"
1338
+ "audio/ogg",
1339
+ "audio/flac",
1340
+ "audio/aac",
1341
+ "audio/mp4",
1342
+ "audio/x-ms-wma",
1343
+ "audio/opus",
1344
+ "audio/webm"
1298
1345
  ];
1299
1346
  weight = 90;
1300
1347
  supports(file) {
@@ -1350,6 +1397,16 @@ var MediaPlugin = class {
1350
1397
  }
1351
1398
  if (instance.play) {
1352
1399
  actions.push(
1400
+ {
1401
+ id: "replay-10",
1402
+ icon: "replay-10",
1403
+ label: "Rewind 10s",
1404
+ type: "button",
1405
+ group: "actions",
1406
+ execute: () => {
1407
+ instance.rewind?.(10);
1408
+ }
1409
+ },
1353
1410
  {
1354
1411
  id: "play",
1355
1412
  icon: "play",
@@ -1369,6 +1426,26 @@ var MediaPlugin = class {
1369
1426
  execute: () => {
1370
1427
  instance.pause?.();
1371
1428
  }
1429
+ },
1430
+ {
1431
+ id: "forward-10",
1432
+ icon: "forward-10",
1433
+ label: "Fast Forward 10s",
1434
+ type: "button",
1435
+ group: "actions",
1436
+ execute: () => {
1437
+ instance.fastForward?.(10);
1438
+ }
1439
+ },
1440
+ {
1441
+ id: "speed",
1442
+ icon: "speed",
1443
+ label: "Playback Speed",
1444
+ type: "button",
1445
+ group: "actions",
1446
+ execute: () => {
1447
+ instance.cycleSpeed?.();
1448
+ }
1372
1449
  }
1373
1450
  );
1374
1451
  }
@@ -1427,8 +1504,12 @@ var MediaPlugin = class {
1427
1504
  const video = document.createElement("video");
1428
1505
  video.src = url;
1429
1506
  video.controls = true;
1430
- video.style.maxWidth = "100%";
1431
- video.style.maxHeight = "100%";
1507
+ video.playsInline = true;
1508
+ video.style.maxWidth = "90%";
1509
+ video.style.maxHeight = "90%";
1510
+ video.style.borderRadius = "8px";
1511
+ video.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
1512
+ video.style.backgroundColor = "#000000";
1432
1513
  element = video;
1433
1514
  mediaElement = video;
1434
1515
  } else if (isAudio) {
@@ -1487,6 +1568,28 @@ var MediaPlugin = class {
1487
1568
  play: mediaElement ? () => mediaElement?.play() : void 0,
1488
1569
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1489
1570
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1571
+ fastForward: mediaElement ? (seconds = 10) => {
1572
+ if (mediaElement) {
1573
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1574
+ }
1575
+ } : void 0,
1576
+ rewind: mediaElement ? (seconds = 10) => {
1577
+ if (mediaElement) {
1578
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1579
+ }
1580
+ } : void 0,
1581
+ cycleSpeed: mediaElement ? () => {
1582
+ if (mediaElement) {
1583
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1584
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1585
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1586
+ mediaElement.playbackRate = speeds[nextIndex];
1587
+ }
1588
+ } : void 0,
1589
+ setPlaybackRate: mediaElement ? (rate) => {
1590
+ if (mediaElement) mediaElement.playbackRate = rate;
1591
+ } : void 0,
1592
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1490
1593
  download: () => {
1491
1594
  const a = document.createElement("a");
1492
1595
  a.href = url;
@@ -1504,7 +1607,7 @@ function mediaPlugin() {
1504
1607
  }
1505
1608
  var DocxPlugin = class {
1506
1609
  id = "docx";
1507
- name = "Word Document Preview";
1610
+ name = "Word Document Preview (.docx, .docm, .dotx, .dotm)";
1508
1611
  extensions = [".docx", ".docm", ".dotx", ".dotm"];
1509
1612
  mimeTypes = [
1510
1613
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -1526,9 +1629,7 @@ var DocxPlugin = class {
1526
1629
  label: "Zoom Out",
1527
1630
  type: "button",
1528
1631
  group: "zoom",
1529
- execute: () => {
1530
- instance.zoomOut?.();
1531
- }
1632
+ execute: () => instance.zoomOut?.()
1532
1633
  },
1533
1634
  {
1534
1635
  id: "zoom-in",
@@ -1536,9 +1637,7 @@ var DocxPlugin = class {
1536
1637
  label: "Zoom In",
1537
1638
  type: "button",
1538
1639
  group: "zoom",
1539
- execute: () => {
1540
- instance.zoomIn?.();
1541
- }
1640
+ execute: () => instance.zoomIn?.()
1542
1641
  },
1543
1642
  {
1544
1643
  id: "fit-page",
@@ -1546,9 +1645,7 @@ var DocxPlugin = class {
1546
1645
  label: "Fit to Page",
1547
1646
  type: "button",
1548
1647
  group: "zoom",
1549
- execute: () => {
1550
- instance.fitToPage?.();
1551
- }
1648
+ execute: () => instance.fitToPage?.()
1552
1649
  },
1553
1650
  {
1554
1651
  id: "download",
@@ -1556,9 +1653,7 @@ var DocxPlugin = class {
1556
1653
  label: "Download",
1557
1654
  type: "button",
1558
1655
  group: "actions",
1559
- execute: () => {
1560
- instance.download?.();
1561
- }
1656
+ execute: () => instance.download?.()
1562
1657
  },
1563
1658
  {
1564
1659
  id: "print",
@@ -1566,9 +1661,7 @@ var DocxPlugin = class {
1566
1661
  label: "Print",
1567
1662
  type: "button",
1568
1663
  group: "actions",
1569
- execute: () => {
1570
- instance.print?.();
1571
- }
1664
+ execute: () => instance.print?.()
1572
1665
  }
1573
1666
  ];
1574
1667
  }
@@ -1577,27 +1670,81 @@ var DocxPlugin = class {
1577
1670
  wrapper.className = "fp-docx-wrapper";
1578
1671
  wrapper.style.transformOrigin = "top center";
1579
1672
  wrapper.style.transition = "transform 0.2s ease";
1580
- wrapper.style.padding = "16px";
1673
+ wrapper.style.padding = "24px 16px";
1674
+ wrapper.style.maxWidth = "950px";
1675
+ wrapper.style.margin = "0 auto";
1676
+ wrapper.style.boxSizing = "border-box";
1581
1677
  ctx.container.style.overflow = "auto";
1678
+ ctx.container.style.backgroundColor = "#f1f5f9";
1582
1679
  ctx.container.appendChild(wrapper);
1680
+ const styleOverride = document.createElement("style");
1681
+ styleOverride.textContent = `
1682
+ .fp-docx-wrapper .docx-wrapper {
1683
+ background: transparent !important;
1684
+ padding: 0 !important;
1685
+ display: flex !important;
1686
+ flex-direction: column !important;
1687
+ align-items: center !important;
1688
+ box-sizing: border-box !important;
1689
+ }
1690
+ .fp-docx-wrapper section.docx {
1691
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1692
+ border-radius: 4px !important;
1693
+ margin-bottom: 24px !important;
1694
+ background-color: #ffffff !important;
1695
+ }
1696
+ `;
1697
+ ctx.container.appendChild(styleOverride);
1583
1698
  let scale = 1;
1699
+ let renderedSuccessfully = false;
1700
+ const createdBlobUrls = [];
1584
1701
  try {
1585
- await docx.renderAsync(ctx.buffer, wrapper, ctx.container, {
1702
+ await docx.renderAsync(ctx.buffer, wrapper, wrapper, {
1586
1703
  inWrapper: true,
1587
1704
  ignoreWidth: false,
1588
- ignoreHeight: false
1705
+ ignoreHeight: false,
1706
+ ignoreFonts: true,
1707
+ // Avoid crashes on embedded obfuscated fonts
1708
+ breakPages: true,
1709
+ experimental: true
1589
1710
  });
1590
- } catch {
1591
- wrapper.innerHTML = `
1592
- <div style="text-align:center; padding: 40px; color: #666;">
1593
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1594
- <h3>${ctx.metadata.name || "Word Document"}</h3>
1595
- <p>DOCX render preview</p>
1596
- </div>
1597
- `;
1711
+ if (!ctx.container.contains(wrapper)) {
1712
+ ctx.container.appendChild(wrapper);
1713
+ }
1714
+ if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1715
+ renderedSuccessfully = true;
1716
+ }
1717
+ } catch (err) {
1718
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1719
+ }
1720
+ if (!renderedSuccessfully) {
1721
+ try {
1722
+ wrapper.innerHTML = "";
1723
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1724
+ if (!ctx.container.contains(wrapper)) {
1725
+ ctx.container.appendChild(wrapper);
1726
+ }
1727
+ renderedSuccessfully = true;
1728
+ } catch (fallbackErr) {
1729
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1730
+ wrapper.innerHTML = `
1731
+ <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1732
+ <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1733
+ <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
1734
+ <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1735
+ </div>
1736
+ `;
1737
+ if (!ctx.container.contains(wrapper)) {
1738
+ ctx.container.appendChild(wrapper);
1739
+ }
1740
+ }
1598
1741
  }
1599
1742
  const cleanup = () => {
1743
+ for (const url of createdBlobUrls) {
1744
+ URL.revokeObjectURL(url);
1745
+ }
1600
1746
  wrapper.remove();
1747
+ styleOverride.remove();
1601
1748
  ctx.container.innerHTML = "";
1602
1749
  };
1603
1750
  ctx.signal.addEventListener("abort", cleanup);
@@ -1618,7 +1765,7 @@ var DocxPlugin = class {
1618
1765
  },
1619
1766
  fitToPage: () => {
1620
1767
  scale = 1;
1621
- wrapper.style.transform = `scale(1)`;
1768
+ wrapper.style.transform = "scale(1)";
1622
1769
  },
1623
1770
  download: () => {
1624
1771
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -1634,6 +1781,212 @@ var DocxPlugin = class {
1634
1781
  }
1635
1782
  };
1636
1783
  }
1784
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1785
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1786
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1787
+ this.renderBinaryDocFallback(ctx, wrapper);
1788
+ return;
1789
+ }
1790
+ const unzipped = unzipSync(new Uint8Array(ctx.buffer));
1791
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1792
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1793
+ if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1794
+ const xmlStr = strFromU8(docXmlEntry);
1795
+ const parser = new DOMParser();
1796
+ const doc = parser.parseFromString(xmlStr, "application/xml");
1797
+ const imageMap = {};
1798
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1799
+ if (relsKey && unzipped[relsKey]) {
1800
+ try {
1801
+ const relsXml = strFromU8(unzipped[relsKey]);
1802
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1803
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1804
+ for (const rel of relEls) {
1805
+ const rId = rel.getAttribute("Id");
1806
+ const target = rel.getAttribute("Target");
1807
+ if (rId && target) {
1808
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1809
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1810
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1811
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1812
+ });
1813
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1814
+ const bytes = unzipped[fullTargetKey];
1815
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1816
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1817
+ const blob = new Blob([bytes], { type: mime });
1818
+ const blobUrl = URL.createObjectURL(blob);
1819
+ imageMap[rId] = blobUrl;
1820
+ createdBlobUrls.push(blobUrl);
1821
+ }
1822
+ }
1823
+ }
1824
+ } catch (relsErr) {
1825
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1826
+ }
1827
+ }
1828
+ const card = document.createElement("div");
1829
+ card.className = "fp-docx-page-card";
1830
+ card.style.backgroundColor = "#ffffff";
1831
+ card.style.borderRadius = "6px";
1832
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1833
+ card.style.padding = "48px 56px";
1834
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1835
+ card.style.color = "#1e293b";
1836
+ card.style.lineHeight = "1.6";
1837
+ const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
1838
+ let html = "";
1839
+ for (const child of Array.from(body.children)) {
1840
+ const tag = child.localName || child.nodeName.split(":").pop();
1841
+ if (tag === "p") {
1842
+ const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1843
+ const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1844
+ const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1845
+ const textContent = this.extractParagraphHtml(child, imageMap);
1846
+ if (!textContent.trim()) {
1847
+ html += '<div style="height: 10px;"></div>';
1848
+ continue;
1849
+ }
1850
+ const lowerStyle = styleVal.toLowerCase();
1851
+ if (lowerStyle.includes("title")) {
1852
+ html += `<h1 style="font-size: 28px; font-weight: 700; color: #1e3a8a; margin: 24px 0 12px; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px;">${textContent}</h1>`;
1853
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
1854
+ html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
1855
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
1856
+ html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
1857
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
1858
+ html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
1859
+ } else if (numPr) {
1860
+ html += `<div style="display: flex; gap: 8px; margin: 4px 0 4px 20px;"><span style="color: #2563eb; font-weight: bold;">\u2022</span><span>${textContent}</span></div>`;
1861
+ } else {
1862
+ html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
1863
+ }
1864
+ } else if (tag === "tbl") {
1865
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
1866
+ const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
1867
+ rows.forEach((tr, rIdx) => {
1868
+ html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1869
+ const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1870
+ cells.forEach((tc) => {
1871
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1872
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1873
+ });
1874
+ html += "</tr>";
1875
+ });
1876
+ html += "</table>";
1877
+ }
1878
+ }
1879
+ card.innerHTML = DOMPurify2.sanitize(html, {
1880
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1881
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1882
+ });
1883
+ wrapper.appendChild(card);
1884
+ }
1885
+ extractParagraphHtml(pElement, imageMap = {}) {
1886
+ let result = "";
1887
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1888
+ for (const drawing of drawings) {
1889
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1890
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1891
+ if (rId && imageMap[rId]) {
1892
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1893
+ }
1894
+ }
1895
+ const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1896
+ if (runs.length === 0 && !result) {
1897
+ return DOMPurify2.sanitize(pElement.textContent || "");
1898
+ }
1899
+ for (const r of runs) {
1900
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1901
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1902
+ if (rId && imageMap[rId]) {
1903
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1904
+ }
1905
+ const brs = r.getElementsByTagNameNS("*", "br");
1906
+ for (let i = 0; i < brs.length; i++) {
1907
+ result += "<br/>";
1908
+ }
1909
+ const tabs = r.getElementsByTagNameNS("*", "tab");
1910
+ if (tabs.length > 0) {
1911
+ result += "&emsp;";
1912
+ }
1913
+ const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1914
+ const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1915
+ const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1916
+ const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
1917
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1918
+ const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1919
+ const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
1920
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
1921
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1922
+ const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1923
+ let text = texts.map((t) => t.textContent || "").join("");
1924
+ if (!text) continue;
1925
+ text = DOMPurify2.sanitize(text);
1926
+ let styles = "";
1927
+ if (isBold) styles += "font-weight: bold; ";
1928
+ if (isItalic) styles += "font-style: italic; ";
1929
+ if (isUnderline) styles += "text-decoration: underline; ";
1930
+ if (isStrike) styles += "text-decoration: line-through; ";
1931
+ if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
1932
+ if (szVal) {
1933
+ const pt = parseInt(szVal, 10) / 2;
1934
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
1935
+ }
1936
+ if (styles) {
1937
+ result += `<span style="${styles}">${text}</span>`;
1938
+ } else {
1939
+ result += text;
1940
+ }
1941
+ }
1942
+ return result;
1943
+ }
1944
+ renderBinaryDocFallback(ctx, wrapper) {
1945
+ const card = document.createElement("div");
1946
+ card.className = "fp-docx-page-card";
1947
+ card.style.backgroundColor = "#ffffff";
1948
+ card.style.borderRadius = "6px";
1949
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1950
+ card.style.padding = "48px 56px";
1951
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1952
+ card.style.color = "#1e293b";
1953
+ card.style.lineHeight = "1.6";
1954
+ try {
1955
+ const cfbf = new CfbfReader(ctx.buffer);
1956
+ const wordDocStream = cfbf.readStream("WordDocument");
1957
+ if (wordDocStream && wordDocStream.length >= 512) {
1958
+ const text2 = this.extractReadableStrings(wordDocStream);
1959
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
1960
+ wrapper.appendChild(card);
1961
+ return;
1962
+ }
1963
+ } catch {
1964
+ }
1965
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
1966
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
1967
+ wrapper.appendChild(card);
1968
+ }
1969
+ extractReadableStrings(bytes) {
1970
+ let result = "";
1971
+ let current = "";
1972
+ for (let i = 0; i < bytes.length; i++) {
1973
+ const b = bytes[i];
1974
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
1975
+ current += String.fromCharCode(b);
1976
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
1977
+ continue;
1978
+ } else {
1979
+ if (current.trim().length > 3) {
1980
+ result += current.trim() + "\n\n";
1981
+ }
1982
+ current = "";
1983
+ }
1984
+ }
1985
+ if (current.trim().length > 3) {
1986
+ result += current.trim();
1987
+ }
1988
+ return result;
1989
+ }
1637
1990
  };
1638
1991
  function docxPlugin() {
1639
1992
  return new DocxPlugin();
@@ -2445,7 +2798,7 @@ var MarkdownPlugin = class {
2445
2798
  gfm: true,
2446
2799
  breaks: true
2447
2800
  });
2448
- const cleanHtml = DOMPurify5.sanitize(rawHtml, {
2801
+ const cleanHtml = DOMPurify2.sanitize(rawHtml, {
2449
2802
  USE_PROFILES: { html: true }
2450
2803
  });
2451
2804
  const wrapper = document.createElement("div");
@@ -2609,35 +2962,27 @@ var PptxPlugin = class {
2609
2962
  group: "navigation",
2610
2963
  execute: () => instance.toggleThumbnails?.()
2611
2964
  },
2612
- {
2613
- id: "page-prev",
2614
- icon: "page-prev",
2615
- label: "Previous Slide",
2616
- type: "button",
2617
- group: "navigation",
2618
- execute: () => {
2619
- const cur = instance.getCurrentPage?.() ?? 1;
2620
- if (cur > 1) instance.goToPage?.(cur - 1);
2621
- }
2622
- },
2623
2965
  {
2624
2966
  id: "page-nav",
2625
2967
  icon: "",
2626
- label: "Slide Number",
2968
+ label: "Slide Navigation",
2627
2969
  type: "page-nav",
2628
2970
  group: "navigation",
2629
- execute: (page) => instance.goToPage?.(Number(page))
2630
- },
2631
- {
2632
- id: "page-next",
2633
- icon: "page-next",
2634
- label: "Next Slide",
2635
- type: "button",
2636
- group: "navigation",
2637
- execute: () => {
2638
- const cur = instance.getCurrentPage?.() ?? 1;
2639
- const total = instance.getPageCount?.() ?? 1;
2640
- if (cur < total) instance.goToPage?.(cur + 1);
2971
+ value: instance.getCurrentPage?.() ?? 1,
2972
+ max: instance.getPageCount?.() ?? 1,
2973
+ execute: (action, page) => {
2974
+ if (action === "prev") {
2975
+ const cur = instance.getCurrentPage?.() ?? 1;
2976
+ if (cur > 1) instance.goToPage?.(cur - 1);
2977
+ } else if (action === "next") {
2978
+ const cur = instance.getCurrentPage?.() ?? 1;
2979
+ const total = instance.getPageCount?.() ?? 1;
2980
+ if (cur < total) instance.goToPage?.(cur + 1);
2981
+ } else if (typeof page === "number") {
2982
+ instance.goToPage?.(page);
2983
+ } else if (typeof action === "number") {
2984
+ instance.goToPage?.(action);
2985
+ }
2641
2986
  }
2642
2987
  },
2643
2988
  {
@@ -3168,7 +3513,7 @@ var HtmlPreviewPlugin = class {
3168
3513
  }
3169
3514
  async render(ctx) {
3170
3515
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3171
- const sanitized = DOMPurify5.sanitize(rawHtml, {
3516
+ const sanitized = DOMPurify2.sanitize(rawHtml, {
3172
3517
  WHOLE_DOCUMENT: true,
3173
3518
  ADD_TAGS: ["style", "link"],
3174
3519
  ADD_ATTR: ["target", "rel"]
@@ -3263,35 +3608,27 @@ var OpenDocumentPlugin = class {
3263
3608
  group: "navigation",
3264
3609
  execute: () => instance.toggleThumbnails?.()
3265
3610
  },
3266
- {
3267
- id: "page-prev",
3268
- icon: "page-prev",
3269
- label: "Previous Slide",
3270
- type: "button",
3271
- group: "navigation",
3272
- execute: () => {
3273
- const cur = instance.getCurrentPage?.() ?? 1;
3274
- if (cur > 1) instance.goToPage?.(cur - 1);
3275
- }
3276
- },
3277
3611
  {
3278
3612
  id: "page-nav",
3279
3613
  icon: "",
3280
- label: "Slide Number",
3614
+ label: "Slide Navigation",
3281
3615
  type: "page-nav",
3282
3616
  group: "navigation",
3283
- execute: (p) => instance.goToPage?.(Number(p))
3284
- },
3285
- {
3286
- id: "page-next",
3287
- icon: "page-next",
3288
- label: "Next Slide",
3289
- type: "button",
3290
- group: "navigation",
3291
- execute: () => {
3292
- const cur = instance.getCurrentPage?.() ?? 1;
3293
- const total = instance.getPageCount?.() ?? 1;
3294
- if (cur < total) instance.goToPage?.(cur + 1);
3617
+ value: instance.getCurrentPage?.() ?? 1,
3618
+ max: instance.getPageCount?.() ?? 1,
3619
+ execute: (action, page) => {
3620
+ if (action === "prev") {
3621
+ const cur = instance.getCurrentPage?.() ?? 1;
3622
+ if (cur > 1) instance.goToPage?.(cur - 1);
3623
+ } else if (action === "next") {
3624
+ const cur = instance.getCurrentPage?.() ?? 1;
3625
+ const total = instance.getPageCount?.() ?? 1;
3626
+ if (cur < total) instance.goToPage?.(cur + 1);
3627
+ } else if (typeof page === "number") {
3628
+ instance.goToPage?.(page);
3629
+ } else if (typeof action === "number") {
3630
+ instance.goToPage?.(action);
3631
+ }
3295
3632
  }
3296
3633
  }
3297
3634
  );
@@ -3419,7 +3756,7 @@ var OpenDocumentPlugin = class {
3419
3756
  wrapper.style.padding = "48px";
3420
3757
  wrapper.style.minHeight = "100%";
3421
3758
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3422
- wrapper.innerHTML = DOMPurify5.sanitize(bodyHtml, {
3759
+ wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
3423
3760
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3424
3761
  ADD_ATTR: ["style", "colspan", "rowspan"]
3425
3762
  });
@@ -3939,24 +4276,24 @@ var DocPlugin = class {
3939
4276
  html += "</ul>";
3940
4277
  inList = false;
3941
4278
  }
3942
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify5.sanitize(line)}</h2>`;
4279
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2.sanitize(line)}</h2>`;
3943
4280
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
3944
4281
  if (!inList) {
3945
4282
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
3946
4283
  inList = true;
3947
4284
  }
3948
4285
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
3949
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify5.sanitize(bulletText)}</li>`;
4286
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
3950
4287
  } else {
3951
4288
  if (inList) {
3952
4289
  html += "</ul>";
3953
4290
  inList = false;
3954
4291
  }
3955
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify5.sanitize(line)}</p>`;
4292
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
3956
4293
  }
3957
4294
  }
3958
4295
  if (inList) html += "</ul>";
3959
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify5.sanitize(filename)})</p>`;
4296
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
3960
4297
  }
3961
4298
  };
3962
4299
  function docPlugin() {
@@ -3983,35 +4320,27 @@ var PptPlugin = class {
3983
4320
  group: "navigation",
3984
4321
  execute: () => instance.toggleThumbnails?.()
3985
4322
  },
3986
- {
3987
- id: "page-prev",
3988
- icon: "page-prev",
3989
- label: "Previous Slide",
3990
- type: "button",
3991
- group: "navigation",
3992
- execute: () => {
3993
- const cur = instance.getCurrentPage?.() ?? 1;
3994
- if (cur > 1) instance.goToPage?.(cur - 1);
3995
- }
3996
- },
3997
4323
  {
3998
4324
  id: "page-nav",
3999
4325
  icon: "",
4000
- label: "Slide Number",
4326
+ label: "Slide Navigation",
4001
4327
  type: "page-nav",
4002
4328
  group: "navigation",
4003
- execute: (p) => instance.goToPage?.(Number(p))
4004
- },
4005
- {
4006
- id: "page-next",
4007
- icon: "page-next",
4008
- label: "Next Slide",
4009
- type: "button",
4010
- group: "navigation",
4011
- execute: () => {
4012
- const cur = instance.getCurrentPage?.() ?? 1;
4013
- const total = instance.getPageCount?.() ?? 1;
4014
- if (cur < total) instance.goToPage?.(cur + 1);
4329
+ value: instance.getCurrentPage?.() ?? 1,
4330
+ max: instance.getPageCount?.() ?? 1,
4331
+ execute: (action, page) => {
4332
+ if (action === "prev") {
4333
+ const cur = instance.getCurrentPage?.() ?? 1;
4334
+ if (cur > 1) instance.goToPage?.(cur - 1);
4335
+ } else if (action === "next") {
4336
+ const cur = instance.getCurrentPage?.() ?? 1;
4337
+ const total = instance.getPageCount?.() ?? 1;
4338
+ if (cur < total) instance.goToPage?.(cur + 1);
4339
+ } else if (typeof page === "number") {
4340
+ instance.goToPage?.(page);
4341
+ } else if (typeof action === "number") {
4342
+ instance.goToPage?.(action);
4343
+ }
4015
4344
  }
4016
4345
  },
4017
4346
  {
@@ -4119,10 +4448,10 @@ var PptPlugin = class {
4119
4448
  </div>
4120
4449
  <div style="text-align: center; width: 100%;">
4121
4450
  <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4122
- ${DOMPurify5.sanitize(s.title || `Slide ${idx}`)}
4451
+ ${DOMPurify2.sanitize(s.title || `Slide ${idx}`)}
4123
4452
  </h1>
4124
4453
  <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4125
- ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify5.sanitize(t)}</div>`).join("")}
4454
+ ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify2.sanitize(t)}</div>`).join("")}
4126
4455
  </div>
4127
4456
  </div>
4128
4457
  `;