]>
Commit | Line | Data |
---|---|---|
7adcb81e | 1 | /*\r |
3b35bd27 | 2 | * Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.\r |
7adcb81e IB |
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 | |
47 | // a first div limit (#11083).\r | |
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 | |
134 | var currentNode = blockGroups[ i ][ 0 ];\r | |
135 | \r | |
136 | // Calculate the common parent node of all contained elements.\r | |
137 | ancestor = currentNode.getParent();\r | |
138 | for ( j = 1; j < blockGroups[ i ].length; j++ )\r | |
139 | ancestor = ancestor.getCommonAncestor( blockGroups[ i ][ j ] );\r | |
140 | \r | |
141 | divElement = new CKEDITOR.dom.element( 'div', editor.document );\r | |
142 | \r | |
143 | // Normalize the blocks in each group to a common parent.\r | |
144 | for ( j = 0; j < blockGroups[ i ].length; j++ ) {\r | |
145 | currentNode = blockGroups[ i ][ j ];\r | |
146 | \r | |
147 | while ( !currentNode.getParent().equals( ancestor ) )\r | |
148 | currentNode = currentNode.getParent();\r | |
149 | \r | |
150 | // This could introduce some duplicated elements in array.\r | |
151 | blockGroups[ i ][ j ] = currentNode;\r | |
152 | }\r | |
153 | \r | |
154 | // Wrapped blocks counting\r | |
155 | for ( j = 0; j < blockGroups[ i ].length; j++ ) {\r | |
156 | currentNode = blockGroups[ i ][ j ];\r | |
157 | \r | |
158 | // Avoid DUP elements introduced by grouping.\r | |
159 | if ( !( currentNode.getCustomData && currentNode.getCustomData( 'block_processed' ) ) ) {\r | |
160 | currentNode.is && CKEDITOR.dom.element.setMarker( database, currentNode, 'block_processed', true );\r | |
161 | \r | |
162 | // Establish new container, wrapping all elements in this group.\r | |
163 | if ( !j )\r | |
164 | divElement.insertBefore( currentNode );\r | |
165 | \r | |
166 | divElement.append( currentNode );\r | |
167 | }\r | |
168 | }\r | |
169 | \r | |
170 | CKEDITOR.dom.element.clearAllMarkers( database );\r | |
171 | containers.push( divElement );\r | |
172 | }\r | |
173 | \r | |
174 | selection.selectBookmarks( bookmarks );\r | |
175 | return containers;\r | |
176 | }\r | |
177 | \r | |
178 | // Divide a set of nodes to different groups by their path's blocklimit element.\r | |
179 | // Note: the specified nodes should be in source order naturally, which mean they are supposed to producea by following class:\r | |
180 | // * CKEDITOR.dom.range.Iterator\r | |
181 | // * CKEDITOR.dom.domWalker\r | |
182 | // @returns {Array[]} the grouped nodes\r | |
183 | function groupByDivLimit( nodes ) {\r | |
184 | var groups = [],\r | |
185 | lastDivLimit = null,\r | |
186 | block;\r | |
187 | \r | |
188 | for ( var i = 0; i < nodes.length; i++ ) {\r | |
189 | block = nodes[ i ];\r | |
190 | var limit = getDivContainer( block );\r | |
191 | if ( !limit.equals( lastDivLimit ) ) {\r | |
192 | lastDivLimit = limit;\r | |
193 | groups.push( [] );\r | |
194 | }\r | |
195 | groups[ groups.length - 1 ].push( block );\r | |
196 | }\r | |
197 | return groups;\r | |
198 | }\r | |
199 | \r | |
200 | // Synchronous field values to other impacted fields is required, e.g. div styles\r | |
201 | // change should also alter inline-style text.\r | |
202 | function commitInternally( targetFields ) {\r | |
203 | var dialog = this.getDialog(),\r | |
204 | element = dialog._element && dialog._element.clone() || new CKEDITOR.dom.element( 'div', editor.document );\r | |
205 | \r | |
206 | // Commit this field and broadcast to target fields.\r | |
207 | this.commit( element, true );\r | |
208 | \r | |
209 | targetFields = [].concat( targetFields );\r | |
210 | var length = targetFields.length,\r | |
211 | field;\r | |
212 | for ( var i = 0; i < length; i++ ) {\r | |
213 | field = dialog.getContentElement.apply( dialog, targetFields[ i ].split( ':' ) );\r | |
214 | field && field.setup && field.setup( element, true );\r | |
215 | }\r | |
216 | }\r | |
217 | \r | |
218 | \r | |
219 | // Registered 'CKEDITOR.style' instances.\r | |
220 | var styles = {};\r | |
221 | \r | |
222 | // Hold a collection of created block container elements.\r | |
223 | var containers = [];\r | |
224 | \r | |
225 | // @type divDialog\r | |
226 | return {\r | |
227 | title: editor.lang.div.title,\r | |
228 | minWidth: 400,\r | |
229 | minHeight: 165,\r | |
230 | contents: [ {\r | |
231 | id: 'info',\r | |
232 | label: editor.lang.common.generalTab,\r | |
233 | title: editor.lang.common.generalTab,\r | |
234 | elements: [ {\r | |
235 | type: 'hbox',\r | |
236 | widths: [ '50%', '50%' ],\r | |
237 | children: [ {\r | |
238 | id: 'elementStyle',\r | |
239 | type: 'select',\r | |
240 | style: 'width: 100%;',\r | |
241 | label: editor.lang.div.styleSelectLabel,\r | |
242 | 'default': '',\r | |
243 | // Options are loaded dynamically.\r | |
244 | items: [\r | |
245 | [ editor.lang.common.notSet, '' ]\r | |
246 | ],\r | |
247 | onChange: function() {\r | |
248 | commitInternally.call( this, [ 'info:elementStyle', 'info:class', 'advanced:dir', 'advanced:style' ] );\r | |
249 | },\r | |
250 | setup: function( element ) {\r | |
251 | for ( var name in styles )\r | |
252 | styles[ name ].checkElementRemovable( element, true, editor ) && this.setValue( name, 1 );\r | |
253 | },\r | |
254 | commit: function( element ) {\r | |
255 | var styleName;\r | |
256 | if ( ( styleName = this.getValue() ) ) {\r | |
257 | var style = styles[ styleName ];\r | |
258 | style.applyToObject( element, editor );\r | |
259 | }\r | |
260 | else {\r | |
261 | element.removeAttribute( 'style' );\r | |
262 | }\r | |
263 | }\r | |
264 | },\r | |
265 | {\r | |
266 | id: 'class',\r | |
267 | type: 'text',\r | |
268 | requiredContent: 'div(cke-xyz)', // Random text like 'xyz' will check if all are allowed.\r | |
269 | label: editor.lang.common.cssClass,\r | |
270 | 'default': ''\r | |
271 | } ]\r | |
272 | } ]\r | |
273 | },\r | |
274 | {\r | |
275 | id: 'advanced',\r | |
276 | label: editor.lang.common.advancedTab,\r | |
277 | title: editor.lang.common.advancedTab,\r | |
278 | elements: [ {\r | |
279 | type: 'vbox',\r | |
280 | padding: 1,\r | |
281 | children: [ {\r | |
282 | type: 'hbox',\r | |
283 | widths: [ '50%', '50%' ],\r | |
284 | children: [ {\r | |
285 | type: 'text',\r | |
286 | id: 'id',\r | |
287 | requiredContent: 'div[id]',\r | |
288 | label: editor.lang.common.id,\r | |
289 | 'default': ''\r | |
290 | },\r | |
291 | {\r | |
292 | type: 'text',\r | |
293 | id: 'lang',\r | |
294 | requiredContent: 'div[lang]',\r | |
295 | label: editor.lang.common.langCode,\r | |
296 | 'default': ''\r | |
297 | } ]\r | |
298 | },\r | |
299 | {\r | |
300 | type: 'hbox',\r | |
301 | children: [ {\r | |
302 | type: 'text',\r | |
303 | id: 'style',\r | |
304 | requiredContent: 'div{cke-xyz}', // Random text like 'xyz' will check if all are allowed.\r | |
305 | style: 'width: 100%;',\r | |
306 | label: editor.lang.common.cssStyle,\r | |
307 | 'default': '',\r | |
308 | commit: function( element ) {\r | |
309 | element.setAttribute( 'style', this.getValue() );\r | |
310 | }\r | |
311 | } ]\r | |
312 | },\r | |
313 | {\r | |
314 | type: 'hbox',\r | |
315 | children: [ {\r | |
316 | type: 'text',\r | |
317 | id: 'title',\r | |
318 | requiredContent: 'div[title]',\r | |
319 | style: 'width: 100%;',\r | |
320 | label: editor.lang.common.advisoryTitle,\r | |
321 | 'default': ''\r | |
322 | } ]\r | |
323 | },\r | |
324 | {\r | |
325 | type: 'select',\r | |
326 | id: 'dir',\r | |
327 | requiredContent: 'div[dir]',\r | |
328 | style: 'width: 100%;',\r | |
329 | label: editor.lang.common.langDir,\r | |
330 | 'default': '',\r | |
331 | items: [\r | |
332 | [ editor.lang.common.notSet, '' ],\r | |
333 | [ editor.lang.common.langDirLtr, 'ltr' ],\r | |
334 | [ editor.lang.common.langDirRtl, 'rtl' ]\r | |
335 | ]\r | |
336 | } ] }\r | |
337 | ]\r | |
338 | } ],\r | |
339 | onLoad: function() {\r | |
340 | setupFields.call( this );\r | |
341 | \r | |
342 | // Preparing for the 'elementStyle' field.\r | |
343 | var dialog = this,\r | |
344 | stylesField = this.getContentElement( 'info', 'elementStyle' );\r | |
345 | \r | |
346 | // Reuse the 'stylescombo' plugin's styles definition.\r | |
347 | editor.getStylesSet( function( stylesDefinitions ) {\r | |
348 | var styleName, style;\r | |
349 | \r | |
350 | if ( stylesDefinitions ) {\r | |
351 | // Digg only those styles that apply to 'div'.\r | |
352 | for ( var i = 0; i < stylesDefinitions.length; i++ ) {\r | |
353 | var styleDefinition = stylesDefinitions[ i ];\r | |
354 | if ( styleDefinition.element && styleDefinition.element == 'div' ) {\r | |
355 | styleName = styleDefinition.name;\r | |
356 | styles[ styleName ] = style = new CKEDITOR.style( styleDefinition );\r | |
357 | \r | |
358 | if ( editor.filter.check( style ) ) {\r | |
359 | // Populate the styles field options with style name.\r | |
360 | stylesField.items.push( [ styleName, styleName ] );\r | |
361 | stylesField.add( styleName, styleName );\r | |
362 | }\r | |
363 | }\r | |
364 | }\r | |
365 | }\r | |
366 | \r | |
367 | // We should disable the content element\r | |
368 | // it if no options are available at all.\r | |
369 | stylesField[ stylesField.items.length > 1 ? 'enable' : 'disable' ]();\r | |
370 | \r | |
371 | // Now setup the field value manually if dialog was opened on element. (#9689)\r | |
372 | setTimeout( function() {\r | |
373 | dialog._element && stylesField.setup( dialog._element );\r | |
374 | }, 0 );\r | |
375 | } );\r | |
376 | },\r | |
377 | onShow: function() {\r | |
378 | // Whether always create new container regardless of existed\r | |
379 | // ones.\r | |
380 | if ( command == 'editdiv' ) {\r | |
381 | // Try to discover the containers that already existed in\r | |
382 | // ranges\r | |
383 | // update dialog field values\r | |
384 | this.setupContent( this._element = CKEDITOR.plugins.div.getSurroundDiv( editor ) );\r | |
385 | }\r | |
386 | },\r | |
387 | onOk: function() {\r | |
388 | if ( command == 'editdiv' )\r | |
389 | containers = [ this._element ];\r | |
390 | else\r | |
391 | containers = createDiv( editor, true );\r | |
392 | \r | |
393 | // Update elements attributes\r | |
394 | var size = containers.length;\r | |
395 | for ( var i = 0; i < size; i++ ) {\r | |
396 | this.commitContent( containers[ i ] );\r | |
397 | \r | |
398 | // Remove empty 'style' attribute.\r | |
399 | !containers[ i ].getAttribute( 'style' ) && containers[ i ].removeAttribute( 'style' );\r | |
400 | }\r | |
401 | \r | |
402 | this.hide();\r | |
403 | },\r | |
404 | onHide: function() {\r | |
405 | // Remove style only when editing existing DIV. (#6315)\r | |
406 | if ( command == 'editdiv' )\r | |
407 | this._element.removeCustomData( 'elementStyle' );\r | |
408 | delete this._element;\r | |
409 | }\r | |
410 | };\r | |
411 | }\r | |
412 | \r | |
413 | CKEDITOR.dialog.add( 'creatediv', function( editor ) {\r | |
414 | return divDialog( editor, 'creatediv' );\r | |
415 | } );\r | |
416 | \r | |
417 | CKEDITOR.dialog.add( 'editdiv', function( editor ) {\r | |
418 | return divDialog( editor, 'editdiv' );\r | |
419 | } );\r | |
420 | \r | |
421 | } )();\r | |
422 | \r | |
423 | /**\r | |
424 | * Whether to wrap the entire table instead of individual cells when creating a `<div>` in a table cell.\r | |
425 | *\r | |
426 | * config.div_wrapTable = true;\r | |
427 | *\r | |
428 | * @cfg {Boolean} [div_wrapTable=false]\r | |
429 | * @member CKEDITOR.config\r | |
430 | */\r |