feat: add step definitions module and integrate with sidepanel

- Introduced a new module for step definitions in `data/step-definitions.js` to manage shared step metadata.
- Updated `sidepanel.html` to dynamically render steps based on the new step definitions module.
- Refactored `sidepanel.js` to utilize the step definitions for rendering and managing step statuses.
- Enhanced tests to validate the step definitions module and its integration with the sidepanel.
- Cleaned up existing tests to ensure they align with the new structure and functionality.
This commit is contained in:
QLHazyCoder
2026-04-17 01:39:46 +08:00
parent a0d6d1f050
commit 3071f94f7c
19 changed files with 2877 additions and 1850 deletions
+2 -47
View File
@@ -526,53 +526,7 @@
<span class="section-label">流程</span>
<span id="steps-progress" class="steps-progress">0 / 9</span>
</div>
<div class="steps-list">
<div class="step-row" data-step="1">
<div class="step-indicator" data-step="1"><span class="step-num">1</span></div>
<button class="step-btn" data-step="1">打开 ChatGPT 官网</button>
<span class="step-status" data-step="1"></span>
</div>
<div class="step-row" data-step="2">
<div class="step-indicator" data-step="2"><span class="step-num">2</span></div>
<button class="step-btn" data-step="2">注册并输入邮箱</button>
<span class="step-status" data-step="2"></span>
</div>
<div class="step-row" data-step="3">
<div class="step-indicator" data-step="3"><span class="step-num">3</span></div>
<button class="step-btn" data-step="3">填写密码并继续</button>
<span class="step-status" data-step="3"></span>
</div>
<div class="step-row" data-step="4">
<div class="step-indicator" data-step="4"><span class="step-num">4</span></div>
<button class="step-btn" data-step="4">获取注册验证码</button>
<span class="step-status" data-step="4"></span>
</div>
<div class="step-row" data-step="5">
<div class="step-indicator" data-step="5"><span class="step-num">5</span></div>
<button class="step-btn" data-step="5">填写姓名和生日</button>
<span class="step-status" data-step="5"></span>
</div>
<div class="step-row" data-step="6">
<div class="step-indicator" data-step="6"><span class="step-num">6</span></div>
<button class="step-btn" data-step="6">刷新 OAuth 并登录</button>
<span class="step-status" data-step="6"></span>
</div>
<div class="step-row" data-step="7">
<div class="step-indicator" data-step="7"><span class="step-num">7</span></div>
<button class="step-btn" data-step="7">获取登录验证码</button>
<span class="step-status" data-step="7"></span>
</div>
<div class="step-row" data-step="8">
<div class="step-indicator" data-step="8"><span class="step-num">8</span></div>
<button class="step-btn" data-step="8">自动确认 OAuth</button>
<span class="step-status" data-step="8"></span>
</div>
<div class="step-row" data-step="9">
<div class="step-indicator" data-step="9"><span class="step-num">9</span></div>
<button class="step-btn" data-step="9">平台回调验证</button>
<span class="step-status" data-step="9"></span>
</div>
</div>
<div class="steps-list"></div>
</section>
<section id="log-section">
@@ -610,6 +564,7 @@
<script src="../icloud-utils.js"></script>
<script src="../hotmail-utils.js"></script>
<script src="../luckmail-utils.js"></script>
<script src="../data/step-definitions.js"></script>
<script src="update-service.js"></script>
<script src="hotmail-manager.js"></script>
<script src="icloud-manager.js"></script>
+92 -76
View File
@@ -172,18 +172,11 @@ const btnAutoStartCancel = document.getElementById('btn-auto-start-cancel');
const btnAutoStartRestart = document.getElementById('btn-auto-start-restart');
const btnAutoStartContinue = document.getElementById('btn-auto-start-continue');
const autoHintText = document.querySelector('.auto-hint');
const STEP_DEFAULT_STATUSES = {
1: 'pending',
2: 'pending',
3: 'pending',
4: 'pending',
5: 'pending',
6: 'pending',
7: 'pending',
8: 'pending',
9: 'pending',
};
const SKIPPABLE_STEPS = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]);
const stepDefinitions = (window.MultiPageStepDefinitions?.getSteps?.() || []).sort((left, right) => left.order - right.order);
const STEP_IDS = stepDefinitions.map((step) => Number(step.id)).filter(Number.isFinite);
const STEP_DEFAULT_STATUSES = Object.fromEntries(STEP_IDS.map((stepId) => [stepId, 'pending']));
const SKIPPABLE_STEPS = new Set(STEP_IDS);
const stepsList = document.querySelector('.steps-list');
const AUTO_DELAY_MIN_MINUTES = 1;
const AUTO_DELAY_MAX_MINUTES = 1440;
const AUTO_DELAY_DEFAULT_MINUTES = 30;
@@ -1455,6 +1448,22 @@ function initializeManualStepActions() {
});
}
function renderStepsList() {
if (!stepsList) return;
stepsList.innerHTML = stepDefinitions.map((step) => `
<div class="step-row" data-step="${step.id}" data-step-key="${escapeHtml(step.key)}">
<div class="step-indicator" data-step="${step.id}"><span class="step-num">${step.id}</span></div>
<button class="step-btn" data-step="${step.id}" data-step-key="${escapeHtml(step.key)}">${escapeHtml(step.title)}</button>
<span class="step-status" data-step="${step.id}"></span>
</div>
`).join('');
if (stepsProgress) {
stepsProgress.textContent = `0 / ${STEP_IDS.length}`;
}
}
// ============================================================
// State Restore on load
// ============================================================
@@ -2249,7 +2258,7 @@ function updatePanelModeUI() {
rowSub2ApiPassword.style.display = useSub2Api ? '' : 'none';
rowSub2ApiGroup.style.display = useSub2Api ? '' : 'none';
const step9Btn = document.querySelector('.step-btn[data-step="9"]');
const step9Btn = document.querySelector('.step-btn[data-step-key="platform-verify"]');
if (step9Btn) {
step9Btn.textContent = useSub2Api ? 'SUB2API 回调验证' : 'CPA 回调验证';
}
@@ -2282,7 +2291,7 @@ function updateStepUI(step, status) {
function updateProgressCounter() {
const completed = Object.values(getStepStatuses()).filter(isDoneStatus).length;
stepsProgress.textContent = `${completed} / 9`;
stepsProgress.textContent = `${completed} / ${STEP_IDS.length}`;
}
function updateButtonStates() {
@@ -2291,7 +2300,7 @@ function updateButtonStates() {
const autoLocked = isAutoRunLockedPhase();
const autoScheduled = isAutoRunScheduledPhase();
for (let step = 1; step <= 9; step++) {
for (const step of STEP_IDS) {
const btn = document.querySelector(`.step-btn[data-step="${step}"]`);
if (!btn) continue;
@@ -2300,16 +2309,20 @@ function updateButtonStates() {
} else if (step === 1) {
btn.disabled = false;
} else {
const prevStatus = statuses[step - 1];
const currentStatus = statuses[step];
btn.disabled = !(isDoneStatus(prevStatus) || currentStatus === 'failed' || isDoneStatus(currentStatus) || currentStatus === 'stopped');
}
const currentIndex = STEP_IDS.indexOf(step);
const prevStep = currentIndex > 0 ? STEP_IDS[currentIndex - 1] : null;
const prevStatus = prevStep === null ? 'completed' : statuses[prevStep];
const currentStatus = statuses[step];
btn.disabled = !(isDoneStatus(prevStatus) || currentStatus === 'failed' || isDoneStatus(currentStatus) || currentStatus === 'stopped');
}
}
document.querySelectorAll('.step-manual-btn').forEach((btn) => {
const step = Number(btn.dataset.step);
const currentStatus = statuses[step];
const prevStatus = statuses[step - 1];
const currentIndex = STEP_IDS.indexOf(step);
const prevStep = currentIndex > 0 ? STEP_IDS[currentIndex - 1] : null;
const prevStatus = prevStep === null ? 'completed' : statuses[prevStep];
if (!SKIPPABLE_STEPS.has(step) || anyRunning || autoLocked || autoScheduled || currentStatus === 'running' || isDoneStatus(currentStatus)) {
btn.style.display = 'none';
@@ -2318,10 +2331,10 @@ function updateButtonStates() {
return;
}
if (step > 1 && !isDoneStatus(prevStatus)) {
if (prevStep !== null && !isDoneStatus(prevStatus)) {
btn.style.display = 'none';
btn.disabled = true;
btn.title = `请先完成步骤 ${step - 1}`;
btn.title = `请先完成步骤 ${prevStep}`;
return;
}
@@ -2674,6 +2687,7 @@ const resetLuckmailManager = luckmailManager?.reset
const bindLuckmailEvents = luckmailManager?.bindLuckmailEvents
|| (() => { });
bindLuckmailEvents();
renderStepsList();
async function exportSettingsFile() {
closeConfigMenu();
@@ -2831,67 +2845,69 @@ async function handleSkipStep(step) {
// Button Handlers
// ============================================================
document.querySelectorAll('.step-btn').forEach(btn => {
btn.addEventListener('click', async () => {
try {
const step = Number(btn.dataset.step);
if (!(await maybeTakeoverAutoRun(`执行步骤 ${step}`))) {
return;
stepsList?.addEventListener('click', async (event) => {
const btn = event.target.closest('.step-btn');
if (!btn) {
return;
}
try {
const step = Number(btn.dataset.step);
if (!(await maybeTakeoverAutoRun(`执行步骤 ${step}`))) {
return;
}
if (step === 3) {
if (inputPassword.value !== (latestState?.customPassword || '')) {
await chrome.runtime.sendMessage({
type: 'SAVE_SETTING',
source: 'sidepanel',
payload: { customPassword: inputPassword.value },
});
syncLatestState({ customPassword: inputPassword.value });
}
if (step === 3) {
if (inputPassword.value !== (latestState?.customPassword || '')) {
await chrome.runtime.sendMessage({
type: 'SAVE_SETTING',
source: 'sidepanel',
payload: { customPassword: inputPassword.value },
});
syncLatestState({ customPassword: inputPassword.value });
}
let email = inputEmail.value.trim();
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
if (response?.error) {
throw new Error(response.error);
}
} else if (usesGeneratedAliasMailProvider(selectMailProvider.value)) {
const emailPrefix = inputEmailPrefix.value.trim();
if (!emailPrefix) {
showToast(selectMailProvider.value === GMAIL_PROVIDER ? '请先填写 Gmail 原邮箱。' : '请先填写 2925 邮箱前缀。', 'warn');
return;
}
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, emailPrefix } });
if (response?.error) {
throw new Error(response.error);
}
} else {
let email = inputEmail.value.trim();
if (!email) {
if (isCustomMailProvider()) {
showToast('当前邮箱服务为自定义邮箱,请先填写注册邮箱后再执行第 3 步。', 'warn');
return;
}
try {
email = await fetchGeneratedEmail({ showFailureToast: false });
} catch (err) {
showToast(`自动获取失败:${err.message},请手动粘贴邮箱后重试。`, 'warn');
return;
}
}
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, email } });
if (response?.error) {
throw new Error(response.error);
}
}
} else {
let email = inputEmail.value.trim();
if (selectMailProvider.value === 'hotmail-api' || isLuckmailProvider()) {
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
if (response?.error) {
throw new Error(response.error);
}
} else if (usesGeneratedAliasMailProvider(selectMailProvider.value)) {
const emailPrefix = inputEmailPrefix.value.trim();
if (!emailPrefix) {
showToast(selectMailProvider.value === GMAIL_PROVIDER ? '请先填写 Gmail 原邮箱。' : '请先填写 2925 邮箱前缀。', 'warn');
return;
}
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, emailPrefix } });
if (response?.error) {
throw new Error(response.error);
}
} else {
let email = inputEmail.value.trim();
if (!email) {
if (isCustomMailProvider()) {
showToast('当前邮箱服务为自定义邮箱,请先填写注册邮箱后再执行第 3 步。', 'warn');
return;
}
try {
email = await fetchGeneratedEmail({ showFailureToast: false });
} catch (err) {
showToast(`自动获取失败:${err.message},请手动粘贴邮箱后重试。`, 'warn');
return;
}
}
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step, email } });
if (response?.error) {
throw new Error(response.error);
}
}
} else {
const response = await chrome.runtime.sendMessage({ type: 'EXECUTE_STEP', source: 'sidepanel', payload: { step } });
if (response?.error) {
throw new Error(response.error);
}
} catch (err) {
showToast(err.message, 'error');
}
});
} catch (err) {
showToast(err.message, 'error');
}
});
btnFetchEmail.addEventListener('click', async () => {