//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM Corporation and others.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//------------------------------------------------------------------------------
// @author Kelvin Low
// @since 1.0
//------------------------------------------------------------------------------
// Note: Mozilla/Firefox does not allow unprivileged scripts to invoke the cut,
// copy and paste commands. The Javascript must either be signed
// (see http://www.mozilla.org/projects/security/components/signed-scripts.html),
// or the users must change their preferences
// (see http://www.mozilla.org/editor/midasdemo/securityprefs.html).
// Alternatively, the users can use the ctrl-x, ctrl-c and ctrl-v keys.
//------------------------------------------------------------------------------

var STATUS_NOP = 0;
var STATUS_INITIALIZED = 1;
var STATUS_MODIFIED = 2;
var STATUS_GET_TEXT = 3;
var STATUS_KEY_DOWN = 4;
var STATUS_KEY_UP = 5;
var STATUS_SELECT_TEXT = 6;
var STATUS_SELECT_CONTROL = 7;
var STATUS_SELECT_NONE = 8;
var STATUS_EXEC_CMD = 9;
var STATUS_REFORMAT_LINKS = 10;

var KEY_ARROW_DOWN = 40;
var KEY_ARROW_LEFT = 37;
var KEY_ARROW_RIGHT = 39;
var KEY_ARROW_UP = 38;
var KEY_BACKSPACE = 8;
var KEY_END = 35;
var KEY_HOME = 36;
var KEY_PAGE_DOWN = 34;
var KEY_PAGE_UP = 33;
var KEY_TAB = 9;
var KEY_C = 67;
var KEY_F = 70;
var KEY_S = 83;
var KEY_V = 86;
var KEY_X = 88;
var KEY_Z = 90;

var CMD_COPY = "copy";
var CMD_CUT = "cut";
var CMD_FIND_TEXT = "findText";
var CMD_PASTE = "paste";
var CMD_SAVE = "save";
var CMD_SAVE_ALL = "saveAll";

var TABLE_HEADERS_NONE = 0;
var TABLE_HEADERS_COLS = 1;
var TABLE_HEADERS_ROWS = 2;
var TABLE_HEADERS_BOTH = 3;

var BOLD = 1;
var ITALIC = BOLD << 1;
var UNDERLINE = ITALIC << 1;
var SUBSCRIPT = UNDERLINE << 1;
var SUPERSCRIPT = SUBSCRIPT << 1;


var editorId;
var editorCSS;
var baseHREF;
var supportRichTextEditing = true;
var editorDoc;
var selection;
var selectionRange;
var readOnly = false;
var initialized = false;
var modified = false;
var checkResizeElement;
var selectionInfo = null;

// Initializes the editor.
function initEditor(id, css, baseURL) {
	editorId = id;
	editorCSS = css;
	baseHREF = baseURL;
	try {
		enableRichTextEditing('');
		initialized = true;
		setStatus(STATUS_INITIALIZED, null);
	}
	catch (e) {
		supportRichTextEditing = false;
	}
}

// Handles the key events.
function keyPressed(event) {
	var keyCode = event.keyCode;
	if (keyCode == 0 && !document.all) {
		keyCode = event.charCode;
		switch (keyCode) {
			case 99:
				keyCode = KEY_C;
				break;
			case 102:
				keyCode = KEY_F;
				break;
			case 115:
				keyCode = KEY_S;
				break;
			case 118:
				keyCode = KEY_V;
				break;
			case 120:
				keyCode = KEY_X;
				break;
			case 122:
				keyCode = KEY_Z;
				break;
		}
	}
	var ctrlKey = event.ctrlKey;
	var shiftKey = event.shiftKey;
	
	switch(keyCode) {
		case KEY_ARROW_DOWN:
		case KEY_ARROW_LEFT:
		case KEY_ARROW_RIGHT:
		case KEY_ARROW_UP:
		case KEY_END:
		case KEY_HOME:
		case KEY_PAGE_DOWN:
		case KEY_PAGE_UP:
		case KEY_TAB:
			break;
		case KEY_BACKSPACE:
			if (!readOnly) {
				setTimeout("setStatus(STATUS_MODIFIED, null);", 10);
			}
			break;
		case KEY_C:
			if (ctrlKey) {
				setStatus(STATUS_KEY_DOWN, CMD_COPY);
			}
			else if (!document.all && readOnly) {
				event.preventDefault();
			}
			break;			
		case KEY_F:
			if (ctrlKey) {
				if (document.all) {
					event.keyCode = -1;
					event.returnValue = false;
				}
				else {
					event.preventDefault();
				}
				setStatus(STATUS_KEY_DOWN, CMD_FIND_TEXT);
			}
			else if (!document.all && readOnly) {
				event.preventDefault();
			}
			break;
		case KEY_S:
			if (!readOnly && ctrlKey) {
				if (document.all) {
					event.keyCode = -1;
					event.returnValue = false;
				}
				else {
					event.preventDefault();
				}
				if (shiftKey) {
					setStatus(STATUS_KEY_DOWN, CMD_SAVE_ALL);
				}
				else {
					setStatus(STATUS_KEY_DOWN, CMD_SAVE);
				}
			}
			else if (!document.all && readOnly) {
				event.preventDefault();
			}			
			break;
		case KEY_V:
			if (ctrlKey) {		
				if (document.all) {
					event.keyCode = -1;
					event.returnValue = false;
					if (!readOnly) {
						setStatus(STATUS_KEY_DOWN, CMD_PASTE);
					}
				}
				else {
					if (!readOnly) {
						// Workaround Mozilla/Firefox paste issues.
						setTimeout("setStatus(STATUS_KEY_DOWN, CMD_PASTE);", 10);
					}
					else {
						event.preventDefault();
					}
				}
			}
			else if (!document.all && readOnly) {
				event.preventDefault();
			}
			break;
		case KEY_X:
			if (ctrlKey) {
				setStatus(STATUS_KEY_DOWN, CMD_CUT);
			}
			else if (!document.all && readOnly) {
				event.preventDefault();
			}
			break;
		case KEY_Z:
			if (!readOnly && ctrlKey) {
				setTimeout("setStatus(STATUS_MODIFIED, null);", 10);
			}
			else if (!document.all && readOnly) {
				event.preventDefault();
			}			
			break;
		default:
			if (!document.all && readOnly) {
				event.preventDefault();
			}
	}
}

