import "./site.css"; const ATTR_XSLT_ONLOAD = "script[xslt-onload]"; const ATTR_XSLT_TEMPLATE = "xslt-template"; const ATTR_XSLT_STATE = "xslt-transformed"; const xslt_processors = new Map(); function setup_xslt() { let els = htmx.findAll(ATTR_XSLT_ONLOAD); for (let element of els) { transform_xslt(element); } } function transform_xslt(element) { if ( element.getAttribute(ATTR_XSLT_STATE) === "true" || !element.hasAttribute(ATTR_XSLT_TEMPLATE) ) { return; } let templateId = "#" + element.getAttribute(ATTR_XSLT_TEMPLATE); let processor = xslt_processors.get(templateId); if (!processor) { let template = htmx.find(templateId); if (template) { let content = template.innerHTML ? new DOMParser().parseFromString(template.innerHTML, "application/xml") : template.contentDocument; processor = new XSLTProcessor(); processor.importStylesheet(content); xslt_processors.set(templateId, processor); } else { throw new Error("Unknown XSLT template: " + templateId); } } let data = new DOMParser().parseFromString(element.innerHTML, "application/xml"); let frag = processor.transformToFragment(data, document); let s = new XMLSerializer().serializeToString(frag); element.outerHTML = s; } function setup_templates() { let templates = document.querySelectorAll("template[simple]"); templates.forEach((template) => { let templateId = template.getAttribute("id"); let templateContent = template.content; customElements.define( templateId, class extends HTMLElement { constructor() { super(); this.appendChild(templateContent.cloneNode(true)); this.slots = this.querySelectorAll("slot"); } connectedCallback() { let toremove = []; this.slots.forEach((tslot) => { let slotName = tslot.getAttribute("name"); let slotContent = this.querySelector(`[slot="${slotName}"]`); if (slotContent) { tslot.replaceWith(slotContent.cloneNode(true)); toremove.push(slotContent); } }); toremove.forEach((element) => { element.remove(); }); } }, ); }); } // =========================== // NEWSPAPER LAYOUT FUNCTIONS // =========================== // Global variables for state management window.highlightObserver = window.highlightObserver || null; window.currentPageContainers = window.currentPageContainers || []; window.currentActiveIndex = window.currentActiveIndex || 0; window.pageObserver = window.pageObserver || null; window.scrollTimeout = window.scrollTimeout || null; // Page highlighting functionality function initializePageHighlighting() { // Clean up existing observer if (window.highlightObserver) { window.highlightObserver.disconnect(); window.highlightObserver = null; } // Get all page containers const pageContainers = document.querySelectorAll(".newspaper-page-container"); // Set up intersection observer for active page tracking window.highlightObserver = new IntersectionObserver( (entries) => { checkAndHighlightVisiblePages(); }, { rootMargin: "-20% 0px -70% 0px", }, ); // Observe all page containers pageContainers.forEach((container) => { window.highlightObserver.observe(container); }); } function checkAndHighlightVisiblePages() { const visiblePageNumbers = []; const allPageContainers = document.querySelectorAll(".newspaper-page-container"); // Find visible page numbers allPageContainers.forEach((container) => { const containerRect = container.getBoundingClientRect(); const viewportHeight = window.innerHeight; const visibleTop = Math.max(containerRect.top, 0); const visibleBottom = Math.min(containerRect.bottom, viewportHeight); const visibleHeight = Math.max(0, visibleBottom - visibleTop); const containerHeight = containerRect.height; const visibilityRatio = visibleHeight / containerHeight; const isVisible = visibilityRatio >= 0.5; const pageImg = container.querySelector("img[data-page]"); const pageNumber = pageImg ? pageImg.getAttribute("data-page") : "unknown"; if (isVisible && pageImg && pageNumber && !visiblePageNumbers.includes(pageNumber)) { visiblePageNumbers.push(pageNumber); } }); // Show continuations only for visible pages showContinuationsForVisiblePages(visiblePageNumbers); // Highlight visible pages if (visiblePageNumbers.length > 0) { markCurrentPagesInInhaltsverzeichnis(visiblePageNumbers); } } function showContinuationsForVisiblePages(visiblePageNumbers) { // Hide all continuation entries by default document.querySelectorAll(".continuation-entry").forEach((entry) => { entry.style.display = "none"; }); // Show continuation entries only for visible pages visiblePageNumbers.forEach((pageNumber) => { const pageEntry = document.querySelector(`[data-page-container="${pageNumber}"]`); if (pageEntry) { const continuationEntries = pageEntry.querySelectorAll(".continuation-entry"); continuationEntries.forEach((entry) => { entry.style.display = ""; }); } }); // Update work titles based on highlighted state updateWorkTitles(visiblePageNumbers); // Update page entry visibility after showing/hiding continuations updatePageEntryVisibility(); } function updateWorkTitles(visiblePageNumbers) { // Reset all work titles to short form document.querySelectorAll(".work-title").forEach((titleElement) => { const shortTitle = titleElement.getAttribute("data-short-title"); if (shortTitle) { titleElement.textContent = shortTitle; } }); // Update work titles to full form for highlighted pages visiblePageNumbers.forEach((pageNumber) => { const pageEntry = document.querySelector(`[data-page-container="${pageNumber}"]`); if (pageEntry) { const workTitles = pageEntry.querySelectorAll(".work-title"); workTitles.forEach((titleElement) => { const fullTitle = titleElement.getAttribute("data-full-title"); if (fullTitle && fullTitle !== titleElement.getAttribute("data-short-title")) { titleElement.textContent = fullTitle; } }); } }); } function updatePageEntryVisibility() { // Check each page entry to see if it has any visible content document.querySelectorAll(".page-entry").forEach((pageEntry) => { const allEntries = pageEntry.querySelectorAll(".inhalts-entry"); let hasVisibleContent = false; // Check if any entry is visible allEntries.forEach((entry) => { const computedStyle = window.getComputedStyle(entry); if (computedStyle.display !== "none") { hasVisibleContent = true; } }); // Hide the entire page entry if it has no visible content if (hasVisibleContent) { pageEntry.style.display = ""; } else { pageEntry.style.display = "none"; } }); } function markCurrentPageInInhaltsverzeichnis(pageNumber) { markCurrentPagesInInhaltsverzeichnis([pageNumber]); } function markCurrentPagesInInhaltsverzeichnis(pageNumbers) { console.log("markCurrentPagesInInhaltsverzeichnis called with:", pageNumbers); // Reset all page container borders to default document.querySelectorAll("[data-page-container]").forEach((container) => { if (container.hasAttribute("data-beilage")) { container.classList.remove("border-red-500"); container.classList.add("border-amber-400"); } else { container.classList.remove("border-red-500"); container.classList.add("border-slate-300"); } }); // 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"); pageNum.style.textDecoration = ""; pageNum.style.pointerEvents = ""; // Restore hover effects if (pageNum.classList.contains("bg-blue-50")) { pageNum.classList.add("hover:bg-blue-100"); } else if (pageNum.classList.contains("bg-amber-50")) { pageNum.classList.add("hover:bg-amber-100"); } // Keep original background colors if (!pageNum.classList.contains("bg-amber-50") && !pageNum.classList.contains("bg-blue-50")) { pageNum.classList.add("bg-blue-50"); } }); // Reset all containers and links in Inhaltsverzeichnis document.querySelectorAll(".inhalts-entry").forEach((container) => { container.classList.add("hover:bg-slate-100"); container.style.cursor = ""; }); document.querySelectorAll('.inhalts-entry a[href*="/"]').forEach((link) => { link.classList.remove("no-underline"); if (link.classList.contains("bg-blue-50")) { link.classList.add("hover:bg-blue-100"); } }); // Find and highlight the current page numbers const highlightedElements = []; pageNumbers.forEach((pageNumber) => { // Find the exact page entry for this page number const pageNumElement = document.querySelector( `.page-number-inhalts[data-page-number="${pageNumber}"]`, ); if (pageNumElement) { // Highlight the page number pageNumElement.classList.remove("text-slate-700", "hover:bg-blue-100", "hover:bg-amber-100"); pageNumElement.classList.add("text-red-600", "font-bold"); pageNumElement.style.textDecoration = "none"; pageNumElement.style.pointerEvents = "none"; highlightedElements.push(pageNumElement); // Highlight the page container's left border const pageContainer = document.querySelector(`[data-page-container="${pageNumber}"]`); if (pageContainer) { pageContainer.classList.remove("border-slate-300", "border-amber-400"); pageContainer.classList.add("border-red-500"); } // Make links in the current page non-clickable and remove hover effects const parentEntry = pageNumElement.closest(".page-entry"); if (parentEntry) { // Remove hover effects from the container const entryContainers = parentEntry.querySelectorAll(".inhalts-entry"); entryContainers.forEach((container) => { container.classList.remove("hover:bg-slate-100"); container.style.cursor = "default"; }); // Also handle issue reference links parentEntry.querySelectorAll('a[href*="/"]').forEach((link) => { if (link.getAttribute("aria-current") === "page") { link.style.textDecoration = "none"; link.style.pointerEvents = "none"; link.classList.add("no-underline"); link.classList.remove("hover:bg-blue-100"); } }); } } }); // Auto-scroll to first highlighted element if it exists if (highlightedElements.length > 0) { scrollToHighlightedPage(highlightedElements[0]); } // Also highlight page indicators document.querySelectorAll(".page-indicator").forEach((indicator) => { indicator.classList.remove("text-red-600", "font-bold"); indicator.classList.add("text-slate-600", "font-semibold"); // Keep original backgrounds if (!indicator.classList.contains("bg-amber-50")) { indicator.classList.add("bg-blue-50"); } }); // Highlight page indicators for all current pages pageNumbers.forEach((pageNumber) => { const pageIndicator = document.querySelector(`.page-indicator[data-page="${pageNumber}"]`); 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", }); } } } // Modal functions function enlargePage(imgElement, pageNumber, isFromSpread, partNumber = null) { // Get or create the single page viewer component let viewer = document.querySelector("single-page-viewer"); if (!viewer) { viewer = document.createElement("single-page-viewer"); document.body.appendChild(viewer); } // Determine if this is a beilage page const isBeilage = imgElement.closest('[data-beilage="true"]') !== null; // Get target page from template data if available const targetPage = window.templateData && window.templateData.targetPage ? window.templateData.targetPage : 0; // Show the page in the viewer viewer.show(imgElement.src, imgElement.alt, pageNumber, isBeilage, targetPage, partNumber); } function closeModal() { const modal = document.getElementById("pageModal"); modal.classList.add("hidden"); } // Page navigation functions 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); }); } } function scrollToPreviousPage() { if (window.currentActiveIndex > 0) { // Find the first page that's not currently visible let targetIndex = -1; // Check which pages are currently visible const visibleIndexes = []; window.currentPageContainers.forEach((container, index) => { const containerRect = container.getBoundingClientRect(); const viewportHeight = window.innerHeight; const visibleTop = Math.max(containerRect.top, 0); const visibleBottom = Math.min(containerRect.bottom, viewportHeight); const visibleHeight = Math.max(0, visibleBottom - visibleTop); const containerHeight = containerRect.height; const visibilityRatio = visibleHeight / containerHeight; if (visibilityRatio >= 0.3) { // Consider visible if 30% or more is showing visibleIndexes.push(index); } }); // Find the first non-visible page before the current visible range const minVisibleIndex = Math.min(...visibleIndexes); for (let i = minVisibleIndex - 1; i >= 0; i--) { if (!visibleIndexes.includes(i)) { targetIndex = i; break; } } // If no non-visible page found, go to the page just before the visible range if (targetIndex === -1 && minVisibleIndex > 0) { targetIndex = minVisibleIndex - 1; } if (targetIndex >= 0) { 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) { // Find the first page that's not currently visible let targetIndex = -1; // Check which pages are currently visible const visibleIndexes = []; window.currentPageContainers.forEach((container, index) => { const containerRect = container.getBoundingClientRect(); const viewportHeight = window.innerHeight; const visibleTop = Math.max(containerRect.top, 0); const visibleBottom = Math.min(containerRect.bottom, viewportHeight); const visibleHeight = Math.max(0, visibleBottom - visibleTop); const containerHeight = containerRect.height; const visibilityRatio = visibleHeight / containerHeight; if (visibilityRatio >= 0.3) { // Consider visible if 30% or more is showing visibleIndexes.push(index); } }); // Find the first non-visible page after the current visible range const maxVisibleIndex = Math.max(...visibleIndexes); for (let i = maxVisibleIndex + 1; i < window.currentPageContainers.length; i++) { if (!visibleIndexes.includes(i)) { targetIndex = i; break; } } // If no non-visible page found, go to the page just after the visible range if (targetIndex === -1 && maxVisibleIndex < window.currentPageContainers.length - 1) { targetIndex = maxVisibleIndex + 1; } if (targetIndex >= 0 && targetIndex < window.currentPageContainers.length) { 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() { // Check if we're currently viewing a Beilage section const isViewingBeilage = isCurrentlyInBeilageSection(); if (isViewingBeilage) { // Go back to main issue (first main page) const firstMainPage = document.querySelector("#newspaper-content .newspaper-page-container"); if (firstMainPage) { firstMainPage.scrollIntoView({ behavior: "smooth", block: "start", }); } } else { // Go to first beilage container const beilageContainer = document.querySelector('[class*="border-t-2 border-amber-200"]'); if (beilageContainer) { beilageContainer.scrollIntoView({ behavior: "smooth", block: "start", }); } } } function isCurrentlyInBeilageSection() { // Check which pages are currently visible const visibleIndexes = []; window.currentPageContainers.forEach((container, index) => { const containerRect = container.getBoundingClientRect(); const viewportHeight = window.innerHeight; const visibleTop = Math.max(containerRect.top, 0); const visibleBottom = Math.min(containerRect.bottom, viewportHeight); const visibleHeight = Math.max(0, visibleBottom - visibleTop); const containerHeight = containerRect.height; const visibilityRatio = visibleHeight / containerHeight; if (visibilityRatio >= 0.3) { // Consider visible if 30% or more is showing visibleIndexes.push(index); } }); // Check if any visible page is a Beilage page for (const index of visibleIndexes) { const container = window.currentPageContainers[index]; if (container && container.id && container.id.includes("beilage-")) { return true; } } return false; } function updateButtonStates() { const prevBtn = document.getElementById("prevPageBtn"); const nextBtn = document.getElementById("nextPageBtn"); const beilageBtn = document.getElementById("beilageBtn"); 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"; } } // Update Beilage button based on current location if (beilageBtn) { const isViewingBeilage = isCurrentlyInBeilageSection(); const icon = beilageBtn.querySelector("i"); if (isViewingBeilage) { // Show "Go to Main Issue" state - use gray styling beilageBtn.title = "Zur Hauptausgabe"; beilageBtn.className = "w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-700 hover:text-gray-800 border border-gray-300 transition-colors duration-200 flex items-center justify-center cursor-pointer"; if (icon) { icon.className = "ri-file-text-line text-lg lg:text-xl"; } } else { // Show "Go to Beilage" state - use amber styling beilageBtn.title = "Zu Beilage"; beilageBtn.className = "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"; if (icon) { icon.className = "ri-attachment-line text-lg lg:text-xl"; } } } } // Share and citation functions 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 = `/${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) => { // 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(() => { showSimplePopup(button, "Link kopiert!"); }) .catch((err) => { showSimplePopup(button, "Kopieren fehlgeschlagen"); }); } else { // Fallback for older browsers const textarea = document.createElement("textarea"); textarea.value = text; document.body.appendChild(textarea); textarea.select(); try { const successful = document.execCommand("copy"); showSimplePopup(button, successful ? "Link kopiert!" : "Kopieren fehlgeschlagen"); } catch (err) { showSimplePopup(button, "Kopieren fehlgeschlagen"); } finally { document.body.removeChild(textarea); } } } function generateCitation() { const button = document.getElementById("citationBtn"); // Get current page and issue information const issueInfo = document.title || "KGPZ"; // Use clean URL without hash fragments let cleanUrl = window.location.origin + window.location.pathname; // Remove any hash fragments that might still exist if (cleanUrl.includes("#")) { cleanUrl = cleanUrl.split("#")[0]; } // 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: ${cleanUrl} (Zugriff: ${currentDate}).`; // Copy to clipboard if (navigator.clipboard) { navigator.clipboard .writeText(citation) .then(() => { showSimplePopup(button, "Zitation kopiert!"); }) .catch((err) => { showSimplePopup(button, "Kopieren fehlgeschlagen"); }); } else { // Fallback for older browsers const textarea = document.createElement("textarea"); textarea.value = citation; document.body.appendChild(textarea); textarea.select(); try { const successful = document.execCommand("copy"); showSimplePopup(button, successful ? "Zitation kopiert!" : "Kopieren fehlgeschlagen"); } catch (err) { showSimplePopup(button, "Kopieren fehlgeschlagen"); } finally { document.body.removeChild(textarea); } } } function showSimplePopup(button, message) { // Remove any existing popup const existingPopup = document.querySelector(".simple-popup"); if (existingPopup) { existingPopup.remove(); } // Create popup element const popup = document.createElement("div"); popup.className = "simple-popup"; popup.textContent = message; // Style the popup popup.style.cssText = ` position: fixed; background: #374151; color: white; padding: 6px 12px; border-radius: 6px; font-size: 13px; font-weight: 500; z-index: 1000; pointer-events: none; opacity: 0; transition: opacity 0.2s ease; white-space: nowrap; `; // Position popup next to button const buttonRect = button.getBoundingClientRect(); const viewportHeight = window.innerHeight; const viewportWidth = window.innerWidth; // Calculate position relative to viewport (since we're using fixed positioning) let left = buttonRect.left - 10; let top = buttonRect.bottom + 8; // Ensure popup doesn't go off-screen const popupWidth = 120; // Estimated popup width const popupHeight = 32; // Estimated popup height // Adjust horizontal position if too far right if (left + popupWidth > viewportWidth) { left = buttonRect.right - popupWidth + 10; } // Adjust vertical position if too far down (show above button instead) if (top + popupHeight > viewportHeight) { top = buttonRect.top - popupHeight - 8; } popup.style.left = Math.max(5, left) + "px"; popup.style.top = Math.max(5, top) + "px"; // Add to page document.body.appendChild(popup); // Fade in setTimeout(() => { popup.style.opacity = "1"; }, 10); // Auto-remove after 2 seconds setTimeout(() => { popup.style.opacity = "0"; setTimeout(() => { if (popup.parentNode) { popup.remove(); } }, 200); }, 2000); } // URL navigation functions function scrollToPageFromURL() { let pageNumber = ""; let targetContainer = null; // Check if URL ends with a page number (e.g., /1768/42/166) const pathParts = window.location.pathname.split("/"); if (pathParts.length >= 4 && !isNaN(pathParts[pathParts.length - 1])) { pageNumber = pathParts[pathParts.length - 1]; // Try to find exact page container first targetContainer = document.getElementById(`page-${pageNumber}`); // If not found, try to find container that contains this page if (!targetContainer) { // Look for double-spread containers that contain this page const containers = document.querySelectorAll(".newspaper-page-container[data-pages]"); for (const container of containers) { const pages = container.getAttribute("data-pages"); if (pages && pages.split(",").includes(pageNumber)) { targetContainer = container; break; } } } // If still not found, try beilage containers if (!targetContainer) { targetContainer = document.getElementById(`beilage-1-page-${pageNumber}`) || document.getElementById(`beilage-2-page-${pageNumber}`) || document.querySelector(`[id*="beilage"][id*="page-${pageNumber}"]`); } } if (targetContainer && pageNumber) { setTimeout(() => { targetContainer.scrollIntoView({ behavior: "smooth", block: "start", }); // Highlight the specific page in the table of contents markCurrentPageInInhaltsverzeichnis(pageNumber); }, 300); } } // Page-specific utilities function copyPagePermalink(pageNumber, button, isBeilage = false) { let pageUrl = ""; if (isBeilage) { // For beilage pages, still use hash for now until beilage URLs are updated pageUrl = window.location.origin + window.location.pathname + `#beilage-1-page-${pageNumber}`; } else { // For regular pages, use the new URL format const pathParts = window.location.pathname.split("/"); if (pathParts.length >= 3) { // Current URL format: /year/issue or /year/issue/page // New format: /year/issue/page const year = pathParts[1]; const issue = pathParts[2]; pageUrl = `${window.location.origin}/${year}/${issue}/${pageNumber}`; } else { // Fallback to current URL with page appended pageUrl = window.location.origin + window.location.pathname + `/${pageNumber}`; } } const currentUrl = pageUrl; // Copy to clipboard if (navigator.clipboard) { navigator.clipboard .writeText(currentUrl) .then(() => { showSimplePopup(button, "Link kopiert!"); }) .catch((err) => { showSimplePopup(button, "Kopieren fehlgeschlagen"); }); } else { // Fallback for older browsers const textarea = document.createElement("textarea"); textarea.value = currentUrl; document.body.appendChild(textarea); textarea.select(); try { const successful = document.execCommand("copy"); showSimplePopup(button, successful ? "Link kopiert!" : "Kopieren fehlgeschlagen"); } catch (err) { showSimplePopup(button, "Kopieren fehlgeschlagen"); } finally { document.body.removeChild(textarea); } } } function generatePageCitation(pageNumber, button) { // Get current issue information const issueInfo = document.title || "KGPZ"; // Generate page URL in new format const pathParts = window.location.pathname.split("/"); let pageUrl; if (pathParts.length >= 3) { const year = pathParts[1]; const issue = pathParts[2]; pageUrl = `${window.location.origin}/${year}/${issue}/${pageNumber}`; } else { pageUrl = `${window.location.origin}${window.location.pathname}/${pageNumber}`; } const currentUrl = pageUrl; // Basic citation format for specific page const currentDate = new Date().toLocaleDateString("de-DE"); const citation = `Königsberger Gelehrte und Politische Zeitung (KGPZ). ${issueInfo}, Seite ${pageNumber}. Digital verfügbar unter: ${currentUrl} (Zugriff: ${currentDate}).`; // Copy to clipboard if (navigator.clipboard) { navigator.clipboard .writeText(citation) .then(() => { showSimplePopup(button, "Zitation kopiert!"); }) .catch((err) => { showSimplePopup(button, "Kopieren fehlgeschlagen"); }); } else { // Fallback for older browsers const textarea = document.createElement("textarea"); textarea.value = citation; document.body.appendChild(textarea); textarea.select(); try { const successful = document.execCommand("copy"); showSimplePopup(button, successful ? "Zitation kopiert!" : "Kopieren fehlgeschlagen"); } catch (err) { showSimplePopup(button, "Kopieren fehlgeschlagen"); } finally { document.body.removeChild(textarea); } } } // Initialize scrollspy functionality for author/agent pages function initializeScrollspy() { // Clean up any existing scrollspy cleanupScrollspy(); const sections = document.querySelectorAll(".author-section"); const navLinks = document.querySelectorAll(".scrollspy-link"); if (sections.length === 0 || navLinks.length === 0) { return; } function updateActiveLink() { const visibleSections = []; const viewportTop = window.scrollY; const viewportBottom = viewportTop + window.innerHeight; // Check which sections have any part of their Werke or Beiträge visible sections.forEach((section) => { const sectionId = section.getAttribute("id"); // Find Werke and Beiträge sections within this author section const werkeSection = section.querySelector(".akteur-werke-section"); const beitraegeSection = section.querySelector(".akteur-beitraege-section"); let isVisible = false; // Check if any part of Werke section is visible if (werkeSection) { const werkeRect = werkeSection.getBoundingClientRect(); const werkeTopVisible = werkeRect.top < window.innerHeight; const werkeBottomVisible = werkeRect.bottom > 0; if (werkeTopVisible && werkeBottomVisible) { isVisible = true; } } // Check if any part of Beiträge section is visible if (beitraegeSection && !isVisible) { const beitraegeRect = beitraegeSection.getBoundingClientRect(); const beitraegeTopVisible = beitraegeRect.top < window.innerHeight; const beitraegeBottomVisible = beitraegeRect.bottom > 0; if (beitraegeTopVisible && beitraegeBottomVisible) { isVisible = true; } } // Fallback: if no Werke/Beiträge sections, check header visibility (for authors without content) if (!werkeSection && !beitraegeSection) { const headerElement = section.querySelector("div:first-child"); if (headerElement) { const headerRect = headerElement.getBoundingClientRect(); const headerTopVisible = headerRect.top >= 0; const headerBottomVisible = headerRect.bottom <= window.innerHeight; if (headerTopVisible && headerBottomVisible) { isVisible = true; } } } if (isVisible) { visibleSections.push(sectionId); } }); // Update highlighting with sliding background animation const activeLinks = []; const slider = document.getElementById("scrollspy-slider"); // Reset all links to default state (no background, just font weight for active) navLinks.forEach((link) => { link.classList.remove("font-medium"); const targetId = link.getAttribute("data-target"); if (visibleSections.includes(targetId)) { link.classList.add("font-medium"); activeLinks.push(link); } }); // Calculate and animate the sliding background if (activeLinks.length > 0 && slider) { // Get the nav container for relative positioning const nav = document.getElementById("scrollspy-nav"); const navRect = nav.getBoundingClientRect(); // Calculate the combined area of all active links let minTop = Infinity; let maxBottom = -Infinity; activeLinks.forEach((link) => { const linkRect = link.getBoundingClientRect(); const relativeTop = linkRect.top - navRect.top + nav.scrollTop; const relativeBottom = relativeTop + linkRect.height; minTop = Math.min(minTop, relativeTop); maxBottom = Math.max(maxBottom, relativeBottom); }); // Handle gaps between non-consecutive active links let totalHeight = maxBottom - minTop; // Position and size the slider slider.style.top = `${minTop}px`; slider.style.height = `${totalHeight}px`; slider.style.opacity = '1'; } else if (slider) { // Hide slider when no active links slider.style.opacity = '0'; } // Implement proportional scrolling to keep active links visible if (activeLinks.length > 0) { updateSidebarScroll(activeLinks); } } function updateSidebarScroll(activeLinks) { // Skip automatic scrolling during manual navigation if (window.scrollspyManualNavigation) return; const sidebar = document.getElementById("scrollspy-nav"); if (!sidebar) return; // Get the first active link as reference const firstActiveLink = activeLinks[0]; // Calculate the main content scroll progress const documentHeight = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight, ); const viewportHeight = window.innerHeight; const maxScroll = documentHeight - viewportHeight; const scrollProgress = maxScroll > 0 ? window.scrollY / maxScroll : 0; // Calculate sidebar scroll dimensions const sidebarHeight = sidebar.clientHeight; const sidebarScrollHeight = sidebar.scrollHeight; const maxSidebarScroll = sidebarScrollHeight - sidebarHeight; if (maxSidebarScroll > 0) { // Calculate proportional scroll position const targetSidebarScroll = scrollProgress * maxSidebarScroll; // Get the position of the first active link within the sidebar const linkRect = firstActiveLink.getBoundingClientRect(); const sidebarRect = sidebar.getBoundingClientRect(); const linkOffsetInSidebar = linkRect.top - sidebarRect.top + sidebar.scrollTop; // Calculate the desired position (center the active link in the sidebar viewport) const sidebarCenter = sidebarHeight / 2; const centeredPosition = linkOffsetInSidebar - sidebarCenter; // Use a blend of proportional scrolling and centering for smooth behavior const blendFactor = 0.7; // 70% proportional, 30% centering const finalScrollPosition = blendFactor * targetSidebarScroll + (1 - blendFactor) * centeredPosition; // Clamp to valid scroll range const clampedPosition = Math.max(0, Math.min(maxSidebarScroll, finalScrollPosition)); // Apply smooth scrolling, but only if the difference is significant const currentScrollTop = sidebar.scrollTop; const scrollDifference = Math.abs(clampedPosition - currentScrollTop); if (scrollDifference > 10) { // Only scroll if more than 10px difference sidebar.scrollTo({ top: clampedPosition, behavior: "smooth", }); } } } // Store scroll handler reference for cleanup window.scrollspyScrollHandler = function () { clearTimeout(window.scrollspyTimeout); window.scrollspyTimeout = setTimeout(updateActiveLink, 50); }; // Add scroll listener window.addEventListener("scroll", window.scrollspyScrollHandler); // Store click handlers for cleanup window.scrollspyClickHandlers = []; // Add smooth scroll on link click navLinks.forEach((link) => { const clickHandler = function (e) { e.preventDefault(); const target = document.getElementById(this.getAttribute("data-target")); if (target) { // Temporarily disable automatic sidebar scrolling during manual navigation window.scrollspyManualNavigation = true; target.scrollIntoView({ behavior: "smooth", block: "start", }); // Re-enable automatic scrolling after navigation completes setTimeout(() => { window.scrollspyManualNavigation = false; }, 1000); } }; window.scrollspyClickHandlers.push({ link, handler: clickHandler }); link.addEventListener("click", clickHandler); }); // Initial active link update updateActiveLink(); } // Cleanup scrollspy functionality function cleanupScrollspy() { // Remove scroll listener if (window.scrollspyScrollHandler) { window.removeEventListener("scroll", window.scrollspyScrollHandler); window.scrollspyScrollHandler = null; } // Clear timeout if (window.scrollspyTimeout) { clearTimeout(window.scrollspyTimeout); window.scrollspyTimeout = null; } // Remove click handlers if (window.scrollspyClickHandlers) { window.scrollspyClickHandlers.forEach(({ link, handler }) => { link.removeEventListener("click", handler); }); window.scrollspyClickHandlers = null; } // Reset slider const slider = document.getElementById("scrollspy-slider"); if (slider) { slider.style.opacity = '0'; slider.style.height = '0'; } // Reset manual navigation flag window.scrollspyManualNavigation = false; } // Initialize newspaper layout functionality function initializeNewspaperLayout() { // Initialize page highlighting initializePageHighlighting(); // Initialize page tracking initializePageTracking(); // Set up scroll handler window.addEventListener("scroll", function () { clearTimeout(window.scrollTimeout); window.scrollTimeout = setTimeout(() => { checkAndHighlightVisiblePages(); updateButtonStates(); // Update button states including Beilage toggle }, 50); }); // Initialize URL-based page navigation scrollToPageFromURL(); // Set up keyboard shortcuts document.addEventListener("keydown", function (e) { if (e.key === "Escape") { closeModal(); } }); } // Export functions for global access window.enlargePage = enlargePage; window.closeModal = closeModal; window.scrollToPreviousPage = scrollToPreviousPage; window.scrollToNextPage = scrollToNextPage; window.scrollToBeilage = scrollToBeilage; window.shareCurrentPage = shareCurrentPage; window.generateCitation = generateCitation; window.copyPagePermalink = copyPagePermalink; window.generatePageCitation = generatePageCitation; // INFO: This is intended to be called once on website load function setup() { setup_xslt(); setup_templates(); // Initialize newspaper layout if present if (document.querySelector(".newspaper-page-container")) { initializeNewspaperLayout(); } // Initialize scrollspy if author/agent sections are present if (document.querySelector(".author-section")) { initializeScrollspy(); } // Set up HTMX event handlers htmx.on("htmx:load", function (_) { // INFO: We can instead use afterSettle; and also clear the map with // xslt_processors.clear(); setup_xslt(); }); // HTMX event handling for newspaper layout and scrollspy document.body.addEventListener("htmx:afterSwap", function (event) { setTimeout(() => { if (document.querySelector(".newspaper-page-container")) { initializeNewspaperLayout(); } if (document.querySelector(".author-section")) { initializeScrollspy(); } }, 100); }); document.body.addEventListener("htmx:afterSettle", function (event) { setTimeout(() => { if (document.querySelector(".newspaper-page-container")) { initializeNewspaperLayout(); } if (document.querySelector(".author-section")) { initializeScrollspy(); } }, 200); }); document.body.addEventListener("htmx:load", function (event) { setTimeout(() => { if (document.querySelector(".newspaper-page-container")) { initializeNewspaperLayout(); } if (document.querySelector(".author-section")) { initializeScrollspy(); } }, 100); }); } // Single Page Viewer Web Component class SinglePageViewer extends HTMLElement { constructor() { super(); // No shadow DOM - use regular DOM to allow Tailwind CSS this.resizeObserver = null; } // Dynamically detect sidebar width in pixels detectSidebarWidth() { // Find the actual sidebar element in the current layout const sidebar = document.querySelector('.lg\\:w-1\\/4, .lg\\:w-1\\/3, [class*="lg:w-1/"]'); if (sidebar) { const sidebarRect = sidebar.getBoundingClientRect(); const sidebarWidth = sidebarRect.width; console.log("Detected sidebar width:", sidebarWidth, "px"); return `${sidebarWidth}px`; } // Fallback: calculate based on viewport width and responsive breakpoints const viewportWidth = window.innerWidth; if (viewportWidth < 1024) { // Below lg breakpoint - no sidebar space needed return "0px"; } else if (viewportWidth < 1280) { // lg breakpoint: assume 1/4 of viewport (similar to both layouts) return `${Math.floor(viewportWidth * 0.25)}px`; } else { // xl breakpoint: assume 1/5 of viewport (narrower on larger screens) return `${Math.floor(viewportWidth * 0.2)}px`; } } connectedCallback() { // Detect sidebar width dynamically const sidebarWidth = this.detectSidebarWidth(); this.innerHTML = `
`; // Set up resize observer to handle window resizing this.setupResizeObserver(); } // Set up resize observer to dynamically update sidebar width setupResizeObserver() { // Clean up existing observer if (this.resizeObserver) { this.resizeObserver.disconnect(); } // Create new resize observer this.resizeObserver = new ResizeObserver(() => { this.updateSidebarWidth(); }); // Observe window resizing by watching the document body this.resizeObserver.observe(document.body); } // Update sidebar width when window is resized updateSidebarWidth() { const sidebarSpacer = this.querySelector("#sidebar-spacer"); if (sidebarSpacer && !sidebarSpacer.style.width.includes("0px")) { // Only update if sidebar is not collapsed (not 0px width) const newWidth = this.detectSidebarWidth(); sidebarSpacer.style.width = newWidth; console.log("Updated sidebar width to:", newWidth); } } show(imgSrc, imgAlt, pageNumber, isBeilage = false, targetPage = 0, partNumber = null) { const img = this.querySelector("#single-page-image"); const pageNumberSpan = this.querySelector("#page-number"); const pageIconSpan = this.querySelector("#page-icon"); const pageIndicator = this.querySelector("#page-indicator"); img.src = imgSrc; img.alt = imgAlt; // Store current page info for button actions this.currentPageNumber = pageNumber; this.currentIsBeilage = isBeilage; this.currentPartNumber = partNumber; // Get issue context from document title or URL const issueContext = this.getIssueContext(pageNumber); // Set page number with issue context in the box pageNumberSpan.innerHTML = issueContext ? `${issueContext}, ${pageNumber}` : `${pageNumber}`; // Add red dot if this is the target page if (targetPage && pageNumber === targetPage) { pageNumberSpan.style.position = "relative"; // Remove any existing red dot const existingDot = pageNumberSpan.querySelector(".target-page-dot"); if (existingDot) { existingDot.remove(); } // Add new red dot const redDot = document.createElement("span"); redDot.className = "target-page-dot absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full z-10"; redDot.title = "verlinkte Seite"; pageNumberSpan.appendChild(redDot); } // Set page icon or part number based on view type if (partNumber !== null) { // Piece view: Show part number instead of icon pageIconSpan.innerHTML = `${partNumber}. Teil`; } else { // Issue view: Show icon based on position and type const iconType = this.determinePageIconType(pageNumber, isBeilage); pageIconSpan.innerHTML = this.getPageIconHTML(iconType); } // Page indicator styling is now consistent (white background) // Update navigation button visibility this.updateNavigationButtons(); this.style.display = "block"; // Prevent background scrolling but allow scrolling within the viewer document.body.style.overflow = "hidden"; // Mark current page in Inhaltsverzeichnis markCurrentPageInInhaltsverzeichnis(pageNumber); } close() { this.style.display = "none"; // Restore background scrolling document.body.style.overflow = ""; } // Clean up component completely destroy() { // Clean up resize observer if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } // Restore background scrolling document.body.style.overflow = ""; // Remove the component from DOM if (this.parentNode) { this.parentNode.removeChild(this); } } // Determine page icon type based on page position and whether it's beilage determinePageIconType(pageNumber, isBeilage) { // Get all page containers to determine position const containerSelector = isBeilage ? '.newspaper-page-container[data-beilage="true"]' : ".newspaper-page-container:not([data-beilage])"; const pageContainers = Array.from(document.querySelectorAll(containerSelector)); // Extract page numbers and sort them const allPages = pageContainers .map((container) => { const pageAttr = container.getAttribute("data-page-container"); return pageAttr ? parseInt(pageAttr) : null; }) .filter((p) => p !== null) .sort((a, b) => a - b); if (allPages.length === 0) { return "first"; } const firstPage = allPages[0]; const lastPage = allPages[allPages.length - 1]; // Same logic as Go determinePageIcon function if (pageNumber === firstPage) { return "first"; // Front page - normal icon } else if (pageNumber === lastPage) { return "last"; // Back page - mirrored icon } else { // For middle pages in newspaper layout if (pageNumber === firstPage + 1) { return "even"; // Page 2 - black + mirrored grey } else if (pageNumber === lastPage - 1) { return "odd"; // Page 3 - grey + black } else { // For newspapers with more than 4 pages, use alternating pattern if (pageNumber % 2 === 0) { return "even"; } else { return "odd"; } } } } // Generate page icon HTML based on type (same as Go PageIcon function) getPageIconHTML(iconType) { const baseClass = "ri-file-text-line text-lg"; switch (iconType) { case "first": return ``; case "last": return ``; case "even": return ``; case "odd": return ``; default: return ``; } } // Share current page shareCurrentPage() { if (typeof copyPagePermalink === "function") { // Use the actual button element const shareBtn = this.querySelector("#share-btn"); copyPagePermalink(this.currentPageNumber, shareBtn, this.currentIsBeilage); } } // Generate citation for current page generatePageCitation() { if (typeof generatePageCitation === "function") { // Use the actual button element const citeBtn = this.querySelector("#cite-btn"); generatePageCitation(this.currentPageNumber, citeBtn); } } // Update navigation button visibility based on available pages updateNavigationButtons() { const prevBtn = this.querySelector("#prev-page-btn"); const nextBtn = this.querySelector("#next-page-btn"); const { prevPage, nextPage } = this.getAdjacentPages(); // Enable/disable previous page button if (prevPage !== null) { prevBtn.disabled = false; prevBtn.className = prevBtn.className.replace("opacity-50 cursor-not-allowed", ""); prevBtn.className = prevBtn.className.replace( "bg-gray-50 text-gray-400", "bg-gray-100 text-gray-700", ); } else { prevBtn.disabled = true; if (!prevBtn.className.includes("opacity-50")) { prevBtn.className += " opacity-50 cursor-not-allowed"; } prevBtn.className = prevBtn.className.replace( "bg-gray-100 text-gray-700", "bg-gray-50 text-gray-400", ); } // Enable/disable next page button if (nextPage !== null) { nextBtn.disabled = false; nextBtn.className = nextBtn.className.replace("opacity-50 cursor-not-allowed", ""); nextBtn.className = nextBtn.className.replace( "bg-gray-50 text-gray-400", "bg-gray-100 text-gray-700", ); } else { nextBtn.disabled = true; if (!nextBtn.className.includes("opacity-50")) { nextBtn.className += " opacity-50 cursor-not-allowed"; } nextBtn.className = nextBtn.className.replace( "bg-gray-100 text-gray-700", "bg-gray-50 text-gray-400", ); } } // Get previous and next page numbers getAdjacentPages() { // Get all page containers of the same type (main or beilage) let containerSelector; if (this.currentIsBeilage) { containerSelector = '.newspaper-page-container[data-beilage="true"]'; } else { containerSelector = ".newspaper-page-container:not([data-beilage])"; } const pageContainers = Array.from(document.querySelectorAll(containerSelector)); console.log( "Found containers:", pageContainers.length, "for", this.currentIsBeilage ? "beilage" : "main", ); // Extract page numbers and sort them const allPages = pageContainers .map((container) => { const pageAttr = container.getAttribute("data-page-container"); const pageNum = pageAttr ? parseInt(pageAttr) : null; console.log("Container page:", pageAttr, "parsed:", pageNum); return pageNum; }) .filter((p) => p !== null) .sort((a, b) => a - b); console.log("All pages found:", allPages); console.log("Current page:", this.currentPageNumber); const currentIndex = allPages.indexOf(this.currentPageNumber); console.log("Current index:", currentIndex); let prevPage = null; let nextPage = null; if (currentIndex > 0) { prevPage = allPages[currentIndex - 1]; } if (currentIndex < allPages.length - 1) { nextPage = allPages[currentIndex + 1]; } console.log("Adjacent pages - prev:", prevPage, "next:", nextPage); return { prevPage, nextPage }; } // Navigate to previous page goToPreviousPage() { const { prevPage } = this.getAdjacentPages(); if (prevPage !== null) { this.navigateToPage(prevPage); } } // Navigate to next page goToNextPage() { const { nextPage } = this.getAdjacentPages(); if (nextPage !== null) { this.navigateToPage(nextPage); } } // Navigate to a specific page navigateToPage(pageNumber) { // Find the image element for the target page const containerSelector = this.currentIsBeilage ? '.newspaper-page-container[data-beilage="true"]' : ".newspaper-page-container:not([data-beilage])"; const targetContainer = document.querySelector( `${containerSelector}[data-page-container="${pageNumber}"]`, ); if (targetContainer) { const imgElement = targetContainer.querySelector(".newspaper-page-image"); if (imgElement) { // Determine part number for piece view let newPartNumber = null; if (this.currentPartNumber !== null) { // We're in piece view, try to find the part number for the new page newPartNumber = this.getPartNumberForPage(pageNumber); } // Update the current view with the new page this.show( imgElement.src, imgElement.alt, pageNumber, this.currentIsBeilage, 0, newPartNumber, ); } } } // Get part number for a specific page in piece view getPartNumberForPage(pageNumber) { // Try to find the part number from the page container in piece view const pageContainer = document.querySelector(`[data-page-container="${pageNumber}"]`); if (pageContainer) { const partNumberElement = pageContainer.querySelector(".part-number"); if (partNumberElement) { // Extract just the number from "X. Teil" format const match = partNumberElement.textContent.match(/(\d+)\./); if (match) { return parseInt(match[1]); } } } // Fallback: if we can't find it, return null to show icon instead return null; } // Toggle sidebar visibility toggleSidebar() { const sidebarSpacer = this.querySelector("#sidebar-spacer"); const toggleBtn = this.querySelector("#sidebar-toggle-btn"); const toggleIcon = toggleBtn.querySelector("i"); // Check if sidebar is currently collapsed by looking at width const currentWidth = sidebarSpacer.style.width; const isCollapsed = currentWidth === "0px" || currentWidth === "0"; console.log("Current state - isCollapsed:", isCollapsed); console.log("Current width:", currentWidth); if (isCollapsed) { // Sidebar is collapsed, expand it const fullWidth = this.detectSidebarWidth(); sidebarSpacer.style.width = fullWidth; // Update button to normal state (sidebar visible) toggleBtn.className = "w-10 h-10 bg-slate-100 hover:bg-slate-200 text-slate-700 border border-slate-300 rounded flex items-center justify-center transition-colors duration-200 cursor-pointer"; toggleIcon.className = "ri-sidebar-fold-line text-lg font-bold"; toggleBtn.title = "Inhaltsverzeichnis ausblenden"; console.log("Expanding sidebar to:", fullWidth); } else { // Sidebar is expanded, collapse it sidebarSpacer.style.width = "0px"; // Update button to active state (sidebar hidden - orange highlight) toggleBtn.className = "w-10 h-10 bg-orange-100 hover:bg-orange-200 text-orange-700 border border-orange-300 rounded flex items-center justify-center transition-colors duration-200 cursor-pointer"; toggleIcon.className = "ri-sidebar-unfold-line text-lg font-bold"; toggleBtn.title = "Inhaltsverzeichnis einblenden"; console.log("Collapsing sidebar"); } console.log("New width:", sidebarSpacer.style.width); } // Extract issue context from document title, URL, or page container getIssueContext(pageNumber) { // Determine if we're in a piece view (beitrag) or issue view (ausgabe) const path = window.location.pathname; const isPieceView = path.includes("/beitrag/"); if (isPieceView) { // For piece view: Return full format "1765 Nr. 2" // Try to get context from page container first (for piece view) const pageContainer = document.querySelector(`[data-page-container="${pageNumber}"]`); if (pageContainer) { // Look for existing page indicator with context const pageIndicator = pageContainer.querySelector(".page-indicator"); if (pageIndicator) { const text = pageIndicator.textContent.trim(); // Extract full date and issue from text like "3.7.1767 Nr. 53, 213" const fullDateMatch = text.match(/(\d{1,2}\.\d{1,2}\.\d{4}\s+Nr\.\s+\d+)/); if (fullDateMatch) { return fullDateMatch[1]; } // Fallback: Extract year and issue from text like "1768 Nr. 20, 79" const yearMatch = text.match(/(\d{4})\s+Nr\.\s+(\d+)/); if (yearMatch) { return `${yearMatch[1]} Nr. ${yearMatch[2]}`; } } } // Fallback: Try to extract from document title const title = document.title; const titleMatch = title.match(/(\d{4}).*Nr\.\s*(\d+)/); if (titleMatch) { return `${titleMatch[1]} Nr. ${titleMatch[2]}`; } } else { // For issue view: Return just empty string (page number only) return ""; } // Final fallback: Try to extract from URL path const urlMatch = path.match(/\/(\d{4})\/(\d+)/); if (urlMatch) { return isPieceView ? `${urlMatch[1]} Nr. ${urlMatch[2]}` : ""; } // Fallback - try to get from any visible page context const anyPageIndicator = document.querySelector(".page-indicator"); if (anyPageIndicator) { const text = anyPageIndicator.textContent.trim(); const match = text.match(/(\d{4})\s+Nr\.\s+(\d+)/); if (match) { return `${match[1]} Nr. ${match[2]}`; } } // Ultimate fallback return "KGPZ"; } } // Register the web component customElements.define("single-page-viewer", SinglePageViewer); // Clean up single page viewer on HTMX navigation document.body.addEventListener("htmx:beforeRequest", function (event) { // Find any active single page viewer const viewer = document.querySelector("single-page-viewer"); if (viewer && viewer.style.display !== "none") { console.log("Cleaning up single page viewer before HTMX navigation"); viewer.destroy(); } // Clean up scrollspy before navigating cleanupScrollspy(); }); // Also clean up on page unload as fallback window.addEventListener("beforeunload", function () { const viewer = document.querySelector("single-page-viewer"); if (viewer) { viewer.destroy(); } }); export { setup };