finish person view

This commit is contained in:
Simon Martens
2025-09-21 19:25:50 +02:00
parent 94883b5edc
commit 810de82442
11 changed files with 550 additions and 382 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

BIN
views/assets/wikipedia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -41,6 +41,9 @@
{{ block "_footer" . }}
{{ end }}
<!-- Scroll to Top Button -->
<scroll-to-top-button></scroll-to-top-button>
{{ EmbedXSLT "xslt/transform-citation.xsl" }}
</body>
</html>

BIN
views/public/wikipedia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -10,53 +10,64 @@
</div>
</div>
{{ else }}
<div class="max-w-full mx-auto px-8 py-8">
<div class="mb-8">
{{ $letter := Upper (FirstLetter $agent.ID) }}
<a href="/akteure/{{ $letter }}" class="inline-flex items-center text-blue-600
hover:text-blue-800 transition-colors text-xl">
<i class="ri-arrow-left-line mr-3 text-xl"></i>
{{ $letter }}
</a>
<div class="max-w-6xl mx-auto px-8 py-8">
<div class="bg-white px-6 py-6 rounded">
<div class="mb-6">
{{ $letter := Upper (FirstLetter $agent.ID) }}
<a href="/akteure/{{ $letter }}" class="inline-flex items-center text-black hover:text-gray-700 transition-colors text-xl no-underline font-bold">
<i class="ri-arrow-left-line mr-3 text-xl font-bold"></i>
{{ $letter }}
</a>
</div>
{{ template "_akteur" $agent }}
</div>
<div>{{ template "_akteur" $agent }}</div>
</div>
{{ end }}
{{ else }}
<div class="max-w-full mx-auto px-8 py-8">
<div class="mb-10">
<div class="bg-slate-100 px-6 py-4 rounded-lg mb-6">
<div class="bg-white px-6 py-4 rounded mb-6">
{{ if eq .model.Search "autoren" }}
<h1 class="text-4xl font-bold text-gray-900 mb-2">Autoren</h1>
<h1 class="text-4xl font-bold text-gray-900 mb-2">Autor:innen</h1>
<p class="text-gray-700 text-lg">Personen, die Beiträge in der Zeitung verfasst haben</p>
{{ else }}
<h1 class="text-4xl font-bold text-gray-900 mb-2">Personen & Körperschaften</h1>
<p class="text-gray-700 text-lg">Verzeichnis aller in der Zeitung erwähnten Personen und Institutionen</p>
{{ end }}
</div>
<div class="flex items-center gap-4 mb-6">
<label class="inline-flex items-center">
<input type="checkbox"
class="form-checkbox h-5 w-5 text-blue-600 rounded"
{{ if eq .model.Search "autoren" }}checked{{ end }}
hx-get="{{ if eq .model.Search "autoren" }}/akteure/a{{ else }}/akteure/autoren{{ end }}"
hx-target="body"
hx-push-url="true">
<span class="ml-2 text-lg text-gray-700">Nur Autoren anzeigen</span>
</label>
</div>
</div>
<!-- Alphabet Navigation -->
<div class="mb-10 p-6 bg-gray-50 rounded-lg">
<div class="flex flex-wrap gap-3">
{{ range $_, $l := .model.AvailableLetters }}
<a href="/akteure/{{ $l }}" class="inline-flex items-center justify-center w-10 h-10 bg-white border border-gray-300 rounded hover:bg-blue-50 hover:border-blue-300 font-medium text-gray-700 hover:text-blue-700 transition-colors text-lg">
{{ $l }}
</a>
{{ end }}
<!-- Integrated checkbox into header -->
<div class="flex items-center gap-4 mt-4">
<label class="inline-flex items-center">
<input type="checkbox"
class="form-checkbox h-5 w-5 text-red-600 focus:ring-red-500 focus:border-red-500 checked:bg-red-600 checked:border-red-600 rounded"
{{ if eq .model.Search "autoren" }}checked{{ end }}
hx-get="{{ if eq .model.Search "autoren" }}/akteure/a{{ else }}/akteure/autoren{{ end }}"
hx-target="body"
hx-push-url="true">
<span class="ml-2 text-lg text-gray-700">Nur Autor:innen anzeigen</span>
</label>
</div>
</div>
<!-- Alphabet Navigation - styled like year selector -->
{{ if ne .model.Search "autoren" }}
<div class="mb-6 w-full">
<div class="bg-white px-6 py-4 rounded">
<div class="mx-auto flex flex-row flex-wrap gap-x-6 gap-y-3 w-fit items-end leading-none justify-center">
{{ range $_, $l := .model.AvailableLetters }}
{{ if eq $l (Upper $.model.Search) }}
<!-- This is the active letter -->
<span class="no-underline leading-none !m-0 !p-0 text-4xl font-bold text-red-600 pointer-events-none" aria-current="true">{{ $l }}</span>
{{ else }}
<!-- This is an inactive letter -->
<a href="/akteure/{{ $l }}" class="no-underline leading-none !m-0 !p-0 text-2xl font-medium text-gray-700 hover:text-red-600 transition-colors">{{ $l }}</a>
{{ end }}
{{ end }}
</div>
</div>
</div>
{{ end }}
</div>
{{ template "_scrollspy_layout" .model }}

