-
Feeds
+
Feeds
+
+
${feeds.length ? feeds.map((fd) => `
📡 ${f.esc(fd.name)} (${fd.articles.length})
${fd.lastUpdate ? f.ago(fd.lastUpdate) : 'not fetched yet'}
+
${f.esc(fd.url)}
`).join('
') : '
No feeds yet. Add one to start polling.
'}
@@ -977,7 +980,9 @@ async function renderRssView(host, seq) {
${articles.map((a, i) => `
| ${f.esc(a.feed)} | ${f.esc(a.title)} |
${a.size ? f.bytes(a.size) : '—'} | ${f.esc(a.pubDate || '')} |
- ${(a.magnet || a.torrentUrl) ? `` : '—'} |
`).join('')}
+
${(a.magnet || a.torrentUrl)
+ ? ``
+ : '—'} | `).join('')}
` : '
No articles yet.
'}
Auto-download rules
@@ -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 ? '
regex' : ''}
${r.addPaused ? '
add paused' : ''}
+