feat(ip-proxy): 添加 IP 代理面板的展开/收起功能及持久化状态支持
This commit is contained in:
+69
-19
@@ -14,6 +14,47 @@ const ipProxyActionState = {
|
||||
busy: false,
|
||||
action: '',
|
||||
};
|
||||
const IP_PROXY_SECTION_EXPANDED_STORAGE_KEY = 'multipage-ip-proxy-section-expanded';
|
||||
let ipProxySectionExpanded = false;
|
||||
|
||||
function readIpProxySectionExpanded() {
|
||||
try {
|
||||
return globalThis.localStorage?.getItem(IP_PROXY_SECTION_EXPANDED_STORAGE_KEY) === '1';
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function persistIpProxySectionExpanded(expanded) {
|
||||
try {
|
||||
if (expanded) {
|
||||
globalThis.localStorage?.setItem(IP_PROXY_SECTION_EXPANDED_STORAGE_KEY, '1');
|
||||
} else {
|
||||
globalThis.localStorage?.removeItem(IP_PROXY_SECTION_EXPANDED_STORAGE_KEY);
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore storage errors; the in-memory collapsed state is still enough for this session.
|
||||
}
|
||||
}
|
||||
|
||||
function setIpProxySectionExpanded(expanded) {
|
||||
ipProxySectionExpanded = Boolean(expanded);
|
||||
persistIpProxySectionExpanded(ipProxySectionExpanded);
|
||||
if (typeof updateIpProxyUI === 'function') {
|
||||
updateIpProxyUI(latestState);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleIpProxySectionExpanded() {
|
||||
setIpProxySectionExpanded(!ipProxySectionExpanded);
|
||||
}
|
||||
|
||||
function initIpProxySectionExpandedState() {
|
||||
ipProxySectionExpanded = readIpProxySectionExpanded();
|
||||
if (typeof updateIpProxyUI === 'function') {
|
||||
updateIpProxyUI(latestState);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeIpProxyActionType(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
@@ -1158,6 +1199,7 @@ function setIpProxyEnabledInlineStatus(state = {}, enabled = getSelectedIpProxyE
|
||||
|
||||
function updateIpProxyUI(state = latestState) {
|
||||
const enabled = getSelectedIpProxyEnabled();
|
||||
const showSettings = enabled && ipProxySectionExpanded;
|
||||
const mode = getSelectedIpProxyMode();
|
||||
const service = normalizeIpProxyService(selectIpProxyService?.value || state?.ipProxyService || DEFAULT_IP_PROXY_SERVICE);
|
||||
const apiModeAvailable = isIpProxyApiModeAvailable();
|
||||
@@ -1175,59 +1217,67 @@ function updateIpProxyUI(state = latestState) {
|
||||
if (rowIpProxyEnabled) {
|
||||
rowIpProxyEnabled.style.display = '';
|
||||
}
|
||||
if (btnToggleIpProxySection) {
|
||||
btnToggleIpProxySection.disabled = !enabled;
|
||||
btnToggleIpProxySection.textContent = showSettings ? '收起设置' : '展开设置';
|
||||
btnToggleIpProxySection.title = enabled
|
||||
? (showSettings ? '收起 IP 代理设置' : '展开 IP 代理设置')
|
||||
: '开启 IP 代理后可展开设置';
|
||||
btnToggleIpProxySection.setAttribute('aria-expanded', String(showSettings));
|
||||
}
|
||||
if (rowIpProxyFold) {
|
||||
rowIpProxyFold.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyFold.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyService) {
|
||||
rowIpProxyService.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyService.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyMode) {
|
||||
rowIpProxyMode.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyMode.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyLayout) {
|
||||
rowIpProxyLayout.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyLayout.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyApiUrl) {
|
||||
rowIpProxyApiUrl.style.display = enabled && apiModeAvailable && isApiMode ? '' : 'none';
|
||||
rowIpProxyApiUrl.style.display = showSettings && apiModeAvailable && isApiMode ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyAccountList) {
|
||||
rowIpProxyAccountList.style.display = enabled && isAccountMode && accountListAvailable ? '' : 'none';
|
||||
rowIpProxyAccountList.style.display = showSettings && isAccountMode && accountListAvailable ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyAccountSessionPrefix) {
|
||||
rowIpProxyAccountSessionPrefix.style.display = enabled && showSessionOptions ? '' : 'none';
|
||||
rowIpProxyAccountSessionPrefix.style.display = showSettings && showSessionOptions ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyAccountLifeMinutes) {
|
||||
rowIpProxyAccountLifeMinutes.style.display = enabled && showSessionOptions ? '' : 'none';
|
||||
rowIpProxyAccountLifeMinutes.style.display = showSettings && showSessionOptions ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyPoolTargetCount) {
|
||||
rowIpProxyPoolTargetCount.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyPoolTargetCount.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyHost) {
|
||||
rowIpProxyHost.style.display = enabled && isAccountMode ? '' : 'none';
|
||||
rowIpProxyHost.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyPort) {
|
||||
rowIpProxyPort.style.display = enabled && isAccountMode ? '' : 'none';
|
||||
rowIpProxyPort.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyProtocol) {
|
||||
rowIpProxyProtocol.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyProtocol.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyUsername) {
|
||||
rowIpProxyUsername.style.display = enabled && isAccountMode ? '' : 'none';
|
||||
rowIpProxyUsername.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyPassword) {
|
||||
rowIpProxyPassword.style.display = enabled && isAccountMode ? '' : 'none';
|
||||
rowIpProxyPassword.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyRegion) {
|
||||
rowIpProxyRegion.style.display = enabled && isAccountMode ? '' : 'none';
|
||||
rowIpProxyRegion.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyActions) {
|
||||
rowIpProxyActions.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyActions.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (ipProxyActionButtons) {
|
||||
ipProxyActionButtons.style.display = enabled ? '' : 'none';
|
||||
ipProxyActionButtons.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (rowIpProxyRuntimeStatus) {
|
||||
rowIpProxyRuntimeStatus.style.display = enabled ? '' : 'none';
|
||||
rowIpProxyRuntimeStatus.style.display = showSettings ? '' : 'none';
|
||||
}
|
||||
if (ipProxyLayout) {
|
||||
ipProxyLayout.classList.toggle('is-account-only', !apiModeAvailable);
|
||||
@@ -1254,7 +1304,7 @@ function updateIpProxyUI(state = latestState) {
|
||||
ipProxyApiPanel.classList.toggle('is-disabled', !apiModeAvailable);
|
||||
ipProxyApiPanel.setAttribute('aria-disabled', String(!apiModeAvailable));
|
||||
ipProxyApiPanel.hidden = !apiModeAvailable;
|
||||
ipProxyApiPanel.style.display = enabled && apiModeAvailable ? '' : 'none';
|
||||
ipProxyApiPanel.style.display = showSettings && apiModeAvailable ? '' : 'none';
|
||||
}
|
||||
if (inputIpProxyApiUrl) {
|
||||
inputIpProxyApiUrl.disabled = !enabled || !apiModeAvailable;
|
||||
|
||||
@@ -791,6 +791,11 @@ header {
|
||||
|
||||
.ip-proxy-header-actions {
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#btn-toggle-ip-proxy-section {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ip-proxy-fold {
|
||||
|
||||
@@ -454,6 +454,8 @@
|
||||
<span class="data-value">用于浏览器代理接管与出口切换</span>
|
||||
</div>
|
||||
<div id="row-ip-proxy-enabled" class="section-mini-actions ip-proxy-header-actions">
|
||||
<button id="btn-toggle-ip-proxy-section" class="btn btn-ghost btn-xs" type="button"
|
||||
aria-expanded="false" aria-controls="row-ip-proxy-fold">展开设置</button>
|
||||
<label class="toggle-switch" for="input-ip-proxy-enabled" title="启用或禁用 IP 代理接管">
|
||||
<input type="checkbox" id="input-ip-proxy-enabled" />
|
||||
<span class="toggle-switch-track" aria-hidden="true">
|
||||
|
||||
@@ -97,6 +97,7 @@ const rowSub2ApiDefaultProxy = document.getElementById('row-sub2api-default-prox
|
||||
const inputSub2ApiDefaultProxy = document.getElementById('input-sub2api-default-proxy');
|
||||
const rowIpProxyEnabled = document.getElementById('row-ip-proxy-enabled');
|
||||
const inputIpProxyEnabled = document.getElementById('input-ip-proxy-enabled');
|
||||
const btnToggleIpProxySection = document.getElementById('btn-toggle-ip-proxy-section');
|
||||
const ipProxyEnabledStatus = document.getElementById('ip-proxy-enabled-status');
|
||||
const ipProxyEnabledStatusDot = document.getElementById('ip-proxy-enabled-status-dot');
|
||||
const ipProxyEnabledStatusText = document.getElementById('ip-proxy-enabled-status-text');
|
||||
@@ -5210,6 +5211,12 @@ btnToggleIpProxyPassword?.addEventListener('click', () => {
|
||||
syncIpProxyPasswordToggleLabel();
|
||||
});
|
||||
|
||||
btnToggleIpProxySection?.addEventListener('click', () => {
|
||||
if (typeof toggleIpProxySectionExpanded === 'function') {
|
||||
toggleIpProxySectionExpanded();
|
||||
}
|
||||
});
|
||||
|
||||
btnMailLogin?.addEventListener('click', async () => {
|
||||
const config = getMailProviderLoginConfig();
|
||||
const loginUrl = getMailProviderLoginUrl();
|
||||
@@ -7033,6 +7040,9 @@ bindPasswordVisibilityToggles();
|
||||
initTheme();
|
||||
initHotmailListExpandedState();
|
||||
initMail2925ListExpandedState();
|
||||
if (typeof initIpProxySectionExpandedState === 'function') {
|
||||
initIpProxySectionExpandedState();
|
||||
}
|
||||
updateSaveButtonState();
|
||||
updateConfigMenuControls();
|
||||
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
|
||||
|
||||
@@ -7,6 +7,7 @@ const {
|
||||
} = require('../mail-provider-utils');
|
||||
|
||||
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
||||
const ipProxyPanelSource = fs.readFileSync('sidepanel/ip-proxy-panel.js', 'utf8');
|
||||
|
||||
function extractFunction(name) {
|
||||
const markers = [`async function ${name}(`, `function ${name}(`];
|
||||
@@ -74,6 +75,9 @@ test('sidepanel renders IP proxy as a standalone card after sms verification wit
|
||||
const cloudflareSectionIndex = html.indexOf('id="cloudflare-temp-email-section"');
|
||||
|
||||
assert.match(html, /id="ip-proxy-section" class="data-card ip-proxy-card"/);
|
||||
assert.match(html, /id="btn-toggle-ip-proxy-section"/);
|
||||
assert.match(html, /aria-controls="row-ip-proxy-fold"/);
|
||||
assert.match(html, />展开设置<\/button>/);
|
||||
assert.ok(phoneToggleIndex >= 0);
|
||||
assert.ok(ipProxySectionIndex > phoneToggleIndex);
|
||||
assert.ok(ipProxyToggleIndex > phoneToggleIndex);
|
||||
@@ -82,6 +86,16 @@ test('sidepanel renders IP proxy as a standalone card after sms verification wit
|
||||
assert.doesNotMatch(html, /id="row-ip-proxy-runtime-status"/);
|
||||
});
|
||||
|
||||
test('IP proxy standalone card supports persisted collapse control', () => {
|
||||
assert.match(ipProxyPanelSource, /IP_PROXY_SECTION_EXPANDED_STORAGE_KEY = 'multipage-ip-proxy-section-expanded'/);
|
||||
assert.match(ipProxyPanelSource, /let ipProxySectionExpanded = false/);
|
||||
assert.match(ipProxyPanelSource, /const showSettings = enabled && ipProxySectionExpanded/);
|
||||
assert.match(ipProxyPanelSource, /rowIpProxyFold\.style\.display = showSettings \? '' : 'none'/);
|
||||
assert.match(ipProxyPanelSource, /btnToggleIpProxySection\.setAttribute\('aria-expanded', String\(showSettings\)\)/);
|
||||
assert.match(sidepanelSource, /btnToggleIpProxySection\?\.addEventListener\('click', \(\) => \{\s*if \(typeof toggleIpProxySectionExpanded === 'function'\)/);
|
||||
assert.match(sidepanelSource, /initIpProxySectionExpandedState\(\)/);
|
||||
});
|
||||
|
||||
test('updatePhoneVerificationSettingsUI toggles HeroSMS rows from the sms switch', () => {
|
||||
const api = new Function(`
|
||||
const inputPhoneVerificationEnabled = { checked: false };
|
||||
|
||||
Reference in New Issue
Block a user