Akteure beginning

This commit is contained in:
Simon Martens
2024-12-26 01:26:51 +01:00
parent 4de0eab443
commit 86152bd46d
47 changed files with 61845 additions and 23644 deletions

View File

@@ -8,15 +8,15 @@ full_bin = "export KGPZ_WATCH=false; ./tmp/main"
cmd = "go build -tags=\"dev\" -o ./tmp/main ." cmd = "go build -tags=\"dev\" -o ./tmp/main ."
delay = 400 delay = 400
exclude_dir = [ exclude_dir = [
"views/public", "views/public",
"views/node_modules", "views/node_modules",
"views/transform", "views/transform",
"tmp", "tmp",
"vendor", "vendor",
"testdata", "testdata",
"data_git", "data_git",
"cache_gnd", "cache_gnd",
"cache_geonames", "cache_geonames",
] ]
exclude_file = [] exclude_file = []
exclude_regex = ["_test.go"] exclude_regex = ["_test.go"]
@@ -30,7 +30,7 @@ log = "build-errors.log"
poll = false poll = false
poll_interval = 0 poll_interval = 0
post_cmd = [] post_cmd = []
pre_cmd = ["npm --prefix views/ run tailwind"] pre_cmd = ["npm --prefix views/ run build"]
rerun = false rerun = false
rerun_delay = 250 rerun_delay = 250
send_interrupt = true send_interrupt = true

View File

@@ -22,6 +22,9 @@ func GetAgents(kgpz *app.KGPZ) fiber.Handler {
logging.Error(nil, "No agents found for letter or id: "+a) logging.Error(nil, "No agents found for letter or id: "+a)
return c.SendStatus(fiber.StatusNotFound) return c.SendStatus(fiber.StatusNotFound)
} }
return c.Render("/akteure/", fiber.Map{"model": fiber.Map{"agents": agents, "search": a}, "title": "Akteure"}) return c.Render(
"/akteure/",
fiber.Map{"model": agents},
)
} }
} }

View File

