-
Notifications
You must be signed in to change notification settings - Fork 77
/
ckeditor.js
168 lines (143 loc) · 4.98 KB
/
ckeditor.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/
/* global console */
import { h } from 'vue';
import { debounce } from 'lodash-es';
const INPUT_EVENT_DEBOUNCE_WAIT = 300;
export default {
name: 'ckeditor',
render() {
return h( this.tagName );
},
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
editor: {
type: Function,
default: null
},
modelValue: {
type: String,
default: ''
},
config: {
type: Object,
default: () => ( {} )
},
tagName: {
type: String,
default: 'div'
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
// Don't define it in #props because it produces a warning.
// https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
$_instance: null,
$_lastEditorData: {
type: String,
default: ''
}
};
},
mounted() {
// Clone the config first so it never gets mutated (across multiple editor instances).
// https://github.com/ckeditor/ckeditor5-vue/issues/101
const editorConfig = Object.assign( {}, this.config );
if ( this.modelValue ) {
editorConfig.initialData = this.modelValue;
}
this.editor.create( this.$el, editorConfig )
.then( editor => {
// Save the reference to the $_instance for further use.
this.$_instance = editor;
// Set initial disabled state.
editor.isReadOnly = this.disabled;
this.$_setUpEditorEvents();
// Let the world know the editor is ready.
this.$emit( 'ready', editor );
} )
.catch( error => {
console.error( error );
} );
},
beforeUnmount() {
if ( this.$_instance ) {
this.$_instance.destroy();
this.$_instance = null;
}
// Note: By the time the editor is destroyed (promise resolved, editor#destroy fired)
// the Vue component will not be able to emit any longer. So emitting #destroy a bit earlier.
this.$emit( 'destroy', this.$_instance );
},
watch: {
modelValue( newValue, oldValue ) {
// Synchronize changes of #modelValue. There are two sources of changes:
//
// External modelValue change ------\
// -----> +-----------+
// | Component |
// -----> +-----------+
// Internal data change ------/
// (typing, commands, collaboration)
//
// Case 1: If the change was external (via props), the editor data must be synced with
// the component using $_instance#setData() and it is OK to destroy the selection.
//
// Case 2: If the change is the result of internal data change, the #modelValue is the
// same as this.$_lastEditorData, which has been cached on #change:data. If we called
// $_instance#setData() at this point, that would demolish the selection.
//
// To limit the number of $_instance#setData() which is time-consuming when there is a
// lot of data we make sure:
// * the new modelValue is at least different than the old modelValue (Case 1.)
// * the new modelValue is different than the last internal instance state (Case 2.)
//
// See: https://github.com/ckeditor/ckeditor5-vue/issues/42.
if ( newValue !== oldValue && newValue !== this.$_lastEditorData ) {
this.$_instance.setData( newValue );
}
},
// Synchronize changes of #disabled.
disabled( val ) {
this.$_instance.isReadOnly = val;
}
},
methods: {
$_setUpEditorEvents() {
const editor = this.$_instance;
// Use the leading edge so the first event in the series is emitted immediately.
// Failing to do so leads to race conditions, for instance, when the component modelValue
// is set twice in a time span shorter than the debounce time.
// See https://github.com/ckeditor/ckeditor5-vue/issues/149.
const emitDebouncedInputEvent = debounce( evt => {
// Cache the last editor data. This kind of data is a result of typing,
// editor command execution, collaborative changes to the document, etc.
// This data is compared when the component modelValue changes in a 2-way binding.
const data = this.$_lastEditorData = editor.getData();
// The compatibility with the v-model and general Vue.js concept of input–like components.
this.$emit( 'update:modelValue', data, evt, editor );
this.$emit( 'input', data, evt, editor );
}, INPUT_EVENT_DEBOUNCE_WAIT, { leading: true } );
// Debounce emitting the #input event. When data is huge, $_instance#getData()
// takes a lot of time to execute on every single key press and ruins the UX.
//
// See: https://github.com/ckeditor/ckeditor5-vue/issues/42
editor.model.document.on( 'change:data', emitDebouncedInputEvent );
editor.editing.view.document.on( 'focus', evt => {
this.$emit( 'focus', evt, editor );
} );
editor.editing.view.document.on( 'blur', evt => {
this.$emit( 'blur', evt, editor );
} );
}
}
};