initial commit
[namibia] / public / scripts / ckeditor / _source / plugins / div / dialogs / div.js
1 /*
2  * Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3  * For licensing, see LICENSE.html or http://ckeditor.com/license
4  */
5
6 (function()
7 {
8
9         /**
10          * Add to collection with DUP examination.
11          * @param {Object} collection
12          * @param {Object} element
13          * @param {Object} database
14          */
15         function addSafely( collection, element, database )
16         {
17                 // 1. IE doesn't support customData on text nodes;
18                 // 2. Text nodes never get chance to appear twice;
19                 if ( !element.is || !element.getCustomData( 'block_processed' ) )
20                 {
21                         element.is && CKEDITOR.dom.element.setMarker( database, element, 'block_processed', true );
22                         collection.push( element );
23                 }
24         }
25
26         function getNonEmptyChildren( element )
27         {
28                 var retval = [];
29                 var children = element.getChildren();
30                 for ( var i = 0 ; i < children.count() ; i++ )
31                 {
32                         var child = children.getItem( i );
33                         if ( ! ( child.type === CKEDITOR.NODE_TEXT
34                                 && ( /^[ \t\n\r]+$/ ).test( child.getText() ) ) )
35                                 retval.push( child );
36                 }
37                 return retval;
38         }
39
40
41         /**
42          * Dialog reused by both 'creatediv' and 'editdiv' commands.
43          * @param {Object} editor
44          * @param {String} command      The command name which indicate what the current command is.
45          */
46         function divDialog( editor, command )
47         {
48                 // Definition of elements at which div operation should stopped.
49                 var divLimitDefinition = ( function(){
50
51                         // Customzie from specialize blockLimit elements
52                         var definition = CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$blockLimit );
53
54                         // Exclude 'div' itself.
55                         delete definition.div;
56
57                         // Exclude 'td' and 'th' when 'wrapping table'
58                         if ( editor.config.div_wrapTable )
59                         {
60                                 delete definition.td;
61                                 delete definition.th;
62                         }
63                         return definition;
64                 })();
65
66                 // DTD of 'div' element
67                 var dtd = CKEDITOR.dtd.div;
68
69                 /**
70                  * Get the first div limit element on the element's path.
71                  * @param {Object} element
72                  */
73                 function getDivLimitElement( element )
74                 {
75                         var pathElements = new CKEDITOR.dom.elementPath( element ).elements;
76                         var divLimit;
77                         for ( var i = 0; i < pathElements.length ; i++ )
78                         {
79                                 if ( pathElements[ i ].getName() in divLimitDefinition )
80                                 {
81                                         divLimit = pathElements[ i ];
82                                         break;
83                                 }
84                         }
85                         return divLimit;
86                 }
87
88                 /**
89                  * Init all fields' setup/commit function.
90                  * @memberof divDialog
91                  */
92                 function setupFields()
93                 {
94                         this.foreach( function( field )
95                         {
96                                 // Exclude layout container elements
97                                 if ( /^(?!vbox|hbox)/.test( field.type ) )
98                                 {
99                                         if ( !field.setup )
100                                         {
101                                                 // Read the dialog fields values from the specified
102                                                 // element attributes.
103                                                 field.setup = function( element )
104                                                 {
105                                                         field.setValue( element.getAttribute( field.id ) || '' );
106                                                 };
107                                         }
108                                         if ( !field.commit )
109                                         {
110                                                 // Set element attributes assigned by the dialog
111                                                 // fields.
112                                                 field.commit = function( element )
113                                                 {
114                                                         var fieldValue = this.getValue();
115                                                         // ignore default element attribute values
116                                                         if ( 'dir' == field.id && element.getComputedStyle( 'direction' ) == fieldValue )
117                                                                 return;
118
119                                                         if ( fieldValue )
120                                                                 element.setAttribute( field.id, fieldValue );
121                                                         else
122                                                                 element.removeAttribute( field.id );
123                                                 };
124                                         }
125                                 }
126                         } );
127                 }
128
129                 /**
130                  * Wrapping 'div' element around appropriate blocks among the selected ranges.
131                  * @param {Object} editor
132                  */
133                 function createDiv( editor )
134                 {
135                         // new adding containers OR detected pre-existed containers.
136                         var containers = [];
137                         // node markers store.
138                         var database = {};
139                         // All block level elements which contained by the ranges.
140                         var containedBlocks = [], block;
141
142                         // Get all ranges from the selection.
143                         var selection = editor.document.getSelection(),
144                                 ranges = selection.getRanges();
145                         var bookmarks = selection.createBookmarks();
146                         var i, iterator;
147
148                         // Calcualte a default block tag if we need to create blocks.
149                         var blockTag = editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p';
150
151                         // collect all included elements from dom-iterator
152                         for ( i = 0 ; i < ranges.length ; i++ )
153                         {
154                                 iterator = ranges[ i ].createIterator();
155                                 while ( ( block = iterator.getNextParagraph() ) )
156                                 {
157                                         // include contents of blockLimit elements.
158                                         if ( block.getName() in divLimitDefinition )
159                                         {
160                                                 var j, childNodes = block.getChildren();
161                                                 for ( j = 0 ; j < childNodes.count() ; j++ )
162                                                         addSafely( containedBlocks, childNodes.getItem( j ) , database );
163                                         }
164                                         else
165                                         {
166                                                 // Bypass dtd disallowed elements.
167                                                 while ( !dtd[ block.getName() ] && block.getName() != 'body' )
168                                                         block = block.getParent();
169                                                 addSafely( containedBlocks, block, database );
170                                         }
171                                 }
172                         }
173
174                         CKEDITOR.dom.element.clearAllMarkers( database );
175
176                         var blockGroups = groupByDivLimit( containedBlocks );
177                         var ancestor, blockEl, divElement;
178
179                         for ( i = 0 ; i < blockGroups.length ; i++ )
180                         {
181                                 var currentNode = blockGroups[ i ][ 0 ];
182
183                                 // Calculate the common parent node of all contained elements.
184                                 ancestor = currentNode.getParent();
185                                 for ( j = 1 ; j < blockGroups[ i ].length; j++ )
186                                         ancestor = ancestor.getCommonAncestor( blockGroups[ i ][ j ] );
187
188                                 divElement = new CKEDITOR.dom.element( 'div', editor.document );
189
190                                 // Normalize the blocks in each group to a common parent.
191                                 for ( j = 0; j < blockGroups[ i ].length ; j++ )
192                                 {
193                                         currentNode = blockGroups[ i ][ j ];
194
195                                         while ( !currentNode.getParent().equals( ancestor ) )
196                                                 currentNode = currentNode.getParent();
197
198                                         // This could introduce some duplicated elements in array.
199                                         blockGroups[ i ][ j ] = currentNode;
200                                 }
201
202                                 // Wrapped blocks counting
203                                 var fixedBlock = null;
204                                 for ( j = 0 ; j < blockGroups[ i ].length ; j++ )
205                                 {
206                                         currentNode = blockGroups[ i ][ j ];
207
208                                         // Avoid DUP elements introduced by grouping.
209                                         if ( !( currentNode.getCustomData && currentNode.getCustomData( 'block_processed' ) ) )
210                                         {
211                                                 currentNode.is && CKEDITOR.dom.element.setMarker( database, currentNode, 'block_processed', true );
212
213                                                 // Establish new container, wrapping all elements in this group.
214                                                 if ( !j )
215                                                         divElement.insertBefore( currentNode );
216
217                                                 divElement.append( currentNode );
218                                         }
219                                 }
220
221                                 CKEDITOR.dom.element.clearAllMarkers( database );
222                                 containers.push( divElement );
223                         }
224
225                         selection.selectBookmarks( bookmarks );
226                         return containers;
227                 }
228
229                 function getDiv( editor )
230                 {
231                         var path = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() ),
232                                 blockLimit = path.blockLimit,
233                                 div = blockLimit && blockLimit.getAscendant( 'div', true );
234                         return div;
235                 }
236                 /**
237                  * Divide a set of nodes to different groups by their path's blocklimit element.
238                  * Note: the specified nodes should be in source order naturally, which mean they are supposed to producea by following class:
239                  *  * CKEDITOR.dom.range.Iterator
240                  *  * CKEDITOR.dom.domWalker
241                  *  @return {Array []} the grouped nodes
242                  */
243                 function groupByDivLimit( nodes )
244                 {
245                         var groups = [],
246                                 lastDivLimit = null,
247                                 path, block;
248                         for ( var i = 0 ; i < nodes.length ; i++ )
249                         {
250                                 block = nodes[i];
251                                 var limit = getDivLimitElement( block );
252                                 if ( !limit.equals( lastDivLimit ) )
253                                 {
254                                         lastDivLimit = limit ;
255                                         groups.push( [] ) ;
256                                 }
257                                 groups[ groups.length - 1 ].push( block ) ;
258                         }
259                         return groups;
260                 }
261
262                 // Synchronous field values to other impacted fields is required, e.g. div styles
263                 // change should also alter inline-style text.
264                 function commitInternally( targetFields )
265                 {
266                         var dialog = this.getDialog(),
267                                  element = dialog._element && dialog._element.clone()
268                                                  || new CKEDITOR.dom.element( 'div', editor.document );
269
270                         // Commit this field and broadcast to target fields.
271                         this.commit( element, true );
272
273                         targetFields = [].concat( targetFields );
274                         var length = targetFields.length, field;
275                         for ( var i = 0; i < length; i++ )
276                         {
277                                 field = dialog.getContentElement.apply( dialog, targetFields[ i ].split( ':' ) );
278                                 field && field.setup && field.setup( element, true );
279                         }
280                 }
281
282
283                 // Registered 'CKEDITOR.style' instances.
284                 var styles = {} ;
285                 /**
286                  * Hold a collection of created block container elements.
287                  */
288                 var containers = [];
289                 /**
290                  * @type divDialog
291                  */
292                 return {
293                         title : editor.lang.div.title,
294                         minWidth : 400,
295                         minHeight : 165,
296                         contents :
297                         [
298                         {
299                                 id :'info',
300                                 label :editor.lang.common.generalTab,
301                                 title :editor.lang.common.generalTab,
302                                 elements :
303                                 [
304                                         {
305                                                 type :'hbox',
306                                                 widths : [ '50%', '50%' ],
307                                                 children :
308                                                 [
309                                                         {
310                                                                 id :'elementStyle',
311                                                                 type :'select',
312                                                                 style :'width: 100%;',
313                                                                 label :editor.lang.div.styleSelectLabel,
314                                                                 'default' : '',
315                                                                 // Options are loaded dynamically.
316                                                                 items :
317                                                                 [
318                                                                         [ editor.lang.common.notSet , '' ]
319                                                                 ],
320                                                                 onChange : function()
321                                                                 {
322                                                                         commitInternally.call( this, [ 'info:class', 'advanced:dir', 'advanced:style' ] );
323                                                                 },
324                                                                 setup : function( element )
325                                                                 {
326                                                                         for ( var name in styles )
327                                                                                 styles[ name ].checkElementRemovable( element, true ) && this.setValue( name );
328                                                                 },
329                                                                 commit: function( element )
330                                                                 {
331                                                                         var styleName;
332                                                                         if ( ( styleName = this.getValue() ) )
333                                                                         {
334                                                                                 var style = styles[ styleName ];
335                                                                                 var customData = element.getCustomData( 'elementStyle' ) || '';
336
337                                                                                 style.applyToObject( element );
338                                                                                 element.setCustomData( 'elementStyle', customData + style._.definition.attributes.style );
339                                                                         }
340                                                                 }
341                                                         },
342                                                         {
343                                                                 id :'class',
344                                                                 type :'text',
345                                                                 label :editor.lang.common.cssClass,
346                                                                 'default' : ''
347                                                         }
348                                                 ]
349                                         }
350                                 ]
351                         },
352                         {
353                                         id :'advanced',
354                                         label :editor.lang.common.advancedTab,
355                                         title :editor.lang.common.advancedTab,
356                                         elements :
357                                         [
358                                         {
359                                                 type :'vbox',
360                                                 padding :1,
361                                                 children :
362                                                 [
363                                                         {
364                                                                 type :'hbox',
365                                                                 widths : [ '50%', '50%' ],
366                                                                 children :
367                                                                 [
368                                                                         {
369                                                                                 type :'text',
370                                                                                 id :'id',
371                                                                                 label :editor.lang.common.id,
372                                                                                 'default' : ''
373                                                                         },
374                                                                         {
375                                                                                 type :'text',
376                                                                                 id :'lang',
377                                                                                 label :editor.lang.link.langCode,
378                                                                                 'default' : ''
379                                                                         }
380                                                                 ]
381                                                         },
382                                                         {
383                                                                 type :'hbox',
384                                                                 children :
385                                                                 [
386                                                                                 {
387                                                                                         type :'text',
388                                                                                         id :'style',
389                                                                                         style :'width: 100%;',
390                                                                                         label :editor.lang.common.cssStyle,
391                                                                                         'default' : '',
392                                                                                         commit : function( element )
393                                                                                         {
394                                                                                                 // Merge with 'elementStyle', which is of higher priority.
395                                                                                                 var merged = this.getValue() + ( element.getCustomData( 'elementStyle' ) || '' );
396                                                                                                 element.setAttribute( 'style', merged );
397                                                                                         }
398                                                                                 }
399                                                                 ]
400                                                         },
401                                                         {
402                                                                 type :'hbox',
403                                                                 children :
404                                                                 [
405                                                                                 {
406                                                                                         type :'text',
407                                                                                         id :'title',
408                                                                                         style :'width: 100%;',
409                                                                                         label :editor.lang.common.advisoryTitle,
410                                                                                         'default' : ''
411                                                                                 }
412                                                                 ]
413                                                         },
414                                                         {
415                                                                 type :'select',
416                                                                 id :'dir',
417                                                                 style :'width: 100%;',
418                                                                 label :editor.lang.common.langDir,
419                                                                 'default' : '',
420                                                                 items :
421                                                                 [
422                                                                         [ editor.lang.common.notSet , '' ],
423                                                                         [
424                                                                                 editor.lang.common.langDirLtr,
425                                                                                 'ltr'
426                                                                         ],
427                                                                         [
428                                                                                 editor.lang.common.langDirRtl,
429                                                                                 'rtl'
430                                                                         ]
431                                                                 ]
432                                                         }
433                                                 ]
434                                         }
435                                         ]
436                                 }
437                         ],
438                         onLoad : function()
439                         {
440                                 setupFields.call( this );
441
442                                 // Preparing for the 'elementStyle' field.
443                                 var dialog = this,
444                                          stylesField = this.getContentElement( 'info', 'elementStyle' );
445
446                                  // Reuse the 'stylescombo' plugin's styles definition.
447                                 editor.getStylesSet( function( stylesDefinitions )
448                                 {
449                                         var styleName;
450
451                                         if ( stylesDefinitions )
452                                         {
453                                                 // Digg only those styles that apply to 'div'.
454                                                 for ( var i = 0 ; i < stylesDefinitions.length ; i++ )
455                                                 {
456                                                         var styleDefinition = stylesDefinitions[ i ];
457                                                         if ( styleDefinition.element && styleDefinition.element == 'div' )
458                                                         {
459                                                                 styleName = styleDefinition.name;
460                                                                 styles[ styleName ] = new CKEDITOR.style( styleDefinition );
461
462                                                                 // Populate the styles field options with style name.
463                                                                 stylesField.items.push( [ styleName, styleName ] );
464                                                                 stylesField.add( styleName, styleName );
465                                                         }
466                                                 }
467                                         }
468
469                                         // We should disable the content element
470                                         // it if no options are available at all.
471                                         stylesField[ stylesField.items.length > 1 ? 'enable' : 'disable' ]();
472
473                                         // Now setup the field value manually.
474                                         setTimeout( function() { stylesField.setup( dialog._element ); }, 0 );
475                                 } );
476                         },
477                         onShow : function()
478                         {
479                                 // Whether always create new container regardless of existed
480                                 // ones.
481                                 if ( command == 'editdiv' )
482                                 {
483                                         // Try to discover the containers that already existed in
484                                         // ranges
485                                         var div = getDiv( editor );
486                                         // update dialog field values
487                                         div && this.setupContent( this._element = div );
488                                 }
489                         },
490                         onOk : function()
491                         {
492                                 if ( command == 'editdiv' )
493                                         containers = [ this._element ];
494                                 else
495                                         containers = createDiv( editor, true );
496
497                                 // Update elements attributes
498                                 var size = containers.length;
499                                 for ( var i = 0; i < size; i++ )
500                                 {
501                                         this.commitContent( containers[ i ] );
502
503                                         // Remove empty 'style' attribute.
504                                         !containers[ i ].getAttribute( 'style' ) && containers[ i ].removeAttribute( 'style' );
505                                 }
506
507                                 this.hide();
508                         },
509                         onHide : function()
510                         {
511                                 // Remove style only when editing existing DIV. (#6315)
512                                 if ( command == 'editdiv' )
513                                         this._element.removeCustomData( 'elementStyle' );
514                                 delete this._element;
515                         }
516                 };
517         }
518
519         CKEDITOR.dialog.add( 'creatediv', function( editor )
520                 {
521                         return divDialog( editor, 'creatediv' );
522                 } );
523         CKEDITOR.dialog.add( 'editdiv', function( editor )
524                 {
525                         return divDialog( editor, 'editdiv' );
526                 } );
527 } )();
528
529 /*
530  * @name CKEDITOR.config.div_wrapTable
531  * Whether to wrap the whole table instead of indivisual cells when created 'div' in table cell.
532  * @type Boolean
533  * @default false
534  * @example config.div_wrapTable = true;
535  */