您需要先安装一个扩展,例如 篡改猴、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");
- });
- });
- })();