function selChanged(event) {
	updateSelection();
}

function enableRichTextEditing(html) {
	var doc = document.getElementById(editorId).contentWindow.document;
	doc.designMode = "on";
	
	var htmlSrc = '<html><head><title></title>';
	
	if (editorCSS != null && editorCSS != '') {
		htmlSrc += '<link rel="StyleSheet" href="' + editorCSS + '" type="text/css"/>';
	}
	
	if (baseHREF != null && baseHREF != '') {	
		htmlSrc += '<base href="' + baseHREF + '"/>';
	}
	
	if (!document.all && html == '') {
		// Mozilla/Firefox will only display the caret if <br/> is added to the HTML body.
		// Adding <br/> also enables the backspace and delete key by default. Otherwise, the
		// user need to enter some text before these 2 keys start to function.
		html = "<br />";
	}
	
	htmlSrc += '</head><body>' + html + '</body></html>';
	
	doc.open();
	doc.write(htmlSrc);
	doc.close();
	
	modified = false;

	if ("attachEvent" in doc) {
		doc.attachEvent("onkeydown", keyPressed);
		doc.attachEvent("onselectionchange", selChanged);
		// for DnD (internal)
		doc.body.attachEvent("ondrop", checkModified);
		// for image/table resizing:
		doc.body.attachEvent("onresizeend", checkModified);
	}	
	if ("addEventListener" in doc) {
		doc.addEventListener("keypress", keyPressed, true);
		doc.addEventListener("keypress", selChanged, false);
		doc.addEventListener("mouseup", selChanged, false);
		doc.addEventListener("dragdrop", checkModified, false);
		
		// check mouseup event for image/table resizing
		doc.addEventListener("mouseup", checkModified, false);
	}

	setStatus(STATUS_EXEC_CMD, 1);
}

// this one is for modification check on drag n drop within the RTE
// checkModified listener
function checkModified(event) {
	setTimeout("setStatus(STATUS_MODIFIED, null);", 10);
}

// Sets the height of the editor.
function setHeight(height) {
	if (initialized) {
		document.getElementById(editorId).height = height + "px";
	}
}

// Sets the status.
// Note: By default, Firefox disables changes to the status bar. For this to work, the user
// must set the global preference "dom.disable_window_status_change" to false.
// For Firefox 1.0.x, this setting can be made in /usr/firefox-1.0.7/defaults/pref/firefox.js.
function setStatus(type, value) {
	var status = '$$$' + type;
	if (value != null && value != '') {
		status += ('$' + value);		
	}
	window.status = status;
	window.status = '$$$' + STATUS_NOP;
}

// Returns the HTML source.
function getHTML() {
	var html = document.getElementById(editorId).contentWindow.document.body.innerHTML;
	if (html == "<P>&nbsp;</P>") {
		html = "";
	}
	if (html != null && html != '') {
		var regEx = new RegExp("\"file\:([^=]*)(/resources/)([^\"]+)\"", "g");
		html = html.replace(regEx, "\"./resources/$3\"");
		regEx = new RegExp("\"file\:([^=]*)/#([^\"]+)\"", "g");
		html = html.replace(regEx, "\"#$2\"");
	}
	return html;
}

//Returns the HTML source to the Java layer
function getText() {
	var html = getHTML();
	setStatus(STATUS_GET_TEXT, html);
	return html;
}

function setInnerHTML(html) {
	if (document.all) {
		// IE has problem setting complex HTML set via doc.body.innerHTML.
		enableRichTextEditing(html);
	}
	else {
		if (html == '') {
			// Mozilla/Firefox will only display the caret if <br/> is added to the HTML body.
			html = "<br/>";
		}
		var doc = document.getElementById(editorId).contentWindow.document;
		if (doc.body != null) {
			doc.body.innerHTML = html;
		}
		else {
			// Mozilla/Firefox can take a while to initialize document.body
			// after document.write().
			try {
				setTimeout("setInnerHTML('" + html + "');", 10);
			}
			catch (e) {
			}
		}
	}
}

// Sets the HTML source.
function setText(html) {
	if (supportRichTextEditing) {
		html = decodeString(html);
		selectionInfo = getSelectionInfo();
		setInnerHTML(html);
		if (selectionInfo != null) {
			setTimeout("setSelection(selectionInfo);", 10);
		}
		modified = false;
		setStatus(STATUS_EXEC_CMD, 1);
	}
}

function setSelection(selectionInfo) {
	if (!supportRichTextEditing) {
		return;
	}
	
	contentWindow = document.getElementById(editorId).contentWindow;
	editorDoc = contentWindow.document;
	
	try {
		if (document.all) {
			var startOffset = selectionInfo.start;
			var len = selectionInfo.len;
			if (startOffset == 0 && len == 0) {
				return;
			}
			var tempRange = editorDoc.body.createTextRange();
			tempRange.moveStart('character', startOffset);
			tempRange.collapse();
			tempRange.moveEnd('character', len);
			tempRange.select();
			tempRange.scrollIntoView();
		} else {
			selection = this.window.getSelection();
			var startContainer = selectionInfo.startContainer;
			var start = selectionInfo.start;
			var endContainer = selectionInfo.endContainer;
			var end = selectionInfo.end;
			var tempRange = document.createRange();
			tempRange.setStart(startContainer, start);
			tempRange.setEnd(endContainer, end);
			selection.removeAllRanges();
			selection.addRange(tempRange);
			contentWindow.focus();
		}
	} catch (e) {
	}
}

function getSelectionInfo() {
	if (!supportRichTextEditing) {
		return null;
	}	
	
	contentWindow = document.getElementById(editorId).contentWindow;
	editorDoc = contentWindow.document;
	
	var tempSelRange;
	try {
	    if (document.all) {
			selection = editorDoc.selection;
			if (selection != null) {
				tempSelRange = selection.createRange();
			}
			// length of selection
			var tempSelLen = tempSelRange.text.length;
			// create new range
			var tempRange = editorDoc.body.createTextRange();
			// set end of new range to start of selection
			// this will throw an exception if tempSelRange is not in editor.doc.body (ie, at the start of the RTE).
			tempRange.setEndPoint("EndToStart", tempSelRange);
			// length of new range is the start offset
			var tempText = tempRange.text;
			// IE counts newlines as 2 characters for length property, but they count as 1 when using moveStart so remove the \r to make the count the same
			tempText = tempText.replace(/\r/g, "");
			var startOffset = tempText.length;
			
			return {start:startOffset, len:tempSelLen};
	    } else {
			selection = contentWindow.getSelection();
			if (selection != null) {
				tempSelRange = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
			}
			return {startContainer: tempSelRange.startContainer, start:tempSelRange.startOffset, 
				endContainer: tempSelRange.endContainer, end:tempSelRange.endOffset};
	    }
	} catch (e) {
		return null;
	}
}

// Decodes the HTML passed from the Java layer.
function decodeString(str) {
	if (str != null && str != '') {
		if (document.all) {
			str = str.replace(/%sq%/g, "'");
			str = str.replace(/%EOL%/g, "\n");
		}
		else {
			str = str.replace(/%sq%/g, "&apos;");
			str = str.replace(/%EOL%/g, "");
			str = str.replace(/\n/g, "");
		}
	}
	return str;
}

// updates selection without notifying the Java layer of the selection state
function internalUpdateSelection() {
	if (!supportRichTextEditing) {
		return false;
	}	
	
	contentWindow = document.getElementById(editorId).contentWindow;
	editorDoc = contentWindow.document;
	
	if (document.all) {
		selection = editorDoc.selection;
		if (selection != null) {
			selectionRange = selection.createRange();
			reformatElementLinks();
		}
	}
	else {
		selection = contentWindow.getSelection();
		if (selection != null) {
			selectionRange = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
			if (selectionRange.startContainer.nodeName == "HTML" &&
					selectionRange.endContainer.nodeName == "HTML") {
				// Mozilla selects the whole document when there's no RTE content, so select just the body
				selectionRange = editorDoc.createRange();
				selectionRange.setStart(editorDoc.body, 0);
				selectionRange.setEnd(editorDoc.body, 0);
			}
		}
	}
	return true;
}

