mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
Frontend annoyances
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -288,7 +288,7 @@ type AlmanachResult struct {
|
||||
<i class="ri-add-line"></i> Reihe hinzufügen
|
||||
</button>
|
||||
<button type="button" id="agents-add-toggle" class="text-gray-700 hover:text-gray-900">
|
||||
<i class="ri-add-line"></i> Akteur hinzufügen
|
||||
<i class="ri-add-line"></i> Person hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -297,19 +297,19 @@ type AlmanachResult struct {
|
||||
<relations-editor data-prefix="entries_series" data-link-base="/reihe/" data-new-label="(Neu)" data-add-toggle-id="series-add-toggle">
|
||||
<div class="inputwrapper">
|
||||
<label class="inputlabel" for="series-section">Reihen</label>
|
||||
<div id="series-section" class="flex flex-col gap-2 mt-2">
|
||||
<div id="series-section" class="rel-section-container">
|
||||
{{- if $model.result.Series -}}
|
||||
{{- range $i, $s := $model.result.Series -}}
|
||||
{{- $rel := index $model.result.EntriesSeries $s.Id -}}
|
||||
{{- if $rel -}}
|
||||
<div data-rel-row class="entries-series-row border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
|
||||
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
|
||||
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
|
||||
<a data-rel-link href="/reihe/{{ $s.MusenalmID }}" class="text-base text-gray-800 no-underline hover:text-slate-900 truncate">
|
||||
<div data-rel-row class="entries-series-row rel-row">
|
||||
<div class="rel-grid">
|
||||
<div data-rel-strike class="relation-strike rel-name-col">
|
||||
<a data-rel-link href="/reihe/{{ $s.MusenalmID }}" class="rel-link">
|
||||
<span data-rel-name>{{- $s.Title -}}</span>
|
||||
</a>
|
||||
{{- if $s.Pseudonyms -}}
|
||||
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail>{{- $s.Pseudonyms -}}</span></div>
|
||||
<div data-rel-detail-container class="rel-detail"><span data-rel-detail>{{- $s.Pseudonyms -}}</span></div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<div data-rel-strike class="relation-strike">
|
||||
@@ -319,15 +319,15 @@ type AlmanachResult struct {
|
||||
{{- end -}}
|
||||
</select>
|
||||
</div>
|
||||
<div data-rel-strike class="relation-strike flex items-center gap-2">
|
||||
<div data-rel-strike class="relation-strike rel-uncertain-container">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="entries_series_uncertain[{{ $rel.Id }}]"
|
||||
id="entries_series_uncertain_{{ $rel.Id }}"
|
||||
{{ if $rel.Uncertain }}checked{{ end }} />
|
||||
<label for="entries_series_uncertain_{{ $rel.Id }}" class="text-sm text-gray-700">Unsicher</label>
|
||||
<label for="entries_series_uncertain_{{ $rel.Id }}" class="rel-uncertain-label">Unsicher</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="rel-button-container">
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm"
|
||||
@@ -344,13 +344,13 @@ type AlmanachResult struct {
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<div class="text-sm text-gray-600">Keine Reihen verknüpft.</div>
|
||||
<div class="rel-empty-text">Keine Reihen verknüpft.</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<div data-role="relation-add-row" class="mt-2"></div>
|
||||
<div data-role="relation-add-panel" class="mt-2 hidden">
|
||||
<div class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
|
||||
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
|
||||
<div data-role="relation-add-row" class="mt-2 px-2"></div>
|
||||
<div data-role="relation-add-panel" class="mt-2 px-2 hidden">
|
||||
<div class="rel-row">
|
||||
<div class="rel-grid">
|
||||
<div class="min-w-0">
|
||||
<label for="series-add-select" class="sr-only">Reihe suchen</label>
|
||||
<single-select-remote
|
||||
@@ -372,11 +372,11 @@ type AlmanachResult struct {
|
||||
{{- end -}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="rel-uncertain-container">
|
||||
<input data-role="relation-uncertain" type="checkbox" name="entries_series_new_uncertain" id="entries_series_new_uncertain" />
|
||||
<label for="entries_series_new_uncertain" class="text-sm text-gray-700">Unsicher</label>
|
||||
<label for="entries_series_new_uncertain" class="rel-uncertain-label">Unsicher</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="rel-button-container">
|
||||
<div class="flex items-center gap-3 text-lg">
|
||||
<button type="button" data-role="relation-add-apply" class="text-gray-700 hover:text-gray-900" id="series-add-apply" aria-label="Reihe hinzufügen">
|
||||
<i class="ri-check-line"></i>
|
||||
@@ -387,29 +387,29 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden">Bitte Reihe auswählen.</div>
|
||||
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden" data-error-empty="Bitte Reihe auswählen." data-error-duplicate="Diese Reihe ist bereits verknüpft.">Bitte Reihe auswählen.</div>
|
||||
</div>
|
||||
</div>
|
||||
<template data-role="relation-new-template">
|
||||
<div data-rel-row class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
|
||||
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
|
||||
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
|
||||
<div data-rel-row class="rel-row">
|
||||
<div class="rel-grid">
|
||||
<div data-rel-strike class="relation-strike rel-name-col">
|
||||
<div class="text-base text-gray-800 truncate">
|
||||
<a data-rel-link class="no-underline hover:text-slate-900">
|
||||
<span data-rel-name></span>
|
||||
</a>
|
||||
<em data-rel-new class="text-sm text-gray-600 ml-1"></em>
|
||||
<em data-rel-new class="rel-new-badge"></em>
|
||||
</div>
|
||||
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail></span></div>
|
||||
<div data-rel-detail-container class="rel-detail"><span data-rel-detail></span></div>
|
||||
</div>
|
||||
<div data-rel-strike class="relation-strike">
|
||||
<select data-rel-input="type" class="inputselect font-bold w-full"></select>
|
||||
</div>
|
||||
<div data-rel-strike class="relation-strike flex items-center gap-2">
|
||||
<div data-rel-strike class="relation-strike rel-uncertain-container">
|
||||
<input data-rel-input="uncertain" type="checkbox" />
|
||||
<label data-rel-uncertain-label class="text-sm text-gray-700">Unsicher</label>
|
||||
<label data-rel-uncertain-label class="rel-uncertain-label">Unsicher</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="rel-button-container">
|
||||
<button type="button" class="text-sm text-red-700 hover:text-red-900" data-role="relation-new-delete">
|
||||
<i class="ri-delete-bin-line mr-1"></i> Entfernen
|
||||
</button>
|
||||
@@ -426,19 +426,19 @@ type AlmanachResult struct {
|
||||
<relations-editor data-prefix="entries_agents" data-link-base="/person/" data-new-label="(Neu)" data-add-toggle-id="agents-add-toggle">
|
||||
<div class="inputwrapper">
|
||||
<label class="inputlabel" for="agents-section">Personen & Körperschaften</label>
|
||||
<div id="agents-section" class="flex flex-col gap-2 mt-2">
|
||||
<div id="agents-section" class="rel-section-container">
|
||||
{{- if $model.result.EntriesAgents -}}
|
||||
{{- range $i, $r := $model.result.EntriesAgents -}}
|
||||
{{- $a := index $model.result.Agents $r.Agent -}}
|
||||
<div data-rel-row class="entries-agent-row border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
|
||||
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
|
||||
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
|
||||
<div data-rel-row class="entries-agent-row rel-row">
|
||||
<div class="rel-grid">
|
||||
<div data-rel-strike class="relation-strike rel-name-col">
|
||||
{{- if $a -}}
|
||||
<a data-rel-link href="/person/{{ $a.Id }}" class="text-base text-gray-800 no-underline hover:text-slate-900 truncate">
|
||||
<a data-rel-link href="/person/{{ $a.Id }}" class="rel-link">
|
||||
<span data-rel-name>{{- $a.Name -}}</span>
|
||||
</a>
|
||||
{{- if $a.BiographicalData -}}
|
||||
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail>{{- $a.BiographicalData -}}</span></div>
|
||||
<div data-rel-detail-container class="rel-detail"><span data-rel-detail>{{- $a.BiographicalData -}}</span></div>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<div class="text-base text-gray-800">Unbekannte Person</div>
|
||||
@@ -451,15 +451,15 @@ type AlmanachResult struct {
|
||||
{{- end -}}
|
||||
</select>
|
||||
</div>
|
||||
<div data-rel-strike class="relation-strike flex items-center gap-2">
|
||||
<div data-rel-strike class="relation-strike rel-uncertain-container">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="entries_agents_uncertain[{{ $r.Id }}]"
|
||||
id="entries_agents_uncertain_{{ $r.Id }}"
|
||||
{{ if $r.Uncertain }}checked{{ end }} />
|
||||
<label for="entries_agents_uncertain_{{ $r.Id }}" class="text-sm text-gray-700">Unsicher</label>
|
||||
<label for="entries_agents_uncertain_{{ $r.Id }}" class="rel-uncertain-label">Unsicher</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="rel-button-container">
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm"
|
||||
@@ -475,13 +475,13 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<div class="text-sm text-gray-600">Keine Personen verknüpft.</div>
|
||||
<div class="rel-empty-text">Keine Personen verknüpft.</div>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<div data-role="relation-add-row" class="mt-2"></div>
|
||||
<div data-role="relation-add-panel" class="mt-2 hidden">
|
||||
<div class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
|
||||
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
|
||||
<div data-role="relation-add-row" class="mt-2 px-2"></div>
|
||||
<div data-role="relation-add-panel" class="mt-2 px-2 hidden">
|
||||
<div class="rel-row">
|
||||
<div class="rel-grid">
|
||||
<div class="min-w-0">
|
||||
<label for="agents-add-select" class="sr-only">Akteur suchen</label>
|
||||
<single-select-remote
|
||||
@@ -503,13 +503,14 @@ type AlmanachResult struct {
|
||||
{{- end -}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="rel-uncertain-container">
|
||||
<input data-role="relation-uncertain" type="checkbox" name="entries_agents_new_uncertain" id="entries_agents_new_uncertain" />
|
||||
<label for="entries_agents_new_uncertain" class="text-sm text-gray-700">Unsicher</label>
|
||||
<label for="entries_agents_new_uncertain" class="rel-uncertain-label">Unsicher</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="rel-button-container">
|
||||
<div class="flex items-center gap-3 text-lg">
|
||||
<button type="button" data-role="relation-add-apply" class="text-gray-700 hover:text-gray-900" id="agents-add-apply" aria-label="Akteur hinzufügen">
|
||||
<button type="button" data-role="relation-add-apply" class="text-gray-700
|
||||
hover:text-gray-900" id="agents-add-apply" aria-label="Person hinzufügen">
|
||||
<i class="ri-check-line"></i>
|
||||
</button>
|
||||
<button type="button" data-role="relation-add-close" class="text-gray-700 hover:text-gray-900" id="agents-add-close" aria-label="Ausblenden">
|
||||
@@ -518,29 +519,29 @@ type AlmanachResult struct {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden">Bitte Akteur auswählen.</div>
|
||||
<div data-role="relation-add-error" class="text-xs text-red-700 mt-2 hidden" data-error-empty="Bitte Akteur auswählen.">Bitte Akteur auswählen.</div>
|
||||
</div>
|
||||
</div>
|
||||
<template data-role="relation-new-template">
|
||||
<div data-rel-row class="border border-stone-200 rounded-xs bg-stone-50 px-3 py-2">
|
||||
<div class="grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center">
|
||||
<div data-rel-strike class="relation-strike flex flex-col min-w-0">
|
||||
<div data-rel-row class="rel-row">
|
||||
<div class="rel-grid">
|
||||
<div data-rel-strike class="relation-strike rel-name-col">
|
||||
<div class="text-base text-gray-800 truncate">
|
||||
<a data-rel-link class="no-underline hover:text-slate-900">
|
||||
<span data-rel-name></span>
|
||||
</a>
|
||||
<em data-rel-new class="text-sm text-gray-600 ml-1"></em>
|
||||
<em data-rel-new class="rel-new-badge"></em>
|
||||
</div>
|
||||
<div data-rel-detail-container class="text-xs text-gray-600 truncate"><span data-rel-detail></span></div>
|
||||
<div data-rel-detail-container class="rel-detail"><span data-rel-detail></span></div>
|
||||
</div>
|
||||
<div data-rel-strike class="relation-strike">
|
||||
<select data-rel-input="type" class="inputselect font-bold w-full"></select>
|
||||
</div>
|
||||
<div data-rel-strike class="relation-strike flex items-center gap-2">
|
||||
<div data-rel-strike class="relation-strike rel-uncertain-container">
|
||||
<input data-rel-input="uncertain" type="checkbox" />
|
||||
<label data-rel-uncertain-label class="text-sm text-gray-700">Unsicher</label>
|
||||
<label data-rel-uncertain-label class="rel-uncertain-label">Unsicher</label>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<div class="rel-button-container">
|
||||
<button type="button" class="text-sm text-red-700 hover:text-red-900" data-role="relation-new-delete">
|
||||
<i class="ri-delete-bin-line mr-1"></i> Entfernen
|
||||
</button>
|
||||
|
||||
@@ -215,6 +215,14 @@ export class AlmanachEditPage extends HTMLElement {
|
||||
});
|
||||
const newAgentRelations = this._collectNewRelations("entries_agents");
|
||||
|
||||
// Validate no duplicate series relations
|
||||
const allSeriesRelations = [...seriesRelations, ...newSeriesRelations];
|
||||
const seriesTargetIds = allSeriesRelations.map((r) => r.target_id);
|
||||
const duplicateSeries = seriesTargetIds.filter((id, index) => seriesTargetIds.indexOf(id) !== index);
|
||||
if (duplicateSeries.length > 0) {
|
||||
throw new Error("Doppelte Reihenverknüpfungen sind nicht erlaubt.");
|
||||
}
|
||||
|
||||
return {
|
||||
csrf_token: this._readValue(formData, "csrf_token"),
|
||||
last_edited: this._readValue(formData, "last_edited"),
|
||||
@@ -281,30 +289,37 @@ export class AlmanachEditPage extends HTMLElement {
|
||||
_collectRelations(formData, { prefix, targetField }) {
|
||||
const relations = [];
|
||||
const deleted = [];
|
||||
|
||||
// Iterate over ID fields instead of type fields (IDs are always submitted even when disabled)
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (!key.startsWith(`${prefix}_type[`)) {
|
||||
if (!key.startsWith(`${prefix}_id[`)) {
|
||||
continue;
|
||||
}
|
||||
const relationKey = key.slice(key.indexOf("[") + 1, -1);
|
||||
const targetKey = `${prefix}_${targetField}[${relationKey}]`;
|
||||
const relationIdKey = `${prefix}_id[${relationKey}]`;
|
||||
const typeKey = `${prefix}_type[${relationKey}]`;
|
||||
const deleteKey = `${prefix}_delete[${relationKey}]`;
|
||||
const uncertainKey = `${prefix}_uncertain[${relationKey}]`;
|
||||
|
||||
const relationId = (value || "").trim();
|
||||
const targetId = (formData.get(targetKey) || "").trim();
|
||||
if (!targetId) {
|
||||
|
||||
if (!targetId || !relationId) {
|
||||
continue;
|
||||
}
|
||||
const relationId = (formData.get(relationIdKey) || relationKey).trim();
|
||||
|
||||
// Check if marked for deletion
|
||||
if (formData.has(deleteKey)) {
|
||||
if (relationId) {
|
||||
deleted.push(relationId);
|
||||
}
|
||||
deleted.push(relationId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not deleted, add to relations
|
||||
const type = (formData.get(typeKey) || "").trim();
|
||||
relations.push({
|
||||
id: relationId,
|
||||
target_id: targetId,
|
||||
type: (value || "").trim(),
|
||||
type: type,
|
||||
uncertain: formData.has(uncertainKey),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,6 +46,57 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Relations editor abstracted classes */
|
||||
.rel-section-container {
|
||||
@apply flex flex-col gap-2 mt-1 px-2;
|
||||
}
|
||||
|
||||
.rel-row {
|
||||
@apply border border-stone-200 rounded-xs bg-stone-50 px-3 py-2;
|
||||
}
|
||||
|
||||
.rel-grid {
|
||||
@apply grid grid-cols-[1fr_14rem_5.5rem_7rem] gap-3 items-center;
|
||||
}
|
||||
|
||||
.rel-name-col {
|
||||
@apply flex flex-col min-w-0;
|
||||
}
|
||||
|
||||
.rel-link {
|
||||
@apply text-base text-gray-800 no-underline hover:text-slate-900 truncate;
|
||||
}
|
||||
|
||||
.rel-detail {
|
||||
@apply text-xs text-gray-600 truncate;
|
||||
}
|
||||
|
||||
.rel-new-badge {
|
||||
@apply text-sm text-gray-600 ml-1;
|
||||
}
|
||||
|
||||
.rel-uncertain-container {
|
||||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.rel-uncertain-label {
|
||||
@apply text-sm text-gray-700;
|
||||
}
|
||||
|
||||
.rel-button-container {
|
||||
@apply flex justify-end;
|
||||
}
|
||||
|
||||
.rel-empty-text {
|
||||
@apply px-1.5 italic text-gray-600;
|
||||
}
|
||||
|
||||
/* Disabled form controls in deleted relations */
|
||||
[data-rel-row].bg-red-50 select:disabled,
|
||||
[data-rel-row].bg-red-50 input[type="checkbox"]:disabled:not([data-delete-toggle]) {
|
||||
@apply opacity-50 cursor-not-allowed;
|
||||
}
|
||||
|
||||
/* Diagonal hatching pattern for deleted relations, items, and pills */
|
||||
[data-rel-row],
|
||||
.items-row,
|
||||
@@ -62,13 +113,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent,
|
||||
transparent 6px,
|
||||
rgba(220, 38, 38, 0.2) 6px,
|
||||
rgba(220, 38, 38, 0.2) 10px
|
||||
);
|
||||
background: repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(220, 38, 38, 0.2) 6px, rgba(220, 38, 38, 0.2) 10px);
|
||||
border-radius: 0.25rem;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
@@ -94,13 +139,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: repeating-linear-gradient(
|
||||
-45deg,
|
||||
#f5f5f4,
|
||||
#f5f5f4 6px,
|
||||
rgba(220, 38, 38, 0.25) 6px,
|
||||
rgba(220, 38, 38, 0.25) 10px
|
||||
);
|
||||
background: repeating-linear-gradient(-45deg, #f5f5f4, #f5f5f4 6px, rgba(220, 38, 38, 0.25) 6px, rgba(220, 38, 38, 0.25) 10px);
|
||||
border-radius: 0.25rem;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
@@ -345,7 +384,7 @@
|
||||
}
|
||||
|
||||
.mss-toggle-button {
|
||||
@apply text-gray-700 hover:text-gray-900 font-semibold text-lg px-2 py-0.5 rounded-xs border border-transparent bg-transparent whitespace-nowrap leading-none;
|
||||
@apply text-gray-700 hover:text-gray-900 font-semibold text-lg py-0.5 rounded-xs border border-transparent bg-transparent whitespace-nowrap leading-none;
|
||||
}
|
||||
|
||||
.mss-options-list {
|
||||
|
||||
@@ -196,16 +196,42 @@ export class ItemsEditor extends HTMLElement {
|
||||
row.setAttribute(REMOVED_ROW_STATE, removed ? "true" : "false");
|
||||
row.classList.toggle("bg-red-50", removed);
|
||||
|
||||
const editButton = row.querySelector(".items-edit-button");
|
||||
if (editButton) {
|
||||
if (removed) {
|
||||
editButton.classList.add("hidden");
|
||||
} else {
|
||||
editButton.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
row.querySelectorAll("[data-delete-label]").forEach((label) => {
|
||||
const nextLabel = removed
|
||||
? label.getAttribute("data-delete-active") || "Wird entfernt"
|
||||
: label.getAttribute("data-delete-default") || "Entfernen";
|
||||
const button = label.closest(`.${REMOVE_BUTTON_CLASS}`);
|
||||
const isHovered = button && button.matches(":hover");
|
||||
|
||||
let nextLabel;
|
||||
if (removed && isHovered) {
|
||||
nextLabel = label.getAttribute("data-delete-hover") || "Rückgängig";
|
||||
} else if (removed) {
|
||||
nextLabel = label.getAttribute("data-delete-active") || "Wird entfernt";
|
||||
} else {
|
||||
nextLabel = label.getAttribute("data-delete-default") || "Entfernen";
|
||||
}
|
||||
label.textContent = nextLabel;
|
||||
});
|
||||
row.querySelectorAll(`.${REMOVE_BUTTON_CLASS} i`).forEach((icon) => {
|
||||
const button = icon.closest(`.${REMOVE_BUTTON_CLASS}`);
|
||||
const isHovered = button && button.matches(":hover");
|
||||
|
||||
if (removed) {
|
||||
icon.classList.add("hidden");
|
||||
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
|
||||
if (isHovered) {
|
||||
icon.classList.remove("hidden");
|
||||
icon.classList.add("ri-arrow-go-back-line");
|
||||
icon.classList.remove("ri-delete-bin-line");
|
||||
} else {
|
||||
icon.classList.add("hidden");
|
||||
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
|
||||
}
|
||||
} else {
|
||||
icon.classList.remove("hidden");
|
||||
icon.classList.add("ri-delete-bin-line");
|
||||
|
||||
@@ -24,10 +24,61 @@ export class RelationsEditor extends HTMLElement {
|
||||
this._linkBase = this.getAttribute("data-link-base") || "";
|
||||
this._newLabel = this.getAttribute("data-new-label") || "(Neu)";
|
||||
this._addToggleId = this.getAttribute("data-add-toggle-id") || "";
|
||||
this._emptyText = this.querySelector(".rel-empty-text");
|
||||
this._setupAddPanel();
|
||||
this._setupDeleteToggles();
|
||||
}
|
||||
|
||||
_getExistingIds() {
|
||||
const ids = new Set();
|
||||
// For series: entries_series_series[...]
|
||||
// For agents: entries_agents_agent[...]
|
||||
const targetField = this._prefix === "entries_series" ? "series" : "agent";
|
||||
|
||||
// Get existing relation target IDs (from server-rendered rows)
|
||||
this.querySelectorAll(`input[name^="${this._prefix}_${targetField}["]`).forEach((input) => {
|
||||
const value = input.value.trim();
|
||||
if (value) {
|
||||
ids.add(value);
|
||||
}
|
||||
});
|
||||
|
||||
// Get new relation target IDs (from dynamically added rows, but not from the add panel)
|
||||
if (this._addRow) {
|
||||
this._addRow.querySelectorAll(`input[name="${this._prefix}_new_id"]`).forEach((input) => {
|
||||
const value = input.value.trim();
|
||||
if (value) {
|
||||
ids.add(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
_updateEmptyTextVisibility() {
|
||||
if (!this._emptyText) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if there are any existing relations (server-rendered rows)
|
||||
const targetField = this._prefix === "entries_series" ? "series" : "agent";
|
||||
const hasExisting = this.querySelectorAll(`input[name^="${this._prefix}_${targetField}["]`).length > 0;
|
||||
|
||||
// Check if there are any new relations in the add row
|
||||
const hasNew = this._addRow && this._addRow.querySelectorAll(`input[name="${this._prefix}_new_id"]`).length > 0;
|
||||
|
||||
// Check if add panel is visible
|
||||
const isPanelVisible = this._addPanel && !this._addPanel.classList.contains("hidden");
|
||||
|
||||
// Hide empty text if: panel is visible OR there are any relations (existing or new)
|
||||
if (isPanelVisible || hasExisting || hasNew) {
|
||||
this._emptyText.classList.add("hidden");
|
||||
} else {
|
||||
this._emptyText.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
_setupAddPanel() {
|
||||
this._addToggle = this.querySelector(ROLE_ADD_TOGGLE);
|
||||
if (this._addToggleId) {
|
||||
@@ -51,15 +102,24 @@ export class RelationsEditor extends HTMLElement {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up filtering for single-select-remote (only for series, not agents)
|
||||
if (this._addSelect && this._prefix === "entries_series") {
|
||||
this._addSelect.addEventListener("ssrbeforefetch", () => {
|
||||
this._addSelect._excludeIds = Array.from(this._getExistingIds());
|
||||
});
|
||||
}
|
||||
|
||||
if (this._addToggle) {
|
||||
this._addToggle.addEventListener("click", () => {
|
||||
this._addPanel.classList.toggle("hidden");
|
||||
this._updateEmptyTextVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
if (this._addClose) {
|
||||
this._addClose.addEventListener("click", () => {
|
||||
this._addPanel.classList.add("hidden");
|
||||
this._updateEmptyTextVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,16 +138,28 @@ export class RelationsEditor extends HTMLElement {
|
||||
const hasSelection = idInput && idInput.value.trim().length > 0;
|
||||
if (!hasSelection) {
|
||||
if (this._addError) {
|
||||
this._addError.textContent = this._addError.getAttribute("data-error-empty") || "Bitte Reihe auswählen.";
|
||||
this._addError.classList.remove("hidden");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this._addError) {
|
||||
this._addError.classList.add("hidden");
|
||||
}
|
||||
if (!this._pendingItem) {
|
||||
return;
|
||||
}
|
||||
// Check for duplicates (only for series, not agents)
|
||||
if (this._prefix === "entries_series") {
|
||||
const existingIds = this._getExistingIds();
|
||||
if (existingIds.has(this._pendingItem.id)) {
|
||||
if (this._addError) {
|
||||
this._addError.textContent = this._addError.getAttribute("data-error-duplicate") || "Diese Verknüpfung existiert bereits.";
|
||||
this._addError.classList.remove("hidden");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this._addError) {
|
||||
this._addError.classList.add("hidden");
|
||||
}
|
||||
this._insertNewRow();
|
||||
});
|
||||
}
|
||||
@@ -187,6 +259,7 @@ export class RelationsEditor extends HTMLElement {
|
||||
if (this._addPanel) {
|
||||
this._addPanel.classList.add("hidden");
|
||||
}
|
||||
this._updateEmptyTextVisibility();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -197,6 +270,7 @@ export class RelationsEditor extends HTMLElement {
|
||||
if (this._addPanel) {
|
||||
this._addPanel.classList.add("hidden");
|
||||
}
|
||||
this._updateEmptyTextVisibility();
|
||||
}
|
||||
|
||||
_setupDeleteToggles() {
|
||||
@@ -212,20 +286,42 @@ export class RelationsEditor extends HTMLElement {
|
||||
const row = button.closest(ROLE_REL_ROW);
|
||||
if (row) {
|
||||
row.classList.toggle("bg-red-50", checkbox.checked);
|
||||
|
||||
// Disable/enable form controls (but not the delete checkbox itself)
|
||||
row.querySelectorAll("select, input[type='checkbox']").forEach((control) => {
|
||||
// Skip the delete checkbox itself
|
||||
if (control === checkbox) {
|
||||
return;
|
||||
}
|
||||
control.disabled = checkbox.checked;
|
||||
});
|
||||
}
|
||||
|
||||
const isHovered = button.matches(":hover");
|
||||
const label = button.querySelector("[data-delete-label]");
|
||||
if (label) {
|
||||
label.textContent = checkbox.checked
|
||||
? label.getAttribute("data-delete-active") || "Wird entfernt"
|
||||
: label.getAttribute("data-delete-default") || "Entfernen";
|
||||
let nextLabel;
|
||||
if (checkbox.checked && isHovered) {
|
||||
nextLabel = label.getAttribute("data-delete-hover") || "Rückgängig";
|
||||
} else if (checkbox.checked) {
|
||||
nextLabel = label.getAttribute("data-delete-active") || "Wird entfernt";
|
||||
} else {
|
||||
nextLabel = label.getAttribute("data-delete-default") || "Entfernen";
|
||||
}
|
||||
label.textContent = nextLabel;
|
||||
}
|
||||
|
||||
const icon = button.querySelector("i");
|
||||
if (icon) {
|
||||
if (checkbox.checked) {
|
||||
icon.classList.add("hidden");
|
||||
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
|
||||
if (isHovered) {
|
||||
icon.classList.remove("hidden");
|
||||
icon.classList.add("ri-arrow-go-back-line");
|
||||
icon.classList.remove("ri-delete-bin-line");
|
||||
} else {
|
||||
icon.classList.add("hidden");
|
||||
icon.classList.remove("ri-delete-bin-line", "ri-arrow-go-back-line");
|
||||
}
|
||||
} else {
|
||||
icon.classList.remove("hidden");
|
||||
icon.classList.add("ri-delete-bin-line");
|
||||
|
||||
@@ -181,6 +181,10 @@ export class SingleSelectRemote extends HTMLElement {
|
||||
if (this._fetchController) {
|
||||
this._fetchController.abort();
|
||||
}
|
||||
|
||||
// Dispatch event before fetch to allow filtering
|
||||
this.dispatchEvent(new CustomEvent("ssrbeforefetch", { bubbles: true }));
|
||||
|
||||
this._fetchController = new AbortController();
|
||||
const url = new URL(this._endpoint, window.location.origin);
|
||||
url.searchParams.set("q", query);
|
||||
@@ -194,7 +198,15 @@ export class SingleSelectRemote extends HTMLElement {
|
||||
}
|
||||
const data = await resp.json();
|
||||
const items = Array.isArray(data?.[this._resultKey]) ? data[this._resultKey] : [];
|
||||
this._options = items.filter((item) => item && item.id && item.name);
|
||||
let filteredItems = items.filter((item) => item && item.id && item.name);
|
||||
|
||||
// Filter out excluded IDs if provided
|
||||
if (this._excludeIds && Array.isArray(this._excludeIds)) {
|
||||
const excludeSet = new Set(this._excludeIds);
|
||||
filteredItems = filteredItems.filter((item) => !excludeSet.has(item.id));
|
||||
}
|
||||
|
||||
this._options = filteredItems;
|
||||
this._highlightedIndex = this._options.length > 0 ? 0 : -1;
|
||||
this._renderOptions();
|
||||
if (this._options.length > 0) {
|
||||
|
||||
Reference in New Issue
Block a user