💄 style(vscode): 代码阅读的「按注释规范写注释」按钮统一排在卡片右上角那排最前面,不再画在「注释」一行上

This commit is contained in:
cruldra committed 2026-10-08 08:03:00 +08:00
1 parent c398a538d9
commit ebea984852
7 files changed
+52 -68

No files matched your search

@@ -1,6 +1,6 @@
import { renderToStaticMarkup } from 'react-dom/server'
import { describe, expect, it } from 'vitest'
import { BlockTools, RewriteButton } from './BlockTools'
import { BlockTools } from './BlockTools'
function render(editing: boolean, ask?: { asking: boolean }, rewrite?: { rewriting: boolean, fresh?: boolean }): string {
return renderToStaticMarkup(
@@ -55,11 +55,13 @@ describe('块右上角的图标按钮', () => {
expect(busy).not.toContain('cr-qa-spin')
})
it('块还没有注释时按钮叫「按注释规范写注释」;「注释」一行上的按钮是同一个,只有图标', () => {
const fresh = render(false, undefined, { rewriting: false, fresh: true })
expect(fresh).toContain('data-tooltip="按注释规范写注释" aria-label="按注释规范写注释"')
const onRow = renderToStaticMarkup(<RewriteButton tool={{ rewriting: false, fresh: false, onRewrite: () => {} }} />)
expect(onRow).toMatch(/^<button[^>]*data-tooltip="按注释规范重写" aria-label="按注释规范重写"[^>]*><svg/)
expect(visibleText(onRow)).toBe('')
it('块还没有注释时同一个位置的按钮叫「按注释规范写注释」,同样只有图标', () => {
const fresh = render(false, { asking: false }, { rewriting: false, fresh: true })
expect(visibleText(fresh)).toBe('')
const buttons = [...fresh.matchAll(/<button[^>]*>/g)].map(match => match[0])
expect(buttons).toHaveLength(3)
expect(buttons[0]).toContain('data-tooltip="按注释规范写注释" aria-label="按注释规范写注释"')
expect(buttons[1]).toContain('data-tooltip="问 AI"')
expect(buttons[2]).toContain('data-tooltip="编辑"')
})
})
+16 -24
View File
@@ -8,30 +8,9 @@ export interface RewriteTool {
onRewrite: () => void
}
/**
* 「按注释规范重写」:用写注释的技能重写这块代码的注释并写回文件(块还没有注释时是「按注释规范写注释」,新写一段);正在重写时不能再点。
* 只有图标(注释),名字在悬停提示和 aria-label 里。卡片有「注释」一行时画在那一行上,没有时排在右上角那排按钮里。
*/
export function RewriteButton({ tool }: { tool: RewriteTool }): ReactElement {
const label = tool.fresh ? '按注释规范写注释' : '按注释规范重写'
return (
<button
type="button"
className="cr-tool flex h-5 w-6 shrink-0 items-center justify-center rounded"
data-tooltip={label}
aria-label={label}
aria-busy={tool.rewriting}
disabled={tool.rewriting}
onClick={tool.onRewrite}
>
<MessageSquareText size={14} strokeWidth={1.8} aria-hidden />
</button>
)
}
/**
* 块右上角的一排图标按钮(普通块、属性表、库规则的卡片共用):只放图标,名字写在悬停提示(data-tooltip)和读屏用的 aria-label 里。
* 「按注释规范重写」(见 RewriteButton):卡片没有「注释」一行时排在这里。
* 「按注释规范重写」:用写注释的技能重写这块代码的注释并写回文件,块还没有注释时叫「按注释规范写注释」、新写一段;正在重写时不能再点。图标是注释。
* 「问 AI」:点开 / 收起块下面的问答区,展开时带按下的底色。
* 「编辑 / 预览」切换:预览时是铅笔「编辑」,编辑时是眼睛「预览」、带按下的底色。
*/
@@ -42,14 +21,27 @@ export function BlockTools({ editing, onToggleEdit, asking, onToggleAsk, rewrite
asking?: boolean
/** 没给就不显示「问 AI」(文件不在工作区文件夹里,问答没地方存) */
onToggleAsk?: () => void
/** 没给就不显示「按注释规范重写」(不能写注释,或按钮画在「注释」一行上) */
/** 没给就不显示「按注释规范重写」(不能写注释的块) */
rewrite?: RewriteTool
}): ReactElement {
const label = editing ? '预览' : '编辑'
const Icon = editing ? Eye : Pencil
const rewriteLabel = rewrite?.fresh ? '按注释规范写注释' : '按注释规范重写'
return (
<div className="flex h-5 shrink-0 items-center gap-0.5" role="group" aria-label="块操作">
{rewrite && <RewriteButton tool={rewrite} />}
{rewrite && (
<button
type="button"
className="cr-tool flex h-5 w-6 items-center justify-center rounded"
data-tooltip={rewriteLabel}
aria-label={rewriteLabel}
aria-busy={rewrite.rewriting}
disabled={rewrite.rewriting}
onClick={rewrite.onRewrite}
>
<MessageSquareText size={14} strokeWidth={1.8} aria-hidden />
</button>
)}
{onToggleAsk && (
<button
type="button"
@@ -111,29 +111,31 @@ describe('挂在卡片子行上的子块', () => {
describe('「注释」一行和「重写注释」按钮', () => {
const comment = { label: '注释', value: { markdown: '按名字找' } }
const html = (block: BlockView): string => renderToStaticMarkup(<BlockLevel blocks={[block]} tree={state(new Set())} />)
/** 「注释」那一行(cr-prop-row)里的 HTML */
const commentRowHtml = (markup: string): string => markup.split('cr-prop-row').find(part => part.includes('按名字找')) ?? ''
/** 按钮只有一个,排在右上角那排按钮(块操作)的最前面 */
const inToolbar = (markup: string, label: string): void => {
expect(markup.match(new RegExp(`aria-label="${label}"`, 'g'))).toHaveLength(1)
expect(markup).toMatch(new RegExp(`role="group" aria-label="块操作"><button[^>]*aria-label="${label}"`))
}
it('属性表卡片:「注释」一行照属性表画,按钮画在这一行上,右上角那排不再有', () => {
it('属性表卡片:「注释」一行照属性表画,按钮在右上角那排,不在这一行上', () => {
const card = view('1', 'function', '', {
properties: [{ label: '名称', value: [{ text: 'find', role: 'function' }] }, comment, { label: '异步', value: [{ text: '否', role: 'storage' }] }],
commentRow: 1,
commentTarget: { kind: 'replace', startLine: 0, endLine: 0 },
})
const markup = html(card)
expect(commentRowHtml(markup)).toContain('aria-label="按注释规范重写"')
expect(markup.match(/aria-label="按注释规范重写"/g)).toHaveLength(1)
inToolbar(markup, '按注释规范重写')
expect(markup.indexOf('按名字找')).toBeLessThan(markup.indexOf('异步'))
})
it('一行译文的卡片:「注释」一行排在译文下面,按钮同样在这一行上', () => {
it('一行译文的卡片:「注释」一行排在译文下面,按钮同样在右上角那排', () => {
const markup = html(view('2', 'statement', 'save()', { rows: [comment], commentRow: 0, commentTarget: { kind: 'replace', startLine: 0, endLine: 0 } }))
expect(markup.indexOf('save()')).toBeLessThan(markup.indexOf('按名字找'))
expect(commentRowHtml(markup)).toContain('aria-label="按注释规范重写"')
inToolbar(markup, '按注释规范重写')
})
it('还没有注释的块:按钮在右上角那排,叫「按注释规范写注释」;不能写注释的块没有按钮', () => {
expect(html(view('3', 'statement', 'save()', { commentTarget: { kind: 'insert', line: 0 } }))).toMatch(/role="group" aria-label="块操作"><button[^>]*aria-label="按注释规范写注释"/)
it('还没有注释的块:按钮同样在右上角那排,叫「按注释规范写注释」;不能写注释的块没有按钮', () => {
inToolbar(html(view('3', 'statement', 'save()', { commentTarget: { kind: 'insert', line: 0 } })), '按注释规范写注释')
expect(html(view('4', 'statement', 'save()'))).not.toContain('按注释规范')
})
})
+7 -10
View File
@@ -9,7 +9,7 @@ import type { BlockMarks, BlockView } from './types'
import { ChevronDown, ChevronRight, ChevronsUpDown, EyeOff } from 'lucide-react'
import { Fragment, useState } from 'react'
import { AskThread, RewriteThread } from './AskThread'
import { BlockTools, RewriteButton } from './BlockTools'
import { BlockTools } from './BlockTools'
import { CodeEditor } from './CodeEditor'
import { DiffText } from './DiffText'
import { removedTitle } from './diffView'
@@ -204,12 +204,10 @@ function BlockRewrite({ anchor, edit }: { anchor: string, edit: EditTree }): Rea
/**
* 预览状态的内容:函数、类、枚举、导入、常量、变量块和库规则的卡片是属性表,其余是一行译文;
* 译文下面依次接着摊开实参的表(跨几行写的调用)、说明、几行属性(「注释」一行)。标差异时 marks 是这块自己的内容改了哪些(页面上正是差异算的那一版才给)。
* rewrite 是「重写注释」按钮,画在「注释」一行上(commentRow);只读的块树(定义卡片浮窗)、删掉的块没有。
*/
function BlockPreview({ block, tree, marks, rewrite }: { block: BlockView, tree: BlockTreeState, marks?: BlockMarks, rewrite?: RewriteTool }): ReactElement {
const rowTools = rewrite && block.commentRow !== undefined ? new Map([[block.commentRow, <RewriteButton key="rewrite" tool={rewrite} />]]) : undefined
function BlockPreview({ block, tree, marks }: { block: BlockView, tree: BlockTreeState, marks?: BlockMarks }): ReactElement {
if ('properties' in block)
return <PropertyGrid blockId={block.id} rows={block.properties} collapsed={tree.collapsed} onToggleFold={tree.onToggleFold} marks={marks?.rows} slots={cardSlots(block, tree)} rowTools={rowTools} />
return <PropertyGrid blockId={block.id} rows={block.properties} collapsed={tree.collapsed} onToggleFold={tree.onToggleFold} marks={marks?.rows} slots={cardSlots(block, tree)} />
return (
<>
{marks?.translation ? <DiffText diff={marks.translation} /> : <TextPreview text={block.translation} />}
@@ -221,14 +219,14 @@ function BlockPreview({ block, tree, marks, rewrite }: { block: BlockView, tree:
{marks?.doc ? <DiffText diff={marks.doc} className="mt-0.5" /> : block.doc && <TextPreview text={block.doc} className="mt-0.5" />}
{block.rows && (
<div className="mt-[3px]">
<PropertyGrid blockId={block.id} rows={block.rows} collapsed={tree.collapsed} onToggleFold={tree.onToggleFold} marks={marks?.rows} rowTools={rowTools} />
<PropertyGrid blockId={block.id} rows={block.rows} collapsed={tree.collapsed} onToggleFold={tree.onToggleFold} marks={marks?.rows} />
</div>
)}
</>
)
}
/** 这块的「重写注释」按钮:能写注释(commentTarget)、页面上能改代码时才有。 */
/** 这块的「重写注释」按钮,排在右上角那排按钮里:能写注释(commentTarget)、页面上能改代码时才有。 */
function rewriteTool(block: BlockView, edit: EditTree | undefined): RewriteTool | undefined {
if (!edit || !block.commentTarget)
return undefined
@@ -271,7 +269,6 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
const hasChildren = toggled || hasShownChildren(block, tree)
const Chevron = collapsed && !toggled ? ChevronRight : ChevronDown
const change = tree.diff?.changes.get(block.id)
const rewrite = rewriteTool(block, edit)
return (
<div
className="cr-block relative flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2"
@@ -307,7 +304,7 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
onExit={() => edit.onToggleEdit(block.id)}
/>
)
: <BlockPreview block={block} tree={tree} marks={tree.diff?.current ? change?.marks : undefined} rewrite={rewrite} />}
: <BlockPreview block={block} tree={tree} marks={tree.diff?.current ? change?.marks : undefined} />}
</div>
{tree.temporary.has(block.id) && (
<span className="cr-temporary flex h-5 shrink-0 items-center gap-1 rounded px-1.5 text-[11px] whitespace-nowrap">
@@ -321,7 +318,7 @@ function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }):
onToggleEdit={() => edit.onToggleEdit(block.id)}
asking={tree.qa?.state.open.includes(block.anchor)}
onToggleAsk={tree.qa && (() => tree.qa?.onToggle(block.anchor))}
{...(rewrite && block.commentRow === undefined && { rewrite })}
rewrite={rewriteTool(block, edit)}
/>
)}
</div>
@@ -252,7 +252,7 @@ export function PropertyTable({ parentId, columns, items, marks, toggles }: {
* 给了列名的(输入参数、数据表的字段)排成表格,没给的排成列表。
* 标差异时(marks)新加的行淡绿底、左边 +,删掉的行放回原来的位置、淡红底划掉、左边 −,值改了的按旧值 → 新值或逐词标。
*/
export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks, slots, rowTools }: {
export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks, slots }: {
blockId: string
rows: PropertyRow[]
collapsed: ReadonlySet<string>
@@ -261,8 +261,6 @@ export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks, sl
marks?: RowsDiff
/** 挂在这张属性表子行上的子块(库规则的卡片),见 GridSlots */
slots?: GridSlots
/** 排在某一行值右边的按钮(「注释」一行的「重写注释」),按行的下标 */
rowTools?: ReadonlyMap<number, ReactNode>
}): ReactElement {
const removed = (after: number): ReactElement[] => removedAt(marks?.removed, after)
.map(({ value, index }) => <RemovedRow key={`removed-${index}`} id={`${blockId}/x${index}`} row={value} />)
@@ -298,14 +296,7 @@ export function PropertyGrid({ blockId, rows, collapsed, onToggleFold, marks, sl
</button>
)
: <span className="cr-prop-label flex h-5 items-center">{row.label}</span>}
{rowTools?.has(index)
? (
<div className="flex min-w-0 items-start gap-1">
<div className="min-w-0 flex-1"><RowValue row={row} mark={mark} /></div>
{rowTools.get(index)}
</div>
)
: <RowValue row={row} mark={mark} />}
<RowValue row={row} mark={mark} />
</div>
{!folded && layout === 'table' && <PropertyTable parentId={id} columns={row.columns ?? []} items={row.items ?? []} marks={mark?.items} toggles={toggles} />}
{!folded && layout === 'list' && <PropertyItems parentId={id} items={row.items ?? []} folding={{ collapsed, onToggleFold }} marks={mark?.items} />}
+1 -1
View File
@@ -143,7 +143,7 @@ interface BlockViewBase {
attributes: Record<string, string>
/** 这块认出来的概念,从左到右。webview 按图标名画,不自己用种类查 */
concepts: BlockConcept[]
/** 「注释」一行(讲这块的普通注释)排在第几行,0 起:属性表卡片是 properties 里的下标,一行译文的卡片是 rows 里的下标。「重写注释」按钮画在这一行上 */
/** 「注释」一行(讲这块的普通注释)排在第几行,0 起:属性表卡片是 properties 里的下标,一行译文的卡片是 rows 里的下标;没有注释就没有 */
commentRow?: number
/** 「重写注释」写到哪(扩展端用,这边只看有没有、是换掉还是新写);没有就不显示按钮 */
commentTarget?: CommentTarget