diff --git a/controllers/almanach_contents_edit.go b/controllers/almanach_contents_edit.go index 6a43c45..5ecb1d2 100644 --- a/controllers/almanach_contents_edit.go +++ b/controllers/almanach_contents_edit.go @@ -24,7 +24,9 @@ const ( URL_ALMANACH_CONTENTS_EDIT = "contents/edit" URL_ALMANACH_CONTENTS_INSERT = "contents/insert" URL_ALMANACH_CONTENTS_DELETE = "contents/delete" + URL_ALMANACH_CONTENTS_EDIT_FORM = "contents/edit/form" TEMPLATE_ALMANACH_CONTENTS_EDIT = "/almanach/contents/edit/" + TEMPLATE_ALMANACH_CONTENTS_EDIT_FORM = "/almanach/contents/edit_form/" ) func init() { @@ -49,6 +51,7 @@ func (p *AlmanachContentsEditPage) Setup(router *router.Router[*core.RequestEven rg.BindFunc(middleware.IsAdminOrEditor()) rg.GET(URL_ALMANACH_CONTENTS_EDIT, p.GET(engine, app)) rg.POST(URL_ALMANACH_CONTENTS_EDIT, p.POSTSave(engine, app)) + rg.GET(URL_ALMANACH_CONTENTS_EDIT_FORM, p.GETEditForm(engine, app)) rg.POST(URL_ALMANACH_CONTENTS_INSERT, p.POSTInsert(engine, app)) rg.POST(URL_ALMANACH_CONTENTS_DELETE, p.POSTDelete(engine, app)) return nil @@ -80,6 +83,58 @@ func (p *AlmanachContentsEditPage) GET(engine *templating.Engine, app core.App) } } +func (p *AlmanachContentsEditPage) GETEditForm(engine *templating.Engine, app core.App) HandleFunc { + return func(e *core.RequestEvent) error { + id := e.Request.PathValue("id") + req := templating.NewRequest(e) + contentID := strings.TrimSpace(e.Request.URL.Query().Get("content_id")) + if contentID == "" { + return e.String(http.StatusBadRequest, "") + } + + 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 e.String(http.StatusNotFound, "") + } + content := contents[0] + if content.Entry() != entry.Id { + return e.String(http.StatusNotFound, "") + } + + agentsMap, contentAgentsMap, err := dbmodels.AgentsForContents(app, []*dbmodels.Content{content}) + if err != nil { + agentsMap = map[string]*dbmodels.Agent{} + contentAgentsMap = map[string][]*dbmodels.RContentsAgents{} + } + + data := map[string]any{ + "content": content, + "content_id": content.Id, + "entry": entry, + "csrf_token": req.Session().Token, + "content_types": dbmodels.CONTENT_TYPE_VALUES, + "musenalm_types": dbmodels.MUSENALM_TYPE_VALUES, + "pagination_values": paginationValuesSorted(), + "agent_relations": dbmodels.AGENT_RELATIONS, + "agents": agentsMap, + "content_agents": contentAgentsMap[content.Id], + } + + var builder strings.Builder + if err := engine.Render(&builder, TEMPLATE_ALMANACH_CONTENTS_EDIT_FORM, data, "fragment"); err != nil { + app.Logger().Error("Failed to render content edit form", "entry_id", entry.Id, "content_id", contentID, "error", err) + return e.String(http.StatusInternalServerError, "") + } + + return e.HTML(http.StatusOK, builder.String()) + } +} + func (p *AlmanachContentsEditPage) renderError(engine *templating.Engine, app core.App, e *core.RequestEvent, message string) error { id := e.Request.PathValue("id") req := templating.NewRequest(e) diff --git a/views/routes/almanach/contents/edit/body.gohtml b/views/routes/almanach/contents/edit/body.gohtml index a9b3c4d..5063151 100644 --- a/views/routes/almanach/contents/edit/body.gohtml +++ b/views/routes/almanach/contents/edit/body.gohtml @@ -134,7 +134,11 @@ Alle Eintraege einklappen -
+
{{- range $_, $content := $model.result.Contents -}} {{- template "_content_item" (Dict "content" $content @@ -178,6 +182,9 @@ } }; + let enterEditMode = null; + let setupEditFormGlobal = null; + const initPage = () => { const list = document.querySelector("[data-role='contents-list']"); if (!list) { @@ -227,7 +234,7 @@ if (item.parentElement !== list) { return; } - const contentId = item.querySelector("[data-role='content-card']")?.dataset.contentId || ""; + const contentId = item.dataset.contentId || ""; list.insertBefore(createGap("before", contentId, false), item); }); list.appendChild(createGap("after", "", true)); @@ -261,7 +268,7 @@ } const isTempContentId = (contentId) => contentId && contentId.startsWith("tmp"); - const orderEndpoint = getItems()[0]?.querySelector("form")?.getAttribute("action") || ""; + const orderEndpoint = list.dataset.orderEndpoint || getItems()[0]?.querySelector("form")?.getAttribute("action") || ""; const deleteEndpoint = window.location.pathname.replace(/\/contents\/edit\/?$/, "/contents/delete"); const csrfToken = document.querySelector("input[name='csrf_token']")?.value || ""; const syncIndicator = document.querySelector("#contents-sync-indicator"); @@ -287,7 +294,7 @@ const payload = new URLSearchParams(); payload.set("csrf_token", csrfToken); list.querySelectorAll("[data-role='content-item']").forEach((card) => { - const contentId = card.querySelector("[data-role='content-card']")?.dataset.contentId; + const contentId = card.dataset.contentId; if (!contentId || isTempContentId(contentId)) { return; } @@ -319,8 +326,11 @@ }, 300); }; - const closeAll = () => { + const closeAll = (keepItem = null) => { getItems().forEach((item) => { + if (keepItem && item === keepItem) { + return; + } const view = item.querySelector("[data-role='content-view']"); const edit = item.querySelector("[data-role='content-edit']"); const editButton = item.querySelector("[data-role='content-edit-button']"); @@ -329,7 +339,7 @@ view.classList.remove("hidden"); } if (edit) { - edit.classList.add("hidden"); + edit.remove(); } if (editButton) { editButton.classList.remove("hidden"); @@ -337,15 +347,12 @@ }); }; - const openItem = (item) => { - closeAll(); + enterEditMode = (item) => { const view = item.querySelector("[data-role='content-view']"); - const edit = item.querySelector("[data-role='content-edit']"); - if (view && edit) { + if (view) { view.classList.add("hidden"); - edit.classList.remove("hidden"); - item.classList.add("data-editing"); } + item.classList.add("data-editing"); setEditSpacing(true); removeGaps(); getItems().forEach((other) => { @@ -357,6 +364,20 @@ }); }; + const openItem = (item) => { + closeAll(item); + const edit = item.querySelector("[data-role='content-edit']"); + if (!edit) { + item.dataset.pendingEdit = "true"; + const editButton = item.querySelector("[data-role='content-edit-button']"); + if (editButton) { + editButton.click(); + } + return; + } + enterEditMode(item); + }; + const removeItem = (item) => { const gap = item.previousElementSibling; if (gap && gap.matches("[data-role='content-gap']")) { @@ -379,18 +400,11 @@ item.dataset.init = "true"; const editButton = item.querySelector("[data-role='content-edit-button']"); - const cancelButton = item.querySelector("[data-role='content-edit-cancel']"); - const deleteButton = item.querySelector("[data-role='content-delete']"); - const deleteDialog = item.querySelector("[data-role='content-delete-dialog']"); - const deleteConfirm = item.querySelector("[data-role='content-delete-confirm']"); - const deleteCancel = item.querySelector("[data-role='content-delete-cancel']"); const view = item.querySelector("[data-role='content-view']"); - const edit = item.querySelector("[data-role='content-edit']"); const collapseButton = item.querySelector("[data-role='content-collapse-toggle']"); const collapseIcon = item.querySelector("[data-role='content-collapse-icon']"); const collapsedSummary = item.querySelector("[data-role='content-collapsed-summary']"); const viewBody = item.querySelector("[data-role='content-view-body']"); - const form = item.querySelector("form"); const setCollapsed = (collapsed) => { if (!viewBody || !collapsedSummary) { @@ -421,27 +435,57 @@ }); } - if (editButton && view && edit) { + if (editButton) { editButton.addEventListener("click", () => { - openItem(item); + if (item.querySelector("[data-role='content-edit']")) { + enterEditMode(item); + return; + } + item.dataset.pendingEdit = "true"; }); } - if (cancelButton && view && edit) { + const pendingEdit = item.dataset.pendingEdit === "true"; + if (pendingEdit && item.querySelector("[data-role='content-edit']")) { + item.dataset.pendingEdit = ""; + enterEditMode(item); + } + + + item.querySelectorAll("multi-select-simple[data-initial-options], multi-select-simple[data-initial-values]").forEach((el) => { + applyMultiSelectInit(el); + }); + + if (item.dataset.openEdit === "true") { + item.dataset.openEdit = ""; + openItem(item); + } + }; + + const setupEditForm = (item) => { + const edit = item.querySelector("[data-role='content-edit']"); + if (!edit || edit.dataset.init === "true") { + return; + } + edit.dataset.init = "true"; + const cancelButton = edit.querySelector("[data-role='content-edit-cancel']"); + const deleteButton = edit.querySelector("[data-role='content-delete']"); + const deleteDialog = edit.querySelector("[data-role='content-delete-dialog']"); + const deleteConfirm = edit.querySelector("[data-role='content-delete-confirm']"); + const deleteCancel = edit.querySelector("[data-role='content-delete-cancel']"); + const form = edit.querySelector("form"); + const view = item.querySelector("[data-role='content-view']"); + + if (cancelButton && view) { cancelButton.addEventListener("click", () => { if (item.dataset.contentTemp === "true") { removeItem(item); return; } - edit.classList.add("hidden"); + edit.remove(); view.classList.remove("hidden"); item.classList.remove("data-editing"); - getItems().forEach((other) => { - const otherButton = other.querySelector("[data-role='content-edit-button']"); - if (otherButton) { - otherButton.classList.remove("hidden"); - } - }); + showEditButtonsIfIdle(); syncEditSpacing(); renderInsertGaps(); }); @@ -476,7 +520,7 @@ } const payload = new URLSearchParams(); payload.set("csrf_token", csrfToken); - payload.set("content_id", item.querySelector("[data-role='content-card']")?.dataset.contentId || ""); + payload.set("content_id", item.dataset.contentId || ""); fetch(deleteEndpoint, { method: "POST", headers: { @@ -499,7 +543,7 @@ form.addEventListener("submit", () => { form.querySelectorAll("input[name='content_order[]']").forEach((input) => input.remove()); getItems().forEach((card) => { - const contentId = card.querySelector("[data-role='content-card']")?.dataset.contentId; + const contentId = card.dataset.contentId; if (!contentId) { return; } @@ -511,16 +555,8 @@ }); }); } - - item.querySelectorAll("multi-select-simple[data-initial-options], multi-select-simple[data-initial-values]").forEach((el) => { - applyMultiSelectInit(el); - }); - - if (item.dataset.openEdit === "true") { - item.dataset.openEdit = ""; - openItem(item); - } }; + setupEditFormGlobal = setupEditForm; const updateCollapseAllLabel = () => { if (!collapseAllButton || !collapseAllLabel) { @@ -575,7 +611,10 @@ } } - getItems().forEach((item) => setupItem(item)); + getItems().forEach((item) => { + setupItem(item); + setupEditForm(item); + }); renderInsertGaps(); syncEditSpacing(); showEditButtonsIfIdle(); @@ -679,7 +718,7 @@ const editContentId = params.get("edit_content"); if (editContentId) { const targetItem = getItems().find((item) => { - return item.querySelector(`[data-role='content-card'][data-content-id='${editContentId}']`); + return item.dataset.contentId === editContentId; }); if (targetItem) { openItem(targetItem); @@ -700,7 +739,18 @@ initWhenReady(); - document.body.addEventListener("htmx:afterSwap", () => { + document.body.addEventListener("htmx:afterSwap", (event) => { + const target = event.detail?.target; + if (target && target.matches("[data-role='content-edit-container']")) { + const item = target.closest("[data-role='content-item']"); + if (item && item.dataset.pendingEdit === "true" && enterEditMode) { + item.dataset.pendingEdit = ""; + enterEditMode(item); + if (setupEditFormGlobal) { + setupEditFormGlobal(item); + } + } + } initWhenReady(); }); diff --git a/views/routes/almanach/contents/edit_form/body.gohtml b/views/routes/almanach/contents/edit_form/body.gohtml new file mode 100644 index 0000000..472e208 --- /dev/null +++ b/views/routes/almanach/contents/edit_form/body.gohtml @@ -0,0 +1,20 @@ +{{- $content := index . "content" -}} +{{- $entry := index . "entry" -}} +{{- $csrf := index . "csrf_token" -}} +{{- $contentTypes := index . "content_types" -}} +{{- $musenalmTypes := index . "musenalm_types" -}} +{{- $paginationValues := index . "pagination_values" -}} +{{- $contentID := index . "content_id" -}} + +{{- template "_content_edit_form" (Dict + "content" $content + "content_id" $contentID + "entry" $entry + "csrf_token" $csrf + "content_types" $contentTypes + "musenalm_types" $musenalmTypes + "pagination_values" $paginationValues + "agents" (index . "agents") + "content_agents" (index . "content_agents") + "agent_relations" (index . "agent_relations") +) -}} diff --git a/views/routes/components/_content_edit_form.gohtml b/views/routes/components/_content_edit_form.gohtml new file mode 100644 index 0000000..ac48a8a --- /dev/null +++ b/views/routes/components/_content_edit_form.gohtml @@ -0,0 +1,72 @@ +{{- $content := index . "content" -}} +{{- $entry := index . "entry" -}} +{{- $csrf := index . "csrf_token" -}} +{{- $contentTypes := index . "content_types" -}} +{{- $musenalmTypes := index . "musenalm_types" -}} +{{- $paginationValues := index . "pagination_values" -}} +{{- $agents := index . "agents" -}} +{{- $contentAgents := index . "content_agents" -}} +{{- $agentRelations := index . "agent_relations" -}} +{{- $contentID := index . "content_id" -}} +{{- $error := index . "error" -}} + +
+
+ + {{- if $error -}} +
+ {{ $error }} +
+ {{- end -}} + {{- template "_content_edit" (Dict + "content" $content + "content_id" $contentID + "entry" $entry + "content_types" $contentTypes + "musenalm_types" $musenalmTypes + "pagination_values" $paginationValues + "agents" $agents + "content_agents" $contentAgents + "agent_relations" $agentRelations + ) -}} +
+ + + +
+
+ +
+
Eintrag löschen?
+ {{- if $content.TitleStmt -}} +
{{ $content.TitleStmt }}
+ {{- end -}} +

+ Der Eintrag wird dauerhaft gelöscht. Verknüpfungen, Exemplare und Inhalte werden entfernt. +

+
+ + +
+
+
+
diff --git a/views/routes/components/_content_item.gohtml b/views/routes/components/_content_item.gohtml index a32ba5a..be42d0a 100644 --- a/views/routes/components/_content_item.gohtml +++ b/views/routes/components/_content_item.gohtml @@ -16,8 +16,9 @@ {{- if and $overrideID (ne $overrideID "") -}} {{- $contentID = $overrideID -}} {{- end -}} +{{- $editContainerID := printf "content-%s-edit-container" $contentID -}} -
+
@@ -56,7 +57,14 @@ {{- 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 -}} - @@ -109,64 +117,21 @@
-
-
- - {{- if $error -}} -
- {{ $error }} -
- {{- end -}} - {{- template "_content_edit" (Dict +
+ {{- if $openEdit -}} + {{- template "_content_edit_form" (Dict "content" $content "content_id" $contentID "entry" $entry + "csrf_token" $csrf "content_types" $contentTypes "musenalm_types" $musenalmTypes "pagination_values" $paginationValues "agents" $agents "content_agents" $contentAgents "agent_relations" $agentRelations + "error" $error ) -}} -
- - - -
- - -
-
Eintrag löschen?
- {{- if $content.TitleStmt -}} -
{{ $content.TitleStmt }}
- {{- end -}} -

- Der Eintrag wird dauerhaft gelöscht. Verknüpfungen, Exemplare und Inhalte werden entfernt. -

-
- - -
-
-
+ {{- end -}}