From 43a10e4ec27d3d8332634719be4063cb43b220de Mon Sep 17 00:00:00 2001 From: Simon Martens Date: Fri, 16 Jan 2026 20:28:07 +0100 Subject: [PATCH] +collapsed view by default --- controllers/almanach_contents_edit.go | 21 ++++- .../routes/almanach/contents/edit/body.gohtml | 92 +++++++++++++++++-- .../components/_content_edit_form.gohtml | 1 + views/routes/components/_content_item.gohtml | 5 +- 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/controllers/almanach_contents_edit.go b/controllers/almanach_contents_edit.go index 5ecb1d2..3e0451d 100644 --- a/controllers/almanach_contents_edit.go +++ b/controllers/almanach_contents_edit.go @@ -293,7 +293,26 @@ func (p *AlmanachContentsEditPage) POSTSave(engine *templating.Engine, app core. if len(updatedContents) == 0 { updatedContents = contents } - go updateContentsFTS5(app, entry, updatedContents) + shouldUpdateFTS := len(contentInputs) > 0 || len(newContentIDs) > 0 + if shouldUpdateFTS { + touched := updatedContents + if len(contentInputs) > 0 { + touchedIDs := map[string]struct{}{} + for id := range contentInputs { + touchedIDs[id] = struct{}{} + } + filtered := make([]*dbmodels.Content, 0, len(touchedIDs)) + for _, content := range updatedContents { + if _, ok := touchedIDs[content.Id]; ok { + filtered = append(filtered, content) + } + } + if len(filtered) > 0 { + touched = filtered + } + } + go updateContentsFTS5(app, entry, touched) + } redirect := fmt.Sprintf("/almanach/%s/contents/edit?saved_message=%s", id, url.QueryEscape("Änderungen gespeichert.")) if isHTMX { diff --git a/views/routes/almanach/contents/edit/body.gohtml b/views/routes/almanach/contents/edit/body.gohtml index 5063151..9669dec 100644 --- a/views/routes/almanach/contents/edit/body.gohtml +++ b/views/routes/almanach/contents/edit/body.gohtml @@ -127,9 +127,13 @@ Reihenfolge wird gespeichert +
- @@ -217,6 +221,7 @@ const button = document.createElement("button"); button.type = "button"; button.className = "absolute left-1/2 top-1/2 z-[10000] -translate-x-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-150 rounded-full border border-slate-300 bg-stone-100 text-slate-700 px-3 py-2 text-base shadow-sm"; + button.dataset.loadingLabel = "Eintrag wird geladen"; button.setAttribute("aria-label", "Beitrag einfügen"); button.setAttribute("hx-post", insertEndpoint); button.setAttribute("hx-target", "closest [data-role='content-gap']"); @@ -272,6 +277,7 @@ 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"); + const htmxIndicator = document.querySelector("#contents-htmx-indicator"); let orderSyncTimer = null; let isOrderSyncing = false; let pendingOrderSync = false; @@ -282,6 +288,21 @@ } syncIndicator.classList.toggle("hidden", !active); }; + const setHtmxIndicator = (active) => { + if (!htmxIndicator) { + return; + } + htmxIndicator.classList.toggle("hidden", !active); + }; + const setHtmxIndicatorLabel = (label) => { + if (!htmxIndicator || !label) { + return; + } + const labelEl = htmxIndicator.querySelector("[data-role='contents-htmx-label']"); + if (labelEl) { + labelEl.textContent = label; + } + }; const performOrderSync = () => { if (!list || !orderEndpoint || !csrfToken || isOrderSyncing) { @@ -405,6 +426,7 @@ 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 header = item.querySelector("[data-content-header='true']"); const setCollapsed = (collapsed) => { if (!viewBody || !collapsedSummary) { @@ -414,6 +436,10 @@ item.classList.toggle("data-collapsed", collapsed); viewBody.classList.toggle("hidden", collapsed); collapsedSummary.classList.toggle("hidden", !collapsed); + if (header) { + header.classList.toggle("bg-stone-100", collapsed); + header.classList.toggle("bg-stone-200", !collapsed); + } if (collapseButton) { collapseButton.setAttribute("aria-expanded", (!collapsed).toString()); collapseButton.setAttribute("aria-label", collapsed ? "Beitrag ausklappen" : "Beitrag einklappen"); @@ -581,6 +607,7 @@ const collapsedSummary = item.querySelector("[data-role='content-collapsed-summary']"); const collapseButton = item.querySelector("[data-role='content-collapse-toggle']"); const collapseIcon = item.querySelector("[data-role='content-collapse-icon']"); + const header = item.querySelector("[data-content-header='true']"); if (!viewBody || !collapsedSummary) { return; } @@ -588,6 +615,10 @@ item.classList.toggle("data-collapsed", collapsed); viewBody.classList.toggle("hidden", collapsed); collapsedSummary.classList.toggle("hidden", !collapsed); + if (header) { + header.classList.toggle("bg-stone-100", collapsed); + header.classList.toggle("bg-stone-200", !collapsed); + } if (collapseButton) { collapseButton.setAttribute("aria-expanded", (!collapsed).toString()); collapseButton.setAttribute("aria-label", collapsed ? "Beitrag ausklappen" : "Beitrag einklappen"); @@ -618,6 +649,10 @@ renderInsertGaps(); syncEditSpacing(); showEditButtonsIfIdle(); + if (!list.dataset.collapseInit) { + list.dataset.collapseInit = "true"; + setAllCollapsed(true); + } updateCollapseAllLabel(); if (list.dataset.pageInit !== "true") { @@ -741,16 +776,55 @@ 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); - } + 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); } } + target.querySelectorAll("multi-select-simple[data-initial-options], multi-select-simple[data-initial-values]").forEach((el) => { + applyMultiSelectInit(el); + }); + } initWhenReady(); }); + + document.body.addEventListener("htmx:beforeRequest", (event) => { + const elt = event.detail?.elt; + if (!elt || !list) { + return; + } + if (elt.closest("[data-role='contents-list']")) { + const label = elt.getAttribute("data-loading-label"); + if (label) { + setHtmxIndicatorLabel(label); + } else { + setHtmxIndicatorLabel("Eintrag wird geladen"); + } + setHtmxIndicator(true); + } + }); + + document.body.addEventListener("htmx:afterRequest", (event) => { + const elt = event.detail?.elt; + if (!elt || !list) { + return; + } + if (elt.closest("[data-role='contents-list']")) { + setHtmxIndicator(false); + } + }); + + document.body.addEventListener("htmx:responseError", (event) => { + const elt = event.detail?.elt; + if (!elt || !list) { + return; + } + if (elt.closest("[data-role='contents-list']")) { + setHtmxIndicator(false); + } + }); diff --git a/views/routes/components/_content_edit_form.gohtml b/views/routes/components/_content_edit_form.gohtml index ac48a8a..206bdb9 100644 --- a/views/routes/components/_content_edit_form.gohtml +++ b/views/routes/components/_content_edit_form.gohtml @@ -15,6 +15,7 @@ autocomplete="off" class="w-full dbform" method="POST" + data-loading-label="Eintrag wird gespeichert" hx-boost="false" hx-post="/almanach/{{ $entry.MusenalmID }}/contents/edit" hx-target="closest [data-role='content-item']" diff --git a/views/routes/components/_content_item.gohtml b/views/routes/components/_content_item.gohtml index be42d0a..39330d3 100644 --- a/views/routes/components/_content_item.gohtml +++ b/views/routes/components/_content_item.gohtml @@ -18,10 +18,10 @@ {{- end -}} {{- $editContainerID := printf "content-%s-edit-container" $contentID -}} -
+
-
+