diff --git a/content.js b/content.js index 5defce0..d473bf5 100644 --- a/content.js +++ b/content.js @@ -1,11 +1,15 @@ +// Current settings snapshot. let whitelist = []; let whitelistHandles = []; let debugEnabled = false; -const memberOnlyIndex = new Map(); -const DATA_PREFIX = "chipperfluff-nobs"; +// In-memory index of known member-only videos. +const memberOnlyIndex = new Map(); // id -> { channelKey, hidden } +// Data-* attribute prefix for DOM tags. +const DATA_PREFIX = "chipperfluff-nobs"; // nobs = "no-bs" (member-only videos) /* ---------------- CSS injection ---------------- */ +// Inject a single style tag for hiding matched videos. function injectStyleOnce() { if (document.getElementById("member-filter-style")) return; @@ -23,6 +27,7 @@ injectStyleOnce(); /* ---------------- load whitelist ---------------- */ +// Debug helpers (no output unless debug is enabled). function debugLog(...args) { if (!debugEnabled) return; console.log("[MemberFilter]", ...args); @@ -38,6 +43,7 @@ function debugGroupEnd() { console.groupEnd(); } +// Normalize strings so matching is consistent. function normalizeKey(value) { return value .toLowerCase() @@ -47,6 +53,7 @@ function normalizeKey(value) { .trim(); } +// Support legacy strings and new { name, handle } entries. function normalizeWhitelist(list) { const names = []; const handles = []; @@ -63,6 +70,7 @@ function normalizeWhitelist(list) { return { names, handles }; } +// Read settings and re-apply visibility immediately. function loadSettings() { chrome.storage.local.get({ whitelist: [], debug: false }, data => { const { names, handles } = normalizeWhitelist(data.whitelist); @@ -81,11 +89,12 @@ function loadSettings() { loadSettings(); -// Reload settings if popup changes them +// Reload settings if popup changes them. chrome.storage.onChanged.addListener(loadSettings); /* ---------------- detection logic ---------------- */ +// Detect member-only videos and tag them for future updates. function process(root = document) { if (isWhitelistedChannelPage()) return; const badges = root.querySelectorAll("badge-shape"); @@ -142,6 +151,7 @@ function process(root = document) { }); } +// Apply whitelist to already-tagged member-only videos. function updateKnownVisibility() { if (isWhitelistedChannelPage()) { revealAll(); @@ -164,17 +174,20 @@ function updateKnownVisibility() { } } +// Allow full visibility on whitelisted channel pages. function isWhitelistedChannelPage() { const handle = getChannelHandleFromUrl(location.href); if (!handle) return false; return whitelistHandles.includes(normalizeKey(handle)); } +// Extract @handle from a channel URL. function getChannelHandleFromUrl(url) { const match = url.match(/youtube\.com\/@([^/]+)/i); return match ? match[1] : ""; } +// Remove hidden flags from any tagged videos. function revealAll() { const videos = document.querySelectorAll( `[data-${DATA_PREFIX}-hidden="true"]` @@ -182,6 +195,7 @@ function revealAll() { videos.forEach(video => video.removeAttribute(`data-${DATA_PREFIX}-hidden`)); } +// Choose a stable identifier for indexing. function getVideoId(video, url) { const dataId = video.getAttribute("data-video-id"); if (dataId) return dataId; @@ -190,6 +204,7 @@ function getVideoId(video, url) { return fallbackId || ""; } +// Read the channel key from DOM or fallback to text. function getChannelKey(video) { const stored = video.getAttribute(`data-${DATA_PREFIX}-channel`); if (stored) return stored; @@ -201,11 +216,13 @@ function getChannelKey(video) { return channel ? normalizeKey(channel) : ""; } +// Find a video element by stored id. function findVideoById(id) { const escaped = cssEscape(id); return document.querySelector(`[data-${DATA_PREFIX}-id="${escaped}"]`); } +// Safe attribute selector escaping. function cssEscape(value) { if (window.CSS && CSS.escape) return CSS.escape(value); return String(value).replace(/["\\]/g, "\\$&"); @@ -215,6 +232,7 @@ function cssEscape(value) { process(); +// Watch for new items so member-only tags are applied as they load. const observer = new MutationObserver(mutations => { for (const m of mutations) { for (const node of m.addedNodes) { diff --git a/popup.css b/popup.css index e83fd09..7c5c068 100644 --- a/popup.css +++ b/popup.css @@ -1,3 +1,4 @@ +/* Base layout */ body { font-family: "Trebuchet MS", "Verdana", sans-serif; min-width: 260px; @@ -7,6 +8,7 @@ body { color: #2b2014; } +/* Title */ h3 { margin: 0 0 10px; letter-spacing: 0.4px; @@ -25,6 +27,7 @@ h3 { margin: 6px 0 8px; } +/* Form rows */ .row { display: flex; gap: 6px; @@ -36,6 +39,7 @@ h3 { justify-content: space-between; } +/* Inputs */ input { flex: 1; padding: 6px 8px; @@ -45,6 +49,7 @@ input { font-size: 12px; } +/* Buttons */ button { padding: 6px 10px; border: 1px solid #b2985b; @@ -64,12 +69,14 @@ button:hover { filter: brightness(0.95); } +/* List */ ul { padding-left: 0; margin: 0; list-style: none; } +/* List rows */ li { display: flex; justify-content: space-between; @@ -88,6 +95,7 @@ li button { background: #f2a285; } +/* Debug toggle row */ .toggle { display: flex; gap: 6px; diff --git a/popup.html b/popup.html index 133df84..9acc5ea 100644 --- a/popup.html +++ b/popup.html @@ -6,12 +6,14 @@
+Hide member-only videos by default. Whitelisted creators stay visible. Add the channel name and its @handle or link.
+