Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 21 additions & 73 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<Status
:document="document"
:dirty="dirty"
:sessions="filteredSessions"
:awareness="awareness"
:sync-error="syncError"
:has-connection-issue="requireReconnect" />
</ReadonlyBar>
Expand All @@ -48,7 +48,7 @@
<Status
:document="document"
:dirty="dirty"
:sessions="filteredSessions"
:awareness="awareness"
:sync-error="syncError"
:has-connection-issue="requireReconnect"
@editor-width-change="handleEditorWidthChange" />
Expand Down Expand Up @@ -87,7 +87,7 @@ import { File } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { Collaboration } from '@tiptap/extension-collaboration'
import { useElementSize } from '@vueuse/core'
import Vue, { defineComponent, ref, set, shallowRef, watch } from 'vue'
import { defineComponent, ref, shallowRef, watch } from 'vue'
import { Doc } from 'yjs'
import Autofocus from '../extensions/Autofocus.js'

Expand All @@ -96,10 +96,10 @@ import { provideEditorFlags } from '../composables/useEditorFlags.ts'
import { ATTACHMENT_RESOLVER, FILE, IS_MOBILE } from './Editor.provider.ts'
import ReadonlyBar from './Menu/ReadonlyBar.vue'

import { t } from '@nextcloud/l10n'
import { generateRemoteUrl } from '@nextcloud/router'
import { Awareness } from 'y-protocols/awareness.js'
import { provideConnection } from '../composables/useConnection.ts'
import { useDelayedFlag } from '../composables/useDelayedFlag.ts'
import { useEditorMethods } from '../composables/useEditorMethods.ts'
import { provideSaveService } from '../composables/useSaveService.ts'
import { provideSyncService } from '../composables/useSyncService.ts'
Expand All @@ -126,7 +126,6 @@ import ContentContainer from './Editor/ContentContainer.vue'
import DocumentStatus from './Editor/DocumentStatus.vue'
import MainContainer from './Editor/MainContainer.vue'
import Status from './Editor/Status.vue'
import { useDelayedFlag } from './Editor/useDelayedFlag.ts'
import Wrapper from './Editor/Wrapper.vue'
import MenuBar from './Menu/MenuBar.vue'
import Translate from './Modal/Translate.vue'
Expand Down Expand Up @@ -245,7 +244,10 @@ export default defineComponent({
const extensions = [
Autofocus.configure({ fileId: props.fileId }),
Collaboration.configure({ document: ydoc }),
CollaborationCursor.configure({ provider: { awareness } }),
CollaborationCursor.configure({
provider: { awareness },
user: { clientId: ydoc.clientID, name: '' },
}),
]
const editor = isRichEditor
? createRichEditor({
Expand Down Expand Up @@ -303,12 +305,8 @@ export default defineComponent({
IDLE_TIMEOUT,

document: null,
sessions: [],
currentSession: null,
fileNode: null,

filteredSessions: {},

idle: false,
lock: null,
dirty: false,
Expand Down Expand Up @@ -343,7 +341,7 @@ export default defineComponent({
: '/'
},
displayed() {
return (this.currentSession && this.active) || this.syncError
return (this.connection && this.active) || this.syncError
},
showLoadingSkeleton() {
return (!this.contentLoaded || !this.displayed) && !this.syncError
Expand Down Expand Up @@ -503,54 +501,7 @@ export default defineComponent({
this.idle = false
},

updateSessions(sessions) {
this.sessions = sessions.sort((a, b) => b.lastContact - a.lastContact)

// Make sure we get our own session updated
// This should ideally be part of a global store where we can have that updated on the actual name change for guests
const currentUpdatedSession = this.sessions.find(
(session) => session.id === this.currentSession.id,
)
set(this, 'currentSession', currentUpdatedSession)

const currentSessionIds = this.sessions.map((session) => session.userId)
const currentGuestIds = this.sessions.map((session) => session.guestId)

const removedSessions = Object.keys(this.filteredSessions).filter(
(sessionId) =>
!currentSessionIds.includes(sessionId)
&& !currentGuestIds.includes(sessionId),
)

for (const index in removedSessions) {
Vue.delete(this.filteredSessions, removedSessions[index])
}
for (const index in this.sessions) {
const session = this.sessions[index]
const sessionKey = session.displayName ? session.userId : session.id
if (this.filteredSessions[sessionKey]) {
// update timestamp if relevant
if (
this.filteredSessions[sessionKey].lastContact
< session.lastContact
) {
set(
this.filteredSessions[sessionKey],
'lastContact',
session.lastContact,
)
}
} else {
set(this.filteredSessions, sessionKey, session)
}
if (session.id === this.currentSession.id) {
set(this.filteredSessions[sessionKey], 'isCurrent', true)
}
}
},

onOpened({ document, session, documentSource, documentState }) {
this.currentSession = session
this.document = document
this.readOnly = document.readOnly
this.baseVersionEtag = document.baseVersionEtag
Expand All @@ -559,18 +510,17 @@ export default defineComponent({

this.setEditable(this.editMode)
this.lock = this.syncService.lock
localStorage.setItem('nick', this.currentSession.guestName)
this.$attachmentResolver = new AttachmentResolver({
session: this.currentSession,
session,
user: getCurrentUser(),
shareToken: this.shareToken,
currentDirectory: this.currentDirectory,
})
if (this.currentSession?.userId && this.relativePath?.length) {
if (session?.userId && this.relativePath?.length) {
const node = new File({
id: this.fileId,
source: generateRemoteUrl(
`dav/files/${this.currentSession.userId}${this.relativePath}`,
`dav/files/${session.userId}${this.relativePath}`,
),
mime: this.mime,
})
Expand All @@ -589,20 +539,18 @@ export default defineComponent({
})
}
})
const user = {
name: session?.userId
? session.displayName
: session?.guestName || t('text', 'Guest'),
color: session?.color,
clientId: this.ydoc.clientID,
}
this.editor.commands.updateUser(user)
const { userId, displayName, guestName, color } = session
localStorage.setItem('nick', guestName)
this.editor.commands.updateUser({
...this.awareness.getLocalState().user,
userId,
name: userId ? displayName : guestName,
color,
})
},

onChange({ document, sessions }) {
this.updateSessions.bind(this)(sessions)
onChange({ document }) {
this.document = document

this.syncError = null
this.setEditable(this.editMode && !this.requireReconnect)
},
Expand Down
28 changes: 13 additions & 15 deletions src/components/Editor/AvatarWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
-->

<template>
<div class="avatar-wrapper" :style="sessionAvatarStyle">
<div class="avatar-wrapper" :style="clientAvatarStyle">
<NcAvatar
v-if="session.userId"
:user="session.userId ? session.userId : session.guestName"
:is-guest="session.userId === null"
v-if="client.userId"
:user="client.userId"
:disable-menu="true"
hide-status
:disable-tooltip="true"
:size="size" />
<div v-else class="avatar" :style="sessionBackgroundStyle">
<div v-else class="avatar" :style="clientBackgroundStyle">
{{ guestInitial }}
</div>
</div>
Expand All @@ -27,7 +26,7 @@ export default {
NcAvatar,
},
props: {
session: {
client: {
type: Object,
required: true,
},
Expand All @@ -37,27 +36,25 @@ export default {
},
},
computed: {
sessionAvatarStyle() {
clientAvatarStyle() {
return {
...this.sessionBackgroundStyle,
'border-color': this.session.color,
...this.clientBackgroundStyle,
'border-color': this.client.color,
'border-width': '2px',
'border-style': 'solid',
'--size': this.size + 'px',
'--font-size': this.size / 2 + 'px',
}
},
sessionBackgroundStyle() {
clientBackgroundStyle() {
return {
'background-color': this.session.userId
? this.session.color + ' !important'
'background-color': this.client.userId
? this.client.color + ' !important'
: '#b9b9b9',
}
},
guestInitial() {
return this.session.guestName === ''
? '?'
: this.session.guestName.slice(0, 1).toUpperCase()
return this.client.name?.at(0)?.toUpperCase() || '?'
},
},
}
Expand All @@ -77,5 +74,6 @@ export default {
display: flex;
justify-content: center;
align-items: center;
box-sizing: content-box;
}
</style>
40 changes: 12 additions & 28 deletions src/components/Editor/GuestNameDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
:title="t('text', 'Enter your name so other people can see who is editing')"
class="guest-name-dialog"
@submit.prevent="setGuestName()">
<label><AvatarWrapper :session="session" :size="32" /></label>
<label>
<AvatarWrapper :client="client" :size="32" />
</label>
<input
v-model="guestName"
v-model="guestNameEntry"
type="text"
:aria-label="t('text', 'Edit guest name')"
:placeholder="t('text', 'Guest')" />
Expand All @@ -25,7 +27,6 @@
<script>
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { useSyncService } from '../../composables/useSyncService.ts'
import AvatarWrapper from './AvatarWrapper.vue'

export default {
Expand All @@ -34,50 +35,33 @@ export default {
AvatarWrapper,
},
props: {
session: {
client: {
type: Object,
required: true,
},
},
setup() {
const { syncService } = useSyncService()
return { syncService }
guestName: {
type: String,
required: true,
},
},
data() {
return {
guestName: '',
guestNameBuffered: '',
guestNameEntry: this.guestName,
}
},
computed: {
avatarUrl() {
const size = 32
const avatarUrl = generateUrl('/avatar/guest/{user}/{size}', {
user: this.guestNameBuffered,
user: this.guestName,
size,
})
return window.location.protocol + '//' + window.location.host + avatarUrl
},
},
beforeMount() {
this.guestName = this.syncService.guestName
this.updateBufferedGuestName()
},
methods: {
setGuestName() {
const previousGuestName = this.syncService.guestName
this.syncService
.updateSession(this.guestName)
.then(() => {
localStorage.setItem('nick', this.guestName)
this.updateBufferedGuestName()
})
.catch((e) => {
this.guestName = previousGuestName
})
},
updateBufferedGuestName() {
this.guestNameBuffered = this.guestName
this.$emit('set-guest-name', this.guestNameEntry)
},
t,
},
Expand Down
Loading
Loading