- {{ notice() }}
+
+
+ {{ notice() }}
}
@if (!hasQuery()) {
-
{{ 'search.hint' | t }}
+
{{ 'search.hint' | t }}
}
@if (hasQuery()) {
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
Sources
-
@if (filtersOpen()) {
-
+
@if (activeFilterChips().length > 0) {
-
-
Actifs
+
+
Actifs
@for (chip of activeFilterChips(); track chip.key) {
}
-
- {{ filteredResults().length }} / {{ scopedVideos().length }} résultats
+
+ {{ countLabel(filteredResults().length) }} / {{ countLabel(scopedVideos().length) }}
}
-
-
+
+
+
+
+
-
-
+
+
+
+
+
-
-
+
+
+
+
+
-
-
+
+
+
+
+
-
-
-
-
- {{ 'filters.serverHint' | t: 'Type, période et durée sont appliqués par les sources ; la langue filtre les résultats affichés.' }}
-
+
+
+
+
+
+
+
+ {{ 'filters.serverHint' | t: 'Type, période et durée sont appliqués par les sources ; la langue filtre les résultats affichés.' }}
+
}
- @if (error()) {
-
- {{ error() }}
-
-
- }
+ @if (error()) {
+
+
+
+ {{ error() }}
+
+
+
+ }
diff --git a/src/components/search/search.component.ts b/src/components/search/search.component.ts
index 482bc2c..19aec8a 100644
--- a/src/components/search/search.component.ts
+++ b/src/components/search/search.component.ts
@@ -30,6 +30,29 @@ const LOADING_TIMEOUT_MS = 20_000;
const STORAGE_HIDDEN_PROVIDERS = 'newtube:search.hiddenProviders';
const STORAGE_FILTERS_OPEN = 'newtube:search.filtersOpen';
+/**
+ * Couleurs de marque, portées par un simple point dans la pastille. Le fond et
+ * le texte du bouton restent neutres : l'en-tête suit alors le thème (clair
+ * compris) au lieu d'imposer des teintes qui ne contrastent plus.
+ */
+const PROVIDER_DOTS: Record
= {
+ yt: '#ef4444',
+ dm: '#3b82f6',
+ tw: '#a855f7',
+ pt: '#22c55e',
+ od: '#eab308',
+ ru: '#f97316',
+};
+
+/** 1 284 -> « 1.3K » : garde les pastilles étroites sans perdre l'info. */
+function formatCount(n: number): string {
+ const v = Number(n);
+ if (!Number.isFinite(v) || v <= 0) return '0';
+ if (v < 1000) return String(v);
+ if (v < 1_000_000) return `${(v / 1000).toFixed(v < 10_000 ? 1 : 0).replace('.0', '')}K`;
+ return `${(v / 1_000_000).toFixed(1).replace('.0', '')}M`;
+}
+
@Component({
selector: 'app-search',
standalone: true,
@@ -221,16 +244,24 @@ export class SearchComponent {
const hidden = this.hiddenProviders();
return this.activeProviders().map(id => {
const spec = PROVIDERS.find(p => p.id === id);
+ const count = counts.get(id) || 0;
return {
id,
shortLabel: spec?.shortLabel || id.toUpperCase(),
displayName: spec?.displayName || id,
- count: counts.get(id) || 0,
+ // Couleur de marque portée par le point, pas par le fond du bouton :
+ // les fonds neutres restent lisibles dans tous les thèmes (clair inclus).
+ dot: PROVIDER_DOTS[id] || 'rgb(148, 163, 184)',
+ count,
+ countLabel: formatCount(count),
visible: !hidden.includes(id),
};
});
});
+ /** Nombre de sources masquées (alimente le libellé « Tout afficher »). */
+ hiddenProviderCount = computed(() => this.hiddenProviders().length);
+
/** Vrai quand toutes les sources connues sont déjà interrogées. */
allSourcesActive = computed(() => this.activeProviders().length >= PROVIDERS.length);
@@ -246,37 +277,15 @@ export class SearchComponent {
});
}
- /** Style de pastille : couleurs de la marque quand visible, neutre quand masquée. */
- providerChipStyle(id: ProviderId, visible: boolean): { [key: string]: string } {
- if (!visible) {
- return {
- backgroundColor: 'transparent',
- color: 'rgb(113, 113, 122)',
- borderColor: 'rgb(63, 63, 70)',
- };
- }
- return this.getProviderColors(this.providerIdMap[id]);
- }
-
- // Brand colors for provider badge (inline styles to match History badges)
- getProviderColors(providerId: string | null | undefined): { [key: string]: string } {
- const id = (providerId || '').toLowerCase();
- switch (id) {
- case 'youtube':
- return { backgroundColor: 'rgba(220, 38, 38, 0.15)', color: 'rgb(248, 113, 113)', borderColor: 'rgba(239, 68, 68, 0.3)' };
- case 'dailymotion':
- return { backgroundColor: 'rgba(37, 99, 235, 0.15)', color: 'rgb(147, 197, 253)', borderColor: 'rgba(59, 130, 246, 0.3)' };
- case 'peertube':
- return { backgroundColor: 'rgba(245, 158, 11, 0.15)', color: 'rgb(252, 211, 77)', borderColor: 'rgba(245, 158, 11, 0.3)' };
- case 'rumble':
- return { backgroundColor: 'rgba(22, 163, 74, 0.15)', color: 'rgb(134, 239, 172)', borderColor: 'rgba(34, 197, 94, 0.3)' };
- case 'twitch':
- return { backgroundColor: 'rgba(168, 85, 247, 0.15)', color: 'rgb(216, 180, 254)', borderColor: 'rgba(168, 85, 247, 0.3)' };
- case 'odysee':
- return { backgroundColor: 'rgba(236, 72, 153, 0.15)', color: 'rgb(251, 207, 232)', borderColor: 'rgba(236, 72, 153, 0.3)' };
- default:
- return { backgroundColor: 'rgba(30, 41, 59, 0.8)', color: 'rgb(203, 213, 225)', borderColor: 'rgb(51, 65, 85)' };
- }
+ /**
+ * Point de la pastille source : plein quand la source est affichée, creux
+ * (anneau) quand elle est masquée. L'identité de la source reste lisible
+ * dans les deux états, et le fond du bouton reste neutre (donc thémable).
+ */
+ providerDotStyle(chip: { dot: string; visible: boolean }): { [key: string]: string } {
+ return chip.visible
+ ? { backgroundColor: chip.dot }
+ : { backgroundColor: 'transparent', boxShadow: `inset 0 0 0 1.5px ${chip.dot}` };
}
// Merge groups with dedup by id/videoId/url
@@ -621,6 +630,11 @@ export class SearchComponent {
return { live, video, clip, channel, all: (list || []).length };
});
+ /** Nombre compact pour les compteurs de l'en-tête (1 284 -> « 1,3 k »). */
+ countLabel(n: number): string {
+ return formatCount(n);
+ }
+
/** Pills Tous / Directs / Vidéos / Clips / Chaînes synchronisées avec typeFilter. */
typePill = computed<'all' | 'live' | 'video' | 'shorts' | 'channel'>(() => {
const t = this.filters().type;
@@ -821,13 +835,6 @@ export class SearchComponent {
}
- formatViews(views: number): string {
- if (views >= 1_000_000_000) return (views / 1_000_000_000).toFixed(1) + 'B';
- if (views >= 1_000_000) return (views / 1_000_000).toFixed(1) + 'M';
- if (views >= 1_000) return (views / 1_000).toFixed(1) + 'K';
- return views.toString();
- }
-
formatRelative(dateIso?: string): string {
if (!dateIso) return '';
return formatRelativeFr(dateIso);