View File

@@ -1,22 +1,23 @@
<div class="max-w-full mx-auto px-8 py-8">
<div class="mb-10">
<div class="bg-slate-100 px-6 py-4 rounded-lg mb-6">
<h1 class="text-4xl font-bold text-gray-900 mb-2">Autoren</h1>
<div class="bg-white px-6 py-4 rounded mb-6">
<h1 class="text-4xl font-bold text-gray-900 mb-2">Autor:innen</h1>
<p class="text-gray-700 text-lg">Personen, die Beiträge in der Zeitung verfasst haben</p>
</div>
<div class="flex items-center gap-4 mb-6">
<label class="inline-flex items-center">
<input type="checkbox"
class="form-checkbox h-5 w-5 text-blue-600 rounded"
checked
hx-get="/akteure/a"
hx-target="body"
hx-push-url="true">
<span class="ml-2 text-lg text-gray-700">Nur Autoren anzeigen</span>
</label>
<!-- Integrated checkbox into header -->
<div class="flex items-center gap-4 mt-4">
<label class="inline-flex items-center">
<input type="checkbox"
class="form-checkbox h-5 w-5 text-red-600 focus:ring-red-500 focus:border-red-500 checked:bg-red-600 checked:border-red-600 rounded"
checked
hx-get="/akteure/a"
hx-target="body"
hx-push-url="true">
<span class="ml-2 text-lg text-gray-700">Nur Autor:innen anzeigen</span>
</label>
</div>
</div>
</div>
{{ template "_scrollspy_layout" .model }}
</div>
</div>

View File

@@ -75,7 +75,7 @@
Ganzer Beitrag
</a>
</div>
{{- end -}}
{{- end }}
</div>
</div>
{{- end -}}

View File

@@ -5,10 +5,11 @@
<!-- Name and external links -->
<div class="flex items-start justify-between gap-4">
<div class="flex-1">
<!-- Large serif name - bold -->
<div class="text-xl font-serif font-bold mb-1">
<a href="/akteure/{{ $a.ID }}" class="hover:text-slate-900 transition-colors no-underline">
{{ index $a.Names 0 }}
<!-- Large serif name - bold with inline permalink icon -->
<div class="text-xl font-serif font-bold mb-1 flex items-center gap-2">
<span>{{ index $a.Names 0 }}</span>
<a href="/akteure/{{ $a.ID }}" class="text-gray-500 hover:text-blue-600 transition-colors text-lg no-underline" title="Permalink zu {{ index $a.Names 0 }}">
<i class="ri-link text-base"></i>
</a>
</div>
@@ -41,19 +42,19 @@
</div>
<!-- External link symbols on the right -->
<div class="flex gap-2 flex-shrink-0">
<div class="flex gap-3 flex-shrink-0 items-center">
{{- if ne $gnd nil -}}
{{- /* Wikipedia link if available */ -}}
{{- if ne (len $gnd.Wikipedia) 0 -}}
<a href="{{ (index $gnd.Wikipedia 0).ID }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="Wikipedia">
<i class="ri-wikipedia-line"></i>
<a href="{{ (index $gnd.Wikipedia 0).ID }}" target="_blank" class="hover:opacity-80 transition-opacity" title="Wikipedia">
<img src="/assets/wikipedia.png" alt="Wikipedia" class="w-6 h-6">
</a>
{{- end -}}
{{- /* GND link if available */ -}}
{{- if ne $a.GND "" -}}
<a href="{{ $a.GND }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="Gemeinsame Normdatei">
<i class="ri-links-line"></i>
<a href="{{ $a.GND }}" target="_blank" class="hover:opacity-80 transition-opacity" title="Gemeinsame Normdatei">
<img src="/assets/GND.png" alt="GND" class="w-6 h-6">
</a>
{{- else -}}
{{- /* VIAF link if no GND available */ -}}

