feat(ip-proxy): 添加 IP 代理面板的展开/收起功能及持久化状态支持
This commit is contained in:
+69
-19
@@ -14,6 +14,47 @@ const ipProxyActionState = {
|
|||||||
busy: false,
|
busy: false,
|
||||||
action: '',
|
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 = '') {
|
function normalizeIpProxyActionType(value = '') {
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
const normalized = String(value || '').trim().toLowerCase();
|
||||||
@@ -1158,6 +1199,7 @@ function setIpProxyEnabledInlineStatus(state = {}, enabled = getSelectedIpProxyE
|
|||||||
|
|
||||||
function updateIpProxyUI(state = latestState) {
|
function updateIpProxyUI(state = latestState) {
|
||||||
const enabled = getSelectedIpProxyEnabled();
|
const enabled = getSelectedIpProxyEnabled();
|
||||||
|
const showSettings = enabled && ipProxySectionExpanded;
|
||||||
const mode = getSelectedIpProxyMode();
|
const mode = getSelectedIpProxyMode();
|
||||||
const service = normalizeIpProxyService(selectIpProxyService?.value || state?.ipProxyService || DEFAULT_IP_PROXY_SERVICE);
|
const service = normalizeIpProxyService(selectIpProxyService?.value || state?.ipProxyService || DEFAULT_IP_PROXY_SERVICE);
|
||||||
const apiModeAvailable = isIpProxyApiModeAvailable();
|
const apiModeAvailable = isIpProxyApiModeAvailable();
|
||||||
@@ -1175,59 +1217,67 @@ function updateIpProxyUI(state = latestState) {
|
|||||||
if (rowIpProxyEnabled) {
|
if (rowIpProxyEnabled) {
|
||||||
rowIpProxyEnabled.style.display = '';
|
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) {
|
if (rowIpProxyFold) {
|
||||||
rowIpProxyFold.style.display = enabled ? '' : 'none';
|
rowIpProxyFold.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyService) {
|
if (rowIpProxyService) {
|
||||||
rowIpProxyService.style.display = enabled ? '' : 'none';
|
rowIpProxyService.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyMode) {
|
if (rowIpProxyMode) {
|
||||||
rowIpProxyMode.style.display = enabled ? '' : 'none';
|
rowIpProxyMode.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyLayout) {
|
if (rowIpProxyLayout) {
|
||||||
rowIpProxyLayout.style.display = enabled ? '' : 'none';
|
rowIpProxyLayout.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyApiUrl) {
|
if (rowIpProxyApiUrl) {
|
||||||
rowIpProxyApiUrl.style.display = enabled && apiModeAvailable && isApiMode ? '' : 'none';
|
rowIpProxyApiUrl.style.display = showSettings && apiModeAvailable && isApiMode ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyAccountList) {
|
if (rowIpProxyAccountList) {
|
||||||
rowIpProxyAccountList.style.display = enabled && isAccountMode && accountListAvailable ? '' : 'none';
|
rowIpProxyAccountList.style.display = showSettings && isAccountMode && accountListAvailable ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyAccountSessionPrefix) {
|
if (rowIpProxyAccountSessionPrefix) {
|
||||||
rowIpProxyAccountSessionPrefix.style.display = enabled && showSessionOptions ? '' : 'none';
|
rowIpProxyAccountSessionPrefix.style.display = showSettings && showSessionOptions ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyAccountLifeMinutes) {
|
if (rowIpProxyAccountLifeMinutes) {
|
||||||
rowIpProxyAccountLifeMinutes.style.display = enabled && showSessionOptions ? '' : 'none';
|
rowIpProxyAccountLifeMinutes.style.display = showSettings && showSessionOptions ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyPoolTargetCount) {
|
if (rowIpProxyPoolTargetCount) {
|
||||||
rowIpProxyPoolTargetCount.style.display = enabled ? '' : 'none';
|
rowIpProxyPoolTargetCount.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyHost) {
|
if (rowIpProxyHost) {
|
||||||
rowIpProxyHost.style.display = enabled && isAccountMode ? '' : 'none';
|
rowIpProxyHost.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyPort) {
|
if (rowIpProxyPort) {
|
||||||
rowIpProxyPort.style.display = enabled && isAccountMode ? '' : 'none';
|
rowIpProxyPort.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyProtocol) {
|
if (rowIpProxyProtocol) {
|
||||||
rowIpProxyProtocol.style.display = enabled ? '' : 'none';
|
rowIpProxyProtocol.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyUsername) {
|
if (rowIpProxyUsername) {
|
||||||
rowIpProxyUsername.style.display = enabled && isAccountMode ? '' : 'none';
|
rowIpProxyUsername.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyPassword) {
|
if (rowIpProxyPassword) {
|
||||||
rowIpProxyPassword.style.display = enabled && isAccountMode ? '' : 'none';
|
rowIpProxyPassword.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyRegion) {
|
if (rowIpProxyRegion) {
|
||||||
rowIpProxyRegion.style.display = enabled && isAccountMode ? '' : 'none';
|
rowIpProxyRegion.style.display = showSettings && isAccountMode ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyActions) {
|
if (rowIpProxyActions) {
|
||||||
rowIpProxyActions.style.display = enabled ? '' : 'none';
|
rowIpProxyActions.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (ipProxyActionButtons) {
|
if (ipProxyActionButtons) {
|
||||||
ipProxyActionButtons.style.display = enabled ? '' : 'none';
|
ipProxyActionButtons.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (rowIpProxyRuntimeStatus) {
|
if (rowIpProxyRuntimeStatus) {
|
||||||
rowIpProxyRuntimeStatus.style.display = enabled ? '' : 'none';
|
rowIpProxyRuntimeStatus.style.display = showSettings ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (ipProxyLayout) {
|
if (ipProxyLayout) {
|
||||||
ipProxyLayout.classList.toggle('is-account-only', !apiModeAvailable);
|
ipProxyLayout.classList.toggle('is-account-only', !apiModeAvailable);
|
||||||
@@ -1254,7 +1304,7 @@ function updateIpProxyUI(state = latestState) {
|
|||||||
ipProxyApiPanel.classList.toggle('is-disabled', !apiModeAvailable);
|
ipProxyApiPanel.classList.toggle('is-disabled', !apiModeAvailable);
|
||||||
ipProxyApiPanel.setAttribute('aria-disabled', String(!apiModeAvailable));
|
ipProxyApiPanel.setAttribute('aria-disabled', String(!apiModeAvailable));
|
||||||
ipProxyApiPanel.hidden = !apiModeAvailable;
|
ipProxyApiPanel.hidden = !apiModeAvailable;
|
||||||
ipProxyApiPanel.style.display = enabled && apiModeAvailable ? '' : 'none';
|
ipProxyApiPanel.style.display = showSettings && apiModeAvailable ? '' : 'none';
|
||||||
}
|
}
|
||||||
if (inputIpProxyApiUrl) {
|
if (inputIpProxyApiUrl) {
|
||||||
inputIpProxyApiUrl.disabled = !enabled || !apiModeAvailable;
|
inputIpProxyApiUrl.disabled = !enabled || !apiModeAvailable;
|
||||||
|
|||||||
@@ -791,6 +791,11 @@ header {
|
|||||||
|
|
||||||
.ip-proxy-header-actions {
|
.ip-proxy-header-actions {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-toggle-ip-proxy-section {
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ip-proxy-fold {
|
.ip-proxy-fold {
|
||||||
|
|||||||
@@ -454,6 +454,8 @@
|
|||||||
<span class="data-value">用于浏览器代理接管与出口切换</span>
|
<span class="data-value">用于浏览器代理接管与出口切换</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="row-ip-proxy-enabled" class="section-mini-actions ip-proxy-header-actions">
|
<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 代理接管">
|
<label class="toggle-switch" for="input-ip-proxy-enabled" title="启用或禁用 IP 代理接管">
|
||||||
<input type="checkbox" id="input-ip-proxy-enabled" />
|
<input type="checkbox" id="input-ip-proxy-enabled" />
|
||||||
<span class="toggle-switch-track" aria-hidden="true">
|
<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 inputSub2ApiDefaultProxy = document.getElementById('input-sub2api-default-proxy');
|
||||||
const rowIpProxyEnabled = document.getElementById('row-ip-proxy-enabled');
|
const rowIpProxyEnabled = document.getElementById('row-ip-proxy-enabled');
|
||||||
const inputIpProxyEnabled = document.getElementById('input-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 ipProxyEnabledStatus = document.getElementById('ip-proxy-enabled-status');
|
||||||
const ipProxyEnabledStatusDot = document.getElementById('ip-proxy-enabled-status-dot');
|
const ipProxyEnabledStatusDot = document.getElementById('ip-proxy-enabled-status-dot');
|
||||||
const ipProxyEnabledStatusText = document.getElementById('ip-proxy-enabled-status-text');
|
const ipProxyEnabledStatusText = document.getElementById('ip-proxy-enabled-status-text');
|
||||||
@@ -5210,6 +5211,12 @@ btnToggleIpProxyPassword?.addEventListener('click', () => {
|
|||||||
syncIpProxyPasswordToggleLabel();
|
syncIpProxyPasswordToggleLabel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnToggleIpProxySection?.addEventListener('click', () => {
|
||||||
|
if (typeof toggleIpProxySectionExpanded === 'function') {
|
||||||
|
toggleIpProxySectionExpanded();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
btnMailLogin?.addEventListener('click', async () => {
|
btnMailLogin?.addEventListener('click', async () => {
|
||||||
const config = getMailProviderLoginConfig();
|
const config = getMailProviderLoginConfig();
|
||||||
const loginUrl = getMailProviderLoginUrl();
|
const loginUrl = getMailProviderLoginUrl();
|
||||||
@@ -7033,6 +7040,9 @@ bindPasswordVisibilityToggles();
|
|||||||
initTheme();
|
initTheme();
|
||||||
initHotmailListExpandedState();
|
initHotmailListExpandedState();
|
||||||
initMail2925ListExpandedState();
|
initMail2925ListExpandedState();
|
||||||
|
if (typeof initIpProxySectionExpandedState === 'function') {
|
||||||
|
initIpProxySectionExpandedState();
|
||||||
|
}
|
||||||
updateSaveButtonState();
|
updateSaveButtonState();
|
||||||
updateConfigMenuControls();
|
updateConfigMenuControls();
|
||||||
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
|
setLocalCpaStep9Mode(DEFAULT_LOCAL_CPA_STEP9_MODE);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const {
|
|||||||
} = require('../mail-provider-utils');
|
} = require('../mail-provider-utils');
|
||||||
|
|
||||||
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
const sidepanelSource = fs.readFileSync('sidepanel/sidepanel.js', 'utf8');
|
||||||
|
const ipProxyPanelSource = fs.readFileSync('sidepanel/ip-proxy-panel.js', 'utf8');
|
||||||
|
|
||||||
function extractFunction(name) {
|
function extractFunction(name) {
|
||||||
const markers = [`async function ${name}(`, `function ${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"');
|
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="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(phoneToggleIndex >= 0);
|
||||||
assert.ok(ipProxySectionIndex > phoneToggleIndex);
|
assert.ok(ipProxySectionIndex > phoneToggleIndex);
|
||||||
assert.ok(ipProxyToggleIndex > 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"/);
|
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', () => {
|
test('updatePhoneVerificationSettingsUI toggles HeroSMS rows from the sms switch', () => {
|
||||||
const api = new Function(`
|
const api = new Function(`
|
||||||
const inputPhoneVerificationEnabled = { checked: false };
|
const inputPhoneVerificationEnabled = { checked: false };
|
||||||
|
|||||||
Reference in New Issue
Block a user