您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Copy the chatGPT Q&A content as a markdown text, with MathJax Render Support, you can use this together with 'OpenAI-ChatGPT LaTeX Auto Render (with MathJax V2)' that adds support for math render, based on 'chatGPT Markdown' by 赵巍໖.
当前为
// ==UserScript== // @name ChatGPT Copy as Markdown with MathJax Support // @name:zh-CN 支持数学公式的ChatGPT Markdown一键复制 // @namespace http://tampermonkey.net/ // @version 0.3 // @description Copy the chatGPT Q&A content as a markdown text, with MathJax Render Support, you can use this together with 'OpenAI-ChatGPT LaTeX Auto Render (with MathJax V2)' that adds support for math render, based on 'chatGPT Markdown' by 赵巍໖. // @description:zh-cn 将chatGPT问答内容复制成markdown文本,并支持MathJax渲染内容导出,与'OpenAI-ChatGPT LaTeX Auto Render(with MathJax V2)'一起使用可以渲染公式, 基于赵巍໖的'chatGPT Markdown'。 // @license MIT // @author jbji // @match https://chat.openai.com/chat // @icon https://chat.openai.com/favicon-32x32.png // @grant none // ==/UserScript== (function () { 'use strict'; var mathFixEnabled = true; function toMarkdown() { var main = document.querySelector("main"); var article = main.querySelector("div > div > div > div"); var chatBlocks = Array.from(article.children) .filter(v => v.getAttribute("class").indexOf("border") >= 0); var new_replacements = [ //['\\', '\\\\', 'backslash'], //Don't need this any more cause it would be checked. ['`', '\\`', 'codeblocks'], ['*', '\\*', 'asterisk'], ['_', '\\_', 'underscores'], ['{', '\\{', 'crulybraces'], ['}', '\\}', 'crulybraces'], ['[', '\\[', 'square brackets'], [']', '\\]', 'square brackets'], ['(', '\\(', 'parentheses'], [')', '\\)', 'parentheses'], ['#', '\\#', 'number signs'], ['+', '\\+', 'plussign'], ['-', '\\-', 'hyphen'], ['.', '\\.', 'dot'], ['!', '\\!', 'exclamation mark'], ['>', '\\>', 'angle brackets'] ]; // A State Machine used to match string and do replacement function replacementSkippingMath(string, char_pattern, replacement) { var inEquationState = 0; // 0:not in equation, 1:inline equation expecting $, 2: line euqation expecting $$ var result = ""; for (let i = 0; i < string.length; i++) { if(string[i] == '\\'){ result += string[i]; if (i+1 < string.length) result += string[i+1]; i++; // one more add to skip escaped char continue; } switch(inEquationState){ case 1: result += string[i]; if(string[i] === '$'){ inEquationState = 0; //simply exit and don't do further check continue; } break; case 2: result += string[i]; if(string[i] === '$'){ if (i+1 < string.length && string[i+1] === '$'){ //matched $$ result += '$'; inEquationState = 0; i++; // one more add } //else is unexpected behavior continue; } break; default: if(string[i] === '$'){ if (i+1 < string.length && string[i+1] === '$'){//matched $$ result += '$$'; inEquationState = 2; i++; // one more add }else{ //matched $ result += '$'; inEquationState = 1; } continue; }else if(string[i] === char_pattern[0]){ //do replacement result += replacement; }else{ result += string[i]; } } } return result; } function markdownEscape(string, skips) { skips = skips || [] //reduce function applied the function in the first with the second as input //this applies across the array with the first element inside as the initial 2nd param for the reduce func. return new_replacements.reduce(function (string, replacement) { var name = replacement[2] if (name && skips.indexOf(name) !== -1) { return string; } else { return replacementSkippingMath(string, replacement[0], replacement[1]); } }, string) } function replaceInnerNode(element) { if (element.outerHTML) { var htmlBak = element.outerHTML; if(mathFixEnabled){ //replace mathjax stuff var mathjaxBeginRegExp = /(<span class="MathJax_Preview".*?)<scr/s; //this is lazy var match = mathjaxBeginRegExp.exec(htmlBak); while(match){ htmlBak = htmlBak.replace(match[1], ''); //repalace math equations var latexMath; //match new line equations first var latexMathNLRegExp = /<script type="math\/tex; mode=display" id="MathJax-Element-\d+">(.*?)<\/script>/s; match = latexMathNLRegExp.exec(htmlBak); if(match){ latexMath = "$$" + match[1] + "$$"; htmlBak = htmlBak.replace(match[0], latexMath); }else{ //then inline equations var latexMathRegExp = /<script type="math\/tex" id="MathJax-Element-\d+">(.*?)<\/script>/s; match = latexMathRegExp.exec(htmlBak); if(match){ latexMath = "$" + match[1] + "$"; htmlBak = htmlBak.replace(match[0], latexMath); } } match = mathjaxBeginRegExp.exec(htmlBak); } } var parser = new DOMParser(); //default code block replacement var nextDomString = htmlBak.replace(/<code>([\w\s-]*)<\/code>/g, (match) => { var doc = parser.parseFromString(match, "text/html"); return "`" + (doc.body.textContent) + "`"; }); return parser.parseFromString(nextDomString, "text/html").body.children[0]; } return element; } var elementMap = { "P": function (element, result) { let p = replaceInnerNode(element); result += markdownEscape(p.textContent, ["codeblocks", "number signs"]); result += `\n\n`; return result; }, //this should be unordered! "UL": function (element, result) { let ul = replaceInnerNode(element); Array.from(ul.querySelectorAll("li")).forEach((li, index) => { result += `- ${markdownEscape(li.textContent, ["codeblocks", "number signs"])}`; result += `\n`; }); result += `\n\n`; return result; }, "OL": function (element, result) { let ol = replaceInnerNode(element); Array.from(ol.querySelectorAll("li")).forEach((li, index) => { result += `${index + 1}. ${markdownEscape(li.textContent, ["codeblocks", "number signs"])}`; result += `\n`; }); result += `\n\n`; return result; }, "PRE": function (element, result) { var codeBlocks = element.querySelectorAll("code"); //first get class name var regex = /^language-/; var codeType = ''; for(var c of codeBlocks){ var classNameStr = c.className.split(' ')[2]; if (regex.test(classNameStr)){ codeType = classNameStr.substr(9); } } //then generate the markdown codeblock result += "```" + codeType + "\n"; Array.from(codeBlocks).forEach(block => { result += `${block.textContent}`; }); result += "```\n"; result += `\n\n`; return result; } }; var TEXT_BLOCKS = Object.keys(elementMap); var mdContent = chatBlocks.reduce((result, nextBlock, i) => { if (i % 2 === 0) { // title let p = replaceInnerNode(nextBlock); result += `> ${markdownEscape(p.textContent, ["codeblocks", "number signs"])}`; result += `\n\n`; }else{ //try to parse the block var iterator = document.createNodeIterator( nextBlock, NodeFilter.SHOW_ELEMENT, { acceptNode: element => TEXT_BLOCKS.indexOf(element.tagName.toUpperCase()) >= 0 }, false, ); let next = iterator.nextNode(); while (next) { result = elementMap[next.tagName.toUpperCase()](next, result); next = iterator.nextNode(); } } return result; }, ""); return mdContent; } //for copy button 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>`; // for copy function var copyElement = document.createElement("div"); document.body.appendChild(copyElement); copyElement.outerHTML = copyHtml; // for button style document.querySelector('#__copy__').addEventListener('mouseenter', function() { this.style.background = '#555555'; this.style.color = 'white'; }); document.querySelector('#__copy__').addEventListener('mouseleave', function() { this.style.background = '#333333'; this.style.color = 'white'; }); document.querySelector('#__copy__').addEventListener('mousedown', function() { this.style.boxShadow = '2px 2px 2px #333333'; }); document.querySelector('#__copy__').addEventListener('mouseup', function() { this.style.boxShadow = 'none'; }); //for anchor var copyAnchor = document.getElementById("__copy__"); copyAnchor.addEventListener("click", () => { // Get the `span` element inside the `div` let span = copyAnchor.querySelector("span"); // Change the text of the `span` to "Done" span.innerText = "Copied!"; // Use `setTimeout` to change the text back to its original value after 3 seconds setTimeout(() => { span.innerText = "Copy .md"; }, 1000); // Perform the rest of the original code navigator.clipboard.writeText(toMarkdown()).then(() => { //alert("done"); }); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址