Skip to content
13 changes: 6 additions & 7 deletions cypress/e2e/share.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('Open test.md in viewer', function () {
it('Shares the file as a public read only link', function () {
cy.shareFile('/test.md')
.then((token) => {
cy.logout()
cy.clearCookies()
cy.visit(`/s/${token}`)
})
.then(() => {
Expand Down Expand Up @@ -71,7 +71,7 @@ describe('Open test.md in viewer', function () {
it('Opens the editor as guest', function () {
cy.shareFile('/test2.md', { edit: true })
.then((token) => {
cy.logout()
cy.clearCookies()
cy.visit(`/s/${token}`)
})
.then(() => {
Expand All @@ -91,9 +91,8 @@ describe('Open test.md in viewer', function () {
it('Shares a folder as a public read only link', function () {
cy.shareFile('/folder')
.then((token) => {
cy.logout()

return cy.visit(`/s/${token}`)
cy.clearCookies()
cy.visit(`/s/${token}`)
})
.then(() => {
cy.openFile('test.md')
Expand All @@ -112,7 +111,7 @@ describe('Open test.md in viewer', function () {
it('Opens the editor as guest and set a session username', function () {
cy.shareFile('/test3.md', { edit: true })
.then((token) => {
cy.logout()
cy.clearCookies()
cy.visit(`/s/${token}`)
})
.then(() => {
Expand Down Expand Up @@ -159,7 +158,7 @@ describe('Open test.md in viewer', function () {
url: '**/apps/text/public/session/*/create',
}).as('create')
cy.shareFile('/test2.md', { edit: true }).then((token) => {
cy.logout()
cy.clearCookies()
cy.visit(`/s/${token}`)
})
cy.wait('@create', { timeout: 10000 })
Expand Down
33 changes: 32 additions & 1 deletion src/apis/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import type { Connection } from '../composables/useConnection.js'
import type { Document, Session } from '../services/SyncService.js'

export interface OpenParams {
fileId?: number
Expand All @@ -16,7 +17,13 @@ export interface OpenParams {
}

export interface OpenData {
document: { baseVersionEtag: string }
document: Document
session: Session
readOnly: boolean
content: string
documentState?: string
lock?: object
hasOwner: boolean
}

/**
Expand All @@ -43,6 +50,30 @@ export async function open(
return { connection, data: response.data }
}

/**
* Update the guest name
* @param guestName the name to use for the local user
* @param connection connection to close
*/
export async function update(
guestName: string,
connection: Connection,
): Promise<Session> {
if (!connection.shareToken) {
throw new Error('Cannot set guest name without a share token!')
}
const id = connection.documentId
const url = generateUrl(`/apps/text/public/session/${id}/session`)
const response = await axios.post(url, {
documentId: connection.documentId,
sessionId: connection.sessionId,
sessionToken: connection.sessionToken,
token: connection.shareToken,
guestName,
})
return response.data
}

/**
* Close the connection
* @param connection connection to close
Expand Down
4 changes: 2 additions & 2 deletions src/apis/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { unref, type ShallowRef } from 'vue'
import type { Connection } from '../composables/useConnection.js'
import type { Session, Step } from '../services/SyncService.js'
import type { Connection } from '../composables/useConnection.ts'
import type { Document, Session, Step } from '../services/SyncService.ts'

interface PushData {
version: number
Expand Down
17 changes: 6 additions & 11 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
</Wrapper>
<DocumentStatus
:idle="idle"
:lock="lock"
:lock="document?.lock"
:sync-error="syncError"
:has-connection-issue="requireReconnect"
@reconnect="reconnect" />
Expand Down Expand Up @@ -238,8 +238,7 @@ export default defineComponent({
isRichEditor,
props,
)
const { connection, openConnection, baseVersionEtag } =
provideConnection(props)
const { connection, openConnection } = provideConnection(props)
const { syncService } = provideSyncService(connection, openConnection)
const extensions = [
Autofocus.configure({ fileId: props.fileId }),
Expand Down Expand Up @@ -279,7 +278,6 @@ export default defineComponent({

return {
awareness,
baseVersionEtag,
editor,
el,
hasConnectionIssue,
Expand Down Expand Up @@ -312,7 +310,6 @@ export default defineComponent({
filteredSessions: {},

idle: false,
lock: null,
dirty: false,
contentLoaded: false,
syncError: null,
Expand Down Expand Up @@ -542,16 +539,14 @@ export default defineComponent({
}
},

onOpened({ document, session, documentSource, documentState }) {
onOpened({ document, session, content, documentState, readOnly }) {
this.currentSession = session
this.document = document
this.readOnly = document.readOnly
this.baseVersionEtag = document.baseVersionEtag
this.editMode = !document.readOnly && !this.openReadOnlyEnabled
this.readOnly = readOnly
this.editMode = !readOnly && !this.openReadOnlyEnabled
this.hasConnectionIssue = false

this.setEditable(this.editMode)
this.lock = this.syncService.lock
localStorage.setItem('nick', this.currentSession.guestName)
this.$attachmentResolver = new AttachmentResolver({
session: this.currentSession,
Expand All @@ -577,7 +572,7 @@ export default defineComponent({
this.lowlightLoaded.then(() => {
this.syncService.startSync()
if (!documentState) {
setInitialYjsState(this.ydoc, documentSource, {
setInitialYjsState(this.ydoc, content, {
isRichEditor: this.isRichEditor,
})
}
Expand Down
24 changes: 16 additions & 8 deletions src/components/Editor/GuestNameDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
</template>

<script>
import { showError, showWarning } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { generateUrl } from '@nextcloud/router'
import { useSyncService } from '../../composables/useSyncService.ts'
import { update } from '../../apis/connect.ts'
import { useConnection } from '../../composables/useConnection.ts'
import AvatarWrapper from './AvatarWrapper.vue'

export default {
Expand All @@ -40,8 +42,8 @@ export default {
},
},
setup() {
const { syncService } = useSyncService()
return { syncService }
const { connection } = useConnection()
return { connection }
},
data() {
return {
Expand All @@ -60,19 +62,25 @@ export default {
},
},
beforeMount() {
this.guestName = this.syncService.guestName
this.guestName = this.session.guestName
this.updateBufferedGuestName()
},

methods: {
setGuestName() {
const previousGuestName = this.syncService.guestName
this.syncService
.updateSession(this.guestName)
if (!this.connection) {
showError(t('text', 'Not connected. Cannot update guest name.'))
return
}
const previousGuestName = this.session.guestName
update(this.guestName, this.connection)
.then(() => {
localStorage.setItem('nick', this.guestName)
this.updateBufferedGuestName()
})
.catch((e) => {
.catch((error) => {
console.warn('Failed to update the session', { error })
showWarning(t('text', 'Failed to update the guest name.'))
this.guestName = previousGuestName
})
},
Expand Down
77 changes: 77 additions & 0 deletions src/components/Editor/OfflineState.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<div class="offline-state">
<NcButton
:title="label"
:aria-label="label"
:disabled="true"
type="tertiary"
class="offline-button">
<template #icon>
<CloudOffIcon :size="20" />
</template>
</NcButton>
</div>
</template>

<script>
import moment from '@nextcloud/moment'
import NcButton from '@nextcloud/vue/components/NcButton'
import CloudOffIcon from 'vue-material-design-icons/CloudOff.vue'
import refreshMoment from '../../mixins/refreshMoment.js'

export default {
name: 'OfflineState',

components: {
CloudOffIcon,
NcButton,
},

mixins: [refreshMoment],

props: {
// 0 if there is a different connection issue than the browser being offline
offlineSince: {
type: Number,
default: 0,
},
},

computed: {
label() {
if (this.offlineSince) {
return t('text', 'Offline since {time}.', { time: this.offlineTime })
}
return t('text', 'Not connected to the cloud.')
},
offlineTime() {
// Make this a dependent of refreshMoment, so it will be recomputed
/* eslint-disable-next-line no-unused-expressions */
this.refreshMoment
return moment(this.offlineSince).fromNow()
},
},
}
</script>

<style scoped lang="scss">
.offline-state {
height: var(--default-clickable-area);
width: calc(var(--default-clickable-area) + 10px);
}

.offline-button {
padding-inline: var(--default-grid-baseline);
width: calc(var(--default-clickable-area) + 10px) !important;

&.button-vue:disabled {
opacity: unset;
filter: unset;
}
}
</style>
18 changes: 16 additions & 2 deletions src/components/Editor/SessionList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<template>
<NcPopover
:no-focus-trap="!$slots.default"
:no-focus-trap="!showGuestNameDialog"
class="session-list"
placement="bottom">
<template #trigger="{ attrs }">
Expand All @@ -31,7 +31,9 @@
<div class="session-menu">
<slot name="lastSaved" />
<ul>
<slot />
<GuestNameDialog
v-if="showGuestNameDialog"
:session="currentSession" />
<li
v-for="session in participantsPopover"
:key="session.id"
Expand Down Expand Up @@ -61,17 +63,20 @@ import { t } from '@nextcloud/l10n'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcPopover from '@nextcloud/vue/components/NcPopover'
import AccountMultipleOutlineIcon from 'vue-material-design-icons/AccountMultipleOutline.vue'
import { useEditorFlags } from '../../composables/useEditorFlags.ts'
import {
COLLABORATOR_DISCONNECT_TIME,
COLLABORATOR_IDLE_TIME,
} from '../../services/SyncService.ts'
import AvatarWrapper from './AvatarWrapper.vue'
import GuestNameDialog from './GuestNameDialog.vue'

export default {
name: 'SessionList',
components: {
AccountMultipleOutlineIcon,
AvatarWrapper,
GuestNameDialog,
NcButton,
NcPopover,
},
Expand All @@ -83,6 +88,10 @@ export default {
},
},
},
setup() {
const { isPublic } = useEditorFlags()
return { isPublic }
},
data() {
return {
myName: '',
Expand Down Expand Up @@ -111,6 +120,11 @@ export default {
)
.sort((a, b) => a.lastContact < b.lastContact)
},
showGuestNameDialog() {
return (
this.isPublic && this.currentSession && !this.currentSession.userId
)
},
currentSession() {
return Object.values(this.sessions).find((session) => session.isCurrent)
},
Expand Down
Loading
Loading