Var fixes

This commit is contained in:
Simon Martens
2025-09-14 23:04:33 +02:00
parent 6b9d45642d
commit 9c287701bb
14 changed files with 1047 additions and 185 deletions

View File

@@ -37,6 +37,6 @@ func GetIssue(kgpz *xmlmodels.Library) fiber.Handler {
return c.SendStatus(fiber.StatusNotFound) return c.SendStatus(fiber.StatusNotFound)
} }
return c.Render("/ausgabe/", fiber.Map{"model": issue, "year": yi, "issue": di}) return c.Render("/ausgabe/", fiber.Map{"model": issue, "year": yi, "issue": di}, "fullwidth")
} }
} }

1
log_output.txt Normal file
View File

@@ -0,0 +1 @@
--> 2025/09/14 19:38:08 Received json struct: {JsonRpc:2.0 Id:1 Method:initialize Params:map[capabilities:map[general:map[positionEncodings:[utf-8 utf-16 utf-32]] textDocument:map[callHierarchy:map[dynamicRegistration:false] codeAction:map[codeActionLiteralSupport:map[codeActionKind:map[valueSet:[ quickfix refactor refactor.extract refactor.inline refactor.rewrite source source.organizeImports]]] dataSupport:true dynamicRegistration:true isPreferredSupport:true resolveSupport:map[properties:[edit command]]] codeLens:map[dynamicRegistration:false resolveSupport:map[properties:[command]]] completion:map[completionItem:map[commitCharactersSupport:false deprecatedSupport:true documentationFormat:[markdown plaintext] insertReplaceSupport:true insertTextModeSupport:map[valueSet:[1]] labelDetailsSupport:true preselectSupport:false resolveSupport:map[properties:[documentation detail additionalTextEdits command data]] snippetSupport:true tagSupport:map[valueSet:[1]]] completionItemKind:map[valueSet:[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25]] completionList:map[itemDefaults:[commitCharacters editRange insertTextFormat insertTextMode data]] contextSupport:true dynamicRegistration:false insertTextMode:1] declaration:map[linkSupport:true] definition:map[dynamicRegistration:true linkSupport:true] diagnostic:map[dynamicRegistration:false tagSupport:map[valueSet:[1 2]]] documentHighlight:map[dynamicRegistration:false] documentSymbol:map[dynamicRegistration:false hierarchicalDocumentSymbolSupport:true symbolKind:map[valueSet:[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]]] foldingRange:map[dynamicRegistration:false foldingRange:map[collapsedText:true] foldingRangeKind:map[valueSet:[comment imports region]] lineFoldingOnly:true] formatting:map[dynamicRegistration:true] hover:map[contentFormat:[markdown plaintext] dynamicRegistration:true] implementation:map[linkSupport:true] inlayHint:map[dynamicRegistration:true resolveSupport:map[properties:[textEdits tooltip location command]]] publishDiagnostics:map[dataSupport:true relatedInformation:true tagSupport:map[valueSet:[1 2]]] rangeFormatting:map[dynamicRegistration:true rangesSupport:true] references:map[dynamicRegistration:false] rename:map[dynamicRegistration:true prepareSupport:true] semanticTokens:map[augmentsSyntaxTokens:true dynamicRegistration:false formats:[relative] multilineTokenSupport:false overlappingTokenSupport:true requests:map[full:map[delta:true] range:false] serverCancelSupport:false tokenModifiers:[declaration definition readonly static deprecated abstract async modification documentation defaultLibrary] tokenTypes:[namespace type class enum interface struct typeParameter parameter variable property enumMember event function method macro keyword modifier comment string number regexp operator decorator]] signatureHelp:map[dynamicRegistration:false signatureInformation:map[activeParameterSupport:true documentationFormat:[markdown plaintext] parameterInformation:map[labelOffsetSupport:true]]] synchronization:map[didSave:true dynamicRegistration:false willSave:true willSaveWaitUntil:true] typeDefinition:map[linkSupport:true]] window:map[showDocument:map[support:true] showMessage:map[messageActionItem:map[additionalPropertiesSupport:true]] workDoneProgress:true] workspace:map[applyEdit:true configuration:true didChangeConfiguration:map[dynamicRegistration:false] didChangeWatchedFiles:map[dynamicRegistration:false relativePatternSupport:true] inlayHint:map[refreshSupport:true] semanticTokens:map[refreshSupport:true] symbol:map[dynamicRegistration:false symbolKind:map[valueSet:[1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26]]] workspaceEdit:map[resourceOperations:[rename create delete]] workspaceFolders:true]] clientInfo:map[name:Neovim version:0.11.4+v0.11.4] processId:112938 rootPath:/home/simon/source/kgpz_web rootUri:file:///home/simon/source/kgpz_web trace:off workDoneToken:1 workspaceFolders:[map[name:/home/simon/source/kgpz_web uri:file:///home/simon/source/kgpz_web]]]}

View File

@@ -5,4 +5,3 @@ import "github.com/gofiber/fiber/v2"
func CacheFunc(c *fiber.Ctx) bool { func CacheFunc(c *fiber.Ctx) bool {
return c.Query("noCache") == "true" || c.Response().StatusCode() != fiber.StatusOK return c.Query("noCache") == "true" || c.Response().StatusCode() != fiber.StatusOK
} }

View File

@@ -17,6 +17,8 @@ type PieceByIssue struct {
xmlmodels.Piece xmlmodels.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 xmlmodels.IssueRef Reference xmlmodels.IssueRef
// Indicates if this is a continuation from a previous page
IsContinuation bool
} }
type PiecesByPage struct { type PiecesByPage struct {
@@ -24,6 +26,19 @@ type PiecesByPage struct {
Pages []int Pages []int
} }
// GroupedPieceByIssue represents a piece that may span multiple consecutive pages
type GroupedPieceByIssue struct {
PieceByIssue
StartPage int
EndPage int // Same as StartPage if not grouped
}
// GroupedPiecesByPage holds pieces grouped by consecutive pages when identical
type GroupedPiecesByPage struct {
Items map[int][]GroupedPieceByIssue
Pages []int
}
type IssuePage struct { type IssuePage struct {
PageNumber int PageNumber int
ImagePath string ImagePath string
@@ -31,9 +46,9 @@ type IssuePage struct {
} }
type IssueImages struct { type IssueImages struct {
MainPages []IssuePage MainPages []IssuePage
AdditionalPages map[int][]IssuePage // Beilage number -> pages AdditionalPages map[int][]IssuePage // Beilage number -> pages
HasImages bool HasImages bool
} }
type ImageFile struct { type ImageFile struct {
@@ -47,9 +62,9 @@ type ImageFile struct {
} }
type ImageRegistry struct { type ImageRegistry struct {
Files []ImageFile Files []ImageFile
ByYearIssue map[string][]ImageFile // "year-issue" -> files ByYearIssue map[string][]ImageFile // "year-issue" -> files
ByYearPage map[string]ImageFile // "year-page" -> file ByYearPage map[string]ImageFile // "year-page" -> file
} }
var imageRegistry *ImageRegistry var imageRegistry *ImageRegistry
@@ -59,8 +74,8 @@ type IssueVM struct {
xmlmodels.Issue xmlmodels.Issue
Next *xmlmodels.Issue Next *xmlmodels.Issue
Prev *xmlmodels.Issue Prev *xmlmodels.Issue
Pieces PiecesByPage Pieces GroupedPiecesByPage
AdditionalPieces PiecesByPage AdditionalPieces GroupedPiecesByPage
Images IssueImages Images IssueImages
} }
@@ -106,8 +121,9 @@ func NewSingleIssueView(y, no int, lib *xmlmodels.Library) (*IssueVM, error) {
slices.Sort(ppi.Pages) slices.Sort(ppi.Pages)
slices.Sort(ppa.Pages) slices.Sort(ppa.Pages)
sivm.Pieces = ppi // Group consecutive continuation pieces
sivm.AdditionalPieces = ppa sivm.Pieces = GroupConsecutiveContinuations(ppi)
sivm.AdditionalPieces = GroupConsecutiveContinuations(ppa)
images, err := LoadIssueImages(*issue) images, err := LoadIssueImages(*issue)
if err != nil { if err != nil {
@@ -130,12 +146,25 @@ func PiecesForIsssue(lib *xmlmodels.Library, issue xmlmodels.Issue) (PiecesByPag
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 {
p := PieceByIssue{Piece: piece, Reference: *d} // Add main entry on starting page
p := PieceByIssue{Piece: piece, Reference: *d, IsContinuation: false}
if d.Beilage > 0 { if d.Beilage > 0 {
functions.MapArrayInsert(ppa.Items, d.Von, p) functions.MapArrayInsert(ppa.Items, d.Von, p)
} else { } else {
functions.MapArrayInsert(ppi.Items, d.Von, p) functions.MapArrayInsert(ppi.Items, d.Von, p)
} }
// Add continuation entries for subsequent pages (if Bis > Von)
if d.Bis > d.Von {
for page := d.Von + 1; page <= d.Bis; page++ {
pContinuation := PieceByIssue{Piece: piece, Reference: *d, IsContinuation: true}
if d.Beilage > 0 {
functions.MapArrayInsert(ppa.Items, page, pContinuation)
} else {
functions.MapArrayInsert(ppi.Items, page, pContinuation)
}
}
}
} }
} }
@@ -145,6 +174,144 @@ func PiecesForIsssue(lib *xmlmodels.Library, issue xmlmodels.Issue) (PiecesByPag
return ppi, ppa, nil return ppi, ppa, nil
} }
// pagesHaveIdenticalContent checks if two pages have the same pieces (ignoring continuation status)
func pagesHaveIdenticalContent(items1, items2 []PieceByIssue) bool {
if len(items1) != len(items2) {
return false
}
// Create maps for comparison (ignore IsContinuation flag)
pieces1 := make(map[string]bool)
pieces2 := make(map[string]bool)
for _, piece := range items1 {
// Use piece ID and reference range as key (ignore continuation status)
key := piece.ID + "|" + strconv.Itoa(piece.Reference.Von) + "|" + strconv.Itoa(piece.Reference.Bis)
pieces1[key] = true
}
for _, piece := range items2 {
key := piece.ID + "|" + strconv.Itoa(piece.Reference.Von) + "|" + strconv.Itoa(piece.Reference.Bis)
pieces2[key] = true
}
// Check if maps are identical
for key := range pieces1 {
if !pieces2[key] {
return false
}
}
for key := range pieces2 {
if !pieces1[key] {
return false
}
}
return true
}
// pageContainsOnlyContinuations checks if a page contains only continuation pieces
func pageContainsOnlyContinuations(pageItems []PieceByIssue) bool {
if len(pageItems) == 0 {
return false
}
for _, piece := range pageItems {
if !piece.IsContinuation {
return false
}
}
return true
}
// GroupConsecutiveContinuations groups consecutive pages where next page only contains continuations
func GroupConsecutiveContinuations(pieces PiecesByPage) GroupedPiecesByPage {
grouped := GroupedPiecesByPage{
Items: make(map[int][]GroupedPieceByIssue),
Pages: []int{},
}
if len(pieces.Pages) == 0 {
return grouped
}
// Sort pages to ensure correct order
sortedPages := make([]int, len(pieces.Pages))
copy(sortedPages, pieces.Pages)
slices.Sort(sortedPages)
processedPages := make(map[int]bool)
for _, page := range sortedPages {
if processedPages[page] {
continue
}
pageItems := pieces.Items[page]
startPage := page
endPage := page
// Keep extending the group while next page contains only continuations
for checkPage := endPage + 1; ; checkPage++ {
// Only proceed if this page exists in our data
if _, exists := pieces.Items[checkPage]; !exists {
break
}
// Only proceed if this page hasn't been processed yet
if processedPages[checkPage] {
break
}
checkPageItems := pieces.Items[checkPage]
// Group if the next page contains ONLY continuations
if pageContainsOnlyContinuations(checkPageItems) {
endPage = checkPage
processedPages[checkPage] = true
// Continue to check if next page also contains only continuations
} else {
break
}
}
// Create grouped items with proper ordering (continuations first)
groupedItems := []GroupedPieceByIssue{}
// First add all continuation pieces
for _, piece := range pageItems {
if piece.IsContinuation {
groupedItems = append(groupedItems, GroupedPieceByIssue{
PieceByIssue: piece,
StartPage: startPage,
EndPage: endPage,
})
}
}
// Then add all non-continuation pieces
for _, piece := range pageItems {
if !piece.IsContinuation {
groupedItems = append(groupedItems, GroupedPieceByIssue{
PieceByIssue: piece,
StartPage: startPage,
EndPage: endPage,
})
}
}
if len(groupedItems) > 0 {
grouped.Items[startPage] = groupedItems
grouped.Pages = append(grouped.Pages, startPage)
}
processedPages[page] = true
}
slices.Sort(grouped.Pages)
return grouped
}
func LoadIssueImages(issue xmlmodels.Issue) (IssueImages, error) { func LoadIssueImages(issue xmlmodels.Issue) (IssueImages, error) {
// Initialize registry if not already done // Initialize registry if not already done
if err := initImageRegistry(); err != nil { if err := initImageRegistry(); err != nil {
@@ -204,39 +371,23 @@ func LoadIssueImages(issue xmlmodels.Issue) (IssueImages, error) {
} }
} }
// Create beilage pages - match with beilage page ranges // Create beilage pages - use ALL detected beilage files regardless of XML definitions
for _, additional := range issue.Additionals { if len(beilageFiles) > 0 {
beilagePages := make([]IssuePage, 0) beilagePages := make([]IssuePage, 0)
for page := additional.Von; page <= additional.Bis; page++ { // Add ALL beilage files found for this issue
var foundFile *ImageFile for _, file := range beilageFiles {
images.HasImages = true
// Look for beilage files that match this page number beilagePages = append(beilagePages, IssuePage{
for _, file := range beilageFiles { PageNumber: file.Page,
if file.Page == page { ImagePath: file.Path,
foundFile = &file Available: true,
break })
}
}
if foundFile != nil {
images.HasImages = true
beilagePages = append(beilagePages, IssuePage{
PageNumber: page,
ImagePath: foundFile.Path,
Available: true,
})
} else {
beilagePages = append(beilagePages, IssuePage{
PageNumber: page,
ImagePath: "",
Available: false,
})
}
} }
if len(beilagePages) > 0 { if len(beilagePages) > 0 {
images.AdditionalPages[additional.Nummer] = beilagePages // Use beilage number 1 as default
images.AdditionalPages[1] = beilagePages
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -19,7 +19,7 @@
@click="open = !open"> @click="open = !open">
<i class="ri-menu-line"></i> <i class="ri-menu-line"></i>
</button> </button>
<div :class="open? 'open' : 'closed'" x-show="open" class="absolute bg-slate-50 px-5 py-3"> <div :class="open? 'open' : 'closed'" x-show="open" class="absolute bg-slate-50 px-5 py-3 z-50">
<div> <div>
<div>Übersicht nach</div> <div>Übersicht nach</div>
<div class="ml-2 flex flex-col gap-y-2 mt-2"> <div class="ml-2 flex flex-col gap-y-2 mt-2">

View File

@@ -0,0 +1,67 @@
<!doctype html>
<html class="w-full h-full" {{ if .lang }}lang="{{ .lang }}"{{ end }}>
<head>
<meta charset="UTF-8" />
{{ block "head" . }}
<!-- Default Head elements -->
{{ end }}
{{ if .isDev }}
<link rel="icon" href="/assets/logo/dev_favicon.png" />
{{ else }}
<link rel="icon" href="/assets/logo/favicon.png" />
{{ end }}
<link href="/assets/css/remixicon.css" rel="stylesheet" />
<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>
<script src="/assets/js/client-side-templates.js" defer></script>
<link rel="stylesheet" type="text/css" href="/assets/css/fonts.css" />
<link rel="stylesheet" type="text/css" href="/assets/style.css" />
<script type="module">
import { setup } from "/assets/scripts.js";
setup();
</script>
</head>
<body class="w-full" hx-ext="response-targets" hx-boost="true">
<div class="flex flex-col min-h-screen">
<!-- Header and menu with constrained width -->
<div class="container max-w-(--breakpoint-2xl) mx-auto">
<header>
{{ block "_header" . }}
<!-- Default app header... -->
{{ end }}
</header>
<div>
{{ block "_menu" . }}
<!-- Default app menu... -->
{{ end }}
</div>
</div>
<!-- Main content with full width -->
<main class="flex-1 w-full">
{{ block "body" . }}
<!-- Default app body... -->
{{ end }}
</main>
<!-- Footer with constrained width -->
<div class="container max-w-(--breakpoint-2xl) mx-auto">
<footer>
{{ block "_footer" . }}
{{ end }}
</footer>
</div>
</div>
{{ EmbedXSLT "xslt/transform-citation.xsl" }}
</body>
</html>

View File

@@ -1,38 +1,84 @@
{{ $model := .model }} {{ $model := .model }}
{{ if $model.Images.HasImages }} {{ if $model.Images.HasImages }}
<!-- Container with proper padding -->
{{ $layout := .Request.URL.Query.Get "layout" }} <div class="px-4 lg:px-6 xl:px-8">
{{ if eq $layout "fullwidth" }} <!-- Three-column layout -->
{{ template "_fullwidth_layout" . }}
{{ else }}
<!-- Default sidebar layout -->
<div class="flex flex-col lg:flex-row gap-6 w-full min-h-screen mt-8"> <div class="flex flex-col lg:flex-row gap-6 w-full min-h-screen mt-8">
<!-- Left side: Sticky Inhaltsverzeichnis --> <!-- Column 1: Sticky Inhaltsverzeichnis -->
<div class="lg:w-1/3 xl:w-1/4 flex-shrink-0"> <div class="lg:w-1/4 xl:w-1/5 flex-shrink-0">
<div class="lg:sticky lg:top-12 lg:max-h-[calc(100vh-2rem)] lg:overflow-y-auto"> <div class="lg:sticky lg:top-12 lg:max-h-[calc(100vh-2rem)] lg:overflow-y-auto">
{{ template "_title_nav" . }} {{ template "_title_nav" . }}
{{ template "_inhaltsverzeichnis" . }} {{ template "_inhaltsverzeichnis" . }}
<!-- Switch to fullwidth button -->
<div class="mt-4">
<a href="?layout=fullwidth" class="flex items-center justify-center px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors text-sm">
<i class="ri-fullscreen-line mr-1"></i>
Vollbild
</a>
</div>
</div> </div>
</div> </div>
<!-- Right side: Newspaper pages --> <!-- Column 2: Newspaper pages -->
<div class="lg:w-2/3 xl:w-3/4 flex-1"> <div class="lg:w-3/5 xl:w-3/5 flex-1">
{{ template "_newspaper_layout" . }} {{ template "_newspaper_layout" . }}
</div> </div>
</div>
{{ end }}
<!-- Column 3: Navigation buttons -->
<div class="w-16 lg:w-20 xl:w-24 flex-shrink-0">
<div class="lg:sticky lg:top-12 lg:max-h-[calc(100vh-2rem)]">
<div class="space-y-3 flex flex-col items-center px-2 pt-8">
<button
id="prevPageBtn"
onclick="scrollToPreviousPage()"
class="w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-gray-200 hover:bg-gray-300 text-gray-700 hover:text-gray-800 border border-gray-300 transition-colors duration-200 flex items-center justify-center cursor-pointer"
title="Vorherige Seite"
style="display: none;">
<i class="ri-arrow-up-line text-lg lg:text-xl"></i>
</button>
<button
id="nextPageBtn"
onclick="scrollToNextPage()"
class="w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-gray-200 hover:bg-gray-300 text-gray-700 hover:text-gray-800 border border-gray-300 transition-colors duration-200 flex items-center justify-center cursor-pointer"
title="Nächste Seite">
<i class="ri-arrow-down-line text-lg lg:text-xl"></i>
</button>
{{ if $model.AdditionalPieces.Pages }}
<button
id="beilageBtn"
onclick="scrollToBeilage()"
class="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"
title="Zu Beilage">
<i class="ri-attachment-line text-lg lg:text-xl"></i>
</button>
{{ end }}
<!-- Separator for utility buttons -->
<div class="w-full border-t border-gray-200 my-4"></div>
<!-- Share Link Button -->
<button
id="shareLinkBtn"
onclick="shareCurrentPage()"
class="w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-blue-100 hover:bg-blue-200 text-blue-700 hover:text-blue-800 border border-blue-300 transition-colors duration-200 flex items-center justify-center cursor-pointer"
title="Link zur aktuellen Seite teilen">
<i class="ri-share-line text-lg lg:text-xl"></i>
</button>
<!-- Citation Button -->
<button
id="citationBtn"
onclick="generateCitation()"
class="w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-green-100 hover:bg-green-200 text-green-700 hover:text-green-800 border border-green-300 transition-colors duration-200 flex items-center justify-center cursor-pointer"
title="Zitation für diese Seite generieren">
<i class="ri-file-text-line text-lg lg:text-xl"></i>
</button>
</div>
</div>
</div>
</div>
</div>
{{ else }} {{ else }}
<div class="max-w-4xl"> <div class="px-4 lg:px-6 xl:px-8">
{{ template "_title_nav" . }} <div class="max-w-4xl">
{{ template "_inhaltsverzeichnis" . }} {{ template "_title_nav" . }}
</div> {{ template "_inhaltsverzeichnis" . }}
</div>
</div>
{{ end }} {{ end }}

View File

@@ -64,11 +64,20 @@ document.addEventListener('DOMContentLoaded', function() {
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => { entries.forEach((entry) => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
// Get page number from the container // Check if this is a double-spread container
const pageImg = entry.target.querySelector('img[data-page]'); const doubleSpread = entry.target.querySelector('.double-spread');
if (pageImg) { if (doubleSpread) {
const pageNumber = pageImg.getAttribute('data-page'); // Handle double-spread: highlight both pages
markCurrentPageInInhaltsverzeichnis(pageNumber); const pageImages = entry.target.querySelectorAll('img[data-page]');
const pageNumbers = Array.from(pageImages).map(img => img.getAttribute('data-page'));
markCurrentPagesInInhaltsverzeichnis(pageNumbers);
} else {
// Handle single page
const pageImg = entry.target.querySelector('img[data-page]');
if (pageImg) {
const pageNumber = pageImg.getAttribute('data-page');
markCurrentPageInInhaltsverzeichnis(pageNumber);
}
} }
// Update current active index // Update current active index
@@ -119,17 +128,52 @@ function scrollToBeilage() {
} }
function markCurrentPageInInhaltsverzeichnis(pageNumber) { function markCurrentPageInInhaltsverzeichnis(pageNumber) {
// Reset all entries in Inhaltsverzeichnis markCurrentPagesInInhaltsverzeichnis([pageNumber]);
document.querySelectorAll('.inhalts-entry').forEach(entry => { }
entry.classList.remove('bg-red-100', 'border-red-300');
entry.classList.add('bg-slate-50'); function markCurrentPagesInInhaltsverzeichnis(pageNumbers) {
// Reset all page numbers in Inhaltsverzeichnis
document.querySelectorAll('.page-number-inhalts').forEach(pageNum => {
pageNum.classList.remove('bg-red-500', 'text-white');
pageNum.classList.add('text-slate-700');
// Restore original background colors
if (pageNum.classList.contains('bg-amber-50')) {
// Keep amber background for Beilage pages
} else {
pageNum.classList.remove('bg-amber-50');
pageNum.classList.add('bg-blue-50');
}
}); });
// Find and highlight the current page entry // Find and highlight the current page numbers
const pageEntry = document.querySelector(`.inhalts-entry[data-page="${pageNumber}"]`); const highlightedElements = [];
if (pageEntry) { const highlightedRanges = new Set(); // Track which ranges we've already highlighted
pageEntry.classList.remove('bg-slate-50');
pageEntry.classList.add('bg-red-100', 'border-red-300'); pageNumbers.forEach(pageNumber => {
// Look for all entries that should be highlighted for this page
const allPageNumbers = document.querySelectorAll('.page-number-inhalts');
for (const pageNumElement of allPageNumbers) {
const startPage = parseInt(pageNumElement.getAttribute('data-page-number'));
const endPage = parseInt(pageNumElement.getAttribute('data-end-page'));
const rangeKey = `${startPage}-${endPage}`;
// Check if this page falls within this range
if (pageNumber >= startPage && pageNumber <= endPage) {
// Only highlight this range once, even if multiple visible pages fall within it
if (!highlightedRanges.has(rangeKey)) {
pageNumElement.classList.remove('bg-blue-50', 'bg-amber-50', 'text-slate-700');
pageNumElement.classList.add('bg-red-500', 'text-white');
highlightedElements.push(pageNumElement);
highlightedRanges.add(rangeKey);
}
}
}
});
// Auto-scroll to first highlighted element if it exists
if (highlightedElements.length > 0) {
scrollToHighlightedPage(highlightedElements[0]);
} }
// Also highlight page indicators // Also highlight page indicators
@@ -138,10 +182,35 @@ function markCurrentPageInInhaltsverzeichnis(pageNumber) {
indicator.classList.add('bg-blue-50', 'text-slate-600'); indicator.classList.add('bg-blue-50', 'text-slate-600');
}); });
const pageIndicator = document.querySelector(`.page-indicator[data-page="${pageNumber}"]`); // Highlight page indicators for all current pages
if (pageIndicator) { pageNumbers.forEach(pageNumber => {
pageIndicator.classList.remove('bg-blue-50', 'bg-green-50', 'text-slate-600'); const pageIndicator = document.querySelector(`.page-indicator[data-page="${pageNumber}"]`);
pageIndicator.classList.add('bg-red-500', 'text-white'); if (pageIndicator) {
pageIndicator.classList.remove('bg-blue-50', 'bg-green-50', 'bg-amber-50', 'text-slate-600');
pageIndicator.classList.add('bg-red-500', 'text-white');
}
});
}
function scrollToHighlightedPage(element) {
// Check if the element is in a scrollable container
const inhaltsContainer = element.closest('.lg\\:overflow-y-auto');
if (inhaltsContainer) {
// Calculate position
const containerRect = inhaltsContainer.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
// Check if element is not fully visible
const isAboveContainer = elementRect.top < containerRect.top;
const isBelowContainer = elementRect.bottom > containerRect.bottom;
if (isAboveContainer || isBelowContainer) {
// Scroll to make element visible with some padding
element.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
} }
} }
@@ -161,4 +230,117 @@ document.addEventListener('keydown', function(e) {
scrollToNextPage(); scrollToNextPage();
} }
}); });
function shareCurrentPage() {
const button = document.getElementById('shareLinkBtn');
// Get current page information
let pageInfo = '';
// Try to get the currently visible page number from active containers
if (window.currentActiveIndex !== undefined && window.currentPageContainers && window.currentPageContainers[window.currentActiveIndex]) {
const activeContainer = window.currentPageContainers[window.currentActiveIndex];
const pageElement = activeContainer.querySelector('[data-page]');
if (pageElement) {
const pageNumber = pageElement.getAttribute('data-page');
pageInfo = `#page-${pageNumber}`;
}
}
// Construct the shareable URL
const currentUrl = window.location.origin + window.location.pathname + pageInfo;
// Try to use Web Share API if available (mobile browsers)
if (navigator.share) {
navigator.share({
title: document.title,
url: currentUrl
}).catch(err => {
console.log('Error sharing:', err);
// Fallback to clipboard
copyToClipboard(currentUrl, button);
});
} else {
// Fallback: copy to clipboard
copyToClipboard(currentUrl, button);
}
}
function copyToClipboard(text, button) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
// Show temporary notification
showNotification('Link kopiert!', 'success', button);
}).catch(err => {
console.error('Failed to copy:', err);
showNotification('Kopieren fehlgeschlagen', 'error', button);
});
} else {
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showNotification('Link kopiert!', 'success', button);
} catch (err) {
console.error('Fallback copy failed:', err);
showNotification('Kopieren fehlgeschlagen', 'error', button);
}
document.body.removeChild(textarea);
}
}
function generateCitation() {
const button = document.getElementById('citationBtn');
// Get current page and issue information
const issueInfo = document.title || 'KGPZ';
const currentUrl = window.location.href;
// Basic citation format (can be expanded later)
const currentDate = new Date().toLocaleDateString('de-DE');
const citation = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${issueInfo}. Digital verfügbar unter: ${currentUrl} (Zugriff: ${currentDate}).`;
// Copy citation to clipboard
copyToClipboard(citation, button);
}
function showNotification(message, type = 'success', button) {
// Remove any existing notifications
const existingNotification = document.getElementById('notification');
if (existingNotification) {
existingNotification.remove();
}
// Create notification element
const notification = document.createElement('div');
notification.id = 'notification';
notification.className = `fixed px-3 py-2 rounded-md text-white text-sm font-medium z-50 transition-opacity duration-300 ${
type === 'success' ? 'bg-green-500' : 'bg-red-500'
}`;
notification.textContent = message;
// Position notification next to button if button is provided
if (button) {
const buttonRect = button.getBoundingClientRect();
notification.style.left = `${buttonRect.left - 80}px`; // Position to the left of button
notification.style.top = `${buttonRect.top + buttonRect.height / 2 - 20}px`; // Center vertically with button
} else {
// Fallback to top-right corner
notification.className += ' top-4 right-4';
}
// Add to page
document.body.appendChild(notification);
// Auto-remove after 3 seconds
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
</script> </script>

View File

@@ -12,25 +12,28 @@
<div class="mb-4 first:mb-0"> <div class="mb-4 first:mb-0">
<div class="flex items-center gap-2 mb-3 pb-2 border-b border-slate-200"> <div class="flex items-center gap-2 mb-3 pb-2 border-b border-slate-200">
<i class="ri-file-text-line text-blue-600 text-sm"></i> <i class="ri-file-text-line text-blue-600 text-sm"></i>
<span class="font-medium text-slate-700 bg-blue-50 px-2 py-1 rounded text-xs">{{ $page }}</span> {{ $pageItems := (index $model.Pieces.Items $page) }}
{{ $maxEndPage := $page }}
{{ range $groupedPiece := $pageItems }}{{ if gt $groupedPiece.EndPage $maxEndPage }}{{ $maxEndPage = $groupedPiece.EndPage }}{{ end }}{{ end }}
<span class="page-number-inhalts font-bold text-slate-700 bg-blue-50 px-2 py-1 rounded text-xs transition-colors duration-200" data-page-number="{{ $page }}" data-end-page="{{ $maxEndPage }}" data-page-range="{{ $page }}-{{ $maxEndPage }}">{{ if ne $page $maxEndPage }}{{ $page }}-{{ $maxEndPage }}{{ else }}{{ $page }}{{ end }}</span>
</div> </div>
<div class="space-y-3"> <div class="space-y-1">
{{ range $piece := (index $model.Pieces.Items $page) }} {{ range $groupedPiece := (index $model.Pieces.Items $page) }}
<div class="inhalts-entry py-2 px-3 bg-slate-50 rounded hover:bg-slate-100 transition-colors duration-200" data-page="{{ $page }}"> <div class="inhalts-entry py-1 px-3 bg-slate-50 rounded hover:bg-slate-100 transition-colors duration-200" data-page="{{ $page }}">
{{ template "_inhaltsverzeichnis_eintrag" $piece }} {{ template "_inhaltsverzeichnis_eintrag" $groupedPiece.PieceByIssue }}
<!-- Links zu anderen Teilen: --> <!-- Links zu anderen Teilen: -->
{{ if gt (len $piece.IssueRefs) 1 }} {{ if and (not $groupedPiece.PieceByIssue.IsContinuation) (gt (len $groupedPiece.IssueRefs) 1) }}
<div class="mt-3 pt-3 border-t border-slate-100"> <div class="mt-1 pt-1 border-t border-slate-100">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-0.5">
<i class="ri-links-line text-blue-500 text-sm"></i> <i class="ri-links-line text-blue-500 text-sm"></i>
<span class="text-sm font-medium text-slate-600">{{ len $piece.IssueRefs }} Teile:</span> <span class="text-sm font-medium text-slate-600">{{ len $groupedPiece.IssueRefs }} Teile:</span>
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-1">
{{ range $issue := $piece.IssueRefs }} {{ range $issue := $groupedPiece.IssueRefs }}
<a <a
href="/{{- $issue.When -}}/{{- $issue.Nr -}}" href="/{{- $issue.When -}}/{{- $issue.Nr -}}{{- if $issue.Von -}}{{- if $issue.Beilage -}}#beilage-{{ $issue.Beilage }}-page-{{ $issue.Von }}{{- else -}}#page-{{ $issue.Von }}{{- end -}}{{- end -}}"
class="inline-flex items-center gap-1 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium hover:bg-blue-100 transition-colors duration-150" class="inline-flex items-center gap-1 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium hover:bg-blue-100 transition-colors duration-150"
{{- if and (eq $issue.Nr $model.Number.No) (eq $issue.When.Year {{- if and (eq $issue.Nr $model.Number.No) (eq $issue.When.Year
$model.Datum.When.Year) $model.Datum.When.Year)
@@ -67,25 +70,28 @@
<div class="mb-4 first:mb-0"> <div class="mb-4 first:mb-0">
<div class="flex items-center gap-2 mb-3 pb-2 border-b border-slate-200"> <div class="flex items-center gap-2 mb-3 pb-2 border-b border-slate-200">
<i class="ri-file-text-line text-amber-600 text-sm"></i> <i class="ri-file-text-line text-amber-600 text-sm"></i>
<span class="font-medium text-slate-700 bg-amber-50 px-2 py-1 rounded text-xs">{{ $page }}</span> {{ $pageItems := (index $model.AdditionalPieces.Items $page) }}
{{ $maxEndPage := $page }}
{{ range $groupedPiece := $pageItems }}{{ if gt $groupedPiece.EndPage $maxEndPage }}{{ $maxEndPage = $groupedPiece.EndPage }}{{ end }}{{ end }}
<span class="page-number-inhalts font-bold text-slate-700 bg-amber-50 px-2 py-1 rounded text-xs transition-colors duration-200" data-page-number="{{ $page }}" data-end-page="{{ $maxEndPage }}" data-page-range="{{ $page }}-{{ $maxEndPage }}">{{ if ne $page $maxEndPage }}{{ $page }}-{{ $maxEndPage }}{{ else }}{{ $page }}{{ end }}</span>
</div> </div>
<div class="space-y-3"> <div class="space-y-3">
{{ range $piece := (index $model.AdditionalPieces.Items $page) }} {{ range $groupedPiece := (index $model.AdditionalPieces.Items $page) }}
<div class="inhalts-entry py-2 px-3 bg-slate-50 rounded hover:bg-slate-100 transition-colors duration-200" data-page="{{ $page }}"> <div class="inhalts-entry py-2 px-3 bg-slate-50 rounded hover:bg-slate-100 transition-colors duration-200" data-page="{{ $page }}">
{{ template "_inhaltsverzeichnis_eintrag" $piece }} {{ template "_inhaltsverzeichnis_eintrag" $groupedPiece.PieceByIssue }}
<!-- Links zu anderen Teilen: --> <!-- Links zu anderen Teilen: -->
{{ if gt (len $piece.IssueRefs) 1 }} {{ if and (not $groupedPiece.PieceByIssue.IsContinuation) (gt (len $groupedPiece.IssueRefs) 1) }}
<div class="mt-3 pt-3 border-t border-slate-100"> <div class="mt-1 pt-1 border-t border-slate-100">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-0.5">
<i class="ri-links-line text-blue-500 text-sm"></i> <i class="ri-links-line text-blue-500 text-sm"></i>
<span class="text-sm font-medium text-slate-600">{{ len $piece.IssueRefs }} Teile:</span> <span class="text-sm font-medium text-slate-600">{{ len $groupedPiece.IssueRefs }} Teile:</span>
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-1">
{{ range $issue := $piece.IssueRefs }} {{ range $issue := $groupedPiece.IssueRefs }}
<a <a
href="/{{- $issue.When -}}/{{- $issue.Nr -}}" href="/{{- $issue.When -}}/{{- $issue.Nr -}}{{- if $issue.Von -}}{{- if $issue.Beilage -}}#beilage-{{ $issue.Beilage }}-page-{{ $issue.Von }}{{- else -}}#page-{{ $issue.Von }}{{- end -}}{{- end -}}"
class="inline-flex items-center gap-1 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium hover:bg-blue-100 transition-colors duration-150" class="inline-flex items-center gap-1 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium hover:bg-blue-100 transition-colors duration-150"
{{- if and (eq $issue.Nr $model.Number.No) (eq $issue.When.Year {{- if and (eq $issue.Nr $model.Number.No) (eq $issue.When.Year
$model.Datum.When.Year) $model.Datum.When.Year)

View File

@@ -1,6 +1,8 @@
{{- $piece := . -}} {{- $piece := . -}}
{{- $fortsPrefix := "" -}}
{{- if $piece.IsContinuation -}}{{- $fortsPrefix = "<span class=\"italic text-gray-600\">(Forts.) </span>" -}}{{- end -}}
<div class="entry-description leading-relaxed mb-2"> <div class="entry-description leading-snug mb-1">
{{- $hasRezension := false -}} {{- $hasRezension := false -}}
{{- $hasWeltnachrichten := false -}} {{- $hasWeltnachrichten := false -}}
{{- $hasEinkommendeFremde := false -}} {{- $hasEinkommendeFremde := false -}}
@@ -131,18 +133,18 @@
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}} {{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
{{- $agent := GetAgent $agentref.Ref -}} {{- $agent := GetAgent $agentref.Ref -}}
{{- if gt (len $agent.Names) 0 -}} {{- if gt (len $agent.Names) 0 -}}
<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>{{ if $workTitle }}, Rezension: <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }}, Rezension: <em>{{ $title }}</em>{{ else }}, Rezension{{ end }}{{ if $place }} ({{ $place }}){{ end }} {{ Safe $fortsPrefix }}<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>{{ if $workTitle }}, Rezension: <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }}, Rezension: <em>{{ $title }}</em>{{ else }}, Rezension{{ end }}{{ if $place }} ({{ $place }}){{ end }}
{{- $authorFound = true -}} {{- $authorFound = true -}}
{{- break -}} {{- break -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- if not $authorFound -}} {{- if not $authorFound -}}
Rezension{{ if $workTitle }}: <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }}: <em>{{ $title }}</em>{{ end }}{{ if $place }} ({{ $place }}){{ end }} {{ Safe $fortsPrefix }}Rezension{{ if $workTitle }}: <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }}: <em>{{ $title }}</em>{{ end }}{{ if $place }} ({{ $place }}){{ end }}
{{- end -}} {{- end -}}
{{- else if $hasWeltnachrichten -}} {{- else if $hasWeltnachrichten -}}
Politische Nachrichten aus aller Welt {{ Safe $fortsPrefix }}Politische Nachrichten aus aller Welt
{{- else if $hasEinkommendeFremde -}} {{- else if $hasEinkommendeFremde -}}
{{- if $hasLokalnachrichten -}}Lokale Meldungen über einreisende Fremde{{- else if $hasNachruf -}}Nachruf und Einreiseliste{{- else -}}Einreiseliste{{- end -}}{{ if $place }} für {{ $place }}{{ end }} {{- if $hasLokalnachrichten -}}Lokale Meldungen über einreisende Fremde{{- else if $hasNachruf -}}Nachruf und Einreiseliste{{- else -}}Einreiseliste{{- end -}}{{ if $place }} für {{ $place }}{{ end }}
@@ -151,10 +153,10 @@
Wechselkurse{{ if $place }} in {{ $place }}{{ end }} Wechselkurse{{ if $place }} in {{ $place }}{{ end }}
{{- else if $hasBuecher -}} {{- else if $hasBuecher -}}
Bücheranzeigen{{ if $title }}: {{ $title }}{{ end }} Bücheranzeigen{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasLokalanzeigen -}} {{- else if $hasLokalanzeigen -}}
{{ if $hasNachruf }}Todesanzeige{{ else }}Lokalanzeige{{ end }}{{ if $place }} aus {{ $place }}{{ end }}{{ if $title }}: {{ $title }}{{ end }} {{ if $hasNachruf }}Todesanzeige{{ else }}Lokalanzeige{{ end }}{{ if $place }} aus {{ $place }}{{ end }}{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasLokalnachrichten -}} {{- else if $hasLokalnachrichten -}}
{{ if $hasLotterie }}Lotterienachrichten{{ else if $hasNachruf }}Nachrufe{{ else if $hasTheaterkritik }}Theaternachrichten{{ else if $hasPanegyrik }}Festlichkeiten{{ else }}Lokalnachrichten{{ end }}{{ if $place }} aus {{ $place }}{{ end }} {{ if $hasLotterie }}Lotterienachrichten{{ else if $hasNachruf }}Nachrufe{{ else if $hasTheaterkritik }}Theaternachrichten{{ else if $hasPanegyrik }}Festlichkeiten{{ else }}Lokalnachrichten{{ end }}{{ if $place }} aus {{ $place }}{{ end }}
@@ -165,18 +167,18 @@
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}} {{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
{{- $agent := GetAgent $agentref.Ref -}} {{- $agent := GetAgent $agentref.Ref -}}
{{- if gt (len $agent.Names) 0 -}} {{- if gt (len $agent.Names) 0 -}}
<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, {{ if $hasKommentar }}Gedicht mit Kommentar{{ else if $hasUebersetzung }}Gedichtübersetzung{{ else if $hasGelehrteNachrichten }}Gedicht zu gelehrten Angelegenheiten{{ else }}Gedicht{{ end }}{{ if $title }}: „{{ $title }}"{{ end }} <a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, {{ if $hasKommentar }}Gedicht mit Kommentar{{ else if $hasUebersetzung }}Gedichtübersetzung{{ else if $hasGelehrteNachrichten }}Gedicht zu gelehrten Angelegenheiten{{ else }}Gedicht{{ end }}{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- $authorFound = true -}} {{- $authorFound = true -}}
{{- break -}} {{- break -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- if not $authorFound -}} {{- if not $authorFound -}}
{{ if $hasKommentar }}Gedicht mit Kommentar{{ else if $hasUebersetzung }}Gedichtübersetzung{{ else if $hasGelehrteNachrichten }}Gedicht zu gelehrten Angelegenheiten{{ else }}Gedicht{{ end }}{{ if $title }}: „{{ $title }}"{{ end }} {{ if $hasKommentar }}Gedicht mit Kommentar{{ else if $hasUebersetzung }}Gedichtübersetzung{{ else if $hasGelehrteNachrichten }}Gedicht zu gelehrten Angelegenheiten{{ else }}Gedicht{{ end }}{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- end -}} {{- end -}}
{{- else if $hasVorladung -}} {{- else if $hasVorladung -}}
Gerichtliche Vorladung{{ if $place }} in {{ $place }}{{ end }}{{ if $title }}: {{ $title }}{{ end }} Gerichtliche Vorladung{{ if $place }} in {{ $place }}{{ end }}{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasAufsatz -}} {{- else if $hasAufsatz -}}
{{- $authorFound := false -}} {{- $authorFound := false -}}
@@ -184,18 +186,18 @@
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}} {{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
{{- $agent := GetAgent $agentref.Ref -}} {{- $agent := GetAgent $agentref.Ref -}}
{{- if gt (len $agent.Names) 0 -}} {{- if gt (len $agent.Names) 0 -}}
<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, {{ if $hasReplik }}Erwiderung{{ else if $hasUebersetzung }}Übersetzung{{ else if $hasNachruf }}Nachruf{{ else if $hasKommentar }}Kommentar{{ else if $hasRezepte }}Rezepte und Anleitungen{{ else }}Aufsatz{{ end }}{{ if $title }}: „{{ $title }}"{{ end }} {{ Safe $fortsPrefix }}<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, {{ if $hasReplik }}Erwiderung{{ else if $hasUebersetzung }}Übersetzung{{ else if $hasNachruf }}Nachruf{{ else if $hasKommentar }}Kommentar{{ else if $hasRezepte }}Rezepte und Anleitungen{{ else }}Aufsatz{{ end }}{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- $authorFound = true -}} {{- $authorFound = true -}}
{{- break -}} {{- break -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- if not $authorFound -}} {{- if not $authorFound -}}
{{ if $hasReplik }}Erwiderung{{ else if $hasUebersetzung }}Übersetzung{{ else if $hasNachruf }}Nachruf{{ else if $hasKommentar }}Kommentar{{ else if $hasRezepte }}Rezepte und Anleitungen{{ else }}Aufsatz{{ end }}{{ if $title }}: „{{ $title }}"{{ end }} {{ Safe $fortsPrefix }}{{ if $hasReplik }}Erwiderung{{ else if $hasUebersetzung }}Übersetzung{{ else if $hasNachruf }}Nachruf{{ else if $hasKommentar }}Kommentar{{ else if $hasRezepte }}Rezepte und Anleitungen{{ else }}Aufsatz{{ end }}{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- end -}} {{- end -}}
{{- else if $hasGelehrteNachrichten -}} {{- else if $hasGelehrteNachrichten -}}
{{ if $hasTheaterkritik }}Theaterkritik{{ else if $hasKommentar }}Gelehrter Kommentar{{ else }}Gelehrte Nachrichten{{ end }}{{ if $place }} aus {{ $place }}{{ end }} {{ Safe $fortsPrefix }}{{ if $hasTheaterkritik }}Theaterkritik{{ else if $hasKommentar }}Gelehrter Kommentar{{ else }}Gelehrte Nachrichten{{ end }}{{ if $place }} aus {{ $place }}{{ end }}
{{- else if $hasTheaterkritik -}} {{- else if $hasTheaterkritik -}}
{{- $authorFound := false -}} {{- $authorFound := false -}}
@@ -203,33 +205,33 @@
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}} {{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
{{- $agent := GetAgent $agentref.Ref -}} {{- $agent := GetAgent $agentref.Ref -}}
{{- if gt (len $agent.Names) 0 -}} {{- if gt (len $agent.Names) 0 -}}
<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, Theaterkritik{{ if $workTitle }} zu <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }} zu <em>{{ $title }}</em>{{ end }}{{ if $place }} ({{ $place }}){{ end }} {{ Safe $fortsPrefix }}<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, Theaterkritik{{ if $workTitle }} zu <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }} zu <em>{{ $title }}</em>{{ end }}{{ if $place }} ({{ $place }}){{ end }}
{{- $authorFound = true -}} {{- $authorFound = true -}}
{{- break -}} {{- break -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- if not $authorFound -}} {{- if not $authorFound -}}
Theaterkritik{{ if $workTitle }} zu <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }} zu <em>{{ $title }}</em>{{ end }}{{ if $place }} ({{ $place }}){{ end }} {{ Safe $fortsPrefix }}Theaterkritik{{ if $workTitle }} zu <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if $title }} zu <em>{{ $title }}</em>{{ end }}{{ if $place }} ({{ $place }}){{ end }}
{{- end -}} {{- end -}}
{{- else if $hasProklamation -}} {{- else if $hasProklamation -}}
Amtliche Proklamation{{ if $title }}: {{ $title }}{{ end }} {{ Safe $fortsPrefix }}Amtliche Proklamation{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasIneigenersache -}} {{- else if $hasIneigenersache -}}
{{ if $hasKommentar }}{{ if $hasNachtrag }}Ergänzender Kommentar{{ else }}Redaktioneller Kommentar{{ end }}{{ else if $hasReplik }}Redaktionelle Stellungnahme{{ else }}Anmerkung der Redaktion{{ end }}{{ if $title }}: {{ $title }}{{ end }} {{ Safe $fortsPrefix }}{{ if $hasKommentar }}{{ if $hasNachtrag }}Ergänzender Kommentar{{ else }}Redaktioneller Kommentar{{ end }}{{ else if $hasReplik }}Redaktionelle Stellungnahme{{ else }}Anmerkung der Redaktion{{ end }}{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasBrief -}} {{- else if $hasBrief -}}
{{ if $hasNachruf }}Kondolenzbrief{{ else }}Leserbrief{{ end }}{{- $authorFound := false -}}{{- range $agentref := $piece.AgentRefs -}}{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}{{- $agent := GetAgent $agentref.Ref -}}{{- if gt (len $agent.Names) 0 -}} von <a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>{{- $authorFound = true -}}{{- break -}}{{- end -}}{{- end -}}{{- end -}}{{ if $place }} aus {{ $place }}{{ end }} {{ Safe $fortsPrefix }}{{ if $hasNachruf }}Kondolenzbrief{{ else }}Leserbrief{{ end }}{{- $authorFound := false -}}{{- range $agentref := $piece.AgentRefs -}}{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}{{- $agent := GetAgent $agentref.Ref -}}{{- if gt (len $agent.Names) 0 -}} von <a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>{{- $authorFound = true -}}{{- break -}}{{- end -}}{{- end -}}{{- end -}}{{ if $place }} aus {{ $place }}{{ end }}
{{- else if $hasDesertionsliste -}} {{- else if $hasDesertionsliste -}}
Desertionsliste{{ if $place }} für {{ $place }}{{ end }} {{ Safe $fortsPrefix }}Desertionsliste{{ if $place }} für {{ $place }}{{ end }}
{{- else if $hasNotenblatt -}} {{- else if $hasNotenblatt -}}
{{ if $hasNachtrag }}Ergänztes {{ end }}Notenblatt{{ if $title }}: {{ $title }}{{ end }} {{ Safe $fortsPrefix }}{{ if $hasNachtrag }}Ergänztes {{ end }}Notenblatt{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasVorlesungsverzeichnis -}} {{- else if $hasVorlesungsverzeichnis -}}
Vorlesungsverzeichnis{{ if $place }} der Universität {{ $place }}{{ end }} {{ Safe $fortsPrefix }}Vorlesungsverzeichnis{{ if $place }} der Universität {{ $place }}{{ end }}
{{- else if $hasErzaehlung -}} {{- else if $hasErzaehlung -}}
{{- $authorFound := false -}} {{- $authorFound := false -}}
@@ -237,30 +239,30 @@
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}} {{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
{{- $agent := GetAgent $agentref.Ref -}} {{- $agent := GetAgent $agentref.Ref -}}
{{- if gt (len $agent.Names) 0 -}} {{- if gt (len $agent.Names) 0 -}}
<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, {{ if $hasUebersetzung }}Übersetzung einer Erzählung{{ else }}Erzählung{{ end }}{{ if $title }}: „{{ $title }}"{{ end }} {{ Safe $fortsPrefix }}<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>, {{ if $hasUebersetzung }}Übersetzung einer Erzählung{{ else }}Erzählung{{ end }}{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- $authorFound = true -}} {{- $authorFound = true -}}
{{- break -}} {{- break -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- if not $authorFound -}} {{- if not $authorFound -}}
{{ if $hasUebersetzung }}Übersetzung einer Erzählung{{ else }}Erzählung{{ end }}{{ if $title }}: „{{ $title }}"{{ end }} {{ Safe $fortsPrefix }}{{ if $hasUebersetzung }}Übersetzung einer Erzählung{{ else }}Erzählung{{ end }}{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}
{{- end -}} {{- end -}}
{{- else if $hasAbbildung -}} {{- else if $hasAbbildung -}}
{{ if $hasAufsatz }}Illustrierter Aufsatz{{ else }}Abbildung{{ end }}{{ if $title }}: {{ $title }}{{ end }} {{ Safe $fortsPrefix }}{{ if $hasAufsatz }}Illustrierter Aufsatz{{ else }}Abbildung{{ end }}{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasKriminalanzeige -}} {{- else if $hasKriminalanzeige -}}
Kriminalanzeige{{ if $place }} aus {{ $place }}{{ end }} {{ Safe $fortsPrefix }}Kriminalanzeige{{ if $place }} aus {{ $place }}{{ end }}
{{- else if $hasKorrektur -}} {{- else if $hasKorrektur -}}
Korrektur{{ if $title }}: {{ $title }}{{ end }} {{ Safe $fortsPrefix }}Korrektur{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasAnzeige -}} {{- else if $hasAnzeige -}}
{{ if $hasAuszug }}{{ if $hasGedicht }}Gedichtauszug{{ else }}Textauszug{{ end }}{{ else }}Anzeige{{ end }}{{ if $title }}: {{ $title }}{{ end }} {{ Safe $fortsPrefix }}{{ if $hasAuszug }}{{ if $hasGedicht }}Gedichtauszug{{ else }}Textauszug{{ end }}{{ else }}Anzeige{{ end }}{{ if $title }}: <em>{{ $title }}</em>{{ end }}
{{- else if $hasAuszug -}} {{- else if $hasAuszug -}}
Auszug{{ if $title }}: „{{ $title }}"{{ end }}{{ if $workTitle }} aus <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ end }} {{ Safe $fortsPrefix }}Auszug{{ if $title }}: <em>„{{ $title }}"</em>{{ end }}{{ if $workTitle }} aus <em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ end }}
{{- else -}} {{- else -}}
{{- $authorFound := false -}} {{- $authorFound := false -}}
@@ -268,21 +270,23 @@
{{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}} {{- if (or (eq $agentref.Category "") (eq $agentref.Category "autor")) -}}
{{- $agent := GetAgent $agentref.Ref -}} {{- $agent := GetAgent $agentref.Ref -}}
{{- if gt (len $agent.Names) 0 -}} {{- if gt (len $agent.Names) 0 -}}
<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>{{ if $title }}: {{ $title }}{{ end }}{{ if $workTitle }}{{ if $title }} aus {{ end }}<em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ end }} {{ Safe $fortsPrefix }}<a href="/akteure/{{ $agentref.Ref }}" class="author-link">{{ index $agent.Names 0 }}</a>{{ if $title }}: <em>{{ $title }}</em>{{ end }}{{ if $workTitle }}{{ if $title }} aus {{ end }}<em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ end }}
{{- $authorFound = true -}} {{- $authorFound = true -}}
{{- break -}} {{- break -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- if not $authorFound -}} {{- if not $authorFound -}}
{{ if $title }}{{ $title }}{{ end }}{{ if $workTitle }}{{ if $title }} aus {{ end }}<em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if not $title }}Beitrag ohne Titel{{ end }} {{ Safe $fortsPrefix }}{{ if $title }}<em>{{ $title }}</em>{{ end }}{{ if $workTitle }}{{ if $title }} aus {{ end }}<em>{{ $workTitle }}</em>{{ if $workAuthorName }} von <a href="/akteure/{{ $workAuthorID }}" class="author-link">{{ $workAuthorName }}</a>{{ end }}{{ else if not $title }}Beitrag ohne Titel{{ end }}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
</div> </div>
{{- if not $piece.IsContinuation -}}
{{- range $annotation := $piece.AnnotationNote.Annotations -}} {{- range $annotation := $piece.AnnotationNote.Annotations -}}
<div class="italic text-xs mt-1 text-slate-600"> <div class="italic text-sm mt-0.5 text-slate-600">
{{ $annotation.Inner.InnerXML }} {{ $annotation.Inner.InnerXML }}
</div> </div>
{{- end -}}
{{- end -}} {{- end -}}

View File

@@ -13,11 +13,11 @@
{{ if ge $pageCount 1 }} {{ if ge $pageCount 1 }}
{{ $firstPage := index $pages 0 }} {{ $firstPage := index $pages 0 }}
{{ if $firstPage.Available }} {{ if $firstPage.Available }}
<div class="newspaper-page-container"> <div class="newspaper-page-container" id="page-{{ $firstPage.PageNumber }}">
<div class="mb-3"> <div class="mb-3">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="ri-file-image-line text-blue-600"></i> <i class="ri-file-image-line text-blue-600"></i>
<span class="page-indicator text-sm font-medium text-slate-600 bg-blue-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $firstPage.PageNumber }}">{{ $firstPage.PageNumber }}</span> <span class="page-indicator text-sm font-bold text-slate-600 bg-blue-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $firstPage.PageNumber }}">{{ $firstPage.PageNumber }}</span>
</div> </div>
</div> </div>
<div class="single-page bg-white p-4 rounded-lg border border-slate-200 hover:border-slate-300 transition-colors duration-200"> <div class="single-page bg-white p-4 rounded-lg border border-slate-200 hover:border-slate-300 transition-colors duration-200">
@@ -37,11 +37,11 @@
{{ $middlePage1 := index $pages 1 }} {{ $middlePage1 := index $pages 1 }}
{{ $middlePage2 := index $pages 2 }} {{ $middlePage2 := index $pages 2 }}
{{ if and $middlePage1.Available $middlePage2.Available }} {{ if and $middlePage1.Available $middlePage2.Available }}
<div class="newspaper-page-container"> <div class="newspaper-page-container" id="page-{{ $middlePage1.PageNumber }}-{{ $middlePage2.PageNumber }}">
<div class="mb-3"> <div class="mb-3">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="ri-file-copy-2-line text-green-600"></i> <i class="ri-file-copy-2-line text-blue-600"></i>
<span class="page-indicator text-sm font-medium text-slate-600 bg-green-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $middlePage1.PageNumber }}">{{ $middlePage1.PageNumber }}-{{ $middlePage2.PageNumber }}</span> <span class="page-indicator text-sm font-bold text-slate-600 bg-blue-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $middlePage1.PageNumber }}">{{ $middlePage1.PageNumber }}-{{ $middlePage2.PageNumber }}</span>
</div> </div>
</div> </div>
<div class="double-spread bg-white p-4 rounded-lg border border-slate-200 hover:border-slate-300 transition-colors duration-200"> <div class="double-spread bg-white p-4 rounded-lg border border-slate-200 hover:border-slate-300 transition-colors duration-200">
@@ -66,11 +66,11 @@
{{ if ge $pageCount 4 }} {{ if ge $pageCount 4 }}
{{ $lastPage := index $pages 3 }} {{ $lastPage := index $pages 3 }}
{{ if $lastPage.Available }} {{ if $lastPage.Available }}
<div class="newspaper-page-container"> <div class="newspaper-page-container" id="page-{{ $lastPage.PageNumber }}">
<div class="mb-3"> <div class="mb-3">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="ri-file-image-line text-blue-600"></i> <i class="ri-file-image-line text-blue-600"></i>
<span class="page-indicator text-sm font-medium text-slate-600 bg-blue-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $lastPage.PageNumber }}">{{ $lastPage.PageNumber }}</span> <span class="page-indicator text-sm font-bold text-slate-600 bg-blue-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $lastPage.PageNumber }}">{{ $lastPage.PageNumber }}</span>
</div> </div>
</div> </div>
<div class="single-page bg-white p-4 rounded-lg border border-slate-200 hover:border-slate-300 transition-colors duration-200"> <div class="single-page bg-white p-4 rounded-lg border border-slate-200 hover:border-slate-300 transition-colors duration-200">
@@ -102,11 +102,11 @@
{{ if ge $pageCount 1 }} {{ if ge $pageCount 1 }}
{{ $firstPage := index $beilagePages 0 }} {{ $firstPage := index $beilagePages 0 }}
{{ if $firstPage.Available }} {{ if $firstPage.Available }}
<div class="newspaper-page-container"> <div class="newspaper-page-container" id="beilage-{{ $beilageNum }}-page-{{ $firstPage.PageNumber }}">
<div class="mb-3"> <div class="mb-3">
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="ri-file-image-line text-amber-600"></i> <i class="ri-file-image-line text-amber-600"></i>
<span class="text-sm font-medium text-slate-600 bg-amber-50 px-2 py-1 rounded">{{ $firstPage.PageNumber }}</span> <span class="page-indicator page-number-inhalts text-sm font-bold text-slate-600 bg-amber-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $firstPage.PageNumber }}" data-page-number="{{ $firstPage.PageNumber }}">{{ $firstPage.PageNumber }}</span>
</div> </div>
</div> </div>
<div class="single-page bg-white p-4 rounded-lg border border-amber-200 hover:border-amber-300 transition-colors duration-200"> <div class="single-page bg-white p-4 rounded-lg border border-amber-200 hover:border-amber-300 transition-colors duration-200">
@@ -126,18 +126,25 @@
{{ $middlePage1 := index $beilagePages 1 }} {{ $middlePage1 := index $beilagePages 1 }}
{{ $middlePage2 := index $beilagePages 2 }} {{ $middlePage2 := index $beilagePages 2 }}
{{ if and $middlePage1.Available $middlePage2.Available }} {{ if and $middlePage1.Available $middlePage2.Available }}
<div class="newspaper-page-container"> <div class="newspaper-page-container" id="beilage-{{ $beilageNum }}-page-{{ $middlePage1.PageNumber }}-{{ $middlePage2.PageNumber }}">
<h4 class="text-sm font-medium mb-2">Beilage {{ $beilageNum }}, Seiten {{ $middlePage1.PageNumber }}-{{ $middlePage2.PageNumber }}</h4> <div class="mb-3">
<div class="double-spread"> <div class="flex items-center gap-2 mb-2">
<i class="ri-file-copy-2-line text-amber-600 text-sm"></i>
<span class="page-indicator text-sm font-bold text-slate-600 bg-amber-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $middlePage1.PageNumber }}">{{ $middlePage1.PageNumber }}-{{ $middlePage2.PageNumber }}</span>
</div>
</div>
<div class="double-spread bg-white p-4 rounded-lg border border-amber-200 hover:border-amber-300 transition-colors duration-200">
<img src="{{ $middlePage1.ImagePath }}" <img src="{{ $middlePage1.ImagePath }}"
alt="Beilage {{ $beilageNum }}, Seite {{ $middlePage1.PageNumber }}" alt="Beilage {{ $beilageNum }}, Seite {{ $middlePage1.PageNumber }}"
class="newspaper-page-image cursor-pointer border shadow-md" class="newspaper-page-image cursor-pointer rounded-md hover:scale-[1.02] transition-transform duration-200"
onclick="enlargePage(this, {{ $middlePage1.PageNumber }}, true)" onclick="enlargePage(this, {{ $middlePage1.PageNumber }}, true)"
data-page="{{ $middlePage1.PageNumber }}"
loading="lazy"> loading="lazy">
<img src="{{ $middlePage2.ImagePath }}" <img src="{{ $middlePage2.ImagePath }}"
alt="Beilage {{ $beilageNum }}, Seite {{ $middlePage2.PageNumber }}" alt="Beilage {{ $beilageNum }}, Seite {{ $middlePage2.PageNumber }}"
class="newspaper-page-image cursor-pointer border shadow-md" class="newspaper-page-image cursor-pointer rounded-md hover:scale-[1.02] transition-transform duration-200"
onclick="enlargePage(this, {{ $middlePage2.PageNumber }}, true)" onclick="enlargePage(this, {{ $middlePage2.PageNumber }}, true)"
data-page="{{ $middlePage2.PageNumber }}"
loading="lazy"> loading="lazy">
</div> </div>
</div> </div>
@@ -146,13 +153,19 @@
<!-- If only 2 pages, show as spread --> <!-- If only 2 pages, show as spread -->
{{ $page2 := index $beilagePages 1 }} {{ $page2 := index $beilagePages 1 }}
{{ if $page2.Available }} {{ if $page2.Available }}
<div class="newspaper-page-container"> <div class="newspaper-page-container" id="beilage-{{ $beilageNum }}-page-{{ $page2.PageNumber }}">
<h4 class="text-sm font-medium mb-2">Beilage {{ $beilageNum }}, Seite {{ $page2.PageNumber }}</h4> <div class="mb-3">
<div class="single-page"> <div class="flex items-center gap-2 mb-2">
<i class="ri-file-image-line text-amber-600"></i>
<span class="page-indicator text-sm font-bold text-slate-600 bg-amber-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $page2.PageNumber }}">{{ $page2.PageNumber }}</span>
</div>
</div>
<div class="single-page bg-white p-4 rounded-lg border border-amber-200 hover:border-amber-300 transition-colors duration-200">
<img src="{{ $page2.ImagePath }}" <img src="{{ $page2.ImagePath }}"
alt="Beilage {{ $beilageNum }}, Seite {{ $page2.PageNumber }}" alt="Beilage {{ $beilageNum }}, Seite {{ $page2.PageNumber }}"
class="newspaper-page-image cursor-pointer border shadow-md" class="newspaper-page-image cursor-pointer rounded-md hover:scale-[1.02] transition-transform duration-200"
onclick="enlargePage(this, {{ $page2.PageNumber }}, false)" onclick="enlargePage(this, {{ $page2.PageNumber }}, false)"
data-page="{{ $page2.PageNumber }}"
loading="lazy"> loading="lazy">
</div> </div>
</div> </div>
@@ -163,13 +176,19 @@
{{ if ge $pageCount 4 }} {{ if ge $pageCount 4 }}
{{ $lastPage := index $beilagePages 3 }} {{ $lastPage := index $beilagePages 3 }}
{{ if $lastPage.Available }} {{ if $lastPage.Available }}
<div class="newspaper-page-container"> <div class="newspaper-page-container" id="beilage-{{ $beilageNum }}-page-{{ $lastPage.PageNumber }}">
<h4 class="text-sm font-medium mb-2">Beilage {{ $beilageNum }}, Seite {{ $lastPage.PageNumber }}</h4> <div class="mb-3">
<div class="single-page"> <div class="flex items-center gap-2 mb-2">
<i class="ri-file-image-line text-amber-600"></i>
<span class="page-indicator text-sm font-bold text-slate-600 bg-amber-50 px-2 py-1 rounded transition-all duration-300" data-page="{{ $lastPage.PageNumber }}">{{ $lastPage.PageNumber }}</span>
</div>
</div>
<div class="single-page bg-white p-4 rounded-lg border border-amber-200 hover:border-amber-300 transition-colors duration-200">
<img src="{{ $lastPage.ImagePath }}" <img src="{{ $lastPage.ImagePath }}"
alt="Beilage {{ $beilageNum }}, Seite {{ $lastPage.PageNumber }}" alt="Beilage {{ $beilageNum }}, Seite {{ $lastPage.PageNumber }}"
class="newspaper-page-image cursor-pointer border shadow-md" class="newspaper-page-image cursor-pointer rounded-md hover:scale-[1.02] transition-transform duration-200"
onclick="enlargePage(this, {{ $lastPage.PageNumber }}, false)" onclick="enlargePage(this, {{ $lastPage.PageNumber }}, false)"
data-page="{{ $lastPage.PageNumber }}"
loading="lazy"> loading="lazy">
</div> </div>
</div> </div>
@@ -262,23 +281,55 @@
max-width: 100%; max-width: 100%;
} }
} }
/* Simple button hover styles */
button#prevPageBtn:hover:not([style*="display: none"]),
button#nextPageBtn:hover,
button#beilageBtn:hover {
background-color: rgb(209 213 219) !important; /* gray-300 */
color: rgb(55 65 81) !important; /* gray-700 */
}
button#beilageBtn:hover {
background-color: rgb(254 215 170) !important; /* amber-200 */
color: rgb(146 64 14) !important; /* amber-800 */
}
</style> </style>
<script> <script>
// Initialize page tracking // Page highlighting state - use window to avoid redeclaration
document.addEventListener('DOMContentLoaded', function() { window.highlightObserver = window.highlightObserver || null;
// Initialize or reinitialize page highlighting - can be called multiple times
function initializePageHighlighting() {
// Clean up existing observer
if (window.highlightObserver) {
window.highlightObserver.disconnect();
window.highlightObserver = null;
}
// Get all page containers // Get all page containers
const pageContainers = document.querySelectorAll('.newspaper-page-container'); const pageContainers = document.querySelectorAll('.newspaper-page-container');
// Set up intersection observer for active page tracking // Set up intersection observer for active page tracking
const observer = new IntersectionObserver((entries) => { window.highlightObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => { entries.forEach((entry) => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
// Get page number from the container // Check if this is a double-spread container
const pageImg = entry.target.querySelector('img[data-page]'); const doubleSpread = entry.target.querySelector('.double-spread');
if (pageImg) { if (doubleSpread) {
const pageNumber = pageImg.getAttribute('data-page'); // Handle double-spread: highlight both pages
markCurrentPageInInhaltsverzeichnis(pageNumber); const pageImages = entry.target.querySelectorAll('img[data-page]');
const pageNumbers = Array.from(pageImages).map(img => img.getAttribute('data-page'));
markCurrentPagesInInhaltsverzeichnis(pageNumbers);
} else {
// Handle single page
const pageImg = entry.target.querySelector('img[data-page]');
if (pageImg) {
const pageNumber = pageImg.getAttribute('data-page');
markCurrentPageInInhaltsverzeichnis(pageNumber);
}
} }
} }
}); });
@@ -288,34 +339,126 @@ document.addEventListener('DOMContentLoaded', function() {
// Observe all page containers // Observe all page containers
pageContainers.forEach(container => { pageContainers.forEach(container => {
observer.observe(container); window.highlightObserver.observe(container);
}); });
}
// Initialize highlighting on page load
document.addEventListener('DOMContentLoaded', initializePageHighlighting);
// More comprehensive HTMX event handling for highlighting
document.body.addEventListener('htmx:afterSwap', function(event) {
console.log('HTMX afterSwap detected, target:', event.detail.target);
console.log('Target ID:', event.detail.target.id);
console.log('Target classes:', event.detail.target.className);
// Reinitialize on any content swap that might affect our page
setTimeout(() => {
console.log('Reinitializing page highlighting...');
initializePageHighlighting();
}, 100);
});
document.body.addEventListener('htmx:afterSettle', function(event) {
console.log('HTMX afterSettle detected');
setTimeout(() => {
initializePageHighlighting();
}, 200);
});
// Also try htmx:load event
document.body.addEventListener('htmx:load', function(event) {
console.log('HTMX load detected');
setTimeout(() => {
initializePageHighlighting();
}, 100);
}); });
function markCurrentPageInInhaltsverzeichnis(pageNumber) { function markCurrentPageInInhaltsverzeichnis(pageNumber) {
// Reset all entries in Inhaltsverzeichnis markCurrentPagesInInhaltsverzeichnis([pageNumber]);
document.querySelectorAll('.inhalts-entry').forEach(entry => { }
entry.classList.remove('bg-red-100', 'border-red-300');
entry.classList.add('bg-slate-50'); function markCurrentPagesInInhaltsverzeichnis(pageNumbers) {
// Reset all page numbers in Inhaltsverzeichnis
document.querySelectorAll('.page-number-inhalts').forEach(pageNum => {
pageNum.classList.remove('text-red-600', 'font-bold');
pageNum.classList.add('text-slate-700', 'font-semibold');
// Keep original background colors
if (!pageNum.classList.contains('bg-amber-50') && !pageNum.classList.contains('bg-blue-50')) {
pageNum.classList.add('bg-blue-50');
}
}); });
// Find and highlight the current page entry // Find and highlight the current page numbers
const pageEntry = document.querySelector(`.inhalts-entry[data-page="${pageNumber}"]`); const highlightedElements = [];
if (pageEntry) { const highlightedRanges = new Set(); // Track which ranges we've already highlighted
pageEntry.classList.remove('bg-slate-50');
pageEntry.classList.add('bg-red-100', 'border-red-300'); pageNumbers.forEach(pageNumber => {
// Look for all entries that should be highlighted for this page
const allPageNumbers = document.querySelectorAll('.page-number-inhalts');
for (const pageNumElement of allPageNumbers) {
const startPage = parseInt(pageNumElement.getAttribute('data-page-number'));
const endPage = parseInt(pageNumElement.getAttribute('data-end-page'));
const rangeKey = `${startPage}-${endPage}`;
// Check if this page falls within this range
if (pageNumber >= startPage && pageNumber <= endPage) {
// Only highlight this range once, even if multiple visible pages fall within it
if (!highlightedRanges.has(rangeKey)) {
pageNumElement.classList.remove('text-slate-700');
pageNumElement.classList.add('text-red-600', 'font-bold');
highlightedElements.push(pageNumElement);
highlightedRanges.add(rangeKey);
}
}
}
});
// Auto-scroll to first highlighted element if it exists
if (highlightedElements.length > 0) {
scrollToHighlightedPage(highlightedElements[0]);
} }
// Also highlight page indicators // Also highlight page indicators
document.querySelectorAll('.page-indicator').forEach(indicator => { document.querySelectorAll('.page-indicator').forEach(indicator => {
indicator.classList.remove('bg-red-500', 'text-white'); indicator.classList.remove('text-red-600', 'font-bold');
indicator.classList.add('bg-blue-50', 'text-slate-600'); indicator.classList.add('text-slate-600', 'font-semibold');
// Keep original backgrounds
if (!indicator.classList.contains('bg-amber-50')) {
indicator.classList.add('bg-blue-50');
}
}); });
const pageIndicator = document.querySelector(`.page-indicator[data-page="${pageNumber}"]`); // Highlight page indicators for all current pages
if (pageIndicator) { pageNumbers.forEach(pageNumber => {
pageIndicator.classList.remove('bg-blue-50', 'bg-green-50', 'text-slate-600'); const pageIndicator = document.querySelector(`.page-indicator[data-page="${pageNumber}"]`);
pageIndicator.classList.add('bg-red-500', 'text-white'); if (pageIndicator) {
pageIndicator.classList.remove('text-slate-600');
pageIndicator.classList.add('text-red-600', 'font-bold');
}
});
}
function scrollToHighlightedPage(element) {
// Check if the element is in a scrollable container
const inhaltsContainer = element.closest('.lg\\:overflow-y-auto');
if (inhaltsContainer) {
// Calculate position
const containerRect = inhaltsContainer.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
// Check if element is not fully visible
const isAboveContainer = elementRect.top < containerRect.top;
const isBelowContainer = elementRect.bottom > containerRect.bottom;
if (isAboveContainer || isBelowContainer) {
// Scroll to make element visible with some padding
element.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
} }
} }
@@ -343,4 +486,267 @@ document.addEventListener('keydown', function(e) {
closeModal(); closeModal();
} }
}); });
// Navigation functions and state management - use window to avoid redeclaration
window.currentPageContainers = window.currentPageContainers || [];
window.currentActiveIndex = window.currentActiveIndex || 0;
window.pageObserver = window.pageObserver || null;
// Initialize or reinitialize page tracking - can be called multiple times
function initializePageTracking() {
// Clean up existing observer
if (window.pageObserver) {
window.pageObserver.disconnect();
window.pageObserver = null;
}
// Reset state
window.currentPageContainers = Array.from(document.querySelectorAll('.newspaper-page-container'));
window.currentActiveIndex = 0;
updateButtonStates();
// Set up new observer
const existingObserver = document.querySelector('.newspaper-page-container');
if (existingObserver) {
let visibleContainers = new Set();
window.pageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const containerIndex = window.currentPageContainers.indexOf(entry.target);
if (containerIndex !== -1) {
if (entry.isIntersecting) {
visibleContainers.add(containerIndex);
} else {
visibleContainers.delete(containerIndex);
}
}
});
// Update currentActiveIndex to the first (topmost) visible container
if (visibleContainers.size > 0) {
const sortedVisible = Array.from(visibleContainers).sort((a, b) => a - b);
const newActiveIndex = sortedVisible[0];
if (newActiveIndex !== window.currentActiveIndex) {
window.currentActiveIndex = newActiveIndex;
updateButtonStates();
}
}
}, {
rootMargin: '-20% 0px -70% 0px'
});
window.currentPageContainers.forEach(container => {
window.pageObserver.observe(container);
});
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', initializePageTracking);
// More comprehensive HTMX event handling for navigation tracking
document.body.addEventListener('htmx:afterSwap', function(event) {
console.log('HTMX afterSwap for navigation tracking');
setTimeout(() => {
console.log('Reinitializing page tracking...');
initializePageTracking();
}, 100);
});
document.body.addEventListener('htmx:afterSettle', function(event) {
console.log('HTMX afterSettle for navigation tracking');
setTimeout(() => {
initializePageTracking();
}, 200);
});
document.body.addEventListener('htmx:load', function(event) {
console.log('HTMX load for navigation tracking');
setTimeout(() => {
initializePageTracking();
}, 100);
});
function scrollToPreviousPage() {
if (window.currentActiveIndex > 0) {
// Move to previous container that's not currently visible
let targetIndex = window.currentActiveIndex - 1;
// Ensure we go to the previous non-visible container
window.currentActiveIndex = targetIndex;
window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Update button states after a brief delay to let intersection observer catch up
setTimeout(() => {
updateButtonStates();
}, 100);
}
}
function scrollToNextPage() {
if (window.currentActiveIndex < window.currentPageContainers.length - 1) {
// Move to next container that's not currently visible
let targetIndex = window.currentActiveIndex + 1;
// Ensure we go to the next non-visible container
window.currentActiveIndex = targetIndex;
window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
behavior: 'smooth',
block: 'start'
});
// Update button states after a brief delay to let intersection observer catch up
setTimeout(() => {
updateButtonStates();
}, 100);
}
}
function scrollToBeilage() {
// Find the first beilage container
const beilageContainer = document.querySelector('[class*="border-t-2 border-amber-200"]');
if (beilageContainer) {
beilageContainer.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
function updateButtonStates() {
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
if (prevBtn) {
if (window.currentActiveIndex <= 0) {
prevBtn.style.display = 'none';
} else {
prevBtn.style.display = 'flex';
}
}
if (nextBtn) {
if (window.currentActiveIndex >= window.currentPageContainers.length - 1) {
nextBtn.style.display = 'none';
} else {
nextBtn.style.display = 'flex';
}
}
}
function shareCurrentPage() {
const button = document.getElementById('shareLinkBtn');
// Get current page information
let pageInfo = '';
// Try to get the currently visible page number from active containers
if (window.currentActiveIndex !== undefined && window.currentPageContainers && window.currentPageContainers[window.currentActiveIndex]) {
const activeContainer = window.currentPageContainers[window.currentActiveIndex];
const pageElement = activeContainer.querySelector('[data-page]');
if (pageElement) {
const pageNumber = pageElement.getAttribute('data-page');
pageInfo = `#page-${pageNumber}`;
}
}
// Construct the shareable URL
const currentUrl = window.location.origin + window.location.pathname + pageInfo;
// Try to use Web Share API if available (mobile browsers)
if (navigator.share) {
navigator.share({
title: document.title,
url: currentUrl
}).catch(err => {
console.log('Error sharing:', err);
// Fallback to clipboard
copyToClipboard(currentUrl, button);
});
} else {
// Fallback: copy to clipboard
copyToClipboard(currentUrl, button);
}
}
function copyToClipboard(text, button) {
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(() => {
// Show temporary notification
showNotification('Link kopiert!', 'success', button);
}).catch(err => {
console.error('Failed to copy:', err);
showNotification('Kopieren fehlgeschlagen', 'error', button);
});
} else {
// Fallback for older browsers
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showNotification('Link kopiert!', 'success', button);
} catch (err) {
console.error('Fallback copy failed:', err);
showNotification('Kopieren fehlgeschlagen', 'error', button);
}
document.body.removeChild(textarea);
}
}
function generateCitation() {
const button = document.getElementById('citationBtn');
// Get current page and issue information
const issueInfo = document.title || 'KGPZ';
const currentUrl = window.location.href;
// Basic citation format (can be expanded later)
const currentDate = new Date().toLocaleDateString('de-DE');
const citation = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${issueInfo}. Digital verfügbar unter: ${currentUrl} (Zugriff: ${currentDate}).`;
// Copy citation to clipboard
copyToClipboard(citation, button);
}
function showNotification(message, type = 'success', button) {
// Remove any existing notifications
const existingNotification = document.getElementById('notification');
if (existingNotification) {
existingNotification.remove();
}
// Create notification element
const notification = document.createElement('div');
notification.id = 'notification';
notification.className = `fixed px-3 py-2 rounded-md text-white text-sm font-medium z-50 transition-opacity duration-300 ${
type === 'success' ? 'bg-green-500' : 'bg-red-500'
}`;
notification.textContent = message;
// Position notification next to button if button is provided
if (button) {
const buttonRect = button.getBoundingClientRect();
notification.style.left = `${buttonRect.left - 80}px`; // Position to the left of button
notification.style.top = `${buttonRect.top + buttonRect.height / 2 - 20}px`; // Center vertically with button
} else {
// Fallback to top-right corner
notification.className += ' top-4 right-4';
}
// Add to page
document.body.appendChild(notification);
// Auto-remove after 3 seconds
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
</script> </script>

View File

@@ -67,7 +67,7 @@
<div class="flex flex-wrap gap-1 mt-1"> <div class="flex flex-wrap gap-1 mt-1">
{{ range $_, $p := $workPieces }} {{ range $_, $p := $workPieces }}
{{ range $_, $issue := $p.Item.IssueRefs }} {{ range $_, $issue := $p.Item.IssueRefs }}
<a href="/{{ $issue.When }}/{{ $issue.Nr }}" class="inline-block bg-green-50 hover:bg-green-100 text-green-700 hover:text-green-800 px-2 py-1 rounded text-sm transition-colors"> <a href="/{{ $issue.When }}/{{ $issue.Nr }}{{ if $issue.Von }}{{ if $issue.Beilage }}#beilage-{{ $issue.Beilage }}-page-{{ $issue.Von }}{{ else }}#page-{{ $issue.Von }}{{ end }}{{ end }}" class="inline-block bg-green-50 hover:bg-green-100 text-green-700 hover:text-green-800 px-2 py-1 rounded text-sm transition-colors">
{{ $issue.Nr }}/{{ $issue.When }}{{ if $issue.Von }} [S. {{ $issue.Von }}{{ if $issue.Bis }}-{{ $issue.Bis }}{{ end }}]{{ end }} {{ $issue.Nr }}/{{ $issue.When }}{{ if $issue.Von }} [S. {{ $issue.Von }}{{ if $issue.Bis }}-{{ $issue.Bis }}{{ end }}]{{ end }}
</a> </a>
{{ end }} {{ end }}
@@ -87,7 +87,7 @@
<div class="flex flex-wrap gap-1 mt-1"> <div class="flex flex-wrap gap-1 mt-1">
{{ range $_, $p := $pieces }} {{ range $_, $p := $pieces }}
{{ range $_, $issue := $p.Item.IssueRefs }} {{ range $_, $issue := $p.Item.IssueRefs }}
<a href="/{{ $issue.When }}/{{ $issue.Nr }}" class="inline-block bg-green-50 hover:bg-green-100 text-green-700 hover:text-green-800 px-2 py-1 rounded text-sm transition-colors"> <a href="/{{ $issue.When }}/{{ $issue.Nr }}{{ if $issue.Von }}{{ if $issue.Beilage }}#beilage-{{ $issue.Beilage }}-page-{{ $issue.Von }}{{ else }}#page-{{ $issue.Von }}{{ end }}{{ end }}" class="inline-block bg-green-50 hover:bg-green-100 text-green-700 hover:text-green-800 px-2 py-1 rounded text-sm transition-colors">
{{ $issue.Nr }}/{{ $issue.When }}{{ if $issue.Von }} [S. {{ $issue.Von }}{{ if $issue.Bis }}-{{ $issue.Bis }}{{ end }}]{{ end }} {{ $issue.Nr }}/{{ $issue.When }}{{ if $issue.Von }} [S. {{ $issue.Von }}{{ if $issue.Bis }}-{{ $issue.Bis }}{{ end }}]{{ end }}
</a> </a>
{{ end }} {{ end }}

View File

@@ -105,7 +105,7 @@
{{ range $_, $p := $pieces }} {{ range $_, $p := $pieces }}
{{- range $_, $i := $p.Item.IssueRefs -}} {{- range $_, $i := $p.Item.IssueRefs -}}
<div> <div>
<a href="/{{ $i.When }}/{{ $i.Nr }}">{{ $i.Nr }}/{{ $i.When }}</a> <a href="/{{ $i.When }}/{{ $i.Nr }}{{ if $i.Von }}{{ if $i.Beilage }}#beilage-{{ $i.Beilage }}-page-{{ $i.Von }}{{ else }}#page-{{ $i.Von }}{{ end }}{{ end }}">{{ $i.Nr }}/{{ $i.When }}</a>
</div> </div>
{{- end -}} {{- end -}}
{{ end }} {{ end }}