From 6cedae7a2c02616668907cd839af065726a2adab Mon Sep 17 00:00:00 2001 From: cottongin Date: Wed, 26 Aug 2026 15:00:49 -0400 Subject: [PATCH] feat: badge DOM manipulation with MutationObservers and dark mode detection Co-authored-by: Cursor --- content/main.js | 23 ++++++ content/tweaks/badges.js | 157 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/content/main.js b/content/main.js index e69de29..6703f4e 100644 --- a/content/main.js +++ b/content/main.js @@ -0,0 +1,23 @@ +(function () { + "use strict"; + + async function init() { + const settings = await window.RedditTweaks.loadSettings(); + if (!settings.enabled) return; + + window.RedditTweaks.badgeTweak.apply(settings); + + browser.storage.onChanged.addListener(function (changes) { + if (changes.enabled) { + location.reload(); + return; + } + + window.RedditTweaks.loadSettings().then(function (updated) { + window.RedditTweaks.badgeTweak.updateColors(updated); + }); + }); + } + + init(); +})(); diff --git a/content/tweaks/badges.js b/content/tweaks/badges.js index e69de29..094c27d 100644 --- a/content/tweaks/badges.js +++ b/content/tweaks/badges.js @@ -0,0 +1,157 @@ +(function () { + "use strict"; + + let contentObserver = null; + let darkModeObserver = null; + let currentSettings = null; + + function isDarkMode(settings) { + if (settings.darkMode === "light") return false; + if (settings.darkMode === "dark") return true; + return document.body.classList.contains("res-nightmode"); + } + + function getColorPrefix(settings) { + return isDarkMode(settings) ? "dark" : "light"; + } + + function applyColorsToContainer(container, settings, subredditName) { + const prefix = getColorPrefix(settings); + + if (isDarkMode(settings)) { + container.classList.add("rt-dark"); + } else { + container.classList.remove("rt-dark"); + } + + const overrides = subredditName && settings.subredditColors[subredditName]; + + container.style.setProperty("--rt-sub-bg", overrides?.bg || settings[prefix + ".subredditBgColor"]); + container.style.setProperty("--rt-sub-border", overrides?.border || settings[prefix + ".subredditBorderColor"]); + container.style.setProperty("--rt-sub-text", overrides?.text || settings[prefix + ".subredditTextColor"]); + container.style.setProperty("--rt-com-bg", settings[prefix + ".commentsBgColor"]); + container.style.setProperty("--rt-com-border", settings[prefix + ".commentsBorderColor"]); + container.style.setProperty("--rt-com-text", settings[prefix + ".commentsTextColor"]); + } + + function normalizeCommentText(text) { + const trimmed = text.trim(); + if (trimmed === "comment") return "0 comments"; + return trimmed; + } + + function processPost(post, settings) { + if (post.hasAttribute("data-rt-processed")) return; + + const entry = post.querySelector(".entry"); + if (!entry) return; + + const container = document.createElement("div"); + container.className = "reddit-tweaks-badges"; + + let subredditName = null; + + const subLink = post.querySelector(".tagline .subreddit"); + if (subLink) { + const badge = document.createElement("a"); + badge.className = "rt-badge rt-badge--subreddit"; + badge.href = subLink.href; + badge.textContent = subLink.textContent.trim(); + subredditName = subLink.textContent.trim().replace(/^r\//, ""); + container.appendChild(badge); + } + + const commentsLink = post.querySelector(".flat-list .comments"); + if (commentsLink) { + const badge = document.createElement("a"); + badge.className = "rt-badge rt-badge--comments"; + badge.href = commentsLink.href; + badge.textContent = normalizeCommentText(commentsLink.textContent); + container.appendChild(badge); + } + + if (container.children.length === 0) return; + + applyColorsToContainer(container, settings, subredditName); + entry.insertBefore(container, entry.firstChild); + post.setAttribute("data-rt-processed", "true"); + } + + function processPosts(settings) { + const posts = document.querySelectorAll("div.thing.link:not([data-rt-processed])"); + posts.forEach(function (post) { + processPost(post, settings); + }); + } + + function getObserverTarget() { + return ( + document.getElementById("siteTable") || + document.querySelector('.content[role="main"]') || + document.body + ); + } + + function startContentObserver(settings) { + if (contentObserver) contentObserver.disconnect(); + + const target = getObserverTarget(); + contentObserver = new MutationObserver(function (mutations) { + let hasNewPosts = false; + for (const mutation of mutations) { + for (const node of mutation.addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + if (node.matches && node.matches("div.thing.link")) { + hasNewPosts = true; + } else if (node.querySelector && node.querySelector("div.thing.link")) { + hasNewPosts = true; + } + } + } + } + if (hasNewPosts) processPosts(currentSettings); + }); + + contentObserver.observe(target, { childList: true, subtree: true }); + } + + function startDarkModeObserver() { + if (darkModeObserver) darkModeObserver.disconnect(); + + darkModeObserver = new MutationObserver(function () { + if (currentSettings && currentSettings.darkMode === "auto") { + updateColors(currentSettings); + } + }); + + darkModeObserver.observe(document.body, { + attributes: true, + attributeFilter: ["class"], + }); + } + + function updateColors(settings) { + currentSettings = settings; + const containers = document.querySelectorAll(".reddit-tweaks-badges"); + containers.forEach(function (container) { + const subBadge = container.querySelector(".rt-badge--subreddit"); + const subredditName = subBadge + ? subBadge.textContent.trim().replace(/^r\//, "") + : null; + applyColorsToContainer(container, settings, subredditName); + }); + } + + function apply(settings) { + currentSettings = settings; + processPosts(settings); + startContentObserver(settings); + startDarkModeObserver(); + } + + window.RedditTweaks = window.RedditTweaks || {}; + window.RedditTweaks.badgeTweak = { + apply: apply, + updateColors: updateColors, + }; +})();