feat(library): liste « À regarder plus tard » (watch later)
CI / build-and-test (push) Successful in 14m55s

Nouvelle liste dans la section Vous de la barre latérale, avec un bouton
horloge a cote du coeur sur toutes les cartes (accueil, themes, provider-
themes, recherche).

- db : les 4 fonctions de listes (like/unlike/isLiked/list) prennent un
  paramètre `tag` — la table `tags` sert déjà de mot-clés génériques, donc
  zéro migration.
- api : les 4 routes likes sont générées par registerVideoTagRoutes(prefix,
  tagName) ; /api/user/watch-later = même fabrique, même enrichissement
  yt-dlp, même middleware cookie-aware. Entrée OpenAPI ajoutée.
- front : LikesService prend un VideoTag, like-button un @Input() list
  (icône horloge, ambre quand enregistré, libellés a11y dédiés) — un seul
  composant pour les deux boutons, pas de copie.
- cartes de recherche : la grille partagée n'avait AUCUN bouton ; cœur +
  horloge ajoutés, masqués sur les lives et cartes chaîne (coin bas-gauche
  déjà occupé par le compteur de spectateurs / pas des vidéos).
- route /library/watch-later = LikedComponent via data.list (titre, icône,
  placeholder et état vide pilotés par la route), clés nav.watchLater FR/EN.

Tests : npm run test:api 41/41 (nouveau test watch-later : ajout, statut,
indépendance vis-à-vis des likes, liste, retrait, 400, 401) + 21 autres
suites vertes. Build OK, instance locale 4200 rebuildée. Sondage Playwright
desktop+mobile : 12/14 (2 faux positifs du sondage : accordéon replié sur
une route hors /library, 3×401 console pendant la phase déconnectée).
This commit is contained in:
2026-10-01 14:13:43 -04:00
parent 88a2d21133
commit 735255b7d8
16 changed files with 246 additions and 114 deletions
@@ -42,6 +42,13 @@
<span *ngIf="showClipBadge" class="rounded bg-orange-600 px-1.5 py-0.5 text-[11px] font-bold text-white">CLIP</span>
<span *ngIf="showChannelBadge" class="rounded bg-slate-700/90 px-1.5 py-0.5 text-[11px] font-bold text-slate-200 ring-1 ring-slate-500/50">CHAÎNE</span>
</div>
<!-- Like + « à regarder plus tard », sur la miniature comme sur les
cartes de thème. Masqués pour un live (badge LIVE en haut à gauche)
et une carte chaîne : ce ne sont pas des vidéos. -->
<div *ngIf="!showChannelBadge && !showLiveBadge" class="absolute bottom-1 left-1 flex items-center gap-1">
<app-like-button [videoId]="video.id" [provider]="video.provider" [title]="video.title" [thumbnail]="video.thumbnailUrl"></app-like-button>
<app-like-button [list]="'watch-later'" [videoId]="video.id" [provider]="video.provider" [title]="video.title" [thumbnail]="video.thumbnailUrl"></app-like-button>
</div>
<span *ngIf="showDuration" class="absolute bottom-1 right-1 rounded bg-black/80 px-1.5 py-0.5 text-xs text-white">
{{ video.durationSec | duration }}
</span>
@@ -6,6 +6,7 @@ import { DurationPipe } from '../../pipes/duration.pipe';
import { ProviderBadgeComponent } from '../provider-badge/provider-badge.component';
import { SubscribeButtonComponent } from '../subscribe-button/subscribe-button.component';
import { ChannelIdentityComponent } from '../channel-identity/channel-identity.component';
import { LikeButtonComponent } from '../../../../components/shared/components/like-button/like-button.component';
import type { ChannelMeta, ProviderId } from '../../models/channel.model';
import { toShortProviderId } from '../../providers/provider-ids';
import { isLiveVideoItem, isShortVideoItem, isVerticalVideoItem, videoAspectRatio, verticalFrameWidthPercent } from '../../utils/video-kind';
@@ -16,7 +17,7 @@ import { capturedAgoLabel, sourceLabel } from '../../../search/adapters/provenan
templateUrl: './video-card.component.html',
styleUrls: ['./video-card.component.scss'],
standalone: true,
imports: [CommonModule, RouterLink, DurationPipe, ProviderBadgeComponent, SubscribeButtonComponent, ChannelIdentityComponent],
imports: [CommonModule, RouterLink, DurationPipe, ProviderBadgeComponent, SubscribeButtonComponent, ChannelIdentityComponent, LikeButtonComponent],
})
export class VideoCardComponent implements OnChanges {
@Input() video!: VideoItem;