diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-14 09:42:14 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-14 09:42:14 -0800 |
| commit | 6fa13a06411048f3217397f4285b3e64e7b9ee58 (patch) | |
| tree | af5ec83884bb45a10d51cf5be3ae895ebf1bcff8 /frontend/src/components/Settings.tsx | |
| parent | 23947045c011e84149bc1b9d48805e57bb0bb3ba (diff) | |
| download | neko-6fa13a06411048f3217397f4285b3e64e7b9ee58.tar.gz neko-6fa13a06411048f3217397f4285b3e64e7b9ee58.tar.bz2 neko-6fa13a06411048f3217397f4285b3e64e7b9ee58.zip | |
feature: implement full OPML and Text import/export (fixing NK-r6nhj0)
Diffstat (limited to 'frontend/src/components/Settings.tsx')
| -rw-r--r-- | frontend/src/components/Settings.tsx | 64 |
1 files changed, 60 insertions, 4 deletions
diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 3f508e9..6b6dab1 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -9,6 +9,8 @@ export default function Settings() { const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); + const [importFile, setImportFile] = useState<File | null>(null); + const fetchFeeds = () => { setLoading(true); apiFetch('/api/feed/') @@ -30,8 +32,6 @@ export default function Settings() { fetchFeeds(); }, []); - - const handleAddFeed = (e: React.FormEvent) => { e.preventDefault(); if (!newFeedUrl) return; @@ -48,7 +48,7 @@ export default function Settings() { }) .then(() => { setNewFeedUrl(''); - fetchFeeds(); // Refresh list (or we could append if server returns full feed object) + fetchFeeds(); }) .catch((err) => { setError(err.message); @@ -74,6 +74,34 @@ export default function Settings() { }); }; + const handleImport = (e: React.FormEvent) => { + e.preventDefault(); + if (!importFile) return; + + setLoading(true); + const formData = new FormData(); + formData.append('file', importFile); + formData.append('format', 'opml'); + + apiFetch('/api/import', { + method: 'POST', + body: formData, + }) + .then((res) => { + if (!res.ok) throw new Error('Failed to import feeds'); + return res.json(); + }) + .then(() => { + setImportFile(null); + fetchFeeds(); + alert('Import successful!'); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; + return ( <div className="settings-page"> <h2>Settings</h2> @@ -94,9 +122,37 @@ export default function Settings() { Add Feed </button> </form> - {error && <p className="error-message">{error}</p>} </div> + <div className="import-export-section"> + <div className="import-section"> + <h3>Import Feeds (OPML)</h3> + <form onSubmit={handleImport} className="import-form"> + <input + type="file" + accept=".opml,.xml,.txt" + onChange={(e) => setImportFile(e.target.files?.[0] || null)} + className="file-input" + disabled={loading} + /> + <button type="submit" disabled={!importFile || loading}> + Import + </button> + </form> + </div> + + <div className="export-section"> + <h3>Export Feeds</h3> + <div className="export-buttons"> + <a href="/api/export/opml" className="export-btn">OPML</a> + <a href="/api/export/text" className="export-btn">Text</a> + <a href="/api/export/json" className="export-btn">JSON</a> + </div> + </div> + </div> + + {error && <p className="error-message">{error}</p>} + <div className="feed-list-section"> <h3>Manage Feeds</h3> {loading && <p>Loading...</p>} |
