Skip to content

Instantly share code, notes, and snippets.

@Boorj
Last active April 23, 2023 07:08
Show Gist options
  • Save Boorj/5c327ba6473a13fa47bf17fd4d872a69 to your computer and use it in GitHub Desktop.
Save Boorj/5c327ba6473a13fa47bf17fd4d872a69 to your computer and use it in GitHub Desktop.
ChatGPT Bookmarklet: copy dialog to clipboard in markdown
javascript:(function(){document.body.appendChild(document.createElement('script')).src='https://gist.githubusercontent.com/Boorj/5c327ba6473a13fa47bf17fd4d872a69/raw/83feeda1e5c9436001300976316769ee0e5d77af/chatgpt-2-markdown.js';})();
(function () {
function markdownEscape(text) {
return text.replace(/\s+/g, " ")
/*.replace(/[\\\-*_>#]/g, "\\$&");*/
.replace(/[\\*>#]/g, "\\$&");
}
function repeat(str, times) {
return (new Array(times + 1)).join(str);
}
function childsToMarkdown(tree, mode) {
var res = "";
for (let i = 0, l = tree.childNodes.length; i < l; ++i) {
res += nodeToMarkdown(tree.childNodes[i], mode);
}
return res;
}
function nodeToMarkdown(tree, mode) {
let nl = "\n\n";
if (tree.nodeType == 3) { // Text node
return markdownEscape(tree.nodeValue);
}
else if (tree.nodeType == 1) {
if (mode == "block") {
switch (tree.tagName.toLowerCase()) {
case "br":
return nl;
case "hr":
return nl + "---" + nl;
// Block container elements
case "p":
case "div":
case "section":
case "address":
case "center":
return nl + childsToMarkdown(tree, "block") + nl;
case "ul":
return nl + childsToMarkdown(tree, "u") + nl;
case "ol":
return nl + childsToMarkdown(tree, "o") + nl;
case "pre":
return nl + " " + childsToMarkdown(tree, "inline") + nl;
case "code":
if (tree.childNodes.length == 1) {
break; // use the inline format
}
return nl + " " + childsToMarkdown(tree, "inline") + nl;
case "h1":
case "h2":
case "h3":
case "h4":
case "h5":
case "h6":
case "h7":
return nl + repeat("#", Number(tree.tagName[1])) + " " + childsToMarkdown(tree, "inline") + nl;
case "blockquote":
return nl + "> " + childsToMarkdown(tree, "inline") + nl;
}
}
if (/^[ou]+$/.test(mode)) {
if (tree.tagName == "LI") {
return "\n" + repeat(" ", mode.length - 1) + (mode[mode.length - 1] == "o" ? "1. " : "- ") + childsToMarkdown(tree, mode + "l");
}
else {
console.log("[toMarkdown] - invalid element at this point " + mode.tagName);
return childsToMarkdown(tree, "inline");
}
}
else if (/^[ou]+l$/.test(mode)) {
if (tree.tagName == "UL") {
return childsToMarkdown(tree, mode.substr(0, mode.length - 1) + "u");
}
else if (tree.tagName == "OL") {
return childsToMarkdown(tree, mode.substr(0, mode.length - 1) + "o");
}
}
// Inline tags
switch (tree.tagName.toLowerCase()) {
case "strong":
case "b":
return "**" + childsToMarkdown(tree, "inline") + "**";
case "em":
case "i":
return "_" + childsToMarkdown(tree, "inline") + "_";
case "code": // Inline version of code
return "`" + childsToMarkdown(tree, "inline") + "`";
case "a":
return "[" + childsToMarkdown(tree, "inline") + "](" + tree.getAttribute("href") + ")";
case "img":
return nl + "[_Image_: " + markdownEscape(tree.getAttribute("alt")) + "](" + tree.getAttribute("src") + ")" + nl;
case "script":
case "style":
case "meta":
return "";
default:
console.log("[toMarkdown] - undefined element " + tree.tagName);
return childsToMarkdown(tree, mode);
}
}
}
function toMarkdown(node) {
return nodeToMarkdown(node, "block")
.replace(/[\n]{2,}/g, "\n\n")
.replace(/^[\n]+/, "")
.replace(/[\n]+$/, "");
}
function main() {
const separator = '\n------------\n';
let markdownLines = [];
const messages = document.querySelectorAll('div.group');
function parseAnswerChild(child) {
let s = '';
switch (child.localName) {
case "p":
const img = child.querySelector("img");
if (img) {
const altText = img.alt || "";
const url = img.src || "";
const title = img.title || "";
s += `![${altText}](${url} "${title}")\n`;
}
else {
s += toMarkdown(child);
/* let html = child.innerHTML; s += html; */
}
break;
case "pre":
const lang = child.querySelector("span") ? child.querySelector("span").innerHTML : "";
let code = '';
code = code.replace(/<[^>]*>/g, "");
code = child.querySelector("code") ? child.querySelector("code").textContent : "";
s += "\n```" + lang + "\n" + code + "```\n";
break;
case "ol":
const liElements = child.querySelectorAll("li");
for (let i = 0; i < liElements.length; i++) {
s += `${i + 1}. ${liElements[i].textContent}\n`;
}
break;
case "table":
const headers = [...child.querySelectorAll("th")].map(header => header.textContent.trim());
const rows = [...child.querySelectorAll("tbody tr")].map(row => [...row.querySelectorAll("td")].map(cell => cell.textContent.trim()));
const markdownTable = [headers, ...rows]
.map((row, index) => {
if (index === 0) {
return `| ${row.join(" | ")} |\n|${row.map(() => "-----")
.join("|")}|`;
}
return `| ${row.join(" | ")} |`;
})
.join("\n");
s += markdownTable + "\n";
break;
default:
s += child.innerHTML + "\n";
}
return s;
}
function parseMessage(group) {
if (group.classList.contains('dark:bg-gray-800')) {
const question = '# ' + group.innerText.trim() + '\n\n';
markdownLines.push(question);
}
else {
let markdownAnswer = '';
const allAnswerChildren = group.querySelectorAll('.prose > *');
allAnswerChildren.forEach(function (child) {
markdownAnswer += parseAnswerChild(child) + '\n';
});
markdownLines.push(markdownAnswer);
markdownLines.push(separator);
}
}
messages.forEach(parseMessage);
const copyContent = markdownLines.join('\n');
navigator.clipboard.writeText(copyContent);
alert('Chat history copied to clipboard as Markdown');
}
main();
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment