diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 6d0bca680c4..b7ecc4f6c77 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -82,8 +82,8 @@ 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 { Doc } from 'yjs' +import debounce from 'debounce' import { EDITOR, @@ -98,7 +98,7 @@ import { import ReadonlyBar from './Menu/ReadonlyBar.vue' import { logger } from '../helpers/logger.js' -import { getDocumentState, applyDocumentState } from '../helpers/yjs.js' +import { yjsMessageTypes, getDocumentState, applyDocumentState, getMessageType, getAwarenessMessageClientIds } from '../helpers/yjs.js' import { SyncService, ERROR_TYPE, IDLE_TIMEOUT } from './../services/SyncService.js' import createSyncServiceProvider from './../services/SyncServiceProvider.js' import AttachmentResolver from './../services/AttachmentResolver.js' @@ -107,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' @@ -321,10 +321,12 @@ export default { }, created() { this.$ydoc = new Doc() + this.$ydoc.on('update', this.onYjsUpdate) this.$providers = [] this.$editor = null this.$syncService = null this.$attachmentResolver = null + this.$cursorLabelTimeouts = {} }, beforeDestroy() { if (!this.richWorkspace) { @@ -642,6 +644,35 @@ export default { this.emit('delete-image-node', imageUrl) }, + onYjsUpdate(update, _origin, _doc, _tr) { + // Update cursor labels for clients from awareness message + if (getMessageType(update) === yjsMessageTypes.awareness) { + if (!this.$editor) { + return + } + + const clientIds = getAwarenessMessageClientIds(update) + const updateUsers = this.$editor.storage.collaborationCursor.users + .filter(u => clientIds.includes(u.clientId)) + for (const user of updateUsers) { + this.updateCursorLabel(user) + } + } + }, + + updateCursorLabel: debounce(function(user) { + // Remove CSS class to fade out cursor label and add it back after five seconds. + // Limit this to once every second to prevent this from happening with each + const cursorLabelEls = document.getElementsByClassName(`collaboration-cursor__label__${user.name}`) + for (const el of cursorLabelEls) { + el.classList.remove('collaboration-cursor__label_fadeout') + clearTimeout(this.$cursorLabelTimeouts[user.clientId]) + this.$cursorLabelTimeouts[user.clientId] = setTimeout(() => { + el.classList.add('collaboration-cursor__label_fadeout') + }, 5000) + } + }, 1000, true), + async close() { if (this.currentSession && this.$syncService) { try { @@ -782,7 +813,6 @@ export default { width: 100%; background-color: var(--color-main-background); } -