From 252f2416bcafe314945b5f70f6eac0dd8dbed334 Mon Sep 17 00:00:00 2001 From: HighImKevin Date: Mon, 19 Oct 2026 08:07:56 -0400 Subject: [PATCH] Add a version label and update reminder in the KefinTweaks configuration modal --- kefinTweaks-plugin.js | 91 ++++++++++++++++++++++++++++++++++++++-- scripts/configuration.js | 84 +++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 3 deletions(-) diff --git a/kefinTweaks-plugin.js b/kefinTweaks-plugin.js index ae1bab2..b7d66e7 100644 --- a/kefinTweaks-plugin.js +++ b/kefinTweaks-plugin.js @@ -777,14 +777,15 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`; window.open(releaseNotesUrl, '_blank', 'noopener'); } catch (_) { /* ignore */ } }; - + + const updateMessage = `KefinTweaks ${versionLabel} is available!`; const handle = toastFn( - `KefinTweaks ${versionLabel} is available! Release Notes`, + updateMessage, '0', false, { link: { - label: 'Release Notes', + label: updateMessage, onClick: openReleaseNotes }, buttons: [ @@ -914,6 +915,82 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`; } } + /** + * Human-readable installed version for config UI (tag, short SHA, branch, or Custom). + */ + function getInstalledVersionLabel(config) { + const cfg = config || window.KefinTweaksConfig; + const root = cfg?.kefinTweaksRootResolved || cfg?.kefinTweaksRoot || ''; + if (!root) return 'Unknown'; + + if (isSymbolicLatestRoot(root)) return 'latest'; + + const ref = parseGithubRootRef(root); + if (!ref) { + return isGithubJsdelivrKefinRoot(root) ? 'Unknown' : 'Custom'; + } + if (ref.kind === 'semver') return ref.value; + if (ref.kind === 'sha') return ref.value.slice(0, 7); + if (ref.kind === 'main' || ref.kind === 'experimental') return ref.kind; + return 'Custom'; + } + + /** + * Whether GitHub Latest is newer than the current pin (ignores dismiss state). + * @returns {Promise<{ available: boolean, latestTag: string }>} + */ + async function isUpdateAvailable(config) { + const cfg = config || window.KefinTweaksConfig; + const root = cfg?.kefinTweaksRoot; + if (!root || isSymbolicLatestRoot(root)) { + return { available: false, latestTag: '' }; + } + + let latest; + try { + latest = await fetchLatestGithubRelease(); + } catch (e) { + console.warn('[KefinTweaks Installer] isUpdateAvailable: fetch failed:', e); + return { available: false, latestTag: '' }; + } + if (!latest?.tag) return { available: false, latestTag: '' }; + + let available = false; + if (isGithubJsdelivrKefinRoot(root)) { + try { + available = await isGithubInstallBehindLatest(root, cfg, latest); + } catch (e) { + console.warn('[KefinTweaks Installer] isUpdateAvailable: compare failed:', e); + available = true; + } + } else { + available = true; + } + + return { available, latestTag: latest.tag }; + } + + /** + * Force-show the update toast (ignores updateState.dismissed). + */ + async function showUpdateAvailablePrompt(config, latestTag) { + const cfg = config || window.KefinTweaksConfig; + let tag = normalizeReleaseTag(latestTag); + if (!tag) { + try { + const latest = await fetchLatestGithubRelease(); + tag = latest?.tag || ''; + } catch (e) { + console.warn('[KefinTweaks Installer] showUpdateAvailablePrompt: fetch failed:', e); + } + } + if (!tag) { + console.warn('[KefinTweaks Installer] showUpdateAvailablePrompt: no latest tag'); + return; + } + await showPinnedVersionUpdateToast(cfg, tag); + } + function isKefinInjectorAttachedOnPage() { return !!( window.KefinTweaksInjectorAttached @@ -2102,6 +2179,14 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`; // Make function globally accessible window.openKefinTweaksSourceModal = openKefinTweaksSourceModal; + window.KefinTweaksInstaller = window.KefinTweaksInstaller || {}; + window.KefinTweaksInstaller.getInstalledVersionLabel = getInstalledVersionLabel; + window.KefinTweaksInstaller.isUpdateAvailable = isUpdateAvailable; + window.KefinTweaksInstaller.showUpdateAvailablePrompt = showUpdateAvailablePrompt; + // Also expose flat helpers for simple call sites + window.getKefinTweaksInstalledVersionLabel = getInstalledVersionLabel; + window.isKefinTweaksUpdateAvailable = isUpdateAvailable; + window.showKefinTweaksUpdateAvailablePrompt = showUpdateAvailablePrompt; // Prevent concurrent card inserts (async config fetch races past DOM checks) let pluginCardAddInProgress = false; diff --git a/scripts/configuration.js b/scripts/configuration.js index 7594bee..3327380 100644 --- a/scripts/configuration.js +++ b/scripts/configuration.js @@ -676,6 +676,43 @@ margin: 0 auto; padding: 1.5em; } + + #${MODAL_ID} .kefin-config-version-cluster { + margin-left: auto; + display: inline-flex; + align-items: center; + gap: 0.6em; + flex-wrap: wrap; + } + + #${MODAL_ID} .kefin-config-version-label { + margin: 0; + opacity: 0.75; + font-size: 0.95em; + white-space: nowrap; + } + + #${MODAL_ID} .kefin-config-update-badge { + display: none; + padding: 0.35em 0.85em; + font-size: 0.85em; + font-weight: 600; + border-radius: 999px; + border: 1px solid rgba(82, 196, 26, 0.55); + background: rgba(82, 196, 26, 0.18); + color: inherit; + cursor: pointer; + white-space: nowrap; + } + + #${MODAL_ID} .kefin-config-update-badge.is-visible { + display: inline-flex; + align-items: center; + } + + #${MODAL_ID} .kefin-config-update-badge:hover { + background: rgba(82, 196, 26, 0.28); + } #${MODAL_ID} .paperList { background: rgba(255, 255, 255, 0.03); @@ -873,6 +910,11 @@ const scripts = config.scripts || {}; const isEnabled = window.KefinTweaksConfig?.enabled !== false; + const versionLabelFn = window.KefinTweaksInstaller?.getInstalledVersionLabel + || window.getKefinTweaksInstalledVersionLabel; + const installedVersion = (typeof versionLabelFn === 'function' + ? versionLabelFn(window.KefinTweaksConfig) + : '') || 'Unknown'; return `
@@ -888,6 +930,12 @@ +
+ ${installedVersion.replace(/ + +
@@ -2609,6 +2657,42 @@ }); } + // Version label + Update Available badge (force-opens update toast even if dismissed) + const updateBadge = modalInstance.dialogContent.querySelector('#kefinTweaksUpdateAvailableBadge'); + if (updateBadge) { + updateBadge.addEventListener('click', async () => { + const promptFn = window.KefinTweaksInstaller?.showUpdateAvailablePrompt + || window.showKefinTweaksUpdateAvailablePrompt; + if (typeof promptFn !== 'function') { + console.warn('[KefinTweaks Configuration] Update prompt API unavailable'); + return; + } + try { + updateBadge.disabled = true; + await promptFn(window.KefinTweaksConfig, updateBadge.dataset.latestTag || ''); + } catch (error) { + console.error('[KefinTweaks Configuration] Failed to open update prompt:', error); + } finally { + updateBadge.disabled = false; + } + }); + + (async () => { + const checkFn = window.KefinTweaksInstaller?.isUpdateAvailable + || window.isKefinTweaksUpdateAvailable; + if (typeof checkFn !== 'function') return; + try { + const result = await checkFn(window.KefinTweaksConfig); + if (!result?.available) return; + if (result.latestTag) updateBadge.dataset.latestTag = result.latestTag; + updateBadge.hidden = false; + updateBadge.classList.add('is-visible'); + } catch (error) { + console.warn('[KefinTweaks Configuration] Update availability check failed:', error); + } + })(); + } + // No validation needed for default skin dropdown - it only allows valid options // Helper function to prevent click propagation on checkboxes (so they don't toggle details)