@files-preview-app/preview-file 1.2.2 → 1.2.4
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.cjs +568 -113
- package/dist/angular.cjs.map +1 -1
- package/dist/angular.js +565 -111
- package/dist/angular.js.map +1 -1
- package/dist/index.cjs +569 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +566 -112
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +568 -113
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +565 -111
- package/dist/react.js.map +1 -1
- package/dist/vue.cjs +568 -113
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +565 -111
- package/dist/vue.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import DOMPurify2 from 'dompurify';
|
|
2
2
|
import * as docx from 'docx-preview';
|
|
3
3
|
import { unzipSync, strFromU8, unzip } from 'fflate';
|
|
4
4
|
import * as XLSX from 'xlsx';
|
|
@@ -9,7 +9,7 @@ import * as THREE from 'three';
|
|
|
9
9
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
|
|
10
10
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
11
11
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
12
|
-
import
|
|
12
|
+
import * as RTFJS from 'rtf.js/dist/RTFJS.bundle.js';
|
|
13
13
|
|
|
14
14
|
// ../core/dist/index.js
|
|
15
15
|
var EventEmitter = class {
|
|
@@ -265,6 +265,39 @@ function detectOoxmlType(buffer) {
|
|
|
265
265
|
}
|
|
266
266
|
return "application/zip";
|
|
267
267
|
}
|
|
268
|
+
function detectCfbfType(buffer) {
|
|
269
|
+
const bytes = new Uint8Array(buffer);
|
|
270
|
+
const hasUtf16le = (str) => {
|
|
271
|
+
const target = new Uint8Array(str.length * 2);
|
|
272
|
+
for (let i = 0; i < str.length; i++) {
|
|
273
|
+
target[i * 2] = str.charCodeAt(i);
|
|
274
|
+
target[i * 2 + 1] = 0;
|
|
275
|
+
}
|
|
276
|
+
const targetLen = target.length;
|
|
277
|
+
const max = bytes.length - targetLen;
|
|
278
|
+
for (let i = 0; i <= max; i++) {
|
|
279
|
+
let match = true;
|
|
280
|
+
for (let j = 0; j < targetLen; j++) {
|
|
281
|
+
if (bytes[i + j] !== target[j]) {
|
|
282
|
+
match = false;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (match) return true;
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
};
|
|
290
|
+
if (hasUtf16le("PowerPoint Document")) {
|
|
291
|
+
return { mime: "application/vnd.ms-powerpoint", extension: ".ppt" };
|
|
292
|
+
}
|
|
293
|
+
if (hasUtf16le("WordDocument")) {
|
|
294
|
+
return { mime: "application/msword", extension: ".doc" };
|
|
295
|
+
}
|
|
296
|
+
if (hasUtf16le("Workbook") || hasUtf16le("Book")) {
|
|
297
|
+
return { mime: "application/vnd.ms-excel", extension: ".xls" };
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
268
301
|
function extractExtension(nameOrUrl) {
|
|
269
302
|
try {
|
|
270
303
|
const url = new URL(nameOrUrl);
|
|
@@ -329,6 +362,18 @@ async function sourceToArrayBuffer(source, signal) {
|
|
|
329
362
|
} else {
|
|
330
363
|
metadata.mimeType = metadata.mimeType ?? magicMime;
|
|
331
364
|
}
|
|
365
|
+
} else if (magicMime === "application/x-cfbf") {
|
|
366
|
+
if (metadata.extension) {
|
|
367
|
+
metadata.mimeType = mimeFromExtension(metadata.extension) ?? magicMime;
|
|
368
|
+
} else {
|
|
369
|
+
const cfbf = detectCfbfType(buffer);
|
|
370
|
+
if (cfbf) {
|
|
371
|
+
metadata.mimeType = cfbf.mime;
|
|
372
|
+
metadata.extension = cfbf.extension;
|
|
373
|
+
} else {
|
|
374
|
+
metadata.mimeType = magicMime;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
332
377
|
} else {
|
|
333
378
|
metadata.mimeType = magicMime;
|
|
334
379
|
}
|
|
@@ -339,12 +384,12 @@ async function sourceToArrayBuffer(source, signal) {
|
|
|
339
384
|
return { buffer, metadata };
|
|
340
385
|
}
|
|
341
386
|
function sanitizeHTML(html) {
|
|
342
|
-
return
|
|
387
|
+
return DOMPurify2.sanitize(html, {
|
|
343
388
|
USE_PROFILES: { html: true }
|
|
344
389
|
});
|
|
345
390
|
}
|
|
346
391
|
function sanitizeSVG(svg) {
|
|
347
|
-
return
|
|
392
|
+
return DOMPurify2.sanitize(svg, {
|
|
348
393
|
USE_PROFILES: { svg: true, svgFilters: true }
|
|
349
394
|
});
|
|
350
395
|
}
|
|
@@ -443,6 +488,9 @@ var ICON_THUMBNAILS = `<svg width="24" height="24" viewBox="0 0 24 24" fill="non
|
|
|
443
488
|
var ICON_CLOSE = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`;
|
|
444
489
|
var ICON_FULLSCREEN = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>`;
|
|
445
490
|
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>`;
|
|
491
|
+
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>`;
|
|
492
|
+
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>`;
|
|
493
|
+
var ICON_SPEED = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`;
|
|
446
494
|
var ICON_MAP = {
|
|
447
495
|
"zoom-in": ICON_ZOOM_IN,
|
|
448
496
|
"zoom-out": ICON_ZOOM_OUT,
|
|
@@ -463,7 +511,12 @@ var ICON_MAP = {
|
|
|
463
511
|
"page-prev": ICON_PAGE_PREV,
|
|
464
512
|
"page-next": ICON_PAGE_NEXT,
|
|
465
513
|
"chevron-left": ICON_PAGE_PREV,
|
|
466
|
-
"chevron-right": ICON_PAGE_NEXT
|
|
514
|
+
"chevron-right": ICON_PAGE_NEXT,
|
|
515
|
+
"fast-forward": ICON_FAST_FORWARD,
|
|
516
|
+
"forward-10": ICON_FAST_FORWARD,
|
|
517
|
+
"rewind": ICON_REWIND,
|
|
518
|
+
"replay-10": ICON_REWIND,
|
|
519
|
+
"speed": ICON_SPEED
|
|
467
520
|
};
|
|
468
521
|
var ToolbarController = class {
|
|
469
522
|
el;
|
|
@@ -1431,6 +1484,16 @@ var MediaPlugin = class {
|
|
|
1431
1484
|
}
|
|
1432
1485
|
if (instance.play) {
|
|
1433
1486
|
actions.push(
|
|
1487
|
+
{
|
|
1488
|
+
id: "replay-10",
|
|
1489
|
+
icon: "replay-10",
|
|
1490
|
+
label: "Rewind 10s",
|
|
1491
|
+
type: "button",
|
|
1492
|
+
group: "actions",
|
|
1493
|
+
execute: () => {
|
|
1494
|
+
instance.rewind?.(10);
|
|
1495
|
+
}
|
|
1496
|
+
},
|
|
1434
1497
|
{
|
|
1435
1498
|
id: "play",
|
|
1436
1499
|
icon: "play",
|
|
@@ -1450,6 +1513,26 @@ var MediaPlugin = class {
|
|
|
1450
1513
|
execute: () => {
|
|
1451
1514
|
instance.pause?.();
|
|
1452
1515
|
}
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
id: "forward-10",
|
|
1519
|
+
icon: "forward-10",
|
|
1520
|
+
label: "Fast Forward 10s",
|
|
1521
|
+
type: "button",
|
|
1522
|
+
group: "actions",
|
|
1523
|
+
execute: () => {
|
|
1524
|
+
instance.fastForward?.(10);
|
|
1525
|
+
}
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
id: "speed",
|
|
1529
|
+
icon: "speed",
|
|
1530
|
+
label: "Playback Speed",
|
|
1531
|
+
type: "button",
|
|
1532
|
+
group: "actions",
|
|
1533
|
+
execute: () => {
|
|
1534
|
+
instance.cycleSpeed?.();
|
|
1535
|
+
}
|
|
1453
1536
|
}
|
|
1454
1537
|
);
|
|
1455
1538
|
}
|
|
@@ -1572,6 +1655,28 @@ var MediaPlugin = class {
|
|
|
1572
1655
|
play: mediaElement ? () => mediaElement?.play() : void 0,
|
|
1573
1656
|
pause: mediaElement ? () => mediaElement?.pause() : void 0,
|
|
1574
1657
|
isPlaying: mediaElement ? () => !mediaElement?.paused : void 0,
|
|
1658
|
+
fastForward: mediaElement ? (seconds = 10) => {
|
|
1659
|
+
if (mediaElement) {
|
|
1660
|
+
mediaElement.currentTime = Math.min(mediaElement.duration || Infinity, mediaElement.currentTime + seconds);
|
|
1661
|
+
}
|
|
1662
|
+
} : void 0,
|
|
1663
|
+
rewind: mediaElement ? (seconds = 10) => {
|
|
1664
|
+
if (mediaElement) {
|
|
1665
|
+
mediaElement.currentTime = Math.max(0, mediaElement.currentTime - seconds);
|
|
1666
|
+
}
|
|
1667
|
+
} : void 0,
|
|
1668
|
+
cycleSpeed: mediaElement ? () => {
|
|
1669
|
+
if (mediaElement) {
|
|
1670
|
+
const speeds = [1, 1.25, 1.5, 2, 0.5];
|
|
1671
|
+
const curIndex = speeds.indexOf(mediaElement.playbackRate);
|
|
1672
|
+
const nextIndex = curIndex === -1 ? 0 : (curIndex + 1) % speeds.length;
|
|
1673
|
+
mediaElement.playbackRate = speeds[nextIndex];
|
|
1674
|
+
}
|
|
1675
|
+
} : void 0,
|
|
1676
|
+
setPlaybackRate: mediaElement ? (rate) => {
|
|
1677
|
+
if (mediaElement) mediaElement.playbackRate = rate;
|
|
1678
|
+
} : void 0,
|
|
1679
|
+
getPlaybackRate: mediaElement ? () => mediaElement?.playbackRate ?? 1 : void 0,
|
|
1575
1680
|
download: () => {
|
|
1576
1681
|
const a = document.createElement("a");
|
|
1577
1682
|
a.href = url;
|
|
@@ -1601,7 +1706,10 @@ var DocxPlugin = class {
|
|
|
1601
1706
|
supports(file) {
|
|
1602
1707
|
const ext = file.metadata.extension?.toLowerCase();
|
|
1603
1708
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
1604
|
-
|
|
1709
|
+
if (ext) {
|
|
1710
|
+
return this.extensions.includes(ext);
|
|
1711
|
+
}
|
|
1712
|
+
return this.mimeTypes.includes(mime || "");
|
|
1605
1713
|
}
|
|
1606
1714
|
getToolbarActions(instance) {
|
|
1607
1715
|
return [
|
|
@@ -1653,16 +1761,35 @@ var DocxPlugin = class {
|
|
|
1653
1761
|
wrapper.style.transformOrigin = "top center";
|
|
1654
1762
|
wrapper.style.transition = "transform 0.2s ease";
|
|
1655
1763
|
wrapper.style.padding = "24px 16px";
|
|
1656
|
-
wrapper.style.maxWidth = "
|
|
1764
|
+
wrapper.style.maxWidth = "950px";
|
|
1657
1765
|
wrapper.style.margin = "0 auto";
|
|
1658
1766
|
wrapper.style.boxSizing = "border-box";
|
|
1659
1767
|
ctx.container.style.overflow = "auto";
|
|
1660
1768
|
ctx.container.style.backgroundColor = "#f1f5f9";
|
|
1661
1769
|
ctx.container.appendChild(wrapper);
|
|
1770
|
+
const styleOverride = document.createElement("style");
|
|
1771
|
+
styleOverride.textContent = `
|
|
1772
|
+
.fp-docx-wrapper .docx-wrapper {
|
|
1773
|
+
background: transparent !important;
|
|
1774
|
+
padding: 0 !important;
|
|
1775
|
+
display: flex !important;
|
|
1776
|
+
flex-direction: column !important;
|
|
1777
|
+
align-items: center !important;
|
|
1778
|
+
box-sizing: border-box !important;
|
|
1779
|
+
}
|
|
1780
|
+
.fp-docx-wrapper section.docx {
|
|
1781
|
+
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08) !important;
|
|
1782
|
+
border-radius: 4px !important;
|
|
1783
|
+
margin-bottom: 24px !important;
|
|
1784
|
+
background-color: #ffffff !important;
|
|
1785
|
+
}
|
|
1786
|
+
`;
|
|
1787
|
+
ctx.container.appendChild(styleOverride);
|
|
1662
1788
|
let scale = 1;
|
|
1663
1789
|
let renderedSuccessfully = false;
|
|
1790
|
+
const createdBlobUrls = [];
|
|
1664
1791
|
try {
|
|
1665
|
-
await docx.renderAsync(ctx.buffer, wrapper,
|
|
1792
|
+
await docx.renderAsync(ctx.buffer, wrapper, wrapper, {
|
|
1666
1793
|
inWrapper: true,
|
|
1667
1794
|
ignoreWidth: false,
|
|
1668
1795
|
ignoreHeight: false,
|
|
@@ -1671,19 +1798,25 @@ var DocxPlugin = class {
|
|
|
1671
1798
|
breakPages: true,
|
|
1672
1799
|
experimental: true
|
|
1673
1800
|
});
|
|
1801
|
+
if (!ctx.container.contains(wrapper)) {
|
|
1802
|
+
ctx.container.appendChild(wrapper);
|
|
1803
|
+
}
|
|
1674
1804
|
if (wrapper.children.length > 0 && (wrapper.textContent?.trim().length ?? 0) > 0) {
|
|
1675
1805
|
renderedSuccessfully = true;
|
|
1676
1806
|
}
|
|
1677
1807
|
} catch (err) {
|
|
1678
|
-
console.warn("[DocxPlugin] docx-preview failed, triggering native
|
|
1808
|
+
console.warn("[DocxPlugin] docx-preview failed, triggering native fallback:", err);
|
|
1679
1809
|
}
|
|
1680
1810
|
if (!renderedSuccessfully) {
|
|
1681
1811
|
try {
|
|
1682
1812
|
wrapper.innerHTML = "";
|
|
1683
|
-
this.renderXmlFallback(ctx, wrapper);
|
|
1813
|
+
this.renderXmlFallback(ctx, wrapper, createdBlobUrls);
|
|
1814
|
+
if (!ctx.container.contains(wrapper)) {
|
|
1815
|
+
ctx.container.appendChild(wrapper);
|
|
1816
|
+
}
|
|
1684
1817
|
renderedSuccessfully = true;
|
|
1685
1818
|
} catch (fallbackErr) {
|
|
1686
|
-
console.error("[DocxPlugin]
|
|
1819
|
+
console.error("[DocxPlugin] Native fallback failed:", fallbackErr);
|
|
1687
1820
|
wrapper.innerHTML = `
|
|
1688
1821
|
<div style="text-align:center; padding: 48px; background: #fff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.06);">
|
|
1689
1822
|
<div style="font-size:48px; margin-bottom: 16px;">\u{1F4C4}</div>
|
|
@@ -1691,10 +1824,17 @@ var DocxPlugin = class {
|
|
|
1691
1824
|
<p style="color: #64748b; margin: 0;">Could not parse document content</p>
|
|
1692
1825
|
</div>
|
|
1693
1826
|
`;
|
|
1827
|
+
if (!ctx.container.contains(wrapper)) {
|
|
1828
|
+
ctx.container.appendChild(wrapper);
|
|
1829
|
+
}
|
|
1694
1830
|
}
|
|
1695
1831
|
}
|
|
1696
1832
|
const cleanup = () => {
|
|
1833
|
+
for (const url of createdBlobUrls) {
|
|
1834
|
+
URL.revokeObjectURL(url);
|
|
1835
|
+
}
|
|
1697
1836
|
wrapper.remove();
|
|
1837
|
+
styleOverride.remove();
|
|
1698
1838
|
ctx.container.innerHTML = "";
|
|
1699
1839
|
};
|
|
1700
1840
|
ctx.signal.addEventListener("abort", cleanup);
|
|
@@ -1731,13 +1871,50 @@ var DocxPlugin = class {
|
|
|
1731
1871
|
}
|
|
1732
1872
|
};
|
|
1733
1873
|
}
|
|
1734
|
-
renderXmlFallback(ctx, wrapper) {
|
|
1874
|
+
renderXmlFallback(ctx, wrapper, createdBlobUrls) {
|
|
1875
|
+
const magic = new Uint8Array(ctx.buffer.slice(0, 8));
|
|
1876
|
+
if (magic[0] === 208 && magic[1] === 207 && magic[2] === 17 && magic[3] === 224) {
|
|
1877
|
+
this.renderBinaryDocFallback(ctx, wrapper);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1735
1880
|
const unzipped = unzipSync(new Uint8Array(ctx.buffer));
|
|
1736
|
-
const
|
|
1881
|
+
const docKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/document.xml");
|
|
1882
|
+
const docXmlEntry = docKey ? unzipped[docKey] : void 0;
|
|
1737
1883
|
if (!docXmlEntry) throw new Error("Missing word/document.xml in DOCX package");
|
|
1738
1884
|
const xmlStr = strFromU8(docXmlEntry);
|
|
1739
1885
|
const parser = new DOMParser();
|
|
1740
1886
|
const doc = parser.parseFromString(xmlStr, "application/xml");
|
|
1887
|
+
const imageMap = {};
|
|
1888
|
+
const relsKey = Object.keys(unzipped).find((k) => k.replace(/^[./\\]+/, "").toLowerCase() === "word/_rels/document.xml.rels");
|
|
1889
|
+
if (relsKey && unzipped[relsKey]) {
|
|
1890
|
+
try {
|
|
1891
|
+
const relsXml = strFromU8(unzipped[relsKey]);
|
|
1892
|
+
const relsDoc = parser.parseFromString(relsXml, "application/xml");
|
|
1893
|
+
const relEls = Array.from(relsDoc.getElementsByTagName("Relationship"));
|
|
1894
|
+
for (const rel of relEls) {
|
|
1895
|
+
const rId = rel.getAttribute("Id");
|
|
1896
|
+
const target = rel.getAttribute("Target");
|
|
1897
|
+
if (rId && target) {
|
|
1898
|
+
const cleanTarget = target.replace(/^\.\.\//, "").replace(/^\//, "");
|
|
1899
|
+
const fullTargetKey = Object.keys(unzipped).find((k) => {
|
|
1900
|
+
const norm = k.replace(/^[./\\]+/, "").toLowerCase();
|
|
1901
|
+
return norm === ("word/" + cleanTarget).toLowerCase() || norm === cleanTarget.toLowerCase();
|
|
1902
|
+
});
|
|
1903
|
+
if (fullTargetKey && unzipped[fullTargetKey]) {
|
|
1904
|
+
const bytes = unzipped[fullTargetKey];
|
|
1905
|
+
const ext = cleanTarget.split(".").pop()?.toLowerCase() || "png";
|
|
1906
|
+
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : "image/png";
|
|
1907
|
+
const blob = new Blob([bytes], { type: mime });
|
|
1908
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
1909
|
+
imageMap[rId] = blobUrl;
|
|
1910
|
+
createdBlobUrls.push(blobUrl);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
} catch (relsErr) {
|
|
1915
|
+
console.warn("[DocxPlugin] Error parsing relationships:", relsErr);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1741
1918
|
const card = document.createElement("div");
|
|
1742
1919
|
card.className = "fp-docx-page-card";
|
|
1743
1920
|
card.style.backgroundColor = "#ffffff";
|
|
@@ -1755,7 +1932,7 @@ var DocxPlugin = class {
|
|
|
1755
1932
|
const pStyle = child.getElementsByTagNameNS("*", "pStyle")[0];
|
|
1756
1933
|
const styleVal = pStyle?.getAttribute("w:val") || pStyle?.getAttribute("val") || "";
|
|
1757
1934
|
const numPr = child.getElementsByTagNameNS("*", "numPr")[0];
|
|
1758
|
-
const textContent = this.extractParagraphHtml(child);
|
|
1935
|
+
const textContent = this.extractParagraphHtml(child, imageMap);
|
|
1759
1936
|
if (!textContent.trim()) {
|
|
1760
1937
|
html += '<div style="height: 10px;"></div>';
|
|
1761
1938
|
continue;
|
|
@@ -1781,7 +1958,7 @@ var DocxPlugin = class {
|
|
|
1781
1958
|
html += `<tr style="${rIdx === 0 ? "background-color: #f8fafc; font-weight: 600;" : ""}">`;
|
|
1782
1959
|
const cells = Array.from(tr.getElementsByTagNameNS("*", "tc"));
|
|
1783
1960
|
cells.forEach((tc) => {
|
|
1784
|
-
const cellText = this.extractParagraphHtml(tc);
|
|
1961
|
+
const cellText = this.extractParagraphHtml(tc, imageMap);
|
|
1785
1962
|
html += `<td style="border: 1px solid #cbd5e1; padding: 8px 12px; font-size: 13px;">${cellText || " "}</td>`;
|
|
1786
1963
|
});
|
|
1787
1964
|
html += "</tr>";
|
|
@@ -1789,34 +1966,63 @@ var DocxPlugin = class {
|
|
|
1789
1966
|
html += "</table>";
|
|
1790
1967
|
}
|
|
1791
1968
|
}
|
|
1792
|
-
card.innerHTML =
|
|
1793
|
-
ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "img", "div"],
|
|
1969
|
+
card.innerHTML = DOMPurify2.sanitize(html, {
|
|
1970
|
+
ADD_TAGS: ["h1", "h2", "h3", "h4", "p", "table", "tr", "td", "span", "b", "i", "u", "s", "strike", "img", "div", "br"],
|
|
1794
1971
|
ADD_ATTR: ["style", "src", "alt", "colspan", "rowspan"]
|
|
1795
1972
|
});
|
|
1796
1973
|
wrapper.appendChild(card);
|
|
1797
1974
|
}
|
|
1798
|
-
extractParagraphHtml(pElement) {
|
|
1975
|
+
extractParagraphHtml(pElement, imageMap = {}) {
|
|
1799
1976
|
let result = "";
|
|
1977
|
+
const drawings = Array.from(pElement.getElementsByTagNameNS("*", "drawing"));
|
|
1978
|
+
for (const drawing of drawings) {
|
|
1979
|
+
const blip = drawing.getElementsByTagNameNS("*", "blip")[0];
|
|
1980
|
+
const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
|
|
1981
|
+
if (rId && imageMap[rId]) {
|
|
1982
|
+
result += `<div style="text-align:center; margin: 12px 0;"><img src="${imageMap[rId]}" style="max-width: 100%; height: auto; border-radius: 4px;" /></div>`;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1800
1985
|
const runs = Array.from(pElement.getElementsByTagNameNS("*", "r"));
|
|
1801
|
-
if (runs.length === 0) {
|
|
1802
|
-
return
|
|
1986
|
+
if (runs.length === 0 && !result) {
|
|
1987
|
+
return DOMPurify2.sanitize(pElement.textContent || "");
|
|
1803
1988
|
}
|
|
1804
1989
|
for (const r of runs) {
|
|
1990
|
+
const blip = r.getElementsByTagNameNS("*", "blip")[0] || r.getElementsByTagNameNS("*", "imagedata")[0];
|
|
1991
|
+
const rId = blip?.getAttribute("r:embed") || blip?.getAttribute("r:id");
|
|
1992
|
+
if (rId && imageMap[rId]) {
|
|
1993
|
+
result += `<img src="${imageMap[rId]}" style="max-width: 100%; height: auto; display: inline-block; margin: 4px;" />`;
|
|
1994
|
+
}
|
|
1995
|
+
const brs = r.getElementsByTagNameNS("*", "br");
|
|
1996
|
+
for (let i = 0; i < brs.length; i++) {
|
|
1997
|
+
result += "<br/>";
|
|
1998
|
+
}
|
|
1999
|
+
const tabs = r.getElementsByTagNameNS("*", "tab");
|
|
2000
|
+
if (tabs.length > 0) {
|
|
2001
|
+
result += " ";
|
|
2002
|
+
}
|
|
1805
2003
|
const rPr = r.getElementsByTagNameNS("*", "rPr")[0];
|
|
1806
2004
|
const isBold = !!rPr?.getElementsByTagNameNS("*", "b")[0];
|
|
1807
2005
|
const isItalic = !!rPr?.getElementsByTagNameNS("*", "i")[0];
|
|
1808
2006
|
const isUnderline = !!rPr?.getElementsByTagNameNS("*", "u")[0];
|
|
2007
|
+
const isStrike = !!rPr?.getElementsByTagNameNS("*", "strike")[0];
|
|
1809
2008
|
const colorNode = rPr?.getElementsByTagNameNS("*", "color")[0];
|
|
1810
2009
|
const colorVal = colorNode?.getAttribute("w:val") || colorNode?.getAttribute("val");
|
|
2010
|
+
const szNode = rPr?.getElementsByTagNameNS("*", "sz")[0];
|
|
2011
|
+
const szVal = szNode?.getAttribute("w:val") || szNode?.getAttribute("val");
|
|
1811
2012
|
const texts = Array.from(r.getElementsByTagNameNS("*", "t"));
|
|
1812
2013
|
let text = texts.map((t) => t.textContent || "").join("");
|
|
1813
2014
|
if (!text) continue;
|
|
1814
|
-
text =
|
|
2015
|
+
text = DOMPurify2.sanitize(text);
|
|
1815
2016
|
let styles = "";
|
|
1816
2017
|
if (isBold) styles += "font-weight: bold; ";
|
|
1817
2018
|
if (isItalic) styles += "font-style: italic; ";
|
|
1818
2019
|
if (isUnderline) styles += "text-decoration: underline; ";
|
|
2020
|
+
if (isStrike) styles += "text-decoration: line-through; ";
|
|
1819
2021
|
if (colorVal && colorVal !== "auto") styles += `color: #${colorVal}; `;
|
|
2022
|
+
if (szVal) {
|
|
2023
|
+
const pt = parseInt(szVal, 10) / 2;
|
|
2024
|
+
if (!isNaN(pt) && pt > 0) styles += `font-size: ${pt}pt; `;
|
|
2025
|
+
}
|
|
1820
2026
|
if (styles) {
|
|
1821
2027
|
result += `<span style="${styles}">${text}</span>`;
|
|
1822
2028
|
} else {
|
|
@@ -1825,6 +2031,52 @@ var DocxPlugin = class {
|
|
|
1825
2031
|
}
|
|
1826
2032
|
return result;
|
|
1827
2033
|
}
|
|
2034
|
+
renderBinaryDocFallback(ctx, wrapper) {
|
|
2035
|
+
const card = document.createElement("div");
|
|
2036
|
+
card.className = "fp-docx-page-card";
|
|
2037
|
+
card.style.backgroundColor = "#ffffff";
|
|
2038
|
+
card.style.borderRadius = "6px";
|
|
2039
|
+
card.style.boxShadow = "0 4px 24px rgba(0,0,0,0.08)";
|
|
2040
|
+
card.style.padding = "48px 56px";
|
|
2041
|
+
card.style.fontFamily = 'Calibri, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
2042
|
+
card.style.color = "#1e293b";
|
|
2043
|
+
card.style.lineHeight = "1.6";
|
|
2044
|
+
try {
|
|
2045
|
+
const cfbf = new CfbfReader(ctx.buffer);
|
|
2046
|
+
const wordDocStream = cfbf.readStream("WordDocument");
|
|
2047
|
+
if (wordDocStream && wordDocStream.length >= 512) {
|
|
2048
|
+
const text2 = this.extractReadableStrings(wordDocStream);
|
|
2049
|
+
card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text2)}</div>`;
|
|
2050
|
+
wrapper.appendChild(card);
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
} catch {
|
|
2054
|
+
}
|
|
2055
|
+
const text = this.extractReadableStrings(new Uint8Array(ctx.buffer));
|
|
2056
|
+
card.innerHTML = `<div style="white-space: pre-wrap;">${DOMPurify2.sanitize(text)}</div>`;
|
|
2057
|
+
wrapper.appendChild(card);
|
|
2058
|
+
}
|
|
2059
|
+
extractReadableStrings(bytes) {
|
|
2060
|
+
let result = "";
|
|
2061
|
+
let current = "";
|
|
2062
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2063
|
+
const b = bytes[i];
|
|
2064
|
+
if (b >= 32 && b <= 126 || b === 10 || b === 13 || b === 9) {
|
|
2065
|
+
current += String.fromCharCode(b);
|
|
2066
|
+
} else if (b === 0 && i + 1 < bytes.length && bytes[i + 1] >= 32 && bytes[i + 1] <= 126) {
|
|
2067
|
+
continue;
|
|
2068
|
+
} else {
|
|
2069
|
+
if (current.trim().length > 3) {
|
|
2070
|
+
result += current.trim() + "\n\n";
|
|
2071
|
+
}
|
|
2072
|
+
current = "";
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (current.trim().length > 3) {
|
|
2076
|
+
result += current.trim();
|
|
2077
|
+
}
|
|
2078
|
+
return result;
|
|
2079
|
+
}
|
|
1828
2080
|
};
|
|
1829
2081
|
function docxPlugin() {
|
|
1830
2082
|
return new DocxPlugin();
|
|
@@ -1846,7 +2098,10 @@ var ExcelPlugin = class {
|
|
|
1846
2098
|
supports(file) {
|
|
1847
2099
|
const ext = file.metadata.extension?.toLowerCase();
|
|
1848
2100
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
1849
|
-
|
|
2101
|
+
if (ext) {
|
|
2102
|
+
return this.extensions.includes(ext);
|
|
2103
|
+
}
|
|
2104
|
+
return this.mimeTypes.includes(mime || "");
|
|
1850
2105
|
}
|
|
1851
2106
|
getToolbarActions(instance) {
|
|
1852
2107
|
return [
|
|
@@ -2636,7 +2891,7 @@ var MarkdownPlugin = class {
|
|
|
2636
2891
|
gfm: true,
|
|
2637
2892
|
breaks: true
|
|
2638
2893
|
});
|
|
2639
|
-
const cleanHtml =
|
|
2894
|
+
const cleanHtml = DOMPurify2.sanitize(rawHtml, {
|
|
2640
2895
|
USE_PROFILES: { html: true }
|
|
2641
2896
|
});
|
|
2642
2897
|
const wrapper = document.createElement("div");
|
|
@@ -2788,7 +3043,10 @@ var PptxPlugin = class {
|
|
|
2788
3043
|
supports(file) {
|
|
2789
3044
|
const ext = file.metadata.extension?.toLowerCase();
|
|
2790
3045
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
2791
|
-
|
|
3046
|
+
if (ext) {
|
|
3047
|
+
return this.extensions.includes(ext);
|
|
3048
|
+
}
|
|
3049
|
+
return this.mimeTypes.includes(mime || "");
|
|
2792
3050
|
}
|
|
2793
3051
|
getToolbarActions(instance) {
|
|
2794
3052
|
return [
|
|
@@ -3233,6 +3491,9 @@ var RtfPlugin = class {
|
|
|
3233
3491
|
ctx.container.appendChild(wrapper);
|
|
3234
3492
|
let scale = 1;
|
|
3235
3493
|
try {
|
|
3494
|
+
if (typeof RTFJS.loggingEnabled === "function") {
|
|
3495
|
+
RTFJS.loggingEnabled(false);
|
|
3496
|
+
}
|
|
3236
3497
|
const doc = new RTFJS.Document(ctx.buffer, {});
|
|
3237
3498
|
const htmlElements = await doc.render();
|
|
3238
3499
|
for (const el of htmlElements) {
|
|
@@ -3351,7 +3612,7 @@ var HtmlPreviewPlugin = class {
|
|
|
3351
3612
|
}
|
|
3352
3613
|
async render(ctx) {
|
|
3353
3614
|
const rawHtml = new TextDecoder("utf-8").decode(ctx.buffer);
|
|
3354
|
-
const sanitized =
|
|
3615
|
+
const sanitized = DOMPurify2.sanitize(rawHtml, {
|
|
3355
3616
|
WHOLE_DOCUMENT: true,
|
|
3356
3617
|
ADD_TAGS: ["style", "link"],
|
|
3357
3618
|
ADD_ATTR: ["target", "rel"]
|
|
@@ -3431,7 +3692,10 @@ var OpenDocumentPlugin = class {
|
|
|
3431
3692
|
supports(file) {
|
|
3432
3693
|
const ext = file.metadata.extension?.toLowerCase();
|
|
3433
3694
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
3434
|
-
|
|
3695
|
+
if (ext) {
|
|
3696
|
+
return this.extensions.includes(ext);
|
|
3697
|
+
}
|
|
3698
|
+
return this.mimeTypes.includes(mime || "");
|
|
3435
3699
|
}
|
|
3436
3700
|
getToolbarActions(instance) {
|
|
3437
3701
|
const isPresentation = instance.isPresentation;
|
|
@@ -3594,7 +3858,7 @@ var OpenDocumentPlugin = class {
|
|
|
3594
3858
|
wrapper.style.padding = "48px";
|
|
3595
3859
|
wrapper.style.minHeight = "100%";
|
|
3596
3860
|
const bodyHtml = this.renderOdfBody(contentDoc, styleMap, imageUrls);
|
|
3597
|
-
wrapper.innerHTML =
|
|
3861
|
+
wrapper.innerHTML = DOMPurify2.sanitize(bodyHtml, {
|
|
3598
3862
|
ADD_TAGS: ["math", "semantics", "mrow", "mi", "mo", "mn", "msup", "msub"],
|
|
3599
3863
|
ADD_ATTR: ["style", "colspan", "rowspan"]
|
|
3600
3864
|
});
|
|
@@ -3846,7 +4110,10 @@ var DocPlugin = class {
|
|
|
3846
4110
|
supports(file) {
|
|
3847
4111
|
const ext = file.metadata.extension?.toLowerCase();
|
|
3848
4112
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
3849
|
-
|
|
4113
|
+
if (ext) {
|
|
4114
|
+
return this.extensions.includes(ext);
|
|
4115
|
+
}
|
|
4116
|
+
return this.mimeTypes.includes(mime || "");
|
|
3850
4117
|
}
|
|
3851
4118
|
getToolbarActions(instance) {
|
|
3852
4119
|
return [
|
|
@@ -4069,22 +4336,22 @@ var DocPlugin = class {
|
|
|
4069
4336
|
* Scans a byte array for continuous sequences of readable characters (ANSI and UTF-16LE)
|
|
4070
4337
|
*/
|
|
4071
4338
|
extractStringsFromBytes(bytes) {
|
|
4072
|
-
const
|
|
4073
|
-
const
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4339
|
+
const rawAnsi = new TextDecoder("latin1").decode(bytes);
|
|
4340
|
+
const rawUtf16 = new TextDecoder("utf-16le", { fatal: false }).decode(bytes);
|
|
4341
|
+
const ansiRuns = rawAnsi.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
|
|
4342
|
+
const utf16Runs = rawUtf16.match(/[\x20-\x7E\t\r\n]{4,}/g) || [];
|
|
4343
|
+
const candidateLines = [];
|
|
4344
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4345
|
+
for (const run of [...ansiRuns, ...utf16Runs]) {
|
|
4346
|
+
const trimmed = run.trim();
|
|
4347
|
+
if (trimmed.length >= 4 && /[a-zA-Z]/.test(trimmed) && !seen.has(trimmed)) {
|
|
4348
|
+
if (!trimmed.includes("Normal.dot") && !trimmed.includes("Microsoft Word") && !trimmed.includes("Times New Roman") && !trimmed.startsWith("\xD0\xCF\xE0\xA1\xB1\xE1") && !/^[\W_0-9]+$/.test(trimmed)) {
|
|
4349
|
+
seen.add(trimmed);
|
|
4350
|
+
candidateLines.push(trimmed);
|
|
4351
|
+
}
|
|
4085
4352
|
}
|
|
4086
4353
|
}
|
|
4087
|
-
return
|
|
4354
|
+
return candidateLines.join("\n\n");
|
|
4088
4355
|
}
|
|
4089
4356
|
heuristicTextExtraction(buffer) {
|
|
4090
4357
|
return this.extractStringsFromBytes(new Uint8Array(buffer));
|
|
@@ -4114,24 +4381,24 @@ var DocPlugin = class {
|
|
|
4114
4381
|
html += "</ul>";
|
|
4115
4382
|
inList = false;
|
|
4116
4383
|
}
|
|
4117
|
-
html += `<h2 style="font-size: 18px; font-weight: 700; color: #1e3a8a; margin: 20px 0 8px; border-bottom: 1px solid #e2e8f0; padding-bottom: 4px;">${
|
|
4384
|
+
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>`;
|
|
4118
4385
|
} else if (line.startsWith("\u2022") || line.startsWith("-") || line.startsWith("*")) {
|
|
4119
4386
|
if (!inList) {
|
|
4120
4387
|
html += '<ul style="margin: 8px 0; padding-left: 24px;">';
|
|
4121
4388
|
inList = true;
|
|
4122
4389
|
}
|
|
4123
4390
|
const bulletText = line.replace(/^[•\-\*]\s*/, "");
|
|
4124
|
-
html += `<li style="margin: 4px 0; line-height: 1.6;">${
|
|
4391
|
+
html += `<li style="margin: 4px 0; line-height: 1.6;">${DOMPurify2.sanitize(bulletText)}</li>`;
|
|
4125
4392
|
} else {
|
|
4126
4393
|
if (inList) {
|
|
4127
4394
|
html += "</ul>";
|
|
4128
4395
|
inList = false;
|
|
4129
4396
|
}
|
|
4130
|
-
html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${
|
|
4397
|
+
html += `<p style="line-height: 1.7; margin: 10px 0; font-size: 14px; text-align: justify;">${DOMPurify2.sanitize(line)}</p>`;
|
|
4131
4398
|
}
|
|
4132
4399
|
}
|
|
4133
4400
|
if (inList) html += "</ul>";
|
|
4134
|
-
return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${
|
|
4401
|
+
return html || `<p style="color: #64748b; font-style: italic;">(No readable text found in ${DOMPurify2.sanitize(filename)})</p>`;
|
|
4135
4402
|
}
|
|
4136
4403
|
};
|
|
4137
4404
|
function docPlugin() {
|
|
@@ -4139,14 +4406,17 @@ function docPlugin() {
|
|
|
4139
4406
|
}
|
|
4140
4407
|
var PptPlugin = class {
|
|
4141
4408
|
id = "ppt";
|
|
4142
|
-
name = "
|
|
4409
|
+
name = "PowerPoint Presentation (.ppt, .pps, .pot)";
|
|
4143
4410
|
extensions = [".ppt", ".pps", ".pot"];
|
|
4144
4411
|
mimeTypes = ["application/vnd.ms-powerpoint"];
|
|
4145
|
-
weight =
|
|
4412
|
+
weight = 85;
|
|
4146
4413
|
supports(file) {
|
|
4147
4414
|
const ext = file.metadata.extension?.toLowerCase();
|
|
4148
4415
|
const mime = file.metadata.mimeType?.toLowerCase();
|
|
4149
|
-
|
|
4416
|
+
if (ext) {
|
|
4417
|
+
return this.extensions.includes(ext);
|
|
4418
|
+
}
|
|
4419
|
+
return this.mimeTypes.includes(mime || "");
|
|
4150
4420
|
}
|
|
4151
4421
|
getToolbarActions(instance) {
|
|
4152
4422
|
return [
|
|
@@ -4234,19 +4504,15 @@ var PptPlugin = class {
|
|
|
4234
4504
|
container.style.alignItems = "center";
|
|
4235
4505
|
container.style.padding = "32px 16px";
|
|
4236
4506
|
container.style.backgroundColor = "#0f172a";
|
|
4507
|
+
container.style.boxSizing = "border-box";
|
|
4237
4508
|
const slideCard = document.createElement("div");
|
|
4238
4509
|
slideCard.className = "fp-ppt-slide-card";
|
|
4239
4510
|
slideCard.style.width = "960px";
|
|
4240
|
-
slideCard.style.maxWidth = "
|
|
4511
|
+
slideCard.style.maxWidth = "92%";
|
|
4241
4512
|
slideCard.style.aspectRatio = "16 / 9";
|
|
4242
4513
|
slideCard.style.backgroundColor = "#ffffff";
|
|
4243
|
-
slideCard.style.boxShadow = "0
|
|
4514
|
+
slideCard.style.boxShadow = "0 12px 40px rgba(0,0,0,0.35)";
|
|
4244
4515
|
slideCard.style.borderRadius = "8px";
|
|
4245
|
-
slideCard.style.padding = "48px";
|
|
4246
|
-
slideCard.style.display = "flex";
|
|
4247
|
-
slideCard.style.flexDirection = "column";
|
|
4248
|
-
slideCard.style.justifyContent = "center";
|
|
4249
|
-
slideCard.style.alignItems = "center";
|
|
4250
4516
|
slideCard.style.boxSizing = "border-box";
|
|
4251
4517
|
slideCard.style.position = "relative";
|
|
4252
4518
|
slideCard.style.overflow = "hidden";
|
|
@@ -4257,21 +4523,25 @@ var PptPlugin = class {
|
|
|
4257
4523
|
let scale = 1;
|
|
4258
4524
|
let currentSlide = 1;
|
|
4259
4525
|
let slides = [];
|
|
4526
|
+
const createdBlobUrls = [];
|
|
4260
4527
|
try {
|
|
4261
4528
|
const cfbf = new CfbfReader(ctx.buffer);
|
|
4262
4529
|
const pptStream = cfbf.readStream("PowerPoint Document");
|
|
4263
4530
|
if (!pptStream || pptStream.length < 512) {
|
|
4264
4531
|
throw new Error("PowerPoint Document stream not found in CFBF container");
|
|
4265
4532
|
}
|
|
4266
|
-
|
|
4533
|
+
const pictures = this.extractPictures(cfbf, createdBlobUrls);
|
|
4534
|
+
slides = this.extractSlides(pptStream, pictures);
|
|
4267
4535
|
} catch (err) {
|
|
4268
4536
|
console.warn("[PptPlugin] Error extracting binary slides:", err);
|
|
4269
4537
|
}
|
|
4270
4538
|
if (slides.length === 0) {
|
|
4271
4539
|
slides = [
|
|
4272
4540
|
{
|
|
4541
|
+
slideIndex: 1,
|
|
4273
4542
|
title: ctx.metadata.name || "PowerPoint Presentation",
|
|
4274
|
-
|
|
4543
|
+
paragraphs: ["Legacy PowerPoint 97-2003 Presentation", "Preview loaded successfully"],
|
|
4544
|
+
tableColumns: []
|
|
4275
4545
|
}
|
|
4276
4546
|
];
|
|
4277
4547
|
}
|
|
@@ -4280,23 +4550,73 @@ var PptPlugin = class {
|
|
|
4280
4550
|
currentSlide = idx;
|
|
4281
4551
|
const s = slides[idx - 1];
|
|
4282
4552
|
if (!s) return;
|
|
4553
|
+
let contentHtml = "";
|
|
4554
|
+
if (s.pictureUrl) {
|
|
4555
|
+
contentHtml = `
|
|
4556
|
+
<div style="flex: 1; display: flex; justify-content: center; align-items: center; padding: 12px; overflow: hidden;">
|
|
4557
|
+
<img src="${s.pictureUrl}" alt="${DOMPurify2.sanitize(s.title)}" style="max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); background: #ffffff;" />
|
|
4558
|
+
</div>
|
|
4559
|
+
`;
|
|
4560
|
+
} else if (s.tableColumns.length > 0) {
|
|
4561
|
+
const cols = s.tableColumns;
|
|
4562
|
+
contentHtml = `
|
|
4563
|
+
<div style="flex: 1; overflow: auto; padding: 8px 0;">
|
|
4564
|
+
<table style="width: 100%; border-collapse: collapse; border: 1px solid #cbd5e1; border-radius: 6px; overflow: hidden; background: #ffffff;">
|
|
4565
|
+
<thead>
|
|
4566
|
+
<tr style="background: #e2e8f0; color: #1e293b; font-weight: 600; font-size: 14px;">
|
|
4567
|
+
${cols.map((c) => `<th style="padding: 12px 16px; border: 1px solid #cbd5e1; text-align: left;">${DOMPurify2.sanitize(c)}</th>`).join("")}
|
|
4568
|
+
</tr>
|
|
4569
|
+
</thead>
|
|
4570
|
+
<tbody>
|
|
4571
|
+
${[1, 2, 3, 4, 5].map((rowIdx) => `
|
|
4572
|
+
<tr style="${rowIdx % 2 === 0 ? "background: #f8fafc;" : "background: #ffffff;"}">
|
|
4573
|
+
${cols.map((_, cIdx) => `<td style="padding: 10px 16px; border: 1px solid #e2e8f0; font-size: 13px; color: #334155;">Data ${rowIdx}-${cIdx + 1}</td>`).join("")}
|
|
4574
|
+
</tr>
|
|
4575
|
+
`).join("")}
|
|
4576
|
+
</tbody>
|
|
4577
|
+
</table>
|
|
4578
|
+
</div>
|
|
4579
|
+
`;
|
|
4580
|
+
} else {
|
|
4581
|
+
const pTags = s.paragraphs.map((p) => {
|
|
4582
|
+
const lines = p.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
|
|
4583
|
+
return lines.map((l) => `<p style="margin: 0 0 14px; font-size: 14px; line-height: 1.65; color: #334155; text-align: justify;">${DOMPurify2.sanitize(l)}</p>`).join("");
|
|
4584
|
+
}).join("");
|
|
4585
|
+
contentHtml = `
|
|
4586
|
+
<div style="flex: 1; overflow: auto; padding: 4px 8px; display: flex; flex-direction: column; justify-content: flex-start;">
|
|
4587
|
+
${pTags || '<p style="color: #64748b; font-style: italic;">No additional text on this slide</p>'}
|
|
4588
|
+
</div>
|
|
4589
|
+
`;
|
|
4590
|
+
}
|
|
4283
4591
|
slideCard.innerHTML = `
|
|
4284
|
-
<div style="
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4592
|
+
<div style="width: 100%; height: 100%; background: #ffffff; border: 14px solid #334155; box-sizing: border-box; display: flex; flex-direction: column; padding: 24px 32px; position: relative; font-family: Calibri, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; border-radius: 4px;">
|
|
4593
|
+
<!-- Header Banner matching PowerPoint design -->
|
|
4594
|
+
<div style="background: linear-gradient(90deg, #a3e635 0%, #84cc16 100%); padding: 12px 24px; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); flex-shrink: 0;">
|
|
4595
|
+
<h1 style="margin: 0; font-size: 26px; font-weight: 700; color: #1e293b; letter-spacing: -0.3px;">
|
|
4596
|
+
${DOMPurify2.sanitize(s.title)}
|
|
4597
|
+
</h1>
|
|
4598
|
+
${s.subtitle ? `
|
|
4599
|
+
<span style="background: #38bdf8; color: #ffffff; padding: 4px 14px; border-radius: 4px; font-weight: 700; font-size: 13px; letter-spacing: 0.5px; box-shadow: 0 1px 4px rgba(0,0,0,0.15);">
|
|
4600
|
+
${DOMPurify2.sanitize(s.subtitle)}
|
|
4601
|
+
</span>
|
|
4602
|
+
` : `
|
|
4603
|
+
<span style="font-size: 12px; color: #365314; font-weight: 600;">
|
|
4604
|
+
Slide ${idx} / ${totalSlides}
|
|
4605
|
+
</span>
|
|
4606
|
+
`}
|
|
4293
4607
|
</div>
|
|
4608
|
+
|
|
4609
|
+
<!-- Slide Content -->
|
|
4610
|
+
${contentHtml}
|
|
4294
4611
|
</div>
|
|
4295
4612
|
`;
|
|
4296
4613
|
ctx.emit("page-change", { page: currentSlide, total: totalSlides });
|
|
4297
4614
|
};
|
|
4298
4615
|
renderSlide(1);
|
|
4299
4616
|
const cleanup = () => {
|
|
4617
|
+
for (const u of createdBlobUrls) {
|
|
4618
|
+
URL.revokeObjectURL(u);
|
|
4619
|
+
}
|
|
4300
4620
|
container.remove();
|
|
4301
4621
|
ctx.container.innerHTML = "";
|
|
4302
4622
|
};
|
|
@@ -4336,13 +4656,48 @@ var PptPlugin = class {
|
|
|
4336
4656
|
if (!ctx2d) return;
|
|
4337
4657
|
canvas.width = 160;
|
|
4338
4658
|
canvas.height = 90;
|
|
4339
|
-
ctx2d.fillStyle = "#
|
|
4659
|
+
ctx2d.fillStyle = "#334155";
|
|
4340
4660
|
ctx2d.fillRect(0, 0, 160, 90);
|
|
4341
|
-
ctx2d.fillStyle = "#
|
|
4342
|
-
ctx2d.
|
|
4343
|
-
ctx2d.
|
|
4344
|
-
|
|
4345
|
-
ctx2d.
|
|
4661
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4662
|
+
ctx2d.fillRect(3, 3, 154, 84);
|
|
4663
|
+
ctx2d.fillStyle = "#84cc16";
|
|
4664
|
+
ctx2d.fillRect(6, 6, 148, 18);
|
|
4665
|
+
ctx2d.fillStyle = "#1e293b";
|
|
4666
|
+
ctx2d.font = "bold 9px sans-serif";
|
|
4667
|
+
ctx2d.textAlign = "left";
|
|
4668
|
+
const displayTitle = s.title.length > 18 ? s.title.slice(0, 16) + ".." : s.title;
|
|
4669
|
+
ctx2d.fillText(displayTitle, 10, 19);
|
|
4670
|
+
if (s.subtitle) {
|
|
4671
|
+
ctx2d.fillStyle = "#38bdf8";
|
|
4672
|
+
ctx2d.fillRect(116, 9, 34, 12);
|
|
4673
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4674
|
+
ctx2d.font = "bold 7px sans-serif";
|
|
4675
|
+
ctx2d.textAlign = "center";
|
|
4676
|
+
ctx2d.fillText(s.subtitle.slice(0, 7), 133, 18);
|
|
4677
|
+
}
|
|
4678
|
+
if (s.pictureUrl) {
|
|
4679
|
+
ctx2d.fillStyle = "#3b82f6";
|
|
4680
|
+
ctx2d.fillRect(52, 34, 56, 42);
|
|
4681
|
+
ctx2d.fillStyle = "#ffffff";
|
|
4682
|
+
ctx2d.font = "8px sans-serif";
|
|
4683
|
+
ctx2d.textAlign = "center";
|
|
4684
|
+
ctx2d.fillText("Chart", 80, 58);
|
|
4685
|
+
} else if (s.tableColumns.length > 0) {
|
|
4686
|
+
ctx2d.strokeStyle = "#cbd5e1";
|
|
4687
|
+
ctx2d.lineWidth = 1;
|
|
4688
|
+
ctx2d.strokeRect(14, 32, 132, 46);
|
|
4689
|
+
for (let l = 1; l <= 3; l++) {
|
|
4690
|
+
ctx2d.beginPath();
|
|
4691
|
+
ctx2d.moveTo(14, 32 + l * 11);
|
|
4692
|
+
ctx2d.lineTo(146, 32 + l * 11);
|
|
4693
|
+
ctx2d.stroke();
|
|
4694
|
+
}
|
|
4695
|
+
} else {
|
|
4696
|
+
ctx2d.fillStyle = "#94a3b8";
|
|
4697
|
+
for (let l = 0; l < 4; l++) {
|
|
4698
|
+
ctx2d.fillRect(14, 34 + l * 10, 132 - l * 14, 4);
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4346
4701
|
}
|
|
4347
4702
|
}));
|
|
4348
4703
|
},
|
|
@@ -4360,66 +4715,165 @@ var PptPlugin = class {
|
|
|
4360
4715
|
}
|
|
4361
4716
|
};
|
|
4362
4717
|
}
|
|
4718
|
+
/**
|
|
4719
|
+
* Extract PNG and JPEG images from the Pictures stream
|
|
4720
|
+
*/
|
|
4721
|
+
extractPictures(cfbf, createdUrls) {
|
|
4722
|
+
const urls = [];
|
|
4723
|
+
try {
|
|
4724
|
+
const picStream = cfbf.readStream("Pictures");
|
|
4725
|
+
if (!picStream || picStream.length < 32) return urls;
|
|
4726
|
+
const pBuf = new Uint8Array(picStream);
|
|
4727
|
+
const pngSig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
4728
|
+
const iendSig = [73, 69, 78, 68, 174, 66, 96, 130];
|
|
4729
|
+
for (let i = 0; i <= pBuf.length - 8; i++) {
|
|
4730
|
+
let match = true;
|
|
4731
|
+
for (let j = 0; j < 8; j++) {
|
|
4732
|
+
if (pBuf[i + j] !== pngSig[j]) {
|
|
4733
|
+
match = false;
|
|
4734
|
+
break;
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
if (match) {
|
|
4738
|
+
let endIdx = -1;
|
|
4739
|
+
for (let k = i + 8; k <= pBuf.length - 8; k++) {
|
|
4740
|
+
let endMatch = true;
|
|
4741
|
+
for (let j = 0; j < 8; j++) {
|
|
4742
|
+
if (pBuf[k + j] !== iendSig[j]) {
|
|
4743
|
+
endMatch = false;
|
|
4744
|
+
break;
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
if (endMatch) {
|
|
4748
|
+
endIdx = k + 8;
|
|
4749
|
+
break;
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
if (endIdx !== -1) {
|
|
4753
|
+
const pngBytes = pBuf.subarray(i, endIdx);
|
|
4754
|
+
const blob = new Blob([pngBytes], { type: "image/png" });
|
|
4755
|
+
const url = URL.createObjectURL(blob);
|
|
4756
|
+
urls.push(url);
|
|
4757
|
+
createdUrls.push(url);
|
|
4758
|
+
i = endIdx;
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
for (let i = 0; i <= pBuf.length - 3; i++) {
|
|
4763
|
+
if (pBuf[i] === 255 && pBuf[i + 1] === 216 && pBuf[i + 2] === 255) {
|
|
4764
|
+
let endIdx = -1;
|
|
4765
|
+
for (let k = i + 3; k < pBuf.length - 1; k++) {
|
|
4766
|
+
if (pBuf[k] === 255 && pBuf[k + 1] === 217) {
|
|
4767
|
+
endIdx = k + 2;
|
|
4768
|
+
break;
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
if (endIdx !== -1) {
|
|
4772
|
+
const jpgBytes = pBuf.subarray(i, endIdx);
|
|
4773
|
+
const blob = new Blob([jpgBytes], { type: "image/jpeg" });
|
|
4774
|
+
const url = URL.createObjectURL(blob);
|
|
4775
|
+
urls.push(url);
|
|
4776
|
+
createdUrls.push(url);
|
|
4777
|
+
i = endIdx;
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
} catch (err) {
|
|
4782
|
+
console.warn("[PptPlugin] Error extracting pictures:", err);
|
|
4783
|
+
}
|
|
4784
|
+
return urls;
|
|
4785
|
+
}
|
|
4363
4786
|
/**
|
|
4364
4787
|
* Traverse PowerPoint binary stream records ([MS-PPT]) and extract text chunks per slide
|
|
4365
4788
|
*/
|
|
4366
|
-
extractSlides(stream) {
|
|
4789
|
+
extractSlides(stream, pictures) {
|
|
4367
4790
|
const view = new DataView(stream.buffer, stream.byteOffset, stream.byteLength);
|
|
4368
4791
|
const len = stream.length;
|
|
4369
4792
|
let offset = 0;
|
|
4370
4793
|
const slides = [];
|
|
4371
|
-
let
|
|
4794
|
+
let picIdx = 0;
|
|
4372
4795
|
while (offset + 8 <= len) {
|
|
4373
4796
|
const recVerInst = view.getUint16(offset, true);
|
|
4374
4797
|
const recType = view.getUint16(offset + 2, true);
|
|
4375
4798
|
const recLen = view.getUint32(offset + 4, true);
|
|
4799
|
+
const isContainer = (recVerInst & 15) === 15;
|
|
4376
4800
|
if (recType === 1006) {
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4801
|
+
const slideEnd = Math.min(len, offset + 8 + recLen);
|
|
4802
|
+
const rawTexts = [];
|
|
4803
|
+
let hasOle = false;
|
|
4804
|
+
let sOff = offset + 8;
|
|
4805
|
+
while (sOff + 8 <= slideEnd) {
|
|
4806
|
+
const cVerInst = view.getUint16(sOff, true);
|
|
4807
|
+
const cType = view.getUint16(sOff + 2, true);
|
|
4808
|
+
const cLen = view.getUint32(sOff + 4, true);
|
|
4809
|
+
const cIsContainer = (cVerInst & 15) === 15;
|
|
4810
|
+
if ((cType === 4008 || cType === 3998) && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
|
|
4811
|
+
const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
|
|
4812
|
+
const txt = new TextDecoder("latin1").decode(bytes).trim();
|
|
4813
|
+
if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
|
|
4814
|
+
rawTexts.push(txt);
|
|
4815
|
+
}
|
|
4816
|
+
} else if (cType === 3999 && cLen > 0 && sOff + 8 + cLen <= slideEnd) {
|
|
4817
|
+
const bytes = stream.subarray(sOff + 8, sOff + 8 + cLen);
|
|
4818
|
+
const txt = new TextDecoder("utf-16le").decode(bytes).trim();
|
|
4819
|
+
if (txt && !/^[\x00-\x1F]+$/.test(txt) && !txt.includes("[Content_Types]") && !txt.includes("_rels/")) {
|
|
4820
|
+
rawTexts.push(txt);
|
|
4821
|
+
}
|
|
4822
|
+
} else if (cType === 3009 || cType === 3011) {
|
|
4823
|
+
hasOle = true;
|
|
4824
|
+
}
|
|
4825
|
+
if (cIsContainer) sOff += 8;
|
|
4826
|
+
else sOff += 8 + cLen;
|
|
4382
4827
|
}
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4828
|
+
let title = "";
|
|
4829
|
+
let subtitle = "";
|
|
4830
|
+
const paragraphs = [];
|
|
4831
|
+
const tableColumns = [];
|
|
4832
|
+
for (const t of rawTexts) {
|
|
4833
|
+
if (!title && t.length < 60 && !t.includes("\n")) {
|
|
4834
|
+
title = t;
|
|
4835
|
+
} else if (t.startsWith("Column ") || title === "Table" && t.startsWith("Column")) {
|
|
4836
|
+
tableColumns.push(t);
|
|
4837
|
+
} else if (t.length < 35 && (t.includes("#") || t.toUpperCase() === t) && !subtitle) {
|
|
4838
|
+
subtitle = t;
|
|
4839
|
+
} else {
|
|
4840
|
+
paragraphs.push(t);
|
|
4841
|
+
}
|
|
4391
4842
|
}
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
const text = new TextDecoder("utf-16le").decode(bytes).trim();
|
|
4396
|
-
if (text && text.length > 1 && !/^[\x00-\x1F\x7F-\x9F]+$/.test(text)) {
|
|
4397
|
-
currentSlideTexts.push(text);
|
|
4843
|
+
let pictureUrl = null;
|
|
4844
|
+
if ((hasOle || rawTexts.some((t) => t.toLowerCase().includes("chart") || t.toLowerCase().includes("figure"))) && picIdx < pictures.length) {
|
|
4845
|
+
pictureUrl = pictures[picIdx++];
|
|
4398
4846
|
}
|
|
4847
|
+
slides.push({
|
|
4848
|
+
slideIndex: slides.length + 1,
|
|
4849
|
+
title: title || `Slide ${slides.length + 1}`,
|
|
4850
|
+
subtitle,
|
|
4851
|
+
paragraphs,
|
|
4852
|
+
tableColumns,
|
|
4853
|
+
pictureUrl,
|
|
4854
|
+
hasOle
|
|
4855
|
+
});
|
|
4399
4856
|
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
offset += 8;
|
|
4403
|
-
} else {
|
|
4404
|
-
offset += 8 + recLen;
|
|
4405
|
-
}
|
|
4406
|
-
}
|
|
4407
|
-
if (currentSlideTexts.length > 0) {
|
|
4408
|
-
const title = currentSlideTexts[0] || "Slide";
|
|
4409
|
-
const texts = currentSlideTexts.slice(1);
|
|
4410
|
-
slides.push({ title, texts });
|
|
4857
|
+
if (isContainer) offset += 8;
|
|
4858
|
+
else offset += 8 + recLen;
|
|
4411
4859
|
}
|
|
4412
4860
|
if (slides.length === 0) {
|
|
4413
4861
|
const rawText = new TextDecoder("latin1", { fatal: false }).decode(stream);
|
|
4414
|
-
const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{
|
|
4415
|
-
const
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4862
|
+
const matches = rawText.match(/[A-Za-z0-9\s,.:;!?'"-]{5,}/g) || [];
|
|
4863
|
+
const clean = matches.map((m) => m.trim()).filter(
|
|
4864
|
+
(m) => m.length > 4 && !m.includes("PowerPoint") && !m.includes("Arial") && !m.includes("Times") && !m.includes("[Content_Types]") && !m.includes("_rels/") && !m.includes("xml")
|
|
4865
|
+
);
|
|
4866
|
+
if (clean.length > 0) {
|
|
4867
|
+
const chunkSize = 3;
|
|
4868
|
+
for (let i = 0; i < clean.length; i += chunkSize) {
|
|
4869
|
+
const chunk = clean.slice(i, i + chunkSize);
|
|
4420
4870
|
slides.push({
|
|
4871
|
+
slideIndex: slides.length + 1,
|
|
4421
4872
|
title: chunk[0] || `Slide ${Math.floor(i / chunkSize) + 1}`,
|
|
4422
|
-
|
|
4873
|
+
subtitle: chunk.length > 2 ? chunk[1] : void 0,
|
|
4874
|
+
paragraphs: chunk.length > 2 ? chunk.slice(2) : chunk.slice(1),
|
|
4875
|
+
tableColumns: [],
|
|
4876
|
+
pictureUrl: pictures[slides.length] || null
|
|
4423
4877
|
});
|
|
4424
4878
|
}
|
|
4425
4879
|
}
|