Inhalte-Bilder + musenalm stage dockerfile

This commit is contained in:
Simon Martens
2026-01-19 16:47:07 +01:00
parent d3ffa5f90d
commit 3017d4164b
9 changed files with 1661 additions and 758 deletions

View File

@@ -26,8 +26,11 @@ const (
URL_ALMANACH_CONTENTS_DELETE = "contents/delete"
URL_ALMANACH_CONTENTS_EDIT_FORM = "contents/edit/form"
URL_ALMANACH_CONTENTS_EDIT_EXTENT = "contents/edit/extent"
URL_ALMANACH_CONTENTS_UPLOAD = "contents/upload"
URL_ALMANACH_CONTENTS_DELETE_SCAN = "contents/scan/delete"
TEMPLATE_ALMANACH_CONTENTS_EDIT = "/almanach/contents/edit/"
TEMPLATE_ALMANACH_CONTENTS_EDIT_FORM = "/almanach/contents/edit_form/"
TEMPLATE_ALMANACH_CONTENTS_IMAGES_PANEL = "/almanach/contents/images_panel/"
)
func init() {
@@ -56,6 +59,8 @@ func (p *AlmanachContentsEditPage) Setup(router *router.Router[*core.RequestEven
rg.POST(URL_ALMANACH_CONTENTS_INSERT, p.POSTInsert(engine, app))
rg.POST(URL_ALMANACH_CONTENTS_DELETE, p.POSTDelete(engine, app))
rg.POST(URL_ALMANACH_CONTENTS_EDIT_EXTENT, p.POSTUpdateExtent(engine, app))
rg.POST(URL_ALMANACH_CONTENTS_UPLOAD, p.POSTUploadScans(engine, app))
rg.POST(URL_ALMANACH_CONTENTS_DELETE_SCAN, p.POSTDeleteScan(engine, app))
return nil
}
@@ -560,6 +565,184 @@ func (p *AlmanachContentsEditPage) POSTDelete(engine *templating.Engine, app cor
}
}
func (p *AlmanachContentsEditPage) POSTUploadScans(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
req := templating.NewRequest(e)
isHTMX := strings.EqualFold(e.Request.Header.Get("HX-Request"), "true")
if e.Request.MultipartForm == nil {
if err := e.Request.ParseMultipartForm(router.DefaultMaxMemory); err != nil {
return renderContentsImagesHTMXError(e, "Upload fehlgeschlagen.", isHTMX)
}
}
if err := req.CheckCSRF(e.Request.FormValue("csrf_token")); err != nil {
return renderContentsImagesHTMXError(e, "Upload fehlgeschlagen.", isHTMX)
}
contentID := strings.TrimSpace(e.Request.FormValue("content_id"))
if contentID == "" || strings.HasPrefix(contentID, "tmp") {
return renderContentsImagesHTMXError(e, "Upload fehlgeschlagen.", isHTMX)
}
entry, err := dbmodels.Entries_MusenalmID(app, id)
if err != nil {
return engine.Response404(e, err, nil)
}
contents, err := dbmodels.Contents_IDs(app, []any{contentID})
if err != nil || len(contents) == 0 {
return renderContentsImagesHTMXError(e, "Beitrag nicht gefunden.", isHTMX)
}
content := contents[0]
if content.Entry() != entry.Id {
return renderContentsImagesHTMXError(e, "Beitrag nicht gefunden.", isHTMX)
}
files, err := e.FindUploadedFiles(dbmodels.SCAN_FIELD)
if err != nil || len(files) == 0 {
return renderContentsImagesHTMXError(e, "Bitte eine Datei auswaehlen.", isHTMX)
}
content.Set(dbmodels.SCAN_FIELD+"+", files)
if user := req.User(); user != nil {
content.SetEditor(user.Id)
}
if err := app.Save(content); err != nil {
app.Logger().Error("Failed to upload scans", "entry_id", entry.Id, "content_id", content.Id, "error", err)
return renderContentsImagesHTMXError(e, "Upload fehlgeschlagen.", isHTMX)
}
if !isHTMX {
redirect := fmt.Sprintf("/almanach/%s/contents/edit", id)
return e.Redirect(http.StatusSeeOther, redirect)
}
if refreshed, err := dbmodels.Contents_IDs(app, []any{content.Id}); err == nil && len(refreshed) > 0 {
content = refreshed[0]
}
data := map[string]any{
"content": content,
"entry": entry,
"csrf_token": req.Session().Token,
"is_new": false,
}
var builder strings.Builder
if err := engine.Render(&builder, TEMPLATE_ALMANACH_CONTENTS_IMAGES_PANEL, data, "fragment"); err != nil {
app.Logger().Error("Failed to render images panel", "entry_id", entry.Id, "content_id", content.Id, "error", err)
return e.String(http.StatusInternalServerError, "")
}
success := `<div hx-swap-oob="innerHTML:#user-message"><div class="text-green-800 text-sm mt-2 rounded-xs bg-green-200 p-2 font-bold border-green-700 shadow border mb-3"><i class="ri-checkbox-circle-fill"></i> Digitalisat gespeichert.</div></div>`
countOOB := renderContentImagesCountOOB(content)
return e.HTML(http.StatusOK, builder.String()+success+countOOB)
}
}
func (p *AlmanachContentsEditPage) POSTDeleteScan(engine *templating.Engine, app core.App) HandleFunc {
return func(e *core.RequestEvent) error {
id := e.Request.PathValue("id")
req := templating.NewRequest(e)
isHTMX := strings.EqualFold(e.Request.Header.Get("HX-Request"), "true")
if err := e.Request.ParseForm(); err != nil {
return renderContentsImagesHTMXError(e, "Loeschen fehlgeschlagen.", isHTMX)
}
if err := req.CheckCSRF(e.Request.FormValue("csrf_token")); err != nil {
return renderContentsImagesHTMXError(e, "Loeschen fehlgeschlagen.", isHTMX)
}
contentID := strings.TrimSpace(e.Request.FormValue("content_id"))
scan := strings.TrimSpace(e.Request.FormValue("scan"))
if contentID == "" || scan == "" {
return renderContentsImagesHTMXError(e, "Loeschen fehlgeschlagen.", isHTMX)
}
entry, err := dbmodels.Entries_MusenalmID(app, id)
if err != nil {
return engine.Response404(e, err, nil)
}
contents, err := dbmodels.Contents_IDs(app, []any{contentID})
if err != nil || len(contents) == 0 {
return renderContentsImagesHTMXError(e, "Beitrag nicht gefunden.", isHTMX)
}
content := contents[0]
if content.Entry() != entry.Id {
return renderContentsImagesHTMXError(e, "Beitrag nicht gefunden.", isHTMX)
}
if !slices.Contains(content.Scans(), scan) {
return renderContentsImagesHTMXError(e, "Datei nicht gefunden.", isHTMX)
}
content.Set(dbmodels.SCAN_FIELD+"-", scan)
if user := req.User(); user != nil {
content.SetEditor(user.Id)
}
if err := app.Save(content); err != nil {
app.Logger().Error("Failed to delete scan", "entry_id", entry.Id, "content_id", content.Id, "scan", scan, "error", err)
return renderContentsImagesHTMXError(e, "Loeschen fehlgeschlagen.", isHTMX)
}
if !isHTMX {
redirect := fmt.Sprintf("/almanach/%s/contents/edit", id)
return e.Redirect(http.StatusSeeOther, redirect)
}
if refreshed, err := dbmodels.Contents_IDs(app, []any{content.Id}); err == nil && len(refreshed) > 0 {
content = refreshed[0]
}
data := map[string]any{
"content": content,
"entry": entry,
"csrf_token": req.Session().Token,
"is_new": false,
}
var builder strings.Builder
if err := engine.Render(&builder, TEMPLATE_ALMANACH_CONTENTS_IMAGES_PANEL, data, "fragment"); err != nil {
app.Logger().Error("Failed to render images panel", "entry_id", entry.Id, "content_id", content.Id, "error", err)
return e.String(http.StatusInternalServerError, "")
}
success := `<div hx-swap-oob="innerHTML:#user-message"><div class="text-green-800 text-sm mt-2 rounded-xs bg-green-200 p-2 font-bold border-green-700 shadow border mb-3"><i class="ri-checkbox-circle-fill"></i> Digitalisat geloescht.</div></div>`
countOOB := renderContentImagesCountOOB(content)
return e.HTML(http.StatusOK, builder.String()+success+countOOB)
}
}
func renderContentImagesCountOOB(content *dbmodels.Content) string {
if content == nil {
return ""
}
count := len(content.Scans())
hiddenClass := ""
if count == 0 {
hiddenClass = " hidden"
}
return fmt.Sprintf(
`<span hx-swap-oob="outerHTML" id="content-%s-images-count" class="inline-flex items-center gap-1 text-sm font-semibold text-slate-600 mr-2.5%s"><i class="ri-image-line"></i><span>%d</span></span>`,
content.Id,
hiddenClass,
count,
)
}
func renderContentsImagesHTMXError(e *core.RequestEvent, message string, isHTMX bool) error {
if !isHTMX {
return e.String(http.StatusBadRequest, message)
}
e.Response.Header().Set("HX-Reswap", "none")
payload := fmt.Sprintf(
`<div hx-swap-oob="innerHTML:#user-message"><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> %s</div></div>`,
message,
)
return e.HTML(http.StatusBadRequest, payload)
}
func (p *AlmanachContentsEditPage) renderSaveError(
engine *templating.Engine,
app core.App,

17
stage.docker-compose.yml Normal file
View File

@@ -0,0 +1,17 @@
services:
musenalmstage:
build: .
expose:
- "8090"
volumes:
- musenalmstage:/app/data
networks:
- caddynet
volumes:
musenalmstage:
external: true
networks:
caddynet:
external: true

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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
) -}}

View 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 -}}

View File

@@ -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,7 +121,8 @@
</div>
</div>
</dialog>
<div class="grid gap-2 grid-cols-[8rem_1fr] items-baseline px-3 py-2" data-role="content-view-body">
<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">
@@ -194,6 +201,13 @@
</div>
{{- end -}}
</div>
{{- template "_content_images_panel" (Dict
"content" $content
"entry" $entry
"csrf_token" $csrf
"is_new" $isNew
) -}}
</div>
</div>
</div>
<div data-role="content-edit-container" id="{{ $editContainerID }}">

View 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();
});
}
}

View File

@@ -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;