feat(channels): YouTube link on channel name + done checkmarks

- Channel name: middle-click opens the channel's YouTube page in a new tab
  (left-click still opens the in-app detail) plus an explicit external-link icon.
- recent/full sync badges show a check icon when the state is reached.
- New i18n key channels.row.openOnYouTube (HU/EN/DE).
This commit is contained in:
npeter83 2026-06-17 13:55:43 +02:00
parent fa4b4ac556
commit ccf044ac2c
4 changed files with 40 additions and 4 deletions

View file

@ -4,6 +4,8 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { import {
ArrowDown, ArrowDown,
ArrowUp, ArrowUp,
Check,
ExternalLink,
Eye, Eye,
EyeOff, EyeOff,
History, History,
@ -361,10 +363,11 @@ function SyncBadge({ ok, label, hint }: { ok: boolean; label: string; hint?: str
return ( return (
<Tooltip hint={hint ?? ""}> <Tooltip hint={hint ?? ""}>
<span <span
className={`text-[10px] px-1.5 py-0.5 rounded-full border ${ className={`inline-flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded-full border ${
ok ? "border-accent/40 text-accent" : "border-border text-muted" ok ? "border-accent/40 text-accent" : "border-border text-muted"
}`} }`}
> >
{ok && <Check className="w-3 h-3" />}
{label} {label}
</span> </span>
</Tooltip> </Tooltip>
@ -393,6 +396,9 @@ function ChannelRow({
onToggleTag: (tagId: number) => void; onToggleTag: (tagId: number) => void;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const ytUrl = c.handle
? `https://www.youtube.com/@${c.handle.replace(/^@/, "")}`
: `https://www.youtube.com/channel/${c.id}`;
return ( return (
<div <div
className={`glass-card glass-hover flex items-center gap-3 p-2.5 rounded-xl transition ${ className={`glass-card glass-hover flex items-center gap-3 p-2.5 rounded-xl transition ${
@ -418,9 +424,36 @@ function ChannelRow({
/> />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<button onClick={onView} className="text-sm font-semibold truncate hover:text-accent block max-w-full text-left"> <div className="flex items-center gap-1 max-w-full">
{c.title ?? c.id} <button
</button> onClick={onView}
onAuxClick={(e) => {
// Middle-click opens the channel's YouTube page in a new tab; left-click
// keeps opening the in-app channel detail.
if (e.button === 1) {
e.preventDefault();
window.open(ytUrl, "_blank", "noopener,noreferrer");
}
}}
onMouseDown={(e) => {
if (e.button === 1) e.preventDefault(); // suppress middle-click autoscroll
}}
className="text-sm font-semibold truncate hover:text-accent text-left min-w-0"
>
{c.title ?? c.id}
</button>
<Tooltip hint={t("channels.row.openOnYouTube")}>
<a
href={ytUrl}
target="_blank"
rel="noopener noreferrer"
className="text-muted hover:text-accent shrink-0"
aria-label={t("channels.row.openOnYouTube")}
>
<ExternalLink className="w-3.5 h-3.5" />
</a>
</Tooltip>
</div>
<div className="flex items-center gap-2 text-[11px] text-muted"> <div className="flex items-center gap-2 text-[11px] text-muted">
<span>{t("channels.row.stored", { count: c.stored_videos, formatted: c.stored_videos.toLocaleString() })}</span> <span>{t("channels.row.stored", { count: c.stored_videos, formatted: c.stored_videos.toLocaleString() })}</span>
{c.subscriber_count != null && <span>· {t("channels.row.subs", { count: c.subscriber_count, formatted: c.subscriber_count.toLocaleString() })}</span>} {c.subscriber_count != null && <span>· {t("channels.row.subs", { count: c.subscriber_count, formatted: c.subscriber_count.toLocaleString() })}</span>}

View file

@ -36,6 +36,7 @@
"row": { "row": {
"stored": "{{formatted}} gespeichert", "stored": "{{formatted}} gespeichert",
"subs": "{{formatted}} Abonnenten", "subs": "{{formatted}} Abonnenten",
"openOnYouTube": "Auf YouTube öffnen",
"priorityHint": "Deine Einstufung für diesen Kanal. Sortiere den Feed nach „Kanalpriorität“, um Kanäle mit höherer Priorität nach oben zu bringen.", "priorityHint": "Deine Einstufung für diesen Kanal. Sortiere den Feed nach „Kanalpriorität“, um Kanäle mit höherer Priorität nach oben zu bringen.",
"raisePriority": "Priorität erhöhen", "raisePriority": "Priorität erhöhen",
"lowerPriority": "Priorität senken", "lowerPriority": "Priorität senken",

View file

@ -36,6 +36,7 @@
"row": { "row": {
"stored": "{{formatted}} stored", "stored": "{{formatted}} stored",
"subs": "{{formatted}} subs", "subs": "{{formatted}} subs",
"openOnYouTube": "Open on YouTube",
"priorityHint": "Your ranking for this channel. Sort the feed by “Channel priority” to bring higher-priority channels to the top.", "priorityHint": "Your ranking for this channel. Sort the feed by “Channel priority” to bring higher-priority channels to the top.",
"raisePriority": "Raise priority", "raisePriority": "Raise priority",
"lowerPriority": "Lower priority", "lowerPriority": "Lower priority",

View file

@ -36,6 +36,7 @@
"row": { "row": {
"stored": "{{formatted}} tárolt", "stored": "{{formatted}} tárolt",
"subs": "{{formatted}} feliratkozó", "subs": "{{formatted}} feliratkozó",
"openOnYouTube": "Megnyitás a YouTube-on",
"priorityHint": "A te rangsorod ehhez a csatornához. Rendezd a hírfolyamot „Csatorna prioritás” szerint, hogy a magasabb prioritású csatornák felülre kerüljenek.", "priorityHint": "A te rangsorod ehhez a csatornához. Rendezd a hírfolyamot „Csatorna prioritás” szerint, hogy a magasabb prioritású csatornák felülre kerüljenek.",
"raisePriority": "Prioritás növelése", "raisePriority": "Prioritás növelése",
"lowerPriority": "Prioritás csökkentése", "lowerPriority": "Prioritás csökkentése",