LaTeX for LIHKG

This is to convert LaTeX to image in LIHKG posts

  1. // ==UserScript==
  2. // @name LaTeX for LIHKG
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.6
  5. // @description This is to convert LaTeX to image in LIHKG posts
  6. // @author CY Fung
  7. // @match https://lihkg.com/*
  8. // @icon https://avatars.githubusercontent.com/u/431808?s=48&v=4
  9. // @grant none
  10. // @run-at document-body
  11. // @license Apache 2.0
  12. // ==/UserScript==
  13.  
  14. 'use strict';
  15. (function() {
  16. 'use strict';
  17.  
  18. /*
  19. * Copyright (C) 2023 culefa.
  20. *
  21. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  22. * use this file except in compliance with the License. You may obtain a copy of
  23. * the License at
  24. *
  25. * http://www.apache.org/licenses/LICENSE-2.0
  26. *
  27. * Unless required by applicable law or agreed to in writing, software
  28. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  29. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  30. * License for the specific language governing permissions and limitations under
  31. * the License.
  32. */
  33.  
  34. // Check if the current hostname ends with 'lihkg.com'
  35. if (!location.hostname.endsWith('lihkg.com')) return;
  36.  
  37. const MAX_QUERY_LENGTH = 800; // 2000 for encoded
  38.  
  39. // Create an image element for LaTeX rendering
  40. const createLatexNode = (t) => {
  41. if (typeof t != 'string') return null;
  42. const img = document.createElement('img');
  43. img.className = 'lihkg-userscript-latex';
  44. img.loading = 'lazy';
  45. let s = t.length > MAX_QUERY_LENGTH ? t.substring(0, MAX_QUERY_LENGTH) : t;
  46. img.src = `https://math.now.sh?bgcolor=auto&from=${encodeURIComponent(s)}`;
  47. let title = `[latex]${t}[/latex]`;
  48. img.setAttribute('alt', title);
  49. img.setAttribute('title', title);
  50. return img;
  51. };
  52.  
  53. const validatedMap = new Map();
  54.  
  55. const validateLaTeX = (t) => {
  56. let r = validatedMap.get(t);
  57. if (typeof r == 'boolean') return r;
  58. r = true;
  59. if (/\:\/\/|\:\\\\|\<(script|h5|h4|h3|h2|h1|span|div|br|pre|quote|img|table|tr|td)\>|\b(javascript)\b|\`\`\`/i.test(t)) r = false;
  60. validatedMap.set(t, r);
  61. return r;
  62. }
  63.  
  64. // Process a text node to replace LaTeX tags with image elements
  65. const processTextNode = (textNode) => {
  66. if (!textNode) return;
  67. let textContent = textNode.textContent;
  68. if (typeof textContent === 'string' && textContent.length > 15) {} else {
  69. return;
  70. }
  71.  
  72. textContent = textContent.trim();
  73.  
  74. // Check if the text content is long enough and has LaTeX tags
  75. if (textContent.length > 15) {} else {
  76. return;
  77. }
  78. if (textContent.indexOf('[latex]') < 0) return;
  79. const split = textContent.split(/\[latex\]((?:(?!\[latex\]|\[\/latex\]).)*)\[\/latex\]/g);
  80.  
  81. // Check if the split array has an odd length (latex tags are found)
  82. if (split.length >= 3 && (split.length % 2) === 1) {
  83. const newNodes = split.map((t, j) => ((((j % 2) === 0) || !validateLaTeX(t)) ? document.createTextNode(t) : createLatexNode(t)));
  84. textNode.replaceWith(...newNodes);
  85. }
  86.  
  87. };
  88.  
  89. // Check a div element and process its text nodes
  90. const checkDivDOM = (div) => {
  91. let textNode = div.firstChild;
  92. while (textNode) {
  93. if (textNode.nodeType === Node.TEXT_NODE) {
  94. processTextNode(textNode);
  95. }
  96. textNode = textNode.nextSibling;
  97. }
  98. }
  99.  
  100. // Check a post div for LaTeX tags and process its children divs
  101. const checkPostDiv = (postDiv) => {
  102. const html = postDiv.innerHTML;
  103. if (html.indexOf('[latex]') >= 0) {
  104. const divs = postDiv.querySelectorAll('div[class]:not(:empty), div[data-ast-root]:not(:empty)');
  105. if (divs.length >= 1) {
  106. for (const div of divs) {
  107. checkDivDOM(div);
  108. }
  109. } else {
  110. checkDivDOM(postDiv);
  111. }
  112. }
  113. };
  114.  
  115. // Delayed check for processing post divs
  116. function delayedCheck(arr) {
  117. window.requestAnimationFrame(() => {
  118. for (const s of arr) {
  119. checkPostDiv(s);
  120. }
  121. });
  122. }
  123.  
  124. // Create an observer to check for new posts and previews
  125. const observer = new MutationObserver((mutations) => {
  126. if (!location.pathname.startsWith('/thread/')) return;
  127. let arr = [];
  128. for (const s of document.querySelectorAll('[data-post-id]:not(.y24Yt), ._3rEWfQ3U63bl18JSaUvRX7 .GAagiRXJU88Nul1M7Ai0H:not(.y24Yt)')) {
  129. s.classList.add('y24Yt');
  130. arr.push(s);
  131. }
  132. if (arr.length >= 1) delayedCheck(arr);
  133. });
  134.  
  135. // Start observing the body element for changes
  136. observer.observe(document.body, {
  137. subtree: true,
  138. childList: true
  139. });
  140.  
  141. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址