This commit is contained in:
Simon Martens
2025-09-25 15:04:16 +02:00
29 changed files with 2809 additions and 678 deletions

View File

@@ -9,8 +9,8 @@
</div>
</div>
{{ else }}
<div class="max-w-7xl mx-auto px-8 py-8">
<div class="bg-white px-6 py-6 rounded">
<div class="w-full max-w-7xl mx-auto px-8 py-8">
<div class="bg-white px-6 py-6 rounded w-full">
{{ template "_back_navigation" $agent }}
{{ template "_akteur" $agent }}
</div>

View File

@@ -8,7 +8,7 @@
{{ $pageCount := len $pages }}
<!-- Historical printing layout grid -->
<div class="grid grid-cols-2">
<div class="grid grid-cols-2 gap-x-2">
{{ template "_historical_layout" (dict "pages" $pages "pageCount" $pageCount "isBeilage" false "targetPage" $.targetPage) }}
</div>
{{ end }}
@@ -26,7 +26,7 @@
{{ $pageCount := len $beilagePages }}
<!-- Historical printing layout grid for Beilage -->
<div class="grid grid-cols-2">
<div class="grid grid-cols-2 gap-x-2">
{{ template "_historical_layout" (dict "pages" $beilagePages "pageCount" $pageCount "isBeilage" true "targetPage" $.targetPage) }}
</div>
</div>
@@ -117,6 +117,9 @@
{{ $hoverColor := "hover:border-slate-300" }}
{{ $bgColor := "bg-blue-50" }}
{{ $idPrefix := "page" }}
{{ if $isBeilage }}
{{ $idPrefix = "beilage" }}
{{ end }}
{{ if $isBeilage }}
{{ $borderColor = "border-amber-200" }}
@@ -127,11 +130,6 @@
<div class="newspaper-page-container pt-4" id="{{ $idPrefix }}-{{ $page.PageNumber }}" data-page-container="{{ $page.PageNumber }}" data-page-icon-type="{{ $page.PageIcon }}"{{ if $isBeilage }} data-beilage="true"{{ end }}>
<!-- Anchor for navigation -->
{{ if $isBeilage }}
<div id="beilage-{{ $page.PageNumber }}"></div>
{{ else }}
<div id="page-{{ $page.PageNumber }}"></div>
{{ end }}
<!-- Page indicator row -->
<div class="flex {{ if $isLeft }}justify-end{{ else }}justify-start{{ end }} items-center gap-1 mb-2">
{{ if $isLeft }}

View File

@@ -1,6 +1,5 @@
{{ $y := .model.Year }}
<div class="mt-6 w-full">
<div class="mx-auto flex flex-row gap-x-4 w-fit items-end leading-none">
{{ range $year := .model.AvailableYears }}
@@ -14,63 +13,6 @@
</div>
</div>
<!-- Page Jump Form -->
<div class="mt-8 w-full">
<div class="mx-auto text-center">
<h3 class="text-lg font-medium text-slate-700 mb-6">Direkt zu Seite springen</h3>
<form hx-post="/jump" hx-target="#jump-errors" hx-swap="innerHTML" hx-target-4*="#jump-errors" hx-target-5*="#jump-errors" hx-ext="response-targets" class="inline-flex items-center gap-3">
<!-- Year Selection -->
<div class="flex items-center gap-2">
<label for="jump-year" class="text-sm text-slate-600 whitespace-nowrap">Jahr</label>
<select id="jump-year" name="year" value="{{ $y }}" class="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">
{{ range $year := .model.AvailableYears }}
<option value="{{ $year }}" {{ if eq $year $y }}selected{{ end }}>{{ $year }}</option>
{{ end }}
</select>
</div>
<!-- Page Input -->
<div class="flex items-center gap-2">
<label for="jump-page" class="text-sm text-slate-600 whitespace-nowrap">Seite</label>
<input type="number" id="jump-page" name="page" min="1" placeholder="42" class="w-20 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">
</div>
<!-- Submit Button -->
<button type="submit" class="inline-flex items-center px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1">
<i class="ri-arrow-right-line mr-1"></i>
Springen
</button>
</form>
<!-- Error Display Area -->
<div id="jump-errors" class="mt-3 min-h-[1.5rem]"></div>
<!-- Instructions -->
<div class="mt-4 text-sm text-slate-500">
<p>Geben Sie Jahr und Seitenzahl ein, um direkt zur entsprechenden Ausgabe zu springen.</p>
</div>
</div>
</div>
<script>
// Clear errors when user starts typing
document.addEventListener('DOMContentLoaded', function() {
const errorContainer = document.getElementById('jump-errors');
const yearSelect = document.getElementById('jump-year');
const pageInput = document.getElementById('jump-page');
function clearErrors() {
if (errorContainer) {
errorContainer.innerHTML = '';
}
}
if (yearSelect) yearSelect.addEventListener('change', clearErrors);
if (pageInput) pageInput.addEventListener('input', clearErrors);
});
</script>
<div class="grid grid-cols-11 gap-x-2 gap-y-4 pt-8">
{{ range $index, $month := .model.Issues }}

