// ==UserScript== // @name Highlight New Replies // @namespace http://veryroundbird.house // @version 2026-06-03 // @description highlights your threads where you're not the most recent reply. // @author Carly Smallbird // @match https://*.dreamwidth.org/* // @icon https://www.google.com/s2/favicons?sz=64&domain=dreamwidth.org // @grant none // ==/UserScript== (function() { 'use strict'; let readReplies = []; let linkList = []; const username = document.querySelector('#account-links-text .ljuser').getAttribute('lj:user'); const newReplyText = "✨ NEW REPLIES ✨"; const markDoneText = "Mark Thread Done"; const highlightColor = "yellow"; const jumpLinks = document.createElement('div'); const jumpLinksOl = document.createElement('ol'); jumpLinks.append(jumpLinksOl); jumpLinks.id = 'jump-links'; document.body.append(jumpLinks); const addReadReply = (id) => { if (readReplies.indexOf(parseInt(id)) === -1) { readReplies.push(parseInt(id)); localStorage.setItem("readComments", JSON.stringify(readReplies)); } }; const highlightCmt = (cmt, top) => { const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); const id = parseInt(top.querySelector('.comment').id.replace("comment-cmt", "")); const info = top.querySelector('.comment-info'); const markReadBtn = document.createElement('button'); let readListener = null; markReadBtn.setAttribute('type', 'button'); markReadBtn.classList.add("markRead"); markReadBtn.textContent = newReplyText; top.classList.add('highlight'); if (cmt.querySelector('.partial')) { top.querySelector('.comment .inner').append(markReadBtn); readListener = top.querySelector('a[onclick]').addEventListener('click', (_e) => { markReadBtn.remove(); setTimeout(() => { top.querySelector('.comment-info h4').append(markReadBtn); }, 1000); }); } else { top.querySelector('.comment-info h4').append(markReadBtn); } markReadBtn.addEventListener('click', (e) => { e.preventDefault(); addReadReply(id); top.classList.remove("highlight"); markReadBtn.remove(); jumpLinks.querySelector(`a[href="#cmt${id}"]`).remove(); if (readListener) { removeEventListener('click', readListener); } }); }; const main = () => { const newStyles = document.createElement('style'); newStyles.setAttribute('type', 'text/css'); newStyles.textContent = ` .highlight .partial > .comment, .highlight .visible .comment-info { background-color: ${highlightColor} !important; } button.markRead { padding: 2px; margin-bottom: 0; font-size: .8em; } .comment .inner > button.markRead { margin-left: 5px; } #jump-links { position: fixed; bottom: 10px; left: 10px; padding: 5px; border: 1px gray solid; background-color: #FFFFFF66; max-height: 150px; max-width: 200px; overflow: auto; opacity: .5; } #jump-links ol { font-size: .8em; margin-bottom: 0; } #jump-links:hover, #jump-links:focus-within { opacity: 1; } `; document.head.append(newStyles); readReplies = localStorage.getItem("readComments") ? JSON.parse(localStorage.getItem("readComments")) : []; let prevDepth = 1; let topOfThread = null; let prevComment = null; document.querySelectorAll('.comment-thread').forEach((cmt) => { const depth = parseInt(Array.from(cmt.classList).filter(cl => cl.match(/comment\-depth\-[0-9]+/))[0].replace("comment-depth-", "")); if (!topOfThread || depth === 1) { if (!cmt.querySelector('.comment-wrapper').classList.contains(`poster-${username}`)) { prevComment = null; return; } topOfThread = cmt; prevComment = cmt; return; } if (depth >= prevDepth) { prevComment = cmt; prevDepth = depth; return; } if (depth < prevDepth) { const id = parseInt(topOfThread.querySelector('.comment').id.replace("comment-cmt", "")); if (prevComment && !prevComment.querySelector('.comment-wrapper').classList.contains(`poster-${username}`) && readReplies.indexOf(id) === -1) { highlightCmt(cmt, topOfThread); linkList.push(id); } topOfThread = null; prevComment = null; prevDepth = 1; return; } prevComment = cmt; prevDepth = depth; }); linkList.forEach((id) => { const newLi = document.createElement('li'); const newA = document.createElement('a'); newA.href = `#cmt${id}`; newA.textContent = `#${id}`; newLi.append(newA); jumpLinksOl.append(newLi); }); }; main(); })();