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:
parent
54ac49fa33
commit
c15cecaa5f
2 changed files with 62 additions and 4 deletions
|
|
@ -409,6 +409,23 @@ code.inline { background: var(--bg-3); border: 1px solid var(--line); border-rad
|
||||||
.modal .mbody { padding: 16px 18px; }
|
.modal .mbody { padding: 16px 18px; }
|
||||||
.modal .mfoot { padding: 12px 18px; border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; }
|
.modal .mfoot { padding: 12px 18px; border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; }
|
||||||
.field { margin-bottom: 12px; }
|
.field { margin-bottom: 12px; }
|
||||||
|
/* checkbox dropdown (multi-select, e.g. tags) */
|
||||||
|
.cbdrop { position: relative; }
|
||||||
|
.cbdrop-btn { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
||||||
|
background: var(--bg-3); border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px;
|
||||||
|
color: var(--txt); font-size: 13px; cursor: pointer; }
|
||||||
|
.cbdrop-btn:hover { border-color: var(--accent); }
|
||||||
|
.cbdrop-btn > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; }
|
||||||
|
.cbdrop-caret { color: var(--txt-faint); flex: none; }
|
||||||
|
.cbdrop-menu { margin-top: 6px; background: var(--bg-2); border: 1px solid var(--line); border-radius: 8px;
|
||||||
|
padding: 6px; max-height: 200px; overflow: auto; }
|
||||||
|
.cbdrop-item { display: flex; align-items: center; gap: 8px; padding: 5px 8px; border-radius: 5px;
|
||||||
|
cursor: pointer; color: var(--txt); font-size: 13px; }
|
||||||
|
.cbdrop-item:hover { background: var(--bg-3); }
|
||||||
|
.cbdrop-item input { accent-color: var(--accent); }
|
||||||
|
.cbdrop-new { display: flex; gap: 6px; padding-top: 6px; margin-top: 4px; border-top: 1px solid var(--line); }
|
||||||
|
.cbdrop-new input { flex: 1; background: var(--bg-1); border: 1px solid var(--line); border-radius: 6px;
|
||||||
|
padding: 5px 8px; color: var(--txt); font-size: 12px; }
|
||||||
.field label { display: block; color: var(--txt-dim); margin-bottom: 4px; font-size: 12px; }
|
.field label { display: block; color: var(--txt-dim); margin-bottom: 4px; font-size: 12px; }
|
||||||
.field input[type=text], .field input[type=password], .field textarea, .field select {
|
.field input[type=text], .field input[type=password], .field textarea, .field select {
|
||||||
width: 100%; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 7px 10px;
|
width: 100%; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 7px 10px;
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,8 @@ async function confirmDelete(hashes) {
|
||||||
function openAddModal() {
|
function openAddModal() {
|
||||||
const cats = '<option value="" selected>Uncategorized</option>' +
|
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('');
|
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', `
|
openModal('Add torrent', `
|
||||||
<div class="field"><label>Magnet link / URL</label>
|
<div class="field"><label>Magnet link / URL</label>
|
||||||
<input type="text" id="addMagnet" placeholder="magnet:?xt=urn:btih:…" /></div>
|
<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 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>Category</label><select id="addCat">${cats}</select></div>
|
||||||
<div class="field"><label>Tags</label>
|
<div class="field"><label>Tags</label>
|
||||||
<input type="text" id="addTags" placeholder="comma-separated, e.g. anime, airing"${
|
<div class="cbdrop">
|
||||||
state.meta.tags.length ? ` list="addTagList"` : ''} />
|
<button type="button" class="cbdrop-btn" id="addTagsBtn">
|
||||||
${state.meta.tags.length ? `<datalist id="addTagList">${state.meta.tags.map((t) => `<option value="${f.esc(t)}">`).join('')}</datalist>` : ''}</div>
|
<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>
|
<div class="field"><label>Save path</label>
|
||||||
<input type="text" id="addPath" value="${f.esc(state.meta.preferences.save_path || '/data/downloads')}" /></div>
|
<input type="text" id="addPath" value="${f.esc(state.meta.preferences.save_path || '/data/downloads')}" /></div>
|
||||||
<div class="checks">
|
<div class="checks">
|
||||||
|
|
@ -519,7 +527,7 @@ function openAddModal() {
|
||||||
const magnet = document.getElementById('addMagnet').value.trim();
|
const magnet = document.getElementById('addMagnet').value.trim();
|
||||||
const common = {
|
const common = {
|
||||||
category: document.getElementById('addCat').value,
|
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(),
|
savePath: document.getElementById('addPath').value.trim(),
|
||||||
paused: document.getElementById('addPaused').checked,
|
paused: document.getElementById('addPaused').checked,
|
||||||
seqDl: document.getElementById('addSeq').checked,
|
seqDl: document.getElementById('addSeq').checked,
|
||||||
|
|
@ -536,6 +544,39 @@ function openAddModal() {
|
||||||
closeModal();
|
closeModal();
|
||||||
}, 'Failed to add torrent'),
|
}, '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)
|
// live readout of the chosen file(s)
|
||||||
document.getElementById('addFile').addEventListener('change', async (e) => {
|
document.getElementById('addFile').addEventListener('change', async (e) => {
|
||||||
const info = document.getElementById('addFileInfo');
|
const info = document.getElementById('addFileInfo');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue