Список: скрытие cancelled, значок типа, ellipsis имени .torrent

Три мелкие правки веб-UI списка загрузок:

- Скрываем cancelled в общем списке наравне с deleted: под группой all
  без IncludeDeleted теперь `state NOT IN (deleted, cancelled)`. reverted
  остаётся видимым. Тумблер и комментарии-инварианты приведены в
  соответствие («включая отменённые и удалённые»).
- Значок типа (🎬 фильм / 📺 сериал) в строке списка: media_type текущей
  попытки распознавания протянут через LEFT JOIN в ListDownloadsPage
  (Download.RecMediaType, симметрично RecTitle) и downloadView.MediaType;
  значок рендерится в card.html, при нераспознанном типе значка нет.
- Ellipsis для длинного имени .torrent-файла: у .btn-file max-width +
  overflow/ellipsis на лейбле, чтобы длинное имя не распирало .add-row;
  показываем базовое имя без .torrent, полное — в title (JS-энхансмент).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-11 09:31:27 +03:00
co-authored by Claude Opus 4.8
parent e8ce828296
commit 44c5952e49
7 changed files with 74 additions and 20 deletions
+48 -6
View File
@@ -45,6 +45,7 @@ func TestListDownloadsPageFilterAndDeleted(t *testing.T) {
active := mkDownload(t, st, 2, StateDownloading, "Fargo")
done := mkDownload(t, st, 3, StateDone, "Оно")
deleted := mkDownload(t, st, 4, StateDeleted, "Старьё")
cancelled := mkDownload(t, st, 5, StateCancelled, "Отменённое")
// Группа review — только review/deferred.
page, total, err := st.ListDownloadsPage(ctx, ListFilter{Group: GroupReview, Limit: 50})
@@ -55,29 +56,44 @@ func TestListDownloadsPageFilterAndDeleted(t *testing.T) {
t.Fatalf("review group = %v (total %d), want [%s]", ids(page), total, review)
}
// all: deleted скрыт по умолчанию.
// all: deleted и cancelled скрыты по умолчанию (скрытые терминальные).
page, total, err = st.ListDownloadsPage(ctx, ListFilter{Group: GroupAll, Limit: 50})
if err != nil {
t.Fatal(err)
}
if total != 3 {
t.Fatalf("all (без deleted) total = %d, want 3; ids=%v", total, ids(page))
t.Fatalf("all (без скрытых терминальных) total = %d, want 3; ids=%v", total, ids(page))
}
for _, d := range page {
if d.ID == deleted {
t.Fatalf("deleted не должен быть виден по умолчанию")
}
if d.ID == cancelled {
t.Fatalf("cancelled не должен быть виден по умолчанию")
}
}
_ = active
_ = done
// all + IncludeDeleted: показываем всё.
_, total, err = st.ListDownloadsPage(ctx, ListFilter{Group: GroupAll, IncludeDeleted: true, Limit: 50})
// all + IncludeDeleted: показываем всё, включая deleted и cancelled.
page, total, err = st.ListDownloadsPage(ctx, ListFilter{Group: GroupAll, IncludeDeleted: true, Limit: 50})
if err != nil {
t.Fatal(err)
}
if total != 4 {
t.Fatalf("all+deleted total = %d, want 4", total)
if total != 5 {
t.Fatalf("all+скрытые терминальные total = %d, want 5", total)
}
var seenDeleted, seenCancelled bool
for _, d := range page {
switch d.ID {
case deleted:
seenDeleted = true
case cancelled:
seenCancelled = true
}
}
if !seenDeleted || !seenCancelled {
t.Fatalf("под IncludeDeleted должны быть видны deleted и cancelled: deleted=%v cancelled=%v", seenDeleted, seenCancelled)
}
}
@@ -278,6 +294,32 @@ func TestListDownloadsPageRecTitleFallback(t *testing.T) {
}
}
func TestListDownloadsPageRecMediaType(t *testing.T) {
st := newTestStore(t)
ctx := context.Background()
// Тип текущей попытки распознавания подтягивается join'ом для значка списка.
id := mkDownload(t, st, 1, StateReview, "Fargo")
if _, err := st.CreateRecognition(ctx, &Recognition{
DownloadID: id,
MediaType: NullString("series"),
Title: NullString("Fargo"),
}, nil); err != nil {
t.Fatal(err)
}
page, _, err := st.ListDownloadsPage(ctx, ListFilter{Group: GroupAll, Limit: 50})
if err != nil {
t.Fatal(err)
}
if len(page) != 1 {
t.Fatalf("len = %d", len(page))
}
if !page[0].RecMediaType.Valid || page[0].RecMediaType.String != "series" {
t.Fatalf("RecMediaType = %q (valid=%v), want series", page[0].RecMediaType.String, page[0].RecMediaType.Valid)
}
}
func TestCreateDownloadPersistsDisplayName(t *testing.T) {
st := newTestStore(t)
ctx := context.Background()