diff options
author | Julius Härtl <jus@bitgrid.net> | 2019-05-14 10:33:45 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2019-05-14 10:33:45 +0300 |
commit | fd1e342338fbf6007ccbcd4fc2a34cdf8c8b2140 (patch) | |
tree | c297f63f941ac8b3feca74f56fd1bc941c9f38c4 | |
parent | f160a6af7147a2c7446edc3ce49edf54d02b874f (diff) |
Add support for public shared files to the frontend
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r-- | css/style.scss | 4 | ||||
-rw-r--r-- | lib/Service/DocumentService.php | 1 | ||||
-rw-r--r-- | package.json | 54 | ||||
-rw-r--r-- | src/EditorSync.js | 33 | ||||
-rw-r--r-- | src/components/Editor.vue | 40 | ||||
-rw-r--r-- | src/public.js | 37 | ||||
-rw-r--r-- | webpack.common.js | 1 |
7 files changed, 118 insertions, 52 deletions
diff --git a/css/style.scss b/css/style.scss index df5b00c09..63ae1bf00 100644 --- a/css/style.scss +++ b/css/style.scss @@ -15,6 +15,10 @@ display: flex; height: 100%; flex-direction: column; + + * { + box-sizing: border-box; + } } .ProseMirror { diff --git a/lib/Service/DocumentService.php b/lib/Service/DocumentService.php index 953bbd005..2ccc588ae 100644 --- a/lib/Service/DocumentService.php +++ b/lib/Service/DocumentService.php @@ -137,6 +137,7 @@ class DocumentService { } // TODO: Only do this when no sessions active, otherise we need to resolve the conflict differently + // TODO: Add parameter so that we can force this, else just opening the document will cause a rebuild $lastMTime = $document->getLastSavedVersionTime(); if ($file->getMTime() > $lastMTime && $lastMTime > 0) { $this->resetDocument($document->getId()); diff --git a/package.json b/package.json index 1b3926f86..bdc15b03f 100644 --- a/package.json +++ b/package.json @@ -30,32 +30,32 @@ "linkifyjs": "^2.1.8", "lodash": "^4.17.11", "nextcloud-axios": "^0.1.3", - "nextcloud-vue": "^0.9.0", - "prosemirror": "^0.11.1", + "nextcloud-vue": "^0.9.7", "prosemirror-collab": "^1.1.1", "prosemirror-example-setup": "^1.0.1", "prosemirror-history": "^1.0.4", + "prosemirror-keymap": "^1.0.1", "prosemirror-markdown": "^1.3.0", "prosemirror-menu": "^1.0.5", "prosemirror-model": "^1.7.0", - "prosemirror-schema-basic": "^1.0.0", - "prosemirror-schema-list": "^1.0.2", - "prosemirror-state": "^1.2.2", - "prosemirror-view": "^1.8.3", - "tributejs": "^3.6.0", + "prosemirror-schema-basic": "^1.0.1", + "prosemirror-schema-list": "^1.0.3", + "prosemirror-state": "^1.2.3", + "prosemirror-view": "^1.9.4", + "tributejs": "^3.7.1", "twemoji": "^11.3.0", "uuid": "^3.3.2", - "v-tooltip": "^2.0.0-rc.33", - "vue": "^2.6.8", + "v-tooltip": "^2.0.2", + "vue": "^2.6.10", "vue-click-outside": "^1.0.7", "vue-contenteditable-directive": "^1.2.0", "vue-emoji-picker": "^1.0.1", - "vue-infinite-loading": "^2.4.3", - "vue-router": "^3.0.2", + "vue-infinite-loading": "^2.4.4", + "vue-router": "^3.0.6", "vue-tribute": "^1.0.2", "vue-twemoji": "^1.0.1", - "vuetrend": "^0.3.2", - "vuex": "^3.1.0", + "vuetrend": "^0.3.4", + "vuex": "^3.1.1", "vuex-router-sync": "^5.0.0" }, "browserslist": [ @@ -66,40 +66,40 @@ "node": ">=10.0.0" }, "devDependencies": { - "@babel/core": "^7.3.4", + "@babel/core": "^7.4.4", "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/preset-env": "^7.3.4", + "@babel/preset-env": "^7.4.4", "@vue/test-utils": "^1.0.0-beta.29", "acorn": "^6.1.1", "babel-eslint": "^10.0.1", - "babel-jest": "^24.3.1", - "babel-loader": "^8.0.5", - "css-loader": "^2.1.0", - "eslint": "^5.15.1", + "babel-jest": "^24.8.0", + "babel-loader": "^8.0.6", + "css-loader": "^2.1.1", + "eslint": "^5.16.0", "eslint-config-standard": "^12.0.0", "eslint-friendly-formatter": "^4.0.1", "eslint-loader": "^2.1.1", - "eslint-plugin-import": "^2.16.0", + "eslint-plugin-import": "^2.17.2", "eslint-plugin-node": "^8.0.1", - "eslint-plugin-promise": "^4.0.1", + "eslint-plugin-promise": "^4.1.1", "eslint-plugin-standard": "^4.0.0", "eslint-plugin-vue": "^5.2.2", "file-loader": "^3.0.1", - "jest": "^24.0.0", + "jest": "^24.8.0", "jest-serializer-vue": "^2.0.2", - "node-sass": "^4.11.0", + "node-sass": "^4.12.0", "prettier-eslint": "^8.8.2", "raw-loader": "^1.0.0", "sass-loader": "^7.1.0", "stylelint": "^8.4.0", - "stylelint-config-recommended-scss": "^3.2.0", + "stylelint-config-recommended-scss": "^3.3.0", "stylelint-webpack-plugin": "^0.10.5", "vue-jest": "^3.0.4", "vue-loader": "^15.7.0", "vue-style-loader": "^4.1.1", - "vue-template-compiler": "^2.6.8", - "webpack": "^4.29.6", - "webpack-cli": "^3.2.3", + "vue-template-compiler": "^2.6.10", + "webpack": "^4.31.0", + "webpack-cli": "^3.3.2", "webpack-merge": "^4.2.1" }, "jest": { diff --git a/src/EditorSync.js b/src/EditorSync.js index 663f7dc04..67f8646d6 100644 --- a/src/EditorSync.js +++ b/src/EditorSync.js @@ -64,12 +64,18 @@ const ERROR_TYPE = { PUSH_FAILURE: 1 } -const URL_SYNC = OC.generateUrl('/apps/text/session/sync') -const URL_PUSH = OC.generateUrl('/apps/text/session/push') +const baseUrl = OC.generateUrl('/apps/text') + +const endpointUrl = (endpoint, isPublic = false) => { + if (isPublic) { + return `${baseUrl}/public/${endpoint}` + } + return `${baseUrl}/${endpoint}` +}; class EditorSync { - constructor(doc, data) { + constructor(doc, data, shareToken) { this.view = null this.doc = doc this.session = data.session @@ -80,6 +86,7 @@ class EditorSync { this.retryTime = MIN_PUSH_RETRY this.dirty = false this.fetchInverval = FETCH_INTERVAL + this.shareToken = shareToken this.onSyncHandlers = [] this.onErrorHandlers = [] @@ -90,6 +97,10 @@ class EditorSync { this.fetcher = setInterval(() => this.fetchSteps(), this.fetchInverval) } + isPublic() { + return !!this.shareToken + } + destroy() { clearInterval(this.fetcher) } @@ -136,14 +147,15 @@ class EditorSync { ) { autosaveContent = this.content() } - axios.post(URL_SYNC, { + axios.post(endpointUrl('session/sync', this.isPublic()), { documentId: this.document.id, sessionId: this.session.id, - token: this.session.token, + sessionToken: this.session.token, version: authority.steps.length, autosaveContent, force: !!this._forcedSave, - manualSave: !!this._manualSave + manualSave: !!this._manualSave, + token: this.shareToken }).then((response) => { if (this.document.lastSavedVersion < response.data.document.lastSavedVersion) { console.debug('Saved document', response.data.document) @@ -255,12 +267,13 @@ class EditorSync { this.lock = true const authority = this let steps = sendable.steps - axios.post(URL_PUSH, { + axios.post(endpointUrl('session/push', this.isPublic()), { documentId: this.document.id, sessionId: this.session.id, - token: this.session.token, + sessionToken: this.session.token, steps: steps.map(s => s.toJSON()) || [], - version: getVersion(authority.view.state) + version: getVersion(authority.view.state), + token: this.shareToken }).then((response) => { // sucessfully applied steps on the server steps.forEach(step => { @@ -289,4 +302,4 @@ class EditorSync { } -export { EditorSync, ERROR_TYPE } +export { EditorSync, ERROR_TYPE, endpointUrl } diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 16e24a55b..2b55a9795 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -47,13 +47,13 @@ </button> </div> </div> -</template>c +</template> <script> import axios from 'nextcloud-axios' import debounce from 'lodash/debounce' -import { EditorSync, ERROR_TYPE } from './../EditorSync' +import { EditorSync, ERROR_TYPE, endpointUrl } from './../EditorSync' import { collab, getVersion } from 'prosemirror-collab' import { EditorState } from 'prosemirror-state' import { EditorView } from 'prosemirror-view' @@ -104,7 +104,7 @@ export default { document: null, currentSession: null, sessions: [], - name: 'Guest', + name: null, dirty: false, initialLoading: false, lastSavedString: '', @@ -151,10 +151,15 @@ export default { }, hasUnsavedChanges() { return this.authority && this.document.lastSavedVersion !== getVersion(this.authority.view.state) + }, + backendUrl() { + return (endpoint) => { + return endpointUrl(endpoint, !!this.shareToken) + } } }, - beforeMount() { - if (this.active || this.shareToken) { + mounted() { + if (this.active && this.fileId || (this.shareToken)) { this.initSession() } setInterval(() => { this.updateLastSavedStatus() }, 2000) @@ -166,30 +171,31 @@ export default { } }, initSession() { - if (!this.relativePath && !this.shareToken) { - console.error('No relative path given') + if ((!this.relativePath && !this.fileId) && !this.shareToken ) { + console.error('No file 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) + axios.get(this.backendUrl('session/create'), { params: { + fileId: this.fileId, file: this.relativePath, - shareToken: this.shareToken + token: this.shareToken } }).then((response) => { this.document = response.data.document this.currentSession = response.data.session - axios.get(OC.generateUrl('/apps/text/session/fetch'), + axios.get(this.backendUrl('session/fetch'), { params: { documentId: this.document.id, sessionId: this.currentSession.id, - token: this.currentSession.token + sessionToken: this.currentSession.token, + token: this.shareToken } } ).then((fileContent) => { - const { authority } = this.initEditor(this.$refs.editor, response.data, fileContent.data) + const { authority } = this.initEditor(this.$refs.editor, response.data, fileContent.data, this.shareToken) this.authority = authority this.authority.onSync((data) => { this.syncError = null @@ -232,8 +238,8 @@ export default { this.initSession() }, - initEditor: (ref, data, initialDocument) => { - const authority = new EditorSync(defaultMarkdownParser.parse(initialDocument), data) + initEditor: (ref, data, initialDocument, shareToken) => { + const authority = new EditorSync(defaultMarkdownParser.parse(initialDocument), data, shareToken) const sendStepsDebounce = () => authority.sendSteps() const sendStepsDebounced = debounce(sendStepsDebounce, EDITOR_PUSH_DEBOUNCE, { maxWait: 500 }) @@ -373,4 +379,8 @@ export default { </style> <style lang="scss"> @import './../../css/style'; + + #files-public-content { + width: 100% !important; + } </style> diff --git a/src/public.js b/src/public.js new file mode 100644 index 000000000..abebe549a --- /dev/null +++ b/src/public.js @@ -0,0 +1,37 @@ +import Vue from 'vue' +import Editor from './components/Editor' +import { documentReady } from './helpers' + +__webpack_nonce__ = btoa(OC.requestToken) // eslint-disable-line + +Vue.prototype.t = t +Vue.prototype.OCA = OCA + +documentReady(() => { + const sharingToken = document.getElementById('sharingToken').value + const dir = document.getElementById('dir').value + const mimetype = document.getElementById('mimetype').value + + // Load regular viewer integration + if (dir !== '') { + // TODO: implement for public file lists + // import('./files') + } + + const container = document.createElement('div') + container.id = 'texteditor' + const body = document.getElementById('app-content'); + body.append(container); + + if (mimetype === 'text/markdown') { + const vm = new Vue({ + render: h => h(Editor, { + props: { + active: true, + shareToken: sharingToken + } + }) + }) + vm.$mount(document.getElementById('preview')) + } +}) diff --git a/webpack.common.js b/webpack.common.js index 4b1a9dff1..b4f92be4a 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -6,6 +6,7 @@ module.exports = { entry: { type: path.join(__dirname, 'src', 'main.js'), files: path.join(__dirname, 'src', 'files.js'), + public: path.join(__dirname, 'src', 'public.js'), }, output: { path: path.resolve(__dirname, './js'), |