/*
Copyright 2004-2005, Paul Tero, Version 1.1.1
Version 1.1.0: made the line spacing more cross browser compatible.
Version 1.1.1: added a small PageEditor class to edit and save a whole page

This is an HTML editor written entirely in Javascript. When instantiated it displays and fills
an iframe with the provided HTML, and updates a form element whenever the HTML changes. (Note 
that the incoming HTML must be all on one line or it will cause a Javascript error.)

This editor is designed to work in three different situations: editing in IE, editing in 
Firefox, and editing the HTML in a plain text box. To handle these situations, it strips
out all <p> and <br> tags when saving, and it is up to the displaying function to put them
back in as appropriate.

This now works on IE and Firefox. It saves the text to a form element whenever the mouse goes
outside of the container div or the iframe's body loses focus. The latter method works only
on IE.

When the text is saved, extraneous tags are stripped out. This is in case you copied and
pasted text from another site. It also converts Firefox's span tags to strong, em and u,
so that the output is compatible across browsers.

There are a few issues to resolve:
-styling the iframe's content

The container can be styled using the div.htmleditor class.
*/

function show(o) {var r="", i=1; for (var p in o) {if (i++ % 20 ==0) {alert (r); r = "";} r += p + ": " + o[p] +"\n";} alert(r);}


//Create a new HtmlEditor object
function HtmlEditor (editorname, rows, columns, editorvalue, formname, withoutstyles) {
	this.editorname = editorname; //name of the editor
	this.fullname = "editor_" + this.editorname; //full name to store for this editor
	this.rows = rows ? rows : 10; //default rows
	this.columns = columns ? columns : 50; //default columns
	//replace all <NEWLINE> tags (which must be used when passing text by Javascript)
	editorvalue = editorvalue.replace (new RegExp ("<NEWLINE>", "g"), "\r\n");
	this.editorvalue = editorvalue ? editorvalue : ""; //the value of the editor
	this.formname = formname ? formname : "0"; //the form this editor appears in
	this.withoutstyles = withoutstyles.toLowerCase(); //leave off these styles from the editor
	//Figure out if I am capable of displaying an editor. The next lines used to have the condition
	//(agent.indexOf("MSIE")>=0) so that it only worked in IE, but then I got it working in Firefox.
	var agent = navigator.userAgent;
	this.showeditor = document.execCommand && (agent.indexOf("Mac")==-1);
	//this.showeditor = agent.indexOf("MSIE")>=0; //it should only work in IE (Firefox doesn't style properly)
	document[this.fullname] = this; //save this object to a vairable so it can be reference later
	document.writeln (this.format()); //display this editor
	this.setBody(0); //this sets up a time delay to set various aspects of the iframe's body
}

//Get a reference to the document of the iframe
function HtmlEditor_getHtmlEditor() {
	return document.getElementById(this.editorname + "_iframe").contentWindow.document;
}

//This is called when an editor loses focus. It saves what has been typed
function HtmlEditor_save() {
	if (!this.showeditor) return;  //don't do anything as I am not an editor
	var savetext = this.getHtmlEditor().body.innerHTML; //get the text to save
	savetext = savetext.formatForSaving(); //strip <p>, <br> and other tags
	document.forms[this.formname][this.editorname].value = savetext;
	//document.forms[this.formname].results.value = savetext; //for debugging
}

//This is called when the iframe has loaded
function HtmlEditor_setBody(iteration) {
	if (iteration == 1) {
		this.getHtmlEditor().createElement("body"); //create a body within the iframe
		this.getHtmlEditor().body.contenteditable = true;
		this.getHtmlEditor().designMode = "on";
	}
	if (iteration == 2) {
		this.getHtmlEditor().body.innerHTML = this.editorvalue.addLineBreaks(); //the value
		this.save(); //save the results so that the input tag has the correct information to start with
		this.getHtmlEditor().body.style.margin = "0px";
		this.getHtmlEditor().body.onblur = new Function ("document." + this.fullname + ".save()"); //works in IE only
	}
	if (iteration < 2) {setTimeout ("document." + this.fullname + ".setBody(" + (iteration+1) + ")", 100);}
}

//This is called when a style is set for the editor 
function HtmlEditor_setStyle (command, userinterface, extra) {
	if (!this.showeditor) return;  //don't do anything as I am not an editor
	this.getHtmlEditor().execCommand (command, userinterface, extra); //run the command
}

//This returns a button (such as bold or italics). Pass in the button value, the execCommand command
//and whether to show an userinterface (for instance to ask for links).
function HtmlEditor_getButton (display, style, command, userprompt, userdefault) {
	var r = "";
	if (this.withoutstyles.match (new RegExp ("\\b" + display + "\\b", "i"))) return r; //not displaying this style
	r += "<input name=\"" + this.editorname + "_" + command + "\" type=\"button\" value=\"" + display + "\" ";
	if (style) r += "style=\"" + style + "\" ";
	r += "onclick=\"var extrainfo=" + (userprompt ? "prompt('" + userprompt + "','" + userdefault + "')" : "false");
	r += "; document." + this.fullname + ".setStyle ('" + command + "', false, extrainfo)\" />";
	return r;
}

//This returns a list of items (such as the font sizes)
function HtmlEditor_getList (display, options, command) {
	var r = "";
	if (this.withoutstyles.match (new RegExp ("\\b" + display + "\\b", "i"))) return r; //not displaying this style
	r += "<select name=\"" + this.editorname + "_" + command + "\" ";
	r += "onchange=\"document." + this.fullname + ".setStyle ";
	r += "('" + command + "', false, this.options[this.selectedIndex].value); ";
	r += "this.selectedIndex=0;\" />";
	r += "<option value=\"\">" + display + "</option>";
	for (var i=0; i<options.length; i++) {r += "<option value=\"" + options[i] + "\">" + options[i] + "</option>";}
	r += "</select>";
	return r;
}

//Get all the editor buttons
function HtmlEditor_getButtons() {
	r = "";
	r += this.getButton ("I", "font-style:italic", "Italic", false, false) + "\n";
	r += this.getButton ("B", "font-weight:bold", "Bold", false, false) + "\n";
	r += this.getButton ("U", "text-decoration: underline", "Underline", false, false) + "\n";
	r += this.getButton ("A", "", "CreateLink", "Please enter the link below", "http://") + "\n";
	r += this.getButton ("-", "font-weight:bold", "InsertUnorderedList", false, false) + "\n";
	r += this.getList ("Size", new Array(1,2,3,4,5,6,7), "FontSize") + "\n";
	return r;
}

//This function outputs the HTML editor after checking the browser capabilities
function HtmlEditor_format() {
	var r = ""; //store result here
	if (this.showeditor) {
		r += "<input name=\"" + this.editorname + "\" type=\"hidden\" />\n"; //this is initialised in save
		r += "<div class=\"htmleditor\" onmouseout=\"document." + this.fullname + ".save()\">"; //saving in Firefox
		r += "Format text (press Shift-Return for a single line break): ";
		r += this.getButtons(); //get the buttons
		r += "<br/>\n";
		r += "<iframe id=\"" + this.editorname + "_iframe\" ";
		r += "width=\"" + Math.ceil(this.columns*10) + "\" height=\"" + Math.ceil(this.rows*16) + "\">";
		r += "</iframe>";
		r += "</div>";
	} else {
		r += "<textarea name=\"" + this.editorname + "\" rows=\"" + this.rows + "\" cols=\"" + this.columns + "\">";
		r += this.editorvalue + "</textarea>\n"; //put in normal carriage returns
	}
	return r;
}

//When using the HTML editor, replace all new lines with HTML line breaks.
function String_addLineBreaks () {
	var mytext = this;
	mytext = mytext.replace (new RegExp ("\r?\n", "g"), "<br/>\n");
	return mytext;
}


//Replace brs with new lines. Not currently used.
function String_removeLineBreaks () {
	var mytext = this;
	mytext = mytext.replace (new RegExp ("<br/?>", "gi"), "\n");
	return mytext;
}



