Refactor code structure and enhance comments: improve readability and maintainability across content.js, popup.js, and associated styles
This commit is contained in:
parent
db63c489b8
commit
705e7cad7e
24
content.js
24
content.js
@ -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) {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
10
popup.js
10
popup.js
@ -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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user