
stylePath = "/skins/ace/";
_skin_path = "/skins/";
_script_path = "/";

baseHref = "http://penguinsunbound.org";

_editor_url = "/editor/";
_editor_lang = "en"; 
_editor_skin = ""; 
_editor_styles_path = "/skins/ace/__editor.css";
_editor_dream_enabled = false;

/*
 * MindTouch Deki - a commercial grade open source wiki
 *  derived from MediaWiki (www.mediawiki.org)
 * Copyright (C) 2006 MindTouch, Inc.
 * http://www.mindtouch.com/  oss@mindtouch.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

/***
 * hooks an onclick to all links in the table of contents dropdown which closes the window
 */
function hookTOCLinks() {
	var elt = document.getElementById('menuPageContent');
	if (!elt) return;
	var a_ = elt.getElementsByTagName('a');	
	for (var i = 0; i < a_.length; i++)
		a_[i].onclick = function () { menuBodyClick() };
};

function breadcrumbLoad(z) {
	document.getElementById('breadcrumb').innerHTML = z;
};

function doClosePopupMessage(divid, clearInnerHtml) {
    parent.hidePopWin(false);
	return false;
};

function doHidePopupMessage() {
   return doClosePopupMessage();
};

/***
 * this function will take all external link icons from topic
 * and append a child span that contains the link icon
 * this is to correct IE's behaviour of handling links that break cross mult lines
 */
function fixLinkIcons() {
	if (clientBrowser.isIe) {
		var elt = document.getElementById('topic');
		var a = elt.getElementsByTagName('a');
		var j = a.length;
		for (var i = 0; i < j; i++) {
			var rel = a[i].getAttribute('rel');
			if (rel && typeof(rel) == 'string' && rel != '') {
				var icona = a[i].cloneNode(true);
				var texta = a[i].cloneNode(true);
				icona.innerHTML = '&nbsp;';
				icona.style.textDecoration = 'none';
				icona.removeAttribute('rel');
				texta.removeAttribute('rel');
				texta.removeAttribute('className');
				if (a[i].className == 'link-user') {
					icona.style.padding = '0 3px 0 6px';
				} else if (rel != 'internal') {
					icona.style.padding = a[i].className == 'link-mailto' ? '0 12px 0 0' : '0 9px 0 0';
					texta.style.paddingRight = '0';
					texta.style.background = '';
				} else {
					icona.style.padding = '0 0 0 14px';
				}
				var span = document.createElement('span');
				if (rel == 'internal') {
					span.appendChild(icona);
					span.appendChild(texta);	
				}
				else{
					span.appendChild(texta);
					span.appendChild(icona);	
				}
				var parent = a[i].parentNode;
				parent.replaceChild(span, a[i]);
			}
		}
	}
};

function createFullHtml(title, body, head, _lang) {
    var html = "<html>\n";
    html += "<head>\n";
    html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + (HTMLArea.is_gecko ? document.characterSet : document.charset) + "\">\n";
    html += "<title>" + title + "</title>\n";
    html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"/editor/popups/popup.css\" />\n";
    html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"/skins/ace/_icons.css\" />\n";
    html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"/skins/ace/__icons.css\" />\n";
    html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"/editor/popups/selectTopic.css\" />\n";
    html += "<script type=\"text/javascript\" src=\"/editor/popups/popup.js\"></script>\n";
    html += "<script type=\"text/javascript\">function Init() {};\n";
    for (var i in _lang)
    	html += "var "+i+" = '"+_lang[i]+"';\n";
    html += "</script>\n";
    if (head) html += head;
    html += "</head>\n";
    html += "<body onload='Init()'><div class=\"wrap\">\n";
    html += body;
    html += "</div></body>\n";
    html += "</html>";
    return html;
};

function doPopupAttach() {
	if (document.getElementById('editarea'))
		{
		alert("Sorry, you can't attach files while editing a page. Please close the editor first.");
		return false;
		}
    showPopWin('/editor/popups/loading.html', 650, 134);
    var lang = new Object();
    lang['_lang_removeattach'] = _lang_removeattach;
    lang['_lang_fileattach'] = _lang_fileattach;
    setPopWinHTML(createFullHtml('Attach File', 
    	'<div id="attachFileForm">'+document.getElementById('attachFileForm').innerHTML+'</div>',
        "<script src=\"/skins/ace/attach.js\"></script>\n" +
        "<script type=\"text/javascript\">function Init() { __dlg_sizeToContent(); };</script>\n", lang
    ));
    return false;
};

function doPopupAttachNotification() {
	gPopupDoc.getElementById('form').style.display = "none";
	gPopupDoc.getElementById('waiting').style.display = "inline";
};
function doPopupRename(titleID) {
	showPopWin('/skins/ace/popup-rename.php?titleID=' + titleID + '&userName=' + _userName, 570, 530);
	return false;
};
function doPopupMoveAttach(titleID, attachID) {
	showPopWin('/skins/ace/popup-move-attach.php?titleID=' + titleID +'&attachID=' +  attachID + '&userName=' + _userName, 570, 459);
	return false;
};
function doPopupEditAttach(attachID) {
	showPopWin('/skins/ace/popup-attach.php?attachID=' + attachID, 380, 206);
	return false;
};
function doPopupRestrict(titleID) {
	showPopWin('/skins/ace/popup-restrict.php?titleID=' + titleID + '&userName=' + _userName, 380, 315);
	return false;
};
function doPopupViewRestrict(titleID) {
	showPopWin('/skins/ace/popup-viewrestrict.php?titleID=' + titleID, 380, 320);
	return false;
};
function doPopupDelete(titleID) {
	showPopWin('/skins/ace/popup-delete.php?titleID=' + titleID + '&userName=' + _userName, 400, 224);
	return false;
};

function hookEditIcons() {	
	function showEditIcon() {
		if (this.childNodes[1]) 
			this.childNodes[1].style.visibility = 'visible';
	};	
	function hideEditIcon() {
		if (this.childNodes[1]) 
			this.childNodes[1].style.visibility = 'hidden';
	};
	var elt = document.getElementById('topic');
	for (var i = 2; i < 7; i++) {
		var headers = elt.getElementsByTagName('h'+i);
		for (var j = 0;j<headers.length;j++) {
			headers[j].onmouseover = showEditIcon;
			headers[j].onmouseout = hideEditIcon;
		}
	}
	var elt = document.getElementById('title');
	if (elt) {
		elt.onmouseover = showEditIcon;
		elt.onmouseout = hideEditIcon;
	}
};

var fps = 500;
function doFade(elt, duration, _fade) {
	if (elt.fadeTimer) 
		clearInterval(elt.fadeTimer);

	elt.fadeOps = new Array();

	elt.fadeFrames = Math.round(fps * duration / 1000);
	elt.fadeInterval = 1000 / fps;
	for (var i = 0; i < _fade.length; i++) {
		var op = {};
		op.param = _fade[i].param;
		op.from = _fade[i].from;
		op.to = _fade[i].to;
		elt.fadeOps[elt.fadeOps.length]= op;
	}
	
	elt.fadeFrame = 0;
	elt.fadeTimer = setInterval ("doStep ('" + elt.id + "')", elt.fadeInterval);
};
	
function doStep(divid) {
	var elt = document.getElementById (divid);

	var completed = elt.fadeFrame / elt.fadeFrames;
	var inverseCompleted = (elt.fadeFrames - elt.fadeFrame) / elt.fadeFrames;

	for (var i = 0; i < elt.fadeOps.length; i ++) {
		var op = elt.fadeOps[i];
		elt.style[op.param] = Number (op.from) * inverseCompleted + Number (op.to) * completed;
	}

	elt.fadeFrame++;

	if (elt.fadeFrame > elt.fadeFrames) {
		clearInterval (elt.fadeTimer);
		elt.fadeTimer = null;
	}
};

function displayInlineMsg(div) {
	var elt = document.getElementById(div);
	
	//page offset from top
	var yOffset = getPageYOffset();
	elt.style.display = 'block';
	var itemHeight = getItemHeight(elt);
	var itemWidth = getItemWidth(elt);
	var eltXOffset = (winX/2) - (itemWidth/2);
	var eltYOffset = (winY/2) - (itemHeight/2) + yOffset;
	
	//set the props of the elt
	elt.style.position = 'absolute';
	elt.style.left = Math.round(eltXOffset) + 'px';
	elt.style.top = Math.round(eltYOffset) + 'px';
	elt.zIndex = 9999;
	
	//hide the message after a few minutes
    window.setTimeout("hideInlineMsg('"+div+"')", 3000);
    
    //IE doesn't support opacity
    if (!ie6 && !ie5) {
		doFade (elt, 650, [{param: "opacity", from: 1.0, to: 0.0}]);
	}
};

function hideInlineMsg(div) {
	document.getElementById(div).style.display = 'none';
};

function fixTables() {
	if (!clientBrowser.isIe)
		return;
	var content = document.getElementById('topic');
	var _tables = content.getElementsByTagName('table');
	if (_tables.length == 0)
		return false;
		
	for (var i = 0; i < _tables.length; i++ ) {		
		var table = _tables[i].cloneNode(true);
		var divTag = document.createElement('div');
		divTag.className = 'wrap_tbl';
		divTag.appendChild(table);
		if (is_gecko) {
			var parentNode = _tables[i].parentNode;
			parentNode.replaceChild(divTag, _tables[i]);
		}
		else {
			_tables[i].replaceNode(divTag);
		}
	}
	
};
function doToggleLayer(elt, setClass, toggleClass) {
	if (!setClass)
		setClass = 'show-no';
		
	if (elt.className == setClass) {
		elt.className = toggleClass;
		return;
	}
	
	if (elt.className == toggleClass) {
		elt.className = setClass;
		return;
	}
		
	var cssClass = elt.className.split(' ');
	if (searchKey = array_search(setClass, cssClass)) 
		cssClass[searchKey] = toggleClass;
	
	else
		cssClass.push(setClass);
		
	elt.className = cssClass.join(' ');
};
function array_search(needle, haystack) {
	for (var i = 0; i < haystack.length; i++ ) {
		if (haystack[i] == needle)
			return i;	
	}	
	return false;
};

function doSectionEditRedirect() {
	var icon = this.getElementsByTagName('span');
	if (icon.length == 0)
		return false;
	for (var i = 0; i < icon.length; i++) {
		if (icon[i].className == 'editsection') {	
			var link = this.getElementsByTagName('a');
			top.location.href = link[0].getAttribute('href');
		}
	}
};

function setHiddenSubmit(divid) {
	document.getElementById(divid).value = '1';
};

// toggles between image description/image form
function fileDesc(divid, type) {
	if (type == 'hide') {
		setDisplayMode('fileDescForm_'+divid, 'hide');
		setDisplayMode('fileDescDisplay_'+divid, 'show');
	}
	else {
		setDisplayMode('fileDescForm_'+divid, 'show');
		setDisplayMode('fileDescDisplay_'+divid, 'hide');
		document.getElementById('fileDescElement_'+divid).focus();
	}	
	imageCellHeights.resize();
	return false;
};

var imageCellHeights = {
	x : null, //total width of the gallery
	cellWidth : 200, //width of each image cell
	cellMinHeight : 235, //minimum cell height
	resize : function ()  {
		var elt = document.getElementById('imageGallery');
		if (!elt) 
			return;
			
		this.x = getItemWidth(elt);
		var imagesPerRow = Math.floor(this.x / this.cellWidth);
		var i = 0; //current number of images in the row
		var curRowHeight = 0;
		var cellHeight = 0;
		var list = elt.getElementsByTagName('li');
		for (var j = 0; j < list.length; j++) {
			list[j].style.height = '';
			list[j].style.minHeight = '';
			cellHeight = getItemHeight(list[j]);
			if (cellHeight > curRowHeight) 
				curRowHeight = cellHeight;
			if (i < (imagesPerRow - 1))
				i++;
			else {
				i = 0;
				curRowHeight = curRowHeight < this.cellMinHeight ? this.cellMinHeight: curRowHeight;
				
				//reset all items in this row to the greatest height
				for (k = 0; k < imagesPerRow; k++) {
					if (ie6 == 1)
						list[j-k].style.height = curRowHeight+'px';
					else
						list[j-k].style.minHeight = curRowHeight+'px';
				}
				curRowHeight = 0;
			}
		}
	}
};

function setDisplayMode(divid, type) {
	if (divLayer = document.getElementById(divid)) {
		if (type == 'show')
			divLayer.style.display = 'block';
		if (type == 'hide')
			divLayer.style.display = 'none';
	}
};

function toggleVisi(divid) {
 	if (myselect = document.getElementById(divid)) {
	 	if (myselect.style.visibility != 'hidden')
		 	myselect.style.visibility = "hidden";
	 	else 
		 	myselect.style.visibility = "visible";
 	}
};

// Wikipedia JavaScript support functions
// if this is true, the toolbar will no longer overwrite the infobox when you move the mouse over individual items
var noOverwrite=false;
var alertText;
var clientPC = navigator.userAgent.toLowerCase(); // Get client info
var is_gecko = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1)
                && (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
var is_safari = ((clientPC.indexOf('AppleWebKit')!=-1) && (clientPC.indexOf('spoofer')==-1));
var is_khtml = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
if (clientPC.indexOf('opera')!=-1) {
    var is_opera = true;
    var is_opera_preseven = (window.opera && !document.childNodes);
    var is_opera_seven = (window.opera && document.childNodes);
}

// add any onload functions in this hook (please don't hard-code any events in the xhtml source)
function onloadhook () {
    // don't run anything below this for non-dom browsers
    if(!(document.getElementById && document.getElementsByTagName)) return;
    histrowinit();
    unhidetzbutton();
    akeytt();
};

if (window.addEventListener) window.addEventListener("load",onloadhook,false);
else if (window.attachEvent) window.attachEvent("onload",onloadhook);


// document.write special stylesheet links
if(typeof stylepath != 'undefined' && typeof skin != 'undefined') {
    if (is_opera_preseven) {
        document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/Opera6Fixes.css" />');
    } else if (is_opera_seven) {
        document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/Opera7Fixes.css" />');
    } else if (is_khtml) {
        document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/KHTMLFixes.css" />');
    }
}
// for enhanced RecentChanges
function toggleVisibility( _levelId, _otherId, _linkId) {
	var thisLevel = document.getElementById( _levelId );
	var otherLevel = document.getElementById( _otherId );
	var linkLevel = document.getElementById( _linkId );
	if ( thisLevel.style.display == 'none' ) {
		thisLevel.style.display = 'block';
		otherLevel.style.display = 'none';
		linkLevel.style.display = 'inline';
	} else {
		thisLevel.style.display = 'none';
		otherLevel.style.display = 'inline';
		linkLevel.style.display = 'none';
		}
};

// page history stuff
// attach event handlers to the input elements on history page
function histrowinit () {
    hf = document.getElementById('pagehistory');
    if(!hf) return;
    lis = hf.getElementsByTagName('td');
    for (i=0;i<lis.length;i++) {
        inputs=lis[i].getElementsByTagName('input');
        if(inputs[0] && inputs[1]) {
                inputs[0].onclick = diffcheck;
                inputs[1].onclick = diffcheck;
        }
    }
    diffcheck();
};

// check selection and tweak visibility/class onclick
function diffcheck() { 
    var dli = false; // the li where the diff radio is checked
    var oli = false; // the li where the oldid radio is checked
    hf = document.getElementById('pagehistory');
    if(!hf) return;
    lis = hf.getElementsByTagName('td');
    for (i=0;i<lis.length;i++) {
        inputs=lis[i].getElementsByTagName('input');
        if(inputs[1] && inputs[0]) {
            if(inputs[1].checked || inputs[0].checked) { // this row has a checked radio button
                if(inputs[1].checked && inputs[0].checked && inputs[0].value == inputs[1].value) return false;
                if(oli) { // it's the second checked radio
                    if(inputs[1].checked) {
                    oli.className = "selected";
                    return false 
                    }
                } else if (inputs[0].checked) {
                    return false;
                }
                if(inputs[0].checked) dli = lis[i];
                if(!oli) inputs[0].style.visibility = 'hidden';
                if(dli) inputs[1].style.visibility = 'hidden';
                lis[i].className = "selected";
                oli = lis[i];
            }  else { // no radio is checked in this row
                if(!oli) inputs[0].style.visibility = 'hidden';
                else inputs[0].style.visibility = 'visible';
                if(dli) inputs[1].style.visibility = 'hidden';
                else inputs[1].style.visibility = 'visible';
                lis[i].className = "";
            }
        }
    }
};


// Timezone stuff
// tz in format [+-]HHMM
function checkTimezone( tz, msg ) {
	var localclock = new Date();
	// returns negative offset from GMT in minutes
	var tzRaw = localclock.getTimezoneOffset();
	var tzHour = Math.floor( Math.abs(tzRaw) / 60);
	var tzMin = Math.abs(tzRaw) % 60;
	var tzString = ((tzRaw >= 0) ? "-" : "+") + ((tzHour < 10) ? "0" : "") + tzHour + ((tzMin < 10) ? "0" : "") + tzMin;
	if( tz != tzString ) {
		var junk = msg.split( '$1' );
		document.write( junk[0] + "UTC" + tzString + junk[1] );
	}
};
function unhidetzbutton() {
    tzb = document.getElementById('guesstimezonebutton')
    if(tzb) tzb.style.display = 'inline';
};

// in [-]HH:MM format...
// won't yet work with non-even tzs
function fetchTimezone() {
	// FIXME: work around Safari bug
	var localclock = new Date();
	// returns negative offset from GMT in minutes
	var tzRaw = localclock.getTimezoneOffset();
	return formatTimezone(tzRaw);
};

function formatTimezone(tzSecs) {
	var tzHour = Math.floor( Math.abs(tzSecs) / 60);
	var tzMin = Math.abs(tzSecs) % 60;
	return  ((tzSecs >= 0) ? "-" : "") + ((tzHour < 10) ? "0" : "") + tzHour +
		":" + ((tzMin < 10) ? "0" : "") + tzMin;
};

function checkTimezone() {
	var currentTZ = fetchTimezone();
	if (_cur_TZ != currentTZ) {
		x_doSetTimezone(currentTZ, cb_blank);
	}
};

function guessTimezone() {
    var wpHourDiff = document.getElementById('wpHourDiff');
	if (wpHourDiff) wpHourDiff.value = fetchTimezone();
};

function toggleItem(toggleid, contentid) {
	var toc = document.getElementById(toggleid);
	var content = document.getElementById(contentid);
	var spans = toc.getElementsByTagName('span');
	var showlink = spans[0];
	var hidelink = spans[1];
	
	//if we are showing the content
	if (content.style.display == 'none') {
		content.style.display = 'block';
		showlink.style.display = 'none';
		hidelink.style.display = 'block';
	}
	else {
		content.style.display = 'none';
		showlink.style.display = 'block';
		hidelink.style.display = 'none';
	}
};

function openWindow(href,menu) {
	window.open(href, 'popupwindow', 'width='+(winX - 100)+',height='+(winY - 100)+',scrollbars,resizable' + (menu ? ',menubar=yes' : ''));
	return false;
};

function addInfobox(infoText,text_alert) {
	alertText=text_alert;
	var clientPC = navigator.userAgent.toLowerCase(); // Get client info

	var re=new RegExp("\\\\n","g");
	alertText=alertText.replace(re,"\n");

	// if no support for changing selection, add a small copy & paste field
	// document.selection is an IE-only property. The full toolbar works in IE and
	// Gecko-based browsers.
	if(!document.selection && !is_gecko) {
 		infoText=escapeQuotesHTML(infoText);
	 	document.write("<form name='infoform' id='infoform'>"+
			"<input size=80 id='infobox' name='infobox' value=\""+
			infoText+"\" readonly='readonly'></form>");
 	}
};

function escapeQuotes(text) {
	var re=new RegExp("'","g");
	text=text.replace(re,"\\'");
	re=new RegExp('"',"g");
	text=text.replace(re,'&quot;');
	re=new RegExp("\\n","g");
	text=text.replace(re,"\\n");
	return text;
};

function escapeQuotesHTML(text) {
	var re=new RegExp('"',"g");
	text=text.replace(re,"&quot;");
	return text;
};

function akeytt() {
    if(typeof ta == "undefined" || !ta) return;
    pref = 'alt-';
    if(is_safari || navigator.userAgent.toLowerCase().indexOf( 'mac' ) + 1 ) pref = 'control-';
    if(is_opera) pref = 'shift-esc-';
    for(id in ta) {
        n = document.getElementById(id);
        if(n){
            a = n.childNodes[0];
            if(a){
                if(ta[id][0].length > 0) {
                    a.accessKey = ta[id][0];
                    ak = ' ['+pref+ta[id][0]+']';
                } else {
                    ak = '';
                }
                a.title = ta[id][1]+ak;
            } else {
                if(ta[id][0].length > 0) {
                    n.accessKey = ta[id][0];
                    ak = ' ['+pref+ta[id][0]+']';
                } else {
                    ak = '';
                }
                n.title = ta[id][1]+ak;
            }
        }
    }
};

function toggleTable(divid) {
	//change icon
 	var showlink = document.getElementById('showlink-'+divid);
 	var hidelink = document.getElementById('hidelink-'+divid);
 	
 	if( showlink.style.display == 'none') {
		hidelink.style.display='none';
		showlink.style.display='block';
	} else {
		hidelink.style.display='block';
		showlink.style.display='none';
	}	
	
	//loop through table	
	var cells = document.getElementsByTagName('tr');
	var numCells = cells.length;
	var i;
	
	for (i = 0; i < numCells; ++i) {
		// If cell should be changed (matching class)...
		if (divid+' show-no' == cells[i].className)
			cells[i].className = divid + ' display';
		else if (divid+' display' == cells[i].className)
			cells[i].className = divid + ' show-no';
	}
	return false;
};

/* blank function for sajax callback */
function cb_blank() {};

/* function for sajax; persistent addtl nav */
function do_context(z) {
	x_wfStoreContextStatus(z, cb_blank);
};

//MT: royk search toggling
function toggleSearchTabs() {
	var titleContent = document.getElementById('search-title');
	var pageContent = document.getElementById('search-page');
	var tabTitle = document.getElementById('search-title-tab');
	var tabPage = document.getElementById('search-page-tab');
	
	if (titleContent.style.display == 'none') {
		titleContent.style.display = 'block';
		pageContent.style.display = 'none';
		tabTitle.className = 'selected';
		tabPage.className = '';
		var page = 'title';
	}
	else {
		titleContent.style.display = 'none';
		pageContent.style.display = 'block';
		tabTitle.className = '';
		tabPage.className = 'selected';
		var page = 'page';
	}	
	x_wfStoreSearchStatus(page, cb_blank);
};

function createCookie(name,value,days) { 
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else {
		var expires = "";
	}
	document.cookie = name+"="+value+expires+"; path=/";
};

function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') 
			c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) 
			return c.substring(nameEQ.length,c.length);
	}
	return null;
};

function eraseCookie(name) {
	createCookie(name,"",-1);
};
function doRestrictAccess(titleID, userName, protectType, userIds) {
	x_wfRestrictTopic(titleID, userName, protectType, userIds, function (data) { 
		hidePopWin (false); 
		window.location.reload(); 
	});
};

function doDeleteAction (titleID, includingChildren) {
	includingChildren = includingChildren == null ? false : includingChildren.checked;
	x_wfDeleteTopic(titleID, includingChildren, _userName, function (data) {
		var status = data.charAt(0);
		if (status == '<') {
			data = data.replace(/<br\s*\/?>/ig, "\n");
		}
		if (status == '+') {
			data = data.substring(1);
			hidePopWin (false);
			window.location = data;
		} else {
			if (status == '-')
				data = data.substring (1);
			alert (data);
			hidePopWin (false);
		}
	});
};

var row_count = 1;
function addRow() {
	var tbl = document.getElementById('attachFiles');
	var maxNum = document.getElementById('maxNum');
	
	var newRow = tbl.insertRow(tbl.rows.length);
	++row_count;
	newRow.insertCell(0).innerHTML = '<a href="#" onclick="return addRow()" title="'+_lang_fileattach+'"><span class="icon"><img src="/skins/ace/icon-trans.gif" class="attach-add" alt=""></span></a><a href="#" onclick="return delRow(this.parentNode.parentNode.rowIndex)" title="'+_lang_removeattach+'"><span class="icon"><img src="/skins/ace/icon-trans.gif" class="attach-remove"></span></a>';
	newRow.insertCell(1).innerHTML = '<input name="file_'+row_count+'" id="file_'+row_count+'" size="40" type="file"/>';	
	newRow.insertCell(2).innerHTML = '<input type="text" name="filedesc_'+row_count+'" style="width:100%"/>';
	if (row_count > maxNum.value)
		maxNum.value = row_count;
	__dlg_sizeToContent();
	return false;
};
function delRow(indexKey) {
	var tbl = document.getElementById('attachFiles');
	if (tbl.rows.length > 2) {
		tbl.deleteRow(indexKey);
		for (var j = indexKey + 1; j < tbl.rows.length; j++)
			if (tbl.rows[j] && typeof(tbl.moveRow) == 'function') 
				tbl.moveRow(j, j - 1);	
	} else
		parent.doClosePopupMessage();
	__dlg_sizeToContent();
	return false;
};

function doEditAttachDescription(attachID, attachDesc) {
	x_attachEdit(attachID, attachDesc, function(returnValue) {
		hidePopWin (false); 
		var elt = document.getElementById('fileDescDisplay_'+attachID);
    	var returnValues = returnValue.split('|');
    	var html = '';
    	if (returnValues.length > 3) {
    		for (var i=2;i<returnValues.length;i++)
    			html += returnValues[i];
    	}
    	else
    		html = returnValues[2];	

    	if (returnValues[0] == 'success')
    		elt.innerHTML = html == '' ? '<span class="nodescription">No description</span>' : html;
    		
        imageCellHeights.resize();
	});
};
/*
 * MindTouch Deki - a commercial grade open source wiki
 *  derived from MediaWiki (www.mediawiki.org)
 * Copyright (C) 2006 MindTouch, Inc.
 * http://www.mindtouch.com/  oss@mindtouch.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

var menuBubble = false;
var menuOpened = false;
var menuJustOpened = false;
var menuSelect = false;

menuLinkClick = function(divid, offsetX) {
	if (menuOpened && menuOpened != divid)
		menuToggle(menuOpened, offsetX);	
	menuToggle(divid, offsetX);
};

menuToggle = function(navid, offsetX) {
	var node = document.getElementById(navid);
	if (!node) return false;
	if (node.style.display == 'block') {
		node.style.display = 'none';	
		menuOpened = false;
	}
	else {
		node.style.visibility = 'hidden';
		node.style.display = 'block';
		var dimensions = Element.getDimensions(node);
		var position = Position.cumulativeOffset(node);
		if (typeof(winX) != 'null') {
			if ((position[0] + dimensions.width) > (winX - 36)) {
				if (typeof(offsetX) == 'undefined') offsetX = 0;
				node.style.left = (position[0] - dimensions.width + offsetX + 5)+'px';
			}
		}
		node.style.visibility = '';
		menuOpened = navid;
		menuJustOpened = true;
	}
};

menuPosition = function (navid, elt, offsetX, offsetY) {
	var x = findPosX(elt);
	var y = findPosY(elt);
	var dimension = Element.getDimensions(elt);	
	offsetY = offsetY + dimension.height;
	if (typeof(offsetX) == 'number') x = x + offsetX;
	if (typeof(offsetY) == 'number') y = y + offsetY;
	var node = $(navid);
	if (!node) return false;
	node.style.left = x+'px';
	node.style.top = y+'px';
	menuLinkClick(navid, dimension.width);
	return false;
};

menuOff = function(navid) {
	var node = document.getElementById(navid);
	if (!node)
		return false;
	if (node.style.display == 'block') {
		node.style.display = 'none';	
		menuOpened = false;
	}
};

menuBodyClick = function() {
	if (!menuJustOpened && !menuBubble && menuOpened) {
		if (menuSelect) {
			selectToggle(menuOpened);
		}
		else {
			menuToggle(menuOpened);
		}
	}
	if (menuBubble)
		menuBubble = false;	
	if (menuJustOpened)
		menuJustOpened = false;	
};

// select stuff
function selectClick(divid) {
	if (menuOpened && menuOpened != divid)
		selectToggle(menuOpened);	
	selectToggle(divid);
}

function selectToggle(divid) {
	var elt = document.getElementById(divid);
	if (!elt) {
		return false;
	}
	var ul_ = document.getElementsByTagName('ul');
	for ( var i = 0 ; i < ul_.length ; i++) {
		if (ul_[i].className == 'fauxOptions') {
	 		if (ul_[i].style.display == 'block') {
	 			ul_[i].style.display = 'none';
		 		doSetSelectClasses(divid, true);
				menuOpened = false;
				menuSelect = false;
	 		}
	 		else {
		 		ul_[i].style.display = 'block';
				menuOpened = divid;
				menuJustOpened = true;
				menuSelect = true;
	 		}
		}
	}		
}
 function selectBodyClick() {
  if (!menuJustOpened && !menuBubble && menuOpened)
    selectToggle(menuOpened);
  menuBubble = false;  
  menuJustOpened = false;  
};

function selectClick(node) {
  if (menuOpened && menuOpened != node)
    selectToggle(menuOpened);  
  selectToggle(node);
};

function selectToggle(node) {
  var fauxOptions = node.mt_select.fauxOptions;
  if (fauxOptions.style.display == 'block') {
    fauxOptions.style.display = 'none';
    node.mt_select.options[node.mt_select.selected].node.className = "selected";
    menuOpened = false;
    menuSelect = false;
  } else {
    fauxOptions.style.display = 'block';
    var selNode = node.mt_select.curSelectVal.parentNode;
    fauxOptions.style.left = MT.getOffsetLeft(selNode) + "px";
    fauxOptions.style.top = MT.getOffsetTop(selNode) + selNode.offsetHeight - 1 + "px";
    menuOpened = node;
    menuJustOpened = true;
	menuSelect = true;
  }
};

var NavMenu = { 
	lastOpened : null, //the last opened menu id
	isOpening : null,
	openMenu : false,
	justClosed : false,
	maxHeight: 132, //max height of the menu
	minHeight: 1, //min height of the menu; keep at least 1px for IE
	
	bodyClick : function() {
		if (this.isOpening) {
			this.isOpening = null;
			return;
		}
		if(this.lastOpened) {
			this.hide();
		}
		return false;
	},
	/*** 
	 * 
	 */
	show : function(contextNode, titleID, title, isWatching, isBookmarked, isLoggedIn, canMove, canDelete, isRestricted) {
		this.menuBubble = true;
		this.isOpening = true;
		var divFiller = 'npdFiller_' + titleID;
		var divMenu = 'npdmenu_' + titleID;
		var divContainer = 'container_' + titleID;
		var eltContainer = document.getElementById(divContainer);
		addCSSClass(eltContainer, 'force');
		
		if (this.lastOpened && '_' + titleID != this.lastOpened) {
			this.hide();
		}
		else if (this.lastOpened) {
			this.hide();
			this.justClosed = true;
			return;
		}
		this.lastOpened = '_' + titleID;
		
		var eltFiller = document.getElementById(divFiller);
		var dimensions = Element.getDimensions(contextNode);
		var position = Position.cumulativeOffset(contextNode);
		eltFiller.style.position = 'absolute';
		eltFiller.style.left = (position[0])+'px';
		eltFiller.style.top = (position[1] + dimensions.height) + 'px';
		
		//for IE
		eltFiller.style.display = '';		
		//first, generate the new menu
		eltFiller.appendChild(this.generateMenu(contextNode, titleID, title, isWatching, isBookmarked, isLoggedIn, canMove, canDelete, isRestricted));
		eltFiller.style.display = 'block';
		menuLinkClick(divMenu);
	},
	hide: function () {
		if (this.lastOpened) {
			var divContainer = 'container' +this.lastOpened;
			var eltContainer = document.getElementById(divContainer);
			removeCSSClass(eltContainer, 'force');
			removeCSSClass(eltContainer, 'container-hover');
			removeCSSClass(eltContainer, 'default');
			addCSSClass(eltContainer, 'container');
			var eltMenu = 'npdmenu' + this.lastOpened;	
			var trielt = document.getElementById('expNode'+this.lastOpened);
			if (trielt) trielt.style.visibility = 'hidden';
			var elt = document.getElementById(eltMenu);
			elt.parentNode.removeChild(elt);
			menuLinkClick(eltMenu);	
		}
		this.lastOpened = null;
	},
	
	generateMenu : function (contextNode, titleID, title, isWatching, isBookmarked, isLoggedIn, canMove, canDelete, isRestricted) {
		function createColor(color, title, divid) {	
			var colorNode = document.createElement('a');
			colorNode.href = '#';
    		colorNode.setAttribute('stat', 102/*MKS_STAT_MENU_HIGHLIGHT*/);
			if (color == 'cancel') {
				colorNode.className = 'block';
				colorNode.appendChild(iconify('cancel', 'icon-s'));	
			}
			else {
				colorNode.className = 'block '+color;
			}
			colorNode.onclick = function() {
				setColor(title, color);
				return false;
			}
			return colorNode;
		};
		
		function addMenuItem(action, icon, text, onclick, stat) {
    		var item = document.createElement('li');
    		var a = document.createElement('a');
    		
    		a.href = action && isLoggedIn ? url+action : "#";
    		a.className = 'mlink';
    		a.setAttribute('stat', stat);
    		a.appendChild(iconify(icon));
    		    		
    		var span = document.createElement('span');
    		span.innerHTML = text;
    		span.className = 'text';
    		a.appendChild(span);
    		
    		if (onclick && isLoggedIn)
    		  a.onclick = onclick;
    		item.appendChild(a);
    		menuList.appendChild(item);
		};
		
		function addDisabledMenuItem(icon,text) {
    		var item = document.createElement('li');
    		var a = document.createElement('a');
    		a.className = 'mlink disabled';
    		a.href = "#";
    		a.onclick = function() { return false; }
    		a.appendChild(iconify(icon));
    		
    		var span = document.createElement('span');
    		span.innerHTML = text;
    		span.className = 'text'; 		
    		a.appendChild(span);
    		
    		item.appendChild(a);
    		menuList.appendChild(item);
		};

		var divid = 'npdmenu_' + titleID;
		var url = mt_gen.getUrlFromName(title);

		var divMain = document.createElement('div');
		divMain.onclick = function () {
			menuBubble = true;	
		};
		divMain.className = 'dmenu';

		var divWrap = document.createElement('div');
		divWrap.id = divid;
		divWrap.className = 'dmenu-wrap';

		var divTop = document.createElement('div');
		divTop.className = 'dmenu-top';

		var divBottom = document.createElement('div');
		divBottom.className = 'dmenu-bottom';

		var divBody = document.createElement('div');
		divBody.className = 'dmenu-body';

		var menuList = document.createElement('ul');
		
		if (isLoggedIn) {
			addMenuItem('&action=addsubpage','addSubpage', _lang_addSubpage, false, 98/*MKS_STAT_MENU_NEW_SUBPAGE*/);
		}
		else {
			addDisabledMenuItem('addSubpage-disabled', _lang_addSubpage);
		}
        
        if (isLoggedIn && canMove) {
	        addMenuItem(null,'move',_lang_move,function () {
				NavMenu.hide();
				doPopupRename(titleID, title);
				return false;
			}, 101/*MKS_STAT_MENU_MOVE*/);
		}
		else if (isRestricted) {
	        addMenuItem(null,'move',_lang_move,function () {
				NavMenu.hide();
				doPopupViewRestrict(titleID);
				return false;
			});
		}
		else {
			addDisabledMenuItem('move-disabled', _lang_move);
		}
		if (isLoggedIn && canDelete) {
	        addMenuItem(null,'delete',_lang_delete,function () {
				NavMenu.hide();
				doPopupDelete(titleID, title);
				return false;
			}, 70/*MKS_STAT_MENU_DELETE*/);
		}
		else if (isRestricted) {
	        addMenuItem(null,'delete',_lang_delete,function () {
				NavMenu.hide();
				doPopupViewRestrict(titleID);
				return false;
			});
		}
		else {
			addDisabledMenuItem('delete-disabled', _lang_delete);
		}

		var separator = document.createElement('li');
		separator.className = 'separator';
		menuList.appendChild(separator);

		//highlighting
		var item = document.createElement('li');
		var span = document.createElement('div');
		span.className = 'option';
		var ispan = document.createElement('span');
		ispan.innerHTML = _lang_hilite;
		var breaker = document.createElement('div');
		breaker.className = 'br';

		var divColors = document.createElement('div');
		divColors.className = 'colors';
		divColors.appendChild(createColor('cancel', titleID, divid));
		divColors.appendChild(createColor('red', titleID, divid));
		divColors.appendChild(createColor('green', titleID, divid));
		divColors.appendChild(createColor('blue', titleID, divid));
		divColors.appendChild(createColor('yellow', titleID, divid));

		span.appendChild(divColors);
		span.appendChild(ispan);
		span.appendChild(breaker);

		item.appendChild(span);
		menuList.appendChild(item);

		divBody.appendChild(menuList);

		divWrap.appendChild(divTop);
		divWrap.appendChild(divBody);
		divWrap.appendChild(divBottom);

		divMain.appendChild(divWrap);
		
		return divMain;
	}
};

var FileMenu = { 
	lastOpened : null, //the last opened menu id
	isOpening : null,
	openMenu : false,
	justClosed : false,
	maxHeight: 132, //max height of the menu
	minHeight: 1, //min height of the menu; keep at least 1px for IE
	
	bodyClick : function() {
		if (this.isOpening) {
			this.isOpening = null;
			return;
		}
		if(this.lastOpened) {
			this.hide();
		}
		return false;
	},
	/*** 
	 * 
	 */
	show : function(node, titleID, attachID, title, isLoggedIn, showRevisions, areRevisions, reviseURL, isRestricted) {
		
		this.menuBubble = true;
		this.isOpening = true;
		var divFiller = 'menuFiller'; //filler div for filling the menu
		var divMenu = 'npdmenu_' + titleID; //menu generated by generateMenu()
		
		if (this.lastOpened && '_' + titleID != this.lastOpened) {
			this.hide();
		}
		else if (this.lastOpened) {
			this.hide();
			this.justClosed = true;
			return false;
		}
		this.lastOpened = '_' + titleID;
		
		var eltFiller = document.getElementById(divFiller);
		eltFiller.style.position = 'absolute';
		eltFiller.style.left = (findPosX(node))+'px';
		eltFiller.style.top = (findPosY(node) + 16) + 'px';
		
		//for IE
		eltFiller.style.display = '';		
		//first, generate the new menu
		eltFiller.appendChild(this.generateMenu(titleID, attachID, title, isLoggedIn, showRevisions, areRevisions, reviseURL, isRestricted));
		eltFiller.style.display = 'block';
		
		var dimensions = Element.getDimensions($(divMenu));
		var linkDimensions = Element.getDimensions(node);
		var position = Position.cumulativeOffset(eltFiller);
		if (typeof(winX) != 'null' && typeof(winY) != 'null') {
			var pageDimensions = Position.cumulativeOffset($('bodyHeight'));
			var pageHeight = pageDimensions[1];
			var extendsRight = (position[0] + dimensions.width) > (winX - 36);
			var extendsBottom = (position[1] + dimensions.height) > pageHeight;
			var horizOffset = position[0] - dimensions.width + linkDimensions.width + 5;
			if (extendsRight) {
				if (typeof(offsetX) == 'undefined') offsetX = 0;
				eltFiller.style.left = horizOffset+'px';
			}
			if (extendsBottom) {
				var vertOffset = position[1] - ((position[1] + dimensions.height) - pageHeight);
				eltFiller.style.top = vertOffset + 'px';
				horizOffset = position[0] + linkDimensions.width;
				eltFiller.style.left = horizOffset+'px';
			}
		}
		menuLinkClick(divMenu, dimensions.width);
		return false;
	},
	hide: function () {
		if (this.lastOpened) {
			var eltMenu = 'npdmenu' + this.lastOpened;	
			var elt = document.getElementById(eltMenu);
			elt.parentNode.removeChild(elt);
			menuLinkClick(eltMenu);	
		}
		this.lastOpened = null;
	},
	
	generateMenu : function (titleID, attachID, url, isLoggedIn, showRevisions, areRevisions, reviseURL, isRestricted) {
		function addMenuItem(icon, text, href, onclick, cssClass, stat) {
    		var item = document.createElement('li');
    		var a = document.createElement('a');
    		
    		a.href = href;
    		a.setAttribute('stat', stat);
    		a.className = 'mlink'+(cssClass && cssClass != '' ? ' '+cssClass: '');
    		a.appendChild(iconify(icon+(cssClass && cssClass != '' ? '-'+cssClass: '')));
    		    		
    		var span = document.createElement('span');
    		span.innerHTML = text;
    		span.className = 'text';    		
    		a.appendChild(span);
    		
    		if (onclick && isLoggedIn)
    		  a.onclick = onclick;
    		item.appendChild(a);
    		menuList.appendChild(item);
		};
		var divid = 'npdmenu_' + titleID;

		var divMain = document.createElement('div');
		divMain.onclick = function () {
			menuBubble = true;	
		};
		divMain.className = 'dmenu';

		var divWrap = document.createElement('div');
		divWrap.id = divid;
		divWrap.className = 'dmenu-wrap';

		var divTop = document.createElement('div');
		divTop.className = 'dmenu-top';

		var divBottom = document.createElement('div');
		divBottom.className = 'dmenu-bottom';

		var divBody = document.createElement('div');
		divBody.className = 'dmenu-body';

		var menuList = document.createElement('ul');
		
		if (isLoggedIn) {
			addMenuItem('attachedit', 'Edit description', '#', function () { 
				if (isRestricted) {
					return doPopupViewRestrict(_page_ID);
				}
				else {
					return doPopupEditAttach(attachID);
				}
			}, false, 107/*MKS_STAT_FILE_DESC*/);
			addMenuItem('attachmove', 'Move', '#', function () { 
				if (isRestricted) {
					return doPopupViewRestrict(_page_ID);
				}
				else {
					return doPopupMoveAttach(_page_ID, attachID);	
				}
			}, false, 108/*MKS_STAT_FILE_MOVE*/);
			addMenuItem('attachdel', 'Delete', url+'&oldid='+attachID+'&action=remove', function() {
				if (isRestricted) {
					return doPopupViewRestrict(_page_ID);
				}
				else {
					return confirm(_lang_removeconfirm);
				}
			}, false, 109/*MKS_STAT_FILE_DELETE*/);
		}
		else {
			addMenuItem('attachedit', 'Edit description', '#', function () { return false; }, 'disabled');
			addMenuItem('attachmove', 'Move', '#', function () { return false; }, 'disabled');
			addMenuItem('attachdel', 'Delete', '#', function() { return false; }, 'disabled');
		}
		if (showRevisions) {
			if (areRevisions) {
				addMenuItem('attachhist', _lang_galleryhist, reviseURL, false, false, 110/*MKS_STAT_FILE_HISTORY*/);
			}
			else {
				addMenuItem('attachhist', _lang_galleryhist, '#', function () { return false;}, 'disabled');
			}
		}
		divBody.appendChild(menuList);

		divWrap.appendChild(divTop);
		divWrap.appendChild(divBody);
		divWrap.appendChild(divBottom);

		divMain.appendChild(divWrap);
		
		return divMain;
	}
};

function setColor(titleID, color) {
	if (color == 'cancel') color = '';
	var elt = document.getElementById('container_' + titleID);
	if (elt) {
		if (!inCSSClass(elt, 'container-current')) {
			elt.className = 'container';
			addCSSClass(elt, color);
			elt.onmouseover = function () {
				NavPane.showBorder(this, color != '' ? ' '+color: '');
			}
			elt.onmouseout = function() {
				NavPane.hideBorder(this, color != '' ? ' '+color: '');
			}
		}
	}
	x_wfStoreHighlights(titleID, color, cb_blank);
};

var NavPane = { 
	lastOpened: null, 
	showBorder : function (elt, hilite) {
		if (inCSSClass(elt, 'force')) {
			return;
		}
		elt.className = hilite != '' ? 'container-hover'+hilite: 'container-hover default';
	}, 
	hideBorder : function (elt, hilite) {
		if (inCSSClass(elt, 'force')) {
			return;
		}
		elt.className = 'container'+hilite;
	},
	boxClick : function (elt) {
		if (NavMenu.menuBubble && (NavMenu.justClosed || NavMenu.lastOpened)) {
			NavMenu.justClosed = null;
			NavMenu.menuBubble = null;
			return;
		}
		var tags = elt.getElementsByTagName('div');
		for (var i = 0; i<tags.length;  i++) {
			if (tags[i].className == 'innerPadding') {
				document.location = tags[i].childNodes[0].href;
			}
		}
	},
	itemClick : function() {
		if (NavMenu.openMenu) return;
	},
	onHover : function (_titleID) {	
		var elt = document.getElementById('expNode' + _titleID);
		if (!elt) return;
		elt.style.visibility = 'visible';
		if (this.lastOpened && this.lastOpened != _titleID) {
			this.onUnhover(this.lastOpened);
			this.lastOpened = null;	
		}
		this.lastOpened = _titleID;
		return true;
	}, 
	
	onUnhover : function (_titleID) {
		if (NavMenu.lastOpened && NavMenu.lastOpened == _titleID) {
			return;
		}
		var elt = document.getElementById('expNode'+_titleID);
		if (!elt) return;
		elt.style.visibility = 'hidden';
		return true;
	}
};


var EditorMenu = { 
	lastOpened : null, //the last opened menu id
	isOpening : null,
	openMenu : false,
	justClosed : false,
	maxHeight: 132, //max height of the menu
	minHeight: 1, //min height of the menu; keep at least 1px for IE
	
	bodyClick : function() {
		if (this.isOpening) {
			this.isOpening = null;
			return;
		}
		if(this.lastOpened) {
			this.hide();
		}
		return false;
	},
	
	bodyClickPtr : function() { EditorMenu.bodyClick(); },

	show : function(menuType) {
		var node;
		var titleID;
		
		switch (menuType) {
			case 'font':
				node = document.getElementById('ebi_Font');
				titleID = 1;
				break;
			case 'fontsize':
				node = document.getElementById('ebi_FontSize');
				titleID = 2;
				break;
			case 'align':
				node = document.getElementById('ebi_Align');
				titleID = 3;
				break;
			case 'fontcolor':
				node = document.getElementById('ebi_FontColor');
				titleID = 4;
				break;
			case 'backcolor':
				node = document.getElementById('ebi_BackgroundColor');
				titleID = 5;
				break;
		}

		this.menuBubble = true;
		this.isOpening = true;
		var divFiller = 'menuFiller'; //filler div for filling the menu
		var divMenu = 'npdmenu_' + titleID; //menu generated by generateMenu()
		
		if (this.lastOpened && '_' + titleID != this.lastOpened) {
			this.hide();
		}
		else if (this.lastOpened) {
			this.hide();
			this.justClosed = true;
			return false;
		}
		this.lastOpened = '_' + titleID;
		
		var eltFiller = document.getElementById(divFiller);
		eltFiller.style.position = 'absolute';
		xOffset = 0;
		yOffset = 22;
		eltFiller.style.left = (findPosX(node) + xOffset) + 'px';
		eltFiller.style.top = (findPosY(node) + yOffset) + 'px';
		
		//for IE
		eltFiller.style.display = '';		
		//first, generate the new menu
		eltFiller.appendChild(this.generateMenu(titleID));
		eltFiller.style.display = 'block';

		HTMLArea._addEvent(document, "mousedown", EditorMenu.bodyClickPtr );
		HTMLArea._addEvent(cur_editor._doc, "mousedown", EditorMenu.bodyClickPtr );

		menuLinkClick(divMenu);
		return false;
	},
	
	hide: function () {
		if (this.lastOpened) {
			var eltMenu = 'npdmenu' + this.lastOpened;	
			var elt = document.getElementById(eltMenu);
			elt.parentNode.removeChild(elt);
			menuLinkClick(eltMenu);

			HTMLArea._removeEvent(document, "mousedown", EditorMenu.bodyClickPtr );
			HTMLArea._removeEvent(cur_editor._doc, "mousedown", EditorMenu.bodyClickPtr );
		}
		this.lastOpened = null;
	},
	
	generateMenu : function (titleID) {
		var divid = 'npdmenu_' + titleID;

		var divMain = document.createElement('div');
		divMain.onclick = function () {
			menuBubble = true;	
		};
		divMain.className = 'dmenu';

		var divWrap = document.createElement('div');
		divWrap.id = divid;
		divWrap.className = 'dmenu-wrap';

		var divTop = document.createElement('div');
		divTop.className = 'dmenu-top';

		var divBottom = document.createElement('div');
		divBottom.className = 'dmenu-bottom';

		var divBody = document.createElement('div');
		divBody.className = 'dmenu-body';
		
		var menuList;
		switch (titleID) {
			case 1:
				menuList = this.generateFontMenu();
				break;
			case 2:
				menuList = this.generateFontSizeMenu();
				break;
			case 3:
				menuList = this.generateAlignMenu();
				break;
			case 4:
				menuList = this.generateFontColorMenu();
				break;
			case 5:
				menuList = this.generateBackColorMenu();
				break;
		}

		divBody.appendChild(menuList);

		divWrap.appendChild(divTop);
		divWrap.appendChild(divBody);
		divWrap.appendChild(divBottom);

		divMain.appendChild(divWrap);
		
		return divMain;
	},
	
	generateFontMenu : function () {
		var menuList = document.createElement('ul');
		
		function addFontMenuItem(fontLabel, fontHTML) {
			var item = document.createElement('li');
			var a = document.createElement('a');

			a.className = 'mlink';
			a.href = '#';
			a.onclick = function() { cur_editor.dropdownCommand('fontname', fontHTML); return false; };

			var span = document.createElement('span');
			span.innerHTML = fontLabel;
			span.style.fontFamily = fontHTML;
			span.style.fontSize = '16px';
			a.appendChild(span);

			item.appendChild(a);
			menuList.appendChild(item);
		};
		
		addFontMenuItem("Courier New", 'courier new,courier,monospace');
		addFontMenuItem("Times New Roman", 'times new roman,georgia,times,serif');
		addFontMenuItem("Verdana", 'verdana,arial,helvetica,sans-serif');
		return menuList;
	},
	
	generateFontSizeMenu : function () {
		var menuList = document.createElement('ul');
		
		function addFontSizeMenuItem(sizeName, relSize) {
    		var item = document.createElement('li');
    		var a = document.createElement('a');
    		
    		a.className = 'mlink';
    		a.href = '#';
    		a.onclick = function() { cur_editor.dropdownCommand('fontsize', relSize); return false; };
    		
    		var span = document.createElement('span');
    		span.innerHTML = sizeName;
    		a.appendChild(span);
    		
    		item.appendChild(a);
    		menuList.appendChild(item);
		};
		
		addFontSizeMenuItem('8 pt', '-2');
		addFontSizeMenuItem('10 pt (normal)', '0');
		addFontSizeMenuItem('14 pt', '+2');
		addFontSizeMenuItem('18 pt', '+3');
		return menuList;
	},
	
	generateAlignMenu : function () {
		var menuList = document.createElement('ul');
		
		function addAlignMenuItem(label, icon, command) {
    		var item = document.createElement('li');
    		var a = document.createElement('a');
    		
    		a.className = 'mlink';
    		a.href = '#';
    		a.onclick = function() { cur_editor.dropdownCommand(command, null); return false; };
    		
    		a.appendChild(iconify(icon));
    		var span = document.createElement('span');
    		span.innerHTML = label;
    		span.className = 'text';
    		a.appendChild(span);
    		
    		item.appendChild(a);
    		menuList.appendChild(item);
		};
		
		// todo tomk: use the same list as in the editor
		addAlignMenuItem('Left justify', 'justifyleft', 'justifyleft');
		addAlignMenuItem('Center', 'justifycenter', 'justifycenter');
		addAlignMenuItem('Right justify', 'justifyright', 'justifyright');
		addAlignMenuItem('Full justify', 'justifyfull', 'justifyfull');
		return menuList;
	},
	
	generateFontColorMenu : function () {
		var menuList = document.createElement('ul');
		
		function addFontColorMenuItem(label, color) {
    		var item = document.createElement('li');
    		var a = document.createElement('a');
    		
    		a.className = 'mlink';
    		a.href = '#';
    		a.onclick = function() { cur_editor.dropdownCommand('forecolor', color); return false; };
    		
    		var span = document.createElement('span');
    		span.innerHTML = label;
    		if (color != '#FFFFFF') {
	    		span.style.color = color;
	 			span.style.fontWeight = 'bold';
	 		}
    		a.appendChild(span);
    		
    		item.appendChild(a);
    		menuList.appendChild(item);
		};
		
		addFontColorMenuItem('Black', '#000000');
		addFontColorMenuItem('Gray', '#505050');
		addFontColorMenuItem('Green', '#598527');
		addFontColorMenuItem('Blue', '#004A80');
		addFontColorMenuItem('Red', '#9E0B0E');
		addFontColorMenuItem('Purple', '#8D258A');
		addFontColorMenuItem('Yellow', '#E6DA00');
		addFontColorMenuItem('Brown', '#736357');
		return menuList;
	},
	
	generateBackColorMenu : function () {
		var menuList = document.createElement('ul');
		
		function addBackColorMenuItem(label, color) {
    		var item = document.createElement('li');
    		var a = document.createElement('a');
    		
    		a.className = 'mlink';
    		a.href = '#';
    		a.onclick = function() { cur_editor.dropdownCommand('backcolor', color); return false; };
    		
    		var span = document.createElement('span');
    		span.innerHTML = '&nbsp;&nbsp;' + label + '&nbsp;&nbsp;';
    		span.style.backgroundColor = color;
    		a.appendChild(span);
    		
    		item.appendChild(a);
    		menuList.appendChild(item);
		};
		
		addBackColorMenuItem('White', '#FFFFFF');
		addBackColorMenuItem('Gray', '#EAEAEA');
		addBackColorMenuItem('Green', '#DDF0D3');
		addBackColorMenuItem('Blue', '#C7ECF5');
		addBackColorMenuItem('Red', '#FCE2DB');
		addBackColorMenuItem('Purple', '#D5CAE7');
		addBackColorMenuItem('Yellow', '#FFF7D3');
		addBackColorMenuItem('Brown', '#E5D5C5');
		return menuList;
	}
};

/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}

var Print = { 
	open : function(href, menu) {
		window.open(href, 'popupwindow', 'width='+(winX - 100)+',height='+winY+',scrollbars,resizable' + (menu ? ',menubar=yes' : ''));
		return false;
	}, 
	doPrint : function () {
		parent.topic.focus();
		parent.topic.print();
		// the first time you print in firefox (or mozilla), immediately executing parent.close() will 
		// result in the page just closing with no print; oddly, if you open the print dialogue again, 
		// the document will print then close the window correctly
		// setting a timeout on the close allows the browser to popup the "print" dialogue before parent.close()
		window.setTimeout("Print.doClose()", 2000);
	},
	doClose : function () {
		parent.close();	
	},
	createEndnotes : function () {
		var elt = document.getElementById('topic');
		var endnotes = '';
		var j = 0;
		var links = elt.getElementsByTagName('a');
		for (i = 0; i < links.length; i++ ) {
			if (links[i].getAttribute('href')) {
				j++;
				var sup = document.createElement('sup');
				sup.className = 'endnotes';
				sup.innerHTML = j;
				links[i].appendChild(sup);
				endnotes += '<sup>'+(j)+'</sup> '+links[i].getAttribute('href')+'<br/>';
			}
		}	
		var div = document.createElement('div');
		div.setAttribute('id', 'endnotes');
		div.setAttribute('class', 'print-toc');
		elt.appendChild(div);
		div.innerHTML = '<h5>Endnotes</h5><div><small>'+endnotes+'</small></div>';
	},
	showEndnotes : function (displayType) {
		var endnotes = parent.topic.document.getElementsByTagName('sup');
		for (i = 0; i < endnotes.length; i++ ) {
			if (endnotes[i].className == 'endnotes') {
				endnotes[i].style.display = displayType == 'show' ? 'inline': 'none';
			}	
		}
		var endnotesBody = parent.topic.document.getElementById('endnotes');
		if (endnotesBody) {
			endnotesBody.style.display = displayType == 'show' ? 'block' : 'none';
		}
	}, 
	onBodyLoad : function () {		
	    Print.createEndnotes();	    
	    if (readCookie('printlinks') == 0) {
	   	 	Print.showEndnotes('none');
		}
	    if (readCookie('printtoc') == 0) {
		   document.getElementById('content-toc').style.display = 'none';
	    }
	    if (readCookie('printfooter') == 0) {
		    document.getElementById('printfooter').style.display = 'none';
	    }	
	}, 
	options : function () {	
		var frameset = parent.topic;
		var printtoc = document.getElementById('print-toc');
		var toc = frameset.document.getElementById('content-toc');
		var printfooter = document.getElementById('print-footer');
		var footer = frameset.document.getElementById('printfooter');
		var printlinks = document.getElementById('print-links');
		
		createCookie('printtoc', (printtoc.checked) ? 1: 0, 365);
		createCookie('printfooter', (printfooter.checked) ? 1: 0, 365);
		createCookie('printlinks', (printlinks.checked) ? 1: 0, 365);
		
		if (printtoc && toc)
			toc.style.display = (printtoc.checked) ? 'block': 'none';
		if (printfooter && footer)
			footer.style.display = (printfooter.checked) ? 'block': 'none';
		if (printlinks) {
			if (printlinks.checked) {
				Print.showEndnotes('show');
			}	
			else {
				Print.showEndnotes('hide');
			}
		}	
	}
};

function saveToPDF (page) {
	var tm = document.getElementById ('tmargin');
	var rm = document.getElementById ('rmargin');
	var bm = document.getElementById ('bmargin');
	var lm = document.getElementById ('lmargin');
	var ls = document.getElementById ('landscape');

	var args = "&action=export&type=pdf";
	
	if (ls.checked) 
		args += "&landscape=true"; 
	
	if (tm.value != null && tm.value != "")
		args += "&tm=" + tm.value; 
	if (rm.value != null && rm.value != "")
		args += "&rm=" + rm.value; 
	if (bm.value != null && bm.value != "")
		args += "&bm=" + bm.value; 
	if (lm.value != null && lm.value != "")
		args += "&lm=" + lm.value; 

	parent.topic.location = page + '&action=export&type=pdf' + args;
}

prefsShow = function (divid) {
	var arrow = document.getElementById('prefs-'+divid+'_button');
	var content = document.getElementById('prefs-'+divid+'_content');
	
	if (content.style.display == 'none') {
		content.style.display = 'block';
		var atype = 'show';
	}
	else {
		content.style.display = 'none';
		var atype = 'hide';	
	}
	arrow.className = (arrow.className == 'buttonify show') ? 'buttonify hide': 'buttonify show';
	x_wfStorePrefsStatus(divid, atype, cb_blank);
};

prefsInfo = function (divid, subdivid, type) {
	if (subdiv = document.getElementById(divid+'-'+subdivid)) {
		var div = document.getElementById(divid+'-msg');
		if (type == 'show') {
			div.style.display = 'block';
			subdiv.style.display = 'block';	
		}
		else {
			div.style.display = 'none';
			subdiv.style.display = 'none';	
		}
	}
};
var ttTimeout = null;
var _ttwidth = 300; //width of the topic tip (set in CSS)
var curTT = null;

ttOn = function(obj, lyr) {
	if (ttTimeout)
		clearTimeout(ttTimeout);
	var dimensions = Element.getDimensions(obj);
	ttTimeout = window.setTimeout("ttDisplay('"+lyr+"', "+findPosY(obj)+", "+findPosX(obj)+", "+dimensions.height+", "+dimensions.width+")", 400);
};

ttDisplay = function(lyr, vertOffset, horizOffset, linkHeight, linkWidth) {
	var tt = document.getElementById(lyr);
	
	var pageOffset = 0;
	if (!tt) return false;
	if (ttTimeout != null)	window.clearTimeout(ttTimeout);
	ttTimeout = null;
	curTT = tt;
	
	var dimensions = Element.getDimensions(tt);
	var ttWidth = dimensions.width;
	var ttHeight = dimensions.height;
	
	//do horizontal flipping
	if (typeof(winX) != 'object' && (horizOffset + ttWidth) > winX) {
		horizOffset = horizOffset - ttWidth;
	} 	
	pageOffset = getPageYOffset();
	
	//do vertical flipping
	var vertOffset = ((vertOffset + ttHeight) > (pageOffset + winY)) ? vertOffset - ttHeight : vertOffset + linkHeight;
	
	tt.width = dimensions.width + 'px';
	tt.style.position = 'absolute';
	tt.style.top = vertOffset + 'px';
	tt.style.left = horizOffset + 'px';
	tt.style.display = 'block';
	
};

function clearTT() {
    if (ttTimeout)
        clearTimeout(ttTimeout);
    ttTimeout = null;
    if (curTT) {
        curTT.style.display = 'none';
    }
    curTT = null;
};

ttOff = function(lyr) {
	if (ttTimeout)
		clearTimeout(ttTimeout);
	ttTimeout = null;
	if (tt = document.getElementById(lyr)) {
	    if (curTT == tt)
	       curTT = null;
		tt.style.display = 'none';
	}
};

var imageTT = null;
var imageTTDont = false;

function imageOptions_cb(content) {
    if (content == "")
        return;
    imageOption_hide();
    if (imageTTDont)
        return;
    // $at_id."\n".$timestamp."\n".$file->at_user_text."\n".$file->at_name.".".$file->at_extension."\n".$file->at_filesize
    var fields = content.split("\n");
    var at_id = fields[0];
    var at_timestamp = fields[1];
    var at_user_text = fields[2];
    var at_name = fields[3];
    var at_filesize = fields[4];
    var imageEl = document.getElementById("imgSrc" + at_id);

    imageTT = document.createElement('div');
	imageTT.style.display = 'block';
	imageTT.style.position = 'absolute';
	var top = findPosY(imageEl) + imageEl.offsetHeight;
	var left = findPosX(imageEl) - 27;
	imageTT.style.top = top + 'px';
	imageTT.style.zIndex = 999;
	imageTT.style.left = left + 'px';
	imageTT.innerHTML = "<div class='ttshadow'><div class='tt_top'></div><div class='tt_content'><div><table><tbody>"+
	   "<tr><td><strong>Name:</strong></td><td>"+at_name+"</td></tr>"+
	   "<tr><td><strong>Size:</strong></td><td>"+at_filesize+"</td></tr>"+
	   "<tr><td><strong>Date:</strong></td><td>"+at_timestamp+"</td></tr>"+
	   "<tr><td><strong>Added by:</strong></td><td>"+at_user_text+"</td></tr>"+
	   "</tbody></table></div></div><div class='tt_bottom'></div></div>";
    imageEl.parentNode.appendChild(imageTT);
}

function imageOption_hide() {
    if (imageTT != null) {
        imageTT.parentNode.removeChild(imageTT);
        imageTT = null;
    }
}

function imageOptions(action, at_id) {
    if (action == 'show') {
        imageTTDont = false;
        x_wfGetImageDescription(at_id, imageOptions_cb)
    } else {
        imageTTDont = true;
        imageOption_hide();
    }
}

// Browser detection
var nav = navigator.userAgent.toLowerCase();
var ie6=0; ie5=0; moz5=0; moz0=0; opera=0; needConfirm=0; confirmFlag = 1;
if (nav.indexOf("win") != -1) {
	var pos = nav.indexOf("msie");
	var v = nav.charAt(pos + 5);
	if (pos != -1) {
		if (v >= 6) {
			ie6 = 1;
			ie5 = 1;

		}

		else if (v >= 5)
			ie5 = 1;
	}
}
if (nav.indexOf("compatible") == -1) {
	if (nav.charAt(8) >= 5 && nav.indexOf("7.02") == -1) {
		moz5 = 1;
		moz0 = 1;
	}
}
if (nav.indexOf("opera") != -1) opera=1;
/**
 * COMMON DHTML FUNCTIONS
 * These are handy functions I use all the time.
 *
 * By Seth Banks (webmaster at subimage dot com)
 * http://www.subimage.com/
 *
 * Up to date code can be found at http://www.subimage.com/dhtml/
 *
 * This code is free for you to use anywhere, just keep this comment block.
 */

/**
 * X-browser event handler attachment and detachment
 *
 * @argument obj - the object to attach event to
 * @argument evType - name of the event - DONT ADD "on", pass only "mouseover", etc
 * @argument fn - function to call
 */
function addEvent(obj, evType, fn){
 if (obj.addEventListener){
    obj.addEventListener(evType, fn, true);
    return true;
 } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, fn);
    return r;
 } else {
    return false;
 }
}
function removeEvent(obj, evType, fn, useCapture){
  if (obj.removeEventListener){
    obj.removeEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.detachEvent){
    var r = obj.detachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be removed");
  }
}

/**
 * Code below taken from - http://www.evolt.org/article/document_body_doctype_switching_and_more/17/30655/
 *
 * Modified 4/22/04 to work with Opera/Moz (by webmaster at subimage dot com)
 *
 * Gets the full width/height because it's different for most browsers.
 */
function getViewportHeight() {
	if (window.innerHeight!=window.undefined) return window.innerHeight;
	if (document.compatMode=='CSS1Compat') return document.documentElement.clientHeight;
	if (document.body) return document.body.clientHeight; 
	return window.undefined; 
}
function getViewportWidth() {
	if (window.innerWidth!=window.undefined) return window.innerWidth; 
	if (document.compatMode=='CSS1Compat') return document.documentElement.clientWidth; 
	if (document.body) return document.body.clientWidth; 
	return window.undefined; 
}
/**
 * POPUP WINDOW CODE v1.1
 * Used for displaying DHTML only popups instead of using buggy modal windows.
 *
 * By Seth Banks (webmaster at subimage dot com)
 * http://www.subimage.com/
 *
 * Contributions by Eric Angel (tab index code) and Scott (hiding/showing selects for IE users)
 *
 * Up to date code can be found at http://www.subimage.com/dhtml/subModal
 *
 * This code is free for you to use anywhere, just keep this comment block.
 */

// Popup code
var gPopupMask = null;
var gPopupContainer = null;
var gPopFrame = null;
var gPopupDoc = null;
var gReturnFunc;
var gPopupIsShown = false;

var gHideSelects = false;


var gTabIndexes = new Array();
// Pre-defined list of tags we want to disable/enable tabbing into
var gTabbableTags = new Array("A","BUTTON","TEXTAREA","INPUT","IFRAME");	

// If using Mozilla or Firefox, use Tab-key trap.
if (!document.all) {
	document.onkeypress = keyDownHandler;
}

/**
 * Initializes popup code on load.	
 */
function initPopUp() {
	gPopupMask = document.getElementById("popupMask");
	gPopupContainer = document.getElementById("popupContainer");
	gPopFrame = document.getElementById("popupFrame");
	
	// check to see if this is IE version 6 or lower. hide select boxes if so
	// maybe they'll fix this in version 7?
	var brsVersion = parseInt(window.navigator.appVersion.charAt(0), 10);
	if (brsVersion <= 6 && window.navigator.userAgent.indexOf("MSIE") > -1) {
		gHideSelects = true;
	}
};
//addEvent(window, "load", initPopUp);

 /**
	* @argument width - int in pixels
	* @argument height - int in pixels
	* @argument url - url to display
	* @argument returnFunc - function to call when returning true from the window.
	*/

function showPopWin(url, width, height, returnFunc) {
	// >MT: tomk: force some padding to the right of the content for non-IE
	if (!HTMLArea.is_ie)
		width += 10;
	// <MT

	if (!gPopupMask) initPopUp();
	if (!gPopupMask) {
debugger;
		return;
	}
	gPopupIsShown = true;
//	disableTabIndexes();
	gPopupMask.style.display = "block";
	gPopupContainer.style.display = "block";
	// calculate where to place the window on screen
	centerPopWin(width, height);

	var titleBarHeight = parseInt(document.getElementById("popupTitleBar").offsetHeight, 10);
	
	gPopupContainer.style.width = width + "px";
	gPopupContainer.style.height = (height+titleBarHeight) + "px";
	// need to set the width of the iframe to the title bar width because of the dropshadow
	// some oddness was occuring and causing the frame to poke outside the border in IE6

	// >MT: tomk: force some padding to the right of the content for non-IE
	frameWidth = parseInt(document.getElementById("popupTitleBar").offsetWidth, 10) - 12;
	if (!HTMLArea.is_ie)
		frameWidth -= 10;
	gPopFrame.style.width = frameWidth + "px";
	// <MT
	gPopFrame.style.height = (height) + "px";
	
	// set the url
	gPopFrame.src = url;

	gReturnFunc = returnFunc;
	// for IE
	if (gHideSelects)
		hideSelectBoxes();
	
  document.getElementById("popupTitle").innerHTML = "";
	window.setTimeout(function () { setPopTitle(); }, 600);
};

function setPopWinHTML(html) {
    try {
        gPopupDoc = gPopFrame.contentDocument ? gPopFrame.contentDocument : gPopFrame.contentWindow.document;
        if (!gPopupDoc) { // try later
            if (HTMLArea.is_gecko) {
                setTimeout(function() { setPopWinHTML(html) }, 50);
                return false;
            } else
                alert("ERROR: IFRAME can't be initialized.");
        }
    } catch(e) {
        setTimeout(function() { setPopWinHTML(html) }, 50);
        return false;
    }

    gPopupDoc.write(html);
    gPopupDoc.close();
    return true;
};

//
var gi = 0;
function centerPopWin(width, height) {
	if (gPopupIsShown) {
		if (width == null || isNaN(width)) {
			width = gPopupContainer.offsetWidth;
		}
		if (height == null) {
			height = gPopupContainer.offsetHeight;
		}
		
		var fullHeight = getViewportHeight();
		var fullWidth = getViewportWidth();
		
		var theBody = document.documentElement;
		if (!theBody.scrollTop && document.body)
			theBody = document.body;
		
		var scTop = parseInt(theBody.scrollTop,10);
		var scLeft = parseInt(theBody.scrollLeft,10);
		
		gPopupMask.style.height = fullHeight + "px";
		gPopupMask.style.width = fullWidth + "px";
		gPopupMask.style.top = scTop + "px";
		gPopupMask.style.left = scLeft + "px";
		
		window.status = gPopupMask.style.top + " " + gPopupMask.style.left + " " + gi++;
		
		var titleBarHeight = parseInt(document.getElementById("popupTitleBar").offsetHeight, 10);
		
		gPopupContainer.style.top = (scTop + ((fullHeight - (height+titleBarHeight)) / 2)) + "px";
		gPopupContainer.style.left =  (scLeft + ((fullWidth - width) / 2)) + "px";
		//alert(fullWidth + " " + width + " " + gPopupContainer.style.left);
	}
}
addEvent(window, "resize", centerPopWin);
//addEvent(window, "scroll", centerPopWin);
window.onscroll = centerPopWin;

/**
 * @argument callReturnFunc - bool - determines if we call the return function specified
 * @argument returnVal - anything - return value 
 */
function hidePopWin(callReturnFunc) {
	gPopupIsShown = false;
	restoreTabIndexes();
	if (!gPopupMask)
		return;
    gPopupMask.style.display = "none";
	gPopupContainer.style.display = "none";
	document.getElementById("popupTitle").innerHTML = "";
	if (callReturnFunc && gReturnFunc != null)
		gReturnFunc(gPopFrame.returnVal);
	gPopFrame.src = '/editor/popups/loading.html';
	// display all select boxes
	if (gHideSelects)
		displaySelectBoxes();
};

/**
 * Sets the popup title based on the title of the html document it contains.
 * Uses a timeout to keep checking until the title is valid.
 */
function setPopTitle() {
    try {
        gPopupDoc = gPopFrame.contentDocument ? gPopFrame.contentDocument : gPopFrame.contentWindow.document;
        if (gPopupDoc && gPopupDoc.title)
            document.getElementById("popupTitle").innerHTML = gPopupDoc.title;
        else
            window.setTimeout(function() { setPopTitle(); }, 100);
    } catch (e) {
        window.setTimeout(function() { setPopTitle(); }, 100);
    }
};

// Tab key trap. iff popup is shown and key was [TAB], suppress it.
// @argument e - event - keyboard event that caused this function to be called.
function keyDownHandler(e) {
    if (gPopupIsShown && e.keyCode == 9)  return false;
};

// For IE.  Go through predefined tags and disable tabbing into them.
function disableTabIndexes() {
	if (document.all) {
		var i = 0;
		for (var j = 0; j < gTabbableTags.length; j++) {
			var tagElements = document.getElementsByTagName(gTabbableTags[j]);
			for (var k = 0 ; k < tagElements.length; k++) {
				gTabIndexes[i] = tagElements[k].tabIndex;
				tagElements[k].tabIndex="-1";
				i++;
			}
		}
	}
};

// For IE. Restore tab-indexes.
function restoreTabIndexes() {
	if (document.all) {
		var i = 0;
		for (var j = 0; j < gTabbableTags.length; j++) {
			var tagElements = document.getElementsByTagName(gTabbableTags[j]);
			for (var k = 0 ; k < tagElements.length; k++) {
				tagElements[k].tabIndex = gTabIndexes[i];
				tagElements[k].tabEnabled = true;
				i++;
			}
		}
	}
};

/**
* Hides all drop down form select boxes on the screen so they do not appear above the mask layer.
* IE has a problem with wanted select form tags to always be the topmost z-index or layer
*
* Thanks for the code Scott!
*/
function hideSelectBoxes() {
	for(var i = 0; i < document.forms.length; i++) {
		for(var e = 0; e < document.forms[i].length; e++){
			if(document.forms[i].elements[e].tagName == "SELECT") {
				document.forms[i].elements[e].style.visibility="hidden";
			}
		}
	}
};

/**
* Makes all drop down form select boxes on the screen visible so they do not reappear after the dialog is closed.
* IE has a problem with wanted select form tags to always be the topmost z-index or layer
*/
function displaySelectBoxes() {
	for(var i = 0; i < document.forms.length; i++) {
		for(var e = 0; e < document.forms[i].length; e++){
			if(document.forms[i].elements[e].tagName == "SELECT") {
			document.forms[i].elements[e].style.visibility="visible";
			}
		}
	}
};

/*
 * MindTouch Deki - a commercial grade open source wiki
 *  derived from MediaWiki (www.mediawiki.org)
 * Copyright (C) 2006 MindTouch, Inc.
 * http://www.mindtouch.com/  oss@mindtouch.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

if (typeof _script_path == "undefined") _script_path = "/";
if (typeof _page_titleName == "undefined") _page_titleName = "Sandbox";
if (typeof _page_titlePath == "undefined") _page_titlePath = "/";
if (typeof _path_skin == "undefined") _path_skin = "/skin/ace";

xinha_editors = null;
xinha_init = null;
xinha_init_adv = null;
xinha_init_simple = null;
xinha_config = null;
xinha_plugins = null;
start_editor  = false;
content_editors = null;
title = null;
sectiontoedit = null;
oldcontent = null;
cur_section = null;
cur_editor = null;
did_quicksave = false;

var width = null;
var height = '';
var prevHTML = '';
var saveFlag = false;

var useMTDialogs = true;

function getWindowSize() {
  if (parseInt(navigator.appVersion)>3) {
    // Can't use HTMLArea.is_ie, not defined yet for some reason
    if (navigator.appName=="Netscape") {
      width = window.innerWidth;
      height = window.innerHeight;
    }
    if (navigator.appName.indexOf("Microsoft")!=-1) {
      width = document.documentElement.offsetWidth;
      height = document.documentElement.offsetHeight;
    }
  }
};
getWindowSize();

xinha_init = xinha_init ? xinha_init : function() {
  //hide the box
  wait = document.getElementById('formLoading');

  if (xinha_config == null) {
    xinha_config = new HTMLArea.Config();
    
    xinha_config.URIs = {
      "blank":        "popups/blank.html",
      "link":         "link.html",
      "insert_image": "insert_image.html",
      "insert_table": "insert_table.html",
      "select_color": "select_color.html",
      "forecolor":    "select_color.php?type=fg",
      "hilitecolor":  "select_color.php?type=bg",
      "mostemplates": "select_template.php",
      "moswidgets":   "/dream/widget/select/",
      "about":        "about.html"
    };
  }

  xinha_config.mozParaHandler = 'built-in'; // set to 'built-in', 'dirty' or 'best'
  xinha_config.stripBaseHref = false;
  xinha_config.baseHref = baseHref;
  xinha_config.formatblock = {
    "Normal" :    "",
    "Title":      "h1",
    "Heading 1":  "h2",
    "Heading 2":  "h3",
    "Heading 3":  "h4",
    "Heading 4":  "h5",
    "Heading 5":  "h6",
    "Normal":     "p",
    "Blockquote": "blockquote",
    "Formatted":  "pre"
  };

  xinha_config.fontsize = {
    "&mdash; size &mdash;"  : "mixed",
    "Small":  "-2",
    "Normal": "0",
    "Big":    "+2",
    "Huge":   "+3"
  };

  xinha_config.fontname = {
    "&mdash; font &mdash;": '',
    "Courier New":      'courier new,courier,monospace',
    "Garamond":         'garamond|small-caps',
    "Impact":           'impact',
    "Times New Roman":  'times new roman,georgia,times,serif',
    "Verdana":          'verdana,arial,helvetica,sans-serif'
  };
  
  xinha_config.fontstyles = {
    "&mdash; styles &mdash;": "mixed",
    "Normal":     "normal",
    "Important":  "edimportant",
    "Highlight":  "edhighlight",
    "Subtle":     "edsubtle", 
    "Code":       "edcode",
    "Plain": "plain"
  };
  
  xinha_config.registerButton(
    "simple", "Simple", _script_path+"editor/images/ed_toggle.gif", false,
    function(editor) { switchToSimple(editor); }
  );

  xinha_config.registerButton(
    "advanced", "Advanced", _script_path+"editor/images/ed_toggle.gif", false,
    function(editor) { switchToAdvanced(editor); }
  );

  xinha_config.registerButton(
    "mostemplates", "Templates", _script_path+"editor/images/ed_template.gif", false,
    function(editor) { popupTemplates(400, 110, editor); }
  );
 
  if (_editor_dream_enabled) {
    xinha_config.registerButton(
      "moswidgets", "Widgets", _script_path+"editor/images/ed_widget.gif", false,
      function(editor) { popupWidgets(400, 110, editor); }
    );
  }
  else {
    
  }
  
  xinha_config.registerButton(
    "mksSave", _lang_tooltip_save, _path_skin+"/save_18.gif", false,
    function(editor) {
      okToLeave();
      doSave();
      document.editform.submit();
    }
  );
  
  xinha_config.registerButton(
    "mksCancel", _lang_tooltip_cancel, _path_skin+"/back_18.gif", false,
    function(editor) {
      if (!doCancelEdit(true))
        return;
      if (typeof _cancel_url != 'undefined')
        window.location = _cancel_url;
      else if (typeof _staticEditor != 'undefined' && _staticEditor)
        window.location = mt_gen.getUrlFromName(document.getElementById('wpOldTitle').value);
	  else if (document.editform)
	    document.editform.submit();
    }
  );
  
  xinha_config.registerButton(
    "mksQuicksave", _lang_tooltip_saveminor, _path_skin+"/save_18.gif", false,
    function(editor) {
      okToLeave();
      doSaveMinor();
    }
  );
  
  xinha_config.registerButton(
    "mksPreview", _lang_tooltip_preview, _path_skin+"/srch_18.gif", false,
    function(editor) {
      okToLeave();
      setHiddenSubmit('wpPreviewH');
      var html = cur_editor.getHTML();
      html = stripTitle(html);
      cur_editor.setHTML(html);
      document.getElementById('editarea').value = html;
      document.editform.submit();
    }
  );
  
  xinha_config.registerButton("mksLink", "Insert/modify link", _path_skin+"/ed_link.gif", false,
    function(editor) { cur_editor._createLinkMT(); }
  );
  
  xinha_config.registerButton("mksInsertImage", "Insert image", _path_skin+"/ed_ins_img.gif", false,
    function(editor) { cur_editor._insertImageMT(); }
  );
  
  xinha_config.registerButton("mksFont", "Font", _path_skin+"/font_18.gif", false,
    function(editor) { EditorMenu.show('font'); }
  );
  
  xinha_config.registerButton("mksSize", "Font Size", _path_skin+"/fontsize_18.gif", false,
    function(editor) { EditorMenu.show('fontsize'); }
  );
  
  xinha_config.registerButton("mksAlign", "Align", _path_skin+"/align_18.gif", false,
    function(editor) { EditorMenu.show('align'); }
  );
  
  xinha_config.registerButton("mksFontColor", "Font Color", _path_skin+"/fontcolor_18.gif", false,
    function(editor) { EditorMenu.show('fontcolor'); }
  );
  
  xinha_config.registerButton("mksBackColor", "Background Color", _path_skin+"/backcolor_18.gif", false,
    function(editor) { EditorMenu.show('backcolor'); }
  );
  
  if (typeof(_editor_styles_path) != 'undefined')
    xinha_config.pageStyleSheets = [_editor_styles_path];

  createConfigToolBar(xinha_config,advancedEditor);

  xinha_config.width = clientBrowser.isIe ? '99%': '100%'; //IE will push the whole editor down otherwise
  xinha_config.height = getEditorHeight(null) + "px";
  function doneLoading() {
    startContentEditor();
    if (wait)
      wait.className = 'show-no';
  };

  if (!createPlugins(advancedEditor, doneLoading))
    return;
  doneLoading();
};

function createPlugins(advancedEditor, callbackIfNotReady) {
  xinha_plugins = xinha_plugins ? xinha_plugins : true ? [
    'MindTouch',
    'ContextMenu',
    'FullScreen',
    'SpellChecker',
    'Stylist',
    'TableOperations'
  ] : [
    'MindTouch',
    'FullScreen',
    'SpellChecker',
    'Stylist'
  ];

  return HTMLArea.loadPlugins(xinha_plugins, callbackIfNotReady);
};

function getOffsetTop(item) {
  return getRecursiveProperty(item,"offsetTop");
};

function getRecursiveProperty(item,prop) {
  var ret = 0;
  while(item) {
    ret += item[prop];
    try {
      item = item.offsetParent;
    } catch (e) { item = null; }
  }
  return ret;
};

function getEditorHeight(editor) {
  getWindowSize();
  if (!editor || document.getElementById('isConflict') || cur_section != null)
    return 400;
  var offsetTop = getOffsetTop(editor._htmlArea);
  var newHeight = height - offsetTop - (HTMLArea.is_ie ? 5 : 12);
  if (newHeight < 400)
    newHeight = 400;
  return newHeight;
};

function switchToSimple(editor) {
  advancedEditor = false;
  if (typeof x_wfSaveEditor != 'undefined') x_wfSaveEditor('0', function() {});
  recreatedToolbar(editor);
};

function switchToAdvanced(editor) {
  advancedEditor = true;
  if (typeof x_wfSaveEditor != 'undefined') x_wfSaveEditor('1', function() {});
  recreatedToolbar(editor);
};

function recreatedToolbar(editor) {
  xinha_plugins = null;
  if (!createPlugins(advancedEditor, function() {recreatedToolbar(editor);}))
    return;
  editor.disableToolbar();
  var tb_cell = editor._framework.tb_cell;
  HTMLArea.removeFromParent(editor._toolBar);
  createConfigToolBar(editor.config,advancedEditor);
  editor.registerPlugins(xinha_plugins);
  for (var i in editor.plugins) {
    var plugin = editor.plugins[i].instance;
    HTMLArea.refreshPlugin(plugin);
  }
  var toolbar = editor._createToolbar();
  editor._framework.tb_cell.appendChild(toolbar);
  editor.enableToolbar();
};

function createConfigToolBar(config,adv) {
  config.toolbar =
  [
    ["mksSave","linebreak","mksCancel"],
    ["separator","undo","redo","removeformat","linebreak","mksQuicksave","htmlmode"],
    ["separator","label:Style:","formatblock","mksFont","mksSize","linebreak","bold","italic","underline","strikethrough","mksFontColor","mksBackColor"],
    ["separator","insertorderedlist","insertunorderedlist","mksAlign","linebreak","outdent","indent"],
    ["separator","mksLink","mksInsertImage","moswidgets","linebreak","inserttable","mostemplates"]
  ];
  
  if (!_editor_dream_enabled)
    config.toolbar[4].splice(3, 1);  // super-hack to remove "moswidgets" from the toolbar
  
  /*if (!adv) {
    config.toolbar =
    [
      ["mksSave","label:mksSave|"+_lang_label_save,"linebreak","mksCancel","label:mksCancel|"+_lang_label_cancel,"linebreak"],
      ["separator","formatblock"],
      ["separator","bold","italic","underline","linebreak","strikethrough"],
      ["separator","createlink","linebreak"],
      ["separator","undo","redo","mksQuicksave","removeformat","linebreak","htmlmode","advanced","mksPreview"]
    ];
  }*/
  
};

function cleanStyles(el,fn) {
  if (fn(el)) {
    HTMLArea.removeTag(el);
    return true;
  }
  var c = el.firstChild;
  while (c != null) {
    var next = c.nextSibling;
    if (c.nodeType == 1 && cleanStyles(c,fn))
      next = el.firstChild;
    c = next;
  }
  return false;
};

var convertStyleToSizeID = {
  normal      : "0",
  edimportant : 2,
  edhighlight : 3,
  edsubtle    : 4,
  edcode      : 5,
  plain       : 6
};

var convertSizeIDToStyle = [
  /*0*/ 'normal',
  /*1*/ 'xxx',
  /*2*/ 'edimportant',
  /*3*/ 'edhighlight',
  /*4*/ 'edsubtle',
  /*5*/ 'edcode',
  /*6*/ 'plain'
];

function doApplyFontStyles(editor, txt, value,isArray, tag, text) {
  if (value == "0") {
    if ((!isArray && editor.getHTMLText(HTMLArea.getOuterHTML(tag)) == editor.getHTMLText(text)) || HTMLArea.is_gecko) 
      editor.execCommand(txt, false, HTMLArea.is_ie ? "<>" : "+0");
    else if (isArray) {
      applyFontSize(editor, txt, value);
      editor.execCommand(txt, false, HTMLArea.is_ie ? "<>" : "+0");
    } else
      editor.splitOutRange(tag, editor._createRange(editor._getSelection()));
  } else
    applyFontSize(editor, txt, value);
};

function updateFontStyles(editor, value) {
  var text = editor.getSelectedHTML();
  var tags = getFontStyleTags(editor);
  if (text == "" && value == "normal") {
    editor.selectWord();
    text = editor.getSelectedHTML();
    tags = getFontSizeTags(editor);
  }
  var r = editor.getRange();
  var isArray = typeof tags.tags != "undefined";
  var root = isArray ? tags.root : tags.tag.parentNode;

  // replace font size into span tags (move away tags to make room)
  HTMLArea.replaceTagsF(root,function (el) {
    if (!/^font$/i.test(el.tagName) || el.size == "")
      return null;
    var ret = editor._doc.createElement("span");
    ret.className = '_font_size|' + el.size;
    if (tags.tag == el)
      tags.tag = ret;
    return HTMLArea.cloneNode(el,ret);
  });
  // replace font styles into font size tags (prepare for native use)
  HTMLArea.replaceTagsF(root,function (el) {
    if (!/^span$/i.test(el.tagName) || !/(edimportant|edhighlight|edsubtle|edcode|plain)/i.test(el.className))
      return null;
    
    var ret = editor._doc.createElement("font");
    ret.size = convertStyleToSizeID[el.className];
    if (tags.tag == el)
      tags.tag = ret;
    return HTMLArea.cloneNode(el,ret);
  });

  editor.setRange(r);
  doApplyFontStyles(editor,"fontsize",convertStyleToSizeID[value],isArray,tags.tag,text);
  
  // replace font size into font style tags (store result of native use into wished tags)
  HTMLArea.replaceTagsF(root,function (el) {
    if (!/^font$/i.test(el.tagName) || el.size == "")
      return null;
    var ret = editor._doc.createElement("span");
    ret.className = convertSizeIDToStyle[el.size];
    return HTMLArea.cloneNode(el,ret);
  });
  // replace span into font size tags (move back from hiding)
  HTMLArea.replaceTagsF(root,function (el) {
    if (!/^span$/i.test(el.tagName) || !/_font_size.+/i.test(el.className))
      return null;
    
    var ret = editor._doc.createElement("font");
    ret.size = el.className.substr('_font_size|'.length);
    return HTMLArea.cloneNode(el,ret);
  });
  editor.setRange(r);
  editor.updateToolbar();
};

function getFontSizeTags(editor) {
  return editor.getSelectionTagsF("font",function(el) { return el.size != ""; });
};

function getFontStyleTags(editor) {
  return editor.getSelectionTagsF("span",function(el) { return el.className != ""; });
};

function fontSizes(editor) {
  var tags = getFontSizeTags(editor);
  if (typeof tags.tags != "undefined") // 'mixed' has a false positive if the same size is applied across multiple blocks
    return tags.tags.length > 0 ? "mixed" : "0";
  return tags.tag.size;
};

function fontStyles(editor) {
  var tags = getFontStyleTags(editor);
  if (typeof tags.tags != "undefined") // 'mixed' has a false positive if the same style is applied across multiple blocks
    return tags.tags.length > 0 ? "mixed" : "normal";
  return tags.tag.className;
};

function applyFontSize(editor, txt, value) {
  editor._doc.execCommand(txt, false, HTMLArea.is_ie ? "1" : value);
  if (HTMLArea.is_ie) {
    var sel = editor._getSelection();
    var range = editor._createRange(sel);
    var rangeEl = editor.getParentElement(sel,range);
    var els = rangeEl.getElementsByTagName('font');
    if (els.length == 0)
      els = [rangeEl];
    for (var i = 0; i < els.length; ++i) {
      var el = els[i];
      if (el.size == 1)
        el.size = value;
    }
  }
};

function updateFontSizes(editor, txt, value) {
  var text = editor.getSelectedHTML();
  var tags = getFontSizeTags(editor);
  if (text == "" && value == "0") {
    editor.selectWord();
    text = editor.getSelectedHTML();
    tags = getFontSizeTags(editor);
  }
  doApplyFontStyles(editor, txt, value, typeof tags.tags != "undefined", tags.tag, text);
  editor.updateToolbar();
};

function popupWidgets(width, height, editor) {
  var sel = editor._getSelection();
  var range = editor._createRange(sel);
  editor._popupDialog(width, height, editor.config.URIs["moswidgets"], function(param) {
    if (!param || !param.widgetHtml) {	// user must have pressed Cancel
      return false;
    }
    Widget.insertWidget(editor, param.widgetHtml, param.widgetID);
    if (!HTMLArea.is_ie) {
      // Toggle in and out of htmlmode after inserting a template
      // This works around the FF insert unable to backspace bug
      editor.execCommand("htmlmode");
      editor.execCommand("htmlmode");
    }
    return true;
  }, null);
}

function popupTemplates(width, height, editor) {
  var sel = editor._getSelection();
  var range = editor._createRange(sel);
  editor._popupDialog(width, height, editor.config.URIs["mostemplates"], function(param) {
    if (!param || (!param.f_template && !param.f_site)) {	// user must have pressed Cancel
      return false;
    }
    editor.focusEditor();
    var doc = editor._doc;
    var template = param.f_template;
    if (!template) {
      var siteTemplate = param.f_site;
      var siteName = siteTemplate.siteName;
      var tree = siteTemplate.tree;
      function createLink(topic,name) {
        return '<a class="site" href="#" title="'+topic+'" template="Template:'+topic+'">'+name+'</a>';
      }
      function createHtml(node) {
        var itemsHtml = '<ul>';
        for (var itemKey in node) {
          if (itemKey == "__value" || itemKey == "__path") continue;
          var item = node[itemKey];
          itemsHtml += '<li>' + createLink(siteName+'/'+item.__path,item.__value) + createHtml(item) + '</li>';
        }
        return itemsHtml == '<ul>' ? "" : itemsHtml + '</ul>';
      };
      template = '<ul><li>' + createLink(siteName,siteName);
      template += createHtml(tree);
      template += '</li></ul>';
    }
    editor.insertHTML(template);
    if (!HTMLArea.is_ie) {
      // Toggle in and out of htmlmode after inserting a template
      // This works around the FF insert unable to backspace bug
      editor.execCommand("htmlmode");
      editor.execCommand("htmlmode");
    }
    return true;
  }, null);
};

function removeFormat(editor) {
  function checkEmpty(el) {
    if (/^(a|span|b|strong|i|em|font|div)$/i.test(el.tagName) &&
        !el.firstChild)
      HTMLArea.removeFromParent(el);
  };
  function parseTree(root) {
    var tag = root.tagName.toLowerCase(), i, next;
    if (/^(a|span|b|strong|i|em|font|small|big|strike|del)$/i.test(root.tagName)) {
      HTMLArea.removeTag(root);
      return false;
    } else {
      for (i = root.firstChild; i; i = next) {
        next = i.nextSibling;
        if (i.nodeType == 1 && parseTree(i))
          checkEmpty(i);
      }
    }
    return true;
  };

  var html = editor.getSelectedHTML();
  var div = editor._doc.createElement("div");
  div.innerHTML = html;
  parseTree(div);
  if (div.innerHTML == html)
    return;
  html = div.innerHTML;
  var r = editor.getRange();
  HTMLArea.is_ie ? document.execCommand("Delete") : editor._createRange().deleteContents();
  editor.insertHTML(html);
  editor.setRange(r);
  editor.updateToolbar();
};

function getAttachmentUrl(id, aref) {
  if (typeof x_wfGetAttachmentUrl == 'undefined') return;
  x_wfGetAttachmentUrl(id, function(data) {
    if (data == '-' || data == '')
      return;
    MindTouch.setLink(aref, data);
  });
}

function startContentEditor() {
  if (!start_editor)
    return;
  
  content_editors = content_editors ? content_editors :
  [
    'editarea'
  ];
  if (document.getElementById('wpTextbox2'))
    content_editors.push('wpTextbox2');
  for (var i = 0; i < content_editors.length; ++i)
    if (!document.getElementById(content_editors[i]))
      return;
  xinha_config = xinha_config ? xinha_config : new HTMLArea.Config();
  content_editors = HTMLArea.makeEditors(content_editors, xinha_config, xinha_plugins);

  for(var i in content_editors) {
    var e = content_editors[i];
    if (i == 'editarea') cur_editor = e;
    if (typeof contextTopic != 'undefined') {
      e.contextTopic = contextTopic;
      e.contextTopicID = _page_ID;
    }
    if(document.compatMode && document.compatMode != 'BackCompat') {
      HTMLArea._addEvent(window, 'resize', function() {
        e.sizeEditor();
      });
    }
  }

  HTMLArea.startEditors(content_editors);
};

var editorStarting = false;
var wpEditTimeValue = null;
var wpSectionValue = null;

function doCheckChanges(editor) {
  if (editorStarting) {
    if (typeof clearTT == 'function') clearTT();

    if (cur_editor != editor)
      return;
    var wpEditTime = document.getElementById('wpEditTime');
    if (wpEditTime) wpEditTime.value = wpEditTimeValue;
    var wpSection = document.getElementById('wpSection');
    if (wpSection) wpSection.value = wpSectionValue;
    wpEditTimeValue = null;
    wpSectionValue = null;
    editorStarting = false;
  }
  if (!editor) 
    return false;
  if (typeof hex_md5 != 'function')
    return false;
  var cur = hex_md5(editor.getHTML());
  if (!saveFlag) {
    saveFlag = true;
    prevHTML = cur;
  }
  else if (prevHTML == cur)
    return true;
  return false;
};

function handleEdit_cb(responseContent) {
  oldcontent = sectiontoedit.innerHTML;
  var r = responseContent.indexOf('|');
  sectiontoedit.innerHTML = responseContent.substring(r+1);

  editorStarting = true;
  wpEditTimeValue = responseContent.substring(0,r);
  wpSectionValue = cur_section != null ? cur_section : '';

  cur_editor = null; content_editors = null; xinha_config = null; xinha_plugins = null;
  start_editor = true;
  new xinha_init;
};

function doSectionEdit_cb(responseContent) {
  if (typeof (sectiontoedit) != 'undefined') {
    handleEdit_cb(responseContent);
  }
};

function doLoadEditor_cb(responseContent) {
  sectiontoedit = document.getElementById("articleText");
  var title = $('title');
  title.style.display = 'none';
  handleEdit_cb(responseContent);
};

function doCancelEdit(check) {
  if (check) {
    var display = checkForChanges();
    if (display != null) {
      if (!confirm("Are you sure you want to navigate away from the editor?\n\n" + display + "\n\nPress OK to continue, or Cancel to stay on the current editor."))
        return false;
    }
  } else {
    x_wfStatistics('', 'edit-cancel', function(data) {});
  }
  if (sectiontoedit != null) {
    sectiontoedit.innerHTML = oldcontent;
    hookEditIcons();
    sectiontoedit = null;
  }
  saveFlag = false;
  var title = $('title');
  if (title.style.display == 'none') 
  	title.style.display = 'block';
  if (did_quicksave)
    window.location.reload();
  return true;
};

// Public entry point
function doLoadEditor() {
  cur_section = null;
  if (sectiontoedit != null)
    if (!doCancelEdit(true))
      return;
  title = contextTopic;
  x_loadInplaceEditor(contextTopic,doLoadEditor_cb);
};

// Public entry point
function doEditSection (page, section, sectionDivID, sectionAnchor) {
  cur_section = section;
  var newSection = document.getElementById(sectionDivID);
  if (sectiontoedit == newSection)
    return;
  if (sectiontoedit != null)
    if (!doCancelEdit(true))
      return;
  oldcontent = document.getElementById("articleText").innerHTML;
  title = page;
  sectiontoedit = newSection;
  x_doSectionEdit(section, page, doSectionEdit_cb);
};

function isNameChanged() {
  var html = HTMLArea.getHTML(cur_editor._doc.getElementsByTagName('body')[0], false, cur_editor);
  html = stripTitle(html);  // sets wpNewTitle based on page content

  var wpNewTitleName = document.getElementById('wpNewTitle').value;
  var wpOldTitleName = _page_titleName;
  return wpNewTitleName != wpOldTitleName;
};

function saveEntryInline() {
  if (doCheckChanges(cur_editor))
    return;
  var html = HTMLArea.getHTML(cur_editor._doc.getElementsByTagName('body')[0], false, cur_editor);
  html = stripTitle(html);
  var title = contextTopic;
  var time = document.getElementById('wpEditTime').value;
  var section = document.getElementById('wpSection').value;
  html = escape(html);
  var r = new RegExp("\\+","g");
  html = html.replace(r,'%2B');
  x_wfSaveEdit(title, html, time, section, cb_saveEntryInline);
};

function cb_saveEntryInline(responseContent) {
  if (responseContent == '') {
    document.getElementById('quicksavewait').style.display = 'none';
    alert('An unknown error has occured, quick save failed.');
    return;
  }
  var r = responseContent.split('|');
  var wpEditTime = document.getElementById('wpEditTime');
  if (wpEditTime) wpEditTime.value = r[0];
  document.getElementById('quicksavewait').style.display = 'none';
  if (r[1] != '1') {
    // Only show the fading text at bottom of editor, if the action succeeded, otherwise show the alert
    alert(r[2]);
  } else {
    displayInlineMsg('saveMsgSuccess');
    saveFlag = false;
    did_quicksave = true;
    doCheckChanges(cur_editor);
  }
};

function stripTitle(html) {
  if (cur_section != null)
    return html;
  var titleStart = html.indexOf('<h1>');
  var firstTag = html.indexOf('<');
  if (titleStart != firstTag)
    return html;
  var titleStop = html.indexOf('</h1>');
  if (titleStart == -1 || titleStop == -1 || titleStop - titleStart <= 0)
    return html;
  var title = html.substring(titleStart + 4, titleStop);
  var node = document.createElement('div');
  node.innerHTML = title;
  title = HTMLArea.getInnerText(node);
  document.getElementById('wpNewTitle').value = title;
  html = html.substring(titleStop + 5);
  return html;
};

function doSave() {
  setHiddenSubmit('wpSaveH');
  doShowSaveMessage();
  var html = cur_editor.getHTML();
  html = stripTitle(html);
  cur_editor.setHTML(html);
  document.getElementById('editarea').value = html;
};

function doSaveMinor() {
  if (isNameChanged()) {
      alert('Unable to quicksave because you have renamed the page.  You must Save.');
      return;
  }
  setHiddenSubmit('wpSaveMinorH');
  doShowSaveMessage();
  saveEntryInline();
  return false;
};

function okToLeave() {
  saveFlag = false;
};

function doShowSaveMessage() {
  document.getElementById('quicksavewait').style.display = 'block';
};

function doHideSaveMessage() {
  document.getElementById('quicksavedone').style.display = 'none';
};

function prependHTMLToToolbar(editor) {
  if (cur_editor != editor)
    return null;
  
  var sectionAndEditTime;
  if (document.getElementById('wpEditTime') == null)
    sectionAndEditTime = '<input type="hidden" name="wpSection" value="" id="wpSection" />'+
      '<input type="hidden" name="wpEdittime" value="" id="wpEditTime" />';
  else
    sectionAndEditTime = '';
  
  return _prependHTMLToToolbar() + sectionAndEditTime;
};

function addClick(a,stat) {
  HTMLArea.addDom0Event(a, 'click', function() {
    x_wfStatistics(a.href, stat, function(data) {
//      debugger;
        window.location = a.href;
    });
    return false;
  });
};

function hookClicks() {
  var as = document.getElementsByTagName("a");
  for (var i = as.length; i > 0; ) {
    var a = as[--i];
    if (a.href == "")
      continue;
    var stat = a.getAttribute('stat');
    if (stat)
      addClick(a,stat)
    //else
    //  debugger;
  }
};

/*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:--
--  Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
--
--  Use of Xinha is granted by the terms of the htmlArea License (based on
--  BSD license)  please read license.txt in this package for details.
--
--  Copyright (c) 2006 MindTouch, Inc.
--
--  Xinha was originally based on work by Mihai Bazon which is:
--      Copyright (c) 2003-2004 dynarch.com.
--      Copyright (c) 2002-2003 interactivetools.com, inc.
--      This copyright notice MUST stay intact for use.
--
--  Developers - Coding Style:
--   For the sake of not committing needlessly conflicting changes,
--
--   * New code to be indented with 2 spaces ("soft tab").
--   * New code preferably uses BSD-Style Bracing
--      if(foo)
--      {
--        bar();
--      }
--   * Don't change brace styles unless you're working on the non BSD-Style
--     area (so we don't get spurious changes in line numbering).
--   * Don't change indentation unless you're working on the badly indented
--     area (so we don't get spurious changes of large blocks of code).
--   * Jedit is the recommended editor, a comment of this format should be
--     included in the top 10 lines of the file (see the embedded edit mode)
--
--  $HeadURL: http://svn.xinha.python-hosting.com/trunk/htmlarea.js $
--  $LastChangedDate: 2005-10-02 04:58:27 -0600 (Sun, 02 Oct 2005) $
--  $LastChangedRevision: 377 $
--  $LastChangedBy: kimss $
--------------------------------------------------------------------------*/

HTMLArea.version =
{
'Release'   : 'Trunk',
'Head'      : '$HeadURL: http://svn.xinha.python-hosting.com/trunk/htmlarea.js $'.replace(/^[^:]*: (.*) \$$/, '$1'),
'Date'      : '$LastChangedDate: 2005-10-02 04:58:27 -0600 (Sun, 02 Oct 2005) $'.replace(/^[^:]*: ([0-9-]*) ([0-9:]*) ([+0-9]*) \((.*)\) \$/, '$4 $2 $3'),
'Revision'  : '$LastChangedRevision: 377 $'.replace(/^[^:]*: (.*) \$$/, '$1'),
'RevisionBy': '$LastChangedBy: kimss $'.replace(/^[^:]*: (.*) \$$/, '$1')
};

if (typeof _editor_url == "string") {
    // Leave exactly one backslash at the end of _editor_url
    _editor_url = _editor_url.replace(/\x2f*$/, '/');
} else {
    alert("WARNING: _editor_url is not set!  You should set this variable to the editor files path; it should preferably be an absolute path, like in '/htmlarea/', but it can be relative if you prefer.  Further we will try to load the editor files correctly but we'll probably fail.");
    _editor_url = '';
}

// make sure we have a language
if (typeof _editor_lang == "string") {
    _editor_lang = _editor_lang.toLowerCase();
} else {
    _editor_lang = "en";
}

// skin stylesheet to load
if (!(typeof _editor_skin == "string")) {
    _editor_skin = "";
}

var __htmlareas = [ ];

// enable experimental opera support
HTMLArea.enable_opera = false; 
// browser identification
HTMLArea.agt = navigator.userAgent.toLowerCase();
HTMLArea.is_ie     = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
HTMLArea.is_safari    = (HTMLArea.agt.indexOf("safari") != -1);
HTMLArea.is_mac    = (HTMLArea.agt.indexOf("mac") != -1);
HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
HTMLArea.is_gecko  = (navigator.product == "Gecko");

// Creates a new HTMLArea object.  Tries to replace the textarea with the given
// ID with it.
function HTMLArea(textarea, config)
{
    if(!textarea) throw("Tried to create HTMLArea without textarea specified.");

    if (HTMLArea.checkSupportedBrowser()) {
        if (typeof config == "undefined") {
            this.config = new HTMLArea.Config();
        } else {
            this.config = config;
        }
        this._htmlArea = null;

        if(typeof textarea != 'object')
        {
            textarea = HTMLArea.getElementById('textarea', textarea);
        }
        this._textArea = textarea;

        // Before we modify anything, get the initial textarea size
        this._initial_ta_size =
        {
            w: textarea.style.width ? textarea.style.width   : (textarea.offsetWidth + 'px'),
            h: textarea.style.height ? textarea.style.height : (textarea.offsetHeight + 'px')
        };

        this._editMode = "wysiwyg";
        this.plugins = {};
        this._timerToolbar = null;
        this._timerUndo = null;
        this._undoQueue = new Array(this.config.undoSteps);
        this._undoPos = -1;
        this._customUndo = true;
        this._mdoc = document; // cache the document, we need it in plugins
        this.doctype = '';
        this.__htmlarea_id_num = __htmlareas.length;
        __htmlareas[this.__htmlarea_id_num] = this;

        this._notifyListeners = { };

        // Panels
        var panels = this._panels =
        {
            right:
            {
                on: true,
                container:    document.createElement('td'),
                panels: [ ]
            },
            left:
            {
                on: true,
                container:    document.createElement('td'),
                panels: [ ]
            },
            top:
            {
                on: true,
                container:    document.createElement('td'),
                panels: [ ]
            },
            bottom:
            {
                on: true,
                container:    document.createElement('td'),
                panels: [ ]
            }
        };

        for(var i in panels)
        {
            panels[i].div = panels[i].container; // legacy
            panels[i].container.className = 'panels ' + i;
            HTMLArea.freeLater(panels[i], 'container');
            HTMLArea.freeLater(panels[i], 'div');
        }
        HTMLArea.freeLater(this, '_textArea');
    }
};

HTMLArea.onload = function(){};
HTMLArea.init = function() {
    HTMLArea.onload();
};


// cache some regexps
HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig;
HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i;
HTMLArea.RE_head    = /<head>((.|\n)*?)<\/head>/i;
HTMLArea.RE_body    = /<body[^>]*>((.|\n|\r|\t)*?)<\/body>/i;
HTMLArea.RE_Specials = /([\/\^$*+?.()|{}[\]])/g;
HTMLArea.RE_email    = /[a-z0-9_]{3,}@[a-z0-9_-]{2,}(\.[a-z0-9_-]{2,})+/i;
HTMLArea.RE_url      = /(https?:\/\/)?(([a-z0-9_]+:[a-z0-9_]+@)?[a-z0-9_-]{2,}(\.[a-z0-9_-]{2,}){2,}(:[0-9]+)?(\/\S+)*)/i;

HTMLArea.compileRegex = function(str, format) {
    // safari doesn't support compiled regexps
    if (HTMLArea.is_safari) {
        if (typeof format != "undefined") {
            return new RegExp(str, format);
	} else {
            return new RegExp(str);
        }
    }
    if (typeof format != "undefined") {
        return new RegExp().compile(str, format);
    } else {
        return new RegExp().compile(str);
    }
}

HTMLArea.Config = function () {
    var cfg = this;
    this.version = HTMLArea.version.Revision;

    // Width and Height
    //  you may set these as follows
    //  width = 'auto'      -- the width of the original textarea will be used
    //  width = 'toolbar'   -- the width of the toolbar will be used
    //  width = '<css measure>' -- use any css measurement, eg width = '75%'
    //
    //  height = 'auto'     -- the height of the original textarea
    //  height = '<css measure>' -- any css measurement, eg height = '480px'
    this.width  = "auto";
    this.height = "auto";

    // the next parameter specifies whether the toolbar should be included
    // in the size above, or are extra to it.  If false then it's recommended
    // to have explicit pixel sizes above (or on your textarea and have auto above)
    this.sizeIncludesBars = true;

    // the next parameter specifies whether the panels should be included
    // in the size above, or are extra to it.  If false then it's recommended
    // to have explicit pixel sizes above (or on your textarea and have auto above)
    this.sizeIncludesPanels = true;

    // each of the panels has a dimension, for the left/right it's the width
    // for the top/bottom it's the height.
    //
    // WARNING: PANEL DIMENSIONS MUST BE SPECIFIED AS PIXEL WIDTHS
    this.panel_dimensions =
    {
        left:   '200px', // Width
        right:  '200px',
        top:    '100px', // Height
        bottom: '100px'
    };

    // enable creation of a status bar?
    // >MT: doesn't help our users and takes up screen real estate
    this.statusBar = false;
    // <MT

    // intercept ^V and use the HTMLArea paste command
    // If false, then passes ^V through to browser editor widget
    this.htmlareaPaste = false;

    this.mozParaHandler = 'best'; // set to 'built-in', 'dirty' or 'best'
    // built-in: will (may) use 'br' instead of 'p' tags
    // dirty   : will use p and work good enough for the majority of cases,
    // best    : works the best, but it's about 12kb worth of javascript
    //   and will probably be slower than 'dirty'.  This is the "EnterParagraphs"
    //   plugin from "hipikat", rolled in to be part of the core code

    // maximum size of the undo queue
    this.undoSteps = 20;

    // the time interval at which undo samples are taken
    this.undoTimeout = 500; // 1/2 sec.

    // if true then HTMLArea will retrieve the full HTML, starting with the
    // <HTML> tag.
    this.fullPage = false;

    // style included in the iframe document
    this.pageStyle = "";

    // external stylesheets to load (REFERENCE THESE ABSOLUTELY)
    this.pageStyleSheets = [ ];

    // specify a base href for relative links
    this.baseHref  = null;

    // we can strip the base href out of relative links to leave them relative, reason for this
    //   especially if you don't specify a baseHref is that mozilla at least (& IE ?) will prefix
    //   the baseHref to any relative links to make them absolute, which isn't what you want most the time.
    this.stripBaseHref = true;

    // and we can strip the url of the editor page from named links (eg <a href="#top">...</a>)
    //  reason for this is that mozilla at least (and IE ?) prefixes location.href to any
    //  that don't have a url prefixing them
    this.stripSelfNamedAnchors = true;

    // sometimes high-ascii in links can cause problems for servers (basically they don't recognise them)
    //  so you can use this flag to ensure that all characters other than the normal ascii set (actually
    //  only ! through ~) are escaped in URLs to % codes
    this.only7BitPrintablesInURLs = true;

    // if you are putting the HTML written in Xinha into an email you might want it to be 7-bit
    //  characters only.  This config option (off by default) will convert all characters consuming
    //  more than 7bits into UNICODE decimal entity references (actually it will convert anything
    //  below <space> (chr 20) except cr, lf and tab and above <tilde> (~, chr 7E))
    this.sevenBitClean  = false;

    // sometimes we want to be able to replace some string in the html comng in and going out
    //  so that in the editor we use the "internal" string, and outside and in the source view
    //  we use the "external" string  this is useful for say making special codes for
    //  your absolute links, your external string might be some special code, say "{server_url}"
    //  an you say that the internal represenattion of that should be http://your.server/
    this.specialReplacements = { }; // { 'external_string' : 'internal_string' }

    // set to true if you want Word code to be cleaned upon Paste
    this.killWordOnPaste = true;

    // enable the 'Target' field in the Make Link dialog
    this.makeLinkShowsTarget = true;

    // CharSet of the iframe, default is the charset of the document
    this.charSet = HTMLArea.is_gecko ? document.characterSet : document.charset;

    // URL-s
    this.imgURL = "images/";
    this.popupURL = "popups/";

    // remove tags (these have to be a regexp, or null if this functionality is not desired)
    this.htmlRemoveTags = null;

    // Turning this on will turn all "linebreak" and "separator" items in your toolbar into soft-breaks,
    // this means that if the items between that item and the next linebreak/separator can
    // fit on the same line as that which came before then they will, otherwise they will
    // float down to the next line.

    // If you put a linebreak and separator next to each other, only the separator will
    // take effect, this allows you to have one toolbar that works for both flowToolbars = true and false
    // infact the toolbar below has been designed in this way, if flowToolbars is false then it will
    // create explictly two lines (plus any others made by plugins) breaking at justifyleft, however if
    // flowToolbars is false and your window is narrow enough then it will create more than one line
    // even neater, if you resize the window the toolbars will reflow.  Niiiice.

    this.flowToolbars = true;

    /** CUSTOMIZING THE TOOLBAR
    * -------------------------
    *
    * It is recommended that you customize the toolbar contents in an
    * external file (i.e. the one calling HTMLArea) and leave this one
    * unchanged.  That's because when we (InteractiveTools.com) release a
    * new official version, it's less likely that you will have problems
    * upgrading HTMLArea.
    */
    this.toolbar =
    [
    ["popupeditor"],
    ["separator","formatblock","fontname","fontsize","bold","italic","underline","strikethrough"],
    ["separator","forecolor","hilitecolor","textindicator"],
    ["separator","subscript","superscript"],
    ["linebreak","separator","justifyleft","justifycenter","justifyright","justifyfull"],
    ["separator","insertorderedlist","insertunorderedlist","outdent","indent"],
    ["separator","inserthorizontalrule","createlink","insertimage","inserttable"],
    ["separator","undo","redo","selectall","print"], (HTMLArea.is_gecko ? [] : ["cut","copy","paste","overwrite","saveas"]),
    ["separator","killword","clearfonts","removeformat","toggleborders","splitblock","lefttoright", "righttoleft"],
    ["separator","htmlmode","showhelp","about"]
    ];


    this.fontname = {
    "&mdash; font &mdash;":         '',
    "Arial":            'arial,helvetica,sans-serif',
    "Courier New":      'courier new,courier,monospace',
    "Georgia":          'georgia,times new roman,times,serif',
    "Tahoma":           'tahoma,arial,helvetica,sans-serif',
    "Times New Roman":  'times new roman,times,serif',
    "Verdana":          'verdana,arial,helvetica,sans-serif',
    "impact":           'impact',
    "WingDings":        'wingdings'
    };

    this.fontsize = {
    "&mdash; size &mdash;"  : "",
    "1 (8 pt)" : "1",
    "2 (10 pt)": "2",
    "3 (12 pt)": "3",
    "4 (14 pt)": "4",
    "5 (18 pt)": "5",
    "6 (24 pt)": "6",
    "7 (36 pt)": "7"
    };

    this.formatblock = {
    "&mdash; format &mdash;"  : "",
    "Heading 1": "h1",
    "Heading 2": "h2",
    "Heading 3": "h3",
    "Heading 4": "h4",
    "Heading 5": "h5",
    "Heading 6": "h6",
    "Normal"   : "p",
    "Address"  : "address",
    "Formatted": "pre"
    };

    this.customSelects = {};

    function cut_copy_paste(e, cmd, obj) {
        e.execCommand(cmd);
    };

    this.debug = true;

    this.URIs = {
    "blank": "popups/blank.html",
    "link": "link.html",
    "insert_image": "insert_image.html",
    "insert_table": "insert_table.html",
    "select_color": "select_color.html",
    // <MT
	"forecolor":    "select_color.php?type=fg",
	"hilitecolor":  "select_color.php?type=bg",
	// MT> 
    "about": "about.html",
    "help": "editor_help.html"
    };


    // ADDING CUSTOM BUTTONS: please read below!
    // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]"
    //    - ID: unique ID for the button.  If the button calls document.execCommand
    //      it's wise to give it the same name as the called command.
    //    - ACTION: function that gets called when the button is clicked.
    //              it has the following prototype:
    //                 function(editor, buttonName)
    //              - editor is the HTMLArea object that triggered the call
    //              - buttonName is the ID of the clicked button
    //              These 2 parameters makes it possible for you to use the same
    //              handler for more HTMLArea objects or for more different buttons.
    //    - ToolTip: tooltip, will be translated below
    //    - Icon: path to an icon image file for the button
    //            OR; you can use an 18x18 block of a larger image by supllying an array
    //            that has three elemtents, the first is the larger image, the second is the column
    //            the third is the row.  The ros and columns numbering starts at 0 but there is
    //            a header row and header column which have numbering to make life easier.
    //            See images/buttons_main.gif to see how it's done.
    //    - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.
    this.btnList = {
        bold:          [ "Bold",   HTMLArea._lc({key: 'button_bold', string: ["ed_buttons_main.gif",3,2]}, 'HTMLArea'), false, function(e) {e.execCommand("bold");} ],
        italic:        [ "Italic", HTMLArea._lc({key: 'button_italic', string: ["ed_buttons_main.gif",2,2]}, 'HTMLArea'), false, function(e) {e.execCommand("italic");} ],
        underline:     [ "Underline", HTMLArea._lc({key: 'button_underline', string: ["ed_buttons_main.gif",2,0]}, 'HTMLArea'), false, function(e) {e.execCommand("underline");} ],
        strikethrough: [ "Strikethrough", HTMLArea._lc({key: 'button_strikethrough', string: ["ed_buttons_main.gif",3,0]}, 'HTMLArea'), false, function(e) {e.execCommand("strikethrough");} ],
        subscript:     [ "Subscript", HTMLArea._lc({key: 'button_subscript', string: ["ed_buttons_main.gif",3,1]}, 'HTMLArea'), false, function(e) {e.execCommand("subscript");} ],
        superscript:   [ "Superscript", HTMLArea._lc({key: 'button_superscript', string: ["ed_buttons_main.gif",2,1]}, 'HTMLArea'), false, function(e) {e.execCommand("superscript");} ],

        justifyleft:   [ "Justify left", ["ed_buttons_main.gif",0,0], false, function(e) {e.execCommand("justifyleft");} ],
        justifycenter: [ "Justify center", ["ed_buttons_main.gif",1,1], false, function(e){e.execCommand("justifycenter");}],
        justifyright:  [ "Justify right", ["ed_buttons_main.gif",1,0], false, function(e) {e.execCommand("justifyright");} ],
        justifyfull:   [ "Justify full", ["ed_buttons_main.gif",0,1], false, function(e) {e.execCommand("justifyfull");} ],

        orderedlist:         [ "Ordered list", ["ed_buttons_main.gif",0,3], false, function(e) {e.execCommand("insertorderedlist");} ],
        unorderedlist:       [ "Bulleted list", ["ed_buttons_main.gif",1,3], false, function(e) {e.execCommand("insertunorderedlist");} ],
        insertorderedlist:   [ "Ordered list", ["ed_buttons_main.gif",0,3], false, function(e) {e.execCommand("insertorderedlist");} ],
        insertunorderedlist: [ "Bulleted list", ["ed_buttons_main.gif",1,3], false, function(e) {e.execCommand("insertunorderedlist");} ],

        outdent:     [ "Decrease indent", ["ed_buttons_main.gif",1,2], false, function(e) {e.execCommand("outdent");} ],
        indent:      [ "Increase indent",["ed_buttons_main.gif",0,2], false, function(e) {e.execCommand("indent");} ],
        // >MT: Change "Font color" to "Text color"
        forecolor:   [ "Text color", ["ed_buttons_main.gif",3,3], false, function(e) {e.execCommand("forecolor");} ],
        // <MT
        hilitecolor: [ "Background color", ["ed_buttons_main.gif",2,3], false, function(e) {e.execCommand("hilitecolor");} ],

        // >MT: Clean up the wordsmithing of "Undo", "Redo", "Cut", "Copy", and "Paste"
        undo: [ "Undo last action", ["ed_buttons_main.gif",4,2], false, function(e) {e.execCommand("undo");} ],
        redo: [ "Redo last action", ["ed_buttons_main.gif",5,2], false, function(e) {e.execCommand("redo");} ],
        cut:       [ "Cut", ["ed_buttons_main.gif",5,0], false, cut_copy_paste ],
        copy:      [ "Copy", ["ed_buttons_main.gif",4,0], false, cut_copy_paste ],
        paste:     [ "Paste", ["ed_buttons_main.gif",4,1], false, cut_copy_paste ],
        // <MT
        selectall: [ "Select all", "ed_selectall.gif", false, function(e) {e.execCommand("selectall");} ],

        inserthorizontalrule: [ "Horizontal rule", ["ed_buttons_main.gif",6,0], false, function(e) {e.execCommand("inserthorizontalrule");} ],
        // >MT: use standard createlink, so that param is passed, also change "Insert web link" to "Insert/modify link"
        createlink:           [ "Insert/modify link", ["ed_buttons_main.gif",6,1], false, function(e) {e.execCommand("createlink");} ],
        // <MT
        insertimage:          [ "Insert/modify image", ["ed_buttons_main.gif",6,3], false, function(e) {e.execCommand("insertimage");} ],
        inserttable:          [ "Insert table", ["ed_buttons_main.gif",6,2], false, function(e) {e.execCommand("inserttable");} ],

        htmlmode:      [ "Toggle HTML source", ["ed_buttons_main.gif",7,0], true, function(e) {e.execCommand("htmlmode");} ],
        toggleborders: [ "Toggle borders", ["ed_buttons_main.gif",7,2], false, function(e) { e._toggleBorders() } ],
        print:         [ "Print document", ["ed_buttons_main.gif",8,1], false, function(e) {e._iframe.contentWindow.print();} ],
        saveas:        [ "Save as", "ed_saveas.gif", false, function(e) {e.execCommand("saveas",false,"noname.htm");} ],
        about:         [ "About this editor", ["ed_buttons_main.gif",8,2], true, function(e) {e.execCommand("about");} ],
        showhelp:      [ "Help using editor", ["ed_buttons_main.gif",9,2], true, function(e) {e.execCommand("showhelp");} ],

        splitblock:   [ "Split block", "ed_splitblock.gif", false, function(e) {e._splitBlock();} ],
        lefttoright:  [ "Direction left to right", ["ed_buttons_main.gif",0,4], false, function(e) {e.execCommand("lefttoright");} ],
        righttoleft:  [ "Direction right to left", ["ed_buttons_main.gif",1,4], false, function(e) {e.execCommand("righttoleft");} ],
        overwrite:    [ "Insert/overwrite", "ed_overwrite.gif", false, function(e) {e.execCommand("overwrite");} ],

        // >MT: Clean up the capitalization of "MS word cleaner"
        wordclean:     [ "MS Word cleaner", ["ed_buttons_main.gif",5,3], false, function(e) {e._wordClean();} ],
        // <MT
        clearfonts:    [ "Clear inline font specifications", ["ed_buttons_main.gif",5,4], true, function(e) {e._clearFonts();} ],
        // >MT: "Clear" is being used instead of "Remove"
        removeformat:  [ "Clear formatting", ["ed_buttons_main.gif",4,4], false, function(e) {e.execCommand("removeformat");} ],
        // <MT
        killword:      [ "Clear MS Office tags", ["ed_buttons_main.gif",4,3], false, function(e) {e.execCommand("killword");} ]

    };
    /* ADDING CUSTOM BUTTONS
    * ---------------------
    *
    * It is recommended that you add the custom buttons in an external
    * file and leave this one unchanged.  That's because when we
    * (InteractiveTools.com) release a new official version, it's less
    * likely that you will have problems upgrading HTMLArea.
    *
    * Example on how to add a custom button when you construct the HTMLArea:
    *
    *   var editor = new HTMLArea("your_text_area_id");
    *   var cfg = editor.config; // this is the default configuration
    *   cfg.btnList["my-hilite"] =
    *  [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action
    *    "Highlight selection", // tooltip
    *    "my_hilite.gif", // image
    *    false // disabled in text mode
    *  ];
    *   cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar
    *
    * An alternate (also more convenient and recommended) way to
    * accomplish this is to use the registerButton function below.
    */
    // initialize tooltips from the I18N module and generate correct image path
    for (var i in this.btnList) {
        var btn = this.btnList[i];
        if(typeof btn[1] != 'string')
        {
            btn[1][0] = _editor_url + this.imgURL + btn[1][0];
        }
        else
        {
            btn[1] = _editor_url + this.imgURL + btn[1];
        }
        btn[0] = HTMLArea._lc(btn[0]); //initialize tooltip
    }

};

/** Helper function: register a new button with the configuration.  It can be
* called with all 5 arguments, or with only one (first one).  When called with
* only one argument it must be an object with the following properties: id,
* tooltip, image, textMode, action.  Examples:
*
* 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...});
* 2. config.registerButton({
*      id       : "my-hilite",      // the ID of your button
*      tooltip  : "Hilite text",    // the tooltip
*      image    : "my-hilite.gif",  // image to be displayed in the toolbar
*      textMode : false,            // disabled in text mode
*      action   : function(editor) { // called when the button is clicked
*                   editor.surroundHTML('<span class="hilite">', '</span>');
*                 },
*      context  : "p"               // will be disabled if outside a <p> element
*    });
*/
HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
    var the_id;
    if (typeof id == "string") {
        the_id = id;
    } else if (typeof id == "object") {
        the_id = id.id;
    } else {
        alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
        return false;
    }
    // check for existing id
    if (typeof this.customSelects[the_id] != "undefined") {
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
    }
    if (typeof this.btnList[the_id] != "undefined") {
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
    }
    switch (typeof id) {
        case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
        case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
    }
};

HTMLArea.prototype.registerPanel = function(side, object)
{
    if(!side) side = 'right';
    var panel = this.addPanel(side);
    if(object)
    {
        object.drawPanelIn(panel);
    }
};

/** The following helper function registers a dropdown box with the editor
* configuration.  You still have to add it to the toolbar, same as with the
* buttons.  Call it like this:
*
* FIXME: add example
*/
HTMLArea.Config.prototype.registerDropdown = function(object) {
    // check for existing id
    if (typeof this.customSelects[object.id] != "undefined") {
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
    }
    if (typeof this.btnList[object.id] != "undefined") {
        // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
    }
    this.customSelects[object.id] = object;
};

/** Call this function to remove some buttons/drop-down boxes from the toolbar.
* Pass as the only parameter a string containing button/drop-down names
* delimited by spaces.  Note that the string should also begin with a space
* and end with a space.  Example:
*
*   config.hideSomeButtons(" fontname fontsize textindicator ");
*
* It's useful because it's easier to remove stuff from the defaul toolbar than
* create a brand new toolbar ;-)
*/
HTMLArea.Config.prototype.hideSomeButtons = function(remove) {
    var toolbar = this.toolbar;
    for (var i = toolbar.length; --i >= 0;) {
        var line = toolbar[i];
        for (var j = line.length; --j >= 0; ) {
            if (remove.indexOf(" " + line[j] + " ") >= 0) {
                var len = 1;
                if (/separator|space/.test(line[j + 1])) {
                    len = 2;
                }
                line.splice(j, len);
            }
        }
    }
};

/** Helper Function: add buttons/drop-downs boxes with title or separator to the toolbar
* if the buttons/drop-downs boxes doesn't allready exists.
* id: button or selectbox (as array with separator or title)
* where: button or selectbox (as array if the first is not found take the second and so on)
* position:
* -1 = insert button (id) one position before the button (where)
* 0 = replace button (where) by button (id)
* +1 = insert button (id) one position after button (where)
*
* cfg.addToolbarElement(["T[title]", "button_id", "separator"] , ["first_id","second_id"], -1);
*/

HTMLArea.Config.prototype.addToolbarElement = function(id, where, position) {
    var toolbar = this.toolbar;
    var a, i, j, o, sid;
    var idIsArray = false;
    var whereIsArray = false;
    var whereLength = 0;
    var whereJ = 0;
    var whereI = 0;
    var exists = false;
    var found = false;
    // check if id and where are arrys
    if ((id && typeof id == "object") && (id.constructor == Array)) {
        idIsArray = true;
    }
    if ((where && typeof where == "object") && (where.constructor == Array)) {
        whereIsArray = true;
        whereLength = where.length;
    }

    if (idIsArray) { //find the button/select box in input array
        for (i = 0; i < id.length; ++i) {
            if ((id[i] != "separator") && (id[i].indexOf("T[") != 0)) {
                sid = id[i];
            }
        }
    } else {
        sid = id;
    }

    for (var i = 0; !exists && !found && i < toolbar.length; ++i) {
        a = toolbar[i]
        for (j = 0; !found && j < a.length; ++j) {
            if (a[i] == sid) { // check if button/select box exists
                exists = true;
                break;
            }
            if (whereIsArray) {
                for (o = 0; o < whereLength; ++o) {
                    if(a[j] == where[o]) {
                        if (o == 0) {
                            found = true;
                            j--;
                            break;
                        } else {
                            whereI = i;
                            whereJ = j;
                            whereLength = o;
                        }
                    }
                }
            } else {
                if (a[j] == where) { // find the position to insert
                    found = true;
                    break;
                }
            }
        }
    }

    if (!exists) {
        if (!found && whereIsArray) { //if check found any other as the first button
            if (where.length != whereLength) {
                j = whereJ;
                a = toolbar[whereI];
                found = true;
            }
        }
        if (found) {
            if (position == 0) { // replace the found button
                if (idIsArray) {
                    a[j] = id[id.length-1];
                    for (i = id.length-1; --i >= 0;) {
                        a.splice(j, 0, id[i]);
                    }
                } else {
                    a[j] = id;
                }
            } else { // insert before/after the found button
                if (position < 0) {
                    j = j + position + 1; //correct position before
                } else if (position > 0) {
                    j = j + position; //correct posion after
                }
                if (idIsArray) {
                    for (i = id.length; --i >= 0;) {
                        a.splice(j, 0, id[i]);
                    }
                } else {
                    a.splice(j, 0, id);
                }
            }
        } else { // no button found
            toolbar[0].splice(0, 0, "separator");
            if (idIsArray) {
                for (i = id.length; --i >= 0;) {
                    toolbar[0].splice(0, 0, id[i]);
                }
            } else {
                toolbar[0].splice(0, 0, id);
            }
        }
    }
};

/** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
HTMLArea.replaceAll = function(config) {
    var tas = document.getElementsByTagName("textarea");
    for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
};

/** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
HTMLArea.replace = function(id, config)
{
    var ta = HTMLArea.getElementById("textarea", id);
    return ta ? (new HTMLArea(ta, config)).generate() : null;;
};

// Creates the toolbar and appends it to the _htmlarea
HTMLArea.prototype._createToolbar = function () {
    var editor = this;  // to access this in nested functions

    var toolbar = document.createElement("div");
    // ._toolbar is for legacy, ._toolBar is better thanks.
    this._toolBar = this._toolbar = toolbar;
    toolbar.className = "toolbar";
    toolbar.unselectable = "1";

    HTMLArea.freeLater(this, '_toolBar');
    HTMLArea.freeLater(this, '_toolbar');

    var tb_row = null;
    var tb_objects = new Object();
    this._toolbarObjects = tb_objects;
    this._createToolbar1(editor, toolbar, tb_objects);
    this._htmlArea.appendChild(toolbar);

    return toolbar;
};


HTMLArea.prototype._setConfig = function(config) {
    this.config = config;
};

HTMLArea.prototype._addToolbar = function() {
    this._createToolbar1(this, this._toolbar, this._toolbarObjects);
};

// separate from previous createToolBar to allow dynamic change of toolbar
HTMLArea.prototype._createToolbar1 = function(editor, toolbar, tb_objects) {
    var tb_row = null;
    // This shouldn't be necessary, but IE seems to float outside of the container
    // when we float toolbar sections, so we have to clear:both here as well
    // as at the end (which we do have to do).
    if(editor.config.flowToolbars)
    {
        var brk = document.createElement('div');
        brk.style.height =
        brk.style.width =
        brk.style.lineHeight =
        brk.style.fontSize = '1px';
        brk.style.clear = 'both';
        toolbar.appendChild(brk);
    }

    // creates a new line in the toolbar
    function newLine() {
        if(tb_row != null && tb_row.childNodes.length == 0) return;

        var table = document.createElement("table");
        table.border = "0px";
        table.cellSpacing = "0px";
        table.cellPadding = "0px";
        if(editor.config.flowToolbars)
        {
            if(HTMLArea.is_ie)
            {
                table.style.styleFloat = "left";
            }
            else
            {
                table.style.cssFloat = "left";
            }
            // >MT: fix flow issue, each newLine is otherwise on a new line
            table.style.width = "auto";
            // <MT
        }

        toolbar.appendChild(table);
        // TBODY is required for IE, otherwise you don't see anything
        // in the TABLE.
        var tb_body = document.createElement("tbody");
        table.appendChild(tb_body);
        tb_row = document.createElement("tr");
        tb_body.appendChild(tb_row);

        table.className = 'toolbarRow'; // meh, kinda.
    }; // END of function: newLine

    // init first line
    newLine();

    // updates the state of a toolbar element.  This function is member of
    // a toolbar element object (unnamed objects created by createButton or
    // createSelect functions below).
    function setButtonStatus(id, newval) {
        var oldval = this[id];
        var el = this.element;
        if (oldval != newval) {
            switch (id) {
                case "enabled":
                if (newval) {
                    HTMLArea._removeClass(el, "buttonDisabled");
                    el.disabled = false;
                } else {
                    HTMLArea._addClass(el, "buttonDisabled");
                    el.disabled = true;
                }
                break;
                case "active":
                if (newval) {
                    HTMLArea._addClass(el, "buttonPressed");
                } else {
                    HTMLArea._removeClass(el, "buttonPressed");
                }
                break;
            }
            this[id] = newval;
        }
    }; // END of function: setButtonStatus

    // this function will handle creation of combo boxes.  Receives as
    // parameter the name of a button as defined in the toolBar config.
    // This function is called from createButton, above, if the given "txt"
    // doesn't match a button.
    function createSelect(txt) {
        var options = null;
        var el = null;
        var cmd = null;
        var customSelects = editor.config.customSelects;
        var context = null;
        var tooltip = "";
        switch (txt) {
            case "fontsize":
            case "fontname":
            case "formatblock":
            // >MT: fontstyles
            case "fontstyles":
            // <MT
            // the following line retrieves the correct
            // configuration option because the variable name
            // inside the Config object is named the same as the
            // button/select in the toolbar.  For instance, if txt
            // == "formatblock" we retrieve config.formatblock (or
            // a different way to write it in JS is
            // config["formatblock"].
            options = editor.config[txt];
            cmd = txt;
            break;
            default:
            // try to fetch it from the list of registered selects
            cmd = txt;
            var dropdown = customSelects[cmd];
            if (typeof dropdown != "undefined") {
                options = dropdown.options;
                context = dropdown.context;
                if (typeof dropdown.tooltip != "undefined") {
                    tooltip = dropdown.tooltip;
                }
            } else {
                alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
            }
            break;
        }
        if (options) {
            el = document.createElement("select");
            el.title = tooltip;
            var obj = {
                name    : txt,    // field name
                element : el,     // the UI element (SELECT)
                enabled : true,   // is it enabled?
                text    : false,  // enabled in text mode?
                cmd     : cmd,    // command ID
                state   : setButtonStatus, // for changing state
                context : context
            };

            HTMLArea.freeLater(obj);

            tb_objects[txt] = obj;

            for (var i in options) {
                var op = document.createElement("option");
                op.innerHTML = HTMLArea._lc(i);
                op.value = options[i];
                el.appendChild(op);
            }
            HTMLArea._addEvent(el, "change", function () {
                editor._comboSelected(el, txt);
            });
        }
        return el;
    }; // END of function: createSelect

    // appends a new button to toolbar
    function createButton(txt) {
        // >MT: handle 'showbutton'
        var btn = editor.config.btnList[txt];
        if (btn && btn[5] == false) {
            return -1;
        }
        // <MT
        // the element that will be created
        var el = null;
        var btn = null;
        switch (txt) {
            case "separator":
            if(editor.config.flowToolbars) newLine();
            el = document.createElement("div");
            el.className = "separator";
            break;
            case "space":
            el = document.createElement("div");
            el.className = "space";
            break;
            case "linebreak":
            newLine();
            return false;
            case "textindicator":
            el = document.createElement("div");
            el.appendChild(document.createTextNode("A"));
            el.className = "indicator";
            el.title = HTMLArea._lc("Current style");
            var obj = {
                name  : txt, // the button name (i.e. 'bold')
                element : el, // the UI element (DIV)
                enabled : true, // is it enabled?
                active  : false, // is it pressed?
                text  : false, // enabled in text mode?
                cmd      : "textindicator", // the command ID
                state  : setButtonStatus // for changing state
            };

            HTMLArea.freeLater(obj);

            tb_objects[txt] = obj;
            break;
            default:
            btn = editor.config.btnList[txt];
        }
        if (!el && btn) {
            el = document.createElement("a");
            el.style.display = 'block';
            el.href = 'javascript:void(0)';
            el.style.textDecoration = 'none';
            el.title = btn[0];
            el.className = "button";
            // let's just pretend we have a button object, and
            // assign all the needed information to it.
            var obj = {
                name  : txt, // the button name (i.e. 'bold')
                element : el, // the UI element (DIV)
                enabled : true, // is it enabled?
                active  : false, // is it pressed?
                text  : btn[2], // enabled in text mode?
                cmd      : btn[3], // the command ID
                state  : setButtonStatus, // for changing state
                context : btn[4] || null // enabled in a certain context?
            };

            HTMLArea.freeLater(obj);

            tb_objects[txt] = obj;
            // handlers to emulate nice flat toolbar buttons
            HTMLArea._addEvent(el, "mouseout", function () {
                if (obj.enabled) with (HTMLArea) {
                    //_removeClass(el, "buttonHover");
                    _removeClass(el, "buttonActive");
                    (obj.active) && _addClass(el, "buttonPressed");
                }
            });

            HTMLArea._addEvent(el, "mousedown", function (ev) {
                if (obj.enabled) with (HTMLArea) {
                    _addClass(el, "buttonActive");
                    _removeClass(el, "buttonPressed");
                    _stopEvent(is_ie ? window.event : ev);
                }
            });
            // when clicked, do the following:
            HTMLArea._addEvent(el, "click", function (ev) {
                if (obj.enabled) with (HTMLArea) {
                    _removeClass(el, "buttonActive");
                    //_removeClass(el, "buttonHover");
                    if(HTMLArea.is_gecko)
                    {
                        editor.activateEditor();
                    }
                    obj.cmd(editor, obj.name, obj);
                    _stopEvent(is_ie ? window.event : ev);
                }
            });

            var i_contain = HTMLArea.makeBtnImg(btn[1]);
            var img = i_contain.firstChild;
            el.appendChild(i_contain);

            obj.imgel = img;
            obj.swapImage = function(newimg)
            {
                if(typeof newimg != 'string')
                {
                    img.src = newimg[0];
                    img.style.position = 'relative';
                    img.style.top  = newimg[2] ? ('-' + (18 * (newimg[2] + 1)) + 'px') : '-18px';
                    img.style.left = newimg[1] ? ('-' + (18 * (newimg[1] + 1)) + 'px') : '-18px';
                }
                else
                {
                    obj.imgel.src = newimg;
                    img.style.top = '0px';
                    img.style.left = '0px';
                }
            }

        } else if (!el) {
            el = createSelect(txt);
        }

        return el;
    };

    var first = true;
    for (var i = 0; i < this.config.toolbar.length; ++i) {
        if (!first) {
            // createButton("linebreak");
        } else {
            first = false;
        }
        if(this.config.toolbar[i] == null) this.config.toolbar[i] = ['separator'];
        var group = this.config.toolbar[i];

        for (var j = 0; j < group.length; ++j)
        {
            var code = group[j];
            if (/^([IT])\[(.*?)\]/.test(code))
            {
                // special case, create text label
                var l7ed = RegExp.$1 == "I"; // localized?
                var label = RegExp.$2;
                if (l7ed) {
                    label = HTMLArea._lc(label);
                }
                var tb_cell = document.createElement("td");
                tb_row.appendChild(tb_cell);
                tb_cell.className = "label";
                tb_cell.innerHTML = label;
            }
            else if(typeof code != 'function')
            {
                var tb_element = createButton(code);

                // >MT: handle hidden buttons
                if (tb_element && tb_element != -1)
                // <MT
                {
                    var tb_cell = document.createElement("td");
                    tb_cell.className = 'toolbarElement';
                    tb_row.appendChild(tb_cell);
                    tb_cell.appendChild(tb_element);
                }
                else if (tb_element == null)
                {
                    alert("FIXME: Unknown toolbar item: " + code);
                }
            }
        }
    }

    if(editor.config.flowToolbars)
    {
        var brk = document.createElement('div');
        brk.style.height =
        brk.style.width =
        brk.style.lineHeight =
        brk.style.fontSize = '1px';
        brk.style.clear = 'both';
        toolbar.appendChild(brk);
    }

    return toolbar;
};

use_clone_img = false;
HTMLArea.makeBtnImg = function(imgDef, doc)
{
    if(!doc) doc = document;

    if(!doc._htmlareaImgCache)
    {
        doc._htmlareaImgCache = { };
        HTMLArea.freeLater(doc._htmlareaImgCache);
    }

    var i_contain = null;
    if(HTMLArea.is_ie && ((!doc.compatMode) || (doc.compatMode && doc.compatMode == "BackCompat")))
    {
        i_contain = doc.createElement('span');
    }
    else
    {
        i_contain = doc.createElement('div');
        i_contain.style.position = 'relative';
    }

    i_contain.style.overflow = 'hidden';
    i_contain.style.width = "18px";
    i_contain.style.height = "18px";
    i_contain.className    = 'buttonImageContainer';

    var img = null;
    if(typeof imgDef == 'string')
    {
        if(doc._htmlareaImgCache[imgDef])
        {
            // >MT: fix cloneNode takes an argument
            img = doc._htmlareaImgCache[imgDef].cloneNode(false);
            // <MT
        }
        else
        {
            img = doc.createElement("img");
            img.src = imgDef;
            img.style.width = "18px";
            img.style.height = "18px";
            if(use_clone_img)
            // >MT: fix cloneNode takes an argument
            doc._htmlareaImgCache[imgDef] = img.cloneNode(false);
            // <MT
        }
    }
    else
    {
        if(doc._htmlareaImgCache[imgDef[0]])
        {
            // >MT: fix cloneNode takes an argument
            img = doc._htmlareaImgCache[imgDef[0]].cloneNode(false);
            // <MT
        }
        else
        {
            img = doc.createElement("img");
            img.src = imgDef[0];
            img.style.position = 'relative';
            if(use_clone_img)
            // >MT: fix cloneNode takes an argument
            doc._htmlareaImgCache[imgDef[0]] = img.cloneNode(false);
            // <MT
        }
        img.style.top  = imgDef[2] ? ('-' + (18 * (imgDef[2] + 1)) + 'px') : '-18px';
        img.style.left = imgDef[1] ? ('-' + (18 * (imgDef[1] + 1)) + 'px') : '-18px';
    }
    i_contain.appendChild(img);
    return i_contain;
};


HTMLArea.prototype._createStatusBar = function() {
    var statusbar = document.createElement("div");
    statusbar.className = "statusBar";
    this._statusBar = statusbar;
    HTMLArea.freeLater(this, '_statusBar');

    // statusbar.appendChild(document.createTextNode(HTMLArea._lc("Path") + ": "));
    // creates a holder for the path view
    div = document.createElement("span");
    div.className = "statusBarTree";
    div.innerHTML = HTMLArea._lc("Path") + ": ";
    this._statusBarTree = div;
    HTMLArea.freeLater(this, '_statusBarTree');
    this._statusBar.appendChild(div);

    div = document.createElement("span");
    div.innerHTML = HTMLArea._lc("You are in TEXT MODE.  Use the [<>] button to switch back to WYSIWYG.");
    div.style.display = "none";
    this._statusBarTextMode = div;
    HTMLArea.freeLater(this, '_statusBarTextMode');
    this._statusBar.appendChild(div);

    if (!this.config.statusBar)
    {
        // disable it...
        statusbar.style.display = "none";
    }

    return statusbar;
};

// Creates the HTMLArea object and replaces the textarea with it.
HTMLArea.prototype.generate = function ()
{
    var editor = this;  // we'll need "this" in some nested functions

    if(typeof Dialog == 'undefined')
    {
        HTMLArea._loadback
        (_editor_url + 'dialog.js', function() { editor.generate(); } );
        return false;
    }

    if(typeof HTMLArea.Dialog == 'undefined')
    {
        HTMLArea._loadback
        (_editor_url + 'inline-dialog.js', function() { editor.generate(); } );
        return false;
    }

    // >MT: use popupdiv instead of popupwin
    if(typeof PopupDiv == 'undefined')
    {
        HTMLArea._loadback
        (_editor_url + 'popupdiv.js', function() { editor.generate(); } );
        return false;
    }
    /*
    if(typeof PopupWin == 'undefined')
    {
    HTMLArea._loadback
    (_editor_url + 'popupwin.js', function() { editor.generate(); } );
    return false;
    }
    */
    // <MT

    if(_editor_skin != "") {
        var found=false;
        var head = document.getElementsByTagName("head")[0];
        var links = document.getElementsByTagName("link");
        for(var i = 0; i<links.length; i++) {
            if((links[i].rel == "stylesheet")&&(links[i].href == _editor_url + 'skins/' + _editor_skin + '/skin.css'))
            found = true;
        }
        if(!found) {
            var link = document.createElement("link");
            link.type = "text/css";
            link.href = _editor_url + 'skins/' + _editor_skin + '/skin.css';
            link.rel = "stylesheet"
            head.appendChild(link);
        }
    }

    //backwards-compatibility: load FullScreen-Plugin if we find a "popupeditor"-button in the toolbar
    var toolbar = editor.config.toolbar;
    for (var i = toolbar.length; --i >= 0;) {
        for (var j = toolbar[i].length; --j >= 0; ) {
            if (toolbar[i][j]=="popupeditor") {
                if(typeof FullScreen == "undefined") {
                    HTMLArea.loadPlugin("FullScreen", function() { editor.generate(); } );
                    return false;
                }
                editor.registerPlugin('FullScreen');
            }
        }
    }

    // If this is gecko, set up the paragraph handling now
    if(HTMLArea.is_gecko)
    {
        switch(editor.config.mozParaHandler)
        {
            case 'best':
            {
                if(typeof EnterParagraphs == 'undefined')
                {
                    HTMLArea.loadPlugin("EnterParagraphs", function() { editor.generate(); } );
                    return false;
                }
                editor.registerPlugin('EnterParagraphs');
            }
            break;

            case 'dirty'   :
            case 'built-in':
            default        :
            {
                // See _editorEvent
            }
            break;
        }
    }

    // create the editor framework, yah, table layout I know, but much easier
    // to get it working correctly this way, sorry about that, patches welcome.

    this._framework =
    {
    'table'     :document.createElement('table'),
    'tbody'     :document.createElement('tbody'), // IE will not show the table if it doesn't have a tbody!
    'tb_row'    :document.createElement('tr'),
    'tb_cell'   :document.createElement('td'), // Toolbar

    'tp_row'    :document.createElement('tr'),
    'tp_cell'   :this._panels.top.container,   // top panel

    'ler_row'   :document.createElement('tr'),
    'lp_cell'   :this._panels.left.container,  // left panel
    'ed_cell'   :document.createElement('td'), // editor
    'rp_cell'   :this._panels.right.container, // right panel

    'bp_row'    :document.createElement('tr'),
    'bp_cell'   :this._panels.bottom.container,// bottom panel

    'sb_row'    :document.createElement('tr'),
    'sb_cell'   :document.createElement('td')  // status bar

    };

    HTMLArea.freeLater(this._framework);

    var fw = this._framework;
    fw.table.border="0";
    fw.table.cellPadding="0";
    fw.table.cellSpacing="0";

    fw.tb_row.style.verticalAlign = 'top';
    fw.tp_row.style.verticalAlign = 'top';
    fw.ler_row.style.verticalAlign= 'top';
    fw.bp_row.style.verticalAlign = 'top';
    fw.sb_row.style.verticalAlign = 'top';
    fw.ed_cell.style.position     = 'relative';

    // Put the cells in the rows        set col & rowspans
    // note that I've set all these so that all panels are showing
    // but they will be redone in sizeEditor() depending on which
    // panels are shown.  It's just here to clarify how the thing
    // is put togethor.
    fw.tb_row.appendChild(fw.tb_cell);  fw.tb_cell.colSpan = 3;

    fw.tp_row.appendChild(fw.tp_cell);  fw.tp_cell.colSpan = 3;

    fw.ler_row.appendChild(fw.lp_cell);
    fw.ler_row.appendChild(fw.ed_cell);
    fw.ler_row.appendChild(fw.rp_cell);

    fw.bp_row.appendChild(fw.bp_cell);  fw.bp_cell.colSpan = 3;

    fw.sb_row.appendChild(fw.sb_cell);  fw.sb_cell.colSpan = 3;

    // Put the rows in the table body
    fw.tbody.appendChild(fw.tb_row);  // Toolbar
    fw.tbody.appendChild(fw.tp_row); // Left, Top, Right panels
    fw.tbody.appendChild(fw.ler_row);  // Editor/Textarea
    fw.tbody.appendChild(fw.bp_row);  // Bottom panel
    fw.tbody.appendChild(fw.sb_row);  // Statusbar

    // and body in the table
    fw.table.appendChild(fw.tbody);

    var htmlarea = this._framework.table;
    this._htmlArea = htmlarea;
    HTMLArea.freeLater(this, '_htmlArea');
    htmlarea.className = "htmlarea";

    // create the toolbar and put in the area
    var toolbar = this._createToolbar();
    // >MT: add this._readOnly
    if (!this._readOnly)
    // <MT
    this._framework.tb_cell.appendChild(toolbar);

    // create the IFRAME & add to container
    var iframe = document.createElement("iframe");
    iframe.src = _editor_url + editor.config.URIs["blank"];
    this._framework.ed_cell.appendChild(iframe);
    this._iframe = iframe;
    this._iframe.className = 'xinha_iframe';
    HTMLArea.freeLater(this, '_iframe');

    // creates & appends the status bar
    var statusbar = this._createStatusBar();
    this._framework.sb_cell.appendChild(statusbar);

    // insert Xinha before the textarea.
    var textarea = this._textArea;
    textarea.parentNode.insertBefore(htmlarea, textarea);
    textarea.className = 'xinha_textarea';

    // extract the textarea and insert it into the htmlarea
    HTMLArea.removeFromParent(textarea);
    this._framework.ed_cell.appendChild(textarea);

    // Set up event listeners for saving the iframe content to the textarea
    if (textarea.form)
    {
        // onsubmit get the HTMLArea content and update original textarea.
        HTMLArea.prependDom0Event
        (
        this._textArea.form,
        'submit',
        function() {editor._textArea.value = editor.outwardHtml(editor.getHTML()); return true;}
        );

        var initialTAContent = textarea.value;

        // onreset revert the HTMLArea content to the textarea content
        HTMLArea.prependDom0Event
        (
        this._textArea.form,
        'reset',
        function() { editor.setHTML(editor.inwardHtml(initialTAContent)); editor.updateToolbar(); return true; }
        );
    }

    // add a handler for the "back/forward" case -- on body.unload we save
    // the HTML content into the original textarea.
    HTMLArea.prependDom0Event(window, 'unload', function() {textarea.value = editor.outwardHtml(editor.getHTML()); return true; });

    // Hide textarea
    textarea.style.display = "none";

    // Initalize size
    editor.initSize();

    // Add an event to initialize the iframe once loaded.
    editor._iframeLoadDone = false;
    HTMLArea._addEvent
    (
    this._iframe,
    'load',
    function(e)
    {
        if(! editor._iframeLoadDone)
        {
            editor._iframeLoadDone = true;
            editor.initIframe();
        }
        return true;
    }
    );

};

/**
* Size the editor according to the INITIAL sizing information.
* config.width
*    The width may be set via three ways
*    auto    = the width is inherited from the original textarea
*    toolbar = the width is set to be the same size as the toolbar
*    <set size> = the width is an explicit size (any CSS measurement, eg 100em should be fine)
*
* config.height
*    auto    = the height is inherited from the original textarea
*    <set size> = an explicit size measurement (again, CSS measurements)
*
* config.sizeIncludesBars
*    true    = the tool & status bars will appear inside the width & height confines
*    false   = the tool & status bars will appear outside the width & height confines
*
*/

HTMLArea.prototype.initSize = function()
{
    var editor = this;

    var width  = null;
    var height = null;
    switch(this.config.width)
    {
        case 'auto':
        {
            width = this._initial_ta_size.w;
        }
        break;

        case 'toolbar':
        {
            width = this._toolBar.offsetWidth;
        }
        break;

        default :
        {
            width = this.config.width;
        }
        break;
    }

    switch(this.config.height)
    {
        case 'auto':
        {
            height = this._initial_ta_size.h;
        }
        break;

        default :
        {
            height = this.config.height;
        }
        break;
    }

    this.sizeEditor(width, height, this.config.sizeIncludesBars, this.config.sizeIncludesPanels);

    HTMLArea.addDom0Event(window, 'resize', function(e) { editor.sizeEditor(); });

    this.notifyOn('panel_change',function(){editor.sizeEditor();});
};

/**
*  Size the editor to a specific size, or just refresh the size (when window resizes for example)
*  @param width optional width (CSS specification)
*  @param height optional height (CSS specification)
*  @param includingBars optional boolean to indicate if the size should include or exclude tool & status bars
*/

HTMLArea.prototype.sizeEditor = function(width, height, includingBars, includingPanels)
{

    // We need to set the iframe & textarea to 100% height so that the htmlarea
    // isn't "pushed out" when we get it's height, so we can change them later.
    this._iframe.style.height   = '100%';
    this._textArea.style.height = '100%';
    this._iframe.style.width    = '';
    this._textArea.style.width  = '';

    if(includingBars != null)     this._htmlArea.sizeIncludesToolbars = includingBars;
    if(includingPanels != null)   this._htmlArea.sizeIncludesPanels   = includingPanels;

    if(width != null)
    {
        this._htmlArea.style.width          = width;
        if(!this._htmlArea.sizeIncludesPanels)
        {
            // Need to add some for l & r panels
            var panel = this._panels.right;
            if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div))
            {
                this._htmlArea.style.width = this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.right);
            }

            var panel = this._panels.left;
            if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div))
            {
                this._htmlArea.style.width = this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.left);
            }
        }
    }

    if(height != null)
    {
        this._htmlArea.style.height         = height;
        if(!this._htmlArea.sizeIncludesToolbars)
        {
            // Need to add some for toolbars
            this._htmlArea.style.height         = this._htmlArea.offsetHeight + this._toolbar.offsetHeight + this._statusBar.offsetHeight;
        }

        if(!this._htmlArea.sizeIncludesPanels)
        {
            // Need to add some for l & r panels
            var panel = this._panels.top;
            if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div))
            {
                this._htmlArea.style.height = this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.top);
            }

            var panel = this._panels.bottom;
            if(panel.on && panel.panels.length && HTMLArea.hasDisplayedChildren(panel.div))
            {
                this._htmlArea.style.height = this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.bottom);
            }
        }
    }

    // >MT: add this._OnResize
    if (typeof this._OnResize == 'function' && width == null && height == null)
    this._OnResize();
    // <MT
    // At this point we have this._htmlArea.style.width & this._htmlArea.style.height
    // which are the size for the OUTER editor area, including toolbars and panels
    // now we size the INNER area and position stuff in the right places.
    width  = this._htmlArea.offsetWidth;
    height = this._htmlArea.offsetHeight;

    // Set colspan for toolbar, and statusbar, rowspan for left & right panels, and insert panels to be displayed
    // into thier rows
    var panels = this._panels;
    var editor = this;
    var col_span = 1;

    function panel_is_alive(pan)
    {
        if(panels[pan].on && panels[pan].panels.length && HTMLArea.hasDisplayedChildren(panels[pan].container))
        {
            panels[pan].container.style.display = '';
            return true;
        }

        // Otherwise make sure it's been removed from the framework
        else
        {
            panels[pan].container.style.display='none';
            return false;
        }
    }

    if(panel_is_alive('left'))
    {
        col_span += 1;
    }

    if(panel_is_alive('top'))
    {
        // NOP
    }

    if(panel_is_alive('right'))
    {
        col_span += 1;
    }

    if(panel_is_alive('bottom'))
    {
        // NOP
    }

    this._framework.tb_cell.colSpan = col_span;
    this._framework.tp_cell.colSpan = col_span;
    this._framework.bp_cell.colSpan = col_span;
    this._framework.sb_cell.colSpan = col_span;

    // Put in the panel rows, top panel goes above editor row
    if(!this._framework.tp_row.childNodes.length)
    {
        HTMLArea.removeFromParent(this._framework.tp_row);
    }
    else
    {
        if(!HTMLArea.hasParentNode(this._framework.tp_row))
        {
            this._framework.tbody.insertBefore(this._framework.tp_row, this._framework.ler_row);
        }
    }

    // bp goes after the editor
    if(!this._framework.bp_row.childNodes.length)
    {
        HTMLArea.removeFromParent(this._framework.bp_row);
    }
    else
    {
        if(!HTMLArea.hasParentNode(this._framework.bp_row))
        {
            this._framework.tbody.insertBefore(this._framework.bp_row, this._framework.ler_row.nextSibling);
        }
    }

    // finally if the statusbar is on, insert it
    if(!this.config.statusBar)
    {
        HTMLArea.removeFromParent(this._framework.sb_row);
    }
    else
    {
        if(!HTMLArea.hasParentNode(this._framework.sb_row))
        {
            this._framework.table.appendChild(this._framework.sb_row);
        }
    }

    // Size and set colspans, link up the framework
    this._framework.lp_cell.style.width  = this.config.panel_dimensions.left;
    this._framework.rp_cell.style.width  = this.config.panel_dimensions.right;
    this._framework.tp_cell.style.height = this.config.panel_dimensions.top;
    this._framework.bp_cell.style.height = this.config.panel_dimensions.bottom;
    this._framework.tb_cell.style.height = this._toolBar.offsetHeight   + 'px';
    this._framework.sb_cell.style.height = this._statusBar.offsetHeight + 'px';

    var edcellheight = height - this._toolBar.offsetHeight - this._statusBar.offsetHeight;
    if(panel_is_alive('top'))    edcellheight  -= parseInt(this.config.panel_dimensions.top);
    if(panel_is_alive('bottom')) edcellheight  -= parseInt(this.config.panel_dimensions.bottom);;
    this._iframe.style.height    = edcellheight + 'px';

    var edcellwidth = width;
    if(panel_is_alive('left'))  edcellwidth -= parseInt(this.config.panel_dimensions.left);
    if(panel_is_alive('right')) edcellwidth -= parseInt(this.config.panel_dimensions.right);
    this._iframe.style.width     =  edcellwidth + 'px';

    this._textArea.style.height = this._iframe.style.height;
    this._textArea.style.width  = this._iframe.style.width;

    this.notifyOf('resize', {width:this._htmlArea.offsetWidth, height:this._htmlArea.offsetHeight});
    // >MT: redraw after resize, to remove artifacts
    this.forceRedraw();
    // <MT
};

HTMLArea.prototype.addPanel = function(side)
{
    var div = document.createElement('div');
    div.side = side;
    if(side == 'left' || side == 'right')
    {
        div.style.width = this.config.panel_dimensions[side];
    }
    HTMLArea.addClasses(div, 'panel');
    this._panels[side].panels.push(div);
    this._panels[side].div.appendChild(div);

    this.notifyOf('panel_change', {'action':'add','panel':div});

    return div;
};

HTMLArea.prototype.removePanel = function(panel)
{
    this._panels[panel.side].div.removeChild(panel);
    var clean = [ ];
    for(var i = 0; i < this._panels[panel.side].panels.length; i++)
    {
        if(this._panels[panel.side].panels[i] != panel)
        {
            clean.push(this._panels[panel.side].panels[i]);
        }
    }
    this._panels[panel.side].panels = clean;
    this.notifyOf('panel_change', {'action':'remove','panel':panel});
};

HTMLArea.prototype.hidePanel = function(panel)
{
    if(panel)
    {
        panel.style.display = 'none';
        this.notifyOf('panel_change', {'action':'hide','panel':panel});
    }
};

HTMLArea.prototype.showPanel = function(panel)
{
    if(panel)
    {
        panel.style.display = '';
        this.notifyOf('panel_change', {'action':'show','panel':panel});
    }
};

HTMLArea.prototype.hidePanels = function(sides)
{
    if(typeof sides == 'undefined')
    {
        sides = ['left','right','top','bottom'];
    }

    var reShow = [];
    for(var i = 0; i < sides.length;i++)
    {
        if(this._panels[sides[i]].on)
        {
            reShow.push(sides[i]);
            this._panels[sides[i]].on = false;
        }
    }
    this.notifyOf('panel_change', {'action':'multi_hide','sides':sides});
};

HTMLArea.prototype.showPanels = function(sides)
{
    if(typeof sides == 'undefined')
    {
        sides = ['left','right','top','bottom'];
    }

    var reHide = [];
    for(var i = 0; i < sides.length;i++)
    {
        if(!this._panels[sides[i]].on)
        {
            reHide.push(sides[i]);
            this._panels[sides[i]].on = true;
        }
    }
    this.notifyOf('panel_change', {'action':'multi_show','sides':sides});
};

HTMLArea.objectProperties = function(obj)
{
    var props = [ ];
    for(var x in obj)
    {
        props[props.length] = x;
    }
    return props;
};

/*
* EDITOR ACTIVATION NOTES:
*  when a page has multiple Xinha editors, ONLY ONE should be activated at any time (this is mostly to
*  work around a bug in Mozilla, but also makes some sense).  No editor should be activated or focused
*  automatically until at least one editor has been activated through user action (by mouse-clicking in
*  the editor).
*/

HTMLArea.prototype.editorIsActivated = function() {
    try {
        if (HTMLArea.is_gecko) return (this._doc.designMode == 'on');
        else return (this._doc.body.contentEditable);
    } catch (e)
    {
        return false;
    }
};

HTMLArea._someEditorHasBeenActivated = false;
HTMLArea._currentlyActiveEditor      = false;
HTMLArea.prototype.activateEditor = function()
{
    // We only want ONE editor at a time to be active
    if(HTMLArea._currentlyActiveEditor)
    {
        if(HTMLArea._currentlyActiveEditor == this) return true;
        HTMLArea._currentlyActiveEditor.deactivateEditor();
    }

    if (HTMLArea.is_gecko && this._doc.designMode != 'on')
    {
        try
        {
            // cannot set design mode if no display
            if (this._iframe.style.display == 'none')
            {
                this._iframe.style.display = '';
                this._doc.designMode = 'on';
                this._iframe.style.display = 'none';
            }
            else
            {
                this._doc.designMode = 'on';
            }
        } catch (e) {}
    }
    else if(!HTMLArea.is_gecko && this._doc.body.contentEditable != true)
    {
        this._doc.body.contentEditable = true;
    }

    // We need to know that at least one editor on the page has been activated
    // this is because we will not focus any editor until an editor has been activated
    HTMLArea._someEditorHasBeenActivated = true;
    HTMLArea._currentlyActiveEditor      = this;

    var editor = this;
    this.enableToolbar();
};

HTMLArea.prototype.deactivateEditor = function()
{
    // If the editor isn't active then the user shouldn't use the toolbar
    this.disableToolbar();

    if (HTMLArea.is_gecko && this._doc.designMode != 'off')
    {
        try {this._doc.designMode = 'off';} catch (e) {}
    }
    else if(!HTMLArea.is_gecko && this._doc.body.contentEditable != false)
    {
        this._doc.body.contentEditable = false;
    }

    if(HTMLArea._currentlyActiveEditor != this)
    {
        // We just deactivated an editor that wasn't marked as the currentlyActiveEditor

        return; // I think this should really be an error, there shouldn't be a situation where
        // an editor is deactivated without first being activated.  but it probably won't
        // hurt anything.
    }

    HTMLArea._currentlyActiveEditor = false;
};

HTMLArea.prototype.initIframe = function()
{
    this.disableToolbar();
    var doc = null;
    var editor = this;
    try
    {
        if (editor._iframe.contentDocument)
        {
            this._doc = editor._iframe.contentDocument;
        }
        else
        {
            this._doc = editor._iframe.contentWindow.document;
        }
        doc = this._doc;
        if (!doc) { // try later
            if (HTMLArea.is_gecko) {
                setTimeout(function() { editor.initIframe()}, 50);
                return false;
            } else {
                alert("ERROR: IFRAME can't be initialized.");
            }
        }
        // >MT: redraw after load is complete to remove redraw artifacts
        else
        setTimeout(function() { editor.forceRedraw()}, 50);
        // <MT
    }
    catch(e)
    { // try later
        setTimeout(function() { editor.initIframe()}, 50);
        // >MT: return on failure
        return false;
        // <MT
    }

    HTMLArea.freeLater(this, '_doc');
    // MT: doc.open() removed
    if (!editor.config.fullPage) {
        var html = "<html>\n";
        html += "<head>\n";
        html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + editor.config.charSet + "\">\n";
        if(typeof editor.config.baseHref != 'undefined' && editor.config.baseHref != null)
        {
            html += "<base href=\"" + editor.config.baseHref + "\"/>\n";
        }
        html += "<style title=\"table borders\">"
        + ".htmtableborders, .htmtableborders td, .htmtableborders th {border : 1px dashed lightgrey ! important;} \n"
        + "</style>\n";
        html += "<style type=\"text/css\">"
        + "html, body { border: 0px; } \n"
        + "span.macro, span.macro ul, span.macro div, span.macro p {background : #CCCCCC;}\n"
        + "</style>\n";

        if(editor.config.pageStyle)
        {
            html += "<style type=\"text/css\">\n" + editor.config.pageStyle + "\n</style>";
        }

        if(typeof editor.config.pageStyleSheets !== 'undefined')
        {
            for(style_i = 0; style_i < editor.config.pageStyleSheets.length; style_i++)
            {
                if(editor.config.pageStyleSheets[style_i].length > 0)
                html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + editor.config.pageStyleSheets[style_i] + "\">";
                //html += "<style> @import url('" + editor.config.pageStyleSheets[style_i] + "'); </style>\n";
            }
        }
        html += "</head>\n";
        html += "<body id='topic'>\n";
        html +=   editor.inwardHtml(editor._textArea.value);
        html += "</body>\n";
        html += "</html>";
    } else {
        var html = editor.inwardHtml(editor._textArea.value);
        if (html.match(HTMLArea.RE_doctype)) {
            editor.setDoctype(RegExp.$1);
            html = html.replace(HTMLArea.RE_doctype, "");
        }
    }
    doc.write(html);
    doc.close();

    // if we have multiple editors some bug in Mozilla makes some lose editing ability
    HTMLArea._addEvents
    (
    doc,
    ["mousedown"],
    function() { editor.activateEditor(); return true; }
    );


    // intercept some events; for updating the toolbar & keyboard handlers
    HTMLArea._addEvents
    (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
    function (event) {
        return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
    });

    // check if any plugins have registered refresh handlers
    for (var i in editor.plugins) {
        var plugin = editor.plugins[i].instance;
        HTMLArea.refreshPlugin(plugin);
    }

    // specific editor initialization
    if(typeof editor._onGenerate == "function") {
        editor._onGenerate();
    }

    // >MT: add this.editorLoaded
    if (typeof editor.editorLoaded == 'function')
    editor.editorLoaded();
    // <MT
};

// Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no
// parameter was passed this function toggles between modes.
HTMLArea.prototype.setMode = function(mode) {
    if (typeof mode == "undefined") {
        mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
    }

    switch (mode) {
        case "textmode":
        {
            // >MT: use XHTML
            var html = this.outwardHtml(this.getXHTML());
            // <MT
            this.setHTML(html);

            // Hide the iframe
            this.deactivateEditor();
            this._iframe.style.display   = 'none';
            this._textArea.style.display = '';

            if (this.config.statusBar)
            {
                this._statusBarTree.style.display = "none";
                this._statusBarTextMode.style.display = "";
            }

            this.notifyOf('modechange', {'mode':'text'});
            break;
        }

        case "wysiwyg":
        {
            var html = this.inwardHtml(this.getHTML());
            this.deactivateEditor();
            this.setHTML(html);
            // >MT: use XHTML
            HTMLArea.makeXHTML(null, this.makeXHTMLContext(false));
            if (window.Widget)
                Widget.initializeEditor(this._doc);
            // <MT
            this._iframe.style.display   = '';
            this._textArea.style.display = "none";
            this.activateEditor();
            if (this.config.statusBar)
            {
                this._statusBarTree.style.display = "";
                this._statusBarTextMode.style.display = "none";
            }

            this.notifyOf('modechange', {'mode':'wysiwyg'});
            break;
        }

        default:
        {
            alert("Mode <" + mode + "> not defined!");
            return false;
        }
    }
    this._editMode = mode;
    // >MT: add this._OnMode
    if (typeof this._OnMode == "function")
    this._OnMode(mode);
    // <MT

    for (var i in this.plugins) {
        var plugin = this.plugins[i].instance;
        if (typeof plugin.onMode == "function") plugin.onMode(mode);
    }
};

HTMLArea.prototype.setFullHTML = function(html) {
    var save_multiline = RegExp.multiline;
    RegExp.multiline = true;
    if (html.match(HTMLArea.RE_doctype)) {
        this.setDoctype(RegExp.$1);
        html = html.replace(HTMLArea.RE_doctype, "");
    }
    RegExp.multiline = save_multiline;
    if (!HTMLArea.is_ie) {
        if (html.match(HTMLArea.RE_head))
        this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
        if (html.match(HTMLArea.RE_body))
        this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
    } else {
        var reac = this.editorIsActivated();
        if(reac) this.deactivateEditor();
        var html_re = /<html>((.|\n)*?)<\/html>/i;
        html = html.replace(html_re, "$1");
        this._doc.open();
        this._doc.write(html);
        // >MT: add topic ID
        this._doc.body.id = "topic";
        // <MT
        this._doc.close();
        if(reac) this.activateEditor();
        return true;
    }
};

/***************************************************
*  Category: PLUGINS
***************************************************/

// Create the specified plugin and register it with this HTMLArea
// return the plugin created to allow refresh when necessary
HTMLArea.prototype.registerPlugin = function() {
    var plugin = arguments[0];

    // We can only register plugins that have been succesfully loaded
    if
    (
    plugin == null
    || typeof plugin == 'undefined'
    || (typeof plugin == 'string' && eval('typeof ' + plugin) == 'undefined')
    ) return false;

    var args = [];
    for (var i = 1; i < arguments.length; ++i)
    args.push(arguments[i]);
    return this.registerPlugin2(plugin, args);
};

// this is the variant of the function above where the plugin arguments are
// already packed in an array.  Externally, it should be only used in the
// full-screen editor code, in order to initialize plugins with the same
// parameters as in the opener window.
HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
    if (typeof plugin == "string")
    plugin = eval(plugin);
    if (typeof plugin == "undefined") {
        /* FIXME: This should never happen. But why does it do? */
        return false;
    }
    var obj = new plugin(this, args);
    if (obj) {
        var clone = {};
        var info = plugin._pluginInfo;
        for (var i in info)
        clone[i] = info[i];
        clone.instance = obj;
        clone.args = args;
        this.plugins[plugin._pluginInfo.name] = clone;
        return obj;
    } else
        alert("Can't register plugin " + plugin.toString() + ".");
};

// static function that loads the required plugin and lang file, based on the
// language loaded already for HTMLArea.  You better make sure that the plugin
// _has_ that language, otherwise shit might happen ;-)
HTMLArea.getPluginDir = function(pluginName) {
    return _editor_url + "plugins/" + pluginName;
};

HTMLArea.loadPlugin = function(pluginName, callback) {
    // Might already be loaded
    if(eval('typeof ' + pluginName) != 'undefined')
    {
        if(callback)
        {
            callback(pluginName);
        }
        return true;
    }

    var dir = this.getPluginDir(pluginName);
    var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
    function (str, l1, l2, l3) {
        return l1 + "-" + l2.toLowerCase() + l3;
    }).toLowerCase() + ".js";
    var plugin_file = dir + "/" + plugin;

    if(callback)
    {
        HTMLArea._loadback(plugin_file, function() { callback(pluginName); });
    }
    else
    {
        document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
    }
    return false;
};

HTMLArea._pluginLoadStatus = { };
HTMLArea.loadPlugins = function(plugins, callbackIfNotReady)
{
    // Rip the ones that are loaded and look for ones that have failed
    var retVal = true;
    var nuPlugins = HTMLArea.cloneObject(plugins);
    while(nuPlugins.length)
    {
        var p = nuPlugins.pop();
        if(typeof HTMLArea._pluginLoadStatus[p] == 'undefined')
        {
            // Load it
            HTMLArea._pluginLoadStatus[p] = 'loading';
            HTMLArea.loadPlugin(p,
            function(plugin)
            {
                if(eval('typeof ' + plugin) != 'undefined')
                {
                    HTMLArea._pluginLoadStatus[plugin] = 'ready';
                }
                else
                {
                    // Actually, this won't happen, because if the script fails
                    // it will throw an exception preventing the callback from
                    // running.  This will leave it always in the "loading" state
                    // unfortunatly that means we can't fail plugins gracefully
                    // by just skipping them.
                    HTMLArea._pluginLoadStatus[plugin] = 'failed';
                }
            }
            );
            retVal = false;
        }
        else
        {
            switch(HTMLArea._pluginLoadStatus[p])
            {
                case 'failed':
                case 'ready' :
                break;

                case 'loading':
                default       :
                retVal = false;
                break;
            }
        }
    }

    if(retVal) return true; // All done, just return

    // Waiting on plugins to load, return false now and come back a bit later
    // if we have to callback
    if(callbackIfNotReady)
    {
        setTimeout(function() { if(HTMLArea.loadPlugins(plugins, callbackIfNotReady)) callbackIfNotReady(); }, 150);
    }
    return retVal;
};

// refresh plugin by calling onGenerate or onGenerateOnce method.
HTMLArea.refreshPlugin = function(plugin) {
    if (typeof plugin.onGenerate == "function")
    plugin.onGenerate();
    if (typeof plugin.onGenerateOnce == "function") {
        plugin.onGenerateOnce();
        plugin.onGenerateOnce = null;
    }
};

HTMLArea.loadStyle = function(style, plugin) {
    var url = _editor_url || '';
    if (typeof plugin != "undefined") {
        url += "plugins/" + plugin + "/";
    }
    url += style;
    if (/^\//.test(style))
    url = style;
    var head = document.getElementsByTagName("head")[0];
    var link = document.createElement("link");
    link.rel = "stylesheet";
    link.href = url;
    head.appendChild(link);
    //document.write("<style type='text/css'>@import url(" + url + ");</style>");
};
HTMLArea.loadStyle(typeof _editor_css == "string" ? _editor_css : "htmlarea.css");

/***************************************************
*  Category: EDITOR UTILITIES
***************************************************/

HTMLArea.prototype.debugTree = function() {
    var ta = document.createElement("textarea");
    ta.style.width = "100%";
    ta.style.height = "20em";
    ta.value = "";
    function debug(indent, str) {
        for (; --indent >= 0;)
        ta.value += " ";
        ta.value += str + "\n";
    };
    function _dt(root, level) {
        var tag = root.tagName.toLowerCase(), i;
        var ns = HTMLArea.is_ie ? root.scopeName : root.prefix;
        debug(level, "- " + tag + " [" + ns + "]");
        for (i = root.firstChild; i; i = i.nextSibling)
        if (i.nodeType == 1)
        _dt(i, level + 2);
    };
    _dt(this._doc.body, 0);
    document.body.appendChild(ta);
};

HTMLArea.getInnerText = function(el) {
    // >MT: use faster native function
    if (HTMLArea.is_ie)
    return el.nodeType == 3 ? el.nodeValue : el.innerText;
    // >MT: This crashes FF 1.0.x
    // else if (HTMLArea.is_gecko)
    //   return el.textContent;
    // <MT
    // <MT
    // >MT: handle text nodes
    var txt = el.nodeType == 3 ? el.data : '', i;
    // <MT
    for (i = el.firstChild; i; i = i.nextSibling) {
        if (i.nodeType == 3)
        txt += i.data;
        else if (i.nodeType == 1)
        txt += HTMLArea.getInnerText(i);
    }
    return txt;
};

HTMLArea.prototype._wordClean = function() {
    var
    editor = this,
    stats = {
        empty_tags : 0,
        mso_class  : 0,
        mso_style  : 0,
        mso_xmlel  : 0,
        orig_len   : this._doc.body.innerHTML.length,
        T          : (new Date()).getTime()
    },
    stats_txt = {
        empty_tags : "Empty tags removed: ",
        mso_class  : "MSO class names removed: ",
        mso_style  : "MSO inline style removed: ",
        mso_xmlel  : "MSO XML elements stripped: "
    };
    function showStats() {
        var txt = "HTMLArea word cleaner stats: \n\n";
        for (var i in stats)
        if (stats_txt[i])
        txt += stats_txt[i] + stats[i] + "\n";
        txt += "\nInitial document length: " + stats.orig_len + "\n";
        txt += "Final document length: " + editor._doc.body.innerHTML.length + "\n";
        txt += "Clean-up took " + (((new Date()).getTime() - stats.T) / 1000) + " seconds";
        alert(txt);
    };
    function clearClass(node) {
        var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig, ' ');
        if (newc != node.className) {
            node.className = newc;
            if (!/\S/.test(node.className)) {
                node.removeAttribute("className");
                ++stats.mso_class;
            }
        }
    };
    function clearStyle(node) {
        var declarations = node.style.cssText.split(/\s*;\s*/);
        for (var i = declarations.length; --i >= 0;)
        if (/^mso|^tab-stops/i.test(declarations[i]) ||
        /^margin\s*:\s*0..\s+0..\s+0../i.test(declarations[i])) {
            ++stats.mso_style;
            declarations.splice(i, 1);
        }
        node.style.cssText = declarations.join("; ");
    };
    function stripTag(el) {
        if (HTMLArea.is_ie)
        el.outerHTML = HTMLArea.htmlEncode(el.innerText);
        else {
            var txt = document.createTextNode(HTMLArea.getInnerText(el));
            el.parentNode.insertBefore(txt, el);
            HTMLArea.removeFromParent(el);
        }
        ++stats.mso_xmlel;
    };
    function checkEmpty(el) {
        if (/^(a|span|b|strong|i|em|font)$/i.test(el.tagName) &&
        !el.firstChild) {
            HTMLArea.removeFromParent(el);
            ++stats.empty_tags;
        }
    };
    function parseTree(root) {
        var tag = root.tagName.toLowerCase(), i, next;
        if ((HTMLArea.is_ie && root.scopeName != 'HTML') || (!HTMLArea.is_ie && /:/.test(tag))) {
            stripTag(root);
            return false;
        } else {
            clearClass(root);
            clearStyle(root);
            for (i = root.firstChild; i; i = next) {
                next = i.nextSibling;
                if (i.nodeType == 1 && parseTree(i))
                checkEmpty(i);
            }
        }
        return true;
    };
    parseTree(this._doc.body);
    // showStats();
    // this.debugTree();
    // this.setHTML(this.getHTML());
    // this.setHTML(this.getInnerHTML());
    // this.forceRedraw();
    this.updateToolbar();
};

HTMLArea.prototype._clearFonts = function() {
    var D = this.getInnerHTML();

    if(confirm(HTMLArea._lc("Would you like to clear font typefaces?")))
    {
        D = D.replace(/face="[^"]*"/gi, '');
    D = D.replace(/font-family:[^;}"']+;?/gi, '');
  }

  if(confirm(HTMLArea._lc("Would you like to clear font sizes?")))
  {
    D = D.replace(/size="[^"]*"/gi, '');
    D = D.replace(/font-size:[^;}"']+;?/gi, '');
    }

    if(confirm(HTMLArea._lc("Would you like to clear font colours?")))
    {
        D = D.replace(/color="[^"]*"/gi, '');
    D = D.replace(/([^-])color:[^;}"']+;?/gi, '$1');
  }

  D = D.replace(/(style|class)="\s*"/gi, '');
  D = D.replace(/<(font|span)\s*>/gi, '');
  this.setHTML(D);
  this.updateToolbar();
};

HTMLArea.prototype._splitBlock = function()
{
  this._doc.execCommand('formatblock', false, 'div');
};

HTMLArea.prototype.forceRedraw = function() {
  // >MT: handle if called before body is loaded
  if (this._doc == null || this._doc.body == null)
    return;
  // <MT
  this._doc.body.style.visibility = "hidden";
  this._doc.body.style.visibility = "visible";
  // this._doc.body.innerHTML = this.getInnerHTML();
};

// focuses the iframe window.  returns a reference to the editor document.
HTMLArea.prototype.focusEditor = function() {
  switch (this._editMode) {
    // notice the try { ... } catch block to avoid some rare exceptions in FireFox
    // (perhaps also in other Gecko browsers). Manual focus by user is required in
      // case of an error. Somebody has an idea?
    case "wysiwyg" :
    try
    {
      // We don't want to focus the field unless at least one field has been activated.
        if(HTMLArea._someEditorHasBeenActivated)
        {
            this.activateEditor(); // Ensure *this* editor is activated
            this._iframe.contentWindow.focus(); // and focus it
        }
    } catch (e) {} break;
    case "textmode": try { this._textArea.focus() } catch (e) {} break;
    default: alert("ERROR: mode " + this._editMode + " is not defined");
}
return this._doc;
};

// takes a snapshot of the current text (for undo)
HTMLArea.prototype._undoTakeSnapshot = function() {
    ++this._undoPos;
    if (this._undoPos >= this.config.undoSteps) {
        // remove the first element
        this._undoQueue.shift();
        --this._undoPos;
    }
    // use the fasted method (getInnerHTML);
    var take = true;
    var txt = this.getInnerHTML();
    if (this._undoPos > 0)
    take = (this._undoQueue[this._undoPos - 1] != txt);
    if (take) {
        this._undoQueue[this._undoPos] = txt;
    } else {
        this._undoPos--;
    }
};

HTMLArea.prototype.undo = function() {
    if (this._undoPos > 0) {
        var txt = this._undoQueue[--this._undoPos];
        if (txt) this.setHTML(txt);
        else ++this._undoPos;
    }
};

HTMLArea.prototype.redo = function() {
    if (this._undoPos < this._undoQueue.length - 1) {
        var txt = this._undoQueue[++this._undoPos];
        if (txt) this.setHTML(txt);
        else --this._undoPos;
    }
};

HTMLArea.prototype.disableToolbar = function(except)
{
    if(this._timerToolbar) clearTimeout(this._timerToolbar);
    if(typeof except == 'undefined')
    {
        except = [ ];
    }
    else if(typeof except != 'object')
    {
        except = [except];
    }

    for (var i in this._toolbarObjects)
    {
        var btn = this._toolbarObjects[i];
        if(except.contains(i))
        {
            continue;
        }
        btn.state("enabled", false);
    }
};

HTMLArea.prototype.enableToolbar = function()
{
    this.updateToolbar();
};

if(!Array.prototype.contains)
{
    Array.prototype.contains = function(needle)
    {
        var haystack = this;
        for(var i = 0; i < haystack.length; i++)
        {
            if(needle == haystack[i]) return true;
        }

        return false;
    };
}

if(!Array.prototype.indexOf)
{
    Array.prototype.indexOf = function(needle)
    {
        var haystack = this;
        for(var i = 0; i < haystack.length; i++)
        {
            if(needle == haystack[i]) return i;
        }

        return null;
    };
}


// updates enabled/disable/active state of the toolbar elements
HTMLArea.prototype.updateToolbar = function(noStatus) {
    var doc = this._doc;
    var text = (this._editMode == "textmode");
    var ancestors = null;
    if (!text) {
        ancestors = this.getAllAncestors();
        if (this.config.statusBar && !noStatus) {
            this._statusBarTree.innerHTML = HTMLArea._lc("Path") + ": "; // clear
            for (var i = ancestors.length; --i >= 0;) {
                var el = ancestors[i];
                if (!el) {
                    // hell knows why we get here; this
                    // could be a classic example of why
                    // it's good to check for conditions
                    // that are impossible to happen ;-)
                    continue;
                }
                var a = document.createElement("a");
                a.href = "javascript:void(0)";
                a.el = el;
                a.editor = this;
                HTMLArea.addDom0Event(a, 'click', function() {
                    this.blur();
                    this.editor.selectNodeContents(this.el);
                    this.editor.updateToolbar(true);
                    return false;
                });
                HTMLArea.addDom0Event(a, 'contextmenu',  function() {
                    // TODO: add context menu here
                    this.blur();
                    // >MT: show HTML instead of CSS on status right click
                    var info = "HTML:\n";
                    /* DEBUG
                    info += HTMLArea.getOuterHTML(this.el);
                    info += "\ngetHTML:\n";
                    /**/
                    info += HTMLArea.getHTMLWrapper(this.el,true,editor ? editor : this.editor);
                    // <MT
                    alert(info);
                    return false;
                });
                var txt = el.tagName.toLowerCase();
                a.title = el.style.cssText;
                if (el.id) {
                    txt += "#" + el.id;
                }
                if (el.className) {
                    txt += "." + el.className;
                }
                a.appendChild(document.createTextNode(txt));
                this._statusBarTree.appendChild(a);
                if (i != 0) {
                    this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
                }
            }
        }
    }

    for (var i in this._toolbarObjects) {
        var btn = this._toolbarObjects[i];
        var cmd = i;
        var inContext = true;
        if (btn.context && !text) {
            inContext = false;
            var context = btn.context;
            var attrs = [];
            if (/(.*)\[(.*?)\]/.test(context)) {
                context = RegExp.$1;
                attrs = RegExp.$2.split(",");
            }
            context = context.toLowerCase();
            var match = (context == "*");
            for (var k = 0; k < ancestors.length; ++k) {
                if (!ancestors[k]) {
                    // the impossible really happens.
                    continue;
                }
                if (match || (ancestors[k].tagName.toLowerCase() == context)) {
                    inContext = true;
                    for (var ka = 0; ka < attrs.length; ++ka) {
                        if (!eval("ancestors[k]." + attrs[ka])) {
                            inContext = false;
                            break;
                        }
                    }
                    if (inContext) {
                        break;
                    }
                }
            }
        }
        btn.state("enabled", (!text || btn.text) && inContext);
        if (typeof cmd == "function") {
            continue;
        }
        // look-it-up in the custom dropdown boxes
        var dropdown = this.config.customSelects[cmd];
        // >MT: disallow formatting in header tags
        var isInHeader = this._isInAny(ancestors,["h1","h2","h3","h4","h5","h6"]);
        // <MT
        if ((!text || btn.text) && (typeof dropdown != "undefined")) {
            dropdown.refresh(this);
            continue;
        }
        switch (cmd)
        {
            case "fontname":
            case "fontsize":
            // >MT: add fontstyles
            case "fontstyles":
            // <MT
            {
                // >MT: add relative font sizes and font styles
                if (!text) if (isInHeader) btn.state("enabled", false);
                else {
                    if (cmd == 'fontstyles' && typeof fontStyles != 'undefined')
                    var value = fontStyles(this);
                    else if (cmd == 'fontsize' && typeof fontSizes != 'undefined')
                    var value = fontSizes(this);
                    else
                    // <MT
                    var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();

                    if (!value) {
                        btn.element.selectedIndex = 0;
                        break;
                    }

                    // HACK -- retrieve the config option for this
                    // combo box.  We rely on the fact that the
                    // variable in config has the same name as
                    // button name in the toolbar.
                    var options = this.config[cmd];
                    var k = 0;
                    // >MT: don't use exception handling
                    var found = false;
                    // <MT
                    for (var j in options)
                    {
                        // FIXME: the following line is scary.
                        if ((j.toLowerCase() == value) || (options[j].substr(0, value.length).toLowerCase() == value))
                        {
                            btn.element.selectedIndex = k;
                            // >MT: don't use exception handling
                            found = true;
                            break;
                            // <MT
                        }
                        ++k;
                    }
                    // >MT: don't use exception handling
                    if (!found)
                    // <MT
                    btn.element.selectedIndex = 0;
                    // >MT: don't use exception handling
                }
                // <MT
            }
            break;

            // It's better to search for the format block by tag name from the
            //  current selection upwards, because IE has a tendancy to return
            //  things like 'heading 1' for 'h1', which breaks things if you want
            //  to call your heading blocks 'header 1'.  Stupid MS.
            case "formatblock"  :
            {
                // >MT: disallow blockformat in lists and tables
                if (this._isInAny(ancestors,["ul","ol","table"])) {
                    //btn.state("enabled", false);
                    for (var i = 0; i < ancestors.length; ++i) {
                        var a = ancestors[i];
                        if (!a)
                            continue;
                        var tag = a.tagName.toLowerCase();
                        if (/ul|ol|table/i.test(tag))
                            break;
                        if (tag == 'span' && /header_/i.test(a.className)) {
                            var level = parseInt(a.className.substring(7));
                            btn.element.selectedIndex = level;
                            break;
                        }
                    }
                } else {
                    // <MT
                    var blocks = [ ];
                    // >MT: fix <p> case
                    var hasP = 0;
                    // <MT
                    for(var i in this.config['formatblock'])
                    {
                        // >MT: fix <p> case
                        var b = this.config['formatblock'][i];
                        if (b.toLowerCase() != "p")
                        blocks[blocks.length] = b;
                        else {
                            hasP = blocks.length;
                            blocks[blocks.length] = '-p-';
                        }
                        // <MT
                    }

                    // >MT: fix <p> case
                    var sel = this._getSelection();
                    var deepestAncestor = this._getFirstAncestor(sel, blocks);
                    if(!deepestAncestor && hasP) {
                        blocks[hasP] = "p";
                        deepestAncestor = this._getFirstAncestor(sel, ["p"]);
                    }
                    // <MT
                    if(deepestAncestor)
                    {
                        for(var x= 0; x < blocks.length; x++)
                        {
                            if(blocks[x].toLowerCase() == deepestAncestor.tagName.toLowerCase())
                            {
                                btn.element.selectedIndex = x;
                            }
                        }
                    }
                    else
                    {
                        btn.element.selectedIndex = 0;
                    }
                }
                // >MT: disallow blockformat in lists and tables
            }
            // <MT
            break;

            case "textindicator":
            if (!text) {
                try {with (btn.element.style) {
                    backgroundColor = HTMLArea._makeColor(
                    doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
                    if (/transparent/i.test(backgroundColor)) {
                        // Mozilla
                        backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
                    }
                    color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
                    fontFamily = doc.queryCommandValue("fontname");
                    fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
                    fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
                }} catch (e) {
                    // alert(e + "\n\n" + cmd);
                }
            }
            break;
            case "htmlmode": btn.state("active", text); break;
            case "lefttoright":
            case "righttoleft":
            var el = this.getParentElement();
            while (el && !HTMLArea.isBlockElement(el))
            el = el.parentNode;
            if (el)
            btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
            break;
            default:
            // >MT: improve performance
            if (cmd.indexOf('insert') < 0)
            // <MT
            cmd = cmd.replace(/(un)?orderedlist/i, "insert$1orderedlist");

            // >MT: disallow formating in headers
            if (isInHeader && typeof HTMLArea.RE_noHeaderCmd != "undefined" && HTMLArea.RE_noHeaderCmd.test(cmd))
            btn.state("enabled", false);
            // <MT

            // >MT: remove exceptions in FF
            var check = HTMLArea.is_ie || /^(bold|italic|underline|strikethrough|subscript|superscript|justify.*|insert(un)?orderedlist|overwrite)$/.test(cmd);
            // <MT
            try {
                // >MT: remove exceptions in FF
                if (check || text)
                // <MT
                btn.state("active", (!text && doc.queryCommandState(cmd)));
            } catch (e) {}
        }
    }
    // take undo snapshots
    if (this._customUndo && !this._timerUndo) {
        this._undoTakeSnapshot();
        var editor = this;
        this._timerUndo = setTimeout(function() {
            editor._timerUndo = null;
        }, this.config.undoTimeout);
    }

    // Insert a space in certain locations, this is just to make editing a little
    // easier (to "get out of" tags), it's not essential.
    // TODO: Make this work for IE?
    // TODO: Perhaps should use a plain space character, I'm not sure.
    //  OK, I've disabled this temporarily, to be honest, I can't rightly remember what the
    //  original problem was I was trying to solve with it.  I think perhaps that EnterParagraphs
    //  might solve the problem, whatever the hell it was.  I'm going senile, I'm sure.
    if(0 && HTMLArea.is_gecko)
    {
        var s = this._getSelection();
        // If the last character in the last text node of the parent tag
        // and the parent tag is not a block tag
        if(s && s.isCollapsed && s.anchorNode
        && s.anchorNode.parentNode.tagName.toLowerCase() != 'body'
        && s.anchorNode.nodeType == 3 && s.anchorOffset == s.anchorNode.length
        && !
        (   s.anchorNode.parentNode.nextSibling
        && s.anchorNode.parentNode.nextSibling.nodeType == 3
        )
        && !HTMLArea.isBlockElement(s.anchorNode.parentNode)
        )
        {
            // Insert hair-width-space after the close tag if there isn't another text node on the other side
            // It could also work with zero-width-space (\u200B) but I don't like it so much.
            // Perhaps this won't work well in various character sets and we should use plain space (20)?
            try
            {
                s.anchorNode.parentNode.parentNode.insertBefore
                (this._doc.createTextNode('\t'), s.anchorNode.parentNode.nextSibling);
            }
            catch(e)
            {
                // Disregard
            }
        }
    }

    // check if any plugins have registered refresh handlers
    for (var i in this.plugins) {
        var plugin = this.plugins[i].instance;
        if (typeof plugin.onUpdateToolbar == "function")
        plugin.onUpdateToolbar();
    }


};

/** Returns a node after which we can insert other nodes, in the current
* selection.  The selection is removed.  It splits a text node, if needed.
*/
HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
    if (!HTMLArea.is_ie) {
        var sel = this._getSelection();
        var range = this._createRange(sel);
        // remove the current selection
        sel.removeAllRanges();
        range.deleteContents();
        var node = range.startContainer;
        var pos = range.startOffset;
        switch (node.nodeType) {
            case 3: // Node.TEXT_NODE
            // we have to split it at the caret position.
            if (toBeInserted.nodeType == 3) {
                // do optimized insertion
                node.insertData(pos, toBeInserted.data);
                range = this._createRange();
                range.setEnd(node, pos + toBeInserted.length);
                range.setStart(node, pos + toBeInserted.length);
                sel.addRange(range);
            } else {
                node = node.splitText(pos);
                var selnode = toBeInserted;
                if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
                    selnode = selnode.firstChild;
                }
                node.parentNode.insertBefore(toBeInserted, node);
                this.selectNodeContents(selnode);
                this.updateToolbar();
            }
            break;
            case 1: // Node.ELEMENT_NODE
            var selnode = toBeInserted;
            if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
                selnode = selnode.firstChild;
            }
            node.insertBefore(toBeInserted, node.childNodes[pos]);
            this.selectNodeContents(selnode);
            this.updateToolbar();
            break;
        }
    } else {
        return null;  // this function not yet used for IE <FIXME>
    }
};

// Returns the deepest node that contains both endpoints of the selection.
// >MT: add range argument
HTMLArea.prototype.getParentElement = function(sel,range) {
    // <MT
    if(typeof sel == 'undefined')
    {
        sel = this._getSelection();
    }
    // >MT: add range argument
    if(typeof range == 'undefined') {
        range = this._createRange(sel);
    }
    // <MT
    if (HTMLArea.is_ie) {
        switch (sel.type) {
            case "Text":
            case "None":
            // It seems that even for selection of type "None",
            // there _is_ a parent element and it's value is not
            // only correct, but very important to us.  MSIE is
            // certainly the buggiest browser in the world and I
            // wonder, God, how can Earth stand it?
                return range ? range.parentElement() : this._doc.body;
            case "Control":
                return range.item(0);
            default:
                return this._doc.body;
        }
    } else try {
        var p = range.commonAncestorContainer;
        if (!range.collapsed && range.startContainer == range.endContainer &&
        range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
        p = range.startContainer.childNodes[range.startOffset];
        /*
        alert(range.startContainer + ":" + range.startOffset + "\n" +
        range.endContainer + ":" + range.endOffset);
        */
        while (p.nodeType == 3) {
            p = p.parentNode;
        }
        return p;
    } catch (e) {
        return null;
    }
};

// Returns an array with all the ancestor nodes of the selection.
HTMLArea.prototype.getAllAncestors = function() {
    var p = this.getParentElement();
    var a = [];
    while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
        a.push(p);
        p = p.parentNode;
    }
    a.push(this._doc.body);
    return a;
};

// Returns the deepest ancestor of the selection that is of the current type
HTMLArea.prototype._getFirstAncestor = function(sel, types)
{
    var prnt = this._activeElement(sel);
    if(prnt == null)
    {
        try
        {
            prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer);
        }
        catch(e)
        {
            return null;
        }
    }

    if(typeof types == 'string')
    {
        types = [types];
    }

    while(prnt)
    {
        if(prnt.nodeType == 1)
        {
            if(types == null) return prnt;
            if(types.contains(prnt.tagName.toLowerCase()))
            {

                return prnt;
            }
            if(prnt.tagName.toLowerCase() == 'body') break;
            if(prnt.tagName.toLowerCase() == 'table') break;
        }
        prnt = prnt.parentNode;
    }

    return null;
};

/**
* Returns the selected element, if any.  That is,
* the element that you have last selected in the "path"
* at the bottom of the editor, or a "control" (eg image)
*
* @returns null | element
*/
HTMLArea.prototype._activeElement = function(sel)
{
    if(sel == null) return null;
    if(this._selectionEmpty(sel)) return null;

    if(HTMLArea.is_ie)
    {
        if(sel.type.toLowerCase() == "control")
        {
            return sel.createRange().item(0);
        }
        else
        {

            // If it's not a control, then we need to see if
            // the selection is the _entire_ text of a parent node
            // (this happens when a node is clicked in the tree)
            var range = sel.createRange();
            // >MT: add range argument
            var p_elm = this.getParentElement(sel,range);
            // <MT
            if(p_elm.innerHTML == range.htmlText)
            {
                return p_elm;
            }
            /*
            if(p_elm)
            {
            var p_rng = this._doc.body.createTextRange();
            p_rng.moveToElementText(p_elm);
            if(p_rng.isEqual(range))
            {
            return p_elm;
            }
            }

            if(range.parentElement())
            {
            var prnt_range = this._doc.body.createTextRange();
            prnt_range.moveToElementText(range.parentElement());
            if(prnt_range.isEqual(range))
            {
            return range.parentElement();
            }
            }
            */
            return null;
        }
    }
    else
    {
        // For Mozilla we just see if the selection is not collapsed (something is selected)
        // and that the anchor (start of selection) is an element.  This might not be totally
        // correct, we possibly should do a simlar check to IE?
        if(! sel.isCollapsed)
        {
            if(sel.anchorNode.childNodes.length > sel.anchorOffset && sel.anchorNode.childNodes[sel.anchorOffset].nodeType == 1)
            {
                return sel.anchorNode.childNodes[sel.anchorOffset];
            }
            else if(sel.anchorNode.nodeType == 1)
            {
                return sel.anchorNode;
            }
            else
            {
                return sel.anchorNode.parentNode;
            }
        }
        return null;
    }
};


HTMLArea.prototype._selectionEmpty = function(sel)
{
    if(!sel) return true;

    if(HTMLArea.is_ie)
    {
        var range = this._createRange(sel);
        return range ? range.htmlText == '' : true;
    }
    else if(typeof sel.isCollapsed != 'undefined')
    {
        return sel.isCollapsed;
    }

    return true;
};

HTMLArea.prototype._getAncestorBlock = function(sel)
{
    // Scan upwards to find a block level element that we can change or apply to
    var prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement : this._createRange(sel).commonAncestorContainer);

    while(prnt && (prnt.nodeType == 1))
    {
        switch(prnt.tagName.toLowerCase())
        {
            case 'div' :
            case 'p'   :
            case 'address'    :
            case 'blockquote' :
            case 'center'  :
            case 'del'     :
            case 'ins'     :
            case 'pre'     :
            case 'h1'      :
            case 'h2'      :
            case 'h3'      :
            case 'h4'      :
            case 'h5'      :
            case 'h6'      :
            case 'h7'      :
            // Block Element
            return prnt;

            case 'body'     :
            case 'noframes' :
            case 'dd'  :
            case 'li'  :
            case 'th'  :
            case 'td'  :
            case 'noscript' :
            // Halting element (stop searching)
            return null;

            default :
            // Keep lookin
            break;
        }
    }

    return null;
};

HTMLArea.prototype._createImplicitBlock = function(type)
{
    // expand it until we reach a block element in either direction
    // then wrap the selection in a block and return
    var sel = this._getSelection();
    if(HTMLArea.is_ie)
    {
        sel.empty();
    }
    else
    {
        sel.collapseToStart();
    }

    var rng = this._createRange(sel);

    // Expand UP

    // Expand DN
};

HTMLArea.prototype._formatBlock = function(block_format)
{
    var ancestors = this.getAllAncestors();
    var apply_to = null;

    // Block format can be a tag followed with class defs
    //  eg div.blue.left
    var target_tag = null;
    var target_classNames = [ ];

    if(block_format.indexOf('.') >= 0)
    {
        target_tag = block_format.substr(0, block_format.indexOf('.')).toLowerCase();;

        target_classNames = block_format.substr(block_format.indexOf('.'), block_format.length - block_format.indexOf('.')).replace(/\./g, '').replace(/^\s*/, '').replace(/\s*$/, '').split(' ');
    }
    else
    {
        target_tag = block_format.toLowerCase();
    }

    var sel = this._getSelection();
    var rng = this._createRange(sel);
    var apply_to = null;

    if(HTMLArea.is_gecko)
    {
        if(sel.isCollapsed)
        {
            // With no selection we want to apply to the whole contents of the ancestor block
            apply_to = this._getAncestorBlock(sel);
            if(apply_to == null)
            {
                // If there wasn't an ancestor, make one.
                apply_to = this._createImplicitBlock(sel, target_tag);
            }
        }
        else
        {
            // With a selection it's more tricky
            switch(target_tag)
            {

                case 'h1'      :
                case 'h2'      :
                case 'h3'      :
                case 'h4'      :
                case 'h5'      :
                case 'h6'      :
                case 'h7'      :
                apply_to = [ ];
                var search_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7'];
                for(var y = 0; y < search_tags.length; y++)
                {
                    var headers = this._doc.getElementsByTagName(search_tag[y]);
                    for(var x = 0; x < headers.length; x++)
                    {
                        if(sel.containsNode(headers[x]))
                        {
                            apply_to[apply_to.length] = headers[x];
                        }
                    }
                }
                if(apply_to.length > 0) break;
                // If there wern't any in the selection drop through
                case 'div' :
                apply_to = this._doc.createElement(target_tag);
                apply_to.appendChild(rng.extractContents());
                rng.insertNode(apply_to);
                break;

                case 'p'   :
                case 'center'  :
                case 'pre' :
                case 'ins' :
                case 'del' :
                case 'blockquote' :
                case 'address'    :
                apply_to = [ ];
                var paras = this._doc.getElementsByTagName(target_tag);
                for(var x = 0; x < paras.length; x++)
                {
                    if(sel.containsNode(paras[x]))
                    {
                        apply_to[apply_to.length] = paras[x];
                    }
                }

                if(apply_to.length == 0)
                {
                    sel.collapseToStart();
                    return this._formatBlock(block_format);
                }
                break;
            }
        }
    }

};

// Selects the contents inside the given node
HTMLArea.prototype.selectNodeContents = function(node, pos) {
    this.focusEditor();
    this.forceRedraw();
    var range;
    var collapsed = typeof pos == "undefined" ? true : false;
    if (HTMLArea.is_ie) {
        // Tables and Images get selected as "objects" rather than the text contents
        if(collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|select|textarea/))
        {
            range = this._doc.body.createControlRange();
            range.add(node);
        }
        else
        {
            range = this._doc.body.createTextRange();
            range.moveToElementText(node);
            //(collapsed) && range.collapse(pos);
        }
        range.select();
    } else {
        var sel = this._getSelection();
        range = this._doc.createRange();
        // Tables and Images get selected as "objects" rather than the text contents
        if(collapsed && node.tagName && node.tagName.toLowerCase().match(/table|img|input|textarea|select/))
        {
            range.selectNode(node);
        }
        else
        {
            range.selectNodeContents(node);
            //(collapsed) && range.collapse(pos);
        }
        sel.removeAllRanges();
        sel.addRange(range);
    }
};

/** Call this function to insert HTML code at the current position.  It deletes
* the selection, if any.
*/
HTMLArea.prototype.insertHTML = function(html) {
    var sel = this._getSelection();
    var range = this._createRange(sel);
    if (HTMLArea.is_ie) {
        // >MT: add error handling
        if (range.parentElement().document != this._doc) {
            alert('Insertion point was not detected within the editor. Please try again and ensure your cursors insertion point remains within the editable region.');
            return;
        }
        // <MT
        range.pasteHTML(html);
    } else {
        // construct a new document fragment with the given HTML
        var fragment = this._doc.createDocumentFragment();
        var div = this._doc.createElement("div");
        div.innerHTML = html;
        while (div.firstChild) {
            // the following call also removes the node from div
            fragment.appendChild(div.firstChild);
        }
        // this also removes the selection
        var node = this.insertNodeAtSelection(fragment);
    }
};

/**
*  Call this function to surround the existing HTML code in the selection with
*  your tags.  FIXME: buggy!  This function will be deprecated "soon".
*/
HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
    var html = this.getSelectedHTML();
    // the following also deletes the selection
    this.insertHTML(startTag + html + endTag);
};

/// Retrieve the selected block
HTMLArea.prototype.getSelectedHTML = function() {
    var sel = this._getSelection();
    var range = this._createRange(sel);
    var existing = null;
    if (HTMLArea.is_ie) {
        existing = range.htmlText;
    } else {
        existing = HTMLArea.getHTML(range.cloneContents(), false, this);
    }
    return existing;
};

/// Return true if we have some selection
HTMLArea.prototype.hasSelectedText = function() {
    // FIXME: come _on_ mishoo, you can do better than this ;-)
    return this.getSelectedHTML() != '';
};

HTMLArea.prototype._createLink = function(link) {
    var editor = this;
    var outparam = null;
    editor.focusEditor();
    if (typeof link == "undefined") {
        link = this.getParentElement();
        if (link) {
            while (link && !/^a$/i.test(link.tagName))
            link = link.parentNode;
        }
    }
    if (!link) {
        var sel = editor._getSelection();
        var range = editor._createRange(sel);
        var compare = 0;
        if (HTMLArea.is_ie) {
            if(sel.type == "Control")
            {
                compare = range.length;
            }
            else
            {
                compare = range.compareEndPoints("StartToEnd", range);
            }
        } else {
            compare = range.compareBoundaryPoints(range.START_TO_END, range);
        }
        if (compare == 0) {
            alert(HTMLArea._lc("You need to select some text before creating a link"));
            return;
        }
        outparam = {
            f_href : '',
            f_title : '',
            f_target : '',
            f_usetarget : editor.config.makeLinkShowsTarget
        };
    } else
    outparam = {
        f_href   : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
        f_title  : link.title,
        f_target : link.target,
        f_usetarget : editor.config.makeLinkShowsTarget
    };
    this._popupDialog(600, 451, editor.config.URIs["link"], function(param) {
        if (!param)
        return false;
        var a = link;
        if (!a) try {
            editor._doc.execCommand("createlink", false, param.f_href);
            a = editor.getParentElement();
            var sel = editor._getSelection();
            var range = editor._createRange(sel);
            if (!HTMLArea.is_ie) {
                a = range.startContainer;
                if (!/^a$/i.test(a.tagName)) {
                    a = a.nextSibling;
                    if (a == null)
                        a = range.startContainer.parentNode;
                }
            }
        } catch(e) {}
        else {
            var href = param.f_href.trim();
            editor.selectNodeContents(a);
            if (href == "") {
                editor._doc.execCommand("unlink", false, null);
                editor.updateToolbar();
                return false;
            }
            else {
                a.href = href;
            }
        }
        if (!(a && /^a$/i.test(a.tagName)))
            return false;
        a.target = param.f_target.trim();
        a.title = param.f_title.trim();
        editor.selectNodeContents(a);
        editor.updateToolbar();
    }, outparam);
};

// Called when the user clicks on "InsertImage" button.  If an image is already
// there, it will just modify it's properties.
HTMLArea.prototype._insertImage = function(image) {
	// >MT: use the MT image dialog
	if (typeof useMTDialogs != "undefined" && useMTDialogs == true) {
		this._insertImageMT(image);
		return;
	}
	// <MT

    var editor = this;  // for nested functions
    var outparam = null;
    if (typeof image == "undefined") {
        image = this.getParentElement();
        if (image && !/^img$/i.test(image.tagName))
        image = null;
    }
    if (image) outparam = {
        f_base   : editor.config.baseHref,
        f_url    : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
        f_alt    : image.alt,
        f_border : image.border,
        f_align  : image.align,
        f_vert   : image.vspace,
        f_horiz  : image.hspace
    };
    this._popupDialog(400, 200, editor.config.URIs["insert_image"], function(param) {
        if (!param) {  // user must have pressed Cancel
            return false;
        }
        var img = image;
        if (!img) {
            var sel = editor._getSelection();
            var range = editor._createRange(sel);
            editor._doc.execCommand("insertimage", false, param.f_url);
            if (HTMLArea.is_ie) {
                img = range.parentElement();
                // wonder if this works...
                if (img.tagName.toLowerCase() != "img") {
                    img = img.previousSibling;
                }
            } else {
                img = range.startContainer.previousSibling;
                if (!img.tagName) {
                    // if the cursor is at the beginning of the document
                    img = range.startContainer.firstChild;
                }
            }
        } else {
            img.src = param.f_url;
        }

        for (var field in param) {
            var value = param[field];
            switch (field) {
                case "f_alt"    : img.alt    = value; break;
                case "f_border" : img.border = parseInt(value || "0"); break;
                case "f_align"  : img.align  = value; break;
                case "f_vert"   : img.vspace = parseInt(value || "0"); break;
                case "f_horiz"  : img.hspace = parseInt(value || "0"); break;
            }
        }
    }, outparam);
};

// Called when the user clicks the Insert Table button
HTMLArea.prototype._insertTable = function() {
    var sel = this._getSelection();
    var range = this._createRange(sel);
    var editor = this;  // for nested functions
    this._popupDialog(400, 205, editor.config.URIs["insert_table"], function(param) {
        if (!param) {  // user must have pressed Cancel
            return false;
        }
        var doc = editor._doc;
        // create the table element
        var table = doc.createElement("table");
        // assign the given arguments

        for (var field in param) {
            var value = param[field];
            if (!value) {
                continue;
            }
            switch (field) {
                case "f_width"   : table.style.width = value + param["f_unit"]; break;
                case "f_align"   : table.align   = value; break;
                case "f_border"  : table.border  = parseInt(value); break;
                case "f_spacing" : table.cellSpacing = parseInt(value); break;
                case "f_padding" : table.cellPadding = parseInt(value); break;
            }
        }
        var cellwidth = 0;
        if (param.f_fixed)
        cellwidth = Math.floor(100 / parseInt(param.f_cols));
        var tbody = doc.createElement("tbody");
        // >MT: align new table 'top'
        tbody.style.verticalAlign = "top";
        // <MT
        table.appendChild(tbody);
        for (var i = 0; i < param["f_rows"]; ++i) {
            var tr = doc.createElement("tr");
            tbody.appendChild(tr);
            for (var j = 0; j < param["f_cols"]; ++j) {
                var td = doc.createElement("td");
                if (cellwidth)
                td.style.width = cellwidth + "%";
                tr.appendChild(td);
                // >MT: use '&nbsp;' which is cross-platform
                td.innerHTML = HTMLArea.is_ie ? '&nbsp;' : '<br>';
                // <MT
            }
        }
        // >MT: use XHTML
        var parent = editor.getParentElement();
        // <MT
        if (HTMLArea.is_ie) {
            range.pasteHTML(table.outerHTML);
        } else {
            // insert the table
            editor.insertNodeAtSelection(table);
        }
        // >MT: use XHTML
        HTMLArea.makeXHTML(parent, editor.makeXHTMLContext(false));
        // <MT
        return true;
    }, null);
};

/***************************************************
*  Category: EVENT HANDLERS
***************************************************/

// el is reference to the SELECT object
// txt is the name of the select field, as in config.toolbar
HTMLArea.prototype._comboSelected = function(el, txt) {
    this.focusEditor();
    var value = el.options[el.selectedIndex].value;
    switch (txt) {
        // >MT: add fontstyles
    case "fontstyles":
        if (typeof updateFontStyles != "undefined")
        updateFontStyles(this, value);
        else
        this.surroundHTML('<span class="'+value+'">', '</span>');
        break;
        // <MT
        // >MT: use relative font sizes
    case "fontsize":
        if (typeof updateFontSizes != "undefined")
        updateFontSizes(this, txt, value);
        else
        this.execCommand(txt, false, value);
        break;
        // <MT
    case "fontname":
    case "fontsize": this.execCommand(txt, false, value); break;
    case "formatblock":
        // (HTMLArea.is_ie) && (value = "<" + value + ">");
        value = "<" + value + ">"
        this.execCommand(txt, false, value);
        break;
    default:
        // try to look it up in the registered dropdowns
        var dropdown = this.config.customSelects[txt];
        if (typeof dropdown != "undefined") {
            dropdown.action(this);
        } else {
            alert("FIXME: combo box " + txt + " not implemented");
        }
    }
};

// the execCommand function (intercepts some commands and replaces them with
// our own implementation)
HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
    // >MT: add this.preExecCommand
    var p = {cmdID: cmdID, UI: UI, param: param};
    if (typeof this.preExecCommand == 'function' && !this.preExecCommand(p))
        return false;
    cmdID = p.cmdID; UI = p.UI; param = p.param;
    // <MT

    var editor = this;  // for nested functions
    this.focusEditor();
    cmdID = cmdID.toLowerCase();
    // >MT: take an undo snapshot before any user action
    this._undoTakeSnapshot();
    // <MT
    if (HTMLArea.is_gecko) try { this._doc.execCommand('useCSS', false, true); } catch (e) {}; //switch useCSS off (true=off)
    switch (cmdID) {
    case "htmlmode" : this.setMode(); break;
    case "hilitecolor":
        (HTMLArea.is_ie) && (cmdID = "backcolor");
        if (HTMLArea.is_gecko) try { editor._doc.execCommand('useCSS', false, false); } catch (e) {};//switch on useCSS (mozilla bug #279330)
    case "forecolor":
       this._popupDialog(400, 206, editor.config.URIs[cmdID == 'forecolor' ? 'forecolor': 'hilitecolor'], function(color) {
            if (color) { // selection not canceled
                // >MT: add this._onColorChange
                editor.focusEditor();
                if (typeof editor._onColorChange == 'function')
                    editor._onColorChange(cmdID, color);
                else
                // <MT
                    editor._doc.execCommand(cmdID, false, "#" + color);
            }
        }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
        break;
    case "createlink":
		// >MT: use MT-specific dialogs
		if (typeof useMTDialogs != "undefined" && useMTDialogs == true)
			this._createLinkMT(param);
		else
		// <MT
			this._createLink();
	    break;
    case "undo":
    case "redo":
        if (this._customUndo)
            this[cmdID]();
        else
            this._doc.execCommand(cmdID, UI, param);
        break;
    case "inserttable": this._insertTable(); break;
    case "insertimage": this._insertImage(); break;
    case "about":       this._popupDialog(400, 200, editor.config.URIs["about"], null, this); break;
    case "showhelp" :   this._popupDialog(400, 200, editor.config.URIs["help"], null, this); break;
    case "killword":    this._wordClean(); break;
        // >MT: handle removeFormat externally
    case "removeformat":
        this._doc.execCommand(cmdID, UI, param);
        if (typeof removeFormat != "undefined")
            removeFormat(this);
        break;
        // <MT

    case "cut":
    case "copy":
    case "paste":
        try {
            this._doc.execCommand(cmdID, UI, param);
        } catch (e) {
            if (HTMLArea.is_gecko) {
                alert(HTMLArea._lc("The Paste button does not work in Mozilla based web browsers (technical security reasons). Press CTRL-V on your keyboard to paste directly."));
            }
        }
        // >MT: move out of exception handling
        if (cmdID == "paste") {
            if (this.config.killWordOnPaste)
            this._wordClean();
        }
        // <MT
        break;
    case "lefttoright":
    case "righttoleft":
        var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
        var el = this.getParentElement();
        while (el && !HTMLArea.isBlockElement(el))
            el = el.parentNode;
        if (el) {
            if (el.style.direction == dir)
                el.style.direction = "";
            else
                el.style.direction = dir;
        }
        break;
    default: try { this._doc.execCommand(cmdID, UI, param); }
        catch(e) { if (this.config.debug) { alert(e + "\n\nby execCommand(" + cmdID + ");"); } }
    }

    // >MT: add this.postExecCommand
    if (typeof this.postExecCommand == 'function' && !this.postExecCommand(cmdID, UI, param))
        return false;
    // <MT
    this.updateToolbar();
    return false;
};

/** A generic event handler for things that happen in the IFRAME's document.
* This function also handles key bindings. */
HTMLArea.prototype._editorEvent = function(ev) {
    var editor = this;
    var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (!HTMLArea.is_ie && ev.type == "keypress");

    //call events of textarea
    if(typeof editor._textArea['on'+ev.type] == "function") {
        editor._textArea['on'+ev.type]();
    }

    if(HTMLArea.is_gecko && keyEvent && ev.ctrlKey &&  this._unLink && this._unlinkOnUndo)
    {
        if(String.fromCharCode(ev.charCode).toLowerCase() == 'z')
        {
            HTMLArea._stopEvent(ev);
            this._unLink();
            editor.updateToolbar();
            return;
        }
    }

    if (keyEvent)
    {
        for (var i in editor.plugins)
        {
            var plugin = editor.plugins[i].instance;
            if (typeof plugin.onKeyPress == "function")
            if (plugin.onKeyPress(ev))
            return false;
        }
    }

    if (keyEvent && ev.ctrlKey && !ev.altKey)
    {
        var sel = null;
        var range = null;
        var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
        var cmd = null;
        var value = null;
        switch (key) {
        case 'a':
            if (!HTMLArea.is_ie) {
                // KEY select all
                sel = this._getSelection();
                sel.removeAllRanges();
                range = this._createRange();
                range.selectNodeContents(this._doc.body);
                sel.addRange(range);
                HTMLArea._stopEvent(ev);
            }
            break;

            // simple key commands follow
        case 'b': cmd = "bold"; break;
        case 'i': cmd = "italic"; break;
        case 'u': cmd = "underline"; break;
            // >MT: add quick-save
        case 'q':
            if (typeof(saveEntryInline) != 'undefined')
            saveEntryInline();
            break;
            // <MT
            /* >MT: Disabled
        case 's': cmd = "strikethrough"; break;
        case 'l': cmd = "justifyleft"; break;
        case 'e': cmd = "justifycenter"; break;
        case 'r': cmd = "justifyright"; break;
        case 'j': cmd = "justifyfull"; break;
            <MT */
        case 'z': cmd = "undo"; break;
        case 'y': cmd = "redo"; break;
            // >MT: spal handling of 'v', 'n', '0', 'w', 'k', and 'l'
        case 'v':
            var sel = editor._getSelection();
            var hx = this._getFirstAncestor(sel, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
            if (hx) {
                /* splitting heading
                var evt = document.createEventObject();
                evt.keyCode = 13; // Enter
                document.fireEvent("onkeydown",evt);
                document.fireEvent("onkeydown",evt);
                var r = editor._createRange(sel);
                r.moveToElementText(hx.nextSibling);
                editor.execCommand("formatblock", false, "<p>");
                */
                var p = hx.ownerDocument.createElement('p');
                p.innerHTML = "<br />";
                hx.parentNode.insertBefore(p, hx.nextSibling);
                if (HTMLArea.is_ie) {
                    var r = editor._createRange(sel);
                    r.moveToElementText(p);
                    r.select();
                } else {
                    sel.removeAllRanges();
                    var r = editor._createRange(sel);
                    r.selectNodeContents(p);
                    sel.addRange(r);
                }
            }
            if (HTMLArea.is_ie || editor.config.htmlareaPaste)
                cmd = "paste";
            else
            if (HTMLArea.is_gecko) 
            {
            /* >MT: not yet:
                var r = editor._createRange(sel);
                sel.removeAllRanges();
                r.deleteContents();
                r.collapse(true);
                if (r.startOffset > 0) {
                    r.setStart(r.startContainer, r.startOffset-1);
                    r.collapse(true);
                }
                MindTouch.insertHTML(r, "<span class='pasteTag start'>|</span>");
                var n = r.startContainer;
                r.selectNode(n.nextSibling.nextSibling);
                var c = r.startContainer.childNodes[r.startOffset];
                while (c.nodeType == 1)
                    c = c.firstChild;
                r.setStart(c, 2);
                r.collapse(true);
                MindTouch.insertHTML(r, "<span class='pasteTag end'>|</span>");
                r.setStart(c, 1);
                r.collapse(true);
                sel.addRange(r);
            /* <MT */
                setTimeout(function() { editor._wordClean(); }, 50);
            }
            break;
        case 'n':
        case '0':
            cmd = "formatblock";
            value = HTMLArea.is_ie ? "<p>" : "p";
            break;
        case 'w':
            value = 'no-popup';
        case 'k':
            cmd = "createlink";
            break;
        case 'l':
            if (this._doc.queryCommandState("insertunorderedlist"))
                cmd = "insertorderedlist";
            else if (this._doc.queryCommandState("insertorderedlist")) {
                var ancestors = this.getAllAncestors();
                var count = 0;
                for (var i = 0; i < ancestors.length; ++i) {
                    if (/^(ul|ol)$/i.test(ancestors[i].tagName))
                    ++count;
                }
                cmd = count >= 2 ? "insertunorderedlist" : "outdent";
            } else
                cmd = "insertunorderedlist";
            break;
            // <MT

            // headings
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
            cmd = "formatblock";
            // >MT: offset <h> tags
            value = "h" + (parseInt(key)+1);
            // <MT
            if (HTMLArea.is_ie)
            value = "<" + value + ">";
            break;
        }
        if (cmd) {
            // execute simple command
            this.execCommand(cmd, false, value);
            HTMLArea._stopEvent(ev);
        }
    }
    else if (keyEvent)
    {
        // >MT: add this.handleTab
        if (ev.keyCode == 9 && typeof this.handleTab == 'function' && !this.handleTab(ev))
        return;
        // <MT

        // IE's textRange and selection object is woefully inadequate,
        // which means this fancy stuff is gecko only sorry :-|
        // Die Bill, Die.  (IE supports it somewhat nativly though)
        if(HTMLArea.is_gecko)
        {
            var s = editor._getSelection()
            var autoWrap = function (textNode, tag)
            {
                var rightText = textNode.nextSibling;
                if(typeof tag == 'string') tag = editor._doc.createElement(tag);
                var a = textNode.parentNode.insertBefore(tag, rightText);
                HTMLArea.removeFromParent(textNode);
                a.appendChild(textNode);
                rightText.data = ' ' + rightText.data;

                if(HTMLArea.is_ie)
                {
                    var r = editor._createRange(s);
                    s.moveToElementText(rightText);
                    s.move('character', 1);
                }
                else
                {
                    s.collapse(rightText, 1);
                }
                HTMLArea._stopEvent(ev);

                editor._unLink = function()
                {
                    var t = a.firstChild;
                    a.removeChild(t);
                    a.parentNode.insertBefore(t, a);
                    HTMLArea.removeFromParent(a);
                    editor._unLink = null;
                    editor._unlinkOnUndo = false;
                }
                editor._unlinkOnUndo = true;

                return a;
            };

            switch(ev.which)
            {
                // Space, see if the text just typed looks like a URL, or email address
                // and link it appropriatly
                case 32:
                {
                    if(s && s.isCollapsed && s.anchorNode.nodeType == 3 && s.anchorNode.data.length > 3 && s.anchorNode.data.indexOf('.') >= 0)
                    {
                        var midStart = s.anchorNode.data.substring(0,s.anchorOffset).search(/\S{4,}$/);
                        if(midStart == -1) break;

                        if(this._getFirstAncestor(s, 'a'))
                        {
                            break; // already in an anchor
                        }

                        var matchData = s.anchorNode.data.substring(0,s.anchorOffset).replace(/^.*?(\S*)$/, '$1');
                        var m        = matchData.match(HTMLArea.RE_email);
                        if(m)
                        {
                            var leftText  = s.anchorNode;
                            var rightText = leftText.splitText(s.anchorOffset);
                            var midText   = leftText.splitText(midStart);

                            autoWrap(midText, 'a').href = 'mailto:' + m[0];
                            break;
                        }

                        var m = matchData.match(HTMLArea.RE_url);
                        if(m)
                        {
                            var leftText  = s.anchorNode;
                            var rightText = leftText.splitText(s.anchorOffset);
                            var midText   = leftText.splitText(midStart);
                            autoWrap(midText, 'a').href = (m[1] ? m[1] : 'http://') + m[2];
                            break;
                        }
                    }

                }
                break;

                default :
                {
                    if(ev.keyCode == 27 || (this._unlinkOnUndo && ev.ctrlKey && ev.which == 122) )
                    {
                        if(this._unLink)
                        {
                            this._unLink();
                            HTMLArea._stopEvent(ev);
                        }
                        break;
                    }
                    else if(ev.which || ev.keyCode == 8 || ev.keyCode == 46)
                    {
                        this._unlinkOnUndo = false;

                        if(s.anchorNode && s.anchorNode.nodeType == 3)
                        {
                            // See if we might be changing a link
                            var a = this._getFirstAncestor(s, 'a');
                            if(!a) break; // not an anchor
                            if(!a._updateAnchTimeout)
                            {
                                if(   s.anchorNode.data.match(HTMLArea.RE_email)
                                && (a.href.match('mailto:' + s.anchorNode.data.trim()))
                                )
                                {
                                    var textNode = s.anchorNode;
                                    var fn = function()
                                    {
                                        a.href = 'mailto:' + textNode.data.trim();
                                        a._updateAnchTimeout = setTimeout(fn, 250);
                                    }
                                    a._updateAnchTimeout = setTimeout(fn, 250);
                                    break;
                                }

                                var m = s.anchorNode.data.match(HTMLArea.RE_url);
                                if(m &&  a.href.match(s.anchorNode.data.trim()) )
                                {
                                    var textNode = s.anchorNode;
                                    var fn = function()
                                    {
                                        var m = textNode.data.match(HTMLArea.RE_url);
                                        a.href = (m[1] ? m[1] : 'http://') + m[2];
                                        a._updateAnchTimeout = setTimeout(fn, 250);
                                    }
                                    a._updateAnchTimeout = setTimeout(fn, 250);
                                }
                            }
                        }

                    }
                }
                break;
            }
        }

        // other keys here
        switch (ev.keyCode)
        {
            case 13: // KEY enter
            if (HTMLArea.is_gecko && !ev.shiftKey && this.config.mozParaHandler == 'dirty' )
            {
                this.dom_checkInsertP();
                HTMLArea._stopEvent(ev);
            }
            break;
            case 8: // KEY backspace
            case 46: // KEY delete
            if (HTMLArea.is_gecko && !ev.shiftKey) {
                if (this.dom_checkBackspace())
                HTMLArea._stopEvent(ev);
            } else if (HTMLArea.is_ie) {
                // >MT: add param
                if (this.ie_checkBackspace(ev))
                // <MT
                HTMLArea._stopEvent(ev);
            }
            break;
        }
    }

    // update the toolbar state after some time
    if (editor._timerToolbar) {
        clearTimeout(editor._timerToolbar);
    }
    editor._timerToolbar = setTimeout(function() {
        editor.updateToolbar();
        editor._timerToolbar = null;
        // >MT: fix issue with Mac FF and slowdown on typing
    }, 750);
    // <MT
};

HTMLArea.prototype.convertNode = function(el, newTagName) {
    var newel = this._doc.createElement(newTagName);
    while (el.firstChild)
    newel.appendChild(el.firstChild);
    return newel;
};

// >MT: add param
HTMLArea.prototype.ie_checkBackspace = function(ev) {
    // <MT
    var sel = this._getSelection();
    if(sel.type == 'Control')
    {
        var elm = this._activeElement(sel);
        HTMLArea.removeFromParent(elm);
        return true;
    }

    // This bit of code preseves links when you backspace over the
    // endpoint of the link in IE.  Without it, if you have something like
    //    link_here |
    // where | is the cursor, and backspace over the last e, then the link
    // will de-link, which is a bit tedious
    // >MT: add this.handleTab
    if (typeof this.handleTab == 'function' && this.handleTab(ev))
		return true;
    // <MT
    var range = this._createRange(sel);
    if (!range)
		return false;
    var r2 = range.duplicate();
    r2.moveStart("character", -1);
    var a = r2.parentElement();
    if (a != range.parentElement() &&
		/^a$/i.test(a.tagName)) {
        r2.collapse(true);
        r2.moveEnd("character", 1);
        r2.pasteHTML('');
        r2.select();
        return true;
    }
};

HTMLArea.prototype.dom_checkBackspace = function() {
    var self = this;
    var sel = self._getSelection();
    var range = self._createRange(sel);
    var SC = range.startContainer;
    var SO = range.startOffset;

    // >MT: don't merge to <a> tags following eachother
    var el = SC.nodeType == 1 ? SC : SC.parentNode;
    var pn = el.parentNode;
    var ppn = pn ? pn.previousSibling : null;
    if (ppn && ppn.nodeType == 3 && ppn.textContent.trim().length == 0)
        ppn = ppn.previousSibling;
    var lc = ppn ? ppn.lastChild : null;
    if (SO == 0 && /^a$/i.test(el.tagName) && pn && /^p$/i.test(pn.tagName) && ppn && /^p$/i.test(ppn.tagName) && lc && /^a$/i.test(lc.tagName)) {
        ppn.appendChild(this._doc.createElement('BR'));
        return false;
    }
    // <MT

    setTimeout(function() {
        var SC = range.startContainer;
        var SO = range.startOffset;
        var EC = range.endContainer;
        var EO = range.endOffset;
        var newr = SC.nextSibling;
        if (SC.nodeType == 3)
            SC = SC.parentNode;
        if (!/\S/.test(SC.tagName)) {
            var p = document.createElement("p");
            while (SC.firstChild)
                p.appendChild(SC.firstChild);
            SC.parentNode.insertBefore(p, SC);
            HTMLArea.removeFromParent(SC);
            var r = range.cloneRange();
            r.setStartBefore(newr);
            r.setEndAfter(newr);
            r.extractContents();
            sel.removeAllRanges();
            sel.addRange(r);
        }
    }, 10);
    // >MT: proper return value
    return false;
    // <MT
};

/** The idea here is
* 1. See if we are in a block element
* 2. If we are not, then wrap the current "block" of text into a paragraph
* 3. Now that we have a block element, select all the text between the insertion point
*    and just AFTER the end of the block
*    eg <p>The quick |brown fox jumped over the lazy dog.</p>|
*                     ---------------------------------------
* 4. Extract that from the document, making
*       <p>The quick </p>
*    and a document fragment with
*       <p>brown fox jumped over the lazy dog.</p>
* 5. Reinsert it just after the block element
*       <p>The quick </p><p>brown fox jumped over the lazy dog.</p>
*
* Along the way, allow inserting blank paragraphs, which will look like <p><br/></p>
*/

HTMLArea.prototype.dom_checkInsertP = function() {

    // Get the insertion point, we'll scrub any highlighted text the user wants rid of while we are there.
    var sel = this._getSelection();
    var range = this._createRange(sel);
    if (!range.collapsed)
    {
        range.deleteContents();
    }
    this.deactivateEditor();
    //sel.removeAllRanges();
    //sel.addRange(range);

    var SC = range.startContainer;
    var SO = range.startOffset;
    var EC = range.endContainer;
    var EO = range.endOffset;

    // If the insertion point is character 0 of the
    // document, then insert a space character that we will wrap into a paragraph
    // in a bit.
    if (SC == EC && SC == body && !SO && !EO)
    {
        p = this._doc.createTextNode(" ");
        body.insertBefore(p, body.firstChild);
        range.selectNodeContents(p);
        SC = range.startContainer;
        SO = range.startOffset;
        EC = range.endContainer;
        EO = range.endOffset;
    }

    // See if we are in a block element, if so, great.
    var p     = this.getAllAncestors();

    var block = null;
    var body = this._doc.body;
    for (var i = 0; i < p.length; ++i)
    {
        if(HTMLArea.isParaContainer(p[i]))
        {
            break;
        }
        else if (HTMLArea.isBlockElement(p[i]) && !/body|html/i.test(p[i].tagName))
        {
            block = p[i];
            break;
        }
    }

    // If not in a block element, we'll have to turn some stuff into a paragraph
    if (!block)
    {
        // We want to wrap as much stuff as possible into the paragraph in both directions
        // from the insertion point.  We start with the start container and walk back up to the
        // node just before any of the paragraph containers.
        var wrap = range.startContainer;
        while(wrap.parentNode && !HTMLArea.isParaContainer(wrap.parentNode))
        {
            wrap = wrap.parentNode;
        }
        var start = wrap;
        var end   = wrap;

        // Now we walk up the sibling list until we hit the top of the document
        // or an element that we shouldn't put in a p (eg other p, div, ul, ol, table)
        while(start.previousSibling)
        {
            if(start.previousSibling.tagName)
            {
                if(!HTMLArea.isBlockElement(start.previousSibling))
                {
                    start = start.previousSibling;
                }
                else
                {
                    break;
                }
            }
            else
            {
                start = start.previousSibling;
            }
        }

        // Same down the list
        while(end.nextSibling)
        {
            if(end.nextSibling.tagName)
            {
                if(!HTMLArea.isBlockElement(end.nextSibling))
                {
                    end = end.nextSibling;
                }
                else
                {
                    break;
                }
            }
            else
            {
                end = end.nextSibling;
            }
        }

        // Select the entire block
        range.setStartBefore(start);
        range.setEndAfter(end);

        // Make it a paragraph
        range.surroundContents(this._doc.createElement('p'));

        // Which becomes the block element
        block = range.startContainer.firstChild;

        // And finally reset the insertion point to where it was originally
        range.setStart(SC, SO);
    }

    // The start point is the insertion point, so just move the end point to immediatly
    // after the block
    range.setEndAfter(block);

    // Extract the range, to split the block
    // If we just did range.extractContents() then Mozilla does wierd stuff
    // with selections, but if we clone, then remove the original range and extract
    // the clone, it's quite happy.
    var r2 = range.cloneRange();
    sel.removeRange(range);
    var df = r2.extractContents();

    if(df.childNodes.length == 0)
    {
        df.appendChild(this._doc.createElement('p'));
        df.firstChild.appendChild(this._doc.createElement('br'));
    }

    if(df.childNodes.length > 1)
    {
        var nb = this._doc.createElement('p');
        while(df.firstChild)
        {
            var s = df.firstChild;
            df.removeChild(s);
            nb.appendChild(s);
        }
        df.appendChild(nb);
    }

    // If the original block is empty, put a nsbp in it.
    if (!/\S/.test(block.innerHTML))
    block.innerHTML = "&nbsp;";

    p = df.firstChild;
    if (!/\S/.test(p.innerHTML))
    p.innerHTML = "<br />";

    // If the new block is empty and it's a heading, make it a paragraph
    // note, the new block is empty when you are hitting enter at the end of the existing block
    if (/^\s*<br\s*\/?>\s*$/.test(p.innerHTML) && /^h[1-6]$/i.test(p.tagName))
    {
        df.appendChild(this.convertNode(p, "p"));
        df.removeChild(p);
    }

    var newblock = block.parentNode.insertBefore(df.firstChild, block.nextSibling);

    // Select the range (to set the insertion)
    // collapse to the start of the new block
    //  (remember the block might be <p><br/></p>, so if we collapsed to the end the <br/> would be noticable)

    //range.selectNode(newblock.firstChild);
    //range.collapse(true);

    this.activateEditor();

    var sel = this._getSelection();
    sel.removeAllRanges();
    sel.collapse(newblock,0);

    // scroll into view
    this.scrollToElement(newblock);

    //this.forceRedraw();

};

HTMLArea.prototype.scrollToElement = function(e)
{
    if(HTMLArea.is_gecko)
    {
        var top  = 0;
        var left = 0;
        while(e)
        {
            top  += e.offsetTop;
            left += e.offsetLeft;
            if(e.offsetParent && e.offsetParent.tagName.toLowerCase() != 'body')
            {
                e = e.offsetParent;
            }
            else
            {
                e = null;
            }
        }
        this._iframe.contentWindow.scrollTo(left, top);
    }
};

// retrieve the HTML
HTMLArea.prototype.getHTML = function() {
    var html = '';
    switch (this._editMode) {
        case "wysiwyg"  :
        {
            if (!this.config.fullPage)
            html = HTMLArea.getHTML(this._doc.body, false, this);
            else
            html = this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
            break;
        }
        case "textmode" :
        {
            html = this._textArea.value;
            break;
        }
        default:
        {
            alert("Mode <" + mode + "> not defined!");
            return false;
        }
    }
    return html;
};

HTMLArea.prototype.outwardHtml = function(html)
{
    html = html.replace(/<(\/?)b(\s|>|\/)/ig, "<$1strong$2");
    html = html.replace(/<(\/?)i(\s|>|\/)/ig, "<$1em$2");
    html = html.replace(/<(\/?)strike(\s|>|\/)/ig, "<$1del$2");
    // >MT: also handle <u> as <ins>
    html = html.replace(/<(\/?)u(\s|>|\/)/ig, "<$1ins$2");
    // <MT

    // replace window.open to that any clicks won't open a popup in designMode
    html = html.replace("onclick=\"try{if(document.designMode &amp;&amp; document.designMode == 'on') return false;}catch(e){} window.open(", "onclick=\"window.open(");

    // Figure out what our server name is, and how it's referenced
    var serverBase = location.href.replace(/(https?:\/\/[^\/]*)\/.*/, '$1') + '/';

    // IE puts this in can't figure out why
    html = html.replace(/https?:\/\/null\//g, serverBase);

    // Make semi-absolute links to be truely absolute
    //  we do this just to standardize so that special replacements knows what
    //  to expect
    html = html.replace(/((href|src|background)=[\'\"])\/+/ig, '$1' + serverBase);

    html = this.outwardSpecialReplacements(html);

    html = this.fixRelativeLinks(html);

    if(this.config.sevenBitClean)
    {
        html = html.replace(/[^ -~\r\n\t]/g, function(c){ return '&#'+c.charCodeAt(0)+';';});
    }

    // ticket:56, the "greesemonkey" plugin for Firefox adds this junk,
    // so we strip it out.  Original submitter gave a plugin, but that's
    // a bit much just for this IMHO - james
    if(HTMLArea.is_gecko)
    {
        html = html.replace(/<script[\s]*src[\s]*=[\s]*['"]chrome:\/\/.*?["']>[\s]*<\/script>/ig, '');
    }

    return html;
};

HTMLArea.prototype.inwardHtml = function(html)
{
    // Midas uses b and i instead of strong and em, um, hello,
    // mozilla, this is the 21st century calling!
    if (HTMLArea.is_gecko) {
        html = html.replace(/<(\/?)strong(\s|>|\/)/ig, "<$1b$2");
        html = html.replace(/<(\/?)em(\s|>|\/)/ig, "<$1i$2");
        html = html.replace(/<\/a>\s*<\/(li|td|th|p|blockquote|dl|pre)>/ig, "</a>&nbsp;</$1>");
    }
    // >MT: also handle <u> as <ins>, and <strike> as <del>
    html = html.replace(/<(\/?)del(\s|>|\/)/ig, "<$1strike$2");
    html = html.replace(/<(\/?)ins(\s|>|\/)/ig, "<$1u$2");
    // <MT

    // replace window.open to that any clicks won't open a popup in designMode
    html = html.replace("onclick=\"window.open(", "onclick=\"try{if(document.designMode &amp;&amp; document.designMode == 'on') return false;}catch(e){} window.open(");

    html = this.inwardSpecialReplacements(html);

    // For IE's sake, make any URLs that are semi-absolute (="/....") to be
    // truely absolute
    var nullRE = new RegExp('((href|src|background)=[\'"])/+', 'gi');
    html = html.replace(nullRE, '$1' + location.href.replace(/(https?:\/\/[^\/]*)\/.*/, '$1') + '/');

    html = this.fixRelativeLinks(html);
    return html;
};

HTMLArea.prototype.outwardSpecialReplacements = function(html)
{
    for(var i in this.config.specialReplacements)
    {
        var from = this.config.specialReplacements[i];
        var to   = i;
        // alert('out : ' + from + '=>' + to);
        var reg = new RegExp(from.replace(HTMLArea.RE_Specials, '\\$1'), 'g');
        html = html.replace(reg, to.replace(/\$/g, '$$$$'));
        //html = html.replace(from, to);
    }
    return html;
};

HTMLArea.prototype.inwardSpecialReplacements = function(html)
{
    // alert("inward");
    for(var i in this.config.specialReplacements)
    {
        var from = i;
        var to   = this.config.specialReplacements[i];
        // alert('in : ' + from + '=>' + to);
        //
        // html = html.replace(reg, to);
        // html = html.replace(from, to);
        var reg = new RegExp(from.replace(HTMLArea.RE_Specials, '\\$1'), 'g');
        html = html.replace(reg, to.replace(/\$/g, '$$$$')); // IE uses doubled dollar signs to escape backrefs, also beware that IE also implements $& $_ and $' like perl.
    }
    return html;
};


HTMLArea.prototype.fixRelativeLinks = function(html)
{

    if(typeof this.config.stripSelfNamedAnchors != 'undefined' && this.config.stripSelfNamedAnchors)
    {
        var stripRe = new RegExp(document.location.href.replace(HTMLArea.RE_Specials, '\\$1') + '(#[^\'" ]*)', 'g');
        html = html.replace(stripRe, '$1');
    }


    if(typeof this.config.stripBaseHref != 'undefined' && this.config.stripBaseHref)
    {
        var baseRe = null
        if(typeof this.config.baseHref != 'undefined' && this.config.baseHref != null)
        {
            baseRe = new RegExp(this.config.baseHref.replace(HTMLArea.RE_Specials, '\\$1'), 'g');
        }
        else
        {
            baseRe = new RegExp(document.location.href.replace(/([^\/]*\/?)$/, '').replace(HTMLArea.RE_Specials, '\\$1'), 'g');
        }

        html = html.replace(baseRe, '');
    }

    if(HTMLArea.is_ie)
    {
        // This is now done in inward & outward
        // Don't know why but IE is doing this (putting http://null/ on links?!
        // alert(html);
        // var nullRE = new RegExp('https?:\/\/null\/', 'g');
        // html = html.replace(nullRE, location.href.replace(/(https?:\/\/[^\/]*\/).*/, '$1'));
        // alert(html);
    }

    return html;
};

// retrieve the HTML (fastest version, but uses innerHTML)
HTMLArea.prototype.getInnerHTML = function() {
    if(!this._doc.body) return '';
    switch (this._editMode) {
      case "wysiwyg"  :
        if (!this.config.fullPage)
        // return this._doc.body.innerHTML;
          html = this._doc.body.innerHTML;
        else
          html = this.doctype + "\n" + this._doc.documentElement.innerHTML;
        break;
      case "textmode" :
        html = this._textArea.value;
        break;
      default:
        alert("Mode <" + mode + "> not defined!");
        return false;
    }

    return html;
};

// completely change the HTML inside
HTMLArea.prototype.setHTML = function(html) {
    if (!this.config.fullPage)
    {
        this._doc.body.innerHTML = html;
    }
    else
    {
        this.setFullHTML(html);
    }
    this._textArea.value = html;
};

// sets the given doctype (useful when config.fullPage is true)
HTMLArea.prototype.setDoctype = function(doctype) {
    this.doctype = doctype;
};

/***************************************************
*  Category: UTILITY FUNCTIONS
***************************************************/

// variable used to pass the object to the popup editor window.
HTMLArea._object = null;

// function that returns a clone of the given object
HTMLArea.cloneObject = function(obj) {
    if (!obj) return null;
    var newObj = new Object;

    // check for array objects
    // >MT: fix FF 1.5 issue
    if (obj.constructor.toString().indexOf("function Array(") >= 0) {
        // <MT
        newObj = obj.constructor();
    }

    // check for function objects (as usual, IE is fucked up)
    // >MT: fix FF 1.5 issue
    if (obj.constructor.toString().indexOf("function Function(") >= 0) {
        // <MT
        newObj = obj; // just copy reference to it
    } else for (var n in obj) {
        var node = obj[n];
        if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
        else                         { newObj[n] = node; }
    }

    return newObj;
};

// FIXME!!! this should return false for IE < 5.5
HTMLArea.checkSupportedBrowser = function() {
    if (HTMLArea.is_gecko) {
        if (navigator.productSub < 20021201) {
            alert(_lang_browsernotsupported);
            return false;
        }
        if (navigator.productSub < 20030210) {
            alert(_lang_browsernotsupported);
        }
    }
    var ret = HTMLArea.is_gecko || HTMLArea.is_ie || (HTMLArea.enable_opera && HTMLArea.is_opera);
    if (!ret) alert(_lang_browsernotsupported);
    return ret;
};

// selection & ranges

// >MT: Restore a cached selection
HTMLArea.prototype._selectRange = function(rng) {
    if (HTMLArea.is_ie) {
        rng.select();
    } else {
        alert ('setSelection is unsupported on firefox.');
    }
}
// <MT

// returns the current selection object
HTMLArea.prototype._getSelection = function() {
    if (HTMLArea.is_ie) {
        return this._doc.selection;
    } else {
        return this._iframe.contentWindow.getSelection();
    }
};

// returns a range for the current selection
HTMLArea.prototype._createRange = function(sel) {
    if (HTMLArea.is_ie) {
        // >MT: allow empty argument
        if (typeof sel == "undefined")
            sel = this._getSelection();
        // <MT
        try {return sel.createRange();} catch(e) {return null;}
    } else {
        this.activateEditor();
        // >MT: allow empty argument
        if (sel == null || typeof sel == "undefined")
            sel = this._getSelection();
        // <MT
        // >MT: handle exception different
        try {
            if (sel != null)
                return sel.getRangeAt(0);
        } catch(e) {}
        // <MT
        return this._doc.createRange();
    }
};

// event handling

/** Event Flushing
*  To try and work around memory leaks in the rather broken
*  garbage collector in IE, HTMLArea.flushEvents can be called
*  onunload, it will remove any event listeners (that were added
*  through _addEvent(s)) and clear any DOM-0 events.
*/
HTMLArea._eventFlushers = [ ];
HTMLArea.flushEvents = function()
{
    var x = 0;
    var e = null;
    while(e = HTMLArea._eventFlushers.pop())
    {
        if(e.length == 3)
        {
            HTMLArea._removeEvent(e[0], e[1], e[2]);
            x++;
        }
        else if (e.length == 2)
        {
            try {
                e[0]['on' + e[1]] = null;
                e[0]._xinha_dom0Events[e[1]] = null;
            } catch (ex) {}
            x++;
        }
    }

    /*
    // This code is very agressive, and incredibly slow in IE, so I've disabled it.

    if(document.all)
    {
    for(var i = 0; i < document.all.length; i++)
    {
    for(var j in document.all[i])
    {
    if(/^on/.test(j) && typeof document.all[i][j] == 'function')
    {
    document.all[i][j] = null;
    x++;
    }
    }
    }
    }
    */

    // alert('Flushed ' + x + ' events.');
};

HTMLArea._addEvent = function(el, evname, func) {
    if (HTMLArea.is_ie || HTMLArea.is_opera) {
        el.attachEvent("on" + evname, func);
    } else {
        el.addEventListener(evname, func, true);
    }
    HTMLArea._eventFlushers.push([el, evname, func]);
};

HTMLArea._addEvents = function(el, evs, func) {
    for (var i = evs.length; --i >= 0;) {
        HTMLArea._addEvent(el, evs[i], func);
    }
};

HTMLArea._removeEvent = function(el, evname, func) {
    if (HTMLArea.is_ie) {
        el.detachEvent("on" + evname, func);
    } else {
        el.removeEventListener(evname, func, true);
    }
};

HTMLArea._removeEvents = function(el, evs, func) {
    for (var i = evs.length; --i >= 0;) {
        HTMLArea._removeEvent(el, evs[i], func);
    }
};

HTMLArea._stopEvent = function(ev) {
    if (HTMLArea.is_ie) {
        ev.cancelBubble = true;
        ev.returnValue = false;
    } else {
        ev.preventDefault();
        ev.stopPropagation();
    }
};

/**
* Adds a standard "DOM-0" event listener to an element.
* The DOM-0 events are those applied directly as attributes to
* an element - eg element.onclick = stuff;
*
* By using this function instead of simply overwriting any existing
* DOM-0 event by the same name on the element it will trigger as well
* as the existing ones.  Handlers are triggered one after the other
* in the order they are added.
*
* Remember to return true/false from your handler, this will determine
* whether subsequent handlers will be triggered (ie that the event will
* continue or be canceled).
*
*/

HTMLArea.addDom0Event = function(el, ev, fn)
{
    HTMLArea._prepareForDom0Events(el, ev);
    el._xinha_dom0Events[ev].unshift(fn);
};


/**
* See addDom0Event, the difference is that handlers registered using
* prependDom0Event will be triggered before existing DOM-0 events of the
* same name on the same element.
*/

HTMLArea.prependDom0Event = function(el, ev, fn)
{
    HTMLArea._prepareForDom0Events(el, ev);
    el._xinha_dom0Events[ev].push(fn);
};

/**
* Prepares an element to receive more than one DOM-0 event handler
* when handlers are added via addDom0Event and prependDom0Event.
*/
HTMLArea._prepareForDom0Events = function(el, ev)
{
    // Create a structure to hold our lists of event handlers
    if(typeof el._xinha_dom0Events == 'undefined')
    {
        el._xinha_dom0Events = { };
        HTMLArea.freeLater(el, '_xinha_dom0Events');
    }

    // Create a list of handlers for this event type
    if(typeof el._xinha_dom0Events[ev] == 'undefined')
    {
        el._xinha_dom0Events[ev] = [ ];
        if(typeof el['on'+ev] == 'function')
        {
            el._xinha_dom0Events[ev].push(el['on'+ev]);
        }

        // Make the actual event handler, which runs through
        // each of the handlers in the list and executes them
        // in the correct context.
        el['on'+ev] = function(event)
        {
            var a = el._xinha_dom0Events[ev];
            // call previous submit methods if they were there.
            var allOK = true;
            for (var i = a.length; --i >= 0;)
            {
                // We want the handler to be a member of the form, not the array, so that "this" will work correctly
                el._xinha_tempEventHandler = a[i];
                if(el._xinha_tempEventHandler(event) == false)
                {
                    el._xinha_tempEventHandler = null;
                    allOK = false;
                    break;
                }
                el._xinha_tempEventHandler = null;
            }
            return allOK;
        };

        HTMLArea._eventFlushers.push([el, ev]);
    }
};

HTMLArea.prototype.notifyOn = function(ev, fn)
{
    if(typeof this._notifyListeners[ev] == 'undefined')
    {
        this._notifyListeners[ev] = [ ];
        HTMLArea.freeLater(this, '_notifyListeners');
    }

    this._notifyListeners[ev].push(fn);
};

HTMLArea.prototype.notifyOf = function(ev, args)
{

    if(this._notifyListeners[ev])
    {
        for(var i = 0; i < this._notifyListeners[ev].length; i++)
        {
            this._notifyListeners[ev][i](ev, args);
        }
    }
};


HTMLArea._removeClass = function(el, className) {
    if (!(el && el.className)) {
        return;
    }
    var cls = el.className.split(" ");
    var ar = new Array();
    for (var i = cls.length; i > 0;) {
        if (cls[--i] != className) {
            ar[ar.length] = cls[i];
        }
    }
    el.className = ar.join(" ");
};

HTMLArea._addClass = function(el, className) {
    // remove the class first, if already there
    HTMLArea._removeClass(el, className);
    el.className += " " + className;
};

HTMLArea._hasClass = function(el, className) {
    if (!(el && el.className)) {
        return false;
    }
    var cls = el.className.split(" ");
    for (var i = cls.length; i > 0;) {
        if (cls[--i] == className) {
            return true;
        }
    }
    return false;
};

HTMLArea._blockTags = " body form textarea fieldset ul ol dl li div " +
"p h1 h2 h3 h4 h5 h6 quote pre table thead " +
"tbody tfoot tr td th iframe address blockquote";
HTMLArea.isBlockElement = function(el) {
    return el && el.nodeType == 1 && (HTMLArea._blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
};

HTMLArea._paraContainerTags = " body td th caption fieldset div ";
HTMLArea.isParaContainer = function(el)
{
    return el && el.nodeType == 1 && (HTMLArea._paraContainerTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
};

HTMLArea._closingTags = " head script style div span tr td tbody table em strong b i strike code cite dfn abbr acronym font a title textarea select form iframe ";
HTMLArea.needsClosingTag = function(el) {
    return el && el.nodeType == 1 && (HTMLArea._closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
};

// performs HTML encoding of some given string
// >MT: use compiled regex
HTMLArea.htmlEncode_regEx = HTMLArea.compileRegex(/[&<>\x22\u0080-\uFFFF]/g);
// <MT
HTMLArea.htmlEncode = function(str) {
    if(typeof str.replace == 'undefined') str = str.toString();
    // >MT: use compiled regex
    return str.replace(HTMLArea.htmlEncode_regEx, function(s,b) {
        switch (s) {
        case '&': return "&amp;";
        case '<': return "&lt;";
        case '>': return "&gt;";
        case "\xA0": return "&nbsp;";
        case '"': return "&quot;";
        default: return "&#"+s.charCodeAt(0)+";";
        }
    });
    // <MT
};

// Retrieves the HTML code from the given node.   This is a replacement for
// getting innerHTML, using standard DOM calls.
// Wrapper catch a Mozilla-Exception with non well formed html source code
HTMLArea.getHTML = function(root, outputRoot, editor) {
    try {
        return HTMLArea.getHTMLWrapper(root,outputRoot,editor).trim();
    }
    catch(e){
        alert(HTMLArea._lc('Your Document is not well formed. Check JavaScript console for details.'));
        return editor._iframe.contentWindow.document.body.innerHTML;
    }
};

HTMLArea.getHTMLWrapper = function(root, outputRoot, editor, indent, isPre) {
    var html = "";
    if(!indent) indent = '';

    // >MT: fix just in case
    if (root == null)
        return html;
    // <MT

    switch (root.nodeType) {
    case 10:// Node.DOCUMENT_TYPE_NODE
    case 6: // Node.ENTITY_NODE
    case 12:// Node.NOTATION_NODE
        // this all are for the document type, probably not necessary
        break;

    case 2: // Node.ATTRIBUTE_NODE
        // Never get here, this has to be handled in the ELEMENT case because
        // of IE crapness requring that some attributes are grabbed directly from
        // the attribute (nodeValue doesn't return correct values), see
        //http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=3porgu4mc4ofcoa1uqkf7u8kvv064kjjb4%404ax.com
        // for information
        break;

    case 4: // Node.CDATA_SECTION_NODE
        // Mozilla seems to convert CDATA into a comment when going into wysiwyg mode,
        //  don't know about IE
        html += (HTMLArea.is_ie ? ('\n' + indent) : '') + '<![CDATA[' + root.data + ']]>' ;
        break;

    case 5: // Node.ENTITY_REFERENCE_NODE
        html += '&' + root.nodeValue + ';';
        break;

    case 7: // Node.PROCESSING_INSTRUCTION_NODE
        // PI's don't seem to survive going into the wysiwyg mode, (at least in moz)
        // so this is purely academic
        // >MT: don't use ? and > together otherwise PHP will not like it
        html += (HTMLArea.is_ie ? ('\n' + indent) : '') + '<'+'?' + root.target + ' ' + root.data + ' ?>';
        // <MT
        break;


    case 1: // Node.ELEMENT_NODE
    case 11: // Node.DOCUMENT_FRAGMENT_NODE
    case 9: // Node.DOCUMENT_NODE
    {
        var closed;
        var i;
        var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
        isPre = isPre || root_tag == "pre";
        if (outputRoot)
            outputRoot = !(editor.config.htmlRemoveTags && editor.config.htmlRemoveTags.test(root_tag));
        if (HTMLArea.is_ie && root_tag == "head") {
            if (outputRoot)
                html += (HTMLArea.is_ie ? ('\n' + indent) : '') + "<head>";
            // lowercasize
            var save_multiline = RegExp.multiline;
            RegExp.multiline = true;
            var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
                return p1 + p2.toLowerCase();
            });
            RegExp.multiline = save_multiline;
            html += txt + '\n';
            if (outputRoot)
                html += (HTMLArea.is_ie ? ('\n' + indent) : '') + "</head>";
            break;
        }
        if (root_tag == "iframe" && window.Widget) {
            var widget = Widget.getWidget(root);
            if (widget)
                return widget.toHtml();
        }
        if (outputRoot) {
            closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root)));
            // >MT: Ignore <br /> when with <li> and at end of the line or followed by <ul>
            if (root_tag=="br" && root.parentNode.nodeType == 1 && root.parentNode.tagName.toLowerCase() == "li") {
                var n = root.nextSibling;
                if (!n)
                    return "";
                if (n.nodeType == 3 && n.data.trim().length == 0) {
                    n = n.nextSibling;
                    if (!n) return "";
                }
                if (n.nodeType == 1 && /^ul|ol$/i.test(n.tagName))
                    return "";
            }
            html += (HTMLArea.isBlockElement(root) && !isPre ? ('\n' + indent) : '') + "<" + root.tagName.toLowerCase();
            // <MT
            // >MT: speed increase
            var tag = root.tagName.toLowerCase();
            var hasAttrs = HTMLArea.is_ie ? root.outerHTML.trim().length - root.innerHTML.length > 2*tag.length + 5 : true;
            if (hasAttrs) {
                // <MT
                var attrs = root.attributes;
                for (i = 0; i < attrs.length; ++i) {
                    var a = attrs.item(i);
                    // >MT: speed increase
                    if (!a.specified) continue;
                    // <MT
                    var name = a.nodeName.toLowerCase();
                    // >MT: speed increase
                    if (name == 'value' && !/input|option/.test(tag)) continue;
                    // <MT
                    if (/_moz_editor_bogus_node/.test(name)) {
                        html = "";
                        break;
                    }
                    if (/(_moz)|(contenteditable)|(_msh)/.test(name)) {
                        // avoid certain attributes
                        continue;
                    }
                    var value;
                    if (name != "style") {
                        // IE5.5 reports 25 when cellSpacing is
                        // 1; other values might be doomed too.
                        // For this reason we extract the
                        // values directly from the root node.
                        // I'm starting to HATE JavaScript
                        // development.  Browser differences
                        // suck.
                        //
                        // Using Gecko the values of href and src are converted to absolute links
                        // unless we get them using nodeValue()
                        if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src" && !/^on/.test(name)) {
                            value = root[a.nodeName];
                        } else {
                            value = a.nodeValue;
                            // IE seems not willing to return the original values - it converts to absolute
                            // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href")
                            // So we have to strip the baseurl manually :-/
                            if (HTMLArea.is_ie && (name == "href" || name == "src")) {
                                value = editor.stripBaseURL(value);
                            }

                            // High-ascii (8bit) characters in links seem to cause problems for some sites,
                            // while this seems to be consistent with RFC 3986 Section 2.4
                            // because these are not "reserved" characters, it does seem to
                            // cause links to international resources not to work.  See ticket:167

                            // IE always returns high-ascii characters un-encoded in links even if they
                            // were supplied as % codes (it unescapes them when we pul the value from the link).

                            // Hmmm, very strange if we use encodeURI here, or encodeURIComponent in place
                            // of escape below, then the encoding is wrong.  I mean, completely.
                            // Nothing like it should be at all.  Using escape seems to work though.
                            // It's in both browsers too, so either I'm doing something wrong, or
                            // something else is going on?

                            if(editor.config.only7BitPrintablesInURLs && (name == "href" || name == "src"))
                            {
                                value = value.replace(/([^!-~]+)/g, function(match) { return escape(match); });
                            }
                        }
                    } else { // IE fails to put style in attributes list
                        // FIXME: cssText reported by IE is UPPERCASE
                        value = root.style.cssText;
                    }
                    if (/^(_moz)?$/.test(value)) {
                        // Mozilla reports some special tags
                        // here; we don't need them.
                        continue;
                    }
                    html += " " + name + '="' + HTMLArea.htmlEncode(value) + '"';
                }
                // >MT: speed increase
            }
            // <MT
            if (html != "") {
              // >MT: extend to td, th, li
                if(closed && (root_tag=="p" || root_tag=="td" || root_tag=="th" || root_tag=="li")) {
                    //never use <p /> as empty paragraphs won't be visible
                    html += "><br /></"+root_tag+">";
                // <MT
                } else if(closed) {
                    html += " />";
                    // >MT: add newline after <BR />
                    if (root_tag=="br" && (root.nextSibling && root.nextSibling.nodeType == 1) && !isPre) {
                        html += '\n' + (root.nextSibling ? indent : '');
                    }
                    // <MT
                } else {
                    html += ">";
                }
            }
        }
        var containsBlock = false;
        // >MT: don't indent body
        var newIndent = root_tag == "body" ? "" : indent + '  ';
        var innerHTML = '';
        // <MT
        for (i = root.firstChild; i; i = i.nextSibling) {
            if(!containsBlock && i.nodeType == 1 && HTMLArea.isBlockElement(i)) containsBlock = true;
            // >MT: don't indent body
            innerHTML += HTMLArea.getHTMLWrapper(i, true, editor, newIndent, isPre);
            // <MT
        }
        if (outputRoot && !closed) {
            // >MT: extend to gecko
            if ((root_tag=="p" || root_tag=="td" || root_tag=="th" || root_tag=="li") && innerHTML.replace(/([\s\xA0]|&nbsp;)/g, '').length == 0) {
                html += "<br /></"+root_tag+">";
            } else html += innerHTML + (HTMLArea.isBlockElement(root) && containsBlock && !isPre ? ('\n' + indent) : '') + "</" + root_tag + ">";
            // <MT
        } else html += innerHTML;
        break;
    }
    case 3: // Node.TEXT_NODE
        // >MT: don't output empty text after element
        if (root.data.length != 0 && root.data.trim().length == 0)
            html = " ";
        else
            html = /^script|style$/i.test(root.parentNode.tagName) ? root.data : HTMLArea.htmlEncode(root.data.replace(/(\s|&nbsp;)+$/,' '));
        // <MT
        break;

    case 8: // Node.COMMENT_NODE
        if (/^\[if .+\]$/.test(root.data) || root.data == '[endif]') return ''; 
        html = "<!--" + root.data + "-->";
        break;
    }
    return html;
};

/** @see getHTMLWrapper (search for "value = a.nodeValue;") */

HTMLArea.prototype.stripBaseURL = function(string)
{
    if(this.config.baseHref==null || !this.config.stripBaseHref)
    {
        return(string);
    }
    var baseurl = this.config.baseHref;

    // strip host-part of URL which is added by MSIE to links relative to server root
    baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
    basere = new RegExp(baseurl);
    return string.replace(basere, "");
};


String.prototype.trim = function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
};

// creates a rgb-style color from a number
HTMLArea._makeColor = function(v) {
    if (typeof v != "number") {
        // already in rgb (hopefully); IE doesn't get here.
        return v;
    }
    // IE sends number; convert to rgb.
    var r = v & 0xFF;
    var g = (v >> 8) & 0xFF;
    var b = (v >> 16) & 0xFF;
    return "rgb(" + r + "," + g + "," + b + ")";
};

// returns hexadecimal color representation from a number or a rgb-style color.
HTMLArea._colorToRgb = function(v) {
    if (!v)
    return '';

    // returns the hex representation of one byte (2 digits)
    function hex(d) {
        return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
    };

    if (typeof v == "number") {
        // we're talking to IE here
        var r = v & 0xFF;
        var g = (v >> 8) & 0xFF;
        var b = (v >> 16) & 0xFF;
        return "#" + hex(r) + hex(g) + hex(b);
    }

    if (v.substr(0, 3) == "rgb") {
        // in rgb(...) form -- Mozilla
        var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
        if (v.match(re)) {
            var r = parseInt(RegExp.$1);
            var g = parseInt(RegExp.$2);
            var b = parseInt(RegExp.$3);
            return "#" + hex(r) + hex(g) + hex(b);
        }
        // doesn't match RE?!  maybe uses percentages or float numbers
        // -- FIXME: not yet implemented.
        return null;
    }

    if (v.substr(0, 1) == "#") {
        // already hex rgb (hopefully :D )
        return v;
    }

    // >MT: add 16 known colors
    switch (v.toLowerCase())
    {
        case "green": return "#008000";
        case "lime": return "#00ff00";
        case "teal": return "#008080";
        case "aqua": return "#00ffff";
        case "navy": return "#000080";
        case "blue": return "#0000ff";
        case "purple": return "#800080";
        case "fuchsia": return "#ff00ff";
        case "maroon": return "#800000";
        case "red": return "#ff0000";
        case "olive": return "#808000";
        case "yellow": return "#ffff00";
        case "white": return "#ffffff";
        case "silver": return "#c0c0c0";
        case "gray": return "#808080";
        case "black": return "#000000";
    }

    // if everything else fails ;)
    return v;
    // <MT
};

// modal dialogs for Mozilla (for IE we're using the showModalDialog() call).

// receives an URL to the popup dialog and a function that receives one value;
// this function will get called after the dialog is closed, with the return
// value of the dialog.
HTMLArea.prototype._popupDialog = function(width, height, url, action, init) {
    Dialog(width, height, this.popupURL(url), action, init, this);
};

// paths

HTMLArea.prototype.imgURL = function(file, plugin) {
    if (typeof plugin == "undefined")
    return _editor_url + file;
    else
    return _editor_url + "plugins/" + plugin + "/img/" + file;
};

HTMLArea.prototype.popupURL = function(file) {
    var url = "";
    if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
        var plugin = RegExp.$1;
        var popup = RegExp.$2;
        if (!/\.html$/.test(popup))
        popup += ".html";
        url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
        // >MT: also allow external popups
    } else if(/^\/.*/.test(file) || /^http/.test(file))
    // <MT
    url = file;
    else
    url = _editor_url + this.config.popupURL + file;
    return url;
};

/**
* FIX: Internet Explorer returns an item having the _name_ equal to the given
* id, even if it's not having any id.  This way it can return a different form
* field even if it's not a textarea.  This workarounds the problem by
* specifically looking to search only elements having a certain tag name.
*/
HTMLArea.getElementById = function(tag, id) {
    var el, i, objs = document.getElementsByTagName(tag);
    for (i = objs.length; --i >= 0 && (el = objs[i]);)
    if (el.id == id)
    return el;
    return null;
};


/** Use some CSS trickery to toggle borders on tables */

HTMLArea.prototype._toggleBorders = function()
{
    tables = this._doc.getElementsByTagName('TABLE');
    if(tables.length != 0)
    {
        if(!this.borders)
        {
            name = "bordered";
            this.borders = true;
        }
        else
        {
            name = "";
            this.borders = false;
        }

        for (var ix=0;ix < tables.length;ix++)
        {
            if(this.borders)
            {
                // flashing the display forces moz to listen (JB:18-04-2005) - #102
                if(HTMLArea.is_gecko)
                {
                    tables[ix].style.display="none";
                    tables[ix].style.display="table";
                }
                HTMLArea._addClass(tables[ix], 'htmtableborders');
            }
            else
            {
                HTMLArea._removeClass(tables[ix], 'htmtableborders');
            }
        }
    }
    return true;
};


HTMLArea.addClasses = function(el, classes)
{
    if(el != null)
    {
        var thiers = el.className.trim().split(' ');
        var ours   = classes.split(' ');
        for(var x = 0; x < ours.length; x++)
        {
            var exists = false;
            for(var i = 0; exists == false && i < thiers.length; i++)
            {
                if(thiers[i] == ours[x])
                {
                    exists = true;
                }
            }
            if(exists == false)
            {
                thiers[thiers.length] = ours[x];
            }
        }
        el.className = thiers.join(' ').trim();
    }
};

HTMLArea.removeClasses = function(el, classes)
{
    var existing    = el.className.trim().split();
    var new_classes = [ ];
    var remove      = classes.trim().split();

    for(var i = 0; i < existing.length; i++)
    {
        var found = false;
        for(var x = 0; x < remove.length && !found; x++)
        {
            if(existing[i] == remove[x])
            {
                found = true;
            }
        }
        if(!found)
        {
            new_classes[new_classes.length] = existing[i];
        }
    }
    return new_classes.join(' ');
};

/** Alias these for convenience */
HTMLArea.addClass       = HTMLArea._addClass;
HTMLArea.removeClass    = HTMLArea._removeClass;
HTMLArea._addClasses    = HTMLArea.addClasses;
HTMLArea._removeClasses = HTMLArea.removeClasses;

/** Use XML HTTPRequest to post some data back to the server and do something
* with the response (asyncronously!), this is used by such things as the tidy functions
*/
HTMLArea._postback = function(url, data, handler)
{
    var req = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : null;

    var content = '';
    for(var i in data)
    {
        content += (content.length ? '&' : '') + i + '=' + encodeURIComponent(data[i]);
    }

    function callBack()
    {
        if(req.readyState == 4)
        {
            if(req.status == 200)
            {
                if(typeof handler == 'function')
                handler(req.responseText, req);
            }
            else
            {
                alert('An error has occurred: ' + req.statusText);
            }
        }
    };

    req.onreadystatechange = callBack;

    req.open('POST', url, true);
    req.setRequestHeader
    (
    'Content-Type',
    'application/x-www-form-urlencoded; charset=UTF-8'
    );
    //alert(content);
    req.send(content);
};

HTMLArea._getback = function(url, handler)
{
    var req = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : null;

    function callBack()
    {
        if(req.readyState == 4)
        {
            if(req.status == 200)
            {
                handler(req.responseText, req);
            }
            else
            {
                alert('An error has occurred: ' + req.statusText);
            }
        }
    };

    req.onreadystatechange = callBack;
    req.open('GET', url, true);
    req.send(null);
};

HTMLArea._geturlcontent = function(url)
{
    var req = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : null;

    // Synchronous!
    req.open('GET', url, false);
    req.send(null);
    if(req.status == 200)
    {
        return req.responseText;
    }
    else
    {
        return '';
    }

};

/**
* Unless somebody already has, make a little function to debug things
*/
if(typeof dump == 'undefined')
{
    function dump(o) {
        var s = '';
        for (var prop in o) {
            s += prop + ' = ' + o[prop] + '\n';
        }

        x = window.open("", "debugger");
        x.document.write('<pre>' + s + '</pre>');
    }
}


HTMLArea.arrayContainsArray = function(a1, a2)
{
    var all_found = true;
    for(var x = 0; x < a2.length; x++)
    {
        var found = false;
        for(var i = 0; i < a1.length; i++)
        {
            if(a1[i] == a2[x])
            {
                found = true;
                break;
            }
        }
        if(!found)
        {
            all_found = false;
            break;
        }
    }
    return all_found;
};

HTMLArea.arrayFilter = function(a1, filterfn)
{
    var new_a = [ ];
    for(var x = 0; x < a1.length; x++)
    {
        if(filterfn(a1[x]))
        new_a[new_a.length] = a1[x];
    }

    return new_a;
};

HTMLArea.uniq_count = 0;
HTMLArea.uniq = function(prefix)
{
    return prefix + HTMLArea.uniq_count++;
};

/** New language handling functions **/


/** Load a language file.
*  This function should not be used directly, HTMLArea._lc will use it when necessary.
* @param context Case sensitive context name, eg 'HTMLArea', 'TableOperations', ...
*/
HTMLArea._loadlang = function(context)
{
    if(typeof _editor_lcbackend == "string")
    {
        //use backend
        var url = _editor_lcbackend;
        url = url.replace(/%lang%/, _editor_lang);
        url = url.replace(/%context%/, context);
    }
    else
    {
        //use internal files
        if(context != 'HTMLArea') {
            var url = _editor_url+"plugins/"+context+"/lang/"+_editor_lang+".js";
        } else {
            var url = _editor_url+"lang/"+_editor_lang+".js";
        }
    }

    var lang;
    var langData = HTMLArea._geturlcontent(url);
    if(langData != "") {
        try {
            eval('lang = ' + langData);
        } catch(Error) {
            alert('Error reading Language-File ('+url+'):\n'+Error.toString());
            lang = { }
        }
    } else {
        lang = { };
    }

    return lang;
};

/** Return a localised string.
* @param string    English language string
* @param context   Case sensitive context name, eg 'HTMLArea' (default), 'TableOperations'...
* @param replace   Replace $variables in String, eg {foo: 'replaceText'} ($foo in string will be replaced)
*/
HTMLArea._lc = function(string, context, replace)
{
    var ret;
    if(_editor_lang == "en")
    {
        if(typeof string == 'object' && string.string) {
            ret = string.string;
        } else {
            ret = string;
        }
    }
    else
    {
        if(typeof HTMLArea._lc_catalog == 'undefined')
        {
            HTMLArea._lc_catalog = [ ];
        }

        if(typeof context == 'undefined')
        {
            context = 'HTMLArea';
        }

        if(typeof HTMLArea._lc_catalog[context] == 'undefined')
        {
            HTMLArea._lc_catalog[context] = HTMLArea._loadlang(context);
        }

        var key;
        if(typeof string == 'object' && string.key)
        {
            key = string.key;
        }
        else if(typeof string == 'object' && string.string)
        {
            key = string.string;
        }
        else
        {
            key = string;
        }

        if(typeof HTMLArea._lc_catalog[context][key] == 'undefined')
        {
            if(context=='HTMLArea')
            {
                // Indicate it's untranslated
                if(typeof string == 'object' && string.string) {
                    ret = string.string;
                } else {
                    ret = string;
                }
            }
            else
            {
                //if string is not found and context is not HTMLArea try if it is in HTMLArea
                return HTMLArea._lc(string, 'HTMLArea', replace);
            }
        }
        else
        {
            ret = HTMLArea._lc_catalog[context][key];
        }
    }

    if(typeof string == 'object' && string.replace)
    {
        replace = string.replace;
    }
    if(typeof replace != "undefined")
    {
        for(var i in replace)
        {
            ret = ret.replace('$'+i, replace[i]);
        }
    }

    return ret;
};

HTMLArea.hasDisplayedChildren = function(el)
{
    var children = el.childNodes;
    for(var i =0; i < children.length;i++)
    {
        if(children[i].tagName)
        {
            if(children[i].style.display != 'none')
            {
                return true;
            }
        }
    }
    return false;
};


HTMLArea._loadback = function(src, callback)
{
    var head = document.getElementsByTagName("head")[0];
    var evt = HTMLArea.is_ie ? "onreadystatechange" : "onload";

    var script = document.createElement("script");
    script.type = "text/javascript";
    script.src = src;
    script[evt] = function()
    {
        if(HTMLArea.is_ie && !/loaded|complete/.test(window.event.srcElement.readyState))  return;
        callback();
    }
    head.appendChild(script);
};

HTMLArea.collectionToArray = function(collection)
{
    var array = [ ];
    for(var i = 0; i < collection.length; i++)
    {
        array.push(collection.item(i));
    }
    return array;
};

if(!Array.prototype.append)
{
    Array.prototype.append  = function(a)
    {
        for(var i = 0; i<a.length;i++)
        {
            this.push(a[i]);
        }
        return this;
    };
}

HTMLArea.makeEditors = function(editor_names, default_config, plugin_names)
{
    if(typeof default_config == 'function')
    {
        default_config = default_config();
    }

    var editors = { };
    for(var x = 0; x < editor_names.length; x++)
    {
        var editor = new HTMLArea(editor_names[x], HTMLArea.cloneObject(default_config));
        editor.registerPlugins(plugin_names);
        editors[editor_names[x]] = editor;
    }
    return editors;
};

HTMLArea.startEditors = function(editors)
{
    for(var i in editors)
    {
        if(editors[i].generate) editors[i].generate();
    }
};

HTMLArea.prototype.registerPlugins = function(plugin_names) {
    if(plugin_names)
    {
        for(var i = 0; i < plugin_names.length; i++)
        {
            this.registerPlugin(eval(plugin_names[i]));
        }
    }
};

/** Utility function to base64_encode some arbitrary data, uses the builtin btoa() if it exists (Moz) */

HTMLArea.base64_encode =  function(input)
{
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    do {
        chr1 = input.charCodeAt(i++);
        chr2 = input.charCodeAt(i++);
        chr3 = input.charCodeAt(i++);

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
            enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
            enc4 = 64;
        }

        output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
        keyStr.charAt(enc3) + keyStr.charAt(enc4);
    } while (i < input.length);

    return output;
};

/** Utility function to base64_decode some arbitrary data, uses the builtin atob() if it exists (Moz) */

HTMLArea.base64_decode =function(input)
{
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    do {
        enc1 = keyStr.indexOf(input.charAt(i++));
        enc2 = keyStr.indexOf(input.charAt(i++));
        enc3 = keyStr.indexOf(input.charAt(i++));
        enc4 = keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        output = output + String.fromCharCode(chr1);

        if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
        }
    } while (i < input.length);

    return output;
};

HTMLArea.removeFromParent = function(el)
{
    if(!el.parentNode) return;
    var pN = el.parentNode;
    pN.removeChild(el);
    return el;
};

HTMLArea.hasParentNode = function(el)
{
    if(el.parentNode)
    {
        // When you remove an element from the parent in IE it makes the parent
        // of the element a document fragment.  Moz doesn't.
        if(el.parentNode.nodeType == 11)
        {
            return false;
        }
        return true;
    }

    return false;
};

HTMLArea.getOuterHTML = function(element)
{
    if(HTMLArea.is_ie)
    {
        return element.outerHTML;
    }
    else
    {
        return (new XMLSerializer()).serializeToString(element);
    }
};

HTMLArea.toFree = [ ];
HTMLArea.freeLater = function(obj,prop)
{
    HTMLArea.toFree.push({o:obj,p:prop});
};

HTMLArea.free = function(obj, prop)
{
    if(obj && !prop)
    {
        for(var p in obj)
        {
            HTMLArea.free(obj, p);
        }
    }
    // >MT: fix
    else if (obj && typeof obj[prop] != 'unknown')
    // <MT
    {
        obj[prop] = null;
    }
};

/** IE's Garbage Collector is broken very badly.  We will do our best to
*   do it's job for it, but we can't be perfect.
*/

HTMLArea.collectGarbageForIE = function()
{
    HTMLArea.flushEvents();
    for(var x = 0; x < HTMLArea.toFree.length; x++)
    {
        if(!HTMLArea.toFree[x].o) alert("What is " + x + ' ' + HTMLArea.toFree[x].o);
        HTMLArea.free(HTMLArea.toFree[x].o, HTMLArea.toFree[x].p);
    }
};

HTMLArea.init();
HTMLArea.addDom0Event(window,'unload',HTMLArea.collectGarbageForIE);

String.prototype.utf8ToCodepoint = function() {
  var z = this.charCodeAt(0), length;
  if (z & 0x80) {
    length = 0;
    while (z & 0x80) {
      length++;
      z <<= 1;
    }
  } else
    length = 1;

  if (length != this.length) return false;
  if (length == 1) return z;

  // Mask off the length-determining bits and shift back to the original location
  z &= 0xff;
  z >>= length;

  // Add in the free bits from subsequent bytes
  for ( var i=1; i < length; i++ ) {
    z <<= 6;
    z |= this.charCodeAt(i) & 0x3f;
  }
  return z;
};

String.prototype.utf8ToString = function() {
  var val = this.replace(/[\xc0-\xfd][\x80-\xbf]*/g, function(s) {
    return String.fromCharCode(s.utf8ToCodepoint());
  });;
  return val;
};

String.prototype.utf8URL = function() {
  var val = this.replace(/[ \?%\+&=#\.\u0080-\uFFFF]/g, function(s) {
    switch (s) {
    case ' ': return '_';
    case '+': return '%2B';
    default:
      return escape(String.charToUtf8(s.charCodeAt(0)));
    }
  });
  return val;
};

String.prototype.utf8 = function() {
  var val = this.replace(/[\u0080-\uFFFF]/g, function(s) {
    return String.charToUtf8(s.charCodeAt(0));
  });
  return val;
};

String.charToUtf8 = function(codepoint) {
  if(codepoint < 0x80) return String.fromCharCode(codepoint);
  if(codepoint < 0x800) return String.fromCharCode(
    codepoint >> 6 & 0x3f | 0xc0, 
    codepoint & 0x3f | 0x80);
  if(codepoint < 0x10000) return String.fromCharCode(
    codepoint >> 12 & 0x0f | 0xe0, 
    codepoint >> 6 & 0x3f | 0x80, 
    codepoint & 0x3f | 0x80);
  if(codepoint < 0x110000) return String.fromCharCode(
    codepoint >> 18 & 0x07 | 0xf0, 
    codepoint >> 12 & 0x3f | 0x80, 
    codepoint >> 6 & 0x3f | 0x80, 
    codepoint & 0x3f | 0x80);

  // There should be no assigned code points outside this range, but
  return String.fromCharCode(codepoint);
};

var winX = null;
var winY = null;

clientWindow = function () {
	if (self.innerHeight) {
		winX = self.innerWidth;
		winY = self.innerHeight;
	}
	else if (document.documentElement && document.documentElement.clientHeight)	{
		winX = document.documentElement.clientWidth;
		winY = document.documentElement.clientHeight;
	}
	else if (document.body) {
		winX = document.body.clientWidth;
		winY = document.body.clientHeight;
	}
};

getItemHeight = function (elt) {
	return elt.offsetHeight;
};

getItemWidth = function (elt) {
	return elt.offsetWidth;
};

//returns the Y offset of a page
getPageYOffset = function() {
	return (window.pageYOffset > 0) ? window.pageYOffset: document.body.scrollTop;
};

//returns the X offset of a page
getPageXOffset = function() {
	return (window.pageXOffset > 0) ? window.pageXOffset: document.body.scrollLeft;
};

findPosX = function (obj) {
	return getRecursiveProperty(obj, "offsetLeft");
};

findPosY = function (obj) {
	return getRecursiveProperty(obj, "offsetTop");
};

findPosScrollY = function () {	
	var sTop = 0;
	if (document.body.scrollTop > 0) {
		sTop = document.body.scrollTop;
	}
	else if (document.body.parentNode.scrollTop > 0) {
		sTop = document.body.parentNode.scrollTop;	
	}
	return sTop;	
};

findPosScrollX = function () {	
	var sLeft = 0;
	if (document.body.scrollLeft > 0) {
		sLeft = document.body.scrollLeft;
	}
	else if (document.body.parentNode.scrollLeft > 0) {
		sLeft = document.body.parentNode.scrollLeft;	
	}
	return sLeft;	
};

getRecursiveProperty = function (obj, property) {
	var ret = 0;
	while (obj) {
		ret += obj[property];
		obj = obj.offsetParent;
	}
	return ret;	
};

//adds a css class
addCSSClass = function (elt, newClass) {
	var class_ = elt.className.split(' ');
	for ( var i = 0; i < class_.length; i++ ) {
		if (class_[i] == newClass) { 
			return;
		}
	}
	elt.className = elt.className+' '+newClass;
	return true;
};
replaceCSSClass = function (elt, newClass) {
	elt.className = newClass;
	return true;
};
removeCSSClass = function (elt, className) {
	var new_ = new Array;
	var class_ = elt.className.split(' ');
	for ( var i = 0; i < class_.length; i++ ) {
		if (class_[i] != className) { 
			new_[i] = class_[i];
		}
	}
	elt.className = new_.join(' ');
};
inCSSClass = function(elt, className) {
	var new_ = new Array;
	var class_ = elt.className.split(' ');
	for ( var i = 0; i < class_.length; i++ ) {
		if (class_[i] == className) { 
			return true;
		}
	}	
	return false;
};
function mt_gen() {
	
}

/***  
 * takes a string and escapes single quotes and encodes html
 */
mt_gen.htmlspecialchars = function(str) {	
	// performs HTML encoding of some given string
	mt_gen.htmlEncode_regEx = [
		new RegExp().compile(/&/ig),
		new RegExp().compile(/</ig),
		new RegExp().compile(/>/ig),
		new RegExp().compile(/'/ig),
		new RegExp().compile(/\xA0/g),
	    // \x22 means '"' -- we use hex reprezentation so that we don't disturb
	    // JS compressors (well, at least mine fails.. ;)
		new RegExp().compile(/\x22/g),
		// special encode none-ASCII
		new RegExp().compile(/[\x80-\xFF]/g)
	];
	mt_gen.htmlEncode_regExR = [
		"&amp;",
		"&lt;",
		"&gt;",
		"\\'",
		"&nbsp;",
		"&quot;",
		function(s,b){return "&#"+s.charCodeAt(0)+";";}
	];
	
    if(typeof str.replace == 'undefined') str = str.toString();
    for (var i = 0; i < mt_gen.htmlEncode_regEx.length; ++i)
    	str = str.replace(mt_gen.htmlEncode_regEx[i], mt_gen.htmlEncode_regExR[i]);
    return str;
};

mt_gen.getUrlFromName = function(href) {
	href = mt_gen.extractName(href).replace(/ /g,'_');
    if (href.indexOf('&') > 0 || href.indexOf('?') > 0 || href.indexOf('+') > 0 || 
        href.indexOf('\\') > 0 || href.indexOf('//') > 0 || href.indexOf('%') > 0
    )
        href = 'index.php?title=' + encodeURIComponent(href);
    return '/' + href;
};

mt_gen.extractName = function(href) {
    if (href.charAt(0) == '/') href = href.substr(1);
    if (href.indexOf('index.php?title=') == 0)
        href = href.substr('index.php?title='.length);
    href = href.replace(/&action=.+$/i, '');
    try { href = unescape(href); } catch (e) {}
    return href;
};


var clientBrowser = { 
	isIe : false, 
	isMoz : false,
	isOpera : false,
	detect : function () { 
		var nav = navigator.userAgent.toLowerCase();
		if (nav.indexOf('msie') >= 0) {
			clientBrowser.isIe = true;
		}
		else if (nav.indexOf('mozilla') >= 0)  {
			clientBrowser.isMoz = true;
		}
		else if (nav.indexOf('opera') >= 0) {
			clientBrowser.isOpera = true;	
		}
	}
};

function iconify(icon_class, parentClass) {
	if (!parentClass) parentClass = 'icon';
	var span = document.createElement('span');
	span.className = parentClass;
	var img = document.createElement('img');
	img.src = '/skins/ace/icon-trans.gif';
	if (typeof(icon_class) != 'undefined' && icon_class != '') {
		img.className = icon_class;	
	}
	span.appendChild(img);
	return span;
};
/**
 * Checks/unchecks all checkboxes
 *
 * @param   string   the form name
 * @param   atring   the name of the array with the checkboxes
 * @param   boolean  whether to check or to uncheck the element
 *
 * @return  boolean  always true
 */
function setBoxes(the_form, the_checkboxes, do_check)
{
    var elts      = (the_checkboxes != '')
                  ? document.forms[the_form].elements[the_checkboxes + '[]']
                  : document.forms[the_form].elements;
    var elts_cnt  = (typeof(elts.length) != 'undefined')
                  ? elts.length
                  : 0;

    if (elts_cnt) {
        for (var i = 0; i < elts_cnt; i++) {
	        
	        if (elts[i].checked == true) {
            	elts[i].checked = false;
	        }
	        else {
            	elts[i].checked = true;
	        }
        } // end for
    } else {
        elts.checked        = do_check;
    } // end if... else

    return true;
} // end of the 'setCheckboxes()' function

function addUser() {
	x_wfAdminUserForm(function (returned) { 
		var cln = document.createElement('div');
		cln.innerHTML = returned;
		var elt = document.getElementById('fillerArea');
		if (!elt) return false;
		elt.appendChild(cln);
	});
	return false;
}
function setUserStatus(div) {
	var cb = document.getElementById('cb_'+div);
	var elt = document.getElementById('hidden_'+div);
	if (!elt || !cb) return;
	elt.value = cb.checked ? 1: 0;
	return true;
}
function addRowToTable(the_element) {
	var tbl = document.getElementById(the_element);
	var lastRow = tbl.rows.length;
	// if there's no header row in the table, then iteration = lastRow + 1
	var iteration = lastRow;
	var row = tbl.insertRow(lastRow - 1);
	
	// left cell
	var cellLeft = row.insertCell(0);
	var el = document.createElement('input');
	el.setAttribute('type', 'text');
	el.setAttribute('name', 'user_name['+iteration+']');
	el.setAttribute('class', 'inputText');
	cellLeft.appendChild(el);
	
	// right cell
	var cellRight = row.insertCell(1);
	var el = document.createElement('input');
	el.setAttribute('type', 'text');
	el.setAttribute('name', 'user_email['+iteration+']');
	el.setAttribute('class', 'inputText');
	cellRight.appendChild(el);
	
	
	var cellRightY = row.insertCell(2);
	var el = document.createElement('input');
	el.setAttribute('type', 'text');
	el.setAttribute('name', 'user_real_name['+iteration+']');
	el.setAttribute('class', 'inputText');
	cellRightY.appendChild(el);
	
	
	var cellRightY = row.insertCell(3);
	var el = document.createElement('input');
	el.setAttribute('type', 'password');
	el.setAttribute('name', 'user_password['+iteration+']');
	el.setAttribute('class', 'inputPassword');
	cellRightY.appendChild(el);
	
	
	var cellRightY = row.insertCell(4);
	var el = document.createElement('input');
	el.setAttribute('type', 'password');
	el.setAttribute('name', 'user_password_verify['+iteration+']');
	el.setAttribute('class', 'inputPassword');
	cellRightY.appendChild(el);
	
	
	var cellRightZ = row.insertCell(5);
	var el = document.createElement('input');
	el.setAttribute('type', 'checkbox');
	el.setAttribute('name', 'user_status['+iteration+']');
	el.setAttribute('id', 'burea' + iteration);
	el.setAttribute('class', 'inputCheckbox');
	var text = document.createTextNode(' Administrator');
	cellRightZ.appendChild(el);
	cellRightZ.appendChild(text);	
}


function disableSubmit(theform) {
	var submitTags = theform.getElementsByTagName('input');
	if (submitTags) {
		var length = submitTags.length;
		for (i = 0; i < length; i++) {
		var tempobj = theform.elements[i];
			var tempobj = submitTags[i].getAttribute('type');
			if (tempobj.toLowerCase() == "submit")
				submitTags[i].disabled = true;
			}
		return true;
	}
	else {
		return false;
	}
}
// Table Operations Plugin for HTMLArea-3.0
// Implementation by Mihai Bazon.  Sponsored by http://www.bloki.com
//
// htmlArea v3.0 - Copyright (c) 2002 interactivetools.com, inc.
// This notice MUST stay intact for use (see license.txt).
//
// A free WYSIWYG editor replacement for <textarea> fields.
// For full source code and docs, visit http://www.interactivetools.com/
//
// Version 3.0 developed by Mihai Bazon for InteractiveTools.
//   http://dynarch.com/mishoo
//
// $Id: table-operations.js 376 2005-10-01 09:23:52Z mokhet $

// Object that will encapsulate all the table operations provided by
// HTMLArea-3.0 (except "insert table" which is included in the main file)
function TableOperations(editor) {
	this.editor = editor;

	var cfg = editor.config;
	var bl = TableOperations.btnList;
	var self = this;

	// register the toolbar buttons provided by this plugin

    
	var toolbar = ["linebreak"];
	for (var i = 0; i < bl.length; ++i) {
		var btn = bl[i];
		if (!btn) {
			/* >MT: don't add separator *
			toolbar.push("separator");
			/* <MT */
		} else {
			var id = "TO-" + btn[0];
			// >MT: don't show buttons on toolbar
			if (typeof HTMLArea.Config.MTregisterButton == 'function')
  				HTMLArea.Config.MTregisterButton(cfg, id, HTMLArea._lc(btn[2], "TableOperations"), editor.imgURL(btn[0] + ".gif", "TableOperations"), false,
					   function(editor, id) {
						   // dispatch button press event
						   self.buttonPress(editor, id);
					   }, btn[1], false);
			else
			// <MT
				cfg.registerButton(id, HTMLArea._lc(btn[2], "TableOperations"), editor.imgURL(btn[0] + ".gif", "TableOperations"), false,
					   function(editor, id) {
						   // dispatch button press event
						   self.buttonPress(editor, id);
					   }, btn[1]);
			toolbar.push(id);
		}
	}

	// add a new line in the toolbar
	cfg.toolbar.push(toolbar);
};

TableOperations._pluginInfo = {
	name          : "TableOperations",
	version       : "1.0",
	developer     : "Mihai Bazon",
	developer_url : "http://dynarch.com/mishoo/",
	c_owner       : "Mihai Bazon",
	sponsor       : "Zapatec Inc.",
	sponsor_url   : "http://www.bloki.com",
	license       : "htmlArea"
};

TableOperations.prototype._lc = function(string) {
    return HTMLArea._lc(string, 'TableOperations');
};

/************************
 * UTILITIES
 ************************/

// retrieves the closest element having the specified tagName in the list of
// ancestors of the current selection/caret.
TableOperations.prototype.getClosest = function(tagName) {
	var editor = this.editor;
	var ancestors = editor.getAllAncestors();
	var ret = null;
	tagName = ("" + tagName).toLowerCase();
	for (var i = 0; i < ancestors.length; ++i) {
		var el = ancestors[i];
		if (el.tagName.toLowerCase() == tagName) {
			ret = el;
			break;
		}
	}
	return ret;
};

// this function requires the file PopupDiv/PopupWin to be loaded from browser
TableOperations.prototype.dialogTableProperties = function() {
	// retrieve existing values
	var table = this.getClosest("table");
	// this.editor.selectNodeContents(table);
	// this.editor.updateToolbar();

    // >MT: Use PopupDiv instead of PopopWin
	var dialog = new PopupDiv(this.editor, HTMLArea._lc("Table Properties", "TableOperations"), function(dialog, params) {
    // <MT
		TableOperations.processStyle(params, table);
		for (var i in params) {
      if(typeof params[i] == 'function') continue;
			var val = params[i];
			switch (i) {
			case "f_caption":
				if (/\S/.test(val)) {
					// contains non white-space characters
					var caption = table.getElementsByTagName("caption")[0];
					if (!caption) {
						caption = dialog.editor._doc.createElement("caption");
						table.insertBefore(caption, table.firstChild);
					}
					caption.innerHTML = val;
				} else {
					// search for caption and delete it if found
					var caption = table.getElementsByTagName("caption")[0];
					if (caption) {
						caption.parentNode.removeChild(caption);
					}
				}
				break;
			case "f_summary":
				table.summary = val;
				break;
			case "f_width":
				table.style.width = ("" + val) + params.f_unit;
				break;
			case "f_align":
				table.align = val;
				break;
			case "f_spacing":
				table.cellSpacing = val;
				break;
			case "f_padding":
				table.cellPadding = val;
				break;
			case "f_borders":
				table.border = val;
				break;
			case "f_frames":
				table.frame = val;
				break;
			case "f_rules":
				table.rules = val;
				break;
			}
		}
		// various workarounds to refresh the table display (Gecko,
		// what's going on?! do not disappoint me!)
		dialog.editor.forceRedraw();
		dialog.editor.focusEditor();
		dialog.editor.updateToolbar();
		var save_collapse = table.style.borderCollapse;
		table.style.borderCollapse = "collapse";
		table.style.borderCollapse = "separate";
		table.style.borderCollapse = save_collapse;
	},

	// this function gets called when the dialog needs to be initialized
	function (dialog) {

		var f_caption = "";
		var capel = table.getElementsByTagName("caption")[0];
		if (capel) {
			f_caption = capel.innerHTML;
		}
		var f_summary = table.summary;
		var f_width = parseInt(table.style.width);
		isNaN(f_width) && (f_width = "");
		var f_unit = /%/.test(table.style.width) ? 'percent' : 'pixels';
		var f_align = table.align;
		var f_spacing = table.cellSpacing;
		var f_padding = table.cellPadding;
		var f_borders = table.border;
		var f_frames = table.frame;
		var f_rules = table.rules;

		function selected(val) {
			return val ? " selected" : "";
		};

		// dialog contents
		dialog.content.style.width = "400px";
		dialog.content.innerHTML = " \
<div class='title'\
 style='background: url(" + dialog.baseURL + dialog.editor.imgURL("table-prop.gif", "TableOperations") + ") #fff 98% 50% no-repeat'>" + HTMLArea._lc("Table Properties", "TableOperations") + "\
</div> \
<table style='width:100%'> \
  <tr> \
    <td> \
      <fieldset><legend>" + HTMLArea._lc("Description", "TableOperations") + "</legend> \
       <table style='width:100%'> \
        <tr> \
          <td class='label'>" + HTMLArea._lc("Caption", "TableOperations") + ":</td> \
          <td class='value'><input type='text' name='f_caption' value='" + f_caption + "'/></td> \
        </tr><tr> \
          <td class='label'>" + HTMLArea._lc("Summary", "TableOperations") + ":</td> \
          <td class='value'><input type='text' name='f_summary' value='" + f_summary + "'/></td> \
        </tr> \
       </table> \
      </fieldset> \
    </td> \
  </tr> \
  <tr><td id='--HA-layout'></td></tr> \
  <tr> \
    <td> \
      <fieldset><legend>" + HTMLArea._lc("Spacing and padding", "TableOperations") + "</legend> \
       <table style='width:100%'> \
"+//        <tr> \
//           <td class='label'>" + HTMLArea._lc("Width", "TableOperations") + ":</td> \
//           <td><input type='text' name='f_width' value='" + f_width + "' size='5' /> \
//             <select name='f_unit'> \
//               <option value='%'" + selected(f_unit == "percent") + ">" + HTMLArea._lc("percent", "TableOperations") + "</option> \
//               <option value='px'" + selected(f_unit == "pixels") + ">" + HTMLArea._lc("pixels", "TableOperations") + "</option> \
//             </select> &nbsp;&nbsp;" + HTMLArea._lc("Align", "TableOperations") + ": \
//             <select name='f_align'> \
//               <option value='left'" + selected(f_align == "left") + ">" + HTMLArea._lc("Left", "TableOperations") + "</option> \
//               <option value='center'" + selected(f_align == "center") + ">" + HTMLArea._lc("Center", "TableOperations") + "</option> \
//               <option value='right'" + selected(f_align == "right") + ">" + HTMLArea._lc("Right", "TableOperations") + "</option> \
//             </select> \
//           </td> \
//         </tr> \
"        <tr> \
          <td class='label'>" + HTMLArea._lc("Spacing", "TableOperations") + ":</td> \
          <td><input type='text' name='f_spacing' size='5' value='" + f_spacing + "' /> &nbsp;" + HTMLArea._lc("Padding", "TableOperations") + ":\
            <input type='text' name='f_padding' size='5' value='" + f_padding + "' /> &nbsp;&nbsp;" + HTMLArea._lc("pixels", "TableOperations") + "\
          </td> \
        </tr> \
       </table> \
      </fieldset> \
    </td> \
  </tr> \
  <tr> \
    <td> \
      <fieldset><legend>Frame and borders</legend> \
        <table width='100%'> \
          <tr> \
            <td class='label'>" + HTMLArea._lc("Borders", "TableOperations") + ":</td> \
            <td><input name='f_borders' type='text' size='5' value='" + f_borders + "' /> &nbsp;&nbsp;" + HTMLArea._lc("pixels", "TableOperations") + "</td> \
          </tr> \
          <tr> \
            <td class='label'>" + HTMLArea._lc("Frames", "TableOperations") + ":</td> \
            <td> \
              <select name='f_frames'> \
                <option value='void'" + selected(f_frames == "void") + ">" + HTMLArea._lc("No sides", "TableOperations") + "</option> \
                <option value='above'" + selected(f_frames == "above") + ">" + HTMLArea._lc("The top side only", "TableOperations") + "</option> \
                <option value='below'" + selected(f_frames == "below") + ">" + HTMLArea._lc("The bottom side only", "TableOperations") + "</option> \
                <option value='hsides'" + selected(f_frames == "hsides") + ">" + HTMLArea._lc("The top and bottom sides only", "TableOperations") + "</option> \
                <option value='vsides'" + selected(f_frames == "vsides") + ">" + HTMLArea._lc("The right and left sides only", "TableOperations") + "</option> \
                <option value='lhs'" + selected(f_frames == "lhs") + ">" + HTMLArea._lc("The left-hand side only", "TableOperations") + "</option> \
                <option value='rhs'" + selected(f_frames == "rhs") + ">" + HTMLArea._lc("The right-hand side only", "TableOperations") + "</option> \
                <option value='box'" + selected(f_frames == "box") + ">" + HTMLArea._lc("All four sides", "TableOperations") + "</option> \
              </select> \
            </td> \
          </tr> \
          <tr> \
            <td class='label'>" + HTMLArea._lc("Rules", "TableOperations") + ":</td> \
            <td> \
              <select name='f_rules'> \
                <option value='none'" + selected(f_rules == "none") + ">" + HTMLArea._lc("No rules", "TableOperations") + "</option> \
                <option value='rows'" + selected(f_rules == "rows") + ">" + HTMLArea._lc("Rules will appear between rows only", "TableOperations") + "</option> \
                <option value='cols'" + selected(f_rules == "cols") + ">" + HTMLArea._lc("Rules will appear between columns only", "TableOperations") + "</option> \
                <option value='all'" + selected(f_rules == "all") + ">" + HTMLArea._lc("Rules will appear between all rows and columns", "TableOperations") + "</option> \
              </select> \
            </td> \
          </tr> \
        </table> \
      </fieldset> \
    </td> \
  </tr> \
  <tr> \
    <td id='--HA-style'></td> \
  </tr> \
</table> \
";
		var st_prop = TableOperations.createStyleFieldset(dialog.doc, dialog.editor, table);
		var p = dialog.doc.getElementById("--HA-style");
		p.appendChild(st_prop);
		var st_layout = TableOperations.createStyleLayoutFieldset(dialog.doc, dialog.editor, table);
		p = dialog.doc.getElementById("--HA-layout");
		p.appendChild(st_layout);
		dialog.modal = true;
		dialog.addButtons("OK", "Cancel");
		dialog.showAtElement(dialog.editor._iframe, "c");
	});
};

// this function requires the file PopupDiv/PopupWin to be loaded from browser
TableOperations.prototype.dialogRowCellProperties = function(cell) {
	// retrieve existing values
	var element = this.getClosest(cell ? "td" : "tr");
	var table = this.getClosest("table");
	// this.editor.selectNodeContents(element);
	// this.editor.updateToolbar();

    // >MT: Use PopupDiv instead of PopopWin
	var dialog = new PopupDiv(this.editor, cell ? HTMLArea._lc("Cell Properties", "TableOperations") : HTMLArea._lc("Row Properties", "TableOperations"), function(dialog, params) {
    // <MT
		TableOperations.processStyle(params, element);
		for (var i in params) {
			if(typeof params[i] == 'function') continue;
			var val = params[i];
			switch (i) {
			case "f_align":
				element.align = val;
				break;
			case "f_char":
				element.ch = val;
				break;
			case "f_valign":
				element.vAlign = val;
				break;
			}
		}
		// various workarounds to refresh the table display (Gecko,
		// what's going on?! do not disappoint me!)
		dialog.editor.forceRedraw();
		dialog.editor.focusEditor();
		dialog.editor.updateToolbar();
		var save_collapse = table.style.borderCollapse;
		table.style.borderCollapse = "collapse";
		table.style.borderCollapse = "separate";
		table.style.borderCollapse = save_collapse;
	},

	// this function gets called when the dialog needs to be initialized
	function (dialog) {

		var f_align = element.align;
		var f_valign = element.vAlign;
		var f_char = element.ch;

		function selected(val) {
			return val ? " selected" : "";
		};

		// dialog contents
		dialog.content.style.width = "400px";
		dialog.content.innerHTML = " \
<div class='title'\
 style='background: url(" + dialog.baseURL + dialog.editor.imgURL(cell ? "cell-prop.gif" : "row-prop.gif", "TableOperations") + ") #fff 98% 50% no-repeat'>" + HTMLArea._lc(cell ? "Cell Properties" : "Row Properties", "TableOperations") + "</div> \
<table style='width:100%'> \
  <tr> \
    <td id='--HA-layout'> \
"+//      <fieldset><legend>" + HTMLArea._lc("Layout", "TableOperations") + "</legend> \
//        <table style='width:100%'> \
//         <tr> \
//           <td class='label'>" + HTMLArea._lc("Align", "TableOperations") + ":</td> \
//           <td> \
//             <select name='f_align'> \
//               <option value='left'" + selected(f_align == "left") + ">" + HTMLArea._lc("Left", "TableOperations") + "</option> \
//               <option value='center'" + selected(f_align == "center") + ">" + HTMLArea._lc("Center", "TableOperations") + "</option> \
//               <option value='right'" + selected(f_align == "right") + ">" + HTMLArea._lc("Right", "TableOperations") + "</option> \
//               <option value='char'" + selected(f_align == "char") + ">" + HTMLArea._lc("Char", "TableOperations") + "</option> \
//             </select> \
//             &nbsp;&nbsp;" + HTMLArea._lc("Char", "TableOperations") + ": \
//             <input type='text' style='font-family: monospace; text-align: center' name='f_char' size='1' value='" + f_char + "' /> \
//           </td> \
//         </tr><tr> \
//           <td class='label'>" + HTMLArea._lc("Vertical align", "TableOperations") + ":</td> \
//           <td> \
//             <select name='f_valign'> \
//               <option value='top'" + selected(f_valign == "top") + ">" + HTMLArea._lc("Top", "TableOperations") + "</option> \
//               <option value='middle'" + selected(f_valign == "middle") + ">" + HTMLArea._lc("Middle", "TableOperations") + "</option> \
//               <option value='bottom'" + selected(f_valign == "bottom") + ">" + HTMLArea._lc("Bottom", "TableOperations") + "</option> \
//               <option value='baseline'" + selected(f_valign == "baseline") + ">" + HTMLArea._lc("Baseline", "TableOperations") + "</option> \
//             </select> \
//           </td> \
//         </tr> \
//        </table> \
//       </fieldset> \
"    </td> \
  </tr> \
  <tr> \
    <td id='--HA-style'></td> \
  </tr> \
</table> \
";
		var st_prop = TableOperations.createStyleFieldset(dialog.doc, dialog.editor, element);
		var p = dialog.doc.getElementById("--HA-style");
		p.appendChild(st_prop);
		var st_layout = TableOperations.createStyleLayoutFieldset(dialog.doc, dialog.editor, element);
		p = dialog.doc.getElementById("--HA-layout");
		p.appendChild(st_layout);
		dialog.modal = true;
		dialog.addButtons("OK", "Cancel");
		dialog.showAtElement(dialog.editor._iframe, "c");
	});
};

// this function gets called when some button from the TableOperations toolbar
// was pressed.
TableOperations.prototype.buttonPress = function(editor, button_id) {
	this.editor = editor;
	var mozbr = HTMLArea.is_gecko ? "<br />" : "";

	// helper function that clears the content in a table row
	function clearRow(tr) {
		var tds = tr.getElementsByTagName("td");
		for (var i = tds.length; --i >= 0;) {
			var td = tds[i];
			td.rowSpan = 1;
			td.innerHTML = mozbr;
		}
	};

	function splitRow(td) {
		var n = parseInt("" + td.rowSpan);
		var nc = parseInt("" + td.colSpan);
		td.rowSpan = 1;
		tr = td.parentNode;
		var itr = tr.rowIndex;
		var trs = tr.parentNode.rows;
		var index = td.cellIndex;
		while (--n > 0) {
			tr = trs[++itr];
			var otd = editor._doc.createElement("td");
			otd.colSpan = td.colSpan;
			otd.innerHTML = mozbr;
			tr.insertBefore(otd, tr.cells[index]);
		}
		editor.forceRedraw();
		editor.updateToolbar();
	};

	function splitCol(td) {
		var nc = parseInt("" + td.colSpan);
		td.colSpan = 1;
		tr = td.parentNode;
		var ref = td.nextSibling;
		while (--nc > 0) {
			var otd = editor._doc.createElement("td");
			otd.rowSpan = td.rowSpan;
			otd.innerHTML = mozbr;
			tr.insertBefore(otd, ref);
		}
		editor.forceRedraw();
		editor.updateToolbar();
	};

	function splitCell(td) {
		var nc = parseInt("" + td.colSpan);
		splitCol(td);
		var items = td.parentNode.cells;
		var index = td.cellIndex;
		while (nc-- > 0) {
			splitRow(items[index++]);
		}
	};

	function selectNextNode(el) {
		var node = el.nextSibling;
		while (node && node.nodeType != 1) {
			node = node.nextSibling;
		}
		if (!node) {
			node = el.previousSibling;
			while (node && node.nodeType != 1) {
				node = node.previousSibling;
			}
		}
		if (!node) {
			node = el.parentNode;
		}
		editor.selectNodeContents(node);
	};

	switch (button_id) {
		// ROWS

	case "TO-row-insert-above":
	case "TO-row-insert-under":
		var tr = this.getClosest("tr");
		if (!tr) {
			break;
		}
		var otr = tr.cloneNode(true);
		clearRow(otr);
		tr.parentNode.insertBefore(otr, /under/.test(button_id) ? tr.nextSibling : tr);
		editor.forceRedraw();
		editor.focusEditor();
		break;
	case "TO-row-delete":
		var tr = this.getClosest("tr");
		if (!tr) {
			break;
		}
		var par = tr.parentNode;
		if (par.rows.length == 1) {
			while (par.tagName.toLowerCase() != 'table')
				par = par.parentNode;
			HTMLArea.removeFromParent(par);
			break;
		}
		// set the caret first to a position that doesn't
		// disappear.
		selectNextNode(tr);
		par.removeChild(tr);
		editor.forceRedraw();
		editor.focusEditor();
		editor.updateToolbar();
		break;
	case "TO-row-split":
		var td = this.getClosest("td");
		if (!td) {
			break;
		}
		splitRow(td);
		break;

		// COLUMNS

	case "TO-col-insert-before":
	case "TO-col-insert-after":
		var td = this.getClosest("td");
		if (!td) {
			break;
		}
		var rows = td.parentNode.parentNode.rows;
		var index = td.cellIndex;
		for (var i = rows.length; --i >= 0;) {
      /*
      var tr = rows;
      var otd = tr.insertCell(index + (/after/.test(button_id) ? 1 : 0));
      otd.innerHTML = mozbr;
      */
			var tr = rows[i];
			var ref = tr.cells[index + (/after/.test(button_id) ? 1 : 0)];
			// >MT: fix issue when accessing out of bounds
			if (typeof ref == "undefined") ref = null;
			// <MT
			var otd = editor._doc.createElement("td");
			otd.innerHTML = mozbr;
			tr.insertBefore(otd, ref);

		}
		editor.focusEditor();
		break;
	case "TO-col-split":
		var td = this.getClosest("td");
		if (!td) {
			break;
		}
		splitCol(td);
		break;
	case "TO-col-delete":
		var td = this.getClosest("td");
		if (!td) {
			break;
		}
		var index = td.cellIndex;
		if (td.parentNode.cells.length == 1) {
			while (td.tagName.toLowerCase() != 'table')
				td = td.parentNode;
			HTMLArea.removeFromParent(td);
			break;
		}
		// set the caret first to a position that doesn't disappear
		selectNextNode(td);
		var rows = td.parentNode.parentNode.rows;
		for (var i = rows.length; --i >= 0;) {
			var tr = rows[i];
			tr.removeChild(tr.cells[index]);
		}
		editor.forceRedraw();
		editor.focusEditor();
		editor.updateToolbar();
		break;

		// CELLS

	case "TO-cell-split":
		var td = this.getClosest("td");
		if (!td) {
			break;
		}
		splitCell(td);
		break;
	case "TO-cell-insert-before":
	case "TO-cell-insert-after":
		var td = this.getClosest("td");
		if (!td) {
			break;
		}
		var tr = td.parentNode;
		var otd = editor._doc.createElement("td");
		otd.innerHTML = mozbr;
		tr.insertBefore(otd, /after/.test(button_id) ? td.nextSibling : td);
		editor.forceRedraw();
		editor.focusEditor();
		break;
	case "TO-cell-delete":
		var td = this.getClosest("td");
		if (!td) {
			break;
		}
		if (td.parentNode.cells.length == 1) {
			while (td.tagName.toLowerCase() != 'table')
				td = td.parentNode;
			HTMLArea.removeFromParent(td);
			break;
		}
		// set the caret first to a position that doesn't disappear
		selectNextNode(td);
		td.parentNode.removeChild(td);
		editor.forceRedraw();
		editor.updateToolbar();
		break;
	case "TO-cell-merge":
		// !! FIXME: Mozilla specific !!
		var sel = editor._getSelection();
		var range, i = 0;
		var rows = [];
		var row = null;
		var cells = null;
		if (!HTMLArea.is_ie) {
			try {
				while (range = sel.getRangeAt(i++)) {
					var td = range.startContainer.childNodes[range.startOffset];
					if (td.parentNode != row) {
						row = td.parentNode;
						(cells) && rows.push(cells);
						cells = [];
					}
					cells.push(td);
				}
			} catch(e) {/* finished walking through selection */}
			rows.push(cells);
		} else {
			// Internet Explorer "browser"
			var td = this.getClosest("td");
			if (!td) {
				alert(HTMLArea._lc("Please click into some cell", "TableOperations"));
				break;
			}
			var tr = td.parentElement;
			var no_cols = prompt(HTMLArea._lc("How many columns would you like to merge?", "TableOperations"), 2);
			if (!no_cols) {
				// cancelled
				break;
			}
			var no_rows = prompt(HTMLArea._lc("How many rows would you like to merge?", "TableOperations"), 2);
			if (!no_rows) {
				// cancelled
				break;
			}
			var cell_index = td.cellIndex;
			while (no_rows-- > 0) {
				td = tr.cells[cell_index];
				cells = [td];
				for (var i = 1; i < no_cols; ++i) {
					td = td.nextSibling;
					if (!td) {
						break;
					}
					cells.push(td);
				}
				rows.push(cells);
				tr = tr.nextSibling;
				if (!tr) {
					break;
				}
			}
		}
		var HTML = "";
		for (i = 0; i < rows.length; ++i) {
			// i && (HTML += "<br />");
			var cells = rows[i];
			for (var j = 0; j < cells.length; ++j) {
				// j && (HTML += "&nbsp;");
				var cell = cells[j];
				HTML += cell.innerHTML;
				(i || j) && (cell.parentNode.removeChild(cell));
			}
		}
		var td = rows[0][0];
		td.innerHTML = HTML;
		td.rowSpan = rows.length;
		td.colSpan = rows[0].length;
		editor.selectNodeContents(td);
		editor.forceRedraw();
		editor.focusEditor();
		break;

		// PROPERTIES

	case "TO-table-prop":
		this.dialogTableProperties();
		break;

	case "TO-row-prop":
		this.dialogRowCellProperties(false);
		break;

	case "TO-cell-prop":
		this.dialogRowCellProperties(true);
		break;

	default:
		alert("Button [" + button_id + "] not yet implemented");
	}
};

// the list of buttons added by this plugin
TableOperations.btnList = [
	// table properties button
    ["table-prop",       "table", "Table properties"],
	null,			// separator

	// ROWS
	["row-prop",         "tr", "Row properties"],
	["row-insert-above", "tr", "Insert row before"],
	["row-insert-under", "tr", "Insert row after"],
	["row-delete",       "tr", "Delete row"],
	["row-split",        "td[rowSpan!=1]", "Split row"],
	null,

	// COLS
	["col-insert-before", "td", "Insert column before"],
	["col-insert-after",  "td", "Insert column after"],
	["col-delete",        "td", "Delete column"],
	["col-split",         "td[colSpan!=1]", "Split column"],
	null,

	// CELLS
	["cell-prop",          "td", "Cell properties"],
	["cell-insert-before", "td", "Insert cell before"],
	["cell-insert-after",  "td", "Insert cell after"],
	["cell-delete",        "td", "Delete cell"],
	["cell-merge",         "tr", "Merge cells"],
	["cell-split",         "td[colSpan!=1,rowSpan!=1]", "Split cell"]
	];



//// GENERIC CODE [style of any element; this should be moved into a separate
//// file as it'll be very useful]
//// BEGIN GENERIC CODE -----------------------------------------------------

TableOperations.getLength = function(value) {
	var len = parseInt(value);
	if (isNaN(len)) {
		len = "";
	}
	return len;
};

// Applies the style found in "params" to the given element.
TableOperations.processStyle = function(params, element) {
	var style = element.style;
	for (var i in params) {
    if(typeof params[i] == 'function') continue;
		var val = params[i];
		switch (i) {
		case "f_st_backgroundColor":
			style.backgroundColor = val;
			break;
		case "f_st_color":
			style.color = val;
			break;
		case "f_st_backgroundImage":
			if (/\S/.test(val)) {
				style.backgroundImage = "url(" + val + ")";
			} else {
				style.backgroundImage = "none";
			}
			break;
		case "f_st_borderWidth":
			style.borderWidth = val;
			break;
		case "f_st_borderStyle":
			style.borderStyle = val;
			break;
		case "f_st_borderColor":
			style.borderColor = val;
			break;
		case "f_st_borderCollapse":
			style.borderCollapse = val ? "collapse" : "";
			break;
		case "f_st_width":
			if (/\S/.test(val)) {
				style.width = val + params["f_st_widthUnit"];
			} else {
				style.width = "";
			}
			break;
		case "f_st_height":
			if (/\S/.test(val)) {
				style.height = val + params["f_st_heightUnit"];
			} else {
				style.height = "";
			}
			break;
		case "f_st_textAlign":
			if (val == "char") {
				var ch = params["f_st_textAlignChar"];
				if (ch == '"') {
					ch = '\\"';
				}
				style.textAlign = '"' + ch + '"';
			} else {
				style.textAlign = val;
			}
			break;
		case "f_st_verticalAlign":
			style.verticalAlign = val;
			break;
		case "f_st_float":
			style.cssFloat = val;
			break;
// 		case "f_st_margin":
// 			style.margin = val + "px";
// 			break;
// 		    case "f_st_padding":
// 			style.padding = val + "px";
// 			break;
		}
	}
};

// Returns an HTML element for a widget that allows color selection.  That is,
// a button that contains the given color, if any, and when pressed will popup
// the sooner-or-later-to-be-rewritten select_color.html dialog allowing user
// to select some color.  If a color is selected, an input field with the name
// "f_st_"+name will be updated with the color value in #123456 format.
TableOperations.createColorButton = function(doc, editor, color, name) {
	if (!color) {
		color = "";
	} else if (!/#/.test(color)) {
		color = HTMLArea._colorToRgb(color);
	}

	var df = doc.createElement("span");
 	var field = doc.createElement("input");
	field.type = "hidden";
	df.appendChild(field);
 	field.name = "f_st_" + name;
	field.value = color;
	var button = doc.createElement("span");
	button.className = "buttonColor";
	df.appendChild(button);
	var span = doc.createElement("span");
	span.className = "chooser";
	// span.innerHTML = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
	// >MT: fix for special cases
	try { span.style.backgroundColor = color; } catch (e) {}
	// <MT
	button.appendChild(span);
	button.onmouseover = function() { if (!this.disabled) { this.className += " buttonColor-hilite"; }};
	button.onmouseout = function() { if (!this.disabled) { this.className = "buttonColor"; }};
	span.onclick = function() {
		if (this.parentNode.disabled) {
			return false;
		}
		var dialog = "select_color.php";
		dialog += name == "color" ? "?type=fg" : "?type=bg";
		editor._popupDialog(400, 206, dialog, function(color) {
			if (color) {
				span.style.backgroundColor = "#" + color;
				field.value = "#" + color;
			}
		}, color);
	};
	var span2 = doc.createElement("span");
	span2.innerHTML = "&#x00d7;";
	span2.className = "nocolor";
	span2.title = HTMLArea._lc("Unset color", "TableOperations");
	button.appendChild(span2);
	span2.onmouseover = function() { if (!this.parentNode.disabled) { this.className += " nocolor-hilite"; }};
	span2.onmouseout = function() { if (!this.parentNode.disabled) { this.className = "nocolor"; }};
	span2.onclick = function() {
		span.style.backgroundColor = "";
		field.value = "";
	};
	return df;
};

TableOperations.createStyleLayoutFieldset = function(doc, editor, el) {
	var fieldset = doc.createElement("fieldset");
	var legend = doc.createElement("legend");
	fieldset.appendChild(legend);
	legend.innerHTML = HTMLArea._lc("Layout", "TableOperations");
	var table = doc.createElement("table");
	fieldset.appendChild(table);
	table.style.width = "100%";
	var tbody = doc.createElement("tbody");
	table.appendChild(tbody);

	var tagname = el.tagName.toLowerCase();
	var tr, td, input, select, option, options, i;

	if (tagname != "td" && tagname != "tr" && tagname != "th") {
		tr = doc.createElement("tr");
		tbody.appendChild(tr);
		td = doc.createElement("td");
		td.className = "label";
		tr.appendChild(td);
		td.innerHTML = HTMLArea._lc("Float", "TableOperations") + ":";
		td = doc.createElement("td");
		tr.appendChild(td);
		select = doc.createElement("select");
		td.appendChild(select);
		select.name = "f_st_float";
		options = ["None", "Left", "Right"];
		for (var i = 0; i < options.length; ++i) {
			var Val = options[i];
			var val = options[i].toLowerCase();
			option = doc.createElement("option");
			option.innerHTML = HTMLArea._lc(Val, "TableOperations");
			option.value = val;
			option.selected = (("" + el.style.cssFloat).toLowerCase() == val);
			select.appendChild(option);
		}
	}

	tr = doc.createElement("tr");
	tbody.appendChild(tr);
	td = doc.createElement("td");
	td.className = "label";
	tr.appendChild(td);
	td.innerHTML = HTMLArea._lc("Width", "TableOperations") + ":";
	td = doc.createElement("td");
	tr.appendChild(td);
	input = doc.createElement("input");
	input.type = "text";
	input.value = TableOperations.getLength(el.style.width);
	input.size = "5";
	input.name = "f_st_width";
	input.style.marginRight = "0.5em";
	td.appendChild(input);
	select = doc.createElement("select");
	select.name = "f_st_widthUnit";
	option = doc.createElement("option");
	option.innerHTML = HTMLArea._lc("percent", "TableOperations");
	option.value = "%";
	option.selected = /%/.test(el.style.width);
	select.appendChild(option);
	option = doc.createElement("option");
	option.innerHTML = HTMLArea._lc("pixels", "TableOperations");
	option.value = "px";
	option.selected = /px/.test(el.style.width);
	select.appendChild(option);
	td.appendChild(select);

	select.style.marginRight = "0.5em";
	td.appendChild(doc.createTextNode(HTMLArea._lc("Text align", "TableOperations") + ":"));
	select = doc.createElement("select");
	select.style.marginLeft = select.style.marginRight = "0.5em";
	td.appendChild(select);
	select.name = "f_st_textAlign";
	options = ["Left", "Center", "Right", "Justify"];
	if (tagname == "td") {
		options.push("Char");
	}
	input = doc.createElement("input");
	input.name = "f_st_textAlignChar";
	input.size = "1";
	input.style.fontFamily = "monospace";
	td.appendChild(input);
	for (var i = 0; i < options.length; ++i) {
		var Val = options[i];
		var val = Val.toLowerCase();
		option = doc.createElement("option");
		option.value = val;
		option.innerHTML = HTMLArea._lc(Val, "TableOperations");
		option.selected = (el.style.textAlign.toLowerCase() == val);
		select.appendChild(option);
	}
	function setCharVisibility(value) {
		input.style.visibility = value ? "visible" : "hidden";
		if (value) {
			input.focus();
			input.select();
		}
	};
	select.onchange = function() { setCharVisibility(this.value == "char"); };
	setCharVisibility(select.value == "char");

	tr = doc.createElement("tr");
	tbody.appendChild(tr);
	td = doc.createElement("td");
	td.className = "label";
	tr.appendChild(td);
	td.innerHTML = HTMLArea._lc("Height", "TableOperations") + ":";
	td = doc.createElement("td");
	tr.appendChild(td);
	input = doc.createElement("input");
	input.type = "text";
	input.value = TableOperations.getLength(el.style.height);
	input.size = "5";
	input.name = "f_st_height";
	input.style.marginRight = "0.5em";
	td.appendChild(input);
	select = doc.createElement("select");
	select.name = "f_st_heightUnit";
	option = doc.createElement("option");
	option.innerHTML = HTMLArea._lc("percent", "TableOperations");
	option.value = "%";
	option.selected = /%/.test(el.style.height);
	select.appendChild(option);
	option = doc.createElement("option");
	option.innerHTML = HTMLArea._lc("pixels", "TableOperations");
	option.value = "px";
	option.selected = /px/.test(el.style.height);
	select.appendChild(option);
	td.appendChild(select);

	select.style.marginRight = "0.5em";
	td.appendChild(doc.createTextNode(HTMLArea._lc("Vertical align", "TableOperations") + ":"));
	select = doc.createElement("select");
	select.name = "f_st_verticalAlign";
	select.style.marginLeft = "0.5em";
	td.appendChild(select);
	options = ["Top", "Middle", "Bottom", "Baseline"];
	for (var i = 0; i < options.length; ++i) {
		var Val = options[i];
		var val = Val.toLowerCase();
		option = doc.createElement("option");
		option.value = val;
		option.innerHTML = HTMLArea._lc(Val, "TableOperations");
		option.selected = (el.style.verticalAlign.toLowerCase() == val);
		select.appendChild(option);
	}

	return fieldset;
};

// Returns an HTML element containing the style attributes for the given
// element.  This can be easily embedded into any dialog; the functionality is
// also provided.
TableOperations.createStyleFieldset = function(doc, editor, el) {
	var fieldset = doc.createElement("fieldset");
	var legend = doc.createElement("legend");
	fieldset.appendChild(legend);
	legend.innerHTML = HTMLArea._lc("CSS Style", "TableOperations");
	var table = doc.createElement("table");
	fieldset.appendChild(table);
	table.style.width = "100%";
	var tbody = doc.createElement("tbody");
	table.appendChild(tbody);

	var tr, td, input, select, option, options, i;

	tr = doc.createElement("tr");
	tbody.appendChild(tr);
	td = doc.createElement("td");
	tr.appendChild(td);
	td.className = "label";
	td.innerHTML = HTMLArea._lc("Background", "TableOperations") + ":";
	td = doc.createElement("td");
	tr.appendChild(td);
	var df = TableOperations.createColorButton(doc, editor, el.style.backgroundColor, "backgroundColor");
	df.firstChild.nextSibling.style.marginRight = "0.5em";
	td.appendChild(df);
	td.appendChild(doc.createTextNode(HTMLArea._lc("Image URL", "TableOperations") + ": "));
	input = doc.createElement("input");
	input.type = "text";
	input.name = "f_st_backgroundImage";
	if (el.style.backgroundImage.match(/url\(\s*(.*?)\s*\)/)) {
		input.value = RegExp.$1;
	}
	// input.style.width = "100%";
	td.appendChild(input);

	tr = doc.createElement("tr");
	tbody.appendChild(tr);
	td = doc.createElement("td");
	tr.appendChild(td);
	td.className = "label";
	td.innerHTML = HTMLArea._lc("FG Color", "TableOperations") + ":";
	td = doc.createElement("td");
	tr.appendChild(td);
	td.appendChild(TableOperations.createColorButton(doc, editor, el.style.color, "color"));

	// for better alignment we include an invisible field.
	input = doc.createElement("input");
	input.style.visibility = "hidden";
	input.type = "text";
	td.appendChild(input);

	tr = doc.createElement("tr");
	tbody.appendChild(tr);
	td = doc.createElement("td");
	tr.appendChild(td);
	td.className = "label";
	td.innerHTML = HTMLArea._lc("Border", "TableOperations") + ":";
	td = doc.createElement("td");
	tr.appendChild(td);

	var colorButton = TableOperations.createColorButton(doc, editor, el.style.borderColor, "borderColor");
	var btn = colorButton.firstChild.nextSibling;
	td.appendChild(colorButton);
	// borderFields.push(btn);
	btn.style.marginRight = "0.5em";

	select = doc.createElement("select");
	var borderFields = [];
	td.appendChild(select);
	select.name = "f_st_borderStyle";
	options = ["none", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"];
	var currentBorderStyle = el.style.borderStyle;
	// Gecko reports "solid solid solid solid" for "border-style: solid".
	// That is, "top right bottom left" -- we only consider the first
	// value.
	(currentBorderStyle.match(/([^\s]*)\s/)) && (currentBorderStyle = RegExp.$1);
	for (var i in options) {
    if(typeof options[i] == 'function') continue;
		var val = options[i];
		option = doc.createElement("option");
		option.value = val;
		option.innerHTML = val;
		(val == currentBorderStyle) && (option.selected = true);
		select.appendChild(option);
	}
	select.style.marginRight = "0.5em";
	function setBorderFieldsStatus(value) {
		for (var i = 0; i < borderFields.length; ++i) {
			var el = borderFields[i];
			el.style.visibility = value ? "hidden" : "visible";
			if (!value && (el.tagName.toLowerCase() == "input")) {
				el.focus();
				el.select();
			}
		}
	};
	select.onchange = function() { setBorderFieldsStatus(this.value == "none"); };

	input = doc.createElement("input");
	borderFields.push(input);
	input.type = "text";
	input.name = "f_st_borderWidth";
	input.value = TableOperations.getLength(el.style.borderWidth);
	input.size = "5";
	td.appendChild(input);
	input.style.marginRight = "0.5em";
	var span = doc.createElement("span");
	span.innerHTML = HTMLArea._lc("pixels", "TableOperations");
	td.appendChild(span);
	borderFields.push(span);

	setBorderFieldsStatus(select.value == "none");

	if (el.tagName.toLowerCase() == "table") {
		// the border-collapse style is only for tables
		tr = doc.createElement("tr");
		tbody.appendChild(tr);
		td = doc.createElement("td");
		td.className = "label";
		tr.appendChild(td);
		input = doc.createElement("input");
		input.type = "checkbox";
		input.name = "f_st_borderCollapse";
		input.id = "f_st_borderCollapse";
		var val = (/collapse/i.test(el.style.borderCollapse));
		input.checked = val ? 1 : 0;
		td.appendChild(input);

		td = doc.createElement("td");
		tr.appendChild(td);
		var label = doc.createElement("label");
		label.htmlFor = "f_st_borderCollapse";
		label.innerHTML = HTMLArea._lc("Collapsed borders", "TableOperations");
		td.appendChild(label);
	}

// 	tr = doc.createElement("tr");
// 	tbody.appendChild(tr);
// 	td = doc.createElement("td");
// 	td.className = "label";
// 	tr.appendChild(td);
// 	td.innerHTML = HTMLArea._lc("Margin", "TableOperations") + ":";
// 	td = doc.createElement("td");
// 	tr.appendChild(td);
// 	input = doc.createElement("input");
// 	input.type = "text";
// 	input.size = "5";
// 	input.name = "f_st_margin";
// 	td.appendChild(input);
// 	input.style.marginRight = "0.5em";
// 	td.appendChild(doc.createTextNode(HTMLArea._lc("Padding", "TableOperations") + ":"));

// 	input = doc.createElement("input");
// 	input.type = "text";
// 	input.size = "5";
// 	input.name = "f_st_padding";
// 	td.appendChild(input);
// 	input.style.marginLeft = "0.5em";
// 	input.style.marginRight = "0.5em";
// 	td.appendChild(doc.createTextNode(HTMLArea._lc("pixels", "TableOperations")));

	return fieldset;
};

//// END GENERIC CODE -------------------------------------------------------

/**
 * Add an empty css_style to Config object's prototype
 *  the format is { '.className' : 'Description' }
 */

HTMLArea.Config.prototype.css_style = { };

/**
 * This method loads an external stylesheet and uses it in the stylist
 */
HTMLArea.Config.prototype.stylistLoadStylesheet = function(url, altnames)
{
  if(!altnames) altnames = { };
  var newStyles = HTMLArea.ripStylesFromCSSFile(url);
  for(var i in newStyles)
  {
    if(altnames[i])
    {
      this.css_style[i] = altnames[i];
    }
    else
    {
      this.css_style[i] = newStyles[i];
    }
  }
  this.pageStyleSheets[this.pageStyleSheets.length] = url;
};

/**
 * This method takes raw style definitions and uses them in the stylist
 */
HTMLArea.Config.prototype.stylistLoadStyles = function(styles, altnames)
{
  if(!altnames) altnames = { };
  var newStyles = HTMLArea.ripStylesFromCSSString(styles);
  for(var i in newStyles)
  {
    if(altnames[i])
    {
      this.css_style[i] = altnames[i];
    }
    else
    {
      this.css_style[i] = newStyles[i];
    }
  }
  this.pageStyle += styles;
};



/**
 * Fill the stylist panel with styles that may be applied to the current selection.  Styles
 * are supplied in the css_style property of the HTMLArea.Config object, which is in the format
 * { '.className' : 'Description' }
 * classes that are defined on a specific tag (eg 'a.email_link') are only shown in the panel
 *    when an element of that type is selected.
 * classes that are defined with selectors/psuedoclasses (eg 'a.email_link:hover') are never
 *    shown (if you have an 'a.email_link' without the pseudoclass it will be shown of course)
 * multiple classes (eg 'a.email_link.staff_member') are shown as a single class, and applied
 *    to the element as multiple classes (class="email_link staff_member")
 * you may click a class name in the stylist panel to add it, and click again to remove it
 * you may add multiple classes to any element
 * spans will be added where no single _and_entire_ element is selected
 */
HTMLArea.prototype._fillStylist = function()
{
  if(!this._stylist) return false;
  this._stylist.innerHTML = '<h1>'+HTMLArea._lc('Styles', 'Stylist')+'</h1>';

  var may_apply = true;
  var sel       = this._getSelection();

  // What is applied
  // var applied = this._getAncestorsClassNames(this._getSelection());

  // Get an active element
  var active_elem = this._activeElement(sel);

  for(var x in this.config.css_style)
  {
    var tag   = null;
    var className = x.trim();
    var applicable = true;
    var apply_to   = active_elem;

    if(applicable && /[^a-zA-Z0-9_.-]/.test(className))
    {
      applicable = false; // Only basic classes are accepted, no selectors, etc.. presumed
                          // that if you have a.foo:visited you'll also have a.foo
      // alert('complex');
    }

    if(className.indexOf('.') < 0)
    {
      // No class name, just redefines a tag
      applicable = false;
    }

    if(applicable && (className.indexOf('.') > 0))
    {
      // requires specific html tag
      tag = className.substring(0, className.indexOf('.')).toLowerCase();
      className = className.substring(className.indexOf('.'), className.length);

      // To apply we must have an ancestor tag that is the right type
      if(active_elem != null && active_elem.tagName.toLowerCase() == tag)
      {
        applicable = true;
        apply_to = active_elem;
      }
      else
      {
        if(this._getFirstAncestor(this._getSelection(), [tag]) != null)
        {
          applicable = true;
          apply_to = this._getFirstAncestor(this._getSelection(), [tag]);
        }
        else
        {
          // alert (this._getFirstAncestor(this._getSelection(), tag));
          // If we don't have an ancestor, but it's a div/span/p/hx stle, we can make one
          if(( tag == 'div' || tag == 'span' || tag == 'p'
              || (tag.substr(0,1) == 'h' && tag.length == 2 && tag != 'hr')))
          {
            if(!this._selectionEmpty(this._getSelection()))
            {
              applicable = true;
              apply_to = 'new';
            }
            else
            {
              // See if we can get a paragraph or header that can be converted
              apply_to = this._getFirstAncestor(sel, ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7']);
              if(apply_to != null)
              {
                applicable = true;
              }
            }
          }
          else
          {
            applicable = false;
          }
        }
      }
    }

    if(applicable)
    {
      // Remove the first .
      className = className.substring(className.indexOf('.'), className.length);

      // Replace any futher ones with spaces (for multiple class definitions)
      className = className.replace('.', ' ');

      if(apply_to == null)
      {
        if(this._selectionEmpty(this._getSelection()))
        {
          // Get the previous element and apply to that
          apply_to = this._getFirstAncestor(this._getSelection(), null);
        }
        else
        {
          apply_to = 'new';
          tag      = 'span';
        }
      }
    }

    var applied    = (this._ancestorsWithClasses(sel, tag, className).length > 0 ? true : false);
    var applied_to = this._ancestorsWithClasses(sel, tag, className);

    if(applicable)
    {
      var anch = document.createElement('a');
      anch._stylist_className = className.trim();
      anch._stylist_applied   = applied;
      anch._stylist_appliedTo = applied_to;
      anch._stylist_applyTo = apply_to;
      anch._stylist_applyTag = tag;

      anch.innerHTML = this.config.css_style[x];
      anch.href = 'javascript:void(0)';
      var editor = this;
      anch.onclick = function()
      {
        if(this._stylist_applied == true)
        {
          editor._stylistRemoveClasses(this._stylist_className, this._stylist_appliedTo);
        }
        else
        {
          editor._stylistAddClasses(this._stylist_applyTo, this._stylist_applyTag, this._stylist_className);
        }
        return false;
      }

      anch.style.display = 'block';
      anch.style.paddingLeft = '3px';
      anch.style.paddingTop = '1px';
      anch.style.paddingBottom = '1px';
      anch.style.textDecoration = 'none';

      if(applied)
      {
        anch.style.background = 'Highlight';
        anch.style.color = 'HighlightText';
      }

      this._stylist.appendChild(anch);
    }
  }
};


/**
 * Add the given classes (space seperated list) to the currently selected element
 * (will add a span if none selected)
 */
HTMLArea.prototype._stylistAddClasses = function(el, tag, classes)
  {
    if(el == 'new')
    {
      this.insertHTML('<' + tag + ' class="' + classes + '">' + this.getSelectedHTML() + '</' + tag + '>');
    }
    else
    {
      if(tag != null && el.tagName.toLowerCase() != tag)
      {
        // Have to change the tag!
        var new_el = this.switchElementTag(el, tag);

        if(typeof el._stylist_usedToBe != 'undefined')
        {
          new_el._stylist_usedToBe = el._stylist_usedToBe;
          new_el._stylist_usedToBe[new_el._stylist_usedToBe.length] = {'tagName' : el.tagName, 'className' : el.getAttribute('class')};
        }
        else
        {
          new_el._stylist_usedToBe = [{'tagName' : el.tagName, 'className' : el.getAttribute('class')}];
        }

        HTMLArea.addClasses(new_el, classes);
      }
      else
      {
        HTMLArea._addClasses(el, classes);
      }
    }
    this.focusEditor();
    this.updateToolbar();
  };

/**
 * Remove the given classes (space seperated list) from the given elements (array of elements)
 */
HTMLArea.prototype._stylistRemoveClasses = function(classes, from)
  {
    for(var x = 0; x < from.length; x++)
    {
      this._stylistRemoveClassesFull(from[x], classes);
    }
    this.focusEditor();
    this.updateToolbar();
  };

HTMLArea.prototype._stylistRemoveClassesFull = function(el, classes)
{
  if(el != null)
  {
    var thiers = el.className.trim().split(' ');
    var new_thiers = [ ];
    var ours   = classes.split(' ');
    for(var x = 0; x < thiers.length; x++)
    {
      var exists = false;
      for(var i = 0; exists == false && i < ours.length; i++)
      {
        if(ours[i] == thiers[x])
        {
          exists = true;
        }
      }
      if(exists == false)
      {
        new_thiers[new_thiers.length] = thiers[x];
      }
    }

    if(new_thiers.length == 0 && el._stylist_usedToBe && el._stylist_usedToBe.length > 0 && el._stylist_usedToBe[el._stylist_usedToBe.length - 1].className != null)
    {
      // Revert back to what we were IF the classes are identical
      var last_el = el._stylist_usedToBe[el._stylist_usedToBe.length - 1];
      var last_classes = HTMLArea.arrayFilter(last_el.className.trim().split(' '), function(c) { if (c == null || c.trim() == '') { return false;} return true; });

      if(
        (new_thiers.length == 0)
        ||
        (
        HTMLArea.arrayContainsArray(new_thiers, last_classes)
        && HTMLArea.arrayContainsArray(last_classes, new_thiers)
        )
      )
      {
        el = this.switchElementTag(el, last_el.tagName);
        new_thiers = last_classes;
      }
      else
      {
        // We can't rely on the remembered tags any more
        el._stylist_usedToBe = [ ];
      }
    }

    if(     new_thiers.length > 0
        ||  el.tagName.toLowerCase() != 'span'
        || (el.id && el.id != '')
      )
    {
      el.className = new_thiers.join(' ').trim();
    }
    else
    {
      // Must be a span with no classes and no id, so we can splice it out
      var prnt = el.parentNode;
      var childs = el.childNodes;
      for(var x = 0; x < childs.length; x++)
      {
        prnt.insertBefore(childs[x], el);
      }
      prnt.removeChild(el);
    }
  }
};

/**
 * Change the tag of an element
 */
HTMLArea.prototype.switchElementTag = function(el, tag)
{
  var prnt = el.parentNode;
  var new_el = this._doc.createElement(tag);

  if(HTMLArea.is_ie || el.hasAttribute('id'))    new_el.setAttribute('id', el.getAttribute('id'));
  if(HTMLArea.is_ie || el.hasAttribute('style')) new_el.setAttribute('style', el.getAttribute('style'));

  var childs = el.childNodes;
  for(var x = 0; x < childs.length; x++)
  {
    new_el.appendChild(childs[x].cloneNode(true));
  }

  prnt.insertBefore(new_el, el);
  new_el._stylist_usedToBe = [el.tagName];
  prnt.removeChild(el);
  this.selectNodeContents(new_el);
  return new_el;
};

HTMLArea.prototype._getAncestorsClassNames = function(sel)
{
  // Scan upwards to find a block level element that we can change or apply to
  var prnt = this._activeElement(sel);
  if(prnt == null)
  {
    prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer);
  }

  var classNames = [ ];
  while(prnt)
  {
    if(prnt.nodeType == 1)
    {
      var classes = prnt.className.trim().split(' ');
      for(var x = 0; x < classes.length; x++)
      {
        classNames[classNames.length] = classes[x];
      }

      if(prnt.tagName.toLowerCase() == 'body') break;
      if(prnt.tagName.toLowerCase() == 'table'  ) break;
    }
      prnt = prnt.parentNode;
  }

  return classNames;
};

HTMLArea.prototype._ancestorsWithClasses = function(sel, tag, classes)
{
  var ancestors = [ ];
  var prnt = this._activeElement(sel);
  if(prnt == null)
  {
    try
    {
      prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer);
    }
    catch(e)
    {
      return ancestors;
    }
  }
  var search_classes = classes.trim().split(' ');

  while(prnt)
  {
    if(prnt.nodeType == 1)
    {
      if(tag == null || prnt.tagName.toLowerCase() == tag)
      {
        var classes = prnt.className.trim().split(' ');
        var found_all = true;
        for(var i = 0; i < search_classes.length; i++)
        {
          var found_class = false;
          for(var x = 0; x < classes.length; x++)
          {
            if(search_classes[i] == classes[x])
            {
              found_class = true;
              break;
            }
          }

          if(!found_class)
          {
            found_all = false;
            break;
          }
        }

        if(found_all) ancestors[ancestors.length] = prnt;
      }
      if(prnt.tagName.toLowerCase() == 'body')    break;
      if(prnt.tagName.toLowerCase() == 'table'  ) break;
    }
    prnt = prnt.parentNode;
  }

  return ancestors;
};


HTMLArea.ripStylesFromCSSFile = function(URL)
{
  var css = HTMLArea._geturlcontent(URL);
  return HTMLArea.ripStylesFromCSSString(css);
};

HTMLArea.ripStylesFromCSSString = function(css)
{
  // We are only interested in the selectors, the rules are not important
  //  so we'll drop out all coments and rules
  RE_comment = /\/\*(.|\r|\n)*?\*\//g;
  RE_rule    = /\{(.|\r|\n)*?\}/g;
  css = css.replace(RE_comment, '');
  css = css.replace(RE_rule, ',');

  // And split on commas
  css = css.split(',');

  // And add those into our structure
  var selectors = { };
  for(var x = 0; x < css.length; x++)
  {
    if(css[x].trim())
    {
      selectors[css[x].trim()] = css[x].trim();
    }
  }


  return selectors;
};

// Make our right side panel and insert appropriatly
function Stylist(editor, args)
{
  this.editor = editor;
  // >MT: make sure to remove when recreated
  if (typeof editor._stylist != "undefined" && editor._stylist != null)
    editor.removePanel(editor._stylist);
  // <MT
  editor._stylist = null; // This needs to be changes to be Stylist::_stylist sometime
  editor._stylist = editor.addPanel('right');
  HTMLArea.addClass(editor._stylist, 'stylist');

  var stylist = this;
  editor.notifyOn('modechange',
                  function(e,args)
                  {
                    switch(args.mode)
                    {
                      case 'text':
                      {
                        editor.hidePanel(editor._stylist);
                        break;
                      }
                      case 'wysiwyg':
                      {
                        editor.showPanel(editor._stylist);
                        break;
                      }
                    }
                  }
                  );
};

Stylist._pluginInfo =
{
  name     : "Stylist",
  version  : "1.0",
  developer: "James Sleeman",
  developer_url: "http://www.gogo.co.nz/",
  c_owner      : "Gogo Internet Services",
  license      : "htmlArea",
  sponsor      : "Gogo Internet Services",
  sponsor_url  : "http://www.gogo.co.nz/"
};

Stylist.prototype.onGenerate = function()
{
  var editor = this.editor;
  if(typeof editor.config.css_style == 'undefined' || HTMLArea.objectProperties(editor.config.css_style).length == 0)
  {
    editor.removePanel(editor._stylist);
    editor._stylist = null;
  }
};

Stylist.prototype.onUpdateToolbar = function()
{
  if(this.editor._stylist)
  {
    if(this._timeoutID)
    {
      window.clearTimeout(this._timeoutID);
    }

    var e = this.editor;
    this._timeoutID = window.setTimeout(function() { e._fillStylist(); }, 250);
  }
};

// Spell Checker Plugin for HTMLArea-3.0
// Sponsored by www.americanbible.org
// Implementation by Mihai Bazon, http://dynarch.com/mishoo/
//
// (c) dynarch.com 2003.
// Distributed under the same terms as HTMLArea itself.
// This notice MUST stay intact for use (see license.txt).
//
// $Id: spell-checker.js 376 2005-10-01 09:23:52Z mokhet $

HTMLArea.Config.prototype.SpellChecker = { 'backend': 'php', 'personalFilesDir' : '', 'defaultDictionary' : 'en_GB' };

function SpellChecker(editor) {
  this.editor = editor;

  var cfg = editor.config;
  var bl = SpellChecker.btnList;
  var self = this;

  // see if we can find the mode switch button, insert this before that
  var id = "SC-spell-check";
  // >MT: Change terminology of "spell-check" to "Spell Checker"
  cfg.registerButton(id, this._lc("Spell Checker"), editor.imgURL("spell-check.gif", "SpellChecker"), false,
  // <MT
             function(editor, id) {
               // dispatch button press event
               self.buttonPress(editor, id);
             });

  // >MT: relocate
  cfg.addToolbarElement("SC-spell-check", "removeformat", 1);
  // <MT
};

SpellChecker._pluginInfo = {
  name          : "SpellChecker",
  version       : "1.0",
  developer     : "Mihai Bazon",
  developer_url : "http://dynarch.com/mishoo/",
  c_owner       : "Mihai Bazon",
  sponsor       : "American Bible Society",
  sponsor_url   : "http://www.americanbible.org",
  license       : "htmlArea"
};

SpellChecker.prototype._lc = function(string) {
    return HTMLArea._lc(string, 'SpellChecker');
};

SpellChecker.btnList = [
  null, // separator
  ["spell-check"]
  ];

SpellChecker.prototype.buttonPress = function(editor, id) {
  switch (id) {
      case "SC-spell-check":
    SpellChecker.editor = editor;
    SpellChecker.init = true;
    var uiurl = _editor_url + "plugins/SpellChecker/spell-check-ui.html";
    var win;
    if (HTMLArea.is_ie) {
      win = window.open(uiurl, "SC_spell_checker",
            "toolbar=no,location=no,directories=no,status=no,menubar=no," +
            "scrollbars=no,resizable=yes,width=600,height=450");
    } else {
      win = window.open(uiurl, "SC_spell_checker",
            "toolbar=no,menubar=no,personalbar=no,width=600,height=450," +
            "scrollbars=no,resizable=yes");
    }
    win.focus();
    break;
  }
};

// this needs to be global, it's accessed from spell-check-ui.html
SpellChecker.editor = null;

function FullScreen(editor, args)
{
  this.editor = editor;
  editor._superclean_on = false;
  cfg = editor.config;

  cfg.registerButton
  ( 'fullscreen',
    // >MT: change tooltip
    //this._lc("Maximize/Minimize Editor"),
    this._lc("Toggle Full Screen Mode"),
    // <MT
    [_editor_url + cfg.imgURL + 'ed_buttons_main.gif',8,0], true,
      function(e, objname, obj)
      {
        e._fullScreen();
        if(e._isFullScreen)
        {
          obj.swapImage([_editor_url + cfg.imgURL + 'ed_buttons_main.gif',9,0]);
        }
        else
        {
          obj.swapImage([_editor_url + cfg.imgURL + 'ed_buttons_main.gif',8,0]);
        }
      }
  );

  // See if we can find 'popupeditor' and replace it with fullscreen
  // >MT: relocate button
  cfg.addToolbarElement("fullscreen", "mksQuicksave", 1);
  // <MT
}

FullScreen._pluginInfo =
{
  name     : "FullScreen",
  version  : "1.0",
  developer: "James Sleeman",
  developer_url: "http://www.gogo.co.nz/",
  c_owner      : "Gogo Internet Services",
  license      : "htmlArea",
  sponsor      : "Gogo Internet Services",
  sponsor_url  : "http://www.gogo.co.nz/"
};

FullScreen.prototype._lc = function(string) {
    return HTMLArea._lc(string, 'FullScreen');
};

/** fullScreen makes an editor take up the full window space (and resizes when the browser is resized)
 *  the principle is the same as the "popupwindow" functionality in the original htmlArea, except
 *  this one doesn't popup a window (it just uses to positioning hackery) so it's much more reliable
 *  and much faster to switch between
 */

HTMLArea.prototype._fullScreen = function()
{
  var e = this;
  function sizeItUp()
  {
    if(!e._isFullScreen || e._sizing) return false;
    e._sizing = true;
    // Width & Height of window
    var x,y;
    if (window.innerHeight) // all except Explorer
    {
      x = window.innerWidth;
      y = window.innerHeight;
    }
    else if (document.documentElement && document.documentElement.clientHeight)
      // Explorer 6 Strict Mode
    {
      x = document.documentElement.clientWidth;
      y = document.documentElement.clientHeight;
    }
    else if (document.body) // other Explorers
    {
      x = document.body.clientWidth;
      y = document.body.clientHeight;
    }

	// >MT: fix
    x -= 2;
	// <MT: fix
    e.sizeEditor(x + 'px',y + 'px',true,true);
    e._sizing = false;
	// >MT: allow callback (for hiding controls in fullscreen)
    if (typeof e._OnMode == "function")
      e._OnMode('maximize');
	// <MT
  }

  function sizeItDown()
  {
    if(e._isFullScreen || e._sizing) return false;
    e._sizing = true;
    e.initSize();
    e._sizing = false;
	// >MT: allow callback (for hiding controls in fullscreen)
    if (typeof e._OnMode == "function")
      e._OnMode('restore');
	// <MT
  }

  /** It's not possible to reliably get scroll events, particularly when we are hiding the scrollbars
   *   so we just reset the scroll ever so often while in fullscreen mode
   */
  function resetScroll()
  {
    if(e._isFullScreen)
    {
      window.scroll(0,0);
      window.setTimeout(resetScroll,150);
    }
  }

  if(typeof this._isFullScreen == 'undefined')
  {
    this._isFullScreen = false;
    if(e.target != e._iframe)
    {
      HTMLArea._addEvent(window, 'resize', sizeItUp);
    }
  }

  // Gecko has a bug where if you change position/display on a
  // designMode iframe that designMode dies.
  if(HTMLArea.is_gecko)
  {
    this.deactivateEditor();
  }

  if(this._isFullScreen)
  {
    // Unmaximize
    this._htmlArea.style.position = '';
    try
    {
      if(HTMLArea.is_ie)
      {
        var bod = document.getElementsByTagName('html');
      }
      else
      {
        var bod = document.getElementsByTagName('body');
      }
      bod[0].style.overflow='';
    }
    catch(e)
    {
      // Nutthin
    }
    this._isFullScreen = false;
    sizeItDown();

    // Restore all ancestor positions
    var ancestor = this._htmlArea;
    while((ancestor = ancestor.parentNode) && ancestor.style)
    {
      ancestor.style.position = ancestor._xinha_fullScreenOldPosition;
      ancestor._xinha_fullScreenOldPosition = null;
    }

    window.scroll(this._unScroll.x, this._unScroll.y);
  }
  else
  {

    // Get the current Scroll Positions
    this._unScroll =
    {
     x:(window.pageXOffset)?(window.pageXOffset):(document.documentElement)?document.documentElement.scrollLeft:document.body.scrollLeft,
     y:(window.pageYOffset)?(window.pageYOffset):(document.documentElement)?document.documentElement.scrollTop:document.body.scrollTop
    };


    // Make all ancestors position = static
    var ancestor = this._htmlArea;
    while((ancestor = ancestor.parentNode) && ancestor.style)
    {
      ancestor._xinha_fullScreenOldPosition = ancestor.style.position;
      ancestor.style.position = 'static';
    }

    // Maximize
    window.scroll(0,0);
    this._htmlArea.style.position = 'absolute';
    this._htmlArea.style.zIndex   = 999;
    this._htmlArea.style.left     = 0;
    this._htmlArea.style.top      = 0;
    this._isFullScreen = true;
    resetScroll();

    try
    {
      if(HTMLArea.is_ie)
      {
        var bod = document.getElementsByTagName('html');
      }
      else
      {
        var bod = document.getElementsByTagName('body');
      }
      bod[0].style.overflow='hidden';
    }
    catch(e)
    {
      // Nutthin
    }

    sizeItUp();
  }

  if(HTMLArea.is_gecko)
  {
    this.activateEditor();
  }
  this.focusEditor();
};

// Context Menu Plugin for HTMLArea-3.0
// Sponsored by www.americanbible.org
// Implementation by Mihai Bazon, http://dynarch.com/mishoo/
//
// (c) dynarch.com 2003.
// Copyright (C) MindTouch, Inc.
// Distributed under the same terms as HTMLArea itself.
// This notice MUST stay intact for use (see license.txt).
//
// $Id: context-menu.js 290 2005-08-05 10:04:54Z niko $

HTMLArea.loadStyle("menu.css", "ContextMenu");

function ContextMenu(editor) {
	this.editor = editor;
};

ContextMenu._pluginInfo = {
	name					: "ContextMenu",
	version			 : "1.0",
	developer		 : "Mihai Bazon",
	developer_url : "http://dynarch.com/mishoo/",
	c_owner			 : "dynarch.com",
	sponsor			 : "American Bible Society",
	sponsor_url	 : "http://www.americanbible.org",
	license			 : "htmlArea"
};

ContextMenu.prototype.onGenerate = function() {
	var self = this;
	var doc = this.editordoc = this.editor._iframe.contentWindow.document;
	HTMLArea._addEvents(doc, ["contextmenu"],
					function (event) {
						return self.popupMenu(HTMLArea.is_ie ? self.editor._iframe.contentWindow.event : event);
					});
	this.currentMenu = null;
};

ContextMenu.prototype.getContextMenu = function(target) {
	var self = this;
	var editor = this.editor;
	var config = editor.config;
	var menu = [];
	var tbo = this.editor.plugins.TableOperations;
	if (tbo) tbo = tbo.instance;

	var selection = editor.hasSelectedText();
	if (selection)
		menu.push([ HTMLArea._lc("Cut", "ContextMenu"), function() { editor.execCommand("cut"); }, null, config.btnList["cut"][1] ],
				[ HTMLArea._lc("Copy", "ContextMenu"), function() { editor.execCommand("copy"); }, null, config.btnList["copy"][1] ]);
	menu.push([ HTMLArea._lc("Paste", "ContextMenu"), function() { editor.execCommand("paste"); }, null, config.btnList["paste"][1] ]);

	// >MT: use current selection instead of event target if it is more accurate
	var rt = editor.getParentElement();
	var t = rt, targetInParent = false;
	while (t && !targetInParent) {
		targetInParent = t == target;
		t = t.parentNode;
	}
	if (targetInParent)
		target = rt;
	// <MT
	var currentTarget = target;
	var elmenus = [];

	var link = null;
	var table = null;
	var tr = null;
	var td = null;
	var img = null;

	function tableOperation(opcode) {
		tbo.buttonPress(editor, opcode);
	};

	function insertPara(after) {
		var el = currentTarget;
		var par = el.parentNode;
		var p = editor._doc.createElement("p");
		p.appendChild(editor._doc.createElement("br"));
		par.insertBefore(p, after ? el.nextSibling : el);
		var sel = editor._getSelection();
		var range = editor._createRange(sel);
		if (!HTMLArea.is_ie) {
			sel.removeAllRanges();
			range.selectNodeContents(p);
			range.collapse(true);
			sel.addRange(range);
		} else {
			range.moveToElementText(p);
			range.collapse(true);
			range.select();
		}
	};

	for (; target; target = target.parentNode) {
		var tag = target.tagName;
		if (!tag)
			continue;
		tag = tag.toLowerCase();
		switch (tag) {
		case "img":
			img = target;
			elmenus.push(null,
						 [ HTMLArea._lc("_Image Properties...", "ContextMenu"),
							 function() {
								 editor._insertImage(img);
							 },
							 HTMLArea._lc("Show the image properties dialog", "ContextMenu"),
							 config.btnList["insertimage"][1] ]
				);
			break;
		case "a":
			link = target;
			elmenus.push(null,
						 [ HTMLArea._lc("_Modify Link...", "ContextMenu"),
							 function() { editor.config.btnList['createlink'][3](editor); },
							 HTMLArea._lc("Current URL is", "ContextMenu") + ': ' + link.href,
							 config.btnList["createlink"][1] ],

						 [ HTMLArea._lc("Chec_k Link...", "ContextMenu"),
						 // >MT: special handling for internal links
							 function() { MindTouch.checkLink(link); },
						 // <MT
							 HTMLArea._lc("Opens this link in a new window", "ContextMenu") ],

						 [ HTMLArea._lc("_Remove Link...", "ContextMenu"),
							 function() {
								 if (confirm(HTMLArea._lc("Please confirm that you want to unlink this element.", "ContextMenu") + "\n" +
								 HTMLArea._lc("Link points to:", "ContextMenu") + " " + link.href)) {
									 while (link.firstChild)
										 link.parentNode.insertBefore(link.firstChild, link);
									 link.parentNode.removeChild(link);
								 }
							 },
							 HTMLArea._lc("Unlink the current element", "ContextMenu") ]
				);
			break;
		case "td":
			td = target;
			if (!tbo) break;
			elmenus.push(null,
						 [ HTMLArea._lc("C_ell Properties...", "ContextMenu"),
							 function() { tableOperation("TO-cell-prop"); },
							 HTMLArea._lc("Show the Table Cell Properties dialog", "ContextMenu"),
							 config.btnList["TO-cell-prop"][1] ]
				);
			break;
		case "tr":
			tr = target;
			if (!tbo) break;
			elmenus.push(null,
						 [ HTMLArea._lc("Ro_w Properties...", "ContextMenu"),
							 function() { tableOperation("TO-row-prop"); },
							 HTMLArea._lc("Show the Table Row Properties dialog", "ContextMenu"),
							 config.btnList["TO-row-prop"][1] ],

						 [ HTMLArea._lc("I_nsert Row Before", "ContextMenu"),
							 function() { tableOperation("TO-row-insert-above"); },
							 HTMLArea._lc("Insert a new row before the current one", "ContextMenu"),
							 config.btnList["TO-row-insert-above"][1] ],

						 [ HTMLArea._lc("In_sert Row After", "ContextMenu"),
							 function() { tableOperation("TO-row-insert-under"); },
							 HTMLArea._lc("Insert a new row after the current one", "ContextMenu"),
							 config.btnList["TO-row-insert-under"][1] ],

						 [ HTMLArea._lc("_Delete Row", "ContextMenu"),
							 function() { tableOperation("TO-row-delete"); },
							 HTMLArea._lc("Delete the current row", "ContextMenu"),
							 config.btnList["TO-row-delete"][1] ]
				);
			break;
		case "table":
			table = target;
			if (!tbo) break;
			elmenus.push(null,
						 [ HTMLArea._lc("_Table Properties...", "ContextMenu"),
							 function() { tableOperation("TO-table-prop"); },
							 HTMLArea._lc("Show the Table Properties dialog", "ContextMenu"),
							 config.btnList["TO-table-prop"][1] ],

						 [ HTMLArea._lc("Insert _Column Before", "ContextMenu"),
							 function() { tableOperation("TO-col-insert-before"); },
							 HTMLArea._lc("Insert a new column before the current one", "ContextMenu"),
							 config.btnList["TO-col-insert-before"][1] ],

						 [ HTMLArea._lc("Insert C_olumn After", "ContextMenu"),
							 function() { tableOperation("TO-col-insert-after"); },
							 HTMLArea._lc("Insert a new column after the current one", "ContextMenu"),
							 config.btnList["TO-col-insert-after"][1] ],

						 [ HTMLArea._lc("De_lete Column", "ContextMenu"),
							 function() { tableOperation("TO-col-delete"); },
							 HTMLArea._lc("Delete the current column", "ContextMenu"),
							 config.btnList["TO-col-delete"][1] ]
				);
			break;
		case "body":
			elmenus.push(null,
						 [ HTMLArea._lc("Justify Left", "ContextMenu"),
							 function() { editor.execCommand("justifyleft"); }, null,
							 config.btnList["justifyleft"][1] ],
						 [ HTMLArea._lc("Justify Center", "ContextMenu"),
							 function() { editor.execCommand("justifycenter"); }, null,
							 config.btnList["justifycenter"][1] ],
						 [ HTMLArea._lc("Justify Right", "ContextMenu"),
							 function() { editor.execCommand("justifyright"); }, null,
							 config.btnList["justifyright"][1] ],
						 [ HTMLArea._lc("Justify Full", "ContextMenu"),
							 function() { editor.execCommand("justifyfull"); }, null,
							 config.btnList["justifyfull"][1] ]
				);
			break;
		}
	}

	if (selection && !link)
		menu.push(null, [ HTMLArea._lc("Make lin_k...", "ContextMenu"),
					 function() { editor.config.btnList['createlink'][3](editor); },
					HTMLArea._lc("Create a link", "ContextMenu"),
					config.btnList["createlink"][1] ]);

	for (var i = 0; i < elmenus.length; ++i)
		menu.push(elmenus[i]);

	// >MT: add remove tag vs remove element
	if (!/html|body/i.test(currentTarget.tagName)) {
	// <MT
		menu.push(null,
				// >MT: add remove tag vs remove element
				[ HTMLArea._lc({string: "Remove the $elem Element Content...", replace: {elem: "&lt;" + currentTarget.tagName + "&gt;"}}, "ContextMenu"),
				// <MT
					function() {
						// >MT: add remove tag vs remove element
						if (confirm(HTMLArea._lc("Please confirm that you want to remove this element and it's content:", "ContextMenu") + " " +
						// <MT
						currentTarget.tagName)) {
							var el = currentTarget;
							var p = el.parentNode;
							p.removeChild(el);
							if (HTMLArea.is_gecko) {
								if (p.tagName.toLowerCase() == "td" && !p.hasChildNodes())
									p.appendChild(editor._doc.createElement("br"));
								editor.forceRedraw();
								editor.focusEditor();
								editor.updateToolbar();
								if (table) {
									var save_collapse = table.style.borderCollapse;
									table.style.borderCollapse = "collapse";
									table.style.borderCollapse = "separate";
									table.style.borderCollapse = save_collapse;
								}
							}
						}
					},
		// >MT: add remove tag vs remove element
					HTMLArea._lc("Remove this node, and it's content from the document", "ContextMenu") ]);
		var data = HTMLArea.validTags[currentTarget.tagName.toLowerCase()];
		if (data.isTableElement) {
			menu.push(
				[ HTMLArea._lc("Convert Table to Text...", "ContextMenu"),
					function() {
						if (confirm(HTMLArea._lc("Please confirm that you want to convert the table into text:", "ContextMenu"))) {
							HTMLArea.convertTableToText(currentTarget);
						}
					},
					HTMLArea._lc("All the table elements will be converted into text elements", "ContextMenu") ]);
		} else {
			data = HTMLArea.validTags[currentTarget.parentNode.tagName.toLowerCase()];
			if (data && !data.noText)
				menu.push(
					[ HTMLArea._lc({string: "Remove the $elem Tag...", replace: {elem: "&lt;" + currentTarget.tagName + "&gt;"}}, "ContextMenu"),
						function() { 
							var p = currentTarget.parentNode;
							if (currentTarget.tagName.toLowerCase() == 'p') {
								if (currentTarget.previousSibling && !(currentTarget.previousSibling.nodeType == 1 && currentTarget.previousSibling.tagName.toLowerCase() == 'p'))
									currentTarget.parentNode.insertBefore(currentTarget.ownerDocument.createElement('br'), currentTarget);
								else if (currentTarget.nextSibling && !(currentTarget.nextSibling.nodeType == 1 && currentTarget.nextSibling.tagName.toLowerCase() == 'p'))
									currentTarget.parentNode.insertBefore(currentTarget.ownerDocument.createElement('br'), currentTarget.nextSibling);
							}
							HTMLArea.removeTag(currentTarget);
							HTMLArea.makeXHTML(p, editor.makeXHTMLContext(false));
						},
						HTMLArea._lc("Remove this tag, but not the content from the document", "ContextMenu") ]);
			// <MT
			menu.push(
				[ HTMLArea._lc("Insert paragraph before", "ContextMenu"),
					function() { insertPara(false); },
					HTMLArea._lc("Insert a paragraph before the current node", "ContextMenu") ],
				[ HTMLArea._lc("Insert paragraph after", "ContextMenu"),
					function() { insertPara(true); },
					HTMLArea._lc("Insert a paragraph after the current node", "ContextMenu") ]
				);
			// >MT: add remove tag vs remove element
		}
	}
	// <MT
	return menu;
};

// >MT: convert table to text
HTMLArea.convertTableToText = function(node) {
	var table = node;
	while (table && (table.nodeType != 1 || table.tagName.toLowerCase() != 'table'))
		table = table.parentNode;

	if (!table) return;

	function handleChild(child) {
		if (child.nodeType == 1) {
			var tag = HTMLArea.validTags[child.tagName.toLowerCase()];
			if (tag.isTableElement && tag.tag != 'table') {
				HTMLArea.xhtml_applyToChildren(child, handleChild);
				HTMLArea.removeTag(child);
			}
		}
	};

	HTMLArea.xhtml_applyToChildren(table, handleChild);
	HTMLArea.removeTag(table);
};
// <MT

ContextMenu.prototype.popupMenu = function(ev) {
	var self = this;
	if (this.currentMenu)
		this.currentMenu.parentNode.removeChild(this.currentMenu);
	function getPos(el) {
		var r = { x: el.offsetLeft, y: el.offsetTop };
		if (el.offsetParent) {
			var tmp = getPos(el.offsetParent);
			r.x += tmp.x;
			r.y += tmp.y;
		}
		return r;
	};
	function documentClick(ev) {
		ev || (ev = window.event);
		if (!self.currentMenu) {
			alert(HTMLArea._lc("How did you get here? (Please report!)", "ContextMenu"));
			return false;
		}
		var el = HTMLArea.is_ie ? ev.srcElement : ev.target;
		for (; el != null && el != self.currentMenu; el = el.parentNode);
		if (el == null)
			self.closeMenu();
		//HTMLArea._stopEvent(ev);
		//return false;
	};
	var keys = [];
	function keyPress(ev) {
		ev || (ev = window.event);
		HTMLArea._stopEvent(ev);
		if (ev.keyCode == 27) {
			self.closeMenu();
			return false;
		}
		var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
		for (var i = keys.length; --i >= 0;) {
			var k = keys[i];
			if (k[0].toLowerCase() == key)
				k[1].__msh.activate();
		}
	};
	self.closeMenu = function() {
		self.currentMenu.parentNode.removeChild(self.currentMenu);
		self.currentMenu = null;
		HTMLArea._removeEvent(document, "mousedown", documentClick);
		HTMLArea._removeEvent(self.editordoc, "mousedown", documentClick);
		if (keys.length > 0)
			HTMLArea._removeEvent(self.editordoc, "keypress", keyPress);
		if (HTMLArea.is_ie)
			self.iePopup.hide();
	};
	var target = HTMLArea.is_ie ? ev.srcElement : ev.target;
		 var ifpos = getPos(self.editor._htmlArea);//_iframe);
	var x = ev.clientX + ifpos.x;
	var y = ev.clientY + ifpos.y;

	var div;
	var doc;
	if (!HTMLArea.is_ie) {
		doc = document;
	} else {
		// IE stinks
		var popup = this.iePopup = window.createPopup();
		doc = popup.document;
		doc.open();
		doc.write("<html><head><style type='text/css'>@import url(" + _editor_url + "plugins/ContextMenu/menu.css); html, body { padding: 0px; margin: 0px; overflow: hidden; border: 0px; }</style></head><body unselectable='yes'></body></html>");
		doc.close();
	}
	div = doc.createElement("div");
	if (HTMLArea.is_ie)
		div.unselectable = "on";
	div.oncontextmenu = function() { return false; };
	div.className = "htmlarea-context-menu";
	if (!HTMLArea.is_ie)
		div.style.left = div.style.top = "0px";
	doc.body.appendChild(div);

	var table = doc.createElement("table");
	div.appendChild(table);
	table.cellSpacing = 0;
	table.cellPadding = 0;
	var parent = doc.createElement("tbody");
	table.appendChild(parent);

	var options = this.getContextMenu(target);
	for (var i = 0; i < options.length; ++i) {
		var option = options[i];
		var item = doc.createElement("tr");
		parent.appendChild(item);
		if (HTMLArea.is_ie)
			item.unselectable = "on";
		else item.onmousedown = function(ev) {
			HTMLArea._stopEvent(ev);
			return false;
		};
		if (!option) {
			item.className = "separator";
			var td = doc.createElement("td");
			td.className = "icon";
			var IE_IS_A_FUCKING_SHIT = '>';
			if (HTMLArea.is_ie) {
				td.unselectable = "on";
				IE_IS_A_FUCKING_SHIT = " unselectable='on' style='height=1px'>&nbsp;";
			}
			td.innerHTML = "<div" + IE_IS_A_FUCKING_SHIT + "</div>";
			var td1 = td.cloneNode(true);
			td1.className = "label";
			item.appendChild(td);
			item.appendChild(td1);
		} else {
			var label = option[0];
			item.className = "item";
			item.__msh = {
				item: item,
				label: label,
				action: option[1],
				tooltip: option[2] || null,
				icon: option[3] || null,
				activate: function() {
					self.closeMenu();
					self.editor.focusEditor();
					this.action();
				}
			};
			label = label.replace(/_([a-zA-Z0-9])/, "<u>$1</u>");
			if (label != option[0])
				keys.push([ RegExp.$1, item ]);
			label = label.replace(/__/, "_");
			var td1 = doc.createElement("td");
			if (HTMLArea.is_ie)
				td1.unselectable = "on";
			item.appendChild(td1);
			td1.className = "icon";
			if (item.__msh.icon)
			{
				var t = HTMLArea.makeBtnImg(item.__msh.icon, doc);
				td1.appendChild(t);
				// td1.innerHTML = "<img align='middle' src='" + item.__msh.icon + "' />";
			}
			var td2 = doc.createElement("td");
			if (HTMLArea.is_ie)
				td2.unselectable = "on";
			item.appendChild(td2);
			td2.className = "label";
			td2.innerHTML = label;
			item.onmouseover = function() {
				this.className += " hover";
				self.editor._statusBarTree.innerHTML = this.__msh.tooltip || '&nbsp;';
			};
			item.onmouseout = function() { this.className = "item"; };
			item.oncontextmenu = function(ev) {
				this.__msh.activate();
				if (!HTMLArea.is_ie)
					HTMLArea._stopEvent(ev);
				return false;
			};
			item.onmouseup = function(ev) {
				var timeStamp = (new Date()).getTime();
				if (timeStamp - self.timeStamp > 500)
					this.__msh.activate();
				if (!HTMLArea.is_ie)
					HTMLArea._stopEvent(ev);
				return false;
			};
			//if (typeof option[2] == "string")
			//item.title = option[2];
		}
	}

	if (!HTMLArea.is_ie) {
		/* FIXME: I think this is to stop the popup from running off the bottom of the screen?
		var dx = x + div.offsetWidth - window.innerWidth + 4;
		var dy = y + div.offsetHeight - window.innerHeight + 4;
		// alert('dy= (' + y + '+' + div.offsetHeight + '-' + window.innerHeight + ' + 4 ) = ' + dy);
		if (dx > 0) x -= dx;
		if (dy > 0) y -= dy;
		*/
		 // <MT deal with context menu appearing past the right side of the screen
		 // also, create visibility rules because offsetWidth for FF makes the context menu flash
		div.style.visibility = 'hidden';
		var w = div.offsetWidth;
		if ((x + w) > winX) {
			x = x - w;
		}
		 // >MT 
		div.style.left = x + "px";
		div.style.top = y + "px";
		 // <MT 
		div.style.visibility = 'visible';
		 // >MT 
	} else {
		// To get the size we need to display the popup with some width/height
		// then we can get the actual size of the div and redisplay the popup at the
		// correct dimensions.
		this.iePopup.show(ev.screenX, ev.screenY, 300,50);
		var w = div.offsetWidth;
		var h = div.offsetHeight;
		this.iePopup.show(ev.screenX, ev.screenY, w, h);
	}

	this.currentMenu = div;
	this.timeStamp = (new Date()).getTime();

	HTMLArea._addEvent(document, "mousedown", documentClick);
	HTMLArea._addEvent(this.editordoc, "mousedown", documentClick);
	if (keys.length > 0)
		HTMLArea._addEvent(this.editordoc, "keypress", keyPress);

	HTMLArea._stopEvent(ev);
	return false;
};

/*
 * MindTouch Deki - a commercial grade open source wiki
 *  derived from MediaWiki (www.mediawiki.org)
 * Copyright (C) 2006 MindTouch, Inc.
 * http://www.mindtouch.com/  oss@mindtouch.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

/**
  * MindTouch additions/changes
  *
  */

HTMLArea.loadStyle("mind-touch.css", "MindTouch");
  
function MindTouch(editor) {
  this.editor = editor;
  var dir = HTMLArea.getPluginDir('MindTouch');
  if (typeof HTMLArea.Config.MTregisterButton != 'function') {
    HTMLArea._loadback(dir + "/mt-new-xinha.js", function() { });
    HTMLArea._loadback(dir + "/xhtml-handling.js", function() { });
    HTMLArea._loadback(dir + "/mt-cng-xinha.js", function() { resetTableButtons(editor) });
  } else resetTableButtons(editor);
};

String.prototype.htmlTrim = function() {
    return this.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
};

MindTouch.prototype.onKeyPress = function(ev) {
  if (ev.keyCode == 13 && !ev.shiftKey)
    return this.handleEnter(ev);
};

MindTouch.checkLink = function(node) {
  window.open(MindTouch.getUrlFromName(node.href, true));
};

MindTouch.normalizeLink = function(node, editor) {
  function stripHost(host, href) {
    if (href.indexOf(host) != 0)
      return false;
    href = href.substr(host.length+1);
    if (href.indexOf('attachments/') == 0 && typeof getAttachmentUrl == 'function')
      getAttachmentUrl(href, node);
    MindTouch.setLink(editor, node, href);
    return true;
  };

  if (node.name != "") {
    node.id = node.name;
    node.name = "";
  }
  if (node.className == 'site')
    return;
  var isA = node.tagName.toLowerCase() == 'a';
  var attrName = isA ? 'href' : 'src';
  var attrVal = node[attrName];
  if (isA && node.innerHTML.replace(/(\s|&nbsp;|<br\s*\/?>)/gi).length == 0) {
      HTMLArea.removeTag(node);
      return;
  }
  var host = document.location.protocol + '//' + document.location.host;
  if (stripHost(editor.config.baseHref, attrVal)) {}
  else if (stripHost(host, attrVal)) {}
  else if (!MindTouch.isLinkExternal(attrVal) && attrVal.indexOf(MindTouch.mksInternalPrefix) != 0)
    MindTouch.setLink(editor, node, attrVal);
  else {
    if (HTMLArea.is_gecko && isA && node.className.indexOf('internal') == 0 && typeof node.title != 'undefined' && node.title.length > 3 && (node.title.substring(0,3) == '../' || node.title.substring(0,2) == './')) {
      // fix issue with gecko when when pasting href to relative internal links
      var title = node.title;
      var href = MindTouch.hrefDecode(node.href);
      if (href.indexOf(MindTouch.mksInternalPrefix) == 0) href = href.substr(MindTouch.mksInternalPrefix.length);
      if (title != href) {
        var stripTitle = title.replace(/^\.\.?\//,'');
        if (href == stripTitle)
          node.href = MindTouch.hrefEncode(MindTouch.mksInternalPrefix + title);
      }
    }
    MindTouch.setLink(editor, node);
  }
  node.onmouseover = null;
  node.onmouseout = null;
  node.onclick = null;

  var children = node.getElementsByTagName("font");
  for (var i = 0; i < children.length; ++i) {
    var font = children[i];
    if (font.size == "" && font.face == "" && font.color != "")
      HTMLArea.removeTag(font);
  }
  children = node.getElementsByTagName("span");
  for (var i = 0; i < children.length; ++i) {
    var span = children[i];
    var text = HTMLArea.getInnerText(span).trim();
    if (text.length == 0)
      HTMLArea.removeFromParent(span);
  }
  // remove urlexpansion span
  var nextEl = node.nextSibling;
  if (nextEl && nextEl.nodeType == 1 && /^span$/i.test(nextEl.tagName) && /^urlexpansion$/i.test(nextEl.className))
    HTMLArea.removeFromParent(nextEl);
  var p = node.parentNode;
  if (p.nodeType == 1 && /^(li|td|th|p|blockquote|dl|pre)$/i.test(p.tagName) && p.innerHTML != p.innerHTML.replace(/<\/a>\s*(<\/(li|td|th|p|blockquote|dl|pre)>|$)/ig, "</a>&nbsp;$1")) {
    nextEl = node.nextSibling;
    if (!nextEl || (!nextEl.nextSibling && nextEl.nodeType == 3 && nextEl.data.trim().length == 0)) {
      if (nextEl) HTMLArea.removeFromParent(nextEl);
      node.parentNode.appendChild(HTMLArea.xhtml_createNBSP(node));
    }
  }
};

MindTouch.getPageTitle = function(title) {
  if (typeof title == 'undefined')
    return _page_titleName;
  title = title.replace(/_/g,' ');
  title = title.replace(/\x2F\x2F/g,'%2F%2F');
  var arr = title.split("/");
  var t = "";
  if (arr.length > 1) {
    t = arr [arr.length-1];
  } else {
    t = title;
  }
  var tt = unescape(t);
  if (tt != t)
    t = tt.utf8ToString();
  return t;
};

MindTouch.getPagePath = function(title, alsoNamespace) {
  if (typeof title == 'undefined')
    return _page_titlePath;
  title = title.replace(/_/g,' ');
  title = title.replace(/\x2F\x2F/g,'%2F%2F');
  var arr = title.split("/");
  var path = "";
  if (arr.length > 1) {
    for (i = 0; i < arr.length-1; i++)
      path += arr[i] + "/";
    path = path.substr (0, path.length-1);
  } else {
    if (alsoNamespace) {
      arr = title.split(":");
      if (arr.length > 1) return arr[0];
    }
    path = "/";
  }
  var tt = unescape(path);
  if (tt != path)
    path = tt.utf8ToString();
  return tt;
};

MindTouch.isLinkExternal = function(href) {
  var re = /^(http|https|ftp|irc|gopher|news|mailto):/i;
  return href.match(re) != null;
};

MindTouch.mksInternalPrefix = "mks://localhost/";
MindTouch.hrefEncode_regEx = HTMLArea.compileRegex(/[ \xA0\x20\x23\x26\x2B\x2E\x3C\x3E\x40\x5B\x5C\x5D\x7B\x7D\x7C]/g);
MindTouch.hrefEncode = function(str) {
  function hex(d) { return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); };

  if(typeof str.replace == 'undefined') str = str.toString();
  var isInternal = str.indexOf(MindTouch.mksInternalPrefix) == 0;
  if (isInternal) {
    str = str.substr(MindTouch.mksInternalPrefix.length);
    str = str.utf8URL();
    str = str.replace(/\x2F\x2F/g, "%2f%2f");
    str = str.replace(/\x2E/g, "%2e");
    str = MindTouch.mksInternalPrefix + str;
  }
  return str;
};

MindTouch.hrefDecode = function(str) {
  if(typeof str.replace == 'undefined') str = str.toString();
  str = str.replace(/&nbsp;/ig, " ");
  if (str.indexOf(MindTouch.mksInternalPrefix) == 0) {
    str = unescape(str);
    str = str.utf8ToString();
    str = str.replace(/_/ig, " ");
  }
  return str;
};

MindTouch.extractName = function(href) {
  var isE = MindTouch.isLinkExternal(href);
  if (!isE) {
    if (href.indexOf(MindTouch.mksInternalPrefix) == 0)
      href = href.substr(MindTouch.mksInternalPrefix.length);
    while (href.charAt(0) == '/') href = href.substr(1);
    if (href.indexOf('index.php?title=') == 0)
      href = href.substr('index.php?title='.length);
    while (href.charAt(0) == '/') href = href.substr(1);
    if (href.toLowerCase().indexOf('file:///') == 0)
      href = 'File:' + href.substr('file:///'.length);
    href = href.replace(/&[a-z]+=.*$/i, '');
    href = href.replace(/_/g, ' ');
    var n = unescape(href);
    if (n != href)
      href = n.utf8ToString();
  }
  return href;
};

MindTouch.getUrlFromName = function(href, addContext) {
  var isE = MindTouch.isLinkExternal(href);
  if (!isE) {
    href = MindTouch.extractName(href).replace(/ /g,'_');
    if (href.indexOf('&') > 0 || href.indexOf('+') > 0 || href.indexOf('?') > 0 || 
      href.indexOf('\\') > 0 || href.indexOf('.') == 0 || href.indexOf('//') > 0 || href.indexOf('%') > 0
    )
      href = 'index.php?title=' + href.utf8URL();
    else
      href = href.utf8URL();
    href = '/' + href;
    if (addContext) {
      if (typeof contextTopicID != 'undefined' && contextTopicID)
        href += "&contextid=" + contextTopicID;
      else if (typeof _page_ID != 'undefined' && _page_ID)
        href += "&contextid=" + _page_ID;
    }
  }
  return href;
};

MindTouch.setLink = function(editor, node, href, text) {
  if (node.className == 'site')
    return;

  var isA = node.tagName.toLowerCase() == 'a';
  var attrName = isA ? 'href' : 'src';
  var oldHRef = MindTouch.hrefDecode(node[attrName]);

  // is not endcoded
  if (typeof href == 'undefined' && MindTouch.hrefEncode(oldHRef) != node[attrName])
    href = oldHRef;

  if (oldHRef.indexOf(MindTouch.mksInternalPrefix) == 0)
    oldHRef = oldHRef.substr(MindTouch.mksInternalPrefix.length);
  var oldText = HTMLArea.getInnerText(node);

  if (typeof href != 'undefined') {
    var isE = MindTouch.isLinkExternal(href);
    if (!isE) {
      if (isA)
        href = MindTouch.mksInternalPrefix + MindTouch.extractName(href).replace(/ /g,'_');
      if (!isA) {
        href = MindTouch.getUrlFromName(href,href.indexOf('./') > 0 || href.indexOf('../') > 0);
        node.alt = unescape(MindTouch.extractName(href).substr('file:'.length)).utf8ToString();
        href += '&action=thumb';
        if (parseInt(node.style.width) > 160 || parseInt(node.style.height) > 160)
            href += "&size=full";
      }
    } else {
        node.alt = unescape(href);
    }
    var html = node.innerHTML;
    node[attrName] = isA ? MindTouch.hrefEncode(href) : href;
    if (node.innerHTML != html) node.innerHTML = html;
  }

  if (isA) {
    node.title = MindTouch.hrefDecode(node.href);
    var isImg = node.firstChild && node.firstChild.nodeType == 1 && node.firstChild.tagName.toLowerCase() == 'img';
    if (node.title.indexOf(MindTouch.mksInternalPrefix) == 0) {
      node.title = node.title.substr(MindTouch.mksInternalPrefix.length);
      if (node.className.indexOf('internal') != 0 && !isImg)
        node.className = 'internal';
    }
    else if (node.href != "")
      if (!isImg) node.className = 'external';
    else
      node.className = '';

    if (typeof text == 'undefined' && oldHRef == oldText && node.href != oldHRef)
      text = node.title;

    if (typeof text != 'undefined' && text != "" && HTMLArea.getInnerText(node) != text 
      && !(node.firstChild && node.firstChild.nodeType == 1 && node.firstChild.tagName.toLowerCase() == 'img')
    )
      if (HTMLArea.is_ie)
        node.innerText = text;
      else
        node.textContent = text;
  } else if (editor) {
    var ialign = node.getAttribute('ialign');
    var divWrap = node.parentNode;
    if (divWrap.tagName.toLowerCase() == 'a')
       divWrap = divWrap.parentNode;
    var isDivWrap = divWrap.tagName.toLowerCase() == 'div';
    if (ialign != null) {
      if (ialign.substring(1) == 'nowrap') {
        var check = false;
        if (!isDivWrap) {
          divWrap = node.ownerDocument.createElement('div');
          var toWrap = node;
          if (node.parentNode.tagName.toLowerCase() == 'a')
              toWrap = node.parentNode;
          HTMLArea.xhtml_surroundNode(toWrap, divWrap);
          check = true;
        }
        if (divWrap.className != node.getAttribute('ialign')) {
          divWrap.className = node.getAttribute('ialign');
          check  = true;
        }
        node.removeAttribute('className');
        if (check && editor) HTMLArea.makeXHTML(divWrap, editor.makeXHTMLContext(false));
      } else {
        if (isDivWrap && divWrap.firstChild == divWrap.lastChild && divWrap.firstChild == node)
          HTMLArea.removeTag(divWrap);
        node.className = node.getAttribute('ialign');
      }
      /*
       * MT: royk
       * I'm going to need spans/divs later for polaroid effect
      */
    } else {
      if (isDivWrap && divWrap.firstChild == divWrap.lastChild && divWrap.firstChild == node)
        HTMLArea.removeTag(divWrap);
    }
  }
};

MindTouch.insertHTML = function(range, html) {
  if (HTMLArea.is_ie)
    range.pasteHTML(html);
  else {
    range.deleteContents();
    var node = range.startContainer;
    var pos = range.startOffset;
    var div = node.ownerDocument.createElement('div');
    div.innerHTML = html;
    switch (node.nodeType) {
    case 3: // Node.TEXT_NODE
      // we have to split it at the caret position.
      node = node.splitText(pos);
      node.parentNode.insertBefore(div.firstChild, node);
      break;
    case 1: // Node.ELEMENT_NODE
      node.insertBefore(div.firstChild, node.childNodes[pos]);
      break;
    }
  }
};

MindTouch.selectNode = function(range, node) {
  if (HTMLArea.is_ie)
    range.moveToElementText(node);
  else
    range.selectNode(node);
};

MindTouch.getTextNodeAndOffset = function(val) {
  var root = val.node;
  var cur = root;
  var offset = val.offset;
  while (true) {
    if (cur.nodeType == 3) {
      var curlen = cur.nodeValue.length;
      if (curlen >= offset) {
        val.node = cur;
        val.offset = offset;
        return;
      }
      offset -= curlen;
    }
    if (cur.firstChild)
      cur = cur.firstChild;
    else if (cur.nextSibling)
      cur = cur.nextSibling;
    else {
      while (!cur.parentNode.nextSibling)
        cur = cur.parentNode;
      cur = cur.parentNode.nextSibling;
    }
  }
};

MindTouch.setStart = function(range, node, offset) {
  if (HTMLArea.is_ie) {
    var r = range.duplicate();
    r.moveToElementText(node);
    r.collapse();
    r.move('character', offset);
    range.setEndPoint('EndToStart', r);
    range.setEndPoint('StartToStart', r);
  } else {
    var val = { node: node, offset: offset };
    MindTouch.getTextNodeAndOffset(val);
    if (val.offset) {
      var l = val.node.nodeValue.length - val.offset;
      range.selectNode(val.node);
      range.collapse(false);
      if (l > 0)
        range.setStart(val.node, val.offset);
    } else {
      range.selectNode(val.node);
      range.collapse(true);
    }
  }
};

MindTouch.setEnd = function(range, node, offset) {
  if (HTMLArea.is_ie) {
    var r = range.duplicate();
    r.moveToElementText(node);
    r.collapse();
    r.moveEnd('character', offset);
    range.setEndPoint('EndToEnd', r);
  } else {
    var val = { node: node, offset: offset };
    MindTouch.getTextNodeAndOffset(val);
    var start = range.startContainer;
    var sOffset = range.startOffset;
    if (val.offset) {
      var l = val.node.nodeValue.length - val.offset;
      if (l > 0)
        range.setEnd(val.node, val.offset);
      else {
        range.selectNode(val.node);
        range.collapse(false);
        range.setStart(start, sOffset);
      }
    } else {
      range.selectNode(val.node);
      range.collapse(true);
      range.setStart(start, sOffset);
    }
  }
};

MindTouch.cloneRange = function(range) {
  return HTMLArea.is_ie ? range.duplicate() : range.cloneRange();
};

MindTouch.setEndToEnd = function(r, r2) {
  if (HTMLArea.is_ie)
    r.setEndPoint("EndToEnd", r2);
  else
    r.setEnd(r2.endContainer, r2.endOffset);
};

MindTouch.setEndToStart = function(r, r2) {
  if (HTMLArea.is_ie)
    r.setEndPoint("EndToStart", r2);
  else
    r.setEnd(r2.startContainer, r2.startOffset);
};

MindTouch.commonAncestorContainer = function(range) {
  if (!range) return range;
  if (HTMLArea.is_ie) {
    if (typeof range.length != 'undefined') return range.item(0);
    var ret = range.parentElement();
    if (range.text.length > ret.innerText.length)
      ret = ret.parentNode;
    return ret;
  } else
    return range.commonAncestorContainer;
};

MindTouch.prototype.handleEnter = function(ev) {
  var editor = this.editor;
  var sel = editor._getSelection();
  var block = editor._getFirstAncestor(sel, ['p','h1','h2','h3','h4','h5','h6','li','dd','dl','pre','blockquote','tr','td','body']);
  if (!block || block.tagName.toLowerCase() == 'body') {
    if (HTMLArea.is_gecko && sel.focusNode.nodeType == 3 && sel.focusNode.parentNode.tagName.toLowerCase() == 'body') {
      editor.execCommand('formatblock', false, 'p');
      block = editor._getFirstAncestor(sel, ['p','h1','h2','h3','h4','h5','h6','li','dd','dl','pre','blockquote','tr','td','body']);
    } else
      return;
  }
  var innerTxt = HTMLArea.getInnerText(block);
  var userLink = '<a class="internal" title="User:'+_userName+'" href="mks://localhost/User:'+_userName+'">'+_userName+'</a>';
  innerTxt = editor.findAndReplace('~~~~',userLink+' '+_today,block,innerTxt);
  innerTxt = editor.findAndReplace('~~~',userLink,block,innerTxt);
  switch (block.tagName.toLowerCase()) {
  case 'p':
    handleP();
    break;
  case 'li':
    handleLi();
    return;
  default:
    return;
  }

  function handleLi() {
    if (HTMLArea.is_ie)
      return;
    var r = editor._createRange();
    if (r.startContainer != r.endContainer) return;
    var node = r.startContainer;
    if (node.nodeType != 3 || node.data.substr(r.startOffset).htmlTrim().length != 0)
      return;
    var n = node.nextSibling;
    if (n && n.nodeType == 1 && n.tagName.toLowerCase() == 'ul') {
      node.data = node.data.replace(/(\s|&nbsp;)+$/,'');
      r.setEnd(node, node.data.length);
      r.setStart(node, node.data.length);
      var sel = editor._getSelection();
      sel.removeAllRanges();
      sel.addRange(r);
      node.parentNode.insertBefore(node.ownerDocument.createElement('br'), n);
    }
  };

  function handleP() {
    /* FF is broken, removing
    var len = 2, pre = innerTxt.substring(0, len);

    function findNonSpace() {
      while (true) {
        var ch = innerTxt.substring(len, len+1);
        if (ch != ' ') break;
        ++len;
      }
    };
    
    if (pre == '- ' || pre == '* ' || pre == '~ ' || pre == '+ ' || pre == '= ') {
      findNonSpace();
      editor.insertList(block, len, 'insertunorderedlist');
      return;
    }
    len = 3;
    pre = innerTxt.substring(0, len);
    if (pre == '1. ' || pre == '1) ') {
      findNonSpace();
      editor.insertList(block, len, 'insertorderedlist');
      return;
    }
    len = 4;
    pre = innerTxt.substring(0, len);
    if (pre == '1). ') {
      findNonSpace();
      editor.insertList(block, len, 'insertorderedlist');
      return;
    }
    */
  };
};

resetTableButtons = function(editor) {
  var cfg = editor.config;
  var bl = TableOperations.btnList;
  var self = editor.plugins.TableOperations;
  var toolbar = ["linebreak"];
  for (var i = 0; i < bl.length; ++i) {
    var btn = bl[i];
    if (btn) {
      var id = "TO-" + btn[0];
      HTMLArea.Config.MTregisterButton(cfg, id, HTMLArea._lc(btn[2], "TableOperations"), editor.imgURL(btn[0] + ".gif", "TableOperations"), false,
        function(editor, id) {
          // dispatch button press event
          self.buttonPress(editor, id);
        }, btn[1], false);
      toolbar.push(id);
    }
  }
};

MindTouch._pluginInfo = {
	name          : "MindTouch Extensions",
	version       : "1.0",
	developer     : "Urs C Muff",
	developer_url : "http://www.mindtouch.com/",
	license       : "htmlArea"
};

/*
 * MindTouch Deki - a commercial grade open source wiki
 *  derived from MediaWiki (www.mediawiki.org)
 * Copyright (C) 2006 MindTouch, Inc.
 * http://www.mindtouch.com/  oss@mindtouch.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

HTMLArea.RE_noHeaderCmd = /insertorderedlist|insertunorderedlist|indent|outdent|inserttable|insertimage|fontname|fontsize|bold|italic|underline|strikethrough|forecolor|hilitecolor|subscript|superscript|mksLink|mksInsertImage|mksFont|mksSize|mksAlign|mksFontColor|mksBackColor|mostemplates|moswidgets/i;

HTMLArea.prototype._OnResize = function() {
  this._htmlArea.style.height = getEditorHeight(this) + "px";
};

HTMLArea.prototype._OnMode = function(mode) {
  var searchBox = document.getElementById('searchBox');
  switch (mode) {
    case "textmode":
      //alert('switch to textmode');
      break;
    case "wysiwyg":
      //alert('switch to wysiwyg');
      break;
    case "maximize":
      if (searchBox) searchBox.style.display = 'none';
      //alert('switch to maximize');
      break;
    case "restore":
      if (searchBox) searchBox.style.display = 'inline';
      //alert('switch to restore');
      this._OnResize();
      break;
  }
};

HTMLArea.prototype.doOnAllElements = function(sel, tagNames, func) {
  var r = this._createRange(sel);
  var r2 = MindTouch.cloneRange(r);
  HTMLArea.is_ie ? r.collapse(true) : r.collapse(false);
  var start = MindTouch.commonAncestorContainer(r);
  HTMLArea.is_ie ? r2.collapse(false) : r2.collapse(true);
  var end = MindTouch.commonAncestorContainer(r2);
  if (start == end) {
    var node = this._getFirstAncestor(sel, tagNames);
    if (node) func(node);
  } else
    this.doUntilEnd(start, end, tagNames, func);
};

HTMLArea.prototype.doUntilEnd = function(cur, end, tagNames, func) {
  if(typeof tagNames == 'string')
    tagNames = [tagNames];
  while (true) {
    if (cur.nodeType == 1 && tagNames.contains(cur.tagName.toLowerCase())) 
      func(cur);

    if (cur == end)
      return;

    if (cur.firstChild && cur.nodeType == 1)
      cur = cur.firstChild;
    else if (cur.nextSibling)
      cur = cur.nextSibling;
    else {
      while (cur.parentNode && !cur.parentNode.nextSibling)
        cur = cur.parentNode;
      if (!cur.parentNode)
        return;
      cur = cur.parentNode.nextSibling;
    }
  }
};

// disallow formatting in certain cases
HTMLArea.prototype.preExecCommand = function(p) {
  var ancestors = this.getAllAncestors();
  if (p.cmdID == "formatblock") {
    if (this._isInAny(ancestors,["ul","ol","table"])) {
      // alert('WARNING: This actions is not allowed on lists or tables');
      if (/p/.test(p.param)) {
        this.doOnAllElements(this._getSelection(), ['span'], function(node) {
          if (/header_/.test(node.className))
            HTMLArea.removeTag(node);
        });
        return false;
      }
    }
    if (this._isInAny(ancestors,["blockquote"]))
      p.cmdID = "outdent";
    else if (/blockquote/i.test(p.param))
      p.cmdID = "indent";
  }
  if (typeof HTMLArea.RE_noHeaderCmd != "undefined" && HTMLArea.RE_noHeaderCmd.test(p.cmdID) 
    && this._isInAny(ancestors,["h1","h2","h3","h4","h5","h6"])) {
    // alert('WARNING: This actions is not allowed on headings');
    return false;
  }
  if (/^justify.*$/.test(p.cmdID) && this._doc.queryCommandState(p.cmdID)) {
    var changed = false;
    this.doOnAllElements(this._getSelection(), ['p','h1','h2','h3','h4','h5','h6','div','li','td','tr'], function(node) {
      if (/^div$/i.test(node.tagName))
        HTMLArea.removeTag(node);
      else if (/^(h[1-6]|p)$/i.test(node.tagName))
        node.align = "";
      changed = true;
    });
    if (changed) {
      this.updateToolbar();
      return false;
    }
  }
  if (p.cmdID == "fontname") {
    var params = p.param.split('|');
    var face = params[0];
    this.focusEditor();
    this._doc.execCommand("fontname", p.UI, face);
    var parent = this.getParentElement().parentNode;
    var editor = this;
    HTMLArea.applyToAll(parent, function(node) {
      if (node.nodeType != 1) return;
      if (node.style.fontFamily)
        HTMLArea.xhtml_checkFont(editor, node, node.style.fontFamily);
      else if (node.face)
        HTMLArea.xhtml_checkFont(editor, node, node.face);
    });
    this.updateToolbar();
    return false;
  }
  return true;
};

HTMLArea.prototype.getSelectionBoundaries = function(b) {
  var r = this._createRange();
  if (HTMLArea.is_ie) {
    var rStart = r.duplicate();
    var rEnd = r.duplicate();
    rStart.collapse();
    rEnd.collapse(false);
    b.Start = rStart.parentElement();
    b.End = rEnd.parentElement();
  } else {
    b.Start = r.startContainer;
    b.End = r.endContainer;
  }
};

HTMLArea.prototype.postExecCommand = function(cmdID, UI, param) {
  if (cmdID == 'formatblock' && /pre/.test(param)) {
    var b = {};
    this.getSelectionBoundaries(b);
    while (b.Start && (b.Start.nodeType != 1 || b.Start.tagName.toLowerCase() != 'pre'))
      b.Start = b.Start.parentNode;
    while (b.End && (b.End.nodeType != 1 || b.End.tagName.toLowerCase() != 'pre'))
      b.End = b.End.parentNode;
    if (b.Start && b.End && b.Start != b.End) {
      var n = b.Start.nextSibling;
      var p = n.parentNode;
      var ins = n.nextSibling;
      while (true) {
        var nn = n.nextSibling;
        var end = n == b.End;
        if (n.nodeType == 1) {
          b.Start.appendChild(b.Start.ownerDocument.createElement('br'));
          b.Start.appendChild(n);
          HTMLArea.removeTag(n);
        } else
          n.parentNode.removeChild(n);
        n = nn;
        if (end)
          break;
      }
    }
  }

  var root = MindTouch.commonAncestorContainer(this._createRange());
  if (root && root.nodeType == 3)
    root = root.parentNode;
  if (root && root.nodeType == 1) {
    var check = false;
    var sel = this._getSelection();
    var hTag = this._getFirstAncestor(sel, ['h1','h2','h3','h4','h5','h6']);
    if (hTag && HTMLArea.getInnerText(hTag).htmlTrim().length != 0) {
      root = hTag;
      check = true;
    } else {
      var listTag = this._getFirstAncestor(sel, ['ol','ul']);
      switch (cmdID) {
      case "indent":
      case "insertunorderedlist":
      case "insertorderedlist":
        if (listTag && HTMLArea.getInnerText(listTag).htmlTrim().length != 0) {
          root = listTag;
          if (root.parentNode.tagName.toLowerCase() != 'body')
            root = root.parentNode;
          if (root.parentNode.tagName.toLowerCase() != 'body')
            root = root.parentNode;
          check = true;
        }
        break;
      }
    }
    if (!check && cmdID == "formatblock" && this._isInAny(this.getAllAncestors(),["ul","ol","table"]))
      check = true;
    if (check)
      HTMLArea.makeXHTML(root, this.makeXHTMLContext(false));
  }
  return true;
};

HTMLArea.applyToAll = function(node, f) {
  f(node);
  var child = node.firstChild;
  while (child != null) {
    var n = child.nextSibling;
    HTMLArea.applyToAll(child, f);
    child = n;
  }
};

HTMLArea.prototype._onColorChange = function(cmdID, color) {
  var editor = this;
  if (color == '-None-') {
    var tags = editor.getSelectionTagsF("font",cmdID == "forecolor" ? function(el) { return el.color != ""; } : function(el) { return el.style.backgroundColor != ""; });
    if (typeof tags.tag != 'undefined') {
      if (editor.getHTMLText(HTMLArea.getOuterHTML(tags.tag)) == editor.getHTMLText(editor.getSelectedHTML()))
        HTMLArea.removeTag(tags.tag);
      else
        editor.splitOutRange(tags.tag, editor._createRange(editor._getSelection()));
    } else {
      var p = editor._getFirstAncestorF(editor._getSelection(),function(el) { return el.style.backgroundColor != ""; });
      if (p && p.style.backgroundColor != "")
        p.style.backgroundColor = "";
      else
        editor._doc.execCommand(cmdID, false, HTMLArea.is_ie ? "none" : '-');
    }
  } else
    editor._doc.execCommand(cmdID, false, color);
  if (HTMLArea.is_gecko && cmdID == "hilitecolor") {
    HTMLArea.replaceTagsF(editor.getParentElement().parentNode,function (el) {
      if (!/^span$/i.test(el.tagName) || el.style.backgroundColor == "")
        return null;
      var ret = editor._doc.createElement("font");
      ret.style.backgroundColor = el.style.backgroundColor;
      return HTMLArea.cloneNode(el,ret);
    });
  }
};

HTMLArea.debugTestPage = function(editor, url) {
  var a = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : null;
  a.open("GET", url, true);
  a.onreadystatechange = function() {
    if (a.readyState != 4) 
      return;
    var html = a.responseText;
    if (html.match(/((.|\n)*?)<body>((.|\n)*?)<\/body>((.|\n)*?)/ig)) {
      html = RegExp.$3.trim();
    }
    editor.setHTML(editor.inwardHtml(html));
    var context = editor.makeXHTMLContext(false);
    HTMLArea.makeXHTML(null, context);
    _starttime = new Date().getTime();
    var html = editor.getXHTML();
    _endtime = new Date().getTime();
    var getHTML_timeDiff = _endtime - _starttime;
    alert("make XHTML: " + context._timeDiff + " ms\nget HTML: " + getHTML_timeDiff + "ms");
  }
  a.send(null);
  delete a;
};

HTMLArea.prototype.editorLoaded = function() {
  var editor = this;
  if (this._doc.body == null) {
    setTimeout(function() { editor.editorLoaded(); }, 50);
    return;
  }

  if (HTMLArea.is_ie) {
    this._doc.body.onpaste = function (event) {
      event = editor._iframe.contentWindow.event;
      var sel = editor._getSelection();
      var r = editor._createRange(sel);
      var r2 = r.duplicate();
      var r3 = r.duplicate();
      r2.collapse();
      r2.move('character', -1);
      r3.collapse(false);
      r3.move('character', 1);
      try {
        r2.pasteHTML("<span class='pasteTag start'>|</span>");
        r3.pasteHTML("<span class='pasteTag end'>|</span>");
      } catch (e) {
//        debugger;
      }
      
      if (event.shiftKey) {
        var clip = window.clipboardData;
        var text = clip.getData('Text');
        clip.clearData();
        clip.setData('Text',text);
      }
    };
  }

  /* >MT: DEBUG 
  HTMLArea.debugTestPage(editor, "/editor/test-cases/test-x.htm");
  //HTMLArea.debugTestPage(editor, "/editor/test-cases/test-1.html");
  //HTMLArea.debugTestPage(editor, "/editor/test-cases/test-2 (real world).htm");
  //HTMLArea.debugTestPage(editor, "/editor/test-cases/test-3 (long 477k).html");
  //HTMLArea.debugTestPage(editor, "/editor/test-cases/test-all-styles.htm");
  //HTMLArea.debugTestPage(editor, "/editor/test-cases/test-4.htm");

  HTMLArea.makeXHTML(null, editor.makeXHTMLContext(false));
  /* <MT */
  if (window.Widget)
    Widget.initializeEditor(this._doc);

  if (this._readOnly) {
      try {
        if (HTMLArea.is_gecko) this._doc.designMode = 'off';
        else this._doc.body.contentEditable = false;
      } catch (e) {}
  }
  this._doc.body.id = "topic";
  if (typeof doCheckChanges == 'function')
    doCheckChanges(this);
  if (typeof this._OnResize == 'function')
    this._OnResize();
  setTimeout(function() {
    editor.focusEditor();
    editor.updateToolbar();
    var h1 = editor._doc.body.firstChild;
    while (h1 && h1.nodeType == 3) h1 = h1.nextSibling;
    if (h1 && h1.tagName.toLowerCase() == 'h1') {
      var el = h1.nextSibling;
      while (el && el.nodeType == 3) el = el.nextSibling;
      if (!el) return;
      var sel = editor._getSelection();
      var isCtrl = false;
      if (el.nodeType == 1) {
        var tag = el.tagName.toLowerCase();
        isCtrl = (tag == 'table' || tag != 'img' || tag == 'div');
      }
      if (!isCtrl)
        editor.selectNodeContents(el);
      setTimeout(function() {
        editor.focusEditor();
        if (!isCtrl)
          editor.selectNodeContents(el);
        var range = editor._createRange(sel);
        if (HTMLArea.is_ie) {
          if (sel.type != "Control" && range) {
            range.collapse();
            range.select();
          }
        } else {
          sel.removeAllRanges();
          range.collapse(true);
          sel.addRange(range);
        }
        editor.updateToolbar();
      }, 100);
    }
  }, 600);
};

HTMLArea.prototype.getPath = function(el) {
  if (typeof el == "undefined" || el == null)
    return "";
  var tagName = el.tagName;
  if (tagName == "BODY")
    return "";
  var index = 0;
  var s = el.previousSibling;
  while (s != null) {
    if (s.tagName == tagName)
      ++index;
    s = s.previousSibling;
  }
  var parentPath = this.getPath(el.parentNode);
  var path = parentPath + "/" + tagName;
  if (index > 0)
    path += "[" + index + "]"; 
  return path;
};

HTMLArea.prototype.selectPath = function(path,el) {
  if (path.charAt(0) == "/") {
    el = this._doc.body;
    path = path.substr(1);
  }
  var pos1 = path.indexOf('[');
  var pos2 = path.indexOf('/');
  var remain = "";
  var split;
  if (pos1 > 0 && pos2 > 0)
    split = pos1 < pos2 ? pos1 : pos2;
  else if (pos1 > 0)
    split = pos1;
  else if (pos2 > 0)
    split = pos2;
  else
    split = -1;
  var index = 0;
  if (split > 0) {
    remain = path.substr(split+1);
    path = path.substr(0,split);
    if (split == pos1) {
      pos2 = remain.indexOf(']');
      index = parseInt(remain.substr(0,pos2));
      remain = remain.substr(pos2+1);
      if (remain.charAt(0) == "/")
        remain = remain.substr(1);
    }
  }
  var c = el.firstChild;
  var i = 0;
  while (c != null) {
    if (c.tagName == path) {
      if (index == i)
        break;
      ++i;
    }
    c = c.nextSibling;
  }
  if (c == null)
    return null;
  if (remain != "")
    return this.selectPath(remain,c);
  return c;
};

HTMLArea.prototype._storeUndoCursor = function(state) {
  var sel = this._getSelection();
  var range = this._createRange(sel);
  var el = this.getParentElement(sel,range);
  var path = this.getPath(el);
  if (path == "")
    path = "/";
  var textEdit = el.parentTextEdit == null ? el : el.parentTextEdit;
  state.scrollTop = textEdit.scrollTop;
  state.scrollLeft = textEdit.scrollLeft;
  state.path = path;
  state.range = this.getRange();
  // alert("store #" + this._undoQueue.indexOf(state) + ", path = " + path + ", scrollTop = " + state.scrollTop);
};

HTMLArea.prototype.restoreState = function(state) {
  if (!state)
    return false;
  var html = state.html;
  if (!html)
    return false;
  this.setHTML(html);

  var curHtml = this.getInnerHTML();
  state.html = curHtml;

  var path = state.curNode;
  // alert("restore #" + this._undoQueue.indexOf(state) + ", path = " + path + ", scrollTop = " + state.scrollTop);
  if (!this.setRange(state.range) && HTMLArea.is_ie) {
    var el = this.selectPath(path,null);
    if (el != null)  {
      this.selectNodeContents(el);
      var range = this._createRange(this._getSelection());
      range.collapse();
      range.select();
    }
  }
  if (HTMLArea.is_ie) {
    this._doc.body.scrollTop = state.scrollTop;
    this._doc.body.scrollLeft = state.scrollLeft;
  }
  this.updateToolbar();
  return true;
};

HTMLArea.prototype._isInAny = function(ancestors,list) {
  if (ancestors == null)
    return false;
  for (var i = ancestors.length; --i >= 0;) {
    // A wonderful error condition that should be impossible
    if (ancestors[i]) {
      var tagName = ancestors[i].tagName.toLowerCase();
      for (var j = 0; j < list.length; ++j)
        if (tagName == list[j])
          return true;
    }
  }
  return false;
};

HTMLArea.prototype.cleanBRs = function() {
  var tags = HTMLArea.getElementsByTagNameF(this._doc,'br',function(el) {
    if (/^(h[1-6]|pre)$/i.test(el.parentNode.tagName))
      return true;
    return !el.nextSibling; 
  });
  for (var i = 0; i < tags.length; ++i) {
    var el = tags[i];
    var p = el.parentNode;
    if (/^pre$/i.test(p.tagName))
      p.insertBefore(this._doc.createTextNode("\r\n"),el);
    else if (/^h[1-6]$/i.test(p.tagName))
      p.insertBefore(this._doc.createTextNode(" "),el);
    HTMLArea.removeFromParent(el);
  }
};

HTMLArea.prototype.getSelectionTagsF = function(tag,fn) {
  var el = this._getFirstAncestorF(this._getSelection(),function(el) {
    return el.nodeType == 1 && (el.tagName.toLowerCase() == tag && fn(el) || /^(p|h[1-6]|blockquote|body)$/i.test(el.tagName));
  });
  if (el == null)
    el = this._doc.body;
  if (el.tagName.toLowerCase() == tag)
    return {tag: el};
  var tmp = this._doc.createElement("div");
  tmp.innerHTML = this.getSelectedHTML();
  return {root: el, tags: HTMLArea.getElementsByTagNameF(tmp,tag,fn)};
};


HTMLArea.prototype.getSelectedText = function() {
  var range = this._createRange(this._getSelection());
  if (HTMLArea.is_ie)
    return range.text;
  else
    return this.getHTMLText(HTMLArea.getHTML(range.cloneContents(), false, this));
};

HTMLArea.prototype.removeInRangeF = function(fn) {
  function parseTree(root) {
    if (fn(root))
      return false;
    else {
      for (var i = root.firstChild; i; i = next) {
        var next = i.nextSibling;
        i.nodeType == 1 && parseTree(i);
      }
    }
    return true;
  };

  var html = this.getSelectedHTML();
  var div = this._doc.createElement("div");
  div.innerHTML = html;
  parseTree(div);
  if (div.innerHTML == html)
    return;
  html = div.innerHTML;
  var r = this.getRange();
  this.insertHTML(html);
  this.setRange(r);
};

HTMLArea.prototype.getHTMLText = function(html) {
  var div = this._doc.createElement("div");
  div.innerHTML = html;
  return HTMLArea.getInnerText(div);
};

HTMLArea.prototype.isParagraphStart = function() {
  function findAncestor(node,testFirst) {
    while(node) {
      if (testFirst && node.previousSibling != null && (node.previousSibling.nodeType != 3 || node.previousSibling.nodeValue.trim() != ''))
        return null;
      if (/^(p|td|h[1-6]|li|dd|)$/i.test(node.tagName))
        return node;
      if (/^(body|table)$/i.test(node.tagName))
        break;
      node = node.parentNode;
    }
  };

  var sel = this._getSelection();
  var range = this._createRange(sel);

  if (HTMLArea.is_ie) {
    var r2 = range.duplicate();
    r2.moveStart('character',-1);
    var a = findAncestor(range.parentElement(),false);
    var a2 = findAncestor(r2.parentElement(),false);
    return a != a2;
  } else {
    if (range.startOffset != 0)
      return false;
    return findAncestor(range.startContainer, true) != null;
  }
};

HTMLArea.prototype.handleTab = function(ev) {
  var def = true;
  var sel = this._getSelection();
  var range = this._createRange(sel);
  var td = this._getFirstAncestor(sel, ['td']);

  if (td != null) {
    if (HTMLArea.is_gecko || ev.keyCode != 9)
      return false;
    var nextTd = ev.shiftKey ? td.previousSibling : td.nextSibling;
    if (nextTd == null) {
      nextTd = ev.shiftKey ? td.parentNode.previousSibling : td.parentNode.nextSibling;
      if (ev.shiftKey && nextTd != null)
        nextTd = nextTd.lastChild;
    }
    if (nextTd == null) {
      var otr = td.parentNode.cloneNode(true);
      var tds = otr.getElementsByTagName("td");
      for (var i = tds.length; --i >= 0;) {
	      var t = tds[i];
	      t.rowSpan = 1;
	      t.innerHTML = "";
      }
      td.parentNode.parentNode.insertBefore(otr, ev.shiftKey ? td.parentNode : null);
      nextTd = ev.shiftKey ? td.parentNode.previousSibling.lastChild : td.parentNode.nextSibling;
    }
    if (nextTd != null) {
      range.moveToElementText(nextTd);
      range.collapse();
      range.select();
    }
    def = false;
  } else if (this.isParagraphStart()) {
    var insert = ev.keyCode == 9 && !ev.shiftKey; 
    var a = this._getFirstAncestor(sel, ['p','h1','h2','h3','h4','h5','h6','ul','ol','td']);
    if (a && /^ul|ol$/i.test(a.tagName)) {
      if (ev.keyCode != 9)
        return false;
      def = false;
      cmd = ev.shiftKey ? "outdent" : "indent";
      this.execCommand(cmd, false, null);
    } else if (a && /^p|tb$/i.test(a.tagName)) {
      var indent = a.style.marginLeft;
      if (indent == "") {
        if (!insert && ev.keyCode != 9)
          return false;
        indent = insert ? "1.5em" : "";
      } else if (indent.indexOf("em") > 0) {
        indent = Number(indent.substring(0,indent.length-2));
        if (!insert && indent == 0)
          indent = "0em";
        else
          indent = ( insert ? indent + 1.5 : indent - 1.5 ) + "em";
      }
      if (/^p$/i.test(a.tagName))
        a.style.marginLeft = indent == "0em" ? '' : indent;
      else {
        var p = document.createElement('p');
        p.innerHTML = a.innerHTML;
        p.style.marginLeft = indent;
        a.innerHTML = HTMLArea.getOuterHTML(p);
      }
      def = false;
      if (ev.keyCode != 9)
        return true;
    }
  }
  if (ev.keyCode != 9)
    return false;
  if (def) {
    if (HTMLArea.is_ie)
      this.insertHTML("&nbsp;&nbsp;&nbsp;&nbsp;");
      //this.insertHTML("<img class='tab' height='0' src='' alt='' width='20' />");
    else
      return false;
  }
  HTMLArea._stopEvent(ev);
  return true;
};

HTMLArea.htmlDecode = function(str) {
  if(typeof str.replace == 'undefined') str = str.toString();
  // we don't need regexp for that, but.. so be it for now.
  str = str.replace(/&lt;/ig, "<");
  str = str.replace(/&gt;/ig, ">");
  str = str.replace(/&nbsp;/g, " "); // Decimal 160, non-breaking-space
  str = str.replace(/&quot;/g, "\"");
  str = str.replace(/&amp;/ig, "&");
  return str;
};

String.prototype.ltrim = function() {
  return this.replace(/^\s+/, '');
};

String.prototype.rtrim = function() {
  return this.replace(/\s+$/, '');
};

HTMLArea.removeTag = function(el) {
  var p = el.parentNode;
  if(!p)
    return;
  var c = el.firstChild;
  while (c != null) {
    var n = c.nextSibling;
    p.insertBefore(c, el);
    c = n;
  }
  HTMLArea.removeFromParent(el);
};

HTMLArea.getElementsByTagNameF = function(root,tag,fn) {
  var tas = root.getElementsByTagName(tag);
  var ret = [];
  for (var i = tas.length; i > 0; ) {
    var el = tas[--i];
    if (fn(el))
      ret[ret.length] = el;
  }
  return ret;
};

HTMLArea.prototype.splitOutRange = function(root,range) {
  if (HTMLArea.is_ie)
    range.select();
  this._doc.execCommand("fontname",false,"split");
  var isSplit = HTMLArea.is_ie ? function(el) {
    return /^font$/i.test(el.tagName) && /^split$/i.test(el.face);
  } : function(el) {
    return (/^font$/i.test(el.tagName) || /^span$/i.test(el.tagName)) && (/^split$/i.test(el.face) || /^split$/i.test(el.style.fontFamily));
  };
  if (isSplit(root))
    HTMLArea.removeTag(root);
  else
    this.splitOutTree(root,range,isSplit);
  
  HTMLArea.replaceTagsF(this.getParentElement().parentNode,function (el) {
    if (!isSplit(el))
      return null;
    HTMLArea.removeTag(el);
    return null;
  });
};

HTMLArea.prototype.adjustSelection = function(sel,range) {
  var text = this.getSelectedText();
  if (!text) return '<nosel>';
  var ltrim = text.ltrim();
  var rtrim = ltrim.rtrim();

  if (text.length != rtrim.length) {
    if (typeof sel == 'undefined')
      sel = this._getSelection();
    if (typeof range == 'undefined')
      range = this._createRange(sel);
    var diff = text.length - ltrim.length;
    if (diff != 0) {
      if (HTMLArea.is_ie)
        range.moveStart('character',diff);
      else {
        if (range.startOffset + diff < 0)
          ltrim = text;
        else
          MindTouch.setStart(range, range.startContainer, range.startOffset + diff);
      }
    }
    diff = rtrim.length - ltrim.length;
    if (diff != 0) {
      if (HTMLArea.is_ie)
        range.moveEnd('character',diff);
      else {
        if (range.endOffset + diff < 0)
          rtrim = ltrim;
        else
          MindTouch.setEnd(range, range.endContainer, range.endOffset + diff);
      }
    }
    if (HTMLArea.is_ie)
      range.select();
    else {
      sel.removeAllRanges();
      sel.addRange(range);
    }
  }
  return rtrim;
}

HTMLArea.prototype.splitOutTree = function(root,range,isMiddle) {
  if (root == null)
    return;
  var left = root.cloneNode(false), middle = root.cloneNode(false), right = root.cloneNode(false);
  HTMLArea.splitOutTreeR(root,isMiddle,left,middle,right,false,false);
  var p = root.parentNode;
  if (HTMLArea.getInnerText(left).length != 0)
    p.insertBefore(left,root);
  var middleL = HTMLArea.getInnerText(middle).length;
  p.insertBefore(middle,root);
  this.selectNodeContents(middle);
  HTMLArea.removeTag(middle);
  var rightL = HTMLArea.getInnerText(right).length;
  if (rightL != 0)
    p.insertBefore(right,root);
  p.removeChild(root);
/*
  if (HTMLArea.is_ie) {
    range.move('character',-rightL);
    range.moveStart('character',-middleL);
    range.select();
  } else { // TODO: adjust selection to be like original
  }
*/
};

// @author Urs C Muff
// This function will split a tree into 3 parts: left, middle and right
// The entire structure is kept intakt of all parts
// Parts before 'isMiddle' are copied into the left part
// Parts that are 'isMiddle' are copied into the middle part
// Parts after 'isMiddle' are copied into the right part
HTMLArea.splitOutTreeR = function(cur,isMiddle,left,middle,right,found,isContextMiddle) {
  var curC = cur.firstChild;

  while (curC != null) {
    var curN = curC.nextSibling;
    var isText = curC.nodeType == 3;
    var isLeaf = !isText && curC.firstChild == null;
    var curIsMiddle = isMiddle(curC);
    if (curIsMiddle)
      found = true;

    if (isText || isLeaf) {
      if (isContextMiddle || curIsMiddle)
        middle.appendChild(curC.cloneNode(false));
      else if (!found) // before found
        left.appendChild(curC.cloneNode(false));
      else // after found
        right.appendChild(curC.cloneNode(false));
    } else {
      var leftC = curC.cloneNode(false), middleC = curC.cloneNode(false), rightC = curC.cloneNode(false);
      found = HTMLArea.splitOutTreeR(curC,isMiddle,leftC,middleC,rightC,found,isContextMiddle || curIsMiddle);
      if (HTMLArea.getInnerText(leftC).length != 0)
        left.appendChild(leftC);
      if (HTMLArea.getInnerText(middleC).length != 0)
        middle.appendChild(middleC);
      if (curIsMiddle)
        HTMLArea.removeTag(middleC);
      if (HTMLArea.getInnerText(rightC).length != 0)
        right.appendChild(rightC);
    }
    
    curC = curN;
  }
  return found;
};

HTMLArea.replaceTagsF = function(root,convertF) {
  var c = root.firstChild;
  while (c != null) {
    var n = c.nextSibling;
    HTMLArea.replaceTagsF(c,convertF);
    var newC = convertF(c);
    if (newC != null) {
      root.insertBefore(newC,c);
      HTMLArea.removeFromParent(c);
    }
    c = n;
  }
};

HTMLArea.prototype.getRange = function() {
  function getBodyOffset(el,cmp,s) {
    for (var i = el.firstChild; i; i = i.nextSibling) {
      if (i == cmp || i.firstChild == cmp)
        return true;
      if (i.nodeType == 3)
        s.len += i.data.length;
      else if (i.nodeType == 8)
        continue;
      else if (getBodyOffset(i,cmp,s))
        return true;
    }
    return false;
  };

  var sel = this._getSelection();
  var range = this._createRange(sel);
  if (HTMLArea.is_ie) {
    var selText = '';
    if (sel.type != 'Control' && range) {
      selText = range.text;
      range.collapse();
      var p = range.parentElement();
      var pOffset = 0;
      while (true) {
        var last = range.htmlText.length;
        range.moveStart('character',-1);
        if (range.parentElement() != p || last == range.htmlText.length)
          break;
        ++pOffset;
      }
    } else {
      var p = this.getParentElement(sel,range);
      var pOffset = 0;
    }
    var s = { len: 0 };
    getBodyOffset(this._doc.body,p,s);
    
    return {
      pOffset: pOffset,
      bOffset: s.len,
      selText: selText
    };
  } else {
    var s = { len: 0 };
    var SC = range.startContainer;
    getBodyOffset(this._doc.body,SC,s);
    var EC = range.endContainer;
    if (SC == EC)
      return {
        pOffset: range.startOffset,
        bOffset: s.len,
        length : range.endOffset - range.startOffset
      };
    else {
      var t = { len: 0 };
      getBodyOffset(this._doc.body,EC,t);
      return {
        pOffset : range.startOffset,
        bOffset : s.len,
        ePOffset: range.endOffset,
        eBOffset: t.len,
        length  : t.len + range.endOffset - range.startOffset - s.len
      };
    }
  }
};

HTMLArea.prototype.setRange = function(r) {
  function findBodyOffset(el,s) {
    if (s.len < 0)
      return el;
    for (var i = el.firstChild; i; i = i.nextSibling) {
      if (i.nodeType == 3)
        s.len -= i.data.length;
      else if (i.nodeType == 8)
        continue;
      else {
        var t = findBodyOffset(i,s);
        if (t != null)
          return t;
      }
      if (s.len < 0)
        return i;
    }
    return null;
  };

  var el = findBodyOffset(this._doc.body,{len:r.bOffset});
  if (el == null)
    return false;
  if (HTMLArea.is_ie) {
    range = this._doc.body.createTextRange();
    range.moveToElementText(el.nodeType == 3 ? el.parentNode : el);
    range.collapse(true);
    range.move('character',r.pOffset);
    range.moveEnd('character',r.selText.length);
    range.select();
    return true;
  } else {
    range = this._createRange();
    MindTouch.setStart(range, el,r.pOffset);
    if (r.length == 0)
      range.collapse(true);
    else if (typeof r.eBOffset == 'undefined') {
      MindTouch.setEnd(range, el,r.pOffset+r.length);
    } else {
      el = findBodyOffset(this._doc.body,{len:r.eBOffset});
      MindTouch.setEnd(range, el,r.ePOffset);
    }
  }
};

HTMLArea.prototype.selectWord = function() {
  if (HTMLArea.is_ie) {
    var sel = this._getSelection();
    var range = this._createRange(sel);
    range.expand('word');
    range.select();
    this.adjustSelection(sel,range);
  } else { // TODO: not handled yet
  }
};

HTMLArea.cloneNode = function(a,b) {
  var c = a.firstChild;
  while (c != null) {
    b.appendChild(c.cloneNode(true));
    c = c.nextSibling;
  }
  return b;
};

HTMLArea.prototype.insertContent = function(range, html) {
  if (HTMLArea.is_ie) {
    range.pasteHTML(html);
  } else {
    var fragment = this._doc.createDocumentFragment();
    var div = this._doc.createElement("div");
    div.innerHTML = html;
    while (div.firstChild) {
      // the following call also removes the node from div
      fragment.appendChild(div.firstChild);
    }
    range.insertNode(fragment);
  }
};

HTMLArea.prototype.getRangeHTML = function(range) {
  if (HTMLArea.is_ie)
    return range.htmlText;
  else
    return HTMLArea.getHTML(range.cloneContents(), false, this);
};

HTMLArea.prototype.selectRange = function(range) {
  if (HTMLArea.is_ie)
    range.select();
  else {
    var sel = this._getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
  }
};

HTMLArea.prototype.insertList = function(para,len,cmd) {
  this._undoTakeSnapshot();
  var r = MindTouch.cloneRange(this._createRange());
  MindTouch.setStart(r, para, 0);
  MindTouch.setEnd(r, para, len);
  this.deleteContents(r);
  this.execCommand(cmd, false, null);
};

HTMLArea.prototype.findAndReplace = function(find,replace,block,innerTxt) {
  var pos = innerTxt.indexOf(find);
  while (pos >= 0) {
    this._undoTakeSnapshot();
    var rSel = this._createRange();
    if (HTMLArea.is_gecko) {
      this.insertContent(rSel,"<span class='marker'></span>");
      var marker = rSel.endContainer.nextSibling;
      var sel = this._getSelection();
      sel.removeAllRanges();
    }
    var r = MindTouch.cloneRange(rSel);
    MindTouch.setStart(r, block, pos);
    if (HTMLArea.is_gecko) {
      if (r.startContainer.nodeType == 1 && r.startContainer.childNodes[r.startOffset] == marker) {
        r.selectNode(marker.nextSibling);
        r.collapse(true);
      }
    }
    MindTouch.setEnd(r, block, pos + find.length);
    var root = MindTouch.commonAncestorContainer(r);
    if (root && root.nodeType == 1 && root.tagName.toLowerCase() == 'span' && root.className == 'plain') {
      pos = innerTxt.indexOf(find, pos+find.length);
      continue;
    }
    if (HTMLArea.is_ie)
      r.pasteHTML('');
    else
      r.deleteContents();
    this.insertContent(r, replace);
    if (marker) {
      if (marker.parentNode) {
        rSel.selectNode(marker);
        sel.addRange(rSel);
        rSel.deleteContents();
      } else
        sel.addRange(rSel);
    }
    innerTxt = HTMLArea.getInnerText(block);
    pos = innerTxt.indexOf(find, pos+find.length);
  }
  return innerTxt;
};

HTMLArea.prototype.deleteContents = function(range) {
  if (HTMLArea.is_ie)
    range.pasteHTML('');
  else {
    var sel = this._getSelection();
    var rSel = this._createRange();
    this.insertContent(rSel,"<span class='marker'></span>");
    var marker = rSel.endContainer.nextSibling;
    sel.removeAllRanges();
    range.deleteContents();
    if (marker.parentNode) {
      rSel.selectNode(marker);
      sel.addRange(rSel);
      rSel.deleteContents();
    } else
      sel.addRange(rSel);
  }
};

/*
 * MindTouch Deki - a commercial grade open source wiki
 *  derived from MediaWiki (www.mediawiki.org)
 * Copyright (C) 2006 MindTouch, Inc.
 * http://www.mindtouch.com/  oss@mindtouch.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

// Author: Urs C. Muff

// NOTE: change the version above each function if changed, so that the C# version can be kept up to date.

// version 1.0
HTMLArea.xhtml_surroundNode = function(node, outer) {
  node.parentNode.insertBefore(outer, node);
  outer.appendChild(node);
};

// version 1.2
HTMLArea.xhtml_invalidFormating = function(node, context) {
  var prev = HTMLArea.xhtml_skipEmptyText(node, 'previousSibling');
  if (prev && prev.nodeType == 1 && prev.tagName.toLowerCase() == 'p') {
    if (node.nodeType == 1 && node.tagName.toLowerCase() == 'br') {
      HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('p'));
      return node;
    }
    prev.appendChild(node);
    return node;
  }
  var parentTag = node.parentNode.tagName.toLowerCase();
  if (parentTag != 'body' && parentTag != 'blockquote') {
    return HTMLArea.xhtml_invalidNonBlock(node, context);
  }
  var sibling = node.nextSibling;
  HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('p'));
  while (sibling) {
    var n = sibling.nextSibling;
    if (sibling.nodeType == 1) {
      var data = HTMLArea.validTags[sibling.tagName.toLowerCase()];
      if (data && data.isBlock)
        break;
    }
    node.parentNode.appendChild(sibling);
    sibling = n;
  }
  return node;
};

// version 1.0
HTMLArea.xhtml_getChildren = function(node) {
  var children = new Array();
  var child = node.firstChild;
  while (child) {
    children.push(child);
    child = child.nextSibling;
  }
  return children;
};

// version 1.0
HTMLArea.xhtml_handleChildren = function(children, context) {
  for (var c = 0; c < children.length; ++c) {
    var child = children[c];
    if (!HTMLArea.isNodeDeleted(child))
      HTMLArea.makeXHTML(child, context, child.parentNode);
  }
};

// version 1.1
HTMLArea.xhtml_removeTag = function(node, context) {
  var children = HTMLArea.xhtml_getChildren(node);
  HTMLArea.removeTag(node);
  HTMLArea.xhtml_handleChildren(children, context);
};

// version 1.1
HTMLArea.xhtml_convertTo = function(node, context, newNode) {
  var parent = node.parentNode;
  if (parent.tagName.toLowerCase() == 'body') {
    node = HTMLArea.xhtml_invalidFormating(node, context);
    parent = node.parentNode;
  }

  var parentData = HTMLArea.validTags[parent.tagName.toLowerCase()];
  if (!parentData.validChild || !parentData.validChild.test(newNode.tagName.toLowerCase())) {
    HTMLArea.xhtml_removeTag(node, context);
    return null;
  }

  HTMLArea.cloneNode(node,newNode);
  parent.replaceChild(newNode,node);
  return newNode;
};

// version 1.0
HTMLArea.xhtml_convertToStrong = function(node, context) {
  return HTMLArea.xhtml_convertTo(node, context, node.ownerDocument.createElement('strong'));
};

// version 1.1
HTMLArea.xhtml_convertToCenterStyle = function(node, context) {
  var c = node.firstChild;
  var parent = node.parentNode;
  while (c != null) {
    var n = c.nextSibling;
    if (c.nodeType == 1) {
      if (context.toSave) c.style.textAlign = "center";
      else c.align = "center";
    } else {
      var newC = node.ownerDocument.createElement('p');
      if (context.toSave) newC.style.textAlign = "center";
      else newC.align = "center";
      newC.appendChild(c);
      c = newC;
    }
    parent.insertBefore(c, node);
    c = n;
  }
  HTMLArea.removeFromParent(node);
  return null;
};

// version 1.1
HTMLArea.xhtml_convertToEm = function(node, context) {
  return HTMLArea.xhtml_convertTo(node, context, node.ownerDocument.createElement('em'));
};

// version 1.1
HTMLArea.xhtml_convertToCode = function(node, context) {
  return HTMLArea.xhtml_convertTo(node, context, node.ownerDocument.createElement('code'));
};

// version 1.1
HTMLArea.xhtml_invalidList = function(node, context) {
  var parent = node.parentNode;
  switch (parent.tagName.toLowerCase()) {
  case "ul":
  case "ol":
    var prev = HTMLArea.xhtml_skipEmptyText(node,'previousSibling');
    if (prev && prev.nodeType == 1 && prev.tagName.toLowerCase() == 'li') {
      prev.appendChild(node);
      return node;
    } else {
      HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('li'));
      node.parentNode.insertBefore(HTMLArea.xhtml_createNBSP(node), node);
      return node;
    }
    break;
  default:
    return HTMLArea.xhtml_moveToBlockContainer(node);
  }
};

// version 1.1
HTMLArea.xhtml_moveToBlockContainer = function(node) {
  var cur = node;
  var parent = cur.parentNode;
  var parentClone = null;
  while (true) {
    var oldClone = parentClone;
    var next = HTMLArea.xhtml_skipEmptyText(cur, 'nextSibling');
    parentClone = next || oldClone ? parent.cloneNode(false) : null;
    if (oldClone)
      parentClone.appendChild(oldClone);
    while (cur.nextSibling != null)
      parentClone.appendChild(cur.nextSibling);
    cur = parent;
    parent = cur.parentNode;
    if (parent == null || typeof parent.tagName == "undefined")
      return null;
    if (HTMLArea.validTags[parent.tagName.toLowerCase()].isBlockContainer)
      break;
  }
  try {
    parent.insertBefore(node, cur.nextSibling);
    if (parentClone != null)
      parent.insertBefore(parentClone, node.nextSibling);
  } catch (e) {
    parent.appendChild(node);
  }
  return node;
};

// version 1.0
HTMLArea.xhtml_createNBSP = function(node) {
  return node.ownerDocument.createTextNode(unescape("%u00a0"));
};

// version 1.1
HTMLArea.xhtml_invalidListItem = function(node, context) {
  if (typeof context.ul != 'undefined') {
    var validUL = true;
    var p = node.parentNode;
    while (p) {
      var pData = HTMLArea.validTags[p.tagName.toLowerCase()];
      if (pData.tag == 'ul') break;
      if (pData.isBlock) {
        validUL = false;
        break;
      }
      p = p.parentNode;
    }
    if (validUL)
      return node;
  }
  var prev = HTMLArea.xhtml_skipEmptyText(node,'previousSibling');
  if (prev && prev.nodeType == 1) {
    var pTag = prev.tagName.toLowerCase();
    if (pTag == 'ul' || pTag == 'ol') {
      prev.appendChild(node);
      return node;
    }
  }
  var newNode = node.ownerDocument.createElement('ul');
  HTMLArea.xhtml_surroundNode(node, newNode);
  return node;
};

// version 1.1
HTMLArea.xhtml_invalidH = function(node, context) {
  var doSplit = true;
  var p = node.parentNode;
  while (p && typeof p.tagName != 'undefined' && doSplit) {
    switch (p.tagName.toLowerCase()) {
    case 'li': case 'td': case 'th':
      doSplit = false;
    }
    p = p.parentNode;
  }
  if (doSplit) {
    if (node.parentNode.tagName.toLowerCase() == 'center') {
      if (context.toSave) node.style.textAlign = "center";
      else node.align = "center";
    }
    return HTMLArea.xhtml_moveToBlockContainer(node);
  }
  var newNode = node.ownerDocument.createElement('span');
  newNode.className = 'header_' + node.tagName.substring(1);
  HTMLArea.xhtml_convertTo(node, context, newNode);
  if (!HTMLArea.isNodeDeleted(newNode))
    newNode.parentNode.appendChild(HTMLArea.xhtml_createNBSP(node));
  return newNode;
};

// version 1.1
HTMLArea.xhtml_invalidP = function(node, context) {
  var prev = HTMLArea.xhtml_skipEmptyText(node, 'previousSibling');
  if (prev != null) {
    var parent = node.parentNode;
    parent.insertBefore(parent.tagName.toLowerCase() == 'pre'
      ? node.ownerDocument.createTextNode("\r")
      : node.ownerDocument.createElement('br'), node);
  };
  HTMLArea.xhtml_removeTag(node, context);
  return null;
};

// version 1.1
HTMLArea.xhtml_invalidBlock = function(node, context) {
  return HTMLArea.xhtml_moveToBlockContainer(node);
};

// version 1.0
HTMLArea.xhtml_invalidNonBlock = function(node, context) {
  var tag = node.tagName.toLowerCase();
  var parentData = HTMLArea.validTags[node.parentNode.tagName.toLowerCase()];

  function handleChild(child) {
    if (child.nodeType == 1) {
      if (!parentData || !parentData.validChild || !parentData.validChild.test(child.tagName)) {
        HTMLArea.xhtml_applyToChildren(child, handleChild);
      } else {
        var childData = HTMLArea.validTags[child.tagName.toLowerCase()];
        if (childData && childData.validChild && childData.validChild.test(tag)) {
          HTMLArea.xhtml_applyToChildren(child, function(cc) {
            HTMLArea.xhtml_surroundNode(cc, node.cloneNode(false));
          });
        }
      }
    }
  };

  HTMLArea.xhtml_applyToChildren(node, handleChild);
  HTMLArea.xhtml_removeTag(node, context);
  return null;
};

// version 1.0
HTMLArea.xhtml_applyToChildren = function(node, func) {
  var child = node.firstChild;
  while (child != null) {
    var next = child.nextSibling;
    func(child);
    child = next;
  }
};

// version 1.1
HTMLArea.xhtml_convertToOl = function(node, context) {
  return HTMLArea.xhtml_convertTo(node, context, node.ownerDocument.createElement('ol'));
};

// version 1.1
HTMLArea.xhtml_convertToIns = function(node, context) {
  return HTMLArea.xhtml_convertTo(node, context, node.ownerDocument.createElement('ins'));
};

// version 1.1
HTMLArea.xhtml_convertToDel = function(node, context) {
  return HTMLArea.xhtml_convertTo(node, context, node.ownerDocument.createElement('del'));
};

// version 1.0
HTMLArea.xhtml_convertFontToSpan = function(node, context) {
  var newNode = node.ownerDocument.createElement('span');
  if (node.size != "") {
    switch (node.size) {
      case "-2":
        newNode = node.ownerDocument.createElement('small');
        break;
      case "+2":
        newNode = node.ownerDocument.createElement('big');
        break;
      case "+3":
        newNode = node.ownerDocument.createElement('big');
        break;
    default:
        newNode.style.fontSize = "";
        break;
    }
  }
  if (node.style.cssText != "")
    newNode.style.cssText = node.style.cssText;
  if (node.face != "")
    newNode.style.fontFamily = node.face;
  if (node.color != "")
    newNode.style.color = node.color;
  if (!HTMLArea.xhtml_convertTo(node, context, newNode))
    return null;
  if (node.size == "+3")
    HTMLArea.xhtml_surroundNode(newNode, node.ownerDocument.createElement('big'));
  return newNode;
};

HTMLArea.xhtml_findBlockOrContainer = function(node) {
  var block = node.parentNode;
  while (true) {
    if (block == null || typeof block.tagName == "undefined")
      return null;
    var tag = HTMLArea.validTags[block.tagName.toLowerCase()];
    if (tag.isBlock || tag.isBlockContainer)
      break;
    block = block.parentNode;
  }
  return block;
};

HTMLArea.xhtml_handleBr = function(node, context) {
  function cloneParents(pnt) {
    if (newContainer && !newContainer.firstChild)
      newContainer.appendChild(HTMLArea.xhtml_createNBSP(newContainer));
    newBlock = block.cloneNode(false);
    block.parentNode.insertBefore(newBlock, blockInsert);
    newContainer = newBlock;
    if (pnt.tagName != block.tagName) {
      var pps = new Array();
      while (pnt.tagName != block.tagName) {
        pps.push(pnt);
        pnt = pnt.parentNode;
      }
      pps.reverse();
      for (var i = 0; i < pps.length; ++i) {
        var newC = pps[i].cloneNode(false);
        newContainer.appendChild(newC);
        newContainer = newC;
      }
    }
  };

  var block = HTMLArea.xhtml_findBlockOrContainer(node);
  if (block == null) return null;
  var parentBlock = HTMLArea.xhtml_findBlockOrContainer(block);
  if (parentBlock != null) {
    switch (parentBlock.tagName.toLowerCase()) {
    case 'td': case 'th': case 'li': case 'dt':
      return null;
    }
  }
  var blockInsert = block.nextSibling;
  var p = node.previousSibling;
  var n = node.nextSibling;
  if (p && p.nodeType == 3 && p.data.trim().length == 0) p = null;
  if (n && n.nodeType == 3 && n.data.trim().length == 0) n = null;
  if ((!p || p.nodeType != 1 || p.tagName.toLowerCase() != 'br') && !n) {
    if (p && p.nodeType == 3) {
      HTMLArea.removeFromParent(node);
      return null;
    }
    return node;
  }
  switch (block.tagName.toLowerCase()) {
  case 'p': case 'blockquote': case 'dd':
    var newBlock, newContainer;
    cloneParents(node.parentNode);
    if (n) {
      HTMLArea.removeFromParent(node);
      while (n) {
        var nn = n.nextSibling;
        if (n.nodeType == 1 && n.tagName.toLowerCase() == 'br') {
          if (n.nextSibling) {
            cloneParents(n.parentNode);
            HTMLArea.removeFromParent(n);
          }
          else HTMLArea.removeFromParent(n);
        }
        else newContainer.appendChild(n);
        n = nn;
      }
    } else {
      var n = node.nextSibling;
      HTMLArea.removeFromParent(node);
      while (true) {
        HTMLArea.removeFromParent(n);
        if (n.nodeType == 1 && n.tagName.toLowerCase() == 'br')
          break;
        n = n.nextSibling;
      }
    }
    if (!newContainer.firstChild)
      newContainer.appendChild(HTMLArea.xhtml_createNBSP(newContainer));
  }
};

HTMLArea.xhtml_handleP = function(node, context) {
  var text = node.firstChild && node.firstChild.nodeType == 3 ? node.firstChild.data : '';
  var block = HTMLArea.xhtml_findBlockOrContainer(node);
  if (text.length > 0) {
    try {
      var level = 0;
      switch (text.charCodeAt(0)) {
      case 183: case '*': case '-': case '+': level = 1; break;
      case 111: level = 2; break;
      case 167: level = 3; break;
      }
      if (level && text.length == 1 && node.firstChild.nextSibling && node.firstChild.nextSibling.nodeType == 3) {
        node.firstChild.data += node.firstChild.nextSibling.data;
        HTMLArea.removeFromParent(node.firstChild.nextSibling);
        text = node.firstChild.data;
      }
      if (level && text.substring(1,2).htmlTrim().length == 0) {
        var curLevel = 0, p = node.previousSibling, lastUl;
        var newText = text.substring(1).htmlTrim();
        while (p && curLevel < level && p.lastChild && p.lastChild.nodeType == 1) {
          var t = p.tagName.toLowerCase();
          if (t == 'ul') {
            ++curLevel;
            lastUl = p;
          }
          p = p.lastChild;
        }
        if (curLevel < level) {
          var ul = node.ownerDocument.createElement('ul');
          HTMLArea.xhtml_surroundNode(node, ul);
        }
        if (newText.length)
          node.firstChild.data = newText;
        else
          HTMLArea.removeFromParent(node.firstChild);
        var li = node.ownerDocument.createElement('li');
        HTMLArea.cloneNode(node,li);
        node.parentNode.replaceChild(li,node);
        if (curLevel == level && lastUl) {
          lastUl.appendChild(li);
        } else if (curLevel < level && curLevel > 0 && lastUl) {
          lastUl.lastChild.appendChild(ul);
        }
        return;
      }
    } catch (e) {}
  }
  if (block == null) return;
  switch (block.tagName.toLowerCase()) {
  case 'td': case 'th': case 'li': case 'dt':
    var n = node.nextSibling;
    if (n && (n.nodeType != 1 || n.tagName.toLowerCase() != 'br' || node.style.cssText != "") 
      && !(node.firstChild == node.lastChild && node.nodeType == 1 && nodeType.tagName.toLowerCase() == "br")) {
      node.parentNode.insertBefore(node.ownerDocument.createElement('br'),n);
    }
    if (node.style.cssText != "") {
      var newNode = node.ownerDocument.createElement('span');
      newNode.style.cssText = node.style.cssText;
      HTMLArea.xhtml_convertTo(node, context, newNode);
    } else {
      var text = HTMLArea.getInnerText(node);
      if (text.htmlTrim().length == 0) {
        if (n) node.parentNode.insertBefore(HTMLArea.xhtml_createNBSP(node),node.nextSibling);
        HTMLArea.removeFromParent(node);
      } else
        HTMLArea.removeTag(node);
    }
  }
};

// version 1.0
HTMLArea.xhtml_handleClassName = function(node, context) {
  var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig, ' ');
  if (newc != node.className) {
    context.is_word = true;
    HTMLArea.xhtml_recordEvent(context, true, "className contains mso.*, removed");
    node.className = newc;
    if (!/\S/.test(node.className)) node.removeAttribute("className");
  }
};

// version 1.1 (C# version also handles text-indent, text-align valiues, and color/background-color values)
HTMLArea.xhtml_cssStyleR = HTMLArea.compileRegex("\s*;\s*");
HTMLArea.xhtml_msoCssR = HTMLArea.compileRegex(/^\s*mso|^\s*tab-stops|^\s*margin: 0in 0in 0pt|^\s*margin-top: 0in|^text-indent: -[0-9\.]+in|^font-weight: [a-z]+|^line-height: [a-z]+|font-style: [a-z]+|^font-size-adjust: [a-z]+|^font-stretch: [a-z]+/i);
HTMLArea.xhtml_handleCssStyle = function(node, context) {
  if (node.style.display == "none") {
    HTMLArea.removeFromParent(node);
    return null;
  }
  var declarations = node.style.cssText.split(HTMLArea.xhtml_cssStyleR);
  var changed = false;
  for (var i = declarations.length; --i >= 0;) {
    if (HTMLArea.xhtml_msoCssR.test(declarations[i]) /*||
        /^margin\s*:|^margin-top\s*:|^margin-right\s*:/i.test(declarations[i])*/) {
      context.is_word = true;
      changed = true;
      declarations.splice(i, 1);
    }
  }
  if (changed) {
    HTMLArea.xhtml_recordEvent(context, true, "style contains mso.*|tab-stops, removed");
    node.style.cssText = declarations.join("; ");
  }
  
  switch (node.style.fontSize) {
  case "-2":
  case "+2":
  case "+3":
    break;
  default:
    node.style.fontSize = null;
  }
  
  if (!context.toSave && node.style.textAlign != "") {
    node.align = node.style.textAlign;
    node.style.textAlign = "";
  }
};

// version 1.1
HTMLArea.xhtml_getStyle = function(node) {
  var ret = new Object();
  if (node.style.cssText != "") {
    var styles = node.style.cssText.split(';');
    for (var i = 0; i < styles.length; ++i) {
      var style = styles[i].trim();
      if (style != "") {
        var styleVal = style.split(':');
        ret[styleVal[0].replace(/-/g,'_').toUpperCase()] = styleVal[1].trim();
      }
    }
  }
  return ret;
};

// version 1.0
HTMLArea.xhtml_get_TR_TD = function(node) {
  var ret = new Object();
  for (var s in {'align':true, 'vAlign':true}) {
    if (node[s] != "")
      ret[s] = node[s];
  }
  return ret;
};

// version 1.0
HTMLArea.xhtml_remove_TR_TD = function(node, vals) {
  for (var s in vals)
    node[s] = "";
};

// version 1.0
HTMLArea.xhtml_add_TR_TD = function(node, vals) {
  for (var s in vals)
    node[s] = vals[s];
};

// version 1.0
HTMLArea.xhtml_style2CssText = function(style) {
  var cssText = "";
  for (var s in style) {
    if (cssText != "") cssText += "; ";
    cssText += s.replace(/_/,'-') + ": " + style[s];
  }
  return cssText;
};

// version 1.0
HTMLArea.xhtml_removeStyle = function(node, style) {
  var now = HTMLArea.xhtml_getStyle(node);
  for (var s in style)
    delete now[s];
  node.style.cssText = HTMLArea.xhtml_style2CssText(now);
};

// version 1.0
HTMLArea.xhtml_addStyle = function(node, style) {
  var now = HTMLArea.xhtml_getStyle(node);
  for (var s in style)
    now[s] = style[s];
  node.style.cssText = HTMLArea.xhtml_style2CssText(now);
};

// version 1.0
HTMLArea.xhtml_deleteCommonStyle = function(child, common, styleName) {
  if (child.innerHTML.htmlTrim().length == 0)
    return;
  delete common[styleName];
  if (styleName.toLowerCase().indexOf('border_') == 0) {
    delete common['BORDER_LEFT'];
    delete common['BORDER_RIGHT'];
    delete common['BORDER_TOP'];
    delete common['BORDER_BOTTOM'];
  }
};

// version 1.0
HTMLArea.xhtml_promoteAttributes = function(node, collect, remove, add, deleteF) {
  function findElement(node) {
    while (node && node.nodeType != 1)
      node = node.nextSibling;
    return node;
  }
  var child = findElement(node.firstChild);
  if (!child) return;
  var common = collect(child);
  child = findElement(child.nextSibling);
  while (child) {
    if (child.nodeType == 1) {
      var vals = collect(child);
      for (var s in common) {
        if (vals[s] != common[s])
          if (deleteF) deleteF(child, common, s);
          else delete common[s];
      }
    }
    child = findElement(child.nextSibling);
  }
  child = findElement(node.firstChild);
  while (child) {
    remove(child, common);
    child = findElement(child.nextSibling);
  }
  add(node, common);
};

// version 1.0
HTMLArea.xhtml_promoteStyle = function(node) {
  HTMLArea.xhtml_promoteAttributes(node, HTMLArea.xhtml_getStyle, 
    HTMLArea.xhtml_removeStyle, HTMLArea.xhtml_addStyle,
    HTMLArea.xhtml_deleteCommonStyle);
};

// version 1.0
HTMLArea.xhtml_handleTR = function(node, context) {
  HTMLArea.xhtml_promoteStyle(node);
  HTMLArea.xhtml_promoteAttributes(node, HTMLArea.xhtml_get_TR_TD, 
    HTMLArea.xhtml_remove_TR_TD, HTMLArea.xhtml_add_TR_TD, null);
};

// version 1.0
HTMLArea.xhtml_handleTD = function(node, context) {
  if (!context.toSave) {
    if (node.firstChild && node.firstChild == node.lastChild && node.firstChild.nodeType == 3 && HTMLArea.getInnerText(node).htmlTrim().length == 0)
      node.removeChild(node.firstChild);
  }
  HTMLArea.xhtml_promoteStyle(node);
};

// version 1.0
HTMLArea.xhtml_handleTable = function(node, context) {
  function getCol(cell) {
    var ret = new Object();
    for (var s in {width: true, align: true, vAlign: true}) {
      if (cell[s] != "")
        ret[s] = cell[s];
    }
    if (cell.style.width != "")
      ret.width = cell.style.width;
    if (cell.style.textAlign != "")
      ret.align = cell.style.textAlign;
    return ret;
  };
  function getRow(row) {
    var ret = new Array();
    for (var i = 0; i < row.cells.length; ++i)
      ret.push(getCol(row.cells[i]));
    return ret;
  };
  function removeStyleRow(row, styles) {
    for (var i = 0; i < row.cells.length; ++i) {
      var cell = row.cells[i];
      var style = styles[i];
      for (var s in style) {
        switch (s) {
        case 'align':
          cell.align = "";
          cell.style.textAlign = "";
          break;
        case 'width':
          cell.width = "";
          cell.style.width = "";
          break;
        default:
          cell[s] = "";
        }
      }
    }
  };

  if (node.rows.length < 2)
    return;
  var row = node.rows[0];
  var cols = row.cells.length;
  var commons = getRow(row);
  var hasCommon = false;
  for (var i = 1; i < node.rows.length; ++i) {
    var row = node.rows[i];
    if (row.cells.length != cols)
      return;
    var valss = getRow(row);
    for (var c = 0; c < cols; ++c) {
      if (row.cells[c].innerHTML.htmlTrim().length != 0) {
        var common = commons[c];
        var vals = valss[c];
        for (var s in common)
          if (vals[s] != common[s])
            delete common[s];
          else
            hasCommon = true;
      }
    }
  }
  
  if (!hasCommon)
    return;
  
  for (var i = 0; i < node.rows.length; ++i) {
    var row = node.rows[i];
    removeStyleRow(row, commons);
  }

  var myCols_ = node.getElementsByTagName('col');
  var myCols = new Array();
  for (var c = 0; c < myCols_.length; ++c) {
    var col = myCols_[c];
    if (col.parentNode == node || col.parentNode.parentNode == node)
      myCols.push(col);
  }
  var insertPoint = myCols.length > 0 ? myCols[myCols.length-1].nextSibling : node.firstChild;
  while (myCols.length < cols) {
    var col = node.ownerDocument.createElement('col');
    node.insertBefore(col, insertPoint);
    myCols.push(col);
  }
  for (var c = 0; c < cols; ++c) {
    var common = commons[c];
    var col = myCols[c];
    for (var s in common)
      col[s] = common[s];
  }
};

// version 1.1
HTMLArea.xhtml_handleH = function(node, context) {
  if (node.style.cssText != "") {
    var textAlign = node.style.textAlign;
    node.style.cssText = "";
    node.style.textAlign = textAlign;
  }
  var title = HTMLArea.getInnerText(node);
  var nt = title.replace(/^[\s\xA0]+/, '').replace(/[\s\xA0]+$/, '');
  if (nt.length == 0)
    HTMLArea.removeFromParent(node);
  if (nt.length != title.length)
    HTMLArea.is_ie ? node.innerText = nt : node.textContent = nt;
};

HTMLArea.xhtml_skipEmptyText = function(node, property) {
  if (!node) return node;
  var ret = node[property];
  while (ret && ret.nodeType == 3 && ret.data.htmlTrim().length == 0)
    ret = ret[property];
  return ret;
};

// version 1.0
HTMLArea.xhtml_handleList = function(node, context) {
  if (node.getAttribute('type') != null)
    node.removeAttribute('type');
  var myLi = node.parentNode;
  var prev = HTMLArea.xhtml_skipEmptyText(node,'previousSibling');
  var parPrev = HTMLArea.xhtml_skipEmptyText(myLi,'previousSibling');
  var isSubList = myLi.tagName.toLowerCase() == 'li';
  if (!prev && !HTMLArea.xhtml_skipEmptyText(node,'nextSibling') && isSubList && parPrev && parPrev.nodeType == 1 && parPrev.tagName.toLowerCase() == 'li') {
    parPrev.appendChild(node);
    HTMLArea.removeFromParent(myLi);
    return node;
  }
  if (node.firstChild && node.firstChild.nodeType == 3 && !node.firstChild.nextSibling && node.innerHTML.htmlTrim().length == 0) {
    HTMLArea.removeFromParent(node);
    return null;
  }
  if (prev && prev.nodeType == 1 && prev.tagName == node.tagName && isSubList) {
    while (node.firstChild)
      prev.appendChild(node.firstChild);
    HTMLArea.removeFromParent(node);
    return null;
  }
  return node;
};

// version 1.0
HTMLArea.xhtml_fontConvert = null;

HTMLArea.xhtml_getFirstAncestorF = function(node, fn) {
  while(node) {
    if (fn(node))
      return node;
    if (/^(body|table)$/i.test(node.tagName))
      break;
    node = node.parentNode;
  }

  return null;
};

// version 1.0
HTMLArea.xhtml_checkFont = function(editor, node, face) {
  // version 1.0
  function setFace(node, val) {
    var vals = val.split('|');
    var face = vals[0];
    var p = HTMLArea.xhtml_getFirstAncestorF(node.parentNode,function(el) { return el.style.fontFamily || el.face; });
    if (!p && face.indexOf('sans-serif') >= 0)
      face = '';
    if (node.tagName.toLowerCase() == 'span') {
      node.style.fontFamily = face;
      if (face == '') node.style.cssText = node.style.cssText.replace(/font-family: ;?/i,'');
    } else
      node.face = face;
    if (vals.length > 1)
      node.style.fontVariant = vals[1];
    else
      node.style.fontVariant = p ? 'normal' : '';
  };
  
  if (HTMLArea.xhtml_fontConvert == null) {
    HTMLArea.xhtml_fontConvert = new Object();
    for (var _cfn in editor.config.fontname) {
      var cfn = editor.config.fontname[_cfn];
      var cfnFace = cfn.split('|')[0];
      if (cfnFace == '') continue;
      var cfnA = cfnFace.split(',');
      HTMLArea.xhtml_fontConvert[cfnFace] = cfn;
      for (var i = cfnA.length; --i >= 0;) {
        var cf = cfnA[i].toLowerCase();
        if (typeof HTMLArea.xhtml_fontConvert[cf] == "undefined" || i == 0)
          HTMLArea.xhtml_fontConvert[cf] = cfn;
      }
    }
  }
  face = face.toLowerCase();
  if (typeof HTMLArea.xhtml_fontConvert[face] != "undefined")
    setFace(node, HTMLArea.xhtml_fontConvert[face]);
  else {
    var faceA = face.split(',');
    for (var j = faceA.length; --j >= 0;) {
      var f = faceA[j];
      if (typeof HTMLArea.xhtml_fontConvert[f] != "undefined") {
        setFace(node, HTMLArea.xhtml_fontConvert[f]);
        HTMLArea.xhtml_fontConvert[face] = HTMLArea.xhtml_fontConvert[f];
        return;
      }
    }
    setFace(node, '');
  }
};

HTMLArea.xhtml_handleCssToFont = function(node, newNode, context) {
  if (node.style.fontFamily != "")
    HTMLArea.xhtml_checkFont(context.editor, newNode, node.style.fontFamily);
  if (node.style.color != "")
    newNode.color = HTMLArea._colorToRgb(node.style.color);
  if (node.style.cssText != "") {
    var style = HTMLArea.xhtml_getStyle(node);
    for (var s in {FONT_FAMILY: true, COLOR: true})
      delete style[s];
    newNode.style.cssText = HTMLArea.xhtml_style2CssText(style);
  }
};

// version 1.1
HTMLArea.xhtml_spanIgnoreAttrR = HTMLArea.compileRegex("(_moz)|(contenteditable)|(_msh)", "i");
HTMLArea.xhtml_handleSpan = function(node, context) {
  if (node.style.fontFamily != "")
    HTMLArea.xhtml_checkFont(context.editor, node, node.style.fontFamily);
  if (!context.toSave) {
    if (node.style.fontFamily != "" || node.style.color != "" || node.style.backgroundColor != "") {
      var newNode = node.ownerDocument.createElement('font');
      HTMLArea.xhtml_convertTo(node, context, newNode);
      HTMLArea.xhtml_handleCssToFont(node, newNode, context);
      return;
    }
  }

  HTMLArea.xhtml_removeTagWithNoAttribute(node, context);
};

// version 1.0
HTMLArea.xhtml_removeTagWithNoAttribute = function(node, context) {
  var hasAttrs;
  if (HTMLArea.is_ie)
    hasAttrs = node.outerHTML.htmlTrim().length - node.innerHTML.length > 2*node.tagName.length + 5;
  else {
    var attrs = node.attributes, count = 0;
    for (i = 0; i < attrs.length; ++i) {
      var a = attrs.item(i);
      if (!a.specified || HTMLArea.xhtml_spanIgnoreAttrR.test(a.nodeName))
        continue;
      ++count;
    }
    hasAttrs = count > 0;
  }
  if (!hasAttrs)
    HTMLArea.xhtml_removeTag(node, context);
};

// version 1.1
HTMLArea.xhtml_handleA = function(node, context) {
  MindTouch.normalizeLink(node, context.editor);
};

HTMLArea.xhtml_handleXmlDiff = function(node, context) {
  switch (node.className) {
  case "diff-insert":
  case "diff-delete":
    HTMLArea.xhtml_removeTag(node, context);
    break;
  }
};

// version 1.0
HTMLArea.xhtml_handleDiv = function(node, context) {
  if (node.className != "") {
    switch (node.className) {
    case 'imageWrap':
      HTMLArea.removeFromParent(node);
      return;
    }
    var img = node.firstChild;
    if (img && img.nodeType == 1) {
      var tag = img.tagName.toLowerCase();
	  if (tag == 'img')
        return;
      img = img.firstChild;
	  if (tag == 'a' && img && img.nodeType == 1 && img.tagName.toLowerCase() == 'img')
        return;
    }
    node.removeAttribute("className");
  }
  HTMLArea.xhtml_removeTagWithNoAttribute(node, context);
};

// version 1.0
HTMLArea.xhtml_handleFont = function(node, context) {
  if (node.face != "")
    HTMLArea.xhtml_checkFont(context.editor, node, node.face);
  switch (node.size) {
  case "-2":
  case "+2":
  case "+3":
    break;
  default:
    node.removeAttribute("size");
  }
  if (context.toSave)
    HTMLArea.xhtml_convertFontToSpan(node, context);
  else
    HTMLArea.xhtml_removeTagWithNoAttribute(node, context);
};

// version 1.0
HTMLArea.xhtml_handleBig = function(node, context) {
  if (!context.toSave) {
    var newNode = node.ownerDocument.createElement('font');
    newNode.size = "+2";
    HTMLArea.xhtml_handleCssToFont(node, newNode, context);
    if (node.firstChild != null && node.firstChild.nodeType == 1
      && node.firstChild == node.lastChild
      && node.firstChild.tagName.toLowerCase() == 'font' && node.firstChild.size == "+2") {
      var child = node.firstChild;
      if (child.face != "" && newNode.face == "")
        newNode.face = child.face;
      if (child.color != "" && newNode.color == "")
        newNode.color = child.color;
      HTMLArea.xhtml_removeTag(child, context);
      newNode.size = "+3";
    }
    HTMLArea.xhtml_convertTo(node, context, newNode);
  }
};

// version 1.0
HTMLArea.xhtml_handleSmall = function(node, context) {
  if (!context.toSave) {
    var newNode = node.ownerDocument.createElement('font');
    newNode.size = "-2";
    HTMLArea.xhtml_handleCssToFont(node, newNode, context);
    HTMLArea.xhtml_convertTo(node, context, newNode);
  }
};

// version 1.1
HTMLArea.xhtml_handleImg = function(node, context) {
  if (node.alt == "")
    node.alt = "alt missing";
  if (node.width == 0)
    node.removeAttribute("width");
  if (node.height == 0)
    node.removeAttribute("height");
  MindTouch.normalizeLink(node, context.editor);
};

// version 1.0
HTMLArea.xhtml_handleAttribute = function(node, attrName, styleAttrName, reset) {
  if (typeof node[attrName] != "undefined" && node[attrName] != "") {
    node.style[styleAttrName] = node[attrName];
    node[attrName] = reset;
  }
};

HTMLArea.xhtml_noText_remove = function(node, context) {
  HTMLArea.xhtml_recordEvent(context, true, parentData.tag + " has text, but not allowed");
  node.data = '';
};

HTMLArea.xhtml_noText_insertDd = function(node, context) {
  HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('dd'));
};

HTMLArea.xhtml_noText_insertLi = function(node, context) {
  HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('li'));
};

HTMLArea.xhtml_noText_insertTr = function(node, context) {
  HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('tr'));
  HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('td'));
};

HTMLArea.xhtml_noText_insertTd = function(node, context) {
  HTMLArea.xhtml_surroundNode(node, node.ownerDocument.createElement('td'));
};

// version 1.1
HTMLArea.isNodeDeleted = function(node) {
  return !node || typeof node.parentNode == "unknown" || !node.parentNode || typeof node.parentNode.tagName == "undefined";
};

// version 1.0
HTMLArea.xhtml_validA = "abbr|acronym|big|br|del|strike|em|i|font|img|ins|u|small|span|strong|b|sub|sup|iframe";
HTMLArea.xhtml_validNonBlocks = "a|"+HTMLArea.xhtml_validA;
HTMLArea.xhtml_validBlocks = "address|blockquote|del|div|dl|h[1-6r]|ins|ol|p|pre|table|ul|iframe";
HTMLArea.xhtml_validNonTopBlocks = "address|blockquote|del|div|dl|hr|ins|ol|p|pre|table|ul|iframe";

// version 1.0
HTMLArea.xhtml_validAR = HTMLArea.compileRegex("^("+HTMLArea.xhtml_validA+")$","i");
HTMLArea.xhtml_validNonBlocksR = HTMLArea.compileRegex("^("+HTMLArea.xhtml_validNonBlocks+")$","i");
HTMLArea.xhtml_validBlocksR = HTMLArea.compileRegex("^("+HTMLArea.xhtml_validBlocks+")$","i");
HTMLArea.xhtml_validAllR = HTMLArea.compileRegex("^("+HTMLArea.xhtml_validNonBlocks+"|"+HTMLArea.xhtml_validNonTopBlocks+")$","i");
HTMLArea.xhtml_validPreR = HTMLArea.compileRegex("^(a|abbr|acronym|br|del|strike|em|i|font|ins|u|span|strong|b)$","i");
HTMLArea.xhtml_widthHeightAttrR = HTMLArea.compileRegex("^img|col|colgroup|table|pre|iframe$", "i");

// version 1.0
HTMLArea.xhtml_isBlock = function(node) {
  return node && node.nodeType == 1 && HTMLArea.xhtml_validBlocksR.test(node.tagName);
};

// version 1.2
HTMLArea.validTags = {
  // anchor for hypertext link, http://www.december.com/html/x1/element/a.html
  'a'         : {tag: 'a', validChild: HTMLArea.xhtml_validAR, invalid: HTMLArea.xhtml_invalidFormating, removeRecursive: true, handleElement: HTMLArea.xhtml_handleA},

  // embed small Java program (applet) in document, http://www.december.com/html/4loose/element/applet.html
  // MKS: not supported
  'applet'    : {tag: 'applet'},

  // encloses shortened phrase which represents whole, http://www.december.com/html/x1/element/abbr.html
  'abbr'      : {tag: 'abbr', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, removeRecursive: true},

  // encloses word formed from letters in a phrase, http://www.december.com/html/x1/element/acronym.html
  'acronym'   : {tag: 'acronym', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, removeRecursive: true},

  // address, signature, or byline, http://www.december.com/html/x1/element/address.html
  'address'   : {isBlock: true, tag: 'address',  validChild: HTMLArea.xhtml_validNonBlocksR},

  // define region on an imagemap which is a hypertext link, http://www.december.com/html/x1/element/area.html
  // MKS: not supported
  'area'      : {tag: 'area'},

  // bold text, http://www.december.com/html/x1/element/b.html
  // will be convered into 'strong'
  'b'         : {tag: 'b', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_convertToStrong},

  // base context document, http://www.december.com/html/x1/element/base.html
  // MKS: not supported
  'base'      : {tag: 'base', validChild: /^(head|body)$/i},

  // change default font in the document, http://www.december.com/html/4loose/element/basefont.html
  // MKS: not supported
  'basefont'  : {tag: 'basefont'},

  // I18N BiDi over-ride, http://www.december.com/html/x1/element/bdo.html
  // MKS: not supported
  'bdo'       : {tag: 'bdo'},

  // set larger text relative to surrounding text, http://www.december.com/html/x1/element/big.html
  'big'       : {tag: 'big', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, handleElement: HTMLArea.xhtml_handleBig},

  // quoted passage, http://www.december.com/html/x1/element/blockquote.html
  'blockquote': {isBlock: true, isBlockContainer: true, tag: 'blockquote', validChild: HTMLArea.xhtml_validBlocksR},

  // document body, http://www.december.com/html/x1/element/body.html
  'body'      : {isBlockContainer: true, tag: 'body', validChild: HTMLArea.xhtml_validBlocksR},

  // line break, http://www.december.com/html/x1/element/br.html
  'br'        : {tag: 'br', invalid: HTMLArea.xhtml_invalidFormating, noChildren: true, handleElement: HTMLArea.xhtml_handleBr},

  // push button, http://www.december.com/html/x1/element/button.html
  // MKS: not supported
  'button'    : {tag: 'button'},

  // descriptive phrase for table, http://www.december.com/html/x1/element/caption.html
  'caption'   : {tag: 'caption'},

  // MKS: not supported -> convert into <* style="text-align:center"></*>
  'center'    : {tag: 'center', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_convertToCenterStyle},

  // name or title of cited work, http://www.december.com/html/x1/element/cite.html
  // MKS: not supported, convert to <em></em>
  'cite'      : {tag: 'cite', invalid: HTMLArea.xhtml_convertToEm},

  // source (computer) code, http://www.december.com/html/x1/element/code.html
  // MKS: not supported, converted into 'code' style
  'code'      : {tag: 'code', invalid: HTMLArea.xhtml_convertToCode},

  // vertical alignment in cells, http://www.december.com/html/x1/element/col.html
  'col'       : {isTableElement: true, tag: 'col', noChildren: true},

  // vertical alignment in cells, http://www.december.com/html/x1/element/colgroup.html
  'colgroup'  : {isTableElement: true, tag: 'colgroup', validChild: /^col$/i, noText: HTMLArea.xhtml_noText_remove},

  // definition of term, http://www.december.com/html/x1/element/dd.html
  'dd'        : {isBlockContainer: true, tag: 'dd', validChild: HTMLArea.xhtml_validAllR},
  
  // inserted text, deleted text, http://www.december.com/html/x1/element/del.html
  'del'       : {isBlock: true, isBlockContainer: true, tag: 'del',  validChild: HTMLArea.xhtml_validAllR, handleElement: HTMLArea.xhtml_handleXmlDiff},

  // enclose term definition, http://www.december.com/html/x1/element/dfn.html
  // MKS: not supported, convert to <em></em>
  'dfn'       : {tag: 'dfn', invalid: HTMLArea.xhtml_convertToEm},

  // define parts of a document, http://www.december.com/html/x1/element/div.html
  'div'       : {isBlock: true, isBlockContainer: true, tag: 'div',  validChild: HTMLArea.xhtml_validAllR, handleElement: HTMLArea.xhtml_handleDiv},

  // directory list, http://www.december.com/html/4loose/element/dir.html
  // MKS: not supported, convert to <ol></ol>
  'dir'       : {tag: 'dir', invalid: HTMLArea.xhtml_convertToOl},

  // definition list or glossary, http://www.december.com/html/x1/element/dl.html
  'dl'        : {isBlock: true, tag: 'dl',  validChild: /^(dd|dt)$/i, noText: HTMLArea.xhtml_noText_insertDd},

  // term in definition list, http://www.december.com/html/x1/element/dt.html
  'dt'        : {tag: 'dt', validChild: HTMLArea.xhtml_validNonBlocksR},

  // emphasized phrase, http://www.december.com/html/x1/element/em.html
  'em'        : {tag: 'em', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, removeRecursive: true},

  // form control group, http://www.december.com/html/x1/element/fieldset.html
  // MKS: not supported
  'fieldset'  : {tag: 'fieldset'},

  // set font face, color, and size of text, http://www.december.com/html/4loose/element/font.html
  // MKS: not XHTML 1.1, convert to <span style="xxx"></span>
  'font'      : {tag: 'font', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, handleElement: HTMLArea.xhtml_handleFont},

  // fill-out or data-entry form, http://www.december.com/html/x1/element/form.html
  // MKS: not supported
  'form'      : {tag: 'form'},

  // primary heading, http://www.december.com/html/x1/element/h1.html
  'h1'        : {isBlock: true, tag: 'h1', invalid: HTMLArea.xhtml_invalidH, handleElement: HTMLArea.xhtml_handleH},

  // secondary heading, http://www.december.com/html/x1/element/h2.html
  'h2'        : {isBlock: true, tag: 'h2', invalid: HTMLArea.xhtml_invalidH, handleElement: HTMLArea.xhtml_handleH},

  // third-level heading, http://www.december.com/html/x1/element/h3.html
  'h3'        : {isBlock: true, tag: 'h3', invalid: HTMLArea.xhtml_invalidH, handleElement: HTMLArea.xhtml_handleH},

  // fourth-level heading, http://www.december.com/html/x1/element/h4.html
  'h4'        : {isBlock: true, tag: 'h4', invalid: HTMLArea.xhtml_invalidH, handleElement: HTMLArea.xhtml_handleH},

  // fifth-level heading, http://www.december.com/html/x1/element/h5.html
  'h5'        : {isBlock: true, tag: 'h5', invalid: HTMLArea.xhtml_invalidH, handleElement: HTMLArea.xhtml_handleH},

  // sixth-level heading, http://www.december.com/html/x1/element/h6.html
  'h6'        : {isBlock: true, tag: 'h6', invalid: HTMLArea.xhtml_invalidH, handleElement: HTMLArea.xhtml_handleH},

  // document head, http://www.december.com/html/x1/element/head.html
  // MKS: not supported
  'head'      : {tag: 'head'},

  // horizontal rule, http://www.december.com/html/x1/element/hr.html
  'hr'        : {isBlock: true, tag: 'hr', noChildren: true},

  // HTML document outer container, http://www.december.com/html/x1/element/html.html 
  // MKS: not supported
  'html'      : {tag: 'html', validChild: /^(head|body)$/i},

  // italic text, http://www.december.com/html/x1/element/i.html
  // MKS: not supported, convert into <em></em>
  'i'         : {tag: 'i', invalid: HTMLArea.xhtml_convertToEm},

  // MKS: not supported
  'iframe'    : {tag: 'iframe',isBlock: true, noChildren: true},

  // image; photo, icon, glyph, or illustration, http://www.december.com/html/x1/element/img.html
  'img'       : {tag: 'img', invalid: HTMLArea.xhtml_invalidFormating, noChildren: true, handleElement: HTMLArea.xhtml_handleImg},

  // form input datum, http://www.december.com/html/x1/element/input.html
  // MKS: not supported
  'input'     : {tag: 'input'},

  // inserted text, deleted text, http://www.december.com/html/x1/element/ins.html
  'ins'       : {isBlock: true, isBlockContainer: true, tag: 'ins',  validChild: HTMLArea.xhtml_validAllR, handleElement: HTMLArea.xhtml_handleXmlDiff},

  // searchable document input form, http://www.december.com/html/4loose/element/isindex.html
  // MKS: not supported
  'isindex'   : {tag: 'isindex'},

  // keyboard phrase (user input), http://www.december.com/html/x1/element/kbd.html
  // MKS: not supported, converted into 'code' style
  'kbd'       : {tag: 'kbd', invalid: HTMLArea.xhtml_convertToCode},

  // form field label text, http://www.december.com/html/x1/element/label.html
  // MKS: not supported
  'label'     : {tag: 'label'},

  // fieldset legend, http://www.december.com/html/x1/element/legend.html
  // MKS: not supported
  'legend'    : {tag: 'legend'},

  // list item, http://www.december.com/html/x1/element/li.html
  'li'        : {isBlockContainer: true, tag: 'li', validChild: HTMLArea.xhtml_validAllR, invalid: HTMLArea.xhtml_invalidListItem},

  // link from this document, http://www.december.com/html/x1/element/link.html
  // MKS: not supported
  'link'      : {tag: 'link'},

  // define client-side imagemap, http://www.december.com/html/x1/element/map.html
  // MKS: not supported
  'map'       : {tag: 'map'},

  // menu list, http://www.december.com/html/4loose/element/menu.html
  // MKS: not supported, convert to <ol></ol>
  'menu'      : {tag: 'menu', invalid: HTMLArea.xhtml_convertToOl},

  // generic meta-information about the document, http://www.december.com/html/x1/element/meta.html
  // MKS: not supported
  'meta'      : {tag: 'meta'},

  // option group, http://www.december.com/html/x1/element/optgroup.html
  // MKS: not supported
  'optgroup'  : {tag: 'optgroup'},

  // a selection option, http://www.december.com/html/x1/element/option.html
  // MKS: not supported
  'option'    : {tag: 'option'},

  // alternate content container for non script-based rendering, http://www.december.com/html/x1/element/noscript.html
  // MKS: not supported
  'noscript'  : {tag: 'noscript'},

  // generic embedded object, http://www.december.com/html/x1/element/object.html
  // MKS: not supported
  'object'    : {tag: 'object'},

  // ordered list, http://www.december.com/html/x1/element/ol.html
  'ol'        : {isBlock: true, tag: 'ol', validChild: /^li$/i, invalid: HTMLArea.xhtml_invalidList, noText: HTMLArea.xhtml_noText_insertLi, handleElement: HTMLArea.xhtml_handleList},

  // paragraph, http://www.december.com/html/x1/element/p.html
  'p'         : {isBlock: true, tag: 'p', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidP, handleElement: HTMLArea.xhtml_handleP},

  // named property value, http://www.december.com/html/x1/element/param.html
  // MKS: not supported
  'param'     : {tag: 'param'},

  // preformatted text, http://www.december.com/html/x1/element/pre.html
  // (no nested blocks, must not contain the img, object, big, small, sub, or sup elements.)
  'pre'       : {isBlock: true, tag: 'pre', validChild: HTMLArea.xhtml_validPreR},

  // short inline quotation, http://www.december.com/html/x1/element/q.html
  // MKS: not supported
  'q'         : {tag: 'q'},
  
  // cross out text, http://www.december.com/html/4loose/element/s.html
  // MKS: not supported, convert to <del></del>
  's'         : {tag: 's', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_convertToDel},

  // sample text or characters, http://www.december.com/html/x1/element/samp.html
  // MKS: not supported, converted into 'code' style
  'samp'      : {tag: 'samp', invalid: HTMLArea.xhtml_convertToCode},

  // script for user-document interaction, http://www.december.com/html/x1/element/script.html
  // MKS: not supported
  'script'    : {tag: 'script'},

  // selection of option(s), http://www.december.com/html/x1/element/select.html
  // MKS: not supported
  'select'    : {tag: 'select'},

  // set smaller text relative to surrounding text, http://www.december.com/html/x1/element/small.html
  'small'     : {tag: 'small', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, handleElement: HTMLArea.xhtml_handleSmall},

  // generic language/style container, http://www.december.com/html/x1/element/span.html
  'span'      : {tag: 'span', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, handleElement: HTMLArea.xhtml_handleSpan},

  // cross out text, http://www.december.com/html/4loose/element/strike.html
  // MKS: not supported, converted into <del></del>
  'strike'    : {tag: 'strike', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_convertToDel},

  // strong emphasis, http://www.december.com/html/x1/element/strong.html
  'strong'    : {tag: 'strong', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating, removeRecursive: true},

  // style information, http://www.december.com/html/x1/element/style.html
  // MKS: not supported (custom style sheets)
  'style'     : {tag: 'style'},

  // subscript text, http://www.december.com/html/x1/element/sub.html
  'sub'       : {tag: 'sub', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating},

  // superscript, http://www.december.com/html/x1/element/sup.html
  'sup'       : {tag: 'sup', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_invalidFormating},

  // tabular presentation of information, http://www.december.com/html/x1/element/table.html
  'table'     : {isTableElement: true, isBlock: true, tag: 'table', validChild: /^(caption|col|colgroup|thead|tbody|tr|tfoot)$/i, noText: HTMLArea.xhtml_noText_insertTr, handleElement: HTMLArea.xhtml_handleTable},

  // table body, http://www.december.com/html/x1/element/tbody.html
  'tbody'     : {isTableElement: true, tag: 'tbody', validChild: /^tr$/i, noText: HTMLArea.xhtml_noText_insertTr, handleElement: HTMLArea.xhtml_handleTR},

  // table data cell, http://www.december.com/html/x1/element/td.html
  'td'        : {isTableElement: true, isBlockContainer: true, tag: 'td', validChild: HTMLArea.xhtml_validAllR, handleElement: HTMLArea.xhtml_handleTD},

  // table footer, http://www.december.com/html/x1/element/tfoot.html
  'tfoot'     : {isTableElement: true, tag: 'tfoot', validChild: /^tr$/i, noText: HTMLArea.xhtml_noText_insertTr, handleElement: HTMLArea.xhtml_handleTR},

  // table header cell, http://www.december.com/html/x1/element/th.html
  'th'        : {isTableElement: true, isBlockContainer: true, tag: 'th', validChild: HTMLArea.xhtml_validAllR, handleElement: HTMLArea.xhtml_handleTD},
  
  // table header, http://www.december.com/html/x1/element/thead.html
  'thead'     : {isTableElement: true, tag: 'thead', validChild: /^tr$/i, noText: HTMLArea.xhtml_noText_insertTr, handleElement: HTMLArea.xhtml_handleTR},

  // title of document, http://www.december.com/html/x1/element/title.html
  // MKS: not supported
  'title'     : {tag: 'title'},

  // table row container, http://www.december.com/html/x1/element/tr.html
  'tr'        : {isTableElement: true, tag: 'tr', validChild: /^(td|th)$/i, noText: HTMLArea.xhtml_noText_insertTd, handleElement: HTMLArea.xhtml_handleTR},

  // an area for text input, http://www.december.com/html/x1/element/textarea.html
  // MKS: not supported
  'textarea'  : {tag: 'textarea'},

  // typewriter text, http://www.december.com/html/x1/element/tt.html
  // MKS: not supported, converted into 'code' style
  'tt'        : {tag: 'tt', invalid: HTMLArea.xhtml_convertToCode},
  
  // underline text, http://www.december.com/html/4loose/element/u.html
  // MKS: not supported, converted into <ins></ins>
  'u'         : {tag: 'u', validChild: HTMLArea.xhtml_validNonBlocksR, invalid: HTMLArea.xhtml_convertToIns},

  // unordered list, http://www.december.com/html/x1/element/ul.html
  'ul'        : {isBlock: true, tag: 'ul', validChild: /^li$/i, invalid: HTMLArea.xhtml_invalidList, noText: HTMLArea.xhtml_noText_insertLi, handleElement: HTMLArea.xhtml_handleList},

  // variable phrase or substitute, http://www.december.com/html/x1/element/var.html
  // MKS: not supported, convert to <em></em>
  'var'       : {tag: 'var', invalid: HTMLArea.xhtml_convertToEm}
};

HTMLArea.prototype.getXHTML = function() {
  var doc = this._doc.documentElement.cloneNode(true);
  var body = doc.getElementsByTagName('body')[0];
  HTMLArea.makeXHTML(body, this.makeXHTMLContext(true));
  var html = HTMLArea.getHTML(body, false, this);
  delete doc;
  return html;
};

// version 1.0
HTMLArea.prototype.makeXHTMLContext = function(toSave) {
  return {editor: this, toSave: toSave, invalid: 0, changed: 0, debugLog: "", _starttime: new Date().getTime(), _nodes: 0};
};

// version 1.0
HTMLArea.xhtml_recordEvent = function(context,isInvalid,text) {
  if (isInvalid) ++context.invalid;
  else ++context.changed;
  context.debugLog += (isInvalid ? "INVALID" : "CHANGED") + ": " + text + "\n";
};

// version 1.3
HTMLArea.xhtml_textUnescapeR = HTMLArea.compileRegex("%u([0-9A-F]{4})", "gi");
HTMLArea.makeXHTML = function(node, context, parent, parentData) {
  var top = !parent;
//  var start;
//  if (top) start = HTMLArea.getOuterHTML(node);

  ++context._nodes;
  if (node == null) node = context.editor._doc.body;
  if (!parent) { parent = node.parentNode; }
  else if (parent != node.parentNode) {
    parent = node.parentNode;
    parentData = null;
  }
  if (!parentData && parent)
    parentData = HTMLArea.validTags[parent.tagName.toLowerCase()];

  if (parentData && parentData.noChildren) {
    parent.removeChild(node);
    return;
  }

  switch (node.nodeType) {
    case 2: // Node.ATTRIBUTE_NODE
    case 7: // Node.PROCESSING_INSTRUCTION_NODE
    case 10:// Node.DOCUMENT_TYPE_NODE
    case 12:// Node.NOTATION_NODE
      break;

    case 8: // Node.COMMENT_NODE
      break;

    case 4: // Node.CDATA_SECTION_NODE
    case 5: // Node.ENTITY_REFERENCE_NODE
    case 6: // Node.ENTITY_NODE
      break;
      
    case 9: // Node.DOCUMENT_NODE
    case 11:// Node.DOCUMENT_FRAGMENT_NODE
      for (var i = node.firstChild; i; i = i.nextSibling)
        HTMLArea.makeXHTML(i, context, node);
      break;

    case 3: // Node.TEXT_NODE
      // handle %uXXXX form in text coming from Word
      var d = node.data;
      var nd = d.replace(HTMLArea.xhtml_textUnescapeR, function(s,b){return unescape(s);});
      if (d.length != nd.length) {
        HTMLArea.xhtml_recordEvent(context, false, "%uXXXX text decoded");
        node.data = nd;
      }

      if (parentData) {
        var l = node.data.htmlTrim().length;
        if (/^h[1-6]$/.test(parentData.tag)) {
          nd = nd.htmlTrim();
          if (nd.length != node.data.length)
            node.data = node.nextSibling ? nd + " " : nd;
        }

        if (l != 0 && (parentData.tag == 'body' || parentData.tag == 'blockquote')) {
          HTMLArea.xhtml_recordEvent(context, true, "text in 'body/blockquote' promoted");
          HTMLArea.xhtml_invalidFormating(node, context);
        }

        if (parentData.noText && l != 0) {
          parentData.noText(node, context);
        }
      }
      break;

    case 1: // Node.ELEMENT_NODE
    {
      var tagName = node.tagName.toLowerCase();
      var tagData = HTMLArea.validTags[tagName];
      var contextTag = tagName;
      var contextExists = typeof context[contextTag] != "undefined";
      if (!contextExists)
        context[contextTag] = 1;
      else
        ++context[contextTag];

      // handle all the children
      var children = HTMLArea.xhtml_getChildren(node);
      HTMLArea.xhtml_handleChildren(children, context);

      if (!contextExists)
        delete context[contextTag];
      else
        --context[contextTag];

      if (HTMLArea.isNodeDeleted(node))
        return;

      if (!tagData || (contextExists && !tagData.isBlock && tagData.removeRecursive)) {
        if (tagData)
          HTMLArea.xhtml_recordEvent(context, false, tagName + " recursive, removed");
        else
          HTMLArea.xhtml_recordEvent(context, true, tagName + " invalid, removed");
        // remove recusive or invalid tags
        HTMLArea.xhtml_removeTag(node, context);
        return;
      }

      if (node.firstChild == null && !tagData.noChildren && tagName != 'body') {
        if ((tagData.isBlock || tagName == 'td') && tagName != 'ul' && tagName != 'div') {
          if (context.toSave || !HTMLArea.is_ie) {
            node.appendChild(node.ownerDocument.createElement('br'));
            HTMLArea.xhtml_recordEvent(context, false, tagName + " empty, &nbsp; added");
          }
        } else {
          // remove empty format tags
          HTMLArea.xhtml_removeTag(node, context);
          HTMLArea.xhtml_recordEvent(context, false, tagName + " empty, removed");
          return;
        }
      }

      if (node.parentNode != parent) {
        parent = node.parentNode;
        parentData = HTMLArea.validTags[parent.tagName.toLowerCase()];
      }
      if (!parentData || !parentData.validChild || !parentData.validChild.test(node.tagName)) {
        HTMLArea.xhtml_recordEvent(context, true, (parentData ? parentData.tag : "???") + " does not allow " + tagName + ", " + (tagData.invalid ? "handled" : "removed"));
        // handle invalid tag's

        if (HTMLArea.isNodeDeleted(node))
          return;

        if (tagData.invalid)
          node = tagData.invalid(node, context);
        else if (tagData.isBlock)
          node = HTMLArea.xhtml_invalidBlock(node, context);
        else
          node = HTMLArea.xhtml_invalidNonBlock(node, context);
      }

      if (HTMLArea.isNodeDeleted(node))
        return;

      if (node.className.length > 0)
        HTMLArea.xhtml_handleClassName(node, context);
      if (context.toSave && tagName != "col" && tagName != "img" && tagName != "table" && tagName != "div")
        HTMLArea.xhtml_handleAttribute(node, "align", "textAlign", "");
      HTMLArea.xhtml_handleAttribute(node, "bgColor", "backgroundColor", "");
      if (tagName != "table")
        HTMLArea.xhtml_handleAttribute(node, "border", "border", null);
      if (!HTMLArea.xhtml_widthHeightAttrR.test(tagName)) {
        HTMLArea.xhtml_handleAttribute(node, "width", "width", "");
        HTMLArea.xhtml_handleAttribute(node, "height", "height", "");
      }
      if (tagName != "body" && node.id != "")
        node.removeAttribute('id');
      if (node.style.cssText.length > 0) {
        HTMLArea.xhtml_handleCssStyle(node, context);
        if (HTMLArea.isNodeDeleted(node))
          return;
      }
      node.onmouseover = node.onmouseout = null;
      if (tagData.handleElement)
        tagData.handleElement(node, context);
      break;
    }
  }

  if (top) {
//    var end = HTMLArea.getOuterHTML(node);
//    alert('start: ' + start.replace(/[\x10\x13]/g,'') + '\nend: ' + end.replace(/[\x10\x13]/g,''));
    context._endtime = new Date().getTime();
    context._timeDiff = context._endtime - context._starttime;
  }
  /* >MT: DEBUG
  if (top && context.invalid + context.changed > 0)
    alert(context.debugLog);
  /* <MT: DEBUG */
};

/*
 * MindTouch Deki - a commercial grade open source wiki
 *  derived from MediaWiki (www.mediawiki.org)
 * Copyright (C) 2006 MindTouch, Inc.
 * http://www.mindtouch.com/  oss@mindtouch.com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * http://www.gnu.org/copyleft/gpl.html
 */

// MT: adding showbutton
HTMLArea.Config.MTregisterButton = function(self, id, tooltip, image, textMode, action, context, showbutton) {
  var the_id;
  if (typeof id == "string") {
    the_id = id;
  } else if (typeof id == "object") {
    the_id = id.id;
  } else {
    alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
    return false;
  }
  // check for existing id
  if (typeof self.customSelects[the_id] != "undefined") {
    // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
  }
  if (typeof self.btnList[the_id] != "undefined") {
    // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
  }
  switch (typeof id) {
    // >MT: adding showbutton
    case "string": self.btnList[id] = [ tooltip, image, textMode, action, context, showbutton ]; break;
    case "object": self.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context, id.showbutton ]; break;
    // <MT
  }
};

// MT: adding this._readOnly
HTMLArea.prototype._createToolbar = function () {
  var editor = this;	// to access this in nested functions
  var toolbar = document.createElement("div");

  // ._toolbar is for legacy, ._toolBar is better thanks.
  this._toolBar = this._toolbar = toolbar;
  toolbar.className = "toolbar";
  toolbar.unselectable = "1";
  // >MT: add this._readOnly
  this._readOnly = false;
  if (typeof prependHTMLToToolbar != "undefined") {
    var prependHTML = prependHTMLToToolbar(this);
    if (prependHTML == null)
      this._readOnly = true;
    else
      toolbar.innerHTML = prependHTML + toolbar.innerHTML;
  }
  // <MT
  
  HTMLArea.freeLater(this, '_toolBar');
  HTMLArea.freeLater(this, '_toolbar');
  
  var tb_row = null;
  var tb_objects = new Object();
  this._toolbarObjects = tb_objects;
  this._createToolbar1(editor, toolbar, tb_objects);
  this._htmlArea.appendChild(toolbar);

  return toolbar;
};


// >MT: update ._createToolbar1 for 2-D layout
HTMLArea.prototype._createToolbar1 = function(editor, toolbar, tb_objects) {

  // each toolbar control is added to a "line" (lineTable, lineRow)
  // a section (secTable/secBody) is a table with 1 or 2 lines
  // the toolbar is a table (tbTable) of sections and separators
  //   (horizontal layout)

  var secBody;
  var lineRow;
  
  var tbTable = document.createElement("table");
  tbTable.style.width = "auto";
  tbTable.border = "0px";
  tbTable.cellSpacing = "0px";
  tbTable.cellPadding = "0px";
  toolbar.appendChild(tbTable);
  var tbBody = document.createElement("tbody");
  tbTable.appendChild(tbBody);
  var tbRow = document.createElement("tr");
  tbBody.appendChild(tbRow);

  // creates a new section in the toolbar
  function newSection() {
    var tbCell = document.createElement("td");
    tbRow.appendChild(tbCell);

    var secTable = document.createElement("table");
    secTable.style.width = "auto";
    secTable.border = "0px";
    secTable.cellSpacing = "0px";
    secTable.cellPadding = "0px";
    tbCell.appendChild(secTable);
    secBody = document.createElement("tbody");
    secTable.appendChild(secBody);

    newLine();
  }; // END of function: newSection

  // creates a new line in the section
  function newLine() {
    var secRow = document.createElement("tr");
    secBody.appendChild(secRow);
    var secCell = document.createElement("td");
    secRow.appendChild(secCell);

    var lineTable = document.createElement("table");
    lineTable.className = "toolbarRow";
    secCell.appendChild(lineTable);
    var lineBody = document.createElement("tbody");
    lineTable.appendChild(lineBody);
    lineRow = document.createElement("tr");
    lineBody.appendChild(lineRow);
  }; // END of function: newLine

  // add the initial section
  newSection();

  // adds a separator to the toolbar, and starts a new section
  function addSeparator() {
    var tbCell = document.createElement("td");
    el = document.createElement("div");
    el.className = "separator";
    tbCell.width = "15px";
    tbCell.align = "center";
    tbCell.appendChild(el);
    tbRow.appendChild(tbCell);

    newSection();
  }; // END of function: addSeparator

  // updates the state of a toolbar element.  This function is member of
  // a toolbar element object (unnamed objects created by createButton or
  // createSelect functions below).
  function setButtonStatus(id, newval) {
    var oldval = this[id];
    var el = this.element;
    if (oldval != newval) {
      switch (id) {
        case "enabled":
        if (newval) {
          HTMLArea._removeClass(el, "buttonDisabled");
          el.disabled = false;
        } else {
          HTMLArea._addClass(el, "buttonDisabled");
          el.disabled = true;
        }
        break;
        case "active":
        if (newval) {
          HTMLArea._addClass(el, "buttonPressed");
        } else {
          HTMLArea._removeClass(el, "buttonPressed");
        }
        break;
      }
      this[id] = newval;
    }
  }; // END of function: setButtonStatus

  // this function will handle creation of combo boxes.  Receives as
  // parameter the name of a button as defined in the toolBar config.
  // This function is called from createButton, above, if the given "txt"
  // doesn't match a button.
  function createSelect(txt) {
    var options = null;
    var el = null;
    var cmd = null;
    var customSelects = editor.config.customSelects;
    var context = null;
    var tooltip = "";
    switch (txt) {
      case "fontsize":
      case "fontname":
      case "formatblock":
      // >MT: fontstyles
      case "fontstyles":
      // <MT
      // the following line retrieves the correct
      // configuration option because the variable name
      // inside the Config object is named the same as the
      // button/select in the toolbar.  For instance, if txt
      // == "formatblock" we retrieve config.formatblock (or
      // a different way to write it in JS is
      // config["formatblock"].
      options = editor.config[txt];
      cmd = txt;
      break;
      default:
      // try to fetch it from the list of registered selects
      cmd = txt;
      var dropdown = customSelects[cmd];
      if (typeof dropdown != "undefined") {
        options = dropdown.options;
        context = dropdown.context;
        if (typeof dropdown.tooltip != "undefined") {
          tooltip = dropdown.tooltip;
        }
      } else {
        alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
      }
      break;
    }
    if (options) {
      el = document.createElement("select");
      el.title = tooltip;
      var obj = {
        name  : txt,  // field name
        element : el,   // the UI element (SELECT)
        enabled : true,   // is it enabled?
        text  : false,  // enabled in text mode?
        cmd   : cmd,  // command ID
        state   : setButtonStatus, // for changing state
        context : context
      };

      HTMLArea.freeLater(obj);

      tb_objects[txt] = obj;

      for (var i in options) {
        var op = document.createElement("option");
        op.innerHTML = HTMLArea._lc(i);
        op.value = options[i];
        el.appendChild(op);
      }
      HTMLArea._addEvent(el, "change", function () {
        editor._comboSelected(el, txt);
      });
    }
    return el;
  }; // END of function: createSelect

  // appends a new button to toolbar
  function createButton(txt) {
    // >MT: handle 'link:' and 'label:'
    if (txt.substring(0, 5) == "link:") {
      var txtLbl = txt.substring(5).split('|');
      var el = document.createElement("a");
      el.href = "javascript:void(0)";
      el.txt = txtLbl[0];
      el.onclick = function() {
        var obj = tb_objects[this.txt]
        obj.cmd(editor, obj.name, obj);
        return false;
      };
      el.innerHTML = txtLbl[1];
      return el;
    }
    if (txt.substring(0, 6) == "label:") {
      var el = document.createElement("span");
      el.innerHTML = txt.substring(6);
      return el;
    }
    // <MT
    // >MT: handle 'showbutton'
    var btn = editor.config.btnList[txt];
    if (btn && btn[5] == false) {
      return -1;
    }
    // <MT
    // the element that will be created
    var el = null;
    var btn = null;
    switch (txt) {
      case "separator":
      addSeparator();
      return false;
      case "space":
      //el = document.createElement("div");
      //el.className = "space";
      //break;
      return false;
      case "linebreak":
      newLine();
      return false;
      case "textindicator":
      el = document.createElement("div");
      el.appendChild(document.createTextNode("A"));
      el.className = "indicator";
      el.title = HTMLArea._lc("Current style");
      var obj = {
        name	: txt, // the button name (i.e. 'bold')
        element : el, // the UI element (DIV)
        enabled : true, // is it enabled?
        active	: false, // is it pressed?
        text	: false, // enabled in text mode?
        cmd	  : "textindicator", // the command ID
        state	: setButtonStatus // for changing state
      };

      HTMLArea.freeLater(obj);

      tb_objects[txt] = obj;
      break;
      default:
      btn = editor.config.btnList[txt];
    }
    if (!el && btn) {
      var btnLabel = "";
      if (btn[0] == _lang_tooltip_save)
        btnLabel = _lang_label_save;
      else if (btn[0] == _lang_tooltip_cancel)
        btnLabel = _lang_label_cancel;
		
      el = document.createElement("a");
      el.id = 'ebi_' + btn[0].replace(/\s*/g,'');  // Editor Button Id
      el.style.display = 'block';
      el.href = 'javascript:void(0)';
      el.style.textDecoration = 'none';
      el.title = btn[0];
      el.className = "buttonMT";
      if (btnLabel == "")
        el.style.width = "18px";
      else
        el.style.width = "60px";
      el.style.height = "18px";

      // let's just pretend we have a button object, and
      // assign all the needed information to it.
      var obj = {
        name	: txt, // the button name (i.e. 'bold')
        element : el, // the UI element (DIV)
        enabled : true, // is it enabled?
        active	: false, // is it pressed?
        text	: btn[2], // enabled in text mode?
        cmd	  : btn[3], // the command ID
        state	: setButtonStatus, // for changing state
        context : btn[4] || null // enabled in a certain context?
      };

      HTMLArea.freeLater(obj);

      tb_objects[txt] = obj;
      // handlers to emulate nice flat toolbar buttons
      HTMLArea._addEvent(el, "mouseout", function () {
        if (obj.enabled) with (HTMLArea) {
          //_removeClass(el, "buttonHover");
          _removeClass(el, "buttonActive");
          (obj.active) && _addClass(el, "buttonPressed");
        }
      });

      HTMLArea._addEvent(el, "mousedown", function (ev) {
        if (obj.enabled) with (HTMLArea) {
          _addClass(el, "buttonActive");
          _removeClass(el, "buttonPressed");
          _stopEvent(is_ie ? window.event : ev);
        }
      });
      // when clicked, do the following:
      HTMLArea._addEvent(el, "click", function (ev) {
        if (obj.enabled) with (HTMLArea) {
          _removeClass(el, "buttonActive");
          //_removeClass(el, "buttonHover");
          if(HTMLArea.is_gecko)
          {
            editor.activateEditor();
          }
          obj.cmd(editor, obj.name, obj);
          _stopEvent(is_ie ? window.event : ev);
        }
      });
		
      //if (btnLabel == '') {
	      var i_contain = HTMLArea.makeBtnImg(btn[1]);
	      var img = i_contain.firstChild;
	      el.appendChild(i_contain);
      //}
      
      if (btnLabel != "") {
        var labelSpan = document.createElement("span");
        labelSpan.innerHTML = btnLabel;
		labelSpan.className = "buttonLabelMT";
		if (!HTMLArea.is_ie) {
		  labelSpan.style.left = "21px";
		  labelSpan.style.top = "-16px";
		}
        el.appendChild(labelSpan);
      }

      obj.imgel = img;
      obj.swapImage = function(newimg)
      {
        if(typeof newimg != 'string')
        {
          img.src = newimg[0];
          img.style.position = 'relative';
          img.style.top  = newimg[2] ? ('-' + (18 * (newimg[2] + 1)) + 'px') : '-18px';
          img.style.left = newimg[1] ? ('-' + (18 * (newimg[1] + 1)) + 'px') : '-18px';
        }
        else
        {
          obj.imgel.src = newimg;
          img.style.top = '0px';
          img.style.left = '0px';
        }
      }

    } else if (!el) {
      el = createSelect(txt);
    }

    return el;
  };

  var first = true;
  for (var i = 0; i < this.config.toolbar.length; ++i) {
    if (!first) {
      // createButton("linebreak");
    } else {
      first = false;
    }
    if(this.config.toolbar[i] == null) this.config.toolbar[i] = ['separator'];
    var group = this.config.toolbar[i];

    for (var j = 0; j < group.length; ++j)
    {
      var code = group[j];
      if (/^([IT])\[(.*?)\]/.test(code))
      {
        // special case, create text label
        var l7ed = RegExp.$1 == "I"; // localized?
        var label = RegExp.$2;
        if (l7ed) {
          label = HTMLArea._lc(label);
        }
        var tb_cell = document.createElement("td");
        lineRow.appendChild(tb_cell);
        tb_cell.className = "label";
        tb_cell.innerHTML = label;
      }
      else if(typeof code != 'function')
      {
        var tb_element = createButton(code);

        // >MT: handle hidden buttons
        if (tb_element && tb_element != -1)
        // <MT
        {
          var tb_cell = document.createElement("td");
          tb_cell.className = 'toolbarElement';
          lineRow.appendChild(tb_cell);
          tb_cell.appendChild(tb_element);
        }
        else if (tb_element == null)
        {
          alert("FIXME: Unknown toolbar item: " + code);
        }
      }
    }
  }

  return toolbar;
};
// <MT


// MT: add many more cases to _wordClean
HTMLArea.prototype._wordClean = function() {
  // _wordClean: main
  var startTag, endTag;
  var t = this._doc.body.innerHTML;
  HTMLArea.getElementsByTagNameF(this._doc.body,'span',function(el) {
    if (el.className == 'pasteTag start')
      startTag = el;
    if (el.className == 'pasteTag end')
      endTag = el;
  });
  var root = this._doc.body;
  if (startTag || endTag) {
    var r = this._createRange();
    if (startTag) {
      MindTouch.selectNode(r, startTag);
      this.deleteContents(r);
    }
    var r2 = MindTouch.cloneRange(r);
    if (endTag) {
      MindTouch.selectNode(r2, endTag);
      this.deleteContents(r2);
    }
    if (endTag && startTag) {
      MindTouch.setEndToStart(r, r2);
      root = MindTouch.commonAncestorContainer(r);
    }
    if (!root) root = this._doc.body;
  }

  HTMLArea.makeXHTML(root, this.makeXHTMLContext(false));
  // >MT: causing Mac FF to clean editor after load
  this.updateToolbar();
  // <MT
};

// MT: use an undo engine
HTMLArea.prototype._undoTakeSnapshot = function() {
  ++this._undoPos;
  if (this._undoPos >= this.config.undoSteps) {
    // remove the first element
    this._undoQueue.shift();
    --this._undoPos;
  }
  // use the fasted method (getInnerHTML);
  var take = typeof this._textArea != "undefined";
  var html = this.getInnerHTML();
  var oldState;
  if (this._undoPos > 0) {
    oldState = this._undoQueue[this._undoPos - 1];
    take = (oldState.html != html);
  }
  if (take) {
    if (oldState != null)
      this._storeUndoCursor(oldState);
    var state = {
        html:       html,
        scrollTop:  0,
        scrollLeft: 0,
        curNode:    "/"
    };
    this._undoQueue[this._undoPos] = state;
  } else {
    this._undoPos--;
  }
};

// MT: use an undo engine
HTMLArea.prototype.undo = function() {
  if (this._undoPos > 0) {
    this._storeUndoCursor(this._undoQueue[this._undoPos]);
    if (!this.restoreState(this._undoQueue[--this._undoPos]))
      ++this._undoPos;
  }
};

// MT: use an undo engine
HTMLArea.prototype.redo = function() {
  if (this._undoPos < this._undoQueue.length - 1) {
    this._storeUndoCursor(this._undoQueue[this._undoPos]);
    if (!this.restoreState(this._undoQueue[++this._undoPos]))
      --this._undoPos;
  }
};


// Returns the deepest ancestor of the selection that is of the current type
HTMLArea.prototype._getFirstAncestorF = function(sel, fn)
{
  var prnt = this._activeElement(sel);
  if(prnt == null)
  {
    try
    {
      prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer);
    }
    catch(e)
    {
      return null;
    }
  }

  while(prnt)
  {
    if (fn(prnt))
      return prnt;
    if (/^(body|table)$/i.test(prnt.tagName))
      break;
    prnt = prnt.parentNode;
  }

  return null;
}

// Returns the deepest ancestor of the selection that is of the current type
HTMLArea.prototype._getFirstAncestor = function(sel, types) {
  if(typeof types == 'string')
    types = [types];

  return this._getFirstAncestorF(sel,function(el) {
    if(el.nodeType == 1)
    {
      if(types == null)
        return true;
      if(types.contains(el.tagName.toLowerCase()))
        return true;
    }
    return false;
  });
};

// Called when the user clicks on "InsertImage" button.  If an image is already
// there, it will just modify it's properties.
HTMLArea.prototype._insertImageMT = function(image) {
    var editor = this;	// for nested functions
    var outparam = null;
    if (typeof image == "undefined") {
        image = this.getParentElement();
        if (image && !/^img$/i.test(image.tagName))
        image = null;
    }
    var stripState = this.config.stripBaseHref;
    this.config.stripBaseHref = true;
    outparam = image ? {
        f_base       : editor.config.baseHref,
        f_url        : MindTouch.extractName(editor.stripBaseURL(image.src)),
        name         : image.alt,
        ialign       : image.getAttribute('ialign'),
        size         : image.getAttribute('size_type'),
        captionText  : image.getAttribute('caption_text') ? image.getAttribute('caption_text') : '',
        resizeHeight : parseInt(image.style.height),
        resizeWidth  : parseInt(image.style.width)
    } : {
        f_base       : editor.config.baseHref,
        f_url        : ""
    };
    this.config.stripBaseHref = stripState;

    function doInsertImage(param) {
        if (!param) {	// user must have pressed Cancel
            return false;
        }
        var img = image;
        param.f_url = param.f_url.replace(/ /g, '_');
        if (!img) {
            var sel = editor._getSelection();
            var range = editor._createRange(sel);
            editor._doc.execCommand("insertimage", false, param.f_url);
            range = editor._createRange(sel);
            img = editor.findTagInRangeF(sel, range, function(el) {
                return /^img$/i.test(el.tagName) && !el.getAttribute('size_type');
            });
            if (!img)
                img = editor.getParentElement();
            while (img && !/^img$/i.test(img.tagName))
                img = img.parentNode;
            if (!(img && /^img$/i.test(img.tagName)))
                return false;
        } else {
            img.src = param.f_url;
        }
        img.setAttribute('ialign', param.ialign);
        img.setAttribute('size_type', param.size);
        MindTouch.setLink(editor, img, param.f_url);

        switch (param.size) {
        case 'direct':
            if (!MindTouch.isLinkExternal(param.f_url))
                img.src += "&size=full";
            img.style.height = param.imageHeight ? param.imageHeight + "px" : "auto";
            img.style.width = param.imageWidth ? param.imageWidth + "px" : "auto";
            break;
        case 'resize':
            if (!MindTouch.isLinkExternal(param.f_url) && (parseInt(param.resizeHeight) > 160 || parseInt(param.resizeWidth) > 160))
                img.src += "&size=full";
            img.style.height = param.resizeHeight;
            img.style.width = param.resizeWidth;
            break;
        case 'thumb':
        case 'caption':
            if (param.imageHeight && param.imageWidth) {
                if (param.imageWidth > param.imageHeight) {
                    if (param.imageWidth > 160) {
                        img.style.width = 160;
                        img.style.height = (param.imageHeight / param.imageWidth) * 160;
                    } else {
                        img.style.width = param.imageWidth;
                        img.style.height = param.imageHeight;
                    }
                }
                else {
                    if (param.imageHeight > 160) {
                        img.style.width = (param.imageWidth / param.imageHeight) * 160;
                        img.style.height = 160;
                    } else {
                        img.style.width = param.imageWidth;
                        img.style.height = param.imageHeight;
                    }
                }
            } else {
                img.style.width = "auto";
                img.style.height = "auto";
            }
            img.setAttribute('caption_text', param.captionText);
            break;
        }
    };

    this._popupDialog(700, 450,'/editor/popups/insert_image.php?href=' + outparam.f_url.utf8URL() 
        + '&cntxtID=' + editor.contextTopicID + '&cntxt=' + editor.contextTopic.utf8URL() + '&userName=' + _userName, doInsertImage, outparam);
};

HTMLArea.prototype.findTagInRangeF = function(sel, range, fn) {
   if (HTMLArea.is_ie) {
     var el = this.getParentElement(sel,range);
     if (fn(el))
       return el;
     return HTMLArea.findInChildren(el, fn);
   }
   var el = range.startContainer;
   var res = {found:null};
   HTMLArea.findTagInRangeRF(range, el, fn, res);
   if (!res.found && el.nodeType == 3) {
     if (el.nodeType == 3)
       el = el.parentNode;
     HTMLArea.findTagInRangeRF(range, el, fn, res);
   }
   return res.found;
};

HTMLArea.findInChildren = function(node, func) {
 var child = node.firstChild;
 while (child != null) {
   var next = child.nextSibling;
   if (func(child))
     return child;
   var ret = HTMLArea.findInChildren(child, func);
   if (ret)
     return ret;
   child = next;
 }
 return null;
};

HTMLArea.findTagInRangeRF = function(range, el, fn, res) {
   while (el) {
     var next = el.nextSibling;
     if (fn(el)) {
       res.found = el;
       return true;
     }
     var stop = HTMLArea.findTagInRangeRF(range, el.firstChild, fn, res);
     if (stop)
       return stop;
     if (el == range.endContainer)
       return true;
     el = next;
   }
   return false;
};

// MT: special handling for link creation
HTMLArea.prototype._createLinkMT = function(nopopup) {
  var editor = this;
  var outparam = null;

  nopopup = (typeof nopopup != "undefined" && nopopup == "no-popup");

  function doCreateLink(param) {
    editor.focusEditor();
    if (!param)
      return false;
    var a = link;
    var text = param.f_text.trim();
    var href = param.f_href.trim();
    if (text == "")
      text = href;
    if (!a) {
      if (text == "" || href == "")
        return false;
      var sel = editor._getSelection();
      var range = editor._createRange(sel);
      if (!MindTouch.isLinkExternal(href) && href != "")
        href = MindTouch.mksInternalPrefix + href;
      if (editor.adjustSelection(sel,range).length == 0)
        editor.insertHTML("<a href='" + MindTouch.hrefEncode(href) + "'>" + HTMLArea.htmlEncode(text) + "</a>");
      else
        editor._doc.execCommand("createlink", false, MindTouch.hrefEncode(href));
      range = editor._createRange(sel);
      a = editor.findTagInRangeF(sel, range, function(el) { return /^a$/i.test(el.tagName) && !el.title; });
      if (!a)
        a = editor.getParentElement();
      while (a && !/^a$/i.test(a.tagName))
        a = a.parentNode;
      if (!(a && /^a$/i.test(a.tagName)))
        return false;
      MindTouch.setLink(editor, a, href, text);
      MindTouch.normalizeLink(a, editor);
    }
    else {
      while (a && !/^a$/i.test(a.tagName))
        a = a.parentNode;
      if (!(a && /^a$/i.test(a.tagName)))
        return false;
      editor.selectNodeContents(a);
      if (href == "") {
        editor._doc.execCommand("unlink", false, null);
        editor.updateToolbar();
        return false;
      }
      else {
        if (nopopup) {
          var isE = MindTouch.isLinkExternal(href);
          var testHRef = href;
          if (!isE && testHRef.indexOf(MindTouch.mksInternalPrefix) != 0)
            testHRef = MindTouch.mksInternalPrefix + testHRef;
          if (a.href == MindTouch.hrefEncode(testHRef)) {
            if (isE)
              text = a.innerHTML == '#' ? a.href : text = '#';
            else
              href = MindTouch.mksInternalPrefix + text;
          }
        }
        MindTouch.setLink(editor, a, href, text);
        MindTouch.normalizeLink(a, editor);
      }
    }
    editor.selectNodeContents(a);
    editor.updateToolbar();
  };

  link = this.getParentElement();
  if (link) {
    while (link && !/^a$/i.test(link.tagName))
      link = link.parentNode;
    if (link && !/^a$/i.test(link.tagName))
      link = null;
  }
  if (!link) {
    var sel = editor.getSelectedText();
    sel = sel ? sel.trim() : '';
    outparam = {
      f_href         : !nopopup ? "./" + sel : sel,
      f_text         : sel,
      contextTopic   : editor.contextTopic,
      contextTopicID : editor.contextTopicID,
      userName       : _userName
    };
  } else {
    if (link.className == 'site')
      return;
    outparam = {
      f_href         : link.href,
      f_text         : HTMLArea.getInnerText(link),
      contextTopic   : editor.contextTopic,
      contextTopicID : editor.contextTopicID,
      userName       : _userName
    };
  }
  outparam.f_href = MindTouch.hrefDecode(outparam.f_href);
  if (outparam.f_href.indexOf(MindTouch.mksInternalPrefix) == 0)
    outparam.f_href = outparam.f_href.substr(MindTouch.mksInternalPrefix.length);
  if (!nopopup)
    this._popupDialog(600, 451, '/editor/popups/link.php?href=' + outparam.f_href.utf8URL()
        + '&cntxtID=' + editor.contextTopicID + '&cntxt=' + editor.contextTopic.utf8URL() + '&userName=' + _userName, doCreateLink, outparam);
  else
    doCreateLink(outparam);
};

HTMLArea.prototype.dropdownCommand = function(command, arg) {
	EditorMenu.hide();
	if (command == 'forecolor' || command == 'backcolor')
		this._onColorChange(command, arg);
	else if (command == 'fontsize')
		updateFontSizes(this, command, arg);
	else if (arg == '')
		this.execCommand(command);
	else
		this.execCommand(command, false, arg);
};

// htmlArea v3.0 - Copyright (c) 2003-2004 interactivetools.com, inc.
// This copyright notice MUST stay intact for use (see license.txt).
//
// Portions (c) dynarch.com, 2003-2004
//
// A free WYSIWYG editor replacement for <textarea> fields.
// For full source code and docs, visit http://www.interactivetools.com/
//
// Version 3.0 developed by Mihai Bazon.
//   http://dynarch.com/mishoo
//
// $Id: dialog.js 183 2005-05-20 06:11:44Z gogo $

// Though "Dialog" looks like an object, it isn't really an object.  Instead
// it's just namespace for protecting global symbols.

function Dialog(width, height, url, action, init, editor) {
// >MT: Cache the current selection before showing the popup
        if (HTMLArea.is_ie) {
                Dialog._editor = editor;
                Dialog._selection = editor._getSelection();
                Dialog._range = editor._createRange(Dialog._selection);
        }
// <MT
      
	if (typeof init == "undefined") {
		init = window;	// pass this window object by default
	}
	Dialog._geckoOpenModal(width, height, url, action, init);
};

Dialog._parentEvent = function(ev) {
	setTimeout( function() { if (Dialog._modal && !Dialog._modal.closed) { Dialog._modal.focus() } }, 50);
	if (Dialog._modal && !Dialog._modal.closed) {
		HTMLArea._stopEvent(ev);
	}
};

// >MT
// the calling editor
Dialog._editor = null;

// the previous selection
Dialog._selection = null;

// the previous range
Dialog._range = null;
// <MT

// should be a function, the return handler of the currently opened dialog.
Dialog._return = null;

// constant, the currently opened dialog
Dialog._modal = null;

// the dialog will read it's args from this variable
Dialog._arguments = null;

Dialog._geckoOpenModal = function(width, height, url, action, init) {
// MT>: use inline dialogs
      showPopWin (url, width, height);
//	var dlg = window.open(url, "hadialog",
//			      "toolbar=no,menubar=no,personalbar=no,width=10,height=10," +
// <MT
// >MT: use non-resizable diaglogs
//			      "scrollbars=no,resizable=no,modal=yes,dependable=yes");
// <MT
// MT>: use inline dialogs
//	Dialog._modal = dlg;
// <MT
	Dialog._arguments = init;

	// capture some window's events
	function capwin(w) {
		HTMLArea._addEvent(w, "click", Dialog._parentEvent);
		HTMLArea._addEvent(w, "mousedown", Dialog._parentEvent);
		HTMLArea._addEvent(w, "focus", Dialog._parentEvent);
	};
	// release the captured events
	function relwin(w) {
		HTMLArea._removeEvent(w, "click", Dialog._parentEvent);
		HTMLArea._removeEvent(w, "mousedown", Dialog._parentEvent);
		HTMLArea._removeEvent(w, "focus", Dialog._parentEvent);
	};
// >MT: use inline dialogs
//	capwin(window);
// <MT
	// capture other frames, note the exception trapping, this is because
  // we are not permitted to add events to frames outside of the current
  // window's domain.
// >MT: use inline dialogs
//	for (var i = 0; i < window.frames.length; i++) {try { capwin(window.frames[i]); } catch(e) { } };
// <MT
	// make up a function to be called when the Dialog ends.
	Dialog._return = function (val) {
		parent.hidePopWin (false);
// >MT: Reset the ie selection before acting
		if (val && action) {
                        if (HTMLArea.is_ie) {
                                Dialog._editor._selectRange(Dialog._range);
                        }
// <MT
			action(val);
		}
// >MT: use inline dialogs
//		relwin(window);
// <MT
		// capture other frames
// >MT: use inline dialogs
//		for (var i = 0; i < window.frames.length; i++) { try { relwin(window.frames[i]); } catch(e) { } };
// <MT
		Dialog._modal = null;
	};
// >MT: fix crash issue
  if (Dialog._modal) Dialog._modal.focus();
// <MT
};


HTMLArea.Dialog = function(editor, html, localizer)
{
  this.id    = { };
  this.r_id  = { }; // reverse lookup id
  this.editor   = editor;
  this.document = document;

  this.rootElem = document.createElement('div');
  this.rootElem.className = 'dialog';
  this.rootElem.style.position = 'absolute';
  this.rootElem.style.display  = 'none';
  this.editor._framework.ed_cell.insertBefore(this.rootElem, this.editor._framework.ed_cell.firstChild);
  this.rootElem.style.width  = this.width  =  this.editor._framework.ed_cell.offsetWidth + 'px';
  this.rootElem.style.height = this.height =  this.editor._framework.ed_cell.offsetHeight + 'px';

  var dialog = this;
  if(typeof localizer == 'function')
  {
    this._lc = localizer;
  }
  else if(localizer)
  {
    this._lc = function(string)
    {
      return HTMLArea._lc(string,localizer);
    };
  }
  else
  {
    this._lc = function(string)
    {
      return string;
    };
  }

  html = html.replace(/\[([a-z0-9_]+)\]/ig,
                      function(fullString, id)
                      {
                        if(typeof dialog.id[id] == 'undefined')
                        {
                          dialog.id[id] = HTMLArea.uniq('Dialog');
                          dialog.r_id[dialog.id[id]] = id;
                        }
                        return dialog.id[id];
                      }
             ).replace(/<l10n>(.*?)<\/l10n>/ig,
                       function(fullString,translate)
                       {
                         return dialog._lc(translate) ;
                       }
             ).replace(/="_\((.*?)\)"/g,
                       function(fullString, translate)
                       {
                         return '="' + dialog._lc(translate) + '"';
                       }
             );

  this.rootElem.innerHTML = html;




  this.editor.notifyOn
   ('resize',
      function(e, args)
      {
        dialog.rootElem.style.width  = dialog.width  =  dialog.editor._framework.ed_cell.offsetWidth + 'px';
        dialog.rootElem.style.height = dialog.height =  dialog.editor._framework.ed_cell.offsetHeight + 'px';
        dialog.onresize();
      }
    );
};

HTMLArea.Dialog.prototype.onresize = function()
{
  return true;
};

HTMLArea.Dialog.prototype.show = function(values)
{
  // We need to preserve the selection for IE
  if(HTMLArea.is_ie)
  {
    this._lastRange = this.editor._createRange(this.editor._getSelection());
  }

  if(typeof values != 'undefined')
  {
    this.setValues(values);
  }
  this._restoreTo = [this.editor._textArea.style.display, this.editor._iframe.style.visibility, this.editor.hidePanels()];

  this.editor._textArea.style.display = 'none';
  this.editor._iframe.style.visibility   = 'hidden';
  this.rootElem.style.display   = '';
};

HTMLArea.Dialog.prototype.hide = function()
{
  this.rootElem.style.display         = 'none';
  this.editor._textArea.style.display = this._restoreTo[0];
  this.editor._iframe.style.visibility   = this._restoreTo[1];
  this.editor.showPanels(this._restoreTo[2]);

  // Restore the selection
  if(HTMLArea.is_ie)
  {
    this._lastRange.select();
  }
  this.editor.updateToolbar();
  return this.getValues();
};

HTMLArea.Dialog.prototype.toggle = function()
{
  if(this.rootElem.style.display == 'none')
  {
    this.show();
  }
  else
  {
    this.hide();
  }
};

HTMLArea.Dialog.prototype.setValues = function(values)
{
  for(var i in values)
  {
    var elems = this.getElementsByName(i);
    if(!elems) continue;
    for(var x = 0; x < elems.length; x++)
    {
      var e = elems[x];
      switch(e.tagName.toLowerCase())
      {
        case 'select'  :
        {
          for(var j = 0; j < e.options.length; j++)
          {
            if(typeof values[i] == 'object')
            {
              for(var k = 0; k < values[i].length; k++)
              {
                if(values[i][k] == e.options[j].value)
                {
                  e.options[j].selected = true;
                }
              }
            }
            else if(values[i] == e.options[j].value)
            {
              e.options[j].selected = true;
            }
          }
          break;
        }


        case 'textarea':
        case 'input'   :
        {
          switch(e.getAttribute('type'))
          {
            case 'radio'   :
            {
              if(e.value == values[i])
              {
                e.checked = true;
              }
              break;
            }

            case 'checkbox':
            {
              if(typeof values[i] == 'object')
              {
                for(var j in values[i])
                {
                  if(values[i][j] == e.value)
                  {
                    e.checked = true;
                  }
                }
              }
              else
              {
                if(values[i] == e.value)
                {
                  e.checked = true;
                }
              }
              break;
            }

            default    :
            {
              e.value = values[i];
            }
          }
          break;
        }

        default        :
        break;
      }
    }
  }
};

HTMLArea.Dialog.prototype.getValues = function()
{
  var values = [ ];
  var inputs = HTMLArea.collectionToArray(this.rootElem.getElementsByTagName('input'))
              .append(HTMLArea.collectionToArray(this.rootElem.getElementsByTagName('textarea')))
              .append(HTMLArea.collectionToArray(this.rootElem.getElementsByTagName('select')));

  for(var x = 0; x < inputs.length; x++)
  {
    var i = inputs[x];
    if(!(i.name && this.r_id[i.name])) continue;

    if(typeof values[this.r_id[i.name]] == 'undefined')
    {
      values[this.r_id[i.name]] = null;
    }
    var v = values[this.r_id[i.name]];

    switch(i.tagName.toLowerCase())
    {
      case 'select':
      {
        if(i.multiple)
        {
          if(!v.push)
          {
            if(v != null)
            {
              v = [v];
            }
            else
            {
              v = new Array();
            }
          }
          for(var j = 0; j < i.options.length; j++)
          {
            if(i.options[j].selected)
            {
              v.push(i.options[j].value);
            }
          }
        }
        else
        {
          if(i.selectedIndex >= 0)
          {
            v = i.options[i.selectedIndex];
          }
        }
        break;
      }

      case 'textarea':
      case 'input'   :
      default        :
      {
        switch(i.type.toLowerCase())
        {
          case  'radio':
          {
            if(i.checked)
            {
              v = i.value;
              break;
            }
          }

          case 'checkbox':
          {
            if(v == null)
            {
              if(this.getElementsByName(this.r_id[i.name]).length > 1)
              {
                v = new Array();
              }
            }

            if(i.checked)
            {
              if(v != null && typeof v == 'object' && v.push)
              {
                v.push(i.value);
              }
              else
              {
                v = i.value;
              }
            }
            break;
          }

          default   :
          {
            v = i.value;
            break;
          }
        }
      }

    }

    values[this.r_id[i.name]] = v;
  }
  return values;
};

HTMLArea.Dialog.prototype.getElementById = function(id)
{
  return this.document.getElementById(this.id[id] ? this.id[id] : id);
};

HTMLArea.Dialog.prototype.getElementsByName = function(name)
{
  return this.document.getElementsByName(this.id[name] ? this.id[name] : name);
};

/** This file is derived from PopupDiv, developed by Mihai Bazon for
 * SamWare.net.  Modifications were needed to make it usable in HTMLArea.
 * HTMLArea is a free WYSIWYG online HTML editor from InteractiveTools.com.
 *
 * This file does not function standalone.  It is dependent of global functions
 * defined in HTMLArea-3.0 (htmlarea.js).
 *
 * Please see file htmlarea.js for further details.
 **/

var is_ie = ( (navigator.userAgent.toLowerCase().indexOf("msie") != -1) &&
	      (navigator.userAgent.toLowerCase().indexOf("opera") == -1) );
var is_compat = (document.compatMode == "BackCompat");

function PopupDiv(editor, titleText, handler, initFunction) {
	var self = this;

	this.editor = editor;
	this.doc = editor._mdoc;
	this.handler = handler;

	var el = this.doc.createElement("div");
	el.className = "content";

	var popup = this.doc.createElement("div");
	popup.className = "dialog popupdiv";
	this.element = popup;
	var s = popup.style;
	s.position = "absolute";
	s.left = "0px";
	s.top = "0px";

	var title = this.doc.createElement("div");
	title.className = "title";
	this.title = title;
	popup.appendChild(title);

	HTMLArea._addEvent(title, "mousedown", function(ev) {
		self._dragStart(is_ie ? window.event : ev);
	});

	var button = this.doc.createElement("div");
	button.className = "button";
	title.appendChild(button);
	button.innerHTML = "&#x00d7;";
	title.appendChild(this.doc.createTextNode(titleText));
	this.titleText = titleText;

	button.onmouseover = function() {
		this.className += " button-hilite";
	};
	button.onmouseout = function() {
		this.className = this.className.replace(/\s*button-hilite\s*/g, " ");
	};
	button.onclick = function() {
		this.className = this.className.replace(/\s*button-hilite\s*/g, " ");
		self.close();
	};

	popup.appendChild(el);
	this.content = el;

	this.doc.body.appendChild(popup);

	this.dragging = false;
	this.onShow = null;
	this.onClose = null;
	this.modal = false;

	initFunction(this);
};

PopupDiv.currentPopup = null;

PopupDiv.prototype.showAtElement = function(el, mode) {
	this.defaultSize();
	var pos, ew, eh;
	var popup = this.element;
	popup.style.display = "block";
	var w = popup.offsetWidth;
	var h = popup.offsetHeight;
	popup.style.display = "none";
	if (el != window) {
		pos = PopupDiv.getAbsolutePos(el);
		ew = el.offsetWidth;
		eh = el.offsetHeight;
	} else {
		pos = {x:0, y:0};
		var size = PopupDiv.getWindowSize();
		ew = size.x;
		eh = size.y;
	}
	var FX = false, FY = false;
	if (mode.indexOf("l") != -1) {
		pos.x -= w;
		FX = true;
	}
	if (mode.indexOf("r") != -1) {
		pos.x += ew;
		FX = true;
	}
	if (mode.indexOf("t") != -1) {
		pos.y -= h;
		FY = true;
	}
	if (mode.indexOf("b") != -1) {
		pos.y += eh;
		FY = true;
	}
	if (mode.indexOf("c") != -1) {
		FX || (pos.x += Math.round((ew - w) / 2));
		FY || (pos.y += Math.round((eh - h) / 2));
	}
	this.showAt(pos.x, pos.y);
};

PopupDiv.prototype.defaultSize = function() {
	var s = this.element.style;
	var cs = this.element.currentStyle;
	var addX = (is_ie && is_compat) ? (parseInt(cs.borderLeftWidth) +
					   parseInt(cs.borderRightWidth) +
					   parseInt(cs.paddingLeft) +
					   parseInt(cs.paddingRight)) : 0;
	var addY = (is_ie && is_compat) ? (parseInt(cs.borderTopWidth) +
					   parseInt(cs.borderBottomWidth) +
					   parseInt(cs.paddingTop) +
					   parseInt(cs.paddingBottom)) : 0;
	// >MT: fix
	if (String(addX) == "NaN") addX = 0;
	if (String(addY) == "NaN") addY = 0;
	// <MT
	s.display = "block";
	s.width = (this.content.offsetWidth + addX) + "px";
	s.height = (this.content.offsetHeight + this.title.offsetHeight) + "px";
	s.display = "none";
};

PopupDiv.prototype.showAt = function(x, y) {
	this.defaultSize();
	var s = this.element.style;
	s.display = "block";
	s.left = x + "px";
	s.top = y + "px";
	this.hideShowCovered();

	PopupDiv.currentPopup = this;
	HTMLArea._addEvents(this.doc.body, ["mousedown", "click"], PopupDiv.checkPopup);
	HTMLArea._addEvents(this.editor._doc.body, ["mousedown", "click"], PopupDiv.checkPopup);
	if (is_ie && this.modal) {
		this.doc.body.setCapture(false);
		// >MT: fix
		var self = this;
		// <MT
		this.doc.body.onlosecapture = function() {
			// >MT: fix
			(PopupDiv.currentPopup) && (self.doc.body.setCapture(false));
			// <MT
		};
	}
	window.event && HTMLArea._stopEvent(window.event);

	if (typeof this.onShow == "function") {
		this.onShow();
	} else if (typeof this.onShow == "string") {
		eval(this.onShow);
	}

	var field = this.element.getElementsByTagName("input")[0];
	if (!field) {
		field = this.element.getElementsByTagName("select")[0];
	}
	if (!field) {
		field = this.element.getElementsByTagName("textarea")[0];
	}
	if (field) {
		field.focus();
	}
};

PopupDiv.prototype.close = function() {
	this.element.style.display = "none";
	PopupDiv.currentPopup = null;
	this.hideShowCovered();
	HTMLArea._removeEvents(this.doc.body, ["mousedown", "click"], PopupDiv.checkPopup);
	HTMLArea._removeEvents(this.editor._doc.body, ["mousedown", "click"], PopupDiv.checkPopup);
	is_ie && this.modal && this.doc.body.releaseCapture();
	if (typeof this.onClose == "function") {
		this.onClose();
	} else if (typeof this.onClose == "string") {
		eval(this.onClose);
	}
	this.element.parentNode.removeChild(this.element);
};

PopupDiv.prototype.getForm = function() {
	var forms = this.content.getElementsByTagName("form");
	return (forms.length > 0) ? forms[0] : null;
};

PopupDiv.prototype.callHandler = function() {
	var tags = ["input", "textarea", "select"];
	var params = new Object();
	for (var ti = tags.length; --ti >= 0;) {
		var tag = tags[ti];
		var els = this.content.getElementsByTagName(tag);
		for (var j = 0; j < els.length; ++j) {
			var el = els[j];
			params[el.name] = el.value;
		}
	}
	this.handler(this, params);
	return false;
};

PopupDiv.getAbsolutePos = function(el) {
	var r = { x: el.offsetLeft, y: el.offsetTop };
	if (el.offsetParent) {
		var tmp = PopupDiv.getAbsolutePos(el.offsetParent);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
};

PopupDiv.getWindowSize = function() {
	if (window.innerHeight) {
		return { y: window.innerHeight, x: window.innerWidth };
	}
	if (this.doc.body.clientHeight) {
		return { y: this.doc.body.clientHeight, x: this.doc.body.clientWidth };
	}
	return { y: this.doc.documentElement.clientHeight, x: this.doc.documentElement.clientWidth };
};

PopupDiv.prototype.hideShowCovered = function () {
	var self = this;
	function isContained(el) {
		while (el) {
			if (el == self.element) {
				return true;
			}
			el = el.parentNode;
		}
		return false;
	};
	var tags = new Array("applet", "select");
	var el = this.element;

	var p = PopupDiv.getAbsolutePos(el);
	var EX1 = p.x;
	var EX2 = el.offsetWidth + EX1;
	var EY1 = p.y;
	var EY2 = el.offsetHeight + EY1;

	if (el.style.display == "none") {
		EX1 = EX2 = EY1 = EY2 = 0;
	}

	for (var k = tags.length; k > 0; ) {
		var ar = this.doc.getElementsByTagName(tags[--k]);
		var cc = null;

		for (var i = ar.length; i > 0;) {
			cc = ar[--i];
			if (isContained(cc)) {
				cc.style.visibility = "visible";
				continue;
			}

			p = PopupDiv.getAbsolutePos(cc);
			var CX1 = p.x;
			var CX2 = cc.offsetWidth + CX1;
			var CY1 = p.y;
			var CY2 = cc.offsetHeight + CY1;

			if ((CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
				cc.style.visibility = "visible";
			} else {
				cc.style.visibility = "hidden";
			}
		}
	}
};

PopupDiv.prototype._dragStart = function (ev) {
	if (this.dragging) {
		return false;
	}
	this.dragging = true;
	PopupDiv.currentPopup = this;
	var posX = ev.clientX;
	var posY = ev.clientY;
	if (is_ie) {
		posY += this.doc.body.scrollTop;
		posX += this.doc.body.scrollLeft;
	} else {
		posY += window.scrollY;
		posX += window.scrollX;
	}
	var st = this.element.style;
	this.xOffs = posX - parseInt(st.left);
	this.yOffs = posY - parseInt(st.top);
	HTMLArea._addEvent(this.doc, "mousemove", PopupDiv.dragIt);
	HTMLArea._addEvent(this.doc, "mouseover", HTMLArea._stopEvent);
	HTMLArea._addEvent(this.doc, "mouseup", PopupDiv.dragEnd);
	HTMLArea._stopEvent(ev);
};

PopupDiv.dragIt = function (ev) {
	var popup = PopupDiv.currentPopup;
	if (!(popup && popup.dragging)) {
		return false;
	}
	is_ie && (ev = window.event);
	var posX = ev.clientX;
	var posY = ev.clientY;
	if (is_ie) {
		// >MT: fix
		posY += popup.doc.body.scrollTop;
		posX += popup.doc.body.scrollLeft;
		// <MT
	} else {
		posY += window.scrollY;
		posX += window.scrollX;
	}
	popup.hideShowCovered();
	var st = popup.element.style;
	st.left = (posX - popup.xOffs) + "px";
	st.top = (posY - popup.yOffs) + "px";
	HTMLArea._stopEvent(ev);
};

PopupDiv.dragEnd = function () {
	var popup = PopupDiv.currentPopup;
	if (!popup) {
		return false;
	}
	popup.dragging = false;
	HTMLArea._removeEvent(popup.doc, "mouseup", PopupDiv.dragEnd);
	HTMLArea._removeEvent(popup.doc, "mouseover", HTMLArea._stopEvent);
	HTMLArea._removeEvent(popup.doc, "mousemove", PopupDiv.dragIt);
	popup.hideShowCovered();
};

PopupDiv.checkPopup = function (ev) {
	is_ie && (ev = window.event);
	// >MT: fix
	if (!ev) return;
	// <MT: fix
	var el = is_ie ? ev.srcElement : ev.target;
	var cp = PopupDiv.currentPopup;
	for (; (el != null) && (el != cp.element); el = el.parentNode);
	if (el == null) {
		cp.modal || ev.type == "mouseover" || cp.close();
		HTMLArea._stopEvent(ev);
	}
};

PopupDiv.prototype.addButtons = function() {
	var self = this;
	var div = this.doc.createElement("div");
	this.content.appendChild(div);
	div.className = "buttons";
	for (var i = 0; i < arguments.length; ++i) {
		var btn = arguments[i];
		var button = this.doc.createElement("button");
		div.appendChild(button);
		// >MT: fix
		button.innerHTML = HTMLArea._lc(btn, 'HTMLArea');
		switch (btn.toLowerCase()) {
		// <MT
		    case "ok":
			button.onclick = function() {
				self.callHandler();
				self.close();
			};
			break;
		    case "cancel":
			button.onclick = function() {
				self.close();
			};
			break;
		}
	}
};

