Index: openacs-4/packages/acs-templating/www/resources/xinha-nightly/modules/Gecko/paraHandlerBest.js =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-templating/www/resources/xinha-nightly/modules/Gecko/paraHandlerBest.js,v diff -u -r1.6 -r1.7 --- openacs-4/packages/acs-templating/www/resources/xinha-nightly/modules/Gecko/paraHandlerBest.js 27 Mar 2009 08:20:43 -0000 1.6 +++ openacs-4/packages/acs-templating/www/resources/xinha-nightly/modules/Gecko/paraHandlerBest.js 23 May 2010 11:58:33 -0000 1.7 @@ -1,3 +1,907 @@ -/* This compressed file is part of Xinha. For uncompressed sources, forum, and bug reports, go to xinha.org */ -/* This file is part of version 0.96beta2 released Fri, 20 Mar 2009 11:01:14 +0100 */ -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"};EnterParagraphs.prototype._whiteSpace=/^\s*$/;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;EnterParagraphs.prototype._pContainers=/^(body|del|div|fieldset|form|ins|map|noscript|object|td|th)$/i;EnterParagraphs.prototype._pBreak=/^(address|pre|blockquote)$/i;EnterParagraphs.prototype._permEmpty=/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i;EnterParagraphs.prototype._elemSolid=/^(applet|br|button|hr|img|input|table)$/i;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;EnterParagraphs.prototype._pifyParent=/^(dd|dt|li|td|th|tr)$/i;function EnterParagraphs(a){this.editor=a;if(Xinha.is_gecko){this.onKeyPress=this.__onKeyPress}}EnterParagraphs.prototype.name="EnterParagraphs";EnterParagraphs.prototype.insertAdjacentElement=function(b,c,a){if(c=="BeforeBegin"){b.parentNode.insertBefore(a,b)}else{if(c=="AfterEnd"){b.nextSibling?b.parentNode.insertBefore(a,b.nextSibling):b.parentNode.appendChild(a)}else{if(c=="AfterBegin"&&b.firstChild){b.insertBefore(a,b.firstChild)}else{if(c=="BeforeEnd"||c=="AfterBegin"){b.appendChild(a)}}}}};EnterParagraphs.prototype.forEachNodeUnder=function(b,e,c,d){var f,a;if(b.nodeType==11&&b.firstChild){f=b.firstChild;a=b.lastChild}else{f=a=b}while(a.lastChild){a=a.lastChild}return this.forEachNode(f,a,e,c,d)};EnterParagraphs.prototype.forEachNode=function(e,j,f,h,l){var i=function(m,n){return(n=="ltr"?m.nextSibling:m.previousSibling)};var g=function(m,n){return(n=="ltr"?m.firstChild:m.lastChild)};var k,b,d;var a=l;var c=false;while(k!=h=="ltr"?j:e){if(!k){k=h=="ltr"?e:j}else{if(g(k,h)){k=g(k,h)}else{if(i(k,h)){k=i(k,h)}else{b=k;while(!i(b,h)&&b!=(h=="ltr"?j:e)){b=b.parentNode}k=(i(b,h)?i(b,h):b)}}}c=(k==(h=="ltr"?j:e));switch(f){case"cullids":d=this._fenCullIds(k,a);break;case"find_fill":d=this._fenEmptySet(k,a,f,c);break;case"find_cursorpoint":d=this._fenEmptySet(k,a,f,c);break}if(d[0]){return d[1]}if(c){break}if(d[1]){a=d[1]}}return false};EnterParagraphs.prototype._fenEmptySet=function(c,b,d,a){if(!b&&!c.firstChild){b=c}if((c.nodeType==1&&this._elemSolid.test(c.nodeName))||(c.nodeType==3&&!this._whiteSpace.test(c.nodeValue))||(c.nodeType!=1&&c.nodeType!=3)){switch(d){case"find_fill":return new Array(true,false);break;case"find_cursorpoint":return new Array(true,c);break}}if(a){return new Array(true,b)}return new Array(false,b)};EnterParagraphs.prototype._fenCullIds=function(c,a,b){if(a.id){b[a.id]?a.id="":b[a.id]=true}return new Array(false,b)};EnterParagraphs.prototype.processSide=function(a,c){var d=function(i,h){return(h=="left"?i.previousSibling:i.nextSibling)};var e=c=="left"?a.startContainer:a.endContainer;var f=c=="left"?a.startOffset:a.endOffset;var b,g=e;while(g.nodeType==1&&!this._permEmpty.test(g.nodeName)){g=(f?g.lastChild:g.firstChild)}while(b=b?(d(b,c)?d(b,c):b.parentNode):g){if(d(b,c)){if(this._pExclusions.test(d(b,c).nodeName)){return this.processRng(a,c,b,d(b,c),(c=="left"?"AfterEnd":"BeforeBegin"),true,false)}}else{if(this._pContainers.test(b.parentNode.nodeName)){return this.processRng(a,c,b,b.parentNode,(c=="left"?"AfterBegin":"BeforeEnd"),true,false)}else{if(this._pExclusions.test(b.parentNode.nodeName)){if(this._pBreak.test(b.parentNode.nodeName)){return this.processRng(a,c,b,b.parentNode,(c=="left"?"AfterBegin":"BeforeEnd"),false,(c=="left"?true:false))}else{return this.processRng(a,c,(b=b.parentNode),(d(b,c)?d(b,c):b.parentNode),(d(b,c)?(c=="left"?"AfterEnd":"BeforeBegin"):(c=="left"?"AfterBegin":"BeforeEnd")),false,false)}}}}}};EnterParagraphs.prototype.processRng=function(a,g,m,o,c,k,i){var d=g=="left"?a.startContainer:a.endContainer;var f=g=="left"?a.startOffset:a.endOffset;var h=this.editor;var p=h._doc.createRange();p.selectNode(m);if(g=="left"){p.setEnd(d,f);a.setStart(p.startContainer,p.startOffset)}else{if(g=="right"){p.setStart(d,f);a.setEnd(p.endContainer,p.endOffset)}}var b=p.cloneContents();this.forEachNodeUnder(b,"cullids","ltr",this.takenIds,false,false);var l,e,n;l=g=="left"?(p.endContainer.nodeType==3?true:false):(p.startContainer.nodeType==3?false:true);e=l?p.startOffset:p.endOffset;l=l?p.startContainer:p.endContainer;if(this._pifyParent.test(l.nodeName)&&l.parentNode.childNodes.item(0)==l){while(!this._pifySibling.test(l.nodeName)){l=l.parentNode}}if(b.nodeType==11&&!b.firstChild){if(l.nodeName!="BODY"||(l.nodeName=="BODY"&&e!=0)){b.appendChild(h._doc.createElement(l.nodeName))}}n=this.forEachNodeUnder(b,"find_fill","ltr",false);if(n&&this._pifySibling.test(l.nodeName)&&((e==0)||(e==1&&this._pifyForced.test(l.nodeName)))){m=h._doc.createElement("p");m.innerHTML=" ";if((g=="left")&&l.previousSibling){return new Array(l.previousSibling,"AfterEnd",m)}else{if((g=="right")&&l.nextSibling){return new Array(l.nextSibling,"BeforeBegin",m)}else{return new Array(l.parentNode,(g=="left"?"AfterBegin":"BeforeEnd"),m)}}}if(n){if(n.nodeType==3){n=h._doc.createDocumentFragment()}if((n.nodeType==1&&!this._elemSolid.test())||n.nodeType==11){var j=h._doc.createElement("p");j.innerHTML=" ";n.appendChild(j)}else{var j=h._doc.createElement("p");j.innerHTML=" ";n.parentNode.insertBefore(parentNode,n)}}if(n){m=n}else{m=(k||(b.nodeType==11&&!b.firstChild))?h._doc.createElement("p"):h._doc.createDocumentFragment();m.appendChild(b)}if(i){m.appendChild(h._doc.createElement("br"))}return new Array(o,c,m)};EnterParagraphs.prototype.isNormalListItem=function(a){var c,b;c=a.startContainer;if((typeof c.nodeName!="undefined")&&(c.nodeName.toLowerCase()=="li")){b=c}else{if((typeof c.parentNode!="undefined")&&(typeof c.parentNode.nodeName!="undefined")&&(c.parentNode.nodeName.toLowerCase()=="li")){b=c.parentNode}else{return false}}if(!b.previousSibling){if(a.startOffset==0){return false}}return true};EnterParagraphs.prototype.__onKeyPress=function(a){if(a.keyCode==13&&!a.shiftKey&&this.editor._iframe.contentWindow.getSelection){return this.handleEnter(a)}};EnterParagraphs.prototype.handleEnter=function(j){var b;var c=this.editor.getSelection();var a=this.editor.createRange(c);if(this.isNormalListItem(a)){return true}this.takenIds=new Object();var f=this.processSide(a,"left");var d=this.processSide(a,"right");b=d[2];c.removeAllRanges();a.deleteContents();var g=this.forEachNodeUnder(b,"find_cursorpoint","ltr",false,true);if(!g){alert("INTERNAL ERROR - could not find place to put cursor after ENTER")}if(f){this.insertAdjacentElement(f[0],f[1],f[2])}if(d&&d.nodeType!=1){this.insertAdjacentElement(d[0],d[1],d[2])}if((g)&&(this._permEmpty.test(g.nodeName))){var i=0;while(g.parentNode.childNodes.item(i)!=g){i++}c.collapse(g.parentNode,i)}else{try{c.collapse(g,0);if(g.nodeType==3){g=g.parentNode}this.editor.scrollToElement(g)}catch(h){}}this.editor.updateToolbar();Xinha._stopEvent(j);return true}; \ No newline at end of file +// 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

'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

 

+ // 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 + //

test

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
  • 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
  • cases except the "press +* enter at the first position" where we want a

     

    inserted before the list. The +* built-in behavior is to open up a
  • 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. + + 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.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 \ No newline at end of file