dsh-vscode-mode 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/lib/client.debug.js +934 -0
- package/lib/client.debug.js.map +1 -0
- package/lib/client.js +848 -1676
- package/lib/client.js.map +1 -1
- package/lib/client.rules.js +793 -0
- package/lib/client.rules.js.map +1 -0
- package/lib/index.js +236 -42
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +9 -0
- package/src/client/lazyPanel.ts +68 -0
- package/src/client/sidebar/PanelBoundary.ts +57 -0
- package/src/client/sidebar/SidebarView.ts +58 -21
- package/src/client/sidebar/panels/FileExplorer.ts +80 -27
- package/src/client/sidebar/panels/SvnPanel.ts +24 -8
- package/src/client/sidebar/panels/index.ts +3 -4
- package/src/client/sidebar/panels/treeWindow.ts +106 -0
- package/src/client/sidebar/panels/useExplorerReveal.ts +5 -2
- package/src/client/sidebar/registry.ts +4 -1
- package/src/client/styles/editor.css +8 -0
- package/src/client/textPages.ts +135 -0
- package/src/client/ui/EditorView.ts +71 -11
- package/src/client/ui/LargeTextView.ts +94 -0
- package/src/rpc.ts +29 -7
- package/src/shared/rpc.ts +3 -1
- package/src/shared/textPages.ts +28 -0
- package/src/textPages.ts +149 -0
- package/src/treeIndex.ts +62 -37
|
@@ -0,0 +1,793 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-vscode-mode",
|
|
3
|
+
chunk: "client.rules.js",
|
|
4
|
+
factory: (require) => {
|
|
5
|
+
var module = { exports: {} };
|
|
6
|
+
var exports = module.exports;
|
|
7
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
|
+
//#region \0rolldown/runtime.js
|
|
9
|
+
var __create = Object.create;
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
14
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
17
|
+
key = keys[i];
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
19
|
+
get: ((k) => from[k]).bind(null, key),
|
|
20
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return to;
|
|
24
|
+
};
|
|
25
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
26
|
+
value: mod,
|
|
27
|
+
enumerable: true
|
|
28
|
+
}) : target, mod));
|
|
29
|
+
//#endregion
|
|
30
|
+
let react = require("react");
|
|
31
|
+
react = __toESM(react, 1);
|
|
32
|
+
let dsh_vscode_mode = require("dsh-vscode-mode");
|
|
33
|
+
//#region \0panel-shared:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/rpc.ts
|
|
34
|
+
const rpc = dsh_vscode_mode.__lazyShared.rpc;
|
|
35
|
+
dsh_vscode_mode.__lazyShared.dbg;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/client/paths.ts
|
|
38
|
+
/**
|
|
39
|
+
* dsh-vscode-mode client — 浏览器端缓存键/路径统一出口(PathConst)。
|
|
40
|
+
* 统一 localStorage 键前缀(各缓存模块/编辑区/搜索面板共用),避免内联散落;
|
|
41
|
+
* 键带 schema 版本号,未来版本递增时经 sweepLegacyKeys 自动清旧。
|
|
42
|
+
* 浏览器不涉 ~/.dsh 文件系统,这里只管键名 + shared 路由常量。
|
|
43
|
+
* 作者 ddj 2026-09-01
|
|
44
|
+
*/
|
|
45
|
+
/** localStorage 键前缀表(按功能隔离;与 scopeStore 作用域键拼接:工作区优先,无 cwd 回退会话)。 */
|
|
46
|
+
const CACHE_KEY = {
|
|
47
|
+
/** 展开状态(explorerCache v1,当前使用)。 */
|
|
48
|
+
expanded: "edrv.cache.explorer.v1.",
|
|
49
|
+
/** 目录条目 SWR 缓存(explorerEntriesCache v2,当前使用)。 */
|
|
50
|
+
entries: "edrv.cache.entries.v2.",
|
|
51
|
+
/** 编辑区视图状态(viewStateCache v1)。 */
|
|
52
|
+
viewstate: "edrv.cache.viewstate.v1.",
|
|
53
|
+
/** 编辑器页签/活动文件(EditorView v3:页签带 pinned 固定标记)。 */
|
|
54
|
+
editor: "edrv.editor.v3.",
|
|
55
|
+
/** Multi-group split tree and floating window geometry. */
|
|
56
|
+
editorLayout: "edrv.editorLayout.v1.",
|
|
57
|
+
/** 旧编辑器页签键(v2:页签为纯路径数组)——仅用于一次性回读迁移,不再写入。 */
|
|
58
|
+
editorLegacy: "edrv.editor.v2.",
|
|
59
|
+
/** 侧边栏状态(EditorView;按布局 side/central 追加段)。 */
|
|
60
|
+
sidebar: "edrv.sidebar.",
|
|
61
|
+
/** 搜索面板条件(SearchPanel v1)。 */
|
|
62
|
+
search: "edrv.search.v1.",
|
|
63
|
+
/** 侧边栏提示已关闭标记(全局,无作用域)。 */
|
|
64
|
+
sideHint: "edrv.side-hint-dismissed",
|
|
65
|
+
/** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
|
|
66
|
+
workspaceFold: "edrv.ws-fold.v1.",
|
|
67
|
+
/** 规则面板 UI 状态(RulesPanel v1:Tab 与项目工作区选择,全局不按会话)。 */
|
|
68
|
+
rules: "edrv.rules.v1.",
|
|
69
|
+
/** SVN 变更面板 UI 状态(SvnPanel v1:未版本控制/忽略项显示开关,按作用域隔离)。 */
|
|
70
|
+
svn: "edrv.svn.v1.",
|
|
71
|
+
/** Markdown 预览态(EditorView v1:处于预览态的 Markdown 路径集合,按作用域隔离)。 */
|
|
72
|
+
mdPreview: "edrv.md-preview.v1.",
|
|
73
|
+
/** 官方侧边栏「文件编辑」Tab 展开/激活态(officialSidebar v1:重启后自动回到编辑 Tab)。 */
|
|
74
|
+
editorTab: "edrv.editor-tab.v1.",
|
|
75
|
+
/** 编辑器导航历史双栈(navStateCache v1:重启后前进/后退与当前位置仍在,按作用域隔离)。 */
|
|
76
|
+
navHistory: "edrv.nav-history.v1."
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/client/rulesMdc.ts
|
|
80
|
+
/**
|
|
81
|
+
* 去除值两侧成对引号(与 host stripQuotes 一致)。
|
|
82
|
+
* @author ddj 2026年09月07号
|
|
83
|
+
* @param raw 原始值
|
|
84
|
+
* @returns 去引号后的值
|
|
85
|
+
*/
|
|
86
|
+
function stripQuotes(raw) {
|
|
87
|
+
const value = raw.trim();
|
|
88
|
+
if (value.length >= 2 && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 解析 .mdc 全文:首行 --- 且 100 行内有闭合 --- 才视为 frontmatter;
|
|
93
|
+
* 已知键 description/alwaysApply/globs/enabled,其余原行进 fmExtra;永不抛错。
|
|
94
|
+
* @author ddj 2026年09月07号
|
|
95
|
+
* @param text 文件全文
|
|
96
|
+
* @returns 解析结果
|
|
97
|
+
*/
|
|
98
|
+
function parseRuleFm(text) {
|
|
99
|
+
const bom = text.startsWith("");
|
|
100
|
+
const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
101
|
+
const none = {
|
|
102
|
+
type: "manual",
|
|
103
|
+
description: "",
|
|
104
|
+
alwaysApply: false,
|
|
105
|
+
globs: [],
|
|
106
|
+
enabled: true,
|
|
107
|
+
hasFm: false,
|
|
108
|
+
body: text.replace(/^\uFEFF/, ""),
|
|
109
|
+
fmExtra: [],
|
|
110
|
+
bom,
|
|
111
|
+
hasEnabledLine: false
|
|
112
|
+
};
|
|
113
|
+
if (lines[0]?.trim() !== "---") return none;
|
|
114
|
+
let close = -1;
|
|
115
|
+
for (let i = 1; i < lines.length && i <= 101; i++) if (lines[i].trim() === "---") {
|
|
116
|
+
close = i;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
if (close < 0) return none;
|
|
120
|
+
const parsed = {
|
|
121
|
+
type: "manual",
|
|
122
|
+
description: "",
|
|
123
|
+
alwaysApply: false,
|
|
124
|
+
globs: [],
|
|
125
|
+
enabled: true,
|
|
126
|
+
hasFm: true,
|
|
127
|
+
body: lines.slice(close + 1).join("\n"),
|
|
128
|
+
fmExtra: [],
|
|
129
|
+
bom,
|
|
130
|
+
hasEnabledLine: false
|
|
131
|
+
};
|
|
132
|
+
for (let i = 1; i < close; i++) {
|
|
133
|
+
const match = /^([A-Za-z_-]+)[ \t]*:[ \t]?(.*)$/.exec(lines[i]);
|
|
134
|
+
if (!match) {
|
|
135
|
+
parsed.fmExtra.push(lines[i]);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const [, key, raw] = match;
|
|
139
|
+
if (key === "description") parsed.description = stripQuotes(raw);
|
|
140
|
+
else if (key === "alwaysApply") parsed.alwaysApply = stripQuotes(raw).toLowerCase() === "true";
|
|
141
|
+
else if (key === "enabled") {
|
|
142
|
+
parsed.enabled = stripQuotes(raw).toLowerCase() !== "false";
|
|
143
|
+
parsed.hasEnabledLine = true;
|
|
144
|
+
} else if (key === "globs") parsed.globs = parseGlobsValue(raw, lines, i, close);
|
|
145
|
+
else parsed.fmExtra.push(lines[i]);
|
|
146
|
+
}
|
|
147
|
+
parsed.type = fmTypeOf(parsed);
|
|
148
|
+
return parsed;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 解析 globs 值:内联逗号分隔串,或空值后接 YAML 短横列表(与 host parseGlobs 一致)。
|
|
152
|
+
* @author ddj 2026年09月07号
|
|
153
|
+
* @param value 内联值
|
|
154
|
+
* @param lines 全文行
|
|
155
|
+
* @param start globs 行下标
|
|
156
|
+
* @param close 闭合 --- 下标
|
|
157
|
+
* @returns glob 数组
|
|
158
|
+
*/
|
|
159
|
+
function parseGlobsValue(value, lines, start, close) {
|
|
160
|
+
const inline = stripQuotes(value);
|
|
161
|
+
if (inline) return inline.split(",").map((item) => stripQuotes(item)).filter(Boolean);
|
|
162
|
+
const list = [];
|
|
163
|
+
for (let j = start + 1; j < close; j++) {
|
|
164
|
+
const item = /^[ \t]*-[ \t]*(.+)$/.exec(lines[j]);
|
|
165
|
+
if (!item) break;
|
|
166
|
+
list.push(stripQuotes(item[1]));
|
|
167
|
+
}
|
|
168
|
+
return list;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 由 frontmatter 推导类型:alwaysApply=true → 总是;有 globs → 自动;否则手动。
|
|
172
|
+
* @author ddj 2026年09月07号
|
|
173
|
+
* @param parsed 解析结果
|
|
174
|
+
* @returns 规则类型
|
|
175
|
+
*/
|
|
176
|
+
function fmTypeOf(parsed) {
|
|
177
|
+
if (parsed.alwaysApply) return "always";
|
|
178
|
+
if (parsed.globs.length) return "auto";
|
|
179
|
+
return "manual";
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 按最新控件值重写 frontmatter:known 键固定顺序 + fmExtra + 闭合 --- + 原 body;
|
|
183
|
+
* 无 frontmatter 且目标为手动时:有描述则新建仅含描述的 frontmatter,否则原文返回。
|
|
184
|
+
* @author ddj 2026年09月07号
|
|
185
|
+
* @param parsed parseRuleFm 的结果(必须来自同一文本)
|
|
186
|
+
* @param next 控件最新值
|
|
187
|
+
* @returns 重写后的全文
|
|
188
|
+
*/
|
|
189
|
+
function applyRuleMeta(parsed, next) {
|
|
190
|
+
if (!parsed.hasFm && next.type === "manual") {
|
|
191
|
+
if (!next.description.trim()) return (parsed.bom ? "" : "") + parsed.body;
|
|
192
|
+
return (parsed.bom ? "" : "") + [
|
|
193
|
+
"---",
|
|
194
|
+
"description: " + next.description.trim(),
|
|
195
|
+
"---"
|
|
196
|
+
].join("\n") + "\n" + parsed.body;
|
|
197
|
+
}
|
|
198
|
+
const lines = ["---"];
|
|
199
|
+
lines.push("description: " + next.description.trim());
|
|
200
|
+
if (next.type === "always") lines.push("alwaysApply: true");
|
|
201
|
+
if (next.type === "auto") lines.push("globs: " + next.globs.join(", "));
|
|
202
|
+
if (parsed.hasEnabledLine) lines.push("enabled: " + (parsed.enabled ? "true" : "false"));
|
|
203
|
+
lines.push(...parsed.fmExtra);
|
|
204
|
+
lines.push("---");
|
|
205
|
+
return (parsed.bom ? "" : "") + lines.join("\n") + "\n" + parsed.body;
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/client/sidebar/panels/RulesPanel.ts
|
|
209
|
+
/**
|
|
210
|
+
* dsh-vscode-mode client — 侧边栏「规则」面板(Codebuddy 规则管理形态)。
|
|
211
|
+
* 双 Tab:用户规则(~/.dsh/rules/)与项目规则(<工作区>/.dsh/rules/);
|
|
212
|
+
* 列表行 = 文件名 + 相对提示,第二行类型徽标与描述,右侧 编辑/删除 图标 + 滑动启用开关;
|
|
213
|
+
* 新建/编辑 = 顶部面包屑 + 规则类型下拉(总是/自动/手动)+ 描述/globs 控件,与原始 .mdc
|
|
214
|
+
* 文本域双向同步(控件改写 frontmatter,文本域回读控件),保存走 rules.save。
|
|
215
|
+
* 作者 ddj 2026年09月03号(2026年09月07号 交互性调整)
|
|
216
|
+
*/
|
|
217
|
+
/** 新建规则模板(frontmatter 字段与 host parseRuleMdc 对齐)。 */
|
|
218
|
+
const NEW_TEMPLATE = "---\ndescription: \nalwaysApply: true\n---\n\n";
|
|
219
|
+
const TYPE_LABEL = {
|
|
220
|
+
always: "总是",
|
|
221
|
+
auto: "自动",
|
|
222
|
+
manual: "手动"
|
|
223
|
+
};
|
|
224
|
+
/** 列表行描述的基础显示上限(字符数;宽面板下的封顶值,超出省略号)。 */
|
|
225
|
+
const DESC_MAX = 24;
|
|
226
|
+
/** 动态字数下限(极窄面板的保底可见字符数)。 */
|
|
227
|
+
const DESC_CAP_MIN = 8;
|
|
228
|
+
/** 动态字数估算:描述之外的行内固定占用 px(动作区/类型标签/徽标/间距/省略余量)。 */
|
|
229
|
+
const DESC_RESERVE_PX = 210;
|
|
230
|
+
/** 动态字数估算:单字符平均宽度 px(11px 字号,中英混排)。 */
|
|
231
|
+
const DESC_CHAR_PX = 10;
|
|
232
|
+
/** 窄面板阈值:低于此宽度隐藏路径提示(编辑界面面包屑与悬停仍有完整路径)。 */
|
|
233
|
+
const NARROW_W = 420;
|
|
234
|
+
/**
|
|
235
|
+
* 读 localStorage(损坏/不可用安全)。
|
|
236
|
+
* @author ddj 2026年09月03号
|
|
237
|
+
* @param key 键
|
|
238
|
+
* @returns 值或 null
|
|
239
|
+
*/
|
|
240
|
+
function readLocal(key) {
|
|
241
|
+
try {
|
|
242
|
+
return localStorage.getItem(key);
|
|
243
|
+
} catch (e) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 写 localStorage(不可用安全)。
|
|
249
|
+
* @author ddj 2026年09月03号
|
|
250
|
+
* @param key 键
|
|
251
|
+
* @param value 值
|
|
252
|
+
*/
|
|
253
|
+
function writeLocal(key, value) {
|
|
254
|
+
try {
|
|
255
|
+
localStorage.setItem(key, value);
|
|
256
|
+
} catch (e) {}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 类型徽标(总是=绿 / 自动=蓝 / 手动=灰)。
|
|
260
|
+
* @author ddj 2026年09月03号
|
|
261
|
+
* @param type 规则类型
|
|
262
|
+
* @returns 徽标元素
|
|
263
|
+
*/
|
|
264
|
+
function typeBadge(type) {
|
|
265
|
+
return react.default.createElement("span", { className: "edrv-rules-type edrv-rules-type-" + type }, TYPE_LABEL[type] ?? type);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* 按字符上限截断列表行描述,超出以省略号收尾(上限随面板宽度动态变化;
|
|
269
|
+
* 窄面板下 CSS 省略号仍会按像素提前截断,二者取更小)。
|
|
270
|
+
* @author ddj 2026年09月07号
|
|
271
|
+
* @param text 完整描述
|
|
272
|
+
* @param cap 本帧字符上限
|
|
273
|
+
* @returns 截断后的展示文本
|
|
274
|
+
*/
|
|
275
|
+
function clampDesc(text, cap) {
|
|
276
|
+
const chars = Array.from(String(text ?? ""));
|
|
277
|
+
const limit = Math.max(4, Number(cap) || DESC_MAX);
|
|
278
|
+
return chars.length > limit ? chars.slice(0, limit).join("") + "…" : chars.join("");
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* 由面板宽度估算描述可用字符数:随宽度动态变化,并与基础上限取小、极窄时保底。
|
|
282
|
+
* @author ddj 2026年09月07号
|
|
283
|
+
* @param panelWidth 面板内容宽度(px)
|
|
284
|
+
* @returns 本帧描述字符上限
|
|
285
|
+
*/
|
|
286
|
+
function descCapOf(panelWidth) {
|
|
287
|
+
const byWidth = Math.floor((Number(panelWidth) - DESC_RESERVE_PX) / DESC_CHAR_PX);
|
|
288
|
+
return Math.max(DESC_CAP_MIN, Math.min(DESC_MAX, byWidth));
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 编辑图标(线性铅笔,与 DiffBox chevron 同风格:currentColor 描边装饰图)。
|
|
292
|
+
* @author ddj 2026年09月07号
|
|
293
|
+
* @returns 13×13 装饰性 SVG
|
|
294
|
+
*/
|
|
295
|
+
function pencilIcon() {
|
|
296
|
+
return react.default.createElement("svg", {
|
|
297
|
+
width: 13,
|
|
298
|
+
height: 13,
|
|
299
|
+
viewBox: "0 0 24 24",
|
|
300
|
+
fill: "none",
|
|
301
|
+
"aria-hidden": true,
|
|
302
|
+
focusable: false
|
|
303
|
+
}, react.default.createElement("path", {
|
|
304
|
+
d: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z",
|
|
305
|
+
stroke: "currentColor",
|
|
306
|
+
strokeWidth: 2,
|
|
307
|
+
strokeLinecap: "round",
|
|
308
|
+
strokeLinejoin: "round"
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* 删除图标(线性垃圾桶,风格同 pencilIcon)。
|
|
313
|
+
* @author ddj 2026年09月07号
|
|
314
|
+
* @returns 13×13 装饰性 SVG
|
|
315
|
+
*/
|
|
316
|
+
function trashIcon() {
|
|
317
|
+
return react.default.createElement("svg", {
|
|
318
|
+
width: 13,
|
|
319
|
+
height: 13,
|
|
320
|
+
viewBox: "0 0 24 24",
|
|
321
|
+
fill: "none",
|
|
322
|
+
"aria-hidden": true,
|
|
323
|
+
focusable: false
|
|
324
|
+
}, react.default.createElement("path", {
|
|
325
|
+
d: "M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14zM10 11v6M14 11v6",
|
|
326
|
+
stroke: "currentColor",
|
|
327
|
+
strokeWidth: 2,
|
|
328
|
+
strokeLinecap: "round",
|
|
329
|
+
strokeLinejoin: "round"
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* 逗号分隔的 globs 输入串 → 数组(去空项)。
|
|
334
|
+
* @author ddj 2026年09月07号
|
|
335
|
+
* @param text 输入串
|
|
336
|
+
* @returns glob 数组
|
|
337
|
+
*/
|
|
338
|
+
function globList(text) {
|
|
339
|
+
return String(text ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* 由控件值重建 .mdc 全文:frontmatter 按类型/描述/globs 重写,正文与其余键不动。
|
|
343
|
+
* @author ddj 2026年09月07号
|
|
344
|
+
* @param base 当前全文
|
|
345
|
+
* @param fmType 目标类型(always/auto/manual)
|
|
346
|
+
* @param desc 描述文本
|
|
347
|
+
* @param globsText globs 输入串
|
|
348
|
+
* @returns 重写后的全文
|
|
349
|
+
*/
|
|
350
|
+
function rebuildContent(base, fmType, desc, globsText) {
|
|
351
|
+
return applyRuleMeta(parseRuleFm(base), {
|
|
352
|
+
type: fmType,
|
|
353
|
+
description: desc,
|
|
354
|
+
globs: globList(globsText)
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* 渲染一条规则行:左侧两行(文件名+相对提示 / 类型徽标+描述),右侧 编辑/删除 图标 + 滑动开关。
|
|
359
|
+
* @author ddj 2026年09月03号
|
|
360
|
+
* @param props.rule 规则元信息
|
|
361
|
+
* @param props.onEdit 编辑回调
|
|
362
|
+
* @param props.onRemove 删除回调
|
|
363
|
+
* @param props.onToggle 开关回调
|
|
364
|
+
* @returns 行元素
|
|
365
|
+
*/
|
|
366
|
+
function RuleRow(props) {
|
|
367
|
+
const rule = props.rule;
|
|
368
|
+
const desc = [rule.error ? "解析失败:" + rule.error : null, rule.description || (rule.type === "auto" ? "globs: " + rule.globs.join(", ") : "")].filter(Boolean).join(" · ");
|
|
369
|
+
return react.default.createElement("div", { className: "edrv-rules-row" + (rule.enabled ? "" : " off") }, react.default.createElement("div", {
|
|
370
|
+
className: "edrv-rules-main",
|
|
371
|
+
onClick: () => props.onEdit(rule),
|
|
372
|
+
title: rule.absPath
|
|
373
|
+
}, react.default.createElement("div", { className: "edrv-rules-line1" }, react.default.createElement("span", { className: "edrv-rules-file" }, rule.file), !props.narrow ? react.default.createElement("span", { className: "edrv-rules-path" }, "[" + rule.relHint + rule.file + "]") : null, props.narrow && rule.scope === "user" && rule.origin === "legacy" ? react.default.createElement("span", {
|
|
374
|
+
className: "edrv-rules-path",
|
|
375
|
+
title: rule.absPath
|
|
376
|
+
}, "旧目录") : null, !rule.enabled ? react.default.createElement("span", { className: "edrv-rules-offtag" }, "已停用") : null), react.default.createElement("div", { className: "edrv-rules-sub" }, react.default.createElement("span", { className: "edrv-rules-sublabel" }, "类型:"), typeBadge(rule.type), desc ? react.default.createElement("span", {
|
|
377
|
+
className: "edrv-rules-desc",
|
|
378
|
+
title: desc
|
|
379
|
+
}, clampDesc(desc, props.descCap)) : null)), react.default.createElement("div", { className: "edrv-rules-actions" }, react.default.createElement("button", {
|
|
380
|
+
className: "edrv-rules-act",
|
|
381
|
+
title: "编辑",
|
|
382
|
+
onClick: () => props.onEdit(rule)
|
|
383
|
+
}, pencilIcon()), react.default.createElement("button", {
|
|
384
|
+
className: "edrv-rules-act",
|
|
385
|
+
title: "删除",
|
|
386
|
+
onClick: () => props.onRemove(rule)
|
|
387
|
+
}, trashIcon()), react.default.createElement("button", {
|
|
388
|
+
className: "edrv-rules-switch" + (rule.enabled ? " on" : ""),
|
|
389
|
+
role: "switch",
|
|
390
|
+
"aria-checked": rule.enabled,
|
|
391
|
+
title: rule.enabled ? "已启用(点击停用)" : "已停用(点击启用)",
|
|
392
|
+
onClick: () => props.onToggle(rule)
|
|
393
|
+
}, react.default.createElement("span", { className: "edrv-rules-knob" }))));
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* 新建/编辑表单:面包屑头部 + 规则类型下拉/描述/globs 控件行 + 原始 .mdc 文本域 + 保存/取消。
|
|
397
|
+
* @author ddj 2026年09月03号
|
|
398
|
+
* @param props.form 表单状态(mode/scope/workspacePath/relHint/file/content/fmType/desc/globs/error/saving)
|
|
399
|
+
* @param props.onType 类型下拉变更回调
|
|
400
|
+
* @param props.onDesc 描述输入回调
|
|
401
|
+
* @param props.onGlobs globs 输入回调
|
|
402
|
+
* @param props.onBody 文本域编辑回调
|
|
403
|
+
* @param props.onSave 保存回调
|
|
404
|
+
* @param props.onCancel 取消回调
|
|
405
|
+
* @returns 表单元素
|
|
406
|
+
*/
|
|
407
|
+
function RuleEditor(props) {
|
|
408
|
+
const form = props.form;
|
|
409
|
+
const isNew = form.mode === "new";
|
|
410
|
+
const crumb = (form.scope === "project" ? "项目规则" : "用户规则") + " > " + form.relHint + " > " + (form.file || "新建规则.mdc");
|
|
411
|
+
return react.default.createElement("div", { className: "edrv-rules-editor" }, react.default.createElement("div", { className: "edrv-rules-editor-head" }, react.default.createElement("span", {
|
|
412
|
+
className: "edrv-rules-crumb",
|
|
413
|
+
title: crumb
|
|
414
|
+
}, crumb), react.default.createElement("button", {
|
|
415
|
+
className: "edrv-rules-act",
|
|
416
|
+
title: "取消",
|
|
417
|
+
onClick: props.onCancel
|
|
418
|
+
}, "✕")), react.default.createElement("div", { className: "edrv-rules-editor-ctl" }, react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "规则类型"), react.default.createElement("select", {
|
|
419
|
+
className: "edrv-rules-select",
|
|
420
|
+
value: form.fmType,
|
|
421
|
+
onChange: props.onType
|
|
422
|
+
}, Object.keys(TYPE_LABEL).map((key) => react.default.createElement("option", {
|
|
423
|
+
key,
|
|
424
|
+
value: key
|
|
425
|
+
}, TYPE_LABEL[key]))), react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "描述"), react.default.createElement("input", {
|
|
426
|
+
className: "edrv-rules-input edrv-rules-desc-input",
|
|
427
|
+
value: form.desc,
|
|
428
|
+
spellCheck: false,
|
|
429
|
+
placeholder: isNew ? "规则用途说明(写入 frontmatter description)" : "规则用途说明",
|
|
430
|
+
onChange: props.onDesc
|
|
431
|
+
})), form.fmType === "auto" ? react.default.createElement("div", { className: "edrv-rules-editor-ctl" }, react.default.createElement("label", { className: "edrv-rules-ctl-label" }, "globs"), react.default.createElement("input", {
|
|
432
|
+
className: "edrv-rules-input",
|
|
433
|
+
value: form.globs,
|
|
434
|
+
spellCheck: false,
|
|
435
|
+
placeholder: "src/**/*.ts, *.md(逗号分隔,命中才注入)",
|
|
436
|
+
onChange: props.onGlobs
|
|
437
|
+
})) : null, react.default.createElement("textarea", {
|
|
438
|
+
className: "edrv-rules-body",
|
|
439
|
+
spellCheck: false,
|
|
440
|
+
value: form.content,
|
|
441
|
+
onChange: props.onBody
|
|
442
|
+
}), react.default.createElement("div", { className: "edrv-rules-editor-hint" }, "类型/描述/globs 与 frontmatter 双向同步;enabled 与其余键原样保留;无 frontmatter 的纯 markdown 视为「手动」规则"), form.error ? react.default.createElement("div", { className: "edrv-rules-err" }, form.error) : null, react.default.createElement("div", { className: "edrv-rules-editor-foot" }, react.default.createElement("button", {
|
|
443
|
+
className: "edrv-rules-save",
|
|
444
|
+
disabled: form.saving,
|
|
445
|
+
onClick: props.onSave
|
|
446
|
+
}, form.saving ? "保存中…" : "保存")));
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 规则面板主体。
|
|
450
|
+
* @param props.ctx 面板共享上下文(notify 可选)
|
|
451
|
+
*/
|
|
452
|
+
function RulesPanel(props) {
|
|
453
|
+
const ctx = props?.ctx;
|
|
454
|
+
const notify = ctx?.notify ?? ((message) => {});
|
|
455
|
+
const cwd = ctx?.cwd ?? null;
|
|
456
|
+
const [tab, setTab] = react.default.useState(() => readLocal(CACHE_KEY.rules + "tab") === "project" ? "project" : "user");
|
|
457
|
+
const [data, setData] = react.default.useState(null);
|
|
458
|
+
const [error, setError] = react.default.useState("");
|
|
459
|
+
const panelRef = react.default.useRef(null);
|
|
460
|
+
const [panelW, setPanelW] = react.default.useState(0);
|
|
461
|
+
const [form, setForm] = react.default.useState(null);
|
|
462
|
+
const mountedRef = react.default.useRef(true);
|
|
463
|
+
/**
|
|
464
|
+
* 当前工作区对应的项目聚合(按 ctx.cwd 自动匹配;无会话/未注册返回 null)。
|
|
465
|
+
* @returns RuleProject 或 null
|
|
466
|
+
*/
|
|
467
|
+
const activeProject = () => {
|
|
468
|
+
if (!cwd) return null;
|
|
469
|
+
return (data?.projects ?? []).find((p) => p.workspacePath === cwd) ?? null;
|
|
470
|
+
};
|
|
471
|
+
/** 拉取规则总列表(rules.list)。 */
|
|
472
|
+
const load = () => {
|
|
473
|
+
rpc("rules.list", {}).then((res) => {
|
|
474
|
+
if (!mountedRef.current) return;
|
|
475
|
+
if (res && res.ok) {
|
|
476
|
+
setData(res);
|
|
477
|
+
setError("");
|
|
478
|
+
} else setError(res?.error ?? "读取规则失败");
|
|
479
|
+
}).catch((e) => {
|
|
480
|
+
if (mountedRef.current) setError("读取规则异常: " + String(e));
|
|
481
|
+
});
|
|
482
|
+
};
|
|
483
|
+
react.default.useEffect(() => {
|
|
484
|
+
mountedRef.current = true;
|
|
485
|
+
load();
|
|
486
|
+
return () => {
|
|
487
|
+
mountedRef.current = false;
|
|
488
|
+
};
|
|
489
|
+
}, []);
|
|
490
|
+
react.default.useEffect(() => {
|
|
491
|
+
const el = panelRef.current;
|
|
492
|
+
if (!el || typeof ResizeObserver === "undefined") return void 0;
|
|
493
|
+
const observer = new ResizeObserver(() => setPanelW(el.clientWidth));
|
|
494
|
+
observer.observe(el);
|
|
495
|
+
setPanelW(el.clientWidth);
|
|
496
|
+
return () => observer.disconnect();
|
|
497
|
+
}, []);
|
|
498
|
+
const descCap = panelW > 0 ? descCapOf(panelW) : DESC_MAX;
|
|
499
|
+
const narrow = panelW > 0 && panelW < NARROW_W;
|
|
500
|
+
/**
|
|
501
|
+
* 切换 Tab(记忆到 localStorage)。
|
|
502
|
+
* @param next 目标 Tab
|
|
503
|
+
*/
|
|
504
|
+
const switchTab = (next) => {
|
|
505
|
+
setTab(next);
|
|
506
|
+
writeLocal(CACHE_KEY.rules + "tab", next);
|
|
507
|
+
};
|
|
508
|
+
/**
|
|
509
|
+
* 打开新建表单(按当前 Tab 决定作用域;项目规则跟随当前会话工作区)。
|
|
510
|
+
*/
|
|
511
|
+
const openNew = () => {
|
|
512
|
+
const scope = tab === "project" ? "project" : "user";
|
|
513
|
+
if (scope === "project" && !cwd) {
|
|
514
|
+
notify("当前会话没有工作区,无法新建项目规则");
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const parsed = parseRuleFm(NEW_TEMPLATE);
|
|
518
|
+
setForm({
|
|
519
|
+
mode: "new",
|
|
520
|
+
scope,
|
|
521
|
+
origin: scope === "user" ? "current" : void 0,
|
|
522
|
+
workspacePath: scope === "project" ? cwd : void 0,
|
|
523
|
+
relHint: scope === "project" ? ".dsh/rules/" : "rules/",
|
|
524
|
+
file: "",
|
|
525
|
+
content: NEW_TEMPLATE,
|
|
526
|
+
fmType: parsed.type,
|
|
527
|
+
desc: parsed.description,
|
|
528
|
+
globs: parsed.globs.join(", "),
|
|
529
|
+
error: "",
|
|
530
|
+
saving: false
|
|
531
|
+
});
|
|
532
|
+
};
|
|
533
|
+
/**
|
|
534
|
+
* 打开编辑表单:先拉全文,再解析 frontmatter 回填类型/描述/globs 控件。
|
|
535
|
+
* @param rule 目标规则
|
|
536
|
+
*/
|
|
537
|
+
const openEdit = (rule) => {
|
|
538
|
+
rpc("rules.read", {
|
|
539
|
+
scope: rule.scope,
|
|
540
|
+
origin: rule.origin,
|
|
541
|
+
workspacePath: rule.scope === "project" ? rule.workspacePath : void 0,
|
|
542
|
+
file: rule.file
|
|
543
|
+
}).then((res) => {
|
|
544
|
+
if (!res || !res.ok) {
|
|
545
|
+
notify(res?.error ?? "读取规则失败");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const parsed = parseRuleFm(res.content);
|
|
549
|
+
setForm({
|
|
550
|
+
mode: "edit",
|
|
551
|
+
scope: rule.scope,
|
|
552
|
+
origin: rule.origin,
|
|
553
|
+
workspacePath: rule.workspacePath,
|
|
554
|
+
relHint: rule.relHint,
|
|
555
|
+
file: rule.file,
|
|
556
|
+
content: res.content,
|
|
557
|
+
fmType: parsed.type,
|
|
558
|
+
desc: parsed.description,
|
|
559
|
+
globs: parsed.globs.join(", "),
|
|
560
|
+
error: "",
|
|
561
|
+
saving: false
|
|
562
|
+
});
|
|
563
|
+
}).catch((e) => notify("读取规则异常: " + String(e)));
|
|
564
|
+
};
|
|
565
|
+
/** 类型下拉变更:frontmatter 按新类型重写,控件同步。 */
|
|
566
|
+
const onTypeChange = (e) => {
|
|
567
|
+
const nextType = e.target.value;
|
|
568
|
+
setForm((prev) => ({
|
|
569
|
+
...prev,
|
|
570
|
+
fmType: nextType,
|
|
571
|
+
content: rebuildContent(prev.content, nextType, prev.desc, prev.globs),
|
|
572
|
+
error: ""
|
|
573
|
+
}));
|
|
574
|
+
};
|
|
575
|
+
/** 描述输入变更:同步 frontmatter description 行。 */
|
|
576
|
+
const onDescChange = (e) => {
|
|
577
|
+
const nextDesc = e.target.value;
|
|
578
|
+
setForm((prev) => ({
|
|
579
|
+
...prev,
|
|
580
|
+
desc: nextDesc,
|
|
581
|
+
content: rebuildContent(prev.content, prev.fmType, nextDesc, prev.globs),
|
|
582
|
+
error: ""
|
|
583
|
+
}));
|
|
584
|
+
};
|
|
585
|
+
/** globs 输入变更(自动规则):同步 frontmatter globs 行。 */
|
|
586
|
+
const onGlobsChange = (e) => {
|
|
587
|
+
const nextGlobs = e.target.value;
|
|
588
|
+
setForm((prev) => ({
|
|
589
|
+
...prev,
|
|
590
|
+
globs: nextGlobs,
|
|
591
|
+
content: rebuildContent(prev.content, prev.fmType, prev.desc, nextGlobs),
|
|
592
|
+
error: ""
|
|
593
|
+
}));
|
|
594
|
+
};
|
|
595
|
+
/** 文本域直接编辑:回读 frontmatter,同步类型/描述/globs 控件(文本域是唯一真源)。 */
|
|
596
|
+
const onBodyChange = (e) => {
|
|
597
|
+
const parsed = parseRuleFm(e.target.value);
|
|
598
|
+
setForm((prev) => ({
|
|
599
|
+
...prev,
|
|
600
|
+
content: e.target.value,
|
|
601
|
+
fmType: parsed.type,
|
|
602
|
+
desc: parsed.description,
|
|
603
|
+
globs: parsed.globs.join(", ")
|
|
604
|
+
}));
|
|
605
|
+
};
|
|
606
|
+
/** 保存表单(rules.save;文件名非空 + 自动规则必须有 globs)。 */
|
|
607
|
+
const saveForm = () => {
|
|
608
|
+
const next = {
|
|
609
|
+
...form,
|
|
610
|
+
saving: true,
|
|
611
|
+
error: ""
|
|
612
|
+
};
|
|
613
|
+
const file = String(form.file ?? "").trim();
|
|
614
|
+
if (!file) {
|
|
615
|
+
setForm({
|
|
616
|
+
...next,
|
|
617
|
+
saving: false,
|
|
618
|
+
error: "文件名不能为空"
|
|
619
|
+
});
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (form.fmType === "auto" && globList(form.globs).length === 0) {
|
|
623
|
+
setForm({
|
|
624
|
+
...next,
|
|
625
|
+
saving: false,
|
|
626
|
+
error: "自动规则需要至少一个 glob(逗号分隔)"
|
|
627
|
+
});
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
setForm(next);
|
|
631
|
+
rpc("rules.save", {
|
|
632
|
+
scope: form.scope,
|
|
633
|
+
origin: form.origin,
|
|
634
|
+
workspacePath: form.workspacePath,
|
|
635
|
+
file,
|
|
636
|
+
content: form.content
|
|
637
|
+
}).then((res) => {
|
|
638
|
+
if (res && res.ok) {
|
|
639
|
+
setForm(null);
|
|
640
|
+
notify("规则已保存:" + file);
|
|
641
|
+
load();
|
|
642
|
+
} else setForm((prev) => ({
|
|
643
|
+
...prev,
|
|
644
|
+
saving: false,
|
|
645
|
+
error: res?.error ?? "保存失败"
|
|
646
|
+
}));
|
|
647
|
+
}).catch((e) => setForm((prev) => ({
|
|
648
|
+
...prev,
|
|
649
|
+
saving: false,
|
|
650
|
+
error: "保存异常: " + String(e)
|
|
651
|
+
})));
|
|
652
|
+
};
|
|
653
|
+
/**
|
|
654
|
+
* 删除规则(confirm 后走 rules.remove)。
|
|
655
|
+
* @param rule 目标规则
|
|
656
|
+
*/
|
|
657
|
+
const removeRule = (rule) => {
|
|
658
|
+
const reveal = rule.scope === "user" && rule.origin !== "legacy" ? "\n若旧目录有同名规则,删除后它将重新生效。" : "";
|
|
659
|
+
if (!window.confirm("删除规则 " + rule.absPath + "?(不可恢复)" + reveal)) return;
|
|
660
|
+
rpc("rules.remove", {
|
|
661
|
+
scope: rule.scope,
|
|
662
|
+
origin: rule.origin,
|
|
663
|
+
workspacePath: rule.workspacePath,
|
|
664
|
+
file: rule.file
|
|
665
|
+
}).then((res) => {
|
|
666
|
+
if (res && res.ok) {
|
|
667
|
+
notify("已删除:" + rule.file);
|
|
668
|
+
load();
|
|
669
|
+
} else notify(res?.error ?? "删除失败");
|
|
670
|
+
}).catch((e) => notify("删除异常: " + String(e)));
|
|
671
|
+
};
|
|
672
|
+
/**
|
|
673
|
+
* 切换启用开关:本地乐观翻转(按 workspacePath+file 定位,失败回滚)。
|
|
674
|
+
* @param rule 目标规则(project 已注入 workspacePath)
|
|
675
|
+
*/
|
|
676
|
+
const toggleRule = (rule) => {
|
|
677
|
+
const enabled = !rule.enabled;
|
|
678
|
+
const applyLocal = (on) => setData((prev) => prev ? {
|
|
679
|
+
...prev,
|
|
680
|
+
user: prev.user.map((r) => rule.scope === "user" && r.file === rule.file && (r.origin ?? "current") === (rule.origin ?? "current") ? {
|
|
681
|
+
...r,
|
|
682
|
+
enabled: on
|
|
683
|
+
} : r),
|
|
684
|
+
projects: prev.projects.map((p) => p.workspacePath === rule.workspacePath ? {
|
|
685
|
+
...p,
|
|
686
|
+
rules: p.rules.map((r) => r.file === rule.file ? {
|
|
687
|
+
...r,
|
|
688
|
+
enabled: on
|
|
689
|
+
} : r)
|
|
690
|
+
} : p)
|
|
691
|
+
} : prev);
|
|
692
|
+
applyLocal(enabled);
|
|
693
|
+
rpc("rules.toggle", {
|
|
694
|
+
scope: rule.scope,
|
|
695
|
+
origin: rule.origin,
|
|
696
|
+
workspacePath: rule.workspacePath,
|
|
697
|
+
file: rule.file,
|
|
698
|
+
enabled
|
|
699
|
+
}).then((res) => {
|
|
700
|
+
if (res && res.ok) {
|
|
701
|
+
applyLocal(res.rule?.enabled === true);
|
|
702
|
+
notify(res.rule?.enabled === false ? "已停用:" + rule.file : "已启用:" + rule.file);
|
|
703
|
+
} else {
|
|
704
|
+
applyLocal(!enabled);
|
|
705
|
+
notify(res?.error ?? "切换失败");
|
|
706
|
+
}
|
|
707
|
+
}).catch((e) => {
|
|
708
|
+
applyLocal(!enabled);
|
|
709
|
+
notify("切换异常: " + String(e));
|
|
710
|
+
});
|
|
711
|
+
};
|
|
712
|
+
/** 当前列表数据(按 Tab 取用户规则或当前工作区的项目规则)。 */
|
|
713
|
+
const rowsOf = () => {
|
|
714
|
+
if (!data) return [];
|
|
715
|
+
if (tab === "user") return data.user;
|
|
716
|
+
return activeProject()?.rules ?? [];
|
|
717
|
+
};
|
|
718
|
+
const renderTabs = () => react.default.createElement("div", { className: "edrv-rules-tabs" }, react.default.createElement("button", {
|
|
719
|
+
className: "edrv-rules-tab" + (tab === "user" ? " on" : ""),
|
|
720
|
+
onClick: () => switchTab("user")
|
|
721
|
+
}, "用户规则"), react.default.createElement("button", {
|
|
722
|
+
className: "edrv-rules-tab" + (tab === "project" ? " on" : ""),
|
|
723
|
+
onClick: () => switchTab("project")
|
|
724
|
+
}, "项目规则"), react.default.createElement("button", {
|
|
725
|
+
className: "edrv-rules-new",
|
|
726
|
+
title: "新建规则",
|
|
727
|
+
onClick: openNew
|
|
728
|
+
}, "+ 新建规则"));
|
|
729
|
+
/** 项目 Tab 的工作区展示(不可编辑,自动跟随当前会话)。 */
|
|
730
|
+
const renderWsLine = () => {
|
|
731
|
+
if (tab !== "project") return null;
|
|
732
|
+
return react.default.createElement("div", {
|
|
733
|
+
className: "edrv-rules-ws-line",
|
|
734
|
+
title: cwd ?? ""
|
|
735
|
+
}, "工作区:" + (cwd ?? "当前会话未打开工作区"));
|
|
736
|
+
};
|
|
737
|
+
const renderEmpty = () => {
|
|
738
|
+
if (tab === "project") {
|
|
739
|
+
if (!cwd) return "当前会话没有工作区,项目规则不可用(项目规则跟随会话所在工作区)";
|
|
740
|
+
if (!data?.projects?.length) return "当前 DSH 没有已注册的工作区;在目标工作区打开一次会话后再来管理";
|
|
741
|
+
if (!activeProject()) return "当前工作区未注册为 DSH workspace,暂不能管理其项目规则";
|
|
742
|
+
return "<工作区>/.dsh/rules/ 还没有规则,点击「+ 新建规则」创建";
|
|
743
|
+
}
|
|
744
|
+
return "还没有用户规则(当前 DSH_HOME/rules/ 或旧目录 ~/.dsh/rules/),点击「+ 新建规则」创建";
|
|
745
|
+
};
|
|
746
|
+
const renderBody = () => {
|
|
747
|
+
if (form) return react.default.createElement(RuleEditor, {
|
|
748
|
+
form,
|
|
749
|
+
onType: onTypeChange,
|
|
750
|
+
onDesc: onDescChange,
|
|
751
|
+
onGlobs: onGlobsChange,
|
|
752
|
+
onBody: onBodyChange,
|
|
753
|
+
onSave: saveForm,
|
|
754
|
+
onCancel: () => setForm(null)
|
|
755
|
+
});
|
|
756
|
+
if (!data) return react.default.createElement("div", { className: "edrv-rules-empty" }, "加载中…");
|
|
757
|
+
if (error) return react.default.createElement("div", { className: "edrv-rules-err" }, error);
|
|
758
|
+
const rows = rowsOf();
|
|
759
|
+
if (!rows.length) return react.default.createElement("div", { className: "edrv-rules-empty" }, renderEmpty());
|
|
760
|
+
const common = {
|
|
761
|
+
descCap,
|
|
762
|
+
narrow,
|
|
763
|
+
onEdit: (rule) => openEdit({
|
|
764
|
+
...rule,
|
|
765
|
+
workspacePath: rule.scope === "project" ? cwd : void 0
|
|
766
|
+
}),
|
|
767
|
+
onRemove: (rule) => removeRule({
|
|
768
|
+
...rule,
|
|
769
|
+
workspacePath: rule.scope === "project" ? cwd : void 0
|
|
770
|
+
}),
|
|
771
|
+
onToggle: (rule) => toggleRule({
|
|
772
|
+
...rule,
|
|
773
|
+
workspacePath: rule.scope === "project" ? cwd : void 0
|
|
774
|
+
})
|
|
775
|
+
};
|
|
776
|
+
return react.default.createElement("div", { className: "edrv-rules-list" }, rows.map((rule) => react.default.createElement(RuleRow, {
|
|
777
|
+
key: rule.scope + ":" + (rule.origin ?? "project") + ":" + rule.file,
|
|
778
|
+
rule,
|
|
779
|
+
...common
|
|
780
|
+
})));
|
|
781
|
+
};
|
|
782
|
+
return react.default.createElement("div", {
|
|
783
|
+
className: "edrv-rules-panel",
|
|
784
|
+
ref: panelRef
|
|
785
|
+
}, renderTabs(), renderWsLine(), react.default.createElement("div", { className: "edrv-rules-hint" }, tab === "user" ? "读取 DSH_HOME/rules/ 与 ~/.dsh/rules/;同名时当前目录优先,新建保存到当前目录" : "项目规则仅对所在工作区的会话生效(存放于 <工作区>/.dsh/rules/)"), renderBody());
|
|
786
|
+
}
|
|
787
|
+
//#endregion
|
|
788
|
+
exports.RulesPanel = RulesPanel;
|
|
789
|
+
return module.exports;
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
//# sourceMappingURL=client.rules.js.map
|