feat(frontend): 编辑器内「润色 / 再沟通」——选段回炉 + 追加意见迭代出版本栈,接受回填
Phase 1(写作工作台重构):编辑器选中一段即可「润色选段」,复用同步 /refine 回炉 (只读、不写库,守不变量 #3);不满意可「再沟通」——以上一版产出为输入 + 作者新意见 再改一版,形成版本栈(useRefine,已单测)。接受回填按内容重锚落回草稿,原文已变则 提示重选、绝不盲替换(applyRefinement,已单测)。Editor 上报选区,结果以内联卡片展示。
This commit is contained in:
@@ -4,17 +4,37 @@ import { useEffect, useRef } from "react";
|
||||
|
||||
import { cn, focusRing, proseBody } from "@/lib/ui/variants";
|
||||
|
||||
export interface EditorSelection {
|
||||
start: number;
|
||||
end: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface EditorProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
streaming: boolean;
|
||||
// 选区变化上报(供「润色选段」等选区级 AI 动作取材)。空选区也会上报(start===end)。
|
||||
onSelectionChange?: (selection: EditorSelection) => void;
|
||||
}
|
||||
|
||||
// 中栏正文编辑器:宋体、720px 居中、行高 1.9(UX §2.3 / §6.3)。
|
||||
// 流式中显示朱砂光标提示(prefers-reduced-motion 下不闪烁,见 globals.css)。
|
||||
export function Editor({ value, onChange, streaming }: EditorProps) {
|
||||
export function Editor({
|
||||
value,
|
||||
onChange,
|
||||
streaming,
|
||||
onSelectionChange,
|
||||
}: EditorProps) {
|
||||
const ref = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const reportSelection = (el: HTMLTextAreaElement): void => {
|
||||
if (!onSelectionChange) return;
|
||||
const start = el.selectionStart;
|
||||
const end = el.selectionEnd;
|
||||
onSelectionChange({ start, end, text: value.slice(start, end) });
|
||||
};
|
||||
|
||||
// 自适应高度:textarea 高度=内容高度(overflow-hidden 不内部滚动),由外层写作区
|
||||
// 单一滚动条统管。修复正文出现嵌套滚动条 + 定高 60vh 不铺满展示区的怪象。
|
||||
// min-h-[60vh] 仍作空稿时的最小可写高度(内容更长时按内容增高、外层滚动)。
|
||||
@@ -35,6 +55,7 @@ export function Editor({ value, onChange, streaming }: EditorProps) {
|
||||
id="chapter-editor"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
onSelect={(e) => reportSelection(e.currentTarget)}
|
||||
readOnly={streaming}
|
||||
aria-busy={streaming}
|
||||
placeholder="夜色如墨……点击「写本章」让 AI 起草,或直接在此书写。"
|
||||
|
||||
Reference in New Issue
Block a user