Greasy Fork 支持简体中文。

CC98 Tools - Math Editor

为CC98网页版添加数学公式支持

  1. // ==UserScript==
  2. // @name CC98 Tools - Math Editor
  3. // @namespace https://www.cc98.org/
  4. // @version 0.0.1
  5. // @description 为CC98网页版添加数学公式支持
  6. // @author ml98
  7. // @match https://www.cc98.org/*
  8. // @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
  9. // @require https://greasyfork.org/scripts/2199-waitforkeyelements/code/waitForKeyElements.js?version=6349
  10. // @grant GM_addStyle
  11. // ==/UserScript==
  12.  
  13. // ref
  14. // https://latex.codecogs.com/svg.latex?\log\prod^N_{i}x_{i}=\sum^N_i\log{x_i}
  15. // https://math.now.sh/?from=\\log\\prod^N_{i}x_{i}=\\sum^N_i\\log{x_i}
  16. // https://www.zhihu.com/equation?tex=~
  17.  
  18. // test https://www.cc98.org/topic/2803718/695#4
  19.  
  20. console.log("%cCC98 Tools Math Editor", "font-size: large");
  21.  
  22.  
  23. function addMathEditor(){
  24. 'use strict';
  25. console.log("addMathEditor");
  26. // add Math Editor modal
  27. console.log("add Math Editor modal");
  28. var myModal = document.createElement('div');
  29. myModal.id = "myModal";
  30. myModal.classList = "modal";
  31. if(1){myModal.innerHTML = String.raw`
  32. <!-- Modal content -->
  33. <div class="modal-content">
  34. <span class="close">&times;</span>
  35. <h3 id="header">Math Editor for CC98</h3>
  36. <div id="main">
  37. <div id="io">
  38. <div id="input">
  39. <p>Input</p>
  40. <textarea id="inputText" spellcheck="false">\LaTeX</textarea>
  41. <button id="copyInput" onclick="copy('inputText')">Copy</button>
  42. </div>
  43.  
  44. <div id="output">
  45. <p>Output</p>
  46. <textarea type="text" id="outputText" spellcheck="false" value=''></textarea>
  47. <button id="copyOutput" onclick="copy('outputText')">Copy</button>
  48. </div>
  49. </div>
  50. <br>
  51. <label style="display: inline-block;">Engine
  52. <select name="select" id="engineSelect">
  53. <option value="zhihu" selected>zhihu</option>
  54. <option value="codecogs">codecogs</option>
  55. <option value="math.now.sh">math.now.sh</option>
  56. </select>
  57. </label>
  58. <label style="display: inline-block;">Format
  59. <select name="select" id="formatSelect">
  60. <option value="Ubb" selected>Ubb</option>
  61. <option value="Markdown">Markdown</option>
  62. <option value="HTML">HTML</option>
  63. <option value="URL">URL</option>
  64. </select>
  65. </label>
  66.  
  67. <div id="preview">
  68. <p>Preview</p>
  69. <div id="imagebox">
  70. <img id="previewImage" src="" />
  71. </div>
  72. </div>
  73. </div>
  74. <div id="footer">
  75. <p>86ɔɔ ɹoɟ ɹoʇıpǝ ɥʇɐɯ</p>
  76. </div>
  77. </div>
  78. `;}
  79. document.body.appendChild(myModal);
  80.  
  81. // add style
  82. console.log("add Math Editor style");
  83. if(1){GM_addStyle(String.raw`
  84. /* The Modal (background) */
  85. .modal {
  86. display: none;
  87. /* Hidden by default */
  88. position: fixed;
  89. /* Stay in place */
  90. z-index: 1;
  91. /* Sit on top */
  92. padding-top: 50px;
  93. /* Location of the box */
  94. left: 0;
  95. top: 0;
  96. width: 100%;
  97. /* Full width */
  98. height: 100%;
  99. /* Full height */
  100. overflow: auto;
  101. /* Enable scroll if needed */
  102. background-color: rgb(0, 0, 0);
  103. /* Fallback color */
  104. background-color: rgba(0, 0, 0, 0.4);
  105. /* Black w/ opacity */
  106. }
  107.  
  108. /* Modal Content */
  109. .modal-content {
  110. background-color: #fefefe;
  111. margin: auto;
  112. padding: 20px;
  113. border: 1px solid #888;
  114. border-radius: 6px;
  115. width: 55%;
  116. height: 75%;
  117. position: relative;
  118. -webkit-animation-name: animatetop;
  119. -webkit-animation-duration: 0.4s;
  120. animation-name: animatetop;
  121. animation-duration: 0.4s
  122. }
  123.  
  124. /* Add Animation */
  125. @-webkit-keyframes animatetop {
  126. from {
  127. top: -300px;
  128. opacity: 0
  129. }
  130.  
  131. to {
  132. top: 0;
  133. opacity: 1
  134. }
  135. }
  136.  
  137. @keyframes animatetop {
  138. from {
  139. top: -300px;
  140. opacity: 0
  141. }
  142.  
  143. to {
  144. top: 0;
  145. opacity: 1
  146. }
  147. }
  148.  
  149. /* The Close Button */
  150. .close {
  151. color: #aaaaaa;
  152. float: right;
  153. font-size: 28px;
  154. font-weight: bold;
  155. }
  156.  
  157. .close:hover,
  158. .close:focus {
  159. color: #000;
  160. text-decoration: none;
  161. cursor: pointer;
  162. }
  163.  
  164. /* main style */
  165. #header {
  166. border-bottom: 1px solid #ccc;
  167. font-size: 2em;
  168. }
  169.  
  170. #main {
  171. max-height: 80%;
  172. overflow-y: auto;
  173. }
  174.  
  175. #input,
  176. #output {
  177. display: inline-block;
  178. vertical-align: top;
  179. }
  180.  
  181. #inputText,
  182. #outputText {
  183. width: 360px;
  184. height: 160px;
  185. font-size: medium;
  186. resize: both;
  187. padding-left: 3px;
  188. }
  189.  
  190. #copyInput,
  191. #copyOutput {
  192. display: block;
  193. }
  194.  
  195. #footer {
  196. position: absolute;
  197. bottom: 0;
  198. }
  199. `);}
  200.  
  201. // add Math Editor script
  202. console.log("add Math Editor script");
  203. var script = document.createElement('script');
  204. if(1){script.innerHTML = String.raw`
  205. // Get the modal
  206. var modal = document.getElementById("myModal");
  207.  
  208. // Get the button that opens the modal
  209. var btn = document.querySelector(".fa-math-editor");
  210.  
  211. // Get the <span> element that closes the modal
  212. var span = document.getElementsByClassName("close")[0];
  213.  
  214. // When the user clicks the button, open the modal
  215. // btn.onclick = function () {
  216. // update();
  217. // modal.style.display = "block";
  218. // }
  219.  
  220. // When the user clicks on <span> (x), close the modal
  221. span.onclick = function () {
  222. modal.style.display = "none";
  223. }
  224.  
  225. // When the user clicks anywhere outside of the modal, close it
  226. window.onclick = function (event) {
  227. if (event.target == modal) {
  228. modal.style.display = "none";
  229. }
  230. }
  231.  
  232. // main script
  233. const inputText = document.querySelector("#inputText");
  234. const previewImage = document.querySelector("#previewImage");
  235. const outputText = document.querySelector("#outputText");
  236. const engineSelect = document.querySelector("#engineSelect");
  237. const formatSelect = document.querySelector("#formatSelect");
  238.  
  239. inputText.addEventListener("input", delay(update, 1200));
  240. engineSelect.addEventListener("change", update);
  241. formatSelect.addEventListener("change", update);
  242. outputText.addEventListener("input", delay(analyse, 1200));
  243. // update();
  244.  
  245. function delay(callback, ms) {
  246. var timer = 0;
  247. return function () {
  248. clearTimeout(timer);
  249. timer = setTimeout(callback, ms);
  250. };
  251. }
  252.  
  253. // 编码 input -> url -> output
  254. function update() {
  255. const input = inputText.value;
  256. const engine = engineSelect.value;
  257. const format = formatSelect.value;
  258.  
  259. if (input === "") return;
  260.  
  261. console.log("update", input);
  262. const purifiedURL = input2Url(input, engine);
  263. previewImage.alt = input;
  264. if (previewImage.src !== purifiedURL) previewImage.src = purifiedURL;
  265. outputText.value = Url2Output(purifiedURL, format);
  266. }
  267.  
  268. function input2Url(input, engine) {
  269. switch (engine) {
  270. case "math.now.sh":
  271. return "https://math.now.sh?from=" + encode(input);
  272. case "zhihu":
  273. return (
  274. "https://www.zhihu.com/equation?tex=" +
  275. // encode(input)
  276. encode("\\bbox[white]{" + input + "}")
  277. );
  278. case "codecogs":
  279. return (
  280. "https://latex.codecogs.com/svg.latex?" +
  281. encode(input.replace(/^\s+|\s+$/g, "").replace(/\s+/g, " "))
  282. );
  283. default:
  284. break;
  285. }
  286. }
  287.  
  288. function encode(s) {
  289. return encodeURIComponent(s).replace(/[\-\_\.\!\~\*\'\(\)]/g, function (c) {
  290. return "%" + c.charCodeAt(0).toString(16).toUpperCase();
  291. });
  292. }
  293.  
  294. function Url2Output(url, format) {
  295. switch (format) {
  296. case "Ubb":
  297. return "[img]" + url + "[/img]";
  298. case "Markdown":
  299. return "![](" + url + ")";
  300. case "HTML":
  301. return '<img src="' + url + '"/>';
  302. case "URL":
  303. return url;
  304. default:
  305. break;
  306. }
  307. }
  308.  
  309. // 简单的反向解析 output -> url -> input
  310. function analyse() {
  311. const output = outputText.value;
  312. if (output === "") return;
  313.  
  314. console.log("analyse", output);
  315. const [format, url] = output2Url(output);
  316. const [engine, input] = url2Input(url);
  317. console.log(engine, format, input);
  318. previewImage.alt = input;
  319. if (previewImage.src !== url) previewImage.src = url;
  320. inputText.value = input;
  321. }
  322.  
  323. function output2Url(output) {
  324. if (output.match(/\[img\]/))
  325. return ["Ubb", output.replace(/\[img\]/, "").replace(/\[\/img\]/, "")];
  326. if (output.match(/\!\[/))
  327. return ["Markdown", output.replace(/\!\[.*?\]\(/, "").replace(/\)/, "")];
  328. if (output.match(/<img src=\"/))
  329. return ["HTML", output.replace(/<img src=\"/, "").replace(/\"\/>/, "")];
  330. if (output.match(/https/)) return ["URL", output];
  331. return ["", ""];
  332. }
  333.  
  334. function url2Input(url) {
  335. if (url.match(/math\.now\.sh/))
  336. return [
  337. "math.now.sh",
  338. decodeURIComponent(url.replace("https://math.now.sh?from=", "")),
  339. ];
  340. if (url.match(/www\.zhihu\.com/))
  341. return [
  342. "zhihu",
  343. decodeURIComponent(url.replace("https://www.zhihu.com/equation?tex=", ""))
  344. .replace("\\bbox[white]{", "")
  345. .slice(0, -1),
  346. ];
  347. if (url.match(/latex\.codecogs\.com/))
  348. return [
  349. "codecogs",
  350. decodeURIComponent(
  351. url.replace("https://latex.codecogs.com/svg.latex?", "")
  352. ),
  353. ];
  354. return ["", ""];
  355. }
  356.  
  357. function copy(e) {
  358. var copyText = document.getElementById(e);
  359. copyText.select();
  360. copyText.setSelectionRange(0, 99999); /* For mobile devices */
  361. document.execCommand("copy");
  362. }
  363. `;}
  364. document.body.appendChild(script);
  365. }
  366.  
  367. //window.addEventListener('load', addMathEditor, false); // not work?
  368. addMathEditor();
  369.  
  370. // ubb-editor 添加Math Editor按钮
  371. function addUbbMathEditorButton(){
  372. 'use strict';
  373. console.log("addMathEditorButton");
  374. let mathEditorButton = document.querySelector(".fa-math-editor");
  375. if(!mathEditorButton) mathEditorButton = createUbbMathEditorButton();
  376. let referenceNode = document.querySelector(".fa.fa-file.ubb-button.ubb-button-icon");
  377. referenceNode.parentNode.insertBefore(mathEditorButton, referenceNode.nextSibling);
  378. }
  379.  
  380. function createUbbMathEditorButton(){
  381. 'use strict';
  382. console.log("createMathEditorButton");
  383. let mathEditorButton = document.createElement("button");
  384. mathEditorButton.className = "fa fa-math-editor ubb-button";
  385. mathEditorButton.type = "button";
  386. mathEditorButton.title = "Math Editor";
  387. mathEditorButton.innerText = "Σ";
  388. mathEditorButton.style = "font-size: larger;";
  389. mathEditorButton.onclick = function(){
  390. update();
  391. var modal = document.getElementById("myModal");
  392. modal.style.display = "block";
  393. }
  394. return mathEditorButton;
  395. }
  396.  
  397. function removeUbbMathEditorButton(){
  398. console.log("removeButton");
  399. let mathEditorButton = document.querySelector(".fa-math-editor");
  400. if(mathEditorButton) mathEditorButton.remove();
  401. }
  402.  
  403. // markdown-editor 添加Math Editor按钮
  404. function addMdMathEditorButton(){
  405. 'use strict';
  406. console.log("addMathEditorButton");
  407. let mathEditorButton = document.querySelector(".mde-header > ul:nth-child(3) > li:nth-child(4)");
  408. if(!mathEditorButton) mathEditorButton = createMdMathEditorButton();
  409. let referenceNode = document.querySelector(".mde-header > ul:nth-child(3) > li:nth-child(3)");
  410. referenceNode.parentNode.insertBefore(mathEditorButton, referenceNode.nextSibling);
  411. }
  412.  
  413. function createMdMathEditorButton(){
  414. 'use strict';
  415. console.log("createMathEditorButton");
  416. let mathEditorButton = document.createElement("li");
  417. mathEditorButton.className = "mde-header-item md-math-editor-btn";
  418. let btn = document.createElement("button");
  419. btn.innerText = "Σ";
  420. btn.onclick = function(){
  421. update();
  422. var modal = document.getElementById("myModal");
  423. modal.style.display = "block";
  424. }
  425. mathEditorButton.appendChild(btn);
  426. return mathEditorButton;
  427. }
  428.  
  429. function removeMdMathEditorButton(){
  430. console.log("removeButton");
  431. let mathEditorButton = document.querySelector(".mde-header > ul:nth-child(3) > li:nth-child(4)");
  432. if(mathEditorButton) mathEditorButton.remove();
  433. }
  434.  
  435. waitForKeyElements(".fa-smile-o", addUbbMathEditorButton);
  436. waitForKeyElements(".ubb-preview", removeUbbMathEditorButton);
  437. waitForKeyElements(".mde-header", addMdMathEditorButton);