webui: tags as a checkbox dropdown on add (not a comma string)

Replace the comma-separated tags input with a checkbox dropdown of
existing tags plus inline new-tag creation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-22 00:18:29 -04:00
parent 54ac49fa33
commit c15cecaa5f
2 changed files with 62 additions and 4 deletions

View file

@ -496,6 +496,8 @@ async function confirmDelete(hashes) {
function openAddModal() {
const cats = '<option value="" selected>Uncategorized</option>' +
state.meta.categories.map((c) => `<option value="${f.esc(c.name)}">${f.esc(c.name || 'Uncategorized')}</option>`).join('');
const tagOpts = state.meta.tags.map((t) =>
`<label class="cbdrop-item"><input type="checkbox" value="${f.esc(t)}"><span>${f.esc(t)}</span></label>`).join('');
openModal('Add torrent', `
<div class="field"><label>Magnet link / URL</label>
<input type="text" id="addMagnet" placeholder="magnet:?xt=urn:btih:…" /></div>
@ -504,9 +506,15 @@ function openAddModal() {
<div id="addFileInfo" class="dim" style="margin-top:6px;font-size:12px"></div></div>
<div class="field"><label>Category</label><select id="addCat">${cats}</select></div>
<div class="field"><label>Tags</label>
<input type="text" id="addTags" placeholder="comma-separated, e.g. anime, airing"${
state.meta.tags.length ? ` list="addTagList"` : ''} />
${state.meta.tags.length ? `<datalist id="addTagList">${state.meta.tags.map((t) => `<option value="${f.esc(t)}">`).join('')}</datalist>` : ''}</div>
<div class="cbdrop">
<button type="button" class="cbdrop-btn" id="addTagsBtn">
<span id="addTagsLabel">No tags selected</span><span class="cbdrop-caret"></span></button>
<div class="cbdrop-menu" id="addTagsMenu" hidden>
<div id="addTagsList">${tagOpts || '<div class="dim" style="padding:4px 4px 8px">No tags yet — add one below.</div>'}</div>
<div class="cbdrop-new"><input type="text" id="addTagNew" placeholder="New tag…" />
<button type="button" class="btn" id="addTagAdd">Add</button></div>
</div>
</div></div>
<div class="field"><label>Save path</label>
<input type="text" id="addPath" value="${f.esc(state.meta.preferences.save_path || '/data/downloads')}" /></div>
<div class="checks">
@ -519,7 +527,7 @@ function openAddModal() {
const magnet = document.getElementById('addMagnet').value.trim();
const common = {
category: document.getElementById('addCat').value,
tags: document.getElementById('addTags').value.split(',').map((s) => s.trim()).filter(Boolean),
tags: [...document.querySelectorAll('#addTagsList input:checked')].map((c) => c.value),
savePath: document.getElementById('addPath').value.trim(),
paused: document.getElementById('addPaused').checked,
seqDl: document.getElementById('addSeq').checked,
@ -536,6 +544,39 @@ function openAddModal() {
closeModal();
}, 'Failed to add torrent'),
}]);
// tags checkbox dropdown: toggle, live label, inline new-tag creation
(() => {
const menu = document.getElementById('addTagsMenu');
const list = document.getElementById('addTagsList');
const label = document.getElementById('addTagsLabel');
const input = document.getElementById('addTagNew');
const refresh = () => {
const sel = [...list.querySelectorAll('input:checked')].map((c) => c.value);
label.textContent = sel.length ? sel.join(', ') : 'No tags selected';
};
const addTag = () => {
const name = input.value.trim();
if (!name) return;
let cb = [...list.querySelectorAll('input')].find((c) => c.value === name);
if (!cb) {
const ph = list.querySelector('.dim'); if (ph) ph.remove();
const lbl = document.createElement('label');
lbl.className = 'cbdrop-item';
lbl.innerHTML = '<input type="checkbox"><span></span>';
cb = lbl.querySelector('input');
cb.value = name;
lbl.querySelector('span').textContent = name;
list.appendChild(lbl);
}
cb.checked = true;
input.value = '';
refresh();
};
document.getElementById('addTagsBtn').addEventListener('click', () => { menu.hidden = !menu.hidden; });
list.addEventListener('change', refresh);
document.getElementById('addTagAdd').addEventListener('click', addTag);
input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addTag(); } });
})();
// live readout of the chosen file(s)
document.getElementById('addFile').addEventListener('change', async (e) => {
const info = document.getElementById('addFileInfo');