Skip to content

Instantly share code, notes, and snippets.

@tqwewe
Last active December 23, 2022 10:32
Show Gist options
  • Save tqwewe/06f9dc895dc03fdc2692b04d34c969c4 to your computer and use it in GitHub Desktop.
Save tqwewe/06f9dc895dc03fdc2692b04d34c969c4 to your computer and use it in GitHub Desktop.
Copies ChatGPT responses as raw Markdown.

Copy ChatGPT as Markdown

Copies ChatGPT responses as raw Markdown.

This script can be pased directly in your JavaScript console when on ChatGPT, or pasted to a script in tampermonkey.

It adds a little copy button next to the replies.

function elToText(el) {
return Array.from(el.childNodes)
.map((node) => {
switch (node.nodeType) {
case 1: {
// ELEMENT_NODE
const nextSibling = node.nextSibling?.nodeName;
const prevSibling = node.previousSibling?.nodeName;
switch (node.nodeName) {
case 'P': {
return `${elToText(node)}\n\n`;
}
case 'OL':
case 'UL': {
if (nextSibling === 'PRE') {
return `${elToText(node)}`;
} else {
return `${elToText(node)}\n\n`;
}
}
case 'LI': {
if (node.parentNode.nodeName === 'OL') {
return `- ${elToText(node)}`;
} else {
const index = nodeIndex(node);
return `${index + 1}. ${elToText(node)}`;
}
}
case 'CODE':
return '`' + elToText(node) + '`';
case 'PRE': {
const code = node.querySelector(
'div > div.p-4.overflow-y-auto > code'
);
const language = Array.from(code.classList).find(c => c.startsWith('language-')).substring(9);
if (prevSibling === 'OL' || prevSibling === 'LI') {
return ' ```' + language + '\n ' + elToText(code) + ' ```\n\n';
} else {
return '```' + language + '\n' + elToText(code) + '```\n\n';
}
}
case 'STRONG':
case 'B':
return `**${elToText(node)}**`;
case 'EM':
case 'I':
return `*${elToText(node)}*`;
case 'DIV':
case 'SPAN':
return elToText(node);
case 'A': {
const link = node.getAttribute('href');
const text = elToText(node);
return `[${text}](${link})`;
}
default: {
console.warn(`Unhandled node name: '${node.nodeName}'`);
return elToText(node);
}
}
}
case 3: // TEXT_NODE
return node.nodeValue;
default:
return '';
}
})
.join('');
}
function nodeIndex(node) {
return Array.from(node.parentNode.children).indexOf(node);
}
function createElementFromHTML(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();
// Change this to div.childNodes to support multiple top-level nodes.
return div.firstChild;
}
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement('textarea');
textArea.value = text;
// Avoid scrolling to bottom
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Fallback: Copying text command was ' + msg);
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
}
function copyTextToClipboard(text) {
if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text);
return;
}
navigator.clipboard.writeText(text).then(
function () {
console.log('Async: Copying to clipboard was successful!');
},
function (err) {
console.error('Async: Could not copy text: ', err);
}
);
}
function addCopyBtns() {
Array.from(
document.querySelectorAll(
'main > div.flex-1.overflow-hidden > div > div div.bg-gray-50 > div > div.relative.flex.w-\\[calc\\(100\\%-50px\\)\\].md\\:flex-col.lg\\:w-\\[calc\\(100\\%-115px\\)\\]'
)
).forEach((el) => {
if (el.dataset.copyAdded == 'true') {
return;
}
const content = el.querySelector(':scope > div:nth-child(1) .markdown');
const btnsContainer = el.querySelector(':scope > div:nth-child(2)');
if (btnsContainer != null) {
el.dataset.copyAdded = true;
} else {
const observer = new MutationObserver(() => {
addCopyBtns();
if (el.dataset.copyAdded == 'true') {
observer.disconnect();
}
});
observer.observe(el, { childList: true });
return;
}
const btn = createElementFromHTML(`
<button class="p-1 rounded-md hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400">
<svg
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 460 460"
style="enable-background: new 0 0 460 460"
xml:space="preserve"
fill="currentColor"
class="h-4 w-4"
>
<g>
<g>
<g>
<path
d="M425.934,0H171.662c-18.122,0-32.864,14.743-32.864,32.864v77.134h30V32.864c0-1.579,1.285-2.864,2.864-2.864h254.272
c1.579,0,2.864,1.285,2.864,2.864v254.272c0,1.58-1.285,2.865-2.864,2.865h-74.729v30h74.729
c18.121,0,32.864-14.743,32.864-32.865V32.864C458.797,14.743,444.055,0,425.934,0z"
/>
<path
d="M288.339,139.998H34.068c-18.122,0-32.865,14.743-32.865,32.865v254.272C1.204,445.257,15.946,460,34.068,460h254.272
c18.122,0,32.865-14.743,32.865-32.864V172.863C321.206,154.741,306.461,139.998,288.339,139.998z M288.341,430H34.068
c-1.58,0-2.865-1.285-2.865-2.864V172.863c0-1.58,1.285-2.865,2.865-2.865h254.272c1.58,0,2.865,1.285,2.865,2.865v254.273h0.001
C291.206,428.715,289.92,430,288.341,430z"
/>
</g>
</g>
</g>
</svg>
</button>
`);
btn.addEventListener('click', () => {
copyTextToClipboard(elToText(content).trim());
});
btnsContainer.appendChild(btn);
});
}
const listContainer = document.querySelector(
'main > div.flex-1.overflow-hidden > div > div > div'
);
const observer = new MutationObserver(addCopyBtns);
observer.observe(listContainer, { childList: true });
setTimeout(addCopyBtns, 500);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment