dsh-bridge-gateway 0.1.3 → 0.1.5
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 +20 -0
- package/README.md +64 -29
- package/README.zh.md +110 -0
- package/client/client.js +235 -5
- package/client/index.js +242 -5
- package/lib/bridge-rpc.js +132 -8
- package/lib/feishu/lark-bundled.mjs +156 -38
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
本项目 `dsh-bridge-gateway` 是基于 [dsh-bridge](https://github.com/wenbin-wb/dsh-bridge) 的移植增强分支,在保留原版能力之外,重点新增「公网直连网关」。
|
|
4
4
|
|
|
5
|
+
## [0.1.5] - 2026-09-12
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
- 适配 DSH 0.1.5:RPC 通道从 `connection.rpc.handle` 迁到 webServer 直注册 + `connection.requestRejection` 鉴权(0.1.5 inject 守卫使旧入口不可用,wire 格式不变)。
|
|
9
|
+
|
|
10
|
+
### 修复(移动端深度适配批次,基于 390px/320px 真机实测坐标)
|
|
11
|
+
- 顶栏「标准模式」与「对话管理」水平重叠(390px 下重叠 11px):titleRow 允许换行,titleCluster/headerActions/headerUtilities 各自可收缩并限宽(45vw/50vw),内部按钮限宽省略号。
|
|
12
|
+
- 输入框被撑到 780px 而消息列仅 318px:从会话根节点收窄 `--dsh-chat-content-width`/`--dsh-composer-card-max-width` 变量,并补上此前遗漏的 `uV2eYG_root` 约束层,输入框祖先链整体不超视口。
|
|
13
|
+
- 320px 下 git 分支 chip 溢出屏幕右侧 39px 被裁切:chip 限宽 `min(120px, 30vw)`,其 `position:absolute` 浮层锚点改用 `right: 8px` 覆盖内联 `left`。
|
|
14
|
+
- 主要控件(模型/权限/工作区/分支 chip/会话操作等 10 类按钮)点击热区经伪元素 `inset: -8px` 扩至约 44px,视觉尺寸不变。
|
|
15
|
+
- 侧边栏关闭时仅被 transform 移出屏幕、内部 7 个按钮仍可被 Tab 聚焦:改用 `visibility: hidden` + `pointer-events: none`,抽屉打开时恢复。
|
|
16
|
+
- 隐藏 dsh-better-sidebar 注入的全屏 `panel-host` 遮罩(z-index 25 压住输入框并拦截点击)与主题装饰性 `glass-fade` 渐变条(右下角小方块)。
|
|
17
|
+
- 输入框底部工具栏(权限选择器/模型选择器/添加命令/发送)弹性自适应互不重叠。
|
|
18
|
+
- 侧边栏抽屉底部溢出屏幕 38px:`box-sizing: border-box` + `100dvh` 高度约束。
|
|
19
|
+
|
|
20
|
+
## [0.1.4] - 2026-08-30
|
|
21
|
+
|
|
22
|
+
### 修复
|
|
23
|
+
- 修复「远程访问」面板在宿主连接未建立时无限卡在「加载中」的问题:为状态拉取增加兜底超时(15s),超时后释放 in-flight 锁并给出可用的「🔄 重试」按钮,避免面板永远无法自行恢复。
|
|
24
|
+
|
|
5
25
|
## [0.1.3] - 待发布
|
|
6
26
|
|
|
7
27
|
### 修复
|
package/README.md
CHANGED
|
@@ -1,47 +1,82 @@
|
|
|
1
1
|
# dsh-bridge-gateway
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
English | [简体中文](./README.zh.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Remote-access plugin for DeepSeek Harness (DSH). Built on top of [dsh-bridge](https://github.com/wenbin-wb/dsh-bridge), it adds a **public direct-connect gateway** so phones and other out-of-network devices can open and operate the Harness running on your computer — no tunnel required.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
LAN access, Cloudflare tunnels, custom tunnels, and the IM bots all keep working exactly as before. The plugin is fully inert until you enable the features you want.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Public direct-connect gateway.** The computer listens on `0.0.0.0:<port>` with its own HTTPS self-signed certificate and a forced login gate, so outside devices connect straight to your machine. The link is terminated at the gateway and proxied to the DSH loopback, so plugin pages and RPC channels work unchanged.
|
|
12
|
+
- **LAN access.** Scan a QR code from the remote-access panel on the same Wi-Fi and you are in.
|
|
13
|
+
- **Public tunnels.** Cloudflare (temporary or fixed hostname) or a self-built tunnel, selectable per need alongside the direct gateway.
|
|
14
|
+
- **Remote workspace.** Browse the host's directory tree and pick workspace conversations from the phone.
|
|
15
|
+
- **Security.** Password gate + QR-based password-free token + admin unlock/lock for sensitive config + rate limiting against brute force. Client-claimed `isLocalhost` is never trusted.
|
|
16
|
+
- **IM bots.** WeChat / QQ / Feishu / Telegram bots connect through their own gateway links — no public IP needed. Per-platform guides live in [docs/](./docs).
|
|
17
|
+
- **Mobile-friendly web.** The remote web UI is deeply adapted for phone screens: drawer sidebar (fully hidden when closed, removing keyboard/screen-reader focus traps), top-bar mode and conversation-management controls that wrap instead of overlapping, git branch chips anchored to the viewport edge instead of being clipped, composer width aligned with the conversation column and stuck to the bottom, ~44px touch targets on the main controls via pseudo-element hit areas, and removal of full-screen overlays and decorative gradient strips that covered the input box.
|
|
18
|
+
- **All configuration in the UI.** Every feature above is configured from DSH Web's remote-access panel; nothing is edited by hand.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node `^22.19.0 || >=24.0.0`.
|
|
23
|
+
- DSH with a web profile (built and tested against `0.1.5-rc.2`; adapted for the 0.1.5 RPC/webServer entry points).
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
# Preferred: install straight from GitHub (no npm involved)
|
|
29
|
+
dsh plugin --profile web add github:lament-z/dsh-bridge-gateway
|
|
30
|
+
|
|
31
|
+
# Alternative: from npm
|
|
10
32
|
dsh plugin --profile web add dsh-bridge-gateway
|
|
33
|
+
|
|
34
|
+
# From a local clone / working copy
|
|
35
|
+
dsh plugin --profile web add link:<this directory>
|
|
11
36
|
```
|
|
12
37
|
|
|
13
|
-
|
|
38
|
+
Upgrade to the newest build: `dsh plugin --profile web add github:lament-z/dsh-bridge-gateway` again (or append `@latest` for the npm source). Then restart `dsh web`.
|
|
39
|
+
|
|
40
|
+
Nothing changes on install: the gateway is off by default and the original LAN/tunnel/IM behavior is untouched.
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Public direct-connect gateway (core feature)
|
|
45
|
+
|
|
46
|
+
No tunnel needed — the computer exposes a port itself and outside devices connect directly.
|
|
47
|
+
|
|
48
|
+
1. Install, then open DSH Web -> Settings -> **Public Access** tab.
|
|
49
|
+
2. In the **Direct Gateway** card, set the port (default `7443`) and click save.
|
|
50
|
+
3. Click **Enable Direct Gateway**.
|
|
51
|
+
4. Map that port to this machine on your router / cloud server, then open `https://<public-IP-or-domain>:<port>` from outside.
|
|
52
|
+
|
|
53
|
+
First visit shows a certificate warning because the HTTPS certificate is self-signed — choose "always allow" to continue. External visitors must pass the login gate configured under **Security**; the gate policy is independent of your LAN settings.
|
|
54
|
+
|
|
55
|
+
### LAN access
|
|
14
56
|
|
|
15
|
-
|
|
57
|
+
Open the remote-access panel in DSH Web and scan the QR code with the phone on the same Wi-Fi.
|
|
16
58
|
|
|
17
|
-
|
|
59
|
+
### Public tunnel
|
|
18
60
|
|
|
19
|
-
|
|
61
|
+
In the same panel choose Cloudflare (temporary or fixed hostname) or a self-built tunnel. See [docs/custom-tunnel.md](./docs/custom-tunnel.md) for the custom-tunnel protocol.
|
|
20
62
|
|
|
21
|
-
|
|
22
|
-
- **强制登录门禁**:外部访问必须输入你在「安全认证」里设置的密码,与局域网策略相互独立。
|
|
23
|
-
- **全界面配置**:端口、随 DSH 自动启动等,都在「公网访问」设置页里配置。
|
|
63
|
+
### Security configuration
|
|
24
64
|
|
|
25
|
-
|
|
65
|
+
Settings -> Security: enable protection, set the password, manage the password-free token, and unlock/lock the admin surface. Unauthorized visitors are rejected before any page content is served.
|
|
26
66
|
|
|
27
|
-
|
|
28
|
-
2. 在「直连网关」卡片设置端口(默认 `7443`),点「保存端口」。
|
|
29
|
-
3. 点击「开启直连网关」。
|
|
30
|
-
4. 在路由器/云服务器把该端口映射到本机,即可通过 `https://<公网IP或域名>:端口` 访问。
|
|
67
|
+
### IM bots
|
|
31
68
|
|
|
32
|
-
|
|
69
|
+
WeChat / QQ / Feishu / Telegram guides: [docs/wechat-usage.md](./docs/wechat-usage.md), [docs/qq-usage.md](./docs/qq-usage.md), [docs/feishu-usage.md](./docs/feishu-usage.md), [docs/telegram-usage.md](./docs/telegram-usage.md).
|
|
33
70
|
|
|
34
|
-
##
|
|
71
|
+
## Security notes
|
|
35
72
|
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- **安全认证**:密码 + 扫码免密 Token + 管理后台防篡改 + 防暴力破解。
|
|
40
|
-
- **IM 机器人**:微信 / QQ / 飞书 / Telegram 扫码直连,无需公网。
|
|
73
|
+
- The direct gateway always enforces `public_only`-style gating: external visitors authenticate through the same AuthManager as LAN access, and loopback-only resources stay loopback-only.
|
|
74
|
+
- RPC channels are registered through the host web server and pass request-rejection authentication, so forged or unauthenticated channel calls are dropped at the entry.
|
|
75
|
+
- The self-signed certificate encrypts transport; it does not add identity. Anyone with the password can log in — keep the password strong and enable admin lock.
|
|
41
76
|
|
|
42
|
-
##
|
|
77
|
+
## Development
|
|
43
78
|
|
|
44
|
-
```
|
|
79
|
+
```sh
|
|
45
80
|
git clone https://github.com/lament-z/dsh-bridge-gateway
|
|
46
81
|
cd dsh-bridge-gateway
|
|
47
82
|
npm install
|
|
@@ -50,10 +85,10 @@ npm test
|
|
|
50
85
|
dsh plugin --profile web add .
|
|
51
86
|
```
|
|
52
87
|
|
|
53
|
-
##
|
|
88
|
+
## Release notes
|
|
54
89
|
|
|
55
|
-
|
|
90
|
+
Pushing a `v*` tag to GitHub triggers automatic `npm publish` (GitHub Actions; the repository needs an `NPM_TOKEN` secret).
|
|
56
91
|
|
|
57
|
-
##
|
|
92
|
+
## License
|
|
58
93
|
|
|
59
|
-
[MIT](./LICENSE)
|
|
94
|
+
[MIT](./LICENSE)
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# dsh-bridge-gateway
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
DeepSeek Harness(DSH)远程访问插件。以 [dsh-bridge](https://github.com/wenbin-wb/dsh-bridge)
|
|
6
|
+
为根基,额外加入**「公网直连网关」**,让手机等外网设备不借助隧道就能直接访问、操作电脑上的
|
|
7
|
+
Harness。
|
|
8
|
+
|
|
9
|
+
局域网访问、Cloudflare 隧道、自建隧道、IM 机器人等原有能力保持不变;插件默认完全不启用,
|
|
10
|
+
装上后原有行为零改动,需要什么再开什么。
|
|
11
|
+
|
|
12
|
+
## 功能
|
|
13
|
+
|
|
14
|
+
- **公网直连网关。** 电脑直接监听 `0.0.0.0:<端口>`,自带 HTTPS 自签证书与强制登录门禁,
|
|
15
|
+
外网设备直连访问。链路在网关处终结并转发到 DSH loopback,插件页面与 RPC 通道原样可用。
|
|
16
|
+
- **局域网访问。** 同一 Wi-Fi 下用手机扫远程访问面板里的二维码即可进入。
|
|
17
|
+
- **公网隧道。** Cloudflare(临时/固定域名)或自建隧道,与直连网关按需二选一或并存。
|
|
18
|
+
- **远程工作区。** 手机端网页目录树直接浏览并选择工作区对话。
|
|
19
|
+
- **安全认证。** 密码门禁 + 扫码免密 Token + 敏感配置的管理员解锁/锁定 + 防暴力破解限频;
|
|
20
|
+
永不信任客户端自称的 `isLocalhost`。
|
|
21
|
+
- **IM 机器人。** 微信 / QQ / 飞书 / Telegram 机器人走各自网关链路,无需公网 IP;
|
|
22
|
+
分平台指南见 [docs/](./docs)。
|
|
23
|
+
- **移动端适配。** 远程网页 UI 针对手机屏幕深度适配:抽屉化侧边栏(隐藏后消除键盘/读屏
|
|
24
|
+
焦点陷阱)、顶栏「标准模式/对话管理」换行不再重叠、git 分支 chip 按视口右边界锚定不再
|
|
25
|
+
被裁切、输入框宽度与对话列对齐且 sticky 固定于底部、主要控件以伪元素把点击热区扩到
|
|
26
|
+
约 44px、隐藏会遮挡输入框的全屏遮罩与装饰性渐变条。
|
|
27
|
+
- **全界面配置。** 以上全部功能都在 DSH Web 远程访问面板里配置,无需手改任何文件。
|
|
28
|
+
|
|
29
|
+
## 要求
|
|
30
|
+
|
|
31
|
+
- Node `^22.19.0 || >=24.0.0`。
|
|
32
|
+
- DSH web profile(构建与测试基于 `0.1.5-rc.2`,已适配 0.1.5 的 RPC/webServer 入口)。
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
# 推荐:直接从 GitHub 安装(不经 npm)
|
|
38
|
+
dsh plugin --profile web add github:lament-z/dsh-bridge-gateway
|
|
39
|
+
|
|
40
|
+
# 备选:从 npm 安装
|
|
41
|
+
dsh plugin --profile web add dsh-bridge-gateway
|
|
42
|
+
|
|
43
|
+
# 从本地 clone / 工作副本安装
|
|
44
|
+
dsh plugin --profile web add link:<本目录>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
升级到最新版:再次执行上面的 GitHub 安装命令(npm 来源可加 `@latest`)。然后重启 `dsh web`。
|
|
48
|
+
|
|
49
|
+
安装后默认不启用:直连网关关闭,原有局域网/隧道/IM 行为完全不变。
|
|
50
|
+
|
|
51
|
+
## 使用方法
|
|
52
|
+
|
|
53
|
+
### 公网直连网关(核心功能)
|
|
54
|
+
|
|
55
|
+
不需要隧道,让电脑直接监听公网端口,外网直连访问。
|
|
56
|
+
|
|
57
|
+
1. 安装后进入 DSH Web 设置 -> **公网访问** Tab。
|
|
58
|
+
2. 在**直连网关**卡片设置端口(默认 `7443`),点「保存端口」。
|
|
59
|
+
3. 点击「开启直连网关」。
|
|
60
|
+
4. 在路由器/云服务器把该端口映射到本机,即可通过 `https://<公网IP或域名>:端口` 访问。
|
|
61
|
+
|
|
62
|
+
HTTPS 证书为自签,浏览器首次访问会提示不安全,勾选「始终允许」即可继续。外部访客必须通过
|
|
63
|
+
**安全认证**里配置的登录门禁;门禁策略与局域网设置相互独立。
|
|
64
|
+
|
|
65
|
+
### 局域网访问
|
|
66
|
+
|
|
67
|
+
在 DSH Web 打开远程访问面板,用同一 Wi-Fi 下的手机扫二维码。
|
|
68
|
+
|
|
69
|
+
### 公网隧道
|
|
70
|
+
|
|
71
|
+
同一面板里选择 Cloudflare(临时/固定域名)或自建隧道。自建隧道协议见
|
|
72
|
+
[docs/custom-tunnel.md](./docs/custom-tunnel.md)。
|
|
73
|
+
|
|
74
|
+
### 安全配置
|
|
75
|
+
|
|
76
|
+
设置 -> 安全认证:开启防护、设置密码、管理免密 Token、解锁/锁定管理面。未认证访客在
|
|
77
|
+
任何页面内容返回之前就会被拒绝。
|
|
78
|
+
|
|
79
|
+
### IM 机器人
|
|
80
|
+
|
|
81
|
+
微信 / QQ / 飞书 / Telegram 使用指南:
|
|
82
|
+
[docs/wechat-usage.md](./docs/wechat-usage.md)、[docs/qq-usage.md](./docs/qq-usage.md)、
|
|
83
|
+
[docs/feishu-usage.md](./docs/feishu-usage.md)、[docs/telegram-usage.md](./docs/telegram-usage.md)。
|
|
84
|
+
|
|
85
|
+
## 安全说明
|
|
86
|
+
|
|
87
|
+
- 直连网关始终强制 `public_only` 式门禁:外部访客与局域网访问共用同一 AuthManager 认证,
|
|
88
|
+
仅限 loopback 的资源绝不经网关暴露。
|
|
89
|
+
- RPC 通道经由宿主 web server 注册并通过请求拒绝(requestRejection)鉴权,
|
|
90
|
+
伪造或未认证的通道调用在入口即被丢弃。
|
|
91
|
+
- 自签证书只加密传输、不提供身份:拿到密码的人就能登录——请设置强密码并开启管理员锁定。
|
|
92
|
+
|
|
93
|
+
## 开发
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
git clone https://github.com/lament-z/dsh-bridge-gateway
|
|
97
|
+
cd dsh-bridge-gateway
|
|
98
|
+
npm install
|
|
99
|
+
npm run build:client
|
|
100
|
+
npm test
|
|
101
|
+
dsh plugin --profile web add .
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 发布说明
|
|
105
|
+
|
|
106
|
+
打 `v*` 标签推送到 GitHub 即自动 `npm publish`(GitHub Actions,需仓库配置 `NPM_TOKEN` secret)。
|
|
107
|
+
|
|
108
|
+
## 开源协议
|
|
109
|
+
|
|
110
|
+
[MIT](./LICENSE)
|
package/client/client.js
CHANGED
|
@@ -2920,18 +2920,29 @@ function BridgePanel({ rpcCall }) {
|
|
|
2920
2920
|
}, [rpcCall, adminToken]);
|
|
2921
2921
|
const loadInFlightRef = React.useRef(false);
|
|
2922
2922
|
const loadSeqRef = React.useRef(0);
|
|
2923
|
+
const LOAD_TIMEOUT_MS = 15e3;
|
|
2923
2924
|
const load = React.useCallback(async (quiet = false) => {
|
|
2924
2925
|
if (loadInFlightRef.current) return;
|
|
2925
2926
|
loadInFlightRef.current = true;
|
|
2926
2927
|
const currentSeq = ++loadSeqRef.current;
|
|
2928
|
+
const timedOut = { done: false };
|
|
2927
2929
|
try {
|
|
2928
|
-
const
|
|
2930
|
+
const result = await Promise.race([
|
|
2931
|
+
authRpcCall(BRIDGE_ENDPOINTS.getStatus, {}),
|
|
2932
|
+
new Promise((resolve) => {
|
|
2933
|
+
setTimeout(() => {
|
|
2934
|
+
timedOut.done = true;
|
|
2935
|
+
resolve(null);
|
|
2936
|
+
}, LOAD_TIMEOUT_MS);
|
|
2937
|
+
})
|
|
2938
|
+
]);
|
|
2939
|
+
if (timedOut.done) throw new Error("\u52A0\u8F7D\u8D85\u65F6\uFF08\u5BBF\u4E3B\u8FDE\u63A5\u53EF\u80FD\u5728\u5207\u6362\u6216\u672A\u5EFA\u7ACB\uFF09\u3002\u8BF7\u70B9\u51FB\u300C\u{1F504} \u91CD\u8BD5\u300D\u5237\u65B0\u3002");
|
|
2929
2940
|
if (currentSeq !== loadSeqRef.current) return;
|
|
2930
|
-
if (!
|
|
2931
|
-
setStatus(
|
|
2941
|
+
if (!result?.ok) throw new Error(result?.error?.message ?? "RPC failed");
|
|
2942
|
+
setStatus(result.value);
|
|
2932
2943
|
if (!quiet) setErr(null);
|
|
2933
2944
|
} catch (e) {
|
|
2934
|
-
if (currentSeq === loadSeqRef.current) setErr(e.message);
|
|
2945
|
+
if (currentSeq === loadSeqRef.current) setErr(e.message || "\u52A0\u8F7D\u5931\u8D25");
|
|
2935
2946
|
} finally {
|
|
2936
2947
|
loadInFlightRef.current = false;
|
|
2937
2948
|
}
|
|
@@ -3345,6 +3356,11 @@ function BridgePanel({ rpcCall }) {
|
|
|
3345
3356
|
}
|
|
3346
3357
|
},
|
|
3347
3358
|
React.createElement("span", { style: { flex: "1 1 auto" } }, err),
|
|
3359
|
+
!isInterceptionErr && React.createElement("button", {
|
|
3360
|
+
type: "button",
|
|
3361
|
+
style: { ...s.btnGhost, height: 26, fontSize: 12, padding: "0 10px", flexShrink: 0 },
|
|
3362
|
+
onClick: () => load()
|
|
3363
|
+
}, "\u{1F504} \u91CD\u8BD5"),
|
|
3348
3364
|
isInterceptionErr && React.createElement("button", {
|
|
3349
3365
|
type: "button",
|
|
3350
3366
|
style: { ...s.btnPri, background: "#dc2626", color: "#ffffff", height: 26, fontSize: 12, padding: "0 10px", flexShrink: 0 },
|
|
@@ -3813,6 +3829,54 @@ function injectMobileStyles() {
|
|
|
3813
3829
|
display: none !important;
|
|
3814
3830
|
}
|
|
3815
3831
|
|
|
3832
|
+
/* ---- \u9876\u680F\u300C\u6807\u51C6\u6A21\u5F0F\u300D\u4E0E\u300C\u5BF9\u8BDD\u7BA1\u7406\u300D\u91CD\u53E0\u4FEE\u590D ----
|
|
3833
|
+
\u5B9E\u6D4B\uFF08390px\uFF09\uFF1A\u6807\u51C6\u6A21\u5F0F(\u5728 titleCluster \u5185) \u53F3\u8FB9\u754C 102\uFF0C
|
|
3834
|
+
\u5BF9\u8BDD\u7BA1\u7406(\u5728 headerUtilities \u5185) \u5DE6\u8FB9\u754C 91 \u2192 \u6C34\u5E73\u91CD\u53E0 11px\u3002
|
|
3835
|
+
\u6839\u56E0\uFF1AtitleCluster \u53EA\u6709 46px \u5BBD\uFF08[33,79]\uFF09\uFF0C\u4F46\u5185\u90E8\u7684\u300C\u6807\u51C6\u6A21\u5F0F\u300D\u80F6\u56CA\u56FA\u5B9A 69px\uFF0C
|
|
3836
|
+
\u6491\u7834\u7236\u5BB9\u5668\u540E\u4E0E\u53F3\u4FA7 headerUtilities \u76F8\u649E\u3002
|
|
3837
|
+
\u4FEE\u590D\uFF1A\u5141\u8BB8 titleRow \u6362\u884C + \u8BA9\u4E24\u4FA7\u5404\u81EA\u53EF\u6536\u7F29\u5E76\u9650\u5BBD\u3002 */
|
|
3838
|
+
div[class*="wSkVaW_titleRow"],
|
|
3839
|
+
div[class*="_titleRow"] {
|
|
3840
|
+
flex-wrap: wrap !important;
|
|
3841
|
+
row-gap: 6px !important;
|
|
3842
|
+
column-gap: 8px !important;
|
|
3843
|
+
overflow: visible !important;
|
|
3844
|
+
}
|
|
3845
|
+
div[class*="wSkVaW_titleCluster"],
|
|
3846
|
+
div[class*="_titleCluster"] {
|
|
3847
|
+
flex: 1 1 auto !important;
|
|
3848
|
+
min-width: 0 !important;
|
|
3849
|
+
max-width: 100% !important;
|
|
3850
|
+
}
|
|
3851
|
+
div[class*="wSkVaW_headerActions"],
|
|
3852
|
+
div[class*="_headerActions"] {
|
|
3853
|
+
flex: 0 0 auto !important;
|
|
3854
|
+
min-width: 0 !important;
|
|
3855
|
+
max-width: 45vw !important;
|
|
3856
|
+
overflow: hidden !important;
|
|
3857
|
+
}
|
|
3858
|
+
div[class*="wSkVaW_headerUtilities"],
|
|
3859
|
+
div[class*="_headerUtilities"] {
|
|
3860
|
+
flex: 0 0 auto !important;
|
|
3861
|
+
min-width: 0 !important;
|
|
3862
|
+
max-width: 50vw !important;
|
|
3863
|
+
margin-left: auto !important;
|
|
3864
|
+
overflow: hidden !important;
|
|
3865
|
+
}
|
|
3866
|
+
/* \u4E24\u8005\u5185\u90E8\u7684\u65E0 class \u6309\u94AE\uFF08\u6A21\u5F0F\u80F6\u56CA / \u5BF9\u8BDD\u7BA1\u7406 / \u5220\u9664\u672C\u5BF9\u8BDD\uFF09\u7EDF\u4E00\u9650\u5BBD\u5E76\u5141\u8BB8\u6536\u7F29 */
|
|
3867
|
+
div[class*="wSkVaW_headerActions"] > *,
|
|
3868
|
+
div[class*="wSkVaW_headerActions"] button,
|
|
3869
|
+
div[class*="wSkVaW_headerActions"] span,
|
|
3870
|
+
div[class*="wSkVaW_headerUtilities"] > *,
|
|
3871
|
+
div[class*="wSkVaW_headerUtilities"] button,
|
|
3872
|
+
div[class*="wSkVaW_headerUtilities"] span {
|
|
3873
|
+
max-width: 100% !important;
|
|
3874
|
+
min-width: 0 !important;
|
|
3875
|
+
overflow: hidden !important;
|
|
3876
|
+
text-overflow: ellipsis !important;
|
|
3877
|
+
white-space: nowrap !important;
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3816
3880
|
/* \u5B50\u4EE3\u7406/\u667A\u80FD\u4F53\u6A21\u5F0F\u80F6\u56CA (Actions)\uFF1A\u7D27\u51D1\u5706\u89D2\u80F6\u56CA */
|
|
3817
3881
|
div[class*="wSkVaW_headerActions"],
|
|
3818
3882
|
div[class*="headerActions"] {
|
|
@@ -3923,6 +3987,136 @@ function injectMobileStyles() {
|
|
|
3923
3987
|
box-sizing: border-box !important;
|
|
3924
3988
|
}
|
|
3925
3989
|
|
|
3990
|
+
/* ---- \u8F93\u5165\u6846\u5BBD\u5EA6\u4E0E\u5BF9\u8BDD\u5217\u5BF9\u9F50\u4FEE\u590D ----
|
|
3991
|
+
\u95EE\u9898\uFF1A\u8FDB\u5165\u771F\u5B9E\u5BF9\u8BDD\u540E\u8F93\u5165\u6846\u88AB\u6491\u5230 780px\uFF08= 748px \u5185\u5BB9\u5BBD + 32px\uFF09\uFF0C
|
|
3992
|
+
\u800C\u6D88\u606F\u5217\u4EC5 318px\uFF0C\u8F93\u5165\u6846\u6BD4\u5BF9\u8BDD\u5185\u5BB9\u5BBD\u7EA6 2.45 \u500D\u3002
|
|
3993
|
+
\u6839\u56E0\uFF1A\u5361\u7247\u7684\u7236\u7EA7 uV2eYG_root \u81EA\u5E26 max-width: var(--dsh-composer-card-max-width)\uFF0C
|
|
3994
|
+
\u5361\u7247\u81EA\u8EAB\u7684 max-width:100% \u662F\u76F8\u5BF9\u7236\u7EA7 780px \u8BA1\u7B97\u7684\uFF0C\u56E0\u6B64\u4ECD\u662F 780px\u3002
|
|
3995
|
+
\u4FEE\u590D\uFF1A\u4ECE\u6839\u53D8\u91CF\u6536\u7A84 + \u8865\u4E0A uV2eYG_root \u8FD9\u4E00\u5C42\u7EA6\u675F\u3002 */
|
|
3996
|
+
|
|
3997
|
+
/* 1. \u4ECE\u4F1A\u8BDD\u6839\u8282\u70B9\u6536\u7A84\u5185\u5BB9\u5BBD\u5EA6\u53D8\u91CF\uFF08\u540C\u65F6\u7EA6\u675F\u6D88\u606F\u5217\u4E0E\u8F93\u5165\u6846\uFF09 */
|
|
3998
|
+
div[class*="wSkVaW_root"] {
|
|
3999
|
+
--dsh-chat-content-width: min(748px, 100vw - 32px) !important;
|
|
4000
|
+
--dsh-composer-card-max-width: min(780px, 100vw - 24px) !important;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
/* 2. \u8865\u4E0A\u63D2\u4EF6\u6B64\u524D\u9057\u6F0F\u7684 uV2eYG_root\uFF08\u8F93\u5165\u6846\u7684\u76F4\u63A5\u7F29\u5BB9\u5668\uFF09 */
|
|
4004
|
+
div[class*="uV2eYG_root"] {
|
|
4005
|
+
max-width: 100% !important;
|
|
4006
|
+
min-width: 0 !important;
|
|
4007
|
+
box-sizing: border-box !important;
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
/* 3. \u8F93\u5165\u6846\u7956\u5148\u94FE\u6574\u4F53\u4E0D\u8D85\u51FA\u89C6\u53E3 */
|
|
4011
|
+
div[class*="wSkVaW_composerStack"],
|
|
4012
|
+
div[class*="wSkVaW_composerSeat"],
|
|
4013
|
+
div[class*="uV2eYG_scroll"],
|
|
4014
|
+
div[class*="uV2eYG_grow"] {
|
|
4015
|
+
max-width: 100% !important;
|
|
4016
|
+
min-width: 0 !important;
|
|
4017
|
+
box-sizing: border-box !important;
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
/* ---- \u9876\u90E8\u63A7\u4EF6\u9632\u6EA2\u51FA\u88C1\u5207\uFF08320px \u4E0B main \u5206\u652F chip \u6EA2\u51FA 39px \u88AB\u88C1\uFF09 ---- */
|
|
4021
|
+
div[class*="wSkVaW_titleRow"],
|
|
4022
|
+
div[class*="_titleRow"] {
|
|
4023
|
+
flex-wrap: wrap !important;
|
|
4024
|
+
row-gap: 6px !important;
|
|
4025
|
+
overflow: visible !important;
|
|
4026
|
+
}
|
|
4027
|
+
button[class*="_7rgC5q_chip"],
|
|
4028
|
+
button[class*="cubgiG_seat"],
|
|
4029
|
+
div[class*="_7rgC5q_chipWrap"] {
|
|
4030
|
+
max-width: min(120px, 30vw) !important;
|
|
4031
|
+
min-width: 0 !important;
|
|
4032
|
+
overflow: hidden !important;
|
|
4033
|
+
text-overflow: ellipsis !important;
|
|
4034
|
+
}
|
|
4035
|
+
|
|
4036
|
+
/* git-graph \u7684\u5206\u652F chip \u5916\u5C42\u662F position:absolute \u6D6E\u5C42\uFF08_7rgC5q_anchor / _7rgC5q_anchorHero\uFF09\uFF0C
|
|
4037
|
+
\u4E14\u901A\u8FC7\u5185\u8054\u6837\u5F0F\u5199\u6B7B left\uFF08\u5982 left: 271.484px\uFF09\uFF0C\u8131\u79BB flex \u6D41\u4E14\u4E0D\u53D7\u7236\u5BB9\u5668\u9650\u5BBD\u7EA6\u675F\uFF0C
|
|
4038
|
+
\u5728 320px \u4E0B\u6EA2\u51FA\u5C4F\u5E55\u53F3\u4FA7\u88AB\u88C1\u5207\uFF08\u53F3\u8FB9\u754C 351 > \u89C6\u53E3 320\uFF09\u3002
|
|
4039
|
+
\u5185\u8054\u6837\u5F0F\u4F18\u5148\u7EA7\u9AD8\u4E8E\u666E\u901A CSS\uFF0C\u5FC5\u987B\u7528 !important \u8986\u76D6 left\uFF0C\u6539\u4E3A\u6309\u89C6\u53E3\u53F3\u8FB9\u754C\u951A\u5B9A\u3002 */
|
|
4040
|
+
div[class*="_7rgC5q_anchor"] {
|
|
4041
|
+
left: auto !important;
|
|
4042
|
+
right: 8px !important;
|
|
4043
|
+
max-width: calc(100vw - 16px) !important;
|
|
4044
|
+
min-width: 0 !important;
|
|
4045
|
+
overflow: hidden !important;
|
|
4046
|
+
}
|
|
4047
|
+
button[class*="_7rgC5q_chip"],
|
|
4048
|
+
div[class*="_7rgC5q_chipWrap"] {
|
|
4049
|
+
width: auto !important;
|
|
4050
|
+
max-width: 100% !important;
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
/* ---- \u70B9\u51FB\u76EE\u6807\u6269\u5230 44px\uFF08\u4F2A\u5143\u7D20\u6269\u70ED\u533A\uFF0C\u89C6\u89C9\u5C3A\u5BF8\u4E0D\u53D8\uFF09 ---- */
|
|
4054
|
+
button[class*="cubgiG_seat"],
|
|
4055
|
+
button[class*="_7rgC5q_chip"],
|
|
4056
|
+
button[class*="pXSMma_workspace"],
|
|
4057
|
+
button[class*="Sh0Q9G_trigger"],
|
|
4058
|
+
button[class*="_7KE1Ra_trigger"],
|
|
4059
|
+
button[class*="uV2eYG_add"],
|
|
4060
|
+
button[class*="qDHVXG_iconButton"],
|
|
4061
|
+
button[class*="qDHVXG_searchButton"],
|
|
4062
|
+
button[class*="hHd-Xa_iconButton"],
|
|
4063
|
+
button[class*="YDXeBa_iconButton"] {
|
|
4064
|
+
position: relative !important;
|
|
4065
|
+
}
|
|
4066
|
+
button[class*="cubgiG_seat"]::after,
|
|
4067
|
+
button[class*="_7rgC5q_chip"]::after,
|
|
4068
|
+
button[class*="pXSMma_workspace"]::after,
|
|
4069
|
+
button[class*="Sh0Q9G_trigger"]::after,
|
|
4070
|
+
button[class*="_7KE1Ra_trigger"]::after,
|
|
4071
|
+
button[class*="uV2eYG_add"]::after,
|
|
4072
|
+
button[class*="qDHVXG_iconButton"]::after,
|
|
4073
|
+
button[class*="qDHVXG_searchButton"]::after,
|
|
4074
|
+
button[class*="hHd-Xa_iconButton"]::after,
|
|
4075
|
+
button[class*="YDXeBa_iconButton"]::after {
|
|
4076
|
+
content: "" !important;
|
|
4077
|
+
position: absolute !important;
|
|
4078
|
+
inset: -8px !important;
|
|
4079
|
+
z-index: 1 !important;
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
/* ---- \u4FA7\u8FB9\u680F\u771F\u6B63\u9690\u85CF\uFF0C\u6D88\u9664\u952E\u76D8/\u8BFB\u5C4F\u7126\u70B9\u9677\u9631 ----
|
|
4083
|
+
\u6B64\u524D\u4FA7\u8FB9\u680F\u4EC5\u88AB transform \u79FB\u5230\u5C4F\u5E55\u5916\uFF08visibility \u4ECD\u4E3A visible\uFF09\uFF0C
|
|
4084
|
+
\u5185\u90E8 7 \u4E2A\u6309\u94AE\u4ECD\u53EF\u88AB Tab \u805A\u7126\u3002 */
|
|
4085
|
+
div[class*="_sidebarCol"] {
|
|
4086
|
+
visibility: hidden !important;
|
|
4087
|
+
pointer-events: none !important;
|
|
4088
|
+
}
|
|
4089
|
+
body.dsh-drawer-open div[class*="_sidebarCol"] {
|
|
4090
|
+
visibility: visible !important;
|
|
4091
|
+
pointer-events: auto !important;
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4094
|
+
/* ---- \u8F93\u5165\u6846 sticky \u5B9A\u4F4D\u4E0E\u906E\u6321\u4FEE\u590D ----
|
|
4095
|
+
\u73B0\u8C61\uFF1A\u53D1\u9001\u540E\u8F93\u5165\u6846\u8DF3\u5230 tabs(\u5BF9\u8BDD/\u8F68\u8FF9/\u4E0A\u4E0B\u6587/\u8BB0\u5FC6) \u4E0B\u65B9\uFF0C\u4E0B\u65B9\u7559\u51FA\u5927\u7247\u7A7A\u767D\uFF1B
|
|
4096
|
+
\u6EDA\u52A8\u65F6\u53F3\u4E0B\u89D2\u51FA\u73B0\u4E00\u4E2A\u5C0F\u65B9\u5757\u3002
|
|
4097
|
+
\u6839\u56E0\uFF1A
|
|
4098
|
+
1) dsh-better-sidebar \u63D2\u4EF6\u6CE8\u5165\u7684 div[data-dsh-panel-host] \u662F\u5168\u5C4F fixed \u906E\u7F69
|
|
4099
|
+
\uFF08390x844\uFF0Cz-index 25\uFF09\uFF0C\u5728\u79FB\u52A8\u7AEF\u672A\u88AB\u9690\u85CF\uFF0C\u538B\u5728 composerSeat(z-index 7) \u4E4B\u4E0A\uFF1B
|
|
4100
|
+
2) \u4E3B\u9898\u88C5\u9970\u5C42 span[data-dsh-glass-fade="bottom"] \u662F\u5168\u5BBD fixed \u6761\uFF08390x13\uFF0Cz-index 7\uFF09\uFF0C
|
|
4101
|
+
\u5E38\u9A7B\u5C4F\u5E55\u53F3\u4E0B\u89D2\uFF0C\u89C6\u89C9\u4E0A\u5C31\u662F\u90A3\u4E2A"\u5C0F\u65B9\u5757"\u3002
|
|
4102
|
+
\u4FEE\u590D\uFF1A\u79FB\u52A8\u7AEF\u9690\u85CF panel-host \u906E\u7F69\u4E0E glass \u6E10\u53D8\u88C5\u9970\u6761\uFF0C\u5E76\u9501\u5B9A\u8F93\u5165\u6846 sticky \u4E8E\u5E95\u90E8\u3002 */
|
|
4103
|
+
|
|
4104
|
+
/* \u9690\u85CF better-sidebar \u7684\u5168\u5C4F\u906E\u7F69\uFF08\u907F\u514D\u8986\u76D6\u8F93\u5165\u6846\u5E76\u62E6\u622A\u70B9\u51FB\uFF09 */
|
|
4105
|
+
div[data-dsh-panel-host],
|
|
4106
|
+
div[data-dsh-better-sidebar] {
|
|
4107
|
+
display: none !important;
|
|
4108
|
+
}
|
|
4109
|
+
/* \u9690\u85CF\u4E3B\u9898\u88C5\u9970\u6027\u6E10\u53D8\u6761\uFF08\u53F3\u4E0B\u89D2\u5C0F\u65B9\u5757\uFF09 */
|
|
4110
|
+
span[data-dsh-glass-fade] {
|
|
4111
|
+
display: none !important;
|
|
4112
|
+
}
|
|
4113
|
+
/* \u6CE8\u610F\uFF1A\u4EE5\u4E0B\u4E24\u5904\u6539\u52A8\u5747\u5DF2\u5B9E\u6D4B\u5931\u8D25\u5E76\u64A4\u56DE\uFF0C\u5207\u52FF\u91CD\u8BD5\u2014\u2014
|
|
4114
|
+
(1) \u628A viewArea \u6539\u4E3A\u53EF\u6536\u7F29\uFF08flex:1 1 auto / min-height:0\uFF09\uFF1A
|
|
4115
|
+
sticky \u5931\u53BB\u6B63\u786E\u6EDA\u52A8\u4E0A\u4E0B\u6587\uFF0C\u8F93\u5165\u6846\u88AB\u9876\u5230\u6574\u4E2A\u5BF9\u8BDD\u5386\u53F2\u6700\u4E0A\u65B9\u3002
|
|
4116
|
+
(2) \u628A composerSeat \u6539\u4E3A absolute\uFF08\u4EFF\u6E90\u7801 composer-overlay \u5206\u652F\uFF09\uFF1A
|
|
4117
|
+
absolute \u8131\u79BB\u6587\u6863\u6D41\u540E\u968F\u6EDA\u52A8\u5185\u5BB9\u79FB\u52A8\uFF0C\u5B9E\u6D4B\u8F93\u5165\u6846\u8DD1\u5230 y=-8972\u3002
|
|
4118
|
+
\u7ED3\u8BBA\uFF1A\u4FDD\u7559\u6E90\u7801\u7684 sticky \u673A\u5236\uFF0C\u4E0D\u505A\u7ED3\u6784\u6027\u6539\u52A8\u3002 */
|
|
4119
|
+
|
|
3926
4120
|
/* \u8F93\u5165\u6846\u5E95\u90E8\u5DE5\u5177\u680F\uFF1A\u5F39\u6027\u81EA\u9002\u5E94\uFF0C\u5F7B\u5E95\u675C\u7EDD\u6743\u9650\u9009\u62E9\u5668(Full access)\u4E0E\u6A21\u578B\u9009\u62E9\u5668\u91CD\u53E0\u78B0\u649E */
|
|
3927
4121
|
div[class*="uV2eYG_row"] {
|
|
3928
4122
|
display: flex !important;
|
|
@@ -4017,8 +4211,16 @@ function injectMobileStyles() {
|
|
|
4017
4211
|
position: fixed !important;
|
|
4018
4212
|
left: 0 !important;
|
|
4019
4213
|
top: 0 !important;
|
|
4020
|
-
bottom:
|
|
4214
|
+
bottom: auto !important;
|
|
4215
|
+
/* \u4FA7\u8FB9\u680F\u5E95\u90E8\u6EA2\u51FA\u5C4F\u5E55\u4FEE\u590D\uFF1A
|
|
4216
|
+
\u5B9E\u6D4B 390x844 \u4E0B\uFF0C\u4FA7\u8FB9\u680F\u5B9E\u9645\u76D2\u5B50\u4E3A T=12 B=882\uFF08\u9AD8\u5EA6 870 > \u89C6\u53E3 844\uFF09\uFF0C\u5E95\u90E8\u6EA2\u51FA 38px\u3002
|
|
4217
|
+
\u6839\u56E0\u662F\u63D2\u4EF6\u6B64\u524D\u672A\u58F0\u660E box-sizing\uFF0C\u5143\u7D20\u7EE7\u627F\u4E3A content-box\uFF0C
|
|
4218
|
+
\u4E8E\u662F padding(10px+14px) \u4E0E margin(12px) \u88AB\u52A0\u5728 height:100dvh \u4E4B\u5916\u3002
|
|
4219
|
+
\u6539\u4E3A border-box \u5E76\u7528 100dvh \u76F4\u63A5\u7EA6\u675F\u9AD8\u5EA6\uFF0Cpadding \u5373\u88AB\u5305\u542B\u5728\u9AD8\u5EA6\u5185\u3002 */
|
|
4220
|
+
box-sizing: border-box !important;
|
|
4021
4221
|
height: 100dvh !important;
|
|
4222
|
+
max-height: 100dvh !important;
|
|
4223
|
+
margin: 0 !important;
|
|
4022
4224
|
width: 290px !important;
|
|
4023
4225
|
max-width: 82vw !important;
|
|
4024
4226
|
z-index: 10000 !important;
|
|
@@ -4536,6 +4738,9 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4536
4738
|
}
|
|
4537
4739
|
const sessionRow = e.target.closest('a, div[class*="sessionRow"], div[role="treeitem"]');
|
|
4538
4740
|
if (sessionRow) {
|
|
4741
|
+
if (sessionRow.hasAttribute("aria-expanded")) {
|
|
4742
|
+
return;
|
|
4743
|
+
}
|
|
4539
4744
|
setTimeout(() => {
|
|
4540
4745
|
if (document.body.classList.contains("dsh-drawer-open")) {
|
|
4541
4746
|
document.body.classList.remove("dsh-drawer-open");
|
|
@@ -4546,6 +4751,31 @@ function setupMobileExperience(rpcCall, ctx) {
|
|
|
4546
4751
|
document.body.classList.remove("dsh-drawer-open");
|
|
4547
4752
|
}
|
|
4548
4753
|
}, true);
|
|
4754
|
+
const syncSidebarInert = () => {
|
|
4755
|
+
const sidebar = document.querySelector('div[class*="_sidebarCol"]');
|
|
4756
|
+
if (!sidebar) return;
|
|
4757
|
+
const open = document.body.classList.contains("dsh-drawer-open");
|
|
4758
|
+
const wantInert = window.innerWidth <= 768 && !open;
|
|
4759
|
+
if (wantInert) {
|
|
4760
|
+
if (!sidebar.hasAttribute("inert")) sidebar.setAttribute("inert", "");
|
|
4761
|
+
} else if (sidebar.hasAttribute("inert")) {
|
|
4762
|
+
sidebar.removeAttribute("inert");
|
|
4763
|
+
}
|
|
4764
|
+
};
|
|
4765
|
+
new MutationObserver(syncSidebarInert).observe(document.body, {
|
|
4766
|
+
attributes: true,
|
|
4767
|
+
attributeFilter: ["class"],
|
|
4768
|
+
childList: true,
|
|
4769
|
+
subtree: true
|
|
4770
|
+
});
|
|
4771
|
+
window.addEventListener("resize", syncSidebarInert);
|
|
4772
|
+
let inertRetry = 0;
|
|
4773
|
+
const inertTimer = setInterval(() => {
|
|
4774
|
+
syncSidebarInert();
|
|
4775
|
+
const sb = document.querySelector('div[class*="_sidebarCol"]');
|
|
4776
|
+
if (sb && sb.hasAttribute("inert") || ++inertRetry > 40) clearInterval(inertTimer);
|
|
4777
|
+
}, 250);
|
|
4778
|
+
syncSidebarInert();
|
|
4549
4779
|
let longPressTimer = null;
|
|
4550
4780
|
let touchStartX = 0;
|
|
4551
4781
|
let touchStartY = 0;
|