// Updates the current selection and selection range.
function updateSelection() {
	if (!supportRichTextEditing) {
		return false;
	}	
	
	contentWindow = document.getElementById(editorId).contentWindow;
	editorDoc = contentWindow.document;
	
	var tempSelRange;
	var selOffsetStart = 0;
	var selectedText = "";
	var fontName = "";
	var fontSize = "";
	var blockStyle = "";
	var textFlags = 0;
	
	
	if (document.all) {
		selection = editorDoc.selection;
		if (selection != null) {
			selectionRange = selection.createRange();
			if (selectionRange != null && selection.type != "Control") {
				tempSelRange = selectionRange.duplicate();
			}
			reformatElementLinks();
		}
	}
	else {
		selection = contentWindow.getSelection();
		if (selection != null) {
			selectionRange = selection.getRangeAt(selection.rangeCount - 1).cloneRange();
			tempSelRange = selectionRange.cloneRange();
		}
	}
	if (tempSelRange != null) {
		try {
			if (document.all) {
				if (selectionRange.text) {
					selectedText = selectionRange.text;
				}
				/* for getting selection offset - commented because we can't select the
				 * proper location in the HTML source tab because JTidy's reformatting of the HTML
				var html = getHTML();
	            var tempSelLen = tempSelRange.htmlText.length;			
	            tempSelRange.moveStart('character', -html.length);
	            selOffsetStart = tempSelRange.htmlText.length - tempSelLen;
	            */
				var selParent = tempSelRange.parentElement();
				fontName = tempSelRange.queryCommandValue('fontName');
				fontSize = tempSelRange.queryCommandValue('fontSize');
				blockStyle = tempSelRange.queryCommandValue('formatBlock');
				if (blockStyle == "Normal") {
					if (selParent.className == "quote") {
						blockStyle = "<quote>";
					} else if (selParent.className == "codeSample") {
						blockStyle = "<code>";
					} else {
						blockStyle = "<p>";
					}
				} else if (blockStyle == "Heading 3") {
					blockStyle = "<h3>";
				} else if (blockStyle == "Heading 4") {
					blockStyle = "<h4>";
				} else if (blockStyle == "Heading 5") {
					blockStyle = "<h5>";
				} else if (blockStyle == "" || blockStyle == null) {
					blockStyle = "<p>";
				}
				if (tempSelRange.queryCommandValue('bold') == true) {
					textFlags |= BOLD;
				}
				if (tempSelRange.queryCommandValue('italic') == true) {
					textFlags |= ITALIC;
				}
				if (tempSelRange.queryCommandValue('underline') == true) {
					textFlags |= UNDERLINE;
				}
				if (tempSelRange.queryCommandValue('subscript') == true) {
					textFlags |= SUBSCRIPT;
				}
				if (tempSelRange.queryCommandValue('superscript') == true) {
					textFlags |= SUPERSCRIPT;
				}
				setStatus(STATUS_SELECT_TEXT, /* selOffsetStart + "$" + */
						fontName + "$" + fontSize + "$" + blockStyle + "$" + textFlags + "$" + selectedText);
			} else {
				if (selectionRange != null) {
					selectedText = selectionRange.toString();
				}
				var selParent = selection.focusNode;
				fontName = editorDoc.queryCommandValue('fontName');
				if (fontName == "") {
					fontName = "default";
				}
				fontSize = editorDoc.queryCommandValue('fontSize');
				if (fontSize == "") {
					fontSize = "default";
				}
				blockStyle = editorDoc.queryCommandValue('formatBlock');
				if (blockStyle == "p") {
					if (selParent.parentNode.className == "quote") {
						blockStyle = "<quote>";
					} else if (selParent.parentNode.className == "codeSample") {
						blockStyle = "<code>";
					} else {
						blockStyle = "<p>";
					}
				} else if (blockStyle == "h3") {
					blockStyle = "<h3>";
				} else if (blockStyle == "h4") {
					blockStyle = "<h4>";
				} else if (blockStyle == "h5") {
					blockStyle = "<h5>";
				} else if (blockStyle == "") {
					blockStyle = "<p>";
				}
				if (editorDoc.queryCommandState('bold') == true) {
					textFlags |= BOLD;
				}
				if (editorDoc.queryCommandState('italic') == true) {
					textFlags |= ITALIC;
				}
				if (editorDoc.queryCommandState('underline') == true) {
					textFlags |= UNDERLINE;
				}
				if (editorDoc.queryCommandState('subscript') == true) {
					textFlags |= SUBSCRIPT;
				}
				if (editorDoc.queryCommandState('superscript') == true) {
					textFlags |= SUPERSCRIPT;
				}
				setStatus(STATUS_SELECT_TEXT, /* selOffsetStart + "$" + */
						fontName + "$" + fontSize + "$" + blockStyle + "$" + textFlags + "$" + selectedText);
			}
		} catch (e) { }
	}	

	return true;
}

// Sets focus to this editor.
function setFocus() {
	if (!supportRichTextEditing) {
		return;
	}	
	if (document.all) {
		iframe = document.getElementById(editorId);
		iframe.focus();
	} else {
		contentWindow = document.getElementById(editorId).contentWindow;
		contentWindow.focus();
	}
	setStatus(STATUS_EXEC_CMD, 1);	
}

