EnterParagraphs/enter-paragraphs.js
Summary
By Adam Wright, for The University of Western Australia
Distributed under the same terms as HTMLArea 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
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(editor)
{
this.editor = editor;
this.ddt = new DDT( editor._textArea + ":EnterParagraphs Plugin" );
this.ddt._ddt( "enter-paragraphs.js","103", "EnterParagraphs(): constructor" );
if (HTMLArea.is_gecko)
{
this.ddt._ddt( "enter-paragraphs.js","112", "EnterParagraphs(): we are gecko. Setting event handler." );
this.onKeyPress = this.__onKeyPress;
}
}; // end of constructor.
EnterParagraphs.prototype.name = "EnterParagraphs";
EnterParagraphs.prototype.insertAdjacentElement = function(ref,pos,el)
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "122", "insertAdjacentElement(): top with pos '" + pos + "' ref:", ref );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "122", "insertAdjacentElement(): top with el:", 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);
}
this.ddt._ddtDumpNode( "enter-paragraphs.js", "122", "insertAdjacentElement(): bottom with ref:", ref );
}; // end of insertAdjacentElement()
EnterParagraphs.prototype.forEachNodeUnder = function ( root, mode, direction, init )
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "144", "forEachNodeUnder(): top mode is '" + mode + "' direction is '" + direction + "' starting with root node:", root );
var start, end;
if ( root.nodeType == 11 && root.firstChild )
{
start = root.firstChild;
end = root.lastChild;
}
else
{
start = end = root;
}
this.ddt._ddtDumpNode( "enter-paragraphs.js", "144", "forEachNodeUnder(): start node is:", start );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "144", "forEachNodeUnder(): initial end node is:", end );
while ( end.lastChild )
end = end.lastChild;
this.ddt._ddtDumpNode( "enter-paragraphs.js", "144", "forEachNodeUnder(): end node after descent is:", end );
return this.forEachNode( start, end, mode, direction, init);
}; // end of forEachNodeUnder()
EnterParagraphs.prototype.forEachNode = function (left_node, right_node, mode, direction, init)
{
this.ddt._ddt( "enter-paragraphs.js","221", "forEachNode(): top - mode is:" + mode + "' direction '" + direction + "'" );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "175", "forEachNode(): top - left node is:", left_node );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "175", "forEachNode(): top - right node is:", right_node );
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;
var next_node = init;
var done_flag = false;
while ( walk != direction == "ltr" ? right_node : left_node )
{
if ( !walk )
{
walk = direction == "ltr" ? left_node : right_node;
this.ddt._ddtDumpNode( "enter-paragraphs.js", "175", "forEachNode(): !walk - current node is:", walk );
}
else
{
if ( getChild(walk,direction) )
{
walk = getChild(walk,direction);
this.ddt._ddtDumpNode( "enter-paragraphs.js", "175", "forEachNode():descending to child node:", walk );
}
else
{
if ( getSibling(walk,direction) )
{
walk = getSibling(walk,direction);
this.ddt._ddtDumpNode( "enter-paragraphs.js", "175", "forEachNode(): moving to sibling node:", walk );
}
else
{
lookup = walk;
while ( !getSibling(lookup,direction) && lookup != (direction == "ltr" ? right_node : left_node) )
{
lookup = lookup.parentNode;
}
walk = ( getSibling(lookup,direction) ? getSibling(lookup,direction) : lookup ) ;
this.ddt._ddtDumpNode( "enter-paragraphs.js", "175", "forEachNode(): climbed back up (or found right node):", walk );
}
}
} // end of else walk.
done_flag = (walk==( direction == "ltr" ? right_node : left_node));
this.ddt._ddt( "enter-paragraphs.js","334", "forEachNode(): calling function" );
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 ( fnReturnVal[0] )
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "175", "forEachNode(): returning node:", fnReturnVal[1] );
return fnReturnVal[1];
}
if ( done_flag )
{
break;
}
if ( fnReturnVal[1] )
{
next_node = fnReturnVal[1];
}
} // end of while loop
this.ddt._ddt( "enter-paragraphs.js","381", "forEachNode(): returning false." );
return false;
}; // end of forEachNode()
EnterParagraphs.prototype._fenEmptySet = function( node, next_node, mode, last_flag)
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "263", "_fenEmptySet() : top with mode '" + mode + "' and last_flag '" + last_flag + "' and node:", node );
if ( !next_node && !node.firstChild )
{
next_node = node;
}
if ( (node.nodeType == 1 && this._elemSolid.test(node.nodeName)) ||
(node.nodeType == 3 && !this._whiteSpace.test(node.nodeValue)) ||
(node.nodeType != 1 && node.nodeType != 3) )
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "263", "_fenEmptySet() : found content in node:", node );
switch( mode )
{
case "find_fill":
return new Array(true, false );
breal;
case "find_cursorpoint":
return new Array(true, node );
break;
}
}
if ( last_flag )
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "263", "_fenEmptySet() : return 'base' node:", next_node );
return new Array( true, next_node );
}
this.ddt._ddtDumpNode( "enter-paragraphs.js", "263", "_fenEmptySet() : bottom returning false and :", next_node );
return new Array( false, next_node );
}; // end of _fenEmptySet()
EnterParagraphs.prototype._fenCullIds = function ( ep_ref, node, pong )
{
this.ddt._ddt( "enter-paragraphs.js","469", "_fenCullIds(): top" );
if ( node.id )
{
this.ddt._ddt( "enter-paragraphs.js","476", "_fenCullIds(): node '" + node.nodeName + "' has an id '" + node.id + "'" );
pong[node.id] ? node.id = '' : pong[node.id] = true;
}
return new Array(false,pong);
};
EnterParagraphs.prototype.processSide = function( rng, search_direction)
{
this.ddt._ddt( "enter-paragraphs.js","499", "processSide(): top search_direction == '" + 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;
this.ddt._ddtDumpNode( "enter-paragraphs.js", "337", "processSide(): starting with node:", node );
while ( start.nodeType == 1 && !this._permEmpty.test(start.nodeName) )
{
start = ( offset ? start.lastChild : start.firstChild );
}
while ( roam = roam ? ( next(roam,search_direction) ? next(roam,search_direction) : roam.parentNode ) : start )
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "357", "processSide(): roaming loop, search_direction is '" + search_direction + "' current node is: ", roam );
if ( next(roam,search_direction) )
{
this.ddt._ddt( "enter-paragraphs.js","542", "processSide(): Checking next node '" + next(roam,search_direction).NodeName + "' for _pExclusions list." );
if ( this._pExclusions.test(next(roam,search_direction).nodeName) )
{
this.ddt._ddt( "enter-paragraphs.js","549", "processSide(): Node '" + next(roam,search_direction).NodeName + "' is on the _pExclusions list. Stopping before it." );
return this.processRng(rng, search_direction, roam, next(roam,search_direction), (search_direction == "left"?'AfterEnd':'BeforeBegin'), true, false);
}
}
else
{
this.ddt._ddt( "enter-paragraphs.js","557", "processSide(): No next node, examing parent node '" + roam.parentNode.nodeName + "' for containers or exclusions." );
if (this._pContainers.test(roam.parentNode.nodeName))
{
this.ddt._ddt( "enter-paragraphs.js","564", "processSide(): Parent Node '" + roam.parentNode.nodeName + "' is on the _pContainer list. Stopping inside it." );
return this.processRng(rng, search_direction, roam, roam.parentNode, (search_direction == "left"?'AfterBegin':'BeforeEnd'), true, false);
}
else if (this._pExclusions.test(roam.parentNode.nodeName))
{
this.ddt._ddt( "enter-paragraphs.js","571", "processSide(): Parent Node '" + roam.parentNode.nodeName + "' is on the _pExclusion list." );
if (this._pBreak.test(roam.parentNode.nodeName))
{
this.ddt._ddt( "enter-paragraphs.js","578", "processSide(): Parent Node '" + roam.parentNode.nodeName + "' is on the _pBreak list." );
return this.processRng(rng, search_direction, roam, roam.parentNode,
(search_direction == "left"?'AfterBegin':'BeforeEnd'), false, (search_direction == "left" ?true:false));
}
else
{
this.ddt._ddt( "enter-paragraphs.js","586", "processSide(): Parent Node '" + roam.parentNode.nodeName + "' is not on the _pBreak list." );
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);
}
}
}
}
this.ddt._ddt( "enter-paragraphs.js","606", "processSide(): bottom" );
}; // end of processSide()
EnterParagraphs.prototype.processRng = function(rng, search_direction, roam, neighbour, insertion, pWrap, preBr)
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "398", "processRng(): top - roam arg is:", roam );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "398", "processRng(): top - neighbor arg is:", neighbour );
this.ddt._ddt( "enter-paragraphs.js","631", "processRng(): top - insertion arg is: '" + insertion + "'" );
var node = search_direction == "left" ? rng.startContainer : rng.endContainer;
var offset = search_direction == "left" ? rng.startOffset : rng.endOffset;
this.ddt._ddtDumpNode( "enter-paragraphs.js", "447", "processRng(): range start (or end) is at offset '" + offset + "' is node :", node );
var editor = this.editor;
var newRng = editor._doc.createRange();
newRng.selectNode(roam);
this.ddt._ddtDumpNode( "enter-paragraphs.js", "522", "processRng(): selecting newRng is:", newRng );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "522", "processRng(): selecting original rng is:", rng );
if ( search_direction == "left")
{
newRng.setEnd(node, offset);
rng.setStart(newRng.startContainer, newRng.startOffset);
this.ddt._ddtDumpNode( "enter-paragraphs.js", "522", "processRng(): extending direction left - newRng is:", newRng );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "522", "processRng(): extending direction left - rng is:", rng );
}
else if ( search_direction == "right" )
{
newRng.setStart(node, offset);
rng.setEnd(newRng.endContainer, newRng.endOffset);
this.ddt._ddt( "enter-paragraphs.js","665", "processRng(): right - new range start is '" + offset + "' end offset is '" + newRng.endOffset + "'" );
}
this.ddt._ddtDumpNode( "enter-paragraphs.js", "522", "processRng(): rng is:", rng );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "522", "processRng(): newRng is:", newRng );
var cnt = newRng.cloneContents();
this.ddt._ddtDumpNode( "enter-paragraphs.js", "509", "processRng(): culling duplicate ids from:", cnt );
this.forEachNodeUnder( cnt, "cullids", "ltr", this.takenIds, false, false);
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;
this.ddt._ddtDumpNode( "enter-paragraphs.js", "521", "processRng(): pify is '" + pify.nodeName + "' pifyOffset is '" + pifyOffset + "':", pify );
if ( this._pifyParent.test(pify.nodeName) && pify.parentNode.childNodes.item(0) == pify )
{
while ( !this._pifySibling.test(pify.nodeName) )
{
pify = pify.parentNode;
}
}
if ( cnt.nodeType == 11 && !cnt.firstChild )
{
cnt.appendChild(editor._doc.createElement(pify.nodeName));
}
this.ddt._ddt( "enter-paragraphs.js","712", "processRng(): find_fill in cnt." );
fill = this.forEachNodeUnder(cnt, "find_fill", "ltr", false );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "612", "processRng(): fill node:" , fill );
if ( fill &&
this._pifySibling.test(pify.nodeName) &&
( (pifyOffset == 0) || ( pifyOffset == 1 && this._pifyForced.test(pify.nodeName) ) ) )
{
this.ddt._ddt( "enter-paragraphs.js","723", "processRng(): pify handling. Creating p tag followed by nbsp tag" );
roam = editor._doc.createElement( 'p' );
roam.innerHTML = " ";
if ((search_direction == "left" ) && pify.previousSibling)
{
this.ddt._ddt( "enter-paragraphs.js","738", "processRng(): returning created roam AfterEnd" );
return new Array(pify.previousSibling, 'AfterEnd', roam);
}
else if (( search_direction == "right") && pify.nextSibling)
{
this.ddt._ddt( "enter-paragraphs.js","745", "processRng(): returning created roam BeforeBegin" );
return new Array(pify.nextSibling, 'BeforeBegin', roam);
}
else
{
this.ddt._ddt( "enter-paragraphs.js","752", "processRng(): returning created roam for direction '" + search_direction + "'" );
return new Array(pify.parentNode, (search_direction == "left"?'AfterBegin':'BeforeEnd'), roam);
}
}
if ( fill )
{
if ( fill.nodeType == 3 )
{
fill = editor._doc.createDocumentFragment();
this.ddt._ddtDumpNode( "enter-paragraphs.js", "575", "processRng(): fill.nodeType is 3. Moving up to parent:", fill );
}
if ( (fill.nodeType == 1 && !this._elemSolid.test()) || fill.nodeType == 11 )
{
var pterminator = editor._doc.createElement( 'p' );
pterminator.innerHTML = " ";
fill.appendChild( pterminator );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "583", "processRng(): fill type is 1 and !elemsolid or it's type 11. Appending an nbsp tag:", fill );
}
else
{
this.ddt._ddt( "enter-paragraphs.js","797", "processRng(): inserting a br tag before." );
var pterminator = editor._doc.createElement( 'p' );
pterminator.innerHTML = " ";
fill.parentNode.insertBefore(parentNode,fill);
}
}
if ( fill )
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "606", "processRng(): no content. Using fill.", fill );
roam = fill;
}
else
{
this.ddt._ddt( "enter-paragraphs.js","825", "processRng(): creating p tag or document fragment - pWrap is '" + pWrap + "' " );
roam = (pWrap || (cnt.nodeType == 11 && !cnt.firstChild)) ? editor._doc.createElement('p') : editor._doc.createDocumentFragment();
roam.appendChild(cnt);
}
if (preBr)
{
this.ddt._ddt( "enter-paragraphs.js","833", "processRng(): appending a br based on preBr flag" );
roam.appendChild(editor._doc.createElement('br'));
}
this.ddt._ddtDumpNode( "enter-paragraphs.js", "606", "processRng(): bottom with roam:", roam );
this.ddt._ddtDumpNode( "enter-paragraphs.js", "606", "processRng(): bottom with neighbour:", neighbour );
return new Array(neighbour, insertion, roam);
}; // end of processRng()
EnterParagraphs.prototype.isNormalListItem = function(rng)
{
this.ddt._ddtDumpNode( "enter-paragraphs.js", "863", "isNormaListItem(): checking rng for list end:", rng );
var node, listNode;
node = rng.startContainer;
if (( typeof node.nodeName != 'undefined') &&
( node.nodeName.toLowerCase() == 'li' ))
{
this.ddt._ddt( "enter-paragraphs.js","876", "isNormaListItem(): node is a list item" );
listNode = node;
}
else if (( typeof node.parentNode != 'undefined' ) &&
( typeof node.parentNode.nodeName != 'undefined' ) &&
( node.parentNode.nodeName.toLowerCase() == 'li' ))
{
this.ddt._ddt( "enter-paragraphs.js","887", "isNormaListItem(): parent is a list item" );
listNode = node.parentNode;
}
else
{
this.ddt._ddt( "enter-paragraphs.js","896", "isNormaListItem(): not list item" );
return false;
}
if ( ! listNode.previousSibling )
{
this.ddt._ddt( "enter-paragraphs.js","908", "isNormaListItem(): we are the first li." );
if ( rng.startOffset == 0 )
{
this.ddt._ddt( "enter-paragraphs.js","914", "isNormaListItem(): we are on the first character." );
return false;
}
}
this.ddt._ddt( "enter-paragraphs.js","920", "isNormaListItem(): this is a normal list item case." );
return true;
}; // end of isNormalListItem()
EnterParagraphs.prototype.__onKeyPress = function(ev)
{
this.ddt._ddt( "enter-paragraphs.js","933", "__onKeyPress(): top with keyCode '" + ev.keyCode + "'" );
if (ev.keyCode == 13 && ev.ctrlKey && this.editor._iframe.contentWindow.getSelection)
{
this.ddt._ddt( "enter-paragraphs.js","939", "__onKeyPress(): calling handleEnter" );
return this.handleEnter(ev);
}
this.ddt._ddt( "enter-paragraphs.js","944", "__onKeyPress(): bottom" );
}; // end of _onKeyPress()
EnterParagraphs.prototype.handleEnter = function(ev)
{
this.ddt._ddt( "enter-paragraphs.js","957", "handleEnter(): top" );
var cursorNode;
var sel = this.editor._getSelection();
var rng = this.editor._createRange(sel);
this.ddt._ddtDumpNode( "enter-paragraphs.js", "757", "handleEnter(): initial range is: ", rng );
if ( this.isNormalListItem(rng) )
{
this.ddt._ddt( "enter-paragraphs.js","973", "handleEnter(): we are at the end of a list with a blank item. Letting the browser handle it." );
return true;
}
this.takenIds = new Object();
this.ddt._ddt( "enter-paragraphs.js","988", "handleEnter(): calling processSide on left side." );
var pStart = this.processSide(rng, "left");
this.ddt._ddtDumpNode( "enter-paragraphs.js", "757", "handleEnter(): after processing left side range is: ", rng );
this.ddt._ddt( "enter-paragraphs.js","994", "handleEnter(): calling processSide on right side." );
var pEnd = this.processSide(rng, "right");
cursorNode = pEnd[2];
sel.removeAllRanges();
rng.deleteContents();
this.ddt._ddt( "enter-paragraphs.js","1011", "handleEnter(): looking for cursor position" );
var holdEnd = this.forEachNodeUnder( cursorNode, "find_cursorpoint", "ltr", false, true);
if ( ! holdEnd )
{
alert( "INTERNAL ERROR - could not find place to put cursor after ENTER" );
}
if ( pStart )
{
this.ddt._ddt( "enter-paragraphs.js","1025", "handleEnter(): inserting pEnd" );
this.insertAdjacentElement(pStart[0], pStart[1], pStart[2]);
}
if ( pEnd && pEnd.nodeType != 1)
{
this.ddt._ddt( "enter-paragraphs.js","1033", "handleEnter(): inserting pEnd" );
this.insertAdjacentElement(pEnd[0], pEnd[1], pEnd[2]);
}
if ((holdEnd) && (this._permEmpty.test(holdEnd.nodeName) ))
{
this.ddt._ddt( "enter-paragraphs.js","1043", "handleEnter(): looping to find cursor element." );
var prodigal = 0;
while ( holdEnd.parentNode.childNodes.item(prodigal) != holdEnd )
{
prodigal++;
}
sel.collapse( holdEnd.parentNode, prodigal);
}
else
{
try
{
sel.collapse(holdEnd, 0);
this.ddt._ddtDumpNode( "enter-paragraphs.js", "1057", "handleEnter(): scrolling to element:", holdEnd );
if ( holdEnd.nodeType == 3 )
{
holdEnd = holdEnd.parentNode;
}
this.editor.scrollToElement(holdEnd);
}
catch (e)
{
}
}
this.editor.updateToolbar();
HTMLArea._stopEvent(ev);
return true;
}; // end of handleEnter()
Documentation generated by
JSDoc on Mon Jun 13 20:27:40 2005