#1227 - Premium popup feature annoying fix.

This commit is contained in:
Anarios
2025-10-22 10:35:16 +02:00
parent 40fda6dd32
commit 00242eaef0
5 changed files with 160 additions and 46 deletions
+33
View File
@@ -112,6 +112,7 @@ ytd-menu-renderer.ytd-watch-metadata {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
font-family: Roboto, Arial, sans-serif;
transition: box-shadow 0.2s ease, transform 0.2s ease, opacity 0.2s ease;
position: relative;
}
.ryd-premium-teaser.is-loading {
@@ -183,6 +184,8 @@ ytd-menu-renderer.ytd-watch-metadata {
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
position: relative;
padding-right: 48px;
}
.ryd-premium-teaser__badge {
@@ -219,6 +222,36 @@ ytd-menu-renderer.ytd-watch-metadata {
text-align: left;
}
.ryd-premium-teaser__close {
position: absolute;
top: 8px;
right: 8px;
width: 32px;
height: 32px;
border: none;
border-radius: 999px;
background: transparent;
color: var(--yt-spec-text-secondary, #94a3b8);
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease;
}
.ryd-premium-teaser__close:hover,
.ryd-premium-teaser__close:focus-visible {
background: rgba(148, 163, 184, 0.16);
color: var(--yt-spec-text-primary, #f8fafc);
outline: none;
}
.ryd-premium-teaser__close-icon {
font-size: 18px;
line-height: 1;
font-weight: 600;
}
.ryd-premium-teaser__cta {
border: none;
background: rgba(59, 130, 246, 0.95);
+29 -30
View File
@@ -257,7 +257,8 @@ function persistChangelogVersion(version) {
return;
}
try {
storage.set({ [CHANGELOG_STORAGE_KEY]: version }, () => {
const valueToStore = version || true;
storage.set({ [CHANGELOG_STORAGE_KEY]: valueToStore }, () => {
if (api.runtime.lastError) {
console.debug("Failed to persist changelog version:", api.runtime.lastError.message);
}
@@ -277,43 +278,41 @@ function maybeShowChangelog(details) {
return;
}
if (reason !== "install" && reason !== "update") {
return;
}
const manifest = api.runtime.getManifest();
const currentVersion = manifest?.version;
if (!currentVersion) {
return;
}
const storage = api?.storage?.local;
if (reason === "install") {
openChangelogTab(currentVersion);
const showChangelog = () => {
openChangelogTab(currentVersion || null);
};
if (!storage || typeof storage.get !== "function") {
showChangelog();
return;
}
if (reason === "update") {
if (!storage || typeof storage.get !== "function") {
openChangelogTab(currentVersion);
return;
}
try {
storage.get(CHANGELOG_STORAGE_KEY, (result) => {
if (api.runtime.lastError) {
console.debug("Changelog storage read failed:", api.runtime.lastError.message);
showChangelog();
return;
}
try {
storage.get(CHANGELOG_STORAGE_KEY, (result) => {
if (api.runtime.lastError) {
console.debug("Changelog storage read failed:", api.runtime.lastError.message);
openChangelogTab(currentVersion);
return;
}
const lastShownValue = result?.[CHANGELOG_STORAGE_KEY];
if (lastShownValue !== undefined && lastShownValue !== null && lastShownValue !== "") {
return;
}
const lastShownVersion = result?.[CHANGELOG_STORAGE_KEY];
if (lastShownVersion === currentVersion) {
return;
}
openChangelogTab(currentVersion);
});
} catch (error) {
console.debug("Storage get failed for changelog version", error);
openChangelogTab(currentVersion);
}
showChangelog();
});
} catch (error) {
console.debug("Storage get failed for changelog version", error);
showChangelog();
}
}
@@ -19,12 +19,18 @@ const teaserState = {
storageListener: null,
};
export function initPremiumTeaser() {
export async function initPremiumTeaser() {
if (teaserState.initialized) return;
teaserState.initialized = true;
syncSuppressionWithSettings();
document.addEventListener("yt-navigate-finish", handleNavigation, { passive: true });
try {
await syncSuppressionWithSettings();
} catch {
// Ignore storage sync failures; teaser suppression will remain manual.
}
handleNavigation();
}
@@ -58,17 +64,39 @@ export function setTeaserSuppressed(shouldSuppress, reason = TEASER_SUPPRESSION_
}
}
function syncSuppressionWithSettings() {
function applySettingsSuppression(shouldHide, persist = false) {
const normalized = shouldHide === true;
extConfig.hidePremiumTeaser = normalized;
setTeaserSuppressed(normalized, TEASER_SUPPRESSION_REASON_SETTINGS);
if (!persist) {
return;
}
try {
const browser = getBrowser();
browser?.storage?.sync?.set?.({ hidePremiumTeaser: normalized });
} catch {
// Ignore persistence failures; suppression state already applied locally.
}
}
async function syncSuppressionWithSettings() {
try {
const browser = getBrowser();
if (!browser?.storage?.sync) {
return;
}
browser.storage.sync.get(["hidePremiumTeaser"], (res) => {
const shouldHide = res?.hidePremiumTeaser === true;
extConfig.hidePremiumTeaser = shouldHide;
setTeaserSuppressed(shouldHide, TEASER_SUPPRESSION_REASON_SETTINGS);
await new Promise((resolve) => {
browser.storage.sync.get(["hidePremiumTeaser"], (res) => {
try {
const shouldHide = res?.hidePremiumTeaser === true;
applySettingsSuppression(shouldHide);
} finally {
resolve();
}
});
});
if (!teaserState.storageListener) {
@@ -77,8 +105,7 @@ function syncSuppressionWithSettings() {
return;
}
const shouldHide = changes.hidePremiumTeaser.newValue === true;
extConfig.hidePremiumTeaser = shouldHide;
setTeaserSuppressed(shouldHide, TEASER_SUPPRESSION_REASON_SETTINGS);
applySettingsSuppression(shouldHide);
};
teaserState.storageListener = listener;
browser.storage.onChanged.addListener(listener);
@@ -219,6 +246,11 @@ function ensurePanel() {
});
}
const dismissButton = panel.querySelector("#ryd-premium-teaser-close");
if (dismissButton) {
dismissButton.addEventListener("click", handleManualDismiss);
}
teaserState.panelElement = panel;
return panel;
}
@@ -298,6 +330,7 @@ function createPanelMarkup() {
const subtitle = localize("premiumTeaser_subtitle");
const ctaText = localize("premiumTeaser_cta");
const secondaryText = localize("premiumTeaser_learn");
const closeLabel = localize("hidePremiumTeaser");
const statRaw = localize("premiumTeaser_statRaw");
const statDislikes = localize("premiumTeaser_statDislikes");
const statLikes = localize("premiumTeaser_statLikes");
@@ -318,6 +351,9 @@ function createPanelMarkup() {
<a href="${PATREON_JOIN_URL}" class="ryd-premium-teaser__cta" id="ryd-premium-teaser-cta">${ctaText}</a>
<a href="${CHANGELOG_URL}" class="ryd-premium-teaser__secondary" id="ryd-premium-teaser-learn">${secondaryText}</a>
</div>
<button type="button" class="ryd-premium-teaser__close" id="ryd-premium-teaser-close" aria-label="${closeLabel}" title="${closeLabel}">
<span class="ryd-premium-teaser__close-icon" aria-hidden="true">&times;</span>
</button>
</header>
<div class="ryd-premium-teaser__body">
<div class="ryd-premium-teaser__stats" role="status" aria-live="polite">
@@ -375,3 +411,9 @@ function openTab(url) {
// ignore navigation failures
}
}
function handleManualDismiss(event) {
event?.preventDefault?.();
event?.stopPropagation?.();
applySettingsSuppression(true, true);
}
@@ -40,6 +40,9 @@ describe("premiumAnalytics.teaser", () => {
let getVideoId;
let TEASER_SUPPRESSION_REASON_SETTINGS;
let TEASER_SUPPRESSION_REASON_PREMIUM;
let storageGetMock;
let storageSetMock;
let storageOnChangedAddListener;
function mountSecondary() {
document.body.innerHTML = `
@@ -56,11 +59,29 @@ describe("premiumAnalytics.teaser", () => {
jest.resetModules();
jest.clearAllMocks();
mountSecondary();
storageGetMock = jest.fn((keys, callback) => {
callback({ hidePremiumTeaser: false });
});
storageSetMock = jest.fn((values, callback) => {
if (typeof callback === "function") {
callback();
}
});
storageOnChangedAddListener = jest.fn();
global.chrome = {
i18n: {
getMessage,
},
runtime: {},
storage: {
sync: {
get: storageGetMock,
set: storageSetMock,
},
onChanged: {
addListener: storageOnChangedAddListener,
},
},
};
({ getVideoId } = require("../../utils"));
@@ -97,7 +118,7 @@ describe("premiumAnalytics.teaser", () => {
it("renders the teaser panel with fetched dislike data", async () => {
getVideoId.mockReturnValue("abcdefghijk");
initPremiumTeaser();
await initPremiumTeaser();
await flushPromises();
const panel = document.querySelector(".ryd-premium-teaser");
@@ -116,7 +137,7 @@ describe("premiumAnalytics.teaser", () => {
it("removes the panel when suppressed and restores it when unsuppressed", async () => {
getVideoId.mockReturnValue("LMNOPQRSTUV");
initPremiumTeaser();
await initPremiumTeaser();
await flushPromises();
expect(document.querySelector(".ryd-premium-teaser")).not.toBeNull();
@@ -135,7 +156,7 @@ describe("premiumAnalytics.teaser", () => {
premium.className = "ryd-premium-analytics";
container.appendChild(premium);
initPremiumTeaser();
await initPremiumTeaser();
await flushPromises();
expect(document.querySelector(".ryd-premium-teaser")).toBeNull();
@@ -144,7 +165,7 @@ describe("premiumAnalytics.teaser", () => {
it("cleans up stray teaser panels even when already suppressed", async () => {
getVideoId.mockReturnValue("ZXCVBNMASDF");
initPremiumTeaser();
await initPremiumTeaser();
await flushPromises();
setTeaserSuppressed(true);
@@ -161,7 +182,7 @@ describe("premiumAnalytics.teaser", () => {
it("keeps the teaser hidden while the settings suppression is active", async () => {
getVideoId.mockReturnValue("SETTI123456");
initPremiumTeaser();
await initPremiumTeaser();
await flushPromises();
expect(document.querySelector(".ryd-premium-teaser")).not.toBeNull();
@@ -182,10 +203,29 @@ describe("premiumAnalytics.teaser", () => {
getVideoId.mockReturnValue("QWERTYUIOP1");
global.fetch = jest.fn().mockRejectedValue(new Error("network down"));
initPremiumTeaser();
await initPremiumTeaser();
await flushPromises();
const status = document.querySelector("#ryd-premium-teaser-status");
expect(status?.textContent).toBe(getMessage("premiumTeaser_statusError"));
});
it("persists the hide setting when the close button is clicked", async () => {
getVideoId.mockReturnValue("DISMISSME01");
await initPremiumTeaser();
await flushPromises();
const closeButton = document.querySelector("#ryd-premium-teaser-close");
expect(closeButton).not.toBeNull();
storageSetMock.mockClear();
closeButton.click();
await flushPromises();
expect(storageSetMock).toHaveBeenCalledWith({ hidePremiumTeaser: true });
expect(document.querySelector(".ryd-premium-teaser")).toBeNull();
});
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "return-youtube-dislike",
"version": "4.0.0",
"version": "4.0.1",
"description": "Chrome extension to return youtube dislikes",
"main": "ryd.content-script.js",
"scripts": {