// Reformats element links created via drag & drop.
function reformatElementLinks() {
	var linksReformatted = 0;
	var elements = editorDoc.getElementsByTagName('A');
	for (var i = 0; i < elements.length; i++) {
		var element = elements[i];
		if (element.className.toLowerCase() == 'elementlink' ||
				element.className.toLowerCase() == 'elementlinkwithtype' ||
				element.className.toLowerCase() == 'elementlinkwithusertext') {
 			if (element.firstChild != null && element.firstChild.firstChild != null &&
 				element.firstChild.firstChild.firstChild != null) {
 				var linkText = element.firstChild.firstChild.firstChild.nodeValue;
 				element.removeChild(element.firstChild);
 				element.appendChild(editorDoc.createTextNode(linkText));
 				linksReformatted++;
 			}
		}
	}
	if (linksReformatted > 0) {
		setStatus(STATUS_REFORMAT_LINKS, null);
	}
}

// Formats the selected text.
function formatText(command, option) {
	if (!readOnly && internalUpdateSelection()) {
		if (editorDoc.execCommand(command, false, option)) {
			setStatus(STATUS_EXEC_CMD, 1);		
			setStatus(STATUS_MODIFIED, null);
		}
	}
}

// Adds HTML.
function addHTML(html) {
	if (!readOnly && html != "")  {
		html = decodeString(html);
		if (internalUpdateSelection()) {
			if (document.all) {
				if (selectionRange.text != null) {
					selectionRange.pasteHTML(html);
					setStatus(STATUS_EXEC_CMD, 1);
					setStatus(STATUS_MODIFIED, null);
				}
			}
			else {
				selectionRange.deleteContents();
				var documentFragment = selectionRange.createContextualFragment(html);
				selectionRange.insertNode(documentFragment);
				setStatus(STATUS_EXEC_CMD, 1);
				setStatus(STATUS_MODIFIED, null);
			}
		}
	}
}

// Adds an image.
function addImage(url, height, width, alt) {
	if (internalUpdateSelection()) {
		if (document.all) {
			if (url != null && url != '') {
				formatText('insertimage', url);
			}
			if (selection != null && selection.type == 'Control' && selectionRange != null) {
				if (height != null && height != '') selectionRange.item().height = height;
				if (width != null && width != '') selectionRange.item().width = width;
				if (alt != null) selectionRange.item().alt = alt;		
			}
		} else {
			var START_MARKER = "A_-_-_";
			var END_MARKER = ":.:.:";
				// mark img links with START_MARKER + id + END_MARKER in the id, for later recovery
			var elements = editorDoc.getElementsByTagName('img');
			for (var i = 0; i < elements.length; i++) {
				var element = elements[i];
				element.id = START_MARKER + element.id + END_MARKER;
			}
			if (url != null && url != '') {
				formatText('insertimage', url);
			}
			if (internalUpdateSelection()) {
				var regExID = new RegExp(START_MARKER + "(.*?)" + END_MARKER);
				var elements = editorDoc.getElementsByTagName('img');
				for (var i = 0; i < elements.length; i++) {
					var element = elements[i];
					var id = element.id;
					if (id != null && id != '') {
						RegExp.lastIndex=0;
						var matchArray = id.match(regExID);
						if (matchArray != null && matchArray.length > 0) {
							var newId = matchArray[1];
							if (newId.length > 0) {
								element.id = newId;
							} else {
								element.removeAttribute('id');
							}
						}
					} else {
						// no id, must be the new img
						if (height != null && height != '') element.height = height;
						if (width != null && width != '') element.width = width;
						if (alt != null) element.alt = alt;		
					}
				}
			}
		}
		setStatus(STATUS_MODIFIED, null);
	}
}

// Adds a horizontal line.
function addLine() {
	formatText('inserthorizontalrule', null);
}

// Adds a link.
function addLink(url) {
	if (!readOnly && url != null && url != '' && internalUpdateSelection()) {
		if (document.all) {
			if (selectionRange.text == null || selectionRange.text == '') {
				selectionRange.text = url;
				setStatus(STATUS_EXEC_CMD, 1);
				setStatus(STATUS_MODIFIED, null);
			}
			else if (selectionRange.execCommand('createlink', false, url)) {
				setStatus(STATUS_EXEC_CMD, 1);
				setStatus(STATUS_MODIFIED, null);
			}
		}
		else {
			if (selection == null || selection == "") {		
				var urlTextNode = editorDoc.createTextNode(url);
				insertNodeAtSelection(document.getElementById(editorFrameId).contentWindow, urlTextNode);
			}			
			if (editorDoc.execCommand('createlink', false, url)) {
				setStatus(STATUS_EXEC_CMD, 1);
				setStatus(STATUS_MODIFIED, null);
			}
		}
	}
}

// Adds an ordered list.
function addOrderedList() {
	formatText('insertorderedlist', null);
}

