@@ -15,10 +15,9 @@ class CMSEditor {
15
15
// CMS Editor: constructor
16
16
// Initialize the editor object
17
17
constructor ( ) {
18
- this . _editors = [ ] ;
19
- this . _generic_editors = [ ] ;
20
18
this . _global_settings = { } ;
21
19
this . _editor_settings = { } ;
20
+ this . _generic_editors = { } ;
22
21
this . _admin_selector = 'textarea.CMS_Editor' ;
23
22
this . _admin_add_row_selector = 'body.change-form .add-row a' ;
24
23
this . _inline_admin_selector = 'body.change-form .form-row' ;
@@ -94,83 +93,25 @@ class CMSEditor {
94
93
// CMS Editor: init
95
94
// Initialize a single editor
96
95
init ( el ) {
97
- let content ;
98
96
99
- // Get content: json > textarea > innerHTML
100
- if ( el . dataset . json ) {
101
- content = JSON . parse ( el . dataset . json ) ;
102
- } else {
103
- content = el . innerHTML ;
104
- }
105
- if ( el . tagName === 'TEXTAREA' ) {
106
- el . visible = false ;
107
- content = el . value ;
108
- // el = el.insertAdjacentElement('afterend', document.createElement('div'));
109
- }
110
97
if ( ! el . id ) {
111
98
el . id = "cms-edit-" + Math . random ( ) . toString ( 36 ) . slice ( 2 , 9 ) ;
112
99
}
113
- const settings = this . getSettings ( el ) ;
114
- // Element options overwrite
115
- settings . options = Object . assign ( { } ,
116
- settings . options || { } ,
117
- JSON . parse ( el . dataset . options || '{}' )
118
- ) ;
119
-
120
- // Add event listener to delete data on modal cancel
121
- if ( settings . revert_on_cancel ) {
122
- const CMS = this . CMS ;
123
- const csrf = CMS . config ?. csrf || document . querySelector ( 'input[name="csrfmiddlewaretoken"]' ) . value ;
124
- CMS . API . Helpers . addEventListener (
125
- 'modal-close.text-plugin.text-plugin-' + settings . plugin_id ,
126
- function ( e , opts ) {
127
- if ( ! settings . revert_on_cancel || ! settings . cancel_plugin_url ) {
128
- return ;
129
- }
130
- CMS . $ . ajax ( {
131
- method : 'POST' ,
132
- url : settings . cancel_plugin_url ,
133
- data : {
134
- token : settings . action_token ,
135
- csrfmiddlewaretoken : csrf
136
- } ,
137
- } ) . done ( function ( ) {
138
- CMS . API . Helpers . removeEventListener (
139
- 'modal-close.text-plugin.text-plugin-' + settings . plugin_id
140
- ) ;
141
- opts . instance . close ( ) ;
142
- } ) . fail ( function ( res ) {
143
- CMS . API . Messages . open ( {
144
- message : res . responseText + ' | ' + res . status + ' ' + res . statusText ,
145
- delay : 0 ,
146
- error : true
147
- } ) ;
148
- } ) ;
149
-
150
- }
151
- ) ;
100
+ if ( el . id in this . _editor_settings ) {
101
+ // Already initialized - happens when an inline editor is scrolled back into the viewport
102
+ return ;
152
103
}
153
- const inModal = ! ! document . querySelector (
154
- '.app-djangocms_text.model-text.change-form #' + el . id
155
- ) ;
156
-
157
104
// Create editor
158
- if ( ! el . dataset . cmsType || el . dataset . cmsType === 'TextPlugin' || el . dataset . cmsType === 'HTMLField' ) {
159
- window . cms_editor_plugin . create (
160
- el ,
161
- inModal ,
162
- content , settings ,
163
- el . tagName !== 'TEXTAREA' ? ( ) => this . saveData ( el ) : ( ) => {
164
- }
165
- ) ;
105
+ if ( ! el . dataset . cmsType || el . dataset . cmsType === 'TextPlugin' || el . dataset . cmsType === 'HTMLField' ) {
106
+ this . _createRTE ( el ) ;
166
107
} else if ( el . dataset . cmsType === 'CharField' ) {
167
- this . _generic_editors . push ( new CmsTextEditor ( el , {
108
+ // Creat simple generic text editor
109
+ this . _generic_editors [ el . id ] = new CmsTextEditor ( el , {
168
110
spellcheck : el . dataset . spellcheck || 'false' ,
169
111
} ,
170
112
( el ) => this . saveData ( el )
171
- ) ) ;
113
+ ) ;
172
114
}
173
- this . _editors . push ( el ) ;
174
115
}
175
116
176
117
// CMS Editor: initInlineEditors
@@ -208,8 +149,8 @@ class CMSEditor {
208
149
209
150
if ( plugin [ 1 ] . type === 'plugin' && plugin [ 1 ] . plugin_type === 'TextPlugin' ) {
210
151
// Text plugin
211
- const elements = document . querySelectorAll ( '.cms-plugin.cms-plugin- ' + id ) ;
212
- wrapper = this . _initInlineRichText ( elements , url , id ) ;
152
+ const elements = document . querySelectorAll ( '.cms-plugin.' + plugin [ 0 ] ) ;
153
+ wrapper = this . _initInlineRichText ( elements , url , plugin [ 0 ] ) ;
213
154
if ( wrapper ) {
214
155
wrapper . dataset . cmsPluginId = id ;
215
156
wrapper . dataset . cmsType = 'TextPlugin' ;
@@ -224,7 +165,7 @@ class CMSEditor {
224
165
const search_key = `${ generic_class [ 2 ] } -${ generic_class [ 3 ] } -${ edit_fields } ` ;
225
166
if ( generic_inline_fields [ search_key ] ) {
226
167
// Inline editable?
227
- wrapper = this . _initInlineRichText ( document . getElementsByClassName ( plugin [ 0 ] ) , url , id ) ;
168
+ wrapper = this . _initInlineRichText ( document . getElementsByClassName ( plugin [ 0 ] ) , url , plugin [ 0 ] ) ;
228
169
if ( wrapper ) {
229
170
wrapper . dataset . cmsCsrfToken = this . CMS . config . csrf ;
230
171
wrapper . dataset . cmsField = edit_fields ;
@@ -241,22 +182,25 @@ class CMSEditor {
241
182
if ( wrapper ) {
242
183
// Catch CMS single click event to highlight the plugin
243
184
// Catch CMS double click event if present, since double click is needed by Editor
244
- this . observer . observe ( wrapper ) ;
245
- if ( this . CMS ) {
246
- // Remove django CMS core's double click event handler which opens an edit dialog
247
- this . CMS . $ ( wrapper ) . off ( 'dblclick.cms.plugin' )
248
- . on ( 'dblclick.cms-editor' , function ( event ) {
249
- event . stopPropagation ( ) ;
250
- } ) ;
251
- wrapper . addEventListener ( 'focusin.cms-editor' , ( ) => {
252
- this . _highlightTextplugin ( id ) ;
253
- } , true ) ;
254
- // Prevent tooltip on hover
255
- this . CMS . $ ( wrapper ) . off ( 'pointerover.cms.plugin pointerout.cms.plugin' )
256
- . on ( 'pointerover.cms-editor' , function ( event ) {
257
- window . CMS . API . Tooltip . displayToggle ( false , event . target , '' , id ) ;
258
- event . stopPropagation ( ) ;
259
- } ) ;
185
+ if ( ! Array . from ( this . observer . root ?. children || [ ] ) . includes ( wrapper ) ) {
186
+ // Only add to the observer if not already observed (e.g., if the page only was update partially)
187
+ this . observer . observe ( wrapper ) ;
188
+ if ( this . CMS ) {
189
+ // Remove django CMS core's double click event handler which opens an edit dialog
190
+ this . CMS . $ ( wrapper ) . off ( 'dblclick.cms.plugin' )
191
+ . on ( 'dblclick.cms-editor' , function ( event ) {
192
+ event . stopPropagation ( ) ;
193
+ } ) ;
194
+ wrapper . addEventListener ( 'focusin' , ( ) => {
195
+ this . _highlightTextplugin ( id ) ;
196
+ } , true ) ;
197
+ // Prevent tooltip on hover
198
+ this . CMS . $ ( wrapper ) . off ( 'pointerover.cms.plugin pointerout.cms.plugin' )
199
+ . on ( 'pointerover.cms-editor' , function ( event ) {
200
+ window . CMS . API . Tooltip . displayToggle ( false , event . target , '' , id ) ;
201
+ event . stopPropagation ( ) ;
202
+ } ) ;
203
+ }
260
204
}
261
205
}
262
206
}
@@ -271,17 +215,22 @@ class CMSEditor {
271
215
} ) ;
272
216
}
273
217
274
- _initInlineRichText ( elements , url , id ) {
218
+ _initInlineRichText ( elements , url , cls ) {
275
219
let wrapper ;
276
220
277
221
if ( elements . length > 0 ) {
278
- if ( elements . length === 1 && elements [ 0 ] . tagName === 'DIV' || elements [ 0 ] . tagName === 'CMS-PLUGIN' ) {
222
+ if ( elements . length === 1 && (
223
+ elements [ 0 ] . tagName === 'DIV' || // Single wrapping div
224
+ elements [ 0 ] . tagName === 'CMS-PLUGIN' || // Single wrapping cms-plugin tag
225
+ elements [ 0 ] . classList . contains ( 'cms-editor-inline-wrapper' ) // already wrapped
226
+ ) ) {
279
227
// already wrapped?
280
228
wrapper = elements [ 0 ] ;
281
229
wrapper . classList . add ( 'cms-editor-inline-wrapper' ) ;
282
230
} else { // no, wrap now!
283
231
wrapper = document . createElement ( 'div' ) ;
284
232
wrapper . classList . add ( 'cms-editor-inline-wrapper' , 'wrapped' ) ;
233
+ wrapper . classList . add ( 'cms-plugin' , cls ) ;
285
234
wrapper = this . _wrapAll ( elements , wrapper ) ;
286
235
}
287
236
wrapper . dataset . cmsEditUrl = url ;
@@ -291,6 +240,73 @@ class CMSEditor {
291
240
return undefined ;
292
241
}
293
242
243
+ _createRTE ( el ) {
244
+ const settings = this . getSettings ( el ) ;
245
+ // Element options overwrite
246
+ settings . options = Object . assign ( { } ,
247
+ settings . options || { } ,
248
+ JSON . parse ( el . dataset . options || '{}' )
249
+ ) ;
250
+
251
+ // Add event listener to delete data on modal cancel
252
+ if ( settings . revert_on_cancel ) {
253
+ const CMS = this . CMS ;
254
+ const csrf = CMS . config ?. csrf || document . querySelector ( 'input[name="csrfmiddlewaretoken"]' ) . value ;
255
+ CMS . API . Helpers . addEventListener (
256
+ 'modal-close.text-plugin.text-plugin-' + settings . plugin_id ,
257
+ function ( e , opts ) {
258
+ if ( ! settings . revert_on_cancel || ! settings . cancel_plugin_url ) {
259
+ return ;
260
+ }
261
+ CMS . $ . ajax ( {
262
+ method : 'POST' ,
263
+ url : settings . cancel_plugin_url ,
264
+ data : {
265
+ token : settings . action_token ,
266
+ csrfmiddlewaretoken : csrf
267
+ } ,
268
+ } ) . done ( function ( ) {
269
+ CMS . API . Helpers . removeEventListener (
270
+ 'modal-close.text-plugin.text-plugin-' + settings . plugin_id
271
+ ) ;
272
+ opts . instance . close ( ) ;
273
+ } ) . fail ( function ( res ) {
274
+ CMS . API . Messages . open ( {
275
+ message : res . responseText + ' | ' + res . status + ' ' + res . statusText ,
276
+ delay : 0 ,
277
+ error : true
278
+ } ) ;
279
+ } ) ;
280
+
281
+ }
282
+ ) ;
283
+ }
284
+ const inModal = ! ! document . querySelector (
285
+ '.app-djangocms_text.model-text.change-form #' + el . id
286
+ ) ;
287
+ // Get content: json > textarea > innerHTML
288
+ let content ;
289
+
290
+ if ( el . dataset . json ) {
291
+ content = JSON . parse ( el . dataset . json ) ;
292
+ } else {
293
+ content = el . innerHTML ;
294
+ }
295
+ if ( el . tagName === 'TEXTAREA' ) {
296
+ el . visible = false ;
297
+ content = el . value ;
298
+ // el = el.insertAdjacentElement('afterend', document.createElement('div'));
299
+ }
300
+
301
+ window . cms_editor_plugin . create (
302
+ el ,
303
+ inModal ,
304
+ content , settings ,
305
+ el . tagName !== 'TEXTAREA' ? ( ) => this . saveData ( el ) : ( ) => {
306
+ }
307
+ ) ;
308
+ }
309
+
294
310
/**
295
311
* Retrieves the settings for the given editor.
296
312
* If the element is a string, it will be treated as an element's ID.
@@ -315,11 +331,15 @@ class CMSEditor {
315
331
this . _editor_settings [ el . id ] = Object . assign (
316
332
{ } ,
317
333
this . _global_settings ,
318
- JSON . parse ( settings_el . textContent ) || { }
334
+ JSON . parse ( settings_el . textContent || '{}' )
335
+ ) ;
336
+ } else {
337
+ this . _editor_settings [ el . id ] = Object . assign (
338
+ { } ,
339
+ this . _global_settings ,
319
340
) ;
320
- return this . _editor_settings [ el . id ] ;
321
341
}
322
- return { } ;
342
+ return this . _editor_settings [ el . id ] ;
323
343
}
324
344
325
345
/**
@@ -336,11 +356,16 @@ class CMSEditor {
336
356
337
357
// CMS Editor: destroy
338
358
destroyAll ( ) {
339
- while ( this . _editors . length ) {
340
- const el = this . _editors . pop ( ) ;
341
- this . destroyGenericEditor ( el ) ;
359
+ this . destroyRTE ( ) ;
360
+ this . destroyGenericEditor ( ) ;
361
+ }
362
+
363
+ destroyRTE ( ) {
364
+ for ( const el of Object . keys ( this . _editor_settings ) ) {
365
+ const element = document . getElementById ( el ) ;
342
366
window . cms_editor_plugin . destroyEditor ( el ) ;
343
367
}
368
+ this . _editor_settings = { } ;
344
369
}
345
370
346
371
// CMS Editor: destroyGenericEditor
@@ -611,7 +636,6 @@ class CMSEditor {
611
636
612
637
// CMS Editor: resetInlineEditors
613
638
_resetInlineEditors ( ) {
614
- this . destroyAll ( ) ;
615
639
this . initAll ( ) ;
616
640
}
617
641
@@ -627,7 +651,7 @@ class CMSEditor {
627
651
}
628
652
629
653
_highlightTextplugin ( pluginId ) {
630
- const HIGHLIGHT_TIMEOUT = 800 ;
654
+ const HIGHLIGHT_TIMEOUT = 100 ;
631
655
632
656
if ( this . CMS ) {
633
657
const $ = this . CMS . $ ;
@@ -683,5 +707,5 @@ class CMSEditor {
683
707
684
708
685
709
// Create global editor object
686
- window . CMS_Editor = new CMSEditor ( ) ;
710
+ window . CMS_Editor = window . CMS_Editor || new CMSEditor ( ) ;
687
711
0 commit comments