feat: manage subscriptions in the app, and act from the menu bar

Takeout is still the way a subscription list arrives. These are the
things it cannot do.

Hovering a channel in the sidebar reveals a delete, sitting where the
count is — the least useful thing on that row at the moment you reach
for it. It asks first, and says what goes: the channel, its videos, and
how many downloaded files will be deleted. It also says plainly that
YouTube is not touched; only this app forgets the channel.

A + beside the cog adds one channel from any link — its page, its
@handle, or one of its videos. The channel id and name are read off the
page, which is the same pair the CSV carries, and the feed is fetched
straight away so it is not sitting there empty.

A menu bar item acts on whatever a browser is showing without leaving
it: save that video, or subscribe to its channel, while it plays on.
The address comes from whichever running browser has a YouTube page —
asked with AppleScript, guarded so no browser is launched to answer,
and reported honestly when macOS has not granted the permission yet.
Answers come back as notifications rather than by raising the window.

Saving a video is not subscribing to its channel, so a channel can now
exist as a parent row without being a subscription: out of the sidebar,
skipped by refreshes, and left alone when a CSV import replaces the
subscription list.

Also removes a stale duplicate of the file header that had been sitting
inside run()'s setup block. Items are legal inside a block, so it
compiled and nobody noticed.
This commit is contained in:
vincent
2026-08-29 19:54:41 +02:00
parent 87d96be178
commit 1790453a47
14 changed files with 1109 additions and 69 deletions
+20
View File
@@ -58,6 +58,26 @@ export const checkYtDlpUpdate = () => invoke<UpdateStatus>("check_yt_dlp_update"
export const updateYtDlp = () => invoke<string>("update_yt_dlp");
/** WebVTT sidecars beside a downloaded video, as [language, path] pairs. */
/** Adds one channel from any YouTube link. Returns its title. */
export const addChannel = (url: string) => invoke<string>("add_channel", { url });
export interface RemovalPreview {
title: string;
videos: number;
downloaded: number;
}
export const previewDeleteChannel = (channelId: string) =>
invoke<RemovalPreview>("preview_delete_channel", { channelId });
/** Unsubscribes in the app only — nothing changes on YouTube. */
export const deleteChannel = (channelId: string) =>
invoke<void>("delete_channel", { channelId });
/** Keeps the menu bar's downloads matching the window's settings. */
export const setDownloadDefaults = (quality: string, subLang: string) =>
invoke<void>("set_download_defaults", { quality, subLang });
export const listSubtitles = (videoId: string) =>
invoke<Array<[string, string]>>("list_subtitles", { videoId });