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

ckeditor / ckeditor5-vue / a5490b25-eb49-473c-a74b-12e44ced0a06

24 Sep 2024 01:48PM UTC coverage: 100.0%. Remained the same
a5490b25-eb49-473c-a74b-12e44ced0a06

Pull #318

circleci

martnpaneq
Updated dependency versions to remove warnings.
Pull Request #318: Updated dependency versions to remove warnings.

30 of 30 branches covered (100.0%)

Branch coverage included in aggregate %.

79 of 79 relevant lines covered (100.0%)

33.77 hits per line

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

100.0
/src/ckeditor.vue
1
<template>
2
  <component
80✔
3
    :is="tagName"
4
    ref="element"
5
  />
6
</template>
7

8
<script
9
        setup
10
        lang="ts"
11
        generic="TEditor extends { create( ...args: any[] ): Promise<Editor> }"
12
>
13
import { debounce } from 'lodash-es';
14
import {
15
        ref,
16
        watch,
17
        markRaw,
18
        onMounted,
19
        onBeforeUnmount
20
} from 'vue';
21
import type { Editor, EditorConfig, EventInfo } from 'ckeditor5';
22
import type { Props, ExtractEditorType } from './types.js';
23

24
type EditorType = ExtractEditorType<TEditor>;
25

26
defineOptions( {
27
        name: 'CKEditor'
28
} );
29

30
const model = defineModel( 'modelValue', { type: String, default: '' } );
66✔
31

32
const props = withDefaults( defineProps<Props<TEditor>>(), {
66✔
33
        config: () => ( {} ),
34
        tagName: 'div',
35
        disabled: false,
36
        disableTwoWayDataBinding: false
37
} );
38

39
const emit = defineEmits<{
66✔
40
        ready: [ editor: EditorType ],
41
        destroy: [],
42
        blur: [ event: EventInfo, editor: EditorType ],
43
        focus: [ event: EventInfo, editor: EditorType ],
44
        input: [ data: string, event: EventInfo, editor: EditorType ],
45
        'update:modelValue': [ data: string, event: EventInfo, editor: EditorType ],
46
}>();
47

48
const VUE_INTEGRATION_READ_ONLY_LOCK_ID = 'Lock from Vue integration (@ckeditor/ckeditor5-vue)';
4✔
49
const INPUT_EVENT_DEBOUNCE_WAIT = 300;
4✔
50

51
const element = ref<HTMLElement>();
66✔
52
const instance = ref<EditorType>();
66✔
53
const lastEditorData = ref<string>();
66✔
54

55
defineExpose( {
66✔
56
        instance,
57
        lastEditorData
58
} );
59

60
watch( model, newModel => {
66✔
61
        // Synchronize changes of #modelValue. There are two sources of changes:
62
        //
63
        //                External modelValue change      ──────╮
64
        //                                                      ╰─────> ┏━━━━━━━━━━━┓
65
        //                                                              ┃ Component ┃
66
        //                                                      ╭─────> ┗━━━━━━━━━━━┛
67
        //                   Internal data change         ──────╯
68
        //             (typing, commands, collaboration)
69
        //
70
        // Case 1: If the change was external (via props), the editor data must be synced with
71
        // the component using instance#setData() and it is OK to destroy the selection.
72
        //
73
        // Case 2: If the change is the result of internal data change, the #modelValue is the
74
        // same as this.lastEditorData, which has been cached on #change:data. If we called
75
        // instance#setData() at this point, that would demolish the selection.
76
        //
77
        // To limit the number of instance#setData() which is time-consuming when there is a
78
        // lot of data we make sure:
79
        //    * the new modelValue is at least different than the old modelValue (Case 1.)
80
        //    * the new modelValue is different than the last internal instance state (Case 2.)
81
        //
82
        // See: https://github.com/ckeditor/ckeditor5-vue/issues/42.
83
        if ( instance.value && newModel !== lastEditorData.value ) {
10✔
84
                instance.value.data.set( newModel );
6✔
85
        }
86
} );
87

88
watch( () => props.disabled, readOnlyMode => {
70✔
89
        if ( readOnlyMode ) {
4✔
90
                instance.value!.enableReadOnlyMode( VUE_INTEGRATION_READ_ONLY_LOCK_ID );
2✔
91
        } else {
92
                instance.value!.disableReadOnlyMode( VUE_INTEGRATION_READ_ONLY_LOCK_ID );
2✔
93
        }
94
} );
95

