tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit c82b401b63d6d5a9a097a4383092487df2e79bd9
parent b8b6cc4d54aaaae543dcba98ea13593c5e04996f
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Tue, 30 Jun 2026 09:17:29 +0100

Update tag list via Ajax when amending

Diffstat:
Astatic/ajax-tags.js | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtemplates/file.html | 8+++++---
2 files changed, 98 insertions(+), 3 deletions(-)

diff --git a/static/ajax-tags.js b/static/ajax-tags.js @@ -0,0 +1,93 @@ + +function initAjaxTagForms() { + document.querySelectorAll('.ajax-tag-form').forEach((form) => { + if (form.dataset.ajaxBound) return; // avoid double-binding after a swap + form.dataset.ajaxBound = 'true'; + form.addEventListener('submit', handleAjaxTagSubmit); + }); +} + +async function handleAjaxTagSubmit(e) { + e.preventDefault(); + const form = e.target; + const submitButton = form.querySelector('button[type="submit"]'); + if (submitButton) submitButton.disabled = true; + + const action = form.getAttribute('action') || window.location.pathname; + + try { + const response = await fetch(action, { + method: 'POST', + body: new FormData(form), + }); + + if (!response.ok) { + throw new Error('Request failed with status ' + response.status); + } + + const html = await response.text(); + const doc = new DOMParser().parseFromString(html, 'text/html'); + + // Swap the tag list + const newTagsList = doc.getElementById('tags-list'); + const currentTagsList = document.getElementById('tags-list'); + if (newTagsList && currentTagsList) { + currentTagsList.innerHTML = newTagsList.innerHTML; + } + + // Swap the category datalist (new categories may have been created) + const newCategories = doc.getElementById('categories'); + const currentCategories = document.getElementById('categories'); + if (newCategories && currentCategories) { + currentCategories.innerHTML = newCategories.innerHTML; + } + + // Show success/error message, pulled from the redirected URL's query params + const finalUrl = new URL(response.url, window.location.origin); + const statusEl = document.getElementById('tag-status'); + const success = finalUrl.searchParams.get('success'); + const error = finalUrl.searchParams.get('error'); + if (statusEl) { + if (error) { + statusEl.textContent = error; + statusEl.className = 'tag-status-error'; + } else if (success) { + statusEl.textContent = success; + statusEl.className = 'tag-status-success'; + } else { + statusEl.textContent = ''; + statusEl.className = ''; + } + if (success || error) { + setTimeout(() => { + statusEl.textContent = ''; + statusEl.className = ''; + }, 4000); + } + } + + // Clear the "Add Tag" inputs after a successful add + if (!error) { + const catInput = form.querySelector('input[name="category"]'); + const valInput = form.querySelector('input[name="value"]'); + if (catInput && form.querySelector('button[type="submit"]').textContent.includes('Add')) { + catInput.value = ''; + valInput.value = ''; + } + } + + // Re-bind the delete buttons that just got swapped in + initAjaxTagForms(); + } catch (err) { + console.error('Tag update failed:', err); + const statusEl = document.getElementById('tag-status'); + if (statusEl) { + statusEl.textContent = 'Failed to update tag. Please try again.'; + statusEl.className = 'tag-status-error'; + } + } finally { + if (submitButton) submitButton.disabled = false; + } +} + +document.addEventListener('DOMContentLoaded', initAjaxTagForms); diff --git a/templates/file.html b/templates/file.html @@ -7,13 +7,13 @@ <details open> <summary>Tags</summary> - <ul> + <ul id="tags-list"> {{range $k, $vs := .Data.File.Tags}} <li> <span class="file-tag-category">{{$k}}:</span><br> {{range $i, $v := $vs}} {{if $i}}<br> {{end}} - <form method="post" action="/file/{{$.Data.File.ID}}/tag/delete"><input type="hidden" name="category" value="{{$k}}"><input type="hidden" name="value" value="{{$v}}"><button class="text-button" type="submit">x</button></form> + <form method="post" action="/file/{{$.Data.File.ID}}/tag/delete" class="ajax-tag-form"><input type="hidden" name="category" value="{{$k}}"><input type="hidden" name="value" value="{{$v}}"><button class="text-button" type="submit">x</button></form> <a href="/tag/{{pathEscape $k}}/{{pathEscape $v}}">{{$v}}</a> {{end}} </li> @@ -25,13 +25,15 @@ <details> <summary>Add Tags</summary> - <form method="post"> + <form method="post" class="ajax-tag-form"> <input type="text" name="category" list="categories" placeholder="Category"><br> <datalist id="categories">{{range .Data.Categories}}<option value="{{.}}">{{end}}</datalist> <input type="text" name="value" placeholder="Value"><br> <button class="text-button" type="submit">Add Tag</button> </form> </details> + <div id="tag-status"></div> + <script src="/static/ajax-tags.js" defer></script> <details> <summary>Properties</summary>