View File

@@ -36,8 +36,8 @@
{{ $workPieces := LookupPieces $w.Item }}
{{ if len $workPieces }}
<div class="mt-1 text-lg">
{{- /* Group pieces by category and display inline */ -}}
{{- $groupedByCategory := dict -}}
{{- /* Group pieces by piece ID first to combine all categories per piece, then by additional authors */ -}}
{{- $pieceData := dict -}}
{{- range $_, $p := $workPieces -}}
{{- $categoryFlags := GetCategoryFlags $p.Item -}}
{{- $categories := slice -}}
@@ -138,16 +138,22 @@
{{- end -}}
{{- $sortedAdditionalAuthorIDs := sortStrings $pieceAdditionalAuthorIDs -}}
{{- /* Create grouping key with category + additional authors */ -}}
{{- $sortedCategories := sortStrings $categories -}}
{{- /* Store piece data by ID to combine categories and avoid duplicates */ -}}
{{- $pieceData = merge $pieceData (dict $p.Item.ID (dict "piece" $p "categories" $categories "additionalAuthorIDs" $sortedAdditionalAuthorIDs)) -}}
{{- end -}}
{{- /* Now group by combined categories and additional authors */ -}}
{{- $groupedByCategory := dict -}}
{{- range $pieceID, $data := $pieceData -}}
{{- $sortedCategories := sortStrings $data.categories -}}
{{- $categoryName := joinWithUnd $sortedCategories -}}
{{- $groupKey := printf "%s|%s" $categoryName (joinWithUnd $sortedAdditionalAuthorIDs) -}}
{{- $groupKey := printf "%s|%s" $categoryName (joinWithUnd $data.additionalAuthorIDs) -}}
{{- $existing := index $groupedByCategory $groupKey -}}
{{- if $existing -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $groupKey (append $existing $p)) -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $groupKey (append $existing $data.piece)) -}}
{{- else -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $groupKey (slice $p)) -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $groupKey (slice $data.piece)) -}}
{{- end -}}
{{- end -}}
@@ -213,8 +219,8 @@
{{- /* Process standalone work pieces that aren't linked to specific works */ -}}
{{- if ne (len $workPieces) 0 -}}
{{- /* Group standalone work pieces by category and additional authors */ -}}
{{- $standaloneGrouped := dict -}}
{{- /* Group standalone work pieces by piece ID first to combine categories, then by additional authors */ -}}
{{- $standalonePieceData := dict -}}
{{- range $_, $p := $workPieces -}}
{{- /* Skip pieces that are already covered by works above */ -}}
{{- $isPieceInWorks := false -}}
@@ -266,21 +272,27 @@
{{- end -}}
{{- $sortedAdditionalAuthorIDs := sortStrings $pieceAdditionalAuthorIDs -}}
{{- /* Create grouping key with category + additional authors */ -}}
{{- $sortedCategories := sortStrings $categories -}}
{{- $categoryName := joinWithUnd $sortedCategories -}}
{{- $groupKey := printf "%s|%s" $categoryName (joinWithUnd $sortedAdditionalAuthorIDs) -}}
{{- $existing := index $standaloneGrouped $groupKey -}}
{{- if $existing -}}
{{- $standaloneGrouped = merge $standaloneGrouped (dict $groupKey (append $existing $p)) -}}
{{- else -}}
{{- $standaloneGrouped = merge $standaloneGrouped (dict $groupKey (slice $p)) -}}
{{- end -}}
{{- /* Store piece data by ID to combine categories and avoid duplicates */ -}}
{{- $standalonePieceData = merge $standalonePieceData (dict $p.Item.ID (dict "piece" $p "categories" $categories "additionalAuthorIDs" $sortedAdditionalAuthorIDs)) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* Now group by combined categories and additional authors */ -}}
{{- $standaloneGrouped := dict -}}
{{- range $pieceID, $data := $standalonePieceData -}}
{{- $sortedCategories := sortStrings $data.categories -}}
{{- $categoryName := joinWithUnd $sortedCategories -}}
{{- $groupKey := printf "%s|%s" $categoryName (joinWithUnd $data.additionalAuthorIDs) -}}
{{- $existing := index $standaloneGrouped $groupKey -}}
{{- if $existing -}}
{{- $standaloneGrouped = merge $standaloneGrouped (dict $groupKey (append $existing $data.piece)) -}}
{{- else -}}
{{- $standaloneGrouped = merge $standaloneGrouped (dict $groupKey (slice $data.piece)) -}}
{{- end -}}
{{- end -}}
{{- /* Display standalone work pieces */ -}}
{{- range $groupKey, $categoryPieces := $standaloneGrouped -}}
<div class="mb-1.5 break-inside-avoid max-w-[95ch]">

