Skip to content

Commit

Permalink
Synchronization Issue when Multiple Users are Editing the Document in…
Browse files Browse the repository at this point in the history
… the Editor (#121)

* Change document local update logic

* Change yorkie updating logic

* Add auto soft line break
  • Loading branch information
devleejb authored Jan 30, 2024
1 parent f12d756 commit f628e6c
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 35 deletions.
15 changes: 13 additions & 2 deletions frontend/src/components/editor/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@ function Preview() {
useEffect(() => {
if (!editorStore.doc) return;

setContent(editorStore.doc?.getRoot().content?.toString() || "");
const updatePreviewContent = () => {
const editorText = editorStore.doc?.getRoot().content?.toString() || "";
// Add soft line break
setContent(
editorText
.split("\n")
.map((line) => line + " ")
.join("\n")
);
};

updatePreviewContent();

const unsubsribe = editorStore.doc.subscribe("$.content", () => {
setContent(editorStore.doc?.getRoot().content.toString() as string);
updatePreviewContent();
});

return () => {
Expand Down
65 changes: 32 additions & 33 deletions frontend/src/utils/yorkie/yorkieSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,24 @@ class YorkieSyncPluginValue implements cmView.PluginValue {
view: cmView.EditorView;
conf: YorkieSyncConfig<YorkieCodeMirrorDocType, YorkieCodeMirrorPresenceType>;
_doc: yorkie.Document<YorkieCodeMirrorDocType, YorkieCodeMirrorPresenceType>;
_observer: yorkie.NextFn<yorkie.DocEvent<YorkieCodeMirrorPresenceType>>;
_unsubscribe: yorkie.Unsubscribe;

constructor(view: cmView.EditorView) {
this.view = view;
this.conf = view.state.facet(yorkieSyncFacet);
this._doc = this.conf.doc;

this._doc.subscribe((event) => {
if (event.type !== "snapshot") return;

// The text is replaced to snapshot and must be re-synced.
const text = this._doc.getRoot().content;
view.dispatch({
changes: { from: 0, to: view.state.doc.length, insert: text.toString() },
annotations: [cmState.Transaction.remote.of(true)],
});
});

this._observer = (event) => {
this._doc.subscribe("$.content", (event) => {
if (event.type !== "remote-change") return;

const { operations } = event.value;
Expand All @@ -65,44 +75,33 @@ class YorkieSyncPluginValue implements cmView.PluginValue {

view.dispatch({
changes,
annotations: [yorkieSyncAnnotation.of(this.conf)],
annotations: [cmState.Transaction.remote.of(true)],
});
}
});
};
this._doc = this.conf.doc;
this._unsubscribe = this._doc.subscribe("$.content", this._observer);
});
}

update(update: cmView.ViewUpdate) {
if (
!update.docChanged ||
(update.transactions.length > 0 &&
update.transactions[0].annotation(yorkieSyncAnnotation) === this.conf)
) {
return;
}

let adj = 0;
this._doc.update((root, presence) => {
update.changes.iterChanges((fromA, toA, _fromB, _toB, insert) => {
if (!root.content) {
root.content = new yorkie.Text();
if (update.docChanged) {
for (const tr of update.transactions) {
const events = ["select", "input", "delete", "move", "undo", "redo"];
if (!events.some((event) => tr.isUserEvent(event))) {
continue;
}
const insertText = insert.sliceString(0, insert.length, "\n");
const updatedIndexRange = root.content.edit(fromA + adj, toA + adj, insertText);
adj += insertText.length - (toA - fromA);
if (updatedIndexRange) {
presence.set({
selection: root.content.indexRangeToPosRange(updatedIndexRange),
} as unknown as YorkieCodeMirrorPresenceType);
if (tr.annotation(cmState.Transaction.remote)) {
continue;
}
});
});
}

destroy() {
this._unsubscribe();
let adj = 0;
tr.changes.iterChanges((fromA, toA, _, __, inserted) => {
const insertText = inserted.toJSON().join("\n");
this._doc.update((root) => {
root.content.edit(fromA + adj, toA + adj, insertText);
});
adj += insertText.length - (toA - fromA);
});
}
}
}
}

Expand Down

0 comments on commit f628e6c

Please sign in to comment.