diff --git a/public/css/styles.css b/public/css/styles.css index bea9e79..d50e709 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -409,6 +409,23 @@ code.inline { background: var(--bg-3); border: 1px solid var(--line); border-rad .modal .mbody { padding: 16px 18px; } .modal .mfoot { padding: 12px 18px; border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; } .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 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; diff --git a/public/js/app.js b/public/js/app.js index 3e0bc0b..66eedf5 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -496,6 +496,8 @@ async function confirmDelete(hashes) { function openAddModal() { const cats = '' + state.meta.categories.map((c) => ``).join(''); + const tagOpts = state.meta.tags.map((t) => + ``).join(''); openModal('Add torrent', `
@@ -504,9 +506,15 @@ function openAddModal() {
- - ${state.meta.tags.length ? `${state.meta.tags.map((t) => `` : ''}
+
+ + +
@@ -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 = ''; + 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');