// Adds a table.
function addTable(rows, cols, width, summary, caption, tableheaders) {
	if (readOnly) return;
	if (rows == 0) rows = 2;
	if (cols == 0) cols = 2;
	if (width == 0) width = "85%";
	if (internalUpdateSelection()) {
		var table = editorDoc.createElement("table");
		table.cellPadding = "2";
		table.cellSpacing = "0";
		table.border = "1";
		table.width = width;
		table.title = "";
		if (summary != null && summary != '') {
			table.summary = summary;
		}
		if (caption != null && caption != '') {
			table.title = caption;
			table.createCaption();
			var captionNode = editorDoc.createTextNode(caption);
			table.caption.appendChild(captionNode);
		}
		tbody = editorDoc.createElement("tbody");
		for (var i = 0; i < rows; i++) {
			tr = editorDoc.createElement("tr");
			for (var j = 0; j < cols; j++) {
				if (i == 0 && (tableheaders == TABLE_HEADERS_COLS || tableheaders == TABLE_HEADERS_BOTH)) {
					th = editorDoc.createElement("th");
					th.scope = "col";
					th.id = "";
					th.abbr = th.id;
					var headerNode = editorDoc.createTextNode(th.id);
					th.appendChild(headerNode);
					if (!document.all) {
						br = editorDoc.createElement("br");
						th.appendChild(br);
					}
					tr.appendChild(th);
				}
				else if (j == 0 && (tableheaders == TABLE_HEADERS_ROWS || tableheaders == TABLE_HEADERS_BOTH)) {
					th = editorDoc.createElement("th");
					th.scope = "row";
					th.id = "";
					th.abbr = th.id;
					var headerNode = editorDoc.createTextNode(th.id);
					th.appendChild(headerNode);
					if (!document.all) {
						br = editorDoc.createElement("br");
						th.appendChild(br);
					}
					tr.appendChild(th);
				}
				else {
					td = editorDoc.createElement("td");
					if (!document.all) {
						br = editorDoc.createElement("br");
						td.appendChild(br);
					}
					tr.appendChild(td);
				}
			}
			tbody.appendChild(tr);
    	}
		table.appendChild(tbody);
		if (document.all) {
			selectionRange.parentElement().appendChild(table);
		}
		else {
			selectionRange.insertNode(table);
		}
		setStatus(STATUS_EXEC_CMD, 1);
		setStatus(STATUS_MODIFIED, null);			
	}
}

// Adds an unordered list.
function addUnorderedList() {
	formatText('insertunorderedlist', null);
}

// Sets the background color of the selected text.
function backColor(color) {
	if (color != null && color != '') {
		formatText('backcolor', color);
	}
}

// Toggles the 'bold' attribute of the selected text.
function bold() {
	formatText('bold', null);
}

// Copies the selected text to the clipboard.
function copy() {
	if (internalUpdateSelection()) {
		if (editorDoc.execCommand('copy', false, null)) {
			setStatus(STATUS_EXEC_CMD, 1);
		}
	}
}

// Cuts the selected text to the clipboard.
function cut() {
	formatText('cut', null);
}

// Deletes the selected text.
function deleteText() {
	formatText('delete', null);
}

// Finds text.
function findText(text, dir, options) {
	if (text == null || text == "") {
		return;
	}
	else {
		text = decodeString(text);
	}
	
	if (internalUpdateSelection()) {
		if (document.all) {
			selectionRange.collapse(dir < 0);
			if (selectionRange.findText(text, dir, options)) {
				selectionRange.scrollIntoView();
				selectionRange.select();
				selectionRange.collapse(dir < 0);
				setStatus(STATUS_EXEC_CMD, 1);
			}
		}
		else {	
			// find(text, caseSensitive, backwards, wrapAround, wholeWord, searchInFrames, showDialog)
			var caseSensitive = true;
			var backwards = false;
			var wholeWord = true;
			if ((options & 4) == 0) caseSensitive = false;
			if (dir == -1) backwards = true;
			if ((options & 2) == 0) wholeWord = false;
			if (contentWindow.find(text, caseSensitive, backwards, false, wholeWord, false, false)) {
				setStatus(STATUS_EXEC_CMD, 1);
			}
		}
	}
}

// Sets the foreground color of the selected text.
function foreColor(color) {
	if (color != null && color != '') {
		formatText('forecolor', color);
	}
}

// Formats the selected text using the given HTML heading tag.
function formatBlock(tag) {
	if (tag != null && tag != '') {
		formatText('formatblock', tag);
	}
}


var INDENTED_LIST_BAD_HTML_IE = "</li>.*<li style=\"list-style: none\">";
var INDENTED_LIST_BAD_HTML_MOZ = "</li>.*<li style=\"list-style-type: none; list-style-image: none; list-style-position: outside;\">";

// Indents the selected text.
function indent() {
	formatText('indent', null);
	// fix for sub-lists
	var html = document.getElementById(editorId).contentWindow.document.body.innerHTML;
	if (document.all) {
		html = html.replace(INDENTED_LIST_BAD_HTML_IE, "");
	} else {
		// firefox sometimes puts the same as IE, sometimes more junk
		html = html.replace(INDENTED_LIST_BAD_HTML_IE, "");
		html = html.replace(INDENTED_LIST_BAD_HTML_MOZ, "");
	}
	setText(html);
}

