Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulius Härtl <jus@bitgrid.net>2019-05-07 15:39:13 +0300
committerJulius Härtl <jus@bitgrid.net>2019-05-07 15:39:13 +0300
commitaed5581c6d40b2fd843cb1fd478c616e70a2f9f7 (patch)
treea5b08713d404b6ad1ceab5e3421fab9bb2b53e3c
parent66df4652bba0cbe403b6eb393d9b0e7f3ab8be36 (diff)
Add conflict view and loading indicator
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r--src/collab.js11
-rw-r--r--src/components/Editor.vue78
2 files changed, 86 insertions, 3 deletions
diff --git a/src/collab.js b/src/collab.js
index 17ff8f92c..4135516e2 100644
--- a/src/collab.js
+++ b/src/collab.js
@@ -115,6 +115,10 @@ class EditorSync {
this._forcedSave = true
}
+ manualSave() {
+ this._manualSave = true
+ }
+
fetchSteps() {
if (this.lock) {
return;
@@ -136,13 +140,18 @@ class EditorSync {
sessionId: this.session.id,
token: this.session.token,
version: authority.steps.length,
- autosaveContent
+ autosaveContent,
+ force: !!this._forcedSave,
+ manualSave: !!this._manualSave
}
}).then((response) => {
if (this.document.lastSavedVersion < response.data.document.lastSavedVersion) {
console.debug('Saved document', response.data.document)
this.document = response.data.document
}
+ this.view.setProps({editable: () => true})
+
+
this.onSyncHandlers.forEach((handler) => handler(response.data))
diff --git a/src/components/Editor.vue b/src/components/Editor.vue
index ff8f0db7b..380313cdd 100644
--- a/src/components/Editor.vue
+++ b/src/components/Editor.vue
@@ -26,7 +26,14 @@
<div class="save-status" :class="lastSavedStatusClass" v-tooltip="lastSavedStatusTooltip">{{ lastSavedStatus }}</div>
<avatar v-for="session in activeSessions" :key="session.id" :user="session.userId" :displayName="session.displayName" :style="sessionStyle(session)"></avatar>
</div>
- <div id="editor"></div>
+ <div id="editor-wrapper" :class="{'has-conflicts': syncError && syncError.type === ERROR_TYPE.SAVE_COLLISSION, 'icon-loading': !initialLoading}">
+ <div id="editor"></div>
+ <div id="remote" v-if="syncError && syncError.type === ERROR_TYPE.SAVE_COLLISSION"></div>
+ </div>
+ <div v-if="syncError && syncError.type === ERROR_TYPE.SAVE_COLLISSION" id="resolve-conflicts">
+ <button @click="resolveUseThisVersion">Use your version</button>
+ <button @click="resolveUseServerVersion">Use the server version</button>
+ </div>
</div>
</template>
@@ -44,7 +51,9 @@
import {EditorView} from 'prosemirror-view'
import {exampleSetup} from 'prosemirror-example-setup'
import {schema, defaultMarkdownParser, defaultMarkdownSerializer} from 'prosemirror-markdown'
- import { debounce, bind } from 'lodash'
+ import debounce from 'lodash/debounce'
+ import bind from 'lodash/bind'
+ import {baseKeymap} from "prosemirror-commands"
import {keymap} from "prosemirror-keymap"
@@ -90,6 +99,7 @@
sessions: [],
name: 'Guest',
dirty: false,
+ initialLoading: false,
lastSavedString: '',
syncError: null,
ERROR_TYPE: ERROR_TYPE
@@ -171,6 +181,7 @@
const {editor, authority} = this.initEditor(response.data, fileContent.data);
this.authority = authority
this.authority.onSync((data) => {
+ this.syncError = null
if (data.document) {
this.document = data.document
}
@@ -182,10 +193,16 @@
type: ERROR_TYPE.SAVE_COLLISSION,
data: data
}
+ this.$nextTick(() => {
+ this.initRemoteView()
+ })
}
})
this.authority.onStateChange(() => {
this.dirty = this.authority.dirty
+ if (!this.initialLoading) {
+ this.initialLoading = !this.authority.dirty
+ }
})
setInterval(() => { this.updateLastSavedStatus() }, 2000)
@@ -198,6 +215,36 @@
},
+ initRemoteView() {
+ if (this.remoteView) {
+ return;
+ }
+ this.remoteView = new EditorView(document.querySelector("#remote"), {
+ state: EditorState.create({
+ doc: defaultMarkdownParser.parse(this.syncError.data.outsideChange),
+ plugins: [
+ ...exampleSetup({schema})
+ ]
+ }),
+ focus() { this.view.focus() },
+ destroy() { this.view.destroy() }
+ })
+ view.setProps({editable: () => false})
+ },
+
+ resolveUseThisVersion() {
+ this.authority.forceSave()
+ this.removeRemoteView()
+ },
+
+ resolveUseServerVersion() {
+ this.removeRemoteView()
+ },
+
+ removeRemoteView() {
+ this.remoteView.destroy()
+ },
+
initEditor: (data, fileContent) => {
const authority = new EditorSync(defaultMarkdownParser.parse(fileContent), data)
@@ -256,11 +303,38 @@
background-color: var(--color-main-background);
}
+ #editor-wrapper {
+ display: flex;
+ height: 100%;
+ &.icon-loading {
+ #editor {
+ opacity: 0.3;
+ }
+ }
+ }
+
+ #resolve-conflicts {
+ display: flex;
+ button {
+ margin: auto;
+ }
+ }
+
#editor {
height: 100%;
overflow-y: scroll;
}
+ #editor-container.has-conflicts {
+ #remove, #editor {
+ width: 50%;
+ }
+
+ #remote .ProseMirror-menubar {
+ visibility: hidden;
+ }
+ }
+
#editor-session-list {
position: absolute;
top: 0;