Spaces:
No application file
No application file
File size: 5,067 Bytes
c20f20c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | /**
* Interactive HTML Post-Processor
*
* Ported from Python's PostProcessor class (learn-your-way/concept_to_html.py:287-385)
*
* Handles:
* - LaTeX delimiter conversion ($$...$$ -> \[...\], $...$ -> \(...\))
* - KaTeX CSS/JS injection with auto-render and MutationObserver
* - Script tag protection during LaTeX conversion
*/
/**
* Main entry point: post-process generated interactive HTML
* Converts LaTeX delimiters and injects KaTeX rendering resources.
*/
export function postProcessInteractiveHtml(html: string): string {
// Convert LaTeX delimiters while protecting script tags
let processed = convertLatexDelimiters(html);
// Inject KaTeX resources if not already present
if (!processed.toLowerCase().includes('katex')) {
processed = injectKatex(processed);
}
return processed;
}
/**
* Convert LaTeX delimiters while protecting <script> tags.
*
* - Protects script blocks from modification
* - Converts $$...$$ to \[...\] (display math)
* - Converts $...$ to \(...\) (inline math)
* - Restores script blocks after conversion
*/
function convertLatexDelimiters(html: string): string {
const scriptBlocks: string[] = [];
// Protect script tags by replacing them with placeholders
let processed = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, (match) => {
scriptBlocks.push(match);
return `__SCRIPT_BLOCK_${scriptBlocks.length - 1}__`;
});
// Convert display math: $$...$$ -> \[...\]
processed = processed.replace(/\$\$([^$]+)\$\$/g, '\\[$1\\]');
// Convert inline math: $...$ -> \(...\)
// Use non-greedy match and exclude newlines to avoid false positives
processed = processed.replace(/\$([^$\n]+?)\$/g, '\\($1\\)');
// Restore script blocks using indexOf + substring (not .replace())
// because script content may contain $ characters that .replace()
// would interpret as special substitution patterns.
for (let i = 0; i < scriptBlocks.length; i++) {
const placeholder = `__SCRIPT_BLOCK_${i}__`;
const idx = processed.indexOf(placeholder);
if (idx !== -1) {
processed =
processed.substring(0, idx) +
scriptBlocks[i] +
processed.substring(idx + placeholder.length);
}
}
return processed;
}
/**
* Inject KaTeX CSS, JS, auto-render, and MutationObserver before </head>.
* Falls back to appending at end if </head> is not found.
*/
function injectKatex(html: string): string {
const katexInjection = `
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const katexOptions = {
delimiters: [
{left: '\\\\[', right: '\\\\]', display: true},
{left: '\\\\(', right: '\\\\)', display: false},
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false}
],
throwOnError: false,
strict: false,
trust: true
};
let renderTimeout;
function safeRender() {
if (renderTimeout) clearTimeout(renderTimeout);
renderTimeout = setTimeout(() => {
renderMathInElement(document.body, katexOptions);
}, 100);
}
renderMathInElement(document.body, katexOptions);
const observer = new MutationObserver((mutations) => {
let shouldRender = false;
mutations.forEach((mutation) => {
if (mutation.target &&
mutation.target.className &&
typeof mutation.target.className === 'string' &&
mutation.target.className.includes('katex')) {
return;
}
shouldRender = true;
});
if (shouldRender) {
safeRender();
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
setInterval(() => {
const text = document.body.innerText;
if (text.includes('\\\\(') || text.includes('$$')) {
safeRender();
}
}, 2000);
});
</script>`;
// Use indexOf + substring instead of String.replace() because the
// katexInjection string contains '$' characters that .replace() would
// interpret as special substitution patterns ($$ → $, $' → post-match text).
const headCloseIdx = html.indexOf('</head>');
if (headCloseIdx !== -1) {
return (
html.substring(0, headCloseIdx) +
katexInjection +
'\n</head>' +
html.substring(headCloseIdx + 7)
);
}
// Fallback: inject before </body> if </head> is missing
const bodyCloseIdx = html.indexOf('</body>');
if (bodyCloseIdx !== -1) {
return (
html.substring(0, bodyCloseIdx) +
katexInjection +
'\n</body>' +
html.substring(bodyCloseIdx + 7)
);
}
// Last resort: append at end
return html + katexInjection;
}
|