Skip to content

Instantly share code, notes, and snippets.

@SMUsamaShah
Last active March 3, 2025 07:51
Show Gist options
  • Select an option

  • Save SMUsamaShah/dfcc6f709158b0adcc0a48c2d66fd353 to your computer and use it in GitHub Desktop.

Select an option

Save SMUsamaShah/dfcc6f709158b0adcc0a48c2d66fd353 to your computer and use it in GitHub Desktop.
Gmail sort by sender bookmarklet (drag the code to bookmark bar)
javascript:(function() {
function sortEmailsBySender() {
var emails = Array.from(document.querySelectorAll('.zA'));
emails.sort(function(a, b) {
var senderA = a.querySelector('.yW span').innerText.toLowerCase();
var senderB = b.querySelector('.yW span').innerText.toLowerCase();
return senderA.localeCompare(senderB);
});
emails.forEach(function(email) {
email.parentNode.appendChild(email);
});
}
sortEmailsBySender();
})();
@SMUsamaShah
Copy link
Copy Markdown
Author

SMUsamaShah commented Feb 28, 2025

Thanks to Claude. Following is working near perfectly

javascript:(function() {
    function sortEmailsBySender() {
        // Find the table containing emails
        var emailTable = document.querySelector('table[role="grid"]');
        if (!emailTable) {
            console.log('Could not find email table');
            return;
        }

        // Get the tbody or the table itself if no tbody
        var tableBody = emailTable.querySelector('tbody') || emailTable;
        
        // Get all row elements (emails)
        var rows = Array.from(tableBody.querySelectorAll('tr[role="row"]'));
        if (rows.length === 0) {
            // Try alternate selector for newer Gmail versions
            rows = Array.from(tableBody.querySelectorAll('tr.zA'));
        }
        
        if (rows.length === 0) {
            alert('No email rows found to sort');
            return;
        }
        
        // Store original positions to remember them later
        rows.forEach(function(row, index) {
            row.setAttribute('data-original-position', index);
        });
        
        // Create array of rows with sender info
        var rowsWithSender = rows.map(function(row) {
            // Different possible selectors for sender names
            var sender = '';
            var senderElement = row.querySelector('.yW span') || 
                             row.querySelector('[email]') ||
                             row.querySelector('[name]');
                             
            if (senderElement) {
                sender = senderElement.innerText || senderElement.getAttribute('email') || senderElement.getAttribute('name') || '';
                sender = sender.toLowerCase();
            }
            
            return {
                row: row,
                sender: sender,
                originalPosition: parseInt(row.getAttribute('data-original-position'))
            };
        });
        
        // Sort the rows by sender
        rowsWithSender.sort(function(a, b) {
            // First by sender name
            var senderCompare = a.sender.localeCompare(b.sender);
            if (senderCompare !== 0) return senderCompare;
            
            // If sender names are the same, keep original order
            return a.originalPosition - b.originalPosition;
        });
        
        // Instead of moving DOM elements, modify their appearance order with CSS
        rowsWithSender.forEach(function(rowData, index) {
            rowData.row.style.order = index;
        });
        
        // Add flexbox display to container to make the order property work
        tableBody.style.display = 'flex';
        tableBody.style.flexDirection = 'column';
        
        // Ensure rows take full width
        rows.forEach(function(row) {
            row.style.width = '100%';
        });
        
        // Add click handler to override Gmail's default behavior
        document.addEventListener('click', function(e) {
            var row = e.target.closest('tr[role="row"]') || e.target.closest('tr.zA');
            if (!row) return;
            
            // Only handle clicks on the row itself, not on buttons or links
            if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON' || 
                e.target.closest('a') || e.target.closest('button') || 
                e.target.closest('[role="button"]') || 
                e.target.closest('.T-I') || e.target.closest('.T-I-J-J5-Ji')) {
                return; // Let Gmail handle these clicks
            }
            
            // Find the conversation link
            var conversationLink = row.querySelector('a[href*="#inbox/"]') || 
                                  row.querySelector('a[href*="mail/u/"]') ||
                                  row.querySelector('a[href*="#search/"]');
            
            if (conversationLink && conversationLink.href) {
                window.location.href = conversationLink.href;
                e.preventDefault();
                e.stopPropagation();
            }
        }, true); // Use capture phase for early interception
    }
    
    sortEmailsBySender();
})();

Shift + Select is not working correctly though

@SMUsamaShah
Copy link
Copy Markdown
Author

SMUsamaShah commented Feb 28, 2025

It fixed that too

