@yezack/dsh-remote-mobile 1.10.0 → 1.10.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.
@@ -42,21 +42,21 @@ import { isMobileUserAgent, applyDataMobileAttr, buildMobileStyleTag, } from '..
42
42
  * 注入到 index.html <head> 最前列的 Polyfill 脚本代码片段
43
43
  * 针对非安全上下文(HTTP + 外部 IP)提供轻量高精度的 UUID v4 生成能力
44
44
  */
45
- export const CRYPTO_POLYFILL_SNIPPET = `
46
- (function() {
47
- if (typeof window !== "undefined") {
48
- if (!window.crypto) window.crypto = {};
49
- if (typeof window.crypto.randomUUID !== "function") {
50
- window.crypto.randomUUID = function() {
51
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
52
- var r = Math.random() * 16 | 0;
53
- var v = c === 'x' ? r : (r & 0x3 | 0x8);
54
- return v.toString(16);
55
- });
56
- };
57
- }
58
- }
59
- })();
45
+ export const CRYPTO_POLYFILL_SNIPPET = `
46
+ (function() {
47
+ if (typeof window !== "undefined") {
48
+ if (!window.crypto) window.crypto = {};
49
+ if (typeof window.crypto.randomUUID !== "function") {
50
+ window.crypto.randomUUID = function() {
51
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
52
+ var r = Math.random() * 16 | 0;
53
+ var v = c === 'x' ? r : (r & 0x3 | 0x8);
54
+ return v.toString(16);
55
+ });
56
+ };
57
+ }
58
+ }
59
+ })();
60
60
  `;
61
61
  /**
62
62
  * 注入到 index.html <head> 最前列的 Clipboard API Polyfill 脚本
@@ -66,192 +66,192 @@ export const CRYPTO_POLYFILL_SNIPPET = `
66
66
  * "Cannot read properties of undefined (reading 'writeText')" 崩溃。
67
67
  * 原生可用时不做任何替换。
68
68
  */
