Merge improvement/inbox-nav-links: navigation links on watched + scheduler notices
This commit is contained in:
commit
795f21a1e1
6 changed files with 38 additions and 7 deletions
|
|
@ -174,7 +174,13 @@ export default function Feed({
|
||||||
? i18n.t("feed.markedWatchedNamed", { title: v.title })
|
? i18n.t("feed.markedWatchedNamed", { title: v.title })
|
||||||
: i18n.t("feed.markedWatched"),
|
: i18n.t("feed.markedWatched"),
|
||||||
action: { label: i18n.t("feed.unwatch"), onClick: () => onState(id, "new") },
|
action: { label: i18n.t("feed.unwatch"), onClick: () => onState(id, "new") },
|
||||||
meta: { kind: "video-watched", videoId: id, title: v?.title ?? "this video" },
|
meta: {
|
||||||
|
kind: "video-watched",
|
||||||
|
videoId: id,
|
||||||
|
title: v?.title ?? "this video",
|
||||||
|
channelId: v?.channel_id ?? "",
|
||||||
|
channelName: v?.channel_title ?? "",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else if (status === "new") {
|
} else if (status === "new") {
|
||||||
// Unhide / unwatch (from a card, the toast's Undo, or the center): quietly resolve
|
// Unhide / unwatch (from a card, the toast's Undo, or the center): quietly resolve
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useSyncExternalStore } from "react";
|
import { useEffect, useSyncExternalStore } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { Bell, Check, CheckCheck, ExternalLink, Eye, RotateCcw, Search, Trash2, Tv, X } from "lucide-react";
|
import { Activity, Bell, Check, CheckCheck, ExternalLink, Eye, RotateCcw, Search, Trash2, Tv, X } from "lucide-react";
|
||||||
import { api, type AppNotification, type FeedFilters } from "../lib/api";
|
import { api, type AppNotification, type FeedFilters } from "../lib/api";
|
||||||
import { useLiveQuery } from "../lib/useLiveQuery";
|
import { useLiveQuery } from "../lib/useLiveQuery";
|
||||||
import {
|
import {
|
||||||
|
|
@ -94,11 +94,12 @@ export default function NotificationsPanel({
|
||||||
clearMut.mutate();
|
clearMut.mutate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Find in feed" / undo, ported from the old bell.
|
// "Find in feed" / undo, ported from the old bell. Works for both hidden and watched
|
||||||
function locateHidden(meta: VideoHiddenMeta) {
|
// notices — jump to the feed filtered to that state and the video's channel.
|
||||||
|
function locate(meta: VideoHiddenMeta | VideoWatchedMeta) {
|
||||||
setFilters({
|
setFilters({
|
||||||
...filters,
|
...filters,
|
||||||
show: "hidden",
|
show: meta.kind === "video-hidden" ? "hidden" : "watched",
|
||||||
channelId: meta.channelId || undefined,
|
channelId: meta.channelId || undefined,
|
||||||
channelName: meta.channelName || undefined,
|
channelName: meta.channelName || undefined,
|
||||||
});
|
});
|
||||||
|
|
@ -168,6 +169,7 @@ export default function NotificationsPanel({
|
||||||
t={t}
|
t={t}
|
||||||
onRead={() => readMut.mutate(n.id)}
|
onRead={() => readMut.mutate(n.id)}
|
||||||
onDismiss={() => dismissMut.mutate(n.id)}
|
onDismiss={() => dismissMut.mutate(n.id)}
|
||||||
|
onOpenScheduler={() => setPage("scheduler")}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -184,7 +186,7 @@ export default function NotificationsPanel({
|
||||||
key={n.id}
|
key={n.id}
|
||||||
n={n}
|
n={n}
|
||||||
t={t}
|
t={t}
|
||||||
onFind={locateHidden}
|
onFind={locate}
|
||||||
onRevert={revertState}
|
onRevert={revertState}
|
||||||
onFocusChannel={onFocusChannel}
|
onFocusChannel={onFocusChannel}
|
||||||
onRemove={() => removeClient(n.id)}
|
onRemove={() => removeClient(n.id)}
|
||||||
|
|
@ -204,11 +206,13 @@ function NotificationRow({
|
||||||
t,
|
t,
|
||||||
onRead,
|
onRead,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
|
onOpenScheduler,
|
||||||
}: {
|
}: {
|
||||||
n: AppNotification;
|
n: AppNotification;
|
||||||
t: (k: string, o?: any) => string;
|
t: (k: string, o?: any) => string;
|
||||||
onRead: () => void;
|
onRead: () => void;
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
|
onOpenScheduler: () => void;
|
||||||
}) {
|
}) {
|
||||||
// The maintenance producer ships the affected videos in `data.videos`; list a few titles.
|
// The maintenance producer ships the affected videos in `data.videos`; list a few titles.
|
||||||
const videos: { id: string; title: string | null }[] = Array.isArray(n.data?.videos)
|
const videos: { id: string; title: string | null }[] = Array.isArray(n.data?.videos)
|
||||||
|
|
@ -261,6 +265,15 @@ function NotificationRow({
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
{isScheduler && (
|
||||||
|
<button
|
||||||
|
onClick={onOpenScheduler}
|
||||||
|
className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline mt-1.5"
|
||||||
|
>
|
||||||
|
<Activity className="w-3.5 h-3.5" />
|
||||||
|
{t("inbox.openScheduler")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
{!n.read && (
|
{!n.read && (
|
||||||
|
|
@ -296,7 +309,7 @@ function ClientActivityRow({
|
||||||
}: {
|
}: {
|
||||||
n: ClientNotif;
|
n: ClientNotif;
|
||||||
t: (k: string, o?: any) => string;
|
t: (k: string, o?: any) => string;
|
||||||
onFind: (meta: VideoHiddenMeta) => void;
|
onFind: (meta: VideoHiddenMeta | VideoWatchedMeta) => void;
|
||||||
onRevert: (meta: VideoHiddenMeta | VideoWatchedMeta) => void;
|
onRevert: (meta: VideoHiddenMeta | VideoWatchedMeta) => void;
|
||||||
onFocusChannel: (name: string) => void;
|
onFocusChannel: (name: string) => void;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
|
|
@ -342,6 +355,13 @@ function ClientActivityRow({
|
||||||
</div>
|
</div>
|
||||||
) : watched ? (
|
) : watched ? (
|
||||||
<div className="flex items-center gap-3 mt-1">
|
<div className="flex items-center gap-3 mt-1">
|
||||||
|
<button
|
||||||
|
onClick={() => onFind(watched)}
|
||||||
|
className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
|
||||||
|
>
|
||||||
|
<Search className="w-3.5 h-3.5" />
|
||||||
|
{t("notifications.findInFeed")}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => onRevert(watched)}
|
onClick={() => onRevert(watched)}
|
||||||
className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
|
className="flex items-center gap-1 text-accent text-sm font-semibold hover:underline"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"sectionSystem": "System",
|
"sectionSystem": "System",
|
||||||
"sectionActivity": "Aktivität",
|
"sectionActivity": "Aktivität",
|
||||||
"andMore": "und {{count}} weitere",
|
"andMore": "und {{count}} weitere",
|
||||||
|
"openScheduler": "Zeitplaner öffnen",
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"title": "Videos entfernt",
|
"title": "Videos entfernt",
|
||||||
"body_one": "{{count}} gespeichertes oder in einer Playlist befindliches Video wurde entfernt, weil es auf YouTube nicht mehr verfügbar ist.",
|
"body_one": "{{count}} gespeichertes oder in einer Playlist befindliches Video wurde entfernt, weil es auf YouTube nicht mehr verfügbar ist.",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"sectionSystem": "System",
|
"sectionSystem": "System",
|
||||||
"sectionActivity": "Activity",
|
"sectionActivity": "Activity",
|
||||||
"andMore": "and {{count}} more",
|
"andMore": "and {{count}} more",
|
||||||
|
"openScheduler": "Open Scheduler",
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"title": "Videos removed",
|
"title": "Videos removed",
|
||||||
"body_one": "{{count}} saved or playlisted video was removed because it's no longer available on YouTube.",
|
"body_one": "{{count}} saved or playlisted video was removed because it's no longer available on YouTube.",
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"sectionSystem": "Rendszer",
|
"sectionSystem": "Rendszer",
|
||||||
"sectionActivity": "Tevékenység",
|
"sectionActivity": "Tevékenység",
|
||||||
"andMore": "és még {{count}}",
|
"andMore": "és még {{count}}",
|
||||||
|
"openScheduler": "Ütemező megnyitása",
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"title": "Videók eltávolítva",
|
"title": "Videók eltávolítva",
|
||||||
"body_one": "{{count}} mentett vagy lejátszási listás videó eltávolítva, mert már nem elérhető a YouTube-on.",
|
"body_one": "{{count}} mentett vagy lejátszási listás videó eltávolítva, mert már nem elérhető a YouTube-on.",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ export type VideoWatchedMeta = {
|
||||||
kind: "video-watched";
|
kind: "video-watched";
|
||||||
videoId: string;
|
videoId: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
channelId: string;
|
||||||
|
channelName: string;
|
||||||
};
|
};
|
||||||
export type ChannelSubscribedMeta = {
|
export type ChannelSubscribedMeta = {
|
||||||
kind: "channel-subscribed";
|
kind: "channel-subscribed";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue