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:
authorVinicius Reis <vinicius.reis@nextcloud.com>2022-05-31 21:29:39 +0300
committerJonas <jonas@freesources.org>2022-07-06 14:45:54 +0300
commit6313ca0d3956608f573241b2f721ab33beba258c (patch)
treeba6c207c1a02fcde6d9add04b3cb5b4767315af6 /src/components
parent9d54f32c465cf78ff901f38d51cffd41f7665373 (diff)
⚡️ (#2462): remove memory leak from unnecessary listeners
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
Diffstat (limited to 'src/components')
-rw-r--r--src/components/EditorWrapper.vue388
1 files changed, 217 insertions, 171 deletions
diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue
index cc41ce076..1a3a9b532 100644
--- a/src/components/EditorWrapper.vue
+++ b/src/components/EditorWrapper.vue
@@ -323,30 +323,20 @@ export default {
this.close()
},
methods: {
- async close() {
- clearInterval(this.saveStatusPolling)
- if (this.currentSession && this.$syncService) {
- try {
- await this.$syncService.close()
- this.currentSession = null
- this.$syncService = null
- } catch (e) {
- // Ignore issues closing the session since those might happen due to network issues
- }
- }
- return true
- },
updateLastSavedStatus() {
if (this.document) {
this.lastSavedString = moment(this.document.lastSavedVersionTime * 1000).fromNow()
}
},
+
initSession() {
if (!this.hasDocumentParameters) {
this.$parent.$emit('error', 'No valid file provided')
return
}
+
const guestName = localStorage.getItem('nick') ? localStorage.getItem('nick') : getRandomGuestName()
+
this.$syncService = new SyncService({
shareToken: this.shareToken,
filePath: this.relativePath,
@@ -360,156 +350,9 @@ export default {
},
})
- .on('opened', ({ document, session }) => {
- this.currentSession = session
- this.document = document
- this.readOnly = document.readOnly
- this.lock = this.$syncService.lock
- localStorage.setItem('nick', this.currentSession.guestName)
- this.$store.dispatch('setCurrentSession', this.currentSession)
- })
- .on('change', ({ document, sessions }) => {
- if (this.document.baseVersionEtag !== '' && document.baseVersionEtag !== this.document.baseVersionEtag) {
- this.resolveUseServerVersion()
- return
- }
- this.updateSessions.bind(this)(sessions)
- this.document = document
- this.syncError = null
- this.$editor.setOptions({ editable: !this.readOnly })
- })
- .on('loaded', ({ documentSource }) => {
- this.hasConnectionIssue = false
- const content = this.isRichEditor
- ? markdownit.render(documentSource)
- : '<pre>' + escapeHtml(documentSource) + '</pre>'
- const language = extensionHighlight[this.fileExtension] || this.fileExtension
- loadSyntaxHighlight(language).then(() => {
- this.$editor = createEditor({
- content,
- onCreate: ({ editor }) => {
- this.$syncService.state = editor.state
- this.$syncService.startSync()
- },
- onUpdate: ({ editor }) => {
- this.$syncService.state = editor.state
- },
- extensions: [
- Collaboration.configure({
- // the initial version we start with
- // version is an integer which is incremented with every change
- version: this.document.initialVersion,
- clientID: this.currentSession.id,
- // debounce changes so we can save some bandwidth
- debounce: EDITOR_PUSH_DEBOUNCE,
- onSendable: ({ sendable }) => {
- if (this.$syncService) {
- this.$syncService.sendSteps()
- }
- },
- update: ({ steps, version, editor }) => {
- const { state, view, schema } = editor
- if (getVersion(state) > version) {
- return
- }
- const tr = receiveTransaction(
- state,
- steps.map(item => Step.fromJSON(schema, item.step)),
- steps.map(item => item.clientID),
- )
- tr.setMeta('clientID', steps.map(item => item.clientID))
- view.dispatch(tr)
- },
- }),
- Keymap.configure({
- 'Mod-s': () => {
- this.$syncService.save()
- return true
- },
- }),
- UserColor.configure({
- clientID: this.currentSession.id,
- color: (clientID) => {
- const session = this.sessions.find(item => '' + item.id === '' + clientID)
- return session?.color
- },
- name: (clientID) => {
- const session = this.sessions.find(item => '' + item.id === '' + clientID)
- return session?.userId ? session.userId : session?.guestName
- },
- }),
- ],
- enableRichEditing: this.isRichEditor,
- currentDirectory: this.currentDirectory,
- })
- this.$editor.on('focus', () => {
- this.$emit('focus')
- })
- this.$editor.on('blur', () => {
- this.$emit('blur')
- })
- this.$syncService.state = this.$editor.state
- })
- })
- .on('sync', ({ steps, document }) => {
- this.hasConnectionIssue = false
- try {
- const collaboration = this.$editor.extensionManager.extensions.find(e => e.name === 'collaboration')
- collaboration.options.update({
- version: document.currentVersion,
- steps,
- editor: this.$editor,
- })
- this.$syncService.state = this.$editor.state
- this.updateLastSavedStatus()
- } catch (e) {
- console.error('Failed to update steps in collaboration plugin', e)
- // TODO: we should recreate the editing session when this happens
- }
- this.document = document
- })
- .on('error', (error, data) => {
- this.$editor.setOptions({ editable: false })
- if (error === ERROR_TYPE.SAVE_COLLISSION && (!this.syncError || this.syncError.type !== ERROR_TYPE.SAVE_COLLISSION)) {
- this.contentLoaded = true
- this.syncError = {
- type: error,
- data,
- }
- }
- if (error === ERROR_TYPE.CONNECTION_FAILED && !this.hasConnectionIssue) {
- this.hasConnectionIssue = true
- // FIXME: ideally we just try to reconnect in the service, so we don't loose steps
- OC.Notification.showTemporary('Connection failed, reconnecting')
- if (data.retry !== false) {
- setTimeout(this.reconnect.bind(this), 5000)
- }
- }
- if (error === ERROR_TYPE.SOURCE_NOT_FOUND) {
- this.hasConnectionIssue = true
- }
- this.$emit('ready')
- })
- .on('stateChange', (state) => {
- if (state.initialLoading && !this.contentLoaded) {
- this.contentLoaded = true
- if (this.autofocus && !this.readOnly) {
- this.$editor.commands.focus()
- }
- this.$emit('ready')
- this.$parent.$emit('ready', true)
- }
- if (Object.prototype.hasOwnProperty.call(state, 'dirty')) {
- this.dirty = state.dirty
- }
- })
- .on('idle', () => {
- this.$syncService.close()
- this.idle = true
- this.readOnly = true
- this.$editor.setOptions({ editable: !this.readOnly })
- })
+ this.listenSyncServiceEvents()
+
this.$syncService.open({
fileId: this.fileId,
filePath: this.relativePath,
@@ -520,6 +363,28 @@ export default {
this.forceRecreate = false
},
+ listenSyncServiceEvents() {
+ this.$syncService
+ .on('opened', this.onOpened)
+ .on('change', this.onChange)
+ .on('loaded', this.onLoaded)
+ .on('sync', this.onSync)
+ .on('error', this.onError)
+ .on('stateChange', this.onStateChange)
+ .on('idle', this.onIdle)
+ },
+
+ unlistenSyncServiceEvents() {
+ this.$syncService
+ .off('opened', this.onOpened)
+ .off('change', this.onChange)
+ .off('loaded', this.onLoaded)
+ .off('sync', this.onSync)
+ .off('error', this.onError)
+ .off('stateChange', this.onStateChange)
+ .off('idle', this.onIdle)
+ },
+
resolveUseThisVersion() {
this.$syncService.forceSave()
this.$editor.setOptions({ editable: !this.readOnly })
@@ -533,19 +398,25 @@ export default {
reconnect() {
this.contentLoaded = false
this.hasConnectionIssue = false
- if (this.$syncService) {
- this.$syncService.close().then(() => {
- this.$syncService = null
- this.$editor.destroy()
- this.initSession()
- }).catch((e) => {
- // Ignore issues closing the session since those might happen due to network issues
- })
- } else {
+
+ const connect = () => {
+ this.unlistenSyncServiceEvents()
this.$syncService = null
this.$editor.destroy()
this.initSession()
}
+
+ if (this.$syncService) {
+ this.$syncService
+ .close()
+ .then(connect)
+ .catch((e) => {
+ // Ignore issues closing the session since those might happen due to network issues
+ })
+ } else {
+ connect()
+ }
+
this.idle = false
},
@@ -649,6 +520,181 @@ export default {
this.$editor.chain().setImage({ src, alt }).focus().run()
}
},
+
+ onOpened({ document, session }) {
+ this.currentSession = session
+ this.document = document
+ this.readOnly = document.readOnly
+ this.lock = this.$syncService.lock
+ localStorage.setItem('nick', this.currentSession.guestName)
+ this.$store.dispatch('setCurrentSession', this.currentSession)
+ },
+
+ onLoaded({ documentSource }) {
+ this.hasConnectionIssue = false
+ const content = this.isRichEditor
+ ? markdownit.render(documentSource)
+ : '<pre>' + escapeHtml(documentSource) + '</pre>'
+ const language = extensionHighlight[this.fileExtension] || this.fileExtension
+
+ loadSyntaxHighlight(language)
+ .then(() => {
+ this.$editor = createEditor({
+ content,
+ onCreate: ({ editor }) => {
+ this.$syncService.state = editor.state
+ this.$syncService.startSync()
+ },
+ onUpdate: ({ editor }) => {
+ this.$syncService.state = editor.state
+ },
+ extensions: [
+ Collaboration.configure({
+ // the initial version we start with
+ // version is an integer which is incremented with every change
+ version: this.document.initialVersion,
+ clientID: this.currentSession.id,
+ // debounce changes so we can save some bandwidth
+ debounce: EDITOR_PUSH_DEBOUNCE,
+ onSendable: ({ sendable }) => {
+ if (this.$syncService) {
+ this.$syncService.sendSteps()
+ }
+ },
+ update: ({ steps, version, editor }) => {
+ const { state, view, schema } = editor
+ if (getVersion(state) > version) {
+ return
+ }
+ const tr = receiveTransaction(
+ state,
+ steps.map(item => Step.fromJSON(schema, item.step)),
+ steps.map(item => item.clientID),
+ )
+ tr.setMeta('clientID', steps.map(item => item.clientID))
+ view.dispatch(tr)
+ },
+ }),
+ Keymap.configure({
+ 'Mod-s': () => {
+ this.$syncService.save()
+ return true
+ },
+ }),
+ UserColor.configure({
+ clientID: this.currentSession.id,
+ color: (clientID) => {
+ const session = this.sessions.find(item => '' + item.id === '' + clientID)
+ return session?.color
+ },
+ name: (clientID) => {
+ const session = this.sessions.find(item => '' + item.id === '' + clientID)
+ return session?.userId ? session.userId : session?.guestName
+ },
+ }),
+ ],
+ enableRichEditing: this.isRichEditor,
+ currentDirectory: this.currentDirectory,
+ })
+ this.$editor.on('focus', () => {
+ this.$emit('focus')
+ })
+ this.$editor.on('blur', () => {
+ this.$emit('blur')
+ })
+ this.$syncService.state = this.$editor.state
+ })
+
+ },
+
+ onChange({ document, sessions }) {
+ if (this.document.baseVersionEtag !== '' && document.baseVersionEtag !== this.document.baseVersionEtag) {
+ this.resolveUseServerVersion()
+ return
+ }
+ this.updateSessions.bind(this)(sessions)
+ this.document = document
+
+ this.syncError = null
+ this.$editor.setOptions({ editable: !this.readOnly })
+ },
+
+ onSync({ steps, document }) {
+ this.hasConnectionIssue = false
+ try {
+ const collaboration = this.$editor.extensionManager.extensions.find(e => e.name === 'collaboration')
+ collaboration.options.update({
+ version: document.currentVersion,
+ steps,
+ editor: this.$editor,
+ })
+ this.$syncService.state = this.$editor.state
+ this.updateLastSavedStatus()
+ } catch (e) {
+ console.error('Failed to update steps in collaboration plugin', e)
+ // TODO: we should recreate the editing session when this happens
+ }
+ this.document = document
+ },
+
+ onError({ type, data }) {
+ this.$editor.setOptions({ editable: false })
+ if (type === ERROR_TYPE.SAVE_COLLISSION && (!this.syncError || this.syncError.type !== ERROR_TYPE.SAVE_COLLISSION)) {
+ this.contentLoaded = true
+ this.syncError = {
+ type,
+ data,
+ }
+ }
+ if (type === ERROR_TYPE.CONNECTION_FAILED && !this.hasConnectionIssue) {
+ this.hasConnectionIssue = true
+ // FIXME: ideally we just try to reconnect in the service, so we don't loose steps
+ OC.Notification.showTemporary('Connection failed, reconnecting')
+ if (data.retry !== false) {
+ setTimeout(this.reconnect.bind(this), 5000)
+ }
+ }
+ if (type === ERROR_TYPE.SOURCE_NOT_FOUND) {
+ this.hasConnectionIssue = true
+ }
+ this.$emit('ready')
+ },
+
+ onStateChange(state) {
+ if (state.initialLoading && !this.contentLoaded) {
+ this.contentLoaded = true
+ if (this.autofocus && !this.readOnly) {
+ this.$editor.commands.focus()
+ }
+ this.$emit('ready')
+ this.$parent.$emit('ready', true)
+ }
+ if (Object.prototype.hasOwnProperty.call(state, 'dirty')) {
+ this.dirty = state.dirty
+ }
+ },
+
+ onIdle() {
+ this.$syncService.close()
+ this.idle = true
+ this.readOnly = true
+ this.$editor.setOptions({ editable: !this.readOnly })
+ },
+
+ async close() {
+ clearInterval(this.saveStatusPolling)
+ if (this.currentSession && this.$syncService) {
+ try {
+ await this.$syncService.close()
+ this.unlistenSyncServiceEvents()
+ this.currentSession = null
+ this.$syncService = null
+ } catch (e) {
+ // Ignore issues closing the session since those might happen due to network issues
+ }
+ }
+ return true
+ },
},
}
</script>