Lots of stuff

This commit is contained in:
Simon Martens
2025-06-24 18:20:06 +02:00
parent 3127446dab
commit 9563145aeb
29 changed files with 1694 additions and 1386 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -30,9 +30,18 @@
</div>
<div class="text flex flex-row print:flex-col">
{{- Safe (ParseGeneric .text.Content) -}}
{{- Safe $model.text -}}
</div>
<div class="traditions mt-12 pt-3 border-t-gray-200 border-t-1 max-w-[90ch] print:border-none">
{{ template "_lettertrad" $model.meta -}}
</div>
<script type="module">
// WARNING: We need to wait for the fonts to settle before rendering anything
document.fonts.ready.then(() => {
if (window.alignSidenotes) {
window.alignSidenotes();
}
});
</script>

View File

@@ -37,8 +37,8 @@
{{ else if $i -}}
,
{{ end }}
{{- $person := Person $p.Reference -}}
{{- $person.Name -}}
{{- $person := Person $p.Reference }}
{{ $person.Name -}}
{{- end -}}
</div>
<div class="mx-3">
@@ -57,8 +57,25 @@
{{- if $i -}}
,
{{- end -}}
{{- $person := Person $p.Reference -}}
{{- $person.Name -}}
{{- $person := Person $p.Reference }}
{{ $person.Name -}}
{{- end -}}
{{ if and $sr.Received.Places (len $sr.Received.Places) }}
{{- range $i, $p := $sr.Received.Places -}}
{{- $place := Place $p.Reference }}
{{- if and $i (eq $i (Minus (len $sr.Received.Places) 1)) -}}
und
{{- end -}}
{{- if $i -}}
,
{{- end -}}
{{- if eq $i 0 -}}
&nbsp;({{- $place.Name -}}
{{- else -}}
{{ $place.Name -}}
{{- end -}}
{{- end -}}
)
{{- end -}}
</div>
{{- else -}}

View File

@@ -5,7 +5,7 @@
{{- (App $trad.Reference).Name -}}
</div>
<div class="tradition-text text hyphens-auto font-sans">
{{- Safe (ParseGeneric $trad.Content) -}}
{{- Safe (ParseGeneric $model $trad.Content) -}}
</div>
</div>
{{- end -}}

View File

@@ -158,28 +158,89 @@ class ScrollButton extends HTMLElement {
}
}
let positionedIntervals = [];
function alignSidenotes() {
positionedIntervals = [];
_alignSidenotes(".count", ".page", ".eanchor-page");
_alignSidenotes(".notes", ".note-hand", ".hand");
_alignSidenotes(".notes", ".note-sidenote-meta", ".sidenote");
}
function _alignSidenotes(container, align, alignto) {
const fulltext = document.querySelector(".fulltext");
const cont = document.querySelector(container);
if (!cont) return;
const notes = Array.from(cont.querySelectorAll(align));
// Reset classes and inline styles
notes.forEach((note) => {
note.classList.remove("margin-note");
note.style.top = "";
});
// Skip on print
if (window.matchMedia("print").matches) return;
const textRect = cont.getBoundingClientRect();
const GUTTER = 0; // space in pixels between notes
notes.forEach((note) => {
const noteId = note.id;
if (!noteId) return;
const anchor = fulltext.querySelector(`${alignto}[aria-describedby="${noteId}"]`);
if (!anchor) return;
note.classList.add("margin-note");
const anchorRect = anchor.getBoundingClientRect();
const baseTop = anchorRect.top - textRect.top;
const noteHeight = note.getBoundingClientRect().height;
let top = baseTop;
// Adjust to prevent overlap
let collision;
do {
collision = false;
for (const interval of positionedIntervals) {
const intervalTop = interval.top;
const intervalBottom = interval.bottom;
if (top < intervalBottom && top + noteHeight > intervalTop) {
console.log("Collision detected", {
top,
bottom: top + noteHeight,
intervalTop,
intervalBottom,
newTop: intervalBottom + GUTTER,
});
top = intervalBottom + GUTTER;
collision = true;
}
}
} while (collision);
// Record this note's interval
positionedIntervals.push({ top, bottom: top + noteHeight });
note.style.top = `${top}px`;
});
notes.forEach((note) => {
note.style.visibility = "visible";
});
}
// INFO: these are global functions that should be executed ONCE when the page loads, not
// on every HTMX request.
function Startup() {
let pagedPreviewer = null;
const positionedIntervals = [];
// INFO: Generate a print preview of the page if the URL has ?print=true
if (new URL(window.location).searchParams.get("print") === "true") {
showPreview();
}
// INFO: Listeners for sidenotes
window.addEventListener("load", () => {
alignSidenotes();
});
window.addEventListener("resize", alignSidenotes);
if (htmx) {
window.addEventListener("htmx:afterSettle", (_) => {
alignSidenotes();
});
}
function showPreview() {
if (!pagedPreviewer) {
pagedPreviewer = new Previewer();
@@ -195,77 +256,11 @@ function Startup() {
window.location.reload();
});
}
function alignSidenotes() {
_alignSidenotes(".count", ".page", ".eanchor-page");
_alignSidenotes(".notes", ".note-hand", ".hand");
_alignSidenotes(".notes", ".note-sidenote-meta", ".sidenote");
}
function _alignSidenotes(container, align, alignto) {
const fulltext = document.querySelector(".fulltext");
const cont = document.querySelector(container);
if (!cont) return;
const notes = Array.from(cont.querySelectorAll(align));
// Reset classes and inline styles
notes.forEach((note) => {
note.classList.remove("margin-note");
note.style.top = "";
});
// Skip on print
if (window.matchMedia("print").matches) return;
const textRect = cont.getBoundingClientRect();
const GUTTER = 0; // space in pixels between notes
notes.forEach((note) => {
const noteId = note.id;
if (!noteId) return;
const anchor = fulltext.querySelector(`${alignto}[aria-describedby="${noteId}"]`);
if (!anchor) return;
note.classList.add("margin-note");
const anchorRect = anchor.getBoundingClientRect();
const baseTop = anchorRect.top - textRect.top;
const noteHeight = note.getBoundingClientRect().height;
let top = baseTop;
// Adjust to prevent overlap
let collision;
do {
collision = false;
for (const interval of positionedIntervals) {
const intervalTop = interval.top;
const intervalBottom = interval.bottom;
if (top < intervalBottom && top + noteHeight > intervalTop) {
console.log("Collision detected", {
top,
bottom: top + noteHeight,
intervalTop,
intervalBottom,
newTop: intervalBottom + GUTTER,
});
top = intervalBottom + GUTTER;
collision = true;
}
}
} while (collision);
// Record this note's interval
positionedIntervals.push({ top, bottom: top + noteHeight });
note.style.top = `${top}px`;
});
notes.forEach((note) => {
note.style.visibility = "visible";
});
}
}
customElements.define(SCROLL_BUTTON_ELEMENT, ScrollButton);
customElements.define(TOOLTIP_ELEMENT, ToolTip);
export { XSLTParseProcess, ScrollButton, Previewer, Startup };
window.alignSidenotes = alignSidenotes;
export { XSLTParseProcess, ScrollButton, Previewer, Startup, alignSidenotes };

