|
425 | 425 | color: var(--blueprint); |
426 | 426 | } |
427 | 427 |
|
| 428 | + .lesson-article .math-inline .katex { |
| 429 | + font-size: 1.04em; |
| 430 | + } |
| 431 | + |
428 | 432 | .lesson-article pre { |
429 | 433 | position: relative; |
430 | 434 | margin: 20px 0; |
|
1971 | 1975 |
|
1972 | 1976 | initCodeCopy(); |
1973 | 1977 | renderMermaidBlocks(); |
| 1978 | + renderMathSpans(); |
1974 | 1979 | if (window.mountLessonFigures) window.mountLessonFigures(el); |
1975 | 1980 | renderAIPanels(); |
1976 | 1981 | buildTOC(); |
|
2484 | 2489 | return cells.map(function (c) { return c.trim(); }); |
2485 | 2490 | } |
2486 | 2491 |
|
| 2492 | + // zh 特化:上游课程把数学公式写成 inline code(如 `h_t = f(h_{t-1}, x_t)`), |
| 2493 | + // 命中强数学信号且无代码特征的 span 改走 KaTeX 渲染,其余保持 code 原样。 |
| 2494 | + // 输入是 HTML 转义后的文本;判定必须保守——漏判只是维持现状,误判会把代码渲染成数学。 |
| 2495 | + function looksLikeMath(s) { |
| 2496 | + if (/[一-鿿]/.test(s)) return false; |
| 2497 | + if (s.indexOf('"') !== -1 || s.indexOf('$') !== -1) return false; |
| 2498 | + // 字面 < 只可能来自 em/strong 注入残留(真实 < 已是 &lt;),整 span 拒绝 |
| 2499 | + if (s.indexOf('<') !== -1) return false; |
| 2500 | + // 单引号:开引号(前面不是标识符/右括号)按字符串字面量排除;prime 记法如 V(s') 放行 |
| 2501 | + if (/(^|[^a-zA-Z0-9)\]])'/.test(s)) return false; |
| 2502 | + if (s.indexOf('*') !== -1) return false; |
| 2503 | + if (/_[a-z]{4,}/.test(s)) return false; |
| 2504 | + if (/--|::|=>|->|\/\//.test(s)) return false; |
| 2505 | + // ^ 上标要求前面有底数字符(拒正则锚点如 ^Bearer);| 别漏,双范数 ||q||^2 靠它 |
| 2506 | + return /[_^]\{/.test(s) || |
| 2507 | + /[½⅓¼·≈≠≤≥±×÷√∇∂∑Σ∏∫πθμσεαβγλδηρτφψωΩΔ∈∉∞∝∀∃²³ᵀ]/.test(s) || |
| 2508 | + /[0-9a-zA-Z)\]}|]\^[0-9a-zA-Z(]/.test(s); |
| 2509 | + } |
| 2510 | + |
2487 | 2511 | function inlineFormat(text) { |
2488 | 2512 | text = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); |
2489 | 2513 | text = text.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>'); |
2490 | 2514 | text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>'); |
2491 | 2515 | text = text.replace(/\*(.+?)\*/g, '<em>$1</em>'); |
2492 | | - text = text.replace(/`([^`]+)`/g, '<code>$1</code>'); |
| 2516 | + text = text.replace(/`([^`]+)`/g, function (m, c) { |
| 2517 | + if (looksLikeMath(c)) return '<code class="math-tex" data-tex="' + c + '">' + c + '</code>'; |
| 2518 | + return '<code>' + c + '</code>'; |
| 2519 | + }); |
2493 | 2520 | text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, function (m, label, href) { |
2494 | 2521 | if (/^https?:\/\/|^mailto:/i.test(href)) { |
2495 | 2522 | return '<a href="' + href + '" target="_blank" rel="noopener">' + label + '</a>'; |
|
2503 | 2530 | return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); |
2504 | 2531 | } |
2505 | 2532 |
|
| 2533 | + // 把课程里的伪 LaTeX 记法整理成 KaTeX 能吃的形式。 |
| 2534 | + // getAttribute 已还原 HTML 实体,这里拿到的是原始文本。 |
| 2535 | + function texPreprocess(s) { |
| 2536 | + s = s.replace(/²/g, '^2').replace(/³/g, '^3').replace(/ᵀ/g, '^T'); |
| 2537 | + s = s.replace(/([A-Za-zα-ωΑ-Ω])̅/g, '\\bar{$1}'); |
| 2538 | + s = s.replace(/([A-Za-zα-ωΑ-Ω])̂/g, '\\hat{$1}'); |
| 2539 | + s = s.replace(/ŵ/g, '\\hat{w}').replace(/â/g, '\\hat{a}'); |
| 2540 | + s = s.replace(/√/g, '\\sqrt '); |
| 2541 | + s = s.replace(/~/g, '\\sim '); |
| 2542 | + s = s.replace(/([_^])([a-zA-Z0-9]+(?:\.[0-9]+)?)/g, '$1{$2}'); |
| 2543 | + s = s.replace(/\b(softmax|argmax|argmin|log10|log|exp|sin|cos|tanh|max|min|KL|Tr|Var|sqrt)\b(\s*\()/g, '\\operatorname{$1}$2'); |
| 2544 | + return s; |
| 2545 | + } |
| 2546 | + |
| 2547 | + // 按需加载 KaTeX 并渲染 math-tex span;任何一步失败都回退原 code 样式。 |
| 2548 | + function renderMathSpans() { |
| 2549 | + var spans = document.querySelectorAll('code.math-tex'); |
| 2550 | + if (!spans.length) return; |
| 2551 | + var KATEX = 'https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min'; |
| 2552 | + if (!document.getElementById('katex-css')) { |
| 2553 | + var link = document.createElement('link'); |
| 2554 | + link.id = 'katex-css'; link.rel = 'stylesheet'; link.href = KATEX + '.css'; |
| 2555 | + document.head.appendChild(link); |
| 2556 | + } |
| 2557 | + function renderAll() { |
| 2558 | + spans.forEach(function (el) { |
| 2559 | + try { |
| 2560 | + var span = document.createElement('span'); |
| 2561 | + span.className = 'math-inline'; |
| 2562 | + window.katex.render(texPreprocess(el.getAttribute('data-tex')), span, { throwOnError: true, strict: false }); |
| 2563 | + el.replaceWith(span); |
| 2564 | + } catch (e) { el.classList.remove('math-tex'); } |
| 2565 | + }); |
| 2566 | + } |
| 2567 | + if (window.katex) { renderAll(); return; } |
| 2568 | + var script = document.createElement('script'); |
| 2569 | + script.src = KATEX + '.js'; |
| 2570 | + script.onload = renderAll; |
| 2571 | + document.head.appendChild(script); |
| 2572 | + } |
| 2573 | + |
2506 | 2574 | function renderCodeBlock(code, lang) { |
2507 | 2575 | var highlighted = highlightSyntax(escapeHtml(code), lang); |
2508 | 2576 | var langLabel = lang ? '<span class="code-lang">' + escapeHtml(lang) + '</span>' : ''; |
|
0 commit comments