// Toggles the 'italic' attribute of the selected text.
function italic() {
	formatText('italic', null);
}

// Center justifies the selected text.
function justifyCenter() {
	formatText('justifycenter', null);
}

// Fully justifies the selected text.
function justifyFull() {
	formatText('justifyfull', null);
}

// Left justifies the selected text.
function justifyLeft() {
	formatText('justifyleft', null);
}

// Right justifies the selected text.
function justifyRight() {
	formatText('justifyright', null);
}

// Outdents the selected text.
function outdent() {
	formatText('outdent', null);
}

// Pastes text from the clipboard.
function paste(sourceURL) {
	if (sourceURL == null) {
		sourceURL = "";
	}
	else {
		sourceURL = decodeString(sourceURL);
	}
	if (document.all) {
		var START_MARKER = "A_-_-_";
		var END_MARKER = ":.:.:";
		// mark img and <a /> links with START_MARKER + src/href + END_MARKER in the id, for later recovery
		var elements = editorDoc.getElementsByTagName('img');
		for (var i = 0; i < elements.length; i++) {
			var element = elements[i];
			var id = element.id;
			element.id = START_MARKER + element.src + END_MARKER + id;
		}
		var elements = editorDoc.getElementsByTagName('a');
		for (var i = 0; i < elements.length; i++) {
			var element = elements[i];
			var id = element.id;
			element.id = START_MARKER + element.href + END_MARKER + id;
		}

		// change the <base> of the document
		var oldBaseHREF = editorDoc.getElementsByTagName('base')[0].href;
		editorDoc.getElementsByTagName('base')[0].href = sourceURL;

		formatText('paste', null);
		
		// restore <base>
		editorDoc.getElementsByTagName('base')[0].href = oldBaseHREF;
	}
	else {
		setStatus(STATUS_EXEC_CMD, 1);
		setStatus(STATUS_MODIFIED, null);
	}
	if (internalUpdateSelection()) {
		try {
			var regExRes = new RegExp("file\:([^=]+)(/resources/)(.+)", "g");
			var regExRef = new RegExp("(.+)(#.+)");
			var regEx = new RegExp("file\:([^=]+)/([^/]+)", "g");	
			var regExID = new RegExp(START_MARKER + "(.*?)" + END_MARKER + "(.*?)");
			var elements = editorDoc.getElementsByTagName('img');
			for (var i = 0; i < elements.length; i++) {
				var element = elements[i];
				var id = element.id;
				if (id != null && id != '') {
					RegExp.lastIndex=0;
					var matchArray = id.match(regExID);
					if (matchArray != null && matchArray.length > 1) {
						element.src = matchArray[1];
						if (matchArray.length > 2 && matchArray[2].length > 0) {
							element.id = matchArray[2];
						}
						else {
							element.removeAttribute('id');
						}
						continue;
					}
				}
				var src = element.src;
				if (src != null && src != '') {
					if (src.indexOf('about:./resources') != -1) {
						// fix for IE 7 when pasting from another RTE
						// IE7 resolves these as "about:./resources/<file>"
						// so remove the "about:."
						src = src.replace("about:", "");
					}
					if (src.indexOf('about:resources') != -1) {
						// fix for IE 7 when pasting from another RTE
						// IE7 sometimes resolves these as "about:resources/<file>"
						// so remove the "about:" and put in "./"
						src = src.replace("about:", "./");
					}
					if (src.indexOf('resources') != -1) {
						element.src = src.replace(regExRes, "./resources/$3");
					}
					else {
						element.src = src.replace(regEx, "./resources/$2");
					}
				}
			}
			var elements = editorDoc.getElementsByTagName('a');
			for (var i = 0; i < elements.length; i++) {
				var element = elements[i];
				var id = element.id;
				if (id != null && id != '') {
					RegExp.lastIndex=0;
					var matchArray = id.match(regExID);
					if (matchArray != null && matchArray.length > 1) {
						element.href = matchArray[1];
						if (matchArray.length > 2 && matchArray[2].length > 0) {
							element.id = matchArray[2];
						}
						else {
							element.removeAttribute('id');
						}
						continue;
					}
				}
				var href = element.href;
				if (href != null && href != '') {
					// fix self-referencing hrefs
					if (href.indexOf('#') != -1) {
						RegExp.lastIndex=0;
						var matchArray = href.match(regExRef);
						if (matchArray != null && matchArray.length > 2) {
							var hrefFile = matchArray[1];
							var ref = matchArray[2];
							if (hrefFile == sourceURL) {
								element.href = ref;
								continue;
							}
						}
					}
					// fix hrefs already in resources
					if (href.indexOf('resources') != -1) {
						element.href = href.replace(regExRes, "./resources/$3");
					}
					// fix hrefs not in resources
					else {
						element.href = href.replace(regEx, "./resources/$2");
					}
				}
			}
		}
		catch (e) {
		}
	}
}

// Redo the previous command.
function redo() {
	formatText('redo', null);
}

// Redo the previous command.
function removeFormat() {
	formatText('removeformat', null);
}



