PluginProbe
Wp-Insert / 1.7.2
Wp-Insert v1.7.2
trunk 1.0 1.1 1.2 1.2.1 1.2.2 1.3.0 1.3.1 1.3.3 1.4 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.6.2 1.7 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 2.0 All 63 releases
wp-insert / fckeditor / editor / _source / commandclasses / fcklistcommands.js

fcklistcommands.js in Wp-Insert 1.7.2, at fckeditor/editor/_source/commandclasses/fcklistcommands.js

383 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /*
2 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
3 * Copyright (C) 2003-2009 Frederico Caldeira Knabben
4 *
5 * == BEGIN LICENSE ==
6 *
7 * Licensed under the terms of any of the following licenses at your
8 * choice:
9 *
10 * - GNU General Public License Version 2 or later (the "GPL")
11 * http://www.gnu.org/licenses/gpl.html
12 *
13 * - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
14 * http://www.gnu.org/licenses/lgpl.html
15 *
16 * - Mozilla Public License Version 1.1 or later (the "MPL")
17 * http://www.mozilla.org/MPL/MPL-1.1.html
18 *
19 * == END LICENSE ==
20 *
21 * Implementation for the "Insert/Remove Ordered/Unordered List" commands.
22 */
23
24 var FCKListCommand = function( name, tagName )
25 {
26 this.Name = name ;
27 this.TagName = tagName ;
28 }
29
30 FCKListCommand.prototype =
31 {
32 GetState : function()
33 {
34 // Disabled if not WYSIWYG.
35 if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG || ! FCK.EditorWindow )
36 return FCK_TRISTATE_DISABLED ;
37
38 // We'll use the style system's convention to determine list state here...
39 // If the starting block is a descendant of an <ol> or <ul> node, then we're in a list.
40 var startContainer = FCKSelection.GetBoundaryParentElement( true ) ;
41 var listNode = startContainer ;
42 while ( listNode )
43 {
44 if ( listNode.nodeName.IEquals( [ 'ul', 'ol' ] ) )
45 break ;
46 listNode = listNode.parentNode ;
47 }
48 if ( listNode && listNode.nodeName.IEquals( this.TagName ) )
49 return FCK_TRISTATE_ON ;
50 else
51 return FCK_TRISTATE_OFF ;
52 },
53
54 Execute : function()
55 {
56 FCKUndo.SaveUndoStep() ;
57
58 var doc = FCK.EditorDocument ;
59 var range = new FCKDomRange( FCK.EditorWindow ) ;
60 range.MoveToSelection() ;
61 var state = this.GetState() ;
62
63 // Midas lists rule #1 says we can create a list even in an empty document.
64 // But FCKDomRangeIterator wouldn't run if the document is really empty.
65 // So create a paragraph if the document is empty and we're going to create a list.
66 if ( state == FCK_TRISTATE_OFF )
67 {
68 FCKDomTools.TrimNode( doc.body ) ;
69 if ( ! doc.body.firstChild )
70 {
71 var paragraph = doc.createElement( 'p' ) ;
72 doc.body.appendChild( paragraph ) ;
73 range.MoveToNodeContents( paragraph ) ;
74 }
75 }
76
77 var bookmark = range.CreateBookmark() ;
78
79 // Group the blocks up because there are many cases where multiple lists have to be created,
80 // or multiple lists have to be cancelled.
81 var listGroups = [] ;
82 var markerObj = {} ;
83 var iterator = new FCKDomRangeIterator( range ) ;
84 var block ;
85
86 iterator.ForceBrBreak = ( state == FCK_TRISTATE_OFF ) ;
87 var nextRangeExists = true ;
88 var rangeQueue = null ;
89 while ( nextRangeExists )
90 {
91 while ( ( block = iterator.GetNextParagraph() ) )
92 {
93 var path = new FCKElementPath( block ) ;
94 var listNode = null ;
95 var processedFlag = false ;
96 var blockLimit = path.BlockLimit ;
97
98 // First, try to group by a list ancestor.
99 for ( var i = path.Elements.length - 1 ; i >= 0 ; i-- )
100 {
101 var el = path.Elements[i] ;
102 if ( el.nodeName.IEquals( ['ol', 'ul'] ) )
103 {
104 // If we've encountered a list inside a block limit
105 // The last group object of the block limit element should
106 // no longer be valid. Since paragraphs after the list
107 // should belong to a different group of paragraphs before
108 // the list. (Bug #1309)
109 if ( blockLimit._FCK_ListGroupObject )
110 blockLimit._FCK_ListGroupObject = null ;
111
112 var groupObj = el._FCK_ListGroupObject ;
113 if ( groupObj )
114 groupObj.contents.push( block ) ;
115 else
116 {
117 groupObj = { 'root' : el, 'contents' : [ block ] } ;
118 listGroups.push( groupObj ) ;
119 FCKDomTools.SetElementMarker( markerObj, el, '_FCK_ListGroupObject', groupObj ) ;
120 }
121 processedFlag = true ;
122 break ;
123 }
124 }
125
126 if ( processedFlag )
127 continue ;
128
129 // No list ancestor? Group by block limit.
130 var root = blockLimit ;
131 if ( root._FCK_ListGroupObject )
132 root._FCK_ListGroupObject.contents.push( block ) ;
133 else
134 {
135 var groupObj = { 'root' : root, 'contents' : [ block ] } ;
136 FCKDomTools.SetElementMarker( markerObj, root, '_FCK_ListGroupObject', groupObj ) ;
137 listGroups.push( groupObj ) ;
138 }
139 }
140
141 if ( FCKBrowserInfo.IsIE )
142 nextRangeExists = false ;
143 else
144 {
145 if ( rangeQueue == null )
146 {
147 rangeQueue = [] ;
148 var selectionObject = FCKSelection.GetSelection() ;
149 if ( selectionObject && listGroups.length == 0 )
150 rangeQueue.push( selectionObject.getRangeAt( 0 ) ) ;
151 for ( var i = 1 ; selectionObject && i < selectionObject.rangeCount ; i++ )
152 rangeQueue.push( selectionObject.getRangeAt( i ) ) ;
153 }
154 if ( rangeQueue.length < 1 )
155 nextRangeExists = false ;
156 else
157 {
158 var internalRange = FCKW3CRange.CreateFromRange( doc, rangeQueue.shift() ) ;
159 range._Range = internalRange ;
160 range._UpdateElementInfo() ;
161 if ( range.StartNode.nodeName.IEquals( 'td' ) )
162 range.SetStart( range.StartNode, 1 ) ;
163 if ( range.EndNode.nodeName.IEquals( 'td' ) )
164 range.SetEnd( range.EndNode, 2 ) ;
165 iterator = new FCKDomRangeIterator( range ) ;
166 iterator.ForceBrBreak = ( state == FCK_TRISTATE_OFF ) ;
167 }
168 }
169 }
170
171 // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element.
172 // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking
173 // at the group that's not rooted at lists. So we have three cases to handle.
174 var listsCreated = [] ;
175 while ( listGroups.length > 0 )
176 {
177 var groupObj = listGroups.shift() ;
178 if ( state == FCK_TRISTATE_OFF )
179 {
180 if ( groupObj.root.nodeName.IEquals( ['ul', 'ol'] ) )
181 this._ChangeListType( groupObj, markerObj, listsCreated ) ;
182 else
183 this._CreateList( groupObj, listsCreated ) ;
184 }
185 else if ( state == FCK_TRISTATE_ON && groupObj.root.nodeName.IEquals( ['ul', 'ol'] ) )
186 this._RemoveList( groupObj, markerObj ) ;
187 }
188
189 // For all new lists created, merge adjacent, same type lists.
190 for ( var i = 0 ; i < listsCreated.length ; i++ )
191 {
192 var listNode = listsCreated[i] ;
193 var stopFlag = false ;
194 var currentNode = listNode ;
195 while ( ! stopFlag )
196 {
197 currentNode = currentNode.nextSibling ;
198 if ( currentNode && currentNode.nodeType == 3 && currentNode.nodeValue.search( /^[\n\r\t ]*$/ ) == 0 )
199 continue ;
200 stopFlag = true ;
201 }
202
203 if ( currentNode && currentNode.nodeName.IEquals( this.TagName ) )
204 {
205 currentNode.parentNode.removeChild( currentNode ) ;
206 while ( currentNode.firstChild )
207 listNode.appendChild( currentNode.removeChild( currentNode.firstChild ) ) ;
208 }
209
210 stopFlag = false ;
211 currentNode = listNode ;
212 while ( ! stopFlag )
213 {
214 currentNode = currentNode.previousSibling ;
215 if ( currentNode && currentNode.nodeType == 3 && currentNode.nodeValue.search( /^[\n\r\t ]*$/ ) == 0 )
216 continue ;
217 stopFlag = true ;
218 }
219 if ( currentNode && currentNode.nodeName.IEquals( this.TagName ) )
220 {
221 currentNode.parentNode.removeChild( currentNode ) ;
222 while ( currentNode.lastChild )
223 listNode.insertBefore( currentNode.removeChild( currentNode.lastChild ),
224 listNode.firstChild ) ;
225 }
226 }
227
228 // Clean up, restore selection and update toolbar button states.
229 FCKDomTools.ClearAllMarkers( markerObj ) ;
230 range.MoveToBookmark( bookmark ) ;
231 range.Select() ;
232
233 FCK.Focus() ;
234 FCK.Events.FireEvent( 'OnSelectionChange' ) ;
235 },
236
237 _ChangeListType : function( groupObj, markerObj, listsCreated )
238 {
239 // This case is easy...
240 // 1. Convert the whole list into a one-dimensional array.
241 // 2. Change the list type by modifying the array.
242 // 3. Recreate the whole list by converting the array to a list.
243 // 4. Replace the original list with the recreated list.
244 var listArray = FCKDomTools.ListToArray( groupObj.root, markerObj ) ;
245 var selectedListItems = [] ;
246 for ( var i = 0 ; i < groupObj.contents.length ; i++ )
247 {
248 var itemNode = groupObj.contents[i] ;
249 itemNode = FCKTools.GetElementAscensor( itemNode, 'li' ) ;
250 if ( ! itemNode || itemNode._FCK_ListItem_Processed )
251 continue ;
252 selectedListItems.push( itemNode ) ;
253 FCKDomTools.SetElementMarker( markerObj, itemNode, '_FCK_ListItem_Processed', true ) ;
254 }
255 var fakeParent = FCKTools.GetElementDocument( groupObj.root ).createElement( this.TagName ) ;
256 for ( var i = 0 ; i < selectedListItems.length ; i++ )
257 {
258 var listIndex = selectedListItems[i]._FCK_ListArray_Index ;
259 listArray[listIndex].parent = fakeParent ;
260 }
261 var newList = FCKDomTools.ArrayToList( listArray, markerObj ) ;
262 for ( var i = 0 ; i < newList.listNode.childNodes.length ; i++ )
263 {
264 if ( newList.listNode.childNodes[i].nodeName.IEquals( this.TagName ) )
265 listsCreated.push( newList.listNode.childNodes[i] ) ;
266 }
267 groupObj.root.parentNode.replaceChild( newList.listNode, groupObj.root ) ;
268 },
269
270 _CreateList : function( groupObj, listsCreated )
271 {
272 var contents = groupObj.contents ;
273 var doc = FCKTools.GetElementDocument( groupObj.root ) ;
274 var listContents = [] ;
275
276 // It is possible to have the contents returned by DomRangeIterator to be the same as the root.
277 // e.g. when we're running into table cells.
278 // In such a case, enclose the childNodes of contents[0] into a <div>.
279 if ( contents.length == 1 && contents[0] == groupObj.root )
280 {
281 var divBlock = doc.createElement( 'div' );
282 while ( contents[0].firstChild )
283 divBlock.appendChild( contents[0].removeChild( contents[0].firstChild ) ) ;
284 contents[0].appendChild( divBlock ) ;
285 contents[0] = divBlock ;
286 }
287
288 // Calculate the common parent node of all content blocks.
289 var commonParent = groupObj.contents[0].parentNode ;
290 for ( var i = 0 ; i < contents.length ; i++ )
291 commonParent = FCKDomTools.GetCommonParents( commonParent, contents[i].parentNode ).pop() ;
292
293 // We want to insert things that are in the same tree level only, so calculate the contents again
294 // by expanding the selected blocks to the same tree level.
295 for ( var i = 0 ; i < contents.length ; i++ )
296 {
297 var contentNode = contents[i] ;
298 while ( contentNode.parentNode )
299 {
300 if ( contentNode.parentNode == commonParent )
301 {
302 listContents.push( contentNode ) ;
303 break ;
304 }
305 contentNode = contentNode.parentNode ;
306 }
307 }
308
309 if ( listContents.length < 1 )
310 return ;
311
312 // Insert the list to the DOM tree.
313 var insertAnchor = listContents[listContents.length - 1].nextSibling ;
314 var listNode = doc.createElement( this.TagName ) ;
315 listsCreated.push( listNode ) ;
316 while ( listContents.length )
317 {
318 var contentBlock = listContents.shift() ;
319 var docFrag = doc.createDocumentFragment() ;
320 while ( contentBlock.firstChild )
321 docFrag.appendChild( contentBlock.removeChild( contentBlock.firstChild ) ) ;
322 contentBlock.parentNode.removeChild( contentBlock ) ;
323 var listItem = doc.createElement( 'li' ) ;
324 listItem.appendChild( docFrag ) ;
325 listNode.appendChild( listItem ) ;
326 }
327 commonParent.insertBefore( listNode, insertAnchor ) ;
328 },
329
330 _RemoveList : function( groupObj, markerObj )
331 {
332 // This is very much like the change list type operation.
333 // Except that we're changing the selected items' indent to -1 in the list array.
334 var listArray = FCKDomTools.ListToArray( groupObj.root, markerObj ) ;
335 var selectedListItems = [] ;
336 for ( var i = 0 ; i < groupObj.contents.length ; i++ )
337 {
338 var itemNode = groupObj.contents[i] ;
339 itemNode = FCKTools.GetElementAscensor( itemNode, 'li' ) ;
340 if ( ! itemNode || itemNode._FCK_ListItem_Processed )
341 continue ;
342 selectedListItems.push( itemNode ) ;
343 FCKDomTools.SetElementMarker( markerObj, itemNode, '_FCK_ListItem_Processed', true ) ;
344 }
345
346 var lastListIndex = null ;
347 for ( var i = 0 ; i < selectedListItems.length ; i++ )
348 {
349 var listIndex = selectedListItems[i]._FCK_ListArray_Index ;
350 listArray[listIndex].indent = -1 ;
351 lastListIndex = listIndex ;
352 }
353
354 // After cutting parts of the list out with indent=-1, we still have to maintain the array list
355 // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the
356 // list cannot be converted back to a real DOM list.
357 for ( var i = lastListIndex + 1; i < listArray.length ; i++ )
358 {
359 if ( listArray[i].indent > listArray[i-1].indent + 1 )
360 {
361 var indentOffset = listArray[i-1].indent + 1 - listArray[i].indent ;
362 var oldIndent = listArray[i].indent ;
363 while ( listArray[i] && listArray[i].indent >= oldIndent)
364 {
365 listArray[i].indent += indentOffset ;
366 i++ ;
367 }
368 i-- ;
369 }
370 }
371
372 var newList = FCKDomTools.ArrayToList( listArray, markerObj ) ;
373 // If groupObj.root is the last element in its parent, or its nextSibling is a <br>, then we should
374 // not add a <br> after the final item. So, check for the cases and trim the <br>.
375 if ( groupObj.root.nextSibling == null || groupObj.root.nextSibling.nodeName.IEquals( 'br' ) )
376 {
377 if ( newList.listNode.lastChild.nodeName.IEquals( 'br' ) )
378 newList.listNode.removeChild( newList.listNode.lastChild ) ;
379 }
380 groupObj.root.parentNode.replaceChild( newList.listNode, groupObj.root ) ;
381 }
382 };
383