From 46044735c19ea943eaf2b9e96e5d4215fdc02002 Mon Sep 17 00:00:00 2001 From: sudipnext Date: Sun, 8 Mar 2026 20:19:32 +0545 Subject: [PATCH] feat: enhance update notification system with detailed changelog and message support --- electron/app/utils/update-checker.ts | 101 ++++++++++++++++++--------- electron/generate_update.js | 5 ++ electron/version.json | 1 + 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/electron/app/utils/update-checker.ts b/electron/app/utils/update-checker.ts index c2caf67d..1abddc63 100644 --- a/electron/app/utils/update-checker.ts +++ b/electron/app/utils/update-checker.ts @@ -32,6 +32,7 @@ function log(msg: string): void { interface VersionResponse { version: string; + message?: string; downloads: { linux: string; mac: string; @@ -91,7 +92,7 @@ async function fetchVersionInfo(): Promise { } /** Pending update to re-inject on navigation (production: React/Next.js may replace DOM). */ -let pendingUpdate: { version: string; downloadUrl: string } | null = null; +let pendingUpdate: { version: string; downloadUrl: string; message?: string } | null = null; /** * Schedules banner injection after INJECT_DELAY_MS so React/Next.js can mount first. @@ -100,79 +101,113 @@ let pendingUpdate: { version: string; downloadUrl: string } | null = null; function scheduleBannerInjection( win: BrowserWindow, version: string, - downloadUrl: string + downloadUrl: string, + message?: string ): void { - pendingUpdate = { version, downloadUrl }; + pendingUpdate = { version, downloadUrl, message }; setTimeout(() => { if (win.isDestroyed() || !pendingUpdate) return; log(`Injecting banner now`); - injectUpdateBanner(win, pendingUpdate.version, pendingUpdate.downloadUrl); + injectUpdateBanner(win, pendingUpdate.version, pendingUpdate.downloadUrl, pendingUpdate.message); }, INJECT_DELAY_MS); } +/** Escape HTML to prevent XSS; preserve newlines for display. */ +function escapeHtml(text: string): string { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/\n/g, "
"); +} + /** - * Injects a Cursor-style update banner at the bottom of the renderer window. - * Safe to call multiple times; a second call while the banner is visible is a no-op. + * Injects an update banner at the bottom, aligned with the app UI. + * Includes a "View details" overlay for changelog/message. */ function injectUpdateBanner( win: BrowserWindow, latest: string, downloadUrl: string, - releaseNotes?: string + message?: string ): void { - const notesHtml = releaseNotes - ? `${releaseNotes}` + const hasMessage = Boolean(message && message.trim()); + const safeMessage = hasMessage ? escapeHtml(message!.trim()) : ""; + const safeMessageJson = JSON.stringify(safeMessage); + const viewDetailsBtnHtml = hasMessage + ? '' : ""; const script = /* js */ ` (function () { if (document.getElementById('__presenton_update_banner__')) return; + const msgHtml = ${safeMessageJson}; + const banner = document.createElement('div'); banner.id = '__presenton_update_banner__'; banner.style.cssText = [ 'position:fixed', - 'bottom:0', - 'left:0', - 'right:0', - 'background:#1e1e2e', - 'color:#cdd6f4', + 'bottom:16px', + 'left:50%', + 'transform:translateX(-50%)', + 'max-width:min(560px,calc(100vw - 32px))', + 'width:100%', + 'background:rgba(255,255,255,0.95)', + 'backdrop-filter:blur(12px)', + '-webkit-backdrop-filter:blur(12px)', + 'color:#191919', 'display:flex', 'align-items:center', 'justify-content:space-between', - 'padding:10px 18px', + 'padding:12px 16px', 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif', 'font-size:13px', - 'z-index:2147483647', - 'border-top:1px solid #313244', - 'box-shadow:0 -4px 16px rgba(0,0,0,0.35)', + 'z-index:2147483646', + 'border:1px solid rgba(148,163,184,0.3)', + 'border-radius:12px', + 'box-shadow:0 4px 24px rgba(0,0,0,0.08)', 'gap:12px', ].join(';'); banner.innerHTML = \` - + - Presenton ${latest} is available + Presenton ${latest} is available — you have ${CURRENT_VERSION} - ${notesHtml}
- Download update - + ${viewDetailsBtnHtml} + Download update +
\`; document.body.appendChild(banner); + + if (msgHtml) { + const overlay = document.createElement('div'); + overlay.id = '__presenton_update_overlay__'; + overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.4);display:none;align-items:center;justify-content:center;z-index:2147483647;padding:24px;'; + overlay.onclick = function(e) { if (e.target === overlay) overlay.style.display = 'none'; }; + overlay.innerHTML = \` +
+
+

What's new in ${latest}

+ +
+
+
+ \`; + document.body.appendChild(overlay); + document.getElementById('__presenton_overlay_content__').innerHTML = msgHtml; + document.getElementById('__presenton_view_details_btn__').onclick = function() { + document.getElementById('__presenton_update_overlay__').style.display = 'flex'; + }; + } })(); `; @@ -202,7 +237,7 @@ async function checkForUpdatesWithRetry(win: BrowserWindow): Promise { if (newer) { const downloadUrl = getDownloadUrlForPlatform(data.downloads); log(`Injecting banner for ${data.version} (after ${INJECT_DELAY_MS}ms delay)`); - scheduleBannerInjection(win, data.version, downloadUrl); + scheduleBannerInjection(win, data.version, downloadUrl, data.message); } else { log("No update needed, skipping banner"); } @@ -230,7 +265,7 @@ export function startUpdateChecker(win: BrowserWindow): void { const onLoad = () => { if (pendingUpdate) { log("did-finish-load (navigation), re-injecting banner"); - scheduleBannerInjection(win, pendingUpdate.version, pendingUpdate.downloadUrl); + scheduleBannerInjection(win, pendingUpdate.version, pendingUpdate.downloadUrl, pendingUpdate.message); } else if (!hasRunCheck) { hasRunCheck = true; log(`did-finish-load fired, first poll in ${INITIAL_DELAY_MS / 1_000}s`); diff --git a/electron/generate_update.js b/electron/generate_update.js index 87e928a2..958c57ae 100644 --- a/electron/generate_update.js +++ b/electron/generate_update.js @@ -1,11 +1,16 @@ const fs = require("fs"); const pkg = JSON.parse(fs.readFileSync("package.json")); +let existing = {}; +try { + existing = JSON.parse(fs.readFileSync("version.json", "utf8")); +} catch (_) {} const version = pkg.version; const update = { version, + message: process.env.UPDATE_MESSAGE || existing.message || "", downloads: { linux: `https://github.com/presenton/presenton/releases/download/electron-v${version}/Presenton-${version}.deb`, mac: `https://github.com/presenton/presenton/releases/download/electron-v${version}/Presenton-${version}.dmg`, diff --git a/electron/version.json b/electron/version.json index 20020a9c..89ee48cc 100644 --- a/electron/version.json +++ b/electron/version.json @@ -1,5 +1,6 @@ { "version": "0.6.0-beta", + "message": "• Bug fixes and performance improvements\n• New template designs\n• Improved export quality", "downloads": { "linux": "https://github.com/presenton/presenton/releases/download/electron-v0.6.0-beta/Presenton-0.6.0-beta.deb", "mac": "https://github.com/presenton/presenton/releases/download/electron-v0.6.0-beta/Presenton-0.6.0-beta.dmg",