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.1 -r1.2 --- openacs-4/packages/acs-templating/www/resources/xinha-nightly/modules/Gecko/paraHandlerBest.js 2 Feb 2007 21:04:47 -0000 1.1 +++ openacs-4/packages/acs-templating/www/resources/xinha-nightly/modules/Gecko/paraHandlerBest.js 25 Feb 2007 19:06:07 -0000 1.2 @@ -1,907 +1,293 @@ -// 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" +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(_1){ +this.editor=_1; +if(Xinha.is_gecko){ +this.onKeyPress=this.__onKeyPress; +} +} +EnterParagraphs.prototype.name="EnterParagraphs"; +EnterParagraphs.prototype.insertAdjacentElement=function(_2,_3,el){ +if(_3=="BeforeBegin"){ +_2.parentNode.insertBefore(el,_2); +}else{ +if(_3=="AfterEnd"){ +_2.nextSibling?_2.parentNode.insertBefore(el,_2.nextSibling):_2.parentNode.appendChild(el); +}else{ +if(_3=="AfterBegin"&&_2.firstChild){ +_2.insertBefore(el,_2.firstChild); +}else{ +if(_3=="BeforeEnd"||_3=="AfterBegin"){ +_2.appendChild(el); +} +} +} +} }; - -// ------------------------------------------------------------------ - -// "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); - +EnterParagraphs.prototype.forEachNodeUnder=function(_5,_6,_7,_8){ +var _9,end; +if(_5.nodeType==11&&_5.firstChild){ +_9=_5.firstChild; +end=_5.lastChild; +}else{ +_9=end=_5; +} +while(end.lastChild){ +end=end.lastChild; +} +return this.forEachNode(_9,end,_6,_7,_8); }; - -// --------------------------------------------------------------------------------- - -/** -* 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 
inserted before the list. The -* built-in behavior is to open up a