chore: auto-merge develop → main

Triggered by: d635635 fix(ui): update notification inline in header title area, no full-page banner
This commit is contained in:
github-actions[bot]
2026-05-04 05:43:20 +00:00
2 changed files with 65 additions and 34 deletions
+35
View File
@@ -135,6 +135,41 @@ body {
letter-spacing: 0.03em; letter-spacing: 0.03em;
} }
/* Update available indicator — replaces title content, same absolute centering */
.header-update-pill {
display: inline-flex;
align-items: center;
gap: 8px;
animation: header-update-pulse 2s ease-in-out infinite;
}
.header-update-pill span {
font-size: 0.85rem;
font-weight: 600;
opacity: 0.95;
white-space: nowrap;
}
.header-update-btn {
background: #fbbf24;
color: #1e293b;
border: none;
border-radius: 20px;
padding: 3px 12px;
font-size: 0.78rem;
font-weight: 800;
cursor: pointer;
letter-spacing: 0.02em;
flex-shrink: 0;
transition: background 0.15s, transform 0.1s;
}
.header-update-btn:active {
background: #f59e0b;
transform: scale(0.96);
}
@keyframes header-update-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.header-btn { .header-btn {
background: rgba(255,255,255,0.2); background: rgba(255,255,255,0.2);
border: none; border: none;
+30 -34
View File
@@ -81,9 +81,9 @@ function reportError(payload) {
const _loadedVersion = (document.querySelector('.header-version')?.textContent?.trim() || '').replace(/^v/, ''); const _loadedVersion = (document.querySelector('.header-version')?.textContent?.trim() || '').replace(/^v/, '');
function _checkWebappUpdate() { function _checkWebappUpdate() {
const STORAGE_KEY = '_evershelf_update_checked_at'; // last-checked timestamp const STORAGE_KEY = '_evershelf_update_checked_at';
const SEEN_KEY = '_evershelf_update_seen_ts'; // published_at of last-dismissed release const SEEN_KEY = '_evershelf_update_seen_ts';
const TTL_MS = 5 * 60 * 1000; // re-check every 5 min const TTL_MS = 5 * 60 * 1000;
const now = Date.now(); const now = Date.now();
const lastCheck = parseInt(localStorage.getItem(STORAGE_KEY) || '0', 10); const lastCheck = parseInt(localStorage.getItem(STORAGE_KEY) || '0', 10);
if (now - lastCheck < TTL_MS) return; if (now - lastCheck < TTL_MS) return;
@@ -93,9 +93,10 @@ function _checkWebappUpdate() {
.then(r => r.ok ? r.json() : null) .then(r => r.ok ? r.json() : null)
.then(data => { .then(data => {
if (!data) return; if (!data) return;
if (document.getElementById('_evershelf_update_banner')) return; // Already showing — don't stack
if (document.getElementById('_header_update_pill')) return;
// ── Check 1: server has a different version deployed since this page loaded ── // ── Check 1: server has a newer version deployed since this page loaded ──
const serverVer = (data.webapp_version || '').replace(/^v/, ''); const serverVer = (data.webapp_version || '').replace(/^v/, '');
const deployChanged = serverVer && _loadedVersion && serverVer !== _loadedVersion; const deployChanged = serverVer && _loadedVersion && serverVer !== _loadedVersion;
@@ -109,40 +110,35 @@ function _checkWebappUpdate() {
if (!deployChanged && !releaseNewer) return; if (!deployChanged && !releaseNewer) return;
const banner = document.createElement('div'); // ── Show update indicator inside the header title area ──
banner.id = '_evershelf_update_banner'; const titleEl = document.querySelector('.header-title');
banner.style.cssText = [ if (!titleEl) return;
'position:fixed;top:0;left:0;right:0;z-index:99999', const originalHTML = titleEl.innerHTML;
'background:#1e293b;color:#fbbf24',
'padding:10px 16px;font-size:13px',
'display:flex;align-items:center;justify-content:space-between',
'border-bottom:2px solid #fbbf24',
'box-shadow:0 2px 8px rgba(0,0,0,.4)',
].join(';');
let msgHtml, dismissAction; const versionLabel = deployChanged
? (serverVer ? `v${serverVer}` : 'Nuova versione')
: (latestTag ? `v${latestTag}` : 'Nuova versione');
let dismissAction;
if (deployChanged) { if (deployChanged) {
// Server files updated — simple reload prompt dismissAction = () => { titleEl.innerHTML = originalHTML; };
msgHtml = `<span>🔄 Nuova versione dell'app disponibile sul server.</span>`;
dismissAction = () => banner.remove(); // just hide, will reappear next check
} else { } else {
const releaseUrl = data.html_url || 'https://github.com/dadaloop82/EverShelf/releases/latest'; dismissAction = () => { localStorage.setItem(SEEN_KEY, publishedAt); titleEl.innerHTML = originalHTML; };
const versionText = /^\d+\.\d+/.test(latestTag) ? ` <strong>v${latestTag}</strong>` : '';
msgHtml = `<span>⬆️ EverShelf${versionText} disponibile. ` +
`<a href="${releaseUrl}" target="_blank" rel="noopener" ` +
`style="color:#64748b;font-size:0.9em;text-decoration:underline">novità</a></span>`;
dismissAction = () => { localStorage.setItem(SEEN_KEY, publishedAt); banner.remove(); };
} }
banner.innerHTML = msgHtml + titleEl.innerHTML =
`<button onclick="window.location.reload()" ` + `<span class="header-update-pill" id="_header_update_pill">` +
`style="background:#fbbf24;border:none;color:#1e293b;font-weight:700;padding:5px 14px;border-radius:6px;cursor:pointer;font-size:13px;margin:0 8px">Ricarica</button>` + `<span>⬆️ ${versionLabel}</span>` +
`<button id="_evershelf_banner_close" ` + `<button class="header-update-btn" onclick="window.location.reload()">Aggiorna</button>` +
`style="background:none;border:none;color:#94a3b8;font-size:18px;cursor:pointer;padding:0 4px">✕</button>`; `<button style="background:none;border:none;color:rgba(255,255,255,0.6);font-size:1rem;cursor:pointer;padding:0 2px;line-height:1" ` +
document.body.prepend(banner); `id="_header_update_close">✕</button>` +
document.getElementById('_evershelf_banner_close').onclick = dismissAction; `</span>`;
// Auto-dismiss after 30 s without marking as seen document.getElementById('_header_update_close').onclick = (e) => {
setTimeout(() => { if (banner.isConnected) banner.remove(); }, 30000); e.stopPropagation();
dismissAction();
};
// Auto-restore after 60 s without marking as seen
setTimeout(() => { if (document.getElementById('_header_update_pill')) dismissAction(); }, 60000);
}) })
.catch(() => {}); .catch(() => {});
} }