// Клиентская мелочь без сборки и фреймворков. // htmx тянет серверные фрагменты, здесь — только то, что серверу знать не нужно. // Копирование infohash в буфер обмена. На целевом деплое (домашний LAN по HTTP) // navigator.clipboard недоступен — secure context только на HTTPS/localhost, // поэтому есть fallback на execCommand, а индикация честная: «✓» лишь при успехе. function copyHash(btn, full) { if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(full).then( () => flashCopy(btn, true), () => flashCopy(btn, legacyCopy(full)), ); } else { flashCopy(btn, legacyCopy(full)); } } function legacyCopy(text) { try { const ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.focus(); ta.select(); const ok = document.execCommand('copy'); document.body.removeChild(ta); return ok; } catch (e) { return false; } } function flashCopy(btn, ok) { const old = btn.dataset.label || btn.textContent; btn.dataset.label = old; btn.textContent = ok ? '✓ скопировано' : '✗ не вышло'; btn.classList.toggle('copied', ok); setTimeout(() => { btn.textContent = old; btn.classList.remove('copied'); }, 1400); } // --- Фильтр/поиск по списку загрузок (по уже отрисованным карточкам) --- let activeFilter = 'all'; function setFilter(el) { document.querySelectorAll('#chips .chip').forEach((c) => c.classList.remove('active')); el.classList.add('active'); activeFilter = el.dataset.f; applyFilters(); } function applyFilters() { const searchEl = document.querySelector('#list-toolbar input[type=search]'); const q = (searchEl ? searchEl.value : '').toLowerCase().trim(); const showAllEl = document.getElementById('showAll'); const showAll = showAllEl ? showAllEl.checked : false; let visible = 0; document.querySelectorAll('#list .card').forEach((card) => { const grp = card.dataset.group; const isDeleted = card.dataset.deleted === '1'; let ok = true; if (isDeleted && !showAll) ok = false; if (ok && activeFilter !== 'all' && grp !== activeFilter) ok = false; if (ok && q && !(card.dataset.text || '').includes(q)) ok = false; card.style.display = ok ? '' : 'none'; if (ok) visible += 1; }); const empty = document.getElementById('empty'); if (empty) empty.style.display = visible ? 'none' : ''; } // Применить начальный фильтр (скрыть удалённые) после загрузки. document.addEventListener('DOMContentLoaded', applyFilters);