mirror of
https://github.com/Alvin-Zilverstand/return-youtube-dislike.git
synced 2026-09-12 09:58:20 +02:00
Prepare 4.0.0.3 extension release
This commit is contained in:
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Patreon ilə daxil ol"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Giriş tamamlanmadı. Yenidən cəhd edin."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Přihlásit se pomocí Patreonu"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Přihlášení se nepodařilo dokončit. Zkuste to znovu."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Mit Patreon anmelden"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Anmeldung konnte nicht abgeschlossen werden. Bitte versuche es erneut."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Σύνδεση με Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Η σύνδεση δεν ολοκληρώθηκε. Δοκιμάστε ξανά."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Login with Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Failed to complete login. Please try again."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Iniciar sesión con Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "No se pudo completar el inicio de sesión. Inténtalo de nuevo."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Se connecter avec Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Échec de la connexion. Veuillez réessayer."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Bejelentkezés Patreonnal"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "A bejelentkezés nem sikerült. Próbáld újra."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Masuk dengan Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Gagal menyelesaikan login. Coba lagi."
|
||||
},
|
||||
|
||||
@@ -83,8 +83,20 @@
|
||||
"patreonLogin": {
|
||||
"message": "Accedi con Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Impossibile completare l’accesso. Riprova."
|
||||
"message": "Impossibile completare l'accesso. Riprova."
|
||||
},
|
||||
"patreonLoginStartFailed": {
|
||||
"message": "Impossibile avviare l’accesso con Patreon. Riprova."
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Patreonでログイン"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "ログインを完了できませんでした。もう一度お試しください。"
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Patreon으로 로그인"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "로그인을 완료하지 못했습니다. 다시 시도해 주세요."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Inloggen met Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Aanmelden kon niet worden voltooid. Probeer het opnieuw."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Zaloguj się przez Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Nie udało się dokończyć logowania. Spróbuj ponownie."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Entrar com o Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Não foi possível concluir o login. Tente novamente."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Войти через Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Не удалось завершить вход. Попробуйте еще раз."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Logga in med Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Det gick inte att slutföra inloggningen. Försök igen."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Patreon ile giriş yap"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Giriş tamamlanamadı. Lütfen tekrar deneyin."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Увійти через Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Не вдалося завершити вхід. Спробуйте ще раз."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "Đăng nhập bằng Patreon"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "Không thể hoàn tất đăng nhập. Vui lòng thử lại."
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "使用 Patreon 登录"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "登录未完成,请重试。"
|
||||
},
|
||||
|
||||
@@ -83,6 +83,18 @@
|
||||
"patreonLogin": {
|
||||
"message": "使用 Patreon 登入"
|
||||
},
|
||||
"githubLoggedOutMessage": {
|
||||
"message": "Premium features available for GitHub contributors"
|
||||
},
|
||||
"githubLogin": {
|
||||
"message": "Login with GitHub (Contributor)"
|
||||
},
|
||||
"githubLoginStartFailed": {
|
||||
"message": "Failed to initiate GitHub login. Please try again."
|
||||
},
|
||||
"githubLoginCompleteFailed": {
|
||||
"message": "Failed to complete GitHub login. Please try again."
|
||||
},
|
||||
"patreonLoginCompleteFailed": {
|
||||
"message": "無法完成登入,請再試一次。"
|
||||
},
|
||||
|
||||
@@ -106,13 +106,21 @@ ytd-menu-renderer.ytd-watch-metadata {
|
||||
background: var(--yt-spec-base-background, #202020);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
padding: 12px 18px 10px;
|
||||
margin-bottom: 16px;
|
||||
color: var(--yt-spec-text-primary, #ffffff);
|
||||
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;
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease, opacity 0.2s ease, padding 0.2s ease;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser.is-expanded {
|
||||
gap: 12px;
|
||||
padding: 12px 18px 16px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser.is-loading {
|
||||
@@ -179,53 +187,88 @@ ytd-menu-renderer.ytd-watch-metadata {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__header {
|
||||
.ryd-premium-teaser__summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
padding-right: 48px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(59, 130, 246, 0.18);
|
||||
color: #0f172a;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
.ryd-premium-teaser__counts {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__title {
|
||||
font-size: 18px;
|
||||
margin: 4px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__subtitle {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--yt-spec-text-secondary, #bcccdc);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__actions {
|
||||
.ryd-premium-teaser__count {
|
||||
flex: 1 1 0;
|
||||
min-width: 120px;
|
||||
padding: 10px 14px;
|
||||
border-radius: 12px;
|
||||
background: rgba(148, 163, 184, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
text-align: left;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__count-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--yt-spec-text-secondary, #9ca3af);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__count-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__toggle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: rgba(148, 163, 184, 0.16);
|
||||
color: var(--yt-spec-text-secondary, #94a3b8);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
align-self: center;
|
||||
margin: 8px auto 0;
|
||||
order: 3;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser.is-expanded .ryd-premium-teaser__toggle {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__toggle:hover,
|
||||
.ryd-premium-teaser__toggle:focus-visible {
|
||||
background: rgba(148, 163, 184, 0.28);
|
||||
color: var(--yt-spec-text-primary, #f8fafc);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__toggle-icon {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 6px solid currentColor;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser.is-expanded .ryd-premium-teaser__toggle-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
@@ -237,6 +280,9 @@ ytd-menu-renderer.ytd-watch-metadata {
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__close:hover,
|
||||
@@ -252,6 +298,54 @@ ytd-menu-renderer.ytd-watch-metadata {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__details {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser.is-expanded .ryd-premium-teaser__details {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__subtitle {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--yt-spec-text-secondary, #bcccdc);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__highlights-title {
|
||||
margin: 0 0 6px 0;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--yt-spec-text-secondary, #9ca3af);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__highlights-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--yt-spec-text-secondary, #93adc8);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__cta {
|
||||
border: none;
|
||||
background: rgba(59, 130, 246, 0.95);
|
||||
@@ -280,82 +374,19 @@ ytd-menu-renderer.ytd-watch-metadata {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__body {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__stat {
|
||||
flex: 1 1 140px;
|
||||
background: rgba(148, 163, 184, 0.08);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__stat-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--yt-spec-text-secondary, #9ca3af);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__status {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--yt-spec-text-secondary, #9ca3af);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__breadcrumbs {
|
||||
background: rgba(59, 130, 246, 0.08);
|
||||
border-radius: 12px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__breadcrumbs-title {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__breadcrumbs-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--yt-spec-text-secondary, #93adc8);
|
||||
}
|
||||
|
||||
.ryd-premium-teaser__breadcrumbs-list li {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
html[dark] .ryd-premium-teaser__badge {
|
||||
background: rgba(59, 130, 246, 0.28);
|
||||
color: #dbeafe;
|
||||
}
|
||||
|
||||
html[dark] .ryd-premium-teaser__stat {
|
||||
html[dark] .ryd-premium-teaser__count {
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
}
|
||||
|
||||
html[dark] .ryd-premium-teaser__breadcrumbs {
|
||||
background: rgba(59, 130, 246, 0.14);
|
||||
html[dark] .ryd-premium-teaser__toggle:hover,
|
||||
html[dark] .ryd-premium-teaser__toggle:focus-visible {
|
||||
background: rgba(148, 163, 184, 0.22);
|
||||
}
|
||||
|
||||
.ryd-analytics__header {
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"host_permissions": [
|
||||
"*://*.youtube.com/*"
|
||||
"*://*.youtube.com/*",
|
||||
"*://returnyoutubedislikeapi.com/*",
|
||||
"https://localhost:7258/*",
|
||||
"http://localhost:7258/*"
|
||||
],
|
||||
"permissions": [
|
||||
"storage"
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
"activeTab",
|
||||
"*://*.youtube.com/*",
|
||||
"storage",
|
||||
"*://returnyoutubedislikeapi.com/*"
|
||||
"*://returnyoutubedislikeapi.com/*",
|
||||
"https://localhost:7258/*",
|
||||
"http://localhost:7258/*"
|
||||
],
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html"
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
</svg>
|
||||
__MSG_patreonLogin__
|
||||
</button>
|
||||
<p style="color: var(--lightGrey); font-size: 0.85rem; margin: 0.6rem 0 0.4rem;" title="__MSG_githubLoggedOutMessage__">__MSG_githubLoggedOutMessage__</p>
|
||||
<button class="m3-btn m3-primary" style="margin-top: 0.1rem;" id="github-login-btn" title="__MSG_githubLogin__">
|
||||
__MSG_githubLogin__
|
||||
</button>
|
||||
</div>
|
||||
<div id="patreon-logged-in" style="display: none;">
|
||||
<div style="display: flex; align-items: center; margin-bottom: 0.5rem;">
|
||||
|
||||
@@ -99,6 +99,7 @@ function initPatreonAuth() {
|
||||
const loggedOutView = document.getElementById("patreon-logged-out");
|
||||
const loggedInView = document.getElementById("patreon-logged-in");
|
||||
const loginBtn = document.getElementById("patreon-login-btn");
|
||||
const githubLoginBtn = document.getElementById("github-login-btn");
|
||||
const logoutBtn = document.getElementById("patreon-logout-btn");
|
||||
const userAvatar = document.getElementById("patreon-user-avatar");
|
||||
const userName = document.getElementById("patreon-user-name");
|
||||
@@ -335,6 +336,33 @@ function initPatreonAuth() {
|
||||
});
|
||||
});
|
||||
|
||||
if (githubLoginBtn) {
|
||||
githubLoginBtn.addEventListener("click", () => {
|
||||
ensureIdentityPermission(async (granted) => {
|
||||
const identityNow = getIdentityApi();
|
||||
if (!granted || !identityNow || typeof identityNow.getRedirectURL !== "function") {
|
||||
alert(chrome.i18n.getMessage("patreonPermissionRequired"));
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({ message: "github_oauth_login" }, (resp) => {
|
||||
if (chrome.runtime && chrome.runtime.lastError) {
|
||||
console.error("GitHub login failed:", chrome.runtime.lastError.message);
|
||||
alert(chrome.i18n.getMessage("githubLoginStartFailed"));
|
||||
return;
|
||||
}
|
||||
if (resp && resp.success) {
|
||||
const user = resp.user;
|
||||
showLoggedInView(user);
|
||||
} else {
|
||||
console.error("GitHub login failed:", resp && resp.error);
|
||||
alert(chrome.i18n.getMessage("githubLoginCompleteFailed"));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logoutBtn.addEventListener("click", () => {
|
||||
chrome.storage.sync.remove(["patreonUser", "patreonSessionToken"], () => {
|
||||
showLoggedOutView();
|
||||
|
||||
@@ -235,6 +235,62 @@ api.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
} else if (request.message === "github_oauth_login") {
|
||||
(async () => {
|
||||
try {
|
||||
const idApi = getIdentityApi();
|
||||
if (
|
||||
!idApi ||
|
||||
typeof (idApi.getRedirectURL || (isChrome() && chrome.identity && chrome.identity.getRedirectURL)) !==
|
||||
"function"
|
||||
) {
|
||||
sendResponse({ success: false, error: "identity API not available" });
|
||||
return;
|
||||
}
|
||||
|
||||
const redirectUri =
|
||||
isFirefox() && browser.identity.getRedirectURL
|
||||
? browser.identity.getRedirectURL()
|
||||
: isChrome() && chrome.identity.getRedirectURL
|
||||
? chrome.identity.getRedirectURL()
|
||||
: "";
|
||||
|
||||
const startRes = await fetch(
|
||||
getApiEndpoint(`/api/auth/github/login?redirectUri=${encodeURIComponent(redirectUri)}`),
|
||||
);
|
||||
const startData = await startRes.json();
|
||||
|
||||
const responseUrl = await launchWebAuthFlow(startData.authUrl);
|
||||
const { code, state } = extractOAuthParams(responseUrl);
|
||||
if (!code) {
|
||||
sendResponse({ success: false, error: "No authorization code received" });
|
||||
return;
|
||||
}
|
||||
|
||||
const exchangeRes = await fetch(getApiEndpoint("/api/auth/github/exchange"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
state,
|
||||
expectedState: startData.state,
|
||||
redirectUri: startData.redirectUri || redirectUri,
|
||||
}),
|
||||
});
|
||||
const authData = await exchangeRes.json();
|
||||
if (authData && authData.success) {
|
||||
handlePatreonAuthComplete(authData.user, authData.sessionToken, () => {
|
||||
sendResponse({ success: true, user: authData.user });
|
||||
});
|
||||
} else {
|
||||
sendResponse({ success: false, error: (authData && authData.error) || "OAuth exchange failed" });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("github_oauth_login error", e);
|
||||
sendResponse({ success: false, error: String((e && e.message) || e) });
|
||||
}
|
||||
})();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { getButtons, getDislikeButton, getLikeButton } from "./buttons";
|
||||
import {
|
||||
extConfig,
|
||||
isMobile,
|
||||
isLikesDisabled,
|
||||
isNewDesign,
|
||||
isRoundedDesign,
|
||||
isShorts,
|
||||
} from "./state";
|
||||
import { getColorFromTheme, isInViewport } from "./utils";
|
||||
import { extConfig, isMobile, isLikesDisabled, isNewDesign, isRoundedDesign, isShorts } from "./state";
|
||||
import { getColorFromTheme, isInViewport, querySelector } from "./utils";
|
||||
|
||||
function createRateBar(likes, dislikes) {
|
||||
let rateBar = document.getElementById("ryd-bar-container");
|
||||
@@ -23,8 +16,7 @@ function createRateBar(likes, dislikes) {
|
||||
parseFloat(window.getComputedStyle(getDislikeButton()).width) +
|
||||
(isRoundedDesign() ? 0 : 8);
|
||||
|
||||
const widthPercent =
|
||||
likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
|
||||
const widthPercent = likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
|
||||
|
||||
var likePercentage = parseFloat(widthPercent.toFixed(1));
|
||||
const dislikePercentage = (100 - likePercentage).toLocaleString();
|
||||
@@ -61,13 +53,10 @@ function createRateBar(likes, dislikes) {
|
||||
colorDislikeStyle = "; background-color: " + getColorFromTheme(false);
|
||||
}
|
||||
let actions =
|
||||
isNewDesign() && getButtons().id === "top-level-buttons-computed"
|
||||
isNewDesign() && getButtons() === querySelector(extConfig.selectors.rateBar.newDesignActions)
|
||||
? getButtons()
|
||||
: document.getElementById("menu-container");
|
||||
(
|
||||
actions ||
|
||||
document.querySelector("ytm-slim-video-action-bar-renderer")
|
||||
).insertAdjacentHTML(
|
||||
: querySelector(extConfig.selectors.rateBar.oldDesignActions);
|
||||
(actions || querySelector(extConfig.selectors.rateBar.mobileActionBar)).insertAdjacentHTML(
|
||||
"beforeend",
|
||||
`
|
||||
<div class="ryd-tooltip ryd-tooltip-${isNewDesign() ? "new" : "old"}-design" style="width: ${widthPx}px">
|
||||
@@ -91,28 +80,23 @@ function createRateBar(likes, dislikes) {
|
||||
|
||||
if (isNewDesign()) {
|
||||
// Add border between info and comments
|
||||
let descriptionAndActionsElement = document.getElementById("top-row");
|
||||
descriptionAndActionsElement.style.borderBottom =
|
||||
"1px solid var(--yt-spec-10-percent-layer)";
|
||||
let descriptionAndActionsElement = querySelector(extConfig.selectors.rateBar.topRow);
|
||||
descriptionAndActionsElement.style.borderBottom = "1px solid var(--yt-spec-10-percent-layer)";
|
||||
descriptionAndActionsElement.style.paddingBottom = "10px";
|
||||
|
||||
// Fix like/dislike ratio bar offset in new UI
|
||||
document.getElementById("actions-inner").style.width = "revert";
|
||||
querySelector(extConfig.selectors.rateBar.actionsInner).style.width = "revert";
|
||||
if (isRoundedDesign()) {
|
||||
document.getElementById("actions").style.flexDirection =
|
||||
"row-reverse";
|
||||
querySelector(extConfig.selectors.rateBar.actions).style.flexDirection = "row-reverse";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
document.querySelector(`.ryd-tooltip`).style.width = widthPx + "px";
|
||||
document.getElementById("ryd-bar").style.width = widthPercent + "%";
|
||||
document.querySelector("#ryd-dislike-tooltip > #tooltip").innerHTML =
|
||||
tooltipInnerHTML;
|
||||
document.querySelector("#ryd-dislike-tooltip > #tooltip").innerHTML = tooltipInnerHTML;
|
||||
if (extConfig.coloredBar) {
|
||||
document.getElementById("ryd-bar-container").style.backgroundColor =
|
||||
getColorFromTheme(false);
|
||||
document.getElementById("ryd-bar").style.backgroundColor =
|
||||
getColorFromTheme(true);
|
||||
document.getElementById("ryd-bar-container").style.backgroundColor = getColorFromTheme(false);
|
||||
document.getElementById("ryd-bar").style.backgroundColor = getColorFromTheme(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { isMobile, isShorts, extConfig } from "./state";
|
||||
import { isInViewport, querySelector, querySelectorAll } from "./utils";
|
||||
|
||||
function getNativeButton(buttonContainer) {
|
||||
return querySelector(extConfig.selectors.buttons.nativeButton, buttonContainer);
|
||||
}
|
||||
|
||||
function isSegmentedButtonLayout() {
|
||||
return querySelector(extConfig.selectors.buttons.segmentedContainer, getButtons()) !== undefined;
|
||||
}
|
||||
|
||||
function getButtons() {
|
||||
//--- If Watching Youtube Shorts: ---//
|
||||
if (isShorts()) {
|
||||
@@ -34,17 +42,10 @@ function getButtons() {
|
||||
}
|
||||
|
||||
function getLikeButton() {
|
||||
return getButtons().children[0].tagName ===
|
||||
"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER"
|
||||
return isSegmentedButtonLayout()
|
||||
? querySelector(extConfig.selectors.buttons.likeButton.segmented) ??
|
||||
querySelector(
|
||||
extConfig.selectors.buttons.likeButton.segmentedGetButtons,
|
||||
getButtons(),
|
||||
)
|
||||
: querySelector(
|
||||
extConfig.selectors.buttons.likeButton.notSegmented,
|
||||
getButtons(),
|
||||
);
|
||||
querySelector(extConfig.selectors.buttons.likeButton.segmentedGetButtons, getButtons())
|
||||
: querySelector(extConfig.selectors.buttons.likeButton.notSegmented, getButtons());
|
||||
}
|
||||
|
||||
function getLikeTextContainer() {
|
||||
@@ -52,57 +53,52 @@ function getLikeTextContainer() {
|
||||
}
|
||||
|
||||
function getDislikeButton() {
|
||||
if (
|
||||
getButtons().children[0].tagName ===
|
||||
"YTD-SEGMENTED-LIKE-DISLIKE-BUTTON-RENDERER"
|
||||
) {
|
||||
if (isSegmentedButtonLayout()) {
|
||||
return (
|
||||
querySelector(extConfig.selectors.buttons.dislikeButton.segmented) ??
|
||||
querySelector(
|
||||
extConfig.selectors.buttons.dislikeButton.segmentedGetButtons,
|
||||
getButtons(),
|
||||
)
|
||||
querySelector(extConfig.selectors.buttons.dislikeButton.segmentedGetButtons, getButtons())
|
||||
);
|
||||
}
|
||||
|
||||
const notSegmentedMatch = querySelector(
|
||||
extConfig.selectors.buttons.dislikeButton.notSegmented,
|
||||
getButtons(),
|
||||
);
|
||||
const notSegmentedMatch = querySelector(extConfig.selectors.buttons.dislikeButton.notSegmented, getButtons());
|
||||
|
||||
if (notSegmentedMatch != null) {
|
||||
return notSegmentedMatch;
|
||||
}
|
||||
|
||||
if (isShorts()) {
|
||||
return querySelector(["#dislike-button"], getButtons());
|
||||
return querySelector(extConfig.selectors.buttons.dislikeButton.shortsFallback, getButtons());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTextContainerTemplate() {
|
||||
const likeButton = getLikeButton();
|
||||
const parentTemplate =
|
||||
querySelector(extConfig.selectors.likeTextContainerTemplateParent, likeButton) ??
|
||||
querySelector(extConfig.selectors.likeTextContainerTemplateParent);
|
||||
|
||||
return querySelector(extConfig.selectors.likeTextContainerTemplate, likeButton) ?? parentTemplate?.parentNode;
|
||||
}
|
||||
|
||||
function updateDislikeButtonShape(dislikeButton) {
|
||||
for (const className of extConfig.selectors.buttonClasses.iconButton) {
|
||||
dislikeButton.classList.remove(className);
|
||||
}
|
||||
|
||||
for (const className of extConfig.selectors.buttonClasses.iconLeading) {
|
||||
dislikeButton.classList.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
function createDislikeTextContainer() {
|
||||
const textNodeClone = (
|
||||
getLikeButton().querySelector(
|
||||
".yt-spec-button-shape-next__button-text-content",
|
||||
) ||
|
||||
getLikeButton().querySelector("button > div[class*='cbox']") ||
|
||||
(
|
||||
getLikeButton().querySelector('div > span[role="text"]') ||
|
||||
document.querySelector(
|
||||
'button > div.yt-spec-button-shape-next__button-text-content > span[role="text"]',
|
||||
)
|
||||
).parentNode
|
||||
).cloneNode(true);
|
||||
const insertPreChild = getDislikeButton().querySelector("button");
|
||||
const textNodeClone = getTextContainerTemplate().cloneNode(true);
|
||||
const dislikeButton = getNativeButton(getDislikeButton());
|
||||
const insertPreChild = dislikeButton;
|
||||
insertPreChild.insertBefore(textNodeClone, null);
|
||||
getDislikeButton()
|
||||
.querySelector("button")
|
||||
.classList.remove("yt-spec-button-shape-next--icon-button");
|
||||
getDislikeButton()
|
||||
.querySelector("button")
|
||||
.classList.add("yt-spec-button-shape-next--icon-leading");
|
||||
if (textNodeClone.querySelector("span[role='text']") === null) {
|
||||
updateDislikeButtonShape(dislikeButton);
|
||||
if (querySelector(extConfig.selectors.textContainerInner, textNodeClone) === undefined) {
|
||||
const span = document.createElement("span");
|
||||
span.setAttribute("role", "text");
|
||||
while (textNodeClone.firstChild) {
|
||||
@@ -116,11 +112,13 @@ function createDislikeTextContainer() {
|
||||
|
||||
function getDislikeTextContainer() {
|
||||
let result;
|
||||
const nativeDislikeButton = getNativeButton(getDislikeButton());
|
||||
for (const selector of extConfig.selectors.dislikeTextContainer) {
|
||||
result = getDislikeButton().querySelector(selector);
|
||||
if (result !== null) {
|
||||
if (result !== null && result !== nativeDislikeButton) {
|
||||
break;
|
||||
}
|
||||
result = null;
|
||||
}
|
||||
if (result == null) {
|
||||
result = createDislikeTextContainer();
|
||||
@@ -129,11 +127,7 @@ function getDislikeTextContainer() {
|
||||
}
|
||||
|
||||
function checkForSignInButton() {
|
||||
if (
|
||||
document.querySelector(
|
||||
"a[href^='https://accounts.google.com/ServiceLogin']",
|
||||
)
|
||||
) {
|
||||
if (querySelector(extConfig.selectors.signInButton)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const DEV_API_URL = "https://localhost:7258";
|
||||
const PROD_API_URL = "https://returnyoutubedislikeapi.com";
|
||||
const DEV_API_URL = PROD_API_URL;
|
||||
|
||||
const runtime = typeof chrome !== "undefined" ? chrome.runtime : null;
|
||||
const manifest = typeof runtime?.getManifest === "function" ? runtime.getManifest() : null;
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
import {
|
||||
getBrowser,
|
||||
getVideoId,
|
||||
numberFormat,
|
||||
createObserver,
|
||||
} from "./utils";
|
||||
import {
|
||||
checkForSignInButton,
|
||||
getButtons,
|
||||
getDislikeButton,
|
||||
getLikeButton,
|
||||
} from "./buttons";
|
||||
import { getBrowser, getVideoId, numberFormat, createObserver, querySelector } from "./utils";
|
||||
import { checkForSignInButton, getButtons, getDislikeButton, getLikeButton } from "./buttons";
|
||||
import {
|
||||
NEUTRAL_STATE,
|
||||
LIKED_STATE,
|
||||
@@ -122,11 +112,8 @@ function createSmartimationObserver() {
|
||||
smartimationObserver.container = null;
|
||||
}
|
||||
|
||||
const smartimationContainer = getButtons().querySelector("yt-smartimation");
|
||||
if (
|
||||
smartimationContainer &&
|
||||
smartimationObserver.container != smartimationContainer
|
||||
) {
|
||||
const smartimationContainer = querySelector(extConfig.selectors.buttons.smartimation, getButtons());
|
||||
if (smartimationContainer && smartimationObserver.container != smartimationContainer) {
|
||||
console.log("Initializing smartimation mutation observer");
|
||||
smartimationObserver.disconnect();
|
||||
smartimationObserver.observe(smartimationContainer);
|
||||
@@ -136,9 +123,7 @@ function createSmartimationObserver() {
|
||||
|
||||
function storageChangeHandler(changes, area) {
|
||||
if (changes.disableVoteSubmission !== undefined) {
|
||||
handleDisableVoteSubmissionChangeEvent(
|
||||
changes.disableVoteSubmission.newValue,
|
||||
);
|
||||
handleDisableVoteSubmissionChangeEvent(changes.disableVoteSubmission.newValue);
|
||||
}
|
||||
if (changes.coloredThumbs !== undefined) {
|
||||
handleColoredThumbsChangeEvent(changes.coloredThumbs.newValue);
|
||||
@@ -153,14 +138,10 @@ function storageChangeHandler(changes, area) {
|
||||
handleNumberDisplayFormatChangeEvent(changes.numberDisplayFormat.newValue);
|
||||
}
|
||||
if (changes.numberDisplayReformatLikes !== undefined) {
|
||||
handleNumberDisplayReformatLikesChangeEvent(
|
||||
changes.numberDisplayReformatLikes.newValue,
|
||||
);
|
||||
handleNumberDisplayReformatLikesChangeEvent(changes.numberDisplayReformatLikes.newValue);
|
||||
}
|
||||
if (changes.hidePremiumTeaser !== undefined) {
|
||||
handleHidePremiumTeaserChangeEvent(
|
||||
changes.hidePremiumTeaser.newValue,
|
||||
);
|
||||
handleHidePremiumTeaserChangeEvent(changes.hidePremiumTeaser.newValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ const teaserState = {
|
||||
abortController: null,
|
||||
suppressionReasons: new Set(),
|
||||
storageListener: null,
|
||||
expanded: false,
|
||||
};
|
||||
|
||||
export async function initPremiumTeaser() {
|
||||
@@ -226,32 +227,41 @@ function ensurePanel() {
|
||||
}
|
||||
|
||||
const panel = document.createElement("section");
|
||||
panel.className = "ryd-premium-teaser";
|
||||
panel.className = "ryd-premium-teaser is-collapsed";
|
||||
panel.innerHTML = createPanelMarkup();
|
||||
container.insertBefore(panel, container.firstChild);
|
||||
|
||||
const cta = panel.querySelector("#ryd-premium-teaser-cta");
|
||||
if (cta) {
|
||||
cta.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
openTab(PATREON_JOIN_URL);
|
||||
});
|
||||
}
|
||||
|
||||
const infoLink = panel.querySelector("#ryd-premium-teaser-learn");
|
||||
if (infoLink) {
|
||||
infoLink.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
openTab(CHANGELOG_URL);
|
||||
});
|
||||
}
|
||||
|
||||
const dismissButton = panel.querySelector("#ryd-premium-teaser-close");
|
||||
if (dismissButton) {
|
||||
dismissButton.addEventListener("click", handleManualDismiss);
|
||||
}
|
||||
|
||||
const toggle = panel.querySelector("#ryd-premium-teaser-toggle");
|
||||
if (toggle) {
|
||||
toggle.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
setExpanded(!teaserState.expanded);
|
||||
});
|
||||
}
|
||||
|
||||
const expandCta = panel.querySelector("#ryd-premium-teaser-cta");
|
||||
if (expandCta) {
|
||||
expandCta.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
openTab(PATREON_JOIN_URL);
|
||||
});
|
||||
}
|
||||
|
||||
const learnMore = panel.querySelector("#ryd-premium-teaser-learn");
|
||||
if (learnMore) {
|
||||
learnMore.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
openTab(CHANGELOG_URL);
|
||||
});
|
||||
}
|
||||
|
||||
teaserState.panelElement = panel;
|
||||
setExpanded(false);
|
||||
return panel;
|
||||
}
|
||||
|
||||
@@ -275,6 +285,7 @@ function resetState() {
|
||||
teaserState.abortController.abort();
|
||||
teaserState.abortController = null;
|
||||
}
|
||||
teaserState.expanded = false;
|
||||
}
|
||||
|
||||
function setLoading(isLoading) {
|
||||
@@ -301,16 +312,13 @@ function updateCounts(payload) {
|
||||
if (!panel) return;
|
||||
|
||||
const dislikesValue = panel.querySelector("#ryd-premium-teaser-dislikes");
|
||||
const rawDislikesValue = panel.querySelector("#ryd-premium-teaser-raw");
|
||||
const likesValue = panel.querySelector("#ryd-premium-teaser-likes");
|
||||
|
||||
const dislikes = normalizeNumber(payload?.dislikes);
|
||||
const rawDislikes = normalizeNumber(payload?.rawDislikes ?? payload?.dislikes);
|
||||
const likes = normalizeNumber(payload?.likes ?? payload?.rawLikes);
|
||||
const rawLikes = normalizeNumber(payload?.rawLikes ?? payload?.likes);
|
||||
|
||||
if (dislikesValue) dislikesValue.textContent = dislikes;
|
||||
if (rawDislikesValue) rawDislikesValue.textContent = rawDislikes;
|
||||
if (likesValue) likesValue.textContent = likes;
|
||||
if (dislikesValue) dislikesValue.textContent = rawDislikes;
|
||||
if (likesValue) likesValue.textContent = rawLikes;
|
||||
}
|
||||
|
||||
function normalizeNumber(value) {
|
||||
@@ -325,60 +333,66 @@ function normalizeNumber(value) {
|
||||
}
|
||||
|
||||
function createPanelMarkup() {
|
||||
const extensionName = localize("extensionName");
|
||||
const title = localize("premiumTeaser_title");
|
||||
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 statDislikes = localize("premiumTeaser_statRaw");
|
||||
const statLikes = localize("premiumTeaser_statLikes");
|
||||
const breadcrumbsAria = localize("premiumTeaser_breadcrumbsAria");
|
||||
const breadcrumbsTitle = localize("premiumTeaser_breadcrumbsTitle");
|
||||
const step1 = localize("premiumTeaser_breadcrumbStep1");
|
||||
const step2 = localize("premiumTeaser_breadcrumbStep2");
|
||||
const step3 = localize("premiumTeaser_breadcrumbStep3");
|
||||
const expandLabel = localize("premiumAnalytics_expand");
|
||||
const collapseLabel = localize("premiumAnalytics_collapse");
|
||||
const highlightsAria = localize("changelog_section_highlights");
|
||||
const highlightsTitle = localize("changelog_section_highlights");
|
||||
const highlightOne = localize("changelog_feature_timeline_title");
|
||||
const highlightTwo = localize("changelog_feature_map_title");
|
||||
const highlightThree = localize("changelog_feature_quality_title");
|
||||
|
||||
return `
|
||||
<header class="ryd-premium-teaser__header">
|
||||
<div>
|
||||
<span class="ryd-premium-teaser__badge">${extensionName}</span>
|
||||
<h2 class="ryd-premium-teaser__title">${title}</h2>
|
||||
<p class="ryd-premium-teaser__subtitle">${subtitle}</p>
|
||||
<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>
|
||||
<div class="ryd-premium-teaser__summary">
|
||||
<div class="ryd-premium-teaser__counts" role="status" aria-live="polite">
|
||||
<div class="ryd-premium-teaser__count">
|
||||
<span class="ryd-premium-teaser__count-label">${statLikes}</span>
|
||||
<span class="ryd-premium-teaser__count-value" id="ryd-premium-teaser-likes">—</span>
|
||||
</div>
|
||||
<div class="ryd-premium-teaser__count">
|
||||
<span class="ryd-premium-teaser__count-label">${statDislikes}</span>
|
||||
<span class="ryd-premium-teaser__count-value" id="ryd-premium-teaser-dislikes">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ryd-premium-teaser__actions">
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="ryd-premium-teaser__toggle"
|
||||
id="ryd-premium-teaser-toggle"
|
||||
aria-expanded="false"
|
||||
aria-controls="ryd-premium-teaser-details"
|
||||
aria-label="${expandLabel}"
|
||||
title="${expandLabel}"
|
||||
data-expand-label="${expandLabel}"
|
||||
data-collapse-label="${collapseLabel}"
|
||||
>
|
||||
<span class="ryd-premium-teaser__toggle-icon" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="ryd-premium-teaser__details" id="ryd-premium-teaser-details" aria-hidden="true">
|
||||
<h3 class="ryd-premium-teaser__title">${title}</h3>
|
||||
<p class="ryd-premium-teaser__subtitle">${subtitle}</p>
|
||||
<section class="ryd-premium-teaser__highlights" aria-label="${highlightsAria}">
|
||||
<h4 class="ryd-premium-teaser__highlights-title">${highlightsTitle}</h4>
|
||||
<ul class="ryd-premium-teaser__highlights-list">
|
||||
<li>${highlightOne}</li>
|
||||
<li>${highlightTwo}</li>
|
||||
<li>${highlightThree}</li>
|
||||
</ul>
|
||||
</section>
|
||||
<div class="ryd-premium-teaser__links">
|
||||
<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">
|
||||
<div class="ryd-premium-teaser__stat">
|
||||
<span class="ryd-premium-teaser__stat-label">${statRaw}</span>
|
||||
<span class="ryd-premium-teaser__stat-value" id="ryd-premium-teaser-raw">—</span>
|
||||
</div>
|
||||
<div class="ryd-premium-teaser__stat">
|
||||
<span class="ryd-premium-teaser__stat-label">${statDislikes}</span>
|
||||
<span class="ryd-premium-teaser__stat-value" id="ryd-premium-teaser-dislikes">—</span>
|
||||
</div>
|
||||
<div class="ryd-premium-teaser__stat">
|
||||
<span class="ryd-premium-teaser__stat-label">${statLikes}</span>
|
||||
<span class="ryd-premium-teaser__stat-value" id="ryd-premium-teaser-likes">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="ryd-premium-teaser__status" id="ryd-premium-teaser-status"></p>
|
||||
<section class="ryd-premium-teaser__breadcrumbs" aria-label="${breadcrumbsAria}">
|
||||
<h3 class="ryd-premium-teaser__breadcrumbs-title">${breadcrumbsTitle}</h3>
|
||||
<ol class="ryd-premium-teaser__breadcrumbs-list">
|
||||
<li>${step1}</li>
|
||||
<li>${step2}</li>
|
||||
<li>${step3}</li>
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -417,3 +431,30 @@ function handleManualDismiss(event) {
|
||||
event?.stopPropagation?.();
|
||||
applySettingsSuppression(true, true);
|
||||
}
|
||||
|
||||
function setExpanded(next) {
|
||||
teaserState.expanded = !!next;
|
||||
const panel = teaserState.panelElement;
|
||||
if (!panel) return;
|
||||
|
||||
panel.classList.toggle("is-expanded", teaserState.expanded);
|
||||
panel.classList.toggle("is-collapsed", !teaserState.expanded);
|
||||
|
||||
const details = panel.querySelector("#ryd-premium-teaser-details");
|
||||
if (details) {
|
||||
details.toggleAttribute("hidden", !teaserState.expanded);
|
||||
details.setAttribute("aria-hidden", teaserState.expanded ? "false" : "true");
|
||||
}
|
||||
|
||||
const toggle = panel.querySelector("#ryd-premium-teaser-toggle");
|
||||
if (toggle) {
|
||||
const expandText = toggle.getAttribute("data-expand-label") || "";
|
||||
const collapseText = toggle.getAttribute("data-collapse-label") || expandText;
|
||||
toggle.setAttribute("aria-expanded", teaserState.expanded ? "true" : "false");
|
||||
const nextLabel = teaserState.expanded ? collapseText : expandText;
|
||||
if (nextLabel) {
|
||||
toggle.setAttribute("aria-label", nextLabel);
|
||||
toggle.setAttribute("title", nextLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ describe("premiumAnalytics.teaser", () => {
|
||||
dislikes: 123,
|
||||
rawDislikes: 456,
|
||||
likes: 789,
|
||||
rawLikes: 654,
|
||||
}),
|
||||
});
|
||||
});
|
||||
@@ -125,13 +126,37 @@ describe("premiumAnalytics.teaser", () => {
|
||||
expect(panel).not.toBeNull();
|
||||
expect(global.fetch).toHaveBeenCalledWith("https://api.test/votes?videoId=abcdefghijk", expect.any(Object));
|
||||
|
||||
const raw = panel.querySelector("#ryd-premium-teaser-raw")?.textContent;
|
||||
const dislikes = panel.querySelector("#ryd-premium-teaser-dislikes")?.textContent;
|
||||
const likes = panel.querySelector("#ryd-premium-teaser-likes")?.textContent;
|
||||
const dislikes = panel.querySelector("#ryd-premium-teaser-dislikes")?.textContent;
|
||||
const details = panel.querySelector("#ryd-premium-teaser-details");
|
||||
|
||||
expect(raw).toBe("456");
|
||||
expect(dislikes).toBe("123");
|
||||
expect(likes).toBe("789");
|
||||
expect(likes).toBe("654");
|
||||
expect(dislikes).toBe("456");
|
||||
expect(panel?.classList.contains("is-collapsed")).toBe(true);
|
||||
expect(details?.hasAttribute("hidden")).toBe(true);
|
||||
});
|
||||
|
||||
it("toggles the expanded state when the toggle is clicked", async () => {
|
||||
getVideoId.mockReturnValue("togglevideo");
|
||||
|
||||
await initPremiumTeaser();
|
||||
await flushPromises();
|
||||
|
||||
const panel = document.querySelector(".ryd-premium-teaser");
|
||||
expect(panel).not.toBeNull();
|
||||
|
||||
const toggle = panel.querySelector("#ryd-premium-teaser-toggle");
|
||||
const details = panel.querySelector("#ryd-premium-teaser-details");
|
||||
|
||||
expect(toggle?.getAttribute("aria-expanded")).toBe("false");
|
||||
expect(details?.hasAttribute("hidden")).toBe(true);
|
||||
|
||||
toggle?.dispatchEvent(new window.MouseEvent("click", { bubbles: true }));
|
||||
await flushPromises();
|
||||
|
||||
expect(panel?.classList.contains("is-expanded")).toBe(true);
|
||||
expect(toggle?.getAttribute("aria-expanded")).toBe("true");
|
||||
expect(details?.hasAttribute("hidden")).toBe(false);
|
||||
});
|
||||
|
||||
it("removes the panel when suppressed and restores it when unsuppressed", async () => {
|
||||
|
||||
@@ -15,6 +15,113 @@ const LIKED_STATE = "LIKED_STATE";
|
||||
const DISLIKED_STATE = "DISLIKED_STATE";
|
||||
const NEUTRAL_STATE = "NEUTRAL_STATE";
|
||||
|
||||
const DEFAULT_SELECTORS = {
|
||||
dislikeTextContainer: [
|
||||
".yt-spec-button-shape-next__button-text-content",
|
||||
".ytSpecButtonShapeNextButtonTextContent",
|
||||
"#text",
|
||||
"yt-formatted-string",
|
||||
"span[role='text']",
|
||||
],
|
||||
likeTextContainer: [
|
||||
".yt-spec-button-shape-next__button-text-content",
|
||||
".ytSpecButtonShapeNextButtonTextContent",
|
||||
"#text",
|
||||
"yt-formatted-string",
|
||||
"span[role='text']",
|
||||
],
|
||||
likeTextContainerTemplate: [
|
||||
".yt-spec-button-shape-next__button-text-content",
|
||||
".ytSpecButtonShapeNextButtonTextContent",
|
||||
"button > div[class*='cbox']",
|
||||
],
|
||||
likeTextContainerTemplateParent: [
|
||||
'div > span[role="text"]',
|
||||
'button > div.yt-spec-button-shape-next__button-text-content > span[role="text"]',
|
||||
],
|
||||
textContainerInner: ["span[role='text']"],
|
||||
buttons: {
|
||||
shorts: {
|
||||
mobile: ["ytm-like-button-renderer"],
|
||||
desktop: ["reel-action-bar-view-model", "#like-button > ytd-like-button-renderer"],
|
||||
},
|
||||
regular: {
|
||||
mobile: [".slim-video-action-bar-actions"],
|
||||
desktopMenu: ["ytd-menu-renderer.ytd-watch-metadata > div"],
|
||||
desktopNoMenu: ["#top-level-buttons-computed"],
|
||||
},
|
||||
segmentedContainer: ["ytd-segmented-like-dislike-button-renderer"],
|
||||
nativeButton: ["button"],
|
||||
mobileText: [".button-renderer-text"],
|
||||
shortsToggleButton: ["tp-yt-paper-button#button"],
|
||||
smartimation: ["yt-smartimation"],
|
||||
likeButton: {
|
||||
segmented: ["#segmented-like-button"],
|
||||
segmentedGetButtons: [":first-child > :first-child"],
|
||||
notSegmented: ["like-button-view-model", ":first-child"],
|
||||
},
|
||||
dislikeButton: {
|
||||
segmented: ["#segmented-dislike-button"],
|
||||
segmentedGetButtons: [":first-child > :nth-child(2)"],
|
||||
notSegmented: ["dislike-button-view-model", ":nth-child(2)", "#dislike-button"],
|
||||
shortsFallback: ["#dislike-button"],
|
||||
},
|
||||
},
|
||||
buttonClasses: {
|
||||
iconButton: ["yt-spec-button-shape-next--icon-button", "ytSpecButtonShapeNextIconButton"],
|
||||
iconLeading: ["yt-spec-button-shape-next--icon-leading", "ytSpecButtonShapeNextIconLeading"],
|
||||
},
|
||||
activeButtonClasses: ["style-default-active"],
|
||||
likeCountButton: ["yt-formatted-string#text", "button"],
|
||||
videoLoaded: [
|
||||
"ytd-watch-grid[video-id='{videoId}']",
|
||||
"ytd-watch-flexy[video-id='{videoId}']",
|
||||
'#player[loading="false"]:not([hidden])',
|
||||
],
|
||||
shortsLoaded: {
|
||||
containers: [".reel-video-in-sequence-new"],
|
||||
thumbnail: [".reel-video-in-sequence-thumbnail"],
|
||||
renderer: ["ytd-reel-video-renderer"],
|
||||
overlay: ["#experiment-overlay"],
|
||||
},
|
||||
rateBar: {
|
||||
newDesignActions: ["#top-level-buttons-computed"],
|
||||
oldDesignActions: ["#menu-container"],
|
||||
mobileActionBar: ["ytm-slim-video-action-bar-renderer"],
|
||||
topRow: ["#top-row"],
|
||||
actionsInner: ["#actions-inner"],
|
||||
actions: ["#actions"],
|
||||
},
|
||||
signInButton: ["a[href^='https://accounts.google.com/ServiceLogin']"],
|
||||
menuContainer: ["#menu-container"],
|
||||
roundedDesign: ["#segmented-like-button", "like-button-view-model"],
|
||||
};
|
||||
|
||||
function cloneConfig(value) {
|
||||
if (value === undefined) return undefined;
|
||||
return JSON.parse(JSON.stringify(value));
|
||||
}
|
||||
|
||||
function mergeConfig(defaultValue, apiValue) {
|
||||
if (apiValue === undefined || apiValue === null) {
|
||||
return cloneConfig(defaultValue);
|
||||
}
|
||||
|
||||
if (Array.isArray(apiValue)) {
|
||||
return [...apiValue];
|
||||
}
|
||||
|
||||
if (typeof apiValue !== "object" || Array.isArray(defaultValue)) {
|
||||
return apiValue;
|
||||
}
|
||||
|
||||
const merged = cloneConfig(defaultValue ?? {});
|
||||
for (const [key, value] of Object.entries(apiValue)) {
|
||||
merged[key] = mergeConfig(defaultValue?.[key], value);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
let extConfig = {
|
||||
disableVoteSubmission: false,
|
||||
disableLogging: false,
|
||||
@@ -26,33 +133,7 @@ let extConfig = {
|
||||
tooltipPercentageMode: "dash_like",
|
||||
numberDisplayReformatLikes: false,
|
||||
hidePremiumTeaser: false,
|
||||
selectors: {
|
||||
dislikeTextContainer: [],
|
||||
likeTextContainer: [],
|
||||
buttons: {
|
||||
shorts: {
|
||||
mobile: [],
|
||||
desktop: [],
|
||||
},
|
||||
regular: {
|
||||
mobile: [],
|
||||
desktopMenu: [],
|
||||
desktopNoMenu: [],
|
||||
},
|
||||
likeButton: {
|
||||
segmented: [],
|
||||
segmentedGetButtons: [],
|
||||
notSegmented: [],
|
||||
},
|
||||
dislikeButton: {
|
||||
segmented: [],
|
||||
segmentedGetButtons: [],
|
||||
notSegmented: [],
|
||||
},
|
||||
},
|
||||
menuContainer: [],
|
||||
roundedDesign: [],
|
||||
},
|
||||
selectors: cloneConfig(DEFAULT_SELECTORS),
|
||||
};
|
||||
|
||||
let storedData = {
|
||||
@@ -112,28 +193,30 @@ if (isShorts() && !shortsObserver) {
|
||||
function isLikesDisabled() {
|
||||
// return true if the like button's text doesn't contain any number
|
||||
if (isMobile()) {
|
||||
return /^\D*$/.test(getButtons().children[0].querySelector(".button-renderer-text").innerText);
|
||||
return /^\D*$/.test(querySelector(extConfig.selectors.buttons.mobileText, getButtons().children[0]).innerText);
|
||||
}
|
||||
return /^\D*$/.test(getLikeTextContainer().innerText);
|
||||
}
|
||||
|
||||
function isVideoLiked() {
|
||||
const likeButton = querySelector(extConfig.selectors.buttons.nativeButton, getLikeButton());
|
||||
if (isMobile()) {
|
||||
return getLikeButton().querySelector("button").getAttribute("aria-label") === "true";
|
||||
return likeButton.getAttribute("aria-label") === "true";
|
||||
}
|
||||
return (
|
||||
getLikeButton().classList.contains("style-default-active") ||
|
||||
getLikeButton().querySelector("button")?.getAttribute("aria-pressed") === "true"
|
||||
extConfig.selectors.activeButtonClasses.some((className) => getLikeButton().classList.contains(className)) ||
|
||||
likeButton?.getAttribute("aria-pressed") === "true"
|
||||
);
|
||||
}
|
||||
|
||||
function isVideoDisliked() {
|
||||
const dislikeButton = querySelector(extConfig.selectors.buttons.nativeButton, getDislikeButton());
|
||||
if (isMobile()) {
|
||||
return getDislikeButton().querySelector("button").getAttribute("aria-label") === "true";
|
||||
return dislikeButton.getAttribute("aria-label") === "true";
|
||||
}
|
||||
return (
|
||||
getDislikeButton().classList.contains("style-default-active") ||
|
||||
getDislikeButton().querySelector("button")?.getAttribute("aria-pressed") === "true"
|
||||
extConfig.selectors.activeButtonClasses.some((className) => getDislikeButton().classList.contains(className)) ||
|
||||
dislikeButton?.getAttribute("aria-pressed") === "true"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -162,14 +245,15 @@ function setDislikes(dislikesCount) {
|
||||
let _dislikeText;
|
||||
if (!isLikesDisabled()) {
|
||||
if (isMobile()) {
|
||||
getButtons().children[1].querySelector(".button-renderer-text").innerText = dislikesCount;
|
||||
querySelector(extConfig.selectors.buttons.mobileText, getButtons().children[1]).innerText = dislikesCount;
|
||||
return;
|
||||
}
|
||||
_dislikeText = dislikesCount;
|
||||
} else {
|
||||
console.log("likes count disabled by creator");
|
||||
if (isMobile()) {
|
||||
getButtons().children[1].querySelector(".button-renderer-text").innerText = localize("TextLikesDisabled");
|
||||
querySelector(extConfig.selectors.buttons.mobileText, getButtons().children[1]).innerText =
|
||||
localize("TextLikesDisabled");
|
||||
return;
|
||||
}
|
||||
_dislikeText = localize("TextLikesDisabled");
|
||||
@@ -188,8 +272,7 @@ function getLikeCountFromButton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let likeButton =
|
||||
getLikeButton().querySelector("yt-formatted-string#text") ?? getLikeButton().querySelector("button");
|
||||
let likeButton = querySelector(extConfig.selectors.likeCountButton, getLikeButton());
|
||||
|
||||
let likesStr = likeButton.getAttribute("aria-label").replace(/\D/g, "");
|
||||
return likesStr.length > 0 ? parseInt(likesStr) : false;
|
||||
@@ -213,8 +296,8 @@ function processResponse(response, storedData) {
|
||||
if (extConfig.coloredThumbs === true) {
|
||||
if (isShorts()) {
|
||||
// for shorts, leave deactivated buttons in default color
|
||||
let shortLikeButton = getLikeButton().querySelector("tp-yt-paper-button#button");
|
||||
let shortDislikeButton = getDislikeButton().querySelector("tp-yt-paper-button#button");
|
||||
let shortLikeButton = querySelector(extConfig.selectors.buttons.shortsToggleButton, getLikeButton());
|
||||
let shortDislikeButton = querySelector(extConfig.selectors.buttons.shortsToggleButton, getDislikeButton());
|
||||
if (shortLikeButton.getAttribute("aria-pressed") === "true") {
|
||||
shortLikeButton.style.color = getColorFromTheme(true);
|
||||
}
|
||||
@@ -228,6 +311,7 @@ function processResponse(response, storedData) {
|
||||
getDislikeButton().style.color = getColorFromTheme(false);
|
||||
}
|
||||
}
|
||||
|
||||
//Temporary disabling this - it breaks all places where getButtons()[1] is used
|
||||
// createStarRating(response.rating, isMobile());
|
||||
}
|
||||
@@ -296,7 +380,7 @@ async function initializeSelectors() {
|
||||
.catch((error) => {
|
||||
console.error("Error fetching selectors:", error);
|
||||
});
|
||||
extConfig.selectors = result ?? extConfig.selectors;
|
||||
extConfig.selectors = mergeConfig(DEFAULT_SELECTORS, result);
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { extConfig, isShorts } from "./state";
|
||||
|
||||
const DEFAULT_SHORTS_LOADED_SELECTORS = {
|
||||
containers: [".reel-video-in-sequence-new"],
|
||||
thumbnail: [".reel-video-in-sequence-thumbnail"],
|
||||
renderer: ["ytd-reel-video-renderer"],
|
||||
overlay: ["#experiment-overlay"],
|
||||
};
|
||||
|
||||
const DEFAULT_VIDEO_LOADED_SELECTORS = [
|
||||
"ytd-watch-grid[video-id='{videoId}']",
|
||||
"ytd-watch-flexy[video-id='{videoId}']",
|
||||
'#player[loading="false"]:not([hidden])',
|
||||
];
|
||||
|
||||
function numberFormat(numberState) {
|
||||
return getNumberFormatter(extConfig.numberDisplayFormat).format(numberState);
|
||||
}
|
||||
@@ -113,20 +126,22 @@ function isInViewport(element) {
|
||||
function isShortsLoaded(videoId) {
|
||||
if (!videoId) return false;
|
||||
|
||||
const selectors = extConfig.selectors.shortsLoaded ?? DEFAULT_SHORTS_LOADED_SELECTORS;
|
||||
|
||||
// Find all reel containers
|
||||
const reelContainers = document.querySelectorAll(".reel-video-in-sequence-new");
|
||||
const reelContainers = querySelectorAll(selectors.containers);
|
||||
|
||||
for (const container of reelContainers) {
|
||||
// Check if this container's thumbnail matches our video ID
|
||||
const thumbnail = container.querySelector(".reel-video-in-sequence-thumbnail");
|
||||
const thumbnail = querySelector(selectors.thumbnail, container);
|
||||
if (thumbnail) {
|
||||
const bgImage = thumbnail.style.backgroundImage;
|
||||
// YouTube thumbnail URLs contain the video ID in the format: /vi/VIDEO_ID/
|
||||
if ((bgImage && bgImage.includes(`/${videoId}/`)) || (!bgImage && isInViewport(container))) {
|
||||
// Check if this container has the renderer with visible experiment-overlay
|
||||
const renderer = container.querySelector("ytd-reel-video-renderer");
|
||||
const renderer = querySelector(selectors.renderer, container);
|
||||
if (renderer) {
|
||||
const experimentOverlay = renderer.querySelector("#experiment-overlay");
|
||||
const experimentOverlay = querySelector(selectors.overlay, renderer);
|
||||
if (
|
||||
experimentOverlay &&
|
||||
!experimentOverlay.hidden &&
|
||||
@@ -151,15 +166,10 @@ function isVideoLoaded() {
|
||||
return isShortsLoaded(videoId);
|
||||
}
|
||||
|
||||
const videoLoadedSelectors = extConfig.selectors.videoLoaded ?? DEFAULT_VIDEO_LOADED_SELECTORS;
|
||||
|
||||
// Regular video checks
|
||||
return (
|
||||
// desktop: spring 2024 UI
|
||||
document.querySelector(`ytd-watch-grid[video-id='${videoId}']`) !== null ||
|
||||
// desktop: older UI
|
||||
document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null ||
|
||||
// mobile: no video-id attribute
|
||||
document.querySelector('#player[loading="false"]:not([hidden])') !== null
|
||||
);
|
||||
return querySelector(videoLoadedSelectors.map((selector) => selector.replace("{videoId}", videoId))) !== undefined;
|
||||
}
|
||||
|
||||
const originalConsole = {
|
||||
@@ -210,7 +220,8 @@ function getColorFromTheme(voteIsLike) {
|
||||
|
||||
function querySelector(selectors, element) {
|
||||
let result;
|
||||
for (const selector of selectors) {
|
||||
for (const selector of Array.isArray(selectors) ? selectors : [selectors]) {
|
||||
if (!selector) continue;
|
||||
result = (element ?? document).querySelector(selector);
|
||||
if (result !== null) {
|
||||
return result;
|
||||
@@ -220,13 +231,14 @@ function querySelector(selectors, element) {
|
||||
|
||||
function querySelectorAll(selectors) {
|
||||
let result;
|
||||
for (const selector of selectors) {
|
||||
for (const selector of Array.isArray(selectors) ? selectors : [selectors]) {
|
||||
if (!selector) continue;
|
||||
result = document.querySelectorAll(selector);
|
||||
if (result.length !== 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return result ?? document.querySelectorAll("__ryd-missing-selector__");
|
||||
}
|
||||
|
||||
function createObserver(options, callback) {
|
||||
|
||||
Reference in New Issue
Block a user