dsh-any-background 0.3.9 → 0.4.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/README.md +17 -24
- package/README.zh.md +17 -24
- package/lib/client.js +349 -95
- package/lib/client.js.map +1 -1
- package/lib/index.js +329 -73
- package/package.json +11 -10
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: "字体",
|
|
@@ -78,7 +77,6 @@ window.__ModuleLoader__.load({
|
|
|
78
77
|
strokeTitle: "文字描边",
|
|
79
78
|
strokeHint: "按区域为文字添加描边:粗细为 0 表示关闭,「自动」会生成与文字颜色相反的颜色,「主题色」跟随当前主题色。代码块与图标已自动豁免,多色语法不会糊成一团",
|
|
80
79
|
strokeWidth: "描边粗细",
|
|
81
|
-
strokeColor: "描边颜色",
|
|
82
80
|
strokeAuto: "自动反色",
|
|
83
81
|
strokeGray: "灰色",
|
|
84
82
|
strokeBlack: "黑色",
|
|
@@ -118,7 +116,6 @@ window.__ModuleLoader__.load({
|
|
|
118
116
|
editorReset: "重置",
|
|
119
117
|
extractColor: "从背景提取主题色",
|
|
120
118
|
extracting: "取色中…",
|
|
121
|
-
extractNoWp: "请先设置背景图片",
|
|
122
119
|
extractDone: "已应用背景主色调",
|
|
123
120
|
extractFail: "未在背景中找到鲜明的颜色,请换一张背景",
|
|
124
121
|
crashTitle: "界面渲染出错",
|
|
@@ -138,7 +135,6 @@ window.__ModuleLoader__.load({
|
|
|
138
135
|
pickerTitle: "从背景取色",
|
|
139
136
|
pickerHint: "移动鼠标预览颜色,点击背景选取为主题色",
|
|
140
137
|
pickerClose: "关闭",
|
|
141
|
-
bgTypeImage: "图片",
|
|
142
138
|
bgTypeMesh: "网格渐变",
|
|
143
139
|
bgTypeShader: "Shader",
|
|
144
140
|
bgTypePattern: "几何图案",
|
|
@@ -194,8 +190,12 @@ window.__ModuleLoader__.load({
|
|
|
194
190
|
profileNamePlaceholder: "输入配置名称…",
|
|
195
191
|
profileNameRequired: "请先输入配置名称",
|
|
196
192
|
profileSaved: "配置已保存",
|
|
193
|
+
profileRenamed: "配置已重命名",
|
|
194
|
+
profileDuplicated: "配置已复制",
|
|
197
195
|
profileApplied: "配置已应用",
|
|
198
196
|
profileApply: "应用",
|
|
197
|
+
profileRename: "重命名",
|
|
198
|
+
profileDuplicate: "复制配置",
|
|
199
199
|
profileDelete: "删除配置",
|
|
200
200
|
profileDeleteConfirm: "确认删除",
|
|
201
201
|
profileNone: "不切换",
|
|
@@ -218,6 +218,7 @@ window.__ModuleLoader__.load({
|
|
|
218
218
|
rotShuffle: "随机",
|
|
219
219
|
rotOrder: "顺序",
|
|
220
220
|
rotReload: "每次刷新",
|
|
221
|
+
rotHourly: "每小时",
|
|
221
222
|
rotDaily: "每天",
|
|
222
223
|
rotWeekly: "每周",
|
|
223
224
|
rotNow: "立即切换"
|
|
@@ -225,7 +226,6 @@ window.__ModuleLoader__.load({
|
|
|
225
226
|
const en = {
|
|
226
227
|
nav: "Theme",
|
|
227
228
|
brandTag: "Appearance",
|
|
228
|
-
close: "Close",
|
|
229
229
|
pageColor: "Color",
|
|
230
230
|
pageInterface: "Interface",
|
|
231
231
|
pageFont: "Font",
|
|
@@ -256,7 +256,7 @@ window.__ModuleLoader__.load({
|
|
|
256
256
|
uiHeader: "Header popovers",
|
|
257
257
|
uiHeaderBar: "Header bar",
|
|
258
258
|
captionInkLabel: "Caption strip text",
|
|
259
|
-
captionInkHint: "Who paints the text on the desktop caption strip — the native
|
|
259
|
+
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
260
|
captionInkAuto: "Auto",
|
|
261
261
|
captionInkTheme: "Follow theme",
|
|
262
262
|
captionInkWhite: "Always white",
|
|
@@ -276,7 +276,6 @@ window.__ModuleLoader__.load({
|
|
|
276
276
|
strokeTitle: "Text outline",
|
|
277
277
|
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
278
|
strokeWidth: "Outline width",
|
|
279
|
-
strokeColor: "Outline color",
|
|
280
279
|
strokeAuto: "Auto contrast",
|
|
281
280
|
strokeGray: "Gray",
|
|
282
281
|
strokeBlack: "Black",
|
|
@@ -316,7 +315,6 @@ window.__ModuleLoader__.load({
|
|
|
316
315
|
editorReset: "Reset",
|
|
317
316
|
extractColor: "Extract from background",
|
|
318
317
|
extracting: "Extracting…",
|
|
319
|
-
extractNoWp: "Set a background first",
|
|
320
318
|
extractDone: "Background color applied",
|
|
321
319
|
extractFail: "No vivid color found in this background, try another",
|
|
322
320
|
crashTitle: "Section crashed",
|
|
@@ -336,7 +334,6 @@ window.__ModuleLoader__.load({
|
|
|
336
334
|
pickerTitle: "Pick from background",
|
|
337
335
|
pickerHint: "Hover to preview, click to pick as theme color",
|
|
338
336
|
pickerClose: "Close",
|
|
339
|
-
bgTypeImage: "Image",
|
|
340
337
|
bgTypeMesh: "Mesh gradient",
|
|
341
338
|
bgTypeShader: "Shader",
|
|
342
339
|
bgTypePattern: "Pattern",
|
|
@@ -392,8 +389,12 @@ window.__ModuleLoader__.load({
|
|
|
392
389
|
profileNamePlaceholder: "Profile name…",
|
|
393
390
|
profileNameRequired: "Enter a name first",
|
|
394
391
|
profileSaved: "Profile saved",
|
|
392
|
+
profileRenamed: "Profile renamed",
|
|
393
|
+
profileDuplicated: "Profile duplicated",
|
|
395
394
|
profileApplied: "Profile applied",
|
|
396
395
|
profileApply: "Apply",
|
|
396
|
+
profileRename: "Rename",
|
|
397
|
+
profileDuplicate: "Duplicate",
|
|
397
398
|
profileDelete: "Delete profile",
|
|
398
399
|
profileDeleteConfirm: "Sure?",
|
|
399
400
|
profileNone: "None",
|
|
@@ -416,6 +417,7 @@ window.__ModuleLoader__.load({
|
|
|
416
417
|
rotShuffle: "Shuffle",
|
|
417
418
|
rotOrder: "In order",
|
|
418
419
|
rotReload: "Every refresh",
|
|
420
|
+
rotHourly: "Hourly",
|
|
419
421
|
rotDaily: "Daily",
|
|
420
422
|
rotWeekly: "Weekly",
|
|
421
423
|
rotNow: "Switch now"
|
|
@@ -902,7 +904,7 @@ window.__ModuleLoader__.load({
|
|
|
902
904
|
return {
|
|
903
905
|
enabled: r.enabled === true,
|
|
904
906
|
mode: r.mode === "order" ? "order" : "shuffle",
|
|
905
|
-
interval: r.interval === "reload" || r.interval === "weekly" ? r.interval : "daily",
|
|
907
|
+
interval: r.interval === "reload" || r.interval === "hourly" || r.interval === "weekly" ? r.interval : "daily",
|
|
906
908
|
current: num(r.current, 0),
|
|
907
909
|
items,
|
|
908
910
|
lastRotate: typeof r.lastRotate === "string" ? r.lastRotate : null
|
|
@@ -1087,6 +1089,10 @@ window.__ModuleLoader__.load({
|
|
|
1087
1089
|
{
|
|
1088
1090
|
channel: "0.2.1",
|
|
1089
1091
|
release: "0.2.1-rc.2"
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
channel: "0.2.1",
|
|
1095
|
+
release: "0.2.1-rc.3"
|
|
1090
1096
|
}
|
|
1091
1097
|
];
|
|
1092
1098
|
const UNKNOWN_HOST_INFO = {
|
|
@@ -1563,7 +1569,7 @@ window.__ModuleLoader__.load({
|
|
|
1563
1569
|
//#region src/client/host-compat/versions/v0-2-1/adapter.ts
|
|
1564
1570
|
/**
|
|
1565
1571
|
* Adapter for the DSH `0.2.1` patch line, verified at `0.2.1-alpha.1`,
|
|
1566
|
-
* `alpha.2`, `alpha.3`, `alpha.4`, `alpha.5`, `rc.1` and `rc.
|
|
1572
|
+
* `alpha.2`, `alpha.3`, `alpha.4`, `alpha.5`, `rc.1`, `rc.2` and `rc.3`.
|
|
1567
1573
|
*
|
|
1568
1574
|
* A new MINOR line opened with a prerelease, and the answer is the `0.2.0-rc.2`
|
|
1569
1575
|
* folder's — which is a statement this file has to earn, because "same as the
|
|
@@ -1587,6 +1593,34 @@ window.__ModuleLoader__.load({
|
|
|
1587
1593
|
* · `TextShimmer` still marks its root `data-shimmer`, so the stroke exemption
|
|
1588
1594
|
* written for rc.1 stands.
|
|
1589
1595
|
*
|
|
1596
|
+
* Re-checked at `0.2.1-rc.3` (`git diff rc.2 rc.3` on every file above), the
|
|
1597
|
+
* facts hold untouched: `design-platform.css`, both menu stylesheets and
|
|
1598
|
+
* `MenuGroup.tsx` byte-identical; `SidebarRight.module.css`, `TabLayout.tsx`,
|
|
1599
|
+
* `base.css`, `gradient-shadow-text.css`, `AppFrame.*`, `InlineEditor.module.css`,
|
|
1600
|
+
* `TextShimmer`, `JobListAction`, `TaskMenu`, `scoped-slots` and
|
|
1601
|
+
* `PluginManagerPage.tsx` all unchanged, and the five header slot keys stand.
|
|
1602
|
+
* What rc.3 did move, and why none of it needs a fact from this folder:
|
|
1603
|
+
* · `ui-primitives/Menu.tsx` is ADDITIVE — a `current` prop that renders
|
|
1604
|
+
* `aria-current="page"` and a `minWidthFromAnchor` switch that sets a
|
|
1605
|
+
* `min-width` style in portal mode. No stylesheet moved with it
|
|
1606
|
+
* (`MenuSurface.module.css` is byte-identical), so the menu paint and the
|
|
1607
|
+
* frost stay exactly where the facts say.
|
|
1608
|
+
* · the guide (`GuideBody.tsx`) and the sidebar panel list (`SidebarRoot.tsx`)
|
|
1609
|
+
* grow pin state and a More menu AROUND the existing registrations — guide
|
|
1610
|
+
* entries still arrive through `useGuideEntries`, panels through
|
|
1611
|
+
* `selectPanel`, and the new menus mount empty until something registers
|
|
1612
|
+
* into their `*.actions` slots, which this plugin does not. The tab
|
|
1613
|
+
* registry itself (`shell/`), which `native-tab.tsx` speaks to, is untouched.
|
|
1614
|
+
* · the session header's View tab row gained its own More menu
|
|
1615
|
+
* (`conversation.session.header.views.actions` slot). It mounts through
|
|
1616
|
+
* `<Menu … portal>` to `document.body`, so `header-tag.ts` already claims
|
|
1617
|
+
* it through the portaled arm — the trigger sits in the header and reports
|
|
1618
|
+
* `aria-expanded`, which is all that arm asks. The five in-place header
|
|
1619
|
+
* slot anchors are unchanged.
|
|
1620
|
+
* · `ConversationRoot.module.css` re-laid the View tab row (wrap, gaps, the
|
|
1621
|
+
* More launcher's classes). Layout inside the header, not a surface the
|
|
1622
|
+
* plugin styles by these class names.
|
|
1623
|
+
*
|
|
1590
1624
|
* Re-checked again across the rest of the line (`alpha.3` … `rc.2`, `git diff
|
|
1591
1625
|
* <tag> rc.2` on every file above): the menu token contract, both menu stylesheets
|
|
1592
1626
|
* (`MenuSurface`, `MenuGroup`), `gradient-shadow-text.css` / `base.css`,
|
|
@@ -1678,7 +1712,7 @@ window.__ModuleLoader__.load({
|
|
|
1678
1712
|
}
|
|
1679
1713
|
function createAdapter$1(host) {
|
|
1680
1714
|
return {
|
|
1681
|
-
id: "0.2.1-alpha.1…rc.
|
|
1715
|
+
id: "0.2.1-alpha.1…rc.3",
|
|
1682
1716
|
channel: "0.2.1",
|
|
1683
1717
|
host,
|
|
1684
1718
|
panelFragments: panelFragments$1,
|
|
@@ -1847,22 +1881,44 @@ window.__ModuleLoader__.load({
|
|
|
1847
1881
|
}
|
|
1848
1882
|
const SAVE_DEBOUNCE_MS = 250;
|
|
1849
1883
|
let saveTimer;
|
|
1884
|
+
/** Write the config and SURFACE a failed persist. The server answers
|
|
1885
|
+
* `ok:true` with `value:false` when the DISK write failed — the transport
|
|
1886
|
+
* succeeded, so `rpcCall` returns the boolean without a word and every caller
|
|
1887
|
+
* used to assume the save had landed. Retry once after a beat (the common
|
|
1888
|
+
* Windows cause is a transient EBUSY from an antivirus scan holding the
|
|
1889
|
+
* file), then say it in the log so a "settings reverted" report has a trail. */
|
|
1890
|
+
let writeRetryQueued = false;
|
|
1891
|
+
async function writeNow() {
|
|
1892
|
+
if (await rpcCall("writeConfig", { config: cfg }) === true) {
|
|
1893
|
+
writeRetryQueued = false;
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (!writeRetryQueued) {
|
|
1897
|
+
writeRetryQueued = true;
|
|
1898
|
+
window.setTimeout(() => {
|
|
1899
|
+
writeNow();
|
|
1900
|
+
}, 1e3);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
writeRetryQueued = false;
|
|
1904
|
+
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)");
|
|
1905
|
+
}
|
|
1850
1906
|
function saveConfig() {
|
|
1851
1907
|
if (saveTimer !== void 0) window.clearTimeout(saveTimer);
|
|
1852
1908
|
saveTimer = window.setTimeout(() => {
|
|
1853
1909
|
saveTimer = void 0;
|
|
1854
|
-
|
|
1910
|
+
writeNow();
|
|
1855
1911
|
}, SAVE_DEBOUNCE_MS);
|
|
1856
1912
|
}
|
|
1857
1913
|
function flushSave() {
|
|
1858
1914
|
if (saveTimer === void 0) return;
|
|
1859
1915
|
window.clearTimeout(saveTimer);
|
|
1860
1916
|
saveTimer = void 0;
|
|
1861
|
-
|
|
1917
|
+
writeNow();
|
|
1862
1918
|
}
|
|
1863
1919
|
/** Persist the current config immediately (import path — no debounce). */
|
|
1864
1920
|
function persistConfig() {
|
|
1865
|
-
|
|
1921
|
+
writeNow();
|
|
1866
1922
|
}
|
|
1867
1923
|
/** Load the persisted theme (config + wallpaper URL + video URL) from the node
|
|
1868
1924
|
* half. Image and video both travel as serve URLs — never bytes — so this RPC
|
|
@@ -2060,6 +2116,10 @@ window.__ModuleLoader__.load({
|
|
|
2060
2116
|
async function removeFont() {
|
|
2061
2117
|
return await rpcCall("removeFont", {}) === true;
|
|
2062
2118
|
}
|
|
2119
|
+
/** Serve URL of one rotation-pool image (the export path fetches its bytes). */
|
|
2120
|
+
function rotationFileUrl(file) {
|
|
2121
|
+
return `/dsh-any-background/rotation/file?f=${encodeURIComponent(file)}`;
|
|
2122
|
+
}
|
|
2063
2123
|
/** Add an image (data URL + small thumbnail) to the server-side rotation pool. */
|
|
2064
2124
|
async function rotationAdd(dataUrl, thumb) {
|
|
2065
2125
|
if (!rpcCallFn) return {
|
|
@@ -2133,6 +2193,32 @@ window.__ModuleLoader__.load({
|
|
|
2133
2193
|
};
|
|
2134
2194
|
}
|
|
2135
2195
|
}
|
|
2196
|
+
/** Replace the whole server-side rotation pool (theme-import path): the bytes
|
|
2197
|
+
* land under their exported file names, so the imported config's pool
|
|
2198
|
+
* metadata is live on this machine instead of pointing at files it does not
|
|
2199
|
+
* have. */
|
|
2200
|
+
async function rotationRestore(items) {
|
|
2201
|
+
if (!rpcCallFn) return {
|
|
2202
|
+
ok: false,
|
|
2203
|
+
error: "rpc not ready"
|
|
2204
|
+
};
|
|
2205
|
+
try {
|
|
2206
|
+
const res = await rpcCallFn(rpcEndpoint("rotationRestore"), { items });
|
|
2207
|
+
if (res && res.ok === true) return res.value ?? {
|
|
2208
|
+
ok: false,
|
|
2209
|
+
error: "no value"
|
|
2210
|
+
};
|
|
2211
|
+
return {
|
|
2212
|
+
ok: false,
|
|
2213
|
+
error: res?.error?.message ?? "request failed"
|
|
2214
|
+
};
|
|
2215
|
+
} catch (e) {
|
|
2216
|
+
return {
|
|
2217
|
+
ok: false,
|
|
2218
|
+
error: e instanceof Error ? e.message : String(e)
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2136
2222
|
//#endregion
|
|
2137
2223
|
//#region src/client/host-compat/capabilities.ts
|
|
2138
2224
|
/**
|
|
@@ -2156,6 +2242,32 @@ window.__ModuleLoader__.load({
|
|
|
2156
2242
|
return rHostAdapter();
|
|
2157
2243
|
}
|
|
2158
2244
|
//#endregion
|
|
2245
|
+
//#region src/client/dom-watch.ts
|
|
2246
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2247
|
+
let observer = null;
|
|
2248
|
+
/** Subscribe to body-subtree mutations. Returns an unsubscribe. */
|
|
2249
|
+
function watchDom(cb) {
|
|
2250
|
+
listeners.add(cb);
|
|
2251
|
+
if (observer === null && typeof MutationObserver !== "undefined" && typeof document !== "undefined") {
|
|
2252
|
+
observer = new MutationObserver((records) => {
|
|
2253
|
+
for (const l of listeners) l(records);
|
|
2254
|
+
});
|
|
2255
|
+
observer.observe(document.body, {
|
|
2256
|
+
childList: true,
|
|
2257
|
+
subtree: true,
|
|
2258
|
+
attributes: true,
|
|
2259
|
+
attributeFilter: ["aria-expanded"]
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
return () => {
|
|
2263
|
+
listeners.delete(cb);
|
|
2264
|
+
if (listeners.size === 0) {
|
|
2265
|
+
observer?.disconnect();
|
|
2266
|
+
observer = null;
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
//#endregion
|
|
2159
2271
|
//#region src/client/header-tag.ts
|
|
2160
2272
|
/**
|
|
2161
2273
|
* Header-surface tagging.
|
|
@@ -2260,7 +2372,6 @@ window.__ModuleLoader__.load({
|
|
|
2260
2372
|
if (el.getAttribute("aria-modal") === "true") return false;
|
|
2261
2373
|
return el.parentElement === document.body || el.closest(headerSlotAnchors()) !== null;
|
|
2262
2374
|
}
|
|
2263
|
-
let observer = null;
|
|
2264
2375
|
let pending = null;
|
|
2265
2376
|
/** False once the effect is torn down, so a pass already queued on the microtask queue
|
|
2266
2377
|
* cannot re-mark a popover after the teardown cleared it. */
|
|
@@ -2301,17 +2412,10 @@ window.__ModuleLoader__.load({
|
|
|
2301
2412
|
if (typeof MutationObserver === "undefined" || typeof document === "undefined") return () => {};
|
|
2302
2413
|
active = true;
|
|
2303
2414
|
refreshHeaderPopovers();
|
|
2304
|
-
|
|
2305
|
-
observer.observe(document.body, {
|
|
2306
|
-
childList: true,
|
|
2307
|
-
subtree: true,
|
|
2308
|
-
attributes: true,
|
|
2309
|
-
attributeFilter: ["aria-expanded"]
|
|
2310
|
-
});
|
|
2415
|
+
const dispose = watchDom(() => schedule());
|
|
2311
2416
|
return () => {
|
|
2312
2417
|
active = false;
|
|
2313
|
-
|
|
2314
|
-
observer = null;
|
|
2418
|
+
dispose();
|
|
2315
2419
|
for (const el of document.querySelectorAll(`[${HEADER_POPOVER_ATTR}]`)) el.removeAttribute(HEADER_POPOVER_ATTR);
|
|
2316
2420
|
};
|
|
2317
2421
|
}
|
|
@@ -4734,7 +4838,8 @@ window.__ModuleLoader__.load({
|
|
|
4734
4838
|
*
|
|
4735
4839
|
* Bound by the CSS-Module class shape, same as `[class*="_ioCard"]`: the built
|
|
4736
4840
|
* bundle names these `pI_x6G_sidebarCol` — prefix first, local name last, and the
|
|
4737
|
-
* pair is identical across `0.2.0-rc.1`, `0.2.1-alpha.3
|
|
4841
|
+
* pair is identical across `0.2.0-rc.1`, `0.2.1-alpha.3`, `0.2.1-rc.2` and
|
|
4842
|
+
* `0.2.1-rc.3` (`AppFrame.module.css` byte-identical rc.2→rc.3). A build
|
|
4738
4843
|
* that renames the track simply stops matching, which is the untouched host look. */
|
|
4739
4844
|
const SIDEBAR_ONE_LAYER_RULE = "[class*=\"_sidebarCol\"] *{--dsw-specific-sidebar-fill:transparent}";
|
|
4740
4845
|
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)}`;
|
|
@@ -5143,8 +5248,11 @@ window.__ModuleLoader__.load({
|
|
|
5143
5248
|
});
|
|
5144
5249
|
syncTableFix();
|
|
5145
5250
|
}
|
|
5146
|
-
let
|
|
5147
|
-
let
|
|
5251
|
+
let partsDispose = null;
|
|
5252
|
+
let partsPending = null;
|
|
5253
|
+
/** False once stopped, so a pass already queued on the microtask queue cannot
|
|
5254
|
+
* re-style anything after the teardown. */
|
|
5255
|
+
let partsActive = false;
|
|
5148
5256
|
/** Signature of the last blur+opacity pass we actually wrote to the DOM. */
|
|
5149
5257
|
let appliedPartsKey = "";
|
|
5150
5258
|
/** The surfaces the last pass styled, compared BY IDENTITY: the host can swap
|
|
@@ -5184,11 +5292,13 @@ window.__ModuleLoader__.load({
|
|
|
5184
5292
|
/** Watch for the AppFrame mounting so persisted blurs land even when the shell
|
|
5185
5293
|
* renders after this plugin's apply. */
|
|
5186
5294
|
function watchParts() {
|
|
5187
|
-
if (
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5295
|
+
if (partsDispose !== null || typeof MutationObserver === "undefined") return;
|
|
5296
|
+
partsActive = true;
|
|
5297
|
+
partsDispose = watchDom(() => {
|
|
5298
|
+
if (partsPending !== null || !partsActive) return;
|
|
5299
|
+
partsPending = Promise.resolve().then(() => {
|
|
5300
|
+
partsPending = null;
|
|
5301
|
+
if (!partsActive) return;
|
|
5192
5302
|
const blurs = rBlurs();
|
|
5193
5303
|
const ops = rOps();
|
|
5194
5304
|
const key = JSON.stringify([blurs, ops]);
|
|
@@ -5199,18 +5309,12 @@ window.__ModuleLoader__.load({
|
|
|
5199
5309
|
appliedTargets = targetsNow();
|
|
5200
5310
|
});
|
|
5201
5311
|
});
|
|
5202
|
-
partsObserver.observe(document.body, {
|
|
5203
|
-
childList: true,
|
|
5204
|
-
subtree: true
|
|
5205
|
-
});
|
|
5206
5312
|
}
|
|
5207
5313
|
function stopWatchingParts() {
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
partsApplyRaf = 0;
|
|
5213
|
-
}
|
|
5314
|
+
partsDispose?.();
|
|
5315
|
+
partsDispose = null;
|
|
5316
|
+
partsActive = false;
|
|
5317
|
+
partsPending = null;
|
|
5214
5318
|
appliedPartsKey = "";
|
|
5215
5319
|
appliedTargets = [];
|
|
5216
5320
|
markerSeen.clear();
|
|
@@ -5523,8 +5627,6 @@ window.__ModuleLoader__.load({
|
|
|
5523
5627
|
applyTrajectoryOverrides(rTrajectoryOpacity());
|
|
5524
5628
|
}
|
|
5525
5629
|
applyPanelOverrides(rPanelOpacity());
|
|
5526
|
-
applyProduced();
|
|
5527
|
-
applyHeaderPopovers();
|
|
5528
5630
|
applyExemptDefaults();
|
|
5529
5631
|
applyPartBlurs(rBlurs());
|
|
5530
5632
|
applyStrokes();
|
|
@@ -8760,6 +8862,12 @@ window.__ModuleLoader__.load({
|
|
|
8760
8862
|
onClick: () => setRotation({ interval: "reload" }),
|
|
8761
8863
|
children: t("rotReload")
|
|
8762
8864
|
}),
|
|
8865
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8866
|
+
type: "button",
|
|
8867
|
+
className: `dab-chip${rotation.interval === "hourly" ? " is-active" : ""}`,
|
|
8868
|
+
onClick: () => setRotation({ interval: "hourly" }),
|
|
8869
|
+
children: t("rotHourly")
|
|
8870
|
+
}),
|
|
8763
8871
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8764
8872
|
type: "button",
|
|
8765
8873
|
className: `dab-chip${rotation.interval === "daily" ? " is-active" : ""}`,
|
|
@@ -9289,7 +9397,7 @@ window.__ModuleLoader__.load({
|
|
|
9289
9397
|
});
|
|
9290
9398
|
}
|
|
9291
9399
|
function ProfilePage({ p, notify }) {
|
|
9292
|
-
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, applyPreset, setSchedule, useStore } = p;
|
|
9400
|
+
const { t, exportTheme, importTheme, saveProfile, applyProfile, deleteProfile, renameProfile, duplicateProfile, applyPreset, setSchedule, useStore } = p;
|
|
9293
9401
|
const profiles = useStore((s) => s.profiles);
|
|
9294
9402
|
const schedule = useStore((s) => s.schedule);
|
|
9295
9403
|
const activeProfile = useStore((s) => s.activeProfile);
|
|
@@ -9297,6 +9405,8 @@ window.__ModuleLoader__.load({
|
|
|
9297
9405
|
const [nameOpen, setNameOpen] = (0, react.useState)(false);
|
|
9298
9406
|
const [nameVal, setNameVal] = (0, react.useState)("");
|
|
9299
9407
|
const [confirmId, setConfirmId] = (0, react.useState)(null);
|
|
9408
|
+
const [editId, setEditId] = (0, react.useState)(null);
|
|
9409
|
+
const [editVal, setEditVal] = (0, react.useState)("");
|
|
9300
9410
|
const onImport = async (file) => {
|
|
9301
9411
|
try {
|
|
9302
9412
|
const ok = await importTheme(file);
|
|
@@ -9325,6 +9435,14 @@ window.__ModuleLoader__.load({
|
|
|
9325
9435
|
setConfirmId(null);
|
|
9326
9436
|
deleteProfile(entry.id);
|
|
9327
9437
|
};
|
|
9438
|
+
const onRenameCommit = (entry) => {
|
|
9439
|
+
const name = editVal.trim();
|
|
9440
|
+
setEditId(null);
|
|
9441
|
+
if (name && name !== entry.name && renameProfile(entry.id, name)) notify(t("profileRenamed"), true);
|
|
9442
|
+
};
|
|
9443
|
+
const onDuplicate = (entry) => {
|
|
9444
|
+
if (duplicateProfile(entry.id)) notify(t("profileDuplicated"), true);
|
|
9445
|
+
};
|
|
9328
9446
|
const profileOptions = (value, onChange) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
9329
9447
|
className: "dab-select",
|
|
9330
9448
|
value: value ?? "",
|
|
@@ -9456,8 +9574,42 @@ window.__ModuleLoader__.load({
|
|
|
9456
9574
|
children: profiles.map((entry, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9457
9575
|
className: `dab-profile-row${activeProfile === entry.id ? " is-active" : ""}`,
|
|
9458
9576
|
style: { "--i": i },
|
|
9459
|
-
children: [
|
|
9460
|
-
|
|
9577
|
+
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", {
|
|
9578
|
+
className: "dab-urlrow",
|
|
9579
|
+
style: {
|
|
9580
|
+
flex: 1,
|
|
9581
|
+
minWidth: 0
|
|
9582
|
+
},
|
|
9583
|
+
children: [
|
|
9584
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
9585
|
+
type: "text",
|
|
9586
|
+
className: "dab-urlinput",
|
|
9587
|
+
value: editVal,
|
|
9588
|
+
maxLength: 60,
|
|
9589
|
+
onChange: (e) => setEditVal(e.target.value),
|
|
9590
|
+
onKeyDown: (e) => {
|
|
9591
|
+
if (e.key === "Enter") {
|
|
9592
|
+
e.preventDefault();
|
|
9593
|
+
onRenameCommit(entry);
|
|
9594
|
+
}
|
|
9595
|
+
},
|
|
9596
|
+
autoFocus: true
|
|
9597
|
+
}),
|
|
9598
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9599
|
+
type: "button",
|
|
9600
|
+
className: "dab-btn dab-btn-primary",
|
|
9601
|
+
disabled: !editVal.trim(),
|
|
9602
|
+
onClick: () => onRenameCommit(entry),
|
|
9603
|
+
children: t("editorCommit")
|
|
9604
|
+
}),
|
|
9605
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9606
|
+
type: "button",
|
|
9607
|
+
className: "dab-btn",
|
|
9608
|
+
onClick: () => setEditId(null),
|
|
9609
|
+
children: t("bgUrlCancel")
|
|
9610
|
+
})
|
|
9611
|
+
]
|
|
9612
|
+
}) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
9461
9613
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9462
9614
|
className: "dab-profile-meta",
|
|
9463
9615
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -9474,6 +9626,23 @@ window.__ModuleLoader__.load({
|
|
|
9474
9626
|
onClick: () => onApply(entry),
|
|
9475
9627
|
children: t("profileApply")
|
|
9476
9628
|
}),
|
|
9629
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9630
|
+
type: "button",
|
|
9631
|
+
className: "dab-btn",
|
|
9632
|
+
title: t("profileRename"),
|
|
9633
|
+
onClick: () => {
|
|
9634
|
+
setEditId(entry.id);
|
|
9635
|
+
setEditVal(entry.name);
|
|
9636
|
+
},
|
|
9637
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EditIcon, { size: 13 })
|
|
9638
|
+
}),
|
|
9639
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9640
|
+
type: "button",
|
|
9641
|
+
className: "dab-btn",
|
|
9642
|
+
title: t("profileDuplicate"),
|
|
9643
|
+
onClick: () => onDuplicate(entry),
|
|
9644
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlusIcon, { size: 13 })
|
|
9645
|
+
}),
|
|
9477
9646
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9478
9647
|
type: "button",
|
|
9479
9648
|
className: `dab-btn dab-btn-danger-solid${confirmId === entry.id ? " dab-btn-confirm" : ""}`,
|
|
@@ -9481,7 +9650,7 @@ window.__ModuleLoader__.load({
|
|
|
9481
9650
|
onClick: () => onDelete(entry),
|
|
9482
9651
|
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")
|
|
9483
9652
|
})
|
|
9484
|
-
]
|
|
9653
|
+
] })]
|
|
9485
9654
|
}, entry.id))
|
|
9486
9655
|
})
|
|
9487
9656
|
]
|
|
@@ -10015,9 +10184,9 @@ window.__ModuleLoader__.load({
|
|
|
10015
10184
|
* unsubscribe for plugin teardown. */
|
|
10016
10185
|
function startBetterSidebarWatch() {
|
|
10017
10186
|
sync();
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10187
|
+
return watchDom((records) => {
|
|
10188
|
+
if (records.some((r) => r.target === document.body && r.type === "childList")) sync();
|
|
10189
|
+
});
|
|
10021
10190
|
}
|
|
10022
10191
|
let nativeTabs = false;
|
|
10023
10192
|
const nativeSubs = /* @__PURE__ */ new Set();
|
|
@@ -10138,6 +10307,50 @@ window.__ModuleLoader__.load({
|
|
|
10138
10307
|
});
|
|
10139
10308
|
}
|
|
10140
10309
|
//#endregion
|
|
10310
|
+
//#region src/shared/rotation.ts
|
|
10311
|
+
/** ISO week key (Monday-first). */
|
|
10312
|
+
function isoWeekKey(d) {
|
|
10313
|
+
const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
|
|
10314
|
+
const day = t.getUTCDay() || 7;
|
|
10315
|
+
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
10316
|
+
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
10317
|
+
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
10318
|
+
return `${t.getUTCFullYear()}-W${week}`;
|
|
10319
|
+
}
|
|
10320
|
+
/** Whether the pool should advance right now. */
|
|
10321
|
+
function rotationDue(rot, now = /* @__PURE__ */ new Date()) {
|
|
10322
|
+
if (!rot.enabled || rot.items.length === 0) return false;
|
|
10323
|
+
if (rot.interval === "reload") return true;
|
|
10324
|
+
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
10325
|
+
if (last === null || isNaN(last.getTime())) return true;
|
|
10326
|
+
if (rot.interval === "hourly") return hourKey(last) !== hourKey(now);
|
|
10327
|
+
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
10328
|
+
return isoWeekKey(last) !== isoWeekKey(now);
|
|
10329
|
+
}
|
|
10330
|
+
/** Epoch-hour bucket: hourly cadence advances on each full hour boundary
|
|
10331
|
+
* (in whole-hour timezones — UTC+8 among them — that is exactly the local
|
|
10332
|
+
* on-the-hour tick). */
|
|
10333
|
+
const hourKey = (d) => Math.floor(d.getTime() / 36e5);
|
|
10334
|
+
/** Whether an OPEN page should keep advancing this cadence on its timer.
|
|
10335
|
+
* Only the time-based cadences tick live; 'reload' is a once-per-READ pick
|
|
10336
|
+
* (the server advances it inside the `read` itself), so a live tick driving
|
|
10337
|
+
* it would flip the wallpaper every 30 s — exactly the v0.4.0 regression
|
|
10338
|
+
* (rotationDue is unconditionally true for 'reload'). */
|
|
10339
|
+
function rotationAdvancesLive(rot) {
|
|
10340
|
+
return rot.interval !== "reload";
|
|
10341
|
+
}
|
|
10342
|
+
/** Pick the next pool index under the configured mode. */
|
|
10343
|
+
function pickNextRotationIndex(rot) {
|
|
10344
|
+
const n = rot.items.length;
|
|
10345
|
+
if (n === 0) return -1;
|
|
10346
|
+
if (rot.mode === "shuffle" && n > 1) {
|
|
10347
|
+
let idx = rot.current;
|
|
10348
|
+
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
10349
|
+
return idx;
|
|
10350
|
+
}
|
|
10351
|
+
return (rot.current + 1) % n;
|
|
10352
|
+
}
|
|
10353
|
+
//#endregion
|
|
10141
10354
|
//#region src/client/index.tsx
|
|
10142
10355
|
/**
|
|
10143
10356
|
* dsh-any-background — browser half entry.
|
|
@@ -10403,38 +10616,11 @@ window.__ModuleLoader__.load({
|
|
|
10403
10616
|
syncMetaNow();
|
|
10404
10617
|
return true;
|
|
10405
10618
|
};
|
|
10406
|
-
const
|
|
10407
|
-
|
|
10408
|
-
const day = t.getUTCDay() || 7;
|
|
10409
|
-
t.setUTCDate(t.getUTCDate() + 4 - day);
|
|
10410
|
-
const yearStart = new Date(Date.UTC(t.getUTCFullYear(), 0, 1));
|
|
10411
|
-
const week = Math.ceil(((t.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
10412
|
-
return `${t.getUTCFullYear()}-W${week}`;
|
|
10413
|
-
};
|
|
10414
|
-
const rotationDue = () => {
|
|
10415
|
-
const rot = rRotation();
|
|
10416
|
-
if (!rot.enabled || rot.items.length === 0) return false;
|
|
10417
|
-
if (rot.interval === "reload") return true;
|
|
10418
|
-
const last = rot.lastRotate !== null ? new Date(rot.lastRotate) : null;
|
|
10419
|
-
if (last === null || isNaN(last.getTime())) return true;
|
|
10420
|
-
const now = /* @__PURE__ */ new Date();
|
|
10421
|
-
if (rot.interval === "daily") return last.toDateString() !== now.toDateString();
|
|
10422
|
-
return isoWeekKey(last) !== isoWeekKey(now);
|
|
10423
|
-
};
|
|
10424
|
-
const pickNextRotationIndex = () => {
|
|
10425
|
-
const rot = rRotation();
|
|
10426
|
-
const n = rot.items.length;
|
|
10427
|
-
if (n === 0) return -1;
|
|
10428
|
-
if (rot.mode === "shuffle" && n > 1) {
|
|
10429
|
-
let idx = rot.current;
|
|
10430
|
-
while (idx === rot.current) idx = Math.floor(Math.random() * n);
|
|
10431
|
-
return idx;
|
|
10432
|
-
}
|
|
10433
|
-
return (rot.current + 1) % n;
|
|
10434
|
-
};
|
|
10619
|
+
const rotationDue$1 = () => rotationDue(rRotation());
|
|
10620
|
+
const pickNextRotationIndex$1 = () => pickNextRotationIndex(rRotation());
|
|
10435
10621
|
const maybeRotate = async () => {
|
|
10436
|
-
if (!rotationDue()) return;
|
|
10437
|
-
await applyRotationIndex(pickNextRotationIndex(), true);
|
|
10622
|
+
if (!rotationDue$1()) return;
|
|
10623
|
+
await applyRotationIndex(pickNextRotationIndex$1(), true);
|
|
10438
10624
|
};
|
|
10439
10625
|
const parseHHMM = (s) => {
|
|
10440
10626
|
const m = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(s);
|
|
@@ -10450,6 +10636,7 @@ window.__ModuleLoader__.load({
|
|
|
10450
10636
|
return cur >= night && cur < day;
|
|
10451
10637
|
};
|
|
10452
10638
|
const scheduleTick = () => {
|
|
10639
|
+
if (rotationAdvancesLive(rRotation())) maybeRotate();
|
|
10453
10640
|
const sc = rSchedule();
|
|
10454
10641
|
if (!sc.enabled) return;
|
|
10455
10642
|
const want = (sc.mode === "system" ? window.matchMedia?.("(prefers-color-scheme: dark)")?.matches ?? false : isNightNow(sc)) ? sc.nightProfile : sc.dayProfile;
|
|
@@ -10784,12 +10971,32 @@ window.__ModuleLoader__.load({
|
|
|
10784
10971
|
}
|
|
10785
10972
|
}
|
|
10786
10973
|
}
|
|
10974
|
+
let pool;
|
|
10975
|
+
const rotItems = rRotation().items;
|
|
10976
|
+
if (rotItems.length > 0) {
|
|
10977
|
+
pool = [];
|
|
10978
|
+
for (const it of rotItems) try {
|
|
10979
|
+
const blob = await fetch(rotationFileUrl(it.file)).then((r) => {
|
|
10980
|
+
if (!r.ok) throw new Error(`http ${r.status}`);
|
|
10981
|
+
return r.blob();
|
|
10982
|
+
});
|
|
10983
|
+
pool.push({
|
|
10984
|
+
file: it.file,
|
|
10985
|
+
thumb: it.thumb,
|
|
10986
|
+
dataUrl: await blobToDataUrl(blob)
|
|
10987
|
+
});
|
|
10988
|
+
} catch {
|
|
10989
|
+
console.warn(`dsh-any-background: export skipped a rotation image (${it.file})`);
|
|
10990
|
+
}
|
|
10991
|
+
if (pool.length === 0) pool = void 0;
|
|
10992
|
+
}
|
|
10787
10993
|
const payload = {
|
|
10788
|
-
version:
|
|
10994
|
+
version: 3,
|
|
10789
10995
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10790
10996
|
config: cfg,
|
|
10791
10997
|
wallpaper: wallpaperPayload,
|
|
10792
|
-
video: videoPayload
|
|
10998
|
+
video: videoPayload,
|
|
10999
|
+
pool
|
|
10793
11000
|
};
|
|
10794
11001
|
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
|
|
10795
11002
|
const url = URL.createObjectURL(blob);
|
|
@@ -10843,6 +11050,26 @@ window.__ModuleLoader__.load({
|
|
|
10843
11050
|
if (cfg.regenerateOnReload) regenerateGeneratedBg();
|
|
10844
11051
|
else if (cfg.generatedBg) updateGeneratedBg(cfg.generatedBg);
|
|
10845
11052
|
}
|
|
11053
|
+
const poolRaw = d.pool;
|
|
11054
|
+
if (Array.isArray(poolRaw)) {
|
|
11055
|
+
if (poolRaw.length > 0) {
|
|
11056
|
+
const r = await rotationRestore(poolRaw);
|
|
11057
|
+
if (r.ok && r.items) cfg.rotation = {
|
|
11058
|
+
...cfg.rotation,
|
|
11059
|
+
items: r.items,
|
|
11060
|
+
current: Math.min(cfg.rotation.current, Math.max(0, r.items.length - 1))
|
|
11061
|
+
};
|
|
11062
|
+
else console.warn("dsh-any-background: rotation pool restore failed", r.error);
|
|
11063
|
+
} else if (cfg.rotation.items.length > 0) cfg.rotation = {
|
|
11064
|
+
...cfg.rotation,
|
|
11065
|
+
items: [],
|
|
11066
|
+
current: 0
|
|
11067
|
+
};
|
|
11068
|
+
} else if ((typeof d.version !== "number" || d.version < 3) && cfg.rotation.items.length > 0) cfg.rotation = {
|
|
11069
|
+
...cfg.rotation,
|
|
11070
|
+
items: [],
|
|
11071
|
+
current: 0
|
|
11072
|
+
};
|
|
10846
11073
|
persistConfig();
|
|
10847
11074
|
syncMetaNow();
|
|
10848
11075
|
if (rHasColor()) {
|
|
@@ -10878,6 +11105,37 @@ window.__ModuleLoader__.load({
|
|
|
10878
11105
|
return true;
|
|
10879
11106
|
},
|
|
10880
11107
|
applyProfile: (id) => applyProfileById(id),
|
|
11108
|
+
renameProfile: (id, name) => {
|
|
11109
|
+
const trimmed = name.trim();
|
|
11110
|
+
if (!trimmed) return false;
|
|
11111
|
+
const before = rProfiles();
|
|
11112
|
+
if (!before.some((p) => p.id === id)) return false;
|
|
11113
|
+
cfg.profiles = before.map((p) => p.id === id ? {
|
|
11114
|
+
...p,
|
|
11115
|
+
name: trimmed.slice(0, 60)
|
|
11116
|
+
} : p);
|
|
11117
|
+
persistConfig();
|
|
11118
|
+
syncMetaNow();
|
|
11119
|
+
return true;
|
|
11120
|
+
},
|
|
11121
|
+
duplicateProfile: (id) => {
|
|
11122
|
+
const before = rProfiles();
|
|
11123
|
+
const src = before.find((p) => p.id === id);
|
|
11124
|
+
if (!src) return false;
|
|
11125
|
+
const profiles = [...before];
|
|
11126
|
+
if (profiles.length >= 20) profiles.shift();
|
|
11127
|
+
const nid = `p-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11128
|
+
profiles.push({
|
|
11129
|
+
...src,
|
|
11130
|
+
id: nid,
|
|
11131
|
+
name: `${src.name} (2)`.slice(0, 60),
|
|
11132
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11133
|
+
});
|
|
11134
|
+
cfg.profiles = profiles;
|
|
11135
|
+
persistConfig();
|
|
11136
|
+
syncMetaNow();
|
|
11137
|
+
return true;
|
|
11138
|
+
},
|
|
10881
11139
|
deleteProfile: (id) => {
|
|
10882
11140
|
const before = rProfiles();
|
|
10883
11141
|
if (!before.some((p) => p.id === id)) return false;
|
|
@@ -10962,7 +11220,7 @@ window.__ModuleLoader__.load({
|
|
|
10962
11220
|
},
|
|
10963
11221
|
rotateNow: async () => {
|
|
10964
11222
|
if (rRotation().items.length === 0) return false;
|
|
10965
|
-
return applyRotationIndex(pickNextRotationIndex(), true);
|
|
11223
|
+
return applyRotationIndex(pickNextRotationIndex$1(), true);
|
|
10966
11224
|
},
|
|
10967
11225
|
setVideoFromUrl: async (url) => {
|
|
10968
11226
|
const r = await setVideoFromUrl(url);
|
|
@@ -11031,10 +11289,10 @@ window.__ModuleLoader__.load({
|
|
|
11031
11289
|
}
|
|
11032
11290
|
}
|
|
11033
11291
|
};
|
|
11034
|
-
let
|
|
11292
|
+
let disposeNavWatch = null;
|
|
11035
11293
|
const watchNavIcon = () => {
|
|
11036
|
-
if (
|
|
11037
|
-
|
|
11294
|
+
if (disposeNavWatch !== null || typeof MutationObserver === "undefined") return;
|
|
11295
|
+
disposeNavWatch = watchDom((records) => {
|
|
11038
11296
|
if (records.some((r) => {
|
|
11039
11297
|
for (const n of r.addedNodes) {
|
|
11040
11298
|
if (n.nodeType !== 1) continue;
|
|
@@ -11044,16 +11302,12 @@ window.__ModuleLoader__.load({
|
|
|
11044
11302
|
return false;
|
|
11045
11303
|
})) applyNavIcon();
|
|
11046
11304
|
});
|
|
11047
|
-
navIconObserver.observe(document.body, {
|
|
11048
|
-
childList: true,
|
|
11049
|
-
subtree: true
|
|
11050
|
-
});
|
|
11051
11305
|
applyNavIcon();
|
|
11052
11306
|
};
|
|
11053
11307
|
watchNavIcon();
|
|
11054
11308
|
ctx.effect(() => () => {
|
|
11055
|
-
|
|
11056
|
-
|
|
11309
|
+
disposeNavWatch?.();
|
|
11310
|
+
disposeNavWatch = null;
|
|
11057
11311
|
}, "dsh-any-background: nav icon watch");
|
|
11058
11312
|
const restoreSaved = () => {
|
|
11059
11313
|
if (rHasColor()) {
|