Compare commits

...
8 Commits
Author SHA1 Message Date
kdee a43e009b92 logo fix 7 2026-09-23 13:17:14 -07:00
kdee ee090e7a4b logo fix 6 2026-09-23 13:07:21 -07:00
kdee bde94dde1c logo fix 5 2026-09-23 13:05:01 -07:00
kdee 80c6587f3b logo fix 4 2026-09-23 13:02:06 -07:00
kdee 2672f8d86d logo fix 3 2026-09-23 12:56:58 -07:00
kdee 8da0d884ed console fix 1 2026-09-23 12:51:21 -07:00
kdee 69f113891f logo fix 2 2026-09-23 12:49:32 -07:00
kdee 3674f07716 logo fix attempt 1 2026-09-23 12:45:11 -07:00
2 changed files with 90 additions and 13 deletions

No files matched your search

+77 -5
View File
@@ -4,6 +4,7 @@
// incrementversion DISMISSED_KEY
const DISMISSED_KEY = 'jf-banner-dismissed-v1';
const BANNER_ID = 'custom-announcement-banner';
const SCRIPT_START = Date.now();
// Edit these to change banner content:
const BANNER_HTML = `
@@ -100,7 +101,10 @@
offsetElements.add(el);
});
if (offsetPx > 0 && candidates.size === 0) {
// Jellyfin's SPA mounts its header asynchronously after DOMContentLoaded,
// so an empty result in the first few seconds is expected, not a bug.
// Only warn once things have had time to settle.
if (offsetPx > 0 && candidates.size === 0 && Date.now() - SCRIPT_START > 4000) {
console.warn('[custom-banner] No fixed top navigation elements detected to push down; banner may overlap the menu.');
}
}
@@ -111,6 +115,72 @@
pushDownTopBars(offset);
}
// Hide the default server-name text next to the top-bar logo icon, leaving
// just the (now custom) logo image in place. "icon-transparent" matches the
// filename inject-banner.sh replaces for the top-bar logo; keep these two
// in sync if that ever changes again.
const LOGO_IMG_MATCH = 'icon-transparent';
const LOGO_MAX_SIZE = '44px'; // grow the icon now that the text next to it is gone; fixed
// px instead of em, since em resolves against whatever
// (possibly tiny/zeroed) font-size the icon wrapper uses
function hideLogoText() {
const logoImg = document.querySelector('img[src*="' + LOGO_IMG_MATCH + '"]');
if (!logoImg) return;
logoImg.style.setProperty('height', LOGO_MAX_SIZE, 'important');
logoImg.style.setProperty('max-height', LOGO_MAX_SIZE, 'important');
logoImg.style.setProperty('width', 'auto', 'important');
logoImg.style.setProperty('max-width', 'none', 'important');
// The image is usually wrapped in its own icon span, so the text label
// is a sibling of that wrapper, not of the <img> itself. Walk up to the
// whole clickable logo button/link and hide every child that isn't part
// of the branch containing the image.
const container = logoImg.closest('button, a, [role="button"]') || logoImg.parentElement;
if (!container) return;
// Strip any fixed size/overflow-clipping the icon wrapper itself might
// impose (MUI's startIcon span is normally sized for a small icon font),
// which would otherwise clip the enlarged image regardless of its own size.
const iconWrap = logoImg.parentElement;
if (iconWrap && iconWrap !== container) {
iconWrap.style.setProperty('overflow', 'visible', 'important');
iconWrap.style.setProperty('height', 'auto', 'important');
iconWrap.style.setProperty('width', 'auto', 'important');
iconWrap.style.setProperty('max-height', 'none', 'important');
iconWrap.style.setProperty('max-width', 'none', 'important');
// MUI's startIcon wrapper carries baked-in negative-left/positive-right
// margin (compensating for icon-font glyphs with their own internal
// padding); left over, it shifts the image into the container's left
// padding and stacks extra space on the right. Zero it so the
// container's own symmetric padding is the only thing spacing it.
iconWrap.style.setProperty('margin', '0', 'important');
}
Array.prototype.forEach.call(container.childNodes, function (node) {
if (node === logoImg) return;
if (node.nodeType === Node.ELEMENT_NODE && node.contains(logoImg)) return;
if (node.nodeType === Node.TEXT_NODE) {
if (node.textContent.trim()) node.textContent = '';
} else if (node.nodeType === Node.ELEMENT_NODE && node.style.display !== 'none') {
node.style.display = 'none';
}
});
// The button was sized (min-width, padding) to fit the icon + label
// together; with the label gone, shrink it to fit just the icon instead
// of leaving the label's dead space behind. It's a flex item in the
// toolbar, so flex-grow/flex-basis would otherwise override the explicit
// width below and keep reserving the old footprint.
container.style.setProperty('flex', '0 0 auto', 'important');
container.style.setProperty('min-width', '0', 'important');
container.style.setProperty('width', 'fit-content', 'important');
// MUI buttons carry their own horizontal padding around the content
// (icon + label); the icon span itself now fits tightly (confirmed via
// devtools), so any remaining right-side gap is this padding.
container.style.setProperty('padding', '0 8px', 'important');
}
function isHomePage() {
const h = window.location.hash;
return h === '' || h === '#' || h === '#/home' || h === '#!'
@@ -160,22 +230,24 @@
debounceTimer = setTimeout(function () {
update();
applyOffset();
hideLogoText();
}, delay || 150);
}
// React to SPA navigation
window.addEventListener('hashchange', update);
window.addEventListener('hashchange', function () { update(); hideLogoText(); });
window.addEventListener('resize', function () { scheduleUpdate(); });
// 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.
// DOMContentLoaded) and re-run on DOM churn, since the nav bar (and the
// logo it contains) 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();
hideLogoText();
});
// Fallback: run immediately if DOMContentLoaded already fired
if (document.readyState !== 'loading') update();
if (document.readyState !== 'loading') { update(); hideLogoText(); }
})();
+13 -8
View File
@@ -36,16 +36,21 @@ with open(index_path, 'w') as f:
print("jellyfin-inject-banner: inlined banner script into " + index_path)
EOF
# Replace the logo image (filename contains a content hash that changes on updates)
# Replace the logo image(s) (filenames contain a content hash that changes on
# updates). "icon-transparent" is the top-bar logo as of the 12.x header
# redesign; "banner-light" is kept too since it's still used for the splash
# screen / PWA icons.
LOGO_SRC="${CUSTOM_DIR}/banner-light.png"
if [[ -f "${LOGO_SRC}" ]]; then
LOGO_DEST=$(ls "${WEB_DIR}"/banner-light.*.png 2>/dev/null | head -1)
if [[ -n "${LOGO_DEST}" ]]; then
cp "${LOGO_SRC}" "${LOGO_DEST}"
echo "jellyfin-inject-banner: replaced logo at ${LOGO_DEST}"
else
echo "jellyfin-inject-banner: no banner-light.*.png found in ${WEB_DIR}, skipping logo." >&2
fi
for LOGO_BASENAME in banner-light icon-transparent; do
LOGO_DEST=$(ls "${WEB_DIR}/${LOGO_BASENAME}".*.png 2>/dev/null | head -1)
if [[ -n "${LOGO_DEST}" ]]; then
cp "${LOGO_SRC}" "${LOGO_DEST}"
echo "jellyfin-inject-banner: replaced logo at ${LOGO_DEST}"
else
echo "jellyfin-inject-banner: no ${LOGO_BASENAME}.*.png found in ${WEB_DIR}, skipping." >&2
fi
done
else
echo "jellyfin-inject-banner: ${LOGO_SRC} not found, skipping logo." >&2
fi