Autocomplete existing tags in the upload and manage forms

A small shared widget (static/tagcomplete.js) completes the tag after the last
comma from the existing tag list, keeping earlier tags intact. It is wired to
every comma-separated tag input: the upload form, and the manage page's per-row
edit field and bulk add/remove fields.

Suggestions come from /tags.json, which the generator already writes on every
build and nginx serves statically, so no server change is needed; the list is
fetched once, cached, and invalidated after an edit that may add a tag. Keyboard
navigation (up/down, enter/tab, escape) and click selection are supported.
This commit is contained in:
puttaalu committed 2026-10-18 22:39:13 +02:00
1 parent bf75c9fe8e
commit c4aa345914
4 files changed
+183 -3

No files matched your search

+6 -2
View File
@@ -105,9 +105,9 @@
<div class="bulkbar" id="bulkbar">
<span class="sel" id="sel">0 selected</span>
<input type="text" id="bulk-add" placeholder="add tags" autocomplete="off">
<input type="text" id="bulk-add" placeholder="add tags" autocomplete="off" data-tagcomplete>
<button class="btn" id="btn-add">Add</button>
<input type="text" id="bulk-remove" placeholder="remove tags" autocomplete="off">
<input type="text" id="bulk-remove" placeholder="remove tags" autocomplete="off" data-tagcomplete>
<button class="btn" id="btn-remove">Remove</button>
<button class="btn danger" id="btn-del-sel">Delete selected</button>
</div>
@@ -118,6 +118,7 @@
<div id="toast"></div>
<script src="/static/tagcomplete.js"></script>
<script>
const list = document.getElementById('list');
const countEl = document.getElementById('count');
@@ -166,6 +167,7 @@
// Set values via property to avoid HTML-escaping issues in attributes.
el.querySelector('.f-caption').value = p.caption;
el.querySelector('.f-tags').value = (p.tags || []).join(', ');
if (window.TagComplete) window.TagComplete.attach(el.querySelector('.f-tags'));
el.querySelector('.pick').addEventListener('change', refreshBulk);
el.querySelector('.f-save').addEventListener('click', () => save(el, p.slug));
@@ -203,6 +205,7 @@
form.append('tags', el.querySelector('.f-tags').value);
try {
await post('/api/edit', form);
if (window.TagComplete) window.TagComplete.invalidate();
flash('Saved ' + slug, true);
} catch (err) { flash('Save failed: ' + err.message, false); }
}
@@ -254,6 +257,7 @@
const res = await post('/api/tags', form);
const j = await res.json().catch(() => ({}));
if (!j.updated) { flash('No posts updated', false); return; }
if (window.TagComplete) window.TagComplete.invalidate();
document.getElementById(field).value = '';
flash('Updated tags on ' + j.updated + ' post(s)', true);
await load();