marginalien

This commit is contained in:
Simon Martens
2025-05-18 22:16:03 +02:00
parent d8d713bef6
commit 9f5d71095f
5 changed files with 285 additions and 195 deletions

198
helpers/functions/html.go Normal file
View File

@@ -0,0 +1,198 @@
package functions
import (
"strings"
xmlparsing "github.com/Theodor-Springmann-Stiftung/lenz-web/xml"
)
type outType int
const (
NA outType = iota
Text
Element
EmptyElement
EndElement
)
type outToken struct {
Type outType
Name string
Classes []string
Id string
Value string
Attributes map[string]string
}
func (o outToken) String() string {
switch o.Type {
case Text:
return o.Value
case Element:
builder := strings.Builder{}
builder.WriteString("<")
builder.WriteString(o.Name)
if len(o.Classes) > 0 {
builder.WriteString(" class=\"")
builder.WriteString(strings.Join(o.Classes, " "))
builder.WriteString("\"")
}
if len(o.Id) > 0 {
builder.WriteString(" id=\"")
builder.WriteString(o.Id)
builder.WriteString("\"")
}
if len(o.Attributes) > 0 {
for key, value := range o.Attributes {
builder.WriteString(" ")
builder.WriteString(key)
builder.WriteString("=\"")
builder.WriteString(value)
builder.WriteString("\"")
}
}
builder.WriteString(">")
return builder.String()
case EndElement:
return "</" + o.Name + ">"
case EmptyElement:
builder := strings.Builder{}
builder.WriteString("<")
builder.WriteString(o.Name)
if len(o.Classes) > 0 {
builder.WriteString(" class=\"")
builder.WriteString(strings.Join(o.Classes, " "))
builder.WriteString("\"")
}
if len(o.Id) > 0 {
builder.WriteString(" id=\"")
builder.WriteString(o.Id)
builder.WriteString("\"")
}
if len(o.Attributes) > 0 {
for key, value := range o.Attributes {
builder.WriteString(" ")
builder.WriteString(key)
builder.WriteString("=\"")
builder.WriteString(value)
builder.WriteString("\"")
}
}
builder.WriteString("/>")
return builder.String()
}
return ""
}
func (o *outToken) ClassesFromAttrs(attrs map[string]string) {
if len(attrs) == 0 {
return
}
for key, value := range attrs {
o.Classes = append(o.Classes, key+"-"+value)
}
}
func Default(token xmlparsing.Token) outToken {
o := outToken{}
switch token.Type {
case xmlparsing.StartElement:
o.Name = "div"
o.Type = Element
o.Classes = []string{token.Name}
o.ClassesFromAttrs(token.Attributes)
case xmlparsing.EndElement:
o.Type = EndElement
case xmlparsing.CharData:
o.Type = Text
o.Value = token.Data
}
return o
}
type Tokens struct {
Out []outToken
}
func (s *Tokens) AppendDefaultElement(token xmlparsing.Token, ids ...string) {
t := Default(token)
if len(ids) > 0 {
t.Id = ids[0]
}
s.Out = append(s.Out, t)
}
func (s *Tokens) AppendEndElement() {
skip := 0
for i := len(s.Out) - 1; i >= 0; i-- {
if s.Out[i].Type == EndElement {
skip++
}
if s.Out[i].Type == Element {
if skip == 0 {
s.Out = append(s.Out, outToken{
Name: s.Out[i].Name,
Type: EndElement,
})
return
} else {
skip--
}
}
}
}
func (s *Tokens) AppendDivElement(id string, classes ...string) {
s.Out = append(s.Out, outToken{
Name: "div",
Id: id,
Classes: classes,
Type: Element,
})
}
func (s *Tokens) AppendEmptyElement(name string, id string, classes ...string) {
s.Out = append(s.Out, outToken{
Name: name,
Id: id,
Classes: classes,
Type: EmptyElement,
})
}
func (s *Tokens) AppendLink(href string, classes ...string) {
s.Out = append(s.Out, outToken{
Name: "a",
Attributes: map[string]string{"href": href},
Classes: classes,
Type: Element,
})
}
func (s *Tokens) AppendText(text string) {
s.Out = append(s.Out, outToken{
Type: Text,
Value: text,
})
}
func (s *Tokens) Append(token outToken) {
s.Out = append(s.Out, token)
}
func (s *Tokens) String() string {
builder := strings.Builder{}
for _, token := range s.Out {
builder.WriteString(token.String())
}
return builder.String()
}

View File

