✨ feat(vscode): 代码阅读预览界面

Vite 加 code-reader.html 第二入口;代码块树(层级竖线、折叠、种类标签配色)、译文 / 源码按词性着色、块内直接编辑(防抖回写整行、Esc 回预览)、翻译下拉、解析失败提示;看板与代码阅读共用带类型的 acquireVsCodeApi 包装和 ErrorBoundary。
This commit is contained in:
cruldra committed 2026-09-24 21:01:34 +08:00
1 parent cab5ac0ebc
commit af56985d9a
16 files changed
+775 -74

No files matched your search

@@ -0,0 +1,118 @@
import type { ReactElement } from 'react'
import type { BlockKind, BlockView } from './types'
import { ChevronDown, ChevronRight, Eye, Pencil } from 'lucide-react'
import { Fragment } from 'react'
import { cn } from '@/lib/utils'
import { CodeEditor } from './CodeEditor'
import { SegmentText } from './SegmentText'
const KIND_LABEL: Record<BlockKind, string> = {
import: '导入',
function: '函数',
class: '类',
type: '类型',
field: '字段',
const: '常量',
variable: '变量',
if: '条件',
else: '否则',
loop: '循环',
try: '尝试',
catch: '捕获',
finally: '最终',
return: '返回',
statement: '语句',
comment: '注释',
}
export interface BlockTreeState {
version: number
editing: ReadonlySet<string>
collapsed: ReadonlySet<string>
/** 刚点了「编辑」、要把光标放进去的块 */
focusId: string | null
onFocused: () => void
onToggleEdit: (id: string) => void
onToggleFold: (id: string) => void
}
/** 一层代码块:左边一条竖线标出这一层,每块的子块紧跟在它下面、再往右缩进一层。 */
export function BlockLevel({ blocks, tree }: { blocks: BlockView[], tree: BlockTreeState }): ReactElement {
return (
<div className="flex gap-2">
<div className="flex w-3 shrink-0 justify-center">
<div className="w-px bg-[var(--vscode-editorIndentGuide-background1)]" />
</div>
<div className="flex min-w-0 flex-1 flex-col gap-2">
{blocks.map(block => (
<Fragment key={block.id}>
<BlockCard block={block} tree={tree} />
{block.children.length > 0 && !tree.collapsed.has(block.id) && (
<BlockLevel blocks={block.children} tree={tree} />
)}
</Fragment>
))}
</div>
</div>
)
}
/** 一块:折叠箭头、种类标签、译文或源码、右边的「编辑 / 预览」切换。 */
function BlockCard({ block, tree }: { block: BlockView, tree: BlockTreeState }): ReactElement {
const editing = tree.editing.has(block.id)
const collapsed = tree.collapsed.has(block.id)
const hasChildren = block.children.length > 0
const Chevron = collapsed ? ChevronRight : ChevronDown
return (
<div
className="cr-block flex items-start gap-2.5 rounded-lg py-2 pr-2.5 pl-2"
data-kind={block.kind}
data-editing={editing ? '' : undefined}
>
{hasChildren
? (
<button
type="button"
className="flex h-5 w-3.5 shrink-0 items-center justify-center rounded-sm text-[var(--vscode-descriptionForeground)] hover:text-[var(--vscode-foreground)]"
aria-label={collapsed ? '展开子块' : '折叠子块'}
aria-expanded={!collapsed}
onClick={() => tree.onToggleFold(block.id)}
>
<Chevron size={14} strokeWidth={1.8} />
</button>
)
: <span className="w-3.5 shrink-0" />}
<span className="cr-badge flex h-5 w-11 shrink-0 items-center justify-center rounded text-[11px] leading-[14px] font-medium">
{KIND_LABEL[block.kind]}
</span>
<div className="min-w-0 flex-1">
{editing
? (
<CodeEditor
block={block}
version={tree.version}
autoFocus={tree.focusId === block.id}
onAutoFocused={tree.onFocused}
onExit={() => tree.onToggleEdit(block.id)}
/>
)
: (
<p className="cr-code">
<SegmentText segments={block.translation} />
</p>
)}
</div>
<button
type="button"
className={cn(
'flex shrink-0 items-center gap-1 rounded px-2 py-0.5 text-xs leading-4 text-[var(--vscode-descriptionForeground)] hover:bg-[var(--vscode-toolbar-hoverBackground)]',
editing && 'bg-[var(--vscode-toolbar-activeBackground)]',
)}
onClick={() => tree.onToggleEdit(block.id)}
>
{editing ? <Eye size={13} strokeWidth={1.8} /> : <Pencil size={13} strokeWidth={1.8} />}
{editing ? '预览' : '编辑'}
</button>
</div>
)
}