@l1yp/file-viewer 0.1.4 → 0.1.5

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.
Files changed (53) hide show
  1. package/README.md +2 -0
  2. package/dist/components/AnsiText.d.ts +47 -0
  3. package/dist/components/AnsiText.js +41 -0
  4. package/dist/components/AnsiText.js.map +1 -0
  5. package/dist/components/CodeView.js +1 -1
  6. package/dist/components/CodeView.js.map +1 -1
  7. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +80 -71
  8. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -1
  9. package/dist/components/DocxEmbeddedFiles.js +9 -0
  10. package/dist/components/DocxEmbeddedFiles.js.map +1 -0
  11. package/dist/components/DocxEmbeddedFiles.vue.d.ts +15 -0
  12. package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js +69 -0
  13. package/dist/components/DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js.map +1 -0
  14. package/dist/components/DocxViewer.js +1 -1
  15. package/dist/components/DocxViewer.js.map +1 -1
  16. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +180 -50
  17. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/components/FileViewerBody.js +1 -1
  19. package/dist/components/FileViewerBody.js.map +1 -1
  20. package/dist/components/FileViewerBody.vue.d.ts +1 -0
  21. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +117 -114
  22. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
  23. package/dist/components/FileViewerPanel.js +1 -1
  24. package/dist/components/FileViewerPanel.js.map +1 -1
  25. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +18 -18
  26. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  27. package/dist/components/LogViewer.js +1 -1
  28. package/dist/components/LogViewer.js.map +1 -1
  29. package/dist/components/LogViewer.vue.d.ts +3 -1
  30. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +284 -280
  31. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  32. package/dist/lib/ansi.d.ts +44 -0
  33. package/dist/lib/ansi.js +224 -0
  34. package/dist/lib/ansi.js.map +1 -0
  35. package/dist/lib/cfb.d.ts +37 -0
  36. package/dist/lib/cfb.js +112 -0
  37. package/dist/lib/cfb.js.map +1 -0
  38. package/dist/lib/docxEmbeddedDom.d.ts +22 -0
  39. package/dist/lib/docxEmbeddedDom.js +128 -0
  40. package/dist/lib/docxEmbeddedDom.js.map +1 -0
  41. package/dist/lib/docxEmbeddings.d.ts +41 -0
  42. package/dist/lib/docxEmbeddings.js +226 -0
  43. package/dist/lib/docxEmbeddings.js.map +1 -0
  44. package/dist/lib/msi.js +62 -154
  45. package/dist/lib/msi.js.map +1 -1
  46. package/dist/lib/olePackage.d.ts +8 -0
  47. package/dist/lib/olePackage.js +37 -0
  48. package/dist/lib/olePackage.js.map +1 -0
  49. package/dist/lib/pptxFirstSlide.d.ts +11 -0
  50. package/dist/lib/pptxFirstSlide.js +34 -0
  51. package/dist/lib/pptxFirstSlide.js.map +1 -0
  52. package/dist/style.css +1 -1
  53. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ansi.js","names":[],"sources":["../../src/lib/ansi.ts"],"sourcesContent":["/**\n * Safe, dependency-free ANSI/ECMA-48 text parser for static console transcripts.\n *\n * Only SGR (Select Graphic Rendition, CSI … m) changes presentation. Other terminal control sequences are\n * consumed without being executed: cursor movement / erase commands have no useful meaning in a static file,\n * while OSC hyperlinks and device-control strings must never become active page content. The result is plain\n * text plus a small whitelist of CSS properties, so callers can render it with normal Vue text nodes.\n */\n\nexport interface AnsiTextStyle {\n color?: string\n backgroundColor?: string\n fontWeight?: '700'\n fontStyle?: 'italic'\n opacity?: number\n textDecorationLine?: string\n textDecorationStyle?: 'double'\n visibility?: 'hidden'\n}\n\nexport interface AnsiSegment {\n text: string\n style?: AnsiTextStyle\n}\n\nexport interface AnsiLine {\n text: string\n segments: AnsiSegment[]\n}\n\nexport interface AnsiDocument {\n /** True when at least one ANSI/C1 control sequence was consumed. */\n hasAnsi: boolean\n /** Control-free text, with CR/LF variants normalized to `\\n`. */\n text: string\n lines: AnsiLine[]\n}\n\nexport interface AnsiRenderPiece extends AnsiSegment {\n marked: boolean\n}\n\ninterface SgrState {\n foreground?: string\n background?: string\n bold: boolean\n dim: boolean\n italic: boolean\n underline: boolean\n doubleUnderline: boolean\n strike: boolean\n overline: boolean\n inverse: boolean\n conceal: boolean\n}\n\nconst ESC = '\\u001b'\nconst CSI = '\\u009b'\nconst OSC = '\\u009d'\nconst ST = '\\u009c'\n\n// Theme-aware ANSI 0..15 palette. Extended 256-color / truecolor values are emitted as validated rgb().\nconst ANSI_COLORS = [\n 'var(--ansi-black, #24292f)',\n 'var(--ansi-red, #cf222e)',\n 'var(--ansi-green, #116329)',\n 'var(--ansi-yellow, #9a6700)',\n 'var(--ansi-blue, #0969da)',\n 'var(--ansi-magenta, #8250df)',\n 'var(--ansi-cyan, #1b7c83)',\n 'var(--ansi-white, #57606a)',\n 'var(--ansi-bright-black, #57606a)',\n 'var(--ansi-bright-red, #a40e26)',\n 'var(--ansi-bright-green, #1a7f37)',\n 'var(--ansi-bright-yellow, #bf8700)',\n 'var(--ansi-bright-blue, #218bff)',\n 'var(--ansi-bright-magenta, #a475f9)',\n 'var(--ansi-bright-cyan, #3192aa)',\n 'var(--ansi-bright-white, #6e7781)',\n] as const\n\nfunction defaultState(): SgrState {\n return {\n foreground: undefined,\n background: undefined,\n bold: false,\n dim: false,\n italic: false,\n underline: false,\n doubleUnderline: false,\n strike: false,\n overline: false,\n inverse: false,\n conceal: false,\n }\n}\n\n/** Fast gate used by the viewers so ordinary large text does not allocate a segment tree. */\nexport function hasAnsiSequences(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i)\n if (code === 0x1b || (code >= 0x80 && code <= 0x9f)) return true\n }\n return false\n}\n\nfunction byte(value: number | undefined): value is number {\n return value !== undefined && Number.isInteger(value) && value >= 0 && value <= 255\n}\n\nfunction indexedColor(index: number | undefined): string | undefined {\n if (!byte(index)) return undefined\n if (index < 16) return ANSI_COLORS[index]\n if (index < 232) {\n const n = index - 16\n const levels = [0, 95, 135, 175, 215, 255]\n const r = levels[Math.floor(n / 36)]\n const g = levels[Math.floor((n % 36) / 6)]\n const b = levels[n % 6]\n return `rgb(${r} ${g} ${b})`\n }\n const gray = 8 + (index - 232) * 10\n return `rgb(${gray} ${gray} ${gray})`\n}\n\nfunction rgbColor(\n r: number | undefined,\n g: number | undefined,\n b: number | undefined,\n): string | undefined {\n return byte(r) && byte(g) && byte(b) ? `rgb(${r} ${g} ${b})` : undefined\n}\n\nfunction styleFor(state: SgrState): AnsiTextStyle | undefined {\n let foreground = state.foreground\n let background = state.background\n if (state.inverse) {\n const inverseForeground = background ?? 'var(--code-bg)'\n background = foreground ?? 'var(--text)'\n foreground = inverseForeground\n }\n\n const style: AnsiTextStyle = {}\n if (foreground) style.color = foreground\n if (background) style.backgroundColor = background\n if (state.bold) style.fontWeight = '700'\n if (state.italic) style.fontStyle = 'italic'\n if (state.dim) style.opacity = 0.65\n const decorations: string[] = []\n if (state.underline || state.doubleUnderline) decorations.push('underline')\n if (state.strike) decorations.push('line-through')\n if (state.overline) decorations.push('overline')\n if (decorations.length) style.textDecorationLine = decorations.join(' ')\n if (state.doubleUnderline) style.textDecorationStyle = 'double'\n if (state.conceal) style.visibility = 'hidden'\n return Object.keys(style).length ? style : undefined\n}\n\nfunction sameStyle(a: AnsiTextStyle | undefined, b: AnsiTextStyle | undefined): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return (\n a.color === b.color &&\n a.backgroundColor === b.backgroundColor &&\n a.fontWeight === b.fontWeight &&\n a.fontStyle === b.fontStyle &&\n a.opacity === b.opacity &&\n a.textDecorationLine === b.textDecorationLine &&\n a.textDecorationStyle === b.textDecorationStyle &&\n a.visibility === b.visibility\n )\n}\n\nfunction applyBasicSgr(code: number, state: SgrState): void {\n if (code === 0) Object.assign(state, defaultState())\n else if (code === 1) state.bold = true\n else if (code === 2) state.dim = true\n else if (code === 3) state.italic = true\n else if (code === 4) {\n state.underline = true\n state.doubleUnderline = false\n } else if (code === 7) state.inverse = true\n else if (code === 8) state.conceal = true\n else if (code === 9) state.strike = true\n else if (code === 21) {\n state.underline = true\n state.doubleUnderline = true\n } else if (code === 22) {\n state.bold = false\n state.dim = false\n } else if (code === 23) state.italic = false\n else if (code === 24) {\n state.underline = false\n state.doubleUnderline = false\n } else if (code === 27) state.inverse = false\n else if (code === 28) state.conceal = false\n else if (code === 29) state.strike = false\n else if (code >= 30 && code <= 37) state.foreground = ANSI_COLORS[code - 30]\n else if (code === 39) state.foreground = undefined\n else if (code >= 40 && code <= 47) state.background = ANSI_COLORS[code - 40]\n else if (code === 49) state.background = undefined\n else if (code === 53) state.overline = true\n else if (code === 55) state.overline = false\n else if (code >= 90 && code <= 97) state.foreground = ANSI_COLORS[code - 90 + 8]\n else if (code >= 100 && code <= 107) state.background = ANSI_COLORS[code - 100 + 8]\n}\n\nfunction applyColonColor(token: string, state: SgrState): boolean {\n const raw = token.split(':')\n const target = Number(raw[0])\n if (target !== 38 && target !== 48) return false\n const mode = Number(raw[1])\n let color: string | undefined\n if (mode === 5) color = indexedColor(Number(raw[raw.length - 1]))\n else if (mode === 2 && raw.length >= 5) {\n const rgb = raw.slice(-3).map(Number)\n color = rgbColor(rgb[0], rgb[1], rgb[2])\n }\n if (color) {\n if (target === 38) state.foreground = color\n else state.background = color\n }\n return true\n}\n\nfunction sgrNumber(token: string): number | undefined {\n if (token === '') return 0\n if (!/^\\d+$/.test(token)) return undefined\n return Number(token)\n}\n\nfunction applySgr(parameters: string, state: SgrState): void {\n const tokens = parameters === '' ? ['0'] : parameters.split(';')\n for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i]\n if (token.includes(':')) {\n applyColonColor(token, state)\n continue\n }\n const code = sgrNumber(token)\n if (code === undefined) continue\n if (code !== 38 && code !== 48) {\n applyBasicSgr(code, state)\n continue\n }\n\n const mode = sgrNumber(tokens[i + 1] ?? '')\n let color: string | undefined\n if (mode === 5 && i + 2 < tokens.length) {\n color = indexedColor(sgrNumber(tokens[i + 2]))\n i += 2\n } else if (mode === 2 && i + 4 < tokens.length) {\n color = rgbColor(sgrNumber(tokens[i + 2]), sgrNumber(tokens[i + 3]), sgrNumber(tokens[i + 4]))\n i += 4\n }\n if (color) {\n if (code === 38) state.foreground = color\n else state.background = color\n }\n }\n}\n\nfunction csiEnd(text: string, start: number): number {\n for (let i = start; i < text.length; i++) {\n const code = text.charCodeAt(i)\n if (code >= 0x40 && code <= 0x7e) return i\n }\n return text.length - 1\n}\n\nfunction stringControlEnd(text: string, start: number): number {\n for (let i = start; i < text.length; i++) {\n if (text[i] === '\\u0007' || text[i] === ST) return i\n if (text[i] === ESC && text[i + 1] === '\\\\') return i + 1\n }\n return text.length - 1\n}\n\n/** Parse ANSI presentation and return only inert text + validated styles. */\nexport function parseAnsi(text: string): AnsiDocument {\n const state = defaultState()\n const lines: AnsiLine[] = []\n let segments: AnsiSegment[] = []\n let lineText = ''\n let buffer = ''\n let hasAnsi = false\n\n const flushText = (): void => {\n if (!buffer) return\n const style = styleFor(state)\n const previous = segments[segments.length - 1]\n if (previous && sameStyle(previous.style, style)) previous.text += buffer\n else segments.push({ text: buffer, style })\n lineText += buffer\n buffer = ''\n }\n const finishLine = (): void => {\n flushText()\n lines.push({ text: lineText, segments })\n segments = []\n lineText = ''\n }\n\n let i = 0\n while (i < text.length) {\n const char = text[i]\n const code = text.charCodeAt(i)\n\n if (char === '\\n') {\n finishLine()\n i++\n continue\n }\n if (char === '\\r') {\n finishLine()\n i += text[i + 1] === '\\n' ? 2 : 1\n continue\n }\n\n const isEscCsi = char === ESC && text[i + 1] === '['\n if (isEscCsi || char === CSI) {\n flushText()\n hasAnsi = true\n const bodyStart = i + (isEscCsi ? 2 : 1)\n const end = csiEnd(text, bodyStart)\n if (end < text.length && text[end] === 'm') applySgr(text.slice(bodyStart, end), state)\n i = end + 1\n continue\n }\n\n const isEscString = char === ESC && (text[i + 1] === ']' || 'PX^_'.includes(text[i + 1] ?? ''))\n const isC1String =\n char === OSC || code === 0x90 || code === 0x98 || code === 0x9e || code === 0x9f\n if (isEscString || isC1String) {\n flushText()\n hasAnsi = true\n const bodyStart = i + (isEscString ? 2 : 1)\n i = stringControlEnd(text, bodyStart) + 1\n continue\n }\n\n if (char === ESC) {\n flushText()\n hasAnsi = true\n i++\n // Consume the intermediates and final byte of a non-CSI escape sequence (charset selection, save/restore,\n // keypad mode, and similar commands). A truncated trailing ESC is simply discarded.\n while (i < text.length && text.charCodeAt(i) >= 0x20 && text.charCodeAt(i) <= 0x2f) i++\n if (i < text.length) i++\n continue\n }\n\n if ((code < 0x20 && char !== '\\t') || (code >= 0x7f && code <= 0x9f)) {\n flushText()\n hasAnsi = true\n i++\n continue\n }\n\n buffer += char\n i++\n }\n\n finishLine()\n return { hasAnsi, text: lines.map((line) => line.text).join('\\n'), lines }\n}\n\n/** Plain text for search/copy/folding; avoids parsing the overwhelmingly common no-ANSI path. */\nexport function ansiPlainText(text: string): string {\n return hasAnsiSequences(text) ? parseAnsi(text).text : text\n}\n\n/** Split one parsed (or ordinary) line around a case-insensitive search query without losing ANSI styling. */\nexport function ansiRenderPieces(line: AnsiLine | string, query = ''): AnsiRenderPiece[] {\n const source: AnsiLine =\n typeof line === 'string' ? { text: line, segments: line ? [{ text: line }] : [] } : line\n if (!query) return source.segments.map((segment) => ({ ...segment, marked: false }))\n\n const lower = source.text.toLowerCase()\n const needle = query.toLowerCase()\n if (!needle) return source.segments.map((segment) => ({ ...segment, marked: false }))\n\n const matches: Array<[number, number]> = []\n let found = lower.indexOf(needle)\n while (found !== -1) {\n matches.push([found, found + query.length])\n found = lower.indexOf(needle, found + query.length)\n }\n if (!matches.length) return source.segments.map((segment) => ({ ...segment, marked: false }))\n\n const pieces: AnsiRenderPiece[] = []\n let offset = 0\n let matchIndex = 0\n for (const segment of source.segments) {\n const segmentStart = offset\n const segmentEnd = offset + segment.text.length\n let cursor = segmentStart\n while (cursor < segmentEnd) {\n while (matchIndex < matches.length && matches[matchIndex][1] <= cursor) matchIndex++\n const match = matches[matchIndex]\n const marked = Boolean(match && match[0] < segmentEnd && match[1] > cursor)\n const boundary = marked\n ? Math.min(segmentEnd, match[1])\n : Math.min(segmentEnd, match?.[0] ?? segmentEnd)\n pieces.push({\n text: segment.text.slice(cursor - segmentStart, boundary - segmentStart),\n style: segment.style,\n marked,\n })\n cursor = boundary\n }\n offset = segmentEnd\n }\n return pieces\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /** Generic, dependency-free reader for OLE2 Compound File Binary Format (MS-CFB / CFBF). */
