[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('editor-selection', function (Y, NAME) { 9 10 /** 11 * Wraps some common Selection/Range functionality into a simple object 12 * @class EditorSelection 13 * @constructor 14 * @module editor 15 * @submodule selection 16 */ 17 18 //TODO This shouldn't be there, Y.Node doesn't normalize getting textnode content. 19 var textContent = 'textContent', 20 INNER_HTML = 'innerHTML', 21 FONT_FAMILY = 'fontFamily'; 22 23 if (Y.UA.ie && Y.UA.ie < 11) { 24 textContent = 'nodeValue'; 25 } 26 27 Y.EditorSelection = function(domEvent) { 28 var sel, par, ieNode, nodes, rng, i, 29 comp, moved = 0, n, id, root = Y.EditorSelection.ROOT; 30 31 32 if (Y.config.win.getSelection && (!Y.UA.ie || Y.UA.ie < 9 || Y.UA.ie > 10)) { 33 sel = Y.config.win.getSelection(); 34 } else if (Y.config.doc.selection) { 35 sel = Y.config.doc.selection.createRange(); 36 } 37 this._selection = sel; 38 39 if (!sel) { 40 return false; 41 } 42 43 if (sel.pasteHTML) { 44 this.isCollapsed = (sel.compareEndPoints('StartToEnd', sel)) ? false : true; 45 if (this.isCollapsed) { 46 this.anchorNode = this.focusNode = Y.one(sel.parentElement()); 47 48 if (domEvent) { 49 ieNode = Y.config.doc.elementFromPoint(domEvent.clientX, domEvent.clientY); 50 } 51 rng = sel.duplicate(); 52 if (!ieNode) { 53 par = sel.parentElement(); 54 nodes = par.childNodes; 55 56 for (i = 0; i < nodes.length; i++) { 57 //This causes IE to not allow a selection on a doubleclick 58 //rng.select(nodes[i]); 59 if (rng.inRange(sel)) { 60 if (!ieNode) { 61 ieNode = nodes[i]; 62 } 63 } 64 } 65 } 66 67 this.ieNode = ieNode; 68 69 if (ieNode) { 70 if (ieNode.nodeType !== 3) { 71 if (ieNode.firstChild) { 72 ieNode = ieNode.firstChild; 73 } 74 if (root.compareTo(ieNode)) { 75 if (ieNode.firstChild) { 76 ieNode = ieNode.firstChild; 77 } 78 } 79 } 80 this.anchorNode = this.focusNode = Y.EditorSelection.resolve(ieNode); 81 82 rng.moveToElementText(sel.parentElement()); 83 comp = sel.compareEndPoints('StartToStart', rng); 84 if (comp) { 85 //We are not at the beginning of the selection. 86 //Setting the move to something large, may need to increase it later 87 moved = this.getEditorOffset(root); 88 sel.move('character', -(moved)); 89 } 90 91 this.anchorOffset = this.focusOffset = moved; 92 93 this.anchorTextNode = this.focusTextNode = Y.one(ieNode); 94 } 95 96 97 } else { 98 //This helps IE deal with a selection and nodeChange events 99 if (sel.htmlText && sel.htmlText !== '') { 100 n = Y.Node.create(sel.htmlText); 101 if (n && n.get('id')) { 102 id = n.get('id'); 103 this.anchorNode = this.focusNode = Y.one('#' + id); 104 } else if (n) { 105 n = n.get('childNodes'); 106 this.anchorNode = this.focusNode = n.item(0); 107 } 108 } 109 } 110 111 //var self = this; 112 //debugger; 113 } else { 114 this.isCollapsed = sel.isCollapsed; 115 this.anchorNode = Y.EditorSelection.resolve(sel.anchorNode); 116 this.focusNode = Y.EditorSelection.resolve(sel.focusNode); 117 this.anchorOffset = sel.anchorOffset; 118 this.focusOffset = sel.focusOffset; 119 120 this.anchorTextNode = Y.one(sel.anchorNode || this.anchorNode); 121 this.focusTextNode = Y.one(sel.focusNode || this.focusNode); 122 } 123 if (Y.Lang.isString(sel.text)) { 124 this.text = sel.text; 125 } else { 126 if (sel.toString) { 127 this.text = sel.toString(); 128 } else { 129 this.text = ''; 130 } 131 } 132 }; 133 134 /** 135 * Utility method to remove dead font-family styles from an element. 136 * @static 137 * @method removeFontFamily 138 */ 139 Y.EditorSelection.removeFontFamily = function(n) { 140 n.removeAttribute('face'); 141 var s = n.getAttribute('style').toLowerCase(); 142 if (s === '' || (s === 'font-family: ')) { 143 n.removeAttribute('style'); 144 } 145 if (s.match(Y.EditorSelection.REG_FONTFAMILY)) { 146 s = s.replace(Y.EditorSelection.REG_FONTFAMILY, ''); 147 n.setAttribute('style', s); 148 } 149 }; 150 151 /** 152 * Performs a prefilter on all nodes in the editor. Looks for nodes with a style: fontFamily or font face 153 * It then creates a dynamic class assigns it and removed the property. This is so that we don't lose 154 * the fontFamily when selecting nodes. 155 * @static 156 * @method filter 157 */ 158 Y.EditorSelection.filter = function(blocks) { 159 160 var startTime = (new Date()).getTime(), 161 editorSelection = Y.EditorSelection, 162 root = editorSelection.ROOT, 163 endTime, 164 nodes = root.all(editorSelection.ALL), 165 baseNodes = root.all('strong,em'), 166 doc = Y.config.doc, hrs, 167 classNames = {}, cssString = '', 168 ls, startTime1 = (new Date()).getTime(), 169 endTime1; 170 171 nodes.each(function(n) { 172 var raw = Y.Node.getDOMNode(n); 173 if (raw.style[FONT_FAMILY]) { 174 classNames['.' + n._yuid] = raw.style[FONT_FAMILY]; 175 n.addClass(n._yuid); 176 177 editorSelection.removeFontFamily(raw); 178 } 179 }); 180 endTime1 = (new Date()).getTime(); 181 182 root.all('.hr').addClass('yui-skip').addClass('yui-non'); 183 184 if (Y.UA.ie) { 185 hrs = Y.Node.getDOMNode(root).getElementsByTagName('hr'); 186 Y.each(hrs, function(hr) { 187 var el = doc.createElement('div'), 188 s = el.style; 189 190 el.className = 'hr yui-non yui-skip'; 191 192 el.setAttribute('readonly', true); 193 el.setAttribute('contenteditable', false); //Keep it from being Edited 194 if (hr.parentNode) { 195 hr.parentNode.replaceChild(el, hr); 196 } 197 //Had to move to inline style. writes for ie's < 8. They don't render el.setAttribute('style'); 198 s.border = '1px solid #ccc'; 199 s.lineHeight = '0'; 200 s.height = '0'; 201 s.fontSize = '0'; 202 s.marginTop = '5px'; 203 s.marginBottom = '5px'; 204 s.marginLeft = '0px'; 205 s.marginRight = '0px'; 206 s.padding = '0'; 207 }); 208 } 209 210 211 Y.each(classNames, function(v, k) { 212 cssString += k + ' { font-family: ' + v.replace(/"/gi, '') + '; }'; 213 }); 214 Y.StyleSheet(cssString, 'editor'); 215 216 217 //Not sure about this one? 218 baseNodes.each(function(n, k) { 219 var t = n.get('tagName').toLowerCase(), 220 newTag = 'i'; 221 if (t === 'strong') { 222 newTag = 'b'; 223 } 224 editorSelection.prototype._swap(baseNodes.item(k), newTag); 225 }); 226 227 //Filter out all the empty UL/OL's 228 ls = root.all('ol,ul'); 229 ls.each(function(v) { 230 var lis = v.all('li'); 231 if (!lis.size()) { 232 v.remove(); 233 } 234 }); 235 236 if (blocks) { 237 editorSelection.filterBlocks(); 238 } 239 endTime = (new Date()).getTime(); 240 }; 241 242 /** 243 * Method attempts to replace all "orphined" text nodes in the main body by wrapping them with a <p>. Called from filter. 244 * @static 245 * @method filterBlocks 246 */ 247 Y.EditorSelection.filterBlocks = function() { 248 var startTime = (new Date()).getTime(), endTime, 249 childs = Y.Node.getDOMNode(Y.EditorSelection.ROOT).childNodes, i, node, wrapped = false, doit = true, 250 sel, single, br, c, s, html; 251 252 if (childs) { 253 for (i = 0; i < childs.length; i++) { 254 node = Y.one(childs[i]); 255 if (!node.test(Y.EditorSelection.BLOCKS)) { 256 doit = true; 257 if (childs[i].nodeType === 3) { 258 c = childs[i][textContent].match(Y.EditorSelection.REG_CHAR); 259 s = childs[i][textContent].match(Y.EditorSelection.REG_NON); 260 if (c === null && s) { 261 doit = false; 262 263 } 264 } 265 if (doit) { 266 if (!wrapped) { 267 wrapped = []; 268 } 269 wrapped.push(childs[i]); 270 } 271 } else { 272 wrapped = Y.EditorSelection._wrapBlock(wrapped); 273 } 274 } 275 wrapped = Y.EditorSelection._wrapBlock(wrapped); 276 } 277 278 single = Y.all(Y.EditorSelection.DEFAULT_BLOCK_TAG); 279 if (single.size() === 1) { 280 br = single.item(0).all('br'); 281 if (br.size() === 1) { 282 if (!br.item(0).test('.yui-cursor')) { 283 br.item(0).remove(); 284 } 285 html = single.item(0).get('innerHTML'); 286 if (html === '' || html === ' ') { 287 single.set('innerHTML', Y.EditorSelection.CURSOR); 288 sel = new Y.EditorSelection(); 289 sel.focusCursor(true, true); 290 } 291 if (br.item(0).test('.yui-cursor') && Y.UA.ie) { 292 br.item(0).remove(); 293 } 294 } 295 } else { 296 single.each(function(p) { 297 var html = p.get('innerHTML'); 298 if (html === '') { 299 p.remove(); 300 } 301 }); 302 } 303 304 endTime = (new Date()).getTime(); 305 }; 306 307 /** 308 * Regular Expression used to find dead font-family styles 309 * @static 310 * @property REG_FONTFAMILY 311 */ 312 Y.EditorSelection.REG_FONTFAMILY = /font-family:\s*;/; 313 314 /** 315 * Regular Expression to determine if a string has a character in it 316 * @static 317 * @property REG_CHAR 318 */ 319 Y.EditorSelection.REG_CHAR = /[a-zA-Z-0-9_!@#\$%\^&*\(\)-=_+\[\]\\{}|;':",.\/<>\?]/gi; 320 321 /** 322 * Regular Expression to determine if a string has a non-character in it 323 * @static 324 * @property REG_NON 325 */ 326 Y.EditorSelection.REG_NON = /[\s|\n|\t]/gi; 327 328 /** 329 * Regular Expression to remove all HTML from a string 330 * @static 331 * @property REG_NOHTML 332 */ 333 Y.EditorSelection.REG_NOHTML = /<\S[^><]*>/g; 334 335 336 /** 337 * Wraps an array of elements in a Block level tag 338 * @static 339 * @private 340 * @method _wrapBlock 341 */ 342 Y.EditorSelection._wrapBlock = function(wrapped) { 343 if (wrapped) { 344 var newChild = Y.Node.create('<' + Y.EditorSelection.DEFAULT_BLOCK_TAG + '></' + Y.EditorSelection.DEFAULT_BLOCK_TAG + '>'), 345 firstChild = Y.one(wrapped[0]), i; 346 347 for (i = 1; i < wrapped.length; i++) { 348 newChild.append(wrapped[i]); 349 } 350 firstChild.replace(newChild); 351 newChild.prepend(firstChild); 352 } 353 return false; 354 }; 355 356 /** 357 * Undoes what filter does enough to return the HTML from the Editor, then re-applies the filter. 358 * @static 359 * @method unfilter 360 * @return {String} The filtered HTML 361 */ 362 Y.EditorSelection.unfilter = function() { 363 var root = Y.EditorSelection.ROOT, 364 nodes = root.all('[class]'), 365 html = '', nons, ids, 366 body = root; 367 368 369 nodes.each(function(n) { 370 if (n.hasClass(n._yuid)) { 371 //One of ours 372 n.setStyle(FONT_FAMILY, n.getStyle(FONT_FAMILY)); 373 n.removeClass(n._yuid); 374 if (n.getAttribute('class') === '') { 375 n.removeAttribute('class'); 376 } 377 } 378 }); 379 380 nons = root.all('.yui-non'); 381 nons.each(function(n) { 382 if (!n.hasClass('yui-skip') && n.get('innerHTML') === '') { 383 n.remove(); 384 } else { 385 n.removeClass('yui-non').removeClass('yui-skip'); 386 } 387 }); 388 389 ids = root.all('[id]'); 390 ids.each(function(n) { 391 if (n.get('id').indexOf('yui_3_') === 0) { 392 n.removeAttribute('id'); 393 n.removeAttribute('_yuid'); 394 } 395 }); 396 397 if (body) { 398 html = body.get('innerHTML'); 399 } 400 401 root.all('.hr').addClass('yui-skip').addClass('yui-non'); 402 403 /* 404 nodes.each(function(n) { 405 n.addClass(n._yuid); 406 n.setStyle(FONT_FAMILY, ''); 407 if (n.getAttribute('style') === '') { 408 n.removeAttribute('style'); 409 } 410 }); 411 */ 412 413 return html; 414 }; 415 /** 416 * Resolve a node from the selection object and return a Node instance 417 * @static 418 * @method resolve 419 * @param {HTMLElement} n The HTMLElement to resolve. Might be a TextNode, gives parentNode. 420 * @return {Node} The Resolved node 421 */ 422 Y.EditorSelection.resolve = function(n) { 423 if (!n) { 424 return Y.EditorSelection.ROOT; 425 } 426 427 if (n && n.nodeType === 3) { 428 //Adding a try/catch here because in rare occasions IE will 429 //Throw a error accessing the parentNode of a stranded text node. 430 //In the case of Ctrl+Z (Undo) 431 try { 432 n = n.parentNode; 433 } catch (re) { 434 n = Y.EditorSelection.ROOT; 435 } 436 } 437 return Y.one(n); 438 }; 439 440 /** 441 * Returns the innerHTML of a node with all HTML tags removed. 442 * @static 443 * @method getText 444 * @param {Node} node The Node instance to remove the HTML from 445 * @return {String} The string of text 446 */ 447 Y.EditorSelection.getText = function(node) { 448 var txt = node.get('innerHTML').replace(Y.EditorSelection.REG_NOHTML, ''); 449 //Clean out the cursor subs to see if the Node is empty 450 txt = txt.replace('<span><br></span>', '').replace('<br>', ''); 451 return txt; 452 }; 453 454 //Y.EditorSelection.DEFAULT_BLOCK_TAG = 'div'; 455 Y.EditorSelection.DEFAULT_BLOCK_TAG = 'p'; 456 457 /** 458 * The selector to use when looking for Nodes to cache the value of: [style],font[face] 459 * @static 460 * @property ALL 461 */ 462 Y.EditorSelection.ALL = '[style],font[face]'; 463 464 /** 465 * The selector to use when looking for block level items. 466 * @static 467 * @property BLOCKS 468 */ 469 Y.EditorSelection.BLOCKS = 'p,div,ul,ol,table,style'; 470 /** 471 * The temporary fontname applied to a selection to retrieve their values: yui-tmp 472 * @static 473 * @property TMP 474 */ 475 Y.EditorSelection.TMP = 'yui-tmp'; 476 /** 477 * The default tag to use when creating elements: span 478 * @static 479 * @property DEFAULT_TAG 480 */ 481 Y.EditorSelection.DEFAULT_TAG = 'span'; 482 483 /** 484 * The id of the outer cursor wrapper 485 * @static 486 * @property CURID 487 */ 488 Y.EditorSelection.CURID = 'yui-cursor'; 489 490 /** 491 * The id used to wrap the inner space of the cursor position 492 * @static 493 * @property CUR_WRAPID 494 */ 495 Y.EditorSelection.CUR_WRAPID = 'yui-cursor-wrapper'; 496 497 /** 498 * The default HTML used to focus the cursor.. 499 * @static 500 * @property CURSOR 501 */ 502 Y.EditorSelection.CURSOR = '<span><br class="yui-cursor"></span>'; 503 504 /** 505 * The default HTML element from which data will be retrieved. Default: body 506 * @static 507 * @property ROOT 508 */ 509 Y.EditorSelection.ROOT = Y.one('body'); 510 511 Y.EditorSelection.hasCursor = function() { 512 var cur = Y.all('#' + Y.EditorSelection.CUR_WRAPID); 513 return cur.size(); 514 }; 515 516 /** 517 * Called from Editor keydown to remove the "extra" space before the cursor. 518 * @static 519 * @method cleanCursor 520 */ 521 Y.EditorSelection.cleanCursor = function() { 522 var cur, sel = 'br.yui-cursor'; 523 cur = Y.all(sel); 524 if (cur.size()) { 525 cur.each(function(b) { 526 var c = b.get('parentNode.parentNode.childNodes'), html; 527 if (c.size()) { 528 b.remove(); 529 } else { 530 html = Y.EditorSelection.getText(c.item(0)); 531 if (html !== '') { 532 b.remove(); 533 } 534 } 535 }); 536 } 537 /* 538 var cur = Y.all('#' + Y.EditorSelection.CUR_WRAPID); 539 if (cur.size()) { 540 cur.each(function(c) { 541 var html = c.get('innerHTML'); 542 if (html == ' ' || html == '<br>') { 543 if (c.previous() || c.next()) { 544 c.remove(); 545 } 546 } 547 }); 548 } 549 */ 550 }; 551 552 Y.EditorSelection.prototype = { 553 /** 554 * Range text value 555 * @property text 556 * @type String 557 */ 558 text: null, 559 /** 560 * Flag to show if the range is collapsed or not 561 * @property isCollapsed 562 * @type Boolean 563 */ 564 isCollapsed: null, 565 /** 566 * A Node instance of the parentNode of the anchorNode of the range 567 * @property anchorNode 568 * @type Node 569 */ 570 anchorNode: null, 571 /** 572 * The offset from the range object 573 * @property anchorOffset 574 * @type Number 575 */ 576 anchorOffset: null, 577 /** 578 * A Node instance of the actual textNode of the range. 579 * @property anchorTextNode 580 * @type Node 581 */ 582 anchorTextNode: null, 583 /** 584 * A Node instance of the parentNode of the focusNode of the range 585 * @property focusNode 586 * @type Node 587 */ 588 focusNode: null, 589 /** 590 * The offset from the range object 591 * @property focusOffset 592 * @type Number 593 */ 594 focusOffset: null, 595 /** 596 * A Node instance of the actual textNode of the range. 597 * @property focusTextNode 598 * @type Node 599 */ 600 focusTextNode: null, 601 /** 602 * The actual Selection/Range object 603 * @property _selection 604 * @private 605 */ 606 _selection: null, 607 /** 608 * Wrap an element, with another element 609 * @private 610 * @method _wrap 611 * @param {HTMLElement} n The node to wrap 612 * @param {String} tag The tag to use when creating the new element. 613 * @return {HTMLElement} The wrapped node 614 */ 615 _wrap: function(n, tag) { 616 var tmp = Y.Node.create('<' + tag + '></' + tag + '>'); 617 tmp.set(INNER_HTML, n.get(INNER_HTML)); 618 n.set(INNER_HTML, ''); 619 n.append(tmp); 620 return Y.Node.getDOMNode(tmp); 621 }, 622 /** 623 * Swap an element, with another element 624 * @private 625 * @method _swap 626 * @param {HTMLElement} n The node to swap 627 * @param {String} tag The tag to use when creating the new element. 628 * @return {HTMLElement} The new node 629 */ 630 _swap: function(n, tag) { 631 var tmp = Y.Node.create('<' + tag + '></' + tag + '>'); 632 tmp.set(INNER_HTML, n.get(INNER_HTML)); 633 n.replace(tmp, n); 634 return Y.Node.getDOMNode(tmp); 635 }, 636 /** 637 * Get all the nodes in the current selection. This method will actually perform a filter first. 638 * Then it calls doc.execCommand('fontname', null, 'yui-tmp') to touch all nodes in the selection. 639 * The it compiles a list of all nodes affected by the execCommand and builds a NodeList to return. 640 * @method getSelected 641 * @return {NodeList} A NodeList of all items in the selection. 642 */ 643 getSelected: function() { 644 var editorSelection = Y.EditorSelection, 645 root = editorSelection.ROOT, 646 nodes, 647 items = []; 648 649 editorSelection.filter(); 650 Y.config.doc.execCommand('fontname', null, editorSelection.TMP); 651 nodes = root.all(editorSelection.ALL); 652 653 nodes.each(function(n, k) { 654 if (n.getStyle(FONT_FAMILY) === editorSelection.TMP) { 655 n.setStyle(FONT_FAMILY, ''); 656 editorSelection.removeFontFamily(n); 657 if (!n.compareTo(root)) { 658 items.push(Y.Node.getDOMNode(nodes.item(k))); 659 } 660 } 661 }); 662 return Y.all(items); 663 }, 664 /** 665 * Insert HTML at the current cursor position and return a Node instance of the newly inserted element. 666 * @method insertContent 667 * @param {String} html The HTML to insert. 668 * @return {Node} The inserted Node. 669 */ 670 insertContent: function(html) { 671 return this.insertAtCursor(html, this.anchorTextNode, this.anchorOffset, true); 672 }, 673 /** 674 * Insert HTML at the current cursor position, this method gives you control over the text node to insert into and the offset where to put it. 675 * @method insertAtCursor 676 * @param {String} html The HTML to insert. 677 * @param {Node} node The text node to break when inserting. 678 * @param {Number} offset The left offset of the text node to break and insert the new content. 679 * @param {Boolean} collapse Should the range be collapsed after insertion. default: false 680 * @return {Node} The inserted Node. 681 */ 682 insertAtCursor: function(html, node, offset, collapse) { 683 var cur = Y.Node.create('<' + Y.EditorSelection.DEFAULT_TAG + ' class="yui-non"></' + Y.EditorSelection.DEFAULT_TAG + '>'), 684 inHTML, txt, txt2, newNode, range = this.createRange(), b, root = Y.EditorSelection.ROOT; 685 686 if (root.compareTo(node)) { 687 b = Y.Node.create('<span></span>'); 688 node.append(b); 689 node = b; 690 } 691 692 693 if (range.pasteHTML) { 694 if (offset === 0 && node && !node.previous() && node.get('nodeType') === 3) { 695 /* 696 * For some strange reason, range.pasteHTML fails if the node is a textNode and 697 * the offset is 0. (The cursor is at the beginning of the line) 698 * It will always insert the new content at position 1 instead of 699 * position 0. Here we test for that case and do it the hard way. 700 */ 701 node.insert(html, 'before'); 702 if (range.moveToElementText) { 703 range.moveToElementText(Y.Node.getDOMNode(node.previous())); 704 } 705 //Move the cursor after the new node 706 range.collapse(false); 707 range.select(); 708 return node.previous(); 709 } else { 710 newNode = Y.Node.create(html); 711 try { 712 range.pasteHTML('<span id="rte-insert"></span>'); 713 } catch (e) {} 714 inHTML = root.one('#rte-insert'); 715 if (inHTML) { 716 inHTML.set('id', ''); 717 inHTML.replace(newNode); 718 if (range.moveToElementText) { 719 range.moveToElementText(Y.Node.getDOMNode(newNode)); 720 } 721 range.collapse(false); 722 range.select(); 723 return newNode; 724 } else { 725 Y.on('available', function() { 726 inHTML.set('id', ''); 727 inHTML.replace(newNode); 728 if (range.moveToElementText) { 729 range.moveToElementText(Y.Node.getDOMNode(newNode)); 730 } 731 range.collapse(false); 732 range.select(); 733 }, '#rte-insert'); 734 } 735 } 736 } else { 737 //TODO using Y.Node.create here throws warnings & strips first white space character 738 //txt = Y.one(Y.Node.create(inHTML.substr(0, offset))); 739 //txt2 = Y.one(Y.Node.create(inHTML.substr(offset))); 740 if (offset > 0) { 741 inHTML = node.get(textContent); 742 743 txt = Y.one(Y.config.doc.createTextNode(inHTML.substr(0, offset))); 744 txt2 = Y.one(Y.config.doc.createTextNode(inHTML.substr(offset))); 745 746 node.replace(txt, node); 747 newNode = Y.Node.create(html); 748 if (newNode.get('nodeType') === 11) { 749 b = Y.Node.create('<span></span>'); 750 b.append(newNode); 751 newNode = b; 752 } 753 txt.insert(newNode, 'after'); 754 //if (txt2 && txt2.get('length')) { 755 if (txt2) { 756 newNode.insert(cur, 'after'); 757 cur.insert(txt2, 'after'); 758 this.selectNode(cur, collapse); 759 } 760 } else { 761 if (node.get('nodeType') === 3) { 762 node = node.get('parentNode') || root; 763 } 764 newNode = Y.Node.create(html); 765 html = node.get('innerHTML').replace(/\n/gi, ''); 766 if (html === '' || html === '<br>') { 767 node.append(newNode); 768 } else { 769 if (newNode.get('parentNode')) { 770 node.insert(newNode, 'before'); 771 } else { 772 root.prepend(newNode); 773 } 774 } 775 if (node.get('firstChild').test('br')) { 776 node.get('firstChild').remove(); 777 } 778 } 779 } 780 return newNode; 781 }, 782 /** 783 * Get all elements inside a selection and wrap them with a new element and return a NodeList of all elements touched. 784 * @method wrapContent 785 * @param {String} tag The tag to wrap all selected items with. 786 * @return {NodeList} A NodeList of all items in the selection. 787 */ 788 wrapContent: function(tag) { 789 tag = (tag) ? tag : Y.EditorSelection.DEFAULT_TAG; 790 791 if (!this.isCollapsed) { 792 var items = this.getSelected(), 793 changed = [], range, last, first, range2; 794 795 items.each(function(n, k) { 796 var t = n.get('tagName').toLowerCase(); 797 if (t === 'font') { 798 changed.push(this._swap(items.item(k), tag)); 799 } else { 800 changed.push(this._wrap(items.item(k), tag)); 801 } 802 }, this); 803 804 range = this.createRange(); 805 first = changed[0]; 806 last = changed[changed.length - 1]; 807 if (this._selection.removeAllRanges) { 808 range.setStart(changed[0], 0); 809 range.setEnd(last, last.childNodes.length); 810 this._selection.removeAllRanges(); 811 this._selection.addRange(range); 812 } else { 813 if (range.moveToElementText) { 814 range.moveToElementText(Y.Node.getDOMNode(first)); 815 range2 = this.createRange(); 816 range2.moveToElementText(Y.Node.getDOMNode(last)); 817 range.setEndPoint('EndToEnd', range2); 818 } 819 range.select(); 820 } 821 822 changed = Y.all(changed); 823 return changed; 824 825 826 } else { 827 return Y.all([]); 828 } 829 }, 830 /** 831 * Find and replace a string inside a text node and replace it with HTML focusing the node after 832 * to allow you to continue to type. 833 * @method replace 834 * @param {String} se The string to search for. 835 * @param {String} re The string of HTML to replace it with. 836 * @return {Node} The node inserted. 837 */ 838 replace: function(se,re) { 839 var range = this.createRange(), node, txt, index, newNode; 840 841 if (range.getBookmark) { 842 index = range.getBookmark(); 843 txt = this.anchorNode.get('innerHTML').replace(se, re); 844 this.anchorNode.set('innerHTML', txt); 845 range.moveToBookmark(index); 846 newNode = Y.one(range.parentElement()); 847 } else { 848 node = this.anchorTextNode; 849 txt = node.get(textContent); 850 index = txt.indexOf(se); 851 852 txt = txt.replace(se, ''); 853 node.set(textContent, txt); 854 newNode = this.insertAtCursor(re, node, index, true); 855 } 856 return newNode; 857 }, 858 /** 859 * Destroy the range. 860 * @method remove 861 * @chainable 862 * @return {EditorSelection} 863 */ 864 remove: function() { 865 if (this._selection && this._selection.removeAllRanges) { 866 this._selection.removeAllRanges(); 867 } 868 return this; 869 }, 870 /** 871 * Wrapper for the different range creation methods. 872 * @method createRange 873 * @return {Range} 874 */ 875 createRange: function() { 876 if (Y.config.doc.selection) { 877 return Y.config.doc.selection.createRange(); 878 } else { 879 return Y.config.doc.createRange(); 880 } 881 }, 882 /** 883 * Select a Node (hilighting it). 884 * @method selectNode 885 * @param {Node} node The node to select 886 * @param {Boolean} collapse Should the range be collapsed after insertion. default: false 887 * @chainable 888 * @return {EditorSelection} 889 */ 890 selectNode: function(node, collapse, end) { 891 if (!node) { 892 return; 893 } 894 end = end || 0; 895 node = Y.Node.getDOMNode(node); 896 var range = this.createRange(); 897 if (range.selectNode) { 898 try { 899 range.selectNode(node); 900 } catch (err) { 901 // Ignore selection errors like INVALID_NODE_TYPE_ERR 902 } 903 this._selection.removeAllRanges(); 904 this._selection.addRange(range); 905 if (collapse) { 906 try { 907 this._selection.collapse(node, end); 908 } catch (err) { 909 this._selection.collapse(node, 0); 910 } 911 } 912 } else { 913 if (node.nodeType === 3) { 914 node = node.parentNode; 915 } 916 try { 917 range.moveToElementText(node); 918 } catch(e) {} 919 if (collapse) { 920 range.collapse(((end) ? false : true)); 921 } 922 range.select(); 923 } 924 return this; 925 }, 926 /** 927 * Put a placeholder in the DOM at the current cursor position. 928 * @method setCursor 929 * @return {Node} 930 */ 931 setCursor: function() { 932 this.removeCursor(false); 933 return this.insertContent(Y.EditorSelection.CURSOR); 934 }, 935 /** 936 * Get the placeholder in the DOM at the current cursor position. 937 * @method getCursor 938 * @return {Node} 939 */ 940 getCursor: function() { 941 return Y.EditorSelection.ROOT.all('.' + Y.EditorSelection.CURID); 942 }, 943 /** 944 * Remove the cursor placeholder from the DOM. 945 * @method removeCursor 946 * @param {Boolean} keep Setting this to true will keep the node, but remove the unique parts that make it the cursor. 947 * @return {Node} 948 */ 949 removeCursor: function(keep) { 950 var cur = this.getCursor(); 951 if (cur) { 952 if (keep) { 953 cur.removeAttribute('id'); 954 cur.set('innerHTML', '<br class="yui-cursor">'); 955 } else { 956 cur.remove(); 957 } 958 } 959 return cur; 960 }, 961 /** 962 * Gets a stored cursor and focuses it for editing, must be called sometime after setCursor 963 * @method focusCursor 964 * @return {Node} 965 */ 966 focusCursor: function(collapse, end) { 967 if (collapse !== false) { 968 collapse = true; 969 } 970 if (end !== false) { 971 end = true; 972 } 973 var cur = this.removeCursor(true); 974 if (cur) { 975 cur.each(function(c) { 976 this.selectNode(c, collapse, end); 977 }, this); 978 } 979 }, 980 /** 981 * Generic toString for logging. 982 * @method toString 983 * @return {String} 984 */ 985 toString: function() { 986 return 'EditorSelection Object'; 987 }, 988 989 /** 990 Gets the offset of the selection for the selection within the current 991 editor 992 @public 993 @method getEditorOffset 994 @param {Y.Node} [node] Element used to measure the offset to 995 @return Number Number of characters the selection is from the beginning 996 @since 3.13.0 997 */ 998 getEditorOffset: function(node) { 999 var container = (node || Y.EditorSelection.ROOT).getDOMNode(), 1000 caretOffset = 0, 1001 doc = Y.config.doc, 1002 win = Y.config.win, 1003 sel, 1004 range, 1005 preCaretRange; 1006 1007 if (typeof win.getSelection !== "undefined") { 1008 range = win.getSelection().getRangeAt(0); 1009 preCaretRange = range.cloneRange(); 1010 preCaretRange.selectNodeContents(container); 1011 preCaretRange.setEnd(range.endContainer, range.endOffset); 1012 caretOffset = preCaretRange.toString().length; 1013 } else { 1014 sel = doc.selection; 1015 1016 if ( sel && sel.type !== "Control") { 1017 range = sel.createRange(); 1018 preCaretRange = doc.body.createTextRange(); 1019 preCaretRange.moveToElementText(container); 1020 preCaretRange.setEndPoint("EndToEnd", range); 1021 caretOffset = preCaretRange.text.length; 1022 } 1023 } 1024 1025 return caretOffset; 1026 } 1027 }; 1028 1029 //TODO Remove this alias in 3.6.0 1030 Y.Selection = Y.EditorSelection; 1031 1032 1033 1034 }, '3.17.2', {"requires": ["node"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |