- Список: серверные фильтр по группе состояний, поиск и пагинация (GET
f/q/page/all, по 25), сортировка по времени добавления в qBittorrent
(added_on) с фолбеком на created_at и tie-break по id.
- Заголовок загрузки = имя раздачи (display_name) → распознанное название →
усечённый источник; сырой magnet вынесен в блок «Информация о торренте».
- Матч метабазы показан ссылкой на запись (страница загрузки и ревью);
URL берётся у выбранного кандидата либо строится по provider+id и типу.
- Полировка вёрстки; клиентская JS-фильтрация убрана (всё серверное, без JS).
- Миграция 0005 (display_name, source_added_at); воркер однократно
фиксирует source_added_at при поллинге/усыновлении; ER-схема обновлена.
- OpenSpec: дельты влиты в specs/{web-ui,ingest}, change заархивирован.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
45 lines
1.7 KiB
JavaScript
45 lines
1.7 KiB
JavaScript
// Клиентская мелочь без сборки и фреймворков.
|
|
// 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);
|
|
}
|
|
|
|
// Фильтр, поиск и пагинация списка — серверные (GET-параметры f/q/page/all),
|
|
// работают без JS. Здесь клиентской фильтрации нет намеренно.
|