feat: allow ignoring current update prompt

This commit is contained in:
QLHazyCoder
2026-05-03 17:29:31 +08:00
parent 61e9b40d85
commit 75ad637ccd
6 changed files with 154 additions and 2 deletions
+7
View File
@@ -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;
+4 -1
View File
@@ -98,7 +98,10 @@
<div id="update-card-version" class="update-card-version"></div>
<p id="update-card-summary" class="update-card-summary"></p>
</div>
<button id="btn-open-release" class="btn btn-primary btn-sm" type="button">前往更新</button>
<div class="update-card-actions">
<button id="btn-ignore-release" class="btn btn-outline btn-sm" type="button" hidden>忽略本次更新</button>
<button id="btn-open-release" class="btn btn-primary btn-sm" type="button">前往更新</button>
</div>
</div>
<p class="update-card-reminder">一定请先导出配置,再执行更新</p>
<div id="update-release-list" class="update-release-list"></div>
+34
View File
@@ -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;
}
+52 -1
View File
@@ -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,
};
+3
View File
@@ -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 class="update-card-reminder">一定请先导出配置,再执行更新<\/p>/);
assert.match(css, /\.update-card-reminder\s*\{/);
assert.match(css, /font-weight:\s*700;/);
+54
View File
@@ -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');
});