feat(channel): identite chaine partagee cliquable partout + logos fiabilises
CI / build-and-test (push) Successful in 13m25s
CI / build-and-test (push) Successful in 13m25s
- Nouveau app-channel-identity (avatar+nom -> /channel, fallback resolve /api/channels, initiales sinon) - ProviderBadge: accepte les ids courts yt/dm/tw/pt/od/ru - Search adapters yt/dm/pt/od: conservent channelExternalId/channelAvatarUrl (pt derive instance|channel) - video-card/themes/watch/related/suggestions/subscriptions/channel-header uniformises sur l'identite partagee
This commit is contained in:
@@ -19,9 +19,15 @@
|
||||
@if (item.thumbnail) {
|
||||
<img [src]="item.thumbnail" class="h-12 w-20 object-cover rounded" alt=""/>
|
||||
}
|
||||
<div class="min-w-0">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-slate-100 truncate">{{ item.title }}</div>
|
||||
<div class="text-slate-400 text-xs">{{ item.uploaderName }}</div>
|
||||
<app-channel-identity
|
||||
size="sm"
|
||||
[provider]="s.id"
|
||||
[externalId]="channelExternalId(s, item)"
|
||||
[avatarUrl]="channelAvatar(item)"
|
||||
[name]="item.uploaderName || null"
|
||||
></app-channel-identity>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { PROVIDERS, PROVIDER_MAP, type ProviderId } from '../../app/core/providers/provider-registry';
|
||||
import type { SuggestionItem } from '../../app/search/search.service';
|
||||
import { ChannelIdentityComponent } from '../../app/shared/components/channel-identity/channel-identity.component';
|
||||
|
||||
interface SuggestionSection {
|
||||
id: ProviderId;
|
||||
@@ -17,7 +18,7 @@ interface SuggestionSection {
|
||||
@Component({
|
||||
selector: 'app-search-suggestions',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink],
|
||||
imports: [CommonModule, RouterLink, ChannelIdentityComponent],
|
||||
templateUrl: './search-suggestions.component.html'
|
||||
})
|
||||
export class SearchSuggestionsComponent {
|
||||
@@ -29,8 +30,7 @@ export class SearchSuggestionsComponent {
|
||||
// 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[]>);
|
||||
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));
|
||||
@@ -47,4 +47,30 @@ export class SearchSuggestionsComponent {
|
||||
} as SuggestionSection;
|
||||
});
|
||||
});
|
||||
|
||||
/** ExternalId chaîne pour le lien /channel (null = non cliquable). */
|
||||
channelExternalId(section: { id: ProviderId }, item: SuggestionItem): string | null {
|
||||
const raw = item as unknown as Record<string, unknown>;
|
||||
if (section.id === 'pt') {
|
||||
// PeerTube : `instance|channel`, instance dérivée de l'URL vidéo.
|
||||
try {
|
||||
const url = String(raw?.['url'] || '');
|
||||
const host = url ? new URL(url).hostname : '';
|
||||
const channel = String(raw?.['channelId'] || '');
|
||||
if (host && channel) return `${host}|${channel}`;
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
if (section.id === 'od') {
|
||||
const name = String(raw?.['uploaderName'] || '');
|
||||
return name || null;
|
||||
}
|
||||
const ext = String(raw?.['channelExternalId'] || raw?.['channelId'] || '');
|
||||
return ext || null;
|
||||
}
|
||||
|
||||
channelAvatar(item: SuggestionItem): string | null {
|
||||
const raw = item as unknown as Record<string, unknown>;
|
||||
return String(raw?.['uploaderAvatar'] || '') || null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user