mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 02:25:30 +00:00
+baende list styling
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/app"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/dbmodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/helpers/datatypes"
|
||||
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/middleware"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/pagemodels"
|
||||
"github.com/Theodor-Springmann-Stiftung/musenalm/templating"
|
||||
@@ -219,7 +219,8 @@ func searchBaendeEntries(app core.App, search string) ([]*dbmodels.Entry, error)
|
||||
|
||||
entries, err := searchBaendeEntriesFTS(app, query)
|
||||
if err != nil {
|
||||
return searchBaendeEntriesLike(app, query)
|
||||
app.Logger().Error("FTS search failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
@@ -282,115 +283,7 @@ func searchBaendeEntriesFTS(app core.App, query string) ([]*dbmodels.Entry, erro
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func searchBaendeEntriesLike(app core.App, query string) ([]*dbmodels.Entry, error) {
|
||||
trimmed := strings.TrimSpace(query)
|
||||
if trimmed == "" {
|
||||
return []*dbmodels.Entry{}, nil
|
||||
}
|
||||
terms := []string{trimmed}
|
||||
normalized := datatypes.NormalizeString(trimmed)
|
||||
normalized = strings.Join(strings.Fields(normalized), " ")
|
||||
if normalized != "" && normalized != trimmed {
|
||||
terms = append(terms, normalized)
|
||||
}
|
||||
collapsed := strings.ReplaceAll(trimmed, "/", "")
|
||||
if collapsed != "" && collapsed != trimmed && collapsed != normalized {
|
||||
terms = append(terms, collapsed)
|
||||
}
|
||||
|
||||
entryFields := []string{
|
||||
dbmodels.PREFERRED_TITLE_FIELD,
|
||||
dbmodels.VARIANT_TITLE_FIELD,
|
||||
dbmodels.PARALLEL_TITLE_FIELD,
|
||||
dbmodels.TITLE_STMT_FIELD,
|
||||
dbmodels.SUBTITLE_STMT_FIELD,
|
||||
dbmodels.INCIPIT_STMT_FIELD,
|
||||
dbmodels.RESPONSIBILITY_STMT_FIELD,
|
||||
dbmodels.PUBLICATION_STMT_FIELD,
|
||||
dbmodels.PLACE_STMT_FIELD,
|
||||
dbmodels.EDITION_FIELD,
|
||||
dbmodels.YEAR_FIELD,
|
||||
dbmodels.EXTENT_FIELD,
|
||||
dbmodels.DIMENSIONS_FIELD,
|
||||
dbmodels.REFERENCES_FIELD,
|
||||
dbmodels.ANNOTATION_FIELD,
|
||||
dbmodels.COMMENT_FIELD,
|
||||
dbmodels.MUSENALMID_FIELD,
|
||||
}
|
||||
|
||||
entryConditions := []dbx.Expression{}
|
||||
for _, term := range terms {
|
||||
for _, field := range entryFields {
|
||||
entryConditions = append(entryConditions, dbx.Like(field, term).Match(true, true))
|
||||
}
|
||||
}
|
||||
|
||||
entries := []*dbmodels.Entry{}
|
||||
if len(entryConditions) > 0 {
|
||||
if err := app.RecordQuery(dbmodels.ENTRIES_TABLE).
|
||||
Where(dbx.Or(entryConditions...)).
|
||||
All(&entries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
entryIDs := map[string]struct{}{}
|
||||
for _, entry := range entries {
|
||||
entryIDs[entry.Id] = struct{}{}
|
||||
}
|
||||
|
||||
itemFields := []string{
|
||||
dbmodels.ITEMS_IDENTIFIER_FIELD,
|
||||
dbmodels.ITEMS_MEDIA_FIELD,
|
||||
dbmodels.ITEMS_LOCATION_FIELD,
|
||||
dbmodels.ITEMS_OWNER_FIELD,
|
||||
dbmodels.ITEMS_CONDITION_FIELD,
|
||||
dbmodels.URI_FIELD,
|
||||
dbmodels.ANNOTATION_FIELD,
|
||||
dbmodels.COMMENT_FIELD,
|
||||
}
|
||||
itemConditions := []dbx.Expression{}
|
||||
for _, term := range terms {
|
||||
for _, field := range itemFields {
|
||||
itemConditions = append(itemConditions, dbx.Like(field, term).Match(true, true))
|
||||
}
|
||||
}
|
||||
|
||||
if len(itemConditions) > 0 {
|
||||
items := []*dbmodels.Item{}
|
||||
if err := app.RecordQuery(dbmodels.ITEMS_TABLE).
|
||||
Where(dbx.Or(itemConditions...)).
|
||||
All(&items); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemEntryIDs := []any{}
|
||||
for _, item := range items {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
if entryID := item.Entry(); entryID != "" {
|
||||
if _, exists := entryIDs[entryID]; !exists {
|
||||
entryIDs[entryID] = struct{}{}
|
||||
itemEntryIDs = append(itemEntryIDs, entryID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(itemEntryIDs) > 0 {
|
||||
itemEntries, err := dbmodels.Entries_IDs(app, itemEntryIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, itemEntries...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
dbmodels.Sort_Entries_Title_Year(entries)
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func searchBaendeEntriesQuick(app core.App, query string) ([]*dbmodels.Entry, error) {
|
||||
trimmed := strings.TrimSpace(query)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,14 +1,34 @@
|
||||
{{ $model := . }}
|
||||
|
||||
<div x-data="{ search : '{{ $model.search }}' }" class="container-normal font-serif mt-10">
|
||||
<div id="baendeheading" class="headingcontainer pb-6">
|
||||
<div class="flex flex-wrap items-end justify-between gap-4">
|
||||
<div x-data="{ search : '{{ $model.search }}' }" class="container-normal font-sans mt-10">
|
||||
<div id="pageheading" class="headingcontainer">
|
||||
<h1 class="heading">Bände A–Z</h1>
|
||||
<div class="min-w-[22.5rem] max-w-96 flex flex-row bg-stone-50 relative font-sans text-lg">
|
||||
<div class="pb-0">
|
||||
|
||||
<div class="mt-2">
|
||||
<div class="flex flex-wrap flex-row border-b px-3 border-zinc-300 items-end justify-between min-h-14 gap-y-4">
|
||||
<!-- Left side group: Count and Alphabet -->
|
||||
<div class="flex items-end gap-x-6">
|
||||
<div id="alphabet" class="alphabet flex flex-row items-end text-xl">
|
||||
{{- range $_, $ch := $model.letters -}}
|
||||
<a
|
||||
href="/baende/?letter={{ $ch }}"
|
||||
:class="search ? 'inactive pointer-events-none' : ''"
|
||||
class="odd:bg-stone-100 even:bg-zinc-100 mr-1 border-zinc-300 border-x border-t [&>a[aria-current='page']]:font-bold px-2 py-0.5 no-underline transition-all duration-75"
|
||||
{{ if and (not $model.search) (eq $model.letter $ch) }}aria-current="page"{{ end }}>
|
||||
{{ $ch }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
<i class="ml-2 ri-hourglass-2-fill request-indicator spinning"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right side group: Search and Spalten menu -->
|
||||
<div class="flex items-end gap-4">
|
||||
<div class="min-w-[22.5rem] max-w-96 flex flex-row bg-stone-50 relative font-sans text-lg items-center">
|
||||
<div>
|
||||
<i class="ri-search-line"></i><i class="-ml-0.5 inline-block ri-arrow-right-s-line"></i>
|
||||
</div>
|
||||
<div class="pb-0 border-b-4 border-zinc-300 grow">
|
||||
<div class="border-b-4 border-zinc-300 grow">
|
||||
<form
|
||||
method="GET"
|
||||
action="/baende/"
|
||||
@@ -23,7 +43,7 @@
|
||||
<input type="hidden" name="letter" value="{{- $model.letter -}}" />
|
||||
{{- end -}}
|
||||
<input
|
||||
class="px-1.5 font-serif placeholder:italic w-full text-lg"
|
||||
class="px-2 py-0.5 font-sans placeholder:italic w-full text-lg"
|
||||
type="search"
|
||||
name="search"
|
||||
value="{{ $model.search }}"
|
||||
@@ -37,39 +57,44 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex items-end gap-2">
|
||||
<div id="alphabet" class="flex flex-wrap gap-1.5 font-sans text-lg">
|
||||
{{- range $_, $ch := $model.letters -}}
|
||||
<a
|
||||
href="/baende/?letter={{ $ch }}"
|
||||
:class="search ? 'inactive pointer-events-none' : ''"
|
||||
class="odd:bg-stone-100 even:bg-zinc-100 px-2 py-0.5 rounded text-lg font-semibold text-slate-700 transition-all duration-150 no-underline"
|
||||
{{ if and (not $model.search) (eq $model.letter $ch) }}aria-current="page"{{ end }}>
|
||||
{{ $ch }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<i class="ml-2 pb-1 ri-hourglass-2-fill request-indicator spinning"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap items-center justify-between gap-3">
|
||||
<div id="baende-count" class="text-lg font-semibold font-sans text-gray-600 whitespace-nowrap">
|
||||
{{ len $model.result.Entries }} Bände
|
||||
</div>
|
||||
<details class="font-sans text-sm" data-role="baende-column-toggle">
|
||||
<summary class="cursor-pointer text-gray-700 hover:text-slate-900">Spalten ein-/ausblenden</summary>
|
||||
<div class="mt-2 flex flex-wrap gap-4 text-sm text-gray-700">
|
||||
|
||||
|
||||
<div class="flex justify-between mt-10">
|
||||
<div class="relative" x-data="{ open: false }">
|
||||
<details class="font-sans text-base list-none" data-role="baende-column-toggle" @toggle="open = $el.open">
|
||||
<summary class="cursor-pointer text-gray-700 hover:text-slate-900 bg-gray-100 px-3 py-1.5 rounded-md flex items-center gap-2">
|
||||
Spalten
|
||||
<i class="ri-arrow-down-s-line transform origin-center transition-transform" :class="{ 'rotate-180': open }"></i>
|
||||
</summary>
|
||||
<div class="absolute left-0 mt-2 w-56 z-10 bg-white rounded-md shadow-lg border border-gray-200">
|
||||
<div class="p-4 flex flex-col gap-2 text-sm text-gray-700">
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" data-col="appearance" checked /> Erscheinung</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" data-col="year" /> Jahr</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" data-col="language" /> Sprachen</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" data-col="extent" checked /> Umfang / Maße</label>
|
||||
<label class="inline-flex items-center gap-2"><input type="checkbox" data-col="signatures" checked /> Signaturen</label>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div id="baenderesults" class="mt-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div id="baende-count" class="text-lg font-semibold font-sans text-gray-600 whitespace-nowrap">
|
||||
{{ len $model.result.Entries }} Bände
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="content-action-button"
|
||||
onclick="window.location.assign('/almanach/new')">
|
||||
<i class="ri-add-line"></i>
|
||||
<span>Neuer Band</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="baenderesults" class="mt-2">
|
||||
{{ template "_baende_table" $model }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
20
views/routes/baende/components/_band.gohtml
Normal file
20
views/routes/baende/components/_band.gohtml
Normal file
@@ -0,0 +1,20 @@
|
||||
<div class="band-text border-b border-stone-200 py-2">
|
||||
<div class="flex justify-between">
|
||||
<a href="/beitrag/{{ .entry.MusenalmID }}" class="text-lg font-semibold hover:underline">{{ .entry.PreferredTitle }}</a>
|
||||
<div class="text-sm text-stone-600">{{ .entry.Year }}</div>
|
||||
</div>
|
||||
<div class="text-sm text-stone-700">{{ .entry.ResponsibilityStmt }}</div>
|
||||
<div class="text-sm text-stone-700">{{ .entry.PublicationStmt }}</div>
|
||||
|
||||
{{ $rels := index .model.result.EntriesSeries .entry.Id }}
|
||||
{{ if $rels }}
|
||||
<div class="text-sm mt-1">
|
||||
<strong>Reihe:</strong>
|
||||
{{ range $i, $rel := $rels }}
|
||||
{{ if $i }}, {{ end }}
|
||||
{{ $series := index $.model.result.Series $rel.Series }}
|
||||
<a href="/reihe/{{ $series.MusenalmID }}" class="hover:underline">{{ $series.Title }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
9
views/routes/baende/components/alphabet.gohtml
Normal file
9
views/routes/baende/components/alphabet.gohtml
Normal file
@@ -0,0 +1,9 @@
|
||||
<div class="w-full">
|
||||
<div class="flex flex-wrap justify-between font-sans text-sm tracking-wide">
|
||||
{{ range .letters }}
|
||||
<a href="/baende/?letter={{ . }}"
|
||||
class="px-1.5 py-0.5"
|
||||
:class="{ 'bg-stone-300' : '{{ . }}' == '{{ $.letter }}' }">{{ . }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
6
views/routes/baende/components/search.gohtml
Normal file
6
views/routes/baende/components/search.gohtml
Normal file
@@ -0,0 +1,6 @@
|
||||
<div class="mb-4">
|
||||
<form action="/baende/" method="get" class="flex">
|
||||
<input type="text" name="search" value="{{ .search }}" placeholder="Suche..." class="w-full px-2 py-1 border border-stone-300">
|
||||
<button type="submit" class="px-4 py-1 bg-stone-200 border border-stone-300 border-l-0">Suchen</button>
|
||||
</form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user