-
Notifications
You must be signed in to change notification settings - Fork 22
Open
Description
Stopped working for me today, with TypeError: undefined is not an object (evaluating 'o.classList [0].split')
Claude suggested the following instead, which now works for me.
// Fixed version of the Claude export script with better error handling
(function() {
// Helper function to safely get language from classList
function getLanguageFromClassList(element) {
if (!element || !element.classList || element.classList.length === 0) {
return 'text'; // default fallback
}
// Look for language- classes
for (let className of element.classList) {
if (className.startsWith('language-')) {
return className.split('-')[1] || 'text';
}
}
// Fallback: try the first class if it contains a dash
const firstClass = element.classList[0];
if (firstClass && firstClass.includes('-')) {
return firstClass.split('-')[1] || 'text';
}
return 'text'; // ultimate fallback
}
// Save function
function saveToFile(content, format, filename = '') {
const mimeType = format === 'json' ? 'application/json' : 'text/plain';
const extension = format === 'json' ? '.json' : '.md';
let finalFilename = filename ?
filename.trim().toLowerCase().replace(/^[^\w\d]+|[^\w\d]+$/g, "").replace(/[\s\W-]+/g, "-") :
"claude";
finalFilename += extension;
if (format === 'json' && typeof content === 'object') {
content = JSON.stringify(content, null, 2);
}
const blob = new Blob([content], { type: mimeType });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.download = finalFilename;
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
}
// Get current timestamp
function getCurrentTimestamp() {
return new Date().toISOString().slice(0, 19).replace("T", " ");
}
// Extract conversation data
function extractConversation() {
const container = document.querySelector("div.flex-1.flex.flex-col.gap-3.px-4");
const titleButton = document.querySelector("button[data-testid='chat-menu-trigger']");
const title = titleButton ? titleButton.textContent : "";
if (!container) {
console.error("Could not find conversation container");
return null;
}
const messageElements = container.querySelectorAll("div.font-claude-message, div.font-user-message");
const conversation = {
meta: {
exported_at: getCurrentTimestamp(),
title: title
},
chats: []
};
for (let i = 0; i < messageElements.length; i++) {
const element = messageElements[i];
const message = {
index: i,
type: element.classList.contains("font-claude-message") ? "response" : "prompt",
message: []
};
const firstChild = element.firstChild;
if (!firstChild) continue;
let contentNodes = [];
if (firstChild.nodeType === Node.ELEMENT_NODE) {
if (message.type === "response") {
const innerChild = firstChild.firstChild || firstChild;
contentNodes = Array.from(innerChild.childNodes);
} else {
contentNodes = Array.from(element.childNodes);
}
for (const node of contentNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
const tagName = node.tagName;
const textContent = node.textContent;
if (tagName === 'P') {
message.message.push({ type: 'p', data: textContent });
} else if (tagName === 'OL' || tagName === 'UL') {
const items = Array.from(node.querySelectorAll('li')).map(li => ({
type: 'li',
data: li.textContent
}));
message.message.push({
type: tagName.toLowerCase(),
data: items
});
} else if (tagName === 'PRE') {
const codeElement = node.querySelector('code');
if (codeElement) {
const code = codeElement.textContent;
const language = getLanguageFromClassList(codeElement);
message.message.push({
type: 'pre',
language: language,
data: code
});
}
} else if (tagName === 'TABLE') {
const tableData = [];
const sections = node.querySelectorAll('thead, tbody');
sections.forEach(section => {
const rows = [];
const rowElements = section.querySelectorAll('tr');
rowElements.forEach(row => {
const cells = [];
const cellElements = row.querySelectorAll('td, th');
cellElements.forEach(cell => {
cells.push({
type: cell.tagName.toLowerCase(),
data: cell.textContent
});
});
rows.push({ type: 'tr', data: cells });
});
tableData.push({
type: section.tagName.toLowerCase(),
data: rows
});
});
message.message.push({ type: 'table', data: tableData });
}
}
}
} else if (firstChild.nodeType === Node.TEXT_NODE) {
message.message.push({ type: 'text', data: firstChild.textContent });
}
conversation.chats.push(message);
}
return conversation;
}
// Main execution
try {
const conversation = extractConversation();
if (conversation) {
console.log("Conversation extracted successfully:", conversation);
saveToFile(conversation, 'json', conversation.meta.title);
console.log("✅ Export completed successfully!");
} else {
console.error("❌ Failed to extract conversation");
}
} catch (error) {
console.error("❌ Export failed:", error);
}
})();
Metadata
Metadata
Assignees
Labels
No labels