From ddabfc98176d58452999cc2da9195b3dd564a1aa Mon Sep 17 00:00:00 2001 From: Ruslan Semak Date: Tue, 15 Apr 2025 17:33:13 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=A7=D1=82=D0=BE-=D1=82=D0=BE=20?= =?UTF-8?q?=D0=B7=D0=B0=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BB=D0=BE=20?= =?UTF-8?q?=D1=81=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=BE=D0=B9!!!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/components/editor/editor-ckeditor.vue | 98 +++++++++++++++++--- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/client/components/editor/editor-ckeditor.vue b/client/components/editor/editor-ckeditor.vue index b39c93ed..3c951d5b 100644 --- a/client/components/editor/editor-ckeditor.vue +++ b/client/components/editor/editor-ckeditor.vue @@ -21,6 +21,7 @@ import DecoupledEditor from '@requarks/ckeditor5' // import DecoupledEditor from '../../../../wiki-ckeditor5/build/ckeditor' import EditorConflict from './ckeditor/conflict.vue' import { html as beautify } from 'js-beautify/js/lib/beautifier.min.js' +import gql from 'graphql-tag' /* global siteLangs */ @@ -65,21 +66,44 @@ export default { async mounted () { this.$store.set('editor/editorKey', 'ckeditor') + const resp = await this.$apollo.query({ + query: gql` + query ($limit: Int!) { + pages { + list(limit: $limit) { + id + path + title + } + } + }`, + fetchPolicy: 'network-only', + variables: { + limit: 10000 + } + }) + + const feed = resp.data.pages.list.map(({title}) => { + return { + id: `@${title}`, + link: 'https://example.com', + userId: 1 + } + }) + this.editor = await DecoupledEditor.create(this.$refs.editor, { language: this.locale, placeholder: 'Type the page content here', disableNativeSpellChecker: false, - // TODO: Mention autocomplete - // - // mention: { - // feeds: [ - // { - // marker: '@', - // feed: [ '@Barney', '@Lily', '@Marshall', '@Robin', '@Ted' ], - // minimumCharacters: 1 - // } - // ] - // }, + mention: { + feeds: [ + { + marker: '@', + feed, + minimumCharacters: 1 + } + ] + }, wordCount: { onUpdate: stats => { this.stats = { @@ -95,6 +119,58 @@ export default { this.editor.setData(this.$store.get('editor/content')) } + this.editor.conversion.for('upcast').elementToAttribute({ + view: { + name: 'a', + key: 'data-mention', + classes: 'mention', + attributes: { + href: true, + 'data-user-id': true + } + }, + model: { + key: 'mention', + value: viewItem => { + // The mention feature expects that the mention attribute value + // in the model is a plain object with a set of additional attributes. + // In order to create a proper object, use the toMentionAttribute helper method: + const mentionAttribute = this.editor.plugins.get('Mention').toMentionAttribute(viewItem, { + // Add any other properties that you need. + link: viewItem.getAttribute('href'), + userId: viewItem.getAttribute('data-user-id') + }) + + return mentionAttribute + } + }, + converterPriority: 'high' + }) + + // Downcast the model 'mention' text attribute to a view element. + this.editor.conversion.for('downcast').attributeToElement({ + model: 'mention', + view: (modelAttributeValue, writer) => { + // Do not convert empty attributes (lack of value means no mention). + if (!modelAttributeValue) { + return + } + + return writer.createAttributeElement('a', { + class: 'mention', + 'data-mention': modelAttributeValue.id, + 'data-user-id': modelAttributeValue.userId, + 'href': modelAttributeValue.link + }, { + // Make mention attribute to be wrapped by other attribute elements. + priority: 20, + // Prevent merging mentions together. + id: modelAttributeValue.uid + }) + }, + converterPriority: 'high' + }) + this.editor.model.document.on('change:data', _.debounce(evt => { this.$store.set('editor/content', beautify(this.editor.getData(), { indent_size: 2, end_with_newline: true })) }, 300))