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