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