@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/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import DOMPurify5 from 'dompurify';
1
+ import DOMPurify2 from 'dompurify';
2
2
  import * as docx from 'docx-preview';
3
+ import { unzipSync, strFromU8, unzip } from 'fflate';
3
4
  import * as XLSX from 'xlsx';
4
5
  import hljs from 'highlight.js';
5
- import { unzip, unzipSync, strFromU8 } from 'fflate';
6
6
  import { marked } from 'marked';
7
7
  import { PptxRenderer } from 'pptx-browser';
8
8
  import * as THREE from 'three';
@@ -339,12 +339,12 @@ async function sourceToArrayBuffer(source, signal) {
339
339
  return { buffer, metadata };
340
340
  }
341
341
  function sanitizeHTML(html) {
342
- return DOMPurify5.sanitize(html, {
342
+ return DOMPurify2.sanitize(html, {
343
343
  USE_PROFILES: { html: true }
344
344
  });
345
345
  }
346
346
  function sanitizeSVG(svg) {
347
- return DOMPurify5.sanitize(svg, {
347
+ return DOMPurify2.sanitize(svg, {
348
348
  USE_PROFILES: { svg: true, svgFilters: true }
349
349
  });
350
350
  }
@@ -443,6 +443,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
443
443
  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>`;
444
444
  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>`;
445
445
  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>`;
446
+ 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>`;
447
+ 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>`;
448
+ 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>`;
446
449
  var ICON_MAP = {
447
450
  "zoom-in": ICON_ZOOM_IN,
448
451
  "zoom-out": ICON_ZOOM_OUT,
@@ -459,7 +462,16 @@ var ICON_MAP = {
459
462
  "close": ICON_CLOSE,
460
463
  "copy": ICON_COPY,
461
464
  "prev": ICON_PAGE_PREV,
462
- "next": ICON_PAGE_NEXT
465
+ "next": ICON_PAGE_NEXT,
466
+ "page-prev": ICON_PAGE_PREV,
467
+ "page-next": ICON_PAGE_NEXT,
468
+ "chevron-left": ICON_PAGE_PREV,
469
+ "chevron-right": ICON_PAGE_NEXT,
470
+ "fast-forward": ICON_FAST_FORWARD,
471
+ "forward-10": ICON_FAST_FORWARD,
472
+ "rewind": ICON_REWIND,
473
+ "replay-10": ICON_REWIND,
474
+ "speed": ICON_SPEED
463
475
  };
464
476
  var ToolbarController = class {
465
477
  el;
@@ -491,7 +503,9 @@ var ToolbarController = class {
491
503
  view: [],
492
504
  actions: []
493
505
  };
494
- for (const action of this.actions) {
506
+ const hasPageNav = this.actions.some((a) => a.type === "page-nav");
507
+ const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
508
+ for (const action of effectiveActions) {
495
509
  if (groups[action.group]) {
496
510
  groups[action.group].push(action);
497
511
  }
@@ -514,13 +528,26 @@ var ToolbarController = class {
514
528
  "prev",
515
529
  ICON_PAGE_PREV,
516
530
  "Previous Page",
517
- () => action.execute("prev")
531
+ () => {
532
+ const cur = parseInt(input.value, 10) || 1;
533
+ if (cur > 1) {
534
+ input.value = (cur - 1).toString();
535
+ action.execute("prev", cur - 1);
536
+ }
537
+ }
518
538
  );
519
539
  const nextBtn = this.createButton(
520
540
  "next",
521
541
  ICON_PAGE_NEXT,
522
542
  "Next Page",
523
- () => action.execute("next")
543
+ () => {
544
+ const cur = parseInt(input.value, 10) || 1;
545
+ const max = action.max ?? 1;
546
+ if (cur < max) {
547
+ input.value = (cur + 1).toString();
548
+ action.execute("next", cur + 1);
549
+ }
550
+ }
524
551
  );
525
552
  const input = createElement("input", {
526
553
  className: "fp-toolbar-input",
@@ -530,7 +557,10 @@ var ToolbarController = class {
530
557
  max: (action.max ?? 1).toString()
531
558
  });
532
559
  input.addEventListener("change", () => {
533
- action.execute("go", parseInt(input.value, 10));
560
+ const val = parseInt(input.value, 10);
561
+ if (!isNaN(val)) {
562
+ action.execute("go", val);
563
+ }
534
564
  });
535
565
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
536
566
  groupEl.appendChild(prevBtn);
@@ -571,11 +601,11 @@ var ToolbarController = class {
571
601
  type: "button",
572
602
  "data-action-id": id
573
603
  });
574
- const svg = ICON_MAP[iconHtml] || iconHtml;
575
- if (svg && svg.startsWith("<svg")) {
604
+ const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
605
+ if (svg) {
576
606
  btn.innerHTML = sanitizeSVG(svg);
577
607
  } else {
578
- btn.textContent = svg || title;
608
+ btn.textContent = title || id;
579
609
  }
580
610
  btn.addEventListener("click", onClick);
581
611
  return btn;
@@ -1318,12 +1348,12 @@ function pdfPlugin() {
1318
1348
  }
1319
1349
 
1320
1350
  // ../plugins/media/dist/index.js
1321
- var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"];
1322
- var VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
1323
- var AUDIO_EXTS = [".mp3", ".wav", ".ogg"];
1351
+ var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp", ".ico", ".tiff", ".tif", ".avif"];
1352
+ var VIDEO_EXTS = [".mp4", ".m4v", ".webm", ".ogv", ".ogg", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".3gp", ".mpg", ".mpeg"];
1353
+ var AUDIO_EXTS = [".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a", ".wma", ".opus", ".weba"];
1324
1354
  var MediaPlugin = class {
1325
1355
  id = "media";
1326
- name = "Media Preview";
1356
+ name = "Media Preview (Image, Video, Audio)";
1327
1357
  extensions = [...IMAGE_EXTS, ...VIDEO_EXTS, ...AUDIO_EXTS];
1328
1358
  mimeTypes = [
1329
1359
  "image/jpeg",
@@ -1331,12 +1361,29 @@ var MediaPlugin = class {
1331
1361
  "image/gif",
1332
1362
  "image/webp",
1333
1363
  "image/svg+xml",
1364
+ "image/bmp",
1365
+ "image/x-icon",
1366
+ "image/tiff",
1367
+ "image/avif",
1334
1368
  "video/mp4",
1335
1369
  "video/webm",
1336
1370
  "video/ogg",
1371
+ "video/quicktime",
1372
+ "video/x-msvideo",
1373
+ "video/x-matroska",
1374
+ "video/x-flv",
1375
+ "video/x-ms-wmv",
1376
+ "video/3gpp",
1377
+ "video/mpeg",
1337
1378
  "audio/mpeg",
1338
1379
  "audio/wav",
1339
- "audio/ogg"
1380
+ "audio/ogg",
1381
+ "audio/flac",
1382
+ "audio/aac",
1383
+ "audio/mp4",
1384
+ "audio/x-ms-wma",
1385
+ "audio/opus",
1386
+ "audio/webm"
1340
1387
  ];
1341
1388
  weight = 90;
1342
1389
  supports(file) {
@@ -1392,6 +1439,16 @@ var MediaPlugin = class {
1392
1439
  }
1393
1440
  if (instance.play) {
1394
1441
  actions.push(
1442
+ {
1443
+ id: "replay-10",
1444
+ icon: "replay-10",
1445
+ label: "Rewind 10s",
1446
+ type: "button",
1447
+ group: "actions",
1448
+ execute: () => {
1449
+ instance.rewind?.(10);
1450
+ }
1451
+ },
1395
1452
  {
1396
1453
  id: "play",
1397
1454
  icon: "play",
@@ -1411,6 +1468,26 @@ var MediaPlugin = class {
1411
1468
  execute: () => {
1412
1469
  instance.pause?.();
1413
1470
  }
1471
+ },
1472
+ {
1473
+ id: "forward-10",
1474
+ icon: "forward-10",
1475
+ label: "Fast Forward 10s",
1476
+ type: "button",
1477
+ group: "actions",
1478
+ execute: () => {
1479
+ instance.fastForward?.(10);
1480
+ }
1481
+ },
1482
+ {
1483
+ id: "speed",
1484
+ icon: "speed",
1485
+ label: "Playback Speed",
1486
+ type: "button",
1487
+ group: "actions",
1488
+ execute: () => {
1489
+ instance.cycleSpeed?.();
1490
+ }
1414
1491
  }
1415
1492
  );
1416
1493
  }
@@ -1469,8 +1546,12 @@ var MediaPlugin = class {
1469
1546
  const video = document.createElement("video");
1470
1547
  video.src = url;
1471
1548
  video.controls = true;
1472
- video.style.maxWidth = "100%";
1473
- video.style.maxHeight = "100%";
1549
+ video.playsInline = true;
1550
+ video.style.maxWidth = "90%";
1551
+ video.style.maxHeight = "90%";
1552
+ video.style.borderRadius = "8px";
1553
+ video.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
1554
+ video.style.backgroundColor = "#000000";
1474
1555
  element = video;
1475
1556
  mediaElement = video;
1476
1557
  } else if (isAudio) {
@@ -1529,6 +1610,28 @@ var MediaPlugin = class {
1529
1610
  play: mediaElement ? () => mediaElement?.play() : void 0,
1530
1611
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1531
1612
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1613
+ fastForward: mediaElement ? (seconds = 10) => {
1614
+ if (mediaElement) {
1615
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1616
+ }
1617
+ } : void 0,
1618
+ rewind: mediaElement ? (seconds = 10) => {
1619
+ if (mediaElement) {
1620
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1621
+ }
1622
+ } : void 0,
1623
+ cycleSpeed: mediaElement ? () => {
1624
+ if (mediaElement) {
1625
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1626
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1627
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1628
+ mediaElement.playbackRate = speeds[nextIndex];
1629
+ }
1630
+ } : void 0,
1631
+ setPlaybackRate: mediaElement ? (rate) => {
1632
+ if (mediaElement) mediaElement.playbackRate = rate;
1633
+ } : void 0,
1634
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1532
1635
  download: () => {
1533
1636
  const a = document.createElement("a");
1534
1637
  a.href = url;
@@ -1546,7 +1649,7 @@ function mediaPlugin() {
1546
1649
  }
1547
1650
  var DocxPlugin = class {
1548
1651
  id = "docx";
1549
- name = "Word Document Preview";
1652
+ name = "Word Document Preview (.docx, .docm, .dotx, .dotm)";
1550
1653
  extensions = [".docx", ".docm", ".dotx", ".dotm"];
1551
1654
  mimeTypes = [
1552
1655
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -1568,9 +1671,7 @@ var DocxPlugin = class {
1568
1671
  label: "Zoom Out",
1569
1672
  type: "button",
1570
1673
  group: "zoom",
1571
- execute: () => {
1572
- instance.zoomOut?.();
1573
- }
1674
+ execute: () => instance.zoomOut?.()
1574
1675
  },
1575
1676
  {
1576
1677
  id: "zoom-in",
@@ -1578,9 +1679,7 @@ var DocxPlugin = class {
1578
1679
  label: "Zoom In",
1579
1680
  type: "button",
1580
1681
  group: "zoom",
1581
- execute: () => {
1582
- instance.zoomIn?.();
1583
- }
1682
+ execute: () => instance.zoomIn?.()
1584
1683
  },
1585
1684
  {
1586
1685
  id: "fit-page",
@@ -1588,9 +1687,7 @@ var DocxPlugin = class {
1588
1687
  label: "Fit to Page",
1589
1688
  type: "button",
1590
1689
  group: "zoom",
1591
- execute: () => {
1592
- instance.fitToPage?.();
1593
- }
1690
+ execute: () => instance.fitToPage?.()
1594
1691
  },
1595
1692
  {
1596
1693
  id: "download",
@@ -1598,9 +1695,7 @@ var DocxPlugin = class {
1598
1695
  label: "Download",
1599
1696
  type: "button",
1600
1697
  group: "actions",
1601
- execute: () => {
1602
- instance.download?.();
1603
- }
1698
+ execute: () => instance.download?.()
1604
1699
  },
1605
1700
  {
1606
1701
  id: "print",
@@ -1608,9 +1703,7 @@ var DocxPlugin = class {
1608
1703
  label: "Print",
1609
1704
  type: "button",
1610
1705
  group: "actions",
1611
- execute: () => {
1612
- instance.print?.();
1613
- }
1706
+ execute: () => instance.print?.()
1614
1707
  }
1615
1708
  ];
1616
1709
  }
@@ -1619,27 +1712,81 @@ var DocxPlugin = class {
1619
1712
  wrapper.className = "fp-docx-wrapper";
1620
1713
  wrapper.style.transformOrigin = "top center";
1621
1714
  wrapper.style.transition = "transform 0.2s ease";
1622
- wrapper.style.padding = "16px";
1715
+ wrapper.style.padding = "24px 16px";
1716
+ wrapper.style.maxWidth = "950px";
1717
+ wrapper.style.margin = "0 auto";
1718
+ wrapper.style.boxSizing = "border-box";
1623
1719
  ctx.container.style.overflow = "auto";
1720
+ ctx.container.style.backgroundColor = "#f1f5f9";
1624
1721
  ctx.container.appendChild(wrapper);
1722
+ const styleOverride = document.createElement("style");
1723
+ styleOverride.textContent = `
1724
+ .fp-docx-wrapper .docx-wrapper {
1725
+ background: transparent !important;
1726
+ padding: 0 !important;
1727
+ display: flex !important;
1728
+ flex-direction: column !important;
1729
+ align-items: center !important;
1730
+ box-sizing: border-box !important;
1731
+ }
1732
+ .fp-docx-wrapper section.docx {
1733
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1734
+ border-radius: 4px !important;
1735
+ margin-bottom: 24px !important;
1736
+ background-color: #ffffff !important;
1737
+ }
1738
+ `;
1739
+ ctx.container.appendChild(styleOverride);
1625
1740
  let scale = 1;
1741
+ let renderedSuccessfully = false;
1742
+ const createdBlobUrls = [];
1626
1743
  try {
1627
- await docx.renderAsync(ctx.buffer, wrapper, ctx.container, {
1744
+ await docx.renderAsync(ctx.buffer, wrapper, wrapper, {
1628
1745
  inWrapper: true,
1629
1746
  ignoreWidth: false,
1630
- ignoreHeight: false
1747
+ ignoreHeight: false,
1748
+ ignoreFonts: true,
1749
+ // Avoid crashes on embedded obfuscated fonts
1750
+ breakPages: true,
1751
+ experimental: true
1631
1752
  });
1632
- } catch {
1633
- wrapper.innerHTML = `
1634
- <div style="text-align:center; padding: 40px; color: #666;">
1635
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1636
- <h3>${ctx.metadata.name || "Word Document"}</h3>
1637
- <p>DOCX render preview</p>
1638
- </div>
1639
- `;
1753
+ if (!ctx.container.contains(wrapper)) {
1754
+ ctx.container.appendChild(wrapper);
1755
+ }
1756
+ if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1757
+ renderedSuccessfully = true;
1758
+ }
1759
+ } catch (err) {
1760
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1761
+ }
1762
+ if (!renderedSuccessfully) {
1763
+ try {
1764
+ wrapper.innerHTML = "";
1765
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1766
+ if (!ctx.container.contains(wrapper)) {
1767
+ ctx.container.appendChild(wrapper);
1768
+ }
1769
+ renderedSuccessfully = true;
1770
+ } catch (fallbackErr) {
1771
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1772
+ wrapper.innerHTML = `
1773
+ <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1774
+ <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1775
+ <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
1776
+ <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1777
+ </div>
1778
+ `;
1779
+ if (!ctx.container.contains(wrapper)) {
1780
+ ctx.container.appendChild(wrapper);
1781
+ }
1782
+ }
1640
1783
  }
1641
1784
  const cleanup = () => {
1785
+ for (const url of createdBlobUrls) {
1786
+ URL.revokeObjectURL(url);
1787
+ }
1642
1788
  wrapper.remove();
1789
+ styleOverride.remove();
1643
1790
  ctx.container.innerHTML = "";
1644
1791
  };
1645
1792
  ctx.signal.addEventListener("abort", cleanup);
@@ -1660,7 +1807,7 @@ var DocxPlugin = class {
1660
1807
  },
1661
1808
  fitToPage: () => {
1662
1809
  scale = 1;
1663
- wrapper.style.transform = `scale(1)`;
1810
+ wrapper.style.transform = "scale(1)";
1664
1811
  },
1665
1812
  download: () => {
1666
1813
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -1676,6 +1823,212 @@ var DocxPlugin = class {
1676
1823
  }
1677
1824
  };
1678
1825
  }
1826
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1827
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1828
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1829
+ this.renderBinaryDocFallback(ctx, wrapper);
1830
+ return;
1831
+ }
1832
+ const unzipped = unzipSync(new Uint8Array(ctx.buffer));
1833
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1834
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1835
+ if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1836
+ const xmlStr = strFromU8(docXmlEntry);
1837
+ const parser = new DOMParser();
1838
+ const doc = parser.parseFromString(xmlStr, "application/xml");
1839
+ const imageMap = {};
1840
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1841
+ if (relsKey && unzipped[relsKey]) {
1842
+ try {
1843
+ const relsXml = strFromU8(unzipped[relsKey]);
1844
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1845
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1846
+ for (const rel of relEls) {
1847
+ const rId = rel.getAttribute("Id");
1848
+ const target = rel.getAttribute("Target");
1849
+ if (rId && target) {
1850
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1851
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1852
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1853
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1854
+ });
1855
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1856
+ const bytes = unzipped[fullTargetKey];
1857
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1858
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1859
+ const blob = new Blob([bytes], { type: mime });
1860
+ const blobUrl = URL.createObjectURL(blob);
1861
+ imageMap[rId] = blobUrl;
1862
+ createdBlobUrls.push(blobUrl);
1863
+ }
1864
+ }
1865
+ }
1866
+ } catch (relsErr) {
1867
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1868
+ }
1869
+ }
1870
+ const card = document.createElement("div");
1871
+ card.className = "fp-docx-page-card";
1872
+ card.style.backgroundColor = "#ffffff";
1873
+ card.style.borderRadius = "6px";
1874
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1875
+ card.style.padding = "48px 56px";
1876
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1877
+ card.style.color = "#1e293b";
1878
+ card.style.lineHeight = "1.6";
1879
+ const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
1880
+ let html = "";
1881
+ for (const child of Array.from(body.children)) {
1882
+ const tag = child.localName || child.nodeName.split(":").pop();
1883
+ if (tag === "p") {
1884
+ const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1885
+ const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1886
+ const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1887
+ const textContent = this.extractParagraphHtml(child, imageMap);
1888
+ if (!textContent.trim()) {
1889
+ html += '<div style="height: 10px;"></div>';
1890
+ continue;
1891
+ }
1892
+ const lowerStyle = styleVal.toLowerCase();
1893
+ if (lowerStyle.includes("title")) {
1894
+ 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>`;
1895
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
1896
+ html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
1897
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
1898
+ html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
1899
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
1900
+ html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
1901
+ } else if (numPr) {
1902
+ 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>`;
1903
+ } else {
1904
+ html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
1905
+ }
1906
+ } else if (tag === "tbl") {
1907
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
1908
+ const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
1909
+ rows.forEach((tr, rIdx) => {
1910
+ html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1911
+ const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1912
+ cells.forEach((tc) => {
1913
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1914
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1915
+ });
1916
+ html += "</tr>";
1917
+ });
1918
+ html += "</table>";
1919
+ }
1920
+ }
1921
+ card.innerHTML = DOMPurify2.sanitize(html, {
1922
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1923
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1924
+ });
1925
+ wrapper.appendChild(card);
1926
+ }
1927
+ extractParagraphHtml(pElement, imageMap = {}) {
1928
+ let result = "";
1929
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1930
+ for (const drawing of drawings) {
1931
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1932
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1933
+ if (rId && imageMap[rId]) {
1934
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1935
+ }
1936
+ }
1937
+ const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1938
+ if (runs.length === 0 && !result) {
1939
+ return DOMPurify2.sanitize(pElement.textContent || "");
1940
+ }
1941
+ for (const r of runs) {
1942
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1943
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1944
+ if (rId && imageMap[rId]) {
1945
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1946
+ }
1947
+ const brs = r.getElementsByTagNameNS("*", "br");
1948
+ for (let i = 0; i < brs.length; i++) {
1949
+ result += "<br/>";
1950
+ }
1951
+ const tabs = r.getElementsByTagNameNS("*", "tab");
1952
+ if (tabs.length > 0) {
1953
+ result += "&emsp;";
1954
+ }
1955
+ const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1956
+ const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1957
+ const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1958
+ const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
1959
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1960
+ const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1961
+ const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
1962
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
1963
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1964
+ const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1965
+ let text = texts.map((t) => t.textContent || "").join("");
1966
+ if (!text) continue;
1967
+ text = DOMPurify2.sanitize(text);
1968
+ let styles = "";
1969
+ if (isBold) styles += "font-weight: bold; ";
1970
+ if (isItalic) styles += "font-style: italic; ";
1971
+ if (isUnderline) styles += "text-decoration: underline; ";
1972
+ if (isStrike) styles += "text-decoration: line-through; ";
1973
+ if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
1974
+ if (szVal) {
1975
+ const pt = parseInt(szVal, 10) / 2;
1976
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
1977
+ }
1978
+ if (styles) {
1979
+ result += `<span style="${styles}">${text}</span>`;
1980
+ } else {
1981
+ result += text;
1982
+ }
1983
+ }
1984
+ return result;
1985
+ }
1986
+ renderBinaryDocFallback(ctx, wrapper) {
1987
+ const card = document.createElement("div");
1988
+ card.className = "fp-docx-page-card";
1989
+ card.style.backgroundColor = "#ffffff";
1990
+ card.style.borderRadius = "6px";
1991
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1992
+ card.style.padding = "48px 56px";
1993
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1994
+ card.style.color = "#1e293b";
1995
+ card.style.lineHeight = "1.6";
1996
+ try {
1997
+ const cfbf = new CfbfReader(ctx.buffer);
1998
+ const wordDocStream = cfbf.readStream("WordDocument");
1999
+ if (wordDocStream && wordDocStream.length >= 512) {
2000
+ const text2 = this.extractReadableStrings(wordDocStream);
2001
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2002
+ wrapper.appendChild(card);
2003
+ return;
2004
+ }
2005
+ } catch {
2006
+ }
2007
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2008
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2009
+ wrapper.appendChild(card);
2010
+ }
2011
+ extractReadableStrings(bytes) {
2012
+ let result = "";
2013
+ let current = "";
2014
+ for (let i = 0; i < bytes.length; i++) {
2015
+ const b = bytes[i];
2016
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2017
+ current += String.fromCharCode(b);
2018
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2019
+ continue;
2020
+ } else {
2021
+ if (current.trim().length > 3) {
2022
+ result += current.trim() + "\n\n";
2023
+ }
2024
+ current = "";
2025
+ }
2026
+ }
2027
+ if (current.trim().length > 3) {
2028
+ result += current.trim();
2029
+ }
2030
+ return result;
2031
+ }
1679
2032
  };
1680
2033
  function docxPlugin() {
1681
2034
  return new DocxPlugin();
@@ -2487,7 +2840,7 @@ var MarkdownPlugin = class {
2487
2840
  gfm: true,
2488
2841
  breaks: true
2489
2842
  });
2490
- const cleanHtml = DOMPurify5.sanitize(rawHtml, {
2843
+ const cleanHtml = DOMPurify2.sanitize(rawHtml, {
2491
2844
  USE_PROFILES: { html: true }
2492
2845
  });
2493
2846
  const wrapper = document.createElement("div");
@@ -2651,35 +3004,27 @@ var PptxPlugin = class {
2651
3004
  group: "navigation",
2652
3005
  execute: () => instance.toggleThumbnails?.()
2653
3006
  },
2654
- {
2655
- id: "page-prev",
2656
- icon: "page-prev",
2657
- label: "Previous Slide",
2658
- type: "button",
2659
- group: "navigation",
2660
- execute: () => {
2661
- const cur = instance.getCurrentPage?.() ?? 1;
2662
- if (cur > 1) instance.goToPage?.(cur - 1);
2663
- }
2664
- },
2665
3007
  {
2666
3008
  id: "page-nav",
2667
3009
  icon: "",
2668
- label: "Slide Number",
3010
+ label: "Slide Navigation",
2669
3011
  type: "page-nav",
2670
3012
  group: "navigation",
2671
- execute: (page) => instance.goToPage?.(Number(page))
2672
- },
2673
- {
2674
- id: "page-next",
2675
- icon: "page-next",
2676
- label: "Next Slide",
2677
- type: "button",
2678
- group: "navigation",
2679
- execute: () => {
2680
- const cur = instance.getCurrentPage?.() ?? 1;
2681
- const total = instance.getPageCount?.() ?? 1;
2682
- if (cur < total) instance.goToPage?.(cur + 1);
3013
+ value: instance.getCurrentPage?.() ?? 1,
3014
+ max: instance.getPageCount?.() ?? 1,
3015
+ execute: (action, page) => {
3016
+ if (action === "prev") {
3017
+ const cur = instance.getCurrentPage?.() ?? 1;
3018
+ if (cur > 1) instance.goToPage?.(cur - 1);
3019
+ } else if (action === "next") {
3020
+ const cur = instance.getCurrentPage?.() ?? 1;
3021
+ const total = instance.getPageCount?.() ?? 1;
3022
+ if (cur < total) instance.goToPage?.(cur + 1);
3023
+ } else if (typeof page === "number") {
3024
+ instance.goToPage?.(page);
3025
+ } else if (typeof action === "number") {
3026
+ instance.goToPage?.(action);
3027
+ }
2683
3028
  }
2684
3029
  },
2685
3030
  {
@@ -3210,7 +3555,7 @@ var HtmlPreviewPlugin = class {
3210
3555
  }
3211
3556
  async render(ctx) {
3212
3557
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3213
- const sanitized = DOMPurify5.sanitize(rawHtml, {
3558
+ const sanitized = DOMPurify2.sanitize(rawHtml, {
3214
3559
  WHOLE_DOCUMENT: true,
3215
3560
  ADD_TAGS: ["style", "link"],
3216
3561
  ADD_ATTR: ["target", "rel"]
@@ -3305,35 +3650,27 @@ var OpenDocumentPlugin = class {
3305
3650
  group: "navigation",
3306
3651
  execute: () => instance.toggleThumbnails?.()
3307
3652
  },
3308
- {
3309
- id: "page-prev",
3310
- icon: "page-prev",
3311
- label: "Previous Slide",
3312
- type: "button",
3313
- group: "navigation",
3314
- execute: () => {
3315
- const cur = instance.getCurrentPage?.() ?? 1;
3316
- if (cur > 1) instance.goToPage?.(cur - 1);
3317
- }
3318
- },
3319
3653
  {
3320
3654
  id: "page-nav",
3321
3655
  icon: "",
3322
- label: "Slide Number",
3656
+ label: "Slide Navigation",
3323
3657
  type: "page-nav",
3324
3658
  group: "navigation",
3325
- execute: (p) => instance.goToPage?.(Number(p))
3326
- },
3327
- {
3328
- id: "page-next",
3329
- icon: "page-next",
3330
- label: "Next Slide",
3331
- type: "button",
3332
- group: "navigation",
3333
- execute: () => {
3334
- const cur = instance.getCurrentPage?.() ?? 1;
3335
- const total = instance.getPageCount?.() ?? 1;
3336
- if (cur < total) instance.goToPage?.(cur + 1);
3659
+ value: instance.getCurrentPage?.() ?? 1,
3660
+ max: instance.getPageCount?.() ?? 1,
3661
+ execute: (action, page) => {
3662
+ if (action === "prev") {
3663
+ const cur = instance.getCurrentPage?.() ?? 1;
3664
+ if (cur > 1) instance.goToPage?.(cur - 1);
3665
+ } else if (action === "next") {
3666
+ const cur = instance.getCurrentPage?.() ?? 1;
3667
+ const total = instance.getPageCount?.() ?? 1;
3668
+ if (cur < total) instance.goToPage?.(cur + 1);
3669
+ } else if (typeof page === "number") {
3670
+ instance.goToPage?.(page);
3671
+ } else if (typeof action === "number") {
3672
+ instance.goToPage?.(action);
3673
+ }
3337
3674
  }
3338
3675
  }
3339
3676
  );
@@ -3461,7 +3798,7 @@ var OpenDocumentPlugin = class {
3461
3798
  wrapper.style.padding = "48px";
3462
3799
  wrapper.style.minHeight = "100%";
3463
3800
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3464
- wrapper.innerHTML = DOMPurify5.sanitize(bodyHtml, {
3801
+ wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
3465
3802
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3466
3803
  ADD_ATTR: ["style", "colspan", "rowspan"]
3467
3804
  });
@@ -3981,24 +4318,24 @@ var DocPlugin = class {
3981
4318
  html += "</ul>";
3982
4319
  inList = false;
3983
4320
  }
3984
- 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>`;
4321
+ 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>`;
3985
4322
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
3986
4323
  if (!inList) {
3987
4324
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
3988
4325
  inList = true;
3989
4326
  }
3990
4327
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
3991
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify5.sanitize(bulletText)}</li>`;
4328
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
3992
4329
  } else {
3993
4330
  if (inList) {
3994
4331
  html += "</ul>";
3995
4332
  inList = false;
3996
4333
  }
3997
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify5.sanitize(line)}</p>`;
4334
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
3998
4335
  }
3999
4336
  }
4000
4337
  if (inList) html += "</ul>";
4001
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify5.sanitize(filename)})</p>`;
4338
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
4002
4339
  }
4003
4340
  };
4004
4341
  function docPlugin() {
@@ -4025,35 +4362,27 @@ var PptPlugin = class {
4025
4362
  group: "navigation",
4026
4363
  execute: () => instance.toggleThumbnails?.()
4027
4364
  },
4028
- {
4029
- id: "page-prev",
4030
- icon: "page-prev",
4031
- label: "Previous Slide",
4032
- type: "button",
4033
- group: "navigation",
4034
- execute: () => {
4035
- const cur = instance.getCurrentPage?.() ?? 1;
4036
- if (cur > 1) instance.goToPage?.(cur - 1);
4037
- }
4038
- },
4039
4365
  {
4040
4366
  id: "page-nav",
4041
4367
  icon: "",
4042
- label: "Slide Number",
4368
+ label: "Slide Navigation",
4043
4369
  type: "page-nav",
4044
4370
  group: "navigation",
4045
- execute: (p) => instance.goToPage?.(Number(p))
4046
- },
4047
- {
4048
- id: "page-next",
4049
- icon: "page-next",
4050
- label: "Next Slide",
4051
- type: "button",
4052
- group: "navigation",
4053
- execute: () => {
4054
- const cur = instance.getCurrentPage?.() ?? 1;
4055
- const total = instance.getPageCount?.() ?? 1;
4056
- if (cur < total) instance.goToPage?.(cur + 1);
4371
+ value: instance.getCurrentPage?.() ?? 1,
4372
+ max: instance.getPageCount?.() ?? 1,
4373
+ execute: (action, page) => {
4374
+ if (action === "prev") {
4375
+ const cur = instance.getCurrentPage?.() ?? 1;
4376
+ if (cur > 1) instance.goToPage?.(cur - 1);
4377
+ } else if (action === "next") {
4378
+ const cur = instance.getCurrentPage?.() ?? 1;
4379
+ const total = instance.getPageCount?.() ?? 1;
4380
+ if (cur < total) instance.goToPage?.(cur + 1);
4381
+ } else if (typeof page === "number") {
4382
+ instance.goToPage?.(page);
4383
+ } else if (typeof action === "number") {
4384
+ instance.goToPage?.(action);
4385
+ }
4057
4386
  }
4058
4387
  },
4059
4388
  {
@@ -4161,10 +4490,10 @@ var PptPlugin = class {
4161
4490
  </div>
4162
4491
  <div style="text-align: center; width: 100%;">
4163
4492
  <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4164
- ${DOMPurify5.sanitize(s.title || `Slide ${idx}`)}
4493
+ ${DOMPurify2.sanitize(s.title || `Slide ${idx}`)}
4165
4494
  </h1>
4166
4495
  <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4167
- ${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("")}
4496
+ ${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("")}
4168
4497
  </div>
4169
4498
  </div>
4170
4499
  `;