Skip to content

Commit

Permalink
Fix issue of incorrect display of remote selection in Quill example (#…
Browse files Browse the repository at this point in the history
…769)

This commit addresses the issue of the remote cursor not being
accurately displayed when local edits are made in the Quill example.
It ensures that the remote cursor is correctly rendered after each
edit.(Ref: y-quill)

It also adds extra checks to update the selection when there are
differences between the selection in Yorkie and Quill. For example,
if you add text before another user's selected text, Quill might
include the added text in the remote-selection. In such cases, it is
necessary to update the Quill selection in Yorkie.
  • Loading branch information
chacha912 authored Apr 5, 2024
1 parent ae4edd4 commit 653f98e
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 73 deletions.
1 change: 0 additions & 1 deletion examples/vanilla-quill/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"quill": "^1.3.7",
"quill-cursors": "^4.0.0",
"quill-delta": "^5.0.0",
"short-unique-id": "^4.4.4",
"yorkie-js-sdk": "^0.4.13"
}
}
68 changes: 44 additions & 24 deletions examples/vanilla-quill/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import yorkie, { DocEventType, Indexable, OperationInfo } from 'yorkie-js-sdk';
import Quill, { type DeltaOperation, type DeltaStatic } from 'quill';
import QuillCursors from 'quill-cursors';
import ShortUniqueId from 'short-unique-id';
import ColorHash from 'color-hash';
import { network } from './network';
import { displayLog, displayPeers } from './utils';
Expand All @@ -19,7 +18,6 @@ const peersElem = document.getElementById('peers')!;
const documentElem = document.getElementById('document')!;
const documentTextElem = document.getElementById('document-text')!;
const networkStatusElem = document.getElementById('network-status')!;
const shortUniqueID = new ShortUniqueId();
const colorHash = new ColorHash();
const documentKey = `vanilla-quill-${new Date()
.toISOString()
Expand Down Expand Up @@ -62,7 +60,8 @@ async function main() {

await client.attach(doc, {
initialPresence: {
username: `username-${shortUniqueID()}`,
username: client.getID()!.slice(-2),
color: colorHash.hex(client.getID()!.slice(-2)),
selection: undefined,
},
});
Expand All @@ -87,32 +86,36 @@ async function main() {
if (event.type === 'remote-change') {
handleOperations(event.value.operations);
}
updateAllCursors();
});
doc.subscribe('others', (event) => {
if (event.type === DocEventType.Unwatched) {
cursors.removeCursor(event.value.presence.username);
cursors.removeCursor(event.value.clientID);
} else if (event.type === DocEventType.PresenceChanged) {
displayRemoteCursors([event.value]);
updateCursor(event.value);
}
});

function displayRemoteCursors(
peers: Array<{ clientID: string; presence: YorkiePresence }>,
) {
for (const peer of peers) {
const {
presence: { username, selection },
} = peer;
if (!selection) continue;
const [fromIdx, toIdx] = doc
.getRoot()
.content.posRangeToIndexRange(selection);

cursors.createCursor(username, username, colorHash.hex(username));
cursors.moveCursor(username, {
index: fromIdx,
length: toIdx - fromIdx,
});
function updateCursor(user: { clientID: string; presence: YorkiePresence }) {
const { clientID, presence } = user;
if (clientID === client.getID()) return;
// TODO(chacha912): After resolving the presence initialization issue(#608),
// remove the following check.
if (!presence) return;

const { username, color, selection } = presence;
if (!selection) return;
const range = doc.getRoot().content.posRangeToIndexRange(selection);
cursors.createCursor(clientID, username, color);
cursors.moveCursor(clientID, {
index: range[0],
length: range[1] - range[0],
});
}

function updateAllCursors() {
for (const user of doc.getPresences()) {
updateCursor(user);
}
}

Expand Down Expand Up @@ -210,10 +213,27 @@ async function main() {
}
})
.on('selection-change', (range, _, source) => {
if (source === 'api' || !range) {
if (!range) {
return;
}

// NOTE(chacha912): If the selection in the Quill editor does not match the range computed by yorkie,
// additional updates are necessary. This condition addresses situations where Quill's selection behaves
// differently, such as when inserting text before a range selection made by another user, causing
// the second character onwards to be included in the selection.
if (source === 'api') {
const { selection } = doc.getMyPresence();
if (selection) {
const [from, to] = doc
.getRoot()
.content.posRangeToIndexRange(selection);
const { index, length } = range;
if (from === index && to === index + length) {
return;
}
}
}

doc.update((root, presence) => {
presence.set({
selection: root.content.indexRangeToPosRange([
Expand Down Expand Up @@ -301,7 +321,7 @@ async function main() {
}

syncText();
displayRemoteCursors(doc.getPresences());
updateAllCursors();
displayLog(documentElem, documentTextElem, doc);
}

Expand Down
1 change: 1 addition & 0 deletions examples/vanilla-quill/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export type YorkieDoc = {

export type YorkiePresence = {
username: string;
color: string;
selection: TextPosStructRange | undefined;
};
29 changes: 10 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 48 additions & 25 deletions public/quill.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/quill-cursors.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/color-hash.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/short-unique-id.min.js"></script>
</head>
<body>
<div id="network-status"></div>
Expand All @@ -28,7 +27,6 @@
const documentElem = document.getElementById('document');
const documentTextElem = document.getElementById('document-text');
const networkStatusElem = document.getElementById('network-status');
const shortUniqueID = new ShortUniqueId();
const colorHash = new ColorHash();
const documentKey = 'quill';

Expand All @@ -51,15 +49,16 @@
}

function displayOnlineClients(presences, myClientID) {
const usernames = [];
const clients = [];
for (const { clientID, presence } of presences) {
usernames.push(
myClientID === clientID
? `<b>${presence.username}</b>`
: presence.username,
);
const clientElem = `<span class="client" style='background: ${presence.color}; color: white; margin-right:2px; padding:2px;'>${presence.name}</span>`;
if (myClientID === clientID) {
clients.unshift(clientElem);
continue;
}
clients.push(clientElem);
}
onlineClientsElem.innerHTML = JSON.stringify(usernames);
onlineClientsElem.innerHTML = clients.join('');
}

async function main() {
Expand All @@ -77,7 +76,10 @@
});

await client.attach(doc, {
initialPresence: { username: `username-${shortUniqueID()}` },
initialPresence: {
name: client.getID().slice(-2),
color: colorHash.hex(client.getID().slice(-2)),
},
});

doc.update((root) => {
Expand All @@ -101,12 +103,14 @@
const { actor, message, operations } = event.value;
handleOperations(operations, actor);
}
updateAllCursors();
});

doc.subscribe('others', (event) => {
if (event.type === 'unwatched') {
cursors.removeCursor(event.value.presence.username);
cursors.removeCursor(event.value.clientID);
} else if (event.type === 'presence-changed') {
displayRemoteCursor(event.value);
updateCursor(event.value);
}
});

Expand Down Expand Up @@ -138,20 +142,29 @@
});
const cursors = quill.getModule('cursors');

function displayRemoteCursor(user) {
const {
clientID: id,
presence: { username, selection },
} = user;
if (!selection || id === client.getID()) return;
function updateCursor(user) {
const { clientID, presence } = user;
if (clientID === client.getID()) return;
// TODO(chacha912): After resolving the presence initialization issue(#608),
// remove the following check.
if (!presence) return;

const { name, color, selection } = presence;
if (!selection) return;
const range = doc.getRoot().content.posRangeToIndexRange(selection);
cursors.createCursor(username, username, colorHash.hex(username));
cursors.moveCursor(username, {
cursors.createCursor(clientID, name, color);
cursors.moveCursor(clientID, {
index: range[0],
length: range[1] - range[0],
});
}

function updateAllCursors() {
for (const user of doc.getPresences()) {
updateCursor(user);
}
}

// 04. bind the document with the Quill.
// 04-1. Quill to Document.
quill
Expand Down Expand Up @@ -231,9 +244,22 @@
}
})
.on('selection-change', (range, _, source) => {
if (source === 'api' || !range) {
if (!range) {
return;
}
// NOTE(chacha912): If the selection in the Quill editor does not match the range computed by yorkie,
// additional updates are necessary. This condition addresses situations where Quill's selection behaves
// differently, such as when inserting text before a range selection made by another user, causing
// the second character onwards to be included in the selection.
if (source === 'api') {
const [from, to] = doc
.getRoot()
.content.posRangeToIndexRange(doc.getMyPresence().selection);
const { index, length } = range;
if (from === index && to === index + length) {
return;
}
}

doc.update((root, presence) => {
presence.set({
Expand All @@ -250,7 +276,6 @@
const deltaOperations = [];
let prevTo = 0;
for (const op of ops) {
const actorName = doc.getPresence(actor).username;
const from = op.from;
const to = op.to;
const retainFrom = from - prevTo;
Expand Down Expand Up @@ -318,10 +343,8 @@
}

syncText();
updateAllCursors();
displayLog(documentElem, documentTextElem, doc);
for (const user of doc.getPresences()) {
displayRemoteCursor(user);
}
} catch (e) {
console.error(e);
}
Expand Down
4 changes: 0 additions & 4 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ menu, nav, output, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
box-sizing: border-box;
}
ol,
Expand Down

0 comments on commit 653f98e

Please sign in to comment.