dsh-any-background 0.3.10 → 0.4.2
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/README.md +11 -13
- package/README.zh.md +11 -13
- package/lib/client.js +872 -275
- package/lib/client.js.map +1 -1
- package/lib/index.js +327 -73
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -27,7 +27,6 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const zh = {
|
|
28
28
|
nav: "主题",
|
|
29
29
|
brandTag: "外观定制",
|
|
30
|
-
close: "关闭",
|
|
31
30
|
pageColor: "色彩",
|
|
32
31
|
pageInterface: "界面",
|
|
33
32
|
pageFont: "字体",
|
|
@@ -63,6 +62,13 @@ window.__ModuleLoader__.load({
|
|
|
63
62
|
captionInkTheme: "跟随主题",
|
|
64
63
|
captionInkWhite: "始终白色",
|
|
65
64
|
captionInkBlack: "始终黑色",
|
|
65
|
+
tpTitle: "第三方插件兼容",
|
|
66
|
+
tpEnabled: "保护第三方表面",
|
|
67
|
+
tpHint: "本插件把宿主的一批底色令牌改成了半透明,好让壁纸透出来。其它插件如果用同样的令牌画表面,就会跟着一起变透明、和滚过的文字叠在一起。开启后认出这些插件,只把它们的那几块钉在可读的密度上,不随滑块调节。",
|
|
68
|
+
tpNone: "未检测到读取这些令牌的其它插件。",
|
|
69
|
+
tpPinned: "已钉住",
|
|
70
|
+
tpUnpinned: "未登记 —— 若它的表面变透明或看不清,请把开机日志这一行发给我",
|
|
71
|
+
tpReaderLanes: "吸顶总结栏",
|
|
66
72
|
fontPageTitle: "字体与描边",
|
|
67
73
|
fontTitle: "自定义字体",
|
|
68
74
|
fontHint: "支持 ttf/otf/woff/woff2 字体文件(上限 100MB),将应用到整个界面的文字;代码块的等宽字体保持不变。中文字体通常有几十 MB,首次上传与刷新加载需要片刻",
|
|
@@ -78,7 +84,6 @@ window.__ModuleLoader__.load({
|
|
|
78
84
|
strokeTitle: "文字描边",
|
|
79
85
|
strokeHint: "按区域为文字添加描边:粗细为 0 表示关闭,「自动」会生成与文字颜色相反的颜色,「主题色」跟随当前主题色。代码块与图标已自动豁免,多色语法不会糊成一团",
|
|
80
86
|
strokeWidth: "描边粗细",
|
|
81
|
-
strokeColor: "描边颜色",
|
|
82
87
|
strokeAuto: "自动反色",
|
|
83
88
|
strokeGray: "灰色",
|
|
84
89
|
strokeBlack: "黑色",
|
|
@@ -118,7 +123,6 @@ window.__ModuleLoader__.load({
|
|
|
118
123
|
editorReset: "重置",
|
|
119
124
|
extractColor: "从背景提取主题色",
|
|
120
125
|
extracting: "取色中…",
|
|
121
|
-
extractNoWp: "请先设置背景图片",
|
|
122
126
|
extractDone: "已应用背景主色调",
|
|
123
127
|
extractFail: "未在背景中找到鲜明的颜色,请换一张背景",
|
|
124
128
|
crashTitle: "界面渲染出错",
|
|
@@ -138,7 +142,6 @@ window.__ModuleLoader__.load({
|
|
|
138
142
|
pickerTitle: "从背景取色",
|
|
139
143
|
pickerHint: "移动鼠标预览颜色,点击背景选取为主题色",
|
|
140
144
|
pickerClose: "关闭",
|
|
141
|
-
bgTypeImage: "图片",
|
|
142
145
|
bgTypeMesh: "网格渐变",
|
|
143
146
|
bgTypeShader: "Shader",
|
|
144
147
|
bgTypePattern: "几何图案",
|
|
@@ -194,8 +197,12 @@ window.__ModuleLoader__.load({
|
|
|
194
197
|
profileNamePlaceholder: "输入配置名称…",
|
|
195
198
|
profileNameRequired: "请先输入配置名称",
|
|
196
199
|
profileSaved: "配置已保存",
|
|
200
|
+
profileRenamed: "配置已重命名",
|
|
201
|
+
profileDuplicated: "配置已复制",
|
|
197
202
|
profileApplied: "配置已应用",
|
|
198
203
|
profileApply: "应用",
|
|
204
|
+
profileRename: "重命名",
|
|
205
|
+
profileDuplicate: "复制配置",
|
|
199
206
|
profileDelete: "删除配置",
|
|
200
207
|
profileDeleteConfirm: "确认删除",
|
|
201
208
|
profileNone: "不切换",
|
|
@@ -218,6 +225,7 @@ window.__ModuleLoader__.load({
|
|
|
218
225
|
rotShuffle: "随机",
|
|
219
226
|
rotOrder: "顺序",
|
|
220
227
|
rotReload: "每次刷新",
|
|
228
|
+
rotHourly: "每小时",
|
|
221
229
|
rotDaily: "每天",
|
|
222
230
|
rotWeekly: "每周",
|
|
223
231
|
rotNow: "立即切换"
|
|
@@ -225,7 +233,6 @@ window.__ModuleLoader__.load({
|
|
|
225
233
|
const en = {
|
|
226
234
|
nav: "Theme",
|
|
227
235
|
brandTag: "Appearance",
|
|
228
|
-
close: "Close",
|
|
229
236
|
pageColor: "Color",
|
|
230
237
|
pageInterface: "Interface",
|
|
231
238
|
pageFont: "Font",
|
|
@@ -256,11 +263,18 @@ window.__ModuleLoader__.load({
|
|
|
256
263
|
uiHeader: "Header popovers",
|
|
257
264
|
uiHeaderBar: "Header bar",
|
|
258
265
|
captionInkLabel: "Caption strip text",
|
|
259
|
-
captionInkHint: "Who paints the text on the desktop caption strip — the native
|
|
266
|
+
captionInkHint: "Who paints the text on the desktop caption strip — the native \"App / Edit\" menu bar and the sidebar toggle. The strip is the sidebar material over the wallpaper, so it can end up on the opposite side of the theme.",
|
|
260
267
|
captionInkAuto: "Auto",
|
|
261
268
|
captionInkTheme: "Follow theme",
|
|
262
269
|
captionInkWhite: "Always white",
|
|
263
270
|
captionInkBlack: "Always black",
|
|
271
|
+
tpTitle: "Third-party compatibility",
|
|
272
|
+
tpEnabled: "Protect third-party surfaces",
|
|
273
|
+
tpHint: "This plugin turns a batch of the host’s background tokens translucent so the wallpaper shows through. Another plugin that paints its surfaces with those same tokens goes translucent with it and collides with the text scrolling underneath. When on, those plugins are detected and only their surfaces are pinned to a legible density — outside the sliders.",
|
|
274
|
+
tpNone: "No other plugin was seen filling from these tokens.",
|
|
275
|
+
tpPinned: "pinned",
|
|
276
|
+
tpUnpinned: "not registered — if its surface fades or becomes unreadable, send me that boot-log line",
|
|
277
|
+
tpReaderLanes: "its sticky reading lanes",
|
|
264
278
|
fontPageTitle: "Font & outline",
|
|
265
279
|
fontTitle: "Custom font",
|
|
266
280
|
fontHint: "Accepts ttf/otf/woff/woff2 files (up to 100MB) and applies them to the whole interface; code blocks keep their monospace stack. CJK fonts routinely span tens of MB, so the first upload and each reload take a moment",
|
|
@@ -276,7 +290,6 @@ window.__ModuleLoader__.load({
|
|
|
276
290
|
strokeTitle: "Text outline",
|
|
277
291
|
strokeHint: "Outline text per region: width 0 turns it off, \"auto\" picks the inverse of the text color, and \"theme\" follows the current accent. Code blocks and icons are exempted automatically, so multi-color syntax never smears",
|
|
278
292
|
strokeWidth: "Outline width",
|
|
279
|
-
strokeColor: "Outline color",
|
|
280
293
|
strokeAuto: "Auto contrast",
|
|
281
294
|
strokeGray: "Gray",
|
|
282
295
|
strokeBlack: "Black",
|
|
@@ -316,7 +329,6 @@ window.__ModuleLoader__.load({
|
|
|
316
329
|
editorReset: "Reset",
|
|
317
330
|
extractColor: "Extract from background",
|
|
318
331
|
extracting: "Extracting…",
|
|
319
|
-
extractNoWp: "Set a background first",
|
|
320
332
|
extractDone: "Background color applied",
|
|
321
333
|
extractFail: "No vivid color found in this background, try another",
|
|
322
334
|
crashTitle: "Section crashed",
|
|
@@ -336,7 +348,6 @@ window.__ModuleLoader__.load({
|
|
|
336
348
|
pickerTitle: "Pick from background",
|
|
337
349
|
pickerHint: "Hover to preview, click to pick as theme color",
|
|
338
350
|
pickerClose: "Close",
|
|
339
|
-
bgTypeImage: "Image",
|
|
340
351
|
bgTypeMesh: "Mesh gradient",
|
|
341
352
|
bgTypeShader: "Shader",
|
|
342
353
|
bgTypePattern: "Pattern",
|
|
@@ -392,8 +403,12 @@ window.__ModuleLoader__.load({
|
|
|
392
403
|
profileNamePlaceholder: "Profile name…",
|
|
393
404
|
profileNameRequired: "Enter a name first",
|
|
394
405
|
profileSaved: "Profile saved",
|
|
406
|
+
profileRenamed: "Profile renamed",
|
|
407
|
+
profileDuplicated: "Profile duplicated",
|
|
395
408
|
profileApplied: "Profile applied",
|
|
396
409
|
profileApply: "Apply",
|
|
410
|
+
profileRename: "Rename",
|
|
411
|
+
profileDuplicate: "Duplicate",
|
|
397
412
|
profileDelete: "Delete profile",
|
|
398
413
|
profileDeleteConfirm: "Sure?",
|
|
399
414
|
profileNone: "None",
|
|
@@ -416,6 +431,7 @@ window.__ModuleLoader__.load({
|
|
|
416
431
|
rotShuffle: "Shuffle",
|
|
417
432
|
rotOrder: "In order",
|
|
418
433
|
rotReload: "Every refresh",
|
|
434
|
+
rotHourly: "Hourly",
|
|
419
435
|
rotDaily: "Daily",
|
|
420
436
|
rotWeekly: "Weekly",
|
|
421
437
|
rotNow: "Switch now"
|
|
@@ -531,6 +547,7 @@ window.__ModuleLoader__.load({
|
|
|
531
547
|
headerOpacity: .5,
|
|
532
548
|
headerBarOpacity: .5,
|
|
533
549
|
captionInk: "auto",
|
|
550
|
+
protectThirdParty: true,
|
|
534
551
|
profiles: [],
|
|
535
552
|
rotation: {
|
|
536
553
|
enabled: false,
|
|
@@ -760,6 +777,10 @@ window.__ModuleLoader__.load({
|
|
|
760
777
|
function rCaptionInk() {
|
|
761
778
|
return adoptCaptionInk(cfg.captionInk);
|
|
762
779
|
}
|
|
780
|
+
/** Whether the third-party compatibility pins are active — see `third-party.ts`. */
|
|
781
|
+
function rProtectThirdParty() {
|
|
782
|
+
return typeof cfg.protectThirdParty === "boolean" ? cfg.protectThirdParty : DEFAULT_CONFIG.protectThirdParty;
|
|
783
|
+
}
|
|
763
784
|
function rBgState() {
|
|
764
785
|
return cfg.bgState;
|
|
765
786
|
}
|
|
@@ -902,7 +923,7 @@ window.__ModuleLoader__.load({
|
|
|
902
923
|
return {
|
|
903
924
|
enabled: r.enabled === true,
|
|
904
925
|
mode: r.mode === "order" ? "order" : "shuffle",
|
|
905
|
-
interval: r.interval === "reload" || r.interval === "weekly" ? r.interval : "daily",
|
|
926
|
+
interval: r.interval === "reload" || r.interval === "hourly" || r.interval === "weekly" ? r.interval : "daily",
|
|
906
927
|
current: num(r.current, 0),
|
|
907
928
|
items,
|
|
908
929
|
lastRotate: typeof r.lastRotate === "string" ? r.lastRotate : null
|
|
@@ -997,6 +1018,7 @@ window.__ModuleLoader__.load({
|
|
|
997
1018
|
headerOpacity: clamp01(c.headerOpacity, DEFAULT_CONFIG.headerOpacity),
|
|
998
1019
|
headerBarOpacity: clamp01(c.headerBarOpacity, DEFAULT_CONFIG.headerBarOpacity),
|
|
999
1020
|
captionInk: adoptCaptionInk(c.captionInk),
|
|
1021
|
+
protectThirdParty: typeof c.protectThirdParty === "boolean" ? c.protectThirdParty : DEFAULT_CONFIG.protectThirdParty,
|
|
1000
1022
|
profiles: adoptProfiles(c.profiles),
|
|
1001
1023
|
rotation: adoptRotation(c.rotation),
|
|
1002
1024
|
schedule: adoptSchedule(c.schedule),
|
|
@@ -1798,7 +1820,7 @@ window.__ModuleLoader__.load({
|
|
|
1798
1820
|
/** False until the server verdict lands. Feature gates must not read "not asked
|
|
1799
1821
|
* yet" as "an old host" — see `adapterResolved()`. */
|
|
1800
1822
|
let resolved = false;
|
|
1801
|
-
const subs$
|
|
1823
|
+
const subs$2 = /* @__PURE__ */ new Set();
|
|
1802
1824
|
/** Cached adapter, keyed by the verdict it was built from. Held because React's
|
|
1803
1825
|
* `useSyncExternalStore` requires a stable snapshot between notifications. */
|
|
1804
1826
|
let adapterCache = null;
|
|
@@ -1821,14 +1843,14 @@ window.__ModuleLoader__.load({
|
|
|
1821
1843
|
resolved = true;
|
|
1822
1844
|
if (changed) {
|
|
1823
1845
|
adapterCache = null;
|
|
1824
|
-
subs$
|
|
1846
|
+
subs$2.forEach((cb) => cb());
|
|
1825
1847
|
}
|
|
1826
1848
|
}
|
|
1827
1849
|
/** Subscribe to the verdict landing (or changing). */
|
|
1828
1850
|
function subscribeHostInfo(cb) {
|
|
1829
|
-
subs$
|
|
1851
|
+
subs$2.add(cb);
|
|
1830
1852
|
return () => {
|
|
1831
|
-
subs$
|
|
1853
|
+
subs$2.delete(cb);
|
|
1832
1854
|
};
|
|
1833
1855
|
}
|
|
1834
1856
|
/** Whether the server verdict has landed at all. */
|
|
@@ -1879,22 +1901,44 @@ window.__ModuleLoader__.load({
|
|
|
1879
1901
|
}
|
|
1880
1902
|
const SAVE_DEBOUNCE_MS = 250;
|
|
1881
1903
|
let saveTimer;
|
|
1904
|
+
/** Write the config and SURFACE a failed persist. The server answers
|
|
1905
|
+
* `ok:true` with `value:false` when the DISK write failed — the transport
|
|
1906
|
+
* succeeded, so `rpcCall` returns the boolean without a word and every caller
|
|
1907
|
+
* used to assume the save had landed. Retry once after a beat (the common
|
|
1908
|
+
* Windows cause is a transient EBUSY from an antivirus scan holding the
|
|
1909
|
+
* file), then say it in the log so a "settings reverted" report has a trail. */
|
|
1910
|
+
let writeRetryQueued = false;
|
|
1911
|
+
async function writeNow() {
|
|
1912
|
+
if (await rpcCall("writeConfig", { config: cfg }) === true) {
|
|
1913
|
+
writeRetryQueued = false;
|
|
1914
|
+
return;
|
|
1915
|
+
}
|
|
1916
|
+
if (!writeRetryQueued) {
|
|
1917
|
+
writeRetryQueued = true;
|
|
1918
|
+
window.setTimeout(() => {
|
|
1919
|
+
writeNow();
|
|
1920
|
+
}, 1e3);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
writeRetryQueued = false;
|
|
1924
|
+
console.warn("dsh-any-background: the host could not persist the config — recent settings may be lost on reload (see the host log for the write error)");
|
|
1925
|
+
}
|
|
1882
1926
|
function saveConfig() {
|
|
1883
1927
|
if (saveTimer !== void 0) window.clearTimeout(saveTimer);
|
|
1884
1928
|
saveTimer = window.setTimeout(() => {
|
|
1885
1929
|
saveTimer = void 0;
|
|
1886
|
-
|
|
1930
|
+
writeNow();
|
|
1887
1931
|
}, SAVE_DEBOUNCE_MS);
|
|
1888
1932
|
}
|
|
1889
1933
|
function flushSave() {
|
|
1890
1934
|
if (saveTimer === void 0) return;
|
|
1891
1935
|
window.clearTimeout(saveTimer);
|
|
1892
1936
|
saveTimer = void 0;
|
|
1893
|
-
|
|
1937
|
+
writeNow();
|
|
1894
1938
|
}
|
|
1895
1939
|
/** Persist the current config immediately (import path — no debounce). */
|
|
1896
1940
|
function persistConfig() {
|
|
1897
|
-
|
|
1941
|
+
writeNow();
|
|
1898
1942
|
}
|
|
1899
1943
|
/** Load the persisted theme (config + wallpaper URL + video URL) from the node
|
|
1900
1944
|
* half. Image and video both travel as serve URLs — never bytes — so this RPC
|
|
@@ -2092,6 +2136,10 @@ window.__ModuleLoader__.load({
|
|
|
2092
2136
|
async function removeFont() {
|
|
2093
2137
|
return await rpcCall("removeFont", {}) === true;
|
|
2094
2138
|
}
|
|
2139
|
+
/** Serve URL of one rotation-pool image (the export path fetches its bytes). */
|
|
2140
|
+
function rotationFileUrl(file) {
|
|
2141
|
+
return `/dsh-any-background/rotation/file?f=${encodeURIComponent(file)}`;
|
|
2142
|
+
}
|
|
2095
2143
|
/** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
|
|
2096
2144
|
async function rotationAdd(dataUrl, thumb) {
|
|
2097
2145
|
if (!rpcCallFn) return {
|
|
@@ -2165,6 +2213,32 @@ window.__ModuleLoader__.load({
|
|
|
2165
2213
|
};
|
|
2166
2214
|
}
|
|
2167
2215
|
}
|
|
2216
|
+
/** Replace the whole server-side rotation pool (theme-import path): the bytes
|
|
2217
|
+
* land under their exported file names, so the imported config's pool
|
|
2218
|
+
* metadata is live on this machine instead of pointing at files it does not
|
|
2219
|
+
* have. */
|
|
2220
|
+
async function rotationRestore(items) {
|
|
2221
|
+
if (!rpcCallFn) return {
|
|
2222
|
+
ok: false,
|
|
2223
|
+
error: "rpc not ready"
|
|
2224
|
+
};
|
|
2225
|
+
try {
|
|
2226
|
+
const res = await rpcCallFn(rpcEndpoint("rotationRestore"), { items });
|
|
2227
|
+
if (res && res.ok === true) return res.value ?? {
|
|
2228
|
+
ok: false,
|
|
2229
|
+
error: "no value"
|
|
2230
|
+
};
|
|
2231
|
+
return {
|
|
2232
|
+
ok: false,
|
|
2233
|
+
error: res?.error?.message ?? "request failed"
|
|
2234
|
+
};
|
|
2235
|
+
} catch (e) {
|
|
2236
|
+
return {
|
|
2237
|
+
ok: false,
|
|
2238
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2168
2242
|
//#endregion
|
|
2169
2243
|
//#region src/client/host-compat/capabilities.ts
|
|
2170
2244
|
/**
|
|
@@ -2188,6 +2262,40 @@ window.__ModuleLoader__.load({
|
|
|
2188
2262
|
return rHostAdapter();
|
|
2189
2263
|
}
|
|
2190
2264
|
//#endregion
|
|
2265
|
+
//#region src/client/dom-watch.ts
|
|
2266
|
+
const listeners = {
|
|
2267
|
+
body: /* @__PURE__ */ new Set(),
|
|
2268
|
+
head: /* @__PURE__ */ new Set()
|
|
2269
|
+
};
|
|
2270
|
+
let observer = null;
|
|
2271
|
+
/** Subscribe to mutations of one tree. Returns an unsubscribe. */
|
|
2272
|
+
function watchDom(cb, target = "body") {
|
|
2273
|
+
listeners[target].add(cb);
|
|
2274
|
+
if (observer === null && typeof MutationObserver !== "undefined" && typeof document !== "undefined") {
|
|
2275
|
+
observer = new MutationObserver((records) => {
|
|
2276
|
+
for (const l of listeners.body) l(records);
|
|
2277
|
+
if (listeners.head.size > 0) {
|
|
2278
|
+
const head = records.filter((r) => r.target === document.head);
|
|
2279
|
+
if (head.length > 0) for (const l of listeners.head) l(head);
|
|
2280
|
+
}
|
|
2281
|
+
});
|
|
2282
|
+
observer.observe(document.body, {
|
|
2283
|
+
childList: true,
|
|
2284
|
+
subtree: true,
|
|
2285
|
+
attributes: true,
|
|
2286
|
+
attributeFilter: ["aria-expanded"]
|
|
2287
|
+
});
|
|
2288
|
+
observer.observe(document.head, { childList: true });
|
|
2289
|
+
}
|
|
2290
|
+
return () => {
|
|
2291
|
+
listeners[target].delete(cb);
|
|
2292
|
+
if (listeners.body.size + listeners.head.size === 0) {
|
|
2293
|
+
observer?.disconnect();
|
|
2294
|
+
observer = null;
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
//#endregion
|
|
2191
2299
|
//#region src/client/header-tag.ts
|
|
2192
2300
|
/**
|
|
2193
2301
|
* Header-surface tagging.
|
|
@@ -2292,7 +2400,6 @@ window.__ModuleLoader__.load({
|
|
|
2292
2400
|
if (el.getAttribute("aria-modal") === "true") return false;
|
|
2293
2401
|
return el.parentElement === document.body || el.closest(headerSlotAnchors()) !== null;
|
|
2294
2402
|
}
|
|
2295
|
-
let observer = null;
|
|
2296
2403
|
let pending = null;
|
|
2297
2404
|
/** False once the effect is torn down, so a pass already queued on the microtask queue
|
|
2298
2405
|
* cannot re-mark a popover after the teardown cleared it. */
|
|
@@ -2333,17 +2440,10 @@ window.__ModuleLoader__.load({
|
|
|
2333
2440
|
if (typeof MutationObserver === "undefined" || typeof document === "undefined") return () => {};
|
|
2334
2441
|
active = true;
|
|
2335
2442
|
refreshHeaderPopovers();
|
|
2336
|
-
|
|
2337
|
-
observer.observe(document.body, {
|
|
2338
|
-
childList: true,
|
|
2339
|
-
subtree: true,
|
|
2340
|
-
attributes: true,
|
|
2341
|
-
attributeFilter: ["aria-expanded"]
|
|
2342
|
-
});
|
|
2443
|
+
const dispose = watchDom(() => schedule());
|
|
2343
2444
|
return () => {
|
|
2344
2445
|
active = false;
|
|
2345
|
-
|
|
2346
|
-
observer = null;
|
|
2446
|
+
dispose();
|
|
2347
2447
|
for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) el.removeAttribute(HEADER_POPOVER_ATTR);
|
|
2348
2448
|
};
|
|
2349
2449
|
}
|
|
@@ -3669,6 +3769,12 @@ window.__ModuleLoader__.load({
|
|
|
3669
3769
|
"--dsw-specific-input-major": "--dsh-any-op-input",
|
|
3670
3770
|
"--dsw-specific-menu": "--dsh-any-op-menu"
|
|
3671
3771
|
};
|
|
3772
|
+
/** Every host token this plugin re-emits on `body` at a slider's alpha. Exported
|
|
3773
|
+
* because it is also the answer to "what can another plugin collide with": anything
|
|
3774
|
+
* that fills from one of these names fades with our sliders whether or not its author
|
|
3775
|
+
* knew we existed — the whole of §2-V. `third-party.ts` scans installed plugin
|
|
3776
|
+
* stylesheets for exactly this list. */
|
|
3777
|
+
const OWNED_TOKENS = Object.keys(OPACITY_VARS);
|
|
3672
3778
|
let baseTokenKey = "";
|
|
3673
3779
|
let pendingOps = null;
|
|
3674
3780
|
let tokensRaf = null;
|
|
@@ -3805,6 +3911,7 @@ window.__ModuleLoader__.load({
|
|
|
3805
3911
|
applyPartOpacities(ops);
|
|
3806
3912
|
}
|
|
3807
3913
|
applyExemptDefaults();
|
|
3914
|
+
applyReaderLanes(surfaces);
|
|
3808
3915
|
} catch {}
|
|
3809
3916
|
}
|
|
3810
3917
|
const SETTINGS_PANEL_SEL = "[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby][class*=\"_panel\"]";
|
|
@@ -4356,6 +4463,89 @@ window.__ModuleLoader__.load({
|
|
|
4356
4463
|
document.head.appendChild(exemptStyleEl);
|
|
4357
4464
|
return exemptStyleEl;
|
|
4358
4465
|
}
|
|
4466
|
+
const READER_ROOT = "[data-dsh-better-display]:not([data-reader-glass])";
|
|
4467
|
+
/** Plate density the lanes are pinned at: 「主界面」 at 20% — a band that reads as a
|
|
4468
|
+
* band while still letting the wallpaper through. */
|
|
4469
|
+
const READER_LANE_ALPHA = .2;
|
|
4470
|
+
/** Frost the lanes are pinned at (px) — the middle of the 0–60 blur range, i.e. the
|
|
4471
|
+
* plugin's own default, so the band looks like every other frosted surface here. */
|
|
4472
|
+
const READER_LANE_BLUR_PX = 30;
|
|
4473
|
+
/** The sticky wrapper holding the status lane and the closed-turn digest. It paints
|
|
4474
|
+
* NOTHING today (`Reader.module.css:27`), and `:556` forces both children
|
|
4475
|
+
* `position:static; z-index:auto` — so it is the one element whose box can simply BE
|
|
4476
|
+
* the band: one plate, one frost region, one pair of rounded corners. */
|
|
4477
|
+
const READER_WRAP = `${READER_ROOT} [class*="_turnStickyLanes"]`;
|
|
4478
|
+
/** The band's two height levers, both published by that plugin itself
|
|
4479
|
+
* (`StickyLane.tsx:11-13` writes the measured toolbar and status-lane heights onto
|
|
4480
|
+
* ancestors the wrapper inherits from), so the merged box tracks real text wrapping
|
|
4481
|
+
* instead of a hardcoded 40px. */
|
|
4482
|
+
const READER_TOOLBAR_H = "var(--reader-toolbar-height,40px)";
|
|
4483
|
+
const READER_BAND_TOP = `max(${READER_TOOLBAR_H},var(--reader-status-height,40px))`;
|
|
4484
|
+
/** The two surfaces that paint a band, spelled once so the static and dynamic halves
|
|
4485
|
+
* cannot drift: the merged wrapper, and the live-fold summary cell — which cannot be
|
|
4486
|
+
* merged into it, because it sticks at `z-index:5` BELOW the wrapper's 6, so growing
|
|
4487
|
+
* the wrapper over that row would paint the wrapper's plate straight across the row's
|
|
4488
|
+
* own text (tried, measured, reverted). */
|
|
4489
|
+
const READER_LANE_SEL = `${READER_WRAP},${READER_ROOT} [data-flow-summary]`;
|
|
4490
|
+
/** Everything that used to paint its own plate and must stop now that the wrapper
|
|
4491
|
+
* does. Their plates are what made the stack read as separate bars: the status lane is
|
|
4492
|
+
* `width:calc(100% - var(--reader-control-width))` (`:546`) so it notched short of the
|
|
4493
|
+
* toolbar's width, and each `box-shadow` gap plate (`:555/:564/:565`) lands on a
|
|
4494
|
+
* neighbour and doubles the alpha there — a 14–16px darker strip per seam.
|
|
4495
|
+
*
|
|
4496
|
+
* The toolbar lane hands its plate over only while a band exists to swallow it: a turn
|
|
4497
|
+
* with no process steps renders no wrapper at all, and there the strip would be left
|
|
4498
|
+
* with nothing behind its own button. It can be swallowed at all because its
|
|
4499
|
+
* `z-index:7` (`:538`) sits ABOVE the wrapper's 6 — its own text keeps painting over
|
|
4500
|
+
* the merged plate, which is exactly the arrangement the live-fold cell cannot use. */
|
|
4501
|
+
const READER_SILENT = `${READER_WRAP}>*,${READER_ROOT}:has([class*="_turnStickyLanes"]) [data-reader-lane="toolbar"]`;
|
|
4502
|
+
/** Static half — the merge, the frost, the shape. Every value is a literal or one of
|
|
4503
|
+
* that plugin's own published variables; nothing here reads a slider, which is what
|
|
4504
|
+
* makes the band immune to 调控.
|
|
4505
|
+
*
|
|
4506
|
+
* It is TEXT in the dynamic sheet rather than a rule in `buildStaticStyles()`, and
|
|
4507
|
+
* that is the point: the static sheet is this plugin's contract with the HOST, and a
|
|
4508
|
+
* third-party accommodation must be able to vanish completely — paint, frost and the
|
|
4509
|
+
* merged box's geometry alike — when 「保护第三方表面」 is switched off. One sheet, one
|
|
4510
|
+
* owner, one thing to remove.
|
|
4511
|
+
*
|
|
4512
|
+
* HOW THE BOX GROWS WITHOUT MOVING CONTENT: `padding-top` reaches up over the toolbar
|
|
4513
|
+
* strip and an equal negative `margin-top` cancels it, so the wrapper's margin box
|
|
4514
|
+
* keeps its height and every following sibling stays exactly where it was; `top` is
|
|
4515
|
+
* pulled up by the same amount so the STUCK position puts the wrapper's content back at
|
|
4516
|
+
* `max(toolbar, status)`. The lanes' text does not move a pixel — only the paint does.
|
|
4517
|
+
* Measured in the §4.2 mock with the pin on and off: the column's height and the first
|
|
4518
|
+
* paragraph's offset are identical, and the status row's `top` is unchanged.
|
|
4519
|
+
*
|
|
4520
|
+
* The rounded bottom goes on whichever band is actually LAST: the live-fold cell sticks
|
|
4521
|
+
* 40px below the wrapper (`:558`), so while the turn has one the wrapper is not the
|
|
4522
|
+
* bottom edge and rounding it would open a notch against the cell under it. A build
|
|
4523
|
+
* without `:has()` keeps both bands square; nothing else changes. */
|
|
4524
|
+
const READER_LANE_RULE = `${READER_LANE_SEL}{background:var(--dsw-alias-bg-base);box-shadow:none;${elementFrostDecl(`blur(${READER_LANE_BLUR_PX}px)`)}}${READER_WRAP}{border-radius:0 0 14px 14px;padding-top:${READER_TOOLBAR_H};margin-top:calc(0px - ${READER_TOOLBAR_H});top:calc(${READER_BAND_TOP} - ${READER_TOOLBAR_H})}${READER_ROOT} [data-flow-summary]{border-radius:0 0 14px 14px}${READER_ROOT} [data-reader-turn]:has([data-flow-summary]) [class*="_turnStickyLanes"]{border-radius:0}${READER_SILENT}{background:transparent;box-shadow:none}`;
|
|
4525
|
+
let readerStyleEl = null;
|
|
4526
|
+
/** Emit the whole pin — geometry, frost and the plate literal — into one sheet, or
|
|
4527
|
+
* remove that sheet entirely. Two things switch it off: 「保护第三方表面」 being turned
|
|
4528
|
+
* off, and no color resolving yet (the palette and the host's own tokens are both
|
|
4529
|
+
* empty pre-boot), because a `var()` naming an unwritten property is the §2-E
|
|
4530
|
+
* guaranteed-invalid trap and its symptom here is the very bug being pinned. */
|
|
4531
|
+
function applyReaderLanes(surfaces) {
|
|
4532
|
+
if (typeof document === "undefined") return;
|
|
4533
|
+
const base = rProtectThirdParty() ? (surfaces ?? paletteTokens() ?? readHostOpacityTokens())?.["--dsw-alias-bg-base"] : void 0;
|
|
4534
|
+
if (base === void 0 || base === "") {
|
|
4535
|
+
if (readerStyleEl !== null) {
|
|
4536
|
+
readerStyleEl.remove();
|
|
4537
|
+
readerStyleEl = null;
|
|
4538
|
+
}
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
if (readerStyleEl === null) {
|
|
4542
|
+
readerStyleEl = document.createElement("style");
|
|
4543
|
+
readerStyleEl.dataset.plugin = "dsh-any-background-reader";
|
|
4544
|
+
document.head.appendChild(readerStyleEl);
|
|
4545
|
+
}
|
|
4546
|
+
const css = `${READER_LANE_RULE}${READER_LANE_SEL}{--dsw-alias-bg-base:${toRgba(base, READER_LANE_ALPHA)}}`;
|
|
4547
|
+
if (readerStyleEl.textContent !== css) readerStyleEl.textContent = css;
|
|
4548
|
+
}
|
|
4359
4549
|
const PRODUCED_RULE = `[data-code-block-content] pre,[data-code-block-banner],[data-composer-chip],[data-changed-files],[data-terminal],[data-read],[data-diff],[data-context-injection-body],[data-search],[data-web],[class*="_ioCard"],[data-inspect],[data-turn-trigger],[style*="--dsh-hover-preview-fade"],:not(pre)>code{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}[data-markdown-variant="compact"] :not(pre)>code{-webkit-backdrop-filter:none;backdrop-filter:none}${BETTER_SIDEBAR_PANEL}{-webkit-backdrop-filter:var(--dsh-any-blur-prod,none);backdrop-filter:var(--dsh-any-blur-prod,none)}${BETTER_SIDEBAR_PANEL}{--dsw-alias-bg-base:var(--dsh-any-prod-panel-bg-base);--dsw-alias-bg-layer-1:var(--dsh-any-prod-panel-layer-1);--dsw-alias-bg-layer-2:var(--dsh-any-prod-panel-layer-2);--dsw-alias-bg-layer-3:var(--dsh-any-prod-panel-layer-3)}@supports (background:color-mix(in srgb,red 50%,transparent)){.md-code-block{background:transparent!important}.md-code-block>div{background-color:transparent!important}.md-code-block [data-code-block-content] pre{background-color:color-mix(in srgb,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}.md-code-block [data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsw-alias-markdown-code-block-banner,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-banner]{background-color:color-mix(in srgb,var(--dsl-code-block-banner-background-color,var(--dsl-code-block-background,var(--dsw-alias-markdown-code-block,transparent))) var(--dsh-any-prod-pct,100%),transparent)!important}[data-code-block-content]{--shiki-background:color-mix(in srgb,var(--dsw-alias-markdown-code-block,var(--dsl-code-block-background,transparent)) var(--dsh-any-prod-pct,100%),transparent)}:not(pre)>code{background-color:color-mix(in srgb,var(--dsw-alias-markdown-inline-code,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-composer-chip]>*{background-color:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-terminal] button{background-color:transparent!important}[data-read],[data-diff]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-read]>div:first-child:not([data-code-block-banner]),[data-diff]>div:first-child:not([data-code-block-banner]){background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block-banner,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-context-injection-body]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-inspect]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-turn-trigger]{background-color:color-mix(in srgb,var(--dsw-alias-turn-trigger-bg,var(--dsw-alias-markdown-code-block,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-turn-trigger]:hover{background-color:color-mix(in srgb,var(--dsw-alias-turn-trigger-bg-hover,var(--dsw-alias-interactive-bg-hover,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]{background-color:var(--dsh-any-prod-layer-1,var(--dsw-alias-bg-layer-1,transparent))!important}[data-changed-files]>button:first-child{background-color:color-mix(in srgb,var(--changes-fill,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]>button:first-child:hover{background-color:color-mix(in srgb,var(--changes-hover,var(--changes-fill,transparent)) var(--dsh-any-prod-pct,100%),transparent)!important}[data-changed-files]>button:first-child>span:first-child{background-color:color-mix(in srgb,var(--changes-fill,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[style*="--dsh-hover-preview-fade"]{background-color:color-mix(in srgb,var(--dsw-hovercard-bg,#2c2c2e) var(--dsh-any-prod-pct,100%),transparent)!important}[style*="--dsh-hover-preview-fade"][class*="_preview"]{background-color:var(--dsh-any-prod-layer-1,var(--dsw-alias-bg-layer-1,transparent))!important}[data-search]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[data-web]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}[class*="_ioCard"]{background-color:color-mix(in srgb,var(--dsw-alias-markdown-code-block,transparent) var(--dsh-any-prod-pct,100%),transparent)!important}}`;
|
|
4360
4550
|
const STROKE_RULE = [
|
|
4361
4551
|
`${SETTINGS_PANEL_SEL}{-webkit-text-stroke:var(--dsh-any-stroke-settings-w,0px) var(--dsh-any-stroke-settings-c,transparent);paint-order:stroke fill}`,
|
|
@@ -4718,6 +4908,45 @@ window.__ModuleLoader__.load({
|
|
|
4718
4908
|
let rightEl = null;
|
|
4719
4909
|
const PART_BLUR_CLASS = "dab-part-blur";
|
|
4720
4910
|
const PART_UNDERLAY_CLASS = "dab-part-underlay";
|
|
4911
|
+
/** Paint-order lift for the frosted SIDEBAR column (issue #32).
|
|
4912
|
+
*
|
|
4913
|
+
* The host's `Tooltip` bubble is an IN-PLACE `span[role="tooltip"]`: its sheet
|
|
4914
|
+
* (`dsh-web-frontend` `Tooltip.module.css`, read on 0.2.1-rc.3) reads
|
|
4915
|
+
* `position:fixed;z-index:100`, going to `z-index:1100` only under
|
|
4916
|
+
* `[data-portal]` — and the `portal` prop defaults to FALSE, which the
|
|
4917
|
+
* workspace browser's 添加工作区 trigger (`ui-workspace` `headerActions`) does
|
|
4918
|
+
* not pass. `position:fixed` keeps the bubble anchored to the viewport, but
|
|
4919
|
+
* its z-index only competes INSIDE the nearest stacking-context ancestor. The
|
|
4920
|
+
* frosted sidebar column IS that ancestor — `PART_BLUR_RULE`'s
|
|
4921
|
+
* `isolation:isolate` is what keeps the underlay's `z-index:-1` inside — so
|
|
4922
|
+
* the bubble, hanging below the icon row and past the column's right edge, is
|
|
4923
|
+
* painted as part of the sidebar's subtree, and the center column (a later
|
|
4924
|
+
* sibling, itself an isolated stacking context once 「主界面」模糊 is up)
|
|
4925
|
+
* paints its translucent fill + frost OVER the overhang: the tooltip's right
|
|
4926
|
+
* half comes out blurred and tinted. The trap is not the tooltip's alone —
|
|
4927
|
+
* every in-place float the sidebar hangs (the workspace pick flow anchors
|
|
4928
|
+
* there too) sits in the same local context.
|
|
4929
|
+
*
|
|
4930
|
+
* Lifting the FROSTED sidebar column to `z-index:1` moves its whole paint
|
|
4931
|
+
* slot after the center column's. The two are grid siblings on separate
|
|
4932
|
+
* tracks — they never overlap geometrically, so the lift only matters to
|
|
4933
|
+
* floating layers that overhang a column edge, which is exactly the set that
|
|
4934
|
+
* must win. It stays below everything that has to beat it: the host's overlay
|
|
4935
|
+
* layer (`AppFrame.module.css` `.overlayLayer{z-index:20}`, byte-identical on
|
|
4936
|
+
* 0.2.0-rc.1 and 0.2.1-rc.3), the settings modal (it escapes the center
|
|
4937
|
+
* column through the `isolation:auto` arm below and carries z-index 1000+),
|
|
4938
|
+
* and body-level portaled menus/tooltips (z-index 100 / 1100). Grid items
|
|
4939
|
+
* honor `z-index` without `position`, so this lands even on a column the
|
|
4940
|
+
* host leaves `static`.
|
|
4941
|
+
*
|
|
4942
|
+
* The `:has([role="dialog"])` arm must pair `z-index:auto` with the
|
|
4943
|
+
* isolation arm: with the settings view open the sidebar column itself holds
|
|
4944
|
+
* a `[role="dialog"]` (counted live when the isolation arm was written), and
|
|
4945
|
+
* a column pinned at `z-index:1` would REMAIN a stacking context through that
|
|
4946
|
+
* arm — the dialog would keep its z-index local and paint under the overlay
|
|
4947
|
+
* layer instead of covering the page. Back at `auto` it escapes the column
|
|
4948
|
+
* exactly as it did before this class existed. */
|
|
4949
|
+
const PART_LIFT_CLASS = "dab-part-blur-side";
|
|
4721
4950
|
/** The AppFrame's main-bg clear-out, as a class rather than an inline write.
|
|
4722
4951
|
*
|
|
4723
4952
|
* WHY NOT INLINE (measured on 0.1.7): the frame's own translucent
|
|
@@ -4770,7 +4999,7 @@ window.__ModuleLoader__.load({
|
|
|
4770
4999
|
* `0.2.1-rc.3` (`AppFrame.module.css` byte-identical rc.2→rc.3). A build
|
|
4771
5000
|
* that renames the track simply stops matching, which is the untouched host look. */
|
|
4772
5001
|
const SIDEBAR_ONE_LAYER_RULE = "[class*=\"_sidebarCol\"] *{--dsw-specific-sidebar-fill:transparent}";
|
|
4773
|
-
const PART_BLUR_RULE = `.${PART_BLUR_CLASS}{isolation:isolate}.${PART_BLUR_CLASS}:has([role="dialog"]){isolation:auto}.${PART_UNDERLAY_CLASS}{position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;backdrop-filter:var(--dsh-any-part-blur,none);-webkit-backdrop-filter:var(--dsh-any-part-blur,none)}`;
|
|
5002
|
+
const PART_BLUR_RULE = `.${PART_BLUR_CLASS}{isolation:isolate}.${PART_BLUR_CLASS}:has([role="dialog"]){isolation:auto}.${PART_LIFT_CLASS}{z-index:1}.${PART_LIFT_CLASS}:has([role="dialog"]){z-index:auto}.${PART_UNDERLAY_CLASS}{position:absolute;inset:0;z-index:-1;pointer-events:none;border-radius:inherit;backdrop-filter:var(--dsh-any-part-blur,none);-webkit-backdrop-filter:var(--dsh-any-part-blur,none)}`;
|
|
4774
5003
|
let partBlurStyleEl = null;
|
|
4775
5004
|
function ensurePartBlurStyle() {
|
|
4776
5005
|
if (partBlurStyleEl?.isConnected) return;
|
|
@@ -4806,9 +5035,9 @@ window.__ModuleLoader__.load({
|
|
|
4806
5035
|
centerEl = children[overlayIdx - 2] ?? null;
|
|
4807
5036
|
}
|
|
4808
5037
|
}
|
|
4809
|
-
function setBlur(el, px) {
|
|
5038
|
+
function setBlur(el, px, lift = false) {
|
|
4810
5039
|
if (el === null) return;
|
|
4811
|
-
if (px === 0 && !el.classList.contains(PART_BLUR_CLASS) && el.getAttribute("data-dab-pos-patched") !== "1") return;
|
|
5040
|
+
if (px === 0 && !el.classList.contains(PART_BLUR_CLASS) && !el.classList.contains(PART_LIFT_CLASS) && el.getAttribute("data-dab-pos-patched") !== "1") return;
|
|
4812
5041
|
const underlay = el.querySelector(`:scope > .${PART_UNDERLAY_CLASS}`);
|
|
4813
5042
|
if (px > 0) {
|
|
4814
5043
|
ensurePartBlurStyle();
|
|
@@ -4817,6 +5046,7 @@ window.__ModuleLoader__.load({
|
|
|
4817
5046
|
el.setAttribute("data-dab-pos-patched", "1");
|
|
4818
5047
|
}
|
|
4819
5048
|
el.classList.add(PART_BLUR_CLASS);
|
|
5049
|
+
el.classList.toggle(PART_LIFT_CLASS, lift);
|
|
4820
5050
|
if (underlay === null) {
|
|
4821
5051
|
const node = document.createElement("div");
|
|
4822
5052
|
node.className = PART_UNDERLAY_CLASS;
|
|
@@ -4825,6 +5055,7 @@ window.__ModuleLoader__.load({
|
|
|
4825
5055
|
el.style.setProperty("--dsh-any-part-blur", `blur(${px}px)`);
|
|
4826
5056
|
} else {
|
|
4827
5057
|
el.classList.remove(PART_BLUR_CLASS);
|
|
5058
|
+
el.classList.remove(PART_LIFT_CLASS);
|
|
4828
5059
|
el.style.removeProperty("--dsh-any-part-blur");
|
|
4829
5060
|
underlay?.remove();
|
|
4830
5061
|
if (el.getAttribute("data-dab-pos-patched") === "1") {
|
|
@@ -4871,7 +5102,7 @@ window.__ModuleLoader__.load({
|
|
|
4871
5102
|
function applyPartBlurs(blurs) {
|
|
4872
5103
|
discoverParts();
|
|
4873
5104
|
setBlur(frameEl, 0);
|
|
4874
|
-
setBlur(sidebarEl, blurs.sidebar);
|
|
5105
|
+
setBlur(sidebarEl, blurs.sidebar, true);
|
|
4875
5106
|
setBlur(centerEl, blurs.bg);
|
|
4876
5107
|
setBlur(rightEl, 0);
|
|
4877
5108
|
applyBgBlurGlobal(blurs.bg);
|
|
@@ -4948,7 +5179,7 @@ window.__ModuleLoader__.load({
|
|
|
4948
5179
|
applyBgBlurGlobal(v);
|
|
4949
5180
|
return;
|
|
4950
5181
|
}
|
|
4951
|
-
setBlur(sidebarEl, v);
|
|
5182
|
+
setBlur(sidebarEl, v, true);
|
|
4952
5183
|
applySidebarBandBlur(v);
|
|
4953
5184
|
}
|
|
4954
5185
|
const VIEW_CARDS = [{
|
|
@@ -5176,8 +5407,11 @@ window.__ModuleLoader__.load({
|
|
|
5176
5407
|
});
|
|
5177
5408
|
syncTableFix();
|
|
5178
5409
|
}
|
|
5179
|
-
let
|
|
5180
|
-
let
|
|
5410
|
+
let partsDispose = null;
|
|
5411
|
+
let partsPending = null;
|
|
5412
|
+
/** False once stopped, so a pass already queued on the microtask queue cannot
|
|
5413
|
+
* re-style anything after the teardown. */
|
|
5414
|
+
let partsActive = false;
|
|
5181
5415
|
/** Signature of the last blur+opacity pass we actually wrote to the DOM. */
|
|
5182
5416
|
let appliedPartsKey = "";
|
|
5183
5417
|
/** The surfaces the last pass styled, compared BY IDENTITY: the host can swap
|
|
@@ -5217,11 +5451,13 @@ window.__ModuleLoader__.load({
|
|
|
5217
5451
|
/** Watch for the AppFrame mounting so persisted blurs land even when the shell
|
|
5218
5452
|
* renders after this plugin's apply. */
|
|
5219
5453
|
function watchParts() {
|
|
5220
|
-
if (
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5454
|
+
if (partsDispose !== null || typeof MutationObserver === "undefined") return;
|
|
5455
|
+
partsActive = true;
|
|
5456
|
+
partsDispose = watchDom(() => {
|
|
5457
|
+
if (partsPending !== null || !partsActive) return;
|
|
5458
|
+
partsPending = Promise.resolve().then(() => {
|
|
5459
|
+
partsPending = null;
|
|
5460
|
+
if (!partsActive) return;
|
|
5225
5461
|
const blurs = rBlurs();
|
|
5226
5462
|
const ops = rOps();
|
|
5227
5463
|
const key = JSON.stringify([blurs, ops]);
|
|
@@ -5232,18 +5468,12 @@ window.__ModuleLoader__.load({
|
|
|
5232
5468
|
appliedTargets = targetsNow();
|
|
5233
5469
|
});
|
|
5234
5470
|
});
|
|
5235
|
-
partsObserver.observe(document.body, {
|
|
5236
|
-
childList: true,
|
|
5237
|
-
subtree: true
|
|
5238
|
-
});
|
|
5239
5471
|
}
|
|
5240
5472
|
function stopWatchingParts() {
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
partsApplyRaf = 0;
|
|
5246
|
-
}
|
|
5473
|
+
partsDispose?.();
|
|
5474
|
+
partsDispose = null;
|
|
5475
|
+
partsActive = false;
|
|
5476
|
+
partsPending = null;
|
|
5247
5477
|
appliedPartsKey = "";
|
|
5248
5478
|
appliedTargets = [];
|
|
5249
5479
|
markerSeen.clear();
|
|
@@ -5556,9 +5786,8 @@ window.__ModuleLoader__.load({
|
|
|
5556
5786
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
5557
5787
|
}
|
|
5558
5788
|
applyPanelOverrides(rPanelOpacity());
|
|
5559
|
-
applyProduced();
|
|
5560
|
-
applyHeaderPopovers();
|
|
5561
5789
|
applyExemptDefaults();
|
|
5790
|
+
applyReaderLanes();
|
|
5562
5791
|
applyPartBlurs(rBlurs());
|
|
5563
5792
|
applyStrokes();
|
|
5564
5793
|
}
|
|
@@ -5618,6 +5847,8 @@ window.__ModuleLoader__.load({
|
|
|
5618
5847
|
headerStyleEl = null;
|
|
5619
5848
|
exemptStyleEl?.remove();
|
|
5620
5849
|
exemptStyleEl = null;
|
|
5850
|
+
readerStyleEl?.remove();
|
|
5851
|
+
readerStyleEl = null;
|
|
5621
5852
|
for (const v of Object.values(OPACITY_VARS)) document.documentElement.style.removeProperty(v);
|
|
5622
5853
|
baseTokenKey = "";
|
|
5623
5854
|
lastBgKey = "";
|
|
@@ -7359,6 +7590,161 @@ window.__ModuleLoader__.load({
|
|
|
7359
7590
|
] });
|
|
7360
7591
|
}
|
|
7361
7592
|
//#endregion
|
|
7593
|
+
//#region src/client/third-party.ts
|
|
7594
|
+
/**
|
|
7595
|
+
* Third-party plugin compatibility — the registry, and the boot self-check that names
|
|
7596
|
+
* collisions out loud.
|
|
7597
|
+
*
|
|
7598
|
+
* WHY THIS FILE EXISTS
|
|
7599
|
+
* This plugin does not restyle surfaces; it re-emits HOST TOKENS on `<body>` at a
|
|
7600
|
+
* slider's alpha (`OWNED_TOKENS`), and every element that fills from one of those names
|
|
7601
|
+
* fades with it — the host's own, and any other plugin's too. A third-party author has
|
|
7602
|
+
* no reason to expect that: a sticky bar painted with `background:var(--dsw-alias-bg-base)`
|
|
7603
|
+
* is correct on an unthemed host and invisible on ours, because our 「主界面」 slider
|
|
7604
|
+
* defaults to 0. That is issue-class §2-V, and it was found the slow way — a user
|
|
7605
|
+
* reported unreadable text, and someone had to grep another plugin's stylesheet to see
|
|
7606
|
+
* which token it drank.
|
|
7607
|
+
*
|
|
7608
|
+
* WHAT THE SELF-CHECK DOES
|
|
7609
|
+
* Third-party plugins tag their own stylesheets (`style[data-plugin="<package>"]` —
|
|
7610
|
+
* dsh-better-display mounts exactly that, at module load, before any of its views
|
|
7611
|
+
* exists). So the page can answer "who is installed and what of ours do they read"
|
|
7612
|
+
* without anyone reporting it: scan those sheets, look for `var(--<owned token>)`, and
|
|
7613
|
+
* name each hit once in the console. A registered pin says so and stays quiet; an
|
|
7614
|
+
* UNREGISTERED collision logs a warning, which turns "the user finds broken pixels and
|
|
7615
|
+
* files an issue" into "the boot log already knows which plugin and which token".
|
|
7616
|
+
*
|
|
7617
|
+
* WHAT IT DELIBERATELY DOES NOT DO
|
|
7618
|
+
* It never auto-pins an unknown surface. Guessing which of another plugin's elements are
|
|
7619
|
+
* "supposed to stay legible" would repaint someone else's design on a heuristic — the
|
|
7620
|
+
* §2-G lesson about heuristics with side effects. Detection is automatic; protection is
|
|
7621
|
+
* a reviewed table entry, and the settings card exposes it so a user can switch the
|
|
7622
|
+
* whole class off.
|
|
7623
|
+
*/
|
|
7624
|
+
/** The table. Adding a plugin is one row here plus the CSS that honors it. */
|
|
7625
|
+
const THIRD_PARTY_PINS = [{
|
|
7626
|
+
plugin: "dsh-better-display",
|
|
7627
|
+
labelKey: "tpReaderLanes",
|
|
7628
|
+
probe: "[data-dsh-better-display]",
|
|
7629
|
+
tokens: ["--dsw-alias-bg-base"]
|
|
7630
|
+
}];
|
|
7631
|
+
/** This plugin's own sheets are tagged with its name (plus a suffix per table); they
|
|
7632
|
+
* consume the tokens by design, so they must never count as a collision. */
|
|
7633
|
+
const SELF = "dsh-any-background";
|
|
7634
|
+
/** The host's own client modules tag their stylesheets the same way — measured on
|
|
7635
|
+
* 0.2.1-rc.3: **157 of the 172 `style[data-plugin]` sheets on a real page belong to
|
|
7636
|
+
* `@deepseek-ai/…`**, and they read these tokens on purpose (fading them IS the
|
|
7637
|
+
* product). Treating them as collisions would concatenate ~540 KB of host CSS per
|
|
7638
|
+
* scan, warn about the host's own UI, and fill the settings card with a hundred rows.
|
|
7639
|
+
* So the host namespace is excluded by prefix. The cost of that rule is asymmetric on
|
|
7640
|
+
* purpose: a scoped third-party package gets missed (no log, no wrong pixel), while a
|
|
7641
|
+
* false positive would spam and mislead. */
|
|
7642
|
+
const HOST_NAMESPACE = "@deepseek-ai/";
|
|
7643
|
+
function isHost(name) {
|
|
7644
|
+
return name.startsWith(HOST_NAMESPACE);
|
|
7645
|
+
}
|
|
7646
|
+
function isSelf(name) {
|
|
7647
|
+
return name === SELF || name.startsWith("dsh-any-background-");
|
|
7648
|
+
}
|
|
7649
|
+
/** The package a sheet came from. Scoped names keep their scope (`@hytime/dsh-x` →
|
|
7650
|
+
* `@hytime/dsh-x`, not `@hytime`), and a `pkg/module.css`-style tag trims to its
|
|
7651
|
+
* package. Ordering matters: this runs AFTER the host/self tests, which must see the
|
|
7652
|
+
* raw attribute — truncating first turned `@deepseek-ai/dsh-client-ui-chat` into
|
|
7653
|
+
* `@deepseek-ai`, which no longer matches the `@deepseek-ai/` prefix and so let 157
|
|
7654
|
+
* host sheets through as one phantom "plugin" (found on the real host, not in the
|
|
7655
|
+
* mock: the mock only ever had one host-ish sheet to begin with). */
|
|
7656
|
+
function pluginKey(name) {
|
|
7657
|
+
if (name.startsWith("@")) {
|
|
7658
|
+
const i = name.indexOf("/", 1);
|
|
7659
|
+
return i > 0 ? name.slice(0, i) : name;
|
|
7660
|
+
}
|
|
7661
|
+
const i = name.indexOf("/");
|
|
7662
|
+
return i > 0 ? name.slice(0, i) : name;
|
|
7663
|
+
}
|
|
7664
|
+
let sightings = [];
|
|
7665
|
+
const subs$1 = /* @__PURE__ */ new Set();
|
|
7666
|
+
const logged = /* @__PURE__ */ new Set();
|
|
7667
|
+
/** Read every tagged third-party stylesheet in `<head>` and report the collisions.
|
|
7668
|
+
* Runs at boot and again whenever a sheet appears; per sheet, not concatenated — the
|
|
7669
|
+
* real page has 170+ sheets and the difference is measurable. */
|
|
7670
|
+
function scan() {
|
|
7671
|
+
if (typeof document === "undefined") return;
|
|
7672
|
+
const hits = /* @__PURE__ */ new Map();
|
|
7673
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7674
|
+
const note = (plugin, tokens) => {
|
|
7675
|
+
seen.add(plugin);
|
|
7676
|
+
if (tokens.length === 0) return;
|
|
7677
|
+
const set = hits.get(plugin) ?? /* @__PURE__ */ new Set();
|
|
7678
|
+
for (const t of tokens) set.add(t);
|
|
7679
|
+
hits.set(plugin, set);
|
|
7680
|
+
};
|
|
7681
|
+
for (const el of Array.from(document.querySelectorAll("style[data-plugin]"))) {
|
|
7682
|
+
const raw = (el.getAttribute("data-plugin") ?? "").trim();
|
|
7683
|
+
if (raw === "" || isSelf(raw) || isHost(raw)) continue;
|
|
7684
|
+
const plugin = pluginKey(raw);
|
|
7685
|
+
const text = el.textContent ?? "";
|
|
7686
|
+
note(plugin, OWNED_TOKENS.filter((t) => text.includes(`var(${t})`) || text.includes(`var(${t},`) || text.includes(`var(${t} `)));
|
|
7687
|
+
}
|
|
7688
|
+
for (const pin of THIRD_PARTY_PINS) if (pin.probe !== void 0 && !seen.has(pin.plugin) && document.querySelector(pin.probe) !== null) note(pin.plugin, []);
|
|
7689
|
+
const next = [];
|
|
7690
|
+
for (const plugin of seen) {
|
|
7691
|
+
const read = [...hits.get(plugin) ?? []];
|
|
7692
|
+
const pin = THIRD_PARTY_PINS.find((p) => p.plugin === plugin);
|
|
7693
|
+
if (pin === void 0 && read.length === 0) continue;
|
|
7694
|
+
const s = {
|
|
7695
|
+
plugin,
|
|
7696
|
+
tokens: read.length > 0 ? read : [...pin?.tokens ?? []],
|
|
7697
|
+
pinned: pin !== void 0,
|
|
7698
|
+
labelKey: pin?.labelKey
|
|
7699
|
+
};
|
|
7700
|
+
next.push(s);
|
|
7701
|
+
report(plugin, s);
|
|
7702
|
+
}
|
|
7703
|
+
const changed = JSON.stringify(next) !== JSON.stringify(sightings);
|
|
7704
|
+
sightings = next;
|
|
7705
|
+
if (changed) subs$1.forEach((cb) => cb());
|
|
7706
|
+
}
|
|
7707
|
+
function report(plugin, s) {
|
|
7708
|
+
if (logged.has(plugin)) return;
|
|
7709
|
+
logged.add(plugin);
|
|
7710
|
+
if (s.pinned) {
|
|
7711
|
+
const state = rProtectThirdParty() ? "the registered compatibility pin keeps them legible" : "a pin exists for it but 「保护第三方表面」 is currently OFF";
|
|
7712
|
+
console.log(`dsh-any-background: ${plugin} detected — its surfaces fill from tokens this plugin fades, and ${state} (${s.tokens.join(", ")})`);
|
|
7713
|
+
return;
|
|
7714
|
+
}
|
|
7715
|
+
console.warn(`dsh-any-background: ${plugin} paints from ${s.tokens.join(", ")} — the same tokens the interface-opacity sliders own, so those surfaces fade with them. No compatibility pin is registered for ${plugin}; if one of its surfaces goes transparent or unreadable, report this line and a pin can be added.`);
|
|
7716
|
+
}
|
|
7717
|
+
/** Snapshot of what the scan found (for the settings card). */
|
|
7718
|
+
function thirdPartySightings() {
|
|
7719
|
+
return sightings;
|
|
7720
|
+
}
|
|
7721
|
+
/** Subscribe to sightings changing. */
|
|
7722
|
+
function subscribeThirdParty(cb) {
|
|
7723
|
+
subs$1.add(cb);
|
|
7724
|
+
return () => {
|
|
7725
|
+
subs$1.delete(cb);
|
|
7726
|
+
};
|
|
7727
|
+
}
|
|
7728
|
+
/** Run the scan at boot and keep it current. Returns an unsubscribe for teardown. */
|
|
7729
|
+
function startThirdPartyWatch() {
|
|
7730
|
+
scan();
|
|
7731
|
+
return watchDom((records) => {
|
|
7732
|
+
let relevant = false;
|
|
7733
|
+
for (const r of records) {
|
|
7734
|
+
for (const n of r.addedNodes) {
|
|
7735
|
+
if (!(n instanceof HTMLStyleElement)) continue;
|
|
7736
|
+
const name = n.getAttribute("data-plugin");
|
|
7737
|
+
if (name !== null && !isSelf(name) && !isHost(name)) {
|
|
7738
|
+
relevant = true;
|
|
7739
|
+
break;
|
|
7740
|
+
}
|
|
7741
|
+
}
|
|
7742
|
+
if (relevant) break;
|
|
7743
|
+
}
|
|
7744
|
+
if (relevant) scan();
|
|
7745
|
+
}, "head");
|
|
7746
|
+
}
|
|
7747
|
+
//#endregion
|
|
7362
7748
|
//#region src/client/components/LiveSlider.tsx
|
|
7363
7749
|
/**
|
|
7364
7750
|
* Zero-lag slider: the thumb and value label update through DOM refs while
|
|
@@ -7495,186 +7881,244 @@ window.__ModuleLoader__.load({
|
|
|
7495
7881
|
function InterfacePage({ p }) {
|
|
7496
7882
|
const { t, setOps, setBlurs, setSop, setPanelOp, useStore } = p;
|
|
7497
7883
|
useStore((s) => s.metaRev);
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
|
|
7538
|
-
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7546
|
-
|
|
7547
|
-
|
|
7548
|
-
|
|
7549
|
-
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
|
|
7584
|
-
if (isChat) {
|
|
7585
|
-
cfg.chatTextOpacity = op;
|
|
7586
|
-
applyViewCards();
|
|
7587
|
-
saveConfig();
|
|
7588
|
-
} else if (isTrajectory) {
|
|
7589
|
-
cfg.trajectoryOpacity = op;
|
|
7590
|
-
applyTrajectoryOverrides(op);
|
|
7591
|
-
saveConfig();
|
|
7592
|
-
} else if (isSettings) setSop(op);
|
|
7593
|
-
else if (isPanel) setPanelOp(op);
|
|
7594
|
-
else if (isProduced) {
|
|
7595
|
-
cfg.producedOpacity = op;
|
|
7596
|
-
applyProduced();
|
|
7597
|
-
saveConfig();
|
|
7598
|
-
} else if (isHeader) {
|
|
7599
|
-
cfg.headerOpacity = op;
|
|
7600
|
-
applyHeaderPopovers();
|
|
7601
|
-
saveConfig();
|
|
7602
|
-
} else if (isHeaderBar) {
|
|
7603
|
-
cfg.headerBarOpacity = op;
|
|
7604
|
-
applyHeaderBar();
|
|
7884
|
+
const [tpRev, setTpRev] = (0, react.useState)(0);
|
|
7885
|
+
(0, react.useEffect)(() => subscribeThirdParty(() => setTpRev((v) => v + 1)), []);
|
|
7886
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
7887
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
7888
|
+
className: "dab-head dab-rise",
|
|
7889
|
+
style: { "--d": 0 },
|
|
7890
|
+
children: [
|
|
7891
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7892
|
+
className: "dab-overline",
|
|
7893
|
+
children: "Surfaces"
|
|
7894
|
+
}),
|
|
7895
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
7896
|
+
className: "dab-h1",
|
|
7897
|
+
children: t("uiTitle")
|
|
7898
|
+
}),
|
|
7899
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7900
|
+
className: "dab-desc",
|
|
7901
|
+
children: t("descInterface")
|
|
7902
|
+
})
|
|
7903
|
+
]
|
|
7904
|
+
}),
|
|
7905
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7906
|
+
className: "dab-grid-parts",
|
|
7907
|
+
children: PARTS$1.map((part, i) => {
|
|
7908
|
+
const { labelKey, Icon, isSettings, isChat, isTrajectory, isPanel, isProduced, isHeader, isHeaderBar } = part;
|
|
7909
|
+
const opKey = part.opKey;
|
|
7910
|
+
const label = t(labelKey);
|
|
7911
|
+
const blurKey = isChat ? "chat" : isTrajectory ? "trajectory" : isSettings ? "settings" : isPanel ? "panel" : isProduced ? "produced" : isHeader ? "header" : isHeaderBar ? "headerBar" : opKey;
|
|
7912
|
+
const opacity = isChat ? rChatTextOpacity() : isTrajectory ? rTrajectoryOpacity() : isSettings ? rSop() : isPanel ? rPanelOpacity() : isProduced ? rProducedOpacity() : isHeader ? rHeaderOpacity() : isHeaderBar ? rHeaderBarOpacity() : rOps()[opKey];
|
|
7913
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
7914
|
+
className: "dab-card dab-card-hover dab-rise",
|
|
7915
|
+
style: { "--d": i + 1 },
|
|
7916
|
+
children: [
|
|
7917
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7918
|
+
className: "dab-part-head",
|
|
7919
|
+
children: [
|
|
7920
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7921
|
+
className: "dab-part-ico",
|
|
7922
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { size: 16 })
|
|
7923
|
+
}),
|
|
7924
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7925
|
+
className: "dab-part-name",
|
|
7926
|
+
children: label
|
|
7927
|
+
}),
|
|
7928
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7929
|
+
className: "dab-part-badge",
|
|
7930
|
+
children: [Math.round(opacity * 100), "%"]
|
|
7931
|
+
})
|
|
7932
|
+
]
|
|
7933
|
+
}),
|
|
7934
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
7935
|
+
label: t("uiOpacity"),
|
|
7936
|
+
min: 0,
|
|
7937
|
+
max: 100,
|
|
7938
|
+
step: 1,
|
|
7939
|
+
def: Math.round(opacity * 100),
|
|
7940
|
+
fmt: (v) => `${v}%`,
|
|
7941
|
+
onInput: (v) => {
|
|
7942
|
+
const op = v / 100;
|
|
7943
|
+
if (isChat) {
|
|
7944
|
+
cfg.chatTextOpacity = op;
|
|
7945
|
+
applyViewCards();
|
|
7946
|
+
} else if (isTrajectory) {
|
|
7947
|
+
cfg.trajectoryOpacity = op;
|
|
7948
|
+
applyTrajectoryOverrides(op);
|
|
7949
|
+
} else if (isSettings) {
|
|
7950
|
+
cfg.settingsOpacity = op;
|
|
7951
|
+
applySettingsOverrides(op);
|
|
7952
|
+
} else if (isPanel) {
|
|
7953
|
+
cfg.panelOpacity = op;
|
|
7954
|
+
applyPanelOverrides(op);
|
|
7955
|
+
} else if (isProduced) {
|
|
7956
|
+
cfg.producedOpacity = op;
|
|
7957
|
+
applyProduced();
|
|
7958
|
+
} else if (isHeader) {
|
|
7959
|
+
cfg.headerOpacity = op;
|
|
7960
|
+
applyHeaderPopovers();
|
|
7961
|
+
} else if (isHeaderBar) {
|
|
7962
|
+
cfg.headerBarOpacity = op;
|
|
7963
|
+
applyHeaderBar();
|
|
7964
|
+
} else {
|
|
7965
|
+
const ops = { ...rOps() };
|
|
7966
|
+
ops[opKey] = op;
|
|
7967
|
+
cfg.opacities = ops;
|
|
7968
|
+
applyCustomTokens(ops);
|
|
7969
|
+
}
|
|
7605
7970
|
saveConfig();
|
|
7606
|
-
} else {
|
|
7607
|
-
const ops = { ...rOps() };
|
|
7608
|
-
ops[opKey] = op;
|
|
7609
|
-
setOps(ops);
|
|
7610
|
-
}
|
|
7611
|
-
}
|
|
7612
|
-
}),
|
|
7613
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
7614
|
-
label: t("uiBlur"),
|
|
7615
|
-
min: 0,
|
|
7616
|
-
max: 60,
|
|
7617
|
-
step: 1,
|
|
7618
|
-
def: rBlurs()[blurKey],
|
|
7619
|
-
fmt: (v) => `${v}px`,
|
|
7620
|
-
onInput: (v) => {
|
|
7621
|
-
const blurs = { ...rBlurs() };
|
|
7622
|
-
blurs[blurKey] = v;
|
|
7623
|
-
cfg.blurs = blurs;
|
|
7624
|
-
setPartBlur(blurKey, v);
|
|
7625
|
-
saveConfig();
|
|
7626
|
-
},
|
|
7627
|
-
onChange: (v) => {
|
|
7628
|
-
const blurs = { ...rBlurs() };
|
|
7629
|
-
blurs[blurKey] = v;
|
|
7630
|
-
setBlurs(blurs);
|
|
7631
|
-
}
|
|
7632
|
-
}),
|
|
7633
|
-
isHeaderBar && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7634
|
-
className: "dab-slider-block",
|
|
7635
|
-
title: t("captionInkHint"),
|
|
7636
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7637
|
-
className: "dab-slider-label",
|
|
7638
|
-
children: t("captionInkLabel")
|
|
7639
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7640
|
-
style: {
|
|
7641
|
-
display: "flex",
|
|
7642
|
-
alignItems: "center",
|
|
7643
|
-
gap: 10
|
|
7644
7971
|
},
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7972
|
+
onChange: (v) => {
|
|
7973
|
+
const op = v / 100;
|
|
7974
|
+
if (isChat) {
|
|
7975
|
+
cfg.chatTextOpacity = op;
|
|
7976
|
+
applyViewCards();
|
|
7977
|
+
saveConfig();
|
|
7978
|
+
} else if (isTrajectory) {
|
|
7979
|
+
cfg.trajectoryOpacity = op;
|
|
7980
|
+
applyTrajectoryOverrides(op);
|
|
7981
|
+
saveConfig();
|
|
7982
|
+
} else if (isSettings) setSop(op);
|
|
7983
|
+
else if (isPanel) setPanelOp(op);
|
|
7984
|
+
else if (isProduced) {
|
|
7985
|
+
cfg.producedOpacity = op;
|
|
7986
|
+
applyProduced();
|
|
7987
|
+
saveConfig();
|
|
7988
|
+
} else if (isHeader) {
|
|
7989
|
+
cfg.headerOpacity = op;
|
|
7990
|
+
applyHeaderPopovers();
|
|
7991
|
+
saveConfig();
|
|
7992
|
+
} else if (isHeaderBar) {
|
|
7993
|
+
cfg.headerBarOpacity = op;
|
|
7650
7994
|
applyHeaderBar();
|
|
7651
7995
|
saveConfig();
|
|
7996
|
+
} else {
|
|
7997
|
+
const ops = { ...rOps() };
|
|
7998
|
+
ops[opKey] = op;
|
|
7999
|
+
setOps(ops);
|
|
8000
|
+
}
|
|
8001
|
+
}
|
|
8002
|
+
}),
|
|
8003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LiveSlider, {
|
|
8004
|
+
label: t("uiBlur"),
|
|
8005
|
+
min: 0,
|
|
8006
|
+
max: 60,
|
|
8007
|
+
step: 1,
|
|
8008
|
+
def: rBlurs()[blurKey],
|
|
8009
|
+
fmt: (v) => `${v}px`,
|
|
8010
|
+
onInput: (v) => {
|
|
8011
|
+
const blurs = { ...rBlurs() };
|
|
8012
|
+
blurs[blurKey] = v;
|
|
8013
|
+
cfg.blurs = blurs;
|
|
8014
|
+
setPartBlur(blurKey, v);
|
|
8015
|
+
saveConfig();
|
|
8016
|
+
},
|
|
8017
|
+
onChange: (v) => {
|
|
8018
|
+
const blurs = { ...rBlurs() };
|
|
8019
|
+
blurs[blurKey] = v;
|
|
8020
|
+
setBlurs(blurs);
|
|
8021
|
+
}
|
|
8022
|
+
}),
|
|
8023
|
+
isHeaderBar && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8024
|
+
className: "dab-slider-block",
|
|
8025
|
+
title: t("captionInkHint"),
|
|
8026
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8027
|
+
className: "dab-slider-label",
|
|
8028
|
+
children: t("captionInkLabel")
|
|
8029
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8030
|
+
style: {
|
|
8031
|
+
display: "flex",
|
|
8032
|
+
alignItems: "center",
|
|
8033
|
+
gap: 10
|
|
7652
8034
|
},
|
|
7653
|
-
children:
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
8035
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
8036
|
+
className: "dab-select",
|
|
8037
|
+
value: rCaptionInk(),
|
|
8038
|
+
onChange: (e) => {
|
|
8039
|
+
cfg.captionInk = e.target.value;
|
|
8040
|
+
applyHeaderBar();
|
|
8041
|
+
saveConfig();
|
|
8042
|
+
},
|
|
8043
|
+
children: [
|
|
8044
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
8045
|
+
value: "auto",
|
|
8046
|
+
children: t("captionInkAuto")
|
|
8047
|
+
}),
|
|
8048
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
8049
|
+
value: "theme",
|
|
8050
|
+
children: t("captionInkTheme")
|
|
8051
|
+
}),
|
|
8052
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
8053
|
+
value: "white",
|
|
8054
|
+
children: t("captionInkWhite")
|
|
8055
|
+
}),
|
|
8056
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
8057
|
+
value: "black",
|
|
8058
|
+
children: t("captionInkBlack")
|
|
8059
|
+
})
|
|
8060
|
+
]
|
|
8061
|
+
})
|
|
8062
|
+
})]
|
|
8063
|
+
})
|
|
8064
|
+
]
|
|
8065
|
+
}, blurKey);
|
|
8066
|
+
})
|
|
8067
|
+
}),
|
|
8068
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
8069
|
+
className: "dab-card dab-rise",
|
|
8070
|
+
style: { "--d": PARTS$1.length + 1 },
|
|
8071
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8072
|
+
className: "dab-row-head",
|
|
8073
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8074
|
+
style: {
|
|
8075
|
+
display: "flex",
|
|
8076
|
+
alignItems: "center",
|
|
8077
|
+
gap: 10
|
|
8078
|
+
},
|
|
8079
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8080
|
+
className: "dab-part-ico",
|
|
8081
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelIcon, { size: 16 })
|
|
8082
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8083
|
+
className: "dab-part-name",
|
|
8084
|
+
children: t("tpTitle")
|
|
8085
|
+
})]
|
|
8086
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8087
|
+
type: "button",
|
|
8088
|
+
className: `dab-toggle${rProtectThirdParty() ? " is-on" : ""}`,
|
|
8089
|
+
role: "switch",
|
|
8090
|
+
"aria-checked": rProtectThirdParty(),
|
|
8091
|
+
"aria-label": t("tpEnabled"),
|
|
8092
|
+
title: t("tpHint"),
|
|
8093
|
+
onClick: () => {
|
|
8094
|
+
cfg.protectThirdParty = !rProtectThirdParty();
|
|
8095
|
+
applyReaderLanes();
|
|
8096
|
+
saveConfig();
|
|
8097
|
+
setTpRev((v) => v + 1);
|
|
8098
|
+
},
|
|
8099
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dab-toggle-knob" })
|
|
8100
|
+
})]
|
|
8101
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8102
|
+
className: "dab-slider-block",
|
|
8103
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8104
|
+
className: "dab-slider-label",
|
|
8105
|
+
title: t("tpHint"),
|
|
8106
|
+
children: t("tpEnabled")
|
|
8107
|
+
}), thirdPartySightings().length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8108
|
+
style: { fontSize: 12 },
|
|
8109
|
+
children: t("tpNone")
|
|
8110
|
+
}) : thirdPartySightings().map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8111
|
+
style: { fontSize: 12 },
|
|
8112
|
+
title: s.tokens.join(", "),
|
|
8113
|
+
children: [
|
|
8114
|
+
s.plugin,
|
|
8115
|
+
":",
|
|
8116
|
+
s.pinned && s.labelKey ? `${t(s.labelKey)} ${t("tpPinned")}` : s.pinned ? t("tpPinned") : t("tpUnpinned")
|
|
8117
|
+
]
|
|
8118
|
+
}, s.plugin))]
|
|
8119
|
+
})]
|
|
7676
8120
|
})
|
|
7677
|
-
|
|
8121
|
+
] });
|
|
7678
8122
|
}
|
|
7679
8123
|
//#endregion
|
|
7680
8124
|
//#region src/client/components/pages/FontPage.tsx
|
|
@@ -8793,6 +9237,12 @@ window.__ModuleLoader__.load({
|
|
|
8793
9237
|
onClick: () => setRotation({ interval: "reload" }),
|
|
8794
9238
|
children: t("rotReload")
|
|
8795
9239
|
}),
|
|
9240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9241
|
+
type: "button",
|
|
9242
|
+
className: `dab-chip${rotation.interval === "hourly" ? " is-active" : ""}`,
|
|
9243
|
+
onClick: () => setRotation({ interval: "hourly" }),
|
|
9244
|
+
children: t("rotHourly")
|
|
9245
|
+
}),
|
|
8796
9246
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8797
9247
|
type: "button",
|
|
8798
9248
|
className: `dab-chip${rotation.interval === "daily" ? " is-active" : ""}`,
|
|
@@ -9322,7 +9772,7 @@ window.__ModuleLoader__.load({
|
|
|
9322
9772
|
});
|
|
9323
9773
|
}
|
|
9324
9774
|
function ProfilePage({ p, notify }) {
|
|
9325
|
-
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, applyPreset, setSchedule, useStore } = p;
|
|
9775
|
+
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, renameProfile, duplicateProfile, applyPreset, setSchedule, useStore } = p;
|
|
9326
9776
|
const profiles = useStore((s) => s.profiles);
|
|
9327
9777
|
const schedule = useStore((s) => s.schedule);
|
|
9328
9778
|
const activeProfile = useStore((s) => s.activeProfile);
|
|
@@ -9330,6 +9780,8 @@ window.__ModuleLoader__.load({
|
|
|
9330
9780
|
const [nameOpen, setNameOpen] = (0, react.useState)(false);
|
|
9331
9781
|
const [nameVal, setNameVal] = (0, react.useState)("");
|
|
9332
9782
|
const [confirmId, setConfirmId] = (0, react.useState)(null);
|
|
9783
|
+
const [editId, setEditId] = (0, react.useState)(null);
|
|
9784
|
+
const [editVal, setEditVal] = (0, react.useState)("");
|
|
9333
9785
|
const onImport = async (file) => {
|
|
9334
9786
|
try {
|
|
9335
9787
|
const ok = await importTheme(file);
|
|
@@ -9358,6 +9810,14 @@ window.__ModuleLoader__.load({
|
|
|
9358
9810
|
setConfirmId(null);
|
|
9359
9811
|
deleteProfile(entry.id);
|
|
9360
9812
|
};
|
|
9813
|
+
const onRenameCommit = (entry) => {
|
|
9814
|
+
const name = editVal.trim();
|
|
9815
|
+
setEditId(null);
|
|
9816
|
+
if (name && name !== entry.name && renameProfile(entry.id, name)) notify(t("profileRenamed"), true);
|
|
9817
|
+
};
|
|
9818
|
+
const onDuplicate = (entry) => {
|
|
9819
|
+
if (duplicateProfile(entry.id)) notify(t("profileDuplicated"), true);
|
|
9820
|
+
};
|
|
9361
9821
|
const profileOptions = (value, onChange) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
9362
9822
|
className: "dab-select",
|
|
9363
9823
|
value: value ?? "",
|
|
@@ -9489,8 +9949,42 @@ window.__ModuleLoader__.load({
|
|
|
9489
9949
|
children: profiles.map((entry, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9490
9950
|
className: `dab-profile-row${activeProfile === entry.id ? " is-active" : ""}`,
|
|
9491
9951
|
style: { "--i": i },
|
|
9492
|
-
children: [
|
|
9493
|
-
|
|
9952
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileDot, { entry }), editId === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9953
|
+
className: "dab-urlrow",
|
|
9954
|
+
style: {
|
|
9955
|
+
flex: 1,
|
|
9956
|
+
minWidth: 0
|
|
9957
|
+
},
|
|
9958
|
+
children: [
|
|
9959
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
9960
|
+
type: "text",
|
|
9961
|
+
className: "dab-urlinput",
|
|
9962
|
+
value: editVal,
|
|
9963
|
+
maxLength: 60,
|
|
9964
|
+
onChange: (e) => setEditVal(e.target.value),
|
|
9965
|
+
onKeyDown: (e) => {
|
|
9966
|
+
if (e.key === "Enter") {
|
|
9967
|
+
e.preventDefault();
|
|
9968
|
+
onRenameCommit(entry);
|
|
9969
|
+
}
|
|
9970
|
+
},
|
|
9971
|
+
autoFocus: true
|
|
9972
|
+
}),
|
|
9973
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9974
|
+
type: "button",
|
|
9975
|
+
className: "dab-btn dab-btn-primary",
|
|
9976
|
+
disabled: !editVal.trim(),
|
|
9977
|
+
onClick: () => onRenameCommit(entry),
|
|
9978
|
+
children: t("editorCommit")
|
|
9979
|
+
}),
|
|
9980
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9981
|
+
type: "button",
|
|
9982
|
+
className: "dab-btn",
|
|
9983
|
+
onClick: () => setEditId(null),
|
|
9984
|
+
children: t("bgUrlCancel")
|
|
9985
|
+
})
|
|
9986
|
+
]
|
|
9987
|
+
}) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
9494
9988
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9495
9989
|
className: "dab-profile-meta",
|
|
9496
9990
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -9507,6 +10001,23 @@ window.__ModuleLoader__.load({
|
|
|
9507
10001
|
onClick: () => onApply(entry),
|
|
9508
10002
|
children: t("profileApply")
|
|
9509
10003
|
}),
|
|
10004
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10005
|
+
type: "button",
|
|
10006
|
+
className: "dab-btn",
|
|
10007
|
+
title: t("profileRename"),
|
|
10008
|
+
onClick: () => {
|
|
10009
|
+
setEditId(entry.id);
|
|
10010
|
+
setEditVal(entry.name);
|
|
10011
|
+
},
|
|
10012
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 13 })
|
|
10013
|
+
}),
|
|
10014
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10015
|
+
type: "button",
|
|
10016
|
+
className: "dab-btn",
|
|
10017
|
+
title: t("profileDuplicate"),
|
|
10018
|
+
onClick: () => onDuplicate(entry),
|
|
10019
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, { size: 13 })
|
|
10020
|
+
}),
|
|
9510
10021
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9511
10022
|
type: "button",
|
|
9512
10023
|
className: `dab-btn dab-btn-danger-solid${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
|
|
@@ -9514,7 +10025,7 @@ window.__ModuleLoader__.load({
|
|
|
9514
10025
|
onClick: () => onDelete(entry),
|
|
9515
10026
|
children: confirmId === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CheckIcon, { size: 13 }), t("profileDeleteConfirm")] }) : t("profileDelete")
|
|
9516
10027
|
})
|
|
9517
|
-
]
|
|
10028
|
+
] })]
|
|
9518
10029
|
}, entry.id))
|
|
9519
10030
|
})
|
|
9520
10031
|
]
|
|
@@ -10048,9 +10559,9 @@ window.__ModuleLoader__.load({
|
|
|
10048
10559
|
* unsubscribe for plugin teardown. */
|
|
10049
10560
|
function startBetterSidebarWatch() {
|
|
10050
10561
|
sync();
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10562
|
+
return watchDom((records) => {
|
|
10563
|
+
if (records.some((r) => r.target === document.body && r.type === "childList")) sync();
|
|
10564
|
+
});
|
|
10054
10565
|
}
|
|
10055
10566
|
let nativeTabs = false;
|
|
10056
10567
|
const nativeSubs = /* @__PURE__ */ new Set();
|
|
@@ -10171,6 +10682,50 @@ window.__ModuleLoader__.load({
|
|
|
10171
10682
|
});
|
|
10172
10683
|
}
|
|
10173
10684
|
//#endregion
|
|
10685
|
+
//#region src/shared/rotation.ts
|
|
10686
|
+
/** ISO week key (Monday-first). */
|
|
10687
|
+
function isoWeekKey(d) {
|
|
10688
|
+
const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
10689
|
+
const day = t.getUTCDay() || 7;
|
|
10690
|
+
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
10691
|
+
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
10692
|
+
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
10693
|
+
return `${t.getUTCFullYear()}-W${week}`;
|
|
10694
|
+
}
|
|
10695
|
+
/** Whether the pool should advance right now. */
|
|
10696
|
+
function rotationDue(rot, now = /* @__PURE__ */ new Date()) {
|
|
10697
|
+
if (!rot.enabled || rot.items.length === 0) return false;
|
|
10698
|
+
if (rot.interval === "reload") return true;
|
|
10699
|
+
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
10700
|
+
if (last === null || isNaN(last.getTime())) return true;
|
|
10701
|
+
if (rot.interval === "hourly") return hourKey(last) !== hourKey(now);
|
|
10702
|
+
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
10703
|
+
return isoWeekKey(last) !== isoWeekKey(now);
|
|
10704
|
+
}
|
|
10705
|
+
/** Epoch-hour bucket: hourly cadence advances on each full hour boundary
|
|
10706
|
+
* (in whole-hour timezones — UTC+8 among them — that is exactly the local
|
|
10707
|
+
* on-the-hour tick). */
|
|
10708
|
+
const hourKey = (d) => Math.floor(d.getTime() / 36e5);
|
|
10709
|
+
/** Whether an OPEN page should keep advancing this cadence on its timer.
|
|
10710
|
+
* Only the time-based cadences tick live; 'reload' is a once-per-READ pick
|
|
10711
|
+
* (the server advances it inside the `read` itself), so a live tick driving
|
|
10712
|
+
* it would flip the wallpaper every 30 s — exactly the v0.4.0 regression
|
|
10713
|
+
* (rotationDue is unconditionally true for 'reload'). */
|
|
10714
|
+
function rotationAdvancesLive(rot) {
|
|
10715
|
+
return rot.interval !== "reload";
|
|
10716
|
+
}
|
|
10717
|
+
/** Pick the next pool index under the configured mode. */
|
|
10718
|
+
function pickNextRotationIndex(rot) {
|
|
10719
|
+
const n = rot.items.length;
|
|
10720
|
+
if (n === 0) return -1;
|
|
10721
|
+
if (rot.mode === "shuffle" && n > 1) {
|
|
10722
|
+
let idx = rot.current;
|
|
10723
|
+
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
10724
|
+
return idx;
|
|
10725
|
+
}
|
|
10726
|
+
return (rot.current + 1) % n;
|
|
10727
|
+
}
|
|
10728
|
+
//#endregion
|
|
10174
10729
|
//#region src/client/index.tsx
|
|
10175
10730
|
/**
|
|
10176
10731
|
* dsh-any-background — browser half entry.
|
|
@@ -10436,38 +10991,11 @@ window.__ModuleLoader__.load({
|
|
|
10436
10991
|
syncMetaNow();
|
|
10437
10992
|
return true;
|
|
10438
10993
|
};
|
|
10439
|
-
const
|
|
10440
|
-
|
|
10441
|
-
const day = t.getUTCDay() || 7;
|
|
10442
|
-
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
10443
|
-
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
10444
|
-
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
10445
|
-
return `${t.getUTCFullYear()}-W${week}`;
|
|
10446
|
-
};
|
|
10447
|
-
const rotationDue = () => {
|
|
10448
|
-
const rot = rRotation();
|
|
10449
|
-
if (!rot.enabled || rot.items.length === 0) return false;
|
|
10450
|
-
if (rot.interval === "reload") return true;
|
|
10451
|
-
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
10452
|
-
if (last === null || isNaN(last.getTime())) return true;
|
|
10453
|
-
const now = /* @__PURE__ */ new Date();
|
|
10454
|
-
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
10455
|
-
return isoWeekKey(last) !== isoWeekKey(now);
|
|
10456
|
-
};
|
|
10457
|
-
const pickNextRotationIndex = () => {
|
|
10458
|
-
const rot = rRotation();
|
|
10459
|
-
const n = rot.items.length;
|
|
10460
|
-
if (n === 0) return -1;
|
|
10461
|
-
if (rot.mode === "shuffle" && n > 1) {
|
|
10462
|
-
let idx = rot.current;
|
|
10463
|
-
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
10464
|
-
return idx;
|
|
10465
|
-
}
|
|
10466
|
-
return (rot.current + 1) % n;
|
|
10467
|
-
};
|
|
10994
|
+
const rotationDue$1 = () => rotationDue(rRotation());
|
|
10995
|
+
const pickNextRotationIndex$1 = () => pickNextRotationIndex(rRotation());
|
|
10468
10996
|
const maybeRotate = async () => {
|
|
10469
|
-
if (!rotationDue()) return;
|
|
10470
|
-
await applyRotationIndex(pickNextRotationIndex(), true);
|
|
10997
|
+
if (!rotationDue$1()) return;
|
|
10998
|
+
await applyRotationIndex(pickNextRotationIndex$1(), true);
|
|
10471
10999
|
};
|
|
10472
11000
|
const parseHHMM = (s) => {
|
|
10473
11001
|
const m = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(s);
|
|
@@ -10483,6 +11011,7 @@ window.__ModuleLoader__.load({
|
|
|
10483
11011
|
return cur >= night && cur < day;
|
|
10484
11012
|
};
|
|
10485
11013
|
const scheduleTick = () => {
|
|
11014
|
+
if (rotationAdvancesLive(rRotation())) maybeRotate();
|
|
10486
11015
|
const sc = rSchedule();
|
|
10487
11016
|
if (!sc.enabled) return;
|
|
10488
11017
|
const want = (sc.mode === "system" ? window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ?? false : isNightNow(sc)) ? sc.nightProfile : sc.dayProfile;
|
|
@@ -10543,6 +11072,7 @@ window.__ModuleLoader__.load({
|
|
|
10543
11072
|
teardownWp();
|
|
10544
11073
|
}, "dsh-any-background: wp cleanup");
|
|
10545
11074
|
ctx.effect(() => startBetterSidebarWatch(), "dsh-any-background: better-sidebar watch");
|
|
11075
|
+
ctx.effect(() => startThirdPartyWatch(), "dsh-any-background: third-party scan");
|
|
10546
11076
|
ctx.effect(() => startHeaderPopoverTagging(), "dsh-any-background: header popover tagging");
|
|
10547
11077
|
ctx.effect(() => ctx.on("theme/change", () => {
|
|
10548
11078
|
if (rHasColor() || rBgDark() !== null || rSchemeOverride() !== "auto") {
|
|
@@ -10817,12 +11347,32 @@ window.__ModuleLoader__.load({
|
|
|
10817
11347
|
}
|
|
10818
11348
|
}
|
|
10819
11349
|
}
|
|
11350
|
+
let pool;
|
|
11351
|
+
const rotItems = rRotation().items;
|
|
11352
|
+
if (rotItems.length > 0) {
|
|
11353
|
+
pool = [];
|
|
11354
|
+
for (const it of rotItems) try {
|
|
11355
|
+
const blob = await fetch(rotationFileUrl(it.file)).then((r) => {
|
|
11356
|
+
if (!r.ok) throw new Error(`http ${r.status}`);
|
|
11357
|
+
return r.blob();
|
|
11358
|
+
});
|
|
11359
|
+
pool.push({
|
|
11360
|
+
file: it.file,
|
|
11361
|
+
thumb: it.thumb,
|
|
11362
|
+
dataUrl: await blobToDataUrl(blob)
|
|
11363
|
+
});
|
|
11364
|
+
} catch {
|
|
11365
|
+
console.warn(`dsh-any-background: export skipped a rotation image (${it.file})`);
|
|
11366
|
+
}
|
|
11367
|
+
if (pool.length === 0) pool = void 0;
|
|
11368
|
+
}
|
|
10820
11369
|
const payload = {
|
|
10821
|
-
version:
|
|
11370
|
+
version: 3,
|
|
10822
11371
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10823
11372
|
config: cfg,
|
|
10824
11373
|
wallpaper: wallpaperPayload,
|
|
10825
|
-
video: videoPayload
|
|
11374
|
+
video: videoPayload,
|
|
11375
|
+
pool
|
|
10826
11376
|
};
|
|
10827
11377
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
|
10828
11378
|
const url = URL.createObjectURL(blob);
|
|
@@ -10876,6 +11426,26 @@ window.__ModuleLoader__.load({
|
|
|
10876
11426
|
if (cfg.regenerateOnReload) regenerateGeneratedBg();
|
|
10877
11427
|
else if (cfg.generatedBg) updateGeneratedBg(cfg.generatedBg);
|
|
10878
11428
|
}
|
|
11429
|
+
const poolRaw = d.pool;
|
|
11430
|
+
if (Array.isArray(poolRaw)) {
|
|
11431
|
+
if (poolRaw.length > 0) {
|
|
11432
|
+
const r = await rotationRestore(poolRaw);
|
|
11433
|
+
if (r.ok && r.items) cfg.rotation = {
|
|
11434
|
+
...cfg.rotation,
|
|
11435
|
+
items: r.items,
|
|
11436
|
+
current: Math.min(cfg.rotation.current, Math.max(0, r.items.length - 1))
|
|
11437
|
+
};
|
|
11438
|
+
else console.warn("dsh-any-background: rotation pool restore failed", r.error);
|
|
11439
|
+
} else if (cfg.rotation.items.length > 0) cfg.rotation = {
|
|
11440
|
+
...cfg.rotation,
|
|
11441
|
+
items: [],
|
|
11442
|
+
current: 0
|
|
11443
|
+
};
|
|
11444
|
+
} else if ((typeof d.version !== "number" || d.version < 3) && cfg.rotation.items.length > 0) cfg.rotation = {
|
|
11445
|
+
...cfg.rotation,
|
|
11446
|
+
items: [],
|
|
11447
|
+
current: 0
|
|
11448
|
+
};
|
|
10879
11449
|
persistConfig();
|
|
10880
11450
|
syncMetaNow();
|
|
10881
11451
|
if (rHasColor()) {
|
|
@@ -10911,6 +11481,37 @@ window.__ModuleLoader__.load({
|
|
|
10911
11481
|
return true;
|
|
10912
11482
|
},
|
|
10913
11483
|
applyProfile: (id) => applyProfileById(id),
|
|
11484
|
+
renameProfile: (id, name) => {
|
|
11485
|
+
const trimmed = name.trim();
|
|
11486
|
+
if (!trimmed) return false;
|
|
11487
|
+
const before = rProfiles();
|
|
11488
|
+
if (!before.some((p) => p.id === id)) return false;
|
|
11489
|
+
cfg.profiles = before.map((p) => p.id === id ? {
|
|
11490
|
+
...p,
|
|
11491
|
+
name: trimmed.slice(0, 60)
|
|
11492
|
+
} : p);
|
|
11493
|
+
persistConfig();
|
|
11494
|
+
syncMetaNow();
|
|
11495
|
+
return true;
|
|
11496
|
+
},
|
|
11497
|
+
duplicateProfile: (id) => {
|
|
11498
|
+
const before = rProfiles();
|
|
11499
|
+
const src = before.find((p) => p.id === id);
|
|
11500
|
+
if (!src) return false;
|
|
11501
|
+
const profiles = [...before];
|
|
11502
|
+
if (profiles.length >= 20) profiles.shift();
|
|
11503
|
+
const nid = `p-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11504
|
+
profiles.push({
|
|
11505
|
+
...src,
|
|
11506
|
+
id: nid,
|
|
11507
|
+
name: `${src.name} (2)`.slice(0, 60),
|
|
11508
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11509
|
+
});
|
|
11510
|
+
cfg.profiles = profiles;
|
|
11511
|
+
persistConfig();
|
|
11512
|
+
syncMetaNow();
|
|
11513
|
+
return true;
|
|
11514
|
+
},
|
|
10914
11515
|
deleteProfile: (id) => {
|
|
10915
11516
|
const before = rProfiles();
|
|
10916
11517
|
if (!before.some((p) => p.id === id)) return false;
|
|
@@ -10995,7 +11596,7 @@ window.__ModuleLoader__.load({
|
|
|
10995
11596
|
},
|
|
10996
11597
|
rotateNow: async () => {
|
|
10997
11598
|
if (rRotation().items.length === 0) return false;
|
|
10998
|
-
return applyRotationIndex(pickNextRotationIndex(), true);
|
|
11599
|
+
return applyRotationIndex(pickNextRotationIndex$1(), true);
|
|
10999
11600
|
},
|
|
11000
11601
|
setVideoFromUrl: async (url) => {
|
|
11001
11602
|
const r = await setVideoFromUrl(url);
|
|
@@ -11064,10 +11665,10 @@ window.__ModuleLoader__.load({
|
|
|
11064
11665
|
}
|
|
11065
11666
|
}
|
|
11066
11667
|
};
|
|
11067
|
-
let
|
|
11668
|
+
let disposeNavWatch = null;
|
|
11068
11669
|
const watchNavIcon = () => {
|
|
11069
|
-
if (
|
|
11070
|
-
|
|
11670
|
+
if (disposeNavWatch !== null || typeof MutationObserver === "undefined") return;
|
|
11671
|
+
disposeNavWatch = watchDom((records) => {
|
|
11071
11672
|
if (records.some((r) => {
|
|
11072
11673
|
for (const n of r.addedNodes) {
|
|
11073
11674
|
if (n.nodeType !== 1) continue;
|
|
@@ -11077,16 +11678,12 @@ window.__ModuleLoader__.load({
|
|
|
11077
11678
|
return false;
|
|
11078
11679
|
})) applyNavIcon();
|
|
11079
11680
|
});
|
|
11080
|
-
navIconObserver.observe(document.body, {
|
|
11081
|
-
childList: true,
|
|
11082
|
-
subtree: true
|
|
11083
|
-
});
|
|
11084
11681
|
applyNavIcon();
|
|
11085
11682
|
};
|
|
11086
11683
|
watchNavIcon();
|
|
11087
11684
|
ctx.effect(() => () => {
|
|
11088
|
-
|
|
11089
|
-
|
|
11685
|
+
disposeNavWatch?.();
|
|
11686
|
+
disposeNavWatch = null;
|
|
11090
11687
|
}, "dsh-any-background: nav icon watch");
|
|
11091
11688
|
const restoreSaved = () => {
|
|
11092
11689
|
if (rHasColor()) {
|