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 | */
|
---|