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 / internals / fckdomtools.js

fckdomtools.js in Wp-Insert 1.7.2, at fckeditor/editor/_source/internals/fckdomtools.js

1,058 lines 31.2 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 * Utility functions to work with the DOM.
22 */
23
24 var FCKDomTools =
25 {
26 /**
27 * Move all child nodes from one node to another.
28 */
29 MoveChildren : function( source, target, toTargetStart )
30 {
31 if ( source == target )
32 return ;
33
34 var eChild ;
35
36 if ( toTargetStart )
37 {
38 while ( (eChild = source.lastChild) )
39 target.insertBefore( source.removeChild( eChild ), target.firstChild ) ;
40 }
41 else
42 {
43 while ( (eChild = source.firstChild) )
44 target.appendChild( source.removeChild( eChild ) ) ;
45 }
46 },
47
48 MoveNode : function( source, target, toTargetStart )
49 {
50 if ( toTargetStart )
51 target.insertBefore( FCKDomTools.RemoveNode( source ), target.firstChild ) ;
52 else
53 target.appendChild( FCKDomTools.RemoveNode( source ) ) ;
54 },
55
56 // Remove blank spaces from the beginning and the end of the contents of a node.
57 TrimNode : function( node )
58 {
59 this.LTrimNode( node ) ;
60 this.RTrimNode( node ) ;
61 },
62
63 LTrimNode : function( node )
64 {
65 var eChildNode ;
66
67 while ( (eChildNode = node.firstChild) )
68 {
69 if ( eChildNode.nodeType == 3 )
70 {
71 var sTrimmed = eChildNode.nodeValue.LTrim() ;
72 var iOriginalLength = eChildNode.nodeValue.length ;
73
74 if ( sTrimmed.length == 0 )
75 {
76 node.removeChild( eChildNode ) ;
77 continue ;
78 }
79 else if ( sTrimmed.length < iOriginalLength )
80 {
81 eChildNode.splitText( iOriginalLength - sTrimmed.length ) ;
82 node.removeChild( node.firstChild ) ;
83 }
84 }
85 break ;
86 }
87 },
88
89 RTrimNode : function( node )
90 {
91 var eChildNode ;
92
93 while ( (eChildNode = node.lastChild) )
94 {
95 if ( eChildNode.nodeType == 3 )
96 {
97 var sTrimmed = eChildNode.nodeValue.RTrim() ;
98 var iOriginalLength = eChildNode.nodeValue.length ;
99
100 if ( sTrimmed.length == 0 )
101 {
102 // If the trimmed text node is empty, just remove it.
103
104 // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#81).
105 eChildNode.parentNode.removeChild( eChildNode ) ;
106 continue ;
107 }
108 else if ( sTrimmed.length < iOriginalLength )
109 {
110 // If the trimmed text length is less than the original
111 // length, strip all spaces from the end by splitting
112 // the text and removing the resulting useless node.
113
114 eChildNode.splitText( sTrimmed.length ) ;
115 // Use "node.lastChild.parentNode" instead of "node" to avoid IE bug (#81).
116 node.lastChild.parentNode.removeChild( node.lastChild ) ;
117 }
118 }
119 break ;
120 }
121
122 if ( !FCKBrowserInfo.IsIE && !FCKBrowserInfo.IsOpera )
123 {
124 eChildNode = node.lastChild ;
125
126 if ( eChildNode && eChildNode.nodeType == 1 && eChildNode.nodeName.toLowerCase() == 'br' )
127 {
128 // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324).
129 eChildNode.parentNode.removeChild( eChildNode ) ;
130 }
131 }
132 },
133
134 RemoveNode : function( node, excludeChildren )
135 {
136 if ( excludeChildren )
137 {
138 // Move all children before the node.
139 var eChild ;
140 while ( (eChild = node.firstChild) )
141 node.parentNode.insertBefore( node.removeChild( eChild ), node ) ;
142 }
143
144 return node.parentNode.removeChild( node ) ;
145 },
146
147 GetFirstChild : function( node, childNames )
148 {
149 // If childNames is a string, transform it in a Array.
150 if ( typeof ( childNames ) == 'string' )
151 childNames = [ childNames ] ;
152
153 var eChild = node.firstChild ;
154 while( eChild )
155 {
156 if ( eChild.nodeType == 1 && eChild.tagName.Equals.apply( eChild.tagName, childNames ) )
157 return eChild ;
158
159 eChild = eChild.nextSibling ;
160 }
161
162 return null ;
163 },
164
165 GetLastChild : function( node, childNames )
166 {
167 // If childNames is a string, transform it in a Array.
168 if ( typeof ( childNames ) == 'string' )
169 childNames = [ childNames ] ;
170
171 var eChild = node.lastChild ;
172 while( eChild )
173 {
174 if ( eChild.nodeType == 1 && ( !childNames || eChild.tagName.Equals( childNames ) ) )
175 return eChild ;
176
177 eChild = eChild.previousSibling ;
178 }
179
180 return null ;
181 },
182
183 /*
184 * Gets the previous element (nodeType=1) in the source order. Returns
185 * "null" If no element is found.
186 * @param {Object} currentNode The node to start searching from.
187 * @param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be
188 * handled. If set to "true", only white spaces text nodes
189 * will be ignored, while non white space text nodes will stop
190 * the search, returning null. If "false" or omitted, all
191 * text nodes are ignored.
192 * @param {string[]} stopSearchElements An array of element names that
193 * will cause the search to stop when found, returning null.
194 * May be omitted (or null).
195 * @param {string[]} ignoreElements An array of element names that
196 * must be ignored during the search.
197 */
198 GetPreviousSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements )
199 {
200 if ( !currentNode )
201 return null ;
202
203 if ( stopSearchElements && currentNode.nodeType == 1 && currentNode.nodeName.IEquals( stopSearchElements ) )
204 return null ;
205
206 if ( currentNode.previousSibling )
207 currentNode = currentNode.previousSibling ;
208 else
209 return this.GetPreviousSourceElement( currentNode.parentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;
210
211 while ( currentNode )
212 {
213 if ( currentNode.nodeType == 1 )
214 {
215 if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) )
216 break ;
217
218 if ( !ignoreElements || !currentNode.nodeName.IEquals( ignoreElements ) )
219 return currentNode ;
220 }
221 else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 )
222 break ;
223
224 if ( currentNode.lastChild )
225 currentNode = currentNode.lastChild ;
226 else
227 return this.GetPreviousSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;
228 }
229
230 return null ;
231 },
232
233 /*
234 * Gets the next element (nodeType=1) in the source order. Returns
235 * "null" If no element is found.
236 * @param {Object} currentNode The node to start searching from.
237 * @param {Boolean} ignoreSpaceTextOnly Sets how text nodes will be
238 * handled. If set to "true", only white spaces text nodes
239 * will be ignored, while non white space text nodes will stop
240 * the search, returning null. If "false" or omitted, all
241 * text nodes are ignored.
242 * @param {string[]} stopSearchElements An array of element names that
243 * will cause the search to stop when found, returning null.
244 * May be omitted (or null).
245 * @param {string[]} ignoreElements An array of element names that
246 * must be ignored during the search.
247 */
248 GetNextSourceElement : function( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements, startFromSibling )
249 {
250 while( ( currentNode = this.GetNextSourceNode( currentNode, startFromSibling ) ) ) // Only one "=".
251 {
252 if ( currentNode.nodeType == 1 )
253 {
254 if ( stopSearchElements && currentNode.nodeName.IEquals( stopSearchElements ) )
255 break ;
256
257 if ( ignoreElements && currentNode.nodeName.IEquals( ignoreElements ) )
258 return this.GetNextSourceElement( currentNode, ignoreSpaceTextOnly, stopSearchElements, ignoreElements ) ;
259
260 return currentNode ;
261 }
262 else if ( ignoreSpaceTextOnly && currentNode.nodeType == 3 && currentNode.nodeValue.RTrim().length > 0 )
263 break ;
264 }
265
266 return null ;
267 },
268
269 /*
270 * Get the next DOM node available in source order.
271 */
272 GetNextSourceNode : function( currentNode, startFromSibling, nodeType, stopSearchNode )
273 {
274 if ( !currentNode )
275 return null ;
276
277 var node ;
278
279 if ( !startFromSibling && currentNode.firstChild )
280 node = currentNode.firstChild ;
281 else
282 {
283 if ( stopSearchNode && currentNode == stopSearchNode )
284 return null ;
285
286 node = currentNode.nextSibling ;
287
288 if ( !node && ( !stopSearchNode || stopSearchNode != currentNode.parentNode ) )
289 return this.GetNextSourceNode( currentNode.parentNode, true, nodeType, stopSearchNode ) ;
290 }
291
292 if ( nodeType && node && node.nodeType != nodeType )
293 return this.GetNextSourceNode( node, false, nodeType, stopSearchNode ) ;
294
295 return node ;
296 },
297
298 /*
299 * Get the next DOM node available in source order.
300 */
301 GetPreviousSourceNode : function( currentNode, startFromSibling, nodeType, stopSearchNode )
302 {
303 if ( !currentNode )
304 return null ;
305
306 var node ;
307
308 if ( !startFromSibling && currentNode.lastChild )
309 node = currentNode.lastChild ;
310 else
311 {
312 if ( stopSearchNode && currentNode == stopSearchNode )
313 return null ;
314
315 node = currentNode.previousSibling ;
316
317 if ( !node && ( !stopSearchNode || stopSearchNode != currentNode.parentNode ) )
318 return this.GetPreviousSourceNode( currentNode.parentNode, true, nodeType, stopSearchNode ) ;
319 }
320
321 if ( nodeType && node && node.nodeType != nodeType )
322 return this.GetPreviousSourceNode( node, false, nodeType, stopSearchNode ) ;
323
324 return node ;
325 },
326
327 // Inserts a element after a existing one.
328 InsertAfterNode : function( existingNode, newNode )
329 {
330 return existingNode.parentNode.insertBefore( newNode, existingNode.nextSibling ) ;
331 },
332
333 GetParents : function( node )
334 {
335 var parents = new Array() ;
336
337 while ( node )
338 {
339 parents.unshift( node ) ;
340 node = node.parentNode ;
341 }
342
343 return parents ;
344 },
345
346 GetCommonParents : function( node1, node2 )
347 {
348 var p1 = this.GetParents( node1 ) ;
349 var p2 = this.GetParents( node2 ) ;
350 var retval = [] ;
351 for ( var i = 0 ; i < p1.length ; i++ )
352 {
353 if ( p1[i] == p2[i] )
354 retval.push( p1[i] ) ;
355 }
356 return retval ;
357 },
358
359 GetCommonParentNode : function( node1, node2, tagList )
360 {
361 var tagMap = {} ;
362 if ( ! tagList.pop )
363 tagList = [ tagList ] ;
364 while ( tagList.length > 0 )
365 tagMap[tagList.pop().toLowerCase()] = 1 ;
366
367 var commonParents = this.GetCommonParents( node1, node2 ) ;
368 var currentParent = null ;
369 while ( ( currentParent = commonParents.pop() ) )
370 {
371 if ( tagMap[currentParent.nodeName.toLowerCase()] )
372 return currentParent ;
373 }
374 return null ;
375 },
376
377 GetIndexOf : function( node )
378 {
379 var currentNode = node.parentNode ? node.parentNode.firstChild : null ;
380 var currentIndex = -1 ;
381
382 while ( currentNode )
383 {
384 currentIndex++ ;
385
386 if ( currentNode == node )
387 return currentIndex ;
388
389 currentNode = currentNode.nextSibling ;
390 }
391
392 return -1 ;
393 },
394
395 PaddingNode : null,
396
397 EnforcePaddingNode : function( doc, tagName )
398 {
399 // In IE it can happen when the page is reloaded that doc or doc.body is null, so exit here
400 try
401 {
402 if ( !doc || !doc.body )
403 return ;
404 }
405 catch (e)
406 {
407 return ;
408 }
409
410 this.CheckAndRemovePaddingNode( doc, tagName, true ) ;
411 try
412 {
413 if ( doc.body.lastChild && ( doc.body.lastChild.nodeType != 1
414 || doc.body.lastChild.tagName.toLowerCase() == tagName.toLowerCase() ) )
415 return ;
416 }
417 catch (e)
418 {
419 return ;
420 }
421
422 var node = doc.createElement( tagName ) ;
423 if ( FCKBrowserInfo.IsGecko && FCKListsLib.NonEmptyBlockElements[ tagName ] )
424 FCKTools.AppendBogusBr( node ) ;
425 this.PaddingNode = node ;
426 if ( doc.body.childNodes.length == 1
427 && doc.body.firstChild.nodeType == 1
428 && doc.body.firstChild.tagName.toLowerCase() == 'br'
429 && ( doc.body.firstChild.getAttribute( '_moz_dirty' ) != null
430 || doc.body.firstChild.getAttribute( 'type' ) == '_moz' ) )
431 doc.body.replaceChild( node, doc.body.firstChild ) ;
432 else
433 doc.body.appendChild( node ) ;
434 },
435
436 CheckAndRemovePaddingNode : function( doc, tagName, dontRemove )
437 {
438 var paddingNode = this.PaddingNode ;
439 if ( ! paddingNode )
440 return ;
441
442 // If the padding node is changed, remove its status as a padding node.
443 try
444 {
445 if ( paddingNode.parentNode != doc.body
446 || paddingNode.tagName.toLowerCase() != tagName
447 || ( paddingNode.childNodes.length > 1 )
448 || ( paddingNode.firstChild && paddingNode.firstChild.nodeValue != '\xa0'
449 && String(paddingNode.firstChild.tagName).toLowerCase() != 'br' ) )
450 {
451 this.PaddingNode = null ;
452 return ;
453 }
454 }
455 catch (e)
456 {
457 this.PaddingNode = null ;
458 return ;
459 }
460
461 // Now we're sure the padding node exists, and it is unchanged, and it
462 // isn't the only node in doc.body, remove it.
463 if ( !dontRemove )
464 {
465 if ( paddingNode.parentNode.childNodes.length > 1 )
466 paddingNode.parentNode.removeChild( paddingNode ) ;
467 this.PaddingNode = null ;
468 }
469 },
470
471 HasAttribute : function( element, attributeName )
472 {
473 if ( element.hasAttribute )
474 return element.hasAttribute( attributeName ) ;
475 else
476 {
477 var att = element.attributes[ attributeName ] ;
478 return ( att != undefined && att.specified ) ;
479 }
480 },
481
482 /**
483 * Checks if an element has "specified" attributes.
484 */
485 HasAttributes : function( element )
486 {
487 var attributes = element.attributes ;
488
489 for ( var i = 0 ; i < attributes.length ; i++ )
490 {
491 if ( FCKBrowserInfo.IsIE && attributes[i].nodeName == 'class' )
492 {
493 // IE has a strange bug. If calling removeAttribute('className'),
494 // the attributes collection will still contain the "class"
495 // attribute, which will be marked as "specified", even if the
496 // outerHTML of the element is not displaying the class attribute.
497 // Note : I was not able to reproduce it outside the editor,
498 // but I've faced it while working on the TC of #1391.
499 if ( element.className.length > 0 )
500 return true ;
501 }
502 else if ( attributes[i].specified )
503 return true ;
504 }
505
506 return false ;
507 },
508
509 /**
510 * Remove an attribute from an element.
511 */
512 RemoveAttribute : function( element, attributeName )
513 {
514 if ( FCKBrowserInfo.IsIE && attributeName.toLowerCase() == 'class' )
515 attributeName = 'className' ;
516
517 return element.removeAttribute( attributeName, 0 ) ;
518 },
519
520 /**
521 * Removes an array of attributes from an element
522 */
523 RemoveAttributes : function (element, aAttributes )
524 {
525 for ( var i = 0 ; i < aAttributes.length ; i++ )
526 this.RemoveAttribute( element, aAttributes[i] );
527 },
528
529 GetAttributeValue : function( element, att )
530 {
531 var attName = att ;
532
533 if ( typeof att == 'string' )
534 att = element.attributes[ att ] ;
535 else
536 attName = att.nodeName ;
537
538 if ( att && att.specified )
539 {
540 // IE returns "null" for the nodeValue of a "style" attribute.
541 if ( attName == 'style' )
542 return element.style.cssText ;
543 // There are two cases when the nodeValue must be used:
544 // - for the "class" attribute (all browsers).
545 // - for events attributes (IE only).
546 else if ( attName == 'class' || attName.indexOf('on') == 0 )
547 return att.nodeValue ;
548 else
549 {
550 // Use getAttribute to get its value exactly as it is
551 // defined.
552 return element.getAttribute( attName, 2 ) ;
553 }
554 }
555 return null ;
556 },
557
558 /**
559 * Checks whether one element contains the other.
560 */
561 Contains : function( mainElement, otherElement )
562 {
563 // IE supports contains, but only for element nodes.
564 if ( mainElement.contains && otherElement.nodeType == 1 )
565 return mainElement.contains( otherElement ) ;
566
567 while ( ( otherElement = otherElement.parentNode ) ) // Only one "="
568 {
569 if ( otherElement == mainElement )
570 return true ;
571 }
572 return false ;
573 },
574
575 /**
576 * Breaks a parent element in the position of one of its contained elements.
577 * For example, in the following case:
578 * <b>This <i>is some<span /> sample</i> test text</b>
579 * If element = <span />, we have these results:
580 * <b>This <i>is some</i><span /><i> sample</i> test text</b> (If parent = <i>)
581 * <b>This <i>is some</i></b><span /><b><i> sample</i> test text</b> (If parent = <b>)
582 */
583 BreakParent : function( element, parent, reusableRange )
584 {
585 var range = reusableRange || new FCKDomRange( FCKTools.GetElementWindow( element ) ) ;
586
587 // We'll be extracting part of this element, so let's use our
588 // range to get the correct piece.
589 range.SetStart( element, 4 ) ;
590 range.SetEnd( parent, 4 ) ;
591
592 // Extract it.
593 var docFrag = range.ExtractContents() ;
594
595 // Move the element outside the broken element.
596 range.InsertNode( element.parentNode.removeChild( element ) ) ;
597
598 // Re-insert the extracted piece after the element.
599 docFrag.InsertAfterNode( element ) ;
600
601 range.Release( !!reusableRange ) ;
602 },
603
604 /**
605 * Retrieves a uniquely identifiable tree address of a DOM tree node.
606 * The tree address returns is an array of integers, with each integer
607 * indicating a child index from a DOM tree node, starting from
608 * document.documentElement.
609 *
610 * For example, assuming <body> is the second child from <html> (<head>
611 * being the first), and we'd like to address the third child under the
612 * fourth child of body, the tree address returned would be:
613 * [1, 3, 2]
614 *
615 * The tree address cannot be used for finding back the DOM tree node once
616 * the DOM tree structure has been modified.
617 */
618 GetNodeAddress : function( node, normalized )
619 {
620 var retval = [] ;
621 while ( node && node != FCKTools.GetElementDocument( node ).documentElement )
622 {
623 var parentNode = node.parentNode ;
624 var currentIndex = -1 ;
625 for( var i = 0 ; i < parentNode.childNodes.length ; i++ )
626 {
627 var candidate = parentNode.childNodes[i] ;
628 if ( normalized === true &&
629 candidate.nodeType == 3 &&
630 candidate.previousSibling &&
631 candidate.previousSibling.nodeType == 3 )
632 continue;
633 currentIndex++ ;
634 if ( parentNode.childNodes[i] == node )
635 break ;
636 }
637 retval.unshift( currentIndex ) ;
638 node = node.parentNode ;
639 }
640 return retval ;
641 },
642
643 /**
644 * The reverse transformation of FCKDomTools.GetNodeAddress(). This
645 * function returns the DOM node pointed to by its index address.
646 */
647 GetNodeFromAddress : function( doc, addr, normalized )
648 {
649 var cursor = doc.documentElement ;
650 for ( var i = 0 ; i < addr.length ; i++ )
651 {
652 var target = addr[i] ;
653 if ( ! normalized )
654 {
655 cursor = cursor.childNodes[target] ;
656 continue ;
657 }
658
659 var currentIndex = -1 ;
660 for (var j = 0 ; j < cursor.childNodes.length ; j++ )
661 {
662 var candidate = cursor.childNodes[j] ;
663 if ( normalized === true &&
664 candidate.nodeType == 3 &&
665 candidate.previousSibling &&
666 candidate.previousSibling.nodeType == 3 )
667 continue ;
668 currentIndex++ ;
669 if ( currentIndex == target )
670 {
671 cursor = candidate ;
672 break ;
673 }
674 }
675 }
676 return cursor ;
677 },
678
679 CloneElement : function( element )
680 {
681 element = element.cloneNode( false ) ;
682
683 // The "id" attribute should never be cloned to avoid duplication.
684 element.removeAttribute( 'id', false ) ;
685
686 return element ;
687 },
688
689 ClearElementJSProperty : function( element, attrName )
690 {
691 if ( FCKBrowserInfo.IsIE )
692 element.removeAttribute( attrName ) ;
693 else
694 delete element[attrName] ;
695 },
696
697 SetElementMarker : function ( markerObj, element, attrName, value)
698 {
699 var id = String( parseInt( Math.random() * 0xffffffff, 10 ) ) ;
700 element._FCKMarkerId = id ;
701 element[attrName] = value ;
702 if ( ! markerObj[id] )
703 markerObj[id] = { 'element' : element, 'markers' : {} } ;
704 markerObj[id]['markers'][attrName] = value ;
705 },
706
707 ClearElementMarkers : function( markerObj, element, clearMarkerObj )
708 {
709 var id = element._FCKMarkerId ;
710 if ( ! id )
711 return ;
712 this.ClearElementJSProperty( element, '_FCKMarkerId' ) ;
713 for ( var j in markerObj[id]['markers'] )
714 this.ClearElementJSProperty( element, j ) ;
715 if ( clearMarkerObj )
716 delete markerObj[id] ;
717 },
718
719 ClearAllMarkers : function( markerObj )
720 {
721 for ( var i in markerObj )
722 this.ClearElementMarkers( markerObj, markerObj[i]['element'], true ) ;
723 },
724
725 /**
726 * Convert a DOM list tree into a data structure that is easier to
727 * manipulate. This operation should be non-intrusive in the sense that it
728 * does not change the DOM tree, with the exception that it may add some
729 * markers to the list item nodes when markerObj is specified.
730 */
731 ListToArray : function( listNode, markerObj, baseArray, baseIndentLevel, grandparentNode )
732 {
733 if ( ! listNode.nodeName.IEquals( ['ul', 'ol'] ) )
734 return [] ;
735
736 if ( ! baseIndentLevel )
737 baseIndentLevel = 0 ;
738 if ( ! baseArray )
739 baseArray = [] ;
740 // Iterate over all list items to get their contents and look for inner lists.
741 for ( var i = 0 ; i < listNode.childNodes.length ; i++ )
742 {
743 var listItem = listNode.childNodes[i] ;
744 if ( ! listItem.nodeName.IEquals( 'li' ) )
745 continue ;
746 var itemObj = { 'parent' : listNode, 'indent' : baseIndentLevel, 'contents' : [] } ;
747 if ( ! grandparentNode )
748 {
749 itemObj.grandparent = listNode.parentNode ;
750 if ( itemObj.grandparent && itemObj.grandparent.nodeName.IEquals( 'li' ) )
751 itemObj.grandparent = itemObj.grandparent.parentNode ;
752 }
753 else
754 itemObj.grandparent = grandparentNode ;
755 if ( markerObj )
756 this.SetElementMarker( markerObj, listItem, '_FCK_ListArray_Index', baseArray.length ) ;
757 baseArray.push( itemObj ) ;
758 for ( var j = 0 ; j < listItem.childNodes.length ; j++ )
759 {
760 var child = listItem.childNodes[j] ;
761 if ( child.nodeName.IEquals( ['ul', 'ol'] ) )
762 // Note the recursion here, it pushes inner list items with
763 // +1 indentation in the correct order.
764 this.ListToArray( child, markerObj, baseArray, baseIndentLevel + 1, itemObj.grandparent ) ;
765 else
766 itemObj.contents.push( child ) ;
767 }
768 }
769 return baseArray ;
770 },
771
772 // Convert our internal representation of a list back to a DOM forest.
773 ArrayToList : function( listArray, markerObj, baseIndex )
774 {
775 if ( baseIndex == undefined )
776 baseIndex = 0 ;
777 if ( ! listArray || listArray.length < baseIndex + 1 )
778 return null ;
779 var doc = FCKTools.GetElementDocument( listArray[baseIndex].parent ) ;
780 var retval = doc.createDocumentFragment() ;
781 var rootNode = null ;
782 var currentIndex = baseIndex ;
783 var indentLevel = Math.max( listArray[baseIndex].indent, 0 ) ;
784 var currentListItem = null ;
785 while ( true )
786 {
787 var item = listArray[currentIndex] ;
788 if ( item.indent == indentLevel )
789 {
790 if ( ! rootNode || listArray[currentIndex].parent.nodeName != rootNode.nodeName )
791 {
792 rootNode = listArray[currentIndex].parent.cloneNode( false ) ;
793 retval.appendChild( rootNode ) ;
794 }
795 currentListItem = doc.createElement( 'li' ) ;
796 rootNode.appendChild( currentListItem ) ;
797 for ( var i = 0 ; i < item.contents.length ; i++ )
798 currentListItem.appendChild( item.contents[i].cloneNode( true ) ) ;
799 currentIndex++ ;
800 }
801 else if ( item.indent == Math.max( indentLevel, 0 ) + 1 )
802 {
803 var listData = this.ArrayToList( listArray, null, currentIndex ) ;
804 currentListItem.appendChild( listData.listNode ) ;
805 currentIndex = listData.nextIndex ;
806 }
807 else if ( item.indent == -1 && baseIndex == 0 && item.grandparent )
808 {
809 var currentListItem ;
810 if ( item.grandparent.nodeName.IEquals( ['ul', 'ol'] ) )
811 currentListItem = doc.createElement( 'li' ) ;
812 else
813 {
814 if ( FCKConfig.EnterMode.IEquals( ['div', 'p'] ) && ! item.grandparent.nodeName.IEquals( 'td' ) )
815 currentListItem = doc.createElement( FCKConfig.EnterMode ) ;
816 else
817 currentListItem = doc.createDocumentFragment() ;
818 }
819 for ( var i = 0 ; i < item.contents.length ; i++ )
820 currentListItem.appendChild( item.contents[i].cloneNode( true ) ) ;
821 if ( currentListItem.nodeType == 11 )
822 {
823 if ( currentListItem.lastChild &&
824 currentListItem.lastChild.getAttribute &&
825 currentListItem.lastChild.getAttribute( 'type' ) == '_moz' )
826 currentListItem.removeChild( currentListItem.lastChild );
827 currentListItem.appendChild( doc.createElement( 'br' ) ) ;
828 }
829 if ( currentListItem.nodeName.IEquals( FCKConfig.EnterMode ) && currentListItem.firstChild )
830 {
831 this.TrimNode( currentListItem ) ;
832 if ( FCKListsLib.BlockBoundaries[currentListItem.firstChild.nodeName.toLowerCase()] )
833 {
834 var tmp = doc.createDocumentFragment() ;
835 while ( currentListItem.firstChild )
836 tmp.appendChild( currentListItem.removeChild( currentListItem.firstChild ) ) ;
837 currentListItem = tmp ;
838 }
839 }
840 if ( FCKBrowserInfo.IsGeckoLike && currentListItem.nodeName.IEquals( ['div', 'p'] ) )
841 FCKTools.AppendBogusBr( currentListItem ) ;
842 retval.appendChild( currentListItem ) ;
843 rootNode = null ;
844 currentIndex++ ;
845 }
846 else
847 return null ;
848
849 if ( listArray.length <= currentIndex || Math.max( listArray[currentIndex].indent, 0 ) < indentLevel )
850 {
851 break ;
852 }
853 }
854
855 // Clear marker attributes for the new list tree made of cloned nodes, if any.
856 if ( markerObj )
857 {
858 var currentNode = retval.firstChild ;
859 while ( currentNode )
860 {
861 if ( currentNode.nodeType == 1 )
862 this.ClearElementMarkers( markerObj, currentNode ) ;
863 currentNode = this.GetNextSourceNode( currentNode ) ;
864 }
865 }
866
867 return { 'listNode' : retval, 'nextIndex' : currentIndex } ;
868 },
869
870 /**
871 * Get the next sibling node for a node. If "includeEmpties" is false,
872 * only element or non empty text nodes are returned.
873 */
874 GetNextSibling : function( node, includeEmpties )
875 {
876 node = node.nextSibling ;
877
878 while ( node && !includeEmpties && node.nodeType != 1 && ( node.nodeType != 3 || node.nodeValue.length == 0 ) )
879 node = node.nextSibling ;
880
881 return node ;
882 },
883
884 /**
885 * Get the previous sibling node for a node. If "includeEmpties" is false,
886 * only element or non empty text nodes are returned.
887 */
888 GetPreviousSibling : function( node, includeEmpties )
889 {
890 node = node.previousSibling ;
891
892 while ( node && !includeEmpties && node.nodeType != 1 && ( node.nodeType != 3 || node.nodeValue.length == 0 ) )
893 node = node.previousSibling ;
894
895 return node ;
896 },
897
898 /**
899 * Checks if an element has no "useful" content inside of it
900 * node tree. No "useful" content means empty text node or a signle empty
901 * inline node.
902 * elementCheckCallback may point to a function that returns a boolean
903 * indicating that a child element must be considered in the element check.
904 */
905 CheckIsEmptyElement : function( element, elementCheckCallback )
906 {
907 var child = element.firstChild ;
908 var elementChild ;
909
910 while ( child )
911 {
912 if ( child.nodeType == 1 )
913 {
914 if ( elementChild || !FCKListsLib.InlineNonEmptyElements[ child.nodeName.toLowerCase() ] )
915 return false ;
916
917 if ( !elementCheckCallback || elementCheckCallback( child ) === true )
918 elementChild = child ;
919 }
920 else if ( child.nodeType == 3 && child.nodeValue.length > 0 )
921 return false ;
922
923 child = child.nextSibling ;
924 }
925
926 return elementChild ? this.CheckIsEmptyElement( elementChild, elementCheckCallback ) : true ;
927 },
928
929 SetElementStyles : function( element, styleDict )
930 {
931 var style = element.style ;
932 for ( var styleName in styleDict )
933 style[ styleName ] = styleDict[ styleName ] ;
934 },
935
936 SetOpacity : function( element, opacity )
937 {
938 if ( FCKBrowserInfo.IsIE )
939 {
940 opacity = Math.round( opacity * 100 ) ;
941 element.style.filter = ( opacity > 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' ) ;
942 }
943 else
944 element.style.opacity = opacity ;
945 },
946
947 GetCurrentElementStyle : function( element, propertyName )
948 {
949 if ( FCKBrowserInfo.IsIE )
950 return element.currentStyle[ propertyName ] ;
951 else
952 return element.ownerDocument.defaultView.getComputedStyle( element, '' ).getPropertyValue( propertyName ) ;
953 },
954
955 GetPositionedAncestor : function( element )
956 {
957 var currentElement = element ;
958
959 while ( currentElement != FCKTools.GetElementDocument( currentElement ).documentElement )
960 {
961 if ( this.GetCurrentElementStyle( currentElement, 'position' ) != 'static' )
962 return currentElement ;
963
964 if ( currentElement == FCKTools.GetElementDocument( currentElement ).documentElement
965 && currentWindow != w )
966 currentElement = currentWindow.frameElement ;
967 else
968 currentElement = currentElement.parentNode ;
969 }
970
971 return null ;
972 },
973
974 /**
975 * Current implementation for ScrollIntoView (due to #1462 and #2279). We
976 * don't have a complete implementation here, just the things that fit our
977 * needs.
978 */
979 ScrollIntoView : function( element, alignTop )
980 {
981 // Get the element window.
982 var window = FCKTools.GetElementWindow( element ) ;
983 var windowHeight = FCKTools.GetViewPaneSize( window ).Height ;
984
985 // Starts the offset that will be scrolled with the negative value of
986 // the visible window height.
987 var offset = windowHeight * -1 ;
988
989 // Appends the height it we are about to align the bottoms.
990 if ( alignTop === false )
991 {
992 offset += element.offsetHeight || 0 ;
993
994 // Consider the margin in the scroll, which is ok for our current
995 // needs, but needs investigation if we will be using this function
996 // in other places.
997 offset += parseInt( this.GetCurrentElementStyle( element, 'marginBottom' ) || 0, 10 ) || 0 ;
998 }
999
1000 // Appends the offsets for the entire element hierarchy.
1001 var elementPosition = FCKTools.GetDocumentPosition( window, element ) ;
1002 offset += elementPosition.y ;
1003
1004 // Scroll the window to the desired position, if not already visible.
1005 var currentScroll = FCKTools.GetScrollPosition( window ).Y ;
1006 if ( offset > 0 && ( offset > currentScroll || offset < currentScroll - windowHeight ) )
1007 window.scrollTo( 0, offset ) ;
1008 },
1009
1010 /**
1011 * Check if the element can be edited inside the browser.
1012 */
1013 CheckIsEditable : function( element )
1014 {
1015 // Get the element name.
1016 var nodeName = element.nodeName.toLowerCase() ;
1017
1018 // Get the element DTD (defaults to span for unknown elements).
1019 var childDTD = FCK.DTD[ nodeName ] || FCK.DTD.span ;
1020
1021 // In the DTD # == text node.
1022 return ( childDTD['#'] && !FCKListsLib.NonEditableElements[ nodeName ] ) ;
1023 },
1024
1025 GetSelectedDivContainers : function()
1026 {
1027 var currentBlocks = [] ;
1028 var range = new FCKDomRange( FCK.EditorWindow ) ;
1029 range.MoveToSelection() ;
1030
1031 var startNode = range.GetTouchedStartNode() ;
1032 var endNode = range.GetTouchedEndNode() ;
1033 var currentNode = startNode ;
1034
1035 if ( startNode == endNode )
1036 {
1037 while ( endNode.nodeType == 1 && endNode.lastChild )
1038 endNode = endNode.lastChild ;
1039 endNode = FCKDomTools.GetNextSourceNode( endNode ) ;
1040 }
1041
1042 while ( currentNode && currentNode != endNode )
1043 {
1044 if ( currentNode.nodeType != 3 || !/^[ \t\n]*$/.test( currentNode.nodeValue ) )
1045 {
1046 var path = new FCKElementPath( currentNode ) ;
1047 var blockLimit = path.BlockLimit ;
1048 if ( blockLimit && blockLimit.nodeName.IEquals( 'div' ) && currentBlocks.IndexOf( blockLimit ) == -1 )
1049 currentBlocks.push( blockLimit ) ;
1050 }
1051
1052 currentNode = FCKDomTools.GetNextSourceNode( currentNode ) ;
1053 }
1054
1055 return currentBlocks ;
1056 }
1057 } ;
1058