feat(briefing): add News Preferences section with topic include/exclude inputs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<string[]>([]);
|
||||
const briefingExcludeTopics = ref<string[]>([]);
|
||||
|
||||
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<Record<string, string>>('/api/settings').catch(() => ({} as Record<string, string>));
|
||||
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<string[]> {
|
||||
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 {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- News Preferences -->
|
||||
<section class="settings-section full-width">
|
||||
<h2>News Preferences</h2>
|
||||
<p class="section-desc">
|
||||
Tell the briefing what topics you care about. Topics are matched against
|
||||
classified RSS items before each briefing runs.
|
||||
</p>
|
||||
<div class="settings-field">
|
||||
<label class="field-label">Interested in</label>
|
||||
<TagInput
|
||||
:model-value="briefingIncludeTopics"
|
||||
placeholder="e.g. technology, science, local"
|
||||
:fetch-tags="fetchTopicSuggestions"
|
||||
@update:model-value="saveIncludeTopics"
|
||||
/>
|
||||
</div>
|
||||
<div class="settings-field" style="margin-top: 0.75rem">
|
||||
<label class="field-label">Not interested in</label>
|
||||
<TagInput
|
||||
:model-value="briefingExcludeTopics"
|
||||
placeholder="e.g. sports, celebrity"
|
||||
:fetch-tags="fetchTopicSuggestions"
|
||||
@update:model-value="saveExcludeTopics"
|
||||
/>
|
||||
</div>
|
||||
<details class="topic-vocab-hint" style="margin-top: 0.75rem">
|
||||
<summary style="cursor: pointer; color: var(--color-text-muted); font-size: 0.82rem">Standard topic vocabulary</summary>
|
||||
<p class="field-hint" style="margin-top: 0.35rem">
|
||||
technology · science · politics · business · health · environment ·
|
||||
local · entertainment · sports · other
|
||||
</p>
|
||||
<p class="field-hint">Custom terms are also accepted.</p>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<!-- Notifications -->
|
||||
<section class="settings-section full-width">
|
||||
<h2>Notifications</h2>
|
||||
|
||||
Reference in New Issue
Block a user