feat(options,popup,background): add per-page aria2 dir option and popup toggle
- Options: add default toggle and base path () - Popup: allow per-send enable + preview; passes AGENTS.md LICENSE node_modules package-lock.json releases src dist manifest.json package.json README.md scripts to aria2 - Background: quick actions honor default, set AGENTS.md LICENSE node_modules package-lock.json releases src dist manifest.json package.json README.md scripts to <base>/<identifier> - Storage: new prefs and
This commit is contained in:
		
					parent
					
						
							
								56035451df
							
						
					
				
			
			
				commit
				
					
						0d944892d8
					
				
			
		
					 5 changed files with 89 additions and 7 deletions
				
			
		| 
						 | 
					@ -74,6 +74,8 @@ async function getPrefs() {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    defaultAction: prefs?.defaultAction || 'download',
 | 
					    defaultAction: prefs?.defaultAction || 'download',
 | 
				
			||||||
    allIncludeMeta: !!prefs?.allIncludeMeta,
 | 
					    allIncludeMeta: !!prefs?.allIncludeMeta,
 | 
				
			||||||
 | 
					    perPageDirEnabled: !!prefs?.perPageDirEnabled,
 | 
				
			||||||
 | 
					    perPageDirBase: prefs?.perPageDirBase || '/aria2/data',
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -108,7 +110,7 @@ async function rebuildContextSubmenu(tab) {
 | 
				
			||||||
  let items = [];
 | 
					  let items = [];
 | 
				
			||||||
  try { items = await collectFromTab(tab.id); } catch (_) { items = []; }
 | 
					  try { items = await collectFromTab(tab.id); } catch (_) { items = []; }
 | 
				
			||||||
  const identifier = parseIdentifierFromUrl(tab.url || '');
 | 
					  const identifier = parseIdentifierFromUrl(tab.url || '');
 | 
				
			||||||
  const { defaultAction, allIncludeMeta } = await getPrefs();
 | 
					  const { defaultAction, allIncludeMeta, perPageDirEnabled, perPageDirBase } = await getPrefs();
 | 
				
			||||||
  const verb = defaultAction === 'copy' ? 'Copy' : 'Download';
 | 
					  const verb = defaultAction === 'copy' ? 'Copy' : 'Download';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const top = computeTopTypes(items, identifier, 4);
 | 
					  const top = computeTopTypes(items, identifier, 4);
 | 
				
			||||||
| 
						 | 
					@ -256,7 +258,7 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => {
 | 
				
			||||||
  const action = dynamicActions.get(String(info.menuItemId));
 | 
					  const action = dynamicActions.get(String(info.menuItemId));
 | 
				
			||||||
  if (!action || !tab?.id) return;
 | 
					  if (!action || !tab?.id) return;
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const { defaultAction, allIncludeMeta } = await getPrefs();
 | 
					    const { defaultAction, allIncludeMeta, perPageDirEnabled, perPageDirBase } = await getPrefs();
 | 
				
			||||||
    const identifier = parseIdentifierFromUrl(tab.url || '');
 | 
					    const identifier = parseIdentifierFromUrl(tab.url || '');
 | 
				
			||||||
    const items = await collectFromTab(tab.id);
 | 
					    const items = await collectFromTab(tab.id);
 | 
				
			||||||
    let selected = items || [];
 | 
					    let selected = items || [];
 | 
				
			||||||
| 
						 | 
					@ -279,7 +281,16 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => {
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
      const { endpoint, secret } = await getAria2();
 | 
					      const { endpoint, secret } = await getAria2();
 | 
				
			||||||
      if (!endpoint) { console.warn('aria2 endpoint not set'); return; }
 | 
					      if (!endpoint) { console.warn('aria2 endpoint not set'); return; }
 | 
				
			||||||
      try { await globalThis.addUrisBatch({ endpoint, secret, uris }); } catch (e) { console.warn('aria2 send failed', e); }
 | 
					      // Build aria2 options (dir) based on prefs
 | 
				
			||||||
 | 
					      let options = {};
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        if (perPageDirEnabled && perPageDirBase) {
 | 
				
			||||||
 | 
					          const base = String(perPageDirBase).replace(/\/+$/, '');
 | 
				
			||||||
 | 
					          const dir = identifier ? `${base}/${identifier}` : base;
 | 
				
			||||||
 | 
					          if (dir) options.dir = dir;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (_) { /* noop */ }
 | 
				
			||||||
 | 
					      try { await globalThis.addUrisBatch({ endpoint, secret, uris, options }); } catch (e) { console.warn('aria2 send failed', e); }
 | 
				
			||||||
      await setBadgeForTab(tab.id, {
 | 
					      await setBadgeForTab(tab.id, {
 | 
				
			||||||
        text: String(count),
 | 
					        text: String(count),
 | 
				
			||||||
        color: '#3b82f6',
 | 
					        color: '#3b82f6',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,6 +41,12 @@
 | 
				
			||||||
        <div class="row">
 | 
					        <div class="row">
 | 
				
			||||||
          <label class="chk"><input type="checkbox" id="include-meta" /> Include metadata in “All”</label>
 | 
					          <label class="chk"><input type="checkbox" id="include-meta" /> Include metadata in “All”</label>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="row">
 | 
				
			||||||
 | 
					          <label class="chk"><input type="checkbox" id="per-page-dir-enabled" /> Use per‑page download directory by default</label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="row">
 | 
				
			||||||
 | 
					          <label>Base directory <input id="per-page-dir-base" placeholder="/aria2/data" /></label>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
      </section>
 | 
					      </section>
 | 
				
			||||||
    </main>
 | 
					    </main>
 | 
				
			||||||
    <script type="module" src="index.js"></script>
 | 
					    <script type="module" src="index.js"></script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,8 @@ const els = {
 | 
				
			||||||
  secret: document.getElementById('rpc-secret'),
 | 
					  secret: document.getElementById('rpc-secret'),
 | 
				
			||||||
  defaultAction: document.getElementById('default-action'),
 | 
					  defaultAction: document.getElementById('default-action'),
 | 
				
			||||||
  includeMeta: document.getElementById('include-meta'),
 | 
					  includeMeta: document.getElementById('include-meta'),
 | 
				
			||||||
 | 
					  perPageDirEnabled: document.getElementById('per-page-dir-enabled'),
 | 
				
			||||||
 | 
					  perPageDirBase: document.getElementById('per-page-dir-base'),
 | 
				
			||||||
  btnSave: document.getElementById('btn-save'),
 | 
					  btnSave: document.getElementById('btn-save'),
 | 
				
			||||||
  btnReset: document.getElementById('btn-reset'),
 | 
					  btnReset: document.getElementById('btn-reset'),
 | 
				
			||||||
  btnTest: document.getElementById('btn-test'),
 | 
					  btnTest: document.getElementById('btn-test'),
 | 
				
			||||||
| 
						 | 
					@ -19,8 +21,12 @@ async function restore() {
 | 
				
			||||||
  const { prefs } = await browser.storage.local.get('prefs');
 | 
					  const { prefs } = await browser.storage.local.get('prefs');
 | 
				
			||||||
  const defaultAction = prefs?.defaultAction || 'download';
 | 
					  const defaultAction = prefs?.defaultAction || 'download';
 | 
				
			||||||
  const includeMeta = !!prefs?.allIncludeMeta;
 | 
					  const includeMeta = !!prefs?.allIncludeMeta;
 | 
				
			||||||
 | 
					  const perPageDirEnabled = !!prefs?.perPageDirEnabled;
 | 
				
			||||||
 | 
					  const perPageDirBase = prefs?.perPageDirBase || '/aria2/data';
 | 
				
			||||||
  els.defaultAction.value = defaultAction;
 | 
					  els.defaultAction.value = defaultAction;
 | 
				
			||||||
  els.includeMeta.checked = includeMeta;
 | 
					  els.includeMeta.checked = includeMeta;
 | 
				
			||||||
 | 
					  els.perPageDirEnabled.checked = perPageDirEnabled;
 | 
				
			||||||
 | 
					  els.perPageDirBase.value = perPageDirBase;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function save() {
 | 
					async function save() {
 | 
				
			||||||
| 
						 | 
					@ -28,9 +34,11 @@ async function save() {
 | 
				
			||||||
  const secret = els.secret.value.trim();
 | 
					  const secret = els.secret.value.trim();
 | 
				
			||||||
  const defaultAction = els.defaultAction.value;
 | 
					  const defaultAction = els.defaultAction.value;
 | 
				
			||||||
  const allIncludeMeta = !!els.includeMeta.checked;
 | 
					  const allIncludeMeta = !!els.includeMeta.checked;
 | 
				
			||||||
 | 
					  const perPageDirEnabled = !!els.perPageDirEnabled.checked;
 | 
				
			||||||
 | 
					  const perPageDirBase = els.perPageDirBase.value.trim() || '/aria2/data';
 | 
				
			||||||
  await browser.storage.local.set({
 | 
					  await browser.storage.local.set({
 | 
				
			||||||
    aria2: { endpoint, secret },
 | 
					    aria2: { endpoint, secret },
 | 
				
			||||||
    prefs: { defaultAction, allIncludeMeta }
 | 
					    prefs: { defaultAction, allIncludeMeta, perPageDirEnabled, perPageDirBase }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  els.status.textContent = 'Saved.';
 | 
					  els.status.textContent = 'Saved.';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,6 +49,12 @@
 | 
				
			||||||
      <div class="row">
 | 
					      <div class="row">
 | 
				
			||||||
        <label>Secret <input id="rpc-secret" type="password" placeholder="your-token" /></label>
 | 
					        <label>Secret <input id="rpc-secret" type="password" placeholder="your-token" /></label>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="row">
 | 
				
			||||||
 | 
					        <label class="chk"><input type="checkbox" id="use-perpage-dir" /> Use per‑page download directory</label>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="row">
 | 
				
			||||||
 | 
					        <small id="dir-preview"></small>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
      <div class="row actions">
 | 
					      <div class="row actions">
 | 
				
			||||||
        <button id="btn-copy">Copy Links</button>
 | 
					        <button id="btn-copy">Copy Links</button>
 | 
				
			||||||
        <button id="btn-send">Send to aria2</button>
 | 
					        <button id="btn-send">Send to aria2</button>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,8 @@ const els = {
 | 
				
			||||||
  btnSend: document.getElementById('btn-send'),
 | 
					  btnSend: document.getElementById('btn-send'),
 | 
				
			||||||
  rpcEndpoint: document.getElementById('rpc-endpoint'),
 | 
					  rpcEndpoint: document.getElementById('rpc-endpoint'),
 | 
				
			||||||
  rpcSecret: document.getElementById('rpc-secret'),
 | 
					  rpcSecret: document.getElementById('rpc-secret'),
 | 
				
			||||||
 | 
					  usePerPageDir: document.getElementById('use-perpage-dir'),
 | 
				
			||||||
 | 
					  dirPreview: document.getElementById('dir-preview'),
 | 
				
			||||||
  count: document.getElementById('count'),
 | 
					  count: document.getElementById('count'),
 | 
				
			||||||
  status: document.getElementById('status'),
 | 
					  status: document.getElementById('status'),
 | 
				
			||||||
  openOptions: document.getElementById('open-options'),
 | 
					  openOptions: document.getElementById('open-options'),
 | 
				
			||||||
| 
						 | 
					@ -42,6 +44,21 @@ async function getActiveTabId() {
 | 
				
			||||||
  return tabs[0]?.id;
 | 
					  return tabs[0]?.id;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function parseIdentifierFromUrl(url) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const u = new URL(url);
 | 
				
			||||||
 | 
					    const parts = u.pathname.split('/').filter(Boolean);
 | 
				
			||||||
 | 
					    const idx = parts.indexOf('download');
 | 
				
			||||||
 | 
					    if (idx >= 0 && parts[idx + 1]) return parts[idx + 1];
 | 
				
			||||||
 | 
					  } catch (_) {}
 | 
				
			||||||
 | 
					  return '';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function getActiveTabUrl() {
 | 
				
			||||||
 | 
					  const tabs = await browser.tabs.query({ active: true, currentWindow: true });
 | 
				
			||||||
 | 
					  return tabs[0]?.url || '';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function refresh() {
 | 
					async function refresh() {
 | 
				
			||||||
  els.status.textContent = '';
 | 
					  els.status.textContent = '';
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
| 
						 | 
					@ -58,6 +75,7 @@ async function refresh() {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    allItems = items || [];
 | 
					    allItems = items || [];
 | 
				
			||||||
    await updateCount();
 | 
					    await updateCount();
 | 
				
			||||||
 | 
					    await updateDirPreview();
 | 
				
			||||||
  } catch (e) {
 | 
					  } catch (e) {
 | 
				
			||||||
    els.status.textContent = String(e?.message || e);
 | 
					    els.status.textContent = String(e?.message || e);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -95,10 +113,21 @@ async function sendToAria2() {
 | 
				
			||||||
  // Persist settings
 | 
					  // Persist settings
 | 
				
			||||||
  await browser.storage.local.set({ aria2: { endpoint, secret } });
 | 
					  await browser.storage.local.set({ aria2: { endpoint, secret } });
 | 
				
			||||||
  els.status.textContent = 'Sending to aria2…';
 | 
					  els.status.textContent = 'Sending to aria2…';
 | 
				
			||||||
 | 
					  // aria2 options
 | 
				
			||||||
 | 
					  let options = {};
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    if (els.usePerPageDir?.checked) {
 | 
				
			||||||
 | 
					      const { prefs } = await browser.storage.local.get('prefs');
 | 
				
			||||||
 | 
					      const base = (prefs?.perPageDirBase || '/aria2/data').replace(/\/+$/, '');
 | 
				
			||||||
 | 
					      const url = await getActiveTabUrl();
 | 
				
			||||||
 | 
					      const id = parseIdentifierFromUrl(url);
 | 
				
			||||||
 | 
					      const dir = id ? `${base}/${id}` : base;
 | 
				
			||||||
 | 
					      if (dir) options.dir = dir;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch (_) {}
 | 
				
			||||||
  const res = await browser.runtime.sendMessage({
 | 
					  const res = await browser.runtime.sendMessage({
 | 
				
			||||||
    type: 'aria2.send',
 | 
					    type: 'aria2.send',
 | 
				
			||||||
    payload: { endpoint, secret, uris }
 | 
					    payload: { endpoint, secret, uris, options }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  if (res?.ok) {
 | 
					  if (res?.ok) {
 | 
				
			||||||
    els.status.textContent = `Sent ${uris.length} link(s) to aria2.`;
 | 
					    els.status.textContent = `Sent ${uris.length} link(s) to aria2.`;
 | 
				
			||||||
| 
						 | 
					@ -115,6 +144,11 @@ async function restoreSettings() {
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    els.rpcEndpoint.value = 'http://localhost:6800/jsonrpc';
 | 
					    els.rpcEndpoint.value = 'http://localhost:6800/jsonrpc';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const { prefs } = await browser.storage.local.get('prefs');
 | 
				
			||||||
 | 
					    const enabled = !!prefs?.perPageDirEnabled;
 | 
				
			||||||
 | 
					    els.usePerPageDir.checked = enabled;
 | 
				
			||||||
 | 
					  } catch (_) {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function wire() {
 | 
					function wire() {
 | 
				
			||||||
| 
						 | 
					@ -126,6 +160,9 @@ function wire() {
 | 
				
			||||||
    els.fType, els.fTypeRe, els.fName, els.fNameRe, els.fSizeMin, els.fSizeMax,
 | 
					    els.fType, els.fTypeRe, els.fName, els.fNameRe, els.fSizeMin, els.fSizeMax,
 | 
				
			||||||
    els.fDateFrom, els.fDateTo, els.fCase
 | 
					    els.fDateFrom, els.fDateTo, els.fCase
 | 
				
			||||||
  ].forEach(el => el.addEventListener('input', () => { updateCount(); }));
 | 
					  ].forEach(el => el.addEventListener('input', () => { updateCount(); }));
 | 
				
			||||||
 | 
					  if (els.usePerPageDir) {
 | 
				
			||||||
 | 
					    els.usePerPageDir.addEventListener('change', () => { updateDirPreview(); });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.addEventListener('DOMContentLoaded', async () => {
 | 
					document.addEventListener('DOMContentLoaded', async () => {
 | 
				
			||||||
| 
						 | 
					@ -150,3 +187,17 @@ function renderPreview() {
 | 
				
			||||||
    els.preview.appendChild(li);
 | 
					    els.preview.appendChild(li);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function updateDirPreview() {
 | 
				
			||||||
 | 
					  if (!els.dirPreview) return;
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const { prefs } = await browser.storage.local.get('prefs');
 | 
				
			||||||
 | 
					    const base = (prefs?.perPageDirBase || '/aria2/data').replace(/\/+$/, '');
 | 
				
			||||||
 | 
					    const url = await getActiveTabUrl();
 | 
				
			||||||
 | 
					    const id = parseIdentifierFromUrl(url);
 | 
				
			||||||
 | 
					    const dir = id ? `${base}/${id}` : base;
 | 
				
			||||||
 | 
					    els.dirPreview.textContent = els.usePerPageDir?.checked ? `Will set aria2 dir: ${dir}` : '';
 | 
				
			||||||
 | 
					  } catch (_) {
 | 
				
			||||||
 | 
					    els.dirPreview.textContent = '';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue