Keyword Filters
Manage the keywords used by the hourly matcher and the daily digest.
No keyword filters yet. Add a few terms to tune your digest.
Notification & Digest Status
Track what the background RSS jobs can use for filtering and delivery.
import { useEffect, useMemo, useState } from 'react'; import { useAuth, useCollection, exportCSV, exportJSON } from '@deplixo/sdk'; import { FeedTimeline } from './components/FeedTimeline.jsx'; import { FeedManager } from './components/FeedManager.jsx'; function App() { const { user, loading, login, logout } = useAuth(); const [activeTab, setActiveTab] = useState('timeline'); const [keywordInput, setKeywordInput] = useState(''); const [digestEnabled, setDigestEnabled] = useState(true); const [matchedOnly, setMatchedOnly] = useState(false); const categoryOptions = useMemo( () => ['All', 'Tech', 'Sports', 'Business', 'World', 'Science', 'Entertainment'], [] ); const feedsCollection = useCollection('rss_feeds', { personal: true }); const savedArticlesCollection = useCollection('saved_articles', { personal: true }); const filtersCollection = useCollection('rss_filters', { personal: true }); const filterStatsCollection = useCollection('rss_filter_stats', { personal: true }); const feeds = feedsCollection.items || []; const savedArticles = savedArticlesCollection.items || []; const filtersItems = filtersCollection.items || []; const filterStatsItems = filterStatsCollection.items || []; const keywordFilters = filtersItems[0] || null; const filterStats = filterStatsItems[0] || null; const [keywords, setKeywords] = useState([]); useEffect(() => { const rawKeywords = keywordFilters?.keywords; const normalized = Array.isArray(rawKeywords) ? rawKeywords : typeof rawKeywords === 'string' ? rawKeywords.split(',') : []; setKeywords( normalized .map((item) => String(item).trim()) .filter(Boolean) ); setDigestEnabled(keywordFilters?.digestEnabled !== false); setMatchedOnly(Boolean(keywordFilters?.matchedOnly)); }, [keywordFilters]); const cleanedKeywords = useMemo( () => keywords.map((keyword) => keyword.trim()).filter(Boolean), [keywords] ); const keywordPreview = cleanedKeywords.slice(0, 6); const handleAddKeyword = async () => { const next = keywordInput.trim(); if (!next || cleanedKeywords.some((keyword) => keyword.toLowerCase() === next.toLowerCase())) { setKeywordInput(''); return; } const updatedKeywords = [...cleanedKeywords, next]; setKeywords(updatedKeywords); setKeywordInput(''); const payload = { keywords: updatedKeywords, digestEnabled, matchedOnly, updatedAt: new Date().toISOString(), }; if (keywordFilters?.id) { await filtersCollection.update(keywordFilters.id, payload); } else { await filtersCollection.add(payload); } }; const handleRemoveKeyword = async (keywordToRemove) => { const updatedKeywords = cleanedKeywords.filter( (keyword) => keyword.toLowerCase() !== keywordToRemove.toLowerCase() ); setKeywords(updatedKeywords); const payload = { keywords: updatedKeywords, digestEnabled, matchedOnly, updatedAt: new Date().toISOString(), }; if (keywordFilters?.id) { await filtersCollection.update(keywordFilters.id, payload); } else { await filtersCollection.add(payload); } }; const handleToggleSetting = async (field, nextValue) => { if (field === 'digestEnabled') setDigestEnabled(nextValue); if (field === 'matchedOnly') setMatchedOnly(nextValue); const payload = { keywords: cleanedKeywords, digestEnabled: field === 'digestEnabled' ? nextValue : digestEnabled, matchedOnly: field === 'matchedOnly' ? nextValue : matchedOnly, updatedAt: new Date().toISOString(), }; if (keywordFilters?.id) { await filtersCollection.update(keywordFilters.id, payload); } else { await filtersCollection.add(payload); } }; const handleExportBookmarksJSON = () => { exportJSON(savedArticles, { filename: 'bookmarks.json' }); }; const handleExportBookmarksCSV = () => { const rows = savedArticles.map((item) => ({ title: item.title || '', source: item.source || item.feedName || '', url: item.url || item.link || '', date: item.date || item.createdAt || '', summary: item.summary || item.snippet || '', })); exportCSV(rows, { filename: 'bookmarks.csv', columns: ['title', 'source', 'url', 'date', 'summary'], }); }; const statusLabel = digestEnabled ? 'Daily digest enabled' : 'Daily digest paused'; const matcherLabel = matchedOnly ? 'Hourly matcher: keyword matches only' : 'Hourly matcher: all feeds'; const lastUpdated = keywordFilters?.updatedAt || keywordFilters?.createdAt || 'Not configured yet'; if (loading) { return
Connect with Google to keep your RSS feed list, keyword filters, and bookmarked articles personal and synced to your account.
Manage the keywords used by the hourly matcher and the daily digest.
No keyword filters yet. Add a few terms to tune your digest.
Track what the background RSS jobs can use for filtering and delivery.