dsh-folia 4.14.1 → 4.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +31 -3
- package/lib/routes/skins.js +44 -11
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -18309,6 +18309,9 @@ window.__ModuleLoader__.load({
|
|
|
18309
18309
|
function SkinsTabPanel() {
|
|
18310
18310
|
const [state, setState] = React.useState(null);
|
|
18311
18311
|
const [busy, setBusy] = React.useState("");
|
|
18312
|
+
// 给"静默对账"用的只读镜像:定时器/可见性回调里不能读闭包里的旧 `busy`(会一直看到 ""),
|
|
18313
|
+
// 用一个 ref 跟住最新值,操作进行中就不打扰用户。
|
|
18314
|
+
const busyRef = React.useRef("");
|
|
18312
18315
|
const [note, setNote] = React.useState(null);
|
|
18313
18316
|
|
|
18314
18317
|
const load = React.useCallback(async (force) => {
|
|
@@ -18356,11 +18359,33 @@ window.__ModuleLoader__.load({
|
|
|
18356
18359
|
|
|
18357
18360
|
React.useEffect(() => { load(false); }, [load]);
|
|
18358
18361
|
|
|
18362
|
+
// 与外部改动**保持同步**:皮肤可能在皮肤中心那边被删/装,也可能在另一个窗口里操作。
|
|
18363
|
+
// 之前只在挂载时拉一次 ⇒ 那边删了,这边还列着(点了才失败,看起来像"没同步")。
|
|
18364
|
+
// 三条腿都只做"重拉一次列表",不做别的:
|
|
18365
|
+
// ① 页签重新可见(switch 回来)② 窗口重新获得焦点 ③ 每 20 秒的轻量对账
|
|
18366
|
+
// 轻量对账只在**没有正在进行的操作**时跑,避免把用户刚点的"安装中…"状态冲掉。
|
|
18367
|
+
React.useEffect(() => {
|
|
18368
|
+
if (typeof document === "undefined" || typeof window === "undefined") return undefined;
|
|
18369
|
+
let timer = null;
|
|
18370
|
+
const refreshQuietly = () => { try { if (!busyRef.current) load(false); } catch { /* ignore */ } };
|
|
18371
|
+
const onVisible = () => { try { if (document.visibilityState === "visible") refreshQuietly(); } catch { /* ignore */ } };
|
|
18372
|
+
try { document.addEventListener("visibilitychange", onVisible); } catch { /* ignore */ }
|
|
18373
|
+
try { window.addEventListener("focus", refreshQuietly); } catch { /* ignore */ }
|
|
18374
|
+
try { timer = window.setInterval(refreshQuietly, 20000); } catch { timer = null; }
|
|
18375
|
+
return () => {
|
|
18376
|
+
try { document.removeEventListener("visibilitychange", onVisible); } catch { /* ignore */ }
|
|
18377
|
+
try { window.removeEventListener("focus", refreshQuietly); } catch { /* ignore */ }
|
|
18378
|
+
if (timer !== null) { try { window.clearInterval(timer); } catch { /* ignore */ } }
|
|
18379
|
+
};
|
|
18380
|
+
}, [load]);
|
|
18381
|
+
|
|
18359
18382
|
const act = async (kind, payload, label) => {
|
|
18360
18383
|
// 结构性操作才锁整块 UI;`activate` 是幂等的、而且要让人能连点换皮肤,所以不锁。
|
|
18361
18384
|
const structural = kind === "install" || kind === "remove";
|
|
18362
|
-
|
|
18363
|
-
|
|
18385
|
+
const busyKey = structural
|
|
18386
|
+
? kind + ":" + ((payload && payload.id) || "")
|
|
18387
|
+
: (kind === "activate" ? "activate:" + ((payload && payload.id) || "") : "");
|
|
18388
|
+
if (busyKey) { setBusy(busyKey); busyRef.current = busyKey; }
|
|
18364
18389
|
setNote(null);
|
|
18365
18390
|
try {
|
|
18366
18391
|
if (kind === "activate") {
|
|
@@ -18454,6 +18479,7 @@ window.__ModuleLoader__.load({
|
|
|
18454
18479
|
setNote({ ok: false, text: weT("请求失败:{msg}", { msg: (e && e.message) || weT("网络错误") }) });
|
|
18455
18480
|
} finally {
|
|
18456
18481
|
setBusy("");
|
|
18482
|
+
busyRef.current = "";
|
|
18457
18483
|
}
|
|
18458
18484
|
};
|
|
18459
18485
|
|
|
@@ -18572,9 +18598,11 @@ window.__ModuleLoader__.load({
|
|
|
18572
18598
|
// 皮肤中心**没收录**这套(它有自己的 manifest 规则)⇒ 应用后它会回退到内置默认款,
|
|
18573
18599
|
// 用户看到的是"点哪套都不变"。这是实测症状(别人机器上"只剩鲸鱼娘"),所以这里
|
|
18574
18600
|
// 直接把原因与「应用」的禁用一起给出来,而不是让人一遍遍点。
|
|
18601
|
+
// ⚠️ 只有 `=== false` 才下结论:`null` 表示"问不到它的 catalog"(未知),
|
|
18602
|
+
// 未知绝不能禁用按钮 —— 那会变成"替我猜错了就不让我点"。
|
|
18575
18603
|
const notListed = s.inCatalog === false;
|
|
18576
18604
|
const notListedWhy = notListed
|
|
18577
|
-
? weT("
|
|
18605
|
+
? weT("皮肤中心仍未收录这套(它按 manifest 规则筛选:id 必须等于目录名、必须 v2、name/nameEn/author/stylesheet 齐全)。点「刷新」会强制它对账一次;若仍显示这条,说明确实被它拒收")
|
|
18578
18606
|
: "";
|
|
18579
18607
|
return React.createElement("div", {
|
|
18580
18608
|
key: s.id,
|
package/lib/routes/skins.js
CHANGED
|
@@ -181,8 +181,24 @@ async function fetchSkinCenterCatalog(hostHeader, force) {
|
|
|
181
181
|
const siteCache = { at: 0, items: [], error: null };
|
|
182
182
|
const SITE_TTL_MS = 30000;
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
/**
|
|
185
|
+
* 给"本机已装"逐条附上**皮肤中心是否收录**。
|
|
186
|
+
*
|
|
187
|
+
* 比对口径要宽:它的 catalog 以 `manifest.id` 为键,而我们的条目同时有 `id`(manifest)与
|
|
188
|
+
* `dirId`(目录名),且历史上站点 slug 可能带 `skin-` 前缀 ⇒ 三种形态都试,任一命中即算收录。
|
|
189
|
+
* `catalog === null`(问不到)时给 `null`:**未知不等于未收录**,界面不能据此禁用「应用」。
|
|
190
|
+
*/
|
|
191
|
+
function annotateCatalog(installed, catalog) {
|
|
192
|
+
if (!catalog) return installed.map((s) => ({ ...s, inCatalog: null, catalogWarnings: [] }));
|
|
193
|
+
return installed.map((s) => {
|
|
194
|
+
const hit = catalog.has(s.id) ? s.id
|
|
195
|
+
: catalog.has(s.dirId) ? s.dirId
|
|
196
|
+
: catalog.has(normSkinId(s.id)) ? normSkinId(s.id) : null;
|
|
197
|
+
return { ...s, inCatalog: Boolean(hit), catalogWarnings: hit ? catalog.get(hit) : [] };
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function fetchSiteSkins(force) { const now = Date.now();
|
|
186
202
|
if (!force && siteCache.at && now - siteCache.at < SITE_TTL_MS) return siteCache;
|
|
187
203
|
const base = siteBase();
|
|
188
204
|
if (!base) { siteCache.at = now; siteCache.items = []; siteCache.error = 'site-off'; return siteCache; }
|
|
@@ -385,14 +401,18 @@ export function registerSkinsRoutes(webServer, c) {
|
|
|
385
401
|
// 关键对账:**它收录了哪几套**。`null` = 问不到(不据此判红)。
|
|
386
402
|
// 被它剔除的皮肤"能装但应用后画面不变"——它客户端会回退到内置默认款,
|
|
387
403
|
// 用户看到的就是"点哪套都还是鲸鱼娘/blue-fantasy"(实测症状)。
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
404
|
+
//
|
|
405
|
+
// ⚠️ 但"它没列出"**不能**只凭一次缓存就下定论:刚装完皮肤时,它自己的 catalog 可能还没
|
|
406
|
+
// 重扫(它按目录 mtime 做缓存),我们 30 秒的副本更可能比它更旧。实测:别人机器上刚装完
|
|
407
|
+
// 就被标成「皮肤中心未收录」,而打开它的设置页却是有的 —— 一次假红。
|
|
408
|
+
// 所以对账分两拍:先按缓存比,**出现"未收录"就强制重拉一次**再定论。
|
|
409
|
+
const hostHeader = req.headers && req.headers.host;
|
|
410
|
+
let catalog = skinCenter === 'present' ? await fetchSkinCenterCatalog(hostHeader, force) : null;
|
|
411
|
+
let installed = annotateCatalog(installedBase, catalog);
|
|
412
|
+
if (catalog && installed.some((s) => s.inCatalog === false)) {
|
|
413
|
+
catalog = await fetchSkinCenterCatalog(hostHeader, true);
|
|
414
|
+
installed = annotateCatalog(installedBase, catalog);
|
|
415
|
+
}
|
|
396
416
|
// ⚠️ 站点 slug 与本机皮肤 id **不总是同一个字符串**:站点上 slug 是全站唯一的,
|
|
397
417
|
// 撞名时上传工具会加 `skin-` 前缀(实测 blue-throated-bee-eater 同时是桌宠 id)。
|
|
398
418
|
// 所以比对要归一:两边都去掉可选的 `skin-` 前缀再比,否则会显示成"未安装"。
|
|
@@ -524,11 +544,24 @@ export function registerSkinsRoutes(webServer, c) {
|
|
|
524
544
|
const dir = resolveInside(root, id);
|
|
525
545
|
if (!dir) { sendJson(res, 400, { ok: false, error: 'unsafe-id' }); return; }
|
|
526
546
|
if (!existsSync(dir)) { sendJson(res, 404, { ok: false, error: 'not-installed' }); return; }
|
|
547
|
+
// 删之前先看它是不是"当前正在用的那套":如果是,必须**先把选择清掉**再删目录,
|
|
548
|
+
// 否则 active 指向一个不存在的 id ⇒ 皮肤中心回退到内置默认款,用户看到的是
|
|
549
|
+
// "皮肤没换成我选的那套"(实测症状)。清空走同一套兜底链路(HTTP→状态文件)。
|
|
550
|
+
let clearedActive = false;
|
|
551
|
+
try {
|
|
552
|
+
const statePath = activeStatePath();
|
|
553
|
+
let cur = null;
|
|
554
|
+
try { cur = (JSON.parse(readFileSync(statePath, 'utf8')) || {}).active || null; } catch { cur = null; }
|
|
555
|
+
if (cur && (cur === id || normSkinId(cur) === normSkinId(id))) {
|
|
556
|
+
writeActiveStateFile(null);
|
|
557
|
+
clearedActive = true;
|
|
558
|
+
}
|
|
559
|
+
} catch { /* 清不掉也不阻塞删除:目录照删,只是 active 可能悬空(页签会显示"恢复默认外观") */ }
|
|
527
560
|
try { rmSync(dir, { recursive: true, force: true }); } catch (e) {
|
|
528
561
|
sendJson(res, 500, { ok: false, error: 'remove-failed', detail: String(e && e.message).slice(0, 160) });
|
|
529
562
|
return;
|
|
530
563
|
}
|
|
531
|
-
sendJson(res, 200, { ok: true, id });
|
|
564
|
+
sendJson(res, 200, { ok: true, id, clearedActive });
|
|
532
565
|
},
|
|
533
566
|
}));
|
|
534
567
|
|