// ==UserScript==
// @name chatGPT Markdown
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Save the chatGPT Q&A content as a markdown text
// @author TripleTre
// @match https://chat.openai.com/chat
// @icon https://chat.openai.com/favicon-32x32.png
// @grant none
// ==/UserScript==
(function () {
'use strict';
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 replacements = [
[/\*/g, '\\*', 'asterisks'],
[/#/g, '\\#', 'number signs'],
[/\//g, '\\/', 'slashes'],
[/\(/g, '\\(', 'parentheses'],
[/\)/g, '\\)', 'parentheses'],
[/\[/g, '\\[', 'square brackets'],
[/\]/g, '\\]', 'square brackets'],
[/</g, '<', 'angle brackets'],
[/>/g, '>', 'angle brackets'],
[/_/g, '\\_', 'underscores'],
[/`/g, '\\`', 'codeblocks']
];
function markdownEscape(string, skips) {
skips = skips || []
return replacements.reduce(function (string, replacement) {
var name = replacement[2]
return name && skips.indexOf(name) !== -1
? string
: string.replace(replacement[0], replacement[1])
}, string)
}
function replaceInnerNode(element) {
if (element.outerHTML) {
var parser = new DOMParser();
var nextDomString = element.outerHTML.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) {
var p = replaceInnerNode(element);
result += markdownEscape(p.textContent, ["codeblocks", "number signs"]);
result += `\n\n`;
return result;
},
"OL": function (element, result) {
var ol = replaceInnerNode(element);
var olStart = parseInt(ol.getAttribute("start") || "1");
Array.from(ol.querySelectorAll("li")).forEach((li, index) => {
result += `${index + olStart}. ${markdownEscape(li.textContent, ["codeblocks", "number signs"])}`;
result += `\n`;
});
result += `\n\n`;
return result;
},
"PRE": function (element, result) {
var codeBlocks = Array.from(element.querySelectorAll("code"));
var languageMarkedBlock = codeBlocks.find(v => /language-(\w+)/.test(v.getAttribute("class") || ""));
var languageMark = languageMarkedBlock.getAttribute("class").match(/language-(\w+)/)[1] || "";
result += "```" + languageMark + "\n";
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
result += `## ${markdownEscape(nextBlock.textContent, ["codeblocks", "number signs"])}`;
result += `\n\n`;
} else {
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;
}
var copyHtml = `<div id="__copy__" style="z-index:9999999;cursor:pointer;position: fixed;top: 20px;right: 20px;width: 60px;height: 60px;background: #8bc34a;/* border: 1px solid #8bc34a; */border-radius: 50%;color: white;display: flex;justify-content: center;align-items: center;"><span>copy</span></div>`;
var copyElement = document.createElement("div");
document.body.appendChild(copyElement);
copyElement.outerHTML = copyHtml;
var copyAnchor = document.getElementById("__copy__");
copyAnchor.addEventListener("click", () => {
navigator.clipboard.writeText(toMarkdown()).then(() => {
alert("done");
});
});
console.log(mdContent);
})();