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:
-rw-r--r--src/EditorSync.js102
-rw-r--r--src/components/Editor.vue423
-rw-r--r--src/components/ReadOnlyEditor.vue74
-rw-r--r--src/files.js33
-rw-r--r--src/helpers.js38
-rw-r--r--src/main.js7
6 files changed, 361 insertions, 316 deletions
diff --git a/src/EditorSync.js b/src/EditorSync.js
index 8a0dec498..663f7dc04 100644
--- a/src/EditorSync.js
+++ b/src/EditorSync.js
@@ -21,37 +21,36 @@
*/
import axios from 'nextcloud-axios'
-import {schema, defaultMarkdownSerializer} from "prosemirror-markdown"
-import {receiveTransaction, sendableSteps, getVersion} from 'prosemirror-collab';
-import {Step} from 'prosemirror-transform';
+import { schema, defaultMarkdownSerializer } from 'prosemirror-markdown'
+import { receiveTransaction, sendableSteps, getVersion } from 'prosemirror-collab'
+import { Step } from 'prosemirror-transform'
/**
* Minimum inverval to refetch the document changes
* @type {number}
*/
-const FETCH_INTERVAL = 200;
+const FETCH_INTERVAL = 200
/**
* Maximum interval between refetches of document state if multiple users have joined
* @type {number}
*/
-const FETCH_INTERVAL_MAX = 2000;
+const FETCH_INTERVAL_MAX = 2000
/**
* Interval to check for changes when there is only one user joined
* @type {number}
*/
-const FETCH_INTERVAL_SINGLE_EDITOR = 5000;
+const FETCH_INTERVAL_SINGLE_EDITOR = 5000
-const MIN_PUSH_RETRY = 500;
-const MAX_PUSH_RETRY = 10000;
-const WARNING_PUSH_RETRY = 2000;
-const COLLABORATOR_DISCONNECT_TIME = 20;
+const MIN_PUSH_RETRY = 500
+const MAX_PUSH_RETRY = 10000
-/**
- * Define how often the editor should retry to apply local changes, before warning the user
- */
-const MAX_REBASE_RETRY = 5;
+/* Timeout after that a PUSH_FAILURE error is emitted */
+const WARNING_PUSH_RETRY = 5000
+
+/* Timeout for sessions to be marked as disconnected */
+const COLLABORATOR_DISCONNECT_TIME = 20
const ERROR_TYPE = {
/**
@@ -62,13 +61,14 @@ const ERROR_TYPE = {
/**
* Failed to push changes for MAX_REBASE_RETRY times
*/
- PUSH_FAILURE: 1,
+ PUSH_FAILURE: 1
}
const URL_SYNC = OC.generateUrl('/apps/text/session/sync')
-const URL_PUSH = OC.generateUrl('/apps/text/session/push');
+const URL_PUSH = OC.generateUrl('/apps/text/session/push')
class EditorSync {
+
constructor(doc, data) {
this.view = null
this.doc = doc
@@ -79,7 +79,7 @@ class EditorSync {
this.lock = false
this.retryTime = MIN_PUSH_RETRY
this.dirty = false
- this.fetchInverval = FETCH_INTERVAL;
+ this.fetchInverval = FETCH_INTERVAL
this.onSyncHandlers = []
this.onErrorHandlers = []
@@ -124,15 +124,15 @@ class EditorSync {
fetchSteps() {
if (this.lock) {
- return;
+ return
}
- this.lock = true;
+ this.lock = true
this.triggerStateChange()
- const authority = this;
- let autosaveContent = undefined
+ const authority = this
+ let autosaveContent
if (
- this._forcedSave || this._manualSave ||
- (!sendableSteps(this.view.state) && (authority.steps.length > this.document.lastSavedVersion))
+ this._forcedSave || this._manualSave
+ || (!sendableSteps(this.view.state) && (authority.steps.length > this.document.lastSavedVersion))
) {
autosaveContent = this.content()
}
@@ -149,22 +149,22 @@ class EditorSync {
console.debug('Saved document', response.data.document)
this.document = response.data.document
}
- this.view.setProps({editable: () => true})
+ this.view.setProps({ editable: () => true })
this.onSyncHandlers.forEach((handler) => handler(response.data))
if (response.data.steps.length === 0) {
- this.lock = false;
- if (response.data.sessions.filter((session) => session.lastContact > Date.now()/1000-COLLABORATOR_DISCONNECT_TIME).length < 2) {
- this.maximumRefetchTimer();
+ this.lock = false
+ if (response.data.sessions.filter((session) => session.lastContact > Date.now() / 1000 - COLLABORATOR_DISCONNECT_TIME).length < 2) {
+ this.maximumRefetchTimer()
} else {
- this.increaseRefetchTimer();
+ this.increaseRefetchTimer()
}
- return;
+ return
}
for (let i = 0; i < response.data.steps.length; i++) {
- let steps = response.data.steps[i].data.map(j => Step.fromJSON(schema, j));
+ let steps = response.data.steps[i].data.map(j => Step.fromJSON(schema, j))
steps.forEach(step => {
authority.steps.push(step)
authority.stepClientIDs.push(response.data.steps[i].sessionId)
@@ -174,30 +174,29 @@ class EditorSync {
authority.view.dispatch(
receiveTransaction(authority.view.state, newData.steps, newData.clientIDs)
)
- console.log(getVersion(authority.view.state))
- this.lock = false;
- this._forcedSave = false;
- //this.sendSteps()
- this.resetRefetchTimer();
+ console.debug('Synced new steps, current version is ' + getVersion(authority.view.state))
+ this.lock = false
+ this._forcedSave = false
+ // this.sendSteps()
+ this.resetRefetchTimer()
}).catch((e) => {
- this.lock = false;
- console.log('fetch error sendSteps')
- //this.sendSteps()
+ this.lock = false
+ // this.sendSteps()
if (e.response.status === 409) {
- console.log('Conflict during file save, please resolve')
- this.view.setProps({editable: () => false})
+ console.error('Conflict during file save, please resolve')
+ this.view.setProps({ editable: () => false })
// TODO recover
this.onErrorHandlers.forEach((handler) => handler(ERROR_TYPE.SAVE_COLLISSION, {
outsideChange: e.response.data.outsideChange
}))
}
})
- this._manualSave = false;
- this._forcedSave = false;
+ this._manualSave = false
+ this._forcedSave = false
}
resetRefetchTimer() {
- this.fetchInverval = FETCH_INTERVAL;
+ this.fetchInverval = FETCH_INTERVAL
clearInterval(this.fetcher)
this.fetcher = setInterval(() => this.fetchSteps(), this.fetchInverval)
@@ -225,8 +224,9 @@ class EditorSync {
carefulRetry(callback) {
let newRetry = this.retryTime ? Math.min(this.retryTime * 2, MAX_PUSH_RETRY) : MIN_PUSH_RETRY
if (newRetry > WARNING_PUSH_RETRY && this.retryTime < WARNING_PUSH_RETRY) {
- OC.Notification.showTemporary('Changes could not be sent yet');
- this.view.setProps({editable: () => false})
+ OC.Notification.showTemporary('Changes could not be sent yet')
+ this.view.setProps({ editable: () => false })
+ this.onErrorHandlers.forEach((handler) => handler(ERROR_TYPE.PUSH_FAILURE, {}))
// TODO recover
}
this.retryTime = newRetry
@@ -242,7 +242,7 @@ class EditorSync {
if (!sendable) {
this.dirty = false
this.triggerStateChange()
- return;
+ return
}
this.dirty = true
this.triggerStateChange()
@@ -250,7 +250,7 @@ class EditorSync {
setTimeout(() => {
this.sendSteps()
}, 500)
- return;
+ return
}
this.lock = true
const authority = this
@@ -274,18 +274,14 @@ class EditorSync {
this.carefulRetryReset()
this.lock = false
this.fetchSteps()
- }).catch((e) =>
-
-
- {
- console.log('failed to apply steps due to collission, retrying');
+ }).catch((e) => {
+ console.error('failed to apply steps due to collission, retrying')
// TODO: increase retry counter to check against MAX_REBASE_RETRY
this.lock = false
// TODO: remove if we have state machine
this.fetchSteps()
this.carefulRetry(() => {
- console.log('carefulRetry sendSteps')
this.sendSteps()
})
})
diff --git a/src/components/Editor.vue b/src/components/Editor.vue
index e63a508a0..16e24a55b 100644
--- a/src/components/Editor.vue
+++ b/src/components/Editor.vue
@@ -21,254 +21,265 @@
-->
<template>
- <div id="editor-container" v-if="session && active">
+ <div v-if="currentSession && active" id="editor-container">
<div id="editor-session-list">
- <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 v-tooltip="lastSavedStatusTooltip" class="save-status" :class="lastSavedStatusClass">
+ {{ lastSavedStatus }}
+ </div>
+ <avatar v-for="session in activeSessions" :key="session.id" :user="session.userId"
+ :display-name="session.displayName" :style="sessionStyle(session)" />
</div>
<div>
- <p class="msg icon-error" v-if="hasSyncCollission">
+ <p v-if="hasSyncCollission" class="msg icon-error">
{{ t('text', 'The document has been changed outside of the editor. The changes cannot be applied.') }}
</p>
</div>
<div id="editor-wrapper" :class="{'has-conflicts': hasSyncCollission, 'icon-loading': !initialLoading}">
- <div id="editor" ref="editor" v-once></div>
+ <div v-once id="editor" ref="editor" />
<read-only-editor v-if="hasSyncCollission" :content="syncError.data.outsideChange" />
</div>
- <div id="resolve-conflicts" v-if="hasSyncCollission">
- <button @click="resolveUseThisVersion">Use your version</button>
- <button @click="resolveUseServerVersion">Use the server version</button>
+ <div v-if="hasSyncCollission" id="resolve-conflicts">
+ <button @click="resolveUseThisVersion">
+ Use your version
+ </button>
+ <button @click="resolveUseServerVersion">
+ Use the server version
+ </button>
</div>
</div>
</template>c
<script>
- const COLLABORATOR_IDLE_TIME = 5;
- const COLLABORATOR_DISCONNECT_TIME = 20;
- const EDITOR_PUSH_DEBOUNCE = 200;
+import axios from 'nextcloud-axios'
+import debounce from 'lodash/debounce'
- import axios from 'nextcloud-axios'
- import debounce from 'lodash/debounce'
+import { EditorSync, ERROR_TYPE } from './../EditorSync'
+import { collab, getVersion } from 'prosemirror-collab'
+import { EditorState } from 'prosemirror-state'
+import { EditorView } from 'prosemirror-view'
+import { exampleSetup } from 'prosemirror-example-setup'
+import { schema, defaultMarkdownParser } from 'prosemirror-markdown'
+import { keymap } from 'prosemirror-keymap'
- import { EditorSync, ERROR_TYPE } from './../EditorSync'
- import {collab, receiveTransaction, sendableSteps, getVersion} from 'prosemirror-collab'
- import {EditorState} from 'prosemirror-state'
- import {EditorView} from 'prosemirror-view'
- import {exampleSetup} from 'prosemirror-example-setup'
- import {schema, defaultMarkdownParser, defaultMarkdownSerializer} from 'prosemirror-markdown'
- import {keymap} from 'prosemirror-keymap'
+import Avatar from 'nextcloud-vue/dist/Components/Avatar'
+import ReadOnlyEditor from './ReadOnlyEditor'
+import Tooltip from 'nextcloud-vue/dist/Directives/Tooltip'
- import Avatar from 'nextcloud-vue/dist/Components/Avatar'
- import ReadOnlyEditor from './ReadOnlyEditor'
- import Tooltip from 'nextcloud-vue/dist/Directives/Tooltip'
+const COLLABORATOR_IDLE_TIME = 5
+const COLLABORATOR_DISCONNECT_TIME = 20
+const EDITOR_PUSH_DEBOUNCE = 200
- export default {
- name: 'Editor',
- components: {
- Avatar,
- ReadOnlyEditor
+export default {
+ name: 'Editor',
+ components: {
+ Avatar,
+ ReadOnlyEditor
+ },
+ directives: {
+ Tooltip
+ },
+ props: {
+ relativePath: {
+ type: String,
+ default: null
},
- directives: {
- Tooltip
+ fileId: {
+ type: String,
+ default: null
},
- beforeMount() {
- if (this.active || this.shareToken) {
- this.initSession()
- }
- setInterval(() => { this.updateLastSavedStatus() }, 2000)
+ active: {
+ type: Boolean,
+ default: false
+ },
+ shareToken: {
+ type: String,
+ default: null
+ }
+ },
+ data() {
+ return {
+ editor: null,
+ /** @type EditorSync */
+ authority: null,
+ document: null,
+ currentSession: null,
+ sessions: [],
+ name: 'Guest',
+ dirty: false,
+ initialLoading: false,
+ lastSavedString: '',
+ syncError: null
+ }
+ },
+ computed: {
+ activeSessions() {
+ // TODO: filter out duplicate user ids
+ return this.sessions.filter((session) => session.lastContact > Date.now() / 1000 - COLLABORATOR_DISCONNECT_TIME)
},
- props: {
- relativePath: {
- default: null
- },
- fileId: {
- default: null
- },
- active: {
- default: false
- },
- shareToken: {
- default: null
+ sessionStyle() {
+ return (session) => {
+ return {
+ 'opacity': session.lastContact > Date.now() / 1000 - COLLABORATOR_IDLE_TIME ? 1 : 0.5,
+ 'border-color': session.color
+ }
}
},
- data() {
- return {
- editor: null,
- /** @type EditorSync */
- authority: null,
- document: null,
- session: null,
- sessions: [],
- name: 'Guest',
- dirty: false,
- initialLoading: false,
- lastSavedString: '',
- syncError: null
+ lastSavedStatus() {
+ return (this.hasUnsavedChanges || this.hasUnpushedChanges ? '*' : '') + this.lastSavedString
+ },
+ lastSavedStatusClass() {
+ return this.syncError && this.lastSavedString !== '' ? 'error' : ''
+ },
+ lastSavedStatusTooltip() {
+ let message = t('text', 'Last save {lastSave}', { lastSave: this.lastSavedString })
+ if (this.hasSyncCollission) {
+ message = t('text', 'The document has been changed outside of the editor. The changes cannot be applied.')
+ }
+ if (this.hasUnpushedChanges) {
+ message += ' - ' + t('text', 'Unpushed changes')
+ }
+ if (this.hasUnsavedChanges) {
+ message += ' - ' + t('text', 'Unsaved changes')
}
+ return { content: message, placement: 'bottom' }
},
- computed: {
- activeSessions() {
- // TODO: filter out duplicate user ids
- return this.sessions.filter((session) => session.lastContact > Date.now()/1000-COLLABORATOR_DISCONNECT_TIME)
- },
- sessionStyle() {
- return (session) => {
- return {
- 'opacity': session.lastContact > Date.now()/1000-COLLABORATOR_IDLE_TIME ? 1 : 0.5,
- 'border-color': session.color
- }
- }
- },
- lastSavedStatus() {
- return (this.hasUnsavedChanges || this.hasUnpushedChanges ? '*' : '') + this.lastSavedString
- },
- lastSavedStatusClass() {
- return this.syncError && this.lastSavedString !== '' ? 'error' : ''
- },
- lastSavedStatusTooltip() {
- let message = t('text', 'Last save {lastSave}', {lastSave: this.lastSavedString})
- if (this.hasSyncCollission) {
- message = t('text', 'The document has been changed outside of the editor. The changes cannot be applied.')
- }
- if (this.hasUnpushedChanges) {
- message += ' - ' + t('text', 'Unpushed changes')
- }
- if (this.hasUnsavedChanges) {
- message += ' - ' + t('text', 'Unsaved changes')
- }
- return { content: message, placement: 'bottom' }
- },
- hasSyncCollission() {
- return this.syncError && this.syncError.type === ERROR_TYPE.SAVE_COLLISSION
- },
- hasUnpushedChanges() {
- return this.dirty
- },
- hasUnsavedChanges() {
- return this.authority && this.document.lastSavedVersion !== getVersion(this.authority.view.state)
+ hasSyncCollission() {
+ return this.syncError && this.syncError.type === ERROR_TYPE.SAVE_COLLISSION
+ },
+ hasUnpushedChanges() {
+ return this.dirty
+ },
+ hasUnsavedChanges() {
+ return this.authority && this.document.lastSavedVersion !== getVersion(this.authority.view.state)
+ }
+ },
+ beforeMount() {
+ if (this.active || this.shareToken) {
+ this.initSession()
+ }
+ setInterval(() => { this.updateLastSavedStatus() }, 2000)
+ },
+ methods: {
+ updateLastSavedStatus() {
+ if (this.document) {
+ this.lastSavedString = window.moment(this.document.lastSavedVersionTime * 1000).fromNow()
}
},
- methods: {
- updateLastSavedStatus() {
- if (this.document) {
- this.lastSavedString = moment(this.document.lastSavedVersionTime * 1000).fromNow();
- }
- },
- initSession() {
- if (!this.relativePath && !this.shareToken) {
- console.error('No relative path given')
- this.$emit('error', 'No relative path given')
- return;
+ initSession() {
+ if (!this.relativePath && !this.shareToken) {
+ console.error('No relative path given')
+ this.$emit('error', 'No relative path given')
+ return
+ }
+ axios.get(OC.generateUrl('/apps/text/session/create'), {
+ // TODO: viewer should provide the file id so we can use it in all places (also for public pages)
+ params: {
+ file: this.relativePath,
+ shareToken: this.shareToken
}
- axios.get(OC.generateUrl('/apps/text/session/create'), {
- // TODO: viewer should provide the file id so we can use it in all places (also for public pages)
- params: {
- file: this.relativePath,
- shareToken: this.shareToken
+ }).then((response) => {
+ this.document = response.data.document
+ this.currentSession = response.data.session
+ axios.get(OC.generateUrl('/apps/text/session/fetch'),
+ {
+ params: {
+ documentId: this.document.id,
+ sessionId: this.currentSession.id,
+ token: this.currentSession.token
+ }
}
- }).then((response) => {
- this.document = response.data.document;
- this.session = response.data.session;
- axios.get(OC.generateUrl('/apps/text/session/fetch',),
- {
- params: {
- documentId: this.document.id,
- sessionId: this.session.id,
- token: this.session.token
- }
+ ).then((fileContent) => {
+ const { authority } = this.initEditor(this.$refs.editor, response.data, fileContent.data)
+ this.authority = authority
+ this.authority.onSync((data) => {
+ this.syncError = null
+ if (data.document) {
+ this.document = data.document
}
- ).then((fileContent) => {
- const {editor, authority} = this.initEditor(this.$refs.editor, response.data, fileContent.data);
- this.authority = authority
- this.authority.onSync((data) => {
- this.syncError = null
- if (data.document) {
- this.document = data.document
- }
- this.sessions = data.sessions
- })
- this.authority.onError((error, data) => {
- if (error === ERROR_TYPE.SAVE_COLLISSION && (!this.syncError || this.syncError.type !== ERROR_TYPE.SAVE_COLLISSION)) {
- this.syncError = {
- type: ERROR_TYPE.SAVE_COLLISSION,
- data: data
- }
- }
- })
- this.authority.onStateChange(() => {
- this.dirty = this.authority.dirty
- if (!this.initialLoading) {
- this.initialLoading = !this.authority.dirty && this.document.lastSavedVersion === getVersion(this.authority.view.state)
+ this.sessions = data.sessions
+ })
+ this.authority.onError((error, data) => {
+ if (error === ERROR_TYPE.SAVE_COLLISSION && (!this.syncError || this.syncError.type !== ERROR_TYPE.SAVE_COLLISSION)) {
+ this.syncError = {
+ type: ERROR_TYPE.SAVE_COLLISSION,
+ data: data
}
- })
+ }
+ })
+ this.authority.onStateChange(() => {
+ this.dirty = this.authority.dirty
+ if (!this.initialLoading) {
+ this.initialLoading = !this.authority.dirty && this.document.lastSavedVersion === getVersion(this.authority.view.state)
+ }
+ })
- this.$emit('update:loaded', true)
- });
- }).catch((error) => {
- console.error(error.response)
- this.$emit('error', error.response.status)
+ this.$emit('update:loaded', true)
})
+ }).catch((error) => {
+ console.error(error.response)
+ this.$emit('error', error.response.status)
+ })
- },
-
- resolveUseThisVersion() {
- this.authority.forceSave()
- this.authority.view.setProps({editable: () => true})
- },
+ },
- resolveUseServerVersion() {
- this.authority.view.destroy()
- this.initSession()
- },
+ resolveUseThisVersion() {
+ this.authority.forceSave()
+ this.authority.view.setProps({ editable: () => true })
+ },
- initEditor: (ref, data, initialDocument) => {
- const authority = new EditorSync(defaultMarkdownParser.parse(initialDocument), data)
+ resolveUseServerVersion() {
+ this.authority.view.destroy()
+ this.initSession()
+ },
- const sendStepsDebounce = () => authority.sendSteps()
- const sendStepsDebounced = debounce(sendStepsDebounce, EDITOR_PUSH_DEBOUNCE, { maxWait: 500 })
+ initEditor: (ref, data, initialDocument) => {
+ const authority = new EditorSync(defaultMarkdownParser.parse(initialDocument), data)
- const view = new EditorView(ref, {
- state: EditorState.create({
- doc: authority.doc,
- plugins: [
- keymap({
- 'Ctrl-s': () => {
- authority.manualSave()
- authority.fetchSteps()
- return true;
- }
- }),
- ...exampleSetup({schema}),
- collab({
- version: authority.steps.length,
- clientID: data.session.id
- })
- ]
- }),
- destroy() {
- this.view.destroy()
- authority.destroy()
- },
- dispatchTransaction: (transaction) => {
- const state = view.state.apply(transaction);
- view.updateState(state);
- sendStepsDebounced()
- },
+ const sendStepsDebounce = () => authority.sendSteps()
+ const sendStepsDebounced = debounce(sendStepsDebounce, EDITOR_PUSH_DEBOUNCE, { maxWait: 500 })
- })
- authority.view = view;
- authority.fetchSteps()
- return {
- view: view,
- authority: authority
+ const view = new EditorView(ref, {
+ state: EditorState.create({
+ doc: authority.doc,
+ plugins: [
+ keymap({
+ 'Ctrl-s': () => {
+ authority.manualSave()
+ authority.fetchSteps()
+ return true
+ }
+ }),
+ ...exampleSetup({ schema }),
+ collab({
+ version: authority.steps.length,
+ clientID: data.session.id
+ })
+ ]
+ }),
+ destroy() {
+ this.view.destroy()
+ authority.destroy()
+ },
+ dispatchTransaction: (transaction) => {
+ const state = view.state.apply(transaction)
+ view.updateState(state)
+ sendStepsDebounced()
}
- },
- onSync(syncState) {
- this.sessions = syncState.sessions
- this.document = syncState.document
+
+ })
+ authority.view = view
+ authority.fetchSteps()
+ return {
+ view: view,
+ authority: authority
}
+ },
+ onSync(syncState) {
+ this.sessions = syncState.sessions
+ this.document = syncState.document
}
}
+}
</script>
<style scoped lang="scss">
@@ -332,8 +343,6 @@
}
}
-
-
#editor-container #editor-wrapper.has-conflicts {
height: calc(100% - 50px);
diff --git a/src/components/ReadOnlyEditor.vue b/src/components/ReadOnlyEditor.vue
index 666a027e6..47883ace1 100644
--- a/src/components/ReadOnlyEditor.vue
+++ b/src/components/ReadOnlyEditor.vue
@@ -21,51 +21,51 @@
-->
<template>
- <div id="remote" ref="remote"></div>
+ <div id="remote" ref="remote" />
</template>
<script>
- import {EditorState} from 'prosemirror-state'
- import {EditorView} from 'prosemirror-view'
- import {exampleSetup} from 'prosemirror-example-setup'
- import {schema, defaultMarkdownParser} from 'prosemirror-markdown'
+import { EditorState } from 'prosemirror-state'
+import { EditorView } from 'prosemirror-view'
+import { exampleSetup } from 'prosemirror-example-setup'
+import { schema, defaultMarkdownParser } from 'prosemirror-markdown'
- export default {
- name: 'ReadOnlyEditor',
- props: {
- content: {
- type: String,
- required: true
- },
- },
- data: () => {
- return {
- remoteView: null
+export default {
+ name: 'ReadOnlyEditor',
+ props: {
+ content: {
+ type: String,
+ required: true
+ }
+ },
+ data: () => {
+ return {
+ remoteView: null
+ }
+ },
+ mounted() {
+ this.initRemoteView()
+ },
+ beforeDestroy() {
+ this.remoteView.destroy()
+ },
+ methods: {
+ initRemoteView() {
+ if (this.remoteView) {
+ return
}
- },
- mounted() {
- this.initRemoteView()
- },
- beforeDestroy() {
- this.remoteView.destroy()
- },
- methods: {
- initRemoteView() {
- if (this.remoteView) {
- return;
- }
- this.remoteView = new EditorView(this.$refs.remote, {
- state: EditorState.create({
- doc: defaultMarkdownParser.parse(this.content),
- plugins: [
- ...exampleSetup({schema})
- ]
- }),
+ this.remoteView = new EditorView(this.$refs.remote, {
+ state: EditorState.create({
+ doc: defaultMarkdownParser.parse(this.content),
+ plugins: [
+ ...exampleSetup({ schema })
+ ]
})
- this.remoteView.setProps({editable: () => false})
- },
+ })
+ this.remoteView.setProps({ editable: () => false })
}
}
+}
</script>
<style scoped>
diff --git a/src/files.js b/src/files.js
index c08e7629c..ee936bc6f 100644
--- a/src/files.js
+++ b/src/files.js
@@ -20,14 +20,16 @@
*
*/
-const newFileMenuPlugin = {
+import Editor from './components/Editor'
+import { documentReady } from './helpers'
- attach: function (menu) {
- var fileList = menu.fileList;
+const newFileMenuPlugin = {
+ attach: function(menu) {
+ var fileList = menu.fileList
// only attach to main file list, public view is not supported yet
if (fileList.id !== 'files') {
- return;
+ return
}
// register the new menu entry
@@ -37,23 +39,22 @@ const newFileMenuPlugin = {
templateName: t('text', 'New text document.md'),
iconClass: 'icon-filetype-text',
fileType: 'file',
- actionHandler: function (name) {
- fileList.createFile(name).then(function (status, data) {
- let fileInfoModel = new OCA.Files.FileInfoModel(data);
- OCA.Files.fileActions.triggerAction('view', fileInfoModel, fileList);
- });
+ actionHandler: function(name) {
+ fileList.createFile(name).then(function(status, data) {
+ let fileInfoModel = new OCA.Files.FileInfoModel(data)
+ OCA.Files.fileActions.triggerAction('view', fileInfoModel, fileList)
+ })
}
- });
+ })
}
-};
-OC.Plugins.register('OCA.Files.NewFileMenu', newFileMenuPlugin);
+}
-import Editor from './components/Editor'
-$(document).ready(function() {
+OC.Plugins.register('OCA.Files.NewFileMenu', newFileMenuPlugin)
+documentReady(() => {
OCA.Viewer.registerHandler({
id: 'text',
mimes: ['text/markdown'],
component: Editor,
group: null
- });
-});
+ })
+})
diff --git a/src/helpers.js b/src/helpers.js
new file mode 100644
index 000000000..f70360bb3
--- /dev/null
+++ b/src/helpers.js
@@ -0,0 +1,38 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * Callback that should be executed after the document is ready
+ * @param callback
+ */
+const documentReady = function(callback) {
+ const fn = () => setTimeout(callback, 0)
+ if (document.attachEvent ? document.readyState === 'complete' : document.readyState !== 'loading') {
+ fn()
+ } else {
+ document.addEventListener('DOMContentLoaded', fn)
+ }
+}
+
+export {
+ documentReady
+}
diff --git a/src/main.js b/src/main.js
index 4e21fd43c..f663d615c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,17 +1,18 @@
import Vue from 'vue'
-__webpack_nonce__ = btoa(OC.requestToken); // eslint-disable-line no-native-reassign
+import Editor from './components/Editor'
+
+__webpack_nonce__ = btoa(OC.requestToken) // eslint-disable-line
Vue.prototype.t = t
Vue.prototype.OCA = OCA
-import Editor from './components/Editor'
new Vue({
render: h => h(Editor, {
props: {
relativePath: '/welcome.md',
active: true
}
- }),
+ })
}).$mount('#maineditor')