dsh-sidebar-row-menu 1.5.1 → 1.7.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 +79 -0
- package/README.en.md +58 -26
- package/README.md +42 -19
- package/lib/client.js +426 -61
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.7.0 — 2026-11-05
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Per-workspace archived-session toggle in the workspace menu.** A workspace
|
|
8
|
+
whose group holds archived sessions now gets "Show archived sessions" /
|
|
9
|
+
"Hide archived sessions" in its right-click menu (and in the row's "…" menu
|
|
10
|
+
when opted in), using the same archive glyphs as the native View options
|
|
11
|
+
items. DSH only offers one global archived filter; the plugin switches it to
|
|
12
|
+
"all conversations" and hides every other workspace's (and "Ungrouped")
|
|
13
|
+
archived rows, so each workspace can reveal its own archived sessions
|
|
14
|
+
independently. The choice persists across reloads, and changing the native filter clears it so the
|
|
15
|
+
host's global choice stays authoritative. The entry can be switched off under
|
|
16
|
+
Settings → row menu and is only shown when the workspace actually has
|
|
17
|
+
archived sessions.
|
|
18
|
+
|
|
19
|
+
## 1.6.0 — 2026-11-05
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Workspace entries can opt into the native "…" menu on DSH 0.2.2.** The
|
|
24
|
+
workspace-row entries (New session, pinned app, Open with, Copy workspace path,
|
|
25
|
+
Open in new tab, Delete workspace and all sessions) are now right-click only by
|
|
26
|
+
default, matching the session rows: a provider answers only while a right-click
|
|
27
|
+
gesture is active. Enabling the matching switch under Settings → "Also show in the
|
|
28
|
+
"…" menu → workspace" makes it skip that check, so the entry also lands in the
|
|
29
|
+
host's "…" menu.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **"Open with" keeps its icon on DSH 0.2.2.** The entry is contributed to the
|
|
34
|
+
host menu as data, so it had no icon of its own; it now registers an
|
|
35
|
+
`contextMenu.icon` slot (`sidebar-row-menu.open-with`) and points the action at
|
|
36
|
+
it, matching the self-drawn 0.2.1 menu.
|
|
37
|
+
- **Menu separators stay visible at 125–150% display scaling.** The host draws
|
|
38
|
+
0.5px hairlines; at fractional device pixel ratios one of them was painted as a
|
|
39
|
+
~0.4px smear and read as missing. A `min-resolution: 1.2dppx` media query now
|
|
40
|
+
thickens menu separators (and the 0.2.1 `.dsrm-sep`) to 1px in that range, and
|
|
41
|
+
leaves 1x and 2x displays untouched.
|
|
42
|
+
- **The workspace menu follows the session menu's grouping.** On 0.2.2 the entries
|
|
43
|
+
were interleaved as right-click group → pinned/open-with → copy path, so the
|
|
44
|
+
separator before "Open with" landed in a different place than in the session
|
|
45
|
+
menu. The workspace providers now use the session order — row actions, then
|
|
46
|
+
Copy path/Reveal, then the open-with group, then Delete — so both menus split
|
|
47
|
+
into the same three blocks.
|
|
48
|
+
- **"Open with" shows its submenu affordance again.** Host menu items draw no
|
|
49
|
+
caret for submenu parents. The action's label is rendered by the host as an
|
|
50
|
+
element, so the workspace entry now passes a label that lays the text out with
|
|
51
|
+
a right-aligned chevron, matching the session menu's arrow instead of a "›"
|
|
52
|
+
glued to the text (the 0.2.1 self-drawn menu keeps its own arrow icon).
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- **The workspace-row switches are no longer hidden on 0.2.2.** The section note now
|
|
57
|
+
explains that 0.2.2 shares one host menu between right-click and the `…` button
|
|
58
|
+
while the plugin's entries still follow the gesture, and that the switches opt
|
|
59
|
+
individual entries into that shared menu. This reverses the 1.5.0 decision to hide
|
|
60
|
+
the group.
|
|
61
|
+
|
|
62
|
+
### 中文摘要
|
|
63
|
+
|
|
64
|
+
- **0.2.2 上工作区条目默认只出现在右键菜单**:工作区 provider 解析时检查同一枚
|
|
65
|
+
右键手势标记,行尾「…」的 `pointerdown` 会先清标,所以「…」菜单保持原生内容。
|
|
66
|
+
- **工作区行可以在设置里「同时放进「…」菜单」**:逐项开启后 provider 跳过手势判断,
|
|
67
|
+
对应条目也会进入宿主的「…」菜单;0.2.2 上这组开关不再隐藏,说明文字改成描述
|
|
68
|
+
共用菜单 + 手势区分的真实行为。0.2.1 仍由插件接管「…」按钮自绘。
|
|
69
|
+
- **0.2.2 的「打开方式」恢复图标**:数据贡献的条目本身没有图标,现在注册
|
|
70
|
+
`contextMenu.icon`(`sidebar-row-menu.open-with`)并由动作引用,与 0.2.1 自绘
|
|
71
|
+
菜单一致。
|
|
72
|
+
- **125%–150% 缩放下菜单分隔线可见**:宿主用的是 0.5px 细线,在分数像素比下会被
|
|
73
|
+
摊成约 0.4px 而像「消失」;现在该区间内分隔线(含 0.2.1 的 `.dsrm-sep`)补到
|
|
74
|
+
1px,1x 与 2x 显示不变。
|
|
75
|
+
- **工作区菜单改用会话菜单的分组规则**:原来「打开方式」组夹在复制路径之前,分隔线
|
|
76
|
+
落点与会话菜单不一致;现在顺序为行内动作 → 复制路径/在文件夹显示 → 打开方式组 →
|
|
77
|
+
删除,两份菜单都分成同样三块。
|
|
78
|
+
- **「打开方式」恢复子菜单箭头**:宿主不给子菜单父项画箭头,但 label 是当元素渲染的,
|
|
79
|
+
所以工作区条目改成「文字 + 右对齐箭头」的 label,与会话菜单的箭头位置一致(不再是
|
|
80
|
+
贴在文字后面的「›」);0.2.1 自绘菜单仍用自己的箭头图标。
|
|
81
|
+
|
|
3
82
|
## 1.5.1 — 2026-11-05
|
|
4
83
|
|
|
5
84
|
### Fixed
|
package/README.en.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
Right-click menus for the sidebar of [DeepSeek Harness](https://github.com/deepseek-ai) (DSH):
|
|
11
11
|
|
|
12
12
|
- **Workspace rows** — right-click opens a menu (since DSH 0.2.2 it is part of the
|
|
13
|
-
host's native menu,
|
|
13
|
+
host's native menu, with the plugin's items interleaved by `order`);
|
|
14
14
|
- **Session rows nested under a workspace** — right-click opens that row's own menu
|
|
15
15
|
at the pointer, with the plugin's own entries appended.
|
|
16
16
|
|
|
@@ -27,8 +27,10 @@ Session menu content comes from the `sidebar.workspaces.session.menu.item` slot
|
|
|
27
27
|
built-in actions and other plugins' actions are all there. This plugin only appends
|
|
28
28
|
its own entries; it does not copy or hardcode any built-in action.
|
|
29
29
|
|
|
30
|
-
> The "…" button menu stays exactly as DSH ships it. The plugin's
|
|
31
|
-
>
|
|
30
|
+
> The "…" button menu stays exactly as DSH ships it. The plugin's entries appear
|
|
31
|
+
> only when the menu is opened by right-click — on 0.2.2 that holds for workspace
|
|
32
|
+
> and session rows alike, and each entry can be opted into the "…" menu in Settings.
|
|
33
|
+
> On 0.2.2 both gestures share
|
|
32
34
|
> one host menu, so the plugin tells them apart by the gesture itself: a right-click
|
|
33
35
|
> sets a mark that is released once the menu panel has actually mounted, while the
|
|
34
36
|
> pointerdown on the "…" button clears it immediately. A right-click menu does not
|
|
@@ -40,20 +42,24 @@ its own entries; it does not copy or hardcode any built-in action.
|
|
|
40
42
|
### Workspace row
|
|
41
43
|
|
|
42
44
|
On 0.2.2 the host renders this menu, and the plugin's items interleave with the
|
|
43
|
-
built-in Rename (100), Reveal in file manager (200) and Delete workspace (300)
|
|
44
|
-
|
|
45
|
-
tab
|
|
46
|
-
|
|
45
|
+
built-in Rename (100), Reveal in file manager (200) and Delete workspace (300),
|
|
46
|
+
using the same grouping rules as the session menu: New session 90, Open in new
|
|
47
|
+
tab 110, archived-session toggle 130, Copy workspace path 150, pinned app 210,
|
|
48
|
+
Open with 220, Delete workspace and all sessions 310. These items are right-click only by default; the row's "…"
|
|
49
|
+
menu keeps its native content unless you opt entries in under Settings → "Also
|
|
50
|
+
show in the "…" menu → workspace". On 0.2.1 the plugin draws the whole menu and
|
|
51
|
+
runs the official dialogs for Rename and Delete workspace.
|
|
47
52
|
|
|
48
53
|
| Item | Behavior |
|
|
49
54
|
| --- | --- |
|
|
50
55
|
| New session | Expands the workspace and starts a session (same as the row's `+`) |
|
|
51
56
|
| Rename (built-in) | Opens the official "Rename workspace" dialog; on 0.2.1 the plugin renders this item itself |
|
|
52
|
-
| Pinned open-with app | A pinned app shows directly at the top level (e.g. "Open with Visual Studio Code") and opens the workspace directory |
|
|
53
|
-
| Open with | A submenu of external apps (editors, terminals, file managers) sharing the session menu's catalog, pinning and toggles; hover expands it |
|
|
54
|
-
| Reveal in file manager (built-in) | Opens the workspace directory in Explorer; provided by the host on 0.2.2, absent on 0.2.1 |
|
|
55
|
-
| Copy workspace path | Copies the directory path |
|
|
56
57
|
| Open in new tab | Only when `dsh-project-tabs` (exposing `openWorkspaceInTab`) is installed |
|
|
58
|
+
| Show / hide archived sessions | Only on workspaces that have archived sessions; the label follows the current state. "Show archived sessions" switches the sidebar's global archived filter to all conversations and then hides every other workspace's archived rows (including "Ungrouped"), so only this workspace's archived sessions appear; "Hide archived sessions" puts them back. Per-workspace choices persist across reloads; changing the global filter under "View options" wins over them |
|
|
59
|
+
| Copy workspace path | Copies the directory path |
|
|
60
|
+
| Reveal in file manager (built-in) | Opens the workspace directory in Explorer; provided by the host on 0.2.2, absent on 0.2.1 |
|
|
61
|
+
| Pinned open-with app | A pinned app shows directly at the top level (e.g. "Open with Visual Studio Code") and opens the workspace directory |
|
|
62
|
+
| Open with | A submenu of external apps (editors, terminals, file managers) sharing the session menu's catalog, pinning and toggles; hover expands it. Host menu items draw no submenu caret, so the plugin renders the label as text plus a right-aligned chevron, matching the session menu; the 0.2.1 self-drawn menu uses an arrow icon |
|
|
57
63
|
| Delete workspace (built-in) | Opens the official delete confirmation; removes only the workspace registration and leaves its sessions under "Ungrouped". On 0.2.1 the plugin renders this item itself |
|
|
58
64
|
| Delete workspace and all sessions | Permanently deletes every session and transcript under the workspace first, then the workspace registration. If any session fails, the workspace is kept so the user can retry. The directory itself is not deleted |
|
|
59
65
|
|
|
@@ -93,12 +99,17 @@ button clears it at once. While the mark is live the entries render normally;
|
|
|
93
99
|
otherwise they render `null`.
|
|
94
100
|
|
|
95
101
|
To use them from the row's "…" button, enable them one by one under Settings →
|
|
96
|
-
"Row menu" → "Also show in the "…" menu"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
"Row menu" → "Also show in the "…" menu":
|
|
103
|
+
|
|
104
|
+
- session entries mount into the native "…" menu;
|
|
105
|
+
- workspace entries on 0.2.2: the host menu is data-driven, so an enabled entry skips
|
|
106
|
+
the gesture check and appears in the "…" menu; with everything off that menu keeps
|
|
107
|
+
only the native items;
|
|
108
|
+
- workspace entries on 0.2.1: enabling any of them makes this plugin take over the
|
|
109
|
+
row's "…" button and render the official "Rename / Delete workspace" items
|
|
110
|
+
alongside the enabled plugin entries.
|
|
111
|
+
|
|
112
|
+
With everything off, the "…" button behaves exactly like DSH's own.
|
|
102
113
|
|
|
103
114
|
Blank draft sessions have no right-click menu and are left alone. Rows that are
|
|
104
115
|
neither a workspace nor a session (e.g. "Show more") are not intercepted.
|
|
@@ -114,16 +125,17 @@ Settings → "Row menu" toggles every new entry:
|
|
|
114
125
|
| Delete session | Hides the single-session delete entry in session menus |
|
|
115
126
|
| Allow deleting unarchived sessions | Off by default; also shows "Delete session permanently" on unarchived rows. Requires the previous switch |
|
|
116
127
|
| Delete all archived sessions | Hides the bulk-delete entry |
|
|
117
|
-
|
|
|
128
|
+
| Show archived sessions | Hides the workspace "Show / hide archived sessions" entry (on by default; the entry itself only appears when that workspace has archived sessions) |
|
|
129
|
+
| Also show in the "…" menu → workspace | Adds workspace right-click entries one by one to the row's "…" menu: New session, Open with, Copy workspace path, Open in new tab, archived-session toggle, Delete workspace and all sessions. On 0.2.1 enabling any of them makes this plugin render the "…" menu |
|
|
118
130
|
| Also show in the "…" menu → session | Adds session right-click entries one by one to the native "…" menu: Open with, Delete session, Delete all archived sessions |
|
|
119
131
|
|
|
120
132
|
The "Also show in the "…" menu" switches are all off by default; until one is
|
|
121
133
|
enabled the "…" menu is exactly DSH's own.
|
|
122
134
|
|
|
123
|
-
On DSH 0.2.2 and later
|
|
124
|
-
|
|
125
|
-
"…" menu
|
|
126
|
-
|
|
135
|
+
On DSH 0.2.2 and later the "…" button and right-click share one host menu, yet the
|
|
136
|
+
plugin's entries still appear only on right-click; flipping a switch also puts that
|
|
137
|
+
entry into the "…" menu. On 0.2.1 workspace rows have no host menu to contribute to,
|
|
138
|
+
so the plugin takes over the "…" button and draws the menu itself.
|
|
127
139
|
|
|
128
140
|
Switches live in browser `localStorage` (`dsh.sidebar-row-menu.items.v1`,
|
|
129
141
|
`dsh.sidebar-row-menu.open-with.v1`, `dsh.sidebar-row-menu.open-with.pinned.v1` and
|
|
@@ -360,6 +372,17 @@ trigger (`memoizedProps.onContextMenu` is a function): if it does, the host open
|
|
|
360
372
|
menu at the pointer by itself and the plugin only drops a gesture mark, never
|
|
361
373
|
touching the event.
|
|
362
374
|
|
|
375
|
+
### Why archived sessions cannot use the global filter alone
|
|
376
|
+
|
|
377
|
+
DSH exposes one global archived-visibility filter (`archivedFilter`: hide archived /
|
|
378
|
+
all conversations / archived only) and no per-workspace equivalent. The plugin builds
|
|
379
|
+
the per-workspace layer on top of it: it drives the host view action to "all
|
|
380
|
+
conversations", then hides other workspaces' (and "Ungrouped") archived rows with an inline
|
|
381
|
+
`display:none`. The snapshot's `archivedSessionIds` tells it which rows are archived,
|
|
382
|
+
and a MutationObserver re-applies the hiding whenever React replaces the row nodes.
|
|
383
|
+
The plugin does not keep the global filter hostage: once the user picks a filter under
|
|
384
|
+
"View options", the per-workspace hidden set is cleared and the host's choice wins.
|
|
385
|
+
|
|
363
386
|
### Why session actions are not hardcoded
|
|
364
387
|
|
|
365
388
|
The native session menu is not a hardcoded button list: it is
|
|
@@ -416,6 +439,12 @@ re-dispatches is not a gesture.
|
|
|
416
439
|
Entries read the mark once while mounting, so a slow panel does not matter, and the
|
|
417
440
|
mark only lives until "the panel for this gesture appeared", never into the next one.
|
|
418
441
|
|
|
442
|
+
Workspace rows on 0.2.2 take a different path: the host menu is built from each
|
|
443
|
+
`ctx.contextMenus.register` provider's `resolve`, which has no React entry to read the
|
|
444
|
+
mark, so the provider checks the same mark while resolving. The "…" pointerdown
|
|
445
|
+
clears it before the host resolves, hence the entries show on right-click only; the
|
|
446
|
+
"Also show in the "…" menu → workspace" switches make the provider skip that check.
|
|
447
|
+
|
|
419
448
|
### Why the 0.2.1 menu is mounted imperatively
|
|
420
449
|
|
|
421
450
|
On 0.2.2 the host mounts every menu and the plugin creates no container. On 0.2.1 a
|
|
@@ -479,10 +508,13 @@ Built against DSH client `0.2.1-rc.5` and `0.2.2` (`ProjectRowItem`,
|
|
|
479
508
|
menus to a data-driven service, and the plugin picks the path by host capability:
|
|
480
509
|
|
|
481
510
|
- **0.2.2+**: workspace entries are registered with `ctx.contextMenus.register`
|
|
482
|
-
(New session 90,
|
|
483
|
-
Open
|
|
484
|
-
order with the native Rename (100), Reveal (200) and Delete workspace (300)
|
|
485
|
-
|
|
511
|
+
(New session 90, Open in new tab 110, Copy workspace path 150, pinned app 210,
|
|
512
|
+
Open with 220, Delete workspace and all sessions 310), interleaved by
|
|
513
|
+
order with the native Rename (100), Reveal (200) and Delete workspace (300) and
|
|
514
|
+
grouped with the same separators as the session menu.
|
|
515
|
+
Right-click and the "…" button share the host's single menu; providers answer only
|
|
516
|
+
during a right-click gesture (or when the matching "Also show in the "…" menu"
|
|
517
|
+
switch is on).
|
|
486
518
|
- **0.2.1**: no `contextMenus` service, so the plugin keeps drawing its own
|
|
487
519
|
workspace menu.
|
|
488
520
|
- **Session rows**: 0.2.2 still supports the
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
给 [DeepSeek Harness](https://github.com/deepseek-ai)(DSH)左侧边栏加右键菜单:
|
|
11
11
|
|
|
12
|
-
- **工作区行**:右键弹出菜单(DSH 0.2.2
|
|
12
|
+
- **工作区行**:右键弹出菜单(DSH 0.2.2 起并入宿主原生菜单,条目按 order 与内置项交错);
|
|
13
13
|
- **工作区下层的会话行**:右键在指针处展开该行自己的菜单,并追加本插件的条目。
|
|
14
14
|
|
|
15
15
|
DSH 0.2.2 把侧栏右键改成数据驱动:工作区菜单由宿主统一渲染,本插件按 order 把条目混排
|
|
@@ -22,7 +22,8 @@ DSH 0.2.2 把侧栏右键改成数据驱动:工作区菜单由宿主统一渲
|
|
|
22
22
|
`sidebar.workspaces.session.menu.item` slot —— 内置动作和其他插件的动作都在里面,
|
|
23
23
|
本插件只追加自己的条目,不复制、不硬编码任何内置动作。
|
|
24
24
|
|
|
25
|
-
>
|
|
25
|
+
> 点行尾「…」按钮看到的菜单保持 DSH 原样;本插件的条目默认只在右键展开时出现(0.2.2 上
|
|
26
|
+
> 工作区行与会话行都是如此,可以在设置里逐项开启)。
|
|
26
27
|
> 0.2.2 上右键与「…」共用同一份宿主菜单,插件按“右键手势”区分:右键时打标,等菜单面板
|
|
27
28
|
> 真正挂载后才收尾;点「…」的按下事件会立刻清标,所以那个菜单不带右键条目。
|
|
28
29
|
> 右键展开的菜单不会因为鼠标移出而关闭,只有点击外部、选中条目或按 Esc 才关闭。
|
|
@@ -32,19 +33,22 @@ DSH 0.2.2 把侧栏右键改成数据驱动:工作区菜单由宿主统一渲
|
|
|
32
33
|
### 工作区行右键
|
|
33
34
|
|
|
34
35
|
0.2.2 上这份菜单由宿主渲染,本插件条目与内置「重命名」(100)、「在文件夹显示」(200)、
|
|
35
|
-
「删除工作区」(300)按 order
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
「删除工作区」(300)按 order 交错,分组规则与会话菜单一致:新会话 90、在新标签打开 110、
|
|
37
|
+
归档会话开关 130、复制工作区路径 150、置顶应用 210、打开方式 220、删除工作区及所有会话 310。这些条目默认
|
|
38
|
+
只出现在右键菜单;行尾「…」里的菜单保持原生,想让它也带上插件条目,可在设置里逐项开启
|
|
39
|
+
「同时放进「…」菜单 → 工作区」。0.2.1 上整份菜单由本插件自绘,「重命名」「删除工作区」
|
|
40
|
+
由插件调用官方对话框。
|
|
38
41
|
|
|
39
42
|
| 菜单项 | 行为 |
|
|
40
43
|
| --- | --- |
|
|
41
44
|
| 新会话 | 展开该工作区并新建会话(等价于行尾 `+`) |
|
|
42
45
|
| 重命名(内置) | 打开官方“重命名工作区”对话框;0.2.1 上由本插件代为渲染 |
|
|
43
|
-
| 置顶的打开方式 | 被置顶的应用直接显示在一级菜单(例如「用 Visual Studio Code 打开」),点击后用该应用打开工作区目录 |
|
|
44
|
-
| 打开方式 | 二级菜单列出宿主提供的外部应用(编辑器、终端、文件管理器);与会话菜单共用同一套应用列表、置顶与开关设置,鼠标悬浮自动展开 |
|
|
45
|
-
| 在文件夹显示(内置) | 在资源管理器里打开工作区目录;0.2.2 由宿主提供,0.2.1 上不出现 |
|
|
46
|
-
| 复制工作区路径 | 复制目录路径到剪贴板 |
|
|
47
46
|
| 在新标签中打开 | 仅在装有 `dsh-project-tabs`(暴露 `openWorkspaceInTab`)时出现 |
|
|
47
|
+
| 显示 / 隐藏已归档会话 | 只在该工作区有已归档会话时出现,文案随当前状态切换。点击「显示已归档会话」会把侧边栏的全局归档筛选切到「全部对话」,再藏起其他工作区(含「未分组」)的归档行,于是只有这个工作区的已归档会话露出来;点击「隐藏已归档会话」把它重新收回。逐工作区的选择存在本地、刷新后保持;在「视图选项」里改全局筛选则以全局选择为准 |
|
|
48
|
+
| 复制工作区路径 | 复制目录路径到剪贴板 |
|
|
49
|
+
| 在文件夹显示(内置) | 在资源管理器里打开工作区目录;0.2.2 由宿主提供,0.2.1 上不出现 |
|
|
50
|
+
| 置顶的打开方式 | 被置顶的应用直接显示在一级菜单(例如「用 Visual Studio Code 打开」),点击后用该应用打开工作区目录 |
|
|
51
|
+
| 打开方式 | 二级菜单列出宿主提供的外部应用(编辑器、终端、文件管理器);与会话菜单共用同一套应用列表、置顶与开关设置,鼠标悬浮自动展开。宿主的菜单项不画二级箭头,本插件把标签渲染成「文字 + 右对齐箭头」,与会话菜单同一位置;0.2.1 自绘菜单画的是箭头图标 |
|
|
48
52
|
| 删除工作区(内置) | 打开官方删除确认对话框;只删除工作区登记,会话保留并归入「未分组」。0.2.1 上由本插件代为渲染 |
|
|
49
53
|
| 删除工作区及所有会话 | 先永久删除该工作区下的每个会话及其日志,全部成功后再删除工作区登记;任一会话删除失败则保留工作区,便于重试。工作区目录本身不删除 |
|
|
50
54
|
|
|
@@ -78,9 +82,12 @@ DSH 0.2.2 把侧栏右键改成数据驱动:工作区菜单由宿主统一渲
|
|
|
78
82
|
「…」的按下事件会立刻清标。标记有效期内的条目正常渲染,其余情况渲染直接返回 `null`。
|
|
79
83
|
|
|
80
84
|
如果想直接使用行尾「…」菜单,可以在设置 →「行菜单」→「同时放进「…」菜单」里逐个开启:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
|
|
86
|
+
- 会话条目:开启后挂进原生「…」菜单;
|
|
87
|
+
- 工作区条目(0.2.2):宿主菜单本身是数据驱动的,开启后 provider 解析时不再看手势,
|
|
88
|
+
条目直接进入「…」菜单;全部关闭时「…」里只有原生项;
|
|
89
|
+
- 工作区条目(0.2.1):开启任意一项后,本插件接管行尾「…」按钮,用同一套菜单展示
|
|
90
|
+
官方「重命名 / 删除工作区」加上已开启的插件条目。
|
|
84
91
|
|
|
85
92
|
草稿会话(还没有任何消息的空白会话)没有右键菜单,本插件同样不接管。
|
|
86
93
|
非工作区 / 非会话的侧边栏行(如“显示更多”)也不会被拦截。
|
|
@@ -96,13 +103,15 @@ DSH 0.2.2 把侧栏右键改成数据驱动:工作区菜单由宿主统一渲
|
|
|
96
103
|
| 永久删除会话 | 关闭后会话菜单里不再出现单条删除入口 |
|
|
97
104
|
| 允许删除未归档会话 | 默认关闭;开启后未归档会话的右键菜单也会出现「永久删除会话」,需先开启上一项 |
|
|
98
105
|
| 删除所有已归档会话 | 关闭后不再出现批量删除入口 |
|
|
99
|
-
|
|
|
106
|
+
| 显示已归档会话 | 关闭后工作区菜单里不再出现「显示 / 隐藏已归档会话」(默认开启;该项只在当前工作区存在已归档会话时出现) |
|
|
107
|
+
| 同时放进「…」菜单 → 工作区 | 逐项把工作区行右键条目也放进行尾「…」菜单:新会话、打开方式、复制工作区路径、在新标签中打开、归档会话开关、删除工作区及所有会话。0.2.1 上开启任意一项后「…」按钮改由本插件渲染 |
|
|
100
108
|
| 同时放进「…」菜单 → 会话 | 逐项把会话行右键条目加进原生「…」菜单:打开方式、永久删除会话、删除所有已归档会话 |
|
|
101
109
|
|
|
102
110
|
「同时放进「…」菜单」下的开关默认全部关闭,`…` 菜单在未开启时与 DSH 完全一致。
|
|
103
111
|
|
|
104
|
-
DSH 0.2.2
|
|
105
|
-
|
|
112
|
+
DSH 0.2.2 起行尾「…」与右键共用同一份宿主菜单,但插件条目仍按手势只出现在右键菜单里;
|
|
113
|
+
上表的开关打开后,对应条目才会同时进入「…」菜单。0.2.1 上工作区行没有宿主菜单可贡献,
|
|
114
|
+
改由插件接管「…」按钮自绘。
|
|
106
115
|
|
|
107
116
|
开关存在浏览器 `localStorage`(`dsh.sidebar-row-menu.items.v1`、
|
|
108
117
|
`dsh.sidebar-row-menu.open-with.v1`、`dsh.sidebar-row-menu.open-with.pinned.v1` 与
|
|
@@ -314,6 +323,14 @@ test/*.test.mjs node:test
|
|
|
314
323
|
这一行是否天生带宿主右键触发器(`memoizedProps.onContextMenu` 是函数):有就说明宿主会把
|
|
315
324
|
菜单开在指针处,插件只补一个手势标记,不再干预事件。
|
|
316
325
|
|
|
326
|
+
### 归档会话为什么不是纯全局筛选
|
|
327
|
+
|
|
328
|
+
DSH 的归档可见性只有一个全局筛选(`archivedFilter`:隐藏已归档 / 全部对话 / 仅显示已归档),
|
|
329
|
+
没有逐工作区的概念。插件把「逐工作区」做成两层:先通过宿主视图动作把全局切到「全部对话」,
|
|
330
|
+
再把其他工作区(含「未分组」)的归档行用行内 `display:none` 藏起来;快照里的 `archivedSessionIds` 决定哪些
|
|
331
|
+
行是归档行,React 重渲染替换掉节点后由 MutationObserver 重新隐藏。插件不长期改写全局筛选:
|
|
332
|
+
用户在「视图选项」里改过之后,之前逐工作区的隐藏会被清空,完全跟随宿主的选择。
|
|
333
|
+
|
|
317
334
|
### 会话行为什么不硬编码
|
|
318
335
|
|
|
319
336
|
内置会话菜单不是硬编码的按钮列表,而是
|
|
@@ -360,6 +377,11 @@ test/*.test.mjs node:test
|
|
|
360
377
|
条目在挂载时读一次标记,所以面板挂得晚也不影响;标记只活到“这次手势对应的面板出现”,
|
|
361
378
|
不会漏进下一次。
|
|
362
379
|
|
|
380
|
+
工作区行在 0.2.2 上走的是另一条路:宿主菜单的数据来自 `ctx.contextMenus.register` 的
|
|
381
|
+
`resolve`,没有 React 条目可以读标记,所以 provider 在解析时直接判断同一枚手势标记,
|
|
382
|
+
并且行尾「…」的 `pointerdown` 清标发生在宿主解析之前 —— 右键时条目进菜单,点「…」时
|
|
383
|
+
不进。「同时放进「…」菜单 → 工作区」的开关则让 provider 跳过手势判断,条目不依赖右键。
|
|
384
|
+
|
|
363
385
|
### 为什么 0.2.1 的菜单是命令式挂载
|
|
364
386
|
|
|
365
387
|
0.2.2 的菜单全部由宿主挂载,插件不创建任何容器。0.2.1 上右键事件不是 React 渲染流的一部分,
|
|
@@ -411,10 +433,11 @@ DSH 宿主按该范围拒绝安装不兼容的宿主,插件市场也按它显
|
|
|
411
433
|
与 `contextMenus` 实现适配。DSH 0.2.2 起把侧栏右键菜单改成数据驱动,本包按宿主能力自动选路:
|
|
412
434
|
|
|
413
435
|
- **0.2.2 及以上**:工作区行改用 `ctx.contextMenus.register` 贡献动作
|
|
414
|
-
(新会话 90、置顶应用
|
|
415
|
-
与原生「重命名」「在文件夹显示」「删除工作区」按 order
|
|
416
|
-
|
|
417
|
-
|
|
436
|
+
(新会话 90、新标签 110、复制路径 150、置顶应用 210、打开方式 220、删除工作区及所有会话 310),
|
|
437
|
+
与原生「重命名」「在文件夹显示」「删除工作区」按 order 交错排列;分组分隔线与会话菜单同一
|
|
438
|
+
套规则。右键与「…」共用同一份宿主菜单,插件不再自绘,也不再接管「…」按钮。provider 解析时
|
|
439
|
+
按右键手势(或设置里「同时放进「…」菜单」的开关)决定是否返回条目。会话行由宿主自己把原生
|
|
440
|
+
菜单开在指针处,本包条目继续从 `sidebar.workspaces.session.menu.item` 渲染;
|
|
418
441
|
- **0.2.1**:没有 `contextMenus` 服务,保持原来的自绘工作区菜单;会话右键仍由插件把
|
|
419
442
|
行尾「…」菜单搬到指针。
|
|
420
443
|
|
package/lib/client.js
CHANGED
|
@@ -69,6 +69,8 @@ window.__ModuleLoader__.load({
|
|
|
69
69
|
IconWarningOutlineRegular,
|
|
70
70
|
IconCopyOutlineRegular,
|
|
71
71
|
IconChevronRightOutlineRegular,
|
|
72
|
+
IconArchiveOutlineRegular,
|
|
73
|
+
IconArchiveOffOutlineRegular,
|
|
72
74
|
IconRightUpOutlineRegular,
|
|
73
75
|
IconTriangleRightFillRegular,
|
|
74
76
|
} = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
@@ -112,6 +114,7 @@ window.__ModuleLoader__.load({
|
|
|
112
114
|
*/
|
|
113
115
|
const ITEM_DEFAULTS = {
|
|
114
116
|
openWith: true,
|
|
117
|
+
archivedSessions: true,
|
|
115
118
|
deleteSession: true,
|
|
116
119
|
deleteArchived: true,
|
|
117
120
|
deleteUnarchived: false,
|
|
@@ -122,8 +125,16 @@ window.__ModuleLoader__.load({
|
|
|
122
125
|
* 全部默认关闭:不勾选时点「…」看到的仍是 DSH 原生菜单。
|
|
123
126
|
*/
|
|
124
127
|
const ELLIPSIS_KEY = 'dsh.sidebar-row-menu.ellipsis.v1';
|
|
128
|
+
/**
|
|
129
|
+
* 按工作区「已隐藏归档会话」的集合。宿主只有全局归档筛选(default/show/only),
|
|
130
|
+
* 没有「这个工作区显示归档、那个工作区不显示」的概念,所以这里记住要单独
|
|
131
|
+
* 隐藏归档行的工作区,再把全局筛选开到 show,最后在 DOM 里把其余工作区的
|
|
132
|
+
* 归档行藏起来。
|
|
133
|
+
*/
|
|
134
|
+
const ARCHIVED_HIDDEN_KEY = 'dsh.sidebar-row-menu.archived-hidden.v1';
|
|
125
135
|
const ELLIPSIS_DEFAULTS = {
|
|
126
136
|
newSession: false,
|
|
137
|
+
showArchived: false,
|
|
127
138
|
openWith: false,
|
|
128
139
|
copyPath: false,
|
|
129
140
|
openInTab: false,
|
|
@@ -154,6 +165,8 @@ window.__ModuleLoader__.load({
|
|
|
154
165
|
openInTab: '在新标签中打开',
|
|
155
166
|
deleteWorkspace: '删除工作区',
|
|
156
167
|
deleteWorkspaceSessions: '删除工作区及所有会话',
|
|
168
|
+
showArchivedSessions: '显示已归档会话',
|
|
169
|
+
hideArchivedSessions: '隐藏已归档会话',
|
|
157
170
|
copied: '已复制',
|
|
158
171
|
copyFailed: '复制失败,请重试',
|
|
159
172
|
noPath: '没有可用的目录路径',
|
|
@@ -166,6 +179,8 @@ window.__ModuleLoader__.load({
|
|
|
166
179
|
'settings.title': '行菜单',
|
|
167
180
|
'settings.openWith.label': '打开方式',
|
|
168
181
|
'settings.openWith.hint': '在子菜单里选择编辑器、终端或文件管理器打开工作区或会话目录;置顶的应用直接显示在菜单一级。',
|
|
182
|
+
'settings.archivedSessions.label': '显示已归档会话',
|
|
183
|
+
'settings.archivedSessions.hint': '在工作区右键菜单里单独显示或隐藏该工作区内所有已归档会话,不影响其他工作区。',
|
|
169
184
|
'settings.deleteSession.label': '永久删除会话',
|
|
170
185
|
'settings.deleteSession.hint': '在会话菜单里永久删除这一个会话及其日志;默认只对已归档会话显示。',
|
|
171
186
|
'settings.deleteArchived.label': '删除所有已归档会话',
|
|
@@ -186,12 +201,13 @@ window.__ModuleLoader__.load({
|
|
|
186
201
|
'settings.expand': '展开应用列表',
|
|
187
202
|
'settings.collapse': '收起应用列表',
|
|
188
203
|
'settings.ellipsisTitle': '同时放进「…」菜单',
|
|
189
|
-
'settings.ellipsisHint': '
|
|
190
|
-
'settings.ellipsisHintNative': '当前 DSH 版本的「…」按钮与右键共用同一份菜单,工作区行无需单独登记;这里只保留会话行的开关。',
|
|
204
|
+
'settings.ellipsisHint': 'DSH 0.2.2 起「…」按钮与右键共用同一份菜单:勾选后对应条目也会出现在「…」菜单里,并沿用上面的单项开关;不勾选则只在右键菜单出现。',
|
|
191
205
|
'settings.ellipsisWorkspace': '工作区行',
|
|
192
206
|
'settings.ellipsisSession': '会话行',
|
|
193
207
|
'settings.ellipsis.newSession.label': '新建会话',
|
|
194
208
|
'settings.ellipsis.newSession.hint': '在「…」菜单里直接新建该工作区的会话。',
|
|
209
|
+
'settings.ellipsis.showArchived.label': '显示已归档会话',
|
|
210
|
+
'settings.ellipsis.showArchived.hint': '在「…」菜单里单独显示或隐藏该工作区的已归档会话。',
|
|
195
211
|
'settings.ellipsis.openWith.label': '打开方式',
|
|
196
212
|
'settings.ellipsis.openWith.hint': '在「…」菜单里用外部应用打开工作区或会话目录。',
|
|
197
213
|
'settings.ellipsis.copyPath.label': '拷贝工作区路径',
|
|
@@ -239,6 +255,8 @@ window.__ModuleLoader__.load({
|
|
|
239
255
|
openInTab: 'Open in new tab',
|
|
240
256
|
deleteWorkspace: 'Delete workspace',
|
|
241
257
|
deleteWorkspaceSessions: 'Delete workspace and all sessions',
|
|
258
|
+
showArchivedSessions: 'Show archived sessions',
|
|
259
|
+
hideArchivedSessions: 'Hide archived sessions',
|
|
242
260
|
copied: 'Copied',
|
|
243
261
|
copyFailed: 'Copy failed, please retry',
|
|
244
262
|
noPath: 'No usable directory path',
|
|
@@ -251,6 +269,8 @@ window.__ModuleLoader__.load({
|
|
|
251
269
|
'settings.title': 'Row menu',
|
|
252
270
|
'settings.openWith.label': 'Open with',
|
|
253
271
|
'settings.openWith.hint': 'Pick an editor, terminal or file manager for workspace or session directories; pinned apps show up directly in the menu.',
|
|
272
|
+
'settings.archivedSessions.label': 'Show archived sessions',
|
|
273
|
+
'settings.archivedSessions.hint': 'Show or hide this workspace\'s archived sessions from its context menu, without affecting other workspaces.',
|
|
254
274
|
'settings.deleteSession.label': 'Delete session permanently',
|
|
255
275
|
'settings.deleteSession.hint': 'Permanently delete one session and its transcript from its menu; shown on archived sessions by default.',
|
|
256
276
|
'settings.deleteArchived.label': 'Delete all archived sessions',
|
|
@@ -271,12 +291,13 @@ window.__ModuleLoader__.load({
|
|
|
271
291
|
'settings.expand': 'Show applications',
|
|
272
292
|
'settings.collapse': 'Hide applications',
|
|
273
293
|
'settings.ellipsisTitle': 'Also show in the "…" menu',
|
|
274
|
-
'settings.ellipsisHint': '
|
|
275
|
-
'settings.ellipsisHintNative': 'In this DSH version the "…" button opens the same menu as right-click, so workspace rows need no separate entries; only the session row switches below apply.',
|
|
294
|
+
'settings.ellipsisHint': 'Since DSH 0.2.2 the "…" button opens the same menu as right-click: checked entries also appear there, still subject to the switches above. Unchecked entries stay right-click only.',
|
|
276
295
|
'settings.ellipsisWorkspace': 'Workspace rows',
|
|
277
296
|
'settings.ellipsisSession': 'Session rows',
|
|
278
297
|
'settings.ellipsis.newSession.label': 'New session',
|
|
279
298
|
'settings.ellipsis.newSession.hint': 'Create a session for this workspace from the "…" menu.',
|
|
299
|
+
'settings.ellipsis.showArchived.label': 'Show archived sessions',
|
|
300
|
+
'settings.ellipsis.showArchived.hint': 'Show or hide this workspace\'s archived sessions from the "…" menu.',
|
|
280
301
|
'settings.ellipsis.openWith.label': 'Open with',
|
|
281
302
|
'settings.ellipsis.openWith.hint': 'Open the workspace or session directory with an external app from the "…" menu.',
|
|
282
303
|
'settings.ellipsis.copyPath.label': 'Copy workspace path',
|
|
@@ -1479,6 +1500,7 @@ window.__ModuleLoader__.load({
|
|
|
1479
1500
|
['deleteSession', 'settings.deleteSession.label', 'settings.deleteSession.hint'],
|
|
1480
1501
|
['deleteUnarchived', 'settings.deleteUnarchived.label', 'settings.deleteUnarchived.hint'],
|
|
1481
1502
|
['deleteArchived', 'settings.deleteArchived.label', 'settings.deleteArchived.hint'],
|
|
1503
|
+
['archivedSessions', 'settings.archivedSessions.label', 'settings.archivedSessions.hint'],
|
|
1482
1504
|
];
|
|
1483
1505
|
|
|
1484
1506
|
/** 「…」菜单附加开关;按行分组,避免用户分不清工作区项和会话项。 */
|
|
@@ -1488,6 +1510,7 @@ window.__ModuleLoader__.load({
|
|
|
1488
1510
|
['openWith', 'settings.ellipsis.openWith.label', 'settings.ellipsis.openWith.hint'],
|
|
1489
1511
|
['copyPath', 'settings.ellipsis.copyPath.label', 'settings.ellipsis.copyPath.hint'],
|
|
1490
1512
|
['openInTab', 'settings.ellipsis.openInTab.label', 'settings.ellipsis.openInTab.hint'],
|
|
1513
|
+
['showArchived', 'settings.ellipsis.showArchived.label', 'settings.ellipsis.showArchived.hint'],
|
|
1491
1514
|
['deleteSessions', 'settings.ellipsis.deleteSessions.label', 'settings.ellipsis.deleteSessions.hint'],
|
|
1492
1515
|
]],
|
|
1493
1516
|
['session', 'settings.ellipsisSession', [
|
|
@@ -1503,11 +1526,6 @@ window.__ModuleLoader__.load({
|
|
|
1503
1526
|
const children = useChildren();
|
|
1504
1527
|
const apps = useApps();
|
|
1505
1528
|
usePinned();
|
|
1506
|
-
// 0.2.2 起行尾「…」与右键共用同一份原生菜单,工作区行的附加开关不再生效。
|
|
1507
|
-
const nativeEllipsis = contextMenusOf(rootCtx) !== null;
|
|
1508
|
-
const ellipsisRows = nativeEllipsis
|
|
1509
|
-
? ELLIPSIS_ROWS.filter(([groupKey]) => groupKey !== 'workspace')
|
|
1510
|
-
: ELLIPSIS_ROWS;
|
|
1511
1529
|
const [expanded, setExpanded] = useState(false);
|
|
1512
1530
|
const anyConflict = takenOver('openWith') || takenOver('deleteSession') || takenOver('deleteArchived');
|
|
1513
1531
|
const locked = items.openWith === false;
|
|
@@ -1587,7 +1605,7 @@ window.__ModuleLoader__.load({
|
|
|
1587
1605
|
return key === 'openWith' && childRows ? [row, childRows] : [row];
|
|
1588
1606
|
});
|
|
1589
1607
|
|
|
1590
|
-
const ellipsisGroups =
|
|
1608
|
+
const ellipsisGroups = ELLIPSIS_ROWS.map(([groupKey, titleKey, rows]) => h('div', {
|
|
1591
1609
|
className: 'dsrm-settings-group',
|
|
1592
1610
|
key: groupKey,
|
|
1593
1611
|
},
|
|
@@ -1615,7 +1633,7 @@ window.__ModuleLoader__.load({
|
|
|
1615
1633
|
anyConflict ? h('div', { className: 'dsrm-settings-note' }, t('settings.conflictNote')) : null,
|
|
1616
1634
|
h('div', { className: 'dsrm-settings-list' }, rows),
|
|
1617
1635
|
h('h2', { className: 'dsrm-settings-title dsrm-settings-subtitle' }, t('settings.ellipsisTitle')),
|
|
1618
|
-
h('div', { className: 'dsrm-settings-note' }, t(
|
|
1636
|
+
h('div', { className: 'dsrm-settings-note' }, t('settings.ellipsisHint')),
|
|
1619
1637
|
h('div', { className: 'dsrm-settings-groups' }, ellipsisGroups),
|
|
1620
1638
|
);
|
|
1621
1639
|
}
|
|
@@ -1751,7 +1769,9 @@ window.__ModuleLoader__.load({
|
|
|
1751
1769
|
return;
|
|
1752
1770
|
}
|
|
1753
1771
|
copyText(cwd, t);
|
|
1772
|
+
return;
|
|
1754
1773
|
}
|
|
1774
|
+
if (id === 'archived') toggleWorkspaceArchived(workspaceId);
|
|
1755
1775
|
};
|
|
1756
1776
|
|
|
1757
1777
|
const rows = [];
|
|
@@ -1820,6 +1840,19 @@ window.__ModuleLoader__.load({
|
|
|
1820
1840
|
children: t('openInTab'),
|
|
1821
1841
|
}));
|
|
1822
1842
|
}
|
|
1843
|
+
const archivedCount = items.archivedSessions === false ? 0 : archivedCountOfWorkspace(workspaceId);
|
|
1844
|
+
if (archivedCount > 0 && show('showArchived')) {
|
|
1845
|
+
const archivedVisible = archivedRowsVisibleIn(workspaceId);
|
|
1846
|
+
rows.push(jsx(MenuItemButton, {
|
|
1847
|
+
key: 'archived',
|
|
1848
|
+
onSelect: () => {
|
|
1849
|
+
onClose();
|
|
1850
|
+
run('archived');
|
|
1851
|
+
},
|
|
1852
|
+
icon: jsx(archivedVisible ? IconArchiveOffOutlineRegular : IconArchiveOutlineRegular, { size: 14 }),
|
|
1853
|
+
children: t(archivedVisible ? 'hideArchivedSessions' : 'showArchivedSessions'),
|
|
1854
|
+
}));
|
|
1855
|
+
}
|
|
1823
1856
|
rows.push(jsx(MenuItemButton, {
|
|
1824
1857
|
key: 'delete',
|
|
1825
1858
|
danger: true,
|
|
@@ -1895,6 +1928,9 @@ window.__ModuleLoader__.load({
|
|
|
1895
1928
|
if (event.defaultPrevented || event.button !== 0) return;
|
|
1896
1929
|
if (event.target === null || event.target === undefined) return;
|
|
1897
1930
|
if (rootCtx === null || !workspaceEllipsisOn()) return;
|
|
1931
|
+
// 0.2.2 起宿主菜单本身就是数据驱动的:附加条目由 provider 按开关加进去,
|
|
1932
|
+
// 再接管「…」只会把原生菜单换成自绘的那一份。
|
|
1933
|
+
if (contextMenusOf(rootCtx) !== null) return;
|
|
1898
1934
|
const target = event.target;
|
|
1899
1935
|
if (typeof target.closest !== 'function') return;
|
|
1900
1936
|
const row = target.closest('[data-row-key]');
|
|
@@ -2069,6 +2105,275 @@ window.__ModuleLoader__.load({
|
|
|
2069
2105
|
});
|
|
2070
2106
|
}
|
|
2071
2107
|
|
|
2108
|
+
/* --------------------- 工作区归档会话:按工作区单独显示/隐藏 */
|
|
2109
|
+
|
|
2110
|
+
function readHiddenWorkspaces() {
|
|
2111
|
+
const hidden = new Set();
|
|
2112
|
+
try {
|
|
2113
|
+
const raw = globalThis.localStorage?.getItem(ARCHIVED_HIDDEN_KEY);
|
|
2114
|
+
if (typeof raw !== 'string' || raw === '') return hidden;
|
|
2115
|
+
const parsed = JSON.parse(raw);
|
|
2116
|
+
if (!Array.isArray(parsed)) return hidden;
|
|
2117
|
+
for (const id of parsed) {
|
|
2118
|
+
if (typeof id === 'string' && id !== '') hidden.add(id);
|
|
2119
|
+
}
|
|
2120
|
+
} catch {
|
|
2121
|
+
/* 坏数据按空集合处理 */
|
|
2122
|
+
}
|
|
2123
|
+
return hidden;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
let hiddenArchivedWorkspaces = readHiddenWorkspaces();
|
|
2127
|
+
|
|
2128
|
+
function persistHiddenWorkspaces() {
|
|
2129
|
+
try {
|
|
2130
|
+
globalThis.localStorage?.setItem(ARCHIVED_HIDDEN_KEY, JSON.stringify([...hiddenArchivedWorkspaces]));
|
|
2131
|
+
} catch {
|
|
2132
|
+
/* 隐私模式写不进去也照样生效到本次会话 */
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* 读宿主工作区视图的归档筛选。插件没有公开 API 订阅宿主视图 store,但视图
|
|
2138
|
+
* 状态会持久化到 localStorage 的 `dsh.workspace.view.vN`(筛选一变就写),
|
|
2139
|
+
* 所以读这里就能知道用户是否在「视图选项」里改过全局筛选。
|
|
2140
|
+
*/
|
|
2141
|
+
function readViewFilter() {
|
|
2142
|
+
try {
|
|
2143
|
+
const storage = globalThis.localStorage;
|
|
2144
|
+
if (storage === null || storage === undefined || typeof storage.key !== 'function') return 'default';
|
|
2145
|
+
const keys = [];
|
|
2146
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
2147
|
+
const key = storage.key(index);
|
|
2148
|
+
if (typeof key === 'string' && key.startsWith('dsh.workspace.view.')) keys.push(key);
|
|
2149
|
+
}
|
|
2150
|
+
// 版本号是数字后缀,v10 必须排在 v5 前面。
|
|
2151
|
+
const versionOf = (key) => {
|
|
2152
|
+
const match = /\.v(\d+)$/.exec(key);
|
|
2153
|
+
return match === null ? -1 : Number(match[1]);
|
|
2154
|
+
};
|
|
2155
|
+
keys.sort((left, right) => versionOf(right) - versionOf(left));
|
|
2156
|
+
for (const key of keys) {
|
|
2157
|
+
const parsed = JSON.parse(storage.getItem(key) ?? '');
|
|
2158
|
+
if (parsed !== null && typeof parsed === 'object' && typeof parsed.archivedFilter === 'string') {
|
|
2159
|
+
return parsed.archivedFilter;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
} catch {
|
|
2163
|
+
/* 视图状态不可读时按默认筛选处理 */
|
|
2164
|
+
}
|
|
2165
|
+
return 'default';
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
/**
|
|
2169
|
+
* 宿主把 view actions 挂在 WorkspaceBrowser 组件的 props 上;插件没有服务可以
|
|
2170
|
+
* 拿到它,只能从行的 __reactFiber$ 往上找。找不到时静默放弃(筛选仍可手动切)。
|
|
2171
|
+
*/
|
|
2172
|
+
function findArchivedViewActions() {
|
|
2173
|
+
if (typeof document === 'undefined' || typeof document.querySelector !== 'function') return null;
|
|
2174
|
+
const row = document.querySelector('[data-row-key^="session:"], [data-row-key^="workspace:"]');
|
|
2175
|
+
if (row === null || row === undefined) return null;
|
|
2176
|
+
const field = Object.keys(row).find((name) => name.startsWith('__reactFiber$'));
|
|
2177
|
+
let fiber = field === undefined ? null : row[field];
|
|
2178
|
+
for (let depth = 0; depth < 48 && fiber !== null && fiber !== undefined; depth += 1) {
|
|
2179
|
+
const actions = fiber.memoizedProps?.actions;
|
|
2180
|
+
if (actions !== null && typeof actions === 'object' && typeof actions.setArchivedFilter === 'function') {
|
|
2181
|
+
return actions;
|
|
2182
|
+
}
|
|
2183
|
+
fiber = fiber.return;
|
|
2184
|
+
}
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
let lastViewFilter = readViewFilter();
|
|
2189
|
+
|
|
2190
|
+
function writeViewFilter(filter) {
|
|
2191
|
+
const actions = findArchivedViewActions();
|
|
2192
|
+
if (actions === null) return false;
|
|
2193
|
+
try {
|
|
2194
|
+
actions.setArchivedFilter(filter);
|
|
2195
|
+
lastViewFilter = filter;
|
|
2196
|
+
return true;
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
console.warn('[dsh-sidebar-row-menu] archived filter change failed:', error);
|
|
2199
|
+
return false;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/** 当前快照里的归档会话集合,以及每个会话所属工作区(未归组的会话没有)。 */
|
|
2204
|
+
function archivedWorkspaceIndex() {
|
|
2205
|
+
const snapshot = serviceOf(rootCtx, 'workspaces')?.list?.getSnapshot?.();
|
|
2206
|
+
const archived = new Set(Array.isArray(snapshot?.archivedSessionIds) ? snapshot.archivedSessionIds : []);
|
|
2207
|
+
const owner = new Map();
|
|
2208
|
+
for (const item of Array.isArray(snapshot?.items) ? snapshot.items : []) {
|
|
2209
|
+
const workspaceId = typeof item?.workspaceId === 'string' ? item.workspaceId : '';
|
|
2210
|
+
if (workspaceId === '' || !Array.isArray(item?.sessionIds)) continue;
|
|
2211
|
+
for (const sessionId of item.sessionIds) {
|
|
2212
|
+
if (typeof sessionId === 'string' && sessionId !== '') owner.set(sessionId, workspaceId);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return { archived, owner };
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
function archivedCountOfWorkspace(workspaceId) {
|
|
2219
|
+
const { archived } = archivedWorkspaceIndex();
|
|
2220
|
+
let count = 0;
|
|
2221
|
+
for (const sessionId of workspaceSessionIds(workspaceId)) {
|
|
2222
|
+
if (archived.has(sessionId)) count += 1;
|
|
2223
|
+
}
|
|
2224
|
+
return count;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
/**
|
|
2228
|
+
* 归档会话的归属桶:工作区 id,未归组的会话算在 UNGROUPED_OWNER 这个桶里。
|
|
2229
|
+
* 逐工作区显示时未归组的归档也要一起藏起来,否则它们会跟着全局 show 漏出来。
|
|
2230
|
+
*/
|
|
2231
|
+
const UNGROUPED_OWNER = '';
|
|
2232
|
+
|
|
2233
|
+
function archivedHolders() {
|
|
2234
|
+
const { archived, owner } = archivedWorkspaceIndex();
|
|
2235
|
+
const holders = new Set();
|
|
2236
|
+
for (const sessionId of archived) holders.add(owner.get(sessionId) ?? UNGROUPED_OWNER);
|
|
2237
|
+
return holders;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
function archivedRowsVisibleIn(workspaceId) {
|
|
2241
|
+
return readViewFilter() !== 'default' && !hiddenArchivedWorkspaces.has(workspaceId);
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/**
|
|
2245
|
+
* 逐工作区开关:宿主只有全局筛选,所以「显示某工作区」= 全局切到 show,
|
|
2246
|
+
* 再把其他所有归档归属桶(含未归组)记进隐藏集合;「隐藏」= 记进隐藏集合,
|
|
2247
|
+
* 全部藏完后把全局收回 default,侧边栏恢复原样。
|
|
2248
|
+
*/
|
|
2249
|
+
function toggleWorkspaceArchived(workspaceId) {
|
|
2250
|
+
if (workspaceId === '') return;
|
|
2251
|
+
const filter = readViewFilter();
|
|
2252
|
+
const visible = filter !== 'default' && !hiddenArchivedWorkspaces.has(workspaceId);
|
|
2253
|
+
if (visible) {
|
|
2254
|
+
hiddenArchivedWorkspaces.add(workspaceId);
|
|
2255
|
+
} else {
|
|
2256
|
+
hiddenArchivedWorkspaces.delete(workspaceId);
|
|
2257
|
+
// 从「隐藏已归档」进入逐工作区显示:只留这一个工作区,其他桶全部藏掉。
|
|
2258
|
+
if (filter === 'default') {
|
|
2259
|
+
for (const holder of archivedHolders()) {
|
|
2260
|
+
if (holder !== workspaceId) hiddenArchivedWorkspaces.add(holder);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
// 清掉已经没有归档会话的桶,避免隐藏集合里堆着永远不会再出现的 id。
|
|
2265
|
+
const holders = archivedHolders();
|
|
2266
|
+
for (const id of [...hiddenArchivedWorkspaces]) {
|
|
2267
|
+
if (!holders.has(id)) hiddenArchivedWorkspaces.delete(id);
|
|
2268
|
+
}
|
|
2269
|
+
persistHiddenWorkspaces();
|
|
2270
|
+
if (!visible) {
|
|
2271
|
+
if (filter === 'default') writeViewFilter('show');
|
|
2272
|
+
} else if (filter === 'show') {
|
|
2273
|
+
// 有归档会话的桶全部被藏起来后,就没必要再把全局筛选停在 show。
|
|
2274
|
+
if (holders.size > 0 && [...holders].every((id) => hiddenArchivedWorkspaces.has(id))) {
|
|
2275
|
+
hiddenArchivedWorkspaces = new Set();
|
|
2276
|
+
persistHiddenWorkspaces();
|
|
2277
|
+
writeViewFilter('default');
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
scheduleArchivedRows();
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
const ARCHIVED_ROW_ATTR = 'data-dsrm-archived-hidden';
|
|
2284
|
+
|
|
2285
|
+
/**
|
|
2286
|
+
* 把不需要显示的归档行藏掉。宿主在 show 模式下会把所有工作区的归档行都渲染
|
|
2287
|
+
* 出来(没有逐工作区筛选),所以这里只能按 data-row-key 的会话 id 与快照
|
|
2288
|
+
* 里的归属关系决定藏哪些行。行组件重渲染时会被整体替换,MutationObserver
|
|
2289
|
+
* 会再次跑一遍。
|
|
2290
|
+
*/
|
|
2291
|
+
function applyArchivedRows() {
|
|
2292
|
+
if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') return;
|
|
2293
|
+
syncViewFilter();
|
|
2294
|
+
const filter = readViewFilter();
|
|
2295
|
+
const active = (filter === 'show' || filter === 'only') && hiddenArchivedWorkspaces.size > 0;
|
|
2296
|
+
const { archived, owner } = active ? archivedWorkspaceIndex() : { archived: new Set(), owner: new Map() };
|
|
2297
|
+
for (const row of document.querySelectorAll('[data-row-key^="session:"]')) {
|
|
2298
|
+
const sessionId = row.getAttribute('data-row-key').slice('session:'.length);
|
|
2299
|
+
const bucket = owner.get(sessionId) ?? UNGROUPED_OWNER;
|
|
2300
|
+
const hide = active && archived.has(sessionId) && hiddenArchivedWorkspaces.has(bucket);
|
|
2301
|
+
if (hide) {
|
|
2302
|
+
if (row.style.display !== 'none') {
|
|
2303
|
+
row.setAttribute(ARCHIVED_ROW_ATTR, '1');
|
|
2304
|
+
row.style.display = 'none';
|
|
2305
|
+
}
|
|
2306
|
+
} else if (row.getAttribute(ARCHIVED_ROW_ATTR) === '1') {
|
|
2307
|
+
row.removeAttribute(ARCHIVED_ROW_ATTR);
|
|
2308
|
+
row.style.display = '';
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
/** 用户在「视图选项」里改了全局筛选:以全局选择为准,清掉逐工作区隐藏。 */
|
|
2314
|
+
function syncViewFilter() {
|
|
2315
|
+
const current = readViewFilter();
|
|
2316
|
+
if (current === lastViewFilter) return;
|
|
2317
|
+
lastViewFilter = current;
|
|
2318
|
+
hiddenArchivedWorkspaces = new Set();
|
|
2319
|
+
persistHiddenWorkspaces();
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
let archivedApplyTimer = 0;
|
|
2323
|
+
let archivedObserver = null;
|
|
2324
|
+
|
|
2325
|
+
function scheduleArchivedRows() {
|
|
2326
|
+
if (typeof setTimeout !== 'function') {
|
|
2327
|
+
applyArchivedRows();
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
if (archivedApplyTimer !== 0) return;
|
|
2331
|
+
archivedApplyTimer = setTimeout(() => {
|
|
2332
|
+
archivedApplyTimer = 0;
|
|
2333
|
+
applyArchivedRows();
|
|
2334
|
+
}, 40);
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
function archivedRowMutation(records) {
|
|
2338
|
+
for (const record of records) {
|
|
2339
|
+
for (const node of record.addedNodes) {
|
|
2340
|
+
if (node.nodeType === 1 && (node.matches?.('[data-row-key]') || node.querySelector?.('[data-row-key]'))) {
|
|
2341
|
+
return true;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
for (const node of record.removedNodes) {
|
|
2345
|
+
if (node.nodeType === 1 && (node.matches?.('[data-row-key]') || node.querySelector?.('[data-row-key]'))) {
|
|
2346
|
+
return true;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
return false;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
function startArchivedRows() {
|
|
2354
|
+
scheduleArchivedRows();
|
|
2355
|
+
if (typeof MutationObserver !== 'function') return () => {};
|
|
2356
|
+
if (typeof document === 'undefined' || document.body === null || document.body === undefined) return () => {};
|
|
2357
|
+
archivedObserver = new MutationObserver((records) => {
|
|
2358
|
+
if (archivedRowMutation(records)) scheduleArchivedRows();
|
|
2359
|
+
});
|
|
2360
|
+
archivedObserver.observe(document.body, { childList: true, subtree: true });
|
|
2361
|
+
return () => {
|
|
2362
|
+
archivedObserver?.disconnect();
|
|
2363
|
+
archivedObserver = null;
|
|
2364
|
+
if (archivedApplyTimer !== 0 && typeof clearTimeout === 'function') {
|
|
2365
|
+
clearTimeout(archivedApplyTimer);
|
|
2366
|
+
archivedApplyTimer = 0;
|
|
2367
|
+
}
|
|
2368
|
+
if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') return;
|
|
2369
|
+
for (const row of document.querySelectorAll(`[${ARCHIVED_ROW_ATTR}="1"]`)) {
|
|
2370
|
+
row.removeAttribute(ARCHIVED_ROW_ATTR);
|
|
2371
|
+
row.style.display = '';
|
|
2372
|
+
}
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
|
|
2072
2377
|
function workspaceSessionIds(workspaceId) {
|
|
2073
2378
|
const snapshot = serviceOf(rootCtx, 'workspaces')?.list?.getSnapshot?.();
|
|
2074
2379
|
const entry = Array.isArray(snapshot?.items)
|
|
@@ -2119,39 +2424,46 @@ window.__ModuleLoader__.load({
|
|
|
2119
2424
|
const apps = appsSnapshot();
|
|
2120
2425
|
const withApps = itemsSnapshot().openWith !== false;
|
|
2121
2426
|
const pinned = withApps ? effectivePinned(apps.ids).filter((id) => childOn(id)) : [];
|
|
2122
|
-
pinned.forEach((id, index) => {
|
|
2123
|
-
items.push({
|
|
2124
|
-
id: `pinned-${id}`,
|
|
2125
|
-
group: 'pinned',
|
|
2126
|
-
separatorBefore: index === 0,
|
|
2127
|
-
label: t('openWithApp', { name: appLabel(id, t) }),
|
|
2128
|
-
icon: { key: 'sidebar-row-menu.app', src: ICON_ROUTE + encodeURIComponent(id) },
|
|
2129
|
-
run: () => openAppAt(id, cwd, t),
|
|
2130
|
-
});
|
|
2131
|
-
});
|
|
2132
2427
|
const enabledApps = withApps ? apps.ids.filter((id) => childOn(id)) : [];
|
|
2133
2428
|
const pinnedSet = new Set(pinned);
|
|
2134
2429
|
const submenuApps = enabledApps.filter((id) => !pinnedSet.has(id));
|
|
2135
2430
|
if (withApps) ensureApps();
|
|
2136
|
-
|
|
2431
|
+
|
|
2432
|
+
// 与会话菜单同一套分组:行内动作 → 复制/在文件夹显示 → 打开方式 → 删除。
|
|
2433
|
+
const tabs = serviceOf(rootCtx, 'projectTabs') ?? globalThis.__DSH_PROJECT_TABS__ ?? null;
|
|
2434
|
+
if (tabs !== null && typeof tabs?.openWorkspaceInTab === 'function') {
|
|
2137
2435
|
items.push({
|
|
2138
|
-
id: '
|
|
2139
|
-
group: '
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2436
|
+
id: 'tab',
|
|
2437
|
+
group: 'tab',
|
|
2438
|
+
label: t('openInTab'),
|
|
2439
|
+
icon: { key: 'sidebar-row-menu.tab' },
|
|
2440
|
+
run: () => {
|
|
2441
|
+
Promise.resolve().then(() => tabs.openWorkspaceInTab(workspaceId)).catch((reason) => {
|
|
2442
|
+
console.warn('[dsh-sidebar-row-menu] open in tab failed:', reason);
|
|
2443
|
+
toast(t('actionFailed'));
|
|
2444
|
+
});
|
|
2445
|
+
},
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// 归档会话的显示开关:只有该工作区确实存在归档会话时才出现,状态用图标
|
|
2450
|
+
// 与文案区分(宿主菜单项没有 checked 字段)。
|
|
2451
|
+
const archivedCount = itemOn('archivedSessions') ? archivedCountOfWorkspace(workspaceId) : 0;
|
|
2452
|
+
if (archivedCount > 0) {
|
|
2453
|
+
const archivedVisible = archivedRowsVisibleIn(workspaceId);
|
|
2454
|
+
items.push({
|
|
2455
|
+
id: 'archived',
|
|
2456
|
+
group: 'archived',
|
|
2457
|
+
label: t(archivedVisible ? 'hideArchivedSessions' : 'showArchivedSessions'),
|
|
2458
|
+
icon: { key: archivedVisible ? 'sidebar-row-menu.archive-off' : 'sidebar-row-menu.archive' },
|
|
2459
|
+
run: () => toggleWorkspaceArchived(workspaceId),
|
|
2148
2460
|
});
|
|
2149
2461
|
}
|
|
2150
2462
|
|
|
2151
2463
|
items.push({
|
|
2152
2464
|
id: 'copy-path',
|
|
2153
2465
|
group: 'copy',
|
|
2154
|
-
separatorBefore:
|
|
2466
|
+
separatorBefore: true,
|
|
2155
2467
|
label: t('copyPath'),
|
|
2156
2468
|
icon: { key: 'sidebar-row-menu.copy' },
|
|
2157
2469
|
run: () => {
|
|
@@ -2163,19 +2475,36 @@ window.__ModuleLoader__.load({
|
|
|
2163
2475
|
},
|
|
2164
2476
|
});
|
|
2165
2477
|
|
|
2166
|
-
|
|
2167
|
-
if (tabs !== null && typeof tabs?.openWorkspaceInTab === 'function') {
|
|
2478
|
+
pinned.forEach((id, index) => {
|
|
2168
2479
|
items.push({
|
|
2169
|
-
id:
|
|
2170
|
-
group: '
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2480
|
+
id: `pinned-${id}`,
|
|
2481
|
+
group: 'pinned',
|
|
2482
|
+
separatorBefore: index === 0,
|
|
2483
|
+
label: t('openWithApp', { name: appLabel(id, t) }),
|
|
2484
|
+
icon: { key: 'sidebar-row-menu.app', src: ICON_ROUTE + encodeURIComponent(id) },
|
|
2485
|
+
run: () => openAppAt(id, cwd, t),
|
|
2486
|
+
});
|
|
2487
|
+
});
|
|
2488
|
+
|
|
2489
|
+
if (withApps && submenuApps.length > 0) {
|
|
2490
|
+
items.push({
|
|
2491
|
+
id: 'open-with',
|
|
2492
|
+
group: 'open-with',
|
|
2493
|
+
separatorBefore: pinned.length === 0,
|
|
2494
|
+
// 宿主的菜单项对子菜单只设 aria-haspopup,不画右侧箭头。label 在宿主里
|
|
2495
|
+
// 是直接当 children 渲染的,所以这里塞一个右对齐的箭头元素,与会话菜单
|
|
2496
|
+
// 里的「打开方式」保持同一套外观(0.2.1 自绘菜单本来就是自己画的)。
|
|
2497
|
+
label: h('span', { className: 'dsrm-host-label' },
|
|
2498
|
+
h('span', { className: 'dsrm-host-label-text' }, t('openWith')),
|
|
2499
|
+
jsx(IconChevronRightOutlineRegular, { size: 12, className: 'dsrm-submenu-chevron' }),
|
|
2500
|
+
),
|
|
2501
|
+
icon: { key: 'sidebar-row-menu.open-with' },
|
|
2502
|
+
children: submenuApps.map((id) => ({
|
|
2503
|
+
id: `app-${id}`,
|
|
2504
|
+
label: appLabel(id, t),
|
|
2505
|
+
icon: { key: 'sidebar-row-menu.app', src: ICON_ROUTE + encodeURIComponent(id) },
|
|
2506
|
+
run: () => openAppAt(id, cwd, t),
|
|
2507
|
+
})),
|
|
2179
2508
|
});
|
|
2180
2509
|
}
|
|
2181
2510
|
|
|
@@ -2203,13 +2532,34 @@ window.__ModuleLoader__.load({
|
|
|
2203
2532
|
/** 宿主只按 provider.order 排序,同组顺序即数组顺序;故每组单独注册。 */
|
|
2204
2533
|
const WORKSPACE_MENU_GROUPS = [
|
|
2205
2534
|
{ key: 'new', order: 90 },
|
|
2206
|
-
{ key: '
|
|
2207
|
-
{ key: '
|
|
2208
|
-
{ key: 'copy', order:
|
|
2209
|
-
{ key: '
|
|
2535
|
+
{ key: 'tab', order: 110 },
|
|
2536
|
+
{ key: 'archived', order: 130 },
|
|
2537
|
+
{ key: 'copy', order: 150 },
|
|
2538
|
+
{ key: 'pinned', order: 210 },
|
|
2539
|
+
{ key: 'open-with', order: 220 },
|
|
2210
2540
|
{ key: 'delete', order: 310 },
|
|
2211
2541
|
];
|
|
2212
2542
|
|
|
2543
|
+
/**
|
|
2544
|
+
* 0.2.2 的「…」按钮与右键共用同一份宿主菜单,条目本身无法感知手势,
|
|
2545
|
+
* 所以按右键标记决定是否进入这份菜单;打开对应附加开关后,点「…」也显示。
|
|
2546
|
+
*/
|
|
2547
|
+
const WORKSPACE_GROUP_ELLIPSIS = {
|
|
2548
|
+
new: 'newSession',
|
|
2549
|
+
pinned: 'openWith',
|
|
2550
|
+
'open-with': 'openWith',
|
|
2551
|
+
copy: 'copyPath',
|
|
2552
|
+
tab: 'openInTab',
|
|
2553
|
+
archived: 'showArchived',
|
|
2554
|
+
delete: 'deleteSessions',
|
|
2555
|
+
};
|
|
2556
|
+
|
|
2557
|
+
function workspaceGroupVisible(groupKey) {
|
|
2558
|
+
if (rightClickMark) return true;
|
|
2559
|
+
const key = WORKSPACE_GROUP_ELLIPSIS[groupKey];
|
|
2560
|
+
return key !== undefined && ellipsisSettings[key] === true;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2213
2563
|
/** 注册工作区动作;服务可能晚于插件 apply 出现,带有限重试。 */
|
|
2214
2564
|
function registerWorkspaceContextMenu(ctx) {
|
|
2215
2565
|
let disposers = null;
|
|
@@ -2223,6 +2573,9 @@ window.__ModuleLoader__.load({
|
|
|
2223
2573
|
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.new' }, IconNewChatOutlineRegular);
|
|
2224
2574
|
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.copy' }, IconCopyOutlineRegular);
|
|
2225
2575
|
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.tab' }, IconProjectAddOutlineRegular);
|
|
2576
|
+
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.archive' }, IconArchiveOutlineRegular);
|
|
2577
|
+
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.archive-off' }, IconArchiveOffOutlineRegular);
|
|
2578
|
+
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.open-with' }, IconRightUpOutlineRegular);
|
|
2226
2579
|
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.delete' }, IconTrashOutlineRegular);
|
|
2227
2580
|
yield ctx.slots.register({ name: 'contextMenu.icon', key: 'sidebar-row-menu.app' }, ContextMenuAppIcon);
|
|
2228
2581
|
});
|
|
@@ -2247,9 +2600,12 @@ window.__ModuleLoader__.load({
|
|
|
2247
2600
|
target: 'workspace',
|
|
2248
2601
|
order: group.order,
|
|
2249
2602
|
observe: () => [appsSource],
|
|
2250
|
-
resolve: (target) =>
|
|
2251
|
-
|
|
2252
|
-
|
|
2603
|
+
resolve: (target) => {
|
|
2604
|
+
if (!workspaceGroupVisible(group.key)) return [];
|
|
2605
|
+
return workspaceMenuActions(ctx, target, localeOf(ctx))
|
|
2606
|
+
.filter((action) => action.group === group.key)
|
|
2607
|
+
.map(({ group: _group, ...action }) => action);
|
|
2608
|
+
},
|
|
2253
2609
|
}));
|
|
2254
2610
|
} catch (error) {
|
|
2255
2611
|
console.warn(`[dsh-sidebar-row-menu] workspace context registration failed (${group.key}):`, error);
|
|
@@ -2482,11 +2838,15 @@ window.__ModuleLoader__.load({
|
|
|
2482
2838
|
.dsrm-submenu-trigger:hover:not(:disabled),.dsrm-submenu-trigger:focus-visible{background:var(--dsw-alias-interactive-bg-hover);outline:none}
|
|
2483
2839
|
.dsrm-submenu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2484
2840
|
.dsrm-submenu-chevron{color:var(--dsw-alias-label-caption)}
|
|
2841
|
+
.dsrm-host-label{display:flex;align-items:center;gap:6px;width:100%}
|
|
2842
|
+
.dsrm-host-label-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
|
|
2485
2843
|
.dsrm-submenu{position:fixed;z-index:1101;min-width:180px;max-width:300px;max-height:min(380px,64vh);overflow:auto;padding:4px}
|
|
2486
2844
|
.dsrm-submenu-empty{padding:6px 8px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
2487
2845
|
.dsrm-menu{position:fixed;z-index:2147483647}
|
|
2488
2846
|
.dsrm-menu-surface{min-width:210px;padding:4px}
|
|
2489
2847
|
.dsrm-sep{height:.5px;margin:3px 2px;background:var(--dsw-alias-border-l2)}
|
|
2848
|
+
/* 150% 缩放等分数像素比下,宿主 0.5px 的分隔线会被摊成两行近乎透明,补到 1px 才看得见。 */
|
|
2849
|
+
@media (min-resolution:1.2dppx) and (max-resolution:1.99dppx){[role="menu"] [role="separator"],.dsrm-sep{height:1px}}
|
|
2490
2850
|
.dsrm-app-icon{width:14px;height:14px;border-radius:3px;display:block;object-fit:contain}
|
|
2491
2851
|
.dsrm-settings{max-width:820px;margin:0 auto;padding:2px 0 36px}
|
|
2492
2852
|
.dsrm-settings-title{margin:0;font-size:16px;line-height:24px;font-weight:600}
|
|
@@ -2554,6 +2914,18 @@ window.__ModuleLoader__.load({
|
|
|
2554
2914
|
const rowKey = row.getAttribute('data-row-key') ?? '';
|
|
2555
2915
|
if (!rowKey.startsWith('workspace:') && !rowKey.startsWith('session:')) return;
|
|
2556
2916
|
|
|
2917
|
+
// 宿主自己会处理右键(0.2.2):只打手势标记,菜单交给宿主开在指针处。
|
|
2918
|
+
// 这样既不用改锚点、也不用代点「…」,少两层时序假设;provider 在解析时用
|
|
2919
|
+
// 这个标记决定条目是否进入菜单(「…」的按下会先清标)。
|
|
2920
|
+
if (hasNativeRowMenu(row)) {
|
|
2921
|
+
if (rowKey.startsWith('session:') && resolveSessionRow(start) === null) return;
|
|
2922
|
+
markRightClickMenu();
|
|
2923
|
+
primeRowActionPointer(typeof row.querySelector === 'function'
|
|
2924
|
+
? row.querySelector('[class*="rowActions"] button')
|
|
2925
|
+
: null);
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2557
2929
|
if (rowKey.startsWith('workspace:')) {
|
|
2558
2930
|
const props = resolveWorkspaceRow(start);
|
|
2559
2931
|
if (props === null) {
|
|
@@ -2570,15 +2942,6 @@ window.__ModuleLoader__.load({
|
|
|
2570
2942
|
}
|
|
2571
2943
|
|
|
2572
2944
|
if (resolveSessionRow(start) === null) return;
|
|
2573
|
-
// 宿主自己会处理右键(0.2.2):只打手势标记,菜单交给宿主开在指针处。
|
|
2574
|
-
// 这样既不用改锚点、也不用代点「…」,少两层时序假设。
|
|
2575
|
-
if (hasNativeRowMenu(row)) {
|
|
2576
|
-
markRightClickMenu();
|
|
2577
|
-
primeRowActionPointer(typeof row.querySelector === 'function'
|
|
2578
|
-
? row.querySelector('[class*="rowActions"] button')
|
|
2579
|
-
: null);
|
|
2580
|
-
return;
|
|
2581
|
-
}
|
|
2582
2945
|
event.preventDefault();
|
|
2583
2946
|
event.stopPropagation();
|
|
2584
2947
|
event.stopImmediatePropagation?.();
|
|
@@ -2632,11 +2995,13 @@ window.__ModuleLoader__.load({
|
|
|
2632
2995
|
ctx.effect(() => installStyles(), 'sidebar-row-menu: styles');
|
|
2633
2996
|
}
|
|
2634
2997
|
const stopWorkspaceMenu = registerWorkspaceContextMenu(ctx);
|
|
2998
|
+
const stopArchivedRows = startArchivedRows();
|
|
2635
2999
|
const teardown = install();
|
|
2636
3000
|
if (ctx !== null && typeof ctx.effect === 'function') {
|
|
2637
3001
|
ctx.effect(() => () => {
|
|
2638
3002
|
rootCtx = null;
|
|
2639
3003
|
stopWorkspaceMenu();
|
|
3004
|
+
stopArchivedRows();
|
|
2640
3005
|
teardown();
|
|
2641
3006
|
}, 'sidebar-row-menu: row context menus');
|
|
2642
3007
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-sidebar-row-menu",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Adds open-with and cleanup entries to the DSH sidebar context menus: interleaved with the built-in workspace menu and extending the session menu at the pointer on DSH 0.2.2+, with a self-drawn workspace menu on 0.2.1.",
|
|
6
6
|
"keywords": [
|