没有直接撬 Step 1~9 的核心状态机,而是先把最适合独立的管理器和桥接层拆出来

This commit is contained in:
QLHazyCoder
2026-04-16 23:18:27 +08:00
parent 7828c196af
commit f0c3dcda62
17 changed files with 2686 additions and 1738 deletions
+529
View File
@@ -0,0 +1,529 @@
(function attachSidepanelHotmailManager(globalScope) {
function createHotmailManager(context = {}) {
const {
state,
dom,
helpers,
runtime,
constants = {},
hotmailUtils = {},
} = context;
const expandedStorageKey = constants.expandedStorageKey || 'multipage-hotmail-list-expanded';
const displayTimeZone = constants.displayTimeZone || 'Asia/Shanghai';
const copyIcon = constants.copyIcon || '';
let actionInFlight = false;
let listExpanded = false;
function getHotmailAccountsByUsage(mode = 'all', currentState = state.getLatestState()) {
const accounts = helpers.getHotmailAccounts(currentState);
if (typeof hotmailUtils.filterHotmailAccountsByUsage === 'function') {
return hotmailUtils.filterHotmailAccountsByUsage(accounts, mode);
}
if (mode === 'used') {
return accounts.filter((account) => Boolean(account?.used));
}
return accounts.slice();
}
function getHotmailBulkActionText(mode, count) {
if (typeof hotmailUtils.getHotmailBulkActionLabel === 'function') {
return hotmailUtils.getHotmailBulkActionLabel(mode, count);
}
const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0;
const prefix = mode === 'used' ? '清空已用' : '全部删除';
const suffix = normalizedCount > 0 ? `${normalizedCount}` : '';
return `${prefix}${suffix}`;
}
function getHotmailListToggleText(expanded, count) {
if (typeof hotmailUtils.getHotmailListToggleLabel === 'function') {
return hotmailUtils.getHotmailListToggleLabel(expanded, count);
}
const normalizedCount = Number.isFinite(Number(count)) ? Math.max(0, Number(count)) : 0;
const suffix = normalizedCount > 0 ? `${normalizedCount}` : '';
return `${expanded ? '收起列表' : '展开列表'}${suffix}`;
}
function updateHotmailListViewport() {
const count = helpers.getHotmailAccounts().length;
const usedCount = getHotmailAccountsByUsage('used').length;
if (dom.btnClearUsedHotmailAccounts) {
dom.btnClearUsedHotmailAccounts.textContent = getHotmailBulkActionText('used', usedCount);
dom.btnClearUsedHotmailAccounts.disabled = usedCount === 0;
}
if (dom.btnDeleteAllHotmailAccounts) {
dom.btnDeleteAllHotmailAccounts.textContent = getHotmailBulkActionText('all', count);
dom.btnDeleteAllHotmailAccounts.disabled = count === 0;
}
if (dom.btnToggleHotmailList) {
dom.btnToggleHotmailList.textContent = getHotmailListToggleText(listExpanded, count);
dom.btnToggleHotmailList.setAttribute('aria-expanded', String(listExpanded));
dom.btnToggleHotmailList.disabled = count === 0;
}
if (dom.hotmailListShell) {
dom.hotmailListShell.classList.toggle('is-expanded', listExpanded);
dom.hotmailListShell.classList.toggle('is-collapsed', !listExpanded);
}
}
function setHotmailListExpanded(expanded, options = {}) {
const { persist = true } = options;
listExpanded = Boolean(expanded);
updateHotmailListViewport();
if (persist) {
localStorage.setItem(expandedStorageKey, listExpanded ? '1' : '0');
}
}
function initHotmailListExpandedState() {
const saved = localStorage.getItem(expandedStorageKey);
setHotmailListExpanded(saved === '1', { persist: false });
}
function shouldClearCurrentHotmailSelectionLocally(account) {
if (typeof hotmailUtils.shouldClearHotmailCurrentSelection === 'function') {
return hotmailUtils.shouldClearHotmailCurrentSelection(account);
}
return Boolean(account) && account.used === true;
}
function upsertHotmailAccountListLocally(accounts, nextAccount) {
if (typeof hotmailUtils.upsertHotmailAccountInList === 'function') {
return hotmailUtils.upsertHotmailAccountInList(accounts, nextAccount);
}
const list = Array.isArray(accounts) ? accounts.slice() : [];
if (!nextAccount?.id) return list;
const existingIndex = list.findIndex((account) => account?.id === nextAccount.id);
if (existingIndex === -1) {
list.push(nextAccount);
return list;
}
list[existingIndex] = nextAccount;
return list;
}
function refreshHotmailSelectionUI() {
renderHotmailAccounts();
if (dom.selectMailProvider.value === 'hotmail-api') {
dom.inputEmail.value = helpers.getCurrentHotmailEmail();
}
}
function applyHotmailAccountMutation(account, options = {}) {
if (!account?.id) return;
const { preserveCurrentSelection = false } = options;
const latestState = state.getLatestState();
const nextState = {
hotmailAccounts: upsertHotmailAccountListLocally(helpers.getHotmailAccounts(), account),
};
if (!preserveCurrentSelection
&& latestState?.currentHotmailAccountId === account.id
&& shouldClearCurrentHotmailSelectionLocally(account)) {
nextState.currentHotmailAccountId = null;
if (dom.selectMailProvider.value === 'hotmail-api') {
nextState.email = null;
}
}
state.syncLatestState(nextState);
refreshHotmailSelectionUI();
}
function formatDateTime(timestamp) {
const value = Number(timestamp);
if (!Number.isFinite(value) || value <= 0) {
return '未使用';
}
return new Date(value).toLocaleString('zh-CN', {
hour12: false,
timeZone: displayTimeZone,
});
}
function getHotmailAvailabilityLabel(account) {
if (account.used) return '已用';
return '可分配';
}
function getHotmailStatusLabel(account) {
if (account.used) return '已用';
switch (account.status) {
case 'authorized':
return '可用';
case 'error':
return '异常';
default:
return '待校验';
}
}
function getHotmailStatusClass(account) {
if (account.used) return 'status-used';
return `status-${account.status || 'pending'}`;
}
function clearHotmailForm() {
dom.inputHotmailEmail.value = '';
dom.inputHotmailClientId.value = '';
dom.inputHotmailPassword.value = '';
dom.inputHotmailRefreshToken.value = '';
}
function renderHotmailAccounts() {
if (!dom.hotmailAccountsList) return;
const latestState = state.getLatestState();
const accounts = helpers.getHotmailAccounts();
const currentId = latestState?.currentHotmailAccountId || '';
if (!accounts.length) {
dom.hotmailAccountsList.innerHTML = '<div class="hotmail-empty">还没有 Hotmail 账号,先添加一条再校验。</div>';
updateHotmailListViewport();
return;
}
dom.hotmailAccountsList.innerHTML = accounts.map((account) => `
<div class="hotmail-account-item${account.id === currentId ? ' is-current' : ''}">
<div class="hotmail-account-top">
<div class="hotmail-account-title-row">
<div class="hotmail-account-email">${helpers.escapeHtml(account.email || '(未命名账号)')}</div>
<button
class="hotmail-copy-btn"
type="button"
data-account-action="copy-email"
data-account-id="${helpers.escapeHtml(account.id)}"
title="复制邮箱"
aria-label="复制邮箱 ${helpers.escapeHtml(account.email || '')}"
>${copyIcon}</button>
</div>
<span class="hotmail-status-chip ${helpers.escapeHtml(getHotmailStatusClass(account))}">${helpers.escapeHtml(getHotmailStatusLabel(account))}</span>
</div>
<div class="hotmail-account-meta">
<span>客户端 ID${helpers.escapeHtml(account.clientId ? `${account.clientId.slice(0, 10)}...` : '未填写')}</span>
<span>刷新令牌:${account.refreshToken ? '已保存' : '未保存'}</span>
<span>分配状态: ${helpers.escapeHtml(getHotmailAvailabilityLabel(account))}</span>
<span>上次校验: ${helpers.escapeHtml(formatDateTime(account.lastAuthAt))}</span>
<span>上次使用: ${helpers.escapeHtml(formatDateTime(account.lastUsedAt))}</span>
</div>
${account.lastError ? `<div class="hotmail-account-error">${helpers.escapeHtml(account.lastError)}</div>` : ''}
<div class="hotmail-account-actions">
<button class="btn btn-outline btn-sm" type="button" data-account-action="select" data-account-id="${helpers.escapeHtml(account.id)}">使用此账号</button>
<button class="btn btn-outline btn-sm" type="button" data-account-action="toggle-used" data-account-id="${helpers.escapeHtml(account.id)}">${account.used ? '标记未用' : '标记已用'}</button>
<button class="btn btn-primary btn-sm" type="button" data-account-action="verify" data-account-id="${helpers.escapeHtml(account.id)}">校验</button>
<button class="btn btn-outline btn-sm" type="button" data-account-action="test" data-account-id="${helpers.escapeHtml(account.id)}">复制最新验证码</button>
<button class="btn btn-ghost btn-sm" type="button" data-account-action="delete" data-account-id="${helpers.escapeHtml(account.id)}">删除</button>
</div>
</div>
`).join('');
updateHotmailListViewport();
}
async function deleteHotmailAccountsByMode(mode) {
const isUsedMode = mode === 'used';
const targetAccounts = getHotmailAccountsByUsage(isUsedMode ? 'used' : 'all');
if (!targetAccounts.length) {
helpers.showToast(isUsedMode ? '没有已用账号可清空。' : '没有可删除的 Hotmail 账号。', 'warn');
return;
}
const confirmed = await helpers.openConfirmModal({
title: isUsedMode ? '清空已用账号' : '全部删除账号',
message: isUsedMode
? `确认删除当前 ${targetAccounts.length} 个已用 Hotmail 账号吗?`
: `确认删除全部 ${targetAccounts.length} 个 Hotmail 账号吗?`,
confirmLabel: isUsedMode ? '确认清空已用' : '确认全部删除',
confirmVariant: isUsedMode ? 'btn-outline' : 'btn-danger',
});
if (!confirmed) {
return;
}
const response = await runtime.sendMessage({
type: 'DELETE_HOTMAIL_ACCOUNTS',
source: 'sidepanel',
payload: { mode: isUsedMode ? 'used' : 'all' },
});
if (response?.error) {
throw new Error(response.error);
}
const latestState = state.getLatestState();
const targetIds = new Set(targetAccounts.map((account) => account.id));
const nextAccounts = isUsedMode
? helpers.getHotmailAccounts().filter((account) => !targetIds.has(account.id))
: [];
const nextState = { hotmailAccounts: nextAccounts };
if (latestState?.currentHotmailAccountId && targetIds.has(latestState.currentHotmailAccountId)) {
nextState.currentHotmailAccountId = null;
if (dom.selectMailProvider.value === 'hotmail-api') {
nextState.email = null;
}
}
state.syncLatestState(nextState);
refreshHotmailSelectionUI();
helpers.showToast(
isUsedMode
? `已清空 ${response.deletedCount || 0} 个已用 Hotmail 账号`
: `已删除全部 ${response.deletedCount || 0} 个 Hotmail 账号`,
'success',
2200
);
}
async function handleAddHotmailAccount() {
if (actionInFlight) return;
const email = dom.inputHotmailEmail.value.trim();
const clientId = dom.inputHotmailClientId.value.trim();
const refreshToken = dom.inputHotmailRefreshToken.value.trim();
if (!email) {
helpers.showToast('请先填写 Hotmail 邮箱。', 'warn');
return;
}
if (!clientId) {
helpers.showToast('请先填写微软应用客户端 ID。', 'warn');
return;
}
if (!refreshToken) {
helpers.showToast('请先填写刷新令牌(refresh token)。', 'warn');
return;
}
actionInFlight = true;
dom.btnAddHotmailAccount.disabled = true;
try {
const response = await runtime.sendMessage({
type: 'UPSERT_HOTMAIL_ACCOUNT',
source: 'sidepanel',
payload: {
email,
clientId,
password: dom.inputHotmailPassword.value,
refreshToken,
},
});
if (response?.error) {
throw new Error(response.error);
}
helpers.showToast(`已保存 Hotmail 账号 ${email}`, 'success', 1800);
clearHotmailForm();
} catch (err) {
helpers.showToast(`保存 Hotmail 账号失败:${err.message}`, 'error');
} finally {
actionInFlight = false;
dom.btnAddHotmailAccount.disabled = false;
}
}
async function handleImportHotmailAccounts() {
if (actionInFlight) return;
if (typeof hotmailUtils.parseHotmailImportText !== 'function') {
helpers.showToast('导入解析器未加载,请刷新扩展后重试。', 'error');
return;
}
const rawText = dom.inputHotmailImport.value.trim();
if (!rawText) {
helpers.showToast('请先粘贴账号导入内容。', 'warn');
return;
}
const parsedAccounts = hotmailUtils.parseHotmailImportText(rawText);
if (!parsedAccounts.length) {
helpers.showToast('没有解析到有效账号,请检查格式是否为 账号----密码----ID----Token。', 'error');
return;
}
actionInFlight = true;
dom.btnImportHotmailAccounts.disabled = true;
try {
for (const account of parsedAccounts) {
const response = await runtime.sendMessage({
type: 'UPSERT_HOTMAIL_ACCOUNT',
source: 'sidepanel',
payload: account,
});
if (response?.error) {
throw new Error(response.error);
}
}
dom.inputHotmailImport.value = '';
helpers.showToast(`已导入 ${parsedAccounts.length} 条 Hotmail 账号`, 'success', 2200);
} catch (err) {
helpers.showToast(`批量导入失败:${err.message}`, 'error');
} finally {
actionInFlight = false;
dom.btnImportHotmailAccounts.disabled = false;
}
}
async function handleAccountListClick(event) {
const actionButton = event.target.closest('[data-account-action]');
if (!actionButton || actionInFlight) {
return;
}
const accountId = actionButton.dataset.accountId;
const action = actionButton.dataset.accountAction;
if (!accountId || !action) {
return;
}
const targetAccount = helpers.getHotmailAccounts().find((account) => account.id === accountId) || null;
actionInFlight = true;
actionButton.disabled = true;
try {
if (action === 'copy-email') {
if (!targetAccount?.email) throw new Error('未找到可复制的邮箱地址。');
await helpers.copyTextToClipboard(targetAccount.email);
helpers.showToast(`已复制 ${targetAccount.email}`, 'success', 1800);
} else if (action === 'select') {
const response = await runtime.sendMessage({
type: 'SELECT_HOTMAIL_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) throw new Error(response.error);
state.syncLatestState({ currentHotmailAccountId: response.account.id });
applyHotmailAccountMutation(response.account, { preserveCurrentSelection: true });
helpers.showToast(`已切换当前 Hotmail 账号为 ${response.account.email}`, 'success', 1800);
} else if (action === 'toggle-used') {
if (!targetAccount) throw new Error('未找到目标 Hotmail 账号。');
const response = await runtime.sendMessage({
type: 'PATCH_HOTMAIL_ACCOUNT',
source: 'sidepanel',
payload: {
accountId,
updates: { used: !targetAccount.used },
},
});
if (response?.error) throw new Error(response.error);
applyHotmailAccountMutation(response.account);
helpers.showToast(`账号 ${response.account.email}${response.account.used ? '标记为已用' : '恢复为未用'}`, 'success', 2200);
} else if (action === 'verify') {
const response = await runtime.sendMessage({
type: 'VERIFY_HOTMAIL_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) throw new Error(response.error);
applyHotmailAccountMutation(response.account, { preserveCurrentSelection: true });
helpers.showToast(`账号 ${response.account.email} 校验通过`, 'success', 2200);
} else if (action === 'test') {
const response = await runtime.sendMessage({
type: 'TEST_HOTMAIL_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) throw new Error(response.error);
applyHotmailAccountMutation(response.account, { preserveCurrentSelection: true });
if (response.latestCode) {
await helpers.copyTextToClipboard(response.latestCode);
const mailbox = response.latestMailbox ? `${response.latestMailbox}` : '';
helpers.showToast(`已复制最新验证码 ${response.latestCode}${mailbox}`, 'success', 2600);
} else if (response.latestSubject) {
const mailbox = response.latestMailbox ? `${response.latestMailbox}` : '';
helpers.showToast(`最新邮件${mailbox}没有验证码:${response.latestSubject}`, 'warn', 3200);
} else {
helpers.showToast('当前没有可读取的最新邮件。', 'warn', 2600);
}
} else if (action === 'delete') {
const confirmed = await helpers.openConfirmModal({
title: '删除账号',
message: '确认删除这个 Hotmail 账号吗?对应 token 也会一起移除。',
confirmLabel: '确认删除',
confirmVariant: 'btn-danger',
});
if (!confirmed) {
return;
}
const response = await runtime.sendMessage({
type: 'DELETE_HOTMAIL_ACCOUNT',
source: 'sidepanel',
payload: { accountId },
});
if (response?.error) throw new Error(response.error);
helpers.showToast('Hotmail 账号已删除', 'success', 1800);
}
} catch (err) {
helpers.showToast(err.message, 'error');
} finally {
actionInFlight = false;
actionButton.disabled = false;
}
}
function bindHotmailEvents() {
dom.btnToggleHotmailList?.addEventListener('click', () => {
setHotmailListExpanded(!listExpanded);
});
dom.btnHotmailUsageGuide?.addEventListener('click', async () => {
await helpers.openConfirmModal({
title: '使用教程',
message: 'API对接模式会直接调用微软邮箱接口取件;本地助手模式仍走本地服务。两种模式继续共用同一套 Hotmail 账号池与导入格式。',
confirmLabel: '确定',
confirmVariant: 'btn-primary',
});
});
dom.btnClearUsedHotmailAccounts?.addEventListener('click', async () => {
if (actionInFlight) return;
actionInFlight = true;
dom.btnClearUsedHotmailAccounts.disabled = true;
try {
await deleteHotmailAccountsByMode('used');
} catch (err) {
helpers.showToast(err.message, 'error');
} finally {
actionInFlight = false;
updateHotmailListViewport();
}
});
dom.btnDeleteAllHotmailAccounts?.addEventListener('click', async () => {
if (actionInFlight) return;
actionInFlight = true;
dom.btnDeleteAllHotmailAccounts.disabled = true;
try {
await deleteHotmailAccountsByMode('all');
} catch (err) {
helpers.showToast(err.message, 'error');
} finally {
actionInFlight = false;
updateHotmailListViewport();
}
});
dom.btnAddHotmailAccount?.addEventListener('click', handleAddHotmailAccount);
dom.btnImportHotmailAccounts?.addEventListener('click', handleImportHotmailAccounts);
dom.hotmailAccountsList?.addEventListener('click', handleAccountListClick);
}
return {
bindHotmailEvents,
initHotmailListExpandedState,
renderHotmailAccounts,
};
}
globalScope.SidepanelHotmailManager = {
createHotmailManager,
};
})(window);
+502
View File
@@ -0,0 +1,502 @@
(function attachSidepanelIcloudManager(globalScope) {
function createIcloudManager(context = {}) {
const {
dom,
helpers,
runtime,
} = context;
let refreshQueued = false;
let renderedAliases = [];
let selectedEmails = new Set();
let searchTerm = '';
let filterMode = 'all';
function normalizeIcloudSearchText(value) {
return String(value || '').trim().toLowerCase();
}
function getFilteredIcloudAliases(aliases = renderedAliases) {
const normalizedSearchTerm = normalizeIcloudSearchText(searchTerm);
return (Array.isArray(aliases) ? aliases : []).filter((alias) => {
const matchesFilter = (() => {
switch (filterMode) {
case 'active': return Boolean(alias.active);
case 'used': return Boolean(alias.used);
case 'unused': return !alias.used;
case 'preserved': return Boolean(alias.preserved);
default: return true;
}
})();
if (!matchesFilter) return false;
if (!normalizedSearchTerm) return true;
const haystack = [
alias.email,
alias.label,
alias.note,
alias.used ? '已用 used' : '未用 unused',
alias.active ? '可用 active' : '不可用 inactive',
alias.preserved ? '保留 preserved' : '',
].join(' ').toLowerCase();
return haystack.includes(normalizedSearchTerm);
});
}
function pruneIcloudSelection(aliases = renderedAliases) {
const existing = new Set((Array.isArray(aliases) ? aliases : []).map((alias) => alias.email));
selectedEmails = new Set([...selectedEmails].filter((email) => existing.has(email)));
}
function updateIcloudBulkUI(visibleAliases = getFilteredIcloudAliases()) {
if (!dom.checkboxIcloudSelectAll || !dom.icloudSelectionSummary) {
return;
}
const visibleEmails = visibleAliases.map((alias) => alias.email);
const selectedVisibleCount = visibleEmails.filter((email) => selectedEmails.has(email)).length;
const hasVisible = visibleEmails.length > 0;
dom.checkboxIcloudSelectAll.checked = hasVisible && selectedVisibleCount === visibleEmails.length;
dom.checkboxIcloudSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleEmails.length;
dom.checkboxIcloudSelectAll.disabled = !hasVisible;
dom.icloudSelectionSummary.textContent = `已选 ${selectedEmails.size} 个(当前显示 ${visibleEmails.length} 个)`;
const hasSelection = selectedEmails.size > 0;
if (dom.btnIcloudBulkUsed) dom.btnIcloudBulkUsed.disabled = !hasSelection;
if (dom.btnIcloudBulkUnused) dom.btnIcloudBulkUnused.disabled = !hasSelection;
if (dom.btnIcloudBulkPreserve) dom.btnIcloudBulkPreserve.disabled = !hasSelection;
if (dom.btnIcloudBulkUnpreserve) dom.btnIcloudBulkUnpreserve.disabled = !hasSelection;
if (dom.btnIcloudBulkDelete) dom.btnIcloudBulkDelete.disabled = !hasSelection;
}
function setIcloudLoadingState(loading, summary = '') {
if (dom.btnIcloudRefresh) dom.btnIcloudRefresh.disabled = loading;
if (dom.btnIcloudDeleteUsed) dom.btnIcloudDeleteUsed.disabled = loading;
if (dom.btnIcloudLoginDone) dom.btnIcloudLoginDone.disabled = loading;
if (dom.inputIcloudSearch) dom.inputIcloudSearch.disabled = loading;
if (dom.selectIcloudFilter) dom.selectIcloudFilter.disabled = loading;
if (dom.checkboxIcloudSelectAll) dom.checkboxIcloudSelectAll.disabled = loading || getFilteredIcloudAliases().length === 0;
if (dom.btnIcloudBulkUsed) dom.btnIcloudBulkUsed.disabled = loading || selectedEmails.size === 0;
if (dom.btnIcloudBulkUnused) dom.btnIcloudBulkUnused.disabled = loading || selectedEmails.size === 0;
if (dom.btnIcloudBulkPreserve) dom.btnIcloudBulkPreserve.disabled = loading || selectedEmails.size === 0;
if (dom.btnIcloudBulkUnpreserve) dom.btnIcloudBulkUnpreserve.disabled = loading || selectedEmails.size === 0;
if (dom.btnIcloudBulkDelete) dom.btnIcloudBulkDelete.disabled = loading || selectedEmails.size === 0;
if (summary && dom.icloudSummary) dom.icloudSummary.textContent = summary;
}
function showIcloudLoginHelp(payload = {}) {
if (!dom.icloudLoginHelp) return;
const loginUrl = String(payload.loginUrl || '').trim();
let host = 'icloud.com.cn / icloud.com';
if (loginUrl) {
try {
host = new URL(loginUrl).host;
} catch {
host = loginUrl;
}
}
if (dom.icloudLoginHelpTitle) dom.icloudLoginHelpTitle.textContent = '需要登录 iCloud';
if (dom.icloudLoginHelpText) dom.icloudLoginHelpText.textContent = `我已经为你打开 ${host}。请在那个页面完成登录,然后回到这里点击“我已登录”。`;
dom.icloudLoginHelp.style.display = 'flex';
}
function hideIcloudLoginHelp() {
if (dom.icloudLoginHelp) {
dom.icloudLoginHelp.style.display = 'none';
}
}
function renderIcloudAliases(aliases = []) {
if (!dom.icloudList || !dom.icloudSummary) return;
renderedAliases = Array.isArray(aliases) ? aliases : [];
pruneIcloudSelection(renderedAliases);
dom.icloudList.innerHTML = '';
if (!aliases.length) {
selectedEmails.clear();
dom.icloudList.innerHTML = '<div class="icloud-empty">未找到 iCloud Hide My Email 别名。</div>';
dom.icloudSummary.textContent = '加载你的 iCloud Hide My Email 别名以便在这里管理。';
if (dom.btnIcloudDeleteUsed) dom.btnIcloudDeleteUsed.disabled = true;
updateIcloudBulkUI([]);
return;
}
const usedCount = aliases.filter((alias) => alias.used).length;
const deletableUsedCount = aliases.filter((alias) => alias.used && !alias.preserved).length;
dom.icloudSummary.textContent = `已加载 ${aliases.length} 个别名,其中 ${usedCount} 个已标记为已用。`;
if (dom.btnIcloudDeleteUsed) dom.btnIcloudDeleteUsed.disabled = deletableUsedCount === 0;
const visibleAliases = getFilteredIcloudAliases(aliases);
if (!visibleAliases.length) {
dom.icloudList.innerHTML = '<div class="icloud-empty">没有匹配当前筛选条件的别名。</div>';
updateIcloudBulkUI([]);
return;
}
for (const alias of visibleAliases) {
const item = document.createElement('div');
item.className = 'icloud-item';
item.innerHTML = `
<input class="icloud-item-check" type="checkbox" data-action="select" ${selectedEmails.has(alias.email) ? 'checked' : ''} />
<div class="icloud-item-main">
<div class="icloud-item-email">${helpers.escapeHtml(alias.email)}</div>
<div class="icloud-item-meta">
${alias.used ? '<span class="icloud-tag used">已用</span>' : ''}
${!alias.used && alias.active ? '<span class="icloud-tag active">可用</span>' : ''}
${alias.preserved ? '<span class="icloud-tag">保留</span>' : ''}
${alias.label ? `<span class="icloud-tag">${helpers.escapeHtml(alias.label)}</span>` : ''}
${alias.note ? `<span class="icloud-tag">${helpers.escapeHtml(alias.note)}</span>` : ''}
</div>
</div>
<div class="icloud-item-actions">
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-used">${helpers.escapeHtml(alias.used ? '标记未用' : '标记已用')}</button>
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-preserved">${helpers.escapeHtml(alias.preserved ? '取消保留' : '保留')}</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) {
selectedEmails.add(alias.email);
} else {
selectedEmails.delete(alias.email);
}
updateIcloudBulkUI(visibleAliases);
});
item.querySelector('[data-action="toggle-used"]').addEventListener('click', async () => {
await setSingleIcloudAliasUsedState(alias, !alias.used);
});
item.querySelector('[data-action="toggle-preserved"]').addEventListener('click', async () => {
await setSingleIcloudAliasPreservedState(alias, !alias.preserved);
});
item.querySelector('[data-action="delete"]').addEventListener('click', async () => {
await deleteSingleIcloudAlias(alias);
});
dom.icloudList.appendChild(item);
}
updateIcloudBulkUI(visibleAliases);
}
async function refreshIcloudAliases(options = {}) {
const { silent = false } = options;
if (!dom.icloudSection || dom.icloudSection.style.display === 'none') {
return;
}
if (!silent) setIcloudLoadingState(true, '正在加载 iCloud 别名...');
try {
const response = await runtime.sendMessage({
type: 'LIST_ICLOUD_ALIASES',
source: 'sidepanel',
payload: {},
});
if (response?.error) throw new Error(response.error);
hideIcloudLoginHelp();
renderIcloudAliases(response?.aliases || []);
} catch (err) {
selectedEmails.clear();
if (dom.icloudList) {
dom.icloudList.innerHTML = '<div class="icloud-empty">无法加载 iCloud 别名。</div>';
}
if (dom.icloudSummary) {
dom.icloudSummary.textContent = err.message;
}
updateIcloudBulkUI([]);
if (!silent) helpers.showToast(`iCloud 别名加载失败:${err.message}`, 'error');
} finally {
setIcloudLoadingState(false);
}
}
function queueIcloudAliasRefresh() {
if (refreshQueued) return;
refreshQueued = true;
setTimeout(async () => {
refreshQueued = false;
await refreshIcloudAliases({ silent: true });
}, 150);
}
async function deleteSingleIcloudAlias(alias) {
const confirmed = await helpers.openConfirmModal({
title: '删除 iCloud 别名',
message: `确认删除 ${alias.email} 吗?此操作不可撤销。`,
confirmLabel: '确认删除',
confirmVariant: 'btn-danger',
});
if (!confirmed) {
return;
}
setIcloudLoadingState(true, `正在删除 ${alias.email} ...`);
try {
const response = await runtime.sendMessage({
type: 'DELETE_ICLOUD_ALIAS',
source: 'sidepanel',
payload: { email: alias.email, anonymousId: alias.anonymousId },
});
if (response?.error) throw new Error(response.error);
helpers.showToast(`已删除 ${alias.email}`, 'success', 2200);
await refreshIcloudAliases({ silent: true });
} catch (err) {
if (dom.icloudSummary) dom.icloudSummary.textContent = err.message;
helpers.showToast(`删除 iCloud 别名失败:${err.message}`, 'error');
} finally {
setIcloudLoadingState(false);
}
}
async function setSingleIcloudAliasUsedState(alias, used) {
setIcloudLoadingState(true, `正在更新 ${alias.email} 的使用状态...`);
try {
const response = await runtime.sendMessage({
type: 'SET_ICLOUD_ALIAS_USED_STATE',
source: 'sidepanel',
payload: { email: alias.email, used },
});
if (response?.error) throw new Error(response.error);
helpers.showToast(`${alias.email}${used ? '标记为已用' : '恢复为未用'}`, 'success', 2200);
await refreshIcloudAliases({ silent: true });
} catch (err) {
if (dom.icloudSummary) dom.icloudSummary.textContent = err.message;
helpers.showToast(`更新 iCloud 使用状态失败:${err.message}`, 'error');
} finally {
setIcloudLoadingState(false);
}
}
async function setSingleIcloudAliasPreservedState(alias, preserved) {
setIcloudLoadingState(true, `正在更新 ${alias.email} 的保留状态...`);
try {
const response = await runtime.sendMessage({
type: 'SET_ICLOUD_ALIAS_PRESERVED_STATE',
source: 'sidepanel',
payload: { email: alias.email, preserved },
});
if (response?.error) throw new Error(response.error);
helpers.showToast(`${alias.email}${preserved ? '设为保留' : '取消保留'}`, 'success', 2200);
await refreshIcloudAliases({ silent: true });
} catch (err) {
if (dom.icloudSummary) dom.icloudSummary.textContent = err.message;
helpers.showToast(`更新 iCloud 保留状态失败:${err.message}`, 'error');
} finally {
setIcloudLoadingState(false);
}
}
async function runBulkIcloudAction(action) {
const selectedAliases = renderedAliases.filter((alias) => selectedEmails.has(alias.email));
if (!selectedAliases.length) {
updateIcloudBulkUI();
return;
}
if (action === 'delete') {
const confirmed = await helpers.openConfirmModal({
title: '批量删除 iCloud 别名',
message: `确认删除选中的 ${selectedAliases.length} 个 iCloud 别名吗?此操作不可撤销。`,
confirmLabel: '确认删除',
confirmVariant: 'btn-danger',
});
if (!confirmed) {
return;
}
}
const actionLabelMap = {
used: '标记已用',
unused: '标记未用',
preserve: '保留',
unpreserve: '取消保留',
delete: '删除',
};
setIcloudLoadingState(true, `正在批量${actionLabelMap[action] || '处理'} iCloud 别名...`);
try {
for (const alias of selectedAliases) {
let response = null;
if (action === 'used' || action === 'unused') {
response = await runtime.sendMessage({
type: 'SET_ICLOUD_ALIAS_USED_STATE',
source: 'sidepanel',
payload: { email: alias.email, used: action === 'used' },
});
} else if (action === 'preserve' || action === 'unpreserve') {
response = await runtime.sendMessage({
type: 'SET_ICLOUD_ALIAS_PRESERVED_STATE',
source: 'sidepanel',
payload: { email: alias.email, preserved: action === 'preserve' },
});
} else if (action === 'delete') {
response = await runtime.sendMessage({
type: 'DELETE_ICLOUD_ALIAS',
source: 'sidepanel',
payload: { email: alias.email, anonymousId: alias.anonymousId },
});
selectedEmails.delete(alias.email);
}
if (response?.error) {
throw new Error(response.error);
}
}
helpers.showToast(`已批量${actionLabelMap[action] || '处理'} ${selectedAliases.length} 个 iCloud 别名`, 'success', 2400);
await refreshIcloudAliases({ silent: true });
} catch (err) {
if (dom.icloudSummary) dom.icloudSummary.textContent = err.message;
helpers.showToast(`批量处理 iCloud 别名失败:${err.message}`, 'error');
} finally {
setIcloudLoadingState(false);
updateIcloudBulkUI();
}
}
async function deleteUsedIcloudAliases() {
const confirmed = await helpers.openConfirmModal({
title: '删除已用 iCloud 别名',
message: '确认删除所有未保留的已用 iCloud 别名吗?此操作不可撤销。',
confirmLabel: '确认删除',
confirmVariant: 'btn-danger',
});
if (!confirmed) {
return;
}
setIcloudLoadingState(true, '正在删除已用 iCloud 别名...');
try {
const response = await runtime.sendMessage({
type: 'DELETE_USED_ICLOUD_ALIASES',
source: 'sidepanel',
payload: {},
});
if (response?.error) throw new Error(response.error);
const deleted = response?.deleted || [];
const skipped = response?.skipped || [];
helpers.showToast(`已删除 ${deleted.length} 个已用别名,跳过 ${skipped.length}`, skipped.length ? 'warn' : 'success', 2800);
await refreshIcloudAliases({ silent: true });
} catch (err) {
if (dom.icloudSummary) dom.icloudSummary.textContent = err.message;
helpers.showToast(`删除已用 iCloud 别名失败:${err.message}`, 'error');
} finally {
setIcloudLoadingState(false);
}
}
async function handleLoginDone() {
if (dom.btnIcloudLoginDone) {
dom.btnIcloudLoginDone.disabled = true;
}
try {
const response = await runtime.sendMessage({
type: 'CHECK_ICLOUD_SESSION',
source: 'sidepanel',
payload: {},
});
if (response?.error) {
throw new Error(response.error);
}
hideIcloudLoginHelp();
helpers.showToast('iCloud 会话已恢复,别名列表已刷新。', 'success', 2600);
await refreshIcloudAliases({ silent: true });
} catch (err) {
helpers.showToast(`看起来还没有登录完成:${err.message}`, 'warn', 4200);
} finally {
if (dom.btnIcloudLoginDone) {
dom.btnIcloudLoginDone.disabled = false;
}
}
}
function reset() {
selectedEmails.clear();
renderedAliases = [];
searchTerm = '';
filterMode = 'all';
refreshQueued = false;
if (dom.inputIcloudSearch) dom.inputIcloudSearch.value = '';
if (dom.selectIcloudFilter) dom.selectIcloudFilter.value = 'all';
if (dom.icloudList) dom.icloudList.innerHTML = '';
if (dom.icloudSummary) dom.icloudSummary.textContent = '加载你的 iCloud Hide My Email 别名以便在这里管理。';
updateIcloudBulkUI([]);
hideIcloudLoginHelp();
}
function hasDeletableUsedAliases() {
return renderedAliases.some((alias) => alias.used && !alias.preserved);
}
function bindIcloudEvents() {
dom.btnIcloudRefresh?.addEventListener('click', async () => {
await refreshIcloudAliases();
});
dom.btnIcloudDeleteUsed?.addEventListener('click', async () => {
await deleteUsedIcloudAliases();
});
dom.inputIcloudSearch?.addEventListener('input', () => {
searchTerm = dom.inputIcloudSearch.value || '';
renderIcloudAliases(renderedAliases);
});
dom.selectIcloudFilter?.addEventListener('change', () => {
filterMode = dom.selectIcloudFilter.value || 'all';
renderIcloudAliases(renderedAliases);
});
dom.checkboxIcloudSelectAll?.addEventListener('change', () => {
const visibleAliases = getFilteredIcloudAliases();
if (dom.checkboxIcloudSelectAll.checked) {
visibleAliases.forEach((alias) => selectedEmails.add(alias.email));
} else {
visibleAliases.forEach((alias) => selectedEmails.delete(alias.email));
}
renderIcloudAliases(renderedAliases);
});
dom.btnIcloudBulkUsed?.addEventListener('click', async () => {
await runBulkIcloudAction('used');
});
dom.btnIcloudBulkUnused?.addEventListener('click', async () => {
await runBulkIcloudAction('unused');
});
dom.btnIcloudBulkPreserve?.addEventListener('click', async () => {
await runBulkIcloudAction('preserve');
});
dom.btnIcloudBulkUnpreserve?.addEventListener('click', async () => {
await runBulkIcloudAction('unpreserve');
});
dom.btnIcloudBulkDelete?.addEventListener('click', async () => {
await runBulkIcloudAction('delete');
});
dom.btnIcloudLoginDone?.addEventListener('click', handleLoginDone);
}
return {
bindIcloudEvents,
hideIcloudLoginHelp,
hasDeletableUsedAliases,
queueIcloudAliasRefresh,
refreshIcloudAliases,
renderIcloudAliases,
reset,
showIcloudLoginHelp,
updateIcloudBulkUI,
};
}
globalScope.SidepanelIcloudManager = {
createIcloudManager,
};
})(window);
+467
View File
@@ -0,0 +1,467 @@
(function attachSidepanelLuckmailManager(globalScope) {
function createLuckmailManager(context = {}) {
const {
dom,
helpers,
runtime,
constants = {},
} = context;
const copyIcon = constants.copyIcon || '';
let renderedPurchases = [];
let selectedPurchaseIds = new Set();
let searchTerm = '';
let filterMode = 'all';
let refreshQueued = false;
function normalizeLuckmailSearchText(value) {
return String(value || '').trim().toLowerCase();
}
function getFilteredLuckmailPurchases(purchases = renderedPurchases) {
const normalizedSearchTerm = normalizeLuckmailSearchText(searchTerm);
return (Array.isArray(purchases) ? purchases : []).filter((purchase) => {
const matchesFilter = (() => {
switch (filterMode) {
case 'reusable': return Boolean(purchase.reusable);
case 'used': return Boolean(purchase.used);
case 'unused': return !purchase.used;
case 'preserved': return Boolean(purchase.preserved);
case 'disabled': return Boolean(purchase.disabled);
default: return true;
}
})();
if (!matchesFilter) return false;
if (!normalizedSearchTerm) return true;
const haystack = [
purchase.email_address,
purchase.project_name,
purchase.tag_name,
purchase.used ? '已用 used' : '未用 unused',
purchase.preserved ? '保留 preserved' : '',
purchase.disabled ? '已禁用 disabled' : '',
purchase.reusable ? '可复用 reusable' : '',
].join(' ').toLowerCase();
return haystack.includes(normalizedSearchTerm);
});
}
function pruneLuckmailSelection(purchases = renderedPurchases) {
const existingIds = new Set((Array.isArray(purchases) ? purchases : []).map((purchase) => String(purchase.id)));
selectedPurchaseIds = new Set([...selectedPurchaseIds].filter((id) => existingIds.has(id)));
}
function updateLuckmailBulkUI(visiblePurchases = getFilteredLuckmailPurchases()) {
if (!dom.checkboxLuckmailSelectAll || !dom.luckmailSelectionSummary) {
return;
}
const visibleIds = visiblePurchases.map((purchase) => String(purchase.id));
const selectedVisibleCount = visibleIds.filter((id) => selectedPurchaseIds.has(id)).length;
const hasVisible = visibleIds.length > 0;
const hasSelection = selectedPurchaseIds.size > 0;
dom.checkboxLuckmailSelectAll.checked = hasVisible && selectedVisibleCount === visibleIds.length;
dom.checkboxLuckmailSelectAll.indeterminate = selectedVisibleCount > 0 && selectedVisibleCount < visibleIds.length;
dom.checkboxLuckmailSelectAll.disabled = !hasVisible;
dom.luckmailSelectionSummary.textContent = `已选 ${selectedPurchaseIds.size} 个(当前显示 ${visibleIds.length} 个)`;
if (dom.btnLuckmailBulkUsed) dom.btnLuckmailBulkUsed.disabled = !hasSelection;
if (dom.btnLuckmailBulkUnused) dom.btnLuckmailBulkUnused.disabled = !hasSelection;
if (dom.btnLuckmailBulkPreserve) dom.btnLuckmailBulkPreserve.disabled = !hasSelection;
if (dom.btnLuckmailBulkUnpreserve) dom.btnLuckmailBulkUnpreserve.disabled = !hasSelection;
if (dom.btnLuckmailBulkDisable) dom.btnLuckmailBulkDisable.disabled = !hasSelection;
if (dom.btnLuckmailBulkEnable) dom.btnLuckmailBulkEnable.disabled = !hasSelection;
}
function setLuckmailLoadingState(loading, summary = '') {
if (dom.btnLuckmailRefresh) dom.btnLuckmailRefresh.disabled = loading;
if (dom.btnLuckmailDisableUsed) dom.btnLuckmailDisableUsed.disabled = loading;
if (dom.inputLuckmailSearch) dom.inputLuckmailSearch.disabled = loading;
if (dom.selectLuckmailFilter) dom.selectLuckmailFilter.disabled = loading;
if (dom.checkboxLuckmailSelectAll) dom.checkboxLuckmailSelectAll.disabled = loading || getFilteredLuckmailPurchases().length === 0;
if (dom.btnLuckmailBulkUsed) dom.btnLuckmailBulkUsed.disabled = loading || selectedPurchaseIds.size === 0;
if (dom.btnLuckmailBulkUnused) dom.btnLuckmailBulkUnused.disabled = loading || selectedPurchaseIds.size === 0;
if (dom.btnLuckmailBulkPreserve) dom.btnLuckmailBulkPreserve.disabled = loading || selectedPurchaseIds.size === 0;
if (dom.btnLuckmailBulkUnpreserve) dom.btnLuckmailBulkUnpreserve.disabled = loading || selectedPurchaseIds.size === 0;
if (dom.btnLuckmailBulkDisable) dom.btnLuckmailBulkDisable.disabled = loading || selectedPurchaseIds.size === 0;
if (dom.btnLuckmailBulkEnable) dom.btnLuckmailBulkEnable.disabled = loading || selectedPurchaseIds.size === 0;
if (summary && dom.luckmailSummary) {
dom.luckmailSummary.textContent = summary;
}
}
function renderLuckmailPurchases(purchases = renderedPurchases) {
if (!dom.luckmailList || !dom.luckmailSummary) return;
renderedPurchases = Array.isArray(purchases) ? purchases : [];
pruneLuckmailSelection(renderedPurchases);
dom.luckmailList.innerHTML = '';
if (!renderedPurchases.length) {
selectedPurchaseIds.clear();
dom.luckmailList.innerHTML = '<div class="luckmail-empty">未找到 openai 项目的 LuckMail 邮箱。</div>';
dom.luckmailSummary.textContent = '加载已购邮箱后可在这里管理 openai 项目的 LuckMail 邮箱。';
if (dom.btnLuckmailDisableUsed) dom.btnLuckmailDisableUsed.disabled = true;
updateLuckmailBulkUI([]);
return;
}
const usedCount = renderedPurchases.filter((purchase) => purchase.used).length;
const reusableCount = renderedPurchases.filter((purchase) => purchase.reusable).length;
const disableUsedCount = renderedPurchases.filter((purchase) => purchase.used && !purchase.preserved && !purchase.disabled).length;
dom.luckmailSummary.textContent = `已加载 ${renderedPurchases.length} 个 openai 邮箱,其中 ${reusableCount} 个可复用,${usedCount} 个已本地标记为已用。`;
if (dom.btnLuckmailDisableUsed) {
dom.btnLuckmailDisableUsed.textContent = `禁用已用${disableUsedCount > 0 ? `${disableUsedCount}` : ''}`;
dom.btnLuckmailDisableUsed.disabled = disableUsedCount === 0;
}
const visiblePurchases = getFilteredLuckmailPurchases(renderedPurchases);
if (!visiblePurchases.length) {
dom.luckmailList.innerHTML = '<div class="luckmail-empty">没有匹配当前筛选条件的 LuckMail 邮箱。</div>';
updateLuckmailBulkUI([]);
return;
}
for (const purchase of visiblePurchases) {
const purchaseId = String(purchase.id);
const item = document.createElement('div');
item.className = `luckmail-item${purchase.current ? ' is-current' : ''}`;
item.innerHTML = `
<input class="luckmail-item-check" type="checkbox" data-action="select" ${selectedPurchaseIds.has(purchaseId) ? 'checked' : ''} />
<div class="luckmail-item-main">
<div class="luckmail-item-email-row">
<div class="luckmail-item-email">${helpers.escapeHtml(purchase.email_address || '(未知邮箱)')}</div>
<button
class="hotmail-copy-btn"
type="button"
data-action="copy-email"
title="复制邮箱"
aria-label="复制邮箱 ${helpers.escapeHtml(purchase.email_address || '')}"
>${copyIcon}</button>
</div>
<div class="luckmail-item-meta">
<span class="luckmail-tag">${helpers.escapeHtml(helpers.normalizeLuckmailProjectName(purchase.project_name) || 'openai')}</span>
${purchase.reusable ? '<span class="luckmail-tag active">可复用</span>' : ''}
${purchase.current ? '<span class="luckmail-tag current">当前</span>' : ''}
${purchase.used ? '<span class="luckmail-tag used">已用</span>' : ''}
${purchase.preserved ? '<span class="luckmail-tag">保留</span>' : ''}
${purchase.disabled ? '<span class="luckmail-tag disabled">已禁用</span>' : ''}
${purchase.tag_name && normalizeLuckmailSearchText(purchase.tag_name) !== normalizeLuckmailSearchText(helpers.getLuckmailPreserveTagName())
? `<span class="luckmail-tag">${helpers.escapeHtml(purchase.tag_name)}</span>`
: ''}
</div>
<div class="luckmail-item-details">
<span>ID${helpers.escapeHtml(String(purchase.id || ''))}</span>
<span>保修至:${helpers.escapeHtml(helpers.formatLuckmailDateTime(purchase.warranty_until))}</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(purchase.used ? '标记未用' : '标记已用')}</button>
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-preserved">${helpers.escapeHtml(purchase.preserved ? '取消保留' : '保留')}</button>
<button class="btn btn-outline btn-xs" type="button" data-action="toggle-disabled">${helpers.escapeHtml(purchase.disabled ? '启用' : '禁用')}</button>
</div>
`;
item.querySelector('[data-action="select"]').addEventListener('change', (event) => {
if (event.target.checked) {
selectedPurchaseIds.add(purchaseId);
} else {
selectedPurchaseIds.delete(purchaseId);
}
updateLuckmailBulkUI(visiblePurchases);
});
item.querySelector('[data-action="copy-email"]').addEventListener('click', async () => {
await helpers.copyTextToClipboard(purchase.email_address || '');
helpers.showToast('邮箱已复制', 'success', 1600);
});
item.querySelector('[data-action="use"]').addEventListener('click', async () => {
await selectSingleLuckmailPurchase(purchase);
});
item.querySelector('[data-action="toggle-used"]').addEventListener('click', async () => {
await setSingleLuckmailPurchaseUsedState(purchase, !purchase.used);
});
item.querySelector('[data-action="toggle-preserved"]').addEventListener('click', async () => {
await setSingleLuckmailPurchasePreservedState(purchase, !purchase.preserved);
});
item.querySelector('[data-action="toggle-disabled"]').addEventListener('click', async () => {
await setSingleLuckmailPurchaseDisabledState(purchase, !purchase.disabled);
});
dom.luckmailList.appendChild(item);
}
updateLuckmailBulkUI(visiblePurchases);
}
async function refreshLuckmailPurchases(options = {}) {
const { silent = false } = options;
if (!dom.luckmailSection || dom.luckmailSection.style.display === 'none') {
return;
}
if (!silent) setLuckmailLoadingState(true, '正在加载 LuckMail openai 邮箱...');
try {
const response = await runtime.sendMessage({
type: 'LIST_LUCKMAIL_PURCHASES',
source: 'sidepanel',
payload: {},
});
if (response?.error) throw new Error(response.error);
renderLuckmailPurchases(response?.purchases || []);
} catch (err) {
selectedPurchaseIds.clear();
if (dom.luckmailList) {
dom.luckmailList.innerHTML = '<div class="luckmail-empty">无法加载 LuckMail 邮箱列表。</div>';
}
if (dom.luckmailSummary) {
dom.luckmailSummary.textContent = err.message;
}
updateLuckmailBulkUI([]);
if (!silent) {
helpers.showToast(`LuckMail 邮箱列表加载失败:${err.message}`, 'error');
}
} finally {
setLuckmailLoadingState(false);
}
}
function queueLuckmailPurchaseRefresh() {
if (refreshQueued) return;
refreshQueued = true;
setTimeout(async () => {
refreshQueued = false;
await refreshLuckmailPurchases({ silent: true });
}, 150);
}
async function selectSingleLuckmailPurchase(purchase) {
setLuckmailLoadingState(true, `正在切换到 ${purchase.email_address} ...`);
try {
const response = await runtime.sendMessage({
type: 'SELECT_LUCKMAIL_PURCHASE',
source: 'sidepanel',
payload: { purchaseId: purchase.id },
});
if (response?.error) throw new Error(response.error);
dom.inputEmail.value = response?.purchase?.email_address || purchase.email_address || '';
helpers.showToast(`已切换当前 LuckMail 邮箱为 ${purchase.email_address}`, 'success', 2200);
await refreshLuckmailPurchases({ silent: true });
} catch (err) {
if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message;
helpers.showToast(`切换 LuckMail 邮箱失败:${err.message}`, 'error');
} finally {
setLuckmailLoadingState(false);
}
}
async function setSingleLuckmailPurchaseUsedState(purchase, used) {
setLuckmailLoadingState(true, `正在更新 ${purchase.email_address} 的已用状态...`);
try {
const response = await runtime.sendMessage({
type: 'SET_LUCKMAIL_PURCHASE_USED_STATE',
source: 'sidepanel',
payload: { purchaseId: purchase.id, used },
});
if (response?.error) throw new Error(response.error);
helpers.showToast(`${purchase.email_address}${used ? '标记为已用' : '恢复为未用'}`, 'success', 2200);
await refreshLuckmailPurchases({ silent: true });
} catch (err) {
if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message;
helpers.showToast(`更新 LuckMail 已用状态失败:${err.message}`, 'error');
} finally {
setLuckmailLoadingState(false);
}
}
async function setSingleLuckmailPurchasePreservedState(purchase, preserved) {
setLuckmailLoadingState(true, `正在更新 ${purchase.email_address} 的保留状态...`);
try {
const response = await runtime.sendMessage({
type: 'SET_LUCKMAIL_PURCHASE_PRESERVED_STATE',
source: 'sidepanel',
payload: { purchaseId: purchase.id, preserved },
});
if (response?.error) throw new Error(response.error);
helpers.showToast(`${purchase.email_address}${preserved ? '设为保留' : '取消保留'}`, 'success', 2200);
await refreshLuckmailPurchases({ silent: true });
} catch (err) {
if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message;
helpers.showToast(`更新 LuckMail 保留状态失败:${err.message}`, 'error');
} finally {
setLuckmailLoadingState(false);
}
}
async function setSingleLuckmailPurchaseDisabledState(purchase, disabled) {
setLuckmailLoadingState(true, `正在${disabled ? '禁用' : '启用'} ${purchase.email_address} ...`);
try {
const response = await runtime.sendMessage({
type: 'SET_LUCKMAIL_PURCHASE_DISABLED_STATE',
source: 'sidepanel',
payload: { purchaseId: purchase.id, disabled },
});
if (response?.error) throw new Error(response.error);
helpers.showToast(`${purchase.email_address}${disabled ? '禁用' : '启用'}`, 'success', 2200);
await refreshLuckmailPurchases({ silent: true });
} catch (err) {
if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message;
helpers.showToast(`更新 LuckMail 禁用状态失败:${err.message}`, 'error');
} finally {
setLuckmailLoadingState(false);
}
}
async function runBulkLuckmailAction(action) {
const selectedIds = renderedPurchases
.filter((purchase) => selectedPurchaseIds.has(String(purchase.id)))
.map((purchase) => purchase.id);
if (!selectedIds.length) {
updateLuckmailBulkUI();
return;
}
const actionLabelMap = {
used: '标记已用',
unused: '标记未用',
preserve: '保留',
unpreserve: '取消保留',
disable: '禁用',
enable: '启用',
};
setLuckmailLoadingState(true, `正在批量${actionLabelMap[action] || '处理'} LuckMail 邮箱...`);
try {
const response = await runtime.sendMessage({
type: 'BATCH_UPDATE_LUCKMAIL_PURCHASES',
source: 'sidepanel',
payload: { action, ids: selectedIds },
});
if (response?.error) throw new Error(response.error);
helpers.showToast(`已批量${actionLabelMap[action] || '处理'} ${selectedIds.length} 个 LuckMail 邮箱`, 'success', 2400);
await refreshLuckmailPurchases({ silent: true });
} catch (err) {
if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message;
helpers.showToast(`批量处理 LuckMail 邮箱失败:${err.message}`, 'error');
} finally {
setLuckmailLoadingState(false);
updateLuckmailBulkUI();
}
}
async function disableUsedLuckmailPurchases() {
const confirmed = await helpers.openConfirmModal({
title: '禁用已用 LuckMail 邮箱',
message: '确认禁用所有本地已用且未保留的 openai LuckMail 邮箱吗?',
confirmLabel: '确认禁用',
confirmVariant: 'btn-danger',
});
if (!confirmed) {
return;
}
setLuckmailLoadingState(true, '正在禁用已用 LuckMail 邮箱...');
try {
const response = await runtime.sendMessage({
type: 'DISABLE_USED_LUCKMAIL_PURCHASES',
source: 'sidepanel',
payload: {},
});
if (response?.error) throw new Error(response.error);
const disabledCount = Array.isArray(response?.disabledIds) ? response.disabledIds.length : 0;
helpers.showToast(`已禁用 ${disabledCount} 个 LuckMail 邮箱`, disabledCount > 0 ? 'success' : 'info', 2400);
await refreshLuckmailPurchases({ silent: true });
} catch (err) {
if (dom.luckmailSummary) dom.luckmailSummary.textContent = err.message;
helpers.showToast(`禁用已用 LuckMail 邮箱失败:${err.message}`, 'error');
} finally {
setLuckmailLoadingState(false);
}
}
function reset() {
renderedPurchases = [];
selectedPurchaseIds.clear();
searchTerm = '';
filterMode = 'all';
refreshQueued = false;
if (dom.inputLuckmailSearch) dom.inputLuckmailSearch.value = '';
if (dom.selectLuckmailFilter) dom.selectLuckmailFilter.value = 'all';
if (dom.luckmailList) dom.luckmailList.innerHTML = '';
if (dom.luckmailSummary) dom.luckmailSummary.textContent = '加载已购邮箱后可在这里管理 openai 项目的 LuckMail 邮箱。';
if (dom.btnLuckmailDisableUsed) dom.btnLuckmailDisableUsed.disabled = true;
updateLuckmailBulkUI([]);
}
function bindLuckmailEvents() {
dom.inputLuckmailSearch?.addEventListener('input', (event) => {
searchTerm = event.target.value || '';
renderLuckmailPurchases(renderedPurchases);
});
dom.selectLuckmailFilter?.addEventListener('change', (event) => {
filterMode = String(event.target.value || 'all').trim() || 'all';
renderLuckmailPurchases(renderedPurchases);
});
dom.checkboxLuckmailSelectAll?.addEventListener('change', () => {
const visiblePurchases = getFilteredLuckmailPurchases();
if (dom.checkboxLuckmailSelectAll.checked) {
visiblePurchases.forEach((purchase) => selectedPurchaseIds.add(String(purchase.id)));
} else {
visiblePurchases.forEach((purchase) => selectedPurchaseIds.delete(String(purchase.id)));
}
renderLuckmailPurchases(renderedPurchases);
});
dom.btnLuckmailRefresh?.addEventListener('click', async () => {
await refreshLuckmailPurchases();
});
dom.btnLuckmailDisableUsed?.addEventListener('click', async () => {
await disableUsedLuckmailPurchases();
});
dom.btnLuckmailBulkUsed?.addEventListener('click', async () => {
await runBulkLuckmailAction('used');
});
dom.btnLuckmailBulkUnused?.addEventListener('click', async () => {
await runBulkLuckmailAction('unused');
});
dom.btnLuckmailBulkPreserve?.addEventListener('click', async () => {
await runBulkLuckmailAction('preserve');
});
dom.btnLuckmailBulkUnpreserve?.addEventListener('click', async () => {
await runBulkLuckmailAction('unpreserve');
});
dom.btnLuckmailBulkDisable?.addEventListener('click', async () => {
await runBulkLuckmailAction('disable');
});
dom.btnLuckmailBulkEnable?.addEventListener('click', async () => {
await runBulkLuckmailAction('enable');
});
}
return {
bindLuckmailEvents,
disableUsedLuckmailPurchases,
queueLuckmailPurchaseRefresh,
refreshLuckmailPurchases,
renderLuckmailPurchases,
reset,
};
}
globalScope.SidepanelLuckmailManager = {
createLuckmailManager,
};
})(window);
+3
View File
@@ -611,6 +611,9 @@
<script src="../hotmail-utils.js"></script>
<script src="../luckmail-utils.js"></script>
<script src="update-service.js"></script>
<script src="hotmail-manager.js"></script>
<script src="icloud-manager.js"></script>
<script src="luckmail-manager.js"></script>
<script src="sidepanel.js"></script>
</body>
+163 -1381
View File
File diff suppressed because it is too large Load Diff