dsh-sidebar-row-menu 1.4.0 → 1.4.2
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 +41 -0
- package/README.en.md +46 -18
- package/README.md +35 -15
- package/lib/client.js +392 -44
- package/lib/host/route.js +67 -6
- package/lib/host/workspace-delete.js +101 -0
- package/lib/index.js +13 -9
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -88,8 +88,10 @@ window.__ModuleLoader__.load({
|
|
|
88
88
|
if (children.length === 0) return jsx(type, props);
|
|
89
89
|
return jsx(type, { ...props, children: children.length === 1 ? children[0] : children });
|
|
90
90
|
}
|
|
91
|
-
/**
|
|
91
|
+
/** 永久删除会话的宿主端点;是否对未归档会话开放由本地开关决定。 */
|
|
92
92
|
const DELETE_ENDPOINT = '/dsh-sidebar-row-menu/session/delete';
|
|
93
|
+
/** 删除「工作区 + 其下全部会话」的宿主端点。 */
|
|
94
|
+
const WORKSPACE_DELETE_ENDPOINT = '/dsh-sidebar-row-menu/workspace/delete';
|
|
93
95
|
/** 官方 open-in-app 的三条路由;用文档相对路径让浏览器自带认证。 */
|
|
94
96
|
const APPS_ROUTE = 'open-in-app/apps';
|
|
95
97
|
const OPEN_ROUTE = 'open-in-app/open';
|
|
@@ -104,7 +106,33 @@ window.__ModuleLoader__.load({
|
|
|
104
106
|
'intellij', 'pycharm', 'webstorm', 'phpstorm', 'goland', 'rider', 'rustrover',
|
|
105
107
|
'androidstudio', 'xcode',
|
|
106
108
|
];
|
|
107
|
-
|
|
109
|
+
/**
|
|
110
|
+
* 各项菜单开关的默认值。删除未归档会话是破坏性能力,默认关闭,
|
|
111
|
+
* 必须在设置里主动开启后才出现在普通会话的右键菜单里。
|
|
112
|
+
*/
|
|
113
|
+
const ITEM_DEFAULTS = {
|
|
114
|
+
openWith: true,
|
|
115
|
+
deleteSession: true,
|
|
116
|
+
deleteArchived: true,
|
|
117
|
+
deleteUnarchived: false,
|
|
118
|
+
};
|
|
119
|
+
const ITEM_KEYS = Object.keys(ITEM_DEFAULTS);
|
|
120
|
+
/**
|
|
121
|
+
* 「右键专属条目是否也放进行尾…菜单」的逐项开关。
|
|
122
|
+
* 全部默认关闭:不勾选时点「…」看到的仍是 DSH 原生菜单。
|
|
123
|
+
*/
|
|
124
|
+
const ELLIPSIS_KEY = 'dsh.sidebar-row-menu.ellipsis.v1';
|
|
125
|
+
const ELLIPSIS_DEFAULTS = {
|
|
126
|
+
newSession: false,
|
|
127
|
+
openWith: false,
|
|
128
|
+
copyPath: false,
|
|
129
|
+
openInTab: false,
|
|
130
|
+
deleteSessions: false,
|
|
131
|
+
sessionOpenWith: false,
|
|
132
|
+
sessionDelete: false,
|
|
133
|
+
sessionDeleteArchived: false,
|
|
134
|
+
};
|
|
135
|
+
const ELLIPSIS_KEYS = Object.keys(ELLIPSIS_DEFAULTS);
|
|
108
136
|
/**
|
|
109
137
|
* dsh-session-menu 注册的同一能力条目 id。检测到对方存在时,本插件对应项
|
|
110
138
|
* 自动隐藏,避免同一份菜单里出现两条一模一样的动作。
|
|
@@ -125,6 +153,7 @@ window.__ModuleLoader__.load({
|
|
|
125
153
|
copyPath: '复制工作区路径',
|
|
126
154
|
openInTab: '在新标签中打开',
|
|
127
155
|
deleteWorkspace: '删除工作区',
|
|
156
|
+
deleteWorkspaceSessions: '删除工作区及所有会话',
|
|
128
157
|
copied: '已复制',
|
|
129
158
|
copyFailed: '复制失败,请重试',
|
|
130
159
|
noPath: '没有可用的目录路径',
|
|
@@ -138,9 +167,11 @@ window.__ModuleLoader__.load({
|
|
|
138
167
|
'settings.openWith.label': '打开方式',
|
|
139
168
|
'settings.openWith.hint': '在子菜单里选择编辑器、终端或文件管理器打开工作区或会话目录;置顶的应用直接显示在菜单一级。',
|
|
140
169
|
'settings.deleteSession.label': '永久删除会话',
|
|
141
|
-
'settings.deleteSession.hint': '
|
|
170
|
+
'settings.deleteSession.hint': '在会话菜单里永久删除这一个会话及其日志;默认只对已归档会话显示。',
|
|
142
171
|
'settings.deleteArchived.label': '删除所有已归档会话',
|
|
143
172
|
'settings.deleteArchived.hint': '在已归档会话的菜单里一次清理全部归档会话。',
|
|
173
|
+
'settings.deleteUnarchived.label': '允许删除未归档会话',
|
|
174
|
+
'settings.deleteUnarchived.hint': '开启后,未归档会话的右键菜单也会出现「永久删除会话」;确认后日志立即从磁盘移除,不可恢复。',
|
|
144
175
|
'settings.children': '选择要显示的应用',
|
|
145
176
|
'settings.pin': '置顶',
|
|
146
177
|
'settings.pinned': '已置顶',
|
|
@@ -154,6 +185,26 @@ window.__ModuleLoader__.load({
|
|
|
154
185
|
'settings.appsAllOff': '所有应用都被二级开关隐藏了',
|
|
155
186
|
'settings.expand': '展开应用列表',
|
|
156
187
|
'settings.collapse': '收起应用列表',
|
|
188
|
+
'settings.ellipsisTitle': '同时放进「…」菜单',
|
|
189
|
+
'settings.ellipsisHint': '勾选后,对应条目会额外出现在行尾「…」按钮的菜单里,并沿用上面的单项开关;不勾选则只在右键菜单出现。',
|
|
190
|
+
'settings.ellipsisWorkspace': '工作区行',
|
|
191
|
+
'settings.ellipsisSession': '会话行',
|
|
192
|
+
'settings.ellipsis.newSession.label': '新建会话',
|
|
193
|
+
'settings.ellipsis.newSession.hint': '在「…」菜单里直接新建该工作区的会话。',
|
|
194
|
+
'settings.ellipsis.openWith.label': '打开方式',
|
|
195
|
+
'settings.ellipsis.openWith.hint': '在「…」菜单里用外部应用打开工作区或会话目录。',
|
|
196
|
+
'settings.ellipsis.copyPath.label': '拷贝工作区路径',
|
|
197
|
+
'settings.ellipsis.copyPath.hint': '在「…」菜单里复制工作区目录路径。',
|
|
198
|
+
'settings.ellipsis.openInTab.label': '在新标签页打开',
|
|
199
|
+
'settings.ellipsis.openInTab.hint': '在「…」菜单里把工作区作为标签页打开。',
|
|
200
|
+
'settings.ellipsis.deleteSessions.label': '删除工作区及所有会话',
|
|
201
|
+
'settings.ellipsis.deleteSessions.hint': '在「…」菜单里同时清掉工作区记录和它下面的全部会话。',
|
|
202
|
+
'settings.ellipsis.sessionOpenWith.label': '打开方式',
|
|
203
|
+
'settings.ellipsis.sessionOpenWith.hint': '在「…」菜单里用外部应用打开会话目录。',
|
|
204
|
+
'settings.ellipsis.sessionDelete.label': '永久删除会话',
|
|
205
|
+
'settings.ellipsis.sessionDelete.hint': '在「…」菜单里永久删除这一个会话及其日志。',
|
|
206
|
+
'settings.ellipsis.sessionDeleteArchived.label': '删除所有已归档会话',
|
|
207
|
+
'settings.ellipsis.sessionDeleteArchived.hint': '在已归档会话的「…」菜单里一次清理全部归档会话。',
|
|
157
208
|
'delete.title': '永久删除会话?',
|
|
158
209
|
'delete.description': '会话「{name}」的日志将从磁盘永久删除,此操作无法撤销。',
|
|
159
210
|
'delete.archivedTitle': '删除所有已归档会话?',
|
|
@@ -168,6 +219,14 @@ window.__ModuleLoader__.load({
|
|
|
168
219
|
'delete.unavailable': '删除服务不可用',
|
|
169
220
|
'delete.done': '已删除会话',
|
|
170
221
|
'delete.doneMany': '已删除 {n} 个会话',
|
|
222
|
+
'delete.workspaceTitle': '删除工作区及所有会话?',
|
|
223
|
+
'delete.workspaceDescription': '工作区「{name}」及其下 {n} 个会话的日志将被永久删除,目录本身保留。此操作无法撤销。',
|
|
224
|
+
'delete.workspaceEmptyDescription': '工作区「{name}」下没有会话,将只删除工作区记录。',
|
|
225
|
+
'delete.workspaceConfirm': '全部删除',
|
|
226
|
+
'delete.workspacePending': '正在删除工作区及会话…',
|
|
227
|
+
'delete.workspacePartial': '已删除 {done} 个会话,失败 {failed} 个;工作区已保留。',
|
|
228
|
+
'delete.workspaceDone': '已删除工作区及 {n} 个会话',
|
|
229
|
+
'delete.workspaceEmptyDone': '已删除工作区',
|
|
171
230
|
};
|
|
172
231
|
const en = {
|
|
173
232
|
newSession: 'New session',
|
|
@@ -177,6 +236,7 @@ window.__ModuleLoader__.load({
|
|
|
177
236
|
copyPath: 'Copy workspace path',
|
|
178
237
|
openInTab: 'Open in new tab',
|
|
179
238
|
deleteWorkspace: 'Delete workspace',
|
|
239
|
+
deleteWorkspaceSessions: 'Delete workspace and all sessions',
|
|
180
240
|
copied: 'Copied',
|
|
181
241
|
copyFailed: 'Copy failed, please retry',
|
|
182
242
|
noPath: 'No usable directory path',
|
|
@@ -190,9 +250,11 @@ window.__ModuleLoader__.load({
|
|
|
190
250
|
'settings.openWith.label': 'Open with',
|
|
191
251
|
'settings.openWith.hint': 'Pick an editor, terminal or file manager for workspace or session directories; pinned apps show up directly in the menu.',
|
|
192
252
|
'settings.deleteSession.label': 'Delete session permanently',
|
|
193
|
-
'settings.deleteSession.hint': 'Permanently delete
|
|
253
|
+
'settings.deleteSession.hint': 'Permanently delete one session and its transcript from its menu; shown on archived sessions by default.',
|
|
194
254
|
'settings.deleteArchived.label': 'Delete all archived sessions',
|
|
195
255
|
'settings.deleteArchived.hint': 'Clean up every archived session from an archived session row.',
|
|
256
|
+
'settings.deleteUnarchived.label': 'Allow deleting unarchived sessions',
|
|
257
|
+
'settings.deleteUnarchived.hint': 'Also show "Delete session permanently" on unarchived rows. The transcript is removed from disk immediately and cannot be restored.',
|
|
196
258
|
'settings.children': 'Applications to show',
|
|
197
259
|
'settings.pin': 'Pin',
|
|
198
260
|
'settings.pinned': 'Pinned',
|
|
@@ -206,6 +268,26 @@ window.__ModuleLoader__.load({
|
|
|
206
268
|
'settings.appsAllOff': 'Every application is hidden by the switches above',
|
|
207
269
|
'settings.expand': 'Show applications',
|
|
208
270
|
'settings.collapse': 'Hide applications',
|
|
271
|
+
'settings.ellipsisTitle': 'Also show in the "…" menu',
|
|
272
|
+
'settings.ellipsisHint': 'Checked entries also appear in the row\'s "…" button menu, still subject to the switches above. Unchecked entries stay right-click only.',
|
|
273
|
+
'settings.ellipsisWorkspace': 'Workspace rows',
|
|
274
|
+
'settings.ellipsisSession': 'Session rows',
|
|
275
|
+
'settings.ellipsis.newSession.label': 'New session',
|
|
276
|
+
'settings.ellipsis.newSession.hint': 'Create a session for this workspace from the "…" menu.',
|
|
277
|
+
'settings.ellipsis.openWith.label': 'Open with',
|
|
278
|
+
'settings.ellipsis.openWith.hint': 'Open the workspace or session directory with an external app from the "…" menu.',
|
|
279
|
+
'settings.ellipsis.copyPath.label': 'Copy workspace path',
|
|
280
|
+
'settings.ellipsis.copyPath.hint': 'Copy the workspace directory path from the "…" menu.',
|
|
281
|
+
'settings.ellipsis.openInTab.label': 'Open in new tab',
|
|
282
|
+
'settings.ellipsis.openInTab.hint': 'Open the workspace as a tab from the "…" menu.',
|
|
283
|
+
'settings.ellipsis.deleteSessions.label': 'Delete workspace and all sessions',
|
|
284
|
+
'settings.ellipsis.deleteSessions.hint': 'Remove the workspace record and every session under it from the "…" menu.',
|
|
285
|
+
'settings.ellipsis.sessionOpenWith.label': 'Open with',
|
|
286
|
+
'settings.ellipsis.sessionOpenWith.hint': 'Open the session directory with an external app from the "…" menu.',
|
|
287
|
+
'settings.ellipsis.sessionDelete.label': 'Delete session permanently',
|
|
288
|
+
'settings.ellipsis.sessionDelete.hint': 'Permanently delete this session and its log from the "…" menu.',
|
|
289
|
+
'settings.ellipsis.sessionDeleteArchived.label': 'Delete all archived sessions',
|
|
290
|
+
'settings.ellipsis.sessionDeleteArchived.hint': 'Clean up every archived session from an archived row\'s "…" menu.',
|
|
209
291
|
'delete.title': 'Delete this session permanently?',
|
|
210
292
|
'delete.description': 'The log of "{name}" will be removed from disk. This cannot be undone.',
|
|
211
293
|
'delete.archivedTitle': 'Delete all archived sessions?',
|
|
@@ -220,6 +302,14 @@ window.__ModuleLoader__.load({
|
|
|
220
302
|
'delete.unavailable': 'The delete service is unavailable',
|
|
221
303
|
'delete.done': 'Session deleted',
|
|
222
304
|
'delete.doneMany': 'Deleted {n} sessions',
|
|
305
|
+
'delete.workspaceTitle': 'Delete the workspace and all its sessions?',
|
|
306
|
+
'delete.workspaceDescription': 'The workspace "{name}" and the logs of its {n} sessions will be permanently deleted; the directory itself is kept. This cannot be undone.',
|
|
307
|
+
'delete.workspaceEmptyDescription': 'The workspace "{name}" has no sessions; only the workspace record will be deleted.',
|
|
308
|
+
'delete.workspaceConfirm': 'Delete all',
|
|
309
|
+
'delete.workspacePending': 'Deleting the workspace and sessions…',
|
|
310
|
+
'delete.workspacePartial': 'Deleted {done} sessions, failed {failed}; the workspace was kept.',
|
|
311
|
+
'delete.workspaceDone': 'Deleted the workspace and {n} sessions',
|
|
312
|
+
'delete.workspaceEmptyDone': 'Workspace deleted',
|
|
223
313
|
};
|
|
224
314
|
|
|
225
315
|
/** 官方 open-in-app 的应用 id → 显示名。官方 locale 没有这张表。 */
|
|
@@ -341,6 +431,8 @@ window.__ModuleLoader__.load({
|
|
|
341
431
|
/** 当前挂着的菜单宿主与 React root。 */
|
|
342
432
|
let menuHost = null;
|
|
343
433
|
let menuRoot = null;
|
|
434
|
+
/** 当前「…」菜单锚定的按钮;用来判断再次点击是否应收起。 */
|
|
435
|
+
let ellipsisTrigger = null;
|
|
344
436
|
/** 客户端根上下文(apply 时记下,右键时用来取服务 / 文案)。 */
|
|
345
437
|
let rootCtx = null;
|
|
346
438
|
|
|
@@ -361,6 +453,7 @@ window.__ModuleLoader__.load({
|
|
|
361
453
|
const root = menuRoot;
|
|
362
454
|
menuHost = null;
|
|
363
455
|
menuRoot = null;
|
|
456
|
+
ellipsisTrigger = null;
|
|
364
457
|
unmountAt(host, root);
|
|
365
458
|
}
|
|
366
459
|
|
|
@@ -487,8 +580,7 @@ window.__ModuleLoader__.load({
|
|
|
487
580
|
/* ------------------------------------------------ 菜单项设置(localStorage) */
|
|
488
581
|
|
|
489
582
|
function readStoredItems() {
|
|
490
|
-
const defaults = {};
|
|
491
|
-
for (const key of ITEM_KEYS) defaults[key] = true;
|
|
583
|
+
const defaults = { ...ITEM_DEFAULTS };
|
|
492
584
|
try {
|
|
493
585
|
const raw = globalThis.localStorage?.getItem(ITEMS_KEY);
|
|
494
586
|
if (typeof raw !== 'string' || raw === '') return defaults;
|
|
@@ -543,7 +635,77 @@ window.__ModuleLoader__.load({
|
|
|
543
635
|
}
|
|
544
636
|
|
|
545
637
|
function itemOn(key) {
|
|
546
|
-
return itemSettings[key]
|
|
638
|
+
return itemSettings[key] ?? ITEM_DEFAULTS[key] ?? false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/* -------------------------------- 「…」菜单附加开关(localStorage) */
|
|
642
|
+
|
|
643
|
+
function readStoredEllipsis() {
|
|
644
|
+
const defaults = { ...ELLIPSIS_DEFAULTS };
|
|
645
|
+
try {
|
|
646
|
+
const raw = globalThis.localStorage?.getItem(ELLIPSIS_KEY);
|
|
647
|
+
if (typeof raw !== 'string' || raw === '') return defaults;
|
|
648
|
+
const parsed = JSON.parse(raw);
|
|
649
|
+
if (parsed === null || typeof parsed !== 'object') return defaults;
|
|
650
|
+
for (const key of ELLIPSIS_KEYS) {
|
|
651
|
+
if (typeof parsed[key] === 'boolean') defaults[key] = parsed[key];
|
|
652
|
+
}
|
|
653
|
+
} catch {
|
|
654
|
+
/* 坏数据回退默认 */
|
|
655
|
+
}
|
|
656
|
+
return defaults;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let ellipsisSettings = readStoredEllipsis();
|
|
660
|
+
const ellipsisListeners = new Set();
|
|
661
|
+
|
|
662
|
+
function publishEllipsis() {
|
|
663
|
+
for (const listener of ellipsisListeners) {
|
|
664
|
+
try {
|
|
665
|
+
listener();
|
|
666
|
+
} catch {
|
|
667
|
+
/* ignore */
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function subscribeEllipsis(listener) {
|
|
673
|
+
ellipsisListeners.add(listener);
|
|
674
|
+
return () => {
|
|
675
|
+
ellipsisListeners.delete(listener);
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function ellipsisSnapshot() {
|
|
680
|
+
return ellipsisSettings;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function useEllipsis() {
|
|
684
|
+
return useSyncExternalStore(subscribeEllipsis, ellipsisSnapshot, ellipsisSnapshot);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function setEllipsis(key, enabled) {
|
|
688
|
+
if (!ELLIPSIS_KEYS.includes(key)) return;
|
|
689
|
+
ellipsisSettings = { ...ellipsisSettings, [key]: enabled === true };
|
|
690
|
+
try {
|
|
691
|
+
globalThis.localStorage?.setItem(ELLIPSIS_KEY, JSON.stringify(ellipsisSettings));
|
|
692
|
+
} catch {
|
|
693
|
+
/* 隐私模式写不进去也照样生效到本次会话 */
|
|
694
|
+
}
|
|
695
|
+
publishEllipsis();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function ellipsisOn(key) {
|
|
699
|
+
return ellipsisSettings[key] === true;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/** 工作区行是否至少有一项要在「…」菜单里显示(否则完全不接管原生菜单)。 */
|
|
703
|
+
function workspaceEllipsisOn() {
|
|
704
|
+
return ellipsisSettings.newSession === true
|
|
705
|
+
|| ellipsisSettings.openWith === true
|
|
706
|
+
|| ellipsisSettings.copyPath === true
|
|
707
|
+
|| ellipsisSettings.openInTab === true
|
|
708
|
+
|| ellipsisSettings.deleteSessions === true;
|
|
547
709
|
}
|
|
548
710
|
|
|
549
711
|
/* ------------------------------------- 打开方式:应用列表与二级开关 */
|
|
@@ -889,6 +1051,20 @@ window.__ModuleLoader__.load({
|
|
|
889
1051
|
.catch(() => ({ ok: false, deleted: 0, failed: ids.length, error: 'network' }));
|
|
890
1052
|
}
|
|
891
1053
|
|
|
1054
|
+
function postWorkspaceDelete(workspaceId) {
|
|
1055
|
+
if (typeof fetch !== 'function') return Promise.resolve({ ok: false, deleted: 0, failed: 0, error: 'network' });
|
|
1056
|
+
return fetch(WORKSPACE_DELETE_ENDPOINT, {
|
|
1057
|
+
method: 'POST',
|
|
1058
|
+
headers: { 'content-type': 'application/json' },
|
|
1059
|
+
body: JSON.stringify({ workspaceId }),
|
|
1060
|
+
})
|
|
1061
|
+
.then((response) => response.json().catch(() => null)
|
|
1062
|
+
.then((payload) => (payload !== null && typeof payload === 'object'
|
|
1063
|
+
? payload
|
|
1064
|
+
: { ok: false, deleted: 0, failed: 0, error: `HTTP ${response.status}` })))
|
|
1065
|
+
.catch(() => ({ ok: false, deleted: 0, failed: 0, error: 'network' }));
|
|
1066
|
+
}
|
|
1067
|
+
|
|
892
1068
|
/** shell.overlay 上的对话框宿主;没有请求时什么都不渲染。 */
|
|
893
1069
|
function DeleteDialog({ t }) {
|
|
894
1070
|
const request = useDeleteRequest();
|
|
@@ -900,8 +1076,11 @@ window.__ModuleLoader__.load({
|
|
|
900
1076
|
const [busy, setBusy] = useState(false);
|
|
901
1077
|
const [error, setError] = useState(null);
|
|
902
1078
|
const [partial, setPartial] = useState(null);
|
|
1079
|
+
const workspace = request.mode === 'workspace';
|
|
903
1080
|
const bulk = request.mode === 'archived';
|
|
904
|
-
const ids =
|
|
1081
|
+
const ids = workspace
|
|
1082
|
+
? (Array.isArray(request.sessionIds) ? request.sessionIds : [])
|
|
1083
|
+
: bulk ? request.sessionIds : [request.sessionId];
|
|
905
1084
|
const close = () => {
|
|
906
1085
|
if (!busy) closeDelete();
|
|
907
1086
|
};
|
|
@@ -913,11 +1092,29 @@ window.__ModuleLoader__.load({
|
|
|
913
1092
|
const wasMain = ids.some((id) => isMainSession(id));
|
|
914
1093
|
void (async () => {
|
|
915
1094
|
try {
|
|
916
|
-
const result =
|
|
1095
|
+
const result = workspace
|
|
1096
|
+
? await postWorkspaceDelete(request.workspaceId)
|
|
1097
|
+
: await postDelete(ids);
|
|
917
1098
|
if (result.ok !== true && (result.deleted ?? 0) === 0) {
|
|
918
1099
|
const reason = typeof result.error === 'string' && result.error !== '' ? result.error : t('delete.unavailable');
|
|
919
1100
|
throw new Error(reason);
|
|
920
1101
|
}
|
|
1102
|
+
if (workspace) {
|
|
1103
|
+
// 会话删除有失败时 host 会保留工作区,让用户重试;成功才关框。
|
|
1104
|
+
if (result.ok !== true) {
|
|
1105
|
+
setPartial({ done: result.deleted ?? 0, failed: result.failed ?? 0, workspace: true });
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
if (wasMain) serviceOf(rootCtx, 'uiWorkspace')?.clearMain?.();
|
|
1109
|
+
closeDelete();
|
|
1110
|
+
toast(
|
|
1111
|
+
(result.deleted ?? 0) > 0
|
|
1112
|
+
? t('delete.workspaceDone', { n: result.deleted ?? 0 })
|
|
1113
|
+
: t('delete.workspaceEmptyDone'),
|
|
1114
|
+
'success',
|
|
1115
|
+
);
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
921
1118
|
if (wasMain) serviceOf(rootCtx, 'uiWorkspace')?.clearMain?.();
|
|
922
1119
|
if ((result.failed ?? 0) > 0) {
|
|
923
1120
|
setPartial({ done: result.deleted ?? 0, failed: result.failed });
|
|
@@ -936,24 +1133,34 @@ window.__ModuleLoader__.load({
|
|
|
936
1133
|
open: true,
|
|
937
1134
|
onClose: close,
|
|
938
1135
|
closeLabel: t('delete.cancel'),
|
|
939
|
-
title: bulk ? t('delete.archivedTitle') : t('delete.title'),
|
|
940
|
-
description:
|
|
941
|
-
?
|
|
942
|
-
|
|
1136
|
+
title: workspace ? t('delete.workspaceTitle') : bulk ? t('delete.archivedTitle') : t('delete.title'),
|
|
1137
|
+
description: workspace
|
|
1138
|
+
? (ids.length > 0
|
|
1139
|
+
? t('delete.workspaceDescription', { name: request.displayTitle ?? '', n: ids.length })
|
|
1140
|
+
: t('delete.workspaceEmptyDescription', { name: request.displayTitle ?? '' }))
|
|
1141
|
+
: bulk
|
|
1142
|
+
? t('delete.archivedDescription', { n: ids.length })
|
|
1143
|
+
: t('delete.description', { name: request.displayTitle ?? '' }),
|
|
943
1144
|
footer: h(Fragment, null,
|
|
944
1145
|
jsx(Button, { variant: 'outline', disabled: busy, onClick: close, children: t('delete.cancel') }),
|
|
945
1146
|
jsx(Button, {
|
|
946
1147
|
variant: 'outline',
|
|
947
1148
|
disabled: busy,
|
|
948
1149
|
onClick: confirm,
|
|
949
|
-
children: h('span', { className: 'dsrm-danger-label' },
|
|
1150
|
+
children: h('span', { className: 'dsrm-danger-label' }, workspace
|
|
1151
|
+
? t('delete.workspaceConfirm')
|
|
1152
|
+
: bulk ? t('delete.archivedConfirm') : t('delete.confirm')),
|
|
950
1153
|
})),
|
|
951
1154
|
children: [
|
|
952
1155
|
busy
|
|
953
|
-
? h('div', { className: 'dsrm-status-text', role: 'status', key: 'busy' },
|
|
1156
|
+
? h('div', { className: 'dsrm-status-text', role: 'status', key: 'busy' }, workspace
|
|
1157
|
+
? t('delete.workspacePending')
|
|
1158
|
+
: bulk ? t('delete.archivedPending') : t('delete.pending'))
|
|
954
1159
|
: null,
|
|
955
1160
|
partial !== null
|
|
956
|
-
? h('div', { className: 'dsrm-status-text', role: 'status', key: 'partial' },
|
|
1161
|
+
? h('div', { className: 'dsrm-status-text', role: 'status', key: 'partial' }, partial.workspace
|
|
1162
|
+
? t('delete.workspacePartial', { done: partial.done, failed: partial.failed })
|
|
1163
|
+
: t('delete.archivedPartial', { done: partial.done, failed: partial.failed }))
|
|
957
1164
|
: null,
|
|
958
1165
|
error !== null
|
|
959
1166
|
? h('div', { className: 'dsrm-error-text', role: 'alert', key: 'error' }, t('delete.failed', { message: error }))
|
|
@@ -1152,7 +1359,9 @@ window.__ModuleLoader__.load({
|
|
|
1152
1359
|
* 且和 dsh-session-menu 的同名能力二选一。
|
|
1153
1360
|
*/
|
|
1154
1361
|
function SessionOpenWithMenuItem({ sessionId, useMenuOpenState, t }) {
|
|
1155
|
-
const
|
|
1362
|
+
const rightClick = useRightClickEntry();
|
|
1363
|
+
const ellipsis = useEllipsis();
|
|
1364
|
+
const visible = rightClick || ellipsis.sessionOpenWith === true;
|
|
1156
1365
|
const items = useItems();
|
|
1157
1366
|
const apps = useApps();
|
|
1158
1367
|
const setMenuOpen = menuCloser(useMenuOpenState);
|
|
@@ -1173,7 +1382,9 @@ window.__ModuleLoader__.load({
|
|
|
1173
1382
|
* 放进一级菜单,用户也可以在设置里改。置顶项不再重复出现在「打开方式」里。
|
|
1174
1383
|
*/
|
|
1175
1384
|
function SessionOpenWithPinnedMenuItem({ sessionId, useMenuOpenState, t }) {
|
|
1176
|
-
const
|
|
1385
|
+
const rightClick = useRightClickEntry();
|
|
1386
|
+
const ellipsis = useEllipsis();
|
|
1387
|
+
const visible = rightClick || ellipsis.sessionOpenWith === true;
|
|
1177
1388
|
const items = useItems();
|
|
1178
1389
|
useChildren();
|
|
1179
1390
|
const apps = useApps();
|
|
@@ -1206,16 +1417,18 @@ window.__ModuleLoader__.load({
|
|
|
1206
1417
|
}
|
|
1207
1418
|
|
|
1208
1419
|
/**
|
|
1209
|
-
*
|
|
1210
|
-
* 的同名功能二选一。
|
|
1420
|
+
* 会话条目「永久删除会话」:已归档会话默认可见;未归档会话要先在设置里
|
|
1421
|
+
* 打开「允许删除未归档会话」。和 dsh-session-menu 的同名功能二选一。
|
|
1211
1422
|
*/
|
|
1212
1423
|
function SessionDeleteMenuItem({ sessionId, displayTitle, useMenuOpenState, t }) {
|
|
1213
|
-
const
|
|
1424
|
+
const rightClick = useRightClickEntry();
|
|
1425
|
+
const ellipsis = useEllipsis();
|
|
1426
|
+
const visible = rightClick || ellipsis.sessionDelete === true;
|
|
1214
1427
|
const items = useItems();
|
|
1215
1428
|
const archived = useArchivedIds();
|
|
1216
1429
|
const setMenuOpen = menuCloser(useMenuOpenState);
|
|
1217
1430
|
if (!visible || items.deleteSession === false || takenOver('deleteSession')) return null;
|
|
1218
|
-
if (!archived.has(sessionId)) return null;
|
|
1431
|
+
if (!archived.has(sessionId) && items.deleteUnarchived !== true) return null;
|
|
1219
1432
|
return jsx(MenuItemButton, {
|
|
1220
1433
|
danger: true,
|
|
1221
1434
|
separatorBefore: true,
|
|
@@ -1233,7 +1446,9 @@ window.__ModuleLoader__.load({
|
|
|
1233
1446
|
* 和 dsh-session-menu 的同名功能二选一。displayTitle 用作确认文案。
|
|
1234
1447
|
*/
|
|
1235
1448
|
function SessionDeleteArchivedMenuItem({ sessionId, displayTitle, useMenuOpenState, t }) {
|
|
1236
|
-
const
|
|
1449
|
+
const rightClick = useRightClickEntry();
|
|
1450
|
+
const ellipsis = useEllipsis();
|
|
1451
|
+
const visible = rightClick || ellipsis.sessionDeleteArchived === true;
|
|
1237
1452
|
const items = useItems();
|
|
1238
1453
|
const archived = useArchivedIds();
|
|
1239
1454
|
const setMenuOpen = menuCloser(useMenuOpenState);
|
|
@@ -1258,11 +1473,29 @@ window.__ModuleLoader__.load({
|
|
|
1258
1473
|
const SETTINGS_ROWS = [
|
|
1259
1474
|
['openWith', 'settings.openWith.label', 'settings.openWith.hint'],
|
|
1260
1475
|
['deleteSession', 'settings.deleteSession.label', 'settings.deleteSession.hint'],
|
|
1476
|
+
['deleteUnarchived', 'settings.deleteUnarchived.label', 'settings.deleteUnarchived.hint'],
|
|
1261
1477
|
['deleteArchived', 'settings.deleteArchived.label', 'settings.deleteArchived.hint'],
|
|
1262
1478
|
];
|
|
1263
1479
|
|
|
1480
|
+
/** 「…」菜单附加开关;按行分组,避免用户分不清工作区项和会话项。 */
|
|
1481
|
+
const ELLIPSIS_ROWS = [
|
|
1482
|
+
['workspace', 'settings.ellipsisWorkspace', [
|
|
1483
|
+
['newSession', 'settings.ellipsis.newSession.label', 'settings.ellipsis.newSession.hint'],
|
|
1484
|
+
['openWith', 'settings.ellipsis.openWith.label', 'settings.ellipsis.openWith.hint'],
|
|
1485
|
+
['copyPath', 'settings.ellipsis.copyPath.label', 'settings.ellipsis.copyPath.hint'],
|
|
1486
|
+
['openInTab', 'settings.ellipsis.openInTab.label', 'settings.ellipsis.openInTab.hint'],
|
|
1487
|
+
['deleteSessions', 'settings.ellipsis.deleteSessions.label', 'settings.ellipsis.deleteSessions.hint'],
|
|
1488
|
+
]],
|
|
1489
|
+
['session', 'settings.ellipsisSession', [
|
|
1490
|
+
['sessionOpenWith', 'settings.ellipsis.sessionOpenWith.label', 'settings.ellipsis.sessionOpenWith.hint'],
|
|
1491
|
+
['sessionDelete', 'settings.ellipsis.sessionDelete.label', 'settings.ellipsis.sessionDelete.hint'],
|
|
1492
|
+
['sessionDeleteArchived', 'settings.ellipsis.sessionDeleteArchived.label', 'settings.ellipsis.sessionDeleteArchived.hint'],
|
|
1493
|
+
]],
|
|
1494
|
+
];
|
|
1495
|
+
|
|
1264
1496
|
function SettingsSection({ t }) {
|
|
1265
1497
|
const items = useItems();
|
|
1498
|
+
const ellipsis = useEllipsis();
|
|
1266
1499
|
const children = useChildren();
|
|
1267
1500
|
const apps = useApps();
|
|
1268
1501
|
usePinned();
|
|
@@ -1309,6 +1542,9 @@ window.__ModuleLoader__.load({
|
|
|
1309
1542
|
|
|
1310
1543
|
// 应用开关必须紧跟「打开方式」这一行,而不是落到整个列表末尾。
|
|
1311
1544
|
const rows = SETTINGS_ROWS.flatMap(([key, labelKey, hintKey]) => {
|
|
1545
|
+
// 「允许删除未归档会话」依赖「永久删除会话」这条主开关。
|
|
1546
|
+
const lockedByParent = key === 'deleteUnarchived'
|
|
1547
|
+
&& (items.deleteSession === false || takenOver('deleteSession'));
|
|
1312
1548
|
const row = h('div', { className: 'dsrm-settings-row', key },
|
|
1313
1549
|
h('div', { className: 'dsrm-settings-copy' },
|
|
1314
1550
|
h('div', { className: 'dsrm-settings-label' }, t(labelKey)),
|
|
@@ -1316,9 +1552,12 @@ window.__ModuleLoader__.load({
|
|
|
1316
1552
|
),
|
|
1317
1553
|
h('div', { className: 'dsrm-settings-controls' },
|
|
1318
1554
|
jsx(Switch, {
|
|
1319
|
-
checked: items[key]
|
|
1320
|
-
disabled: takenOver(key),
|
|
1555
|
+
checked: items[key] === true,
|
|
1556
|
+
disabled: takenOver(key) || lockedByParent,
|
|
1321
1557
|
label: t(labelKey),
|
|
1558
|
+
title: lockedByParent
|
|
1559
|
+
? t('settings.childLocked', { name: t('settings.deleteSession.label') })
|
|
1560
|
+
: undefined,
|
|
1322
1561
|
onChange: (next) => setItem(key, next),
|
|
1323
1562
|
}),
|
|
1324
1563
|
key === 'openWith'
|
|
@@ -1339,10 +1578,36 @@ window.__ModuleLoader__.load({
|
|
|
1339
1578
|
return key === 'openWith' && childRows ? [row, childRows] : [row];
|
|
1340
1579
|
});
|
|
1341
1580
|
|
|
1581
|
+
const ellipsisGroups = ELLIPSIS_ROWS.map(([groupKey, titleKey, rows]) => h('div', {
|
|
1582
|
+
className: 'dsrm-settings-group',
|
|
1583
|
+
key: groupKey,
|
|
1584
|
+
},
|
|
1585
|
+
h('div', { className: 'dsrm-settings-group-title' }, t(titleKey)),
|
|
1586
|
+
h('div', { className: 'dsrm-settings-list' }, rows.map(([key, labelKey, hintKey]) => h('div', {
|
|
1587
|
+
className: 'dsrm-settings-row',
|
|
1588
|
+
key,
|
|
1589
|
+
},
|
|
1590
|
+
h('div', { className: 'dsrm-settings-copy' },
|
|
1591
|
+
h('div', { className: 'dsrm-settings-label' }, t(labelKey)),
|
|
1592
|
+
h('div', { className: 'dsrm-settings-hint' }, t(hintKey)),
|
|
1593
|
+
),
|
|
1594
|
+
h('div', { className: 'dsrm-settings-controls' },
|
|
1595
|
+
jsx(Switch, {
|
|
1596
|
+
checked: ellipsis[key] === true,
|
|
1597
|
+
label: t(labelKey),
|
|
1598
|
+
onChange: (next) => setEllipsis(key, next),
|
|
1599
|
+
}),
|
|
1600
|
+
),
|
|
1601
|
+
))),
|
|
1602
|
+
));
|
|
1603
|
+
|
|
1342
1604
|
return h('div', { className: 'dsrm-settings' },
|
|
1343
1605
|
h('h2', { className: 'dsrm-settings-title' }, t('settings.title')),
|
|
1344
1606
|
anyConflict ? h('div', { className: 'dsrm-settings-note' }, t('settings.conflictNote')) : null,
|
|
1345
1607
|
h('div', { className: 'dsrm-settings-list' }, rows),
|
|
1608
|
+
h('h2', { className: 'dsrm-settings-title dsrm-settings-subtitle' }, t('settings.ellipsisTitle')),
|
|
1609
|
+
h('div', { className: 'dsrm-settings-note' }, t('settings.ellipsisHint')),
|
|
1610
|
+
h('div', { className: 'dsrm-settings-groups' }, ellipsisGroups),
|
|
1346
1611
|
);
|
|
1347
1612
|
}
|
|
1348
1613
|
|
|
@@ -1352,16 +1617,20 @@ window.__ModuleLoader__.load({
|
|
|
1352
1617
|
* 工作区行右键菜单。官方 Menu 的 items 只认单层条目,不支持子菜单,
|
|
1353
1618
|
* 所以这里自绘 panel;普通行仍用 MenuItemButton,分隔线与菜单外观和官方一致。
|
|
1354
1619
|
*/
|
|
1355
|
-
function WorkspaceRowMenu({ x, y, props, onClose }) {
|
|
1620
|
+
function WorkspaceRowMenu({ x, y, props, onClose, mode = 'context' }) {
|
|
1356
1621
|
const t = localeOf(rootCtx);
|
|
1357
1622
|
const items = useItems();
|
|
1623
|
+
const ellipsis = useEllipsis();
|
|
1358
1624
|
const apps = useApps();
|
|
1359
1625
|
usePinned();
|
|
1360
1626
|
useChildren();
|
|
1361
1627
|
const hostRef = useRef(null);
|
|
1362
1628
|
const [pos, setPos] = useState(null);
|
|
1363
1629
|
|
|
1364
|
-
const
|
|
1630
|
+
const ellipsisMode = mode === 'ellipsis';
|
|
1631
|
+
/** 右键菜单永远显示;「…」菜单只在对应开关打开时显示(内置项除外)。 */
|
|
1632
|
+
const show = (key) => !ellipsisMode || ellipsis[key] === true;
|
|
1633
|
+
const openWithOn = items.openWith !== false && show('openWith');
|
|
1365
1634
|
|
|
1366
1635
|
useEffect(() => {
|
|
1367
1636
|
if (openWithOn) ensureApps();
|
|
@@ -1411,6 +1680,26 @@ window.__ModuleLoader__.load({
|
|
|
1411
1680
|
});
|
|
1412
1681
|
};
|
|
1413
1682
|
|
|
1683
|
+
const requestWorkspaceDelete = () => {
|
|
1684
|
+
if (workspaceId === '') {
|
|
1685
|
+
toast(t('actionFailed'));
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
const snapshot = serviceOf(rootCtx, 'workspaces')?.list?.getSnapshot?.();
|
|
1689
|
+
const entry = Array.isArray(snapshot?.items)
|
|
1690
|
+
? snapshot.items.find((candidate) => candidate?.workspaceId === workspaceId)
|
|
1691
|
+
: undefined;
|
|
1692
|
+
const sessionIds = Array.isArray(entry?.sessionIds)
|
|
1693
|
+
? entry.sessionIds.filter((id) => typeof id === 'string' && id !== '')
|
|
1694
|
+
: [];
|
|
1695
|
+
const title = typeof group.label === 'string' && group.label !== ''
|
|
1696
|
+
? group.label
|
|
1697
|
+
: typeof group.title === 'string' && group.title !== ''
|
|
1698
|
+
? group.title
|
|
1699
|
+
: cwd;
|
|
1700
|
+
requestDelete({ mode: 'workspace', workspaceId, sessionIds, displayTitle: title });
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1414
1703
|
const run = (id) => {
|
|
1415
1704
|
if (id === 'new-session') {
|
|
1416
1705
|
try {
|
|
@@ -1436,6 +1725,10 @@ window.__ModuleLoader__.load({
|
|
|
1436
1725
|
}
|
|
1437
1726
|
return;
|
|
1438
1727
|
}
|
|
1728
|
+
if (id === 'delete-sessions') {
|
|
1729
|
+
requestWorkspaceDelete();
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1439
1732
|
if (id === 'tab') {
|
|
1440
1733
|
Promise.resolve().then(() => tabs.openWorkspaceInTab(workspaceId)).catch((reason) => {
|
|
1441
1734
|
console.warn('[dsh-sidebar-row-menu] open in tab failed:', reason);
|
|
@@ -1453,7 +1746,7 @@ window.__ModuleLoader__.load({
|
|
|
1453
1746
|
};
|
|
1454
1747
|
|
|
1455
1748
|
const rows = [];
|
|
1456
|
-
if (typeof props.onCreate === 'function') {
|
|
1749
|
+
if (typeof props.onCreate === 'function' && show('newSession')) {
|
|
1457
1750
|
rows.push(jsx(MenuItemButton, {
|
|
1458
1751
|
key: 'new-session',
|
|
1459
1752
|
onSelect: () => {
|
|
@@ -1495,17 +1788,19 @@ window.__ModuleLoader__.load({
|
|
|
1495
1788
|
}));
|
|
1496
1789
|
}
|
|
1497
1790
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1791
|
+
if (show('copyPath')) {
|
|
1792
|
+
rows.push(jsx(MenuItemButton, {
|
|
1793
|
+
key: 'copy-path',
|
|
1794
|
+
separatorBefore: !openWithOn,
|
|
1795
|
+
onSelect: () => {
|
|
1796
|
+
onClose();
|
|
1797
|
+
run('copy-path');
|
|
1798
|
+
},
|
|
1799
|
+
icon: jsx(IconCopyOutlineRegular, { size: 14 }),
|
|
1800
|
+
children: t('copyPath'),
|
|
1801
|
+
}));
|
|
1802
|
+
}
|
|
1803
|
+
if (canOpenInTab && show('openInTab')) {
|
|
1509
1804
|
rows.push(jsx(MenuItemButton, {
|
|
1510
1805
|
key: 'tab',
|
|
1511
1806
|
onSelect: () => {
|
|
@@ -1527,6 +1822,18 @@ window.__ModuleLoader__.load({
|
|
|
1527
1822
|
icon: jsx(IconTrashOutlineRegular, {}),
|
|
1528
1823
|
children: t('deleteWorkspace'),
|
|
1529
1824
|
}));
|
|
1825
|
+
if (show('deleteSessions')) {
|
|
1826
|
+
rows.push(jsx(MenuItemButton, {
|
|
1827
|
+
key: 'delete-sessions',
|
|
1828
|
+
danger: true,
|
|
1829
|
+
onSelect: () => {
|
|
1830
|
+
onClose();
|
|
1831
|
+
run('delete-sessions');
|
|
1832
|
+
},
|
|
1833
|
+
icon: jsx(IconTrashOutlineRegular, {}),
|
|
1834
|
+
children: t('deleteWorkspaceSessions'),
|
|
1835
|
+
}));
|
|
1836
|
+
}
|
|
1530
1837
|
|
|
1531
1838
|
return h('div', {
|
|
1532
1839
|
className: 'dsrm-menu',
|
|
@@ -1539,8 +1846,8 @@ window.__ModuleLoader__.load({
|
|
|
1539
1846
|
}, h(MenuSurface, { role: 'group', className: 'dsrm-menu-surface' }, rows));
|
|
1540
1847
|
}
|
|
1541
1848
|
|
|
1542
|
-
/** 在 (x, y) 处挂出工作区菜单。 */
|
|
1543
|
-
function showWorkspaceMenu(props, x, y) {
|
|
1849
|
+
/** 在 (x, y) 处挂出工作区菜单。mode='ellipsis' 表示从行尾「…」按钮打开。 */
|
|
1850
|
+
function showWorkspaceMenu(props, x, y, mode = 'context') {
|
|
1544
1851
|
closeMenu();
|
|
1545
1852
|
menuHost = document.createElement('div');
|
|
1546
1853
|
menuHost.setAttribute(MENU_ATTR, '1');
|
|
@@ -1551,6 +1858,7 @@ window.__ModuleLoader__.load({
|
|
|
1551
1858
|
x,
|
|
1552
1859
|
y,
|
|
1553
1860
|
props,
|
|
1861
|
+
mode,
|
|
1554
1862
|
onClose: () => closeMenu(),
|
|
1555
1863
|
}));
|
|
1556
1864
|
}
|
|
@@ -1565,9 +1873,44 @@ window.__ModuleLoader__.load({
|
|
|
1565
1873
|
}
|
|
1566
1874
|
|
|
1567
1875
|
/** 工作区行右键菜单(没有 slot 可复用,只能自己组)。 */
|
|
1568
|
-
function openWorkspaceMenu(props, x, y) {
|
|
1876
|
+
function openWorkspaceMenu(props, x, y, mode = 'context') {
|
|
1569
1877
|
if (rootCtx === null) return;
|
|
1570
|
-
showWorkspaceMenu(props, x, y);
|
|
1878
|
+
showWorkspaceMenu(props, x, y, mode);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
/**
|
|
1882
|
+
* 行尾「…」按钮已经展开内置菜单时,用同一个按钮位置改挂本插件菜单。
|
|
1883
|
+
* 只在用户为工作区行开启了至少一个「…」附加项时接管;否则原生菜单原样工作。
|
|
1884
|
+
*/
|
|
1885
|
+
function onWorkspaceEllipsisClick(event) {
|
|
1886
|
+
if (event.defaultPrevented || event.button !== 0) return;
|
|
1887
|
+
if (event.target === null || event.target === undefined) return;
|
|
1888
|
+
if (rootCtx === null || !workspaceEllipsisOn()) return;
|
|
1889
|
+
const target = event.target;
|
|
1890
|
+
if (typeof target.closest !== 'function') return;
|
|
1891
|
+
const row = target.closest('[data-row-key]');
|
|
1892
|
+
if (row === null) return;
|
|
1893
|
+
const rowKey = row.getAttribute('data-row-key') ?? '';
|
|
1894
|
+
if (!rowKey.startsWith('workspace:')) return;
|
|
1895
|
+
const button = target.closest('button');
|
|
1896
|
+
if (button === null || !row.contains(button)) return;
|
|
1897
|
+
// 只接管行内第一个按钮(「…」);新建会话按钮带快捷键标记,直接放行。
|
|
1898
|
+
const buttons = row.querySelectorAll('button');
|
|
1899
|
+
if (buttons.length === 0 || buttons[0] !== button) return;
|
|
1900
|
+
if (button.getAttribute('aria-keyshortcuts') !== null) return;
|
|
1901
|
+
const props = resolveWorkspaceRow(target);
|
|
1902
|
+
if (props === null) return;
|
|
1903
|
+
event.preventDefault();
|
|
1904
|
+
event.stopPropagation();
|
|
1905
|
+
event.stopImmediatePropagation?.();
|
|
1906
|
+
// 再点一次「…」视为收起当前菜单。
|
|
1907
|
+
if (menuHost !== null && ellipsisTrigger === button) {
|
|
1908
|
+
closeMenu();
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
const rect = button.getBoundingClientRect();
|
|
1912
|
+
openWorkspaceMenu(props, rect.left + rect.width, rect.bottom + 4, 'ellipsis');
|
|
1913
|
+
ellipsisTrigger = button;
|
|
1571
1914
|
}
|
|
1572
1915
|
|
|
1573
1916
|
/* ------------------------------------------------ 会话菜单的插件条目 */
|
|
@@ -1586,7 +1929,7 @@ window.__ModuleLoader__.load({
|
|
|
1586
1929
|
/**
|
|
1587
1930
|
* 会话菜单条目:
|
|
1588
1931
|
* - 置顶应用 + 「打开方式」子菜单(官方 open-in-app 路由)
|
|
1589
|
-
* -
|
|
1932
|
+
* - 「永久删除会话」「删除所有已归档会话」(已归档会话显示;未归档会话需在设置里开启)
|
|
1590
1933
|
* 目录从客户端服务里现取 —— 这样不依赖行组件的内部闭包。
|
|
1591
1934
|
*/
|
|
1592
1935
|
function registerSessionMenu(ctx) {
|
|
@@ -1838,6 +2181,9 @@ window.__ModuleLoader__.load({
|
|
|
1838
2181
|
.dsrm-app-icon{width:14px;height:14px;border-radius:3px;display:block;object-fit:contain}
|
|
1839
2182
|
.dsrm-settings{max-width:820px;margin:0 auto;padding:2px 0 36px}
|
|
1840
2183
|
.dsrm-settings-title{margin:0;font-size:16px;line-height:24px;font-weight:600}
|
|
2184
|
+
.dsrm-settings-subtitle{margin-top:28px}
|
|
2185
|
+
.dsrm-settings-groups{margin-top:10px;display:flex;flex-direction:column;gap:18px}
|
|
2186
|
+
.dsrm-settings-group-title{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;font-weight:600;margin-bottom:2px}
|
|
1841
2187
|
.dsrm-settings-note{margin-top:8px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
1842
2188
|
.dsrm-settings-list{margin-top:10px;display:flex;flex-direction:column}
|
|
1843
2189
|
.dsrm-settings-row{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:14px 0;border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
@@ -1912,9 +2258,11 @@ window.__ModuleLoader__.load({
|
|
|
1912
2258
|
|
|
1913
2259
|
function install() {
|
|
1914
2260
|
window.addEventListener('contextmenu', onContextMenu, true);
|
|
2261
|
+
document.addEventListener('click', onWorkspaceEllipsisClick, true);
|
|
1915
2262
|
console.info('[dsh-sidebar-row-menu] installed');
|
|
1916
2263
|
return () => {
|
|
1917
2264
|
window.removeEventListener('contextmenu', onContextMenu, true);
|
|
2265
|
+
document.removeEventListener('click', onWorkspaceEllipsisClick, true);
|
|
1918
2266
|
closeMenu();
|
|
1919
2267
|
closeToast();
|
|
1920
2268
|
};
|