TexCopyer

双击网页中的LaTex公式,将其复制到剪切板

  1. // ==UserScript==
  2. // @name TexCopyer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @license GPLv3
  6. // @description 双击网页中的LaTex公式,将其复制到剪切板
  7. // @description:en Double click on a LaTeX formula on a webpage to copy it to the clipboard
  8. // @author yjy
  9. // @match *://*.wikipedia.org/*
  10. // @match *://*.zhihu.com/*
  11. // @match *://*.chatgpt.com/*
  12. // @match *://*.moonshot.cn/*
  13. // @match *://*.stackexchange.com/*
  14. // @match *://oi-wiki.org/*
  15. // @match *://*.luogu.com/*
  16. // @match *://*.luogu.com.cn/*
  17. // @match *://*.doubao.com/*
  18. // @match *://*.deepseek.com/*
  19. // @match *://*.chatboxai.app/*
  20. // @match *://ieeexplore.ieee.org/*
  21. // ==/UserScript==
  22.  
  23. (function () {
  24. 'use strict';
  25. // 插入样式表
  26. const css = `
  27. .latex-tooltip { position: fixed; background-color: rgba(0, 0, 0, 0.7); color: #fff; padding: 5px 10px; border-radius: 5px; font-size: 11px; z-index: 1000; opacity: 0; transition: opacity 0.2s; pointer-events: none; }
  28. .latex-copy-success { position: fixed; bottom: 10%; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.7); color: #fff; padding: 10px 20px; border-radius: 5px; font-size: 12px; z-index: 1000; transition: opacity 0.2s; pointer-events: none; }
  29. `;
  30. const styleSheet = document.createElement("style");
  31. styleSheet.type = "text/css";
  32. styleSheet.innerText = css;
  33. document.head.appendChild(styleSheet);
  34.  
  35. // 创建提示框元素
  36. const tooltip = document.createElement('div');
  37. tooltip.classList.add('latex-tooltip');
  38. document.body.appendChild(tooltip);
  39.  
  40. // 获取对象和公式方法
  41. function getTarget(url) {
  42. let target = { elementSelector: '', getLatexString: null }
  43. // 格式化latex
  44. function formatLatex(input) {
  45. while (input.endsWith(' ') || input.endsWith('\\')) {
  46. input = input.slice(0, -1);
  47. }
  48. return '$' + input + '$';
  49. }
  50. if (url.includes('wikipedia.org')) {
  51. target.elementSelector = 'span.mwe-math-element';
  52. target.getLatexString = (element) => formatLatex(element.querySelector('math').getAttribute('alttext'));
  53. return target
  54.  
  55. } else if (url.includes('zhihu.com')) {
  56. target.elementSelector = 'span.ztext-math';
  57. target.getLatexString = (element) => formatLatex(element.getAttribute('data-tex'));
  58. return target
  59.  
  60. } else if (url.includes('chatgpt.com')) {
  61. target.elementSelector = 'span.katex';
  62. target.getLatexString = (element) => formatLatex(element.querySelector('annotation').textContent);
  63. return target
  64.  
  65. } else if (url.includes('moonshot.cn')) {
  66. target.elementSelector = 'span.katex';
  67. target.getLatexString = (element) => formatLatex(element.querySelector('annotation').textContent);
  68. return target
  69. } else if (url.includes('stackexchange.com')) {
  70. target.elementSelector = 'span.math-container';
  71. target.getLatexString = (element) => formatLatex(element.querySelector('script').textContent);
  72. return target
  73. }
  74. else if (url.includes('oi-wiki.org')) {
  75. target.elementSelector = 'mjx-container.MathJax';
  76. target.getLatexString = (element) => formatLatex(element.querySelector('img').title);
  77. return target
  78. }
  79. else if (url.includes('luogu.com')) {
  80. target.elementSelector = 'span.katex';
  81. target.getLatexString = (element) => formatLatex(element.querySelector('annotation').textContent);
  82. return target
  83. }
  84. else if (url.includes('doubao.com')) {
  85. target.elementSelector = 'span.math-inline';
  86. target.getLatexString = (element) => formatLatex(element.getAttribute('data-custom-copy-text'));
  87. return target
  88. }
  89. else if (url.includes('deepseek.com')) {
  90. target.elementSelector = 'span.katex';
  91. target.getLatexString = (element) => formatLatex(element.querySelector('annotation').textContent);
  92. return target
  93. }
  94. else if (url.includes('chatboxai.app')) {
  95. target.elementSelector = 'span.katex';
  96. target.getLatexString = (element) => formatLatex(element.querySelector('annotation').textContent);
  97. return target
  98. }
  99. else if (url.includes('ieeexplore.ieee.org')) {
  100. target.elementSelector = 'span[id^="MathJax-Element-"][id$="-Frame"]';
  101. target.getLatexString = (element) => {
  102. // 提取元素ID中的编号部分(支持数字+字母组合)
  103. const idMatch = element.id.match(/Element-(\w+)-Frame/);
  104. if (!idMatch) return '';
  105.  
  106. const elementNumber = idMatch[1];
  107. const scriptId = `MathJax-Element-${elementNumber}`;
  108.  
  109. // 获取对应的script元素
  110. const scriptElement = document.getElementById(scriptId);
  111. if (!scriptElement) return '';
  112.  
  113. let latex = scriptElement.textContent;
  114.  
  115. // 特殊格式处理
  116. latex = latex.replace(/\\begin\{equation\*\}/g, '\\[\\begin{array}{l}');
  117. latex = latex.replace(/\\end\{equation\*\}/g, '\\end{array}\\]');
  118. // 处理标签 \tag{数字}
  119. latex = latex.replace(/\\tag\{(\w+)\}/g, '\\\\');
  120.  
  121. return formatLatex(latex);
  122. };
  123. return target;
  124. }
  125. // 待添加更多网站的条件
  126. return null;
  127. }
  128.  
  129. // 绑定事件到元素
  130. function addHandler() {
  131. let target = getTarget(window.location.href);
  132. if (!target) return;
  133.  
  134. let tooltipTimeout;
  135. document.querySelectorAll(target.elementSelector).forEach(element => {
  136. element.addEventListener('mouseenter', function () {
  137. element.style.cursor = "pointer";
  138. tooltipTimeout = setTimeout(function () {
  139. tooltip.textContent = getTarget(window.location.href).getLatexString(element);;
  140. const rect = element.getBoundingClientRect();
  141. tooltip.style.left = `${rect.left}px`;
  142. tooltip.style.top = `${rect.top - tooltip.offsetHeight - 5}px`;
  143. tooltip.style.display = 'block';
  144. tooltip.style.opacity = '0.8';
  145. }, 1000); // 进入延迟1秒
  146. });
  147.  
  148. element.addEventListener('mouseleave', function () {
  149. element.style.cursor = "auto";
  150. clearTimeout(tooltipTimeout);
  151. tooltip.style.display = 'none';
  152. tooltip.style.opacity = '0';
  153. });
  154.  
  155. element.ondblclick = function () {
  156. const latexString = target.getLatexString(element)
  157. if (latexString !== null) {
  158. console.log(`LaTeX copied: ${latexString}`) // for debug
  159. navigator.clipboard.writeText(latexString).then(() => {
  160. showCopySuccessTooltip();
  161. });
  162. }
  163. // 取消网页上的选中状态(不是很优雅)
  164. window.getSelection().removeAllRanges();
  165. };
  166. });
  167. }
  168.  
  169. // 显示复制成功提示
  170. function showCopySuccessTooltip() {
  171. const copyTooltip = document.createElement("div");
  172. copyTooltip.className = "latex-copy-success";
  173. copyTooltip.innerText = "已复制LaTeX公式";
  174. document.body.appendChild(copyTooltip);
  175. setTimeout(() => {
  176. copyTooltip.style.opacity = "0";
  177. setTimeout(() => {
  178. document.body.removeChild(copyTooltip);
  179. }, 200); // 与transition时间匹配
  180. }, 1000); // 提示短时间后消失
  181. }
  182.  
  183. // 监听页面加载或变化,绑定事件
  184. document.addEventListener('DOMContentLoaded', addHandler);
  185. new MutationObserver(addHandler).observe(document.documentElement, { childList: true, subtree: true });
  186.  
  187.  
  188. })();

QingJ © 2025

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