1 /** 2 * Range implementation across browsers for kissy editor. Modified from CKEditor. 3 * @author yiminghe@gmail.com 4 */ 5 /* 6 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. 7 For licensing, see LICENSE.html or http://ckeditor.com/license 8 */ 9 KISSY.add("editor/core/range", function (S, Editor, Utils, Walker, ElementPath) { 10 /** 11 * Enum for range 12 * @enum {number} 13 */ 14 Editor.RANGE = { 15 POSITION_AFTER_START:1, // <element>^contents</element> "^text" 16 POSITION_BEFORE_END:2, // <element>contents^</element> "text^" 17 POSITION_BEFORE_START:3, // ^<element>contents</element> ^"text" 18 POSITION_AFTER_END:4, // <element>contents</element>^ "text"^ 19 ENLARGE_ELEMENT:1, 20 ENLARGE_BLOCK_CONTENTS:2, 21 ENLARGE_LIST_ITEM_CONTENTS:3, 22 START:1, 23 END:2, 24 SHRINK_ELEMENT:1, 25 SHRINK_TEXT:2 26 }; 27 28 var TRUE = true, 29 FALSE = false, 30 NULL = null, 31 KER = Editor.RANGE, 32 KEP = Editor.POSITION, 33 DOM = S.DOM, 34 UA = S.UA, 35 dtd = Editor.XHTML_DTD, 36 Node = S.Node, 37 $ = Node.all, 38 EMPTY = {area:1, base:1, br:1, col:1, hr:1, img:1, input:1, link:1, meta:1, param:1}; 39 40 var isWhitespace = new Walker.whitespaces(), 41 isBookmark = new Walker.bookmark(), 42 isNotWhitespaces = Walker.whitespaces(TRUE), 43 isNotBookmarks = Walker.bookmark(false, true); 44 45 var inlineChildReqElements = { abbr:1, acronym:1, b:1, bdo:1, 46 big:1, cite:1, code:1, del:1, dfn:1, 47 em:1, font:1, i:1, ins:1, label:1, 48 kbd:1, q:1, samp:1, small:1, span:1, 49 strike:1, strong:1, sub:1, sup:1, tt:1, u:1, 'var':1 }; 50 51 // Evaluator for checkBoundaryOfElement, reject any 52 // text node and non-empty elements unless it's being bookmark text. 53 function elementBoundaryEval(node) { 54 // Reject any text node unless it's being bookmark 55 // OR it's spaces. (#3883) 56 // 如果不是文本节点并且是空的,可以继续取下一个判断边界 57 var c1 = node.nodeType != DOM.TEXT_NODE && 58 DOM.nodeName(node) in dtd.$removeEmpty, 59 // 文本为空,可以继续取下一个判断边界 60 c2 = node.nodeType == DOM.TEXT_NODE && !S.trim(node.nodeValue), 61 // 恩,进去了书签,可以继续取下一个判断边界 62 c3 = !!node.parentNode.getAttribute('_ke_bookmark'); 63 return c1 || c2 || c3; 64 } 65 66 function nonWhitespaceOrIsBookmark(node) { 67 // Whitespaces and bookmark nodes are to be ignored. 68 return !isWhitespace(node) && !isBookmark(node); 69 } 70 71 function getCheckStartEndBlockEvalFunction(isStart) { 72 var hadBr = FALSE; 73 return function (node) { 74 // First ignore bookmark nodes. 75 if (isBookmark(node)) 76 return TRUE; 77 78 if (node.nodeType == DOM.TEXT_NODE) { 79 // If there's any visible text, then we're not at the start. 80 if (S.trim(node.nodeValue).length) { 81 return FALSE; 82 } 83 } else if (node.nodeType == DOM.ELEMENT_NODE) { 84 var nodeName = DOM.nodeName(node); 85 // If there are non-empty inline elements (e.g. <img />), then we're not 86 // at the start. 87 if (!inlineChildReqElements[ nodeName ]) { 88 // If we're working at the end-of-block, forgive the first <br /> in non-IE 89 // browsers. 90 if (!isStart && !UA['ie'] && nodeName == 'br' && !hadBr) { 91 hadBr = TRUE; 92 } else { 93 return FALSE; 94 } 95 } 96 } 97 return TRUE; 98 }; 99 } 100 101 102 /** 103 * Extract html content within range. 104 * @param {Number} action 105 * 0 : delete 106 * 1 : extract 107 * 2 : clone 108 */ 109 function execContentsAction(self, action) { 110 var startNode = self.startContainer, 111 endNode = self.endContainer, 112 startOffset = self.startOffset, 113 endOffset = self.endOffset, 114 removeStartNode, 115 hasSplitStart = FALSE, 116 hasSplitEnd = FALSE, 117 t, 118 docFrag = undefined, 119 doc = self.document, 120 removeEndNode; 121 122 if (action > 0) { 123 docFrag = doc.createDocumentFragment(); 124 } 125 126 if (self.collapsed) { 127 return docFrag; 128 } 129 130 // 将 bookmark 包含在选区内 131 self.optimizeBookmark(); 132 133 134 // endNode -> end guard , not included in range 135 136 // For text containers, we must simply split the node and point to the 137 // second part. The removal will be handled by the rest of the code . 138 //最关键:一般起始都是在文字节点中,得到起点选择右边的文字节点,只对节点处理! 139 if (endNode[0].nodeType == DOM.TEXT_NODE) { 140 hasSplitEnd = TRUE; 141 endNode = endNode._4e_splitText(endOffset); 142 } else { 143 // If the end container has children and the offset is pointing 144 // to a child, then we should start from it. 145 if (endNode[0].childNodes.length > 0) { 146 // If the offset points after the last node. 147 if (endOffset >= endNode[0].childNodes.length) { 148 // Let's create a temporary node and mark it for removal. 149 endNode = new Node( 150 endNode[0].appendChild(doc.createTextNode("")) 151 ); 152 removeEndNode = TRUE; 153 } else { 154 endNode = new Node(endNode[0].childNodes[endOffset]); 155 } 156 } 157 } 158 159 // startNode -> start guard , not included in range 160 161 // For text containers, we must simply split the node. The removal will 162 // be handled by the rest of the code . 163 if (startNode[0].nodeType == DOM.TEXT_NODE) { 164 hasSplitStart = TRUE; 165 startNode._4e_splitText(startOffset); 166 } else { 167 // If the start container has children and the offset is pointing 168 // to a child, then we should start from its previous sibling. 169 170 // If the offset points to the first node, we don't have a 171 // sibling, so let's use the first one, but mark it for removal. 172 if (!startOffset) { 173 // Let's create a temporary node and mark it for removal. 174 t = new Node(doc.createTextNode("")); 175 startNode.prepend(t); 176 startNode = t; 177 removeStartNode = TRUE; 178 } 179 else if (startOffset >= startNode[0].childNodes.length) { 180 // Let's create a temporary node and mark it for removal. 181 startNode = new Node(startNode[0] 182 .appendChild(doc.createTextNode(''))); 183 removeStartNode = TRUE; 184 } else 185 startNode = new Node( 186 startNode[0].childNodes[startOffset].previousSibling 187 ); 188 } 189 190 // Get the parent nodes tree for the start and end boundaries. 191 //从根到自己 192 var startParents = startNode._4e_parents(), 193 endParents = endNode._4e_parents(); 194 195 startParents.each(function (n, i) { 196 startParents[i] = n; 197 }); 198 199 endParents.each(function (n, i) { 200 endParents[i] = n; 201 }); 202 203 204 // Compare them, to find the top most siblings. 205 var i, topStart, topEnd; 206 207 for (i = 0; i < startParents.length; i++) { 208 topStart = startParents[ i ]; 209 topEnd = endParents[ i ]; 210 211 // The compared nodes will match until we find the top most 212 // siblings (different nodes that have the same parent). 213 // "i" will hold the index in the parents array for the top 214 // most element. 215 if (!topStart.equals(topEnd)) { 216 break; 217 } 218 } 219 220 var clone = docFrag, 221 levelStartNode, 222 levelClone, 223 currentNode, 224 currentSibling; 225 226 // Remove all successive sibling nodes for every node in the 227 // startParents tree. 228 for (var j = i; j < startParents.length; j++) { 229 levelStartNode = startParents[j]; 230 231 // For Extract and Clone, we must clone this level. 232 if (action > 0 && !levelStartNode.equals(startNode)) { 233 // action = 0 = Delete 234 levelClone = clone.appendChild(levelStartNode.clone()[0]); 235 } else { 236 levelClone = null; 237 } 238 239 // 开始节点的路径所在父节点不能 clone(TRUE),其他节点(结束节点路径左边的节点)可以直接 clone(true) 240 currentNode = levelStartNode[0].nextSibling; 241 242 var endParentJ = endParents[ j ], 243 domEndNode = endNode[0], 244 domEndParentJ = endParentJ && endParentJ[0]; 245 246 while (currentNode) { 247 // Stop processing when the current node matches a node in the 248 // endParents tree or if it is the endNode. 249 if (domEndParentJ == currentNode || domEndNode == currentNode) { 250 break; 251 } 252 253 // Cache the next sibling. 254 currentSibling = currentNode.nextSibling; 255 256 // If cloning, just clone it. 257 if (action == 2) { 258 // 2 = Clone 259 clone.appendChild(currentNode.cloneNode(TRUE)); 260 } else { 261 // Both Delete and Extract will remove the node. 262 DOM._4e_remove(currentNode); 263 264 // When Extracting, move the removed node to the docFrag. 265 if (action == 1) { 266 // 1 = Extract 267 clone.appendChild(currentNode); 268 } 269 } 270 271 currentNode = currentSibling; 272 } 273 // 开始节点的路径所在父节点不能 clone(TRUE),要在后面深入子节点处理 274 if (levelClone) { 275 clone = levelClone; 276 } 277 } 278 279 clone = docFrag; 280 281 // Remove all previous sibling nodes for every node in the 282 // endParents tree. 283 for (var k = i; k < endParents.length; k++) { 284 levelStartNode = endParents[ k ]; 285 286 // For Extract and Clone, we must clone this level. 287 if (action > 0 && !levelStartNode.equals(endNode)) { 288 // action = 0 = Delete 289 // 浅复制 290 levelClone = clone.appendChild(levelStartNode.clone()[0]); 291 } else { 292 levelClone = null; 293 } 294 295 // The processing of siblings may have already been done by the parent. 296 if ( 297 !startParents[ k ] || 298 // 前面 startParents 循环已经处理过了 299 !levelStartNode._4e_sameLevel(startParents[ k ]) 300 ) { 301 currentNode = levelStartNode[0].previousSibling; 302 while (currentNode) { 303 // Cache the next sibling. 304 currentSibling = currentNode.previousSibling; 305 306 // If cloning, just clone it. 307 if (action == 2) { // 2 = Clone 308 clone.insertBefore(currentNode.cloneNode(TRUE), 309 clone.firstChild); 310 } else { 311 // Both Delete and Extract will remove the node. 312 DOM._4e_remove(currentNode); 313 314 // When Extracting, mode the removed node to the docFrag. 315 if (action == 1) { 316 // 1 = Extract 317 clone.insertBefore(currentNode, clone.firstChild); 318 } 319 } 320 321 currentNode = currentSibling; 322 } 323 } 324 325 if (levelClone) { 326 clone = levelClone; 327 } 328 } 329 // 2 = Clone. 330 if (action == 2) { 331 332 // No changes in the DOM should be done, so fix the split text (if any). 333 334 if (hasSplitStart) { 335 var startTextNode = startNode[0]; 336 if (startTextNode.nodeType == DOM.TEXT_NODE 337 && startTextNode.nextSibling 338 // careful, next sibling should be text node 339 && startTextNode.nextSibling.nodeType == DOM.TEXT_NODE) { 340 startTextNode.data += startTextNode.nextSibling.data; 341 startTextNode.parentNode.removeChild(startTextNode.nextSibling); 342 } 343 } 344 345 if (hasSplitEnd) { 346 var endTextNode = endNode[0]; 347 if (endTextNode.nodeType == DOM.TEXT_NODE && 348 endTextNode.previousSibling && 349 endTextNode.previousSibling.nodeType == DOM.TEXT_NODE) { 350 endTextNode.previousSibling.data += endTextNode.data; 351 endTextNode.parentNode.removeChild(endTextNode); 352 } 353 } 354 355 } else { 356 357 // Collapse the range. 358 // If a node has been partially selected, collapse the range between 359 // topStart and topEnd. Otherwise, simply collapse it to the start. 360 // (W3C specs). 361 if ( 362 topStart && topEnd && 363 ( 364 !startNode._4e_sameLevel(topStart) 365 || 366 !endNode._4e_sameLevel(topEnd) 367 ) 368 ) { 369 var startIndex = topStart._4e_index(); 370 371 // If the start node is to be removed, we must correct the 372 // index to reflect the removal. 373 if (removeStartNode && 374 // startNode 和 topStart 同级 375 (topStart._4e_sameLevel(startNode))) { 376 startIndex--; 377 } 378 379 self.setStart(topStart.parent(), startIndex + 1); 380 } 381 382 // Collapse it to the start. 383 self.collapse(TRUE); 384 385 } 386 387 // Cleanup any marked node. 388 if (removeStartNode) { 389 startNode.remove(); 390 } 391 392 if (removeEndNode) { 393 endNode.remove(); 394 } 395 396 return docFrag; 397 } 398 399 function updateCollapsed(self) { 400 self.collapsed = ( 401 self.startContainer && 402 self.endContainer && 403 self.startContainer[0] == self.endContainer[0] && 404 self.startOffset == self.endOffset ); 405 } 406 407 408 /** 409 * @memberOf Editor 410 * @class 411 * Range implementation across browsers. 412 * @param document {Document} 413 * @name Range 414 */ 415 function KERange(document) { 416 var self = this; 417 self.startContainer = NULL; 418 self.startOffset = NULL; 419 self.endContainer = NULL; 420 self.endOffset = NULL; 421 self.collapsed = TRUE; 422 self.document = document; 423 } 424 425 S.augment(KERange, 426 /** 427 * @lends Editor.Range 428 */ 429 { 430 431 /** 432 * Range string representation. 433 */ 434 toString:function () { 435 var s = [], 436 self = this, 437 startContainer = self.startContainer[0], 438 endContainer = self.endContainer[0]; 439 s.push((startContainer.id || startContainer.nodeName) + ":" + self.startOffset); 440 s.push((endContainer.id || endContainer.nodeName) + ":" + self.endOffset); 441 return s.join("<br/>"); 442 }, 443 444 /** 445 * Transforms the startContainer and endContainer properties from text 446 * nodes to element nodes, whenever possible. This is actually possible 447 * if either of the boundary containers point to a text node, and its 448 * offset is set to zero, or after the last char in the node. 449 */ 450 optimize:function () { 451 var self = this, 452 container = self.startContainer, 453 offset = self.startOffset; 454 455 if (container[0].nodeType != DOM.ELEMENT_NODE) { 456 if (!offset) { 457 self.setStartBefore(container); 458 } else if (offset >= container[0].nodeValue.length) { 459 self.setStartAfter(container); 460 } 461 } 462 463 container = self.endContainer; 464 offset = self.endOffset; 465 466 if (container[0].nodeType != DOM.ELEMENT_NODE) { 467 if (!offset) { 468 self.setEndBefore(container); 469 } else if (offset >= container[0].nodeValue.length) { 470 self.setEndAfter(container); 471 } 472 } 473 }, 474 475 /** 476 * Set range start after node 477 * @param {NodeList} node 478 */ 479 setStartAfter:function (node) { 480 this.setStart(node.parent(), node._4e_index() + 1); 481 }, 482 /** 483 * Set range start before node 484 * @param {NodeList} node 485 */ 486 setStartBefore:function (node) { 487 this.setStart(node.parent(), node._4e_index()); 488 }, 489 /** 490 * Set range end after node 491 * @param {NodeList} node 492 */ 493 setEndAfter:function (node) { 494 this.setEnd(node.parent(), node._4e_index() + 1); 495 }, 496 /** 497 * Set range end before node 498 * @param {NodeList} node 499 */ 500 setEndBefore:function (node) { 501 this.setEnd(node.parent(), node._4e_index()); 502 }, 503 504 /** 505 * Make edge bookmarks included in current range. 506 */ 507 optimizeBookmark:function () { 508 var self = this, 509 startNode = self.startContainer, 510 endNode = self.endContainer; 511 512 if (startNode && 513 startNode.nodeName() == 'span' && 514 startNode.attr('_ke_bookmark')) { 515 self.setStartBefore(startNode); 516 } 517 if (endNode && 518 endNode.nodeName() == 'span' && 519 endNode.attr('_ke_bookmark')) { 520 self.setEndAfter(endNode); 521 } 522 }, 523 524 /** 525 * Sets the start position of a Range. 526 * @param {NodeList} startNode The node to start the range. 527 * @param {Number} startOffset An integer greater than or equal to zero 528 * representing the offset for the start of the range from the start 529 * of startNode. 530 */ 531 setStart:function (startNode, startOffset) { 532 // W3C requires a check for the new position. If it is after the end 533 // boundary, the range should be collapsed to the new start. It seams 534 // we will not need this check for our use of this class so we can 535 // ignore it for now. 536 537 // Fixing invalid range start inside dtd empty elements. 538 var self = this; 539 if (startNode[0].nodeType == DOM.ELEMENT_NODE && EMPTY[ startNode.nodeName() ]) { 540 startNode = startNode.parent(); 541 startOffset = startNode._4e_index(); 542 } 543 544 self.startContainer = startNode; 545 self.startOffset = startOffset; 546 547 if (!self.endContainer) { 548 self.endContainer = startNode; 549 self.endOffset = startOffset; 550 } 551 552 updateCollapsed(self); 553 }, 554 555 /** 556 * Sets the end position of a Range. 557 * @param {NodeList} endNode The node to end the range. 558 * @param {Number} endOffset An integer greater than or equal to zero 559 * representing the offset for the end of the range from the start 560 * of endNode. 561 */ 562 setEnd:function (endNode, endOffset) { 563 // W3C requires a check for the new position. If it is before the start 564 // boundary, the range should be collapsed to the new end. It seams we 565 // will not need this check for our use of this class so we can ignore 566 // it for now. 567 568 // Fixing invalid range end inside dtd empty elements. 569 var self = this; 570 if (endNode[0].nodeType == DOM.ELEMENT_NODE && EMPTY[ endNode.nodeName() ]) { 571 endNode = endNode.parent(); 572 endOffset = endNode._4e_index() + 1; 573 } 574 575 self.endContainer = endNode; 576 self.endOffset = endOffset; 577 578 if (!self.startContainer) { 579 self.startContainer = endNode; 580 self.startOffset = endOffset; 581 } 582 583 updateCollapsed(self); 584 }, 585 586 /** 587 * Sets the start position of a Range by specified rules. 588 * @param {NodeList} node 589 * @param {Number} position 590 */ 591 setStartAt:function (node, position) { 592 var self = this; 593 switch (position) { 594 case KER.POSITION_AFTER_START : 595 self.setStart(node, 0); 596 break; 597 598 case KER.POSITION_BEFORE_END : 599 if (node[0].nodeType == DOM.TEXT_NODE) { 600 self.setStart(node, node[0].nodeValue.length); 601 } else { 602 self.setStart(node, node[0].childNodes.length); 603 } 604 break; 605 606 case KER.POSITION_BEFORE_START : 607 self.setStartBefore(node); 608 break; 609 610 case KER.POSITION_AFTER_END : 611 self.setStartAfter(node); 612 } 613 614 updateCollapsed(self); 615 }, 616 617 /** 618 * Sets the end position of a Range by specified rules. 619 * @param {NodeList} node 620 * @param {Number} position 621 */ 622 setEndAt:function (node, position) { 623 var self = this; 624 switch (position) { 625 case KER.POSITION_AFTER_START : 626 self.setEnd(node, 0); 627 break; 628 629 case KER.POSITION_BEFORE_END : 630 if (node[0].nodeType == DOM.TEXT_NODE) { 631 self.setEnd(node, node[0].nodeValue.length); 632 } else { 633 self.setEnd(node, node[0].childNodes.length); 634 } 635 break; 636 637 case KER.POSITION_BEFORE_START : 638 self.setEndBefore(node); 639 break; 640 641 case KER.POSITION_AFTER_END : 642 self.setEndAfter(node); 643 } 644 645 updateCollapsed(self); 646 }, 647 648 /** 649 * Clone html content within range 650 */ 651 cloneContents:function () { 652 return execContentsAction(this, 2); 653 }, 654 655 /** 656 * Remove html content within range 657 */ 658 deleteContents:function () { 659 return execContentsAction(this, 0); 660 }, 661 662 /** 663 * Extract html content within range. 664 */ 665 extractContents:function () { 666 return execContentsAction(this, 1); 667 }, 668 669 /** 670 * Collpase current range 671 * @param {Boolean} toStart 672 */ 673 collapse:function (toStart) { 674 var self = this; 675 if (toStart) { 676 self.endContainer = self.startContainer; 677 self.endOffset = self.startOffset; 678 } else { 679 self.startContainer = self.endContainer; 680 self.startOffset = self.endOffset; 681 } 682 self.collapsed = TRUE; 683 }, 684 685 /** 686 * Clone current range. 687 * @return {Editor.Range} 688 */ 689 clone:function () { 690 var self = this, 691 clone = new KERange(self.document); 692 693 clone.startContainer = self.startContainer; 694 clone.startOffset = self.startOffset; 695 clone.endContainer = self.endContainer; 696 clone.endOffset = self.endOffset; 697 clone.collapsed = self.collapsed; 698 699 return clone; 700 }, 701 702 /** 703 * Get node which is enclosed by range. 704 * @example 705 * <code> 706 * ^<book/><span/><book/>^ 707 * => 708 * ^<span/>^ 709 * </code> 710 */ 711 getEnclosedNode:function () { 712 var walkerRange = this.clone(); 713 714 // Optimize and analyze the range to avoid DOM destructive nature of walker. 715 walkerRange.optimize(); 716 717 if (walkerRange.startContainer[0].nodeType != DOM.ELEMENT_NODE || 718 walkerRange.endContainer[0].nodeType != DOM.ELEMENT_NODE) { 719 return NULL; 720 } 721 722 var walker = new Walker(walkerRange), 723 node, pre; 724 725 walker.evaluator = function (node) { 726 return isNotWhitespaces(node) && isNotBookmarks(node); 727 }; 728 729 //深度优先遍历的第一个元素 730 // x 731 // y z 732 // x->y ,return y 733 node = walker.next(); 734 walker.reset(); 735 pre = walker.previous(); 736 //前后相等,则脱一层皮 :) 737 return node && node.equals(pre) ? node : NULL; 738 }, 739 740 /** 741 * Shrink range to its innermost element.(make sure text content is unchanged) 742 * @param mode 743 * @param {Boolean} [selectContents] 744 */ 745 shrink:function (mode, selectContents) { 746 // Unable to shrink a collapsed range. 747 var self = this; 748 if (!self.collapsed) { 749 mode = mode || KER.SHRINK_TEXT; 750 751 var walkerRange = self.clone(), 752 startContainer = self.startContainer, 753 endContainer = self.endContainer, 754 startOffset = self.startOffset, 755 endOffset = self.endOffset, 756 // Whether the start/end boundary is movable. 757 moveStart = TRUE, 758 currentElement, 759 walker, 760 moveEnd = TRUE; 761 762 if (startContainer && 763 startContainer[0].nodeType == DOM.TEXT_NODE) { 764 if (!startOffset) { 765 walkerRange.setStartBefore(startContainer); 766 } else if (startOffset >= startContainer[0].nodeValue.length) { 767 walkerRange.setStartAfter(startContainer); 768 } else { 769 // Enlarge the range properly to avoid walker making 770 // DOM changes caused by trimming the text nodes later. 771 walkerRange.setStartBefore(startContainer); 772 moveStart = FALSE; 773 } 774 } 775 776 if (endContainer && 777 endContainer[0].nodeType == DOM.TEXT_NODE) { 778 if (!endOffset) { 779 walkerRange.setEndBefore(endContainer); 780 } else if (endOffset >= endContainer[0].nodeValue.length) { 781 walkerRange.setEndAfter(endContainer); 782 } else { 783 walkerRange.setEndAfter(endContainer); 784 moveEnd = FALSE; 785 } 786 } 787 788 if (moveStart || moveEnd) { 789 790 walker = new Walker(walkerRange); 791 792 walker.evaluator = function (node) { 793 return node.nodeType == ( mode == KER.SHRINK_ELEMENT ? 794 DOM.ELEMENT_NODE : DOM.TEXT_NODE ); 795 }; 796 797 walker.guard = function (node, movingOut) { 798 // Stop when we're shrink in element mode while encountering a text node. 799 if (mode == KER.SHRINK_ELEMENT && 800 node.nodeType == DOM.TEXT_NODE) { 801 return FALSE; 802 } 803 // Stop when we've already walked "through" an element. 804 if (movingOut && node == currentElement) { 805 return FALSE; 806 } 807 if (!movingOut && node.nodeType == DOM.ELEMENT_NODE) { 808 currentElement = node; 809 } 810 return TRUE; 811 }; 812 813 } 814 815 if (moveStart) { 816 var textStart = walker[mode == KER.SHRINK_ELEMENT ? 'lastForward' : 'next'](); 817 if (textStart) { 818 self.setStartAt(textStart, selectContents ? KER.POSITION_AFTER_START : KER.POSITION_BEFORE_START); 819 } 820 } 821 822 if (moveEnd) { 823 walker.reset(); 824 var textEnd = walker[mode == KER.SHRINK_ELEMENT ? 'lastBackward' : 'previous'](); 825 if (textEnd) { 826 self.setEndAt(textEnd, selectContents ? KER.POSITION_BEFORE_END : KER.POSITION_AFTER_END); 827 } 828 } 829 830 return moveStart || moveEnd; 831 } 832 }, 833 834 /** 835 * Create virtual bookmark by remeber its position index. 836 * @param normalized 837 */ 838 createBookmark2:function (normalized) { 839 840 var self = this, 841 startContainer = self.startContainer, 842 endContainer = self.endContainer, 843 startOffset = self.startOffset, 844 endOffset = self.endOffset, 845 child, previous; 846 847 // If there is no range then get out of here. 848 // It happens on initial load in Safari #962 and if the editor it's 849 // hidden also in Firefox 850 if (!startContainer || !endContainer) { 851 return { 852 start:0, 853 end:0 854 }; 855 } 856 857 if (normalized) { 858 // Find out if the start is pointing to a text node that will 859 // be normalized. 860 if (startContainer[0].nodeType == DOM.ELEMENT_NODE) { 861 child = new Node(startContainer[0].childNodes[startOffset]); 862 863 // In this case, move the start information to that text 864 // node. 865 if (child && child[0] && child[0].nodeType == DOM.TEXT_NODE 866 && startOffset > 0 && child[0].previousSibling.nodeType == DOM.TEXT_NODE) { 867 startContainer = child; 868 startOffset = 0; 869 } 870 871 } 872 873 // Normalize the start. 874 while (startContainer[0].nodeType == DOM.TEXT_NODE 875 && ( previous = startContainer.prev(undefined, 1) ) 876 && previous[0].nodeType == DOM.TEXT_NODE) { 877 startContainer = previous; 878 startOffset += previous[0].nodeValue.length; 879 } 880 881 // Process the end only if not normalized. 882 if (!self.collapsed) { 883 // Find out if the start is pointing to a text node that 884 // will be normalized. 885 if (endContainer[0].nodeType == DOM.ELEMENT_NODE) { 886 child = new Node(endContainer[0].childNodes[endOffset]); 887 888 // In this case, move the start information to that 889 // text node. 890 if (child && child[0] && 891 child[0].nodeType == DOM.TEXT_NODE && endOffset > 0 && 892 child[0].previousSibling.nodeType == DOM.TEXT_NODE) { 893 endContainer = child; 894 endOffset = 0; 895 } 896 } 897 898 // Normalize the end. 899 while (endContainer[0].nodeType == DOM.TEXT_NODE 900 && ( previous = endContainer.prev(undefined, 1) ) 901 && previous[0].nodeType == DOM.TEXT_NODE) { 902 endContainer = previous; 903 endOffset += previous[0].nodeValue.length; 904 } 905 } 906 } 907 908 return { 909 start:startContainer._4e_address(normalized), 910 end:self.collapsed ? NULL : endContainer._4e_address(normalized), 911 startOffset:startOffset, 912 endOffset:endOffset, 913 normalized:normalized, 914 is2:TRUE // It's a createBookmark2 bookmark. 915 }; 916 }, 917 /** 918 * Create bookmark by create bookmark node. 919 * @param {Boolean} [serializable] 920 */ 921 createBookmark:function (serializable) { 922 var startNode, 923 endNode, 924 baseId, 925 clone, 926 self = this, 927 collapsed = self.collapsed; 928 startNode = new Node("<span>", NULL, self.document); 929 startNode.attr('_ke_bookmark', 1); 930 startNode.css('display', 'none'); 931 932 // For IE, it must have something inside, otherwise it may be 933 // removed during DOM operations. 934 startNode.html(' '); 935 936 if (serializable) { 937 baseId = S.guid('ke_bm_'); 938 startNode.attr('id', baseId + 'S'); 939 } 940 941 // If collapsed, the endNode will not be created. 942 if (!collapsed) { 943 endNode = startNode.clone(); 944 endNode.html(' '); 945 946 if (serializable) { 947 endNode.attr('id', baseId + 'E'); 948 } 949 950 clone = self.clone(); 951 clone.collapse(); 952 clone.insertNode(endNode); 953 } 954 955 clone = self.clone(); 956 clone.collapse(TRUE); 957 clone.insertNode(startNode); 958 959 // Update the range position. 960 if (endNode) { 961 self.setStartAfter(startNode); 962 self.setEndBefore(endNode); 963 } else { 964 self.moveToPosition(startNode, KER.POSITION_AFTER_END); 965 } 966 967 return { 968 startNode:serializable ? baseId + 'S' : startNode, 969 endNode:serializable ? baseId + 'E' : endNode, 970 serializable:serializable, 971 collapsed:collapsed 972 }; 973 }, 974 975 /** 976 * Set the start posititon and then collapse range. 977 * @param {NodeList} node 978 * @param {Number} position 979 */ 980 moveToPosition:function (node, position) { 981 var self = this; 982 self.setStartAt(node, position); 983 self.collapse(TRUE); 984 }, 985 986 /** 987 * Pull range out of text edge and split text node if range is in the middle of text node. 988 * @param {Boolean} ignoreStart 989 * @param {Boolean} ignoreEnd 990 */ 991 trim:function (ignoreStart, ignoreEnd) { 992 var self = this, 993 startContainer = self.startContainer, 994 startOffset = self.startOffset, 995 collapsed = self.collapsed; 996 997 if (( !ignoreStart || collapsed ) && 998 startContainer[0] && 999 startContainer[0].nodeType == DOM.TEXT_NODE) { 1000 // If the offset is zero, we just insert the new node before 1001 // the start. 1002 if (!startOffset) { 1003 startOffset = startContainer._4e_index(); 1004 startContainer = startContainer.parent(); 1005 } 1006 // If the offset is at the end, we'll insert it after the text 1007 // node. 1008 else if (startOffset >= startContainer[0].nodeValue.length) { 1009 startOffset = startContainer._4e_index() + 1; 1010 startContainer = startContainer.parent(); 1011 } 1012 // In other case, we split the text node and insert the new 1013 // node at the split point. 1014 else { 1015 var nextText = startContainer._4e_splitText(startOffset); 1016 1017 startOffset = startContainer._4e_index() + 1; 1018 startContainer = startContainer.parent(); 1019 1020 // Check all necessity of updating the end boundary. 1021 if (DOM.equals(self.startContainer, self.endContainer)) { 1022 self.setEnd(nextText, self.endOffset - self.startOffset); 1023 } else if (DOM.equals(startContainer, self.endContainer)) { 1024 self.endOffset += 1; 1025 } 1026 } 1027 1028 self.setStart(startContainer, startOffset); 1029 1030 if (collapsed) { 1031 self.collapse(TRUE); 1032 return; 1033 } 1034 } 1035 1036 var endContainer = self.endContainer, 1037 endOffset = self.endOffset; 1038 1039 if (!( ignoreEnd || collapsed ) && 1040 endContainer[0] && endContainer[0].nodeType == DOM.TEXT_NODE) { 1041 // If the offset is zero, we just insert the new node before 1042 // the start. 1043 if (!endOffset) { 1044 endOffset = endContainer._4e_index(); 1045 endContainer = endContainer.parent(); 1046 } 1047 // If the offset is at the end, we'll insert it after the text 1048 // node. 1049 else if (endOffset >= endContainer[0].nodeValue.length) { 1050 endOffset = endContainer._4e_index() + 1; 1051 endContainer = endContainer.parent(); 1052 } 1053 // In other case, we split the text node and insert the new 1054 // node at the split point. 1055 else { 1056 endContainer._4e_splitText(endOffset); 1057 1058 endOffset = endContainer._4e_index() + 1; 1059 endContainer = endContainer.parent(); 1060 } 1061 1062 self.setEnd(endContainer, endOffset); 1063 } 1064 }, 1065 /** 1066 * Insert a new node at start position of current range 1067 * @param {NodeList} node 1068 */ 1069 insertNode:function (node) { 1070 var self = this; 1071 self.optimizeBookmark(); 1072 self.trim(FALSE, TRUE); 1073 var startContainer = self.startContainer, 1074 startOffset = self.startOffset, 1075 nextNode = startContainer[0].childNodes[startOffset] || null; 1076 1077 startContainer[0].insertBefore(node[0], nextNode); 1078 // Check if we need to update the end boundary. 1079 if (startContainer[0] == self.endContainer[0]) { 1080 self.endOffset++; 1081 } 1082 // Expand the range to embrace the new node. 1083 self.setStartBefore(node); 1084 }, 1085 1086 /** 1087 * Move range to previous saved bookmark. 1088 * @param bookmark 1089 */ 1090 moveToBookmark:function (bookmark) { 1091 var self = this, 1092 doc = $(self.document); 1093 if (bookmark.is2) { 1094 // Get the start information. 1095 var startContainer = doc._4e_getByAddress(bookmark.start, bookmark.normalized), 1096 startOffset = bookmark.startOffset, 1097 endContainer = bookmark.end && doc._4e_getByAddress(bookmark.end, bookmark.normalized), 1098 endOffset = bookmark.endOffset; 1099 1100 // Set the start boundary. 1101 self.setStart(startContainer, startOffset); 1102 1103 // Set the end boundary. If not available, collapse it. 1104 if (endContainer) { 1105 self.setEnd(endContainer, endOffset); 1106 } else { 1107 self.collapse(TRUE); 1108 } 1109 } else { 1110 // Created with createBookmark(). 1111 var serializable = bookmark.serializable, 1112 startNode = serializable ? S.one("#" + bookmark.startNode, 1113 doc) : bookmark.startNode, 1114 endNode = serializable ? S.one("#" + bookmark.endNode, 1115 doc) : bookmark.endNode; 1116 1117 // Set the range start at the bookmark start node position. 1118 self.setStartBefore(startNode); 1119 1120 // Remove it, because it may interfere in the setEndBefore call. 1121 startNode._4e_remove(); 1122 1123 // Set the range end at the bookmark end node position, or simply 1124 // collapse it if it is not available. 1125 if (endNode && endNode[0]) { 1126 self.setEndBefore(endNode); 1127 endNode._4e_remove(); 1128 } else { 1129 self.collapse(TRUE); 1130 } 1131 } 1132 }, 1133 1134 /** 1135 * Find the node which contains current range completely. 1136 * @param {Boolean} includeSelf whether to return the only element with in range 1137 * @param {Boolean} ignoreTextNode whether to return text node's parent node. 1138 */ 1139 getCommonAncestor:function (includeSelf, ignoreTextNode) { 1140 var self = this, 1141 start = self.startContainer, 1142 end = self.endContainer, 1143 ancestor; 1144 1145 if (start[0] == end[0]) { 1146 if (includeSelf && 1147 start[0].nodeType == DOM.ELEMENT_NODE && 1148 self.startOffset == self.endOffset - 1) { 1149 ancestor = new Node(start[0].childNodes[self.startOffset]); 1150 } else { 1151 ancestor = start; 1152 } 1153 } else { 1154 ancestor = start._4e_commonAncestor(end); 1155 } 1156 1157 return ignoreTextNode && ancestor[0].nodeType == DOM.TEXT_NODE 1158 ? ancestor.parent() : ancestor; 1159 }, 1160 /** 1161 * Enlarge the range as mush as possible 1162 * @param {Number} unit 1163 * @function 1164 * @example 1165 * <code> 1166 * <div><span><span>^1</span>2^</span>x</div> 1167 * => 1168 * <div>^<span><span>1</span>2</span>^x</div> 1169 * </code> 1170 */ 1171 enlarge:(function () { 1172 1173 function enlargeElement(self, left, stop, commonAncestor) { 1174 var container = self[left ? 'startContainer' : 'endContainer'], 1175 enlarge, 1176 sibling, 1177 index = left ? 0 : 1, 1178 commonReached = 0, 1179 direction = left ? "previousSibling" : "nextSibling", 1180 offset = self[left ? 'startOffset' : 'endOffset']; 1181 1182 if (container[0].nodeType == DOM.TEXT_NODE) { 1183 if (left) { 1184 // 不在字的开头,立即结束 1185 if (offset) { 1186 return; 1187 } 1188 } else { 1189 if (offset < container[0].nodeValue.length) { 1190 return 1191 } 1192 } 1193 1194 // 文字节点的兄弟 1195 sibling = container[0][direction]; 1196 // 可能会扩展到到的容器节点 1197 enlarge = container[0].parentNode; 1198 } else { 1199 // 开始节点的兄弟节点 1200 sibling = container[0].childNodes[offset + (left ? -1 : 1)] || null; 1201 // 可能会扩展到到的容器节点 1202 enlarge = container[0]; 1203 } 1204 1205 while (enlarge) { 1206 // 兄弟节点是否都是空节点? 1207 while (sibling) { 1208 if (isWhitespace(sibling) || isBookmark(sibling)) { 1209 sibling = sibling[direction]; 1210 } else { 1211 break; 1212 } 1213 } 1214 1215 // 一个兄弟节点阻止了扩展 1216 if (sibling) { 1217 // 如果没有超过公共祖先 1218 if (!commonReached) { 1219 // 仅仅扩展到兄弟 1220 self[left ? 'setStartAfter' : 'setEndBefore']($(sibling)); 1221 } 1222 return; 1223 } 1224 1225 // 没有兄弟节点阻止 1226 1227 // 超过了公共祖先,先记下来,最终不能 partly 选择某个节点,要完全选中 1228 1229 enlarge = $(enlarge); 1230 1231 if (enlarge.nodeName() == "body") { 1232 return; 1233 } 1234 1235 if (commonReached || enlarge.equals(commonAncestor)) { 1236 stop[index] = enlarge; 1237 commonReached = 1; 1238 } else { 1239 // 扩展到容器外边 1240 self[left ? 'setStartBefore' : 'setEndAfter'](enlarge); 1241 } 1242 1243 sibling = enlarge[0][direction]; 1244 enlarge = enlarge[0].parentNode; 1245 } 1246 1247 } 1248 1249 return function (unit) { 1250 var self = this; 1251 switch (unit) { 1252 case KER.ENLARGE_ELEMENT : 1253 1254 if (self.collapsed) { 1255 return; 1256 } 1257 1258 var commonAncestor = self.getCommonAncestor(), 1259 stop = []; 1260 1261 enlargeElement(self, 1, stop, commonAncestor); 1262 enlargeElement(self, 0, stop, commonAncestor); 1263 1264 if (stop[0] && stop[1]) { 1265 var commonStop = stop[0].contains(stop[1]) ? stop[1] : stop[0]; 1266 self.setStartBefore(commonStop); 1267 self.setEndAfter(commonStop); 1268 } 1269 1270 break; 1271 1272 case KER.ENLARGE_BLOCK_CONTENTS: 1273 case KER.ENLARGE_LIST_ITEM_CONTENTS: 1274 1275 // Enlarging the start boundary. 1276 var walkerRange = new KERange(self.document); 1277 var body = new Node(self.document.body); 1278 1279 walkerRange.setStartAt(body, KER.POSITION_AFTER_START); 1280 walkerRange.setEnd(self.startContainer, self.startOffset); 1281 1282 var walker = new Walker(walkerRange), 1283 blockBoundary, // The node on which the enlarging should stop. 1284 tailBr, // 1285 defaultGuard = Walker.blockBoundary( 1286 ( unit == KER.ENLARGE_LIST_ITEM_CONTENTS ) ? 1287 { br:1 } : NULL), 1288 // Record the encountered 'blockBoundary' for later use. 1289 boundaryGuard = function (node) { 1290 var retVal = defaultGuard(node); 1291 if (!retVal) { 1292 blockBoundary = $(node); 1293 } 1294 return retVal; 1295 }, 1296 // Record the encountered 'tailBr' for later use. 1297 tailBrGuard = function (node) { 1298 var retVal = boundaryGuard(node); 1299 if (!retVal && DOM.nodeName(node) == 'br') { 1300 tailBr = $(node); 1301 } 1302 return retVal; 1303 }; 1304 1305 walker.guard = boundaryGuard; 1306 1307 enlargeable = walker.lastBackward(); 1308 1309 // It's the body which stop the enlarging if no block boundary found. 1310 blockBoundary = blockBoundary || body; 1311 1312 // Start the range at different position by comparing 1313 // the document position of it with 'enlargeable' node. 1314 self.setStartAt( 1315 blockBoundary, 1316 blockBoundary.nodeName() != 'br' && 1317 // <table></table> <span>1234^56</span> <table></table> 1318 // => 1319 // <table></table> ^<span>123456</span>$ <table></table> 1320 1321 // <p> <span>123^456</span> </p> 1322 // => 1323 // <p> ^<span>123456</span>$ </p> 1324 ( !enlargeable && self.checkStartOfBlock() 1325 || enlargeable && blockBoundary.contains(enlargeable) ) ? 1326 KER.POSITION_AFTER_START : 1327 KER.POSITION_AFTER_END); 1328 1329 // Enlarging the end boundary. 1330 walkerRange = self.clone(); 1331 walkerRange.collapse(); 1332 walkerRange.setEndAt(body, KER.POSITION_BEFORE_END); 1333 walker = new Walker(walkerRange); 1334 1335 // tailBrGuard only used for on range end. 1336 walker.guard = ( unit == KER.ENLARGE_LIST_ITEM_CONTENTS ) ? 1337 tailBrGuard : boundaryGuard; 1338 blockBoundary = NULL; 1339 // End the range right before the block boundary node. 1340 1341 var enlargeable = walker.lastForward(); 1342 1343 // It's the body which stop the enlarging if no block boundary found. 1344 blockBoundary = blockBoundary || body; 1345 1346 // Start the range at different position by comparing 1347 // the document position of it with 'enlargeable' node. 1348 self.setEndAt( 1349 blockBoundary, 1350 ( !enlargeable && self.checkEndOfBlock() 1351 || enlargeable && blockBoundary.contains(enlargeable) ) ? 1352 KER.POSITION_BEFORE_END : 1353 KER.POSITION_BEFORE_START); 1354 // We must include the <br> at the end of range if there's 1355 // one and we're expanding list item contents 1356 if (tailBr) { 1357 self.setEndAfter(tailBr); 1358 } 1359 } 1360 } 1361 })(), 1362 1363 /** 1364 * Check whether current range 's start position is at the start of a block (visible) 1365 * @return Boolean 1366 */ 1367 checkStartOfBlock:function () { 1368 var self = this, 1369 startContainer = self.startContainer, 1370 startOffset = self.startOffset; 1371 1372 // If the starting node is a text node, and non-empty before the offset, 1373 // then we're surely not at the start of block. 1374 if (startOffset && startContainer[0].nodeType == DOM.TEXT_NODE) { 1375 var textBefore = S.trim(startContainer[0].nodeValue.substring(0, startOffset)); 1376 if (textBefore.length) { 1377 return FALSE; 1378 } 1379 } 1380 1381 // Anticipate the trim() call here, so the walker will not make 1382 // changes to the DOM, which would not get reflected into this 1383 // range otherwise. 1384 self.trim(); 1385 1386 // We need to grab the block element holding the start boundary, so 1387 // let's use an element path for it. 1388 var path = new ElementPath(self.startContainer); 1389 1390 // Creates a range starting at the block start until the range start. 1391 var walkerRange = self.clone(); 1392 walkerRange.collapse(TRUE); 1393 walkerRange.setStartAt(path.block || path.blockLimit, KER.POSITION_AFTER_START); 1394 1395 var walker = new Walker(walkerRange); 1396 walker.evaluator = getCheckStartEndBlockEvalFunction(TRUE); 1397 1398 return walker.checkBackward(); 1399 }, 1400 1401 /** 1402 * Check whether current range 's end position is at the end of a block (visible) 1403 * @return Boolean 1404 */ 1405 checkEndOfBlock:function () { 1406 var self = this, endContainer = self.endContainer, 1407 endOffset = self.endOffset; 1408 1409 // If the ending node is a text node, and non-empty after the offset, 1410 // then we're surely not at the end of block. 1411 if (endContainer[0].nodeType == DOM.TEXT_NODE) { 1412 var textAfter = S.trim(endContainer[0].nodeValue.substring(endOffset)); 1413 if (textAfter.length) { 1414 return FALSE; 1415 } 1416 } 1417 1418 // Anticipate the trim() call here, so the walker will not make 1419 // changes to the DOM, which would not get reflected into this 1420 // range otherwise. 1421 self.trim(); 1422 1423 // We need to grab the block element holding the start boundary, so 1424 // let's use an element path for it. 1425 var path = new ElementPath(self.endContainer); 1426 1427 // Creates a range starting at the block start until the range start. 1428 var walkerRange = self.clone(); 1429 walkerRange.collapse(FALSE); 1430 walkerRange.setEndAt(path.block || path.blockLimit, KER.POSITION_BEFORE_END); 1431 1432 var walker = new Walker(walkerRange); 1433 walker.evaluator = getCheckStartEndBlockEvalFunction(FALSE); 1434 1435 return walker.checkForward(); 1436 }, 1437 1438 /** 1439 * Check whether current range is on the inner edge of the specified element. 1440 * @param {Number} checkType The checking side. 1441 * @param {NodeList} element The target element to check. 1442 */ 1443 checkBoundaryOfElement:function (element, checkType) { 1444 var walkerRange = this.clone(); 1445 // Expand the range to element boundary. 1446 walkerRange[ checkType == KER.START ? 1447 'setStartAt' : 'setEndAt' ] 1448 (element, checkType == KER.START ? 1449 KER.POSITION_AFTER_START 1450 : KER.POSITION_BEFORE_END); 1451 1452 var walker = new Walker(walkerRange); 1453 1454 walker.evaluator = elementBoundaryEval; 1455 return walker[ checkType == KER.START ? 1456 'checkBackward' : 'checkForward' ](); 1457 }, 1458 1459 /** 1460 * Get two node which are at the edge of current range. 1461 * @return {Object} Map with startNode and endNode as key/value. 1462 */ 1463 getBoundaryNodes:function () { 1464 var self = this, 1465 startNode = self.startContainer, 1466 endNode = self.endContainer, 1467 startOffset = self.startOffset, 1468 endOffset = self.endOffset, 1469 childCount; 1470 1471 if (startNode[0].nodeType == DOM.ELEMENT_NODE) { 1472 childCount = startNode[0].childNodes.length; 1473 if (childCount > startOffset) { 1474 startNode = $(startNode[0].childNodes[startOffset]); 1475 } else if (childCount == 0) { 1476 // ?? startNode 1477 startNode = startNode._4e_previousSourceNode(); 1478 } else { 1479 // startOffset >= childCount but childCount is not 0 1480 // Try to take the node just after the current position. 1481 startNode = startNode[0]; 1482 while (startNode.lastChild) { 1483 startNode = startNode.lastChild; 1484 } 1485 1486 startNode = $(startNode); 1487 1488 // Normally we should take the next node in DFS order. But it 1489 // is also possible that we've already reached the end of 1490 // document. 1491 startNode = startNode._4e_nextSourceNode() || startNode; 1492 } 1493 } 1494 1495 if (endNode[0].nodeType == DOM.ELEMENT_NODE) { 1496 childCount = endNode[0].childNodes.length; 1497 if (childCount > endOffset) { 1498 endNode = $(endNode[0].childNodes[endOffset]) 1499 // in case endOffset == 0 1500 ._4e_previousSourceNode(TRUE); 1501 } else if (childCount == 0) { 1502 endNode = endNode._4e_previousSourceNode(); 1503 } else { 1504 // endOffset > childCount but childCount is not 0 1505 // Try to take the node just before the current position. 1506 endNode = endNode[0]; 1507 while (endNode.lastChild) 1508 endNode = endNode.lastChild; 1509 endNode = $(endNode); 1510 } 1511 } 1512 1513 // Sometimes the endNode will come right before startNode for collapsed 1514 // ranges. Fix it. (#3780) 1515 if (startNode._4e_position(endNode) & KEP.POSITION_FOLLOWING) { 1516 startNode = endNode; 1517 } 1518 1519 return { startNode:startNode, endNode:endNode }; 1520 }, 1521 1522 /** 1523 * Wrap the content in range which is block-enlarged 1524 * at the start or end of current range into a block element. 1525 * @param {Boolean} isStart Start or end of current range tobe enlarged. 1526 * @param {String} blockTag Block element's tag name. 1527 * @return {NodeList} Newly generated block element. 1528 */ 1529 fixBlock:function (isStart, blockTag) { 1530 var self = this, 1531 bookmark = self.createBookmark(), 1532 fixedBlock = $(self.document.createElement(blockTag)); 1533 self.collapse(isStart); 1534 self.enlarge(KER.ENLARGE_BLOCK_CONTENTS); 1535 fixedBlock[0].appendChild(self.extractContents()); 1536 fixedBlock._4e_trim(); 1537 if (!UA['ie']) { 1538 fixedBlock._4e_appendBogus(); 1539 } 1540 self.insertNode(fixedBlock); 1541 self.moveToBookmark(bookmark); 1542 return fixedBlock; 1543 }, 1544 1545 /** 1546 * Split current block which current range into two if current range is in the same block. 1547 * Fix block at the start and end position of range if necessary. 1548 * @param {String} blockTag Block tag if need fixBlock 1549 */ 1550 splitBlock:function (blockTag) { 1551 var self = this, 1552 startPath = new ElementPath(self.startContainer), 1553 endPath = new ElementPath(self.endContainer), 1554 startBlockLimit = startPath.blockLimit, 1555 endBlockLimit = endPath.blockLimit, 1556 startBlock = startPath.block, 1557 endBlock = endPath.block, 1558 elementPath = NULL; 1559 1560 // Do nothing if the boundaries are in different block limits. 1561 if (!startBlockLimit.equals(endBlockLimit)) { 1562 return NULL; 1563 } 1564 1565 // Get or fix current blocks. 1566 if (blockTag != 'br') { 1567 if (!startBlock) { 1568 startBlock = self.fixBlock(TRUE, blockTag); 1569 endBlock = new ElementPath(self.endContainer).block; 1570 } 1571 1572 if (!endBlock) { 1573 endBlock = self.fixBlock(FALSE, blockTag); 1574 } 1575 } 1576 1577 // Get the range position. 1578 var isStartOfBlock = startBlock && self.checkStartOfBlock(), 1579 isEndOfBlock = endBlock && self.checkEndOfBlock(); 1580 1581 // Delete the current contents. 1582 self.deleteContents(); 1583 1584 if (startBlock && startBlock[0] == endBlock[0]) { 1585 if (isEndOfBlock) { 1586 elementPath = new ElementPath(self.startContainer); 1587 self.moveToPosition(endBlock, KER.POSITION_AFTER_END); 1588 endBlock = NULL; 1589 } 1590 else if (isStartOfBlock) { 1591 elementPath = new ElementPath(self.startContainer); 1592 self.moveToPosition(startBlock, KER.POSITION_BEFORE_START); 1593 startBlock = NULL; 1594 } 1595 else { 1596 endBlock = self.splitElement(startBlock); 1597 // In Gecko, the last child node must be a bogus <br>. 1598 // Note: bogus <br> added under <ul> or <ol> would cause 1599 // lists to be incorrectly rendered. 1600 if (!UA['ie'] && !S.inArray(startBlock.nodeName(), ['ul', 'ol'])) { 1601 startBlock._4e_appendBogus(); 1602 } 1603 } 1604 } 1605 1606 return { 1607 previousBlock:startBlock, 1608 nextBlock:endBlock, 1609 wasStartOfBlock:isStartOfBlock, 1610 wasEndOfBlock:isEndOfBlock, 1611 elementPath:elementPath 1612 }; 1613 }, 1614 1615 /** 1616 * Split toSplit element into two parts at current range's start position. 1617 * @param {NodeList} toSplit Element to split. 1618 * @return {NodeList} The second newly generated element. 1619 */ 1620 splitElement:function (toSplit) { 1621 var self = this; 1622 if (!self.collapsed) 1623 return NULL; 1624 1625 // Extract the contents of the block from the selection point to the end 1626 // of its contents. 1627 self.setEndAt(toSplit, KER.POSITION_BEFORE_END); 1628 var documentFragment = self.extractContents(), 1629 // Duplicate the element after it. 1630 clone = toSplit.clone(FALSE); 1631 1632 // Place the extracted contents into the duplicated element. 1633 clone[0].appendChild(documentFragment); 1634 1635 clone.insertAfter(toSplit); 1636 self.moveToPosition(toSplit, KER.POSITION_AFTER_END); 1637 return clone; 1638 }, 1639 1640 /** 1641 * Move the range to the depth-first start/end editing point inside 1642 * an element. 1643 * @param {NodeList} el The element to find edit point into. 1644 * @param {Boolean} [isMoveToEnd] Find start or end editing point. 1645 * Set true to find end editing point. 1646 * @return {Boolean} Whether find edit point 1647 */ 1648 moveToElementEditablePosition:function (el, isMoveToEnd) { 1649 function nextDFS(node, childOnly) { 1650 var next; 1651 1652 if (node[0].nodeType == DOM.ELEMENT_NODE && 1653 node._4e_isEditable()) { 1654 next = node[ isMoveToEnd ? 'last' : 'first' ](nonWhitespaceOrIsBookmark, 1); 1655 } 1656 1657 if (!childOnly && !next) { 1658 next = node[ isMoveToEnd ? 'prev' : 'next' ](nonWhitespaceOrIsBookmark, 1); 1659 } 1660 1661 return next; 1662 } 1663 1664 var found = 0, self = this; 1665 1666 while (el) { 1667 // Stop immediately if we've found a text node. 1668 if (el[0].nodeType == DOM.TEXT_NODE) { 1669 self.moveToPosition(el, isMoveToEnd ? 1670 KER.POSITION_AFTER_END : 1671 KER.POSITION_BEFORE_START); 1672 found = 1; 1673 break; 1674 } 1675 1676 // If an editable element is found, move inside it, but not stop the searching. 1677 if (el[0].nodeType == DOM.ELEMENT_NODE && el._4e_isEditable()) { 1678 self.moveToPosition(el, isMoveToEnd ? 1679 KER.POSITION_BEFORE_END : 1680 KER.POSITION_AFTER_START); 1681 found = 1; 1682 } 1683 1684 el = nextDFS(el, found); 1685 } 1686 1687 return !!found; 1688 }, 1689 1690 /** 1691 * Set range surround current node 's content. 1692 * @param {NodeList} node 1693 */ 1694 selectNodeContents:function (node) { 1695 var self = this, domNode = node[0]; 1696 self.setStart(node, 0); 1697 self.setEnd(node, domNode.nodeType == DOM.TEXT_NODE ? 1698 domNode.nodeValue.length : 1699 domNode.childNodes.length); 1700 }, 1701 1702 /* 1703 insertNodeByDtd:function (element) { 1704 var current, 1705 self = this, 1706 tmpDtd, 1707 elementName = element['nodeName'](), 1708 isBlock = dtd['$block'][ elementName ]; 1709 self.deleteContents(); 1710 if (isBlock) { 1711 while (( current = self.getCommonAncestor(FALSE, TRUE) ) && 1712 ( tmpDtd = dtd[ current.nodeName() ] ) && 1713 !( tmpDtd && tmpDtd [ elementName ] )) { 1714 // Split up inline elements. 1715 if (current.nodeName() in dtd["span"]) { 1716 self.splitElement(current); 1717 } 1718 // If we're in an empty block which indicate a new paragraph, 1719 // simply replace it with the inserting block.(#3664) 1720 else if (self.checkStartOfBlock() && self.checkEndOfBlock()) { 1721 self.setStartBefore(current); 1722 self.collapse(TRUE); 1723 current.remove(); 1724 } 1725 else { 1726 self.splitBlock(undefined); 1727 } 1728 } 1729 } 1730 // Insert the new node. 1731 self.insertNode(element); 1732 },*/ 1733 1734 /** 1735 * Insert node by dtd.(not invalidate dtd convention) 1736 * @param {NodeList} element 1737 */ 1738 insertNodeByDtd:function (element) { 1739 var current, 1740 self = this, 1741 tmpDtd, 1742 last, 1743 elementName = element['nodeName'](), 1744 isBlock = dtd['$block'][ elementName ]; 1745 self.deleteContents(); 1746 if (isBlock) { 1747 current = self.getCommonAncestor(FALSE, TRUE); 1748 while (( tmpDtd = dtd[ current.nodeName() ] ) && 1749 !( tmpDtd && tmpDtd [ elementName ] )) { 1750 var parent = current.parent(); 1751 // If we're in an empty block which indicate a new paragraph, 1752 // simply replace it with the inserting block.(#3664) 1753 if (self.checkStartOfBlock() && self.checkEndOfBlock()) { 1754 self.setStartBefore(current); 1755 self.collapse(TRUE); 1756 current.remove(); 1757 } else { 1758 last = current; 1759 } 1760 current = parent; 1761 1762 } 1763 if (last) { 1764 self.splitElement(last); 1765 } 1766 } 1767 // Insert the new node. 1768 self.insertNode(element); 1769 } 1770 }); 1771 1772 Utils.injectDom({ 1773 _4e_breakParent:function (el, parent) { 1774 parent = $(parent); 1775 el = $(el); 1776 1777 var KERange = Editor.Range, 1778 docFrag, 1779 range = new KERange(el[0].ownerDocument); 1780 1781 // We'll be extracting part of this element, so let's use our 1782 // range to get the correct piece. 1783 range.setStartAfter(el); 1784 range.setEndAfter(parent); 1785 1786 // Extract it. 1787 docFrag = range.extractContents(); 1788 1789 // Move the element outside the broken element. 1790 range.insertNode(el.remove()); 1791 1792 // Re-insert the extracted piece after the element. 1793 el.after(docFrag); 1794 } 1795 }); 1796 1797 Editor.Range = KERange; 1798 1799 return KERange; 1800 }, { 1801 requires:['./base', './utils', './walker', './elementPath', './dom'] 1802 }); 1803