fix: sync LuckMail PR with dev

This commit is contained in:
QLHazyCoder
2026-04-29 22:12:28 +08:00
52 changed files with 8013 additions and 909 deletions
+269
View File
@@ -0,0 +1,269 @@
(function attachSidepanelFormDialog(globalScope) {
const FORM_EYE_OPEN_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>';
const FORM_EYE_CLOSED_ICON = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.94 10.94 0 0 1 12 19C5 19 1 12 1 12a21.77 21.77 0 0 1 5.06-6.94"/><path d="M9.9 4.24A10.94 10.94 0 0 1 12 5c7 0 11 7 11 7a21.86 21.86 0 0 1-2.16 3.19"/><path d="M1 1l22 22"/><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/></svg>';
function syncPasswordToggleButton(button, input, labels) {
if (!button || !input) return;
const isHidden = input.type === 'password';
button.innerHTML = isHidden ? FORM_EYE_OPEN_ICON : FORM_EYE_CLOSED_ICON;
button.setAttribute('aria-label', isHidden ? labels.show : labels.hide);
button.title = isHidden ? labels.show : labels.hide;
}
function createFormDialog(context = {}) {
const {
overlay = null,
titleNode = null,
closeButton = null,
messageNode = null,
alertNode = null,
fieldsContainer = null,
cancelButton = null,
confirmButton = null,
documentRef = globalScope.document,
} = context;
let resolver = null;
let currentConfig = null;
let currentInputs = [];
function setHidden(node, hidden) {
if (!node) return;
node.hidden = Boolean(hidden);
}
function resetAlert() {
if (!alertNode) return;
alertNode.textContent = '';
alertNode.className = 'modal-alert modal-form-alert';
alertNode.hidden = true;
}
function setAlert(message = '', tone = 'danger') {
if (!alertNode) return;
const text = String(message || '').trim();
if (!text) {
resetAlert();
return;
}
alertNode.textContent = text;
alertNode.className = `modal-alert modal-form-alert${tone === 'danger' ? ' is-danger' : ''}`;
alertNode.hidden = false;
}
function close(result = null) {
if (resolver) {
resolver(result);
resolver = null;
}
currentConfig = null;
currentInputs = [];
resetAlert();
if (fieldsContainer) {
fieldsContainer.innerHTML = '';
}
if (overlay) {
overlay.hidden = true;
}
}
function buildFieldNode(field, values) {
const wrapper = documentRef.createElement('div');
wrapper.className = 'modal-form-row';
const label = documentRef.createElement('label');
label.className = 'modal-form-label';
const labelText = String(field.label || field.key || '').trim();
label.textContent = labelText;
wrapper.appendChild(label);
let input = null;
if (field.type === 'textarea') {
input = documentRef.createElement('textarea');
input.className = 'data-textarea';
} else if (field.type === 'select') {
input = documentRef.createElement('select');
input.className = 'data-select';
const options = Array.isArray(field.options) ? field.options : [];
options.forEach((option) => {
const optionNode = documentRef.createElement('option');
optionNode.value = String(option?.value || '');
optionNode.textContent = String(option?.label || option?.value || '');
input.appendChild(optionNode);
});
} else {
input = documentRef.createElement('input');
input.type = field.type === 'password' ? 'password' : 'text';
input.className = field.type === 'password'
? 'data-input data-input-with-icon'
: 'data-input';
}
const normalizedValue = Object.prototype.hasOwnProperty.call(values, field.key)
? values[field.key]
: field.value;
if (normalizedValue !== undefined && normalizedValue !== null) {
input.value = String(normalizedValue);
}
if (field.placeholder) {
input.placeholder = String(field.placeholder);
}
if (field.autocomplete) {
input.autocomplete = String(field.autocomplete);
}
if (field.inputMode) {
input.inputMode = String(field.inputMode);
}
if (field.rows && field.type === 'textarea') {
input.rows = Number(field.rows) || 3;
}
input.dataset.fieldKey = String(field.key || '');
label.htmlFor = field.key;
input.id = field.key;
if (field.type === 'password') {
const inputShell = documentRef.createElement('div');
inputShell.className = 'input-with-icon';
inputShell.appendChild(input);
const toggleButton = documentRef.createElement('button');
toggleButton.className = 'input-icon-btn';
toggleButton.type = 'button';
const labels = {
show: String(field.showPasswordLabel || `\u663e\u793a${labelText || '\u5bc6\u7801'}`),
hide: String(field.hidePasswordLabel || `\u9690\u85cf${labelText || '\u5bc6\u7801'}`),
};
syncPasswordToggleButton(toggleButton, input, labels);
toggleButton.addEventListener('click', () => {
input.type = input.type === 'password' ? 'text' : 'password';
syncPasswordToggleButton(toggleButton, input, labels);
});
inputShell.appendChild(toggleButton);
wrapper.appendChild(inputShell);
} else {
wrapper.appendChild(input);
}
if (field.type !== 'textarea') {
input.addEventListener('keydown', (event) => {
if (event.key !== 'Enter') {
return;
}
event.preventDefault();
void handleConfirm();
});
}
currentInputs.push({ field, input });
return wrapper;
}
function collectValues() {
return currentInputs.reduce((result, item) => {
result[item.field.key] = item.input.value;
return result;
}, {});
}
async function handleConfirm() {
if (!currentConfig) {
close(null);
return;
}
const values = collectValues();
resetAlert();
for (const item of currentInputs) {
const { field, input } = item;
const rawValue = values[field.key];
const textValue = String(rawValue || '').trim();
if (field.required && !textValue) {
setAlert(field.requiredMessage || `${field.label || field.key}不能为空。`);
input.focus?.();
return;
}
if (typeof field.validate === 'function') {
const validationMessage = await field.validate(rawValue, values);
if (validationMessage) {
setAlert(validationMessage);
input.focus?.();
return;
}
}
}
close(values);
}
function bindEvents() {
overlay?.addEventListener('click', (event) => {
if (event.target === overlay) {
close(null);
}
});
closeButton?.addEventListener('click', () => close(null));
cancelButton?.addEventListener('click', () => close(null));
confirmButton?.addEventListener('click', () => {
void handleConfirm();
});
}
async function open(config = {}) {
if (!overlay || !titleNode || !fieldsContainer || !confirmButton) {
return null;
}
if (resolver) {
close(null);
}
currentConfig = config || {};
currentInputs = [];
titleNode.textContent = String(currentConfig.title || '填写表单');
if (messageNode) {
const message = String(currentConfig.message || '').trim();
messageNode.textContent = message;
setHidden(messageNode, !message);
}
resetAlert();
if (currentConfig.alert?.text) {
setAlert(currentConfig.alert.text, currentConfig.alert.tone || 'danger');
}
confirmButton.textContent = String(currentConfig.confirmLabel || '确认');
confirmButton.className = `btn ${currentConfig.confirmVariant || 'btn-primary'} btn-sm`;
fieldsContainer.innerHTML = '';
const initialValues = currentConfig.initialValues && typeof currentConfig.initialValues === 'object'
? currentConfig.initialValues
: {};
const fields = Array.isArray(currentConfig.fields) ? currentConfig.fields : [];
fields.forEach((field) => {
fieldsContainer.appendChild(buildFieldNode(field, initialValues));
});
overlay.hidden = false;
const firstInput = currentInputs[0]?.input || null;
if (firstInput && typeof globalScope.requestAnimationFrame === 'function') {
globalScope.requestAnimationFrame(() => firstInput.focus?.());
} else {
firstInput?.focus?.();
}
return new Promise((resolve) => {
resolver = resolve;
});
}
bindEvents();
return {
close,
open,
};
}
globalScope.SidepanelFormDialog = {
createFormDialog,
};
})(window);
+82 -44
View File
@@ -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();
@@ -866,14 +907,18 @@ function buildIpProxyActionHintText(options = {}) {
const mode = normalizeIpProxyModeForCurrentRelease(options?.mode || DEFAULT_IP_PROXY_MODE);
const poolCount = Math.max(0, Number(options?.poolCount) || 0);
const changeAvailable = Boolean(options?.changeAvailable);
const dynamicPoolCount = poolCount > 0 ? poolCount : 1;
if (mode === 'api') {
return '下一条:切到已拉取代理池的下一条。Change:仅账号模式可用。';
const nextPart = poolCount > 1
? `下一条:当前共 ${dynamicPoolCount} 条节点,切到已拉取代理池的下一条节点。`
: `下一条:当前仅 ${dynamicPoolCount} 条节点,执行重绑复测(不保证更换出口)。`;
return `${nextPart} Change:仅账号模式可用。`;
}
const nextPart = poolCount > 1
? '下一条:切到代理池的下一条节点。'
: '下一条:当前仅 1 条节点,执行重绑复测(不保证更换出口)。';
? `下一条:当前共 ${dynamicPoolCount} 条节点,切到代理池的下一条节点。`
: `下一条:当前仅 ${dynamicPoolCount} 条节点,执行重绑复测(不保证更换出口)。`;
const changePart = changeAvailable
? 'Change:保持当前 session 重绑链路并复测出口。'
: 'Change:需 711 账号模式且用户名包含 session。';
@@ -890,7 +935,6 @@ function setIpProxyCurrentDisplay(text = '', hasValue = false) {
function formatIpProxyCurrentDisplay(state = latestState) {
const mode = normalizeIpProxyModeForCurrentRelease(state?.ipProxyMode);
if (mode === 'account') {
const runtime = getIpProxyRuntimeSnapshot(state, mode);
const current = getIpProxyCurrentEntry(state);
if (!current) {
return {
@@ -898,10 +942,8 @@ function formatIpProxyCurrentDisplay(state = latestState) {
hasValue: false,
};
}
const count = runtime.pool.length > 0 ? runtime.pool.length : 1;
const index = runtime.index;
return {
text: `${current.host}:${current.port}${current.region ? ` [${current.region}]` : ''} (${Math.min(index + 1, count)}/${count})`,
text: `${current.host}:${current.port}${current.region ? ` [${current.region}]` : ''}`,
hasValue: true,
};
}
@@ -919,7 +961,7 @@ function formatIpProxyCurrentDisplay(state = latestState) {
const region = String(current.region || '').trim();
const label = region ? `${current.host}:${current.port} [${region}]` : `${current.host}:${current.port}`;
return {
text: `${label}${count ? ` (${Math.min(index + 1, count)}/${count})` : ''}`,
text: label,
hasValue: true,
};
}
@@ -945,19 +987,7 @@ function buildIpProxyCurrentDisplayText(display = {}, runtimeStatus = {}) {
if (!hasValue || !rawText) {
return rawText;
}
const runtimeText = String(runtimeStatus?.text || '').trim().toLowerCase();
if (!runtimeText) {
return rawText;
}
const endpointToken = extractIpProxyEndpointToken(rawText);
if (!endpointToken || !runtimeText.includes(endpointToken)) {
return rawText;
}
const indexToken = extractIpProxyIndexToken(rawText);
if (indexToken) {
return `节点 ${indexToken}`;
}
return '当前节点';
return rawText;
}
function formatIpProxyRuntimeStatus(state = latestState) {
@@ -1158,6 +1188,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();
@@ -1172,64 +1203,70 @@ function updateIpProxyUI(state = latestState) {
const busyAction = normalizeIpProxyActionType(actionState.action);
const runtimeState = state || latestState || {};
setIpProxyEnabledInlineStatus(runtimeState, enabled);
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);
@@ -1256,7 +1293,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;
@@ -1305,11 +1342,12 @@ function updateIpProxyUI(state = latestState) {
setIpProxyCurrentDisplay(currentDisplayText, currentDisplay.hasValue);
const runtimeSnapshot = getIpProxyRuntimeSnapshot(runtimeState, mode, service);
const runtimePoolCount = Array.isArray(runtimeSnapshot?.pool) ? runtimeSnapshot.pool.length : 0;
const runtimePoolCountForDisplay = runtimePoolCount > 0 ? runtimePoolCount : 1;
const hasCurrentEntry = Boolean(getIpProxyCurrentEntry(runtimeState));
const changeAvailable = canChangeIpProxyExitWithCurrentSession(runtimeState);
const nextActionTitle = runtimePoolCount > 1
? '切换到代理池下一条节点并应用'
: '当前仅 1 条节点:重绑当前节点并复测连通性(不保证更换出口)';
? `切换到代理池下一条节点并应用(当前共 ${runtimePoolCountForDisplay} 条)`
: `当前仅 ${runtimePoolCountForDisplay} 条节点:重绑当前节点并复测连通性(不保证更换出口)`;
if (btnIpProxyRefresh) {
btnIpProxyRefresh.disabled = actionBusy || !enabled || !canOperate;
+191
View File
@@ -0,0 +1,191 @@
(function attachSidepanelPayPalManager(globalScope) {
function createPayPalManager(context = {}) {
const {
state,
dom,
helpers,
runtime,
paypalUtils = {},
} = context;
let actionInFlight = false;
function getPayPalAccounts(currentState = state.getLatestState()) {
return helpers.getPayPalAccounts(currentState);
}
function getCurrentPayPalAccountId(currentState = state.getLatestState()) {
return String(currentState?.currentPayPalAccountId || '').trim();
}
function buildSelectOptions(accounts = []) {
if (!accounts.length) {
return '<option value="">请先添加 PayPal 账号</option>';
}
return accounts.map((account) => (
`<option value="${helpers.escapeHtml(account.id)}">${helpers.escapeHtml(account.email || '(未命名账号)')}</option>`
)).join('');
}
function applyPayPalAccountMutation(account) {
if (!account?.id) return;
const latestState = state.getLatestState();
const nextAccounts = typeof paypalUtils.upsertPayPalAccountInList === 'function'
? paypalUtils.upsertPayPalAccountInList(getPayPalAccounts(latestState), account)
: [...getPayPalAccounts(latestState), account];
state.syncLatestState({ paypalAccounts: nextAccounts });
renderPayPalAccounts();
}
function renderPayPalAccounts() {
if (!dom.selectPayPalAccount) return;
const latestState = state.getLatestState();
const accounts = getPayPalAccounts(latestState);
const currentId = getCurrentPayPalAccountId(latestState);
dom.selectPayPalAccount.innerHTML = buildSelectOptions(accounts);
dom.selectPayPalAccount.disabled = accounts.length === 0;
dom.selectPayPalAccount.value = accounts.some((account) => account.id === currentId) ? currentId : '';
}
async function syncSelectedPayPalAccount(options = {}) {
const { silent = false } = options;
const accountId = String(dom.selectPayPalAccount?.value || '').trim();
if (!accountId) {
state.syncLatestState({
currentPayPalAccountId: null,
paypalEmail: '',
paypalPassword: '',
});
renderPayPalAccounts();
return null;
}
const response = await runtime.sendMessage({
type: 'SELECT_PAYPAL_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) {
throw new Error(response.error);
}
state.syncLatestState({
currentPayPalAccountId: response.account?.id || accountId,
paypalEmail: String(response.account?.email || '').trim(),
paypalPassword: String(response.account?.password || ''),
});
renderPayPalAccounts();
if (!silent) {
helpers.showToast(`已切换当前 PayPal 账号为 ${response.account?.email || accountId}`, 'success', 1800);
}
return response.account || null;
}
async function openPayPalAccountDialog() {
if (typeof helpers.openFormDialog !== 'function') {
throw new Error('表单弹窗能力未加载,请刷新扩展后重试。');
}
return helpers.openFormDialog({
title: '添加 PayPal 账号',
confirmLabel: '保存账号',
confirmVariant: 'btn-primary',
fields: [
{
key: 'email',
label: 'PayPal 账号',
type: 'text',
placeholder: '请输入 PayPal 登录邮箱',
autocomplete: 'username',
required: true,
requiredMessage: '请先填写 PayPal 账号。',
validate: (value) => {
const normalized = String(value || '').trim();
if (!normalized.includes('@')) {
return 'PayPal 账号需填写邮箱格式。';
}
return '';
},
},
{
key: 'password',
label: 'PayPal 密码',
type: 'password',
placeholder: '请输入 PayPal 登录密码',
autocomplete: 'current-password',
required: true,
requiredMessage: '请先填写 PayPal 密码。',
},
],
});
}
async function handleAddPayPalAccount() {
if (actionInFlight) return;
const formValues = await openPayPalAccountDialog();
if (!formValues) {
return;
}
actionInFlight = true;
if (dom.btnAddPayPalAccount) {
dom.btnAddPayPalAccount.disabled = true;
}
try {
const response = await runtime.sendMessage({
type: 'UPSERT_PAYPAL_ACCOUNT',
source: 'sidepanel',
payload: {
email: String(formValues.email || '').trim(),
password: String(formValues.password || ''),
},
});
if (response?.error) {
throw new Error(response.error);
}
applyPayPalAccountMutation(response.account);
if (response.account?.id) {
state.syncLatestState({ currentPayPalAccountId: response.account.id });
renderPayPalAccounts();
dom.selectPayPalAccount.value = response.account.id;
await syncSelectedPayPalAccount({ silent: true });
}
helpers.showToast(`已保存 PayPal 账号 ${response.account?.email || ''}`, 'success', 2200);
} catch (error) {
helpers.showToast(`保存 PayPal 账号失败:${error.message}`, 'error');
throw error;
} finally {
actionInFlight = false;
if (dom.btnAddPayPalAccount) {
dom.btnAddPayPalAccount.disabled = false;
}
}
}
function bindPayPalEvents() {
dom.btnAddPayPalAccount?.addEventListener('click', () => {
void handleAddPayPalAccount();
});
dom.selectPayPalAccount?.addEventListener('change', () => {
void syncSelectedPayPalAccount().catch((error) => {
helpers.showToast(error.message, 'error');
renderPayPalAccounts();
});
});
}
return {
bindPayPalEvents,
renderPayPalAccounts,
syncSelectedPayPalAccount,
};
}
globalScope.SidepanelPayPalManager = {
createPayPalManager,
};
})(window);
+425 -46
View File
@@ -607,7 +607,12 @@ header {
Data Card
============================================================ */
#data-section { margin-bottom: 14px; }
#data-section {
margin-bottom: 14px;
display: flex;
flex-direction: column;
gap: 12px;
}
.data-card {
background: var(--bg-surface);
@@ -769,6 +774,21 @@ header {
gap: 8px;
}
#settings-card .data-row.module-divider-start {
position: relative;
margin-top: 10px;
padding-top: 12px;
}
#settings-card .data-row.module-divider-start::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
border-top: 1px solid color-mix(in srgb, var(--border) 76%, transparent);
}
.data-check-row {
align-items: flex-start;
}
@@ -785,6 +805,19 @@ header {
display: block;
}
.ip-proxy-card {
margin-top: 10px;
}
.ip-proxy-header-actions {
flex: 0 0 auto;
align-items: center;
}
#btn-toggle-ip-proxy-section {
white-space: nowrap;
}
.ip-proxy-fold {
width: 100%;
border: none;
@@ -801,6 +834,39 @@ header {
padding-top: 0;
}
.phone-verification-card {
margin-top: 10px;
}
.phone-verification-header-actions {
flex: 0 0 auto;
align-items: center;
}
#btn-toggle-phone-verification-section {
white-space: nowrap;
}
.phone-verification-fold-row {
display: block;
}
.phone-verification-fold {
width: 100%;
border: none;
border-radius: 0;
background: transparent;
padding: 0;
}
.phone-verification-fold-body {
display: flex;
flex-direction: column;
gap: 8px;
border-top: none;
padding-top: 0;
}
.ip-proxy-layout-row {
display: block;
}
@@ -858,17 +924,20 @@ header {
}
}
.ip-proxy-enabled-inline {
justify-content: flex-end;
gap: 10px;
}
.ip-proxy-actions-inline {
flex-wrap: wrap;
align-items: center;
align-items: flex-start;
row-gap: 6px;
}
#row-ip-proxy-actions {
align-items: flex-start;
}
#row-ip-proxy-actions > .data-label {
padding-top: 9px;
}
.ip-proxy-action-grid {
width: 100%;
display: flex;
@@ -888,42 +957,6 @@ header {
color: var(--text-secondary);
}
.ip-proxy-enabled-status {
margin-left: auto;
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
min-width: 92px;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
text-align: right;
white-space: nowrap;
}
.ip-proxy-enabled-status-dot {
width: 7px;
height: 7px;
border-radius: 999px;
background: var(--text-muted);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--text-muted) 25%, transparent);
flex-shrink: 0;
}
.ip-proxy-enabled-status.is-on {
color: var(--green);
}
.ip-proxy-enabled-status.is-on .ip-proxy-enabled-status-dot {
background: var(--green);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--green) 30%, transparent);
}
.ip-proxy-enabled-status.is-off {
color: var(--text-muted);
}
.ip-proxy-runtime-status {
display: inline-flex;
align-items: flex-start;
@@ -946,19 +979,25 @@ header {
.ip-proxy-runtime-main {
min-width: 0;
font-size: 12px;
line-height: 1.45;
}
.ip-proxy-runtime-meta {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-start;
gap: 8px;
}
.ip-proxy-check-ip-btn {
min-width: 64px;
padding-inline: 10px;
min-width: 0;
padding-inline: 8px;
flex-shrink: 0;
margin-left: 0;
position: absolute;
top: 0;
right: 0;
}
.ip-proxy-runtime-current {
@@ -972,6 +1011,14 @@ header {
color: var(--text-primary);
}
#row-ip-proxy-runtime-status {
align-items: flex-start;
}
#row-ip-proxy-runtime-status > .data-label {
padding-top: 9px;
}
.ip-proxy-runtime-dot {
width: 8px;
height: 8px;
@@ -1000,20 +1047,53 @@ header {
.ip-proxy-runtime-details {
margin: 0;
padding: 0;
min-width: 0;
width: 100%;
padding-right: 84px;
}
.ip-proxy-runtime-details-row {
position: relative;
min-width: 0;
width: 100%;
min-height: 24px;
}
.ip-proxy-runtime-details summary {
display: inline-flex;
align-items: center;
gap: 4px;
min-height: 24px;
cursor: pointer;
user-select: none;
color: var(--text-secondary);
font-size: 11px;
line-height: 1.4;
list-style: none;
}
.ip-proxy-runtime-details summary::-webkit-details-marker {
display: none;
}
.ip-proxy-runtime-details summary::after {
content: '▾';
font-size: 10px;
line-height: 1;
color: inherit;
transform: rotate(-90deg);
transform-origin: center;
transition: transform var(--transition);
}
.ip-proxy-runtime-details[open] summary {
color: var(--text-primary);
}
.ip-proxy-runtime-details[open] summary::after {
transform: rotate(0deg);
}
.ip-proxy-runtime-details-text {
margin-top: 4px;
font-size: 11px;
@@ -1869,6 +1949,271 @@ header {
text-align: center;
}
.hero-sms-country-stack {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 6px;
}
.hero-sms-country-mainline {
width: 100%;
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.hero-sms-country-note {
font-size: 12px;
color: var(--text-muted);
}
.hero-sms-reuse-max-inline {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: nowrap;
}
.hero-sms-reuse-max-left {
flex: 1 1 auto;
min-width: 0;
display: flex;
align-items: center;
}
.hero-sms-reuse-max-right {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.hero-sms-max-price-input {
width: 72px;
text-align: center;
}
.hero-sms-country-menu {
position: relative;
flex: 1;
min-width: 260px;
}
.hero-sms-country-menu-btn {
width: 100%;
height: 33px;
min-height: 33px;
padding-top: 0;
padding-bottom: 0;
justify-content: flex-start;
overflow: hidden;
text-overflow: ellipsis;
}
.hero-sms-country-menu-btn[aria-expanded="true"] {
border-color: var(--blue);
color: var(--blue);
background: var(--blue-soft);
}
.hero-sms-country-menu-dropdown {
position: absolute;
top: calc(100% + 6px);
left: 0;
right: 0;
z-index: 1200;
display: flex;
flex-direction: column;
gap: 4px;
padding: 6px;
max-height: 180px;
overflow-y: auto;
background: var(--bg-base);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-md);
}
.hero-sms-country-menu-search {
padding-bottom: 6px;
border-bottom: 1px solid var(--border-subtle);
}
.hero-sms-country-menu-search-input {
width: 100%;
}
.hero-sms-country-menu-dropdown[hidden] {
display: none !important;
}
.hero-sms-country-menu-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
text-align: left;
}
.hero-sms-country-menu-item-label {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hero-sms-country-menu-item-badge {
flex: 0 0 auto;
min-width: 42px;
text-align: right;
color: var(--brand);
font-weight: 700;
}
.hero-sms-runtime-grid {
width: 100%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 4px 12px;
}
.hero-sms-runtime-cell {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.hero-sms-runtime-cell-span2 {
grid-column: 1 / -1;
}
.hero-sms-runtime-key {
flex: 0 0 auto;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
white-space: nowrap;
}
.hero-sms-runtime-value {
flex: 1 1 auto;
min-width: 0;
}
.hero-sms-price-preview-stack {
width: 100%;
display: flex;
flex-direction: column;
gap: 6px;
}
.hero-sms-price-preview-head {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: nowrap;
gap: 8px;
}
#btn-hero-sms-price-preview {
height: 33px;
min-height: 33px;
padding-top: 0;
padding-bottom: 0;
align-self: flex-start;
}
.hero-sms-price-controls-grid {
width: 100%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px 12px;
}
.hero-sms-price-control {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
min-width: 0;
}
.hero-sms-price-control .setting-controls {
margin-left: auto;
width: 104px;
justify-content: flex-start;
}
.hero-sms-price-control-reuse {
justify-content: space-between;
}
#row-hero-sms-max-price,
#row-phone-code-settings-group {
align-items: flex-start;
}
#row-hero-sms-max-price > .data-label,
#row-phone-code-settings-group > .data-label {
padding-top: 9px;
}
.hero-sms-toggle-controls {
width: 104px;
justify-content: flex-start;
}
.hero-sms-price-preview-result {
width: 100%;
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
background: var(--bg-surface);
padding: 6px 8px;
}
.hero-sms-price-preview-text {
display: block;
white-space: pre-line;
line-height: 1.45;
}
.hero-sms-settings-grid {
width: 100%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px 12px;
}
.hero-sms-settings-cell {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
min-width: 0;
}
.hero-sms-settings-cell .setting-controls {
margin-left: auto;
}
.hero-sms-settings-caption {
flex: 0 0 auto;
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
white-space: nowrap;
}
.data-unit {
font-size: 12px;
font-weight: 600;
@@ -2722,6 +3067,40 @@ header {
cursor: pointer;
}
.modal-form-fields {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 14px;
}
.modal-form-row {
display: flex;
flex-direction: column;
gap: 6px;
}
.modal-form-label {
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
}
.modal-form-row .data-input,
.modal-form-row .data-select,
.modal-form-row .data-textarea {
width: 100%;
}
.modal-form-row .input-with-icon {
width: 100%;
}
.modal-form-alert {
margin-top: -4px;
margin-bottom: 14px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
+455 -261
View File
@@ -184,7 +184,13 @@
</div>
<div class="data-row" id="row-sub2api-password" style="display:none;">
<span class="data-label">密码</span>
<input type="password" id="input-sub2api-password" class="data-input" placeholder="请输入 SUB2API 登录密码" />
<div class="input-with-icon">
<input type="password" id="input-sub2api-password" class="data-input data-input-with-icon"
placeholder="请输入 SUB2API 登录密码" />
<button id="btn-toggle-sub2api-password" class="input-icon-btn" type="button"
data-password-toggle="input-sub2api-password" data-show-label="显示 SUB2API 密码"
data-hide-label="隐藏 SUB2API 密码" aria-label="显示 SUB2API 密码" title="显示 SUB2API 密码"></button>
</div>
</div>
<div class="data-row" id="row-sub2api-group" style="display:none;">
<span class="data-label">分组</span>
@@ -194,19 +200,387 @@
<span class="data-label">默认代理</span>
<input type="text" id="input-sub2api-default-proxy" class="data-input" placeholder="留空则不使用代理;或填写代理名称 / ID" />
</div>
<div class="data-row" id="row-ip-proxy-enabled">
<span class="data-label">IP代理</span>
<div class="data-inline ip-proxy-enabled-inline">
<div class="data-row" id="row-codex2api-url" style="display:none;">
<span class="data-label">Codex2API</span>
<input type="text" id="input-codex2api-url" class="data-input"
placeholder="http://localhost:8080/admin/accounts" />
</div>
<div class="data-row" id="row-codex2api-admin-key" style="display:none;">
<span class="data-label">管理密钥</span>
<div class="input-with-icon">
<input type="password" id="input-codex2api-admin-key" class="data-input data-input-with-icon"
placeholder="请输入 Codex2API Admin Secret" />
<button id="btn-toggle-codex2api-admin-key" class="input-icon-btn" type="button"
data-password-toggle="input-codex2api-admin-key" data-show-label="显示 Codex2API 管理密钥"
data-hide-label="隐藏 Codex2API 管理密钥" aria-label="显示 Codex2API 管理密钥"
title="显示 Codex2API 管理密钥"></button>
</div>
</div>
<div class="data-row module-divider-start" id="row-custom-password">
<span class="data-label">账户密码</span>
<div class="input-with-icon">
<input type="password" id="input-password" class="data-input data-input-with-icon"
placeholder="codex密码,留空则自动生成" />
<button id="btn-toggle-password" class="input-icon-btn" type="button" aria-label="显示密码" title="显示密码"></button>
</div>
</div>
<div class="data-row" id="row-plus-mode">
<span class="data-label">Plus 模式</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-plus-mode-enabled" title="开启后使用 Plus Checkout + PayPal 授权流程">
<input type="checkbox" id="input-plus-mode-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
<span class="setting-caption">PayPal 订阅链路</span>
</div>
</div>
<div class="data-row" id="row-paypal-account" style="display:none;">
<span class="data-label">PayPal 账号</span>
<div class="data-inline">
<select id="select-paypal-account" class="data-select">
<option value="">请先添加 PayPal 账号</option>
</select>
<button id="btn-add-paypal-account" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
</div>
</div>
<div class="data-row module-divider-start" id="row-mail-provider">
<span class="data-label">邮箱服务</span>
<div class="data-inline">
<select id="select-mail-provider" class="data-select">
<option value="custom">自定义邮箱</option>
<option value="hotmail-api">Hotmail(账号池)</option>
<option value="luckmail-api">LuckMailAPI 购邮)</option>
<option value="icloud">iCloud 邮箱</option>
<option value="163">163 邮箱 (mail.163.com)</option>
<option value="163-vip">163 VIP 邮箱 (vip.163.com)</option>
<option value="126">126 邮箱 (mail.126.com)</option>
<option value="qq">QQ 邮箱 (wx.mail.qq.com)</option>
<option value="inbucket">Inbucket(自定义主机)</option>
<option value="2925">2925 邮箱 (2925.com)</option>
<option value="gmail">Gmail 邮箱 (mail.google.com)</option>
<option value="cloudflare-temp-email">Cloudflare Temp Email</option>
</select>
<button id="btn-mail-login" class="btn btn-outline btn-sm data-inline-btn" type="button" disabled>登录</button>
</div>
</div>
<div class="data-row data-check-row" id="row-custom-mail-provider-pool" style="display:none;">
<span class="data-label">自定义号池</span>
<textarea id="input-custom-mail-provider-pool" class="data-textarea"
placeholder="每行一个注册邮箱,例如&#10;alias001@example.com&#10;alias002@example.com"></textarea>
</div>
<div class="data-row" id="row-mail-2925-mode" style="display:none;">
<span class="data-label">2925 模式</span>
<div id="mail-2925-mode-group" class="choice-group" role="group" aria-label="2925 邮箱模式">
<button type="button" class="choice-btn" data-mail2925-mode="provide">提供邮箱</button>
<button type="button" class="choice-btn" data-mail2925-mode="receive">接收邮箱</button>
</div>
</div>
<div class="data-row" id="row-email-generator">
<span class="data-label">邮箱生成</span>
<select id="select-email-generator" class="data-select">
<option value="gmail-alias">Gmail +tag</option>
<option value="duck">DuckDuckGo</option>
<option value="custom-pool">自定义邮箱池</option>
<option value="cloudflare">Cloudflare</option>
<option value="icloud">iCloud 隐私邮箱</option>
<option value="cloudflare-temp-email">Cloudflare Temp Email</option>
</select>
</div>
<div class="data-row data-check-row" id="row-custom-email-pool" style="display:none;">
<span class="data-label">邮箱池</span>
<textarea id="input-custom-email-pool" class="data-textarea"
placeholder="每行一个邮箱,例如&#10;alias001@gmail.com&#10;alias002@gmail.com"></textarea>
</div>
<div class="data-row" id="row-cf-domain" style="display:none;">
<span class="data-label">CF 域名</span>
<div class="data-inline">
<select id="select-cf-domain" class="data-select"></select>
<input type="text" id="input-cf-domain" class="data-input" placeholder="例如 yourdomain.xyz"
style="display:none;" />
<button id="btn-cf-domain-mode" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
</div>
</div>
<div class="data-row" id="row-mail2925-pool-settings" style="display:none;">
<span class="data-label">2925 号池</span>
<div class="data-inline mail2925-base-inline">
<label class="toggle-switch mail2925-pool-toggle" id="label-mail2925-use-account-pool"
for="input-mail2925-use-account-pool" style="display:none;" title="开启后启用 2925 账号池">
<input type="checkbox" id="input-mail2925-use-account-pool" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
<span>号池</span>
</label>
<select id="select-mail2925-pool-account" class="data-select mail2925-pool-select" style="display:none;">
<option value="">请选择号池邮箱</option>
</select>
</div>
</div>
<div class="data-row" id="row-email-prefix" style="display:none;">
<span class="data-label" id="label-email-prefix">别名基邮箱</span>
<div class="data-inline">
<input type="text" id="input-email-prefix" class="data-input mail2925-base-input"
placeholder="例如 yourname@example.com" />
</div>
</div>
<div class="data-row" id="row-inbucket-host" style="display:none;">
<span class="data-label">Inbucket</span>
<input type="text" id="input-inbucket-host" class="data-input" placeholder="填写主机或 https://主机地址" />
</div>
<div class="data-row" id="row-inbucket-mailbox" style="display:none;">
<span class="data-label">邮箱名</span>
<input type="text" id="input-inbucket-mailbox" class="data-input" placeholder="例如 zju2001" />
</div>
<div class="data-row" id="row-auto-run-controls">
<span class="data-label">注册邮箱</span>
<div class="data-inline">
<input type="text" id="input-email" class="data-input" placeholder="自动生成或手动粘贴邮箱" />
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
</div>
</div>
<div class="data-row module-divider-start" id="row-auto-delay-settings">
<span class="data-label">延迟</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-auto-delay-enabled">
<input type="checkbox" id="input-auto-delay-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
<div class="setting-controls">
<input type="number" id="input-auto-delay-minutes" class="data-input auto-delay-input" value="30" min="1"
max="1440" step="1" title="启动前倒计时分钟数" />
<span class="data-unit">分钟</span>
</div>
</div>
<div class="setting-group setting-group-secondary">
<span class="setting-caption">步间间隔</span>
<div class="setting-controls">
<input type="number" id="input-auto-step-delay-seconds" class="data-input auto-delay-input" value=""
min="0" max="600" step="1" title="步间延迟秒数,0 或留空则不延迟" />
<span class="data-unit"></span>
</div>
</div>
</div>
</div>
<div class="data-row">
<span class="data-label">自动重试</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-auto-skip-failures">
<input type="checkbox" id="input-auto-skip-failures" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
<div class="setting-group setting-group-secondary">
<span class="setting-caption">线程间隔</span>
<div class="setting-controls">
<input type="number" id="input-auto-skip-failures-thread-interval-minutes"
class="data-input auto-delay-input" value="0" min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
<span class="data-unit">分钟</span>
</div>
</div>
</div>
</div>
<div class="data-row module-divider-start" id="row-oauth-display">
<span class="data-label">OAuth</span>
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
</div>
<div class="data-row">
<span class="data-label">回调</span>
<div class="data-inline data-value-actions">
<span id="display-localhost-url" class="data-value data-value-fill mono truncate">等待中...</span>
<button id="btn-save-settings" class="btn btn-outline btn-sm data-inline-btn" type="button">保存</button>
</div>
</div>
</div>
<div id="phone-verification-section" class="data-card phone-verification-card">
<div class="section-mini-header">
<div class="section-mini-copy">
<span class="section-label">接码设置</span>
<span class="data-value">手机号验证与 HeroSMS 获取策略</span>
</div>
<div id="row-phone-verification-enabled" class="section-mini-actions phone-verification-header-actions">
<button id="btn-toggle-phone-verification-section" class="btn btn-ghost btn-xs" type="button"
aria-expanded="false" aria-controls="row-phone-verification-fold">展开设置</button>
<label class="toggle-switch" for="input-phone-verification-enabled" title="启用或禁用手机号接码流程">
<input type="checkbox" id="input-phone-verification-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
</div>
<div class="data-row phone-verification-fold-row" id="row-phone-verification-fold" style="display:none;">
<div id="phone-verification-fold" class="phone-verification-fold">
<div class="phone-verification-fold-body">
<div class="data-row" id="row-account-run-history-helper-base-url" style="display:none;">
<span class="data-label">同步服务</span>
<input type="text" id="input-account-run-history-helper-base-url" class="data-input mono"
placeholder="http://127.0.0.1:17373" />
</div>
<div class="data-row" id="row-hero-sms-platform" style="display:none;">
<span class="data-label">接码平台</span>
<span id="display-hero-sms-platform" class="data-value mono">HeroSMS / OpenAI / Thailand</span>
</div>
<div class="data-row" id="row-hero-sms-country" style="display:none;">
<span class="data-label">国家优先级</span>
<div class="data-inline hero-sms-country-stack">
<select id="select-hero-sms-country" class="data-input mono" multiple size="6" style="display:none;">
<option value="52" selected>Thailand</option>
</select>
<div class="hero-sms-country-mainline">
<div id="hero-sms-country-menu-shell" class="hero-sms-country-menu">
<button id="btn-hero-sms-country-menu" class="btn btn-outline btn-sm hero-sms-country-menu-btn" type="button" aria-haspopup="listbox" aria-expanded="false">
Thailand (1/3)
</button>
<div id="hero-sms-country-menu" class="hero-sms-country-menu-dropdown" role="listbox" aria-multiselectable="true" hidden></div>
</div>
</div>
<span class="data-value hero-sms-country-note">多选最多 3 个,按点击顺序生效。</span>
</div>
</div>
<div class="data-row" id="row-hero-sms-country-fallback" style="display:none;">
<span class="data-label">生效顺序</span>
<div class="data-inline data-value-actions">
<span id="display-hero-sms-country-fallback-order" class="data-value data-value-fill mono">Thailand(52)</span>
<button id="btn-hero-sms-country-clear" class="btn btn-ghost btn-xs data-inline-btn" type="button">清空</button>
</div>
</div>
<div class="data-row" id="row-hero-sms-acquire-priority" style="display:none;">
<span class="data-label">拿号优先级</span>
<select id="select-hero-sms-acquire-priority" class="data-input mono">
<option value="country">国家优先(默认)</option>
<option value="price">价格优先(同价按国家顺序)</option>
</select>
</div>
<div class="data-row" id="row-hero-sms-api-key" style="display:none;">
<span class="data-label">接码 API</span>
<div class="input-with-icon">
<input type="password" id="input-hero-sms-api-key" class="data-input data-input-with-icon mono"
placeholder="请输入 HeroSMS API Key" />
<button id="btn-toggle-hero-sms-api-key" class="input-icon-btn" type="button"
data-password-toggle="input-hero-sms-api-key" data-show-label="显示 HeroSMS API Key"
data-hide-label="隐藏 HeroSMS API Key" aria-label="显示 HeroSMS API Key" title="显示 HeroSMS API Key"></button>
</div>
</div>
<div class="data-row" id="row-hero-sms-max-price" style="display:none;">
<span class="data-label">价格</span>
<div class="data-inline hero-sms-price-preview-stack">
<div class="hero-sms-price-preview-head">
<button id="btn-hero-sms-price-preview" class="btn btn-outline btn-xs data-inline-btn" type="button">查询价格</button>
</div>
<div id="row-hero-sms-price-tiers" class="hero-sms-price-preview-result" style="display:none;">
<span id="display-hero-sms-price-tiers" class="data-value mono hero-sms-price-preview-text">未获取</span>
</div>
<div class="hero-sms-price-controls-grid">
<div class="hero-sms-price-control">
<span class="hero-sms-settings-caption">价格上限</span>
<div class="setting-controls">
<input type="number" id="input-hero-sms-max-price" class="data-input auto-delay-input mono hero-sms-max-price-input" placeholder="0.12" min="0" step="0.0001" title="接码价格上限;可空(空=自动价格)" />
</div>
</div>
<div class="hero-sms-price-control hero-sms-price-control-reuse">
<span class="hero-sms-settings-caption">号码复用</span>
<div class="setting-controls hero-sms-toggle-controls">
<label class="toggle-switch hero-sms-price-reuse-toggle" for="input-hero-sms-reuse-enabled" title="开启后会优先复用未超次数的可用号码">
<input type="checkbox" id="input-hero-sms-reuse-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="data-row" id="row-phone-code-settings-group" style="display:none;">
<span class="data-label">接码参数</span>
<div class="data-inline hero-sms-settings-grid">
<div id="row-phone-verification-resend-count" class="hero-sms-settings-cell" style="display:none;">
<span class="hero-sms-settings-caption">验证码重发</span>
<div class="setting-controls">
<input type="number" id="input-verification-resend-count" class="data-input auto-delay-input" value="4"
min="0" max="20" step="1" title="自动点击重新发送验证码的次数" />
<span class="data-unit"></span>
</div>
</div>
<div id="row-phone-replacement-limit" class="hero-sms-settings-cell" style="display:none;">
<span class="hero-sms-settings-caption">换号上限</span>
<div class="setting-controls">
<input type="number" id="input-phone-replacement-limit" class="data-input auto-delay-input" value="3" min="1" max="20" step="1" title="步骤 9 内部允许更换号码的最大次数" />
<span class="data-unit"></span>
</div>
</div>
<div id="row-phone-code-wait-seconds" class="hero-sms-settings-cell" style="display:none;">
<span class="hero-sms-settings-caption">验证码限时</span>
<div class="setting-controls">
<input type="number" id="input-phone-code-wait-seconds" class="data-input auto-delay-input" value="60" min="15" max="300" step="1" title="每轮等待验证码的秒数" />
<span class="data-unit"></span>
</div>
</div>
<div id="row-phone-code-timeout-windows" class="hero-sms-settings-cell" style="display:none;">
<span class="hero-sms-settings-caption">超时次数</span>
<div class="setting-controls">
<input type="number" id="input-phone-code-timeout-windows" class="data-input auto-delay-input" value="2" min="1" max="10" step="1" title="验证码超时后,最多继续等待几轮再换号" />
<span class="data-unit"></span>
</div>
</div>
<div id="row-phone-code-poll-interval-seconds" class="hero-sms-settings-cell" style="display:none;">
<span class="hero-sms-settings-caption">轮询间隔</span>
<div class="setting-controls">
<input type="number" id="input-phone-code-poll-interval-seconds" class="data-input auto-delay-input" value="5" min="1" max="30" step="1" title="向 HeroSMS 查询验证码状态的间隔秒数" />
<span class="data-unit"></span>
</div>
</div>
<div id="row-phone-code-poll-max-rounds" class="hero-sms-settings-cell" style="display:none;">
<span class="hero-sms-settings-caption">轮询次数</span>
<div class="setting-controls">
<input type="number" id="input-phone-code-poll-max-rounds" class="data-input auto-delay-input" value="4" min="1" max="120" step="1" title="每轮验证码等待窗口最多轮询次数" />
<span class="data-unit"></span>
</div>
</div>
</div>
</div>
<div class="data-row" id="row-hero-sms-runtime-pair" style="display:none;">
<span class="data-label">运行状态</span>
<div class="data-inline hero-sms-runtime-grid">
<div id="row-hero-sms-current-number" class="hero-sms-runtime-cell" style="display:none;">
<span class="hero-sms-runtime-key">当前分配</span>
<span id="display-hero-sms-current-number" class="data-value mono hero-sms-runtime-value">未分配</span>
</div>
<div id="row-hero-sms-current-code" class="hero-sms-runtime-cell" style="display:none;">
<span class="hero-sms-runtime-key">验证码</span>
<span id="display-hero-sms-current-code" class="data-value mono hero-sms-runtime-value">未获取</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="ip-proxy-section" class="data-card ip-proxy-card">
<div class="section-mini-header">
<div class="section-mini-copy">
<span class="section-label">IP 代理</span>
<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">
<span class="toggle-switch-thumb"></span>
</span>
</label>
<span id="ip-proxy-enabled-status" class="ip-proxy-enabled-status" aria-live="polite">
<span id="ip-proxy-enabled-status-dot" class="ip-proxy-enabled-status-dot" aria-hidden="true"></span>
<span id="ip-proxy-enabled-status-text" class="ip-proxy-enabled-status-text">未开启</span>
</span>
</div>
</div>
<div class="data-row ip-proxy-fold-row" id="row-ip-proxy-fold" style="display:none;">
@@ -219,7 +593,7 @@
<option value="711proxy">711Proxy(首版)</option>
</select>
<button id="btn-ip-proxy-service-login" class="btn btn-outline btn-sm data-inline-btn" type="button">
登录
注册
</button>
</div>
</div>
@@ -331,259 +705,26 @@
</div>
<div class="data-row" id="row-ip-proxy-runtime-status" style="display:none;">
<span class="data-label">代理状态</span>
<div id="ip-proxy-runtime-status" class="ip-proxy-runtime-status" aria-live="polite">
<div id="ip-proxy-runtime-status" class="ip-proxy-runtime-status state-idle">
<span id="ip-proxy-runtime-dot" class="ip-proxy-runtime-dot" aria-hidden="true"></span>
<div class="ip-proxy-runtime-content">
<div class="ip-proxy-runtime-main">
<span id="ip-proxy-runtime-text" class="data-value data-value-fill">未启用,沿用浏览器默认/全局代理。</span>
</div>
<div id="ip-proxy-runtime-text" class="ip-proxy-runtime-main">未启用,沿用浏览器默认/全局代理。</div>
<div class="ip-proxy-runtime-meta">
<span id="ip-proxy-current" class="ip-proxy-runtime-current mono truncate">未启用</span>
<button id="btn-ip-proxy-check-ip" class="btn btn-outline btn-xs data-inline-btn ip-proxy-check-ip-btn" type="button">检查IP</button>
<span id="ip-proxy-current" class="ip-proxy-runtime-current">暂无可用代理</span>
</div>
<div class="ip-proxy-runtime-details-row">
<details id="ip-proxy-runtime-details" class="ip-proxy-runtime-details" hidden>
<summary>详情</summary>
<div id="ip-proxy-runtime-details-text" class="ip-proxy-runtime-details-text"></div>
</details>
<button id="btn-ip-proxy-check-ip" class="btn btn-outline btn-xs ip-proxy-check-ip-btn" type="button">检查IP</button>
</div>
<details id="ip-proxy-runtime-details" class="ip-proxy-runtime-details" hidden>
<summary>查看详细信息</summary>
<div id="ip-proxy-runtime-details-text" class="ip-proxy-runtime-details-text"></div>
</details>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="data-row" id="row-codex2api-url" style="display:none;">
<span class="data-label">Codex2API</span>
<input type="text" id="input-codex2api-url" class="data-input"
placeholder="http://localhost:8080/admin/accounts" />
</div>
<div class="data-row" id="row-codex2api-admin-key" style="display:none;">
<span class="data-label">管理密钥</span>
<input type="password" id="input-codex2api-admin-key" class="data-input"
placeholder="请输入 Codex2API Admin Secret" />
</div>
<div class="data-row" id="row-custom-password">
<span class="data-label">账户密码</span>
<div class="input-with-icon">
<input type="password" id="input-password" class="data-input data-input-with-icon"
placeholder="codex密码,留空则自动生成" />
<button id="btn-toggle-password" class="input-icon-btn" type="button" aria-label="显示密码" title="显示密码"></button>
</div>
</div>
<div class="data-row" id="row-plus-mode">
<span class="data-label">Plus 模式</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-plus-mode-enabled" title="开启后使用 Plus Checkout + PayPal 授权流程">
<input type="checkbox" id="input-plus-mode-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
<span class="setting-caption">PayPal 订阅链路</span>
</div>
</div>
<div class="data-row" id="row-paypal-email" style="display:none;">
<span class="data-label">PayPal 账号</span>
<input type="text" id="input-paypal-email" class="data-input" placeholder="请输入 PayPal 登录邮箱" />
</div>
<div class="data-row" id="row-paypal-password" style="display:none;">
<span class="data-label">PayPal 密码</span>
<input type="password" id="input-paypal-password" class="data-input" placeholder="请输入 PayPal 登录密码" />
</div>
<div class="data-row">
<span class="data-label">邮箱服务</span>
<div class="data-inline">
<select id="select-mail-provider" class="data-select">
<option value="custom">自定义邮箱</option>
<option value="hotmail-api">Hotmail(账号池)</option>
<option value="luckmail-api">LuckMailAPI 购邮)</option>
<option value="icloud">iCloud 邮箱</option>
<option value="163">163 邮箱 (mail.163.com)</option>
<option value="163-vip">163 VIP 邮箱 (vip.163.com)</option>
<option value="126">126 邮箱 (mail.126.com)</option>
<option value="qq">QQ 邮箱 (wx.mail.qq.com)</option>
<option value="inbucket">Inbucket(自定义主机)</option>
<option value="2925">2925 邮箱 (2925.com)</option>
<option value="gmail">Gmail 邮箱 (mail.google.com)</option>
<option value="cloudflare-temp-email">Cloudflare Temp Email</option>
</select>
<button id="btn-mail-login" class="btn btn-outline btn-sm data-inline-btn" type="button" disabled>登录</button>
</div>
</div>
<div class="data-row data-check-row" id="row-custom-mail-provider-pool" style="display:none;">
<span class="data-label">自定义号池</span>
<textarea id="input-custom-mail-provider-pool" class="data-textarea"
placeholder="每行一个注册邮箱,例如&#10;alias001@example.com&#10;alias002@example.com"></textarea>
</div>
<div class="data-row" id="row-mail-2925-mode" style="display:none;">
<span class="data-label">2925 模式</span>
<div id="mail-2925-mode-group" class="choice-group" role="group" aria-label="2925 邮箱模式">
<button type="button" class="choice-btn" data-mail2925-mode="provide">提供邮箱</button>
<button type="button" class="choice-btn" data-mail2925-mode="receive">接收邮箱</button>
</div>
</div>
<div class="data-row" id="row-email-generator">
<span class="data-label">邮箱生成</span>
<select id="select-email-generator" class="data-select">
<option value="gmail-alias">Gmail +tag</option>
<option value="duck">DuckDuckGo</option>
<option value="custom-pool">自定义邮箱池</option>
<option value="cloudflare">Cloudflare</option>
<option value="icloud">iCloud 隐私邮箱</option>
<option value="cloudflare-temp-email">Cloudflare Temp Email</option>
</select>
</div>
<div class="data-row data-check-row" id="row-custom-email-pool" style="display:none;">
<span class="data-label">邮箱池</span>
<textarea id="input-custom-email-pool" class="data-textarea"
placeholder="每行一个邮箱,例如&#10;alias001@gmail.com&#10;alias002@gmail.com"></textarea>
</div>
<div class="data-row" id="row-cf-domain" style="display:none;">
<span class="data-label">CF 域名</span>
<div class="data-inline">
<select id="select-cf-domain" class="data-select"></select>
<input type="text" id="input-cf-domain" class="data-input" placeholder="例如 yourdomain.xyz"
style="display:none;" />
<button id="btn-cf-domain-mode" class="btn btn-outline btn-sm data-inline-btn" type="button">添加</button>
</div>
</div>
<div class="data-row" id="row-mail2925-pool-settings" style="display:none;">
<span class="data-label">2925 号池</span>
<div class="data-inline mail2925-base-inline">
<label class="toggle-switch mail2925-pool-toggle" id="label-mail2925-use-account-pool"
for="input-mail2925-use-account-pool" style="display:none;" title="开启后启用 2925 账号池">
<input type="checkbox" id="input-mail2925-use-account-pool" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
<span>号池</span>
</label>
<select id="select-mail2925-pool-account" class="data-select mail2925-pool-select" style="display:none;">
<option value="">请选择号池邮箱</option>
</select>
</div>
</div>
<div class="data-row" id="row-email-prefix" style="display:none;">
<span class="data-label" id="label-email-prefix">别名基邮箱</span>
<div class="data-inline">
<input type="text" id="input-email-prefix" class="data-input mail2925-base-input"
placeholder="例如 yourname@example.com" />
</div>
</div>
<div class="data-row" id="row-inbucket-host" style="display:none;">
<span class="data-label">Inbucket</span>
<input type="text" id="input-inbucket-host" class="data-input" placeholder="填写主机或 https://主机地址" />
</div>
<div class="data-row" id="row-inbucket-mailbox" style="display:none;">
<span class="data-label">邮箱名</span>
<input type="text" id="input-inbucket-mailbox" class="data-input" placeholder="例如 zju2001" />
</div>
<div class="data-row">
<span class="data-label">注册邮箱</span>
<div class="data-inline">
<input type="text" id="input-email" class="data-input" placeholder="自动生成或手动粘贴邮箱" />
<button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
</div>
</div>
<div class="data-row">
<span class="data-label">延迟</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-auto-delay-enabled">
<input type="checkbox" id="input-auto-delay-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
<div class="setting-controls">
<input type="number" id="input-auto-delay-minutes" class="data-input auto-delay-input" value="30" min="1"
max="1440" step="1" title="启动前倒计时分钟数" />
<span class="data-unit">分钟</span>
</div>
</div>
<div class="setting-group setting-group-secondary">
<span class="setting-caption">步间间隔</span>
<div class="setting-controls">
<input type="number" id="input-auto-step-delay-seconds" class="data-input auto-delay-input" value=""
min="0" max="600" step="1" title="步间延迟秒数,0 或留空则不延迟" />
<span class="data-unit"></span>
</div>
</div>
</div>
</div>
<div class="data-row">
<span class="data-label">自动重试</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-auto-skip-failures">
<input type="checkbox" id="input-auto-skip-failures" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
<div class="setting-group setting-group-secondary">
<span class="setting-caption">线程间隔</span>
<div class="setting-controls">
<input type="number" id="input-auto-skip-failures-thread-interval-minutes"
class="data-input auto-delay-input" value="0" min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
<span class="data-unit">分钟</span>
</div>
</div>
</div>
</div>
<div class="data-row" id="row-phone-verification-enabled">
<span class="data-label">接码</span>
<div class="data-inline setting-pair">
<div class="setting-group setting-group-primary">
<label class="toggle-switch" for="input-phone-verification-enabled">
<input type="checkbox" id="input-phone-verification-enabled" />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
</div>
<div class="setting-group setting-group-secondary">
<span class="setting-caption">验证码重发</span>
<div class="setting-controls">
<input type="number" id="input-verification-resend-count" class="data-input auto-delay-input" value="4"
min="0" max="20" step="1" title="自动点击重新发送验证码的次数" />
<span class="data-unit"></span>
</div>
</div>
</div>
</div>
<div class="data-row" id="row-account-run-history-helper-base-url" style="display:none;">
<span class="data-label">同步服务</span>
<input type="text" id="input-account-run-history-helper-base-url" class="data-input mono"
placeholder="http://127.0.0.1:17373" />
</div>
<div class="data-row" id="row-hero-sms-platform" style="display:none;">
<span class="data-label">接码平台</span>
<span id="display-hero-sms-platform" class="data-value mono">HeroSMS / OpenAI / Thailand</span>
</div>
<div class="data-row" id="row-hero-sms-country" style="display:none;">
<span class="data-label">接码国家</span>
<select id="select-hero-sms-country" class="data-input mono">
<option value="52" selected>Thailand</option>
</select>
</div>
<div class="data-row" id="row-hero-sms-api-key" style="display:none;">
<span class="data-label">接码 API</span>
<input type="password" id="input-hero-sms-api-key" class="data-input mono" placeholder="请输入 HeroSMS API Key" />
</div>
<div class="data-row">
<span class="data-label">OAuth</span>
<span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
</div>
<div class="data-row">
<span class="data-label">回调</span>
<div class="data-inline data-value-actions">
<span id="display-localhost-url" class="data-value data-value-fill mono truncate">等待中...</span>
<button id="btn-save-settings" class="btn btn-outline btn-sm data-inline-btn" type="button">保存</button>
</div>
</div>
</div>
<div id="cloudflare-temp-email-section" class="data-card hotmail-card" style="display:none;">
<div class="section-mini-header">
@@ -602,13 +743,23 @@
</div>
<div class="data-row" id="row-temp-email-admin-auth" style="display:none;">
<span class="data-label">Admin Auth</span>
<input type="password" id="input-temp-email-admin-auth" class="data-input data-input-with-icon"
placeholder="Cloudflare Temp Email admin password" />
<div class="input-with-icon">
<input type="password" id="input-temp-email-admin-auth" class="data-input data-input-with-icon"
placeholder="Cloudflare Temp Email admin password" />
<button id="btn-toggle-temp-email-admin-auth" class="input-icon-btn" type="button"
data-password-toggle="input-temp-email-admin-auth" data-show-label="显示 Admin Auth"
data-hide-label="隐藏 Admin Auth" aria-label="显示 Admin Auth" title="显示 Admin Auth"></button>
</div>
</div>
<div class="data-row" id="row-temp-email-custom-auth" style="display:none;">
<span class="data-label">Custom Auth</span>
<input type="password" id="input-temp-email-custom-auth" class="data-input data-input-with-icon"
placeholder="仅当站点启用了访问密码时再填写;这是额外鉴权,不替代 Admin Auth。" />
<div class="input-with-icon">
<input type="password" id="input-temp-email-custom-auth" class="data-input data-input-with-icon"
placeholder="仅当站点启用了访问密码时再填写;这是额外鉴权,不替代 Admin Auth。" />
<button id="btn-toggle-temp-email-custom-auth" class="input-icon-btn" type="button"
data-password-toggle="input-temp-email-custom-auth" data-show-label="显示 Custom Auth"
data-hide-label="隐藏 Custom Auth" aria-label="显示 Custom Auth" title="显示 Custom Auth"></button>
</div>
</div>
<div class="data-row" id="row-temp-email-receive-mailbox" style="display:none;">
<span class="data-label">邮件接收</span>
@@ -682,12 +833,24 @@
</div>
<div class="data-row">
<span class="data-label">邮箱密码</span>
<input type="password" id="input-hotmail-password" class="data-input" placeholder="可选,仅用于记录" />
<div class="input-with-icon">
<input type="password" id="input-hotmail-password" class="data-input data-input-with-icon"
placeholder="可选,仅用于记录" />
<button id="btn-toggle-hotmail-password" class="input-icon-btn" type="button"
data-password-toggle="input-hotmail-password" data-show-label="显示 Hotmail 密码"
data-hide-label="隐藏 Hotmail 密码" aria-label="显示 Hotmail 密码" title="显示 Hotmail 密码"></button>
</div>
</div>
<div class="data-row">
<span class="data-label">刷新令牌</span>
<input type="password" id="input-hotmail-refresh-token" class="data-input mono"
placeholder="必填,粘贴刷新令牌(refresh token" />
<div class="input-with-icon">
<input type="password" id="input-hotmail-refresh-token" class="data-input data-input-with-icon mono"
placeholder="必填,粘贴刷新令牌(refresh token" />
<button id="btn-toggle-hotmail-refresh-token" class="input-icon-btn" type="button"
data-password-toggle="input-hotmail-refresh-token" data-show-label="显示 Hotmail 刷新令牌"
data-hide-label="隐藏 Hotmail 刷新令牌" aria-label="显示 Hotmail 刷新令牌"
title="显示 Hotmail 刷新令牌"></button>
</div>
</div>
<div class="data-row hotmail-actions-row">
<span class="data-label"></span>
@@ -729,7 +892,13 @@
</div>
<div class="data-row">
<span class="data-label">密码</span>
<input type="password" id="input-mail2925-password" class="data-input" placeholder="2925 登录密码" />
<div class="input-with-icon">
<input type="password" id="input-mail2925-password" class="data-input data-input-with-icon"
placeholder="2925 登录密码" />
<button id="btn-toggle-mail2925-password" class="input-icon-btn" type="button"
data-password-toggle="input-mail2925-password" data-show-label="显示 2925 密码"
data-hide-label="隐藏 2925 密码" aria-label="显示 2925 密码" title="显示 2925 密码"></button>
</div>
</div>
<div class="data-row hotmail-actions-row">
<span class="data-label"></span>
@@ -759,7 +928,13 @@
</div>
<div class="data-row">
<span class="data-label">API Key</span>
<input type="password" id="input-luckmail-api-key" class="data-input mono" placeholder="请输入 LuckMail API Key" />
<div class="input-with-icon">
<input type="password" id="input-luckmail-api-key" class="data-input data-input-with-icon mono"
placeholder="请输入 LuckMail API Key" />
<button id="btn-toggle-luckmail-api-key" class="input-icon-btn" type="button"
data-password-toggle="input-luckmail-api-key" data-show-label="显示 LuckMail API Key"
data-hide-label="隐藏 LuckMail API Key" aria-label="显示 LuckMail API Key" title="显示 LuckMail API Key"></button>
</div>
</div>
<div class="data-row">
<span class="data-label">Base URL</span>
@@ -986,6 +1161,22 @@
</div>
</div>
<div id="shared-form-modal" class="modal-overlay" hidden>
<div class="modal-card modal-card-form">
<div class="modal-header">
<span id="shared-form-modal-title" class="modal-title">添加账号</span>
<button id="btn-shared-form-modal-close" class="modal-close" type="button" aria-label="关闭">×</button>
</div>
<p id="shared-form-modal-message" class="modal-message" hidden></p>
<p id="shared-form-modal-alert" class="modal-alert" hidden></p>
<div id="shared-form-modal-fields" class="modal-form-fields"></div>
<div class="modal-actions">
<button id="btn-shared-form-modal-cancel" class="btn btn-ghost btn-sm" type="button">取消</button>
<button id="btn-shared-form-modal-confirm" class="btn btn-primary btn-sm" type="button">确认</button>
</div>
</div>
</div>
<div id="auto-start-modal" class="modal-overlay" hidden>
<div class="modal-card">
<div class="modal-header">
@@ -1012,6 +1203,7 @@
<input id="input-import-settings-file" type="file" accept=".json,application/json" hidden />
<script src="../managed-alias-utils.js"></script>
<script src="../mail2925-utils.js"></script>
<script src="../paypal-utils.js"></script>
<script src="../icloud-utils.js"></script>
<script src="../mail-provider-utils.js"></script>
<script src="../hotmail-utils.js"></script>
@@ -1020,8 +1212,10 @@
<script src="update-service.js"></script>
<script src="contribution-content-update-service.js"></script>
<script src="account-pool-ui.js"></script>
<script src="form-dialog.js"></script>
<script src="hotmail-manager.js"></script>
<script src="mail-2925-manager.js"></script>
<script src="paypal-manager.js"></script>
<script src="icloud-manager.js"></script>
<script src="luckmail-manager.js"></script>
<script src="ip-proxy-provider-711proxy.js"></script>
+1567 -83
View File
File diff suppressed because it is too large Load Diff