@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
package/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  ## 特性
8
8
 
9
9
  - **60+ 格式**:docx/xlsx/pptx/pdf、zip/7z/rar/tar、png/tiff/psd/heic/dcm、csv/jsonl/sqlite、markdown/代码高亮、eml/mht/har、字体/证书/密钥库、epub/mob、drawio/dxf/geojson、hprof/.class/.so/.dll、易语言 .e/.ec 等。
10
+ - **Word 内嵌文件**:识别 DOCX 中被 `docx-preview` 跳过的嵌入包(含直接嵌入的 XLSX 与 OLE 包装的 PPTX),把可用的 Word/WPS 内容快照插回正文原位置;遇到浏览器不支持的 EMF 快照时,按需用 PPTX renderer 渲染第一页。点击正文预览即可打开完整文件,也可下载原始附件。
11
+ - **终端日志还原**:日志和纯文本可解释 ANSI SGR 前景色、背景色、16/256/真彩色及常见文字装饰;搜索、折叠、复制使用净化后的纯文本,其他终端控制命令不会在页面执行。
10
12
  - **ZIP 网页预览**:打开压缩包里的 HTML 时,自动解析并加载同包的 JS、CSS、图片、字体等相对依赖;源码保持原样,脚本仍需用户主动开启后才会在不透明源沙箱中运行。
11
13
  - **零后端**:上游字节只在浏览器解析;HTML/SVG 走沙箱 iframe、下载端点无法内联执行。
12
14
  - **按需分包**:每个查看器是独立的懒加载 chunk,打开 PDF 才拉 PDF 引擎;用不到的格式零成本。
