Some refinements; anonymous authors

This commit is contained in:
Simon Martens
2025-09-22 21:03:27 +02:00
parent f8d7b92be0
commit 9268402ebf
16 changed files with 177 additions and 428 deletions

View File

@@ -256,16 +256,15 @@ The application supports viewing pieces/articles that span multiple issues throu
### URL Structure & Routing
**URL Pattern**: `/beitrag/:id` where ID format is `YYYY-NNN-PPP` (year-issue-page)
- **Example**: `/beitrag/1768-020-079` (piece starting at year 1768, issue 20, page 79)
**URL Pattern**: `/beitrag/:id` where ID is the piece's XML ID
- **Example**: `/beitrag/piece-abc123` (piece with XML ID "piece-abc123")
- **Route Definition**: `PIECE_URL = "/beitrag/:id"` in `app/kgpz.go`
- **Controller**: `controllers.GetPiece(k.Library)` handles piece lookup and rendering
### Architecture & Components
**Controller** (`controllers/piece_controller.go`):
- Parses YYYY-NNN-PPP ID format using regex pattern matching
- Looks up pieces by year/issue/page when XML IDs aren't reliable
- Looks up pieces directly by XML ID
- Handles piece aggregation across multiple issues
- Returns 404 for invalid IDs or non-existent pieces
@@ -327,7 +326,7 @@ The application supports viewing pieces/articles that span multiple issues throu
**Linking to Pieces**:
```gohtml
<a href="{{ GetPieceURL $piece.Reference.When.Year $piece.Reference.Nr $piece.Reference.Von }}">
<a href="{{ GetPieceURL $piece.ID }}">
gesamten beitrag anzeigen
</a>
```
@@ -342,7 +341,7 @@ The application supports viewing pieces/articles that span multiple issues throu
### Error Handling
**Invalid IDs**: Returns 404 for malformed YYYY-NNN-PPP format
**Invalid IDs**: Returns 404 for non-existent piece IDs
**Missing Pieces**: Returns 404 when piece lookup fails in XML data
**Missing Images**: Graceful fallback with "Keine Bilder verfügbar" message
**Cross-Issue Navigation**: Handles pieces spanning non-consecutive issues

View File

@@ -202,6 +202,16 @@ func (k *KGPZ) Funcs() map[string]interface{} {
e["LookupPieces"] = k.Library.Pieces.ReverseLookup
e["LookupWorks"] = k.Library.Works.ReverseLookup
e["LookupIssues"] = k.Library.Issues.ReverseLookup
e["LookupAnonymWorks"] = func() []xmlmodels.Work {
var anonymWorks []xmlmodels.Work
for _, work := range k.Library.Works.Array {
// Check if work has no agents
if len(work.AgentRefs) == 0 {
anonymWorks = append(anonymWorks, work)
}
}
return anonymWorks
}
return e
}

View File

