| // tabs 2 |
| |
| /** |
| * @fileoverview By Adam Wright, for The University of Western Australia |
| * |
| * Distributed under the same terms as Xinha itself. |
| * This notice MUST stay intact for use (see license.txt). |
| * |
| * Heavily modified by Yermo Lamers of DTLink, LLC, College Park, Md., USA. |
| * For more info see http://www.areaedit.com |
| */ |
| |
| /** |
| * plugin Info |
| */ |
| |
| EnterParagraphs._pluginInfo = |
| { |
| name : "EnterParagraphs", |
| version : "1.0", |
| developer : "Adam Wright", |
| developer_url : "http://www.hipikat.org/", |
| sponsor : "The University of Western Australia", |
| sponsor_url : "http://www.uwa.edu.au/", |
| license : "htmlArea" |
| }; |
| |
| // ------------------------------------------------------------------ |
| |
| // "constants" |
| |
| /** |
| * Whitespace Regex |
| */ |
| |
| EnterParagraphs.prototype._whiteSpace = /^\s*$/; |
| |
| /** |
| * The pragmatic list of which elements a paragraph may not contain |
| */ |
| |
| EnterParagraphs.prototype._pExclusions = /^(address|blockquote|body|dd|div|dl|dt|fieldset|form|h1|h2|h3|h4|h5|h6|hr|li|noscript|ol|p|pre|table|ul)$/i; |
| |
| /** |
| * elements which may contain a paragraph |
| */ |
| |
| EnterParagraphs.prototype._pContainers = /^(body|del|div|fieldset|form|ins|map|noscript|object|td|th)$/i; |
| |
| /** |
| * Elements which may not contain paragraphs, and would prefer a break to being split |
| */ |
| |
| EnterParagraphs.prototype._pBreak = /^(address|pre|blockquote)$/i; |
| |
| /** |
| * Elements which may not contain children |
| */ |
| |
| EnterParagraphs.prototype._permEmpty = /^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i; |
| |
| /** |
| * Elements which count as content, as distinct from whitespace or containers |
| */ |
| |
| EnterParagraphs.prototype._elemSolid = /^(applet|br|button|hr|img|input|table)$/i; |
| |
| /** |
| * Elements which should get a new P, before or after, when enter is pressed at either end |
| */ |
| |
| EnterParagraphs.prototype._pifySibling = /^(address|blockquote|del|div|dl|fieldset|form|h1|h2|h3|h4|h5|h6|hr|ins|map|noscript|object|ol|p|pre|table|ul|)$/i; |
| EnterParagraphs.prototype._pifyForced = /^(ul|ol|dl|table)$/i; |
| |
| /** |
| * Elements which should get a new P, before or after a close parent, when enter is pressed at either end |
| */ |
| |
| EnterParagraphs.prototype._pifyParent = /^(dd|dt|li|td|th|tr)$/i; |
| |
| // --------------------------------------------------------------------- |
| |
| /** |
| * EnterParagraphs Constructor |
| */ |
| |
| function EnterParagraphs(editor) |
| { |
| |
| this.editor = editor; |
| |
| // hook into the event handler to intercept key presses if we are using |
| // gecko (Mozilla/FireFox) |
| if (Xinha.is_gecko) |
| { |
| this.onKeyPress = this.__onKeyPress; |
| } |
| |
| } // end of constructor. |
| |
| // ------------------------------------------------------------------ |
| |
| /** |
| * name member for debugging |
| * |
| * This member is used to identify objects of this class in debugging |
| * messages. |
| */ |
| EnterParagraphs.prototype.name = "EnterParagraphs"; |
| |
| /** |
| * Gecko's a bit lacking in some odd ways... |
| */ |
| EnterParagraphs.prototype.insertAdjacentElement = function(ref,pos,el) |
| { |
| if ( pos == 'BeforeBegin' ) |
| { |
| ref.parentNode.insertBefore(el,ref); |
| } |
| else if ( pos == 'AfterEnd' ) |
| { |
| ref.nextSibling ? ref.parentNode.insertBefore(el,ref.nextSibling) : ref.parentNode.appendChild(el); |
| } |
| else if ( pos == 'AfterBegin' && ref.firstChild ) |
| { |
| ref.insertBefore(el,ref.firstChild); |
| } |
| else if ( pos == 'BeforeEnd' || pos == 'AfterBegin' ) |
| { |
| ref.appendChild(el); |
| } |
| |
| }; // end of insertAdjacentElement() |
| |
| // ---------------------------------------------------------------- |
| |
| /** |
| * Passes a global parent node or document fragment to forEachNode |
| * |
| * @param root node root node to start search from. |
| * @param mode string function to apply to each node. |
| * @param direction string traversal direction "ltr" (left to right) or "rtl" (right_to_left) |
| * @param init boolean |
| */ |
| |
| EnterParagraphs.prototype.forEachNodeUnder = function ( root, mode, direction, init ) |
| { |
| |
| // Identify the first and last nodes to deal with |
| var start, end; |
| |
| // nodeType 11 is DOCUMENT_FRAGMENT_NODE which is a container. |
| if ( root.nodeType == 11 && root.firstChild ) |
| { |
| start = root.firstChild; |
| end = root.lastChild; |
| } |
| else |
| { |
| start = end = root; |
| } |
| // traverse down the right hand side of the tree getting the last child of the last |
| // child in each level until we reach bottom. |
| while ( end.lastChild ) |
| { |
| end = end.lastChild; |
| } |
| |
| return this.forEachNode( start, end, mode, direction, init); |
| |
| }; // end of forEachNodeUnder() |
| |
| // ----------------------------------------------------------------------- |
| |
| /** |
| * perform a depth first descent in the direction requested. |
| * |
| * @param left_node node "start node" |
| * @param right_node node "end node" |
| * @param mode string function to apply to each node. cullids or emptyset. |
| * @param direction string traversal direction "ltr" (left to right) or "rtl" (right_to_left) |
| * @param init boolean or object. |
| */ |
| |
| EnterParagraphs.prototype.forEachNode = function (left_node, right_node, mode, direction, init) |
| { |
| |
| // returns "Brother" node either left or right. |
| var getSibling = function(elem, direction) |
| { |
| return ( direction == "ltr" ? elem.nextSibling : elem.previousSibling ); |
| }; |
| |
| var getChild = function(elem, direction) |
| { |
| return ( direction == "ltr" ? elem.firstChild : elem.lastChild ); |
| }; |
| |
| var walk, lookup, fnReturnVal; |
| |
| // FIXME: init is a boolean in the emptyset case and an object in |
| // the cullids case. Used inconsistently. |
| |
| var next_node = init; |
| |
| // used to flag having reached the last node. |
| |
| var done_flag = false; |
| |
| // loop ntil we've hit the last node in the given direction. |
| // if we're going left to right that's the right_node and visa-versa. |
| |
| while ( walk != direction == "ltr" ? right_node : left_node ) |
| { |
| |
| // on first entry, walk here is null. So this is how |
| // we prime the loop with the first node. |
| |
| if ( !walk ) |
| { |
| walk = direction == "ltr" ? left_node : right_node; |
| } |
| else |
| { |
| |
| // is there a child node? |
| |
| if ( getChild(walk,direction) ) |
| { |
| |
| // descend down into the child. |
| |
| walk = getChild(walk,direction); |
| |
| } |
| else |
| { |
| |
| // is there a sibling node on this level? |
| |
| if ( getSibling(walk,direction) ) |
| { |
| // move to the sibling. |
| walk = getSibling(walk,direction); |
| } |
| else |
| { |
| lookup = walk; |
| |
| // climb back up the tree until we find a level where we are not the end |
| // node on the level (i.e. that we have a sibling in the direction |
| // we are searching) or until we reach the end. |
| |
| while ( !getSibling(lookup,direction) && lookup != (direction == "ltr" ? right_node : left_node) ) |
| { |
| lookup = lookup.parentNode; |
| } |
| |
| // did we find a level with a sibling? |
| |
| // walk = ( lookup.nextSibling ? lookup.nextSibling : lookup ) ; |
| |
| walk = ( getSibling(lookup,direction) ? getSibling(lookup,direction) : lookup ) ; |
| |
| } |
| } |
| |
| } // end of else walk. |
| |
| // have we reached the end? either as a result of the top while loop or climbing |
| // back out above. |
| |
| done_flag = (walk==( direction == "ltr" ? right_node : left_node)); |
| |
| // call the requested function on the current node. Functions |
| // return an array. |
| // |
| // Possible functions are _fenCullIds, _fenEmptySet |
| // |
| // The situation is complicated by the fact that sometimes we want to |
| // return the base node and sometimes we do not. |
| // |
| // next_node can be an object (this.takenIds), a node (text, el, etc) or false. |
| |
| switch( mode ) |
| { |
| |
| case "cullids": |
| |
| fnReturnVal = this._fenCullIds(walk, next_node ); |
| break; |
| |
| case "find_fill": |
| |
| fnReturnVal = this._fenEmptySet(walk, next_node, mode, done_flag); |
| break; |
| |
| case "find_cursorpoint": |
| |
| fnReturnVal = this._fenEmptySet(walk, next_node, mode, done_flag); |
| break; |
| |
| } |
| |
| // If this node wants us to return, return next_node |
| |
| if ( fnReturnVal[0] ) |
| { |
| return fnReturnVal[1]; |
| } |
| |
| // are we done with the loop? |
| |
| if ( done_flag ) |
| { |
| break; |
| } |
| |
| // Otherwise, pass to the next node |
| |
| if ( fnReturnVal[1] ) |
| { |
| next_node = fnReturnVal[1]; |
| } |
| |
| } // end of while loop |
| |
| return false; |
| |
| }; // end of forEachNode() |
| |
| // ------------------------------------------------------------------- |
| |
| /** |
| * Find a post-insertion node, only if all nodes are empty, or the first content |
| * |
| * @param node node current node beinge examined. |
| * @param next_node node next node to be examined. |
| * @param node string "find_fill" or "find_cursorpoint" |
| * @param last_flag boolean is this the last node? |
| */ |
| |
| EnterParagraphs.prototype._fenEmptySet = function( node, next_node, mode, last_flag) |
| { |
| |
| // Mark this if it's the first base |
| |
| if ( !next_node && !node.firstChild ) |
| { |
| next_node = node; |
| } |
| |
| // Is it an element node and is it considered content? (br, hr, etc) |
| // or is it a text node that is not just whitespace? |
| // or is it not an element node and not a text node? |
| |
| if ( (node.nodeType == 1 && this._elemSolid.test(node.nodeName)) || |
| (node.nodeType == 3 && !this._whiteSpace.test(node.nodeValue)) || |
| (node.nodeType != 1 && node.nodeType != 3) ) |
| { |
| |
| switch( mode ) |
| { |
| |
| case "find_fill": |
| |
| // does not return content. |
| |
| return new Array(true, false ); |
| break; |
| |
| case "find_cursorpoint": |
| |
| // returns content |
| |
| return new Array(true, node ); |
| break; |
| |
| } |
| |
| } |
| |
| // In either case (fill or findcursor) we return the base node. The avoids |
| // problems in terminal cases (beginning or end of document or container tags) |
| |
| if ( last_flag ) |
| { |
| return new Array( true, next_node ); |
| } |
| |
| return new Array( false, next_node ); |
| |
| }; // end of _fenEmptySet() |
| |
| // ------------------------------------------------------------------------------ |
| |
| /** |
| * remove duplicate Id's. |
| * |
| * @param ep_ref enterparagraphs reference to enterparagraphs object |
| */ |
| |
| EnterParagraphs.prototype._fenCullIds = function ( ep_ref, node, pong ) |
| { |
| |
| // Check for an id, blast it if it's in the store, otherwise add it |
| |
| if ( node.id ) |
| { |
| |
| pong[node.id] ? node.id = '' : pong[node.id] = true; |
| } |
| |
| return new Array(false,pong); |
| |
| }; |
| |
| // --------------------------------------------------------------------------------- |
| |
| /** |
| * Grabs a range suitable for paragraph stuffing |
| * |
| * @param rng Range |
| * @param search_direction string "left" or "right" |
| * |
| * @todo check blank node issue in roaming loop. |
| */ |
| |
| EnterParagraphs.prototype.processSide = function( rng, search_direction) |
| { |
| |
| var next = function(element, search_direction) |
| { |
| return ( search_direction == "left" ? element.previousSibling : element.nextSibling ); |
| }; |
| |
| var node = search_direction == "left" ? rng.startContainer : rng.endContainer; |
| var offset = search_direction == "left" ? rng.startOffset : rng.endOffset; |
| var roam, start = node; |
| |
| // Never start with an element, because then the first roaming node might |
| // be on the exclusion list and we wouldn't know until it was too late |
| |
| while ( start.nodeType == 1 && !this._permEmpty.test(start.nodeName) ) |
| { |
| start = ( offset ? start.lastChild : start.firstChild ); |
| } |
| |
| // Climb the tree, left or right, until our course of action presents itself |
| // |
| // if roam is NULL try start. |
| // if roam is NOT NULL, try next node in our search_direction |
| // If that node is NULL, get our parent node. |
| // |
| // If all the above turns out NULL end the loop. |
| // |
| // FIXME: gecko (firefox 1.0.3) - enter "test" into an empty document and press enter. |
| // sometimes this loop finds a blank text node, sometimes it doesn't. |
| |
| while ( roam = roam ? ( next(roam,search_direction) ? next(roam,search_direction) : roam.parentNode ) : start ) |
| { |
| |
| // next() is an inline function defined above that returns the next node depending |
| // on the direction we're searching. |
| |
| if ( next(roam,search_direction) ) |
| { |
| |
| // If the next sibling's on the exclusion list, stop before it |
| |
| if ( this._pExclusions.test(next(roam,search_direction).nodeName) ) |
| { |
| |
| return this.processRng(rng, search_direction, roam, next(roam,search_direction), (search_direction == "left"?'AfterEnd':'BeforeBegin'), true, false); |
| } |
| } |
| else |
| { |
| |
| // If our parent's on the container list, stop inside it |
| |
| if (this._pContainers.test(roam.parentNode.nodeName)) |
| { |
| |
| return this.processRng(rng, search_direction, roam, roam.parentNode, (search_direction == "left"?'AfterBegin':'BeforeEnd'), true, false); |
| } |
| else if (this._pExclusions.test(roam.parentNode.nodeName)) |
| { |
| |
| // chop without wrapping |
| |
| if (this._pBreak.test(roam.parentNode.nodeName)) |
| { |
| |
| return this.processRng(rng, search_direction, roam, roam.parentNode, |
| (search_direction == "left"?'AfterBegin':'BeforeEnd'), false, (search_direction == "left" ?true:false)); |
| } |
| else |
| { |
| |
| // the next(roam,search_direction) in this call is redundant since we know it's false |
| // because of the "if next(roam,search_direction)" above. |
| // |
| // the final false prevents this range from being wrapped in <p>'s most likely |
| // because it's already wrapped. |
| |
| return this.processRng(rng, |
| search_direction, |
| (roam = roam.parentNode), |
| (next(roam,search_direction) ? next(roam,search_direction) : roam.parentNode), |
| (next(roam,search_direction) ? (search_direction == "left"?'AfterEnd':'BeforeBegin') : (search_direction == "left"?'AfterBegin':'BeforeEnd')), |
| false, |
| false); |
| } |
| } |
| } |
| } |
| |
| }; // end of processSide() |
| |
| // ------------------------------------------------------------------------------ |
| |
| /** |
| * processRng - process Range. |
| * |
| * Neighbour and insertion identify where the new node, roam, needs to enter |
| * the document; landmarks in our selection will be deleted before insertion |
| * |
| * @param rn Range original selected range |
| * @param search_direction string Direction to search in. |
| * @param roam node |
| * @param insertion string may be AfterBegin of BeforeEnd |
| * @return array |
| */ |
| |
| EnterParagraphs.prototype.processRng = function(rng, search_direction, roam, neighbour, insertion, pWrap, preBr) |
| { |
| var node = search_direction == "left" ? rng.startContainer : rng.endContainer; |
| var offset = search_direction == "left" ? rng.startOffset : rng.endOffset; |
| |
| // Define the range to cut, and extend the selection range to the same boundary |
| |
| var editor = this.editor; |
| var newRng = editor._doc.createRange(); |
| |
| newRng.selectNode(roam); |
| // extend the range in the given direction. |
| |
| if ( search_direction == "left") |
| { |
| newRng.setEnd(node, offset); |
| rng.setStart(newRng.startContainer, newRng.startOffset); |
| } |
| else if ( search_direction == "right" ) |
| { |
| |
| newRng.setStart(node, offset); |
| rng.setEnd(newRng.endContainer, newRng.endOffset); |
| } |
| // Clone the range and remove duplicate ids it would otherwise produce |
| |
| var cnt = newRng.cloneContents(); |
| |
| // in this case "init" is an object not a boolen. |
| |
| this.forEachNodeUnder( cnt, "cullids", "ltr", this.takenIds, false, false); |
| |
| // Special case, for inserting paragraphs before some blocks when caret is at |
| // their zero offset. |
| // |
| // Used to "open up space" in front of a list, table. Usefull if the list is at |
| // the top of the document. (otherwise you'd have no way of "moving it down"). |
| |
| var pify, pifyOffset, fill; |
| pify = search_direction == "left" ? (newRng.endContainer.nodeType == 3 ? true:false) : (newRng.startContainer.nodeType == 3 ? false:true); |
| pifyOffset = pify ? newRng.startOffset : newRng.endOffset; |
| pify = pify ? newRng.startContainer : newRng.endContainer; |
| |
| if ( this._pifyParent.test(pify.nodeName) && pify.parentNode.childNodes.item(0) == pify ) |
| { |
| while ( !this._pifySibling.test(pify.nodeName) ) |
| { |
| pify = pify.parentNode; |
| } |
| } |
| |
| // NODE TYPE 11 is DOCUMENT_FRAGMENT NODE |
| // I do not profess to understand any of this, simply applying a patch that others say is good - ticket:446 |
| if ( cnt.nodeType == 11 && !cnt.firstChild) |
| { |
| if (pify.nodeName != "BODY" || (pify.nodeName == "BODY" && pifyOffset != 0)) |
| { //WKR: prevent body tag in empty doc |
| cnt.appendChild(editor._doc.createElement(pify.nodeName)); |
| } |
| } |
| |
| // YmL: Added additional last parameter for fill case to work around logic |
| // error in forEachNode() |
| |
| fill = this.forEachNodeUnder(cnt, "find_fill", "ltr", false ); |
| |
| if ( fill && |
| this._pifySibling.test(pify.nodeName) && |
| ( (pifyOffset == 0) || ( pifyOffset == 1 && this._pifyForced.test(pify.nodeName) ) ) ) |
| { |
| |
| roam = editor._doc.createElement( 'p' ); |
| roam.innerHTML = " "; |
| |
| // roam = editor._doc.createElement('p'); |
| // roam.appendChild(editor._doc.createElement('br')); |
| |
| // for these cases, if we are processing the left hand side we want it to halt |
| // processing instead of doing the right hand side. (Avoids adding another <p> </p> |
| // after the list etc. |
| |
| if ((search_direction == "left" ) && pify.previousSibling) |
| { |
| |
| return new Array(pify.previousSibling, 'AfterEnd', roam); |
| } |
| else if (( search_direction == "right") && pify.nextSibling) |
| { |
| |
| return new Array(pify.nextSibling, 'BeforeBegin', roam); |
| } |
| else |
| { |
| |
| return new Array(pify.parentNode, (search_direction == "left"?'AfterBegin':'BeforeEnd'), roam); |
| } |
| |
| } |
| |
| // If our cloned contents are 'content'-less, shove a break in them |
| |
| if ( fill ) |
| { |
| |
| // Ill-concieved? |
| // |
| // 3 is a TEXT node and it should be empty. |
| // |
| |
| if ( fill.nodeType == 3 ) |
| { |
| // fill = fill.parentNode; |
| |
| fill = editor._doc.createDocumentFragment(); |
| } |
| |
| if ( (fill.nodeType == 1 && !this._elemSolid.test()) || fill.nodeType == 11 ) |
| { |
| |
| // FIXME:/CHECKME: When Xinha is switched from WYSIWYG to text mode |
| // Xinha.getHTMLWrapper() will strip out the trailing br. Not sure why. |
| |
| // fill.appendChild(editor._doc.createElement('br')); |
| |
| var pterminator = editor._doc.createElement( 'p' ); |
| pterminator.innerHTML = " "; |
| |
| fill.appendChild( pterminator ); |
| |
| } |
| else |
| { |
| |
| // fill.parentNode.insertBefore(editor._doc.createElement('br'),fill); |
| |
| var pterminator = editor._doc.createElement( 'p' ); |
| pterminator.innerHTML = " "; |
| |
| fill.parentNode.insertBefore(parentNode,fill); |
| |
| } |
| } |
| |
| // YmL: If there was no content replace with fill |
| // (previous code did not use fill and we ended up with the |
| // <p>test</p><p></p> because Gecko was finding two empty text nodes |
| // when traversing on the right hand side of an empty document. |
| |
| if ( fill ) |
| { |
| |
| roam = fill; |
| } |
| else |
| { |
| // And stuff a shiny new object with whatever contents we have |
| |
| roam = (pWrap || (cnt.nodeType == 11 && !cnt.firstChild)) ? editor._doc.createElement('p') : editor._doc.createDocumentFragment(); |
| roam.appendChild(cnt); |
| } |
| |
| if (preBr) |
| { |
| roam.appendChild(editor._doc.createElement('br')); |
| } |
| // Return the nearest relative, relative insertion point and fragment to insert |
| |
| return new Array(neighbour, insertion, roam); |
| |
| }; // end of processRng() |
| |
| // ---------------------------------------------------------------------------------- |
| |
| /** |
| * are we an <li> that should be handled by the browser? |
| * |
| * there is no good way to "get out of" ordered or unordered lists from Javascript. |
| * We have to pass the onKeyPress 13 event to the browser so it can take care of |
| * getting us "out of" the list. |
| * |
| * The Gecko engine does a good job of handling all the normal <li> cases except the "press |
| * enter at the first position" where we want a <p> </p> inserted before the list. The |
| * built-in behavior is to open up a <li> before the current entry (not good). |
| * |
| * @param rng Range range. |
| */ |
| |
| EnterParagraphs.prototype.isNormalListItem = function(rng) |
| { |
| |
| var node, listNode; |
| |
| node = rng.startContainer; |
| |
| if (( typeof node.nodeName != 'undefined') && |
| ( node.nodeName.toLowerCase() == 'li' )) |
| { |
| |
| // are we a list item? |
| |
| listNode = node; |
| } |
| else if (( typeof node.parentNode != 'undefined' ) && |
| ( typeof node.parentNode.nodeName != 'undefined' ) && |
| ( node.parentNode.nodeName.toLowerCase() == 'li' )) |
| { |
| |
| // our parent is a list item. |
| |
| listNode = node.parentNode; |
| |
| } |
| else |
| { |
| // neither we nor our parent are a list item. this is not a normal |
| // li case. |
| var listNode = this.editor._getFirstAncestorForNodeAndWhy(node, ["li"])[0]; |
| if ( typeof listNode == 'undefined' || !listNode ) |
| { |
| return false; |
| } |
| } |
| |
| // at this point we have a listNode. Is it the first list item? |
| |
| if ( ! listNode.previousSibling ) |
| { |
| // are we on the first character of the first li? |
| |
| if ( rng.startOffset == 0 ) |
| { |
| return false; |
| } |
| } |
| return true; |
| |
| }; // end of isNormalListItem() |
| |
| // ---------------------------------------------------------------------------------- |
| /** |
| * Called when a key is pressed in the editor |
| */ |
| |
| EnterParagraphs.prototype.__onKeyPress = function(ev) |
| { |
| |
| // If they've hit enter and shift is not pressed, handle it |
| |
| if (ev.keyCode == 13 && !ev.shiftKey && this.editor._iframe.contentWindow.getSelection) |
| { |
| return this.handleEnter(ev); |
| } |
| |
| }; // end of _onKeyPress() |
| |
| // ----------------------------------------------------------------------------------- |
| |
| /** |
| * Handles the pressing of an unshifted enter for Gecko |
| */ |
| |
| EnterParagraphs.prototype.handleEnter = function(ev) |
| { |
| |
| var cursorNode; |
| |
| // Grab the selection and associated range |
| |
| var sel = this.editor.getSelection(); |
| var rng = this.editor.createRange(sel); |
| |
| // if we are at the end of a list and the node is empty let the browser handle |
| // it to get us out of the list. |
| |
| if ( this.isNormalListItem(rng) ) |
| { |
| |
| return true; |
| } |
| |
| // as far as I can tell this isn't actually used. |
| |
| this.takenIds = new Object(); |
| |
| // Grab ranges for document re-stuffing, if appropriate |
| // |
| // pStart and pEnd are arrays consisting of |
| // [0] neighbor node |
| // [1] insertion type |
| // [2] roam |
| |
| var pStart = this.processSide(rng, "left"); |
| |
| var pEnd = this.processSide(rng, "right"); |
| |
| // used to position the cursor after insertion. |
| |
| cursorNode = pEnd[2]; |
| |
| // Get rid of everything local to the selection |
| |
| sel.removeAllRanges(); |
| rng.deleteContents(); |
| |
| // Grab a node we'll have after insertion, since fragments will be lost |
| // |
| // we'll use this to position the cursor. |
| |
| var holdEnd = this.forEachNodeUnder( cursorNode, "find_cursorpoint", "ltr", false, true); |
| |
| if ( ! holdEnd ) |
| { |
| alert( "INTERNAL ERROR - could not find place to put cursor after ENTER" ); |
| } |
| |
| // Insert our carefully chosen document fragments |
| |
| if ( pStart ) |
| { |
| |
| this.insertAdjacentElement(pStart[0], pStart[1], pStart[2]); |
| } |
| |
| if ( pEnd && pEnd.nodeType != 1) |
| { |
| |
| this.insertAdjacentElement(pEnd[0], pEnd[1], pEnd[2]); |
| } |
| |
| // Move the caret in front of the first good text element |
| |
| if ((holdEnd) && (this._permEmpty.test(holdEnd.nodeName) )) |
| { |
| |
| var prodigal = 0; |
| while ( holdEnd.parentNode.childNodes.item(prodigal) != holdEnd ) |
| { |
| prodigal++; |
| } |
| |
| sel.collapse( holdEnd.parentNode, prodigal); |
| } |
| else |
| { |
| |
| // holdEnd might be false. |
| |
| try |
| { |
| sel.collapse(holdEnd, 0); |
| |
| // interestingly, scrollToElement() scroll so the top if holdEnd is a text node. |
| |
| if ( holdEnd.nodeType == 3 ) |
| { |
| holdEnd = holdEnd.parentNode; |
| } |
| |
| this.editor.scrollElementIntoViewport(holdEnd); |
| //this.editor.scrollToElement(holdEnd); |
| } |
| catch (e) |
| { |
| // we could try to place the cursor at the end of the document. |
| } |
| } |
| |
| this.editor.updateToolbar(); |
| |
| Xinha._stopEvent(ev); |
| |
| return true; |
| |
| }; // end of handleEnter() |
| |
| // END |