+collapsed view by default

This commit is contained in:
Simon Martens
2026-01-16 20:28:07 +01:00
parent 6583114773
commit 43a10e4ec2
4 changed files with 107 additions and 12 deletions

View File

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

View File

@@ -127,9 +127,13 @@
<i class="ri-loader-4-line spinning mr-2"></i>
Reihenfolge wird gespeichert
</div>
<div id="contents-htmx-indicator" class="fixed right-6 bottom-16 z-50 hidden rounded-full bg-stone-200 px-3 py-2 text-sm text-stone-700 shadow-md">
<i class="ri-loader-4-line spinning mr-2"></i>
<span data-role="contents-htmx-label">Eintrag wird geladen</span>
</div>
<input type="hidden" name="csrf_token" value="{{ $model.csrf_token }}" data-role="csrf-token" />
<div class="flex items-center justify-end px-4">
<button type="button" class="resetbutton w-auto px-3 py-2 text-sm flex items-center gap-2" data-role="contents-collapse-all" data-state="expanded">
<button type="button" class="resetbutton w-auto px-3 py-2 flex items-center gap-2" data-role="contents-collapse-all" data-state="expanded">
<i class="ri-arrow-up-s-line" data-role="contents-collapse-all-icon"></i>
<span data-role="contents-collapse-all-label">Alle Eintraege einklappen</span>
</button>
@@ -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") {
@@ -750,7 +785,46 @@
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);
}
});
</script>

View File

@@ -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']"

View File

@@ -18,10 +18,10 @@
{{- end -}}
{{- $editContainerID := printf "content-%s-edit-container" $contentID -}}
<div data-role="content-item" class="relative {{ if $isNew }}data-new-content{{ end }} {{ if $openEdit }}data-editing{{ end }}" data-open-edit="{{ if $openEdit }}true{{ end }}" data-content-temp="{{ if $isNew }}true{{ end }}" data-content-id="{{ $contentID }}">
<div data-role="content-item" class="relative {{ if $isNew }}data-new-content{{ end }} {{ if $openEdit }}data-editing{{ end }}" data-open-edit="{{ if $openEdit }}true{{ end }}" data-content-temp="{{ if $isNew }}true{{ end }}" data-content-id="{{ $contentID }}" data-collapsed="true">
<div data-role="content-view" class="{{ if $openEdit }}hidden{{ end }} mt-2">
<div class="border border-slate-200 bg-stone-100 rounded-xs overflow-hidden">
<div class="flex items-center justify-between gap-4 border-b border-slate-200 bg-stone-200 px-3 py-2 cursor-grab" data-role="content-drag-handle" draggable="true" aria-label="Beitrag verschieben">
<div class="flex items-center justify-between gap-4 border-b border-slate-200 bg-stone-100 px-3 py-2 cursor-grab" data-role="content-drag-handle" data-content-header="true" draggable="true" aria-label="Beitrag verschieben">
<div class="flex items-center gap-2 text-sm font-bold text-gray-800">
<button type="button" class="text-slate-600 rounded-xs px-2 py-1 text-sm transition-colors hover:bg-stone-100 {{ if $isNew }}hidden{{ end }}" data-role="content-collapse-toggle" aria-label="Beitrag einklappen" aria-expanded="true">
<i class="ri-arrow-up-s-line" data-role="content-collapse-icon"></i>
@@ -61,6 +61,7 @@
type="button"
class="resetbutton w-32 flex items-center gap-2 justify-center"
data-role="content-edit-button"
data-loading-label="Eintrag wird geladen"
hx-boost="false"
hx-get="/almanach/{{ $entry.MusenalmID }}/contents/edit/form?content_id={{ $contentID }}"
hx-target="#{{ $editContainerID }}"