]>
Commit | Line | Data |
---|---|---|
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 |