View 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 -}}

View File

@@ -0,0 +1,238 @@
<div class="container-normal">
<div class="text [&_*]:!indent-0 border px-8 py-7">
<h1 class="!mt-0">Impressum</h1>
<div>
<p>
<strong>Telemedienanbieter im Sinne des § 5 TMG:</strong><br />Theodor Springmann
Stiftung<br />Hirschgasse 2<br />69120 Heidelberg<br /><br />Telefon +49 6221 436235<br />Email&nbsp;<a
href="mailto:info@theodor-springmann-stiftung.de"
>info@theodor-springmann-stiftung.de</a
>
</p>
<p>
<strong>Rechtsform und Sitz:</strong><br />Die Theodor Springmann Stiftung ist eine
rechtsfähige Stiftung bürgerlichen Rechts. Sitz der Stiftung ist Heidelberg.
</p>
<p>
<strong>Vorstand der Theodor Springmann Stiftung:</strong><br />Dr. Randolf Straky
(Präsident)
</p>
<p>
<strong>Geschäftsführung:</strong><br />Dr. Randolf Straky<br />Theodor Springmann
Stiftung<br />Hirschgasse 2<br />69120 Heidelberg
</p>
<p>
<strong>Zuständige Aufsichtsbehörde:</strong><br />Regierungspräsidium Karlsruhe<br />Schlossplatz
1-3<br />76131 Karlsruhe<br /><br /><a
href="https://rp.baden-wuerttemberg.de/Themen/Stiftung/Seiten/Ansprechpartner.aspx"
>Ansprechpartner</a
>
</p>
<p>
<strong>Redaktionelle Verantwortung nach § 55 Abs. 2 RStV:</strong><br />Janina Reibold
<br />Theodor Springmann Stiftung<br />Hirschgasse 2<br />69120 Heidelberg
</p>
</div>
2024-10-17 09:52:17.412Z
</div>
<div class="text">
<h1 class="mt-12">Datenschutzerklärung</h1>
<div class="prose">
<div>
<h2>Präambel</h2>
<p>
Diese Datenschutzerklärung informiert Sie über die Art, den Umfang und den Zweck der
personenbezogenen Daten, die im Rahmen dieser Onlinepräsenz von Ihnen erhoben und von uns
verarbeitet werden, sowie die Ihnen zustehenden Rechte.<br />Personenbezogene Daten sind
alle Informationen, die sich auf eine identifizierte oder identifizierbare natürliche
Person beziehen. Als identifizierbar wird eine natürliche Person angesehen, die direkt
oder indirekt identifiziert werden kann. Im Hinblick auf weitere verwendete
Begrifflichkeiten verweisen wir auf die Definitionen der Datenschutz-Grundverordnung
(DSGVO), Artikel 4.<br />Erfolgt die Verarbeitung personenbezogener Daten auf Grundlage
des Art. 6 Abs. 1 lit. f DSGVO, so besteht unser berechtigtes Interesse in der Erfüllung
unseres satzungsgemäßen Stiftungszwecks. Zweck der Stiftung ist es, in gemeinnütziger
Weise Wissenschaft und Kunst, Völkerverständigung und Entwicklungshilfe zu fördern und zum
menschlichen Selbstverständnis sowie zum Erkennen und Lindern strukturell bedingter Not
und Bedürftigkeit von Menschen in aller Welt beizutragen.
</p>
<h2>Verantwortlicher</h2>
<p>
Verantwortlich im Sinne von Art. 4 Abs. 7 DSGVO für die Verarbeitung personenbezogener
Daten ist:<br />Theodor Springmann Stiftung<br />Hirschgasse 2<br />69120 Heidelberg<br /><br /><a
href="mailto:info@theodor-springmann-stiftung.de"
>info@theodor-springmann-stiftung.de</a
>
</p>
<h2>Automatisch bei Nutzung unserer Onlinepräsenz erfasste Daten</h2>
<p>
Mit der Nutzung unserer Onlinepräsenz werden automatisch personenbezogene und allgemeine
Daten vom Nutzer übermittelt und von uns erfasst und gespeichert. Die von uns erhobenen
Daten umfassen:
</p>
<ol>
<li>
die IP-Adresse des ans Internet angeschlossenen Netzwerkgeräts (Computer oder Router des
Nutzers),
</li>
<li>den verwendeten Browsertyp und dessen Version,</li>
<li>das verwendete Betriebssystem und dessen Version,</li>
<li>die Internetseite, die den Nutzer auf unsere Onlinepräsenz verweist (Referrer),</li>
<li>die Unterwebseiten, die der Nutzer auf unserer Onlinepräsenz aufruft,</li>
<li>das Datum und die Uhrzeit des Aufrufs,</li>
<li>den Internet-Service-Provider des Nutzers,</li>
<li>sonstige ähnliche Daten.</li>
</ol>
<p>
Die Theodor Springmann Stiftung zieht aus diesen Daten keine Rückschlüsse auf die
betroffenen Nutzer. Diese Informationen werden benötigt, um
</p>
<ol>
<li>die vom Nutzer angeforderten Inhalte korrekt auszuliefern,</li>
<li>
technische Probleme zu diagnostizieren, unsere IT-Systeme vor Angriffen zu schützen und
im Falle eines Angriffs den zuständigen Behörden notwendige Informationen zur
Strafverfolgung bereitstellen zu können,
</li>
<li>die Inhalte und die Bedienung unserer Onlinepräsenz zu optimieren.</li>
</ol>
<p>
Die oben genannten Verarbeitungszwecke werden von Subsystemen erfüllt, die unabhängig von
anderen Subsystemen ihre jeweils eigene Kopie der vom Nutzer erhobenen Daten erhalten,
verarbeiten und löschen. Dabei werden den Subsystemen lediglich die zur Ausführung ihrer
Aufgabe erforderlichen Daten übergeben. Alle Subsysteme befinden sich auf Servern der
Theodor Springmann Stiftung.
</p>
<h3>Auslieferung der vom Nutzer angeforderten Inhalte</h3>
<p>
Mit der Anforderung einer Seite dieser Onlinepräsenz übermittelt der Browser des Nutzers
automatisch die IP-Adresse seines ans Internet angeschlossenen Netzwerkgeräts (Computer
oder Router) und andere Daten. Ohne diese IP-Adresse können die Inhalte dieser
Online-Präsenz nicht an den Nutzer zurückgesendet werden. Daher ist die Speicherung und
Verarbeitung der IP-Adresse für den Betrieb dieser Onlinepräsenz notwendig. Ebenfalls
übermittelte Daten über den verwendeten Browser usw. werden gegebenenfalls verwendet, um
die Anzeige der Webseite an das Anzeigegerät oder den Browser anzupassen. Die zum Zwecke
der Auslieferung von Inhalten gesammelten Daten werden direkt nach Übertragung der Inhalte
vom Subsystem gelöscht. Die Verarbeitung dieser Daten erfolgt auf der Grundlage eines
berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO.
</p>
<h3>Diagnose und Schutz der IT-Systeme</h3>
<p>
Daneben erfolgt eine Speicherung der oben genannten Daten in Diagnose-Protokollen. Die
Protokollierung ist notwendig, um etwaige technische Probleme analysieren oder Angriffe
erkennen und abwehren zu können. Bei Angriffen auf unsere IT-Systeme übergeben wir die
Diagnose-Protokolle den zuständigen Strafverfolgungsbehörden. Die Diagnose-Protokolle
werden nach 14 Tagen automatisch gelöscht. Die Speicherung und Verarbeitung dieser Daten
erfolgt auf Grundlage eines berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO.
</p>
<h3>Analyse zur Optimierung der Inhalte und der Bedienung</h3>
<p>
Die oben genannten Daten werden von einem Webanalyseprogramm verarbeitet. Dabei werden die
Daten pseudonymisiert und aggregiert, so dass sie einzelnen Nutzern nicht mehr zugeordnet
werden können. Das Analyseprogramm wird auf einem von uns betriebenen Server ausgeführt,
daher werden keine Daten an Dritte übermittelt. Der Zweck dieser Erfassung besteht in der
Analyse der Nutzung unseres Angebots, durch die eine Verbesserung unserer Webseiten, der
Inhalte und der Bedienung ermöglicht wird. Das Analyseprogramm speichert die
anonymisierten Daten ohne zeitliche Begrenzung. Die Verarbeitung dieser Daten erfolgt auf
der Grundlage eines berechtigten Interesses (Art. 6 Abs. 1 lit. f DSGVO).
</p>
<h2>Sonstige Daten</h2>
<p>
Sämtliche Daten, die mit dem Aufruf einer Webseite von unserer Onlinepräsenz an den Nutzer
übertragen werden, werden von unseren Servern bereitgestellt. Wir nutzen kein von Dritten
bereitgestelltes Content Delivery Network (CDN), um Teile unserer Onlinepräsenz (z. B.
Javascript- oder Webfont-Dateien) von dort an den Nutzer zu übermitteln. Wir verlinken
auch nicht in soziale Netzwerke. Insofern können von Dritten keine personenbezogenen Daten
über unsere Onlinepräsenz erhoben werden, es sei denn, die Datenerfassung durch Dritte
basiert auf einer gesetzlichen Grundlage und/oder wurde behördlich angeordnet.<br />In
unseren Inhalten können sich jedoch Hyperlinks („Links“) auf fremde Onlineangebote
befinden. Mit dem Anklicken eines solchen Links verlässt der Nutzer unsere Onlinepräsenz
und damit den Geltungsbereich dieser Datenschutzerklärung.
</p>
<h2>Erhobene Daten bei Kontakt</h2>
<p>
Unsere Onlinepräsenz hält unter anderem aufgrund gesetzlicher Vorgaben verschiedene
Möglichkeiten bereit, mit uns in Kontakt zu treten. Sofern eine betroffene Person Kontakt
zu uns aufnimmt, werden die von der betroffenen Person übermittelten personenbezogenen
Daten automatisch gespeichert (z. B. Telefonnummer, E-Mail-Adresse). Diese Angaben werden
zum Zwecke der Bearbeitung sowie für sich möglicherweise anschließende Kommunikation von
uns gespeichert. Sobald die Speicherung dieser Daten nicht mehr erforderlich ist und keine
gesetzlichen Archivierungsgründe vorliegen, werden die Daten gelöscht. Die
Erforderlichkeit wird alle zwei Jahre überprüft. Es erfolgt keine Weitergabe dieser
personenbezogenen Daten an Dritte.<br />Von betroffenen Personen übermittelte
Informationen zur Bearbeitung einer Kontaktanfrage werden gemäß Art. 6 Abs. 1 lit. b
(vertragliche und vorvertragliche Beziehungen) oder lit. f (andere Anfragen) der DSGVO
verarbeitet.
</p>
<h2>Speicherung und Löschung von personenbezogenen Daten</h2>
<p>
Personenbezogene Daten werden von uns für die Dauer der entsprechenden gesetzlichen
Aufbewahrungspflichten oder für den zur Vertragserfüllung erforderlichen Zeitraum
gespeichert. Liegen solche Gründe nicht vor, werden personenbezogene Daten für den
Zeitraum, der zur Erreichung des Speicherzwecks erforderlich ist, verarbeitet und
gespeichert, sofern in dieser Datenschutzerklärung nicht ausdrücklich anderes angegeben
ist. Sobald die Daten für ihre Zweckbestimmung nicht mehr erforderlich sind oder der
Speicherungszweck entfällt, werden sie nach Maßgabe der gesetzlichen Vorschriften gelöscht
oder in ihrer Verarbeitung eingeschränkt.<br />Sollten die Daten nicht gelöscht werden
dürfen, weil sie einer gesetzlichen Aufbewahrungspflicht unterliegen, wird deren
Verarbeitung durch Archivierung eingeschränkt. Die Daten werden erst nach Ablauf der
gesetzlich vorgeschriebenen Speicherfrist gelöscht.<br />Alle sechs Monate wird
routinemäßig geprüft, ob der Speicherungszweck weggefallen bzw. die Aufbewahrungspflicht
abgelaufen ist. Anschließend wird gegebenenfalls die Löschung durchgeführt.
</p>
<h2>Rechte der betroffenen Personen</h2>
<p>
Unter den angegebenen Kontaktdaten können betroffene Personen jederzeit nachgenannte
Rechte ausüben. Eine betroffene Person kann von uns verlangen, dass
</p>
<ul>
<li>
Auskunft über sie betreffende, bei uns gespeicherte Daten und deren Verarbeitung erteilt
wird (Art. 15 DSGVO),
</li>
<li>
sie betreffende unrichtige personenbezogene Daten berichtigt werden (Art. 16 DSGVO),
</li>
<li>
sie betreffende, bei uns gespeicherte personenbezogene Daten gelöscht werden (Art. 17
DSGVO),
</li>
<li>
die Verarbeitung sie betreffender, bei uns gespeicherter Daten, die aufgrund
gesetzlicher Vorschriften oder anderer Gründe nicht gelöscht werden dürfen,
eingeschränkt wird (Art. 18 DSGVO),
</li>
<li>
sie betreffende Daten übertragbar sind, soweit sie in die Datenverarbeitung eingewilligt
oder einen Vertrag mit uns geschlossen hat (Art. 20 DSGVO),
</li>
<li>
sie betreffende Daten nach einem Widerspruch nicht weiter von uns verarbeitet werden
(Art. 21 DSGVO).
</li>
</ul>
<p>Eine betroffene Person hat ferner jederzeit das Recht,</p>
<ul>
<li>
eine erteilte Einwilligung in die Erhebung und Verarbeitung personenbezogener Daten nach
Art. 6 Abs. 1 lit. a oder Art. 9 Abs. 2 lit. a DSGVO für die Zukunft zu widerrufen (Art.
7 Abs. 3 DSGVO),
</li>
<li>
sich mit einer Beschwerde an eine Aufsichtsbehörde zu wenden (Art. 15 Abs. 1 lit. f
DSGVO). Eine Liste der Aufsichtsbehörden lässt sich unter&nbsp;<a
href="https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html"
>diesem Link</a
>&nbsp;abrufen.
</li>
</ul>
<h2>Änderung unserer Datenschutzerklärung</h2>
<p>
Wir behalten uns vor, diese Datenschutzerklärung ohne vorherige Ankündigung an neue
rechtliche oder technische Sachverhalte sowie an Änderungen unserer Leistungen oder
Prozesse anzupassen. Es gilt die jeweils auf der Onlinepräsenz veröffentlichte Version der
Datenschutzerklärung.
</p>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,167 @@
<div class="flex flex-row justify-center gap-4 h-96" id="filter">
<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-calendar-line text-slate-600"></i>
Auswahl nach Datum, Nummer od. Seite
</h3>
<!-- Unified Year Jump Filter -->
<year-jump-filter data-issues="{{ .IssuesByYearJSON }}">
<div class="space-y-3">
<!-- Year Selection -->
<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>
<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>
{{ range $year := .AvailableYears }}
<option value="{{ $year }}">{{ $year }}</option>
{{ end }}
</select>
</div>
<!-- Ausgabe Selection - Two Selects -->
<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 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>
</select>
<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>
</select>
</div>
<!-- OR Divider -->
<div class="flex items-center my-3">
<div class="flex-1 border-t border-slate-300"></div>
<span class="px-2 text-xs text-slate-500 bg-white">oder</span>
<div class="flex-1 border-t border-slate-300"></div>
</div>
<!-- Page Input -->
<div class="flex items-center gap-2">
<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 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>
<!-- Page Jump Button -->
<button id="page-jump-btn" type="button" disabled class="w-full inline-flex items-center justify-center px-3 py-2 bg-slate-400 text-white text-sm rounded transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-slate-300 focus:ring-offset-1 disabled:bg-slate-400 disabled:cursor-not-allowed enabled:bg-blue-600 enabled:hover:bg-blue-700 enabled:focus:ring-blue-500">
<i class="ri-arrow-right-line mr-2"></i>
Zur Seite springen
</button>
<!-- Error Display Area -->
<div id="jump-errors" class="mt-2 min-h-[1rem]"></div>
</div>
</year-jump-filter>
</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 justify-between">
<div class="flex items-center gap-2">
<i class="ri-user-line text-slate-600"></i>
Auswahl nach Person
</div>
<div class="flex items-center gap-1">
<i class="ri-arrow-right-line"></i>
<a href="/akteure/a" class="text-sm text-slate-600 hover:underline">Personen</a>
</div>
</h3>
<!-- Person Jump Filter -->
<person-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="person-search" class="hidden text-sm text-slate-600 w-16">Filter</label>
<input
type="text"
id="person-search"
placeholder="Name oder Lebensdaten 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 items-center gap-2">
<input
type="checkbox"
id="authors-only"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 focus:ring-2"
>
<label for="authors-only" class="text-sm text-slate-600">Nur Autor:innen anzeigen</label>
</div>
<div class="flex-1 min-h-0 overflow-y-auto border border-slate-300 rounded bg-white text-sm">
<!-- All Persons List -->
<div id="all-persons">
{{ range $person := .Persons }}
<div class="person-item odd:bg-slate-50 even:bg-white">
<a href="/akteure/{{ $person.ID }}" class="block px-2 py-1 hover:bg-blue-50 border-b border-slate-100 last:border-b-0">
<span class="person-name font-medium text-slate-800">{{ $person.Name }}</span>
{{ if $person.Life }}
<span class="person-life text-xs text-slate-500 ml-2">{{ $person.Life }}</span>
{{ end }}
</a>
</div>
{{ end }}
</div>
<!-- Authors Only List (initially hidden) -->
<div id="authors-only-list" style="display: none;">
{{ range $author := .Authors }}
<div class="person-item odd:bg-slate-50 even:bg-white">
<a href="/akteure/{{ $author.ID }}" class="block px-2 py-1 hover:bg-blue-50 border-b border-slate-100 last:border-b-0">
<span class="person-name font-medium text-slate-800">{{ $author.Name }}</span>
{{ if $author.Life }}
<span class="person-life text-xs text-slate-500 ml-2">{{ $author.Life }}</span>
{{ end }}
</a>
</div>
{{ end }}
</div>
</div>
</div>
</person-jump-filter>
</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 justify-between">
<div class="flex items-center gap-2">
<i class="ri-map-pin-line text-slate-600"></i>
Auswahl nach Ort
</div>
<div class="flex items-center gap-1">
<i class="ri-arrow-right-line"></i>
<a href="/ort/" class="text-sm text-slate-600 hover:underline">Orte</a>
</div>
</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>

