support all languages && improved code readability and maintainability

This commit is contained in:
qcrao
2024-05-15 12:03:10 +08:00
parent 0267d05e83
commit 6fc1a93ac2
6 changed files with 87 additions and 101 deletions

View File

@@ -1,16 +1,67 @@
console.log('addCheckboxes.js loaded'); console.log('addCheckboxes.js loaded');
// 查找带有特定选择器的祖先元素 // Add checkboxes to each conversation
function addCheckboxes() {
console.log('Adding checkboxes to conversations...', Selectors);
const conversations = document.querySelectorAll(Selectors.CONVERSATION_SELECTOR);
conversations.forEach((conversation, index) => {
let checkbox = conversation.querySelector(`.${CHECKBOX_CLASS}`);
// If the checkbox does not exist, create and insert it
if (!checkbox) {
checkbox = createCheckbox(index);
conversation.insertAdjacentElement('afterbegin', checkbox);
}
// Add click event to the conversation title
const titleElement = conversation.querySelector(Selectors.TITLE_SELECTOR);
if (titleElement) {
titleElement.style.cursor = 'default';
// Add click event listener if not already added
if (!titleElement.dataset.hasClickListener) {
addClickEventListener(titleElement);
}
// Add click event listener to the parent element if not already added
const parentElement = titleElement.parentElement;
if (parentElement && !parentElement.dataset.hasClickListener) {
parentElement.style.cursor = 'default';
addClickEventListener(parentElement);
}
}
});
}
// Create a new checkbox element
function createCheckbox(index) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = CHECKBOX_CLASS;
checkbox.dataset.index = index;
checkbox.addEventListener('click', preventEventPropagation);
return checkbox;
}
// Add click event listener to an element
function addClickEventListener(element) {
const handleTitleClick = (event) => {
toggleCheckbox(event);
event.stopPropagation();
};
element.addEventListener('click', handleTitleClick);
element.dataset.hasClickListener = 'true';
}
function findAncestorWithCheckbox(el, selector) { function findAncestorWithCheckbox(el, selector) {
while ((el = el.parentElement) && !el.querySelector(selector)); while ((el = el.parentElement) && !el.querySelector(selector));
return el; return el;
} }
// 切换复选框的选中状态 // Toggle the checkbox's checked state
function toggleCheckbox(event) { function toggleCheckbox(event) {
// 阻止事件的默认行为(例如链接跳转)
event.preventDefault(); event.preventDefault();
// 阻止事件冒泡到父元素
event.stopPropagation(); event.stopPropagation();
const parentElement = findAncestorWithCheckbox(event.currentTarget, `.${CHECKBOX_CLASS}`); const parentElement = findAncestorWithCheckbox(event.currentTarget, `.${CHECKBOX_CLASS}`);
@@ -18,66 +69,10 @@ function toggleCheckbox(event) {
if (checkbox) { if (checkbox) {
checkbox.checked = !checkbox.checked; checkbox.checked = !checkbox.checked;
} }
event.stopPropagation();
} }
// 阻止事件冒泡
function preventEventPropagation(event) { function preventEventPropagation(event) {
event.stopPropagation(); event.stopPropagation();
} }
// 添加复选框到每个对话
function addCheckboxes() {
console.log('Adding checkboxes to conversations...', Selectors);
const conversations = document.querySelectorAll(Selectors.CONVERSATION_SELECTOR);
conversations.forEach((conversation, index) => {
let existingCheckbox = conversation.querySelector(`.${CHECKBOX_CLASS}`);
// 如果复选框已存在,获取其选中状态并移除它
let isChecked = existingCheckbox ? existingCheckbox.checked : false;
if (existingCheckbox) {
existingCheckbox.remove();
}
// 创建新的复选框并设置其属性
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = CHECKBOX_CLASS;
checkbox.dataset.index = index;
checkbox.checked = isChecked;
checkbox.addEventListener('click', preventEventPropagation);
conversation.insertAdjacentElement('afterbegin', checkbox);
// 为对话标题添加点击事件
const titleElement = conversation.querySelector(Selectors.TITLE_SELECTOR);
if (titleElement) {
titleElement.style.cursor = 'default';
// 获取 titleElement 的父元素
const parentElement = titleElement.parentElement;
// 定义一个通用的事件处理函数
const handleTitleClick = (event) => {
toggleCheckbox(event);
event.stopPropagation(); // 防止事件冒泡
};
// 为 titleElement 添加点击事件
if (!titleElement.dataset.hasClickListener) {
titleElement.addEventListener('click', handleTitleClick);
titleElement.dataset.hasClickListener = 'true';
}
// 为 titleElement 的父元素添加点击事件
if (parentElement && !parentElement.dataset.hasClickListener) {
parentElement.style.cursor = 'default';
parentElement.addEventListener('click', handleTitleClick);
parentElement.dataset.hasClickListener = 'true';
}
}
});
}
// 执行主函数
addCheckboxes(); addCheckboxes();

View File

@@ -21,18 +21,14 @@ function getSelectedConversations() {
} }
function removeAllCheckboxes() { function removeAllCheckboxes() {
const allCheckboxes = document.querySelectorAll(Selectors.conversationsCheckbox); const allCheckboxes = document.querySelectorAll(`.${CHECKBOX_CLASS}`);
allCheckboxes.forEach(checkbox => checkbox.remove()); allCheckboxes.forEach(checkbox => checkbox.remove());
} }
async function deleteConversation(checkbox) { async function deleteConversation(checkbox) {
const conversationElement = checkbox.parentElement;
await delay(100); await delay(100);
// console.log("1. Clicking conversation...", conversationElement);
// conversationElement.click();
// 将 click 替换为悬停
// 创建一个鼠标悬停事件 const conversationElement = checkbox.parentElement;
const hoverEvent = new MouseEvent('mouseover', { const hoverEvent = new MouseEvent('mouseover', {
view: window, view: window,
bubbles: true, bubbles: true,
@@ -40,7 +36,6 @@ async function deleteConversation(checkbox) {
}); });
console.log("1. Hovering over conversation...", conversationElement); console.log("1. Hovering over conversation...", conversationElement);
// 触发鼠标悬停事件
conversationElement.dispatchEvent(hoverEvent); conversationElement.dispatchEvent(hoverEvent);
await delay(200); await delay(200);
@@ -50,9 +45,9 @@ async function deleteConversation(checkbox) {
pointerType: 'mouse' pointerType: 'mouse'
}); });
const threeDotButton = await waitForElement(Selectors.threeDotButton, conversationElement.parentElement); const threeDotButton = await waitForElement(Selectors.threeDotButton, conversationElement.parentElement);
console.log("2. Clicking three dot button...", threeDotButton);
threeDotButton.dispatchEvent(pointerDownEvent); threeDotButton.dispatchEvent(pointerDownEvent);
await delay(300); await delay(300);
console.log("2. Clicking three dot button...", threeDotButton);
const deleteButton = await waitForDeleteButton(); const deleteButton = await waitForDeleteButton();
@@ -60,8 +55,8 @@ async function deleteConversation(checkbox) {
console.log("3. Clicking delete button...", deleteButton); console.log("3. Clicking delete button...", deleteButton);
deleteButton.click(); deleteButton.click();
const confirmButton = await waitForElement(Selectors.confirmDeleteButton);
const confirmButton = await waitForElement(Selectors.confirmDeleteButton);
if (confirmButton) { if (confirmButton) {
console.log("4. Clicking confirm button..."); console.log("4. Clicking confirm button...");
confirmButton.click(); confirmButton.click();
@@ -74,22 +69,22 @@ async function deleteConversation(checkbox) {
} }
async function waitForDeleteButton(parent = document, timeout = 2000) { async function waitForDeleteButton(parent = document, timeout = 2000) {
const selector = 'div[role="menuitem"]'; // 设定好选择器 const selector = 'div[role="menuitem"]';
const textContent = "Delete"; // 指定文本内容 const textContent = "Delete";
const startedAt = Date.now(); const startedAt = Date.now();
while ((Date.now() - startedAt) < timeout) { while ((Date.now() - startedAt) < timeout) {
const elements = parent.querySelectorAll(selector); const elements = parent.querySelectorAll(selector);
const element = Array.from(elements).find(el => el.textContent.trim() === textContent); const element = Array.from(elements).find(el =>
if (element) return element; // 返回找到的元素 el.textContent.trim() === textContent || el.querySelector('.text-token-text-error')
);
if (element) return element;
await delay(100); await delay(100);
} }
console.log(`Delete button not found within ${timeout}ms`); return null;
throw new Error(`Delete button not found within ${timeout}ms`);
} }
// Helper function to create a delay
function delay(ms) { function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
@@ -98,10 +93,10 @@ async function waitForElement(selector, parent = document, timeout = 2000) {
const startedAt = Date.now(); const startedAt = Date.now();
while ((Date.now() - startedAt) < timeout) { while ((Date.now() - startedAt) < timeout) {
const element = parent.querySelector(selector); const element = parent.querySelector(selector);
if (element) return element; // 返回找到的元素 if (element) return element;
await delay(100); await delay(100);
} }
console.log(`Element ${selector} not found within ${timeout}ms in the specified parent`);
throw new Error(`Element ${selector} not found within ${timeout}ms in the specified parent`); throw new Error(`Element ${selector} not found within ${timeout}ms in the specified parent`);
} }
@@ -112,11 +107,8 @@ async function waitForElementToDisappear(selector, timeout = 2000) {
if (!element) return; if (!element) return;
await delay(100); await delay(100);
} }
throw new Error(`Element ${selector} did not disappear within ${timeout}ms`); throw new Error(`Element ${selector} did not disappear within ${timeout}ms`);
} }
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
bulkDeleteConversations(); bulkDeleteConversations();

