Skip to content

Commit

Permalink
- Made label show on selection changes too
Browse files Browse the repository at this point in the history
- Cleaned up code
- Added visibility dot to cursors
- Added animations
  • Loading branch information
matthewlipski committed Jan 16, 2025
1 parent ebd1f12 commit a1dbddf
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 72 deletions.
156 changes: 89 additions & 67 deletions packages/core/src/editor/BlockNoteExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,87 +252,109 @@ const getTipTapExtensions = <
})
);
if (opts.collaboration.provider?.awareness) {
const defaultRender = (user: {
clientID: number;
color: string;
name: string;
}) => {
const cursor = document.createElement("span");

cursor.classList.add("collaboration-cursor__caret");
cursor.setAttribute("style", `border-color: ${user.color}`);

const label = document.createElement("span");

label.classList.add("collaboration-cursor__label");
label.setAttribute("style", `background-color: ${user.color}`);
label.insertBefore(document.createTextNode(user.name), null);

const nonbreakingSpace1 = document.createTextNode("\u2060");
const nonbreakingSpace2 = document.createTextNode("\u2060");

let hideTimeout: NodeJS.Timeout | undefined = undefined;
let oldDoc = opts.editor.document;
const awareness = opts.collaboration!.provider.awareness as Awareness;

awareness.on(
"change",
(a: {
added: Array<number>;
updated: Array<number>;
removed: Array<number>;
}) => {
if (!a.updated.includes(user.clientID)) {
return;
const cursors = new Map<
number,
{ element: HTMLElement; hideTimeout: NodeJS.Timeout | undefined }
>();

const awareness = opts.collaboration!.provider.awareness as Awareness;

awareness.on(
"change",
({
updated,
}: {
added: Array<number>;
updated: Array<number>;
removed: Array<number>;
}) => {
for (const clientID of updated) {
const cursor = cursors.get(clientID);

if (cursor) {
cursor.element.setAttribute("data-active", "");

if (cursor.hideTimeout) {
clearTimeout(cursor.hideTimeout);
}

cursors.set(clientID, {
element: cursor.element,
hideTimeout: setTimeout(() => {
cursor.element.removeAttribute("data-active");
}, 1000),
});
}
}
}
);

if (hideTimeout) {
clearTimeout(hideTimeout);
}
const createCursor = (clientID: number, name: string, color: string) => {
const cursorElement = document.createElement("span");

if (
JSON.stringify(opts.editor.document) !== JSON.stringify(oldDoc)
) {
cursor.insertBefore(nonbreakingSpace1, null);
cursor.insertBefore(label, null);
cursor.insertBefore(nonbreakingSpace2, null);

hideTimeout = setTimeout(() => {
label.remove();
nonbreakingSpace1.remove();
nonbreakingSpace2.remove();
}, 500);
}
cursorElement.classList.add("collaboration-cursor__caret");
cursorElement.setAttribute("style", `border-color: ${color}`);

oldDoc = opts.editor.document;
}
);
const labelElement = document.createElement("span");

cursor.addEventListener("mouseenter", () => {
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = undefined;
}
labelElement.classList.add("collaboration-cursor__label");
labelElement.setAttribute("style", `background-color: ${color}`);
labelElement.insertBefore(document.createTextNode(name), null);

cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space
cursorElement.insertBefore(labelElement, null);
cursorElement.insertBefore(document.createTextNode("\u2060"), null); // Non-breaking space

cursor.insertBefore(nonbreakingSpace1, null);
cursor.insertBefore(label, null);
cursor.insertBefore(nonbreakingSpace2, null);
cursors.set(clientID, {
element: cursorElement,
hideTimeout: undefined,
});

cursor.addEventListener("mouseleave", () => {
hideTimeout = setTimeout(() => {
label.remove();
nonbreakingSpace1.remove();
nonbreakingSpace2.remove();
}, 250);
cursorElement.addEventListener("mouseenter", () => {
const cursor = cursors.get(clientID)!;
cursor.element.setAttribute("data-active", "");

if (cursor.hideTimeout) {
clearTimeout(cursor.hideTimeout);
cursors.set(clientID, {
element: cursor.element,
hideTimeout: undefined,
});
}
});

return cursor;
cursorElement.addEventListener("mouseleave", () => {
const cursor = cursors.get(clientID)!;

cursors.set(clientID, {
element: cursor.element,
hideTimeout: setTimeout(() => {
cursor.element.removeAttribute("data-active");
}, 1000),
});
});

return cursors.get(clientID)!;
};

const defaultRender = (user: { color: string; name: string }) => {
const clientState = [...awareness.getStates().entries()].find(
(state) => state[1].user === user
);

if (!clientState) {
throw new Error("Could not find client state for user");
}

const clientID = clientState[0];

return (
cursors.get(clientID) || createCursor(clientID, user.name, user.color)
).element;
};
tiptapExtensions.push(
CollaborationCursor.configure({
user: {
clientID: opts.collaboration.provider.awareness.clientID,
name: opts.collaboration.user.name,
color: opts.collaboration.user.color,
},
Expand Down
66 changes: 61 additions & 5 deletions packages/core/src/editor/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,75 @@ Tippy popups that are appended to document.body directly
white-space: nowrap !important;
}

@keyframes label-collapse {
from {
border-radius: 3px 3px 3px 0;
color: #0d0d0d;
max-height: 1.1rem;
max-width: 10rem;
padding: 0.1rem 0.3rem;
top: -1.1rem;
}

to {
border-radius: 0 3px 3px 0;
color: transparent;
max-height: 4px;
max-width: 4px;
padding: 0;
top: 0;
}
}

/* Render the username above the caret */
.collaboration-cursor__label {
border-radius: 3px 3px 3px 0;
color: #0d0d0d;
animation-name: label-collapse;
animation-duration: 0.2s;
border-radius: 0 3px 3px 0;
color: transparent;
font-size: 12px;
font-style: normal;
font-weight: 600;
left: -1px;
line-height: normal;
padding: 0.1rem 0.3rem;
left: -1px;
max-height: 4px;
max-width: 4px;
overflow: hidden;
padding: 0;
position: absolute;
top: -1.4em;
top: 0;
user-select: none;
}

@keyframes label-expand {
from {
border-radius: 0 3px 3px 0;
color: transparent;
max-height: 4px;
max-width: 4px;
padding: 0;
top: -4px;
}

to {
border-radius: 3px 3px 3px 0;
color: #0d0d0d;
max-height: 1.1rem;
max-width: 20rem;
padding: 0.1rem 0.3rem;
top: -1.1rem;
}
}

.collaboration-cursor__caret[data-active] > .collaboration-cursor__label {
animation-name: label-expand;
animation-duration: 0.2s;
border-radius: 3px 3px 3px 0;
color: #0d0d0d;
max-height: 1.1rem;
max-width: 20rem;
padding: 0.1rem 0.3rem;
top: -1.1rem;
white-space: nowrap;
}

Expand Down

0 comments on commit a1dbddf

Please sign in to comment.