1
0

Enhance popup UI and functionality: update styles, improve input handling, and add max archive settings

This commit is contained in:
Chipperfluff 2026-01-20 06:11:32 +01:00
parent cb062589fa
commit 49bbce95ea
4 changed files with 264 additions and 80 deletions

View File

@ -13,8 +13,7 @@ let generatedIdCounter = 0;
let hudDirty = false; let hudDirty = false;
let lastHudSignature = ""; let lastHudSignature = "";
const STORAGE_KEY = "memberOnlyHidden"; const STORAGE_KEY = "memberOnlyHidden";
const MAX_GLOBAL_ITEMS = 200; const DEFAULT_MAX_ARCHIVE = 500;
const MAX_PER_CHANNEL = 20;
let persistTimer = null; let persistTimer = null;
/* ---------------- CSS injection ---------------- */ /* ---------------- CSS injection ---------------- */
@ -259,7 +258,7 @@ function process(root = document) {
const channelUrl = channelInfo.url || ""; const channelUrl = channelInfo.url || "";
const channelKey = channelInfo.key; const channelKey = channelInfo.key;
const id = getOrCreateVideoId(video, url); const id = getOrCreateVideoId(video, url);
const stableId = getStableId(video, url); const stableId = getStableId(video, url, id);
video.setAttribute(`data-${DATA_PREFIX}-member-only`, "true"); video.setAttribute(`data-${DATA_PREFIX}-member-only`, "true");
if (channelKey) video.setAttribute(`data-${DATA_PREFIX}-channel`, channelKey); if (channelKey) video.setAttribute(`data-${DATA_PREFIX}-channel`, channelKey);
@ -374,11 +373,11 @@ function getOrCreateVideoId(video, url) {
return generated; return generated;
} }
function getStableId(video, url) { function getStableId(video, url, fallbackId) {
const dataId = video.getAttribute("data-video-id"); const dataId = video.getAttribute("data-video-id");
if (dataId) return dataId; if (dataId) return dataId;
if (url) return url; if (url) return url;
return ""; return fallbackId || "";
} }
// Read the channel key from DOM or fallback to text. // Read the channel key from DOM or fallback to text.
@ -490,6 +489,9 @@ function updateHudDot() {
} }
function updateSharedIndex(meta, hidden) { function updateSharedIndex(meta, hidden) {
if (!meta.stableId) {
meta.stableId = meta.url || meta.id || "";
}
if (!meta.stableId) return; if (!meta.stableId) return;
if (hidden) { if (hidden) {
sharedIndex.set(meta.stableId, { sharedIndex.set(meta.stableId, {
@ -500,7 +502,8 @@ function updateSharedIndex(meta, hidden) {
title: meta.title, title: meta.title,
url: meta.url, url: meta.url,
thumb: meta.thumb, thumb: meta.thumb,
hidden: true hidden: true,
lastSeen: Date.now()
}); });
} else { } else {
sharedIndex.delete(meta.stableId); sharedIndex.delete(meta.stableId);
@ -518,20 +521,16 @@ function schedulePersistSharedIndex() {
function persistSharedIndex() { function persistSharedIndex() {
const items = Array.from(sharedIndex.values()); const items = Array.from(sharedIndex.values());
items.sort((a, b) => a.id.localeCompare(b.id)); items.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0));
chrome.storage.local.get({ maxArchive: DEFAULT_MAX_ARCHIVE }, data => {
const perChannelCounts = new Map(); const maxArchive = Number(data.maxArchive);
const pruned = []; const max = Number.isFinite(maxArchive) ? maxArchive : DEFAULT_MAX_ARCHIVE;
for (const item of items) { if (max < 0) {
const key = item.channelKey || item.channelLabel || "(unknown)"; chrome.storage.local.set({ [STORAGE_KEY]: items });
const count = perChannelCounts.get(key) || 0; return;
if (count >= MAX_PER_CHANNEL) continue; }
if (pruned.length >= MAX_GLOBAL_ITEMS) break; chrome.storage.local.set({ [STORAGE_KEY]: items.slice(0, max) });
perChannelCounts.set(key, count + 1); });
pruned.push(item);
}
chrome.storage.local.set({ [STORAGE_KEY]: pruned });
} }
function loadStoredIndex() { function loadStoredIndex() {

182
popup.css
View File

@ -1,19 +1,58 @@
/* Base layout */ /* Base layout */
body { body {
font-family: "Trebuchet MS", "Verdana", sans-serif; font-family: "Oswald", "Impact", "Franklin Gothic Medium", sans-serif;
min-width: 260px; min-width: 460px;
margin: 0; margin: 0;
padding: 12px; padding: 0;
background: linear-gradient(180deg, #f7f3e8, #efe6d2); background: radial-gradient(circle at 20% 10%, #2b2b2b, #151515 60%);
color: #2b2014; color: #f5efe6;
} }
/* Title */ .scroll {
h3 { max-height: 640px;
margin: 0 0 10px; overflow-y: auto;
letter-spacing: 0.4px; padding-bottom: 14px;
}
:root {
--orange: #e35b14;
--orange-strong: #ff7a1a;
--dark: #171717;
--dark-2: #1f1f1f;
--cream: #f5efe6;
--muted: #c7b8a4;
}
.topbar {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
background: linear-gradient(90deg, var(--orange), #c9490e);
border-bottom: 2px solid #b2410e;
}
.logo {
width: 24px;
height: 24px;
border-radius: 6px;
background: radial-gradient(circle, #fff1d6, #ffb256);
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.25);
}
.title-wrap h3 {
margin: 0;
letter-spacing: 1px;
text-transform: uppercase; text-transform: uppercase;
font-size: 13px; font-size: 16px;
color: #fff8ee;
}
.subtitle {
font-size: 10px;
letter-spacing: 0.8px;
text-transform: uppercase;
color: #2a1506;
} }
.subtext { .subtext {
@ -32,47 +71,110 @@ h3 {
display: flex; display: flex;
gap: 6px; gap: 6px;
align-items: center; align-items: center;
margin-bottom: 8px; margin: 8px 12px 0;
}
details.section {
margin: 10px 10px 0;
padding: 6px 6px 10px;
border-radius: 10px;
background: rgba(15, 15, 15, 0.55);
border: 1px solid #2a2a2a;
}
details.section > summary {
list-style: none;
cursor: pointer;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
font-size: 11px;
color: #ffd8b0;
padding: 6px 6px 2px;
}
details.section > summary::-webkit-details-marker {
display: none;
}
details.section > summary::before {
content: "▸";
margin-right: 6px;
color: #ffb26a;
}
details.section[open] > summary::before {
content: "▾";
}
/* Description */
.subtext {
margin: 10px 12px 0;
font-size: 11px;
line-height: 1.35;
color: var(--muted);
font-family: "Verdana", sans-serif;
}
.subtext.subtext-debug {
margin-top: 6px;
} }
.row.settings { .row.settings {
justify-content: space-between; justify-content: space-between;
margin-top: 10px;
}
.hint {
margin: 6px 12px 0;
font-size: 11px;
color: #ffb26a;
font-family: "Verdana", sans-serif;
min-height: 14px;
} }
/* Inputs */ /* Inputs */
input { input {
flex: 1; flex: 1;
padding: 6px 8px; padding: 8px 10px;
border: 1px solid #cdbf9e; border: 1px solid #393939;
border-radius: 6px; border-radius: 8px;
background: #fffaf0; background: var(--dark-2);
font-size: 12px; font-size: 12px;
color: var(--cream);
outline: none;
} }
/* Buttons */ /* Buttons */
button { button {
padding: 6px 10px; padding: 8px 12px;
border: 1px solid #b2985b; border: 1px solid #9b3b0c;
border-radius: 6px; border-radius: 8px;
background: #d9c18c; background: var(--orange);
color: #2b2014; color: #1f0f06;
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
transition: transform 0.08s ease, filter 0.15s ease;
} }
button.ghost { button.ghost {
background: transparent; background: transparent;
border-color: #cdbf9e; border-color: #4a3a2a;
color: var(--cream);
} }
button:hover { button:hover {
filter: brightness(0.95); filter: brightness(1.05);
}
button:active {
transform: translateY(1px);
} }
/* List */ /* List */
ul { ul {
padding-left: 0; padding-left: 0;
margin: 0; margin: 10px 12px 12px;
list-style: none; list-style: none;
} }
@ -81,18 +183,20 @@ li {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin: 4px 0; margin: 4px 0;
padding: 6px 8px; padding: 8px 10px;
background: #fffaf0; background: #232323;
border: 1px solid #e2d7be; border: 1px solid #3a2c22;
border-radius: 6px; border-radius: 8px;
font-size: 12px; font-size: 12px;
color: var(--cream);
} }
li button { li button {
padding: 2px 8px; padding: 2px 8px;
border-radius: 999px; border-radius: 999px;
border-color: #c95a3a; border-color: #9b3b0c;
background: #f2a285; background: var(--orange-strong);
color: #1f0f06;
} }
/* Debug toggle row */ /* Debug toggle row */
@ -101,4 +205,24 @@ li button {
gap: 6px; gap: 6px;
align-items: center; align-items: center;
font-size: 12px; font-size: 12px;
color: var(--cream);
}
.section.explain {
margin: 10px 12px 12px;
padding: 10px 12px;
border-radius: 10px;
background: #1c1c1c;
border: 1px solid #2b2b2b;
font-family: "Verdana", sans-serif;
font-size: 11px;
color: var(--muted);
}
.explain-title {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
color: #ffb26a;
margin-bottom: 6px;
} }

View File

@ -6,36 +6,78 @@
<link rel="stylesheet" href="popup.css" /> <link rel="stylesheet" href="popup.css" />
</head> </head>
<body> <body>
<!-- Title + short description --> <div class="scroll">
<h3>Channel Whitelist</h3> <!-- Title + short description -->
<p class="subtext"> <div class="topbar">
Hide member-only videos by default. Whitelisted creators stay visible. <div class="logo"></div>
Add the channel name and its @handle or link. <div class="title-wrap">
</p> <h3>ANTI-BS</h3>
<div class="subtitle">Channel whitelist</div>
</div>
</div>
<p class="subtext">
Hide member-only videos by default. Whitelisted creators stay visible.
Add the channel name and its @handle or link.
</p>
<!-- Inputs --> <!-- Inputs -->
<div class="row"> <details class="section" open>
<input id="channelInput" placeholder="Channel name" /> <summary>Whitelist a creator</summary>
</div> <div class="row">
<div class="row"> <input id="channelInput" placeholder="Channel name" />
<input id="channelLinkInput" placeholder="Channel link or @handle" /> </div>
<button id="addBtn">Add</button> <div class="row">
</div> <input id="channelLinkInput" placeholder="Channel link or @handle" />
<button id="addBtn">Add</button>
</div>
<div id="addError" class="hint"></div>
</details>
<!-- Settings --> <details class="section">
<div class="row settings"> <summary>Archive settings</summary>
<label class="toggle"> <div class="row">
<input id="debugToggle" type="checkbox" /> <input id="maxInput" type="number" placeholder="Max archived (-1 = unlimited)" />
<span>Debug logging</span> <button id="saveMaxBtn">Save max</button>
</label> </div>
<button id="resetBtn" class="ghost">Reset defaults</button> </details>
</div>
<p class="subtext subtext-debug">
Debug logs will appear in the console only when enabled.
</p>
<!-- Whitelist list --> <!-- Settings -->
<ul id="channelList"></ul> <details class="section">
<summary>App settings</summary>
<div class="row settings">
<label class="toggle">
<input id="debugToggle" type="checkbox" />
<span>Debug logging</span>
</label>
<button id="resetBtn" class="ghost">Reset defaults</button>
</div>
<p class="subtext subtext-debug">
Debug logs will appear in the console only when enabled.
</p>
</details>
<p class="subtext">
Bottom-right dot: grey means none detected. Orange pulsing means member-only
videos were found. Click it to see the list.
</p>
<!-- Whitelist list -->
<details class="section" open>
<summary>Whitelisted creators</summary>
<ul id="channelList"></ul>
</details>
<div class="section explain">
<div class="explain-title">For non-technical users</div>
<p>
This tool hides members-only videos you cant watch. Add creators you
support to the whitelist so their videos stay visible.
</p>
<p>
The orange dot shows when hidden members-only videos exist. Click it to
see a list of whats hidden.
</p>
</div>
</div>
<script src="popup.js"></script> <script src="popup.js"></script>
</body> </body>

View File

@ -2,6 +2,9 @@
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");
const maxInput = document.getElementById("maxInput");
const saveMaxBtn = document.getElementById("saveMaxBtn");
const addError = document.getElementById("addError");
const list = document.getElementById("channelList"); 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");
@ -9,14 +12,16 @@ const resetBtn = document.getElementById("resetBtn");
// Default settings payload. // Default settings payload.
const DEFAULTS = { const DEFAULTS = {
whitelist: [], whitelist: [],
debug: false debug: false,
maxArchive: 200
}; };
// Render whitelist and debug state. // Render whitelist and debug state.
function loadChannels() { function loadChannels() {
chrome.storage.local.get(DEFAULTS, ({ whitelist, debug }) => { chrome.storage.local.get(DEFAULTS, ({ whitelist, debug, maxArchive }) => {
list.innerHTML = ""; list.innerHTML = "";
debugToggle.checked = Boolean(debug); debugToggle.checked = Boolean(debug);
maxInput.value = String(maxArchive);
const entries = normalizeWhitelist(whitelist); const entries = normalizeWhitelist(whitelist);
entries.forEach((entry, index) => { entries.forEach((entry, index) => {
const li = document.createElement("li"); const li = document.createElement("li");
@ -39,9 +44,16 @@ function loadChannels() {
function addChannel() { function addChannel() {
const name = input.value.trim(); const name = input.value.trim();
const link = linkInput.value.trim(); const link = linkInput.value.trim();
if (!name || !link) return; if (!name || !link) {
addError.textContent = "Need both name + @handle/link.";
return;
}
const handle = normalizeHandle(link); const handle = normalizeHandle(link);
if (!handle) return; if (!handle) {
addError.textContent = "Invalid link or @handle.";
return;
}
addError.textContent = "";
chrome.storage.local.get(DEFAULTS, ({ whitelist }) => { chrome.storage.local.get(DEFAULTS, ({ whitelist }) => {
const entries = normalizeWhitelist(whitelist); const entries = normalizeWhitelist(whitelist);
@ -92,8 +104,15 @@ function normalizeHandle(value) {
return ""; return "";
} }
function saveMaxArchive() {
const value = parseInt(maxInput.value, 10);
if (Number.isNaN(value)) return;
chrome.storage.local.set({ maxArchive: value }, loadChannels);
}
// Wire UI events. // Wire UI events.
addBtn.onclick = addChannel; addBtn.onclick = addChannel;
saveMaxBtn.onclick = saveMaxArchive;
debugToggle.onchange = e => setDebug(e.target.checked); debugToggle.onchange = e => setDebug(e.target.checked);
resetBtn.onclick = resetDefaults; resetBtn.onclick = resetDefaults;
loadChannels(); loadChannels();