commit
This commit is contained in:
+71
-10
@@ -7,8 +7,10 @@ import { getDemoServerInstance } from "./ot/server";
|
|||||||
function Editor({ name }: { name: string }) {
|
function Editor({ name }: { name: string }) {
|
||||||
const clientRef = useRef<Client | null>(null);
|
const clientRef = useRef<Client | null>(null);
|
||||||
const [doc, setDoc] = useState<string>("");
|
const [doc, setDoc] = useState<string>("");
|
||||||
|
const [inputValue, setInputValue] = useState<string>("");
|
||||||
const prevDocRef = useRef<string>("");
|
const prevDocRef = useRef<string>("");
|
||||||
const [enableSync, setEnableSync] = useState<boolean>(true);
|
const [enableSync, setEnableSync] = useState<boolean>(true);
|
||||||
|
const isComposingRef = useRef<boolean>(false);
|
||||||
|
|
||||||
// 初始化client和文档状态
|
// 初始化client和文档状态
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -18,6 +20,7 @@ function Editor({ name }: { name: string }) {
|
|||||||
const initialDoc = clientRef.current.document || "";
|
const initialDoc = clientRef.current.document || "";
|
||||||
prevDocRef.current = initialDoc;
|
prevDocRef.current = initialDoc;
|
||||||
setDoc(initialDoc);
|
setDoc(initialDoc);
|
||||||
|
setInputValue(initialDoc);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
@@ -35,9 +38,19 @@ function Editor({ name }: { name: string }) {
|
|||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
// 当doc变化时,同步到inputValue
|
||||||
|
useEffect(() => {
|
||||||
|
setInputValue(doc);
|
||||||
|
}, [doc]);
|
||||||
|
|
||||||
|
const handleChange = (newValue: string) => {
|
||||||
if (!clientRef.current) return;
|
if (!clientRef.current) return;
|
||||||
const newValue = e.target.value;
|
// 立即更新inputValue,让输入框响应更快
|
||||||
|
setInputValue(newValue);
|
||||||
|
// 如果正在输入CJK字符,不触发onChange逻辑
|
||||||
|
if (isComposingRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 使用client当前文档状态作为旧值
|
// 使用client当前文档状态作为旧值
|
||||||
const oldValue = clientRef.current.document || "";
|
const oldValue = clientRef.current.document || "";
|
||||||
if (newValue !== oldValue) {
|
if (newValue !== oldValue) {
|
||||||
@@ -50,6 +63,16 @@ function Editor({ name }: { name: string }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCompositionStart = () => {
|
||||||
|
isComposingRef.current = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCompositionEnd = (e: React.CompositionEvent<HTMLTextAreaElement>) => {
|
||||||
|
isComposingRef.current = false;
|
||||||
|
// composition结束后,手动触发一次change处理
|
||||||
|
handleChange(e.currentTarget.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleSync = () => {
|
const handleToggleSync = () => {
|
||||||
const newValue = !enableSync;
|
const newValue = !enableSync;
|
||||||
setEnableSync(newValue);
|
setEnableSync(newValue);
|
||||||
@@ -59,7 +82,9 @@ function Editor({ name }: { name: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ border: "1px solid #ccc", padding: 10 }}>
|
<div
|
||||||
|
style={{ border: "1px solid #ccc", padding: 10, display: "flex", flexDirection: "column", paddingRight: 16 }}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -74,27 +99,63 @@ function Editor({ name }: { name: string }) {
|
|||||||
<span>同步到服务端</span>
|
<span>同步到服务端</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<textarea value={doc} onChange={handleChange} rows={5} />
|
<textarea
|
||||||
|
value={inputValue}
|
||||||
|
onChange={(e) => handleChange(e.target.value)}
|
||||||
|
onCompositionStart={handleCompositionStart}
|
||||||
|
onCompositionEnd={handleCompositionEnd}
|
||||||
|
style={{ width: "100%", height: 100 }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
export function Server() {
|
||||||
const [serverDoc, setServerDoc] = useState<string>("");
|
const [serverDoc, setServerDoc] = useState<string>("");
|
||||||
|
const [operations, setOperations] = useState<string[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
setServerDoc(getDemoServerInstance().document || "");
|
const server = getDemoServerInstance();
|
||||||
|
setServerDoc(server.document || "");
|
||||||
|
setOperations(server.operations.map((op) => op.toString()));
|
||||||
}, 100);
|
}, 100);
|
||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ border: "1px solid #ccc", padding: 10, height: 150 }}>
|
||||||
<div style={{ border: "1px solid #ccc", padding: 10, height: 100 }}>
|
<div style={{ display: "flex", gap: 20 }}>
|
||||||
<p>Server Document:</p>
|
<div style={{ flex: 1 }}>
|
||||||
<p>{serverDoc}</p>
|
<p style={{ margin: "0 0 10px 0" }}>Server Document:</p>
|
||||||
|
<p style={{ margin: 0 }}>{serverDoc}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<p style={{ margin: "0 0 10px 0" }}>Operations:</p>
|
||||||
|
<div style={{ maxHeight: 100, overflowY: "auto" }}>
|
||||||
|
{operations.length === 0 ? (
|
||||||
|
<p style={{ margin: 0, color: "#999", fontSize: 14 }}>暂无操作</p>
|
||||||
|
) : (
|
||||||
|
operations.map((opStr, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{ marginBottom: 4, fontFamily: "monospace", fontSize: "12px" }}
|
||||||
|
>
|
||||||
|
{index + 1}. {opStr}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Server />
|
||||||
<div style={{ display: "flex", gap: 10, marginTop: 20 }}>
|
<div style={{ display: "flex", gap: 10, marginTop: 20 }}>
|
||||||
<Editor name="A" />
|
<Editor name="A" />
|
||||||
<Editor name="B" />
|
<Editor name="B" />
|
||||||
|
|||||||
Reference in New Issue
Block a user