feat: upgrade custom email pool with manager UI and entry states
(cherry picked from commit 3cf5f22d40a4ef563a3d06751bbb25b003d4ef1e)
This commit is contained in:
+119
-2
@@ -532,6 +532,7 @@ const PERSISTED_SETTING_DEFAULTS = {
|
|||||||
emailGenerator: 'duck',
|
emailGenerator: 'duck',
|
||||||
customMailProviderPool: [],
|
customMailProviderPool: [],
|
||||||
customEmailPool: [],
|
customEmailPool: [],
|
||||||
|
customEmailPoolEntries: [],
|
||||||
autoDeleteUsedIcloudAlias: false,
|
autoDeleteUsedIcloudAlias: false,
|
||||||
icloudHostPreference: 'auto',
|
icloudHostPreference: 'auto',
|
||||||
icloudTargetMailboxType: 'icloud-inbox',
|
icloudTargetMailboxType: 'icloud-inbox',
|
||||||
@@ -1432,6 +1433,36 @@ function normalizeCustomEmailPool(value = []) {
|
|||||||
.filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
|
.filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeCustomEmailPoolEntryObjects(value = []) {
|
||||||
|
const source = Array.isArray(value) ? value : [];
|
||||||
|
const seenEmails = new Set();
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
for (const rawEntry of source) {
|
||||||
|
const asObject = rawEntry && typeof rawEntry === 'object'
|
||||||
|
? rawEntry
|
||||||
|
: { email: rawEntry };
|
||||||
|
const email = String(asObject.email || '').trim().toLowerCase();
|
||||||
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seenEmails.has(email)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenEmails.add(email);
|
||||||
|
entries.push({
|
||||||
|
id: String(asObject.id || `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`),
|
||||||
|
email,
|
||||||
|
enabled: asObject.enabled !== undefined ? Boolean(asObject.enabled) : true,
|
||||||
|
used: Boolean(asObject.used),
|
||||||
|
note: String(asObject.note || '').trim(),
|
||||||
|
lastUsedAt: Number.isFinite(Number(asObject.lastUsedAt)) ? Number(asObject.lastUsedAt) : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
function isCustomEmailPoolGenerator(stateOrValue = {}) {
|
function isCustomEmailPoolGenerator(stateOrValue = {}) {
|
||||||
const generator = typeof stateOrValue === 'string'
|
const generator = typeof stateOrValue === 'string'
|
||||||
? stateOrValue
|
? stateOrValue
|
||||||
@@ -1443,9 +1474,91 @@ function isCustomEmailPoolGenerator(stateOrValue = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCustomEmailPool(state = {}) {
|
function getCustomEmailPool(state = {}) {
|
||||||
|
if (typeof normalizeCustomEmailPoolEntryObjects === 'function') {
|
||||||
|
const entries = normalizeCustomEmailPoolEntryObjects(state?.customEmailPoolEntries);
|
||||||
|
if (entries.length > 0) {
|
||||||
|
return entries
|
||||||
|
.filter((entry) => entry.enabled && !entry.used)
|
||||||
|
.map((entry) => entry.email);
|
||||||
|
}
|
||||||
|
}
|
||||||
return normalizeCustomEmailPool(state?.customEmailPool);
|
return normalizeCustomEmailPool(state?.customEmailPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCustomEmailPoolEntries(state = {}) {
|
||||||
|
const entries = normalizeCustomEmailPoolEntryObjects(state?.customEmailPoolEntries);
|
||||||
|
if (entries.length > 0) {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
return normalizeCustomEmailPool(state?.customEmailPool).map((email) => ({
|
||||||
|
id: `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
||||||
|
email,
|
||||||
|
enabled: true,
|
||||||
|
used: false,
|
||||||
|
note: '',
|
||||||
|
lastUsedAt: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function markCurrentCustomEmailPoolEntryUsed(state = {}) {
|
||||||
|
if (!isCustomEmailPoolGenerator(state)) {
|
||||||
|
return { updated: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentEmail = String(state?.email || '').trim().toLowerCase();
|
||||||
|
if (!currentEmail) {
|
||||||
|
return { updated: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = getCustomEmailPoolEntries(state);
|
||||||
|
if (!entries.length) {
|
||||||
|
return { updated: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
const now = Date.now();
|
||||||
|
const nextEntries = entries.map((entry) => {
|
||||||
|
if (entry.email !== currentEmail) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
if (entry.used && entry.lastUsedAt) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
return {
|
||||||
|
...entry,
|
||||||
|
used: true,
|
||||||
|
lastUsedAt: now,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
return { updated: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextCustomEmailPool = nextEntries
|
||||||
|
.filter((entry) => entry.enabled && !entry.used)
|
||||||
|
.map((entry) => entry.email);
|
||||||
|
await setPersistentSettings({
|
||||||
|
customEmailPoolEntries: nextEntries,
|
||||||
|
customEmailPool: nextCustomEmailPool,
|
||||||
|
});
|
||||||
|
await setState({
|
||||||
|
customEmailPoolEntries: nextEntries,
|
||||||
|
customEmailPool: nextCustomEmailPool,
|
||||||
|
});
|
||||||
|
broadcastDataUpdate({
|
||||||
|
customEmailPoolEntries: nextEntries,
|
||||||
|
customEmailPool: nextCustomEmailPool,
|
||||||
|
});
|
||||||
|
await addLog(`自定义邮箱池:流程成功后已将 ${currentEmail} 标记为已用。`, 'ok');
|
||||||
|
return {
|
||||||
|
updated: true,
|
||||||
|
customEmailPoolEntries: nextEntries,
|
||||||
|
customEmailPool: nextCustomEmailPool,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getCustomEmailPoolEmailForRun(state = {}, targetRun = 1) {
|
function getCustomEmailPoolEmailForRun(state = {}, targetRun = 1) {
|
||||||
const entries = getCustomEmailPool(state);
|
const entries = getCustomEmailPool(state);
|
||||||
const numericRun = Math.max(1, Math.floor(Number(targetRun) || 1));
|
const numericRun = Math.max(1, Math.floor(Number(targetRun) || 1));
|
||||||
@@ -1814,6 +1927,8 @@ function normalizePersistentSettingValue(key, value) {
|
|||||||
case 'customMailProviderPool':
|
case 'customMailProviderPool':
|
||||||
case 'customEmailPool':
|
case 'customEmailPool':
|
||||||
return normalizeCustomEmailPool(value);
|
return normalizeCustomEmailPool(value);
|
||||||
|
case 'customEmailPoolEntries':
|
||||||
|
return normalizeCustomEmailPoolEntryObjects(value);
|
||||||
case 'autoDeleteUsedIcloudAlias':
|
case 'autoDeleteUsedIcloudAlias':
|
||||||
case 'accountRunHistoryTextEnabled':
|
case 'accountRunHistoryTextEnabled':
|
||||||
case 'cloudflareTempEmailUseRandomSubdomain':
|
case 'cloudflareTempEmailUseRandomSubdomain':
|
||||||
@@ -7687,6 +7802,9 @@ async function handleStepData(step, payload) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
|
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
|
||||||
|
if (typeof markCurrentCustomEmailPoolEntryUsed === 'function') {
|
||||||
|
await markCurrentCustomEmailPoolEntryUsed(latestState);
|
||||||
|
}
|
||||||
const shouldClearCustomPoolEmail = String(latestState?.emailGenerator || '').trim().toLowerCase() === (
|
const shouldClearCustomPoolEmail = String(latestState?.emailGenerator || '').trim().toLowerCase() === (
|
||||||
typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
|
typeof CUSTOM_EMAIL_POOL_GENERATOR === 'string'
|
||||||
? CUSTOM_EMAIL_POOL_GENERATOR
|
? CUSTOM_EMAIL_POOL_GENERATOR
|
||||||
@@ -9794,8 +9912,7 @@ const messageRouter = self.MultiPageBackgroundMessageRouter?.createMessageRouter
|
|||||||
runIpProxyAutoSync,
|
runIpProxyAutoSync,
|
||||||
listIcloudAliases,
|
listIcloudAliases,
|
||||||
listLuckmailPurchasesForManagement,
|
listLuckmailPurchasesForManagement,
|
||||||
refreshIpProxyPool,
|
markCurrentCustomEmailPoolEntryUsed,
|
||||||
getCurrentPayPalAccount,
|
|
||||||
getCurrentMail2925Account,
|
getCurrentMail2925Account,
|
||||||
normalizeHotmailAccounts,
|
normalizeHotmailAccounts,
|
||||||
normalizeMail2925Accounts,
|
normalizeMail2925Accounts,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
runIpProxyAutoSync,
|
runIpProxyAutoSync,
|
||||||
listIcloudAliases,
|
listIcloudAliases,
|
||||||
listLuckmailPurchasesForManagement,
|
listLuckmailPurchasesForManagement,
|
||||||
refreshIpProxyPool,
|
markCurrentCustomEmailPoolEntryUsed,
|
||||||
normalizeHotmailAccounts,
|
normalizeHotmailAccounts,
|
||||||
normalizeMail2925Accounts,
|
normalizeMail2925Accounts,
|
||||||
normalizePayPalAccounts,
|
normalizePayPalAccounts,
|
||||||
@@ -339,6 +339,62 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 8:
|
||||||
|
await setState({
|
||||||
|
lastEmailTimestamp: payload.emailTimestamp || null,
|
||||||
|
loginVerificationRequestedAt: null,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
if (payload.localhostUrl) {
|
||||||
|
if (!isLocalhostOAuthCallbackUrl(payload.localhostUrl)) {
|
||||||
|
throw new Error('步骤 9 返回了无效的 localhost OAuth 回调地址。');
|
||||||
|
}
|
||||||
|
await setState({ localhostUrl: payload.localhostUrl });
|
||||||
|
broadcastDataUpdate({ localhostUrl: payload.localhostUrl });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 10: {
|
||||||
|
if (payload.localhostUrl) {
|
||||||
|
await closeLocalhostCallbackTabs(payload.localhostUrl);
|
||||||
|
}
|
||||||
|
const latestState = await getState();
|
||||||
|
if (latestState.currentHotmailAccountId && isHotmailProvider(latestState)) {
|
||||||
|
await patchHotmailAccount(latestState.currentHotmailAccountId, {
|
||||||
|
used: true,
|
||||||
|
lastUsedAt: Date.now(),
|
||||||
|
});
|
||||||
|
await addLog('当前 Hotmail 账号已自动标记为已用。', 'ok');
|
||||||
|
}
|
||||||
|
if (String(latestState.mailProvider || '').trim().toLowerCase() === '2925' && latestState.currentMail2925AccountId) {
|
||||||
|
await patchMail2925Account(latestState.currentMail2925AccountId, {
|
||||||
|
lastUsedAt: Date.now(),
|
||||||
|
lastError: '',
|
||||||
|
});
|
||||||
|
await addLog('当前 2925 账号已记录最近使用时间。', 'ok');
|
||||||
|
}
|
||||||
|
if (isLuckmailProvider(latestState)) {
|
||||||
|
const currentPurchase = getCurrentLuckmailPurchase(latestState);
|
||||||
|
if (currentPurchase?.id) {
|
||||||
|
await setLuckmailPurchaseUsedState(currentPurchase.id, true);
|
||||||
|
await addLog(`当前 LuckMail 邮箱 ${currentPurchase.email_address} 已在本地标记为已用。`, 'ok');
|
||||||
|
}
|
||||||
|
await clearLuckmailRuntimeState({ clearEmail: true });
|
||||||
|
await addLog('当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。', 'ok');
|
||||||
|
}
|
||||||
|
const localhostPrefix = buildLocalhostCleanupPrefix(payload.localhostUrl);
|
||||||
|
if (localhostPrefix) {
|
||||||
|
await closeTabsByUrlPrefix(localhostPrefix, {
|
||||||
|
excludeUrls: [payload.localhostUrl],
|
||||||
|
excludeLocalhostCallbacks: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await finalizeIcloudAliasAfterSuccessfulFlow(latestState);
|
||||||
|
if (typeof markCurrentCustomEmailPoolEntryUsed === 'function') {
|
||||||
|
await markCurrentCustomEmailPoolEntryUsed(latestState);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,536 @@
|
|||||||
|
(function attachSidepanelCustomEmailPoolManager(globalScope) {
|
||||||
|
function createCustomEmailPoolManager(context = {}) {
|
||||||
|
const {
|
||||||
|
dom,
|
||||||
|
helpers,
|
||||||
|
state,
|
||||||
|
actions,
|
||||||
|
constants = {},
|
||||||
|
} = context;
|
||||||
|
|
||||||
|
const copyIcon = constants.copyIcon || '';
|
||||||
|
|
||||||
|
let renderedEntries = [];
|
||||||
|
let selectedEntryIds = new Set();
|
||||||
|
let searchTerm = '';
|
||||||
|
let filterMode = 'all';
|
||||||
|
let refreshQueued = false;
|
||||||
|
let loading = false;
|
||||||
|
|
||||||
|
function normalizeEmail(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidEmail(value = '') {
|
||||||
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizeEmail(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEntryId() {
|
||||||
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||||
|
return crypto.randomUUID();
|
||||||
|
}
|
||||||
|
return `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeEntry(rawEntry = {}) {
|
||||||
|
const email = normalizeEmail(rawEntry?.email || '');
|
||||||
|
if (!isValidEmail(email)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: String(rawEntry?.id || createEntryId()),
|
||||||
|
email,
|
||||||
|
enabled: rawEntry?.enabled !== undefined ? Boolean(rawEntry.enabled) : true,
|
||||||
|
used: Boolean(rawEntry?.used),
|
||||||
|
note: String(rawEntry?.note || '').trim(),
|
||||||
|
lastUsedAt: Number.isFinite(Number(rawEntry?.lastUsedAt)) ? Number(rawEntry.lastUsedAt) : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeEntries(entries = []) {
|
||||||
|
if (!Array.isArray(entries)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const seenEmails = new Set();
|
||||||
|
const normalized = [];
|
||||||
|
for (const entry of entries) {
|
||||||
|
const item = normalizeEntry(entry);
|
||||||
|
if (!item) continue;
|
||||||
|
if (seenEmails.has(item.email)) continue;
|
||||||
|
seenEmails.add(item.email);
|
||||||
|
normalized.push(item);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function withCurrentFlag(entries = renderedEntries) {
|
||||||
|
const currentEmail = normalizeEmail(state.getCurrentEmail?.());
|
||||||
|
return normalizeEntries(entries).map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
current: Boolean(currentEmail) && entry.email === currentEmail,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSearchText(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilteredEntries(entries = renderedEntries) {
|
||||||
|
const normalizedSearchTerm = normalizeSearchText(searchTerm);
|
||||||
|
return withCurrentFlag(entries).filter((entry) => {
|
||||||
|
const matchesFilter = (() => {
|
||||||
|
switch (filterMode) {
|
||||||
|
case 'enabled': return Boolean(entry.enabled);
|
||||||
|
case 'disabled': return !entry.enabled;
|
||||||
|
case 'used': return Boolean(entry.used);
|
||||||
|
case 'unused': return !entry.used;
|
||||||
|
case 'current': return Boolean(entry.current);
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!matchesFilter) return false;
|
||||||
|
if (!normalizedSearchTerm) return true;
|
||||||
|
|
||||||
|
const haystack = [
|
||||||
|
entry.email,
|
||||||
|
entry.note,
|
||||||
|
entry.enabled ? 'enabled 启用' : 'disabled 停用',
|
||||||
|
entry.used ? 'used 已用' : 'unused 未用',
|
||||||
|
entry.current ? 'current 当前' : '',
|
||||||
|
].join(' ').toLowerCase();
|
||||||
|
|
||||||
|
return haystack.includes(normalizedSearchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneSelection(entries = renderedEntries) {
|
||||||
|
const existingIds = new Set(withCurrentFlag(entries).map((entry) => String(entry.id)));
|
||||||
|
selectedEntryIds = new Set([...selectedEntryIds].filter((id) => existingIds.has(id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBulkUi(visibleEntries = getFilteredEntries()) {
|
||||||
|
if (!dom.checkboxCustomEmailPoolSelectAll || !dom.customEmailPoolSelectionSummary) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visibleIds = visibleEntries.map((entry) => String(entry.id));
|
||||||
|
const selectedVisibleCount = visibleIds.filter((id) => selectedEntryIds.has(id)).length;
|
||||||
|
const hasVisible = visibleIds.length > 0;
|
||||||
|
const hasSelection = selectedEntryIds.size > 0;
|
||||||
|
|
||||||
|
dom.checkboxCustomEmailPoolSelectAll.checked = hasVisible && selectedVisibleCount === visibleIds.length;
|
||||||
|
dom.checkboxCustomEmailPoolSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleIds.length;
|
||||||
|
dom.checkboxCustomEmailPoolSelectAll.disabled = loading || !hasVisible;
|
||||||
|
dom.customEmailPoolSelectionSummary.textContent = `已选 ${selectedEntryIds.size} 个(当前显示 ${visibleIds.length} 个)`;
|
||||||
|
|
||||||
|
if (dom.btnCustomEmailPoolBulkUsed) dom.btnCustomEmailPoolBulkUsed.disabled = loading || !hasSelection;
|
||||||
|
if (dom.btnCustomEmailPoolBulkUnused) dom.btnCustomEmailPoolBulkUnused.disabled = loading || !hasSelection;
|
||||||
|
if (dom.btnCustomEmailPoolBulkEnable) dom.btnCustomEmailPoolBulkEnable.disabled = loading || !hasSelection;
|
||||||
|
if (dom.btnCustomEmailPoolBulkDisable) dom.btnCustomEmailPoolBulkDisable.disabled = loading || !hasSelection;
|
||||||
|
if (dom.btnCustomEmailPoolBulkDelete) dom.btnCustomEmailPoolBulkDelete.disabled = loading || !hasSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLoadingState(nextLoading, summary = '') {
|
||||||
|
loading = Boolean(nextLoading);
|
||||||
|
if (dom.btnCustomEmailPoolImport) dom.btnCustomEmailPoolImport.disabled = loading;
|
||||||
|
if (dom.btnCustomEmailPoolRefresh) dom.btnCustomEmailPoolRefresh.disabled = loading;
|
||||||
|
if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = loading;
|
||||||
|
if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = loading;
|
||||||
|
if (dom.inputCustomEmailPoolImport) dom.inputCustomEmailPoolImport.disabled = loading;
|
||||||
|
if (dom.inputCustomEmailPoolSearch) dom.inputCustomEmailPoolSearch.disabled = loading;
|
||||||
|
if (dom.selectCustomEmailPoolFilter) dom.selectCustomEmailPoolFilter.disabled = loading;
|
||||||
|
|
||||||
|
if (summary && dom.customEmailPoolSummary) {
|
||||||
|
dom.customEmailPoolSummary.textContent = summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBulkUi(getFilteredEntries());
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCustomEmailPoolEntries(entries = state.getEntries?.()) {
|
||||||
|
if (!dom.customEmailPoolList || !dom.customEmailPoolSummary) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderedEntries = normalizeEntries(entries);
|
||||||
|
pruneSelection(renderedEntries);
|
||||||
|
dom.customEmailPoolList.innerHTML = '';
|
||||||
|
|
||||||
|
if (!renderedEntries.length) {
|
||||||
|
selectedEntryIds.clear();
|
||||||
|
dom.customEmailPoolList.innerHTML = '<div class="luckmail-empty">还没有自定义邮箱,先导入一批邮箱再开始。</div>';
|
||||||
|
dom.customEmailPoolSummary.textContent = '导入你提前准备好的注册邮箱,每行一个邮箱地址。';
|
||||||
|
if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = true;
|
||||||
|
if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = true;
|
||||||
|
updateBulkUi([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entriesWithCurrent = withCurrentFlag(renderedEntries);
|
||||||
|
const usedCount = entriesWithCurrent.filter((entry) => entry.used).length;
|
||||||
|
const enabledCount = entriesWithCurrent.filter((entry) => entry.enabled).length;
|
||||||
|
dom.customEmailPoolSummary.textContent = `已加载 ${entriesWithCurrent.length} 个邮箱,其中 ${enabledCount} 个启用,${usedCount} 个已标记为已用。`;
|
||||||
|
if (dom.btnCustomEmailPoolClearUsed) dom.btnCustomEmailPoolClearUsed.disabled = loading || usedCount === 0;
|
||||||
|
if (dom.btnCustomEmailPoolDeleteAll) dom.btnCustomEmailPoolDeleteAll.disabled = loading || entriesWithCurrent.length === 0;
|
||||||
|
|
||||||
|
const visibleEntries = getFilteredEntries(entriesWithCurrent);
|
||||||
|
if (!visibleEntries.length) {
|
||||||
|
dom.customEmailPoolList.innerHTML = '<div class="luckmail-empty">没有匹配当前筛选条件的邮箱。</div>';
|
||||||
|
updateBulkUi([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of visibleEntries) {
|
||||||
|
const entryId = String(entry.id);
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = `luckmail-item${entry.current ? ' is-current' : ''}`;
|
||||||
|
item.innerHTML = `
|
||||||
|
<input class="luckmail-item-check" type="checkbox" data-action="select" ${selectedEntryIds.has(entryId) ? 'checked' : ''} />
|
||||||
|
<div class="luckmail-item-main">
|
||||||
|
<div class="luckmail-item-email-row">
|
||||||
|
<div class="luckmail-item-email">${helpers.escapeHtml(entry.email || '(未知邮箱)')}</div>
|
||||||
|
<button
|
||||||
|
class="hotmail-copy-btn"
|
||||||
|
type="button"
|
||||||
|
data-action="copy-email"
|
||||||
|
title="复制邮箱"
|
||||||
|
aria-label="复制邮箱 ${helpers.escapeHtml(entry.email || '')}"
|
||||||
|
>${copyIcon}</button>
|
||||||
|
</div>
|
||||||
|
<div class="luckmail-item-meta">
|
||||||
|
${entry.current ? '<span class="luckmail-tag current">当前</span>' : ''}
|
||||||
|
${entry.used ? '<span class="luckmail-tag used">已用</span>' : '<span class="luckmail-tag active">未用</span>'}
|
||||||
|
${entry.enabled ? '<span class="luckmail-tag active">启用</span>' : '<span class="luckmail-tag disabled">停用</span>'}
|
||||||
|
${entry.note ? `<span class="luckmail-tag">${helpers.escapeHtml(entry.note)}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="luckmail-item-actions">
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="use">使用此邮箱</button>
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-used">${helpers.escapeHtml(entry.used ? '标记未用' : '标记已用')}</button>
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-enabled">${helpers.escapeHtml(entry.enabled ? '停用' : '启用')}</button>
|
||||||
|
<button class="btn btn-outline btn-xs" type="button" data-action="delete">删除</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
item.querySelector('[data-action="select"]').addEventListener('change', (event) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
selectedEntryIds.add(entryId);
|
||||||
|
} else {
|
||||||
|
selectedEntryIds.delete(entryId);
|
||||||
|
}
|
||||||
|
updateBulkUi(visibleEntries);
|
||||||
|
});
|
||||||
|
|
||||||
|
item.querySelector('[data-action="copy-email"]').addEventListener('click', async () => {
|
||||||
|
await helpers.copyTextToClipboard(entry.email || '');
|
||||||
|
helpers.showToast('邮箱已复制', 'success', 1600);
|
||||||
|
});
|
||||||
|
|
||||||
|
item.querySelector('[data-action="use"]').addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
setLoadingState(true, '正在切换当前邮箱...');
|
||||||
|
await actions.setRuntimeEmail?.(entry.email);
|
||||||
|
helpers.showToast(`已切换到 ${entry.email}`, 'success', 1800);
|
||||||
|
queueCustomEmailPoolRefresh();
|
||||||
|
} catch (error) {
|
||||||
|
helpers.showToast(`切换邮箱失败:${error.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLoadingState(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
item.querySelector('[data-action="toggle-used"]').addEventListener('click', async () => {
|
||||||
|
await patchEntries((entriesList) => entriesList.map((candidate) => (
|
||||||
|
String(candidate.id) === entryId
|
||||||
|
? {
|
||||||
|
...candidate,
|
||||||
|
used: !entry.used,
|
||||||
|
lastUsedAt: !entry.used ? Date.now() : candidate.lastUsedAt,
|
||||||
|
}
|
||||||
|
: candidate
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
item.querySelector('[data-action="toggle-enabled"]').addEventListener('click', async () => {
|
||||||
|
await patchEntries((entriesList) => entriesList.map((candidate) => (
|
||||||
|
String(candidate.id) === entryId
|
||||||
|
? { ...candidate, enabled: !entry.enabled }
|
||||||
|
: candidate
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
item.querySelector('[data-action="delete"]').addEventListener('click', async () => {
|
||||||
|
await deleteEntries({
|
||||||
|
ids: [entry.id],
|
||||||
|
}, `确认删除 ${entry.email} 吗?此操作不可撤销。`);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.customEmailPoolList.appendChild(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBulkUi(visibleEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function patchEntries(mutator) {
|
||||||
|
const previousEntries = normalizeEntries(state.getEntries?.() || []);
|
||||||
|
const nextEntries = normalizeEntries(mutator(previousEntries.map((entry) => ({ ...entry }))));
|
||||||
|
|
||||||
|
setLoadingState(true, '正在更新自定义邮箱池...');
|
||||||
|
state.setEntries?.(nextEntries);
|
||||||
|
renderCustomEmailPoolEntries(nextEntries);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await actions.persistEntries?.();
|
||||||
|
} catch (error) {
|
||||||
|
state.setEntries?.(previousEntries);
|
||||||
|
renderCustomEmailPoolEntries(previousEntries);
|
||||||
|
helpers.showToast(`更新自定义邮箱池失败:${error.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteEntries(payload = {}, confirmMessage = '') {
|
||||||
|
const confirmed = await helpers.openConfirmModal({
|
||||||
|
title: '删除邮箱',
|
||||||
|
message: confirmMessage || '确认删除选中的邮箱吗?此操作不可撤销。',
|
||||||
|
confirmLabel: '确认删除',
|
||||||
|
confirmVariant: 'btn-danger',
|
||||||
|
});
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ids = Array.isArray(payload.ids)
|
||||||
|
? payload.ids.map((id) => String(id || '').trim()).filter(Boolean)
|
||||||
|
: [];
|
||||||
|
const mode = String(payload.mode || '').trim().toLowerCase();
|
||||||
|
|
||||||
|
await patchEntries((entriesList) => {
|
||||||
|
if (mode === 'all') {
|
||||||
|
selectedEntryIds.clear();
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (mode === 'used') {
|
||||||
|
const usedIds = new Set(entriesList.filter((entry) => entry.used).map((entry) => String(entry.id)));
|
||||||
|
usedIds.forEach((id) => selectedEntryIds.delete(id));
|
||||||
|
return entriesList.filter((entry) => !entry.used);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetIds = new Set(ids);
|
||||||
|
ids.forEach((id) => selectedEntryIds.delete(id));
|
||||||
|
return entriesList.filter((entry) => !targetIds.has(String(entry.id)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importEntriesFromTextarea() {
|
||||||
|
const text = String(dom.inputCustomEmailPoolImport?.value || '');
|
||||||
|
if (!text.trim()) {
|
||||||
|
helpers.showToast('请先粘贴邮箱列表,每行一个邮箱。', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousEntries = normalizeEntries(state.getEntries?.() || []);
|
||||||
|
const knownEmails = new Set(previousEntries.map((entry) => entry.email));
|
||||||
|
const importedEntries = [];
|
||||||
|
let skippedCount = 0;
|
||||||
|
|
||||||
|
for (const line of String(text || '').split(/[\r\n,,;;]+/)) {
|
||||||
|
const email = normalizeEmail(line);
|
||||||
|
if (!email) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isValidEmail(email) || knownEmails.has(email)) {
|
||||||
|
skippedCount += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
knownEmails.add(email);
|
||||||
|
importedEntries.push({
|
||||||
|
id: createEntryId(),
|
||||||
|
email,
|
||||||
|
enabled: true,
|
||||||
|
used: false,
|
||||||
|
note: '',
|
||||||
|
lastUsedAt: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!importedEntries.length && skippedCount > 0) {
|
||||||
|
helpers.showToast('没有可导入的新邮箱(可能都重复或无效)。', 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextEntries = normalizeEntries([...previousEntries, ...importedEntries]);
|
||||||
|
setLoadingState(true, '正在导入邮箱...');
|
||||||
|
state.setEntries?.(nextEntries);
|
||||||
|
renderCustomEmailPoolEntries(nextEntries);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await actions.persistEntries?.();
|
||||||
|
if (dom.inputCustomEmailPoolImport) {
|
||||||
|
dom.inputCustomEmailPoolImport.value = '';
|
||||||
|
}
|
||||||
|
helpers.showToast(
|
||||||
|
skippedCount > 0
|
||||||
|
? `已导入 ${importedEntries.length} 个邮箱,跳过 ${skippedCount} 条无效或重复数据。`
|
||||||
|
: `已导入 ${importedEntries.length} 个邮箱。`,
|
||||||
|
importedEntries.length > 0 ? 'success' : 'warn',
|
||||||
|
2400
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
state.setEntries?.(previousEntries);
|
||||||
|
renderCustomEmailPoolEntries(previousEntries);
|
||||||
|
helpers.showToast(`导入邮箱失败:${error.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
setLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshCustomEmailPoolEntries(options = {}) {
|
||||||
|
const { silent = false } = options;
|
||||||
|
if (state.isVisible && !state.isVisible()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
setLoadingState(true, '正在刷新自定义邮箱池...');
|
||||||
|
}
|
||||||
|
renderCustomEmailPoolEntries(state.getEntries?.());
|
||||||
|
if (!silent) {
|
||||||
|
setLoadingState(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueCustomEmailPoolRefresh() {
|
||||||
|
if (refreshQueued) return;
|
||||||
|
refreshQueued = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
refreshQueued = false;
|
||||||
|
refreshCustomEmailPoolEntries({ silent: true });
|
||||||
|
}, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
selectedEntryIds.clear();
|
||||||
|
searchTerm = '';
|
||||||
|
filterMode = 'all';
|
||||||
|
if (dom.inputCustomEmailPoolSearch) dom.inputCustomEmailPoolSearch.value = '';
|
||||||
|
if (dom.selectCustomEmailPoolFilter) dom.selectCustomEmailPoolFilter.value = 'all';
|
||||||
|
if (dom.customEmailPoolList) {
|
||||||
|
dom.customEmailPoolList.innerHTML = '';
|
||||||
|
}
|
||||||
|
if (dom.customEmailPoolSummary) {
|
||||||
|
dom.customEmailPoolSummary.textContent = '导入你提前准备好的注册邮箱,每行一个邮箱地址。';
|
||||||
|
}
|
||||||
|
updateBulkUi([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindEvents() {
|
||||||
|
dom.btnCustomEmailPoolRefresh?.addEventListener('click', async () => {
|
||||||
|
await refreshCustomEmailPoolEntries();
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolImport?.addEventListener('click', async () => {
|
||||||
|
await importEntriesFromTextarea();
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.inputCustomEmailPoolImport?.addEventListener('keydown', async (event) => {
|
||||||
|
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
await importEntriesFromTextarea();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.inputCustomEmailPoolSearch?.addEventListener('input', (event) => {
|
||||||
|
searchTerm = normalizeSearchText(event.target.value);
|
||||||
|
renderCustomEmailPoolEntries(renderedEntries);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.selectCustomEmailPoolFilter?.addEventListener('change', (event) => {
|
||||||
|
filterMode = String(event.target.value || 'all');
|
||||||
|
renderCustomEmailPoolEntries(renderedEntries);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.checkboxCustomEmailPoolSelectAll?.addEventListener('change', (event) => {
|
||||||
|
const visibleEntries = getFilteredEntries(renderedEntries);
|
||||||
|
const visibleIds = visibleEntries.map((entry) => String(entry.id));
|
||||||
|
if (event.target.checked) {
|
||||||
|
visibleIds.forEach((id) => selectedEntryIds.add(id));
|
||||||
|
} else {
|
||||||
|
visibleIds.forEach((id) => selectedEntryIds.delete(id));
|
||||||
|
}
|
||||||
|
renderCustomEmailPoolEntries(renderedEntries);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolBulkUsed?.addEventListener('click', async () => {
|
||||||
|
const targetIds = new Set([...selectedEntryIds]);
|
||||||
|
await patchEntries((entriesList) => entriesList.map((entry) => (
|
||||||
|
targetIds.has(String(entry.id))
|
||||||
|
? { ...entry, used: true, lastUsedAt: entry.lastUsedAt || Date.now() }
|
||||||
|
: entry
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolBulkUnused?.addEventListener('click', async () => {
|
||||||
|
const targetIds = new Set([...selectedEntryIds]);
|
||||||
|
await patchEntries((entriesList) => entriesList.map((entry) => (
|
||||||
|
targetIds.has(String(entry.id))
|
||||||
|
? { ...entry, used: false }
|
||||||
|
: entry
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolBulkEnable?.addEventListener('click', async () => {
|
||||||
|
const targetIds = new Set([...selectedEntryIds]);
|
||||||
|
await patchEntries((entriesList) => entriesList.map((entry) => (
|
||||||
|
targetIds.has(String(entry.id))
|
||||||
|
? { ...entry, enabled: true }
|
||||||
|
: entry
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolBulkDisable?.addEventListener('click', async () => {
|
||||||
|
const targetIds = new Set([...selectedEntryIds]);
|
||||||
|
await patchEntries((entriesList) => entriesList.map((entry) => (
|
||||||
|
targetIds.has(String(entry.id))
|
||||||
|
? { ...entry, enabled: false }
|
||||||
|
: entry
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolBulkDelete?.addEventListener('click', async () => {
|
||||||
|
await deleteEntries({
|
||||||
|
ids: [...selectedEntryIds],
|
||||||
|
}, `确认删除当前选中的 ${selectedEntryIds.size} 个邮箱吗?此操作不可撤销。`);
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolClearUsed?.addEventListener('click', async () => {
|
||||||
|
await deleteEntries({
|
||||||
|
mode: 'used',
|
||||||
|
}, '确认删除当前所有已用邮箱吗?此操作不可撤销。');
|
||||||
|
});
|
||||||
|
|
||||||
|
dom.btnCustomEmailPoolDeleteAll?.addEventListener('click', async () => {
|
||||||
|
await deleteEntries({
|
||||||
|
mode: 'all',
|
||||||
|
}, '确认删除当前全部邮箱吗?此操作不可撤销。');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bindEvents,
|
||||||
|
queueCustomEmailPoolRefresh,
|
||||||
|
refreshCustomEmailPoolEntries,
|
||||||
|
renderCustomEmailPoolEntries,
|
||||||
|
reset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
globalScope.SidepanelCustomEmailPoolManager = {
|
||||||
|
createCustomEmailPoolManager,
|
||||||
|
};
|
||||||
|
})(typeof window !== 'undefined' ? window : globalThis);
|
||||||
@@ -297,8 +297,54 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="data-row data-check-row" id="row-custom-email-pool" style="display:none;">
|
<div class="data-row data-check-row" id="row-custom-email-pool" style="display:none;">
|
||||||
<span class="data-label">邮箱池</span>
|
<span class="data-label">邮箱池</span>
|
||||||
<textarea id="input-custom-email-pool" class="data-textarea"
|
<div class="hotmail-import-box">
|
||||||
placeholder="每行一个邮箱,例如 alias001@gmail.com alias002@gmail.com"></textarea>
|
<textarea id="input-custom-email-pool" class="data-textarea mono" hidden
|
||||||
|
placeholder="每行一个邮箱,例如 alias001@gmail.com alias002@gmail.com"></textarea>
|
||||||
|
<div class="section-mini-header">
|
||||||
|
<div class="section-mini-copy">
|
||||||
|
<span class="section-label">自定义邮箱池</span>
|
||||||
|
</div>
|
||||||
|
<div class="section-mini-actions">
|
||||||
|
<button id="btn-custom-email-pool-refresh" class="btn btn-ghost btn-xs" type="button">刷新</button>
|
||||||
|
<button id="btn-custom-email-pool-clear-used" class="btn btn-ghost btn-xs" type="button">清空已用</button>
|
||||||
|
<button id="btn-custom-email-pool-delete-all" class="btn btn-ghost btn-xs" type="button">全部删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<textarea id="input-custom-email-pool-import" class="data-textarea mono" rows="4"
|
||||||
|
placeholder="每行一个邮箱,例如 alias001@gmail.com alias002@gmail.com"></textarea>
|
||||||
|
<div class="data-inline">
|
||||||
|
<button id="btn-custom-email-pool-import" class="btn btn-primary btn-sm" type="button">导入邮箱</button>
|
||||||
|
</div>
|
||||||
|
<div class="icloud-card">
|
||||||
|
<div id="custom-email-pool-summary" class="icloud-summary">导入你提前准备好的注册邮箱,每行一个邮箱地址。</div>
|
||||||
|
<div class="icloud-toolbar">
|
||||||
|
<input id="input-custom-email-pool-search" class="data-input icloud-search" type="text"
|
||||||
|
placeholder="搜索邮箱 / 备注" />
|
||||||
|
<select id="select-custom-email-pool-filter" class="data-select icloud-filter">
|
||||||
|
<option value="all">全部</option>
|
||||||
|
<option value="current">当前</option>
|
||||||
|
<option value="enabled">启用</option>
|
||||||
|
<option value="disabled">停用</option>
|
||||||
|
<option value="used">已用</option>
|
||||||
|
<option value="unused">未用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="icloud-bulkbar">
|
||||||
|
<label class="option-toggle luckmail-select-all" for="checkbox-custom-email-pool-select-all">
|
||||||
|
<input type="checkbox" id="checkbox-custom-email-pool-select-all" />
|
||||||
|
<span id="custom-email-pool-selection-summary">已选 0 个</span>
|
||||||
|
</label>
|
||||||
|
<div class="luckmail-bulk-actions">
|
||||||
|
<button id="btn-custom-email-pool-bulk-used" class="btn btn-outline btn-xs" type="button">标记已用</button>
|
||||||
|
<button id="btn-custom-email-pool-bulk-unused" class="btn btn-outline btn-xs" type="button">标记未用</button>
|
||||||
|
<button id="btn-custom-email-pool-bulk-enable" class="btn btn-outline btn-xs" type="button">启用</button>
|
||||||
|
<button id="btn-custom-email-pool-bulk-disable" class="btn btn-outline btn-xs" type="button">停用</button>
|
||||||
|
<button id="btn-custom-email-pool-bulk-delete" class="btn btn-outline btn-xs" type="button">删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="custom-email-pool-list" class="luckmail-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="data-row" id="row-cf-domain" style="display:none;">
|
<div class="data-row" id="row-cf-domain" style="display:none;">
|
||||||
<span class="data-label">CF 域名</span>
|
<span class="data-label">CF 域名</span>
|
||||||
@@ -1391,8 +1437,7 @@
|
|||||||
<script src="paypal-manager.js"></script>
|
<script src="paypal-manager.js"></script>
|
||||||
<script src="icloud-manager.js"></script>
|
<script src="icloud-manager.js"></script>
|
||||||
<script src="luckmail-manager.js"></script>
|
<script src="luckmail-manager.js"></script>
|
||||||
<script src="ip-proxy-provider-711proxy.js"></script>
|
<script src="custom-email-pool-manager.js"></script>
|
||||||
<script src="ip-proxy-panel.js"></script>
|
|
||||||
<script src="contribution-mode.js"></script>
|
<script src="contribution-mode.js"></script>
|
||||||
<script src="account-records-manager.js"></script>
|
<script src="account-records-manager.js"></script>
|
||||||
<script src="sidepanel.js"></script>
|
<script src="sidepanel.js"></script>
|
||||||
|
|||||||
+195
-67
@@ -177,6 +177,22 @@ const rowEmailGenerator = document.getElementById('row-email-generator');
|
|||||||
const selectEmailGenerator = document.getElementById('select-email-generator');
|
const selectEmailGenerator = document.getElementById('select-email-generator');
|
||||||
const rowCustomEmailPool = document.getElementById('row-custom-email-pool');
|
const rowCustomEmailPool = document.getElementById('row-custom-email-pool');
|
||||||
const inputCustomEmailPool = document.getElementById('input-custom-email-pool');
|
const inputCustomEmailPool = document.getElementById('input-custom-email-pool');
|
||||||
|
const btnCustomEmailPoolRefresh = document.getElementById('btn-custom-email-pool-refresh');
|
||||||
|
const btnCustomEmailPoolClearUsed = document.getElementById('btn-custom-email-pool-clear-used');
|
||||||
|
const btnCustomEmailPoolDeleteAll = document.getElementById('btn-custom-email-pool-delete-all');
|
||||||
|
const inputCustomEmailPoolImport = document.getElementById('input-custom-email-pool-import');
|
||||||
|
const btnCustomEmailPoolImport = document.getElementById('btn-custom-email-pool-import');
|
||||||
|
const customEmailPoolSummary = document.getElementById('custom-email-pool-summary');
|
||||||
|
const inputCustomEmailPoolSearch = document.getElementById('input-custom-email-pool-search');
|
||||||
|
const selectCustomEmailPoolFilter = document.getElementById('select-custom-email-pool-filter');
|
||||||
|
const checkboxCustomEmailPoolSelectAll = document.getElementById('checkbox-custom-email-pool-select-all');
|
||||||
|
const customEmailPoolSelectionSummary = document.getElementById('custom-email-pool-selection-summary');
|
||||||
|
const btnCustomEmailPoolBulkUsed = document.getElementById('btn-custom-email-pool-bulk-used');
|
||||||
|
const btnCustomEmailPoolBulkUnused = document.getElementById('btn-custom-email-pool-bulk-unused');
|
||||||
|
const btnCustomEmailPoolBulkEnable = document.getElementById('btn-custom-email-pool-bulk-enable');
|
||||||
|
const btnCustomEmailPoolBulkDisable = document.getElementById('btn-custom-email-pool-bulk-disable');
|
||||||
|
const btnCustomEmailPoolBulkDelete = document.getElementById('btn-custom-email-pool-bulk-delete');
|
||||||
|
const customEmailPoolList = document.getElementById('custom-email-pool-list');
|
||||||
const rowTempEmailBaseUrl = document.getElementById('row-temp-email-base-url');
|
const rowTempEmailBaseUrl = document.getElementById('row-temp-email-base-url');
|
||||||
const inputTempEmailBaseUrl = document.getElementById('input-temp-email-base-url');
|
const inputTempEmailBaseUrl = document.getElementById('input-temp-email-base-url');
|
||||||
const rowTempEmailAdminAuth = document.getElementById('row-temp-email-admin-auth');
|
const rowTempEmailAdminAuth = document.getElementById('row-temp-email-admin-auth');
|
||||||
@@ -869,44 +885,7 @@ let configActionInFlight = false;
|
|||||||
let currentReleaseSnapshot = null;
|
let currentReleaseSnapshot = null;
|
||||||
let currentContributionContentSnapshot = null;
|
let currentContributionContentSnapshot = null;
|
||||||
let contributionContentSnapshotRequestInFlight = null;
|
let contributionContentSnapshotRequestInFlight = null;
|
||||||
let phoneVerificationSectionExpanded = true;
|
let customEmailPoolEntriesState = [];
|
||||||
|
|
||||||
function readPhoneVerificationSectionExpanded() {
|
|
||||||
try {
|
|
||||||
return globalThis.localStorage?.getItem(PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY) !== '0';
|
|
||||||
} catch (err) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function persistPhoneVerificationSectionExpanded(expanded) {
|
|
||||||
try {
|
|
||||||
globalThis.localStorage?.setItem(
|
|
||||||
PHONE_VERIFICATION_SECTION_EXPANDED_STORAGE_KEY,
|
|
||||||
expanded ? '1' : '0'
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
// Ignore storage errors; in-memory state is sufficient for current session.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPhoneVerificationSectionExpanded(expanded) {
|
|
||||||
phoneVerificationSectionExpanded = Boolean(expanded);
|
|
||||||
persistPhoneVerificationSectionExpanded(phoneVerificationSectionExpanded);
|
|
||||||
updatePhoneVerificationSettingsUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePhoneVerificationSectionExpanded() {
|
|
||||||
if (!inputPhoneVerificationEnabled?.checked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setPhoneVerificationSectionExpanded(!phoneVerificationSectionExpanded);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initPhoneVerificationSectionExpandedState() {
|
|
||||||
phoneVerificationSectionExpanded = readPhoneVerificationSectionExpanded();
|
|
||||||
updatePhoneVerificationSettingsUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
const 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 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 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>';
|
const 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>';
|
||||||
@@ -1982,6 +1961,85 @@ function normalizeCustomEmailPoolEntries(value = '') {
|
|||||||
.filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
|
.filter((item) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeCustomEmailPoolEntryEmail(value = '') {
|
||||||
|
return String(value || '').trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCustomEmailPoolEntryId() {
|
||||||
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||||
|
return crypto.randomUUID();
|
||||||
|
}
|
||||||
|
return `custom-pool-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCustomEmailPoolEntryObjects(value = []) {
|
||||||
|
const source = Array.isArray(value) ? value : [];
|
||||||
|
const seenEmails = new Set();
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
for (const rawEntry of source) {
|
||||||
|
const asObject = rawEntry && typeof rawEntry === 'object'
|
||||||
|
? rawEntry
|
||||||
|
: { email: rawEntry };
|
||||||
|
const email = normalizeCustomEmailPoolEntryEmail(asObject.email || '');
|
||||||
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seenEmails.has(email)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seenEmails.add(email);
|
||||||
|
entries.push({
|
||||||
|
id: String(asObject.id || createCustomEmailPoolEntryId()),
|
||||||
|
email,
|
||||||
|
enabled: asObject.enabled !== undefined ? Boolean(asObject.enabled) : true,
|
||||||
|
used: Boolean(asObject.used),
|
||||||
|
note: String(asObject.note || '').trim(),
|
||||||
|
lastUsedAt: Number.isFinite(Number(asObject.lastUsedAt)) ? Number(asObject.lastUsedAt) : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNormalizedCustomEmailPoolEntriesState() {
|
||||||
|
const entries = (typeof customEmailPoolEntriesState !== 'undefined' && Array.isArray(customEmailPoolEntriesState))
|
||||||
|
? customEmailPoolEntriesState
|
||||||
|
: [];
|
||||||
|
return normalizeCustomEmailPoolEntryObjects(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveCustomEmailPoolEmails(entries = getNormalizedCustomEmailPoolEntriesState()) {
|
||||||
|
return normalizeCustomEmailPoolEntryObjects(entries)
|
||||||
|
.filter((entry) => entry.enabled && !entry.used)
|
||||||
|
.map((entry) => entry.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCustomEmailPoolEntriesState(entries = [], options = {}) {
|
||||||
|
const { syncInput = true } = options;
|
||||||
|
customEmailPoolEntriesState = normalizeCustomEmailPoolEntryObjects(entries);
|
||||||
|
if (syncInput && inputCustomEmailPool) {
|
||||||
|
inputCustomEmailPool.value = getActiveCustomEmailPoolEmails(customEmailPoolEntriesState).join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreCustomEmailPoolEntriesFromState(state = {}) {
|
||||||
|
const rawEntries = Array.isArray(state?.customEmailPoolEntries)
|
||||||
|
? state.customEmailPoolEntries
|
||||||
|
: [];
|
||||||
|
if (rawEntries.length > 0) {
|
||||||
|
return normalizeCustomEmailPoolEntryObjects(rawEntries);
|
||||||
|
}
|
||||||
|
return normalizeCustomEmailPoolEntries(state?.customEmailPool).map((email) => ({
|
||||||
|
id: createCustomEmailPoolEntryId(),
|
||||||
|
email,
|
||||||
|
enabled: true,
|
||||||
|
used: false,
|
||||||
|
note: '',
|
||||||
|
lastUsedAt: 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function usesCustomEmailPoolGenerator(provider = selectMailProvider.value) {
|
function usesCustomEmailPoolGenerator(provider = selectMailProvider.value) {
|
||||||
return !isCustomMailProvider(provider)
|
return !isCustomMailProvider(provider)
|
||||||
&& !isLuckmailProvider(provider)
|
&& !isLuckmailProvider(provider)
|
||||||
@@ -1997,6 +2055,12 @@ function usesCustomMailProviderPool(provider = selectMailProvider.value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getCustomEmailPoolSize() {
|
function getCustomEmailPoolSize() {
|
||||||
|
if (typeof customEmailPoolEntriesState !== 'undefined' && Array.isArray(customEmailPoolEntriesState)) {
|
||||||
|
const activeEntries = getActiveCustomEmailPoolEmails(customEmailPoolEntriesState);
|
||||||
|
if (activeEntries.length > 0 || customEmailPoolEntriesState.length > 0) {
|
||||||
|
return activeEntries.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
return normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value).length;
|
return normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2802,27 +2866,14 @@ function collectSettingsPayload() {
|
|||||||
id: normalizeHeroSmsCountryId(latestState?.heroSmsCountryId, 0),
|
id: normalizeHeroSmsCountryId(latestState?.heroSmsCountryId, 0),
|
||||||
label: normalizeHeroSmsCountryLabel(latestState?.heroSmsCountryLabel),
|
label: normalizeHeroSmsCountryLabel(latestState?.heroSmsCountryLabel),
|
||||||
};
|
};
|
||||||
const heroSmsCountryFallback = phoneSmsProviderValue === heroProviderValue
|
const normalizedCustomEmailPool = typeof getActiveCustomEmailPoolEmails === 'function'
|
||||||
? (typeof syncHeroSmsFallbackSelectionOrderFromSelect === 'function'
|
? getActiveCustomEmailPoolEmails()
|
||||||
? syncHeroSmsFallbackSelectionOrderFromSelect()
|
: (typeof normalizeCustomEmailPoolEntries === 'function'
|
||||||
.filter((country) => Number(country.id) !== Number(heroSmsCountry.id))
|
? normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value)
|
||||||
: [])
|
: []);
|
||||||
: normalizeHeroSmsCountryFallbackList(
|
const normalizedCustomEmailPoolEntries = typeof getNormalizedCustomEmailPoolEntriesState === 'function'
|
||||||
Array.isArray(latestState?.heroSmsCountryFallback) ? latestState.heroSmsCountryFallback : []
|
? getNormalizedCustomEmailPoolEntriesState()
|
||||||
);
|
: [];
|
||||||
const payPalAccounts = typeof getPayPalAccounts === 'function'
|
|
||||||
? getPayPalAccounts(latestState)
|
|
||||||
: (Array.isArray(latestState?.paypalAccounts) ? latestState.paypalAccounts : []);
|
|
||||||
const currentPayPalAccount = typeof getCurrentPayPalAccount === 'function'
|
|
||||||
? getCurrentPayPalAccount(latestState)
|
|
||||||
: payPalAccounts.find((account) => account?.id === String(latestState?.currentPayPalAccountId || '').trim()) || null;
|
|
||||||
const plusPaymentMethod = typeof getSelectedPlusPaymentMethod === 'function'
|
|
||||||
? getSelectedPlusPaymentMethod()
|
|
||||||
: (String(
|
|
||||||
(typeof selectPlusPaymentMethod !== 'undefined' && selectPlusPaymentMethod
|
|
||||||
? selectPlusPaymentMethod.value
|
|
||||||
: latestState?.plusPaymentMethod) || ''
|
|
||||||
).trim().toLowerCase() === 'gopay' ? 'gopay' : 'paypal');
|
|
||||||
return {
|
return {
|
||||||
...(contributionModeEnabled ? {} : {
|
...(contributionModeEnabled ? {} : {
|
||||||
panelMode: selectPanelMode.value,
|
panelMode: selectPanelMode.value,
|
||||||
@@ -2876,9 +2927,8 @@ function collectSettingsPayload() {
|
|||||||
customMailProviderPool: typeof normalizeCustomEmailPoolEntries === 'function'
|
customMailProviderPool: typeof normalizeCustomEmailPoolEntries === 'function'
|
||||||
? normalizeCustomEmailPoolEntries(inputCustomMailProviderPool?.value)
|
? normalizeCustomEmailPoolEntries(inputCustomMailProviderPool?.value)
|
||||||
: [],
|
: [],
|
||||||
customEmailPool: typeof normalizeCustomEmailPoolEntries === 'function'
|
customEmailPool: normalizedCustomEmailPool,
|
||||||
? normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value)
|
customEmailPoolEntries: normalizedCustomEmailPoolEntries,
|
||||||
: [],
|
|
||||||
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
|
autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
|
||||||
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
|
icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
|
||||||
icloudTargetMailboxType: normalizedIcloudTargetMailboxType,
|
icloudTargetMailboxType: normalizedIcloudTargetMailboxType,
|
||||||
@@ -6980,9 +7030,7 @@ function applySettingsState(state) {
|
|||||||
if (inputCustomMailProviderPool) {
|
if (inputCustomMailProviderPool) {
|
||||||
inputCustomMailProviderPool.value = normalizeCustomEmailPoolEntries(state?.customMailProviderPool).join('\n');
|
inputCustomMailProviderPool.value = normalizeCustomEmailPoolEntries(state?.customMailProviderPool).join('\n');
|
||||||
}
|
}
|
||||||
if (inputCustomEmailPool) {
|
setCustomEmailPoolEntriesState(restoreCustomEmailPoolEntriesFromState(state));
|
||||||
inputCustomEmailPool.value = normalizeCustomEmailPoolEntries(state?.customEmailPool).join('\n');
|
|
||||||
}
|
|
||||||
setHotmailServiceMode(state?.hotmailServiceMode);
|
setHotmailServiceMode(state?.hotmailServiceMode);
|
||||||
inputHotmailRemoteBaseUrl.value = state?.hotmailRemoteBaseUrl || '';
|
inputHotmailRemoteBaseUrl.value = state?.hotmailRemoteBaseUrl || '';
|
||||||
inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
|
inputHotmailLocalBaseUrl.value = state?.hotmailLocalBaseUrl || '';
|
||||||
@@ -7179,6 +7227,7 @@ function applySettingsState(state) {
|
|||||||
}
|
}
|
||||||
updatePanelModeUI();
|
updatePanelModeUI();
|
||||||
updateMailProviderUI();
|
updateMailProviderUI();
|
||||||
|
queueCustomEmailPoolRefresh();
|
||||||
if (isLuckmailProvider(state?.mailProvider)) {
|
if (isLuckmailProvider(state?.mailProvider)) {
|
||||||
queueLuckmailPurchaseRefresh();
|
queueLuckmailPurchaseRefresh();
|
||||||
}
|
}
|
||||||
@@ -8130,6 +8179,11 @@ function updateMailProviderUI() {
|
|||||||
}
|
}
|
||||||
if (typeof rowCustomEmailPool !== 'undefined' && rowCustomEmailPool) {
|
if (typeof rowCustomEmailPool !== 'undefined' && rowCustomEmailPool) {
|
||||||
rowCustomEmailPool.style.display = useCustomEmailPool ? '' : 'none';
|
rowCustomEmailPool.style.display = useCustomEmailPool ? '' : 'none';
|
||||||
|
if (useCustomEmailPool) {
|
||||||
|
queueCustomEmailPoolRefresh();
|
||||||
|
} else {
|
||||||
|
resetCustomEmailPoolManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (cloudflareTempEmailSection) {
|
if (cloudflareTempEmailSection) {
|
||||||
cloudflareTempEmailSection.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
cloudflareTempEmailSection.style.display = showCloudflareTempEmailSettings ? '' : 'none';
|
||||||
@@ -8642,8 +8696,8 @@ async function fetchGeneratedEmail(options = {}) {
|
|||||||
mail2925Mode: getSelectedMail2925Mode(),
|
mail2925Mode: getSelectedMail2925Mode(),
|
||||||
...(getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR
|
...(getSelectedEmailGenerator() === CUSTOM_EMAIL_POOL_GENERATOR
|
||||||
? {
|
? {
|
||||||
customEmailPool: normalizeCustomEmailPoolEntries(inputCustomEmailPool?.value),
|
customEmailPool: getActiveCustomEmailPoolEmails(),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...buildManagedAliasBaseEmailPayload(),
|
...buildManagedAliasBaseEmailPayload(),
|
||||||
},
|
},
|
||||||
@@ -8976,6 +9030,70 @@ const bindLuckmailEvents = luckmailManager?.bindLuckmailEvents
|
|||||||
|| (() => { });
|
|| (() => { });
|
||||||
bindLuckmailEvents();
|
bindLuckmailEvents();
|
||||||
|
|
||||||
|
const customEmailPoolManager = window.SidepanelCustomEmailPoolManager?.createCustomEmailPoolManager({
|
||||||
|
dom: {
|
||||||
|
btnCustomEmailPoolRefresh,
|
||||||
|
btnCustomEmailPoolClearUsed,
|
||||||
|
btnCustomEmailPoolDeleteAll,
|
||||||
|
inputCustomEmailPoolImport,
|
||||||
|
btnCustomEmailPoolImport,
|
||||||
|
customEmailPoolSummary,
|
||||||
|
inputCustomEmailPoolSearch,
|
||||||
|
selectCustomEmailPoolFilter,
|
||||||
|
checkboxCustomEmailPoolSelectAll,
|
||||||
|
customEmailPoolSelectionSummary,
|
||||||
|
btnCustomEmailPoolBulkUsed,
|
||||||
|
btnCustomEmailPoolBulkUnused,
|
||||||
|
btnCustomEmailPoolBulkEnable,
|
||||||
|
btnCustomEmailPoolBulkDisable,
|
||||||
|
btnCustomEmailPoolBulkDelete,
|
||||||
|
customEmailPoolList,
|
||||||
|
},
|
||||||
|
helpers: {
|
||||||
|
copyTextToClipboard,
|
||||||
|
escapeHtml,
|
||||||
|
openConfirmModal,
|
||||||
|
showToast,
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
getEntries: () => getNormalizedCustomEmailPoolEntriesState(),
|
||||||
|
setEntries: (entries) => {
|
||||||
|
setCustomEmailPoolEntriesState(entries);
|
||||||
|
},
|
||||||
|
getCurrentEmail: () => String(inputEmail?.value || latestState?.email || '').trim().toLowerCase(),
|
||||||
|
isVisible: () => Boolean(rowCustomEmailPool) && rowCustomEmailPool.style.display !== 'none',
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
persistEntries: async () => {
|
||||||
|
syncRunCountFromConfiguredEmailPool();
|
||||||
|
updateMailProviderUI();
|
||||||
|
markSettingsDirty(true);
|
||||||
|
await saveSettings({ silent: true });
|
||||||
|
},
|
||||||
|
setRuntimeEmail: async (email) => {
|
||||||
|
await setRuntimeEmailState(email);
|
||||||
|
syncLatestState({ email });
|
||||||
|
if (inputEmail) {
|
||||||
|
inputEmail.value = email || '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
constants: {
|
||||||
|
copyIcon: COPY_ICON,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const queueCustomEmailPoolRefresh = customEmailPoolManager?.queueCustomEmailPoolRefresh
|
||||||
|
|| (() => { });
|
||||||
|
const refreshCustomEmailPoolEntries = customEmailPoolManager?.refreshCustomEmailPoolEntries
|
||||||
|
|| (async () => { });
|
||||||
|
const renderCustomEmailPoolEntries = customEmailPoolManager?.renderCustomEmailPoolEntries
|
||||||
|
|| (() => { });
|
||||||
|
const resetCustomEmailPoolManager = customEmailPoolManager?.reset
|
||||||
|
|| (() => { });
|
||||||
|
const bindCustomEmailPoolEvents = customEmailPoolManager?.bindEvents
|
||||||
|
|| (() => { });
|
||||||
|
bindCustomEmailPoolEvents();
|
||||||
|
|
||||||
const accountRecordsManager = window.SidepanelAccountRecordsManager?.createAccountRecordsManager({
|
const accountRecordsManager = window.SidepanelAccountRecordsManager?.createAccountRecordsManager({
|
||||||
state: {
|
state: {
|
||||||
getLatestState: () => latestState,
|
getLatestState: () => latestState,
|
||||||
@@ -11171,6 +11289,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
logArea.innerHTML = '';
|
logArea.innerHTML = '';
|
||||||
resetIcloudManager();
|
resetIcloudManager();
|
||||||
resetLuckmailManager();
|
resetLuckmailManager();
|
||||||
|
resetCustomEmailPoolManager();
|
||||||
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
document.querySelectorAll('.step-row').forEach(row => row.className = 'step-row');
|
||||||
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
document.querySelectorAll('.step-status').forEach(el => el.textContent = '');
|
||||||
syncAutoRunState({
|
syncAutoRunState({
|
||||||
@@ -11200,6 +11319,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
syncLatestState(message.payload);
|
syncLatestState(message.payload);
|
||||||
if (message.payload.email !== undefined) {
|
if (message.payload.email !== undefined) {
|
||||||
inputEmail.value = message.payload.email || '';
|
inputEmail.value = message.payload.email || '';
|
||||||
|
queueCustomEmailPoolRefresh();
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
message.payload.password !== undefined
|
message.payload.password !== undefined
|
||||||
@@ -11424,6 +11544,14 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|||||||
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
setManagedAliasBaseEmailInputForProvider('2925', latestState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (message.payload.customEmailPoolEntries !== undefined || message.payload.customEmailPool !== undefined) {
|
||||||
|
setCustomEmailPoolEntriesState(restoreCustomEmailPoolEntriesFromState({
|
||||||
|
...latestState,
|
||||||
|
...message.payload,
|
||||||
|
}));
|
||||||
|
syncRunCountFromConfiguredEmailPool();
|
||||||
|
queueCustomEmailPoolRefresh();
|
||||||
|
}
|
||||||
if (message.payload.luckmailApiKey !== undefined) {
|
if (message.payload.luckmailApiKey !== undefined) {
|
||||||
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
|
inputLuckmailApiKey.value = message.payload.luckmailApiKey || '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ function extractFunction(name) {
|
|||||||
const bundle = [
|
const bundle = [
|
||||||
extractFunction('normalizeEmailGenerator'),
|
extractFunction('normalizeEmailGenerator'),
|
||||||
extractFunction('normalizeCustomEmailPool'),
|
extractFunction('normalizeCustomEmailPool'),
|
||||||
|
extractFunction('normalizeCustomEmailPoolEntryObjects'),
|
||||||
extractFunction('getCustomEmailPool'),
|
extractFunction('getCustomEmailPool'),
|
||||||
extractFunction('getCustomEmailPoolEmailForRun'),
|
extractFunction('getCustomEmailPoolEmailForRun'),
|
||||||
extractFunction('getCustomMailProviderPool'),
|
extractFunction('getCustomMailProviderPool'),
|
||||||
@@ -122,3 +123,18 @@ test('background selects the matching custom provider pool email for the current
|
|||||||
assert.equal(api.getCustomMailProviderPoolEmailForRun(state, 3), 'third@example.com');
|
assert.equal(api.getCustomMailProviderPoolEmailForRun(state, 3), 'third@example.com');
|
||||||
assert.equal(api.getCustomMailProviderPoolEmailForRun(state, 4), '');
|
assert.equal(api.getCustomMailProviderPoolEmailForRun(state, 4), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('background derives active custom email pool from structured entries', () => {
|
||||||
|
const api = createApi();
|
||||||
|
const state = {
|
||||||
|
customEmailPoolEntries: [
|
||||||
|
{ id: 'a', email: 'one@example.com', enabled: true, used: false },
|
||||||
|
{ id: 'b', email: 'two@example.com', enabled: true, used: true },
|
||||||
|
{ id: 'c', email: 'three@example.com', enabled: false, used: false },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.deepEqual(api.getCustomEmailPool(state), ['one@example.com']);
|
||||||
|
assert.equal(api.getCustomEmailPoolEmailForRun(state, 1), 'one@example.com');
|
||||||
|
assert.equal(api.getCustomEmailPoolEmailForRun(state, 2), '');
|
||||||
|
});
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ test('sidepanel html exposes custom email pool generator option and input row',
|
|||||||
assert.match(html, /option value="custom-pool">自定义邮箱池<\/option>/);
|
assert.match(html, /option value="custom-pool">自定义邮箱池<\/option>/);
|
||||||
assert.match(html, /id="row-custom-email-pool"/);
|
assert.match(html, /id="row-custom-email-pool"/);
|
||||||
assert.match(html, /id="input-custom-email-pool"/);
|
assert.match(html, /id="input-custom-email-pool"/);
|
||||||
|
assert.match(html, /id="input-custom-email-pool-import"/);
|
||||||
|
assert.match(html, /id="custom-email-pool-list"/);
|
||||||
|
assert.match(html, /id="btn-custom-email-pool-bulk-used"/);
|
||||||
assert.match(html, /id="row-custom-mail-provider-pool"/);
|
assert.match(html, /id="row-custom-mail-provider-pool"/);
|
||||||
assert.match(html, /id="input-custom-mail-provider-pool"/);
|
assert.match(html, /id="input-custom-mail-provider-pool"/);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user