javascript:(function() {
    function sortEmailsBySender() {
        // Find the table containing emails
        var emailTable = document.querySelector('table[role="grid"]');
        if (!emailTable) {
            console.log('Could not find email table');
            return;
        }

        // Get the tbody or the table itself if no tbody
        var tableBody = emailTable.querySelector('tbody') || emailTable;
        
        // Get all row elements (emails)
        var rows = Array.from(tableBody.querySelectorAll('tr[role="row"]'));
        if (rows.length === 0) {
            // Try alternate selector for newer Gmail versions
            rows = Array.from(tableBody.querySelectorAll('tr.zA'));
        }
        
        if (rows.length === 0) {
            alert('No email rows found to sort');
            return;
        }
        
        // Store original positions to remember them later
        rows.forEach(function(row, index) {
            row.setAttribute('data-original-position', index);
        });
        
        // Create array of rows with sender info
        var rowsWithSender = rows.map(function(row) {
            // Different possible selectors for sender names
            var sender = '';
            var senderElement = row.querySelector('.yW span') || 
                             row.querySelector('[email]') ||
                             row.querySelector('[name]');
                             
            if (senderElement) {
                sender = senderElement.innerText || senderElement.getAttribute('email') || senderElement.getAttribute('name') || '';
                sender = sender.toLowerCase();
            }
            
            return {
                row: row,
                sender: sender,
                originalPosition: parseInt(row.getAttribute('data-original-position'))
            };
        });
        
        // Sort the rows by sender
        rowsWithSender.sort(function(a, b) {
            // First by sender name
            var senderCompare = a.sender.localeCompare(b.sender);
            if (senderCompare !== 0) return senderCompare;
            
            // If sender names are the same, keep original order
            return a.originalPosition - b.originalPosition;
        });
        
        // Store sorted order for each row
        rowsWithSender.forEach(function(rowData, index) {
            rowData.row.setAttribute('data-sorted-position', index);
            rowData.row.style.order = index;
        });
        
        // Add flexbox display to container to make the order property work
        tableBody.style.display = 'flex';
        tableBody.style.flexDirection = 'column';
        
        // Ensure rows take full width
        rows.forEach(function(row) {
            row.style.width = '100%';
        });
        
        // Track last clicked row for shift-select
        var lastClickedRow = null;
        
        // Add custom click handler for shift-select functionality
        document.addEventListener('click', function(e) {
            var row = e.target.closest('tr[role="row"]') || e.target.closest('tr.zA');
            if (!row) return;
            
            // Find the checkbox element
            var checkbox = row.querySelector('input[type="checkbox"]') || 
                          row.querySelector('[role="checkbox"]');
            
            // Handle shift-clicks for our custom select behavior
            if (e.shiftKey && checkbox && lastClickedRow) {
                e.preventDefault();
                e.stopPropagation();
                
                // Get sorted positions
                var currentPos = parseInt(row.getAttribute('data-sorted-position'));
                var lastPos = parseInt(lastClickedRow.getAttribute('data-sorted-position'));
                
                // Determine range to select
                var startPos = Math.min(currentPos, lastPos);
                var endPos = Math.max(currentPos, lastPos);
                
                // Select all rows in the range based on sorted order
                rows.forEach(function(r) {
                    var sortedPos = parseInt(r.getAttribute('data-sorted-position'));
                    if (sortedPos >= startPos && sortedPos <= endPos) {
                        var cb = r.querySelector('input[type="checkbox"]') || 
                               r.querySelector('[role="checkbox"]');
                        if (cb) {
                            // Check if not already checked
                            if (cb.checked !== true && cb.getAttribute('aria-checked') !== 'true') {
                                cb.click(); // Simulate click to check
                            }
                        }
                    }
                });
                
                return;
            }
            
            // Update last clicked row if checkbox was clicked
            if (e.target === checkbox || e.target.closest('[role="checkbox"]')) {
                lastClickedRow = row;
                return; // Let Gmail handle the single checkbox click
            }
            
            // Only handle clicks on the row itself, not on buttons or links
            if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON' || 
                e.target.closest('a') || e.target.closest('button') || 
                e.target.closest('[role="button"]') || 
                e.target.closest('.T-I') || e.target.closest('.T-I-J-J5-Ji')) {
                return; // Let Gmail handle these clicks
            }
            
            // Find the conversation link
            var conversationLink = row.querySelector('a[href*="#inbox/"]') || 
                                  row.querySelector('a[href*="mail/u/"]') ||
                                  row.querySelector('a[href*="#search/"]');
            
            if (conversationLink && conversationLink.href) {
                window.location.href = conversationLink.href;
                e.preventDefault();
                e.stopPropagation();
            }
        }, true); // Use capture phase for early interception
        
        console.log('Gmail emails sorted by sender with custom shift-select enabled');
    }    
    sortEmailsBySender();
})();

@shadsnz
Copy link
Copy Markdown

shadsnz commented Mar 2, 2025

Hi SMUsamaShah,
I'm trying to run this but it doesn't seem to sort the emails. I tested the very first version and it does sort but when you open the email its the original message from that position.

So these newer versions are obviously better but don't seem to work for me. I checked the console messages and it does seem to be triggering but doesn't change the emails shown.

Any suggestions on what I might have missed?

Thanks

@SMUsamaShah
Copy link
Copy Markdown
Author

@shadsnz Did you remove all the comments when using it as bookmarklet?

@shadsnz
Copy link
Copy Markdown

shadsnz commented Mar 2, 2025

Yes, I did. I'm using Chrome, have tested in both the Gmail Inbox, Sent and All Mail folders. I might try a different browser and another Gmail account to test.

@shadsnz
Copy link
Copy Markdown

shadsnz commented Mar 3, 2025

Well, it seems to work sometimes now after refreshing browsers and increasing the number of messages per page. Thanks for posting the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment