支持数学公式的ChatGPT Markdown一键复制

将chatGPT问答内容复制成markdown文本,并支持MathJax渲染内容导出,基于赵巍໖的'chatGPT Markdown',与'OpenAI-ChatGPT LaTeX Auto Render(with MathJax V2)'一起使用可以渲染公式。

目前为 2022-12-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ChatGPT Copy as Markdown with MathJax Support
  3. // @name:zh-CN 支持数学公式的ChatGPT Markdown一键复制
  4. // @namespace http://tampermonkey.net/
  5. // @version 0.2
  6. // @description Copy the chatGPT Q&A content as a markdown text, with MathJax Render Support, based on 'chatGPT Markdown' by 赵巍໖, you can use this together with 'OpenAI-ChatGPT LaTeX Auto Render (with MathJax V2)' that adds support for math render.
  7. // @description:zh-cn 将chatGPT问答内容复制成markdown文本,并支持MathJax渲染内容导出,基于赵巍໖的'chatGPT Markdown',与'OpenAI-ChatGPT LaTeX Auto Render(with MathJax V2)'一起使用可以渲染公式。
  8. // @license MIT
  9. // @author jbji
  10. // @match https://chat.openai.com/chat
  11. // @icon https://chat.openai.com/favicon-32x32.png
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17. var mathFixEnabled = true;
  18. function toMarkdown() {
  19. var main = document.querySelector("main");
  20. var article = main.querySelector("div > div > div > div");
  21. var chatBlocks = Array.from(article.children)
  22. .filter(v => v.getAttribute("class").indexOf("border") >= 0);
  23.  
  24. var replacements = [
  25. [/\*/g, '\\*', 'asterisks'],
  26. [/#/g, '\\#', 'number signs'],
  27. [/\//g, '\\/', 'slashes'],
  28. //[/\(/g, '\\(', 'parentheses'], //this breaks math euqations
  29. //[/\)/g, '\\)', 'parentheses'], //this breaks math euqations
  30. [/\[/g, '\\[', 'square brackets'],
  31. [/\]/g, '\\]', 'square brackets'],
  32. [/</g, '&lt;', 'angle brackets'],
  33. //[/>/g, '&gt;', 'angle brackets'], //breaks math
  34. //[/_/g, '\\_', 'underscores'], //this breaks math euqations
  35. [/`/g, '\\`', 'codeblocks']
  36. ];
  37.  
  38. function markdownEscape(string, skips) {
  39. skips = skips || []
  40. //reduce function applied the function in the first with the second as input
  41. //this applies across the array with the first element inside as the initial 2nd param for the reduce func.
  42. return replacements.reduce(function (string, replacement) {
  43. var name = replacement[2]
  44. return name && skips.indexOf(name) !== -1
  45. ? string
  46. : string.replace(replacement[0], replacement[1])
  47. }, string)
  48. }
  49.  
  50. function replaceInnerNode(element) {
  51. if (element.outerHTML) {
  52. var htmlBak = element.outerHTML;
  53. if(mathFixEnabled){
  54. //replace mathjax stuff
  55. var mathjaxBeginRegExp = /(<span class="MathJax_Preview".*?)<scr/s; //this is lazy
  56. var match = mathjaxBeginRegExp.exec(htmlBak);
  57. while(match){
  58. htmlBak = htmlBak.replace(match[1], '');
  59. //repalace math equations
  60. var latexMath;
  61. //match new line equations first
  62. var latexMathNLRegExp = /<script type="math\/tex; mode=display" id="MathJax-Element-\d+">(.*?)<\/script>/s;
  63. match = latexMathNLRegExp.exec(htmlBak);
  64. if(match){
  65. latexMath = "$$" + match[1] + "$$";
  66. htmlBak = htmlBak.replace(match[0], latexMath);
  67. }else{
  68. //then inline equations
  69. var latexMathRegExp = /<script type="math\/tex" id="MathJax-Element-\d+">(.*?)<\/script>/s;
  70. match = latexMathRegExp.exec(htmlBak);
  71. if(match){
  72. latexMath = "$" + match[1] + "$";
  73. htmlBak = htmlBak.replace(match[0], latexMath);
  74. }
  75. }
  76. match = mathjaxBeginRegExp.exec(htmlBak);
  77. }
  78. }
  79.  
  80. var parser = new DOMParser();
  81. //default code block replacement
  82. var nextDomString = htmlBak.replace(/<code>([\w\s-]*)<\/code>/g, (match) => {
  83. var doc = parser.parseFromString(match, "text/html");
  84. return "`" + (doc.body.textContent) + "`";
  85. });
  86. return parser.parseFromString(nextDomString, "text/html").body.children[0];
  87. }
  88. return element;
  89. }
  90.  
  91. var elementMap = {
  92. "P": function (element, result) {
  93. let p = replaceInnerNode(element);
  94. result += markdownEscape(p.textContent, ["codeblocks", "number signs"]);
  95. result += `\n\n`;
  96. return result;
  97. },
  98. //this should be unordered!
  99. "UL": function (element, result) {
  100. let ul = replaceInnerNode(element);
  101. Array.from(ul.querySelectorAll("li")).forEach((li, index) => {
  102. result += `- ${markdownEscape(li.textContent, ["codeblocks", "number signs"])}`;
  103. result += `\n`;
  104. });
  105. result += `\n\n`;
  106. return result;
  107. },
  108. "OL": function (element, result) {
  109. let ol = replaceInnerNode(element);
  110. Array.from(ol.querySelectorAll("li")).forEach((li, index) => {
  111. result += `${index + 1}. ${markdownEscape(li.textContent, ["codeblocks", "number signs"])}`;
  112. result += `\n`;
  113. });
  114. result += `\n\n`;
  115. return result;
  116. },
  117. "PRE": function (element, result) {
  118. var codeBlocks = element.querySelectorAll("code");
  119. //first get class name
  120. var regex = /^language-/;
  121. var codeType = '';
  122. for(var c of codeBlocks){
  123. var classNameStr = c.className.split(' ')[2];
  124. if (regex.test(classNameStr)){
  125. codeType = classNameStr.substr(9);
  126. }
  127. }
  128. //then generate the markdown codeblock
  129. result += "```" + codeType + "\n";
  130. Array.from(codeBlocks).forEach(block => {
  131. result += `${block.textContent}`;
  132. });
  133. result += "```\n";
  134. result += `\n\n`;
  135. return result;
  136. }
  137. };
  138. var TEXT_BLOCKS = Object.keys(elementMap);
  139.  
  140. var mdContent = chatBlocks.reduce((result, nextBlock, i) => {
  141. if (i % 2 === 0) { // title
  142. let p = replaceInnerNode(nextBlock);
  143. result += `> ${markdownEscape(p.textContent, ["codeblocks", "number signs"])}`;
  144. result += `\n\n`;
  145. }else{
  146. //try to parse the block
  147. var iterator = document.createNodeIterator(
  148. nextBlock,
  149. NodeFilter.SHOW_ELEMENT,
  150. {
  151. acceptNode: element => TEXT_BLOCKS.indexOf(element.tagName.toUpperCase()) >= 0
  152. },
  153. false,
  154. );
  155. let next = iterator.nextNode();
  156. while (next) {
  157. result = elementMap[next.tagName.toUpperCase()](next, result);
  158. next = iterator.nextNode();
  159. }
  160. }
  161. return result;
  162. }, "");
  163. return mdContent;
  164. }
  165. //for copy button
  166. var copyHtml = `<div id="__copy__" style="cursor:pointer;position: fixed;bottom: 20px;left: 20px;width: 100px;height: 35px;background: #333333;border: 1px solid #555555;border-radius: 5px;color: white;display: flex;justify-content: center;align-items: center;transition: all 0.2s ease-in-out;"><span>Copy .md</span></div>`;
  167. // for copy function
  168. var copyElement = document.createElement("div");
  169. document.body.appendChild(copyElement);
  170. copyElement.outerHTML = copyHtml;
  171. // for button style
  172. document.querySelector('#__copy__').addEventListener('mouseenter', function() {
  173. this.style.background = '#555555';
  174. this.style.color = 'white';
  175. });
  176. document.querySelector('#__copy__').addEventListener('mouseleave', function() {
  177. this.style.background = '#333333';
  178. this.style.color = 'white';
  179. });
  180. document.querySelector('#__copy__').addEventListener('mousedown', function() {
  181. this.style.boxShadow = '2px 2px 2px #333333';
  182. });
  183. document.querySelector('#__copy__').addEventListener('mouseup', function() {
  184. this.style.boxShadow = 'none';
  185. });
  186. //for anchor
  187. var copyAnchor = document.getElementById("__copy__");
  188. copyAnchor.addEventListener("click", () => {
  189. // Get the `span` element inside the `div`
  190. let span = copyAnchor.querySelector("span");
  191.  
  192. // Change the text of the `span` to "Done"
  193. span.innerText = "Copied!";
  194.  
  195. // Use `setTimeout` to change the text back to its original value after 3 seconds
  196. setTimeout(() => {
  197. span.innerText = "Copy .md";
  198. }, 1000);
  199.  
  200. // Perform the rest of the original code
  201. navigator.clipboard.writeText(toMarkdown()).then(() => {
  202. //alert("done");
  203. });
  204. });
  205. })();