[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/editor-selection/ -> editor-selection.js (source)

   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 == '&nbsp;' || 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"]});


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1