function _replaceAllText(findText, replaceText, options) {
	// this is IE only
	if (document.all) {
		var tempRange =  document.getElementById(editorId).contentWindow.document.body.createTextRange();
		tempRange.moveStart('character', -10000000000);
		do {
			tempRange.collapse();
			if (tempRange.findText(findText, 10000000000, options)) {
				tempRange.text = replaceText;
				tempRange.select();
			} else {		
				break;
			}
		} while (true);
	}
}

// Replaces all text.
function replaceAllText(findText, replaceText, options) {
	if (readOnly || findText == null || findText == "") {
		return;
	}
	else {
		findText = decodeString(findText);
	}
	if (replaceText == null) {
		replaceText = "";
	}
	else {
		replaceText = decodeString(replaceText);
	}
	
	if (document.all) {
		// TODO: Move the insertion point to the start of the HTML
		// and perform a search and replace in the forward direction. 
		_replaceAllText(findText, replaceText, options);
	}
	else {
		// TODO: Emulate the IE implementation.
		var html = document.getElementById(editorId).contentWindow.document.body.innerHTML;
		var optionStr = "/g";
		if ((options & 4) == 0) {
			optionStr += "i";
		}
		var regExp = eval("/" + findText + optionStr);
		html = html.replace(regExp, replaceText);
		setText(html);
	}
	
	setStatus(STATUS_EXEC_CMD, 1);
	setStatus(STATUS_MODIFIED, null);
}

// Replaces text.
function replaceText(replaceText, dir, options) {
	if (readOnly || !internalUpdateSelection()) {
		return;
	}
	if (replaceText == null) {
		replaceText = "";
	}
	else {
		replaceText = decodeString(replaceText);
	}
	if (document.all) {
		selectionRange.text = replaceText;
		if (replaceText != "") {
			selectionRange.moveStart("word", -1);
			selectionRange.select();
			selectionRange.collapse(dir < 0);
		}
	}
	else {
		selectionRange.deleteContents();
		selectionRange.insertNode(editorDoc.createTextNode(replaceText));
	}
	setStatus(STATUS_EXEC_CMD, 1);
	setStatus(STATUS_MODIFIED, null);
}

// Selects all text.
function selectAll() {
	if (internalUpdateSelection()) {
		if (editorDoc.execCommand('selectall', false, null)) {
			setStatus(STATUS_EXEC_CMD, 1);
		}
	}
}

// Sets the font name for the selected text.
function setFontName(name) {
	if (internalUpdateSelection()) {
		if (name != null) {
			if (name == '') {
				formatText('removeFormat');
			} else {
				formatText('fontname', name);
			}
		}
	}
}

// Sets the font size for the selected text.
function setFontSize(size) {
	if (internalUpdateSelection()) {
		if (size != null) {
			if (size == '') {
				formatText('removeFormat');
			} else {
				formatText('fontsize', size);
			}
		}
	}
}

// Sets the font style for the selected text.
function setFontStyle(style) { 
	if (!readOnly && style != null && style != '' && internalUpdateSelection()) {
		try {
			if (document.all) {
				selectionRange.execCommand("removeformat");
				selectionRange.parentElement().removeAttribute("className");
			}
		}
		catch (e) {
		}
		if (style == "<quote>") {
			formatText('formatblock', '<p>');
			if (document.all) {
				selectionRange.parentElement().className = "quote";
			}
			else {
				selection.focusNode.parentNode.className = "quote";
			}
		}
		else if (style == "<code>") {
			formatText('formatblock', '<p>');
			if (document.all) {
				selectionRange.parentElement().className = "codeSample";
			}
			else {
				selection.focusNode.parentNode.className = "codeSample";
			}
		}
		else {
			if (!document.all && style == "<p>") {
				// A hack to get rid of the "className" attribute in Mozilla/Firefox.
				formatText('formatblock', '<h4>');
			}
			formatText('formatblock', style);
		}
	}
}

// Sets whether the content can be edited.
function setEditable(editable) {
	var doc = document.getElementById(editorId).contentWindow.document;
    if (editable != null && editable == 'true') {
		if (document.all) {
			doc.body.contentEditable = "true";
		}
		else {
			doc.designMode = "on";
		}
		readOnly = false;
	}
	else {
		if (document.all) {		
			doc.body.contentEditable = "false";
		}
		else {
			doc.designMode = "off";
		}
		readOnly = true;
	}
	setStatus(STATUS_EXEC_CMD, 1);	
}

// Toggles the 'strike-through' attribute of the selected text.
function strikeThrough() {
	formatText('strikethrough', size);
}

// Toggles the 'subscript' attribute of the selected text.
function subscript() {
	formatText('subscript', null);
}

// Toggles the 'superscript' attribute of the selected text.
function superscript() {
	formatText('superscript', null);
}

// Toggles the 'underline' attribute of the selected text.
function underline() {
	formatText('underline', null);
}

// Converts a link to normal text.
function unlink() {
	formatText('unlink', null);
}
