]>
Commit | Line | Data |
---|---|---|
c63493c8 IB |
1 | /* global ToolbarConfigurator */ |
2 | ||
3 | 'use strict'; | |
4 | ||
5 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create | |
6 | if ( typeof Object.create != 'function' ) { | |
7 | ( function() { | |
8 | var F = function() {}; | |
9 | Object.create = function( o ) { | |
10 | if ( arguments.length > 1 ) { | |
11 | throw Error( 'Second argument not supported' ); | |
12 | } | |
13 | if ( o === null ) { | |
14 | throw Error( 'Cannot set a null [[Prototype]]' ); | |
15 | } | |
16 | if ( typeof o != 'object' ) { | |
17 | throw TypeError( 'Argument must be an object' ); | |
18 | } | |
19 | F.prototype = o; | |
20 | return new F(); | |
21 | }; | |
22 | } )(); | |
23 | } | |
24 | ||
25 | // Copy of the divarea plugin (with some enhancements), so we always have some editable mode, regardless of the build's config. | |
26 | CKEDITOR.plugins.add( 'toolbarconfiguratorarea', { | |
27 | // Use afterInit to override wysiwygarea's mode. May still fail to override divarea, but divarea is nice. | |
28 | afterInit: function( editor ) { | |
29 | editor.addMode( 'wysiwyg', function( callback ) { | |
30 | var editingBlock = CKEDITOR.dom.element.createFromHtml( '<div class="cke_wysiwyg_div cke_reset" hidefocus="true"></div>' ); | |
31 | ||
32 | var contentSpace = editor.ui.space( 'contents' ); | |
33 | contentSpace.append( editingBlock ); | |
34 | ||
35 | editingBlock = editor.editable( editingBlock ); | |
36 | ||
37 | editingBlock.detach = CKEDITOR.tools.override( editingBlock.detach, | |
38 | function( org ) { | |
39 | return function() { | |
40 | org.apply( this, arguments ); | |
41 | this.remove(); | |
42 | }; | |
43 | } ); | |
44 | ||
45 | editor.setData( editor.getData( 1 ), callback ); | |
46 | editor.fire( 'contentDom' ); | |
47 | } ); | |
48 | ||
49 | // Additions to the divarea. | |
50 | ||
51 | // Speed up data processing. | |
52 | editor.dataProcessor.toHtml = function( html ) { | |
53 | return html; | |
54 | }; | |
55 | editor.dataProcessor.toDataFormat = function( html ) { | |
56 | return html; | |
57 | }; | |
58 | ||
59 | // End of the additions. | |
60 | } | |
61 | } ); | |
62 | ||
63 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys | |
64 | if ( !Object.keys ) { | |
65 | Object.keys = ( function() { | |
66 | var hasOwnProperty = Object.prototype.hasOwnProperty, | |
67 | hasDontEnumBug = !( { toString: null } ).propertyIsEnumerable( 'toString' ), | |
68 | dontEnums = [ | |
69 | 'toString', | |
70 | 'toLocaleString', | |
71 | 'valueOf', | |
72 | 'hasOwnProperty', | |
73 | 'isPrototypeOf', | |
74 | 'propertyIsEnumerable', | |
75 | 'constructor' | |
76 | ], | |
77 | dontEnumsLength = dontEnums.length; | |
78 | ||
79 | return function( obj ) { | |
80 | if ( typeof obj !== 'object' && ( typeof obj !== 'function' || obj === null ) ) | |
81 | throw new TypeError( 'Object.keys called on non-object' ); | |
82 | ||
83 | var result = [], prop, i; | |
84 | ||
85 | for ( prop in obj ) { | |
86 | if ( hasOwnProperty.call( obj, prop ) ) | |
87 | result.push( prop ); | |
88 | ||
89 | } | |
90 | ||
91 | if ( hasDontEnumBug ) { | |
92 | for ( i = 0; i < dontEnumsLength; i++ ) { | |
93 | if ( hasOwnProperty.call( obj, dontEnums[ i ] ) ) | |
94 | result.push( dontEnums[ i ] ); | |
95 | ||
96 | } | |
97 | } | |
98 | return result; | |
99 | }; | |
100 | }() ); | |
101 | } | |
102 | ||
103 | ( function() { | |
104 | /** | |
105 | * @class ToolbarConfigurator.AbstractToolbarModifier | |
106 | * @param {String} editorId An id of modified editor | |
107 | * @constructor | |
108 | */ | |
109 | function AbstractToolbarModifier( editorId, cfg ) { | |
110 | this.cfg = cfg || {}; | |
111 | this.hidden = false; | |
112 | this.editorId = editorId; | |
113 | this.fullToolbarEditor = new ToolbarConfigurator.FullToolbarEditor(); | |
114 | ||
115 | this.mainContainer = null; | |
116 | ||
117 | this.originalConfig = null; | |
118 | this.actualConfig = null; | |
119 | ||
120 | this.waitForReady = false; | |
121 | this.isEditableVisible = false; | |
122 | ||
123 | this.toolbarContainer = null; | |
124 | this.toolbarButtons = []; | |
125 | } | |
126 | ||
127 | // Expose the class. | |
128 | ToolbarConfigurator.AbstractToolbarModifier = AbstractToolbarModifier; | |
129 | ||
130 | /** | |
131 | * @param {String} config | |
132 | */ | |
133 | AbstractToolbarModifier.prototype.setConfig = function( config ) { | |
134 | this._onInit( undefined, config, true ); | |
135 | }; | |
136 | ||
137 | /** | |
138 | * @param {Function} [callback] | |
139 | */ | |
140 | AbstractToolbarModifier.prototype.init = function( callback ) { | |
141 | var that = this; | |
142 | ||
143 | this.mainContainer = new CKEDITOR.dom.element( 'div' ); | |
144 | ||
145 | if ( this.fullToolbarEditor.editorInstance !== null ) { | |
146 | throw 'Only one instance of ToolbarModifier is allowed'; | |
147 | } | |
148 | ||
149 | if ( !this.editorInstance ) { | |
150 | // Do not refresh yet, let's wait for the full toolbar editor (see below). | |
151 | this._createEditor( false ); | |
152 | } | |
153 | ||
154 | this.editorInstance.once( 'loaded', function() { | |
155 | that.fullToolbarEditor.init( function() { | |
156 | that._onInit( callback ); | |
157 | ||
158 | if ( typeof that.onRefresh == 'function' ) { | |
159 | that.onRefresh(); | |
160 | } | |
161 | }, that.editorInstance.config ); | |
162 | } ); | |
163 | ||
164 | return this.mainContainer; | |
165 | }; | |
166 | ||
167 | /** | |
168 | * Called editor initialization finished. | |
169 | * | |
170 | * @param {Function} callback | |
171 | * @param {String} [actualConfig] | |
172 | * @private | |
173 | */ | |
174 | AbstractToolbarModifier.prototype._onInit = function( callback, actualConfig ) { | |
175 | this.originalConfig = this.editorInstance.config; | |
176 | ||
177 | if ( !actualConfig ) { | |
178 | this.actualConfig = JSON.parse( JSON.stringify( this.originalConfig ) ); | |
179 | } else { | |
180 | this.actualConfig = JSON.parse( actualConfig ); | |
181 | } | |
182 | ||
183 | if ( !this.actualConfig.toolbarGroups && !this.actualConfig.toolbar ) { | |
184 | this.actualConfig.toolbarGroups = getDefaultToolbarGroups( this.editorInstance ); | |
185 | } | |
186 | ||
187 | if ( typeof callback === 'function' ) | |
188 | callback( this.mainContainer ); | |
189 | ||
190 | // Here we are going to keep only `name` and `groups` data from editor `toolbar` property. | |
191 | function getDefaultToolbarGroups( editor ) { | |
192 | var toolbarGroups = editor.toolbar, | |
193 | copy = []; | |
194 | ||
195 | var max = toolbarGroups.length; | |
196 | for ( var i = 0; i < max; i++ ) { | |
197 | var group = toolbarGroups[ i ]; | |
198 | ||
199 | if ( typeof group == 'string' ) { | |
200 | copy.push( group ); // separator | |
201 | } else { | |
202 | copy.push( { | |
203 | name: group.name, | |
204 | groups: group.groups ? group.groups.slice() : [] | |
205 | } ); | |
206 | } | |
207 | } | |
208 | ||
209 | return copy; | |
210 | } | |
211 | }; | |
212 | ||
213 | /** | |
214 | * Creates DOM structure of tool. | |
215 | * | |
216 | * @returns {CKEDITOR.dom.element} | |
217 | * @private | |
218 | */ | |
219 | AbstractToolbarModifier.prototype._createModifier = function() { | |
220 | this.mainContainer.addClass( 'unselectable' ); | |
221 | ||
222 | if ( this.modifyContainer ) { | |
223 | this.modifyContainer.remove(); | |
224 | } | |
225 | ||
226 | this.modifyContainer = new CKEDITOR.dom.element( 'div' ); | |
227 | this.modifyContainer.addClass( 'toolbarModifier' ); | |
228 | ||
229 | this.mainContainer.append( this.modifyContainer ); | |
230 | ||
231 | return this.mainContainer; | |
232 | }; | |
233 | ||
234 | /** | |
235 | * Find editable area in CKEditor instance DOM container | |
236 | * | |
237 | * @returns {CKEDITOR.dom.element} | |
238 | */ | |
239 | AbstractToolbarModifier.prototype.getEditableArea = function() { | |
240 | var selector = ( '#' + this.editorInstance.id + '_contents' ); | |
241 | ||
242 | return this.editorInstance.container.findOne( selector ); | |
243 | }; | |
244 | ||
245 | /** | |
246 | * Hide editable area in modified editor by sets its height to 0. | |
247 | * | |
248 | * @private | |
249 | */ | |
250 | AbstractToolbarModifier.prototype._hideEditable = function() { | |
251 | var area = this.getEditableArea(); | |
252 | ||
253 | this.isEditableVisible = false; | |
254 | ||
255 | this.lastEditableAreaHeight = area.getStyle( 'height' ); | |
256 | area.setStyle( 'height', '0' ); | |
257 | }; | |
258 | ||
259 | /** | |
260 | * Show editable area in modified editor. | |
261 | * | |
262 | * @private | |
263 | */ | |
264 | AbstractToolbarModifier.prototype._showEditable = function() { | |
265 | this.isEditableVisible = true; | |
266 | ||
267 | this.getEditableArea().setStyle( 'height', this.lastEditableAreaHeight || 'auto' ); | |
268 | }; | |
269 | ||
270 | /** | |
271 | * Toggle editable area visibility. | |
272 | * | |
273 | * @private | |
274 | */ | |
275 | AbstractToolbarModifier.prototype._toggleEditable = function() { | |
276 | if ( this.isEditableVisible ) | |
277 | this._hideEditable(); | |
278 | else | |
279 | this._showEditable(); | |
280 | }; | |
281 | ||
282 | /** | |
283 | * Usually called when configuration changes. | |
284 | * | |
285 | * @private | |
286 | */ | |
287 | AbstractToolbarModifier.prototype._refreshEditor = function() { | |
288 | var that = this, | |
289 | status = this.editorInstance.status; | |
290 | ||
291 | // Wait for ready only once. | |
292 | if ( this.waitForReady ) | |
293 | return; | |
294 | ||
295 | // Not ready. | |
296 | if ( status == 'unloaded' || status == 'loaded' ) { | |
297 | this.waitForReady = true; | |
298 | ||
299 | this.editorInstance.once( 'instanceReady', function() { | |
300 | refresh(); | |
301 | }, this ); | |
302 | // Ready or destroyed. | |
303 | } else { | |
304 | refresh(); | |
305 | } | |
306 | ||
307 | function refresh() { | |
308 | that.editorInstance.destroy(); | |
309 | that._createEditor( true, that.getActualConfig() ); | |
310 | that.waitForReady = false; | |
311 | } | |
312 | }; | |
313 | ||
314 | /** | |
315 | * Creates editor that can be used to present the toolbar configuration. | |
316 | * | |
317 | * @private | |
318 | */ | |
319 | AbstractToolbarModifier.prototype._createEditor = function( doRefresh, configOverrides ) { | |
320 | var that = this; | |
321 | ||
322 | this.editorInstance = CKEDITOR.replace( this.editorId ); | |
323 | ||
324 | this.editorInstance.on( 'configLoaded', function() { | |
325 | var config = that.editorInstance.config; | |
326 | ||
327 | if ( configOverrides ) { | |
328 | CKEDITOR.tools.extend( config, configOverrides, true ); | |
329 | } | |
330 | ||
331 | AbstractToolbarModifier.extendPluginsConfig( config ); | |
332 | } ); | |
333 | ||
334 | // Prevent creating any other space than the top one. | |
335 | this.editorInstance.on( 'uiSpace', function( evt ) { | |
336 | if ( evt.data.space != 'top' ) { | |
337 | evt.stop(); | |
338 | } | |
339 | }, null, null, -999 ); | |
340 | ||
341 | this.editorInstance.once( 'loaded', function() { | |
342 | var btns = that.editorInstance.ui.instances; | |
343 | ||
344 | for ( var i in btns ) { | |
345 | if ( btns[ i ] ) { | |
346 | btns[ i ].click = empty; | |
347 | btns[ i ].onClick = empty; | |
348 | } | |
349 | } | |
350 | ||
351 | if ( !that.isEditableVisible ) { | |
352 | that._hideEditable(); | |
353 | } | |
354 | ||
355 | if ( that.currentActive && that.currentActive.name ) { | |
356 | that._highlightGroup( that.currentActive.name ); | |
357 | } | |
358 | ||
359 | if ( that.hidden ) { | |
360 | that.hideUI(); | |
361 | } else { | |
362 | that.showUI(); | |
363 | } | |
364 | ||
365 | if ( doRefresh && ( typeof that.onRefresh === 'function' ) ) { | |
366 | that.onRefresh(); | |
367 | } | |
368 | } ); | |
369 | ||
370 | function empty() {} | |
371 | }; | |
372 | ||
373 | /** | |
374 | * Always returns copy of config. | |
375 | * | |
376 | * @returns {Object} | |
377 | */ | |
378 | AbstractToolbarModifier.prototype.getActualConfig = function() { | |
379 | return JSON.parse( JSON.stringify( this.actualConfig ) ); | |
380 | }; | |
381 | ||
382 | /** | |
383 | * Creates toolbar in tool. | |
384 | * | |
385 | * @private | |
386 | */ | |
387 | AbstractToolbarModifier.prototype._createToolbar = function() { | |
388 | if ( !this.toolbarButtons.length ) { | |
389 | return; | |
390 | } | |
391 | ||
392 | this.toolbarContainer = new CKEDITOR.dom.element( 'div' ); | |
393 | this.toolbarContainer.addClass( 'toolbar' ); | |
394 | ||
395 | var max = this.toolbarButtons.length; | |
396 | for ( var i = 0; i < max; i += 1 ) { | |
397 | this._createToolbarBtn( this.toolbarButtons[ i ] ); | |
398 | } | |
399 | }; | |
400 | ||
401 | /** | |
402 | * Create toolbar button and add it to toolbar container | |
403 | * | |
404 | * @param {Object} cfg | |
405 | * @returns {CKEDITOR.dom.element} | |
406 | * @private | |
407 | */ | |
408 | AbstractToolbarModifier.prototype._createToolbarBtn = function( cfg ) { | |
409 | var btnText = ( typeof cfg.text === 'string' ? cfg.text : cfg.text.inactive ), | |
410 | btn = ToolbarConfigurator.FullToolbarEditor.createButton( btnText, cfg.cssClass ); | |
411 | ||
412 | this.toolbarContainer.append( btn ); | |
413 | btn.data( 'group', cfg.group ); | |
414 | btn.addClass( cfg.position ); | |
415 | btn.on( 'click', function() { | |
416 | cfg.clickCallback.call( this, btn, cfg ); | |
417 | }, this ); | |
418 | ||
419 | return btn; | |
420 | }; | |
421 | ||
422 | /** | |
423 | * @private | |
424 | * @param {Object} config | |
425 | */ | |
426 | AbstractToolbarModifier.prototype._fixGroups = function( config ) { | |
427 | var groups = config.toolbarGroups || []; | |
428 | ||
429 | var max = groups.length; | |
430 | for ( var i = 0; i < max; i += 1 ) { | |
431 | var currentGroup = groups[ i ]; | |
432 | ||
433 | // separator, in config, is in raw format | |
434 | // need to make it more sophisticated to keep unique id | |
435 | // for each one | |
436 | if ( currentGroup == '/' ) { | |
437 | currentGroup = groups[ i ] = {}; | |
438 | currentGroup.type = 'separator'; | |
439 | currentGroup.name = ( 'separator' + CKEDITOR.tools.getNextNumber() ); | |
440 | continue; | |
441 | } | |
442 | ||
443 | // sometimes subgroups are not set (basic package), so need to | |
444 | // create them artifically | |
445 | currentGroup.groups = currentGroup.groups || []; | |
446 | ||
447 | // when there is no subgroup with same name like its parent name | |
448 | // then it have to be added artificially | |
449 | // in order to maintain consistency between user interface and config | |
450 | if ( CKEDITOR.tools.indexOf( currentGroup.groups, currentGroup.name ) == -1 ) { | |
451 | this.editorInstance.ui.addToolbarGroup( currentGroup.name, currentGroup.groups[ currentGroup.groups.length - 1 ], currentGroup.name ); | |
452 | currentGroup.groups.push( currentGroup.name ); | |
453 | } | |
454 | ||
455 | this._fixSubgroups( currentGroup ); | |
456 | } | |
457 | }; | |
458 | ||
459 | /** | |
460 | * Transform subgroup string to object literal | |
461 | * with keys: {String} name and {Number} totalBtns | |
462 | * Please note: this method modify Object provided in first argument | |
463 | * | |
464 | * input: | |
465 | * [ | |
466 | * { groups: [ 'nameOne', 'nameTwo' ] } | |
467 | * ] | |
468 | * | |
469 | * output: | |
470 | * [ | |
471 | * { groups: [ { name: 'nameOne', totalBtns: 3 }, { name: 'nameTwo', totalBtns: 5 } ] } | |
472 | * ] | |
473 | * | |
474 | * @param {Object} group | |
475 | * @private | |
476 | */ | |
477 | AbstractToolbarModifier.prototype._fixSubgroups = function( group ) { | |
478 | var subGroups = group.groups; | |
479 | ||
480 | var max = subGroups.length; | |
481 | for ( var i = 0; i < max; i += 1 ) { | |
482 | var subgroupName = subGroups[ i ]; | |
483 | ||
484 | subGroups[ i ] = { | |
485 | name: subgroupName, | |
486 | totalBtns: ToolbarConfigurator.ToolbarModifier.getTotalSubGroupButtonsNumber( subgroupName, this.fullToolbarEditor ) | |
487 | }; | |
488 | } | |
489 | }; | |
490 | ||
491 | /** | |
492 | * Same as JSON.stringify method but returned string is in one line | |
493 | * | |
494 | * @param {Object} json | |
495 | * @param {Object} opts | |
496 | * @param {Boolean} opts.addSpaces | |
497 | * @param {Boolean} opts.noQuotesOnKey | |
498 | * @param {Boolean} opts.singleQuotes | |
499 | * @returns {Object} | |
500 | */ | |
501 | AbstractToolbarModifier.stringifyJSONintoOneLine = function( json, opts ) { | |
502 | opts = opts || {}; | |
503 | var stringJSON = JSON.stringify( json, null, '' ); | |
504 | ||
505 | // IE8 make new line characters | |
506 | stringJSON = stringJSON.replace( /\n/g, '' ); | |
507 | ||
508 | if ( opts.addSpaces ) { | |
509 | stringJSON = stringJSON.replace( /(\{|:|,|\[|\])/g, function( sentence ) { | |
510 | return sentence + ' '; | |
511 | } ); | |
512 | ||
513 | stringJSON = stringJSON.replace( /(\])/g, function( sentence ) { | |
514 | return ' ' + sentence; | |
515 | } ); | |
516 | } | |
517 | ||
518 | if ( opts.noQuotesOnKey ) { | |
519 | stringJSON = stringJSON.replace( /"(\w*)":/g, function( sentence, word ) { | |
520 | return word + ':'; | |
521 | } ); | |
522 | } | |
523 | ||
524 | if ( opts.singleQuotes ) { | |
525 | stringJSON = stringJSON.replace( /\"/g, '\'' ); | |
526 | } | |
527 | ||
528 | return stringJSON; | |
529 | }; | |
530 | ||
531 | /** | |
532 | * Hide toolbar configurator | |
533 | */ | |
534 | AbstractToolbarModifier.prototype.hideUI = function() { | |
535 | this.hidden = true; | |
536 | this.mainContainer.hide(); | |
537 | if ( this.editorInstance.container ) { | |
538 | this.editorInstance.container.hide(); | |
539 | } | |
540 | }; | |
541 | ||
542 | /** | |
543 | * Show toolbar configurator | |
544 | */ | |
545 | AbstractToolbarModifier.prototype.showUI = function() { | |
546 | this.hidden = false; | |
547 | this.mainContainer.show(); | |
548 | if ( this.editorInstance.container ) { | |
549 | this.editorInstance.container.show(); | |
550 | } | |
551 | }; | |
552 | ||
553 | ||
554 | /** | |
555 | * Extends plugins setttings in the specified config with settings useful for | |
556 | * the toolbar configurator. | |
557 | * | |
558 | * @static | |
559 | */ | |
560 | AbstractToolbarModifier.extendPluginsConfig = function( config ) { | |
561 | var extraPlugins = config.extraPlugins; | |
562 | ||
563 | // Enable the special, lightweight area to replace wysiwygarea. | |
564 | config.extraPlugins = ( extraPlugins ? extraPlugins + ',' : '' ) + 'toolbarconfiguratorarea'; | |
565 | }; | |
566 | } )(); |