@@ -31,6 +31,20 @@ func GetAgents(kgpz *xmlmodels.Library) fiber.Handler {
)
}
// Handle special "anonym" route
if a == "anonym" {
anonymAgent := viewmodels.AnonymView(kgpz)
return c.Render(
"/akteure/",
fiber.Map{"model": &viewmodels.AgentsListView{
Search: "anonym",
AvailableLetters: []string{},
Agents: map[string]xmlmodels.Agent{"anonym": *anonymAgent},
Sorted: []string{"anonym"},
}},
)
}
// Handle normal letter/id lookup
agents := viewmodels.AgentsView(a, kgpz)
if len(agents.Agents) == 0 {

View File

@@ -19,26 +19,7 @@ func GetPiece(kgpz *xmlmodels.Library) fiber.Handler {
return c.SendStatus(fiber.StatusNotFound)
}
// Parse the generated ID format: YYYY-NNN-PPP
var piece *xmlmodels.Piece
if strings.Contains(id, "-") {
parts := strings.Split(id, "-")
if len(parts) == 3 {
year, yearErr := strconv.Atoi(parts[0])
issueNum, issueErr := strconv.Atoi(parts[1])
page, pageErr := strconv.Atoi(parts[2])
if yearErr == nil && issueErr == nil && pageErr == nil {
piece = findPieceByYearIssuePage(kgpz, year, issueNum, page)
}
}
}
// Fallback to original ID lookup if generated ID doesn't work
if piece == nil {
piece = kgpz.Pieces.Item(id)
}
piece := kgpz.Pieces.Item(id)
if piece == nil {
logging.Error(nil, "Piece could not be found with ID: "+id)
return c.SendStatus(fiber.StatusNotFound)
@@ -98,25 +79,7 @@ func GetPieceWithPage(kgpz *xmlmodels.Library) fiber.Handler {
}
}
// Parse the generated ID format: YYYY-NNN-PPP
var piece *xmlmodels.Piece
if strings.Contains(id, "-") {
parts := strings.Split(id, "-")
if len(parts) == 3 {
year, yearErr := strconv.Atoi(parts[0])
issueNum, issueErr := strconv.Atoi(parts[1])
page, pageErr := strconv.Atoi(parts[2])
if yearErr == nil && issueErr == nil && pageErr == nil {
piece = findPieceByYearIssuePage(kgpz, year, issueNum, page)
}
}
}
// Fallback to original ID lookup if generated ID doesn't work
if piece == nil {
piece = kgpz.Pieces.Item(id)
}
piece := kgpz.Pieces.Item(id)
if piece == nil {
logging.Error(nil, "Piece could not be found with ID: "+id)
@@ -155,17 +118,3 @@ func GetPieceWithPage(kgpz *xmlmodels.Library) fiber.Handler {
}
}
// findPieceByYearIssuePage finds a piece that starts on the given year, issue, and page
func findPieceByYearIssuePage(kgpz *xmlmodels.Library, year, issueNum, page int) *xmlmodels.Piece {
kgpz.Pieces.Lock()
defer kgpz.Pieces.Unlock()
for _, piece := range kgpz.Pieces.Array {
for _, issueRef := range piece.IssueRefs {
if issueRef.When.Year == year && issueRef.Nr == issueNum && issueRef.Von == page {
return &piece
}
}
}
return nil
}

View File

@@ -69,15 +69,9 @@ func PageIcon(iconType string) template.HTML {
}
}
// GetPieceURL generates a piece view URL from year, issue number, page, and optional piece ID
func GetPieceURL(year, issueNum, page int, pieceID ...string) string {
if len(pieceID) > 0 && pieceID[0] != "" && pieceID[0] != "0" {
// Use just the piece ID (no year/issue prefix in URL)
return "/beitrag/" + pieceID[0]
}
// Fallback to old format for backward compatibility
id := fmt.Sprintf("%d-%03d-%03d", year, issueNum, page)
return "/beitrag/" + id
// GetPieceURL generates a piece view URL from piece ID
func GetPieceURL(pieceID string) string {
return "/beitrag/" + pieceID
}
// IssueContext formats an issue reference into a readable context string

View File

@@ -77,3 +77,12 @@ func AuthorsView(lib *xmlmodels.Library) *AgentsListView {
return &res
}
// AnonymView creates a synthetic agent for works without associated authors
func AnonymView(lib *xmlmodels.Library) *xmlmodels.Agent {
return &xmlmodels.Agent{
Identifier: xmlmodels.Identifier{ID: "anonym"},
Names: []string{"anonym"},
Org: false, // person, not organization
}
}

View File

@@ -11,7 +11,7 @@ class H extends HTMLElement {
if (this.sections = document.querySelectorAll(".author-section"), this.navLinks = document.querySelectorAll(".scrollspy-link"), this.sections.length === 0 || this.navLinks.length === 0) {
setTimeout(() => {
this.sections = document.querySelectorAll(".author-section"), this.navLinks = document.querySelectorAll(".scrollspy-link"), this.sections.length > 0 && this.navLinks.length > 0 && this.initializeScrollspy();
}, 200);
}, 500);
return;
}
this.initializeScrollspy();

