mirror of
https://github.com/Anarios/return-youtube-dislike.git
synced 2026-09-12 10:22:10 +02:00
#1227 - Premium popup feature annoying fix.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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">×</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
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user