fix: feedback round 2 — language, subscriptions, feed scope, UX

- Language detection: classify one cleaned+concatenated blob (strip emoji,
  @mentions, #tags, numbers, punctuation); fixes caps/emoji-heavy channels
  (e.g. Nessaj -> Hungarian, no more bogus Chinese/Korean)
- Feed now joins the user's subscriptions, so unsubscribing on YouTube removes a
  channel from the feed; periodic subscription re-sync job picks up changes
- Watched/Saved/Hidden views ignore the Shorts/live default-hiding so the full
  set is visible (fixes hidden videos missing from the Hidden view)
- Persist feed filters + search across reloads (localStorage)
- 3D polish: cards lift with shadow on hover; chips/buttons get depth and a
  press effect; undo toast lasts longer
This commit is contained in:
npeter83 2026-06-11 03:28:45 +02:00
parent 8c245e986f
commit e07a37622d
9 changed files with 97 additions and 27 deletions

View file

@ -6,7 +6,7 @@ from sqlalchemy.orm import Session, aliased
from app.auth import current_user
from app.db import get_db
from app.models import Channel, ChannelTag, Tag, User, Video, VideoState
from app.models import Channel, ChannelTag, Subscription, Tag, User, Video, VideoState
router = APIRouter(prefix="/api", tags=["feed"])
@ -78,14 +78,26 @@ def get_feed(
status_expr,
)
.join(Channel, Channel.id == Video.channel_id)
# Only channels this user is subscribed to (and hasn't hidden).
.join(
Subscription,
and_(
Subscription.channel_id == Video.channel_id,
Subscription.user_id == user.id,
Subscription.hidden.is_(False),
),
)
.outerjoin(
state, and_(state.video_id == Video.id, state.user_id == user.id)
)
)
if not include_shorts:
# In the explicit Watched/Saved/Hidden views, show the complete set regardless of
# the Shorts / live default-hiding (so e.g. a hidden Short still shows up there).
explicit_view = show in ("watched", "saved", "hidden")
if not include_shorts and not explicit_view:
query = query.where(Video.is_short.is_(False))
if not include_live:
if not include_live and not explicit_view:
query = query.where(Video.live_status.notin_(HIDDEN_LIVE))
if channel_id:
query = query.where(Video.channel_id == channel_id)