@mrrisega/dsh-remote 0.7.0 → 0.7.1-beta.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/clients/dsh-remote/mobile-adapter.mjs +273 -22
- package/clients/dsh-remote/test/mobile-adapter-settings.test.mjs +352 -0
- package/clients/dsh-remote/test/mobile-adapter.test.mjs +35 -0
- package/package.json +2 -2
- package/packages/dsh-remote-web/lib/index.js +337 -16
- package/packages/dsh-remote-web/package.json +1 -1
- package/packages/dsh-remote-web/runtime/clients/dsh-remote/mobile-adapter.mjs +273 -22
- package/packages/dsh-remote-web/test/selfheal-restart-guard.test.mjs +420 -0
|
@@ -85,6 +85,30 @@
|
|
|
85
85
|
* ③ 遮罩不变量:只要遮罩在拦截点击而视口内没有任何可交互抽屉,立刻摘掉(800ms 看门狗 +
|
|
86
86
|
* 300ms 复核),且**只摘遮罩不动展开 class**(不破坏抽屉的滑入动画与"点一下就打开")。
|
|
87
87
|
*
|
|
88
|
+
* 🔴 2026-10-13 手机端设置面板「挤成一条竖线」根治(用户反馈,真机 390×844 + 本机 0.2 宿主实测):
|
|
89
|
+
* 现象:窄屏打开「设置」,左侧 nav(通用设置/模型/内置插件/…)是一条竖列,右侧内容被压成
|
|
90
|
+
* 四五字宽的竖条并向右溢出被裁。用户明确要求的目标形态:**tab 放顶部(横排)+ 下方内容满宽**。
|
|
91
|
+
* 根因(实测数字):官方设置弹层是**桌面两栏**布局 —— 本机 0.2.0-rc.2(dsh-web-frontend
|
|
92
|
+
* 0.2 + dsh-client-ui-settings-general 0.2)实测 panel 342×796、`nav` 188 宽(flex column)、
|
|
93
|
+
* `content` 可视宽只剩 **154px**。而适配层只挂了 `[class~="VOzbGW_*"]` 精确类,那是
|
|
94
|
+
* **0.1.2–0.1.5** 的混淆前缀;0.2 重新打包后前缀变成 **`Dws9Sa_*`**(结构、类名一一对应:
|
|
95
|
+
* Dws9Sa_overlay/panel/nav/navTitle/navList/navCell/content/header/close/options)
|
|
96
|
+
* → 整段设置区 CSS **静默失效**。这正是文件开头"类名变化退化策略"预言过的失效模式:
|
|
97
|
+
* 页面照常打开、只有真机看得出布局坏,而且没人会收到任何信号。
|
|
98
|
+
* 修法(三层识别,任一层命中即可成型,不再可能整段静默失效):
|
|
99
|
+
* ① 已知前缀:`[class~="VOzbGW_*"]` 与 `[class~="Dws9Sa_*"]` 并挂(CSS 与 JS 都改);
|
|
100
|
+
* ② **结构兜底**:JS 按 `[role=dialog]/[aria-modal] + 直接子 <nav> + 近似全屏` 认面板,
|
|
101
|
+
* 再按"子元素最多者为 tab 列表"认 navList,打 `dsh-ma-settings-*` 稳定类,由 CSS 成型
|
|
102
|
+
* —— 前缀再改也是"仍然基本可用",而不是彻底坏掉;
|
|
103
|
+
* ③ CSS `:has()` 兜底(`div[role="dialog"]:has(> nav)` 等):支持 :has 的浏览器连 JS 都不等。
|
|
104
|
+
* 降级诊断(**可见**,但不引入任何覆盖层 —— 护栏明令禁止):① 前缀认不出 → 报
|
|
105
|
+
* `console.warn("[dsh-mobile-adapter]", …注意 …混淆前缀已变化…)` 一次;② 有"大弹层"却认不出
|
|
106
|
+
* 设置面板结构 → 报一次"手机端可能仍是桌面两栏布局"。两条都只在 ≤820px 发生。
|
|
107
|
+
* 同批:窄屏**通用弹层**按手机尺寸收敛(`[role=dialog]/[role=alertdialog]/[aria-modal]/.dru-popup-card`
|
|
108
|
+
* 宽 ≤ 100vw-24px、高 ≤ 100dvh-24px 且内部可滚、圆角收敛 12px),修掉"内容裁在右边"。
|
|
109
|
+
* ⚠️ 顺序有讲究:通用弹层规则必须排在设置弹层规则**之前** —— 两者特异性相同,靠顺序让
|
|
110
|
+
* "设置面板铺满全屏"赢(否则设置面板会被 100vw-24px 钉住,又变成带边的浮层)。
|
|
111
|
+
*
|
|
88
112
|
* 开关:环境变量 DSH_MOBILE_ADAPTER=0 整体关闭(默认开启)。
|
|
89
113
|
*/
|
|
90
114
|
|
|
@@ -99,6 +123,17 @@ const HOST_FEATURE_RE =
|
|
|
99
123
|
const RUNTIME_HOST_RE =
|
|
100
124
|
/(data-sidebar-collapsed|data-details-collapsed|data-rightbar-collapsed|data-shell-overlay)|(pI_x6G_frame|hHd-Xa_root|hHd-Xa_toggle)/;
|
|
101
125
|
|
|
126
|
+
/**
|
|
127
|
+
* 【可选】官方锚点清单 —— 供 scripts/check-ui-anchors.mjs 分类用(它直接读这个常量)。
|
|
128
|
+
* 这些名字**存在就用、不存在就按结构/几何退化**,所以它们缺失**不算故障**:
|
|
129
|
+
* · data-details-collapsed —— 官方 0.1.5-rc.2 已改名 data-rightbar-collapsed,这里只为兼容旧版保留;
|
|
130
|
+
* · data-sidebar-open —— 官方是否输出这个属性本身就不确定(见下面对 sidebarAttrSeen 的注释),
|
|
131
|
+
* 适配层刻意做了"认不到就只信几何"的分支;
|
|
132
|
+
* · data-rtt —— 适配层**自己**的延迟角标属性(不是官方锚点),列出只为让自检脚本不去它身上找。
|
|
133
|
+
* 约定:这里**只放**这种"缺了也无所谓"的锚点;凡是缺了会让布局坏掉的锚点,一律不要写进来。
|
|
134
|
+
*/
|
|
135
|
+
const OPTIONAL_UI_ANCHORS = ["data-details-collapsed", "data-sidebar-open", "data-rtt"];
|
|
136
|
+
|
|
102
137
|
const STYLE = `
|
|
103
138
|
|
|
104
139
|
@media (max-width: 820px) {
|
|
@@ -226,11 +261,42 @@ const STYLE = `
|
|
|
226
261
|
div.wSkVaW_heroWorkspaceRow > * { max-width: calc(100vw - 32px); }
|
|
227
262
|
div.wSkVaW_composerStack, div.wSkVaW_composerHero, div.uV2eYG_card { min-width: 0; }
|
|
228
263
|
|
|
229
|
-
/*
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
264
|
+
/* ===== 通用弹层(dialog/modal)按手机尺寸收敛(2026-10-13)=====
|
|
265
|
+
官方弹层(设置 / 审批 / 确认 / 各类 modal)在桌面上写死 px 宽高,窄屏会右溢被裁;
|
|
266
|
+
本项目自己的 .dru-popup 卡片同理。这里只加三条**尺寸**硬约束,不动配色与结构:
|
|
267
|
+
· 宽 ≤ 100vw - 24px(两侧各留 12px)—— 用户报的"内容裁在右边"根治;
|
|
268
|
+
· 高 ≤ 100dvh - 24px 且自身可滚(先写 100vh 给不认 dvh 的老浏览器兜底);
|
|
269
|
+
· 窄屏圆角收敛到 12px(官方 --dsw-radius-panel 在手机上偏大)。
|
|
270
|
+
⚠️ 必须放在设置弹层规则**之前**:两者特异性相同,靠顺序让"设置面板铺满全屏"赢。 */
|
|
271
|
+
[role="dialog"], [role="alertdialog"], [aria-modal="true"], .dru-popup-card {
|
|
272
|
+
box-sizing: border-box;
|
|
273
|
+
max-width: calc(100vw - 24px) !important;
|
|
274
|
+
max-height: calc(100vh - 24px) !important;
|
|
275
|
+
max-height: calc(100dvh - 24px) !important;
|
|
276
|
+
border-radius: 12px !important;
|
|
277
|
+
overflow: auto !important;
|
|
278
|
+
overscroll-behavior: contain;
|
|
279
|
+
-webkit-overflow-scrolling: touch;
|
|
280
|
+
}
|
|
281
|
+
/* .dru-popup 是遮罩层,卡片 .dru-popup-card 才是"弹窗":窄屏把 24px 外边距收到 12px */
|
|
282
|
+
.dru-popup { padding: 12px !important; }
|
|
283
|
+
.dru-popup-card { width: 100% !important; max-width: calc(100vw - 24px) !important; }
|
|
284
|
+
|
|
285
|
+
/* ===== 设置弹层:窄屏「顶部横向 tab 行 + 下方内容区满宽」(2026-10-13 重写)=====
|
|
286
|
+
🔴 根因(真机 390×844 实测,用户报「设置界面挤成一条竖线」):
|
|
287
|
+
官方弹层是**桌面两栏** —— 0.2.0-rc.2 实测 panel 342×796、nav 188 宽竖排、
|
|
288
|
+
content(内容区)只剩 **154px** 可视宽 → 内容被压成竖条并向右溢出被裁。
|
|
289
|
+
旧实现只有 [class~="VOzbGW_*"](0.1.2–0.1.5 的混淆前缀);0.2.0-rc.2 重新打包后
|
|
290
|
+
前缀变成 Dws9Sa_*,**整段 CSS 静默失效** —— 页面照常打开,只有真机看得出坏。
|
|
291
|
+
所以识别分三层,任一层命中即可成型(这是"不再静默失效"的核心):
|
|
292
|
+
① 已知前缀 [class~="VOzbGW_*"] / [class~="Dws9Sa_*"](最短路径,随版本追加);
|
|
293
|
+
② JS 结构识别后打的稳定类 .dsh-ma-settings-*(role=dialog + 直接子 <nav> + 几何,
|
|
294
|
+
见脚本里的 maTagSettingsDialog)—— 前缀再改也照样生效;
|
|
295
|
+
③ CSS :has() 兜底(div[role="dialog"]:has(> nav) 等),不必等 JS 就能成型。
|
|
296
|
+
⚠️ 全部 !important:官方模块 CSS 是**运行期**注入 head 的(晚于本 <style>),
|
|
297
|
+
同特异性下后加载者赢 —— fix2 之前 nav 选择器"从未命中"正是这个原因。 */
|
|
298
|
+
[class~="VOzbGW_overlay"], [class~="Dws9Sa_overlay"], .dsh-ma-settings-overlay,
|
|
299
|
+
div[role="presentation"]:has(> div[role="dialog"] > nav) {
|
|
234
300
|
position: fixed !important;
|
|
235
301
|
inset: 0 !important;
|
|
236
302
|
width: auto !important; max-width: none !important;
|
|
@@ -240,31 +306,37 @@ const STYLE = `
|
|
|
240
306
|
box-sizing: border-box;
|
|
241
307
|
justify-content: center; align-items: stretch;
|
|
242
308
|
}
|
|
243
|
-
|
|
309
|
+
/* 面板:nav 在上、content 在下的**竖排**,并铺满视口(圆角/外边距在窄屏收敛为 0) */
|
|
310
|
+
[class~="VOzbGW_panel"], [class~="Dws9Sa_panel"], .dsh-ma-settings-panel,
|
|
311
|
+
div[role="dialog"]:has(> nav) {
|
|
244
312
|
box-sizing: border-box;
|
|
245
313
|
width: 100% !important; max-width: 100% !important;
|
|
246
314
|
height: 100% !important; max-height: 100% !important;
|
|
247
315
|
margin: 0 !important; border-radius: 0 !important;
|
|
248
316
|
flex-direction: column !important;
|
|
317
|
+
overflow: hidden !important; /* 内层 options 自己滚 */
|
|
249
318
|
}
|
|
250
319
|
/* 顶部 tab 行:整行铺满,字号调小(≤13px),横向可滚动 */
|
|
251
|
-
[class~="VOzbGW_nav"]
|
|
320
|
+
[class~="VOzbGW_nav"], [class~="Dws9Sa_nav"], .dsh-ma-settings-nav,
|
|
321
|
+
div[role="dialog"] > nav {
|
|
252
322
|
box-sizing: border-box;
|
|
253
323
|
flex: none;
|
|
254
324
|
width: 100% !important;
|
|
255
325
|
flex-direction: row !important;
|
|
256
326
|
align-items: center;
|
|
257
327
|
gap: 4px;
|
|
258
|
-
padding: max(4px, env(safe-area-inset-top))
|
|
328
|
+
padding: max(4px, env(safe-area-inset-top)) 8px 6px;
|
|
259
329
|
overflow: visible;
|
|
260
330
|
}
|
|
261
|
-
[class~="VOzbGW_navTitle"]
|
|
331
|
+
[class~="VOzbGW_navTitle"], [class~="Dws9Sa_navTitle"], .dsh-ma-settings-navtitle,
|
|
332
|
+
div[role="dialog"] > nav > :first-child {
|
|
262
333
|
flex: none;
|
|
263
334
|
font-size: 13px !important; line-height: 1.2;
|
|
264
335
|
padding: 0 4px 0 8px;
|
|
265
336
|
white-space: nowrap;
|
|
266
337
|
}
|
|
267
|
-
[class~="VOzbGW_navList"]
|
|
338
|
+
[class~="VOzbGW_navList"], [class~="Dws9Sa_navList"], .dsh-ma-settings-navlist,
|
|
339
|
+
div[role="dialog"] > nav > :last-child {
|
|
268
340
|
box-sizing: border-box;
|
|
269
341
|
flex: 1 1 auto; min-width: 0;
|
|
270
342
|
flex-direction: row !important;
|
|
@@ -274,33 +346,40 @@ const STYLE = `
|
|
|
274
346
|
scrollbar-width: none;
|
|
275
347
|
padding: 0 0 6px;
|
|
276
348
|
}
|
|
277
|
-
[class~="VOzbGW_navList"]::-webkit-scrollbar
|
|
278
|
-
[class~="
|
|
349
|
+
[class~="VOzbGW_navList"]::-webkit-scrollbar,
|
|
350
|
+
[class~="Dws9Sa_navList"]::-webkit-scrollbar,
|
|
351
|
+
.dsh-ma-settings-navlist::-webkit-scrollbar { display: none; }
|
|
352
|
+
[class~="VOzbGW_navCell"], [class~="Dws9Sa_navCell"], .dsh-ma-settings-cell,
|
|
353
|
+
div[role="dialog"] > nav > :last-child > * {
|
|
279
354
|
flex: none; min-width: max-content;
|
|
280
355
|
box-sizing: border-box;
|
|
281
356
|
font-size: 13px !important;
|
|
282
357
|
height: 32px !important;
|
|
283
358
|
padding: 0 12px !important;
|
|
359
|
+
white-space: nowrap;
|
|
284
360
|
}
|
|
285
361
|
/* 内容区:全宽拉满,收敛内边距,最大化有效宽度 */
|
|
286
|
-
[class~="VOzbGW_content"]
|
|
362
|
+
[class~="VOzbGW_content"], [class~="Dws9Sa_content"], .dsh-ma-settings-content,
|
|
363
|
+
div[role="dialog"]:has(> nav) > div {
|
|
287
364
|
box-sizing: border-box;
|
|
288
365
|
width: 100% !important; max-width: none !important;
|
|
289
366
|
flex: 1 1 auto; min-width: 0; min-height: 0;
|
|
290
367
|
}
|
|
291
|
-
[class~="VOzbGW_header"] {
|
|
368
|
+
[class~="VOzbGW_header"], [class~="Dws9Sa_header"], .dsh-ma-settings-header {
|
|
292
369
|
box-sizing: border-box;
|
|
293
370
|
flex: none;
|
|
294
|
-
min-height: 40px;
|
|
371
|
+
min-height: 40px; height: auto !important;
|
|
295
372
|
padding: 6px 8px 4px 14px !important;
|
|
296
373
|
align-items: center;
|
|
297
374
|
}
|
|
298
|
-
[class~="VOzbGW_close"]
|
|
299
|
-
|
|
375
|
+
[class~="VOzbGW_close"], [class~="Dws9Sa_close"], .dsh-ma-settings-close {
|
|
376
|
+
width: 32px !important; height: 32px !important;
|
|
377
|
+
}
|
|
378
|
+
[class~="VOzbGW_options"], [class~="Dws9Sa_options"], .dsh-ma-settings-options {
|
|
300
379
|
box-sizing: border-box;
|
|
301
380
|
flex: 1 1 auto; min-height: 0; overflow-y: auto;
|
|
302
381
|
-webkit-overflow-scrolling: touch;
|
|
303
|
-
padding:
|
|
382
|
+
padding: 4px 14px calc(14px + env(safe-area-inset-bottom)) !important;
|
|
304
383
|
}
|
|
305
384
|
}
|
|
306
385
|
|
|
@@ -995,13 +1074,177 @@ const SCRIPT = `(() => {
|
|
|
995
1074
|
whaleNativeDragToBottomRight();
|
|
996
1075
|
}
|
|
997
1076
|
|
|
1077
|
+
/* ===== 设置面板:结构识别 + 稳定类(2026-10-13;根因见 CSS 里那段长注释)=====
|
|
1078
|
+
为什么不能只靠 [class~="VOzbGW_*"]:官方重新打包会换混淆前缀(0.1.5 = VOzbGW_,
|
|
1079
|
+
0.2.0-rc.2 = Dws9Sa_),改名后整段 CSS **静默失效** —— 页面照常打开,
|
|
1080
|
+
只是手机端布局坏掉,而且只有真机看得出。所以这里再按**结构 + 几何**认一遍:
|
|
1081
|
+
· 面板 = [role=dialog]/[aria-modal] 且**直接子元素里有 <nav>**、尺寸接近全屏
|
|
1082
|
+
(0.1.5 与 0.2 的面板都是 div[role=dialog] > nav + div 两栏,且都是固定像素宽);
|
|
1083
|
+
· 列表 = nav 里子元素最多的那个孩子(单元格 ≥3 个);
|
|
1084
|
+
· content = 面板里除 nav 之外的孩子;header/options = content 的首/末孩子。
|
|
1085
|
+
命中 → 打 .dsh-ma-settings-* 稳定类,由 CSS 接管(前缀改名后**仍然基本可用**);
|
|
1086
|
+
命中不到 → 原样放过(绝不破坏官方默认),只 warn 一次作为**可见的降级诊断**
|
|
1087
|
+
—— 护栏禁止适配层引入全屏覆盖层,所以诊断只能走 console / 既有上报通道。 */
|
|
1088
|
+
var MA_SETTINGS_PREFIX_RE = /(^|\s)(VOzbGW_|Dws9Sa_)/;
|
|
1089
|
+
var maSettingsPanelEl = null; // 已识别的面板(靠 isConnected 判存活,官方重渲染后自动重认)
|
|
1090
|
+
var maSettingsCells = []; // 已识别的 tab 单元格
|
|
1091
|
+
var maSettingsNextScan = 0; // 未命中时的扫描节流(见 maSettingsMaybeTag)
|
|
1092
|
+
var maSettingsWarned = "";
|
|
1093
|
+
function maSettingsWarn(msg) {
|
|
1094
|
+
if (maSettingsWarned === msg) return;
|
|
1095
|
+
maSettingsWarned = msg;
|
|
1096
|
+
/* window.console 优先(镜像页里它就是页面控制台),拿不到再退到全局 console ——
|
|
1097
|
+
诊断**必须**能出得来,否则"静默失效"只是换了个地方静默。 */
|
|
1098
|
+
try {
|
|
1099
|
+
var c = (typeof window !== "undefined" && window && window.console) || (typeof console !== "undefined" ? console : null);
|
|
1100
|
+
if (c && c.warn) c.warn("[dsh-mobile-adapter]", msg);
|
|
1101
|
+
} catch (eW) {}
|
|
1102
|
+
}
|
|
1103
|
+
function maClsOf(el) { try { return (el && typeof el.className === "string") ? el.className : ""; } catch (eC) { return ""; } }
|
|
1104
|
+
function maKidsOf(el) { try { return (el && el.children) ? [].slice.call(el.children) : []; } catch (eK) { return []; } }
|
|
1105
|
+
/** 面板级几何门:排除小气泡/菜单/下拉,只认接近全屏的弹层。 */
|
|
1106
|
+
function maPanelSized(el) {
|
|
1107
|
+
try {
|
|
1108
|
+
var r = el.getBoundingClientRect();
|
|
1109
|
+
if (!r || !(r.width > 0) || !(r.height > 0)) return false;
|
|
1110
|
+
return r.width >= Math.max(240, window.innerWidth * 0.5) && r.height >= 160;
|
|
1111
|
+
} catch (eG) { return false; }
|
|
1112
|
+
}
|
|
1113
|
+
/** 结构识别:找到官方设置面板(flex 两栏 = 直接子 <nav> + 另一栏)。 */
|
|
1114
|
+
function maFindSettingsPanel() {
|
|
1115
|
+
var cands = [];
|
|
1116
|
+
try {
|
|
1117
|
+
cands = [].slice.call(document.querySelectorAll('[role="dialog"], [role="alertdialog"], [aria-modal="true"]'));
|
|
1118
|
+
} catch (eQ) { return null; }
|
|
1119
|
+
var best = null, bestArea = 0;
|
|
1120
|
+
for (var i = 0; i < cands.length; i++) {
|
|
1121
|
+
var el = cands[i];
|
|
1122
|
+
var kids = maKidsOf(el);
|
|
1123
|
+
if (kids.length < 2) continue; // 两栏才有 content 可谈
|
|
1124
|
+
var nav = null;
|
|
1125
|
+
for (var j = 0; j < kids.length; j++) { if (kids[j] && kids[j].tagName === "NAV") { nav = kids[j]; break; } }
|
|
1126
|
+
if (!nav || maKidsOf(nav).length < 2) continue; // 设置面板的 nav = 标题 + tab 列表
|
|
1127
|
+
if (!maPanelSized(el)) continue;
|
|
1128
|
+
var r = el.getBoundingClientRect();
|
|
1129
|
+
var area = r.width * r.height;
|
|
1130
|
+
if (area > bestArea) { bestArea = area; best = el; }
|
|
1131
|
+
}
|
|
1132
|
+
return best;
|
|
1133
|
+
}
|
|
1134
|
+
/** nav 里的 tab 列表 = 子元素最多的那个孩子(标题只有一个文本孩子)。 */
|
|
1135
|
+
function maNavListOf(nav) {
|
|
1136
|
+
var kids = maKidsOf(nav);
|
|
1137
|
+
if (!kids.length) return null;
|
|
1138
|
+
var best = null, bestN = 0;
|
|
1139
|
+
for (var i = 0; i < kids.length; i++) {
|
|
1140
|
+
var n = maKidsOf(kids[i]).length;
|
|
1141
|
+
if (n > bestN) { bestN = n; best = kids[i]; }
|
|
1142
|
+
}
|
|
1143
|
+
return bestN >= 2 ? best : (kids.length > 1 ? kids[kids.length - 1] : null);
|
|
1144
|
+
}
|
|
1145
|
+
/** 当前选中项:官方状态类(任意前缀 + _active)或 aria-current —— 两者都不依赖前缀。 */
|
|
1146
|
+
function maCellActive(cell) {
|
|
1147
|
+
try {
|
|
1148
|
+
if (cell.getAttribute && cell.getAttribute("aria-current") === "true") return true;
|
|
1149
|
+
/* 只认「整段 token 以 _active 结尾」或「token 就叫 active」——
|
|
1150
|
+
不能用 *active 这种宽松匹配:inactive 也会命中,会把没选中的 tab 当成当前项。
|
|
1151
|
+
⚠️ 本文件是模板字符串,注释里**不得出现反引号**(会提前闭合注入体,见 AGENTS.md)。 */
|
|
1152
|
+
return /(^|\s)(?:[A-Za-z0-9_-]+_active|active)(\s|$)/.test(maClsOf(cell));
|
|
1153
|
+
} catch (eA) { return false; }
|
|
1154
|
+
}
|
|
1155
|
+
function maMarkActiveCells() {
|
|
1156
|
+
for (var i = 0; i < maSettingsCells.length; i++) {
|
|
1157
|
+
var c = maSettingsCells[i];
|
|
1158
|
+
try { if (c && c.isConnected && maCellActive(c)) c.classList.add("dsh-ma-settings-active"); } catch (eM) {}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
function maTagSettingsDialog() {
|
|
1162
|
+
var panel = maFindSettingsPanel();
|
|
1163
|
+
if (!panel) {
|
|
1164
|
+
/* 认不到面板时给一条**可见的**降级诊断:现场是"大弹层 + 首栏比次栏窄"的桌面两栏形态,
|
|
1165
|
+
说明官方又换了结构(或换成了我们不认识的前缀)—— 这条 warn 就是下次定位问题的起点。 */
|
|
1166
|
+
try {
|
|
1167
|
+
var any = document.querySelector('[role="dialog"], [aria-modal="true"]');
|
|
1168
|
+
if (any && maPanelSized(any)) {
|
|
1169
|
+
maSettingsWarn("检测到大弹层但未能识别为设置面板(官方结构/类名可能已变化):手机端可能仍是桌面两栏布局。");
|
|
1170
|
+
}
|
|
1171
|
+
} catch (eAny) {}
|
|
1172
|
+
maSettingsPanelEl = null; maSettingsCells = [];
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
var kids = maKidsOf(panel);
|
|
1176
|
+
var nav = null, content = null;
|
|
1177
|
+
for (var i = 0; i < kids.length; i++) {
|
|
1178
|
+
if (!nav && kids[i].tagName === "NAV") { nav = kids[i]; continue; }
|
|
1179
|
+
if (!content) content = kids[i];
|
|
1180
|
+
}
|
|
1181
|
+
var list = nav ? maNavListOf(nav) : null;
|
|
1182
|
+
var title = null;
|
|
1183
|
+
var navKids = maKidsOf(nav);
|
|
1184
|
+
for (var k = 0; k < navKids.length; k++) { if (navKids[k] !== list) { title = navKids[k]; break; } }
|
|
1185
|
+
var contentKids = maKidsOf(content);
|
|
1186
|
+
var header = contentKids[0] || null;
|
|
1187
|
+
var options = contentKids.length > 1 ? contentKids[contentKids.length - 1] : null;
|
|
1188
|
+
var overlay = null;
|
|
1189
|
+
try {
|
|
1190
|
+
var p = panel.parentElement || panel.parent || null; // parentElement 是标准属性;parent 兜底给极简 DOM
|
|
1191
|
+
if (p && p.getBoundingClientRect) {
|
|
1192
|
+
var pr = p.getBoundingClientRect();
|
|
1193
|
+
if (pr && pr.width >= window.innerWidth * 0.9 && pr.height >= window.innerHeight * 0.9) overlay = p;
|
|
1194
|
+
}
|
|
1195
|
+
} catch (eP) {}
|
|
1196
|
+
try {
|
|
1197
|
+
panel.classList.add("dsh-ma-settings-panel");
|
|
1198
|
+
if (nav) nav.classList.add("dsh-ma-settings-nav");
|
|
1199
|
+
if (title) title.classList.add("dsh-ma-settings-navtitle");
|
|
1200
|
+
if (list) list.classList.add("dsh-ma-settings-navlist");
|
|
1201
|
+
if (content) content.classList.add("dsh-ma-settings-content");
|
|
1202
|
+
if (header) header.classList.add("dsh-ma-settings-header");
|
|
1203
|
+
if (options) options.classList.add("dsh-ma-settings-options");
|
|
1204
|
+
if (overlay) overlay.classList.add("dsh-ma-settings-overlay");
|
|
1205
|
+
maSettingsCells = list ? maKidsOf(list) : [];
|
|
1206
|
+
for (var c = 0; c < maSettingsCells.length; c++) maSettingsCells[c].classList.add("dsh-ma-settings-cell");
|
|
1207
|
+
maMarkActiveCells();
|
|
1208
|
+
} catch (eT) { /* 打类失败也要继续:已知前缀那条 CSS 仍可能命中 */ }
|
|
1209
|
+
maSettingsPanelEl = panel;
|
|
1210
|
+
/* 前缀认不出 = 官方改名的**直接信号** —— 这时布局靠结构类仍然可用,所以只警告不降级。 */
|
|
1211
|
+
if (!MA_SETTINGS_PREFIX_RE.test(maClsOf(panel) + " " + maClsOf(nav))) {
|
|
1212
|
+
maSettingsWarn("设置面板已按**结构**识别(官方混淆前缀已变化:既不是 VOzbGW_ 也不是 Dws9Sa_);布局仍可用,但请把新前缀补进适配层。");
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* 每拍调用(节流):已识别且面板还在文档里 → 只刷新选中态;否则限频重扫。
|
|
1217
|
+
* 为什么要节流:未命中时每拍都要 querySelectorAll 一遍全文档,流式输出时每帧一拍。
|
|
1218
|
+
*/
|
|
1219
|
+
function maSettingsMaybeTag() {
|
|
1220
|
+
if (!NARROW()) return;
|
|
1221
|
+
try {
|
|
1222
|
+
if (maSettingsPanelEl && maSettingsPanelEl.isConnected) { maMarkActiveCells(); return; }
|
|
1223
|
+
} catch (eLive) { /* 掉进重扫 */ }
|
|
1224
|
+
var now = Date.now();
|
|
1225
|
+
if (now < maSettingsNextScan) return;
|
|
1226
|
+
maSettingsNextScan = now + 400; // 未命中时最多 2.5 次/秒;命中后面板引用直接短路,不再全扫
|
|
1227
|
+
maTagSettingsDialog();
|
|
1228
|
+
}
|
|
1229
|
+
|
|
998
1230
|
/* 设置面板打开时,把当前 tab 滚进横向可视区(顶部 tab 行手机横向滚动) */
|
|
999
1231
|
let settingsWatchOn = false;
|
|
1000
1232
|
let settingsTick = 0;
|
|
1001
1233
|
function scrollActiveSettingsTab() {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1234
|
+
/* 逐个尝试而不是写一条长选择器:结构类(前缀无关)优先,已知前缀退居兜底;
|
|
1235
|
+
任何一条在旧浏览器上解析失败都不影响其余。 */
|
|
1236
|
+
const SEL = [
|
|
1237
|
+
".dsh-ma-settings-cell.dsh-ma-settings-active",
|
|
1238
|
+
'[class~="Dws9Sa_navCell"].Dws9Sa_active',
|
|
1239
|
+
'[class~="VOzbGW_navCell"].VOzbGW_active',
|
|
1240
|
+
'[class~="Dws9Sa_navCell"][aria-current="true"]',
|
|
1241
|
+
'[class~="VOzbGW_navCell"][aria-current="true"]'
|
|
1242
|
+
];
|
|
1243
|
+
let act = null;
|
|
1244
|
+
for (const sel of SEL) {
|
|
1245
|
+
try { act = document.querySelector(sel); } catch (e13a) { act = null; }
|
|
1246
|
+
if (act) break;
|
|
1247
|
+
}
|
|
1005
1248
|
if (act && act.scrollIntoView) {
|
|
1006
1249
|
try { act.scrollIntoView({ block: "nearest", inline: "nearest" }); } catch (e13) {
|
|
1007
1250
|
try { act.scrollIntoView(); } catch (e14) {}
|
|
@@ -1014,7 +1257,12 @@ const SCRIPT = `(() => {
|
|
|
1014
1257
|
try {
|
|
1015
1258
|
const ob = new MutationObserver(function () {
|
|
1016
1259
|
clearTimeout(settingsTick);
|
|
1017
|
-
settingsTick = setTimeout(function () {
|
|
1260
|
+
settingsTick = setTimeout(function () {
|
|
1261
|
+
if (!NARROW()) return;
|
|
1262
|
+
maSettingsNextScan = 0; // 官方刚挂上面板:别被节流挡在门外
|
|
1263
|
+
try { maSettingsMaybeTag(); } catch (e16) {}
|
|
1264
|
+
scrollActiveSettingsTab();
|
|
1265
|
+
}, 140);
|
|
1018
1266
|
});
|
|
1019
1267
|
ob.observe(document.body, { childList: true, subtree: true });
|
|
1020
1268
|
} catch (e15) {}
|
|
@@ -2056,6 +2304,9 @@ const SCRIPT = `(() => {
|
|
|
2056
2304
|
if (!whaleGuardsOn || !settingsWatchOn) {
|
|
2057
2305
|
try { whaleHookInit(); watchSettings(); } catch (eHook) { /* 两者内部都自带一次性守卫 */ }
|
|
2058
2306
|
}
|
|
2307
|
+
/* 设置面板结构识别(自带节流,幂等):官方每次重新打包都换混淆前缀,
|
|
2308
|
+
这一步保证"改名后仍然基本可用",而不是整段静默失效(2026-10-13 根因)。 */
|
|
2309
|
+
try { maSettingsMaybeTag(); } catch (eSetTag) { /* 忽略 */ }
|
|
2059
2310
|
};
|
|
2060
2311
|
/* 节流:一次 DOM 变更往往产生几十条记录,逐条 boot() 就是忙轮询。
|
|
2061
2312
|
统一合并成"本帧最多跑一次"(有 rAF 用 rAF,没有就退化成 0ms 宏任务) —— 天然去重。 */
|