✨ feat(vscode): React 文件照源码一块一张卡片,组件卡片只留名称、说明、构造参数
- 组件体的语句不再并进组件卡片,照源码顺序是它的子块;状态、引用、数据源、派生值、副作用、事件处理函数、渲染输出各成一张卡片,回调和函数里的语句是这些卡片下面的子卡片 - 普通变量、提前返回的 if、注释照语言默认的卡片;表格格子里只写名字和位置,去掉「改它的」「来自」格子里单独打开源码的那条路 - 用词改成书面叫法(更新位置、使用位置、计算表达式、依赖项、触发时机、清理函数、绑定事件、样式取决于……),新增 toggle-right、anchor、download、sigma、layout-template 五个种类图标 - 夹具加上 luffy 的 QueuedBubble.tsx、PlanChecklist.tsx,规格的 React 一节照新样子改写
This commit is contained in:
1 parent
194544b4ba
commit
6ce4ef7ee2
20 files changed
+1327
-989
No files matched your search
@@ -1,8 +1,7 @@
|
||||
import type { CSSProperties, ReactElement, ReactNode } from 'react'
|
||||
import type { BlockView, CellEdit, Inline, InlinePart, ItemDiff, ItemsDiff, KindBadge, PropertyItem, PropertyRow, Removed, RowsDiff } from './types'
|
||||
import { ChevronDown, ChevronRight, Eye, Pencil } from 'lucide-react'
|
||||
import { createContext, Fragment, useContext } from 'react'
|
||||
import { CodeEditor } from './CodeEditor'
|
||||
import type { BlockView, Inline, InlinePart, ItemDiff, ItemsDiff, KindBadge, PropertyItem, PropertyRow, Removed, RowsDiff } from './types'
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react'
|
||||
import { Fragment } from 'react'
|
||||
import { DiffInline, DiffText } from './DiffText'
|
||||
import { inlineText } from './segments'
|
||||
import { SegmentText } from './SegmentText'
|
||||
@@ -57,83 +56,6 @@ function CellText({ cell, parts }: { cell: Inline[], parts: InlinePart[] | undef
|
||||
return parts ? <DiffInline parts={parts} /> : <SegmentText segments={cell} />
|
||||
}
|
||||
|
||||
/** 格子里单独打开的那段源码:和块共用同一套铅笔、同一个编辑框。 */
|
||||
export interface SnippetEditState {
|
||||
ids: ReadonlyMap<CellEdit, string>
|
||||
editing: ReadonlySet<string>
|
||||
onToggle: (id: string) => void
|
||||
version: number
|
||||
focusId: string | null
|
||||
onFocused: () => void
|
||||
}
|
||||
|
||||
export const SnippetEditContext = createContext<SnippetEditState | null>(null)
|
||||
|
||||
function EditToggle({ editing, onToggle }: { editing: boolean, onToggle: () => void }): ReactElement {
|
||||
const label = editing ? '预览' : '编辑'
|
||||
const Icon = editing ? Eye : Pencil
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="cr-tool inline-flex h-5 w-6 items-center justify-center rounded align-middle"
|
||||
data-active={editing ? '' : undefined}
|
||||
data-tooltip={label}
|
||||
aria-label={label}
|
||||
onClick={onToggle}
|
||||
>
|
||||
<Icon size={14} strokeWidth={1.8} aria-hidden />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function SnippetSpan({ edit, segments }: { edit: CellEdit, segments: Inline[] }): ReactElement {
|
||||
const ctx = useContext(SnippetEditContext)
|
||||
const id = ctx?.ids.get(edit)
|
||||
if (!ctx || id === undefined)
|
||||
return <SegmentText segments={segments} />
|
||||
const editing = ctx.editing.has(id)
|
||||
return (
|
||||
<span className="inline-flex flex-wrap items-baseline gap-0.5">
|
||||
{editing
|
||||
? (
|
||||
<span className="block w-full">
|
||||
<CodeEditor
|
||||
source={edit.snippet}
|
||||
version={ctx.version}
|
||||
autoFocus={ctx.focusId === id}
|
||||
onAutoFocused={ctx.onFocused}
|
||||
onExit={() => ctx.onToggle(id)}
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
: <SegmentText segments={segments} />}
|
||||
<EditToggle editing={editing} onToggle={() => ctx.onToggle(id)} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function CellView({ cell, index, edits, parts }: {
|
||||
cell: Inline[]
|
||||
index: number
|
||||
edits: CellEdit[] | undefined
|
||||
parts: InlinePart[] | undefined
|
||||
}): ReactElement {
|
||||
const mine = (edits ?? []).filter(edit => edit.cell === index).sort((a, b) => a.from - b.from)
|
||||
if (mine.length === 0)
|
||||
return <CellText cell={cell} parts={parts} />
|
||||
const chunks: ReactElement[] = []
|
||||
let at = 0
|
||||
mine.forEach((edit, n) => {
|
||||
if (edit.from > at)
|
||||
chunks.push(<SegmentText key={`t${n}`} segments={cell.slice(at, edit.from)} />)
|
||||
chunks.push(<SnippetSpan key={`s${n}`} edit={edit} segments={cell.slice(edit.from, edit.to)} />)
|
||||
at = edit.to
|
||||
})
|
||||
if (at < cell.length)
|
||||
chunks.push(<SegmentText key="tail" segments={cell.slice(at)} />)
|
||||
return <>{chunks}</>
|
||||
}
|
||||
|
||||
/**
|
||||
* 子行的第一列(字典的键):带下一层的子行前面是折叠箭头(图标、大小、颜色同属性行的),点箭头或这一列切换;
|
||||
* 同一层里不带下一层的子行在同样的位置留空,第一列才对得齐。
|
||||
@@ -193,9 +115,9 @@ function PropertyItems({ parentId, items, folding, marks }: { parentId: string,
|
||||
{item.cells.map((cell, i) => i === 0 && nested
|
||||
? <ItemHead key={i} id={id} cell={cell} foldable={children.length > 0 && change !== 'del'} folding={folds} />
|
||||
: (
|
||||
<div key={i} className="cr-code min-w-0">
|
||||
<CellView cell={cell} index={i} edits={item.edits} parts={mark?.cells?.[i]} />
|
||||
</div>
|
||||
<p key={i} className="cr-code min-w-0">
|
||||
<CellText cell={cell} parts={mark?.cells?.[i]} />
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
{children.length > 0 && !folds.collapsed.has(id) && (change === 'del'
|
||||
@@ -234,7 +156,7 @@ function PropertyItems({ parentId, items, folding, marks }: { parentId: string,
|
||||
* 列宽按整列的内容定(见 tableTemplate),最后一列分剩下的,长了在格子里换行。一行译文下面摊开实参的表也用它;
|
||||
* 行带 table(实参的值里跨几行写的调用)时,那张表排在最后一格里、文字下面,往右缩一点,一层层往下套。
|
||||
* 标差异时(marks)有加行、删行的表每行第一格前多一个记号位(+ / −,没变的空着),加的行淡绿底,删的行放回原来的位置、淡红底划掉。
|
||||
* 讲某个子块的行(toggles,React 组件的事件处理表)第一格是按钮,点它在表下面展开那一块,展开时这一行淡淡垫上那一块的种类色。
|
||||
* 讲某个子块的行(toggles,Rust 实现块的方法表)第一格是按钮,点它在表下面展开那一块,展开时这一行淡淡垫上那一块的种类色。
|
||||
*/
|
||||
export function PropertyTable({ parentId, columns, items, marks, toggles }: {
|
||||
parentId: string
|
||||
@@ -255,10 +177,10 @@ export function PropertyTable({ parentId, columns, items, marks, toggles }: {
|
||||
{toggle && i === 0
|
||||
? (
|
||||
<button type="button" className="cr-item-toggle" aria-expanded={toggle.open} aria-label={`${toggle.open ? '收起' : '展开'}${inlineText(cell)}`} onClick={toggle.onToggle}>
|
||||
<CellView cell={cell} index={i} edits={item.edits} parts={mark?.cells?.[i]} />
|
||||
<CellText cell={cell} parts={mark?.cells?.[i]} />
|
||||
</button>
|
||||
)
|
||||
: <CellView cell={cell} index={i} edits={item.edits} parts={mark?.cells?.[i]} />}
|
||||
: <CellText cell={cell} parts={mark?.cells?.[i]} />}
|
||||
</>
|
||||
)
|
||||
const tint = toggle?.open && toggle.badge
|
||||
@@ -269,11 +191,11 @@ export function PropertyTable({ parentId, columns, items, marks, toggles }: {
|
||||
{cells.map((cell, i) => item.table && i === cells.length - 1
|
||||
? (
|
||||
<div key={i} className="cr-table-cell flex min-w-0 flex-col gap-[3px]" role="cell">
|
||||
<div className="cr-code">{text(cell, i)}</div>
|
||||
<p className="cr-code">{text(cell, i)}</p>
|
||||
<PropertyTable parentId={tableId(id)} columns={item.table.columns} items={item.table.items} marks={mark?.table} />
|
||||
</div>
|
||||
)
|
||||
: <div key={i} className="cr-table-cell cr-code min-w-0" role="cell">{text(cell, i)}</div>)}
|
||||
: <p key={i} className="cr-table-cell cr-code min-w-0" role="cell">{text(cell, i)}</p>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user