View File

@@ -3,23 +3,22 @@ if (typeof window.globalsLoaded === 'undefined') {
window.globalsLoaded = true; window.globalsLoaded = true;
let lastChecked = null; const lastChecked = null;
let Selectors = { const Selectors = {
// Plus 用户的选择器
conversationsCheckbox: '.conversation-checkbox:checked', conversationsCheckbox: '.conversation-checkbox:checked',
confirmDeleteButton: 'button.btn.btn-danger', confirmDeleteButton: 'button.btn.btn-danger',
threeDotButton: '[id^="radix-"]', threeDotButton: '[id^="radix-"]',
// 其他 Plus 用户选择器...
CONVERSATION_SELECTOR: 'div > div > div > div > div > div > nav > div > div > div > div > ol > li > div > a', CONVERSATION_SELECTOR: 'div > div > div > div > div > div > nav > div > div > div > div > ol > li > div > a',
TITLE_SELECTOR: '.relative.grow.overflow-hidden.whitespace-nowrap', TITLE_SELECTOR: '.relative.grow.overflow-hidden.whitespace-nowrap',
}; };
let CHECKBOX_CLASS = 'conversation-checkbox'; const CHECKBOX_CLASS = 'conversation-checkbox';
// Expose variables to the global scope
window.lastChecked = lastChecked; window.lastChecked = lastChecked;
window.Selectors = Selectors; window.Selectors = Selectors;
window.CHECKBOX_CLASS = CHECKBOX_CLASS; window.CHECKBOX_CLASS = CHECKBOX_CLASS;
} else { } else {
console.log('globals.js already loaded, skipping re-initialization'); console.log('globals.js already loaded, skipping re-initialization');
} }

View File

@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "ChatGPT Bulk Delete", "name": "ChatGPT Bulk Delete",
"version": "4.5", "version": "4.6",
"description": "A Chrome extension to bulk delete ChatGPT conversations", "description": "A Chrome extension to bulk delete ChatGPT conversations",
"icons": { "icons": {
"48": "icon48.png" "48": "icon48.png"

View File

@@ -1,9 +1,9 @@
(function() { function removeCheckboxesAndReload() {
const removeConversationCheckboxes = document.querySelectorAll('.conversation-checkbox'); const checkboxes = document.querySelectorAll(`.${CHECKBOX_CLASS}`);
removeConversationCheckboxes.forEach(checkbox => { checkboxes.forEach(checkbox => checkbox.remove());
checkbox.remove();
}); // Refresh the page after removing all checkboxes
// 在移除所有复选框后刷新页面 location.reload();
location.reload(); }
})();
removeCheckboxesAndReload();

View File

@@ -1,9 +1,9 @@
function toggleCheckboxes() { function toggleCheckboxes() {
const conversations = document.querySelectorAll(".conversation-checkbox"); const conversations = document.querySelectorAll(`.${CHECKBOX_CLASS}`);
conversations.forEach((checkbox) => { conversations.forEach((checkbox) => {
checkbox.checked = !checkbox.checked; checkbox.checked = !checkbox.checked;
}); });
} }
toggleCheckboxes(); toggleCheckboxes();