Auto-suffix duplicate filenames in web bulk upload

The server derives a photo's slug from the uploaded filename, so two photos with
the same name in one batch would overwrite each other. Each card now gets a slug
that is unique among the current cards (second same-named photo becomes -2, -3,
…), computed with a client mirror of the server's sanitiseSlug and sent as the
multipart filename. The slug is assigned once when a photo is added, so it stays
stable across retries.

Kept client-side on purpose: a server-side "slug exists -> suffix" would turn
the apps' idempotent re-upload retries (same filename overwrites the same post)
into duplicates.
This commit is contained in:
puttaalu committed 2026-10-18 23:31:05 +02:00
1 parent c21ddc5b7e
commit 003a4057bf
1 file changed
+29 -2
+29 -2
View File
@@ -94,8 +94,33 @@
refresh();
});
// Mirror of the server's sanitiseSlug, so the client can predict the slug a
// filename yields and keep two files in one batch from colliding.
function slugify(name) {
let out = '';
for (const ch of name.toLowerCase()) {
if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch === '-') out += ch;
else if (ch === ' ' || ch === '_') out += '-';
}
return out;
}
// A slug unique among the current cards. The server derives the slug from
// the uploaded filename, so two photos named the same would otherwise
// overwrite each other; the second (third, …) gets -2, -3, …
function uniqueSlug(filename) {
const stem = filename.replace(/\.[^.]+$/, '');
const base = slugify(stem) || 'photo';
let candidate = base;
let n = 2;
while (items.some((i) => i.slug === candidate)) candidate = base + '-' + (n++);
return candidate;
}
function addItem(file) {
const item = { id: ++seq, file, status: 'pending', el: null };
const ext = (file.name.match(/\.[^.]+$/)?.[0] || '.jpg').toLowerCase();
const item = { id: ++seq, file, ext, slug: '', status: 'pending', el: null };
item.slug = uniqueSlug(file.name); // before push, so it compares against the others
items.push(item);
item.el = buildCard(item);
list.appendChild(item.el);
@@ -177,7 +202,9 @@
setStatus(item, 'up', 'Uploading…');
const form = new FormData();
form.append('image', item.file);
// Send under the deduped slug so the server (which slugs from the
// filename) can't overwrite another photo in the same batch.
form.append('image', item.file, item.slug + item.ext);
form.append('caption', item.el.querySelector('.c-caption').value);
form.append('date', item.el.querySelector('.c-date').value);
form.append('tags', item.el.querySelector('.c-tags').value);