mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-29 17:15:31 +00:00
scroll highlighting
This commit is contained in:
@@ -134,8 +134,8 @@ class H extends HTMLElement {
|
||||
document.documentElement.offsetHeight
|
||||
), o = window.innerHeight, s = n - o, a = s > 0 ? window.scrollY / s : 0, l = t.clientHeight, d = t.scrollHeight - l;
|
||||
if (d > 0) {
|
||||
const g = a * d, u = i.getBoundingClientRect(), p = t.getBoundingClientRect(), f = u.top - p.top + t.scrollTop, m = l / 2, L = f - m, y = 0.7, I = y * g + (1 - y) * L, x = Math.max(0, Math.min(d, I)), T = t.scrollTop;
|
||||
Math.abs(x - T) > 10 && t.scrollTo({
|
||||
const g = a * d, u = i.getBoundingClientRect(), p = t.getBoundingClientRect(), b = u.top - p.top + t.scrollTop, m = l / 2, I = b - m, y = 0.7, T = y * g + (1 - y) * I, x = Math.max(0, Math.min(d, T)), L = t.scrollTop;
|
||||
Math.abs(x - L) > 10 && t.scrollTo({
|
||||
top: x,
|
||||
behavior: "smooth"
|
||||
});
|
||||
@@ -289,13 +289,13 @@ class A extends HTMLElement {
|
||||
if (l)
|
||||
u = l;
|
||||
else {
|
||||
const f = this.getIssueContext(i);
|
||||
u = f ? `${f}, ${i}` : `${i}`;
|
||||
const b = this.getIssueContext(i);
|
||||
u = b ? `${b}, ${i}` : `${i}`;
|
||||
}
|
||||
if (d.innerHTML = u, o && i === o) {
|
||||
d.style.position = "relative";
|
||||
const f = d.querySelector(".target-page-dot");
|
||||
f && f.remove();
|
||||
const b = d.querySelector(".target-page-dot");
|
||||
b && b.remove();
|
||||
const m = document.createElement("span");
|
||||
m.className = "target-page-dot absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full z-10", m.title = "verlinkte Seite", d.appendChild(m);
|
||||
}
|
||||
@@ -580,7 +580,7 @@ class N extends HTMLElement {
|
||||
const n = i.target.getAttribute("data-page-container"), o = this.pageContainers.get(n);
|
||||
if (o) {
|
||||
const a = i.isIntersecting && i.intersectionRatio >= 0.5 || this.singlePageViewerActive ? "full" : "short";
|
||||
o.state !== a && (o.state = a, this.updateEntriesState(o));
|
||||
o.state !== a ? (o.state = a, this.updateEntriesState(o)) : a === "full" && i.isIntersecting && i.intersectionRatio >= 0.5 && this.scrollPageIntoInhaltsverzeichnis(o);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
@@ -611,13 +611,45 @@ class N extends HTMLElement {
|
||||
const i = e.container.getAttribute("data-page-container"), n = this.querySelector(`[data-page-number="${i}"]`);
|
||||
(a = n == null ? void 0 : n.closest(".page-entry")) == null || a.querySelector(".icon-container");
|
||||
const o = n == null ? void 0 : n.closest(".page-entry");
|
||||
o && (t ? (o.classList.add("!border-l-red-500"), o.classList.remove("border-slate-300")) : (o.classList.remove("!border-l-red-500"), o.classList.add("border-slate-300")));
|
||||
o && (t ? (o.classList.add("!border-l-red-500"), o.classList.remove("border-slate-300")) : (o.classList.remove("!border-l-red-500"), o.classList.add("border-slate-300")), t && this.scrollEntryIntoView(o));
|
||||
const s = document.querySelector(`[data-page="${i}"].page-indicator`);
|
||||
if (s) {
|
||||
const l = s.querySelectorAll("i:not(.text-slate-400)");
|
||||
t ? (s.classList.add("!bg-red-50", "!text-red-600"), l.forEach((c) => c.classList.add("!text-red-600"))) : (s.classList.remove("!bg-red-50", "!text-red-600"), l.forEach((c) => c.classList.remove("!text-red-600")));
|
||||
}
|
||||
}
|
||||
scrollEntryIntoView(e) {
|
||||
const t = document.querySelector(".overflow-y-auto");
|
||||
if (!t || !e)
|
||||
return;
|
||||
const i = t.querySelectorAll(".page-entry"), n = i.length > 0 && i[0] === e, o = i.length > 0 && i[i.length - 1] === e;
|
||||
if (n) {
|
||||
t.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (o) {
|
||||
t.scrollTo({
|
||||
top: t.scrollHeight,
|
||||
behavior: "smooth"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const s = t.getBoundingClientRect(), a = e.getBoundingClientRect();
|
||||
if (!(a.top >= s.top && a.bottom <= s.bottom)) {
|
||||
const c = t.scrollTop, d = a.top - s.top + c, g = s.height, u = a.height, p = d - (g - u) / 2;
|
||||
t.scrollTo({
|
||||
top: Math.max(0, p),
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
}
|
||||
scrollPageIntoInhaltsverzeichnis(e) {
|
||||
const t = e.container.getAttribute("data-page-container"), i = this.querySelector(`[data-page-number="${t}"]`), n = i == null ? void 0 : i.closest(".page-entry");
|
||||
n && this.scrollEntryIntoView(n);
|
||||
}
|
||||
setupSinglePageViewerDetection() {
|
||||
document.addEventListener("singlepageviewer:opened", this.boundHandleSinglePageViewer), document.addEventListener("singlepageviewer:closed", this.boundHandleSinglePageViewer), document.addEventListener("singlepageviewer:pagechanged", this.boundHandleSinglePageViewer), this.checkSinglePageViewerState();
|
||||
}
|
||||
@@ -664,7 +696,7 @@ function k() {
|
||||
document.getElementById("pageModal").classList.add("hidden");
|
||||
}
|
||||
function V() {
|
||||
if (window.pageObserver && (window.pageObserver.disconnect(), window.pageObserver = null), window.currentPageContainers = Array.from(document.querySelectorAll(".newspaper-page-container")), window.currentActiveIndex = 0, b(), document.querySelector(".newspaper-page-container")) {
|
||||
if (window.pageObserver && (window.pageObserver.disconnect(), window.pageObserver = null), window.currentPageContainers = Array.from(document.querySelectorAll(".newspaper-page-container")), window.currentActiveIndex = 0, f(), document.querySelector(".newspaper-page-container")) {
|
||||
let e = /* @__PURE__ */ new Set();
|
||||
window.pageObserver = new IntersectionObserver(
|
||||
(t) => {
|
||||
@@ -673,7 +705,7 @@ function V() {
|
||||
n !== -1 && (i.isIntersecting ? e.add(n) : e.delete(n));
|
||||
}), e.size > 0) {
|
||||
const n = Array.from(e).sort((o, s) => o - s)[0];
|
||||
n !== window.currentActiveIndex && (window.currentActiveIndex = n, b());
|
||||
n !== window.currentActiveIndex && (window.currentActiveIndex = n, f());
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -701,7 +733,7 @@ function $() {
|
||||
r === -1 && t > 0 && (r = t - 1), r >= 0 && (window.currentActiveIndex = r, window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
|
||||
block: "start"
|
||||
}), setTimeout(() => {
|
||||
b();
|
||||
f();
|
||||
}, 100));
|
||||
}
|
||||
}
|
||||
@@ -722,7 +754,7 @@ function M() {
|
||||
r === -1 && t < window.currentPageContainers.length - 1 && (r = t + 1), r >= 0 && r < window.currentPageContainers.length && (window.currentActiveIndex = r, window.currentPageContainers[window.currentActiveIndex].scrollIntoView({
|
||||
block: "start"
|
||||
}), setTimeout(() => {
|
||||
b();
|
||||
f();
|
||||
}, 100));
|
||||
}
|
||||
}
|
||||
@@ -733,10 +765,17 @@ function R() {
|
||||
block: "start"
|
||||
});
|
||||
} else {
|
||||
const e = document.querySelector('[class*="border-t-2 border-amber-200"]');
|
||||
e && e.scrollIntoView({
|
||||
block: "start"
|
||||
});
|
||||
const e = document.querySelector('[data-beilage="true"]');
|
||||
if (e)
|
||||
e.scrollIntoView({
|
||||
block: "start"
|
||||
});
|
||||
else {
|
||||
const t = document.querySelector(".bg-amber-50");
|
||||
t && t.scrollIntoView({
|
||||
block: "start"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function E() {
|
||||
@@ -752,7 +791,7 @@ function E() {
|
||||
}
|
||||
return !1;
|
||||
}
|
||||
function b() {
|
||||
function f() {
|
||||
const r = document.getElementById("prevPageBtn"), e = document.getElementById("nextPageBtn"), t = document.getElementById("beilageBtn");
|
||||
if (r && (r.style.display = "flex", window.currentActiveIndex <= 0 ? (r.disabled = !0, r.classList.add("opacity-50", "cursor-not-allowed"), r.classList.remove("hover:bg-gray-200")) : (r.disabled = !1, r.classList.remove("opacity-50", "cursor-not-allowed"), r.classList.add("hover:bg-gray-200"))), e && (e.style.display = "flex", window.currentActiveIndex >= window.currentPageContainers.length - 1 ? (e.disabled = !0, e.classList.add("opacity-50", "cursor-not-allowed"), e.classList.remove("hover:bg-gray-200")) : (e.disabled = !1, e.classList.remove("opacity-50", "cursor-not-allowed"), e.classList.add("hover:bg-gray-200"))), t) {
|
||||
const i = E(), n = t.querySelector("i");
|
||||
@@ -910,7 +949,7 @@ function D(r, e) {
|
||||
function P() {
|
||||
V(), window.addEventListener("scroll", function() {
|
||||
clearTimeout(window.scrollTimeout), window.scrollTimeout = setTimeout(() => {
|
||||
b();
|
||||
f();
|
||||
}, 50);
|
||||
}), document.addEventListener("keydown", function(r) {
|
||||
r.key === "Escape" && k();
|
||||
@@ -968,7 +1007,7 @@ window.shareCurrentPage = z;
|
||||
window.generateCitation = O;
|
||||
window.copyPagePermalink = K;
|
||||
window.generatePageCitation = D;
|
||||
function Z() {
|
||||
function W() {
|
||||
C(), w(), document.querySelector(".newspaper-page-container") && P();
|
||||
let r = function(t) {
|
||||
C(), w(), j(), setTimeout(() => {
|
||||
@@ -979,5 +1018,5 @@ function Z() {
|
||||
document.body.addEventListener("htmx:afterSettle", r), document.body.addEventListener("htmx:afterSettle", w), document.body.addEventListener("htmx:beforeRequest", e);
|
||||
}
|
||||
export {
|
||||
Z as setup
|
||||
W as setup
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -44,13 +44,12 @@
|
||||
</button>
|
||||
|
||||
{{ if $model.HasBeilageButton }}
|
||||
<button
|
||||
id="beilageBtn"
|
||||
onclick="scrollToBeilage()"
|
||||
class="w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-amber-100 hover:bg-amber-200 text-amber-700 hover:text-amber-800 border border-amber-300 transition-colors duration-200 flex items-center justify-center cursor-pointer"
|
||||
<a
|
||||
href="#beilage"
|
||||
class="w-14 h-10 lg:w-16 lg:h-12 px-2 py-1 bg-amber-100 hover:bg-amber-200 text-amber-700 hover:text-amber-800 border border-amber-300 transition-colors duration-200 flex items-center justify-center cursor-pointer no-underline"
|
||||
title="Zu Beilage">
|
||||
<i class="ri-attachment-line text-lg lg:text-xl"></i>
|
||||
</button>
|
||||
</a>
|
||||
{{ end }}
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{{ $pageCount := len $pages }}
|
||||
|
||||
<!-- Historical printing layout grid -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-2">
|
||||
{{ template "_historical_layout" (dict "pages" $pages "pageCount" $pageCount "isBeilage" false "targetPage" $.targetPage) }}
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -19,14 +19,14 @@
|
||||
{{ if $beilagePages }}
|
||||
<div class="mt-12 pt-8">
|
||||
<!-- Header for beilage -->
|
||||
<div class="flex items-center gap-3 mb-6 bg-amber-50 px-4 py-3 rounded border border-amber-200">
|
||||
<div id="beilage" class="flex items-center gap-3 mb-6 bg-amber-50 px-4 py-3 rounded border border-amber-200">
|
||||
<i class="ri-attachment-line text-2xl text-amber-600"></i>
|
||||
<h2 class="text-xl font-semibold text-slate-800">Beilage</h2>
|
||||
</div>
|
||||
{{ $pageCount := len $beilagePages }}
|
||||
|
||||
<!-- Historical printing layout grid for Beilage -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-2">
|
||||
{{ template "_historical_layout" (dict "pages" $beilagePages "pageCount" $pageCount "isBeilage" true "targetPage" $.targetPage) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -125,7 +125,7 @@
|
||||
{{ $idPrefix = "beilage-1-page" }}
|
||||
{{ end }}
|
||||
|
||||
<div class="newspaper-page-container" id="{{ $idPrefix }}-{{ $page.PageNumber }}" data-page-container="{{ $page.PageNumber }}" data-page-icon-type="{{ $page.PageIcon }}"{{ if $isBeilage }} data-beilage="true"{{ end }}>
|
||||
<div class="newspaper-page-container pt-4" id="{{ $idPrefix }}-{{ $page.PageNumber }}" data-page-container="{{ $page.PageNumber }}" data-page-icon-type="{{ $page.PageIcon }}"{{ if $isBeilage }} data-beilage="true"{{ end }}>
|
||||
<!-- Anchor for navigation -->
|
||||
{{ if $isBeilage }}
|
||||
<div id="beilage-{{ $page.PageNumber }}"></div>
|
||||
|
||||
@@ -67,9 +67,13 @@ export class InhaltsverzeichnisScrollspy extends HTMLElement {
|
||||
const shouldBeFullMode = entry.isIntersecting && entry.intersectionRatio >= 0.5;
|
||||
const newState = shouldBeFullMode || this.singlePageViewerActive ? 'full' : 'short';
|
||||
|
||||
if (pageData.state !== newState) {
|
||||
const stateChanged = pageData.state !== newState;
|
||||
if (stateChanged) {
|
||||
pageData.state = newState;
|
||||
this.updateEntriesState(pageData);
|
||||
} else if (newState === 'full' && entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
||||
// Page is becoming visible and should be highlighted - trigger scroll even if state didn't change
|
||||
this.scrollPageIntoInhaltsverzeichnis(pageData);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -142,6 +146,11 @@ export class InhaltsverzeichnisScrollspy extends HTMLElement {
|
||||
pageEntryContainer.classList.remove('!border-l-red-500');
|
||||
pageEntryContainer.classList.add('border-slate-300');
|
||||
}
|
||||
|
||||
// Always scroll highlighted entry into view when it becomes active
|
||||
if (highlight) {
|
||||
this.scrollEntryIntoView(pageEntryContainer);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Highlight in layout view (page indicator above image)
|
||||
@@ -160,6 +169,71 @@ export class InhaltsverzeichnisScrollspy extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
scrollEntryIntoView(pageEntryContainer) {
|
||||
// Find the scrollable Inhaltsverzeichnis container
|
||||
const scrollableContainer = document.querySelector('.overflow-y-auto');
|
||||
|
||||
if (!scrollableContainer || !pageEntryContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is the first or last page entry to handle edge cases
|
||||
const allPageEntries = scrollableContainer.querySelectorAll('.page-entry');
|
||||
const isFirstPage = allPageEntries.length > 0 && allPageEntries[0] === pageEntryContainer;
|
||||
const isLastPage = allPageEntries.length > 0 && allPageEntries[allPageEntries.length - 1] === pageEntryContainer;
|
||||
|
||||
if (isFirstPage) {
|
||||
// Scroll to the very top for the first page
|
||||
scrollableContainer.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLastPage) {
|
||||
// Scroll to the very bottom for the last page
|
||||
scrollableContainer.scrollTo({
|
||||
top: scrollableContainer.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Get container and element positions for middle pages
|
||||
const containerRect = scrollableContainer.getBoundingClientRect();
|
||||
const elementRect = pageEntryContainer.getBoundingClientRect();
|
||||
|
||||
// Check if element is already fully visible
|
||||
const isVisible = elementRect.top >= containerRect.top &&
|
||||
elementRect.bottom <= containerRect.bottom;
|
||||
|
||||
if (!isVisible) {
|
||||
// Calculate the scroll position to center the element in the container
|
||||
const containerScrollTop = scrollableContainer.scrollTop;
|
||||
const elementTop = elementRect.top - containerRect.top + containerScrollTop;
|
||||
const containerHeight = containerRect.height;
|
||||
const elementHeight = elementRect.height;
|
||||
|
||||
// Center the element in the container
|
||||
const scrollTo = elementTop - (containerHeight - elementHeight) / 2;
|
||||
|
||||
scrollableContainer.scrollTo({
|
||||
top: Math.max(0, scrollTo),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
scrollPageIntoInhaltsverzeichnis(pageData) {
|
||||
const pageNumber = pageData.container.getAttribute('data-page-container');
|
||||
const pageLink = this.querySelector(`[data-page-number="${pageNumber}"]`);
|
||||
const pageEntryContainer = pageLink?.closest('.page-entry');
|
||||
|
||||
if (pageEntryContainer) {
|
||||
this.scrollEntryIntoView(pageEntryContainer);
|
||||
}
|
||||
}
|
||||
|
||||
setupSinglePageViewerDetection() {
|
||||
// Listen for single page viewer events
|
||||
|
||||
@@ -229,12 +229,20 @@ export function scrollToBeilage() {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Go to first beilage container
|
||||
const beilageContainer = document.querySelector('[class*="border-t-2 border-amber-200"]');
|
||||
// Go to first beilage container - look for the first beilage page container
|
||||
const beilageContainer = document.querySelector('[data-beilage="true"]');
|
||||
if (beilageContainer) {
|
||||
beilageContainer.scrollIntoView({
|
||||
block: "start",
|
||||
});
|
||||
} else {
|
||||
// Fallback: try to find beilage header section
|
||||
const beilageHeader = document.querySelector('.bg-amber-50');
|
||||
if (beilageHeader) {
|
||||
beilageHeader.scrollIntoView({
|
||||
block: "start",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user