@@ -4,34 +4,76 @@ const cleanupKey = Symbol('cleanup');
44// Set up a single textarea with CKEditor
55async function initializeEditor ( textarea , component ) {
66 try {
7- const editorConfig = JSON . parse ( document . querySelector ( 'meta[name="ckeditor-config"]' ) ?. content || 'null' ) || { }
7+ let globalEditorConfig = null ;
8+ try {
9+ globalEditorConfig = JSON . parse ( document . querySelector ( 'meta[name="ckeditor-config"]' ) ?. content || 'null' ) ;
10+ } catch ( e ) {
11+ // Ignore
12+ console . debug ( 'Unable to parse CKEditor config from meta tag' , e ) ;
13+ }
14+
15+ let localEditorConfig = null ;
16+ try {
17+ localEditorConfig = JSON . parse ( textarea . dataset . editorConfig || 'null' ) ;
18+ } catch ( e ) {
19+ // Ignore
20+ console . debug ( 'Unable to parse CKEditor config from textarea data-editor-config' , e ) ;
21+ }
22+
23+ const editorConfig = {
24+ ...( globalEditorConfig || { } ) ,
25+ ...( localEditorConfig || { } ) ,
26+ toolbar : {
27+ items : [
28+ 'bold' , 'italic' , 'link' ,
29+ 'bulletedList' , 'numberedList' , 'blockQuote' , '|' ,
30+ 'heading' , 'insertTable' , 'mediaEmbed' , '|' ,
31+ 'undo' , 'redo'
32+ ] ,
33+ // Collapse into three dots menu when the toolbar is full.
34+ shouldNotGroupWhenFull : false
35+ }
36+ }
37+
838 const editor = await ClassicEditor . create ( textarea , editorConfig ) ;
939 const editorId = textarea . dataset . editorId ;
1040
11- // Listen for changes to the editor content and update the component's content property
12- editor . model . document . on ( 'change:data' , ( ) => {
13- component . $wire . $set ( 'content' , editor . getData ( ) ) ;
41+ // Listen for changes to the editor content and update the component's content property.
42+ const sync = ( ) => {
43+ const content = editor . getData ( ) ;
44+ textarea . value = content ;
45+ component . $wire . $set ( 'content' , content , false ) ;
46+ } ;
47+
48+ editor . model . document . on ( 'change:data' , sync ) ;
49+
50+ // Also sync when the editor loses focus.
51+ editor . ui . focusTracker . on ( 'change:isFocused' , ( _event , _name , isFocused ) => {
52+ if ( ! isFocused ) sync ( ) ;
1453 } ) ;
1554
1655 // Reset the editor content when we receive a notification from the backend.
17- const onEditorClear = ( event ) => {
56+ const clearEditor = ( event ) => {
1857 if ( event . detail . editorId === editorId ) {
1958 editor . setData ( '' ) ;
59+ textarea . value = '' ;
2060 }
2161 } ;
2262
23- document . addEventListener ( 'editor:clear' , onEditorClear ) ;
63+ document . addEventListener ( 'editor:clear' , clearEditor ) ;
2464
2565 // Update the component's content property when the force sync event is received
26- const onForceSync = ( ) => {
27- component . $wire . $set ( 'content' , editor . getData ( ) ) ;
66+ const syncAndFlush = ( ) => {
67+ const content = editor . getData ( ) ;
68+ textarea . value = content ;
69+ component . $wire . $set ( 'content' , content ) ;
2870 } ;
2971
30- document . addEventListener ( 'livewire:force-sync' , onForceSync ) ;
72+ document . addEventListener ( 'livewire:force-sync' , syncAndFlush ) ;
3173
3274 textarea [ cleanupKey ] = ( ) => {
33- document . removeEventListener ( 'editor:clear' , onEditorClear ) ;
34- document . removeEventListener ( 'livewire:force-sync' , onForceSync ) ;
75+ document . removeEventListener ( 'editor:clear' , clearEditor ) ;
76+ document . removeEventListener ( 'livewire:force-sync' , syncAndFlush ) ;
3577 editor . destroy ( ) . catch ( e => console . error ( 'Error destroying editor' , e ) ) ;
3678 } ;
3779 } catch ( error ) {
0 commit comments