initial commit 2

This commit is contained in:
2026-04-02 18:33:07 -07:00
parent fcf67bee82
commit 5883a664d0
3 changed files with 181 additions and 0 deletions

120
banner.js Normal file
View File

@@ -0,0 +1,120 @@
(function () {
// =========== README ==========
// Update the message below
// incrementversion DISMISSED_KEY
const DISMISSED_KEY = 'jf-banner-dismissed-v1';
const BANNER_ID = 'custom-announcement-banner';
// Edit these to change banner content:
const BANNER_HTML = `
<span class="banner-text">
<strong>News:</strong>
<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>
</span>
<button id="custom-banner-close" aria-label="Dismiss banner">&#x2715;</button>
`;
const BANNER_CSS = `
#custom-announcement-banner {
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: space-between;
background: #1c3a5c;
color: #e0e0e0;
font-size: 0.9em;
padding: 8px 16px;
box-sizing: border-box;
width: 100%;
z-index: 9999;
border-bottom: 1px solid #2d5a8e;
}
#custom-announcement-banner a {
color: #7cb8f0;
}
#custom-banner-close {
background: none;
border: none;
color: #e0e0e0;
cursor: pointer;
font-size: 1.1em;
padding: 0 4px;
flex-shrink: 0;
}
`;
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;
}
function isHomePage() {
const h = window.location.hash;
return h === '' || h === '#' || h === '#/home' || h === '#!'
|| h === '#!/' || h.startsWith('#/home') || h.startsWith('#!/home');
}
function injectBanner() {
if (document.getElementById(BANNER_ID)) return;
const banner = document.createElement('div');
banner.id = BANNER_ID;
banner.innerHTML = BANNER_HTML;
document.body.insertBefore(banner, document.body.firstChild);
document.getElementById('custom-banner-close').addEventListener('click', function () {
sessionStorage.setItem(DISMISSED_KEY, '1');
document.getElementById(BANNER_ID).remove();
applyOffset();
});
applyOffset();
}
function removeBanner() {
const el = document.getElementById(BANNER_ID);
if (el) el.remove();
applyOffset();
}
function update() {
if (sessionStorage.getItem(DISMISSED_KEY)) { removeBanner(); return; }
if (isHomePage()) { injectBanner(); } else { removeBanner(); }
}
// Inject CSS once
const style = document.createElement('style');
style.textContent = BANNER_CSS;
document.head.appendChild(style);
// React to SPA navigation
window.addEventListener('hashchange', update);
// 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();
});
document.addEventListener('DOMContentLoaded', function () {
observer.observe(document.body, { childList: true, subtree: true });
update();
});
// Fallback: run immediately if DOMContentLoaded already fired
if (document.readyState !== 'loading') update();
})();

51
inject-banner.sh Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
set -euo pipefail
CUSTOM_DIR="/srv/jellyfin/custom"
WEB_DIR="/usr/share/jellyfin/web"
SRC_JS="${CUSTOM_DIR}/banner.js"
INDEX="${WEB_DIR}/index.html"
if [[ ! -f "${SRC_JS}" ]]; then
echo "jellyfin-inject-banner: ${SRC_JS} not found, skipping." >&2
exit 0
fi
python3 - "${INDEX}" "${SRC_JS}" <<'EOF'
import sys
index_path, js_path = sys.argv[1], sys.argv[2]
with open(index_path, 'r') as f:
html = f.read()
with open(js_path, 'r') as f:
js = f.read()
# Remove any previous injection
import re
html = re.sub(r'<script id="custom-banner-script">.*?</script>', '', html, flags=re.DOTALL)
# Inject before </body>
tag = f'<script id="custom-banner-script">{js}</script>'
html = html.replace('</body>', tag + '</body>', 1)
with open(index_path, 'w') as f:
f.write(html)
print("jellyfin-inject-banner: inlined banner script into " + index_path)
EOF
# Replace the logo image (filename contains a content hash that changes on updates)
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
else
echo "jellyfin-inject-banner: ${LOGO_SRC} not found, skipping logo." >&2
fi

10
jellyfin-banner.hook Normal file
View File

@@ -0,0 +1,10 @@
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = jellyfin-web
[Action]
Description = Injecting custom banner into Jellyfin web UI...
When = PostTransaction
Exec = /srv/jellyfin/custom/inject-banner.sh