selection.js revision c653ab6f9e382f9eca2d44393ee71262257fbe55
5180N/A //TODO This shouldn't be there, Y.Node doesn't normalize getting textnode content. 5264N/A //This causes IE to not allow a selection on a doubleclick //This helps IE deal with a selection and nodeChange events * Utility method to remove dead font-family styles from an element. * @method removeFontFamily if (s ===
'' || (s ==
'font-family: ')) {
* Performs a prefilter on all nodes in the editor. Looks for nodes with a style: fontFamily or font face * It then creates a dynamic class assigns it and removed the property. This is so that we don't lose * the fontFamily when selecting nodes. Y.
log(
'Filtering nodes',
'info',
'selection');
if (n.getStyle(FONT_FAMILY)) { classNames['.' + n._yuid] = n.getStyle(FONT_FAMILY); n.removeAttribute('face'); n.setStyle(FONT_FAMILY, ''); if (n.getAttribute('style') === '') { n.removeAttribute('style'); if (n.getAttribute('style').toLowerCase() === 'font-family: ') { n.removeAttribute('style'); el.
setAttribute(
'contenteditable',
false);
//Keep it from being Edited //Had to move to inline style. writes for ie's < 8. They don't render el.setAttribute('style'); Y.StyleSheet(cssString, 'editor'); //Not sure about this one? baseNodes.each(function(n, k) { var t = n.get('tagName').toLowerCase(), Y.Selection.prototype._swap(baseNodes.item(k), newTag); //Filter out all the empty UL/OL's Y.Selection.filterBlocks(); var endTime = (new Date()).getTime(); Y.log('Filter Timer: ' + (endTime - startTime) + 'ms', 'info', 'selection'); * Method attempts to replace all "orphined" text nodes in the main body by wrapping them with a <p>. Called from filter. Y.Selection.filterBlocks = function() { var startTime = (new Date()).getTime(); Y.log('RAW filter blocks', 'info', 'selection'); var childs = Y.config.doc.body.childNodes, i, node, wrapped = false, doit = true, sel, single, br, divs, spans, c, s; for (i = 0; i < childs.length; i++) { if (!node.test(Y.Selection.BLOCKS)) { if (childs[i].nodeType == 3) { c = childs[i][textContent].match(Y.Selection.REG_CHAR); s = childs[i][textContent].match(Y.Selection.REG_NON); wrapped = Y.Selection._wrapBlock(wrapped); wrapped = Y.Selection._wrapBlock(wrapped); single = Y.all(Y.Selection.DEFAULT_BLOCK_TAG); if (single.size() === 1) { Y.log('Only One default block tag (' + Y.Selection.DEFAULT_BLOCK_TAG + '), focus it..', 'info', 'selection'); br = single.item(0).all('br'); if (!br.item(0).test('.yui-cursor')) { var html = single.item(0).get('innerHTML'); if (html === '' || html === ' ') { Y.log('Paragraph empty, focusing cursor', 'info', 'selection'); single.set('innerHTML', Y.Selection.CURSOR); sel.focusCursor(true, true); if (br.item(0).test('.yui-cursor') && Y.UA.ie) { single.each(function(p) { var html = p.get('innerHTML'); Y.log('Empty Paragraph Tag Found, Removing It', 'info', 'selection'); if (d.hasClass('yui-non')) { var html = d.get('innerHTML'); Y.log('Empty DIV/P Tag Found, Removing It', 'info', 'selection'); Y.log('DIVS/PS Count: ' + d.get('childNodes').size(), 'info', 'selection'); if (d.get('childNodes').size() == 1) { Y.log('This Div/P only has one Child Node', 'info', 'selection'); Y.log('This Div/P is a child of a paragraph, remove it..', 'info', 'selection'); d.replace(d.get('firstChild')); /** Removed this, as it was causing Pasting to be funky in Safari spans = Y.all('.Apple-style-span, .apple-style-span'); Y.log('Apple Spans found: ' + spans.size(), 'info', 'selection'); s.setAttribute('style', ''); var endTime = (new Date()).getTime(); Y.log('FilterBlocks Timer: ' + (endTime - startTime) + 'ms', 'info', 'selection'); * Regular Expression used to find dead font-family styles * @property REG_FONTFAMILY Y.Selection.REG_FONTFAMILY = /font-family: ;/; * Regular Expression to determine if a string has a character in it Y.Selection.REG_CHAR = /[a-zA-Z-0-9_!@#\$%\^&*\(\)-=_+\[\]\\{}|;':",.\/<>\?]/
gi;
* Regular Expression to determine if a string has a non-character in it * Regular Expression to remove all HTML from a string * Wraps an array of elements in a Block level tag * Undoes what filter does enough to return the HTML from the Editor, then re-applies the filter. * @return {String} The filtered HTML Y.
log(
'UnFiltering nodes',
'info',
'selection');
if (!n.
hasClass(
'yui-skip') && n.
get(
'innerHTML') ===
'') {
n.setStyle(FONT_FAMILY, ''); if (n.getAttribute('style') === '') { n.removeAttribute('style'); * Resolve a node from the selection object and return a Node instance * @param {HTMLElement} n The HTMLElement to resolve. Might be a TextNode, gives parentNode. * @return {Node} The Resolved node //Adding a try/catch here because in rare occasions IE will //Throw a error accessing the parentNode of a stranded text node. //In the case of Ctrl+Z (Undo) * Returns the innerHTML of a node with all HTML tags removed. * @param {Node} node The Node instance to remove the HTML from * @return {String} The string of text //Clean out the cursor subs to see if the Node is empty //Y.Selection.DEFAULT_BLOCK_TAG = 'div'; * The selector to use when looking for Nodes to cache the value of: [style],font[face] * The selector to use when looking for block level items. * The temporary fontname applied to a selection to retrieve their values: yui-tmp * The default tag to use when creating elements: span * The id of the outer cursor wrapper * The id used to wrap the inner space of the cursor position * The default HTML used to focus the cursor.. Y.
log(
'Has Cursor: ' +
cur.
size(),
'info',
'selection');
* Called from Editor keydown to remove the "extra" space before the cursor. //Y.log('Cleaning Cursor', 'info', 'Selection'); var cur,
sel =
'br.yui-cursor';
var c = b.
get(
'parentNode.parentNode.childNodes'),
html;
var cur = Y.all('#' + Y.Selection.CUR_WRAPID); var html = c.get('innerHTML'); if (html == ' ' || html == '<br>') { if (c.previous() || c.next()) { * Flag to show if the range is collapsed or not * A Node instance of the parentNode of the anchorNode of the range * The offset from the range object * A Node instance of the actual textNode of the range. * @property anchorTextNode * A Node instance of the parentNode of the focusNode of the range * The offset from the range object * A Node instance of the actual textNode of the range. * @property focusTextNode * Wrap an element, with another element * @param {HTMLElement} n The node to wrap * @param {String} tag The tag to use when creating the new element. * @return {HTMLElement} The wrapped node * Swap an element, with another element * @param {HTMLElement} n The node to swap * @param {String} tag The tag to use when creating the new element. * @return {HTMLElement} The new node * Get all the nodes in the current selection. This method will actually perform a filter first. * Then it calls doc.execCommand('fontname', null, 'yui-tmp') to touch all nodes in the selection. * The it compiles a list of all nodes affected by the execCommand and builds a NodeList to return. * @return {NodeList} A NodeList of all items in the selection. * Insert HTML at the current cursor position and return a Node instance of the newly inserted element. * @param {String} html The HTML to insert. * @return {Node} The inserted Node. * 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. * @param {String} html The HTML to insert. * @param {Node} node The text node to break when inserting. * @param {Number} offset The left offset of the text node to break and insert the new content. * @param {Boolean} collapse Should the range be collapsed after insertion. default: false * @return {Node} The inserted Node. * For some strange reason, range.pasteHTML fails if the node is a textNode and * the offset is 0. (The cursor is at the beginning of the line) * It will always insert the new content at position 1 instead of * position 0. Here we test for that case and do it the hard way. //Move the cursor after the new node Y.
on(
'available',
function() {
//TODO using Y.Node.create here throws warnings & strips first white space character //txt = Y.one(Y.Node.create(inHTML.substr(0, offset))); //txt2 = Y.one(Y.Node.create(inHTML.substr(offset))); //if (txt2 && txt2.get('length')) { * Get all elements inside a selection and wrap them with a new element and return a NodeList of all elements touched. * @param {String} tag The tag to wrap all selected items with. * @return {NodeList} A NodeList of all items in the selection. Y.
log(
'Wrapping selection with: ' +
tag,
'info',
'selection');
Y.
log(
'Returning NodeList with (' +
changed.
size() +
') item(s)' ,
'info',
'selection');
Y.
log(
'Can not wrap a collapsed selection, use insertContent',
'error',
'selection');
* Find and replace a string inside a text node and replace it with HTML focusing the node after * to allow you to continue to type. * @param {String} se The string to search for. * @param {String} re The string of HTML to replace it with. * @return {Node} The node inserted. Y.
log(
'replacing (' +
se +
') with (' +
re +
')');
* Wrapper for the different range creation methods. * Select a Node (hilighting it). * @param {Node} node The node to select * @param {Boolean} collapse Should the range be collapsed after insertion. default: false Y.
log(
'Node passed to selectNode is null',
'error',
'selection');
* Put a placeholder in the DOM at the current cursor position. * Get the placeholder in the DOM at the current cursor position. * Remove the cursor placeholder from the DOM. * @param {Boolean} keep Setting this to true will keep the node, but remove the unique parts that make it the cursor. cur.
set(
'innerHTML',
'<br class="yui-cursor">');
* Gets a stored cursor and focuses it for editing, must be called sometime after setCursor * Generic toString for logging. return 'Selection Object';