LaTeX Copier

Copy selected text with equations in LaTeX format (Short cut: Ctrl+Alt+Shift+C).

  1. // ==UserScript==
  2. // @name LaTeX Copier
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.9.6
  5. // @description Copy selected text with equations in LaTeX format (Short cut: Ctrl+Alt+Shift+C).
  6. // @author Jie-Qiao
  7. // @match *://*.wikipedia.org/*
  8. // @match *://*.stackexchange.com/*
  9. // @match *://alejandroschuler.github.io/*
  10. // @match *://*.zhihu.com/*
  11. // @match *://*.arxiv.org/*
  12. // @match *://*.ar5iv.org/*
  13. // @match *://*.csdn.net/*
  14. // @match *://chatgpt.com/*
  15. // @match *://chat.deepseek.com/*
  16. // @match *://amreis.github.io/*
  17. // @grant none
  18. // @license MIT
  19. // ==/UserScript==
  20.  
  21. function decodeHTMLEntities(text) {
  22. const parser = new DOMParser();
  23. const decodedString = parser.parseFromString(text, 'text/html').documentElement.textContent;
  24. return decodedString;
  25. }
  26.  
  27.  
  28. function getTextContentWithReplacements(url,node) {
  29. let text = '';
  30. if (node && node.childNodes) {
  31. node.childNodes.forEach(child => {
  32. let breakout=false;
  33. const nodeType = child.nodeType;
  34. const nodeName = child.nodeName.toLowerCase();
  35. // Default behavior for text nodes
  36. if (child.nodeType === Node.TEXT_NODE) {
  37. text += child.textContent;
  38. }
  39. if (nodeType === Node.ELEMENT_NODE){
  40. // URL-specific processing rules
  41. if (url.includes('wikipedia.org')) {
  42. if (nodeName === 'span') {
  43. if (child.querySelectorAll('math').length > 0) {
  44. breakout=true;
  45. text += '$' + child.getElementsByTagName('math')[0].getAttribute('alttext') + '$';
  46. } else if (child.querySelectorAll('img').length > 0) {
  47. breakout=true;
  48. text += '$' + child.querySelectorAll('img')[0].getAttribute('alt') + '$';
  49. }
  50. }
  51. } else if (url.includes('stackexchange.com') || url.includes('zhihu.com') || url.includes('amreis.github.io')) {
  52. if (nodeName === 'span') {
  53. breakout=true;
  54. if (child.getElementsByTagName('script').length > 0) {
  55. text += '$' + child.getElementsByTagName('script')[0].textContent + '$';
  56. }
  57. }
  58. else if (nodeName === 'script') {
  59. text += '$' + child.textContent + '$';
  60. }
  61. } else if (url.includes('chatgpt.com')||url.includes('alejandroschuler.github.io')||url.includes('chat.deepseek.com')) {
  62. if (nodeName === 'span') {
  63. if (child.getElementsByTagName('annotation').length > 0) {
  64. breakout=true;
  65. text += '$' + child.getElementsByTagName('annotation')[0].textContent + '$';
  66. }
  67. }
  68. } else if (url.includes('arxiv.org')|| url.includes('ar5iv.org')){
  69. if (nodeName === 'math'){
  70. text += '$' + child.getAttribute('alttext') + '$'
  71. }
  72. } else if (url.includes('csdn.net')){
  73. if (nodeName === 'span' && (child.getAttribute('class')==="katex--display" || child.getAttribute('class')==="katex--inline")){
  74. const temp=child.getElementsByClassName("katex-mathml")[0].textContent
  75. const terms = temp.split('\n')
  76. .map(line => line.trim())
  77. .filter(line => line.length > 0);
  78. // Return the last non-empty term, or empty string if none found
  79. if (terms.length>0){
  80. text += '$' + terms[terms.length - 1] + '$'
  81. }
  82. }
  83. }
  84. }
  85. // For other elements, recurse into their children
  86. if (!breakout && child.nodeType === Node.ELEMENT_NODE && !['script', 'math', 'img'].includes(child.nodeName.toLowerCase())) {
  87. text += getTextContentWithReplacements(url,child);
  88. }
  89. });
  90. }
  91. text = text.replace(/\n+/g, '\n').trim();
  92. text = text.replace(/\\bm\{([^}]+)\}/g, "\\mathbf{$1}");
  93. text = text.replace(/\\bigg\{\|\}/g, "\\Bigl|");
  94. text = text.replace(/\\big\{\|\}/g, "\\big|");
  95. text = decodeHTMLEntities(text);
  96. return text
  97. }
  98.  
  99. (function() {
  100. 'use strict';
  101.  
  102. // Create the button element
  103. const button = document.createElement('button');
  104. button.textContent = 'Copy';
  105. button.style.position = 'absolute';
  106. button.style.zIndex = '2147483647'; // High z-index
  107. button.style.display = 'none'; // Initially hidden
  108. button.style.border = '1px solid black'; // For visibility
  109. button.style.padding = '5px 10px'; // For better appearance
  110. button.style.fontSize = '14px'; // For better appearance
  111. button.style.cursor = 'pointer'; // Visual feedback
  112. button.style.visibility = 'visible'; // Ensure visibility
  113. button.style.opacity = '1'; // Ensure opacity
  114.  
  115. // Append the button to the body
  116. document.body.appendChild(button);
  117.  
  118. // Function to handle button click
  119. button.addEventListener('click', function() {
  120. const selectedText = window.getSelection();
  121. if (selectedText) {
  122. myfunction(selectedText);
  123. }
  124. button.style.display = 'none'; // Hide the button after click
  125. });
  126.  
  127. let previousSelectedText = '';
  128.  
  129. document.addEventListener('mouseup', function(event) {
  130. const selectedText = window.getSelection().toString().trim();
  131.  
  132. // Only show the button if the selected text has changed
  133. if (selectedText && selectedText !== previousSelectedText) {
  134. const x = event.pageX + 5;
  135. const y = event.pageY + 5;
  136. button.style.left = `${x}px`;
  137. button.style.top = `${y}px`;
  138. button.style.display = 'block';
  139. previousSelectedText = selectedText; // Update the previous selection
  140. //console.log(button)
  141. } else {
  142. button.style.display = 'none';
  143. previousSelectedText = ''; // Reset previous selection if no text is selected
  144. }
  145. });
  146.  
  147.  
  148. // Custom function to process the selected text
  149. function myfunction(selection) {
  150. let url = window.location.href
  151. let range = selection.getRangeAt(0);
  152. //console.log('Text selected:', selection.toString()); // Log selected text
  153. let c=range.cloneContents();
  154. let text = getTextContentWithReplacements(url,c);
  155. navigator.clipboard.writeText(text);
  156. }
  157.  
  158. document.addEventListener('keydown', function(event) {
  159. if (event.shiftKey && event.ctrlKey && event.altKey && event.key === 'C') {
  160. //event.preventDefault(); // Prevent default browser behavior (like copying)
  161. const selectedText = window.getSelection();
  162. if (selectedText) {
  163. myfunction(selectedText);
  164. }
  165. }
  166. })
  167.  
  168.  
  169. })();