@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.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var DOMPurify5 = require('dompurify');
3
+ var DOMPurify2 = require('dompurify');
4
4
  var docx = require('docx-preview');
5
+ var fflate = require('fflate');
5
6
  var XLSX = require('xlsx');
6
7
  var hljs = require('highlight.js');
7
- var fflate = require('fflate');
8
8
  var marked = require('marked');
9
9
  var pptxBrowser = require('pptx-browser');
10
10
  var THREE = require('three');
@@ -33,7 +33,7 @@ function _interopNamespace(e) {
33
33
  return Object.freeze(n);
34
34
  }
35
35
 
36
- var DOMPurify5__default = /*#__PURE__*/_interopDefault(DOMPurify5);
36
+ var DOMPurify2__default = /*#__PURE__*/_interopDefault(DOMPurify2);
37
37
  var docx__namespace = /*#__PURE__*/_interopNamespace(docx);
38
38
  var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
39
39
  var hljs__default = /*#__PURE__*/_interopDefault(hljs);
@@ -367,12 +367,12 @@ async function sourceToArrayBuffer(source, signal) {
367
367
  return { buffer, metadata };
368
368
  }
369
369
  function sanitizeHTML(html) {
370
- return DOMPurify5__default.default.sanitize(html, {
370
+ return DOMPurify2__default.default.sanitize(html, {
371
371
  USE_PROFILES: { html: true }
372
372
  });
373
373
  }
374
374
  function sanitizeSVG(svg) {
375
- return DOMPurify5__default.default.sanitize(svg, {
375
+ return DOMPurify2__default.default.sanitize(svg, {
376
376
  USE_PROFILES: { svg: true, svgFilters: true }
377
377
  });
378
378
  }
@@ -471,6 +471,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
471
471
  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>`;
472
472
  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>`;
473
473
  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>`;
474
+ 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>`;
475
+ 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>`;
476
+ 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>`;
474
477
  var ICON_MAP = {
475
478
  "zoom-in": ICON_ZOOM_IN,
476
479
  "zoom-out": ICON_ZOOM_OUT,
@@ -487,7 +490,16 @@ var ICON_MAP = {
487
490
  "close": ICON_CLOSE,
488
491
  "copy": ICON_COPY,
489
492
  "prev": ICON_PAGE_PREV,
490
- "next": ICON_PAGE_NEXT
493
+ "next": ICON_PAGE_NEXT,
494
+ "page-prev": ICON_PAGE_PREV,
495
+ "page-next": ICON_PAGE_NEXT,
496
+ "chevron-left": ICON_PAGE_PREV,
497
+ "chevron-right": ICON_PAGE_NEXT,
498
+ "fast-forward": ICON_FAST_FORWARD,
499
+ "forward-10": ICON_FAST_FORWARD,
500
+ "rewind": ICON_REWIND,
501
+ "replay-10": ICON_REWIND,
502
+ "speed": ICON_SPEED
491
503
  };
492
504
  var ToolbarController = class {
493
505
  el;
@@ -519,7 +531,9 @@ var ToolbarController = class {
519
531
  view: [],
520
532
  actions: []
521
533
  };
522
- for (const action of this.actions) {
534
+ const hasPageNav = this.actions.some((a) => a.type === "page-nav");
535
+ const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
536
+ for (const action of effectiveActions) {
523
537
  if (groups[action.group]) {
524
538
  groups[action.group].push(action);
525
539
  }
@@ -542,13 +556,26 @@ var ToolbarController = class {
542
556
  "prev",
543
557
  ICON_PAGE_PREV,
544
558
  "Previous Page",
545
- () => action.execute("prev")
559
+ () => {
560
+ const cur = parseInt(input.value, 10) || 1;
561
+ if (cur > 1) {
562
+ input.value = (cur - 1).toString();
563
+ action.execute("prev", cur - 1);
564
+ }
565
+ }
546
566
  );
547
567
  const nextBtn = this.createButton(
548
568
  "next",
549
569
  ICON_PAGE_NEXT,
550
570
  "Next Page",
551
- () => action.execute("next")
571
+ () => {
572
+ const cur = parseInt(input.value, 10) || 1;
573
+ const max = action.max ?? 1;
574
+ if (cur < max) {
575
+ input.value = (cur + 1).toString();
576
+ action.execute("next", cur + 1);
577
+ }
578
+ }
552
579
  );
553
580
  const input = createElement("input", {
554
581
  className: "fp-toolbar-input",
@@ -558,7 +585,10 @@ var ToolbarController = class {
558
585
  max: (action.max ?? 1).toString()
559
586
  });
560
587
  input.addEventListener("change", () => {
561
- action.execute("go", parseInt(input.value, 10));
588
+ const val = parseInt(input.value, 10);
589
+ if (!isNaN(val)) {
590
+ action.execute("go", val);
591
+ }
562
592
  });
563
593
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
564
594
  groupEl.appendChild(prevBtn);
@@ -599,11 +629,11 @@ var ToolbarController = class {
599
629
  type: "button",
600
630
  "data-action-id": id
601
631
  });
602
- const svg = ICON_MAP[iconHtml] || iconHtml;
603
- if (svg && svg.startsWith("<svg")) {
632
+ const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
633
+ if (svg) {
604
634
  btn.innerHTML = sanitizeSVG(svg);
605
635
  } else {
606
- btn.textContent = svg || title;
636
+ btn.textContent = title || id;
607
637
  }
608
638
  btn.addEventListener("click", onClick);
609
639
  return btn;
@@ -1346,12 +1376,12 @@ function pdfPlugin() {
1346
1376
  }
1347
1377
 
1348
1378
  // ../plugins/media/dist/index.js
1349
- var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"];
1350
- var VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
1351
- var AUDIO_EXTS = [".mp3", ".wav", ".ogg"];
1379
+ var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp", ".ico", ".tiff", ".tif", ".avif"];
1380
+ var VIDEO_EXTS = [".mp4", ".m4v", ".webm", ".ogv", ".ogg", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".3gp", ".mpg", ".mpeg"];
1381
+ var AUDIO_EXTS = [".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a", ".wma", ".opus", ".weba"];
1352
1382
  var MediaPlugin = class {
1353
1383
  id = "media";
1354
- name = "Media Preview";
1384
+ name = "Media Preview (Image, Video, Audio)";
1355
1385
  extensions = [...IMAGE_EXTS, ...VIDEO_EXTS, ...AUDIO_EXTS];
1356
1386
  mimeTypes = [
1357
1387
  "image/jpeg",
@@ -1359,12 +1389,29 @@ var MediaPlugin = class {
1359
1389
  "image/gif",
1360
1390
  "image/webp",
1361
1391
  "image/svg+xml",
1392
+ "image/bmp",
1393
+ "image/x-icon",
1394
+ "image/tiff",
1395
+ "image/avif",
1362
1396
  "video/mp4",
1363
1397
  "video/webm",
1364
1398
  "video/ogg",
1399
+ "video/quicktime",
1400
+ "video/x-msvideo",
1401
+ "video/x-matroska",
1402
+ "video/x-flv",
1403
+ "video/x-ms-wmv",
1404
+ "video/3gpp",
1405
+ "video/mpeg",
1365
1406
  "audio/mpeg",
1366
1407
  "audio/wav",
1367
- "audio/ogg"
1408
+ "audio/ogg",
1409
+ "audio/flac",
1410
+ "audio/aac",
1411
+ "audio/mp4",
1412
+ "audio/x-ms-wma",
1413
+ "audio/opus",
1414
+ "audio/webm"
1368
1415
  ];
1369
1416
  weight = 90;
1370
1417
  supports(file) {
@@ -1420,6 +1467,16 @@ var MediaPlugin = class {
1420
1467
  }
1421
1468
  if (instance.play) {
1422
1469
  actions.push(
1470
+ {
1471
+ id: "replay-10",
1472
+ icon: "replay-10",
1473
+ label: "Rewind 10s",
1474
+ type: "button",
1475
+ group: "actions",
1476
+ execute: () => {
1477
+ instance.rewind?.(10);
1478
+ }
1479
+ },
1423
1480
  {
1424
1481
  id: "play",
1425
1482
  icon: "play",
@@ -1439,6 +1496,26 @@ var MediaPlugin = class {
1439
1496
  execute: () => {
1440
1497
  instance.pause?.();
1441
1498
  }
1499
+ },
1500
+ {
1501
+ id: "forward-10",
1502
+ icon: "forward-10",
1503
+ label: "Fast Forward 10s",
1504
+ type: "button",
1505
+ group: "actions",
1506
+ execute: () => {
1507
+ instance.fastForward?.(10);
1508
+ }
1509
+ },
1510
+ {
1511
+ id: "speed",
1512
+ icon: "speed",
1513
+ label: "Playback Speed",
1514
+ type: "button",
1515
+ group: "actions",
1516
+ execute: () => {
1517
+ instance.cycleSpeed?.();
1518
+ }
1442
1519
  }
1443
1520
  );
1444
1521
  }
@@ -1497,8 +1574,12 @@ var MediaPlugin = class {
1497
1574
  const video = document.createElement("video");
1498
1575
  video.src = url;
1499
1576
  video.controls = true;
1500
- video.style.maxWidth = "100%";
1501
- video.style.maxHeight = "100%";
1577
+ video.playsInline = true;
1578
+ video.style.maxWidth = "90%";
1579
+ video.style.maxHeight = "90%";
1580
+ video.style.borderRadius = "8px";
1581
+ video.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
1582
+ video.style.backgroundColor = "#000000";
1502
1583
  element = video;
1503
1584
  mediaElement = video;
1504
1585
  } else if (isAudio) {
@@ -1557,6 +1638,28 @@ var MediaPlugin = class {
1557
1638
  play: mediaElement ? () => mediaElement?.play() : void 0,
1558
1639
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1559
1640
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1641
+ fastForward: mediaElement ? (seconds = 10) => {
1642
+ if (mediaElement) {
1643
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1644
+ }
1645
+ } : void 0,
1646
+ rewind: mediaElement ? (seconds = 10) => {
1647
+ if (mediaElement) {
1648
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1649
+ }
1650
+ } : void 0,
1651
+ cycleSpeed: mediaElement ? () => {
1652
+ if (mediaElement) {
1653
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1654
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1655
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1656
+ mediaElement.playbackRate = speeds[nextIndex];
1657
+ }
1658
+ } : void 0,
1659
+ setPlaybackRate: mediaElement ? (rate) => {
1660
+ if (mediaElement) mediaElement.playbackRate = rate;
1661
+ } : void 0,
1662
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1560
1663
  download: () => {
1561
1664
  const a = document.createElement("a");
1562
1665
  a.href = url;
@@ -1574,7 +1677,7 @@ function mediaPlugin() {
1574
1677
  }
1575
1678
  var DocxPlugin = class {
1576
1679
  id = "docx";
1577
- name = "Word Document Preview";
1680
+ name = "Word Document Preview (.docx, .docm, .dotx, .dotm)";
1578
1681
  extensions = [".docx", ".docm", ".dotx", ".dotm"];
1579
1682
  mimeTypes = [
1580
1683
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -1596,9 +1699,7 @@ var DocxPlugin = class {
1596
1699
  label: "Zoom Out",
1597
1700
  type: "button",
1598
1701
  group: "zoom",
1599
- execute: () => {
1600
- instance.zoomOut?.();
1601
- }
1702
+ execute: () => instance.zoomOut?.()
1602
1703
  },
1603
1704
  {
1604
1705
  id: "zoom-in",
@@ -1606,9 +1707,7 @@ var DocxPlugin = class {
1606
1707
  label: "Zoom In",
1607
1708
  type: "button",
1608
1709
  group: "zoom",
1609
- execute: () => {
1610
- instance.zoomIn?.();
1611
- }
1710
+ execute: () => instance.zoomIn?.()
1612
1711
  },
1613
1712
  {
1614
1713
  id: "fit-page",
@@ -1616,9 +1715,7 @@ var DocxPlugin = class {
1616
1715
  label: "Fit to Page",
1617
1716
  type: "button",
1618
1717
  group: "zoom",
1619
- execute: () => {
1620
- instance.fitToPage?.();
1621
- }
1718
+ execute: () => instance.fitToPage?.()
1622
1719
  },
1623
1720
  {
1624
1721
  id: "download",
@@ -1626,9 +1723,7 @@ var DocxPlugin = class {
1626
1723
  label: "Download",
1627
1724
  type: "button",
1628
1725
  group: "actions",
1629
- execute: () => {
1630
- instance.download?.();
1631
- }
1726
+ execute: () => instance.download?.()
1632
1727
  },
1633
1728
  {
1634
1729
  id: "print",
@@ -1636,9 +1731,7 @@ var DocxPlugin = class {
1636
1731
  label: "Print",
1637
1732
  type: "button",
1638
1733
  group: "actions",
1639
- execute: () => {
1640
- instance.print?.();
1641
- }
1734
+ execute: () => instance.print?.()
1642
1735
  }
1643
1736
  ];
1644
1737
  }
@@ -1647,27 +1740,81 @@ var DocxPlugin = class {
1647
1740
  wrapper.className = "fp-docx-wrapper";
1648
1741
  wrapper.style.transformOrigin = "top center";
1649
1742
  wrapper.style.transition = "transform 0.2s ease";
1650
- wrapper.style.padding = "16px";
1743
+ wrapper.style.padding = "24px 16px";
1744
+ wrapper.style.maxWidth = "950px";
1745
+ wrapper.style.margin = "0 auto";
1746
+ wrapper.style.boxSizing = "border-box";
1651
1747
  ctx.container.style.overflow = "auto";
1748
+ ctx.container.style.backgroundColor = "#f1f5f9";
1652
1749
  ctx.container.appendChild(wrapper);
1750
+ const styleOverride = document.createElement("style");
1751
+ styleOverride.textContent = `
1752
+ .fp-docx-wrapper .docx-wrapper {
1753
+ background: transparent !important;
1754
+ padding: 0 !important;
1755
+ display: flex !important;
1756
+ flex-direction: column !important;
1757
+ align-items: center !important;
1758
+ box-sizing: border-box !important;
1759
+ }
1760
+ .fp-docx-wrapper section.docx {
1761
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1762
+ border-radius: 4px !important;
1763
+ margin-bottom: 24px !important;
1764
+ background-color: #ffffff !important;
1765
+ }
1766
+ `;
1767
+ ctx.container.appendChild(styleOverride);
1653
1768
  let scale = 1;
1769
+ let renderedSuccessfully = false;
1770
+ const createdBlobUrls = [];
1654
1771
  try {
1655
- await docx__namespace.renderAsync(ctx.buffer, wrapper, ctx.container, {
1772
+ await docx__namespace.renderAsync(ctx.buffer, wrapper, wrapper, {
1656
1773
  inWrapper: true,
1657
1774
  ignoreWidth: false,
1658
- ignoreHeight: false
1775
+ ignoreHeight: false,
1776
+ ignoreFonts: true,
1777
+ // Avoid crashes on embedded obfuscated fonts
1778
+ breakPages: true,
1779
+ experimental: true
1659
1780
  });
1660
- } catch {
1661
- wrapper.innerHTML = `
1662
- <div style="text-align:center; padding: 40px; color: #666;">
1663
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1664
- <h3>${ctx.metadata.name || "Word Document"}</h3>
1665
- <p>DOCX render preview</p>
1666
- </div>
1667
- `;
1781
+ if (!ctx.container.contains(wrapper)) {
1782
+ ctx.container.appendChild(wrapper);
1783
+ }
1784
+ if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1785
+ renderedSuccessfully = true;
1786
+ }
1787
+ } catch (err) {
1788
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1789
+ }
1790
+ if (!renderedSuccessfully) {
1791
+ try {
1792
+ wrapper.innerHTML = "";
1793
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1794
+ if (!ctx.container.contains(wrapper)) {
1795
+ ctx.container.appendChild(wrapper);
1796
+ }
1797
+ renderedSuccessfully = true;
1798
+ } catch (fallbackErr) {
1799
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1800
+ wrapper.innerHTML = `
1801
+ <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1802
+ <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1803
+ <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
1804
+ <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1805
+ </div>
1806
+ `;
1807
+ if (!ctx.container.contains(wrapper)) {
1808
+ ctx.container.appendChild(wrapper);
1809
+ }
1810
+ }
1668
1811
  }
1669
1812
  const cleanup = () => {
1813
+ for (const url of createdBlobUrls) {
1814
+ URL.revokeObjectURL(url);
1815
+ }
1670
1816
  wrapper.remove();
1817
+ styleOverride.remove();
1671
1818
  ctx.container.innerHTML = "";
1672
1819
  };
1673
1820
  ctx.signal.addEventListener("abort", cleanup);
@@ -1688,7 +1835,7 @@ var DocxPlugin = class {
1688
1835
  },
1689
1836
  fitToPage: () => {
1690
1837
  scale = 1;
1691
- wrapper.style.transform = `scale(1)`;
1838
+ wrapper.style.transform = "scale(1)";
1692
1839
  },
1693
1840
  download: () => {
1694
1841
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -1704,6 +1851,212 @@ var DocxPlugin = class {
1704
1851
  }
1705
1852
  };
1706
1853
  }
1854
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1855
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1856
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1857
+ this.renderBinaryDocFallback(ctx, wrapper);
1858
+ return;
1859
+ }
1860
+ const unzipped = fflate.unzipSync(new Uint8Array(ctx.buffer));
1861
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1862
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1863
+ if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1864
+ const xmlStr = fflate.strFromU8(docXmlEntry);
1865
+ const parser = new DOMParser();
1866
+ const doc = parser.parseFromString(xmlStr, "application/xml");
1867
+ const imageMap = {};
1868
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1869
+ if (relsKey && unzipped[relsKey]) {
1870
+ try {
1871
+ const relsXml = fflate.strFromU8(unzipped[relsKey]);
1872
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1873
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1874
+ for (const rel of relEls) {
1875
+ const rId = rel.getAttribute("Id");
1876
+ const target = rel.getAttribute("Target");
1877
+ if (rId && target) {
1878
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1879
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1880
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1881
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1882
+ });
1883
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1884
+ const bytes = unzipped[fullTargetKey];
1885
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1886
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1887
+ const blob = new Blob([bytes], { type: mime });
1888
+ const blobUrl = URL.createObjectURL(blob);
1889
+ imageMap[rId] = blobUrl;
1890
+ createdBlobUrls.push(blobUrl);
1891
+ }
1892
+ }
1893
+ }
1894
+ } catch (relsErr) {
1895
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1896
+ }
1897
+ }
1898
+ const card = document.createElement("div");
1899
+ card.className = "fp-docx-page-card";
1900
+ card.style.backgroundColor = "#ffffff";
1901
+ card.style.borderRadius = "6px";
1902
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1903
+ card.style.padding = "48px 56px";
1904
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1905
+ card.style.color = "#1e293b";
1906
+ card.style.lineHeight = "1.6";
1907
+ const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
1908
+ let html = "";
1909
+ for (const child of Array.from(body.children)) {
1910
+ const tag = child.localName || child.nodeName.split(":").pop();
1911
+ if (tag === "p") {
1912
+ const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1913
+ const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1914
+ const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1915
+ const textContent = this.extractParagraphHtml(child, imageMap);
1916
+ if (!textContent.trim()) {
1917
+ html += '<div style="height: 10px;"></div>';
1918
+ continue;
1919
+ }
1920
+ const lowerStyle = styleVal.toLowerCase();
1921
+ if (lowerStyle.includes("title")) {
1922
+ 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>`;
1923
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
1924
+ html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
1925
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
1926
+ html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
1927
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
1928
+ html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
1929
+ } else if (numPr) {
1930
+ 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>`;
1931
+ } else {
1932
+ html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
1933
+ }
1934
+ } else if (tag === "tbl") {
1935
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
1936
+ const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
1937
+ rows.forEach((tr, rIdx) => {
1938
+ html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1939
+ const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1940
+ cells.forEach((tc) => {
1941
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1942
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1943
+ });
1944
+ html += "</tr>";
1945
+ });
1946
+ html += "</table>";
1947
+ }
1948
+ }
1949
+ card.innerHTML = DOMPurify2__default.default.sanitize(html, {
1950
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1951
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1952
+ });
1953
+ wrapper.appendChild(card);
1954
+ }
1955
+ extractParagraphHtml(pElement, imageMap = {}) {
1956
+ let result = "";
1957
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1958
+ for (const drawing of drawings) {
1959
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1960
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1961
+ if (rId && imageMap[rId]) {
1962
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1963
+ }
1964
+ }
1965
+ const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1966
+ if (runs.length === 0 && !result) {
1967
+ return DOMPurify2__default.default.sanitize(pElement.textContent || "");
1968
+ }
1969
+ for (const r of runs) {
1970
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1971
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1972
+ if (rId && imageMap[rId]) {
1973
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1974
+ }
1975
+ const brs = r.getElementsByTagNameNS("*", "br");
1976
+ for (let i = 0; i < brs.length; i++) {
1977
+ result += "<br/>";
1978
+ }
1979
+ const tabs = r.getElementsByTagNameNS("*", "tab");
1980
+ if (tabs.length > 0) {
1981
+ result += "&emsp;";
1982
+ }
1983
+ const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1984
+ const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1985
+ const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1986
+ const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
1987
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1988
+ const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1989
+ const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
1990
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
1991
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1992
+ const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1993
+ let text = texts.map((t) => t.textContent || "").join("");
1994
+ if (!text) continue;
1995
+ text = DOMPurify2__default.default.sanitize(text);
1996
+ let styles = "";
1997
+ if (isBold) styles += "font-weight: bold; ";
1998
+ if (isItalic) styles += "font-style: italic; ";
1999
+ if (isUnderline) styles += "text-decoration: underline; ";
2000
+ if (isStrike) styles += "text-decoration: line-through; ";
2001
+ if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
2002
+ if (szVal) {
2003
+ const pt = parseInt(szVal, 10) / 2;
2004
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
2005
+ }
2006
+ if (styles) {
2007
+ result += `<span style="${styles}">${text}</span>`;
2008
+ } else {
2009
+ result += text;
2010
+ }
2011
+ }
2012
+ return result;
2013
+ }
2014
+ renderBinaryDocFallback(ctx, wrapper) {
2015
+ const card = document.createElement("div");
2016
+ card.className = "fp-docx-page-card";
2017
+ card.style.backgroundColor = "#ffffff";
2018
+ card.style.borderRadius = "6px";
2019
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
2020
+ card.style.padding = "48px 56px";
2021
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
2022
+ card.style.color = "#1e293b";
2023
+ card.style.lineHeight = "1.6";
2024
+ try {
2025
+ const cfbf = new CfbfReader(ctx.buffer);
2026
+ const wordDocStream = cfbf.readStream("WordDocument");
2027
+ if (wordDocStream && wordDocStream.length >= 512) {
2028
+ const text2 = this.extractReadableStrings(wordDocStream);
2029
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text2)}</div>`;
2030
+ wrapper.appendChild(card);
2031
+ return;
2032
+ }
2033
+ } catch {
2034
+ }
2035
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2036
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2__default.default.sanitize(text)}</div>`;
2037
+ wrapper.appendChild(card);
2038
+ }
2039
+ extractReadableStrings(bytes) {
2040
+ let result = "";
2041
+ let current = "";
2042
+ for (let i = 0; i < bytes.length; i++) {
2043
+ const b = bytes[i];
2044
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2045
+ current += String.fromCharCode(b);
2046
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2047
+ continue;
2048
+ } else {
2049
+ if (current.trim().length > 3) {
2050
+ result += current.trim() + "\n\n";
2051
+ }
2052
+ current = "";
2053
+ }
2054
+ }
2055
+ if (current.trim().length > 3) {
2056
+ result += current.trim();
2057
+ }
2058
+ return result;
2059
+ }
1707
2060
  };
1708
2061
  function docxPlugin() {
1709
2062
  return new DocxPlugin();
@@ -2515,7 +2868,7 @@ var MarkdownPlugin = class {
2515
2868
  gfm: true,
2516
2869
  breaks: true
2517
2870
  });
2518
- const cleanHtml = DOMPurify5__default.default.sanitize(rawHtml, {
2871
+ const cleanHtml = DOMPurify2__default.default.sanitize(rawHtml, {
2519
2872
  USE_PROFILES: { html: true }
2520
2873
  });
2521
2874
  const wrapper = document.createElement("div");
@@ -2679,35 +3032,27 @@ var PptxPlugin = class {
2679
3032
  group: "navigation",
2680
3033
  execute: () => instance.toggleThumbnails?.()
2681
3034
  },
2682
- {
2683
- id: "page-prev",
2684
- icon: "page-prev",
2685
- label: "Previous Slide",
2686
- type: "button",
2687
- group: "navigation",
2688
- execute: () => {
2689
- const cur = instance.getCurrentPage?.() ?? 1;
2690
- if (cur > 1) instance.goToPage?.(cur - 1);
2691
- }
2692
- },
2693
3035
  {
2694
3036
  id: "page-nav",
2695
3037
  icon: "",
2696
- label: "Slide Number",
3038
+ label: "Slide Navigation",
2697
3039
  type: "page-nav",
2698
3040
  group: "navigation",
2699
- execute: (page) => instance.goToPage?.(Number(page))
2700
- },
2701
- {
2702
- id: "page-next",
2703
- icon: "page-next",
2704
- label: "Next Slide",
2705
- type: "button",
2706
- group: "navigation",
2707
- execute: () => {
2708
- const cur = instance.getCurrentPage?.() ?? 1;
2709
- const total = instance.getPageCount?.() ?? 1;
2710
- if (cur < total) instance.goToPage?.(cur + 1);
3041
+ value: instance.getCurrentPage?.() ?? 1,
3042
+ max: instance.getPageCount?.() ?? 1,
3043
+ execute: (action, page) => {
3044
+ if (action === "prev") {
3045
+ const cur = instance.getCurrentPage?.() ?? 1;
3046
+ if (cur > 1) instance.goToPage?.(cur - 1);
3047
+ } else if (action === "next") {
3048
+ const cur = instance.getCurrentPage?.() ?? 1;
3049
+ const total = instance.getPageCount?.() ?? 1;
3050
+ if (cur < total) instance.goToPage?.(cur + 1);
3051
+ } else if (typeof page === "number") {
3052
+ instance.goToPage?.(page);
3053
+ } else if (typeof action === "number") {
3054
+ instance.goToPage?.(action);
3055
+ }
2711
3056
  }
2712
3057
  },
2713
3058
  {
@@ -3238,7 +3583,7 @@ var HtmlPreviewPlugin = class {
3238
3583
  }
3239
3584
  async render(ctx) {
3240
3585
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3241
- const sanitized = DOMPurify5__default.default.sanitize(rawHtml, {
3586
+ const sanitized = DOMPurify2__default.default.sanitize(rawHtml, {
3242
3587
  WHOLE_DOCUMENT: true,
3243
3588
  ADD_TAGS: ["style", "link"],
3244
3589
  ADD_ATTR: ["target", "rel"]
@@ -3333,35 +3678,27 @@ var OpenDocumentPlugin = class {
3333
3678
  group: "navigation",
3334
3679
  execute: () => instance.toggleThumbnails?.()
3335
3680
  },
3336
- {
3337
- id: "page-prev",
3338
- icon: "page-prev",
3339
- label: "Previous Slide",
3340
- type: "button",
3341
- group: "navigation",
3342
- execute: () => {
3343
- const cur = instance.getCurrentPage?.() ?? 1;
3344
- if (cur > 1) instance.goToPage?.(cur - 1);
3345
- }
3346
- },
3347
3681
  {
3348
3682
  id: "page-nav",
3349
3683
  icon: "",
3350
- label: "Slide Number",
3684
+ label: "Slide Navigation",
3351
3685
  type: "page-nav",
3352
3686
  group: "navigation",
3353
- execute: (p) => instance.goToPage?.(Number(p))
3354
- },
3355
- {
3356
- id: "page-next",
3357
- icon: "page-next",
3358
- label: "Next Slide",
3359
- type: "button",
3360
- group: "navigation",
3361
- execute: () => {
3362
- const cur = instance.getCurrentPage?.() ?? 1;
3363
- const total = instance.getPageCount?.() ?? 1;
3364
- if (cur < total) instance.goToPage?.(cur + 1);
3687
+ value: instance.getCurrentPage?.() ?? 1,
3688
+ max: instance.getPageCount?.() ?? 1,
3689
+ execute: (action, page) => {
3690
+ if (action === "prev") {
3691
+ const cur = instance.getCurrentPage?.() ?? 1;
3692
+ if (cur > 1) instance.goToPage?.(cur - 1);
3693
+ } else if (action === "next") {
3694
+ const cur = instance.getCurrentPage?.() ?? 1;
3695
+ const total = instance.getPageCount?.() ?? 1;
3696
+ if (cur < total) instance.goToPage?.(cur + 1);
3697
+ } else if (typeof page === "number") {
3698
+ instance.goToPage?.(page);
3699
+ } else if (typeof action === "number") {
3700
+ instance.goToPage?.(action);
3701
+ }
3365
3702
  }
3366
3703
  }
3367
3704
  );
@@ -3489,7 +3826,7 @@ var OpenDocumentPlugin = class {
3489
3826
  wrapper.style.padding = "48px";
3490
3827
  wrapper.style.minHeight = "100%";
3491
3828
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3492
- wrapper.innerHTML = DOMPurify5__default.default.sanitize(bodyHtml, {
3829
+ wrapper.innerHTML = DOMPurify2__default.default.sanitize(bodyHtml, {
3493
3830
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3494
3831
  ADD_ATTR: ["style", "colspan", "rowspan"]
3495
3832
  });
@@ -4009,24 +4346,24 @@ var DocPlugin = class {
4009
4346
  html += "</ul>";
4010
4347
  inList = false;
4011
4348
  }
4012
- html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify5__default.default.sanitize(line)}</h2>`;
4349
+ html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${DOMPurify2__default.default.sanitize(line)}</h2>`;
4013
4350
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
4014
4351
  if (!inList) {
4015
4352
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
4016
4353
  inList = true;
4017
4354
  }
4018
4355
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
4019
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify5__default.default.sanitize(bulletText)}</li>`;
4356
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2__default.default.sanitize(bulletText)}</li>`;
4020
4357
  } else {
4021
4358
  if (inList) {
4022
4359
  html += "</ul>";
4023
4360
  inList = false;
4024
4361
  }
4025
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify5__default.default.sanitize(line)}</p>`;
4362
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2__default.default.sanitize(line)}</p>`;
4026
4363
  }
4027
4364
  }
4028
4365
  if (inList) html += "</ul>";
4029
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify5__default.default.sanitize(filename)})</p>`;
4366
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2__default.default.sanitize(filename)})</p>`;
4030
4367
  }
4031
4368
  };
4032
4369
  function docPlugin() {
@@ -4053,35 +4390,27 @@ var PptPlugin = class {
4053
4390
  group: "navigation",
4054
4391
  execute: () => instance.toggleThumbnails?.()
4055
4392
  },
4056
- {
4057
- id: "page-prev",
4058
- icon: "page-prev",
4059
- label: "Previous Slide",
4060
- type: "button",
4061
- group: "navigation",
4062
- execute: () => {
4063
- const cur = instance.getCurrentPage?.() ?? 1;
4064
- if (cur > 1) instance.goToPage?.(cur - 1);
4065
- }
4066
- },
4067
4393
  {
4068
4394
  id: "page-nav",
4069
4395
  icon: "",
4070
- label: "Slide Number",
4396
+ label: "Slide Navigation",
4071
4397
  type: "page-nav",
4072
4398
  group: "navigation",
4073
- execute: (p) => instance.goToPage?.(Number(p))
4074
- },
4075
- {
4076
- id: "page-next",
4077
- icon: "page-next",
4078
- label: "Next Slide",
4079
- type: "button",
4080
- group: "navigation",
4081
- execute: () => {
4082
- const cur = instance.getCurrentPage?.() ?? 1;
4083
- const total = instance.getPageCount?.() ?? 1;
4084
- if (cur < total) instance.goToPage?.(cur + 1);
4399
+ value: instance.getCurrentPage?.() ?? 1,
4400
+ max: instance.getPageCount?.() ?? 1,
4401
+ execute: (action, page) => {
4402
+ if (action === "prev") {
4403
+ const cur = instance.getCurrentPage?.() ?? 1;
4404
+ if (cur > 1) instance.goToPage?.(cur - 1);
4405
+ } else if (action === "next") {
4406
+ const cur = instance.getCurrentPage?.() ?? 1;
4407
+ const total = instance.getPageCount?.() ?? 1;
4408
+ if (cur < total) instance.goToPage?.(cur + 1);
4409
+ } else if (typeof page === "number") {
4410
+ instance.goToPage?.(page);
4411
+ } else if (typeof action === "number") {
4412
+ instance.goToPage?.(action);
4413
+ }
4085
4414
  }
4086
4415
  },
4087
4416
  {
@@ -4189,10 +4518,10 @@ var PptPlugin = class {
4189
4518
  </div>
4190
4519
  <div style="text-align: center; width: 100%;">
4191
4520
  <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4192
- ${DOMPurify5__default.default.sanitize(s.title || `Slide ${idx}`)}
4521
+ ${DOMPurify2__default.default.sanitize(s.title || `Slide ${idx}`)}
4193
4522
  </h1>
4194
4523
  <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4195
- ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify5__default.default.sanitize(t)}</div>`).join("")}
4524
+ ${s.texts.map((t) => `<div style="font-size: 18px; color: #334155; line-height: 1.5; font-family: -apple-system, BlinkMacSystemFont, sans-serif;">${DOMPurify2__default.default.sanitize(t)}</div>`).join("")}
4196
4525
  </div>
4197
4526
  </div>
4198
4527
  `;