feat(badge): make badge per-tab and reactive\n\n- Track per-tab counts (total + filtered)\n- Update on tab switch and URL change\n- Popup sends filtered count; background updates badge for tab\n- Default to total count on archive.org download pages\n- Scope quick-action badge updates to tabId
This commit is contained in:
parent
7348316409
commit
a4be6d0c4f
3 changed files with 136 additions and 16 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Archive.org Link Grabber",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"description": "Filter and export archive.org /download links; copy or send to aria2 RPC.",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
|
|
@ -36,8 +36,12 @@
|
|||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://archive.org/download/*"],
|
||||
"js": ["src/content/collect.js"]
|
||||
"matches": [
|
||||
"https://archive.org/download/*"
|
||||
],
|
||||
"js": [
|
||||
"src/content/collect.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,53 @@
|
|||
// In MV2 background, aria2 helpers are exposed on globalThis by src/lib/aria2-bg.js
|
||||
|
||||
const DYNAMIC_PREFIX = 'aolg-act-';
|
||||
const A_DOWNLOAD_RE = /^https:\/\/archive\.org\/download\//;
|
||||
|
||||
// Per-tab state: { url, isArchive, itemsCount, filteredCount }
|
||||
const tabState = new Map();
|
||||
|
||||
function getState(tabId) {
|
||||
let s = tabState.get(tabId);
|
||||
if (!s) { s = { url: '', isArchive: false, itemsCount: 0, filteredCount: null }; tabState.set(tabId, s); }
|
||||
return s;
|
||||
}
|
||||
|
||||
async function setBadgeForTab(tabId, { text = '', color = '#3b82f6', title = '' } = {}) {
|
||||
try { await browser.browserAction.setBadgeBackgroundColor({ color, tabId }); } catch (_) {}
|
||||
try { await browser.browserAction.setBadgeText({ text, tabId }); } catch (_) {}
|
||||
if (title) { try { await browser.browserAction.setTitle({ title, tabId }); } catch (_) {} }
|
||||
}
|
||||
|
||||
async function recomputeBadge(tabId) {
|
||||
try {
|
||||
const tab = await browser.tabs.get(tabId);
|
||||
const s = getState(tabId);
|
||||
s.url = tab.url || '';
|
||||
s.isArchive = !!(tab.url && A_DOWNLOAD_RE.test(tab.url));
|
||||
// Prefer filteredCount when present (user-adjusted in popup)
|
||||
if (s.filteredCount != null) {
|
||||
const count = s.filteredCount;
|
||||
await setBadgeForTab(tabId, {
|
||||
text: count ? String(count) : '',
|
||||
color: '#6366f1',
|
||||
title: count ? `Filtered ${count} link(s)` : 'No filtered links'
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Default for archive pages: show total items
|
||||
if (s.isArchive) {
|
||||
const count = s.itemsCount || 0;
|
||||
await setBadgeForTab(tabId, {
|
||||
text: count ? String(count) : '',
|
||||
color: '#3b82f6',
|
||||
title: count ? `Found ${count} link(s)` : 'No links found'
|
||||
});
|
||||
} else {
|
||||
// Non-archive pages: clear badge unless popup provides a filtered count
|
||||
await setBadgeForTab(tabId, { text: '' });
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
let dynamicMenuIds = [];
|
||||
const dynamicActions = new Map(); // id -> { kind: 'all'|'type', type?: string }
|
||||
|
||||
|
|
@ -118,11 +165,24 @@ browser.runtime.onMessage.addListener(async (msg, sender) => {
|
|||
if (!tabId) return { ok: false, error: 'No tabId' };
|
||||
try {
|
||||
const items = await collectFromTab(tabId);
|
||||
const s = getState(tabId);
|
||||
s.itemsCount = (items || []).length;
|
||||
await recomputeBadge(tabId);
|
||||
return { ok: true, items };
|
||||
} catch (err) {
|
||||
return { ok: false, error: String(err && err.message || err) };
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.type === 'popup.filteredCount') {
|
||||
const tabId = msg.tabId || sender?.tab?.id;
|
||||
if (!tabId) return;
|
||||
const count = Number(msg.count || 0);
|
||||
const s = getState(tabId);
|
||||
s.filteredCount = count;
|
||||
await recomputeBadge(tabId);
|
||||
return { ok: true };
|
||||
}
|
||||
});
|
||||
|
||||
async function collectFromTab(tabId) {
|
||||
|
|
@ -172,13 +232,20 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => {
|
|||
try {
|
||||
const items = await collectFromTab(tab.id);
|
||||
const count = (items || []).length;
|
||||
const s = getState(tab.id);
|
||||
s.url = tab.url || '';
|
||||
s.isArchive = !!(tab.url && A_DOWNLOAD_RE.test(tab.url));
|
||||
s.itemsCount = count;
|
||||
s.filteredCount = null; // reset to default after explicit collect
|
||||
await browser.storage.local.set({
|
||||
lastCollected: { tabId: tab.id, url: tab.url, time: Date.now(), count },
|
||||
lastItems: items
|
||||
});
|
||||
try { await browser.browserAction.setBadgeBackgroundColor({ color: '#3b82f6' }); } catch (e) {}
|
||||
try { await browser.browserAction.setBadgeText({ text: count ? String(count) : '' }); } catch (e) {}
|
||||
try { await browser.browserAction.setTitle({ title: count ? `Collected ${count} link(s)` : 'No links collected' }); } catch (e) {}
|
||||
await setBadgeForTab(tab.id, {
|
||||
text: count ? String(count) : '',
|
||||
color: '#3b82f6',
|
||||
title: count ? `Collected ${count} link(s)` : 'No links collected'
|
||||
});
|
||||
console.debug('Collected links from page:', { count });
|
||||
} catch (e) {
|
||||
console.warn('Context menu collection failed:', e);
|
||||
|
|
@ -204,18 +271,60 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => {
|
|||
|
||||
if (defaultAction === 'copy') {
|
||||
try { await navigator.clipboard.writeText(uris.join('\n')); } catch (e) { console.warn('clipboard write failed', e); }
|
||||
try { await browser.browserAction.setBadgeBackgroundColor({ color: '#10b981' }); } catch (e) {}
|
||||
try { await browser.browserAction.setBadgeText({ text: String(count) }); } catch (e) {}
|
||||
try { await browser.browserAction.setTitle({ title: `Copied ${count} link(s)` }); } catch (e) {}
|
||||
await setBadgeForTab(tab.id, {
|
||||
text: String(count),
|
||||
color: '#10b981',
|
||||
title: `Copied ${count} link(s)`
|
||||
});
|
||||
} else {
|
||||
const { endpoint, secret } = await getAria2();
|
||||
if (!endpoint) { console.warn('aria2 endpoint not set'); return; }
|
||||
try { await globalThis.addUrisBatch({ endpoint, secret, uris }); } catch (e) { console.warn('aria2 send failed', e); }
|
||||
try { await browser.browserAction.setBadgeBackgroundColor({ color: '#3b82f6' }); } catch (e) {}
|
||||
try { await browser.browserAction.setBadgeText({ text: String(count) }); } catch (e) {}
|
||||
try { await browser.browserAction.setTitle({ title: `Sent ${count} link(s) to aria2` }); } catch (e) {}
|
||||
await setBadgeForTab(tab.id, {
|
||||
text: String(count),
|
||||
color: '#3b82f6',
|
||||
title: `Sent ${count} link(s) to aria2`
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Quick action failed:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// React to tab activation and URL changes to refresh per-tab badges
|
||||
browser.tabs.onActivated.addListener(async ({ tabId }) => {
|
||||
await recomputeBadge(tabId);
|
||||
try {
|
||||
const tab = await browser.tabs.get(tabId);
|
||||
if (tab?.url && A_DOWNLOAD_RE.test(tab.url)) {
|
||||
const items = await collectFromTab(tabId);
|
||||
const s = getState(tabId);
|
||||
s.itemsCount = (items || []).length;
|
||||
// Only update badge if no filtered count is set
|
||||
if (s.filteredCount == null) await recomputeBadge(tabId);
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status === 'complete' || changeInfo.url) {
|
||||
const s = getState(tabId);
|
||||
s.url = tab?.url || '';
|
||||
s.isArchive = !!(tab?.url && A_DOWNLOAD_RE.test(tab.url));
|
||||
// Reset filtered count on navigation; it reflects popup state for previous page
|
||||
s.filteredCount = null;
|
||||
if (s.isArchive) {
|
||||
try {
|
||||
const items = await collectFromTab(tabId);
|
||||
s.itemsCount = (items || []).length;
|
||||
} catch (_) { s.itemsCount = 0; }
|
||||
} else {
|
||||
s.itemsCount = 0;
|
||||
}
|
||||
await recomputeBadge(tabId);
|
||||
}
|
||||
});
|
||||
|
||||
browser.tabs.onRemoved.addListener((tabId) => {
|
||||
tabState.delete(tabId);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ async function refresh() {
|
|||
else throw new Error(res?.error || e?.message || 'collect failed');
|
||||
}
|
||||
allItems = items || [];
|
||||
updateCount();
|
||||
await updateCount();
|
||||
} catch (e) {
|
||||
els.status.textContent = String(e?.message || e);
|
||||
}
|
||||
|
|
@ -67,9 +67,16 @@ function filtered() {
|
|||
return applyFilters(allItems, getFilters());
|
||||
}
|
||||
|
||||
function updateCount() {
|
||||
els.count.textContent = String(filtered().length);
|
||||
async function updateCount() {
|
||||
const count = filtered().length;
|
||||
els.count.textContent = String(count);
|
||||
renderPreview();
|
||||
try {
|
||||
const tabId = await getActiveTabId();
|
||||
if (tabId) {
|
||||
await browser.runtime.sendMessage({ type: 'popup.filteredCount', tabId, count });
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
async function copyLinks() {
|
||||
|
|
@ -118,7 +125,7 @@ function wire() {
|
|||
[
|
||||
els.fType, els.fTypeRe, els.fName, els.fNameRe, els.fSizeMin, els.fSizeMax,
|
||||
els.fDateFrom, els.fDateTo, els.fCase
|
||||
].forEach(el => el.addEventListener('input', updateCount));
|
||||
].forEach(el => el.addEventListener('input', () => { updateCount(); }));
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue