@chengzhao-dev/dsh-cpp-workflow 0.6.7 → 0.6.9
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 +3 -3
- package/README.md +4 -4
- package/lib/client.js +353 -287
- package/lib/comment-policy.js +81 -62
- package/lib/index.js +20 -14
- package/package.json +2 -2
- package/skills/building-cpp/SKILL.md +1 -1
- package/skills/configuring-cmake/SKILL.md +1 -1
- package/skills/developing-cpp/SKILL.md +2 -2
- package/skills/developing-cpp/examples/structure.md +1 -1
- package/skills/developing-cpp/references/comments.md +27 -6
- package/skills/developing-cpp/references/google-cpp-style.md +2 -2
- package/skills/developing-cpp/references/language.md +2 -1
- package/skills/developing-cpp/references/types.md +1 -1
- package/skills/developing-cpp/scripts/checking-comments.mjs +139 -17
- package/skills/developing-cpp/steps/03-declaration.md +1 -1
package/lib/client.js
CHANGED
|
@@ -8,8 +8,8 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
/**
|
|
9
9
|
* 注释标签策略:设置页的预览、技能快照与检查器参数共用这一份。
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* `skills/developing-cpp/references/comments.md`。设置页保存的值由宿主
|
|
11
|
+
* 配置只描述「写注释时默认带哪些标签、声明用哪种注释符号」,不描述写法本身;
|
|
12
|
+
* 写法在 `skills/developing-cpp/references/comments.md`。设置页保存的值由宿主
|
|
13
13
|
* 写回插件 Config,服务端 `apply(ctx, config)` 用它生成本页快照与检查器参数。
|
|
14
14
|
*
|
|
15
15
|
* 这份文件被 `tsc` 编进 `lib/comment-policy.js`,打包客户端半侧时再内联进
|
|
@@ -17,29 +17,16 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
*/
|
|
18
18
|
/** 文件头标签。 */
|
|
19
19
|
const HEADER_TAGS = ['brief', 'author', 'date', 'copyright', 'version'];
|
|
20
|
-
/**
|
|
20
|
+
/** 函数声明标签:开关决定是否要求写出。 */
|
|
21
21
|
const CONTRACT_TAGS = ['param', 'returns', 'throws'];
|
|
22
|
-
/**
|
|
22
|
+
/** 函数声明里出厂开启的补充标签。 */
|
|
23
|
+
const FUNCTION_EXTRA_TAGS = ['pre', 'post', 'retval'];
|
|
24
|
+
/** 类型标签:出厂开启,函数模板共用同一组键。 */
|
|
25
|
+
const TYPE_TAGS = ['tparam', 'note', 'see'];
|
|
26
|
+
/** 全部可选标签,快照与测试按它排序。 */
|
|
23
27
|
const EXTRA_TAGS = ['note', 'see', 'pre', 'post', 'tparam', 'retval'];
|
|
24
28
|
/** 只有用户主动填了非空值才放行的隐私标签。 */
|
|
25
29
|
const PRIVACY_TAGS = ['author', 'date', 'copyright', 'version'];
|
|
26
|
-
/** 标签说明:设置页逐行显示,用作行的辅助文字。 */
|
|
27
|
-
const TAG_TEXT = {
|
|
28
|
-
brief: '一句话说明这个文件定义或实现什么',
|
|
29
|
-
author: '只在用户填了非空值时写出',
|
|
30
|
-
date: '只在用户填了非空值时写出',
|
|
31
|
-
copyright: '只在用户填了非空值时写出',
|
|
32
|
-
version: '只在用户填了非空值时写出',
|
|
33
|
-
param: '函数参数的含义、范围或所有权',
|
|
34
|
-
returns: '返回值的条件;返回类型自明时可以省略',
|
|
35
|
-
throws: '抛出的异常类型与条件;不抛异常的函数不写',
|
|
36
|
-
note: '调用方需要注意的补充说明',
|
|
37
|
-
see: '相关函数、类型或文档',
|
|
38
|
-
pre: '调用前必须成立的条件',
|
|
39
|
-
post: '调用后保证成立的条件',
|
|
40
|
-
tparam: '模板参数',
|
|
41
|
-
retval: '特定返回值的含义',
|
|
42
|
-
};
|
|
43
30
|
/** 开关型标签在设置页的标题。 */
|
|
44
31
|
const TAG_LABEL = {
|
|
45
32
|
file: '@file',
|
|
@@ -62,22 +49,25 @@ const TAG_LABEL = {
|
|
|
62
49
|
const SAMPLE_BRIEF = '<一句话说明本文件>';
|
|
63
50
|
/** 预览里的示例文件名:只是占位符,不对应任何工程的真实文件。 */
|
|
64
51
|
const SAMPLE_FILE = '<文件名>';
|
|
65
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* 出厂配置:声明用 `///`,除 @file 与函数体 // 外全部开启;空值仍不写文件头。
|
|
54
|
+
*/
|
|
66
55
|
const DEFAULT_CONFIG = {
|
|
56
|
+
commentStyle: 'slash',
|
|
67
57
|
brief: { enabled: true, value: '' },
|
|
68
|
-
author: { enabled:
|
|
69
|
-
date: { enabled:
|
|
70
|
-
copyright: { enabled:
|
|
71
|
-
version: { enabled:
|
|
58
|
+
author: { enabled: true, value: '' },
|
|
59
|
+
date: { enabled: true, value: '' },
|
|
60
|
+
copyright: { enabled: true, value: '' },
|
|
61
|
+
version: { enabled: true, value: '' },
|
|
72
62
|
param: true,
|
|
73
63
|
returns: true,
|
|
74
64
|
throws: true,
|
|
75
|
-
note:
|
|
76
|
-
see:
|
|
77
|
-
pre:
|
|
78
|
-
post:
|
|
79
|
-
tparam:
|
|
80
|
-
retval:
|
|
65
|
+
note: true,
|
|
66
|
+
see: true,
|
|
67
|
+
pre: true,
|
|
68
|
+
post: true,
|
|
69
|
+
tparam: true,
|
|
70
|
+
retval: true,
|
|
81
71
|
};
|
|
82
72
|
/**
|
|
83
73
|
* 宿主把 volatile 字段交给插件时给的是可读写单元(`.get()`),设置页快照给的是普通值;
|
|
@@ -99,12 +89,16 @@ function flag(value, fallback) {
|
|
|
99
89
|
const plain = unwrap(value);
|
|
100
90
|
return typeof plain === 'boolean' ? plain : fallback;
|
|
101
91
|
}
|
|
92
|
+
/** 声明符号只认两档;未知值回到 `///`。 */
|
|
93
|
+
function commentStyleOf(raw) {
|
|
94
|
+
return unwrap(raw) === 'block' ? 'block' : 'slash';
|
|
95
|
+
}
|
|
102
96
|
/**
|
|
103
97
|
* 把宿主给的值补齐成完整配置;缺字段、旧版本或手工改坏的配置都回到出厂值。
|
|
104
98
|
*/
|
|
105
99
|
function normalizeConfig(raw) {
|
|
106
100
|
const source = raw && typeof raw === 'object' ? raw : {};
|
|
107
|
-
const config = {};
|
|
101
|
+
const config = { commentStyle: commentStyleOf(source.commentStyle) };
|
|
108
102
|
for (const tag of HEADER_TAGS) {
|
|
109
103
|
const entry = source[tag] && typeof source[tag] === 'object' ? source[tag] : {};
|
|
110
104
|
config[tag] = {
|
|
@@ -117,59 +111,85 @@ function normalizeConfig(raw) {
|
|
|
117
111
|
}
|
|
118
112
|
return config;
|
|
119
113
|
}
|
|
114
|
+
/** 一行声明注释:`/// 内容` 或块注释里的 ` * 内容`。 */
|
|
115
|
+
function declarationLine(style, content) {
|
|
116
|
+
return style === 'block' ? ` * ${content}` : `/// ${content}`;
|
|
117
|
+
}
|
|
118
|
+
/** 文件头后面的说明行:块注释里空行写成 ` *`。 */
|
|
119
|
+
function separatorLine(style) {
|
|
120
|
+
return style === 'block' ? ' *' : '///';
|
|
121
|
+
}
|
|
120
122
|
/**
|
|
121
|
-
* 按配置渲染文件头;`@file`
|
|
123
|
+
* 按配置渲染文件头;`@file` 始终是传入的文件名,块注释按 Doxygen 结构分行。
|
|
122
124
|
*/
|
|
123
125
|
function renderFileHeader(raw, fileName) {
|
|
124
126
|
const config = normalizeConfig(raw);
|
|
125
127
|
const name = text(fileName) || SAMPLE_FILE;
|
|
126
|
-
const lines = [
|
|
128
|
+
const lines = [`@file ${name}`];
|
|
127
129
|
if (config.brief.enabled) {
|
|
128
|
-
lines.push(config.brief.value ?
|
|
130
|
+
lines.push(config.brief.value ? `@brief ${config.brief.value}` : '@brief');
|
|
129
131
|
}
|
|
130
132
|
for (const tag of ['author', 'date', 'copyright', 'version']) {
|
|
131
133
|
if (config[tag].enabled && config[tag].value)
|
|
132
|
-
lines.push(
|
|
134
|
+
lines.push(`@${tag} ${config[tag].value}`);
|
|
133
135
|
}
|
|
134
|
-
|
|
136
|
+
if (config.commentStyle === 'block') {
|
|
137
|
+
return ['/**', ...lines.map((line) => ` * ${line}`), ' */'];
|
|
138
|
+
}
|
|
139
|
+
return lines.map((line) => `/// ${line}`);
|
|
135
140
|
}
|
|
136
141
|
/**
|
|
137
142
|
* 设置页的预览:文件头 + 一段函数注释,只显示当前配置会写出的标签。
|
|
143
|
+
* 返回值里的 `header` 已按声明符号排好,`contract` 是每行标签的正文。
|
|
138
144
|
*/
|
|
139
145
|
function previewParts(raw, sampleName) {
|
|
140
146
|
const config = normalizeConfig(raw);
|
|
141
147
|
const header = renderFileHeader(config, sampleName || SAMPLE_FILE);
|
|
142
148
|
// 预览是示例:@brief 开着但用户没填句子时,用示例文件自己的摘要顶替。
|
|
143
|
-
const briefIndex = header.
|
|
144
|
-
if (briefIndex !== -1)
|
|
145
|
-
header[briefIndex] =
|
|
149
|
+
const briefIndex = header.findIndex((line) => /@brief$/.test(line.trim()));
|
|
150
|
+
if (briefIndex !== -1) {
|
|
151
|
+
header[briefIndex] = header[briefIndex].replace(/@brief$/, `@brief ${SAMPLE_BRIEF}`);
|
|
152
|
+
}
|
|
146
153
|
const contract = [];
|
|
147
154
|
if (config.param)
|
|
148
|
-
contract.push('
|
|
155
|
+
contract.push('@param row 目标行号。');
|
|
149
156
|
if (config.returns)
|
|
150
|
-
contract.push('
|
|
157
|
+
contract.push('@return 放置成功时为 true。');
|
|
151
158
|
if (config.throws)
|
|
152
|
-
contract.push('
|
|
153
|
-
|
|
154
|
-
return { header, contract, declaration };
|
|
159
|
+
contract.push('@throws std::out_of_range 行号越界时抛出。');
|
|
160
|
+
return { header, contract, declaration: '[[nodiscard]] bool Place(int row, int column);' };
|
|
155
161
|
}
|
|
156
162
|
/**
|
|
157
163
|
* 设置页预览的纯文本形式;客户端半侧用同一份实现渲染 `<pre>`。
|
|
164
|
+
* 块注释的每个内容行前加 ` * `,首尾各占一行。
|
|
158
165
|
*/
|
|
159
166
|
function previewText(raw, sampleName) {
|
|
160
|
-
const
|
|
161
|
-
const
|
|
167
|
+
const config = normalizeConfig(raw);
|
|
168
|
+
const { header, contract, declaration } = previewParts(config, sampleName);
|
|
169
|
+
const style = config.commentStyle;
|
|
170
|
+
const summary = '放置成功时返回 true;行号越界或目标格子已占时返回 false。';
|
|
171
|
+
if (style === 'block') {
|
|
172
|
+
const body = [declarationLine(style, summary)];
|
|
173
|
+
if (contract.length > 0) {
|
|
174
|
+
body.push(separatorLine(style), ...contract.map((line) => declarationLine(style, line)));
|
|
175
|
+
}
|
|
176
|
+
return [...header, '/**', ...body, ' */', declaration].join('\n');
|
|
177
|
+
}
|
|
178
|
+
const lines = [...header, separatorLine(style), declarationLine(style, summary)];
|
|
162
179
|
if (contract.length > 0)
|
|
163
|
-
lines.push(
|
|
180
|
+
lines.push(separatorLine(style), ...contract.map((line) => declarationLine(style, line)));
|
|
164
181
|
lines.push(declaration);
|
|
165
182
|
return lines.join('\n');
|
|
166
183
|
}
|
|
167
184
|
/**
|
|
168
|
-
* 检查器参数:隐私标签要显式放行,@brief
|
|
185
|
+
* 检查器参数:隐私标签要显式放行,@brief 被关掉时跳过摘要检查,
|
|
186
|
+
* 声明符号切到块注释时让检查器按块注释收口。
|
|
169
187
|
*/
|
|
170
188
|
function checkerArgs(raw) {
|
|
171
189
|
const config = normalizeConfig(raw);
|
|
172
190
|
const args = [];
|
|
191
|
+
if (config.commentStyle === 'block')
|
|
192
|
+
args.push('--comment-style', 'block');
|
|
173
193
|
if (!config.brief.enabled)
|
|
174
194
|
args.push('--no-brief');
|
|
175
195
|
for (const tag of PRIVACY_TAGS) {
|
|
@@ -180,10 +200,11 @@ function checkerArgs(raw) {
|
|
|
180
200
|
}
|
|
181
201
|
/**
|
|
182
202
|
* 技能快照:`get()` 把它接在技能正文末尾,让代理看到当前 profile 的标签开关。
|
|
203
|
+
* 只写一行,列出声明符号、开启与关闭的标签,避免正文膨胀。
|
|
183
204
|
*/
|
|
184
205
|
function snapshotText(raw) {
|
|
185
206
|
const config = normalizeConfig(raw);
|
|
186
|
-
const on = [];
|
|
207
|
+
const on = ['@file'];
|
|
187
208
|
const off = [];
|
|
188
209
|
for (const tag of HEADER_TAGS) {
|
|
189
210
|
const entry = config[tag];
|
|
@@ -193,17 +214,15 @@ function snapshotText(raw) {
|
|
|
193
214
|
else
|
|
194
215
|
off.push(TAG_LABEL[tag]);
|
|
195
216
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
];
|
|
206
|
-
return head + body.join('\n');
|
|
217
|
+
for (const tag of [...CONTRACT_TAGS, ...EXTRA_TAGS]) {
|
|
218
|
+
if (config[tag])
|
|
219
|
+
on.push(TAG_LABEL[tag]);
|
|
220
|
+
else
|
|
221
|
+
off.push(TAG_LABEL[tag]);
|
|
222
|
+
}
|
|
223
|
+
const style = config.commentStyle === 'block' ? '/** ... */' : '///';
|
|
224
|
+
const line = `- 声明符号:${style}(函数体固定 //);开启 ${on.join('、')};关闭 ${off.join('、') || '无'}。`;
|
|
225
|
+
return `## 当前注释配置(profile)\n\n${line}`;
|
|
207
226
|
}
|
|
208
227
|
|
|
209
228
|
|
|
@@ -218,29 +237,42 @@ const PACKAGE_NAME = '@chengzhao-dev/dsh-cpp-workflow'
|
|
|
218
237
|
const STRINGS = {
|
|
219
238
|
zh: {
|
|
220
239
|
nav: 'C++ 工程流程',
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
240
|
+
heading: '文档注释',
|
|
241
|
+
lede: '文件头、类型与函数声明各自决定默认写入哪些标签;预览随改动即时更新。',
|
|
242
|
+
style: '注释符号',
|
|
243
|
+
styleHint: '声明用哪种注释;函数体始终用 //。',
|
|
244
|
+
bodyRow: '函数体',
|
|
245
|
+
bodyPlaceholder: '固定 //',
|
|
224
246
|
header: '文件头',
|
|
225
|
-
headerHint: '
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
versionHint: '文件版本号。默认不写。',
|
|
231
|
-
paramHint: '函数参数的含义、范围或所有权。',
|
|
232
|
-
returnHint: '返回值在什么条件下成立。返回类型自明时可以省略。',
|
|
233
|
-
throwsHint: '抛出的异常类型与条件。不抛异常的函数不写。',
|
|
234
|
-
contractHint: '函数声明的注释里必须交代的参数与失败信息。',
|
|
235
|
-
extra: '更多标签',
|
|
236
|
-
extraHint: '默认关闭,需要时在单个函数上补写。',
|
|
247
|
+
headerHint: '开关打开后填写内容;空值不会写进文件。',
|
|
248
|
+
type: '类型',
|
|
249
|
+
typeHint: '类型块与函数模板里的补充标签。',
|
|
250
|
+
declaration: '函数声明',
|
|
251
|
+
declarationHint: '函数契约与前置、后置条件。',
|
|
237
252
|
preview: '预览',
|
|
238
|
-
previewHint: '
|
|
253
|
+
previewHint: '代码块随开关和输入即时更新。',
|
|
254
|
+
filePlaceholder: '跟随文件名',
|
|
255
|
+
briefPlaceholder: '文件摘要:留空则各文件自写',
|
|
256
|
+
authorPlaceholder: '作者署名',
|
|
257
|
+
datePlaceholder: '日期',
|
|
258
|
+
copyrightPlaceholder: '版权',
|
|
259
|
+
versionPlaceholder: '版本',
|
|
260
|
+
paramPlaceholder: '逐项说明参数',
|
|
261
|
+
returnPlaceholder: '说明返回值',
|
|
262
|
+
throwsPlaceholder: '列出可能抛出的异常',
|
|
263
|
+
prePlaceholder: '前置条件',
|
|
264
|
+
postPlaceholder: '后置条件',
|
|
265
|
+
retvalPlaceholder: '按枚举值说明返回值',
|
|
266
|
+
tparamPlaceholder: '说明模板参数',
|
|
267
|
+
notePlaceholder: '补充说明',
|
|
268
|
+
seePlaceholder: '相关接口',
|
|
269
|
+
styleSlashExample: '/// @brief 定义棋盘。',
|
|
270
|
+
styleBlockExample: '/** @brief 定义棋盘。 */',
|
|
239
271
|
save: '保存',
|
|
240
272
|
reset: '撤销修改',
|
|
241
273
|
defaults: '恢复默认值',
|
|
242
274
|
restored: '已填入内置默认值,点“保存”后生效。',
|
|
243
|
-
saved: '
|
|
275
|
+
saved: '已保存到 ~/.dsh/profiles/<profile>/cordis.patch.yml,下一次读取技能时生效。',
|
|
244
276
|
rejected: '保存被拒绝:配置可能已在别处修改。请先撤销修改,再重新编辑。',
|
|
245
277
|
unsaved: '有未保存的改动',
|
|
246
278
|
loading: '正在读取配置…',
|
|
@@ -249,38 +281,45 @@ const STRINGS = {
|
|
|
249
281
|
readOnly: '这个页面暂时没有连到配置表单,下面的值是内置默认值。重启 DeepSeek Harness 后重新打开本页即可编辑。',
|
|
250
282
|
reload: '重新载入页面',
|
|
251
283
|
noService: '(configForms 服务未连接)',
|
|
252
|
-
fixed: '跟随文件名',
|
|
253
|
-
fixedHint: '值由实际文件决定,不写进配置。',
|
|
254
|
-
emptyValue: '留空则由文件自己写',
|
|
255
|
-
blankBrief: '留空则文件自述',
|
|
256
|
-
on: '写出',
|
|
257
|
-
off: '不写',
|
|
258
284
|
},
|
|
259
285
|
en: {
|
|
260
286
|
nav: 'C++ Workflow',
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
287
|
+
heading: 'Doxygen comments',
|
|
288
|
+
lede: 'The file header, types, and declarations each decide which tags are written by default; the preview updates as you edit.',
|
|
289
|
+
style: 'Comment symbol',
|
|
290
|
+
styleHint: 'Symbol for declarations; function bodies always use //.',
|
|
291
|
+
bodyRow: 'Body',
|
|
292
|
+
bodyPlaceholder: 'Always //',
|
|
264
293
|
header: 'File header',
|
|
265
|
-
headerHint: '
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
versionHint: 'File version. Off by default.',
|
|
271
|
-
paramHint: 'Meaning, range, or ownership of a parameter.',
|
|
272
|
-
returnHint: 'When the returned value holds. Optional when the type is self-evident.',
|
|
273
|
-
throwsHint: 'Exception type and condition. Omitted when the function does not throw.',
|
|
274
|
-
contractHint: 'Parameters and failure information every function comment must carry.',
|
|
275
|
-
extra: 'More tags',
|
|
276
|
-
extraHint: 'Off by default; add them per function when needed.',
|
|
294
|
+
headerHint: 'Turn a tag on, then fill in its value; empty values are not written.',
|
|
295
|
+
type: 'Types',
|
|
296
|
+
typeHint: 'Extra tags for type blocks and function templates.',
|
|
297
|
+
declaration: 'Function declarations',
|
|
298
|
+
declarationHint: 'Function contracts plus pre- and postconditions.',
|
|
277
299
|
preview: 'Preview',
|
|
278
|
-
previewHint: '
|
|
300
|
+
previewHint: 'The code block updates as you toggle switches and type.',
|
|
301
|
+
filePlaceholder: 'Follows the file name',
|
|
302
|
+
briefPlaceholder: 'File summary: leave empty for each file',
|
|
303
|
+
authorPlaceholder: 'Author',
|
|
304
|
+
datePlaceholder: 'Date',
|
|
305
|
+
copyrightPlaceholder: 'Copyright',
|
|
306
|
+
versionPlaceholder: 'Version',
|
|
307
|
+
paramPlaceholder: 'Describe each parameter',
|
|
308
|
+
returnPlaceholder: 'Describe the return value',
|
|
309
|
+
throwsPlaceholder: 'List exceptions that may be thrown',
|
|
310
|
+
prePlaceholder: 'Precondition',
|
|
311
|
+
postPlaceholder: 'Postcondition',
|
|
312
|
+
retvalPlaceholder: 'Describe a named return value',
|
|
313
|
+
tparamPlaceholder: 'Describe template parameters',
|
|
314
|
+
notePlaceholder: 'Additional notes',
|
|
315
|
+
seePlaceholder: 'Related interfaces',
|
|
316
|
+
styleSlashExample: '/// @brief Defines the board.',
|
|
317
|
+
styleBlockExample: '/** @brief Defines the board. */',
|
|
279
318
|
save: 'Save',
|
|
280
319
|
reset: 'Discard changes',
|
|
281
320
|
defaults: 'Restore defaults',
|
|
282
321
|
restored: 'Built-in defaults are filled in; select Save to apply them.',
|
|
283
|
-
saved: 'Saved; the next skill read uses it.',
|
|
322
|
+
saved: 'Saved to ~/.dsh/profiles/<profile>/cordis.patch.yml; the next skill read uses it.',
|
|
284
323
|
rejected: 'The host rejected the save: the config changed elsewhere. Discard your changes and edit again.',
|
|
285
324
|
unsaved: 'Unsaved changes',
|
|
286
325
|
loading: 'Reading configuration…',
|
|
@@ -289,12 +328,6 @@ const STRINGS = {
|
|
|
289
328
|
readOnly: 'This page is not connected to a configuration form yet; the values below are built-in defaults. Restart DeepSeek Harness, then reopen this page to edit.',
|
|
290
329
|
reload: 'Reload page',
|
|
291
330
|
noService: ' (the configForms service is not connected)',
|
|
292
|
-
fixed: 'Follows the file name',
|
|
293
|
-
fixedHint: 'Derived from the file itself, never stored in the config.',
|
|
294
|
-
emptyValue: 'Leave empty and each file writes its own',
|
|
295
|
-
blankBrief: 'Leave empty for each file',
|
|
296
|
-
on: 'Write',
|
|
297
|
-
off: 'Omit',
|
|
298
331
|
},
|
|
299
332
|
}
|
|
300
333
|
|
|
@@ -311,64 +344,65 @@ const STYLE_ID = 'dsh-cpp-workflow-settings'
|
|
|
311
344
|
|
|
312
345
|
// 外观只用宿主变量与两档中性色;没有变量时回落到浅色主题的一组合法颜色。
|
|
313
346
|
const CSS = `
|
|
314
|
-
.dcw-page{--dcw-fg:var(--dsw-alias-label-primary,#1f2328);--dcw-muted:var(--dsw-alias-label-secondary,#69707d);--dcw-faint:var(--dsw-alias-label-tertiary,#8a919c);--dcw-border:var(--dsw-alias-border-l2,rgba(31,35,40,.14));--dcw-bg1:var(--dsw-alias-bg-layer-1,#fff);--dcw-bg2:var(--dsw-alias-bg-layer-2,rgba(31,35,40,.035));--dcw-accent:var(--dsw-alias-brand-primary,#2f6fed);--dcw-danger:var(--dsw-alias-state-error-primary,#c0392b);color:var(--dcw-fg);max-width:760px;padding:2px 0 12px;font-size:13px;line-height:1.6}
|
|
315
|
-
.dcw-head{margin:0 0
|
|
316
|
-
.dcw-
|
|
317
|
-
.dcw-
|
|
318
|
-
.dcw-lede{margin:0;color:var(--dcw-muted);font-size:12.5px;line-height:1.65;max-width:64ch}
|
|
347
|
+
.dcw-page{--dcw-fg:var(--dsw-alias-label-primary,#1f2328);--dcw-muted:var(--dsw-alias-label-secondary,#69707d);--dcw-faint:var(--dsw-alias-label-tertiary,#8a919c);--dcw-border:var(--dsw-alias-border-l2,rgba(31,35,40,.14));--dcw-bg1:var(--dsw-alias-bg-layer-1,#fff);--dcw-bg2:var(--dsw-alias-bg-layer-2,rgba(31,35,40,.035));--dcw-accent:var(--dsw-alias-brand-primary,#2f6fed);--dcw-danger:var(--dsw-alias-state-error-primary,#c0392b);--dcw-success:var(--dsw-alias-state-success-primary,#1a7f37);color:var(--dcw-fg);max-width:760px;padding:2px 0 12px;font-size:13px;line-height:1.6}
|
|
348
|
+
.dcw-head{margin:0 0 16px}
|
|
349
|
+
.dcw-title{margin:0 0 6px;font-size:17px;font-weight:620;letter-spacing:.005em}
|
|
350
|
+
.dcw-lede{margin:0;color:var(--dcw-muted);font-size:12.5px;line-height:1.65;max-width:66ch}
|
|
319
351
|
.dcw-note{display:flex;gap:8px;align-items:center;margin:12px 0 0;padding:9px 11px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg2);color:var(--dcw-muted);font-size:12px;line-height:1.55}
|
|
320
352
|
.dcw-note[data-tone="warn"]{color:var(--dcw-danger);border-color:color-mix(in srgb,var(--dcw-danger) 35%,transparent)}
|
|
321
353
|
.dcw-note-dot{flex:none;width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.55}
|
|
322
|
-
.dcw-
|
|
323
|
-
.dcw-
|
|
324
|
-
.dcw-
|
|
325
|
-
.dcw-card
|
|
326
|
-
.dcw-
|
|
327
|
-
.dcw-
|
|
328
|
-
.dcw-
|
|
329
|
-
.dcw-
|
|
330
|
-
.dcw-row
|
|
331
|
-
.dcw-
|
|
332
|
-
.dcw-
|
|
333
|
-
.dcw-
|
|
334
|
-
.dcw-
|
|
354
|
+
.dcw-note-code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;opacity:.8;margin-left:6px}
|
|
355
|
+
.dcw-note-action{margin-left:auto;flex:none;padding:3px 10px;border:1px solid currentColor;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}
|
|
356
|
+
.dcw-note-action:hover{background:var(--dcw-bg1)}
|
|
357
|
+
.dcw-card{margin:14px 0 0;border:1px solid var(--dcw-border);border-radius:12px;background:var(--dcw-bg1);overflow:hidden}
|
|
358
|
+
.dcw-card-head{padding:12px 16px 10px;border-bottom:1px solid var(--dcw-border);background:var(--dcw-bg2)}
|
|
359
|
+
.dcw-card-title{margin:0;font-size:13px;font-weight:620;letter-spacing:.01em}
|
|
360
|
+
.dcw-card-hint{margin:2px 0 0;color:var(--dcw-muted);font-size:11.5px;line-height:1.5}
|
|
361
|
+
.dcw-card-body{padding:0}
|
|
362
|
+
.dcw-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:2px 14px;min-height:48px;padding:10px 16px;border-top:1px solid var(--dcw-border)}
|
|
363
|
+
.dcw-card-body>.dcw-row:first-child{border-top:0}
|
|
364
|
+
.dcw-row-main{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
365
|
+
.dcw-row-label{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;color:var(--dcw-fg)}
|
|
366
|
+
.dcw-row-hint{color:var(--dcw-muted);font-size:11.5px;line-height:1.5}
|
|
367
|
+
.dcw-row-value .dcw-input{grid-column:1/-1;margin-top:8px}
|
|
368
|
+
.dcw-fixed-value{color:var(--dcw-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12.5px;white-space:nowrap}
|
|
369
|
+
.dcw-row-flag,.dcw-row-value{cursor:pointer}
|
|
370
|
+
.dcw-style-options{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:14px 16px}
|
|
371
|
+
.dcw-style-option{display:flex;flex-direction:column;align-items:flex-start;gap:6px;padding:12px 14px;border:1px solid var(--dcw-border);border-radius:10px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;text-align:left;cursor:pointer}
|
|
372
|
+
.dcw-style-option:hover:enabled{border-color:color-mix(in srgb,var(--dcw-fg) 30%,transparent)}
|
|
373
|
+
.dcw-style-option[aria-checked="true"]{border-color:var(--dcw-accent);background:color-mix(in srgb,var(--dcw-accent) 7%,var(--dcw-bg1));box-shadow:inset 0 0 0 1px var(--dcw-accent)}
|
|
374
|
+
.dcw-style-option:focus-visible{outline:2px solid var(--dcw-accent);outline-offset:2px}
|
|
375
|
+
.dcw-style-option:disabled{opacity:.5;cursor:default}
|
|
376
|
+
.dcw-style-symbol{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:13px;font-weight:620}
|
|
377
|
+
.dcw-style-example{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;line-height:1.55;color:var(--dcw-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
|
|
378
|
+
.dcw-switch{position:relative;display:inline-flex;align-items:center;flex:none;width:40px;height:22px;cursor:pointer}
|
|
335
379
|
.dcw-switch input{position:absolute;inset:0;margin:0;opacity:0;cursor:pointer}
|
|
336
|
-
.dcw-track{position:absolute;inset:0;border-radius:999px;background:color-mix(in srgb,var(--dcw-fg)
|
|
337
|
-
.dcw-knob{position:absolute;top:2px;left:2px;width:
|
|
380
|
+
.dcw-track{position:absolute;inset:0;border-radius:999px;background:color-mix(in srgb,var(--dcw-fg) 20%,transparent);transition:background .16s ease}
|
|
381
|
+
.dcw-knob{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 1px 2px rgba(0,0,0,.28);transition:transform .16s ease}
|
|
338
382
|
.dcw-switch input:checked + .dcw-track{background:var(--dcw-accent)}
|
|
339
|
-
.dcw-switch input:checked + .dcw-track .dcw-knob{transform:translateX(
|
|
383
|
+
.dcw-switch input:checked + .dcw-track .dcw-knob{transform:translateX(18px)}
|
|
340
384
|
.dcw-switch input:focus-visible + .dcw-track{outline:2px solid var(--dcw-accent);outline-offset:2px}
|
|
341
385
|
.dcw-switch input:disabled + .dcw-track{opacity:.4}
|
|
342
386
|
.dcw-switch input:disabled{cursor:default}
|
|
343
|
-
.dcw-
|
|
344
|
-
.dcw-input{flex:1 1 220px;min-width:0;height:28px;padding:0 9px;border:1px solid var(--dcw-border);border-radius:6px;background:transparent;color:var(--dcw-fg);font:inherit;font-size:12.5px}
|
|
387
|
+
.dcw-input{width:100%;min-width:0;height:36px;padding:0 11px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;font-size:12.5px}
|
|
345
388
|
.dcw-input::placeholder{color:var(--dcw-faint)}
|
|
346
|
-
.dcw-input:focus{outline:none;border-color:var(--dcw-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--dcw-accent)
|
|
347
|
-
.dcw-input:disabled{
|
|
348
|
-
.dcw-
|
|
349
|
-
.dcw-
|
|
350
|
-
.dcw-
|
|
351
|
-
.dcw-
|
|
352
|
-
.dcw-
|
|
353
|
-
.dcw-
|
|
354
|
-
.dcw-card>summary:hover{background:var(--dcw-bg2)}
|
|
355
|
-
.dcw-chevron{transition:transform .16s ease;color:var(--dcw-muted)}
|
|
356
|
-
.dcw-card[open] .dcw-chevron{transform:rotate(90deg)}
|
|
357
|
-
.dcw-preview{margin:0;padding:11px 14px 13px;border-top:1px solid var(--dcw-border);overflow-x:auto;background:var(--dcw-bg2);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.7;white-space:pre;color:var(--dcw-fg)}
|
|
358
|
-
.dcw-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:14px;padding:11px 2px 4px;border-top:1px solid var(--dcw-border)}
|
|
359
|
-
.dcw-btn{height:30px;padding:0 13px;border:1px solid var(--dcw-border);border-radius:7px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;font-size:12.5px;cursor:pointer}
|
|
389
|
+
.dcw-input:focus{outline:none;border-color:var(--dcw-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--dcw-accent) 16%,transparent)}
|
|
390
|
+
.dcw-input:disabled{opacity:.55}
|
|
391
|
+
.dcw-preview{margin:0;border-top:1px solid var(--dcw-border)}
|
|
392
|
+
.dcw-preview-head{display:flex;align-items:center;gap:7px;padding:8px 14px;border-bottom:1px solid var(--dcw-border);background:var(--dcw-bg2);color:var(--dcw-muted);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}
|
|
393
|
+
.dcw-preview-dot{width:6px;height:6px;border-radius:50%;background:var(--dcw-faint)}
|
|
394
|
+
.dcw-preview-body{margin:0;padding:12px 14px 14px;overflow-x:auto;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.7;white-space:pre;color:var(--dcw-fg)}
|
|
395
|
+
.dcw-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:16px;padding:12px 0 4px;border-top:1px solid var(--dcw-border)}
|
|
396
|
+
.dcw-btn{height:32px;padding:0 14px;border:1px solid var(--dcw-border);border-radius:8px;background:var(--dcw-bg1);color:var(--dcw-fg);font:inherit;font-size:12.5px;cursor:pointer}
|
|
360
397
|
.dcw-btn:hover:enabled{border-color:color-mix(in srgb,var(--dcw-fg) 32%,transparent)}
|
|
361
398
|
.dcw-btn:focus-visible{outline:2px solid var(--dcw-accent);outline-offset:1px}
|
|
362
399
|
.dcw-btn:disabled{opacity:.45;cursor:default}
|
|
363
400
|
.dcw-btn-primary{background:var(--dcw-accent);border-color:transparent;color:var(--dsw-alias-label-primary-foreground,#fff)}
|
|
364
401
|
.dcw-btn-primary:hover:enabled{filter:brightness(1.06)}
|
|
365
|
-
.dcw-status{flex:1 1
|
|
402
|
+
.dcw-status{flex:1 1 180px;margin:0;min-height:20px;color:var(--dcw-muted);font-size:12px}
|
|
366
403
|
.dcw-status[data-tone="warn"]{color:var(--dcw-danger)}
|
|
367
|
-
.dcw-status[data-tone="ok"]{color:var(--
|
|
368
|
-
.dcw-
|
|
369
|
-
.dcw-note-action{margin-left:auto;flex:none;padding:3px 10px;border:1px solid currentColor;border-radius:6px;background:transparent;color:inherit;font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}
|
|
370
|
-
.dcw-note-action:hover{background:var(--dcw-bg1)}
|
|
371
|
-
@media (max-width:640px){.dcw-row{grid-template-columns:1fr;gap:4px}.dcw-row-label{padding-top:0}.dcw-flags{grid-template-columns:1fr}.dcw-flag:nth-child(2){border-top:1px solid var(--dcw-border)}}
|
|
404
|
+
.dcw-status[data-tone="ok"]{color:var(--dcw-success)}
|
|
405
|
+
@media (max-width:520px){.dcw-style-options{grid-template-columns:1fr}.dcw-row{grid-template-columns:minmax(0,1fr) auto}.dcw-row-value .dcw-input{grid-column:1/-1}}
|
|
372
406
|
`
|
|
373
407
|
|
|
374
408
|
function ensureStyles() {
|
|
@@ -382,7 +416,7 @@ function ensureStyles() {
|
|
|
382
416
|
|
|
383
417
|
function Switch(props) {
|
|
384
418
|
return h(
|
|
385
|
-
'
|
|
419
|
+
'label',
|
|
386
420
|
{ className: 'dcw-switch' },
|
|
387
421
|
h('input', {
|
|
388
422
|
type: 'checkbox',
|
|
@@ -395,98 +429,139 @@ function Switch(props) {
|
|
|
395
429
|
)
|
|
396
430
|
}
|
|
397
431
|
|
|
398
|
-
|
|
432
|
+
/** 设置页的分组卡片:标题下一句说明,正文按行排控件。 */
|
|
433
|
+
function Section(props) {
|
|
399
434
|
return h(
|
|
400
|
-
'
|
|
401
|
-
{
|
|
402
|
-
h(
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
435
|
+
'section',
|
|
436
|
+
{ className: 'dcw-card' },
|
|
437
|
+
h(
|
|
438
|
+
'div',
|
|
439
|
+
{ className: 'dcw-card-head' },
|
|
440
|
+
h('h4', { className: 'dcw-card-title' }, props.title),
|
|
441
|
+
props.hint ? h('p', { className: 'dcw-card-hint' }, props.hint) : null,
|
|
442
|
+
),
|
|
443
|
+
h('div', { className: 'dcw-card-body' }, props.children),
|
|
406
444
|
)
|
|
407
445
|
}
|
|
408
446
|
|
|
409
|
-
|
|
447
|
+
/** 文件头可填写的一行:标题与开关一行,输入框整行在下;关闭时收起输入框。 */
|
|
448
|
+
function ValueRow(props) {
|
|
449
|
+
const entry = props.entry
|
|
410
450
|
return h(
|
|
411
|
-
'
|
|
412
|
-
{
|
|
413
|
-
|
|
451
|
+
'div',
|
|
452
|
+
{
|
|
453
|
+
className: 'dcw-row dcw-row-value',
|
|
454
|
+
onClick: (event) => {
|
|
455
|
+
if (props.disabled) return
|
|
456
|
+
if (event.target.closest('.dcw-switch, .dcw-input')) return
|
|
457
|
+
props.onChange({ ...entry, enabled: !entry.enabled })
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
h(
|
|
461
|
+
'div',
|
|
462
|
+
{ className: 'dcw-row-main' },
|
|
463
|
+
h('span', { className: 'dcw-row-label' }, TAG_LABEL[props.tag]),
|
|
464
|
+
),
|
|
465
|
+
h(Switch, {
|
|
466
|
+
label: TAG_LABEL[props.tag],
|
|
467
|
+
checked: entry.enabled,
|
|
468
|
+
disabled: props.disabled,
|
|
469
|
+
onChange: (enabled) => props.onChange({ ...entry, enabled }),
|
|
470
|
+
}),
|
|
471
|
+
entry.enabled
|
|
472
|
+
? h('input', {
|
|
473
|
+
className: 'dcw-input',
|
|
474
|
+
type: 'text',
|
|
475
|
+
value: entry.value,
|
|
476
|
+
placeholder: props.placeholder,
|
|
477
|
+
disabled: props.disabled,
|
|
478
|
+
'aria-label': `${TAG_LABEL[props.tag]} 的值`,
|
|
479
|
+
onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
|
|
480
|
+
})
|
|
481
|
+
: null,
|
|
414
482
|
)
|
|
415
483
|
}
|
|
416
484
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
author: 'authorHint',
|
|
420
|
-
date: 'dateHint',
|
|
421
|
-
copyright: 'copyrightHint',
|
|
422
|
-
version: 'versionHint',
|
|
423
|
-
param: 'paramHint',
|
|
424
|
-
returns: 'returnHint',
|
|
425
|
-
throws: 'throwsHint',
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function ValueRow(props) {
|
|
429
|
-
const s = props.strings
|
|
430
|
-
const entry = props.entry
|
|
485
|
+
/** 只有开关的标签行;说明放在标题下面,不再放一个不可用的假输入框。 */
|
|
486
|
+
function FlagRow(props) {
|
|
431
487
|
return h(
|
|
432
488
|
'div',
|
|
433
|
-
{
|
|
434
|
-
|
|
489
|
+
{
|
|
490
|
+
className: 'dcw-row dcw-row-flag',
|
|
491
|
+
onClick: (event) => {
|
|
492
|
+
if (props.disabled) return
|
|
493
|
+
if (event.target.closest('.dcw-switch, .dcw-input')) return
|
|
494
|
+
props.onChange(!props.checked)
|
|
495
|
+
},
|
|
496
|
+
},
|
|
435
497
|
h(
|
|
436
498
|
'div',
|
|
437
|
-
{ className: 'dcw-row-
|
|
438
|
-
h(
|
|
439
|
-
|
|
440
|
-
checked: entry.enabled,
|
|
441
|
-
disabled: props.disabled,
|
|
442
|
-
onChange: (enabled) => props.onChange({ ...entry, enabled }),
|
|
443
|
-
}),
|
|
444
|
-
h('span', { className: 'dcw-state', 'aria-hidden': 'true' }, entry.enabled ? s.on : s.off),
|
|
445
|
-
h('input', {
|
|
446
|
-
className: 'dcw-input',
|
|
447
|
-
type: 'text',
|
|
448
|
-
value: entry.value,
|
|
449
|
-
placeholder: props.tag === 'brief' ? s.blankBrief : s.emptyValue,
|
|
450
|
-
disabled: props.disabled || !entry.enabled,
|
|
451
|
-
'aria-label': `${TAG_LABEL[props.tag]} 的值`,
|
|
452
|
-
onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
|
|
453
|
-
}),
|
|
454
|
-
h('p', { className: 'dcw-row-hint' }, s[HINT_KEY[props.tag]] || ''),
|
|
499
|
+
{ className: 'dcw-row-main' },
|
|
500
|
+
h('span', { className: 'dcw-row-label' }, TAG_LABEL[props.tag]),
|
|
501
|
+
props.hint ? h('span', { className: 'dcw-row-hint' }, props.hint) : null,
|
|
455
502
|
),
|
|
503
|
+
h(Switch, {
|
|
504
|
+
label: TAG_LABEL[props.tag],
|
|
505
|
+
checked: props.checked,
|
|
506
|
+
disabled: props.disabled,
|
|
507
|
+
onChange: props.onChange,
|
|
508
|
+
}),
|
|
456
509
|
)
|
|
457
510
|
}
|
|
458
511
|
|
|
459
|
-
|
|
512
|
+
/** 值由文件或语法固定的行:右侧写成普通文本,不放锁图标。 */
|
|
513
|
+
function LockRow(props) {
|
|
460
514
|
return h(
|
|
461
515
|
'div',
|
|
462
|
-
{ className: 'dcw-
|
|
516
|
+
{ className: 'dcw-row dcw-row-fixed' },
|
|
463
517
|
h(
|
|
464
518
|
'div',
|
|
465
|
-
{ className: 'dcw-row-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
519
|
+
{ className: 'dcw-row-main' },
|
|
520
|
+
h('span', { className: 'dcw-row-label' }, props.label),
|
|
521
|
+
),
|
|
522
|
+
h('span', { className: 'dcw-fixed-value' }, props.value),
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** 两种声明注释符号:两张等宽卡片,上面符号、下面示例。 */
|
|
527
|
+
function StyleRow(props) {
|
|
528
|
+
const options = [
|
|
529
|
+
{ value: 'slash', symbol: '///', example: props.slashExample },
|
|
530
|
+
{ value: 'block', symbol: '/** */', example: props.blockExample },
|
|
531
|
+
]
|
|
532
|
+
return h(
|
|
533
|
+
'div',
|
|
534
|
+
{ className: 'dcw-style-options', role: 'radiogroup', 'aria-label': props.label },
|
|
535
|
+
options.map((option) =>
|
|
536
|
+
h(
|
|
537
|
+
'button',
|
|
538
|
+
{
|
|
539
|
+
key: option.value,
|
|
540
|
+
type: 'button',
|
|
541
|
+
role: 'radio',
|
|
542
|
+
'aria-checked': props.value === option.value ? 'true' : 'false',
|
|
543
|
+
className: 'dcw-style-option',
|
|
544
|
+
disabled: props.disabled,
|
|
545
|
+
onClick: () => props.onChange(option.value),
|
|
546
|
+
},
|
|
547
|
+
h('span', { className: 'dcw-style-symbol' }, option.symbol),
|
|
548
|
+
h('span', { className: 'dcw-style-example' }, option.example),
|
|
549
|
+
),
|
|
473
550
|
),
|
|
474
|
-
h('p', { className: 'dcw-row-hint' }, props.hint || ''),
|
|
475
551
|
)
|
|
476
552
|
}
|
|
477
553
|
|
|
478
554
|
function Preview(props) {
|
|
479
555
|
const s = props.strings
|
|
480
556
|
return h(
|
|
481
|
-
|
|
482
|
-
{
|
|
557
|
+
Section,
|
|
558
|
+
{ title: s.preview, hint: s.previewHint },
|
|
483
559
|
h(
|
|
484
560
|
'div',
|
|
485
|
-
{ className: 'dcw-
|
|
486
|
-
h('
|
|
487
|
-
h('
|
|
561
|
+
{ className: 'dcw-preview' },
|
|
562
|
+
h('div', { className: 'dcw-preview-head' }, h('span', { className: 'dcw-preview-dot' }), SAMPLE_FILE),
|
|
563
|
+
h('pre', { className: 'dcw-preview-body' }, previewText(props.config, SAMPLE_FILE)),
|
|
488
564
|
),
|
|
489
|
-
h('pre', { className: 'dcw-preview' }, previewText(props.config, SAMPLE_FILE)),
|
|
490
565
|
)
|
|
491
566
|
}
|
|
492
567
|
|
|
@@ -542,6 +617,28 @@ const ENTRY_ID = 'cpp-workflow'
|
|
|
542
617
|
const FORM_RETRY_MS = 500
|
|
543
618
|
const MAX_FORM_RETRIES = 20
|
|
544
619
|
|
|
620
|
+
/** 文件头可填写标签的占位文字;顺序与 HEADER_TAGS 一致。 */
|
|
621
|
+
const HEADER_PLACEHOLDER = {
|
|
622
|
+
brief: 'briefPlaceholder',
|
|
623
|
+
author: 'authorPlaceholder',
|
|
624
|
+
date: 'datePlaceholder',
|
|
625
|
+
copyright: 'copyrightPlaceholder',
|
|
626
|
+
version: 'versionPlaceholder',
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** 只有开关的标签按区块取占位文字。 */
|
|
630
|
+
const FLAG_PLACEHOLDER = {
|
|
631
|
+
param: 'paramPlaceholder',
|
|
632
|
+
returns: 'returnPlaceholder',
|
|
633
|
+
throws: 'throwsPlaceholder',
|
|
634
|
+
pre: 'prePlaceholder',
|
|
635
|
+
post: 'postPlaceholder',
|
|
636
|
+
retval: 'retvalPlaceholder',
|
|
637
|
+
tparam: 'tparamPlaceholder',
|
|
638
|
+
note: 'notePlaceholder',
|
|
639
|
+
see: 'seePlaceholder',
|
|
640
|
+
}
|
|
641
|
+
|
|
545
642
|
function ConfigPage(props) {
|
|
546
643
|
const s = t()
|
|
547
644
|
ensureStyles()
|
|
@@ -576,9 +673,11 @@ function ConfigPage(props) {
|
|
|
576
673
|
return () => clearTimeout(timer)
|
|
577
674
|
}, [form, attempt])
|
|
578
675
|
|
|
676
|
+
// 脏标记按与已保存值的差集算:把开关关了又开回原值时,页面不该再说“有未保存的改动”。
|
|
579
677
|
const update = (patch) => {
|
|
580
|
-
|
|
581
|
-
|
|
678
|
+
const next = { ...draft, ...patch }
|
|
679
|
+
setDraft(next)
|
|
680
|
+
setDirty(diffOperations(next, base).length > 0)
|
|
582
681
|
setMessage('')
|
|
583
682
|
setTone('')
|
|
584
683
|
}
|
|
@@ -593,8 +692,9 @@ function ConfigPage(props) {
|
|
|
593
692
|
|
|
594
693
|
// 恢复默认值:只填进草稿,点“保存”后才写盘,避免误触直接覆盖已保存的配置。
|
|
595
694
|
const restoreDefaults = () => {
|
|
596
|
-
|
|
597
|
-
|
|
695
|
+
const next = normalizeConfig(DEFAULT_CONFIG)
|
|
696
|
+
setDraft(next)
|
|
697
|
+
setDirty(diffOperations(next, base).length > 0)
|
|
598
698
|
setMessage(s.restored)
|
|
599
699
|
setTone('')
|
|
600
700
|
}
|
|
@@ -635,15 +735,25 @@ function ConfigPage(props) {
|
|
|
635
735
|
const editable = Boolean(form)
|
|
636
736
|
const disabled = !ready
|
|
637
737
|
|
|
738
|
+
/** 一个标签一行:标题与说明,右侧开关。 */
|
|
739
|
+
const flag = (tag, checked) =>
|
|
740
|
+
h(FlagRow, {
|
|
741
|
+
key: tag,
|
|
742
|
+
tag,
|
|
743
|
+
checked,
|
|
744
|
+
disabled,
|
|
745
|
+
hint: s[FLAG_PLACEHOLDER[tag]],
|
|
746
|
+
onChange: (value) => update({ [tag]: value }),
|
|
747
|
+
})
|
|
748
|
+
|
|
638
749
|
return h(
|
|
639
750
|
'div',
|
|
640
751
|
{ className: 'dcw-page' },
|
|
641
752
|
h(
|
|
642
753
|
'header',
|
|
643
754
|
{ className: 'dcw-head' },
|
|
644
|
-
h('
|
|
645
|
-
h('
|
|
646
|
-
h('p', { className: 'dcw-lede' }, s.intro),
|
|
755
|
+
h('h3', { className: 'dcw-title' }, s.heading),
|
|
756
|
+
h('p', { className: 'dcw-lede' }, s.lede),
|
|
647
757
|
),
|
|
648
758
|
missing
|
|
649
759
|
? h(
|
|
@@ -656,11 +766,7 @@ function ConfigPage(props) {
|
|
|
656
766
|
s.readOnly,
|
|
657
767
|
props.hasService === false ? h('span', { className: 'dcw-note-code' }, s.noService) : null,
|
|
658
768
|
),
|
|
659
|
-
h(
|
|
660
|
-
'button',
|
|
661
|
-
{ type: 'button', className: 'dcw-note-action', onClick: reload },
|
|
662
|
-
s.reload,
|
|
663
|
-
),
|
|
769
|
+
h('button', { type: 'button', className: 'dcw-note-action', onClick: reload }, s.reload),
|
|
664
770
|
)
|
|
665
771
|
: null,
|
|
666
772
|
editable && !ready && status !== 'missing'
|
|
@@ -672,78 +778,38 @@ function ConfigPage(props) {
|
|
|
672
778
|
)
|
|
673
779
|
: null,
|
|
674
780
|
h(
|
|
675
|
-
|
|
676
|
-
{
|
|
677
|
-
h(
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
h('p', { className: 'dcw-row-hint' }, s.fixedHint),
|
|
692
|
-
),
|
|
693
|
-
),
|
|
781
|
+
Section,
|
|
782
|
+
{ title: s.style, hint: s.styleHint },
|
|
783
|
+
h(StyleRow, {
|
|
784
|
+
label: s.style,
|
|
785
|
+
value: draft.commentStyle,
|
|
786
|
+
disabled,
|
|
787
|
+
slashExample: s.styleSlashExample,
|
|
788
|
+
blockExample: s.styleBlockExample,
|
|
789
|
+
onChange: (value) => update({ commentStyle: value }),
|
|
790
|
+
}),
|
|
791
|
+
h(LockRow, { label: s.bodyRow, value: s.bodyPlaceholder }),
|
|
792
|
+
),
|
|
793
|
+
h(
|
|
794
|
+
Section,
|
|
795
|
+
{ title: s.header, hint: s.headerHint },
|
|
796
|
+
h(LockRow, { label: TAG_LABEL.file, value: s.filePlaceholder }),
|
|
694
797
|
...HEADER_TAGS.map((tag) =>
|
|
695
798
|
h(ValueRow, {
|
|
696
799
|
key: tag,
|
|
697
800
|
tag,
|
|
698
|
-
strings: s,
|
|
699
801
|
entry: draft[tag],
|
|
700
802
|
disabled,
|
|
803
|
+
placeholder: s[HEADER_PLACEHOLDER[tag]],
|
|
701
804
|
onChange: (entry) => update({ [tag]: entry }),
|
|
702
805
|
}),
|
|
703
806
|
),
|
|
704
807
|
),
|
|
808
|
+
h(Section, { title: s.type, hint: s.typeHint }, ...TYPE_TAGS.map((tag) => flag(tag, draft[tag]))),
|
|
705
809
|
h(
|
|
706
|
-
|
|
707
|
-
{
|
|
708
|
-
|
|
709
|
-
'div',
|
|
710
|
-
{ className: 'dcw-card-head' },
|
|
711
|
-
h('h4', { className: 'dcw-card-title', id: 'dcw-contract-title' }, s.contract),
|
|
712
|
-
h('p', { className: 'dcw-card-hint' }, s.contractHint),
|
|
713
|
-
),
|
|
714
|
-
h(
|
|
715
|
-
'div',
|
|
716
|
-
{ className: 'dcw-flags' },
|
|
717
|
-
...CONTRACT_TAGS.map((tag) =>
|
|
718
|
-
h(Flag, {
|
|
719
|
-
key: tag,
|
|
720
|
-
tag,
|
|
721
|
-
checked: draft[tag],
|
|
722
|
-
disabled,
|
|
723
|
-
hint: s[HINT_KEY[tag]],
|
|
724
|
-
onChange: (value) => update({ [tag]: value }),
|
|
725
|
-
}),
|
|
726
|
-
),
|
|
727
|
-
),
|
|
728
|
-
),
|
|
729
|
-
h(
|
|
730
|
-
'details',
|
|
731
|
-
{ className: 'dcw-card' },
|
|
732
|
-
h('summary', null, h('span', { className: 'dcw-card-title' }, s.extra), h(Chevron)),
|
|
733
|
-
h('p', { className: 'dcw-card-hint', style: { margin: '0 14px 2px' } }, s.extraHint),
|
|
734
|
-
h(
|
|
735
|
-
'div',
|
|
736
|
-
{ className: 'dcw-flags' },
|
|
737
|
-
...EXTRA_TAGS.map((tag) =>
|
|
738
|
-
h(Flag, {
|
|
739
|
-
key: tag,
|
|
740
|
-
tag,
|
|
741
|
-
checked: draft[tag],
|
|
742
|
-
disabled,
|
|
743
|
-
onChange: (value) => update({ [tag]: value }),
|
|
744
|
-
}),
|
|
745
|
-
),
|
|
746
|
-
),
|
|
810
|
+
Section,
|
|
811
|
+
{ title: s.declaration, hint: s.declarationHint },
|
|
812
|
+
...[...CONTRACT_TAGS, ...FUNCTION_EXTRA_TAGS].map((tag) => flag(tag, draft[tag])),
|
|
747
813
|
),
|
|
748
814
|
h(Preview, { config: draft, strings: s }),
|
|
749
815
|
editable
|