feat: complete UI migration to Tailwind CSS + HTMX

This commit is contained in:
Your Name
2026-07-18 22:20:40 +05:00
parent baf0e858aa
commit 5220c4051b
19 changed files with 1497 additions and 1501 deletions
+102 -8
View File
@@ -2020,8 +2020,9 @@ async def sources_list(request: Request, q: str = "", status_filter: str = "", p
item["last_checked_at_fmt"] = format_dt(item.get("last_checked_at"))
item["created_at_fmt"] = format_dt(item.get("created_at"))
sources.append(item)
template_name = "sources_content.html" if request.headers.get("hx-request") else "sources.html"
return templates.TemplateResponse(
"sources.html",
template_name,
base_context(
request,
user,
@@ -2317,6 +2318,25 @@ async def source_toggle(request: Request, source_id: int, csrf_token: str = Form
source_id,
)
await audit(user["id"], "source.toggle", "source", source_id, {"active": bool(row["active"]) if row else None})
if request.headers.get("hx-request"):
# Fetch the updated source
source_row = await pool.fetchrow("SELECT * FROM sources WHERE id=$1", source_id)
if source_row:
s = dict(source_row)
# Fetch stats just for this source to render correctly
stats = await pool.fetchrow(
"""
SELECT COUNT(*) AS total_posts,
COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '24 hours') AS recent_posts
FROM raw_posts
WHERE source_id=$1
""",
source_id
)
s["posts_count"] = stats["total_posts"]
s["posts_24h"] = stats["recent_posts"]
s["last_parsed_at_fmt"] = s["last_parsed_at"].strftime("%d.%m.%Y %H:%M") if s.get("last_parsed_at") else None
return templates.TemplateResponse("source_row.html", base_context(request, user, s=s))
return redirect("/sources")
@@ -2329,11 +2349,12 @@ async def source_delete(request: Request, source_id: int, csrf_token: str = Form
pool = await get_pool()
await pool.execute("UPDATE sources SET archived_at=NOW(), active=FALSE, updated_at=NOW() WHERE id=$1", source_id)
await audit(user["id"], "source.archive", "source", source_id)
if request.headers.get("hx-request"):
return HTMLResponse("")
return redirect("/sources")
@app.get("/raw", response_class=HTMLResponse)
@app.get("/v2/raw", response_class=HTMLResponse)
async def raw_posts(
request: Request,
status_filter: str = "",
@@ -2482,11 +2503,7 @@ async def raw_posts(
)
posts = [prepare_raw_post(row) for row in rows]
is_v2 = request.url.path.startswith("/v2")
if is_v2:
template_name = "v2/raw_posts_content.html" if request.headers.get("hx-request") else "v2/raw_posts.html"
else:
template_name = "raw_posts.html"
template_name = "raw_posts_content.html" if request.headers.get("hx-request") else "raw_posts.html"
return templates.TemplateResponse(
template_name,
@@ -2567,6 +2584,58 @@ async def raw_send_to_rewrite(
return redirect(safe_next)
async def fetch_single_editor_row(pool, post_id: int):
return await pool.fetchrow(
"""
SELECT rp.*, s.name AS source_name, s.tag AS source_tag, s.platform AS source_platform, s.url AS source_url,
u.login AS reviewed_by_login,
COALESCE(c_id.tag, c_name.tag) AS resolved_category_tag,
COUNT(rpm.id) AS media_count,
COALESCE(
jsonb_agg(
jsonb_build_object(
'id', rpm.id,
'type', rpm.media_type,
'url', rpm.original_url,
'status', rpm.status,
'error', rpm.error,
'duration_sec', rpm.duration_sec
)
ORDER BY rpm.sort_order ASC, rpm.id ASC
) FILTER (WHERE rpm.id IS NOT NULL),
'[]'::jsonb
) AS media_items,
COALESCE(
(
SELECT jsonb_agg(
jsonb_build_object(
'platform', pp.platform,
'status', pp.status,
'target_id', pp.target_id,
'external_id', pp.external_id,
'url', pp.url,
'error', pp.error,
'published_at', pp.published_at
)
ORDER BY pp.platform
)
FROM post_publications pp
WHERE pp.raw_post_id=rp.id
),
'[]'::jsonb
) AS platform_publications
FROM raw_posts rp
JOIN sources s ON s.id=rp.source_id
LEFT JOIN admin_users u ON u.id=rp.reviewed_by
LEFT JOIN content_categories c_id ON c_id.sort_order=COALESCE(rp.final_category_id, rp.rewrite_category_id)
LEFT JOIN content_categories c_name ON lower(c_name.name)=lower(COALESCE(rp.final_category, rp.rewrite_category, ''))
LEFT JOIN raw_post_media rpm ON rpm.raw_post_id=rp.id AND COALESCE(rpm.editor_hidden, FALSE)=FALSE
WHERE rp.id = $1
GROUP BY rp.id, s.name, s.tag, s.platform, s.url, u.login, c_id.tag, c_name.tag
""",
post_id
)
@app.get("/editor", response_class=HTMLResponse)
async def editor_feed(
request: Request,
@@ -2709,8 +2778,9 @@ async def editor_feed(
"""
)
counts = {str(row["status"]): int(row["count"]) for row in counts_rows}
template_name = "editor_content.html" if request.headers.get("hx-request") else "editor.html"
return templates.TemplateResponse(
"editor.html",
template_name,
base_context(
request,
user,
@@ -2782,6 +2852,14 @@ async def editor_accept(request: Request, post_id: int, csrf_token: str = Form(.
user["id"],
)
await audit(user["id"], "editor.accept", "raw_post", post_id)
if request.headers.get("hx-request"):
row = await fetch_single_editor_row(pool, post_id)
if row:
categories = await writer_category_options()
return templates.TemplateResponse(
"editor_post.html",
base_context(request, user, p=prepare_editor_post(row), categories=categories)
)
return redirect("/editor")
@@ -2807,6 +2885,14 @@ async def editor_reject(request: Request, post_id: int, csrf_token: str = Form(.
user["id"],
)
await audit(user["id"], "editor.reject", "raw_post", post_id)
if request.headers.get("hx-request"):
row = await fetch_single_editor_row(pool, post_id)
if row:
categories = await writer_category_options()
return templates.TemplateResponse(
"editor_post.html",
base_context(request, user, p=prepare_editor_post(row), categories=categories)
)
return redirect("/editor")
@@ -2934,6 +3020,14 @@ async def editor_save(
user["id"],
)
await audit(user["id"], "editor.save" if status_value == "edited" else "editor.save_accept", "raw_post", post_id)
if request.headers.get("hx-request"):
row = await fetch_single_editor_row(pool, post_id)
if row:
categories = await writer_category_options()
return templates.TemplateResponse(
"editor_post.html",
base_context(request, user, p=prepare_editor_post(row), categories=categories)
)
return redirect("/editor")