2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('editor-selection', function (Y, NAME) {
11 * Wraps some common Selection/Range functionality into a simple object
12 * @class EditorSelection
15 * @submodule selection
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';
24 textContent = 'nodeValue';
27 Y.EditorSelection = function(domEvent) {
28 var sel, par, ieNode, nodes, rng, i,
29 comp, moved = 0, n, id, root = Y.EditorSelection.ROOT;
32 if (Y.config.win.getSelection && (!Y.UA.ie || Y.UA.ie < 9)) {
33 sel = Y.config.win.getSelection();
34 } else if (Y.config.doc.selection) {
35 sel = Y.config.doc.selection.createRange();
37 this._selection = sel;
44 this.isCollapsed = (sel.compareEndPoints('StartToEnd', sel)) ? false : true;
45 if (this.isCollapsed) {
46 this.anchorNode = this.focusNode = Y.one(sel.parentElement());
49 ieNode = Y.config.doc.elementFromPoint(domEvent.clientX, domEvent.clientY);
51 rng = sel.duplicate();
53 par = sel.parentElement();
54 nodes = par.childNodes;
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)) {
70 if (ieNode.nodeType !== 3) {
71 if (ieNode.firstChild) {
72 ieNode = ieNode.firstChild;
74 if (root.compareTo(ieNode)) {
75 if (ieNode.firstChild) {
76 ieNode = ieNode.firstChild;
80 this.anchorNode = this.focusNode = Y.EditorSelection.resolve(ieNode);
82 rng.moveToElementText(sel.parentElement());
83 comp = sel.compareEndPoints('StartToStart', rng);
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));
91 this.anchorOffset = this.focusOffset = moved;
93 this.anchorTextNode = this.focusTextNode = Y.one(ieNode);
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')) {
103 this.anchorNode = this.focusNode = Y.one('#' + id);
105 n = n.get('childNodes');
106 this.anchorNode = this.focusNode = n.item(0);
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;
120 this.anchorTextNode = Y.one(sel.anchorNode || this.anchorNode);
121 this.focusTextNode = Y.one(sel.focusNode || this.focusNode);
123 if (Y.Lang.isString(sel.text)) {
124 this.text = sel.text;
127 this.text = sel.toString();
135 * Utility method to remove dead font-family styles from an element.
137 * @method removeFontFamily
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');
145 if (s.match(Y.EditorSelection.REG_FONTFAMILY)) {
146 s = s.replace(Y.EditorSelection.REG_FONTFAMILY, '');
147 n.setAttribute('style', s);
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.
158 Y.EditorSelection.filter = function(blocks) {
159 Y.log('Filtering nodes', 'info', 'editor-selection');
161 var startTime = (new Date()).getTime(),
162 editorSelection = Y.EditorSelection,
163 root = editorSelection.ROOT,
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(),
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];
178 editorSelection.removeFontFamily(raw);
181 endTime1 = (new Date()).getTime();
182 Y.log('Node Filter Timer: ' + (endTime1 - startTime1) + 'ms', 'info', 'editor-selection');
184 root.all('.hr').addClass('yui-skip').addClass('yui-non');
187 hrs = Y.Node.getDOMNode(root).getElementsByTagName('hr');
188 Y.each(hrs, function(hr) {
189 var el = doc.createElement('div'),
192 el.className = 'hr yui-non yui-skip';
194 el.setAttribute('readonly', true);
195 el.setAttribute('contenteditable', false); //Keep it from being Edited
197 hr.parentNode.replaceChild(el, hr);
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';
205 s.marginBottom = '5px';
206 s.marginLeft = '0px';
207 s.marginRight = '0px';
213 Y.each(classNames, function(v, k) {
214 cssString += k + ' { font-family: ' + v.replace(/"/gi, '') + '; }';
216 Y.StyleSheet(cssString, 'editor');
219 //Not sure about this one?
220 baseNodes.each(function(n, k) {
221 var t = n.get('tagName').toLowerCase(),
223 if (t === 'strong') {
226 editorSelection.prototype._swap(baseNodes.item(k), newTag);
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');
239 editorSelection.filterBlocks();
241 endTime = (new Date()).getTime();
242 Y.log('Filter Timer: ' + (endTime - startTime) + 'ms', 'info', 'editor-selection');
246 * Method attempts to replace all "orphined" text nodes in the main body by wrapping them with a <p>. Called from filter.
248 * @method filterBlocks
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;
257 for (i = 0; i < childs.length; i++) {
258 node = Y.one(childs[i]);
259 if (!node.test(Y.EditorSelection.BLOCKS)) {
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) {
273 wrapped.push(childs[i]);
276 wrapped = Y.EditorSelection._wrapBlock(wrapped);
279 wrapped = Y.EditorSelection._wrapBlock(wrapped);
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')) {
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);
297 if (br.item(0).test('.yui-cursor') && Y.UA.ie) {
302 single.each(function(p) {
303 var html = p.get('innerHTML');
305 Y.log('Empty Paragraph Tag Found, Removing It', 'info', 'editor-selection');
311 endTime = (new Date()).getTime();
312 Y.log('FilterBlocks Timer: ' + (endTime - startTime) + 'ms', 'info', 'editor-selection');
316 * Regular Expression used to find dead font-family styles
318 * @property REG_FONTFAMILY
320 Y.EditorSelection.REG_FONTFAMILY = /font-family:\s*;/;
323 * Regular Expression to determine if a string has a character in it
327 Y.EditorSelection.REG_CHAR = /[a-zA-Z-0-9_!@#\$%\^&*\(\)-=_+\[\]\\{}|;':",.\/<>\?]/gi;
330 * Regular Expression to determine if a string has a non-character in it
334 Y.EditorSelection.REG_NON = /[\s|\n|\t]/gi;
337 * Regular Expression to remove all HTML from a string
339 * @property REG_NOHTML
341 Y.EditorSelection.REG_NOHTML = /<\S[^><]*>/g;
345 * Wraps an array of elements in a Block level tag
350 Y.EditorSelection._wrapBlock = function(wrapped) {
352 var newChild = Y.Node.create('<' + Y.EditorSelection.DEFAULT_BLOCK_TAG + '></' + Y.EditorSelection.DEFAULT_BLOCK_TAG + '>'),
353 firstChild = Y.one(wrapped[0]), i;
355 for (i = 1; i < wrapped.length; i++) {
356 newChild.append(wrapped[i]);
358 firstChild.replace(newChild);
359 newChild.prepend(firstChild);
365 * Undoes what filter does enough to return the HTML from the Editor, then re-applies the filter.
368 * @return {String} The filtered HTML
370 Y.EditorSelection.unfilter = function() {
371 var root = Y.EditorSelection.ROOT,
372 nodes = root.all('[class]'),
373 html = '', nons, ids,
376 Y.log('UnFiltering nodes', 'info', 'editor-selection');
378 nodes.each(function(n) {
379 if (n.hasClass(n._yuid)) {
381 n.setStyle(FONT_FAMILY, n.getStyle(FONT_FAMILY));
382 n.removeClass(n._yuid);
383 if (n.getAttribute('class') === '') {
384 n.removeAttribute('class');
389 nons = root.all('.yui-non');
390 nons.each(function(n) {
391 if (!n.hasClass('yui-skip') && n.get('innerHTML') === '') {
394 n.removeClass('yui-non').removeClass('yui-skip');
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');
407 html = body.get('innerHTML');
410 root.all('.hr').addClass('yui-skip').addClass('yui-non');
413 nodes.each(function(n) {
415 n.setStyle(FONT_FAMILY, '');
416 if (n.getAttribute('style') === '') {
417 n.removeAttribute('style');
425 * Resolve a node from the selection object and return a Node instance
428 * @param {HTMLElement} n The HTMLElement to resolve. Might be a TextNode, gives parentNode.
429 * @return {Node} The Resolved node
431 Y.EditorSelection.resolve = function(n) {
433 return Y.EditorSelection.ROOT;
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)
443 n = Y.EditorSelection.ROOT;
450 * Returns the innerHTML of a node with all HTML tags removed.
453 * @param {Node} node The Node instance to remove the HTML from
454 * @return {String} The string of text
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>', '');
463 //Y.EditorSelection.DEFAULT_BLOCK_TAG = 'div';
464 Y.EditorSelection.DEFAULT_BLOCK_TAG = 'p';
467 * The selector to use when looking for Nodes to cache the value of: [style],font[face]
471 Y.EditorSelection.ALL = '[style],font[face]';
474 * The selector to use when looking for block level items.
478 Y.EditorSelection.BLOCKS = 'p,div,ul,ol,table,style';
480 * The temporary fontname applied to a selection to retrieve their values: yui-tmp
484 Y.EditorSelection.TMP = 'yui-tmp';
486 * The default tag to use when creating elements: span
488 * @property DEFAULT_TAG
490 Y.EditorSelection.DEFAULT_TAG = 'span';
493 * The id of the outer cursor wrapper
495 * @property DEFAULT_TAG
497 Y.EditorSelection.CURID = 'yui-cursor';
500 * The id used to wrap the inner space of the cursor position
502 * @property CUR_WRAPID
504 Y.EditorSelection.CUR_WRAPID = 'yui-cursor-wrapper';
507 * The default HTML used to focus the cursor..
511 Y.EditorSelection.CURSOR = '<span><br class="yui-cursor"></span>';
514 * The default HTML element from which data will be retrieved. Default: body
518 Y.EditorSelection.ROOT = Y.one('body');
520 Y.EditorSelection.hasCursor = function() {
521 var cur = Y.all('#' + Y.EditorSelection.CUR_WRAPID);
522 Y.log('Has Cursor: ' + cur.size(), 'info', 'editor-selection');
527 * Called from Editor keydown to remove the "extra" space before the cursor.
529 * @method cleanCursor
531 Y.EditorSelection.cleanCursor = function() {
532 //Y.log('Cleaning Cursor', 'info', 'Selection');
533 var cur, sel = 'br.yui-cursor';
536 cur.each(function(b) {
537 var c = b.get('parentNode.parentNode.childNodes'), html;
541 html = Y.EditorSelection.getText(c.item(0));
549 var cur = Y.all('#' + Y.EditorSelection.CUR_WRAPID);
551 cur.each(function(c) {
552 var html = c.get('innerHTML');
553 if (html == ' ' || html == '<br>') {
554 if (c.previous() || c.next()) {
563 Y.EditorSelection.prototype = {
571 * Flag to show if the range is collapsed or not
572 * @property isCollapsed
577 * A Node instance of the parentNode of the anchorNode of the range
578 * @property anchorNode
583 * The offset from the range object
584 * @property anchorOffset
589 * A Node instance of the actual textNode of the range.
590 * @property anchorTextNode
593 anchorTextNode: null,
595 * A Node instance of the parentNode of the focusNode of the range
596 * @property focusNode
601 * The offset from the range object
602 * @property focusOffset
607 * A Node instance of the actual textNode of the range.
608 * @property focusTextNode
613 * The actual Selection/Range object
614 * @property _selection
619 * Wrap an element, with another element
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
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, '');
631 return Y.Node.getDOMNode(tmp);
634 * Swap an element, with another element
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
641 _swap: function(n, tag) {
642 var tmp = Y.Node.create('<' + tag + '></' + tag + '>');
643 tmp.set(INNER_HTML, n.get(INNER_HTML));
645 return Y.Node.getDOMNode(tmp);
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.
654 getSelected: function() {
655 var editorSelection = Y.EditorSelection,
656 root = editorSelection.ROOT,
660 editorSelection.filter();
661 Y.config.doc.execCommand('fontname', null, editorSelection.TMP);
662 nodes = root.all(editorSelection.ALL);
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)));
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.
681 insertContent: function(html) {
682 return this.insertAtCursor(html, this.anchorTextNode, this.anchorOffset, true);
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.
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;
697 if (root.compareTo(node)) {
698 b = Y.Node.create('<span></span>');
704 if (range.pasteHTML) {
705 if (offset === 0 && node && !node.previous() && node.get('nodeType') === 3) {
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.
712 node.insert(html, 'before');
713 if (range.moveToElementText) {
714 range.moveToElementText(Y.Node.getDOMNode(node.previous()));
716 //Move the cursor after the new node
717 range.collapse(false);
719 return node.previous();
721 newNode = Y.Node.create(html);
723 range.pasteHTML('<span id="rte-insert"></span>');
725 inHTML = root.one('#rte-insert');
727 inHTML.set('id', '');
728 inHTML.replace(newNode);
729 if (range.moveToElementText) {
730 range.moveToElementText(Y.Node.getDOMNode(newNode));
732 range.collapse(false);
736 Y.on('available', function() {
737 inHTML.set('id', '');
738 inHTML.replace(newNode);
739 if (range.moveToElementText) {
740 range.moveToElementText(Y.Node.getDOMNode(newNode));
742 range.collapse(false);
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)));
752 inHTML = node.get(textContent);
754 txt = Y.one(Y.config.doc.createTextNode(inHTML.substr(0, offset)));
755 txt2 = Y.one(Y.config.doc.createTextNode(inHTML.substr(offset)));
757 node.replace(txt, node);
758 newNode = Y.Node.create(html);
759 if (newNode.get('nodeType') === 11) {
760 b = Y.Node.create('<span></span>');
764 txt.insert(newNode, 'after');
765 //if (txt2 && txt2.get('length')) {
767 newNode.insert(cur, 'after');
768 cur.insert(txt2, 'after');
769 this.selectNode(cur, collapse);
772 if (node.get('nodeType') === 3) {
773 node = node.get('parentNode') || root;
775 newNode = Y.Node.create(html);
776 html = node.get('innerHTML').replace(/\n/gi, '');
777 if (html === '' || html === '<br>') {
778 node.append(newNode);
780 if (newNode.get('parentNode')) {
781 node.insert(newNode, 'before');
783 root.prepend(newNode);
786 if (node.get('firstChild').test('br')) {
787 node.get('firstChild').remove();
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.
799 wrapContent: function(tag) {
800 tag = (tag) ? tag : Y.EditorSelection.DEFAULT_TAG;
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;
807 items.each(function(n, k) {
808 var t = n.get('tagName').toLowerCase();
810 changed.push(this._swap(items.item(k), tag));
812 changed.push(this._wrap(items.item(k), tag));
816 range = this.createRange();
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);
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);
834 changed = Y.all(changed);
835 Y.log('Returning NodeList with (' + changed.size() + ') item(s)' , 'info', 'editor-selection');
840 Y.log('Can not wrap a collapsed selection, use insertContent', 'error', 'editor-selection');
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.
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.
852 replace: function(se,re) {
853 Y.log('replacing (' + se + ') with (' + re + ')');
854 var range = this.createRange(), node, txt, index, newNode;
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());
863 node = this.anchorTextNode;
864 txt = node.get(textContent);
865 index = txt.indexOf(se);
867 txt = txt.replace(se, '');
868 node.set(textContent, txt);
869 newNode = this.insertAtCursor(re, node, index, true);
877 * @return {EditorSelection}
880 if (this._selection && this._selection.removeAllRanges) {
881 this._selection.removeAllRanges();
886 * Wrapper for the different range creation methods.
887 * @method createRange
888 * @return {RangeObject}
890 createRange: function() {
891 if (Y.config.doc.selection) {
892 return Y.config.doc.selection.createRange();
894 return Y.config.doc.createRange();
898 * Select a Node (hilighting it).
900 * @param {Node} node The node to select
901 * @param {Boolean} collapse Should the range be collapsed after insertion. default: false
903 * @return {EditorSelection}
905 selectNode: function(node, collapse, end) {
907 Y.log('Node passed to selectNode is null', 'error', 'editor-selection');
911 node = Y.Node.getDOMNode(node);
912 var range = this.createRange();
913 if (range.selectNode) {
914 range.selectNode(node);
915 this._selection.removeAllRanges();
916 this._selection.addRange(range);
919 this._selection.collapse(node, end);
921 this._selection.collapse(node, 0);
925 if (node.nodeType === 3) {
926 node = node.parentNode;
929 range.moveToElementText(node);
932 range.collapse(((end) ? false : true));
939 * Put a placeholder in the DOM at the current cursor position.
943 setCursor: function() {
944 this.removeCursor(false);
945 return this.insertContent(Y.EditorSelection.CURSOR);
948 * Get the placeholder in the DOM at the current cursor position.
952 getCursor: function() {
953 return Y.EditorSelection.ROOT.all('#' + Y.EditorSelection.CURID);
956 * Remove the cursor placeholder from the DOM.
957 * @method removeCursor
958 * @param {Boolean} keep Setting this to true will keep the node, but remove the unique parts that make it the cursor.
961 removeCursor: function(keep) {
962 var cur = this.getCursor();
965 cur.removeAttribute('id');
966 cur.set('innerHTML', '<br class="yui-cursor">');
974 * Gets a stored cursor and focuses it for editing, must be called sometime after setCursor
975 * @method focusCursor
978 focusCursor: function(collapse, end) {
979 if (collapse !== false) {
985 var cur = this.removeCursor(true);
987 cur.each(function(c) {
988 this.selectNode(c, collapse, end);
993 * Generic toString for logging.
997 toString: function() {
998 return 'EditorSelection Object';
1002 Gets the offset of the selection for the selection within the current
1005 @method getEditorOffset
1006 @param {Y.Node} [node] Element used to measure the offset to
1007 @return Number Number of characters the selection is from the beginning
1010 getEditorOffset: function(node) {
1011 var container = (node || Y.EditorSelection.ROOT).getDOMNode(),
1019 if (typeof win.getSelection !== "undefined") {
1020 range = win.getSelection().getRangeAt(0);
1021 preCaretRange = range.cloneRange();
1022 preCaretRange.selectNodeContents(container);
1023 preCaretRange.setEnd(range.endContainer, range.endOffset);
1024 caretOffset = preCaretRange.toString().length;
1026 sel = doc.selection;
1028 if ( sel && sel.type !== "Control") {
1029 range = sel.createRange();
1030 preCaretRange = doc.body.createTextRange();
1031 preCaretRange.moveToElementText(container);
1032 preCaretRange.setEndPoint("EndToEnd", range);
1033 caretOffset = preCaretRange.text.length;
1041 //TODO Remove this alias in 3.6.0
1042 Y.Selection = Y.EditorSelection;
1046 }, '3.13.0', {"requires": ["node"]});