BUGFIX: values resetted if error

This commit is contained in:
Simon Martens
2026-01-23 20:21:32 +01:00
parent 0beb5a2c79
commit ad02de8807
11 changed files with 465 additions and 317 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -91,6 +91,7 @@
<p <p
id="abk-save-feedback" id="abk-save-feedback"
class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}" class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}"
{{ if $model.success }}data-autohide="true"{{ end }}
aria-live="polite"> aria-live="polite">
{{- if $model.error -}} {{- if $model.error -}}
{{ $model.error }} {{ $model.error }}

View File

@@ -257,6 +257,7 @@
<p <p
id="user-message" id="user-message"
class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}" class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}"
{{ if $model.success }}data-autohide="true"{{ end }}
aria-live="polite"> aria-live="polite">
{{- if $model.error -}} {{- if $model.error -}}
{{ $model.error }} {{ $model.error }}
@@ -433,7 +434,7 @@
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
event.stopImmediatePropagation(); event.stopImmediatePropagation();
const submitter = event.submitter; const submitter = event.submitter || document.activeElement;
const files = Array.from(uploadInput.files || []); const files = Array.from(uploadInput.files || []);
if (files.length > 0) { if (files.length > 0) {
const hasInvalid = files.some((file) => !file.type || !file.type.startsWith("image/")); const hasInvalid = files.some((file) => !file.type || !file.type.startsWith("image/"));
@@ -488,6 +489,23 @@
return; return;
} }
const doc = new DOMParser().parseFromString(html, "text/html"); const doc = new DOMParser().parseFromString(html, "text/html");
const nextMessage = doc.getElementById("user-message");
const liveMessage = document.getElementById("user-message");
const isErrorMessage = nextMessage && nextMessage.classList.contains("save-feedback-error");
if (nextMessage && liveMessage) {
liveMessage.className = nextMessage.className;
liveMessage.textContent = nextMessage.textContent || "";
if (!liveMessage.textContent.trim()) {
liveMessage.classList.add("hidden");
}
}
if (isErrorMessage || !response.ok) {
if (!nextMessage && !response.ok) {
showUserMessage("Speichern fehlgeschlagen.", "error");
}
return;
}
const nextForm = doc.querySelector("form.dbform"); const nextForm = doc.querySelector("form.dbform");
if (nextForm) { if (nextForm) {
form.replaceWith(nextForm); form.replaceWith(nextForm);
@@ -495,19 +513,6 @@
attachFormHandlers(); attachFormHandlers();
return; return;
} }
const nextMessage = doc.getElementById("user-message");
if (nextMessage) {
const liveMessage = document.getElementById("user-message");
if (liveMessage) {
liveMessage.className = nextMessage.className;
liveMessage.textContent = nextMessage.textContent || "";
if (!liveMessage.textContent.trim()) {
liveMessage.classList.add("hidden");
}
}
} else if (!response.ok) {
showUserMessage("Speichern fehlgeschlagen.", "error");
}
}, true); }, true);
}; };

View File

@@ -1208,6 +1208,7 @@ type AlmanachResult struct {
<p <p
id="almanach-save-feedback" id="almanach-save-feedback"
class="save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}" class="save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}"
{{ if $model.success }}data-autohide="true"{{ end }}
aria-live="polite"> aria-live="polite">
{{- if $model.error -}} {{- if $model.error -}}
{{ $model.error }} {{ $model.error }}

View File

@@ -197,6 +197,7 @@
<p <p
id="place-save-feedback" id="place-save-feedback"
class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}" class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}"
{{ if $model.success }}data-autohide="true"{{ end }}
aria-live="polite"> aria-live="polite">
{{- if $model.error -}} {{- if $model.error -}}
{{ $model.error }} {{ $model.error }}

View File

@@ -248,6 +248,7 @@
<p <p
id="person-save-feedback" id="person-save-feedback"
class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}" class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}"
{{ if $model.success }}data-autohide="true"{{ end }}
aria-live="polite"> aria-live="polite">
{{- if $model.error -}} {{- if $model.error -}}
{{ $model.error }} {{ $model.error }}

View File

@@ -183,6 +183,7 @@
<p <p
id="series-save-feedback" id="series-save-feedback"
class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}" class="form-action-bar-message save-feedback {{ if $model.error }}save-feedback-error text-red-700{{ else if $model.success }}save-feedback-success text-green-700{{ else }}hidden{{ end }}"
{{ if $model.success }}data-autohide="true"{{ end }}
aria-live="polite"> aria-live="polite">
{{- if $model.error -}} {{- if $model.error -}}
{{ $model.error }} {{ $model.error }}

View File