69
- export const CLIPBOARD_POLYFILL_SNIPPET = `
70
- (function() {
71
- if (typeof navigator === "undefined") return;
72
- try {
73
- if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") return;
74
- var writeText = function(text) {
75
- try {
76
- var ta = document.createElement('textarea');
77
- ta.value = String(text == null ? '' : text);
78
- ta.setAttribute('readonly', '');
79
- ta.style.position = 'fixed';
80
- ta.style.top = '-9999px';
81
- ta.style.left = '-9999px';
82
- document.body.appendChild(ta);
83
- ta.select();
84
- var ok = document.execCommand('copy');
85
- document.body.removeChild(ta);
86
- return ok ? Promise.resolve() : Promise.reject(new Error('copy failed'));
87
- } catch (err) {
88
- return Promise.reject(err);
89
- }
90
- };
91
- var readText = function() {
92
- return Promise.reject(new Error('clipboard readText is not supported in insecure context'));
93
- };
94
- try {
95
- if (!navigator.clipboard) navigator.clipboard = {};
96
- if (typeof navigator.clipboard.writeText !== "function") navigator.clipboard.writeText = writeText;
97
- if (typeof navigator.clipboard.readText !== "function") navigator.clipboard.readText = readText;
98
- } catch (err) {
99
- try {
100
- Object.defineProperty(navigator, 'clipboard', {
101
- value: { writeText: writeText, readText: readText },
102
- configurable: true,
103
- });
104
- } catch (err2) {}
105
- }
106
- } catch (err) {}
107
- })();
69
+ export const CLIPBOARD_POLYFILL_SNIPPET = `
70
+ (function() {
71
+ if (typeof navigator === "undefined") return;
72
+ try {
73
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") return;
74
+ var writeText = function(text) {
75
+ try {
76
+ var ta = document.createElement('textarea');
77
+ ta.value = String(text == null ? '' : text);
78
+ ta.setAttribute('readonly', '');
79
+ ta.style.position = 'fixed';
80
+ ta.style.top = '-9999px';
81
+ ta.style.left = '-9999px';
82
+ document.body.appendChild(ta);
83
+ ta.select();
84
+ var ok = document.execCommand('copy');
85
+ document.body.removeChild(ta);
86
+ return ok ? Promise.resolve() : Promise.reject(new Error('copy failed'));
87
+ } catch (err) {
88
+ return Promise.reject(err);
89
+ }
90
+ };
91
+ var readText = function() {
92
+ return Promise.reject(new Error('clipboard readText is not supported in insecure context'));
93
+ };
94
+ try {
95
+ if (!navigator.clipboard) navigator.clipboard = {};
96
+ if (typeof navigator.clipboard.writeText !== "function") navigator.clipboard.writeText = writeText;
97
+ if (typeof navigator.clipboard.readText !== "function") navigator.clipboard.readText = readText;
98
+ } catch (err) {
99
+ try {
100
+ Object.defineProperty(navigator, 'clipboard', {
101
+ value: { writeText: writeText, readText: readText },
102
+ configurable: true,
103
+ });
104
+ } catch (err2) {}
105
+ }
106
+ } catch (err) {}
107
+ })();
108
108
  `;
109
109
  /**
110
110
  * 注入到 index.html <head> 最前列的生态插件 remote-web-ui 移动端冲突防护脚本
111
111
  * 写入 sessionStorage 'dsh-remote-force-desktop' = '1',触发对方内置的强制桌面跳过机制,
112
112
  * 避免生态插件重复渲染小鲸鱼按钮 (#dshRemoteWhale) 以及引发手势拦截冲突
113
113
  */
114
- export const FORCE_DESKTOP_SUPPRESSION_SNIPPET = `
115
- (function() {
116
- try {
117
- if (typeof window !== "undefined" && window.sessionStorage) {
118
- window.sessionStorage.setItem('dsh-remote-force-desktop', '1');
119
- }
120
- } catch (e) {}
121
- })();
114
+ export const FORCE_DESKTOP_SUPPRESSION_SNIPPET = `
115
+ (function() {
116
+ try {
117
+ if (typeof window !== "undefined" && window.sessionStorage) {
118
+ window.sessionStorage.setItem('dsh-remote-force-desktop', '1');
119
+ }
120
+ } catch (e) {}
121
+ })();
122
122
  `;
123
123
  /**
124
124
  * 注入到 index.html 的移动端悬浮入口拖拽与位置记忆脚本
125
125
  * 允许用户在移动端上下随心拖动左上角入口把手,避免遮挡聊天内容,并自动保存位置
126
126
  */