96
function checkVersion(): void {
97
        const version = window.CKEDITOR_VERSION;
66✔
98

99
        if ( !version ) {
66✔
100
                return console.warn( 'Cannot find the "CKEDITOR_VERSION" in the "window" scope.' );
2✔
101
        }
102

103
        const [ major ] = version.split( '.' ).map( Number );
64✔
104

105
        if ( major >= 42 || version.startsWith( '0.0.0' ) ) {
64✔
106
                return;
62✔
107
        }
108

109
        console.warn( 'The <CKEditor> component requires using CKEditor 5 in version 42+ or nightly build.' );
2✔
110
}
111

112
function setUpEditorEvents( editor: EditorType ) {
113
        // Use the leading edge so the first event in the series is emitted immediately.
114
        // Failing to do so leads to race conditions, for instance, when the component modelValue
115
        // is set twice in a time span shorter than the debounce time.
116
        // See https://github.com/ckeditor/ckeditor5-vue/issues/149.
117
        const emitDebouncedInputEvent = debounce( ( evt: EventInfo ) => {
64✔
118
                if ( props.disableTwoWayDataBinding ) {
6✔
119
                        return;
2✔
120
                }
121

122
                // Cache the last editor data. This kind of data is a result of typing,
123
                // editor command execution, collaborative changes to the document, etc.
124
                // This data is compared when the component modelValue changes in a 2-way binding.
125
                const data = lastEditorData.value = editor.data.get();
4✔
126

127
                // The compatibility with the v-model and general Vue.js concept of input–like components.
128
                emit( 'update:modelValue', data, evt, editor );
4✔
129
                emit( 'input', data, evt, editor );
4✔
130
        }, INPUT_EVENT_DEBOUNCE_WAIT, { leading: true } );
131

132
        // Debounce emitting the #input event. When data is huge, instance#getData()
133
        // takes a lot of time to execute on every single key press and ruins the UX.
134
        //
135
        // See: https://github.com/ckeditor/ckeditor5-vue/issues/42
136
        editor.model.document.on( 'change:data', emitDebouncedInputEvent );
64✔
137

138
        editor.editing.view.document.on( 'focus', ( evt: EventInfo ) => {
64✔
139
                emit( 'focus', evt, editor );
2✔
140
        } );
141

142
        editor.editing.view.document.on( 'blur', ( evt: EventInfo ) => {
64✔
143
                emit( 'blur', evt, editor );
2✔
144
        } );
145
}
146

147
checkVersion();
66✔
148

149
onMounted( () => {
66✔
150
        // Clone the config first so it never gets mutated (across multiple editor instances).
151
        // https://github.com/ckeditor/ckeditor5-vue/issues/101
152
        const editorConfig: EditorConfig = Object.assign( {}, props.config );
66✔
153

154
        if ( model.value ) {
66✔
155
                editorConfig.initialData = model.value;
11✔
156
        }
157

158
        ( props.editor.create( element.value, editorConfig ) as unknown as Promise<EditorType> )
66✔
159
                .then( editor => {
160
                        // Save the reference to the instance for further use.
161
                        instance.value = markRaw( editor );
64✔
162

163
                        setUpEditorEvents( editor );
64✔
164

165
                        // Synchronize the editor content. The #modelValue may change while the editor is being created, so the editor content has
166
                        // to be synchronized with these potential changes as soon as it is ready.
167
                        if ( model.value !== editorConfig.initialData ) {
64✔
168
                                editor.data.set( model.value );
55✔
169
                        }
170

171
                        // Set initial disabled state.
172
                        if ( props.disabled ) {
64✔
173
                                editor.enableReadOnlyMode( VUE_INTEGRATION_READ_ONLY_LOCK_ID );
4✔
174
                        }
175

176
                        // Let the world know the editor is ready.
177
                        emit( 'ready', editor );
64✔
178
                } )
179
                .catch( error => {
180
                        console.error( error );
2✔
181
                } );
182
} );
183

184
onBeforeUnmount( () => {
66✔
185
        if ( instance.value ) {
66✔
186
                instance.value.destroy();
62✔
187
                instance.value = undefined;
62✔
188
        }
189

190
        // Note: By the time the editor is destroyed (promise resolved, editor#destroy fired)
191
        // the Vue component will not be able to emit any longer. So emitting #destroy a bit earlier.
192
        emit( 'destroy' );
66✔
193
} );
194
</script>
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