source: branches/2.2/filemanager/tp/ckeditor/_source/plugins/list/plugin.js @ 3019

Revision 3019, 19.5 KB checked in by amuller, 14 years ago (diff)

Ticket #1135 - Corrigindo CSS e adicionando filemanager

Line 
1/*
2Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
3For licensing, see LICENSE.html or http://ckeditor.com/license
4*/
5
6/**
7 * @file Insert and remove numbered and bulleted lists.
8 */
9
10(function()
11{
12        var listNodeNames = { ol : 1, ul : 1 },
13                emptyTextRegex = /^[\n\r\t ]*$/;
14
15        CKEDITOR.plugins.list = {
16                /*
17                 * Convert a DOM list tree into a data structure that is easier to
18                 * manipulate. This operation should be non-intrusive in the sense that it
19                 * does not change the DOM tree, with the exception that it may add some
20                 * markers to the list item nodes when database is specified.
21                 */
22                listToArray : function( listNode, database, baseArray, baseIndentLevel, grandparentNode )
23                {
24                        if ( !listNodeNames[ listNode.getName() ] )
25                                return [];
26
27                        if ( !baseIndentLevel )
28                                baseIndentLevel = 0;
29                        if ( !baseArray )
30                                baseArray = [];
31
32                        // Iterate over all list items to get their contents and look for inner lists.
33                        for ( var i = 0, count = listNode.getChildCount() ; i < count ; i++ )
34                        {
35                                var listItem = listNode.getChild( i );
36
37                                // It may be a text node or some funny stuff.
38                                if ( listItem.$.nodeName.toLowerCase() != 'li' )
39                                        continue;
40                                var itemObj = { 'parent' : listNode, indent : baseIndentLevel, contents : [] };
41                                if ( !grandparentNode )
42                                {
43                                        itemObj.grandparent = listNode.getParent();
44                                        if ( itemObj.grandparent && itemObj.grandparent.$.nodeName.toLowerCase() == 'li' )
45                                                itemObj.grandparent = itemObj.grandparent.getParent();
46                                }
47                                else
48                                        itemObj.grandparent = grandparentNode;
49
50                                if ( database )
51                                        CKEDITOR.dom.element.setMarker( database, listItem, 'listarray_index', baseArray.length );
52                                baseArray.push( itemObj );
53
54                                for ( var j = 0, itemChildCount = listItem.getChildCount() ; j < itemChildCount ; j++ )
55                                {
56                                        var child = listItem.getChild( j );
57                                        if ( child.type == CKEDITOR.NODE_ELEMENT && listNodeNames[ child.getName() ] )
58                                                // Note the recursion here, it pushes inner list items with
59                                                // +1 indentation in the correct order.
60                                                CKEDITOR.plugins.list.listToArray( child, database, baseArray, baseIndentLevel + 1, itemObj.grandparent );
61                                        else
62                                                itemObj.contents.push( child );
63                                }
64                        }
65                        return baseArray;
66                },
67
68                // Convert our internal representation of a list back to a DOM forest.
69                arrayToList : function( listArray, database, baseIndex, paragraphMode )
70                {
71                        if ( !baseIndex )
72                                baseIndex = 0;
73                        if ( !listArray || listArray.length < baseIndex + 1 )
74                                return null;
75                        var doc = listArray[ baseIndex ].parent.getDocument(),
76                                retval = new CKEDITOR.dom.documentFragment( doc ),
77                                rootNode = null,
78                                currentIndex = baseIndex,
79                                indentLevel = Math.max( listArray[ baseIndex ].indent, 0 ),
80                                currentListItem = null,
81                                paragraphName = ( paragraphMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
82                        while ( true )
83                        {
84                                var item = listArray[ currentIndex ];
85                                if ( item.indent == indentLevel )
86                                {
87                                        if ( !rootNode || listArray[ currentIndex ].parent.getName() != rootNode.getName() )
88                                        {
89                                                rootNode = listArray[ currentIndex ].parent.clone( false, true );
90                                                retval.append( rootNode );
91                                        }
92                                        currentListItem = rootNode.append( doc.createElement( 'li' ) );
93                                        for ( var i = 0 ; i < item.contents.length ; i++ )
94                                                currentListItem.append( item.contents[i].clone( true, true ) );
95                                        currentIndex++;
96                                }
97                                else if ( item.indent == Math.max( indentLevel, 0 ) + 1 )
98                                {
99                                        var listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode );
100                                        currentListItem.append( listData.listNode );
101                                        currentIndex = listData.nextIndex;
102                                }
103                                else if ( item.indent == -1 && !baseIndex && item.grandparent )
104                                {
105                                        currentListItem;
106                                        if ( listNodeNames[ item.grandparent.getName() ] )
107                                                currentListItem = doc.createElement( 'li' );
108                                        else
109                                        {
110                                                if ( paragraphMode != CKEDITOR.ENTER_BR && item.grandparent.getName() != 'td' )
111                                                        currentListItem = doc.createElement( paragraphName );
112                                                else
113                                                        currentListItem = new CKEDITOR.dom.documentFragment( doc );
114                                        }
115
116                                        for ( i = 0 ; i < item.contents.length ; i++ )
117                                                currentListItem.append( item.contents[i].clone( true, true ) );
118
119                                        if ( currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT
120                                                 && currentIndex != listArray.length - 1 )
121                                        {
122                                                if ( currentListItem.getLast()
123                                                                && currentListItem.getLast().type == CKEDITOR.NODE_ELEMENT
124                                                                && currentListItem.getLast().getAttribute( 'type' ) == '_moz' )
125                                                        currentListItem.getLast().remove();
126                                                currentListItem.appendBogus();
127                                        }
128
129                                        if ( currentListItem.type == CKEDITOR.NODE_ELEMENT &&
130                                                        currentListItem.getName() == paragraphName &&
131                                                        currentListItem.$.firstChild )
132                                        {
133                                                currentListItem.trim();
134                                                var firstChild = currentListItem.getFirst();
135                                                if ( firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.isBlockBoundary() )
136                                                {
137                                                        var tmp = new CKEDITOR.dom.documentFragment( doc );
138                                                        currentListItem.moveChildren( tmp );
139                                                        currentListItem = tmp;
140                                                }
141                                        }
142
143                                        var currentListItemName = currentListItem.$.nodeName.toLowerCase();
144                                        if ( !CKEDITOR.env.ie && ( currentListItemName == 'div' || currentListItemName == 'p' ) )
145                                                currentListItem.appendBogus();
146                                        retval.append( currentListItem );
147                                        rootNode = null;
148                                        currentIndex++;
149                                }
150                                else
151                                        return null;
152
153                                if ( listArray.length <= currentIndex || Math.max( listArray[ currentIndex ].indent, 0 ) < indentLevel )
154                                        break;
155                        }
156
157                        // Clear marker attributes for the new list tree made of cloned nodes, if any.
158                        if ( database )
159                        {
160                                var currentNode = retval.getFirst();
161                                while ( currentNode )
162                                {
163                                        if ( currentNode.type == CKEDITOR.NODE_ELEMENT )
164                                                CKEDITOR.dom.element.clearMarkers( database, currentNode );
165                                        currentNode = currentNode.getNextSourceNode();
166                                }
167                        }
168
169                        return { listNode : retval, nextIndex : currentIndex };
170                }
171        };
172
173        function setState( editor, state )
174        {
175                editor.getCommand( this.name ).setState( state );
176        }
177
178        function onSelectionChange( evt )
179        {
180                var path = evt.data.path,
181                        blockLimit = path.blockLimit,
182                        elements = path.elements,
183                        element;
184
185                // Grouping should only happen under blockLimit.(#3940).
186                for ( var i = 0 ; i < elements.length && ( element = elements[ i ] )
187                          && !element.equals( blockLimit ); i++ )
188                {
189                        if ( listNodeNames[ elements[i].getName() ] )
190                        {
191                                return setState.call( this, evt.editor,
192                                                this.type == elements[i].getName() ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF );
193                        }
194                }
195
196                return setState.call( this, evt.editor, CKEDITOR.TRISTATE_OFF );
197        }
198
199        function changeListType( editor, groupObj, database, listsCreated )
200        {
201                // This case is easy...
202                // 1. Convert the whole list into a one-dimensional array.
203                // 2. Change the list type by modifying the array.
204                // 3. Recreate the whole list by converting the array to a list.
205                // 4. Replace the original list with the recreated list.
206                var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ),
207                        selectedListItems = [];
208
209                for ( var i = 0 ; i < groupObj.contents.length ; i++ )
210                {
211                        var itemNode = groupObj.contents[i];
212                        itemNode = itemNode.getAscendant( 'li', true );
213                        if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) )
214                                continue;
215                        selectedListItems.push( itemNode );
216                        CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true );
217                }
218
219                var fakeParent = groupObj.root.getDocument().createElement( this.type );
220                for ( i = 0 ; i < selectedListItems.length ; i++ )
221                {
222                        var listIndex = selectedListItems[i].getCustomData( 'listarray_index' );
223                        listArray[listIndex].parent = fakeParent;
224                }
225                var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode );
226                var child, length = newList.listNode.getChildCount();
227                for ( i = 0 ; i < length && ( child = newList.listNode.getChild( i ) ) ; i++ )
228                {
229                        if ( child.getName() == this.type )
230                                listsCreated.push( child );
231                }
232                newList.listNode.replace( groupObj.root );
233        }
234
235        function createList( editor, groupObj, listsCreated )
236        {
237                var contents = groupObj.contents,
238                        doc = groupObj.root.getDocument(),
239                        listContents = [];
240
241                // It is possible to have the contents returned by DomRangeIterator to be the same as the root.
242                // e.g. when we're running into table cells.
243                // In such a case, enclose the childNodes of contents[0] into a <div>.
244                if ( contents.length == 1 && contents[0].equals( groupObj.root ) )
245                {
246                        var divBlock = doc.createElement( 'div' );
247                        contents[0].moveChildren && contents[0].moveChildren( divBlock );
248                        contents[0].append( divBlock );
249                        contents[0] = divBlock;
250                }
251
252                // Calculate the common parent node of all content blocks.
253                var commonParent = groupObj.contents[0].getParent();
254                for ( var i = 0 ; i < contents.length ; i++ )
255                        commonParent = commonParent.getCommonAncestor( contents[i].getParent() );
256
257                // We want to insert things that are in the same tree level only, so calculate the contents again
258                // by expanding the selected blocks to the same tree level.
259                for ( i = 0 ; i < contents.length ; i++ )
260                {
261                        var contentNode = contents[i],
262                                parentNode;
263                        while ( ( parentNode = contentNode.getParent() ) )
264                        {
265                                if ( parentNode.equals( commonParent ) )
266                                {
267                                        listContents.push( contentNode );
268                                        break;
269                                }
270                                contentNode = parentNode;
271                        }
272                }
273
274                if ( listContents.length < 1 )
275                        return;
276
277                // Insert the list to the DOM tree.
278                var insertAnchor = listContents[ listContents.length - 1 ].getNext(),
279                        listNode = doc.createElement( this.type );
280
281                listsCreated.push( listNode );
282                while ( listContents.length )
283                {
284                        var contentBlock = listContents.shift(),
285                                listItem = doc.createElement( 'li' );
286                        contentBlock.moveChildren( listItem );
287                        contentBlock.remove();
288                        listItem.appendTo( listNode );
289
290                        // Append a bogus BR to force the LI to render at full height
291                        if ( !CKEDITOR.env.ie )
292                                listItem.appendBogus();
293                }
294                if ( insertAnchor )
295                        listNode.insertBefore( insertAnchor );
296                else
297                        listNode.appendTo( commonParent );
298        }
299
300        function removeList( editor, groupObj, database )
301        {
302                // This is very much like the change list type operation.
303                // Except that we're changing the selected items' indent to -1 in the list array.
304                var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ),
305                        selectedListItems = [];
306
307                for ( var i = 0 ; i < groupObj.contents.length ; i++ )
308                {
309                        var itemNode = groupObj.contents[i];
310                        itemNode = itemNode.getAscendant( 'li', true );
311                        if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) )
312                                continue;
313                        selectedListItems.push( itemNode );
314                        CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true );
315                }
316
317                var lastListIndex = null;
318                for ( i = 0 ; i < selectedListItems.length ; i++ )
319                {
320                        var listIndex = selectedListItems[i].getCustomData( 'listarray_index' );
321                        listArray[listIndex].indent = -1;
322                        lastListIndex = listIndex;
323                }
324
325                // After cutting parts of the list out with indent=-1, we still have to maintain the array list
326                // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the
327                // list cannot be converted back to a real DOM list.
328                for ( i = lastListIndex + 1 ; i < listArray.length ; i++ )
329                {
330                        if ( listArray[i].indent > listArray[i-1].indent + 1 )
331                        {
332                                var indentOffset = listArray[i-1].indent + 1 - listArray[i].indent;
333                                var oldIndent = listArray[i].indent;
334                                while ( listArray[i] && listArray[i].indent >= oldIndent )
335                                {
336                                        listArray[i].indent += indentOffset;
337                                        i++;
338                                }
339                                i--;
340                        }
341                }
342
343                var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode );
344
345                // Compensate <br> before/after the list node if the surrounds are non-blocks.(#3836)
346                var docFragment = newList.listNode, boundaryNode, siblingNode;
347                function compensateBrs( isStart )
348                {
349                        if ( ( boundaryNode = docFragment[ isStart ? 'getFirst' : 'getLast' ]() )
350                                 && !( boundaryNode.is && boundaryNode.isBlockBoundary() )
351                                 && ( siblingNode = groupObj.root[ isStart ? 'getPrevious' : 'getNext' ]
352                                      ( CKEDITOR.dom.walker.whitespaces( true ) ) )
353                                 && !( siblingNode.is && siblingNode.isBlockBoundary( { br : 1 } ) ) )
354                                editor.document.createElement( 'br' )[ isStart ? 'insertBefore' : 'insertAfter' ]( boundaryNode );
355                }
356                compensateBrs( true );
357                compensateBrs();
358
359                var rootParent = groupObj.root.getParent();
360                docFragment.replace( groupObj.root );
361        }
362
363        function listCommand( name, type )
364        {
365                this.name = name;
366                this.type = type;
367        }
368
369        listCommand.prototype = {
370                exec : function( editor )
371                {
372                        editor.focus();
373
374                        var doc = editor.document,
375                                selection = editor.getSelection(),
376                                ranges = selection && selection.getRanges();
377
378                        // There should be at least one selected range.
379                        if ( !ranges || ranges.length < 1 )
380                                return;
381
382                        // Midas lists rule #1 says we can create a list even in an empty document.
383                        // But DOM iterator wouldn't run if the document is really empty.
384                        // So create a paragraph if the document is empty and we're going to create a list.
385                        if ( this.state == CKEDITOR.TRISTATE_OFF )
386                        {
387                                var body = doc.getBody();
388                                body.trim();
389                                if ( !body.getFirst() )
390                                {
391                                        var paragraph = doc.createElement( editor.config.enterMode == CKEDITOR.ENTER_P ? 'p' :
392                                                        ( editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'br' ) );
393                                        paragraph.appendTo( body );
394                                        ranges = [ new CKEDITOR.dom.range( doc ) ];
395                                        // IE exception on inserting anything when anchor inside <br>.
396                                        if ( paragraph.is( 'br' ) )
397                                        {
398                                                ranges[ 0 ].setStartBefore( paragraph );
399                                                ranges[ 0 ].setEndAfter( paragraph );
400                                        }
401                                        else
402                                                ranges[ 0 ].selectNodeContents( paragraph );
403                                        selection.selectRanges( ranges );
404                                }
405                                // Maybe a single range there enclosing the whole list,
406                                // turn on the list state manually(#4129).
407                                else
408                                {
409                                        var range = ranges.length == 1 && ranges[ 0 ],
410                                                enclosedNode = range && range.getEnclosedNode();
411                                        if ( enclosedNode && enclosedNode.is
412                                                && this.type == enclosedNode.getName() )
413                                        {
414                                                setState.call( this, editor, CKEDITOR.TRISTATE_ON );
415                                        }
416                                }
417                        }
418
419                        var bookmarks = selection.createBookmarks( true );
420
421                        // Group the blocks up because there are many cases where multiple lists have to be created,
422                        // or multiple lists have to be cancelled.
423                        var listGroups = [],
424                                database = {};
425
426                        while ( ranges.length > 0 )
427                        {
428                                range = ranges.shift();
429
430                                var boundaryNodes = range.getBoundaryNodes(),
431                                        startNode = boundaryNodes.startNode,
432                                        endNode = boundaryNodes.endNode;
433
434                                if ( startNode.type == CKEDITOR.NODE_ELEMENT && startNode.getName() == 'td' )
435                                        range.setStartAt( boundaryNodes.startNode, CKEDITOR.POSITION_AFTER_START );
436
437                                if ( endNode.type == CKEDITOR.NODE_ELEMENT && endNode.getName() == 'td' )
438                                        range.setEndAt( boundaryNodes.endNode, CKEDITOR.POSITION_BEFORE_END );
439
440                                var iterator = range.createIterator(),
441                                        block;
442
443                                iterator.forceBrBreak = ( this.state == CKEDITOR.TRISTATE_OFF );
444
445                                while ( ( block = iterator.getNextParagraph() ) )
446                                {
447                                        var path = new CKEDITOR.dom.elementPath( block ),
448                                                listNode = null,
449                                                processedFlag = false,
450                                                blockLimit = path.blockLimit,
451                                                element;
452
453                                        // First, try to group by a list ancestor.
454                                        for ( var i = 0 ; i < path.elements.length &&
455                                                  ( element = path.elements[ i ] ) && !element.equals( blockLimit ); i++ )
456                                        {
457                                                if ( listNodeNames[ element.getName() ] )
458                                                {
459                                                        // If we've encountered a list inside a block limit
460                                                        // The last group object of the block limit element should
461                                                        // no longer be valid. Since paragraphs after the list
462                                                        // should belong to a different group of paragraphs before
463                                                        // the list. (Bug #1309)
464                                                        blockLimit.removeCustomData( 'list_group_object' );
465
466                                                        var groupObj = element.getCustomData( 'list_group_object' );
467                                                        if ( groupObj )
468                                                                groupObj.contents.push( block );
469                                                        else
470                                                        {
471                                                                groupObj = { root : element, contents : [ block ] };
472                                                                listGroups.push( groupObj );
473                                                                CKEDITOR.dom.element.setMarker( database, element, 'list_group_object', groupObj );
474                                                        }
475                                                        processedFlag = true;
476                                                        break;
477                                                }
478                                        }
479
480                                        if ( processedFlag )
481                                                continue;
482
483                                        // No list ancestor? Group by block limit.
484                                        var root = blockLimit;
485                                        if ( root.getCustomData( 'list_group_object' ) )
486                                                root.getCustomData( 'list_group_object' ).contents.push( block );
487                                        else
488                                        {
489                                                groupObj = { root : root, contents : [ block ] };
490                                                CKEDITOR.dom.element.setMarker( database, root, 'list_group_object', groupObj );
491                                                listGroups.push( groupObj );
492                                        }
493                                }
494                        }
495
496                        // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element.
497                        // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking
498                        // at the group that's not rooted at lists. So we have three cases to handle.
499                        var listsCreated = [];
500                        while ( listGroups.length > 0 )
501                        {
502                                groupObj = listGroups.shift();
503                                if ( this.state == CKEDITOR.TRISTATE_OFF )
504                                {
505                                        if ( listNodeNames[ groupObj.root.getName() ] )
506                                                changeListType.call( this, editor, groupObj, database, listsCreated );
507                                        else
508                                                createList.call( this, editor, groupObj, listsCreated );
509                                }
510                                else if ( this.state == CKEDITOR.TRISTATE_ON && listNodeNames[ groupObj.root.getName() ] )
511                                        removeList.call( this, editor, groupObj, database );
512                        }
513
514                        // For all new lists created, merge adjacent, same type lists.
515                        for ( i = 0 ; i < listsCreated.length ; i++ )
516                        {
517                                listNode = listsCreated[i];
518                                var mergeSibling, listCommand = this;
519                                ( mergeSibling = function( rtl ){
520
521                                        var sibling = listNode[ rtl ?
522                                                'getPrevious' : 'getNext' ]( CKEDITOR.dom.walker.whitespaces( true ) );
523                                        if ( sibling && sibling.getName &&
524                                             sibling.getName() == listCommand.type )
525                                        {
526                                                sibling.remove();
527                                                // Move children order by merge direction.(#3820)
528                                                sibling.moveChildren( listNode, rtl ? true : false );
529                                        }
530                                } )();
531                                mergeSibling( true );
532                        }
533
534                        // Clean up, restore selection and update toolbar button states.
535                        CKEDITOR.dom.element.clearAllMarkers( database );
536                        selection.selectBookmarks( bookmarks );
537                        editor.focus();
538                }
539        };
540
541        CKEDITOR.plugins.add( 'list',
542        {
543                init : function( editor )
544                {
545                        // Register commands.
546                        var numberedListCommand = new listCommand( 'numberedlist', 'ol' ),
547                                bulletedListCommand = new listCommand( 'bulletedlist', 'ul' );
548                        editor.addCommand( 'numberedlist', numberedListCommand );
549                        editor.addCommand( 'bulletedlist', bulletedListCommand );
550
551                        // Register the toolbar button.
552                        editor.ui.addButton( 'NumberedList',
553                                {
554                                        label : editor.lang.numberedlist,
555                                        command : 'numberedlist'
556                                } );
557                        editor.ui.addButton( 'BulletedList',
558                                {
559                                        label : editor.lang.bulletedlist,
560                                        command : 'bulletedlist'
561                                } );
562
563                        // Register the state changing handlers.
564                        editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, numberedListCommand ) );
565                        editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, bulletedListCommand ) );
566                },
567
568                requires : [ 'domiterator' ]
569        } );
570})();
Note: See TracBrowser for help on using the repository browser.