Phase 0(写作工作台重构):新增 /write 草稿编辑器入口,只有敲入实质正文并停顿后 才懒建占位「未命名草稿」项目并种入首章草稿(useDeferredDraft,双重幂等闸 + 草稿落库 非致命降级),随即 replace 换入完整工作台;空进空出不落库,杜绝孤儿草稿。 落地页 CTA 改为「直接开始写」为主、「先立项」(立项向导)为辅。
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
"use client";
|
||
|
||
import { useRef, useState } from "react";
|
||
import { useRouter } from "next/navigation";
|
||
|
||
import { AppShell } from "@/components/AppShell";
|
||
import { TextArea } from "@/components/ui/TextArea";
|
||
import { useDeferredDraft } from "@/lib/start/useDeferredDraft";
|
||
|
||
// 敲入正文后停顿多久才懒建项目(落库)——在停顿点落库,快照即所见,避免打断连续输入。
|
||
const CREATE_DEBOUNCE_MS = 700;
|
||
|
||
// 进来即写:空白编辑器直达。敲入实质正文并停顿后懒建占位项目 + 落首章草稿,随即换入完整工作台。
|
||
// 空进空出不落库(从不敲字 → 从不 POST)。落库逻辑在 useDeferredDraft(已单测),此处只管交互。
|
||
export function DraftComposer() {
|
||
const router = useRouter();
|
||
const { ensureCreated } = useDeferredDraft();
|
||
const [text, setText] = useState("");
|
||
const textRef = useRef("");
|
||
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||
const redirectingRef = useRef(false);
|
||
|
||
const tryCreate = async (): Promise<void> => {
|
||
if (redirectingRef.current) return;
|
||
const snapshot = textRef.current;
|
||
if (snapshot.trim().length === 0) return;
|
||
const id = await ensureCreated(snapshot);
|
||
if (id && !redirectingRef.current) {
|
||
redirectingRef.current = true;
|
||
router.replace(`/projects/${id}/write`);
|
||
}
|
||
};
|
||
|
||
const onChange = (value: string): void => {
|
||
setText(value);
|
||
textRef.current = value;
|
||
if (timerRef.current) clearTimeout(timerRef.current);
|
||
timerRef.current = setTimeout(() => void tryCreate(), CREATE_DEBOUNCE_MS);
|
||
};
|
||
|
||
return (
|
||
<AppShell>
|
||
<div className="mx-auto flex min-h-[calc(100vh-var(--chrome,4rem))] max-w-3xl flex-col px-6 py-10 sm:px-8">
|
||
<p className="mb-4 text-sm text-ink-soft">
|
||
直接开始写——落笔即为你建立作品,正文自动保存。书名、设定、大纲都可在写作中随时补齐。
|
||
</p>
|
||
<TextArea
|
||
value={text}
|
||
onChange={(e) => onChange(e.target.value)}
|
||
className="min-h-[60vh] flex-1 resize-none text-lg leading-8"
|
||
placeholder="从这里开始写你的第一章……"
|
||
autoFocus
|
||
aria-label="正文"
|
||
/>
|
||
</div>
|
||
</AppShell>
|
||
);
|
||
}
|