View File

@@ -1,130 +1,155 @@
{{ $place := .place }}
{{ if $place }}
{{ $geonames := GetGeonames $place.Geo }}
{{ if .model.SelectedPlace }}
<!-- Single Place Detail View -->
<div class="max-w-7xl mx-auto px-8 py-8">
<div class="bg-white px-6 py-6 rounded w-full">
<!-- Back Navigation -->
<div class="mb-6">
<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>
Zurück zur Übersicht
</a>
</div>
<div class="container mx-auto px-4 py-8">
<!-- Place Header -->
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex-1">
<!-- Large serif name with permalink -->
<div class="text-2xl font-serif font-bold mb-2 flex items-center gap-3">
<span>{{ index $place.Names 0 }}</span>
<a href="/ort/{{ $place.ID }}" class="text-gray-500 hover:text-blue-600 transition-colors no-underline" title="Permalink zu {{ index $place.Names 0 }}">
<i class="ri-link text-lg"></i>
</a>
<!-- Place Header -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-slate-800 mb-2">
{{ if .model.SelectedPlace.Place.Names }}
{{ index .model.SelectedPlace.Place.Names 0 }}
{{ else }}
{{ .model.SelectedPlace.Place.ID }}
{{ end }}
</h1>
{{ if .model.SelectedPlace.Place.Geo }}
<p class="text-slate-600">
<i class="ri-map-pin-line mr-1"></i>
<a href="{{ .model.SelectedPlace.Place.Geo }}" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-700 underline">
Geonames
</a>
</p>
{{ end }}
</div>
<!-- Associated Pieces -->
<div>
<h2 class="text-xl font-semibold text-slate-800 mb-4">
<i class="ri-newspaper-line mr-2"></i><u class="decoration underline-offset-3">Beiträge</u> ({{ len .model.SelectedPlace.Pieces }})
</h2>
{{ if .model.SelectedPlace.Pieces }}
<div class="space-y-2">
{{- /* Group pieces by their own title/incipit */ -}}
{{- $groupedPieces := dict -}}
{{- range $_, $p := .model.SelectedPlace.Pieces -}}
{{- $groupKey := "" -}}
{{- if $p.Title -}}
{{- $groupKey = index $p.Title 0 -}}
{{- else if $p.Incipit -}}
{{- $groupKey = index $p.Incipit 0 -}}
{{- else -}}
{{- $groupKey = printf "untitled-%s" $p.ID -}}
{{- end -}}
{{- $existing := index $groupedPieces $groupKey -}}
{{- if $existing -}}
{{- $groupedPieces = merge $groupedPieces (dict $groupKey (append $existing $p)) -}}
{{- else -}}
{{- $groupedPieces = merge $groupedPieces (dict $groupKey (slice $p)) -}}
{{- end -}}
{{- 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>
</div>
{{- end }}
</div>
</div>
{{- end -}}
</div>
</div>
{{ else }}
<p class="text-slate-500 italic">Keine Beiträge für diesen Ort gefunden.</p>
{{ end }}
</div>
</div>
</div>
{{ else }}
<!-- Places Overview -->
<div class="max-w-7xl mx-auto px-8 py-8">
<div class="bg-white px-6 py-6 rounded w-full">
<h1 class="text-3xl font-bold text-slate-800 mb-8">Orte</h1>
<!-- Available Letters Navigation -->
{{ if .model.AvailableLetters }}
<div class="mb-8">
<h2 class="text-lg font-semibold text-slate-700 mb-4">Nach Anfangsbuchstabe</h2>
<div class="flex flex-wrap gap-2">
{{ range $letter := .model.AvailableLetters }}
<span class="px-3 py-2 bg-slate-100 text-slate-700 rounded text-sm">
{{ $letter }}
</span>
{{ end }}
</div>
</div>
{{ end }}
<!-- Geographic Information from Geonames -->
{{ if ne $geonames nil }}
<div class="text-lg text-gray-800 mb-4">
<!-- Country and Administrative Info -->
{{ if ne $geonames.CountryName "" }}
<div class="mb-2">
{{ $geonames.CountryName }}
{{ if ne $geonames.AdminName1 "" }}
, {{ $geonames.AdminName1 }}
{{ end }}
{{ if and (ne $geonames.AdminName2 "") (ne $geonames.AdminName2 $geonames.AdminName1) }}
, {{ $geonames.AdminName2 }}
{{ end }}
</div>
{{ end }}
<!-- Places List -->
{{ if .model.Places }}
<div>
<h2 class="text-lg font-semibold text-slate-700 mb-4">
Alle Orte ({{ len .model.Places }})
</h2>
<!-- Coordinates -->
{{ if and (ne $geonames.Lat "") (ne $geonames.Lng "") }}
<div class="text-gray-600 text-base">
<i class="ri-map-pin-line"></i> {{ $geonames.Lat }}, {{ $geonames.Lng }}
</div>
{{ end }}
<!-- Population -->
{{ if gt $geonames.Population 0 }}
<div class="text-gray-600 text-base">
<i class="ri-group-line"></i> {{ $geonames.Population }} Einwohner
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{{ range $placeID := .model.Sorted }}
{{ $place := index $.model.Places $placeID }}
<div class="border border-slate-200 rounded-lg hover:bg-slate-50">
<a href="/ort/{{ $place.ID }}" class="block p-4">
<h3 class="font-medium text-slate-800 mb-1">
{{ if $place.Names }}
{{ index $place.Names 0 }}
{{ else }}
{{ $place.ID }}
{{ end }}
</h3>
{{ if $place.Geo }}
<p class="text-sm text-slate-600">
<i class="ri-map-pin-line mr-1"></i>
{{ $place.Geo }}
</p>
{{ end }}
</a>
</div>
{{ end }}
</div>
{{ end }}
</div>
<!-- External link symbols on the right -->
<div class="flex gap-3 flex-shrink-0 items-center">
{{ if ne $geonames nil }}
<!-- Wikipedia link if available -->
{{ if ne $geonames.WikipediaURL "" }}
<a href="https://{{ $geonames.WikipediaURL }}" target="_blank" class="hover:opacity-80 transition-opacity" title="Wikipedia">
<img src="/assets/wikipedia.png" alt="Wikipedia" class="w-6 h-6">
</a>
{{ end }}
<!-- Geonames link -->
{{ if ne $place.Geo "" }}
<a href="{{ $place.Geo }}" target="_blank" class="hover:opacity-80 transition-opacity" title="Geonames">
<i class="ri-global-line text-2xl text-blue-600"></i>
</a>
{{ end }}
{{ end }}
</div>
</div>
<!-- Additional place details -->
{{ if ne $geonames nil }}
<div class="bg-gray-50 rounded-lg p-6">
<h3 class="text-lg font-semibold mb-4">Geografische Details</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{{ if ne $geonames.Timezone.TimeZoneId "" }}
<div>
<strong>Zeitzone:</strong> {{ $geonames.Timezone.TimeZoneId }}
</div>
{{ end }}
{{ if ne $geonames.FcodeName "" }}
<div>
<strong>Typ:</strong> {{ $geonames.FcodeName }}
</div>
{{ end }}
{{ if ne (len $geonames.AlternateNames) 0 }}
<div class="md:col-span-2">
<strong>Alternative Namen:</strong>
<div class="flex flex-wrap gap-2 mt-2">
{{ range $i, $altName := $geonames.AlternateNames }}
{{ if lt $i 10 }}
{{ if ne $altName.Name "" }}
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-md text-sm">
{{ $altName.Name }}
{{ if ne $altName.Lang "" }}
<span class="text-blue-600">({{ $altName.Lang }})</span>
{{ end }}
</span>
{{ end }}
{{ end }}
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
{{ end }}
<!-- Back Navigation -->
<div class="mt-8 pt-6 border-t border-gray-200">
<a href="javascript:history.back()" class="inline-flex items-center gap-2 text-blue-600 hover:text-blue-800 transition-colors">
<i class="ri-arrow-left-line"></i>
Zur<75>ck
</a>
{{ else }}
<p class="text-slate-500 italic">Keine Orte gefunden.</p>
{{ end }}
</div>
</div>
{{ else }}
<div class="container mx-auto px-4 py-8">
<div class="text-center">
<h1 class="text-2xl font-bold text-gray-800 mb-4">Ort nicht gefunden</h1>
<p class="text-gray-600 mb-6">Der angeforderte Ort existiert nicht in unserer Datenbank.</p>
<a href="javascript:history.back()" class="inline-flex items-center gap-2 text-blue-600 hover:text-blue-800 transition-colors">
<i class="ri-arrow-left-line"></i>
Zur<75>ck
</a>
</div>
</div>
{{ end }}
{{ end }}