/source/Plug-in/fck/editor/_source/classes/fckdomrange.js

http://prosporous.googlecode.com/ · JavaScript · 905 lines · 593 code · 149 blank · 163 comment · 198 complexity · c3ca361322c5800f5f3c22e3e7b0b438 MD5 · raw file

  1. /*
  2. * FCKeditor - The text editor for Internet - http://www.fckeditor.net
  3. * Copyright (C) 2003-2007 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. * Class for working with a selection range, much like the W3C DOM Range, but
  22. * it is not intended to be an implementation of the W3C interface.
  23. */
  24. var FCKDomRange = function( sourceWindow )
  25. {
  26. this.Window = sourceWindow ;
  27. this._Cache = {} ;
  28. }
  29. FCKDomRange.prototype =
  30. {
  31. _UpdateElementInfo : function()
  32. {
  33. var innerRange = this._Range ;
  34. if ( !innerRange )
  35. this.Release( true ) ;
  36. else
  37. {
  38. // For text nodes, the node itself is the StartNode.
  39. var eStart = innerRange.startContainer ;
  40. var eEnd = innerRange.endContainer ;
  41. var oElementPath = new FCKElementPath( eStart ) ;
  42. this.StartNode = eStart.nodeType == 3 ? eStart : eStart.childNodes[ innerRange.startOffset ] ;
  43. this.StartContainer = eStart ;
  44. this.StartBlock = oElementPath.Block ;
  45. this.StartBlockLimit = oElementPath.BlockLimit ;
  46. if ( eStart != eEnd )
  47. oElementPath = new FCKElementPath( eEnd ) ;
  48. // The innerRange.endContainer[ innerRange.endOffset ] is not
  49. // usually part of the range, but the marker for the range end. So,
  50. // let's get the previous available node as the real end.
  51. var eEndNode = eEnd ;
  52. if ( innerRange.endOffset == 0 )
  53. {
  54. while ( eEndNode && !eEndNode.previousSibling )
  55. eEndNode = eEndNode.parentNode ;
  56. if ( eEndNode )
  57. eEndNode = eEndNode.previousSibling ;
  58. }
  59. else if ( eEndNode.nodeType == 1 )
  60. eEndNode = eEndNode.childNodes[ innerRange.endOffset - 1 ] ;
  61. this.EndNode = eEndNode ;
  62. this.EndContainer = eEnd ;
  63. this.EndBlock = oElementPath.Block ;
  64. this.EndBlockLimit = oElementPath.BlockLimit ;
  65. }
  66. this._Cache = {} ;
  67. },
  68. CreateRange : function()
  69. {
  70. return new FCKW3CRange( this.Window.document ) ;
  71. },
  72. DeleteContents : function()
  73. {
  74. if ( this._Range )
  75. {
  76. this._Range.deleteContents() ;
  77. this._UpdateElementInfo() ;
  78. }
  79. },
  80. ExtractContents : function()
  81. {
  82. if ( this._Range )
  83. {
  84. var docFrag = this._Range.extractContents() ;
  85. this._UpdateElementInfo() ;
  86. return docFrag ;
  87. }
  88. },
  89. CheckIsCollapsed : function()
  90. {
  91. if ( this._Range )
  92. return this._Range.collapsed ;
  93. },
  94. Collapse : function( toStart )
  95. {
  96. if ( this._Range )
  97. this._Range.collapse( toStart ) ;
  98. this._UpdateElementInfo() ;
  99. },
  100. Clone : function()
  101. {
  102. var oClone = FCKTools.CloneObject( this ) ;
  103. if ( this._Range )
  104. oClone._Range = this._Range.cloneRange() ;
  105. return oClone ;
  106. },
  107. MoveToNodeContents : function( targetNode )
  108. {
  109. if ( !this._Range )
  110. this._Range = this.CreateRange() ;
  111. this._Range.selectNodeContents( targetNode ) ;
  112. this._UpdateElementInfo() ;
  113. },
  114. MoveToElementStart : function( targetElement )
  115. {
  116. this.SetStart(targetElement,1) ;
  117. this.SetEnd(targetElement,1) ;
  118. },
  119. // Moves to the first editing point inside a element. For example, in a
  120. // element tree like "<p><b><i></i></b> Text</p>", the start editing point
  121. // is "<p><b><i>^</i></b> Text</p>" (inside <i>).
  122. MoveToElementEditStart : function( targetElement )
  123. {
  124. var child ;
  125. while ( ( child = targetElement.firstChild ) && child.nodeType == 1 && FCKListsLib.EmptyElements[ child.nodeName.toLowerCase() ] == null )
  126. targetElement = child ;
  127. this.MoveToElementStart( targetElement ) ;
  128. },
  129. InsertNode : function( node )
  130. {
  131. if ( this._Range )
  132. this._Range.insertNode( node ) ;
  133. },
  134. CheckIsEmpty : function()
  135. {
  136. if ( this.CheckIsCollapsed() )
  137. return true ;
  138. // Inserts the contents of the range in a div tag.
  139. var eToolDiv = this.Window.document.createElement( 'div' ) ;
  140. this._Range.cloneContents().AppendTo( eToolDiv ) ;
  141. FCKDomTools.TrimNode( eToolDiv ) ;
  142. return ( eToolDiv.innerHTML.length == 0 ) ;
  143. },
  144. /**
  145. * Checks if the start boundary of the current range is "visually" (like a
  146. * selection caret) at the beginning of the block. It means that some
  147. * things could be brefore the range, like spaces or empty inline elements,
  148. * but it would still be considered at the beginning of the block.
  149. */
  150. CheckStartOfBlock : function()
  151. {
  152. var cache = this._Cache ;
  153. var bIsStartOfBlock = cache.IsStartOfBlock ;
  154. if ( bIsStartOfBlock != undefined )
  155. return bIsStartOfBlock ;
  156. // Take the block reference.
  157. var block = this.StartBlock || this.StartBlockLimit ;
  158. var container = this._Range.startContainer ;
  159. var offset = this._Range.startOffset ;
  160. var currentNode ;
  161. if ( offset > 0 )
  162. {
  163. // First, check the start container. If it is a text node, get the
  164. // substring of the node value before the range offset.
  165. if ( container.nodeType == 3 )
  166. {
  167. var textValue = container.nodeValue.substr( 0, offset ).Trim() ;
  168. // If we have some text left in the container, we are not at
  169. // the end for the block.
  170. if ( textValue.length != 0 )
  171. return cache.IsStartOfBlock = false ;
  172. }
  173. else
  174. currentNode = container.childNodes[ offset - 1 ] ;
  175. }
  176. // We'll not have a currentNode if the container was a text node, or
  177. // the offset is zero.
  178. if ( !currentNode )
  179. currentNode = FCKDomTools.GetPreviousSourceNode( container, true, null, block ) ;
  180. while ( currentNode )
  181. {
  182. switch ( currentNode.nodeType )
  183. {
  184. case 1 :
  185. // It's not an inline element.
  186. if ( !FCKListsLib.InlineChildReqElements[ currentNode.nodeName.toLowerCase() ] )
  187. return cache.IsStartOfBlock = false ;
  188. break ;
  189. case 3 :
  190. // It's a text node with real text.
  191. if ( currentNode.nodeValue.Trim().length > 0 )
  192. return cache.IsStartOfBlock = false ;
  193. }
  194. currentNode = FCKDomTools.GetPreviousSourceNode( currentNode, false, null, block ) ;
  195. }
  196. return cache.IsStartOfBlock = true ;
  197. },
  198. /**
  199. * Checks if the end boundary of the current range is "visually" (like a
  200. * selection caret) at the end of the block. It means that some things
  201. * could be after the range, like spaces, empty inline elements, or a
  202. * single <br>, but it would still be considered at the end of the block.
  203. */
  204. CheckEndOfBlock : function( refreshSelection )
  205. {
  206. var isEndOfBlock = this._Cache.IsEndOfBlock ;
  207. if ( isEndOfBlock != undefined )
  208. return isEndOfBlock ;
  209. // Take the block reference.
  210. var block = this.EndBlock || this.EndBlockLimit ;
  211. var container = this._Range.endContainer ;
  212. var offset = this._Range.endOffset ;
  213. var currentNode ;
  214. // First, check the end container. If it is a text node, get the
  215. // substring of the node value after the range offset.
  216. if ( container.nodeType == 3 )
  217. {
  218. var textValue = container.nodeValue ;
  219. if ( offset < textValue.length )
  220. {
  221. textValue = textValue.substr( offset ) ;
  222. // If we have some text left in the container, we are not at
  223. // the end for the block.
  224. if ( textValue.Trim().length != 0 )
  225. return this._Cache.IsEndOfBlock = false ;
  226. }
  227. }
  228. else
  229. currentNode = container.childNodes[ offset ] ;
  230. // We'll not have a currentNode if the container was a text node, of
  231. // the offset is out the container children limits (after it probably).
  232. if ( !currentNode )
  233. currentNode = FCKDomTools.GetNextSourceNode( container, true, null, block ) ;
  234. var hadBr = false ;
  235. while ( currentNode )
  236. {
  237. switch ( currentNode.nodeType )
  238. {
  239. case 1 :
  240. var nodeName = currentNode.nodeName.toLowerCase() ;
  241. // It's an inline element.
  242. if ( FCKListsLib.InlineChildReqElements[ nodeName ] )
  243. break ;
  244. // It is the first <br> found.
  245. if ( nodeName == 'br' && !hadBr )
  246. {
  247. hadBr = true ;
  248. break ;
  249. }
  250. return this._Cache.IsEndOfBlock = false ;
  251. case 3 :
  252. // It's a text node with real text.
  253. if ( currentNode.nodeValue.Trim().length > 0 )
  254. return this._Cache.IsEndOfBlock = false ;
  255. }
  256. currentNode = FCKDomTools.GetNextSourceNode( currentNode, false, null, block ) ;
  257. }
  258. if ( refreshSelection )
  259. this.Select() ;
  260. return this._Cache.IsEndOfBlock = true ;
  261. },
  262. // This is an "intrusive" way to create a bookmark. It includes <span> tags
  263. // in the range boundaries. The advantage of it is that it is possible to
  264. // handle DOM mutations when moving back to the bookmark.
  265. // Attention: the inclusion of nodes in the DOM is a design choice and
  266. // should not be changed as there are other points in the code that may be
  267. // using those nodes to perform operations. See GetBookmarkNode.
  268. // For performance, includeNodes=true if intended to SelectBookmark.
  269. CreateBookmark : function( includeNodes )
  270. {
  271. // Create the bookmark info (random IDs).
  272. var oBookmark =
  273. {
  274. StartId : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S',
  275. EndId : (new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E'
  276. } ;
  277. var oDoc = this.Window.document ;
  278. var eStartSpan ;
  279. var eEndSpan ;
  280. var oClone ;
  281. // For collapsed ranges, add just the start marker.
  282. if ( !this.CheckIsCollapsed() )
  283. {
  284. eEndSpan = oDoc.createElement( 'span' ) ;
  285. eEndSpan.style.display = 'none' ;
  286. eEndSpan.id = oBookmark.EndId ;
  287. eEndSpan.setAttribute( '_fck_bookmark', true ) ;
  288. // For IE, it must have something inside, otherwise it may be
  289. // removed during DOM operations.
  290. // if ( FCKBrowserInfo.IsIE )
  291. eEndSpan.innerHTML = '&nbsp;' ;
  292. oClone = this.Clone() ;
  293. oClone.Collapse( false ) ;
  294. oClone.InsertNode( eEndSpan ) ;
  295. }
  296. eStartSpan = oDoc.createElement( 'span' ) ;
  297. eStartSpan.style.display = 'none' ;
  298. eStartSpan.id = oBookmark.StartId ;
  299. eStartSpan.setAttribute( '_fck_bookmark', true ) ;
  300. // For IE, it must have something inside, otherwise it may be removed
  301. // during DOM operations.
  302. // if ( FCKBrowserInfo.IsIE )
  303. eStartSpan.innerHTML = '&nbsp;' ;
  304. oClone = this.Clone() ;
  305. oClone.Collapse( true ) ;
  306. oClone.InsertNode( eStartSpan ) ;
  307. if ( includeNodes )
  308. {
  309. oBookmark.StartNode = eStartSpan ;
  310. oBookmark.EndNode = eEndSpan ;
  311. }
  312. // Update the range position.
  313. if ( eEndSpan )
  314. {
  315. this.SetStart( eStartSpan, 4 ) ;
  316. this.SetEnd( eEndSpan, 3 ) ;
  317. }
  318. else
  319. this.MoveToPosition( eStartSpan, 4 ) ;
  320. return oBookmark ;
  321. },
  322. // This one should be a part of a hypothetic "bookmark" object.
  323. GetBookmarkNode : function( bookmark, start )
  324. {
  325. var doc = this.Window.document ;
  326. if ( start )
  327. return bookmark.StartNode || doc.getElementById( bookmark.StartId ) ;
  328. else
  329. return bookmark.EndNode || doc.getElementById( bookmark.EndId ) ;
  330. },
  331. MoveToBookmark : function( bookmark, preserveBookmark )
  332. {
  333. var eStartSpan = this.GetBookmarkNode( bookmark, true ) ;
  334. var eEndSpan = this.GetBookmarkNode( bookmark, false ) ;
  335. this.SetStart( eStartSpan, 3 ) ;
  336. if ( !preserveBookmark )
  337. FCKDomTools.RemoveNode( eStartSpan ) ;
  338. // If collapsed, the end span will not be available.
  339. if ( eEndSpan )
  340. {
  341. this.SetEnd( eEndSpan, 3 ) ;
  342. if ( !preserveBookmark )
  343. FCKDomTools.RemoveNode( eEndSpan ) ;
  344. }
  345. else
  346. this.Collapse( true ) ;
  347. this._UpdateElementInfo() ;
  348. },
  349. // Non-intrusive bookmark algorithm
  350. CreateBookmark2 : function()
  351. {
  352. // If there is no range then get out of here.
  353. // It happens on initial load in Safari #962 and if the editor it's hidden also in Firefox
  354. if ( ! this._Range )
  355. return { "Start" : 0, "End" : 0 } ;
  356. // First, we record down the offset values
  357. var bookmark =
  358. {
  359. "Start" : [ this._Range.startOffset ],
  360. "End" : [ this._Range.endOffset ]
  361. } ;
  362. // Since we're treating the document tree as normalized, we need to backtrack the text lengths
  363. // of previous text nodes into the offset value.
  364. var curStart = this._Range.startContainer.previousSibling ;
  365. var curEnd = this._Range.endContainer.previousSibling ;
  366. // Also note that the node that we use for "address base" would change during backtracking.
  367. var addrStart = this._Range.startContainer ;
  368. var addrEnd = this._Range.endContainer ;
  369. while ( curStart && curStart.nodeType == 3 )
  370. {
  371. bookmark.Start[0] += curStart.length ;
  372. addrStart = curStart ;
  373. curStart = curStart.previousSibling ;
  374. }
  375. while ( curEnd && curEnd.nodeType == 3 )
  376. {
  377. bookmark.End[0] += curEnd.length ;
  378. addrEnd = curEnd ;
  379. curEnd = curEnd.previousSibling ;
  380. }
  381. // If the object pointed to by the startOffset and endOffset are text nodes, we need
  382. // to backtrack and add in the text offset to the bookmark addresses.
  383. if ( addrStart.nodeType == 1 && addrStart.childNodes[bookmark.Start[0]] && addrStart.childNodes[bookmark.Start[0]].nodeType == 3 )
  384. {
  385. var curNode = addrStart.childNodes[bookmark.Start[0]] ;
  386. var offset = 0 ;
  387. while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
  388. {
  389. curNode = curNode.previousSibling ;
  390. offset += curNode.length ;
  391. }
  392. addrStart = curNode ;
  393. bookmark.Start[0] = offset ;
  394. }
  395. if ( addrEnd.nodeType == 1 && addrEnd.childNodes[bookmark.End[0]] && addrEnd.childNodes[bookmark.End[0]].nodeType == 3 )
  396. {
  397. var curNode = addrEnd.childNodes[bookmark.End[0]] ;
  398. var offset = 0 ;
  399. while ( curNode.previousSibling && curNode.previousSibling.nodeType == 3 )
  400. {
  401. curNode = curNode.previousSibling ;
  402. offset += curNode.length ;
  403. }
  404. addrEnd = curNode ;
  405. bookmark.End[0] = offset ;
  406. }
  407. // Then, we record down the precise position of the container nodes
  408. // by walking up the DOM tree and counting their childNode index
  409. bookmark.Start = FCKDomTools.GetNodeAddress( addrStart, true ).concat( bookmark.Start ) ;
  410. bookmark.End = FCKDomTools.GetNodeAddress( addrEnd, true ).concat( bookmark.End ) ;
  411. return bookmark;
  412. },
  413. MoveToBookmark2 : function( bookmark )
  414. {
  415. // Reverse the childNode counting algorithm in CreateBookmark2()
  416. var curStart = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.Start.slice( 0, -1 ), true ) ;
  417. var curEnd = FCKDomTools.GetNodeFromAddress( this.Window.document, bookmark.End.slice( 0, -1 ), true ) ;
  418. // Generate the W3C Range object and update relevant data
  419. this.Release( true ) ;
  420. this._Range = new FCKW3CRange( this.Window.document ) ;
  421. var startOffset = bookmark.Start[ bookmark.Start.length - 1 ] ;
  422. var endOffset = bookmark.End[ bookmark.End.length - 1 ] ;
  423. while ( curStart.nodeType == 3 && startOffset > curStart.length )
  424. {
  425. if ( ! curStart.nextSibling || curStart.nextSibling.nodeType != 3 )
  426. break ;
  427. startOffset -= curStart.length ;
  428. curStart = curStart.nextSibling ;
  429. }
  430. while ( curEnd.nodeType == 3 && endOffset > curEnd.length )
  431. {
  432. if ( ! curEnd.nextSibling || curEnd.nextSibling.nodeType != 3 )
  433. break ;
  434. endOffset -= curEnd.length ;
  435. curEnd = curEnd.nextSibling ;
  436. }
  437. this._Range.setStart( curStart, startOffset ) ;
  438. this._Range.setEnd( curEnd, endOffset ) ;
  439. this._UpdateElementInfo() ;
  440. },
  441. MoveToPosition : function( targetElement, position )
  442. {
  443. this.SetStart( targetElement, position ) ;
  444. this.Collapse( true ) ;
  445. },
  446. /*
  447. * Moves the position of the start boundary of the range to a specific position
  448. * relatively to a element.
  449. * @position:
  450. * 1 = After Start <target>^contents</target>
  451. * 2 = Before End <target>contents^</target>
  452. * 3 = Before Start ^<target>contents</target>
  453. * 4 = After End <target>contents</target>^
  454. */
  455. SetStart : function( targetElement, position, noInfoUpdate )
  456. {
  457. var oRange = this._Range ;
  458. if ( !oRange )
  459. oRange = this._Range = this.CreateRange() ;
  460. switch( position )
  461. {
  462. case 1 : // After Start <target>^contents</target>
  463. oRange.setStart( targetElement, 0 ) ;
  464. break ;
  465. case 2 : // Before End <target>contents^</target>
  466. oRange.setStart( targetElement, targetElement.childNodes.length ) ;
  467. break ;
  468. case 3 : // Before Start ^<target>contents</target>
  469. oRange.setStartBefore( targetElement ) ;
  470. break ;
  471. case 4 : // After End <target>contents</target>^
  472. oRange.setStartAfter( targetElement ) ;
  473. }
  474. if ( !noInfoUpdate )
  475. this._UpdateElementInfo() ;
  476. },
  477. /*
  478. * Moves the position of the start boundary of the range to a specific position
  479. * relatively to a element.
  480. * @position:
  481. * 1 = After Start <target>^contents</target>
  482. * 2 = Before End <target>contents^</target>
  483. * 3 = Before Start ^<target>contents</target>
  484. * 4 = After End <target>contents</target>^
  485. */
  486. SetEnd : function( targetElement, position, noInfoUpdate )
  487. {
  488. var oRange = this._Range ;
  489. if ( !oRange )
  490. oRange = this._Range = this.CreateRange() ;
  491. switch( position )
  492. {
  493. case 1 : // After Start <target>^contents</target>
  494. oRange.setEnd( targetElement, 0 ) ;
  495. break ;
  496. case 2 : // Before End <target>contents^</target>
  497. oRange.setEnd( targetElement, targetElement.childNodes.length ) ;
  498. break ;
  499. case 3 : // Before Start ^<target>contents</target>
  500. oRange.setEndBefore( targetElement ) ;
  501. break ;
  502. case 4 : // After End <target>contents</target>^
  503. oRange.setEndAfter( targetElement ) ;
  504. }
  505. if ( !noInfoUpdate )
  506. this._UpdateElementInfo() ;
  507. },
  508. Expand : function( unit )
  509. {
  510. var oNode, oSibling ;
  511. switch ( unit )
  512. {
  513. // Expand the range to include all inline parent elements if we are
  514. // are in their boundary limits.
  515. // For example (where [ ] are the range limits):
  516. // Before => Some <b>[<i>Some sample text]</i></b>.
  517. // After => Some [<b><i>Some sample text</i></b>].
  518. case 'inline_elements' :
  519. // Expand the start boundary.
  520. if ( this._Range.startOffset == 0 )
  521. {
  522. oNode = this._Range.startContainer ;
  523. if ( oNode.nodeType != 1 )
  524. oNode = oNode.previousSibling ? null : oNode.parentNode ;
  525. if ( oNode )
  526. {
  527. while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
  528. {
  529. this._Range.setStartBefore( oNode ) ;
  530. if ( oNode != oNode.parentNode.firstChild )
  531. break ;
  532. oNode = oNode.parentNode ;
  533. }
  534. }
  535. }
  536. // Expand the end boundary.
  537. oNode = this._Range.endContainer ;
  538. var offset = this._Range.endOffset ;
  539. if ( ( oNode.nodeType == 3 && offset >= oNode.nodeValue.length ) || ( oNode.nodeType == 1 && offset >= oNode.childNodes.length ) || ( oNode.nodeType != 1 && oNode.nodeType != 3 ) )
  540. {
  541. if ( oNode.nodeType != 1 )
  542. oNode = oNode.nextSibling ? null : oNode.parentNode ;
  543. if ( oNode )
  544. {
  545. while ( FCKListsLib.InlineNonEmptyElements[ oNode.nodeName.toLowerCase() ] )
  546. {
  547. this._Range.setEndAfter( oNode ) ;
  548. if ( oNode != oNode.parentNode.lastChild )
  549. break ;
  550. oNode = oNode.parentNode ;
  551. }
  552. }
  553. }
  554. break ;
  555. case 'block_contents' :
  556. case 'list_contents' :
  557. var boundarySet = FCKListsLib.BlockBoundaries ;
  558. if ( unit == 'list_contents' || FCKConfig.EnterMode == 'br' )
  559. boundarySet = FCKListsLib.ListBoundaries ;
  560. if ( this.StartBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' )
  561. this.SetStart( this.StartBlock, 1 ) ;
  562. else
  563. {
  564. // Get the start node for the current range.
  565. oNode = this._Range.startContainer ;
  566. // If it is an element, get the node right before of it (in source order).
  567. if ( oNode.nodeType == 1 )
  568. {
  569. var lastNode = oNode.childNodes[ this._Range.startOffset ] ;
  570. if ( lastNode )
  571. oNode = FCKDomTools.GetPreviousSourceNode( lastNode, true ) ;
  572. else
  573. oNode = oNode.lastChild || oNode ;
  574. }
  575. // We must look for the left boundary, relative to the range
  576. // start, which is limited by a block element.
  577. while ( oNode
  578. && ( oNode.nodeType != 1
  579. || ( oNode != this.StartBlockLimit
  580. && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
  581. {
  582. this._Range.setStartBefore( oNode ) ;
  583. oNode = oNode.previousSibling || oNode.parentNode ;
  584. }
  585. }
  586. if ( this.EndBlock && FCKConfig.EnterMode != 'br' && unit == 'block_contents' && this.EndBlock.nodeName.toLowerCase() != 'li' )
  587. this.SetEnd( this.EndBlock, 2 ) ;
  588. else
  589. {
  590. oNode = this._Range.endContainer ;
  591. if ( oNode.nodeType == 1 )
  592. oNode = oNode.childNodes[ this._Range.endOffset ] || oNode.lastChild ;
  593. // We must look for the right boundary, relative to the range
  594. // end, which is limited by a block element.
  595. while ( oNode
  596. && ( oNode.nodeType != 1
  597. || ( oNode != this.StartBlockLimit
  598. && !boundarySet[ oNode.nodeName.toLowerCase() ] ) ) )
  599. {
  600. this._Range.setEndAfter( oNode ) ;
  601. oNode = oNode.nextSibling || oNode.parentNode ;
  602. }
  603. // In EnterMode='br', the end <br> boundary element must
  604. // be included in the expanded range.
  605. if ( oNode && oNode.nodeName.toLowerCase() == 'br' )
  606. this._Range.setEndAfter( oNode ) ;
  607. }
  608. this._UpdateElementInfo() ;
  609. }
  610. },
  611. /**
  612. * Split the block element for the current range. It deletes the contents
  613. * of the range and splits the block in the collapsed position, resulting
  614. * in two sucessive blocks. The range is then positioned in the middle of
  615. * them.
  616. *
  617. * It returns and object with the following properties:
  618. * - PreviousBlock : a reference to the block element that preceeds
  619. * the range after the split.
  620. * - NextBlock : a reference to the block element that preceeds the
  621. * range after the split.
  622. * - WasStartOfBlock : a boolean indicating that the range was
  623. * originaly at the start of the block.
  624. * - WasEndOfBlock : a boolean indicating that the range was originaly
  625. * at the end of the block.
  626. *
  627. * If the range was originaly at the start of the block, no split will happen
  628. * and the PreviousBlock value will be null. The same is valid for the
  629. * NextBlock value if the range was at the end of the block.
  630. */
  631. SplitBlock : function()
  632. {
  633. if ( !this._Range )
  634. this.MoveToSelection() ;
  635. // The range boundaries must be in the same "block limit" element.
  636. if ( this.StartBlockLimit == this.EndBlockLimit )
  637. {
  638. // Get the current blocks.
  639. var eStartBlock = this.StartBlock ;
  640. var eEndBlock = this.EndBlock ;
  641. var oElementPath = null ;
  642. if ( FCKConfig.EnterMode != 'br' )
  643. {
  644. if ( !eStartBlock )
  645. {
  646. eStartBlock = this.FixBlock( true ) ;
  647. eEndBlock = this.EndBlock ; // FixBlock may have fixed the EndBlock too.
  648. }
  649. if ( !eEndBlock )
  650. eEndBlock = this.FixBlock( false ) ;
  651. }
  652. // Get the range position.
  653. var bIsStartOfBlock = ( eStartBlock != null && this.CheckStartOfBlock() ) ;
  654. var bIsEndOfBlock = ( eEndBlock != null && this.CheckEndOfBlock() ) ;
  655. // Delete the current contents.
  656. if ( !this.CheckIsEmpty() )
  657. this.DeleteContents() ;
  658. if ( eStartBlock && eEndBlock && eStartBlock == eEndBlock )
  659. {
  660. if ( bIsEndOfBlock )
  661. {
  662. oElementPath = new FCKElementPath( this.StartContainer ) ;
  663. this.MoveToPosition( eEndBlock, 4 ) ;
  664. eEndBlock = null ;
  665. }
  666. else if ( bIsStartOfBlock )
  667. {
  668. oElementPath = new FCKElementPath( this.StartContainer ) ;
  669. this.MoveToPosition( eStartBlock, 3 ) ;
  670. eStartBlock = null ;
  671. }
  672. else
  673. {
  674. // Extract the contents of the block from the selection point to the end of its contents.
  675. this.SetEnd( eStartBlock, 2 ) ;
  676. var eDocFrag = this.ExtractContents() ;
  677. // Duplicate the block element after it.
  678. eEndBlock = eStartBlock.cloneNode( false ) ;
  679. eEndBlock.removeAttribute( 'id', false ) ;
  680. // Place the extracted contents in the duplicated block.
  681. eDocFrag.AppendTo( eEndBlock ) ;
  682. FCKDomTools.InsertAfterNode( eStartBlock, eEndBlock ) ;
  683. this.MoveToPosition( eStartBlock, 4 ) ;
  684. // In Gecko, the last child node must be a bogus <br>.
  685. // Note: bogus <br> added under <ul> or <ol> would cause lists to be incorrectly rendered.
  686. if ( FCKBrowserInfo.IsGecko &&
  687. ! eStartBlock.nodeName.IEquals( ['ul', 'ol'] ) )
  688. FCKTools.AppendBogusBr( eStartBlock ) ;
  689. }
  690. }
  691. return {
  692. PreviousBlock : eStartBlock,
  693. NextBlock : eEndBlock,
  694. WasStartOfBlock : bIsStartOfBlock,
  695. WasEndOfBlock : bIsEndOfBlock,
  696. ElementPath : oElementPath
  697. } ;
  698. }
  699. return null ;
  700. },
  701. // Transform a block without a block tag in a valid block (orphan text in the body or td, usually).
  702. FixBlock : function( isStart )
  703. {
  704. // Bookmark the range so we can restore it later.
  705. var oBookmark = this.CreateBookmark() ;
  706. // Collapse the range to the requested ending boundary.
  707. this.Collapse( isStart ) ;
  708. // Expands it to the block contents.
  709. this.Expand( 'block_contents' ) ;
  710. // Create the fixed block.
  711. var oFixedBlock = this.Window.document.createElement( FCKConfig.EnterMode ) ;
  712. // Move the contents of the temporary range to the fixed block.
  713. this.ExtractContents().AppendTo( oFixedBlock ) ;
  714. FCKDomTools.TrimNode( oFixedBlock ) ;
  715. // Insert the fixed block into the DOM.
  716. this.InsertNode( oFixedBlock ) ;
  717. // Move the range back to the bookmarked place.
  718. this.MoveToBookmark( oBookmark ) ;
  719. return oFixedBlock ;
  720. },
  721. Release : function( preserveWindow )
  722. {
  723. if ( !preserveWindow )
  724. this.Window = null ;
  725. this.StartNode = null ;
  726. this.StartContainer = null ;
  727. this.StartBlock = null ;
  728. this.StartBlockLimit = null ;
  729. this.EndNode = null ;
  730. this.EndContainer = null ;
  731. this.EndBlock = null ;
  732. this.EndBlockLimit = null ;
  733. this._Range = null ;
  734. this._Cache = null ;
  735. },
  736. CheckHasRange : function()
  737. {
  738. return !!this._Range ;
  739. },
  740. GetTouchedStartNode : function()
  741. {
  742. var range = this._Range ;
  743. var container = range.startContainer ;
  744. if ( range.collapsed || container.nodeType != 1 )
  745. return container ;
  746. return container.childNodes[ range.startOffset ] || container ;
  747. },
  748. GetTouchedEndNode : function()
  749. {
  750. var range = this._Range ;
  751. var container = range.endContainer ;
  752. if ( range.collapsed || container.nodeType != 1 )
  753. return container ;
  754. return container.childNodes[ range.endOffset - 1 ] || container ;
  755. }
  756. } ;