feat: allow ignoring current update prompt
This commit is contained in:
@@ -511,6 +511,13 @@ header {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-card-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.update-card-version {
|
.update-card-version {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|||||||
@@ -98,7 +98,10 @@
|
|||||||
<div id="update-card-version" class="update-card-version"></div>
|
<div id="update-card-version" class="update-card-version"></div>
|
||||||
<p id="update-card-summary" class="update-card-summary"></p>
|
<p id="update-card-summary" class="update-card-summary"></p>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<p class="update-card-reminder">一定请先导出配置,再执行更新</p>
|
<p class="update-card-reminder">一定请先导出配置,再执行更新</p>
|
||||||
<div id="update-release-list" class="update-release-list"></div>
|
<div id="update-release-list" class="update-release-list"></div>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const btnReleaseLog = document.getElementById('btn-release-log');
|
|||||||
const updateCardVersion = document.getElementById('update-card-version');
|
const updateCardVersion = document.getElementById('update-card-version');
|
||||||
const updateCardSummary = document.getElementById('update-card-summary');
|
const updateCardSummary = document.getElementById('update-card-summary');
|
||||||
const updateReleaseList = document.getElementById('update-release-list');
|
const updateReleaseList = document.getElementById('update-release-list');
|
||||||
|
const btnIgnoreRelease = document.getElementById('btn-ignore-release');
|
||||||
const btnOpenRelease = document.getElementById('btn-open-release');
|
const btnOpenRelease = document.getElementById('btn-open-release');
|
||||||
const settingsCard = document.getElementById('settings-card');
|
const settingsCard = document.getElementById('settings-card');
|
||||||
const contributionModePanel = document.getElementById('contribution-mode-panel');
|
const contributionModePanel = document.getElementById('contribution-mode-panel');
|
||||||
@@ -7071,6 +7072,24 @@ function openReleaseListPage() {
|
|||||||
openExternalUrl(getReleaseListUrl());
|
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() {
|
function openCloudflareTempEmailUsageGuidePage() {
|
||||||
const targetUrl = getContributionPortalUrl();
|
const targetUrl = getContributionPortalUrl();
|
||||||
if (!targetUrl) {
|
if (!targetUrl) {
|
||||||
@@ -7165,6 +7184,10 @@ function resetUpdateCard() {
|
|||||||
btnOpenRelease.hidden = true;
|
btnOpenRelease.hidden = true;
|
||||||
btnOpenRelease.onclick = null;
|
btnOpenRelease.onclick = null;
|
||||||
}
|
}
|
||||||
|
if (btnIgnoreRelease) {
|
||||||
|
btnIgnoreRelease.hidden = true;
|
||||||
|
btnIgnoreRelease.onclick = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderReleaseSnapshot(snapshot) {
|
function renderReleaseSnapshot(snapshot) {
|
||||||
@@ -7212,6 +7235,17 @@ function renderReleaseSnapshot(snapshot) {
|
|||||||
btnOpenRelease.textContent = '前往更新';
|
btnOpenRelease.textContent = '前往更新';
|
||||||
btnOpenRelease.onclick = () => openExternalUrl(logUrl);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
const RELEASES_PAGE_URL = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases`;
|
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 RELEASES_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases?per_page=10`;
|
||||||
const CACHE_KEY = 'multipage-release-snapshot-v1';
|
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 CACHE_TTL_MS = 60 * 60 * 1000;
|
||||||
const FETCH_TIMEOUT_MS = 8000;
|
const FETCH_TIMEOUT_MS = 8000;
|
||||||
const MAX_RELEASES = 10;
|
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() {
|
async function fetchReleases() {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
||||||
@@ -326,18 +353,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newerReleases = releases.filter((release) => compareVersions(release.displayVersion, localVersion) > 0);
|
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 {
|
return {
|
||||||
status: newerReleases.length > 0 ? 'update-available' : 'latest',
|
status: newerReleases.length > 0
|
||||||
|
? (updateIgnored ? 'ignored' : 'update-available')
|
||||||
|
: 'latest',
|
||||||
localVersion,
|
localVersion,
|
||||||
latestVersion: latestRelease.displayVersion,
|
latestVersion: latestRelease.displayVersion,
|
||||||
latestRelease,
|
latestRelease,
|
||||||
newerReleases,
|
newerReleases,
|
||||||
|
ignoredVersion: updateIgnored ? ignoredVersion : '',
|
||||||
logUrl: latestRelease.url || RELEASES_PAGE_URL,
|
logUrl: latestRelease.url || RELEASES_PAGE_URL,
|
||||||
releasesPageUrl: RELEASES_PAGE_URL,
|
releasesPageUrl: RELEASES_PAGE_URL,
|
||||||
checkedAt: Date.now(),
|
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()) {
|
function getLocalVersionLabel(manifest = chrome.runtime.getManifest()) {
|
||||||
const versionName = formatDisplayVersion(manifest?.version_name, VERSION_FAMILY_ULTRA);
|
const versionName = formatDisplayVersion(manifest?.version_name, VERSION_FAMILY_ULTRA);
|
||||||
if (versionName) {
|
if (versionName) {
|
||||||
@@ -389,8 +437,11 @@
|
|||||||
compareVersions,
|
compareVersions,
|
||||||
formatDisplayVersion,
|
formatDisplayVersion,
|
||||||
formatReleaseDate,
|
formatReleaseDate,
|
||||||
|
clearIgnoredUpdateVersion,
|
||||||
|
getIgnoredUpdateVersion,
|
||||||
getLocalVersionLabel,
|
getLocalVersionLabel,
|
||||||
getReleaseSnapshot,
|
getReleaseSnapshot,
|
||||||
|
ignoreReleaseSnapshot,
|
||||||
releasesPageUrl: RELEASES_PAGE_URL,
|
releasesPageUrl: RELEASES_PAGE_URL,
|
||||||
stripVersionPrefix,
|
stripVersionPrefix,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ test('update card highlights exporting config before upgrade', () => {
|
|||||||
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
|
||||||
const css = fs.readFileSync('sidepanel/sidepanel.css', '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(html, /<p class="update-card-reminder">一定请先导出配置,再执行更新<\/p>/);
|
||||||
assert.match(css, /\.update-card-reminder\s*\{/);
|
assert.match(css, /\.update-card-reminder\s*\{/);
|
||||||
assert.match(css, /font-weight:\s*700;/);
|
assert.match(css, /font-weight:\s*700;/);
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ function createUpdateService(options = {}) {
|
|||||||
setItem(key, value) {
|
setItem(key, value) {
|
||||||
cache.set(key, String(value));
|
cache.set(key, String(value));
|
||||||
},
|
},
|
||||||
|
removeItem(key) {
|
||||||
|
cache.delete(key);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.cachedSnapshot) {
|
if (options.cachedSnapshot) {
|
||||||
@@ -180,3 +183,54 @@ test('getReleaseSnapshot reorders cached releases before choosing latest version
|
|||||||
['Ultra1.1']
|
['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');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user