// ==UserScript==
// @name Claude - Bulk Delete Conversations
// @description Add checkboxes to chat list for bulk delete conversations
// @author intenzemotion
// @namespace https://greasyfork.org/en/users/923450
// @version 1.2
// @license MIT
// @match https://claude.ai/*
// @run-at document-end
// @grant none
// ==/UserScript==
(function () {
'use strict';
let org = '';
let isBulkMode = false;
let buttonObserver = null;
let selectAllCheckbox = null;
// Retrieve a cookie value by name
const getCookieValue = (name) => document.cookie.split(`; ${name}=`).pop().split(';').shift();
// Create an HTML element with specified styles and content
const createElement = (tagName, styles, content = '') => {
const element = document.createElement(tagName);
element.innerHTML = content.trim();
Object.assign(element.style, styles);
return element;
};
// Set styles for "Bulk Mode" button based on its state
const setButtonStyles = (button, state) => {
const baseStyles = {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: '10',
transition: 'filter 0.3s ease',
padding: '10px 20px',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
};
const stateStyles = {
default: { backgroundColor: '#2D5F91', text: 'Bulk Mode' },
cancel: { backgroundColor: '#6E788C', text: 'Cancel' },
delete: { backgroundColor: '#BE4646', text: 'Delete Selected' },
};
Object.assign(button.style, baseStyles, { backgroundColor: stateStyles[state].backgroundColor });
button.textContent = stateStyles[state].text;
button.onmouseover = () => (button.style.filter = 'brightness(1.1)');
button.onmouseout = () => (button.style.filter = 'none');
};
// Show a loading message during deletion process
const showLoading = (message) => {
const loadingDiv = createElement('div', {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
padding: '20px',
borderRadius: '5px',
zIndex: '10',
}, `<div id="loading-message">${message}</div>`);
document.body.appendChild(loadingDiv);
};
// Delete selected conversations
const deleteSelectedChats = async () => {
if (!org) {
alert('Could not retrieve current organization id');
return;
}
const checkedItems = document.querySelectorAll('ul.flex.flex-col.gap-3 > li input[type="checkbox"]:checked');
const uuids = Array.from(checkedItems).map((checkbox) => checkbox.closest('li').querySelector('a').getAttribute('href').split('/').pop());
if (!uuids.length) {
alert('No conversations selected for deletion.');
return;
}
if (confirm(`Delete ${uuids.length} conversation(s)?`)) {
showLoading('Deleting conversations...');
try {
await Promise.all(uuids.map((uuid) =>
fetch(`https://claude.ai/api/organizations/${org}/chat_conversations/${uuid}`, {
method: 'DELETE',
credentials: 'include',
})
));
window.location.reload();
} catch (error) {
console.error('Error deleting conversations:', error);
document.getElementById('loading-message')?.remove();
alert('An error occurred while deleting conversations. Please try again.');
}
}
};
// Toggle checkboxes for each conversation
const toggleCheckboxes = (add) => {
const listItems = document.querySelectorAll('ul.flex.flex-col.gap-3 > li');
listItems.forEach((item, index) => {
const existingCheckbox = item.querySelector('input[type="checkbox"]');
if (add && !existingCheckbox) {
const checkbox = createElement('input', {
position: 'absolute',
right: '10px',
top: '50%',
transform: 'translateY(-50%)',
zIndex: '10',
});
checkbox.type = 'checkbox';
checkbox.id = `conversation-checkbox-${index}`;
item.style.position = 'relative';
item.appendChild(checkbox);
checkbox.addEventListener('change', updateBulkModeState);
} else if (!add && existingCheckbox) {
existingCheckbox.remove();
}
});
toggleSelectAllCheckbox(add);
};
// Toggle "Select All" checkbox
const toggleSelectAllCheckbox = (add) => {
const container = document.querySelector('ul.flex.flex-col.gap-3');
if (!container) return;
if (add && !selectAllCheckbox) {
const selectAllLabel = createElement('label', {
display: 'flex',
alignItems: 'center',
marginBottom: '10px',
cursor: 'pointer',
}, `<input type="checkbox" id="select-all-checkbox" style="margin-right: 5px;">Select All`);
selectAllCheckbox = selectAllLabel.querySelector('#select-all-checkbox');
selectAllCheckbox.addEventListener('change', handleSelectAll);
container.parentNode.insertBefore(selectAllLabel, container);
} else if (!add && selectAllCheckbox) {
selectAllCheckbox.closest('label').remove();
selectAllCheckbox = null;
}
};
// Handle "Select All" checkbox changes
const handleSelectAll = () => {
const checkboxes = document.querySelectorAll('ul.flex.flex-col.gap-3 > li input[type="checkbox"]');
checkboxes.forEach((checkbox) => (checkbox.checked = selectAllCheckbox.checked));
updateBulkModeState();
};
// Update "Bulk Mode" button and "Select All" checkbox states
const updateBulkModeState = () => {
const bulkButton = document.getElementById('bulkDeleteButton');
if (!bulkButton) return;
const checkboxes = document.querySelectorAll('ul.flex.flex-col.gap-3 > li input[type="checkbox"]');
const checkedItems = Array.from(checkboxes).filter(cb => cb.checked);
setButtonStyles(bulkButton, checkedItems.length ? 'delete' : 'cancel');
if (selectAllCheckbox) {
selectAllCheckbox.checked = checkboxes.length && checkedItems.length === checkboxes.length;
selectAllCheckbox.indeterminate = checkedItems.length > 0 && checkedItems.length < checkboxes.length;
}
};
// Toggle "Bulk Mode" on and off
const toggleBulkMode = () => {
isBulkMode = !isBulkMode;
toggleCheckboxes(isBulkMode);
const bulkButton = document.getElementById('bulkDeleteButton');
if (bulkButton) setButtonStyles(bulkButton, isBulkMode ? 'cancel' : 'default');
if (isBulkMode) updateBulkModeState();
};
// Handle clicks on "Bulk Mode" button
const handleBulkButtonClick = () => {
if (!isBulkMode) {
toggleBulkMode();
} else {
const checkedItems = document.querySelectorAll('ul.flex.flex-col.gap-3 > li input[type="checkbox"]:checked');
checkedItems.length ? deleteSelectedChats() : toggleBulkMode();
}
};
// Update visibility of "Bulk Mode" button
const toggleBulkButton = (show) => {
const existingButton = document.getElementById('bulkDeleteButton');
if (show && !existingButton) {
const button = createElement('button', {
padding: '10px 20px',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
}, 'Bulk Mode');
button.id = 'bulkDeleteButton';
button.addEventListener('click', handleBulkButtonClick);
setButtonStyles(button, 'default');
document.body.appendChild(button);
} else if (!show && existingButton) {
existingButton.remove();
if (isBulkMode) toggleBulkMode();
}
};
// Handle navigation changes and update UI accordingly
const handleNavigation = () => {
const isRecentsPage = window.location.pathname === '/recents';
toggleBulkButton(isRecentsPage);
if (isRecentsPage) {
if (!buttonObserver) {
buttonObserver = new MutationObserver((mutations) => {
if (mutations.some((mutation) => mutation.type === 'childList')) {
toggleBulkButton(true);
}
});
buttonObserver.observe(document.body, { childList: true, subtree: true });
}
} else if (buttonObserver) {
buttonObserver.disconnect();
buttonObserver = null;
}
};
// Initialize script
const init = () => {
org = getCookieValue('lastActiveOrg') || '';
handleNavigation();
window.addEventListener('popstate', handleNavigation);
const pushState = history.pushState;
history.pushState = function () {
pushState.apply(history, arguments);
handleNavigation();
};
};
// Start the script
init();
})();