Files
narlblog/frontend/src/components/react/PostEnhancer.tsx
T
2026-03-28 13:54:34 +01:00

62 lines
1.9 KiB
TypeScript

import { useEffect } from 'react';
import hljs from 'highlight.js';
import katex from 'katex';
import 'katex/dist/katex.min.css';
function renderMath(element: HTMLElement) {
const delimiters = [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: true },
];
const walk = (node: Node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const el = node as HTMLElement;
if (el.tagName === 'CODE' || el.tagName === 'PRE') return;
for (const child of Array.from(el.childNodes)) walk(child);
} else if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent || '';
for (const { left, right, display } of delimiters) {
const idx = text.indexOf(left);
if (idx === -1) continue;
const end = text.indexOf(right, idx + left.length);
if (end === -1) continue;
const tex = text.slice(idx + left.length, end);
try {
const rendered = katex.renderToString(tex, { displayMode: display, throwOnError: false });
const span = document.createElement('span');
span.innerHTML = rendered;
const range = document.createRange();
range.setStart(node, idx);
range.setEnd(node, end + right.length);
range.deleteContents();
range.insertNode(span);
} catch { /* skip invalid tex */ }
return;
}
}
};
for (let i = 0; i < 3; i++) walk(element);
}
interface Props {
containerId: string;
}
export default function PostEnhancer({ containerId }: Props) {
useEffect(() => {
const el = document.getElementById(containerId);
if (!el) return;
renderMath(el);
el.querySelectorAll<HTMLElement>('pre code').forEach((block) => {
hljs.highlightElement(block);
});
}, [containerId]);
return null;
}