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" . }} {{ block "_footer" . }}
{{ end }} {{ end }}
<!-- Scroll to Top Button -->
<scroll-to-top-button></scroll-to-top-button>
{{ EmbedXSLT "xslt/transform-citation.xsl" }} {{ EmbedXSLT "xslt/transform-citation.xsl" }}
</body> </body>
</html> </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>
</div> </div>
{{ else }} {{ else }}
<div class="max-w-full mx-auto px-8 py-8"> <div class="max-w-6xl mx-auto px-8 py-8">
<div class="mb-8"> <div class="bg-white px-6 py-6 rounded">
{{ $letter := Upper (FirstLetter $agent.ID) }} <div class="mb-6">
<a href="/akteure/{{ $letter }}" class="inline-flex items-center text-blue-600 {{ $letter := Upper (FirstLetter $agent.ID) }}
hover:text-blue-800 transition-colors text-xl"> <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"></i> <i class="ri-arrow-left-line mr-3 text-xl font-bold"></i>
{{ $letter }} {{ $letter }}
</a> </a>
</div>
{{ template "_akteur" $agent }}
</div> </div>
<div>{{ template "_akteur" $agent }}</div>
</div> </div>
{{ end }} {{ end }}
{{ else }} {{ else }}
<div class="max-w-full mx-auto px-8 py-8"> <div class="max-w-full mx-auto px-8 py-8">
<div class="mb-10"> <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" }} {{ 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> <p class="text-gray-700 text-lg">Personen, die Beiträge in der Zeitung verfasst haben</p>
{{ else }} {{ else }}
<h1 class="text-4xl font-bold text-gray-900 mb-2">Personen & Körperschaften</h1> <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> <p class="text-gray-700 text-lg">Verzeichnis aller in der Zeitung erwähnten Personen und Institutionen</p>
{{ end }} {{ 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 --> <!-- Integrated checkbox into header -->
<div class="mb-10 p-6 bg-gray-50 rounded-lg"> <div class="flex items-center gap-4 mt-4">
<div class="flex flex-wrap gap-3"> <label class="inline-flex items-center">
{{ range $_, $l := .model.AvailableLetters }} <input type="checkbox"
<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"> 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"
{{ $l }} {{ if eq .model.Search "autoren" }}checked{{ end }}
</a> hx-get="{{ if eq .model.Search "autoren" }}/akteure/a{{ else }}/akteure/autoren{{ end }}"
{{ 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> </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> </div>
{{ template "_scrollspy_layout" .model }} {{ template "_scrollspy_layout" .model }}

View File

@@ -1,22 +1,23 @@
<div class="max-w-full mx-auto px-8 py-8"> <div class="max-w-full mx-auto px-8 py-8">
<div class="mb-10"> <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">
<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> <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"> <!-- Integrated checkbox into header -->
<label class="inline-flex items-center"> <div class="flex items-center gap-4 mt-4">
<input type="checkbox" <label class="inline-flex items-center">
class="form-checkbox h-5 w-5 text-blue-600 rounded" <input type="checkbox"
checked 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"
hx-get="/akteure/a" checked
hx-target="body" hx-get="/akteure/a"
hx-push-url="true"> hx-target="body"
<span class="ml-2 text-lg text-gray-700">Nur Autoren anzeigen</span> hx-push-url="true">
</label> <span class="ml-2 text-lg text-gray-700">Nur Autor:innen anzeigen</span>
</label>
</div>
</div> </div>
</div> </div>
{{ template "_scrollspy_layout" .model }} {{ template "_scrollspy_layout" .model }}
</div> </div>

View File

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

View File

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

View File

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

View File

@@ -1291,7 +1291,7 @@ function setup() {
setup_xslt(); 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) { document.body.addEventListener("htmx:afterSwap", function (event) {
setTimeout(() => { setTimeout(() => {
if (document.querySelector(".newspaper-page-container")) { if (document.querySelector(".newspaper-page-container")) {
@@ -1300,6 +1300,11 @@ function setup() {
if (document.querySelector(".author-section")) { if (document.querySelector(".author-section")) {
initializeScrollspy(); initializeScrollspy();
} }
// Reassess scroll-to-top button visibility after page swap
const scrollToTopButton = document.querySelector("scroll-to-top-button");
if (scrollToTopButton) {
scrollToTopButton.reassessScrollPosition();
}
}, 100); }, 100);
}); });
@@ -1311,6 +1316,11 @@ function setup() {
if (document.querySelector(".author-section")) { if (document.querySelector(".author-section")) {
initializeScrollspy(); initializeScrollspy();
} }
// Reassess scroll-to-top button visibility after page settle
const scrollToTopButton = document.querySelector("scroll-to-top-button");
if (scrollToTopButton) {
scrollToTopButton.reassessScrollPosition();
}
}, 200); }, 200);
}); });
@@ -1322,6 +1332,11 @@ function setup() {
if (document.querySelector(".author-section")) { if (document.querySelector(".author-section")) {
initializeScrollspy(); initializeScrollspy();
} }
// Reassess scroll-to-top button visibility after HTMX load
const scrollToTopButton = document.querySelector("scroll-to-top-button");
if (scrollToTopButton) {
scrollToTopButton.reassessScrollPosition();
}
}, 100); }, 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 }; export { setup };