@@ -248,6 +248,7 @@ export class AlmanachEditPage extends HTMLElement {
try { try {
const response = await fetch(this._saveEndpoint, { const response = await fetch(this._saveEndpoint, {
method: "POST", method: "POST",
credentials: "same-origin",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
@@ -296,6 +297,7 @@ export class AlmanachEditPage extends HTMLElement {
try { try {
const response = await fetch(this._saveEndpoint, { const response = await fetch(this._saveEndpoint, {
method: "POST", method: "POST",
credentials: "same-origin",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Accept: "application/json", Accept: "application/json",
@@ -684,6 +686,7 @@ export class AlmanachEditPage extends HTMLElement {
} }
this._statusEl.textContent = ""; this._statusEl.textContent = "";
this._statusEl.classList.remove("text-red-700", "text-green-700", "save-feedback-error", "save-feedback-success"); this._statusEl.classList.remove("text-red-700", "text-green-700", "save-feedback-error", "save-feedback-success");
this._statusEl.classList.remove("is-hidden");
this._statusEl.classList.add("hidden"); this._statusEl.classList.add("hidden");
} }
@@ -694,11 +697,29 @@ export class AlmanachEditPage extends HTMLElement {
this._clearStatus(); this._clearStatus();
this._statusEl.textContent = message; this._statusEl.textContent = message;
this._statusEl.classList.remove("hidden"); this._statusEl.classList.remove("hidden");
this._statusEl.classList.remove("is-hidden");
if (type === "success") { if (type === "success") {
this._statusEl.classList.add("text-green-700", "save-feedback-success"); this._statusEl.classList.add("text-green-700", "save-feedback-success");
} else if (type === "error") { } else if (type === "error") {
this._statusEl.classList.add("text-red-700", "save-feedback-error"); this._statusEl.classList.add("text-red-700", "save-feedback-error");
} }
if (type === "success") {
const el = this._statusEl;
if (el) {
if (el.dataset.autohideScheduled === "true") {
return;
}
el.dataset.autohideScheduled = "true";
setTimeout(() => {
el.classList.add("is-hiding");
setTimeout(() => {
el.classList.add("is-hidden");
el.classList.remove("is-hiding");
delete el.dataset.autohideScheduled;
}, 320);
}, 4000);
}
}
} }
async _reloadForm(successMessage) { async _reloadForm(successMessage) {
@@ -711,6 +732,7 @@ export class AlmanachEditPage extends HTMLElement {
} }
const response = await fetch(targetUrl.toString(), { const response = await fetch(targetUrl.toString(), {
credentials: "same-origin",
headers: { headers: {
"X-Requested-With": "fetch", "X-Requested-With": "fetch",
}, },

View File

@@ -205,7 +205,12 @@
} }
.save-feedback { .save-feedback {
@apply text-sm font-semibold px-3 py-2 rounded-xs border bg-stone-50 text-gray-700; @apply text-sm font-semibold px-3 py-2 rounded-md border bg-stone-50 text-gray-700 transition-all duration-300 relative overflow-hidden;
transform-origin: center left;
}
.save-feedback.hidden {
@apply opacity-0 pointer-events-none;
} }
.save-feedback-error { .save-feedback-error {
@@ -216,6 +221,37 @@
@apply bg-green-50 border-green-200 text-green-800; @apply bg-green-50 border-green-200 text-green-800;
} }
.save-feedback.is-hiding {
@apply opacity-0;
transform: translateY(4px) scale(0.98);
}
.save-feedback[data-autohide="true"]::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
background: currentColor;
opacity: 0.35;
transform-origin: left center;
animation: save-feedback-timer 2s linear forwards;
}
@keyframes save-feedback-timer {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
.save-feedback.is-hidden {
@apply opacity-0 pointer-events-none;
}
.lf-warn-icon { .lf-warn-icon {
@apply ml-1 mr-2; @apply ml-1 mr-2;
} }

View File

@@ -644,6 +644,48 @@ function InitStickyActionBars() {
document.addEventListener("htmx:afterSwap", update); document.addEventListener("htmx:afterSwap", update);
} }
function InitTimedMessages() {
const duration = 2000;
const hide = (el) => {
if (!el || el.classList.contains("hidden") || el.classList.contains("is-hidden")) {
return;
}
requestAnimationFrame(() => {
el.classList.add("is-hiding");
});
setTimeout(() => {
el.classList.add("is-hidden");
el.classList.remove("is-hiding");
delete el.dataset.autohideScheduled;
}, 320);
};
const schedule = (root) => {
const scope = root || document;
scope.querySelectorAll("[data-autohide='true']").forEach((el) => {
if (el.dataset.autohideScheduled === "true") {
return;
}
el.dataset.autohideScheduled = "true";
setTimeout(() => hide(el), duration);
});
};
schedule(document);
document.addEventListener("htmx:afterSwap", (event) => {
schedule(event.target);
});
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
schedule(node);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
document.addEventListener("keydown", (event) => { document.addEventListener("keydown", (event) => {
if (event.key !== "Enter") { if (event.key !== "Enter") {
return; return;
@@ -664,7 +706,9 @@ window.PathPlusQuery = PathPlusQuery;
window.HookupRBChange = HookupRBChange; window.HookupRBChange = HookupRBChange;
window.FormLoad = FormLoad; window.FormLoad = FormLoad;
window.TextareaAutoResize = TextareaAutoResize; window.TextareaAutoResize = TextareaAutoResize;
window.InitTimedMessages = InitTimedMessages;
InitGlobalHtmxNotice(); InitGlobalHtmxNotice();
InitStickyActionBars(); InitStickyActionBars();
InitTimedMessages();
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor, SingleSelectRemote, AlmanachEditPage, RelationsEditor, EditPage, FabMenu, LookupField }; export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor, SingleSelectRemote, AlmanachEditPage, RelationsEditor, EditPage, FabMenu, LookupField };