dsh-folia 2.8.0 → 2.9.1
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 +171 -45
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -16231,38 +16231,68 @@ window.__ModuleLoader__.load({
|
|
|
16231
16231
|
}
|
|
16232
16232
|
|
|
16233
16233
|
/**
|
|
16234
|
-
*
|
|
16234
|
+
* 取一套皮肤**最终该用的 CSS**。
|
|
16235
16235
|
*
|
|
16236
|
-
*
|
|
16237
|
-
*
|
|
16238
|
-
*
|
|
16239
|
-
*
|
|
16240
|
-
*
|
|
16236
|
+
* 关键:皮肤中心不是把皮肤目录里的 CSS 原样发出去的 ——
|
|
16237
|
+
* `GET /api/skin-center/v2/skins/<id>/stylesheet` → `transformSkinCss()`:**强制作用域化**
|
|
16238
|
+
* `GET /api/skin-center/v2/skins/<id>/patches` → 同上(无则 404)
|
|
16239
|
+
* 作用域化 = 把每条规则包进 `html[data-dsh-skin="<id>"]`。这一步直接决定成败:
|
|
16240
|
+
* **包了作用域的规则特异性更高**,才压得过应用自身的面板/令牌规则。我们早先注入的是
|
|
16241
|
+
* 目录里的裸 CSS,特异性与应用同级 ⇒ 大量规则输掉 ⇒ 配色变了一点、面板依旧不透明。
|
|
16241
16242
|
*
|
|
16242
|
-
*
|
|
16243
|
+
* 所以优先**直接用它加工好的那份**(同源,浏览器自带凭证);只有拿不到才退回我们自己的
|
|
16244
|
+
* `/folia/skins/css`(未作用域化,能变配色但压不住应用规则)。
|
|
16245
|
+
*
|
|
16246
|
+
* @returns {Promise<{ css: string, source: string }>}
|
|
16247
|
+
*/
|
|
16248
|
+
async function fetchSkinCss(id) {
|
|
16249
|
+
const enc = encodeURIComponent(id);
|
|
16250
|
+
const parts = [];
|
|
16251
|
+
let source = "";
|
|
16252
|
+
for (const part of ["stylesheet", "patches"]) {
|
|
16253
|
+
try {
|
|
16254
|
+
// 根相对路径 + 原生请求:桌面壳可能跑在 `dsh-app://` 下(`location.origin` 是 "null"),
|
|
16255
|
+
// 拼绝对 URL 会作废;`/api/...` 交给浏览器按文档解析才可靠
|
|
16256
|
+
//(皮肤中心自己的客户端用的也是这种写法)。
|
|
16257
|
+
const r = await apiRawResponse("/api/skin-center/v2/skins/" + enc + "/" + part);
|
|
16258
|
+
if (r && r.ok) {
|
|
16259
|
+
const text = await r.text();
|
|
16260
|
+
if (text && text.trim()) {
|
|
16261
|
+
parts.push(text);
|
|
16262
|
+
source = "skin-center";
|
|
16263
|
+
}
|
|
16264
|
+
}
|
|
16265
|
+
} catch { /* 拿不到就试下一段 / 退回自己的路由 */ }
|
|
16266
|
+
}
|
|
16267
|
+
if (parts.length > 0) return { css: parts.join("\n/* ---- patches ---- */\n"), source };
|
|
16268
|
+
const r2 = await apiFetch("/skins/css?id=" + enc + "&t=" + Date.now(), { parse: false, cache: "no-store" });
|
|
16269
|
+
if (!r2 || !r2.ok || !r2.response) throw new Error("css-http-" + ((r2 && r2.status) || 0));
|
|
16270
|
+
const text = await r2.response.text();
|
|
16271
|
+
if (!text || !text.trim()) throw new Error("css-empty");
|
|
16272
|
+
return { css: text, source: "folia-raw" };
|
|
16273
|
+
}
|
|
16274
|
+
|
|
16275
|
+
/**
|
|
16276
|
+
* 把一套皮肤的样式**当场**挂进文档。
|
|
16277
|
+
*
|
|
16278
|
+
* 用 `<style>` 而不是 `<link>`:`<style>` 一赋值就参与样式计算,且追加到 `<body>` 末尾
|
|
16279
|
+
* ⇒ 文档顺序最后、同等特异性下后写的赢;`<link>` 是异步的,顺序不由我们定。
|
|
16243
16280
|
*
|
|
16244
16281
|
* @param {string} id 皮肤 id
|
|
16245
|
-
* @returns {Promise<{ bytes: number, rules: number }>}
|
|
16282
|
+
* @returns {Promise<{ bytes: number, rules: number, source: string }>}
|
|
16246
16283
|
*/
|
|
16247
16284
|
async function applySkinCss(id) {
|
|
16248
|
-
const
|
|
16249
|
-
parse: false,
|
|
16250
|
-
cache: "no-store",
|
|
16251
|
-
});
|
|
16252
|
-
if (!resp || !resp.ok || !resp.response) throw new Error("css-http-" + ((resp && resp.status) || 0));
|
|
16253
|
-
const css = await resp.response.text();
|
|
16254
|
-
if (!css || !css.trim()) throw new Error("css-empty");
|
|
16285
|
+
const { css, source } = await fetchSkinCss(id);
|
|
16255
16286
|
let el = document.getElementById("folia-skin-hot");
|
|
16256
16287
|
if (!el) {
|
|
16257
16288
|
el = document.createElement("style");
|
|
16258
16289
|
el.id = "folia-skin-hot";
|
|
16259
|
-
// 挂 body 末尾(不是 head):文档顺序最后 ⇒ 同等特异性下覆盖先前的规则。
|
|
16260
16290
|
(document.body || document.head).appendChild(el);
|
|
16261
16291
|
}
|
|
16262
16292
|
el.textContent = css;
|
|
16263
16293
|
let rules = 0;
|
|
16264
16294
|
try { rules = (el.sheet && el.sheet.cssRules && el.sheet.cssRules.length) || 0; } catch { /* 读不到就报 0 */ }
|
|
16265
|
-
return { bytes: css.length, rules };
|
|
16295
|
+
return { bytes: css.length, rules, source };
|
|
16266
16296
|
}
|
|
16267
16297
|
|
|
16268
16298
|
/**
|
|
@@ -16480,6 +16510,84 @@ window.__ModuleLoader__.load({
|
|
|
16480
16510
|
} catch { /* ignore */ }
|
|
16481
16511
|
}
|
|
16482
16512
|
|
|
16513
|
+
/**
|
|
16514
|
+
* 把一套皮肤**完整**应用上去(作用域 + 六层 + 样式 + 背景 + hooks)。
|
|
16515
|
+
* 按钮和"壁纸撤下后自动恢复"走的是**同一个**函数 —— 两条路径不能各写一遍。
|
|
16516
|
+
*/
|
|
16517
|
+
async function applySkinHot(id) {
|
|
16518
|
+
clearSkinHot();
|
|
16519
|
+
try { document.documentElement.setAttribute("data-dsh-skin", id); } catch { /* ignore */ }
|
|
16520
|
+
ensureSkinLayers();
|
|
16521
|
+
const info = await applySkinCss(id);
|
|
16522
|
+
const bg = await applySkinBackground(id);
|
|
16523
|
+
let hooksInfo = { ran: false, reason: "skipped" };
|
|
16524
|
+
try {
|
|
16525
|
+
hooksInfo = await applySkinHooks(id);
|
|
16526
|
+
} catch (e) {
|
|
16527
|
+
hooksInfo = { ran: false, reason: String((e && e.message) || e) };
|
|
16528
|
+
}
|
|
16529
|
+
return { info, bg, hooks: hooksInfo };
|
|
16530
|
+
}
|
|
16531
|
+
|
|
16532
|
+
/** 把皮肤**整个撤掉**,回到应用原生外观(壁纸接管背景时走这条)。 */
|
|
16533
|
+
function teardownSkin() {
|
|
16534
|
+
clearSkinHot();
|
|
16535
|
+
try { document.documentElement.removeAttribute("data-dsh-skin"); } catch { /* ignore */ }
|
|
16536
|
+
try { document.body.removeAttribute("data-dsh-skin-center"); } catch { /* ignore */ }
|
|
16537
|
+
for (const name of SKIN_LAYER_NAMES) {
|
|
16538
|
+
try {
|
|
16539
|
+
const el = document.querySelector('[data-dsh-skin-layer="' + name + '"]');
|
|
16540
|
+
if (el) el.innerHTML = "";
|
|
16541
|
+
} catch { /* ignore */ }
|
|
16542
|
+
}
|
|
16543
|
+
}
|
|
16544
|
+
|
|
16545
|
+
/** 向宿主问"当前选中的皮肤是哪个"(没选中返回空串)。 */
|
|
16546
|
+
async function activeSkinIdFromHost() {
|
|
16547
|
+
try {
|
|
16548
|
+
const resp = await apiJson("/skins");
|
|
16549
|
+
const doc = (resp && resp.data) || null;
|
|
16550
|
+
return (doc && typeof doc.active === "string") ? doc.active : "";
|
|
16551
|
+
} catch {
|
|
16552
|
+
return "";
|
|
16553
|
+
}
|
|
16554
|
+
}
|
|
16555
|
+
|
|
16556
|
+
let skinArbiterStarted = false;
|
|
16557
|
+
/**
|
|
16558
|
+
* **皮肤与壁纸互斥的仲裁器**(两个方向都要管)。
|
|
16559
|
+
*
|
|
16560
|
+
* 现状:点「使用」时我们会主动清掉壁纸(一个方向);但用户**反过来**点壁纸时,
|
|
16561
|
+
* 没人撤掉我们热注入的皮肤 —— 于是皮肤那层不透明样式压在壁纸上面,壁纸看起来被"遮挡"。
|
|
16562
|
+
*
|
|
16563
|
+
* 这里盯 `body[data-we-wallpaper]` 这个标记(**壁纸插件自己在挂/摘**,是双方共同的事实源):
|
|
16564
|
+
* · 标记出现 ⇒ 撤掉皮肤(teardownSkin)
|
|
16565
|
+
* · 标记消失 ⇒ 若选中过皮肤,重新完整应用(applySkinHot)
|
|
16566
|
+
* 这样无论用户先点哪边,最后都是"只有一个在台"。
|
|
16567
|
+
*/
|
|
16568
|
+
function startSkinArbiter() {
|
|
16569
|
+
if (skinArbiterStarted) return;
|
|
16570
|
+
const body = (typeof document !== "undefined") ? document.body : null;
|
|
16571
|
+
if (!body) { setTimeout(startSkinArbiter, 400); return; }
|
|
16572
|
+
skinArbiterStarted = true;
|
|
16573
|
+
let seq = 0;
|
|
16574
|
+
const sync = async () => {
|
|
16575
|
+
const mine = ++seq;
|
|
16576
|
+
const wallpaperOn = body.hasAttribute("data-we-wallpaper");
|
|
16577
|
+
if (wallpaperOn) { teardownSkin(); return; }
|
|
16578
|
+
const id = await activeSkinIdFromHost();
|
|
16579
|
+
if (mine !== seq) return; // 期间又变了,放弃这次
|
|
16580
|
+
if (!id) return;
|
|
16581
|
+
try { await applySkinHot(id); } catch { /* 失败就保持原生外观 */ }
|
|
16582
|
+
};
|
|
16583
|
+
try {
|
|
16584
|
+
new MutationObserver(() => { sync().catch(() => {}); })
|
|
16585
|
+
.observe(body, { attributes: true, attributeFilter: ["data-we-wallpaper"] });
|
|
16586
|
+
// 启动时就跑一次:处理"上次退出时壁纸在台、皮肤被让位"这类残留状态
|
|
16587
|
+
sync().catch(() => {});
|
|
16588
|
+
} catch { /* 环境不支持观察就只保留按钮路径 */ }
|
|
16589
|
+
}
|
|
16590
|
+
|
|
16483
16591
|
function SkinsTabPanel(props) {
|
|
16484
16592
|
// 壁纸让位:挂载点(设置页 / 首页抽屉)各自把"清掉当前壁纸"的回调传进来 ——
|
|
16485
16593
|
// 皮肤和壁纸都在铺背景,必须二选一。本组件不直接碰设置,只调用传进来的入口。
|
|
@@ -16545,51 +16653,36 @@ window.__ModuleLoader__.load({
|
|
|
16545
16653
|
setNote({ kind: "ok", text: weT("已安装:") + label });
|
|
16546
16654
|
await load(true);
|
|
16547
16655
|
} else if (kind === "activate") {
|
|
16548
|
-
//
|
|
16549
|
-
|
|
16550
|
-
// 这一行就是判断"是没注入"还是"注入了但被压住"的依据,不用再猜。
|
|
16656
|
+
// **当场换肤**:作用域 + 六层 + 样式 + 背景 + hooks,全在 applySkinHot 里
|
|
16657
|
+
//(与"壁纸撤下后自动恢复"共用同一实现,两条路径不会漂移)。
|
|
16551
16658
|
let info = null;
|
|
16552
16659
|
let applyErr = "";
|
|
16553
16660
|
let bgKind = "none";
|
|
16554
16661
|
let hooksInfo = { ran: false, reason: "skipped" };
|
|
16555
16662
|
try {
|
|
16556
|
-
// ①
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
try { document.documentElement.setAttribute("data-dsh-skin", id); } catch { /* ignore */ }
|
|
16561
|
-
// ③ 六个装饰层(hooks 与部分皮肤 CSS 都要用)
|
|
16562
|
-
ensureSkinLayers();
|
|
16563
|
-
// ④ 样式、⑤ 背景、⑥ hooks
|
|
16564
|
-
info = await applySkinCss(id);
|
|
16565
|
-
const bg = await applySkinBackground(id);
|
|
16566
|
-
bgKind = bg.kind;
|
|
16567
|
-
try {
|
|
16568
|
-
hooksInfo = await applySkinHooks(id);
|
|
16569
|
-
} catch (e) {
|
|
16570
|
-
hooksInfo = { ran: false, reason: String((e && e.message) || e) };
|
|
16663
|
+
// ① 壁纸先让位:清掉 `id` 之后壁纸层撤下、`data-we-wallpaper` 消失,
|
|
16664
|
+
// 皮肤中心也不会再扣住皮肤。(反方向由 startSkinArbiter 负责。)
|
|
16665
|
+
if (typeof onClearWallpaper === "function") {
|
|
16666
|
+
try { await Promise.resolve(onClearWallpaper()); } catch { /* 清不掉也继续换肤 */ }
|
|
16571
16667
|
}
|
|
16668
|
+
// ② 应用皮肤
|
|
16669
|
+
const applied = await applySkinHot(id);
|
|
16670
|
+
info = applied.info;
|
|
16671
|
+
bgKind = applied.bg.kind;
|
|
16672
|
+
hooksInfo = applied.hooks;
|
|
16572
16673
|
} catch (e) {
|
|
16573
16674
|
applyErr = String((e && e.message) || e);
|
|
16574
16675
|
}
|
|
16575
|
-
// ⑦ 壁纸让位:皮肤和壁纸都在铺背景,必须互斥。清掉 `id` 之后壁纸层会自己撤下
|
|
16576
|
-
// (同时 `data-we-wallpaper` 标记消失,皮肤中心也不会再扣住皮肤)。
|
|
16577
|
-
let wallpaperCleared = false;
|
|
16578
|
-
try {
|
|
16579
|
-
if (typeof onClearWallpaper === "function") {
|
|
16580
|
-
await Promise.resolve(onClearWallpaper());
|
|
16581
|
-
wallpaperCleared = true;
|
|
16582
|
-
}
|
|
16583
|
-
} catch { /* 清壁纸失败不影响换肤本身 */ }
|
|
16584
16676
|
setNote({
|
|
16585
16677
|
kind: applyErr ? "err" : "ok",
|
|
16586
16678
|
text: applyErr
|
|
16587
16679
|
? weT("已切换为「") + label + weT("」,但样式注入失败:") + applyErr
|
|
16588
16680
|
: weT("已切换到「") + label + weT("」—— 样式 ")
|
|
16589
16681
|
+ Math.round(info.bytes / 1024) + " KB / " + info.rules + weT(" 条规则")
|
|
16682
|
+
+ (info.source === "skin-center" ? weT("(已作用域化)") : weT("(未作用域化,可能显示不全)"))
|
|
16590
16683
|
+ (bgKind === "none" ? weT(",无背景媒体") : weT(",背景:") + bgKind)
|
|
16591
16684
|
+ (hooksInfo.ran ? weT(",动效已启动") : (hooksInfo.reason === "no-hooks" ? weT(",无动效") : weT(",动效未启动(") + hooksInfo.reason + ")"))
|
|
16592
|
-
+ (
|
|
16685
|
+
+ (typeof onClearWallpaper === "function" ? weT(",壁纸已关闭") : ""),
|
|
16593
16686
|
// hooks 已经跑过了;这里只作为"整页重载"的可选入口(极少数皮肤依赖启动期布局)
|
|
16594
16687
|
reload: true,
|
|
16595
16688
|
});
|
|
@@ -16814,6 +16907,11 @@ window.__ModuleLoader__.load({
|
|
|
16814
16907
|
return React.createElement(SkinsTabPanel, props || null);
|
|
16815
16908
|
}
|
|
16816
16909
|
|
|
16910
|
+
// 模块加载即启动互斥仲裁(不依赖用户是否打开过皮肤包页签):
|
|
16911
|
+
// 壁纸在台 ⇒ 撤掉皮肤;壁纸撤下 ⇒ 恢复选中的皮肤。
|
|
16912
|
+
// 放在模块作用域而不是 React effect 里,是因为它必须在**任何面板渲染之前**就守住这个不变量。
|
|
16913
|
+
try { startSkinArbiter(); } catch { /* 环境异常时退化为纯按钮路径 */ }
|
|
16914
|
+
|
|
16817
16915
|
// ── 内联模块:src/picker-model.js —— 选择器模型层:过滤判定 + 派生数据 + 分页切片(纯函数 / 显式入参 / 零 DOM)(构建期注入;勿手改本段)──
|
|
16818
16916
|
/**
|
|
16819
16917
|
* picker-model.js — 壁纸选择器的**模型层**:过滤判定 + 派生数据 + 分页切片。
|
|
@@ -22787,6 +22885,34 @@ window.__ModuleLoader__.load({
|
|
|
22787
22885
|
/** DELETE(多数接口不返回体;需要时传 parse: true)。 */
|
|
22788
22886
|
const apiDelete = (path, options) => apiFetch(path, Object.assign({ method: 'DELETE', parse: false }, options));
|
|
22789
22887
|
|
|
22888
|
+
/**
|
|
22889
|
+
* 根相对路径的**原生**请求(**不补 BASE**),返回原始 `Response`(失败给 null)。
|
|
22890
|
+
*
|
|
22891
|
+
* 用途:访问**同源其它插件**的路由 —— 典型是皮肤中心的 `/api/skin-center/v2/...`
|
|
22892
|
+
*(它把皮肤 CSS 做了强制作用域化,只有那条路由给的版本才能压过应用自身规则)。
|
|
22893
|
+
*
|
|
22894
|
+
* 为什么必须是**根相对路径**而不是 `location.origin + path`:桌面壳里文档可能跑在
|
|
22895
|
+
* 自定义协议下(`dsh-app://…`),此时 `location.origin` 是 `"null"`,拼出来的绝对 URL
|
|
22896
|
+
* 直接作废。`/api/...` 交给浏览器按文档解析,两种协议下都成立 —— 皮肤中心自己的客户端
|
|
22897
|
+
* 用的也正是这种写法。
|
|
22898
|
+
*
|
|
22899
|
+
* @param {string} path 以 `/` 开头的同源路径
|
|
22900
|
+
* @param {{ method?: string, cache?: string, headers?: object, fetch?: Function }} [options]
|
|
22901
|
+
* @returns {Promise<Response|null>}
|
|
22902
|
+
*/
|
|
22903
|
+
async function apiRawResponse(path, options) {
|
|
22904
|
+
const o = options || {};
|
|
22905
|
+
const doFetch = pickFetch(o.fetch);
|
|
22906
|
+
if (!doFetch) return null;
|
|
22907
|
+
const init = { method: o.method || 'GET', cache: o.cache || 'no-store' };
|
|
22908
|
+
if (o.headers) init.headers = o.headers;
|
|
22909
|
+
try {
|
|
22910
|
+
return await doFetch(String(path), init);
|
|
22911
|
+
} catch {
|
|
22912
|
+
return null;
|
|
22913
|
+
}
|
|
22914
|
+
}
|
|
22915
|
+
|
|
22790
22916
|
// ── 内联模块:src/occlusion.js —— 遮挡投递:/occlusion 轮询 + 缩放 + postMessage(1Hz,含退避与可见性暂停)(构建期注入;勿手改本段)──
|
|
22791
22917
|
/**
|
|
22792
22918
|
* occlusion.js — Linux 壳窗遮挡的**客户端轮询器**:宿主 `/occlusion` 快照 → 画布局部
|