File diff suppressed because one or more lines are too long

View File

@@ -15,6 +15,20 @@
<link href="/assets/css/remixicon.css" rel="stylesheet" />
<script>
// Configure HTMX scroll behavior
document.addEventListener('DOMContentLoaded', function() {
if (typeof htmx !== 'undefined') {
htmx.config.scrollBehavior = 'instant';
}
});
// Fallback configuration
window.addEventListener('load', function() {
if (typeof htmx !== 'undefined') {
htmx.config.scrollBehavior = 'instant';
}
});
</script>
<script src="/assets/js/alpine.min.js" defer></script>
<script src="/assets/js/htmx.min.js" defer></script>
<script src="/assets/js/htmx-response-targets.js" defer></script>

View File

@@ -13,11 +13,18 @@
<div class="max-w-7xl mx-auto px-8 py-8">
<div class="bg-white px-6 py-6 rounded">
<div class="mb-6">
{{ if eq $agent.ID "anonym" }}
<a href="/akteure/a" class="inline-flex items-center text-black hover:text-gray-700 transition-colors text-xl no-underline font-bold">
<i class="ri-arrow-left-line mr-3 text-xl font-bold"></i>
A
</a>
{{ else }}
{{ $letter := Upper (FirstLetter $agent.ID) }}
<a href="/akteure/{{ $letter }}" class="inline-flex items-center text-black hover:text-gray-700 transition-colors text-xl no-underline font-bold">
<i class="ri-arrow-left-line mr-3 text-xl font-bold"></i>
{{ $letter }}
</a>
{{ end }}
</div>
{{ template "_akteur" $agent }}
</div>
@@ -66,6 +73,13 @@
<a href="/akteure/{{ $l }}" class="no-underline leading-none !m-0 !p-0 text-2xl font-medium text-gray-700 hover:text-red-600 transition-colors">{{ $l }}</a>
{{ end }}
{{ end }}
<!-- Separator and Anonym link -->
<span class="text-gray-400 text-2xl">|</span>
{{ if eq $.model.Search "anonym" }}
<span class="no-underline leading-none !m-0 !p-0 text-4xl font-bold text-red-600 pointer-events-none" aria-current="true">anonym</span>
{{ else }}
<a href="/akteure/anonym" class="no-underline leading-none !m-0 !p-0 text-2xl font-medium text-gray-700 hover:text-red-600 transition-colors">anonym</a>
{{ end }}
</div>
</div>
</div>

View File

@@ -93,7 +93,7 @@
duration-200">
<i class="ri-file-copy-2-line text-xs"></i>
<a
href="{{ GetPieceURL $individualPiece.PieceByIssue.Reference.When.Year $individualPiece.PieceByIssue.Reference.Nr $individualPiece.PieceByIssue.Reference.Von (index $individualPiece.PieceByIssue.Keys 0) }}"
href="{{ GetPieceURL $individualPiece.PieceByIssue.ID }}"
class="">
Ganzer Beitrag
</a>
@@ -228,7 +228,7 @@
duration-200">
<i class="ri-file-copy-2-line text-xs"></i>
<a
href="{{ GetPieceURL $individualPiece.PieceByIssue.Reference.When.Year $individualPiece.PieceByIssue.Reference.Nr $individualPiece.PieceByIssue.Reference.Von (index $individualPiece.PieceByIssue.Keys 0) }}"
href="{{ GetPieceURL $individualPiece.PieceByIssue.ID }}"
class="">
Ganzer Beitrag
</a>

View File

