@chengzhao-dev/dsh-cpp-workflow 0.6.0 → 0.6.1

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 CHANGED
@@ -19,13 +19,13 @@ The exception material follows the C++ Core Guidelines' Error handling section a
19
19
  Pinned npm install:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.0
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.1
23
23
  ```
24
24
 
25
25
  The matching Git tag:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.0
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.1
29
29
  ```
30
30
 
31
31
  The desktop app owns the `desktop` profile. If the command-line `add` is rejected, add the plugin in the app settings and use the same pinned npm version. A Git tag install uses the prebuilt `lib/` committed in the repository and runs no build on the target machine; the package has no install script that needs approval.
package/README.md CHANGED
@@ -19,13 +19,13 @@
19
19
  npm 安装钉死版本:
20
20
 
21
21
  ```bash
22
- dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.0
22
+ dsh plugin --profile web add npm:@chengzhao-dev/dsh-cpp-workflow@0.6.1
23
23
  ```
24
24
 
25
25
  同版本的 Git 标签:
26
26
 
27
27
  ```bash
28
- dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.0
28
+ dsh plugin --profile web add github:chengzhao-dev/dsh-cpp-workflow#v0.6.1
29
29
  ```
30
30
 
31
31
  桌面应用独占 `desktop` profile。命令行 `add` 被拒绝时,在应用设置里添加插件,地址填同一个 npm 版本。Git 标签安装使用仓库内已构建的 `lib/`,不在目标机器上重新构建;包内也不含需要授权执行的安装脚本。
package/lib/client.js CHANGED
@@ -57,6 +57,8 @@ const TAG_LABEL = {
57
57
  tparam: '@tparam',
58
58
  retval: '@retval',
59
59
  };
60
+ /** 预览里的示例摘要:用户没填 @brief 时用它,避免预览出现空标签。 */
61
+ const SAMPLE_BRIEF = '定义井字棋棋盘、格子坐标与格子类型。';
60
62
  /** 出厂配置:文件头只留 @file 与 @brief,契约标签全开,隐私标签全关。 */