@@ -19,168 +19,31 @@ func RandString(length int) string {
return string(b) return string(b)
} }
type outType int type Note struct {
Id string
const ( Tokens Tokens
NA outType = iota
Text
Element
EmptyElement
EndElement
)
type outToken struct {
Type outType
Name string
Classes []string
Id string
Value string
Attributes map[string]string
}
func (o outToken) String() string {
switch o.Type {
case Text:
return o.Value
case Element:
builder := strings.Builder{}
builder.WriteString("<")
builder.WriteString(o.Name)
if len(o.Classes) > 0 {
builder.WriteString(" class=\"")
builder.WriteString(strings.Join(o.Classes, " "))
builder.WriteString("\"")
}
if len(o.Id) > 0 {
builder.WriteString(" id=\"")
builder.WriteString(o.Id)
builder.WriteString("\"")
}
builder.WriteString(">")
return builder.String()
case EndElement:
return "</" + o.Name + ">"
case EmptyElement:
builder := strings.Builder{}
builder.WriteString("<")
builder.WriteString(o.Name)
if len(o.Classes) > 0 {
builder.WriteString(" class=\"")
builder.WriteString(strings.Join(o.Classes, " "))
builder.WriteString("\"")
}
if len(o.Id) > 0 {
builder.WriteString(" id=\"")
builder.WriteString(o.Id)
builder.WriteString("\"")
}
builder.WriteString("/>")
}
return ""
}
func (o *outToken) ClassesFromAttrs(attrs map[string]string) {
if len(attrs) == 0 {
return
}
for key, value := range attrs {
o.Classes = append(o.Classes, key+"-"+value)
}
}
func Default(token xmlparsing.Token) outToken {
o := outToken{}
switch token.Type {
case xmlparsing.StartElement:
o.Name = "div"
o.Type = Element
o.Classes = []string{token.Name}
o.ClassesFromAttrs(token.Attributes)
case xmlparsing.EndElement:
o.Type = EndElement
case xmlparsing.CharData:
o.Type = Text
o.Value = token.Data
}
return o
} }
type LenzParseState struct { type LenzParseState struct {
Out []outToken Tokens Tokens
Notes []Note
LC int LC int
PC string PC string
CloseElement bool CloseElement bool
PageBreak bool
} }
func (s *LenzParseState) String() string { func (s *LenzParseState) String() string {
builder := strings.Builder{} builder := strings.Builder{}
for _, token := range s.Out { builder.WriteString(s.Tokens.String())
builder.WriteString(token.String()) for _, note := range s.Notes {
builder.WriteString(note.Tokens.String())
} }
return builder.String() return builder.String()
} }
func (s *LenzParseState) AppendDefaultElement(token xmlparsing.Token, ids ...string) { func (s *LenzParseState) AppendNote(note Note) {
t := Default(token) s.Notes = append(s.Notes, note)
if len(ids) > 0 {
t.Id = ids[0]
}
s.Out = append(s.Out, t)
}
func (s *LenzParseState) AppendEndElement() {
for i := len(s.Out) - 1; i >= 0; i-- {
if s.Out[i].Type == Element {
s.Out = append(s.Out, outToken{
Name: s.Out[i].Name,
Type: EndElement,
})
return
}
}
}
func (s *LenzParseState) AppendDivElement(id string, classes ...string) {
s.Out = append(s.Out, outToken{
Name: "div",
Id: id,
Classes: classes,
})
}
func (s *LenzParseState) AppendEmptyElement(name string, id string, classes ...string) {
s.Out = append(s.Out, outToken{
Name: name,
Id: id,
Classes: classes,
Type: EmptyElement,
})
}
func (s *LenzParseState) AppendLink(href string, classes ...string) {
s.Out = append(s.Out, outToken{
Name: "a",
Attributes: map[string]string{"href": href},
Classes: classes,
Type: Element,
})
}
func (s *LenzParseState) AppendText(text string) {
s.Out = append(s.Out, outToken{
Type: Text,
Value: text,
})
}
func (s *LenzParseState) Append(token outToken) {
s.Out = append(s.Out, token)
} }
func Parse(lib *xmlmodels.Library) func(s string) string { func Parse(lib *xmlmodels.Library) func(s string) string {
@@ -199,7 +62,7 @@ func Parse(lib *xmlmodels.Library) func(s string) string {
if elem.Token.Type < 3 { if elem.Token.Type < 3 {
if elem.Token.Type == xmlparsing.EndElement { if elem.Token.Type == xmlparsing.EndElement {
if ps.CloseElement { if ps.CloseElement {
ps.AppendEndElement() ps.Tokens.AppendEndElement()
} else { } else {
ps.CloseElement = true ps.CloseElement = true
} }
@@ -210,36 +73,38 @@ func Parse(lib *xmlmodels.Library) func(s string) string {
case "sidenote": case "sidenote":
id := RandString(8) id := RandString(8)
ps.Tokens.AppendDefaultElement(elem.Token, id)
if elem.Token.Attributes["annotation"] != "" || if elem.Token.Attributes["annotation"] != "" ||
elem.Token.Attributes["page"] != "" || elem.Token.Attributes["page"] != "" ||
elem.Token.Attributes["pos"] != "" { elem.Token.Attributes["pos"] != "" {
ps.AppendLink("#"+id, "nanchor-sidenote") ps.Tokens.AppendLink("#"+id, "nanchor-sidenote")
ps.AppendEndElement() ps.Tokens.AppendEndElement()
ps.AppendDivElement(id, "note-sidenote-meta") note := Note{Id: id}
note.Tokens.AppendDivElement(id, "note-sidenote-meta")
if elem.Token.Attributes["annotation"] != "" { if elem.Token.Attributes["annotation"] != "" {
ps.AppendDivElement("", "sidenote-note") note.Tokens.AppendDivElement("", "sidenote-note")
ps.AppendText(elem.Token.Attributes["annotation"]) note.Tokens.AppendText(elem.Token.Attributes["annotation"])
ps.AppendEndElement() note.Tokens.AppendEndElement()
} }
if elem.Token.Attributes["page"] != "" { if elem.Token.Attributes["page"] != "" {
ps.AppendDivElement("", "sidenote-page") note.Tokens.AppendDivElement("", "sidenote-page")
ps.AppendText(elem.Token.Attributes["page"]) note.Tokens.AppendText(elem.Token.Attributes["page"])
ps.AppendEndElement() note.Tokens.AppendEndElement()
} }
if elem.Token.Attributes["pos"] != "" { if elem.Token.Attributes["pos"] != "" {
ps.AppendDivElement("", "sidenote-pos") note.Tokens.AppendDivElement("", "sidenote-pos")
ps.AppendText(elem.Token.Attributes["pos"]) note.Tokens.AppendText(elem.Token.Attributes["pos"])
ps.AppendEndElement() note.Tokens.AppendEndElement()
} }
ps.AppendEndElement() // sidenote-meta note.Tokens.AppendEndElement() // sidenote-meta
ps.AppendNote(note)
} }
ps.AppendDefaultElement(elem.Token, id)
case "note": case "note":
id := RandString(8) id := RandString(8)
ps.AppendLink("#"+id, "nanchor-note") ps.Tokens.AppendLink("#"+id, "nanchor-note")
ps.AppendEndElement() ps.Tokens.AppendEndElement()
ps.AppendDivElement(id, "note", "note-note") ps.Tokens.AppendDivElement(id, "note", "note-note")
case "hand": case "hand":
id := elem.Token.Attributes["ref"] id := elem.Token.Attributes["ref"]
@@ -248,36 +113,38 @@ func Parse(lib *xmlmodels.Library) func(s string) string {
if err == nil { if err == nil {
person = lib.Persons.Item(idno) person = lib.Persons.Item(idno)
} }
ps.AppendLink("#"+id, "nanchor-hand") ps.Tokens.AppendLink("#"+id, "nanchor-hand")
ps.AppendEndElement() ps.Tokens.AppendEndElement()
ps.AppendDivElement(id, "note-hand") note := Note{Id: id}
note.Tokens.AppendDivElement(id, "note-hand")
hand := "N/A" hand := "N/A"
if person != nil { if person != nil {
hand = person.Name hand = person.Name
} }
ps.AppendText(hand) note.Tokens.AppendText(hand)
ps.AppendEndElement() note.Tokens.AppendEndElement()
ps.AppendDefaultElement(elem.Token) ps.AppendNote(note)
ps.Tokens.AppendDefaultElement(elem.Token)
case "line": case "line":
if val := elem.Token.Attributes["type"]; val != "empty" { if val := elem.Token.Attributes["type"]; val != "empty" {
ps.LC += 1 ps.LC += 1
ps.AppendEmptyElement("br", ps.PC+"-"+strconv.Itoa(ps.LC)) ps.Tokens.AppendEmptyElement("br", ps.PC+"-"+strconv.Itoa(ps.LC))
ps.AppendDefaultElement(elem.Token) // This is for indents, must be closed ps.Tokens.AppendDefaultElement(elem.Token) // This is for indents, must be closed
} else { } else {
ps.AppendEmptyElement("br", "", "empty") ps.Tokens.AppendEmptyElement("br", "", "empty")
ps.CloseElement = false // Here Indents make no sense, so we dont open an element ps.CloseElement = false // Here Indents make no sense, so we dont open an element
} }
case "page": case "page":
ps.PC = elem.Token.Attributes["index"] ps.PC = elem.Token.Attributes["index"]
ps.AppendLink("#"+ps.PC, "eanchor-page") ps.Tokens.AppendLink("#"+ps.PC, "eanchor-page")
ps.AppendEndElement() ps.Tokens.AppendEndElement()
ps.AppendDivElement(ps.PC, "page") ps.Tokens.AppendDivElement(ps.PC, "page")
ps.AppendText(ps.PC) ps.Tokens.AppendText(ps.PC)
default: default:
ps.AppendDefaultElement(elem.Token) ps.Tokens.AppendDefaultElement(elem.Token)
} }
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -44,7 +44,7 @@
{{ end }} {{ end }}
<scroll-button></scroll-button> <scroll-button class="print:hidden"></scroll-button>
{{ block "scripts" . }} {{ block "scripts" . }}
<!-- Default scripts... --> <!-- Default scripts... -->

View File

@@ -64,6 +64,12 @@
} }
} }
@media print {
html {
font-size: 14px;
}
}
body { body {
@apply bg-stone-50; @apply bg-stone-50;
} }
@@ -79,8 +85,9 @@
nav a[aria-current="page"] { nav a[aria-current="page"] {
@apply font-bold text-red-500; @apply font-bold text-red-500;
} }
.text { .text {
@apply font-serif max-w-[80ch] print:max-w-[60ch] relative; @apply font-serif max-w-[80ch] relative;
} }
.text .page, .text .page,
@@ -89,6 +96,7 @@
.text .ul, .text .ul,
.text .dul, .text .dul,
.text .it, .text .it,
.text .tl,
.text .pe, .text .pe,
.text .gr, .text .gr,
.text .hb, .text .hb,
@@ -97,6 +105,7 @@
.text .align, .text .align,
.text .b, .text .b,
.text .i, .text .i,
.text .subst,
.text .insertion, .text .insertion,
.text .del, .text .del,
.text .fn, .text .fn,
@@ -104,6 +113,10 @@
@apply inline; @apply inline;
} }
.address {
@apply contents;
}
.text .b { .text .b {
@apply font-bold; @apply font-bold;
} }
@@ -157,7 +170,20 @@
} }
.text .page { .text .page {
@apply font-sans text-sm text-gray-500; @apply font-sans text-sm text-gray-500 absolute left-0 ml-[-5%] w-[2%] mt-[0.4rem] leading-[1.2];
}
.text .eanchor-page::before {
content: " | ";
@apply text-gray-500 font-sans text-sm relative bottom-[0.1rem];
}
/* .text .page + br { */
/* @apply hidden; */
/* } */
.text .page::before {
content: "";
} }
.text .page.index-1 { .text .page.index-1 {
@@ -180,7 +206,7 @@
.tradition, .tradition,
.text, .text,
.text * { .text * {
clear: both; @apply clear-both;
} }
.text .align.pos-right { .text .align.pos-right {
@@ -197,11 +223,12 @@
} }
.text .insertion::before { .text .insertion::before {
margin-right: -0.2em;
content: "⌞"; content: "⌞";
} }
.text .insertion::after { .text .insertion::after {
padding-left: 0.5em; margin-left: -0.2em;
content: "⌟"; content: "⌟";
} }
@@ -227,22 +254,20 @@
top: 55%; top: 55%;
} }
.text .sidenote { .text .note-note {
@apply block border-l-4 border-slate-200 pl-2 my-1; @apply inline text-sm text-gray-500 relative left-[0.1rem] -top-[0.1rem];
} }
.text .note, .text .sidenote {
.text .hand-person, @apply border-l-4 border-slate-200 pl-2 my-1;
.text .sidenote-meta {
@apply absolute right-[-45ch] w-[40ch] px-2 mr-2 bg-gray-100 italic text-sm text-gray-700 font-sans;
} }
.text .sidenote-page::before { .text .sidenote-page::before {
content: "S. "; content: "| S. ";
} }
.text .sidenote-pos::before { .text .sidenote-pos {
content: "Pos: "; @apply !hidden;
} }
.text .hand { .text .hand {