2
+ export interface CompoundFileEntry {
3
+ /** Raw UTF-16 directory name, excluding its null terminator. */
4
+ name: string;
5
+ /** 1 = storage, 2 = stream, 5 = root storage. */
6
+ type: number;
7
+ start: number;
8
+ size: number;
9
+ }
10
+ export declare class CompoundFileError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ /**
14
+ * Read-only compound-file mini-filesystem. Used by both MSI metadata parsing and DOCX OLE package extraction.
15
+ * Sector chains are bounded and stream reads are clamped to the input so corrupt files cannot loop forever or
16
+ * read outside their supplied ArrayBuffer.
17
+ */
18
+ export declare class CompoundFile {
19
+ private readonly buffer;
20
+ private readonly sectorSize;
21
+ private readonly maxSectors;
22
+ private readonly miniSectorSize;
23
+ private readonly miniCutoff;
24
+ private readonly fat;
25
+ private readonly miniFat;
26
+ private miniStream;
27
+ readonly entries: CompoundFileEntry[];
28
+ constructor(buffer: ArrayBuffer);
29
+ findStream(name: string): CompoundFileEntry | undefined;
30
+ /** Return one stream's bytes, bounded to its declared size and the input's available sectors. */
31
+ stream(entry: CompoundFileEntry): Uint8Array;
32
+ private sectorOffset;
33
+ private chain;
34
+ private sectorBytes;
35
+ private readFatStream;
36
+ private readMiniStream;
37
+ }
@@ -0,0 +1,112 @@
1
+ //#region src/lib/cfb.ts
2
+ var e = [
3
+ 208,
4
+ 207,
5
+ 17,
6
+ 224,
7
+ 161,
8
+ 177,
9
+ 26,
10
+ 225
11
+ ], t = 4294967294, n = 4294967295, r = class extends Error {
12
+ constructor(e) {
13
+ super(e), this.name = "CompoundFileError";
14
+ }
15
+ }, i = class {
16
+ buffer;
17
+ sectorSize;
18
+ maxSectors;
19
+ miniSectorSize;
20
+ miniCutoff;
21
+ fat = [];
22
+ miniFat = [];
23
+ miniStream = new Uint8Array();
24
+ entries = [];
25
+ constructor(i) {
26
+ this.buffer = i;
27
+ let a = new DataView(i);
28
+ if (i.byteLength < 512) throw new r("文件过小,不是有效的 OLE 复合文件");
29
+ for (let t = 0; t < e.length; t++) if (a.getUint8(t) !== e[t]) throw new r("不是 OLE 复合文件(缺少 D0CF11E0 魔数)");
30
+ let o = a.getUint16(26, !0), s = a.getUint16(30, !0), c = a.getUint16(32, !0);
31
+ if (s !== 9 && s !== 12 || c !== 6) throw new r("OLE 复合文件的扇区尺寸无效");
32
+ this.sectorSize = 1 << s, this.maxSectors = Math.max(0, Math.floor(i.byteLength / this.sectorSize) - 1), this.miniSectorSize = 1 << c, this.miniCutoff = a.getUint32(56, !0);
33
+ let l = a.getUint32(48, !0), u = a.getUint32(60, !0), d = a.getUint32(68, !0), f = this.sectorSize / 4, p = [];
34
+ for (let e = 0; e < 109; e++) {
35
+ let r = a.getUint32(76 + e * 4, !0);
36
+ r !== n && r !== t && p.push(r);
37
+ }
38
+ let m = d, h = 0;
39
+ for (; m !== t && m !== n && h++ < this.maxSectors;) {
40
+ let e = this.sectorOffset(m);
41
+ if (e + this.sectorSize > i.byteLength) break;
42
+ for (let t = 0; t < f - 1; t++) {
43
+ let r = a.getUint32(e + t * 4, !0);
44
+ r !== n && p.push(r);
45
+ }
46
+ m = a.getUint32(e + (f - 1) * 4, !0);
47
+ }
48
+ for (let e of p) {
49
+ let t = this.sectorOffset(e);
50
+ if (!(t + this.sectorSize > i.byteLength)) for (let e = 0; e < f; e++) this.fat.push(a.getUint32(t + e * 4, !0));
51
+ }
52
+ let g = this.readFatStream(l, this.chain(l).length * this.sectorSize), _ = new DataView(g.buffer, g.byteOffset, g.byteLength);
53
+ for (let e = 0; e + 128 <= g.length; e += 128) {
54
+ let t = _.getUint8(e + 66);
55
+ if (t === 0) continue;
56
+ let n = Math.min(_.getUint16(e + 64, !0), 64), r = "";
57
+ for (let t = 0; t + 1 < n; t += 2) {
58
+ let n = _.getUint16(e + t, !0);
59
+ if (n === 0) break;
60
+ r += String.fromCharCode(n);
61
+ }
62
+ let i = _.getUint32(e + 116, !0), a = o <= 3 ? _.getUint32(e + 120, !0) : Number(_.getBigUint64(e + 120, !0));
63
+ Number.isSafeInteger(a) && a >= 0 && this.entries.push({
64
+ name: r,
65
+ type: t,
66
+ start: i,
67
+ size: a
68
+ });
69
+ }
70
+ let v = this.entries.find((e) => e.type === 5);
71
+ v && (this.miniStream = this.readFatStream(v.start, v.size));
72
+ for (let e of this.chain(u)) {
73
+ let t = this.sectorOffset(e);
74
+ if (!(t + this.sectorSize > i.byteLength)) for (let e = 0; e < f; e++) this.miniFat.push(a.getUint32(t + e * 4, !0));
75
+ }
76
+ }
77
+ findStream(e) {
78
+ return this.entries.find((t) => t.type === 2 && t.name === e);
79
+ }
80
+ stream(e) {
81
+ return e.size === 0 ? new Uint8Array() : e.type === 5 || e.size >= this.miniCutoff ? this.readFatStream(e.start, e.size) : this.readMiniStream(e.start, e.size);
82
+ }
83
+ sectorOffset(e) {
84
+ return (e + 1) * this.sectorSize;
85
+ }
86
+ chain(e) {
87
+ let r = [], i = e, a = 0;
88
+ for (; i !== t && i !== n && i < this.fat.length && i < this.maxSectors && a++ < this.maxSectors;) r.push(i), i = this.fat[i];
89
+ return r;
90
+ }
91
+ sectorBytes(e) {
92
+ let t = this.sectorOffset(e), n = Math.min(this.buffer.byteLength, t + this.sectorSize), r = new Uint8Array(this.sectorSize);
93
+ return t < this.buffer.byteLength && r.set(new Uint8Array(this.buffer, t, n - t)), r;
94
+ }
95
+ readFatStream(e, t) {
96
+ let n = this.chain(e), r = new Uint8Array(n.length * this.sectorSize);
97
+ for (let e = 0; e < n.length; e++) r.set(this.sectorBytes(n[e]), e * this.sectorSize);
98
+ return r.subarray(0, Math.min(t, r.length));
99
+ }
100
+ readMiniStream(e, r) {
101
+ let i = Math.ceil(r / this.miniSectorSize), a = new Uint8Array(i * this.miniSectorSize), o = e, s = 0;
102
+ for (; o !== t && o !== n && o < this.miniFat.length && s < i;) {
103
+ let e = o * this.miniSectorSize;
104
+ a.set(this.miniStream.subarray(e, e + this.miniSectorSize), s * this.miniSectorSize), s++, o = this.miniFat[o];
105
+ }
106
+ return a.subarray(0, Math.min(r, a.length));
107
+ }
108
+ };
109
+ //#endregion
110
+ export { i as CompoundFile, r as CompoundFileError };
111
+
112
+ //# sourceMappingURL=cfb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cfb.js","names":[],"sources":["../../src/lib/cfb.ts"],"sourcesContent":["/** Generic, dependency-free reader for OLE2 Compound File Binary Format (MS-CFB / CFBF). */\n\nconst CFB_SIGNATURE = [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]\nconst ENDOFCHAIN = 0xfffffffe\nconst FREESECT = 0xffffffff\n\nexport interface CompoundFileEntry {\n /** Raw UTF-16 directory name, excluding its null terminator. */\n name: string\n /** 1 = storage, 2 = stream, 5 = root storage. */\n type: number\n start: number\n size: number\n}\n\nexport class CompoundFileError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CompoundFileError'\n }\n}\n\n/**\n * Read-only compound-file mini-filesystem. Used by both MSI metadata parsing and DOCX OLE package extraction.\n * Sector chains are bounded and stream reads are clamped to the input so corrupt files cannot loop forever or\n * read outside their supplied ArrayBuffer.\n */\nexport class CompoundFile {\n private readonly buffer: ArrayBuffer\n private readonly sectorSize: number\n private readonly maxSectors: number\n private readonly miniSectorSize: number\n private readonly miniCutoff: number\n private readonly fat: number[] = []\n private readonly miniFat: number[] = []\n private miniStream: Uint8Array = new Uint8Array(0)\n readonly entries: CompoundFileEntry[] = []\n\n constructor(buffer: ArrayBuffer) {\n this.buffer = buffer\n const view = new DataView(buffer)\n if (buffer.byteLength < 512) throw new CompoundFileError('文件过小,不是有效的 OLE 复合文件')\n for (let i = 0; i < CFB_SIGNATURE.length; i++) {\n if (view.getUint8(i) !== CFB_SIGNATURE[i]) {\n throw new CompoundFileError('不是 OLE 复合文件(缺少 D0CF11E0 魔数)')\n }\n }\n\n const major = view.getUint16(0x1a, true)\n const sectorShift = view.getUint16(0x1e, true)\n const miniSectorShift = view.getUint16(0x20, true)\n if ((sectorShift !== 9 && sectorShift !== 12) || miniSectorShift !== 6) {\n throw new CompoundFileError('OLE 复合文件的扇区尺寸无效')\n }\n this.sectorSize = 1 << sectorShift\n this.maxSectors = Math.max(0, Math.floor(buffer.byteLength / this.sectorSize) - 1)\n this.miniSectorSize = 1 << miniSectorShift\n this.miniCutoff = view.getUint32(0x38, true)\n const firstDirectory = view.getUint32(0x30, true)\n const firstMiniFat = view.getUint32(0x3c, true)\n const firstDifat = view.getUint32(0x44, true)\n const entriesPerSector = this.sectorSize / 4\n\n // DIFAT → FAT-sector indices. The header carries the first 109; large files chain additional sectors.\n const difat: number[] = []\n for (let i = 0; i < 109; i++) {\n const value = view.getUint32(0x4c + i * 4, true)\n if (value !== FREESECT && value !== ENDOFCHAIN) difat.push(value)\n }\n let difatSector = firstDifat\n let difatGuard = 0\n while (\n difatSector !== ENDOFCHAIN &&\n difatSector !== FREESECT &&\n difatGuard++ < this.maxSectors\n ) {\n const base = this.sectorOffset(difatSector)\n if (base + this.sectorSize > buffer.byteLength) break\n for (let i = 0; i < entriesPerSector - 1; i++) {\n const value = view.getUint32(base + i * 4, true)\n if (value !== FREESECT) difat.push(value)\n }\n difatSector = view.getUint32(base + (entriesPerSector - 1) * 4, true)\n }\n for (const fatSector of difat) {\n const base = this.sectorOffset(fatSector)\n if (base + this.sectorSize > buffer.byteLength) continue\n for (let i = 0; i < entriesPerSector; i++) {\n this.fat.push(view.getUint32(base + i * 4, true))\n }\n }\n\n // Directory entries are 128-byte records in a regular FAT stream.\n const directoryBytes = this.readFatStream(\n firstDirectory,\n this.chain(firstDirectory).length * this.sectorSize,\n )\n const directoryView = new DataView(\n directoryBytes.buffer,\n directoryBytes.byteOffset,\n directoryBytes.byteLength,\n )\n for (let offset = 0; offset + 128 <= directoryBytes.length; offset += 128) {\n const type = directoryView.getUint8(offset + 66)\n if (type === 0) continue\n const nameLength = Math.min(directoryView.getUint16(offset + 64, true), 64)\n let name = ''\n for (let cursor = 0; cursor + 1 < nameLength; cursor += 2) {\n const code = directoryView.getUint16(offset + cursor, true)\n if (code === 0) break\n name += String.fromCharCode(code)\n }\n const start = directoryView.getUint32(offset + 116, true)\n const size =\n major <= 3\n ? directoryView.getUint32(offset + 120, true)\n : Number(directoryView.getBigUint64(offset + 120, true))\n if (Number.isSafeInteger(size) && size >= 0) this.entries.push({ name, type, start, size })\n }\n\n // Small streams are stored inside the root's regular-FAT mini stream and indexed by the MiniFAT.\n const root = this.entries.find((entry) => entry.type === 5)\n if (root) this.miniStream = this.readFatStream(root.start, root.size)\n for (const sector of this.chain(firstMiniFat)) {\n const base = this.sectorOffset(sector)\n if (base + this.sectorSize > buffer.byteLength) continue\n for (let i = 0; i < entriesPerSector; i++) {\n this.miniFat.push(view.getUint32(base + i * 4, true))\n }\n }\n }\n\n findStream(name: string): CompoundFileEntry | undefined {\n return this.entries.find((entry) => entry.type === 2 && entry.name === name)\n }\n\n /** Return one stream's bytes, bounded to its declared size and the input's available sectors. */\n stream(entry: CompoundFileEntry): Uint8Array {\n if (entry.size === 0) return new Uint8Array(0)\n if (entry.type === 5 || entry.size >= this.miniCutoff) {\n return this.readFatStream(entry.start, entry.size)\n }\n return this.readMiniStream(entry.start, entry.size)\n }\n\n private sectorOffset(sector: number): number {\n return (sector + 1) * this.sectorSize\n }\n\n private chain(start: number): number[] {\n const result: number[] = []\n let sector = start\n let guard = 0\n while (\n sector !== ENDOFCHAIN &&\n sector !== FREESECT &&\n sector < this.fat.length &&\n sector < this.maxSectors &&\n guard++ < this.maxSectors\n ) {\n result.push(sector)\n sector = this.fat[sector]\n }\n return result\n }\n\n private sectorBytes(sector: number): Uint8Array {\n const offset = this.sectorOffset(sector)\n const end = Math.min(this.buffer.byteLength, offset + this.sectorSize)\n const result = new Uint8Array(this.sectorSize)\n if (offset < this.buffer.byteLength) {\n result.set(new Uint8Array(this.buffer, offset, end - offset))\n }\n return result\n }\n\n private readFatStream(start: number, size: number): Uint8Array {\n const sectors = this.chain(start)\n const result = new Uint8Array(sectors.length * this.sectorSize)\n for (let i = 0; i < sectors.length; i++) {\n result.set(this.sectorBytes(sectors[i]), i * this.sectorSize)\n }\n return result.subarray(0, Math.min(size, result.length))\n }\n\n private readMiniStream(start: number, size: number): Uint8Array {\n const needed = Math.ceil(size / this.miniSectorSize)\n const result = new Uint8Array(needed * this.miniSectorSize)\n let sector = start\n let index = 0\n while (\n sector !== ENDOFCHAIN &&\n sector !== FREESECT &&\n sector < this.miniFat.length &&\n index < needed\n ) {\n const offset = sector * this.miniSectorSize\n result.set(\n this.miniStream.subarray(offset, offset + this.miniSectorSize),\n index * this.miniSectorSize,\n )\n index++\n sector = this.miniFat[sector]\n }\n return result.subarray(0, Math.min(size, result.length))\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type DocxEmbeddedFile } from '@/lib/docxEmbeddings';
2
+ export interface DocxEmbeddedDomController {
3
+ /** Number of embedded objects mounted into their original document position. */
4
+ mounted: number;
5
+ /** Resolves after every inline snapshot/slide has either rendered or fallen back to a file card. */
6
+ ready: Promise<void>;
7
+ dispose: () => void;
8
+ }
9
+ interface MountDocxEmbeddedPreviewsOptions {
10
+ root: HTMLElement;
11
+ buffer: ArrayBuffer;
12
+ files: readonly DocxEmbeddedFile[];
13
+ onOpen: (file: DocxEmbeddedFile) => void;
14
+ }
15
+ /**
16
+ * Put Word/WPS's own OLE snapshots, or a PPTX first-slide fallback, into the empty paragraphs left by
17
+ * docx-preview.
18
+ * The third-party renderer owns this subtree imperatively, so this adapter also uses explicit DOM nodes and
19
+ * listeners; dispose removes every listener and Blob URL before the renderer replaces the document.
20
+ */
21
+ export declare function mountDocxEmbeddedPreviews(options: MountDocxEmbeddedPreviewsOptions): DocxEmbeddedDomController;
22
+ export {};
@@ -0,0 +1,128 @@
1
+ import { extractDocxEmbeddedFile as e, extractDocxEmbeddedPreview as t } from "./docxEmbeddings.js";
2
+ //#region src/lib/docxEmbeddedDom.ts
3
+ function n(e) {
4
+ return (e.textContent ?? "").replace(/\s+/g, " ").trim();
5
+ }
6
+ function r(e) {
7
+ return !n(e) && !e.querySelector("img, table, svg, button, object");
8
+ }
9
+ function i(e, t) {
10
+ let i = Array.from(e.querySelectorAll("section.docx p")), a = [], o = /* @__PURE__ */ new Set(), s = 0;
11
+ for (let e of t) {
12
+ let t = -1;
13
+ if (e.label) {
14
+ for (let r = s; r < i.length; r++) if (n(i[r]) === e.label) {
15
+ t = r;
16
+ break;
17
+ }
18
+ }
19
+ let c;
20
+ if (t >= 0) for (let e = t + 1; e < i.length; e++) {
21
+ let t = i[e];
22
+ if (r(t) && !o.has(t)) {
23
+ c = t, s = e + 1;
24
+ break;
25
+ }
26
+ if (n(t)) break;
27
+ }
28
+ if (!c) for (let e = s; e < i.length - 1; e++) {
29
+ let t = i[e], a = n(i[e + 1]);
30
+ if (r(t) && !o.has(t) && /^(?:点击|双击).*(?:查看|打开).*(?:文件|表格)/.test(a)) {
31
+ c = t, s = e + 1;
32
+ break;
33
+ }
34
+ }
35
+ if (!c && e.storage === "ole-package") for (let e = s; e < i.length; e++) {
36
+ let t = i[e];
37
+ if (r(t) && t.childElementCount > 0 && !t.querySelector("[id^=\"heading_\"]") && !o.has(t)) {
38
+ c = t, s = e + 1;
39
+ break;
40
+ }
41
+ }
42
+ c && (o.add(c), a.push({
43
+ file: e,
44
+ host: c
45
+ }));
46
+ }
47
+ return a;
48
+ }
49
+ function a(e) {
50
+ let t = e.lastIndexOf(".");
51
+ return t > 0 ? e.slice(t + 1).toUpperCase() : "FILE";
52
+ }
53
+ function o(e) {
54
+ return e.contentType === "application/vnd.openxmlformats-officedocument.presentationml.presentation" || /\.pptx$/i.test(e.name);
55
+ }
56
+ function s(e, t) {
57
+ e.classList.remove("docx-embedded-preview--image", "docx-embedded-preview--pptx", "docx-embedded-preview--loading"), e.removeAttribute("aria-busy"), e.style.aspectRatio = "";
58
+ let n = document.createElement("span");
59
+ n.className = "docx-embedded-preview__badge", n.textContent = a(t.name);
60
+ let r = document.createElement("span");
61
+ r.className = "docx-embedded-preview__fallback-label", r.textContent = `${t.label || t.name} · 点击预览完整文件`, e.replaceChildren(n, r);
62
+ }
63
+ function c(n) {
64
+ let r = i(n.root, n.files), c = [], l = [], u = !1, d = r.map(async ({ file: r, host: i }) => {
65
+ i.classList.add("docx-embedded-host");
66
+ let d = document.createElement("div");
67
+ d.className = "docx-embedded-preview", d.dataset.docxEmbeddedId = r.id, d.title = `预览 ${r.name}`, d.setAttribute("role", "button"), d.setAttribute("aria-label", `预览内嵌文件 ${r.name}`), d.tabIndex = 0, r.preview?.widthPt && (d.style.maxWidth = `${r.preview.widthPt}pt`);
68
+ let f = () => n.onOpen(r), p = (e) => {
69
+ e.key !== "Enter" && e.key !== " " || (e.preventDefault(), f());
70
+ }, m = null, h = null;
71
+ d.addEventListener("click", f), d.addEventListener("keydown", p), s(d, r), i.replaceChildren(d), l.push(() => {
72
+ m?.abort(), h?.dispose(), d.removeEventListener("click", f), d.removeEventListener("keydown", p), i.contains(d) && i.replaceChildren(), i.classList.remove("docx-embedded-host");
73
+ });
74
+ try {
75
+ let i = await t(n.buffer, r);
76
+ if (u) return;
77
+ if (i) {
78
+ r.preview?.widthPt && r.preview.heightPt && (d.style.aspectRatio = `${r.preview.widthPt} / ${r.preview.heightPt}`);
79
+ let e = URL.createObjectURL(new Blob([i.bytes], { type: i.contentType }));
80
+ if (u) {
81
+ URL.revokeObjectURL(e);
82
+ return;
83
+ }
84
+ c.push(e);
85
+ let t = document.createElement("img");
86
+ t.className = "docx-embedded-preview__image", t.src = e, t.alt = `${r.label || r.name} 内容预览`, t.draggable = !1;
87
+ let n = document.createElement("span");
88
+ n.className = "docx-embedded-preview__hint", n.textContent = `${a(r.name)} · 点击预览完整文件`, d.classList.add("docx-embedded-preview--image"), d.replaceChildren(t, n);
89
+ return;
90
+ }
91
+ if (!o(r)) return;
92
+ d.classList.add("docx-embedded-preview--pptx", "docx-embedded-preview--loading"), d.setAttribute("aria-busy", "true"), d.style.aspectRatio = r.preview?.widthPt && r.preview.heightPt ? `${r.preview.widthPt} / ${r.preview.heightPt}` : "16 / 9";
93
+ let s = document.createElement("div");
94
+ s.className = "docx-embedded-preview__pptx-slide";
95
+ let l = document.createElement("span");
96
+ l.className = "docx-embedded-preview__pptx-status", l.textContent = "正在渲染 PPTX 第一页…", d.replaceChildren(s, l), m = new AbortController();
97
+ let [f, p] = await Promise.all([e(n.buffer, r), import("./pptxFirstSlide.js")]);
98
+ if (u) return;
99
+ if (h = await p.mountPptxFirstSlide({
100
+ container: s,
101
+ bytes: f,
102
+ signal: m.signal
103
+ }), u) {
104
+ h.dispose(), h = null;
105
+ return;
106
+ }
107
+ d.classList.remove("docx-embedded-preview--loading"), d.removeAttribute("aria-busy"), l.className = "docx-embedded-preview__hint", l.textContent = "PPTX · 点击预览完整文件";
108
+ } catch {
109
+ u || s(d, r);
110
+ }
111
+ });
112
+ return {
113
+ mounted: r.length,
114
+ ready: Promise.allSettled(d).then(() => void 0),
115
+ dispose: () => {
116
+ if (!u) {
117
+ u = !0;
118
+ for (let e of l) e();
119
+ for (let e of c) URL.revokeObjectURL(e);
120
+ c.length = 0, l.length = 0;
121
+ }
122
+ }
123
+ };
124
+ }
125
+ //#endregion
126
+ export { c as mountDocxEmbeddedPreviews };
127
+
128
+ //# sourceMappingURL=docxEmbeddedDom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docxEmbeddedDom.js","names":[],"sources":["../../src/lib/docxEmbeddedDom.ts"],"sourcesContent":["import {\n extractDocxEmbeddedFile,\n extractDocxEmbeddedPreview,\n type DocxEmbeddedFile,\n} from '@/lib/docxEmbeddings'\nimport type { PptxFirstSlideController } from '@/lib/pptxFirstSlide'\n\nexport interface DocxEmbeddedDomController {\n /** Number of embedded objects mounted into their original document position. */\n mounted: number\n /** Resolves after every inline snapshot/slide has either rendered or fallen back to a file card. */\n ready: Promise<void>\n dispose: () => void\n}\n\ninterface MountDocxEmbeddedPreviewsOptions {\n root: HTMLElement\n buffer: ArrayBuffer\n files: readonly DocxEmbeddedFile[]\n onOpen: (file: DocxEmbeddedFile) => void\n}\n\nfunction normalizedText(element: Element): string {\n return (element.textContent ?? '').replace(/\\s+/g, ' ').trim()\n}\n\nfunction isEmptyParagraph(paragraph: HTMLParagraphElement): boolean {\n return !normalizedText(paragraph) && !paragraph.querySelector('img, table, svg, button, object')\n}\n\nfunction findHostParagraphs(\n root: HTMLElement,\n files: readonly DocxEmbeddedFile[],\n): Array<{ file: DocxEmbeddedFile; host: HTMLParagraphElement }> {\n const paragraphs = Array.from(root.querySelectorAll<HTMLParagraphElement>('section.docx p'))\n const result: Array<{ file: DocxEmbeddedFile; host: HTMLParagraphElement }> = []\n const used = new Set<HTMLParagraphElement>()\n let cursor = 0\n\n for (const file of files) {\n let headingIndex = -1\n if (file.label) {\n for (let i = cursor; i < paragraphs.length; i++) {\n if (normalizedText(paragraphs[i]) === file.label) {\n headingIndex = i\n break\n }\n }\n }\n\n let host: HTMLParagraphElement | undefined\n if (headingIndex >= 0) {\n // docx-preview drops `w:object` itself but preserves its containing paragraph as the first empty <p>\n // after the caption. Stop at the next non-empty paragraph so a distant layout spacer is never hijacked.\n for (let i = headingIndex + 1; i < paragraphs.length; i++) {\n const paragraph = paragraphs[i]\n if (isEmptyParagraph(paragraph) && !used.has(paragraph)) {\n host = paragraph\n cursor = i + 1\n break\n }\n if (normalizedText(paragraph)) break\n }\n }\n\n if (!host) {\n // Caption-less fallback: accept only an empty paragraph immediately followed by Word's usual OLE hint.\n for (let i = cursor; i < paragraphs.length - 1; i++) {\n const paragraph = paragraphs[i]\n const nextText = normalizedText(paragraphs[i + 1])\n if (\n isEmptyParagraph(paragraph) &&\n !used.has(paragraph) &&\n /^(?:点击|双击).*(?:查看|打开).*(?:文件|表格)/.test(nextText)\n ) {\n host = paragraph\n cursor = i + 1\n break\n }\n }\n }\n\n if (!host && file.storage === 'ole-package') {\n // A caption-less OLE object at the document tail still leaves a non-empty DOM shell (typically its\n // bookmark spans). Prefer that over truly empty spacer paragraphs and generated heading anchors.\n for (let i = cursor; i < paragraphs.length; i++) {\n const paragraph = paragraphs[i]\n if (\n isEmptyParagraph(paragraph) &&\n paragraph.childElementCount > 0 &&\n !paragraph.querySelector('[id^=\"heading_\"]') &&\n !used.has(paragraph)\n ) {\n host = paragraph\n cursor = i + 1\n break\n }\n }\n }\n\n if (host) {\n used.add(host)\n result.push({ file, host })\n }\n }\n return result\n}\n\nfunction extension(name: string): string {\n const i = name.lastIndexOf('.')\n return i > 0 ? name.slice(i + 1).toUpperCase() : 'FILE'\n}\n\nfunction isPptx(file: DocxEmbeddedFile): boolean {\n return (\n file.contentType ===\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ||\n /\\.pptx$/i.test(file.name)\n )\n}\n\nfunction mountFallback(previewElement: HTMLElement, file: DocxEmbeddedFile): void {\n previewElement.classList.remove(\n 'docx-embedded-preview--image',\n 'docx-embedded-preview--pptx',\n 'docx-embedded-preview--loading',\n )\n previewElement.removeAttribute('aria-busy')\n previewElement.style.aspectRatio = ''\n const badge = document.createElement('span')\n badge.className = 'docx-embedded-preview__badge'\n badge.textContent = extension(file.name)\n const label = document.createElement('span')\n label.className = 'docx-embedded-preview__fallback-label'\n label.textContent = `${file.label || file.name} · 点击预览完整文件`\n previewElement.replaceChildren(badge, label)\n}\n\n/**\n * Put Word/WPS's own OLE snapshots, or a PPTX first-slide fallback, into the empty paragraphs left by\n * docx-preview.\n * The third-party renderer owns this subtree imperatively, so this adapter also uses explicit DOM nodes and\n * listeners; dispose removes every listener and Blob URL before the renderer replaces the document.\n */\nexport function mountDocxEmbeddedPreviews(\n options: MountDocxEmbeddedPreviewsOptions,\n): DocxEmbeddedDomController {\n const mounts = findHostParagraphs(options.root, options.files)\n const urls: string[] = []\n const cleanups: Array<() => void> = []\n let disposed = false\n\n const loads = mounts.map(async ({ file, host }) => {\n host.classList.add('docx-embedded-host')\n const previewElement = document.createElement('div')\n previewElement.className = 'docx-embedded-preview'\n previewElement.dataset.docxEmbeddedId = file.id\n previewElement.title = `预览 ${file.name}`\n previewElement.setAttribute('role', 'button')\n previewElement.setAttribute('aria-label', `预览内嵌文件 ${file.name}`)\n previewElement.tabIndex = 0\n if (file.preview?.widthPt) previewElement.style.maxWidth = `${file.preview.widthPt}pt`\n const open = (): void => options.onOpen(file)\n const openFromKeyboard = (event: KeyboardEvent): void => {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n open()\n }\n let pptxAbort: AbortController | null = null\n let pptxController: PptxFirstSlideController | null = null\n previewElement.addEventListener('click', open)\n previewElement.addEventListener('keydown', openFromKeyboard)\n mountFallback(previewElement, file)\n host.replaceChildren(previewElement)\n\n cleanups.push(() => {\n pptxAbort?.abort()\n pptxController?.dispose()\n previewElement.removeEventListener('click', open)\n previewElement.removeEventListener('keydown', openFromKeyboard)\n if (host.contains(previewElement)) host.replaceChildren()\n host.classList.remove('docx-embedded-host')\n })\n\n try {\n const preview = await extractDocxEmbeddedPreview(options.buffer, file)\n if (disposed) return\n if (preview) {\n if (file.preview?.widthPt && file.preview.heightPt) {\n previewElement.style.aspectRatio = `${file.preview.widthPt} / ${file.preview.heightPt}`\n }\n const url = URL.createObjectURL(new Blob([preview.bytes], { type: preview.contentType }))\n if (disposed) {\n URL.revokeObjectURL(url)\n return\n }\n urls.push(url)\n const image = document.createElement('img')\n image.className = 'docx-embedded-preview__image'\n image.src = url\n image.alt = `${file.label || file.name} 内容预览`\n image.draggable = false\n const hint = document.createElement('span')\n hint.className = 'docx-embedded-preview__hint'\n hint.textContent = `${extension(file.name)} · 点击预览完整文件`\n previewElement.classList.add('docx-embedded-preview--image')\n previewElement.replaceChildren(image, hint)\n return\n }\n\n if (!isPptx(file)) return\n previewElement.classList.add('docx-embedded-preview--pptx', 'docx-embedded-preview--loading')\n previewElement.setAttribute('aria-busy', 'true')\n previewElement.style.aspectRatio =\n file.preview?.widthPt && file.preview.heightPt\n ? `${file.preview.widthPt} / ${file.preview.heightPt}`\n : '16 / 9'\n const slide = document.createElement('div')\n slide.className = 'docx-embedded-preview__pptx-slide'\n const hint = document.createElement('span')\n hint.className = 'docx-embedded-preview__pptx-status'\n hint.textContent = '正在渲染 PPTX 第一页…'\n previewElement.replaceChildren(slide, hint)\n\n pptxAbort = new AbortController()\n const [bytes, renderer] = await Promise.all([\n extractDocxEmbeddedFile(options.buffer, file),\n import('@/lib/pptxFirstSlide'),\n ])\n if (disposed) return\n pptxController = await renderer.mountPptxFirstSlide({\n container: slide,\n bytes,\n signal: pptxAbort.signal,\n })\n if (disposed) {\n pptxController.dispose()\n pptxController = null\n return\n }\n previewElement.classList.remove('docx-embedded-preview--loading')\n previewElement.removeAttribute('aria-busy')\n hint.className = 'docx-embedded-preview__hint'\n hint.textContent = 'PPTX · 点击预览完整文件'\n } catch {\n // The actual embedded file remains previewable from this fallback card and from the top toolbar.\n if (!disposed) mountFallback(previewElement, file)\n }\n })\n\n return {\n mounted: mounts.length,\n ready: Promise.allSettled(loads).then(() => undefined),\n dispose: () => {\n if (disposed) return\n disposed = true\n for (const cleanup of cleanups) cleanup()\n for (const url of urls) URL.revokeObjectURL(url)\n urls.length = 0\n cleanups.length = 0\n },\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { type ZipEntry } from '@/lib/zip';
2
+ export interface DocxEmbeddedFile {
3
+ /** Stable relationship id inside word/document.xml. */
4
+ id: string;
5
+ /** Human-facing caption inferred from the nearest preceding Word paragraph. */
6
+ label: string;
7
+ /** Friendly download name (caption + original extension when a caption exists). */
8
+ name: string;
9
+ /** Original package basename, e.g. Microsoft_Excel_Worksheet1.xlsx. */
10
+ originalName: string;
11
+ /** Resolved package path inside the DOCX ZIP. */
12
+ path: string;
13
+ size: number;
14
+ contentType: string;
15
+ storage: 'package' | 'ole-package';
16
+ /** Word/WPS-generated visual snapshot for the embedded object, when present. */
17
+ preview?: DocxEmbeddedPreview;
18
+ /** Internal ZIP descriptor retained so extraction stays on-demand. */
19
+ entry: ZipEntry;
20
+ }
21
+ export interface DocxEmbeddedPreview {
22
+ path: string;
23
+ size: number;
24
+ contentType: string;
25
+ widthPt?: number;
26
+ heightPt?: number;
27
+ entry: ZipEntry;
28
+ }
29
+ export interface ExtractedDocxPreview {
30
+ bytes: Uint8Array;
31
+ contentType: string;
32
+ }
33
+ /**
34
+ * Discover embedded package relationships (`w:object` / `o:OLEObject`) in document order.
35
+ * `docx-preview` currently drops these nodes, so the caller can expose the original bytes separately.
36
+ */
37
+ export declare function readDocxEmbeddedFiles(buffer: ArrayBuffer): Promise<DocxEmbeddedFile[]>;
38
+ /** Extract one discovered embedded package on demand, with the same per-file ceiling as server exports. */
39
+ export declare function extractDocxEmbeddedFile(buffer: ArrayBuffer, file: DocxEmbeddedFile): Promise<Uint8Array>;
40
+ /** Extract the inert Word/WPS snapshot paired with an embedded object. */
41
+ export declare function extractDocxEmbeddedPreview(buffer: ArrayBuffer, file: DocxEmbeddedFile): Promise<ExtractedDocxPreview | null>;