Веб-UI: основной идентификатор карточек — download.id вместо infohash
В карточке списка и шапке /download/{id} показываем и копируем download.id
(ULID) — тот же ключ, что в логах (download_id), удобно грепать. Infohash
остаётся в блоке «Информация о торренте». Поиск по списку расширен: матчит
любой идентификатор (download.id ИЛИ infohash), плюс название/контекст.
Удалены осиротевшие поля Infohash/InfohashShort и хелпер shortenHash.
Дельта web-ui влита в спеки, change заархивирован.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,6 @@ type downloadDetailView struct {
|
|||||||
Title string
|
Title string
|
||||||
SourceType string // тип источника (magnet/torrent/url) — блок «Информация о торренте»
|
SourceType string // тип источника (magnet/torrent/url) — блок «Информация о торренте»
|
||||||
SourceFull string // полный источник (magnet) — блок «Информация о торренте»
|
SourceFull string // полный источник (magnet) — блок «Информация о торренте»
|
||||||
Infohash string // первый хеш (для шапки)
|
|
||||||
InfohashShort string
|
|
||||||
Infohashes []string // все хеши загрузки (блок «Информация о торренте»)
|
Infohashes []string // все хеши загрузки (блок «Информация о торренте»)
|
||||||
Context string
|
Context string
|
||||||
State string
|
State string
|
||||||
@@ -88,8 +86,6 @@ func (s *server) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||||||
Title: detailTitle(d, rd),
|
Title: detailTitle(d, rd),
|
||||||
SourceType: string(d.SourceType),
|
SourceType: string(d.SourceType),
|
||||||
SourceFull: d.SourceRef,
|
SourceFull: d.SourceRef,
|
||||||
Infohash: d.PrimaryInfohash(),
|
|
||||||
InfohashShort: shortenHash(d.PrimaryInfohash()),
|
|
||||||
Infohashes: d.HashList(),
|
Infohashes: d.HashList(),
|
||||||
Context: d.Context,
|
Context: d.Context,
|
||||||
State: string(d.State),
|
State: string(d.State),
|
||||||
|
|||||||
@@ -181,8 +181,6 @@ type pageLink struct {
|
|||||||
type downloadView struct {
|
type downloadView struct {
|
||||||
ID string
|
ID string
|
||||||
Title string // отображаемый заголовок карточки
|
Title string // отображаемый заголовок карточки
|
||||||
Infohash string // полный (для копирования)
|
|
||||||
InfohashShort string // усечённый (для показа)
|
|
||||||
Context string
|
Context string
|
||||||
State string
|
State string
|
||||||
Error string
|
Error string
|
||||||
@@ -524,8 +522,6 @@ func toView(d store.Download) downloadView {
|
|||||||
return downloadView{
|
return downloadView{
|
||||||
ID: d.ID,
|
ID: d.ID,
|
||||||
Title: downloadTitle(d),
|
Title: downloadTitle(d),
|
||||||
Infohash: d.PrimaryInfohash(),
|
|
||||||
InfohashShort: shortenHash(d.PrimaryInfohash()),
|
|
||||||
Context: d.Context,
|
Context: d.Context,
|
||||||
State: state,
|
State: state,
|
||||||
Error: d.ErrorMsg.String,
|
Error: d.ErrorMsg.String,
|
||||||
@@ -560,14 +556,6 @@ func oneLine(s string) string {
|
|||||||
return strings.Join(strings.Fields(s), " ")
|
return strings.Join(strings.Fields(s), " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// shortenHash усекает infohash до вида "a1b2c3d4e5…f0" для компактного показа.
|
|
||||||
func shortenHash(h string) string {
|
|
||||||
if len(h) <= 12 {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
return h[:10] + "…" + h[len(h)-2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// desyncNote — пояснение состояния рассинхрона для UI (см. state-reconciliation).
|
// desyncNote — пояснение состояния рассинхрона для UI (см. state-reconciliation).
|
||||||
func desyncNote(s store.State) string {
|
func desyncNote(s store.State) string {
|
||||||
switch s {
|
switch s {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func statesInGroup(g StateGroup) []State {
|
|||||||
// ListFilter — параметры страницы списка загрузок.
|
// ListFilter — параметры страницы списка загрузок.
|
||||||
type ListFilter struct {
|
type ListFilter struct {
|
||||||
Group StateGroup // группа состояний ("" / all — без сужения)
|
Group StateGroup // группа состояний ("" / all — без сужения)
|
||||||
Query string // поиск по названию/infohash/контексту (подстрока)
|
Query string // поиск по id/названию/infohash/контексту (подстрока)
|
||||||
IncludeDeleted bool // показывать состояние deleted (только под all)
|
IncludeDeleted bool // показывать состояние deleted (только под all)
|
||||||
Limit int // размер страницы
|
Limit int // размер страницы
|
||||||
Offset int // смещение страницы
|
Offset int // смещение страницы
|
||||||
@@ -71,12 +71,13 @@ func listWhere(f ListFilter) (string, []any) {
|
|||||||
// ICU в чистом modernc-драйвере нет. Достаточно для инфохэша/латиницы;
|
// ICU в чистом modernc-драйвере нет. Достаточно для инфохэша/латиницы;
|
||||||
// кириллические названия ищем как введены.
|
// кириллические названия ищем как введены.
|
||||||
like := "%" + q + "%"
|
like := "%" + q + "%"
|
||||||
conds = append(conds, "(source_ref LIKE ? COLLATE NOCASE "+
|
conds = append(conds, "(download.id LIKE ? COLLATE NOCASE "+
|
||||||
|
"OR source_ref LIKE ? COLLATE NOCASE "+
|
||||||
"OR display_name LIKE ? COLLATE NOCASE "+
|
"OR display_name LIKE ? COLLATE NOCASE "+
|
||||||
"OR context LIKE ? COLLATE NOCASE "+
|
"OR context LIKE ? COLLATE NOCASE "+
|
||||||
"OR EXISTS (SELECT 1 FROM download_infohash dh "+
|
"OR EXISTS (SELECT 1 FROM download_infohash dh "+
|
||||||
"WHERE dh.download_id = download.id AND dh.infohash LIKE ? COLLATE NOCASE))")
|
"WHERE dh.download_id = download.id AND dh.infohash LIKE ? COLLATE NOCASE))")
|
||||||
args = append(args, like, like, like, like)
|
args = append(args, like, like, like, like, like)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conds) == 0 {
|
if len(conds) == 0 {
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-07-04
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
Изменение презентационное и уже реализовано в коде (шаблоны, поиск, view-структуры).
|
||||||
|
Дизайн-документ здесь короткий и нужен лишь как зависимость для `tasks` и чтобы
|
||||||
|
зафиксировать одно продуктовое решение. Полноценного двухчекпоинтного ревью
|
||||||
|
задача не требует (тривиальная — достаточно прохода по коду).
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Показывать и копировать в карточках `download.id` — тот же ключ, что в логах
|
||||||
|
(`download_id`), чтобы «скопировал → нашёл в логах» работало без перекодировки.
|
||||||
|
- Поиск по списку — по любому идентификатору загрузки (`download.id` ИЛИ infohash).
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Не убираем infohash из UI совсем: он остаётся в блоке «Информация о торренте».
|
||||||
|
- Не меняем модель данных, API-DTO и формат логов.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- **Идентификатор карточки — `download.id`, не infohash.** В логах основной ключ
|
||||||
|
связывания — `download_id` (см. `internal/worker/*`), поэтому именно его нужно
|
||||||
|
копировать и грепать. Infohash второстепенен и уместен только в детальном блоке
|
||||||
|
о торренте. Альтернатива (оставить infohash в шапке) отвергнута: заставляет
|
||||||
|
пользователя вручную сопоставлять хэш с id при чтении логов.
|
||||||
|
- **Поиск матчит любой идентификатор.** В LIKE-условие `internal/store/list.go`
|
||||||
|
добавлен `download.id` рядом с уже имевшимся infohash; название/контекст
|
||||||
|
сохранены. Так скопированный из карточки id находится обратным поиском.
|
||||||
|
- **Чистка мёртвого кода.** Поля `Infohash`/`InfohashShort` во view-структурах и
|
||||||
|
хелпер `shortenHash` стали не нужны — удалены, чтобы не копить неиспользуемое.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [ULID менее узнаваем на глаз, чем hex-infohash] → показываем полностью (26
|
||||||
|
символов помещаются в строку), рядом кнопка копирования; в детальном блоке
|
||||||
|
infohash остаётся для тех, кому он нужен.
|
||||||
|
- [Проза `## Purpose` в live-спеке упоминает «копирование infohash»] → правится
|
||||||
|
прямой правкой live-спеки при archive (Purpose не покрывается дельта-операциями).
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Веб-UI показывал в карточках (список + шапка страницы просмотра) infohash с
|
||||||
|
кнопкой копирования. Но в логах основной ключ загрузки — `download_id` (ULID),
|
||||||
|
именно по нему удобно грепать и связывать события; infohash в логах второстепенен.
|
||||||
|
Пользователю нужно копировать из карточки то, что реально ищется в логах.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- В карточке списка и в шапке `/download/{id}` показываем и копируем
|
||||||
|
`download.id` (ULID) вместо infohash — как основной идентификатор загрузки.
|
||||||
|
- Infohash остаётся видимым только в блоке «Информация о торренте» (без
|
||||||
|
изменений), где ему место рядом с сырым источником.
|
||||||
|
- Поиск по списку расширяем: искать можно по **любому идентификатору**
|
||||||
|
(`download.id` ИЛИ infohash), плюс прежние название/контекст. Плейсхолдер
|
||||||
|
строки поиска обновлён.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
<!-- нет -->
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `web-ui`: основной копируемый идентификатор карточек — `download.id`, а не
|
||||||
|
infohash (Requirement «Клиентские взаимодействия без сборки» и Scenario
|
||||||
|
копирования); поиск по списку матчит любой идентификатор загрузки (Scenario
|
||||||
|
«Серверный фильтр и поиск»).
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Шаблоны: `web/templates/index.html`, `web/templates/download.html`.
|
||||||
|
- Поиск: `internal/store/list.go` (в LIKE-условие добавлен `download.id`).
|
||||||
|
- View: `internal/httpapi/httpapi.go`, `internal/httpapi/download.go` (удалены
|
||||||
|
осиротевшие поля `Infohash`/`InfohashShort` и хелпер `shortenHash`).
|
||||||
|
- Клиент: `web/static/js/app.js` (комментарий — функция копирования теперь про
|
||||||
|
идентификатор загрузки, не только infohash).
|
||||||
|
- Совместимость: чисто презентационно; API-DTO и модель данных не меняются.
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Страницы веб-UI
|
||||||
|
|
||||||
|
Веб-UI SHALL предоставлять страницы: список загрузок с единым окном
|
||||||
|
добавления, **серверными фильтром по группе состояний, поиском и постраничной
|
||||||
|
выдачей (пагинацией)** (`/`), экран ревью одной загрузки (`/review/{id}`)
|
||||||
|
и страницу просмотра одной загрузки (`/download/{id}`) с распознаванием,
|
||||||
|
файлами→раскладкой, историей, блоком информации о торренте и — для сидирующих
|
||||||
|
задач — секцией живой статистики раздачи. Карточки активных (downloading)
|
||||||
|
загрузок в списке SHALL содержать индикатор прогресса. Фильтр, поиск и номер
|
||||||
|
страницы SHALL передаваться GET-параметрами запроса (например `f`, `q`, `page`)
|
||||||
|
и SHALL работать без клиентского JavaScript. Состояние `deleted` SHALL быть
|
||||||
|
скрыто в списке по умолчанию (с переключателем «показать всё»). Механика живого
|
||||||
|
обновления прогресса и наполнение секции раздачи определяются capability
|
||||||
|
`live-status`.
|
||||||
|
|
||||||
|
#### Scenario: Просмотр одной загрузки
|
||||||
|
|
||||||
|
- **WHEN** клиент открывает `GET /download/{id}` существующей загрузки
|
||||||
|
- **THEN** отрисовывается страница с её распознаванием, файлами, раскладкой и
|
||||||
|
историей
|
||||||
|
|
||||||
|
#### Scenario: Прогресс активной загрузки в списке
|
||||||
|
|
||||||
|
- **WHEN** в списке есть загрузка в состоянии `downloading`
|
||||||
|
- **THEN** её карточка содержит индикатор прогресса (прогресс-бар со скоростью
|
||||||
|
и ETA)
|
||||||
|
|
||||||
|
#### Scenario: Удалённые скрыты по умолчанию
|
||||||
|
|
||||||
|
- **WHEN** в списке есть загрузки в состоянии `deleted` и фильтр «показать
|
||||||
|
всё» не включён
|
||||||
|
- **THEN** они не отображаются, но доступны при включённом переключателе
|
||||||
|
|
||||||
|
#### Scenario: Пагинация списка
|
||||||
|
|
||||||
|
- **WHEN** загрузок под текущим фильтром больше, чем помещается на одну
|
||||||
|
страницу, и клиент запрашивает `GET /?page=N`
|
||||||
|
- **THEN** возвращается N-я страница результатов и элементы навигации по
|
||||||
|
страницам, сохраняющие текущие фильтр и поисковый запрос
|
||||||
|
|
||||||
|
#### Scenario: Серверный фильтр и поиск
|
||||||
|
|
||||||
|
- **WHEN** клиент запрашивает список с параметрами фильтра по состоянию и/или
|
||||||
|
строкой поиска (`GET /?f=review&q=дюна`)
|
||||||
|
- **THEN** сервер возвращает только подходящие загрузки (по группе состояний и
|
||||||
|
совпадению строки в названии, любом идентификаторе загрузки — `download.id`
|
||||||
|
ИЛИ infohash — и контексте), отфильтрованные на стороне БД, а не на клиенте
|
||||||
|
|
||||||
|
### Requirement: Клиентские взаимодействия без сборки
|
||||||
|
|
||||||
|
Веб-UI SHALL реализовывать клиентскую логику без шага сборки и без реактивных
|
||||||
|
фреймворков: копирование идентификатора загрузки (vanilla JS) и раскрытие
|
||||||
|
контекста нативным `<details>`. Основной копируемый идентификатор в карточке
|
||||||
|
списка и в шапке страницы просмотра SHALL быть `download.id` (ULID) — тот же
|
||||||
|
ключ, что пишется в логи (`download_id`). Все действия над загрузкой SHALL
|
||||||
|
выполняться через формы/htmx (раундтрип на сервер), без клиентского пересчёта
|
||||||
|
доменного состояния.
|
||||||
|
|
||||||
|
#### Scenario: Копирование идентификатора загрузки
|
||||||
|
|
||||||
|
- **WHEN** пользователь нажимает кнопку копирования рядом с идентификатором
|
||||||
|
загрузки (`download.id`) в карточке списка или шапке страницы просмотра
|
||||||
|
- **THEN** значение `download.id` копируется в буфер обмена без перезагрузки
|
||||||
|
страницы
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
## 1. Карточки: идентификатор = download.id
|
||||||
|
|
||||||
|
- [x] 1.1 `web/templates/index.html` — в карточке списка показать и копировать
|
||||||
|
`{{.ID}}` вместо infohash; убрать `{{if .Infohash}}`
|
||||||
|
- [x] 1.2 `web/templates/download.html` — в шапке страницы просмотра показать и
|
||||||
|
копировать `{{.ID}}` вместо infohash
|
||||||
|
- [x] 1.3 `internal/httpapi/httpapi.go`, `internal/httpapi/download.go` — удалить
|
||||||
|
осиротевшие поля `Infohash`/`InfohashShort` и хелпер `shortenHash`
|
||||||
|
- [x] 1.4 `web/static/js/app.js` — комментарий: функция копирования теперь про
|
||||||
|
идентификатор загрузки (не только infohash)
|
||||||
|
|
||||||
|
## 2. Поиск по любому идентификатору
|
||||||
|
|
||||||
|
- [x] 2.1 `internal/store/list.go` — добавить `download.id` в LIKE-условие
|
||||||
|
поиска (рядом с infohash/названием/контекстом); обновить комментарий `Query`
|
||||||
|
- [x] 2.2 `web/templates/index.html` — обновить плейсхолдер строки поиска
|
||||||
|
|
||||||
|
## 3. Спека и проверки
|
||||||
|
|
||||||
|
- [x] 3.1 Дельта-спека `web-ui`: MODIFIED «Страницы веб-UI» (поле поиска) и
|
||||||
|
«Клиентские взаимодействия без сборки» (копирование идентификатора)
|
||||||
|
- [x] 3.2 Обновить прозу `## Purpose` в live-спеке `openspec/specs/web-ui/spec.md`
|
||||||
|
(упоминание «копирование infohash») — при archive
|
||||||
|
- [x] 3.3 `openspec validate --strict` для change
|
||||||
|
- [x] 3.4 `task test` / `task lint` — зелёные
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
Презентационный слой веб-интерфейса: встроенная (`go:embed`) отдача статики и
|
Презентационный слой веб-интерфейса: встроенная (`go:embed`) отдача статики и
|
||||||
self-hosted шрифтов, единая дизайн-система (`jellybit.css`, тёмная тема по
|
self-hosted шрифтов, единая дизайн-система (`jellybit.css`, тёмная тема по
|
||||||
настройке ОС), рендеринг страниц (список загрузок, ревью, просмотр) с бейджами
|
настройке ОС), рендеринг страниц (список загрузок, ревью, просмотр) с бейджами
|
||||||
состояний и клиентскими взаимодействиями без сборки (копирование infohash,
|
состояний и клиентскими взаимодействиями без сборки (копирование идентификатора
|
||||||
спойлер контекста). Превью раскладки берётся из единой логики `internal/layout`,
|
загрузки, спойлер контекста). Превью раскладки берётся из единой логики `internal/layout`,
|
||||||
а не дублируется в шаблонах. Тонкий транспорт над ядром (приём — `ingest`,
|
а не дублируется в шаблонах. Тонкий транспорт над ядром (приём — `ingest`,
|
||||||
команды — `worker`, чтение — `store`).
|
команды — `worker`, чтение — `store`).
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -130,8 +130,8 @@ self-hosted шрифтов, единая дизайн-система (`jellybit.
|
|||||||
- **WHEN** клиент запрашивает список с параметрами фильтра по состоянию и/или
|
- **WHEN** клиент запрашивает список с параметрами фильтра по состоянию и/или
|
||||||
строкой поиска (`GET /?f=review&q=дюна`)
|
строкой поиска (`GET /?f=review&q=дюна`)
|
||||||
- **THEN** сервер возвращает только подходящие загрузки (по группе состояний и
|
- **THEN** сервер возвращает только подходящие загрузки (по группе состояний и
|
||||||
совпадению строки в названии/infohash/контексте), отфильтрованные на стороне
|
совпадению строки в названии, любом идентификаторе загрузки — `download.id`
|
||||||
БД, а не на клиенте
|
ИЛИ infohash — и контексте), отфильтрованные на стороне БД, а не на клиенте
|
||||||
|
|
||||||
### Requirement: Порядок списка загрузок
|
### Requirement: Порядок списка загрузок
|
||||||
|
|
||||||
@@ -262,14 +262,18 @@ jellybit (`created_at`). Порядок MUST быть согласован ме
|
|||||||
### Requirement: Клиентские взаимодействия без сборки
|
### Requirement: Клиентские взаимодействия без сборки
|
||||||
|
|
||||||
Веб-UI SHALL реализовывать клиентскую логику без шага сборки и без реактивных
|
Веб-UI SHALL реализовывать клиентскую логику без шага сборки и без реактивных
|
||||||
фреймворков: копирование infohash (vanilla JS) и раскрытие контекста нативным
|
фреймворков: копирование идентификатора загрузки (vanilla JS) и раскрытие
|
||||||
`<details>`. Все действия над загрузкой SHALL выполняться через формы/htmx
|
контекста нативным `<details>`. Основной копируемый идентификатор в карточке
|
||||||
(раундтрип на сервер), без клиентского пересчёта доменного состояния.
|
списка и в шапке страницы просмотра SHALL быть `download.id` (ULID) — тот же
|
||||||
|
ключ, что пишется в логи (`download_id`). Все действия над загрузкой SHALL
|
||||||
|
выполняться через формы/htmx (раундтрип на сервер), без клиентского пересчёта
|
||||||
|
доменного состояния.
|
||||||
|
|
||||||
#### Scenario: Копирование infohash
|
#### Scenario: Копирование идентификатора загрузки
|
||||||
|
|
||||||
- **WHEN** пользователь нажимает кнопку копирования рядом с infohash
|
- **WHEN** пользователь нажимает кнопку копирования рядом с идентификатором
|
||||||
- **THEN** значение infohash копируется в буфер обмена без перезагрузки
|
загрузки (`download.id`) в карточке списка или шапке страницы просмотра
|
||||||
|
- **THEN** значение `download.id` копируется в буфер обмена без перезагрузки
|
||||||
страницы
|
страницы
|
||||||
|
|
||||||
#### Scenario: Раскрытие контекста без JS
|
#### Scenario: Раскрытие контекста без JS
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// Клиентская мелочь без сборки и фреймворков.
|
// Клиентская мелочь без сборки и фреймворков.
|
||||||
// htmx тянет серверные фрагменты, здесь — только то, что серверу знать не нужно.
|
// htmx тянет серверные фрагменты, здесь — только то, что серверу знать не нужно.
|
||||||
|
|
||||||
// Копирование infohash в буфер обмена. На целевом деплое (домашний LAN по HTTP)
|
// Копирование идентификатора (download id / infohash) в буфер обмена. На целевом
|
||||||
// navigator.clipboard недоступен — secure context только на HTTPS/localhost,
|
// деплое (домашний LAN по HTTP) navigator.clipboard недоступен — secure context
|
||||||
// поэтому есть fallback на execCommand, а индикация честная: «✓» лишь при успехе.
|
// только на HTTPS/localhost, поэтому есть fallback на execCommand, а индикация
|
||||||
|
// честная: «✓» лишь при успехе.
|
||||||
function copyHash(btn, full) {
|
function copyHash(btn, full) {
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
navigator.clipboard.writeText(full).then(
|
navigator.clipboard.writeText(full).then(
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
<div class="review-title" style="font-size:var(--fs-xl)">{{.Title}}</div>
|
<div class="review-title" style="font-size:var(--fs-xl)">{{.Title}}</div>
|
||||||
<div class="hash-row" style="margin-top:9px">
|
<div class="hash-row" style="margin-top:9px">
|
||||||
{{template "status_badge" .State}}
|
{{template "status_badge" .State}}
|
||||||
{{if .Infohash}}<span class="hash">{{.InfohashShort}}</span>
|
<span class="hash">{{.ID}}</span>
|
||||||
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.Infohash}}')">⧉ копировать</button>{{end}}
|
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.ID}}')">⧉ копировать</button>
|
||||||
</div>
|
</div>
|
||||||
{{if .Note}}<p class="muted" style="margin:var(--sp-2) 0 0">{{.Note}}</p>{{end}}
|
{{if .Note}}<p class="muted" style="margin:var(--sp-2) 0 0">{{.Note}}</p>{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<form class="search" method="get" action="/">
|
<form class="search" method="get" action="/">
|
||||||
<span class="ico">⌕</span>
|
<span class="ico">⌕</span>
|
||||||
<input class="input" type="search" name="q" value="{{.Query}}"
|
<input class="input" type="search" name="q" value="{{.Query}}"
|
||||||
placeholder="Поиск по названию или infohash…">
|
placeholder="Поиск по названию, id или infohash…">
|
||||||
{{if ne .Filter "all"}}<input type="hidden" name="f" value="{{.Filter}}">{{end}}
|
{{if ne .Filter "all"}}<input type="hidden" name="f" value="{{.Filter}}">{{end}}
|
||||||
{{if .ShowAll}}<input type="hidden" name="all" value="1">{{end}}
|
{{if .ShowAll}}<input type="hidden" name="all" value="1">{{end}}
|
||||||
</form>
|
</form>
|
||||||
@@ -56,12 +56,10 @@
|
|||||||
<div class="card-top">
|
<div class="card-top">
|
||||||
<div>
|
<div>
|
||||||
<div class="card-title"><a href="/download/{{.ID}}" style="color:inherit">{{.Title}}</a></div>
|
<div class="card-title"><a href="/download/{{.ID}}" style="color:inherit">{{.Title}}</a></div>
|
||||||
{{if .Infohash}}
|
|
||||||
<div class="hash-row">
|
<div class="hash-row">
|
||||||
<span class="hash">{{.InfohashShort}}</span>
|
<span class="hash">{{.ID}}</span>
|
||||||
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.Infohash}}')">⧉ копировать</button>
|
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.ID}}')">⧉ копировать</button>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
{{template "status_badge" .State}}
|
{{template "status_badge" .State}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user