()
| 204 | ) |
| 205 | |
| 206 | const highlight = async () => { |
| 207 | // Show plain text if language needs to be loaded. |
| 208 | if (currentLanguage && !isLanguageLoaded(currentLanguage)) { |
| 209 | if (isMountedRef.current) { |
| 210 | setHighlightedCode(fallback) |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | const highlighter = await getHighlighter(currentLanguage) |
| 215 | if (!isMountedRef.current) return |
| 216 | |
| 217 | const hast = await highlighter.codeToHast(source || "", { |
| 218 | lang: currentLanguage || "txt", |
| 219 | theme: document.body.className.toLowerCase().includes("light") ? "github-light" : "github-dark", |
| 220 | transformers: [ |
| 221 | { |
| 222 | pre(node) { |
| 223 | node.properties.style = "padding: 0; margin: 0;" |
| 224 | return node |
| 225 | }, |
| 226 | code(node) { |
| 227 | // Add hljs classes for consistent styling |
| 228 | node.properties.class = `hljs language-${currentLanguage}` |
| 229 | return node |
| 230 | }, |
| 231 | line(node) { |
| 232 | // Preserve existing line handling |
| 233 | node.properties.class = node.properties.class || "" |
| 234 | return node |
| 235 | }, |
| 236 | }, |
| 237 | ] as ShikiTransformer[], |
| 238 | }) |
| 239 | if (!isMountedRef.current) return |
| 240 | |
| 241 | // Convert HAST to React elements using hast-util-to-jsx-runtime |
| 242 | // This approach eliminates XSS vulnerabilities by avoiding dangerouslySetInnerHTML |
| 243 | // while maintaining the exact same visual output and syntax highlighting |
| 244 | try { |
| 245 | const reactElement = toJsxRuntime(hast, { |
| 246 | Fragment, |
| 247 | jsx, |
| 248 | jsxs, |
| 249 | // Don't override components - let them render as-is to maintain exact output |
| 250 | }) |
| 251 | |
| 252 | if (isMountedRef.current) { |
| 253 | setHighlightedCode(reactElement) |
| 254 | } |
| 255 | } catch (error) { |
| 256 | console.error("[CodeBlock] Error converting HAST to JSX:", error) |
| 257 | if (isMountedRef.current) { |
| 258 | setHighlightedCode(fallback) |
| 259 | } |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | highlight().catch((e) => { |
no test coverage detected