• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ckeditor / ckeditor5-vue / 333

pending completion
333

push

travis-ci-com

pomek
Internal: Bumped the year.

19 of 19 branches covered (100.0%)

Branch coverage included in aggregate %.

46 of 46 relevant lines covered (100.0%)

18.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/ckeditor.js
1
/**
2
 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
 * For licensing, see LICENSE.md.
4
 */
5

6
/* global window, console */
7

8
import { h, markRaw } from 'vue';
9
import { debounce } from 'lodash-es';
10

11
const SAMPLE_READ_ONLY_LOCK_ID = 'Integration Sample';
1✔
12
const INPUT_EVENT_DEBOUNCE_WAIT = 300;
1✔
13

14
export default {
15
        name: 'ckeditor',
16

17
        created() {
18
                const { CKEDITOR_VERSION } = window;
33✔
19

20
                // Starting from v34.0.0, CKEditor 5 introduces a lock mechanism enabling/disabling the read-only mode.
21
                // As it is a breaking change between major releases of the integration, the component requires using
22
                // CKEditor 5 in version 34 or higher.
23
                if ( CKEDITOR_VERSION ) {
33✔
24
                        const [ major ] = CKEDITOR_VERSION.split( '.' ).map( Number );
32✔
25

26
                        if ( major < 34 ) {
32✔
27
                                console.warn( 'The <CKEditor> component requires using CKEditor 5 in version 34 or higher.' );
1✔
28
                        }
29
                } else {
30
                        console.warn( 'Cannot find the "CKEDITOR_VERSION" in the "window" scope.' );
1✔
31
                }
32
        },
33

34
        render() {
35
                return h( this.tagName );
40✔
36
        },
37

38
        model: {
39
                prop: 'modelValue',
40
                event: 'update:modelValue'
41
        },
42

43
        props: {
44
                editor: {
45
                        type: Function,
46
                        default: null
47
                },
48
                modelValue: {
49
                        type: String,
50
                        default: ''
51
                },
52
                config: {
53
                        type: Object,
54
                        default: () => ( {} )
29✔
55
                },
56
                tagName: {
57
                        type: String,
58
                        default: 'div'
59
                },
60
                disabled: {
61
                        type: Boolean,
62
                        default: false
63
                }
64
        },
65

66
        data() {
67
                return {
33✔
68
                        // Don't define it in #props because it produces a warning.
69
                        // https://v3.vuejs.org/guide/component-props.html#one-way-data-flow
70
                        instance: null,
71

72
                        lastEditorData: {
73
                                type: String,
74
                                default: ''
75
                        }
76
                };
77
        },
78

79
        mounted() {
80
                // Clone the config first so it never gets mutated (across multiple editor instances).
81
                // https://github.com/ckeditor/ckeditor5-vue/issues/101
82
                const editorConfig = Object.assign( {}, this.config );
33✔
83

84
                if ( this.modelValue ) {
33✔
85
                        editorConfig.initialData = this.modelValue;
6✔
86
                }
87

88
                this.editor.create( this.$el, editorConfig )
33✔
89
                        .then( editor => {
90
                                // Save the reference to the instance for further use.
91
                                this.instance = markRaw( editor );
32✔
92

93
                                this.setUpEditorEvents();
32✔
94

95
                                // Synchronize the editor content. The #modelValue may change while the editor is being created, so the editor content has
96
                                // to be synchronized with these potential changes as soon as it is ready.
97
                                if ( this.modelValue !== editorConfig.initialData ) {
32✔
98
                                        editor.setData( this.modelValue );
27✔
99
                                }
100

101
                                // Set initial disabled state.
102
                                if ( this.disabled ) {
32✔
103
                                        editor.enableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
2✔
104
                                }
105

106
                                // Let the world know the editor is ready.
107
                                this.$emit( 'ready', editor );
32✔
108
                        } )
109
                        .catch( error => {
110
                                console.error( error );
1✔
111
                        } );
112
        },
113

114
        beforeUnmount() {
115
                if ( this.instance ) {
33✔
116
                        this.instance.destroy();
31✔
117
                        this.instance = null;
31✔
118
                }
119

120
                // Note: By the time the editor is destroyed (promise resolved, editor#destroy fired)
121
                // the Vue component will not be able to emit any longer. So emitting #destroy a bit earlier.
122
                this.$emit( 'destroy', this.instance );
33✔
123
        },
124

125
        watch: {
126
                modelValue( value ) {
127
                        // Synchronize changes of #modelValue. There are two sources of changes:
128
                        //
129
                        //                External modelValue change      ──────╮
130
                        //                                                      ╰─────> ┏━━━━━━━━━━━┓
131
                        //                                                              ┃ Component ┃
132
                        //                                                      ╭─────> ┗━━━━━━━━━━━┛
133
                        //                   Internal data change         ──────╯
134
                        //             (typing, commands, collaboration)
135
                        //
136
                        // Case 1: If the change was external (via props), the editor data must be synced with
137
                        // the component using instance#setData() and it is OK to destroy the selection.
138
                        //
139
                        // Case 2: If the change is the result of internal data change, the #modelValue is the
140
                        // same as this.lastEditorData, which has been cached on #change:data. If we called
141
                        // instance#setData() at this point, that would demolish the selection.
142
                        //
143
                        // To limit the number of instance#setData() which is time-consuming when there is a
144
                        // lot of data we make sure:
145
                        //    * the new modelValue is at least different than the old modelValue (Case 1.)
146
                        //    * the new modelValue is different than the last internal instance state (Case 2.)
147
                        //
148
                        // See: https://github.com/ckeditor/ckeditor5-vue/issues/42.
149
                        if ( this.instance && value !== this.lastEditorData ) {
5✔
150
                                this.instance.setData( value );
3✔
151
                        }
152
                },
153

154
                // Synchronize changes of #disabled.
155
                disabled( readOnlyMode ) {
156
                        if ( readOnlyMode ) {
2✔
157
                                this.instance.enableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
1✔
158
                        } else {
159
                                this.instance.disableReadOnlyMode( SAMPLE_READ_ONLY_LOCK_ID );
1✔
160
                        }
161
                }
162
        },
163

164
        methods: {
165
                setUpEditorEvents() {
166
                        const editor = this.instance;
32✔
167

168
                        // Use the leading edge so the first event in the series is emitted immediately.
169
                        // Failing to do so leads to race conditions, for instance, when the component modelValue
170
                        // is set twice in a time span shorter than the debounce time.
171
                        // See https://github.com/ckeditor/ckeditor5-vue/issues/149.
172
                        const emitDebouncedInputEvent = debounce( evt => {
32✔
173
                                // Cache the last editor data. This kind of data is a result of typing,
174
                                // editor command execution, collaborative changes to the document, etc.
175
                                // This data is compared when the component modelValue changes in a 2-way binding.
176
                                const data = this.lastEditorData = editor.getData();
2✔
177

178
                                // The compatibility with the v-model and general Vue.js concept of input–like components.
179
                                this.$emit( 'update:modelValue', data, evt, editor );
2✔
180
                                this.$emit( 'input', data, evt, editor );
2✔
181
                        }, INPUT_EVENT_DEBOUNCE_WAIT, { leading: true } );
182

183
                        // Debounce emitting the #input event. When data is huge, instance#getData()
184
                        // takes a lot of time to execute on every single key press and ruins the UX.
185
                        //
186
                        // See: https://github.com/ckeditor/ckeditor5-vue/issues/42
187
                        editor.model.document.on( 'change:data', emitDebouncedInputEvent );
32✔
188

189
                        editor.editing.view.document.on( 'focus', evt => {
32✔
190
                                this.$emit( 'focus', evt, editor );
1✔
191
                        } );
192

193
                        editor.editing.view.document.on( 'blur', evt => {
32✔
194
                                this.$emit( 'blur', evt, editor );
1✔
195
                        } );
196
                }
197
        }
198
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc