I am designing a google-doc like collaborative tool with latest React + Slate as Frontend and Flask in Backend. I am using socket-io in React and flask_socketio in Python to emit and listen content from other collaborators. React app code:
const RichTextExample = props => {
const [value, setValue] = useState(props.currentEditor);
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
const id = useRef(`${Date.now()}`);
const remote = useRef(false);
const socketchange = useRef(false);
useEffect(() => {
socket.on("new-remote-operations", ({ editorId, ops, doc_id }) => {
if (id.current !== editorId && doc_id === props.document.doc_id) {
remote.current = true;
JSON.parse(ops).forEach(op => {
console.log("LISTEN: applying op", op);
editor.apply(op);
});
remote.current = false;
console.log('value is ', value);
socketchange.current = true; //variable to track socket changes in editor via operations
}
});}, [])
return(
<Slate
editor={editor}
value={value}
onChange={value => {
setValue(value);
const ops = editor.operations
.filter(o => {
if (o) {
return o.type !== "set_selection" && o.type !== "set_value";
}
return false;
});
if (ops.length && !remote.current && !socketchange.current) {
console.log("EMIT: Editor operations are ", ops);
socket.emit("new-operations", {
editorId: id.current,
ops: JSON.stringify(ops),
doc_id: props.document.doc_id
});
}
socketchange.current = false;
}}
>
Python code for socket is simple:
app = Flask(__name__)
db_name = 'userdoc.db'
app.config['SECRET_KEY'] = 'secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+db_name
socketio = SocketIO(app, cors_allowed_origins="*")
@socketio.on('new-operations', namespace='/')
def operations(data):
print('operations listened...1/2/3..')
emit('new-remote-operations', data, broadcast=True, include_self=False)
Issue:
When split_node is passed as an type of operation in socket.on(), editor.apply(op) doesn't apply it as it suppose to. Please help me on this.
Because of this, I get following two cases:
I think the issue you are facing is because you send a batch of operations that should not be applied one by one.
A split_node
operation like the one you are generating by hitting enter
will actually split all the nested nodes till it reaches the leaves, and move some nodes around.
Concretely, a split_node is actually 2-3 operations following each others, that can't be applied solely. If you apply the first one for example, that would split the text node, and end up with two Text
sharing the same attributes. Slate will normalize them and re-merge them as soon as it can, which in your case, happen between each editor.apply(op)
.
I think the solution here, is simply to wrap your whole loop inside the withoutNormalizing
method. It will prevent Slate to normalize the document in-between the operations.
editor.withoutNormalizing(() => {
JSON.parse(ops).forEach(op => {
editor.apply(op);
});
})
Editor.withoutNormalizing(editor, () => {
JSON.parse(ops).forEach(op => {
editor.apply(op);
});
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With