Some improvements with the akteure page

This commit is contained in:
Simon Martens
2025-09-21 11:06:28 +02:00
parent 0f6ffbf63f
commit 3f2811acbc
18 changed files with 1404 additions and 463 deletions

191
CLAUDE.md
View File

@@ -118,9 +118,15 @@ views/
│ └── default/ # Default layout (root.gohtml)
├── routes/ # Page-specific templates
│ ├── akteure/ # Agents/People pages (body.gohtml, head.gohtml)
│ ├── autoren/ # Authors-only pages (body.gohtml, head.gohtml)
│ ├── ausgabe/ # Issue pages with components
│ │ └── components/ # Issue-specific components (_inhaltsverzeichnis, _bilder, etc.)
│ ├── components/ # Shared route components (_akteur.gohtml)
│ ├── components/ # Shared route components
│ │ ├── _akteur.gohtml # Main agent component (uses sub-components)
│ │ ├── _akteur_header.gohtml # Agent name, dates, professions, links
│ │ ├── _akteur_werke.gohtml # Works section with categorized pieces
│ │ ├── _akteur_beitraege.gohtml # Contributions/pieces with grouping
│ │ └── _piece_summary.gohtml # Individual piece display logic
│ ├── datenschutz/ # Privacy policy
│ ├── edition/ # Edition pages
│ ├── kategorie/ # Category pages
@@ -171,6 +177,7 @@ Each route has dedicated `head.gohtml` and `body.gohtml` files following Go temp
- **HTMX**: Core interactivity and AJAX requests
- **Alpine.js**: Lightweight reactivity for UI components
- **Custom Extensions**: HTMX plugins for response targets, client-side templates, loading states
- **Scrollspy System**: Advanced navigation for agents/authors pages with multi-item highlighting
- **Build Tool**: Vite for module bundling and development server
**CSS Stack**:
@@ -217,8 +224,10 @@ The application follows a **logic-in-Go, presentation-in-templates** approach:
### JavaScript Integration
- **Progressive Enhancement**: HTMX + Alpine.js for interactivity
- **Real-time Highlighting**: Intersection Observer API with scroll fallback
- **Real-time Highlighting**: Intersection Observer API with scroll fallback (issue view)
- **Scrollspy Navigation**: Multi-item highlighting system for agents/authors pages
- **Page Navigation**: Smooth scrolling with visibility detection
- **HTMX Integration**: Automatic cleanup and re-initialization on page swaps
- **Responsive Design**: Mobile-optimized with proper touch interactions
## Development Workflow
@@ -438,4 +447,180 @@ http://127.0.0.1:8080/1771/42/166 # Direct link to page 166
const pageUrl = `/${year}/${issue}/${pageNumber}`;
// Old format still used for beilage pages
const beilageUrl = `${window.location.pathname}#beilage-1-page-${pageNumber}`;
```
```
## Agents/Authors View System (/akteure/ and /autoren/)
The application provides sophisticated person and organization browsing through dual view systems with advanced navigation and filtering capabilities.
### Dual View Architecture
**General Agents View** (`/akteure/`):
- Displays all persons and organizations mentioned in the newspaper
- Supports letter-based navigation (A-Z)
- Individual person pages with detailed information
- Two-column layout with scrollspy navigation on large screens
**Authors-Only View** (`/autoren/`):
- Filtered view showing only people who authored pieces (Beiträge)
- Single-page display of all authors regardless of starting letter
- No alphabet navigation (all authors shown together)
- Same advanced layout and scrollspy functionality
### URL Structure & Navigation
**URL Patterns**:
- `/akteure/` - All persons overview
- `/akteure/a` - Persons starting with letter "A"
- `/akteure/{id}` - Individual person page
- `/akteure/autoren` - Authors-only filtered view
**Toggle Navigation**:
- Checkbox interface: "Nur Autoren anzeigen" switches between views
- HTMX-powered transitions with URL history management
- Unchecking returns to `/akteure/a` (letter A starting point)
### Template Architecture & Components
**Modular Template System** (`views/routes/components/`):
- `_akteur.gohtml` - Main component using sub-components
- `_akteur_header.gohtml` - Name, life dates, professions, external links
- `_akteur_werke.gohtml` - Works section with categorized pieces
- `_akteur_beitraege.gohtml` - Contributions/pieces with grouping
**Component Benefits**:
- Reusable across different view contexts
- Maintainable separation of concerns
- Consistent styling and behavior
- Easy customization for specific views (authors vs. full agents)
### Advanced Scrollspy Navigation
**Full-Height Sidebar** (2XL+ screens only):
- Fixed 320px width with full viewport height
- Sticky positioning that follows scroll
- Complete name list with smooth scrolling navigation
- Automatic cleanup on HTMX page transitions
**Multi-Item Highlighting**:
- Highlights ALL currently visible authors simultaneously
- Red left border indicating visible items (matches issue view pattern)
- Header visibility detection (name, life data, professions must be fully visible)
- Real-time updates during scroll with 50ms debouncing
**Visual Features**:
- Larger text (`text-base`) for better readability
- Closer spacing (`py-1`) for more names visible
- Smooth transitions and hover effects
- Blue background highlighting for active items
### Controller Architecture
**Unified Controller** (`controllers/akteur_controller.go`):
- Handles both general agents and authors-only views
- Special routing for "autoren" parameter
- Template path switching based on view type
- Letter-based filtering and ID lookup
**View Models** (`viewmodels/agent_view.go`):
- `AgentsView()` - General person lookup by letter/ID
- `AuthorsView()` - Filtered view of piece authors only
- `AuthorsListView` struct with sorting and letter availability
- Pre-processed agent data for efficient template rendering
### Template Features & Data Processing
**Enhanced Data Presentation**:
- Grouped pieces by title and work reference
- Category combination with proper German grammar ("und" vs. "mit")
- Inline citation format: DD.MM.YYYY/ISSUENO, PPP[-PPP]
- Works section showing review/commentary pieces
- External link integration (Wikipedia, GND, VIAF)
**Text Sizing & Hierarchy**:
- Large serif names (`text-2xl font-serif font-bold`)
- Readable life dates and professions (`text-xl`)
- Appropriately sized content text (`text-lg`)
- Larger pill text (`text-sm`) matching issue view standards
### JavaScript Integration
**HTMX-Safe Scrollspy** (`views/transform/main.js`):
- Proper event listener cleanup on page navigation
- Memory leak prevention with timeout management
- Auto-initialization detection for `.author-section` elements
- Smooth scroll behavior for sidebar navigation
**Performance Optimizations**:
- Debounced scroll handling (50ms)
- Efficient viewport calculations using `getBoundingClientRect()`
- Minimal DOM queries with cached element references
- Responsive behavior with automatic sidebar hiding
### Responsive Design
**Desktop Experience** (2XL+ screens):
- Two-column layout: 320px sidebar + flexible content area
- Fixed scrollspy navigation with full name list
- Multi-author highlighting system
- Smooth scrolling between authors
**Mobile Experience** (< 2XL screens):
- Single-column layout with full-width content
- Hidden scrollspy navigation (saves space)
- Touch-optimized interactions
- Same content organization and functionality
### Data Categories & Processing
**Comprehensive Category Support**:
- All 29 XML-defined categories supported
- Dynamic category detection and grouping
- Proper German grammar rules for combinations
- Author filtering for non-current-user pieces
**Helper Functions** (`templating/engine.go`):
- `merge`, `append`, `slice` - Data manipulation
- `sortStrings`, `unique` - Array processing
- `joinWithUnd` - German grammar formatting
- Enhanced data processing for complex template logic
### Usage Examples
**Template Integration**:
```gohtml
{{ template "_akteur_header" $agent }}
{{ template "_akteur_werke" $agent }}
{{ template "_akteur_beitraege" $agent }}
```
**Scrollspy Navigation**:
```gohtml
<a href="#author-{{ $id }}"
class="scrollspy-link border-l-4 border-transparent"
data-target="author-{{ $id }}">
{{ index $agent.Names 0 }}
</a>
```
**HTMX Toggle**:
```gohtml
<input type="checkbox"
hx-get="/akteure/autoren"
hx-target="body"
hx-push-url="true">
```
### Error Handling & Edge Cases
**Template Safety**:
- Null checks for GND data and agent information
- Graceful fallback for missing names or professions
- Safe handling of empty work/piece lists
- Error boundaries for external link data
**Navigation Robustness**:
- 404 handling for invalid agent IDs
- Automatic fallback for missing letters
- Smooth transitions between view modes
- Proper state management across HTMX swaps

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -17,6 +17,21 @@ func GetAgents(kgpz *xmlmodels.Library) fiber.Handler {
return func(c *fiber.Ctx) error {
a := c.Params("letterorid", DEFAULT_AGENT)
a = strings.ToLower(a)
// Handle special "autoren" route
if a == "autoren" {
agents := viewmodels.AuthorsView(kgpz)
if len(agents.Agents) == 0 {
logging.Error(nil, "No authors found")
return c.SendStatus(fiber.StatusNotFound)
}
return c.Render(
"/autoren/",
fiber.Map{"model": agents},
)
}
// Handle normal letter/id lookup
agents := viewmodels.AgentsView(a, kgpz)
if len(agents.Agents) == 0 {
logging.Error(nil, "No agents found for letter or id: "+a)

View File

@@ -4,6 +4,7 @@ import "github.com/Theodor-Springmann-Stiftung/kgpz_web/xmlmodels"
// CategoryFlags represents all possible category flags for a piece
type CategoryFlags struct {
// Categories from kategorien.xml
Rezension bool
Weltnachrichten bool
EinkommendeFremde bool
@@ -15,6 +16,7 @@ type CategoryFlags struct {
Gedicht bool
Vorladung bool
Auszug bool
Provinienz bool // Added missing category
Aufsatz bool
GelehrteNachrichten bool
Theaterkritik bool
@@ -23,17 +25,18 @@ type CategoryFlags struct {
Nachruf bool
Replik bool
Proklamation bool
Ineigenersache bool
Brief bool
Anzeige bool
Ineigenersache bool
Desertionsliste bool
Notenblatt bool
Vorlesungsverzeichnis bool
Erzaehlung bool
Abbildung bool
// Additional categories that appear in combinations
Nachtrag bool
Panegyrik bool
Kriminalanzeige bool
Abbildung bool
Rezepte bool
Korrektur bool
}
@@ -67,6 +70,8 @@ func GetCategoryFlags(piece xmlmodels.Piece) CategoryFlags {
flags.Vorladung = true
case "auszug":
flags.Auszug = true
case "provinienz":
flags.Provinienz = true
case "aufsatz":
flags.Aufsatz = true
case "gelehrte-nachrichten":

View File

@@ -225,6 +225,121 @@ func (e *Engine) funcs() error {
return dict, nil
})
e.AddFunc("merge", func(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
// Copy from dest first
for k, v := range dest {
result[k] = v
}
// Override with src values
for k, v := range src {
result[k] = v
}
return result
})
e.AddFunc("append", func(slice interface{}, item interface{}) interface{} {
v := reflect.ValueOf(slice)
if v.Kind() != reflect.Slice {
return slice
}
newSlice := reflect.Append(v, reflect.ValueOf(item))
return newSlice.Interface()
})
e.AddFunc("slice", func(items ...interface{}) []interface{} {
return items
})
e.AddFunc("keys", func(m map[string]interface{}) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
})
e.AddFunc("has", func(slice interface{}, item interface{}) bool {
v := reflect.ValueOf(slice)
if v.Kind() != reflect.Slice {
return false
}
for i := 0; i < v.Len(); i++ {
if reflect.DeepEqual(v.Index(i).Interface(), item) {
return true
}
}
return false
})
e.AddFunc("joinWithUnd", func(items []string) string {
if len(items) == 0 {
return ""
}
if len(items) == 1 {
return items[0]
}
if len(items) == 2 {
return items[0] + " und " + items[1]
}
// For 3+ items: "A, B und C"
result := ""
for i, item := range items {
if i == 0 {
result = item
} else if i == len(items)-1 {
result += " und " + item
} else {
result += ", " + item
}
}
return result
})
e.AddFunc("sortStrings", func(items interface{}) []string {
v := reflect.ValueOf(items)
if v.Kind() != reflect.Slice {
return []string{}
}
// Convert to string slice
result := make([]string, v.Len())
for i := 0; i < v.Len(); i++ {
if str, ok := v.Index(i).Interface().(string); ok {
result[i] = str
}
}
// Simple bubble sort
for i := 0; i < len(result)-1; i++ {
for j := i + 1; j < len(result); j++ {
if result[i] > result[j] {
result[i], result[j] = result[j], result[i]
}
}
}
return result
})
e.AddFunc("unique", func(items interface{}) []string {
v := reflect.ValueOf(items)
if v.Kind() != reflect.Slice {
return []string{}
}
seen := make(map[string]bool)
result := []string{}
for i := 0; i < v.Len(); i++ {
if str, ok := v.Index(i).Interface().(string); ok {
if !seen[str] {
seen[str] = true
result = append(result, str)
}
}
}
return result
})
// Strings
e.AddFunc("FirstLetter", functions.FirstLetter)
e.AddFunc("Upper", strings.ToUpper)

View File

@@ -46,3 +46,34 @@ func AgentsView(letterorid string, lib *xmlmodels.Library) *AgentsListView {
return &res
}
// AuthorsView returns only agents who have authored pieces (have written Beiträge)
func AuthorsView(lib *xmlmodels.Library) *AgentsListView {
res := AgentsListView{Search: "autoren", Agents: make(map[string]xmlmodels.Agent)}
av := make(map[string]bool)
// Find all agents who have pieces
authorIDs := make(map[string]bool)
for _, piece := range lib.Pieces.Array {
for _, agentRef := range piece.AgentRefs {
if agentRef.Category == "" || agentRef.Category == "autor" {
authorIDs[agentRef.Ref] = true
}
}
}
// Add all agents who are authors
for _, a := range lib.Agents.Array {
av[strings.ToUpper(a.ID[:1])] = true
if authorIDs[a.ID] {
res.Sorted = append(res.Sorted, a.ID)
res.Agents[a.ID] = a
}
}
res.AvailableLetters = slices.Collect(maps.Keys(av))
slices.Sort(res.AvailableLetters)
slices.Sort(res.Sorted)
return &res
}

View File

@@ -1,25 +1,25 @@
const A = "script[xslt-onload]", w = "xslt-template", E = "xslt-transformed", y = /* @__PURE__ */ new Map();
function v() {
let i = htmx.findAll(A);
for (let t of i)
L(t);
const D = "script[xslt-onload]", S = "xslt-template", V = "xslt-transformed", P = /* @__PURE__ */ new Map();
function C() {
let o = htmx.findAll(D);
for (let t of o)
K(t);
}
function L(i) {
if (i.getAttribute(E) === "true" || !i.hasAttribute(w))
function K(o) {
if (o.getAttribute(V) === "true" || !o.hasAttribute(S))
return;
let t = "#" + i.getAttribute(w), e = y.get(t);
let t = "#" + o.getAttribute(S), e = P.get(t);
if (!e) {
let s = htmx.find(t);
if (s) {
let a = s.innerHTML ? new DOMParser().parseFromString(s.innerHTML, "application/xml") : s.contentDocument;
e = new XSLTProcessor(), e.importStylesheet(a), y.set(t, e);
e = new XSLTProcessor(), e.importStylesheet(a), P.set(t, e);
} else
throw new Error("Unknown XSLT template: " + t);
}
let n = new DOMParser().parseFromString(i.innerHTML, "application/xml"), o = e.transformToFragment(n, document), r = new XMLSerializer().serializeToString(o);
i.outerHTML = r;
let n = new DOMParser().parseFromString(o.innerHTML, "application/xml"), i = e.transformToFragment(n, document), r = new XMLSerializer().serializeToString(i);
o.outerHTML = r;
}
function q() {
function j() {
document.querySelectorAll("template[simple]").forEach((t) => {
let e = t.getAttribute("id"), n = t.content;
customElements.define(
@@ -29,11 +29,11 @@ function q() {
super(), this.appendChild(n.cloneNode(!0)), this.slots = this.querySelectorAll("slot");
}
connectedCallback() {
let o = [];
let i = [];
this.slots.forEach((r) => {
let s = r.getAttribute("name"), a = this.querySelector(`[slot="${s}"]`);
a && (r.replaceWith(a.cloneNode(!0)), o.push(a));
}), o.forEach((r) => {
a && (r.replaceWith(a.cloneNode(!0)), i.push(a));
}), i.forEach((r) => {
r.remove();
});
}
@@ -46,63 +46,63 @@ window.currentPageContainers = window.currentPageContainers || [];
window.currentActiveIndex = window.currentActiveIndex || 0;
window.pageObserver = window.pageObserver || null;
window.scrollTimeout = window.scrollTimeout || null;
function $() {
function W() {
window.highlightObserver && (window.highlightObserver.disconnect(), window.highlightObserver = null);
const i = document.querySelectorAll(".newspaper-page-container");
const o = document.querySelectorAll(".newspaper-page-container");
window.highlightObserver = new IntersectionObserver(
(t) => {
P();
I();
},
{
rootMargin: "-20% 0px -70% 0px"
}
), i.forEach((t) => {
), o.forEach((t) => {
window.highlightObserver.observe(t);
});
}
function P() {
const i = [];
function I() {
const o = [];
document.querySelectorAll(".newspaper-page-container").forEach((e) => {
const n = e.getBoundingClientRect(), o = window.innerHeight, r = Math.max(n.top, 0), s = Math.min(n.bottom, o), a = Math.max(0, s - r), l = n.height, g = a / l >= 0.5, u = e.querySelector("img[data-page]"), p = u ? u.getAttribute("data-page") : "unknown";
g && u && p && !i.includes(p) && i.push(p);
}), k(i), i.length > 0 && C(i);
const n = e.getBoundingClientRect(), i = window.innerHeight, r = Math.max(n.top, 0), s = Math.min(n.bottom, i), a = Math.max(0, s - r), l = n.height, g = a / l >= 0.5, d = e.querySelector("img[data-page]"), p = d ? d.getAttribute("data-page") : "unknown";
g && d && p && !o.includes(p) && o.push(p);
}), Z(o), o.length > 0 && A(o);
}
function k(i) {
function Z(o) {
document.querySelectorAll(".continuation-entry").forEach((t) => {
t.style.display = "none";
}), i.forEach((t) => {
}), o.forEach((t) => {
const e = document.querySelector(`[data-page-container="${t}"]`);
e && e.querySelectorAll(".continuation-entry").forEach((o) => {
o.style.display = "";
e && e.querySelectorAll(".continuation-entry").forEach((i) => {
i.style.display = "";
});
}), B(i), M();
}), _(o), F();
}
function B(i) {
function _(o) {
document.querySelectorAll(".work-title").forEach((t) => {
const e = t.getAttribute("data-short-title");
e && (t.textContent = e);
}), i.forEach((t) => {
}), o.forEach((t) => {
const e = document.querySelector(`[data-page-container="${t}"]`);
e && e.querySelectorAll(".work-title").forEach((o) => {
const r = o.getAttribute("data-full-title");
r && r !== o.getAttribute("data-short-title") && (o.textContent = r);
e && e.querySelectorAll(".work-title").forEach((i) => {
const r = i.getAttribute("data-full-title");
r && r !== i.getAttribute("data-short-title") && (i.textContent = r);
});
});
}
function M() {
document.querySelectorAll(".page-entry").forEach((i) => {
const t = i.querySelectorAll(".inhalts-entry");
function F() {
document.querySelectorAll(".page-entry").forEach((o) => {
const t = o.querySelectorAll(".inhalts-entry");
let e = !1;
t.forEach((n) => {
window.getComputedStyle(n).display !== "none" && (e = !0);
}), e ? i.style.display = "" : i.style.display = "none";
}), e ? o.style.display = "" : o.style.display = "none";
});
}
function S(i) {
C([i]);
function E(o) {
A([o]);
}
function C(i) {
console.log("markCurrentPagesInInhaltsverzeichnis called with:", i), document.querySelectorAll("[data-page-container]").forEach((e) => {
function A(o) {
console.log("markCurrentPagesInInhaltsverzeichnis called with:", o), document.querySelectorAll("[data-page-container]").forEach((e) => {
e.hasAttribute("data-beilage") ? (e.classList.remove("border-red-500"), e.classList.add("border-amber-400")) : (e.classList.remove("border-red-500"), e.classList.add("border-slate-300"));
}), document.querySelectorAll(".page-number-inhalts").forEach((e) => {
e.classList.remove("text-red-600", "font-bold"), e.classList.add("text-slate-700", "font-semibold"), e.style.textDecoration = "", e.style.pointerEvents = "", e.classList.contains("bg-blue-50") ? e.classList.add("hover:bg-blue-100") : e.classList.contains("bg-amber-50") && e.classList.add("hover:bg-amber-100"), !e.classList.contains("bg-amber-50") && !e.classList.contains("bg-blue-50") && e.classList.add("bg-blue-50");
@@ -112,14 +112,14 @@ function C(i) {
e.classList.remove("no-underline"), e.classList.contains("bg-blue-50") && e.classList.add("hover:bg-blue-100");
});
const t = [];
i.forEach((e) => {
o.forEach((e) => {
const n = document.querySelector(
`.page-number-inhalts[data-page-number="${e}"]`
);
if (n) {
n.classList.remove("text-slate-700", "hover:bg-blue-100", "hover:bg-amber-100"), n.classList.add("text-red-600", "font-bold"), n.style.textDecoration = "none", n.style.pointerEvents = "none", t.push(n);
const o = document.querySelector(`[data-page-container="${e}"]`);
o && (o.classList.remove("border-slate-300", "border-amber-400"), o.classList.add("border-red-500"));
const i = document.querySelector(`[data-page-container="${e}"]`);
i && (i.classList.remove("border-slate-300", "border-amber-400"), i.classList.add("border-red-500"));
const r = n.closest(".page-entry");
r && (r.querySelectorAll(".inhalts-entry").forEach((a) => {
a.classList.remove("hover:bg-slate-100"), a.style.cursor = "default";
@@ -127,43 +127,43 @@ function C(i) {
a.getAttribute("aria-current") === "page" && (a.style.textDecoration = "none", a.style.pointerEvents = "none", a.classList.add("no-underline"), a.classList.remove("hover:bg-blue-100"));
}));
}
}), t.length > 0 && N(t[0]), document.querySelectorAll(".page-indicator").forEach((e) => {
}), t.length > 0 && X(t[0]), document.querySelectorAll(".page-indicator").forEach((e) => {
e.classList.remove("text-red-600", "font-bold"), e.classList.add("text-slate-600", "font-semibold"), e.classList.contains("bg-amber-50") || e.classList.add("bg-blue-50");
}), i.forEach((e) => {
}), o.forEach((e) => {
const n = document.querySelector(`.page-indicator[data-page="${e}"]`);
n && (n.classList.remove("text-slate-600"), n.classList.add("text-red-600", "font-bold"));
});
}
function N(i) {
const t = i.closest(".lg\\:overflow-y-auto");
function X(o) {
const t = o.closest(".lg\\:overflow-y-auto");
if (t) {
const e = t.getBoundingClientRect(), n = i.getBoundingClientRect(), o = n.top < e.top, r = n.bottom > e.bottom;
(o || r) && i.scrollIntoView({
const e = t.getBoundingClientRect(), n = o.getBoundingClientRect(), i = n.top < e.top, r = n.bottom > e.bottom;
(i || r) && o.scrollIntoView({
behavior: "smooth",
block: "center"
});
}
}
function H(i, t, e, n = null) {
let o = document.querySelector("single-page-viewer");
o || (o = document.createElement("single-page-viewer"), document.body.appendChild(o));
const r = i.closest('[data-beilage="true"]') !== null, s = window.templateData && window.templateData.targetPage ? window.templateData.targetPage : 0;
o.show(i.src, i.alt, t, r, s, n);
function U(o, t, e, n = null) {
let i = document.querySelector("single-page-viewer");
i || (i = document.createElement("single-page-viewer"), document.body.appendChild(i));
const r = o.closest('[data-beilage="true"]') !== null, s = window.templateData && window.templateData.targetPage ? window.templateData.targetPage : 0;
i.show(o.src, o.alt, t, r, s, n);
}
function I() {
function L() {
document.getElementById("pageModal").classList.add("hidden");
}
function O() {
function G() {
if (window.pageObserver && (window.pageObserver.disconnect(), window.pageObserver = null), window.currentPageContainers = Array.from(document.querySelectorAll(".newspaper-page-container")), window.currentActiveIndex = 0, h(), document.querySelector(".newspaper-page-container")) {
let t = /* @__PURE__ */ new Set();
window.pageObserver = new IntersectionObserver(
(e) => {
if (e.forEach((n) => {
const o = window.currentPageContainers.indexOf(n.target);
o !== -1 && (n.isIntersecting ? t.add(o) : t.delete(o));
const i = window.currentPageContainers.indexOf(n.target);
i !== -1 && (n.isIntersecting ? t.add(i) : t.delete(i));
}), t.size > 0) {
const o = Array.from(t).sort((r, s) => r - s)[0];
o !== window.currentActiveIndex && (window.currentActiveIndex = o, h());
const i = Array.from(t).sort((r, s) => r - s)[0];
i !== window.currentActiveIndex && (window.currentActiveIndex = i, h());
}
},
{
@@ -174,21 +174,21 @@ function O() {
});
}
}
function R() {
function Y() {
if (window.currentActiveIndex > 0) {
let i = -1;
let o = -1;
const t = [];
window.currentPageContainers.forEach((n, o) => {
window.currentPageContainers.forEach((n, i) => {
const r = n.getBoundingClientRect(), s = window.innerHeight, a = Math.max(r.top, 0), l = Math.min(r.bottom, s), c = Math.max(0, l - a), g = r.height;
c / g >= 0.3 && t.push(o);
c / g >= 0.3 && t.push(i);
});
const e = Math.min(...t);
for (let n = e - 1; n >= 0; n--)
if (!t.includes(n)) {
i = n;
o = n;
break;
}
i === -1 && e > 0 && (i = e - 1), i >= 0 && (window.currentActiveIndex = i, window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
o === -1 && e > 0 && (o = e - 1), o >= 0 && (window.currentActiveIndex = o, window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
behavior: "smooth",
block: "start"
}), setTimeout(() => {
@@ -196,21 +196,21 @@ function R() {
}, 100));
}
}
function z() {
function J() {
if (window.currentActiveIndex < window.currentPageContainers.length - 1) {
let i = -1;
let o = -1;
const t = [];
window.currentPageContainers.forEach((n, o) => {
window.currentPageContainers.forEach((n, i) => {
const r = n.getBoundingClientRect(), s = window.innerHeight, a = Math.max(r.top, 0), l = Math.min(r.bottom, s), c = Math.max(0, l - a), g = r.height;
c / g >= 0.3 && t.push(o);
c / g >= 0.3 && t.push(i);
});
const e = Math.max(...t);
for (let n = e + 1; n < window.currentPageContainers.length; n++)
if (!t.includes(n)) {
i = n;
o = n;
break;
}
i === -1 && e < window.currentPageContainers.length - 1 && (i = e + 1), i >= 0 && i < window.currentPageContainers.length && (window.currentActiveIndex = i, window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
o === -1 && e < window.currentPageContainers.length - 1 && (o = e + 1), o >= 0 && o < window.currentPageContainers.length && (window.currentActiveIndex = o, window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
behavior: "smooth",
block: "start"
}), setTimeout(() => {
@@ -218,8 +218,8 @@ function z() {
}, 100));
}
}
function D() {
if (T()) {
function Q() {
if (q()) {
const t = document.querySelector("#newspaper-content .newspaper-page-container");
t && t.scrollIntoView({
behavior: "smooth",
@@ -233,13 +233,13 @@ function D() {
});
}
}
function T() {
const i = [];
function q() {
const o = [];
window.currentPageContainers.forEach((t, e) => {
const n = t.getBoundingClientRect(), o = window.innerHeight, r = Math.max(n.top, 0), s = Math.min(n.bottom, o), a = Math.max(0, s - r), l = n.height;
a / l >= 0.3 && i.push(e);
const n = t.getBoundingClientRect(), i = window.innerHeight, r = Math.max(n.top, 0), s = Math.min(n.bottom, i), a = Math.max(0, s - r), l = n.height;
a / l >= 0.3 && o.push(e);
});
for (const t of i) {
for (const t of o) {
const e = window.currentPageContainers[t];
if (e && e.id && e.id.includes("beilage-"))
return !0;
@@ -247,72 +247,72 @@ function T() {
return !1;
}
function h() {
const i = document.getElementById("prevPageBtn"), t = document.getElementById("nextPageBtn"), e = document.getElementById("beilageBtn");
if (i && (window.currentActiveIndex <= 0 ? i.style.display = "none" : i.style.display = "flex"), t && (window.currentActiveIndex >= window.currentPageContainers.length - 1 ? t.style.display = "none" : t.style.display = "flex"), e) {
const n = T(), o = e.querySelector("i");
n ? (e.title = "Zur Hauptausgabe", e.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", o && (o.className = "ri-file-text-line text-lg lg:text-xl")) : (e.title = "Zu Beilage", e.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", o && (o.className = "ri-attachment-line text-lg lg:text-xl"));
const o = document.getElementById("prevPageBtn"), t = document.getElementById("nextPageBtn"), e = document.getElementById("beilageBtn");
if (o && (window.currentActiveIndex <= 0 ? o.style.display = "none" : o.style.display = "flex"), t && (window.currentActiveIndex >= window.currentPageContainers.length - 1 ? t.style.display = "none" : t.style.display = "flex"), e) {
const n = q(), i = e.querySelector("i");
n ? (e.title = "Zur Hauptausgabe", e.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", i && (i.className = "ri-file-text-line text-lg lg:text-xl")) : (e.title = "Zu Beilage", e.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", i && (i.className = "ri-attachment-line text-lg lg:text-xl"));
}
}
function K() {
const i = document.getElementById("shareLinkBtn");
function ee() {
const o = document.getElementById("shareLinkBtn");
let t = "";
if (window.currentActiveIndex !== void 0 && window.currentPageContainers && window.currentPageContainers[window.currentActiveIndex]) {
const o = window.currentPageContainers[window.currentActiveIndex].querySelector("[data-page]");
o && (t = `/${o.getAttribute("data-page")}`);
const i = window.currentPageContainers[window.currentActiveIndex].querySelector("[data-page]");
i && (t = `/${i.getAttribute("data-page")}`);
}
const e = window.location.origin + window.location.pathname + t;
navigator.share ? navigator.share({
title: document.title,
url: e
}).catch((n) => {
x(e, i);
}) : x(e, i);
T(e, o);
}) : T(e, o);
}
function x(i, t) {
function T(o, t) {
if (navigator.clipboard)
navigator.clipboard.writeText(i).then(() => {
d(t, "Link kopiert!");
navigator.clipboard.writeText(o).then(() => {
u(t, "Link kopiert!");
}).catch((e) => {
d(t, "Kopieren fehlgeschlagen");
u(t, "Kopieren fehlgeschlagen");
});
else {
const e = document.createElement("textarea");
e.value = i, document.body.appendChild(e), e.select();
e.value = o, document.body.appendChild(e), e.select();
try {
const n = document.execCommand("copy");
d(t, n ? "Link kopiert!" : "Kopieren fehlgeschlagen");
u(t, n ? "Link kopiert!" : "Kopieren fehlgeschlagen");
} catch {
d(t, "Kopieren fehlgeschlagen");
u(t, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(e);
}
}
}
function V() {
const i = document.getElementById("citationBtn"), t = document.title || "KGPZ";
function te() {
const o = document.getElementById("citationBtn"), t = document.title || "KGPZ";
let e = window.location.origin + window.location.pathname;
e.includes("#") && (e = e.split("#")[0]);
const n = (/* @__PURE__ */ new Date()).toLocaleDateString("de-DE"), o = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${t}. Digital verfügbar unter: ${e} (Zugriff: ${n}).`;
const n = (/* @__PURE__ */ new Date()).toLocaleDateString("de-DE"), i = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${t}. Digital verfügbar unter: ${e} (Zugriff: ${n}).`;
if (navigator.clipboard)
navigator.clipboard.writeText(o).then(() => {
d(i, "Zitation kopiert!");
navigator.clipboard.writeText(i).then(() => {
u(o, "Zitation kopiert!");
}).catch((r) => {
d(i, "Kopieren fehlgeschlagen");
u(o, "Kopieren fehlgeschlagen");
});
else {
const r = document.createElement("textarea");
r.value = o, document.body.appendChild(r), r.select();
r.value = i, document.body.appendChild(r), r.select();
try {
const s = document.execCommand("copy");
d(i, s ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
u(o, s ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
} catch {
d(i, "Kopieren fehlgeschlagen");
u(o, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(r);
}
}
}
function d(i, t) {
function u(o, t) {
const e = document.querySelector(".simple-popup");
e && e.remove();
const n = document.createElement("div");
@@ -330,10 +330,10 @@ function d(i, t) {
transition: opacity 0.2s ease;
white-space: nowrap;
`;
const o = i.getBoundingClientRect(), r = window.innerHeight, s = window.innerWidth;
let a = o.left - 10, l = o.bottom + 8;
const i = o.getBoundingClientRect(), r = window.innerHeight, s = window.innerWidth;
let a = i.left - 10, l = i.bottom + 8;
const c = 120, g = 32;
a + c > s && (a = o.right - c + 10), l + g > r && (l = o.top - g - 8), n.style.left = Math.max(5, a) + "px", n.style.top = Math.max(5, l) + "px", document.body.appendChild(n), setTimeout(() => {
a + c > s && (a = i.right - c + 10), l + g > r && (l = i.top - g - 8), n.style.left = Math.max(5, a) + "px", n.style.top = Math.max(5, l) + "px", document.body.appendChild(n), setTimeout(() => {
n.style.opacity = "1";
}, 10), setTimeout(() => {
n.style.opacity = "0", setTimeout(() => {
@@ -341,125 +341,189 @@ function d(i, t) {
}, 200);
}, 2e3);
}
function j() {
let i = "", t = null;
function ne() {
let o = "", t = null;
const e = window.location.pathname.split("/");
if (e.length >= 4 && !isNaN(e[e.length - 1])) {
if (i = e[e.length - 1], t = document.getElementById(`page-${i}`), !t) {
if (o = e[e.length - 1], t = document.getElementById(`page-${o}`), !t) {
const n = document.querySelectorAll(".newspaper-page-container[data-pages]");
for (const o of n) {
const r = o.getAttribute("data-pages");
if (r && r.split(",").includes(i)) {
t = o;
for (const i of n) {
const r = i.getAttribute("data-pages");
if (r && r.split(",").includes(o)) {
t = i;
break;
}
}
}
t || (t = document.getElementById(`beilage-1-page-${i}`) || document.getElementById(`beilage-2-page-${i}`) || document.querySelector(`[id*="beilage"][id*="page-${i}"]`));
t || (t = document.getElementById(`beilage-1-page-${o}`) || document.getElementById(`beilage-2-page-${o}`) || document.querySelector(`[id*="beilage"][id*="page-${o}"]`));
}
t && i && setTimeout(() => {
t && o && setTimeout(() => {
t.scrollIntoView({
behavior: "smooth",
block: "start"
}), S(i);
}), E(o);
}, 300);
}
function b(i, t, e = !1) {
function m(o, t, e = !1) {
let n = "";
if (e)
n = window.location.origin + window.location.pathname + `#beilage-1-page-${i}`;
n = window.location.origin + window.location.pathname + `#beilage-1-page-${o}`;
else {
const r = window.location.pathname.split("/");
if (r.length >= 3) {
const s = r[1], a = r[2];
n = `${window.location.origin}/${s}/${a}/${i}`;
n = `${window.location.origin}/${s}/${a}/${o}`;
} else
n = window.location.origin + window.location.pathname + `/${i}`;
n = window.location.origin + window.location.pathname + `/${o}`;
}
const o = n;
const i = n;
if (navigator.clipboard)
navigator.clipboard.writeText(o).then(() => {
d(t, "Link kopiert!");
navigator.clipboard.writeText(i).then(() => {
u(t, "Link kopiert!");
}).catch((r) => {
d(t, "Kopieren fehlgeschlagen");
u(t, "Kopieren fehlgeschlagen");
});
else {
const r = document.createElement("textarea");
r.value = o, document.body.appendChild(r), r.select();
r.value = i, document.body.appendChild(r), r.select();
try {
const s = document.execCommand("copy");
d(t, s ? "Link kopiert!" : "Kopieren fehlgeschlagen");
u(t, s ? "Link kopiert!" : "Kopieren fehlgeschlagen");
} catch {
d(t, "Kopieren fehlgeschlagen");
u(t, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(r);
}
}
}
function m(i, t) {
function y(o, t) {
const e = document.title || "KGPZ", n = window.location.pathname.split("/");
let o;
let i;
if (n.length >= 3) {
const l = n[1], c = n[2];
o = `${window.location.origin}/${l}/${c}/${i}`;
i = `${window.location.origin}/${l}/${c}/${o}`;
} else
o = `${window.location.origin}${window.location.pathname}/${i}`;
const r = o, s = (/* @__PURE__ */ new Date()).toLocaleDateString("de-DE"), a = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${e}, Seite ${i}. Digital verfügbar unter: ${r} (Zugriff: ${s}).`;
i = `${window.location.origin}${window.location.pathname}/${o}`;
const r = i, s = (/* @__PURE__ */ new Date()).toLocaleDateString("de-DE"), a = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${e}, Seite ${o}. Digital verfügbar unter: ${r} (Zugriff: ${s}).`;
if (navigator.clipboard)
navigator.clipboard.writeText(a).then(() => {
d(t, "Zitation kopiert!");
u(t, "Zitation kopiert!");
}).catch((l) => {
d(t, "Kopieren fehlgeschlagen");
u(t, "Kopieren fehlgeschlagen");
});
else {
const l = document.createElement("textarea");
l.value = a, document.body.appendChild(l), l.select();
try {
const c = document.execCommand("copy");
d(t, c ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
u(t, c ? "Zitation kopiert!" : "Kopieren fehlgeschlagen");
} catch {
d(t, "Kopieren fehlgeschlagen");
u(t, "Kopieren fehlgeschlagen");
} finally {
document.body.removeChild(l);
}
}
}
function f() {
$(), O(), window.addEventListener("scroll", function() {
k();
const o = document.querySelectorAll(".author-section"), t = document.querySelectorAll(".scrollspy-link");
if (o.length === 0 || t.length === 0)
return;
function e() {
const i = [];
o.forEach((s) => {
s.getBoundingClientRect().top + window.scrollY;
const l = s.querySelector("div:first-child");
if (l) {
const c = l.getBoundingClientRect();
c.top + window.scrollY + c.height;
const d = c.top >= 0, p = c.bottom <= window.innerHeight;
d && p && i.push(s.getAttribute("id"));
}
});
const r = [];
t.forEach((s) => {
s.classList.remove("bg-blue-100", "text-blue-700", "font-medium", "border-red-500"), s.classList.add("text-gray-600", "border-transparent");
const a = s.getAttribute("data-target");
i.includes(a) && (s.classList.remove("text-gray-600", "border-transparent"), s.classList.add("bg-blue-100", "text-blue-700", "font-medium", "border-red-500"), r.push(s));
}), r.length > 0 && n(r);
}
function n(i) {
if (window.scrollspyManualNavigation) return;
const r = document.getElementById("scrollspy-nav");
if (!r) return;
const s = i[0], a = Math.max(
document.body.scrollHeight,
document.body.offsetHeight,
document.documentElement.clientHeight,
document.documentElement.scrollHeight,
document.documentElement.offsetHeight
), l = window.innerHeight, c = a - l, g = c > 0 ? window.scrollY / c : 0, d = r.clientHeight, w = r.scrollHeight - d;
if (w > 0) {
const H = g * w, B = s.getBoundingClientRect(), $ = r.getBoundingClientRect(), M = B.top - $.top + r.scrollTop, N = d / 2, R = M - N, v = 0.7, O = v * H + (1 - v) * R, x = Math.max(0, Math.min(w, O)), z = r.scrollTop;
Math.abs(x - z) > 10 && r.scrollTo({
top: x,
behavior: "smooth"
});
}
}
window.scrollspyScrollHandler = function() {
clearTimeout(window.scrollspyTimeout), window.scrollspyTimeout = setTimeout(e, 50);
}, window.addEventListener("scroll", window.scrollspyScrollHandler), window.scrollspyClickHandlers = [], t.forEach((i) => {
const r = function(s) {
s.preventDefault();
const a = document.getElementById(this.getAttribute("data-target"));
a && (window.scrollspyManualNavigation = !0, a.scrollIntoView({
behavior: "smooth",
block: "start"
}), setTimeout(() => {
window.scrollspyManualNavigation = !1;
}, 1e3));
};
window.scrollspyClickHandlers.push({ link: i, handler: r }), i.addEventListener("click", r);
}), e();
}
function k() {
window.scrollspyScrollHandler && (window.removeEventListener("scroll", window.scrollspyScrollHandler), window.scrollspyScrollHandler = null), window.scrollspyTimeout && (clearTimeout(window.scrollspyTimeout), window.scrollspyTimeout = null), window.scrollspyClickHandlers && (window.scrollspyClickHandlers.forEach(({ link: o, handler: t }) => {
o.removeEventListener("click", t);
}), window.scrollspyClickHandlers = null), window.scrollspyManualNavigation = !1;
}
function b() {
W(), G(), window.addEventListener("scroll", function() {
clearTimeout(window.scrollTimeout), window.scrollTimeout = setTimeout(() => {
P(), h();
I(), h();
}, 50);
}), j(), document.addEventListener("keydown", function(i) {
i.key === "Escape" && I();
}), ne(), document.addEventListener("keydown", function(o) {
o.key === "Escape" && L();
});
}
window.enlargePage = H;
window.closeModal = I;
window.scrollToPreviousPage = R;
window.scrollToNextPage = z;
window.scrollToBeilage = D;
window.shareCurrentPage = K;
window.generateCitation = V;
window.copyPagePermalink = b;
window.generatePageCitation = m;
function Z() {
v(), q(), document.querySelector(".newspaper-page-container") && f(), htmx.on("htmx:load", function(i) {
v();
}), document.body.addEventListener("htmx:afterSwap", function(i) {
window.enlargePage = U;
window.closeModal = L;
window.scrollToPreviousPage = Y;
window.scrollToNextPage = J;
window.scrollToBeilage = Q;
window.shareCurrentPage = ee;
window.generateCitation = te;
window.copyPagePermalink = m;
window.generatePageCitation = y;
function re() {
C(), j(), document.querySelector(".newspaper-page-container") && b(), document.querySelector(".author-section") && f(), htmx.on("htmx:load", function(o) {
C();
}), document.body.addEventListener("htmx:afterSwap", function(o) {
setTimeout(() => {
document.querySelector(".newspaper-page-container") && f();
document.querySelector(".newspaper-page-container") && b(), document.querySelector(".author-section") && f();
}, 100);
}), document.body.addEventListener("htmx:afterSettle", function(i) {
}), document.body.addEventListener("htmx:afterSettle", function(o) {
setTimeout(() => {
document.querySelector(".newspaper-page-container") && f();
document.querySelector(".newspaper-page-container") && b(), document.querySelector(".author-section") && f();
}, 200);
}), document.body.addEventListener("htmx:load", function(i) {
}), document.body.addEventListener("htmx:load", function(o) {
setTimeout(() => {
document.querySelector(".newspaper-page-container") && f();
document.querySelector(".newspaper-page-container") && b(), document.querySelector(".author-section") && f();
}, 100);
});
}
class W extends HTMLElement {
class oe extends HTMLElement {
constructor() {
super(), this.resizeObserver = null;
}
@@ -467,8 +531,8 @@ class W extends HTMLElement {
detectSidebarWidth() {
const t = document.querySelector('.lg\\:w-1\\/4, .lg\\:w-1\\/3, [class*="lg:w-1/"]');
if (t) {
const o = t.getBoundingClientRect().width;
return console.log("Detected sidebar width:", o, "px"), `${o}px`;
const i = t.getBoundingClientRect().width;
return console.log("Detected sidebar width:", i, "px"), `${i}px`;
}
const e = window.innerWidth;
return e < 1024 ? "0px" : e < 1280 ? `${Math.floor(e * 0.25)}px` : `${Math.floor(e * 0.2)}px`;
@@ -584,24 +648,24 @@ class W extends HTMLElement {
t.style.width = e, console.log("Updated sidebar width to:", e);
}
}
show(t, e, n, o = !1, r = 0, s = null) {
show(t, e, n, i = !1, r = 0, s = null) {
const a = this.querySelector("#single-page-image"), l = this.querySelector("#page-number"), c = this.querySelector("#page-icon");
this.querySelector("#page-indicator"), a.src = t, a.alt = e, this.currentPageNumber = n, this.currentIsBeilage = o, this.currentPartNumber = s;
this.querySelector("#page-indicator"), a.src = t, a.alt = e, this.currentPageNumber = n, this.currentIsBeilage = i, this.currentPartNumber = s;
const g = this.getIssueContext(n);
if (l.innerHTML = g ? `${g}, ${n}` : `${n}`, r && n === r) {
l.style.position = "relative";
const u = l.querySelector(".target-page-dot");
u && u.remove();
const d = l.querySelector(".target-page-dot");
d && d.remove();
const p = document.createElement("span");
p.className = "target-page-dot absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full z-10", p.title = "verlinkte Seite", l.appendChild(p);
}
if (s !== null)
c.innerHTML = `<span class="part-number bg-slate-100 text-slate-800 font-bold px-1.5 py-0.5 rounded border border-slate-400 flex items-center justify-center">${s}. Teil</span>`;
else {
const u = this.determinePageIconType(n, o);
c.innerHTML = this.getPageIconHTML(u);
const d = this.determinePageIconType(n, i);
c.innerHTML = this.getPageIconHTML(d);
}
this.updateNavigationButtons(), this.style.display = "block", document.body.style.overflow = "hidden", S(n);
this.updateNavigationButtons(), this.style.display = "block", document.body.style.overflow = "hidden", E(n);
}
close() {
this.style.display = "none", document.body.style.overflow = "";
@@ -639,28 +703,28 @@ class W extends HTMLElement {
}
// Share current page
shareCurrentPage() {
if (typeof b == "function") {
if (typeof m == "function") {
const t = this.querySelector("#share-btn");
b(this.currentPageNumber, t, this.currentIsBeilage);
m(this.currentPageNumber, t, this.currentIsBeilage);
}
}
// Generate citation for current page
generatePageCitation() {
if (typeof m == "function") {
if (typeof y == "function") {
const t = this.querySelector("#cite-btn");
m(this.currentPageNumber, t);
y(this.currentPageNumber, t);
}
}
// Update navigation button visibility based on available pages
updateNavigationButtons() {
const t = this.querySelector("#prev-page-btn"), e = this.querySelector("#next-page-btn"), { prevPage: n, nextPage: o } = this.getAdjacentPages();
const t = this.querySelector("#prev-page-btn"), e = this.querySelector("#next-page-btn"), { prevPage: n, nextPage: i } = this.getAdjacentPages();
n !== null ? (t.disabled = !1, t.className = t.className.replace("opacity-50 cursor-not-allowed", ""), t.className = t.className.replace(
"bg-gray-50 text-gray-400",
"bg-gray-100 text-gray-700"
)) : (t.disabled = !0, t.className.includes("opacity-50") || (t.className += " opacity-50 cursor-not-allowed"), t.className = t.className.replace(
"bg-gray-100 text-gray-700",
"bg-gray-50 text-gray-400"
)), o !== null ? (e.disabled = !1, e.className = e.className.replace("opacity-50 cursor-not-allowed", ""), e.className = e.className.replace(
)), i !== null ? (e.disabled = !1, e.className = e.className.replace("opacity-50 cursor-not-allowed", ""), e.className = e.className.replace(
"bg-gray-50 text-gray-400",
"bg-gray-100 text-gray-700"
)) : (e.disabled = !0, e.className.includes("opacity-50") || (e.className += " opacity-50 cursor-not-allowed"), e.className = e.className.replace(
@@ -684,10 +748,10 @@ class W extends HTMLElement {
return console.log("Container page:", l, "parsed:", c), c;
}).filter((a) => a !== null).sort((a, l) => a - l);
console.log("All pages found:", n), console.log("Current page:", this.currentPageNumber);
const o = n.indexOf(this.currentPageNumber);
console.log("Current index:", o);
const i = n.indexOf(this.currentPageNumber);
console.log("Current index:", i);
let r = null, s = null;
return o > 0 && (r = n[o - 1]), o < n.length - 1 && (s = n[o + 1]), console.log("Adjacent pages - prev:", r, "next:", s), { prevPage: r, nextPage: s };
return i > 0 && (r = n[i - 1]), i < n.length - 1 && (s = n[i + 1]), console.log("Adjacent pages - prev:", r, "next:", s), { prevPage: r, nextPage: s };
}
// Navigate to previous page
goToPreviousPage() {
@@ -705,12 +769,12 @@ class W extends HTMLElement {
`${e}[data-page-container="${t}"]`
);
if (n) {
const o = n.querySelector(".newspaper-page-image");
if (o) {
const i = n.querySelector(".newspaper-page-image");
if (i) {
let r = null;
this.currentPartNumber !== null && (r = this.getPartNumberForPage(t)), this.show(
o.src,
o.alt,
i.src,
i.alt,
t,
this.currentIsBeilage,
0,
@@ -725,17 +789,17 @@ class W extends HTMLElement {
if (e) {
const n = e.querySelector(".part-number");
if (n) {
const o = n.textContent.match(/(\d+)\./);
if (o)
return parseInt(o[1]);
const i = n.textContent.match(/(\d+)\./);
if (i)
return parseInt(i[1]);
}
}
return null;
}
// Toggle sidebar visibility
toggleSidebar() {
const t = this.querySelector("#sidebar-spacer"), e = this.querySelector("#sidebar-toggle-btn"), n = e.querySelector("i"), o = t.style.width, r = o === "0px" || o === "0";
if (console.log("Current state - isCollapsed:", r), console.log("Current width:", o), r) {
const t = this.querySelector("#sidebar-spacer"), e = this.querySelector("#sidebar-toggle-btn"), n = e.querySelector("i"), i = t.style.width, r = i === "0px" || i === "0";
if (console.log("Current state - isCollapsed:", r), console.log("Current width:", i), r) {
const s = this.detectSidebarWidth();
t.style.width = s, e.className = "w-10 h-10 bg-slate-100 hover:bg-slate-200 text-slate-700 border border-slate-300 rounded flex items-center justify-center transition-colors duration-200 cursor-pointer", n.className = "ri-sidebar-fold-line text-lg font-bold", e.title = "Inhaltsverzeichnis ausblenden", console.log("Expanding sidebar to:", s);
} else
@@ -750,9 +814,9 @@ class W extends HTMLElement {
if (s) {
const c = s.querySelector(".page-indicator");
if (c) {
const g = c.textContent.trim(), u = g.match(/(\d{1,2}\.\d{1,2}\.\d{4}\s+Nr\.\s+\d+)/);
if (u)
return u[1];
const g = c.textContent.trim(), d = g.match(/(\d{1,2}\.\d{1,2}\.\d{4}\s+Nr\.\s+\d+)/);
if (d)
return d[1];
const p = g.match(/(\d{4})\s+Nr\.\s+(\d+)/);
if (p)
return `${p[1]} Nr. ${p[2]}`;
@@ -763,9 +827,9 @@ class W extends HTMLElement {
return `${l[1]} Nr. ${l[2]}`;
} else
return "";
const o = e.match(/\/(\d{4})\/(\d+)/);
if (o)
return n ? `${o[1]} Nr. ${o[2]}` : "";
const i = e.match(/\/(\d{4})\/(\d+)/);
if (i)
return n ? `${i[1]} Nr. ${i[2]}` : "";
const r = document.querySelector(".page-indicator");
if (r) {
const a = r.textContent.trim().match(/(\d{4})\s+Nr\.\s+(\d+)/);
@@ -775,15 +839,15 @@ class W extends HTMLElement {
return "KGPZ";
}
}
customElements.define("single-page-viewer", W);
document.body.addEventListener("htmx:beforeRequest", function(i) {
customElements.define("single-page-viewer", oe);
document.body.addEventListener("htmx:beforeRequest", function(o) {
const t = document.querySelector("single-page-viewer");
t && t.style.display !== "none" && (console.log("Cleaning up single page viewer before HTMX navigation"), t.destroy());
t && t.style.display !== "none" && (console.log("Cleaning up single page viewer before HTMX navigation"), t.destroy()), k();
});
window.addEventListener("beforeunload", function() {
const i = document.querySelector("single-page-viewer");
i && i.destroy();
const o = document.querySelector("single-page-viewer");
o && o.destroy();
});
export {
Z as setup
re as setup
};

File diff suppressed because one or more lines are too long

View File

@@ -1,21 +1,22 @@
{{ if ne (len .model.Search) 1 }}
{{ $agent := index $.model.Agents .model.Search }}
{{ if not $agent }}
<div class="max-w-4xl mx-auto px-4 py-8">
<div class="bg-red-50 border border-red-200 rounded-lg p-4">
<div class="max-w-6xl mx-auto px-8 py-8">
<div class="bg-red-50 border border-red-200 rounded-lg p-6">
<div class="flex items-center">
<i class="ri-error-warning-line text-red-600 text-xl mr-2"></i>
<span class="text-red-800">Person nicht gefunden: <strong>{{ .model.Search }}</strong></span>
<i class="ri-error-warning-line text-red-600 text-2xl mr-3"></i>
<span class="text-red-800 text-lg">Person nicht gefunden: <strong>{{ .model.Search }}</strong></span>
</div>
</div>
</div>
{{ else }}
<div class="max-w-4xl mx-auto px-4 py-8">
<div class="mb-6">
<div class="max-w-full mx-auto px-8 py-8">
<div class="mb-8">
{{ $letter := Upper (FirstLetter $agent.ID) }}
<a href="/akteure/{{ $letter }}" class="inline-flex items-center text-blue-600 hover:text-blue-800 transition-colors">
<i class="ri-arrow-left-line mr-2"></i>
Zurück zu Buchstabe {{ $letter }}
<a href="/akteure/{{ $letter }}" class="inline-flex items-center text-blue-600
hover:text-blue-800 transition-colors text-xl">
<i class="ri-arrow-left-line mr-3 text-xl"></i>
{{ $letter }}
</a>
</div>
<div>{{ template "_akteur" $agent }}</div>
@@ -23,31 +24,71 @@
{{ end }}
{{ else }}
<div class="max-w-7xl mx-auto px-4 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 mb-4">Personen & Körperschaften</h1>
<p class="text-gray-600">Verzeichnis aller in der Zeitung erwähnten Personen und Institutionen</p>
<div class="max-w-full mx-auto px-8 py-8">
<div class="mb-10">
<div class="bg-slate-100 px-6 py-4 rounded-lg mb-6">
{{ if eq .model.Search "autoren" }}
<h1 class="text-4xl font-bold text-gray-900 mb-2">Autoren</h1>
<p class="text-gray-700 text-lg">Personen, die Beiträge in der Zeitung verfasst haben</p>
{{ else }}
<h1 class="text-4xl font-bold text-gray-900 mb-2">Personen & Körperschaften</h1>
<p class="text-gray-700 text-lg">Verzeichnis aller in der Zeitung erwähnten Personen und Institutionen</p>
{{ end }}
</div>
<div class="flex items-center gap-4 mb-6">
<label class="inline-flex items-center">
<input type="checkbox"
class="form-checkbox h-5 w-5 text-blue-600 rounded"
{{ if eq .model.Search "autoren" }}checked{{ end }}
hx-get="{{ if eq .model.Search "autoren" }}/akteure/a{{ else }}/akteure/autoren{{ end }}"
hx-target="body"
hx-push-url="true">
<span class="ml-2 text-lg text-gray-700">Nur Autoren anzeigen</span>
</label>
</div>
</div>
<!-- Alphabet Navigation -->
<div class="mb-8 p-4 bg-gray-50 rounded-lg">
<div class="flex flex-wrap gap-2">
<div class="mb-10 p-6 bg-gray-50 rounded-lg">
<div class="flex flex-wrap gap-3">
{{ range $_, $l := .model.AvailableLetters }}
<a href="/akteure/{{ $l }}" class="inline-flex items-center justify-center w-8 h-8 bg-white border border-gray-300 rounded hover:bg-blue-50 hover:border-blue-300 font-medium text-gray-700 hover:text-blue-700 transition-colors">
<a href="/akteure/{{ $l }}" class="inline-flex items-center justify-center w-10 h-10 bg-white border border-gray-300 rounded hover:bg-blue-50 hover:border-blue-300 font-medium text-gray-700 hover:text-blue-700 transition-colors text-lg">
{{ $l }}
</a>
{{ end }}
</div>
</div>
<!-- People List - Dictionary Column Layout -->
<div class="columns-1 lg:columns-2 gap-8 space-y-0">
{{ range $_, $id := .model.Sorted }}
{{ $a := index $.model.Agents $id }}
<div class="break-inside-avoid mb-4 bg-stone-100 rounded-lg p-4 border border-stone-200">
{{ template "_akteur" $a }}
<!-- Two Column Layout: Scrollspy + Content -->
<div class="flex gap-8">
<!-- Scrollspy Navigation - Hidden on smaller screens, shown on 2xl+ -->
<div class="hidden 2xl:block w-80 flex-shrink-0">
<div class="sticky top-8 h-screen">
<div class="bg-white border border-gray-200 rounded-lg p-4 h-full flex flex-col">
<h3 class="text-lg font-bold text-gray-900 mb-4">Auf dieser Seite</h3>
<nav class="flex-1 overflow-y-auto" id="scrollspy-nav">
{{ range $_, $id := .model.Sorted }}
{{ $a := index $.model.Agents $id }}
<a href="#author-{{ $id }}"
class="block px-3 py-1 text-base text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded scrollspy-link transition-colors border-l-4 border-transparent"
data-target="author-{{ $id }}">
{{ index $a.Names 0 }}
</a>
{{ end }}
</nav>
</div>
</div>
{{ end }}
</div>
<!-- People List - Main Content -->
<div class="flex-1 space-y-6">
{{ range $_, $id := .model.Sorted }}
{{ $a := index $.model.Agents $id }}
<div id="author-{{ $id }}" class="bg-stone-100 rounded-lg p-6 border border-stone-200 scroll-mt-8 author-section">
{{ template "_akteur" $a }}
</div>
{{ end }}
</div>
</div>
</div>
{{ end }}

View File

@@ -1,7 +1,14 @@
<title>
KGPZ &ndash;
{{ if ne (len .model.Search) 1 }}
{{ index (index .model.Agents .model.Search).Names 0 }}
{{ if eq .model.Search "autoren" }}
Autoren
{{ else if ne (len .model.Search) 1 }}
{{ $agent := index .model.Agents .model.Search }}
{{ if $agent }}
{{ index $agent.Names 0 }}
{{ else }}
Person nicht gefunden
{{ end }}
{{ else }}
Personen &amp; Körperschaften:
{{ Upper .model.Search }}

View File

@@ -0,0 +1,54 @@
<div class="max-w-full mx-auto px-8 py-8">
<div class="mb-10">
<div class="bg-slate-100 px-6 py-4 rounded-lg mb-6">
<h1 class="text-4xl font-bold text-gray-900 mb-2">Autoren</h1>
<p class="text-gray-700 text-lg">Personen, die Beiträge in der Zeitung verfasst haben</p>
</div>
<div class="flex items-center gap-4 mb-6">
<label class="inline-flex items-center">
<input type="checkbox"
class="form-checkbox h-5 w-5 text-blue-600 rounded"
checked
hx-get="/akteure/a"
hx-target="body"
hx-push-url="true">
<span class="ml-2 text-lg text-gray-700">Nur Autoren anzeigen</span>
</label>
</div>
</div>
<!-- Two Column Layout: Scrollspy + Content -->
<div class="flex gap-8">
<!-- Scrollspy Navigation - Hidden on smaller screens, shown on 2xl+ -->
<div class="hidden 2xl:block w-80 flex-shrink-0">
<div class="sticky top-8 h-screen">
<div class="bg-white border border-gray-200 rounded-lg p-4 h-full flex flex-col">
<h3 class="text-lg font-bold text-gray-900 mb-4">Auf dieser Seite</h3>
<nav class="flex-1 overflow-y-auto" id="scrollspy-nav">
{{ range $_, $id := .model.Sorted }}
{{ $a := index $.model.Agents $id }}
<a href="#author-{{ $id }}"
class="block px-3 py-1 text-base text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded scrollspy-link transition-colors border-l-4 border-transparent"
data-target="author-{{ $id }}">
{{ index $a.Names 0 }}
</a>
{{ end }}
</nav>
</div>
</div>
</div>
<!-- Authors List - Main Content -->
<div class="flex-1 space-y-6">
{{ range $_, $id := .model.Sorted }}
{{ $a := index $.model.Agents $id }}
<div id="author-{{ $id }}" class="bg-stone-100 rounded-lg p-6 border border-stone-200 scroll-mt-8 author-section">
{{ template "_akteur_header" $a }}
{{ template "_akteur_beitraege" $a }}
</div>
{{ end }}
</div>
</div>
</div>

View File

@@ -0,0 +1 @@
<title>KGPZ &ndash; Autoren</title>

View File

@@ -1,144 +1,8 @@
{{ $a := . }}
{{ if and $a (ne (len $a.Names) 0) }}
{{ $gnd := GetGND $a.GND }}
{{ $works := LookupWorks $a }}
{{ $pieces := LookupPieces $a }}
<div>
<!-- Name and external links -->
<div class="flex items-start justify-between gap-4">
<div class="flex-1">
<!-- Large serif name - bold -->
<div class="text-xl font-serif font-bold text-gray-900 leading-tight">
<a href="/akteure/{{ $a.ID }}" class="hover:text-blue-600 transition-colors">
{{ index $a.Names 0 }}
</a>
</div>
<!-- Years only below name -->
{{ if ne $gnd nil }}
{{- if or (ne (len $gnd.DateOfBirth) 0) (ne (len $gnd.DateOfDeath) 0) -}}
<div class="text-gray-600 text-sm mt-1">
{{- if ne (len $gnd.DateOfBirth) 0 -}}
{{ HRDateYear (index $gnd.DateOfBirth 0) }}
{{- end -}}
{{- if ne (len $gnd.DateOfDeath) 0 -}}
{{ HRDateYear (index $gnd.DateOfDeath 0) }}
{{- end -}}
</div>
{{- end -}}
{{ end }}
<!-- First three professions -->
{{ if ne $gnd nil }}
{{- if ne (len $gnd.ProfessionOrOccupation) 0 -}}
<div class="text-gray-600 text-sm mt-1">
{{ range $i, $prof := $gnd.ProfessionOrOccupation }}
{{ if lt $i 3 }}
{{ if gt $i 0 }} · {{ end }}{{ $prof.Label }}
{{ end }}
{{ end }}
</div>
{{- end -}}
{{ end }}
</div>
<!-- External link symbols on the right -->
<div class="flex gap-2 flex-shrink-0">
{{- if ne $gnd nil -}}
{{- /* Wikipedia link if available */ -}}
{{- if ne (len $gnd.Wikipedia) 0 -}}
<a href="{{ (index $gnd.Wikipedia 0).ID }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="Wikipedia">
<i class="ri-wikipedia-line"></i>
</a>
{{- end -}}
{{- /* GND link if available */ -}}
{{- if ne $a.GND "" -}}
<a href="{{ $a.GND }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="Gemeinsame Normdatei">
<i class="ri-links-line"></i>
</a>
{{- else -}}
{{- /* VIAF link if no GND available */ -}}
{{- if ne (len $gnd.SameAs) 0 -}}
{{ range $_, $ref := $gnd.SameAs }}
{{- if ne $ref.ID "" -}}
<a href="{{ $ref.ID }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="External Link">
<i class="ri-global-line"></i>
</a>
{{- end -}}
{{ end }}
{{- end -}}
{{- end -}}
{{- end -}}
</div>
</div>
{{- if ne (len $works) 0 -}}
<div class="mt-3">
<div class="text-sm font-medium text-gray-700 mb-2">
<i class="ri-book-line mr-1"></i>Werke
</div>
{{ range $_, $w := $works }}
<div class="mb-2">
{{- if ne (len $w.Item.Citation.InnerXML ) 0 -}}
<div class="text-sm">
<span class="italic">
<script type="application/xml" xslt-template="transform-citation" xslt-onload>
<xml>
{{- Safe $w.Item.Citation.InnerXML -}}
</xml>
</script>
</span>
{{- range $_, $url := $w.Item.URLs -}}
<span class="ml-1">
<a href="{{ $url.Address }}" target="_blank" class="text-blue-600 hover:text-blue-800 text-sm">
{{ $url.Chardata }} <i class="ri-external-link-line text-xs"></i>
</a>
</span>
{{- end -}}
</div>
{{- end -}}
{{ $workPieces := LookupPieces $w.Item }}
{{ if len $workPieces }}
<div class="flex flex-wrap gap-1 mt-1">
{{ range $_, $p := $workPieces }}
{{ range $_, $issue := $p.Item.IssueRefs }}
<span class="inline-block bg-green-50 hover:bg-green-100 text-green-700 hover:text-green-800 px-2 py-1 rounded text-xs transition-colors">
{{ template "_citation" $issue }}
</span>
{{ end }}
{{ end }}
</div>
{{ end }}
</div>
{{ end }}
</div>
{{ end }}
{{ $pieces := LookupPieces $a }}
{{ if ne (len $pieces) 0 }}
<div class="mt-3">
<div class="text-sm font-medium text-gray-700 mb-2">
<i class="ri-newspaper-line mr-1"></i>Beiträge
</div>
<div class="space-y-2">
{{ range $_, $p := SortPiecesByDate $pieces }}
<div class="border-l-2 border-gray-200 pl-3">
<div class="text-sm">
{{ template "_piece_summary" $p.Item }}
</div>
<div class="mt-1 flex flex-wrap gap-1">
{{ range $_, $issue := $p.Item.IssueRefs }}
<span class="inline-block bg-green-50 hover:bg-green-100 text-green-700 hover:text-green-800 px-2 py-1 rounded text-xs transition-colors">
{{ template "_citation" $issue }}
</span>
{{ end }}
</div>
</div>
{{ end }}
</div>
</div>
{{ end }}
{{ template "_akteur_header" $a }}
{{ template "_akteur_werke" $a }}
{{ template "_akteur_beitraege" $a }}
</div>
{{ end }}

View File

@@ -0,0 +1,74 @@
{{ $a := . }}
{{ $pieces := LookupPieces $a }}
{{ if ne (len $pieces) 0 }}
<div class="mt-8">
<div class="px-4 py-3 rounded-lg mb-4">
<h2 class="text-lg font-bold text-gray-900">
<i class="ri-newspaper-line mr-2"></i>Beiträge
</h2>
</div>
<div class="space-y-2">
{{- /* Group pieces by title and work reference */ -}}
{{- $groupedPieces := dict -}}
{{- range $_, $p := SortPiecesByDate $pieces -}}
{{- $groupKey := "" -}}
{{- if $p.Item.Title -}}
{{- $groupKey = index $p.Item.Title 0 -}}
{{- else if $p.Item.WorkRefs -}}
{{- $work := GetWork (index $p.Item.WorkRefs 0).Ref -}}
{{- if $work.PreferredTitle -}}
{{- $groupKey = $work.PreferredTitle -}}
{{- else if $work.Citation.Title -}}
{{- $groupKey = $work.Citation.Title -}}
{{- end -}}
{{- else if $p.Item.Incipit -}}
{{- $groupKey = index $p.Item.Incipit 0 -}}
{{- else -}}
{{- $groupKey = printf "untitled-%s" $p.Item.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-6">
{{- /* Display grouped pieces */ -}}
{{- range $groupKey, $groupedItems := $groupedPieces -}}
<div class="border-l-2 border-gray-200 pl-4 break-inside-avoid">
<div class="text-lg">
{{- /* Use first piece for display text */ -}}
{{ template "_piece_summary" (dict "Piece" (index $groupedItems 0).Item "CurrentActorID" $a.ID) }}
{{- /* Show all citations from all pieces in this group */ -}}
{{- range $_, $groupItem := $groupedItems -}}
{{- range $i, $issue := $groupItem.Item.IssueRefs -}}
{{ $issueData := GetIssue (printf "%d-%d" $issue.When.Year $issue.Nr) }}
{{- $url := printf "/%s/%d" $issue.When $issue.Nr -}}
{{- if $issue.Von -}}
{{- if $issue.Beilage -}}
{{- $url = printf "%s#beilage-%d-page-%d" $url $issue.Beilage $issue.Von -}}
{{- else -}}
{{- $url = printf "%s/%d" $url $issue.Von -}}
{{- end -}}
{{- end -}}
<a href="{{ $url }}" class="inline-block text-sm bg-green-50 text-green-700 hover:bg-green-100 hover:text-green-800 px-3 py-2 rounded ml-2 no-underline">
{{- if $issueData -}}
{{ $issueData.Datum.When.Day }}.{{ $issueData.Datum.When.Month }}.{{ $issueData.Datum.When.Year }}/{{ $issue.Nr }}, S. {{ $issue.Von }}{{- if and $issue.Bis (ne $issue.Von $issue.Bis) }}-{{ $issue.Bis }}{{ end }}
{{- else -}}
{{ $issue.When.Day }}.{{ $issue.When.Month }}.{{ $issue.When.Year }}/{{ $issue.Nr }}, S. {{ $issue.Von }}{{- if and $issue.Bis (ne $issue.Von $issue.Bis) }}-{{ $issue.Bis }}{{ end }}
{{- end -}}
</a>
{{- end -}}
{{- end -}}
</div>
</div>
{{- end -}}
</div>
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,73 @@
{{ $a := . }}
{{ if and $a (ne (len $a.Names) 0) }}
{{ $gnd := GetGND $a.GND }}
<!-- Name and external links -->
<div class="flex items-start justify-between gap-4">
<div class="flex-1">
<!-- Large serif name - bold -->
<div class="text-2xl font-serif font-bold text-gray-900 leading-tight">
<a href="/akteure/{{ $a.ID }}" class="hover:text-blue-600 transition-colors">
{{ index $a.Names 0 }}
</a>
</div>
<!-- Years only below name -->
{{ if ne $gnd nil }}
{{- if or (ne (len $gnd.DateOfBirth) 0) (ne (len $gnd.DateOfDeath) 0) -}}
<div class="text-gray-600 text-xl mt-2">
{{- if ne (len $gnd.DateOfBirth) 0 -}}
{{ HRDateYear (index $gnd.DateOfBirth 0) }}
{{- end -}}
{{- if ne (len $gnd.DateOfDeath) 0 -}}
{{ HRDateYear (index $gnd.DateOfDeath 0) }}
{{- end -}}
</div>
{{- end -}}
{{ end }}
<!-- First three professions -->
{{ if ne $gnd nil }}
{{- if ne (len $gnd.ProfessionOrOccupation) 0 -}}
<div class="text-gray-600 text-xl mt-2">
{{ range $i, $prof := $gnd.ProfessionOrOccupation }}
{{ if lt $i 3 }}
{{ if gt $i 0 }} · {{ end }}{{ $prof.Label }}
{{ end }}
{{ end }}
</div>
{{- end -}}
{{ end }}
</div>
<!-- External link symbols on the right -->
<div class="flex gap-2 flex-shrink-0">
{{- if ne $gnd nil -}}
{{- /* Wikipedia link if available */ -}}
{{- if ne (len $gnd.Wikipedia) 0 -}}
<a href="{{ (index $gnd.Wikipedia 0).ID }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="Wikipedia">
<i class="ri-wikipedia-line"></i>
</a>
{{- end -}}
{{- /* GND link if available */ -}}
{{- if ne $a.GND "" -}}
<a href="{{ $a.GND }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="Gemeinsame Normdatei">
<i class="ri-links-line"></i>
</a>
{{- else -}}
{{- /* VIAF link if no GND available */ -}}
{{- if ne (len $gnd.SameAs) 0 -}}
{{ range $_, $ref := $gnd.SameAs }}
{{- if ne $ref.ID "" -}}
<a href="{{ $ref.ID }}" target="_blank" class="text-gray-500 hover:text-blue-600 transition-colors text-lg" title="External Link">
<i class="ri-global-line"></i>
</a>
{{- end -}}
{{ end }}
{{- end -}}
{{- end -}}
{{- end -}}
</div>
</div>
{{ end }}

View File

@@ -0,0 +1,188 @@
{{ $a := . }}
{{ $works := LookupWorks $a }}
{{- if ne (len $works) 0 -}}
<div class="mt-8">
<div class="px-4 py-3 rounded-lg mb-4">
<h2 class="text-lg font-bold text-gray-900">
<i class="ri-book-line mr-2"></i>Werke
</h2>
</div>
<div class="columns-2 gap-6">
{{ range $_, $w := $works }}
<div class="mb-2 break-inside-avoid pl-4">
{{- if ne (len $w.Item.Citation.InnerXML ) 0 -}}
<div class="text-lg">
<span class="italic">
<script type="application/xml" xslt-template="transform-citation" xslt-onload>
<xml>
{{- Safe $w.Item.Citation.InnerXML -}}
</xml>
</script>
</span>
{{- range $_, $url := $w.Item.URLs -}}
<span class="ml-1">
<a href="{{ $url.Address }}" target="_blank" class="text-blue-600 hover:text-blue-800 text-sm">
{{ $url.Chardata }} <i class="ri-external-link-line text-xs"></i>
</a>
</span>
{{- end -}}
</div>
{{- end -}}
{{ $workPieces := LookupPieces $w.Item }}
{{ if len $workPieces }}
<div class="mt-1 text-lg">
{{- /* Group pieces by category and display inline */ -}}
{{- $groupedByCategory := dict -}}
{{- range $_, $p := $workPieces -}}
{{- $categoryFlags := GetCategoryFlags $p.Item -}}
{{- $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 -}}
{{- if eq (len $categories) 0 -}}
{{- $categories = append $categories "Beitrag" -}}
{{- end -}}
{{- $categoryName := "" -}}
{{- if eq (len $categories) 0 -}}
{{- $categoryName = "Beitrag" -}}
{{- else -}}
{{- $sortedCategories := sortStrings $categories -}}
{{- $categoryName = joinWithUnd $sortedCategories -}}
{{- end -}}
{{- $existing := index $groupedByCategory $categoryName -}}
{{- if $existing -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $categoryName (append $existing $p)) -}}
{{- else -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $categoryName (slice $p)) -}}
{{- end -}}
{{- end -}}
{{- /* Display each category group */ -}}
{{- range $categoryName, $categoryPieces := $groupedByCategory -}}
<span class="inline-block text-sm bg-green-50 text-green-700 px-3 py-2 rounded ml-2">
{{ $categoryName }}
{{- /* Check for additional authors (non-current actor) */ -}}
{{- $additionalAuthorIDs := slice -}}
{{- range $_, $p := $categoryPieces -}}
{{- range $agentref := $p.Item.AgentRefs -}}
{{- if and (or (eq $agentref.Category "") (eq $agentref.Category "autor")) (ne $agentref.Ref $a.ID) -}}
{{- $additionalAuthorIDs = append $additionalAuthorIDs $agentref.Ref -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- $uniqueAuthorIDs := unique $additionalAuthorIDs -}}
{{- if $uniqueAuthorIDs -}}
{{ " " }}von {{ range $i, $authorID := $uniqueAuthorIDs }}{{- if gt $i 0 }} und {{ end }}{{- $agent := GetAgent $authorID -}}{{- if and $agent (gt (len $agent.Names) 0) -}}<a href="/akteure/{{ $authorID }}" class="text-green-700 hover:text-green-900 underline text-sm font-bold">{{ index $agent.Names 0 }}</a>{{- end -}}{{ end }}
{{- end -}}{{ ":" }}
</span>
{{- /* Show all citations for this category */ -}}
{{- range $_, $p := $categoryPieces -}}
{{- range $_, $issue := $p.Item.IssueRefs -}}
{{ $issueData := GetIssue (printf "%d-%d" $issue.When.Year $issue.Nr) }}
{{- $url := printf "/%s/%d" $issue.When $issue.Nr -}}
{{- if $issue.Von -}}
{{- if $issue.Beilage -}}
{{- $url = printf "%s#beilage-%d-page-%d" $url $issue.Beilage $issue.Von -}}
{{- else -}}
{{- $url = printf "%s/%d" $url $issue.Von -}}
{{- end -}}
{{- end -}}
<a href="{{ $url }}" class="inline-block text-sm bg-blue-50 text-blue-700 hover:bg-blue-100 hover:text-blue-800 px-3 py-2 rounded ml-2 no-underline">
{{- if $issueData -}}
{{ $issueData.Datum.When.Day }}.{{ $issueData.Datum.When.Month }}.{{ $issueData.Datum.When.Year }}/{{ $issue.Nr }}, S. {{ $issue.Von }}{{- if and $issue.Bis (ne $issue.Von $issue.Bis) }}-{{ $issue.Bis }}{{ end }}
{{- else -}}
{{ $issue.When.Day }}.{{ $issue.When.Month }}.{{ $issue.When.Year }}/{{ $issue.Nr }}, S. {{ $issue.Von }}{{- if and $issue.Bis (ne $issue.Von $issue.Bis) }}-{{ $issue.Bis }}{{ end }}
{{- end -}}
</a>
{{- end -}}
{{- end -}}
{{- end -}}
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
{{ end }}

View File

@@ -1,4 +1,5 @@
{{- $piece := . -}}
{{- $piece := .Piece -}}
{{- $currentActorID := .CurrentActorID -}}
<div class="leading-snug">
{{- $categoryFlags := GetCategoryFlags $piece -}}
@@ -42,14 +43,113 @@
{{- end -}}
{{- end -}}
{{- /* Generate piece descriptions */ -}}
{{- /* 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" -}}
{{- $authorFound := 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) -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>, Rezension von:
{{- if ne $agentref.Ref $currentActorID -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>,
{{- end -}}
{{ $categoryName }} von:
{{ 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 }}
@@ -66,7 +166,7 @@
{{- end -}}
{{- end -}}
{{- if not $authorFound -}}
Rezension von:
{{ $categoryName }} von:
{{ 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 }}
@@ -79,104 +179,24 @@
{{ end }}
{{- end -}}
{{- else if $categoryFlags.Gedicht -}}
{{- $authorFound := 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) -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>, Gedicht{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- $authorFound = true -}}
{{- break -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $authorFound -}}
Gedicht{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- end -}}
{{- else if $categoryFlags.Aufsatz -}}
{{- $authorFound := 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) -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>, Aufsatz{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- $authorFound = true -}}
{{- break -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $authorFound -}}
Aufsatz{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- end -}}
{{- else if $categoryFlags.Theaterkritik -}}
{{- $authorFound := 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) -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>, Theaterkritik{{ if $workTitle }} zu <em>{{ $workTitle }}</em>{{ else if $title }} zu <em>{{ $title }}</em>{{ end }}
{{- $authorFound = true -}}
{{- break -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $authorFound -}}
Theaterkritik{{ if $workTitle }} zu <em>{{ $workTitle }}</em>{{ else if $title }} zu <em>{{ $title }}</em>{{ end }}
{{- end -}}
{{- else if $categoryFlags.Brief -}}
{{ if $categoryFlags.Nachruf }}Kondolenzbrief{{ else }}Leserbrief{{ end }}
{{- 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) -}}
von <a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>
{{- break -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- else if $categoryFlags.Erzaehlung -}}
{{- $authorFound := 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) -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>, Erzählung{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- $authorFound = true -}}
{{- break -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $authorFound -}}
Erzählung{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- end -}}
{{- else if $categoryFlags.Lokalnachrichten -}}
{{ if $categoryFlags.Lotterie }}Lotterienachrichten{{ else if $categoryFlags.Nachruf }}Nachrufe{{ else if $categoryFlags.Theaterkritik }}Theaternachrichten{{ else }}Lokalnachrichten{{ end }}
{{- else if $categoryFlags.Weltnachrichten -}}
Politische Nachrichten aus aller Welt
{{- else -}}
{{- $authorFound := 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) -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- if ne $agentref.Ref $currentActorID -}}
<a href="/akteure/{{ $agentref.Ref }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>,
{{- end -}}
{{ $categoryName }}{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- $authorFound = true -}}
{{- break -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $authorFound -}}
{{ if $title }}<em>{{ $title }}</em>{{ else }}Beitrag ohne Titel{{ end }}
{{ $categoryName }}{{ if $title }}: <em>{{ $title }}"</em>{{ else if eq $categoryName "Beitrag" }} ohne Titel{{ end }}
{{- end -}}
{{- end -}}
{{- if $place }} <span class="inline-block bg-slate-200 text-slate-700 text-xs px-2 py-0.5 rounded-md ml-1 whitespace-nowrap">{{ $place }}</span>{{ end }}
</div>

View File

@@ -977,6 +977,192 @@ function generatePageCitation(pageNumber, button) {
}
}
// Initialize scrollspy functionality for author/agent pages
function initializeScrollspy() {
// Clean up any existing scrollspy
cleanupScrollspy();
const sections = document.querySelectorAll('.author-section');
const navLinks = document.querySelectorAll('.scrollspy-link');
if (sections.length === 0 || navLinks.length === 0) {
return;
}
function updateActiveLink() {
const visibleSections = [];
const viewportTop = window.scrollY;
const viewportBottom = viewportTop + window.innerHeight;
// Check which sections are fully visible (header must be completely visible)
sections.forEach(section => {
const sectionRect = section.getBoundingClientRect();
const sectionTop = sectionRect.top + window.scrollY;
// Find the header element (name, life data, professions)
const headerElement = section.querySelector('div:first-child');
if (headerElement) {
const headerRect = headerElement.getBoundingClientRect();
const headerTop = headerRect.top + window.scrollY;
const headerBottom = headerTop + headerRect.height;
// Check if the entire header is visible in the viewport
const headerTopVisible = headerRect.top >= 0;
const headerBottomVisible = headerRect.bottom <= window.innerHeight;
if (headerTopVisible && headerBottomVisible) {
visibleSections.push(section.getAttribute('id'));
}
}
});
// Update highlighting for all visible sections
const activeLinks = [];
navLinks.forEach(link => {
link.classList.remove('bg-blue-100', 'text-blue-700', 'font-medium', 'border-red-500');
link.classList.add('text-gray-600', 'border-transparent');
const targetId = link.getAttribute('data-target');
if (visibleSections.includes(targetId)) {
link.classList.remove('text-gray-600', 'border-transparent');
link.classList.add('bg-blue-100', 'text-blue-700', 'font-medium', 'border-red-500');
activeLinks.push(link);
}
});
// Implement proportional scrolling to keep active links visible
if (activeLinks.length > 0) {
updateSidebarScroll(activeLinks);
}
}
function updateSidebarScroll(activeLinks) {
// Skip automatic scrolling during manual navigation
if (window.scrollspyManualNavigation) return;
const sidebar = document.getElementById('scrollspy-nav');
if (!sidebar) return;
// Get the first active link as reference
const firstActiveLink = activeLinks[0];
// Calculate the main content scroll progress
const documentHeight = Math.max(
document.body.scrollHeight,
document.body.offsetHeight,
document.documentElement.clientHeight,
document.documentElement.scrollHeight,
document.documentElement.offsetHeight
);
const viewportHeight = window.innerHeight;
const maxScroll = documentHeight - viewportHeight;
const scrollProgress = maxScroll > 0 ? window.scrollY / maxScroll : 0;
// Calculate sidebar scroll dimensions
const sidebarHeight = sidebar.clientHeight;
const sidebarScrollHeight = sidebar.scrollHeight;
const maxSidebarScroll = sidebarScrollHeight - sidebarHeight;
if (maxSidebarScroll > 0) {
// Calculate proportional scroll position
const targetSidebarScroll = scrollProgress * maxSidebarScroll;
// Get the position of the first active link within the sidebar
const linkRect = firstActiveLink.getBoundingClientRect();
const sidebarRect = sidebar.getBoundingClientRect();
const linkOffsetInSidebar = linkRect.top - sidebarRect.top + sidebar.scrollTop;
// Calculate the desired position (center the active link in the sidebar viewport)
const sidebarCenter = sidebarHeight / 2;
const centeredPosition = linkOffsetInSidebar - sidebarCenter;
// Use a blend of proportional scrolling and centering for smooth behavior
const blendFactor = 0.7; // 70% proportional, 30% centering
const finalScrollPosition = (blendFactor * targetSidebarScroll) + ((1 - blendFactor) * centeredPosition);
// Clamp to valid scroll range
const clampedPosition = Math.max(0, Math.min(maxSidebarScroll, finalScrollPosition));
// Apply smooth scrolling, but only if the difference is significant
const currentScrollTop = sidebar.scrollTop;
const scrollDifference = Math.abs(clampedPosition - currentScrollTop);
if (scrollDifference > 10) { // Only scroll if more than 10px difference
sidebar.scrollTo({
top: clampedPosition,
behavior: 'smooth'
});
}
}
}
// Store scroll handler reference for cleanup
window.scrollspyScrollHandler = function() {
clearTimeout(window.scrollspyTimeout);
window.scrollspyTimeout = setTimeout(updateActiveLink, 50);
};
// Add scroll listener
window.addEventListener('scroll', window.scrollspyScrollHandler);
// Store click handlers for cleanup
window.scrollspyClickHandlers = [];
// Add smooth scroll on link click
navLinks.forEach(link => {
const clickHandler = function(e) {
e.preventDefault();
const target = document.getElementById(this.getAttribute('data-target'));
if (target) {
// Temporarily disable automatic sidebar scrolling during manual navigation
window.scrollspyManualNavigation = true;
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Re-enable automatic scrolling after navigation completes
setTimeout(() => {
window.scrollspyManualNavigation = false;
}, 1000);
}
};
window.scrollspyClickHandlers.push({ link, handler: clickHandler });
link.addEventListener('click', clickHandler);
});
// Initial active link update
updateActiveLink();
}
// Cleanup scrollspy functionality
function cleanupScrollspy() {
// Remove scroll listener
if (window.scrollspyScrollHandler) {
window.removeEventListener('scroll', window.scrollspyScrollHandler);
window.scrollspyScrollHandler = null;
}
// Clear timeout
if (window.scrollspyTimeout) {
clearTimeout(window.scrollspyTimeout);
window.scrollspyTimeout = null;
}
// Remove click handlers
if (window.scrollspyClickHandlers) {
window.scrollspyClickHandlers.forEach(({ link, handler }) => {
link.removeEventListener('click', handler);
});
window.scrollspyClickHandlers = null;
}
// Reset manual navigation flag
window.scrollspyManualNavigation = false;
}
// Initialize newspaper layout functionality
function initializeNewspaperLayout() {
// Initialize page highlighting
@@ -1026,6 +1212,11 @@ function setup() {
initializeNewspaperLayout();
}
// Initialize scrollspy if author/agent sections are present
if (document.querySelector(".author-section")) {
initializeScrollspy();
}
// Set up HTMX event handlers
htmx.on("htmx:load", function (_) {
// INFO: We can instead use afterSettle; and also clear the map with
@@ -1033,12 +1224,16 @@ function setup() {
setup_xslt();
});
// HTMX event handling for newspaper layout
// HTMX event handling for newspaper layout and scrollspy
document.body.addEventListener("htmx:afterSwap", function (event) {
setTimeout(() => {
if (document.querySelector(".newspaper-page-container")) {
initializeNewspaperLayout();
}
if (document.querySelector(".author-section")) {
initializeScrollspy();
}
}, 100);
});
@@ -1047,6 +1242,9 @@ function setup() {
if (document.querySelector(".newspaper-page-container")) {
initializeNewspaperLayout();
}
if (document.querySelector(".author-section")) {
initializeScrollspy();
}
}, 200);
});
@@ -1055,6 +1253,9 @@ function setup() {
if (document.querySelector(".newspaper-page-container")) {
initializeNewspaperLayout();
}
if (document.querySelector(".author-section")) {
initializeScrollspy();
}
}, 100);
});
}
@@ -1664,6 +1865,9 @@ document.body.addEventListener("htmx:beforeRequest", function (event) {
console.log("Cleaning up single page viewer before HTMX navigation");
viewer.destroy();
}
// Clean up scrollspy before navigating
cleanupScrollspy();
});
// Also clean up on page unload as fallback