@@ -1,5 +1,11 @@
package functions package functions
import (
"strconv"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/xsdtime"
)
type Month struct { type Month struct {
Full string Full string
Short string Short string
@@ -48,6 +54,28 @@ var TRANSLD = []Weekday{
{"Sonntag", "So", 7}, {"Sonntag", "So", 7},
} }
func HRDateShort(date string) string {
xsdt, err := xsdtime.New(date)
if err != nil {
return ""
}
t := xsdt.Type()
if t == xsdtime.GYear {
return strconv.Itoa(xsdt.Year)
}
if t == xsdtime.GYearMonth {
return strconv.Itoa(xsdt.Month) + "." + strconv.Itoa(xsdt.Year)
}
if t == xsdtime.Date {
return strconv.Itoa(xsdt.Day) + "." + strconv.Itoa(xsdt.Month) + "." + strconv.Itoa(xsdt.Year)
}
return ""
}
func MonthName(i int) Month { func MonthName(i int) Month {
if i > 12 || i < 1 { if i > 12 || i < 1 {
return TRANSLM[0] return TRANSLM[0]

1
functions/slices.go Normal file
View File

@@ -0,0 +1 @@
package functions

8
functions/string.go Normal file
View File

@@ -0,0 +1,8 @@
package functions
func FirstLetter(s string) string {
if len(s) == 0 {
return ""
}
return string(s[:1])
}

View File

@@ -1 +0,0 @@
package providers

View File

@@ -1 +0,0 @@
package providers

View File

@@ -4,6 +4,7 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -60,6 +61,15 @@ func (p Piece) ReferencesIssue(y, no int) (*IssueRef, bool) {
return nil, false return nil, false
} }
func (p Piece) ReferencesAgent(a string) (*AgentRef, bool) {
for _, i := range p.AgentRefs {
if strings.HasPrefix(i.Ref, a) {
return &i, true
}
}
return nil, false
}
// TODO: We can make this fast depending on which category to look for // TODO: We can make this fast depending on which category to look for
// but we'll have to define rules for every single category (~35 of them) // but we'll have to define rules for every single category (~35 of them)
func (p Piece) IsCat(k string) bool { func (p Piece) IsCat(k string) bool {

View File

@@ -3,6 +3,7 @@ package xmlprovider
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"strings"
) )
type Work struct { type Work struct {
@@ -10,11 +11,20 @@ type Work struct {
URLs []URL `xml:"url"` URLs []URL `xml:"url"`
Citation Citation `xml:"zitation"` Citation Citation `xml:"zitation"`
PreferredTitle string `xml:"preferred"` PreferredTitle string `xml:"preferred"`
Akteur []AgentRef `xml:"akteur"` AgentRefs []AgentRef `xml:"akteur"`
Identifier Identifier
AnnotationNote AnnotationNote
} }
func (p Work) ReferencesAgent(a string) (*AgentRef, bool) {
for _, i := range p.AgentRefs {
if strings.HasPrefix(i.Ref, a) {
return &i, true
}
}
return nil, false
}
type Citation struct { type Citation struct {
XMLName xml.Name `xml:"zitation"` XMLName xml.Name `xml:"zitation"`
Title string `xml:"title"` Title string `xml:"title"`
@@ -24,5 +34,5 @@ type Citation struct {
} }
func (w Work) String() string { func (w Work) String() string {
return fmt.Sprintf("URLs: %v, Citation: %v, PreferredTitle: %s, Akteur: %v, Identifier: %v, AnnotationNote: %v\n", w.URLs, w.Citation, w.PreferredTitle, w.Akteur, w.Identifier, w.AnnotationNote) return fmt.Sprintf("URLs: %v, Citation: %v, PreferredTitle: %s, Akteur: %v, Identifier: %v, AnnotationNote: %v\n", w.URLs, w.Citation, w.PreferredTitle, w.AgentRefs, w.Identifier, w.AnnotationNote)
} }

View File

@@ -142,25 +142,15 @@ func (p *XMLProvider[T]) Item(id string) *T {
return i return i
} }
func (p *XMLProvider[T]) Find(fn func(*T) bool) []*T { func (p *XMLProvider[T]) Find(fn func(*T) bool) []T {
var items []*T p.mu.Lock()
p.Items.Range(func(key, value interface{}) bool { defer p.mu.Unlock()
if fn(value.(*T)) { var items []T
items = append(items, value.(*T)) for _, item := range p.Array {
if fn(&item) {
items = append(items, item)
} }
return true }
})
return items
}
func (p *XMLProvider[T]) FindKey(fn func(string) bool) []*T {
var items []*T
p.Items.Range(func(key, value interface{}) bool {
if fn(key.(string)) {
items = append(items, value.(*T))
}
return true
})
return items return items
} }

View File

@@ -4,6 +4,7 @@ import (
"html/template" "html/template"
"io" "io"
"io/fs" "io/fs"
"strings"
"sync" "sync"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/app" "github.com/Theodor-Springmann-Stiftung/kgpz_web/app"
@@ -38,16 +39,23 @@ func (e *Engine) Funcs(app *app.KGPZ) error {
e.FuncMap = make(map[string]interface{}) e.FuncMap = make(map[string]interface{})
e.mu.Unlock() e.mu.Unlock()
// Dates
e.AddFunc("MonthName", functions.MonthName) e.AddFunc("MonthName", functions.MonthName)
e.AddFunc("WeekdayName", functions.WeekdayName) e.AddFunc("WeekdayName", functions.WeekdayName)
e.AddFunc("HRDateShort", functions.HRDateShort)
// Strings
e.AddFunc("FirstLetter", functions.FirstLetter)
e.AddFunc("Upper", strings.ToUpper)
e.AddFunc("Lower", strings.ToLower)
// App specific
e.AddFunc("GetAgent", app.Library.Agents.Item) e.AddFunc("GetAgent", app.Library.Agents.Item)
e.AddFunc("GetPlace", app.Library.Places.Item) e.AddFunc("GetPlace", app.Library.Places.Item)
e.AddFunc("GetWork", app.Library.Works.Item) e.AddFunc("GetWork", app.Library.Works.Item)
e.AddFunc("GetCategory", app.Library.Categories.Item) e.AddFunc("GetCategory", app.Library.Categories.Item)
e.AddFunc("GetIssue", app.Library.Issues.Item) e.AddFunc("GetIssue", app.Library.Issues.Item)
e.AddFunc("GetPiece", app.Library.Pieces.Item) e.AddFunc("GetPiece", app.Library.Pieces.Item)
e.AddFunc("GetGND", app.GND.Person) e.AddFunc("GetGND", app.GND.Person)
return nil return nil

View File

@@ -1,58 +1,85 @@
package viewmodels package viewmodels
import ( import (
"maps"
"slices"
"strings" "strings"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
) )
type AgentView struct { type AgentsListView struct {
Agents []xmlprovider.Agent Search string
Works map[string][]xmlprovider.Work AvailableLetters []string
Pieces map[string][]xmlprovider.Piece Agents map[string]AgentView
Sorted []string
} }
func AgentsView(letterorid string, lib *xmlprovider.Library) *AgentView { type AgentView struct {
res := AgentView{} xmlprovider.Agent
lib.Agents.Items.Range(func(key, value interface{}) bool { Works []WorkByAgent
k := key.(string) Pieces []PieceByAgent
if strings.HasPrefix(k, letterorid) { }
agent := value.(xmlprovider.Agent)
res.Agents = append(res.Agents, agent)
}
return true
})
res.Works = make(map[string][]xmlprovider.Work) type WorkByAgent struct {
res.Pieces = make(map[string][]xmlprovider.Piece) xmlprovider.Work
Reference xmlprovider.AgentRef
}
lib.Works.Items.Range(func(key, value interface{}) bool { type PieceByAgent struct {
w := value.(xmlprovider.Work) xmlprovider.Piece
for _, a := range res.Agents { Reference xmlprovider.AgentRef
}
func AgentsView(letterorid string, lib *xmlprovider.Library) *AgentsListView {
res := AgentsListView{Search: letterorid, Agents: make(map[string]AgentView)}
av := make(map[string]bool)
if len(letterorid) == 1 {
// INFO: This is all persons beginning with a letter
for _, a := range lib.Agents.Array {
av[strings.ToUpper(a.ID[:1])] = true
if strings.HasPrefix(a.ID, letterorid) { if strings.HasPrefix(a.ID, letterorid) {
_, ok := res.Works[a.ID] res.Sorted = append(res.Sorted, a.ID)
if !ok { res.Agents[a.ID] = AgentView{Agent: a}
res.Works[a.ID] = []xmlprovider.Work{}
}
res.Works[a.ID] = append(res.Works[a.ID], w)
} }
} }
return true } else {
}) // INFO: This is a specific person lookup by ID
for _, a := range lib.Agents.Array {
lib.Pieces.Items.Range(func(key, value interface{}) bool { av[strings.ToUpper(a.ID[:1])] = true
p := value.(xmlprovider.Piece) if a.ID == letterorid {
for _, a := range res.Agents { res.Sorted = append(res.Sorted, a.ID)
if strings.HasPrefix(a.ID, letterorid) { res.Agents[a.ID] = AgentView{Agent: a}
_, ok := res.Pieces[a.ID] break
if !ok {
res.Pieces[a.ID] = []xmlprovider.Piece{}
}
res.Pieces[a.ID] = append(res.Pieces[a.ID], p)
} }
} }
return true }
})
// TODO: We won't need to lock the library if we take down all routes during parsing
lib.Works.Lock()
for _, w := range lib.Works.Array {
if ref, ok := w.ReferencesAgent(letterorid); ok {
if entry, ok := res.Agents[ref.Ref]; ok {
entry.Works = append(entry.Works, WorkByAgent{Work: w, Reference: *ref})
}
}
}
lib.Works.Unlock()
lib.Pieces.Lock()
for _, p := range lib.Pieces.Array {
if ref, ok := p.ReferencesAgent(letterorid); ok {
if entry, ok := res.Agents[ref.Ref]; ok {
entry.Pieces = append(entry.Pieces, PieceByAgent{Piece: p, Reference: *ref})
}
}
}
lib.Pieces.Unlock()
res.AvailableLetters = slices.Collect(maps.Keys(av))
slices.Sort(res.AvailableLetters)
slices.Sort(res.Sorted)
return &res return &res
} }

View File

@@ -2,7 +2,6 @@ package viewmodels
import ( import (
"fmt" "fmt"
"log/slog"
"maps" "maps"
"slices" "slices"
@@ -10,20 +9,20 @@ import (
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
) )
type PieceListitemVM struct { type PieceByIssue struct {
xmlprovider.Piece xmlprovider.Piece
// TODO: this is a bit hacky, but it refences the page number of the piece in the issue // TODO: this is a bit hacky, but it refences the page number of the piece in the issue
Reference xmlprovider.IssueRef Reference xmlprovider.IssueRef
} }
type PiecesByPage struct { type PiecesByPage struct {
Items map[int][]PieceListitemVM Items map[int][]PieceByIssue
Pages []int Pages []int
} }
// TODO: Next & Prev // TODO: Next & Prev
type IssueVM struct { type IssueVM struct {
IssueListitemVM xmlprovider.Issue
Pieces PiecesByPage Pieces PiecesByPage
AdditionalPieces PiecesByPage AdditionalPieces PiecesByPage
} }
@@ -34,35 +33,34 @@ func NewSingleIssueView(y string, no string, lib *xmlprovider.Library) (*IssueVM
return nil, fmt.Errorf("No issue found for %v-%v", y, no) return nil, fmt.Errorf("No issue found for %v-%v", y, no)
} }
ivm, err := ListitemFromIssue(*issue) sivm := IssueVM{Issue: *issue}
ppi, ppa, err := PiecesForIsssue(lib, *issue)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sivm := IssueVM{IssueListitemVM: *ivm}
ppi, ppa, err := PiecesForIsssue(lib, *issue)
slices.Sort(ppi.Pages) slices.Sort(ppi.Pages)
slices.Sort(ppa.Pages) slices.Sort(ppa.Pages)
sivm.Pieces = *ppi sivm.Pieces = ppi
sivm.AdditionalPieces = *ppa sivm.AdditionalPieces = ppa
return &sivm, nil return &sivm, nil
} }
func PiecesForIsssue(lib *xmlprovider.Library, issue xmlprovider.Issue) (*PiecesByPage, *PiecesByPage, error) { func PiecesForIsssue(lib *xmlprovider.Library, issue xmlprovider.Issue) (PiecesByPage, PiecesByPage, error) {
year := issue.Datum.When.Year year := issue.Datum.When.Year
ppi := PiecesByPage{Items: make(map[int][]PieceListitemVM)} ppi := PiecesByPage{Items: make(map[int][]PieceByIssue)}
ppa := PiecesByPage{Items: make(map[int][]PieceListitemVM)} ppa := PiecesByPage{Items: make(map[int][]PieceByIssue)}
// TODO: will we have to lock this, if we shutdown the server while loading the library?
lib.Pieces.Lock()
defer lib.Pieces.Unlock()
slog.Debug(fmt.Sprintf("Checking piece for year %v, number %v", year, issue.Number.No))
for _, piece := range lib.Pieces.Array { for _, piece := range lib.Pieces.Array {
if d, ok := piece.ReferencesIssue(year, issue.Number.No); ok { if d, ok := piece.ReferencesIssue(year, issue.Number.No); ok {
slog.Debug(fmt.Sprintf("Found piece %v in issue %v-%v", piece, year, issue.Number.No)) p := PieceByIssue{Piece: piece, Reference: *d}
p := PieceListitemVM{Piece: piece, Reference: *d}
if d.Beilage > 0 { if d.Beilage > 0 {
functions.MapArrayInsert(ppa.Items, d.Von, p) functions.MapArrayInsert(ppa.Items, d.Von, p)
} else { } else {
@@ -74,5 +72,5 @@ func PiecesForIsssue(lib *xmlprovider.Library, issue xmlprovider.Issue) (*Pieces
ppi.Pages = slices.Collect(maps.Keys(ppi.Items)) ppi.Pages = slices.Collect(maps.Keys(ppi.Items))
ppa.Pages = slices.Collect(maps.Keys(ppa.Items)) ppa.Pages = slices.Collect(maps.Keys(ppa.Items))
return &ppi, &ppa, nil return ppi, ppa, nil
} }

View File

@@ -1,25 +0,0 @@
package viewmodels
import (
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
)
const TLAYOUT = "2006-01-02"
type IssueListitemVM struct {
xmlprovider.Issue
No int
Day int
Month int
Year int
}
func ListitemFromIssue(i xmlprovider.Issue) (*IssueListitemVM, error) {
return &IssueListitemVM{
No: i.Number.No,
Issue: i,
Day: i.Datum.When.Day,
Month: i.Datum.When.Month,
Year: i.Datum.When.Year,
}, nil
}

View File

@@ -6,10 +6,11 @@ import (
"slices" "slices"
"sort" "sort"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/functions"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
) )
type IssuesByMonth map[int][]IssueListitemVM type IssuesByMonth map[int][]xmlprovider.Issue
func (ibm *IssuesByMonth) Sort() { func (ibm *IssuesByMonth) Sort() {
for _, issues := range *ibm { for _, issues := range *ibm {
@@ -34,9 +35,7 @@ func YearView(year int, lib *xmlprovider.Library) (*YearVM, error) {
y := issue.Datum.When.Year y := issue.Datum.When.Year
years[y] = true years[y] = true
if y == year { if y == year {
if issuevm, err := ListitemFromIssue(issue); err == nil { functions.MapArrayInsert(issues, issue.Datum.When.Month, issue)
issues[issuevm.Month] = append(issues[issuevm.Month], *issuevm)
}
} }
} }
lib.Issues.Unlock() lib.Issues.Unlock()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,58 @@
{{ if ne (len .model.Search) 1 }}
{{ $agent := index $.model.Agents .model.Search }}
{{ if not $agent }}
<div>Agent nicht gefunden: {{ .model.Search }}</div>
{{ else }}
<div>
{{ $letter := Upper (FirstLetter $agent.ID) }}
<a href="/akteure/{{ $letter }}">
&larr; Personen &amp; Körperschaften &ndash; Buchstabe
{{ $letter }}
</a>
</div>
<div>{{ index $agent.Names 0 }}</div>
{{ end }}
{{ else }}
<div>
{{ range $_, $l := .model.AvailableLetters }}
<a href="/akteure/{{ $l }}">
{{ $l }}
</a>
{{ end }}
</div>
{{ range $_, $id := .model.Sorted }}
<div class="pb-4">
<a href="/akteure/{{ $id }}">
{{ $a := index $.model.Agents $id }}
{{ index $a.Names 0 }}
</a>
<div>
{{ $gnd := GetGND $a.GND }}
{{ if and (ne $gnd nil) (ne $gnd.DateOfBirth nil) }}
{{- if ne (len $gnd.DateOfBirth) 0 -}}
<i class="ri-asterisk text-xs relative bottom-0.5"></i>&nbsp;
{{- HRDateShort (index $gnd.DateOfBirth 0) -}}
{{- end -}}
{{- if ne (len $gnd.DateOfDeath) 0 }}
&emsp;<i class="ri-cross-fill text-xs relative bottom-0.5"></i
>&nbsp;{{ HRDateShort (index $gnd.DateOfDeath 0) }}
{{ end }}
{{- if ne (len $gnd.ProfessionOrOccupation) 0 -}}
<div>
{{- (index $gnd.ProfessionOrOccupation 0).Label -}}
{{- if gt (len $gnd.ProfessionOrOccupation) 1 -}}
,
{{ (index $gnd.ProfessionOrOccupation 1).Label -}}
{{ end -}}
{{- if gt (len $gnd.ProfessionOrOccupation) 2 -}}
,
{{ (index $gnd.ProfessionOrOccupation 2).Label -}}
{{ end -}}
</div>
{{ end }}
{{ end }}
</div>
</div>
{{ end }}
{{ end }}

View File

@@ -0,0 +1,9 @@
<title>
KGPZ &ndash;
{{ if ne (len .model.Search) 1 }}
{{ index (index .model.Agents .model.Search).Names 0 }}
{{ else }}
Personen &amp; Körperschaften:
{{ Upper .model.Search }}
{{ end }}
</title>

View File

@@ -10,7 +10,7 @@
<div> <div>
<div class="py-3 text-xl"> <div class="py-3 text-xl">
<div>{{ $date.Year }}</div> <div>{{ $date.Year }}</div>
<div>Stück {{ $model.No }}</div> <div>Stück {{ $model.Number.No }}</div>
<div>{{ WeekdayName $date.Weekday }}, {{ $date.Day }}. {{ MonthName $date.Month }}</div> <div>{{ WeekdayName $date.Weekday }}, {{ $date.Day }}. {{ MonthName $date.Month }}</div>
</div> </div>
{{ template "_inhaltsverzeichnis" . }} {{ template "_inhaltsverzeichnis" . }}

View File

@@ -8,7 +8,6 @@
<div>Seite {{ $page }}</div> <div>Seite {{ $page }}</div>
{{ range $piece := (index $model.Pieces.Items $page) }} {{ range $piece := (index $model.Pieces.Items $page) }}
{{ template "_inhaltsverzeichnis_eintrag" $piece }} {{ template "_inhaltsverzeichnis_eintrag" $piece }}
@@ -21,7 +20,9 @@
<li> <li>
<a <a
href="/{{- $issue.When -}}/{{- $issue.Nr -}}" href="/{{- $issue.When -}}/{{- $issue.Nr -}}"
{{ if and (eq $issue.Nr $model.No) (eq $issue.When.Year $model.Datum.When.Year) }} {{- if and (eq $issue.Nr $model.Number.No) (eq $issue.When.Year
$model.Datum.When.Year)
-}}
aria-current="page" aria-current="page"
{{ end }}> {{ end }}>
{{- $issue.When.Year }} Nr. {{- $issue.When.Year }} Nr.

View File

@@ -1 +1 @@
<title>KGPZ &ndash; Ausgabe {{ .model.No }}&hairsp;/&hairsp;{{ .model.Year }}</title> <title>KGPZ &ndash; Ausgabe {{ .model.Number.No }}&hairsp;/&hairsp;{{ .model.Year }}</title>