{"version":3,"file":"useShikiHighlight-CvyoNbCI.mjs","sources":["../../src/hooks/useShikiHighlight.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { codeToHtml, type ShikiTransformer } from 'shiki';\nimport { useResolvedTheme } from '../ThemeContext';\n\n/**\n * 用 shiki 把代码高亮成 HTML（与 vue-file-preview 同引擎、同主题，保证两端视觉一致）。\n *\n * - dark 主题用 `dark-plus`（VSCode Dark Plus）\n * - light 主题用 `github-light`（GitHub Light）\n *\n * shiki 输出的 <pre> 自带 inline 背景/前景色，主题切换时必须重新高亮，\n * 因此 resolvedTheme 进入依赖数组。\n *\n * @returns\n *  - `html`: 完整的 shiki 输出 HTML（含 pre/code 包裹），markdown 等场景使用\n *  - `lineHtmls`: 拆分后的每一行 HTML（用于双列行号布局）\n *  - `loading`: 是否正在高亮\n */\nexport function useShikiHighlight(\n  code: string,\n  lang: string,\n): { html: string; lineHtmls: string[]; loading: boolean } {\n  const resolvedTheme = useResolvedTheme();\n  const [html, setHtml] = useState('');\n  const [lineHtmls, setLineHtmls] = useState<string[]>([]);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    let cancelled = false;\n    setLoading(true);\n\n    const lineNumbersTransformer: ShikiTransformer = {\n      name: 'line-numbers',\n      line(node, line) {\n        node.properties['data-line'] = line;\n        this.addClassToHast(node, 'line');\n      },\n    };\n\n    codeToHtml(code, {\n      lang,\n      theme: resolvedTheme === 'light' ? 'github-light' : 'dark-plus',\n      transformers: [lineNumbersTransformer],\n    })\n      .then((out) => {\n        if (!cancelled) {\n          setHtml(out);\n          setLineHtmls(extractLines(out));\n          setLoading(false);\n        }\n      })\n      .catch(() => {\n        if (!cancelled) {\n          setHtml('');\n          setLineHtmls([]);\n          setLoading(false);\n        }\n      });\n    return () => {\n      cancelled = true;\n    };\n  }, [code, lang, resolvedTheme]);\n\n  return { html, lineHtmls, loading };\n}\n\n/**\n * 从 shiki 输出的 HTML 中提取每一行的内容（保留高亮标签）\n */\nfunction extractLines(html: string): string[] {\n  if (typeof window === 'undefined' || !html) return [];\n  const parser = new DOMParser();\n  const doc = parser.parseFromString(html, 'text/html');\n  const codeElement = doc.querySelector('code');\n  if (!codeElement) return [];\n  const lineElements = codeElement.querySelectorAll('.line');\n  return Array.from(lineElements).map((line) => line.innerHTML);\n}\n"],"names":["useShikiHighlight","code","lang","resolvedTheme","useResolvedTheme","html","setHtml","useState","lineHtmls","setLineHtmls","loading","setLoading","useEffect","cancelled","codeToHtml","node","line","out","extractLines","codeElement","lineElements"],"mappings":";;;AAkBO,SAASA,EACdC,GACAC,GACyD;AACzD,QAAMC,IAAgBC,EAAA,GAChB,CAACC,GAAMC,CAAO,IAAIC,EAAS,EAAE,GAC7B,CAACC,GAAWC,CAAY,IAAIF,EAAmB,CAAA,CAAE,GACjD,CAACG,GAASC,CAAU,IAAIJ,EAAS,EAAI;AAE3C,SAAAK,EAAU,MAAM;AACd,QAAIC,IAAY;AAChB,WAAAF,EAAW,EAAI,GAUfG,EAAWb,GAAM;AAAA,MACf,MAAAC;AAAA,MACA,OAAOC,MAAkB,UAAU,iBAAiB;AAAA,MACpD,cAAc,CAXiC;AAAA,QAC/C,MAAM;AAAA,QACN,KAAKY,GAAMC,GAAM;AACf,UAAAD,EAAK,WAAW,WAAW,IAAIC,GAC/B,KAAK,eAAeD,GAAM,MAAM;AAAA,QAClC;AAAA,MAAA,CAMqC;AAAA,IAAA,CACtC,EACE,KAAK,CAACE,MAAQ;AACb,MAAKJ,MACHP,EAAQW,CAAG,GACXR,EAAaS,EAAaD,CAAG,CAAC,GAC9BN,EAAW,EAAK;AAAA,IAEpB,CAAC,EACA,MAAM,MAAM;AACX,MAAKE,MACHP,EAAQ,EAAE,GACVG,EAAa,CAAA,CAAE,GACfE,EAAW,EAAK;AAAA,IAEpB,CAAC,GACI,MAAM;AACX,MAAAE,IAAY;AAAA,IACd;AAAA,EACF,GAAG,CAACZ,GAAMC,GAAMC,CAAa,CAAC,GAEvB,EAAE,MAAAE,GAAM,WAAAG,GAAW,SAAAE,EAAA;AAC5B;AAKA,SAASQ,EAAab,GAAwB;AAC5C,MAAI,OAAO,SAAW,OAAe,CAACA,UAAa,CAAA;AAGnD,QAAMc,IAFS,IAAI,UAAA,EACA,gBAAgBd,GAAM,WAAW,EAC5B,cAAc,MAAM;AAC5C,MAAI,CAACc,EAAa,QAAO,CAAA;AACzB,QAAMC,IAAeD,EAAY,iBAAiB,OAAO;AACzD,SAAO,MAAM,KAAKC,CAAY,EAAE,IAAI,CAACJ,MAASA,EAAK,SAAS;AAC9D;"}