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