-
Feeds
- ${feeds.map((fd) => `
📡 ${f.esc(fd.name)}${f.ago(fd.lastUpdate)}
-
${f.esc(fd.url)}
`).join('
')}
+
+
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.
'}
-
Unread articles
-
| Feed | Title | Size | Published | |
- ${feeds.flatMap((fd) => fd.articles.map((a) => `
- | ${f.esc(fd.name)} | ${f.esc(a.title)} |
- ${f.bytes(a.size)} | ${f.ago(a.date)} |
- ${a.isRead ? 'read' : '● new'} |
`)).join('')}
-
+
Articles
+ ${articles.length ? `
| Feed | Title | Size | Published | |
+ ${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('')}
+
` : '
No articles yet.
'}
-
Auto-download rules
- ${rules.map((r) => renderRule(r)).join('')}
+
Auto-download rules
+ ${rules.length ? rules.map((r) => renderRule(r)).join('') : '
No rules yet. New matching articles are not auto-downloaded.
'}
`;
+
+ document.getElementById('addFeedBtn').addEventListener('click', openAddFeed);
+ document.getElementById('addRuleBtn').addEventListener('click', () => openRuleEditor(null, feeds));
+ 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');
+ renderView();
+ }));
+ 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 || '' });
+ 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-del]').forEach((b) => b.addEventListener('click', async () => {
+ await guard(() => api.deleteRssRule(b.dataset.ruleDel), 'Failed to delete rule');
+ renderView();
+ }));
+}
+
+function openAddFeed() {
+ openModal('Add RSS feed', `
+
+
`,
+ [{ label: 'Cancel', act: closeModal }, {
+ label: 'Add', primary: true, act: () => guard(async () => {
+ const name = document.getElementById('feedName').value.trim();
+ const url = document.getElementById('feedUrl').value.trim();
+ if (!name || !url) return closeModal();
+ await api.addFeed(name, url);
+ closeModal();
+ renderView();
+ }, 'Failed to add feed'),
+ }]);
+ setTimeout(() => document.getElementById('feedName')?.focus(), 0);
+}
+
+// Rule editor: create (rule=null) or edit an existing auto-download rule.
+function openRuleEditor(rule, feeds) {
+ const r = rule || { name: '', enabled: true, useRegex: false, addPaused: false,
+ mustContain: '', mustNotContain: '', assignedCategory: '', savePath: '', affectedFeeds: [] };
+ const catOpts = '
' +
+ state.meta.categories.filter((c) => c.name).map((c) =>
+ `
`).join('');
+ const feedChecks = (feeds || []).map((fd) =>
+ `
`).join('')
+ || '
No feeds yet.
';
+ openModal(rule ? 'Edit rule' : 'New rule', `
+
+
+
+
${feedChecks}
+
+
+
+
+
+
+
`,
+ [{ label: 'Cancel', act: closeModal }, {
+ label: 'Save', primary: true, act: () => guard(async () => {
+ const name = document.getElementById('rName').value.trim();
+ if (!name) return closeModal();
+ await api.saveRssRule({
+ name,
+ enabled: document.getElementById('rEnabled').checked,
+ useRegex: document.getElementById('rRegex').checked,
+ addPaused: document.getElementById('rPaused').checked,
+ mustContain: document.getElementById('rMust').value.trim(),
+ mustNotContain: document.getElementById('rMustNot').value.trim(),
+ assignedCategory: document.getElementById('rCat').value,
+ savePath: document.getElementById('rPath').value.trim(),
+ affectedFeeds: [...document.querySelectorAll('.rule-feeds input:checked')].map((c) => c.value),
+ });
+ closeModal();
+ renderView();
+ }, 'Failed to save rule'),
+ }]);
+ setTimeout(() => document.getElementById(rule ? 'rMust' : 'rName')?.focus(), 0);
}
function renderRule(r) {
@@ -985,11 +1073,14 @@ function renderRule(r) {
${r.enabled ? 'enabled' : 'disabled'}
${r.useRegex ? '
regex' : ''}
${r.addPaused ? '
add paused' : ''}
+
+
+
feeds: ${r.affectedFeeds.length ? r.affectedFeeds.join(', ') : 'all'} · last match ${r.lastMatch ? f.ago(r.lastMatch) : 'never'}