feat(channels): dedicated channel page + ephemeral explore UI
Frontend for the channel-explore feature: - ChannelPage: banner/avatar/stats header, Subscribe/unsubscribe, an "exploring" badge while browsing an un-subscribed channel, Videos/About tabs. Reuses Feed scoped to the channel (scope=all + source=all so the per-user ephemeral videos show). Auto-ingests recent uploads on first visit (background, with a loading note) + "Load more from YouTube" to page deeper; skipped for demo / already-subscribed channels. - App: openChannel/closeChannel as a Back-aware sub-view (history.state._chan, mirrors the YT-search _yt pattern); ChannelPage takes over the content column, nav rail stays. - ChannelLink/cards/player: the channel name now opens our channel page (onChannelFilter → onOpenChannel); the in-card "only this channel" filter button is dropped (the page subsumes it). PlayerModal channel-name wiring follows in the next commit. - api: channelDetail + exploreChannel; ChannelDetail/ExploreResult types. - i18n EN/HU/DE: channel namespace, explore_cleanup scheduler labels, explore config group, channels_explore quota label.
This commit is contained in:
parent
bc4c362423
commit
cc1e670202
18 changed files with 744 additions and 120 deletions
20
frontend/src/i18n/locales/de/channel.json
Normal file
20
frontend/src/i18n/locales/de/channel.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"back": "Zurück",
|
||||
"exploringBadge": "Erkunden",
|
||||
"subscribe": "Abonnieren",
|
||||
"subscribedState": "Abonniert",
|
||||
"unsubscribe": "Abbestellen",
|
||||
"unsubTitle": "Abbestellen?",
|
||||
"unsubBody": "{{name}} auf YouTube nicht mehr folgen?",
|
||||
"subscribed": "{{name}} abonniert",
|
||||
"subscribers": "{{formatted}} Abonnenten",
|
||||
"videoCount_one": "{{count}} Video",
|
||||
"videoCount_other": "{{count}} Videos",
|
||||
"totalViews": "{{formatted}} Aufrufe",
|
||||
"joined": "Beigetreten {{date}}",
|
||||
"loadingVideos": "Videos dieses Kanals werden geladen…",
|
||||
"tabVideos": "Videos",
|
||||
"tabAbout": "Info",
|
||||
"loadMore": "Mehr von YouTube laden",
|
||||
"noDescription": "Dieser Kanal hat keine Beschreibung."
|
||||
}
|
||||
|
|
@ -9,29 +9,94 @@
|
|||
"quota": "Kontingent",
|
||||
"backfill": "Nachladen (Backfill)",
|
||||
"shorts": "Shorts-Prüfung",
|
||||
"batch": "Stapelgrößen"
|
||||
"batch": "Stapelgrößen",
|
||||
"explore": "Kanal erkunden"
|
||||
},
|
||||
"fields": {
|
||||
"smtp_host": { "label": "SMTP-Host", "hint": "z. B. smtp.gmail.com" },
|
||||
"smtp_port": { "label": "SMTP-Port", "hint": "Üblicherweise 587 (STARTTLS)." },
|
||||
"smtp_user": { "label": "SMTP-Benutzername", "hint": "Das sendende Konto / der Login." },
|
||||
"smtp_from": { "label": "Absenderadresse", "hint": "z. B. Siftlode <addr@gmail.com>. Standard ist der Benutzername." },
|
||||
"smtp_password": { "label": "SMTP-Passwort", "hint": "App-Passwort. Verschlüsselt gespeichert; nur schreibbar — wird nie wieder angezeigt." },
|
||||
"quota_daily_budget": { "label": "Tägliches Kontingentbudget", "hint": "YouTube-Data-API-Einheiten pro Tag (kostenloses Limit 10.000). Standard 9000 lässt Puffer." },
|
||||
"backfill_quota_reserve": { "label": "Nachlade-Kontingentreserve", "hint": "Reservierte Einheiten, damit geplantes Nachladen interaktive Syncs / Anreicherung nie aushungert." },
|
||||
"search_daily_limit_per_user": { "label": "Live-Suche — Tageslimit pro Nutzer", "hint": "Maximale Live-YouTube-Suchen pro Nutzer und Tag. Bei der API-Quelle kostet jede Suche 100 Einheiten (das gemeinsame Budget reicht also für insgesamt nur ~80-90/Tag); bei der Scrape-Quelle ist es ein reines Missbrauchslimit. 0 = Live-Suche deaktiviert." },
|
||||
"search_source": { "label": "Quelle der Live-Suche", "hint": "Woher die Ergebnisse der Live-YouTube-Suche stammen. \"scrape\" nutzt YouTubes internen Endpunkt und verbraucht kein API-Kontingent (empfohlen). \"api\" nutzt das offizielle search.list (100 Einheiten/Seite). Auf \"api\" umstellen, falls das Scraping ausfällt oder blockiert wird." },
|
||||
"backfill_recent_max_videos": { "label": "Aktuelles Nachladen — max. Videos", "hint": "Neueste Videos pro Kanal beim ersten Durchlauf." },
|
||||
"backfill_recent_max_days": { "label": "Aktuelles Nachladen — max. Alter (Tage)", "hint": "Wie weit der erste Durchlauf pro Kanal zurückreicht." },
|
||||
"shorts_probe_max_seconds": { "label": "Shorts-Prüfung — max. Dauer (s)", "hint": "Nur Videos bis zu dieser Länge werden als mögliche Shorts geprüft." },
|
||||
"shorts_probe_batch": { "label": "Shorts-Prüfung — Stapelgröße", "hint": "Wie viele Videos pro Lauf klassifiziert werden." },
|
||||
"enrich_batch_size": { "label": "Anreicherungs-Stapelgröße", "hint": "videos.list-IDs pro Aufruf (YouTube begrenzt auf 50)." },
|
||||
"autotag_title_sample": { "label": "Auto-Tag-Titelstichprobe", "hint": "Pro Kanal abgetastete aktuelle Videotitel zur Spracherkennung." },
|
||||
"youtube_api_key": { "label": "YouTube-API-Schlüssel", "hint": "Optional. Für öffentliche Lesezugriffe (Kanäle/Videos), damit gemeinsames Nachladen nicht vom OAuth eines Nutzers abhängt. Verschlüsselt gespeichert; nur schreibbar." },
|
||||
"youtube_api_proxy": { "label": "YouTube-API-Egress-Proxy", "hint": "Optional. HTTP(S)-Proxy-URL für ausgehende YouTube-API-Aufrufe — über einen Host mit fester IP leiten, damit ein IP-beschränkter Schlüssel von einem Server mit dynamischer IP funktioniert. Leer = direkt." },
|
||||
"google_client_id": { "label": "Google-Client-ID", "hint": "OAuth-2.0-Client-ID für die Google-Anmeldung. Verschlüsselt gespeichert; nur schreibbar. Beide Google-Felder leer lassen, um die Google-Anmeldung zu deaktivieren (E-Mail+Passwort funktioniert weiter)." },
|
||||
"google_client_secret": { "label": "Google-Client-Secret", "hint": "OAuth-2.0-Client-Secret. Verschlüsselt gespeichert; nur schreibbar." },
|
||||
"allow_registration": { "label": "Registrierung erlauben", "hint": "Wenn aktiviert, kann jeder eine E-Mail+Passwort-Registrierung einreichen. Für die Anmeldung sind weiterhin E-Mail-Bestätigung und Admin-Freigabe nötig." }
|
||||
"smtp_host": {
|
||||
"label": "SMTP-Host",
|
||||
"hint": "z. B. smtp.gmail.com"
|
||||
},
|
||||
"smtp_port": {
|
||||
"label": "SMTP-Port",
|
||||
"hint": "Üblicherweise 587 (STARTTLS)."
|
||||
},
|
||||
"smtp_user": {
|
||||
"label": "SMTP-Benutzername",
|
||||
"hint": "Das sendende Konto / der Login."
|
||||
},
|
||||
"smtp_from": {
|
||||
"label": "Absenderadresse",
|
||||
"hint": "z. B. Siftlode <addr@gmail.com>. Standard ist der Benutzername."
|
||||
},
|
||||
"smtp_password": {
|
||||
"label": "SMTP-Passwort",
|
||||
"hint": "App-Passwort. Verschlüsselt gespeichert; nur schreibbar — wird nie wieder angezeigt."
|
||||
},
|
||||
"quota_daily_budget": {
|
||||
"label": "Tägliches Kontingentbudget",
|
||||
"hint": "YouTube-Data-API-Einheiten pro Tag (kostenloses Limit 10.000). Standard 9000 lässt Puffer."
|
||||
},
|
||||
"backfill_quota_reserve": {
|
||||
"label": "Nachlade-Kontingentreserve",
|
||||
"hint": "Reservierte Einheiten, damit geplantes Nachladen interaktive Syncs / Anreicherung nie aushungert."
|
||||
},
|
||||
"search_daily_limit_per_user": {
|
||||
"label": "Live-Suche — Tageslimit pro Nutzer",
|
||||
"hint": "Maximale Live-YouTube-Suchen pro Nutzer und Tag. Bei der API-Quelle kostet jede Suche 100 Einheiten (das gemeinsame Budget reicht also für insgesamt nur ~80-90/Tag); bei der Scrape-Quelle ist es ein reines Missbrauchslimit. 0 = Live-Suche deaktiviert."
|
||||
},
|
||||
"search_source": {
|
||||
"label": "Quelle der Live-Suche",
|
||||
"hint": "Woher die Ergebnisse der Live-YouTube-Suche stammen. \"scrape\" nutzt YouTubes internen Endpunkt und verbraucht kein API-Kontingent (empfohlen). \"api\" nutzt das offizielle search.list (100 Einheiten/Seite). Auf \"api\" umstellen, falls das Scraping ausfällt oder blockiert wird."
|
||||
},
|
||||
"backfill_recent_max_videos": {
|
||||
"label": "Aktuelles Nachladen — max. Videos",
|
||||
"hint": "Neueste Videos pro Kanal beim ersten Durchlauf."
|
||||
},
|
||||
"backfill_recent_max_days": {
|
||||
"label": "Aktuelles Nachladen — max. Alter (Tage)",
|
||||
"hint": "Wie weit der erste Durchlauf pro Kanal zurückreicht."
|
||||
},
|
||||
"shorts_probe_max_seconds": {
|
||||
"label": "Shorts-Prüfung — max. Dauer (s)",
|
||||
"hint": "Nur Videos bis zu dieser Länge werden als mögliche Shorts geprüft."
|
||||
},
|
||||
"shorts_probe_batch": {
|
||||
"label": "Shorts-Prüfung — Stapelgröße",
|
||||
"hint": "Wie viele Videos pro Lauf klassifiziert werden."
|
||||
},
|
||||
"enrich_batch_size": {
|
||||
"label": "Anreicherungs-Stapelgröße",
|
||||
"hint": "videos.list-IDs pro Aufruf (YouTube begrenzt auf 50)."
|
||||
},
|
||||
"autotag_title_sample": {
|
||||
"label": "Auto-Tag-Titelstichprobe",
|
||||
"hint": "Pro Kanal abgetastete aktuelle Videotitel zur Spracherkennung."
|
||||
},
|
||||
"youtube_api_key": {
|
||||
"label": "YouTube-API-Schlüssel",
|
||||
"hint": "Optional. Für öffentliche Lesezugriffe (Kanäle/Videos), damit gemeinsames Nachladen nicht vom OAuth eines Nutzers abhängt. Verschlüsselt gespeichert; nur schreibbar."
|
||||
},
|
||||
"youtube_api_proxy": {
|
||||
"label": "YouTube-API-Egress-Proxy",
|
||||
"hint": "Optional. HTTP(S)-Proxy-URL für ausgehende YouTube-API-Aufrufe — über einen Host mit fester IP leiten, damit ein IP-beschränkter Schlüssel von einem Server mit dynamischer IP funktioniert. Leer = direkt."
|
||||
},
|
||||
"google_client_id": {
|
||||
"label": "Google-Client-ID",
|
||||
"hint": "OAuth-2.0-Client-ID für die Google-Anmeldung. Verschlüsselt gespeichert; nur schreibbar. Beide Google-Felder leer lassen, um die Google-Anmeldung zu deaktivieren (E-Mail+Passwort funktioniert weiter)."
|
||||
},
|
||||
"google_client_secret": {
|
||||
"label": "Google-Client-Secret",
|
||||
"hint": "OAuth-2.0-Client-Secret. Verschlüsselt gespeichert; nur schreibbar."
|
||||
},
|
||||
"allow_registration": {
|
||||
"label": "Registrierung erlauben",
|
||||
"hint": "Wenn aktiviert, kann jeder eine E-Mail+Passwort-Registrierung einreichen. Für die Anmeldung sind weiterhin E-Mail-Bestätigung und Admin-Freigabe nötig."
|
||||
},
|
||||
"explore_grace_days": {
|
||||
"label": "Erkunden — Karenzzeit (Tage)",
|
||||
"hint": "Wie lange ein erkundeter, nicht abonnierter Kanal (und seine flüchtigen Videos) behalten wird, bevor die Aufräumaufgabe ihn entfernt. Ein Abo behält ihn dauerhaft."
|
||||
}
|
||||
},
|
||||
"save": "Speichern",
|
||||
"saving": "Speichern…",
|
||||
|
|
|
|||
|
|
@ -13,5 +13,6 @@
|
|||
"channels_subscribe": "Abonnieren",
|
||||
"channels_unsubscribe": "Abo beenden",
|
||||
"maintenance_revalidate": "Wartungs-Neuvalidierung",
|
||||
"other": "Sonstiges"
|
||||
"other": "Sonstiges",
|
||||
"channels_explore": "Kanal erkunden"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@
|
|||
"shorts": "Prüft youtube.com/shorts, um Shorts zu markieren. Fällt er aus, werden Shorts im Feed nicht von normalen Videos getrennt.",
|
||||
"subscriptions": "Importiert die YouTube-Abos jedes Nutzers neu. Fällt er aus, werden auf YouTube hinzugefügte oder entfernte Abos hier nicht übernommen.",
|
||||
"playlist_sync": "Spiegelt die YouTube-Wiedergabelisten jedes Nutzers in die App. Fällt er aus, erscheinen Änderungen an deinen YouTube-Listen lokal nicht.",
|
||||
"maintenance": "Findet Videos, die nicht mehr abspielbar sind (gelöscht, auf privat gesetzt, abgebrochene Premieren), blendet sie aus dem Feed aus und entfernt sie nach einer Schonfrist; prüft die am längsten nicht geprüften Videos erneut. Fällt er aus, bleiben tote Videos im Katalog."
|
||||
"maintenance": "Findet Videos, die nicht mehr abspielbar sind (gelöscht, auf privat gesetzt, abgebrochene Premieren), blendet sie aus dem Feed aus und entfernt sie nach einer Schonfrist; prüft die am längsten nicht geprüften Videos erneut. Fällt er aus, bleiben tote Videos im Katalog.",
|
||||
"explore_cleanup": "Entfernt Kanäle, die du erkundet, aber nie abonniert hast (und ihre Videos), nach einer Karenzzeit, damit Neugier-Stöbern den Katalog nicht überfüllt. Wenn es stoppt, bleiben verlassene erkundete Kanäle bestehen."
|
||||
},
|
||||
"jobs": {
|
||||
"rss_poll": "RSS-Abfrage (neue Uploads)",
|
||||
|
|
@ -77,7 +78,8 @@
|
|||
"subscriptions": "Abo-Resync",
|
||||
"playlist_sync": "YouTube-Wiedergabelisten-Sync",
|
||||
"maintenance": "Wartung + Validierung",
|
||||
"demo_reset": "Demo-Konto-Reset"
|
||||
"demo_reset": "Demo-Konto-Reset",
|
||||
"explore_cleanup": "Bereinigung erkundeter Kanäle"
|
||||
},
|
||||
"queue": {
|
||||
"recentPending": "Zu synchronisierende Kanäle",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue