//------------------------------------------------------------------------------
// 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_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 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_Z = 90;

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 editorId;
var editorCSS;
var baseHREF;
var supportRichTextEditing = true;
var editorDoc;
var selection;
var selectionRange;
var readOnly = false;
var initialized = false;
var modified = false;

// 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;
		if (keyCode == 99) {
			keyCode = KEY_C;
		}
		else if (keyCode == 102) {
			keyCode = KEY_F;
		}
		else if (keyCode == 115) {
			keyCode = KEY_S;
		}
		else if (keyCode == 118) {
			keyCode = KEY_V;
		}
		else if (keyCode == 122) {
			keyCode = KEY_Z;
		}
	}
	var ctrlKey = event.ctrlKey;
	var shiftKey = event.shiftKey;
		
	if (readOnly) {
		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:
				return;
			case KEY_C:
				if (ctrlKey) {
					return;
				}
				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);
				}
				break;
			case KEY_S:
				if (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);
					}
				}
				break;		
		}
		if (document.all) {
			event.keyCode = -1;
			event.returnValue = false;
		}
		else {
			event.preventDefault();
		}	
		return;
	}
	
	if (ctrlKey) {
		switch (keyCode) {
			case KEY_F:
				if (document.all) {
					event.keyCode = -1;
					event.returnValue = false;
				}
				else {
					event.preventDefault();
				}
				setStatus(STATUS_KEY_DOWN, CMD_FIND_TEXT);
				break;
			case KEY_S:
				if (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);
					}
				}
				break;		
			case KEY_V:
				if (document.all) {
					event.keyCode = -1;
					event.returnValue = false;
					setStatus(STATUS_KEY_DOWN, CMD_PASTE);
				}
				else {
					// Workaround Mozilla/Firefox paste issues.
					setTimeout("setStatus(STATUS_KEY_DOWN, CMD_PASTE);", 10);
				}
				break;
		}
	}
	
	if (keyCode == KEY_BACKSPACE) {
		setStatus(STATUS_MODIFIED, null);
	}
	if (ctrlKey && keyCode == KEY_Z) {
		setStatus(STATUS_MODIFIED, null);
	}
}

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 ("addEventListener" in doc) {
		doc.addEventListener("keypress", keyPressed, true);
	}
	else if ("attachEvent" in doc) {
		doc.attachEvent("onkeydown", keyPressed);
	}

	setStatus(STATUS_EXEC_CMD, 1);
}

// 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;
}

// Returns the HTML source.
function getText() {
	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\"");
	}
	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);
		setInnerHTML(html);
		modified = false;
		setStatus(STATUS_EXEC_CMD, 1);
	}
}

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

// Updates the current selection and selection range.
function updateSelection() {
	if (!supportRichTextEditing) {
		return false;
	}	
	
	contentWindow = document.getElementById(editorId).contentWindow;
	editorDoc = contentWindow.document;
	
	contentWindow.focus();
	
	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();
		}
	}
	return true;
}

// Sets focus to this editor.
function setFocus() {
	if (updateSelection()) {
		setStatus(STATUS_EXEC_CMD, 1);	
	}
}

// Gets the selected text.
function getSelectedText() {
	if (updateSelection()) {
		if (document.all) {	
			if (selection != null) {
				if (selection.type == 'Text' && selectionRange != null && selectionRange.text != '') {
					setStatus(STATUS_SELECT_TEXT, selectionRange.text);
					return;
				}
				else if (selection.type == 'Control') {
					setStatus(STATUS_SELECT_CONTROL, null);
					return;
				}
			}
			setStatus(STATUS_SELECT_NONE, null);
		}
		else {
			if (selection != null) {
				if (selectionRange != null) {
					var container = selectionRange.startContainer;
					if (container.nodeType == 1) {
						setStatus(STATUS_SELECT_CONTROL, null);
						return;
					}
					if (container.nodeType == 3) {
						setStatus(STATUS_SELECT_TEXT, container.nodeValue.substring(selectionRange.startOffset, selectionRange.endOffset));
						return;
					}
				}
				setStatus(STATUS_SELECT_NONE, null);
			}
		}
	}
}
	
// 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() == 'elementlinkwithtype') {
 			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_MODIFIED, null);
	}
}

// Formats the selected text.
function formatText(command, option) {
	if (!readOnly && updateSelection()) {
		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 (updateSelection()) {
			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 (url != null && url != '') {
		formatText('insertimage', url);
	}
	if (updateSelection()) {	
		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;		
		}
	}
}

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

// Adds a link.
function addLink(url) {
	if (!readOnly && url != null && url != '' && updateSelection()) {
		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 = 200;
	if (updateSelection()) {
		var table = editorDoc.createElement("table");
		table.cellPadding = "2";
		table.cellSpacing = "2";
		table.border = "1";
		table.width = width;
		if (summary != null && summary != '') {
			table.summary = summary;
		}
		if (caption != null && 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";
					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";
					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 (updateSelection()) {
		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 (updateSelection()) {
		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);
	}
}

// Indents the selected text.
function indent() {
	formatText('indent', null);
}

// 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 (updateSelection()) {
		try {
			var regExRes = new RegExp("file\:([^=]+)(/resources/)(.+)", 'g');
			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('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 != '') {
					if (href.indexOf('resources') != -1) {
						element.href = href.replace(regExRes, "./resources/$3");
					}
					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);
}

// Replaces all text (internal implementation).
function _replaceAllText(findText, replaceText, dir, options) {
	do {
		if (updateSelection()) {
			if (document.all) {		
				selectionRange.collapse(dir < 0);
				if (selectionRange.findText(findText, dir, options)) {
					selectionRange.text = replaceText;
					if (replaceText != "") {
						selectionRange.moveStart("word", -1);
						selectionRange.select();
						selectionRange.collapse(dir < 0);
					}
				}
				else {		
					break;
				}
			}
			else {	
				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(findText, caseSensitive, backwards, false, wholeWord, false, false)) {
					selectionRange.deleteContents();
					selectionRange.insertNode(editorDoc.createTextNode(replaceText));					
				}
				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, -1, options);
		_replaceAllText(findText, replaceText, 1, options);
		_replaceAllText(findText, replaceText, -1, 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 || !updateSelection()) {
		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 (updateSelection()) {
		if (editorDoc.execCommand('selectall', false, null)) {
			setStatus(STATUS_EXEC_CMD, 1);
		}
	}
}

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

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

// Sets the font style for the selected text.
function setFontStyle(style) { 
	if (!readOnly && style != null && style != '' && updateSelection()) {
		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);
}
