62 lines
1.9 KiB
TypeScript
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;
|
|
}
|