// ==UserScript==
// @name Mastodon Timeline Counter
// @version 1.0
// @description Indicates the number of remaining posts on the timeline
// @namespace http://tampermonkey.net/
// @author Bene Laszlo
// @match https://mastodon.social/@*
// @match https://mastodon.online/@*
// @icon https://mastodon.social/packs/media/icons/favicon-16x16-c58fdef40ced38d582d5b8eed9d15c5a.png
// @grant none
// @license MIT
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
var list, listContainerEl,
boxEl, counterEl, textEl,
count, lastCount=0,
subCount, placeholdersJustCreated,
result, lastResult,
total,
pack, packSize,
scrollCount=0,
lastPos,
firstContentFound,
first = true;
preInit();
// wait for timeline to load before anything can start
function preInit() {
var t = setInterval(function() {
var coll = document.getElementsByClassName('item-list');
if (coll.length) {
listContainerEl = coll[0];
clearTimeout(t);
init();
}
}, 200);
}
function init() {
// total;
var totalEl = document.getElementsByClassName('account__header__extra__links')[0].firstElementChild;
result = total = totalEl.getAttribute('title').replace(',', '');
// the counter element
var navPanel = document.getElementsByClassName('navigation-panel')[0];
var boxX = navPanel.getBoundingClientRect().left;
//// counter container
boxEl = document.createElement("div");
boxEl.style.position = 'fixed';
boxEl.style.left = (boxX+12)+'px';
boxEl.style.bottom = '10px';
boxEl.style.display = 'flex';
boxEl.style.gap = '4px';
boxEl.style.alignItems = 'flex-end';
//// counter
counterEl = document.createElement("div");
counterEl.style.fontSize = '40px';
counterEl.style.lineHeight = '.9em';
counterEl.style.fontWeight = '500';
//// additional text
textEl = document.createElement("div");
textEl.innerText = 'total';
textEl.style.color = '#606984';
boxEl.append(counterEl);
boxEl.append(textEl);
// featured hashtags should move aside
for (var el of document.getElementsByClassName('getting-started__trends')) {
el.style.position = 'relative';
el.style.top = '-70px';
el.style.borderBottom = '1px solid #393f4f';
}
putContent(total);
document.body.appendChild(boxEl);
document.addEventListener('scroll', handleScroll);
}
// scroll event, the main function
function handleScroll() {
if (window.scrollY <= lastPos) return; // scolling up or not scrolling further down
// update the counterEl1 (at every 12th scroll);
if (scrollCount == 0)
{
placeholdersJustCreated = false;
list = listContainerEl.getElementsByTagName('article');
count = list.length;
if (count != lastCount)
{
placeholdersJustCreated = true;
// get the newly loaded post placeholders
packSize = count-lastCount;
//console.log(count+" "+lastCount+' → '+packSize);
pack = Array.prototype.slice.call(list, -packSize);
//console.log(pack[0].getAttribute('aria-posinset')+' "'+pack[0].style.overflow+'"');
lastCount = count;
}
// there's only info about how many post PLACEHOLDERS are loaded
// need to know how many of them are fully loaded
if (!placeholdersJustCreated)
{
firstContentFound = false;
for (var i in pack)
{
var article = pack[i];
var isEmpty = article.style.overflow && article.style.overflow=='hidden';
if (!firstContentFound && isEmpty) continue; // posts scrolled past, which are ALREADY re-unloaded
firstContentFound = true;
if (isEmpty) break; // first post that is STILL unloaded
}
subCount = firstContentFound ? i : 0;
}
else {
subCount = 0;
}
var realCount = count-(packSize-subCount);
//console.log(subCount+' :: '+realCount);
console.log(count%20+" "+realCount+"=="+(count-1));
if (count%20!=0 && realCount==count-1) {end(result); return;}
result = total-realCount;
if (result!=total) result++; // post is digested when the NEXT post is loaded and its top is already visible
lastResult = result;
//first occurence
if (first) {
textEl.innerText = 'more to go';
first = false;
}
}
scrollCount ++;
if (scrollCount==12) scrollCount = 0;
lastPos = window.scrollY;
putContent(result);
}
function putContent(n) {
// displaying result while fixing kerning of number 1
var resultStr = '';
const digits = Array.from(n.toString());
for (const [j, c] of digits.entries()) {
resultStr += (c!=1 || j==digits.length-1) ? c : ''+c+'';
}
counterEl.innerHTML = resultStr;
for (var el of counterEl.getElementsByTagName('span')) { // inline styling of doesn't take effect FSR
el.style.position = 'relative';
el.style.left = '1px';
}
}
// zeroing the counter
// this userscript relies on the timeline header about the number of posts, which is never exact
function end(n) {
document.removeEventListener('scroll', handleScroll);
var t = setInterval(function() {
n--;
putContent(n);
if (n==0) clearTimeout(t);
}, 60);
}
})();