@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":"DocxEmbeddedFiles.js","names":[],"sources":["../../src/components/DocxEmbeddedFiles.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport Icon from '@/components/icons/Icon.vue'\nimport { formatBytes } from '@/lib/format'\nimport type { DocxEmbeddedFile } from '@/lib/docxEmbeddings'\n\ndefineProps<{\n files: readonly DocxEmbeddedFile[]\n downloadBusyId?: string\n error?: string\n}>()\n\nconst emit = defineEmits<{\n open: [file: DocxEmbeddedFile]\n download: [file: DocxEmbeddedFile]\n}>()\n\nfunction extension(name: string): string {\n const i = name.lastIndexOf('.')\n return i > 0 ? name.slice(i + 1).toUpperCase() : 'FILE'\n}\n</script>\n\n<template>\n <section class=\"def\" aria-label=\"Word 文档内嵌文件\">\n <div class=\"def__heading\">\n <Icon name=\"paperclip\" :size=\"14\" />\n <span>内嵌文件</span>\n <span class=\"def__count\">{{ files.length }}</span>\n </div>\n <div class=\"def__list\">\n <div v-for=\"file in files\" :key=\"file.id\" class=\"def__item\">\n <button\n type=\"button\"\n class=\"def__open\"\n :title=\"`预览 ${file.name}`\"\n @click=\"emit('open', file)\"\n >\n <span class=\"def__ext\">{{ extension(file.name) }}</span>\n <span class=\"def__name\">{{ file.label || file.name }}</span>\n <span class=\"def__size\">{{ formatBytes(file.size) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"def__download\"\n :disabled=\"downloadBusyId === file.id\"\n :title=\"downloadBusyId === file.id ? '正在准备下载' : `下载 ${file.name}`\"\n @click=\"emit('download', file)\"\n >\n <Icon :name=\"downloadBusyId === file.id ? 'clock' : 'download'\" :size=\"14\" />\n </button>\n </div>\n </div>\n <span v-if=\"error\" class=\"def__error\" role=\"status\" :title=\"error\">{{ error }}</span>\n </section>\n</template>\n\n<style scoped>\n.def {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-shrink: 0;\n min-width: 0;\n padding: 7px 10px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.def__heading {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n color: var(--muted);\n font-size: 11.5px;\n font-weight: 600;\n white-space: nowrap;\n}\n.def__count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 999px;\n color: var(--accent);\n background: var(--accent-soft);\n font-size: 10.5px;\n font-variant-numeric: tabular-nums;\n}\n.def__list {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n overscroll-behavior-inline: contain;\n scrollbar-width: thin;\n}\n.def__item {\n display: flex;\n align-items: stretch;\n flex: 0 0 auto;\n min-width: 180px;\n max-width: 260px;\n height: 32px;\n border: 1px solid var(--border-2);\n border-radius: 8px;\n background: var(--surface-2);\n overflow: hidden;\n}\n.def__item:hover {\n border-color: var(--accent);\n}\n.def__open,\n.def__download {\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.def__open {\n display: flex;\n align-items: center;\n gap: 7px;\n flex: 1 1 auto;\n min-width: 0;\n padding: 0 8px;\n text-align: left;\n}\n.def__ext {\n flex-shrink: 0;\n color: var(--accent);\n font-size: 9.5px;\n font-weight: 700;\n letter-spacing: 0.04em;\n}\n.def__name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n color: var(--text);\n font-size: 11.5px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.def__size {\n flex-shrink: 0;\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n.def__download {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 30px;\n border-left: 1px solid var(--border);\n color: var(--muted);\n}\n.def__download:hover:not(:disabled) {\n color: var(--accent);\n background: var(--surface);\n}\n.def__download:disabled {\n cursor: wait;\n opacity: 0.6;\n}\n.def__error {\n flex: 0 1 180px;\n min-width: 0;\n overflow: hidden;\n color: var(--danger);\n font-size: 10.5px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@container (max-width: 500px) {\n .def {\n align-items: stretch;\n flex-direction: column;\n gap: 6px;\n }\n .def__item {\n min-width: 170px;\n }\n .def__error {\n flex-basis: auto;\n }\n}\n</style>\n"],"mappings":""}
@@ -0,0 +1,15 @@
1
+ import type { DocxEmbeddedFile } from '@/lib/docxEmbeddings';
2
+ type __VLS_Props = {
3
+ files: readonly DocxEmbeddedFile[];
4
+ downloadBusyId?: string;
5
+ error?: string;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ download: (file: DocxEmbeddedFile) => any;
9
+ open: (file: DocxEmbeddedFile) => any;
10
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
+ onDownload?: ((file: DocxEmbeddedFile) => any) | undefined;
12
+ onOpen?: ((file: DocxEmbeddedFile) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const _default: typeof __VLS_export;
15
+ export default _default;
@@ -0,0 +1,69 @@
1
+ import { formatBytes as e } from "../lib/format.js";
2
+ import t from "./icons/Icon.js";
3
+ import { Fragment as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, createVNode as o, defineComponent as s, openBlock as c, renderList as l, toDisplayString as u, unref as d } from "vue";
4
+ //#region src/components/DocxEmbeddedFiles.vue?vue&type=script&setup=true&lang.ts
5
+ var f = {
6
+ class: "def",
7
+ "aria-label": "Word 文档内嵌文件"
8
+ }, p = { class: "def__heading" }, m = { class: "def__count" }, h = { class: "def__list" }, g = ["title", "onClick"], _ = { class: "def__ext" }, v = { class: "def__name" }, y = { class: "def__size" }, b = [
9
+ "disabled",
10
+ "title",
11
+ "onClick"
12
+ ], x = ["title"], S = /*@__PURE__*/ s({
13
+ __name: "DocxEmbeddedFiles",
14
+ props: {
15
+ files: {},
16
+ downloadBusyId: {},
17
+ error: {}
18
+ },
19
+ emits: ["open", "download"],
20
+ setup(s, { emit: S }) {
21
+ let C = S;
22
+ function w(e) {
23
+ let t = e.lastIndexOf(".");
24
+ return t > 0 ? e.slice(t + 1).toUpperCase() : "FILE";
25
+ }
26
+ return (S, T) => (c(), i("section", f, [
27
+ a("div", p, [
28
+ o(t, {
29
+ name: "paperclip",
30
+ size: 14
31
+ }),
32
+ T[0] ||= a("span", null, "内嵌文件", -1),
33
+ a("span", m, u(s.files.length), 1)
34
+ ]),
35
+ a("div", h, [(c(!0), i(n, null, l(s.files, (n) => (c(), i("div", {
36
+ key: n.id,
37
+ class: "def__item"
38
+ }, [a("button", {
39
+ type: "button",
40
+ class: "def__open",
41
+ title: `预览 ${n.name}`,
42
+ onClick: (e) => C("open", n)
43
+ }, [
44
+ a("span", _, u(w(n.name)), 1),
45
+ a("span", v, u(n.label || n.name), 1),
46
+ a("span", y, u(d(e)(n.size)), 1)
47
+ ], 8, g), a("button", {
48
+ type: "button",
49
+ class: "def__download",
50
+ disabled: s.downloadBusyId === n.id,
51
+ title: s.downloadBusyId === n.id ? "正在准备下载" : `下载 ${n.name}`,
52
+ onClick: (e) => C("download", n)
53
+ }, [o(t, {
54
+ name: s.downloadBusyId === n.id ? "clock" : "download",
55
+ size: 14
56
+ }, null, 8, ["name"])], 8, b)]))), 128))]),
57
+ s.error ? (c(), i("span", {
58
+ key: 0,
59
+ class: "def__error",
60
+ role: "status",
61
+ title: s.error
62
+ }, u(s.error), 9, x)) : r("", !0)
63
+ ]));
64
+ }
65
+ });
66
+ //#endregion
67
+ export { S as default };
68
+
69
+ //# sourceMappingURL=DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/DocxEmbeddedFiles.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport Icon from '@/components/icons/Icon.vue'\nimport { formatBytes } from '@/lib/format'\nimport type { DocxEmbeddedFile } from '@/lib/docxEmbeddings'\n\ndefineProps<{\n files: readonly DocxEmbeddedFile[]\n downloadBusyId?: string\n error?: string\n}>()\n\nconst emit = defineEmits<{\n open: [file: DocxEmbeddedFile]\n download: [file: DocxEmbeddedFile]\n}>()\n\nfunction extension(name: string): string {\n const i = name.lastIndexOf('.')\n return i > 0 ? name.slice(i + 1).toUpperCase() : 'FILE'\n}\n</script>\n\n<template>\n <section class=\"def\" aria-label=\"Word 文档内嵌文件\">\n <div class=\"def__heading\">\n <Icon name=\"paperclip\" :size=\"14\" />\n <span>内嵌文件</span>\n <span class=\"def__count\">{{ files.length }}</span>\n </div>\n <div class=\"def__list\">\n <div v-for=\"file in files\" :key=\"file.id\" class=\"def__item\">\n <button\n type=\"button\"\n class=\"def__open\"\n :title=\"`预览 ${file.name}`\"\n @click=\"emit('open', file)\"\n >\n <span class=\"def__ext\">{{ extension(file.name) }}</span>\n <span class=\"def__name\">{{ file.label || file.name }}</span>\n <span class=\"def__size\">{{ formatBytes(file.size) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"def__download\"\n :disabled=\"downloadBusyId === file.id\"\n :title=\"downloadBusyId === file.id ? '正在准备下载' : `下载 ${file.name}`\"\n @click=\"emit('download', file)\"\n >\n <Icon :name=\"downloadBusyId === file.id ? 'clock' : 'download'\" :size=\"14\" />\n </button>\n </div>\n </div>\n <span v-if=\"error\" class=\"def__error\" role=\"status\" :title=\"error\">{{ error }}</span>\n </section>\n</template>\n\n<style scoped>\n.def {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-shrink: 0;\n min-width: 0;\n padding: 7px 10px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.def__heading {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n color: var(--muted);\n font-size: 11.5px;\n font-weight: 600;\n white-space: nowrap;\n}\n.def__count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: 999px;\n color: var(--accent);\n background: var(--accent-soft);\n font-size: 10.5px;\n font-variant-numeric: tabular-nums;\n}\n.def__list {\n display: flex;\n align-items: center;\n gap: 6px;\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n overscroll-behavior-inline: contain;\n scrollbar-width: thin;\n}\n.def__item {\n display: flex;\n align-items: stretch;\n flex: 0 0 auto;\n min-width: 180px;\n max-width: 260px;\n height: 32px;\n border: 1px solid var(--border-2);\n border-radius: 8px;\n background: var(--surface-2);\n overflow: hidden;\n}\n.def__item:hover {\n border-color: var(--accent);\n}\n.def__open,\n.def__download {\n font-family: inherit;\n color: inherit;\n background: transparent;\n border: none;\n cursor: pointer;\n}\n.def__open {\n display: flex;\n align-items: center;\n gap: 7px;\n flex: 1 1 auto;\n min-width: 0;\n padding: 0 8px;\n text-align: left;\n}\n.def__ext {\n flex-shrink: 0;\n color: var(--accent);\n font-size: 9.5px;\n font-weight: 700;\n letter-spacing: 0.04em;\n}\n.def__name {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n color: var(--text);\n font-size: 11.5px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.def__size {\n flex-shrink: 0;\n color: var(--faint);\n font-size: 10px;\n font-variant-numeric: tabular-nums;\n}\n.def__download {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 30px;\n border-left: 1px solid var(--border);\n color: var(--muted);\n}\n.def__download:hover:not(:disabled) {\n color: var(--accent);\n background: var(--surface);\n}\n.def__download:disabled {\n cursor: wait;\n opacity: 0.6;\n}\n.def__error {\n flex: 0 1 180px;\n min-width: 0;\n overflow: hidden;\n color: var(--danger);\n font-size: 10.5px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n@container (max-width: 500px) {\n .def {\n align-items: stretch;\n flex-direction: column;\n gap: 6px;\n }\n .def__item {\n min-width: 170px;\n }\n .def__error {\n flex-basis: auto;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAWA,IAAM,IAAO;EAKb,SAAS,EAAU,GAAsB;GACvC,IAAM,IAAI,EAAK,YAAY,GAAG;GAC9B,OAAO,IAAI,IAAI,EAAK,MAAM,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI;EACnD;yBAIE,EA8BU,WA9BV,GA8BU;GA7BR,EAIM,OAJN,GAIM;IAHJ,EAAoC,GAAA;KAA9B,MAAK;KAAa,MAAM;;aAC9B,EAAiB,QAAA,MAAX,QAAI,EAAA;IACV,EAAkD,QAAlD,GAAkD,EAAtB,EAAA,MAAM,MAAM,GAAA,CAAA;;GAE1C,EAsBM,OAtBN,GAsBM,EAAA,EAAA,EAAA,GArBJ,EAoBM,GAAA,MAAA,EApBc,EAAA,QAAR,YAAZ,EAoBM,OAAA;IApBsB,KAAK,EAAK;IAAI,OAAM;OAC9C,EASS,UAAA;IARP,MAAK;IACL,OAAM;IACL,OAAK,MAAQ,EAAK;IAClB,UAAK,MAAE,EAAI,QAAS,CAAI;;IAEzB,EAAwD,QAAxD,GAAwD,EAA9B,EAAU,EAAK,IAAI,CAAA,GAAA,CAAA;IAC7C,EAA4D,QAA5D,GAA4D,EAAjC,EAAK,SAAS,EAAK,IAAI,GAAA,CAAA;IAClD,EAA2D,QAA3D,GAA2D,EAAhC,EAAA,CAAA,CAAW,CAAC,EAAK,IAAI,CAAA,GAAA,CAAA;aAElD,EAQS,UAAA;IAPP,MAAK;IACL,OAAM;IACL,UAAU,EAAA,mBAAmB,EAAK;IAClC,OAAO,EAAA,mBAAmB,EAAK,KAAE,WAAA,MAAoB,EAAK;IAC1D,UAAK,MAAE,EAAI,YAAa,CAAI;OAE7B,EAA6E,GAAA;IAAtE,MAAM,EAAA,mBAAmB,EAAK,KAAE,UAAA;IAA0B,MAAM;;GAIjE,EAAA,SAAA,EAAA,GAAZ,EAAqF,QAAA;;IAAlE,OAAM;IAAa,MAAK;IAAU,OAAO,EAAA;QAAU,EAAA,KAAK,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA"}
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./DocxViewer.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/DocxViewer.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-5e8d92a6"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-f9695f8d"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
8
 
@@ -1 +1 @@
1
- {"version":3,"file":"DocxViewer.js","names":[],"sources":["../../src/components/DocxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'\nimport { renderAsync } from 'docx-preview'\nimport DocxNavigationPane from '@/components/DocxNavigationPane.vue'\nimport { useElementNavigation } from '@/composables/useElementNavigation'\nimport {\n buildDocxNavigation,\n buildDocxPageNavigation,\n type ParsedDocxModel,\n} from '@/lib/docxNavigation'\n\n/**\n * Side-viewer Word (`.docx`) renderer. Fetches the document bytes from the download URL and renders the\n * OOXML to HTML entirely in-browser via docx-preview (https://github.com/VolodymyrBaydalka/docxjs) — a\n * paginated, paper-like preview (white pages on a neutral canvas, like Word). No CDN / network beyond the\n * file fetch: docx-preview bundles its own jszip + renderer, and images are inlined as base64.\n *\n * Two entry shapes mirror the other browsers (DICOM/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes. docx-preview injects its own CSS\n * into the render container, prefixed with the `className` ('docx') so it can't leak onto the page.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .docx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// The element docx-preview renders into. Always mounted (v-show, not v-if) so the ref exists when we render.\nconst docEl = shallowRef<HTMLElement | null>(null)\nconst navView = shallowRef<'outline' | 'pages'>('outline')\nconst navCollapsed = shallowRef(false)\nconst { outlineItems, pageItems, activeOutlineId, activePageId, setItems, navigate } =\n useElementNavigation(docEl)\n\nasync function render(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n setItems([], [])\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const document = (await renderAsync(buf, el, undefined, {\n className: 'docx', // CSS prefix → docx-preview's injected styles stay scoped to this subtree\n inWrapper: true, // wrap pages in the gray \"paper on desk\" canvas, like Word\n // The side panel is narrow (and resizable), so don't pin pages to the fixed A4 width — that overflows\n // and clips the left edge. Let pages fill the available width and reflow; height follows content.\n ignoreWidth: true,\n ignoreHeight: true,\n breakPages: true, // honor page breaks → still separate page sheets, stacked vertically\n renderHeaders: true,\n renderFooters: true,\n renderFootnotes: true,\n renderEndnotes: true,\n useBase64URL: true, // inline embedded images as data URLs (no blob-URL lifecycle to manage)\n })) as ParsedDocxModel\n if (props.url !== url || docEl.value !== el) return\n let navigation\n try {\n navigation = buildDocxNavigation(document, el)\n } catch (error) {\n // A malformed/unsupported Word model must never turn an otherwise rendered document into an error page.\n console.warn('DOCX outline navigation unavailable:', error)\n navigation = { outline: [], pages: buildDocxPageNavigation(el) }\n }\n setItems(navigation.outline, navigation.pages)\n navView.value = navigation.outline.length ? 'outline' : 'pages'\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nonMounted(render)\nwatch(() => props.url, render)\nonBeforeUnmount(() => {\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"dx\">\n <DocxNavigationPane\n v-if=\"status === 'ready'\"\n v-model:collapsed=\"navCollapsed\"\n v-model:view=\"navView\"\n :outline-items=\"outlineItems\"\n :page-items=\"pageItems\"\n :active-outline-id=\"activeOutlineId\"\n :active-page-id=\"activePageId\"\n @navigate=\"navigate\"\n />\n <div class=\"dx__stage\">\n <p v-if=\"status === 'loading'\" class=\"dx__msg\">正在渲染 Word 文档…</p>\n <div v-else-if=\"status === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"render\">重试</button>\n </div>\n <!-- Always mounted (v-show) so docEl is available to docx-preview; hidden until render completes. -->\n <div v-show=\"status === 'ready'\" ref=\"docEl\" class=\"dx__doc\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.dx {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.dx__stage {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.dx__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dx__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dx__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.dx__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Scroll region holding the rendered document. The pages keep their native Word appearance (white paper,\n * black text) for fidelity; we only retint the surrounding \"desk\" to the app's neutral surface so it sits\n * in the panel naturally in both light and dark themes. :deep reaches docx-preview's injected DOM. */\n.dx__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n}\n.dx__doc :deep(.docx-wrapper) {\n background: var(--surface-2);\n padding: 22px 16px 40px;\n gap: 16px;\n}\n/* Each page: a white sheet with a soft shadow. With ignoreWidth the page reflows to the panel width, so\n * pin it to fill the wrapper (box-sizing so the doc's own margins stay inside the sheet, not overflowing). */\n.dx__doc :deep(.docx-wrapper > section.docx) {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n margin-bottom: 0;\n}\n/* Some Word docs anchor tables as \"floating\" objects (OOXML `w:tblpPr` — a table pinned to a fixed spot on\n * the page with body text wrapping around it). docx-preview approximates that with `float: left` + a fixed\n * point width. On a full A4 page the table lands where Word put it, but in this narrow, width-reflowing\n * panel the floats stack unpredictably and the surrounding paragraphs wrap into the gaps beside them — so on\n * resize the layout scrambles and dark cell shading appears to streak across (the reported \"背景乱串 / 排版\n * 混乱\"). Since we've already chosen reflow over pixel-exact A4 fidelity (ignoreWidth + section width:100%),\n * force every table back to a normal full-width block that stacks top-to-bottom at any panel width, and let\n * long cell content wrap instead of overflowing. */\n.dx__doc :deep(section.docx table) {\n float: none !important;\n width: 100% !important;\n max-width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n.dx__doc :deep(section.docx td),\n.dx__doc :deep(section.docx th) {\n overflow-wrap: break-word;\n}\n/* The page's own side margins (this doc uses ~90pt) would swallow most of a narrow panel now that tables\n * fill the width, so cap the horizontal padding while keeping the top/bottom page margin intact. */\n.dx__doc :deep(section.docx) {\n padding-left: clamp(14px, 5%, 90pt) !important;\n padding-right: clamp(14px, 5%, 90pt) !important;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"DocxViewer.js","names":[],"sources":["../../src/components/DocxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { defineAsyncComponent, nextTick, onBeforeUnmount, shallowRef, watch } from 'vue'\nimport { renderAsync } from 'docx-preview'\nimport DocxEmbeddedFiles from '@/components/DocxEmbeddedFiles.vue'\nimport DocxNavigationPane from '@/components/DocxNavigationPane.vue'\nimport Icon from '@/components/icons/Icon.vue'\nimport type { ViewerFile } from '@/composables/useFileViewer'\nimport { useElementNavigation } from '@/composables/useElementNavigation'\nimport {\n extractDocxEmbeddedFile,\n readDocxEmbeddedFiles,\n type DocxEmbeddedFile,\n} from '@/lib/docxEmbeddings'\nimport { mountDocxEmbeddedPreviews, type DocxEmbeddedDomController } from '@/lib/docxEmbeddedDom'\nimport {\n buildDocxNavigation,\n buildDocxPageNavigation,\n type ParsedDocxModel,\n} from '@/lib/docxNavigation'\nimport { formatBytes } from '@/lib/format'\nimport { classifyViewerFile } from '@/lib/viewerKind'\n\n// The shared body lets an extracted package reuse the same viewer as a top-level file. Lazy loading breaks the\n// FileViewerBody → DocxViewer → FileViewerBody module cycle, matching ZipBrowser's nested-entry hand-off.\nconst FileViewerBody = defineAsyncComponent(() => import('@/components/FileViewerBody.vue'))\n\n/**\n * Side-viewer Word (`.docx`) renderer. Fetches the document bytes from the download URL and renders the\n * OOXML to HTML entirely in-browser via docx-preview (https://github.com/VolodymyrBaydalka/docxjs) — a\n * paginated, paper-like preview (white pages on a neutral canvas, like Word). No CDN / network beyond the\n * file fetch: docx-preview bundles its own jszip + renderer, and images are inlined as base64.\n *\n * Two entry shapes mirror the other browsers (DICOM/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes. docx-preview injects its own CSS\n * into the render container, prefixed with the `className` ('docx') so it can't leak onto the page.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .docx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// The element docx-preview renders into. Always mounted (v-show, not v-if) so the ref exists when we render.\nconst docEl = shallowRef<HTMLElement | null>(null)\nconst navView = shallowRef<'outline' | 'pages'>('outline')\nconst navCollapsed = shallowRef(false)\nconst { outlineItems, pageItems, activeOutlineId, activePageId, setItems, navigate } =\n useElementNavigation(docEl)\n\nlet documentBuffer: ArrayBuffer | null = null\nlet embeddedBlobUrl = ''\nlet embeddedSelectionRun = 0\nlet renderRun = 0\nlet documentScrollTop = 0\nlet inlineEmbedded: DocxEmbeddedDomController | null = null\nconst embeddedFiles = shallowRef<DocxEmbeddedFile[]>([])\nconst selectedEmbedded = shallowRef<DocxEmbeddedFile | null>(null)\nconst embeddedViewerFile = shallowRef<ViewerFile | null>(null)\nconst embeddedStatus = shallowRef<Status>('ready')\nconst embeddedError = shallowRef('')\nconst downloadBusyId = shallowRef('')\nconst downloadError = shallowRef('')\n\nfunction revokeEmbeddedBlob(): void {\n if (!embeddedBlobUrl) return\n URL.revokeObjectURL(embeddedBlobUrl)\n embeddedBlobUrl = ''\n}\n\nfunction clearInlineEmbedded(): void {\n inlineEmbedded?.dispose()\n inlineEmbedded = null\n}\n\nfunction closeEmbedded(restoreDocumentScroll = true): void {\n const shouldRestoreScroll = restoreDocumentScroll && selectedEmbedded.value !== null\n embeddedSelectionRun++\n selectedEmbedded.value = null\n embeddedViewerFile.value = null\n embeddedStatus.value = 'ready'\n embeddedError.value = ''\n revokeEmbeddedBlob()\n if (shouldRestoreScroll) {\n void nextTick(() => {\n if (!selectedEmbedded.value && docEl.value) docEl.value.scrollTop = documentScrollTop\n })\n }\n}\n\nfunction resetEmbedded(): void {\n closeEmbedded(false)\n clearInlineEmbedded()\n documentBuffer = null\n documentScrollTop = 0\n embeddedFiles.value = []\n downloadBusyId.value = ''\n downloadError.value = ''\n}\n\nasync function render(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n const run = ++renderRun\n status.value = 'loading'\n errorMsg.value = ''\n resetEmbedded()\n setItems([], [])\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (renderRun !== run || props.url !== url || docEl.value !== el) return\n // Embedded packages are a best-effort enhancement: malformed relationship XML must not blank a Word file\n // that docx-preview can otherwise render. Parse concurrently because both consumers only read the buffer.\n const embeddedPromise = readDocxEmbeddedFiles(buf).catch((error) => {\n console.warn('DOCX embedded files unavailable:', error)\n return []\n })\n const document = (await renderAsync(buf, el, undefined, {\n className: 'docx', // CSS prefix → docx-preview's injected styles stay scoped to this subtree\n inWrapper: true, // wrap pages in the gray \"paper on desk\" canvas, like Word\n // The side panel is narrow (and resizable), so don't pin pages to the fixed A4 width — that overflows\n // and clips the left edge. Let pages fill the available width and reflow; height follows content.\n ignoreWidth: true,\n ignoreHeight: true,\n breakPages: true, // honor page breaks → still separate page sheets, stacked vertically\n renderHeaders: true,\n renderFooters: true,\n renderFootnotes: true,\n renderEndnotes: true,\n useBase64URL: true, // inline embedded images as data URLs (no blob-URL lifecycle to manage)\n })) as ParsedDocxModel\n const discoveredEmbeddedFiles = await embeddedPromise\n if (renderRun !== run || props.url !== url || docEl.value !== el) return\n let navigation\n try {\n navigation = buildDocxNavigation(document, el)\n } catch (error) {\n // A malformed/unsupported Word model must never turn an otherwise rendered document into an error page.\n console.warn('DOCX outline navigation unavailable:', error)\n navigation = { outline: [], pages: buildDocxPageNavigation(el) }\n }\n setItems(navigation.outline, navigation.pages)\n navView.value = navigation.outline.length ? 'outline' : 'pages'\n documentBuffer = buf\n embeddedFiles.value = discoveredEmbeddedFiles\n inlineEmbedded = mountDocxEmbeddedPreviews({\n root: el,\n buffer: buf,\n files: discoveredEmbeddedFiles,\n onOpen: (file) => void openEmbedded(file),\n })\n status.value = 'ready'\n } catch (e) {\n if (renderRun !== run) return\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nasync function openEmbedded(file: DocxEmbeddedFile): Promise<void> {\n if (!documentBuffer) return\n const buffer = documentBuffer\n let pendingUrl = ''\n documentScrollTop = docEl.value?.scrollTop ?? 0\n closeEmbedded(false)\n // closeEmbedded invalidates prior work; this run now becomes the active selection.\n const activeRun = ++embeddedSelectionRun\n selectedEmbedded.value = file\n embeddedStatus.value = 'loading'\n embeddedError.value = ''\n try {\n const bytes = await extractDocxEmbeddedFile(buffer, file)\n if (embeddedSelectionRun !== activeRun || documentBuffer !== buffer) return\n const url = URL.createObjectURL(new Blob([bytes], { type: file.contentType }))\n pendingUrl = url\n const viewerFile = classifyViewerFile({\n name: file.name,\n url,\n size: file.size,\n mime: file.contentType,\n })\n if (embeddedSelectionRun !== activeRun || selectedEmbedded.value !== file) {\n URL.revokeObjectURL(url)\n pendingUrl = ''\n return\n }\n embeddedBlobUrl = url\n pendingUrl = ''\n embeddedViewerFile.value = viewerFile\n embeddedStatus.value = 'ready'\n } catch (error) {\n if (pendingUrl) URL.revokeObjectURL(pendingUrl)\n if (embeddedSelectionRun !== activeRun) return\n embeddedError.value = error instanceof Error ? error.message : String(error)\n embeddedStatus.value = 'error'\n }\n}\n\nasync function downloadEmbedded(file: DocxEmbeddedFile): Promise<void> {\n if (!documentBuffer || downloadBusyId.value) return\n const buffer = documentBuffer\n downloadBusyId.value = file.id\n downloadError.value = ''\n let url = ''\n try {\n const bytes = await extractDocxEmbeddedFile(buffer, file)\n if (documentBuffer !== buffer) return\n url = URL.createObjectURL(new Blob([bytes], { type: file.contentType }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = file.name\n document.body.appendChild(anchor)\n anchor.click()\n anchor.remove()\n } catch (error) {\n downloadError.value = error instanceof Error ? error.message : String(error)\n } finally {\n if (url) setTimeout(() => URL.revokeObjectURL(url), 1000)\n if (downloadBusyId.value === file.id) downloadBusyId.value = ''\n }\n}\n\nwatch(\n [() => props.url, docEl],\n ([url, element]) => {\n if (url && element) void render()\n },\n { immediate: true },\n)\nonBeforeUnmount(() => {\n renderRun++\n resetEmbedded()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"dx\">\n <DocxNavigationPane\n v-if=\"status === 'ready' && !selectedEmbedded\"\n v-model:collapsed=\"navCollapsed\"\n v-model:view=\"navView\"\n :outline-items=\"outlineItems\"\n :page-items=\"pageItems\"\n :active-outline-id=\"activeOutlineId\"\n :active-page-id=\"activePageId\"\n @navigate=\"navigate\"\n />\n <div class=\"dx__stage\">\n <div v-if=\"selectedEmbedded\" class=\"dx__embedded-bar\">\n <button type=\"button\" class=\"dx__back\" title=\"返回 Word 文档\" @click=\"closeEmbedded()\">\n <Icon name=\"arrowLeft\" :size=\"16\" />\n </button>\n <span class=\"dx__embedded-title\" :title=\"selectedEmbedded.name\">\n {{ selectedEmbedded.label || selectedEmbedded.name }}\n </span>\n <span class=\"dx__embedded-size\">{{ formatBytes(selectedEmbedded.size) }}</span>\n <button\n type=\"button\"\n class=\"dx__download\"\n title=\"下载内嵌文件\"\n @click=\"downloadEmbedded(selectedEmbedded)\"\n >\n <Icon name=\"download\" :size=\"15\" />\n </button>\n </div>\n\n <p v-if=\"status === 'loading'\" class=\"dx__msg\">正在渲染 Word 文档…</p>\n <div v-else-if=\"status === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"render\">重试</button>\n </div>\n <template v-else-if=\"selectedEmbedded\">\n <p v-if=\"embeddedStatus === 'loading'\" class=\"dx__msg\">正在读取内嵌文件…</p>\n <div v-else-if=\"embeddedStatus === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>读取失败:{{ embeddedError }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"openEmbedded(selectedEmbedded)\">\n 重试\n </button>\n </div>\n <FileViewerBody v-else-if=\"embeddedViewerFile\" :file=\"embeddedViewerFile\" mode=\"render\" />\n </template>\n\n <DocxEmbeddedFiles\n v-else-if=\"status === 'ready' && embeddedFiles.length\"\n :files=\"embeddedFiles\"\n :download-busy-id=\"downloadBusyId\"\n :error=\"downloadError\"\n @open=\"openEmbedded\"\n @download=\"downloadEmbedded\"\n />\n <!-- Always mounted (v-show) so docEl is available to docx-preview; hidden until render completes. -->\n <div v-show=\"status === 'ready' && !selectedEmbedded\" ref=\"docEl\" class=\"dx__doc\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.dx {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.dx__stage {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.dx__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dx__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dx__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.dx__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.dx__embedded-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n padding: 7px 10px 7px 8px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.dx__back,\n.dx__download {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 30px;\n height: 28px;\n padding: 0;\n color: var(--muted);\n background: transparent;\n border: none;\n border-radius: 7px;\n cursor: pointer;\n}\n.dx__back:hover,\n.dx__download:hover {\n color: var(--accent);\n background: var(--surface-2);\n}\n.dx__embedded-title {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dx__embedded-size {\n flex-shrink: 0;\n color: var(--faint);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n\n/* docx-preview intentionally skips OLE `w:object` nodes but leaves their empty <p>. The DOM adapter inserts\n * Word/WPS's inert raster snapshot, or a rendered PPTX first slide when the snapshot is browser-incompatible,\n * into that exact paragraph. The whole preview opens the real embedded file. */\n.dx__doc :deep(.docx-embedded-host) {\n min-height: 0;\n margin: 8px 0 12px;\n text-align: center;\n}\n.dx__doc :deep(.docx-embedded-preview) {\n position: relative;\n display: inline-flex;\n box-sizing: border-box;\n align-items: center;\n justify-content: center;\n gap: 10px;\n width: 100%;\n min-height: 72px;\n padding: 12px;\n overflow: hidden;\n color: #334155;\n background: #f8fafc;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12);\n cursor: pointer;\n font-family: Arial, sans-serif;\n text-align: left;\n}\n.dx__doc :deep(.docx-embedded-preview:hover),\n.dx__doc :deep(.docx-embedded-preview:focus-visible) {\n border-color: #3370ff;\n box-shadow:\n 0 0 0 2px rgba(51, 112, 255, 0.16),\n 0 2px 8px rgba(15, 23, 42, 0.16);\n outline: none;\n}\n.dx__doc :deep(.docx-embedded-preview--image) {\n display: inline-block;\n min-height: 0;\n padding: 0;\n background: #fff;\n}\n.dx__doc :deep(.docx-embedded-preview--pptx) {\n display: block;\n min-height: 0;\n padding: 0;\n background: #fff;\n}\n.dx__doc :deep(.docx-embedded-preview--loading) {\n cursor: progress;\n}\n.dx__doc :deep(.docx-embedded-preview__pptx-slide) {\n width: 100%;\n height: 100%;\n overflow: hidden;\n pointer-events: none;\n}\n.dx__doc :deep(.docx-embedded-preview__pptx-status) {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #64748b;\n background: #f8fafc;\n font-size: 12px;\n font-weight: 600;\n}\n.dx__doc :deep(.docx-embedded-preview__image) {\n display: block;\n width: 100%;\n height: auto;\n object-fit: contain;\n}\n.dx__doc :deep(.docx-embedded-preview__hint) {\n position: absolute;\n right: 8px;\n bottom: 8px;\n padding: 5px 9px;\n color: #fff;\n background: rgba(15, 23, 42, 0.76);\n border-radius: 999px;\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n opacity: 0;\n pointer-events: none;\n transform: translateY(3px);\n transition:\n opacity 0.15s,\n transform 0.15s;\n}\n.dx__doc :deep(.docx-embedded-preview:hover .docx-embedded-preview__hint),\n.dx__doc :deep(.docx-embedded-preview:focus-visible .docx-embedded-preview__hint) {\n opacity: 1;\n transform: translateY(0);\n}\n.dx__doc :deep(.docx-embedded-preview__badge) {\n flex-shrink: 0;\n padding: 5px 7px;\n color: #166534;\n background: #dcfce7;\n border-radius: 5px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.04em;\n}\n.dx__doc :deep(.docx-embedded-preview__fallback-label) {\n min-width: 0;\n overflow: hidden;\n font-size: 12px;\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Scroll region holding the rendered document. The pages keep their native Word appearance (white paper,\n * black text) for fidelity; we only retint the surrounding \"desk\" to the app's neutral surface so it sits\n * in the panel naturally in both light and dark themes. :deep reaches docx-preview's injected DOM. */\n.dx__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n}\n.dx__doc :deep(.docx-wrapper) {\n background: var(--surface-2);\n padding: 22px 16px 40px;\n gap: 16px;\n}\n/* Each page: a white sheet with a soft shadow. With ignoreWidth the page reflows to the panel width, so\n * pin it to fill the wrapper (box-sizing so the doc's own margins stay inside the sheet, not overflowing). */\n.dx__doc :deep(.docx-wrapper > section.docx) {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n margin-bottom: 0;\n}\n/* Some Word docs anchor tables as \"floating\" objects (OOXML `w:tblpPr` — a table pinned to a fixed spot on\n * the page with body text wrapping around it). docx-preview approximates that with `float: left` + a fixed\n * point width. On a full A4 page the table lands where Word put it, but in this narrow, width-reflowing\n * panel the floats stack unpredictably and the surrounding paragraphs wrap into the gaps beside them — so on\n * resize the layout scrambles and dark cell shading appears to streak across (the reported \"背景乱串 / 排版\n * 混乱\"). Since we've already chosen reflow over pixel-exact A4 fidelity (ignoreWidth + section width:100%),\n * force every table back to a normal full-width block that stacks top-to-bottom at any panel width, and let\n * long cell content wrap instead of overflowing. */\n.dx__doc :deep(section.docx table) {\n float: none !important;\n width: 100% !important;\n max-width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n.dx__doc :deep(section.docx td),\n.dx__doc :deep(section.docx th) {\n overflow-wrap: break-word;\n}\n/* The page's own side margins (this doc uses ~90pt) would swallow most of a narrow panel now that tables\n * fill the width, so cap the horizontal padding while keeping the top/bottom page margin intact. */\n.dx__doc :deep(section.docx) {\n padding-left: clamp(14px, 5%, 90pt) !important;\n padding-right: clamp(14px, 5%, 90pt) !important;\n}\n</style>\n"],"mappings":""}
@@ -1,35 +1,66 @@
1
- import e from "./DocxNavigationPane.js";
2
- import { useElementNavigation as t } from "../composables/useElementNavigation.js";
3
- import { buildDocxNavigation as n, buildDocxPageNavigation as r } from "../lib/docxNavigation.js";
4
- import { createBlock as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, defineComponent as c, onBeforeUnmount as l, onMounted as u, openBlock as d, shallowRef as f, toDisplayString as p, unref as m, vShow as h, watch as g, withDirectives as _ } from "vue";
5
- import { renderAsync as v } from "docx-preview";
1
+ import { classifyViewerFile as e } from "../lib/viewerKind.js";
2
+ import { formatBytes as t } from "../lib/format.js";
3
+ import n from "./icons/Icon.js";
4
+ import r from "./DocxEmbeddedFiles.js";
5
+ import i from "./DocxNavigationPane.js";
6
+ import { useElementNavigation as a } from "../composables/useElementNavigation.js";
7
+ import { extractDocxEmbeddedFile as o, readDocxEmbeddedFiles as s } from "../lib/docxEmbeddings.js";
8
+ import { mountDocxEmbeddedPreviews as c } from "../lib/docxEmbeddedDom.js";
9
+ import { buildDocxNavigation as ee, buildDocxPageNavigation as te } from "../lib/docxNavigation.js";
10
+ import { Fragment as l, createBlock as u, createCommentVNode as d, createElementBlock as f, createElementVNode as p, createVNode as m, defineAsyncComponent as h, defineComponent as g, nextTick as ne, onBeforeUnmount as re, openBlock as _, shallowRef as v, toDisplayString as y, unref as b, vShow as ie, watch as ae, withDirectives as oe } from "vue";
11
+ import { renderAsync as se } from "docx-preview";
6
12
  //#region src/components/DocxViewer.vue?vue&type=script&setup=true&lang.ts
7
- var y = { class: "dx" }, b = { class: "dx__stage" }, x = {
13
+ var ce = { class: "dx" }, le = { class: "dx__stage" }, x = {
8
14
  key: 0,
15
+ class: "dx__embedded-bar"
16
+ }, S = ["title"], C = { class: "dx__embedded-size" }, ue = {
17
+ key: 1,
9
18
  class: "dx__msg"
10
- }, S = {
19
+ }, de = {
20
+ key: 2,
21
+ class: "dx__msg dx__msg--error"
22
+ }, fe = {
23
+ key: 0,
24
+ class: "dx__msg"
25
+ }, pe = {
11
26
  key: 1,
12
27
  class: "dx__msg dx__msg--error"
13
- }, C = /*@__PURE__*/ c({
28
+ }, w = /*@__PURE__*/ g({
14
29
  __name: "DocxViewer",
15
30
  props: {
16
31
  url: {},
17
32
  name: {},
18
33
  size: {}
19
34
  },
20
- setup(c) {
21
- let C = c, w = f("loading"), T = f(""), E = f(null), D = f("outline"), O = f(!1), { outlineItems: k, pageItems: A, activeOutlineId: j, activePageId: M, setItems: N, navigate: P } = t(E);
22
- async function F() {
23
- let e = E.value;
24
- if (!C.url || !e) return;
25
- w.value = "loading", T.value = "", N([], []), e.innerHTML = "";
26
- let t = C.url;
35
+ setup(g) {
36
+ let w = h(() => import("./FileViewerBody.js")), T = g, E = v("loading"), D = v(""), O = v(null), k = v("outline"), A = v(!1), { outlineItems: me, pageItems: he, activeOutlineId: j, activePageId: M, setItems: N, navigate: P } = a(O), F = null, I = "", L = 0, R = 0, z = 0, B = null, V = v([]), H = v(null), U = v(null), W = v("ready"), G = v(""), K = v(""), q = v("");
37
+ function ge() {
38
+ I &&= (URL.revokeObjectURL(I), "");
39
+ }
40
+ function J() {
41
+ B?.dispose(), B = null;
42
+ }
43
+ function Y(e = !0) {
44
+ let t = e && H.value !== null;
45
+ L++, H.value = null, U.value = null, W.value = "ready", G.value = "", ge(), t && ne(() => {
46
+ !H.value && O.value && (O.value.scrollTop = z);
47
+ });
48
+ }
49
+ function X() {
50
+ Y(!1), J(), F = null, z = 0, V.value = [], K.value = "", q.value = "";
51
+ }
52
+ async function Z() {
53
+ let e = O.value;
54
+ if (!T.url || !e) return;
55
+ let t = ++R;
56
+ E.value = "loading", D.value = "", X(), N([], []), e.innerHTML = "";
57
+ let n = T.url;
27
58
  try {
28
- let i = await fetch(t);
29
- if (!i.ok) throw Error(`HTTP ${i.status}`);
30
- let a = await i.arrayBuffer();
31
- if (C.url !== t || E.value !== e) return;
32
- let o = await v(a, e, void 0, {
59
+ let r = await fetch(n);
60
+ if (!r.ok) throw Error(`HTTP ${r.status}`);
61
+ let i = await r.arrayBuffer();
62
+ if (R !== t || T.url !== n || O.value !== e) return;
63
+ let a = s(i).catch((e) => (console.warn("DOCX embedded files unavailable:", e), [])), o = await se(i, e, void 0, {
33
64
  className: "docx",
34
65
  inWrapper: !0,
35
66
  ignoreWidth: !0,
@@ -40,35 +71,87 @@ var y = { class: "dx" }, b = { class: "dx__stage" }, x = {
40
71
  renderFootnotes: !0,
41
72
  renderEndnotes: !0,
42
73
  useBase64URL: !0
43
- });
44
- if (C.url !== t || E.value !== e) return;
45
- let s;
74
+ }), l = await a;
75
+ if (R !== t || T.url !== n || O.value !== e) return;
76
+ let u;
46
77
  try {
47
- s = n(o, e);
78
+ u = ee(o, e);
48
79
  } catch (t) {
49
- console.warn("DOCX outline navigation unavailable:", t), s = {
80
+ console.warn("DOCX outline navigation unavailable:", t), u = {
50
81
  outline: [],
51
- pages: r(e)
82
+ pages: te(e)
52
83
  };
53
84
  }
54
- N(s.outline, s.pages), D.value = s.outline.length ? "outline" : "pages", w.value = "ready";
85
+ N(u.outline, u.pages), k.value = u.outline.length ? "outline" : "pages", F = i, V.value = l, B = c({
86
+ root: e,
87
+ buffer: i,
88
+ files: l,
89
+ onOpen: (e) => void Q(e)
90
+ }), E.value = "ready";
91
+ } catch (e) {
92
+ if (R !== t) return;
93
+ D.value = e instanceof Error ? e.message : String(e), E.value = "error";
94
+ }
95
+ }
96
+ async function Q(t) {
97
+ if (!F) return;
98
+ let n = F, r = "";
99
+ z = O.value?.scrollTop ?? 0, Y(!1);
100
+ let i = ++L;
101
+ H.value = t, W.value = "loading", G.value = "";
102
+ try {
103
+ let a = await o(n, t);
104
+ if (L !== i || F !== n) return;
105
+ let s = URL.createObjectURL(new Blob([a], { type: t.contentType }));
106
+ r = s;
107
+ let c = e({
108
+ name: t.name,
109
+ url: s,
110
+ size: t.size,
111
+ mime: t.contentType
112
+ });
113
+ if (L !== i || H.value !== t) {
114
+ URL.revokeObjectURL(s), r = "";
115
+ return;
116
+ }
117
+ I = s, r = "", U.value = c, W.value = "ready";
118
+ } catch (e) {
119
+ if (r && URL.revokeObjectURL(r), L !== i) return;
120
+ G.value = e instanceof Error ? e.message : String(e), W.value = "error";
121
+ }
122
+ }
123
+ async function $(e) {
124
+ if (!F || K.value) return;
125
+ let t = F;
126
+ K.value = e.id, q.value = "";
127
+ let n = "";
128
+ try {
129
+ let r = await o(t, e);
130
+ if (F !== t) return;
131
+ n = URL.createObjectURL(new Blob([r], { type: e.contentType }));
132
+ let i = document.createElement("a");
133
+ i.href = n, i.download = e.name, document.body.appendChild(i), i.click(), i.remove();
55
134
  } catch (e) {
56
- T.value = e instanceof Error ? e.message : String(e), w.value = "error";
135
+ q.value = e instanceof Error ? e.message : String(e);
136
+ } finally {
137
+ n && setTimeout(() => URL.revokeObjectURL(n), 1e3), K.value === e.id && (K.value = "");
57
138
  }
58
139
  }
59
- return u(F), g(() => C.url, F), l(() => {
60
- E.value && (E.value.innerHTML = "");
61
- }), (t, n) => (d(), o("div", y, [w.value === "ready" ? (d(), i(e, {
140
+ return ae([() => T.url, O], ([e, t]) => {
141
+ e && t && Z();
142
+ }, { immediate: !0 }), re(() => {
143
+ R++, X(), O.value && (O.value.innerHTML = "");
144
+ }), (e, a) => (_(), f("div", ce, [E.value === "ready" && !H.value ? (_(), u(i, {
62
145
  key: 0,
63
- collapsed: O.value,
64
- "onUpdate:collapsed": n[0] ||= (e) => O.value = e,
65
- view: D.value,
66
- "onUpdate:view": n[1] ||= (e) => D.value = e,
67
- "outline-items": m(k),
68
- "page-items": m(A),
69
- "active-outline-id": m(j),
70
- "active-page-id": m(M),
71
- onNavigate: m(P)
146
+ collapsed: A.value,
147
+ "onUpdate:collapsed": a[0] ||= (e) => A.value = e,
148
+ view: k.value,
149
+ "onUpdate:view": a[1] ||= (e) => k.value = e,
150
+ "outline-items": b(me),
151
+ "page-items": b(he),
152
+ "active-outline-id": b(j),
153
+ "active-page-id": b(M),
154
+ onNavigate: b(P)
72
155
  }, null, 8, [
73
156
  "collapsed",
74
157
  "view",
@@ -77,18 +160,65 @@ var y = { class: "dx" }, b = { class: "dx__stage" }, x = {
77
160
  "active-outline-id",
78
161
  "active-page-id",
79
162
  "onNavigate"
80
- ])) : a("", !0), s("div", b, [w.value === "loading" ? (d(), o("p", x, "正在渲染 Word 文档…")) : w.value === "error" ? (d(), o("div", S, [s("p", null, "渲染失败:" + p(T.value), 1), s("button", {
81
- type: "button",
82
- class: "dx__retry",
83
- onClick: F
84
- }, "重试")])) : a("", !0), _(s("div", {
85
- ref_key: "docEl",
86
- ref: E,
87
- class: "dx__doc"
88
- }, null, 512), [[h, w.value === "ready"]])])]));
163
+ ])) : d("", !0), p("div", le, [
164
+ H.value ? (_(), f("div", x, [
165
+ p("button", {
166
+ type: "button",
167
+ class: "dx__back",
168
+ title: "返回 Word 文档",
169
+ onClick: a[2] ||= (e) => Y()
170
+ }, [m(n, {
171
+ name: "arrowLeft",
172
+ size: 16
173
+ })]),
174
+ p("span", {
175
+ class: "dx__embedded-title",
176
+ title: H.value.name
177
+ }, y(H.value.label || H.value.name), 9, S),
178
+ p("span", C, y(b(t)(H.value.size)), 1),
179
+ p("button", {
180
+ type: "button",
181
+ class: "dx__download",
182
+ title: "下载内嵌文件",
183
+ onClick: a[3] ||= (e) => $(H.value)
184
+ }, [m(n, {
185
+ name: "download",
186
+ size: 15
187
+ })])
188
+ ])) : d("", !0),
189
+ E.value === "loading" ? (_(), f("p", ue, "正在渲染 Word 文档…")) : E.value === "error" ? (_(), f("div", de, [p("p", null, "渲染失败:" + y(D.value), 1), p("button", {
190
+ type: "button",
191
+ class: "dx__retry",
192
+ onClick: Z
193
+ }, "重试")])) : H.value ? (_(), f(l, { key: 3 }, [W.value === "loading" ? (_(), f("p", fe, "正在读取内嵌文件…")) : W.value === "error" ? (_(), f("div", pe, [p("p", null, "读取失败:" + y(G.value), 1), p("button", {
194
+ type: "button",
195
+ class: "dx__retry",
196
+ onClick: a[4] ||= (e) => Q(H.value)
197
+ }, " 重试 ")])) : U.value ? (_(), u(b(w), {
198
+ key: 2,
199
+ file: U.value,
200
+ mode: "render"
201
+ }, null, 8, ["file"])) : d("", !0)], 64)) : E.value === "ready" && V.value.length ? (_(), u(r, {
202
+ key: 4,
203
+ files: V.value,
204
+ "download-busy-id": K.value,
205
+ error: q.value,
206
+ onOpen: Q,
207
+ onDownload: $
208
+ }, null, 8, [
209
+ "files",
210
+ "download-busy-id",
211
+ "error"
212
+ ])) : d("", !0),
213
+ oe(p("div", {
214
+ ref_key: "docEl",
215
+ ref: O,
216
+ class: "dx__doc"
217
+ }, null, 512), [[ie, E.value === "ready" && !H.value]])
218
+ ])]));
89
219
  }
90
220
  });
91
221
  //#endregion
92
- export { C as default };
222
+ export { w as default };
93
223
 
94
224
  //# sourceMappingURL=DocxViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DocxViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/DocxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'\nimport { renderAsync } from 'docx-preview'\nimport DocxNavigationPane from '@/components/DocxNavigationPane.vue'\nimport { useElementNavigation } from '@/composables/useElementNavigation'\nimport {\n buildDocxNavigation,\n buildDocxPageNavigation,\n type ParsedDocxModel,\n} from '@/lib/docxNavigation'\n\n/**\n * Side-viewer Word (`.docx`) renderer. Fetches the document bytes from the download URL and renders the\n * OOXML to HTML entirely in-browser via docx-preview (https://github.com/VolodymyrBaydalka/docxjs) — a\n * paginated, paper-like preview (white pages on a neutral canvas, like Word). No CDN / network beyond the\n * file fetch: docx-preview bundles its own jszip + renderer, and images are inlined as base64.\n *\n * Two entry shapes mirror the other browsers (DICOM/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes. docx-preview injects its own CSS\n * into the render container, prefixed with the `className` ('docx') so it can't leak onto the page.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .docx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// The element docx-preview renders into. Always mounted (v-show, not v-if) so the ref exists when we render.\nconst docEl = shallowRef<HTMLElement | null>(null)\nconst navView = shallowRef<'outline' | 'pages'>('outline')\nconst navCollapsed = shallowRef(false)\nconst { outlineItems, pageItems, activeOutlineId, activePageId, setItems, navigate } =\n useElementNavigation(docEl)\n\nasync function render(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n setItems([], [])\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const document = (await renderAsync(buf, el, undefined, {\n className: 'docx', // CSS prefix → docx-preview's injected styles stay scoped to this subtree\n inWrapper: true, // wrap pages in the gray \"paper on desk\" canvas, like Word\n // The side panel is narrow (and resizable), so don't pin pages to the fixed A4 width — that overflows\n // and clips the left edge. Let pages fill the available width and reflow; height follows content.\n ignoreWidth: true,\n ignoreHeight: true,\n breakPages: true, // honor page breaks → still separate page sheets, stacked vertically\n renderHeaders: true,\n renderFooters: true,\n renderFootnotes: true,\n renderEndnotes: true,\n useBase64URL: true, // inline embedded images as data URLs (no blob-URL lifecycle to manage)\n })) as ParsedDocxModel\n if (props.url !== url || docEl.value !== el) return\n let navigation\n try {\n navigation = buildDocxNavigation(document, el)\n } catch (error) {\n // A malformed/unsupported Word model must never turn an otherwise rendered document into an error page.\n console.warn('DOCX outline navigation unavailable:', error)\n navigation = { outline: [], pages: buildDocxPageNavigation(el) }\n }\n setItems(navigation.outline, navigation.pages)\n navView.value = navigation.outline.length ? 'outline' : 'pages'\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nonMounted(render)\nwatch(() => props.url, render)\nonBeforeUnmount(() => {\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"dx\">\n <DocxNavigationPane\n v-if=\"status === 'ready'\"\n v-model:collapsed=\"navCollapsed\"\n v-model:view=\"navView\"\n :outline-items=\"outlineItems\"\n :page-items=\"pageItems\"\n :active-outline-id=\"activeOutlineId\"\n :active-page-id=\"activePageId\"\n @navigate=\"navigate\"\n />\n <div class=\"dx__stage\">\n <p v-if=\"status === 'loading'\" class=\"dx__msg\">正在渲染 Word 文档…</p>\n <div v-else-if=\"status === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"render\">重试</button>\n </div>\n <!-- Always mounted (v-show) so docEl is available to docx-preview; hidden until render completes. -->\n <div v-show=\"status === 'ready'\" ref=\"docEl\" class=\"dx__doc\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.dx {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.dx__stage {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.dx__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dx__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dx__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.dx__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Scroll region holding the rendered document. The pages keep their native Word appearance (white paper,\n * black text) for fidelity; we only retint the surrounding \"desk\" to the app's neutral surface so it sits\n * in the panel naturally in both light and dark themes. :deep reaches docx-preview's injected DOM. */\n.dx__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n}\n.dx__doc :deep(.docx-wrapper) {\n background: var(--surface-2);\n padding: 22px 16px 40px;\n gap: 16px;\n}\n/* Each page: a white sheet with a soft shadow. With ignoreWidth the page reflows to the panel width, so\n * pin it to fill the wrapper (box-sizing so the doc's own margins stay inside the sheet, not overflowing). */\n.dx__doc :deep(.docx-wrapper > section.docx) {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n margin-bottom: 0;\n}\n/* Some Word docs anchor tables as \"floating\" objects (OOXML `w:tblpPr` — a table pinned to a fixed spot on\n * the page with body text wrapping around it). docx-preview approximates that with `float: left` + a fixed\n * point width. On a full A4 page the table lands where Word put it, but in this narrow, width-reflowing\n * panel the floats stack unpredictably and the surrounding paragraphs wrap into the gaps beside them — so on\n * resize the layout scrambles and dark cell shading appears to streak across (the reported \"背景乱串 / 排版\n * 混乱\"). Since we've already chosen reflow over pixel-exact A4 fidelity (ignoreWidth + section width:100%),\n * force every table back to a normal full-width block that stacks top-to-bottom at any panel width, and let\n * long cell content wrap instead of overflowing. */\n.dx__doc :deep(section.docx table) {\n float: none !important;\n width: 100% !important;\n max-width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n.dx__doc :deep(section.docx td),\n.dx__doc :deep(section.docx th) {\n overflow-wrap: break-word;\n}\n/* The page's own side margins (this doc uses ~90pt) would swallow most of a narrow panel now that tables\n * fill the width, so cap the horizontal padding while keeping the top/bottom page margin intact. */\n.dx__doc :deep(section.docx) {\n padding-left: clamp(14px, 5%, 90pt) !important;\n padding-right: clamp(14px, 5%, 90pt) !important;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAqBA,IAAM,IAAQ,GAQR,IAAS,EAAmB,SAAS,GACrC,IAAW,EAAW,EAAE,GAExB,IAAQ,EAA+B,IAAI,GAC3C,IAAU,EAAgC,SAAS,GACnD,IAAe,EAAW,EAAK,GAC/B,EAAE,iBAAc,cAAW,oBAAiB,iBAAc,aAAU,gBACxE,EAAqB,CAAK;EAE5B,eAAe,IAAwB;GACrC,IAAM,IAAK,EAAM;GACjB,IAAI,CAAC,EAAM,OAAO,CAAC,GAAI;GAIvB,AAHA,EAAO,QAAQ,WACf,EAAS,QAAQ,IACjB,EAAS,CAAC,GAAG,CAAC,CAAC,GACf,EAAG,YAAY;GACf,IAAM,IAAM,EAAM;GAClB,IAAI;IACF,IAAM,IAAM,MAAM,MAAM,CAAG;IAC3B,IAAI,CAAC,EAAI,IAAI,MAAU,MAAM,QAAQ,EAAI,QAAQ;IACjD,IAAM,IAAM,MAAM,EAAI,YAAY;IAElC,IAAI,EAAM,QAAQ,KAAO,EAAM,UAAU,GAAI;IAC7C,IAAM,IAAY,MAAM,EAAY,GAAK,GAAI,KAAA,GAAW;KACtD,WAAW;KACX,WAAW;KAGX,aAAa;KACb,cAAc;KACd,YAAY;KACZ,eAAe;KACf,eAAe;KACf,iBAAiB;KACjB,gBAAgB;KAChB,cAAc;IAChB,CAAC;IACD,IAAI,EAAM,QAAQ,KAAO,EAAM,UAAU,GAAI;IAC7C,IAAI;IACJ,IAAI;KACF,IAAa,EAAoB,GAAU,CAAE;IAC/C,SAAS,GAAO;KAGd,AADA,QAAQ,KAAK,wCAAwC,CAAK,GAC1D,IAAa;MAAE,SAAS,CAAC;MAAG,OAAO,EAAwB,CAAE;KAAE;IACjE;IAGA,AAFA,EAAS,EAAW,SAAS,EAAW,KAAK,GAC7C,EAAQ,QAAQ,EAAW,QAAQ,SAAS,YAAY,SACxD,EAAO,QAAQ;GACjB,SAAS,GAAG;IAEV,AADA,EAAS,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAC1D,EAAO,QAAQ;GACjB;EACF;SAEA,EAAU,CAAM,GAChB,QAAY,EAAM,KAAK,CAAM,GAC7B,QAAsB;GACpB,AAAI,EAAM,UAAO,EAAM,MAAM,YAAY;EAC3C,CAAC,mBAIC,EAoBM,OApBN,GAoBM,CAlBI,EAAA,UAAM,WAAA,EAAA,GADd,EASE,GAAA;;GAPQ,WAAW,EAAA;2CAAY,QAAA;GACvB,MAAM,EAAA;sCAAO,QAAA;GACpB,iBAAe,EAAA,CAAA;GACf,cAAY,EAAA,CAAA;GACZ,qBAAmB,EAAA,CAAA;GACnB,kBAAgB,EAAA,CAAA;GAChB,YAAU,EAAA,CAAA;;;;;;;;;mBAEb,EAQM,OARN,GAQM,CAPK,EAAA,UAAM,aAAA,EAAA,GAAf,EAAgE,KAAhE,GAA+C,eAAa,KAC5C,EAAA,UAAM,WAAA,EAAA,GAAtB,EAGM,OAHN,GAGM,CAFJ,EAA0B,KAAA,MAAvB,UAAK,EAAG,EAAA,KAAQ,GAAA,CAAA,GACnB,EAAmE,UAAA;GAA3D,MAAK;GAAS,OAAM;GAAa,SAAO;KAAQ,IAAE,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAAA,EAG5D,EAA+D,OAAA;YAA1B;GAAJ,KAAI;GAAQ,OAAM;sBAAtC,EAAA,UAAM,OAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"DocxViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/DocxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { defineAsyncComponent, nextTick, onBeforeUnmount, shallowRef, watch } from 'vue'\nimport { renderAsync } from 'docx-preview'\nimport DocxEmbeddedFiles from '@/components/DocxEmbeddedFiles.vue'\nimport DocxNavigationPane from '@/components/DocxNavigationPane.vue'\nimport Icon from '@/components/icons/Icon.vue'\nimport type { ViewerFile } from '@/composables/useFileViewer'\nimport { useElementNavigation } from '@/composables/useElementNavigation'\nimport {\n extractDocxEmbeddedFile,\n readDocxEmbeddedFiles,\n type DocxEmbeddedFile,\n} from '@/lib/docxEmbeddings'\nimport { mountDocxEmbeddedPreviews, type DocxEmbeddedDomController } from '@/lib/docxEmbeddedDom'\nimport {\n buildDocxNavigation,\n buildDocxPageNavigation,\n type ParsedDocxModel,\n} from '@/lib/docxNavigation'\nimport { formatBytes } from '@/lib/format'\nimport { classifyViewerFile } from '@/lib/viewerKind'\n\n// The shared body lets an extracted package reuse the same viewer as a top-level file. Lazy loading breaks the\n// FileViewerBody → DocxViewer → FileViewerBody module cycle, matching ZipBrowser's nested-entry hand-off.\nconst FileViewerBody = defineAsyncComponent(() => import('@/components/FileViewerBody.vue'))\n\n/**\n * Side-viewer Word (`.docx`) renderer. Fetches the document bytes from the download URL and renders the\n * OOXML to HTML entirely in-browser via docx-preview (https://github.com/VolodymyrBaydalka/docxjs) — a\n * paginated, paper-like preview (white pages on a neutral canvas, like Word). No CDN / network beyond the\n * file fetch: docx-preview bundles its own jszip + renderer, and images are inlined as base64.\n *\n * Two entry shapes mirror the other browsers (DICOM/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes. docx-preview injects its own CSS\n * into the render container, prefixed with the `className` ('docx') so it can't leak onto the page.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .docx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// The element docx-preview renders into. Always mounted (v-show, not v-if) so the ref exists when we render.\nconst docEl = shallowRef<HTMLElement | null>(null)\nconst navView = shallowRef<'outline' | 'pages'>('outline')\nconst navCollapsed = shallowRef(false)\nconst { outlineItems, pageItems, activeOutlineId, activePageId, setItems, navigate } =\n useElementNavigation(docEl)\n\nlet documentBuffer: ArrayBuffer | null = null\nlet embeddedBlobUrl = ''\nlet embeddedSelectionRun = 0\nlet renderRun = 0\nlet documentScrollTop = 0\nlet inlineEmbedded: DocxEmbeddedDomController | null = null\nconst embeddedFiles = shallowRef<DocxEmbeddedFile[]>([])\nconst selectedEmbedded = shallowRef<DocxEmbeddedFile | null>(null)\nconst embeddedViewerFile = shallowRef<ViewerFile | null>(null)\nconst embeddedStatus = shallowRef<Status>('ready')\nconst embeddedError = shallowRef('')\nconst downloadBusyId = shallowRef('')\nconst downloadError = shallowRef('')\n\nfunction revokeEmbeddedBlob(): void {\n if (!embeddedBlobUrl) return\n URL.revokeObjectURL(embeddedBlobUrl)\n embeddedBlobUrl = ''\n}\n\nfunction clearInlineEmbedded(): void {\n inlineEmbedded?.dispose()\n inlineEmbedded = null\n}\n\nfunction closeEmbedded(restoreDocumentScroll = true): void {\n const shouldRestoreScroll = restoreDocumentScroll && selectedEmbedded.value !== null\n embeddedSelectionRun++\n selectedEmbedded.value = null\n embeddedViewerFile.value = null\n embeddedStatus.value = 'ready'\n embeddedError.value = ''\n revokeEmbeddedBlob()\n if (shouldRestoreScroll) {\n void nextTick(() => {\n if (!selectedEmbedded.value && docEl.value) docEl.value.scrollTop = documentScrollTop\n })\n }\n}\n\nfunction resetEmbedded(): void {\n closeEmbedded(false)\n clearInlineEmbedded()\n documentBuffer = null\n documentScrollTop = 0\n embeddedFiles.value = []\n downloadBusyId.value = ''\n downloadError.value = ''\n}\n\nasync function render(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n const run = ++renderRun\n status.value = 'loading'\n errorMsg.value = ''\n resetEmbedded()\n setItems([], [])\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (renderRun !== run || props.url !== url || docEl.value !== el) return\n // Embedded packages are a best-effort enhancement: malformed relationship XML must not blank a Word file\n // that docx-preview can otherwise render. Parse concurrently because both consumers only read the buffer.\n const embeddedPromise = readDocxEmbeddedFiles(buf).catch((error) => {\n console.warn('DOCX embedded files unavailable:', error)\n return []\n })\n const document = (await renderAsync(buf, el, undefined, {\n className: 'docx', // CSS prefix → docx-preview's injected styles stay scoped to this subtree\n inWrapper: true, // wrap pages in the gray \"paper on desk\" canvas, like Word\n // The side panel is narrow (and resizable), so don't pin pages to the fixed A4 width — that overflows\n // and clips the left edge. Let pages fill the available width and reflow; height follows content.\n ignoreWidth: true,\n ignoreHeight: true,\n breakPages: true, // honor page breaks → still separate page sheets, stacked vertically\n renderHeaders: true,\n renderFooters: true,\n renderFootnotes: true,\n renderEndnotes: true,\n useBase64URL: true, // inline embedded images as data URLs (no blob-URL lifecycle to manage)\n })) as ParsedDocxModel\n const discoveredEmbeddedFiles = await embeddedPromise\n if (renderRun !== run || props.url !== url || docEl.value !== el) return\n let navigation\n try {\n navigation = buildDocxNavigation(document, el)\n } catch (error) {\n // A malformed/unsupported Word model must never turn an otherwise rendered document into an error page.\n console.warn('DOCX outline navigation unavailable:', error)\n navigation = { outline: [], pages: buildDocxPageNavigation(el) }\n }\n setItems(navigation.outline, navigation.pages)\n navView.value = navigation.outline.length ? 'outline' : 'pages'\n documentBuffer = buf\n embeddedFiles.value = discoveredEmbeddedFiles\n inlineEmbedded = mountDocxEmbeddedPreviews({\n root: el,\n buffer: buf,\n files: discoveredEmbeddedFiles,\n onOpen: (file) => void openEmbedded(file),\n })\n status.value = 'ready'\n } catch (e) {\n if (renderRun !== run) return\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nasync function openEmbedded(file: DocxEmbeddedFile): Promise<void> {\n if (!documentBuffer) return\n const buffer = documentBuffer\n let pendingUrl = ''\n documentScrollTop = docEl.value?.scrollTop ?? 0\n closeEmbedded(false)\n // closeEmbedded invalidates prior work; this run now becomes the active selection.\n const activeRun = ++embeddedSelectionRun\n selectedEmbedded.value = file\n embeddedStatus.value = 'loading'\n embeddedError.value = ''\n try {\n const bytes = await extractDocxEmbeddedFile(buffer, file)\n if (embeddedSelectionRun !== activeRun || documentBuffer !== buffer) return\n const url = URL.createObjectURL(new Blob([bytes], { type: file.contentType }))\n pendingUrl = url\n const viewerFile = classifyViewerFile({\n name: file.name,\n url,\n size: file.size,\n mime: file.contentType,\n })\n if (embeddedSelectionRun !== activeRun || selectedEmbedded.value !== file) {\n URL.revokeObjectURL(url)\n pendingUrl = ''\n return\n }\n embeddedBlobUrl = url\n pendingUrl = ''\n embeddedViewerFile.value = viewerFile\n embeddedStatus.value = 'ready'\n } catch (error) {\n if (pendingUrl) URL.revokeObjectURL(pendingUrl)\n if (embeddedSelectionRun !== activeRun) return\n embeddedError.value = error instanceof Error ? error.message : String(error)\n embeddedStatus.value = 'error'\n }\n}\n\nasync function downloadEmbedded(file: DocxEmbeddedFile): Promise<void> {\n if (!documentBuffer || downloadBusyId.value) return\n const buffer = documentBuffer\n downloadBusyId.value = file.id\n downloadError.value = ''\n let url = ''\n try {\n const bytes = await extractDocxEmbeddedFile(buffer, file)\n if (documentBuffer !== buffer) return\n url = URL.createObjectURL(new Blob([bytes], { type: file.contentType }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = file.name\n document.body.appendChild(anchor)\n anchor.click()\n anchor.remove()\n } catch (error) {\n downloadError.value = error instanceof Error ? error.message : String(error)\n } finally {\n if (url) setTimeout(() => URL.revokeObjectURL(url), 1000)\n if (downloadBusyId.value === file.id) downloadBusyId.value = ''\n }\n}\n\nwatch(\n [() => props.url, docEl],\n ([url, element]) => {\n if (url && element) void render()\n },\n { immediate: true },\n)\nonBeforeUnmount(() => {\n renderRun++\n resetEmbedded()\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"dx\">\n <DocxNavigationPane\n v-if=\"status === 'ready' && !selectedEmbedded\"\n v-model:collapsed=\"navCollapsed\"\n v-model:view=\"navView\"\n :outline-items=\"outlineItems\"\n :page-items=\"pageItems\"\n :active-outline-id=\"activeOutlineId\"\n :active-page-id=\"activePageId\"\n @navigate=\"navigate\"\n />\n <div class=\"dx__stage\">\n <div v-if=\"selectedEmbedded\" class=\"dx__embedded-bar\">\n <button type=\"button\" class=\"dx__back\" title=\"返回 Word 文档\" @click=\"closeEmbedded()\">\n <Icon name=\"arrowLeft\" :size=\"16\" />\n </button>\n <span class=\"dx__embedded-title\" :title=\"selectedEmbedded.name\">\n {{ selectedEmbedded.label || selectedEmbedded.name }}\n </span>\n <span class=\"dx__embedded-size\">{{ formatBytes(selectedEmbedded.size) }}</span>\n <button\n type=\"button\"\n class=\"dx__download\"\n title=\"下载内嵌文件\"\n @click=\"downloadEmbedded(selectedEmbedded)\"\n >\n <Icon name=\"download\" :size=\"15\" />\n </button>\n </div>\n\n <p v-if=\"status === 'loading'\" class=\"dx__msg\">正在渲染 Word 文档…</p>\n <div v-else-if=\"status === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"render\">重试</button>\n </div>\n <template v-else-if=\"selectedEmbedded\">\n <p v-if=\"embeddedStatus === 'loading'\" class=\"dx__msg\">正在读取内嵌文件…</p>\n <div v-else-if=\"embeddedStatus === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>读取失败:{{ embeddedError }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"openEmbedded(selectedEmbedded)\">\n 重试\n </button>\n </div>\n <FileViewerBody v-else-if=\"embeddedViewerFile\" :file=\"embeddedViewerFile\" mode=\"render\" />\n </template>\n\n <DocxEmbeddedFiles\n v-else-if=\"status === 'ready' && embeddedFiles.length\"\n :files=\"embeddedFiles\"\n :download-busy-id=\"downloadBusyId\"\n :error=\"downloadError\"\n @open=\"openEmbedded\"\n @download=\"downloadEmbedded\"\n />\n <!-- Always mounted (v-show) so docEl is available to docx-preview; hidden until render completes. -->\n <div v-show=\"status === 'ready' && !selectedEmbedded\" ref=\"docEl\" class=\"dx__doc\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.dx {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.dx__stage {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.dx__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dx__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dx__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.dx__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.dx__embedded-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n padding: 7px 10px 7px 8px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n}\n.dx__back,\n.dx__download {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 30px;\n height: 28px;\n padding: 0;\n color: var(--muted);\n background: transparent;\n border: none;\n border-radius: 7px;\n cursor: pointer;\n}\n.dx__back:hover,\n.dx__download:hover {\n color: var(--accent);\n background: var(--surface-2);\n}\n.dx__embedded-title {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 500;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dx__embedded-size {\n flex-shrink: 0;\n color: var(--faint);\n font-size: 11px;\n font-variant-numeric: tabular-nums;\n}\n\n/* docx-preview intentionally skips OLE `w:object` nodes but leaves their empty <p>. The DOM adapter inserts\n * Word/WPS's inert raster snapshot, or a rendered PPTX first slide when the snapshot is browser-incompatible,\n * into that exact paragraph. The whole preview opens the real embedded file. */\n.dx__doc :deep(.docx-embedded-host) {\n min-height: 0;\n margin: 8px 0 12px;\n text-align: center;\n}\n.dx__doc :deep(.docx-embedded-preview) {\n position: relative;\n display: inline-flex;\n box-sizing: border-box;\n align-items: center;\n justify-content: center;\n gap: 10px;\n width: 100%;\n min-height: 72px;\n padding: 12px;\n overflow: hidden;\n color: #334155;\n background: #f8fafc;\n border: 1px solid #cbd5e1;\n border-radius: 6px;\n box-shadow: 0 1px 3px rgba(15, 23, 42, 0.12);\n cursor: pointer;\n font-family: Arial, sans-serif;\n text-align: left;\n}\n.dx__doc :deep(.docx-embedded-preview:hover),\n.dx__doc :deep(.docx-embedded-preview:focus-visible) {\n border-color: #3370ff;\n box-shadow:\n 0 0 0 2px rgba(51, 112, 255, 0.16),\n 0 2px 8px rgba(15, 23, 42, 0.16);\n outline: none;\n}\n.dx__doc :deep(.docx-embedded-preview--image) {\n display: inline-block;\n min-height: 0;\n padding: 0;\n background: #fff;\n}\n.dx__doc :deep(.docx-embedded-preview--pptx) {\n display: block;\n min-height: 0;\n padding: 0;\n background: #fff;\n}\n.dx__doc :deep(.docx-embedded-preview--loading) {\n cursor: progress;\n}\n.dx__doc :deep(.docx-embedded-preview__pptx-slide) {\n width: 100%;\n height: 100%;\n overflow: hidden;\n pointer-events: none;\n}\n.dx__doc :deep(.docx-embedded-preview__pptx-status) {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #64748b;\n background: #f8fafc;\n font-size: 12px;\n font-weight: 600;\n}\n.dx__doc :deep(.docx-embedded-preview__image) {\n display: block;\n width: 100%;\n height: auto;\n object-fit: contain;\n}\n.dx__doc :deep(.docx-embedded-preview__hint) {\n position: absolute;\n right: 8px;\n bottom: 8px;\n padding: 5px 9px;\n color: #fff;\n background: rgba(15, 23, 42, 0.76);\n border-radius: 999px;\n font-size: 10px;\n font-weight: 600;\n letter-spacing: 0.02em;\n opacity: 0;\n pointer-events: none;\n transform: translateY(3px);\n transition:\n opacity 0.15s,\n transform 0.15s;\n}\n.dx__doc :deep(.docx-embedded-preview:hover .docx-embedded-preview__hint),\n.dx__doc :deep(.docx-embedded-preview:focus-visible .docx-embedded-preview__hint) {\n opacity: 1;\n transform: translateY(0);\n}\n.dx__doc :deep(.docx-embedded-preview__badge) {\n flex-shrink: 0;\n padding: 5px 7px;\n color: #166534;\n background: #dcfce7;\n border-radius: 5px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.04em;\n}\n.dx__doc :deep(.docx-embedded-preview__fallback-label) {\n min-width: 0;\n overflow: hidden;\n font-size: 12px;\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Scroll region holding the rendered document. The pages keep their native Word appearance (white paper,\n * black text) for fidelity; we only retint the surrounding \"desk\" to the app's neutral surface so it sits\n * in the panel naturally in both light and dark themes. :deep reaches docx-preview's injected DOM. */\n.dx__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n}\n.dx__doc :deep(.docx-wrapper) {\n background: var(--surface-2);\n padding: 22px 16px 40px;\n gap: 16px;\n}\n/* Each page: a white sheet with a soft shadow. With ignoreWidth the page reflows to the panel width, so\n * pin it to fill the wrapper (box-sizing so the doc's own margins stay inside the sheet, not overflowing). */\n.dx__doc :deep(.docx-wrapper > section.docx) {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n margin-bottom: 0;\n}\n/* Some Word docs anchor tables as \"floating\" objects (OOXML `w:tblpPr` — a table pinned to a fixed spot on\n * the page with body text wrapping around it). docx-preview approximates that with `float: left` + a fixed\n * point width. On a full A4 page the table lands where Word put it, but in this narrow, width-reflowing\n * panel the floats stack unpredictably and the surrounding paragraphs wrap into the gaps beside them — so on\n * resize the layout scrambles and dark cell shading appears to streak across (the reported \"背景乱串 / 排版\n * 混乱\"). Since we've already chosen reflow over pixel-exact A4 fidelity (ignoreWidth + section width:100%),\n * force every table back to a normal full-width block that stacks top-to-bottom at any panel width, and let\n * long cell content wrap instead of overflowing. */\n.dx__doc :deep(section.docx table) {\n float: none !important;\n width: 100% !important;\n max-width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n.dx__doc :deep(section.docx td),\n.dx__doc :deep(section.docx th) {\n overflow-wrap: break-word;\n}\n/* The page's own side margins (this doc uses ~90pt) would swallow most of a narrow panel now that tables\n * fill the width, so cap the horizontal padding while keeping the top/bottom page margin intact. */\n.dx__doc :deep(section.docx) {\n padding-left: clamp(14px, 5%, 90pt) !important;\n padding-right: clamp(14px, 5%, 90pt) !important;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwBA,IAAM,IAAiB,QAA2B,OAAO,sBAAkC,GAYrF,IAAQ,GAQR,IAAS,EAAmB,SAAS,GACrC,IAAW,EAAW,EAAE,GAExB,IAAQ,EAA+B,IAAI,GAC3C,IAAU,EAAgC,SAAS,GACnD,IAAe,EAAW,EAAK,GAC/B,EAAE,kBAAc,eAAW,oBAAiB,iBAAc,aAAU,gBACxE,EAAqB,CAAK,GAExB,IAAqC,MACrC,IAAkB,IAClB,IAAuB,GACvB,IAAY,GACZ,IAAoB,GACpB,IAAmD,MACjD,IAAgB,EAA+B,CAAC,CAAC,GACjD,IAAmB,EAAoC,IAAI,GAC3D,IAAqB,EAA8B,IAAI,GACvD,IAAiB,EAAmB,OAAO,GAC3C,IAAgB,EAAW,EAAE,GAC7B,IAAiB,EAAW,EAAE,GAC9B,IAAgB,EAAW,EAAE;EAEnC,SAAS,KAA2B;GAC7B,AAEL,OADA,IAAI,gBAAgB,CAAe,GACjB;EACpB;EAEA,SAAS,IAA4B;GAEnC,AADA,GAAgB,QAAQ,GACxB,IAAiB;EACnB;EAEA,SAAS,EAAc,IAAwB,IAAY;GACzD,IAAM,IAAsB,KAAyB,EAAiB,UAAU;GAOhF,AANA,KACA,EAAiB,QAAQ,MACzB,EAAmB,QAAQ,MAC3B,EAAe,QAAQ,SACvB,EAAc,QAAQ,IACtB,GAAmB,GACf,KACF,SAAoB;IAClB,AAAI,CAAC,EAAiB,SAAS,EAAM,UAAO,EAAM,MAAM,YAAY;GACtE,CAAC;EAEL;EAEA,SAAS,IAAsB;GAO7B,AANA,EAAc,EAAK,GACnB,EAAoB,GACpB,IAAiB,MACjB,IAAoB,GACpB,EAAc,QAAQ,CAAC,GACvB,EAAe,QAAQ,IACvB,EAAc,QAAQ;EACxB;EAEA,eAAe,IAAwB;GACrC,IAAM,IAAK,EAAM;GACjB,IAAI,CAAC,EAAM,OAAO,CAAC,GAAI;GACvB,IAAM,IAAM,EAAE;GAKd,AAJA,EAAO,QAAQ,WACf,EAAS,QAAQ,IACjB,EAAc,GACd,EAAS,CAAC,GAAG,CAAC,CAAC,GACf,EAAG,YAAY;GACf,IAAM,IAAM,EAAM;GAClB,IAAI;IACF,IAAM,IAAM,MAAM,MAAM,CAAG;IAC3B,IAAI,CAAC,EAAI,IAAI,MAAU,MAAM,QAAQ,EAAI,QAAQ;IACjD,IAAM,IAAM,MAAM,EAAI,YAAY;IAElC,IAAI,MAAc,KAAO,EAAM,QAAQ,KAAO,EAAM,UAAU,GAAI;IAGlE,IAAM,IAAkB,EAAsB,CAAG,CAAC,CAAC,OAAO,OACxD,QAAQ,KAAK,oCAAoC,CAAK,GAC/C,CAAC,EACT,GACK,IAAY,MAAM,GAAY,GAAK,GAAI,KAAA,GAAW;KACtD,WAAW;KACX,WAAW;KAGX,aAAa;KACb,cAAc;KACd,YAAY;KACZ,eAAe;KACf,eAAe;KACf,iBAAiB;KACjB,gBAAgB;KAChB,cAAc;IAChB,CAAC,GACK,IAA0B,MAAM;IACtC,IAAI,MAAc,KAAO,EAAM,QAAQ,KAAO,EAAM,UAAU,GAAI;IAClE,IAAI;IACJ,IAAI;KACF,IAAa,GAAoB,GAAU,CAAE;IAC/C,SAAS,GAAO;KAGd,AADA,QAAQ,KAAK,wCAAwC,CAAK,GAC1D,IAAa;MAAE,SAAS,CAAC;MAAG,OAAO,GAAwB,CAAE;KAAE;IACjE;IAWA,AAVA,EAAS,EAAW,SAAS,EAAW,KAAK,GAC7C,EAAQ,QAAQ,EAAW,QAAQ,SAAS,YAAY,SACxD,IAAiB,GACjB,EAAc,QAAQ,GACtB,IAAiB,EAA0B;KACzC,MAAM;KACN,QAAQ;KACR,OAAO;KACP,SAAS,MAAS,KAAK,EAAa,CAAI;IAC1C,CAAC,GACD,EAAO,QAAQ;GACjB,SAAS,GAAG;IACV,IAAI,MAAc,GAAK;IAEvB,AADA,EAAS,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAC1D,EAAO,QAAQ;GACjB;EACF;EAEA,eAAe,EAAa,GAAuC;GACjE,IAAI,CAAC,GAAgB;GACrB,IAAM,IAAS,GACX,IAAa;GAEjB,AADA,IAAoB,EAAM,OAAO,aAAa,GAC9C,EAAc,EAAK;GAEnB,IAAM,IAAY,EAAE;GAGpB,AAFA,EAAiB,QAAQ,GACzB,EAAe,QAAQ,WACvB,EAAc,QAAQ;GACtB,IAAI;IACF,IAAM,IAAQ,MAAM,EAAwB,GAAQ,CAAI;IACxD,IAAI,MAAyB,KAAa,MAAmB,GAAQ;IACrE,IAAM,IAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,CAAK,GAAG,EAAE,MAAM,EAAK,YAAY,CAAC,CAAC;IAC7E,IAAa;IACb,IAAM,IAAa,EAAmB;KACpC,MAAM,EAAK;KACX;KACA,MAAM,EAAK;KACX,MAAM,EAAK;IACb,CAAC;IACD,IAAI,MAAyB,KAAa,EAAiB,UAAU,GAAM;KAEzE,AADA,IAAI,gBAAgB,CAAG,GACvB,IAAa;KACb;IACF;IAIA,AAHA,IAAkB,GAClB,IAAa,IACb,EAAmB,QAAQ,GAC3B,EAAe,QAAQ;GACzB,SAAS,GAAO;IAEd,IADI,KAAY,IAAI,gBAAgB,CAAU,GAC1C,MAAyB,GAAW;IAExC,AADA,EAAc,QAAQ,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK,GAC3E,EAAe,QAAQ;GACzB;EACF;EAEA,eAAe,EAAiB,GAAuC;GACrE,IAAI,CAAC,KAAkB,EAAe,OAAO;GAC7C,IAAM,IAAS;GAEf,AADA,EAAe,QAAQ,EAAK,IAC5B,EAAc,QAAQ;GACtB,IAAI,IAAM;GACV,IAAI;IACF,IAAM,IAAQ,MAAM,EAAwB,GAAQ,CAAI;IACxD,IAAI,MAAmB,GAAQ;IAC/B,IAAM,IAAI,gBAAgB,IAAI,KAAK,CAAC,CAAK,GAAG,EAAE,MAAM,EAAK,YAAY,CAAC,CAAC;IACvE,IAAM,IAAS,SAAS,cAAc,GAAG;IAKzC,AAJA,EAAO,OAAO,GACd,EAAO,WAAW,EAAK,MACvB,SAAS,KAAK,YAAY,CAAM,GAChC,EAAO,MAAM,GACb,EAAO,OAAO;GAChB,SAAS,GAAO;IACd,EAAc,QAAQ,aAAiB,QAAQ,EAAM,UAAU,OAAO,CAAK;GAC7E,UAAU;IAER,AADI,KAAK,iBAAiB,IAAI,gBAAgB,CAAG,GAAG,GAAI,GACpD,EAAe,UAAU,EAAK,OAAI,EAAe,QAAQ;GAC/D;EACF;SAEA,GACE,OAAO,EAAM,KAAK,CAAK,IACtB,CAAC,GAAK,OAAa;GAClB,AAAI,KAAO,KAAS,EAAY;EAClC,GACA,EAAE,WAAW,GAAK,CACpB,GACA,SAAsB;GAGpB,AAFA,KACA,EAAc,GACV,EAAM,UAAO,EAAM,MAAM,YAAY;EAC3C,CAAC,mBAIC,EAyDM,OAzDN,IAyDM,CAvDI,EAAA,UAAM,WAAA,CAAiB,EAAA,SAAA,EAAA,GAD/B,EASE,GAAA;;GAPQ,WAAW,EAAA;2CAAY,QAAA;GACvB,MAAM,EAAA;sCAAO,QAAA;GACpB,iBAAe,EAAA,EAAA;GACf,cAAY,EAAA,EAAA;GACZ,qBAAmB,EAAA,CAAA;GACnB,kBAAgB,EAAA,CAAA;GAChB,YAAU,EAAA,CAAA;;;;;;;;;mBAEb,EA6CM,OA7CN,IA6CM;GA5CO,EAAA,SAAA,EAAA,GAAX,EAgBM,OAhBN,GAgBM;IAfJ,EAES,UAAA;KAFD,MAAK;KAAS,OAAM;KAAW,OAAM;KAAc,SAAK,AAAA,EAAA,QAAA,MAAE,EAAa;QAC7E,EAAoC,GAAA;KAA9B,MAAK;KAAa,MAAM;;IAEhC,EAEO,QAAA;KAFD,OAAM;KAAsB,OAAO,EAAA,MAAiB;SACrD,EAAA,MAAiB,SAAS,EAAA,MAAiB,IAAI,GAAA,GAAA,CAAA;IAEpD,EAA+E,QAA/E,GAA+E,EAA5C,EAAA,CAAA,CAAW,CAAC,EAAA,MAAiB,IAAI,CAAA,GAAA,CAAA;IACpE,EAOS,UAAA;KANP,MAAK;KACL,OAAM;KACN,OAAM;KACL,SAAK,AAAA,EAAA,QAAA,MAAE,EAAiB,EAAA,KAAgB;QAEzC,EAAmC,GAAA;KAA7B,MAAK;KAAY,MAAM;;;GAIxB,EAAA,UAAM,aAAA,EAAA,GAAf,EAAgE,KAAhE,IAA+C,eAAa,KAC5C,EAAA,UAAM,WAAA,EAAA,GAAtB,EAGM,OAHN,IAGM,CAFJ,EAA0B,KAAA,MAAvB,UAAK,EAAG,EAAA,KAAQ,GAAA,CAAA,GACnB,EAAmE,UAAA;IAA3D,MAAK;IAAS,OAAM;IAAa,SAAO;MAAQ,IAAE,CAAA,CAAA,KAEvC,EAAA,SAAA,EAAA,GAArB,EASW,GAAA,EAAA,KAAA,EAAA,GAAA,CARA,EAAA,UAAc,aAAA,EAAA,GAAvB,EAAoE,KAApE,IAAuD,WAAS,KAChD,EAAA,UAAc,WAAA,EAAA,GAA9B,EAKM,OALN,IAKM,CAJJ,EAA+B,KAAA,MAA5B,UAAK,EAAG,EAAA,KAAa,GAAA,CAAA,GACxB,EAES,UAAA;IAFD,MAAK;IAAS,OAAM;IAAa,SAAK,AAAA,EAAA,QAAA,MAAE,EAAa,EAAA,KAAgB;MAAG,MAEhF,CAAA,CAAA,KAEyB,EAAA,SAAA,EAAA,GAA3B,EAA0F,EAAA,CAAA,GAAA;;IAA1C,MAAM,EAAA;IAAoB,MAAK;+CAIpE,EAAA,UAAM,WAAgB,EAAA,MAAc,UAAA,EAAA,GADjD,EAOE,GAAA;;IALC,OAAO,EAAA;IACP,oBAAkB,EAAA;IAClB,OAAO,EAAA;IACP,QAAM;IACN,YAAU;;;;;;MAGb,EAAoF,OAAA;aAA1B;IAAJ,KAAI;IAAQ,OAAM;wBAA3D,EAAA,UAAM,WAAA,CAAiB,EAAA,KAAgB,CAAA,CAAA"}
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./FileViewerBody.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/FileViewerBody.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-82d079e3"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-82dd34f8"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
8