feat(popup): 重做扩展云同步中心与项目文档

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Developer
2026-03-18 00:28:14 +08:00
parent 5d0611de60
commit a7ff557942
4 changed files with 1080 additions and 125 deletions

298
popup.js
View File

@@ -1,33 +1,277 @@
document.getElementById('copyBtn').addEventListener('click', function() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
var activeTab = tabs && tabs[0];
if (!activeTab || typeof activeTab.id !== 'number') {
alert('未找到当前标签页');
return;
}
chrome.tabs.sendMessage(activeTab.id, { action: 'getMagnets' }, function(response) {
function sendMessageToBackground(message) {
return new Promise(function(resolve, reject) {
chrome.runtime.sendMessage(message, function(response) {
if (chrome.runtime.lastError) {
alert('当前页面不支持,请在涩花塘论坛页面使用');
reject(new Error(chrome.runtime.lastError.message));
return;
}
var magnets = response && Array.isArray(response.magnets) ? response.magnets : [];
magnets = Array.from(new Set(magnets));
if (magnets.length === 0) {
alert('当前页面未找到磁力链接');
return;
}
navigator.clipboard.writeText(magnets.join('\n'))
.then(function() {
alert('已复制 ' + magnets.length + ' 个磁力链接!');
})
.catch(function(err) {
var errorMsg = err && err.message ? err.message : '复制失败';
alert('复制失败:' + errorMsg);
});
resolve(response || null);
});
});
}
function setMessage(text, isError) {
var message = document.getElementById('message');
if (!message) return;
message.textContent = text || '';
message.style.color = isError ? '#ff8585' : '#8892a4';
}
var lastGoodCloudStats = null;
var lastGoodSyncStatus = null;
var syncProgressTimer = null;
var lastServerHealthy = null;
function wait(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
function setSyncProgress(text) {
var el = document.getElementById('syncProgressText');
if (!el) return;
el.textContent = text || '';
}
function startSyncProgress(prefix) {
var startedAt = Date.now();
var dots = 0;
if (syncProgressTimer) {
clearInterval(syncProgressTimer);
}
setSyncProgress((prefix || '正在同步') + ' 0s');
syncProgressTimer = setInterval(function() {
dots = (dots + 1) % 4;
var suffix = new Array(dots + 1).join('.');
var elapsed = Math.max(0, Math.floor((Date.now() - startedAt) / 1000));
setSyncProgress((prefix || '正在同步') + suffix + ' ' + elapsed + 's');
}, 500);
}
function stopSyncProgress(text) {
if (syncProgressTimer) {
clearInterval(syncProgressTimer);
syncProgressTimer = null;
}
setSyncProgress(text || '');
}
function updateStatus(status) {
if (status && status.authenticated !== undefined) {
lastGoodSyncStatus = status;
}
var accountDot = document.getElementById('accountDot');
var accountText = document.getElementById('accountText');
var email = document.getElementById('syncEmail');
var loggedOut = document.getElementById('authLoggedOut');
var loggedIn = document.getElementById('authLoggedIn');
var loggedInEmail = document.getElementById('loggedInEmail');
var healthy = !!(status && status.color === 'green');
var authenticated = !!(status && status.authenticated);
if (accountDot) accountDot.classList.toggle('ok', healthy);
if (accountText) accountText.textContent = status && (status.accountText || status.text) ? (status.accountText || status.text) : '账号状态未知';
if (email) email.textContent = status && status.email ? ('账号:' + status.email) : '';
if (loggedOut) loggedOut.classList.toggle('hidden', authenticated);
if (loggedIn) loggedIn.classList.toggle('hidden', !authenticated);
if (loggedInEmail) loggedInEmail.textContent = status && status.email ? ('当前账号:' + status.email) : '当前账号:未知';
}
function updateServerStatus(ok, text) {
var serverDot = document.getElementById('serverDot');
var serverText = document.getElementById('serverText');
lastServerHealthy = !!ok;
if (serverDot) serverDot.classList.toggle('ok', !!ok);
if (serverText) serverText.textContent = text || (ok ? '服务器状态正常' : '服务器状态异常');
}
function formatDateTime(value) {
if (!value) return '暂无';
try {
var date = new Date(value);
if (Number.isNaN(date.getTime())) return String(value);
return date.toLocaleString('zh-CN');
} catch (error) {
return String(value);
}
}
function renderCloudStats(stats) {
var summary = document.getElementById('cloudStatsSummary');
var latest = document.getElementById('cloudStatsLatest');
var tables = document.getElementById('cloudStatsTables');
if (!summary || !latest || !tables) return;
if (!stats || !stats.ok) {
if (lastGoodCloudStats && lastGoodCloudStats.ok) {
updateServerStatus(true, '服务器状态正常(展示最近一次成功数据)');
summary.textContent = '云端状态暂时刷新失败,显示最近一次成功数据';
latest.textContent = '最近成功更新:线程 ' + formatDateTime(lastGoodCloudStats.latest.threads) + ' 范围 ' + formatDateTime(lastGoodCloudStats.latest.coverages) + ' 页缓存 ' + formatDateTime(lastGoodCloudStats.latest.pages);
tables.textContent = '上次空间占用:' + (Array.isArray(lastGoodCloudStats.tables) ? lastGoodCloudStats.tables.map(function(table) {
return table.tableName + ' ' + table.sizeMb + 'MB';
}).join(' ') : '暂无');
return;
}
updateServerStatus(false, '服务器状态暂时不可读');
summary.textContent = '暂时无法读取云端状态';
latest.textContent = '';
tables.textContent = '';
return;
}
updateServerStatus(true, '服务器状态正常');
lastGoodCloudStats = stats;
summary.textContent = '帖子索引 ' + stats.counts.threads + ' 条(含磁力 ' + (stats.counts.magnetThreads || 0) + ' 条)/ 页缓存 ' + stats.counts.pages + ' 条 / 范围缓存 ' + stats.counts.coverages + ' 条 / 用户 ' + stats.counts.users + ' 个 / 保险柜 ' + stats.counts.vaultItems + ' 条';
latest.textContent = '最近更新:线程 ' + formatDateTime(stats.latest.threads) + ' 范围 ' + formatDateTime(stats.latest.coverages) + ' 页缓存 ' + formatDateTime(stats.latest.pages);
tables.textContent = '空间占用:' + (Array.isArray(stats.tables) ? stats.tables.map(function(table) {
return table.tableName + ' ' + table.sizeMb + 'MB';
}).join(' ') : '暂无');
}
function formatBytes(bytes) {
var value = Number(bytes || 0);
if (!Number.isFinite(value) || value <= 0) return '0 B';
if (value < 1024) return value + ' B';
if (value < 1024 * 1024) return (value / 1024).toFixed(1) + ' KB';
if (value < 1024 * 1024 * 1024) return (value / 1024 / 1024).toFixed(2) + ' MB';
return (value / 1024 / 1024 / 1024).toFixed(2) + ' GB';
}
function renderLocalStats(stats) {
var summary = document.getElementById('localStatsSummary');
var storage = document.getElementById('localStatsStorage');
var meta = document.getElementById('localStatsMeta');
if (!summary || !storage || !meta) return;
if (!stats || !stats.ok) {
summary.textContent = '暂时无法读取本地状态';
storage.textContent = '';
meta.textContent = '';
return;
}
summary.textContent = '帖子索引 ' + stats.counts.threads + ' 条(含磁力 ' + (stats.counts.magnetThreads || 0) + ' 条)/ 页缓存 ' + stats.counts.pages + ' 条 / 范围缓存 ' + stats.counts.coverages + ' 条 / 收藏 ' + stats.counts.favorites + ' 条 / 历史 ' + stats.counts.history + ' 条';
storage.textContent = '本地存储占用:' + formatBytes(stats.storage.usage) + ' / ' + formatBytes(stats.storage.quota);
meta.textContent = '上传抑制元数据:线程 ' + stats.counts.uploadMetaThreads + ' 条 范围 ' + stats.counts.uploadMetaCoverages + ' 条 页 ' + stats.counts.uploadMetaPages + ' 条';
}
async function refreshStatus() {
try {
var response = await sendMessageToBackground({ action: 'cloudGetSyncStatus' });
if (response && response.ok !== false) {
updateStatus(response || { color: 'red', text: '状态未知', authenticated: false });
return;
}
if (lastGoodSyncStatus) {
updateStatus(lastGoodSyncStatus);
setMessage('云同步状态暂时刷新失败,显示最近一次成功状态', true);
return;
}
updateStatus({ color: 'red', text: '云同步异常', authenticated: false, email: '' });
} catch (error) {
if (lastGoodSyncStatus) {
updateStatus(lastGoodSyncStatus);
setMessage('云同步状态暂时刷新失败,显示最近一次成功状态', true);
return;
}
updateStatus({ color: 'red', text: '云同步异常', authenticated: false, email: '' });
setMessage('状态获取失败:' + error.message, true);
}
}
async function refreshCloudStats() {
var attempts = 0;
var response = null;
var error = null;
for (attempts = 0; attempts < 3; attempts++) {
try {
response = await sendMessageToBackground({ action: 'cloudGetCacheStats' });
if (response && response.ok) {
renderCloudStats(response);
return;
}
} catch (err) {
error = err;
}
await wait(500 * (attempts + 1));
}
renderCloudStats(null);
if (error) {
setMessage('云端状态暂时刷新失败,稍后会自动重试', true);
}
}
async function refreshLocalStats() {
try {
var response = await sendMessageToBackground({ action: 'localGetCacheStats' });
renderLocalStats(response);
} catch (error) {
renderLocalStats(null);
}
}
async function submitAuth(action) {
var emailInput = document.getElementById('emailInput');
var passwordInput = document.getElementById('passwordInput');
var email = emailInput ? emailInput.value.trim() : '';
var password = passwordInput ? passwordInput.value : '';
if (!email || !password) {
setMessage('请输入邮箱和密码', true);
return;
}
setMessage(action === 'cloudRegister' ? '正在注册...' : '正在登录...', false);
try {
var response = await sendMessageToBackground({ action: action, email: email, password: password });
if (!response || !response.ok) {
setMessage((action === 'cloudRegister' ? '注册失败:' : '登录失败:') + (response && response.error ? response.error : '未知错误'), true);
return;
}
if (passwordInput) passwordInput.value = '';
updateStatus(response.status || { color: 'green', text: '云同步正常', authenticated: true, email: email });
setMessage(action === 'cloudRegister' ? '注册成功,云同步已开启' : '登录成功,云同步已开启', false);
} catch (error) {
setMessage((action === 'cloudRegister' ? '注册失败:' : '登录失败:') + error.message, true);
}
}
async function syncNow() {
setMessage('', false);
startSyncProgress('正在同步本地缓存到云端');
try {
await sendMessageToBackground({ action: 'cloudBackfillLocalCache', force: true });
stopSyncProgress('已提交同步任务,正在刷新状态...');
await wait(1200);
startSyncProgress('正在刷新云端状态');
await refreshStatus();
await refreshCloudStats();
await refreshLocalStats();
stopSyncProgress('同步完成');
setMessage('已触发云端回填', false);
} catch (error) {
stopSyncProgress('同步失败');
setMessage('同步失败:' + error.message, true);
}
}
async function logout() {
try {
await sendMessageToBackground({ action: 'cloudLogout' });
await refreshStatus();
setMessage('已退出登录', false);
} catch (error) {
setMessage('退出失败:' + error.message, true);
}
}
function openExternalUrl(url) {
chrome.tabs.create({ url: url });
}
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('loginBtn').addEventListener('click', function() { submitAuth('cloudLogin'); });
document.getElementById('registerBtn').addEventListener('click', function() { submitAuth('cloudRegister'); });
document.getElementById('syncNowBtn').addEventListener('click', syncNow);
document.getElementById('logoutBtn').addEventListener('click', logout);
document.getElementById('downloadNode2Btn').addEventListener('click', function() { openExternalUrl('http://7.haory.cn/x/x72/qBittorrent_5.1.4_x64_setup.exe'); });
document.getElementById('downloadOfficialBtn').addEventListener('click', function() { openExternalUrl('https://sourceforge.net/projects/qbittorrent/files/qbittorrent-win32/qbittorrent-5.1.4/qbittorrent_5.1.4_x64_setup.exe/download'); });
document.getElementById('downloadMotrixBtn').addEventListener('click', function() { openExternalUrl('http://7.haory.cn/x/x72/Motrix-Setup-1.8.19.exe'); });
refreshStatus();
refreshCloudStats();
refreshLocalStats();
});