Skip to content

Instantly share code, notes, and snippets.

@Minecraftian14
Created January 15, 2026 06:26
Show Gist options
  • Select an option

  • Save Minecraftian14/a2eaab8d156c093b381261ad2a0fe595 to your computer and use it in GitHub Desktop.

Select an option

Save Minecraftian14/a2eaab8d156c093b381261ad2a0fe595 to your computer and use it in GitHub Desktop.
Copy ChatGPT conversation as Markdown
let preserverWhitespaces = false;
textToMarkdown = (text) => {
if (preserverWhitespaces)
return text;
else
return text.replaceAll(/[\s]+/g, " ");
}
// text.replaceAll(/(?<=\S)[\s]+(?=\S)/g, " ");
let listIndentation = -1;
listToMarkdown = (isOrdered, nodes) => {
listIndentation++;
let index = 1;
const markdown = [...nodes].map(node => {
if (node.nodeType === Node.TEXT_NODE)
return textToMarkdown(node.textContent);
else if (node.nodeType !== Node.ELEMENT_NODE)
return "";
else if (node.tagName !== "LI")
return nodeToMarkdown(node);
else {
const head = isOrdered ? `${index++}. ` : "- ";
return "\n" + " ".repeat(listIndentation) + head + nodesToMarkdown(node.childNodes).trimLeft();
}
}).join("");
listIndentation--;
return markdown;
};
preToMarkdown = (nodes) => {
preserverWhitespaces = true;
const markdown = nodesToMarkdown(nodes);
preserverWhitespaces = false;
return "\n```" + markdown + "```";
};
tableToMarkdown = (nodes) => {
let rows = [...nodes]
.filter(node => node.nodeType === Node.ELEMENT_NODE)
.filter(node => ["THEAD", "TBODY", "TR"].includes(node.tagName))
.flatMap(node => node.tagName === "TR" ? [node] : [...node.childNodes])
.filter(node => node.nodeType === Node.ELEMENT_NODE)
.map(tr =>
"| " + [...tr.childNodes]
.filter(node => node.nodeType === Node.ELEMENT_NODE)
.map(nodeToMarkdown)
.join(" | ") + " |"
)
rows.splice(1, 0, rows[0].replaceAll(/(?<=|)[^|]+(?=|)/g, " - "))
return "\n" + rows.join("\n");
};
mdFormats = {
'STRONG': (content) => `**${content}**`,
'CODE': (content) => preserverWhitespaces ? content : `\`${content}\``,
'EM': (content) => `_${content}_`,
'P': (content) => `\n\n${content}`,
'HR': (content) => `\n\n---\n`,
'H1': (content) => `\n# ${content.trimLeft()}`,
'H2': (content) => `\n## ${content.trimLeft()}`,
'H3': (content) => `\n### ${content.trimLeft()}`,
'H4': (content) => `\n#### ${content.trimLeft()}`,
'H5': (content) => `\n##### ${content.trimLeft()}`,
'H6': (content) => `\n###### ${content.trimLeft()}`,
'DIV': (content) => { content = content.trimRight(); return content === "" ? "" : content + "\n" },
'SPAN': (content) => content,
'BUTTON': (content) => "",
'TH': (content) => content, // It can be bolded by the table render itself
'TD': (content) => content,
'BR': (content) => "\n",
// 'BLOCKQUOTE': (content) => content.replaceAll(/^|(?=\n)/g, "> "),
'BLOCKQUOTE': (content) => content.split("\n").map(line => "> " + line).join("\n"),
'svg': (content) => "",
}
nodeToMarkdown = (node) => {
if (node.nodeType === Node.TEXT_NODE) {
return textToMarkdown(node.textContent);
} else if (node.nodeType === Node.ELEMENT_NODE) {
if (node.tagName === "OL")
return listToMarkdown(true, node.childNodes);
if (node.tagName === "UL")
return listToMarkdown(false, node.childNodes);
if (node.tagName === "PRE")
return preToMarkdown(node.childNodes);
if (node.tagName === "TABLE")
return tableToMarkdown(node.childNodes);
if (node.tagName === "A")
return `[${nodesToMarkdown(node.childNodes)}](${node.href})`;
if (node.tagName in mdFormats)
return mdFormats[node.tagName](nodesToMarkdown(node.childNodes));
return `\nTODO: "${node.tagName}" ${node.textContent}`;
}
};
nodesToMarkdown = (nodes) =>
[...nodes].map(nodeToMarkdown).join("");
articleToMarkdown = (article) => {
const actualChildren = article.children[1].children[0].children[0].children[0].children[0].children[0].childNodes;
return nodeToMarkdown(article.childNodes[1])
+ nodesToMarkdown(actualChildren);
};
const floatingButton = document.createElement('button');
floatingButton.textContent = 'Copy as Markdown';
floatingButton.classList.add('floating-btn');
floatingButton.style.position = 'fixed';
floatingButton.style.top = '10px';
floatingButton.style.left = '10px';
floatingButton.style.zIndex = '1000';
floatingButton.style.padding = '10px 15px';
floatingButton.style.backgroundColor = '#007BFF';
floatingButton.style.color = 'white';
floatingButton.style.border = 'none';
floatingButton.style.borderRadius = '5px';
floatingButton.style.cursor = 'pointer';
async function copyTextToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('Text successfully copied to clipboard');
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
floatingButton.addEventListener('click', () => {
const markdown = [...thread.children[0].children[0].children[1].children].map(articleToMarkdown).join("\n\n");
copyTextToClipboard(markdown);
floatingButton.remove();
});
document.body.appendChild(floatingButton);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment