From 75ad637ccdb261d5a2fd328dc3af845d390c701c Mon Sep 17 00:00:00 2001 From: QLHazyCoder Date: Sun, 3 May 2026 17:29:31 +0800 Subject: [PATCH] feat: allow ignoring current update prompt --- sidepanel/sidepanel.css | 7 ++++ sidepanel/sidepanel.html | 5 ++- sidepanel/sidepanel.js | 34 ++++++++++++++++ sidepanel/update-service.js | 53 ++++++++++++++++++++++++- tests/sidepanel-icloud-manager.test.js | 3 ++ tests/update-service.test.js | 54 ++++++++++++++++++++++++++ 6 files changed, 154 insertions(+), 2 deletions(-) diff --git a/sidepanel/sidepanel.css b/sidepanel/sidepanel.css index 5eb0f78..38943ff 100644 --- a/sidepanel/sidepanel.css +++ b/sidepanel/sidepanel.css @@ -511,6 +511,13 @@ header { gap: 4px; } +.update-card-actions { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 8px; +} + .update-card-version { font-size: 14px; font-weight: 700; diff --git a/sidepanel/sidepanel.html b/sidepanel/sidepanel.html index 79bc1dd..55f127b 100644 --- a/sidepanel/sidepanel.html +++ b/sidepanel/sidepanel.html @@ -98,7 +98,10 @@

- +
+ + +

一定请先导出配置,再执行更新

diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 70edcfb..526de92 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -31,6 +31,7 @@ const btnReleaseLog = document.getElementById('btn-release-log'); const updateCardVersion = document.getElementById('update-card-version'); const updateCardSummary = document.getElementById('update-card-summary'); const updateReleaseList = document.getElementById('update-release-list'); +const btnIgnoreRelease = document.getElementById('btn-ignore-release'); const btnOpenRelease = document.getElementById('btn-open-release'); const settingsCard = document.getElementById('settings-card'); const contributionModePanel = document.getElementById('contribution-mode-panel'); @@ -7071,6 +7072,24 @@ function openReleaseListPage() { openExternalUrl(getReleaseListUrl()); } +function ignoreCurrentReleaseUpdate() { + if (!sidepanelUpdateService?.ignoreReleaseSnapshot) { + return; + } + + const ignoredVersion = sidepanelUpdateService.ignoreReleaseSnapshot(currentReleaseSnapshot); + if (!ignoredVersion) { + return; + } + + renderReleaseSnapshot({ + ...currentReleaseSnapshot, + status: 'ignored', + ignoredVersion, + }); + showToast(`已忽略 ${ignoredVersion} 更新,有新版本时会再次提醒。`, 'info', 2200); +} + function openCloudflareTempEmailUsageGuidePage() { const targetUrl = getContributionPortalUrl(); if (!targetUrl) { @@ -7165,6 +7184,10 @@ function resetUpdateCard() { btnOpenRelease.hidden = true; btnOpenRelease.onclick = null; } + if (btnIgnoreRelease) { + btnIgnoreRelease.hidden = true; + btnIgnoreRelease.onclick = null; + } } function renderReleaseSnapshot(snapshot) { @@ -7212,6 +7235,17 @@ function renderReleaseSnapshot(snapshot) { btnOpenRelease.textContent = '前往更新'; btnOpenRelease.onclick = () => openExternalUrl(logUrl); } + if (btnIgnoreRelease) { + btnIgnoreRelease.hidden = false; + btnIgnoreRelease.onclick = ignoreCurrentReleaseUpdate; + } + break; + } + + case 'ignored': { + extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0'; + extensionUpdateStatus.classList.add('is-version-label'); + resetUpdateCard(); break; } diff --git a/sidepanel/update-service.js b/sidepanel/update-service.js index 44f637b..06bf5d0 100644 --- a/sidepanel/update-service.js +++ b/sidepanel/update-service.js @@ -4,6 +4,7 @@ const RELEASES_PAGE_URL = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases`; const RELEASES_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases?per_page=10`; const CACHE_KEY = 'multipage-release-snapshot-v1'; + const IGNORED_UPDATE_VERSION_KEY = 'multipage-ignored-release-version-v1'; const CACHE_TTL_MS = 60 * 60 * 1000; const FETCH_TIMEOUT_MS = 8000; const MAX_RELEASES = 10; @@ -257,6 +258,32 @@ } } + function getIgnoredUpdateVersion() { + try { + return String(localStorage.getItem(IGNORED_UPDATE_VERSION_KEY) || '').trim(); + } catch (error) { + return ''; + } + } + + function setIgnoredUpdateVersion(version) { + const normalized = formatDisplayVersion(version, VERSION_FAMILY_ULTRA) || String(version || '').trim(); + try { + if (normalized) { + localStorage.setItem(IGNORED_UPDATE_VERSION_KEY, normalized); + } else { + localStorage.removeItem(IGNORED_UPDATE_VERSION_KEY); + } + } catch (error) { + // Ignore localStorage failures. + } + return normalized; + } + + function clearIgnoredUpdateVersion() { + return setIgnoredUpdateVersion(''); + } + async function fetchReleases() { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); @@ -326,18 +353,39 @@ } const newerReleases = releases.filter((release) => compareVersions(release.displayVersion, localVersion) > 0); + const ignoredVersion = getIgnoredUpdateVersion(); + const newestUpdateVersion = newerReleases[0]?.displayVersion || ''; + const updateIgnored = Boolean(newestUpdateVersion) + && Boolean(ignoredVersion) + && compareVersions(ignoredVersion, newestUpdateVersion) === 0; return { - status: newerReleases.length > 0 ? 'update-available' : 'latest', + status: newerReleases.length > 0 + ? (updateIgnored ? 'ignored' : 'update-available') + : 'latest', localVersion, latestVersion: latestRelease.displayVersion, latestRelease, newerReleases, + ignoredVersion: updateIgnored ? ignoredVersion : '', logUrl: latestRelease.url || RELEASES_PAGE_URL, releasesPageUrl: RELEASES_PAGE_URL, checkedAt: Date.now(), }; } + function getSnapshotUpdateVersion(snapshot = {}) { + const newerReleases = Array.isArray(snapshot?.newerReleases) ? snapshot.newerReleases : []; + return newerReleases[0]?.displayVersion || snapshot?.latestVersion || ''; + } + + function ignoreReleaseSnapshot(snapshot = {}) { + const targetVersion = getSnapshotUpdateVersion(snapshot); + if (!targetVersion) { + return ''; + } + return setIgnoredUpdateVersion(targetVersion); + } + function getLocalVersionLabel(manifest = chrome.runtime.getManifest()) { const versionName = formatDisplayVersion(manifest?.version_name, VERSION_FAMILY_ULTRA); if (versionName) { @@ -389,8 +437,11 @@ compareVersions, formatDisplayVersion, formatReleaseDate, + clearIgnoredUpdateVersion, + getIgnoredUpdateVersion, getLocalVersionLabel, getReleaseSnapshot, + ignoreReleaseSnapshot, releasesPageUrl: RELEASES_PAGE_URL, stripVersionPrefix, }; diff --git a/tests/sidepanel-icloud-manager.test.js b/tests/sidepanel-icloud-manager.test.js index 7efff50..95dde23 100644 --- a/tests/sidepanel-icloud-manager.test.js +++ b/tests/sidepanel-icloud-manager.test.js @@ -23,6 +23,9 @@ test('update card highlights exporting config before upgrade', () => { const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8'); const css = fs.readFileSync('sidepanel/sidepanel.css', 'utf8'); + assert.match(html, /id="btn-ignore-release"/); + assert.match(html, /class="update-card-actions"/); + assert.match(css, /\.update-card-actions\s*\{/); assert.match(html, /

一定请先导出配置,再执行更新<\/p>/); assert.match(css, /\.update-card-reminder\s*\{/); assert.match(css, /font-weight:\s*700;/); diff --git a/tests/update-service.test.js b/tests/update-service.test.js index da6ad38..393b891 100644 --- a/tests/update-service.test.js +++ b/tests/update-service.test.js @@ -20,6 +20,9 @@ function createUpdateService(options = {}) { setItem(key, value) { cache.set(key, String(value)); }, + removeItem(key) { + cache.delete(key); + }, }; if (options.cachedSnapshot) { @@ -180,3 +183,54 @@ test('getReleaseSnapshot reorders cached releases before choosing latest version ['Ultra1.1'] ); }); + +test('getReleaseSnapshot suppresses an ignored latest update until a newer release appears', async () => { + let releases = [ + { + tag_name: 'Ultra1.1', + name: 'Ultra1.1', + html_url: 'https://example.com/Ultra1.1', + published_at: '2026-04-19T00:00:00.000Z', + body: '- current release', + draft: false, + prerelease: false, + }, + ]; + const { api } = createUpdateService({ + manifest: { + version: '1.0', + version_name: 'Ultra1.0', + }, + fetchImpl: async () => ({ + ok: true, + async json() { + return releases; + }, + }), + }); + + const firstSnapshot = await api.getReleaseSnapshot({ force: true }); + assert.equal(firstSnapshot.status, 'update-available'); + assert.equal(api.ignoreReleaseSnapshot(firstSnapshot), 'Ultra1.1'); + + const ignoredSnapshot = await api.getReleaseSnapshot({ force: true }); + assert.equal(ignoredSnapshot.status, 'ignored'); + assert.equal(ignoredSnapshot.ignoredVersion, 'Ultra1.1'); + + releases = [ + { + tag_name: 'Ultra1.2', + name: 'Ultra1.2', + html_url: 'https://example.com/Ultra1.2', + published_at: '2026-04-20T00:00:00.000Z', + body: '- next release', + draft: false, + prerelease: false, + }, + ...releases, + ]; + + const newerSnapshot = await api.getReleaseSnapshot({ force: true }); + assert.equal(newerSnapshot.status, 'update-available'); + assert.equal(newerSnapshot.latestVersion, 'Ultra1.2'); +});