From 25477964b860171af6a06f7ad583d411955b4941 Mon Sep 17 00:00:00 2001 From: QLHazyCoder <2825305047@qq.com> Date: Thu, 23 Apr 2026 10:53:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(contribution-update):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=B4=A1=E7=8C=AE=E6=9B=B4=E6=96=B0=E6=8F=90=E7=A4=BA=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=92=8C=E7=A1=AE=E8=AE=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sidepanel/sidepanel.js | 71 ++++++++++++++- ...sidepanel-auto-run-content-refresh.test.js | 36 ++++++-- ...sidepanel-contribution-update-hint.test.js | 88 +++++++++++++++++++ 3 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 tests/sidepanel-contribution-update-hint.test.js diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index fd4bfab..58f72ce 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -2369,11 +2369,70 @@ async function initializeReleaseInfo() { } function getContributionUpdateHintMessage(snapshot = currentContributionContentSnapshot) { - if (!snapshot?.promptVersion) { + const lines = getContributionUpdatePromptLines(snapshot); + if (!lines.length) { return ''; } + if (lines.length === 1) { + return lines[0]; + } + return lines.map((line, index) => `${index + 1}. ${line}`).join('\n'); +} - return '公告 / 使用教程有更新了,可点上方“贡献/使用”查看。'; +function getContributionUpdatePromptLines(snapshot = currentContributionContentSnapshot) { + if (!snapshot?.promptVersion) { + return []; + } + + const items = Array.isArray(snapshot.items) ? snapshot.items : []; + const hasAnnouncementOrTutorial = items.some((item) => + item + && item.isVisible + && ['announcement', 'tutorial'].includes(String(item.slug || '').trim().toLowerCase()) + ); + const hasQuestionnaire = items.some((item) => + item + && item.isVisible + && String(item.slug || '').trim().toLowerCase() === 'questionnaire' + ); + + const lines = []; + if (hasAnnouncementOrTutorial) { + lines.push('公告 / 使用教程有更新了,可点上方“贡献/使用”查看。'); + } + if (hasQuestionnaire) { + lines.push('有新的征求意见,请佬友共同参与选择。'); + } + return lines; +} + +function shouldPromptContributionUpdateBeforeAutoRun(snapshot = currentContributionContentSnapshot) { + const promptVersion = String(snapshot?.promptVersion || '').trim(); + if (!promptVersion) { + return false; + } + if (promptVersion === getDismissedContributionContentPromptVersion()) { + return false; + } + return getContributionUpdatePromptLines(snapshot).length > 0; +} + +async function maybeConfirmContributionUpdateBeforeAutoRun(snapshot = currentContributionContentSnapshot) { + if (!shouldPromptContributionUpdateBeforeAutoRun(snapshot)) { + return true; + } + + const confirmed = await openConfirmModal({ + title: '自动前提醒', + message: getContributionUpdateHintMessage(snapshot), + confirmLabel: '确定继续', + confirmVariant: 'btn-primary', + }); + if (!confirmed) { + return false; + } + dismissContributionUpdateHint(); + return true; } function positionContributionUpdateHint() { @@ -3982,12 +4041,18 @@ autoStartModal?.addEventListener('click', (event) => { btnAutoStartClose?.addEventListener('click', () => resolveModalChoice(null)); async function startAutoRunFromCurrentSettings() { + let contributionSnapshot = null; try { - await refreshContributionContentHint(); + contributionSnapshot = await refreshContributionContentHint(); } catch (error) { console.warn('Failed to refresh contribution content hint before auto run:', error); } + const confirmedContributionUpdate = await maybeConfirmContributionUpdateBeforeAutoRun(contributionSnapshot); + if (!confirmedContributionUpdate) { + return false; + } + const totalRuns = getRunCountValue(); let mode = 'restart'; const autoRunSkipFailures = inputAutoSkipFailures.checked; diff --git a/tests/sidepanel-auto-run-content-refresh.test.js b/tests/sidepanel-auto-run-content-refresh.test.js index d9af2df..c91cb51 100644 --- a/tests/sidepanel-auto-run-content-refresh.test.js +++ b/tests/sidepanel-auto-run-content-refresh.test.js @@ -48,7 +48,7 @@ function extractFunction(name) { return sidepanelSource.slice(start, end); } -function createApi({ refreshImpl } = {}) { +function createApi({ refreshImpl, confirmImpl, dismissImpl } = {}) { const bundle = extractFunction('startAutoRunFromCurrentSettings'); return new Function(` @@ -90,6 +90,14 @@ async function refreshContributionContentHint() { events.push({ type: 'refresh' }); ${refreshImpl ? 'return (' + refreshImpl + ')();' : 'return null;'} } +async function maybeConfirmContributionUpdateBeforeAutoRun(snapshot) { + events.push({ type: 'confirm-check', snapshot }); + ${confirmImpl ? 'return (' + confirmImpl + ')(snapshot);' : 'return true;'} +} +function dismissContributionUpdateHint() { + events.push({ type: 'dismiss-hint' }); + ${dismissImpl ? '(' + dismissImpl + ')();' : ''} +} ${bundle} return { startAutoRunFromCurrentSettings, @@ -108,9 +116,9 @@ test('startAutoRunFromCurrentSettings refreshes contribution content hint before assert.equal(result, true); assert.deepEqual( api.getEvents().map((entry) => entry.type), - ['refresh', 'send'] + ['refresh', 'confirm-check', 'send'] ); - assert.equal(api.getEvents()[1].message.type, 'AUTO_RUN'); + assert.equal(api.getEvents()[2].message.type, 'AUTO_RUN'); }); test('startAutoRunFromCurrentSettings continues auto run when contribution content refresh fails', async () => { @@ -124,8 +132,26 @@ test('startAutoRunFromCurrentSettings continues auto run when contribution conte assert.equal(result, true); assert.deepEqual( events.map((entry) => entry.type), - ['refresh', 'warn', 'send'] + ['refresh', 'warn', 'confirm-check', 'send'] ); assert.match(String(events[1].args[0]), /Failed to refresh contribution content hint before auto run/); - assert.equal(events[2].message.type, 'AUTO_RUN'); + assert.equal(events[3].message.type, 'AUTO_RUN'); +}); + +test('startAutoRunFromCurrentSettings aborts when contribution update confirmation is declined', async () => { + const api = createApi({ + refreshImpl: `async () => ({ + promptVersion: 'questionnaire:2026-04-23T00:00:00Z', + items: [{ slug: 'questionnaire', isVisible: true }], + })`, + confirmImpl: 'async () => false', + }); + + const result = await api.startAutoRunFromCurrentSettings(); + + assert.equal(result, false); + assert.deepEqual( + api.getEvents().map((entry) => entry.type), + ['refresh', 'confirm-check'] + ); }); diff --git a/tests/sidepanel-contribution-update-hint.test.js b/tests/sidepanel-contribution-update-hint.test.js new file mode 100644 index 0000000..6592dfa --- /dev/null +++ b/tests/sidepanel-contribution-update-hint.test.js @@ -0,0 +1,88 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8'); + +function extractFunction(name) { + const markers = [`async function ${name}(`, `function ${name}(`]; + const start = markers + .map((marker) => sidepanelSource.indexOf(marker)) + .find((index) => index >= 0); + if (start < 0) { + throw new Error(`missing function ${name}`); + } + + let parenDepth = 0; + let signatureEnded = false; + let braceStart = -1; + for (let i = start; i < sidepanelSource.length; i += 1) { + const ch = sidepanelSource[i]; + if (ch === '(') { + parenDepth += 1; + } else if (ch === ')') { + parenDepth -= 1; + if (parenDepth === 0) { + signatureEnded = true; + } + } else if (ch === '{' && signatureEnded) { + braceStart = i; + break; + } + } + + let depth = 0; + let end = braceStart; + for (; end < sidepanelSource.length; end += 1) { + const ch = sidepanelSource[end]; + if (ch === '{') depth += 1; + if (ch === '}') { + depth -= 1; + if (depth === 0) { + end += 1; + break; + } + } + } + + return sidepanelSource.slice(start, end); +} + +const helperBundle = [ + extractFunction('getContributionUpdatePromptLines'), + extractFunction('getContributionUpdateHintMessage'), +].join('\n'); + +const api = new Function(` +${helperBundle} +return { + getContributionUpdatePromptLines, + getContributionUpdateHintMessage, +}; +`)(); + +test('getContributionUpdateHintMessage numbers contribution updates when both content and questionnaire are visible', () => { + const message = api.getContributionUpdateHintMessage({ + promptVersion: 'announcement:2026-04-23T00:00:00Z|questionnaire:2026-04-23T00:00:01Z', + items: [ + { slug: 'announcement', isVisible: true }, + { slug: 'questionnaire', isVisible: true }, + ], + }); + + assert.equal( + message, + '1. 公告 / 使用教程有更新了,可点上方“贡献/使用”查看。\n2. 有新的征求意见,请佬友共同参与选择。' + ); +}); + +test('getContributionUpdateHintMessage returns questionnaire prompt alone when only questionnaire is updated', () => { + const message = api.getContributionUpdateHintMessage({ + promptVersion: 'questionnaire:2026-04-23T00:00:01Z', + items: [ + { slug: 'questionnaire', isVisible: true }, + ], + }); + + assert.equal(message, '有新的征求意见,请佬友共同参与选择。'); +});