Created
January 15, 2026 06:26
-
-
Save Minecraftian14/a2eaab8d156c093b381261ad2a0fe595 to your computer and use it in GitHub Desktop.
Copy ChatGPT conversation as Markdown
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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