feat(油猴): 行内推送按钮 + 已推送画廊标灰
需求两条:
1) 列表页的复选框改成按钮,点一下直接推送到工具
2) 推送过但没点进去的画廊,标题也要变灰(像浏览过的),
这样刷新后还认得出哪些推过
一、行内推送按钮
复选框 + 底部「批量推送」两步走太啰嗦,列表页每行改成「📤 推送」
胶囊按钮,点一下直接调 /add。按钮三态:
📤 推送 -> ⏳ 推送中(禁用) -> ✓ 已推送(变绿)
底部悬浮条保留,改为「全部推送」+「重置状态」+ 本页计数。
二、标灰::visited 伪造不了,只能自己记
E-Hentai 的灰色来自自带样式
a:visited .glink, a:active .glink { color:#8F6063 }
但 :visited 是浏览器**受保护**伪类:脚本查 matches(':visited')
永远返回 false,加 class 也触发不了站点的原生规则。
所以改成自己记一份「推送过」的清单,再照抄站点的色值手动染色,
观感与原生 :visited 一致(hover 恢复站点的 #8F4701)。
三、清单存哪:工具端为主,浏览器端为辅
新增 internal/core/pushed.go —— PushedStore 独立于 Store,
单独存 %APPDATA%\EH Downloader\pushed.json。
为什么单独一个文件:state.json 里的 tasks 是「当前要下载的东西」,
用户点「清除已完成」就删掉了;但「这个画廊我推送过」是历史事实,
删了任务不该忘掉。所以推送历史与任务列表解耦。
新增桥口 GET /pushed 返回推送历史(与 GET /tasks 语义不同:
后者是当前任务列表,会被清除)。
油猴脚本侧:
- localStorage(ehdl_pushed_gids)先渲染,刷新后立刻可见,
工具不在线也能用
- 工具在线时用 /pushed 校正一次(localStorage 可能被清,
工具才是权威),以工具为准整体覆盖
- GET /tasks 保留给需要看当前任务列表的场合
四、附带:state.json 事故的恢复工具
tools/recoverstate —— 修复被写坏的 state.json:
1) token 先从下载目录的 .ehdl.json 清单里找
2) 找不到的用 E-Hentai 的 ?next=<gid> 定位法去站点抓
(该 URL 会返回以 gid 开头的 25 条列表,正则取 token,
offset 0..5 轮着试)
3) 抓不到的说明画廊在站点上已被删除
-dry 预演不写盘,写盘前先备份原文件。
配套回归测试:
- internal/core/pushed_test.go 推送历史读写、任务删除后仍保留
- internal/core/recover_e2e_test.go 恢复出的任务能真下载(71/71 页)
- internal/eh/recover_verify_test.go 恢复出的 token 能解析画廊
- app_test.go 点「清除已完成」后灰标不消失
- internal/core/bridge_test.go /tasks 与 /pushed 的 token 校验
后两者需要能连 e-hentai.org,设 EH_TEST_PROXY 才跑。
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
// ==UserScript==
|
||||
// @name EH Downloader 助手
|
||||
// @namespace http://127.0.0.1:37777
|
||||
// @version 2.0.0
|
||||
// @description ① 画廊页:把当前画廊推送到本地 EH Downloader ② 列表页:勾选后批量推送
|
||||
// @version 2.3.0
|
||||
// @description ① 画廊页:把当前画廊推送到本地 EH Downloader ② 列表页:每行一个推送按钮,点击直接推送;推送过的标题标灰
|
||||
// @author jack
|
||||
// @match https://e-hentai.org/*
|
||||
// @match https://exhentai.org/*
|
||||
@@ -56,6 +56,103 @@
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 公共:已推送记录
|
||||
//
|
||||
// 浏览器给「访问过的画廊」上灰色用的是原生 :visited 伪类,
|
||||
// 这个伪类受浏览器保护,脚本没法伪造(matches(':visited') 永远 false)。
|
||||
// 所以这里自己记一份「推送过」的清单,再照着 E-Hentai 自己的色值
|
||||
// (a:visited .glink { color:#8F6063 } ) 把标题染成一样的灰。
|
||||
//
|
||||
// 记录放 localStorage:同源、刷新后还在、工具不在线也能用。
|
||||
// 工具在线时再用 /pushed 校正一次(localStorage 可能被清,工具才是权威)。
|
||||
//
|
||||
// /pushed 读的是工具自己的 pushed.json —— 独立于任务列表,
|
||||
// 所以点「清除已完成」把任务删了,灰标依然在。
|
||||
// ============================================================
|
||||
|
||||
const PUSHED_KEY = 'ehdl_pushed_gids';
|
||||
const VISITED_COLOR = '#8F6063'; // E-Hentai 自带样式里的 :visited 色值
|
||||
|
||||
/** 读取本地已推送的 gid 集合 */
|
||||
function loadPushed() {
|
||||
const set = new Set();
|
||||
try {
|
||||
const raw = localStorage.getItem(PUSHED_KEY);
|
||||
if (raw) JSON.parse(raw).forEach((g) => set.add(String(g)));
|
||||
} catch (_) {}
|
||||
return set;
|
||||
}
|
||||
|
||||
/** 把 gid 记进本地清单 */
|
||||
function rememberPushed(gid) {
|
||||
const set = loadPushed();
|
||||
set.add(String(gid));
|
||||
try {
|
||||
localStorage.setItem(PUSHED_KEY, JSON.stringify([...set]));
|
||||
} catch (_) {}
|
||||
return set;
|
||||
}
|
||||
|
||||
/** 从工具拉取任务清单,校正本地记录 */
|
||||
function syncPushedFromTool(cb) {
|
||||
GM_xmlhttpRequest({
|
||||
method: 'GET',
|
||||
url: `${API}/pushed`,
|
||||
headers: { 'X-EHDL-Token': TOKEN },
|
||||
timeout: 8000,
|
||||
onload: (r) => {
|
||||
if (r.status !== 200) return cb(false);
|
||||
let j = {};
|
||||
try { j = JSON.parse(r.responseText); } catch (_) { return cb(false); }
|
||||
if (!j.ok || !Array.isArray(j.items)) return cb(false);
|
||||
const set = new Set();
|
||||
j.items.forEach((t) => { if (t && t.gid) set.add(String(t.gid)); });
|
||||
// 工具里的为准:整体覆盖本地
|
||||
try { localStorage.setItem(PUSHED_KEY, JSON.stringify([...set])); } catch (_) {}
|
||||
cb(true, set);
|
||||
},
|
||||
onerror: () => cb(false),
|
||||
ontimeout: () => cb(false),
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 公共:弹窗
|
||||
// ============================================================
|
||||
|
||||
function ensureModal() {
|
||||
let mask = document.getElementById('ehdl-mask');
|
||||
if (mask) return mask;
|
||||
|
||||
mask = document.createElement('div');
|
||||
mask.id = 'ehdl-mask';
|
||||
mask.innerHTML = `
|
||||
<div class="ehdl-modal" role="dialog" aria-modal="true">
|
||||
<div class="ehdl-modal-icon">⚠️</div>
|
||||
<div class="ehdl-modal-title">EH Downloader 未运行</div>
|
||||
<div class="ehdl-modal-msg">请先打开 EH Downloader 工具,然后再推送。</div>
|
||||
<div class="ehdl-modal-actions">
|
||||
<button class="ehdl-modal-btn primary" id="ehdl-modal-close">知道了</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(mask);
|
||||
|
||||
const close = () => { mask.classList.remove('show'); };
|
||||
mask.querySelector('#ehdl-modal-close').addEventListener('click', close);
|
||||
mask.addEventListener('click', (e) => { if (e.target === mask) close(); });
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && mask.classList.contains('show')) close();
|
||||
});
|
||||
return mask;
|
||||
}
|
||||
|
||||
function showOfflineModal() {
|
||||
const mask = ensureModal();
|
||||
mask.classList.add('show');
|
||||
}
|
||||
|
||||
const CSS = `
|
||||
#ehdl-btn {
|
||||
display:inline-flex; align-items:center; gap:6px;
|
||||
@@ -73,13 +170,27 @@
|
||||
#ehdl-status.err{color:#f85149}
|
||||
#ehdl-status.ok{color:#3fb950}
|
||||
|
||||
/* 列表页勾选框 */
|
||||
.ehdl-cb {
|
||||
display:inline-block; vertical-align:middle; margin-right:6px;
|
||||
width:15px; height:15px; cursor:pointer; accent-color:#4a9eff;
|
||||
position:relative; top:-1px;
|
||||
/* 列表页:每行的推送按钮 */
|
||||
.ehdl-push {
|
||||
display:inline-flex; align-items:center; justify-content:center; gap:4px;
|
||||
vertical-align:middle; margin-right:7px;
|
||||
padding:2px 9px; border-radius:12px; cursor:pointer;
|
||||
font:12px/1.6 -apple-system,"PingFang SC",sans-serif;
|
||||
color:#fff; background:#4a9eff; border:1px solid #3b8ae6;
|
||||
transition:background .12s,opacity .12s; white-space:nowrap;
|
||||
}
|
||||
.ehdl-cb-row.off a{ opacity:.5 }
|
||||
.ehdl-push:hover:not([disabled]){background:#62acff}
|
||||
.ehdl-push:active:not([disabled]){transform:translateY(1px)}
|
||||
.ehdl-push[disabled]{opacity:.5;cursor:default}
|
||||
.ehdl-push.is-done{background:#3fb950;border-color:#3fb950}
|
||||
.ehdl-push.is-done:hover:not([disabled]){background:#4cc45c}
|
||||
.ehdl-push.is-busy{background:#6b7280;border-color:#6b7280}
|
||||
|
||||
/* 已推送的画廊:标题染成和 E-Hentai :visited 一样的灰色 */
|
||||
a.ehdl-pushed .glink{ color:#8F6063 !important }
|
||||
a.ehdl-pushed:hover .glink{ color:#8F4701 !important }
|
||||
/* 标签也跟着淡一点,跟站点观感一致 */
|
||||
a.ehdl-pushed .gt{ opacity:.75 }
|
||||
|
||||
/* 悬浮批量工具条 */
|
||||
#ehdl-bar {
|
||||
@@ -106,6 +217,35 @@
|
||||
#ehdl-bar .msg{ max-width:280px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
|
||||
#ehdl-bar .msg.err{ color:#f85149 }
|
||||
#ehdl-bar .msg.ok{ color:#3fb950 }
|
||||
|
||||
/* 弹窗 */
|
||||
#ehdl-mask {
|
||||
position:fixed; inset:0; z-index:2147483100;
|
||||
display:none; align-items:center; justify-content:center;
|
||||
background:rgba(0,0,0,.45); backdrop-filter:blur(2px);
|
||||
}
|
||||
#ehdl-mask.show{ display:flex }
|
||||
#ehdl-mask .ehdl-modal {
|
||||
min-width:280px; max-width:360px; padding:22px 22px 16px;
|
||||
border-radius:14px; background:#1e2128; color:#e6e8ec;
|
||||
border:1px solid #333944; box-shadow:0 18px 60px rgba(0,0,0,.6);
|
||||
font:13px/1.6 -apple-system,"PingFang SC",sans-serif;
|
||||
text-align:center; animation:ehdl-pop .14s ease-out;
|
||||
}
|
||||
@keyframes ehdl-pop{ from{ transform:scale(.94); opacity:0 } to{ transform:scale(1); opacity:1 } }
|
||||
#ehdl-mask .ehdl-modal-icon{ font-size:32px; line-height:1.2; margin-bottom:6px }
|
||||
#ehdl-mask .ehdl-modal-title{ font-size:15px; font-weight:600; margin-bottom:6px }
|
||||
#ehdl-mask .ehdl-modal-msg{ font-size:12.5px; color:#9aa3b2; margin-bottom:16px }
|
||||
#ehdl-mask .ehdl-modal-actions{ display:flex; gap:9px; justify-content:center }
|
||||
#ehdl-mask .ehdl-modal-btn {
|
||||
padding:6px 16px; border-radius:8px; cursor:pointer; font-size:12.5px;
|
||||
border:1px solid #3d4450; background:#2a2f38; color:#e6e8ec;
|
||||
}
|
||||
#ehdl-mask .ehdl-modal-btn:hover{ background:#333944 }
|
||||
#ehdl-mask .ehdl-modal-btn.primary{
|
||||
background:#4a9eff; border-color:#4a9eff; color:#04121f; font-weight:600;
|
||||
}
|
||||
#ehdl-mask .ehdl-modal-btn.primary:hover{ background:#62acff }
|
||||
`;
|
||||
|
||||
addStyle(CSS);
|
||||
@@ -159,8 +299,18 @@
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
if (!online) {
|
||||
markOffline();
|
||||
setStatus('EH Downloader 没在运行,请先打开它', 'err');
|
||||
// 重新探一次:工具可能刚被打开
|
||||
ping((ok) => {
|
||||
online = ok;
|
||||
if (!ok) {
|
||||
markOffline();
|
||||
showOfflineModal();
|
||||
setStatus('EH Downloader 没在运行,请先打开它', 'err');
|
||||
return;
|
||||
}
|
||||
btn.classList.remove('is-off');
|
||||
btn.click();
|
||||
});
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
@@ -171,10 +321,12 @@
|
||||
online = false;
|
||||
markOffline();
|
||||
btn.innerHTML = '<span>📤</span><span>发送画廊地址</span>';
|
||||
showOfflineModal();
|
||||
setStatus(j.error === 'timeout' ? '✗ 连接超时,请先打开工具' : '✗ 连不上,请先打开 EH Downloader', 'err');
|
||||
return;
|
||||
}
|
||||
markDone();
|
||||
rememberPushed(gid);
|
||||
setStatus(j.existed ? '已在列表中(跳过重复)' : '✓ 已推送,等待下载', 'ok');
|
||||
});
|
||||
});
|
||||
@@ -188,11 +340,11 @@
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 模式 B:列表页 —— 勾选批量推送
|
||||
// 模式 B:列表页 —— 每行一个推送按钮,点击直接推送
|
||||
// ============================================================
|
||||
|
||||
function runListPage() {
|
||||
// 收集本页所有画廊链接(按 gid 去重)
|
||||
// 收集本页所有画廊链接(按 gid/token 去重)
|
||||
const links = [...document.querySelectorAll('a[href*="/g/"]')];
|
||||
const seen = new Set();
|
||||
const items = [];
|
||||
@@ -217,35 +369,21 @@
|
||||
url: `${location.origin}/g/${gid}/${token}/`,
|
||||
title: readTitle(a),
|
||||
anchor: a,
|
||||
cb: null,
|
||||
btn: null,
|
||||
state: 'idle', // idle | busy | done
|
||||
});
|
||||
}
|
||||
|
||||
if (!items.length) return false;
|
||||
|
||||
// 每行前面插复选框
|
||||
for (const it of items) {
|
||||
const a = it.anchor;
|
||||
if (!a || !a.parentNode) continue;
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.className = 'ehdl-cb';
|
||||
cb.title = `勾选 ${it.gid}`;
|
||||
a.parentNode.insertBefore(cb, a);
|
||||
cb.addEventListener('change', refresh);
|
||||
it.cb = cb;
|
||||
}
|
||||
|
||||
// ---- 悬浮工具条 ----
|
||||
// ---- 悬浮工具条(显示状态 / 批量操作) ----
|
||||
const bar = document.createElement('div');
|
||||
bar.id = 'ehdl-bar';
|
||||
bar.innerHTML = `
|
||||
<span class="dot" id="ehdl-dot"></span>
|
||||
<span class="n">已选 <b id="ehdl-n">0</b> / ${items.length}</span>
|
||||
<button id="ehdl-all">全选</button>
|
||||
<button id="ehdl-inv">反选</button>
|
||||
<button id="ehdl-none">清空</button>
|
||||
<button id="ehdl-send" class="primary" disabled>推送到工具</button>
|
||||
<span class="n">本页 <b id="ehdl-n">0</b> / ${items.length}</span>
|
||||
<button id="ehdl-all">全部推送</button>
|
||||
<button id="ehdl-none">重置状态</button>
|
||||
<span class="msg" id="ehdl-msg"></span>
|
||||
`;
|
||||
document.body.appendChild(bar);
|
||||
@@ -253,68 +391,189 @@
|
||||
const $ = (id) => bar.querySelector('#' + id);
|
||||
const dot = $('ehdl-dot');
|
||||
const numEl = $('ehdl-n');
|
||||
const sendBtn = $('ehdl-send');
|
||||
const msg = $('ehdl-msg');
|
||||
|
||||
let online = false;
|
||||
const boxes = items.map((i) => i.cb).filter(Boolean);
|
||||
|
||||
function selected() {
|
||||
return boxes.filter((c) => c.checked).length;
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
const n = selected();
|
||||
numEl.textContent = n;
|
||||
sendBtn.disabled = n === 0 || !online;
|
||||
sendBtn.textContent = online ? '推送到工具' : '工具未运行';
|
||||
if (!online) { sendBtn.classList.remove('primary'); }
|
||||
else { sendBtn.classList.add('primary'); }
|
||||
}
|
||||
|
||||
function say(text, kind) {
|
||||
msg.textContent = text;
|
||||
msg.className = 'msg' + (kind ? ' ' + kind : '');
|
||||
}
|
||||
|
||||
$('ehdl-all').onclick = () => { boxes.forEach((c) => (c.checked = true)); refresh(); };
|
||||
$('ehdl-inv').onclick = () => { boxes.forEach((c) => (c.checked = !c.checked)); refresh(); };
|
||||
$('ehdl-none').onclick = () => { boxes.forEach((c) => (c.checked = false)); refresh(); };
|
||||
// ---- 把「已推送」的画廊标题染灰 ----
|
||||
// 用 a.ehdl-pushed 选中的 .glink 染色,和站点 :visited 的观感一致
|
||||
function markPushed(it) {
|
||||
const a = it.anchor;
|
||||
if (!a) return;
|
||||
const glink = a.querySelector('.glink');
|
||||
if (!glink) return;
|
||||
a.classList.add('ehdl-pushed');
|
||||
glink.style.color = VISITED_COLOR;
|
||||
glink.dataset.ehdlPushed = '1';
|
||||
}
|
||||
|
||||
sendBtn.onclick = () => {
|
||||
const picked = items.filter((i) => i.cb && i.cb.checked);
|
||||
if (!picked.length) return;
|
||||
sendBtn.disabled = true;
|
||||
say(`推送 ${picked.length} 个…`, '');
|
||||
function unmarkPushed(it) {
|
||||
const a = it.anchor;
|
||||
if (!a) return;
|
||||
const glink = a.querySelector('.glink');
|
||||
a.classList.remove('ehdl-pushed');
|
||||
if (glink) {
|
||||
glink.style.color = '';
|
||||
delete glink.dataset.ehdlPushed;
|
||||
}
|
||||
}
|
||||
|
||||
post('/add-batch', {
|
||||
items: picked.map((i) => ({ gid: i.gid, token: i.token, url: i.url, title: i.title })),
|
||||
}, (ok, j) => {
|
||||
sendBtn.disabled = false;
|
||||
// 本地记录里已经推过的,先染灰(刷新后立刻可见,不用等工具)
|
||||
const localPushed = loadPushed();
|
||||
items.forEach((it) => { if (localPushed.has(String(it.gid))) markPushed(it); });
|
||||
|
||||
// ---- 每行前面插推送按钮 ----
|
||||
for (const it of items) {
|
||||
const a = it.anchor;
|
||||
if (!a || !a.parentNode) continue;
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'ehdl-push';
|
||||
btn.innerHTML = '<span>📤</span><span>推送</span>';
|
||||
btn.title = `推送到 EH Downloader(${it.gid})`;
|
||||
a.parentNode.insertBefore(btn, a);
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
pushOne(it, btn);
|
||||
});
|
||||
it.btn = btn;
|
||||
}
|
||||
|
||||
function setBtnState(it, btn, state) {
|
||||
it.state = state;
|
||||
if (state === 'busy') {
|
||||
btn.classList.add('is-busy');
|
||||
btn.classList.remove('is-done');
|
||||
btn.innerHTML = '<span>⏳</span><span>推送中</span>';
|
||||
btn.disabled = true;
|
||||
} else if (state === 'done') {
|
||||
btn.classList.remove('is-busy');
|
||||
btn.classList.add('is-done');
|
||||
btn.innerHTML = '<span>✓</span><span>已推送</span>';
|
||||
btn.disabled = false;
|
||||
} else {
|
||||
btn.classList.remove('is-busy');
|
||||
btn.classList.remove('is-done');
|
||||
btn.innerHTML = '<span>📤</span><span>推送</span>';
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 推送单个 ----
|
||||
function pushOne(it, btn) {
|
||||
if (!online) {
|
||||
// 重新探一次:工具可能刚被打开
|
||||
ping((ok) => {
|
||||
online = ok;
|
||||
dot.classList.toggle('on', ok);
|
||||
if (!ok) {
|
||||
showOfflineModal();
|
||||
say('EH Downloader 未运行', 'err');
|
||||
return;
|
||||
}
|
||||
pushOne(it, btn);
|
||||
});
|
||||
return;
|
||||
}
|
||||
setBtnState(it, btn, 'busy');
|
||||
post('/add', { gid: it.gid, token: it.token, url: it.url, title: it.title }, (ok, j) => {
|
||||
if (!ok) {
|
||||
online = false;
|
||||
dot.classList.remove('on');
|
||||
setBtnState(it, btn, 'idle');
|
||||
showOfflineModal();
|
||||
say(j.error === 'timeout' ? '✗ 连接超时,请先打开工具' : '✗ 连不上,请先打开 EH Downloader', 'err');
|
||||
return;
|
||||
}
|
||||
setBtnState(it, btn, 'done');
|
||||
markPushed(it);
|
||||
rememberPushed(it.gid);
|
||||
numEl.textContent = items.filter((i) => i.state === 'done').length;
|
||||
say(j.existed ? '已在列表中(跳过重复)' : '✓ 已推送,等待下载', 'ok');
|
||||
});
|
||||
}
|
||||
|
||||
// ---- 批量推送本页全部 ----
|
||||
$('ehdl-all').onclick = () => {
|
||||
if (!online) {
|
||||
ping((ok) => {
|
||||
online = ok;
|
||||
dot.classList.toggle('on', ok);
|
||||
if (!ok) {
|
||||
showOfflineModal();
|
||||
say('EH Downloader 未运行', 'err');
|
||||
return;
|
||||
}
|
||||
$('ehdl-all').onclick();
|
||||
});
|
||||
return;
|
||||
}
|
||||
const pending = items.filter((i) => i.btn && i.state !== 'busy');
|
||||
if (!pending.length) return;
|
||||
|
||||
pending.forEach((i) => setBtnState(i, i.btn, 'busy'));
|
||||
say(`推送 ${pending.length} 个…`, '');
|
||||
|
||||
post('/add-batch', {
|
||||
items: pending.map((i) => ({ gid: i.gid, token: i.token, url: i.url, title: i.title })),
|
||||
}, (ok, j) => {
|
||||
if (!ok) {
|
||||
online = false;
|
||||
dot.classList.remove('on');
|
||||
pending.forEach((i) => setBtnState(i, i.btn, 'idle'));
|
||||
showOfflineModal();
|
||||
say(j.error === 'timeout' ? '✗ 连接超时,请先打开工具' : '✗ 连不上,请先打开 EH Downloader', 'err');
|
||||
refresh();
|
||||
return;
|
||||
}
|
||||
const bits = [];
|
||||
if (j.added) bits.push(`新增 ${j.added}`);
|
||||
if (j.existed) bits.push(`已存在 ${j.existed}`);
|
||||
say(`✓ ${bits.join(' · ') || '无变化'}`, 'ok');
|
||||
picked.forEach((i) => { if (i.cb) i.cb.checked = false; });
|
||||
refresh();
|
||||
pending.forEach((i) => {
|
||||
setBtnState(i, i.btn, 'done');
|
||||
markPushed(i);
|
||||
rememberPushed(i.gid);
|
||||
});
|
||||
numEl.textContent = items.filter((i) => i.state === 'done').length;
|
||||
});
|
||||
};
|
||||
|
||||
$('ehdl-none').onclick = () => {
|
||||
items.forEach((i) => {
|
||||
if (!i.btn) return;
|
||||
setBtnState(i, i.btn, 'idle');
|
||||
unmarkPushed(i);
|
||||
});
|
||||
numEl.textContent = 0;
|
||||
say('已重置', '');
|
||||
};
|
||||
|
||||
ping((ok) => {
|
||||
online = ok;
|
||||
dot.classList.toggle('on', ok);
|
||||
say(ok ? '工具运行中' : 'EH Downloader 未运行', ok ? 'ok' : 'err');
|
||||
refresh();
|
||||
if (!ok) return;
|
||||
|
||||
// 工具在线:以工具的任务清单为准,校正本地记录 + 染色
|
||||
syncPushedFromTool((synced, set) => {
|
||||
if (!synced) return;
|
||||
items.forEach((it) => {
|
||||
const on = set.has(String(it.gid));
|
||||
if (on) markPushed(it);
|
||||
else if (!loadPushed().has(String(it.gid))) unmarkPushed(it);
|
||||
// 工具里已有的,按钮也显示成「已推送」
|
||||
if (on && it.state === 'idle') setBtnState(it, it.btn, 'done');
|
||||
});
|
||||
numEl.textContent = items.filter((i) => i.state === 'done').length;
|
||||
});
|
||||
});
|
||||
|
||||
refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -324,4 +583,4 @@
|
||||
|
||||
if (runGalleryPage()) return;
|
||||
runListPage();
|
||||
})();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user