//Format the text for saving. This involves three things:
//1) stripping br's and p's from the HTML, as this is more consistent when being edited by different browsers
//2) replace Firefox's way of doing bold and italics (using span and font) with other tags
//3) leave in all allowed tags, including the size for fonts
//4) remove all other extraneous tags (in case they copied and pasted into the box from elsewhere)
function String_formatForSaving() {
	var mytext = this; //get the string's text
	//1) first replace paragraph and br tags - this just makes it easier to be cross-browser compatible. It
	//also removes newlines after br's because browsers (Firefox) at least puts them in automatically.
	mytext = mytext.replace (new RegExp ("<p>\r?\n?", "gi"), "<NEWLINE><NEWLINE>"); //replace paragraphs
	mytext = mytext.replace (new RegExp ("</p>\r?\n?", "gi"), ""); //remove closing paragraph tags
	mytext = mytext.replace (new RegExp ("<br/?>\r?\n?", "gi"), "<NEWLINE>"); //replace brs with single line breaks
	mytext = mytext.replace (new RegExp ("\r?\n", "g"), " "); //remove ordinary newlines (in wrapped text)
	mytext = mytext.replace (new RegExp ("<NEWLINE>", "g"), "\n"); //put back the new lines
	//when you type a paragraph break in IE it adds a <p> at the beginning which is unwanted so it's removed here
	mytext = mytext.replace (new RegExp ("^\n+", ""), ""); //remove newlines at the beginning
	//2) now replace Firefox's span tags with u, em and strong tags
	sexp = new RegExp ("<span", "ig");
	uexp = new RegExp ("<span style=\"text-decoration: underline;\">([^<]*)</span>", "ig");
	iexp = new RegExp ("<span style=\"font-style: italic;\">([^<]*)</span>", "ig");
	bexp = new RegExp ("<span style=\"font-weight: bold;\">([^<]*)</span>", "ig");
	var i=0; while (mytext.match (sexp) && (i<100)) { //while the text still has span tags
		mytext = mytext.replace (uexp, "<u>$1</u>");
		mytext = mytext.replace (iexp, "<em>$1</em>");
		mytext = mytext.replace (bexp, "<strong>$1</strong>");
	}
	//3) now process all the other tags, replacing the < and > with special characters to they aren't removed
	//"font size" has to be processed before "font". Note that this will still leave in font tags even though
	//all the attributes may have been stripped (so they are removed later). It does the same for links. The
	//a href=".*" matches the opening tag and the a matches the closing tag </a>.
	//These are the allowed opening and closing tags: lists, fonts and links. ul must come before u
	var tags = new Array ("ul", "li", "font size=\".\"", "font", "b", "strong", "em", "i", "u", "a href=[^>]*",  "a");
	for (var i=0; i<tags.length; i++) { //loop through the allowed tags
		var expression = "<(/?)(" + tags[i] + ")([^>]*)>"; //match the tag
		mytext = mytext.replace (new RegExp (expression, "ig"), "TAGSTART$1$2TAGEND");
	}
	//4) remove all the other tags
	//mytext = mytext.replace (new RegExp ("</?.[^>]*>", "g"), ""); //replace other tags
	mytext = mytext.replace (new RegExp ("TAGSTART", "g"), "<"); //put good tags back in
	mytext = mytext.replace (new RegExp ("TAGEND", "g"), ">"); //put good tags back in
	mytext = mytext.replace (new RegExp ("<font>([^<]*)</font>", "ig"), "$1"); //remove empty font tags
	//mytext = mytext.replace (new RegExp ("<a>([^<]*)</a>", "ig"), "$1"); //remove empty links
	return mytext;
}






//The PageEditor is a stripped down version of the HtmlEditor. It is used to edit a whole page, where
//the iframe is already on the screen.
function PageEditor (editorname, withoutstyles) {
	this.editorname = editorname; //name of the editor
	this.fullname = "editor_" + this.editorname; //full name to store for this editor
	this.withoutstyles = withoutstyles.toLowerCase(); //leave off these styles from the editor
	this.showeditor = true; //we will always show the editor
	document[this.fullname] = this; //save this object to a vairable so it can be reference later
	this.setBody(0); //run set body, which will go in intervals
}

function PageEditor_getHtmlEditor() {
	var e = document.getElementById(this.editorname);
	return e && e.contentWindow ? e.contentWindow.document : null;
}
function PageEditor_setBody (iteration) { //try setting the body every few milliseconds
	var editor = this.getHtmlEditor();
	if (iteration == 0) {
		if (!editor || !editor.body) setTimeout ("document." + this.fullname + ".setBody(0)", 500);
		else setTimeout ("document." + this.fullname + ".setBody(1)", 500); //wait again to set the body
	}
	if (iteration == 1) {
		this.getHtmlEditor().body.contenteditable = true;
		this.getHtmlEditor().designMode = "on";
	}
}
function PageEditor_saveChanges (formelement) {
	var firsttag = this.getHtmlEditor().firstChild.nodeName; //get the first node name
	var innerhtml = this.getHtmlEditor().firstChild.innerHTML; //get the inner HTML
	var savetext = "<" + firsttag + ">" + innerhtml + "</" + firsttag + ">\n"; //the text to save
	formelement.value = savetext; //save this text to the form element
	return true;
}




HtmlEditor.prototype.getHtmlEditor = HtmlEditor_getHtmlEditor;
HtmlEditor.prototype.getButtons = HtmlEditor_getButtons;
HtmlEditor.prototype.getButton = HtmlEditor_getButton;
HtmlEditor.prototype.getList = HtmlEditor_getList;
HtmlEditor.prototype.setStyle = HtmlEditor_setStyle;
HtmlEditor.prototype.save = HtmlEditor_save;
HtmlEditor.prototype.format = HtmlEditor_format;
HtmlEditor.prototype.setBody = HtmlEditor_setBody;

PageEditor.prototype.getHtmlEditor = PageEditor_getHtmlEditor;
PageEditor.prototype.getButtons = HtmlEditor_getButtons;
PageEditor.prototype.getButton = HtmlEditor_getButton;
PageEditor.prototype.getList = HtmlEditor_getList;
PageEditor.prototype.setStyle = HtmlEditor_setStyle;
PageEditor.prototype.setBody = PageEditor_setBody;
PageEditor.prototype.saveChanges = PageEditor_saveChanges;

String.prototype.formatForSaving = String_formatForSaving;
String.prototype.addLineBreaks = String_addLineBreaks;
String.prototype.removeLineBreaks = String_removeLineBreaks;


