feat(groups): ratissage élargi (3 lettres, sous-chaînes, singletons, plafond, copie inclassables)
This commit is contained in:
@@ -41,7 +41,10 @@
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<h3 class="font-semibold text-slate-100">✨ Groupes suggérés</h3>
|
||||
<p class="text-xs text-slate-400">Noms de chaînes + sujets détectés. Vérifiez avant d’appliquer.</p>
|
||||
<p class="text-xs text-slate-400">
|
||||
Noms de chaînes + sujets détectés. Vérifiez avant d’appliquer.
|
||||
<span *ngIf="ungroupedCount() > 0" class="text-amber-200/90">{{ ungroupedCount() }} sans groupe.</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
@@ -79,7 +82,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="suggestions().length === 0" class="mt-3 rounded-lg border border-dashed border-slate-700 p-4 text-center text-sm text-slate-400">
|
||||
Aucune suggestion : chaque thème a moins de 2 chaînes, ou tout est déjà rangé.
|
||||
<p>Aucune suggestion : chaque thème a moins de 2 chaînes, ou tout est déjà rangé.</p>
|
||||
<button
|
||||
*ngIf="ungroupedCount() > 0"
|
||||
type="button"
|
||||
(click)="copyUngrouped()"
|
||||
class="mt-2 rounded-md border border-slate-600 px-3 py-1.5 text-xs text-slate-200 transition hover:border-slate-400 hover:text-white"
|
||||
>
|
||||
{{ copiedUngrouped() ? 'Copié ! Collez la liste pour enrichir les règles.' : 'Copier les ' + ungroupedCount() + ' sans-groupe' }}
|
||||
</button>
|
||||
</div>
|
||||
<ul *ngIf="suggestions().length > 0" class="mt-3 space-y-2">
|
||||
<li *ngFor="let s of suggestions(); trackBy: trackBySuggestion" class="flex flex-col gap-2 rounded-lg border border-slate-800 bg-slate-900/60 p-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
|
||||
@@ -219,6 +219,45 @@ export class SubscriptionsComponent {
|
||||
}
|
||||
}
|
||||
|
||||
copiedUngrouped = signal<boolean>(false);
|
||||
|
||||
/** Copie les chaînes sans groupe (une par ligne) pour analyse des règles. */
|
||||
copyUngrouped(): void {
|
||||
const lines = this.items()
|
||||
.filter((item) => this.itemGroupIds(item).length === 0)
|
||||
.map((item) => item.channel?.title || item.channel?.handle || `#${item.subscriptionId}`);
|
||||
const text = lines.join('\n');
|
||||
if (!text) return;
|
||||
const done = () => {
|
||||
this.copiedUngrouped.set(true);
|
||||
setTimeout(() => this.copiedUngrouped.set(false), 2000);
|
||||
};
|
||||
try {
|
||||
const nav = navigator as Navigator & { clipboard?: { writeText(t: string): Promise<void> } };
|
||||
if (nav?.clipboard?.writeText) {
|
||||
nav.clipboard.writeText(text).then(done, () => this.copyUngroupedFallback(text, done));
|
||||
} else {
|
||||
this.copyUngroupedFallback(text, done);
|
||||
}
|
||||
} catch {
|
||||
this.copyUngroupedFallback(text, done);
|
||||
}
|
||||
}
|
||||
|
||||
private copyUngroupedFallback(text: string, done: () => void): void {
|
||||
try {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
done();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// --- trackBy : DOM stable (pas de re-rendu complet + recharge d'images à chaque tick) ---
|
||||
trackBySubscriptionId(_idx: number, item: SubscriptionItem): number {
|
||||
return item.subscriptionId;
|
||||
|
||||
Reference in New Issue
Block a user