Greasy Fork 还支持 简体中文。

LaTeX Unicode Shortcuts

Highlight text then press [ALT+X] to convert LaTeX commands to their unicode equivalent (ex. \pi → π)

目前為 2020-08-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name LaTeX Unicode Shortcuts
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Highlight text then press [ALT+X] to convert LaTeX commands to their unicode equivalent (ex. \pi → π)
  6. // @author eyl327
  7. // @match *://*/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. var convert;
  15.  
  16. var dictLoaded = false;
  17.  
  18. /* source url for shortcut file */
  19. var dictionarySource = "https://raw.githubusercontent.com/eyl327/LaTeX-Gboard-Dictionary/master/dictionary.txt";
  20.  
  21. /* fetch text file when requested */
  22. function loadAsset(url, callback) {
  23. var xhr = new XMLHttpRequest();
  24. xhr.open("GET", url, false);
  25. xhr.onreadystatechange = function () {
  26. if (xhr.readyState === 4) {
  27. if (xhr.status === 200 || xhr.status === 0) {
  28. callback(xhr.responseText);
  29. }
  30. }
  31. }
  32. xhr.send();
  33. }
  34.  
  35. /* on dictionary loaded callback */
  36. function loaded(response) {
  37. console.log("LaTeX Unicode Shortcuts has been loaded.");
  38. /* generate dictionary from text file */
  39. var dictArr = response.split("\n").slice(1);
  40. var dictionary = {};
  41. for (var i = 0, len = dictArr.length; i < len; ++i) {
  42. var kvp = dictArr[i].split("\t");
  43. dictionary[kvp[0]] = kvp[1];
  44. }
  45. /* conversion function */
  46. convert = function (text) {
  47. var result = text.replace(/{([A-Za-z0-9])}/g, '$1'); // {R} => R
  48. for (var key in dictionary) {
  49. var pattern = new RegExp(key.replace(/([[^$.|\\?*+(){}])/g, '\\$1') + "\\b", 'g'); // clean and escape key
  50. var replaced = result.replace(pattern, dictionary[key]);
  51. if (replaced.length < result.length) {
  52. result = replaced;
  53. }
  54. }
  55. return result;
  56. };
  57. dictLoaded = true;
  58. }
  59.  
  60. /* get caret position within input box */
  61. function getCaretPositionInputBox(el) {
  62. if ("selectionStart" in el && document.activeElement == el) {
  63. return {
  64. start: el.selectionStart,
  65. end: el.selectionEnd
  66. };
  67. }
  68. else if (el.createTextRange) {
  69. var sel = document.selection.createRange();
  70. if (sel.parentElement() === el) {
  71. var range = el.createTextRange();
  72. range.moveToBookmark(sel.getBookmark());
  73. for (var len = 0;
  74. range.compareEndPoints("EndToStart", range) > 0;
  75. range.moveEnd("character", -1)) {
  76. len++;
  77. }
  78. range.setEndPoint("StartToStart", el.createTextRange());
  79. for (var pos = { start: 0, end: len };
  80. range.compareEndPoints("EndToStart", range) > 0;
  81. range.moveEnd("character", -1)) {
  82. pos.start++;
  83. pos.end++;
  84. }
  85. return pos;
  86. }
  87. }
  88. return -1;
  89. }
  90.  
  91. /* set caret position within input box */
  92. function setCaretPosition(el, pos) {
  93. if (el.setSelectionRange) {
  94. el.focus();
  95. el.setSelectionRange(pos, pos);
  96. }
  97. else if (el.createTextRange) {
  98. var range = el.createTextRange();
  99. range.collapse(true);
  100. range.moveEnd('character', pos);
  101. range.moveStart('character', pos);
  102. range.select();
  103. }
  104. }
  105.  
  106. function overwriteInputBoxText(activeEl, before, convertedText, after) {
  107. // overwrite text
  108. activeEl.value = before + convertedText + after;
  109. // set cursor to be at end of selection
  110. setCaretPosition(activeEl, before.length + convertedText.length);
  111. }
  112.  
  113. function replaceConversionInElement(activeEl, fullText, start, end) {
  114. var textToConvert = fullText.substring(start, end);
  115. var before = fullText.substring(0, start);
  116. var after = fullText.substring(end, fullText.length);
  117. // convert selection
  118. var convertedText = convert(textToConvert);
  119. if ("value" in activeEl) {
  120. overwriteInputBoxText(activeEl, before, convertedText, after);
  121. }
  122. }
  123.  
  124. /* convert hilighted text in active element */
  125. function convertSelectionInputBox(activeEl) {
  126. var caretRange = getCaretPositionInputBox(activeEl);
  127. var selStart = caretRange.start;
  128. var selEnd = caretRange.end;
  129. var fullText = activeEl.value;
  130. /* if selection is empty, find word at caret */
  131. if (selStart == selEnd) {
  132. // Find beginning and end of word
  133. var left = fullText.slice(0, selStart + 1).search(/\S+$/);
  134. var right = fullText.slice(selStart).search(/(\s|$)/);
  135. /* convert the word at the caret selection */
  136. replaceConversionInElement(activeEl, fullText, left, right + selStart)
  137. }
  138. /* else convert the selection */
  139. else {
  140. replaceConversionInElement(activeEl, fullText, selStart, selEnd);
  141. }
  142. }
  143.  
  144. /* convert hilighted text in active element */
  145. function convertSelectionContentEditable(element) {
  146. var NodeTree = {
  147. // Used to find all DOM nodes in window.getSelection()
  148. getInnerNodes: function (anchor, focus) {
  149. var ancestor = NodeTree.lowestCommonAncestor(anchor, focus);
  150. var childList = NodeTree.findChildrenList(ancestor);
  151. var [i, j] = [childList.indexOf(anchor), childList.indexOf(focus)].sort();
  152. return childList.slice(i, j + 1);
  153. },
  154. getNodeChain: function (node) {
  155. var chain = [];
  156. chain.push(node);
  157. while (node.parentNode) {
  158. node = node.parentNode;
  159. chain.push(node);
  160. }
  161. return chain.reverse();
  162. },
  163. lowestCommonAncestor: function (anchor, focus) {
  164. var uChain = NodeTree.getNodeChain(anchor);
  165. var vChain = NodeTree.getNodeChain(focus);
  166. var i;
  167. for (i = 0; i < uChain.length; i++) {
  168. if (uChain[i] !== vChain[i]) {
  169. break
  170. }
  171. }
  172. return uChain[i - 1]
  173. },
  174. findChildrenList: function (node) {
  175. var list = []
  176. var find = function (n) {
  177. if (!n) {
  178. return;
  179. }
  180. list.push(n);
  181. for (var child of Array.from(n.childNodes || [])) {
  182. find(child);
  183. }
  184. }
  185. find(node);
  186. return list;
  187. }
  188. }
  189.  
  190. var sel = element.ownerDocument.getSelection();
  191.  
  192. var selAN = sel.anchorNode;
  193. var selFN = sel.focusNode;
  194.  
  195. var nodesBetweenNodes = NodeTree.getInnerNodes(selAN, selFN);
  196.  
  197. var startNode = nodesBetweenNodes[0];
  198. var endNode = nodesBetweenNodes[nodesBetweenNodes.length - 1];
  199.  
  200. var selAO = sel.anchorOffset;
  201. var selFO = sel.focusOffset;
  202.  
  203. var [startCursor, endCursor] = (startNode === selAN && selAO <= selFO) ? [selAO, selFO] : [selFO, selAO];
  204.  
  205. var cursor;
  206.  
  207. for (var node of nodesBetweenNodes) {
  208. if (node.nodeType === 3) { // 3 = text type
  209. var selStart = (node === nodesBetweenNodes[0]) ? startCursor : 0;
  210. var selEnd = (node === nodesBetweenNodes[nodesBetweenNodes.length - 1]) ? endCursor : node.nodeValue.length;
  211. var text = node.nodeValue;
  212. selEnd = Math.min(text.length, selEnd);
  213.  
  214. var convertStart = selStart;
  215. var convertEnd = selEnd;
  216.  
  217. // cursor is not a hilighted selection
  218. if (selStart == selEnd) {
  219. // Find beginning and end of word
  220. convertStart = text.slice(0, selStart + 1).search(/\S+$/);
  221. convertEnd = text.slice(selEnd).search(/(\s|$)/) + selStart;
  222. }
  223.  
  224. /* convert the word at the caret selection */
  225. var textToConvert = text.substring(convertStart, convertEnd);
  226. var before = text.substring(0, convertStart);
  227. var after = text.substring(convertEnd, text.length);
  228. var convertedText = convert(textToConvert);
  229.  
  230. // replace in element
  231. var result = before + convertedText + after;
  232. cursor = Math.min(result.length, before.length + convertedText.length);
  233. node.nodeValue = result;
  234. }
  235. }
  236. sel.collapse(endNode, cursor)
  237. }
  238.  
  239. /* detect ALT+X keyboard shortcut */
  240. function enableLaTeXShortcuts(event) {
  241. if (event.altKey && event.keyCode == 88) { // ALT+X
  242. // load dictionary when first pressed
  243. if (!dictLoaded) {
  244. loadAsset(dictionarySource, loaded);
  245. }
  246. // convert selection
  247. var activeEl = document.activeElement;
  248. var activeElTag = activeEl.tagName.toLowerCase();
  249. if (activeElTag == "textarea" || activeElTag == "input") {
  250. convertSelectionInputBox(activeEl);
  251. }
  252. else if (activeEl.contentEditable) {
  253. convertSelectionContentEditable(activeEl);
  254. }
  255. }
  256. }
  257.  
  258. document.addEventListener('keydown', enableLaTeXShortcuts, true);
  259.  
  260. })();