mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-28 16:45:32 +00:00
346 lines
11 KiB
Plaintext
346 lines
11 KiB
Plaintext
{{ $model := .model }}
|
|
|
|
<div class="w-full min-h-screen">
|
|
<!-- Navigation Bar -->
|
|
<div class="sticky top-0 z-40 bg-white border-b border-gray-200 shadow-sm mb-6">
|
|
<div class="max-w-7xl mx-auto px-4 py-4">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
{{ template "_title_nav" . }}
|
|
</div>
|
|
<div class="flex items-center space-x-3">
|
|
<!-- Navigation buttons -->
|
|
<button onclick="scrollToPreviousPage()" class="flex items-center px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
|
<i class="ri-arrow-up-line mr-1"></i>
|
|
Vorherige Seite
|
|
</button>
|
|
<button onclick="scrollToNextPage()" class="flex items-center px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
|
<i class="ri-arrow-down-line mr-1"></i>
|
|
Nächste Seite
|
|
</button>
|
|
{{ if $model.Images.AdditionalPages }}
|
|
<button onclick="scrollToBeilage()" class="flex items-center px-3 py-2 bg-amber-600 hover:bg-amber-700 text-white rounded-md transition-colors">
|
|
<i class="ri-attachment-line mr-1"></i>
|
|
Zu Beilage
|
|
</button>
|
|
{{ end }}
|
|
<!-- Switch back to sidebar layout -->
|
|
<a href="?layout=sidebar" class="flex items-center px-3 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-md transition-colors">
|
|
<i class="ri-sidebar-unfold-line mr-1"></i>
|
|
Seitenleiste
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-4">
|
|
<div class="flex flex-col lg:flex-row gap-6 w-full">
|
|
<!-- Left side: Collapsible Inhaltsverzeichnis -->
|
|
<div class="lg:w-1/4 xl:w-1/5 flex-shrink-0">
|
|
<div class="lg:sticky lg:top-24 lg:max-h-[calc(100vh-6rem)] lg:overflow-y-auto">
|
|
{{ template "_inhaltsverzeichnis" . }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right side: Full-width Newspaper pages -->
|
|
<div class="lg:w-3/4 xl:w-4/5 flex-1">
|
|
{{ template "_newspaper_layout" . }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentPageContainers = [];
|
|
let currentActiveIndex = 0;
|
|
|
|
// Initialize page tracking for full-width layout
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Get all page containers
|
|
currentPageContainers = document.querySelectorAll('.newspaper-page-container');
|
|
|
|
// Set up intersection observer for active page tracking
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
// Check if this is a double-spread container
|
|
const doubleSpread = entry.target.querySelector('.double-spread');
|
|
if (doubleSpread) {
|
|
// Handle double-spread: highlight both pages
|
|
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
|
|
const containerIndex = Array.from(currentPageContainers).indexOf(entry.target);
|
|
if (containerIndex !== -1) {
|
|
currentActiveIndex = containerIndex;
|
|
}
|
|
}
|
|
});
|
|
}, {
|
|
rootMargin: '-20% 0px -70% 0px' // Trigger when page is mostly in view
|
|
});
|
|
|
|
// Observe all page containers
|
|
currentPageContainers.forEach(container => {
|
|
observer.observe(container);
|
|
});
|
|
});
|
|
|
|
function scrollToPreviousPage() {
|
|
if (currentActiveIndex > 0) {
|
|
currentActiveIndex--;
|
|
currentPageContainers[currentActiveIndex].scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
}
|
|
|
|
function scrollToNextPage() {
|
|
if (currentActiveIndex < currentPageContainers.length - 1) {
|
|
currentActiveIndex++;
|
|
currentPageContainers[currentActiveIndex].scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
}
|
|
|
|
function scrollToBeilage() {
|
|
const beilageElement = document.querySelector('[id^="beilage-"]');
|
|
if (beilageElement) {
|
|
beilageElement.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
}
|
|
|
|
function markCurrentPageInInhaltsverzeichnis(pageNumber) {
|
|
markCurrentPagesInInhaltsverzeichnis([pageNumber]);
|
|
}
|
|
|
|
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 numbers
|
|
const highlightedElements = [];
|
|
const highlightedRanges = new Set(); // Track which ranges we've already highlighted
|
|
|
|
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
|
|
document.querySelectorAll('.page-indicator').forEach(indicator => {
|
|
indicator.classList.remove('bg-red-500', 'text-white');
|
|
indicator.classList.add('bg-blue-50', 'text-slate-600');
|
|
});
|
|
|
|
// Highlight page indicators for all current pages
|
|
pageNumbers.forEach(pageNumber => {
|
|
const pageIndicator = document.querySelector(`.page-indicator[data-page="${pageNumber}"]`);
|
|
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'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keyboard navigation
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
// Close modal if open
|
|
const modal = document.getElementById('pageModal');
|
|
if (modal && !modal.classList.contains('hidden')) {
|
|
modal.classList.add('hidden');
|
|
}
|
|
} else if (e.key === 'ArrowUp' || e.key === 'PageUp') {
|
|
e.preventDefault();
|
|
scrollToPreviousPage();
|
|
} else if (e.key === 'ArrowDown' || e.key === 'PageDown') {
|
|
e.preventDefault();
|
|
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> |