feat(hotmail): 添加 Hotmail 账号池的展开/收起功能及持久化状态支持

This commit is contained in:
QLHazyCoder
2026-04-28 23:37:10 +08:00
parent f399277047
commit 854d726cce
4 changed files with 164 additions and 60 deletions
+14
View File
@@ -798,6 +798,20 @@ header {
white-space: nowrap; white-space: nowrap;
} }
.section-collapse-body {
display: flex;
flex-direction: column;
gap: 9px;
}
.section-collapse-body[hidden] {
display: none;
}
#btn-toggle-hotmail-section {
white-space: nowrap;
}
.ip-proxy-fold { .ip-proxy-fold {
width: 100%; width: 100%;
border: none; border: none;
+4
View File
@@ -658,6 +658,8 @@
<span class="section-label">Hotmail 账号池</span> <span class="section-label">Hotmail 账号池</span>
</div> </div>
<div class="section-mini-actions"> <div class="section-mini-actions">
<button id="btn-toggle-hotmail-section" class="btn btn-ghost btn-xs" type="button"
aria-expanded="false" aria-controls="hotmail-section-body">展开设置</button>
<button id="btn-toggle-hotmail-form" class="btn btn-outline btn-xs" type="button" <button id="btn-toggle-hotmail-form" class="btn btn-outline btn-xs" type="button"
aria-expanded="false">添加账号</button> aria-expanded="false">添加账号</button>
<button id="btn-hotmail-usage-guide" class="btn btn-ghost btn-xs" type="button">使用教程</button> <button id="btn-hotmail-usage-guide" class="btn btn-ghost btn-xs" type="button">使用教程</button>
@@ -667,6 +669,7 @@
aria-expanded="false">展开列表</button> aria-expanded="false">展开列表</button>
</div> </div>
</div> </div>
<div id="hotmail-section-body" class="section-collapse-body" hidden>
<div class="data-row" id="row-hotmail-service-mode"> <div class="data-row" id="row-hotmail-service-mode">
<span class="data-label">接码模式</span> <span class="data-label">接码模式</span>
<div id="hotmail-service-mode-group" class="choice-group" role="group" aria-label="Hotmail 接码模式"> <div id="hotmail-service-mode-group" class="choice-group" role="group" aria-label="Hotmail 接码模式">
@@ -734,6 +737,7 @@
<div id="hotmail-accounts-list" class="hotmail-accounts-list"></div> <div id="hotmail-accounts-list" class="hotmail-accounts-list"></div>
</div> </div>
</div> </div>
</div>
<div id="mail2925-section" class="data-card hotmail-card" style="display:none;"> <div id="mail2925-section" class="data-card hotmail-card" style="display:none;">
<div class="section-mini-header"> <div class="section-mini-header">
<div class="section-mini-copy"> <div class="section-mini-copy">
+70
View File
@@ -190,6 +190,8 @@ const cloudflareTempEmailSection = document.getElementById('cloudflare-temp-emai
const btnCloudflareTempEmailUsageGuide = document.getElementById('btn-cloudflare-temp-email-usage-guide'); const btnCloudflareTempEmailUsageGuide = document.getElementById('btn-cloudflare-temp-email-usage-guide');
const btnCloudflareTempEmailGithub = document.getElementById('btn-cloudflare-temp-email-github'); const btnCloudflareTempEmailGithub = document.getElementById('btn-cloudflare-temp-email-github');
const hotmailSection = document.getElementById('hotmail-section'); const hotmailSection = document.getElementById('hotmail-section');
const btnToggleHotmailSection = document.getElementById('btn-toggle-hotmail-section');
const hotmailSectionBody = document.getElementById('hotmail-section-body');
const mail2925Section = document.getElementById('mail2925-section'); const mail2925Section = document.getElementById('mail2925-section');
const luckmailSection = document.getElementById('luckmail-section'); const luckmailSection = document.getElementById('luckmail-section');
const icloudSection = document.getElementById('icloud-section'); const icloudSection = document.getElementById('icloud-section');
@@ -372,6 +374,7 @@ const PLUS_CONTRIBUTION_ACCOUNT_CREDIT = 5;
const PLUS_CONTRIBUTION_DONATION_CREDIT = 20; const PLUS_CONTRIBUTION_DONATION_CREDIT = 20;
const HOTMAIL_SERVICE_MODE_REMOTE = 'remote'; const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
const HOTMAIL_SERVICE_MODE_LOCAL = 'local'; const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
const HOTMAIL_SECTION_EXPANDED_STORAGE_KEY = 'multipage-hotmail-section-expanded';
const ICLOUD_PROVIDER = 'icloud'; const ICLOUD_PROVIDER = 'icloud';
const GMAIL_PROVIDER = 'gmail'; const GMAIL_PROVIDER = 'gmail';
const GMAIL_ALIAS_GENERATOR = 'gmail-alias'; const GMAIL_ALIAS_GENERATOR = 'gmail-alias';
@@ -649,6 +652,7 @@ let settingsAutoSaveTimer = null;
let settingsSaveRevision = 0; let settingsSaveRevision = 0;
let cloudflareDomainEditMode = false; let cloudflareDomainEditMode = false;
let cloudflareTempEmailDomainEditMode = false; let cloudflareTempEmailDomainEditMode = false;
let hotmailSectionExpanded = false;
let modalChoiceResolver = null; let modalChoiceResolver = null;
let currentModalActions = []; let currentModalActions = [];
let modalResultBuilder = null; let modalResultBuilder = null;
@@ -2486,6 +2490,56 @@ function setHotmailServiceMode(mode) {
}); });
} }
function readHotmailSectionExpanded() {
try {
return localStorage.getItem(HOTMAIL_SECTION_EXPANDED_STORAGE_KEY) === '1';
} catch (err) {
return false;
}
}
function persistHotmailSectionExpanded(expanded) {
try {
if (expanded) {
localStorage.setItem(HOTMAIL_SECTION_EXPANDED_STORAGE_KEY, '1');
} else {
localStorage.removeItem(HOTMAIL_SECTION_EXPANDED_STORAGE_KEY);
}
} catch (err) {
// Keep the current session state even if storage is unavailable.
}
}
function updateHotmailSectionExpandedUI() {
const useHotmail = selectMailProvider?.value === 'hotmail-api';
const expanded = useHotmail && hotmailSectionExpanded;
if (hotmailSectionBody) {
hotmailSectionBody.hidden = !expanded;
}
if (btnToggleHotmailSection) {
btnToggleHotmailSection.textContent = expanded ? '收起设置' : '展开设置';
btnToggleHotmailSection.title = expanded ? '收起 Hotmail 账号池设置' : '展开 Hotmail 账号池设置';
btnToggleHotmailSection.setAttribute('aria-expanded', String(expanded));
}
}
function setHotmailSectionExpanded(expanded, options = {}) {
const { persist = true } = options;
hotmailSectionExpanded = Boolean(expanded);
updateHotmailSectionExpandedUI();
if (persist) {
persistHotmailSectionExpanded(hotmailSectionExpanded);
}
}
function toggleHotmailSectionExpanded() {
setHotmailSectionExpanded(!hotmailSectionExpanded);
}
function initHotmailSectionExpandedState() {
setHotmailSectionExpanded(readHotmailSectionExpanded(), { persist: false });
}
function updateAccountRunHistorySettingsUI() { function updateAccountRunHistorySettingsUI() {
if (!rowAccountRunHistoryHelperBaseUrl) { if (!rowAccountRunHistoryHelperBaseUrl) {
return; return;
@@ -4018,6 +4072,9 @@ function updateMailProviderUI() {
if (hotmailSection) { if (hotmailSection) {
hotmailSection.style.display = useHotmail ? '' : 'none'; hotmailSection.style.display = useHotmail ? '' : 'none';
} }
if (typeof updateHotmailSectionExpandedUI === 'function') {
updateHotmailSectionExpandedUI();
}
if (mail2925Section) { if (mail2925Section) {
mail2925Section.style.display = useMail2925AccountPool ? '' : 'none'; mail2925Section.style.display = useMail2925AccountPool ? '' : 'none';
} }
@@ -5217,6 +5274,18 @@ btnToggleIpProxySection?.addEventListener('click', () => {
} }
}); });
btnToggleHotmailSection?.addEventListener('click', () => {
toggleHotmailSectionExpanded();
});
btnToggleHotmailForm?.addEventListener('click', () => {
setHotmailSectionExpanded(true);
}, true);
btnToggleHotmailList?.addEventListener('click', () => {
setHotmailSectionExpanded(true);
}, true);
btnMailLogin?.addEventListener('click', async () => { btnMailLogin?.addEventListener('click', async () => {
const config = getMailProviderLoginConfig(); const config = getMailProviderLoginConfig();
const loginUrl = getMailProviderLoginUrl(); const loginUrl = getMailProviderLoginUrl();
@@ -7040,6 +7109,7 @@ bindPasswordVisibilityToggles();
initTheme(); initTheme();
initHotmailListExpandedState(); initHotmailListExpandedState();
initMail2925ListExpandedState(); initMail2925ListExpandedState();
initHotmailSectionExpandedState();
if (typeof initIpProxySectionExpandedState === 'function') { if (typeof initIpProxySectionExpandedState === 'function') {
initIpProxySectionExpandedState(); initIpProxySectionExpandedState();
} }
+16
View File
@@ -2,6 +2,8 @@ const test = require('node:test');
const assert = require('node:assert/strict'); const assert = require('node:assert/strict');
const fs = require('node:fs'); const fs = require('node:fs');
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
function createAccountPoolUiStub() { function createAccountPoolUiStub() {
return { return {
createAccountPoolFormController({ createAccountPoolFormController({
@@ -60,11 +62,25 @@ test('sidepanel loads hotmail manager before sidepanel bootstrap', () => {
test('sidepanel html contains collapsible hotmail form controls', () => { test('sidepanel html contains collapsible hotmail form controls', () => {
const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8'); const html = fs.readFileSync('sidepanel/sidepanel.html', 'utf8');
assert.match(html, /id="btn-toggle-hotmail-section"/);
assert.match(html, /aria-controls="hotmail-section-body"/);
assert.match(html, /id="hotmail-section-body" class="section-collapse-body" hidden/);
assert.match(html, /id="btn-toggle-hotmail-form"/); assert.match(html, /id="btn-toggle-hotmail-form"/);
assert.match(html, /id="hotmail-form-shell"/); assert.match(html, /id="hotmail-form-shell"/);
assert.match(html, /id="btn-import-hotmail-accounts"[^>]*>批量导入</); assert.match(html, /id="btn-import-hotmail-accounts"[^>]*>批量导入</);
}); });
test('sidepanel keeps hotmail account pool behind a persisted section collapse', () => {
assert.match(sidepanelSource, /HOTMAIL_SECTION_EXPANDED_STORAGE_KEY = 'multipage-hotmail-section-expanded'/);
assert.match(sidepanelSource, /let hotmailSectionExpanded = false/);
assert.match(sidepanelSource, /function updateHotmailSectionExpandedUI\(\)/);
assert.match(sidepanelSource, /hotmailSectionBody\.hidden = !expanded/);
assert.match(sidepanelSource, /btnToggleHotmailSection\.setAttribute\('aria-expanded', String\(expanded\)\)/);
assert.match(sidepanelSource, /btnToggleHotmailSection\?\.addEventListener\('click', \(\) => \{\s*toggleHotmailSectionExpanded\(\)/);
assert.match(sidepanelSource, /btnToggleHotmailForm\?\.addEventListener\('click', \(\) => \{\s*setHotmailSectionExpanded\(true\)/);
assert.match(sidepanelSource, /initHotmailSectionExpandedState\(\)/);
});
test('hotmail manager exposes a factory and renders empty state', () => { test('hotmail manager exposes a factory and renders empty state', () => {
const source = fs.readFileSync('sidepanel/hotmail-manager.js', 'utf8'); const source = fs.readFileSync('sidepanel/hotmail-manager.js', 'utf8');
const windowObject = { const windowObject = {