webui: RSS manual download / repull / rule-run controls
- Per-article Add button now sends the article key and reflects the grabbed state (✓ Added, disabled). - Per-feed ⟳ repull plus a Refresh-all button. - Per-rule ⏵ "Run now" that re-applies the rule to existing articles and reports how many were added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ed906a3549
commit
0aeb039586
2 changed files with 27 additions and 3 deletions
|
|
@ -47,6 +47,8 @@ export const api = {
|
|||
deleteFeed: (name) => jpost('/api/rss/delete', { name }),
|
||||
saveRssRule: (rule) => jpost('/api/rss/rules', rule),
|
||||
deleteRssRule: (name) => jpost('/api/rss/rules/delete', { name }),
|
||||
runRssRule: (name) => jpost('/api/rss/rules/run', { name }),
|
||||
refreshFeeds: (name) => jpost('/api/rss/refresh', name ? { name } : {}),
|
||||
rssDownload: (item) => jpost('/api/rss/download', item),
|
||||
saveIndexer: (ix) => jpost('/api/indexers', ix),
|
||||
deleteIndexer: (name) => jpost('/api/indexers/delete', { name }),
|
||||
|
|
|
|||
|
|
@ -962,10 +962,13 @@ async function renderRssView(host, seq) {
|
|||
host.innerHTML = `<div class="pane"><div class="split2">
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-head"><h3>Feeds</h3><button class="btn" id="addFeedBtn">+ Add feed</button></div>
|
||||
<div class="card-head"><h3>Feeds</h3><span>
|
||||
<button class="btn" id="refreshAllBtn" title="Re-fetch all feeds now">⟳ Refresh all</button>
|
||||
<button class="btn" id="addFeedBtn">+ Add feed</button></span></div>
|
||||
${feeds.length ? feeds.map((fd) => `<div class="feed-row" data-feed="${f.esc(fd.name)}">
|
||||
<div class="kvrow"><span style="flex:1">📡 <b>${f.esc(fd.name)}</b> <span class="dim">(${fd.articles.length})</span></span>
|
||||
<span class="dim">${fd.lastUpdate ? f.ago(fd.lastUpdate) : 'not fetched yet'}</span>
|
||||
<button class="iconbtn repull-feed" title="Re-fetch this feed now">⟳</button>
|
||||
<button class="iconbtn del-feed" title="Remove feed">✕</button></div>
|
||||
<div class="kvrow dim"><code class="inline">${f.esc(fd.url)}</code></div>
|
||||
</div>`).join('<div style="height:8px"></div>') : '<div class="dim">No feeds yet. Add one to start polling.</div>'}
|
||||
|
|
@ -977,7 +980,9 @@ async function renderRssView(host, seq) {
|
|||
${articles.map((a, i) => `<tr style="${a.isRead ? 'opacity:.5' : ''}">
|
||||
<td class="dim">${f.esc(a.feed)}</td><td>${f.esc(a.title)}</td>
|
||||
<td class="num">${a.size ? f.bytes(a.size) : '—'}</td><td class="num dim">${f.esc(a.pubDate || '')}</td>
|
||||
<td>${(a.magnet || a.torrentUrl) ? `<button class="btn dl-art" data-art="${i}">+ Add</button>` : '<span class="faint">—</span>'}</td></tr>`).join('')}
|
||||
<td>${(a.magnet || a.torrentUrl)
|
||||
? `<button class="btn dl-art" data-art="${i}"${a.grabbed ? ' disabled' : ''}>${a.grabbed ? '✓ Added' : '+ Add'}</button>`
|
||||
: '<span class="faint">—</span>'}</td></tr>`).join('')}
|
||||
</tbody></table>` : '<div class="dim">No articles yet.</div>'}
|
||||
</div>
|
||||
<div class="card-head" style="margin-top:16px"><h3>Auto-download rules</h3><button class="btn" id="addRuleBtn">+ New rule</button></div>
|
||||
|
|
@ -987,6 +992,17 @@ async function renderRssView(host, seq) {
|
|||
|
||||
document.getElementById('addFeedBtn').addEventListener('click', openAddFeed);
|
||||
document.getElementById('addRuleBtn').addEventListener('click', () => openRuleEditor(null, feeds));
|
||||
document.getElementById('refreshAllBtn').addEventListener('click', (e) => guard(async () => {
|
||||
e.target.disabled = true; e.target.textContent = '⟳ Refreshing…';
|
||||
await api.refreshFeeds();
|
||||
renderView();
|
||||
}, 'Failed to refresh feeds'));
|
||||
host.querySelectorAll('.repull-feed').forEach((b) => b.addEventListener('click', () => guard(async () => {
|
||||
const name = b.closest('.feed-row').dataset.feed;
|
||||
b.disabled = true;
|
||||
await api.refreshFeeds(name);
|
||||
renderView();
|
||||
}, 'Failed to refresh feed')));
|
||||
host.querySelectorAll('.del-feed').forEach((b) => b.addEventListener('click', async () => {
|
||||
const name = b.closest('.feed-row').dataset.feed;
|
||||
await guard(() => api.deleteFeed(name), 'Failed to remove feed');
|
||||
|
|
@ -994,11 +1010,16 @@ async function renderRssView(host, seq) {
|
|||
}));
|
||||
host.querySelectorAll('.dl-art').forEach((b) => b.addEventListener('click', () => guard(async () => {
|
||||
const a = articles[+b.dataset.art];
|
||||
await api.rssDownload({ magnet: a.magnet || '', torrentUrl: a.torrentUrl || '' });
|
||||
await api.rssDownload({ magnet: a.magnet || '', torrentUrl: a.torrentUrl || '', key: a.key || '' });
|
||||
b.textContent = '✓ Added'; b.disabled = true;
|
||||
}, 'Failed to add torrent')));
|
||||
host.querySelectorAll('[data-rule-edit]').forEach((b) => b.addEventListener('click', () =>
|
||||
openRuleEditor(rules.find((r) => r.name === b.dataset.ruleEdit), feeds)));
|
||||
host.querySelectorAll('[data-rule-run]').forEach((b) => b.addEventListener('click', () => guard(async () => {
|
||||
const res = await api.runRssRule(b.dataset.ruleRun);
|
||||
toast(`Rule run: ${res.grabbed} added of ${res.matched} match(es)`, 'ok');
|
||||
renderView();
|
||||
}, 'Failed to run rule')));
|
||||
host.querySelectorAll('[data-rule-del]').forEach((b) => b.addEventListener('click', async () => {
|
||||
await guard(() => api.deleteRssRule(b.dataset.ruleDel), 'Failed to delete rule');
|
||||
renderView();
|
||||
|
|
@ -1074,6 +1095,7 @@ function renderRule(r) {
|
|||
${r.useRegex ? '<span class="pill">regex</span>' : ''}
|
||||
${r.addPaused ? '<span class="pill paused">add paused</span>' : ''}
|
||||
<span style="flex:1"></span>
|
||||
<button class="iconbtn" data-rule-run="${f.esc(r.name)}" title="Run now against existing articles">⏵</button>
|
||||
<button class="iconbtn" data-rule-edit="${f.esc(r.name)}" title="Edit rule">✎</button>
|
||||
<button class="iconbtn" data-rule-del="${f.esc(r.name)}" title="Delete rule">✕</button>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue