From 5f6107bbf82bc89ba0e6306a40fc468bd41a29b9 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 25 Mar 2026 10:46:59 -0400 Subject: [PATCH] feat(briefing): add News Preferences section with topic include/exclude inputs Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/views/SettingsView.vue | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 7dbe453..d230735 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -7,6 +7,7 @@ import { apiGet, apiPost, apiPut, apiDelete, listGroups, createGroup, deleteGrou import { usePushStore } from "@/stores/push"; import type { User } from "@/types/auth"; import PaginationBar from "@/components/PaginationBar.vue"; +import TagInput from "@/components/TagInput.vue"; const store = useSettingsStore(); const authStore = useAuthStore(); @@ -268,6 +269,17 @@ const newFeedUrl = ref(''); const newFeedCategory = ref(''); const addingFeed = ref(false); const refreshingFeeds = ref(false); +const briefingIncludeTopics = ref([]); +const briefingExcludeTopics = ref([]); + +function _parseTopics(raw: unknown): string[] { + try { + const val = typeof raw === 'string' ? JSON.parse(raw) : raw; + return Array.isArray(val) ? val.map(String) : []; + } catch { + return []; + } +} async function loadBriefingTab() { briefingConfig.value = await getBriefingConfig(); @@ -276,6 +288,25 @@ async function loadBriefingTab() { if (!briefingConfig.value.timezone) { briefingConfig.value.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; } + const allSettings = await apiGet>('/api/settings').catch(() => ({} as Record)); + briefingIncludeTopics.value = _parseTopics(allSettings['briefing_include_topics'] ?? '[]'); + briefingExcludeTopics.value = _parseTopics(allSettings['briefing_exclude_topics'] ?? '[]'); +} + +async function saveIncludeTopics(topics: string[]) { + briefingIncludeTopics.value = topics; + await apiPut('/api/settings', { briefing_include_topics: JSON.stringify(topics) }); +} + +async function saveExcludeTopics(topics: string[]) { + briefingExcludeTopics.value = topics; + await apiPut('/api/settings', { briefing_exclude_topics: JSON.stringify(topics) }); +} + +const _STANDARD_TOPICS = ['technology', 'science', 'politics', 'business', 'health', 'environment', 'local', 'entertainment', 'sports', 'other']; +async function fetchTopicSuggestions(q: string): Promise { + if (!q) return _STANDARD_TOPICS; + return _STANDARD_TOPICS.filter((t) => t.startsWith(q.toLowerCase())); } async function geocodeLocation(key: 'home' | 'work') { @@ -1763,6 +1794,41 @@ function formatUserDate(iso: string): string { + +
+

News Preferences

+

+ Tell the briefing what topics you care about. Topics are matched against + classified RSS items before each briefing runs. +

+
+ + +
+
+ + +
+
+ Standard topic vocabulary +

+ technology · science · politics · business · health · environment · + local · entertainment · sports · other +

+

Custom terms are also accepted.

+
+
+

Notifications