+ some minor frontend annoyanceS

This commit is contained in:
Simon Martens
2026-01-21 22:51:13 +01:00
parent e2b9fca6d9
commit 8f4558e331
3 changed files with 194 additions and 173 deletions

File diff suppressed because one or more lines are too long

View File

@@ -142,15 +142,15 @@
class="min-w-[10rem] w-full max-w-none flex-1 border border-slate-300 rounded-xs bg-white px-3 py-1.5 text-base leading-6 text-gray-800 focus:outline-none focus:ring-2 focus:ring-slate-400/30" class="min-w-[10rem] w-full max-w-none flex-1 border border-slate-300 rounded-xs bg-white px-3 py-1.5 text-base leading-6 text-gray-800 focus:outline-none focus:ring-2 focus:ring-slate-400/30"
placeholder="z. B. 12 Bl., 3 Taf." placeholder="z. B. 12 Bl., 3 Taf."
value="{{- $model.result.Entry.Extent -}}" /> value="{{- $model.result.Entry.Extent -}}" />
<button type="submit" class="rounded-xs border border-slate-300 bg-stone-100 px-3 py-1.5 text-base font-semibold text-gray-700 hover:bg-stone-200"> <button type="submit" class="rounded-xs border border-slate-300 bg-stone-50 px-3 py-1.5 text-base font-semibold text-gray-700 shadow-sm hover:bg-stone-100">
Speichern Struktur speichern
</button> </button>
</form> </form>
</div> </div>
<div class="px-4 text-xl font-bold text-gray-800 mt-3">Inhalt</div> <div class="px-4 text-xl font-bold text-gray-800 mt-3">Inhalt</div>
<div class="px-4 py-2 flex flex-wrap items-center gap-3"> <div class="px-4 py-2 flex flex-wrap items-center gap-3">
<label for="content-filter" class="text-sm font-bold text-gray-700 whitespace-nowrap"> <label for="content-filter" class="text-base font-bold text-gray-700 whitespace-nowrap">
<i class="ri-search-line"></i> Filtern <i class="ri-search-line"></i> Filter
</label> </label>
<input <input
id="content-filter" id="content-filter"
@@ -185,7 +185,9 @@
<span>Neuer Beitrag</span> <span>Neuer Beitrag</span>
</button> </button>
</div> </div>
<div class="flex flex-col gap-0 mt-2" <div class="mx-4 mt-2 rounded-md border border-slate-200 bg-white flex flex-col overflow-hidden" data-role="contents-panel">
<div class="flex-1 min-h-0 overflow-y-auto">
<div class="flex flex-col gap-0"
data-role="contents-list" data-role="contents-list"
data-order-endpoint="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit"> data-order-endpoint="/almanach/{{ $model.result.Entry.MusenalmID }}/contents/edit">
{{- range $_, $content := $model.result.Contents -}} {{- range $_, $content := $model.result.Contents -}}
@@ -204,7 +206,8 @@
) -}} ) -}}
{{- end -}} {{- end -}}
</div> </div>
<div class="px-4 py-2 mt-2 flex items-center"> </div>
<div class="px-3 py-2 border-t border-slate-200 bg-stone-50 flex items-center">
<button <button
type="button" type="button"
class="content-action-button" class="content-action-button"
@@ -214,6 +217,7 @@
</button> </button>
</div> </div>
</div> </div>
</div>
<dialog id="content-delete-dialog" class="dbform fixed inset-0 m-auto rounded-md border border-slate-200 p-0 shadow-xl backdrop:bg-black/40"> <dialog id="content-delete-dialog" class="dbform fixed inset-0 m-auto rounded-md border border-slate-200 p-0 shadow-xl backdrop:bg-black/40">
<div class="p-5 w-[22rem]"> <div class="p-5 w-[22rem]">
@@ -290,6 +294,8 @@
const list = document.querySelector("[data-role='contents-list']"); const list = document.querySelector("[data-role='contents-list']");
if (!filterInput || !list) return; if (!filterInput || !list) return;
if (filterInput.dataset.init === "true") return;
filterInput.dataset.init = "true";
// Populate type select from search index // Populate type select from search index
if (filterType && window.contentsSearchIndex) { if (filterType && window.contentsSearchIndex) {
@@ -574,6 +580,11 @@
let orderSyncTimer = null; let orderSyncTimer = null;
let isOrderSyncing = false; let isOrderSyncing = false;
let pendingOrderSync = false; let pendingOrderSync = false;
if (list.__contentsDragController) {
list.__contentsDragController.abort();
}
list.__contentsDragController = new AbortController();
const { signal: dragSignal } = list.__contentsDragController;
const setSyncIndicator = (active) => { const setSyncIndicator = (active) => {
if (!syncIndicator) { if (!syncIndicator) {
@@ -638,7 +649,8 @@
}; };
// Setup shared delete dialog events // Setup shared delete dialog events
if (deleteDialog) { if (deleteDialog && deleteDialog.dataset.init !== "true") {
deleteDialog.dataset.init = "true";
deleteCancelBtn?.addEventListener("click", () => deleteDialog.close()); deleteCancelBtn?.addEventListener("click", () => deleteDialog.close());
deleteDialog.addEventListener("cancel", (e) => { deleteDialog.addEventListener("cancel", (e) => {
e.preventDefault(); e.preventDefault();
@@ -730,7 +742,6 @@
setupItem(item); setupItem(item);
}); });
if (list.dataset.pageInit !== "true") {
list.dataset.pageInit = "true"; list.dataset.pageInit = "true";
let draggedItem = null; let draggedItem = null;
let pointerDrag = null; let pointerDrag = null;
@@ -762,6 +773,11 @@
if (!pointerDrag || event.pointerId !== pointerDrag.pointerId) { if (!pointerDrag || event.pointerId !== pointerDrag.pointerId) {
return; return;
} }
const now = Date.now();
if (now - lastDragOverTime < DRAG_THROTTLE_MS) {
return;
}
lastDragOverTime = now;
const item = pointerDrag.item; const item = pointerDrag.item;
const targetItem = document.elementFromPoint(event.clientX, event.clientY)?.closest("[data-role='content-item']"); const targetItem = document.elementFromPoint(event.clientX, event.clientY)?.closest("[data-role='content-item']");
if (!targetItem || targetItem === item) { if (!targetItem || targetItem === item) {
@@ -815,7 +831,7 @@
} }
} }
syncOrder(); syncOrder();
}); }, { signal: dragSignal });
list.addEventListener("dragstart", (event) => { list.addEventListener("dragstart", (event) => {
if (pointerDrag) { if (pointerDrag) {
@@ -843,7 +859,7 @@
setDraggingState(true); setDraggingState(true);
event.dataTransfer.effectAllowed = "move"; event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", "move"); event.dataTransfer.setData("text/plain", "move");
}); }, { signal: dragSignal });
list.addEventListener("dragover", (event) => { list.addEventListener("dragover", (event) => {
if (!draggedItem) { if (!draggedItem) {
@@ -861,7 +877,7 @@
} else { } else {
targetItem.after(draggedItem); targetItem.after(draggedItem);
} }
}); }, { signal: dragSignal });
list.addEventListener("dragend", () => { list.addEventListener("dragend", () => {
if (draggedItem) { if (draggedItem) {
@@ -871,15 +887,20 @@
draggedItem = null; draggedItem = null;
setDraggingState(false); setDraggingState(false);
syncOrder(); syncOrder();
}); }, { signal: dragSignal });
list.addEventListener("pointerdown", startPointerDrag); list.addEventListener("pointerdown", startPointerDrag, { signal: dragSignal });
list.addEventListener("pointermove", movePointerDrag); list.addEventListener("pointermove", movePointerDrag, { signal: dragSignal });
list.addEventListener("pointerup", endPointerDrag); list.addEventListener("pointerup", endPointerDrag, { signal: dragSignal });
list.addEventListener("pointercancel", endPointerDrag); list.addEventListener("pointercancel", endPointerDrag, { signal: dragSignal });
}
}; };
initFilter(); initFilter();
initPage(); initPage();
window.addEventListener("pageshow", (event) => {
if (event.persisted) {
initFilter();
initPage();
}
});
</script> </script>

View File

@@ -135,7 +135,7 @@
} }
.content-action-button { .content-action-button {
@apply inline-flex items-center justify-center gap-2 rounded-xs border border-slate-300 bg-white px-3 py-1.5 text-sm font-medium text-gray-800 shadow-sm transition-all duration-75 hover:bg-stone-50 focus:outline-none focus:ring-2 focus:ring-slate-400/30; @apply inline-flex items-center justify-center gap-2 rounded-xs border border-slate-300 bg-white px-3 py-1.5 text-base font-semibold text-gray-800 shadow-sm transition-all duration-75 hover:bg-stone-50 focus:outline-none focus:ring-2 focus:ring-slate-400/30;
} }
.dbform div-menu { .dbform div-menu {