Enhance popup UI and functionality: update styles, improve input handling, and add max archive settings
This commit is contained in:
parent
cb062589fa
commit
49bbce95ea
39
content.js
39
content.js
@ -13,8 +13,7 @@ let generatedIdCounter = 0;
|
||||
let hudDirty = false;
|
||||
let lastHudSignature = "";
|
||||
const STORAGE_KEY = "memberOnlyHidden";
|
||||
const MAX_GLOBAL_ITEMS = 200;
|
||||
const MAX_PER_CHANNEL = 20;
|
||||
const DEFAULT_MAX_ARCHIVE = 500;
|
||||
let persistTimer = null;
|
||||
|
||||
/* ---------------- CSS injection ---------------- */
|
||||
@ -259,7 +258,7 @@ function process(root = document) {
|
||||
const channelUrl = channelInfo.url || "";
|
||||
const channelKey = channelInfo.key;
|
||||
const id = getOrCreateVideoId(video, url);
|
||||
const stableId = getStableId(video, url);
|
||||
const stableId = getStableId(video, url, id);
|
||||
|
||||
video.setAttribute(`data-${DATA_PREFIX}-member-only`, "true");
|
||||
if (channelKey) video.setAttribute(`data-${DATA_PREFIX}-channel`, channelKey);
|
||||
@ -374,11 +373,11 @@ function getOrCreateVideoId(video, url) {
|
||||
return generated;
|
||||
}
|
||||
|
||||
function getStableId(video, url) {
|
||||
function getStableId(video, url, fallbackId) {
|
||||
const dataId = video.getAttribute("data-video-id");
|
||||
if (dataId) return dataId;
|
||||
if (url) return url;
|
||||
return "";
|
||||
return fallbackId || "";
|
||||
}
|
||||
|
||||
// Read the channel key from DOM or fallback to text.
|
||||
@ -490,6 +489,9 @@ function updateHudDot() {
|
||||
}
|
||||
|
||||
function updateSharedIndex(meta, hidden) {
|
||||
if (!meta.stableId) {
|
||||
meta.stableId = meta.url || meta.id || "";
|
||||
}
|
||||
if (!meta.stableId) return;
|
||||
if (hidden) {
|
||||
sharedIndex.set(meta.stableId, {
|
||||
@ -500,7 +502,8 @@ function updateSharedIndex(meta, hidden) {
|
||||
title: meta.title,
|
||||
url: meta.url,
|
||||
thumb: meta.thumb,
|
||||
hidden: true
|
||||
hidden: true,
|
||||
lastSeen: Date.now()
|
||||
});
|
||||
} else {
|
||||
sharedIndex.delete(meta.stableId);
|
||||
@ -518,20 +521,16 @@ function schedulePersistSharedIndex() {
|
||||
|
||||
function persistSharedIndex() {
|
||||
const items = Array.from(sharedIndex.values());
|
||||
items.sort((a, b) => a.id.localeCompare(b.id));
|
||||
|
||||
const perChannelCounts = new Map();
|
||||
const pruned = [];
|
||||
for (const item of items) {
|
||||
const key = item.channelKey || item.channelLabel || "(unknown)";
|
||||
const count = perChannelCounts.get(key) || 0;
|
||||
if (count >= MAX_PER_CHANNEL) continue;
|
||||
if (pruned.length >= MAX_GLOBAL_ITEMS) break;
|
||||
perChannelCounts.set(key, count + 1);
|
||||
pruned.push(item);
|
||||
}
|
||||
|
||||
chrome.storage.local.set({ [STORAGE_KEY]: pruned });
|
||||
items.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0));
|
||||
chrome.storage.local.get({ maxArchive: DEFAULT_MAX_ARCHIVE }, data => {
|
||||
const maxArchive = Number(data.maxArchive);
|
||||
const max = Number.isFinite(maxArchive) ? maxArchive : DEFAULT_MAX_ARCHIVE;
|
||||
if (max < 0) {
|
||||
chrome.storage.local.set({ [STORAGE_KEY]: items });
|
||||
return;
|
||||
}
|
||||
chrome.storage.local.set({ [STORAGE_KEY]: items.slice(0, max) });
|
||||
});
|
||||
}
|
||||
|
||||
function loadStoredIndex() {
|
||||
|
||||
182
popup.css
182
popup.css
@ -1,19 +1,58 @@
|
||||
/* Base layout */
|
||||
body {
|
||||
font-family: "Trebuchet MS", "Verdana", sans-serif;
|
||||
min-width: 260px;
|
||||
font-family: "Oswald", "Impact", "Franklin Gothic Medium", sans-serif;
|
||||
min-width: 460px;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
background: linear-gradient(180deg, #f7f3e8, #efe6d2);
|
||||
color: #2b2014;
|
||||
padding: 0;
|
||||
background: radial-gradient(circle at 20% 10%, #2b2b2b, #151515 60%);
|
||||
color: #f5efe6;
|
||||
}
|
||||
|
||||
/* Title */
|
||||
h3 {
|
||||
margin: 0 0 10px;
|
||||
letter-spacing: 0.4px;
|
||||
.scroll {
|
||||
max-height: 640px;
|
||||
overflow-y: auto;
|
||||
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;
|
||||
font-size: 13px;
|
||||
font-size: 16px;
|
||||
color: #fff8ee;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.8px;
|
||||
text-transform: uppercase;
|
||||
color: #2a1506;
|
||||
}
|
||||
|
||||
.subtext {
|
||||
@ -32,47 +71,110 @@ h3 {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
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 {
|
||||
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 */
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #cdbf9e;
|
||||
border-radius: 6px;
|
||||
background: #fffaf0;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #393939;
|
||||
border-radius: 8px;
|
||||
background: var(--dark-2);
|
||||
font-size: 12px;
|
||||
color: var(--cream);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #b2985b;
|
||||
border-radius: 6px;
|
||||
background: #d9c18c;
|
||||
color: #2b2014;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #9b3b0c;
|
||||
border-radius: 8px;
|
||||
background: var(--orange);
|
||||
color: #1f0f06;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.08s ease, filter 0.15s ease;
|
||||
}
|
||||
|
||||
button.ghost {
|
||||
background: transparent;
|
||||
border-color: #cdbf9e;
|
||||
border-color: #4a3a2a;
|
||||
color: var(--cream);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(0.95);
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* List */
|
||||
ul {
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
margin: 10px 12px 12px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@ -81,18 +183,20 @@ li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 4px 0;
|
||||
padding: 6px 8px;
|
||||
background: #fffaf0;
|
||||
border: 1px solid #e2d7be;
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
background: #232323;
|
||||
border: 1px solid #3a2c22;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--cream);
|
||||
}
|
||||
|
||||
li button {
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border-color: #c95a3a;
|
||||
background: #f2a285;
|
||||
border-color: #9b3b0c;
|
||||
background: var(--orange-strong);
|
||||
color: #1f0f06;
|
||||
}
|
||||
|
||||
/* Debug toggle row */
|
||||
@ -101,4 +205,24 @@ li button {
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
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;
|
||||
}
|
||||
|
||||
96
popup.html
96
popup.html
@ -6,36 +6,78 @@
|
||||
<link rel="stylesheet" href="popup.css" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Title + short description -->
|
||||
<h3>Channel Whitelist</h3>
|
||||
<p class="subtext">
|
||||
Hide member-only videos by default. Whitelisted creators stay visible.
|
||||
Add the channel name and its @handle or link.
|
||||
</p>
|
||||
<div class="scroll">
|
||||
<!-- Title + short description -->
|
||||
<div class="topbar">
|
||||
<div class="logo"></div>
|
||||
<div class="title-wrap">
|
||||
<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 -->
|
||||
<div class="row">
|
||||
<input id="channelInput" placeholder="Channel name" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="channelLinkInput" placeholder="Channel link or @handle" />
|
||||
<button id="addBtn">Add</button>
|
||||
</div>
|
||||
<!-- Inputs -->
|
||||
<details class="section" open>
|
||||
<summary>Whitelist a creator</summary>
|
||||
<div class="row">
|
||||
<input id="channelInput" placeholder="Channel name" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="channelLinkInput" placeholder="Channel link or @handle" />
|
||||
<button id="addBtn">Add</button>
|
||||
</div>
|
||||
<div id="addError" class="hint"></div>
|
||||
</details>
|
||||
|
||||
<!-- Settings -->
|
||||
<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 class="section">
|
||||
<summary>Archive settings</summary>
|
||||
<div class="row">
|
||||
<input id="maxInput" type="number" placeholder="Max archived (-1 = unlimited)" />
|
||||
<button id="saveMaxBtn">Save max</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Whitelist list -->
|
||||
<ul id="channelList"></ul>
|
||||
<!-- Settings -->
|
||||
<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 can’t 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 what’s hidden.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
|
||||
27
popup.js
27
popup.js
@ -2,6 +2,9 @@
|
||||
const input = document.getElementById("channelInput");
|
||||
const linkInput = document.getElementById("channelLinkInput");
|
||||
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 debugToggle = document.getElementById("debugToggle");
|
||||
const resetBtn = document.getElementById("resetBtn");
|
||||
@ -9,14 +12,16 @@ const resetBtn = document.getElementById("resetBtn");
|
||||
// Default settings payload.
|
||||
const DEFAULTS = {
|
||||
whitelist: [],
|
||||
debug: false
|
||||
debug: false,
|
||||
maxArchive: 200
|
||||
};
|
||||
|
||||
// Render whitelist and debug state.
|
||||
function loadChannels() {
|
||||
chrome.storage.local.get(DEFAULTS, ({ whitelist, debug }) => {
|
||||
chrome.storage.local.get(DEFAULTS, ({ whitelist, debug, maxArchive }) => {
|
||||
list.innerHTML = "";
|
||||
debugToggle.checked = Boolean(debug);
|
||||
maxInput.value = String(maxArchive);
|
||||
const entries = normalizeWhitelist(whitelist);
|
||||
entries.forEach((entry, index) => {
|
||||
const li = document.createElement("li");
|
||||
@ -39,9 +44,16 @@ function loadChannels() {
|
||||
function addChannel() {
|
||||
const name = input.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);
|
||||
if (!handle) return;
|
||||
if (!handle) {
|
||||
addError.textContent = "Invalid link or @handle.";
|
||||
return;
|
||||
}
|
||||
addError.textContent = "";
|
||||
|
||||
chrome.storage.local.get(DEFAULTS, ({ whitelist }) => {
|
||||
const entries = normalizeWhitelist(whitelist);
|
||||
@ -92,8 +104,15 @@ function normalizeHandle(value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function saveMaxArchive() {
|
||||
const value = parseInt(maxInput.value, 10);
|
||||
if (Number.isNaN(value)) return;
|
||||
chrome.storage.local.set({ maxArchive: value }, loadChannels);
|
||||
}
|
||||
|
||||
// Wire UI events.
|
||||
addBtn.onclick = addChannel;
|
||||
saveMaxBtn.onclick = saveMaxArchive;
|
||||
debugToggle.onchange = e => setDebug(e.target.checked);
|
||||
resetBtn.onclick = resetDefaults;
|
||||
loadChannels();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user