您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows one email at a time in Gmail inbox, with enhanced email detection and accurate counter for empty inbox.
当前为
// ==UserScript== // @name Gmail Single Email View // @namespace http://tampermonkey.net/ // @version 2.0 // Incremented version to signify major bug fix for counter // @description Shows one email at a time in Gmail inbox, with enhanced email detection and accurate counter for empty inbox. // @author Your Name // @match https://mail.google.com/* // @grant none // ==/UserScript== (function() { 'use strict'; let emailRows = []; let currentIndex = 0; let navControls; let prevBtn, nextBtn, countDisplay; let observer = null; let updateTimeoutId = null; let isPluginInitialized = false; // Flag to ensure initialization runs only once /** * Injects necessary CSS into the Gmail page to ensure elements can be hidden and essential UI remains visible. */ function injectCss() { const styleId = 'single-email-view-style'; if (document.getElementById(styleId)) { return; // Style already injected } const style = document.createElement('style'); style.id = styleId; style.textContent = ` /* Styling for the plugin's navigation buttons */ #single-email-nav-controls { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 10000; /* Ensure it's above Gmail's UI */ display: flex; gap: 10px; background-color: #fff; padding: 10px 20px; border-radius: 12px; /* Rounded corners */ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Soft shadow */ border: 1px solid #ddd; } #single-email-nav-controls button { padding: 10px 20px; font-size: 16px; cursor: pointer; border: none; border-radius: 8px; /* Rounded corners for buttons */ background-color: #4285f4; /* Gmail blue */ color: white; transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } #single-email-nav-controls button:hover { background-color: #357ae8; /* Darker blue on hover */ transform: translateY(-1px); } #single-email-nav-controls button:active { background-color: #2a6ac3; transform: translateY(0); box-shadow: none; } #single-email-nav-controls button:disabled { background-color: #cccccc; cursor: not-allowed; box-shadow: none; } /* Styling for the current email count display */ #email-count-display { display: flex; align-items: center; justify-content: center; min-width: 80px; padding: 0 10px; font-weight: bold; color: #333; } /* IMPORTANT: Only hide email rows with this class to avoid hiding everything */ .single-email-hidden { display: none !important; } /* Ensure essential Gmail UI components (like sidebars, toolbars) remain visible */ .nH.nn.aKz { /* Pagination/toolbar at the bottom */ display: flex !important; /* This should generally be flex */ } .G-atb, .Cp { /* Top toolbar and category tabs */ display: block !important; /* These are usually block or flex */ } /* Explicitly ensure left navigation sidebar is visible (but let internal layout be managed by Gmail) */ div[role="navigation"] { /* The main navigation sidebar container */ display: block !important; } `; document.head.appendChild(style); console.log('Gmail Single Email View: Injected CSS styles.'); } /** * Initializes the Gmail Single Email View plugin. * Creates navigation controls using createElement to avoid TrustedHTML issues. */ function initGmailSingleEmailView() { if (isPluginInitialized) { console.log('Gmail Single Email View: Plugin already initialized, skipping.'); return; } console.log('Gmail Single Email View: Initializing plugin interface...'); injectCss(); // Ensure CSS is injected first navControls = document.getElementById('single-email-nav-controls'); if (!navControls) { navControls = document.createElement('div'); navControls.id = 'single-email-nav-controls'; document.body.appendChild(navControls); console.log('Gmail Single Email View: Navigation controls container created and appended.'); // Create buttons and display element separately and append them prevBtn = document.createElement('button'); prevBtn.id = 'prevEmailBtn'; prevBtn.textContent = 'Previous'; prevBtn.disabled = true; // Start disabled navControls.appendChild(prevBtn); countDisplay = document.createElement('div'); countDisplay.id = 'email-count-display'; navControls.appendChild(countDisplay); nextBtn = document.createElement('button'); nextBtn.id = 'nextEmailBtn'; nextBtn.textContent = 'Next'; nextBtn.disabled = true; // Start disabled navControls.appendChild(nextBtn); console.log('Gmail Single Email View: Navigation buttons and display element created and appended.'); } else { console.log('Gmail Single Email View: Navigation controls already exist.'); // If controls exist, just get references prevBtn = document.getElementById('prevEmailBtn'); nextBtn = document.getElementById('nextEmailBtn'); countDisplay = document.getElementById('email-count-display'); } addEventListeners(); updateEmailList(); isPluginInitialized = true; // Set flag after successful initialization } /** * Adds event listeners to the navigation buttons. */ function addEventListeners() { if (prevBtn && !prevBtn.hasAttribute('data-listener-added')) { prevBtn.onclick = showPreviousEmail; prevBtn.setAttribute('data-listener-added', 'true'); console.log('Gmail Single Email View: Previous button event listener added.'); } if (nextBtn && !nextBtn.hasAttribute('data-listener-added')) { nextBtn.onclick = showNextEmail; nextBtn.setAttribute('data-listener-added', 'true'); console.log('Gmail Single Email View: Next button event listener added.'); } } /** * Resets the plugin state when no emails are found. * This ensures the counter correctly displays "No Emails Found". */ function resetPluginState() { emailRows = []; // Clear the list of emails currentIndex = 0; // Reset index console.log('Gmail Single Email View: Resetting plugin state: no emails found.'); showEmail(0); // This will ensure all hidden emails are revealed, and internal display logic runs updateButtonStates(); updateCountDisplay(); } /** * Updates the list of email rows and displays the current email. */ function updateEmailList() { console.log('Gmail Single Email View: Scanning for email rows...'); let potentialEmailRows = []; // Check if the "No new emails!" message is present const noEmailsMessage = document.querySelector('div[role="main"] .nH.nZ.nn'); if (noEmailsMessage && noEmailsMessage.textContent.includes('No new emails!')) { console.log('Gmail Single Email View: "No new emails!" message detected.'); resetPluginState(); // Call the dedicated reset function return; // Exit early as there are no emails to process } // Primary selector based on your provided HTML structure potentialEmailRows = Array.from(document.querySelectorAll('tr.zA.yO[role="row"]:has([role="checkbox"])')); console.log(`Gmail Single Email View: Found ${potentialEmailRows.length} potential email rows with primary selector (tr.zA.yO[role="row"]:has([role="checkbox"])).`); // If primary selector finds few or none, try other common patterns in Gmail if (potentialEmailRows.length === 0 || potentialEmailRows.length < 5) { console.log('Gmail Single Email View: Primary selector found few/no rows, trying alternative selectors...'); // Fallback to more general selectors if the primary one isn't fruitful let altRows = Array.from(document.querySelectorAll('div[role="main"] tr[role="row"], .AO > .xY, [aria-labelledby^="thread_"], [aria-labelledby^="msg_"]')); // Only use altRows if they find more results if (altRows.length > potentialEmailRows.length) { potentialEmailRows = altRows; console.log(`Gmail Single Email View: Found ${potentialEmailRows.length} rows with alternative combined selectors.`); } } // Filter out non-email rows (e.g., header, pagination, ads, empty message rows, non-email UI elements) emailRows = potentialEmailRows.filter(row => { // Check for elements characteristic of an actual email row: const hasCheckbox = row.querySelector('[role="checkbox"]'); const hasGridcell = row.querySelector('[role="gridcell"]'); const isColumnHeader = row.querySelector('[role="columnheader"]'); // More robust subject/sender checks based on your HTML const hasSubject = row.querySelector('.bog, .ts, .y6'); const hasSender = row.querySelector('.yP, .zF, .go'); // The row should look like email content AND not be a header. // Require at least a checkbox, a gridcell, and EITHER a subject/sender. const isEmailContentRow = hasCheckbox && hasGridcell && (hasSubject || hasSender) && !isColumnHeader; // Explicitly exclude known non-email rows or UI elements that might get caught by broad selectors const isKnownNonEmailUI = row.classList.contains('Cp') || row.classList.contains('nH') || row.classList.contains('G-atb') || row.classList.contains('Bu') || row.querySelector('.aeJ') || row.matches('.ads, .AP'); if (!isEmailContentRow || isKnownNonEmailUI) { // console.log('Gmail Single Email View: Filtering out non-email row (lacks essential email characteristics or is known UI element):', row.outerHTML); } return isEmailContentRow && !isKnownNonEmailUI; }); console.log(`Gmail Single Email View: Filtered down to ${emailRows.length} actual email rows.`); // After filtering, if no emails are found, reset the plugin state if (emailRows.length === 0) { console.log('Gmail Single Email View: Filtered result is 0 emails. Calling resetPluginState.'); resetPluginState(); } else { // If emails are found, proceed with normal display logic // Adjust currentIndex if it's out of bounds after filtering if (currentIndex >= emailRows.length) { currentIndex = emailRows.length - 1; } else if (currentIndex < 0) { currentIndex = 0; } showEmail(currentIndex); updateButtonStates(); updateCountDisplay(); } } /** * Shows only the email at the given index and hides all others. * @param {number} index - The index of the email to show. */ function showEmail(index) { // Always ensure ALL previously hidden emails are made visible before re-hiding document.querySelectorAll('.single-email-hidden').forEach(el => el.classList.remove('single-email-hidden')); if (emailRows.length === 0) { console.log('No email rows found to display, ensuring all elements are visible and buttons disabled.'); updateButtonStates(); // Ensure buttons are disabled updateCountDisplay(); // Ensure counter says "No Emails Found" return; // No emails to show, so nothing to hide } if (index < 0 || index >= emailRows.length) { console.warn(`Attempted to show email at invalid index: ${index}. Clamping to valid range.`); index = Math.max(0, Math.min(index, emailRows.length - 1)); } emailRows.forEach((row, i) => { if (i === index) { row.classList.remove('single-email-hidden'); row.style.display = ''; // Ensure display is not explicitly set to none by previous operation } else { row.classList.add('single-email-hidden'); } }); currentIndex = index; updateButtonStates(); updateCountDisplay(); console.log(`Showing email ${currentIndex + 1} of ${emailRows.length}.`); } /** * Shows the next email in the list. */ function showNextEmail() { if (currentIndex < emailRows.length - 1) { console.log('Moving to next email...'); showEmail(currentIndex + 1); } else { console.log('Already at the last email.'); } } /** * Shows the previous email in the list. */ function showPreviousEmail() { if (currentIndex > 0) { console.log('Moving to previous email...'); showEmail(currentIndex - 1); } else { console.log('Already at the first email.'); } } /** * Updates the disabled state of the navigation buttons. */ function updateButtonStates() { if (prevBtn) { prevBtn.disabled = currentIndex === 0 || emailRows.length === 0; } if (nextBtn) { nextBtn.disabled = currentIndex >= emailRows.length - 1 || emailRows.length === 0; } } /** * Updates the display showing current email index and total count. */ function updateCountDisplay() { if (countDisplay) { countDisplay.textContent = emailRows.length > 0 ? `${currentIndex + 1} / ${emailRows.length}` : 'No Emails Found'; } } /** * Observes the DOM for changes in Gmail. * This function now observes the entire document body for stability. */ function setupMutationObserver() { // Disconnect existing observer if it exists to prevent duplicates if (observer) { observer.disconnect(); console.log('Gmail Single Email View: Disconnected existing observer.'); } isPluginInitialized = false; // Reset initialization flag on new observer setup // Observe the entire document body for maximum robustness observer = new MutationObserver((mutations) => { // Check for relevant changes that would impact email list const relevantChange = mutations.some(mutation => mutation.addedNodes.length > 0 && Array.from(mutation.addedNodes).some(node => node.nodeType === Node.ELEMENT_NODE && (node.matches('tr.zA.yO[role="row"]') || node.matches('.nH.nZ.nn')) // Check for new email rows or "No new emails!" message ) || mutation.removedNodes.length > 0 && Array.from(mutation.removedNodes).some(node => node.nodeType === Node.ELEMENT_NODE && (node.matches('tr.zA.yO[role="row"]') || node.matches('.nH.nZ.nn')) ) || // Also trigger if the immediate parent of email rows (the tbody or table) changes its children (mutation.type === 'childList' && (mutation.target.matches('tbody') || mutation.target.matches('table.F.cf.zt'))) ); // Only re-scan if a relevant change occurred if (relevantChange) { clearTimeout(updateTimeoutId); updateTimeoutId = setTimeout(() => { console.log('Gmail Single Email View: DOM changes detected, re-scanning emails...'); initGmailSingleEmailView(); // Re-initialize to ensure state and elements are correct }, 500); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false }); console.log('Gmail Single Email View: Started observing document.body for changes.'); // Initial call to initGmailSingleEmailView initGmailSingleEmailView(); } // Initial setup attempts window.addEventListener('load', setupMutationObserver); document.addEventListener('DOMContentLoaded', setupMutationObserver); // Fallback for immediate execution if DOM is already ready if (document.readyState === 'complete' || document.readyState === 'interactive') { setupMutationObserver(); } })();