commit
This commit is contained in:
+71
-10
@@ -7,8 +7,10 @@ import { getDemoServerInstance } from "./ot/server";
|
||||
function Editor({ name }: { name: string }) {
|
||||
const clientRef = useRef<Client | null>(null);
|
||||
const [doc, setDoc] = useState<string>("");
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const prevDocRef = useRef<string>("");
|
||||
const [enableSync, setEnableSync] = useState<boolean>(true);
|
||||
const isComposingRef = useRef<boolean>(false);
|
||||
|
||||
// 初始化client和文档状态
|
||||
useLayoutEffect(() => {
|
||||
@@ -18,6 +20,7 @@ function Editor({ name }: { name: string }) {
|
||||
const initialDoc = clientRef.current.document || "";
|
||||
prevDocRef.current = initialDoc;
|
||||
setDoc(initialDoc);
|
||||
setInputValue(initialDoc);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -35,9 +38,19 @@ function Editor({ name }: { name: string }) {
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
// 当doc变化时,同步到inputValue
|
||||
useEffect(() => {
|
||||
setInputValue(doc);
|
||||
}, [doc]);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
if (!clientRef.current) return;
|
||||
const newValue = e.target.value;
|
||||
// 立即更新inputValue,让输入框响应更快
|
||||
setInputValue(newValue);
|
||||
// 如果正在输入CJK字符,不触发onChange逻辑
|
||||
if (isComposingRef.current) {
|
||||
return;
|
||||
}
|
||||
// 使用client当前文档状态作为旧值
|
||||
const oldValue = clientRef.current.document || "";
|
||||
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 newValue = !enableSync;
|
||||
setEnableSync(newValue);
|
||||
@@ -59,7 +82,9 @@ function Editor({ name }: { name: string }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ border: "1px solid #ccc", padding: 10 }}>
|
||||
<div
|
||||
style={{ border: "1px solid #ccc", padding: 10, display: "flex", flexDirection: "column", paddingRight: 16 }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -74,27 +99,63 @@ function Editor({ name }: { name: string }) {
|
||||
<span>同步到服务端</span>
|
||||
</label>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
export function Server() {
|
||||
const [serverDoc, setServerDoc] = useState<string>("");
|
||||
const [operations, setOperations] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => {
|
||||
setServerDoc(getDemoServerInstance().document || "");
|
||||
const server = getDemoServerInstance();
|
||||
setServerDoc(server.document || "");
|
||||
setOperations(server.operations.map((op) => op.toString()));
|
||||
}, 100);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ border: "1px solid #ccc", padding: 10, height: 100 }}>
|
||||
<p>Server Document:</p>
|
||||
<p>{serverDoc}</p>
|
||||
<div style={{ border: "1px solid #ccc", padding: 10, height: 150 }}>
|
||||
<div style={{ display: "flex", gap: 20 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<p style={{ margin: "0 0 10px 0" }}>Server Document:</p>
|
||||
<p style={{ margin: 0 }}>{serverDoc}</p>
|
||||
</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 }}>
|
||||
<Editor name="A" />
|
||||
<Editor name="B" />
|
||||
|
||||
Reference in New Issue
Block a user