commit e57ad4bcdeca11e8c57bf6e874ea0d8adb511c09
parent cfcd136f9f62f5e20df384f95af056ed25f7e8c4
Author: Silas Brack <silasbrack@gmail.com>
Date: Sun, 7 Jun 2026 18:23:58 +0200
feat: restore tag selection using hidden field with JS
Use a hidden input populated by JS on checkbox change to send tags
as a comma-separated string, avoiding serde_urlencoded's inability
to handle repeated form keys.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/src/handlers/story.rs b/src/handlers/story.rs
@@ -139,11 +139,17 @@ pub async fn show_submit(
Ok(Html(app.render()?))
}
+fn parse_tags(s: &str) -> Vec<i64> {
+ s.split(',').filter_map(|s| s.trim().parse().ok()).collect()
+}
+
#[derive(Debug, Deserialize)]
pub struct SubmitForm {
pub title: String,
pub url: Option<String>,
pub text: Option<String>,
+ #[serde(default)]
+ pub tags: String,
}
pub async fn submit_story(
@@ -200,6 +206,13 @@ pub async fn submit_story(
.await
.map_err(|e| render_submit_error(&form, &all_tags, &format!("Failed to create story: {}", e)))?;
+ // Add tags
+ for tag_id in parse_tags(&form.tags) {
+ if let Err(e) = trailbase::add_tag_to_story(&client, story.id, tag_id).await {
+ tracing::warn!("Failed to add tag {} to story {}: {}", tag_id, story.id, e);
+ }
+ }
+
Ok(Redirect::to(&format!("/story/{}", story.id)))
}
@@ -210,7 +223,7 @@ fn render_submit_error(form: &SubmitForm, all_tags: &[trailbase::Category], erro
url: form.url.clone().unwrap_or_default(),
text: form.text.clone().unwrap_or_default(),
all_tags: all_tags.to_vec(),
- selected_tags: vec![],
+ selected_tags: parse_tags(&form.tags),
};
let app = ApplicationTemplate {
content: submit.render().unwrap_or_default(),
@@ -268,6 +281,8 @@ pub struct EditForm {
pub title: String,
pub url: Option<String>,
pub text: Option<String>,
+ #[serde(default)]
+ pub tags: String,
}
pub async fn edit_story(
@@ -316,6 +331,16 @@ pub async fn edit_story(
.await
.map_err(|e| render_edit_error(id, &form, &[], &format!("Failed to update: {}", e)))?;
+ // Update tags: remove all, then re-add selected
+ if let Err(e) = trailbase::remove_all_tags_from_story(&client, id).await {
+ tracing::warn!("Failed to remove tags from story {}: {}", id, e);
+ }
+ for tag_id in parse_tags(&form.tags) {
+ if let Err(e) = trailbase::add_tag_to_story(&client, id, tag_id).await {
+ tracing::warn!("Failed to add tag {} to story {}: {}", tag_id, id, e);
+ }
+ }
+
Ok(Redirect::to(&format!("/story/{}", id)))
}
@@ -327,7 +352,7 @@ fn render_edit_error(story_id: i64, form: &EditForm, all_tags: &[trailbase::Cate
url: form.url.clone().unwrap_or_default(),
text: form.text.clone().unwrap_or_default(),
all_tags: all_tags.to_vec(),
- selected_tags: vec![],
+ selected_tags: parse_tags(&form.tags),
};
let app = ApplicationTemplate {
content: edit.render().unwrap_or_default(),
diff --git a/templates/edit.html b/templates/edit.html
@@ -35,8 +35,9 @@
{% match tag.name %}
{% when Some with (name) %}
<label class="tag-checkbox">
- <input type="checkbox" name="tags" value="{{ tag.id }}"
- {% if selected_tags.contains(tag.id) %} checked{% endif %} />
+ <input type="checkbox" value="{{ tag.id }}"
+ {% if selected_tags.contains(tag.id) %} checked{% endif %}
+ onchange="updateTags(this.closest('form'))" />
{{ name }}
</label>
{% when None %}
@@ -46,9 +47,19 @@
</div>
{% endif %}
+ <input type="hidden" name="tags" id="tags-hidden" value="" />
<div class="form-actions">
<button type="submit">Save Changes</button>
<a href="/story/{{ story_id }}">Cancel</a>
</div>
</form>
+ <script>
+ function updateTags(form) {
+ var checked = form.querySelectorAll('.tag-checkboxes input[type=checkbox]:checked');
+ var ids = Array.from(checked).map(function(cb) { return cb.value; });
+ form.querySelector('#tags-hidden').value = ids.join(',');
+ }
+ // Initialize on load
+ document.querySelectorAll('.submit-form').forEach(updateTags);
+ </script>
</div>
diff --git a/templates/submit.html b/templates/submit.html
@@ -35,8 +35,9 @@
{% match tag.name %}
{% when Some with (name) %}
<label class="tag-checkbox">
- <input type="checkbox" name="tags" value="{{ tag.id }}"
- {% if selected_tags.contains(tag.id) %} checked{% endif %} />
+ <input type="checkbox" value="{{ tag.id }}"
+ {% if selected_tags.contains(tag.id) %} checked{% endif %}
+ onchange="updateTags(this.closest('form'))" />
{{ name }}
</label>
{% when None %}
@@ -46,10 +47,20 @@
</div>
{% endif %}
+ <input type="hidden" name="tags" id="tags-hidden" value="" />
<div class="form-actions">
<button type="submit">Submit</button>
</div>
</form>
+ <script>
+ function updateTags(form) {
+ var checked = form.querySelectorAll('.tag-checkboxes input[type=checkbox]:checked');
+ var ids = Array.from(checked).map(function(cb) { return cb.value; });
+ form.querySelector('#tags-hidden').value = ids.join(',');
+ }
+ // Initialize on load
+ document.querySelectorAll('.submit-form').forEach(updateTags);
+ </script>
<p class="submit-info">
Leave URL blank to submit a question or discussion.