P1-6 useStyleLearn/useKimiOauth 修 stale-closure 依赖。 P1-7 CommandPalette/Drawer 加 focus trap(新 lib/a11y/focusTrap)。 P1-8 SSE 解析与 server.ts 去 as 强转改类型守卫/运行时校验。 P2 删冗余强转、开 noUncheckedIndexedAccess、Toast 清理+稳定 key、列表 key 稳定化、 rel=noopener、useMemo 大文本、ConflictCard role=group、useInjection 加锁、 client.test 真断言。 codegen 重生成 schema.d.ts + 消费 DimensionEntry/ReviewConflictView 强类型。
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
"use client";
|
||
|
||
import type { ReviewConflict } from "@/lib/review/sse";
|
||
|
||
interface AnnotatedTextProps {
|
||
text: string;
|
||
conflicts: ReviewConflict[];
|
||
// 当前聚焦的冲突下标(来自报告卡「跳转」),用于锚点联动。
|
||
focusedIndex: number | null;
|
||
onAnchorClick: (index: number) => void;
|
||
}
|
||
|
||
// 正文就地标注(UX §8.3 / §6.1)。
|
||
// M2 占位(R6):`where` 是文字定位(如「第4段」),M2 不做精确字符 range —
|
||
// 改为段级/锚点联动:每个冲突渲染为一枚朱砂波浪线锚点挂在正文上方,点击=回跳报告卡。
|
||
// 精确 inline range 标注留 M3+(需后端给字符 offset)。
|
||
export function AnnotatedText({
|
||
text,
|
||
conflicts,
|
||
focusedIndex,
|
||
onAnchorClick,
|
||
}: AnnotatedTextProps) {
|
||
return (
|
||
<div className="flex h-full flex-col">
|
||
{conflicts.length > 0 ? (
|
||
<div className="mb-3 flex flex-wrap gap-2 border-b border-line pb-3">
|
||
{conflicts.map((c, i) => (
|
||
<button
|
||
key={`${c.type}-${c.where}-${i}`}
|
||
type="button"
|
||
id={`anchor-${i}`}
|
||
onClick={() => onAnchorClick(i)}
|
||
aria-current={focusedIndex === i ? "true" : undefined}
|
||
className={`conflict-anchor rounded px-2 py-0.5 text-xs ${
|
||
focusedIndex === i
|
||
? "bg-cinnabar text-panel"
|
||
: "text-conflict hover:bg-[var(--color-conflict)]/10"
|
||
}`}
|
||
title={`${c.type}:${c.where || "正文"}`}
|
||
>
|
||
<span aria-hidden="true">⌇</span> {c.type}
|
||
{c.where ? ` · ${c.where}` : ""}
|
||
</button>
|
||
))}
|
||
</div>
|
||
) : null}
|
||
<article className="flex-1 overflow-auto whitespace-pre-wrap font-serif text-[17px] leading-[1.9] text-ink">
|
||
{text || (
|
||
<span className="text-ink-soft/60">(无待审正文,先去写作页起草本章)</span>
|
||
)}
|
||
</article>
|
||
</div>
|
||
);
|
||
}
|