Copy ChatGPT responses as Markdown with proper tables, code blocks, and formula support for Typora Markdown editors. Trigger with Alt + Shift + C.
// ==UserScript==
// @name ChatGPT Enhanced Markdown Copy with Code Blocks ,Tables and Formula (For Typora and Chrome)
// @namespace http://tampermonkey.net/
// @version 1.5
// @description Copy ChatGPT responses as Markdown with proper tables, code blocks, and formula support for Typora Markdown editors. Trigger with Alt + Shift + C.
// @author Showna
// @match *://chatgpt.com/c
// @match *://chatgpt.com/c/*
// @grant none
// @license MIT
// @require https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.2/turndown.min.js
// ==/UserScript==
(function () {
'use strict';
// Function to handle enhanced copy
function enhancedCopy() {
const selection = window.getSelection();
if (!selection || selection.isCollapsed) {
console.warn("No content selected for copying.");
return;
}
// Initialize Turndown
const turndownService = new TurndownService({
headingStyle: 'atx',
codeBlockStyle: 'fenced',
});
// Add rule for handling formulas in KaTeX
turndownService.addRule('katex', {
filter: (node) => node.classList && node.classList.contains('katex'),
replacement: (content, node) => {
const annotation = node.querySelector('annotation');
if (annotation) {
const latex = annotation.textContent.trim();
return `$$${latex}$$`;
}
return '';
},
});
// Add rule for handling code blocks
turndownService.addRule('codeBlock', {
filter: (node) => node.tagName === 'PRE',
replacement: (content, node) => {
let codeLang = node.getAttribute('data-language') || '';
const codeContent = node.textContent.trim();
// Check for "CopyEdit" in the first line and process it
const lines = codeContent.split('\n');
if (lines[0].includes('CopyEdit')) {
const parts = lines[0].split('CopyEdit');
const extractedLang = parts[0].trim();
codeLang = extractedLang || codeLang;
lines[0] = parts[1].trim();
return `\n\`\`\`${codeLang}\n${lines.join('\n')}\n\`\`\``;
}
return `\n\`\`\`${codeLang}\n${codeContent}\n\`\`\``;
},
});
// Add rule for converting tables to Markdown
turndownService.addRule('table', {
filter: 'table',
replacement: (content, node) => {
const rows = Array.from(node.querySelectorAll('tr')).map((row) => {
const cells = Array.from(row.querySelectorAll('th, td')).map((cell) => {
const tempDiv = document.createElement('div');
tempDiv.innerHTML = cell.innerHTML;
return turndownService.turndown(tempDiv.innerHTML).trim();
});
return `| ${cells.join(' | ')} |`;
});
const maxColumns = rows[0].split('|').length - 2;
const correctSeparator = `| ${'--- | '.repeat(maxColumns).trim()} |`;
if (rows.length > 1) {
rows.splice(1, 0, correctSeparator);
}
const fixRowColumns = (row) => {
const cells = row.split('|');
while (cells.length - 2 < maxColumns) {
cells.splice(cells.length - 1, 0, ' ');
}
return cells.slice(0, maxColumns + 2).join('|');
};
const fixedRows = rows.map(fixRowColumns);
return fixedRows.join('\n');
},
});
// Process selected content
const tempDiv = document.createElement('div');
const range = selection.getRangeAt(0);
tempDiv.appendChild(range.cloneContents());
// Convert HTML to Markdown
let markdownText = turndownService.turndown(tempDiv.innerHTML);
// Ensure consistent line breaks
markdownText = markdownText
.replace(/\n{2,}/g, '\n\n')
.trim();
// Copy the Markdown to clipboard
navigator.clipboard.writeText(markdownText)
.then(() => {
console.log("Enhanced Markdown copied to clipboard:", markdownText);
//alert("Enhanced Markdown copied!v5");
})
.catch((err) => {
console.error("Failed to copy enhanced content:", err);
});
}
// Listen for Alt + Shift + C to trigger enhanced copy
document.addEventListener('keydown', function (e) {
if (e.altKey && e.shiftKey && e.code === 'KeyC') {
e.preventDefault();
enhancedCopy();
}
});
console.log("Shortcut Alt + Shift + C is ready for enhanced copying.");
})();