first attempt
This commit is contained in:
1 parent
5883a664d0
commit
36df765000
1 file changed
+75
-14
@@ -12,7 +12,7 @@
|
||||
<a href="https://jellyfin.org/downloads/clients/" target="_blank" rel="noopener">
|
||||
Get the official app for your device
|
||||
</a>
|
||||
|
|
||||
|
|
||||
<a href="https://google.com/" target="_blank" rel="noopener">
|
||||
Submit requests
|
||||
</a>
|
||||
@@ -51,15 +51,64 @@
|
||||
}
|
||||
`;
|
||||
|
||||
// Jellyfin's own nav chrome has moved before (left drawer -> top app bar in
|
||||
// 12.x) and will likely move again. Rather than hardcoding selectors that
|
||||
// break on every redesign, find whatever is fixed/sticky and anchored near
|
||||
// the top of the viewport at runtime, and push it down (Y) by the banner's
|
||||
// height so the banner never just sits in front of (Z) live nav chrome.
|
||||
const originalMarginTop = new WeakMap();
|
||||
const offsetElements = new Set();
|
||||
|
||||
function isTopBarCandidate(el) {
|
||||
if (el.id === BANNER_ID || el.closest('#' + BANNER_ID)) return false;
|
||||
const style = getComputedStyle(el);
|
||||
if (style.position !== 'fixed' && style.position !== 'sticky') return false;
|
||||
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) return false;
|
||||
const rect = el.getBoundingClientRect();
|
||||
if (rect.width < window.innerWidth * 0.5) return false; // must span most of the viewport width
|
||||
if (rect.height <= 0 || rect.height > window.innerHeight * 0.6) return false; // skip full-page overlays/modals
|
||||
if (rect.top > 400) return false; // must be anchored near the top of the page
|
||||
return true;
|
||||
}
|
||||
|
||||
function findTopBars() {
|
||||
return Array.prototype.filter.call(document.body.querySelectorAll('*'), isTopBarCandidate);
|
||||
}
|
||||
|
||||
function pushDownTopBars(offsetPx) {
|
||||
const candidates = new Set(findTopBars());
|
||||
|
||||
// Restore anything we previously offset that's no longer a candidate
|
||||
// (e.g. replaced by the SPA router on navigation).
|
||||
offsetElements.forEach(function (el) {
|
||||
if (!candidates.has(el) || !document.contains(el)) {
|
||||
if (originalMarginTop.has(el)) {
|
||||
el.style.marginTop = originalMarginTop.get(el);
|
||||
}
|
||||
offsetElements.delete(el);
|
||||
}
|
||||
});
|
||||
|
||||
candidates.forEach(function (el) {
|
||||
if (!originalMarginTop.has(el)) {
|
||||
originalMarginTop.set(el, el.style.marginTop || '');
|
||||
}
|
||||
const base = originalMarginTop.get(el);
|
||||
el.style.marginTop = offsetPx > 0
|
||||
? 'calc(' + (base || '0px') + ' + ' + offsetPx + 'px)'
|
||||
: base;
|
||||
offsetElements.add(el);
|
||||
});
|
||||
|
||||
if (offsetPx > 0 && candidates.size === 0) {
|
||||
console.warn('[custom-banner] No fixed top navigation elements detected to push down; banner may overlap the menu.');
|
||||
}
|
||||
}
|
||||
|
||||
function applyOffset() {
|
||||
const banner = document.getElementById(BANNER_ID);
|
||||
const offset = banner ? (banner.offsetHeight + 'px') : '';
|
||||
const header = document.querySelector('.headerTop');
|
||||
const drawer = document.querySelector('.mainDrawer');
|
||||
const sections = document.querySelector('.homeSectionsContainer');
|
||||
if (header) header.style.marginTop = offset;
|
||||
if (drawer) drawer.style.marginTop = offset;
|
||||
if (sections) sections.style.marginTop = offset;
|
||||
const offset = banner ? banner.offsetHeight : 0;
|
||||
pushDownTopBars(offset);
|
||||
}
|
||||
|
||||
function isHomePage() {
|
||||
@@ -101,15 +150,27 @@
|
||||
style.textContent = BANNER_CSS;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Debounce: the SPA's own DOM churn (list virtualization, image loads, etc.)
|
||||
// can fire many mutations per second. We don't need per-frame precision for
|
||||
// nav-bar layout, so coalesce bursts instead of rescanning the whole DOM
|
||||
// on every single mutation.
|
||||
let debounceTimer = null;
|
||||
function scheduleUpdate(delay) {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(function () {
|
||||
update();
|
||||
applyOffset();
|
||||
}, delay || 150);
|
||||
}
|
||||
|
||||
// React to SPA navigation
|
||||
window.addEventListener('hashchange', update);
|
||||
window.addEventListener('resize', function () { scheduleUpdate(); });
|
||||
|
||||
// Watch for Jellyfin's deferred body rendering (SPA bootstraps after DOMContentLoaded)
|
||||
// subtree: true catches .headerTop wherever it appears in the DOM
|
||||
const observer = new MutationObserver(function () {
|
||||
update();
|
||||
applyOffset();
|
||||
});
|
||||
// Watch for Jellyfin's deferred body rendering (SPA bootstraps after
|
||||
// DOMContentLoaded) and re-run on DOM churn, since the nav bar can be
|
||||
// unmounted/remounted by the SPA router.
|
||||
const observer = new MutationObserver(function () { scheduleUpdate(); });
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
update();
|
||||
|
||||
Reference in new issue
Block a user