{{ ui::textarea(name="description", id="description", rows="5", value=v_desc) }}
diff --git a/src/controllers/admin_categories.rs b/src/controllers/admin_categories.rs
index 8d11121..d2b5286 100644
--- a/src/controllers/admin_categories.rs
+++ b/src/controllers/admin_categories.rs
@@ -49,10 +49,6 @@ async fn parse_category_fields(
.text("name")
.ok_or_else(|| Error::BadRequest("category name is required".to_string()))?;
let description = form.text("description");
- let position = form
- .text("position")
- .and_then(|s| s.parse::().ok())
- .unwrap_or(0);
let published = form.checked("published");
// Resolve the chosen parent, rejecting cycles: a category may not be its
@@ -81,6 +77,28 @@ async fn parse_category_fields(
None => None,
};
+ // Position is optional: an explicit value sorts the category among its
+ // siblings, but a blank field appends it to the end of its parent's group
+ // (one past the current max), so new categories land last instead of first.
+ let position = match form.text("position").and_then(|s| s.parse::().ok()) {
+ Some(explicit) => explicit,
+ None => {
+ let mut query = categories::Entity::find();
+ query = match parent_id {
+ Some(pid) => query.filter(categories::Column::ParentId.eq(pid)),
+ None => query.filter(categories::Column::ParentId.is_null()),
+ };
+ query
+ .all(&ctx.db)
+ .await?
+ .iter()
+ .filter(|c| Some(c.id) != current_id)
+ .map(|c| c.position)
+ .max()
+ .map_or(0, |max| max + 1)
+ }
+ };
+
let desired = form
.text("slug")
.map(|s| slugify(&s))