dsh-mobile-access 1.1.5 → 1.1.7

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.
Files changed (3) hide show
  1. package/README.md +20 -3
  2. package/client.js +114 -0
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -79,7 +79,7 @@ iPhone 打开「设置 → 无线局域网」,确认和 Mac 连的是同一个
79
79
  ### 第 2 步:在 Mac 终端执行一条命令
80
80
 
81
81
  ```bash
82
- dsh plugin --profile web add dsh-mobile-access@1.1.5 --registry=https://registry.npmjs.org/
82
+ dsh plugin --profile web add dsh-mobile-access@1.1.7 --registry=https://registry.npmjs.org/
83
83
  ```
84
84
 
85
85
  这条命令做三件事:
@@ -190,6 +190,21 @@ iOS Safari 会把第一次触摸先用去聚焦,第二次才派发 `click`。
190
190
  如果左侧栏仍然是窄窄的图标条,第一下是展开侧栏——这是官方布局,不是会话行的问题;
191
191
  展开后每个会话只需单击一次。
192
192
 
193
+ ### iPhone 上展开 / 收起左侧栏
194
+
195
+ 从 `1.1.7` 起,iPhone / iPad 上支持:
196
+
197
+ - 在对话内容区**向右滑**:展开左侧栏(仅在左侧栏已收起时生效);
198
+ - 在对话内容区**单击一下**:收起已经展开的左侧栏;
199
+ - 右滑需要水平距离至少 70px,且明显大于上下移动,避免和滚动、
200
+ 横向代码块、输入框、模型菜单和右侧面板冲突;
201
+ - 单击收起只对“内容区”生效,点按钮、输入框、链接、代码块、会话行、
202
+ 模型菜单或右侧面板不会误收起。
203
+
204
+ 普通 Safari 标签页里,右滑会避开屏幕左右各 24px 的边缘,以免和 Safari
205
+ 自带的“后退 / 前进”边缘手势抢事件;添加到主屏幕的独立窗口里没有这个
206
+ 系统手势,因此边缘也可以滑动。Mac 没有触摸事件,完全不受影响。
207
+
193
208
  `1.1.3` 的旧实现只移除 `tabindex`,在部分 iOS Safari 版本上仍可能被第一次触摸吃掉;
194
209
  如果更新到 `1.1.4` 后仍是两次,请彻底关闭这个 Safari 标签页再重新打开(不要只刷新),
195
210
  以排除旧的 `client.js` 缓存。
@@ -370,7 +385,7 @@ iPhone 软键盘没有可用的 Shift 键,因此插件在 **iOS / iPadOS** 上
370
385
  ### 推荐:npm 官方命令
371
386
 
372
387
  ```bash
373
- dsh plugin --profile web add dsh-mobile-access@1.1.5 --registry=https://registry.npmjs.org/
388
+ dsh plugin --profile web add dsh-mobile-access@1.1.7 --registry=https://registry.npmjs.org/
374
389
  ```
375
390
 
376
391
  ### 使用 `@latest`
@@ -500,7 +515,9 @@ dsh plugin --profile web remove dsh-mobile-access
500
515
  - `1.1.0` — 回环监听自动局域网网关(macOS / Windows,HTTP + `/api/remote.mux` WebSocket)。
501
516
  - `1.1.1` — iPhone / iPad 上模型选择页 16px 缩放守卫。
502
517
  - `1.1.2` — 打开模型选择时不自动弹键盘、不整体上移。
503
- - `1.1.5` — Tailscale Serve 经 HTTPS 访问时,允许带 Tailscale 身份头的 `*.ts.net` Host(仅限本机回环转发);当前 npm `latest`。
518
+ - `1.1.7` — iPhone / iPad 上右滑展开左侧栏,点击内容区收起;当前 npm `latest`。
519
+ - `1.1.6` — iPhone / iPad 上右滑展开、左滑收起左侧栏。
520
+ - `1.1.5` — Tailscale Serve 经 HTTPS 访问时,允许带 Tailscale 身份头的 `*.ts.net` Host(仅限本机回环转发)。
504
521
  - `1.1.4` — iPhone / iPad 上第一次点击直接切换会话,并在切换后自动收起左侧栏。
505
522
  - `1.1.3` — 首次尝试临时移除会话行 `tabindex`。
506
523
 
package/client.js CHANGED
@@ -291,6 +291,108 @@ function installIOSSidebarTapGuard(layout) {
291
291
  timers.clear();
292
292
  };
293
293
  }
