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
1 change: 1 addition & 0 deletions .github/workflows/cypress-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ env:
# Adjust APP_NAME if your repository name is different
APP_NAME: ${{ github.event.repository.name }}
CYPRESS_baseUrl: http://localhost:8081/index.php
CYPRESS_CACHE_FOLDER: ${{ github.workspace }}/Cypress

jobs:
init:
Expand Down
10 changes: 8 additions & 2 deletions src/components/CollisionResolveDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@

<template>
<div id="resolve-conflicts" class="collision-resolve-dialog" :class="{'icon-loading': clicked }">
<NcButton :disabled="clicked" data-cy="resolveThisVersion" @click="resolveThisVersion">
<NcButton size="large"
:disabled="clicked"
data-cy="resolveThisVersion"
@click="resolveThisVersion">
{{ t('text', 'Use current version') }}
</NcButton>
<NcButton :disabled="clicked" data-cy="resolveServerVersion" @click="resolveServerVersion">
<NcButton size="large"
:disabled="clicked"
data-cy="resolveServerVersion"
@click="resolveServerVersion">
{{ t('text', 'Use the saved version') }}
</NcButton>
</div>
Expand Down
20 changes: 8 additions & 12 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,6 @@
class="text-editor"
tabindex="-1"
@keydown.stop="onKeyDown">
<DocumentStatus v-if="displayedStatus"
:idle="idle"
:lock="lock"
:is-resolving-conflict="isResolvingConflict"
:sync-error="syncError"
:has-connection-issue="hasConnectionIssue"
@reconnect="reconnect" />

