]> git.immae.eu Git - perso/Immae/Projets/packagist/ludivine-ckeditor-component.git/blame - sources/plugins/indentlist/plugin.js
Validation initiale
[perso/Immae/Projets/packagist/ludivine-ckeditor-component.git] / sources / plugins / indentlist / plugin.js
CommitLineData
c63493c8
IB
1/**\r
2 * @license 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/**\r
7 * @fileOverview Handles the indentation of lists.\r
8 */\r
9\r
10( function() {\r
11 'use strict';\r
12\r
13 var isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),\r
14 isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ),\r
15 TRISTATE_DISABLED = CKEDITOR.TRISTATE_DISABLED,\r
16 TRISTATE_OFF = CKEDITOR.TRISTATE_OFF;\r
17\r
18 CKEDITOR.plugins.add( 'indentlist', {\r
19 requires: 'indent',\r
20 init: function( editor ) {\r
21 var globalHelpers = CKEDITOR.plugins.indent;\r
22\r
23 // Register commands.\r
24 globalHelpers.registerCommands( editor, {\r
25 indentlist: new commandDefinition( editor, 'indentlist', true ),\r
26 outdentlist: new commandDefinition( editor, 'outdentlist' )\r
27 } );\r
28\r
29 function commandDefinition( editor ) {\r
30 globalHelpers.specificDefinition.apply( this, arguments );\r
31\r
32 // Require ul OR ol list.\r
33 this.requiredContent = [ 'ul', 'ol' ];\r
34\r
35 // Indent and outdent lists with TAB/SHIFT+TAB key. Indenting can\r
36 // be done for any list item that isn't the first child of the parent.\r
37 editor.on( 'key', function( evt ) {\r
38 if ( editor.mode != 'wysiwyg' )\r
39 return;\r
40\r
41 if ( evt.data.keyCode == this.indentKey ) {\r
42 var list = this.getContext( editor.elementPath() );\r
43\r
44 if ( list ) {\r
45 // Don't indent if in first list item of the parent.\r
46 // Outdent, however, can always be done to collapse\r
47 // the list into a paragraph (div).\r
48 if ( this.isIndent && CKEDITOR.plugins.indentList.firstItemInPath( this.context, editor.elementPath(), list ) )\r
49 return;\r
50\r
51 // Exec related global indentation command. Global\r
52 // commands take care of bookmarks and selection,\r
53 // so it's much easier to use them instead of\r
54 // content-specific commands.\r
55 editor.execCommand( this.relatedGlobal );\r
56\r
57 // Cancel the key event so editor doesn't lose focus.\r
58 evt.cancel();\r
59 }\r
60 }\r
61 }, this );\r
62\r
63 // There are two different jobs for this plugin:\r
64 //\r
65 // * Indent job (priority=10), before indentblock.\r
66 //\r
67 // This job is before indentblock because, if this plugin is\r
68 // loaded it has higher priority over indentblock. It means that,\r
69 // if possible, nesting is performed, and then block manipulation,\r
70 // if necessary.\r
71 //\r
72 // * Outdent job (priority=30), after outdentblock.\r
73 //\r
74 // This job got to be after outdentblock because in some cases\r
75 // (margin, config#indentClass on list) outdent must be done on\r
76 // block-level.\r
77\r
78 this.jobs[ this.isIndent ? 10 : 30 ] = {\r
79 refresh: this.isIndent ?\r
80 function( editor, path ) {\r
81 var list = this.getContext( path ),\r
82 inFirstListItem = CKEDITOR.plugins.indentList.firstItemInPath( this.context, path, list );\r
83\r
84 if ( !list || !this.isIndent || inFirstListItem )\r
85 return TRISTATE_DISABLED;\r
86\r
87 return TRISTATE_OFF;\r
88 } : function( editor, path ) {\r
89 var list = this.getContext( path );\r
90\r
91 if ( !list || this.isIndent )\r
92 return TRISTATE_DISABLED;\r
93\r
94 return TRISTATE_OFF;\r
95 },\r
96\r
97 exec: CKEDITOR.tools.bind( indentList, this )\r
98 };\r
99 }\r
100\r
101 CKEDITOR.tools.extend( commandDefinition.prototype, globalHelpers.specificDefinition.prototype, {\r
102 // Elements that, if in an elementpath, will be handled by this\r
103 // command. They restrict the scope of the plugin.\r
104 context: { ol: 1, ul: 1 }\r
105 } );\r
106 }\r
107 } );\r
108\r
109 function indentList( editor ) {\r
110 var that = this,\r
111 database = this.database,\r
112 context = this.context;\r
113\r
114 function indent( listNode ) {\r
115 // Our starting and ending points of the range might be inside some blocks under a list item...\r
116 // So before playing with the iterator, we need to expand the block to include the list items.\r
117 var startContainer = range.startContainer,\r
118 endContainer = range.endContainer;\r
119 while ( startContainer && !startContainer.getParent().equals( listNode ) )\r
120 startContainer = startContainer.getParent();\r
121 while ( endContainer && !endContainer.getParent().equals( listNode ) )\r
122 endContainer = endContainer.getParent();\r
123\r
124 if ( !startContainer || !endContainer )\r
125 return false;\r
126\r
127 // Now we can iterate over the individual items on the same tree depth.\r
128 var block = startContainer,\r
129 itemsToMove = [],\r
130 stopFlag = false;\r
131\r
132 while ( !stopFlag ) {\r
133 if ( block.equals( endContainer ) )\r
134 stopFlag = true;\r
135\r
136 itemsToMove.push( block );\r
137 block = block.getNext();\r
138 }\r
139\r
140 if ( itemsToMove.length < 1 )\r
141 return false;\r
142\r
143 // Do indent or outdent operations on the array model of the list, not the\r
144 // list's DOM tree itself. The array model demands that it knows as much as\r
145 // possible about the surrounding lists, we need to feed it the further\r
146 // ancestor node that is still a list.\r
147 var listParents = listNode.getParents( true );\r
148 for ( var i = 0; i < listParents.length; i++ ) {\r
149 if ( listParents[ i ].getName && context[ listParents[ i ].getName() ] ) {\r
150 listNode = listParents[ i ];\r
151 break;\r
152 }\r
153 }\r
154\r
155 var indentOffset = that.isIndent ? 1 : -1,\r
156 startItem = itemsToMove[ 0 ],\r
157 lastItem = itemsToMove[ itemsToMove.length - 1 ],\r
158\r
159 // Convert the list DOM tree into a one dimensional array.\r
160 listArray = CKEDITOR.plugins.list.listToArray( listNode, database ),\r
161\r
162 // Apply indenting or outdenting on the array.\r
163 baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent;\r
164\r
165 for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ ) {\r
166 listArray[ i ].indent += indentOffset;\r
167 // Make sure the newly created sublist get a brand-new element of the same type. (#5372)\r
168 if ( indentOffset > 0 ) {\r
169 var listRoot = listArray[ i ].parent;\r
170 listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() );\r
171 }\r
172 }\r
173\r
174 for ( i = lastItem.getCustomData( 'listarray_index' ) + 1; i < listArray.length && listArray[ i ].indent > baseIndent; i++ )\r
175 listArray[ i ].indent += indentOffset;\r
176\r
177 // Convert the array back to a DOM forest (yes we might have a few subtrees now).\r
178 // And replace the old list with the new forest.\r
179 var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, listNode.getDirection() );\r
180\r
181 // Avoid nested <li> after outdent even they're visually same,\r
182 // recording them for later refactoring.(#3982)\r
183 if ( !that.isIndent ) {\r
184 var parentLiElement;\r
185 if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) ) {\r
186 var children = newList.listNode.getChildren(),\r
187 pendingLis = [],\r
188 count = children.count(),\r
189 child;\r
190\r
191 for ( i = count - 1; i >= 0; i-- ) {\r
192 if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' ) )\r
193 pendingLis.push( child );\r
194 }\r
195 }\r
196 }\r
197\r
198 if ( newList )\r
199 newList.listNode.replace( listNode );\r
200\r
201 // Move the nested <li> to be appeared after the parent.\r
202 if ( pendingLis && pendingLis.length ) {\r
203 for ( i = 0; i < pendingLis.length; i++ ) {\r
204 var li = pendingLis[ i ],\r
205 followingList = li;\r
206\r
207 // Nest preceding <ul>/<ol> inside current <li> if any.\r
208 while ( ( followingList = followingList.getNext() ) && followingList.is && followingList.getName() in context ) {\r
209 // IE requires a filler NBSP for nested list inside empty list item,\r
210 // otherwise the list item will be inaccessiable. (#4476)\r
211 if ( CKEDITOR.env.needsNbspFiller && !li.getFirst( neitherWhitespacesNorBookmark ) )\r
212 li.append( range.document.createText( '\u00a0' ) );\r
213\r
214 li.append( followingList );\r
215 }\r
216\r
217 li.insertAfter( parentLiElement );\r
218 }\r
219 }\r
220\r
221 if ( newList )\r
222 editor.fire( 'contentDomInvalidated' );\r
223\r
224 return true;\r
225 }\r
226\r
227 var selection = editor.getSelection(),\r
228 ranges = selection && selection.getRanges(),\r
229 iterator = ranges.createIterator(),\r
230 range;\r
231\r
232 while ( ( range = iterator.getNextRange() ) ) {\r
233 var nearestListBlock = range.getCommonAncestor();\r
234\r
235 while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT && context[ nearestListBlock.getName() ] ) ) {\r
236 // Avoid having plugin propagate to parent of editor in inline mode by canceling the indentation. (#12796)\r
237 if ( editor.editable().equals( nearestListBlock ) ) {\r
238 nearestListBlock = false;\r
239 break;\r
240 }\r
241 nearestListBlock = nearestListBlock.getParent();\r
242 }\r
243\r
244 // Avoid having selection boundaries out of the list.\r
245 // <ul><li>[...</li></ul><p>...]</p> => <ul><li>[...]</li></ul><p>...</p>\r
246 if ( !nearestListBlock ) {\r
247 if ( ( nearestListBlock = range.startPath().contains( context ) ) )\r
248 range.setEndAt( nearestListBlock, CKEDITOR.POSITION_BEFORE_END );\r
249 }\r
250\r
251 // Avoid having selection enclose the entire list. (#6138)\r
252 // [<ul><li>...</li></ul>] =><ul><li>[...]</li></ul>\r
253 if ( !nearestListBlock ) {\r
254 var selectedNode = range.getEnclosedNode();\r
255 if ( selectedNode && selectedNode.type == CKEDITOR.NODE_ELEMENT && selectedNode.getName() in context ) {\r
256 range.setStartAt( selectedNode, CKEDITOR.POSITION_AFTER_START );\r
257 range.setEndAt( selectedNode, CKEDITOR.POSITION_BEFORE_END );\r
258 nearestListBlock = selectedNode;\r
259 }\r
260 }\r
261\r
262 // Avoid selection anchors under list root.\r
263 // <ul>[<li>...</li>]</ul> => <ul><li>[...]</li></ul>\r
264 if ( nearestListBlock && range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in context ) {\r
265 var walker = new CKEDITOR.dom.walker( range );\r
266 walker.evaluator = listItem;\r
267 range.startContainer = walker.next();\r
268 }\r
269\r
270 if ( nearestListBlock && range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in context ) {\r
271 walker = new CKEDITOR.dom.walker( range );\r
272 walker.evaluator = listItem;\r
273 range.endContainer = walker.previous();\r
274 }\r
275\r
276 if ( nearestListBlock )\r
277 return indent( nearestListBlock );\r
278 }\r
279 return 0;\r
280 }\r
281\r
282 // Determines whether a node is a list <li> element.\r
283 function listItem( node ) {\r
284 return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' );\r
285 }\r
286\r
287 function neitherWhitespacesNorBookmark( node ) {\r
288 return isNotWhitespaces( node ) && isNotBookmark( node );\r
289 }\r
290\r
291 /**\r
292 * Global namespace for methods exposed by the Indent List plugin.\r
293 *\r
294 * @singleton\r
295 * @class\r
296 */\r
297 CKEDITOR.plugins.indentList = {};\r
298\r
299 /**\r
300 * Checks whether the first child of the list is in the path.\r
301 * The list can be extracted from the path or given explicitly\r
302 * e.g. for better performance if cached.\r
303 *\r
304 * @since 4.4.6\r
305 * @param {Object} query See the {@link CKEDITOR.dom.elementPath#contains} method arguments.\r
306 * @param {CKEDITOR.dom.elementPath} path\r
307 * @param {CKEDITOR.dom.element} [list]\r
308 * @returns {Boolean}\r
309 * @member CKEDITOR.plugins.indentList\r
310 */\r
311 CKEDITOR.plugins.indentList.firstItemInPath = function( query, path, list ) {\r
312 var firstListItemInPath = path.contains( listItem );\r
313 if ( !list )\r
314 list = path.contains( query );\r
315\r
316 return list && firstListItemInPath && firstListItemInPath.equals( list.getFirst( listItem ) );\r
317 };\r
318} )();\r