Fix item save feedback, redesign public download list
Return 'Saved.' HTML for HTMX item update requests instead of raw
JSON. Redesign public item page version history as a flat download
list showing version number, label (or filename), size, and
download button. Renamed section from "Version History" to
"Downloads".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 files changed,
+19 insertions,
-14 deletions
| 165 |
165 |
|
#[tracing::instrument(skip_all, name = "items::update_item", fields(item_id))]
|
| 166 |
166 |
|
pub(in crate::routes::api) async fn update_item(
|
| 167 |
167 |
|
State(state): State<AppState>,
|
|
168 |
+ |
headers: HeaderMap,
|
| 168 |
169 |
|
AuthUser(user): AuthUser,
|
| 169 |
170 |
|
Path(id): Path<ItemId>,
|
| 170 |
171 |
|
Form(req): Form<UpdateItemRequest>,
|
| 171 |
|
- |
) -> Result<impl IntoResponse> {
|
|
172 |
+ |
) -> Result<Response> {
|
| 172 |
173 |
|
tracing::Span::current().record("item_id", tracing::field::display(&id));
|
| 173 |
174 |
|
user.check_not_suspended()?;
|
| 174 |
175 |
|
verify_item_ownership(&state, id, user.id).await?;
|
| 254 |
255 |
|
|
| 255 |
256 |
|
db::projects::bump_cache_generation(&state.db, updated.project_id).await?;
|
| 256 |
257 |
|
|
|
258 |
+ |
if is_htmx_request(&headers) {
|
|
259 |
+ |
return Ok(axum::response::Html("Saved.".to_string()).into_response());
|
|
260 |
+ |
}
|
|
261 |
+ |
|
| 257 |
262 |
|
Ok(Json(ItemResponse {
|
| 258 |
263 |
|
id: updated.id,
|
| 259 |
264 |
|
project_id: updated.project_id,
|
| 266 |
271 |
|
web_only: updated.web_only,
|
| 267 |
272 |
|
ai_tier: updated.ai_tier,
|
| 268 |
273 |
|
ai_disclosure: updated.ai_disclosure,
|
| 269 |
|
- |
}))
|
|
274 |
+ |
}).into_response())
|
| 270 |
275 |
|
}
|
| 271 |
276 |
|
|
| 272 |
277 |
|
/// Soft-delete an item owned by the authenticated user (recoverable for 7 days).
|
| 366 |
366 |
|
|
| 367 |
367 |
|
{% if !versions.is_empty() %}
|
| 368 |
368 |
|
<section class="item-versions">
|
| 369 |
|
- |
<h2>Version History</h2>
|
|
369 |
+ |
<h2>Downloads</h2>
|
| 370 |
370 |
|
{% for version in versions %}
|
| 371 |
|
- |
<div class="version-item">
|
| 372 |
|
- |
<div class="version-header">
|
| 373 |
|
- |
<span class="version-number">v{{ version.number }}</span>
|
| 374 |
|
- |
<span class="version-date">{{ version.uploaded_date }}</span>
|
| 375 |
|
- |
</div>
|
| 376 |
|
- |
{% if version.has_file %}
|
| 377 |
|
- |
<div class="version-download" style="margin-top: 0.5rem;">
|
| 378 |
|
- |
<span class="version-notes" style="margin-right: 1rem;">{{ version.size }}</span>
|
| 379 |
|
- |
<button class="secondary" style="padding: 0.4rem 0.8rem; font-size: 0.85rem;"
|
| 380 |
|
- |
onclick="downloadVersion('{{ version.id }}')">Download</button>
|
| 381 |
|
- |
</div>
|
|
371 |
+ |
{% if version.has_file %}
|
|
372 |
+ |
<div style="display: flex; align-items: center; gap: 1rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border);">
|
|
373 |
+ |
<span style="min-width: 4em; font-family: var(--font-mono); font-size: 0.9rem;">v{{ version.number }}</span>
|
|
374 |
+ |
{% if let Some(label) = version.label %}
|
|
375 |
+ |
<span style="flex: 1;">{{ label }}</span>
|
|
376 |
+ |
{% else %}
|
|
377 |
+ |
<span style="flex: 1; opacity: 0.5;">{% match version.file_name %}{% when Some with (name) %}{{ name }}{% when None %}Download{% endmatch %}</span>
|
| 382 |
378 |
|
{% endif %}
|
|
379 |
+ |
<span style="font-size: 0.85rem; opacity: 0.6; min-width: 5em; text-align: right;">{{ version.size }}</span>
|
|
380 |
+ |
<button class="secondary" style="padding: 0.4rem 0.8rem; font-size: 0.85rem;"
|
|
381 |
+ |
onclick="downloadVersion('{{ version.id }}')">Download</button>
|
| 383 |
382 |
|
</div>
|
|
383 |
+ |
{% endif %}
|
| 384 |
384 |
|
{% endfor %}
|
| 385 |
385 |
|
</section>
|
| 386 |
386 |
|
{% endif %}
|