61
63
  const DEFAULT_CONFIG = {
62
64
  brief: { enabled: true, value: '' },
@@ -120,6 +122,10 @@ function renderFileHeader(raw, fileName) {
120
122
  function previewParts(raw, sampleName) {
121
123
  const config = normalizeConfig(raw);
122
124
  const header = renderFileHeader(config, sampleName || 'board.h');
125
+ // 预览是示例:@brief 开着但用户没填句子时,用示例文件自己的摘要顶替。
126
+ const briefIndex = header.indexOf('/// @brief');
127
+ if (briefIndex !== -1)
128
+ header[briefIndex] = `/// @brief ${SAMPLE_BRIEF}`;
123
129
  const contract = [];
124
130
  if (config.param)
125
131
  contract.push('/// @param position 目标行列坐标。');
@@ -199,14 +205,21 @@ const STRINGS = {
199
205
  title: '文档注释',
200
206
  intro: '文件头与函数契约默认写出哪些 Doxygen 标签。配置只影响代理写注释的默认值,不改已有源码。',
201
207
  header: '文件头',
208
+ headerHint: '每个手写源文件开头的一段 ///;带值的标签填写后写出,@brief 留空由文件自述。',
202
209
  contract: '函数契约',
210
+ contractHint: '声明注释里的参数与失败方式;关闭后不再要求写出这些标签。',
203
211
  extra: '更多标签',
204
212
  extraHint: '默认关闭;需要时在单个函数上补写。',
205
213
  file: '@file',
214
+ fixed: '自动',
206
215
  fileHint: '固定为当前文件名,不参与配置。',
207
- emptyValue: '留空则由每个文件自己写',
208
- on: '写出',
209
- off: '不写',
216
+ briefHint: '一句话说明本文件定义或实现什么;留空则由每个文件自己写。',
217
+ authorHint: '作者署名;留空不写。',
218
+ dateHint: '创建或修改日期;留空不写。',
219
+ copyrightHint: '版权声明;留空不写。',
220
+ versionHint: '文件版本号;留空不写。',
221
+ emptyValue: '留空不写',
222
+ briefEmpty: '留空则由文件自述',
210
223
  preview: '预览',
211
224
  previewHint: '示例文件名 board.h;预览随开关即时更新。',
212
225
  save: '保存',
@@ -217,6 +230,7 @@ const STRINGS = {
217
230
  loading: '正在读取配置…',
218
231
  unreadable: '当前账户不能修改这份配置。',
219
232
  readOnly: '此处只显示出厂值。要修改配置,请到「插件」页打开 dsh-cpp-workflow 的配置卡片。',
233
+ openSettings: '打开插件配置',
220
234
  noForm: '这个入口没有可写表单,配置在插件页修改。',
221
235
  },
222
236
  en: {
@@ -224,14 +238,21 @@ const STRINGS = {
224
238
  title: 'Documentation comments',
225
239
  intro: 'Which Doxygen tags the agent writes by default. This changes defaults only; existing sources are untouched.',
226
240
  header: 'File header',
241
+ headerHint: 'The /// block at the top of every hand-written source; value tags are written only when filled.',
227
242
  contract: 'Function contract',
243
+ contractHint: 'Parameter and failure tags in declaration comments; turning one off stops requiring it.',
228
244
  extra: 'More tags',
229
245
  extraHint: 'Off by default; add them per function when needed.',
230
246
  file: '@file',
247
+ fixed: 'Auto',
231
248
  fileHint: 'Always the current file name; not configurable.',
232
- emptyValue: 'Leave empty and each file writes its own',
233
- on: 'Write',
234
- off: 'Omit',
249
+ briefHint: 'One sentence about what this file defines; leave empty and each file writes its own.',
250
+ authorHint: 'Author credit; left empty, omitted.',
251
+ dateHint: 'Creation or change date; left empty, omitted.',
252
+ copyrightHint: 'Copyright notice; left empty, omitted.',
253
+ versionHint: 'File version; left empty, omitted.',
254
+ emptyValue: 'Empty means omit',
255
+ briefEmpty: 'Empty: the file writes its own',
235
256
  preview: 'Preview',
236
257
  previewHint: 'Sample name board.h; the preview follows the switches.',
237
258
  save: 'Save',
@@ -242,6 +263,7 @@ const STRINGS = {
242
263
  loading: 'Reading configuration…',
243
264
  unreadable: 'This account cannot edit the configuration.',
244
265
  readOnly: 'Showing factory defaults. Edit the dsh-cpp-workflow card on the Plugins page.',
266
+ openSettings: 'Open plugin settings',
245
267
  noForm: 'This entry has no writable form; edit it on the Plugins page.',
246
268
  },
247
269
  }
@@ -256,84 +278,152 @@ function t() {
256
278
  }
257
279
 
258
280
  const palette = {
259
- muted: 'var(--dsw-text-tertiary, #6b7280)',
260
- line: 'var(--dsw-border-subtle, rgba(127, 127, 127, 0.24))',
261
- surface: 'var(--dsw-surface-raised, rgba(127, 127, 127, 0.06))',
281
+ label: 'var(--dsw-alias-label-primary, inherit)',
282
+ muted: 'var(--dsw-alias-label-secondary, #6b7280)',
283
+ faint: 'var(--dsw-alias-label-tertiary, #8b93a1)',
284
+ line: 'var(--dsw-alias-border-l2, rgba(127, 127, 127, 0.24))',
285
+ surface: 'var(--dsw-alias-bg-layer-2, rgba(127, 127, 127, 0.06))',
286
+ field: 'var(--dsw-alias-bg-layer-1, rgba(127, 127, 127, 0.04))',
287
+ brand: 'var(--dsw-alias-brand-primary, #4f6bed)',
262
288
  mono: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
263
289
  }
264
290
 
265
- function rowStyle(extra) {
266
- return {
267
- display: 'flex',
268
- alignItems: 'center',
269
- gap: 10,
270
- padding: '7px 0',
271
- borderBottom: `1px solid ${palette.line}`,
272
- ...extra,
273
- }
274
- }
275
-
276
- function hintStyle() {
277
- return { margin: '2px 0 0', fontSize: 12, lineHeight: 1.5, color: palette.muted }
278
- }
291
+ const hintStyle = () => ({ margin: '2px 0 0', fontSize: 12, lineHeight: 1.55, color: palette.muted })
292
+ const labelCellStyle = () => ({
293
+ padding: '10px 0',
294
+ fontFamily: palette.mono,
295
+ fontSize: 12.5,
296
+ lineHeight: '20px',
297
+ color: palette.label,
298
+ whiteSpace: 'nowrap',
299
+ borderBottom: `1px solid ${palette.line}`,
300
+ })
301
+ const controlCellStyle = () => ({
302
+ padding: '10px 0',
303
+ minWidth: 0,
304
+ borderBottom: `1px solid ${palette.line}`,
305
+ })
306
+ const gridStyle = () => ({ display: 'grid', gridTemplateColumns: '92px minmax(0, 1fr)', columnGap: 14 })
279
307
 
280
- function Switch(props) {
308
+ function Toggle(props) {
281
309
  return h(
282
- 'label',
283
- { style: { display: 'inline-flex', alignItems: 'center', gap: 8, cursor: props.disabled ? 'default' : 'pointer', minWidth: 132 } },
284
- h('input', {
285
- type: 'checkbox',
286
- checked: Boolean(props.checked),
310
+ 'button',
311
+ {
312
+ type: 'button',
313
+ role: 'switch',
314
+ 'aria-checked': Boolean(props.checked),
315
+ 'aria-label': props.label,
287
316
  disabled: Boolean(props.disabled),
288
- onChange: (event) => props.onChange(event.target.checked),
289
- style: { margin: 0 },
317
+ onClick: () => props.onChange(!props.checked),
318
+ style: {
319
+ flex: 'none',
320
+ width: 34,
321
+ height: 20,
322
+ padding: 0,
323
+ border: 0,
324
+ borderRadius: 999,
325
+ position: 'relative',
326
+ cursor: props.disabled ? 'default' : 'pointer',
327
+ opacity: props.disabled ? 0.5 : 1,
328
+ background: props.checked ? palette.brand : 'var(--dsw-alias-bg-layer-4, rgba(127, 127, 127, 0.35))',
329
+ transition: 'background-color 0.15s ease',
330
+ },
331
+ },
332
+ h('span', {
333
+ 'aria-hidden': 'true',
334
+ style: {
335
+ position: 'absolute',
336
+ top: 2,
337
+ left: props.checked ? 16 : 2,
338
+ width: 16,
339
+ height: 16,
340
+ borderRadius: '50%',
341
+ background: '#fff',
342
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.25)',
343
+ transition: 'left 0.15s ease',
344
+ },
290
345
  }),
291
- h('span', { style: { fontSize: 13, fontFamily: palette.mono } }, props.label),
292
346
  )
293
347
  }
294
348
 
295
- function HeaderTagRow(props) {
349
+ function TagRow(props) {
296
350
  const s = props.strings
297
- const entry = props.entry
298
- return h(
299
- 'div',
300
- { style: rowStyle({ flexWrap: 'wrap' }) },
301
- h('div', { style: { flex: '1 1 280px', minWidth: 0 } },
302
- h(Switch, {
303
- label: TAG_LABEL[props.tag],
304
- checked: entry.enabled,
305
- onChange: (enabled) => props.onChange({ ...entry, enabled }),
306
- }),
307
- h('p', { style: hintStyle() }, TAG_TEXT[props.tag] || ''),
351
+ return [
352
+ h('div', { key: `${props.tag}-label`, style: labelCellStyle() }, props.label),
353
+ h(
354
+ 'div',
355
+ { key: `${props.tag}-control`, style: controlCellStyle() },
356
+ h(
357
+ 'div',
358
+ { style: { display: 'flex', alignItems: 'center', gap: 10, minHeight: 22, flexWrap: 'wrap' } },
359
+ props.control,
360
+ ),
361
+ props.hint ? h('p', { style: hintStyle() }, props.hint) : null,
308
362
  ),
309
- h('input', {
310
- type: 'text',
311
- value: entry.value,
312
- placeholder: s.emptyValue,
313
- disabled: !entry.enabled,
314
- 'aria-label': `${TAG_LABEL[props.tag]} value`,
315
- onChange: (event) => props.onChange({ ...entry, value: event.target.value }),
316
- style: {
317
- flex: '1 1 220px',
318
- minWidth: 0,
319
- fontSize: 13,
320
- padding: '5px 8px',
321
- borderRadius: 6,
322
- border: `1px solid ${palette.line}`,
323
- background: entry.enabled ? 'transparent' : palette.surface,
324
- color: 'inherit',
325
- },
363
+ ]
364
+ }
365
+
366
+ function ValueRow(props) {
367
+ const s = props.strings
368
+ return h(TagRow, {
369
+ tag: props.tag,
370
+ label: TAG_LABEL[props.tag],
371
+ strings: s,
372
+ hint: props.hint,
373
+ control: [
374
+ h(Toggle, {
375
+ key: 'toggle',
376
+ label: props.label || TAG_LABEL[props.tag],
377
+ checked: props.entry.enabled,
378
+ disabled: props.disabled,
379
+ onChange: (enabled) => !props.disabled && props.onChange({ ...props.entry, enabled }),
380
+ }),
381
+ h('input', {
382
+ key: 'input',
383
+ type: 'text',
384
+ value: props.entry.value,
385
+ placeholder: props.tag === 'brief' ? s.briefEmpty : s.emptyValue,
386
+ disabled: props.disabled || !props.entry.enabled,
387
+ 'aria-label': TAG_LABEL[props.tag],
388
+ onChange: (event) => props.onChange({ ...props.entry, value: event.target.value }),
389
+ style: {
390
+ flex: '1 1 180px',
391
+ minWidth: 0,
392
+ maxWidth: 320,
393
+ fontSize: 13,
394
+ padding: '4px 8px',
395
+ borderRadius: 6,
396
+ border: `1px solid ${palette.line}`,
397
+ background: props.entry.enabled && !props.disabled ? palette.field : 'transparent',
398
+ color: props.entry.enabled ? 'inherit' : palette.muted,
399
+ fontFamily: palette.mono,
400
+ },
401
+ }),
402
+ ],
403
+ })
404
+ }
405
+
406
+ function FlagRow(props) {
407
+ return h(TagRow, {
408
+ tag: props.tag,
409
+ label: TAG_LABEL[props.tag],
410
+ strings: props.strings,
411
+ hint: props.hint,
412
+ control: h(Toggle, {
413
+ label: TAG_LABEL[props.tag],
414
+ checked: Boolean(props.checked),
415
+ disabled: props.disabled,
416
+ onChange: (value) => !props.disabled && props.onChange(value),
326
417
  }),
327
- )
418
+ })
328
419
  }
329
420
 
330
421
  function Preview(props) {
331
422
  const s = props.strings
332
- const text = previewText(props.config, SAMPLE_FILE)
333
423
  return h(
334
424
  'section',
335
- { 'aria-label': s.preview, style: { marginTop: 18 } },
336
- h('h4', { style: { margin: '0 0 4px', fontSize: 13, fontWeight: 600 } }, s.preview),
425
+ { 'aria-label': s.preview, style: { marginTop: 20 } },
426
+ h('h4', { style: { margin: '0 0 4px', fontSize: 13, fontWeight: 600, color: palette.label } }, s.preview),
337
427
  h('p', { style: hintStyle() }, s.previewHint),
338
428
  h(
339
429
  'pre',
@@ -349,9 +439,10 @@ function Preview(props) {
349
439
  lineHeight: 1.65,
350
440
  fontFamily: palette.mono,
351
441
  whiteSpace: 'pre',
442
+ color: palette.label,
352
443
  },
353
444
  },
354
- text,
445
+ previewText(props.config, SAMPLE_FILE),
355
446
  ),
356
447
  )
357
448
  }
@@ -421,74 +512,127 @@ function ConfigPage(props) {
421
512
 
422
513
  const editable = Boolean(form)
423
514
  const editableReady = editable && ready
424
- const headerTags = ['brief', 'author', 'date', 'copyright', 'version']
515
+ const header = [
516
+ { tag: 'brief', hint: s.briefHint },
517
+ { tag: 'author', hint: s.authorHint },
518
+ { tag: 'date', hint: s.dateHint },
519
+ { tag: 'copyright', hint: s.copyrightHint },
520
+ { tag: 'version', hint: s.versionHint },
521
+ ]
522
+ const contractHints = { param: TAG_TEXT.param, returns: TAG_TEXT.returns, throws: TAG_TEXT.throws }
523
+ const extraHints = { note: TAG_TEXT.note, see: TAG_TEXT.see, pre: TAG_TEXT.pre, post: TAG_TEXT.post, tparam: TAG_TEXT.tparam, retval: TAG_TEXT.retval }
425
524
 
426
525
  return h(
427
526
  'div',
428
- { style: { display: 'flex', flexDirection: 'column', gap: 4, padding: '4px 0 12px', maxWidth: 720 } },
429
- h('h3', { style: { margin: '0 0 2px', fontSize: 15, fontWeight: 600 } }, s.title),
527
+ { style: { display: 'flex', flexDirection: 'column', padding: '4px 0 12px', maxWidth: 720, color: palette.label } },
528
+ h('h3', { style: { margin: '0 0 4px', fontSize: 15, fontWeight: 600 } }, s.title),
430
529
  h('p', { style: hintStyle() }, s.intro),
431
530
  !editable
432
- ? h('p', { role: 'status', style: { ...hintStyle(), color: 'inherit', marginTop: 8 } }, s.readOnly)
531
+ ? h(
532
+ 'div',
533
+ { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginTop: 8 } },
534
+ h('p', { role: 'status', style: { ...hintStyle(), margin: 0 } }, s.readOnly),
535
+ props.openPluginPage
536
+ ? h('button', { type: 'button', onClick: props.openPluginPage, style: secondaryButton(true) }, s.openSettings)
537
+ : null,
538
+ )
433
539
  : null,
434
540
  editable && !ready && status !== 'missing'
435
541
  ? h('p', { role: 'status', style: hintStyle() }, status === 'loading' ? s.loading : s.unreadable)
436
542
  : null,
437
543
  h(
438
- 'fieldset',
439
- { style: { margin: '14px 0 0', padding: 0, border: 0 } },
440
- h('legend', { style: { padding: 0, fontSize: 13, fontWeight: 600 } }, s.header),
544
+ 'section',
545
+ { style: { marginTop: 18 } },
546
+ h('h4', { style: { margin: '0 0 4px', fontSize: 13, fontWeight: 600 } }, s.header),
547
+ h('p', { style: hintStyle() }, s.headerHint),
441
548
  h(
442
549
  'div',
443
- { style: rowStyle() },
444
- h(Switch, { label: s.file, checked: true, disabled: true, onChange: () => {} }),
445
- h('p', { style: { ...hintStyle(), margin: 0 } }, s.fileHint),
446
- ),
447
- ...headerTags.map((tag) =>
448
- h(HeaderTagRow, {
449
- key: tag,
450
- tag,
451
- strings: s,
452
- entry: draft[tag],
453
- onChange: (entry) => update({ [tag]: entry }),
454
- }),
550
+ { role: 'group', 'aria-label': s.header, style: { ...gridStyle(), marginTop: 8 } },
551
+ h(
552
+ 'div',
553
+ { key: 'file-label', style: labelCellStyle() },
554
+ s.file,
555
+ ),
556
+ h(
557
+ 'div',
558
+ { key: 'file-control', style: controlCellStyle() },
559
+ h(
560
+ 'div',
561
+ { style: { display: 'flex', alignItems: 'center', gap: 10, minHeight: 22 } },
562
+ h(
563
+ 'span',
564
+ {
565
+ style: {
566
+ fontSize: 12,
567
+ lineHeight: '20px',
568
+ padding: '0 8px',
569
+ borderRadius: 999,
570
+ border: `1px solid ${palette.line}`,
571
+ color: palette.muted,
572
+ },
573
+ },
574
+ s.fixed,
575
+ ),
576
+ h('code', { style: { fontSize: 12.5, fontFamily: palette.mono, color: palette.muted } }, SAMPLE_FILE),
577
+ ),
578
+ h('p', { style: hintStyle() }, s.fileHint),
579
+ ),
580
+ ...header.flatMap(({ tag, hint }) =>
581
+ h(ValueRow, {
582
+ key: tag,
583
+ tag,
584
+ hint,
585
+ strings: s,
586
+ disabled: !editable,
587
+ entry: draft[tag],
588
+ onChange: (entry) => update({ [tag]: entry }),
589
+ }),
590
+ ),
455
591
  ),
456
592
  ),
457
593
  h(
458
- 'fieldset',
459
- { style: { margin: '18px 0 0', padding: 0, border: 0 } },
460
- h('legend', { style: { padding: 0, fontSize: 13, fontWeight: 600 } }, s.contract),
594
+ 'section',
595
+ { style: { marginTop: 20 } },
596
+ h('h4', { style: { margin: '0 0 4px', fontSize: 13, fontWeight: 600 } }, s.contract),
597
+ h('p', { style: hintStyle() }, s.contractHint),
461
598
  h(
462
599
  'div',
463
- { style: { display: 'flex', flexWrap: 'wrap', gap: '6px 18px', padding: '8px 0 2px' } },
464
- ...CONTRACT_TAGS.map((tag) =>
465
- h(
466
- 'div',
467
- { key: tag, style: { flex: '1 1 180px', minWidth: 180 } },
468
- h(Switch, {
469
- label: TAG_LABEL[tag],
470
- checked: draft[tag],
471
- onChange: (value) => update({ [tag]: value }),
472
- }),
473
- h('p', { style: hintStyle() }, TAG_TEXT[tag]),
474
- ),
600
+ { role: 'group', 'aria-label': s.contract, style: { ...gridStyle(), marginTop: 8 } },
601
+ ...CONTRACT_TAGS.flatMap((tag) =>
602
+ h(FlagRow, {
603
+ key: tag,
604
+ tag,
605
+ hint: contractHints[tag],
606
+ strings: s,
607
+ disabled: !editable,
608
+ checked: draft[tag],
609
+ onChange: (value) => update({ [tag]: value }),
610
+ }),
475
611
  ),
476
612
  ),
477
613
  ),
478
614
  h(
479
- 'details',
480
- { style: { marginTop: 16, paddingTop: 10, borderTop: `1px solid ${palette.line}` } },
481
- h('summary', { style: { cursor: 'pointer', fontSize: 13, fontWeight: 600 } }, s.extra),
482
- h('p', { style: hintStyle() }, s.extraHint),
615
+ 'section',
616
+ { style: { marginTop: 20, paddingTop: 12, borderTop: `1px solid ${palette.line}` } },
483
617
  h(
484
- 'div',
485
- { style: { display: 'flex', flexWrap: 'wrap', gap: '6px 18px', paddingTop: 4 } },
486
- ...EXTRA_TAGS.map((tag) =>
487
- h(
488
- 'div',
489
- { key: tag, style: { flex: '1 1 180px', minWidth: 180 } },
490
- h(Switch, {
491
- label: TAG_LABEL[tag],
618
+ 'details',
619
+ {},
620
+ h(
621
+ 'summary',
622
+ { style: { cursor: 'pointer', fontSize: 13, fontWeight: 600, listStylePosition: 'inside' } },
623
+ s.extra,
624
+ ),
625
+ h('p', { style: hintStyle() }, s.extraHint),
626
+ h(
627
+ 'div',
628
+ { role: 'group', 'aria-label': s.extra, style: { ...gridStyle(), marginTop: 8 } },
629
+ ...EXTRA_TAGS.flatMap((tag) =>
630
+ h(FlagRow, {
631
+ key: tag,
632
+ tag,
633
+ hint: extraHints[tag],
634
+ strings: s,
635
+ disabled: !editable,
492
636
  checked: draft[tag],
493
637
  onChange: (value) => update({ [tag]: value }),
494
638
  }),
@@ -500,14 +644,24 @@ function ConfigPage(props) {
500
644
  editable
501
645
  ? h(
502
646
  'div',
503
- { style: { display: 'flex', alignItems: 'center', gap: 10, marginTop: 16, flexWrap: 'wrap' } },
647
+ {
648
+ style: {
649
+ display: 'flex',
650
+ alignItems: 'center',
651
+ gap: 10,
652
+ marginTop: 18,
653
+ paddingTop: 12,
654
+ borderTop: `1px solid ${palette.line}`,
655
+ flexWrap: 'wrap',
656
+ },
657
+ },
504
658
  h(
505
659
  'button',
506
660
  {
507
661
  type: 'button',
508
662
  onClick: save,
509
663
  disabled: busy || !editableReady || !dirty,
510
- style: { fontSize: 13, padding: '6px 14px', borderRadius: 6, cursor: 'pointer' },
664
+ style: primaryButton(dirty && editableReady && !busy),
511
665
  },
512
666
  s.save,
513
667
  ),
@@ -517,7 +671,7 @@ function ConfigPage(props) {
517
671
  type: 'button',
518
672
  onClick: reset,
519
673
  disabled: busy || !dirty,
520
- style: { fontSize: 13, padding: '6px 14px', borderRadius: 6, cursor: 'pointer' },
674
+ style: secondaryButton(dirty && !busy),
521
675
  },
522
676
  s.reset,
523
677
  ),
@@ -527,12 +681,45 @@ function ConfigPage(props) {
527
681
  )
528
682
  }
529
683
 
530
- function page(ownerProps, titled) {
684
+ function primaryButton(enabled) {
685
+ return {
686
+ fontSize: 13,
687
+ lineHeight: '20px',
688
+ padding: '5px 14px',
689
+ borderRadius: 6,
690
+ border: '1px solid transparent',
691
+ background: enabled ? palette.brand : 'var(--dsw-alias-bg-layer-3, rgba(127, 127, 127, 0.2))',
692
+ color: enabled ? '#fff' : palette.muted,
693
+ cursor: enabled ? 'pointer' : 'default',
694
+ }
695
+ }
696
+
697
+ function secondaryButton(enabled) {
698
+ return {
699
+ fontSize: 13,
700
+ lineHeight: '20px',
701
+ padding: '5px 14px',
702
+ borderRadius: 6,
703
+ border: `1px solid ${palette.line}`,
704
+ background: 'transparent',
705
+ color: enabled ? palette.label : palette.muted,
706
+ cursor: enabled ? 'pointer' : 'default',
707
+ }
708
+ }
709
+
710
+ function page(ownerProps, openPluginPage) {
531
711
  const props = ownerProps || {}
532
- return h(ConfigPage, { ...props, titled })
712
+ return h(ConfigPage, { ...props, openPluginPage })
533
713
  }
534
714
 
535
715
  function apply(ctx) {
716
+ let openPluginPage
717
+ try {
718
+ const navigation = typeof ctx.get === 'function' ? ctx.get('pluginNavigation') : undefined
719
+ if (navigation && typeof navigation.openBundle === 'function') {
720
+ openPluginPage = () => navigation.openBundle(PACKAGE_NAME)
721
+ }
722
+ } catch {}
536
723
  ctx.slots.inject('settings.section', () =>
537
724
  ctx.slots.register(
538
725
  {
@@ -541,7 +728,7 @@ function apply(ctx) {
541
728
  order: 60,
542
729
  label: () => t().nav,
543
730
  },
544
- (ownerProps) => page(ownerProps, true),
731
+ (ownerProps) => page(ownerProps, openPluginPage),
545
732
  ),
546
733
  )
547
734
  ctx.slots.inject('plugins.bundle.config', () =>
@@ -551,7 +738,7 @@ function apply(ctx) {
551
738
  key: PACKAGE_NAME,
552
739
  label: () => t().nav,
553
740
  },
554
- (ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps, false)),
741
+ (ownerProps) => (ownerProps && ownerProps.view === 'summary' ? null : page(ownerProps, undefined)),
555
742
  ),
556
743
  )
557
744
  }
@@ -50,6 +50,8 @@ export const TAG_LABEL = {
50
50
  tparam: '@tparam',
51
51
  retval: '@retval',
52
52
  };
53
+ /** 预览里的示例摘要:用户没填 @brief 时用它,避免预览出现空标签。 */
54
+ export const SAMPLE_BRIEF = '定义井字棋棋盘、格子坐标与格子类型。';
53
55
  /** 出厂配置:文件头只留 @file 与 @brief,契约标签全开,隐私标签全关。 */
54
56
  export const DEFAULT_CONFIG = {
55
57
  brief: { enabled: true, value: '' },
@@ -113,6 +115,10 @@ export function renderFileHeader(raw, fileName) {
113
115
  export function previewParts(raw, sampleName) {
114
116
  const config = normalizeConfig(raw);
115
117
  const header = renderFileHeader(config, sampleName || 'board.h');
118
+ // 预览是示例:@brief 开着但用户没填句子时,用示例文件自己的摘要顶替。
119
+ const briefIndex = header.indexOf('/// @brief');
120
+ if (briefIndex !== -1)
121
+ header[briefIndex] = `/// @brief ${SAMPLE_BRIEF}`;
116
122
  const contract = [];
117
123
  if (config.param)
118
124
  contract.push('/// @param position 目标行列坐标。');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chengzhao-dev/dsh-cpp-workflow",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "写 C++ 源码、配 CMake、用 Clang 构建;文档注释统一为 /// Doxygen,文件头标签可在设置里改。 · Write C++ sources, configure CMake, and build with Clang; /// Doxygen comments, with file-header tags you can change in Settings.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",