Skip to content

Instantly share code, notes, and snippets.

@jackcarey
Last active January 7, 2024 13:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jackcarey/b3f65e67b1fe4544096c02d05cc065c6 to your computer and use it in GitHub Desktop.
Save jackcarey/b3f65e67b1fe4544096c02d05cc065c6 to your computer and use it in GitHub Desktop.
Adds buttons to Gmail to quickly toggle the unread filter on the current view. Appears left of the search text input.
// ==UserScript==
// @name Filter Gmail to unread
// @namespace http://tampermonkey.net/
// @version 1.1.1
// @description Adds a button to Gmail to quickly toggle the unread filter on the current view.
// @author jackcarey
// @match https://mail.google.com/mail/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant none
// @run-at document-idle
// @homepageURL https://gist.github.com/jackcarey/b3f65e67b1fe4544096c02d05cc065c6
// @downloadURL https://gist.github.com/jackcarey/b3f65e67b1fe4544096c02d05cc065c6/raw/filter-gmail-unread.user.js
// @updateURL https://gist.github.com/jackcarey/b3f65e67b1fe4544096c02d05cc065c6/raw/filter-gmail-unread.user.js
// ==/UserScript==
function waitForElement(selector) {
return new Promise((resolve) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(() => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.querySelector("body"), {
childList: true,
subtree: true,
});
});
}
waitForElement(`button[aria-label="Search mail"]`).then(function() {
'use strict';
const searchInputEl = document.querySelector(`*[name="q"]`);
const searchBtnEl = document.querySelector(`button[aria-label="Search mail"]`);
const unreadBtn = document.createElement("button");
const archivedBtn = document.createElement("button");
const btnSize = "1.5em";
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
which: 13,
keyCode: 13,
});
const clickToggler = (querySegment)=>{
return (evt)=>{
evt.preventDefault();
if(!searchInputEl?.value?.includes(querySegment)){
searchInputEl.value+=` ${querySegment}`;
}else{
searchInputEl.value = searchInputEl.value.replace(querySegment,"").trim();
}
while(searchInputEl.value.includes(" ")){
searchInputEl.value = searchInputEl.value.replace(" "," ");
}
searchInputEl.value = searchInputEl.value.replace(new RegExp("\s{2,}","gi")," ");
searchInputEl.value = searchInputEl.value.trim();
searchInputEl.dispatchEvent(enterEvent);
};
};
//Unread button
unreadBtn.style.width=btnSize;
unreadBtn.style.height=btnSize;
unreadBtn.position="relative";
unreadBtn.style.top= "1em";
unreadBtn.style.left= "-2.5em";
unreadBtn.style.backgroundSize = "contain";
unreadBtn.title="Toggle unread messages in current view";
unreadBtn.style.backgroundImage="url(https://ssl.gstatic.com/ui/v1/icons/mail/gm3/1x/stacked_email_baseline_nv700_20dp.png)";
unreadBtn.className = searchBtnEl.className;
unreadBtn.addEventListener("click",clickToggler("-is:read"));
///Archived button
archivedBtn.style.width=btnSize;
archivedBtn.style.height=btnSize;
archivedBtn.position="relative";
archivedBtn.style.top= "1em";
archivedBtn.style.left= "-5em";
archivedBtn.style.backgroundSize = "contain";
archivedBtn.title="Toggle archived messages in current view";
archivedBtn.style.backgroundImage="url(https://ssl.gstatic.com/ui/v1/icons/mail/gm3/1x/inbox_baseline_nv700_20dp.png)";
archivedBtn.className = searchBtnEl.className;
archivedBtn.addEventListener("click",clickToggler("in:inbox"));
//append buttons
searchBtnEl.after(unreadBtn);
searchBtnEl.after(archivedBtn);
});
@f-steff
Copy link

f-steff commented Dec 30, 2023

This seems to no longer work. Tested during Christmas 2023.

@jackcarey
Copy link
Author

This seems to no longer work. Tested during Christmas 2023.

Hey Flemming, I've only used this script with TamperMonkey on the latest version of Chrome so please can you tell me more about the issues you're facing? Which browser are you in and do you see any error messages in the browser developer console?

@f-steff
Copy link

f-steff commented Jan 1, 2024

Thank you for your quick response, Jack.
I hope you have had a nice Christmas and new years eve.

I'm also using TamperMonkey on the latest version of Chrome, but on MacOS - I don't know if theres really a difference?
The main problem I have, is that no quick toggle button appear when running the script.
Perhaps I'm not looking the right place?

I don't really see any errors in the developer console that's directly relatable to your script, but comparing the output between no userscripts at all, and enabling just your userscript, I do see this error three times on page reload: "The service worker navigation preload request was cancelled before 'preloadResponse' settled. If you intend to use 'preloadResponse', use waitUntil() or respondWith() to wait for the promise to settle."

I have good success with other userscripts on gmail, such as this excellent script: https://gist.github.com/szhu/1d816086307c5de02bc9a2bb1cf01fe0

@jackcarey
Copy link
Author

jackcarey commented Jan 1, 2024

The buttons should appear left of the search field on desktop. I’ll get a screenshot later

@f-steff
Copy link

