Skip to content

Instantly share code, notes, and snippets.

@richdrummer33
Last active April 7, 2025 00:25
Show Gist options
  • Save richdrummer33/6a22cc4d73b3ce0a2a48ca8e6a741d92 to your computer and use it in GitHub Desktop.
Save richdrummer33/6a22cc4d73b3ce0a2a48ca8e6a741d92 to your computer and use it in GitHub Desktop.
Downloads Claude.ai conversations as markdown
javascript:(function() {
console.log("Claude to Markdown bookmarklet started");
function waitForClaudeMessages(retries = 10) {
const messages = document.querySelectorAll(".p-4.text-base");
if (messages.length === 0 && retries > 0) {
console.log("Waiting for messages to load...");
setTimeout(() => waitForClaudeMessages(retries - 1), 500);
return;
}
console.log("Messages found:", messages.length);
extractClaudeMarkdown(messages);
}
// Basic HTML to Markdown conversion functions
function htmlToMarkdown(html) {
// Create a temporary div to parse HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
// Process code blocks
tempDiv.querySelectorAll('pre').forEach(pre => {
const codeElement = pre.querySelector('code');
const language = codeElement && codeElement.className.match(/language-(\w+)/) ?
codeElement.className.match(/language-(\w+)/)[1] : '';
const code = codeElement ? codeElement.innerText : pre.innerText;
pre.outerHTML = `\n\`\`\`${language}\n${code}\n\`\`\`\n`;
});
// Process inline code
tempDiv.querySelectorAll('code:not(pre code)').forEach(code => {
code.outerHTML = '`' + code.innerText + '`';
});
// Process strong/bold
tempDiv.querySelectorAll('strong, b').forEach(strong => {
strong.outerHTML = '**' + strong.innerText + '**';
});
// Process emphasis/italic
tempDiv.querySelectorAll('em, i').forEach(em => {
em.outerHTML = '*' + em.innerText + '*';
});
// Process links
tempDiv.querySelectorAll('a').forEach(link => {
link.outerHTML = '[' + link.innerText + '](' + link.href + ')';
});
// Process lists
tempDiv.querySelectorAll('ul, ol').forEach(list => {
const isOrdered = list.tagName === 'OL';
const items = Array.from(list.querySelectorAll('li'));
items.forEach((item, index) => {
const prefix = isOrdered ? `${index + 1}. ` : '- ';
item.innerHTML = prefix + item.innerHTML;
});
list.outerHTML = '\n' + Array.from(items).map(i => i.innerHTML).join('\n') + '\n';
});
// Process headings
for (let i = 1; i <= 6; i++) {
tempDiv.querySelectorAll(`h${i}`).forEach(heading => {
const hashes = '#'.repeat(i);
heading.outerHTML = `\n${hashes} ${heading.innerText}\n`;
});
}
// Process paragraphs
tempDiv.querySelectorAll('p').forEach(p => {
p.outerHTML = '\n' + p.innerHTML + '\n';
});
// Process line breaks
tempDiv.querySelectorAll('br').forEach(br => {
br.outerHTML = '\n';
});
// Clean up the text
let markdown = tempDiv.innerText || tempDiv.textContent;
markdown = markdown.replace(/\n{3,}/g, '\n\n'); // Replace multiple newlines with just two
return markdown;
}
// Wait for Claude messages to load
waitForClaudeMessages();
// Clone the body to work with
const bodyClone = document.body.cloneNode(true);
console.log("Body cloned for processing");
// Get the title of the conversation
const title = document.title || "Claude Conversation";
let markdownContent = `# ${title}\n\n`;
console.log("Processing title:", title);
// Find user messages
const userMessages = bodyClone.querySelectorAll('[data-testid="user-message"]');
console.log(`Found ${userMessages.length} user messages`);
// Find Claude's messages
const claudeMessages = bodyClone.querySelectorAll('.font-claude-message');
console.log(`Found ${claudeMessages.length} Claude messages`);
// Determine the total number of message exchanges
const messageCount = Math.max(userMessages.length, claudeMessages.length);
console.log(`Processing ${messageCount} message exchanges`);
// Process each message pair
for (let i = 0; i < messageCount; i++) {
const userMessage = userMessages[i];
if (userMessage) {
markdownContent += `## Human: ${i+1}\n\n`;
markdownContent += htmlToMarkdown(userMessage.innerHTML);
markdownContent += '\n\n';
}
const claudeMessage = claudeMessages[i];
if (claudeMessage) {
markdownContent += `## Claude: ${i+1}\n\n`;
// Get the main content from Claude's message
const claudeContent = claudeMessage.querySelector('[data-testid="message-content"]') ||
claudeMessage.querySelector('.grid-cols-1') ||
claudeMessage;
markdownContent += htmlToMarkdown(claudeContent.innerHTML);
markdownContent += '\n\n';
}
}
// Clean up the markdown content
markdownContent = markdownContent
.replace(/\n{3,}/g, '\n\n')
.replace(/&gt;/g, '>')
.replace(/&lt;/g, '<')
.replace(/&amp;/g, '&');
console.log("Markdown content generated");
// Create a download link for the markdown file
const downloadLink = document.createElement('a');
downloadLink.download = `${title.replace(/[^\w\s]/gi, '')}.md`;
downloadLink.href = URL.createObjectURL(new Blob([markdownContent], {type: 'text/markdown'}));
downloadLink.style.display = "none";
// Add the link to the document and click it to start the download
document.body.appendChild(downloadLink);
console.log("Triggering download");
downloadLink.click();
// Clean up
document.body.removeChild(downloadLink);
console.log("Bookmarklet execution completed");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment