From 6fc1a93ac2f3b82980270cda83853c8c206e17ef Mon Sep 17 00:00:00 2001 From: qcrao Date: Wed, 15 May 2024 12:03:10 +0800 Subject: [PATCH] support all languages && improved code readability and maintainability --- addCheckboxes.js | 115 ++++++++++++++++++------------------- bulkDeleteConversations.js | 38 +++++------- globals.js | 11 ++-- manifest.json | 2 +- removeCheckboxes.js | 18 +++--- toggleCheckboxes.js | 4 +- 6 files changed, 87 insertions(+), 101 deletions(-) diff --git a/addCheckboxes.js b/addCheckboxes.js index b44459e..aa21de1 100644 --- a/addCheckboxes.js +++ b/addCheckboxes.js @@ -1,16 +1,67 @@ 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) { while ((el = el.parentElement) && !el.querySelector(selector)); return el; } -// 切换复选框的选中状态 +// Toggle the checkbox's checked state function toggleCheckbox(event) { - // 阻止事件的默认行为(例如链接跳转) event.preventDefault(); - // 阻止事件冒泡到父元素 event.stopPropagation(); const parentElement = findAncestorWithCheckbox(event.currentTarget, `.${CHECKBOX_CLASS}`); @@ -18,66 +69,10 @@ function toggleCheckbox(event) { if (checkbox) { checkbox.checked = !checkbox.checked; } - event.stopPropagation(); } -// 阻止事件冒泡 function preventEventPropagation(event) { 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(); diff --git a/bulkDeleteConversations.js b/bulkDeleteConversations.js index 92b9b96..9ef07a0 100644 --- a/bulkDeleteConversations.js +++ b/bulkDeleteConversations.js @@ -21,18 +21,14 @@ function getSelectedConversations() { } function removeAllCheckboxes() { - const allCheckboxes = document.querySelectorAll(Selectors.conversationsCheckbox); + const allCheckboxes = document.querySelectorAll(`.${CHECKBOX_CLASS}`); allCheckboxes.forEach(checkbox => checkbox.remove()); } async function deleteConversation(checkbox) { - const conversationElement = checkbox.parentElement; await delay(100); - // console.log("1. Clicking conversation...", conversationElement); - // conversationElement.click(); - // 将 click 替换为悬停 - // 创建一个鼠标悬停事件 + const conversationElement = checkbox.parentElement; const hoverEvent = new MouseEvent('mouseover', { view: window, bubbles: true, @@ -40,7 +36,6 @@ async function deleteConversation(checkbox) { }); console.log("1. Hovering over conversation...", conversationElement); - // 触发鼠标悬停事件 conversationElement.dispatchEvent(hoverEvent); await delay(200); @@ -50,9 +45,9 @@ async function deleteConversation(checkbox) { pointerType: 'mouse' }); const threeDotButton = await waitForElement(Selectors.threeDotButton, conversationElement.parentElement); + console.log("2. Clicking three dot button...", threeDotButton); threeDotButton.dispatchEvent(pointerDownEvent); await delay(300); - console.log("2. Clicking three dot button...", threeDotButton); const deleteButton = await waitForDeleteButton(); @@ -60,8 +55,8 @@ async function deleteConversation(checkbox) { console.log("3. Clicking delete button...", deleteButton); deleteButton.click(); - const confirmButton = await waitForElement(Selectors.confirmDeleteButton); + const confirmButton = await waitForElement(Selectors.confirmDeleteButton); if (confirmButton) { console.log("4. Clicking confirm button..."); confirmButton.click(); @@ -74,22 +69,22 @@ async function deleteConversation(checkbox) { } async function waitForDeleteButton(parent = document, timeout = 2000) { - const selector = 'div[role="menuitem"]'; // 设定好选择器 - const textContent = "Delete"; // 指定文本内容 + const selector = 'div[role="menuitem"]'; + const textContent = "Delete"; const startedAt = Date.now(); while ((Date.now() - startedAt) < timeout) { const elements = parent.querySelectorAll(selector); - const element = Array.from(elements).find(el => el.textContent.trim() === textContent); - if (element) return element; // 返回找到的元素 + const element = Array.from(elements).find(el => + el.textContent.trim() === textContent || el.querySelector('.text-token-text-error') + ); + if (element) return element; await delay(100); } - - console.log(`Delete button not found within ${timeout}ms`); - throw new Error(`Delete button not found within ${timeout}ms`); + + return null; } -// Helper function to create a delay function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -98,10 +93,10 @@ async function waitForElement(selector, parent = document, timeout = 2000) { const startedAt = Date.now(); while ((Date.now() - startedAt) < timeout) { const element = parent.querySelector(selector); - if (element) return element; // 返回找到的元素 + if (element) return element; 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`); } @@ -112,11 +107,8 @@ async function waitForElementToDisappear(selector, timeout = 2000) { if (!element) return; await delay(100); } + throw new Error(`Element ${selector} did not disappear within ${timeout}ms`); } -function delay(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - bulkDeleteConversations(); diff --git a/globals.js b/globals.js index e8ad938..7f89c6a 100644 --- a/globals.js +++ b/globals.js @@ -3,23 +3,22 @@ if (typeof window.globalsLoaded === 'undefined') { window.globalsLoaded = true; - let lastChecked = null; + const lastChecked = null; - let Selectors = { - // Plus 用户的选择器 + const Selectors = { conversationsCheckbox: '.conversation-checkbox:checked', confirmDeleteButton: 'button.btn.btn-danger', threeDotButton: '[id^="radix-"]', - // 其他 Plus 用户选择器... 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', }; - let CHECKBOX_CLASS = 'conversation-checkbox'; + const CHECKBOX_CLASS = 'conversation-checkbox'; + // Expose variables to the global scope window.lastChecked = lastChecked; window.Selectors = Selectors; window.CHECKBOX_CLASS = CHECKBOX_CLASS; } else { console.log('globals.js already loaded, skipping re-initialization'); -} \ No newline at end of file +} diff --git a/manifest.json b/manifest.json index 313bed3..eedca8a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "ChatGPT Bulk Delete", - "version": "4.5", + "version": "4.6", "description": "A Chrome extension to bulk delete ChatGPT conversations", "icons": { "48": "icon48.png" diff --git a/removeCheckboxes.js b/removeCheckboxes.js index 5289ef6..66fb57f 100644 --- a/removeCheckboxes.js +++ b/removeCheckboxes.js @@ -1,9 +1,9 @@ -(function() { - const removeConversationCheckboxes = document.querySelectorAll('.conversation-checkbox'); - removeConversationCheckboxes.forEach(checkbox => { - checkbox.remove(); - }); - // 在移除所有复选框后刷新页面 - location.reload(); - })(); - \ No newline at end of file +function removeCheckboxesAndReload() { + const checkboxes = document.querySelectorAll(`.${CHECKBOX_CLASS}`); + checkboxes.forEach(checkbox => checkbox.remove()); + + // Refresh the page after removing all checkboxes + location.reload(); +} + +removeCheckboxesAndReload(); \ No newline at end of file diff --git a/toggleCheckboxes.js b/toggleCheckboxes.js index 18c464d..78b30d1 100644 --- a/toggleCheckboxes.js +++ b/toggleCheckboxes.js @@ -1,9 +1,9 @@ function toggleCheckboxes() { - const conversations = document.querySelectorAll(".conversation-checkbox"); + const conversations = document.querySelectorAll(`.${CHECKBOX_CLASS}`); conversations.forEach((checkbox) => { checkbox.checked = !checkbox.checked; }); } -toggleCheckboxes(); +toggleCheckboxes(); \ No newline at end of file