Skip to content

Instantly share code, notes, and snippets.

@donaldguy
Last active September 12, 2024 03:47
Show Gist options
  • Save donaldguy/8b56909e704f2977914ac61ec586c735 to your computer and use it in GitHub Desktop.
Save donaldguy/8b56909e704f2977914ac61ec586c735 to your computer and use it in GitHub Desktop.
app.hey.com: add unread counts and auto-advance to the "Imbox" - very much a work in progress
// ==UserScript==
// @name Add counts to Hey.com Imbox (and contents)
// @run-at document-end
// @match https://app.hey.com/*
// @grant GM_getValue
// @grant GM_setValue
const BASE_URL_PATTERN = 'https://app\\.hey\\.com'
let UnreadCount = -1;
let SeenCount = -999;
let CachedUnreadURLs = new Set()
let ShouldAutoAdvance = false
function countsText() {
if (UnreadCount < 0) {
return ""
}
if (SeenCount < 0) {
return ` (${UnreadCount})`
}
return ` (${UnreadCount} / ${UnreadCount + SeenCount})`
}
class Page {
static url_pattern = new RegExp(`${BASE_URL_PATTERN}/.+`);
loaded() { return; }
}
const Imbox = new (class Imbox extends Page {
url_pattern = new RegExp(`^${BASE_URL_PATTERN}/$|^${BASE_URL_PATTERN}/imbox`);
get(_url) { return this }
loaded() {
this.postings = document.getElementById("postings")
this.selectUnread()
if (UnreadCount < 0) {
this.insertCountButton("Count Unread", this.scrollToLoadUnread.bind(this))
} else if (SeenCount < 0) {
this.insertCountButton("Count Total", this.scrollToLoadAll.bind(this))
}
this.processUnread()
if (ShouldAutoAdvance) {
this.auto_advance()
} else {
const titleWithCounts = `Imbox${countsText()}`
document.getElementsByTagName('h1')[0].innerText = titleWithCounts
document.title = titleWithCounts
}
}
auto_advance() {
ShouldAutoAdvance = false;
this.postings.querySelector("a.permalink").click()
}
selectUnread() {
this.unreadEmailArticles = this.postings.querySelectorAll('article[data-new-for-you="true"]');
}
insertCountButton(text, handler) {
const sheetActions = document.getElementsByClassName('sheet-actions')[0];
const firstButton = sheetActions.querySelector('a.btn');
this.countButton = document.createElement('a')
this.countButton.href = "#"
this.countButton.classList.add('btn')
this.countButton.classList.add('btn--secondary')
this.countButton.innerText = text
this.countButton.addEventListener('click', (e) => {
e.preventDefault();
handler()
});
sheetActions.insertBefore(this.countButton, firstButton)
}
scrollToLoadUnread() {
if (!this.scrollPoller) {
this.scrollPoller = setInterval(this.scrollToLoadUnread.bind(this), 300)
return
}
const readEmailLoaded = this.postings.querySelector('article[data-seen="true"]')
if (!readEmailLoaded) {
const oldestNewEmail = this.unreadEmailArticles[this.unreadEmailArticles.length - 1]
oldestNewEmail.scrollIntoView(true)
this.selectUnread()
return
} else {
readEmailLoaded.scrollIntoView(true)
this.selectUnread()
}
// if we made it to here, we have an old email in view and fetching
// _should_ be done
clearInterval(this.scrollPoller)
this.scrollPoller = false
UnreadCount = this.unreadEmailArticles.length
CachedUnreadURLs = new Set()
this.countButton.remove()
window.scrollTo(0, 0)
this.loaded()
}
scrollToLoadAll() {
// XXX: only works if cover is not active; state is in localStorage.peeking
// but how to mutate best?
if (!this.scrollPoller) {
this.scrollPoller = setInterval(this.scrollToLoadAll.bind(this), 800)
return
}
if (UnreadCount > 0) {
this.unreadEmailArticles[this.unreadEmailArticles.length - 1].scrollIntoView(true)
}
const readEmailLoaded = this.postings.querySelectorAll('article[data-seen="true"]')
if (!readEmailLoaded) {
return
}
const paginationLinkExists = this.postings.querySelector('.pagination-link')
while (paginationLinkExists) {
const oldestEmail = readEmailLoaded[readEmailLoaded.length - 1]
oldestEmail.scrollIntoView(true)
return
}
// if we made it to here, we have an old email in view and fetching
// _should_ be done
clearInterval(this.scrollPoller)
this.scrollPoller = false
SeenCount = readEmailLoaded.length
this.countButton.remove()
window.scrollTo(0, 0)
this.loaded()
}
processUnread() {
for (const article of this.unreadEmailArticles) {
CachedUnreadURLs.add(article.querySelector('a.permalink').href)
}
}
})()
class Thread extends Page {
static url_pattern = new RegExp(`^${BASE_URL_PATTERN}/topics/[^/]+$`);
constructor(url) {
super()
this.url = url
this.was_unread = CachedUnreadURLs && CachedUnreadURLs.has(this.url)
}
static get(url) { return new Thread(url); }
loaded() {
document.title = `${document.title}${countsText()}`
}
}
// ---------
class GMHeyNavigation {
constructor(from_page, to_page, why) {
this.from_page = from_page
this.to_page = to_page
this.why = why
console.log(this);
if (!from_page) {
return;
}
if (to_page.constructor === Thread && to_page.was_unread) {
UnreadCount -= 1
SeenCount += 1
CachedUnreadURLs.delete(from_page.url)
}
if (from_page.constructor === Thread && why == 'unseen') {
UnreadCount += 1
SeenCount -= 1
CachedUnreadURLs.add(from_page.url)
}
if (from_page.constructor === Thread && why == 'status/trashed') {
SeenCount -= 1
}
//unless its being moved _to_ the Imbox
if (from_page.constructor === Thread && why.startsWith('moves')) {
SeenCount -= 1
}
if (from_page.constructor === Thread && to_page === Imbox && why != 'back' && why != 'unseen') {
ShouldAutoAdvance = true
}
}
}
// --------
const detectPage = (url) => {
for (page of [Imbox, Thread]) {
if (page.url_pattern.test(url)) {
return page.get(url)
}
}
}
let CurrentPage = detectPage(window.location.href);
(function() {
new GMHeyNavigation(
undefined,
CurrentPage,
'load'
);
document.addEventListener('turbo:visit', function(event) {
var from = CurrentPage
var to = detectPage(event.detail.url)
var why = 'visit';
if (from.exit_reason) {
why = from.exit_reason
} else if (to === Imbox && event.detail.action == 'restore') {
why = 'back'
}
new GMHeyNavigation(from, to, why);
CurrentPage = to;
return true;
});
document.addEventListener('turbo:load', function() {
CurrentPage.loaded();
});
document.addEventListener('turbo:submit-end', function(event) {
if (!event.detail.success) {
return true;
}
if (CurrentPage.constructor === Thread && event.target.action.startsWith(CurrentPage.url)) {
CurrentPage.exit_reason = event.target.action.substr(CurrentPage.url.length + 1)
}
return true;
});
})();
@donaldguy
Copy link
Author

donaldguy commented Aug 9, 2024

Posted on reddit (and elaborated known issues, inter alia) here: https://www.reddit.com/r/HeyEmail/comments/1enms2q/sharing_my_userscript_js_arc_boost_imbox/

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