127
- export const DRAGGABLE_NAV_SNIPPET = `
128
- (function() {
129
- if (typeof window === "undefined" || typeof document === "undefined") return;
130
-
131
- /* ===== 防误收起守卫 =====
132
- * 第三方生态插件(如 @linxin666/dsh-web-ui-all 的移动端抽屉增强)会在用户点击
133
- * 侧边栏条目(role=treeitem)后,于下一帧程序化点击「收起侧边栏」按钮自动收回抽屉。
134
- * 该判定无法区分「选中条目回主区」与「点击条目内的次级操作图标」(更多操作 ⋯ /
135
- * 展开箭头等),导致后者抽屉被误关。
136
- * 这里记录最近一次对条目内任意部位(项目名称主体 / 次级按钮 / 箭头图标)的触摸时间,
137
- * 并包装 HTMLButtonElement.prototype.click:短时间窗口内的程序化收起调用直接忽略。
138
- * 覆盖条目主体是为了让选中高亮后浮现的展开/收起箭头与新增/更多操作图标有足够的
139
- * 停留操作时间;用户真实点击收起按钮(浏览器合成事件不走 prototype.click)与
140
- * 点击抽屉外暗处收起的官方行为均不受影响。 */
141
- if (!window.__dshRmAntiCollapseInstalled__) {
142
- window.__dshRmAntiCollapseInstalled__ = true;
143
- var lastEntryTapAt = 0;
144
- document.addEventListener('pointerdown', function (e) {
145
- try {
146
- var t = e.target;
147
- if (!t || !t.closest) return;
148
- if (!t.closest('[data-pane="sidebar"], [class*="_sidebarCol"]')) return;
149
-
150
- // 会话条目(_sessionRow)除操作按钮(_rowActions/button等)以外的主体点击:
151
- // 用户意在进入/切换会话并查看正文,不拦截程序化收起(放行自动回主区)
152
- var isSessionRow = t.closest('[class*="_sessionRow"]');
153
- var isActionOrToggle = t.closest('[class*="_rowActions"], [class*="_chevron"], [class*="_arrow"], button, [role="button"]');
154
- if (isSessionRow && !isActionOrToggle) {
155
- lastEntryTapAt = 0;
156
- return;
157
- }
158
-
159
- var entry = t.closest('[role="treeitem"], [data-dsh-part="sidebar-entry"]');
160
- if (!entry) return;
161
- lastEntryTapAt = Date.now();
162
- } catch (err) {}
163
- }, { capture: true, passive: true });
164
-
165
- var protoClick = HTMLButtonElement.prototype.click;
166
- HTMLButtonElement.prototype.click = function () {
167
- try {
168
- var isSidebarToggle =
169
- this.matches('[data-dsh-responsive-part="sidebar-toggle"]') ||
170
- /收起侧边栏|Collapse sidebar|Open sidebar/i.test(this.getAttribute('aria-label') || '');
171
- if (isSidebarToggle && Date.now() - lastEntryTapAt < 900) {
172
- return; // 吞掉紧随工作区/操作控件点击之后的程序化收起
173
- }
174
- } catch (err) {}
175
- return protoClick.apply(this, arguments);
176
- };
177
- }
178
-
179
- function initDraggableNav() {
180
- var logoRow = document.querySelector('[data-pane="sidebar"] [class*="_logoRow"], [class*="_sidebarCol"] [class*="_logoRow"]');
181
- if (!logoRow || logoRow.__dsh_drag_init__) return;
182
- logoRow.__dsh_drag_init__ = true;
183
-
184
- var savedTop = localStorage.getItem('dsh_mobile_nav_top');
185
- if (savedTop && !isNaN(Number(savedTop))) {
186
- var topVal = Math.max(10, Math.min(window.innerHeight - 60, Number(savedTop)));
187
- logoRow.style.setProperty('top', topVal + 'px', 'important');
188
- }
189
-
190
- var startY = 0;
191
- var initialTop = 0;
192
- var isDragging = false;
193
- var hasMoved = false;
194
-
195
- function onTouchStart(e) {
196
- var sidebar = document.querySelector('[data-pane="sidebar"], [class*="_sidebarCol"]');
197
- if (sidebar && !sidebar.querySelector('[class*="_collapsed"]')) return;
198
-
199
- var touch = e.touches ? e.touches[0] : e;
200
- startY = touch.clientY;
201
- var rect = logoRow.getBoundingClientRect();
202
- initialTop = rect.top;
203
- isDragging = true;
204
- hasMoved = false;
205
- }
206
-
207
- function onTouchMove(e) {
208
- if (!isDragging) return;
209
- var touch = e.touches ? e.touches[0] : e;
210
- var deltaY = touch.clientY - startY;
211
-
212
- if (!hasMoved && Math.abs(deltaY) > 5) {
213
- hasMoved = true;
214
- }
215
-
216
- if (hasMoved) {
217
- if (e.cancelable) e.preventDefault();
218
- var newTop = initialTop + deltaY;
219
- var minTop = 10;
220
- var maxTop = window.innerHeight - 56;
221
- newTop = Math.max(minTop, Math.min(maxTop, newTop));
222
- logoRow.style.setProperty('top', newTop + 'px', 'important');
223
- }
224
- }
225
-
226
- function onTouchEnd(e) {
227
- if (!isDragging) return;
228
- isDragging = false;
229
- if (hasMoved) {
230
- e.preventDefault();
231
- e.stopPropagation();
232
- var currentRect = logoRow.getBoundingClientRect();
233
- try {
234
- localStorage.setItem('dsh_mobile_nav_top', String(Math.round(currentRect.top)));
235
- } catch (err) {}
236
- }
237
- }
238
-
239
- logoRow.addEventListener('touchstart', onTouchStart, { passive: true });
240
- window.addEventListener('touchmove', onTouchMove, { passive: false });
241
- window.addEventListener('touchend', onTouchEnd, { capture: true });
242
-
243
- logoRow.addEventListener('mousedown', onTouchStart);
244
- window.addEventListener('mousemove', onTouchMove);
245
- window.addEventListener('mouseup', onTouchEnd, { capture: true });
246
- }
247
-
248
- if (document.readyState === 'loading') {
249
- document.addEventListener('DOMContentLoaded', initDraggableNav);
250
- } else {
251
- initDraggableNav();
252
- }
253
- setInterval(initDraggableNav, 800);
254
- })();
127
+ export const DRAGGABLE_NAV_SNIPPET = `
128
+ (function() {
129
+ if (typeof window === "undefined" || typeof document === "undefined") return;
130
+
131
+ /* ===== 防误收起守卫 =====
132
+ * 第三方生态插件(如 @linxin666/dsh-web-ui-all 的移动端抽屉增强)会在用户点击
133
+ * 侧边栏条目(role=treeitem)后,于下一帧程序化点击「收起侧边栏」按钮自动收回抽屉。
134
+ * 该判定无法区分「选中条目回主区」与「点击条目内的次级操作图标」(更多操作 ⋯ /
135
+ * 展开箭头等),导致后者抽屉被误关。
136
+ * 这里记录最近一次对条目内任意部位(项目名称主体 / 次级按钮 / 箭头图标)的触摸时间,
137
+ * 并包装 HTMLButtonElement.prototype.click:短时间窗口内的程序化收起调用直接忽略。
138
+ * 覆盖条目主体是为了让选中高亮后浮现的展开/收起箭头与新增/更多操作图标有足够的
139
+ * 停留操作时间;用户真实点击收起按钮(浏览器合成事件不走 prototype.click)与
140
+ * 点击抽屉外暗处收起的官方行为均不受影响。 */
141
+ if (!window.__dshRmAntiCollapseInstalled__) {
142
+ window.__dshRmAntiCollapseInstalled__ = true;
143
+ var lastEntryTapAt = 0;
144
+ document.addEventListener('pointerdown', function (e) {
145
+ try {
146
+ var t = e.target;
147
+ if (!t || !t.closest) return;
148
+ if (!t.closest('[data-pane="sidebar"], [class*="_sidebarCol"]')) return;
149
+
150
+ // 会话条目(_sessionRow)除操作按钮(_rowActions/button等)以外的主体点击:
151
+ // 用户意在进入/切换会话并查看正文,不拦截程序化收起(放行自动回主区)
152
+ var isSessionRow = t.closest('[class*="_sessionRow"]');
153
+ var isActionOrToggle = t.closest('[class*="_rowActions"], [class*="_chevron"], [class*="_arrow"], button, [role="button"]');
154
+ if (isSessionRow && !isActionOrToggle) {
155
+ lastEntryTapAt = 0;
156
+ return;
157
+ }
158
+
159
+ var entry = t.closest('[role="treeitem"], [data-dsh-part="sidebar-entry"]');
160
+ if (!entry) return;
161
+ lastEntryTapAt = Date.now();
162
+ } catch (err) {}
163
+ }, { capture: true, passive: true });
164
+
165
+ var protoClick = HTMLButtonElement.prototype.click;
166
+ HTMLButtonElement.prototype.click = function () {
167
+ try {
168
+ var isSidebarToggle =
169
+ this.matches('[data-dsh-responsive-part="sidebar-toggle"]') ||
170
+ /收起侧边栏|Collapse sidebar|Open sidebar/i.test(this.getAttribute('aria-label') || '');
171
+ if (isSidebarToggle && Date.now() - lastEntryTapAt < 900) {
172
+ return; // 吞掉紧随工作区/操作控件点击之后的程序化收起
173
+ }
174
+ } catch (err) {}
175
+ return protoClick.apply(this, arguments);
176
+ };
177
+ }
178
+
179
+ function initDraggableNav() {
180
+ var logoRow = document.querySelector('[data-pane="sidebar"] [class*="_logoRow"], [class*="_sidebarCol"] [class*="_logoRow"]');
181
+ if (!logoRow || logoRow.__dsh_drag_init__) return;
182
+ logoRow.__dsh_drag_init__ = true;
183
+
184
+ var savedTop = localStorage.getItem('dsh_mobile_nav_top');
185
+ if (savedTop && !isNaN(Number(savedTop))) {
186
+ var topVal = Math.max(10, Math.min(window.innerHeight - 60, Number(savedTop)));
187
+ logoRow.style.setProperty('top', topVal + 'px', 'important');
188
+ }
189
+
190
+ var startY = 0;
191
+ var initialTop = 0;
192
+ var isDragging = false;
193
+ var hasMoved = false;
194
+
195
+ function onTouchStart(e) {
196
+ var sidebar = document.querySelector('[data-pane="sidebar"], [class*="_sidebarCol"]');
197
+ if (sidebar && !sidebar.querySelector('[class*="_collapsed"]')) return;
198
+
199
+ var touch = e.touches ? e.touches[0] : e;
200
+ startY = touch.clientY;
201
+ var rect = logoRow.getBoundingClientRect();
202
+ initialTop = rect.top;
203
+ isDragging = true;
204
+ hasMoved = false;
205
+ }
206
+
207
+ function onTouchMove(e) {
208
+ if (!isDragging) return;
209
+ var touch = e.touches ? e.touches[0] : e;
210
+ var deltaY = touch.clientY - startY;
211
+
212
+ if (!hasMoved && Math.abs(deltaY) > 5) {
213
+ hasMoved = true;
214
+ }
215
+
216
+ if (hasMoved) {
217
+ if (e.cancelable) e.preventDefault();
218
+ var newTop = initialTop + deltaY;
219
+ var minTop = 10;
220
+ var maxTop = window.innerHeight - 56;
221
+ newTop = Math.max(minTop, Math.min(maxTop, newTop));
222
+ logoRow.style.setProperty('top', newTop + 'px', 'important');
223
+ }
224
+ }
225
+
226
+ function onTouchEnd(e) {
227
+ if (!isDragging) return;
228
+ isDragging = false;
229
+ if (hasMoved) {
230
+ e.preventDefault();
231
+ e.stopPropagation();
232
+ var currentRect = logoRow.getBoundingClientRect();
233
+ try {
234
+ localStorage.setItem('dsh_mobile_nav_top', String(Math.round(currentRect.top)));
235
+ } catch (err) {}
236
+ }
237
+ }
238
+
239
+ logoRow.addEventListener('touchstart', onTouchStart, { passive: true });
240
+ window.addEventListener('touchmove', onTouchMove, { passive: false });
241
+ window.addEventListener('touchend', onTouchEnd, { capture: true });
242
+
243
+ logoRow.addEventListener('mousedown', onTouchStart);
244
+ window.addEventListener('mousemove', onTouchMove);
245
+ window.addEventListener('mouseup', onTouchEnd, { capture: true });
246
+ }
247
+
248
+ if (document.readyState === 'loading') {
249
+ document.addEventListener('DOMContentLoaded', initDraggableNav);
250
+ } else {
251
+ initDraggableNav();
252
+ }
253
+ setInterval(initDraggableNav, 800);
254
+ })();
255
255
  `;
256
256
  /**
257
257
  * 注入到 index.html 的移动端抽屉「点外部收起」脚本
@@ -264,76 +264,76 @@ export const DRAGGABLE_NAV_SNIPPET = `
264
264
  * 后缀稳定),不依赖 web-ui 注入的 data-pane / data-dsh-frame 属性,因此装/不装 web-ui
