math-render.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /**
  2. * 数学公式渲染系统 - 全局配置
  3. * 适用于 Laravel + Livewire + Filament
  4. */
  5. (function() {
  6. 'use strict';
  7. // 全局配置
  8. window.MathRenderConfig = {
  9. attempts: 0,
  10. maxAttempts: 50,
  11. delay: 100,
  12. selector: '.math-text',
  13. autoInit: true
  14. };
  15. // 渲染单个数学元素
  16. window.renderMathElement = function(element) {
  17. if (!element) return;
  18. // 避免重复渲染
  19. if (element.dataset.rendered === 'true') {
  20. return;
  21. }
  22. // 确保 KaTeX 已加载
  23. if (typeof window.katex === 'undefined') {
  24. if (window.MathRenderConfig.attempts < window.MathRenderConfig.maxAttempts) {
  25. window.MathRenderConfig.attempts++;
  26. setTimeout(() => {
  27. window.renderMathElement(element);
  28. }, window.MathRenderConfig.delay);
  29. }
  30. return;
  31. }
  32. try {
  33. const content = element.dataset.mathContent || element.textContent;
  34. if (!content) return;
  35. let html = content;
  36. // 渲染 $$...$$ 块级公式
  37. html = html.replace(/\$\$([\s\S]*?)\$\$/g, (match, formula) => {
  38. try {
  39. return window.katex.renderToString(formula.trim(), {
  40. throwOnError: false,
  41. displayMode: true
  42. });
  43. } catch (e) {
  44. console.warn('[KaTeX] Render error for block formula:', e);
  45. return match;
  46. }
  47. });
  48. // 渲染 \[...\] 块级公式
  49. html = html.replace(/\\\[([\s\S]*?)\\\]/g, (match, formula) => {
  50. try {
  51. return window.katex.renderToString(formula.trim(), {
  52. throwOnError: false,
  53. displayMode: true
  54. });
  55. } catch (e) {
  56. console.warn('[KaTeX] Render error for block formula:', e);
  57. return match;
  58. }
  59. });
  60. // 渲染 $...$ 行内公式
  61. html = html.replace(/\$(.*?)\$/g, (match, formula) => {
  62. try {
  63. return window.katex.renderToString(formula, {
  64. throwOnError: false,
  65. displayMode: false
  66. });
  67. } catch (e) {
  68. console.warn('[KaTeX] Render error for inline formula:', e);
  69. return match;
  70. }
  71. });
  72. // 渲染 \(...\) 行内公式
  73. html = html.replace(/\\\((.*?)\\\)/g, (match, formula) => {
  74. try {
  75. return window.katex.renderToString(formula, {
  76. throwOnError: false,
  77. displayMode: false
  78. });
  79. } catch (e) {
  80. console.warn('[KaTeX] Render error for inline formula:', e);
  81. return match;
  82. }
  83. });
  84. element.innerHTML = html;
  85. element.dataset.rendered = 'true';
  86. } catch (error) {
  87. console.error('[KaTeX] Render error:', error);
  88. }
  89. };
  90. // 渲染所有数学元素
  91. window.renderAllMath = function() {
  92. document.querySelectorAll(window.MathRenderConfig.selector).forEach(window.renderMathElement);
  93. window.MathRenderConfig.attempts = 0;
  94. };
  95. // 手动触发渲染
  96. window.triggerMathRender = function() {
  97. window.renderAllMath();
  98. };
  99. // DOM 加载完成初始化
  100. function init() {
  101. if (!window.MathRenderConfig.autoInit) {
  102. return;
  103. }
  104. // 加载 KaTeX(如果尚未加载)
  105. if (typeof window.katex === 'undefined') {
  106. const script = document.createElement('script');
  107. script.src = '/js/katex.min.js';
  108. script.defer = true;
  109. script.onload = () => {
  110. console.log('[KaTeX] Loaded successfully');
  111. window.renderAllMath();
  112. };
  113. script.onerror = () => {
  114. console.error('[KaTeX] Failed to load');
  115. };
  116. document.head.appendChild(script);
  117. } else {
  118. window.renderAllMath();
  119. }
  120. }
  121. // 事件监听器
  122. document.addEventListener('DOMContentLoaded', init);
  123. // Livewire 事件
  124. document.addEventListener('livewire:initialized', () => {
  125. console.log('[MathRender] Livewire initialized');
  126. window.renderAllMath();
  127. });
  128. document.addEventListener('livewire:navigated', () => {
  129. console.log('[MathRender] Livewire navigated');
  130. setTimeout(window.renderAllMath, 100);
  131. });
  132. document.addEventListener('livewire:updated', () => {
  133. console.log('[MathRender] Livewire updated');
  134. setTimeout(window.renderAllMath, 100);
  135. });
  136. // Alpine.js 事件
  137. document.addEventListener('alpine:init', () => {
  138. console.log('[MathRender] Alpine initialized');
  139. window.renderAllMath();
  140. });
  141. // 自定义事件
  142. document.addEventListener('math:render', window.renderAllMath);
  143. document.addEventListener('math:render:force', () => {
  144. document.querySelectorAll(window.MathRenderConfig.selector).forEach(el => {
  145. delete el.dataset.rendered;
  146. });
  147. window.renderAllMath();
  148. });
  149. // 表单验证后渲染
  150. document.addEventListener('filament:form:validated', window.renderAllMath);
  151. // 全局暴露
  152. window.MathRender = {
  153. render: window.renderMathElement,
  154. renderAll: window.renderAllMath,
  155. trigger: window.triggerMathRender,
  156. config: window.MathRenderConfig
  157. };
  158. console.log('[MathRender] System initialized');
  159. })();