Веб-UI: htmx-своп действий на месте вместо PRG-редиректа
Мутирующие действия больше не уводят со страницы: undo/relink/retry/cancel
в списке свопят карточку (#card-{id}), на /download/{id} — #download-main;
петля ревью (refine/rerecognize) свопит #review-main и допалливает
recognizing до готового плана (fragment /fragments/downloads/{id}/review,
every 2s). Выходы ревью (apply/defer/cancel) остаются навигацией. Без htmx —
прежний PRG-редирект (деградация). Ошибка действия на htmx-пути — HTTP 200
с сообщением в фрагменте (ActionError), иначе htmx не свопит DOM.
Разметка вынесена в партиалы card/download_main/review_main (корень = элемент
с целевым id), различение поверхности — скрытым полем surface=list|download.
Извлечены buildCardView/buildDownloadView. handleSetProvider переведён на
reviewBlockAction (консистентность source-действий).
Реализация change htmx-action-swap (OpenSpec).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
{{define "download_main"}}
|
||||
<main class="main" id="download-main">
|
||||
<a class="back-link" href="/">← ко всем загрузкам</a>
|
||||
|
||||
{{if .Error}}<div class="result-banner" style="background:var(--st-err-bg);border-color:color-mix(in srgb,var(--st-err) 35%,transparent)"><h2 style="color:var(--st-err)">{{.Error}}</h2></div>{{end}}
|
||||
{{if .ActionError}}<div class="result-banner" style="background:var(--st-err-bg);border-color:color-mix(in srgb,var(--st-err) 35%,transparent)"><h2 style="color:var(--st-err)">{{.ActionError}}</h2></div>{{end}}
|
||||
|
||||
<!-- Шапка -->
|
||||
<div class="review-head" style="margin-bottom:var(--sp-4)">
|
||||
<div>
|
||||
<div class="review-title" style="font-size:var(--fs-xl)">{{.Title}}</div>
|
||||
<div class="hash-row" style="margin-top:9px">
|
||||
{{template "status_badge" .State}}
|
||||
<span class="hash">{{.ID}}</span>
|
||||
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.ID}}')">⧉ копировать</button>
|
||||
</div>
|
||||
{{if .Added}}<p class="muted" style="margin:var(--sp-2) 0 0">Добавлено {{.Added}} <span class="ago">({{.AddedAgo}})</span></p>{{end}}
|
||||
{{if .Note}}<p class="muted" style="margin:var(--sp-2) 0 0">{{.Note}}</p>{{end}}
|
||||
</div>
|
||||
<div class="card-acts" style="align-items:center">
|
||||
{{if .Reviewable}}<a class="btn btn-primary" href="/review/{{.ID}}">Ревью →</a>{{end}}
|
||||
{{if .Undoable}}<form method="post" action="/ui/downloads/{{.ID}}/undo" hx-post="/ui/downloads/{{.ID}}/undo" hx-target="#download-main" hx-swap="outerHTML"><input type="hidden" name="surface" value="download"><button class="btn btn-danger" type="submit">↩ Откатить</button></form>{{end}}
|
||||
{{if .Relinkable}}<form method="post" action="/ui/downloads/{{.ID}}/relink" hx-post="/ui/downloads/{{.ID}}/relink" hx-target="#download-main" hx-swap="outerHTML"><input type="hidden" name="surface" value="download"><button class="btn" type="submit">Привязать заново</button></form>{{end}}
|
||||
{{if .Retriable}}<form method="post" action="/ui/downloads/{{.ID}}/retry" hx-post="/ui/downloads/{{.ID}}/retry" hx-target="#download-main" hx-swap="outerHTML"><input type="hidden" name="surface" value="download"><button class="btn" type="submit">Повторить</button></form>{{end}}
|
||||
{{if not .Terminal}}<form method="post" action="/ui/downloads/{{.ID}}/cancel" hx-post="/ui/downloads/{{.ID}}/cancel" hx-target="#download-main" hx-swap="outerHTML"><input type="hidden" name="surface" value="download"><button class="btn btn-ghost" type="submit">Отменить</button></form>{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Распознано как -->
|
||||
<div class="section">
|
||||
<div class="section-head"><h2>Распознано как</h2></div>
|
||||
{{if .HasPlan}}
|
||||
<dl class="kv">
|
||||
<dt>Название</dt><dd>{{.RecTitle}}</dd>
|
||||
<dt>Ориг. название</dt><dd>{{if .OriginalTitle}}{{.OriginalTitle}}{{else}}<span class="faint">—</span>{{end}}</dd>
|
||||
<dt>Тип</dt><dd>{{if .IsSeries}}сериал{{else if eq .MediaType "movie"}}фильм{{else}}{{.MediaType}}{{end}}</dd>
|
||||
{{if .IsSeries}}<dt>Сезон</dt><dd>{{if .Season}}{{.Season}}{{else}}<span class="faint">—</span>{{end}}</dd>{{end}}
|
||||
<dt>Год</dt><dd>{{if .Year}}{{.Year}}{{else}}<span class="faint">—</span>{{end}}</dd>
|
||||
<dt>Режиссёр</dt><dd><span class="faint">—</span></dd>
|
||||
<dt>База</dt><dd>{{if .Provider}}{{if .MatchURL}}<a class="ext-link" href="{{.MatchURL}}" target="_blank" rel="noopener">{{.Provider}} · {{.ProviderID}} ↗</a>{{else}}{{.Provider}} · <span class="mono">{{.ProviderID}}</span>{{end}}{{else if .NoBase}}без базы{{else}}<span class="faint">—</span>{{end}}</dd>
|
||||
<dt>Уверенность</dt><dd class="mono">{{if .Confidence}}{{.Confidence}}{{else}}<span class="faint">—</span>{{end}}</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
<p class="muted" style="margin:0">Ещё не распознано.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- Файлы и раскладка -->
|
||||
{{if .HasPlan}}
|
||||
<div class="section">
|
||||
<div class="section-head"><h2>Файлы и раскладка</h2><span class="hint">файл источника → целевой хардлинк</span></div>
|
||||
{{template "layout_widget" .Files}}
|
||||
<p class="faint" style="font-size:var(--fs-xs);margin:var(--sp-3) 0 0">
|
||||
Целевые файлы связаны хардлинками — место не дублируется, исходник остаётся на раздаче.
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Раздача (живая статистика; секция деградирует, если торрент не сидирует) -->
|
||||
{{template "seeding" .Seeding}}
|
||||
|
||||
<!-- История -->
|
||||
<div class="section">
|
||||
<div class="section-head"><h2>История</h2></div>
|
||||
<ul class="timeline">
|
||||
{{if .CreatedAt}}<li class="tl-item is-done"><div class="tl-t">Добавлено</div><time>{{.CreatedAt}}</time></li>{{end}}
|
||||
<li class="tl-item is-now"><div class="tl-t">{{badgeLabel .State}} <span class="badge st-{{.State}}">текущее</span></div>{{if .UpdatedAt}}<time>{{.UpdatedAt}}</time>{{end}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Контекст -->
|
||||
{{if .Context}}
|
||||
<div class="section">
|
||||
<div class="section-head"><h2>Контекст</h2><span class="hint">сообщение при добавлении</span></div>
|
||||
<p style="margin:0;color:var(--text-dim)">«{{.Context}}»</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Информация о торренте -->
|
||||
<div class="section" style="margin-bottom:0">
|
||||
<div class="section-head"><h2>Информация о торренте</h2><span class="hint">сырой источник и infohash</span></div>
|
||||
<dl class="kv">
|
||||
<dt>Тип источника</dt><dd>{{.SourceType}}</dd>
|
||||
{{range .Infohashes}}<dt>infohash</dt><dd class="mono">{{.}}</dd>{{end}}
|
||||
</dl>
|
||||
{{if .SourceFull}}
|
||||
<div class="src-block">
|
||||
<code class="src-full">{{.SourceFull}}</code>
|
||||
<button class="copy-btn" type="button" onclick="copyHash(this,'{{.SourceFull}}')">⧉ копировать</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</main>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user