294
+ /**
295
+ * Right swipe on iPhone opens the left sidebar. The closing gesture is a tap
296
+ * on the content area (not a left swipe): a normal tap is easier to discover
297
+ * and does not compete with horizontal scrolling. The swipe is scoped to the
298
+ * content area and ignores vertical movement, editable controls, menus, code
299
+ * blocks and the right panel. On a normal Safari tab we stay 24px away from the
300
+ * screen edges so the system back/forward gesture keeps priority; an installed
301
+ * PWA has no such gesture, so edge starts are allowed there. Mac never installs
302
+ * this listener.
303
+ */
304
+ function installIOSSidebarSwipeGuard(layout) {
305
+ if (!isAppleMobile() || layout === undefined)
306
+ return () => undefined;
307
+ const doc = document;
308
+ let pending = null;
309
+ const isBlocked = (target) => target.closest('input, textarea, select, [contenteditable="true"], [data-composer-card], pre, code, [role="slider"], [data-rightbar], [role="menu"], [role="listbox"], button, a, [role="button"]') !== null;
310
+ const point = (event) => {
311
+ const touches = event.changedTouches ?? event.touches;
312
+ const touch = touches?.[0];
313
+ return touch === undefined ? null : { x: touch.clientX, y: touch.clientY };
314
+ };
315
+ const onTouchStart = (event) => {
316
+ pending = null;
317
+ const touches = event.touches;
318
+ if (touches !== undefined && touches.length !== 1)
319
+ return;
320
+ const target = event.target;
321
+ if (target === null || typeof target.closest !== 'function' || isBlocked(target))
322
+ return;
323
+ const at = point(event);
324
+ if (at === null)
325
+ return;
326
+ const standalone = navigator.standalone === true;
327
+ if (!standalone && (at.x < 24 || at.x > window.innerWidth - 24))
328
+ return;
329
+ pending = { x: at.x, y: at.y, startedAt: Date.now() };
330
+ };
331
+ const onTouchEnd = (event) => {
332
+ if (pending === null)
333
+ return;
334
+ const { x, y, startedAt } = pending;
335
+ pending = null;
336
+ if (Date.now() - startedAt > 800)
337
+ return;
338
+ const at = point(event);
339
+ if (at === null)
340
+ return;
341
+ const dx = at.x - x;
342
+ const dy = at.y - y;
343
+ if (Math.abs(dx) < 70 || Math.abs(dx) <= Math.abs(dy) * 1.5)
344
+ return;
345
+ const collapsed = doc.querySelector('[data-sidebar-collapsed]') !== null;
346
+ if (!collapsed)
347
+ return;
348
+ try {
349
+ layout.toggleSidebar();
350
+ }
351
+ catch {
352
+ }
353
+ };
354
+ doc.addEventListener('touchstart', onTouchStart, true);
355
+ doc.addEventListener('touchend', onTouchEnd, true);
356
+ return () => {
357
+ doc.removeEventListener('touchstart', onTouchStart, true);
358
+ doc.removeEventListener('touchend', onTouchEnd, true);
359
+ };
360
+ }
361
+ /**
362
+ * A tap on the conversation content closes an expanded narrow sidebar. It has
363
+ * to run in the bubble phase so message links and buttons still receive their
364
+ * click first, and it skips every interactive/row/right-panel target. The
365
+ * sidebar width comes from the frame's inline grid template, so a tap on the
366
+ * left column is not mistaken for content. Mac and wide layouts are untouched.
367
+ */
368
+ function installIOSContentTapCollapse(layout) {
369
+ if (!isAppleMobile() || layout === undefined)
370
+ return () => undefined;
371
+ const doc = document;
372
+ const interactive = 'button, a, input, textarea, select, pre, code, [contenteditable="true"], [data-composer-card], [data-row-key], [role="treeitem"], [role="tree"], nav, [role="menu"], [role="listbox"], [role="button"], [role="menuitem"], [data-rightbar]';
373
+ const onClick = (event) => {
374
+ if (window.innerWidth >= 1024)
375
+ return;
376
+ if (doc.querySelector('[data-sidebar-collapsed]') !== null)
377
+ return;
378
+ const target = event.target;
379
+ if (target === null || typeof target.closest !== 'function' || target.closest(interactive) !== null)
380
+ return;
381
+ const frame = doc.querySelector('[style*="grid-template-columns"]');
382
+ const first = frame?.style.gridTemplateColumns.split(' ')[0] ?? '';
383
+ const width = Number.parseFloat(first);
384
+ const x = event.clientX;
385
+ if (Number.isFinite(width) && x < width)
386
+ return;
387
+ try {
388
+ layout.toggleSidebar();
389
+ }
390
+ catch {
391
+ }
392
+ };
393
+ doc.addEventListener('click', onClick, true);
394
+ return () => doc.removeEventListener('click', onClick, true);
395
+ }
294
396
  /**
295
397
  * The official model menu can move focus into its search field while it opens.
296
398
  * On iPhone that opens the software keyboard and shifts the fixed-position menu
@@ -637,6 +739,18 @@ function factory(require) {
637
739
  catch (error) {
638
740
  ctx.logger?.warn?.(`[mobile-access] iOS sidebar tap guard unavailable: ${String(error)}`);
639
741
  }
742
+ try {
743
+ cleanups.push(installIOSSidebarSwipeGuard(ctx.layout));
744
+ }
745
+ catch (error) {
746
+ ctx.logger?.warn?.(`[mobile-access] iOS sidebar swipe guard unavailable: ${String(error)}`);
747
+ }
748
+ try {
749
+ cleanups.push(installIOSContentTapCollapse(ctx.layout));
750
+ }
751
+ catch (error) {
752
+ ctx.logger?.warn?.(`[mobile-access] iOS content tap collapse unavailable: ${String(error)}`);
753
+ }
640
754
  return () => {
641
755
  for (const cleanup of cleanups.reverse()) {
642
756
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mobile-access",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Independent DSH bundle that pairs iPhones and iPads with the official DeepSeek Harness Web GUI over the local network.",
5
5
  "type": "module",
6
6
  "license": "MIT",