@qzsy/dsh-bridge 3.3.1 → 3.4.0
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/CHANGELOG.md +13 -0
- package/README.md +1 -0
- package/client/client.js +337 -5
- package/client/mobile.js +349 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## [v3.4.0] - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### ✨ 新增
|
|
6
|
+
|
|
7
|
+
- **移动端模型切换器底部 sheet**:宿主的模型二级菜单从桌面 popover 改成全宽底部 sheet——圆角、抓手、遮罩、滚动区、`safe-area` 底部留白,切换模型更接近原生。
|
|
8
|
+
- **模型 sheet 手势**:点遮罩关闭;在顶部抓手上向下拖关闭(跟手 + 松手吸附);桌面端 popover 完全不变。
|
|
9
|
+
- **兼容有/无搜索框的宿主**:优先用「搜索模型」输入框定位;没有搜索框的移动版本按模型触发链 + 菜单行数兜底,不依赖宿主 CSS Modules 哈希。
|
|
10
|
+
|
|
11
|
+
### 🧪 验证
|
|
12
|
+
|
|
13
|
+
- 真实 DSH + 无头 Chrome:模型二级菜单变成 390px 全宽、`z=1200`、18px 顶部圆角、遮罩 `z=1190`;底部安全区叠加后内边距 48px;点遮罩 / 顶部下拉均能关闭,7/7 通过。
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
## [v3.3.1] - 2026-11-02
|
|
4
17
|
|
|
5
18
|
### 🐛 修复
|
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ dsh plugin --profile <name> add @qzsy/dsh-bridge
|
|
|
48
48
|
|
|
49
49
|
- 内容区占满屏宽,左上角「菜单」按钮打开会话 / 工作区 / 插件入口;
|
|
50
50
|
- 支持手势:左缘向右滑打开、抽屉内向左滑关闭,拖动时跟手,松手吸附;
|
|
51
|
+
- 模型切换器在手机上改为底部 sheet:全宽、抓手、遮罩,支持下拉/点遮罩关闭;
|
|
51
52
|
- 全面屏自动避开安全区:刘海 / 状态栏 / 底部 home indicator 不遮挡内容;
|
|
52
53
|
- 点遮罩、点会话行、按 Esc 或转回桌面宽度都会自动收起;
|
|
53
54
|
- 桌面端保持 DSH 原生布局,不受影响。
|
package/client/client.js
CHANGED
|
@@ -236,7 +236,8 @@ var MOBILE_DRAWER_CSS = `
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
.qzsy-mobile-header,
|
|
239
|
-
.qzsy-mobile-backdrop
|
|
239
|
+
.qzsy-mobile-backdrop,
|
|
240
|
+
.qzsy-model-sheet-backdrop {
|
|
240
241
|
display: none;
|
|
241
242
|
}
|
|
242
243
|
|
|
@@ -366,6 +367,109 @@ var MOBILE_DRAWER_CSS = `
|
|
|
366
367
|
opacity: 0;
|
|
367
368
|
pointer-events: none;
|
|
368
369
|
}
|
|
370
|
+
|
|
371
|
+
/* 5. \u6A21\u578B\u5207\u6362\u5668\uFF1A\u628A\u5BBF\u4E3B\u684C\u9762 popover \u6539\u6210\u79FB\u52A8\u7AEF\u5E95\u90E8 sheet */
|
|
372
|
+
@keyframes qzsy-model-sheet-in {
|
|
373
|
+
from { transform: translateY(100%); opacity: 0.6; }
|
|
374
|
+
to { transform: translateY(0); opacity: 1; }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.qzsy-model-sheet-backdrop {
|
|
378
|
+
display: block;
|
|
379
|
+
position: fixed;
|
|
380
|
+
inset: 0;
|
|
381
|
+
z-index: 1190;
|
|
382
|
+
background: rgba(0, 0, 0, 0.4);
|
|
383
|
+
opacity: 0;
|
|
384
|
+
pointer-events: none;
|
|
385
|
+
transition: opacity 240ms ease;
|
|
386
|
+
touch-action: none;
|
|
387
|
+
-webkit-tap-highlight-color: transparent;
|
|
388
|
+
}
|
|
389
|
+
body.qzsy-model-sheet-open .qzsy-model-sheet-backdrop {
|
|
390
|
+
opacity: 1;
|
|
391
|
+
pointer-events: auto;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
[data-qzsy-model-sheet] {
|
|
395
|
+
position: fixed !important;
|
|
396
|
+
top: auto !important;
|
|
397
|
+
left: 0 !important;
|
|
398
|
+
right: 0 !important;
|
|
399
|
+
bottom: 0 !important;
|
|
400
|
+
width: 100vw !important;
|
|
401
|
+
max-width: 100vw !important;
|
|
402
|
+
height: auto !important;
|
|
403
|
+
max-height: 82vh !important;
|
|
404
|
+
max-height: 82dvh !important;
|
|
405
|
+
margin: 0 !important;
|
|
406
|
+
padding: 18px 0 0 !important;
|
|
407
|
+
border: 0 !important;
|
|
408
|
+
border-radius: 18px 18px 0 0 !important;
|
|
409
|
+
background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
|
|
410
|
+
box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.18) !important;
|
|
411
|
+
box-sizing: border-box !important;
|
|
412
|
+
display: flex !important;
|
|
413
|
+
flex-direction: column !important;
|
|
414
|
+
overflow: hidden !important;
|
|
415
|
+
z-index: 1200 !important;
|
|
416
|
+
transform-origin: bottom center !important;
|
|
417
|
+
animation: qzsy-model-sheet-in 260ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
418
|
+
will-change: transform;
|
|
419
|
+
}
|
|
420
|
+
[data-qzsy-model-sheet]::before {
|
|
421
|
+
content: '';
|
|
422
|
+
position: absolute;
|
|
423
|
+
top: 7px;
|
|
424
|
+
left: 50%;
|
|
425
|
+
width: 36px;
|
|
426
|
+
height: 4px;
|
|
427
|
+
margin-left: -18px;
|
|
428
|
+
border-radius: 999px;
|
|
429
|
+
background: rgba(0, 0, 0, 0.18);
|
|
430
|
+
pointer-events: none;
|
|
431
|
+
}
|
|
432
|
+
[data-qzsy-model-sheet] > div,
|
|
433
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
434
|
+
width: 100% !important;
|
|
435
|
+
max-width: 100% !important;
|
|
436
|
+
border: 0 !important;
|
|
437
|
+
border-radius: 0 !important;
|
|
438
|
+
background: transparent !important;
|
|
439
|
+
box-shadow: none !important;
|
|
440
|
+
box-sizing: border-box !important;
|
|
441
|
+
}
|
|
442
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
443
|
+
max-height: calc(82vh - 40px) !important;
|
|
444
|
+
max-height: calc(82dvh - 40px) !important;
|
|
445
|
+
overflow-y: auto !important;
|
|
446
|
+
overscroll-behavior: contain !important;
|
|
447
|
+
padding: 8px 12px calc(14px + var(--qzsy-safe-bottom, 0px)) !important;
|
|
448
|
+
}
|
|
449
|
+
[data-qzsy-model-sheet] input {
|
|
450
|
+
width: 100% !important;
|
|
451
|
+
box-sizing: border-box !important;
|
|
452
|
+
}
|
|
453
|
+
[data-qzsy-model-sheet] button[role="menuitem"],
|
|
454
|
+
[data-qzsy-model-sheet] [role="option"] {
|
|
455
|
+
width: 100% !important;
|
|
456
|
+
box-sizing: border-box !important;
|
|
457
|
+
}
|
|
458
|
+
[data-qzsy-model-sheet].qzsy-model-sheet-closing {
|
|
459
|
+
transform: translateY(100%) !important;
|
|
460
|
+
opacity: 0 !important;
|
|
461
|
+
transition: transform 220ms ease, opacity 220ms ease !important;
|
|
462
|
+
animation: none !important;
|
|
463
|
+
}
|
|
464
|
+
body.qzsy-model-sheet-dragging [data-qzsy-model-sheet] {
|
|
465
|
+
transform: translateY(var(--qzsy-model-sheet-y, 0px)) !important;
|
|
466
|
+
transition: none !important;
|
|
467
|
+
animation: none !important;
|
|
468
|
+
}
|
|
469
|
+
body.qzsy-model-sheet-dragging .qzsy-model-sheet-backdrop {
|
|
470
|
+
opacity: var(--qzsy-model-sheet-progress, 1) !important;
|
|
471
|
+
transition: none !important;
|
|
472
|
+
}
|
|
369
473
|
}
|
|
370
474
|
`;
|
|
371
475
|
function isElement(value) {
|
|
@@ -405,6 +509,11 @@ function setupMobileDrawer() {
|
|
|
405
509
|
let disposed = false;
|
|
406
510
|
let gesture = null;
|
|
407
511
|
let suppressClickUntil = 0;
|
|
512
|
+
let modelPortal = null;
|
|
513
|
+
let modelBackdrop = null;
|
|
514
|
+
let modelChainUntil = 0;
|
|
515
|
+
let modelDrag = null;
|
|
516
|
+
let modelDragHandlers = null;
|
|
408
517
|
const ensureElements = () => {
|
|
409
518
|
if (disposed || !document.body) return;
|
|
410
519
|
injectStyles();
|
|
@@ -484,6 +593,219 @@ function setupMobileDrawer() {
|
|
|
484
593
|
if (!disposed && isOpen()) closeDrawer({ restoreFocus: false });
|
|
485
594
|
}, 60);
|
|
486
595
|
};
|
|
596
|
+
const bodyPortalOf = (node) => {
|
|
597
|
+
let el = node;
|
|
598
|
+
while (el && el.parentElement && el.parentElement !== document.body) el = el.parentElement;
|
|
599
|
+
return el && el.parentElement === document.body ? el : null;
|
|
600
|
+
};
|
|
601
|
+
const visiblePortal = (el) => {
|
|
602
|
+
if (!el || !el.isConnected) return false;
|
|
603
|
+
const s = getComputedStyle(el);
|
|
604
|
+
if (s.display === "none" || s.visibility === "hidden") return false;
|
|
605
|
+
const r = el.getBoundingClientRect();
|
|
606
|
+
return r.width > 0 && r.height > 0;
|
|
607
|
+
};
|
|
608
|
+
const isModelTrigger = (target) => {
|
|
609
|
+
const el = target?.closest?.('button, [role="menuitem"], [role="button"], a');
|
|
610
|
+
if (!el) return false;
|
|
611
|
+
const label = `${el.getAttribute("aria-label") || ""} ${el.getAttribute("title") || ""} ${el.innerText || ""}`;
|
|
612
|
+
return /模型|Model|model/.test(label);
|
|
613
|
+
};
|
|
614
|
+
const findModelPortal = () => {
|
|
615
|
+
if (!isMobileViewport()) return null;
|
|
616
|
+
const search = document.querySelector(
|
|
617
|
+
'input[placeholder*="\u641C\u7D22\u6A21\u578B"], input[placeholder*="Search model"], input[aria-label*="\u641C\u7D22\u6A21\u578B"], input[aria-label*="Search model"]'
|
|
618
|
+
);
|
|
619
|
+
if (search) {
|
|
620
|
+
const menu = search.closest('[role="menu"]') || search.closest('[class*="_menu"]');
|
|
621
|
+
const portal = bodyPortalOf(menu || search);
|
|
622
|
+
if (portal && visiblePortal(portal)) {
|
|
623
|
+
const s = getComputedStyle(portal);
|
|
624
|
+
if ((s.position === "fixed" || s.position === "absolute") && portal.getBoundingClientRect().width > 120) return portal;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (Date.now() > modelChainUntil) return null;
|
|
628
|
+
const candidates = [];
|
|
629
|
+
for (const el of document.body.children) {
|
|
630
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
631
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
632
|
+
if (!visiblePortal(el)) continue;
|
|
633
|
+
const s = getComputedStyle(el);
|
|
634
|
+
if (s.position !== "fixed" && s.position !== "absolute") continue;
|
|
635
|
+
const menu = el.querySelector('[role="menu"], [role="listbox"]');
|
|
636
|
+
if (!menu) continue;
|
|
637
|
+
const text = menu.innerText || "";
|
|
638
|
+
const rows = menu.querySelectorAll('[role="menuitem"], [role="option"], button, a, [tabindex]').length;
|
|
639
|
+
const hasSearch = Boolean(el.querySelector('input[placeholder*="\u641C\u7D22\u6A21\u578B"], input[placeholder*="Search model"]'));
|
|
640
|
+
if (/推理等级|Reasoning effort|推理强度/i.test(text) && rows <= 2) continue;
|
|
641
|
+
const rect = el.getBoundingClientRect();
|
|
642
|
+
const alreadySheet = rect.width >= window.innerWidth - 2 && Math.abs(rect.bottom - window.innerHeight) < 2 && rect.height > 120;
|
|
643
|
+
if (alreadySheet && !hasSearch) continue;
|
|
644
|
+
if (hasSearch || rows > 2) candidates.push({ el, rows, hasSearch });
|
|
645
|
+
}
|
|
646
|
+
candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch) || b.rows - a.rows);
|
|
647
|
+
return candidates[0]?.el || null;
|
|
648
|
+
};
|
|
649
|
+
const hostBackdropPresent = () => {
|
|
650
|
+
for (const el of document.body.children) {
|
|
651
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
652
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
653
|
+
const s = getComputedStyle(el);
|
|
654
|
+
if (s.position !== "fixed") continue;
|
|
655
|
+
const r = el.getBoundingClientRect();
|
|
656
|
+
if (r.width < window.innerWidth - 4 || r.height < window.innerHeight - 4) continue;
|
|
657
|
+
if (Number(s.opacity) < 0.1) continue;
|
|
658
|
+
const bg = s.backgroundColor || "";
|
|
659
|
+
if (!/^rgba?\(/.test(bg) || /rgba\(0,\s*0,\s*0,\s*0\)/.test(bg)) continue;
|
|
660
|
+
if (el.querySelector('[role="menu"]')) continue;
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
return false;
|
|
664
|
+
};
|
|
665
|
+
const requestHostModelClose = (portal) => {
|
|
666
|
+
if (disposed || !portal || !portal.isConnected) return;
|
|
667
|
+
const PointerCtor = typeof window.PointerEvent === "function" ? window.PointerEvent : window.MouseEvent;
|
|
668
|
+
try {
|
|
669
|
+
document.body.dispatchEvent(new PointerCtor("pointerdown", { bubbles: true, cancelable: true }));
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }));
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
window.setTimeout(() => {
|
|
677
|
+
if (disposed || !portal.isConnected) return;
|
|
678
|
+
const trigger = document.querySelector('button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="\u9009\u62E9\u6A21\u578B"], button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="Select model"]');
|
|
679
|
+
try {
|
|
680
|
+
trigger?.click();
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
window.setTimeout(() => {
|
|
684
|
+
if (!disposed && portal.isConnected && modelPortal === portal) clearModelSheet();
|
|
685
|
+
}, 450);
|
|
686
|
+
}, 120);
|
|
687
|
+
};
|
|
688
|
+
const closeModelSheetNow = () => {
|
|
689
|
+
const portal = modelPortal;
|
|
690
|
+
if (!portal) return;
|
|
691
|
+
document.body.classList.remove("qzsy-model-sheet-open");
|
|
692
|
+
document.body.style.removeProperty("--qzsy-model-sheet-y");
|
|
693
|
+
document.body.style.removeProperty("--qzsy-model-sheet-progress");
|
|
694
|
+
requestHostModelClose(portal);
|
|
695
|
+
};
|
|
696
|
+
const closeModelSheetAnimated = () => {
|
|
697
|
+
const portal = modelPortal;
|
|
698
|
+
if (!portal) return;
|
|
699
|
+
portal.classList.add("qzsy-model-sheet-closing");
|
|
700
|
+
document.body.classList.remove("qzsy-model-sheet-open");
|
|
701
|
+
window.setTimeout(() => requestHostModelClose(portal), 180);
|
|
702
|
+
};
|
|
703
|
+
const ensureModelBackdrop = () => {
|
|
704
|
+
if (modelBackdrop || hostBackdropPresent()) return;
|
|
705
|
+
modelBackdrop = document.createElement("div");
|
|
706
|
+
modelBackdrop.className = "qzsy-model-sheet-backdrop";
|
|
707
|
+
modelBackdrop.setAttribute("aria-hidden", "true");
|
|
708
|
+
modelBackdrop.addEventListener("pointerdown", (event) => event.stopPropagation(), true);
|
|
709
|
+
modelBackdrop.addEventListener("click", (event) => {
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
event.stopPropagation();
|
|
712
|
+
closeModelSheetNow();
|
|
713
|
+
});
|
|
714
|
+
document.body.appendChild(modelBackdrop);
|
|
715
|
+
};
|
|
716
|
+
const detachModelDrag = () => {
|
|
717
|
+
if (!modelPortal || !modelDragHandlers) return;
|
|
718
|
+
modelPortal.removeEventListener("touchstart", modelDragHandlers.onStart);
|
|
719
|
+
modelPortal.removeEventListener("touchmove", modelDragHandlers.onMove);
|
|
720
|
+
modelPortal.removeEventListener("touchend", modelDragHandlers.onEnd);
|
|
721
|
+
modelPortal.removeEventListener("touchcancel", modelDragHandlers.onEnd);
|
|
722
|
+
modelDragHandlers = null;
|
|
723
|
+
};
|
|
724
|
+
const attachModelDrag = (portal) => {
|
|
725
|
+
if (!portal || modelDragHandlers) return;
|
|
726
|
+
const onStart = (event) => {
|
|
727
|
+
if (event.touches?.length !== 1) return;
|
|
728
|
+
const touch = event.touches[0];
|
|
729
|
+
const rect = portal.getBoundingClientRect();
|
|
730
|
+
if (touch.clientY - rect.top > 30) return;
|
|
731
|
+
if (event.target.closest?.('input, textarea, select, button, a, [contenteditable="true"]')) return;
|
|
732
|
+
modelDrag = { startY: touch.clientY, startT: Date.now(), dy: 0, active: false, height: rect.height };
|
|
733
|
+
};
|
|
734
|
+
const onMove = (event) => {
|
|
735
|
+
if (!modelDrag) return;
|
|
736
|
+
const touch = event.touches?.[0];
|
|
737
|
+
if (!touch) return;
|
|
738
|
+
const dy = touch.clientY - modelDrag.startY;
|
|
739
|
+
if (!modelDrag.active) {
|
|
740
|
+
if (dy < 6) return;
|
|
741
|
+
modelDrag.active = true;
|
|
742
|
+
document.body.classList.add("qzsy-model-sheet-dragging");
|
|
743
|
+
}
|
|
744
|
+
modelDrag.dy = Math.max(0, dy);
|
|
745
|
+
document.body.style.setProperty("--qzsy-model-sheet-y", `${modelDrag.dy}px`);
|
|
746
|
+
document.body.style.setProperty("--qzsy-model-sheet-progress", String(Math.max(0, 1 - modelDrag.dy / Math.max(1, modelDrag.height))));
|
|
747
|
+
if (event.cancelable) event.preventDefault();
|
|
748
|
+
};
|
|
749
|
+
const onEnd = () => {
|
|
750
|
+
if (!modelDrag) return;
|
|
751
|
+
const drag = modelDrag;
|
|
752
|
+
modelDrag = null;
|
|
753
|
+
if (!drag.active) return;
|
|
754
|
+
document.body.classList.remove("qzsy-model-sheet-dragging");
|
|
755
|
+
document.body.style.removeProperty("--qzsy-model-sheet-y");
|
|
756
|
+
document.body.style.removeProperty("--qzsy-model-sheet-progress");
|
|
757
|
+
const velocity = drag.dy / Math.max(1, Date.now() - drag.startT);
|
|
758
|
+
if (drag.dy > 110 || velocity > 0.5) closeModelSheetAnimated();
|
|
759
|
+
};
|
|
760
|
+
portal.addEventListener("touchstart", onStart, { passive: true });
|
|
761
|
+
portal.addEventListener("touchmove", onMove, { passive: false });
|
|
762
|
+
portal.addEventListener("touchend", onEnd, { passive: true });
|
|
763
|
+
portal.addEventListener("touchcancel", onEnd, { passive: true });
|
|
764
|
+
modelDragHandlers = { onStart, onMove, onEnd };
|
|
765
|
+
};
|
|
766
|
+
const clearModelSheet = () => {
|
|
767
|
+
if (modelPortal) {
|
|
768
|
+
detachModelDrag();
|
|
769
|
+
modelPortal.removeAttribute("data-qzsy-model-sheet");
|
|
770
|
+
modelPortal.classList.remove("qzsy-model-sheet", "qzsy-model-sheet-closing");
|
|
771
|
+
}
|
|
772
|
+
modelPortal = null;
|
|
773
|
+
modelDrag = null;
|
|
774
|
+
document.body?.classList.remove("qzsy-model-sheet-open", "qzsy-model-sheet-dragging");
|
|
775
|
+
document.body?.style.removeProperty("--qzsy-model-sheet-y");
|
|
776
|
+
document.body?.style.removeProperty("--qzsy-model-sheet-progress");
|
|
777
|
+
if (modelBackdrop) {
|
|
778
|
+
modelBackdrop.remove();
|
|
779
|
+
modelBackdrop = null;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
const applyModelSheet = (portal) => {
|
|
783
|
+
if (modelPortal === portal) return;
|
|
784
|
+
clearModelSheet();
|
|
785
|
+
modelPortal = portal;
|
|
786
|
+
portal.setAttribute("data-qzsy-model-sheet", "");
|
|
787
|
+
portal.classList.add("qzsy-model-sheet");
|
|
788
|
+
attachModelDrag(portal);
|
|
789
|
+
ensureModelBackdrop();
|
|
790
|
+
window.requestAnimationFrame(() => {
|
|
791
|
+
if (!disposed && modelPortal === portal) document.body.classList.add("qzsy-model-sheet-open");
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
const syncModelSheet = () => {
|
|
795
|
+
if (disposed) return;
|
|
796
|
+
if (!isMobileViewport()) {
|
|
797
|
+
clearModelSheet();
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
|
|
801
|
+
const found = findModelPortal();
|
|
802
|
+
if (!found) {
|
|
803
|
+
if (modelPortal?.isConnected) return;
|
|
804
|
+
if (modelPortal) clearModelSheet();
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (found !== modelPortal) applyModelSheet(found);
|
|
808
|
+
};
|
|
487
809
|
const onClick = (event) => {
|
|
488
810
|
if (Date.now() < suppressClickUntil) {
|
|
489
811
|
event.preventDefault();
|
|
@@ -498,6 +820,11 @@ function setupMobileDrawer() {
|
|
|
498
820
|
else openDrawer();
|
|
499
821
|
return;
|
|
500
822
|
}
|
|
823
|
+
if (isModelTrigger(target)) {
|
|
824
|
+
modelChainUntil = Date.now() + 6e3;
|
|
825
|
+
window.setTimeout(syncModelSheet, 30);
|
|
826
|
+
window.setTimeout(syncModelSheet, 180);
|
|
827
|
+
}
|
|
501
828
|
if (!isOpen()) return;
|
|
502
829
|
if (target.closest(`.${BACKDROP_CLASS}`)) {
|
|
503
830
|
closeDrawer();
|
|
@@ -531,6 +858,7 @@ function setupMobileDrawer() {
|
|
|
531
858
|
clearDrag();
|
|
532
859
|
closeDrawer({ restoreFocus: false });
|
|
533
860
|
}
|
|
861
|
+
syncModelSheet();
|
|
534
862
|
};
|
|
535
863
|
const onVisibilityChange = () => {
|
|
536
864
|
if (document.visibilityState === "hidden" && isOpen()) {
|
|
@@ -538,9 +866,10 @@ function setupMobileDrawer() {
|
|
|
538
866
|
clearDrag();
|
|
539
867
|
closeDrawer({ restoreFocus: false });
|
|
540
868
|
}
|
|
869
|
+
if (document.visibilityState === "visible") syncModelSheet();
|
|
541
870
|
};
|
|
542
871
|
const onTouchStart = (event) => {
|
|
543
|
-
if (disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
872
|
+
if (modelPortal || disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
544
873
|
const touch = event.touches[0];
|
|
545
874
|
const sidebar = document.querySelector('div[class*="_sidebarCol"]');
|
|
546
875
|
if (!sidebar) return;
|
|
@@ -572,7 +901,7 @@ function setupMobileDrawer() {
|
|
|
572
901
|
};
|
|
573
902
|
};
|
|
574
903
|
const onTouchMove = (event) => {
|
|
575
|
-
if (!gesture || disposed) return;
|
|
904
|
+
if (modelPortal || !gesture || disposed) return;
|
|
576
905
|
const touch = event.touches?.[0];
|
|
577
906
|
if (!touch) return;
|
|
578
907
|
const dx = touch.clientX - gesture.startX;
|
|
@@ -620,11 +949,11 @@ function setupMobileDrawer() {
|
|
|
620
949
|
window.requestAnimationFrame(clearDrag);
|
|
621
950
|
};
|
|
622
951
|
const onTouchEnd = (event) => {
|
|
623
|
-
if (!gesture) return;
|
|
952
|
+
if (modelPortal || !gesture) return;
|
|
624
953
|
finishGesture(event.changedTouches?.[0]);
|
|
625
954
|
};
|
|
626
955
|
const onTouchCancel = () => {
|
|
627
|
-
if (!gesture) return;
|
|
956
|
+
if (modelPortal || !gesture) return;
|
|
628
957
|
finishGesture(null);
|
|
629
958
|
};
|
|
630
959
|
injectStyles();
|
|
@@ -645,9 +974,11 @@ function setupMobileDrawer() {
|
|
|
645
974
|
if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
646
975
|
ensureElements();
|
|
647
976
|
}
|
|
977
|
+
syncModelSheet();
|
|
648
978
|
});
|
|
649
979
|
observer.observe(document.body, { childList: true });
|
|
650
980
|
}
|
|
981
|
+
syncModelSheet();
|
|
651
982
|
syncAria();
|
|
652
983
|
return () => {
|
|
653
984
|
disposed = true;
|
|
@@ -664,6 +995,7 @@ function setupMobileDrawer() {
|
|
|
664
995
|
window.removeEventListener("touchmove", onTouchMove);
|
|
665
996
|
window.removeEventListener("touchend", onTouchEnd);
|
|
666
997
|
window.removeEventListener("touchcancel", onTouchCancel);
|
|
998
|
+
clearModelSheet();
|
|
667
999
|
clearDrag();
|
|
668
1000
|
document.body?.classList.remove(OPEN_CLASS);
|
|
669
1001
|
document.body?.classList.remove("qzsy-drawer-ready");
|
package/client/mobile.js
CHANGED
|
@@ -33,7 +33,8 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.qzsy-mobile-header,
|
|
36
|
-
.qzsy-mobile-backdrop
|
|
36
|
+
.qzsy-mobile-backdrop,
|
|
37
|
+
.qzsy-model-sheet-backdrop {
|
|
37
38
|
display: none;
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -163,6 +164,109 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
163
164
|
opacity: 0;
|
|
164
165
|
pointer-events: none;
|
|
165
166
|
}
|
|
167
|
+
|
|
168
|
+
/* 5. 模型切换器:把宿主桌面 popover 改成移动端底部 sheet */
|
|
169
|
+
@keyframes qzsy-model-sheet-in {
|
|
170
|
+
from { transform: translateY(100%); opacity: 0.6; }
|
|
171
|
+
to { transform: translateY(0); opacity: 1; }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.qzsy-model-sheet-backdrop {
|
|
175
|
+
display: block;
|
|
176
|
+
position: fixed;
|
|
177
|
+
inset: 0;
|
|
178
|
+
z-index: 1190;
|
|
179
|
+
background: rgba(0, 0, 0, 0.4);
|
|
180
|
+
opacity: 0;
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
transition: opacity 240ms ease;
|
|
183
|
+
touch-action: none;
|
|
184
|
+
-webkit-tap-highlight-color: transparent;
|
|
185
|
+
}
|
|
186
|
+
body.qzsy-model-sheet-open .qzsy-model-sheet-backdrop {
|
|
187
|
+
opacity: 1;
|
|
188
|
+
pointer-events: auto;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
[data-qzsy-model-sheet] {
|
|
192
|
+
position: fixed !important;
|
|
193
|
+
top: auto !important;
|
|
194
|
+
left: 0 !important;
|
|
195
|
+
right: 0 !important;
|
|
196
|
+
bottom: 0 !important;
|
|
197
|
+
width: 100vw !important;
|
|
198
|
+
max-width: 100vw !important;
|
|
199
|
+
height: auto !important;
|
|
200
|
+
max-height: 82vh !important;
|
|
201
|
+
max-height: 82dvh !important;
|
|
202
|
+
margin: 0 !important;
|
|
203
|
+
padding: 18px 0 0 !important;
|
|
204
|
+
border: 0 !important;
|
|
205
|
+
border-radius: 18px 18px 0 0 !important;
|
|
206
|
+
background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
|
|
207
|
+
box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.18) !important;
|
|
208
|
+
box-sizing: border-box !important;
|
|
209
|
+
display: flex !important;
|
|
210
|
+
flex-direction: column !important;
|
|
211
|
+
overflow: hidden !important;
|
|
212
|
+
z-index: 1200 !important;
|
|
213
|
+
transform-origin: bottom center !important;
|
|
214
|
+
animation: qzsy-model-sheet-in 260ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
215
|
+
will-change: transform;
|
|
216
|
+
}
|
|
217
|
+
[data-qzsy-model-sheet]::before {
|
|
218
|
+
content: '';
|
|
219
|
+
position: absolute;
|
|
220
|
+
top: 7px;
|
|
221
|
+
left: 50%;
|
|
222
|
+
width: 36px;
|
|
223
|
+
height: 4px;
|
|
224
|
+
margin-left: -18px;
|
|
225
|
+
border-radius: 999px;
|
|
226
|
+
background: rgba(0, 0, 0, 0.18);
|
|
227
|
+
pointer-events: none;
|
|
228
|
+
}
|
|
229
|
+
[data-qzsy-model-sheet] > div,
|
|
230
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
231
|
+
width: 100% !important;
|
|
232
|
+
max-width: 100% !important;
|
|
233
|
+
border: 0 !important;
|
|
234
|
+
border-radius: 0 !important;
|
|
235
|
+
background: transparent !important;
|
|
236
|
+
box-shadow: none !important;
|
|
237
|
+
box-sizing: border-box !important;
|
|
238
|
+
}
|
|
239
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
240
|
+
max-height: calc(82vh - 40px) !important;
|
|
241
|
+
max-height: calc(82dvh - 40px) !important;
|
|
242
|
+
overflow-y: auto !important;
|
|
243
|
+
overscroll-behavior: contain !important;
|
|
244
|
+
padding: 8px 12px calc(14px + var(--qzsy-safe-bottom, 0px)) !important;
|
|
245
|
+
}
|
|
246
|
+
[data-qzsy-model-sheet] input {
|
|
247
|
+
width: 100% !important;
|
|
248
|
+
box-sizing: border-box !important;
|
|
249
|
+
}
|
|
250
|
+
[data-qzsy-model-sheet] button[role="menuitem"],
|
|
251
|
+
[data-qzsy-model-sheet] [role="option"] {
|
|
252
|
+
width: 100% !important;
|
|
253
|
+
box-sizing: border-box !important;
|
|
254
|
+
}
|
|
255
|
+
[data-qzsy-model-sheet].qzsy-model-sheet-closing {
|
|
256
|
+
transform: translateY(100%) !important;
|
|
257
|
+
opacity: 0 !important;
|
|
258
|
+
transition: transform 220ms ease, opacity 220ms ease !important;
|
|
259
|
+
animation: none !important;
|
|
260
|
+
}
|
|
261
|
+
body.qzsy-model-sheet-dragging [data-qzsy-model-sheet] {
|
|
262
|
+
transform: translateY(var(--qzsy-model-sheet-y, 0px)) !important;
|
|
263
|
+
transition: none !important;
|
|
264
|
+
animation: none !important;
|
|
265
|
+
}
|
|
266
|
+
body.qzsy-model-sheet-dragging .qzsy-model-sheet-backdrop {
|
|
267
|
+
opacity: var(--qzsy-model-sheet-progress, 1) !important;
|
|
268
|
+
transition: none !important;
|
|
269
|
+
}
|
|
166
270
|
}
|
|
167
271
|
`;
|
|
168
272
|
|
|
@@ -211,6 +315,11 @@ export function setupMobileDrawer() {
|
|
|
211
315
|
let disposed = false;
|
|
212
316
|
let gesture = null;
|
|
213
317
|
let suppressClickUntil = 0;
|
|
318
|
+
let modelPortal = null;
|
|
319
|
+
let modelBackdrop = null;
|
|
320
|
+
let modelChainUntil = 0;
|
|
321
|
+
let modelDrag = null;
|
|
322
|
+
let modelDragHandlers = null;
|
|
214
323
|
|
|
215
324
|
const ensureElements = () => {
|
|
216
325
|
if (disposed || !document.body) return;
|
|
@@ -296,6 +405,228 @@ export function setupMobileDrawer() {
|
|
|
296
405
|
}, 60);
|
|
297
406
|
};
|
|
298
407
|
|
|
408
|
+
// ---- 模型切换器:宿主桌面 popover → 移动端底部 sheet ----
|
|
409
|
+
const bodyPortalOf = (node) => {
|
|
410
|
+
let el = node;
|
|
411
|
+
while (el && el.parentElement && el.parentElement !== document.body) el = el.parentElement;
|
|
412
|
+
return el && el.parentElement === document.body ? el : null;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const visiblePortal = (el) => {
|
|
416
|
+
if (!el || !el.isConnected) return false;
|
|
417
|
+
const s = getComputedStyle(el);
|
|
418
|
+
if (s.display === 'none' || s.visibility === 'hidden') return false;
|
|
419
|
+
const r = el.getBoundingClientRect();
|
|
420
|
+
return r.width > 0 && r.height > 0;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const isModelTrigger = (target) => {
|
|
424
|
+
const el = target?.closest?.('button, [role="menuitem"], [role="button"], a');
|
|
425
|
+
if (!el) return false;
|
|
426
|
+
const label = `${el.getAttribute('aria-label') || ''} ${el.getAttribute('title') || ''} ${el.innerText || ''}`;
|
|
427
|
+
return /模型|Model|model/.test(label);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const findModelPortal = () => {
|
|
431
|
+
if (!isMobileViewport()) return null;
|
|
432
|
+
const search = document.querySelector(
|
|
433
|
+
'input[placeholder*="搜索模型"], input[placeholder*="Search model"], input[aria-label*="搜索模型"], input[aria-label*="Search model"]',
|
|
434
|
+
);
|
|
435
|
+
if (search) {
|
|
436
|
+
const menu = search.closest('[role="menu"]') || search.closest('[class*="_menu"]');
|
|
437
|
+
const portal = bodyPortalOf(menu || search);
|
|
438
|
+
if (portal && visiblePortal(portal)) {
|
|
439
|
+
const s = getComputedStyle(portal);
|
|
440
|
+
if ((s.position === 'fixed' || s.position === 'absolute') && portal.getBoundingClientRect().width > 120) return portal;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// 无搜索框的宿主(如移动端版本)用「模型触发链 + 菜单行数」兜底
|
|
444
|
+
if (Date.now() > modelChainUntil) return null;
|
|
445
|
+
const candidates = [];
|
|
446
|
+
for (const el of document.body.children) {
|
|
447
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
448
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
449
|
+
if (!visiblePortal(el)) continue;
|
|
450
|
+
const s = getComputedStyle(el);
|
|
451
|
+
if (s.position !== 'fixed' && s.position !== 'absolute') continue;
|
|
452
|
+
const menu = el.querySelector('[role="menu"], [role="listbox"]');
|
|
453
|
+
if (!menu) continue;
|
|
454
|
+
const text = menu.innerText || '';
|
|
455
|
+
const rows = menu.querySelectorAll('[role="menuitem"], [role="option"], button, a, [tabindex]').length;
|
|
456
|
+
const hasSearch = Boolean(el.querySelector('input[placeholder*="搜索模型"], input[placeholder*="Search model"]'));
|
|
457
|
+
// 排除「模型 / 推理等级」两行的第一层 popover
|
|
458
|
+
if (/推理等级|Reasoning effort|推理强度/i.test(text) && rows <= 2) continue;
|
|
459
|
+
// 已经是全宽底部 sheet 的宿主菜单不再重复改造
|
|
460
|
+
const rect = el.getBoundingClientRect();
|
|
461
|
+
const alreadySheet = rect.width >= window.innerWidth - 2 && Math.abs(rect.bottom - window.innerHeight) < 2 && rect.height > 120;
|
|
462
|
+
if (alreadySheet && !hasSearch) continue;
|
|
463
|
+
if (hasSearch || rows > 2) candidates.push({ el, rows, hasSearch });
|
|
464
|
+
}
|
|
465
|
+
candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch) || b.rows - a.rows);
|
|
466
|
+
return candidates[0]?.el || null;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const hostBackdropPresent = () => {
|
|
470
|
+
for (const el of document.body.children) {
|
|
471
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
472
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
473
|
+
const s = getComputedStyle(el);
|
|
474
|
+
if (s.position !== 'fixed') continue;
|
|
475
|
+
const r = el.getBoundingClientRect();
|
|
476
|
+
if (r.width < window.innerWidth - 4 || r.height < window.innerHeight - 4) continue;
|
|
477
|
+
if (Number(s.opacity) < 0.1) continue;
|
|
478
|
+
const bg = s.backgroundColor || '';
|
|
479
|
+
if (!/^rgba?\(/.test(bg) || /rgba\(0,\s*0,\s*0,\s*0\)/.test(bg)) continue;
|
|
480
|
+
if (el.querySelector('[role="menu"]')) continue;
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const requestHostModelClose = (portal) => {
|
|
487
|
+
if (disposed || !portal || !portal.isConnected) return;
|
|
488
|
+
const PointerCtor = typeof window.PointerEvent === 'function' ? window.PointerEvent : window.MouseEvent;
|
|
489
|
+
try { document.body.dispatchEvent(new PointerCtor('pointerdown', { bubbles: true, cancelable: true })); } catch { /* 忽略 */ }
|
|
490
|
+
try { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })); } catch { /* 忽略 */ }
|
|
491
|
+
window.setTimeout(() => {
|
|
492
|
+
if (disposed || !portal.isConnected) return;
|
|
493
|
+
const trigger = document.querySelector('button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="选择模型"], button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="Select model"]');
|
|
494
|
+
try { trigger?.click(); } catch { /* 忽略 */ }
|
|
495
|
+
window.setTimeout(() => {
|
|
496
|
+
if (!disposed && portal.isConnected && modelPortal === portal) clearModelSheet();
|
|
497
|
+
}, 450);
|
|
498
|
+
}, 120);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const closeModelSheetNow = () => {
|
|
502
|
+
const portal = modelPortal;
|
|
503
|
+
if (!portal) return;
|
|
504
|
+
document.body.classList.remove('qzsy-model-sheet-open');
|
|
505
|
+
document.body.style.removeProperty('--qzsy-model-sheet-y');
|
|
506
|
+
document.body.style.removeProperty('--qzsy-model-sheet-progress');
|
|
507
|
+
requestHostModelClose(portal);
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const closeModelSheetAnimated = () => {
|
|
511
|
+
const portal = modelPortal;
|
|
512
|
+
if (!portal) return;
|
|
513
|
+
portal.classList.add('qzsy-model-sheet-closing');
|
|
514
|
+
document.body.classList.remove('qzsy-model-sheet-open');
|
|
515
|
+
window.setTimeout(() => requestHostModelClose(portal), 180);
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const ensureModelBackdrop = () => {
|
|
519
|
+
if (modelBackdrop || hostBackdropPresent()) return;
|
|
520
|
+
modelBackdrop = document.createElement('div');
|
|
521
|
+
modelBackdrop.className = 'qzsy-model-sheet-backdrop';
|
|
522
|
+
modelBackdrop.setAttribute('aria-hidden', 'true');
|
|
523
|
+
modelBackdrop.addEventListener('pointerdown', (event) => event.stopPropagation(), true);
|
|
524
|
+
modelBackdrop.addEventListener('click', (event) => {
|
|
525
|
+
event.preventDefault();
|
|
526
|
+
event.stopPropagation();
|
|
527
|
+
closeModelSheetNow();
|
|
528
|
+
});
|
|
529
|
+
document.body.appendChild(modelBackdrop);
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const detachModelDrag = () => {
|
|
533
|
+
if (!modelPortal || !modelDragHandlers) return;
|
|
534
|
+
modelPortal.removeEventListener('touchstart', modelDragHandlers.onStart);
|
|
535
|
+
modelPortal.removeEventListener('touchmove', modelDragHandlers.onMove);
|
|
536
|
+
modelPortal.removeEventListener('touchend', modelDragHandlers.onEnd);
|
|
537
|
+
modelPortal.removeEventListener('touchcancel', modelDragHandlers.onEnd);
|
|
538
|
+
modelDragHandlers = null;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const attachModelDrag = (portal) => {
|
|
542
|
+
if (!portal || modelDragHandlers) return;
|
|
543
|
+
const onStart = (event) => {
|
|
544
|
+
if (event.touches?.length !== 1) return;
|
|
545
|
+
const touch = event.touches[0];
|
|
546
|
+
const rect = portal.getBoundingClientRect();
|
|
547
|
+
if (touch.clientY - rect.top > 30) return;
|
|
548
|
+
if (event.target.closest?.('input, textarea, select, button, a, [contenteditable="true"]')) return;
|
|
549
|
+
modelDrag = { startY: touch.clientY, startT: Date.now(), dy: 0, active: false, height: rect.height };
|
|
550
|
+
};
|
|
551
|
+
const onMove = (event) => {
|
|
552
|
+
if (!modelDrag) return;
|
|
553
|
+
const touch = event.touches?.[0];
|
|
554
|
+
if (!touch) return;
|
|
555
|
+
const dy = touch.clientY - modelDrag.startY;
|
|
556
|
+
if (!modelDrag.active) {
|
|
557
|
+
if (dy < 6) return;
|
|
558
|
+
modelDrag.active = true;
|
|
559
|
+
document.body.classList.add('qzsy-model-sheet-dragging');
|
|
560
|
+
}
|
|
561
|
+
modelDrag.dy = Math.max(0, dy);
|
|
562
|
+
document.body.style.setProperty('--qzsy-model-sheet-y', `${modelDrag.dy}px`);
|
|
563
|
+
document.body.style.setProperty('--qzsy-model-sheet-progress', String(Math.max(0, 1 - modelDrag.dy / Math.max(1, modelDrag.height))));
|
|
564
|
+
if (event.cancelable) event.preventDefault();
|
|
565
|
+
};
|
|
566
|
+
const onEnd = () => {
|
|
567
|
+
if (!modelDrag) return;
|
|
568
|
+
const drag = modelDrag;
|
|
569
|
+
modelDrag = null;
|
|
570
|
+
if (!drag.active) return;
|
|
571
|
+
document.body.classList.remove('qzsy-model-sheet-dragging');
|
|
572
|
+
document.body.style.removeProperty('--qzsy-model-sheet-y');
|
|
573
|
+
document.body.style.removeProperty('--qzsy-model-sheet-progress');
|
|
574
|
+
const velocity = drag.dy / Math.max(1, Date.now() - drag.startT);
|
|
575
|
+
if (drag.dy > 110 || velocity > 0.5) closeModelSheetAnimated();
|
|
576
|
+
};
|
|
577
|
+
portal.addEventListener('touchstart', onStart, { passive: true });
|
|
578
|
+
portal.addEventListener('touchmove', onMove, { passive: false });
|
|
579
|
+
portal.addEventListener('touchend', onEnd, { passive: true });
|
|
580
|
+
portal.addEventListener('touchcancel', onEnd, { passive: true });
|
|
581
|
+
modelDragHandlers = { onStart, onMove, onEnd };
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const clearModelSheet = () => {
|
|
585
|
+
if (modelPortal) {
|
|
586
|
+
detachModelDrag();
|
|
587
|
+
modelPortal.removeAttribute('data-qzsy-model-sheet');
|
|
588
|
+
modelPortal.classList.remove('qzsy-model-sheet', 'qzsy-model-sheet-closing');
|
|
589
|
+
}
|
|
590
|
+
modelPortal = null;
|
|
591
|
+
modelDrag = null;
|
|
592
|
+
document.body?.classList.remove('qzsy-model-sheet-open', 'qzsy-model-sheet-dragging');
|
|
593
|
+
document.body?.style.removeProperty('--qzsy-model-sheet-y');
|
|
594
|
+
document.body?.style.removeProperty('--qzsy-model-sheet-progress');
|
|
595
|
+
if (modelBackdrop) {
|
|
596
|
+
modelBackdrop.remove();
|
|
597
|
+
modelBackdrop = null;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const applyModelSheet = (portal) => {
|
|
602
|
+
if (modelPortal === portal) return;
|
|
603
|
+
clearModelSheet();
|
|
604
|
+
modelPortal = portal;
|
|
605
|
+
portal.setAttribute('data-qzsy-model-sheet', '');
|
|
606
|
+
portal.classList.add('qzsy-model-sheet');
|
|
607
|
+
attachModelDrag(portal);
|
|
608
|
+
ensureModelBackdrop();
|
|
609
|
+
window.requestAnimationFrame(() => {
|
|
610
|
+
if (!disposed && modelPortal === portal) document.body.classList.add('qzsy-model-sheet-open');
|
|
611
|
+
});
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const syncModelSheet = () => {
|
|
615
|
+
if (disposed) return;
|
|
616
|
+
if (!isMobileViewport()) {
|
|
617
|
+
clearModelSheet();
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
|
|
621
|
+
const found = findModelPortal();
|
|
622
|
+
if (!found) {
|
|
623
|
+
if (modelPortal?.isConnected) return; // 已应用,等待宿主关闭后移除
|
|
624
|
+
if (modelPortal) clearModelSheet();
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (found !== modelPortal) applyModelSheet(found);
|
|
628
|
+
};
|
|
629
|
+
|
|
299
630
|
const onClick = (event) => {
|
|
300
631
|
if (Date.now() < suppressClickUntil) {
|
|
301
632
|
event.preventDefault();
|
|
@@ -311,6 +642,14 @@ export function setupMobileDrawer() {
|
|
|
311
642
|
else openDrawer();
|
|
312
643
|
return;
|
|
313
644
|
}
|
|
645
|
+
|
|
646
|
+
// 模型切换器:记录触发链,等宿主把二级菜单 portal 挂到 body 后再改造成 sheet
|
|
647
|
+
if (isModelTrigger(target)) {
|
|
648
|
+
modelChainUntil = Date.now() + 6000;
|
|
649
|
+
window.setTimeout(syncModelSheet, 30);
|
|
650
|
+
window.setTimeout(syncModelSheet, 180);
|
|
651
|
+
}
|
|
652
|
+
|
|
314
653
|
if (!isOpen()) return;
|
|
315
654
|
|
|
316
655
|
if (target.closest(`.${BACKDROP_CLASS}`)) {
|
|
@@ -353,6 +692,7 @@ export function setupMobileDrawer() {
|
|
|
353
692
|
clearDrag();
|
|
354
693
|
closeDrawer({ restoreFocus: false });
|
|
355
694
|
}
|
|
695
|
+
syncModelSheet();
|
|
356
696
|
};
|
|
357
697
|
|
|
358
698
|
const onVisibilityChange = () => {
|
|
@@ -361,11 +701,12 @@ export function setupMobileDrawer() {
|
|
|
361
701
|
clearDrag();
|
|
362
702
|
closeDrawer({ restoreFocus: false });
|
|
363
703
|
}
|
|
704
|
+
if (document.visibilityState === 'visible') syncModelSheet();
|
|
364
705
|
};
|
|
365
706
|
|
|
366
707
|
// ---- 手势:左缘右滑打开;抽屉内左滑关闭;拖动跟手 ----
|
|
367
708
|
const onTouchStart = (event) => {
|
|
368
|
-
if (disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
709
|
+
if (modelPortal || disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
369
710
|
const touch = event.touches[0];
|
|
370
711
|
const sidebar = document.querySelector('div[class*="_sidebarCol"]');
|
|
371
712
|
if (!sidebar) return;
|
|
@@ -401,7 +742,7 @@ export function setupMobileDrawer() {
|
|
|
401
742
|
};
|
|
402
743
|
|
|
403
744
|
const onTouchMove = (event) => {
|
|
404
|
-
if (!gesture || disposed) return;
|
|
745
|
+
if (modelPortal || !gesture || disposed) return;
|
|
405
746
|
const touch = event.touches?.[0];
|
|
406
747
|
if (!touch) return;
|
|
407
748
|
const dx = touch.clientX - gesture.startX;
|
|
@@ -450,12 +791,12 @@ export function setupMobileDrawer() {
|
|
|
450
791
|
};
|
|
451
792
|
|
|
452
793
|
const onTouchEnd = (event) => {
|
|
453
|
-
if (!gesture) return;
|
|
794
|
+
if (modelPortal || !gesture) return;
|
|
454
795
|
finishGesture(event.changedTouches?.[0]);
|
|
455
796
|
};
|
|
456
797
|
|
|
457
798
|
const onTouchCancel = () => {
|
|
458
|
-
if (!gesture) return;
|
|
799
|
+
if (modelPortal || !gesture) return;
|
|
459
800
|
finishGesture(null);
|
|
460
801
|
};
|
|
461
802
|
|
|
@@ -480,10 +821,12 @@ export function setupMobileDrawer() {
|
|
|
480
821
|
if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
481
822
|
ensureElements();
|
|
482
823
|
}
|
|
824
|
+
syncModelSheet();
|
|
483
825
|
});
|
|
484
826
|
observer.observe(document.body, { childList: true });
|
|
485
827
|
}
|
|
486
828
|
|
|
829
|
+
syncModelSheet();
|
|
487
830
|
syncAria();
|
|
488
831
|
|
|
489
832
|
return () => {
|
|
@@ -498,6 +841,7 @@ export function setupMobileDrawer() {
|
|
|
498
841
|
window.removeEventListener('touchmove', onTouchMove);
|
|
499
842
|
window.removeEventListener('touchend', onTouchEnd);
|
|
500
843
|
window.removeEventListener('touchcancel', onTouchCancel);
|
|
844
|
+
clearModelSheet();
|
|
501
845
|
clearDrag();
|
|
502
846
|
document.body?.classList.remove(OPEN_CLASS);
|
|
503
847
|
document.body?.classList.remove('qzsy-drawer-ready');
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qzsy/dsh-bridge",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
|
|
5
|
-
"releaseNotes": "【v3.
|
|
5
|
+
"releaseNotes": "【v3.4.0】移动端模型切换器改为底部 sheet\\n• 模型二级菜单全宽贴底:圆角、抓手、遮罩、滚动区、safe-area 底部留白\\n• 支持点遮罩关闭、顶部下拉关闭(跟手 + 松手吸附)\\n• 兼容有/无「搜索模型」输入框的宿主版本,不依赖宿主哈希\\n• 桌面端模型 popover 完全不变",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
8
|
"exports": {
|