+Image upload

This commit is contained in:
Simon Martens
2026-01-21 21:43:02 +01:00
parent 1aa24b97cc
commit 52d7b3b938
7 changed files with 865 additions and 531 deletions

View File

@@ -150,6 +150,8 @@
autocomplete="off"
class="w-full dbform"
method="POST"
enctype="multipart/form-data"
hx-boost="false"
action="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit">
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" />
@@ -343,5 +345,65 @@
deleteDialog.close();
});
});
const form = document.querySelector("form.dbform");
const uploadInput = document.querySelector("[data-role='content-images-upload-input']");
const userMessage = document.getElementById("user-message");
if (form && uploadInput && userMessage) {
form.addEventListener("submit", async (event) => {
event.stopImmediatePropagation();
const files = Array.from(uploadInput.files || []);
if (files.length > 0) {
const hasInvalid = files.some((file) => !file.type || !file.type.startsWith("image/"));
if (hasInvalid) {
event.preventDefault();
userMessage.innerHTML = `
<div class="text-red-800 text-sm mt-2 rounded-xs bg-red-200 p-2 font-bold border-red-700 shadow border mb-3">
<i class="ri-error-warning-fill"></i> Bitte nur Bilddateien auswählen.
</div>
`;
return;
}
}
event.preventDefault();
const payload = new FormData(form);
if (payload.has("scans")) {
payload.delete("scans");
}
const imagesComponent = document.querySelector("content-images");
if (imagesComponent && typeof imagesComponent.getPendingFiles === "function") {
imagesComponent.getPendingFiles().forEach((file) => {
payload.append("scans", file);
});
if (typeof imagesComponent.getPendingDeletes === "function") {
imagesComponent.getPendingDeletes().forEach((fileName) => {
payload.append("scans_delete[]", fileName);
});
}
}
const response = await fetch(form.action, {
method: form.method || "POST",
body: payload,
credentials: "same-origin",
});
if (response.redirected && response.url) {
window.location.assign(response.url);
return;
}
if (!response.ok) {
return;
}
const html = await response.text();
if (!html) {
return;
}
const doc = new DOMParser().parseFromString(html, "text/html");
const nextMessage = doc.getElementById("user-message");
if (nextMessage) {
userMessage.innerHTML = nextMessage.innerHTML;
}
});
}
})();
</script>

View File

@@ -4,8 +4,11 @@
{{- $isNew := index . "is_new" -}}
{{- if or $content.ImagePaths (not $isNew) -}}
<div class="w-full" data-role="content-images-panel">
<div class="flex flex-col items-start gap-2">
<div class="w-full inputwrapper" data-role="content-images-panel">
<div class="inputlabelrow">
<label class="inputlabel">Scans</label>
</div>
<div class="flex flex-col items-start gap-2 p-2">
<content-images
class="w-full"
data-images='[{{- range $i, $scan := $content.ImagePaths -}}{{- if $i }},{{ end -}}{{ printf "%q" $scan }}{{- end -}}]'
@@ -15,7 +18,8 @@
data-csrf-token="{{ $csrf }}">
</content-images>
{{- if not $isNew -}}
<div class="flex" data-role="content-images-upload">
<div class="hidden" data-role="content-images-upload">
<input type="hidden" name="content_id" value="{{ $content.Id }}" />
<label
for="content-{{ $content.Id }}-scan-upload"
class="flex h-28 w-28 items-center justify-center rounded-xs border-2 border-dashed border-slate-300 bg-stone-50 text-lg font-semibold text-slate-600 transition hover:border-slate-400 hover:text-slate-800"
@@ -30,7 +34,6 @@
accept="image/*"
class="sr-only"
data-role="content-images-upload-input"
data-upload-endpoint="/almanach/{{ $entry.MusenalmID }}/contents/upload"
data-content-id="{{ $content.Id }}"
data-csrf-token="{{ $csrf }}" />
</div>