Enhance popup UI and functionality: update styles, improve input handling, and add max archive settings
This commit is contained in:
parent
cb062589fa
commit
49bbce95ea
37
content.js
37
content.js
@ -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;
|
|
||||||
perChannelCounts.set(key, count + 1);
|
|
||||||
pruned.push(item);
|
|
||||||
}
|
}
|
||||||
|
chrome.storage.local.set({ [STORAGE_KEY]: items.slice(0, max) });
|
||||||
chrome.storage.local.set({ [STORAGE_KEY]: pruned });
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadStoredIndex() {
|
function loadStoredIndex() {
|
||||||
|
|||||||
182
popup.css
182
popup.css
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
44
popup.html
44
popup.html
@ -6,14 +6,23 @@
|
|||||||
<link rel="stylesheet" href="popup.css" />
|
<link rel="stylesheet" href="popup.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="scroll">
|
||||||
<!-- Title + short description -->
|
<!-- Title + short description -->
|
||||||
<h3>Channel Whitelist</h3>
|
<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">
|
<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 -->
|
<!-- Inputs -->
|
||||||
|
<details class="section" open>
|
||||||
|
<summary>Whitelist a creator</summary>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input id="channelInput" placeholder="Channel name" />
|
<input id="channelInput" placeholder="Channel name" />
|
||||||
</div>
|
</div>
|
||||||
@ -21,8 +30,20 @@
|
|||||||
<input id="channelLinkInput" placeholder="Channel link or @handle" />
|
<input id="channelLinkInput" placeholder="Channel link or @handle" />
|
||||||
<button id="addBtn">Add</button>
|
<button id="addBtn">Add</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="addError" class="hint"></div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
|
<details class="section">
|
||||||
|
<summary>App settings</summary>
|
||||||
<div class="row settings">
|
<div class="row settings">
|
||||||
<label class="toggle">
|
<label class="toggle">
|
||||||
<input id="debugToggle" type="checkbox" />
|
<input id="debugToggle" type="checkbox" />
|
||||||
@ -33,9 +54,30 @@
|
|||||||
<p class="subtext subtext-debug">
|
<p class="subtext subtext-debug">
|
||||||
Debug logs will appear in the console only when enabled.
|
Debug logs will appear in the console only when enabled.
|
||||||
</p>
|
</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 -->
|
<!-- Whitelist list -->
|
||||||
|
<details class="section" open>
|
||||||
|
<summary>Whitelisted creators</summary>
|
||||||
<ul id="channelList"></ul>
|
<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>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
27
popup.js
27
popup.js
@ -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();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user