@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/angular.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Component, ChangeDetectionStrategy, Input, Output, ViewChild, EventEmitter as EventEmitter$1 } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
- import DOMPurify5 from 'dompurify';
3
+ import DOMPurify2 from 'dompurify';
4
4
  import * as docx from 'docx-preview';
5
+ import { unzipSync, strFromU8, unzip } from 'fflate';
5
6
  import * as XLSX from 'xlsx';
6
7
  import hljs from 'highlight.js';
7
- import { unzipSync, strFromU8, unzip } from 'fflate';
8
8
  import { marked } from 'marked';
9
9
  import { PptxRenderer } from 'pptx-browser';
10
10
  import * as THREE from 'three';
@@ -388,7 +388,7 @@ async function sourceToArrayBuffer(source, signal) {
388
388
  return { buffer, metadata };
389
389
  }
390
390
  function sanitizeSVG(svg) {
391
- return DOMPurify5.sanitize(svg, {
391
+ return DOMPurify2.sanitize(svg, {
392
392
  USE_PROFILES: { svg: true, svgFilters: true }
393
393
  });
394
394
  }
@@ -448,6 +448,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
448
448
  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>`;
449
449
  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>`;
450
450
  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>`;
451
+ 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>`;
452
+ 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>`;
453
+ 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>`;
451
454
  var ICON_MAP = {
452
455
  "zoom-in": ICON_ZOOM_IN,
453
456
  "zoom-out": ICON_ZOOM_OUT,
@@ -464,7 +467,16 @@ var ICON_MAP = {
464
467
  "close": ICON_CLOSE,
465
468
  "copy": ICON_COPY,
466
469
  "prev": ICON_PAGE_PREV,
467
- "next": ICON_PAGE_NEXT
470
+ "next": ICON_PAGE_NEXT,
471
+ "page-prev": ICON_PAGE_PREV,
472
+ "page-next": ICON_PAGE_NEXT,
473
+ "chevron-left": ICON_PAGE_PREV,
474
+ "chevron-right": ICON_PAGE_NEXT,
475
+ "fast-forward": ICON_FAST_FORWARD,
476
+ "forward-10": ICON_FAST_FORWARD,
477
+ "rewind": ICON_REWIND,
478
+ "replay-10": ICON_REWIND,
479
+ "speed": ICON_SPEED
468
480
  };
469
481
  var ToolbarController = class {
470
482
  el;
@@ -496,7 +508,9 @@ var ToolbarController = class {
496
508
  view: [],
497
509
  actions: []
498
510
  };
499
- for (const action of this.actions) {
511
+ const hasPageNav = this.actions.some((a) => a.type === "page-nav");
512
+ const effectiveActions = hasPageNav ? this.actions.filter((a) => a.id !== "page-prev" && a.id !== "page-next" && a.id !== "prev" && a.id !== "next") : this.actions;
513
+ for (const action of effectiveActions) {
500
514
  if (groups[action.group]) {
501
515
  groups[action.group].push(action);
502
516
  }
@@ -519,13 +533,26 @@ var ToolbarController = class {
519
533
  "prev",
520
534
  ICON_PAGE_PREV,
521
535
  "Previous Page",
522
- () => action.execute("prev")
536
+ () => {
537
+ const cur = parseInt(input.value, 10) || 1;
538
+ if (cur > 1) {
539
+ input.value = (cur - 1).toString();
540
+ action.execute("prev", cur - 1);
541
+ }
542
+ }
523
543
  );
524
544
  const nextBtn = this.createButton(
525
545
  "next",
526
546
  ICON_PAGE_NEXT,
527
547
  "Next Page",
528
- () => action.execute("next")
548
+ () => {
549
+ const cur = parseInt(input.value, 10) || 1;
550
+ const max = action.max ?? 1;
551
+ if (cur < max) {
552
+ input.value = (cur + 1).toString();
553
+ action.execute("next", cur + 1);
554
+ }
555
+ }
529
556
  );
530
557
  const input = createElement("input", {
531
558
  className: "fp-toolbar-input",
@@ -535,7 +562,10 @@ var ToolbarController = class {
535
562
  max: (action.max ?? 1).toString()
536
563
  });
537
564
  input.addEventListener("change", () => {
538
- action.execute("go", parseInt(input.value, 10));
565
+ const val = parseInt(input.value, 10);
566
+ if (!isNaN(val)) {
567
+ action.execute("go", val);
568
+ }
539
569
  });
540
570
  const label = createElement("span", { className: "fp-toolbar-label" }, ` / ${action.max ?? 1}`);
541
571
  groupEl.appendChild(prevBtn);
@@ -576,11 +606,11 @@ var ToolbarController = class {
576
606
  type: "button",
577
607
  "data-action-id": id
578
608
  });
579
- const svg = ICON_MAP[iconHtml] || iconHtml;
580
- if (svg && svg.startsWith("<svg")) {
609
+ const svg = ICON_MAP[iconHtml] || ICON_MAP[id] || (iconHtml && iconHtml.startsWith("<svg") ? iconHtml : null);
610
+ if (svg) {
581
611
  btn.innerHTML = sanitizeSVG(svg);
582
612
  } else {
583
- btn.textContent = svg || title;
613
+ btn.textContent = title || id;
584
614
  }
585
615
  btn.addEventListener("click", onClick);
586
616
  return btn;
@@ -1323,12 +1353,12 @@ function pdfPlugin() {
1323
1353
  }
1324
1354
 
1325
1355
  // ../plugins/media/dist/index.js
1326
- var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"];
1327
- var VIDEO_EXTS = [".mp4", ".webm", ".ogg"];
1328
- var AUDIO_EXTS = [".mp3", ".wav", ".ogg"];
1356
+ var IMAGE_EXTS = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp", ".ico", ".tiff", ".tif", ".avif"];
1357
+ var VIDEO_EXTS = [".mp4", ".m4v", ".webm", ".ogv", ".ogg", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".3gp", ".mpg", ".mpeg"];
1358
+ var AUDIO_EXTS = [".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a", ".wma", ".opus", ".weba"];
1329
1359
  var MediaPlugin = class {
1330
1360
  id = "media";
1331
- name = "Media Preview";
1361
+ name = "Media Preview (Image, Video, Audio)";
1332
1362
  extensions = [...IMAGE_EXTS, ...VIDEO_EXTS, ...AUDIO_EXTS];
1333
1363
  mimeTypes = [
1334
1364
  "image/jpeg",
@@ -1336,12 +1366,29 @@ var MediaPlugin = class {
1336
1366
  "image/gif",
1337
1367
  "image/webp",
1338
1368
  "image/svg+xml",
1369
+ "image/bmp",
1370
+ "image/x-icon",
1371
+ "image/tiff",
1372
+ "image/avif",
1339
1373
  "video/mp4",
1340
1374
  "video/webm",
1341
1375
  "video/ogg",
1376
+ "video/quicktime",
1377
+ "video/x-msvideo",
1378
+ "video/x-matroska",
1379
+ "video/x-flv",
1380
+ "video/x-ms-wmv",
1381
+ "video/3gpp",
1382
+ "video/mpeg",
1342
1383
  "audio/mpeg",
1343
1384
  "audio/wav",
1344
- "audio/ogg"
1385
+ "audio/ogg",
1386
+ "audio/flac",
1387
+ "audio/aac",
1388
+ "audio/mp4",
1389
+ "audio/x-ms-wma",
1390
+ "audio/opus",
1391
+ "audio/webm"
1345
1392
  ];
1346
1393
  weight = 90;
1347
1394
  supports(file) {
@@ -1397,6 +1444,16 @@ var MediaPlugin = class {
1397
1444
  }
1398
1445
  if (instance.play) {
1399
1446
  actions.push(
1447
+ {
1448
+ id: "replay-10",
1449
+ icon: "replay-10",
1450
+ label: "Rewind 10s",
1451
+ type: "button",
1452
+ group: "actions",
1453
+ execute: () => {
1454
+ instance.rewind?.(10);
1455
+ }
1456
+ },
1400
1457
  {
1401
1458
  id: "play",
1402
1459
  icon: "play",
@@ -1416,6 +1473,26 @@ var MediaPlugin = class {
1416
1473
  execute: () => {
1417
1474
  instance.pause?.();
1418
1475
  }
1476
+ },
1477
+ {
1478
+ id: "forward-10",
1479
+ icon: "forward-10",
1480
+ label: "Fast Forward 10s",
1481
+ type: "button",
1482
+ group: "actions",
1483
+ execute: () => {
1484
+ instance.fastForward?.(10);
1485
+ }
1486
+ },
1487
+ {
1488
+ id: "speed",
1489
+ icon: "speed",
1490
+ label: "Playback Speed",
1491
+ type: "button",
1492
+ group: "actions",
1493
+ execute: () => {
1494
+ instance.cycleSpeed?.();
1495
+ }
1419
1496
  }
1420
1497
  );
1421
1498
  }
@@ -1474,8 +1551,12 @@ var MediaPlugin = class {
1474
1551
  const video = document.createElement("video");
1475
1552
  video.src = url;
1476
1553
  video.controls = true;
1477
- video.style.maxWidth = "100%";
1478
- video.style.maxHeight = "100%";
1554
+ video.playsInline = true;
1555
+ video.style.maxWidth = "90%";
1556
+ video.style.maxHeight = "90%";
1557
+ video.style.borderRadius = "8px";
1558
+ video.style.boxShadow = "0 8px 30px rgba(0,0,0,0.3)";
1559
+ video.style.backgroundColor = "#000000";
1479
1560
  element = video;
1480
1561
  mediaElement = video;
1481
1562
  } else if (isAudio) {
@@ -1534,6 +1615,28 @@ var MediaPlugin = class {
1534
1615
  play: mediaElement ? () => mediaElement?.play() : void 0,
1535
1616
  pause: mediaElement ? () => mediaElement?.pause() : void 0,
1536
1617
  isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
1618
+ fastForward: mediaElement ? (seconds = 10) => {
1619
+ if (mediaElement) {
1620
+ mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
1621
+ }
1622
+ } : void 0,
1623
+ rewind: mediaElement ? (seconds = 10) => {
1624
+ if (mediaElement) {
1625
+ mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
1626
+ }
1627
+ } : void 0,
1628
+ cycleSpeed: mediaElement ? () => {
1629
+ if (mediaElement) {
1630
+ const speeds = [1, 1.25, 1.5, 2, 0.5];
1631
+ const curIndex = speeds.indexOf(mediaElement.playbackRate);
1632
+ const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
1633
+ mediaElement.playbackRate = speeds[nextIndex];
1634
+ }
1635
+ } : void 0,
1636
+ setPlaybackRate: mediaElement ? (rate) => {
1637
+ if (mediaElement) mediaElement.playbackRate = rate;
1638
+ } : void 0,
1639
+ getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
1537
1640
  download: () => {
1538
1641
  const a = document.createElement("a");
1539
1642
  a.href = url;
@@ -1551,7 +1654,7 @@ function mediaPlugin() {
1551
1654
  }
1552
1655
  var DocxPlugin = class {
1553
1656
  id = "docx";
1554
- name = "Word Document Preview";
1657
+ name = "Word Document Preview (.docx, .docm, .dotx, .dotm)";
1555
1658
  extensions = [".docx", ".docm", ".dotx", ".dotm"];
1556
1659
  mimeTypes = [
1557
1660
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -1573,9 +1676,7 @@ var DocxPlugin = class {
1573
1676
  label: "Zoom Out",
1574
1677
  type: "button",
1575
1678
  group: "zoom",
1576
- execute: () => {
1577
- instance.zoomOut?.();
1578
- }
1679
+ execute: () => instance.zoomOut?.()
1579
1680
  },
1580
1681
  {
1581
1682
  id: "zoom-in",
@@ -1583,9 +1684,7 @@ var DocxPlugin = class {
1583
1684
  label: "Zoom In",
1584
1685
  type: "button",
1585
1686
  group: "zoom",
1586
- execute: () => {
1587
- instance.zoomIn?.();
1588
- }
1687
+ execute: () => instance.zoomIn?.()
1589
1688
  },
1590
1689
  {
1591
1690
  id: "fit-page",
@@ -1593,9 +1692,7 @@ var DocxPlugin = class {
1593
1692
  label: "Fit to Page",
1594
1693
  type: "button",
1595
1694
  group: "zoom",
1596
- execute: () => {
1597
- instance.fitToPage?.();
1598
- }
1695
+ execute: () => instance.fitToPage?.()
1599
1696
  },
1600
1697
  {
1601
1698
  id: "download",
@@ -1603,9 +1700,7 @@ var DocxPlugin = class {
1603
1700
  label: "Download",
1604
1701
  type: "button",
1605
1702
  group: "actions",
1606
- execute: () => {
1607
- instance.download?.();
1608
- }
1703
+ execute: () => instance.download?.()
1609
1704
  },
1610
1705
  {
1611
1706
  id: "print",
@@ -1613,9 +1708,7 @@ var DocxPlugin = class {
1613
1708
  label: "Print",
1614
1709
  type: "button",
1615
1710
  group: "actions",
1616
- execute: () => {
1617
- instance.print?.();
1618
- }
1711
+ execute: () => instance.print?.()
1619
1712
  }
1620
1713
  ];
1621
1714
  }
@@ -1624,27 +1717,81 @@ var DocxPlugin = class {
1624
1717
  wrapper.className = "fp-docx-wrapper";
1625
1718
  wrapper.style.transformOrigin = "top center";
1626
1719
  wrapper.style.transition = "transform 0.2s ease";
1627
- wrapper.style.padding = "16px";
1720
+ wrapper.style.padding = "24px 16px";
1721
+ wrapper.style.maxWidth = "950px";
1722
+ wrapper.style.margin = "0 auto";
1723
+ wrapper.style.boxSizing = "border-box";
1628
1724
  ctx.container.style.overflow = "auto";
1725
+ ctx.container.style.backgroundColor = "#f1f5f9";
1629
1726
  ctx.container.appendChild(wrapper);
1727
+ const styleOverride = document.createElement("style");
1728
+ styleOverride.textContent = `
1729
+ .fp-docx-wrapper .docx-wrapper {
1730
+ background: transparent !important;
1731
+ padding: 0 !important;
1732
+ display: flex !important;
1733
+ flex-direction: column !important;
1734
+ align-items: center !important;
1735
+ box-sizing: border-box !important;
1736
+ }
1737
+ .fp-docx-wrapper section.docx {
1738
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
1739
+ border-radius: 4px !important;
1740
+ margin-bottom: 24px !important;
1741
+ background-color: #ffffff !important;
1742
+ }
1743
+ `;
1744
+ ctx.container.appendChild(styleOverride);
1630
1745
  let scale = 1;
1746
+ let renderedSuccessfully = false;
1747
+ const createdBlobUrls = [];
1631
1748
  try {
1632
- await docx.renderAsync(ctx.buffer, wrapper, ctx.container, {
1749
+ await docx.renderAsync(ctx.buffer, wrapper, wrapper, {
1633
1750
  inWrapper: true,
1634
1751
  ignoreWidth: false,
1635
- ignoreHeight: false
1752
+ ignoreHeight: false,
1753
+ ignoreFonts: true,
1754
+ // Avoid crashes on embedded obfuscated fonts
1755
+ breakPages: true,
1756
+ experimental: true
1636
1757
  });
1637
- } catch {
1638
- wrapper.innerHTML = `
1639
- <div style="text-align:center; padding: 40px; color: #666;">
1640
- <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1641
- <h3>${ctx.metadata.name || "Word Document"}</h3>
1642
- <p>DOCX render preview</p>
1643
- </div>
1644
- `;
1758
+ if (!ctx.container.contains(wrapper)) {
1759
+ ctx.container.appendChild(wrapper);
1760
+ }
1761
+ if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
1762
+ renderedSuccessfully = true;
1763
+ }
1764
+ } catch (err) {
1765
+ console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
1766
+ }
1767
+ if (!renderedSuccessfully) {
1768
+ try {
1769
+ wrapper.innerHTML = "";
1770
+ this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
1771
+ if (!ctx.container.contains(wrapper)) {
1772
+ ctx.container.appendChild(wrapper);
1773
+ }
1774
+ renderedSuccessfully = true;
1775
+ } catch (fallbackErr) {
1776
+ console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
1777
+ wrapper.innerHTML = `
1778
+ <div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
1779
+ <div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
1780
+ <h3 style="margin: 0 0 8px; color: #1e293b;">${ctx.metadata.name || "Word Document"}</h3>
1781
+ <p style="color: #64748b; margin: 0;">Could not parse document content</p>
1782
+ </div>
1783
+ `;
1784
+ if (!ctx.container.contains(wrapper)) {
1785
+ ctx.container.appendChild(wrapper);
1786
+ }
1787
+ }
1645
1788
  }
1646
1789
  const cleanup = () => {
1790
+ for (const url of createdBlobUrls) {
1791
+ URL.revokeObjectURL(url);
1792
+ }
1647
1793
  wrapper.remove();
1794
+ styleOverride.remove();
1648
1795
  ctx.container.innerHTML = "";
1649
1796
  };
1650
1797
  ctx.signal.addEventListener("abort", cleanup);
@@ -1665,7 +1812,7 @@ var DocxPlugin = class {
1665
1812
  },
1666
1813
  fitToPage: () => {
1667
1814
  scale = 1;
1668
- wrapper.style.transform = `scale(1)`;
1815
+ wrapper.style.transform = "scale(1)";
1669
1816
  },
1670
1817
  download: () => {
1671
1818
  const blob = new Blob([ctx.buffer], { type: this.mimeTypes[0] });
@@ -1681,6 +1828,212 @@ var DocxPlugin = class {
1681
1828
  }
1682
1829
  };
1683
1830
  }
1831
+ renderXmlFallback(ctx, wrapper, createdBlobUrls) {
1832
+ const magic = new Uint8Array(ctx.buffer.slice(0, 8));
1833
+ if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
1834
+ this.renderBinaryDocFallback(ctx, wrapper);
1835
+ return;
1836
+ }
1837
+ const unzipped = unzipSync(new Uint8Array(ctx.buffer));
1838
+ const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
1839
+ const docXmlEntry = docKey ? unzipped[docKey] : void 0;
1840
+ if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
1841
+ const xmlStr = strFromU8(docXmlEntry);
1842
+ const parser = new DOMParser();
1843
+ const doc = parser.parseFromString(xmlStr, "application/xml");
1844
+ const imageMap = {};
1845
+ const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
1846
+ if (relsKey && unzipped[relsKey]) {
1847
+ try {
1848
+ const relsXml = strFromU8(unzipped[relsKey]);
1849
+ const relsDoc = parser.parseFromString(relsXml, "application/xml");
1850
+ const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
1851
+ for (const rel of relEls) {
1852
+ const rId = rel.getAttribute("Id");
1853
+ const target = rel.getAttribute("Target");
1854
+ if (rId && target) {
1855
+ const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
1856
+ const fullTargetKey = Object.keys(unzipped).find((k) => {
1857
+ const norm = k.replace(/^[./\\]+/, "").toLowerCase();
1858
+ return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
1859
+ });
1860
+ if (fullTargetKey && unzipped[fullTargetKey]) {
1861
+ const bytes = unzipped[fullTargetKey];
1862
+ const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
1863
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
1864
+ const blob = new Blob([bytes], { type: mime });
1865
+ const blobUrl = URL.createObjectURL(blob);
1866
+ imageMap[rId] = blobUrl;
1867
+ createdBlobUrls.push(blobUrl);
1868
+ }
1869
+ }
1870
+ }
1871
+ } catch (relsErr) {
1872
+ console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
1873
+ }
1874
+ }
1875
+ const card = document.createElement("div");
1876
+ card.className = "fp-docx-page-card";
1877
+ card.style.backgroundColor = "#ffffff";
1878
+ card.style.borderRadius = "6px";
1879
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1880
+ card.style.padding = "48px 56px";
1881
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1882
+ card.style.color = "#1e293b";
1883
+ card.style.lineHeight = "1.6";
1884
+ const body = doc.getElementsByTagNameNS("*", "body")[0] || doc.documentElement;
1885
+ let html = "";
1886
+ for (const child of Array.from(body.children)) {
1887
+ const tag = child.localName || child.nodeName.split(":").pop();
1888
+ if (tag === "p") {
1889
+ const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
1890
+ const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
1891
+ const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
1892
+ const textContent = this.extractParagraphHtml(child, imageMap);
1893
+ if (!textContent.trim()) {
1894
+ html += '<div style="height: 10px;"></div>';
1895
+ continue;
1896
+ }
1897
+ const lowerStyle = styleVal.toLowerCase();
1898
+ if (lowerStyle.includes("title")) {
1899
+ 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>`;
1900
+ } else if (lowerStyle.includes("heading1") || styleVal === "1") {
1901
+ html += `<h2 style="font-size: 22px; font-weight: 700; color: #1e40af; margin: 20px 0 10px;">${textContent}</h2>`;
1902
+ } else if (lowerStyle.includes("heading2") || styleVal === "2") {
1903
+ html += `<h3 style="font-size: 18px; font-weight: 600; color: #2563eb; margin: 16px 0 8px;">${textContent}</h3>`;
1904
+ } else if (lowerStyle.includes("heading3") || styleVal === "3") {
1905
+ html += `<h4 style="font-size: 15px; font-weight: 600; color: #334155; margin: 12px 0 6px;">${textContent}</h4>`;
1906
+ } else if (numPr) {
1907
+ 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>`;
1908
+ } else {
1909
+ html += `<p style="margin: 8px 0; font-size: 14px;">${textContent}</p>`;
1910
+ }
1911
+ } else if (tag === "tbl") {
1912
+ html += '<table style="width: 100%; border-collapse: collapse; margin: 20px 0; border: 1px solid #cbd5e1;">';
1913
+ const rows = Array.from(child.getElementsByTagNameNS("*", "tr"));
1914
+ rows.forEach((tr, rIdx) => {
1915
+ html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
1916
+ const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
1917
+ cells.forEach((tc) => {
1918
+ const cellText = this.extractParagraphHtml(tc, imageMap);
1919
+ html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || "&nbsp;"}</td>`;
1920
+ });
1921
+ html += "</tr>";
1922
+ });
1923
+ html += "</table>";
1924
+ }
1925
+ }
1926
+ card.innerHTML = DOMPurify2.sanitize(html, {
1927
+ ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
1928
+ ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
1929
+ });
1930
+ wrapper.appendChild(card);
1931
+ }
1932
+ extractParagraphHtml(pElement, imageMap = {}) {
1933
+ let result = "";
1934
+ const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
1935
+ for (const drawing of drawings) {
1936
+ const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
1937
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1938
+ if (rId && imageMap[rId]) {
1939
+ result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
1940
+ }
1941
+ }
1942
+ const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
1943
+ if (runs.length === 0 && !result) {
1944
+ return DOMPurify2.sanitize(pElement.textContent || "");
1945
+ }
1946
+ for (const r of runs) {
1947
+ const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
1948
+ const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
1949
+ if (rId && imageMap[rId]) {
1950
+ result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
1951
+ }
1952
+ const brs = r.getElementsByTagNameNS("*", "br");
1953
+ for (let i = 0; i < brs.length; i++) {
1954
+ result += "<br/>";
1955
+ }
1956
+ const tabs = r.getElementsByTagNameNS("*", "tab");
1957
+ if (tabs.length > 0) {
1958
+ result += "&emsp;";
1959
+ }
1960
+ const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
1961
+ const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
1962
+ const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
1963
+ const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
1964
+ const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
1965
+ const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
1966
+ const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
1967
+ const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
1968
+ const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
1969
+ const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
1970
+ let text = texts.map((t) => t.textContent || "").join("");
1971
+ if (!text) continue;
1972
+ text = DOMPurify2.sanitize(text);
1973
+ let styles = "";
1974
+ if (isBold) styles += "font-weight: bold; ";
1975
+ if (isItalic) styles += "font-style: italic; ";
1976
+ if (isUnderline) styles += "text-decoration: underline; ";
1977
+ if (isStrike) styles += "text-decoration: line-through; ";
1978
+ if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
1979
+ if (szVal) {
1980
+ const pt = parseInt(szVal, 10) / 2;
1981
+ if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
1982
+ }
1983
+ if (styles) {
1984
+ result += `<span style="${styles}">${text}</span>`;
1985
+ } else {
1986
+ result += text;
1987
+ }
1988
+ }
1989
+ return result;
1990
+ }
1991
+ renderBinaryDocFallback(ctx, wrapper) {
1992
+ const card = document.createElement("div");
1993
+ card.className = "fp-docx-page-card";
1994
+ card.style.backgroundColor = "#ffffff";
1995
+ card.style.borderRadius = "6px";
1996
+ card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
1997
+ card.style.padding = "48px 56px";
1998
+ card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
1999
+ card.style.color = "#1e293b";
2000
+ card.style.lineHeight = "1.6";
2001
+ try {
2002
+ const cfbf = new CfbfReader(ctx.buffer);
2003
+ const wordDocStream = cfbf.readStream("WordDocument");
2004
+ if (wordDocStream && wordDocStream.length >= 512) {
2005
+ const text2 = this.extractReadableStrings(wordDocStream);
2006
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
2007
+ wrapper.appendChild(card);
2008
+ return;
2009
+ }
2010
+ } catch {
2011
+ }
2012
+ const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
2013
+ card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
2014
+ wrapper.appendChild(card);
2015
+ }
2016
+ extractReadableStrings(bytes) {
2017
+ let result = "";
2018
+ let current = "";
2019
+ for (let i = 0; i < bytes.length; i++) {
2020
+ const b = bytes[i];
2021
+ if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
2022
+ current += String.fromCharCode(b);
2023
+ } else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
2024
+ continue;
2025
+ } else {
2026
+ if (current.trim().length > 3) {
2027
+ result += current.trim() + "\n\n";
2028
+ }
2029
+ current = "";
2030
+ }
2031
+ }
2032
+ if (current.trim().length > 3) {
2033
+ result += current.trim();
2034
+ }
2035
+ return result;
2036
+ }
1684
2037
  };
1685
2038
  function docxPlugin() {
1686
2039
  return new DocxPlugin();
@@ -2492,7 +2845,7 @@ var MarkdownPlugin = class {
2492
2845
  gfm: true,
2493
2846
  breaks: true
2494
2847
  });
2495
- const cleanHtml = DOMPurify5.sanitize(rawHtml, {
2848
+ const cleanHtml = DOMPurify2.sanitize(rawHtml, {
2496
2849
  USE_PROFILES: { html: true }
2497
2850
  });
2498
2851
  const wrapper = document.createElement("div");
@@ -2656,35 +3009,27 @@ var PptxPlugin = class {
2656
3009
  group: "navigation",
2657
3010
  execute: () => instance.toggleThumbnails?.()
2658
3011
  },
2659
- {
2660
- id: "page-prev",
2661
- icon: "page-prev",
2662
- label: "Previous Slide",
2663
- type: "button",
2664
- group: "navigation",
2665
- execute: () => {
2666
- const cur = instance.getCurrentPage?.() ?? 1;
2667
- if (cur > 1) instance.goToPage?.(cur - 1);
2668
- }
2669
- },
2670
3012
  {
2671
3013
  id: "page-nav",
2672
3014
  icon: "",
2673
- label: "Slide Number",
3015
+ label: "Slide Navigation",
2674
3016
  type: "page-nav",
2675
3017
  group: "navigation",
2676
- execute: (page) => instance.goToPage?.(Number(page))
2677
- },
2678
- {
2679
- id: "page-next",
2680
- icon: "page-next",
2681
- label: "Next Slide",
2682
- type: "button",
2683
- group: "navigation",
2684
- execute: () => {
2685
- const cur = instance.getCurrentPage?.() ?? 1;
2686
- const total = instance.getPageCount?.() ?? 1;
2687
- if (cur < total) instance.goToPage?.(cur + 1);
3018
+ value: instance.getCurrentPage?.() ?? 1,
3019
+ max: instance.getPageCount?.() ?? 1,
3020
+ execute: (action, page) => {
3021
+ if (action === "prev") {
3022
+ const cur = instance.getCurrentPage?.() ?? 1;
3023
+ if (cur > 1) instance.goToPage?.(cur - 1);
3024
+ } else if (action === "next") {
3025
+ const cur = instance.getCurrentPage?.() ?? 1;
3026
+ const total = instance.getPageCount?.() ?? 1;
3027
+ if (cur < total) instance.goToPage?.(cur + 1);
3028
+ } else if (typeof page === "number") {
3029
+ instance.goToPage?.(page);
3030
+ } else if (typeof action === "number") {
3031
+ instance.goToPage?.(action);
3032
+ }
2688
3033
  }
2689
3034
  },
2690
3035
  {
@@ -3215,7 +3560,7 @@ var HtmlPreviewPlugin = class {
3215
3560
  }
3216
3561
  async render(ctx) {
3217
3562
  const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
3218
- const sanitized = DOMPurify5.sanitize(rawHtml, {
3563
+ const sanitized = DOMPurify2.sanitize(rawHtml, {
3219
3564
  WHOLE_DOCUMENT: true,
3220
3565
  ADD_TAGS: ["style", "link"],
3221
3566
  ADD_ATTR: ["target", "rel"]
@@ -3310,35 +3655,27 @@ var OpenDocumentPlugin = class {
3310
3655
  group: "navigation",
3311
3656
  execute: () => instance.toggleThumbnails?.()
3312
3657
  },
3313
- {
3314
- id: "page-prev",
3315
- icon: "page-prev",
3316
- label: "Previous Slide",
3317
- type: "button",
3318
- group: "navigation",
3319
- execute: () => {
3320
- const cur = instance.getCurrentPage?.() ?? 1;
3321
- if (cur > 1) instance.goToPage?.(cur - 1);
3322
- }
3323
- },
3324
3658
  {
3325
3659
  id: "page-nav",
3326
3660
  icon: "",
3327
- label: "Slide Number",
3661
+ label: "Slide Navigation",
3328
3662
  type: "page-nav",
3329
3663
  group: "navigation",
3330
- execute: (p) => instance.goToPage?.(Number(p))
3331
- },
3332
- {
3333
- id: "page-next",
3334
- icon: "page-next",
3335
- label: "Next Slide",
3336
- type: "button",
3337
- group: "navigation",
3338
- execute: () => {
3339
- const cur = instance.getCurrentPage?.() ?? 1;
3340
- const total = instance.getPageCount?.() ?? 1;
3341
- if (cur < total) instance.goToPage?.(cur + 1);
3664
+ value: instance.getCurrentPage?.() ?? 1,
3665
+ max: instance.getPageCount?.() ?? 1,
3666
+ execute: (action, page) => {
3667
+ if (action === "prev") {
3668
+ const cur = instance.getCurrentPage?.() ?? 1;
3669
+ if (cur > 1) instance.goToPage?.(cur - 1);
3670
+ } else if (action === "next") {
3671
+ const cur = instance.getCurrentPage?.() ?? 1;
3672
+ const total = instance.getPageCount?.() ?? 1;
3673
+ if (cur < total) instance.goToPage?.(cur + 1);
3674
+ } else if (typeof page === "number") {
3675
+ instance.goToPage?.(page);
3676
+ } else if (typeof action === "number") {
3677
+ instance.goToPage?.(action);
3678
+ }
3342
3679
  }
3343
3680
  }
3344
3681
  );
@@ -3466,7 +3803,7 @@ var OpenDocumentPlugin = class {
3466
3803
  wrapper.style.padding = "48px";
3467
3804
  wrapper.style.minHeight = "100%";
3468
3805
  const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
3469
- wrapper.innerHTML = DOMPurify5.sanitize(bodyHtml, {
3806
+ wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
3470
3807
  ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
3471
3808
  ADD_ATTR: ["style", "colspan", "rowspan"]
3472
3809
  });
@@ -3986,24 +4323,24 @@ var DocPlugin = class {
3986
4323
  html += "</ul>";
3987
4324
  inList = false;
3988
4325
  }
3989
- 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>`;
4326
+ 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>`;
3990
4327
  } else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
3991
4328
  if (!inList) {
3992
4329
  html += '<ul style="margin: 8px 0; padding-left: 24px;">';
3993
4330
  inList = true;
3994
4331
  }
3995
4332
  const bulletText = line.replace(/^[•\-\*]\s*/, "");
3996
- html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify5.sanitize(bulletText)}</li>`;
4333
+ html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
3997
4334
  } else {
3998
4335
  if (inList) {
3999
4336
  html += "</ul>";
4000
4337
  inList = false;
4001
4338
  }
4002
- html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify5.sanitize(line)}</p>`;
4339
+ html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
4003
4340
  }
4004
4341
  }
4005
4342
  if (inList) html += "</ul>";
4006
- return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify5.sanitize(filename)})</p>`;
4343
+ return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
4007
4344
  }
4008
4345
  };
4009
4346
  function docPlugin() {
@@ -4030,35 +4367,27 @@ var PptPlugin = class {
4030
4367
  group: "navigation",
4031
4368
  execute: () => instance.toggleThumbnails?.()
4032
4369
  },
4033
- {
4034
- id: "page-prev",
4035
- icon: "page-prev",
4036
- label: "Previous Slide",
4037
- type: "button",
4038
- group: "navigation",
4039
- execute: () => {
4040
- const cur = instance.getCurrentPage?.() ?? 1;
4041
- if (cur > 1) instance.goToPage?.(cur - 1);
4042
- }
4043
- },
4044
4370
  {
4045
4371
  id: "page-nav",
4046
4372
  icon: "",
4047
- label: "Slide Number",
4373
+ label: "Slide Navigation",
4048
4374
  type: "page-nav",
4049
4375
  group: "navigation",
4050
- execute: (p) => instance.goToPage?.(Number(p))
4051
- },
4052
- {
4053
- id: "page-next",
4054
- icon: "page-next",
4055
- label: "Next Slide",
4056
- type: "button",
4057
- group: "navigation",
4058
- execute: () => {
4059
- const cur = instance.getCurrentPage?.() ?? 1;
4060
- const total = instance.getPageCount?.() ?? 1;
4061
- if (cur < total) instance.goToPage?.(cur + 1);
4376
+ value: instance.getCurrentPage?.() ?? 1,
4377
+ max: instance.getPageCount?.() ?? 1,
4378
+ execute: (action, page) => {
4379
+ if (action === "prev") {
4380
+ const cur = instance.getCurrentPage?.() ?? 1;
4381
+ if (cur > 1) instance.goToPage?.(cur - 1);
4382
+ } else if (action === "next") {
4383
+ const cur = instance.getCurrentPage?.() ?? 1;
4384
+ const total = instance.getPageCount?.() ?? 1;
4385
+ if (cur < total) instance.goToPage?.(cur + 1);
4386
+ } else if (typeof page === "number") {
4387
+ instance.goToPage?.(page);
4388
+ } else if (typeof action === "number") {
4389
+ instance.goToPage?.(action);
4390
+ }
4062
4391
  }
4063
4392
  },
4064
4393
  {
@@ -4166,10 +4495,10 @@ var PptPlugin = class {
4166
4495
  </div>
4167
4496
  <div style="text-align: center; width: 100%;">
4168
4497
  <h1 style="font-size: ${idx === 1 ? "36px" : "28px"}; color: #1e3a8a; margin: 0 0 24px; font-family: -apple-system, BlinkMacSystemFont, sans-serif; font-weight: 700;">
4169
- ${DOMPurify5.sanitize(s.title || `Slide ${idx}`)}
4498
+ ${DOMPurify2.sanitize(s.title || `Slide ${idx}`)}
4170
4499
  </h1>
4171
4500
  <div style="display: flex; flex-direction: column; gap: 12px; max-width: 80%; margin: 0 auto; text-align: ${idx === 1 ? "center" : "left"};">
4172
- ${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("")}
4501
+ ${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("")}
4173
4502
  </div>
4174
4503
  </div>
4175
4504
  `;