[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

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


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