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 ? `` : ''}
+
+
+
+
@@ -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');