265
265
  * 两种环境行为一致。折叠状态沿用官方写入的 data-sidebar-collapsed 属性。
266
266
  */
267
- export const MOBILE_DISMISS_SNIPPET = `
268
- (function() {
269
- if (typeof window === "undefined" || typeof document === "undefined") return;
270
-
271
- var frameSelector = '[class*="_frame"]';
272
- var sidebarSelector = '[class*="_sidebarCol"]';
273
-
274
- function webUiOwnsDrawer() {
275
- return !!document.querySelector('style[data-dsh-compat="responsive"]');
276
- }
277
-
278
- function findToggle(frame) {
279
- return frame.querySelector('[class*="_toggle"]') ||
280
- Array.prototype.filter.call(frame.querySelectorAll('button'), function (b) {
281
- return /收起侧边栏|Collapse sidebar|Open sidebar/i.test(b.getAttribute('aria-label') || '');
282
- })[0] || null;
283
- }
284
-
285
- function install(frame) {
286
- if (frame.__dshRmDismissInstalled__) return;
287
- frame.__dshRmDismissInstalled__ = true;
288
- var raf = 0;
289
-
290
- frame.addEventListener('click', function (event) {
291
- try {
292
- // web-ui 可能运行中才注入 responsive style:每次事件都复查,动态让位
293
- if (webUiOwnsDrawer()) return;
294
- var target = event.target;
295
- if (!target || !(target instanceof Element)) return;
296
- if (typeof window.matchMedia !== 'function' || !window.matchMedia('(max-width: 900px)').matches) return;
297
- var sidebar = frame.querySelector(sidebarSelector);
298
- var toggle = findToggle(frame);
299
- if (!sidebar || !toggle) return;
300
-
301
- // 抽屉展开时点击正文(sidebar 外)→ 触发官方收起按钮,并拦截被吞掉的点击;
302
- // 找不到收起按钮时上面已 return,绝不误拦正文交互;
303
- // 增加对 sidebar 内部 _collapsed 的状态校验,双重保证仅在侧边栏真正展开时才拦截正文点击
304
- var isSidebarOpen = !frame.hasAttribute('data-sidebar-collapsed') && !sidebar.querySelector('[class*="_collapsed"]');
305
- if (isSidebarOpen && !sidebar.contains(target)) {
306
- event.preventDefault();
307
- event.stopPropagation();
308
- toggle.click();
309
- return;
310
- }
311
- // 点击侧边栏内次级操作图标(更多操作 ⋯ / 展开箭头等)、工作区行(_projectRow)或原生切换按钮 → 不收起抽屉
312
- if (target.closest('[class*="_toggle"], [class*="_rowActions"], [class*="_projectRow"], [class*="_chevron"], [class*="_arrow"], button, [role="button"]')) return;
313
- // 点击会话条目(_sessionRow)主体(选中会话后回主区)→ 下一帧程序化收起
314
- if (!target.closest('[class*="_sessionRow"], [role="treeitem"], [data-dsh-part="sidebar-entry"]')) return;
315
- if (raf !== 0) cancelAnimationFrame(raf);
316
- raf = requestAnimationFrame(function () {
317
- raf = 0;
318
- if (!frame.hasAttribute('data-sidebar-collapsed')) toggle.click();
319
- });
320
- } catch (err) {}
321
- }, true);
322
- }
323
-
324
- function ensure() {
325
- if (webUiOwnsDrawer()) return;
326
- var frame = document.querySelector(frameSelector);
327
- if (frame) install(frame);
328
- }
329
-
330
- if (document.readyState === 'loading') {
331
- document.addEventListener('DOMContentLoaded', ensure);
332
- } else {
333
- ensure();
334
- }
335
- setInterval(ensure, 800);
336
- })();
267
+ export const MOBILE_DISMISS_SNIPPET = `
268
+ (function() {
269
+ if (typeof window === "undefined" || typeof document === "undefined") return;
270
+
271
+ var frameSelector = '[class*="_frame"]';
272
+ var sidebarSelector = '[class*="_sidebarCol"]';
273
+
274
+ function webUiOwnsDrawer() {
275
+ return !!document.querySelector('style[data-dsh-compat="responsive"]');
276
+ }
277
+
278
+ function findToggle(frame) {
279
+ return frame.querySelector('[class*="_toggle"]') ||
280
+ Array.prototype.filter.call(frame.querySelectorAll('button'), function (b) {
281
+ return /收起侧边栏|Collapse sidebar|Open sidebar/i.test(b.getAttribute('aria-label') || '');
282
+ })[0] || null;
283
+ }
284
+
285
+ function install(frame) {
286
+ if (frame.__dshRmDismissInstalled__) return;
287
+ frame.__dshRmDismissInstalled__ = true;
288
+ var raf = 0;
289
+
290
+ frame.addEventListener('click', function (event) {
291
+ try {
292
+ // web-ui 可能运行中才注入 responsive style:每次事件都复查,动态让位
293
+ if (webUiOwnsDrawer()) return;
294
+ var target = event.target;
295
+ if (!target || !(target instanceof Element)) return;
296
+ if (typeof window.matchMedia !== 'function' || !window.matchMedia('(max-width: 900px)').matches) return;
297
+ var sidebar = frame.querySelector(sidebarSelector);
298
+ var toggle = findToggle(frame);
299
+ if (!sidebar || !toggle) return;
300
+
301
+ // 抽屉展开时点击正文(sidebar 外)→ 触发官方收起按钮,并拦截被吞掉的点击;
302
+ // 找不到收起按钮时上面已 return,绝不误拦正文交互;
303
+ // 增加对 sidebar 内部 _collapsed 的状态校验,双重保证仅在侧边栏真正展开时才拦截正文点击
304
+ var isSidebarOpen = !frame.hasAttribute('data-sidebar-collapsed') && !sidebar.querySelector('[class*="_collapsed"]');
305
+ if (isSidebarOpen && !sidebar.contains(target)) {
306
+ event.preventDefault();
307
+ event.stopPropagation();
308
+ toggle.click();
309
+ return;
310
+ }
311
+ // 点击侧边栏内次级操作图标(更多操作 ⋯ / 展开箭头等)、工作区行(_projectRow)或原生切换按钮 → 不收起抽屉
312
+ if (target.closest('[class*="_toggle"], [class*="_rowActions"], [class*="_projectRow"], [class*="_chevron"], [class*="_arrow"], button, [role="button"]')) return;
313
+ // 点击会话条目(_sessionRow)主体(选中会话后回主区)→ 下一帧程序化收起
314
+ if (!target.closest('[class*="_sessionRow"], [role="treeitem"], [data-dsh-part="sidebar-entry"]')) return;
315
+ if (raf !== 0) cancelAnimationFrame(raf);
316
+ raf = requestAnimationFrame(function () {
317
+ raf = 0;
318
+ if (!frame.hasAttribute('data-sidebar-collapsed')) toggle.click();
319
+ });
320
+ } catch (err) {}
321
+ }, true);
322
+ }
323
+
324
+ function ensure() {
325
+ if (webUiOwnsDrawer()) return;
326
+ var frame = document.querySelector(frameSelector);
327
+ if (frame) install(frame);
328
+ }
329
+
330
+ if (document.readyState === 'loading') {
331
+ document.addEventListener('DOMContentLoaded', ensure);
332
+ } else {
333
+ ensure();
334
+ }
335
+ setInterval(ensure, 800);
336
+ })();
337
337
  `;
338
338
  /**
339
339
  * 向 HTML 文档的 <head> 顶部安全注入样式片段与 Polyfill 脚本(自带幂等防重守卫)