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:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/EditorWrapper.provider.js13
-rw-r--r--src/components/EditorWrapper.vue114
-rw-r--r--src/components/GuestNameDialog.vue14
-rw-r--r--src/components/MenuBar.vue24
-rw-r--r--src/components/MenuBubble.vue19
5 files changed, 102 insertions, 82 deletions
diff --git a/src/components/EditorWrapper.provider.js b/src/components/EditorWrapper.provider.js
new file mode 100644
index 000000000..c19ebdc0d
--- /dev/null
+++ b/src/components/EditorWrapper.provider.js
@@ -0,0 +1,13 @@
+export const EDITOR = Symbol('tiptap:editor')
+export const SYNC_SERVICE = Symbol('sync:service')
+
+export const useEditorMixin = {
+ inject: {
+ $editor: { from: EDITOR, default: null },
+ },
+}
+export const useSyncServiceMixin = {
+ inject: {
+ $syncService: { from: SYNC_SERVICE, default: null },
+ },
+}
diff --git a/src/components/EditorWrapper.vue b/src/components/EditorWrapper.vue
index 782a91434..913528229 100644
--- a/src/components/EditorWrapper.vue
+++ b/src/components/EditorWrapper.vue
@@ -37,7 +37,7 @@
</p>
</div>
<div v-if="displayed" id="editor-wrapper" :class="{'has-conflicts': hasSyncCollission, 'icon-loading': !contentLoaded && !hasConnectionIssue, 'richEditor': isRichEditor, 'show-color-annotations': showAuthorAnnotations}">
- <div v-if="tiptap"
+ <div v-if="$editor"
id="editor"
:class="{ draggedOver }"
@image-paste="onPaste"
@@ -46,8 +46,6 @@
@image-drop="onEditorDrop">
<MenuBar v-if="renderMenus"
ref="menubar"
- :editor="tiptap"
- :sync-service="syncService"
:file-path="relativePath"
:file-id="fileId"
:is-rich-editor="isRichEditor"
@@ -63,7 +61,7 @@
{{ lastSavedStatus }}
</div>
<SessionList :sessions="filteredSessions">
- <GuestNameDialog v-if="isPublic && currentSession.guestName" :sync-service="syncService" />
+ <GuestNameDialog v-if="isPublic && currentSession.guestName" />
</SessionList>
</div>
<slot name="header" />
@@ -71,12 +69,11 @@
<div v-if="!menubarLoaded" class="menubar placeholder" />
<div ref="contentWrapper" class="content-wrapper">
<MenuBubble v-if="renderMenus"
- :editor="tiptap"
:content-wrapper="contentWrapper"
:file-path="relativePath" />
<EditorContent v-show="contentLoaded"
class="editor__content"
- :editor="tiptap" />
+ :editor="$editor" />
</div>
</div>
<ReadOnlyEditor v-if="hasSyncCollission"
@@ -95,6 +92,8 @@ import escapeHtml from 'escape-html'
import moment from '@nextcloud/moment'
import { showError } from '@nextcloud/dialogs'
+import { EDITOR, SYNC_SERVICE } from './EditorWrapper.provider'
+
import { SyncService, ERROR_TYPE, IDLE_TIMEOUT } from './../services/SyncService'
import { endpointUrl, getRandomGuestName } from './../helpers'
import { extensionHighlight } from '../helpers/mappings'
@@ -144,6 +143,27 @@ export default {
isMobile,
store,
],
+ provide() {
+ const val = {}
+
+ // providers aren't naturally reactive
+ // and $editor will start as null
+ // using getters we can always provide the
+ // actual $editor without being reactive
+ Object.defineProperty(val, EDITOR, {
+ get: () => {
+ return this.$editor
+ },
+ })
+
+ Object.defineProperty(val, SYNC_SERVICE, {
+ get: () => {
+ return this.$syncService
+ },
+ })
+
+ return val
+ },
props: {
initialSession: {
type: Object,
@@ -186,10 +206,6 @@ export default {
return {
IDLE_TIMEOUT,
- tiptap: null,
- /** @type {SyncService} */
- syncService: null,
-
document: null,
sessions: [],
currentSession: null,
@@ -297,6 +313,8 @@ export default {
this.$parent.$emit('update:loaded', true)
},
created() {
+ this.$editor = null
+ this.$syncService = null
this.saveStatusPolling = setInterval(() => {
this.updateLastSavedStatus()
}, 2000)
@@ -307,11 +325,11 @@ export default {
methods: {
async close() {
clearInterval(this.saveStatusPolling)
- if (this.currentSession && this.syncService) {
+ if (this.currentSession && this.$syncService) {
try {
- await this.syncService.close()
+ await this.$syncService.close()
this.currentSession = null
- this.syncService = null
+ this.$syncService = null
} catch (e) {
// Ignore issues closing the session since those might happen due to network issues
}
@@ -329,16 +347,16 @@ export default {
return
}
const guestName = localStorage.getItem('nick') ? localStorage.getItem('nick') : getRandomGuestName()
- this.syncService = new SyncService({
+ this.$syncService = new SyncService({
shareToken: this.shareToken,
filePath: this.relativePath,
guestName,
forceRecreate: this.forceRecreate,
serialize: (document) => {
if (this.isRichEditor) {
- return (createMarkdownSerializer(this.tiptap.schema)).serialize(document)
+ return (createMarkdownSerializer(this.$editor.schema)).serialize(document)
}
- return serializePlainText(this.tiptap)
+ return serializePlainText(this.$editor)
},
})
@@ -346,7 +364,7 @@ export default {
this.currentSession = session
this.document = document
this.readOnly = document.readOnly
- this.lock = this.syncService.lock
+ this.lock = this.$syncService.lock
localStorage.setItem('nick', this.currentSession.guestName)
this.$store.dispatch('setCurrentSession', this.currentSession)
})
@@ -359,7 +377,7 @@ export default {
this.document = document
this.syncError = null
- this.tiptap.setOptions({ editable: !this.readOnly })
+ this.$editor.setOptions({ editable: !this.readOnly })
})
.on('loaded', ({ documentSource }) => {
this.hasConnectionIssue = false
@@ -368,14 +386,14 @@ export default {
: '<pre>' + escapeHtml(documentSource) + '</pre>'
const language = extensionHighlight[this.fileExtension] || this.fileExtension
loadSyntaxHighlight(language).then(() => {
- this.tiptap = createEditor({
+ this.$editor = createEditor({
content,
onCreate: ({ editor }) => {
- this.syncService.state = editor.state
- this.syncService.startSync()
+ this.$syncService.state = editor.state
+ this.$syncService.startSync()
},
onUpdate: ({ editor }) => {
- this.syncService.state = editor.state
+ this.$syncService.state = editor.state
},
extensions: [
Collaboration.configure({
@@ -386,8 +404,8 @@ export default {
// debounce changes so we can save some bandwidth
debounce: EDITOR_PUSH_DEBOUNCE,
onSendable: ({ sendable }) => {
- if (this.syncService) {
- this.syncService.sendSteps()
+ if (this.$syncService) {
+ this.$syncService.sendSteps()
}
},
update: ({ steps, version, editor }) => {
@@ -406,7 +424,7 @@ export default {
}),
Keymap.configure({
'Mod-s': () => {
- this.syncService.save()
+ this.$syncService.save()
return true
},
}),
@@ -425,25 +443,25 @@ export default {
enableRichEditing: this.isRichEditor,
currentDirectory: this.currentDirectory,
})
- this.tiptap.on('focus', () => {
+ this.$editor.on('focus', () => {
this.$emit('focus')
})
- this.tiptap.on('blur', () => {
+ this.$editor.on('blur', () => {
this.$emit('blur')
})
- this.syncService.state = this.tiptap.state
+ this.$syncService.state = this.$editor.state
})
})
.on('sync', ({ steps, document }) => {
this.hasConnectionIssue = false
try {
- const collaboration = this.tiptap.extensionManager.extensions.find(e => e.name === 'collaboration')
+ const collaboration = this.$editor.extensionManager.extensions.find(e => e.name === 'collaboration')
collaboration.options.update({
version: document.currentVersion,
steps,
- editor: this.tiptap,
+ editor: this.$editor,
})
- this.syncService.state = this.tiptap.state
+ this.$syncService.state = this.$editor.state
this.updateLastSavedStatus()
} catch (e) {
console.error('Failed to update steps in collaboration plugin', e)
@@ -452,7 +470,7 @@ export default {
this.document = document
})
.on('error', (error, data) => {
- this.tiptap.setOptions({ editable: false })
+ 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 = {
@@ -477,7 +495,7 @@ export default {
if (state.initialLoading && !this.contentLoaded) {
this.contentLoaded = true
if (this.autofocus && !this.readOnly) {
- this.tiptap.commands.focus()
+ this.$editor.commands.focus()
}
this.$emit('ready')
this.$parent.$emit('ready', true)
@@ -487,12 +505,12 @@ export default {
}
})
.on('idle', () => {
- this.syncService.close()
+ this.$syncService.close()
this.idle = true
this.readOnly = true
- this.tiptap.setOptions({ editable: !this.readOnly })
+ this.$editor.setOptions({ editable: !this.readOnly })
})
- this.syncService.open({
+ this.$syncService.open({
fileId: this.fileId,
filePath: this.relativePath,
initialSession: this.initialSession,
@@ -503,8 +521,8 @@ export default {
},
resolveUseThisVersion() {
- this.syncService.forceSave()
- this.tiptap.setOptions({ editable: !this.readOnly })
+ this.$syncService.forceSave()
+ this.$editor.setOptions({ editable: !this.readOnly })
},
resolveUseServerVersion() {
@@ -515,17 +533,17 @@ export default {
reconnect() {
this.contentLoaded = false
this.hasConnectionIssue = false
- if (this.syncService) {
- this.syncService.close().then(() => {
- this.syncService = null
- this.tiptap.destroy()
+ 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 {
- this.syncService = null
- this.tiptap.destroy()
+ this.$syncService = null
+ this.$editor.destroy()
this.initSession()
}
this.idle = false
@@ -597,7 +615,7 @@ export default {
return
}
- return this.syncService.uploadImage(file).then((response) => {
+ return this.$syncService.uploadImage(file).then((response) => {
this.insertAttachmentImage(response.data?.name, response.data?.id, position)
}).catch((error) => {
console.error(error)
@@ -606,7 +624,7 @@ export default {
},
insertImagePath(imagePath) {
this.uploadingImages = true
- this.syncService.insertImageFile(imagePath).then((response) => {
+ this.$syncService.insertImageFile(imagePath).then((response) => {
this.insertAttachmentImage(response.data?.name, response.data?.id)
}).catch((error) => {
console.error(error)
@@ -621,9 +639,9 @@ export default {
// as it does not need to be unique and matching the real file name
const alt = name.replaceAll(/[[\]]/g, '')
if (position) {
- this.tiptap.chain().focus(position).setImage({ src, alt }).focus().run()
+ this.$editor.chain().focus(position).setImage({ src, alt }).focus().run()
} else {
- this.tiptap.chain().setImage({ src, alt }).focus().run()
+ this.$editor.chain().setImage({ src, alt }).focus().run()
}
},
},
diff --git a/src/components/GuestNameDialog.vue b/src/components/GuestNameDialog.vue
index 624c8d069..77b4cc03a 100644
--- a/src/components/GuestNameDialog.vue
+++ b/src/components/GuestNameDialog.vue
@@ -35,6 +35,7 @@
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import { generateUrl } from '@nextcloud/router'
+import { useSyncServiceMixin } from './EditorWrapper.provider'
export default {
name: 'GuestNameDialog',
@@ -44,12 +45,7 @@ export default {
directives: {
tooltip: Tooltip,
},
- props: {
- syncService: {
- type: Object,
- default: null,
- },
- },
+ mixins: [useSyncServiceMixin],
data() {
return {
guestName: '',
@@ -69,13 +65,13 @@ export default {
},
},
beforeMount() {
- this.guestName = this.syncService.session.guestName
+ this.guestName = this.$syncService.session.guestName
this.updateBufferedGuestName()
},
methods: {
setGuestName() {
- const previousGuestName = this.syncService.session.guestName
- this.syncService.updateSession(this.guestName).then(() => {
+ const previousGuestName = this.$syncService.session.guestName
+ this.$syncService.updateSession(this.guestName).then(() => {
localStorage.setItem('nick', this.guestName)
this.updateBufferedGuestName()
}).catch((e) => {
diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue
index 64637047d..4b0e57fe7 100644
--- a/src/components/MenuBar.vue
+++ b/src/components/MenuBar.vue
@@ -123,6 +123,8 @@ import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import menuBarIcons from './../mixins/menubar'
import isMobile from './../mixins/isMobile'
+import { useEditorMixin } from './EditorWrapper.provider'
+
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import PopoverMenu from '@nextcloud/vue/dist/Components/PopoverMenu'
@@ -145,17 +147,9 @@ export default {
},
mixins: [
isMobile,
+ useEditorMixin,
],
props: {
- editor: {
- type: Object,
- required: true,
- },
- syncService: {
- type: Object,
- required: false,
- default: null,
- },
isRichEditor: {
type: Boolean,
default: true,
@@ -211,16 +205,16 @@ export default {
return false
}
const args = Array.isArray(isActive) ? isActive : [isActive]
- return this.editor.isActive(...args)
+ return this.$editor.isActive(...args)
}
},
isVisible() {
- return this.editor.isFocused
+ return this.$editor.isFocused
|| Object.values(this.submenuVisibility).find((v) => v)
},
disabled() {
return (menuItem) => {
- return menuItem.action && !menuItem.action(this.editor.can())
+ return menuItem.action && !menuItem.action(this.$editor.can())
}
},
isChildMenuVisible() {
@@ -302,7 +296,7 @@ export default {
})
},
refocus() {
- this.editor.chain().focus().run()
+ this.$editor.chain().focus().run()
},
clickIcon(icon) {
if (icon.click) {
@@ -310,7 +304,7 @@ export default {
}
// Some actions run themselves.
// others still need to have .run() called upon them.
- const action = icon.action(this.editor.chain().focus())
+ const action = icon.action(this.$editor.chain().focus())
action && action.run()
},
getWindowWidth(event) {
@@ -369,7 +363,7 @@ export default {
return current.fill('..').concat(target).join('/')
},
addEmoji(icon, emojiObject) {
- return icon.action(this.editor.chain(), { id: emojiObject.id, native: emojiObject.native })
+ return icon.action(this.$editor.chain(), { id: emojiObject.id, native: emojiObject.native })
.focus()
.run()
},
diff --git a/src/components/MenuBubble.vue b/src/components/MenuBubble.vue
index d63fc9c41..31be99db0 100644
--- a/src/components/MenuBubble.vue
+++ b/src/components/MenuBubble.vue
@@ -21,7 +21,7 @@
-->
<template>
- <BubbleMenu :editor="editor"
+ <BubbleMenu :editor="$editor"
:tippy-options="{ onHide: hideLinkMenu, duration: 200, placement: 'bottom' }"
class="menububble">
<form v-if="linkMenuIsActive" class="menububble__form" @submit.prevent="setLinkUrl()">
@@ -74,6 +74,8 @@ import { getCurrentUser } from '@nextcloud/auth'
import { optimalPath } from './../helpers/files'
import { loadState } from '@nextcloud/initial-state'
+import { useEditorMixin } from './EditorWrapper.provider'
+
export default {
name: 'MenuBubble',
components: {
@@ -82,11 +84,8 @@ export default {
directives: {
tooltip: Tooltip,
},
+ mixins: [useEditorMixin],
props: {
- editor: {
- type: Object,
- required: true,
- },
// used to calculate the position based on the scrollOffset
contentWrapper: {
type: HTMLDivElement,
@@ -108,7 +107,7 @@ export default {
},
methods: {
showLinkMenu() {
- const attrs = getMarkAttributes(this.editor.state, 'link')
+ const attrs = getMarkAttributes(this.$editor.state, 'link')
this.linkUrl = attrs.href
this.linkMenuIsActive = true
this.$nextTick(() => {
@@ -131,7 +130,7 @@ export default {
const path = optimalPath(this.filePath, `${fileInfo.path}/${fileInfo.name}`)
const encodedPath = path.split('/').map(encodeURIComponent).join('/')
const href = `${encodedPath}?fileId=${fileInfo.id}`
- this.editor.chain().setLink({ href }).focus().run()
+ this.$editor.chain().setLink({ href }).focus().run()
this.hideLinkMenu()
})
}, false, [], true, undefined, startPath)
@@ -153,14 +152,14 @@ export default {
// Avoid issues when parsing urls later on in markdown that might be entered in an invalid format (e.g. "mailto: example@example.com")
const href = url.replaceAll(' ', '%20')
- this.editor.chain().setLink({ href }).focus().run()
+ this.$editor.chain().setLink({ href }).focus().run()
this.hideLinkMenu()
},
removeLinkUrl() {
- this.editor.chain().unsetLink().focus().run()
+ this.$editor.chain().unsetLink().focus().run()
},
isActive(selector, args = {}) {
- return this.editor.isActive(selector, args)
+ return this.$editor.isActive(selector, args)
},
},
}