zcode-pro 0.20.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +8 -5
- package/README.md +7 -4
- package/dist/inject.js +1433 -434
- package/package.json +1 -1
- package/src/host/helper.mjs +147 -60
- package/src/host/main.mjs +7 -4
- package/src/host/taskIndex.mjs +97 -0
- package/src/host/version.mjs +51 -0
- package/src/inject/core.js +101 -18
- package/src/inject/features/alias-migration.js +470 -0
- package/src/inject/features/alias.js +6 -1
- package/src/inject/features/idle-reclaim.js +23 -48
- package/src/inject/features/pinned-collapse.js +4 -3
- package/src/inject/features/project-menu.js +209 -19
- package/src/inject/features/session-switch.js +6 -2
- package/src/inject/features/settings-dialog.js +88 -59
- package/src/inject/features/styles.js +8 -1
- package/src/inject/features/title-gen.js +41 -0
- package/src/inject/features/title-lock.js +20 -2
- package/src/inject/features/toolbar-icons.js +85 -45
- package/src/inject/features/ws-rows.js +62 -0
- package/src/inject/features/ws-running.js +384 -27
- package/src/inject/index.js +3 -2
- package/src/inject/ui.js +20 -11
- package/src/inject/features/pinned-expand.js +0 -142
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// 侧栏菜单并入顶栏:侧边栏顶部的“新建任务 / 搜索 / 自动化 / 插件市场”四项
|
|
2
2
|
// 收成顶部导航栏(后退/前进旁)的四个图标按钮,原菜单区域整体隐藏。
|
|
3
|
+
// 双结构(findParts):≤3.15.x 四项同在一个菜单容器;3.16.1 起新建任务/自动化/
|
|
4
|
+
// 插件市场在 sidebar-primary-navigation、搜索独立在 sidebar-search-toolbar,两处都隐藏。
|
|
3
5
|
// 悬停显示名称(带快捷键的项照带快捷键),按钮点亮表示对应界面正打开:
|
|
4
6
|
// 自动化/插件市场跟随原按钮的 aria-pressed,搜索跟随命令面板是否在场,
|
|
5
7
|
// 新建任务跟随“当前无选中会话”(即处于新建任务视图)。状态都从应用现状推导,
|
|
@@ -7,7 +9,7 @@
|
|
|
7
9
|
//(display:none 不影响事件派发,已实测),原生行为全部保留。
|
|
8
10
|
// 不移动应用的任何节点(避免与界面框架自身的增删冲突),自有按钮行追加在
|
|
9
11
|
// 导航按钮容器末尾——框架不排斥外来子节点;界面重渲染换新节点后由观察器重建。
|
|
10
|
-
import { h, t, getConfig } from '../core.js';
|
|
12
|
+
import { h, t, getConfig, isNewProjectSidebar } from '../core.js';
|
|
11
13
|
import { ensureStyle } from '../ui.js';
|
|
12
14
|
|
|
13
15
|
const HIDE_CLASS = 'zcodepro-tb-hidden';
|
|
@@ -16,31 +18,45 @@ const SCAN_FALLBACK_MS = 5000; // 兜底轮询周期:仅补观察器覆盖不
|
|
|
16
18
|
|
|
17
19
|
// 会话行(识别当前有无选中会话 → 新建任务态)
|
|
18
20
|
const ROW_SEL = '[data-task-item-key]';
|
|
19
|
-
//
|
|
21
|
+
// 与本功能相关的新增/删除节点:菜单各项、命令面板、顶栏导航按钮
|
|
22
|
+
//(sidebar-search-open 是 3.16.1 新结构的搜索入口)
|
|
20
23
|
const MENU_SEL = '[data-testid="task-new-button"], [data-testid="automations-open"],'
|
|
21
|
-
+ ' [data-testid="plugin-store-sidebar-open"], [data-
|
|
22
|
-
+ ' [data-testid="desktop-top-nav-back"]';
|
|
24
|
+
+ ' [data-testid="plugin-store-sidebar-open"], [data-testid="sidebar-search-open"],'
|
|
25
|
+
+ ' [data-slot="command"], [data-testid="desktop-top-nav-back"]';
|
|
23
26
|
|
|
24
27
|
let enabled = true; // 配置开关(features.toolbarIcons !== false)
|
|
25
28
|
let rowEl = null; // 顶栏自有按钮行
|
|
26
|
-
let origs = null; // {
|
|
29
|
+
let origs = null; // { hideEls, newTask, search, auto, plugin } 原元素引用
|
|
27
30
|
let syncTimer = 0;
|
|
28
31
|
let widthObserver = null; // 监听侧栏宽度变化(拖分隔条/缩放窗口)
|
|
29
32
|
let widthObserved = null;
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
// 找四项原按钮与要隐藏的容器。双结构:
|
|
35
|
+
// - 旧(≤3.15.x):四项同在一个菜单容器(task-new-button 的父级,验证含
|
|
36
|
+
// automations-open),隐藏该容器;搜索按钮无 testid,按 lucide-search 图标识别
|
|
37
|
+
// - 新(3.16.1 起):新建任务/自动化/插件市场在 sidebar-primary-navigation,
|
|
38
|
+
// 搜索独立在 sidebar-search-toolbar,两处容器都隐藏
|
|
39
|
+
function findParts() {
|
|
40
|
+
const newTask = document.querySelector('[data-testid="task-new-button"]');
|
|
41
|
+
if (!newTask) return null;
|
|
42
|
+
if (!isNewProjectSidebar()) {
|
|
43
|
+
const menu = newTask.parentElement;
|
|
44
|
+
if (!menu || !menu.querySelector('[data-testid="automations-open"]')) return null;
|
|
45
|
+
const search = menu.querySelector('button [class*="lucide-search"]')?.closest('button');
|
|
46
|
+
const auto = menu.querySelector('[data-testid="automations-open"]');
|
|
47
|
+
const plugin = menu.querySelector('[data-testid="plugin-store-sidebar-open"]');
|
|
48
|
+
return (search && auto && plugin) ? { newTask, search, auto, plugin, hideEls: [menu] } : null;
|
|
49
|
+
}
|
|
50
|
+
const search = document.querySelector('[data-testid="sidebar-search-open"]');
|
|
51
|
+
const auto = document.querySelector('[data-testid="automations-open"]');
|
|
52
|
+
const plugin = document.querySelector('[data-testid="plugin-store-sidebar-open"]');
|
|
53
|
+
if (!search || !auto || !plugin) return null;
|
|
54
|
+
const hideEls = [...new Set([
|
|
55
|
+
newTask.closest('[data-testid="sidebar-primary-navigation"]'),
|
|
56
|
+
auto.closest('[data-testid="sidebar-primary-navigation"]'),
|
|
57
|
+
search.closest('[data-testid="sidebar-search-toolbar"]'),
|
|
58
|
+
])].filter(Boolean);
|
|
59
|
+
return hideEls.length ? { newTask, search, auto, plugin, hideEls } : null;
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
// 名称从原按钮提取(跟随界面语言);提取不到再回退词典
|
|
@@ -74,6 +90,9 @@ function setActive(kind, on) {
|
|
|
74
90
|
if (kind === 'auto' || kind === 'plugin') btn.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
75
91
|
}
|
|
76
92
|
|
|
93
|
+
// 上一次选中的会话行键:分区折叠会把任务行(含选中行)整体卸载,此时侧栏
|
|
94
|
+
// “无选中行”不代表进入了新建视图(用户还在原会话里),不能据此点亮按钮
|
|
95
|
+
let lastSelectedKey = '';
|
|
77
96
|
function syncActive() {
|
|
78
97
|
if (!origs || !rowEl) return;
|
|
79
98
|
const auto = origs.auto.getAttribute('aria-pressed') === 'true';
|
|
@@ -82,26 +101,45 @@ function syncActive() {
|
|
|
82
101
|
// 当前会话的识别方式与“会话快捷切换”一致(侧栏条目的 bg-selected 标记)
|
|
83
102
|
let selected = false;
|
|
84
103
|
for (const el of document.querySelectorAll(ROW_SEL)) {
|
|
85
|
-
if ((el.className + '').includes('bg-selected')) {
|
|
104
|
+
if ((el.className + '').includes('bg-selected')) {
|
|
105
|
+
selected = true;
|
|
106
|
+
lastSelectedKey = el.getAttribute('data-task-item-key') || lastSelectedKey;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
86
109
|
}
|
|
87
110
|
// 新建任务态 = 无选中会话且无面板打开(自动化/插件市场面板会清掉会话选中,
|
|
88
|
-
//
|
|
89
|
-
|
|
111
|
+
// 不排除的话那些面板打开时本按钮会跟着误亮;命令面板是浮层不遮挡底层视图,不排除)。
|
|
112
|
+
// 无选中时分两种:上次选中的行仍在场(真的切到了新建视图)→ 点亮;
|
|
113
|
+
// 行不在场(所在分区折叠/卸载)→ 视图没变,保持按钮原状态
|
|
114
|
+
let newTaskOn = false;
|
|
115
|
+
if (!selected && lastSelectedKey) {
|
|
116
|
+
const stillMounted = document.querySelector(`${ROW_SEL}[data-task-item-key="${CSS.escape(lastSelectedKey)}"]`);
|
|
117
|
+
if (stillMounted) {
|
|
118
|
+
newTaskOn = !auto && !plugin;
|
|
119
|
+
lastSelectedKey = '';
|
|
120
|
+
} else {
|
|
121
|
+
const btn = rowEl?.querySelector('[data-kind="newTask"]');
|
|
122
|
+
newTaskOn = btn ? btn.getAttribute('data-active') === '1' : false; // 沿用当前显示
|
|
123
|
+
}
|
|
124
|
+
} else if (!selected) {
|
|
125
|
+
newTaskOn = !auto && !plugin;
|
|
126
|
+
}
|
|
127
|
+
setActive('newTask', newTaskOn);
|
|
90
128
|
setActive('search', palette);
|
|
91
129
|
setActive('auto', auto);
|
|
92
130
|
setActive('plugin', plugin);
|
|
93
131
|
}
|
|
94
132
|
|
|
95
133
|
function rebuild() {
|
|
96
|
-
const
|
|
97
|
-
if (!menu) return false;
|
|
98
|
-
const next = originalsOf(menu);
|
|
134
|
+
const parts = findParts();
|
|
99
135
|
const navRow = document.querySelector('[data-testid="desktop-top-nav-back"]')?.parentElement;
|
|
100
|
-
if (!
|
|
101
|
-
//
|
|
102
|
-
if (origs && origs.
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
if (!parts || !navRow) return false;
|
|
137
|
+
// 界面重渲染换了新节点:给旧容器摘掉隐藏类,新容器藏起来
|
|
138
|
+
if (origs && origs.hideEls[0] !== parts.hideEls[0]) {
|
|
139
|
+
for (const el of origs.hideEls) el.classList.remove(HIDE_CLASS);
|
|
140
|
+
}
|
|
141
|
+
for (const el of parts.hideEls) el.classList.add(HIDE_CLASS);
|
|
142
|
+
origs = parts;
|
|
105
143
|
rowEl?.remove();
|
|
106
144
|
// 原生顶栏新建按钮(文件子页会展开的那个)钉死收起:新建入口统一由我们
|
|
107
145
|
// 的行提供,省去随子页进出显隐自己按钮的同步
|
|
@@ -119,17 +157,17 @@ function rebuild() {
|
|
|
119
157
|
setTimeout(syncActive, 150);
|
|
120
158
|
},
|
|
121
159
|
});
|
|
122
|
-
const newBtn = mk('newTask',
|
|
123
|
-
const icon = iconOf(
|
|
160
|
+
const newBtn = mk('newTask', parts.newTask, 'span.truncate', L.tbNewTask);
|
|
161
|
+
const icon = iconOf(parts.newTask);
|
|
124
162
|
if (icon) newBtn.append(icon);
|
|
125
|
-
const searchBtn = mk('search',
|
|
126
|
-
const sIcon = iconOf(
|
|
163
|
+
const searchBtn = mk('search', parts.search, 'span.truncate', L.tbSearch);
|
|
164
|
+
const sIcon = iconOf(parts.search);
|
|
127
165
|
if (sIcon) searchBtn.append(sIcon);
|
|
128
|
-
const autoBtn = mk('auto',
|
|
129
|
-
const aIcon = iconOf(
|
|
166
|
+
const autoBtn = mk('auto', parts.auto, null, L.tbAutomations);
|
|
167
|
+
const aIcon = iconOf(parts.auto);
|
|
130
168
|
if (aIcon) autoBtn.append(aIcon);
|
|
131
|
-
const pluginBtn = mk('plugin',
|
|
132
|
-
const pIcon = iconOf(
|
|
169
|
+
const pluginBtn = mk('plugin', parts.plugin, null, L.tbPluginStore);
|
|
170
|
+
const pIcon = iconOf(parts.plugin);
|
|
133
171
|
if (pIcon) pluginBtn.append(pIcon);
|
|
134
172
|
rowEl = h('div', { class: 'zcodepro-tb-row' }, newBtn, searchBtn, autoBtn, pluginBtn);
|
|
135
173
|
// 插在容器内“更新”按钮之前(实测容器子序:侧栏切换/后退/前进/原生新建
|
|
@@ -159,7 +197,9 @@ function sidebarCollapsed() {
|
|
|
159
197
|
// 任何入口都不丢。宽度取侧栏滚动容器(侧栏打开时始终在场)
|
|
160
198
|
const NARROW_SIDEBAR_PX = 260;
|
|
161
199
|
function sidebarTooNarrow() {
|
|
162
|
-
|
|
200
|
+
// 旧结构的侧栏滚动容器(3.15.x)与新结构的(3.16.1 起,类名组合不同)都认
|
|
201
|
+
const scroll = document.querySelector('.flex.flex-1.min-h-0.flex-col.gap-3.overflow-y-auto')
|
|
202
|
+
|| document.querySelector('aside .min-h-0.flex-1.overflow-y-auto');
|
|
163
203
|
if (!scroll) return false;
|
|
164
204
|
// 顺带盯住宽度变化(容器重建后换目标重新观察)
|
|
165
205
|
if (!widthObserver) widthObserver = new ResizeObserver(() => scheduleSync());
|
|
@@ -173,25 +213,25 @@ function sidebarTooNarrow() {
|
|
|
173
213
|
|
|
174
214
|
function sync() {
|
|
175
215
|
if (!enabled) return;
|
|
176
|
-
const
|
|
216
|
+
const parts = findParts();
|
|
177
217
|
// 侧栏收起/未渲染/过窄:藏起图标行。过窄时还原原生菜单(收起时菜单本就不可见)
|
|
178
|
-
if (!
|
|
218
|
+
if (!parts) {
|
|
179
219
|
if (rowEl) rowEl.style.display = 'none';
|
|
180
220
|
return;
|
|
181
221
|
}
|
|
182
222
|
if (sidebarCollapsed() || sidebarTooNarrow()) {
|
|
183
223
|
if (rowEl) rowEl.style.display = 'none';
|
|
184
|
-
|
|
224
|
+
for (const el of parts.hideEls) el.classList.remove(HIDE_CLASS);
|
|
185
225
|
// 这两种形态藏起我们的整行:原生新建按钮要放出来(去掉钉死类),
|
|
186
226
|
// 否则收起侧栏时顶栏没有任何新建入口
|
|
187
227
|
document.querySelectorAll('.zcodepro-tb-hidden-native').forEach((el) => el.classList.remove('zcodepro-tb-hidden-native'));
|
|
188
228
|
return;
|
|
189
229
|
}
|
|
190
230
|
// 旧引用是否全部仍可用;不可用时先试重建,重建未就绪(React 分批挂载,
|
|
191
|
-
//
|
|
231
|
+
// 菜单/导航容器还没上屏)则保留现有的行原样等待重试——若在这里提前把旧行
|
|
192
232
|
// 拆掉,重试成功前图标会整行消失,只能等兜底轮询补上(表现为返回会话后
|
|
193
233
|
// 图标迟两三秒才回来)
|
|
194
|
-
const alive = origs && origs.
|
|
234
|
+
const alive = origs && origs.hideEls[0] === parts.hideEls[0]
|
|
195
235
|
&& origs.newTask.isConnected && origs.search.isConnected
|
|
196
236
|
&& origs.auto.isConnected && origs.plugin.isConnected
|
|
197
237
|
&& rowEl && rowEl.isConnected;
|
|
@@ -208,7 +248,7 @@ function sync() {
|
|
|
208
248
|
// 类同理(收起分支会摘掉它,展开回来若节点仍活着不会触发 rebuild,须在此补)
|
|
209
249
|
const navRow = document.querySelector('[data-testid="desktop-top-nav-back"]')?.parentElement;
|
|
210
250
|
if (navRow) navRow.classList.add('zcodepro-tb-hidden-native');
|
|
211
|
-
|
|
251
|
+
for (const el of parts.hideEls) el.classList.add(HIDE_CLASS);
|
|
212
252
|
if (rowEl) rowEl.style.display = '';
|
|
213
253
|
syncActive();
|
|
214
254
|
}
|
|
@@ -249,7 +289,7 @@ function teardown() {
|
|
|
249
289
|
rowEl?.remove();
|
|
250
290
|
rowEl = null;
|
|
251
291
|
if (origs) {
|
|
252
|
-
origs.
|
|
292
|
+
for (const el of origs.hideEls) el.classList.remove(HIDE_CLASS);
|
|
253
293
|
// 恢复原生子页新建按钮(功能关闭时不再钉死)
|
|
254
294
|
document.querySelectorAll('.zcodepro-tb-hidden-native').forEach((el) => el.classList.remove('zcodepro-tb-hidden-native'));
|
|
255
295
|
origs = null;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// 侧边栏项目行定位:同时兼容两种结构(按 isNewProjectSidebar 自动选择)。
|
|
2
|
+
// - 旧结构(3.15.x 及以前):项目行 testid 为 workspace-item-<绝对路径>;
|
|
3
|
+
// - 新结构(3.16.1 起“虚拟项目”):项目行为 section[data-project-id],不再携带
|
|
4
|
+
// 路径;本地项目的路径从区内任务行的 data-task-item-key 前缀取得(键格式
|
|
5
|
+
// “<工作区路径>:<任务id>”,本地工作区路径即项目主目录)。没有任务行的项目
|
|
6
|
+
// 取不到路径,只能靠调用方按名称兜底(helper /projects)。
|
|
7
|
+
import { isNewProjectSidebar } from '../core.js';
|
|
8
|
+
|
|
9
|
+
const norm = (p) => String(p || '').replace(/[\\/]+$/, '');
|
|
10
|
+
|
|
11
|
+
// 新结构项目行选择器(旧结构为 [data-testid^="workspace-item-"])
|
|
12
|
+
const NEW_ROW_SEL = 'section[data-project-id]';
|
|
13
|
+
const OLD_ROW_SEL = '[data-testid^="workspace-item-"]';
|
|
14
|
+
|
|
15
|
+
// 新结构:data-project-id → 工作区路径。任务行随折叠卸载,项目展开过一次才拿得到
|
|
16
|
+
// 路径;关联在会话内记住,之后按路径找行(session-switch、ws-running 等)不再依赖
|
|
17
|
+
// 该项目当前是否展开。
|
|
18
|
+
const pathByProjectId = new Map();
|
|
19
|
+
|
|
20
|
+
export function projectRowSelector() {
|
|
21
|
+
return isNewProjectSidebar() ? NEW_ROW_SEL : OLD_ROW_SEL;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function projectRows(root = document) {
|
|
25
|
+
return [...root.querySelectorAll(projectRowSelector())];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 项目行 → 本地项目路径;取不到返回 null。
|
|
29
|
+
// 远程项目分区(data-project-host 非 local)的任务键前缀是 identity 而非路径,
|
|
30
|
+
// 一律不返回路径——打开文件夹/复制路径/别名迁移都只应作用于本地项目。
|
|
31
|
+
// 新结构优先从在场任务行现算(官方“编辑项目”改过文件夹后路径会变),项目折叠、
|
|
32
|
+
// 任务行卸载时才用上次学到的缓存。
|
|
33
|
+
export function rowPath(row) {
|
|
34
|
+
if (!row) return null;
|
|
35
|
+
if (row.hasAttribute && row.hasAttribute('data-project-id')) {
|
|
36
|
+
const host = row.getAttribute('data-project-host');
|
|
37
|
+
if (host !== null && host !== 'local') return null;
|
|
38
|
+
const id = row.getAttribute('data-project-id');
|
|
39
|
+
let found = null;
|
|
40
|
+
for (const el of row.querySelectorAll('[data-task-item-key]')) {
|
|
41
|
+
const key = el.getAttribute('data-task-item-key') || '';
|
|
42
|
+
// lastIndexOf 兼容 Windows 盘符路径里的 ':'(C:\… 只在盘符处出现一次)
|
|
43
|
+
const cut = key.lastIndexOf(':');
|
|
44
|
+
if (cut > 1) { found = key.slice(0, cut); break; }
|
|
45
|
+
}
|
|
46
|
+
if (found) pathByProjectId.set(id, found);
|
|
47
|
+
return found || pathByProjectId.get(id) || null;
|
|
48
|
+
}
|
|
49
|
+
const testid = row.getAttribute && row.getAttribute('data-testid');
|
|
50
|
+
if (!testid || !testid.startsWith('workspace-item-')) return null;
|
|
51
|
+
return norm(testid.slice('workspace-item-'.length)) || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function findProjectRow(path) {
|
|
55
|
+
const want = norm(path);
|
|
56
|
+
if (!want) return null;
|
|
57
|
+
for (const row of projectRows()) {
|
|
58
|
+
const p = rowPath(row);
|
|
59
|
+
if (p && norm(p) === want) return row;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|