View File

@@ -109,6 +109,8 @@
.text {
@apply font-serif relative;
--text-color-rgb: 53, 53, 53;
color: rgb(var(--text-color-rgb));
}
.text .count {
@@ -141,6 +143,8 @@
.text .i,
.text .subst,
.text .insertion,
.text .insertion-marker,
.text .ddel,
.text .del,
.text .fn,
.text .anchor {
@@ -220,7 +224,7 @@
}
.text .dul {
@apply underline decoration-double;
@apply underline decoration-double decoration-[1px];
}
.text .it {
@@ -255,10 +259,35 @@
.text .insertion::after {
@apply text-slate-700;
margin-left: -0.2em;
margin-left: -0.4ch;
content: "⌟";
}
.text .insertion-marker {
@apply text-nowrap;
}
.text .insertion-marker::before {
@apply text-slate-700 text-nowrap text-sm relative bottom-[-0.15rem] -ml-[0.4ch] pr-[0.4ch] inline-block;
}
.text .insertion.pos-left .insertion-marker::before {
content: "🠊";
}
.text .insertion.pos-right .insertion-marker::before {
content: "🠜";
}
.text .insertion.pos-top .insertion-marker::before {
@apply bottom-0 text-xs;
content: "🠟";
}
.text .insertion.pos-bottom .insertion-marker::before {
@apply bottom-0 text-xs;
content: "🠝";
}
.text .nr::before {
@apply text-slate-700;
content: "⸰";
@@ -279,26 +308,60 @@
}
.text .del {
@apply line-through;
@apply line-through relative;
}
.text .del .del::before {
content: "";
@apply absolute inset-x-0 top-1/2 h-px bg-black;
@apply absolute inset-x-0 top-[65%] h-[1px] bg-black w-full;
}
.text .ddel {
@apply line-through relative;
}
.text .ddel::before {
content: "";
@apply absolute inset-x-0 top-[65%] h-[1px] bg-black w-full;
}
.text .ddel {
@apply relative;
}
.text .ddel::before {
top: 55%;
}
.text .ddel::after {
top: 45%;
}
.text .sidenote {
@apply border-l-4 border-slate-200 pl-2 my-4;
}
.text .hand {
@apply inline text-blue-950 !font-didone text-[0.9rem];
@apply inline !font-didone text-[0.9rem];
/* darker blue hue */
--text-color-rgb: 0, 0, 39;
color: rgb(var(--text-color-rgb));
}
.text .er {
text-decoration: line-through;
text-decoration-thickness: 17px;
background-image: repeating-linear-gradient(
-45deg,
rgba(var(--text-color-rgb), 0.5),
transparent 1px,
transparent 6px
);
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
color: transparent;
text-shadow: 0 0 rgb(var(--text-color-rgb));
}
.text .sidenote-page::before {