描述:
我正在使用 Yjs 作为我的 React 应用程序中的脚本编辑器实现协作编辑功能。协作编辑效果很好,但当多个用户同时键入时我遇到了问题。对于每次按键,Yjs 文档中的整个键入内容都会被复制。
这是我的设置的简化概述:
套接字处理(socket.mjs):
// socket.mjs
import { createServer } from 'http';
import { parse } from 'url';
import next from 'next';
import { Server as SocketIOServer } from 'socket.io';
import * as Y from 'yjs';
import dotenv from 'dotenv';
const envFile = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development';
dotenv.config({ path: envFile });
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const ydoc = new Y.Doc();
const yxmlFragment = ydoc.getXmlFragment('shared');
const ydocMap = new Map();
const activeUsers = {};
app.prepare().then(() => {
const server = createServer((req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
});
const io = new SocketIOServer(server, {
cors: {
origin: process.env.BASE_URL || "http://localhost:3000", // Use environment variable or default
methods: ["GET", "POST"],
allowedHeaders: ["*"],
credentials: true
}
});
io.on('connection', (socket) => {
// When a user connects, send the current document state as a binary update
console.log('Initiating')
socket.emit('init', Y.encodeStateAsUpdate(ydoc));
socket.on('updateScene', ({ sceneId, userId, update }) => {
console.log(sceneId);
const ydocForScene = ydocMap.get(sceneId);
Y.applyUpdate(ydocForScene, update);
socket.to(sceneId).emit(`update:${sceneId}`, { update: update, userId: userId });
});
});
const PORT = process.env.SOCKET_PORT || 4000;
server.listen(PORT, () => {
console.log(`> Ready on http://localhost:${PORT}`);
});
});
脚本编辑器组件(ScriptEditor.tsx):
// ScriptEditor.tsx
import * as yup from 'yup'
import io from 'socket.io-client';
import * as Y from 'yjs';
const ScriptEditor = (props: any) => {
// Initialize Yjs document and WebsocketProvider
const ydoc = new Y.Doc();
const wsProvider = new WebsocketProvider('ws://localhost:3001', 'my-room', ydoc);
initializeSocket(ydoc);
useEffect(() => {
// Initialize once and store in refs
if (!ydocRef.current) {
ydocRef.current = new Y.Doc();
}
if (!yxmlFragmentRef.current) {
yxmlFragmentRef.current = ydocRef.current.getXmlFragment('shared');
}
if (!socketRef.current) {
socketRef.current = io(process.env.WEB_SOCKET_URL);
socketRef.current.on('init', (update) => {
if (update) {
const updatedArray = new Uint8Array(update);
Y.applyUpdate(ydocRef.current, updatedArray);
}
});
}
const ydoc = ydocRef.current;
const yxmlFragment = yxmlFragmentRef.current;
const socket = socketRef.current;
socket.on(`update:${sceneId}`, (data) => {
if (data.userId != userData.user.id) {
try {
// Convert the received ArrayBuffer to a Uint8Array
const update = new Uint8Array(data.update);
// Apply the update to the new document
Y.applyUpdate(ydoc, update);
} catch (e) {
console.error('Error applying update:', e);
}
}
});
}
return (
<div>
{/* Your script editor UI and contenteditable area */}
<div
id="script-container"
ref={editorRef}
style={{ outline: 'none' }}
contentEditable={!(project?.isDemoProject || !allowEdit)}
dangerouslySetInnerHTML={{ __html: scriptText }}
onInput={valueChange}
onMouseUp={handleSelection}
onKeyDown={handleKeyDown}
onPaste={handlePaste}
>
</div>
);
};
export default ScriptEditor;
问题:
每当多个用户同时在协作脚本编辑器中输入内容时,每次按键都会导致整个内容在 Yjs 文档中重复。这会导致意外行为,并使协作编辑功能变得不切实际。
问题:
此外,我想提一下,我们正在使用自定义的 contenteditable
div
进行脚本编辑,并且我们没有使用任何内置插件(例如 Quill)。任何见解、建议或代码改进将不胜感激。谢谢!
我需要查看您的输入处理代码(例如 valueChange)。但我猜你正在通过完全覆盖其状态来更新
yXmlFragment
。 Yjs 将其解释为删除所有现有文本并插入所有新文本;如果两个用户同时这样做,他们都会插入完整的新文本,并复制它。
相反,您需要找出精确的更改(例如在...插入字符“x”)并调用相应的 Yjs 方法。例如。请参阅 y-prosemirror 的
updateYText
方法,该方法采用 diff 并将精确更改发送到 XML 树中的 Y.Text
节点:https://github.com/yjs/y-prosemirror/blob/master/src/插件/sync-plugin.js