Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Slate.js editor.apply(operation) is not applying "split_node" operations correctly

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: Case 1

enter image description here enter image description here

like image 331
HoneyBeer Avatar asked Feb 12 '20 08:02

HoneyBeer


1 Answers

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.

For Slate <= 0.47

editor.withoutNormalizing(() => {
  JSON.parse(ops).forEach(op => {
    editor.apply(op);
  });
})

For Slate >= 0.5

Editor.withoutNormalizing(editor, () => {
  JSON.parse(ops).forEach(op => {
    editor.apply(op);
  });
})
like image 74
Calyhre Avatar answered Nov 14 '22 08:11

Calyhre