f-steff commented Jan 2, 2024

"On desktop"?
I was expecting it to be accessible with the browser with Gmail at the inbox (https://mail.google.com/mail/u/0/#inbox), with a label filter active(https://mail.google.com/mail/u/0/#label/MyPrivatLabel) or in a category (https://mail.google.com/mail/u/0/#category/social). Yet, in none of these locations do I see the toggle button left of the search field.
Did I misunderstand the location?

@jackcarey
Copy link
Author

Here's the functionality in a gif:

gmail-buttons

The buttons will appear on every view and they alter which emails are displayed by toggling fragments of the search term. The left button toggles in:inbox, which filters out emails that are archived. The right button toggles -is:read, which filters out emails that have been read.

@f-steff
Copy link

f-steff commented Jan 2, 2024

It looks good, but unfortunately, it's not working here. :-(

Screenshot 2024-01-02 at 16 54 41

@jackcarey
Copy link
Author

for the removal of doubt, is the script enabled when you load https://mail.google.com/mail?

image

@f-steff
Copy link

f-steff commented Jan 2, 2024

Yes, the script is shown as running when I click on the TamperMonkey icon.

@jackcarey
Copy link
Author

Ah okay, I think I know where this is going wrong. On line 36 I hardcoded the script to look for a button with the label Search mail, but your search bar says Search in emails so I suspect the button is the same.

It’ll be some time before I can alter the selector so it doesn’t rely on language/locale. For now, you can manually update line 36.

@f-steff
Copy link

f-steff commented Jan 3, 2024

Okay. After modifying the Search mail text in lines 36 and 39, I partially made it work.
Partial means that the icons show up, and I can click on them to activate and deactivate the two search modes which is nice.
However, only in a cagatory or in a label list view, when in the Inbox, Bin, Starred etc. when I click on them to disable the search mode, only the search string is modified but not the URL, which means the search mode continues to be active. :-(

As a side-note. I've been working on some Gmail userscripts, too, and it seems to me that Gmail is made to be the least scriptable possible: Depending on the current list type, such as Inbox, Bin, Starred, Category or Label, most of the id's and many of the classes, as well as the aria text-strings are changing, and now it also seems some are depending on the selected language, too - mine is set to UK English.
I'm considering to try to rely as much as possible on the classes that are defined in the css, as they appear static, although this will probably require using a lot of :nth-of-type selectors and other workarounds to count around in the elements on the page. Have you made any considerations, or have any suggestions, about this?

@jackcarey
Copy link
Author

The starred view is simply a shortcut to searching for is:starred and the bin for in:trash. For this reason, the buttons in this script should also update the page URL when they fire the 'Enter' key press event (line 43). This happens for me and it's using a standard browser API, so I'm interested to know why this isn't the case for you 😕

The classes on the page are minified and obscured like you say so my advice is not to rely on them. This is why I currently target the aria-label as I know it is less likely to change. FWIW, Gmail can be scripted well through Apps Script. This doesn't allow for changing the UI though and it's really aimed at add-on developers. If you're happy to trigger functions via scripts.google.com (manually or through timers), it's a useful thing to do.

@f-steff
Copy link

f-steff commented Jan 4, 2024

Over the weekend I'll investigate further exactly when the the page URL sometimes does not update. It appears very consistent.

Apps script won't allow me to modify the things I want to, but thanks for pointing that option out. I'm well aware all the variables are minified, but for the things I want to modify (such as the label lists), it seems the minified class names are more stable than the minified id's, and many of the aria-* values. In some cases, I found the aria-* texts to include the id values, but changing between at least 10 different values depending on which Section/Category/Label was selected. So I'm really wondering exactly what can be relied on.

@jackcarey
Copy link
Author

jackcarey commented Jan 5, 2024 via email

@f-steff
Copy link

f-steff commented Jan 5, 2024

Sorry for hijacking your gist's comment section.

I have a lot (thousands) of labels and nested labels - years back this functionality had better support i gmail, but now it's dreadful, to the point that currently the only place Gmail really handles it well is when manually adding a label to a message, where it's possible to search for substrings in a label, so it can quickly be located and selected. Looking up a label else-where, the search is either not, or hardly existing, making it hard to add new sub labels and to find the ones that currently exist.
So my plan was to try to improve that. Since they already have a fully working search implementation it seems like an easy case of reuse - but it will require many things in the UI to be hidden or modified, and new things injected.
All this should be relatively easy, if it wasn't for the UI elements, that always exists in the same order in the HTML,, to have 10+ different id's, classes or aria-* identifiers, depending on the initial starting point and apparently also the UI language.

@jackcarey
Copy link
Author

Cool. Handling nested labels is something I dislike too. I have an Apps Script that includes a searchTermFromLabelFragment function for finding nested labels. My plan is to develop an add-on that allows users to store these searches and take actions against matching threads. Using the Gmail Add-on API is the way to build UI elements as far as Google is concerned. It also has the benefits of cross-platform support and predictable interfaces.

Once I’ve had chance to develop something, I’ll share it here

@f-steff
Copy link

f-steff commented Jan 7, 2024

Sounds really interesting. I'll keep monitoring this space. :-)

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