diff --git a/README.md b/README.md index 0e2bd32..ac89acd 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,4 @@ English | [中文版本](./README-CN.md) - Select the conversations you wish to delete. - Click the "Bulk delete" button, and the selected conversations will be deleted. - If needed, you can click the "Remove checkboxes" button to hide the checkboxes. +- It's possible to select all checkboxes between your last selection and the one being selected by holding shift. diff --git a/addCheckboxes.js b/addCheckboxes.js index aa21de1..42917d1 100644 --- a/addCheckboxes.js +++ b/addCheckboxes.js @@ -1,78 +1,152 @@ -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); - } - } - }); -} +console.log("addCheckboxes.js loaded"); // 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; + 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'; + 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; + while ((el = el.parentElement) && !el.querySelector(selector)); + return el; } // Toggle the checkbox's checked state function toggleCheckbox(event) { - event.preventDefault(); - event.stopPropagation(); + event.preventDefault(); + event.stopPropagation(); - const parentElement = findAncestorWithCheckbox(event.currentTarget, `.${CHECKBOX_CLASS}`); - const checkbox = parentElement ? parentElement.querySelector(`.${CHECKBOX_CLASS}`) : null; - if (checkbox) { - checkbox.checked = !checkbox.checked; + const parentElement = findAncestorWithCheckbox( + event.currentTarget, + `.${CHECKBOX_CLASS}` + ); + const checkbox = parentElement + ? parentElement.querySelector(`.${CHECKBOX_CLASS}`) + : null; + if (checkbox) { + checkbox.checked = !checkbox.checked; + checkPreviousCheckboxes(checkbox); + + // 更新最后选中的复选框 + if (checkbox.checked) { + window.lastCheckedCheckbox = checkbox; } + } } -function preventEventPropagation(event) { - event.stopPropagation(); +function handleCheckboxClick(event) { + event.stopPropagation(); + + const clickedCheckbox = event.target; + checkPreviousCheckboxes(clickedCheckbox); + + // 更新最后选中的复选框 + window.lastCheckedCheckbox = clickedCheckbox; } +function checkPreviousCheckboxes(clickedCheckbox) { + if (window.shiftPressed && window.lastCheckedCheckbox) { + const allCheckboxes = Array.from( + document.querySelectorAll(`.${CHECKBOX_CLASS}`) + ); + const start = allCheckboxes.indexOf(window.lastCheckedCheckbox); + const end = allCheckboxes.indexOf(clickedCheckbox); + + const [lower, upper] = start < end ? [start, end] : [end, start]; + + for (let i = lower; i <= upper; i++) { + allCheckboxes[i].checked = true; + } + } +} + +function addShiftKeyEventListeners() { + console.log("Adding Shift key event listeners..."); + document.addEventListener("keydown", (event) => { + if (event.key === "Shift") { + console.log("Shift key pressed"); + window.shiftPressed = true; + } + }); + + document.addEventListener("keyup", (event) => { + if (event.key === "Shift") { + console.log("Shift key released"); + window.shiftPressed = false; + } + }); +} + +// 添加复选框到每个对话 +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", handleCheckboxClick); + conversation.insertAdjacentElement("afterbegin", checkbox); + + // add click event listener to the title element + const titleElement = conversation.querySelector(Selectors.TITLE_SELECTOR); + if (titleElement) { + titleElement.style.cursor = "default"; + + // get the parent element of titleElement + const parentElement = titleElement.parentElement; + + // define a common event handler + const handleTitleClick = (event) => { + toggleCheckbox(event); + event.stopPropagation(); // prevent event propagation + }; + + // add click event listener to titleElement + if (!titleElement.dataset.hasClickListener) { + titleElement.addEventListener("click", handleTitleClick); + titleElement.dataset.hasClickListener = "true"; + } + + // add click event listener to the parent element of titleElement + if (parentElement && !parentElement.dataset.hasClickListener) { + parentElement.style.cursor = "default"; + parentElement.addEventListener("click", handleTitleClick); + parentElement.dataset.hasClickListener = "true"; + } + } + }); + + addShiftKeyEventListeners(); +} + +// run the main function addCheckboxes(); diff --git a/globals.js b/globals.js index f2c50a8..d67a454 100644 --- a/globals.js +++ b/globals.js @@ -3,8 +3,6 @@ if (typeof window.globalsLoaded === "undefined") { window.globalsLoaded = true; - const lastChecked = null; - const Selectors = { conversationsCheckbox: ".conversation-checkbox:checked", confirmDeleteButton: "button.btn.btn-danger", @@ -32,8 +30,9 @@ if (typeof window.globalsLoaded === "undefined") { } // Expose variables to the global scope - window.lastChecked = lastChecked; window.Selectors = Selectors; + window.shiftPressed = false; + window.lastCheckedCheckbox = null; window.CHECKBOX_CLASS = CHECKBOX_CLASS; } else { console.log("globals.js already loaded, skipping re-initialization"); diff --git a/popup.css b/popup.css index 6400c94..593895e 100644 --- a/popup.css +++ b/popup.css @@ -249,3 +249,98 @@ button#bulk-archive { #modalCancel:hover { background-color: #e2e6ea; } + +/* 调整 button-container 样式 */ +.button-container { + position: relative; + display: inline-block; + width: calc(50% - 5px); /* 调整宽度以适应两个按钮并留有间隙 */ +} + +/* 调整 tooltip-trigger 样式 */ +.tooltip-trigger { + position: absolute; + top: 5px; + right: 5px; + width: 16px; + height: 16px; + color: rgba(255, 255, 255, 0.9); /* 近白色 */ + font-size: 14px; + font-style: italic; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + cursor: help; + transition: all 0.3s ease; + z-index: 2; + text-shadow: 0 0 2px rgba(0, 0, 0, 0.5); /* 添加轻微阴影以增加可见度 */ +} + +/* 调整 tooltip-content 样式 */ +.tooltip-content { + visibility: hidden; + width: 130px; + background-color: #333; + color: #fff; + text-align: left; + border-radius: 6px; + padding: 10px; + position: absolute; + z-index: 3; + top: -5px; + left: calc(100% + 10px); + opacity: 0; + transition: opacity 0.3s, visibility 0.3s; + pointer-events: none; + font-style: normal; + font-weight: normal; + font-size: 12px; + line-height: 1.4; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); +} + +.tooltip-content::after { + content: ""; + position: absolute; + top: 10px; + right: 100%; + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent #333 transparent transparent; +} + +.tooltip-trigger:hover .tooltip-content { + visibility: visible; + opacity: 1; +} + +/* 确保按钮容器不会因为tooltip内容而变形 */ +.button-container { + position: relative; + display: inline-block; + width: calc(50% - 5px); + overflow: visible; +} + +/* 可能需要调整按钮样式以适应新的布局 */ +.buttons-row button, +.button-container button { + width: 100%; + height: 100%; + position: relative; + z-index: 1; +} + +/* 更新按钮悬停效果 */ +.button-container:hover button, +.button-container:hover .tooltip-trigger { + transform: translateY(-3px); +} + +/* 添加悬停效果到 i 图标 */ +.tooltip-trigger:hover { + color: #fff; /* 完全白色 */ + text-shadow: 0 0 4px rgba(255, 255, 255, 0.5); /* 增强悬停时的光晕效果 */ +} diff --git a/popup.html b/popup.html index 9b3f271..1496a1f 100644 --- a/popup.html +++ b/popup.html @@ -12,9 +12,17 @@