From 4385cc1c28741d8e6c6fb046a2184401f4845547 Mon Sep 17 00:00:00 2001 From: cottongin Date: Wed, 26 Aug 2026 16:04:48 -0400 Subject: [PATCH] feat: inline color popover with mode toggle, live preview, and save/reset Co-authored-by: Cursor --- content/tweaks/color-popover.js | 300 ++++++++++++++++++++++++++++++++ manifest.json | 1 + 2 files changed, 301 insertions(+) create mode 100644 content/tweaks/color-popover.js diff --git a/content/tweaks/color-popover.js b/content/tweaks/color-popover.js new file mode 100644 index 0000000..734e398 --- /dev/null +++ b/content/tweaks/color-popover.js @@ -0,0 +1,300 @@ +(function () { + "use strict"; + + var popoverEl = null; + var currentSubreddit = null; + var currentAnchor = null; + var editState = { light: null, dark: null }; + var selectedMode = "light"; + + var els = {}; + + function getHelpers() { + return { + isDarkMode: window.RedditTweaks.badgeTweak.isDarkMode, + getColorPrefix: window.RedditTweaks.badgeTweak.getColorPrefix, + }; + } + + function hexValid(val) { + return /^#[0-9a-f]{6}$/i.test(val); + } + + function colorToHex(val) { + if (!val) return "#ffffff"; + return val; + } + + function createPopover() { + var el = document.createElement("div"); + el.className = "rt-color-popover"; + + el.innerHTML = [ + '
', + ' ', + ' ', + "
", + '
', + ' ", + "
", + '
', + '
', + ' Background', + '
', + ' ', + ' ', + "
", + "
", + '
', + ' Border', + '
', + ' ', + ' ', + "
", + "
", + '
', + ' Text', + '
', + ' ', + ' ', + "
", + "
", + "
", + '
', + ' ', + "
", + '", + ].join("\n"); + + document.body.appendChild(el); + popoverEl = el; + + els.title = el.querySelector(".rt-popover-title"); + els.closeBtn = el.querySelector(".rt-popover-close"); + els.modeSelect = el.querySelector(".rt-popover-mode-select"); + els.preview = el.querySelector(".rt-popover-preview-badge"); + els.saveBtn = el.querySelector(".rt-btn-save"); + els.cancelBtn = el.querySelector(".rt-btn-cancel"); + els.resetBtn = el.querySelector(".rt-btn-reset"); + + els.rows = {}; + var rows = el.querySelectorAll(".rt-popover-row"); + for (var i = 0; i < rows.length; i++) { + var row = rows[i]; + var prop = row.getAttribute("data-prop"); + els.rows[prop] = { + swatch: row.querySelector(".rt-color-swatch"), + hex: row.querySelector(".rt-color-hex"), + }; + } + + els.closeBtn.addEventListener("click", close); + els.cancelBtn.addEventListener("click", close); + els.saveBtn.addEventListener("click", save); + els.resetBtn.addEventListener("click", resetToDefault); + + els.modeSelect.addEventListener("change", function () { + storeCurrentEdits(); + selectedMode = els.modeSelect.value; + loadModeIntoInputs(); + updatePreview(); + }); + + var props = ["bg", "border", "text"]; + for (var p = 0; p < props.length; p++) { + (function (prop) { + var row = els.rows[prop]; + row.swatch.addEventListener("input", function () { + row.hex.value = row.swatch.value; + row.hex.classList.remove("rt-input-error"); + updatePreview(); + }); + row.hex.addEventListener("input", function () { + if (hexValid(row.hex.value)) { + row.swatch.value = row.hex.value; + row.hex.classList.remove("rt-input-error"); + updatePreview(); + } else { + row.hex.classList.add("rt-input-error"); + } + }); + })(props[p]); + } + + document.addEventListener("mousedown", onDocumentMouseDown, true); + document.addEventListener("keydown", onDocumentKeyDown, true); + } + + function onDocumentMouseDown(e) { + if (!popoverEl || !popoverEl.classList.contains("rt-popover-visible")) return; + if (popoverEl.contains(e.target)) return; + + var active = document.activeElement; + if (active && active.type === "color" && popoverEl.contains(active)) return; + + close(); + } + + function onDocumentKeyDown(e) { + if (e.key === "Escape" && popoverEl && popoverEl.classList.contains("rt-popover-visible")) { + close(); + } + } + + function getDefaultsForMode(settings, mode) { + return { + bg: settings[mode + ".subredditBgColor"], + border: settings[mode + ".subredditBorderColor"], + text: settings[mode + ".subredditTextColor"], + }; + } + + function getColorsForMode(settings, subredditName, mode) { + var entry = settings.subredditColors[subredditName]; + if (entry && entry[mode]) { + return { bg: entry[mode].bg, border: entry[mode].border, text: entry[mode].text }; + } + return getDefaultsForMode(settings, mode); + } + + function storeCurrentEdits() { + editState[selectedMode] = { + bg: els.rows.bg.swatch.value, + border: els.rows.border.swatch.value, + text: els.rows.text.swatch.value, + }; + } + + function loadModeIntoInputs() { + var colors = editState[selectedMode]; + var props = ["bg", "border", "text"]; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + var val = colorToHex(colors[prop]); + els.rows[prop].swatch.value = val; + els.rows[prop].hex.value = val; + els.rows[prop].hex.classList.remove("rt-input-error"); + } + } + + function updatePreview() { + var bg = els.rows.bg.swatch.value; + var border = els.rows.border.swatch.value; + var text = els.rows.text.swatch.value; + els.preview.style.background = bg; + els.preview.style.borderColor = border; + els.preview.style.color = text; + } + + function positionPopover(anchorEl) { + var rect = anchorEl.getBoundingClientRect(); + var gap = 6; + + popoverEl.style.visibility = "hidden"; + popoverEl.classList.add("rt-popover-visible"); + + var popRect = popoverEl.getBoundingClientRect(); + var top = rect.bottom + window.scrollY + gap; + var left = rect.left + window.scrollX; + + if (rect.bottom + gap + popRect.height > window.innerHeight) { + top = rect.top + window.scrollY - popRect.height - gap; + } + + var maxLeft = document.documentElement.scrollWidth - popRect.width - 8; + if (left > maxLeft) left = maxLeft; + if (left < 8) left = 8; + + popoverEl.style.top = top + "px"; + popoverEl.style.left = left + "px"; + popoverEl.style.visibility = ""; + } + + function applyDarkModeToPopover(settings) { + var helpers = getHelpers(); + if (helpers.isDarkMode(settings)) { + popoverEl.classList.add("rt-dark"); + } else { + popoverEl.classList.remove("rt-dark"); + } + } + + function open(badgeEl, subredditName, settings) { + if (!popoverEl) createPopover(); + + if (currentSubreddit === subredditName && popoverEl.classList.contains("rt-popover-visible")) { + close(); + return; + } + + currentSubreddit = subredditName; + currentAnchor = badgeEl; + + var helpers = getHelpers(); + selectedMode = helpers.getColorPrefix(settings); + + editState.light = getColorsForMode(settings, subredditName, "light"); + editState.dark = getColorsForMode(settings, subredditName, "dark"); + + els.title.textContent = "r/" + subredditName; + els.modeSelect.value = selectedMode; + els.preview.textContent = "r/" + subredditName; + + applyDarkModeToPopover(settings); + loadModeIntoInputs(); + updatePreview(); + positionPopover(badgeEl); + } + + function close() { + if (!popoverEl) return; + popoverEl.classList.remove("rt-popover-visible"); + currentSubreddit = null; + currentAnchor = null; + editState = { light: null, dark: null }; + } + + async function save() { + if (!currentSubreddit) return; + + storeCurrentEdits(); + + var settings = await window.RedditTweaks.loadSettings(); + var entry = settings.subredditColors[currentSubreddit] || {}; + + if (editState.light) entry.light = editState.light; + if (editState.dark) entry.dark = editState.dark; + + settings.subredditColors[currentSubreddit] = entry; + await window.RedditTweaks.saveSettings(settings); + close(); + } + + async function resetToDefault() { + if (!currentSubreddit) return; + + var settings = await window.RedditTweaks.loadSettings(); + delete settings.subredditColors[currentSubreddit]; + await window.RedditTweaks.saveSettings(settings); + + editState.light = getDefaultsForMode(settings, "light"); + editState.dark = getDefaultsForMode(settings, "dark"); + loadModeIntoInputs(); + updatePreview(); + } + + window.RedditTweaks = window.RedditTweaks || {}; + window.RedditTweaks.colorPopover = { + open: open, + close: close, + }; +})(); diff --git a/manifest.json b/manifest.json index 249391d..d6a072c 100644 --- a/manifest.json +++ b/manifest.json @@ -20,6 +20,7 @@ "js": [ "lib/settings.js", "content/tweaks/badges.js", + "content/tweaks/color-popover.js", "content/main.js" ], "run_at": "document_idle"