dsh-dock 0.14.0 → 0.16.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/README.md +49 -19
- package/client.js +2461 -1008
- package/features/animation/host.js +2 -1
- package/features/balance/host.js +2 -1
- package/features/mobile/host.js +93 -98
- package/features/mobile/view.js +136 -133
- package/features/mobile-relay/host.js +2 -1
- package/features/modelconfig/host.js +21 -462
- package/features/modelconfig/view.js +19 -704
- package/features/notify/host.js +2 -1
- package/features/notify/view.jsx +153 -30
- package/features/providerconfig/host.js +235 -0
- package/features/providerconfig/view.js +604 -0
- package/features/runstate/host.js +12 -1
- package/features/tokenlog/host.js +2 -1
- package/features/tokenlog/view.jsx +42 -12
- package/features/tts/host.js +164 -0
- package/features/tts/view.jsx +1189 -0
- package/features/visionproxy/host.js +1 -1
- package/index.js +9 -1
- package/package.json +3 -3
- package/screenshots/dock-modelconfig.png +0 -0
- package/src/client.jsx +54 -24
- package/src/host-core.js +84 -0
- package/src/model-directory.js +275 -0
- package/src/model-draft.js +119 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// 配置模型(schemastery schema 见 src/host-core.js DockConfig.animation):
|
|
12
12
|
// animationEnabled / effectMode / robotScale
|
|
13
|
-
import { ANIMATION_MODES, sendJson, readBody, readDockRoot, mutateDockSection } from '../../src/host-core.js'
|
|
13
|
+
import { ANIMATION_MODES, sendJson, readBody, rejectUntrustedRequest, readDockRoot, mutateDockSection } from '../../src/host-core.js'
|
|
14
14
|
import { acquireTaskTracker } from '../../src/task-track.js'
|
|
15
15
|
|
|
16
16
|
// 默认配置(schema 默认值一致;settings 未挂载时的兜底)
|
|
@@ -63,6 +63,7 @@ export const feature = {
|
|
|
63
63
|
kind: 'prefix',
|
|
64
64
|
path: '/dsh-dock/animation',
|
|
65
65
|
async handler(req, res) {
|
|
66
|
+
if (rejectUntrustedRequest(ctx, req, res)) return
|
|
66
67
|
try {
|
|
67
68
|
const url = new URL(req.url || '/', 'http://dsh.internal')
|
|
68
69
|
const method = url.pathname.replace(/^\/dsh-dock\/animation\/?/, '').split('/')[0] || ''
|
package/features/balance/host.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// 按余额策略查询各账号余额/配额,返回归一化 JSON 给浏览器半身(同源 fetch,无需 typert)。
|
|
5
5
|
// 余额策略表沿用 @wycto/dsh-balance-panel@0.1.1 的实现(MIT,同作者)。
|
|
6
6
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
7
|
-
import { sendJson, walkPath } from '../../src/host-core.js'
|
|
7
|
+
import { sendJson, walkPath, rejectUntrustedRequest } from '../../src/host-core.js'
|
|
8
8
|
|
|
9
9
|
/** deepseek-official 内置默认值(适配器默认配置的镜像,配置缺失时兜底)。 */
|
|
10
10
|
const DEEPSEEK_DEFAULTS = {
|
|
@@ -480,6 +480,7 @@ export const feature = {
|
|
|
480
480
|
const webServer = ctx.get('webServer')
|
|
481
481
|
if (webServer === undefined) throw new Error('webServer 服务不可用,无法提供余额查询路由')
|
|
482
482
|
const handler = async (req, res) => {
|
|
483
|
+
if (rejectUntrustedRequest(ctx, req, res)) return
|
|
483
484
|
try {
|
|
484
485
|
sendJson(res, 200, await queryAll(ctx))
|
|
485
486
|
} catch (e) {
|
package/features/mobile/host.js
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 职责:窄屏(≤700px)下的排版与交互兜底,经 webServer.tapIndex 内联注入 index.html。
|
|
4
4
|
// 本模块从【远程访问】整体接管手机端适配——远程访问只保留「账号密码登录的
|
|
5
|
-
//
|
|
5
|
+
// 远程入口」本身(网关、账号、二维码、令牌兑换),手机端排版/汉堡菜单/抽屉/遮罩/软键盘
|
|
6
6
|
// 全归这里。因此:
|
|
7
7
|
// - 只有【手机适配】启用时才有手机端 chrome(含未开远程访问的本机窄屏);
|
|
8
8
|
// - 停用【手机适配】= 整套窄屏排版还原,与远程访问开关互不影响。
|
|
9
9
|
//
|
|
10
|
+
// ⚠️ 底部 Tab 栏(会话/插件/设置三入口)已按用户要求移除:它常驻占掉视口底部一条
|
|
11
|
+
// (48px + 安全区),在 390×844 上约 8% 的纵向空间,且与抽屉本身语义重复。会话入口改为
|
|
12
|
+
// 左上角汉堡菜单(三条横线,44×44)——点开左侧会话抽屉;插件/设置/运行状态等入口本来
|
|
13
|
+
// 就在抽屉里(原生侧栏 rail),展开抽屉即可达。
|
|
14
|
+
//
|
|
10
15
|
// 注入三件事(幂等,均可重复调用):
|
|
11
|
-
// 1) MOBILE_LAYOUT_CSS
|
|
16
|
+
// 1) MOBILE_LAYOUT_CSS:窄屏排版——设置/功能坞弹层纵向堆叠、侧栏浮层化、左上角汉堡菜单、
|
|
12
17
|
// 抽屉遮罩、滚动体留白、滚动兜底(见下);
|
|
13
|
-
// 2) MOBILE_BEHAVIOR_JS
|
|
18
|
+
// 2) MOBILE_BEHAVIOR_JS:窄屏行为——遮罩显隐、汉堡按钮(展开抽屉)、软键盘让位、
|
|
14
19
|
// 「点会话收抽屉、点文件夹不收」的全局捕获。
|
|
15
20
|
// 客户端半部(features/mobile/view.js)在宿主脚本缺位时自建同一套 chrome,两者用
|
|
16
21
|
// window.__dshDockMobileDrawer 先到先得互斥,绝不各建一套(曾出现双 Tab 栏与 CSS 互相覆盖)。
|
|
@@ -53,48 +58,63 @@ const MOBILE_LAYOUT_CSS = [
|
|
|
53
58
|
' [data-sidebar-right-panel="fullscreen"][data-sidebar-right-open]{z-index:88!important;pointer-events:auto!important;background:var(--dsw-alias-bg-layer-2,rgb(22,24,28))!important}',
|
|
54
59
|
' [class*="overlayLayer"]{z-index:90!important}',
|
|
55
60
|
// ── 抽屉遮罩:侧栏浮层展开时盖住页面,点击收起(阻断滚动穿透,故 touch-action:none)。
|
|
56
|
-
//
|
|
61
|
+
// 会话入口统一走左上角汉堡菜单,不设左缘浮动把手、不再有底部常驻 Tab 栏。
|
|
57
62
|
' .dsh-mobile-scrim{position:fixed;inset:0;z-index:75;background:rgba(8,10,14,.45);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);touch-action:none}',
|
|
58
|
-
// ──
|
|
59
|
-
// z 序
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
' .dsh-mobile-
|
|
66
|
-
' .dsh-mobile-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
// ── 左上角汉堡菜单(三条横线,44×44 触控面积,避让刘海/圆角安全区)。
|
|
64
|
+
// z 序 66:高于正文,低于遮罩 75 / 抽屉 80 / overlayLayer 90 / 功能坞面板 200,
|
|
65
|
+
// 这些浮层打开时自然盖住汉堡。
|
|
66
|
+
// ⚠️ 视觉上刻意「不贴合卡片」:页面左上角本来就是空的,原生同类控件(右上的侧栏开关)
|
|
67
|
+
// 是**裸图标**——无底色、无边框、无阴影。早期版本做成白底圆角卡片 + 阴影 + 模糊,
|
|
68
|
+
// 悬浮在白底页面上像一块贴上去的异物(用户实测反馈「没有贴合边缘,很突兀」)。
|
|
69
|
+
// 现在与原生同款:透明底、无边框无阴影,只留图标;点按才给极轻的底色反馈。
|
|
70
|
+
' .dsh-mobile-burger{position:fixed;left:env(safe-area-inset-left,0px);top:env(safe-area-inset-top,0px);z-index:66;box-sizing:border-box;width:44px;height:44px;display:flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:0;background:transparent;color:var(--dsw-alias-label-primary,#e6eaf2);cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:manipulation;transition:color .15s ease}',
|
|
71
|
+
' .dsh-mobile-burger svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.9;stroke-linecap:round}',
|
|
72
|
+
' .dsh-mobile-burger.on{color:#4d9fff}',
|
|
73
|
+
' .dsh-mobile-burger:active{background:color-mix(in srgb,currentColor 12%,transparent)}',
|
|
74
|
+
// 待确认任务角标:挂在汉堡右上角。底部 Tab 栏移除前这个提醒挂在「插件」页签上,
|
|
75
|
+
// 现在改挂汉堡——小圆点/数字锚在按钮右上角,不占布局、不破坏裸图标观感。
|
|
76
|
+
// 等待确认(要人介入)= 红点 + 脉冲;进行中 = 蓝色数字(弱一级)。
|
|
77
|
+
' .dsh-mobile-burger-badge{position:absolute;top:4px;right:4px;box-sizing:border-box;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:#4d9fff;color:#fff;font:700 9px/16px inherit;font-variant-numeric:tabular-nums;text-align:center;pointer-events:none;box-shadow:0 0 0 2px var(--dsw-alias-bg-layer-1,#12151b)}',
|
|
78
|
+
' .dsh-mobile-burger-badge[hidden]{display:none}',
|
|
79
|
+
' .dsh-mobile-burger-badge.wait{background:#ef4444;animation:dsh-burger-badge-pulse 1.2s ease-in-out infinite}',
|
|
80
|
+
' @keyframes dsh-burger-badge-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.18)}}',
|
|
81
|
+
' @media (prefers-reduced-motion:reduce){.dsh-mobile-burger-badge.wait{animation:none}}',
|
|
82
|
+
// ── 给标题让出汉堡宽度(用户实测:标题被汉堡压住——截图里「更新所有仓库代码」的首字
|
|
83
|
+
// 直接叠在三条横线上)。汉堡是 fixed 定位(贴安全区、44×44),**不占布局**,而原生
|
|
84
|
+
// 标题从头部的 padding-left:20px 起排——真实实例 390×844 逐字实测:会话标题首字
|
|
85
|
+
// x=28、汉堡右缘 44 → **重叠 16px**;插件页/自动化任务页的页头(自身 padding-left:0,
|
|
86
|
+
// 靠外层容器给 24px)首字 x=24 → 重叠 20px。
|
|
87
|
+
// 改法照原生排版习惯走:**给容器补内边距**,而不是给汉堡加底色/加宽(那是把问题搬到
|
|
88
|
+
// 视觉上)。与客户端半部(features/mobile/view.js)逐字同款,改一处必须同步另一处。
|
|
89
|
+
' header:has([class*="titleRow"]){padding-left:calc(44px + env(safe-area-inset-left,0px))}',
|
|
90
|
+
' [class$="_pageHead"],[class$="_pageHeading"]{padding-left:calc(28px + env(safe-area-inset-left,0px));gap:4px}',
|
|
91
|
+
// ── 输入区吸附到视口底部:欢迎内容原生是 justify-content:center 垂直居中,
|
|
92
|
+
// 手机视口下输入卡片悬在中部、下方留出大片空白。改用「输入区 margin-top:auto」贴底。
|
|
93
|
+
// 底部 Tab 栏已移除,滚动体留白从 72px(Tab 栏高度 + 安全区)进一步收到
|
|
94
|
+
// **只剩安全区**:真实实例 390×844 像素扫描实测,输入卡片下方还有 51px 空白带,
|
|
95
|
+
// 其中 24px 是下面这条滚动体内边距、6px 是 composerSeat 的内边距——都是 Tab 栏时代的
|
|
96
|
+
// 遗留,没有底栏后纯属浪费;收掉后这 30px 直接变成会话区可显示的正文。
|
|
97
|
+
// dsh 自带的 ~21px 卡片下边距保留,视觉仍有呼吸感、不会贴死屏幕边缘。
|
|
77
98
|
//
|
|
78
99
|
// ⚠️ 绝不能再给滚动体加 justify-content:flex-end/center 来贴底:那会把内容挤到
|
|
79
100
|
// 滚动体起点之外,而 Chrome 不把起点方向的溢出算进 scrollHeight(手机视口实测
|
|
80
101
|
// scrollHeight==clientHeight、maxScroll==0),结果是会话记录完全滚不动、首条消息
|
|
81
102
|
// 永远停在视口上方。auto 外边距只在有剩余空间时生效、不产生起点溢出。
|
|
82
103
|
//
|
|
83
|
-
// 软键盘弹出(visualViewport 判定,行为脚本挂 html.dsh-dock-kbd
|
|
84
|
-
|
|
85
|
-
' html
|
|
86
|
-
' html.dsh-dock-kbd
|
|
87
|
-
'
|
|
88
|
-
' [class*="scrollBody"]>[class*="composerSeat"]{margin-top:auto;padding-bottom:6px}',
|
|
104
|
+
// 软键盘弹出(visualViewport 判定,行为脚本挂 html.dsh-dock-kbd):汉堡让位。
|
|
105
|
+
' html:not(.dsh-dock-kbd) [class*="scrollBody"]{padding-bottom:env(safe-area-inset-bottom,0px)!important}',
|
|
106
|
+
' html.dsh-dock-kbd [class*="scrollBody"]{padding-bottom:env(safe-area-inset-bottom,0px)!important}',
|
|
107
|
+
' html.dsh-dock-kbd .dsh-mobile-burger{display:none!important}',
|
|
108
|
+
' [class*="scrollBody"]>[class*="composerSeat"]{margin-top:auto;padding-bottom:0}',
|
|
89
109
|
// ── 滚动兜底(见文件头 a/b 两条根因):所有纵向滚动体显式声明可纵向平移 + iOS 惯性滚动,
|
|
90
110
|
// 避免祖先合成层/触控声明把触摸滚动吃掉;overscroll-behavior 防滚动穿透到外层。
|
|
91
111
|
' [class*="scrollBody"],[class*="sidebarCol"] [class*="list"],[role="dialog"] [class*="options"],[class*="dockm-content"]{-webkit-overflow-scrolling:touch;overscroll-behavior-y:contain;touch-action:pan-y}',
|
|
92
112
|
// 输入区工具行上的 dsh-dock chip(用量/余额):窄屏下 28cqw 的宽度上限把它们挤成
|
|
93
113
|
// 「…」「余..」(手机实测),既读不出数值又白占输入行空间。手机端整组收起,数值在
|
|
94
|
-
//
|
|
114
|
+
// 功能坞(抽屉里进)看;桌面端不动。
|
|
95
115
|
' .dockchip-row{display:none}',
|
|
96
116
|
// 收起的侧栏轨道(collapsed rail)在窄屏只剩 1px 占位,但轨道里的按钮仍会溢出贴在
|
|
97
|
-
//
|
|
117
|
+
// 左边缘(添加工作区/搜索/功能坞/设置等露出半边)。这些入口已由汉堡菜单 + 抽屉接管;
|
|
98
118
|
// 展开抽屉(z 80)不受影响。
|
|
99
119
|
' [class*="sidebarCol"]:has([class*="collapsed"]){visibility:hidden}',
|
|
100
120
|
// ⚠️ 列级 visibility:hidden 在 dsh 0.2.0 上挡不住:工作区浏览器为了展开/收拢动画给自己的
|
|
@@ -124,7 +144,7 @@ const MOBILE_LAYOUT_MARKER = 'data-dsh-mobile-layout'
|
|
|
124
144
|
// 窄屏行为补丁:侧边栏浮层化后,原生的「保持展开」会一直挡住半屏——
|
|
125
145
|
// 点会话行/新会话后自动收起;点侧栏外的页面区域也收起(浮层语义)。
|
|
126
146
|
// 点工作区文件夹**不收**(只展开/收拢分组);点侧栏入口(插件/设置…)等面板弹出后再收,
|
|
127
|
-
//
|
|
147
|
+
// 避免面板被抽屉盖住(等于「点了没反应」)。会话入口是左上角汉堡菜单。
|
|
128
148
|
// 注入点在 <head> 后,body 尚未解析,DOM 创建一律推迟到 DOMContentLoaded;
|
|
129
149
|
// 显隐同步走 MutationObserver(属性翻转即时)+ 低速轮询(覆盖路由重渲等一切边角)。
|
|
130
150
|
const MOBILE_BEHAVIOR_JS = [
|
|
@@ -142,74 +162,51 @@ const MOBILE_BEHAVIOR_JS = [
|
|
|
142
162
|
'function collapse(){var b=document.querySelector(\'button[aria-label="收起侧边栏"],button[aria-label="Collapse sidebar"]\');if(b)b.click()}',
|
|
143
163
|
'function expand(){var b=document.querySelector(\'button[aria-label="打开侧边栏"],button[aria-label="Open sidebar"]\');if(b)b.click()}',
|
|
144
164
|
'function closeDetails(){var d=document.querySelector(\'[data-sidebar-right-panel][data-sidebar-right-open]\');if(!d)return;var c=d.querySelector(\'[data-sidebar-right-toggle]\');if(c)c.click()}',
|
|
145
|
-
'var scrim=null,
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
// 面板首页——宿主版「插件」页签的降级导航路径)。
|
|
149
|
-
'function openDockPage(page){',
|
|
150
|
-
' var b=document.querySelector("button.docke2-rail,button[class*=\'docke2-btn\'][class*=\'docke2-rail\']");',
|
|
151
|
-
' if(b){var col=b.closest(\'[class*="sidebarCol"]\');if(col)col.style.visibility="";b.click();if(col)setTimeout(function(){col.style.visibility=""},0)}',
|
|
152
|
-
' if(!page)return;',
|
|
153
|
-
' setTimeout(function(){',
|
|
154
|
-
' var panel=document.querySelector(".dockm-backdrop");if(!panel)return;',
|
|
155
|
-
' var bs=panel.querySelectorAll("button");',
|
|
156
|
-
' for(var i=0;i<bs.length;i++){var tx=bs[i].textContent||"";if(tx.indexOf(page)>=0&&bs[i].offsetParent){bs[i].click();return}}',
|
|
157
|
-
' },120);',
|
|
158
|
-
'}',
|
|
159
|
-
// 「任务」「功能坞」合并为「插件」页签:有任务在跑/等确认时直接落到运行状态页,否则进面板首页。
|
|
160
|
-
'function openDockSmart(){openDockPage((tasksActive>0||tasksWaiting>0)?"运行状态":null)}',
|
|
161
|
-
'var TAB_DEFS=[',
|
|
162
|
-
' {id:"sessions",label:"会话",icon:\'<svg viewBox="0 0 24 24"><path d="M21 12a8 8 0 0 1-8 8H5l-2 2V12a8 8 0 0 1 8-8h2a8 8 0 0 1 8 8z"/></svg>\',act:function(){expand()}},',
|
|
163
|
-
' {id:"dock",label:"插件",icon:\'<svg viewBox="0 0 24 24"><rect x="4" y="4" width="7" height="7" rx="1.5"/><rect x="13" y="4" width="7" height="7" rx="1.5"/><rect x="4" y="13" width="7" height="7" rx="1.5"/><rect x="13" y="13" width="7" height="7" rx="1.5"/></svg>\',act:function(){openDockSmart()}},',
|
|
164
|
-
' {id:"settings",label:"设置",icon:\'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06A1.7 1.7 0 0 0 15 19.4a1.7 1.7 0 0 0-1 1.55V21a2 2 0 1 1-4 0v-.09A1.7 1.7 0 0 0 9 19.4a1.7 1.7 0 0 0-1.87.34l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.55-1H3a2 2 0 1 1 0-4h.09A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.34-1.87l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.7 1.7 0 0 0 9 4.6a1.7 1.7 0 0 0 1-1.55V3a2 2 0 1 1 4 0v.09a1.7 1.7 0 0 0 1 1.51 1.7 1.7 0 0 0 1.87-.34l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.7 1.7 0 0 0 19.4 9c.23.6.86 1 1.51 1H21a2 2 0 1 1 0 4h-.09a1.7 1.7 0 0 0-1.51 1z"/></svg>\',act:function(){var bs=document.querySelectorAll("button");for(var i=0;i<bs.length;i++){var a=bs[i].getAttribute("aria-label")||"";if(a==="设置"||a==="Settings"){bs[i].click();return}}}}',
|
|
165
|
-
'];',
|
|
166
|
-
'function syncTabs(){',
|
|
167
|
-
' if(!tabbar)return;',
|
|
168
|
-
' var expanded=!sidebarCollapsed();',
|
|
169
|
-
' var settingsOpen=dialogOpen();',
|
|
170
|
-
' var dockOpen=!!document.querySelector(".dockm-backdrop");',
|
|
171
|
-
' var bs=tabbar.querySelectorAll(".dsh-mobile-tab");',
|
|
172
|
-
' for(var i=0;i<bs.length;i++){',
|
|
173
|
-
' var id=bs[i].dataset.tab;',
|
|
174
|
-
' var on=(id==="sessions"&&expanded)||(id==="dock"&&dockOpen)||(id==="settings"&&settingsOpen);',
|
|
175
|
-
' if(on)bs[i].classList.add("on");else bs[i].classList.remove("on");',
|
|
176
|
-
' }',
|
|
177
|
-
'}',
|
|
165
|
+
'var scrim=null,burger=null,badge=null,runstateGone=false;',
|
|
166
|
+
// 左上角汉堡菜单:语义上就是原「会话」页签——点开左侧会话抽屉。插件/设置/运行状态等
|
|
167
|
+
// 入口本来就在抽屉里(原生侧栏 rail),展开即可达,不重复提供。
|
|
178
168
|
'function ensureChrome(){',
|
|
179
169
|
' if(!scrim){scrim=document.createElement("div");scrim.className="dsh-mobile-scrim";document.body.appendChild(scrim)}',
|
|
180
|
-
|
|
181
|
-
'
|
|
182
|
-
'
|
|
183
|
-
'
|
|
184
|
-
'
|
|
185
|
-
'
|
|
186
|
-
'
|
|
187
|
-
'
|
|
188
|
-
'
|
|
189
|
-
' })(TAB_DEFS[i]);',
|
|
190
|
-
' }',
|
|
191
|
-
' document.body.appendChild(tabbar);',
|
|
170
|
+
// 兜底摘除历史上可能残留的旧 chrome(旧 Tab 栏),保证全程只有一份。
|
|
171
|
+
' var old=document.querySelectorAll(".dsh-mobile-tabbar");for(var i=0;i<old.length;i++)old[i].remove();',
|
|
172
|
+
' if(!burger){',
|
|
173
|
+
' burger=document.createElement("button");burger.type="button";burger.className="dsh-mobile-burger";',
|
|
174
|
+
' burger.setAttribute("aria-label","打开会话列表");burger.setAttribute("aria-expanded","false");',
|
|
175
|
+
' burger.innerHTML=\'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16"/><path d="M4 12h16"/><path d="M4 17h16"/></svg><span class="dsh-mobile-burger-badge" hidden></span>\';',
|
|
176
|
+
' badge=burger.querySelector(".dsh-mobile-burger-badge");',
|
|
177
|
+
' burger.addEventListener("click",function(){expand()});',
|
|
178
|
+
' document.body.appendChild(burger);',
|
|
192
179
|
' }',
|
|
193
180
|
'}',
|
|
194
|
-
|
|
195
|
-
|
|
181
|
+
'function syncBurger(){',
|
|
182
|
+
' if(!burger)return;',
|
|
183
|
+
' var expanded=!sidebarCollapsed();',
|
|
184
|
+
' burger.setAttribute("aria-expanded",expanded?"true":"false");',
|
|
185
|
+
' if(expanded)burger.classList.add("on");else burger.classList.remove("on");',
|
|
186
|
+
'}',
|
|
187
|
+
// 待确认任务角标:挂汉堡右上角(替代底部 Tab 栏「插件」页签上那个角标)。
|
|
188
|
+
// 等待确认=红点脉冲(要人介入),进行中=蓝色数字。runstate 路由不存在时静默停轮询。
|
|
189
|
+
// ⚠️ 宿主注入脚本拿不到客户端的功能开关,因此靠【运行状态】注册路由时置的旗标
|
|
190
|
+
// window.__dshDockRunstate(features/runstate/host.js)判断:功能停用时宿主根本不注册
|
|
191
|
+
// 该路由,无条件轮询会先打一次必然 405/404 的请求(控制台一条噪音),
|
|
192
|
+
// 与【用量记录】chip 当年同一个毛病。旗标缺失/为假就完全不轮询。
|
|
196
193
|
'function pollTasks(){',
|
|
197
|
-
' if(runstateGone||!badge||!narrow()||document.visibilityState==="hidden"||!
|
|
194
|
+
' if(runstateGone||!badge||!narrow()||document.visibilityState==="hidden"||!burger||!burger.isConnected)return;',
|
|
195
|
+
' if(!window.__dshDockRunstate){badge.hidden=true;return}',
|
|
198
196
|
' fetch("dsh-dock/runstate/status",{method:"POST",headers:{"content-type":"application/json"},body:"{}"}).then(function(r){',
|
|
199
|
-
' if(r.status===404||r.status===405){runstateGone=true;
|
|
197
|
+
' if(r.status===404||r.status===405){runstateGone=true;badge.hidden=true;return null}',
|
|
200
198
|
' return r.json()',
|
|
201
199
|
' }).then(function(body){',
|
|
202
200
|
' if(!body||!badge)return;',
|
|
203
201
|
' var active=(body.data&&body.data.active)||[],wait=0;',
|
|
204
|
-
' for(var i=0;i<active.length;i++){var ap=active[i].approvals;if(ap&&ap.length)wait
|
|
205
|
-
'
|
|
206
|
-
' if(
|
|
207
|
-
' else
|
|
208
|
-
' else{badge.style.display="none"}',
|
|
202
|
+
' for(var i=0;i<active.length;i++){var ap=active[i].approvals;if(ap&&ap.length)wait+=ap.length}',
|
|
203
|
+
' if(wait>0){badge.textContent=wait>99?"99+":String(wait);badge.className="dsh-mobile-burger-badge wait";badge.hidden=false;burger.setAttribute("title",wait+" 个任务等待你确认")}',
|
|
204
|
+
' else if(active.length>0){badge.textContent=active.length>99?"99+":String(active.length);badge.className="dsh-mobile-burger-badge";badge.hidden=false;burger.setAttribute("title",active.length+" 个任务进行中")}',
|
|
205
|
+
' else{badge.hidden=true;burger.removeAttribute("title")}',
|
|
209
206
|
' }).catch(function(){})',
|
|
210
207
|
'}',
|
|
211
208
|
// 软键盘判定:visualViewport 与布局视口高度差超阈值视为键盘弹出(挂 html.dsh-dock-kbd,
|
|
212
|
-
//
|
|
209
|
+
// 样式表隐藏汉堡并收窄滚动体留白)。
|
|
213
210
|
'var vv=window.visualViewport||null;',
|
|
214
211
|
'function kbdSync(){',
|
|
215
212
|
' var kbd=false;',
|
|
@@ -221,27 +218,25 @@ const MOBILE_BEHAVIOR_JS = [
|
|
|
221
218
|
'function syncChrome(){',
|
|
222
219
|
' kbdSync();',
|
|
223
220
|
' if(!scrim)return;',
|
|
224
|
-
// 兜底摘除历史上可能残留的旧 Tab 栏(正常路径下走到这里说明客户端版未自建,不会有第二份)。
|
|
225
|
-
' var old=document.querySelectorAll(".dsh-mobile-tabbar");for(var i=0;i<old.length;i++){if(old[i]!==tabbar)old[i].remove()}',
|
|
226
221
|
' var on=narrow()&&!!frameEl()&&!dialogOpen();',
|
|
227
222
|
' var col=sidebarCollapsed();',
|
|
228
|
-
|
|
229
|
-
// 右侧详情面板在窄屏是原生全屏(z 40),而 Tab 栏 65 比它高会压边——
|
|
223
|
+
// 右侧详情面板在窄屏是原生全屏(z 40),而汉堡 66 比它高会压边——
|
|
230
224
|
// 面板打开时整体让位,收起(面板自带的折叠按钮)后再回来。
|
|
231
225
|
' var rightOpen=!!document.querySelector(\'[data-sidebar-right-panel][data-sidebar-right-open]\');',
|
|
232
226
|
// 遮罩只在「抽屉真的展开」时显示;收起态必须 display:none——它是全屏 touch-action:none
|
|
233
|
-
//
|
|
227
|
+
// 浮层,残留一份会让整页都滑不动。
|
|
234
228
|
' scrim.style.display=(on&&!col)?"":"none";',
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
'
|
|
238
|
-
' syncTabs();',
|
|
229
|
+
// 汉堡:窄屏且页面骨架就绪后常驻;右侧详情面板打开时隐藏(原生全屏面板会压边)。
|
|
230
|
+
' if(burger)burger.style.display=narrow()&&!!frameEl()&&!rightOpen?"":"none";',
|
|
231
|
+
' syncBurger();',
|
|
239
232
|
'}',
|
|
240
233
|
'function boot(){',
|
|
241
234
|
' ensureChrome();syncChrome();',
|
|
242
235
|
' if(typeof MutationObserver!=="undefined"){new MutationObserver(function(){syncChrome()}).observe(document.documentElement,{attributes:true,attributeFilter:["data-sidebar-collapsed"],subtree:true})}',
|
|
243
236
|
' setInterval(syncChrome,1500);',
|
|
237
|
+
// 角标轮询:低频 3s,仅窄屏 + 页面可见时发;路由不存在则静默停。
|
|
244
238
|
' setInterval(pollTasks,3000);pollTasks();',
|
|
239
|
+
' document.addEventListener("visibilitychange",function(){if(document.visibilityState==="visible")pollTasks()});',
|
|
245
240
|
' var onMq=function(){syncChrome()};',
|
|
246
241
|
' if(mq.addEventListener)mq.addEventListener("change",onMq);else if(mq.addListener)mq.addListener(onMq);',
|
|
247
242
|
' if(vv&&vv.addEventListener)vv.addEventListener("resize",kbdSync);',
|
|
@@ -252,7 +247,7 @@ const MOBILE_BEHAVIOR_JS = [
|
|
|
252
247
|
' if(!narrow())return;',
|
|
253
248
|
' var t=e.target;',
|
|
254
249
|
' if(!t||!t.closest)return;',
|
|
255
|
-
' if(t.closest(".dsh-mobile-
|
|
250
|
+
' if(t.closest(".dsh-mobile-burger"))return;',
|
|
256
251
|
' var expanded=!!document.querySelector(\'button[aria-label="收起侧边栏"],button[aria-label="Collapse sidebar"]\');',
|
|
257
252
|
' var col=document.querySelector(\'[class*="sidebarCol"]\');',
|
|
258
253
|
' var inSidebar=col&&col.contains(t);',
|
|
@@ -297,7 +292,7 @@ export function injectMobile(html) {
|
|
|
297
292
|
export const feature = {
|
|
298
293
|
id: 'mobile',
|
|
299
294
|
name: '手机适配',
|
|
300
|
-
description: '手机/窄屏排版与触控兜底:窄屏弹层纵排、侧栏浮层 +
|
|
295
|
+
description: '手机/窄屏排版与触控兜底:窄屏弹层纵排、侧栏浮层 + 左上角汉堡菜单、抽屉遮罩、软键盘让位、滚动兜底',
|
|
301
296
|
// 与客户端半部一致:默认启用。宿主半部只负责把窄屏排版/行为注入 index.html,
|
|
302
297
|
// 与【远程访问】完全解耦——没开远程访问的本机窄屏同样生效。
|
|
303
298
|
defaultEnabled: true,
|