🏠 Home 

Meguca Unread

On Meguca/shamichan imageboards marks last read post and scrolls to it

// ==UserScript==
// @name         Meguca Unread
// @namespace    meguca.shamichan.ext
// @version      1.0.5
// @description  On Meguca/shamichan imageboards marks last read post and scrolls to it
// @author       SaddestPanda
// @license      GNU GPLv3
// @match        https://2chen.moe/*
// @match        https://sturdychan.help/*
// @match        https://shamik.ooo/*
// @match        https://shamiko.org/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==
(function () {
'use strict';
let threadPosts = document.querySelectorAll("#threads #thread-container article");
if (threadPosts?.length < 10) {
//disable if there are less than 10 posts
return;
}
addMyStyle("meguca-extended-css", `
.lastRead {
border-top: 8px solid #1cb9d2;
}
`);
let db;
let retries = 0;
dbStart();
function dbStart() {
let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
let DBOpenRequest = indexedDB.open('meguca');
DBOpenRequest.onsuccess = (event) => {
db = event.target.r###lt;
dbContinue();
}
DBOpenRequest.onerror = (event) => {
//retry db access
if (retries < 5) {
retries++;
setTimeout(() => {
dbStart();
}, 150);
}
}
}
async function dbContinue() {
let transaction = db.transaction("seenPost", "readonly");
let objectStore = transaction.objectStore("seenPost");
let getAll = objectStore.getAll();
getAll.onsuccess = (event) => {
//Close db early
db.close();
let allData = event.target.r###lt;
let threadID = document.querySelector("#thread-container").dataset.id;
//Find "first unread post"
let postIDs = new Map();
threadPosts.forEach(element => {
let id = parseInt(element.id.split("p")[1]);
postIDs.set(id, true);
});
allData.forEach(obj => {
if (obj.op == threadID) {
postIDs.delete(obj.id)
}
});
if (postIDs.size == 0) {
//No unread posts. Scroll to bottom.
document.querySelector("html").scrollIntoView(false);
} else {
//Scroll to first unread post
const iterator = postIDs.keys();
let firstUnreadID = iterator.next().value;
let firstUnreadElem = document.querySelector(`article[id="p${firstUnreadID}"]`);
if (firstUnreadElem) {
//Mark as read (add styling)
firstUnreadElem.classList.add("lastRead");
//Do scroll (top of next elem)
let firstUnreadPos = findPos(firstUnreadElem?.nextElementSibling || firstUnreadElem);
window.scroll(0, firstUnreadPos.top - window.innerHeight);
}
}
/*
//Find "last read post"
//This method doesn't work as hovered backlinks are set to read as well
let filteredData = allData.filter(obj => obj.op == threadID);
let lastObj = filteredData[filteredData.length - 1];
let lastReadElem = document.querySelector(`article[id="p${lastObj.id}"]`);
//Mark as read (add styling)
lastReadElem.classList.add("lastRead");
//Scroll one screen height above last read (don't show last read)
let lastReadPos = findPos(lastReadElem);
window.scroll(0, lastReadPos.top - window.innerHeight + 150); //+N is to show last read post and part of the next post
*/
};
getAll.onerror = event => {
console.error("🚀 ~ dbContinue ~ event ~ onerror:", event);
db.close();
};
}
function addMyStyle(newID, newStyle) {
let myStyle = document.createElement('style');
//myStyle.type = 'text/css';
myStyle.id = newID;
myStyle.textContent = newStyle;
document.querySelector("head").appendChild(myStyle);
}
function findPos(obj) {
const rect = obj.getBoundingClientRect();
return {
left: rect.left + window.scrollX,
top: rect.top + window.scrollY
}
}
})();