]>
git.immae.eu Git - perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git/blob - sources/samples/toolbarconfigurator/js/toolbartextmodifier.js
1 /* global CodeMirror, ToolbarConfigurator */
6 var AbstractToolbarModifier
= ToolbarConfigurator
.AbstractToolbarModifier
,
7 FullToolbarEditor
= ToolbarConfigurator
.FullToolbarEditor
;
10 * @class ToolbarConfigurator.ToolbarTextModifier
11 * @param {String} editorId An id of modified editor
12 * @extends AbstractToolbarModifier
15 function ToolbarTextModifier( editorId
) {
16 AbstractToolbarModifier
.call( this, editorId
);
18 this.codeContainer
= null;
19 this.hintContainer
= null;
23 ToolbarConfigurator
.ToolbarTextModifier
= ToolbarTextModifier
;
25 ToolbarTextModifier
.prototype = Object
.create( AbstractToolbarModifier
.prototype );
28 * @param {Function} callback
29 * @param {String} [config]
32 ToolbarTextModifier
.prototype._onInit = function( callback
, config
) {
33 AbstractToolbarModifier
.prototype._onInit
.call( this, undefined, config
);
35 this._createModifier( config
? this.actualConfig : undefined );
37 if ( typeof callback
=== 'function' )
38 callback( this.mainContainer
);
42 * Creates HTML main container of modifier.
45 * @returns {CKEDITOR.dom.element}
48 ToolbarTextModifier
.prototype._createModifier = function( cfg
) {
51 this._createToolbar();
53 if ( this.toolbarContainer
) {
54 this.mainContainer
.append( this.toolbarContainer
);
57 AbstractToolbarModifier
.prototype._createModifier
.call( this );
59 this._setupActualConfig( cfg
);
61 var toolbarCfg
= this.actualConfig
.toolbar
,
64 if ( CKEDITOR
.tools
.isArray( toolbarCfg
) ) {
65 var stringifiedToolbar
= '[\n\t\t' + FullToolbarEditor
.map( toolbarCfg
, function( json
) {
66 return AbstractToolbarModifier
.stringifyJSONintoOneLine( json
, {
71 } ).join( ',\n\t\t' ) + '\n\t]';
73 cfgValue
= '\tconfig.toolbar = ' + stringifiedToolbar
+ ';';
75 cfgValue
= 'config.toolbar = [];';
79 'CKEDITOR.editorConfig = function( config ) {\n',
85 var data
= setupData( cm
);
87 if ( data
.charsBetween
=== null ) {
91 var unused
= that
.getUnusedButtonsArray( that
.actualConfig
.toolbar
, true, data
.charsBetween
),
93 from = CodeMirror
.Pos( to
.line
, ( to
.ch
- ( data
.charsBetween
.length
) ) ),
94 token
= cm
.getTokenAt( to
),
95 prevToken
= cm
.getTokenAt( { line: to
.line
, ch: token
.start
} );
97 // determine that we are at beginning of group,
98 // so first key is "name"
99 if ( prevToken
.string
=== '{' )
102 // preventing close with special character and move cursor forward
103 // when no autocomplete
104 if ( unused
.length
=== 0 )
107 return new HintData( from, to
, unused
);
110 function HintData( from, to
, list
) {
117 function setupData( cm
, character
) {
120 result
.cur
= cm
.getCursor();
121 result
.tok
= cm
.getTokenAt( result
.cur
);
123 result
[ 'char' ] = character
|| result
.tok
.string
.charAt( result
.tok
.string
.length
- 1 );
125 // Getting string between begin of line and cursor.
126 var curLineTillCur
= cm
.getRange( CodeMirror
.Pos( result
.cur
.line
, 0 ), result
.cur
);
129 var currLineTillCurReversed
= curLineTillCur
.split( '' ).reverse().join( '' );
131 // Removing proper string definitions :
133 // R' ,'odeR' ,'odnU' [ :smeti{
137 currLineTillCurReversed
= currLineTillCurReversed
.replace( /(['|"]\w*['|"])/g, '' );
139 // Matching letters till ' or " character and end string char.
142 result
.charsBetween
= currLineTillCurReversed
.match( /(^\w*)(['|"])/ );
144 if ( result
.charsBetween
) {
145 result
.endChar
= result
.charsBetween
[ 2 ];
147 // And reverse string (bring to original state).
148 result
.charsBetween
= result
.charsBetween
[ 1 ].split( '' ).reverse().join( '' );
154 function complete( cm
) {
155 setTimeout( function() {
156 if ( !cm
.state
.completionActive
) {
157 CodeMirror
.showHint( cm
, hint
, {
158 hintsClass: 'toolbar-modifier',
159 completeSingle: false
164 return CodeMirror
.Pass
;
167 var codeMirrorWrapper
= new CKEDITOR
.dom
.element( 'div' );
168 codeMirrorWrapper
.addClass( 'codemirror-wrapper' );
169 this.modifyContainer
.append( codeMirrorWrapper
);
170 this.codeContainer
= CodeMirror( codeMirrorWrapper
.$, {
171 mode: { name: 'javascript', json: true },
172 // For some reason (most likely CM's bug) gutter breaks CM's height.
173 // Refreshing CM does not help.
176 // Trick to make CM autogrow. http://codemirror.net/demo/resize.html
177 viewportMargin: Infinity
,
180 indentWithTabs: true,
191 'Shift-Tab': 'indentLess'
195 this.codeContainer
.on( 'endCompletion', function( cm
, completionData
) {
196 var data
= setupData( cm
);
198 // preventing close with special character and move cursor forward
199 // when no autocomplete
200 if ( completionData
=== undefined )
203 cm
.replaceSelection( data
.endChar
);
206 this.codeContainer
.on( 'change', function() {
207 var value
= that
.codeContainer
.getValue();
209 value
= that
._evaluateValue( value
);
211 if ( value
!== null ) {
212 that
.actualConfig
.toolbar
= ( value
.toolbar
? value
.toolbar : that
.actualConfig
.toolbar
);
214 that
._fillHintByUnusedElements();
215 that
._refreshEditor();
217 that
.mainContainer
.removeClass( 'invalid' );
219 that
.mainContainer
.addClass( 'invalid' );
223 this.hintContainer
= new CKEDITOR
.dom
.element( 'div' );
224 this.hintContainer
.addClass( 'toolbarModifier-hints' );
226 this._fillHintByUnusedElements();
227 this.hintContainer
.insertBefore( codeMirrorWrapper
);
231 * Create DOM string and set to hint container,
232 * show proper information when no unused element left.
236 ToolbarTextModifier
.prototype._fillHintByUnusedElements = function() {
237 var unused
= this.getUnusedButtonsArray( this.actualConfig
.toolbar
, true );
238 unused
= this.groupButtonNamesByGroup( unused
);
240 var unusedElements
= FullToolbarEditor
.map( unused
, function( elem
) {
241 var buttonsList
= FullToolbarEditor
.map( elem
.buttons
, function( buttonName
) {
242 return '<code>' + buttonName
+ '</code> ';
247 '<code>', elem
.name
, '</code>',
256 '<dt class="list-header">Toolbar group</dt>',
257 '<dd class="list-header">Unused items</dd>'
260 var header
= '<h3>Unused toolbar items</h3>';
262 if ( !unused
.length
) {
263 listHeader
= '<p>All items are in use.</p>';
266 this.codeContainer
.refresh();
268 this.hintContainer
.setHtml( header
+ '<dl>' + listHeader
+ unusedElements
+ '</dl>' );
272 * @param {String} buttonName
275 ToolbarTextModifier
.prototype.getToolbarGroupByButtonName = function( buttonName
) {
276 var buttonNames
= this.fullToolbarEditor
.buttonNamesByGroup
;
278 for ( var groupName
in buttonNames
) {
279 var buttons
= buttonNames
[ groupName
];
281 var i
= buttons
.length
;
283 if ( buttonName
=== buttons
[ i
] ) {
294 * Filter all available toolbar elements by array of elements provided in first argument.
295 * Returns elements which are not used.
297 * @param {Object} toolbar
298 * @param {Boolean} [sorted=false]
299 * @param {String} prefix
302 ToolbarTextModifier
.prototype.getUnusedButtonsArray = function( toolbar
, sorted
, prefix
) {
303 sorted
= ( sorted
=== true ? true : false );
304 var providedElements
= ToolbarTextModifier
.mapToolbarCfgToElementsList( toolbar
),
305 allElements
= Object
.keys( this.fullToolbarEditor
.editorInstance
.ui
.items
);
307 // get rid of "-" elements
308 allElements
= FullToolbarEditor
.filter( allElements
, function( elem
) {
309 var isSeparator
= ( elem
=== '-' ),
310 matchPrefix
= ( prefix
=== undefined || elem
.toLowerCase().indexOf( prefix
.toLowerCase() ) === 0 );
312 return !isSeparator
&& matchPrefix
;
315 var elementsNotUsed
= FullToolbarEditor
.filter( allElements
, function( elem
) {
316 return CKEDITOR
.tools
.indexOf( providedElements
, elem
) == -1;
320 elementsNotUsed
.sort();
322 return elementsNotUsed
;
327 * @param {Array} buttons
330 ToolbarTextModifier
.prototype.groupButtonNamesByGroup = function( buttons
) {
332 groupedBtns
= JSON
.parse( JSON
.stringify( this.fullToolbarEditor
.buttonNamesByGroup
) );
334 for ( var groupName
in groupedBtns
) {
335 var currGroup
= groupedBtns
[ groupName
];
336 currGroup
= FullToolbarEditor
.filter( currGroup
, function( btnName
) {
337 return CKEDITOR
.tools
.indexOf( buttons
, btnName
) !== -1;
340 if ( currGroup
.length
) {
353 * Map toolbar config value to flat items list.
357 * { name: "basicstyles", items: ["Bold", "Italic"] },
358 * { name: "advancedstyles", items: ["Bold", "Outdent", "Indent"] }
362 * ["Bold", "Italic", "Outdent", "Indent"]
364 * @param {Object} toolbar
367 ToolbarTextModifier
.mapToolbarCfgToElementsList = function( toolbar
) {
370 var max
= toolbar
.length
;
371 for ( var i
= 0; i
< max
; i
+= 1 ) {
372 if ( !toolbar
[ i
] || typeof toolbar
[ i
] === 'string' )
375 elements
= elements
.concat( FullToolbarEditor
.filter( toolbar
[ i
].items
, checker
) );
378 function checker( elem
) {
386 * @param {String} cfg
389 ToolbarTextModifier
.prototype._setupActualConfig = function( cfg
) {
390 cfg
= cfg
|| this.editorInstance
.config
;
392 // if toolbar already exists in config, there is nothing to do
393 if ( CKEDITOR
.tools
.isArray( cfg
.toolbar
) )
396 // if toolbar group not present, we need to pick them from full toolbar instance
397 if ( !cfg
.toolbarGroups
)
398 cfg
.toolbarGroups
= this.fullToolbarEditor
.getFullToolbarGroupsConfig( true );
400 this._fixGroups( cfg
);
402 cfg
.toolbar
= this._mapToolbarGroupsToToolbar( cfg
.toolbarGroups
, this.actualConfig
.removeButtons
);
404 this.actualConfig
.toolbar
= cfg
.toolbar
;
405 this.actualConfig
.removeButtons
= '';
409 * **Please note:** This method modify element provided in first argument.
411 * @param {Array} toolbarGroups
415 ToolbarTextModifier
.prototype._mapToolbarGroupsToToolbar = function( toolbarGroups
, removedBtns
) {
416 removedBtns
= removedBtns
|| this.editorInstance
.config
.removedBtns
;
417 removedBtns
= typeof removedBtns
== 'string' ? removedBtns
.split( ',' ) : [];
419 // from the end, because array indexes may change
420 var i
= toolbarGroups
.length
;
422 var mappedSubgroup
= this._mapToolbarSubgroup( toolbarGroups
[ i
], removedBtns
);
424 if ( toolbarGroups
[ i
].type
=== 'separator' ) {
425 toolbarGroups
[ i
] = '/';
429 // don't want empty groups
430 if ( CKEDITOR
.tools
.isArray( mappedSubgroup
) && mappedSubgroup
.length
=== 0 ) {
431 toolbarGroups
.splice( i
, 1 );
435 if ( typeof mappedSubgroup
== 'string' )
436 toolbarGroups
[ i
] = mappedSubgroup
;
438 toolbarGroups
[ i
] = {
439 name: toolbarGroups
[ i
].name
,
440 items: mappedSubgroup
445 return toolbarGroups
;
450 * @param {String|Object} group
451 * @param {Array} removedBtns
455 ToolbarTextModifier
.prototype._mapToolbarSubgroup = function( group
, removedBtns
) {
457 if ( typeof group
== 'string' )
460 var max
= group
.groups
? group
.groups
.length : 0,
462 for ( var i
= 0; i
< max
; i
+= 1 ) {
463 var currSubgroup
= group
.groups
[ i
];
465 var buttons
= this.fullToolbarEditor
.buttonsByGroup
[ typeof currSubgroup
=== 'string' ? currSubgroup : currSubgroup
.name
] || [];
466 buttons
= this._mapButtonsToButtonsNames( buttons
, removedBtns
);
467 var currTotalBtns
= buttons
.length
;
468 totalBtns
+= currTotalBtns
;
469 result
= result
.concat( buttons
);
475 if ( result
[ result
.length
- 1 ] == '-' )
483 * @param {Array} buttons
484 * @param {Array} removedBtns
488 ToolbarTextModifier
.prototype._mapButtonsToButtonsNames = function( buttons
, removedBtns
) {
489 var i
= buttons
.length
;
491 var currBtn
= buttons
[ i
],
494 if ( typeof currBtn
=== 'string' ) {
495 camelCasedName
= currBtn
;
497 camelCasedName
= this.fullToolbarEditor
.getCamelCasedButtonName( currBtn
.name
);
500 if ( CKEDITOR
.tools
.indexOf( removedBtns
, camelCasedName
) !== -1 ) {
501 buttons
.splice( i
, 1 );
505 buttons
[ i
] = camelCasedName
;
512 * @param {String} val
516 ToolbarTextModifier
.prototype._evaluateValue = function( val
) {
522 var CKEDITOR
= Function( 'var CKEDITOR = {}; ' + val
+ '; return CKEDITOR;' )();
524 CKEDITOR
.editorConfig( config
);
528 // CKEditor does not handle empty arrays in configuration files
530 var i
= parsed
.toolbar
.length
;
532 if ( !parsed
.toolbar
[ i
] ) parsed
.toolbar
.splice( i
, 1 );
542 * @param {Array} toolbar
543 * @returns {{toolbarGroups: Array, removeButtons: string}}
545 ToolbarTextModifier
.prototype.mapToolbarToToolbarGroups = function( toolbar
) {
550 var max
= toolbar
.length
;
551 for ( var i
= 0; i
< max
; i
++ ) {
552 if ( toolbar
[ i
] === '/' ) {
553 toolbarGroups
.push( '/' );
557 var items
= toolbar
[ i
].items
;
559 var toolbarGroup
= {};
560 toolbarGroup
.name
= toolbar
[ i
].name
;
561 toolbarGroup
.groups
= [];
563 var max2
= items
.length
;
564 for ( var j
= 0; j
< max2
; j
++ ) {
565 var item
= items
[ j
];
567 if ( item
=== '-' ) {
571 var groupName
= this.getToolbarGroupByButtonName( item
);
573 var groupIndex
= toolbarGroup
.groups
.indexOf( groupName
);
574 if ( groupIndex
=== -1 ) {
575 toolbarGroup
.groups
.push( groupName
);
578 usedGroups
[ groupName
] = usedGroups
[ groupName
] || {};
580 var buttons
= ( usedGroups
[ groupName
].buttons
= usedGroups
[ groupName
].buttons
|| {} );
582 buttons
[ item
] = buttons
[ item
] || { used: 0, origin: toolbarGroup
.name
};
583 buttons
[ item
].used
++;
586 toolbarGroups
.push( toolbarGroup
);
589 // Handling removed buttons
590 removeButtons
= prepareRemovedButtons( usedGroups
, this.fullToolbarEditor
.buttonNamesByGroup
);
592 function prepareRemovedButtons( usedGroups
, buttonNames
) {
595 for ( var groupName
in usedGroups
) {
596 var group
= usedGroups
[ groupName
];
597 var allButtonsInGroup
= buttonNames
[ groupName
].slice();
599 removed
= removed
.concat( removeStuffFromArray( allButtonsInGroup
, Object
.keys( group
.buttons
) ) );
605 function removeStuffFromArray( array
, stuff
) {
606 array
= array
.slice();
607 var i
= stuff
.length
;
610 var atIndex
= array
.indexOf( stuff
[ i
] );
611 if ( atIndex
!== -1 ) {
612 array
.splice( atIndex
, 1 );
619 return { toolbarGroups: toolbarGroups
, removeButtons: removeButtons
.join( ',' ) };
622 return ToolbarTextModifier
;