fckdomrange.js
上传用户:dbstep
上传日期:2022-08-06
资源大小:2803k
文件大小:27k
源码类别:

WEB源码(ASP,PHP,...)

开发平台:

ASP/ASPX

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