math-render.blade.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. @props(['content' => '', 'class' => '', 'inline' => false])
  2. <span class="math-render {{ $class }}">
  3. {!! $content !!}
  4. </span>
  5. @push('scripts')
  6. <!-- 引入 KaTeX 核心库 -->
  7. <script src="/js/katex.min.js?v={{ time() }}"></script>
  8. <!-- 引入 auto-render 扩展 (本地) -->
  9. <script src="/js/auto-render.min.js?v={{ time() }}"></script>
  10. <!-- 引入数学公式预处理器 -->
  11. <script src="/js/math-formula-processor.js?v={{ time() }}"></script>
  12. <script>
  13. (function() {
  14. 'use strict';
  15. console.log('Math Render Script Loaded (Local Auto-Render)');
  16. // 防止无限递归的全局开关
  17. window.MATH_RENDER_LOCK = false;
  18. window.MATH_RENDER_COUNT = 0;
  19. const MAX_RENDER_COUNT = 50; // 最大渲染次数,防止死循环
  20. // 配置项
  21. const renderOptions = {
  22. delimiters: [
  23. {left: '$$', right: '$$', display: true},
  24. {left: '$', right: '$', display: false},
  25. {left: '\\(', right: '\\)', display: false},
  26. {left: '\\[', right: '\\]', display: true}
  27. ],
  28. throwOnError: false,
  29. ignoredTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code', 'option']
  30. };
  31. function renderAllMath() {
  32. // 防止无限递归
  33. if (window.MATH_RENDER_LOCK) {
  34. console.warn('Math render already in progress, skipping...');
  35. return;
  36. }
  37. if (window.MATH_RENDER_COUNT >= MAX_RENDER_COUNT) {
  38. console.warn('Max render count reached, stopping to prevent infinite loop');
  39. return;
  40. }
  41. window.MATH_RENDER_LOCK = true;
  42. window.MATH_RENDER_COUNT++;
  43. if (typeof renderMathInElement === 'undefined') {
  44. console.warn('auto-render extension not loaded yet');
  45. window.MATH_RENDER_LOCK = false;
  46. return;
  47. }
  48. if (typeof window.katex === 'undefined') {
  49. console.warn('katex core not loaded yet');
  50. window.MATH_RENDER_LOCK = false;
  51. return;
  52. }
  53. console.log(`Rendering math using auto-render... (attempt #${window.MATH_RENDER_COUNT})`);
  54. try {
  55. // 1. 优先渲染 .math-render 元素 (通常是经过后端处理的)
  56. const elements = document.querySelectorAll('.math-render');
  57. let renderedCount = 0;
  58. elements.forEach(elem => {
  59. // 避免重复渲染
  60. if (elem.dataset.rendered === 'true') {
  61. return;
  62. }
  63. try {
  64. // 预处理内容
  65. let originalContent = elem.textContent || elem.innerHTML || '';
  66. if (originalContent.includes('$') || originalContent.includes('\\(') || originalContent.includes('\\[')) {
  67. // 使用预处理器标准化内容
  68. const processedContent = window.MathFormulaProcessor ?
  69. window.MathFormulaProcessor.processContent(originalContent) : originalContent;
  70. // 避免重复处理
  71. if (processedContent !== originalContent) {
  72. if (elem.innerHTML !== processedContent) {
  73. elem.innerHTML = processedContent;
  74. }
  75. }
  76. renderMathInElement(elem, renderOptions);
  77. elem.dataset.rendered = 'true';
  78. renderedCount++;
  79. }
  80. } catch (e) {
  81. console.error('Auto-render failed for element:', elem, e);
  82. }
  83. });
  84. // 2. 只渲染特定的容器,避免全局渲染导致死循环
  85. const containers = [
  86. '.question-content',
  87. '.math-content',
  88. '.ocr-content',
  89. '.question-text'
  90. ];
  91. containers.forEach(selector => {
  92. const container = document.querySelector(selector);
  93. if (container && container.dataset.mathRendered !== 'true') {
  94. try {
  95. // 检查是否包含数学公式
  96. const content = container.textContent || '';
  97. if (content.includes('$') || content.includes('\\(') || content.includes('\\[')) {
  98. renderMathInElement(container, renderOptions);
  99. container.dataset.mathRendered = 'true';
  100. renderedCount++;
  101. }
  102. } catch (e) {
  103. console.warn('Auto-render failed for container:', selector, e);
  104. }
  105. }
  106. });
  107. console.log(`Rendered ${renderedCount} math elements (total attempts: ${window.MATH_RENDER_COUNT})`);
  108. } finally {
  109. // 延迟解锁,避免立即再次触发
  110. setTimeout(() => {
  111. window.MATH_RENDER_LOCK = false;
  112. }, 50);
  113. }
  114. }
  115. // 初始化
  116. document.addEventListener('DOMContentLoaded', () => {
  117. // 稍微延迟以确保脚本执行顺序
  118. setTimeout(checkAndRender, 100);
  119. });
  120. function checkAndRender() {
  121. if (typeof window.katex !== 'undefined' && typeof renderMathInElement !== 'undefined') {
  122. console.log('KaTeX and auto-render loaded');
  123. initMathRenderer();
  124. } else {
  125. console.log('Waiting for KaTeX/auto-render...');
  126. setTimeout(checkAndRender, 100);
  127. }
  128. }
  129. function initMathRenderer() {
  130. renderAllMath();
  131. setupObservers();
  132. }
  133. function setupObservers() {
  134. const observer = new MutationObserver((mutations) => {
  135. let shouldRender = false;
  136. let hasMathContent = false;
  137. mutations.forEach((mutation) => {
  138. if (mutation.addedNodes.length > 0) {
  139. mutation.addedNodes.forEach((node) => {
  140. if (node.nodeType === 1) { // Element node
  141. // 检查是否包含数学公式标识符
  142. const textContent = node.textContent || '';
  143. if (textContent.includes('$') ||
  144. textContent.includes('\\(') ||
  145. textContent.includes('\\[') ||
  146. node.querySelector('.math-render') ||
  147. node.querySelector('.question-content') ||
  148. node.querySelector('.math-content') ||
  149. node.querySelector('.ocr-content') ||
  150. node.querySelector('.question-text')) {
  151. hasMathContent = true;
  152. shouldRender = true;
  153. }
  154. }
  155. });
  156. }
  157. });
  158. if (shouldRender && hasMathContent) {
  159. if (window.mathRenderTimeout) clearTimeout(window.mathRenderTimeout);
  160. window.mathRenderTimeout = setTimeout(() => {
  161. console.log('DOM mutation detected with math content');
  162. renderAllMath();
  163. }, 200); // 增加延迟,避免频繁触发
  164. }
  165. });
  166. // 只观察特定容器,减少全局观察的性能影响
  167. const containers = [
  168. '.question-content',
  169. '.math-content',
  170. '.ocr-content',
  171. '.question-text',
  172. '.math-render'
  173. ];
  174. containers.forEach(selector => {
  175. const elements = document.querySelectorAll(selector);
  176. elements.forEach(element => {
  177. observer.observe(element, {
  178. childList: true,
  179. subtree: true
  180. });
  181. });
  182. });
  183. // 限制全局观察的范围,只在必要时启用
  184. // observer.observe(document.body, {
  185. // childList: true,
  186. // subtree: true
  187. // });
  188. }
  189. // Livewire 兼容性 - 添加防抖机制
  190. function debouncedRender() {
  191. if (window.mathRenderDebounceTimeout) {
  192. clearTimeout(window.mathRenderDebounceTimeout);
  193. }
  194. window.mathRenderDebounceTimeout = setTimeout(() => {
  195. renderAllMath();
  196. }, 150);
  197. }
  198. document.addEventListener('livewire:initialized', () => {
  199. setTimeout(debouncedRender, 50);
  200. });
  201. if (typeof Livewire !== 'undefined' && Livewire.hook) {
  202. Livewire.hook('morph.updated', ({ el, component }) => {
  203. debouncedRender();
  204. });
  205. Livewire.hook('commit', ({ component, commit, respond, succeed, fail }) => {
  206. succeed(({ snapshot, effect }) => {
  207. debouncedRender();
  208. });
  209. });
  210. }
  211. document.addEventListener('livewire:navigated', () => {
  212. // 重置计数器,允许新页面重新开始渲染
  213. window.MATH_RENDER_COUNT = 0;
  214. setTimeout(debouncedRender, 100);
  215. });
  216. document.addEventListener('alpine:init', () => {
  217. setTimeout(debouncedRender, 50);
  218. });
  219. document.addEventListener('math:render', () => {
  220. debouncedRender();
  221. });
  222. })();
  223. </script>
  224. @endpush
  225. @push('styles')
  226. <link rel="stylesheet" href="/css/katex/katex.min.css">
  227. @endpush