Files
jellybit/web/static/js/app.js
T
avandClaude Opus 4.8 c0b5ab7295 UI/UX списка и карточки загрузки: серверные фильтр/поиск/пагинация, матч-ссылка, имя раздачи (web-ui-list-detail)
- Список: серверные фильтр по группе состояний, поиск и пагинация (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>
2026-07-01 09:50:24 +03:00

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. Здесь клиентской фильтрации нет намеренно.