Skip to content

Commit

Permalink
Fade out user cursor labels after inactivity
Browse files Browse the repository at this point in the history
Implementation details:
* Add a custom CollaborationCursor Tiptap extension
* Add CSS to fade out the cursor label after some time.
* Listen for Yjs updates.
  - If it's a doc change by ourself, update the timestamp of own user in
    awareness state.
  - If it's a remote awareness update, add back the CSS class to the
    corresponding cursor.
  - Wait 50ms before showing the cursor in the DOM to account for cases
    where the cursor gets re-rendered by y-prosemirror.

Fixes: #4126

Signed-off-by: Jonas <[email protected]>
  • Loading branch information
mejo- authored and max-nextcloud committed Jul 27, 2023
1 parent e96d170 commit 188298d
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 4 deletions.
24 changes: 20 additions & 4 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ import { getCurrentUser } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'
import { Collaboration } from '@tiptap/extension-collaboration'
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
import Autofocus from '../extensions/Autofocus.js'
import { Doc } from 'yjs'
Expand All @@ -108,7 +107,7 @@ import { createEditor, serializePlainText, loadSyntaxHighlight } from './../Edit
import { createMarkdownSerializer } from './../extensions/Markdown.js'
import markdownit from './../markdownit/index.js'
import { Keymap } from './../extensions/index.js'
import { CollaborationCursor, Keymap } from '../extensions/index.js'
import DocumentStatus from './Editor/DocumentStatus.vue'
import isMobile from './../mixins/isMobile.js'
import setContent from './../mixins/setContent.js'
Expand Down Expand Up @@ -322,6 +321,7 @@ export default {
},
created() {
this.$ydoc = new Doc()
this.$ydoc.on('update', this.onYjsUpdate)
this.$providers = []
this.$editor = null
this.$syncService = null
Expand Down Expand Up @@ -507,6 +507,8 @@ export default {
? session.displayName
: (session?.guestName || t('text', 'Guest')),
color: session?.color,
clientId: this.$ydoc.clientID,
lastUpdate: Date.now(),
},
}),
Keymap.configure({
Expand Down Expand Up @@ -642,6 +644,13 @@ export default {
this.emit('delete-image-node', imageUrl)
},
onYjsUpdate(_update, origin) {
if (origin.key === 'y-sync$') {
// Update timestamp of own cursor
this.$editor.commands.updateSelf()
}
},
async close() {
if (this.currentSession && this.$syncService) {
try {
Expand Down Expand Up @@ -794,7 +803,6 @@ export default {
width: 100%;
background-color: var(--color-main-background);
}
</style>
<style lang="scss">
Expand Down Expand Up @@ -918,6 +926,14 @@ export default {
padding: 0.1rem 0.3rem;
border-radius: 3px 3px 3px 0;
white-space: nowrap;
}
opacity: 0;
&.collaboration-cursor__label__active {
opacity: 1;
}
&:not(.collaboration-cursor__label__active) {
transition: opacity 0.2s 5s;
}
}
</style>
75 changes: 75 additions & 0 deletions src/extensions/CollaborationCursor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { CollaborationCursor as TiptapCollaborationCursor } from '@tiptap/extension-collaboration-cursor'

/**
* Show cursor for client ID
* Wait 50ms for cases where the cursor gets re-rendered
*
* @param {number} clientId The Yjs client ID
*/
function showCursorLabel(clientId) {
setTimeout(() => {
const el = document.getElementById(`collaboration-cursor__label__${clientId}`)
if (!el) {
return
}

el.classList.add('collaboration-cursor__label__active')
setTimeout(() => {
el?.classList.remove('collaboration-cursor__label__active')
}, 50)
}, 50)
}

const CollaborationCursor = TiptapCollaborationCursor.extend({
addOptions() {
return {
provider: null,
user: {
name: null,
clientId: null,
color: null,
lastUpdate: null,
},
render: user => {
const cursor = document.createElement('span')

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

const label = document.createElement('div')

label.classList.add('collaboration-cursor__label')
label.id = `collaboration-cursor__label__${user.clientId}`
label.setAttribute('style', `background-color: ${user.color}`)
label.insertBefore(document.createTextNode(user.name), null)
cursor.insertBefore(label, null)

return cursor
},
}
},

onCreate() {
this.options.provider.awareness.on('change', ({ added, removed, updated }, origin) => {
if (origin !== 'local') {
for (const clientId of [...added, ...updated]) {
if (clientId !== this.options.user.clientId) {
showCursorLabel(clientId)
}
}
}
})
},

addCommands() {
return {
...this.parent(),
updateSelf: () => ({ editor }) => {
const attributes = { ...this.options.user, lastUpdate: Date.now() }
return editor.commands.updateUser(attributes)
},
}
},
})

export default CollaborationCursor
2 changes: 2 additions & 0 deletions src/extensions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*
*/

import CollaborationCursor from './CollaborationCursor.js'
import Emoji from './Emoji.js'
import Keymap from './Keymap.js'
import UserColor from './UserColor.js'
Expand All @@ -30,6 +31,7 @@ import KeepSyntax from './KeepSyntax.js'
import Mention from './Mention.js'

export {
CollaborationCursor,
Emoji,
Keymap,
UserColor,
Expand Down

0 comments on commit 188298d

Please sign in to comment.