@@ -0,0 +1,47 @@
1
+ import { type PropType, type VNodeChild } from 'vue';
2
+ import { type AnsiLine } from '@/lib/ansi';
3
+ /**
4
+ * Render one inert ANSI line with Vue text nodes. The parser owns the style whitelist; this component never
5
+ * accepts or emits raw HTML. A query is optionally wrapped in `<mark>` after matching against control-free text.
6
+ */
7
+ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
8
+ line: {
9
+ type: PropType<string | AnsiLine>;
10
+ default: undefined;
11
+ };
12
+ lines: {
13
+ type: PropType<readonly (string | AnsiLine)[]>;
14
+ default: () => never[];
15
+ };
16
+ query: {
17
+ type: StringConstructor;
18
+ default: string;
19
+ };
20
+ markClass: {
21
+ type: StringConstructor;
22
+ default: string;
23
+ };
24
+ }>, () => VNodeChild[], {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
25
+ line: {
26
+ type: PropType<string | AnsiLine>;
27
+ default: undefined;
28
+ };
29
+ lines: {
30
+ type: PropType<readonly (string | AnsiLine)[]>;
31
+ default: () => never[];
32
+ };
33
+ query: {
34
+ type: StringConstructor;
35
+ default: string;
36
+ };
37
+ markClass: {
38
+ type: StringConstructor;
39
+ default: string;
40
+ };
41
+ }>> & Readonly<{}>, {
42
+ line: string | AnsiLine;
43
+ lines: readonly (string | AnsiLine)[];
44
+ query: string;
45
+ markClass: string;
46
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
47
+ export default _default;
@@ -0,0 +1,41 @@
1
+ import { ansiRenderPieces as e } from "../lib/ansi.js";
2
+ import { defineComponent as t, h as n } from "vue";
3
+ //#region src/components/AnsiText.ts
4
+ var r = t({
5
+ name: "AnsiText",
6
+ props: {
7
+ line: {
8
+ type: [String, Object],
9
+ default: void 0
10
+ },
11
+ lines: {
12
+ type: Array,
13
+ default: () => []
14
+ },
15
+ query: {
16
+ type: String,
17
+ default: ""
18
+ },
19
+ markClass: {
20
+ type: String,
21
+ default: "ansi__hit"
22
+ }
23
+ },
24
+ setup(t) {
25
+ return () => {
26
+ let r = t.line === void 0 ? t.lines : [t.line], i = [];
27
+ return r.forEach((a, o) => {
28
+ e(a, t.query).forEach((e, r) => {
29
+ i.push(n("span", {
30
+ key: `${o}:${r}`,
31
+ style: e.style
32
+ }, e.marked ? n("mark", { class: t.markClass }, e.text) : e.text));
33
+ }), o < r.length - 1 && i.push("\n");
34
+ }), i;
35
+ };
36
+ }
37
+ });
38
+ //#endregion
39
+ export { r as default };
40
+
41
+ //# sourceMappingURL=AnsiText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnsiText.js","names":[],"sources":["../../src/components/AnsiText.ts"],"sourcesContent":["import { defineComponent, h, type PropType, type VNodeChild } from 'vue'\nimport { ansiRenderPieces, type AnsiLine } from '@/lib/ansi'\n\n/**\n * Render one inert ANSI line with Vue text nodes. The parser owns the style whitelist; this component never\n * accepts or emits raw HTML. A query is optionally wrapped in `<mark>` after matching against control-free text.\n */\nexport default defineComponent({\n name: 'AnsiText',\n props: {\n line: { type: [String, Object] as PropType<string | AnsiLine>, default: undefined },\n lines: {\n type: Array as PropType<readonly (string | AnsiLine)[]>,\n default: () => [],\n },\n query: { type: String, default: '' },\n markClass: { type: String, default: 'ansi__hit' },\n },\n setup(props) {\n return (): VNodeChild[] => {\n const lines = props.line !== undefined ? [props.line] : props.lines\n const nodes: VNodeChild[] = []\n lines.forEach((line, lineIndex) => {\n ansiRenderPieces(line, props.query).forEach((piece, pieceIndex) => {\n nodes.push(\n h(\n 'span',\n { key: `${lineIndex}:${pieceIndex}`, style: piece.style },\n piece.marked ? h('mark', { class: props.markClass }, piece.text) : piece.text,\n ),\n )\n })\n if (lineIndex < lines.length - 1) nodes.push('\\n')\n })\n return nodes\n }\n },\n})\n"],"mappings":";;;AAOA,IAAA,IAAe,EAAgB;CAC7B,MAAM;CACN,OAAO;EACL,MAAM;GAAE,MAAM,CAAC,QAAQ,MAAM;GAAkC,SAAS,KAAA;EAAU;EAClF,OAAO;GACL,MAAM;GACN,eAAe,CAAC;EAClB;EACA,OAAO;GAAE,MAAM;GAAQ,SAAS;EAAG;EACnC,WAAW;GAAE,MAAM;GAAQ,SAAS;EAAY;CAClD;CACA,MAAM,GAAO;EACX,aAA2B;GACzB,IAAM,IAAQ,EAAM,SAAS,KAAA,IAA2B,EAAM,QAArB,CAAC,EAAM,IAAI,GAC9C,IAAsB,CAAC;GAa7B,OAZA,EAAM,SAAS,GAAM,MAAc;IAUjC,AATA,EAAiB,GAAM,EAAM,KAAK,CAAC,CAAC,SAAS,GAAO,MAAe;KACjE,EAAM,KACJ,EACE,QACA;MAAE,KAAK,GAAG,EAAU,GAAG;MAAc,OAAO,EAAM;KAAM,GACxD,EAAM,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAM,UAAU,GAAG,EAAM,IAAI,IAAI,EAAM,IAC3E,CACF;IACF,CAAC,GACG,IAAY,EAAM,SAAS,KAAG,EAAM,KAAK,IAAI;GACnD,CAAC,GACM;EACT;CACF;AACF,CAAC"}
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./CodeView.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/CodeView.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-f432e526"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-b7b2f434"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
8
 
@@ -1 +1 @@
1
- {"version":3,"file":"CodeView.js","names":[],"sources":["../../src/components/CodeView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'\nimport { escapeHtml, highlightCode, highlightLines } from '@/lib/highlight'\nimport { computeFoldRegions } from '@/lib/codefold'\nimport { canFormat, formatCode } from '@/lib/codeformat'\nimport { fileExt } from '@/lib/fileType'\nimport { langLabel, searchLangs } from '@/lib/highlightLangs'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * The side viewer's source-code view: a line-number gutter + syntax-highlighted code, with indentation-based\n * code FOLDING (click a chevron to collapse a block; see lib/codefold.ts), on-demand Prettier FORMATTING (the\n * 格式化 button, shown only for languages Prettier can parse — see lib/codeformat.ts; loaded lazily), and a\n * manual HIGHLIGHT-LANGUAGE picker (the 语言 button — auto-detection by extension/mime is often empty or wrong,\n * so any file can be re-colorized as JSON / XML / Python / … or forced to plain text; see lib/highlightLangs).\n * Used for every non-rendered file (plain text, code, or markdown/html/svg viewed as source). Highlighting is\n * split per line (see highlightLines) so rows can be hidden individually.\n *\n * Very large files (> {@link MAX_FOLD_LINES}) skip the per-line DOM and fall back to a single highlighted block\n * — folding a 10k-line file isn't worth tens of thousands of row elements, and such files mostly reach the\n * streaming log viewer anyway (see isLogFile).\n */\nconst props = defineProps<{ code: string; lang: string; name?: string }>()\n\nconst MAX_FOLD_LINES = 4000\n\n// Manual highlight-language override (null = use the auto-detected `props.lang`; '' = forced plain text). The\n// effective language feeds highlighting AND the 格式化 button (so \"force JSON, then format\" works). Reset when a\n// different file / detected language arrives (see the reset watch below).\nconst langOverride = ref<string | null>(null)\nconst effLang = computed(() => langOverride.value ?? props.lang)\n\n// Formatted output (null = showing the original). The view always renders `displayCode`.\nconst formatted = ref<string | null>(null)\nconst formatting = ref(false)\nconst formatErr = ref(false)\nconst displayCode = computed(() => formatted.value ?? props.code)\n\nconst ext = computed(() => fileExt(props.name ?? ''))\nconst canFmt = computed(() => canFormat(ext.value, effLang.value))\n\nconst rawLines = computed(() => displayCode.value.split('\\n'))\nconst lineCount = computed(() => rawLines.value.length)\nconst foldable = computed(() => lineCount.value <= MAX_FOLD_LINES)\n\nconst lineHtml = computed(() => highlightLines(displayCode.value, effLang.value))\nconst regions = computed(() => computeFoldRegions(displayCode.value))\nconst folded = reactive(new Set<number>())\n\n// A new file (or a re-detected language) resets the formatting toggle AND the manual language override; any\n// change to the shown code clears stale fold state.\nwatch(\n () => [props.code, props.lang],\n () => {\n formatted.value = null\n formatErr.value = false\n langOverride.value = null\n },\n)\nwatch(displayCode, () => folded.clear())\n\n// ── highlight-language picker ────────────────────────────────────────────────────────────────────\nconst langMenuOpen = ref(false)\nconst langMenuRef = ref<HTMLElement | null>(null)\nconst langQuery = ref('')\nconst langResults = computed(() => searchLangs(langQuery.value))\n\nfunction chooseLang(id: string): void {\n langOverride.value = id\n langMenuOpen.value = false\n langQuery.value = ''\n}\n\n// Close the menu on an outside click or Esc — listeners live only while it's open (SSR-guarded). The toggle\n// button + popup both sit inside langMenuRef, so a click on either is \"inside\" and never self-closes.\nfunction onLangDocPointer(e: MouseEvent): void {\n if (langMenuRef.value && !langMenuRef.value.contains(e.target as Node)) langMenuOpen.value = false\n}\nfunction onLangKey(e: KeyboardEvent): void {\n if (e.key === 'Escape') langMenuOpen.value = false\n}\nwatch(langMenuOpen, (on) => {\n if (typeof document === 'undefined') return\n if (on) {\n document.addEventListener('click', onLangDocPointer)\n document.addEventListener('keydown', onLangKey)\n } else {\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n }\n})\nonBeforeUnmount(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n})\n\nlet errTimer: ReturnType<typeof setTimeout> | undefined\nasync function toggleFormat(): Promise<void> {\n if (formatted.value != null) {\n formatted.value = null // revert to the original\n return\n }\n if (formatting.value) return\n formatting.value = true\n formatErr.value = false\n try {\n formatted.value = await formatCode(props.code, { ext: ext.value, lang: effLang.value })\n } catch {\n formatErr.value = true\n clearTimeout(errTimer)\n errTimer = setTimeout(() => (formatErr.value = false), 2200)\n } finally {\n formatting.value = false\n }\n}\n\n// Visible line indices, skipping the bodies of folded regions (and any nested folds within them).\nconst visible = computed(() => {\n const out: number[] = []\n const n = lineCount.value\n let i = 0\n while (i < n) {\n out.push(i)\n const end = folded.has(i) ? regions.value.get(i) : undefined\n i = end != null ? end + 1 : i + 1\n }\n return out\n})\n\nfunction toggle(i: number): void {\n if (folded.has(i)) folded.delete(i)\n else folded.add(i)\n}\n\n// Blob fallback for huge files: one highlighted block + a newline-joined gutter.\nconst blobHtml = computed(() =>\n effLang.value ? highlightCode(displayCode.value, effLang.value) : escapeHtml(displayCode.value),\n)\nconst blobGutter = computed(() =>\n Array.from({ length: lineCount.value }, (_, i) => i + 1).join('\\n'),\n)\n</script>\n\n<template>\n <div class=\"cv\">\n <!-- floating tools, top-right over the code: highlight-language picker + Prettier format toggle -->\n <div class=\"cv__tools\">\n <!-- highlight-language picker: force which grammar colorizes the source (auto-detect is often empty/wrong) -->\n <div ref=\"langMenuRef\" class=\"cv__lang\">\n <button\n type=\"button\"\n class=\"cv__toolbtn\"\n :class=\"{ 'cv__toolbtn--on': langMenuOpen }\"\n title=\"高亮语言 —— 选择用哪种语言着色(自动识别不准时手动指定)\"\n :aria-haspopup=\"true\"\n :aria-expanded=\"langMenuOpen\"\n @click=\"langMenuOpen = !langMenuOpen\"\n >\n <Icon name=\"code\" :size=\"12\" /><span>{{ langLabel(effLang) }}</span>\n </button>\n <div v-if=\"langMenuOpen\" class=\"cv__langpop\" role=\"menu\">\n <input\n v-model=\"langQuery\"\n class=\"cv__langsearch\"\n type=\"text\"\n placeholder=\"搜索语言…\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n <div class=\"cv__langlist\">\n <button\n v-if=\"!langQuery\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === '' }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === ''\"\n @click=\"chooseLang('')\"\n >\n <span class=\"cv__langitem-label\">纯文本(无高亮)</span>\n <Icon v-if=\"effLang === ''\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <button\n v-for=\"l in langResults\"\n :key=\"l.id\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === l.id }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === l.id\"\n @click=\"chooseLang(l.id)\"\n >\n <span class=\"cv__langitem-label\">{{ l.label }}</span>\n <Icon v-if=\"effLang === l.id\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <p v-if=\"langQuery && !langResults.length\" class=\"cv__langempty\">无匹配语言</p>\n </div>\n </div>\n </div>\n\n <!-- format toggle (only for languages Prettier can parse) -->\n <button\n v-if=\"canFmt\"\n type=\"button\"\n class=\"cv__toolbtn cv__fmt\"\n :class=\"{ 'cv__toolbtn--on': formatted != null, 'cv__fmt--err': formatErr }\"\n :disabled=\"formatting\"\n :title=\"\n formatErr\n ? '格式化失败:源码可能无法解析'\n : formatted != null\n ? '还原为原始格式'\n : '用 Prettier 格式化'\n \"\n @click=\"toggleFormat\"\n >\n {{\n formatErr\n ? '格式化失败'\n : formatting\n ? '格式化中…'\n : formatted != null\n ? '还原'\n : '格式化'\n }}\n </button>\n </div>\n\n <!-- huge file: single highlighted block (no folding) -->\n <div v-if=\"!foldable\" class=\"cv__scroll cv__scroll--blob\">\n <div class=\"cv__codewrap\">\n <pre class=\"cv__gutter\" aria-hidden=\"true\">{{ blobGutter }}</pre>\n <div class=\"cv__codecol\">\n <pre class=\"cv__pre\"><code class=\"hljs\" v-html=\"blobHtml\" /></pre>\n </div>\n </div>\n </div>\n\n <!-- foldable: one row per visible line, with a fold chevron on block headers -->\n <div v-else class=\"cv__scroll\">\n <div class=\"cv__rows\">\n <div v-for=\"i in visible\" :key=\"i\" class=\"cv__row\">\n <span class=\"cv__gut\">\n <button\n v-if=\"regions.has(i)\"\n type=\"button\"\n class=\"cv__fold\"\n :title=\"folded.has(i) ? '展开' : '折叠'\"\n @click=\"toggle(i)\"\n >\n <Icon name=\"chevronRight\" :size=\"12\" :class=\"{ 'cv__fold--open': !folded.has(i) }\" />\n </button>\n <span v-else class=\"cv__fold cv__fold--empty\" />\n <span class=\"cv__ln\">{{ i + 1 }}</span>\n </span>\n <code class=\"cv__code hljs\" v-html=\"lineHtml[i]\" />\n <button\n v-if=\"folded.has(i)\"\n type=\"button\"\n class=\"cv__ellipsis\"\n :title=\"`展开折叠的 ${(regions.get(i) ?? i) - i} 行`\"\n @click=\"toggle(i)\"\n >\n ⋯\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.cv {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* Floating tools bar — top-right, above the code (highlight-language picker + format toggle). */\n.cv__tools {\n position: absolute;\n top: 8px;\n right: 14px;\n z-index: 3;\n display: flex;\n align-items: flex-start;\n gap: 6px;\n}\n\n/* Shared small pill used by both floating tools. */\n.cv__toolbtn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n max-width: 190px;\n padding: 3px 10px;\n font-family: inherit;\n font-size: 11.5px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n cursor: pointer;\n box-shadow: var(--shadow);\n transition:\n background 0.15s,\n color 0.15s,\n border-color 0.15s;\n}\n.cv__toolbtn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.cv__toolbtn span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__toolbtn--on {\n color: #fff;\n background: var(--accent);\n border-color: var(--accent);\n}\n.cv__toolbtn--on:hover {\n color: #fff;\n}\n\n/* Format toggle specifics (disabled + error states). */\n.cv__fmt:disabled {\n cursor: default;\n opacity: 0.7;\n}\n.cv__fmt--err {\n color: var(--danger);\n border-color: var(--danger);\n}\n\n/* Highlight-language picker dropdown (anchored under its pill, right-aligned). */\n.cv__lang {\n position: relative;\n display: inline-flex;\n}\n.cv__langpop {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 4;\n width: 224px;\n padding: 7px;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 10px;\n box-shadow: var(--shadow);\n}\n.cv__langsearch {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 9px;\n font-family: inherit;\n font-size: 12px;\n color: var(--text);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n outline: none;\n}\n.cv__langsearch:focus {\n border-color: var(--accent);\n}\n.cv__langlist {\n max-height: 288px;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: 6px;\n}\n.cv__langitem {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 6px 8px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: var(--text);\n font-family: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n transition:\n background 0.12s,\n color 0.12s;\n}\n.cv__langitem:hover {\n background: var(--surface-2);\n color: var(--accent);\n}\n.cv__langitem--on {\n color: var(--accent);\n}\n.cv__langitem-label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__langitem-check {\n flex-shrink: 0;\n color: var(--accent);\n}\n.cv__langempty {\n margin: 0;\n padding: 10px 8px;\n font-size: 12px;\n color: var(--faint);\n text-align: center;\n}\n\n.cv__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--code-bg);\n}\n\n/* Foldable per-line view -------------------------------------------------------------------------- */\n.cv__rows {\n min-width: max-content;\n padding: 14px 0;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n}\n.cv__row {\n display: flex;\n align-items: flex-start;\n}\n/* Gutter (fold chevron + line number) sticks to the left so it stays put while long lines scroll. */\n.cv__gut {\n position: sticky;\n left: 0;\n z-index: 1;\n flex-shrink: 0;\n display: flex;\n align-items: flex-start;\n background: var(--code-bg);\n user-select: none;\n}\n.cv__fold {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 16px;\n height: var(--fv-code-lh, 21px);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--faint);\n cursor: pointer;\n}\n.cv__fold--empty {\n cursor: default;\n}\n.cv__fold :deep(svg) {\n transition: transform 0.12s;\n}\n.cv__fold--open {\n transform: rotate(90deg);\n}\n.cv__fold:hover :deep(svg) {\n color: var(--accent);\n}\n.cv__ln {\n flex-shrink: 0;\n width: 48px;\n padding: 0 12px 0 4px;\n text-align: right;\n color: var(--faint);\n}\n.cv__code {\n flex: 1 1 auto;\n padding: 0 18px 0 14px;\n white-space: pre;\n color: var(--text);\n background: transparent;\n border: none;\n font-family: inherit;\n tab-size: 2;\n}\n.cv__ellipsis {\n align-self: center;\n margin: 0 0 0 2px;\n padding: 0 7px;\n font-family: inherit;\n line-height: 1.3;\n color: var(--muted);\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 5px;\n cursor: pointer;\n}\n.cv__ellipsis:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Huge-file blob fallback (mirrors the panel's original source layout) ----------------------------- */\n.cv__scroll--blob {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.cv__codewrap {\n display: flex;\n align-items: stretch;\n min-height: 100%;\n padding: 14px 0;\n}\n.cv__gutter {\n flex-shrink: 0;\n margin: 0;\n padding: 0 12px 0 16px;\n border-right: 1px solid var(--code-border);\n text-align: right;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--faint);\n user-select: none;\n white-space: pre;\n}\n.cv__codecol {\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n.cv__pre {\n margin: 0;\n padding: 0 18px 0 14px;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--text);\n white-space: pre;\n tab-size: 2;\n}\n.cv__pre code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"CodeView.js","names":[],"sources":["../../src/components/CodeView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'\nimport { escapeHtml, highlightCode, highlightLines } from '@/lib/highlight'\nimport { computeFoldRegions } from '@/lib/codefold'\nimport { canFormat, formatCode } from '@/lib/codeformat'\nimport { fileExt } from '@/lib/fileType'\nimport { hasAnsiSequences, parseAnsi } from '@/lib/ansi'\nimport { langLabel, searchLangs } from '@/lib/highlightLangs'\nimport Icon from '@/components/icons/Icon.vue'\nimport AnsiText from '@/components/AnsiText'\n\n/**\n * The side viewer's source-code view: a line-number gutter + syntax-highlighted code, with indentation-based\n * code FOLDING (click a chevron to collapse a block; see lib/codefold.ts), on-demand Prettier FORMATTING (the\n * 格式化 button, shown only for languages Prettier can parse — see lib/codeformat.ts; loaded lazily), and a\n * manual HIGHLIGHT-LANGUAGE picker (the 语言 button — auto-detection by extension/mime is often empty or wrong,\n * so any file can be re-colorized as JSON / XML / Python / … or forced to plain text; see lib/highlightLangs).\n * Used for every non-rendered file (plain text, code, or markdown/html/svg viewed as source). Highlighting is\n * split per line (see highlightLines) so rows can be hidden individually.\n *\n * Very large files (> {@link MAX_FOLD_LINES}) skip the per-line DOM and fall back to a single highlighted block\n * — folding a 10k-line file isn't worth tens of thousands of row elements, and such files mostly reach the\n * streaming log viewer anyway (see isLogFile).\n */\nconst props = defineProps<{ code: string; lang: string; name?: string }>()\n\nconst MAX_FOLD_LINES = 4000\n\n// Manual highlight-language override (null = use the auto-detected `props.lang`; '' = forced plain text). The\n// effective language feeds highlighting AND the 格式化 button (so \"force JSON, then format\" works). Reset when a\n// different file / detected language arrives (see the reset watch below).\nconst langOverride = ref<string | null>(null)\nconst effLang = computed(() => langOverride.value ?? props.lang)\n\n// Formatted output (null = showing the original). The view always renders `displayCode`.\nconst formatted = ref<string | null>(null)\nconst formatting = ref(false)\nconst formatErr = ref(false)\nconst displayCode = computed(() => formatted.value ?? props.code)\n\nconst ext = computed(() => fileExt(props.name ?? ''))\n// ANSI presentation is meaningful only in plain-text mode. Selecting a syntax language deliberately returns to\n// highlight.js; forcing \"纯文本\" on an otherwise-highlighted file enables ANSI restoration when controls exist.\nconst ansiDocument = computed(() =>\n !effLang.value && hasAnsiSequences(displayCode.value) ? parseAnsi(displayCode.value) : null,\n)\nconst ansiMode = computed(() => ansiDocument.value !== null)\nconst plainDisplayCode = computed(() => ansiDocument.value?.text ?? displayCode.value)\nconst canFmt = computed(() => !ansiMode.value && canFormat(ext.value, effLang.value))\n\nconst rawLines = computed(() => plainDisplayCode.value.split('\\n'))\nconst lineCount = computed(() => rawLines.value.length)\nconst foldable = computed(() => lineCount.value <= MAX_FOLD_LINES)\n\nconst lineHtml = computed(() =>\n ansiMode.value ? [] : highlightLines(displayCode.value, effLang.value),\n)\nconst regions = computed(() => computeFoldRegions(plainDisplayCode.value))\nconst folded = reactive(new Set<number>())\n\n// A new file (or a re-detected language) resets the formatting toggle AND the manual language override; any\n// change to the shown code clears stale fold state.\nwatch(\n () => [props.code, props.lang],\n () => {\n formatted.value = null\n formatErr.value = false\n langOverride.value = null\n },\n)\nwatch(displayCode, () => folded.clear())\n\n// ── highlight-language picker ────────────────────────────────────────────────────────────────────\nconst langMenuOpen = ref(false)\nconst langMenuRef = ref<HTMLElement | null>(null)\nconst langQuery = ref('')\nconst langResults = computed(() => searchLangs(langQuery.value))\n\nfunction chooseLang(id: string): void {\n langOverride.value = id\n langMenuOpen.value = false\n langQuery.value = ''\n}\n\n// Close the menu on an outside click or Esc — listeners live only while it's open (SSR-guarded). The toggle\n// button + popup both sit inside langMenuRef, so a click on either is \"inside\" and never self-closes.\nfunction onLangDocPointer(e: MouseEvent): void {\n if (langMenuRef.value && !langMenuRef.value.contains(e.target as Node)) langMenuOpen.value = false\n}\nfunction onLangKey(e: KeyboardEvent): void {\n if (e.key === 'Escape') langMenuOpen.value = false\n}\nwatch(langMenuOpen, (on) => {\n if (typeof document === 'undefined') return\n if (on) {\n document.addEventListener('click', onLangDocPointer)\n document.addEventListener('keydown', onLangKey)\n } else {\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n }\n})\nonBeforeUnmount(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n})\n\nlet errTimer: ReturnType<typeof setTimeout> | undefined\nasync function toggleFormat(): Promise<void> {\n if (formatted.value != null) {\n formatted.value = null // revert to the original\n return\n }\n if (formatting.value) return\n formatting.value = true\n formatErr.value = false\n try {\n formatted.value = await formatCode(props.code, { ext: ext.value, lang: effLang.value })\n } catch {\n formatErr.value = true\n clearTimeout(errTimer)\n errTimer = setTimeout(() => (formatErr.value = false), 2200)\n } finally {\n formatting.value = false\n }\n}\n\n// Visible line indices, skipping the bodies of folded regions (and any nested folds within them).\nconst visible = computed(() => {\n const out: number[] = []\n const n = lineCount.value\n let i = 0\n while (i < n) {\n out.push(i)\n const end = folded.has(i) ? regions.value.get(i) : undefined\n i = end != null ? end + 1 : i + 1\n }\n return out\n})\n\nfunction toggle(i: number): void {\n if (folded.has(i)) folded.delete(i)\n else folded.add(i)\n}\n\n// Blob fallback for huge files: one highlighted block + a newline-joined gutter.\nconst blobHtml = computed(() =>\n effLang.value ? highlightCode(displayCode.value, effLang.value) : escapeHtml(displayCode.value),\n)\nconst blobGutter = computed(() =>\n Array.from({ length: lineCount.value }, (_, i) => i + 1).join('\\n'),\n)\n</script>\n\n<template>\n <div class=\"cv\">\n <!-- floating tools, top-right over the code: highlight-language picker + Prettier format toggle -->\n <div class=\"cv__tools\">\n <!-- highlight-language picker: force which grammar colorizes the source (auto-detect is often empty/wrong) -->\n <div ref=\"langMenuRef\" class=\"cv__lang\">\n <button\n type=\"button\"\n class=\"cv__toolbtn\"\n :class=\"{ 'cv__toolbtn--on': langMenuOpen }\"\n title=\"高亮语言 —— 选择用哪种语言着色(自动识别不准时手动指定)\"\n :aria-haspopup=\"true\"\n :aria-expanded=\"langMenuOpen\"\n @click=\"langMenuOpen = !langMenuOpen\"\n >\n <Icon name=\"code\" :size=\"12\" /><span>{{ langLabel(effLang) }}</span>\n </button>\n <div v-if=\"langMenuOpen\" class=\"cv__langpop\" role=\"menu\">\n <input\n v-model=\"langQuery\"\n class=\"cv__langsearch\"\n type=\"text\"\n placeholder=\"搜索语言…\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n <div class=\"cv__langlist\">\n <button\n v-if=\"!langQuery\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === '' }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === ''\"\n @click=\"chooseLang('')\"\n >\n <span class=\"cv__langitem-label\">纯文本(无高亮)</span>\n <Icon v-if=\"effLang === ''\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <button\n v-for=\"l in langResults\"\n :key=\"l.id\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === l.id }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === l.id\"\n @click=\"chooseLang(l.id)\"\n >\n <span class=\"cv__langitem-label\">{{ l.label }}</span>\n <Icon v-if=\"effLang === l.id\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <p v-if=\"langQuery && !langResults.length\" class=\"cv__langempty\">无匹配语言</p>\n </div>\n </div>\n </div>\n\n <!-- format toggle (only for languages Prettier can parse) -->\n <button\n v-if=\"canFmt\"\n type=\"button\"\n class=\"cv__toolbtn cv__fmt\"\n :class=\"{ 'cv__toolbtn--on': formatted != null, 'cv__fmt--err': formatErr }\"\n :disabled=\"formatting\"\n :title=\"\n formatErr\n ? '格式化失败:源码可能无法解析'\n : formatted != null\n ? '还原为原始格式'\n : '用 Prettier 格式化'\n \"\n @click=\"toggleFormat\"\n >\n {{\n formatErr\n ? '格式化失败'\n : formatting\n ? '格式化中…'\n : formatted != null\n ? '还原'\n : '格式化'\n }}\n </button>\n </div>\n\n <!-- huge file: single highlighted block (no folding) -->\n <div v-if=\"!foldable\" class=\"cv__scroll cv__scroll--blob\">\n <div class=\"cv__codewrap\">\n <pre class=\"cv__gutter\" aria-hidden=\"true\">{{ blobGutter }}</pre>\n <div class=\"cv__codecol\">\n <pre\n class=\"cv__pre\"\n ><code v-if=\"ansiDocument\" class=\"hljs\"><AnsiText :lines=\"ansiDocument.lines\" /></code><code v-else class=\"hljs\" v-html=\"blobHtml\" /></pre>\n </div>\n </div>\n </div>\n\n <!-- foldable: one row per visible line, with a fold chevron on block headers -->\n <div v-else class=\"cv__scroll\">\n <div class=\"cv__rows\">\n <div v-for=\"i in visible\" :key=\"i\" class=\"cv__row\">\n <span class=\"cv__gut\">\n <button\n v-if=\"regions.has(i)\"\n type=\"button\"\n class=\"cv__fold\"\n :title=\"folded.has(i) ? '展开' : '折叠'\"\n @click=\"toggle(i)\"\n >\n <Icon name=\"chevronRight\" :size=\"12\" :class=\"{ 'cv__fold--open': !folded.has(i) }\" />\n </button>\n <span v-else class=\"cv__fold cv__fold--empty\" />\n <span class=\"cv__ln\">{{ i + 1 }}</span>\n </span>\n <code class=\"cv__code hljs\">\n <AnsiText v-if=\"ansiDocument\" :line=\"ansiDocument.lines[i] ?? ''\" />\n <span v-else v-html=\"lineHtml[i]\" />\n </code>\n <button\n v-if=\"folded.has(i)\"\n type=\"button\"\n class=\"cv__ellipsis\"\n :title=\"`展开折叠的 ${(regions.get(i) ?? i) - i} 行`\"\n @click=\"toggle(i)\"\n >\n ⋯\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.cv {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* Floating tools bar — top-right, above the code (highlight-language picker + format toggle). */\n.cv__tools {\n position: absolute;\n top: 8px;\n right: 14px;\n z-index: 3;\n display: flex;\n align-items: flex-start;\n gap: 6px;\n}\n\n/* Shared small pill used by both floating tools. */\n.cv__toolbtn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n max-width: 190px;\n padding: 3px 10px;\n font-family: inherit;\n font-size: 11.5px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n cursor: pointer;\n box-shadow: var(--shadow);\n transition:\n background 0.15s,\n color 0.15s,\n border-color 0.15s;\n}\n.cv__toolbtn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.cv__toolbtn span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__toolbtn--on {\n color: #fff;\n background: var(--accent);\n border-color: var(--accent);\n}\n.cv__toolbtn--on:hover {\n color: #fff;\n}\n\n/* Format toggle specifics (disabled + error states). */\n.cv__fmt:disabled {\n cursor: default;\n opacity: 0.7;\n}\n.cv__fmt--err {\n color: var(--danger);\n border-color: var(--danger);\n}\n\n/* Highlight-language picker dropdown (anchored under its pill, right-aligned). */\n.cv__lang {\n position: relative;\n display: inline-flex;\n}\n.cv__langpop {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 4;\n width: 224px;\n padding: 7px;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 10px;\n box-shadow: var(--shadow);\n}\n.cv__langsearch {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 9px;\n font-family: inherit;\n font-size: 12px;\n color: var(--text);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n outline: none;\n}\n.cv__langsearch:focus {\n border-color: var(--accent);\n}\n.cv__langlist {\n max-height: 288px;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: 6px;\n}\n.cv__langitem {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 6px 8px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: var(--text);\n font-family: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n transition:\n background 0.12s,\n color 0.12s;\n}\n.cv__langitem:hover {\n background: var(--surface-2);\n color: var(--accent);\n}\n.cv__langitem--on {\n color: var(--accent);\n}\n.cv__langitem-label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__langitem-check {\n flex-shrink: 0;\n color: var(--accent);\n}\n.cv__langempty {\n margin: 0;\n padding: 10px 8px;\n font-size: 12px;\n color: var(--faint);\n text-align: center;\n}\n\n.cv__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--code-bg);\n}\n\n/* Foldable per-line view -------------------------------------------------------------------------- */\n.cv__rows {\n min-width: max-content;\n padding: 14px 0;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n}\n.cv__row {\n display: flex;\n align-items: flex-start;\n}\n/* Gutter (fold chevron + line number) sticks to the left so it stays put while long lines scroll. */\n.cv__gut {\n position: sticky;\n left: 0;\n z-index: 1;\n flex-shrink: 0;\n display: flex;\n align-items: flex-start;\n background: var(--code-bg);\n user-select: none;\n}\n.cv__fold {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 16px;\n height: var(--fv-code-lh, 21px);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--faint);\n cursor: pointer;\n}\n.cv__fold--empty {\n cursor: default;\n}\n.cv__fold :deep(svg) {\n transition: transform 0.12s;\n}\n.cv__fold--open {\n transform: rotate(90deg);\n}\n.cv__fold:hover :deep(svg) {\n color: var(--accent);\n}\n.cv__ln {\n flex-shrink: 0;\n width: 48px;\n padding: 0 12px 0 4px;\n text-align: right;\n color: var(--faint);\n}\n.cv__code {\n flex: 1 1 auto;\n padding: 0 18px 0 14px;\n white-space: pre;\n color: var(--text);\n background: transparent;\n border: none;\n font-family: inherit;\n tab-size: 2;\n}\n.cv__ellipsis {\n align-self: center;\n margin: 0 0 0 2px;\n padding: 0 7px;\n font-family: inherit;\n line-height: 1.3;\n color: var(--muted);\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 5px;\n cursor: pointer;\n}\n.cv__ellipsis:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Huge-file blob fallback (mirrors the panel's original source layout) ----------------------------- */\n.cv__scroll--blob {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.cv__codewrap {\n display: flex;\n align-items: stretch;\n min-height: 100%;\n padding: 14px 0;\n}\n.cv__gutter {\n flex-shrink: 0;\n margin: 0;\n padding: 0 12px 0 16px;\n border-right: 1px solid var(--code-border);\n text-align: right;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--faint);\n user-select: none;\n white-space: pre;\n}\n.cv__codecol {\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n.cv__pre {\n margin: 0;\n padding: 0 18px 0 14px;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--text);\n white-space: pre;\n tab-size: 2;\n}\n.cv__pre code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n</style>\n"],"mappings":""}
@@ -1,45 +1,50 @@
1
1
  import { fileExt as e } from "../lib/fileType.js";
2
- import { escapeHtml as t, highlightCode as n, highlightLines as ee } from "../lib/highlight.js";
3
- import { computeFoldRegions as te } from "../lib/codefold.js";
4
- import { canFormat as ne, formatCode as r } from "../lib/codeformat.js";
5
- import { langLabel as re, searchLangs as ie } from "../lib/highlightLangs.js";
2
+ import { hasAnsiSequences as t, parseAnsi as n } from "../lib/ansi.js";
3
+ import { escapeHtml as ee, highlightCode as r, highlightLines as te } from "../lib/highlight.js";
4
+ import { computeFoldRegions as ne } from "../lib/codefold.js";
5
+ import { canFormat as re, formatCode as ie } from "../lib/codeformat.js";
6
+ import { langLabel as ae, searchLangs as oe } from "../lib/highlightLangs.js";
6
7
  import i from "./icons/Icon.js";
7
- import { Fragment as a, computed as o, createBlock as s, createCommentVNode as c, createElementBlock as l, createElementVNode as u, createVNode as d, defineComponent as f, normalizeClass as p, onBeforeUnmount as m, openBlock as h, reactive as ae, ref as g, renderList as _, toDisplayString as v, unref as oe, vModelText as se, watch as y, withDirectives as ce } from "vue";
8
+ import a from "./AnsiText.js";
9
+ import { Fragment as o, computed as s, createBlock as c, createCommentVNode as l, createElementBlock as u, createElementVNode as d, createVNode as f, defineComponent as p, normalizeClass as m, onBeforeUnmount as se, openBlock as h, reactive as ce, ref as g, renderList as _, toDisplayString as v, unref as y, vModelText as le, watch as b, withDirectives as ue } from "vue";
8
10
  //#region src/components/CodeView.vue?vue&type=script&setup=true&lang.ts
9
- var le = { class: "cv" }, ue = { class: "cv__tools" }, de = ["aria-expanded"], fe = {
11
+ var de = { class: "cv" }, fe = { class: "cv__tools" }, pe = ["aria-expanded"], me = {
10
12
  key: 0,
11
13
  class: "cv__langpop",
12
14
  role: "menu"
13
- }, pe = { class: "cv__langlist" }, me = ["aria-checked"], he = ["aria-checked", "onClick"], ge = { class: "cv__langitem-label" }, _e = {
15
+ }, he = { class: "cv__langlist" }, ge = ["aria-checked"], _e = ["aria-checked", "onClick"], ve = { class: "cv__langitem-label" }, ye = {
14
16
  key: 1,
15
17
  class: "cv__langempty"
16
- }, ve = ["disabled", "title"], ye = {
18
+ }, be = ["disabled", "title"], xe = {
17
19
  key: 0,
18
20
  class: "cv__scroll cv__scroll--blob"
19
- }, b = { class: "cv__codewrap" }, x = {
21
+ }, Se = { class: "cv__codewrap" }, Ce = {
20
22
  class: "cv__gutter",
21
23
  "aria-hidden": "true"
22
- }, S = { class: "cv__codecol" }, C = { class: "cv__pre" }, w = ["innerHTML"], T = {
24
+ }, x = { class: "cv__codecol" }, S = { class: "cv__pre" }, C = {
25
+ key: 0,
26
+ class: "hljs"
27
+ }, w = ["innerHTML"], T = {
23
28
  key: 1,
24
29
  class: "cv__scroll"
25
30
  }, E = { class: "cv__rows" }, D = { class: "cv__gut" }, O = ["title", "onClick"], k = {
26
31
  key: 1,
27
32
  class: "cv__fold cv__fold--empty"
28
- }, A = { class: "cv__ln" }, j = ["innerHTML"], M = ["title", "onClick"], N = 4e3, P = /*@__PURE__*/ f({
33
+ }, A = { class: "cv__ln" }, we = { class: "cv__code hljs" }, Te = ["innerHTML"], Ee = ["title", "onClick"], De = 4e3, j = /*@__PURE__*/ p({
29
34
  __name: "CodeView",
30
35
  props: {
31
36
  code: {},
32
37
  lang: {},
33
38
  name: {}
34
39
  },
35
- setup(f) {
36
- let P = f, F = g(null), I = o(() => F.value ?? P.lang), L = g(null), R = g(!1), z = g(!1), B = o(() => L.value ?? P.code), V = o(() => e(P.name ?? "")), be = o(() => ne(V.value, I.value)), xe = o(() => B.value.split("\n")), H = o(() => xe.value.length), Se = o(() => H.value <= N), Ce = o(() => ee(B.value, I.value)), U = o(() => te(B.value)), W = ae(/* @__PURE__ */ new Set());
37
- y(() => [P.code, P.lang], () => {
38
- L.value = null, z.value = !1, F.value = null;
39
- }), y(B, () => W.clear());
40
- let G = g(!1), K = g(null), q = g(""), J = o(() => ie(q.value));
40
+ setup(p) {
41
+ let j = p, M = g(null), N = s(() => M.value ?? j.lang), P = g(null), F = g(!1), I = g(!1), L = s(() => P.value ?? j.code), R = s(() => e(j.name ?? "")), z = s(() => !N.value && t(L.value) ? n(L.value) : null), B = s(() => z.value !== null), V = s(() => z.value?.text ?? L.value), Oe = s(() => !B.value && re(R.value, N.value)), ke = s(() => V.value.split("\n")), H = s(() => ke.value.length), Ae = s(() => H.value <= De), je = s(() => B.value ? [] : te(L.value, N.value)), U = s(() => ne(V.value)), W = ce(/* @__PURE__ */ new Set());
42
+ b(() => [j.code, j.lang], () => {
43
+ P.value = null, I.value = !1, M.value = null;
44
+ }), b(L, () => W.clear());
45
+ let G = g(!1), K = g(null), q = g(""), J = s(() => oe(q.value));
41
46
  function Y(e) {
42
- F.value = e, G.value = !1, q.value = "";
47
+ M.value = e, G.value = !1, q.value = "";
43
48
  }
44
49
  function X(e) {
45
50
  K.value && !K.value.contains(e.target) && (G.value = !1);
@@ -47,32 +52,32 @@ var le = { class: "cv" }, ue = { class: "cv__tools" }, de = ["aria-expanded"], f
47
52
  function Z(e) {
48
53
  e.key === "Escape" && (G.value = !1);
49
54
  }
50
- y(G, (e) => {
55
+ b(G, (e) => {
51
56
  typeof document > "u" || (e ? (document.addEventListener("click", X), document.addEventListener("keydown", Z)) : (document.removeEventListener("click", X), document.removeEventListener("keydown", Z)));
52
- }), m(() => {
57
+ }), se(() => {
53
58
  typeof document > "u" || (document.removeEventListener("click", X), document.removeEventListener("keydown", Z));
54
59
  });
55
60
  let Q;
56
- async function we() {
57
- if (L.value != null) {
58
- L.value = null;
61
+ async function Me() {
62
+ if (P.value != null) {
63
+ P.value = null;
59
64
  return;
60
65
  }
61
- if (!R.value) {
62
- R.value = !0, z.value = !1;
66
+ if (!F.value) {
67
+ F.value = !0, I.value = !1;
63
68
  try {
64
- L.value = await r(P.code, {
65
- ext: V.value,
66
- lang: I.value
69
+ P.value = await ie(j.code, {
70
+ ext: R.value,
71
+ lang: N.value
67
72
  });
68
73
  } catch {
69
- z.value = !0, clearTimeout(Q), Q = setTimeout(() => z.value = !1, 2200);
74
+ I.value = !0, clearTimeout(Q), Q = setTimeout(() => I.value = !1, 2200);
70
75
  } finally {
71
- R.value = !1;
76
+ F.value = !1;
72
77
  }
73
78
  }
74
79
  }
75
- let Te = o(() => {
80
+ let Ne = s(() => {
76
81
  let e = [], t = H.value, n = 0;
77
82
  for (; n < t;) {
78
83
  e.push(n);
@@ -84,99 +89,103 @@ var le = { class: "cv" }, ue = { class: "cv__tools" }, de = ["aria-expanded"], f
84
89
  function $(e) {
85
90
  W.has(e) ? W.delete(e) : W.add(e);
86
91
  }
87
- let Ee = o(() => I.value ? n(B.value, I.value) : t(B.value)), De = o(() => Array.from({ length: H.value }, (e, t) => t + 1).join("\n"));
88
- return (e, t) => (h(), l("div", le, [u("div", ue, [u("div", {
92
+ let Pe = s(() => N.value ? r(L.value, N.value) : ee(L.value)), Fe = s(() => Array.from({ length: H.value }, (e, t) => t + 1).join("\n"));
93
+ return (e, t) => (h(), u("div", de, [d("div", fe, [d("div", {
89
94
  ref_key: "langMenuRef",
90
95
  ref: K,
91
96
  class: "cv__lang"
92
- }, [u("button", {
97
+ }, [d("button", {
93
98
  type: "button",
94
- class: p(["cv__toolbtn", { "cv__toolbtn--on": G.value }]),
99
+ class: m(["cv__toolbtn", { "cv__toolbtn--on": G.value }]),
95
100
  title: "高亮语言 —— 选择用哪种语言着色(自动识别不准时手动指定)",
96
101
  "aria-haspopup": !0,
97
102
  "aria-expanded": G.value,
98
103
  onClick: t[0] ||= (e) => G.value = !G.value
99
- }, [d(i, {
104
+ }, [f(i, {
100
105
  name: "code",
101
106
  size: 12
102
- }), u("span", null, v(oe(re)(I.value)), 1)], 10, de), G.value ? (h(), l("div", fe, [ce(u("input", {
107
+ }), d("span", null, v(y(ae)(N.value)), 1)], 10, pe), G.value ? (h(), u("div", me, [ue(d("input", {
103
108
  "onUpdate:modelValue": t[1] ||= (e) => q.value = e,
104
109
  class: "cv__langsearch",
105
110
  type: "text",
106
111
  placeholder: "搜索语言…",
107
112
  spellcheck: "false",
108
113
  autocomplete: "off"
109
- }, null, 512), [[se, q.value]]), u("div", pe, [
110
- q.value ? c("", !0) : (h(), l("button", {
114
+ }, null, 512), [[le, q.value]]), d("div", he, [
115
+ q.value ? l("", !0) : (h(), u("button", {
111
116
  key: 0,
112
117
  type: "button",
113
- class: p(["cv__langitem", { "cv__langitem--on": I.value === "" }]),
118
+ class: m(["cv__langitem", { "cv__langitem--on": N.value === "" }]),
114
119
  role: "menuitemradio",
115
- "aria-checked": I.value === "",
120
+ "aria-checked": N.value === "",
116
121
  onClick: t[2] ||= (e) => Y("")
117
- }, [t[3] ||= u("span", { class: "cv__langitem-label" }, "纯文本(无高亮)", -1), I.value === "" ? (h(), s(i, {
122
+ }, [t[3] ||= d("span", { class: "cv__langitem-label" }, "纯文本(无高亮)", -1), N.value === "" ? (h(), c(i, {
118
123
  key: 0,
119
124
  name: "check",
120
125
  size: 14,
121
126
  class: "cv__langitem-check"
122
- })) : c("", !0)], 10, me)),
123
- (h(!0), l(a, null, _(J.value, (e) => (h(), l("button", {
127
+ })) : l("", !0)], 10, ge)),
128
+ (h(!0), u(o, null, _(J.value, (e) => (h(), u("button", {
124
129
  key: e.id,
125
130
  type: "button",
126
- class: p(["cv__langitem", { "cv__langitem--on": I.value === e.id }]),
131
+ class: m(["cv__langitem", { "cv__langitem--on": N.value === e.id }]),
127
132
  role: "menuitemradio",
128
- "aria-checked": I.value === e.id,
133
+ "aria-checked": N.value === e.id,
129
134
  onClick: (t) => Y(e.id)
130
- }, [u("span", ge, v(e.label), 1), I.value === e.id ? (h(), s(i, {
135
+ }, [d("span", ve, v(e.label), 1), N.value === e.id ? (h(), c(i, {
131
136
  key: 0,
132
137
  name: "check",
133
138
  size: 14,
134
139
  class: "cv__langitem-check"
135
- })) : c("", !0)], 10, he))), 128)),
136
- q.value && !J.value.length ? (h(), l("p", _e, "无匹配语言")) : c("", !0)
137
- ])])) : c("", !0)], 512), be.value ? (h(), l("button", {
140
+ })) : l("", !0)], 10, _e))), 128)),
141
+ q.value && !J.value.length ? (h(), u("p", ye, "无匹配语言")) : l("", !0)
142
+ ])])) : l("", !0)], 512), Oe.value ? (h(), u("button", {
138
143
  key: 0,
139
144
  type: "button",
140
- class: p(["cv__toolbtn cv__fmt", {
141
- "cv__toolbtn--on": L.value != null,
142
- "cv__fmt--err": z.value
145
+ class: m(["cv__toolbtn cv__fmt", {
146
+ "cv__toolbtn--on": P.value != null,
147
+ "cv__fmt--err": I.value
143
148
  }]),
144
- disabled: R.value,
145
- title: z.value ? "格式化失败:源码可能无法解析" : L.value == null ? "用 Prettier 格式化" : "还原为原始格式",
146
- onClick: we
147
- }, v(z.value ? "格式化失败" : R.value ? "格式化中…" : L.value == null ? "格式化" : "还原"), 11, ve)) : c("", !0)]), Se.value ? (h(), l("div", T, [u("div", E, [(h(!0), l(a, null, _(Te.value, (e) => (h(), l("div", {
149
+ disabled: F.value,
150
+ title: I.value ? "格式化失败:源码可能无法解析" : P.value == null ? "用 Prettier 格式化" : "还原为原始格式",
151
+ onClick: Me
152
+ }, v(I.value ? "格式化失败" : F.value ? "格式化中…" : P.value == null ? "格式化" : "还原"), 11, be)) : l("", !0)]), Ae.value ? (h(), u("div", T, [d("div", E, [(h(!0), u(o, null, _(Ne.value, (e) => (h(), u("div", {
148
153
  key: e,
149
154
  class: "cv__row"
150
155
  }, [
151
- u("span", D, [U.value.has(e) ? (h(), l("button", {
156
+ d("span", D, [U.value.has(e) ? (h(), u("button", {
152
157
  key: 0,
153
158
  type: "button",
154
159
  class: "cv__fold",
155
160
  title: W.has(e) ? "展开" : "折叠",
156
161
  onClick: (t) => $(e)
157
- }, [d(i, {
162
+ }, [f(i, {
158
163
  name: "chevronRight",
159
164
  size: 12,
160
- class: p({ "cv__fold--open": !W.has(e) })
161
- }, null, 8, ["class"])], 8, O)) : (h(), l("span", k)), u("span", A, v(e + 1), 1)]),
162
- u("code", {
163
- class: "cv__code hljs",
164
- innerHTML: Ce.value[e]
165
- }, null, 8, j),
166
- W.has(e) ? (h(), l("button", {
165
+ class: m({ "cv__fold--open": !W.has(e) })
166
+ }, null, 8, ["class"])], 8, O)) : (h(), u("span", k)), d("span", A, v(e + 1), 1)]),
167
+ d("code", we, [z.value ? (h(), c(y(a), {
168
+ key: 0,
169
+ line: z.value.lines[e] ?? ""
170
+ }, null, 8, ["line"])) : (h(), u("span", {
171
+ key: 1,
172
+ innerHTML: je.value[e]
173
+ }, null, 8, Te))]),
174
+ W.has(e) ? (h(), u("button", {
167
175
  key: 0,
168
176
  type: "button",
169
177
  class: "cv__ellipsis",
170
178
  title: `展开折叠的 ${(U.value.get(e) ?? e) - e} 行`,
171
179
  onClick: (t) => $(e)
172
- }, " ⋯ ", 8, M)) : c("", !0)
173
- ]))), 128))])])) : (h(), l("div", ye, [u("div", b, [u("pre", x, v(De.value), 1), u("div", S, [u("pre", C, [u("code", {
180
+ }, " ⋯ ", 8, Ee)) : l("", !0)
181
+ ]))), 128))])])) : (h(), u("div", xe, [d("div", Se, [d("pre", Ce, v(Fe.value), 1), d("div", x, [d("pre", S, [z.value ? (h(), u("code", C, [f(y(a), { lines: z.value.lines }, null, 8, ["lines"])])) : (h(), u("code", {
182
+ key: 1,
174
183
  class: "hljs",
175
- innerHTML: Ee.value
176
- }, null, 8, w)])])])]))]));
184
+ innerHTML: Pe.value
185
+ }, null, 8, w))])])])]))]));
177
186
  }
178
187
  });
179
188
  //#endregion
180
- export { P as default };
189
+ export { j as default };
181
190
 
182
191
  //# sourceMappingURL=CodeView.vue_vue_type_script_setup_true_lang.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CodeView.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/CodeView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'\nimport { escapeHtml, highlightCode, highlightLines } from '@/lib/highlight'\nimport { computeFoldRegions } from '@/lib/codefold'\nimport { canFormat, formatCode } from '@/lib/codeformat'\nimport { fileExt } from '@/lib/fileType'\nimport { langLabel, searchLangs } from '@/lib/highlightLangs'\nimport Icon from '@/components/icons/Icon.vue'\n\n/**\n * The side viewer's source-code view: a line-number gutter + syntax-highlighted code, with indentation-based\n * code FOLDING (click a chevron to collapse a block; see lib/codefold.ts), on-demand Prettier FORMATTING (the\n * 格式化 button, shown only for languages Prettier can parse — see lib/codeformat.ts; loaded lazily), and a\n * manual HIGHLIGHT-LANGUAGE picker (the 语言 button — auto-detection by extension/mime is often empty or wrong,\n * so any file can be re-colorized as JSON / XML / Python / … or forced to plain text; see lib/highlightLangs).\n * Used for every non-rendered file (plain text, code, or markdown/html/svg viewed as source). Highlighting is\n * split per line (see highlightLines) so rows can be hidden individually.\n *\n * Very large files (> {@link MAX_FOLD_LINES}) skip the per-line DOM and fall back to a single highlighted block\n * — folding a 10k-line file isn't worth tens of thousands of row elements, and such files mostly reach the\n * streaming log viewer anyway (see isLogFile).\n */\nconst props = defineProps<{ code: string; lang: string; name?: string }>()\n\nconst MAX_FOLD_LINES = 4000\n\n// Manual highlight-language override (null = use the auto-detected `props.lang`; '' = forced plain text). The\n// effective language feeds highlighting AND the 格式化 button (so \"force JSON, then format\" works). Reset when a\n// different file / detected language arrives (see the reset watch below).\nconst langOverride = ref<string | null>(null)\nconst effLang = computed(() => langOverride.value ?? props.lang)\n\n// Formatted output (null = showing the original). The view always renders `displayCode`.\nconst formatted = ref<string | null>(null)\nconst formatting = ref(false)\nconst formatErr = ref(false)\nconst displayCode = computed(() => formatted.value ?? props.code)\n\nconst ext = computed(() => fileExt(props.name ?? ''))\nconst canFmt = computed(() => canFormat(ext.value, effLang.value))\n\nconst rawLines = computed(() => displayCode.value.split('\\n'))\nconst lineCount = computed(() => rawLines.value.length)\nconst foldable = computed(() => lineCount.value <= MAX_FOLD_LINES)\n\nconst lineHtml = computed(() => highlightLines(displayCode.value, effLang.value))\nconst regions = computed(() => computeFoldRegions(displayCode.value))\nconst folded = reactive(new Set<number>())\n\n// A new file (or a re-detected language) resets the formatting toggle AND the manual language override; any\n// change to the shown code clears stale fold state.\nwatch(\n () => [props.code, props.lang],\n () => {\n formatted.value = null\n formatErr.value = false\n langOverride.value = null\n },\n)\nwatch(displayCode, () => folded.clear())\n\n// ── highlight-language picker ────────────────────────────────────────────────────────────────────\nconst langMenuOpen = ref(false)\nconst langMenuRef = ref<HTMLElement | null>(null)\nconst langQuery = ref('')\nconst langResults = computed(() => searchLangs(langQuery.value))\n\nfunction chooseLang(id: string): void {\n langOverride.value = id\n langMenuOpen.value = false\n langQuery.value = ''\n}\n\n// Close the menu on an outside click or Esc — listeners live only while it's open (SSR-guarded). The toggle\n// button + popup both sit inside langMenuRef, so a click on either is \"inside\" and never self-closes.\nfunction onLangDocPointer(e: MouseEvent): void {\n if (langMenuRef.value && !langMenuRef.value.contains(e.target as Node)) langMenuOpen.value = false\n}\nfunction onLangKey(e: KeyboardEvent): void {\n if (e.key === 'Escape') langMenuOpen.value = false\n}\nwatch(langMenuOpen, (on) => {\n if (typeof document === 'undefined') return\n if (on) {\n document.addEventListener('click', onLangDocPointer)\n document.addEventListener('keydown', onLangKey)\n } else {\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n }\n})\nonBeforeUnmount(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n})\n\nlet errTimer: ReturnType<typeof setTimeout> | undefined\nasync function toggleFormat(): Promise<void> {\n if (formatted.value != null) {\n formatted.value = null // revert to the original\n return\n }\n if (formatting.value) return\n formatting.value = true\n formatErr.value = false\n try {\n formatted.value = await formatCode(props.code, { ext: ext.value, lang: effLang.value })\n } catch {\n formatErr.value = true\n clearTimeout(errTimer)\n errTimer = setTimeout(() => (formatErr.value = false), 2200)\n } finally {\n formatting.value = false\n }\n}\n\n// Visible line indices, skipping the bodies of folded regions (and any nested folds within them).\nconst visible = computed(() => {\n const out: number[] = []\n const n = lineCount.value\n let i = 0\n while (i < n) {\n out.push(i)\n const end = folded.has(i) ? regions.value.get(i) : undefined\n i = end != null ? end + 1 : i + 1\n }\n return out\n})\n\nfunction toggle(i: number): void {\n if (folded.has(i)) folded.delete(i)\n else folded.add(i)\n}\n\n// Blob fallback for huge files: one highlighted block + a newline-joined gutter.\nconst blobHtml = computed(() =>\n effLang.value ? highlightCode(displayCode.value, effLang.value) : escapeHtml(displayCode.value),\n)\nconst blobGutter = computed(() =>\n Array.from({ length: lineCount.value }, (_, i) => i + 1).join('\\n'),\n)\n</script>\n\n<template>\n <div class=\"cv\">\n <!-- floating tools, top-right over the code: highlight-language picker + Prettier format toggle -->\n <div class=\"cv__tools\">\n <!-- highlight-language picker: force which grammar colorizes the source (auto-detect is often empty/wrong) -->\n <div ref=\"langMenuRef\" class=\"cv__lang\">\n <button\n type=\"button\"\n class=\"cv__toolbtn\"\n :class=\"{ 'cv__toolbtn--on': langMenuOpen }\"\n title=\"高亮语言 —— 选择用哪种语言着色(自动识别不准时手动指定)\"\n :aria-haspopup=\"true\"\n :aria-expanded=\"langMenuOpen\"\n @click=\"langMenuOpen = !langMenuOpen\"\n >\n <Icon name=\"code\" :size=\"12\" /><span>{{ langLabel(effLang) }}</span>\n </button>\n <div v-if=\"langMenuOpen\" class=\"cv__langpop\" role=\"menu\">\n <input\n v-model=\"langQuery\"\n class=\"cv__langsearch\"\n type=\"text\"\n placeholder=\"搜索语言…\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n <div class=\"cv__langlist\">\n <button\n v-if=\"!langQuery\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === '' }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === ''\"\n @click=\"chooseLang('')\"\n >\n <span class=\"cv__langitem-label\">纯文本(无高亮)</span>\n <Icon v-if=\"effLang === ''\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <button\n v-for=\"l in langResults\"\n :key=\"l.id\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === l.id }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === l.id\"\n @click=\"chooseLang(l.id)\"\n >\n <span class=\"cv__langitem-label\">{{ l.label }}</span>\n <Icon v-if=\"effLang === l.id\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <p v-if=\"langQuery && !langResults.length\" class=\"cv__langempty\">无匹配语言</p>\n </div>\n </div>\n </div>\n\n <!-- format toggle (only for languages Prettier can parse) -->\n <button\n v-if=\"canFmt\"\n type=\"button\"\n class=\"cv__toolbtn cv__fmt\"\n :class=\"{ 'cv__toolbtn--on': formatted != null, 'cv__fmt--err': formatErr }\"\n :disabled=\"formatting\"\n :title=\"\n formatErr\n ? '格式化失败:源码可能无法解析'\n : formatted != null\n ? '还原为原始格式'\n : '用 Prettier 格式化'\n \"\n @click=\"toggleFormat\"\n >\n {{\n formatErr\n ? '格式化失败'\n : formatting\n ? '格式化中…'\n : formatted != null\n ? '还原'\n : '格式化'\n }}\n </button>\n </div>\n\n <!-- huge file: single highlighted block (no folding) -->\n <div v-if=\"!foldable\" class=\"cv__scroll cv__scroll--blob\">\n <div class=\"cv__codewrap\">\n <pre class=\"cv__gutter\" aria-hidden=\"true\">{{ blobGutter }}</pre>\n <div class=\"cv__codecol\">\n <pre class=\"cv__pre\"><code class=\"hljs\" v-html=\"blobHtml\" /></pre>\n </div>\n </div>\n </div>\n\n <!-- foldable: one row per visible line, with a fold chevron on block headers -->\n <div v-else class=\"cv__scroll\">\n <div class=\"cv__rows\">\n <div v-for=\"i in visible\" :key=\"i\" class=\"cv__row\">\n <span class=\"cv__gut\">\n <button\n v-if=\"regions.has(i)\"\n type=\"button\"\n class=\"cv__fold\"\n :title=\"folded.has(i) ? '展开' : '折叠'\"\n @click=\"toggle(i)\"\n >\n <Icon name=\"chevronRight\" :size=\"12\" :class=\"{ 'cv__fold--open': !folded.has(i) }\" />\n </button>\n <span v-else class=\"cv__fold cv__fold--empty\" />\n <span class=\"cv__ln\">{{ i + 1 }}</span>\n </span>\n <code class=\"cv__code hljs\" v-html=\"lineHtml[i]\" />\n <button\n v-if=\"folded.has(i)\"\n type=\"button\"\n class=\"cv__ellipsis\"\n :title=\"`展开折叠的 ${(regions.get(i) ?? i) - i} 行`\"\n @click=\"toggle(i)\"\n >\n ⋯\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.cv {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* Floating tools bar — top-right, above the code (highlight-language picker + format toggle). */\n.cv__tools {\n position: absolute;\n top: 8px;\n right: 14px;\n z-index: 3;\n display: flex;\n align-items: flex-start;\n gap: 6px;\n}\n\n/* Shared small pill used by both floating tools. */\n.cv__toolbtn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n max-width: 190px;\n padding: 3px 10px;\n font-family: inherit;\n font-size: 11.5px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n cursor: pointer;\n box-shadow: var(--shadow);\n transition:\n background 0.15s,\n color 0.15s,\n border-color 0.15s;\n}\n.cv__toolbtn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.cv__toolbtn span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__toolbtn--on {\n color: #fff;\n background: var(--accent);\n border-color: var(--accent);\n}\n.cv__toolbtn--on:hover {\n color: #fff;\n}\n\n/* Format toggle specifics (disabled + error states). */\n.cv__fmt:disabled {\n cursor: default;\n opacity: 0.7;\n}\n.cv__fmt--err {\n color: var(--danger);\n border-color: var(--danger);\n}\n\n/* Highlight-language picker dropdown (anchored under its pill, right-aligned). */\n.cv__lang {\n position: relative;\n display: inline-flex;\n}\n.cv__langpop {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 4;\n width: 224px;\n padding: 7px;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 10px;\n box-shadow: var(--shadow);\n}\n.cv__langsearch {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 9px;\n font-family: inherit;\n font-size: 12px;\n color: var(--text);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n outline: none;\n}\n.cv__langsearch:focus {\n border-color: var(--accent);\n}\n.cv__langlist {\n max-height: 288px;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: 6px;\n}\n.cv__langitem {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 6px 8px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: var(--text);\n font-family: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n transition:\n background 0.12s,\n color 0.12s;\n}\n.cv__langitem:hover {\n background: var(--surface-2);\n color: var(--accent);\n}\n.cv__langitem--on {\n color: var(--accent);\n}\n.cv__langitem-label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__langitem-check {\n flex-shrink: 0;\n color: var(--accent);\n}\n.cv__langempty {\n margin: 0;\n padding: 10px 8px;\n font-size: 12px;\n color: var(--faint);\n text-align: center;\n}\n\n.cv__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--code-bg);\n}\n\n/* Foldable per-line view -------------------------------------------------------------------------- */\n.cv__rows {\n min-width: max-content;\n padding: 14px 0;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n}\n.cv__row {\n display: flex;\n align-items: flex-start;\n}\n/* Gutter (fold chevron + line number) sticks to the left so it stays put while long lines scroll. */\n.cv__gut {\n position: sticky;\n left: 0;\n z-index: 1;\n flex-shrink: 0;\n display: flex;\n align-items: flex-start;\n background: var(--code-bg);\n user-select: none;\n}\n.cv__fold {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 16px;\n height: var(--fv-code-lh, 21px);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--faint);\n cursor: pointer;\n}\n.cv__fold--empty {\n cursor: default;\n}\n.cv__fold :deep(svg) {\n transition: transform 0.12s;\n}\n.cv__fold--open {\n transform: rotate(90deg);\n}\n.cv__fold:hover :deep(svg) {\n color: var(--accent);\n}\n.cv__ln {\n flex-shrink: 0;\n width: 48px;\n padding: 0 12px 0 4px;\n text-align: right;\n color: var(--faint);\n}\n.cv__code {\n flex: 1 1 auto;\n padding: 0 18px 0 14px;\n white-space: pre;\n color: var(--text);\n background: transparent;\n border: none;\n font-family: inherit;\n tab-size: 2;\n}\n.cv__ellipsis {\n align-self: center;\n margin: 0 0 0 2px;\n padding: 0 7px;\n font-family: inherit;\n line-height: 1.3;\n color: var(--muted);\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 5px;\n cursor: pointer;\n}\n.cv__ellipsis:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Huge-file blob fallback (mirrors the panel's original source layout) ----------------------------- */\n.cv__scroll--blob {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.cv__codewrap {\n display: flex;\n align-items: stretch;\n min-height: 100%;\n padding: 14px 0;\n}\n.cv__gutter {\n flex-shrink: 0;\n margin: 0;\n padding: 0 12px 0 16px;\n border-right: 1px solid var(--code-border);\n text-align: right;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--faint);\n user-select: none;\n white-space: pre;\n}\n.cv__codecol {\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n.cv__pre {\n margin: 0;\n padding: 0 18px 0 14px;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--text);\n white-space: pre;\n tab-size: 2;\n}\n.cv__pre code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"CodeView.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/CodeView.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'\nimport { escapeHtml, highlightCode, highlightLines } from '@/lib/highlight'\nimport { computeFoldRegions } from '@/lib/codefold'\nimport { canFormat, formatCode } from '@/lib/codeformat'\nimport { fileExt } from '@/lib/fileType'\nimport { hasAnsiSequences, parseAnsi } from '@/lib/ansi'\nimport { langLabel, searchLangs } from '@/lib/highlightLangs'\nimport Icon from '@/components/icons/Icon.vue'\nimport AnsiText from '@/components/AnsiText'\n\n/**\n * The side viewer's source-code view: a line-number gutter + syntax-highlighted code, with indentation-based\n * code FOLDING (click a chevron to collapse a block; see lib/codefold.ts), on-demand Prettier FORMATTING (the\n * 格式化 button, shown only for languages Prettier can parse — see lib/codeformat.ts; loaded lazily), and a\n * manual HIGHLIGHT-LANGUAGE picker (the 语言 button — auto-detection by extension/mime is often empty or wrong,\n * so any file can be re-colorized as JSON / XML / Python / … or forced to plain text; see lib/highlightLangs).\n * Used for every non-rendered file (plain text, code, or markdown/html/svg viewed as source). Highlighting is\n * split per line (see highlightLines) so rows can be hidden individually.\n *\n * Very large files (> {@link MAX_FOLD_LINES}) skip the per-line DOM and fall back to a single highlighted block\n * — folding a 10k-line file isn't worth tens of thousands of row elements, and such files mostly reach the\n * streaming log viewer anyway (see isLogFile).\n */\nconst props = defineProps<{ code: string; lang: string; name?: string }>()\n\nconst MAX_FOLD_LINES = 4000\n\n// Manual highlight-language override (null = use the auto-detected `props.lang`; '' = forced plain text). The\n// effective language feeds highlighting AND the 格式化 button (so \"force JSON, then format\" works). Reset when a\n// different file / detected language arrives (see the reset watch below).\nconst langOverride = ref<string | null>(null)\nconst effLang = computed(() => langOverride.value ?? props.lang)\n\n// Formatted output (null = showing the original). The view always renders `displayCode`.\nconst formatted = ref<string | null>(null)\nconst formatting = ref(false)\nconst formatErr = ref(false)\nconst displayCode = computed(() => formatted.value ?? props.code)\n\nconst ext = computed(() => fileExt(props.name ?? ''))\n// ANSI presentation is meaningful only in plain-text mode. Selecting a syntax language deliberately returns to\n// highlight.js; forcing \"纯文本\" on an otherwise-highlighted file enables ANSI restoration when controls exist.\nconst ansiDocument = computed(() =>\n !effLang.value && hasAnsiSequences(displayCode.value) ? parseAnsi(displayCode.value) : null,\n)\nconst ansiMode = computed(() => ansiDocument.value !== null)\nconst plainDisplayCode = computed(() => ansiDocument.value?.text ?? displayCode.value)\nconst canFmt = computed(() => !ansiMode.value && canFormat(ext.value, effLang.value))\n\nconst rawLines = computed(() => plainDisplayCode.value.split('\\n'))\nconst lineCount = computed(() => rawLines.value.length)\nconst foldable = computed(() => lineCount.value <= MAX_FOLD_LINES)\n\nconst lineHtml = computed(() =>\n ansiMode.value ? [] : highlightLines(displayCode.value, effLang.value),\n)\nconst regions = computed(() => computeFoldRegions(plainDisplayCode.value))\nconst folded = reactive(new Set<number>())\n\n// A new file (or a re-detected language) resets the formatting toggle AND the manual language override; any\n// change to the shown code clears stale fold state.\nwatch(\n () => [props.code, props.lang],\n () => {\n formatted.value = null\n formatErr.value = false\n langOverride.value = null\n },\n)\nwatch(displayCode, () => folded.clear())\n\n// ── highlight-language picker ────────────────────────────────────────────────────────────────────\nconst langMenuOpen = ref(false)\nconst langMenuRef = ref<HTMLElement | null>(null)\nconst langQuery = ref('')\nconst langResults = computed(() => searchLangs(langQuery.value))\n\nfunction chooseLang(id: string): void {\n langOverride.value = id\n langMenuOpen.value = false\n langQuery.value = ''\n}\n\n// Close the menu on an outside click or Esc — listeners live only while it's open (SSR-guarded). The toggle\n// button + popup both sit inside langMenuRef, so a click on either is \"inside\" and never self-closes.\nfunction onLangDocPointer(e: MouseEvent): void {\n if (langMenuRef.value && !langMenuRef.value.contains(e.target as Node)) langMenuOpen.value = false\n}\nfunction onLangKey(e: KeyboardEvent): void {\n if (e.key === 'Escape') langMenuOpen.value = false\n}\nwatch(langMenuOpen, (on) => {\n if (typeof document === 'undefined') return\n if (on) {\n document.addEventListener('click', onLangDocPointer)\n document.addEventListener('keydown', onLangKey)\n } else {\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n }\n})\nonBeforeUnmount(() => {\n if (typeof document === 'undefined') return\n document.removeEventListener('click', onLangDocPointer)\n document.removeEventListener('keydown', onLangKey)\n})\n\nlet errTimer: ReturnType<typeof setTimeout> | undefined\nasync function toggleFormat(): Promise<void> {\n if (formatted.value != null) {\n formatted.value = null // revert to the original\n return\n }\n if (formatting.value) return\n formatting.value = true\n formatErr.value = false\n try {\n formatted.value = await formatCode(props.code, { ext: ext.value, lang: effLang.value })\n } catch {\n formatErr.value = true\n clearTimeout(errTimer)\n errTimer = setTimeout(() => (formatErr.value = false), 2200)\n } finally {\n formatting.value = false\n }\n}\n\n// Visible line indices, skipping the bodies of folded regions (and any nested folds within them).\nconst visible = computed(() => {\n const out: number[] = []\n const n = lineCount.value\n let i = 0\n while (i < n) {\n out.push(i)\n const end = folded.has(i) ? regions.value.get(i) : undefined\n i = end != null ? end + 1 : i + 1\n }\n return out\n})\n\nfunction toggle(i: number): void {\n if (folded.has(i)) folded.delete(i)\n else folded.add(i)\n}\n\n// Blob fallback for huge files: one highlighted block + a newline-joined gutter.\nconst blobHtml = computed(() =>\n effLang.value ? highlightCode(displayCode.value, effLang.value) : escapeHtml(displayCode.value),\n)\nconst blobGutter = computed(() =>\n Array.from({ length: lineCount.value }, (_, i) => i + 1).join('\\n'),\n)\n</script>\n\n<template>\n <div class=\"cv\">\n <!-- floating tools, top-right over the code: highlight-language picker + Prettier format toggle -->\n <div class=\"cv__tools\">\n <!-- highlight-language picker: force which grammar colorizes the source (auto-detect is often empty/wrong) -->\n <div ref=\"langMenuRef\" class=\"cv__lang\">\n <button\n type=\"button\"\n class=\"cv__toolbtn\"\n :class=\"{ 'cv__toolbtn--on': langMenuOpen }\"\n title=\"高亮语言 —— 选择用哪种语言着色(自动识别不准时手动指定)\"\n :aria-haspopup=\"true\"\n :aria-expanded=\"langMenuOpen\"\n @click=\"langMenuOpen = !langMenuOpen\"\n >\n <Icon name=\"code\" :size=\"12\" /><span>{{ langLabel(effLang) }}</span>\n </button>\n <div v-if=\"langMenuOpen\" class=\"cv__langpop\" role=\"menu\">\n <input\n v-model=\"langQuery\"\n class=\"cv__langsearch\"\n type=\"text\"\n placeholder=\"搜索语言…\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n />\n <div class=\"cv__langlist\">\n <button\n v-if=\"!langQuery\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === '' }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === ''\"\n @click=\"chooseLang('')\"\n >\n <span class=\"cv__langitem-label\">纯文本(无高亮)</span>\n <Icon v-if=\"effLang === ''\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <button\n v-for=\"l in langResults\"\n :key=\"l.id\"\n type=\"button\"\n class=\"cv__langitem\"\n :class=\"{ 'cv__langitem--on': effLang === l.id }\"\n role=\"menuitemradio\"\n :aria-checked=\"effLang === l.id\"\n @click=\"chooseLang(l.id)\"\n >\n <span class=\"cv__langitem-label\">{{ l.label }}</span>\n <Icon v-if=\"effLang === l.id\" name=\"check\" :size=\"14\" class=\"cv__langitem-check\" />\n </button>\n <p v-if=\"langQuery && !langResults.length\" class=\"cv__langempty\">无匹配语言</p>\n </div>\n </div>\n </div>\n\n <!-- format toggle (only for languages Prettier can parse) -->\n <button\n v-if=\"canFmt\"\n type=\"button\"\n class=\"cv__toolbtn cv__fmt\"\n :class=\"{ 'cv__toolbtn--on': formatted != null, 'cv__fmt--err': formatErr }\"\n :disabled=\"formatting\"\n :title=\"\n formatErr\n ? '格式化失败:源码可能无法解析'\n : formatted != null\n ? '还原为原始格式'\n : '用 Prettier 格式化'\n \"\n @click=\"toggleFormat\"\n >\n {{\n formatErr\n ? '格式化失败'\n : formatting\n ? '格式化中…'\n : formatted != null\n ? '还原'\n : '格式化'\n }}\n </button>\n </div>\n\n <!-- huge file: single highlighted block (no folding) -->\n <div v-if=\"!foldable\" class=\"cv__scroll cv__scroll--blob\">\n <div class=\"cv__codewrap\">\n <pre class=\"cv__gutter\" aria-hidden=\"true\">{{ blobGutter }}</pre>\n <div class=\"cv__codecol\">\n <pre\n class=\"cv__pre\"\n ><code v-if=\"ansiDocument\" class=\"hljs\"><AnsiText :lines=\"ansiDocument.lines\" /></code><code v-else class=\"hljs\" v-html=\"blobHtml\" /></pre>\n </div>\n </div>\n </div>\n\n <!-- foldable: one row per visible line, with a fold chevron on block headers -->\n <div v-else class=\"cv__scroll\">\n <div class=\"cv__rows\">\n <div v-for=\"i in visible\" :key=\"i\" class=\"cv__row\">\n <span class=\"cv__gut\">\n <button\n v-if=\"regions.has(i)\"\n type=\"button\"\n class=\"cv__fold\"\n :title=\"folded.has(i) ? '展开' : '折叠'\"\n @click=\"toggle(i)\"\n >\n <Icon name=\"chevronRight\" :size=\"12\" :class=\"{ 'cv__fold--open': !folded.has(i) }\" />\n </button>\n <span v-else class=\"cv__fold cv__fold--empty\" />\n <span class=\"cv__ln\">{{ i + 1 }}</span>\n </span>\n <code class=\"cv__code hljs\">\n <AnsiText v-if=\"ansiDocument\" :line=\"ansiDocument.lines[i] ?? ''\" />\n <span v-else v-html=\"lineHtml[i]\" />\n </code>\n <button\n v-if=\"folded.has(i)\"\n type=\"button\"\n class=\"cv__ellipsis\"\n :title=\"`展开折叠的 ${(regions.get(i) ?? i) - i} 行`\"\n @click=\"toggle(i)\"\n >\n ⋯\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style scoped>\n.cv {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* Floating tools bar — top-right, above the code (highlight-language picker + format toggle). */\n.cv__tools {\n position: absolute;\n top: 8px;\n right: 14px;\n z-index: 3;\n display: flex;\n align-items: flex-start;\n gap: 6px;\n}\n\n/* Shared small pill used by both floating tools. */\n.cv__toolbtn {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n max-width: 190px;\n padding: 3px 10px;\n font-family: inherit;\n font-size: 11.5px;\n color: var(--muted);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n cursor: pointer;\n box-shadow: var(--shadow);\n transition:\n background 0.15s,\n color 0.15s,\n border-color 0.15s;\n}\n.cv__toolbtn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.cv__toolbtn span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__toolbtn--on {\n color: #fff;\n background: var(--accent);\n border-color: var(--accent);\n}\n.cv__toolbtn--on:hover {\n color: #fff;\n}\n\n/* Format toggle specifics (disabled + error states). */\n.cv__fmt:disabled {\n cursor: default;\n opacity: 0.7;\n}\n.cv__fmt--err {\n color: var(--danger);\n border-color: var(--danger);\n}\n\n/* Highlight-language picker dropdown (anchored under its pill, right-aligned). */\n.cv__lang {\n position: relative;\n display: inline-flex;\n}\n.cv__langpop {\n position: absolute;\n top: calc(100% + 6px);\n right: 0;\n z-index: 4;\n width: 224px;\n padding: 7px;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 10px;\n box-shadow: var(--shadow);\n}\n.cv__langsearch {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 9px;\n font-family: inherit;\n font-size: 12px;\n color: var(--text);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n outline: none;\n}\n.cv__langsearch:focus {\n border-color: var(--accent);\n}\n.cv__langlist {\n max-height: 288px;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: 6px;\n}\n.cv__langitem {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n padding: 6px 8px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: var(--text);\n font-family: inherit;\n font-size: 12.5px;\n text-align: left;\n cursor: pointer;\n transition:\n background 0.12s,\n color 0.12s;\n}\n.cv__langitem:hover {\n background: var(--surface-2);\n color: var(--accent);\n}\n.cv__langitem--on {\n color: var(--accent);\n}\n.cv__langitem-label {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.cv__langitem-check {\n flex-shrink: 0;\n color: var(--accent);\n}\n.cv__langempty {\n margin: 0;\n padding: 10px 8px;\n font-size: 12px;\n color: var(--faint);\n text-align: center;\n}\n\n.cv__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--code-bg);\n}\n\n/* Foldable per-line view -------------------------------------------------------------------------- */\n.cv__rows {\n min-width: max-content;\n padding: 14px 0;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n}\n.cv__row {\n display: flex;\n align-items: flex-start;\n}\n/* Gutter (fold chevron + line number) sticks to the left so it stays put while long lines scroll. */\n.cv__gut {\n position: sticky;\n left: 0;\n z-index: 1;\n flex-shrink: 0;\n display: flex;\n align-items: flex-start;\n background: var(--code-bg);\n user-select: none;\n}\n.cv__fold {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n width: 16px;\n height: var(--fv-code-lh, 21px);\n padding: 0;\n border: none;\n background: transparent;\n color: var(--faint);\n cursor: pointer;\n}\n.cv__fold--empty {\n cursor: default;\n}\n.cv__fold :deep(svg) {\n transition: transform 0.12s;\n}\n.cv__fold--open {\n transform: rotate(90deg);\n}\n.cv__fold:hover :deep(svg) {\n color: var(--accent);\n}\n.cv__ln {\n flex-shrink: 0;\n width: 48px;\n padding: 0 12px 0 4px;\n text-align: right;\n color: var(--faint);\n}\n.cv__code {\n flex: 1 1 auto;\n padding: 0 18px 0 14px;\n white-space: pre;\n color: var(--text);\n background: transparent;\n border: none;\n font-family: inherit;\n tab-size: 2;\n}\n.cv__ellipsis {\n align-self: center;\n margin: 0 0 0 2px;\n padding: 0 7px;\n font-family: inherit;\n line-height: 1.3;\n color: var(--muted);\n background: var(--surface-2);\n border: 1px solid var(--border-2);\n border-radius: 5px;\n cursor: pointer;\n}\n.cv__ellipsis:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Huge-file blob fallback (mirrors the panel's original source layout) ----------------------------- */\n.cv__scroll--blob {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.cv__codewrap {\n display: flex;\n align-items: stretch;\n min-height: 100%;\n padding: 14px 0;\n}\n.cv__gutter {\n flex-shrink: 0;\n margin: 0;\n padding: 0 12px 0 16px;\n border-right: 1px solid var(--code-border);\n text-align: right;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--faint);\n user-select: none;\n white-space: pre;\n}\n.cv__codecol {\n flex: 1 1 auto;\n min-width: 0;\n overflow-x: auto;\n}\n.cv__pre {\n margin: 0;\n padding: 0 18px 0 14px;\n font-family: var(--font-mono);\n font-size: var(--fv-code-fs, 12.5px);\n line-height: var(--fv-code-lh, 21px);\n color: var(--text);\n white-space: pre;\n tab-size: 2;\n}\n.cv__pre code {\n font-family: inherit;\n background: transparent;\n border: none;\n padding: 0;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4GA0BM,KAAiB;;;;;;;;EAFvB,IAAM,IAAQ,GAOR,IAAe,EAAmB,IAAI,GACtC,IAAU,QAAe,EAAa,SAAS,EAAM,IAAI,GAGzD,IAAY,EAAmB,IAAI,GACnC,IAAa,EAAI,EAAK,GACtB,IAAY,EAAI,EAAK,GACrB,IAAc,QAAe,EAAU,SAAS,EAAM,IAAI,GAE1D,IAAM,QAAe,EAAQ,EAAM,QAAQ,EAAE,CAAC,GAG9C,IAAe,QACnB,CAAC,EAAQ,SAAS,EAAiB,EAAY,KAAK,IAAI,EAAU,EAAY,KAAK,IAAI,IACzF,GACM,IAAW,QAAe,EAAa,UAAU,IAAI,GACrD,IAAmB,QAAe,EAAa,OAAO,QAAQ,EAAY,KAAK,GAC/E,KAAS,QAAe,CAAC,EAAS,SAAS,GAAU,EAAI,OAAO,EAAQ,KAAK,CAAC,GAE9E,KAAW,QAAe,EAAiB,MAAM,MAAM,IAAI,CAAC,GAC5D,IAAY,QAAe,GAAS,MAAM,MAAM,GAChD,KAAW,QAAe,EAAU,SAAS,EAAc,GAE3D,KAAW,QACf,EAAS,QAAQ,CAAC,IAAI,GAAe,EAAY,OAAO,EAAQ,KAAK,CACvE,GACM,IAAU,QAAe,GAAmB,EAAiB,KAAK,CAAC,GACnE,IAAS,mBAAS,IAAI,IAAY,CAAC;EAYzC,AARA,QACQ,CAAC,EAAM,MAAM,EAAM,IAAI,SACvB;GAGJ,AAFA,EAAU,QAAQ,MAClB,EAAU,QAAQ,IAClB,EAAa,QAAQ;EACvB,CACF,GACA,EAAM,SAAmB,EAAO,MAAM,CAAC;EAGvC,IAAM,IAAe,EAAI,EAAK,GACxB,IAAc,EAAwB,IAAI,GAC1C,IAAY,EAAI,EAAE,GAClB,IAAc,QAAe,GAAY,EAAU,KAAK,CAAC;EAE/D,SAAS,EAAW,GAAkB;GAGpC,AAFA,EAAa,QAAQ,GACrB,EAAa,QAAQ,IACrB,EAAU,QAAQ;EACpB;EAIA,SAAS,EAAiB,GAAqB;GAC7C,AAAI,EAAY,SAAS,CAAC,EAAY,MAAM,SAAS,EAAE,MAAc,MAAG,EAAa,QAAQ;EAC/F;EACA,SAAS,EAAU,GAAwB;GACzC,AAAI,EAAE,QAAQ,aAAU,EAAa,QAAQ;EAC/C;EAWA,AAVA,EAAM,IAAe,MAAO;GACtB,OAAO,WAAa,QACpB,KACF,SAAS,iBAAiB,SAAS,CAAgB,GACnD,SAAS,iBAAiB,WAAW,CAAS,MAE9C,SAAS,oBAAoB,SAAS,CAAgB,GACtD,SAAS,oBAAoB,WAAW,CAAS;EAErD,CAAC,GACD,SAAsB;GAChB,OAAO,WAAa,QACxB,SAAS,oBAAoB,SAAS,CAAgB,GACtD,SAAS,oBAAoB,WAAW,CAAS;EACnD,CAAC;EAED,IAAI;EACJ,eAAe,KAA8B;GAC3C,IAAI,EAAU,SAAS,MAAM;IAC3B,EAAU,QAAQ;IAClB;GACF;GACI,OAAW,OAEf;IADA,EAAW,QAAQ,IACnB,EAAU,QAAQ;IAClB,IAAI;KACF,EAAU,QAAQ,MAAM,GAAW,EAAM,MAAM;MAAE,KAAK,EAAI;MAAO,MAAM,EAAQ;KAAM,CAAC;IACxF,QAAQ;KAGN,AAFA,EAAU,QAAQ,IAClB,aAAa,CAAQ,GACrB,IAAW,iBAAkB,EAAU,QAAQ,IAAQ,IAAI;IAC7D,UAAU;KACR,EAAW,QAAQ;IACrB;GATkB;EAUpB;EAGA,IAAM,KAAU,QAAe;GAC7B,IAAM,IAAgB,CAAC,GACjB,IAAI,EAAU,OAChB,IAAI;GACR,OAAO,IAAI,IAAG;IACZ,EAAI,KAAK,CAAC;IACV,IAAM,IAAM,EAAO,IAAI,CAAC,IAAI,EAAQ,MAAM,IAAI,CAAC,IAAI,KAAA;IACnD,IAAI,KAAO,OAAiB,IAAI,IAAd,IAAM;GAC1B;GACA,OAAO;EACT,CAAC;EAED,SAAS,EAAO,GAAiB;GAC/B,AAAI,EAAO,IAAI,CAAC,IAAG,EAAO,OAAO,CAAC,IAC7B,EAAO,IAAI,CAAC;EACnB;EAGA,IAAM,KAAW,QACf,EAAQ,QAAQ,EAAc,EAAY,OAAO,EAAQ,KAAK,IAAI,GAAW,EAAY,KAAK,CAChG,GACM,KAAa,QACjB,MAAM,KAAK,EAAE,QAAQ,EAAU,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CACpE;yBAIE,EAiIM,OAjIN,IAiIM,CA/HJ,EAgFM,OAhFN,IAgFM,CA9EJ,EAkDM,OAAA;YAlDG;GAAJ,KAAI;GAAc,OAAM;MAC3B,EAUS,UAAA;GATP,MAAK;GACL,OAAK,EAAA,CAAC,eAAa,EAAA,mBACU,EAAA,MAAY,CAAA,CAAA;GACzC,OAAM;GACL,iBAAe;GACf,iBAAe,EAAA;GACf,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,QAAY,CAAI,EAAA;MAExB,EAA+B,GAAA;GAAzB,MAAK;GAAQ,MAAM;MAAM,EAAqC,QAAA,MAAA,EAA5B,EAAA,EAAA,CAAS,CAAC,EAAA,KAAO,CAAA,GAAA,CAAA,CAAA,GAAA,IAAA,EAAA,GAEhD,EAAA,SAAA,EAAA,GAAX,EAqCM,OArCN,IAqCM,CAAA,GApCJ,EAOE,SAAA;4CANkB,QAAA;GAClB,OAAM;GACN,MAAK;GACL,aAAY;GACZ,YAAW;GACX,cAAa;uBALJ,EAAA,KAAS,CAAA,CAAA,GAOpB,EA2BM,OA3BN,IA2BM;GAzBK,EAAA,qBAAA,EAAA,GADT,EAWS,UAAA;;IATP,MAAK;IACL,OAAK,EAAA,CAAC,gBAAc,EAAA,oBACU,EAAA,UAAO,GAAA,CAAA,CAAA;IACrC,MAAK;IACJ,gBAAc,EAAA,UAAO;IACrB,SAAK,AAAA,EAAA,QAAA,MAAE,EAAU,EAAA;gBAElB,EAAgD,QAAA,EAA1C,OAAM,qBAAoB,GAAC,YAAQ,EAAA,GAC7B,EAAA,UAAO,MAAA,EAAA,GAAnB,EAAiF,GAAA;;IAArD,MAAK;IAAS,MAAM;IAAI,OAAM;;WAE5D,EAYS,GAAA,MAAA,EAXK,EAAA,QAAL,YADT,EAYS,UAAA;IAVN,KAAK,EAAE;IACR,MAAK;IACL,OAAK,EAAA,CAAC,gBAAc,EAAA,oBACU,EAAA,UAAY,EAAE,GAAE,CAAA,CAAA;IAC9C,MAAK;IACJ,gBAAc,EAAA,UAAY,EAAE;IAC5B,UAAK,MAAE,EAAW,EAAE,EAAE;OAEvB,EAAqD,QAArD,IAAqD,EAAjB,EAAE,KAAK,GAAA,CAAA,GAC/B,EAAA,UAAY,EAAE,MAAA,EAAA,GAA1B,EAAmF,GAAA;;IAArD,MAAK;IAAS,MAAM;IAAI,OAAM;;GAErD,EAAA,SAAS,CAAK,EAAA,MAAY,UAAA,EAAA,GAAnC,EAA0E,KAA1E,IAAiE,OAAK,KAAA,EAAA,IAAA,EAAA;4BAOpE,GAAA,SAAA,EAAA,GADR,EAwBS,UAAA;;GAtBP,MAAK;GACL,OAAK,EAAA,CAAC,uBAAqB;IAAA,mBACE,EAAA,SAAS;IAAA,gBAA0B,EAAA;GAAS,CAAA,CAAA;GACxE,UAAU,EAAA;GACV,OAAkB,EAAA,QAAA,mBAAuD,EAAA,SAAS,OAAA,mBAAA;GAOlF,SAAO;OAGN,EAAA,QAAA,UAA8C,EAAA,QAAA,UAAmD,EAAA,SAAS,OAAA,QAAA,IAAA,GAAA,IAAA,EAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,GAYpG,GAAA,cAYZ,EA+BM,OA/BN,GA+BM,CA9BJ,EA6BM,OA7BN,GA6BM,EAAA,EAAA,EAAA,GA5BJ,EA2BM,GAAA,MAAA,EA3BW,GAAA,QAAL,YAAZ,EA2BM,OAAA;GA3BqB,KAAK;GAAG,OAAM;;GACvC,EAYO,QAZP,GAYO,CAVG,EAAA,MAAQ,IAAI,CAAC,KAAA,EAAA,GADrB,EAQS,UAAA;;IANP,MAAK;IACL,OAAM;IACL,OAAO,EAAO,IAAI,CAAC,IAAA,OAAA;IACnB,UAAK,MAAE,EAAO,CAAC;OAEhB,EAAqF,GAAA;IAA/E,MAAK;IAAgB,MAAM;IAAK,OAAK,EAAA,EAAA,kBAAA,CAAuB,EAAO,IAAI,CAAC,EAAA,CAAA;2CAEhF,EAAgD,QAAhD,CAAgD,IAChD,EAAuC,QAAvC,GAAuC,EAAf,IAAC,CAAA,GAAA,CAAA,CAAA,CAAA;GAE3B,EAGO,QAHP,IAGO,CAFW,EAAA,SAAA,EAAA,GAAhB,EAAoE,EAAA,CAAA,GAAA;;IAArC,MAAM,EAAA,MAAa,MAAM,MAAC;kCACzD,EAAoC,QAAA;;IAAvB,WAAQ,GAAA,MAAS;;GAGxB,EAAO,IAAI,CAAC,KAAA,EAAA,GADpB,EAQS,UAAA;;IANP,MAAK;IACL,OAAM;IACL,OAAK,UAAY,EAAA,MAAQ,IAAI,CAAC,KAAK,KAAK,EAAC;IACzC,UAAK,MAAE,EAAO,CAAC;MACjB,OAED,GAAA,EAAA,KAAA,EAAA,IAAA,EAAA;sBAxCM,EAAA,GAAZ,EASM,OATN,IASM,CARJ,EAOM,OAPN,IAOM,CANJ,EAAiE,OAAjE,IAAiE,EAAnB,GAAA,KAAU,GAAA,CAAA,GACxD,EAIM,OAJN,GAIM,CAHJ,EAE2I,OAF3I,GAE2I,CAA9H,EAAA,SAAA,EAAA,GAAZ,EAAsF,QAAtF,GAAsF,CAA/C,EAAwC,EAAA,CAAA,GAAA,EAA7B,OAAO,EAAA,MAAa,MAAA,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,CAAA,CAAA,MAAA,EAAA,GAAgB,EAA8C,QAAA;;GAAjC,OAAM;GAAO,WAAQ,GAAA"}
@@ -0,0 +1,9 @@
1
+ import e from "../_virtual/_plugin-vue_export-helper.js";
2
+ import t from "./DocxEmbeddedFiles.vue_vue_type_script_setup_true_lang.js";
3
+ /* empty css */
4
+ //#region src/components/DocxEmbeddedFiles.vue
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-2106357b"]]);
6
+ //#endregion
7
+ export { n as default };
8
+
9
+ //# sourceMappingURL=DocxEmbeddedFiles.js.map