diff options
| -rw-r--r-- | apps/web/app/reader/_components/sidebar-content.tsx | 2 | ||||
| -rw-r--r-- | apps/web/app/reader/settings/_components/settings-shell.tsx | 2 | ||||
| -rw-r--r-- | apps/web/app/reader/settings/_components/subscriptions-settings.tsx | 18 | ||||
| -rw-r--r-- | apps/web/lib/queries/use-subscription-mutations.ts | 30 | ||||
| -rw-r--r-- | apps/web/lib/queries/use-subscriptions.ts | 4 | ||||
| -rw-r--r-- | apps/web/lib/types/subscription.ts | 1 | ||||
| -rw-r--r-- | supabase/schema.sql | 3 |
7 files changed, 58 insertions, 2 deletions
diff --git a/apps/web/app/reader/_components/sidebar-content.tsx b/apps/web/app/reader/_components/sidebar-content.tsx index b5732c0..bd11470 100644 --- a/apps/web/app/reader/_components/sidebar-content.tsx +++ b/apps/web/app/reader/_components/sidebar-content.tsx @@ -335,6 +335,7 @@ export function SidebarContent() { "flex items-center truncate pl-6 text-[0.85em]", activeFeedIdentifier === subscription.feedIdentifier && ACTIVE_LINK_CLASS, + subscription.hiddenFromTimeline && "opacity-50", sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) )} > @@ -396,6 +397,7 @@ export function SidebarContent() { "flex items-center truncate pl-4 text-[0.85em]", activeFeedIdentifier === subscription.feedIdentifier && ACTIVE_LINK_CLASS, + subscription.hiddenFromTimeline && "opacity-50", sidebarFocusClass(focusedPanel, focusedSidebarIndex, navIndex++) )} > diff --git a/apps/web/app/reader/settings/_components/settings-shell.tsx b/apps/web/app/reader/settings/_components/settings-shell.tsx index 4153fc4..9d6c2d6 100644 --- a/apps/web/app/reader/settings/_components/settings-shell.tsx +++ b/apps/web/app/reader/settings/_components/settings-shell.tsx @@ -65,7 +65,7 @@ export function SettingsShell() { </div> </nav> <div className="flex-1 overflow-y-auto"> - <div className="max-w-3xl"> + <div className="max-w-4xl"> {activeTab === "subscriptions" && <SubscriptionsSettings />} {activeTab === "folders" && <FoldersSettings />} {activeTab === "muted-phrases" && <MutedPhrasesSettings />} diff --git a/apps/web/app/reader/settings/_components/subscriptions-settings.tsx b/apps/web/app/reader/settings/_components/subscriptions-settings.tsx index 1e9245d..7ac037d 100644 --- a/apps/web/app/reader/settings/_components/subscriptions-settings.tsx +++ b/apps/web/app/reader/settings/_components/subscriptions-settings.tsx @@ -4,6 +4,7 @@ import { useState } from "react" import { useSubscriptions } from "@/lib/queries/use-subscriptions" import { useUpdateSubscriptionTitle, + useUpdateSubscriptionHiddenFromTimeline, useUpdateFeedUrl, useUpdateFeedCredentials, useAddFeedCredentials, @@ -50,6 +51,7 @@ function SubscriptionRow({ const [editedAuthType, setEditedAuthType] = useState("bearer") const [editedCredential, setEditedCredential] = useState("") const updateTitle = useUpdateSubscriptionTitle() + const updateHiddenFromTimeline = useUpdateSubscriptionHiddenFromTimeline() const updateFeedUrl = useUpdateFeedUrl() const updateCredentials = useUpdateFeedCredentials() const addCredentials = useAddFeedCredentials() @@ -279,6 +281,22 @@ function SubscriptionRow({ </div> ) : null} <div className="flex flex-wrap items-center gap-2"> + <button + onClick={() => + updateHiddenFromTimeline.mutate({ + subscriptionIdentifier: subscription.subscriptionIdentifier, + hiddenFromTimeline: !subscription.hiddenFromTimeline, + }) + } + disabled={updateHiddenFromTimeline.isPending} + className={ + subscription.hiddenFromTimeline + ? "px-2 py-1 text-text-dim transition-colors hover:text-text-secondary disabled:opacity-50" + : "px-2 py-1 text-text-secondary transition-colors hover:text-text-primary disabled:opacity-50" + } + > + {subscription.hiddenFromTimeline ? "hidden from timeline" : "hide from timeline"} + </button> <select value={subscription.folderIdentifier ?? ""} onChange={(event) => handleFolderChange(event.target.value)} diff --git a/apps/web/lib/queries/use-subscription-mutations.ts b/apps/web/lib/queries/use-subscription-mutations.ts index ca52bd2..1570167 100644 --- a/apps/web/lib/queries/use-subscription-mutations.ts +++ b/apps/web/lib/queries/use-subscription-mutations.ts @@ -5,6 +5,36 @@ import { createSupabaseBrowserClient } from "@/lib/supabase/client" import { queryKeys } from "./query-keys" import { notify } from "@/lib/notify" +export function useUpdateSubscriptionHiddenFromTimeline() { + const supabaseClient = createSupabaseBrowserClient() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + subscriptionIdentifier, + hiddenFromTimeline, + }: { + subscriptionIdentifier: string + hiddenFromTimeline: boolean + }) => { + const { error } = await supabaseClient + .from("subscriptions") + .update({ hidden_from_timeline: hiddenFromTimeline }) + .eq("id", subscriptionIdentifier) + + if (error) throw error + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: queryKeys.subscriptions.all }) + queryClient.invalidateQueries({ queryKey: queryKeys.timeline.all }) + notify(variables.hiddenFromTimeline ? "feed hidden from timeline" : "feed visible in timeline") + }, + onError: (error: Error) => { + notify("failed to update visibility: " + error.message) + }, + }) +} + export function useUpdateSubscriptionTitle() { const supabaseClient = createSupabaseBrowserClient() const queryClient = useQueryClient() diff --git a/apps/web/lib/queries/use-subscriptions.ts b/apps/web/lib/queries/use-subscriptions.ts index 2378411..5dc6076 100644 --- a/apps/web/lib/queries/use-subscriptions.ts +++ b/apps/web/lib/queries/use-subscriptions.ts @@ -11,6 +11,7 @@ interface SubscriptionRow { folder_id: string | null custom_title: string | null position: number + hidden_from_timeline: boolean feeds: { title: string | null url: string @@ -39,7 +40,7 @@ export function useSubscriptions() { const [subscriptionsResult, foldersResult] = await Promise.all([ supabaseClient .from("subscriptions") - .select("id, feed_id, folder_id, custom_title, position, feeds(title, url, visibility, consecutive_failures, last_fetch_error, last_fetched_at, fetch_interval_seconds, feed_type)") + .select("id, feed_id, folder_id, custom_title, position, hidden_from_timeline, feeds(title, url, visibility, consecutive_failures, last_fetch_error, last_fetched_at, fetch_interval_seconds, feed_type)") .order("position", { ascending: true }), supabaseClient .from("folders") @@ -65,6 +66,7 @@ export function useSubscriptions() { fetchIntervalSeconds: row.feeds?.fetch_interval_seconds ?? 3600, feedType: row.feeds?.feed_type ?? null, feedVisibility: row.feeds?.visibility ?? "public", + hiddenFromTimeline: row.hidden_from_timeline, })) const folders: Folder[] = ( diff --git a/apps/web/lib/types/subscription.ts b/apps/web/lib/types/subscription.ts index 0dbc8cb..96f314d 100644 --- a/apps/web/lib/types/subscription.ts +++ b/apps/web/lib/types/subscription.ts @@ -18,4 +18,5 @@ export interface Subscription { fetchIntervalSeconds: number feedType: string | null feedVisibility: "public" | "authenticated" + hiddenFromTimeline: boolean } diff --git a/supabase/schema.sql b/supabase/schema.sql index 147dd8a..6603c36 100644 --- a/supabase/schema.sql +++ b/supabase/schema.sql @@ -85,6 +85,7 @@ CREATE TABLE public.subscriptions ( custom_title text, position integer NOT NULL DEFAULT 0, vault_secret_id uuid, + hidden_from_timeline boolean NOT NULL DEFAULT false, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now() ); @@ -581,6 +582,7 @@ AS $function$ (e.owner_id IS NULL OR e.owner_id = auth.uid()) AND (target_folder_id IS NULL OR s.folder_id = target_folder_id) AND (target_feed_id IS NULL OR e.feed_id = target_feed_id) + AND (target_feed_id IS NOT NULL OR s.hidden_from_timeline = false) AND (pagination_cursor IS NULL OR e.published_at < pagination_cursor) AND (NOT unread_only OR COALESCE(ues.read, false) = false) AND NOT EXISTS ( @@ -797,6 +799,7 @@ AS $function$ OR e.summary ILIKE '%' || replace(replace(replace(p_query, '\', '\\'), '%', '\%'), '_', '\_') || '%' ESCAPE '\' OR e.author ILIKE '%' || replace(replace(replace(p_query, '\', '\\'), '%', '\%'), '_', '\_') || '%' ESCAPE '\' ) + AND s.hidden_from_timeline = false AND ( (SELECT tier FROM public.user_profiles WHERE id = auth.uid()) IN ('pro', 'developer') OR e.published_at >= now() - interval '14 days' |