@@ -8,19 +8,12 @@
</h2>
</div>
<div class="space-y-2">
{{- /* Group pieces by title and work reference */ -}}
{{- /* Group pieces by their own title/incipit, not by work being reviewed */ -}}
{{- $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 -}}
@@ -55,7 +48,7 @@
{{- if gt (len $firstGroupItem.Item.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 (index $firstGroupItem.Item.IssueRefs 0).When.Year (index $firstGroupItem.Item.IssueRefs 0).Nr (index $firstGroupItem.Item.IssueRefs 0).Von (index $firstGroupItem.Item.Keys 0) }}" class="">
<a href="{{ GetPieceURL $firstGroupItem.Item.ID }}" class="">
Ganzer Beitrag
</a>
</div>

View File

@@ -1,17 +1,15 @@
{{ $a := . }}
{{ $works := LookupWorks $a }}
{{ $allPieces := LookupPieces $a }}
{{ $works := slice }}
{{ if eq $a.ID "anonym" }}
{{ $anonymWorks := LookupAnonymWorks }}
{{ range $_, $work := $anonymWorks }}
{{ $works = append $works (dict "Item" $work) }}
{{ end }}
{{ else }}
{{ $works = LookupWorks $a }}
{{ end }}
{{- /* Filter pieces for work-related categories */ -}}
{{- $workPieces := slice -}}
{{- range $_, $p := $allPieces -}}
{{- $categoryFlags := GetCategoryFlags $p.Item -}}
{{- if or $categoryFlags.Rezension $categoryFlags.Auszug $categoryFlags.Theaterkritik $categoryFlags.Uebersetzung $categoryFlags.Kommentar $categoryFlags.Replik $categoryFlags.Anzeige $categoryFlags.Provinienz -}}
{{- $workPieces = append $workPieces $p -}}
{{- end -}}
{{- end -}}
{{- if or (ne (len $works) 0) (ne (len $workPieces) 0) -}}
{{ if ne (len $works) 0 }}
<div class="mt-4 akteur-werke-section">
<div class="py-1 rounded-lg mb-1">
<h2 class="font-bold">
@@ -20,7 +18,7 @@
</div>
<div class="">
{{ range $_, $w := $works }}
<div class="mb-1.5 break-inside-avoid max-w-[95ch]">
<div class="mb-2 break-inside-avoid max-w-[95ch]">
{{- if ne (len $w.Item.Citation.InnerXML ) 0 -}}
<div class="indent-6">
{{- Safe $w.Item.Citation.HTML -}}
@@ -33,191 +31,14 @@
{{- end -}}
</div>
{{- end -}}
{{- /* Find all pieces that reference this work */ -}}
{{ $workPieces := LookupPieces $w.Item }}
{{ if len $workPieces }}
<div class="mt-1">
{{- /* Group pieces by piece ID first to combine all categories per piece, then by additional authors */ -}}
{{- $pieceData := dict -}}
<div class="">
{{- /* Group pieces by category + author combination */ -}}
{{- $pieceGroups := 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 -}}
{{- /* Get additional authors for this specific piece */ -}}
{{- $pieceAdditionalAuthorIDs := slice -}}
{{- range $agentref := $p.Item.AgentRefs -}}
{{- if and (or (eq $agentref.Category "") (eq $agentref.Category "autor")) (ne $agentref.Ref $a.ID) -}}
{{- $pieceAdditionalAuthorIDs = append $pieceAdditionalAuthorIDs $agentref.Ref -}}
{{- end -}}
{{- end -}}
{{- $sortedAdditionalAuthorIDs := sortStrings $pieceAdditionalAuthorIDs -}}
{{- /* Store piece data by ID to combine categories and avoid duplicates */ -}}
{{- $pieceData = merge $pieceData (dict $p.Item.ID (dict "piece" $p "categories" $categories "additionalAuthorIDs" $sortedAdditionalAuthorIDs)) -}}
{{- end -}}
{{- /* Now group by combined categories and additional authors */ -}}
{{- $groupedByCategory := dict -}}
{{- range $pieceID, $data := $pieceData -}}
{{- $sortedCategories := sortStrings $data.categories -}}
{{- $categoryName := joinWithUnd $sortedCategories -}}
{{- $groupKey := printf "%s|%s" $categoryName (joinWithUnd $data.additionalAuthorIDs) -}}
{{- $existing := index $groupedByCategory $groupKey -}}
{{- if $existing -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $groupKey (append $existing $data.piece)) -}}
{{- else -}}
{{- $groupedByCategory = merge $groupedByCategory (dict $groupKey (slice $data.piece)) -}}
{{- end -}}
{{- end -}}
{{- /* Display each category group */ -}}
{{- range $groupKey, $categoryPieces := $groupedByCategory -}}
<div>
<span class="inline-block">
{{- /* Extract category and additional authors from group key */ -}}
{{- $keyParts := split $groupKey "|" -}}
{{- $categoryName := index $keyParts 0 -}}
{{ $categoryName }}
{{- /* Get additional authors from first piece in group */ -}}
{{- $firstPiece := index $categoryPieces 0 -}}
{{- $additionalAuthorIDs := slice -}}
{{- range $agentref := $firstPiece.Item.AgentRefs -}}
{{- if and (or (eq $agentref.Category "") (eq $agentref.Category "autor")) (ne $agentref.Ref $a.ID) -}}
{{- $additionalAuthorIDs = append $additionalAuthorIDs $agentref.Ref -}}
{{- end -}}
{{- end -}}
{{- if $additionalAuthorIDs -}}
{{ " " }}von {{ range $i, $authorID := $additionalAuthorIDs }}{{- if gt $i 0 }} und {{ end }}{{- $agent := GetAgent $authorID -}}{{- if and $agent (gt (len $agent.Names) 0) -}}<a href="/akteure/{{ $authorID }}" class="">{{ index $agent.Names 0 }}</a>{{- end -}}{{ end }}
{{- end -}}{{ ":" }}
</span>
{{- /* Show all citations for this category inline with commas */ -}}
{{ " " }}{{- range $pieceIndex, $p := $categoryPieces -}}
{{- range $issueIndex, $issue := $p.Item.IssueRefs -}}
{{- if or (gt $pieceIndex 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 */ -}}
{{- $firstPiece := index $categoryPieces 0 -}}
{{- if gt (len $firstPiece.Item.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 (index $firstPiece.Item.IssueRefs 0).When.Year (index $firstPiece.Item.IssueRefs 0).Nr (index $firstPiece.Item.IssueRefs 0).Von (index $firstPiece.Item.Keys 0) }}" class="">
Ganzer Beitrag
</a>
</div>
{{- end -}}
</div>
{{- end -}}
</div>
{{ end }}
</div>
{{ end }}
{{- /* Process standalone work pieces that aren't linked to specific works */ -}}
{{- if ne (len $workPieces) 0 -}}
{{- /* Group standalone work pieces by piece ID first to combine categories, then by additional authors */ -}}
{{- $standalonePieceData := dict -}}
{{- range $_, $p := $workPieces -}}
{{- /* Skip pieces that are already covered by works above */ -}}
{{- $isPieceInWorks := false -}}
{{- range $_, $w := $works -}}
{{- $workPiecesCheck := LookupPieces $w.Item -}}
{{- range $_, $wp := $workPiecesCheck -}}
{{- if eq $wp.Item.ID $p.Item.ID -}}
{{- $isPieceInWorks = true -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if not $isPieceInWorks -}}
{{- /* Get categories for this piece */ -}}
{{- $categoryFlags := GetCategoryFlags $p.Item -}}
{{- $categories := slice -}}
@@ -245,80 +66,93 @@
{{- if $categoryFlags.Provinienz -}}
{{- $categories = append $categories "Provinienz" -}}
{{- end -}}
{{- if eq (len $categories) 0 -}}
{{- $categories = append $categories "Beitrag" -}}
{{- end -}}
{{- if ne (len $categories) 0 -}}
{{- /* Get additional authors for this specific piece */ -}}
{{- $pieceAdditionalAuthorIDs := slice -}}
{{- /* Get authors for this piece (excluding current person) */ -}}
{{- $pieceAuthors := slice -}}
{{- range $agentref := $p.Item.AgentRefs -}}
{{- if and (or (eq $agentref.Category "") (eq $agentref.Category "autor")) (ne $agentref.Ref $a.ID) -}}
{{- $pieceAdditionalAuthorIDs = append $pieceAdditionalAuthorIDs $agentref.Ref -}}
{{- $pieceAuthors = append $pieceAuthors $agentref.Ref -}}
{{- end -}}
{{- end -}}
{{- $sortedAdditionalAuthorIDs := sortStrings $pieceAdditionalAuthorIDs -}}
{{- $sortedAuthors := sortStrings $pieceAuthors -}}
{{- /* Store piece data by ID to combine categories and avoid duplicates */ -}}
{{- $standalonePieceData = merge $standalonePieceData (dict $p.Item.ID (dict "piece" $p "categories" $categories "additionalAuthorIDs" $sortedAdditionalAuthorIDs)) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /* Create group key: categories + authors */ -}}
{{- $sortedCategories := sortStrings $categories -}}
{{- $groupKey := printf "%s|%s" (joinWithUnd $sortedCategories) (joinWithUnd $sortedAuthors) -}}
{{- /* Now group by combined categories and additional authors */ -}}
{{- $standaloneGrouped := dict -}}
{{- range $pieceID, $data := $standalonePieceData -}}
{{- $sortedCategories := sortStrings $data.categories -}}
{{- $categoryName := joinWithUnd $sortedCategories -}}
{{- $groupKey := printf "%s|%s" $categoryName (joinWithUnd $data.additionalAuthorIDs) -}}
{{- $existing := index $standaloneGrouped $groupKey -}}
{{- /* Add piece to group (check for duplicates by ID) */ -}}
{{- $existing := index $pieceGroups $groupKey -}}
{{- if $existing -}}
{{- $standaloneGrouped = merge $standaloneGrouped (dict $groupKey (append $existing $data.piece)) -}}
{{- /* Check if piece is already in group */ -}}
{{- $found := false -}}
{{- range $existingPiece := $existing -}}
{{- if eq $existingPiece.Item.ID $p.Item.ID -}}
{{- $found = true -}}
{{- end -}}
{{- end -}}
{{- if not $found -}}
{{- $pieceGroups = merge $pieceGroups (dict $groupKey (append $existing $p)) -}}
{{- end -}}
{{- else -}}
{{- $standaloneGrouped = merge $standaloneGrouped (dict $groupKey (slice $data.piece)) -}}
{{- $pieceGroups = merge $pieceGroups (dict $groupKey (slice $p)) -}}
{{- end -}}
{{- end -}}
{{- /* Display standalone work pieces */ -}}
{{- range $groupKey, $categoryPieces := $standaloneGrouped -}}
<div class="mb-1.5 break-inside-avoid max-w-[95ch]">
<div class="">
<span class="inline-block">
{{- /* Extract category and additional authors from group key */ -}}
{{- /* Display each group */ -}}
{{- range $groupKey, $groupPieces := $pieceGroups -}}
<div class="mb-1 text-gray-600">
{{- /* Extract categories and authors from group key */ -}}
{{- $keyParts := split $groupKey "|" -}}
{{- $categoryName := index $keyParts 0 -}}
{{- $authorPart := index $keyParts 1 -}}
{{ $categoryName }}
{{- /* Get additional authors from first piece in group */ -}}
{{- $firstPiece := index $categoryPieces 0 -}}
{{- $additionalAuthorIDs := slice -}}
{{- range $agentref := $firstPiece.Item.AgentRefs -}}
{{- if and (or (eq $agentref.Category "") (eq $agentref.Category "autor")) (ne $agentref.Ref $a.ID) -}}
{{- $additionalAuthorIDs = append $additionalAuthorIDs $agentref.Ref -}}
{{- /* Use plural if multiple pieces grouped together */ -}}
{{- $displayCategory := $categoryName -}}
{{- if gt (len $groupPieces) 1 -}}
{{- if eq $categoryName "Rezension" -}}
{{- $displayCategory = "Rezensionen" -}}
{{- else if eq $categoryName "Auszug" -}}
{{- $displayCategory = "Auszüge" -}}
{{- else if eq $categoryName "Theaterkritik" -}}
{{- $displayCategory = "Theaterkritiken" -}}
{{- else if eq $categoryName "Übersetzung" -}}
{{- $displayCategory = "Übersetzungen" -}}
{{- else if eq $categoryName "Kommentar" -}}
{{- $displayCategory = "Kommentare" -}}
{{- else if eq $categoryName "Replik" -}}
{{- $displayCategory = "Repliken" -}}
{{- else if eq $categoryName "Anzeige" -}}
{{- $displayCategory = "Anzeigen" -}}
{{- else if eq $categoryName "Beitrag" -}}
{{- $displayCategory = "Beiträge" -}}
{{- end -}}
{{- end -}}
{{- if $additionalAuthorIDs -}}
{{ " " }}von {{ range $i, $authorID := $additionalAuthorIDs }}{{- if gt $i 0 }} und {{ end }}{{- $agent := GetAgent $authorID -}}{{- if and $agent (gt (len $agent.Names) 0) -}}<a href="/akteure/{{ $authorID }}" class="">{{ index $agent.Names 0 }}</a>{{- end -}}{{ end }}
{{- end -}}{{ ":" }}
</span>
{{- /* Show all citations for this category inline with commas */ -}}
{{ " " }}{{- range $pieceIndex, $p := $categoryPieces -}}
{{ $displayCategory }}{{- if ne $authorPart "" }} von {{ range $i, $authorID := (split $authorPart " und ") }}{{- if gt $i 0 }} und {{ end }}{{- $agent := GetAgent $authorID -}}{{- if and $agent (gt (len $agent.Names) 0) -}}<a href="/akteure/{{ $authorID }}" class="text-slate-700 hover:text-slate-900 underline decoration-slate-400 hover:decoration-slate-600">{{ index $agent.Names 0 }}</a>{{- end -}}{{ end }}{{- end }}:
{{- /* Show citations separated by commas */ -}}
{{ " " }}{{- range $pieceIndex, $p := $groupPieces -}}
{{- range $issueIndex, $issue := $p.Item.IssueRefs -}}
{{- if or (gt $pieceIndex 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 -}}
<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 -}}
{{- /* Add "Ganzer Beitrag" link if piece spans multiple issues */ -}}
{{- $firstPiece := index $categoryPieces 0 -}}
{{- if gt (len $firstPiece.Item.IssueRefs) 1 -}}
{{- if gt (len $p.Item.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 (index $firstPiece.Item.IssueRefs 0).When.Year (index $firstPiece.Item.IssueRefs 0).Nr (index $firstPiece.Item.IssueRefs 0).Von (index $firstPiece.Item.Keys 0) }}" class="">
<a href="{{ GetPieceURL $p.Item.ID }}" class="">
Ganzer Beitrag
</a>
</div>
{{- end -}}
</div>
{{- end -}}
</div>
{{- end -}}
{{- end -}}
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>

View File

@@ -23,7 +23,9 @@
{{ if eq .Search "autoren" }}checked{{ end }}
autocomplete="off"
hx-get="{{ if eq .Search "autoren" }}/akteure/a{{ else }}/akteure/autoren{{ end }}"
hx-target="body"
hx-select="main"
hx-target="main"
hx-swap="innerHTML show:window:top focus-scroll:false"
hx-push-url="true">
<span class="ml-2 text-base text-gray-700">Nur Autor:innen anzeigen</span>
</label>

View File

@@ -31,7 +31,7 @@ export class AkteureScrollspy extends HTMLElement {
if (this.sections.length > 0 && this.navLinks.length > 0) {
this.initializeScrollspy();
}
}, 200);
}, 500);
return;
}
@@ -402,4 +402,3 @@ export class AkteureScrollspy extends HTMLElement {
// Register the web component
customElements.define("akteure-scrollspy", AkteureScrollspy);

View File

@@ -1,12 +1,8 @@
package xmlmodels
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"encoding/xml"
"fmt"
"sort"
"strconv"
"strings"
@@ -37,68 +33,6 @@ func (p Piece) String() string {
return string(data)
}
// generateContentBasedID creates a deterministic ID based on piece content
func (p Piece) generateContentBasedID() string {
var parts []string
// Add title if available
if len(p.Title) > 0 && p.Title[0] != "" {
parts = append(parts, "title:"+strings.ToLower(strings.TrimSpace(p.Title[0])))
}
// Add incipit if available
if len(p.Incipit) > 0 && p.Incipit[0] != "" {
incipit := strings.ToLower(strings.TrimSpace(p.Incipit[0]))
// Limit incipit to first 50 characters to avoid overly long IDs
if len(incipit) > 50 {
incipit = incipit[:50]
}
parts = append(parts, "incipit:"+incipit)
}
// Add author references
var authors []string
for _, agent := range p.AgentRefs {
if agent.Category == "" || agent.Category == "autor" {
authors = append(authors, agent.Ref)
}
}
sort.Strings(authors) // Ensure consistent ordering
if len(authors) > 0 {
parts = append(parts, "authors:"+strings.Join(authors, ","))
}
// Add categories
var categories []string
for _, cat := range p.CategoryRefs {
if cat.Category != "" {
categories = append(categories, cat.Category)
}
}
sort.Strings(categories) // Ensure consistent ordering
if len(categories) > 0 {
parts = append(parts, "categories:"+strings.Join(categories, ","))
}
// If we have no meaningful content, create a minimal hash from issue refs
if len(parts) == 0 {
// Use issue references as fallback content
for _, issue := range p.IssueRefs {
parts = append(parts, fmt.Sprintf("issue:%d-%d-%d-%d", issue.When.Year, issue.Nr, issue.Von, issue.Bis))
}
// If still no content, use a generic identifier
if len(parts) == 0 {
parts = append(parts, "unknown-piece")
}
}
// Create hash of combined content
content := strings.Join(parts, "|")
hash := sha256.Sum256([]byte(content))
// Return first 12 characters of hex hash for reasonable ID length
return hex.EncodeToString(hash[:])[:12]
}
func (p Piece) Categories() map[string]bool {
cats := make(map[string]bool)
@@ -136,24 +70,8 @@ func (p Piece) Categories() map[string]bool {
}
func (p Piece) Keys() []string {
// Always regenerate keys to ensure we use the new content-based logic
ret := make([]string, 0, 3)
// Primary ID: Use existing ID if available, otherwise content-based ID
var primaryID string
if p.ID != "" {
primaryID = p.ID
} else {
primaryID = p.generateContentBasedID()
}
ret = append(ret, primaryID)
// Create issue-specific keys using the primary ID for lookup
for _, i := range p.IssueRefs {
ret = append(ret, strconv.Itoa(i.When.Year)+"-"+strconv.Itoa(i.Nr)+"-"+primaryID)
}
return ret
// All pieces now have XML IDs, so we just return the ID
return []string{p.ID}
}
func (p Piece) ReferencesIssue(y, no int) (*IssueRef, bool) {