Перенос дизайна в server-rendered веб-UI (web-ui)
Новая capability web-ui: презентационный перенос готового дизайна
(семантический HTML + единый jellybit.css, тёмная тема по настройке ОС)
в html/template без шага сборки.
- Встроенная (go:embed) отдача статики под /static с Cache-Control и
cache-busting (?v=<hash> по содержимому css/js).
- Шрифты IBM Plex self-hosted (@font-face, cyrillic+latin), без CDN.
- Наколеночный менеджер зависимостей: вендор (htmx + шрифты) не хранится
в репо (gitignore), идемпотентно добывается `task assets` по
web/assets.manifest с проверкой sha256; task build/run зависят от assets.
- Общие партиалы: шапка, бейдж статуса (карта всех 14 состояний),
виджет «файл источника → раскладка» (общий для review и download).
- Страницы: список с фильтром/поиском, ревью, новая страница просмотра
загрузки (/download/{id}). deleted скрыт по умолчанию.
- Превью раскладки берётся из единой логики internal/layout
(buildFileRows), без дублирования правил имён в шаблонах.
- Убраны meta-refresh и инлайн-стили; copyHash на vanilla с fallback
на execCommand и честной индикацией (целевой деплой — HTTP LAN).
Вне scope (отдельный change): живые обновления прогресса и раздел
раздачи, клиентский режим ручной раскладки файл→серия (с Alpine.js).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,21 +46,13 @@ type reviewView struct {
|
||||
Confidence string
|
||||
Reasons []string
|
||||
Hints []string
|
||||
Files []reviewFileView
|
||||
Preview []string
|
||||
Files []fileRow
|
||||
HasPlan bool
|
||||
HasLinks bool // есть хотя бы один целевой путь → можно применять
|
||||
NoBase bool // выбрано «без базы»
|
||||
Candidates []candidateView
|
||||
}
|
||||
|
||||
type reviewFileView struct {
|
||||
Src string
|
||||
Role string
|
||||
Season string
|
||||
Episode string
|
||||
Ignored bool
|
||||
}
|
||||
|
||||
type candidateView struct {
|
||||
ID int64
|
||||
Provider string
|
||||
@@ -114,16 +106,9 @@ func (s *server) handleReview(w http.ResponseWriter, r *http.Request) {
|
||||
if rec.Confidence.Valid {
|
||||
view.Confidence = strconv.FormatFloat(rec.Confidence.Float64, 'f', 2, 64)
|
||||
}
|
||||
for _, f := range rd.Plan.Files {
|
||||
view.Files = append(view.Files, reviewFileView{
|
||||
Src: f.Src,
|
||||
Role: string(f.Role),
|
||||
Season: intPtrStr(f.Season),
|
||||
Episode: intPtrStr(f.Episode),
|
||||
Ignored: f.Role == "ignore",
|
||||
})
|
||||
}
|
||||
view.Files = buildFileRows(rd.Plan, rd.Preview)
|
||||
view.HasPlan = len(rd.Plan.Files) > 0
|
||||
view.HasLinks = len(rd.Preview) > 0
|
||||
for _, c := range rd.Candidates {
|
||||
view.Candidates = append(view.Candidates, candidateView{
|
||||
ID: c.ID,
|
||||
@@ -136,14 +121,8 @@ func (s *server) handleReview(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
}
|
||||
for _, l := range rd.Preview {
|
||||
view.Preview = append(view.Preview, l.Dst)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := s.review.Execute(w, view); err != nil {
|
||||
s.deps.Logger.Error("render review", "error", err)
|
||||
}
|
||||
s.render(w, "review.html", view)
|
||||
}
|
||||
|
||||
// --- Действия ревью (POST → redirect) ---
|
||||
@@ -281,10 +260,3 @@ func redirectReview(w http.ResponseWriter, r *http.Request, id int64, msg string
|
||||
}
|
||||
http.Redirect(w, r, u, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func intPtrStr(p *int) string {
|
||||
if p == nil {
|
||||
return "—"
|
||||
}
|
||||
return strconv.Itoa(*p)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user