<SkeletonLoading v-if="showLoadingSkeleton" />
<Wrapper v-if="displayed"
:is-resolving-conflict="isResolvingConflict"
Expand Down Expand Up @@ -77,6 +69,12 @@
<Reader v-if="isResolvingConflict"
:content="syncError.data.outsideChange"
:is-rich-editor="isRichEditor" />
<CollisionResolveDialog v-if="isResolvingConflict" :sync-error="syncError" />
<DocumentStatus :idle="idle"
:lock="lock"
:sync-error="syncError"
:has-connection-issue="hasConnectionIssue"
@reconnect="reconnect" />
</Wrapper>
<Assistant v-if="$editor" />
</div>
Expand Down Expand Up @@ -132,6 +130,7 @@ import Assistant from './Assistant.vue'
export default {
name: 'Editor',
components: {
CollisionResolveDialog,
SkeletonLoading,
DocumentStatus,
Wrapper,
Expand Down Expand Up @@ -300,10 +299,7 @@ export default {
: '/'
},
displayed() {
return this.currentSession && this.active
},
displayedStatus() {
return this.displayed || !!this.syncError
return (this.currentSession && this.active) || this.syncError
},
showLoadingSkeleton() {
return (!this.contentLoaded || !this.displayed) && !this.syncError
Expand Down
103 changes: 35 additions & 68 deletions src/components/Editor/DocumentStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,56 +21,39 @@
-->

<template>
<div class="document-status">
<NcNoteCard v-if="hasWarning" type="warning">
<p v-if="isLoadingError">
{{ syncError.data.data.error }}
<!-- Display reload button on PRECONDITION_FAILED response type -->
<a v-if="syncError.data.status === 412" class="button primary" @click="reload">{{ t('text', 'Reload') }}</a>
</p>
<p v-else-if="hasSyncCollission">
{{ t('text', 'Document has been changed outside of the editor. The changes cannot be applied') }}
</p>
<p v-else-if="hasConnectionIssue">
{{ t('text', 'Document could not be loaded. Please check your internet connection.') }}
<a class="button primary" @click="reconnect">{{ t('text', 'Reconnect') }}</a>
</p>
</NcNoteCard>
<NcNoteCard v-else-if="idle" type="info">
<p>
{{ t('text', 'Document idle for {timeout} minutes, click to continue editing', { timeout: IDLE_TIMEOUT }) }}
<a class="button primary" @click="reconnect">{{ t('text', 'Reconnect') }}</a>
</p>
</NcNoteCard>
<NcNoteCard v-if="lock" type="info">
<template #icon>
<Lock :size="20" />
</template>
<p>
{{ t('text', 'This file is opened read-only as it is currently locked by {user}.', { user: lock.displayName }) }}
</p>
</NcNoteCard>

<CollisionResolveDialog v-if="isResolvingConflict" :sync-error="syncError" />
<div class="document-status" :class="{ mobile: isMobile }">
<div class="status-wrapper">
<SyncStatus :idle="idle"
:sync-error="syncError"
:has-connection-issue="hasConnectionIssue"
@reconnect="$emit('reconnect')" />
<NcNoteCard v-if="lock" type="info" :text="lockText">
<template #icon>
<Lock :size="20" />
</template>
</NcNoteCard>
</div>
</div>
</template>

<script>

import { ERROR_TYPE, IDLE_TIMEOUT } from './../../services/SyncService.js'
import Lock from 'vue-material-design-icons/Lock.vue'
import { NcNoteCard } from '@nextcloud/vue'
import CollisionResolveDialog from '../CollisionResolveDialog.vue'
import Lock from 'vue-material-design-icons/Lock.vue'
import isMobile from '../../mixins/isMobile.js'
import SyncStatus from './DocumentStatus/SyncStatus.vue'

export default {
name: 'DocumentStatus',

components: {
CollisionResolveDialog,
Lock,
SyncStatus,
NcNoteCard,
Lock,
},

mixins: [isMobile],

props: {
idle: {
type: Boolean,
Expand All @@ -84,55 +67,39 @@ export default {
type: Object,
default: null,
},

hasConnectionIssue: {
type: Boolean,
require: true,
},
isResolvingConflict: {
type: Boolean,
require: true,
},
},

data() {
return {
IDLE_TIMEOUT,
}
},

computed: {
hasSyncCollission() {
return this.syncError && this.syncError.type === ERROR_TYPE.SAVE_COLLISSION
},
isLoadingError() {
return this.syncError && this.syncError.type === ERROR_TYPE.LOAD_ERROR
},
hasWarning() {
return this.syncError || this.hasConnectionIssue
},
},

methods: {
reconnect() {
this.$emit('reconnect')
},
reload() {
window.location.reload()
lockText() {
return t(
'text',
'This file is opened read-only as it is currently locked by {user}.',
{ user: this.lock.displayName },
)
},
},

}
</script>

<style scoped lang="scss">
.document-status {
position: sticky;
top: 16px;
position: absolute;
bottom: var(--default-clickable-area);
z-index: 100000;
// max-height: 50px;
max-width: var(--text-editor-max-width);
margin: auto;
display: flex;
width: 100%;
justify-content: center;
}
.document-status.mobile {
bottom: 0;
}
.status-wrapper {
background-color: var(--color-main-background);
}
</style>
117 changes: 117 additions & 0 deletions src/components/Editor/DocumentStatus/SyncStatus.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!--
- SPDX-FileCopyrightText: 2022-2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<template>
<NcNoteCard v-if="hasWarning || idle" :type="card.type || 'warning'">
<p v-if="card.message">
{{ card.message }}
<a v-if="card.action" class="button primary" @click="card.action">{{ card.actionLabel }}</a>
</p>
</NcNoteCard>
</template>

<script>

import { ERROR_TYPE, IDLE_TIMEOUT } from '../../../services/SyncService.js'
import { NcNoteCard } from '@nextcloud/vue'

export default {
name: 'SyncStatus',

components: {
NcNoteCard,
},

props: {
idle: {
type: Boolean,
default: false,
},
syncError: {
type: Object,
default: null,
},
hasConnectionIssue: {
type: Boolean,
default: false,
},
},

data() {
return {
IDLE_TIMEOUT,
}
},

computed: {
card() {
if (this.isLoadingError) {
return {
message: this.syncError.data.data.error,
action: this.isPreconditionFailed ? this.reload : false,
actionLabel: t('text', 'Reload'),
}
}
if (this.hasSyncCollission) {
return {
message: t('text', 'Document has been changed outside of the editor. The changes cannot be applied'),
}
}
if (this.hasConnectionIssue) {
return {
message: t('text', 'Document could not be loaded. Please check your internet connection.'),
action: this.reconnect,
actionLabel: t('text', 'Reconnect'),
}
}
if (this.idle) {
return {
type: 'info',
message: t('text', 'Document idle for {timeout} minutes, click to continue editing', { timeout: IDLE_TIMEOUT }),
action: this.reconnect,
actionLabel: t('text', 'Reconnect'),
}
}
return {}
},
hasSyncCollission() {
return this.syncError && this.syncError.type === ERROR_TYPE.SAVE_COLLISSION
},
isLoadingError() {
return this.syncError && this.syncError.type === ERROR_TYPE.LOAD_ERROR
},
isPreconditionFailed() {
return this.isLoadingError && this.syncError.data.status === 412
},
hasWarning() {
return this.syncError || this.hasConnectionIssue
},
},

methods: {
reconnect() {
this.$emit('reconnect')
},
reload() {
window.location.reload()
},
},

}

</script>

<style scoped lang="scss">
.document-status {
.notecard {
margin-bottom: 0;
}
}
.document-status.mobile {
.notecard {
border-radius: 0;
}
}
</style>
1 change: 1 addition & 0 deletions src/components/Editor/Wrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export default {
display: flex;
width: 100%;
height: 100%;
overflow: auto;

&.show-color-annotations:deep(.author-annotation) {
padding-top: 2px;
Expand Down
6 changes: 5 additions & 1 deletion src/components/ViewerComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ export default {
</script>
<style lang="scss" scoped>
.text-editor:not(.viewer__file--hidden) {
overflow: scroll;
top: 0;
width: 100%;
max-width: 100%;
Expand Down Expand Up @@ -203,6 +202,11 @@ export default {
}
}

body .toastify.dialogs {
// Move the dialogs below the toolbar / status
margin-top: calc(45px + var(--default-clickable-area));
}

.viewer[data-handler='text'] .modal-wrapper .modal-container {
bottom: 0;
}
Expand Down
Loading