mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
Inhalte-Bilder + musenalm stage dockerfile
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
11
views/routes/almanach/contents/images_panel/body.gohtml
Normal file
11
views/routes/almanach/contents/images_panel/body.gohtml
Normal file
@@ -0,0 +1,11 @@
|
||||
{{- $content := index . "content" -}}
|
||||
{{- $entry := index . "entry" -}}
|
||||
{{- $csrf := index . "csrf_token" -}}
|
||||
{{- $isNew := index . "is_new" -}}
|
||||
|
||||
{{- template "_content_images_panel" (Dict
|
||||
"content" $content
|
||||
"entry" $entry
|
||||
"csrf_token" $csrf
|
||||
"is_new" $isNew
|
||||
) -}}
|
||||
48
views/routes/components/_content_images_panel.gohtml
Normal file
48
views/routes/components/_content_images_panel.gohtml
Normal file
@@ -0,0 +1,48 @@
|
||||
{{- $content := index . "content" -}}
|
||||
{{- $entry := index . "entry" -}}
|
||||
{{- $csrf := index . "csrf_token" -}}
|
||||
{{- $isNew := index . "is_new" -}}
|
||||
|
||||
{{- if or $content.ImagePaths (not $isNew) -}}
|
||||
<div class="w-full md:w-56 lg:w-72 shrink-0" data-role="content-images-panel">
|
||||
<div class="flex flex-wrap items-start gap-2">
|
||||
{{- if $content.ImagePaths -}}
|
||||
<content-images
|
||||
data-images='[{{- range $i, $scan := $content.ImagePaths -}}{{- if $i }},{{ end -}}{{ printf "%q" $scan }}{{- end -}}]'
|
||||
data-files='[{{- range $i, $scan := $content.Scans -}}{{- if $i }},{{ end -}}{{ printf "%q" $scan }}{{- end -}}]'
|
||||
data-delete-endpoint="/almanach/{{ $entry.MusenalmID }}/contents/scan/delete"
|
||||
data-content-id="{{ $content.Id }}"
|
||||
data-csrf-token="{{ $csrf }}">
|
||||
</content-images>
|
||||
{{- end -}}
|
||||
{{- if not $isNew -}}
|
||||
<form
|
||||
class="flex"
|
||||
method="POST"
|
||||
action="/almanach/{{ $entry.MusenalmID }}/contents/upload"
|
||||
hx-post="/almanach/{{ $entry.MusenalmID }}/contents/upload"
|
||||
hx-trigger="change"
|
||||
hx-target="closest [data-role='content-images-panel']"
|
||||
hx-swap="outerHTML"
|
||||
hx-encoding="multipart/form-data"
|
||||
data-loading-label="Digitalisat wird hochgeladen">
|
||||
<input type="hidden" name="csrf_token" value="{{ $csrf }}" />
|
||||
<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"
|
||||
aria-label="Bilder hinzufuegen">
|
||||
<i class="ri-upload-2-line"></i>
|
||||
</label>
|
||||
<input
|
||||
id="content-{{ $content.Id }}-scan-upload"
|
||||
type="file"
|
||||
name="scans"
|
||||
multiple
|
||||
accept="image/*"
|
||||
class="sr-only" />
|
||||
</form>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
@@ -67,6 +67,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-nowrap whitespace-nowrap shrink-0">
|
||||
<span
|
||||
id="content-{{ $content.Id }}-images-count"
|
||||
class="inline-flex items-center gap-1 text-sm font-semibold text-slate-600 mr-2.5 {{ if eq (len $content.Scans) 0 }}hidden{{ end }}">
|
||||
<i class="ri-image-line"></i>
|
||||
<span>{{ len $content.Scans }}</span>
|
||||
</span>
|
||||
<span class="status-badge text-xs shadow-sm" data-status="{{ $content.EditState }}">
|
||||
<i class="status-icon {{- if eq $content.EditState "Edited" }} ri-checkbox-circle-line{{- else if eq $content.EditState "Seen" }} ri-information-line{{- else if eq $content.EditState "Review" }} ri-search-line{{- else if eq $content.EditState "ToDo" }} ri-list-check{{- else }} ri-forbid-2-line{{- end }}"></i>
|
||||
{{- if eq $content.EditState "Edited" -}}Erfasst{{- else if eq $content.EditState "Review" -}}Überprüfen{{- else if eq $content.EditState "ToDo" -}}Zu erledigen{{- else if eq $content.EditState "Seen" -}}Autopsiert{{- else -}}Unbekannt{{- end -}}
|
||||
@@ -115,84 +121,92 @@
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<div class="grid gap-2 grid-cols-[8rem_1fr] items-baseline px-3 py-2" data-role="content-view-body">
|
||||
{{- if or $content.Extent $content.MusenalmPagination -}}
|
||||
<div class="text-sm font-bold text-gray-700">Seite</div>
|
||||
<div class="text-base">
|
||||
{{ if $content.Extent }}{{ $content.Extent }}{{ end }}{{ if and $content.Extent $content.MusenalmPagination }}, {{ end }}{{ if $content.MusenalmPagination }}{{ $content.MusenalmPagination }}{{ end }}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if $content.TitleStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Titel</div>
|
||||
<div class="text-base italic">{{- $content.TitleStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.SubtitleStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Untertitel</div>
|
||||
<div class="text-base italic">{{- $content.SubtitleStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.ParallelTitle -}}
|
||||
<div class="text-sm font-bold text-gray-700">Paralleltitel</div>
|
||||
<div class="text-base italic">{{- $content.ParallelTitle -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.VariantTitle -}}
|
||||
<div class="text-sm font-bold text-gray-700">Titelvarianten</div>
|
||||
<div class="text-base italic">{{- $content.VariantTitle -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.PlaceStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Ortsangabe</div>
|
||||
<div class="text-base italic">{{- $content.PlaceStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if gt (len $content.Language) 0 -}}
|
||||
<div class="text-sm font-bold text-gray-700">Sprache</div>
|
||||
<div class="text-base">
|
||||
{{- range $i, $lang := $content.Language -}}{{- if $i }}, {{ end -}}{{- $lang -}}{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if gt (len $content.ContentType) 0 -}}
|
||||
<div class="text-sm font-bold text-gray-700">Beitragstyp</div>
|
||||
<div class="text-base">
|
||||
{{- range $i, $t := $content.ContentType -}}{{- if $i }}, {{ end -}}{{- $t -}}{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if $content.IncipitStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Incipit</div>
|
||||
<div class="text-base italic">{{ $content.IncipitStmt }}…</div>
|
||||
{{- end -}}
|
||||
{{- if $content.ResponsibilityStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Autorangabe</div>
|
||||
<div class="text-base italic">{{- $content.ResponsibilityStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.Comment -}}
|
||||
<div class="text-sm font-bold text-gray-700">Kommentar</div>
|
||||
<div class="text-base italic">{{- $content.Comment -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $contentAgents -}}
|
||||
<div class="text-sm font-bold text-gray-700">Personen</div>
|
||||
<div class="text-base">
|
||||
<div class="flex flex-col">
|
||||
{{- range $_, $rel := $contentAgents -}}
|
||||
{{- $agent := index $agents $rel.Agent -}}
|
||||
{{- if $agent -}}
|
||||
<div class="font-sans w-max">
|
||||
<a href="/person/{{- $agent.Id -}}" class="no-underline hover:text-slate-900">
|
||||
{{- $agent.Name -}}
|
||||
</a>
|
||||
{{- if $agent.BiographicalData -}}
|
||||
<span> ({{ $agent.BiographicalData }})</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<div class="flex flex-col gap-3 px-3 py-2 md:flex-row md:items-start" data-role="content-view-body">
|
||||
<div class="grid flex-1 gap-2 grid-cols-[8rem_1fr] items-baseline">
|
||||
{{- if or $content.Extent $content.MusenalmPagination -}}
|
||||
<div class="text-sm font-bold text-gray-700">Seite</div>
|
||||
<div class="text-base">
|
||||
{{ if $content.Extent }}{{ $content.Extent }}{{ end }}{{ if and $content.Extent $content.MusenalmPagination }}, {{ end }}{{ if $content.MusenalmPagination }}{{ $content.MusenalmPagination }}{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if $content.Annotation -}}
|
||||
{{- $link := printf "%s%s" "/almanach/" $entry.MusenalmIDString -}}
|
||||
<div class="text-sm font-bold text-gray-700">Anmerkung</div>
|
||||
<div class="text-base">
|
||||
{{- Safe (LinksAnnotation (ReplaceSlashParen $content.Annotation) $link) -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if $content.TitleStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Titel</div>
|
||||
<div class="text-base italic">{{- $content.TitleStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.SubtitleStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Untertitel</div>
|
||||
<div class="text-base italic">{{- $content.SubtitleStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.ParallelTitle -}}
|
||||
<div class="text-sm font-bold text-gray-700">Paralleltitel</div>
|
||||
<div class="text-base italic">{{- $content.ParallelTitle -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.VariantTitle -}}
|
||||
<div class="text-sm font-bold text-gray-700">Titelvarianten</div>
|
||||
<div class="text-base italic">{{- $content.VariantTitle -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.PlaceStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Ortsangabe</div>
|
||||
<div class="text-base italic">{{- $content.PlaceStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if gt (len $content.Language) 0 -}}
|
||||
<div class="text-sm font-bold text-gray-700">Sprache</div>
|
||||
<div class="text-base">
|
||||
{{- range $i, $lang := $content.Language -}}{{- if $i }}, {{ end -}}{{- $lang -}}{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if gt (len $content.ContentType) 0 -}}
|
||||
<div class="text-sm font-bold text-gray-700">Beitragstyp</div>
|
||||
<div class="text-base">
|
||||
{{- range $i, $t := $content.ContentType -}}{{- if $i }}, {{ end -}}{{- $t -}}{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if $content.IncipitStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Incipit</div>
|
||||
<div class="text-base italic">{{ $content.IncipitStmt }}…</div>
|
||||
{{- end -}}
|
||||
{{- if $content.ResponsibilityStmt -}}
|
||||
<div class="text-sm font-bold text-gray-700">Autorangabe</div>
|
||||
<div class="text-base italic">{{- $content.ResponsibilityStmt -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $content.Comment -}}
|
||||
<div class="text-sm font-bold text-gray-700">Kommentar</div>
|
||||
<div class="text-base italic">{{- $content.Comment -}}</div>
|
||||
{{- end -}}
|
||||
{{- if $contentAgents -}}
|
||||
<div class="text-sm font-bold text-gray-700">Personen</div>
|
||||
<div class="text-base">
|
||||
<div class="flex flex-col">
|
||||
{{- range $_, $rel := $contentAgents -}}
|
||||
{{- $agent := index $agents $rel.Agent -}}
|
||||
{{- if $agent -}}
|
||||
<div class="font-sans w-max">
|
||||
<a href="/person/{{- $agent.Id -}}" class="no-underline hover:text-slate-900">
|
||||
{{- $agent.Name -}}
|
||||
</a>
|
||||
{{- if $agent.BiographicalData -}}
|
||||
<span> ({{ $agent.BiographicalData }})</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- if $content.Annotation -}}
|
||||
{{- $link := printf "%s%s" "/almanach/" $entry.MusenalmIDString -}}
|
||||
<div class="text-sm font-bold text-gray-700">Anmerkung</div>
|
||||
<div class="text-base">
|
||||
{{- Safe (LinksAnnotation (ReplaceSlashParen $content.Annotation) $link) -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- template "_content_images_panel" (Dict
|
||||
"content" $content
|
||||
"entry" $entry
|
||||
"csrf_token" $csrf
|
||||
"is_new" $isNew
|
||||
) -}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
387
views/transform/content-images.js
Normal file
387
views/transform/content-images.js
Normal file
@@ -0,0 +1,387 @@
|
||||
const CONTENT_IMAGES_LIST_ROLE = "content-images-list";
|
||||
const CONTENT_IMAGES_DIALOG_ROLE = "content-images-dialog";
|
||||
const CONTENT_IMAGES_CLOSE_ROLE = "content-images-close";
|
||||
const CONTENT_IMAGES_FULL_ROLE = "content-images-full";
|
||||
const CONTENT_IMAGES_DELETE_DIALOG_ROLE = "content-images-delete-dialog";
|
||||
const CONTENT_IMAGES_DELETE_CONFIRM_ROLE = "content-images-delete-confirm";
|
||||
const CONTENT_IMAGES_DELETE_CANCEL_ROLE = "content-images-delete-cancel";
|
||||
const CONTENT_IMAGES_DELETE_NAME_ROLE = "content-images-delete-name";
|
||||
|
||||
const THUMB_PARAM = "300x0";
|
||||
const FULL_PARAM = "0x1000";
|
||||
|
||||
const buildThumbUrl = (rawUrl, size) => {
|
||||
if (!rawUrl) {
|
||||
return "";
|
||||
}
|
||||
if (rawUrl.includes("thumb=")) {
|
||||
return rawUrl;
|
||||
}
|
||||
const separator = rawUrl.includes("?") ? "&" : "?";
|
||||
return `${rawUrl}${separator}thumb=${size}`;
|
||||
};
|
||||
|
||||
const buildFullUrl = (rawUrl) => {
|
||||
return buildThumbUrl(rawUrl, FULL_PARAM);
|
||||
};
|
||||
|
||||
const extractFileName = (rawUrl) => {
|
||||
if (!rawUrl) {
|
||||
return "";
|
||||
}
|
||||
const cleanUrl = rawUrl.split("?")[0] || "";
|
||||
const parts = cleanUrl.split("/");
|
||||
return parts[parts.length - 1] || "";
|
||||
};
|
||||
|
||||
const normalizeImages = (rawImages, rawFiles) => {
|
||||
const files = Array.isArray(rawFiles) ? rawFiles : [];
|
||||
return (Array.isArray(rawImages) ? rawImages : []).map((item, index) => {
|
||||
if (typeof item === "string") {
|
||||
const name = files[index] || extractFileName(item);
|
||||
return { url: item, name };
|
||||
}
|
||||
if (item && typeof item === "object") {
|
||||
const url = item.url || "";
|
||||
const name = item.name || files[index] || extractFileName(url);
|
||||
return { url, name };
|
||||
}
|
||||
return { url: "", name: "" };
|
||||
});
|
||||
};
|
||||
|
||||
export class ContentImages extends HTMLElement {
|
||||
connectedCallback() {
|
||||
if (this.dataset.init === "true") {
|
||||
return;
|
||||
}
|
||||
this.dataset.init = "true";
|
||||
|
||||
const raw = this.getAttribute("data-images") || "[]";
|
||||
const rawFiles = this.getAttribute("data-files") || "[]";
|
||||
let images = [];
|
||||
let files = [];
|
||||
try {
|
||||
images = JSON.parse(raw);
|
||||
} catch {
|
||||
images = [];
|
||||
}
|
||||
try {
|
||||
files = JSON.parse(rawFiles);
|
||||
} catch {
|
||||
files = [];
|
||||
}
|
||||
|
||||
const normalized = normalizeImages(images, files);
|
||||
|
||||
if (!Array.isArray(normalized) || normalized.length === 0) {
|
||||
this.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
this._render(normalized);
|
||||
}
|
||||
|
||||
_render(images) {
|
||||
this.classList.add("inline-flex");
|
||||
const list = this._ensureList();
|
||||
list.innerHTML = "";
|
||||
|
||||
const deleteEndpoint = this.getAttribute("data-delete-endpoint") || "";
|
||||
const contentId = this.getAttribute("data-content-id") || "";
|
||||
const csrfToken = this.getAttribute("data-csrf-token") || "";
|
||||
const canDelete = deleteEndpoint && contentId && csrfToken;
|
||||
|
||||
images.forEach((image, index) => {
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "group relative";
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = [
|
||||
"rounded",
|
||||
"border",
|
||||
"border-slate-200",
|
||||
"bg-white",
|
||||
"p-1",
|
||||
"shadow-sm",
|
||||
"transition",
|
||||
"hover:border-slate-400",
|
||||
"hover:shadow-md",
|
||||
].join(" ");
|
||||
button.dataset.imageUrl = image.url;
|
||||
button.dataset.imageIndex = String(index);
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.src = buildThumbUrl(image.url, THUMB_PARAM);
|
||||
img.alt = "Digitalisat";
|
||||
img.loading = "lazy";
|
||||
img.className = "h-28 w-28 object-cover";
|
||||
|
||||
button.appendChild(img);
|
||||
wrapper.appendChild(button);
|
||||
|
||||
if (canDelete && image.name) {
|
||||
const deleteButton = document.createElement("button");
|
||||
deleteButton.type = "button";
|
||||
deleteButton.className = [
|
||||
"absolute",
|
||||
"right-1",
|
||||
"top-1",
|
||||
"hidden",
|
||||
"h-8",
|
||||
"w-8",
|
||||
"rounded-full",
|
||||
"border",
|
||||
"border-red-200",
|
||||
"bg-white/90",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
"text-red-700",
|
||||
"shadow-sm",
|
||||
"transition",
|
||||
"group-hover:flex",
|
||||
"hover:text-red-900",
|
||||
"hover:border-red-300",
|
||||
].join(" ");
|
||||
deleteButton.innerHTML = '<i class="ri-delete-bin-line"></i>';
|
||||
deleteButton.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._openDeleteDialog({
|
||||
endpoint: deleteEndpoint,
|
||||
contentId,
|
||||
csrfToken,
|
||||
fileName: image.name,
|
||||
});
|
||||
});
|
||||
wrapper.appendChild(deleteButton);
|
||||
}
|
||||
|
||||
list.appendChild(wrapper);
|
||||
});
|
||||
|
||||
const dialog = this._ensureDialog();
|
||||
const fullImage = dialog.querySelector(`[data-role='${CONTENT_IMAGES_FULL_ROLE}']`);
|
||||
|
||||
list.addEventListener("click", (event) => {
|
||||
const target = event.target.closest("button[data-image-url]");
|
||||
if (!target || !fullImage) {
|
||||
return;
|
||||
}
|
||||
const url = target.dataset.imageUrl || "";
|
||||
fullImage.src = buildFullUrl(url);
|
||||
fullImage.alt = "Digitalisat";
|
||||
if (dialog.showModal) {
|
||||
dialog.showModal();
|
||||
} else {
|
||||
dialog.setAttribute("open", "true");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ensureList() {
|
||||
let list = this.querySelector(`[data-role='${CONTENT_IMAGES_LIST_ROLE}']`);
|
||||
if (!list) {
|
||||
list = document.createElement("div");
|
||||
list.dataset.role = CONTENT_IMAGES_LIST_ROLE;
|
||||
list.className = "inline-flex flex-wrap gap-2";
|
||||
this.appendChild(list);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
_ensureDialog() {
|
||||
let dialog = this.querySelector(`[data-role='${CONTENT_IMAGES_DIALOG_ROLE}']`);
|
||||
if (dialog) {
|
||||
return dialog;
|
||||
}
|
||||
dialog = document.createElement("dialog");
|
||||
dialog.dataset.role = CONTENT_IMAGES_DIALOG_ROLE;
|
||||
dialog.className = [
|
||||
"fixed",
|
||||
"inset-0",
|
||||
"m-auto",
|
||||
"w-full",
|
||||
"max-w-5xl",
|
||||
"rounded-md",
|
||||
"border",
|
||||
"border-slate-200",
|
||||
"bg-white",
|
||||
"p-0",
|
||||
"shadow-xl",
|
||||
"backdrop:bg-black/60",
|
||||
].join(" ");
|
||||
|
||||
dialog.innerHTML = `
|
||||
<div class="flex items-center justify-between border-b border-slate-200 px-4 py-3">
|
||||
<div class="text-sm font-semibold text-gray-800">Digitalisat</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xs border border-slate-300 bg-stone-100 px-3 py-1 text-sm font-semibold text-gray-700 hover:bg-stone-200"
|
||||
data-role="${CONTENT_IMAGES_CLOSE_ROLE}">
|
||||
Schliessen
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<img data-role="${CONTENT_IMAGES_FULL_ROLE}" class="max-h-[75vh] w-full object-contain" alt="Digitalisat" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
const closeButton = dialog.querySelector(`[data-role='${CONTENT_IMAGES_CLOSE_ROLE}']`);
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener("click", () => {
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
dialog.addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
dialog.close();
|
||||
});
|
||||
dialog.addEventListener("click", (event) => {
|
||||
if (event.target === dialog) {
|
||||
dialog.close();
|
||||
}
|
||||
});
|
||||
|
||||
this.appendChild(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
_openDeleteDialog(payload) {
|
||||
const dialog = this._ensureDeleteDialog();
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
dialog.dataset.endpoint = payload.endpoint;
|
||||
dialog.dataset.contentId = payload.contentId;
|
||||
dialog.dataset.csrfToken = payload.csrfToken;
|
||||
dialog.dataset.fileName = payload.fileName;
|
||||
|
||||
const nameEl = dialog.querySelector(`[data-role='${CONTENT_IMAGES_DELETE_NAME_ROLE}']`);
|
||||
if (nameEl) {
|
||||
nameEl.textContent = payload.fileName;
|
||||
}
|
||||
|
||||
if (dialog.showModal) {
|
||||
dialog.showModal();
|
||||
} else {
|
||||
dialog.setAttribute("open", "true");
|
||||
}
|
||||
}
|
||||
|
||||
_ensureDeleteDialog() {
|
||||
let dialog = this.querySelector(`[data-role='${CONTENT_IMAGES_DELETE_DIALOG_ROLE}']`);
|
||||
if (dialog) {
|
||||
return dialog;
|
||||
}
|
||||
dialog = document.createElement("dialog");
|
||||
dialog.dataset.role = CONTENT_IMAGES_DELETE_DIALOG_ROLE;
|
||||
dialog.className = [
|
||||
"dbform",
|
||||
"fixed",
|
||||
"inset-0",
|
||||
"m-auto",
|
||||
"rounded-md",
|
||||
"border",
|
||||
"border-slate-200",
|
||||
"p-0",
|
||||
"shadow-xl",
|
||||
"backdrop:bg-black/40",
|
||||
].join(" ");
|
||||
dialog.innerHTML = `
|
||||
<div class="p-5 w-[22rem]">
|
||||
<div class="text-base font-bold text-gray-900">Digitalisat loeschen?</div>
|
||||
<div class="text-sm font-bold text-gray-900 mt-1" data-role="${CONTENT_IMAGES_DELETE_NAME_ROLE}"></div>
|
||||
<p class="text-sm text-gray-700 mt-2">
|
||||
Das Digitalisat wird dauerhaft entfernt.
|
||||
</p>
|
||||
<div class="flex items-center justify-end gap-3 mt-4">
|
||||
<button type="button" class="resetbutton w-auto px-3 py-1 text-sm" data-role="${CONTENT_IMAGES_DELETE_CANCEL_ROLE}">Abbrechen</button>
|
||||
<button type="button" class="submitbutton w-auto bg-red-700 hover:bg-red-800 px-3 py-1 text-sm" data-role="${CONTENT_IMAGES_DELETE_CONFIRM_ROLE}">
|
||||
Loeschen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const cancelButton = dialog.querySelector(`[data-role='${CONTENT_IMAGES_DELETE_CANCEL_ROLE}']`);
|
||||
const confirmButton = dialog.querySelector(`[data-role='${CONTENT_IMAGES_DELETE_CONFIRM_ROLE}']`);
|
||||
|
||||
const closeDialog = () => {
|
||||
if (dialog.open) {
|
||||
dialog.close();
|
||||
}
|
||||
};
|
||||
|
||||
if (cancelButton) {
|
||||
cancelButton.addEventListener("click", closeDialog);
|
||||
}
|
||||
dialog.addEventListener("cancel", (event) => {
|
||||
event.preventDefault();
|
||||
closeDialog();
|
||||
});
|
||||
|
||||
if (confirmButton) {
|
||||
confirmButton.addEventListener("click", () => {
|
||||
this._performDelete(dialog);
|
||||
});
|
||||
}
|
||||
|
||||
this.appendChild(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
_performDelete(dialog) {
|
||||
const endpoint = dialog.dataset.endpoint || "";
|
||||
const csrfToken = dialog.dataset.csrfToken || "";
|
||||
const contentId = dialog.dataset.contentId || "";
|
||||
const fileName = dialog.dataset.fileName || "";
|
||||
if (!endpoint || !csrfToken || !contentId || !fileName) {
|
||||
dialog.close();
|
||||
return;
|
||||
}
|
||||
const panel = this.closest("[data-role='content-images-panel']");
|
||||
if (window.htmx?.ajax && panel) {
|
||||
window.htmx.ajax("POST", endpoint, {
|
||||
target: panel,
|
||||
swap: "outerHTML",
|
||||
values: {
|
||||
csrf_token: csrfToken,
|
||||
content_id: contentId,
|
||||
scan: fileName,
|
||||
},
|
||||
});
|
||||
dialog.close();
|
||||
return;
|
||||
}
|
||||
const payload = new URLSearchParams();
|
||||
payload.set("csrf_token", csrfToken);
|
||||
payload.set("content_id", contentId);
|
||||
payload.set("scan", fileName);
|
||||
fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"HX-Request": "true",
|
||||
},
|
||||
body: payload.toString(),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok || !panel) {
|
||||
return null;
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then((html) => {
|
||||
if (!html || !panel) {
|
||||
return;
|
||||
}
|
||||
panel.outerHTML = html;
|
||||
})
|
||||
.catch(() => null)
|
||||
.finally(() => {
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import { RelationsEditor } from "./relations-editor.js";
|
||||
import { EditPage } from "./edit-page.js";
|
||||
import { FabMenu } from "./fab-menu.js";
|
||||
import { DuplicateWarningChecker } from "./duplicate-warning.js";
|
||||
import { ContentImages } from "./content-images.js";
|
||||
|
||||
const FILTER_LIST_ELEMENT = "filter-list";
|
||||
const FAB_MENU_ELEMENT = "fab-menu";
|
||||
@@ -49,6 +50,7 @@ const ALMANACH_EDIT_PAGE_ELEMENT = "almanach-edit-page";
|
||||
const RELATIONS_EDITOR_ELEMENT = "relations-editor";
|
||||
const EDIT_PAGE_ELEMENT = "edit-page";
|
||||
const DUPLICATE_WARNING_ELEMENT = "duplicate-warning-checker";
|
||||
const CONTENT_IMAGES_ELEMENT = "content-images";
|
||||
|
||||
customElements.define(INT_LINK_ELEMENT, IntLink);
|
||||
customElements.define(ABBREV_TOOLTIPS_ELEMENT, AbbreviationTooltips);
|
||||
@@ -70,6 +72,7 @@ customElements.define(RELATIONS_EDITOR_ELEMENT, RelationsEditor);
|
||||
customElements.define(EDIT_PAGE_ELEMENT, EditPage);
|
||||
customElements.define(FAB_MENU_ELEMENT, FabMenu);
|
||||
customElements.define(DUPLICATE_WARNING_ELEMENT, DuplicateWarningChecker);
|
||||
customElements.define(CONTENT_IMAGES_ELEMENT, ContentImages);
|
||||
|
||||
function PathPlusQuery() {
|
||||
const path = window.location.pathname;
|
||||
|
||||
Reference in New Issue
Block a user