mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-29 09:05:30 +00:00
Orte + Filter; -Alpine
This commit is contained in:
@@ -176,9 +176,9 @@ Each route has dedicated `head.gohtml` and `body.gohtml` files following Go temp
|
|||||||
|
|
||||||
**JavaScript Stack**:
|
**JavaScript Stack**:
|
||||||
- **HTMX**: Core interactivity and AJAX requests
|
- **HTMX**: Core interactivity and AJAX requests
|
||||||
- **Alpine.js**: Lightweight reactivity for UI components
|
- **Web Components**: Custom elements for self-contained functionality (replaced Alpine.js)
|
||||||
- **Modular Architecture**: ES6 modules with focused responsibilities
|
- **Modular Architecture**: ES6 modules with focused responsibilities
|
||||||
- **Web Components**: Custom elements for self-contained functionality
|
- **Event-Driven Architecture**: Custom events for inter-component communication
|
||||||
- **Build Tool**: Vite for module bundling and development server
|
- **Build Tool**: Vite for module bundling and development server
|
||||||
|
|
||||||
**CSS Stack**:
|
**CSS Stack**:
|
||||||
|
|||||||
@@ -102,11 +102,49 @@ func GetQuickFilter(kgpz *xmlmodels.Library) fiber.Handler {
|
|||||||
return strings.Compare(a.ID, b.ID)
|
return strings.Compare(a.ID, b.ID)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get all places that are referenced in pieces
|
||||||
|
places := make([]PlaceSummary, 0)
|
||||||
|
referencedPlaces := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, piece := range kgpz.Pieces.Array {
|
||||||
|
for _, placeRef := range piece.PlaceRefs {
|
||||||
|
referencedPlaces[placeRef.Ref] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kgpz.Places.Lock()
|
||||||
|
for _, place := range kgpz.Places.Array {
|
||||||
|
// Only include places that are actually referenced in pieces
|
||||||
|
if referencedPlaces[place.ID] {
|
||||||
|
// Get the primary name (first name in the list)
|
||||||
|
var name string
|
||||||
|
if len(place.Names) > 0 {
|
||||||
|
name = place.Names[0]
|
||||||
|
} else {
|
||||||
|
name = place.ID // fallback to ID if no names
|
||||||
|
}
|
||||||
|
|
||||||
|
placeSummary := PlaceSummary{
|
||||||
|
ID: place.ID,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
places = append(places, placeSummary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kgpz.Places.Unlock()
|
||||||
|
|
||||||
|
// Sort places list by ID
|
||||||
|
slices.SortFunc(places, func(a, b PlaceSummary) int {
|
||||||
|
return strings.Compare(a.ID, b.ID)
|
||||||
|
})
|
||||||
|
|
||||||
// Prepare data for the filter template
|
// Prepare data for the filter template
|
||||||
data := fiber.Map{
|
data := fiber.Map{
|
||||||
"AvailableYears": availableYears,
|
"AvailableYears": availableYears,
|
||||||
"Persons": persons,
|
"Persons": persons,
|
||||||
"Authors": authors,
|
"Authors": authors,
|
||||||
|
"Places": places,
|
||||||
"IssuesByYearJSON": string(issuesByYearJSON),
|
"IssuesByYearJSON": string(issuesByYearJSON),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +160,12 @@ type PersonSummary struct {
|
|||||||
Life string
|
Life string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PlaceSummary represents a simplified place for the filter list
|
||||||
|
type PlaceSummary struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
// IssueSummary represents an issue for the Jahr/Ausgabe filter
|
// IssueSummary represents an issue for the Jahr/Ausgabe filter
|
||||||
type IssueSummary struct {
|
type IssueSummary struct {
|
||||||
Number int `json:"number"`
|
Number int `json:"number"`
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -22,7 +22,6 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script src="/assets/js/alpine.min.js" defer></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="/assets/css/fonts.css?v={{ .timestamp }}" />
|
<link rel="stylesheet" type="text/css" href="/assets/css/fonts.css?v={{ .timestamp }}" />
|
||||||
<link rel="stylesheet" type="text/css" href="/assets/style.css?v={{ .timestamp }}" />
|
<link rel="stylesheet" type="text/css" href="/assets/style.css?v={{ .timestamp }}" />
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
<div class="flex flex-row justify-center mt-12 mb-8">
|
<div class="flex flex-row justify-center mt-12 mb-8">
|
||||||
<!-- Schnellfilter Button -->
|
<!-- Schnellfilter Button -->
|
||||||
<div>
|
<schnellauswahl-button></schnellauswahl-button>
|
||||||
<button
|
|
||||||
id="filter-toggle"
|
|
||||||
class="mr-2 text-lg border px-4 h-full hover:bg-slate-200 transition-colors cursor-pointer"
|
|
||||||
title="Schnellfilter öffnen/schließen">
|
|
||||||
<i class="ri-filter-2-line"></i> <div class="inline-block text-lg">Schnellauswahl</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-6/12">
|
<div class="w-6/12">
|
||||||
<input
|
<input
|
||||||
@@ -22,29 +15,6 @@
|
|||||||
hx-target="main" />
|
hx-target="main" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div x-data="{ open: false }">
|
<navigation-menu></navigation-menu>
|
||||||
<button
|
|
||||||
class="ml-2 text-2xl border px-4 h-full hover:bg-slate-200 transition-colors cursor-pointer"
|
|
||||||
:class="open? 'open bg-slate-200' : 'closed'"
|
|
||||||
@click="open = !open">
|
|
||||||
<i class="ri-menu-line"></i>
|
|
||||||
</button>
|
|
||||||
<div :class="open? 'open' : 'closed'" x-show="open" class="absolute bg-slate-50 px-5 py-3 z-50">
|
|
||||||
<div>
|
|
||||||
<div>Übersicht nach</div>
|
|
||||||
<div class="ml-2 flex flex-col gap-y-2 mt-2">
|
|
||||||
<a href="/">Jahrgängen</a>
|
|
||||||
<a href="/akteure/a">Personen</a>
|
|
||||||
<a href="/kategorie/">Betragsarten</a>
|
|
||||||
<a href="/ort/">Orten</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-y-2 mt-2">
|
|
||||||
<a href="/edition/">Geschichte & Edition der KGPZ</a>
|
|
||||||
<a href="/zitation/">Zitation</a>
|
|
||||||
<a href="/kontakt/">Kontakt</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
203
views/routes/components/_piece_summary_for_place.gohtml
Normal file
203
views/routes/components/_piece_summary_for_place.gohtml
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
{{- $piece := .Piece -}}
|
||||||
|
{{- $currentActorID := .CurrentActorID -}}
|
||||||
|
|
||||||
|
{{- $categoryFlags := GetCategoryFlags $piece -}}
|
||||||
|
|
||||||
|
{{- $place := "" -}}
|
||||||
|
{{- if $piece.PlaceRefs -}}
|
||||||
|
{{- $placeObj := GetPlace (index $piece.PlaceRefs 0).Ref -}}
|
||||||
|
{{- if gt (len $placeObj.Names) 0 -}}
|
||||||
|
{{- $place = index $placeObj.Names 0 -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $title := "" -}}
|
||||||
|
{{- if $piece.Title -}}
|
||||||
|
{{- $title = index $piece.Title 0 -}}
|
||||||
|
{{- else if $piece.Incipit -}}
|
||||||
|
{{- $title = index $piece.Incipit 0 -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- $workTitle := "" -}}
|
||||||
|
{{- $workAuthorName := "" -}}
|
||||||
|
{{- $workAuthorID := "" -}}
|
||||||
|
{{- if $piece.WorkRefs -}}
|
||||||
|
{{- $work := GetWork (index $piece.WorkRefs 0).Ref -}}
|
||||||
|
{{- if $work.PreferredTitle -}}
|
||||||
|
{{- $workTitle = $work.PreferredTitle -}}
|
||||||
|
{{- else if $work.Citation.Title -}}
|
||||||
|
{{- $workTitle = $work.Citation.Title -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $work.AgentRefs -}}
|
||||||
|
{{- range $workAgentRef := $work.AgentRefs -}}
|
||||||
|
{{- if (or (eq $workAgentRef.Category "") (eq $workAgentRef.Category "autor")) -}}
|
||||||
|
{{- $workAgent := GetAgent $workAgentRef.Ref -}}
|
||||||
|
{{- if and $workAgent (gt (len $workAgent.Names) 0) -}}
|
||||||
|
{{- $workAuthorName = index $workAgent.Names 0 -}}
|
||||||
|
{{- $workAuthorID = $workAgentRef.Ref -}}
|
||||||
|
{{- break -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- /* Build category list */ -}}
|
||||||
|
{{- $categories := slice -}}
|
||||||
|
{{- if $categoryFlags.Rezension -}}
|
||||||
|
{{- $categories = append $categories "Rezension" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Gedicht -}}
|
||||||
|
{{- $categories = append $categories "Gedicht" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Aufsatz -}}
|
||||||
|
{{- $categories = append $categories "Aufsatz" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Theaterkritik -}}
|
||||||
|
{{- $categories = append $categories "Theaterkritik" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Brief -}}
|
||||||
|
{{- $categories = append $categories "Brief" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Erzaehlung -}}
|
||||||
|
{{- $categories = append $categories "Erzählung" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Kommentar -}}
|
||||||
|
{{- $categories = append $categories "Kommentar" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Uebersetzung -}}
|
||||||
|
{{- $categories = append $categories "Übersetzung" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Auszug -}}
|
||||||
|
{{- $categories = append $categories "Auszug" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Replik -}}
|
||||||
|
{{- $categories = append $categories "Replik" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Lokalnachrichten -}}
|
||||||
|
{{- $categories = append $categories "Lokalnachrichten" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Lotterie -}}
|
||||||
|
{{- $categories = append $categories "Lotterie" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Nachruf -}}
|
||||||
|
{{- $categories = append $categories "Nachruf" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Weltnachrichten -}}
|
||||||
|
{{- $categories = append $categories "Weltnachrichten" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.EinkommendeFremde -}}
|
||||||
|
{{- $categories = append $categories "Einkommende Fremde" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Wechselkurse -}}
|
||||||
|
{{- $categories = append $categories "Wechselkurse" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Buecher -}}
|
||||||
|
{{- $categories = append $categories "Bücher" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Lokalanzeigen -}}
|
||||||
|
{{- $categories = append $categories "Lokalanzeigen" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Vorladung -}}
|
||||||
|
{{- $categories = append $categories "Vorladung" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.GelehrteNachrichten -}}
|
||||||
|
{{- $categories = append $categories "Gelehrte Nachrichten" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Anzeige -}}
|
||||||
|
{{- $categories = append $categories "Anzeige" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Proklamation -}}
|
||||||
|
{{- $categories = append $categories "Proklamation" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Desertionsliste -}}
|
||||||
|
{{- $categories = append $categories "Desertionsliste" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Notenblatt -}}
|
||||||
|
{{- $categories = append $categories "Notenblatt" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Vorlesungsverzeichnis -}}
|
||||||
|
{{- $categories = append $categories "Vorlesungsverzeichnis" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Abbildung -}}
|
||||||
|
{{- $categories = append $categories "Abbildung" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Ineigenersache -}}
|
||||||
|
{{- $categories = append $categories "In eigener Sache" -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $categoryFlags.Provinienz -}}
|
||||||
|
{{- $categories = append $categories "Provinienz" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- /* Display category combination */ -}}
|
||||||
|
{{- $categoryName := "" -}}
|
||||||
|
{{- if eq (len $categories) 0 -}}
|
||||||
|
{{- $categoryName = "Beitrag" -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $sortedCategories := sortStrings $categories -}}
|
||||||
|
{{- $categoryName = joinWithUnd $sortedCategories -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- /* Generate piece descriptions */ -}}
|
||||||
|
{{- if has $categories "Rezension" -}}
|
||||||
|
{{- /* Collect all additional authors (not current actor) */ -}}
|
||||||
|
{{- $additionalAuthors := slice -}}
|
||||||
|
{{- $currentAuthorFound := false -}}
|
||||||
|
{{- range $agentref := $piece.AgentRefs -}}
|
||||||
|
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
|
||||||
|
{{- $agent := GetAgent $agentref.Ref -}}
|
||||||
|
{{- if and $agent (gt (len $agent.Names) 0) -}}
|
||||||
|
{{- if ne $agentref.Ref $currentActorID -}}
|
||||||
|
{{- $additionalAuthors = append $additionalAuthors $agent -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $currentAuthorFound = true -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- /* Display review with colon instead of "mit" for place view */ -}}
|
||||||
|
{{- if $additionalAuthors -}}
|
||||||
|
{{ range $i, $author := $additionalAuthors }}{{- if gt $i 0 }} und {{ end }}<a href="/akteure/{{ $author.ID }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $author.Names 0 }}</a>{{ end }}: {{ $categoryName }} von:
|
||||||
|
{{- else if $currentAuthorFound -}}
|
||||||
|
{{ $categoryName }} von:
|
||||||
|
{{- else -}}
|
||||||
|
{{ $categoryName }} von:
|
||||||
|
{{- end -}}
|
||||||
|
{{ if $workAuthorName }}
|
||||||
|
<a href="/akteure/{{ $workAuthorID }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ $workAuthorName }}</a>,
|
||||||
|
{{ end }}
|
||||||
|
{{ if $workTitle }}
|
||||||
|
<em>{{ $workTitle }}</em>
|
||||||
|
{{ else if $title }}
|
||||||
|
<em>{{ $title }}</em>
|
||||||
|
{{ else }}
|
||||||
|
[Werk unbekannt]
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{- else -}}
|
||||||
|
{{- /* Collect all additional authors (not current actor) */ -}}
|
||||||
|
{{- $additionalAuthors := slice -}}
|
||||||
|
{{- $currentAuthorFound := false -}}
|
||||||
|
{{- range $agentref := $piece.AgentRefs -}}
|
||||||
|
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
|
||||||
|
{{- $agent := GetAgent $agentref.Ref -}}
|
||||||
|
{{- if and $agent (gt (len $agent.Names) 0) -}}
|
||||||
|
{{- if ne $agentref.Ref $currentActorID -}}
|
||||||
|
{{- $additionalAuthors = append $additionalAuthors $agent -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $currentAuthorFound = true -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- /* Display with colon instead of "mit" for place view */ -}}
|
||||||
|
{{- if $additionalAuthors -}}
|
||||||
|
{{ range $i, $author := $additionalAuthors }}{{- if gt $i 0 }} und {{ end }}<a href="/akteure/{{ $author.ID }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $author.Names 0 }}</a>{{ end }}: {{ $categoryName }}{{ if $title }}: <em>{{ $title }}</em>{{ end }}
|
||||||
|
{{- else if $currentAuthorFound -}}
|
||||||
|
{{ $categoryName }}{{ if $title }}: <em>{{ $title }}</em>{{ end }}
|
||||||
|
{{- else -}}
|
||||||
|
{{ $categoryName }}{{ if $title }}: <em>{{ $title }}</em>{{ else if eq $categoryName "Beitrag" }} ohne Titel{{ end }}
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="flex flex-row justify-center gap-4" id="filter">
|
<div class="flex flex-row justify-center gap-4 h-96" id="filter">
|
||||||
<div class="bg-white border border-slate-200 rounded-lg shadow-sm p-4 w-full max-w-md">
|
<div class="p-4 w-full max-w-md flex flex-col h-full">
|
||||||
<h3 class="text-lg font-semibold text-slate-800 mb-4 flex items-center gap-2">
|
<h3 class="text-lg font-semibold text-slate-800 mb-4 flex items-center gap-2">
|
||||||
<i class="ri-calendar-line text-slate-600"></i>
|
<i class="ri-calendar-line text-slate-600"></i>
|
||||||
Auswahl nach Datum, Nummer od. Seite
|
Auswahl nach Datum, Nummer od. Seite
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<!-- Year Selection -->
|
<!-- Year Selection -->
|
||||||
<div class="flex items-center gap-2 mb-4">
|
<div class="flex items-center gap-2 mb-4">
|
||||||
<label for="year-select" class="text-sm text-slate-600 w-12 hidden">Jahr wählen...</label>
|
<label for="year-select" class="text-sm text-slate-600 w-12 hidden">Jahr wählen...</label>
|
||||||
<select id="year-select" class="tabular-nums flex-1 px-2 py-1 border border-slate-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400" style="max-height: 200px; overflow-y: auto;">
|
<select id="year-select" class="tabular-nums flex-1 px-2 py-1 border border-slate-300 rounded text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400" style="max-height: 200px; overflow-y: auto;">
|
||||||
<option value="">Jahr wählen</option>
|
<option value="">Jahr wählen</option>
|
||||||
{{ range $year := .AvailableYears }}
|
{{ range $year := .AvailableYears }}
|
||||||
<option value="{{ $year }}">{{ $year }}</option>
|
<option value="{{ $year }}">{{ $year }}</option>
|
||||||
@@ -21,10 +21,10 @@
|
|||||||
|
|
||||||
<!-- Ausgabe Selection - Two Selects -->
|
<!-- Ausgabe Selection - Two Selects -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<select id="issue-number-select" disabled class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400 disabled:bg-slate-100 disabled:cursor-not-allowed">
|
<select id="issue-number-select" disabled class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400 disabled:bg-slate-100 disabled:cursor-not-allowed">
|
||||||
<option value="">Nr.</option>
|
<option value="">Nr.</option>
|
||||||
</select>
|
</select>
|
||||||
<select id="issue-date-select" disabled class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400 disabled:bg-slate-100 disabled:cursor-not-allowed">
|
<select id="issue-date-select" disabled class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400 disabled:bg-slate-100 disabled:cursor-not-allowed">
|
||||||
<option value="">Datum</option>
|
<option value="">Datum</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<!-- Page Input -->
|
<!-- Page Input -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label for="page-input" class="text-sm text-slate-600 w-12 hidden"> oder Seite eingeben...</label>
|
<label for="page-input" class="text-sm text-slate-600 w-12 hidden"> oder Seite eingeben...</label>
|
||||||
<input type="number" id="page-input" min="1" placeholder="Seite eingeben" disabled class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400 disabled:bg-slate-100 disabled:cursor-not-allowed disabled:text-slate-500">
|
<input type="number" id="page-input" min="1" placeholder="Seite eingeben" disabled class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400 disabled:bg-slate-100 disabled:cursor-not-allowed disabled:text-slate-500">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Page Jump Button -->
|
<!-- Page Jump Button -->
|
||||||
@@ -54,22 +54,22 @@
|
|||||||
</year-jump-filter>
|
</year-jump-filter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-white border border-slate-200 rounded-lg shadow-sm p-4 w-full max-w-md">
|
<div class="p-4 w-full max-w-md flex flex-col h-full">
|
||||||
<h3 class="text-lg font-semibold text-slate-800 mb-4 flex items-center gap-2">
|
<h3 class="text-lg font-semibold text-slate-800 mb-4 flex items-center gap-2">
|
||||||
<i class="ri-user-line text-slate-600"></i>
|
<i class="ri-user-line text-slate-600"></i>
|
||||||
Auswahl nach Person
|
Auswahl nach Person
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<!-- Person Jump Filter -->
|
<!-- Person Jump Filter -->
|
||||||
<person-jump-filter>
|
<person-jump-filter class="flex-1 flex flex-col min-h-0">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3 flex flex-col h-full">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label for="person-search" class="hidden text-sm text-slate-600 w-16">Filter</label>
|
<label for="person-search" class="hidden text-sm text-slate-600 w-16">Filter</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="person-search"
|
id="person-search"
|
||||||
placeholder="Name oder Lebensdaten eingeben..."
|
placeholder="Name oder Lebensdaten eingeben..."
|
||||||
class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400"
|
class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<label for="authors-only" class="text-sm text-slate-600">Nur Autor:innen anzeigen</label>
|
<label for="authors-only" class="text-sm text-slate-600">Nur Autor:innen anzeigen</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-h-48 overflow-y-auto border border-slate-300 rounded bg-white text-sm">
|
<div class="flex-1 min-h-0 overflow-y-auto border border-slate-300 rounded bg-white text-sm">
|
||||||
<!-- All Persons List -->
|
<!-- All Persons List -->
|
||||||
<div id="all-persons">
|
<div id="all-persons">
|
||||||
{{ range $person := .Persons }}
|
{{ range $person := .Persons }}
|
||||||
@@ -114,5 +114,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</person-jump-filter>
|
</person-jump-filter>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 w-full max-w-md flex flex-col h-full">
|
||||||
|
<h3 class="text-lg font-semibold text-slate-800 mb-4 flex items-center gap-2">
|
||||||
|
<i class="ri-map-pin-line text-slate-600"></i>
|
||||||
|
Auswahl nach Ort
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<!-- Place Jump Filter -->
|
||||||
|
<place-jump-filter class="flex-1 flex flex-col min-h-0">
|
||||||
|
<div class="space-y-3 flex flex-col h-full">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label for="place-search" class="hidden text-sm text-slate-600 w-16">Filter</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="place-search"
|
||||||
|
placeholder="Ortsname eingeben..."
|
||||||
|
class="flex-1 px-2 py-1 border border-slate-300 rounded text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 min-h-0 overflow-y-auto border border-slate-300 rounded bg-white text-sm">
|
||||||
|
<!-- All Places List -->
|
||||||
|
<div id="all-places">
|
||||||
|
{{ range $place := .Places }}
|
||||||
|
<div class="place-item odd:bg-slate-50 even:bg-white">
|
||||||
|
<a href="/ort/{{ $place.ID }}" class="block px-2 py-1 hover:bg-blue-50 border-b border-slate-100 last:border-b-0">
|
||||||
|
<span class="place-name font-medium text-slate-800">{{ $place.Name }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</place-jump-filter>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="/ort/" class="inline-flex items-center text-blue-600 hover:text-blue-700 text-sm">
|
<a href="/ort/" class="inline-flex items-center text-blue-600 hover:text-blue-700 text-sm">
|
||||||
<i class="ri-arrow-left-line mr-2"></i>
|
<i class="ri-arrow-left-line mr-2"></i>
|
||||||
Zur<75>ck zur <20>bersicht
|
Zur<75>ck zur <20>bersicht
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,7 +22,9 @@
|
|||||||
{{ if .model.SelectedPlace.Place.Geo }}
|
{{ if .model.SelectedPlace.Place.Geo }}
|
||||||
<p class="text-slate-600">
|
<p class="text-slate-600">
|
||||||
<i class="ri-map-pin-line mr-1"></i>
|
<i class="ri-map-pin-line mr-1"></i>
|
||||||
Geonames: {{ .model.SelectedPlace.Place.Geo }}
|
<a href="{{ .model.SelectedPlace.Place.Geo }}" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-700 underline">
|
||||||
|
Geonames
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
@@ -30,38 +32,66 @@
|
|||||||
<!-- Associated Pieces -->
|
<!-- Associated Pieces -->
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xl font-semibold text-slate-800 mb-4">
|
<h2 class="text-xl font-semibold text-slate-800 mb-4">
|
||||||
Beitr<EFBFBD>ge ({{ len .model.SelectedPlace.Pieces }})
|
<i class="ri-newspaper-line mr-2"></i><u class="decoration underline-offset-3">Beiträge</u> ({{ len .model.SelectedPlace.Pieces }})
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{{ if .model.SelectedPlace.Pieces }}
|
{{ if .model.SelectedPlace.Pieces }}
|
||||||
<div class="space-y-4">
|
<div class="space-y-2">
|
||||||
{{ range $piece := .model.SelectedPlace.Pieces }}
|
{{- /* Group pieces by their own title/incipit */ -}}
|
||||||
<div class="border border-slate-200 rounded-lg p-4 hover:bg-slate-50">
|
{{- $groupedPieces := dict -}}
|
||||||
<h3 class="font-medium text-slate-800 mb-2">
|
{{- range $_, $p := .model.SelectedPlace.Pieces -}}
|
||||||
{{ if $piece.Title }}
|
{{- $groupKey := "" -}}
|
||||||
{{ index $piece.Title 0 }}
|
{{- if $p.Title -}}
|
||||||
{{ else }}
|
{{- $groupKey = index $p.Title 0 -}}
|
||||||
Untitled
|
{{- else if $p.Incipit -}}
|
||||||
{{ end }}
|
{{- $groupKey = index $p.Incipit 0 -}}
|
||||||
</h3>
|
{{- else -}}
|
||||||
|
{{- $groupKey = printf "untitled-%s" $p.ID -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
{{ if $piece.IssueRefs }}
|
{{- $existing := index $groupedPieces $groupKey -}}
|
||||||
<div class="text-sm text-slate-600">
|
{{- if $existing -}}
|
||||||
{{ range $issueRef := $piece.IssueRefs }}
|
{{- $groupedPieces = merge $groupedPieces (dict $groupKey (append $existing $p)) -}}
|
||||||
<span class="inline-block mr-4">
|
{{- else -}}
|
||||||
<i class="ri-calendar-line mr-1"></i>
|
{{- $groupedPieces = merge $groupedPieces (dict $groupKey (slice $p)) -}}
|
||||||
<a href="/{{ $issueRef.When.Year }}/{{ $issueRef.IssueNumber }}" class="text-blue-600 hover:text-blue-700">
|
{{- end -}}
|
||||||
{{ $issueRef.When.Year }} Nr. {{ $issueRef.IssueNumber }}
|
{{- end -}}
|
||||||
|
|
||||||
|
<div class="columns-2 gap-1 hyphens-auto">
|
||||||
|
{{- /* Display grouped pieces */ -}}
|
||||||
|
{{- range $groupKey, $groupedItems := $groupedPieces -}}
|
||||||
|
<div class="break-inside-avoid pl-4">
|
||||||
|
<div class="pb-1 indent-4">
|
||||||
|
{{- /* Use first piece for display text with colon format for places */ -}}
|
||||||
|
{{ template "_piece_summary_for_place" (dict "Piece" (index $groupedItems 0) "CurrentActorID" "") }}
|
||||||
|
|
||||||
|
{{- /* Show all citations from all pieces in this group inline with commas */ -}}
|
||||||
|
{{ " " }}{{- range $groupIndex, $groupItem := $groupedItems -}}
|
||||||
|
{{- range $issueIndex, $issue := $groupItem.IssueRefs -}}
|
||||||
|
{{- if or (gt $groupIndex 0) (gt $issueIndex 0) }}, {{ end -}}
|
||||||
|
<span class="text-blue-600 hover:text-blue-700 underline decoration-dotted hover:decoration-solid [&>a]:text-blue-600 [&>a:hover]:text-blue-700">{{- template "_citation" $issue -}}</span>{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- /* Add "Ganzer Beitrag" link if piece spans multiple issues */ -}}
|
||||||
|
{{- $firstGroupItem := index $groupedItems 0 -}}
|
||||||
|
{{- if gt (len $firstGroupItem.IssueRefs) 1 -}}
|
||||||
|
{{ " " }}<div class="inline-flex items-center gap-1 px-2 py-1 bg-blue-50
|
||||||
|
hover:bg-blue-100 text-blue-700 hover:text-blue-800 border border-blue-200
|
||||||
|
hover:border-blue-300 rounded text-xs font-medium transition-colors duration-200
|
||||||
|
indent-0">
|
||||||
|
<i class="ri-file-copy-2-line text-xs"></i>
|
||||||
|
<a href="{{ GetPieceURL $firstGroupItem.ID }}" class="">
|
||||||
|
Ganzer Beitrag
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</div>
|
||||||
{{ end }}
|
{{- end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
</div>
|
||||||
</div>
|
{{- end -}}
|
||||||
{{ end }}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<p class="text-slate-500 italic">Keine Beitr<EFBFBD>ge f<EFBFBD>r diesen Ort gefunden.</p>
|
<p class="text-slate-500 italic">Keine Beiträge für diesen Ort gefunden.</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ if (document.querySelector(".newspaper-page-container")) {
|
|||||||
let htmxAfterSwapHandler = function (event) {
|
let htmxAfterSwapHandler = function (event) {
|
||||||
// Apply page-specific backdrop styling after navigation
|
// Apply page-specific backdrop styling after navigation
|
||||||
applyPageBackdrop();
|
applyPageBackdrop();
|
||||||
|
|
||||||
// Update citation links after navigation
|
// Update citation links after navigation
|
||||||
updateCitationLinks();
|
updateCitationLinks();
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +1,3 @@
|
|||||||
const filterBtn = document.getElementById("filter-toggle");
|
|
||||||
if (filterBtn) {
|
|
||||||
filterBtn.addEventListener("click", toggleFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter toggle functionality
|
|
||||||
function toggleFilter() {
|
|
||||||
const filterContainer = document.getElementById("filter-container");
|
|
||||||
const filterButton = document.getElementById("filter-toggle");
|
|
||||||
const filterContentDiv = filterContainer?.querySelector("div.flex.justify-center");
|
|
||||||
|
|
||||||
if (filterContainer.classList.contains("hidden")) {
|
|
||||||
// Show the filter
|
|
||||||
filterContainer.classList.remove("hidden");
|
|
||||||
filterButton.classList.add("bg-slate-200");
|
|
||||||
|
|
||||||
// Load content only if it doesn't exist - check for actual content
|
|
||||||
const hasContent = filterContentDiv && filterContentDiv.querySelector("div, form, h3");
|
|
||||||
if (!hasContent) {
|
|
||||||
htmx
|
|
||||||
.ajax("GET", "/filter", {
|
|
||||||
target: "#filter-container",
|
|
||||||
select: "#filter",
|
|
||||||
swap: "innerHTML",
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log("HTMX request completed");
|
|
||||||
// Re-query the element to see if it changed
|
|
||||||
const updatedDiv = document.querySelector("#filter-container .flex.justify-center");
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log("HTMX request failed:", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filterContainer.classList.add("hidden");
|
|
||||||
filterButton.classList.remove("bg-slate-200");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export for global access
|
|
||||||
window.toggleFilter = toggleFilter;
|
|
||||||
|
|
||||||
// Close filter when clicking outside
|
|
||||||
document.addEventListener("click", function (event) {
|
|
||||||
const filterContainer = document.getElementById("filter-container");
|
|
||||||
const filterButton = document.getElementById("filter-toggle");
|
|
||||||
|
|
||||||
if (
|
|
||||||
filterContainer &&
|
|
||||||
filterButton &&
|
|
||||||
!filterContainer.contains(event.target) &&
|
|
||||||
!filterButton.contains(event.target)
|
|
||||||
) {
|
|
||||||
if (!filterContainer.classList.contains("hidden")) {
|
|
||||||
filterContainer.classList.add("hidden");
|
|
||||||
filterButton.classList.remove("bg-slate-200");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle search input logic
|
// Handle search input logic
|
||||||
document.body.addEventListener("htmx:configRequest", function (event) {
|
document.body.addEventListener("htmx:configRequest", function (event) {
|
||||||
let element = event.detail.elt;
|
let element = event.detail.elt;
|
||||||
@@ -158,6 +97,60 @@ class PersonJumpFilter extends HTMLElement {
|
|||||||
// Register the custom element
|
// Register the custom element
|
||||||
customElements.define('person-jump-filter', PersonJumpFilter);
|
customElements.define('person-jump-filter', PersonJumpFilter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlaceJumpFilter - Web component for filtering places list
|
||||||
|
* Works with server-rendered place list and provides client-side filtering
|
||||||
|
*/
|
||||||
|
class PlaceJumpFilter extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
const searchInput = this.querySelector('#place-search');
|
||||||
|
const allPlacesList = this.querySelector('#all-places');
|
||||||
|
|
||||||
|
if (!searchInput || !allPlacesList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
searchInput.addEventListener('input', (e) => {
|
||||||
|
const query = e.target.value.toLowerCase().trim();
|
||||||
|
this.filterPlaces(query);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
filterPlaces(query) {
|
||||||
|
const allPlacesList = this.querySelector('#all-places');
|
||||||
|
|
||||||
|
if (!allPlacesList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const placeItems = allPlacesList.querySelectorAll('.place-item');
|
||||||
|
|
||||||
|
placeItems.forEach(item => {
|
||||||
|
const name = item.querySelector('.place-name')?.textContent || '';
|
||||||
|
|
||||||
|
const matches = !query || name.toLowerCase().includes(query);
|
||||||
|
|
||||||
|
if (matches) {
|
||||||
|
item.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
item.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the custom element
|
||||||
|
customElements.define('place-jump-filter', PlaceJumpFilter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YearJumpFilter - Unified web component for Jahr-based navigation
|
* YearJumpFilter - Unified web component for Jahr-based navigation
|
||||||
* Allows jumping by Jahr/Ausgabe or Jahr/Seite
|
* Allows jumping by Jahr/Ausgabe or Jahr/Seite
|
||||||
@@ -408,3 +401,303 @@ class YearJumpFilter extends HTMLElement {
|
|||||||
|
|
||||||
// Register the custom element
|
// Register the custom element
|
||||||
customElements.define('year-jump-filter', YearJumpFilter);
|
customElements.define('year-jump-filter', YearJumpFilter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SchnellauswahlButton - Web component for the quick selection/filter button
|
||||||
|
* Encapsulates the toggle functionality and HTMX content loading
|
||||||
|
*/
|
||||||
|
class SchnellauswahlButton extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.createButton();
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
// Clean up event listeners when component is removed
|
||||||
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
|
document.removeEventListener('quickfilter:selection', this.handleSelectionEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
createButton() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<button
|
||||||
|
id="filter-toggle"
|
||||||
|
class="mr-2 text-lg border px-4 h-full hover:bg-slate-200 transition-colors cursor-pointer"
|
||||||
|
title="Schnellfilter öffnen/schließen">
|
||||||
|
<i class="ri-filter-2-line"></i> <div class="inline-block text-lg">Schnellauswahl</div>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
const button = this.querySelector('button');
|
||||||
|
if (button) {
|
||||||
|
button.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.toggleFilter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind event handlers to maintain 'this' context
|
||||||
|
this.handleSelectionEvent = this.handleSelectionEvent.bind(this);
|
||||||
|
this.handleOutsideClick = this.handleOutsideClick.bind(this);
|
||||||
|
|
||||||
|
// Listen for person/place selection events
|
||||||
|
document.addEventListener('quickfilter:selection', this.handleSelectionEvent);
|
||||||
|
|
||||||
|
// Listen for outside clicks
|
||||||
|
document.addEventListener('click', this.handleOutsideClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFilter() {
|
||||||
|
const filterContainer = document.getElementById("filter-container");
|
||||||
|
const filterButton = this.querySelector('button');
|
||||||
|
|
||||||
|
if (!filterContainer || !filterButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterContentDiv = filterContainer.querySelector("div.flex.justify-center");
|
||||||
|
|
||||||
|
if (filterContainer.classList.contains("hidden")) {
|
||||||
|
// Show the filter
|
||||||
|
filterContainer.classList.remove("hidden");
|
||||||
|
filterButton.classList.add("bg-slate-200");
|
||||||
|
this.isOpen = true;
|
||||||
|
|
||||||
|
// Load content only if it doesn't exist - check for actual content
|
||||||
|
const hasContent = filterContentDiv && filterContentDiv.querySelector("div, form, h3");
|
||||||
|
if (!hasContent) {
|
||||||
|
htmx
|
||||||
|
.ajax("GET", "/filter", {
|
||||||
|
target: "#filter-container",
|
||||||
|
select: "#filter",
|
||||||
|
swap: "innerHTML",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
console.log("HTMX request completed");
|
||||||
|
// Re-query the element to see if it changed
|
||||||
|
const updatedDiv = document.querySelector("#filter-container .flex.justify-center");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("HTMX request failed:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.hideFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideFilter() {
|
||||||
|
const filterContainer = document.getElementById("filter-container");
|
||||||
|
const filterButton = this.querySelector('button');
|
||||||
|
|
||||||
|
if (!filterContainer || !filterButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterContainer.classList.add("hidden");
|
||||||
|
filterButton.classList.remove("bg-slate-200");
|
||||||
|
this.isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectionEvent(event) {
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.hideFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOutsideClick(event) {
|
||||||
|
const filterContainer = document.getElementById("filter-container");
|
||||||
|
const filterButton = this.querySelector('button');
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isOpen &&
|
||||||
|
filterContainer &&
|
||||||
|
filterButton &&
|
||||||
|
!filterContainer.contains(event.target) &&
|
||||||
|
!this.contains(event.target)
|
||||||
|
) {
|
||||||
|
this.hideFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the custom element
|
||||||
|
customElements.define('schnellauswahl-button', SchnellauswahlButton);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NavigationMenu - Web component for the main navigation menu
|
||||||
|
* Web component for the main navigation menu with proper event handling
|
||||||
|
*/
|
||||||
|
class NavigationMenu extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.createMenu();
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
|
document.removeEventListener('quickfilter:selection', this.handleSelectionEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
createMenu() {
|
||||||
|
this.innerHTML = `
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="ml-2 text-2xl border px-4 h-full hover:bg-slate-200 transition-colors cursor-pointer"
|
||||||
|
id="menu-toggle">
|
||||||
|
<i class="ri-menu-line"></i>
|
||||||
|
</button>
|
||||||
|
<div id="menu-dropdown" class="hidden absolute bg-slate-50 px-5 py-3 z-50">
|
||||||
|
<div>
|
||||||
|
<div>Übersicht nach</div>
|
||||||
|
<div class="ml-2 flex flex-col gap-y-2 mt-2">
|
||||||
|
<a href="/">Jahrgängen</a>
|
||||||
|
<a href="/akteure/a">Personen</a>
|
||||||
|
<a href="/kategorie/">Betragsarten</a>
|
||||||
|
<a href="/ort/">Orten</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-y-2 mt-2">
|
||||||
|
<a href="/edition/">Geschichte & Edition der KGPZ</a>
|
||||||
|
<a href="/zitation/">Zitation</a>
|
||||||
|
<a href="/kontakt/">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
const button = this.querySelector('#menu-toggle');
|
||||||
|
const dropdown = this.querySelector('#menu-dropdown');
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
button.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.toggleMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for link clicks within the menu
|
||||||
|
if (dropdown) {
|
||||||
|
dropdown.addEventListener('click', (e) => {
|
||||||
|
const link = e.target.closest('a[href]');
|
||||||
|
if (link) {
|
||||||
|
// Dispatch selection event
|
||||||
|
const selectionEvent = new CustomEvent('quickfilter:selection', {
|
||||||
|
detail: {
|
||||||
|
type: 'navigation',
|
||||||
|
source: 'menu',
|
||||||
|
url: link.getAttribute('href'),
|
||||||
|
text: link.textContent.trim()
|
||||||
|
},
|
||||||
|
bubbles: true
|
||||||
|
});
|
||||||
|
|
||||||
|
document.dispatchEvent(selectionEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind event handlers for cleanup
|
||||||
|
this.handleOutsideClick = this.handleOutsideClick.bind(this);
|
||||||
|
this.handleSelectionEvent = this.handleSelectionEvent.bind(this);
|
||||||
|
|
||||||
|
// Listen for outside clicks
|
||||||
|
document.addEventListener('click', this.handleOutsideClick);
|
||||||
|
|
||||||
|
// Listen for selection events to close menu
|
||||||
|
document.addEventListener('quickfilter:selection', this.handleSelectionEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMenu() {
|
||||||
|
const button = this.querySelector('#menu-toggle');
|
||||||
|
const dropdown = this.querySelector('#menu-dropdown');
|
||||||
|
|
||||||
|
if (!button || !dropdown) return;
|
||||||
|
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.hideMenu();
|
||||||
|
} else {
|
||||||
|
this.showMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showMenu() {
|
||||||
|
const button = this.querySelector('#menu-toggle');
|
||||||
|
const dropdown = this.querySelector('#menu-dropdown');
|
||||||
|
|
||||||
|
if (!button || !dropdown) return;
|
||||||
|
|
||||||
|
dropdown.classList.remove('hidden');
|
||||||
|
button.classList.add('bg-slate-200');
|
||||||
|
this.isOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideMenu() {
|
||||||
|
const button = this.querySelector('#menu-toggle');
|
||||||
|
const dropdown = this.querySelector('#menu-dropdown');
|
||||||
|
|
||||||
|
if (!button || !dropdown) return;
|
||||||
|
|
||||||
|
dropdown.classList.add('hidden');
|
||||||
|
button.classList.remove('bg-slate-200');
|
||||||
|
this.isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectionEvent(event) {
|
||||||
|
// Close menu when any selection is made (from quickfilter or menu)
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.hideMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOutsideClick(event) {
|
||||||
|
if (this.isOpen && !this.contains(event.target)) {
|
||||||
|
this.hideMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the custom element
|
||||||
|
customElements.define('navigation-menu', NavigationMenu);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global event handler for quickfilter selections
|
||||||
|
* Dispatches custom events when users select persons or places from quickfilter
|
||||||
|
*/
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Add event delegation for person and place links in quickfilter
|
||||||
|
document.addEventListener('click', function(event) {
|
||||||
|
// Check if the clicked element is a person or place link within the quickfilter
|
||||||
|
const quickfilterTarget = event.target.closest('a[href^="/akteure/"], a[href^="/ort/"]');
|
||||||
|
const filterContainer = document.getElementById('filter-container');
|
||||||
|
|
||||||
|
if (quickfilterTarget && filterContainer && filterContainer.contains(quickfilterTarget)) {
|
||||||
|
// Dispatch custom event to notify components
|
||||||
|
const selectionEvent = new CustomEvent('quickfilter:selection', {
|
||||||
|
detail: {
|
||||||
|
type: quickfilterTarget.getAttribute('href').startsWith('/akteure/') ? 'person' : 'place',
|
||||||
|
source: 'quickfilter',
|
||||||
|
id: quickfilterTarget.getAttribute('href').split('/').pop(),
|
||||||
|
url: quickfilterTarget.getAttribute('href')
|
||||||
|
},
|
||||||
|
bubbles: true
|
||||||
|
});
|
||||||
|
|
||||||
|
document.dispatchEvent(selectionEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user