mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-29 17:15:31 +00:00
Schellfilter; bugfixes; Tagewerk; Anfang Ort Controller
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { ExecuteNextSettle } from "./helpers.js";
|
||||
// ===========================
|
||||
// AKTEURE/AUTHORS SCROLLSPY WEB COMPONENT
|
||||
// ===========================
|
||||
@@ -13,7 +14,7 @@ export class AkteureScrollspy extends HTMLElement {
|
||||
|
||||
connectedCallback() {
|
||||
// Small delay to ensure DOM is fully rendered after HTMX swap
|
||||
window.ExecuteNextSettle(() => {
|
||||
ExecuteNextSettle(() => {
|
||||
this.initializeScrollspyAfterDelay();
|
||||
});
|
||||
}
|
||||
@@ -68,7 +69,7 @@ export class AkteureScrollspy extends HTMLElement {
|
||||
this.manualNavigation = true;
|
||||
|
||||
target.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
behavior: "instant",
|
||||
block: "start",
|
||||
});
|
||||
|
||||
|
||||
83
views/transform/error-modal.js
Normal file
83
views/transform/error-modal.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* ErrorModal Web Component
|
||||
* A reusable error modal component without shadow DOM for Tailwind compatibility
|
||||
*/
|
||||
class ErrorModal extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
// No shadow DOM - use regular DOM for Tailwind styling
|
||||
this.innerHTML = `
|
||||
<div id="error-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center backdrop-blur-sm">
|
||||
<div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 max-h-96 overflow-y-auto">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-red-600 flex items-center gap-2">
|
||||
<i class="ri-error-warning-line text-xl"></i>
|
||||
Fehler
|
||||
</h3>
|
||||
<button class="close-btn text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<i class="ri-close-line text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="error-content text-slate-700">
|
||||
<!-- Error content will be loaded here -->
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button class="close-btn px-4 py-2 bg-slate-600 hover:bg-slate-700 text-white rounded transition-colors">
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.modal = this.querySelector('#error-modal');
|
||||
this.errorContent = this.querySelector('.error-content');
|
||||
this.closeButtons = this.querySelectorAll('.close-btn');
|
||||
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Close button clicks
|
||||
this.closeButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => this.close());
|
||||
});
|
||||
|
||||
// Close on ESC key
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && !this.modal.classList.contains('hidden')) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Close when clicking outside modal
|
||||
this.modal.addEventListener('click', (event) => {
|
||||
if (event.target === this.modal) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show(content) {
|
||||
this.errorContent.innerHTML = content;
|
||||
this.modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
close() {
|
||||
this.modal.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Global helper functions for backward compatibility
|
||||
connectedCallback() {
|
||||
// Make functions globally available
|
||||
window.showErrorModal = (content) => this.show(content);
|
||||
window.closeErrorModal = () => this.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Define the custom element
|
||||
customElements.define('error-modal', ErrorModal);
|
||||
|
||||
export { ErrorModal };
|
||||
35
views/transform/helpers.js
Normal file
35
views/transform/helpers.js
Normal file
@@ -0,0 +1,35 @@
|
||||
// This is a queue that stores functions to be executed after the DOM is settled.
|
||||
// It is used to ensure the DOM is fully rendered before executing certain actions.
|
||||
// Works as well as a DOMContentLoaded event listener.
|
||||
|
||||
const settleQueue = [];
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
ExecuteSettleQueue();
|
||||
});
|
||||
|
||||
const ExecuteNextSettle = function (fn) {
|
||||
if (typeof fn === "function") {
|
||||
settleQueue.push(fn);
|
||||
}
|
||||
};
|
||||
|
||||
const DeleteNextSettle = function (fn) {
|
||||
const index = settleQueue.indexOf(fn);
|
||||
if (index !== -1) {
|
||||
settleQueue.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const ExecuteSettleQueue = function () {
|
||||
while (settleQueue.length > 0) {
|
||||
const fn = settleQueue.shift();
|
||||
try {
|
||||
fn();
|
||||
} catch (error) {
|
||||
console.error("Error executing settle queue function:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { ExecuteNextSettle, ExecuteSettleQueue, DeleteNextSettle };
|
||||
@@ -1,8 +1,11 @@
|
||||
import "./site.css";
|
||||
import "./search.js";
|
||||
import "./akteure.js";
|
||||
import { SinglePageViewer } from "./single-page-viewer.js";
|
||||
import { ScrollToTopButton } from "./scroll-to-top.js";
|
||||
import { InhaltsverzeichnisScrollspy } from "./inhaltsverzeichnis-scrollspy.js";
|
||||
import { ErrorModal } from "./error-modal.js";
|
||||
import { ExecuteSettleQueue } from "./helpers.js";
|
||||
import {
|
||||
enlargePage,
|
||||
closeModal,
|
||||
@@ -91,29 +94,7 @@ function applyPageBackdrop() {
|
||||
}
|
||||
}
|
||||
|
||||
// Function queue system for HTMX settle events
|
||||
let settleQueue = [];
|
||||
|
||||
// Global function to register functions for next settle event
|
||||
window.ExecuteNextSettle = function(fn) {
|
||||
if (typeof fn === 'function') {
|
||||
settleQueue.push(fn);
|
||||
}
|
||||
};
|
||||
|
||||
// Execute and clear the queue
|
||||
function executeSettleQueue() {
|
||||
while (settleQueue.length > 0) {
|
||||
const fn = settleQueue.shift();
|
||||
try {
|
||||
fn();
|
||||
} catch (error) {
|
||||
console.error('Error executing settle queue function:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions for global access
|
||||
// Export functions for global access - moved outside setup() so they're available immediately
|
||||
window.enlargePage = enlargePage;
|
||||
window.closeModal = closeModal;
|
||||
window.scrollToPreviousPage = scrollToPreviousPage;
|
||||
@@ -124,44 +105,35 @@ window.generateCitation = generateCitation;
|
||||
window.copyPagePermalink = copyPagePermalink;
|
||||
window.generatePageCitation = generatePageCitation;
|
||||
|
||||
// INFO: This is intended to be called once on website load
|
||||
function setup() {
|
||||
// Apply page-specific backdrop styling
|
||||
applyPageBackdrop();
|
||||
// Apply page-specific backdrop styling
|
||||
applyPageBackdrop();
|
||||
|
||||
// Update citation links on initial load
|
||||
updateCitationLinks();
|
||||
// Update citation links on initial load
|
||||
updateCitationLinks();
|
||||
|
||||
// Initialize newspaper layout if present
|
||||
if (document.querySelector(".newspaper-page-container")) {
|
||||
initializeNewspaperLayout();
|
||||
}
|
||||
|
||||
// Akteure scrollspy web component will auto-initialize when present in template
|
||||
|
||||
// HTMX event handling for newspaper layout, scrollspy, and scroll-to-top button
|
||||
let htmxAfterSwapHandler = function (event) {
|
||||
// Apply page-specific backdrop styling after navigation
|
||||
applyPageBackdrop();
|
||||
// Update citation links after navigation
|
||||
updateCitationLinks();
|
||||
|
||||
// Execute all queued functions
|
||||
executeSettleQueue();
|
||||
|
||||
// Use shorter delay since afterSettle ensures DOM is ready
|
||||
setTimeout(() => {
|
||||
if (document.querySelector(".newspaper-page-container")) {
|
||||
initializeNewspaperLayout();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
|
||||
let htmxBeforeRequestHandler = function (event) {};
|
||||
|
||||
document.body.addEventListener("htmx:afterSettle", htmxAfterSwapHandler);
|
||||
document.body.addEventListener("htmx:afterSettle", updateCitationLinks);
|
||||
document.body.addEventListener("htmx:beforeRequest", htmxBeforeRequestHandler);
|
||||
// Initialize newspaper layout if present
|
||||
if (document.querySelector(".newspaper-page-container")) {
|
||||
initializeNewspaperLayout();
|
||||
}
|
||||
|
||||
export { setup };
|
||||
// Akteure scrollspy web component will auto-initialize when present in template
|
||||
|
||||
// HTMX event handling for newspaper layout, scrollspy, and scroll-to-top button
|
||||
let htmxAfterSwapHandler = function (event) {
|
||||
// Apply page-specific backdrop styling after navigation
|
||||
applyPageBackdrop();
|
||||
// Update citation links after navigation
|
||||
updateCitationLinks();
|
||||
|
||||
// Execute all queued functions
|
||||
ExecuteSettleQueue();
|
||||
|
||||
// Use shorter delay since afterSettle ensures DOM is ready
|
||||
setTimeout(() => {
|
||||
if (document.querySelector(".newspaper-page-container")) {
|
||||
initializeNewspaperLayout();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
|
||||
document.body.addEventListener("htmx:afterSettle", htmxAfterSwapHandler);
|
||||
|
||||
410
views/transform/search.js
Normal file
410
views/transform/search.js
Normal file
@@ -0,0 +1,410 @@
|
||||
const filterBtn = document.getElementById("filter-toggle");
|
||||
if (filterBtn) {
|
||||
filterBtn.addEventListener("click", toggleFilter);
|
||||
}
|
||||
|
||||
// Filter toggle functionality
|
||||
function toggleFilter() {
|
||||
const filterContainer = document.getElementById("filter-container");
|
||||
const filterButton = document.getElementById("filter-toggle");
|
||||
const filterContentDiv = filterContainer?.querySelector("div.flex.justify-center");
|
||||
|
||||
if (filterContainer.classList.contains("hidden")) {
|
||||
// Show the filter
|
||||
filterContainer.classList.remove("hidden");
|
||||
filterButton.classList.add("bg-slate-200");
|
||||
|
||||
// Load content only if it doesn't exist - check for actual content
|
||||
const hasContent = filterContentDiv && filterContentDiv.querySelector("div, form, h3");
|
||||
if (!hasContent) {
|
||||
htmx
|
||||
.ajax("GET", "/filter", {
|
||||
target: "#filter-container",
|
||||
select: "#filter",
|
||||
swap: "innerHTML",
|
||||
})
|
||||
.then(() => {
|
||||
console.log("HTMX request completed");
|
||||
// Re-query the element to see if it changed
|
||||
const updatedDiv = document.querySelector("#filter-container .flex.justify-center");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("HTMX request failed:", error);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
filterContainer.classList.add("hidden");
|
||||
filterButton.classList.remove("bg-slate-200");
|
||||
}
|
||||
}
|
||||
|
||||
// Export for global access
|
||||
window.toggleFilter = toggleFilter;
|
||||
|
||||
// Close filter when clicking outside
|
||||
document.addEventListener("click", function (event) {
|
||||
const filterContainer = document.getElementById("filter-container");
|
||||
const filterButton = document.getElementById("filter-toggle");
|
||||
|
||||
if (
|
||||
filterContainer &&
|
||||
filterButton &&
|
||||
!filterContainer.contains(event.target) &&
|
||||
!filterButton.contains(event.target)
|
||||
) {
|
||||
if (!filterContainer.classList.contains("hidden")) {
|
||||
filterContainer.classList.add("hidden");
|
||||
filterButton.classList.remove("bg-slate-200");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle search input logic
|
||||
document.body.addEventListener("htmx:configRequest", function (event) {
|
||||
let element = event.detail.elt;
|
||||
|
||||
if (element.id === "search" && element.value === "") {
|
||||
event.detail.parameters = {};
|
||||
event.detail.path = window.location.pathname + window.location.search;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PersonJumpFilter - Web component for filtering persons list
|
||||
* Works with server-rendered person list and provides client-side filtering
|
||||
*/
|
||||
class PersonJumpFilter extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const searchInput = this.querySelector('#person-search');
|
||||
const authorsCheckbox = this.querySelector('#authors-only');
|
||||
const allPersonsList = this.querySelector('#all-persons');
|
||||
const authorsOnlyList = this.querySelector('#authors-only-list');
|
||||
|
||||
if (!searchInput || !authorsCheckbox || !allPersonsList || !authorsOnlyList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search functionality
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase().trim();
|
||||
this.filterPersons(query);
|
||||
});
|
||||
|
||||
// Checkbox functionality
|
||||
authorsCheckbox.addEventListener('change', () => {
|
||||
this.togglePersonsList();
|
||||
// Clear and re-apply search filter
|
||||
const query = searchInput.value.toLowerCase().trim();
|
||||
this.filterPersons(query);
|
||||
});
|
||||
}
|
||||
|
||||
togglePersonsList() {
|
||||
const authorsCheckbox = this.querySelector('#authors-only');
|
||||
const allPersonsList = this.querySelector('#all-persons');
|
||||
const authorsOnlyList = this.querySelector('#authors-only-list');
|
||||
|
||||
if (!authorsCheckbox || !allPersonsList || !authorsOnlyList) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (authorsCheckbox.checked) {
|
||||
allPersonsList.style.display = 'none';
|
||||
authorsOnlyList.style.display = 'block';
|
||||
} else {
|
||||
allPersonsList.style.display = 'block';
|
||||
authorsOnlyList.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
filterPersons(query) {
|
||||
// Filter items in the currently visible list
|
||||
const authorsCheckbox = this.querySelector('#authors-only');
|
||||
const currentList = authorsCheckbox?.checked ?
|
||||
this.querySelector('#authors-only-list') :
|
||||
this.querySelector('#all-persons');
|
||||
|
||||
if (!currentList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const personItems = currentList.querySelectorAll('.person-item');
|
||||
|
||||
personItems.forEach(item => {
|
||||
const name = item.querySelector('.person-name')?.textContent || '';
|
||||
const life = item.querySelector('.person-life')?.textContent || '';
|
||||
|
||||
const matches = !query ||
|
||||
name.toLowerCase().includes(query) ||
|
||||
life.toLowerCase().includes(query);
|
||||
|
||||
if (matches) {
|
||||
item.style.display = 'block';
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
customElements.define('person-jump-filter', PersonJumpFilter);
|
||||
|
||||
/**
|
||||
* YearJumpFilter - Unified web component for Jahr-based navigation
|
||||
* Allows jumping by Jahr/Ausgabe or Jahr/Seite
|
||||
*/
|
||||
class YearJumpFilter extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.issuesByYear = {};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.parseIssuesData();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
parseIssuesData() {
|
||||
// Parse issues data from data attributes
|
||||
const issuesData = this.dataset.issues;
|
||||
if (issuesData) {
|
||||
try {
|
||||
this.issuesByYear = JSON.parse(issuesData);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse issues data:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const yearSelect = this.querySelector('#year-select');
|
||||
const issueNumberSelect = this.querySelector('#issue-number-select');
|
||||
const issueDateSelect = this.querySelector('#issue-date-select');
|
||||
const pageInput = this.querySelector('#page-input');
|
||||
const pageJumpBtn = this.querySelector('#page-jump-btn');
|
||||
|
||||
if (!yearSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Year selection change handler
|
||||
yearSelect.addEventListener('change', () => {
|
||||
this.updateIssueOptions();
|
||||
this.updatePageInputState();
|
||||
this.clearPageErrors();
|
||||
});
|
||||
|
||||
// Issue number selection change handler - jump immediately
|
||||
if (issueNumberSelect) {
|
||||
issueNumberSelect.addEventListener('change', () => {
|
||||
const year = yearSelect.value;
|
||||
const issueNum = issueNumberSelect.value;
|
||||
if (year && issueNum) {
|
||||
window.location.href = `/${year}/${issueNum}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Issue date selection change handler - jump immediately
|
||||
if (issueDateSelect) {
|
||||
issueDateSelect.addEventListener('change', () => {
|
||||
const year = yearSelect.value;
|
||||
const issueNum = issueDateSelect.value; // value contains issue number
|
||||
if (year && issueNum) {
|
||||
window.location.href = `/${year}/${issueNum}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Page input handlers
|
||||
if (pageInput) {
|
||||
pageInput.addEventListener('input', () => {
|
||||
this.updatePageJumpButton();
|
||||
this.clearPageErrors();
|
||||
});
|
||||
|
||||
// Handle Enter key in page input
|
||||
pageInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.handlePageJump();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Page jump button
|
||||
if (pageJumpBtn) {
|
||||
pageJumpBtn.addEventListener('click', () => {
|
||||
this.handlePageJump();
|
||||
});
|
||||
}
|
||||
|
||||
// Page jump form submission
|
||||
const pageForm = this.querySelector('#page-jump-form');
|
||||
if (pageForm) {
|
||||
pageForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.handlePageJump();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize everything
|
||||
this.updateIssueOptions();
|
||||
this.updatePageInputState();
|
||||
this.updatePageJumpButton();
|
||||
}
|
||||
|
||||
updateIssueOptions() {
|
||||
const yearSelect = this.querySelector('#year-select');
|
||||
const issueNumberSelect = this.querySelector('#issue-number-select');
|
||||
const issueDateSelect = this.querySelector('#issue-date-select');
|
||||
|
||||
if (!yearSelect || !issueNumberSelect || !issueDateSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedYear = yearSelect.value;
|
||||
const issues = this.issuesByYear[selectedYear] || [];
|
||||
|
||||
// Clear existing options
|
||||
issueNumberSelect.innerHTML = '<option value="">Nr.</option>';
|
||||
issueDateSelect.innerHTML = '<option value="">Datum</option>';
|
||||
|
||||
// Add options for selected year
|
||||
issues.forEach(issue => {
|
||||
// Issue number select - just the number
|
||||
const numberOption = document.createElement('option');
|
||||
numberOption.value = issue.number;
|
||||
numberOption.textContent = issue.number;
|
||||
issueNumberSelect.appendChild(numberOption);
|
||||
|
||||
// Issue date select - date with issue number as value
|
||||
const dateOption = document.createElement('option');
|
||||
dateOption.value = issue.number; // value is still issue number for navigation
|
||||
dateOption.textContent = `${issue.date} [${issue.number}]`;
|
||||
issueDateSelect.appendChild(dateOption);
|
||||
});
|
||||
|
||||
const hasIssues = issues.length > 0 && selectedYear;
|
||||
issueNumberSelect.disabled = !hasIssues;
|
||||
issueDateSelect.disabled = !hasIssues;
|
||||
}
|
||||
|
||||
async handlePageJump() {
|
||||
const yearSelect = this.querySelector('#year-select');
|
||||
const pageInput = this.querySelector('#page-input');
|
||||
const errorContainer = this.querySelector('#jump-errors');
|
||||
|
||||
if (!yearSelect || !pageInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const year = yearSelect.value;
|
||||
const page = pageInput.value;
|
||||
|
||||
if (!year || !page) {
|
||||
this.showError('Bitte Jahr und Seite auswählen.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('year', year);
|
||||
formData.append('page', page);
|
||||
|
||||
const response = await fetch('/jump', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
redirect: 'manual'
|
||||
});
|
||||
|
||||
// Check for HTMX redirect header
|
||||
const hxRedirect = response.headers.get('HX-Redirect');
|
||||
if (hxRedirect) {
|
||||
window.location.href = hxRedirect;
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 302 || response.status === 301) {
|
||||
const location = response.headers.get('Location');
|
||||
if (location) {
|
||||
window.location.href = location;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
if (errorContainer) {
|
||||
errorContainer.innerHTML = '';
|
||||
}
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
if (errorContainer) {
|
||||
errorContainer.innerHTML = errorText;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Page jump failed:', error);
|
||||
this.showError('Fehler beim Suchen der Seite.');
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
const errorContainer = this.querySelector('#jump-errors');
|
||||
if (errorContainer) {
|
||||
errorContainer.innerHTML = `<div class="text-red-600 text-sm mt-1">${message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
clearPageErrors() {
|
||||
const errorContainer = this.querySelector('#jump-errors');
|
||||
if (errorContainer) {
|
||||
errorContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
updatePageInputState() {
|
||||
const yearSelect = this.querySelector('#year-select');
|
||||
const pageInput = this.querySelector('#page-input');
|
||||
|
||||
if (!yearSelect || !pageInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasYear = yearSelect.value;
|
||||
pageInput.disabled = !hasYear;
|
||||
|
||||
if (!hasYear) {
|
||||
pageInput.value = '';
|
||||
this.updatePageJumpButton();
|
||||
}
|
||||
}
|
||||
|
||||
updatePageJumpButton() {
|
||||
const yearSelect = this.querySelector('#year-select');
|
||||
const pageInput = this.querySelector('#page-input');
|
||||
const pageJumpBtn = this.querySelector('#page-jump-btn');
|
||||
|
||||
if (!yearSelect || !pageInput || !pageJumpBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasYear = yearSelect.value;
|
||||
const hasPage = pageInput.value && pageInput.value.trim();
|
||||
const shouldEnable = hasYear && hasPage;
|
||||
|
||||
pageJumpBtn.disabled = !shouldEnable;
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
customElements.define('year-jump-filter', YearJumpFilter);
|
||||
@@ -255,9 +255,11 @@ export class SinglePageViewer extends HTMLElement {
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
// Dispatch event for scrollspy
|
||||
document.dispatchEvent(new CustomEvent('singlepageviewer:opened', {
|
||||
detail: { pageNumber: this.currentPageNumber, isBeilage: this.currentIsBeilage }
|
||||
}));
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("singlepageviewer:opened", {
|
||||
detail: { pageNumber: this.currentPageNumber, isBeilage: this.currentIsBeilage },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
close() {
|
||||
@@ -268,9 +270,11 @@ export class SinglePageViewer extends HTMLElement {
|
||||
document.body.style.overflow = "";
|
||||
|
||||
// Dispatch event for scrollspy
|
||||
document.dispatchEvent(new CustomEvent('singlepageviewer:closed', {
|
||||
detail: { pageNumber: this.currentPageNumber, isBeilage: this.currentIsBeilage }
|
||||
}));
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("singlepageviewer:closed", {
|
||||
detail: { pageNumber: this.currentPageNumber, isBeilage: this.currentIsBeilage },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -282,16 +286,14 @@ export class SinglePageViewer extends HTMLElement {
|
||||
|
||||
// Clean up keyboard event listeners
|
||||
if (this.keyboardHandler) {
|
||||
document.removeEventListener('keydown', this.keyboardHandler);
|
||||
document.removeEventListener("keydown", this.keyboardHandler);
|
||||
this.keyboardHandler = null;
|
||||
}
|
||||
|
||||
|
||||
// Restore background scrolling
|
||||
document.body.style.overflow = "";
|
||||
}
|
||||
|
||||
|
||||
// Generate icon HTML from Go icon type - matches templating/engine.go PageIcon function
|
||||
generateIconFromType(iconType) {
|
||||
switch (iconType) {
|
||||
@@ -314,24 +316,24 @@ export class SinglePageViewer extends HTMLElement {
|
||||
setupKeyboardNavigation() {
|
||||
// Remove any existing listener to avoid duplicates
|
||||
if (this.keyboardHandler) {
|
||||
document.removeEventListener('keydown', this.keyboardHandler);
|
||||
document.removeEventListener("keydown", this.keyboardHandler);
|
||||
}
|
||||
|
||||
// Create bound handler
|
||||
this.keyboardHandler = (event) => {
|
||||
// Only handle keyboard events when the viewer is visible
|
||||
if (this.style.display === 'none') return;
|
||||
if (this.style.display === "none") return;
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
case "ArrowLeft":
|
||||
event.preventDefault();
|
||||
this.goToPreviousPage();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
case "ArrowRight":
|
||||
event.preventDefault();
|
||||
this.goToNextPage();
|
||||
break;
|
||||
case 'Escape':
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
this.close();
|
||||
break;
|
||||
@@ -339,10 +341,9 @@ export class SinglePageViewer extends HTMLElement {
|
||||
};
|
||||
|
||||
// Add event listener
|
||||
document.addEventListener('keydown', this.keyboardHandler);
|
||||
document.addEventListener("keydown", this.keyboardHandler);
|
||||
}
|
||||
|
||||
|
||||
// Share current page
|
||||
shareCurrentPage() {
|
||||
if (typeof copyPagePermalink === "function") {
|
||||
@@ -411,11 +412,12 @@ export class SinglePageViewer extends HTMLElement {
|
||||
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])";
|
||||
}
|
||||
containerSelector = ".newspaper-page-container";
|
||||
// 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(
|
||||
@@ -433,8 +435,7 @@ export class SinglePageViewer extends HTMLElement {
|
||||
console.log("Container page:", pageAttr, "parsed:", pageNum);
|
||||
return pageNum;
|
||||
})
|
||||
.filter((p) => p !== null)
|
||||
.sort((a, b) => a - b);
|
||||
.filter((p) => p !== null);
|
||||
|
||||
console.log("All pages found:", allPages);
|
||||
console.log("Current page:", this.currentPageNumber);
|
||||
@@ -535,9 +536,11 @@ export class SinglePageViewer extends HTMLElement {
|
||||
);
|
||||
|
||||
// Dispatch event for scrollspy to update highlighting
|
||||
document.dispatchEvent(new CustomEvent('singlepageviewer:pagechanged', {
|
||||
detail: { pageNumber: this.currentPageNumber, isBeilage: this.currentIsBeilage }
|
||||
}));
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("singlepageviewer:pagechanged", {
|
||||
detail: { pageNumber: this.currentPageNumber, isBeilage: this.currentIsBeilage },
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,29 +302,30 @@ body.page-edition::before {
|
||||
}
|
||||
|
||||
.single-page .newspaper-page-image {
|
||||
max-width: min(400px, 100%);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
height: 750px;
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Larger screens */
|
||||
@media (min-width: 1280px) {
|
||||
.single-page .newspaper-page-image {
|
||||
max-width: min(600px, 100%);
|
||||
height: 900px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Very wide screens */
|
||||
@media (min-width: 1536px) {
|
||||
.single-page .newspaper-page-image {
|
||||
max-width: min(700px, 100%);
|
||||
height: 1050px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile constraints */
|
||||
@media (max-width: 640px) {
|
||||
.single-page .newspaper-page-image {
|
||||
max-width: 100%;
|
||||
height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user