feat(channel): page details chaine multi-providers avec onglets et infinite scroll
CI / build-and-test (push) Successful in 13m34s

- Strategy Pattern front: ChannelProviderInterface + 6 providers (yt/dm/tw/pt/od/ru) + factory (ids longs/courts)
- ChannelContentService: cache par onglet/tri/recherche (TTL 5min), pagination nextPage
- ChannelPage: header (banniere/avatar/subs/description ...plus), tabs dynamiques par capabilities, grilles videos/shorts 9:16/playlists/live, recherche debounced, tri recent/populaire, infinite scroll IntersectionObserver, skeletons/empty/error
- Backend: GET /api/channels/:provider/:id/content (videos/shorts/playlists/live) natif par provider
This commit is contained in:
2026-09-25 10:15:12 -04:00
parent fe35340948
commit 97e5de74f5
147 changed files with 7954 additions and 77497 deletions
@@ -1,9 +1,19 @@
import { Component, Input } from '@angular/core';
import { Component, Input, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import type { ProviderId } from '../../app/core/providers/provider-registry';
import { PROVIDERS, PROVIDER_MAP, type ProviderId } from '../../app/core/providers/provider-registry';
import type { SuggestionItem } from '../../app/search/search.service';
interface SuggestionSection {
id: ProviderId;
name: string;
shortLabel: string;
colorClass: string;
items: SuggestionItem[];
supported: boolean;
deepLinkProviders: string;
}
@Component({
selector: 'app-search-suggestions',
standalone: true,
@@ -15,4 +25,26 @@ export class SearchSuggestionsComponent {
yt: [], dm: [], tw: [], pt: [], od: [], ru: []
} as any;
@Input() query: string = '';
// Providers that were actually requested; drives which sections render and
// which unsupported providers get an explicit row (Step 6)
@Input() activeProviders: ProviderId[] | 'all' = 'all';
readonly sections = computed<SuggestionSection[]>(() => {
const groups = this.groups || ({} as Record<ProviderId, SuggestionItem[]>);
const active = this.activeProviders === 'all'
? PROVIDERS.map(p => p.id)
: (this.activeProviders || []).filter(id => PROVIDER_MAP.has(id));
return active.map(id => {
const spec = PROVIDER_MAP.get(id)!;
return {
id,
name: spec.displayName,
shortLabel: spec.shortLabel,
colorClass: spec.colorClass,
items: Array.isArray(groups[id]) ? groups[id] : [],
supported: !!spec.supports?.search,
deepLinkProviders: id,
} as SuggestionSection;
});
});
}