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