Auto LaTeX Renderer

Converts LaTeX on web pages into formatted math using MathJax

  1. // ==UserScript==
  2. // @name Auto LaTeX Renderer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.7
  5. // @description Converts LaTeX on web pages into formatted math using MathJax
  6. // @match *://*/*
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. (function() {
  11. 'use strict';
  12.  
  13. // Set MathJax configuration to enable single dollar signs
  14. window.MathJax = {
  15. tex: {
  16. inlineMath: [['$', '$'], ['\\(', '\\)']],
  17. displayMath: [['$$', '$$'], ['\\[', '\\]']],
  18. processEscapes: true, // Allows \$ to escape dollar signs
  19. },
  20. options: {
  21. skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
  22. },
  23. };
  24.  
  25. // Function to add MathJax to the page
  26. function addMathJax(callback) {
  27. if (!window.MathJax || !window.MathJax.version) {
  28. let script = document.createElement('script');
  29. script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js';
  30. script.async = true;
  31. script.onload = function() {
  32. console.log('MathJax loaded');
  33. if (callback) callback();
  34. };
  35. script.onerror = function() {
  36. console.error('Failed to load MathJax. Please check your network or CDN availability.');
  37. // Retry in case of failure
  38. setTimeout(function(){ addMathJax(callback); }, 2000);
  39. };
  40. document.head.appendChild(script);
  41. } else {
  42. if (callback) callback();
  43. }
  44. }
  45.  
  46. // Function to scan the document and render LaTeX
  47. function renderLatex(root = document.body) {
  48. try {
  49. if (window.MathJax && MathJax.typesetPromise) {
  50. MathJax.typesetPromise([root]).catch(function (err) {
  51. console.error('MathJax typeset failed:', err);
  52. });
  53.  
  54. root.querySelectorAll('*').forEach(elem => {
  55. if (elem.shadowRoot) {
  56. renderLatex(elem.shadowRoot);
  57. }
  58. });
  59. } else {
  60. console.log('MathJax not loaded yet. Retrying in 500ms...');
  61. setTimeout(() => renderLatex(root), 500);
  62. }
  63. } catch (error) {
  64. console.error('Error during LaTeX rendering:', error);
  65. }
  66. }
  67.  
  68. // Initial injection after page fully loads
  69. window.addEventListener('load', function() {
  70. addMathJax(function() {
  71. // Initial render for existing content
  72. renderLatex();
  73.  
  74. // Observe the DOM for changes and render LaTeX
  75. const observer = new MutationObserver((mutations) => {
  76. // Throttle the renderLatex calls
  77. if (observer.renderTimeout) clearTimeout(observer.renderTimeout);
  78. observer.renderTimeout = setTimeout(() => {
  79. mutations.forEach(mutation => {
  80. if (mutation.addedNodes) {
  81. mutation.addedNodes.forEach(node => {
  82. if (node.nodeType === Node.ELEMENT_NODE) {
  83. renderLatex(node);
  84. }
  85. });
  86. }
  87. });
  88. }, 300);
  89. });
  90.  
  91. observer.observe(document.body, { childList: true, subtree: true });
  92.  
  93. document.querySelectorAll('*').forEach(elem => {
  94. if (elem.shadowRoot) {
  95. observer.observe(elem.shadowRoot, { childList: true, subtree: true });
  96. }
  97. });
  98. });
  99. });
  100.  
  101. })();