Refined orte

This commit is contained in:
Simon Martens
2025-09-27 17:44:34 +02:00
parent d48bba8e92
commit e8ff6d3d37
20 changed files with 882 additions and 906 deletions

View File

@@ -221,6 +221,178 @@ func (k *KGPZ) Funcs() map[string]interface{} {
e["contains"] = func(s, substr string) bool { return strings.Contains(s, substr) }
e["lower"] = func(s string) string { return strings.ToLower(s) }
// Place helper functions
e["GetModernCountryName"] = func(geoID string) string {
if geoID == "" || k.Geonames == nil {
return ""
}
geoPlace := k.Geonames.Place(geoID)
if geoPlace == nil {
return ""
}
// Map country names to German translations
switch geoPlace.CountryName {
case "France":
return "heutiges Frankreich"
case "United Kingdom":
return "heutiges Großbritannien"
case "Russia":
return "heutiges Russland"
case "Czech Republic", "Czechia":
return "heutiges Tschechien"
case "Netherlands", "The Netherlands":
return "heutige Niederlande"
case "Poland":
return "heutiges Polen"
case "Switzerland":
return "heutige Schweiz"
case "Latvia":
return "heutiges Lettland"
case "Sweden":
return "heutiges Schweden"
case "Austria":
return "heutiges Österreich"
case "Belgium":
return "heutiges Belgien"
case "Slovakia":
return "heutige Slowakei"
case "Finland":
return "heutiges Finnland"
case "Denmark":
return "heutiges Dänemark"
default:
// Return original country name for unknown countries (excluding Germany)
if geoPlace.CountryName != "Germany" && geoPlace.CountryName != "" {
return geoPlace.CountryName
}
return ""
}
}
e["GetFullPlaceInfo"] = func(geoID string, originalName string) string {
if geoID == "" || k.Geonames == nil {
return ""
}
geoPlace := k.Geonames.Place(geoID)
if geoPlace == nil {
return ""
}
// Only show info for places outside Germany
if geoPlace.CountryName == "Germany" || geoPlace.CountryName == "" {
return ""
}
// Get the modern country name
countryName := ""
switch geoPlace.CountryName {
case "France":
countryName = "heutiges Frankreich"
case "United Kingdom":
countryName = "heutiges Großbritannien"
case "Russia":
countryName = "heutiges Russland"
case "Czech Republic", "Czechia":
countryName = "heutiges Tschechien"
case "Netherlands", "The Netherlands":
countryName = "heutige Niederlande"
case "Poland":
countryName = "heutiges Polen"
case "Switzerland":
countryName = "heutige Schweiz"
case "Latvia":
countryName = "heutiges Lettland"
case "Sweden":
countryName = "heutiges Schweden"
case "Austria":
countryName = "heutiges Österreich"
case "Belgium":
countryName = "heutiges Belgien"
case "Slovakia":
countryName = "heutige Slowakei"
case "Finland":
countryName = "heutiges Finnland"
case "Denmark":
countryName = "heutiges Dänemark"
default:
countryName = geoPlace.CountryName
}
// Extract German alternate name (same logic as GetModernPlaceName)
modernName := ""
hasGermanName := false
for _, altName := range geoPlace.AlternateNames {
if altName.Lang == "de" {
hasGermanName = true
if altName.IsPreferredName {
modernName = altName.Name
break
} else if modernName == "" {
modernName = altName.Name
}
}
}
if !hasGermanName {
modernName = geoPlace.ToponymName
}
// Combine country and modern place name
result := countryName
if modernName != "" && strings.ToLower(modernName) != strings.ToLower(originalName) {
result += ", " + modernName
}
return result
}
e["GetModernPlaceName"] = func(geoID string, originalName string) string {
if geoID == "" || k.Geonames == nil {
return ""
}
geoPlace := k.Geonames.Place(geoID)
if geoPlace == nil {
return ""
}
// Only show modern names for places outside Germany
if geoPlace.CountryName == "Germany" || geoPlace.CountryName == "" {
return ""
}
// Extract German alternate name
modernName := ""
hasGermanName := false
for _, altName := range geoPlace.AlternateNames {
if altName.Lang == "de" {
hasGermanName = true
if altName.IsPreferredName {
modernName = altName.Name
break
} else if modernName == "" {
modernName = altName.Name
}
}
}
if !hasGermanName {
modernName = geoPlace.ToponymName
}
// Only return if it's different from the original name
if modernName != "" && strings.ToLower(modernName) != strings.ToLower(originalName) {
return modernName
}
return ""
}
e["LookupPieces"] = k.Library.Pieces.ReverseLookup
e["LookupWorks"] = k.Library.Works.ReverseLookup
e["LookupIssues"] = k.Library.Issues.ReverseLookup
@@ -263,6 +435,126 @@ func (k *KGPZ) Enrich() error {
return nil
}
// EnrichAndRebuildIndex ensures enrichment completes before rebuilding search index
func (k *KGPZ) EnrichAndRebuildIndex() error {
if k.Library == nil || k.Library.Agents == nil {
return nil
}
go func() {
k.fsmu.Lock()
defer k.fsmu.Unlock()
logging.Info("Starting enrichment process...")
// Fetch GND data for agents
data := xmlmodels.AgentsIntoDataset(k.Library.Agents)
k.GND.FetchPersons(data)
k.GND.WriteCache(filepath.Join(k.Config.BaseDIR, k.Config.GNDPath))
// Fetch Geonames data for places
if k.Library.Places != nil {
placeData := xmlmodels.PlacesIntoDataset(k.Library.Places)
k.Geonames.FetchPlaces(placeData)
k.Geonames.WriteCache(filepath.Join(k.Config.BaseDIR, k.Config.GeoPath))
}
logging.Info("Enrichment complete. Starting search index rebuild...")
// Clear existing indices before rebuilding
k.ClearSearchIndices()
// Rebuild search index after enrichment is complete
k.buildSearchIndexSync()
}()
return nil
}
// ClearSearchIndices removes all existing search indices
func (k *KGPZ) ClearSearchIndices() error {
if k.Search == nil {
return nil
}
return k.Search.ClearAllIndices()
}
// buildSearchIndexSync builds the search index synchronously (no goroutine)
func (k *KGPZ) buildSearchIndexSync() error {
if k.Library == nil || k.Library.Agents == nil || k.Search == nil {
return nil
}
wg := new(sync.WaitGroup)
wg.Add(6)
go func() {
for _, agent := range k.Library.Agents.Array {
err := k.Search.Index(agent, k.Library)
if err != nil {
logging.Error(err, "Error indexing agent")
}
}
wg.Done()
}()
go func() {
for _, place := range k.Library.Places.Array {
err := k.Search.Index(place, k.Library)
if err != nil {
logging.Error(err, "Error indexing place")
}
}
wg.Done()
}()
go func() {
for _, cat := range k.Library.Categories.Array {
err := k.Search.Index(cat, k.Library)
if err != nil {
logging.Error(err, "Error indexing category")
}
}
wg.Done()
}()
go func() {
for _, work := range k.Library.Works.Array {
err := k.Search.Index(work, k.Library)
if err != nil {
logging.Error(err, "Error indexing work")
}
}
wg.Done()
}()
go func() {
for _, issue := range k.Library.Issues.Array {
err := k.Search.Index(issue, k.Library)
if err != nil {
logging.Error(err, "Error indexing issue")
}
}
wg.Done()
}()
go func() {
for _, piece := range k.Library.Pieces.Array {
err := k.Search.Index(piece, k.Library)
if err != nil {
logging.Error(err, "Error indexing piece")
}
}
wg.Done()
}()
wg.Wait()
logging.Info("Search index built.")
return nil
}
func (k *KGPZ) BuildSearchIndex() error {
if k.Library == nil || k.Library.Agents == nil || k.Search == nil {
return nil
@@ -271,71 +563,7 @@ func (k *KGPZ) BuildSearchIndex() error {
go func() {
k.fsmu.Lock()
defer k.fsmu.Unlock()
wg := new(sync.WaitGroup)
wg.Add(6)
go func() {
for _, agent := range k.Library.Agents.Array {
err := k.Search.Index(agent, k.Library)
if err != nil {
logging.Error(err, "Error indexing agent")
}
}
wg.Done()
}()
go func() {
for _, place := range k.Library.Places.Array {
err := k.Search.Index(place, k.Library)
if err != nil {
logging.Error(err, "Error indexing place")
}
}
wg.Done()
}()
go func() {
for _, cat := range k.Library.Categories.Array {
err := k.Search.Index(cat, k.Library)
if err != nil {
logging.Error(err, "Error indexing category")
}
}
wg.Done()
}()
go func() {
for _, work := range k.Library.Works.Array {
err := k.Search.Index(work, k.Library)
if err != nil {
logging.Error(err, "Error indexing work")
}
}
wg.Done()
}()
go func() {
for _, issue := range k.Library.Issues.Array {
err := k.Search.Index(issue, k.Library)
if err != nil {
logging.Error(err, "Error indexing issue")
}
}
wg.Done()
}()
go func() {
for _, piece := range k.Library.Pieces.Array {
err := k.Search.Index(piece, k.Library)
if err != nil {
logging.Error(err, "Error indexing piece")
}
}
wg.Done()
}()
wg.Wait()
logging.Info("Search index built.")
k.buildSearchIndexSync()
}()
return nil
}
@@ -380,8 +608,7 @@ func (k *KGPZ) Pull() {
if changed {
logging.ObjDebug(&k.Repo, "Remote changed. Reparsing")
k.Serialize()
k.Enrich()
k.BuildSearchIndex()
k.EnrichAndRebuildIndex()
}
}

View File

@@ -127,6 +127,7 @@ func GetQuickFilter(kgpz *xmlmodels.Library) fiber.Handler {
placeSummary := PlaceSummary{
ID: place.ID,
Name: name,
Geo: place.Geo,
}
places = append(places, placeSummary)
@@ -164,6 +165,7 @@ type PersonSummary struct {
type PlaceSummary struct {
ID string
Name string
Geo string
}
// IssueSummary represents an issue for the Jahr/Ausgabe filter

View File

@@ -33,8 +33,17 @@ func GetPlace(kgpz *xmlmodels.Library) fiber.Handler {
return c.SendStatus(fiber.StatusNotFound)
}
return c.Render("/ort/", fiber.Map{
"model": places,
})
// Render different templates based on whether we're showing list or detail view
if places.SelectedPlace != nil {
// Individual place detail view
return c.Render("/ort/detail/", fiber.Map{
"model": places,
})
} else {
// Places overview/list view
return c.Render("/ort/overview/", fiber.Map{
"model": places,
})
}
}
}

View File

@@ -2,6 +2,7 @@ package searchprovider
import (
"errors"
"os"
"path/filepath"
"sync"
@@ -161,3 +162,29 @@ func default_mapping() (*mapping.IndexMappingImpl, error) {
indexMapping.DefaultAnalyzer = "customNgramAnalyzer"
return indexMapping, nil
}
// ClearAllIndices closes and removes all search indices
func (sp *SearchProvider) ClearAllIndices() error {
// Close all open indices
sp.indeces.Range(func(key, value interface{}) bool {
if index, ok := value.(bleve.Index); ok {
index.Close()
}
return true
})
// Clear the sync.Map
sp.indeces = sync.Map{}
// Remove all .bleve directories from disk
files, err := filepath.Glob(filepath.Join(sp.basepath, "*.bleve"))
if err != nil {
return err
}
for _, file := range files {
os.RemoveAll(file)
}
return nil
}

View File

@@ -497,7 +497,7 @@ func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...
l = lay
} else {
if layout[0] == "clear" {
lay, err := template.New("clear").Parse(CLEAR_LAYOUT)
lay, err := template.New("clear").Funcs(e.FuncMap).Parse(CLEAR_LAYOUT)
if err != nil {
return err
}

View File

@@ -433,8 +433,8 @@ class O extends HTMLElement {
if (!o && !r) {
const c = n.querySelector("div:first-child");
if (c) {
const d = c.getBoundingClientRect(), u = d.top >= 0, g = d.bottom <= window.innerHeight;
u && g && (l = !0);
const d = c.getBoundingClientRect(), u = d.top >= 0, h = d.bottom <= window.innerHeight;
u && h && (l = !0);
}
}
l && e.push(s);
@@ -451,8 +451,8 @@ class O extends HTMLElement {
const n = document.getElementById("scrollspy-nav"), s = n.getBoundingClientRect();
let o = 1 / 0, r = -1 / 0;
t.forEach((c) => {
const d = c.getBoundingClientRect(), u = d.top - s.top + n.scrollTop, g = u + d.height;
o = Math.min(o, u), r = Math.max(r, g);
const d = c.getBoundingClientRect(), u = d.top - s.top + n.scrollTop, h = u + d.height;
o = Math.min(o, u), r = Math.max(r, h);
});
let l = r - o;
i.style.top = `${o}px`, i.style.height = `${l}px`, i.style.opacity = "1", setTimeout(() => this.ensureMarkerVisibility(), 100);
@@ -490,8 +490,8 @@ class O extends HTMLElement {
document.documentElement.offsetHeight
), s = window.innerHeight, o = n - s, r = o > 0 ? window.scrollY / o : 0, l = t.clientHeight, d = t.scrollHeight - l;
if (d > 0) {
const u = r * d, g = i.getBoundingClientRect(), p = t.getBoundingClientRect(), f = g.top - p.top + t.scrollTop, m = l / 2, q = f - m, v = 0.7, I = v * u + (1 - v) * q, y = Math.max(0, Math.min(d, I)), T = t.scrollTop;
Math.abs(y - T) > 10 && t.scrollTo({
const u = r * d, h = i.getBoundingClientRect(), p = t.getBoundingClientRect(), f = h.top - p.top + t.scrollTop, m = l / 2, I = f - m, v = 0.7, T = v * u + (1 - v) * I, y = Math.max(0, Math.min(d, T)), q = t.scrollTop;
Math.abs(y - q) > 10 && t.scrollTo({
top: y,
behavior: "smooth"
});
@@ -512,6 +512,59 @@ class O extends HTMLElement {
}
}
customElements.define("akteure-scrollspy", O);
class $ extends HTMLElement {
constructor() {
super(), this.searchInput = null, this.placeCards = [], this.countElement = null, this.debounceTimer = null, this.originalCount = 0;
}
connectedCallback() {
this.render(), this.setupEventListeners(), this.initializePlaceCards();
}
disconnectedCallback() {
this.cleanupEventListeners(), this.debounceTimer && clearTimeout(this.debounceTimer);
}
render() {
this.innerHTML = `
<div class="mb-6">
<input
type="text"
id="places-search"
placeholder="Ortsnamen eingeben..."
autocomplete="off"
class="w-full px-3 py-2 border border-slate-300 rounded-md text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400"
>
</div>
`;
}
setupEventListeners() {
this.searchInput = this.querySelector("#places-search"), this.searchInput && this.searchInput.addEventListener("input", this.handleSearchInput.bind(this));
}
cleanupEventListeners() {
this.searchInput && this.searchInput.removeEventListener("input", this.handleSearchInput.bind(this));
}
initializePlaceCards() {
const e = this.closest(".bg-white") || document;
this.placeCards = Array.from(e.querySelectorAll("[data-place-name]")), this.countElement = e.querySelector("[data-places-count]"), this.countElement && (this.originalCount = this.placeCards.length);
}
handleSearchInput(e) {
this.debounceTimer && clearTimeout(this.debounceTimer), this.debounceTimer = setTimeout(() => {
this.filterPlaces(e.target.value.trim());
}, 150);
}
filterPlaces(e) {
if (!this.placeCards.length) return;
const t = e.toLowerCase();
let i = 0;
this.placeCards.forEach((n) => {
var l, c;
const s = ((l = n.getAttribute("data-place-name")) == null ? void 0 : l.toLowerCase()) || "", o = ((c = n.getAttribute("data-modern-name")) == null ? void 0 : c.toLowerCase()) || "";
e === "" || s.includes(t) || o.includes(t) ? (n.style.display = "", i++) : n.style.display = "none";
}), this.updateCountDisplay(i, e);
}
updateCountDisplay(e, t) {
this.countElement && (t === "" ? this.countElement.textContent = `Alle Orte (${this.originalCount})` : e === 0 ? this.countElement.textContent = `Keine Orte gefunden für "${t}"` : this.countElement.textContent = `${e} von ${this.originalCount} Orten`);
}
}
customElements.define("places-filter", $);
class V extends HTMLElement {
constructor() {
super(), this.resizeObserver = null;
@@ -641,14 +694,14 @@ class V extends HTMLElement {
show(e, t, i, n = !1, s = 0, o = null, r = null, l = null) {
const c = this.querySelector("#single-page-image"), d = this.querySelector("#page-number"), u = this.querySelector("#page-icon");
this.querySelector("#page-indicator"), c.src = e, c.alt = t, this.currentPageNumber = i, this.currentIsBeilage = n, this.currentPartNumber = o;
let g;
let h;
if (l)
g = l;
h = l;
else {
const f = this.getIssueContext(i);
g = f ? `${f}, ${i}` : `${i}`;
h = f ? `${f}, ${i}` : `${i}`;
}
if (d.innerHTML = g, s && i === s) {
if (d.innerHTML = h, s && i === s) {
d.style.position = "relative";
const f = d.querySelector(".target-page-dot");
f && f.remove();
@@ -843,9 +896,9 @@ class V extends HTMLElement {
const d = c.textContent.trim(), u = d.match(/(\d{1,2}\.\d{1,2}\.\d{4}\s+Nr\.\s+\d+)/);
if (u)
return u[1];
const g = d.match(/(\d{4})\s+Nr\.\s+(\d+)/);
if (g)
return `${g[1]} Nr. ${g[2]}`;
const h = d.match(/(\d{4})\s+Nr\.\s+(\d+)/);
if (h)
return `${h[1]} Nr. ${h[2]}`;
}
}
const l = document.title.match(/(\d{4}).*Nr\.\s*(\d+)/);
@@ -874,7 +927,7 @@ window.addEventListener("beforeunload", function() {
const a = document.querySelector("single-page-viewer");
a && a.close();
});
class $ extends HTMLElement {
class R extends HTMLElement {
constructor() {
super(), this.isVisible = !1, this.scrollHandler = null, this.htmxAfterSwapHandler = null;
}
@@ -915,8 +968,8 @@ class $ extends HTMLElement {
});
}
}
customElements.define("scroll-to-top-button", $);
class R extends HTMLElement {
customElements.define("scroll-to-top-button", R);
class z extends HTMLElement {
constructor() {
super(), this.pageObserver = null, this.pageContainers = /* @__PURE__ */ new Map(), this.singlePageViewerActive = !1, this.singlePageViewerCurrentPage = null, this.boundHandleSinglePageViewer = this.handleSinglePageViewer.bind(this);
}
@@ -1001,7 +1054,7 @@ class R extends HTMLElement {
}
const o = t.getBoundingClientRect(), r = e.getBoundingClientRect();
if (!(r.top >= o.top && r.bottom <= o.bottom)) {
const c = t.scrollTop, d = r.top - o.top + c, u = o.height, g = r.height, p = d - (u - g) / 2;
const c = t.scrollTop, d = r.top - o.top + c, u = o.height, h = r.height, p = d - (u - h) / 2;
t.scrollTo({
top: Math.max(0, p),
behavior: "smooth"
@@ -1035,8 +1088,8 @@ class R extends HTMLElement {
this.pageObserver && (this.pageObserver.disconnect(), this.pageObserver = null), document.removeEventListener("singlepageviewer:opened", this.boundHandleSinglePageViewer), document.removeEventListener("singlepageviewer:closed", this.boundHandleSinglePageViewer), document.removeEventListener("singlepageviewer:pagechanged", this.boundHandleSinglePageViewer), this.pageContainers.clear();
}
}
customElements.define("inhaltsverzeichnis-scrollspy", R);
class z extends HTMLElement {
customElements.define("inhaltsverzeichnis-scrollspy", z);
class j extends HTMLElement {
constructor() {
super(), this.innerHTML = `
<div id="error-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center backdrop-blur-sm">
@@ -1084,11 +1137,11 @@ class z extends HTMLElement {
window.showErrorModal = (e) => this.show(e), window.closeErrorModal = () => this.close();
}
}
customElements.define("error-modal", z);
customElements.define("error-modal", j);
window.currentPageContainers = window.currentPageContainers || [];
window.currentActiveIndex = window.currentActiveIndex || 0;
window.pageObserver = window.pageObserver || null;
function j(a, e, t, i = null) {
function D(a, e, t, i = null) {
let n = document.querySelector("single-page-viewer");
n || (n = document.createElement("single-page-viewer"), document.body.appendChild(n));
const s = a.closest('[data-beilage="true"]') !== null, o = window.templateData && window.templateData.targetPage ? window.templateData.targetPage : 0, r = a.closest(".newspaper-page-container, .piece-page-container");
@@ -1097,8 +1150,8 @@ function j(a, e, t, i = null) {
l = r.getAttribute("data-page-icon-type"), r.querySelector(".part-number") && (l = "part-number");
const u = r.querySelector(".page-indicator");
if (u) {
const g = u.cloneNode(!0);
g.querySelectorAll("i").forEach((m) => m.remove()), g.querySelectorAll('[class*="target-page-dot"], .target-page-indicator').forEach((m) => m.remove()), c = g.textContent.trim();
const h = u.cloneNode(!0);
h.querySelectorAll("i").forEach((m) => m.remove()), h.querySelectorAll('[class*="target-page-dot"], .target-page-indicator').forEach((m) => m.remove()), c = h.textContent.trim();
}
}
n.show(a.src, a.alt, e, s, o, i, l, c);
@@ -1106,7 +1159,7 @@ function j(a, e, t, i = null) {
function E() {
document.getElementById("pageModal").classList.add("hidden");
}
function D() {
function F() {
if (window.pageObserver && (window.pageObserver.disconnect(), window.pageObserver = null), window.currentPageContainers = Array.from(document.querySelectorAll(".newspaper-page-container")), window.currentActiveIndex = 0, b(), document.querySelector(".newspaper-page-container")) {
let e = /* @__PURE__ */ new Set();
window.pageObserver = new IntersectionObserver(
@@ -1127,7 +1180,7 @@ function D() {
});
}
}
function F() {
function K() {
if (window.currentActiveIndex > 0) {
let a = -1;
const e = [];
@@ -1148,7 +1201,7 @@ function F() {
}, 100));
}
}
function K() {
function Z() {
if (window.currentActiveIndex < window.currentPageContainers.length - 1) {
let a = -1;
const e = [];
@@ -1169,8 +1222,8 @@ function K() {
}, 100));
}
}
function Z() {
if (P()) {
function W() {
if (C()) {
const e = document.querySelector("#newspaper-content .newspaper-page-container");
e && e.scrollIntoView({
block: "start"
@@ -1189,7 +1242,7 @@ function Z() {
}
}
}
function P() {
function C() {
const a = [];
window.currentPageContainers.forEach((e, t) => {
const i = e.getBoundingClientRect(), n = window.innerHeight, s = Math.max(i.top, 0), o = Math.min(i.bottom, n), r = Math.max(0, o - s), l = i.height;
@@ -1205,11 +1258,11 @@ function P() {
function b() {
const a = document.getElementById("prevPageBtn"), e = document.getElementById("nextPageBtn"), t = document.getElementById("beilageBtn");
if (a && (a.style.display = "flex", window.currentActiveIndex <= 0 ? (a.disabled = !0, a.classList.add("opacity-50", "cursor-not-allowed"), a.classList.remove("hover:bg-gray-200")) : (a.disabled = !1, a.classList.remove("opacity-50", "cursor-not-allowed"), a.classList.add("hover:bg-gray-200"))), e && (e.style.display = "flex", window.currentActiveIndex >= window.currentPageContainers.length - 1 ? (e.disabled = !0, e.classList.add("opacity-50", "cursor-not-allowed"), e.classList.remove("hover:bg-gray-200")) : (e.disabled = !1, e.classList.remove("opacity-50", "cursor-not-allowed"), e.classList.add("hover:bg-gray-200"))), t) {
const i = P(), n = t.querySelector("i");
const i = C(), n = t.querySelector("i");
i ? (t.title = "Zur Hauptausgabe", t.className = "w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 hover:text-gray-800 border border-gray-300 transition-colors duration-200 flex items-center justify-center cursor-pointer", n && (n.className = "ri-file-text-line text-lg lg:text-xl")) : (t.title = "Zu Beilage", t.className = "w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-amber-100 hover:bg-amber-200 text-amber-700 hover:text-amber-800 border border-amber-300 transition-colors duration-200 flex items-center justify-center cursor-pointer", n && (n.className = "ri-attachment-line text-lg lg:text-xl"));
}
}
function W() {
function J() {
const a = document.getElementById("shareLinkBtn");
let e = "";
if (window.currentActiveIndex !== void 0 && window.currentPageContainers && window.currentPageContainers[window.currentActiveIndex]) {
@@ -1227,48 +1280,48 @@ function W() {
function x(a, e) {
if (navigator.clipboard)
navigator.clipboard.writeText(a).then(() => {
h(e, "Link kopiert!");
g(e, "Link kopiert!");
}).catch((t) => {
h(e, "Kopieren fehlgeschlagen");
g(e, "Kopieren fehlgeschlagen");
});
else {
const t = document.createElement("textarea");
t.value = a, document.body.appendChild(t), t.select();
try {
const i = document.execCommand("copy");
h(e, i ? "Link kopiert!" : "Kopieren fehlgeschlagen");
g(e, i ? "Link kopiert!" : "Kopieren fehlgeschlagen");
} catch {
h(e, "Kopieren fehlgeschlagen");
g(e, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(t);
}
}
}
function J() {
function Y() {
const a = document.getElementById("citationBtn"), e = document.title || "KGPZ";
let t = window.location.origin + window.location.pathname;
t.includes("#") && (t = t.split("#")[0]);
const i = (/* @__PURE__ */ new Date()).toLocaleDateString("de-DE"), n = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${e}. Digital verfügbar unter: ${t} (Zugriff: ${i}).`;
if (navigator.clipboard)
navigator.clipboard.writeText(n).then(() => {
h(a, "Zitation kopiert!");
g(a, "Zitation kopiert!");
}).catch((s) => {
h(a, "Kopieren fehlgeschlagen");
g(a, "Kopieren fehlgeschlagen");
});
else {
const s = document.createElement("textarea");
s.value = n, document.body.appendChild(s), s.select();
try {
const o = document.execCommand("copy");
h(a, o ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
g(a, o ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
} catch {
h(a, "Kopieren fehlgeschlagen");
g(a, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(s);
}
}
}
function h(a, e) {
function g(a, e) {
const t = document.querySelector(".simple-popup");
t && t.remove();
const i = document.createElement("div");
@@ -1297,7 +1350,7 @@ function h(a, e) {
}, 200);
}, 2e3);
}
function Y(a, e, t = !1) {
function G(a, e, t = !1) {
let i = "";
if (t)
i = window.location.origin + window.location.pathname + `#beilage-1-page-${a}`;
@@ -1312,24 +1365,24 @@ function Y(a, e, t = !1) {
const n = i;
if (navigator.clipboard)
navigator.clipboard.writeText(n).then(() => {
h(e, "Link kopiert!");
g(e, "Link kopiert!");
}).catch((s) => {
h(e, "Kopieren fehlgeschlagen");
g(e, "Kopieren fehlgeschlagen");
});
else {
const s = document.createElement("textarea");
s.value = n, document.body.appendChild(s), s.select();
try {
const o = document.execCommand("copy");
h(e, o ? "Link kopiert!" : "Kopieren fehlgeschlagen");
g(e, o ? "Link kopiert!" : "Kopieren fehlgeschlagen");
} catch {
h(e, "Kopieren fehlgeschlagen");
g(e, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(s);
}
}
}
function G(a, e) {
function U(a, e) {
const t = document.title || "KGPZ", i = window.location.pathname.split("/");
let n;
if (i.length >= 3) {
@@ -1340,25 +1393,25 @@ function G(a, e) {
const s = n, o = (/* @__PURE__ */ new Date()).toLocaleDateString("de-DE"), r = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${t}, Seite ${a}. Digital verfügbar unter: ${s} (Zugriff: ${o}).`;
if (navigator.clipboard)
navigator.clipboard.writeText(r).then(() => {
h(e, "Zitation kopiert!");
g(e, "Zitation kopiert!");
}).catch((l) => {
h(e, "Kopieren fehlgeschlagen");
g(e, "Kopieren fehlgeschlagen");
});
else {
const l = document.createElement("textarea");
l.value = r, document.body.appendChild(l), l.select();
try {
const c = document.execCommand("copy");
h(e, c ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
g(e, c ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
} catch {
h(e, "Kopieren fehlgeschlagen");
g(e, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(l);
}
}
}
function L() {
D(), window.addEventListener("scroll", function() {
F(), window.addEventListener("scroll", function() {
clearTimeout(window.scrollTimeout), window.scrollTimeout = setTimeout(() => {
b();
}, 50);
@@ -1366,7 +1419,7 @@ function L() {
a.key === "Escape" && E();
});
}
function k() {
function P() {
const a = window.location.pathname;
document.querySelectorAll(".citation-link[data-citation-url]").forEach((t) => {
const i = t.getAttribute("data-citation-url");
@@ -1383,7 +1436,7 @@ function k() {
n ? (t.classList.add("text-red-700", "pointer-events-none"), t.setAttribute("aria-current", "page")) : (t.classList.remove("text-red-700", "pointer-events-none"), t.removeAttribute("aria-current"));
});
}
function C() {
function k() {
const a = window.location.pathname, e = document.body;
e.classList.remove(
"page-akteure",
@@ -1395,21 +1448,21 @@ function C() {
"page-edition"
), a.includes("/akteure/") || a.includes("/autoren") ? e.classList.add("page-akteure") : a.match(/\/\d{4}\/\d+/) ? e.classList.add("page-ausgabe") : a.includes("/search") || a.includes("/suche") ? e.classList.add("page-search") : a.includes("/ort/") ? e.classList.add("page-ort") : a.includes("/kategorie/") ? e.classList.add("page-kategorie") : a.includes("/beitrag/") ? e.classList.add("page-piece") : a.includes("/edition") && e.classList.add("page-edition");
}
window.enlargePage = j;
window.enlargePage = D;
window.closeModal = E;
window.scrollToPreviousPage = F;
window.scrollToNextPage = K;
window.scrollToBeilage = Z;
window.shareCurrentPage = W;
window.generateCitation = J;
window.copyPagePermalink = Y;
window.generatePageCitation = G;
C();
window.scrollToPreviousPage = K;
window.scrollToNextPage = Z;
window.scrollToBeilage = W;
window.shareCurrentPage = J;
window.generateCitation = Y;
window.copyPagePermalink = G;
window.generatePageCitation = U;
k();
P();
document.querySelector(".newspaper-page-container") && L();
let U = function(a) {
C(), k(), S(), setTimeout(() => {
let X = function(a) {
k(), P(), S(), setTimeout(() => {
document.querySelector(".newspaper-page-container") && L();
}, 50);
};
document.body.addEventListener("htmx:afterSettle", U);
document.body.addEventListener("htmx:afterSettle", X);

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,7 @@
<!-- 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;">
<select id="year-select" autocomplete="off" 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>
@@ -21,10 +21,10 @@
<!-- 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">
<select id="issue-number-select" disabled autocomplete="off" 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">
<select id="issue-date-select" disabled autocomplete="off" 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>
@@ -39,7 +39,7 @@
<!-- 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">
<input type="number" id="page-input" min="1" placeholder="Seite eingeben" disabled autocomplete="off" 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 -->
@@ -75,6 +75,7 @@
type="text"
id="person-search"
placeholder="Name oder Lebensdaten eingeben..."
autocomplete="off"
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>
@@ -143,6 +144,7 @@
type="text"
id="place-search"
placeholder="Ortsname eingeben..."
autocomplete="off"
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>
@@ -154,6 +156,10 @@
<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>
{{ $modernName := GetModernPlaceName $place.Geo $place.Name }}
{{ if ne $modernName "" }}
<span class="text-xs text-slate-400 ml-2">{{ $modernName }}</span>
{{ end }}
</a>
</div>
{{ end }}

View File

@@ -1,722 +0,0 @@
{{ 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 hover:text-black text-gray-600 transition-colors text-xl no-underline font-bold">
<i class="ri-arrow-left-line mr-1 text-xl font-bold"></i>
Orte
</a>
</div>
<!-- Place Header -->
<div class="mb-8">
{{ $geonames := GetGeonames .model.SelectedPlace.Place.Geo }}
<!-- Name and external links - similar to akteure header -->
<div class="flex items-start justify-between gap-4">
<div class="flex-1">
<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>
<!-- Geographic Information from Geonames -->
{{ if ne $geonames nil }}
<div class="text-lg text-slate-700 mb-2">
<!-- Modern Country Info (only if not Germany) -->
{{ if and (ne $geonames.CountryName "") (ne $geonames.CountryName "Germany") }}
<div class="mb-1">
{{ $mainPlaceName := "" }}
{{ if .model.SelectedPlace.Place.Names }}
{{ $mainPlaceName = index .model.SelectedPlace.Place.Names 0 }}
{{ end }}
{{ if eq $geonames.CountryName "France" }}
heutiges Frankreich{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "United Kingdom" }}
heutiges Großbritannien{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Russia" }}
heutiges Russland{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if or (eq $geonames.CountryName "Czech Republic") (eq $geonames.CountryName "Czechia") }}
heutiges Tschechien{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if or (eq $geonames.CountryName "Netherlands") (eq $geonames.CountryName "The Netherlands") }}
heutige Niederlande{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Poland" }}
heutiges Polen{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Switzerland" }}
heutige Schweiz{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Latvia" }}
heutiges Lettland{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Sweden" }}
heutiges Schweden{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Austria" }}
heutiges Österreich{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Belgium" }}
heutiges Belgien{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Slovakia" }}
heutige Slowakei{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Finland" }}
heutiges Finnland{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Denmark" }}
heutiges Dänemark{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else }}
{{ $geonames.CountryName }}
{{ end }}
</div>
{{ end }}
<!-- Coordinates -->
<div class="text-slate-600 text-base space-y-1">
{{ if and (ne $geonames.Lat "") (ne $geonames.Lng "") }}
<div>
<i class="ri-map-pin-line mr-1"></i><a href="https://www.openstreetmap.org/?mlat={{ $geonames.Lat }}&mlon={{ $geonames.Lng }}&zoom=12" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-700 underline">{{ $geonames.Lat }}, {{ $geonames.Lng }}</a>
</div>
{{ end }}
</div>
</div>
{{ else }}
<!-- Fallback when no Geonames data -->
{{ if .model.SelectedPlace.Place.Geo }}
<p class="text-slate-600 mb-2">
<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 }}
{{ end }}
</div>
<!-- External link symbols on the right - similar to akteure -->
<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 }}
{{ end }}
<!-- Geonames link -->
{{ if .model.SelectedPlace.Place.Geo }}
<a href="{{ .model.SelectedPlace.Place.Geo }}" target="_blank" class="hover:opacity-80 transition-opacity no-underline" title="Geonames">
<i class="ri-global-line text-xl text-blue-600"></i>
</a>
{{ end }}
</div>
</div>
</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">Verlinkte 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 verlinkten 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 -->
<!-- Places List -->
{{ if .model.Places }}
<div>
<h2 class="text-lg font-semibold text-slate-700 mb-4">
Alle Orte ({{ len .model.Places }})
</h2>
<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 }}
{{ $geonames := GetGeonames $place.Geo }}
<div class="border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors h-24">
<a href="/ort/{{ $place.ID }}" class="block p-4 h-full flex flex-col justify-between">
<div class="flex items-start justify-between gap-2">
<div class="flex-1">
<h3 class="font-medium text-slate-800 mb-1">
{{ if $place.Names }}
{{ index $place.Names 0 }}
{{ else }}
{{ $place.ID }}
{{ end }}
</h3>
<!-- Show geographic info if available (only if not Germany) -->
{{ if ne $geonames nil }}
{{ if and (ne $geonames.CountryName "") (ne $geonames.CountryName "Germany") }}
<p class="text-sm text-slate-600 mb-1">
<i class="ri-map-pin-line mr-1"></i>
{{ $mainPlaceName := "" }}
{{ if $place.Names }}
{{ $mainPlaceName = index $place.Names 0 }}
{{ end }}
{{ if eq $geonames.CountryName "France" }}
heutiges Frankreich{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "United Kingdom" }}
heutiges Großbritannien{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Russia" }}
heutiges Russland{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if or (eq $geonames.CountryName "Czech Republic") (eq $geonames.CountryName "Czechia") }}
heutiges Tschechien{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if or (eq $geonames.CountryName "Netherlands") (eq $geonames.CountryName "The Netherlands") }}
heutige Niederlande{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Poland" }}
heutiges Polen{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Switzerland" }}
heutige Schweiz{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Latvia" }}
heutiges Lettland{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Sweden" }}
heutiges Schweden{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Austria" }}
heutiges Österreich{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Belgium" }}
heutiges Belgien{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Slovakia" }}
heutige Slowakei{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Finland" }}
heutiges Finnland{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else if eq $geonames.CountryName "Denmark" }}
heutiges Dänemark{{- $modernName := "" -}}
{{- $hasGermanName := false -}}
{{- range $altName := $geonames.AlternateNames -}}
{{- if eq $altName.Lang "de" -}}
{{- $hasGermanName = true -}}
{{- if $altName.IsPreferredName -}}
{{- $modernName = $altName.Name -}}
{{- break -}}
{{- else if eq $modernName "" -}}
{{- $modernName = $altName.Name -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $hasGermanName -}}
{{- $modernName = $geonames.ToponymName -}}
{{- end -}}
{{- if and (ne $modernName "") (ne (lower $modernName) (lower $mainPlaceName)) -}}, {{ $modernName }}{{- end }}
{{ else }}
{{ $geonames.CountryName }}
{{ end }}
</p>
{{ end }}
{{ else if $place.Geo }}
<p class="text-sm text-slate-600">
<i class="ri-map-pin-line mr-1"></i>Geonames verfügbar
</p>
{{ end }}
</div>
</div>
</a>
</div>
{{ end }}
</div>
</div>
{{ else }}
<p class="text-slate-500 italic">Keine Orte gefunden.</p>
{{ end }}
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,8 @@
{{- /* Back navigation for individual place pages */ -}}
<!-- Back Navigation -->
<div class="mb-6">
<a href="/ort/" class="inline-flex items-center hover:text-black text-gray-600 transition-colors text-xl no-underline font-bold">
<i class="ri-arrow-left-line mr-1 text-xl font-bold"></i>
Orte
</a>
</div>

View File

@@ -0,0 +1,42 @@
{{- /* Individual place card for overview grid */ -}}
{{ $geonames := GetGeonames .Geo }}
{{ $mainPlaceName := "" }}
{{ if .Names }}
{{ $mainPlaceName = index .Names 0 }}
{{ else }}
{{ $mainPlaceName = .ID }}
{{ end }}
{{ $modernName := GetModernPlaceName .Geo $mainPlaceName }}
<div class="border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors h-20" data-place-name="{{ $mainPlaceName }}" data-modern-name="{{ $modernName }}">
<a href="/ort/{{ .ID }}" class="block p-4 h-full flex flex-col justify-between">
<div class="flex items-start justify-between gap-2">
<div class="flex-1">
<h3 class="font-medium text-slate-800 mb-1">
{{ if .Names }}
{{ index .Names 0 }}
{{ else }}
{{ .ID }}
{{ end }}
</h3>
<!-- Show geographic info if available (only if not Germany) -->
{{ if ne $geonames nil }}
{{ $mainPlaceName := "" }}
{{ if .Names }}
{{ $mainPlaceName = index .Names 0 }}
{{ end }}
{{ $fullInfo := GetFullPlaceInfo .Geo $mainPlaceName }}
{{ if ne $fullInfo "" }}
<p class="text-sm text-slate-600 mb-1">
<i class="ri-map-pin-line mr-1"></i>{{ $fullInfo }}
</p>
{{ end }}
{{ else if .Geo }}
<p class="text-sm text-slate-600">
<i class="ri-map-pin-line mr-1"></i>Geonames verfügbar
</p>
{{ end }}
</div>
</div>
</a>
</div>

View File

@@ -0,0 +1,71 @@
{{- /* Place header with name, geographic info, and external links */ -}}
<!-- Place Header -->
<div class="mb-8">
{{ $geonames := GetGeonames .Place.Geo }}
<!-- Name and external links - similar to akteure header -->
<div class="flex items-start justify-between gap-4">
<div class="flex-1">
<h1 class="text-3xl font-bold text-slate-800 mb-2">
{{ if .Place.Names }}
{{ index .Place.Names 0 }}
{{ else }}
{{ .Place.ID }}
{{ end }}
</h1>
<!-- Geographic Information from Geonames -->
{{ if ne $geonames nil }}
<div class="text-lg text-slate-700 mb-2">
<!-- Modern Country Info (only if not Germany) -->
{{ $mainPlaceName := "" }}
{{ if .Place.Names }}
{{ $mainPlaceName = index .Place.Names 0 }}
{{ end }}
{{ $fullInfo := GetFullPlaceInfo .Place.Geo $mainPlaceName }}
{{ if ne $fullInfo "" }}
<div class="mb-1">{{ $fullInfo }}</div>
{{ end }}
<!-- Coordinates -->
<div class="text-slate-600 text-base space-y-1">
{{ if and (ne $geonames.Lat "") (ne $geonames.Lng "") }}
<div>
<i class="ri-map-pin-line mr-1"></i><a href="https://www.openstreetmap.org/?mlat={{ $geonames.Lat }}&mlon={{ $geonames.Lng }}&zoom=12" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-700 underline">{{ $geonames.Lat }}, {{ $geonames.Lng }}</a>
</div>
{{ end }}
</div>
</div>
{{ else }}
<!-- Fallback when no Geonames data -->
{{ if .Place.Geo }}
<p class="text-slate-600 mb-2">
<i class="ri-map-pin-line mr-1"></i>
<a href="{{ .Place.Geo }}" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:text-blue-700 underline">
Geonames
</a>
</p>
{{ end }}
{{ end }}
</div>
<!-- External link symbols on the right - similar to akteure -->
<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 }}
{{ end }}
<!-- Geonames link -->
{{ if .Place.Geo }}
<a href="{{ .Place.Geo }}" target="_blank" class="hover:opacity-80 transition-opacity no-underline" title="Geonames">
<i class="ri-global-line text-xl text-blue-600"></i>
</a>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1,98 @@
{{- /* Associated pieces section for place detail view */ -}}
<!-- 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">Verlinkte Beiträge</u> ({{ len .Pieces }})
</h2>
{{ if .Pieces }}
{{- /* Group pieces by year */ -}}
{{- $piecesByYear := dict -}}
{{- range $_, $p := .Pieces -}}
{{- range $issueRef := $p.IssueRefs -}}
{{- $year := printf "%d" $issueRef.When.Year -}}
{{- $existing := index $piecesByYear $year -}}
{{- if $existing -}}
{{- $piecesByYear = merge $piecesByYear (dict $year (append $existing $p)) -}}
{{- else -}}
{{- $piecesByYear = merge $piecesByYear (dict $year (slice $p)) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* Get sorted years */ -}}
{{- $sortedYears := slice -}}
{{- range $year, $pieces := $piecesByYear -}}
{{- $sortedYears = append $sortedYears $year -}}
{{- end -}}
{{- $sortedYears = sortStrings $sortedYears -}}
<div class="space-y-6 max-w-[85ch]">
{{- range $year := $sortedYears -}}
{{- $yearPieces := index $piecesByYear $year -}}
<!-- Year Header -->
<div>
<h3 class="text-lg font-bold font-serif text-slate-800 mb-3">{{ $year }}</h3>
<div class="space-y-1">
{{- /* Group pieces by title within each year */ -}}
{{- $groupedPieces := dict -}}
{{- range $_, $p := $yearPieces -}}
{{- $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 -}}
{{- range $groupKey, $groupedItems := $groupedPieces -}}
<div>
<div class="pb-1 text-lg 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 -}}
{{- /* Only show citations for the current year */ -}}
{{- if eq (printf "%d" $issue.When.Year) $year -}}
{{- 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 -}}
{{- 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">
<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>
{{- end -}}
</div>
{{ else }}
<p class="text-slate-500 italic">Keine verlinkten Beiträge für diesen Ort gefunden.</p>
{{ end }}
</div>

View File

@@ -0,0 +1,8 @@
<!-- 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 lg:min-w-[800px] xl:min-w-[900px]">
{{ template "_back_navigation" . }}
{{ template "_place_header" .model.SelectedPlace }}
{{ template "_place_pieces" .model.SelectedPlace }}
</div>
</div>

View File

@@ -0,0 +1,36 @@
{{ $place := .model.SelectedPlace.Place }}
<title>{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} - KGPZ</title>
<meta name="description" content="Informationen zu {{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} in der Königsberger Gelehrten und Politischen Zeitung.">
<meta name="keywords" content="{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }}, Ort, KGPZ, Königsberg, Zeitung">
<!-- Open Graph tags for social media -->
<meta property="og:title" content="{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} - KGPZ">
<meta property="og:description" content="Informationen zu {{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} in der Königsberger Gelehrten und Politischen Zeitung.">
<meta property="og:type" content="article">
<meta property="og:url" content="/ort/{{ $place.ID }}">
<!-- JSON-LD structured data -->
{{ $geonames := GetGeonames $place.Geo }}
{{ if ne $geonames nil }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Place",
"name": "{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }}",
"description": "Historischer Ort erwähnt in der Königsberger Gelehrten und Politischen Zeitung"{{ if or (and (ne $geonames.Lat "") (ne $geonames.Lng "")) (ne $geonames.CountryName "") (ne $place.Geo "") }},{{ end }}
{{ if and (ne $geonames.Lat "") (ne $geonames.Lng "") }}
"geo": {
"@type": "GeoCoordinates",
"latitude": {{ $geonames.Lat }},
"longitude": {{ $geonames.Lng }}
}{{ if or (ne $geonames.CountryName "") (ne $place.Geo "") }},{{ end }}
{{ end }}
{{ if ne $geonames.CountryName "" }}
"addressCountry": "{{ $geonames.CountryName }}"{{ if ne $place.Geo "" }},{{ end }}
{{ end }}
{{ if ne $place.Geo "" }}
"sameAs": "{{ $place.Geo }}"
{{ end }}
}
</script>
{{ end }}

View File

@@ -1,42 +0,0 @@
{{ if .model.SelectedPlace }}
{{ $place := .model.SelectedPlace.Place }}
<title>{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} - KGPZ</title>
<meta name="description" content="Informationen zu {{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} in der Königsberger Gelehrten und Politischen Zeitung.">
<meta name="keywords" content="{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }}, Ort, KGPZ, Königsberg, Zeitung">
<!-- Open Graph tags for social media -->
<meta property="og:title" content="{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} - KGPZ">
<meta property="og:description" content="Informationen zu {{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }} in der Königsberger Gelehrten und Politischen Zeitung.">
<meta property="og:type" content="article">
<meta property="og:url" content="/ort/{{ $place.ID }}">
<!-- JSON-LD structured data -->
{{ $geonames := GetGeonames $place.Geo }}
{{ if ne $geonames nil }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Place",
"name": "{{ if $place.Names }}{{ index $place.Names 0 }}{{ else }}{{ $place.ID }}{{ end }}",
"description": "Historischer Ort erwähnt in der Königsberger Gelehrten und Politischen Zeitung"{{ if or (and (ne $geonames.Lat "") (ne $geonames.Lng "")) (ne $geonames.CountryName "") (ne $place.Geo "") }},{{ end }}
{{ if and (ne $geonames.Lat "") (ne $geonames.Lng "") }}
"geo": {
"@type": "GeoCoordinates",
"latitude": {{ $geonames.Lat }},
"longitude": {{ $geonames.Lng }}
}{{ if or (ne $geonames.CountryName "") (ne $place.Geo "") }},{{ end }}
{{ end }}
{{ if ne $geonames.CountryName "" }}
"addressCountry": "{{ $geonames.CountryName }}"{{ if ne $place.Geo "" }},{{ end }}
{{ end }}
{{ if ne $place.Geo "" }}
"sameAs": "{{ $place.Geo }}"
{{ end }}
}
</script>
{{ end }}
{{ else }}
<title>Orte - KGPZ</title>
<meta name="description" content="Übersicht aller Orte in der Königsberger Gelehrten und Politischen Zeitung.">
<meta name="keywords" content="Orte, Geografie, KGPZ, Königsberg, Zeitung">
{{ end }}

View File

@@ -0,0 +1,27 @@
<!-- 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>
<!-- Places List -->
{{ if .model.Places }}
<div>
<!-- Search Filter -->
<places-filter></places-filter>
<h2 class="text-lg font-semibold text-slate-700 mb-4" data-places-count>
Alle Orte ({{ len .model.Places }})
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 lg:min-w-[768px] xl:min-w-[1024px]">
{{ range $placeID := .model.Sorted }}
{{ $place := index $.model.Places $placeID }}
{{ template "_place_card" $place }}
{{ end }}
</div>
</div>
{{ else }}
<p class="text-slate-500 italic">Keine Orte gefunden.</p>
{{ end }}
</div>
</div>

View File

@@ -0,0 +1,3 @@
<title>Orte - KGPZ</title>
<meta name="description" content="Übersicht aller Orte in der Königsberger Gelehrten und Politischen Zeitung.">
<meta name="keywords" content="Orte, Geografie, KGPZ, Königsberg, Zeitung">

View File

@@ -1,6 +1,7 @@
import "./site.css";
import "./search.js";
import "./akteure.js";
import "./places.js";
import { SinglePageViewer } from "./single-page-viewer.js";
import { ScrollToTopButton } from "./scroll-to-top.js";
import { InhaltsverzeichnisScrollspy } from "./inhaltsverzeichnis-scrollspy.js";

122
views/transform/places.js Normal file
View File

@@ -0,0 +1,122 @@
/**
* Places Filter Web Component
* Provides search functionality for filtering place cards in the overview
*/
export class PlacesFilter extends HTMLElement {
constructor() {
super();
this.searchInput = null;
this.placeCards = [];
this.countElement = null;
this.debounceTimer = null;
this.originalCount = 0;
}
connectedCallback() {
this.render();
this.setupEventListeners();
this.initializePlaceCards();
}
disconnectedCallback() {
this.cleanupEventListeners();
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
render() {
this.innerHTML = `
<div class="mb-6">
<input
type="text"
id="places-search"
placeholder="Ortsnamen eingeben..."
autocomplete="off"
class="w-full px-3 py-2 border border-slate-300 rounded-md text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400 focus:border-blue-400"
>
</div>
`;
}
setupEventListeners() {
this.searchInput = this.querySelector('#places-search');
if (this.searchInput) {
this.searchInput.addEventListener('input', this.handleSearchInput.bind(this));
}
}
cleanupEventListeners() {
if (this.searchInput) {
this.searchInput.removeEventListener('input', this.handleSearchInput.bind(this));
}
}
initializePlaceCards() {
// Find all place cards and the count element
const container = this.closest('.bg-white') || document;
this.placeCards = Array.from(container.querySelectorAll('[data-place-name]'));
this.countElement = container.querySelector('[data-places-count]');
if (this.countElement) {
this.originalCount = this.placeCards.length;
}
}
handleSearchInput(event) {
// Clear previous debounce timer
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
// Debounce the search to avoid excessive filtering
this.debounceTimer = setTimeout(() => {
this.filterPlaces(event.target.value.trim());
}, 150);
}
filterPlaces(searchTerm) {
if (!this.placeCards.length) return;
const normalizedSearch = searchTerm.toLowerCase();
let visibleCount = 0;
this.placeCards.forEach(card => {
const placeName = card.getAttribute('data-place-name')?.toLowerCase() || '';
const modernName = card.getAttribute('data-modern-name')?.toLowerCase() || '';
// Check if search term matches either the place name or modern name
const isMatch = searchTerm === '' ||
placeName.includes(normalizedSearch) ||
modernName.includes(normalizedSearch);
if (isMatch) {
card.style.display = '';
visibleCount++;
} else {
card.style.display = 'none';
}
});
// Update the count display
this.updateCountDisplay(visibleCount, searchTerm);
}
updateCountDisplay(visibleCount, searchTerm) {
if (!this.countElement) return;
if (searchTerm === '') {
// Show original count when no search
this.countElement.textContent = `Alle Orte (${this.originalCount})`;
} else if (visibleCount === 0) {
// Show no results message
this.countElement.textContent = `Keine Orte gefunden für "${searchTerm}"`;
} else {
// Show filtered count
this.countElement.textContent = `${visibleCount} von ${this.originalCount} Orten`;
}
}
}
// Register the custom element
customElements.define('places-filter', PlacesFilter);