dsh-sidebar-overlay 0.1.0 → 0.1.1
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 +73 -12
- package/client.js +192 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,25 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
+
## 更新日志
|
|
18
|
+
|
|
19
|
+
### 0.1.1
|
|
20
|
+
|
|
21
|
+
- **新增顶栏「插件」按钮**(风车图标,38×38,与头像/新会话同排)。点一下切到
|
|
22
|
+
官方「插件」面板并收起浮层,动作走 `ctx.layout.selectPanel('plugins')` ——
|
|
23
|
+
与官方点 `sidebar.panellist` 里 plugins 那一行是同一条路径。
|
|
24
|
+
只为该面板可用时才渲染(读 `sidebar.panellist` 登记表判断),不占官方座位;
|
|
25
|
+
`showPluginsPanel: false` 可关闭。
|
|
26
|
+
- **收起后抑制重开**:导航类操作收起浮层后,指针若仍停在热区内不会被立刻重新
|
|
27
|
+
打开(离开热区一次即恢复),消除「点了按钮浮层又弹回来」的观感。
|
|
28
|
+
- 测试增至 463 项;新增断言经变异测试验证(刻意改坏实现后对应断言必然失败)。
|
|
29
|
+
|
|
30
|
+
### 0.1.0
|
|
31
|
+
|
|
32
|
+
- 首次发布。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
17
36
|
## 为什么要重做
|
|
18
37
|
|
|
19
38
|
旧方案走的是「复用官方侧边栏 DOM + CSS 浮层化」。那条路能工作,但有两个
|
|
@@ -142,6 +161,42 @@ const shellStore = { ...shellHandle, create: () => shellInstance }
|
|
|
142
161
|
两样都取不到时(官方改了座位名、或相关插件未启用),个人信息按钮**不渲染** ——
|
|
143
162
|
而不是渲染一个点了没反应的假按钮。
|
|
144
163
|
|
|
164
|
+
### 插件按钮:顶栏右侧,点了打开「插件」页
|
|
165
|
+
|
|
166
|
+
顶栏「新会话」右边那个风车方块按钮,点一下切到官方的「插件」主面板
|
|
167
|
+
(Plugin Manager 那一页),随后浮层自动收起。
|
|
168
|
+
|
|
169
|
+
它跟官方的全局面板图标是**同一个目的地、同一条路径**:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
ctx.layout.selectPanel('plugins') // 官方点 sidebar.panellist 的 plugins 行也是这句
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
所以插件里没有任何「打开插件页」的复刻逻辑,面板内容、页内导航、返回会话
|
|
176
|
+
全都由官方负责(从插件页点任意会话即可回到对话:`uiWorkspace.openSession()`
|
|
177
|
+
内部会 `selectPanel(null)`)。
|
|
178
|
+
|
|
179
|
+
图标沿用官方 `sidebar.panellist` 里那一行的同一个图标
|
|
180
|
+
(`IconPluginPinwheelOutlineRegular`,风车),缺失时用内置同路径 SVG 兜底。
|
|
181
|
+
|
|
182
|
+
**为什么不去占 `sidebar.panellist` 座位**:那个座位的图标是由声明者用
|
|
183
|
+
`renderSlot('sidebar.panellist', …, { only: id })` 交出来的,第三方拿不到;
|
|
184
|
+
而且浮层是自建 DOM,根本不渲染那个座位。去占它只会在官方侧边栏里多出一个
|
|
185
|
+
重复按钮,浮层里仍然画不出来。所以本插件只把那个座位当**能力登记表**读:
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
slots.entriesOfSlot('sidebar.panellist').some(e => e.options.id === 'plugins')
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
有这一行 ⇒ main 里确实注册着这个面板 ⇒ 按钮才渲染。这一步是必须的:
|
|
192
|
+
`layout.selectPanel` 对未注册的 key 会**直接抛错**
|
|
193
|
+
(`main panel "plugins" is not registered`),少了它就是一个点了报错的假按钮。
|
|
194
|
+
`showPluginsPanel: false` 可以关掉这个按钮。
|
|
195
|
+
|
|
196
|
+
> 收起时会置一个「抑制重开」标记:点完按钮指针通常还停在卡片原来的位置,
|
|
197
|
+
> 而那里可能仍在热区内;不抑制的话鼠标一动浮层就弹回来,看起来像按钮没生效。
|
|
198
|
+
> 指针离开热区一次即恢复正常。
|
|
199
|
+
|
|
145
200
|
---
|
|
146
201
|
|
|
147
202
|
## 数据来源(官方公开契约)
|
|
@@ -212,15 +267,18 @@ staticModules: () => ({
|
|
|
212
267
|
|
|
213
268
|
## 功能清单
|
|
214
269
|
|
|
215
|
-
**顶栏:个人信息 +
|
|
216
|
-
-
|
|
270
|
+
**顶栏:个人信息 + 新会话 + 插件**
|
|
271
|
+
- 顶部一行按钮:**个人信息(头像圆钮)→ 新会话 → 插件(风车方块)**,同高(38px)居中对齐
|
|
217
272
|
- 个人信息按钮:**点一下直接打开官方设置弹窗**,不弹账户菜单,
|
|
218
273
|
所以没有「联系我们 / 退出登录」(详见下方「个人信息按钮」一节)。
|
|
219
274
|
头像与昵称读官方同一份账户数据源,未登录时降级为内置人形图标
|
|
220
275
|
- 新会话按钮:38px 高、`.5px` 描边、`button-elevated-fill` 底、字重 500,
|
|
221
276
|
右侧带快捷键提示(`ShortcutKeys`),`aria-keyshortcuts` 同步暴露
|
|
222
|
-
-
|
|
223
|
-
|
|
277
|
+
- 插件按钮:**点一下切到官方「插件」面板并收起浮层**,动作走
|
|
278
|
+
`ctx.layout.selectPanel('plugins')`;图标与官方 `sidebar.panellist` 的
|
|
279
|
+
plugins 行同源(详见下方「插件按钮」一节)
|
|
280
|
+
- 三者互不影响,可分别用 `showProfile` / `showNewSession` / `showPluginsPanel`
|
|
281
|
+
关掉;关掉中间那个时其余两个仍成立
|
|
224
282
|
|
|
225
283
|
**工作区**
|
|
226
284
|
- 分组展示(按工作区 / 按工作区树 / 单列表,三态可切并持久化)
|
|
@@ -378,6 +436,7 @@ window.__dshFloatingSidebarOverlay.isOpen()
|
|
|
378
436
|
archivedFilter: 'default' # 'default' | 'show' | 'only'
|
|
379
437
|
showNewSession: true # 顶部「新会话」按钮
|
|
380
438
|
showProfile: true # 顶部「个人信息」(官方账户入口)按钮
|
|
439
|
+
showPluginsPanel: true # 顶部「插件」面板按钮(面板未注册时不渲染)
|
|
381
440
|
hotkey: true
|
|
382
441
|
hotkeyBinding: { code: 'KeyL', modifiers: ['primary', 'alt'] }
|
|
383
442
|
debug: false
|
|
@@ -568,8 +627,8 @@ max-width: var(--dsh-chat-content-width); width: 100%; margin: 0 auto;
|
|
|
568
627
|
无需浏览器即可跑的五套(共 433 项),全部通过:
|
|
569
628
|
```bash
|
|
570
629
|
node test.mjs # 纯函数 + CSS 契约 + token 白名单 + 账号/设置绑定(206 项)
|
|
571
|
-
node smoke.mjs # 组件渲染烟雾(
|
|
572
|
-
node apply-check.mjs # apply 级集成(
|
|
630
|
+
node smoke.mjs # 组件渲染烟雾(84 项,真实执行组件体)
|
|
631
|
+
node apply-check.mjs # apply 级集成(131 项,Cordis + DOM 桩)
|
|
573
632
|
node layout-check.mjs # CSS 选择器 × 真实 DOM 的间距契约(28 项)
|
|
574
633
|
node repro-hover-flicker.mjs # 鼠标触发闪烁的逐帧复现(14 项)
|
|
575
634
|
```
|
|
@@ -675,13 +734,15 @@ node e2e-profile.mjs # 个人信息按钮端到端(19 项:座位链
|
|
|
675
734
|
`sidebar.session.row.leading`、`.hover`、`sidebar.panellist`、
|
|
676
735
|
`sidebar.settings`、`sidebar.footer.action`)**不会**出现在本插件的浮层里。
|
|
677
736
|
原因是 slot 的「声明即认领」规则,见上。
|
|
678
|
-
2.
|
|
679
|
-
|
|
680
|
-
|
|
737
|
+
2. **只搬了一个全局面板**:`sidebar.panellist` 座位仍被官方独占,浮层里没有
|
|
738
|
+
完整的「全局面板」列表 —— 只有**插件**这一个入口(顶栏右侧风车按钮)。
|
|
739
|
+
三个例外,都是**只借用数据/能力、不占用座位**:
|
|
681
740
|
- **个人信息按钮**:读 `settings.launcher` 条目的 `account` source 画头像,
|
|
682
741
|
并从 `sidebar.settings` 条目的 store 调 `actions.open()` 打开设置弹窗
|
|
683
742
|
(只保留「设置」这一件事,见上文「个人信息按钮」一节)。
|
|
684
|
-
-
|
|
743
|
+
- **插件按钮**:读 `sidebar.panellist` 的登记表判断面板是否可用,动作走
|
|
744
|
+
`ctx.layout.selectPanel('plugins')`(见上文「插件按钮」一节)。
|
|
745
|
+
- 设置弹窗、插件页本体仍由官方渲染,本插件只是把它们**叫出来**。
|
|
685
746
|
3. **依赖官方 hook 名**:`useSessions` / `useSessionStatus` / `useWorkspaces` /
|
|
686
747
|
`usePanelInfo` 来自官方 `provideRoot`,官方改名会失配(此时组件降级为空列表
|
|
687
748
|
并在控制台告警,不会崩)。
|
|
@@ -710,8 +771,8 @@ node e2e-profile.mjs # 个人信息按钮端到端(19 项:座位链
|
|
|
710
771
|
| `cordis.patch.yml` | bundle patch:`insert` 条目(id `sidebar-overlay`) |
|
|
711
772
|
| `package.json` | `dsh.client` 声明:web 平台、立即加载 |
|
|
712
773
|
| `test.mjs` | 纯函数 + CSS 契约 + token 白名单 + 账号/设置解析(206 项) |
|
|
713
|
-
| `smoke.mjs` | 组件渲染烟雾(
|
|
714
|
-
| `apply-check.mjs` | apply 级集成测试(
|
|
774
|
+
| `smoke.mjs` | 组件渲染烟雾(84 项,含顶栏排版、点击开设置与插件按钮) |
|
|
775
|
+
| `apply-check.mjs` | apply 级集成测试(131 项) |
|
|
715
776
|
| `layout-check.mjs` | CSS 选择器 × 真实 DOM 的间距契约测试(28 项) |
|
|
716
777
|
| `repro-hover-flicker.mjs` | 鼠标触发闪烁的逐帧复现(14 项) |
|
|
717
778
|
| `visual-check.mjs` | headless Chrome 真实渲染几何验证(52 项,含顶栏几何 12 项) |
|
package/client.js
CHANGED
|
@@ -94,6 +94,41 @@ window.__ModuleLoader__.load({
|
|
|
94
94
|
)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* 内置「风车」图标(官方 `IconPluginPinwheelOutlineRegular` 的等价路径)。
|
|
99
|
+
*
|
|
100
|
+
* 「插件」面板按钮靠它兜底:primitives 缺失时 `iconOf` 返回空组件,
|
|
101
|
+
* 按钮就成了没有任何内容的圆壳,用户看不出那是什么。
|
|
102
|
+
*/
|
|
103
|
+
function PluginGlyph({ size = 16 }) {
|
|
104
|
+
return h('svg', {
|
|
105
|
+
width: size,
|
|
106
|
+
height: size,
|
|
107
|
+
viewBox: '0 0 16 16',
|
|
108
|
+
fill: 'none',
|
|
109
|
+
'aria-hidden': 'true',
|
|
110
|
+
focusable: 'false',
|
|
111
|
+
strokeWidth: 1,
|
|
112
|
+
},
|
|
113
|
+
h('path', {
|
|
114
|
+
d: 'M7.84457 5.06199C11.6605 4.93876 14.7962 6.14848 14.8484 7.76397C14.8875 8.97461 13.1838 10.0696 10.7215 10.5942',
|
|
115
|
+
stroke: 'currentColor',
|
|
116
|
+
}),
|
|
117
|
+
h('path', {
|
|
118
|
+
d: 'M5.12742 8.07731C5.00419 4.26138 6.21391 1.12568 7.8294 1.07351C9.04004 1.03441 10.135 2.73808 10.6596 5.20037',
|
|
119
|
+
stroke: 'currentColor',
|
|
120
|
+
}),
|
|
121
|
+
h('path', {
|
|
122
|
+
d: 'M8.02457 10.6802C4.20865 10.8034 1.07294 9.5937 1.02077 7.97821C0.981678 6.76758 2.68535 5.67262 5.14763 5.14798',
|
|
123
|
+
stroke: 'currentColor',
|
|
124
|
+
}),
|
|
125
|
+
h('path', {
|
|
126
|
+
d: 'M10.7476 7.89535C10.8708 11.7113 9.66109 14.847 8.0456 14.8991C6.83496 14.9382 5.74 13.2346 5.21536 10.7723',
|
|
127
|
+
stroke: 'currentColor',
|
|
128
|
+
}),
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
97
132
|
/** 官方图标(缺失时为空组件)。 */
|
|
98
133
|
const Icons = {
|
|
99
134
|
search: iconOf('IconSearchOutlineRegular'),
|
|
@@ -111,6 +146,9 @@ window.__ModuleLoader__.load({
|
|
|
111
146
|
/** 个人信息按钮:未登录 / 无头像时的兜底图标。
|
|
112
147
|
* 优先官方 `IconUserOutlineMedium`,缺失时用内置同名规格的 SVG。 */
|
|
113
148
|
user: P.IconUserOutlineMedium ?? UserGlyph,
|
|
149
|
+
/** 「插件」面板按钮:官方侧边栏 `sidebar.panellist` 的 plugins 行同一个图标
|
|
150
|
+
* (风车)。缺失时用内置同路径 SVG,保证按钮不会变成空壳。 */
|
|
151
|
+
plugins: P.IconPluginPinwheelOutlineRegular ?? PluginGlyph,
|
|
114
152
|
edit: iconOf('IconEditOutlineRegular'),
|
|
115
153
|
trash: iconOf('IconTrashOutlineRegular'),
|
|
116
154
|
archive: iconOf('IconArchiveOutlineRegular'),
|
|
@@ -136,6 +174,8 @@ window.__ModuleLoader__.load({
|
|
|
136
174
|
const inject = ['slots']
|
|
137
175
|
|
|
138
176
|
const PANEL_ID = 'sidebar-overlay'
|
|
177
|
+
/** 官方「插件」main 面板的 key(Plugin Manager 占用,见 ui-sidebar 的 panellist 登记)。 */
|
|
178
|
+
const PANEL_PLUGINS_ID = 'plugins'
|
|
139
179
|
/** 语言命名空间。 */
|
|
140
180
|
const NS = 'floatingSidebarOverlay'
|
|
141
181
|
/** 官方存储的默认工作区标题;展示时替换为本地化名称。 */
|
|
@@ -189,6 +229,8 @@ window.__ModuleLoader__.load({
|
|
|
189
229
|
showNewSession: true,
|
|
190
230
|
/** 是否显示卡片左上角的「个人信息」头像按钮(原侧边栏底部的账户入口)。 */
|
|
191
231
|
showProfile: true,
|
|
232
|
+
/** 是否显示顶栏的「插件」面板按钮(等价于官方 sidebar.panellist 的 plugins 行)。 */
|
|
233
|
+
showPluginsPanel: true,
|
|
192
234
|
/** 是否注册总开关快捷键。 */
|
|
193
235
|
hotkey: true,
|
|
194
236
|
/** 总开关快捷键。 */
|
|
@@ -1086,6 +1128,35 @@ window.__ModuleLoader__.load({
|
|
|
1086
1128
|
/* 头像加载失败/未登录:图标按官方尺寸(16)落在槽内。 */
|
|
1087
1129
|
[data-dsh-fso-profile-avatar] > svg { flex: none; }
|
|
1088
1130
|
|
|
1131
|
+
/* ── 「插件」面板按钮:官方 sidebar.panellist 的 plugins 行的浮层版 ──────
|
|
1132
|
+
* 官方那一行是宽 36 高 36 的方形、radius-md、hover/interactive-bg-hover、
|
|
1133
|
+
* 选中是同一底色 + label-primary(见 ui-sidebar 的 panelRow / panelActive)。
|
|
1134
|
+
* 这里与「个人信息」并排,取同高 38 与同描边,只在圆角上保留官方的方形
|
|
1135
|
+
* 语义(radius-md),一眼能看出「这个进面板、那个进设置」。 */
|
|
1136
|
+
[data-dsh-fso-panel-button] {
|
|
1137
|
+
box-sizing: border-box;
|
|
1138
|
+
flex: none;
|
|
1139
|
+
display: inline-flex;
|
|
1140
|
+
align-items: center;
|
|
1141
|
+
justify-content: center;
|
|
1142
|
+
width: 38px;
|
|
1143
|
+
height: 38px;
|
|
1144
|
+
padding: 0;
|
|
1145
|
+
border: .5px solid var(--dsw-alias-border-l3);
|
|
1146
|
+
border-radius: var(--dsw-radius-md);
|
|
1147
|
+
background: var(--dsw-alias-button-elevated-fill);
|
|
1148
|
+
color: var(--dsw-alias-label-primary);
|
|
1149
|
+
cursor: pointer;
|
|
1150
|
+
}
|
|
1151
|
+
[data-dsh-fso-panel-button]:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1152
|
+
[data-dsh-fso-panel-button]:focus-visible {
|
|
1153
|
+
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
1154
|
+
outline-offset: -2px;
|
|
1155
|
+
}
|
|
1156
|
+
/* 选中态:官方 panelActive 用的就是 interactive-bg-hover,不另造颜色。 */
|
|
1157
|
+
[data-dsh-fso-panel-button][data-active] { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1158
|
+
[data-dsh-fso-panel-button] > svg { flex: none; }
|
|
1159
|
+
|
|
1089
1160
|
/* ── 区头:对齐官方 sectionHeader(36px)─────────────────────────────────
|
|
1090
1161
|
* 官方在非 rail 态给 sectionHeader 加了 margin-top:2px; margin-right:-4px,
|
|
1091
1162
|
* 让区头的操作按钮视觉上贴到列表右边线。 */
|
|
@@ -1702,6 +1773,10 @@ window.__ModuleLoader__.load({
|
|
|
1702
1773
|
'profile.openSettings': '打开设置',
|
|
1703
1774
|
'profile.signedIn': '已登录 DeepSeek',
|
|
1704
1775
|
'profile.signedOut': '未登录',
|
|
1776
|
+
/* 「插件」面板按钮:与官方 sidebar 命名空间 `panels.label`(全局面板)
|
|
1777
|
+
* 同义,但这里只有一个面板,所以文案直接点名「插件」。 */
|
|
1778
|
+
'panel.plugins': '插件',
|
|
1779
|
+
'panel.failed': '无法打开插件面板',
|
|
1705
1780
|
'session.untitled': '未命名',
|
|
1706
1781
|
'search': '搜索会话',
|
|
1707
1782
|
'search.sessions.aria': '搜索会话',
|
|
@@ -1829,6 +1904,8 @@ window.__ModuleLoader__.load({
|
|
|
1829
1904
|
'profile.openSettings': 'Open settings',
|
|
1830
1905
|
'profile.signedIn': 'Signed in to DeepSeek',
|
|
1831
1906
|
'profile.signedOut': 'Signed out',
|
|
1907
|
+
'panel.plugins': 'Plugins',
|
|
1908
|
+
'panel.failed': 'Cannot open the Plugins panel',
|
|
1832
1909
|
'session.untitled': 'Untitled',
|
|
1833
1910
|
'search': 'Search sessions',
|
|
1834
1911
|
'search.sessions.aria': 'Search sessions',
|
|
@@ -2914,6 +2991,31 @@ window.__ModuleLoader__.load({
|
|
|
2914
2991
|
}
|
|
2915
2992
|
}
|
|
2916
2993
|
|
|
2994
|
+
/**
|
|
2995
|
+
* 判断官方 `sidebar.panellist` 里是否登记了某个全局面板。
|
|
2996
|
+
*
|
|
2997
|
+
* 浮层是自建 DOM,**渲染不出**那个座位里的图标(图标由声明者
|
|
2998
|
+
* `renderSlot('sidebar.panellist', …, { only: id })` 交出,第三方拿不到)。
|
|
2999
|
+
* 所以这里只把它当**能力登记表**读:有这一行 ⇒ 对应的 main 面板确实
|
|
3000
|
+
* 注册着,`ctx.layout.selectPanel(id)` 不会抛错,按钮才画出来。
|
|
3001
|
+
*
|
|
3002
|
+
* 这条检查是必须的:`selectPanel` 对未注册的 key 会**直接 throw**
|
|
3003
|
+
* (`layout.selectPanel: main panel "…" is not registered`),
|
|
3004
|
+
* 没这一步就会得到一个「点了报错」的假按钮。
|
|
3005
|
+
*
|
|
3006
|
+
* @param {object} slots - `ctx.slots`
|
|
3007
|
+
* @param {string} id - 面板 id(如 `'plugins'`)。
|
|
3008
|
+
* @returns {boolean} 座位里是否存在该 id 的登记。
|
|
3009
|
+
*/
|
|
3010
|
+
function hasPanelEntry(slots, id) {
|
|
3011
|
+
try {
|
|
3012
|
+
if (slots === undefined || slots === null || typeof slots.entriesOfSlot !== 'function') return false
|
|
3013
|
+
return slots.entriesOfSlot('sidebar.panellist').some(entry => entry?.options?.id === id)
|
|
3014
|
+
} catch {
|
|
3015
|
+
return false
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
|
|
2917
3019
|
/**
|
|
2918
3020
|
* 个人信息按钮:浮层顶部、新会话按钮左边的账户入口。
|
|
2919
3021
|
*
|
|
@@ -2964,6 +3066,34 @@ window.__ModuleLoader__.load({
|
|
|
2964
3066
|
)
|
|
2965
3067
|
}
|
|
2966
3068
|
|
|
3069
|
+
/**
|
|
3070
|
+
* 「插件」面板按钮:官方 `sidebar.panellist` 里 plugins 那一行的浮层版。
|
|
3071
|
+
*
|
|
3072
|
+
* 点击 = `ctx.layout.selectPanel('plugins')`,与官方点同一个图标**走同一条
|
|
3073
|
+
* 路径**,所以不必在这里复制任何「打开插件页」的逻辑。
|
|
3074
|
+
*
|
|
3075
|
+
* 选中态读 `usePanelInfo`(与本组件已在用的那一个 hook 同源),高亮用官方
|
|
3076
|
+
* `panelActive` 的同一个 token。
|
|
3077
|
+
*
|
|
3078
|
+
* @param {{usePanelInfo: Function, label: string, onOpen: Function}} props
|
|
3079
|
+
*/
|
|
3080
|
+
function PanelButton({ usePanelInfo, label, onOpen }) {
|
|
3081
|
+
const active = typeof usePanelInfo === 'function'
|
|
3082
|
+
? usePanelInfo(info => info.activePanelId === PANEL_PLUGINS_ID)
|
|
3083
|
+
: false
|
|
3084
|
+
return h(
|
|
3085
|
+
P.Tooltip ?? TooltipFallback,
|
|
3086
|
+
{ label, side: 'bottom', delayMs: 500, children: h('button', {
|
|
3087
|
+
type: 'button',
|
|
3088
|
+
'data-dsh-fso-panel-button': '',
|
|
3089
|
+
...(active === true ? { 'data-active': '' } : {}),
|
|
3090
|
+
'aria-label': label,
|
|
3091
|
+
'aria-current': active === true ? 'page' : undefined,
|
|
3092
|
+
onClick: () => { onOpen() },
|
|
3093
|
+
}, h(Icons.plugins, { size: 16 })) },
|
|
3094
|
+
)
|
|
3095
|
+
}
|
|
3096
|
+
|
|
2967
3097
|
/**
|
|
2968
3098
|
* 浮动侧边栏主体。
|
|
2969
3099
|
*
|
|
@@ -2980,6 +3110,7 @@ window.__ModuleLoader__.load({
|
|
|
2980
3110
|
renameWorkspace, deleteWorkspace, insertWorkspaceBefore, createWorkspace,
|
|
2981
3111
|
searchSessions, searchResultLimit, pickDirectory, hostHome, notify,
|
|
2982
3112
|
accountSource, accountT, openSettings,
|
|
3113
|
+
pluginsPanel, openPluginsPanel, closeOverlay,
|
|
2983
3114
|
} = props
|
|
2984
3115
|
|
|
2985
3116
|
const merged = { ...DEFAULTS, ...(config ?? {}) }
|
|
@@ -3707,7 +3838,24 @@ window.__ModuleLoader__.load({
|
|
|
3707
3838
|
onOpenSettings: typeof openSettings === 'function' ? openSettings : () => {},
|
|
3708
3839
|
})
|
|
3709
3840
|
: null
|
|
3710
|
-
|
|
3841
|
+
/* 「插件」面板按钮:与头像同排,排在「新会话」右边。
|
|
3842
|
+
* 只有官方 `sidebar.panellist` 里确实登记了 plugins 时才渲染 ——
|
|
3843
|
+
* `layout.selectPanel` 对未注册的 key 会抛错,这里不能给假按钮。 */
|
|
3844
|
+
const pluginsButton = merged.showPluginsPanel === true
|
|
3845
|
+
&& pluginsPanel === true
|
|
3846
|
+
&& typeof openPluginsPanel === 'function'
|
|
3847
|
+
? h(PanelButton, {
|
|
3848
|
+
key: 'plugins-panel',
|
|
3849
|
+
usePanelInfo,
|
|
3850
|
+
label: t('panel.plugins'),
|
|
3851
|
+
onOpen: () => {
|
|
3852
|
+
// 先叫出面板,再收浮层:selectPanel 失败时不至于连浮层都关了。
|
|
3853
|
+
const opened = openPluginsPanel()
|
|
3854
|
+
if (opened === true && typeof closeOverlay === 'function') closeOverlay()
|
|
3855
|
+
},
|
|
3856
|
+
})
|
|
3857
|
+
: null
|
|
3858
|
+
const topbar = h('div', { 'data-dsh-fso-topbar': '' }, profileButton, newSessionButton, pluginsButton)
|
|
3711
3859
|
|
|
3712
3860
|
/* ── 模态框 ───────────────────────────────────────────────────────── */
|
|
3713
3861
|
const Modal = P.Modal
|
|
@@ -3872,6 +4020,15 @@ window.__ModuleLoader__.load({
|
|
|
3872
4020
|
let open = false
|
|
3873
4021
|
let enabled = merged.enabled !== false
|
|
3874
4022
|
let disposed = false
|
|
4023
|
+
/**
|
|
4024
|
+
* 「刚因导航而收起」标记。
|
|
4025
|
+
*
|
|
4026
|
+
* 顶栏按钮点完会收起浮层,但此时指针往往还停在卡片原来的位置上,而热区
|
|
4027
|
+
* (`trigger: 'content'` 时到内容列左缘的 40%)可能覆盖那一带 —— 用户只要
|
|
4028
|
+
* 轻动一下鼠标,浮层就“又弹出来”,看起来像按钮没生效。所以收起后置上这个
|
|
4029
|
+
* 标记,直到指针离开热区一次才恢复热区触发。
|
|
4030
|
+
*/
|
|
4031
|
+
let suppressReveal = false
|
|
3875
4032
|
|
|
3876
4033
|
/** 取卡片节点(可能还没渲染出来)。 */
|
|
3877
4034
|
const cardOf = () => document.querySelector('[data-dsh-fso-card]')
|
|
@@ -4190,6 +4347,13 @@ window.__ModuleLoader__.load({
|
|
|
4190
4347
|
const inTrigger = x <= triggerWidth() && belowTop
|
|
4191
4348
|
|
|
4192
4349
|
if (!open) {
|
|
4350
|
+
/* 导航后抑制:指针还有一次机会离开热区,离开即恢复。
|
|
4351
|
+
* 否则点完「插件」浮层马上又弹回来。 */
|
|
4352
|
+
if (suppressReveal) {
|
|
4353
|
+
if (!inTrigger) suppressReveal = false
|
|
4354
|
+
return
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4193
4357
|
/* 未命中时**低频**重测热区:中栏宽度会因用户拖拽、侧边栏开合、
|
|
4194
4358
|
* 会话切换而变化(官方写入 `--dsh-chat-user-width`),只读 DOM 的
|
|
4195
4359
|
* 布局也会变(header 有无 tab 时高度不同)。只靠 resize 会漏。
|
|
@@ -4326,6 +4490,31 @@ window.__ModuleLoader__.load({
|
|
|
4326
4490
|
* `actions.open()`(与官方点「设置」同一条路径)。取不到时
|
|
4327
4491
|
* 传 undefined,个人信息按钮就不渲染 —— 而不是一个点了没反应的假按钮。 */
|
|
4328
4492
|
openSettings,
|
|
4493
|
+
/** 「插件」面板是否可用:官方 `sidebar.panellist` 里登记了 plugins
|
|
4494
|
+
* 才为 true。这里只读登记表,不占座位、不渲染它的图标。 */
|
|
4495
|
+
pluginsPanel: hasPanelEntry(slots, PANEL_PLUGINS_ID),
|
|
4496
|
+
/** 打开「插件」面板:与官方点同一个图标走同一条路径。
|
|
4497
|
+
* `selectPanel` 对未注册 key 会 throw,所以要包起来并把结果
|
|
4498
|
+
* 作为布尔返回,调用处据此决定是否收起浮层。 */
|
|
4499
|
+
openPluginsPanel: () => {
|
|
4500
|
+
try {
|
|
4501
|
+
const layout = svc('layout')
|
|
4502
|
+
if (typeof layout?.selectPanel !== 'function') return false
|
|
4503
|
+
layout.selectPanel(PANEL_PLUGINS_ID)
|
|
4504
|
+
return true
|
|
4505
|
+
} catch (error) {
|
|
4506
|
+
console.warn('[sidebar-overlay] 打开插件面板失败:', error?.message ?? error)
|
|
4507
|
+
notify('panel.failed')
|
|
4508
|
+
return false
|
|
4509
|
+
}
|
|
4510
|
+
},
|
|
4511
|
+
/** 收起浮层(供顶栏按钮在导航后调用)。
|
|
4512
|
+
* 置 `suppressReveal`:指针通常还停在卡片原位置,而那里仍在
|
|
4513
|
+
* 热区里;不抑制就会立刻又弹回来。指针离开热区一次即恢复。 */
|
|
4514
|
+
closeOverlay: () => {
|
|
4515
|
+
suppressReveal = true
|
|
4516
|
+
setOpen(false)
|
|
4517
|
+
},
|
|
4329
4518
|
/** 快捷键目录:渲染器按 hooks 约定自动生成 useShortcuts prop。
|
|
4330
4519
|
* source 缺失时组件内的类型检查会兜住。 */
|
|
4331
4520
|
...(shortcuts?.catalog === undefined ? {} : { hooks: { shortcuts: shortcuts.catalog } }),
|
|
@@ -4487,6 +4676,8 @@ window.__ModuleLoader__.load({
|
|
|
4487
4676
|
exports.__resolveOpenSettings = resolveOpenSettings
|
|
4488
4677
|
exports.__bindEntryFace = bindEntryFace
|
|
4489
4678
|
exports.__ProfileButton = ProfileButton
|
|
4679
|
+
exports.__PanelButton = PanelButton
|
|
4680
|
+
exports.__hasPanelEntry = hasPanelEntry
|
|
4490
4681
|
return module.exports
|
|
4491
4682
|
},
|
|
4492
4683
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-sidebar-overlay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Floating sidebar overlay for DSH: an independently rendered workspace/session browser mounted in the official shell.overlay seat, without touching the official sidebar or grid. 独立渲染的 DSH 浮动侧边栏:不碰官方侧边栏与 grid。",
|
|
6
6
|
"author": "blockman0321",
|