View File

@@ -1291,7 +1291,7 @@ function setup() {
setup_xslt();
});
// HTMX event handling for newspaper layout and scrollspy
// HTMX event handling for newspaper layout, scrollspy, and scroll-to-top button
document.body.addEventListener("htmx:afterSwap", function (event) {
setTimeout(() => {
if (document.querySelector(".newspaper-page-container")) {
@@ -1300,6 +1300,11 @@ function setup() {
if (document.querySelector(".author-section")) {
initializeScrollspy();
}
// Reassess scroll-to-top button visibility after page swap
const scrollToTopButton = document.querySelector("scroll-to-top-button");
if (scrollToTopButton) {
scrollToTopButton.reassessScrollPosition();
}
}, 100);
});
@@ -1311,6 +1316,11 @@ function setup() {
if (document.querySelector(".author-section")) {
initializeScrollspy();
}
// Reassess scroll-to-top button visibility after page settle
const scrollToTopButton = document.querySelector("scroll-to-top-button");
if (scrollToTopButton) {
scrollToTopButton.reassessScrollPosition();
}
}, 200);
});
@@ -1322,6 +1332,11 @@ function setup() {
if (document.querySelector(".author-section")) {
initializeScrollspy();
}
// Reassess scroll-to-top button visibility after HTMX load
const scrollToTopButton = document.querySelector("scroll-to-top-button");
if (scrollToTopButton) {
scrollToTopButton.reassessScrollPosition();
}
}, 100);
});
}
@@ -1944,4 +1959,83 @@ window.addEventListener("beforeunload", function () {
}
});
// Scroll to Top Web Component
class ScrollToTopButton extends HTMLElement {
constructor() {
super();
this.isVisible = false;
this.scrollHandler = null;
}
connectedCallback() {
// Create the button without shadow DOM so Tailwind works
this.innerHTML = `
<button
id="scroll-to-top-btn"
class="fixed bottom-6 right-6 w-12 h-12 bg-gray-700 hover:bg-gray-800 text-gray-100 rounded-full shadow-lg hover:shadow-xl transition-all duration-300 flex items-center justify-center cursor-pointer z-50 opacity-0 pointer-events-none"
title="Nach oben scrollen"
onclick="this.closest('scroll-to-top-button').scrollToTop()">
<i class="ri-arrow-up-line text-xl font-bold"></i>
</button>
`;
// Set up scroll listener
this.scrollHandler = () => {
this.handleScroll();
};
window.addEventListener('scroll', this.scrollHandler);
// Initial check
this.handleScroll();
}
disconnectedCallback() {
// Clean up event listener
if (this.scrollHandler) {
window.removeEventListener('scroll', this.scrollHandler);
this.scrollHandler = null;
}
}
// Method to reassess scroll position (called after HTMX swaps)
reassessScrollPosition() {
// Small delay to ensure DOM is settled after HTMX swap
setTimeout(() => {
this.handleScroll();
}, 100);
}
handleScroll() {
const button = this.querySelector('#scroll-to-top-btn');
if (!button) return;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const viewportHeight = window.innerHeight;
const shouldShow = scrollTop > viewportHeight;
if (shouldShow && !this.isVisible) {
// Show button
this.isVisible = true;
button.classList.remove('opacity-0', 'pointer-events-none');
button.classList.add('opacity-100', 'pointer-events-auto');
} else if (!shouldShow && this.isVisible) {
// Hide button
this.isVisible = false;
button.classList.remove('opacity-100', 'pointer-events-auto');
button.classList.add('opacity-0', 'pointer-events-none');
}
}
scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
}
// Register the scroll to top component
customElements.define('scroll-to-top-button', ScrollToTopButton);
export { setup };