feat: Add feed source filtering to settings and API

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-01-03 00:01:46 +00:00
parent 93903eedf9
commit ab15868397
3 changed files with 72 additions and 18 deletions

View File

@@ -26,6 +26,15 @@ function App() {
const [bodyFont, setBodyFont] = useState(localStorage.getItem('bodyFont') || 'Sans Serif');
const [articleFont, setArticleFont] = useState(localStorage.getItem('articleFont') || 'Apparatus SIL');
const [filterSmallweb, setFilterSmallweb] = useState(() => localStorage.getItem('filterSmallweb') === 'true');
const [feedSources, setFeedSources] = useState(() => {
const saved = localStorage.getItem('feedSources');
return saved ? JSON.parse(saved) : {
hackernews: true,
reddit: true,
lobsters: true,
tildes: true,
};
});
const updateCache = useCallback((key, value) => {
cache.current[key] = value;
@@ -57,6 +66,14 @@ function App() {
localStorage.setItem('filterSmallweb', isChecked);
};
const handleFeedSourceChange = (source) => {
setFeedSources(prevSources => {
const newSources = { ...prevSources, [source]: !prevSources[source] };
localStorage.setItem('feedSources', JSON.stringify(newSources));
return newSources;
});
};
const changeBodyFont = (font) => {
setBodyFont(font);
localStorage.setItem('bodyFont', font);
@@ -177,6 +194,22 @@ function App() {
<input className="checkbox" type="checkbox" id="filter-smallweb" checked={filterSmallweb} onChange={handleFilterChange} />
<label htmlFor="filter-smallweb">Small websites</label>
</div>
<div className="font-option">
<input className="checkbox" type="checkbox" id="filter-hackernews" name="feed-source" checked={feedSources.hackernews} onChange={() => handleFeedSourceChange('hackernews')} />
<label htmlFor="filter-hackernews">Hacker News</label>
</div>
<div className="font-option">
<input className="checkbox" type="checkbox" id="filter-reddit" name="feed-source" checked={feedSources.reddit} onChange={() => handleFeedSourceChange('reddit')} />
<label htmlFor="filter-reddit">Reddit</label>
</div>
<div className="font-option">
<input className="checkbox" type="checkbox" id="filter-lobsters" name="feed-source" checked={feedSources.lobsters} onChange={() => handleFeedSourceChange('lobsters')} />
<label htmlFor="filter-lobsters">Lobsters</label>
</div>
<div className="font-option">
<input className="checkbox" type="checkbox" id="filter-tildes" name="feed-source" checked={feedSources.tildes} onChange={() => handleFeedSourceChange('tildes')} />
<label htmlFor="filter-tildes">Tildes</label>
</div>
</div>
<div className="setting-group">
<h4>Font Size</h4>
@@ -251,7 +284,7 @@ function App() {
<Route path='/(|search)' component={Submit} />
</div>
<Route path='/' exact render={(props) => <Feed {...props} updateCache={updateCache} filterSmallweb={filterSmallweb} />} />
<Route path='/' exact render={(props) => <Feed {...props} updateCache={updateCache} filterSmallweb={filterSmallweb} feedSources={feedSources} />} />
<Switch>
<Route path='/search' component={Results} />
<Route path='/:id' exact render={(props) => <Article {...props} cache={cache.current} />} />

View File

@@ -4,7 +4,7 @@ import { Helmet } from 'react-helmet';
import localForage from 'localforage';
import { sourceLink, infoLine, logos } from './utils.js';
function Feed({ updateCache, filterSmallweb }) {
function Feed({ updateCache, filterSmallweb, feedSources }) {
const [stories, setStories] = useState(() => JSON.parse(localStorage.getItem('stories')) || false);
const [error, setError] = useState('');
const [loadingStatus, setLoadingStatus] = useState(null);
@@ -16,7 +16,7 @@ function Feed({ updateCache, filterSmallweb }) {
} else {
setStories(false);
}
}, [filterSmallweb]);
}, [filterSmallweb, feedSources]);
useEffect(() => {
const controller = new AbortController();
@@ -30,7 +30,20 @@ function Feed({ updateCache, filterSmallweb }) {
});
}
fetch(filterSmallweb ? '/api?smallweb=true' : '/api', { signal: controller.signal })
const params = new URLSearchParams();
if (filterSmallweb) {
params.append('smallweb', 'true');
}
const allSources = Object.keys(feedSources);
const enabledSources = allSources.filter(key => feedSources[key]);
if (enabledSources.length > 0 && enabledSources.length < allSources.length) {
enabledSources.forEach(source => params.append('source', source));
}
const apiUrl = `/api?${params.toString()}`;
fetch(apiUrl, { signal: controller.signal })
.then(res => {
if (!res.ok) {
throw new Error(`Server responded with ${res.status} ${res.statusText}`);
@@ -117,7 +130,7 @@ function Feed({ updateCache, filterSmallweb }) {
);
return () => controller.abort();
}, [updateCache, filterSmallweb]);
}, [updateCache, filterSmallweb, feedSources]);
return (
<div className='container'>