1
0

Refactor code structure and enhance comments: improve readability and maintainability across content.js, popup.js, and associated styles

This commit is contained in:
Chipperfluff 2026-01-19 23:16:43 +01:00
parent db63c489b8
commit 705e7cad7e
4 changed files with 43 additions and 3 deletions

View File

@ -1,11 +1,15 @@
// Current settings snapshot.
let whitelist = []; let whitelist = [];
let whitelistHandles = []; let whitelistHandles = [];
let debugEnabled = false; let debugEnabled = false;
const memberOnlyIndex = new Map(); // In-memory index of known member-only videos.
const DATA_PREFIX = "chipperfluff-nobs"; 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 ---------------- */ /* ---------------- CSS injection ---------------- */
// Inject a single style tag for hiding matched videos.
function injectStyleOnce() { function injectStyleOnce() {
if (document.getElementById("member-filter-style")) return; if (document.getElementById("member-filter-style")) return;
@ -23,6 +27,7 @@ injectStyleOnce();
/* ---------------- load whitelist ---------------- */ /* ---------------- load whitelist ---------------- */
// Debug helpers (no output unless debug is enabled).
function debugLog(...args) { function debugLog(...args) {
if (!debugEnabled) return; if (!debugEnabled) return;
console.log("[MemberFilter]", ...args); console.log("[MemberFilter]", ...args);
@ -38,6 +43,7 @@ function debugGroupEnd() {
console.groupEnd(); console.groupEnd();
} }
// Normalize strings so matching is consistent.
function normalizeKey(value) { function normalizeKey(value) {
return value return value
.toLowerCase() .toLowerCase()
@ -47,6 +53,7 @@ function normalizeKey(value) {
.trim(); .trim();
} }
// Support legacy strings and new { name, handle } entries.
function normalizeWhitelist(list) { function normalizeWhitelist(list) {
const names = []; const names = [];
const handles = []; const handles = [];
@ -63,6 +70,7 @@ function normalizeWhitelist(list) {
return { names, handles }; return { names, handles };
} }
// Read settings and re-apply visibility immediately.
function loadSettings() { function loadSettings() {
chrome.storage.local.get({ whitelist: [], debug: false }, data => { chrome.storage.local.get({ whitelist: [], debug: false }, data => {
const { names, handles } = normalizeWhitelist(data.whitelist); const { names, handles } = normalizeWhitelist(data.whitelist);
@ -81,11 +89,12 @@ function loadSettings() {
loadSettings(); loadSettings();
// Reload settings if popup changes them // Reload settings if popup changes them.
chrome.storage.onChanged.addListener(loadSettings); chrome.storage.onChanged.addListener(loadSettings);
/* ---------------- detection logic ---------------- */ /* ---------------- detection logic ---------------- */
// Detect member-only videos and tag them for future updates.
function process(root = document) { function process(root = document) {
if (isWhitelistedChannelPage()) return; if (isWhitelistedChannelPage()) return;
const badges = root.querySelectorAll("badge-shape"); const badges = root.querySelectorAll("badge-shape");
@ -142,6 +151,7 @@ function process(root = document) {
}); });
} }
// Apply whitelist to already-tagged member-only videos.
function updateKnownVisibility() { function updateKnownVisibility() {
if (isWhitelistedChannelPage()) { if (isWhitelistedChannelPage()) {
revealAll(); revealAll();
@ -164,17 +174,20 @@ function updateKnownVisibility() {
} }
} }
// Allow full visibility on whitelisted channel pages.
function isWhitelistedChannelPage() { function isWhitelistedChannelPage() {
const handle = getChannelHandleFromUrl(location.href); const handle = getChannelHandleFromUrl(location.href);
if (!handle) return false; if (!handle) return false;
return whitelistHandles.includes(normalizeKey(handle)); return whitelistHandles.includes(normalizeKey(handle));
} }
// Extract @handle from a channel URL.
function getChannelHandleFromUrl(url) { function getChannelHandleFromUrl(url) {
const match = url.match(/youtube\.com\/@([^/]+)/i); const match = url.match(/youtube\.com\/@([^/]+)/i);
return match ? match[1] : ""; return match ? match[1] : "";
} }
// Remove hidden flags from any tagged videos.
function revealAll() { function revealAll() {
const videos = document.querySelectorAll( const videos = document.querySelectorAll(
`[data-${DATA_PREFIX}-hidden="true"]` `[data-${DATA_PREFIX}-hidden="true"]`
@ -182,6 +195,7 @@ function revealAll() {
videos.forEach(video => video.removeAttribute(`data-${DATA_PREFIX}-hidden`)); videos.forEach(video => video.removeAttribute(`data-${DATA_PREFIX}-hidden`));
} }
// Choose a stable identifier for indexing.
function getVideoId(video, url) { function getVideoId(video, url) {
const dataId = video.getAttribute("data-video-id"); const dataId = video.getAttribute("data-video-id");
if (dataId) return dataId; if (dataId) return dataId;
@ -190,6 +204,7 @@ function getVideoId(video, url) {
return fallbackId || ""; return fallbackId || "";
} }
// Read the channel key from DOM or fallback to text.
function getChannelKey(video) { function getChannelKey(video) {
const stored = video.getAttribute(`data-${DATA_PREFIX}-channel`); const stored = video.getAttribute(`data-${DATA_PREFIX}-channel`);
if (stored) return stored; if (stored) return stored;
@ -201,11 +216,13 @@ function getChannelKey(video) {
return channel ? normalizeKey(channel) : ""; return channel ? normalizeKey(channel) : "";
} }
// Find a video element by stored id.
function findVideoById(id) { function findVideoById(id) {
const escaped = cssEscape(id); const escaped = cssEscape(id);
return document.querySelector(`[data-${DATA_PREFIX}-id="${escaped}"]`); return document.querySelector(`[data-${DATA_PREFIX}-id="${escaped}"]`);
} }
// Safe attribute selector escaping.
function cssEscape(value) { function cssEscape(value) {
if (window.CSS && CSS.escape) return CSS.escape(value); if (window.CSS && CSS.escape) return CSS.escape(value);
return String(value).replace(/["\\]/g, "\\$&"); return String(value).replace(/["\\]/g, "\\$&");
@ -215,6 +232,7 @@ function cssEscape(value) {
process(); process();
// Watch for new items so member-only tags are applied as they load.
const observer = new MutationObserver(mutations => { const observer = new MutationObserver(mutations => {
for (const m of mutations) { for (const m of mutations) {
for (const node of m.addedNodes) { for (const node of m.addedNodes) {

View File

@ -1,3 +1,4 @@
/* Base layout */
body { body {
font-family: "Trebuchet MS", "Verdana", sans-serif; font-family: "Trebuchet MS", "Verdana", sans-serif;
min-width: 260px; min-width: 260px;
@ -7,6 +8,7 @@ body {
color: #2b2014; color: #2b2014;
} }
/* Title */
h3 { h3 {
margin: 0 0 10px; margin: 0 0 10px;
letter-spacing: 0.4px; letter-spacing: 0.4px;
@ -25,6 +27,7 @@ h3 {
margin: 6px 0 8px; margin: 6px 0 8px;
} }
/* Form rows */
.row { .row {
display: flex; display: flex;
gap: 6px; gap: 6px;
@ -36,6 +39,7 @@ h3 {
justify-content: space-between; justify-content: space-between;
} }
/* Inputs */
input { input {
flex: 1; flex: 1;
padding: 6px 8px; padding: 6px 8px;
@ -45,6 +49,7 @@ input {
font-size: 12px; font-size: 12px;
} }
/* Buttons */
button { button {
padding: 6px 10px; padding: 6px 10px;
border: 1px solid #b2985b; border: 1px solid #b2985b;
@ -64,12 +69,14 @@ button:hover {
filter: brightness(0.95); filter: brightness(0.95);
} }
/* List */
ul { ul {
padding-left: 0; padding-left: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
} }
/* List rows */
li { li {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -88,6 +95,7 @@ li button {
background: #f2a285; background: #f2a285;
} }
/* Debug toggle row */
.toggle { .toggle {
display: flex; display: flex;
gap: 6px; gap: 6px;

View File

@ -6,12 +6,14 @@
<link rel="stylesheet" href="popup.css" /> <link rel="stylesheet" href="popup.css" />
</head> </head>
<body> <body>
<!-- Title + short description -->
<h3>Channel Whitelist</h3> <h3>Channel Whitelist</h3>
<p class="subtext"> <p class="subtext">
Hide member-only videos by default. Whitelisted creators stay visible. Hide member-only videos by default. Whitelisted creators stay visible.
Add the channel name and its @handle or link. Add the channel name and its @handle or link.
</p> </p>
<!-- Inputs -->
<div class="row"> <div class="row">
<input id="channelInput" placeholder="Channel name" /> <input id="channelInput" placeholder="Channel name" />
</div> </div>
@ -20,6 +22,7 @@
<button id="addBtn">Add</button> <button id="addBtn">Add</button>
</div> </div>
<!-- Settings -->
<div class="row settings"> <div class="row settings">
<label class="toggle"> <label class="toggle">
<input id="debugToggle" type="checkbox" /> <input id="debugToggle" type="checkbox" />
@ -31,6 +34,7 @@
Debug logs will appear in the console only when enabled. Debug logs will appear in the console only when enabled.
</p> </p>
<!-- Whitelist list -->
<ul id="channelList"></ul> <ul id="channelList"></ul>
<script src="popup.js"></script> <script src="popup.js"></script>

View File

@ -1,3 +1,4 @@
// UI elements.
const input = document.getElementById("channelInput"); const input = document.getElementById("channelInput");
const linkInput = document.getElementById("channelLinkInput"); const linkInput = document.getElementById("channelLinkInput");
const addBtn = document.getElementById("addBtn"); const addBtn = document.getElementById("addBtn");
@ -5,11 +6,13 @@ const list = document.getElementById("channelList");
const debugToggle = document.getElementById("debugToggle"); const debugToggle = document.getElementById("debugToggle");
const resetBtn = document.getElementById("resetBtn"); const resetBtn = document.getElementById("resetBtn");
// Default settings payload.
const DEFAULTS = { const DEFAULTS = {
whitelist: [], whitelist: [],
debug: false debug: false
}; };
// Render whitelist and debug state.
function loadChannels() { function loadChannels() {
chrome.storage.local.get(DEFAULTS, ({ whitelist, debug }) => { chrome.storage.local.get(DEFAULTS, ({ whitelist, debug }) => {
list.innerHTML = ""; list.innerHTML = "";
@ -32,6 +35,7 @@ function loadChannels() {
}); });
} }
// Add a new whitelist entry with name + handle.
function addChannel() { function addChannel() {
const name = input.value.trim(); const name = input.value.trim();
const link = linkInput.value.trim(); const link = linkInput.value.trim();
@ -49,6 +53,7 @@ function addChannel() {
linkInput.value = ""; linkInput.value = "";
} }
// Normalize storage entries to { name, handle } objects.
function normalizeWhitelist(whitelist) { function normalizeWhitelist(whitelist) {
return (whitelist || []).map(item => { return (whitelist || []).map(item => {
if (typeof item === "string") { if (typeof item === "string") {
@ -58,6 +63,7 @@ function normalizeWhitelist(whitelist) {
}); });
} }
// Remove a whitelist entry by index.
function removeChannel(index) { function removeChannel(index) {
chrome.storage.local.get(DEFAULTS, ({ whitelist }) => { chrome.storage.local.get(DEFAULTS, ({ whitelist }) => {
const entries = normalizeWhitelist(whitelist); const entries = normalizeWhitelist(whitelist);
@ -66,14 +72,17 @@ function removeChannel(index) {
}); });
} }
// Persist debug toggle.
function setDebug(enabled) { function setDebug(enabled) {
chrome.storage.local.set({ debug: Boolean(enabled) }); chrome.storage.local.set({ debug: Boolean(enabled) });
} }
// Reset all settings to defaults.
function resetDefaults() { function resetDefaults() {
chrome.storage.local.set(DEFAULTS, loadChannels); chrome.storage.local.set(DEFAULTS, loadChannels);
} }
// Accept @handle or full channel URL.
function normalizeHandle(value) { function normalizeHandle(value) {
const atMatch = value.match(/@([^/?#]+)/); const atMatch = value.match(/@([^/?#]+)/);
if (atMatch) return atMatch[1].trim(); if (atMatch) return atMatch[1].trim();
@ -83,6 +92,7 @@ function normalizeHandle(value) {
return ""; return "";
} }
// Wire UI events.
addBtn.onclick = addChannel; addBtn.onclick = addChannel;
debugToggle.onchange = e => setDebug(e.target.checked); debugToggle.onchange = e => setDebug(e.target.checked);
resetBtn.onclick = resetDefaults; resetBtn.onclick = resetDefaults;