Some frontend validation logic

This commit is contained in:
Simon Martens
2026-01-22 16:46:31 +01:00
parent 1749d0e224
commit 17ab271de3
13 changed files with 2787 additions and 1532 deletions

View File

@@ -17,6 +17,22 @@ type AlmanachResult struct {
}
-->
{{- $preferredSeriesTitle := "" -}}
{{- $preferredSeriesId := "" -}}
{{- $preferredSeriesMusenalmID := "" -}}
{{- $preferredRelationId := "" -}}
{{- if and $model.result $model.result.Series -}}
{{- range $i, $s := $model.result.Series -}}
{{- $rel := index $model.result.EntriesSeries $s.Id -}}
{{- if and $rel (eq $rel.Type "Bevorzugter Reihentitel") -}}
{{- $preferredSeriesTitle = $s.Title -}}
{{- $preferredSeriesId = $s.Id -}}
{{- $preferredSeriesMusenalmID = $s.MusenalmID -}}
{{- $preferredRelationId = $rel.Id -}}
{{- end -}}
{{- end -}}
{{- end -}}
<almanach-edit-page>
<duplicate-warning-checker></duplicate-warning-checker>
@@ -187,18 +203,62 @@ type AlmanachResult struct {
<div class="inputwrapper">
<div class="inputlabelrow">
<div class="flex items-center gap-1">
<label for="preferred_title" class="inputlabel">Kurztitel</label>
<label for="preferred-title-field-input" class="inputlabel">Kurztitel</label>
<tool-tip position="top" class="!inline">
<div class="data-tip">{{ help "entries" "preferred_title" }}</div>
<i class="ri-question-line"></i>
</tool-tip>
</div>
</div>
<textarea name="preferred_title" id="preferred_title" class="inputinput no-enter" placeholder="" required autocomplete="off" rows="1" data-duplicate-check data-duplicate-endpoint="/api/entries/search" data-duplicate-result-key="entries" data-duplicate-current-id="{{ if not $model.is_new }}{{ $model.result.Entry.Id }}{{ end }}">{{- $model.result.Entry.PreferredTitle -}}</textarea>
<div class="duplicate-warning hidden" data-duplicate-warning-for="preferred_title">
<i class="ri-information-line"></i>
<span data-duplicate-count></span>
<lookup-field
id="preferred-title-field"
name="preferred_title"
data-multiline="true"
data-no-enter="true"
data-required="true"
data-autocomplete="false"
data-valid-fn="lookupRequiredText"
data-dup-endpoint="/api/entries/search"
data-dup-result-key="entries"
data-dup-current-id="{{ if not $model.is_new }}{{ $model.result.Entry.Id }}{{ end }}"
value="{{- $model.result.Entry.PreferredTitle -}}">
</lookup-field>
</div>
<div class="inputwrapper">
<div class="inputlabelrow">
<div class="flex items-center gap-1">
<label for="preferred-series-field-input" class="inputlabel">Reihentitel</label>
<tool-tip position="top" class="!inline">
<div class="data-tip">{{ helpOr "entries" "series" "Bevorzugter Reihentitel für diesen Almanach." }}</div>
<i class="ri-question-line"></i>
</tool-tip>
</div>
<div class="flex items-center gap-3 ml-auto pr-2">
<a href="/reihen/new/" class="text-sm font-bold text-gray-700 hover:text-slate-950 no-underline" target="_blank" rel="noreferrer">
<i class="ri-add-line"></i> Neue Reihe anlegen
</a>
</div>
</div>
<lookup-field
id="preferred-series-field"
name="preferred_series_id"
data-value-name="preferred_series_id"
data-text-name=""
data-valid-fn="lookupRequiredId"
data-endpoint="/api/series/search"
data-result-key="series"
data-minchars="1"
data-limit="15"
data-link-fn="lookupSeriesLink"
data-value-fn="lookupSeriesValue"
data-required="true"
data-initial-id="{{ $preferredSeriesId }}"
data-initial-name="{{ $preferredSeriesTitle }}"
data-initial-musenalm-id="{{ $preferredSeriesMusenalmID }}"
data-preferred-relation-id="{{ $preferredRelationId }}"
data-preferred-series-id="{{ $preferredSeriesId }}">
</lookup-field>
</div>
<div class="mt-3">
@@ -438,7 +498,7 @@ type AlmanachResult struct {
{{- if $model.result.Series -}}
{{- range $i, $s := $model.result.Series -}}
{{- $rel := index $model.result.EntriesSeries $s.Id -}}
{{- if $rel -}}
{{- if and $rel (ne $rel.Type "Bevorzugter Reihentitel") -}}
<div data-rel-row class="entries-series-row rel-row">
<div class="rel-grid">
<div data-rel-strike class="relation-strike rel-name-col">
@@ -452,7 +512,9 @@ type AlmanachResult struct {
<div data-rel-strike class="relation-strike">
<select name="entries_series_type[{{ $rel.Id }}]" id="entries_series_type_{{ $rel.Id }}" autocomplete="off" class="inputselect font-bold w-full">
{{- range $t := $model.series_relations -}}
<option value="{{- $t -}}" {{ if eq $rel.Type $t }}selected{{ end }}>{{- $t -}}</option>
{{- if ne $t "Bevorzugter Reihentitel" -}}
<option value="{{- $t -}}" {{ if eq $rel.Type $t }}selected{{ end }}>{{- $t -}}</option>
{{- end -}}
{{- end -}}
</select>
</div>
@@ -505,7 +567,9 @@ type AlmanachResult struct {
<label for="entries_series_new_type" class="sr-only">Beziehung</label>
<select data-role="relation-type-select" name="entries_series_new_type" id="entries_series_new_type" autocomplete="off" class="inputselect font-bold w-full">
{{- range $t := $model.series_relations -}}
<option value="{{- $t -}}">{{- $t -}}</option>
{{- if ne $t "Bevorzugter Reihentitel" -}}
<option value="{{- $t -}}">{{- $t -}}</option>
{{- end -}}
{{- end -}}
</select>
</div>
@@ -1142,9 +1206,9 @@ type AlmanachResult struct {
</div>
<!-- End Right Column -->
</div>
<div class="w-full flex items-end justify-between gap-4 mt-6 flex-wrap">
<p id="almanach-save-feedback" class="text-sm text-gray-600" aria-live="polite"></p>
<div class="flex items-center gap-3 self-end flex-wrap">
<div class="w-full flex flex-col gap-3 mt-6 items-end">
<p id="almanach-save-feedback" class="save-feedback hidden text-right" aria-live="polite"></p>
<div class="flex items-center gap-3 flex-wrap justify-end">
<a href="{{ if $model.is_new }}/suche/baende{{ else }}/almanach/{{ $model.result.Entry.MusenalmID }}{{ end }}" class="resetbutton w-40 flex items-center gap-2 justify-center">
<i class="ri-close-line"></i>
<span>Abbrechen</span>

View File

@@ -12,31 +12,28 @@
<div data-role="content-item" class="relative odd:bg-stone-100" data-content-id="{{ $contentID }}">
<div data-role="content-view">
<div class="bg-transparent overflow-visible">
<div class="flex items-center justify-between gap-3 bg-transparent px-2 py-0 flex-nowrap whitespace-nowrap" data-content-header="true">
<div class="flex items-center gap-2 text-sm font-bold text-gray-800 flex-1 min-w-0 flex-nowrap whitespace-nowrap">
<div class="flex items-center justify-between gap-2 bg-transparent px-2 py-0.5 flex-nowrap whitespace-nowrap" data-content-header="true">
<div class="flex items-center gap-1.5 text-sm font-bold text-gray-800 flex-1 min-w-0 flex-nowrap whitespace-nowrap">
<div class="flex items-center gap-1">
<button
type="button"
class="text-slate-600 rounded-sm px-2 py-1 text-sm cursor-grab"
class="text-slate-600 rounded-sm px-1 py-0.5 text-xs cursor-grab"
data-role="content-drag-handle"
draggable="true"
aria-label="Beitrag verschieben">
<i class="ri-draggable"></i>
<i class="ri-draggable text-xs"></i>
</button>
<button type="button" class="text-slate-600 rounded-sm px-2 py-1 text-sm transition-colors hover:bg-stone-300" data-role="content-move-up" aria-label="Beitrag nach oben">
<i class="ri-arrow-up-line"></i>
<button type="button" class="text-slate-600 rounded-sm px-1 py-0.5 text-xs transition-colors hover:bg-stone-300" data-role="content-move-up" aria-label="Beitrag nach oben">
<i class="ri-arrow-up-line text-xs"></i>
</button>
<button type="button" class="text-slate-600 rounded-sm px-2 py-1 text-sm transition-colors hover:bg-stone-300" data-role="content-move-down" aria-label="Beitrag nach unten">
<i class="ri-arrow-down-line"></i>
<button type="button" class="text-slate-600 rounded-sm px-1 py-0.5 text-xs transition-colors hover:bg-stone-300" data-role="content-move-down" aria-label="Beitrag nach unten">
<i class="ri-arrow-down-line text-xs"></i>
</button>
</div>
{{- if $content.Extent -}}
<span class="content-search-text bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-sm font-semibold shadow-sm shrink-0" data-role="content-page-pill">S. {{- $content.Extent -}}</span>
{{- end -}}
{{- if $content.MusenalmType -}}
<span class="flex flex-nowrap gap-1 text-gray-700 font-normal overflow-hidden">
{{- range $i, $t := $content.MusenalmType -}}
<span class="content-search-text bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-base font-semibold shadow-sm" data-role="content-type-pill">{{- $t -}}</span>
<span class="content-search-text bg-slate-200 text-slate-900 px-1.5 py-0.5 rounded text-xs font-semibold shadow-sm" data-role="content-type-pill">{{- $t -}}</span>
{{- end -}}
</span>
{{- end -}}
@@ -52,28 +49,42 @@
</a>
</div>
</div>
<div class="flex items-center gap-2 flex-nowrap whitespace-nowrap shrink-0">
<span
id="content-{{ $content.Id }}-images-count"
class="inline-flex items-center gap-1 text-sm font-semibold text-slate-600 mr-2.5 {{ if eq (len $content.Scans) 0 }}hidden{{ end }}">
<i class="ri-image-line"></i>
<span>{{ len $content.Scans }}</span>
</span>
<span class="status-badge text-xs shadow-sm" data-status="{{ $content.EditState }}">
<i class="status-icon {{- if eq $content.EditState "Edited" }} ri-checkbox-circle-line{{- else if eq $content.EditState "Seen" }} ri-information-line{{- else if eq $content.EditState "Review" }} ri-search-line{{- else if eq $content.EditState "ToDo" }} ri-list-check{{- else }} ri-forbid-2-line{{- end }}"></i>
<div class="flex items-center gap-1.5 flex-nowrap whitespace-nowrap shrink-0">
{{- $scans := $content.ImagePaths -}}
{{- if $scans -}}
<span class="inline-flex items-center gap-1 mr-2.5">
{{- range $i, $scan := $scans -}}
{{- if lt $i 3 -}}
<popup-image data-image-url="{{- $scan -}}" class="inline-flex items-center">
<img
src="{{- $scan -}}"
alt="Scan Vorschau"
class="h-5 w-5 rounded-full object-cover border border-slate-500 shadow-sm" />
</popup-image>
{{- end -}}
{{- end -}}
</span>
{{- end -}}
{{- if or $content.Extent (not $scans) -}}
<span class="content-search-text {{ if $content.Extent }}bg-blue-900 text-white shadow-sm{{ else }}bg-transparent text-transparent shadow-none{{ end }} px-1.5 py-0.5 rounded-xs text-xs font-semibold shrink-0 inline-flex items-center justify-end min-w-[5ch]" data-role="content-page-pill">
{{- if $content.Extent -}}S.&thinsp;{{- $content.Extent -}}{{- else -}}&nbsp;&nbsp;&nbsp;{{- end -}}
</span>
{{- end -}}
<span class="status-badge text-xs shadow-sm ml-2" data-status="{{ $content.EditState }}">
<i class="status-icon text-xs {{- if eq $content.EditState "Edited" }} ri-checkbox-circle-line{{- else if eq $content.EditState "Seen" }} ri-information-line{{- else if eq $content.EditState "Review" }} ri-search-line{{- else if eq $content.EditState "ToDo" }} ri-list-check{{- else }} ri-forbid-2-line{{- end }}"></i>
</span>
<a
href="/almanach/{{ $entry.MusenalmID }}/contents/{{ $content.MusenalmID }}/edit"
class="resetbutton w-9 h-9 flex items-center justify-center rounded-sm cursor-pointer hover:bg-stone-300"
class="resetbutton w-8 h-8 flex items-center justify-center rounded-sm cursor-pointer hover:bg-stone-300"
aria-label="Beitrag bearbeiten">
<i class="ri-edit-2-line"></i>
<i class="ri-edit-2-line text-sm"></i>
</a>
<button
type="button"
class="resetbutton w-9 h-9 flex items-center justify-center text-red-700 hover:text-red-900 hover:bg-red-100 rounded-sm"
class="resetbutton w-8 h-8 flex items-center justify-center text-red-700 hover:text-red-900 hover:bg-red-100 rounded-sm"
data-role="content-delete-view"
aria-label="Beitrag löschen">
<i class="ri-delete-bin-line"></i>
<i class="ri-delete-bin-line text-sm"></i>
</button>
</div>
</div>

View File

@@ -130,11 +130,19 @@
</tool-tip>
</div>
</div>
<textarea name="name" id="name" class="inputinput no-enter" autocomplete="off" rows="1" data-duplicate-check data-duplicate-endpoint="/api/places/search" data-duplicate-result-key="places" data-duplicate-current-id="{{ if not $model.is_new }}{{ $place.Id }}{{ end }}">{{- $place.Name -}}</textarea>
<div class="duplicate-warning hidden" data-duplicate-warning-for="name">
<i class="ri-information-line"></i>
<span data-duplicate-count></span>
</div>
<lookup-field
id="place-name-field"
name="name"
data-multiline="true"
data-no-enter="true"
data-required="true"
data-autocomplete="false"
data-valid-fn="lookupRequiredText"
data-dup-endpoint="/api/places/search"
data-dup-result-key="places"
data-dup-current-id="{{ if not $model.is_new }}{{ $place.Id }}{{ end }}"
value="{{- $place.Name -}}">
</lookup-field>
</div>
<div class="inputwrapper">
<div class="inputlabelrow">

View File

@@ -132,11 +132,19 @@
</tool-tip>
</div>
</div>
<textarea name="name" id="name" class="inputinput no-enter" autocomplete="off" rows="1" data-duplicate-check data-duplicate-endpoint="/api/agents/search" data-duplicate-result-key="agents" data-duplicate-current-id="{{ if not $model.is_new }}{{ $agent.Id }}{{ end }}">{{- $agent.Name -}}</textarea>
<div class="duplicate-warning hidden" data-duplicate-warning-for="name">
<i class="ri-information-line"></i>
<span data-duplicate-count></span>
</div>
<lookup-field
id="person-name-field"
name="name"
data-multiline="true"
data-no-enter="true"
data-required="true"
data-autocomplete="false"
data-valid-fn="lookupRequiredText"
data-dup-endpoint="/api/agents/search"
data-dup-result-key="agents"
data-dup-current-id="{{ if not $model.is_new }}{{ $agent.Id }}{{ end }}"
value="{{- $agent.Name -}}">
</lookup-field>
</div>
<div class="inputwrapper">
<div class="inputlabelrow">

View File

@@ -132,11 +132,19 @@
</tool-tip>
</div>
</div>
<textarea name="title" id="title" class="inputinput no-enter" autocomplete="off" rows="1" data-duplicate-check data-duplicate-endpoint="/api/series/search" data-duplicate-result-key="series" data-duplicate-current-id="{{ if not $model.is_new }}{{ $series.Id }}{{ end }}">{{- $series.Title -}}</textarea>
<div class="duplicate-warning hidden" data-duplicate-warning-for="title">
<i class="ri-information-line"></i>
<span data-duplicate-count></span>
</div>
<lookup-field
id="series-title-field"
name="title"
data-multiline="true"
data-no-enter="true"
data-required="true"
data-autocomplete="false"
data-valid-fn="lookupRequiredText"
data-dup-endpoint="/api/series/search"
data-dup-result-key="series"
data-dup-current-id="{{ if not $model.is_new }}{{ $series.Id }}{{ end }}"
value="{{- $series.Title -}}">
</lookup-field>
</div>
<div class="flex flex-row gap-3">
<div class="inputwrapper grow">