Annotation of loncom/html/htmlarea/htmlarea.js, revision 1.5

1.3       www         1: // htmlArea v3.0 - Copyright (c) 2002-2004 interactivetools.com, inc.
                      2: // This copyright notice MUST stay intact for use (see license.txt).
1.1       www         3: //
1.3       www         4: // Portions (c) dynarch.com, 2003-2004
1.1       www         5: //
                      6: // A free WYSIWYG editor replacement for <textarea> fields.
                      7: // For full source code and docs, visit http://www.interactivetools.com/
                      8: //
1.3       www         9: // Version 3.0 developed by Mihai Bazon.
                     10: //   http://dynarch.com/mishoo
1.1       www        11: //
1.5     ! albertel   12: // $Id: htmlarea.js,v 1.4 2004/06/02 00:55:16 www Exp $
1.3       www        13: 
                     14: if (typeof _editor_url == "string") {
                     15: 	// Leave exactly one backslash at the end of _editor_url
                     16: 	_editor_url = _editor_url.replace(/\x2f*$/, '/');
                     17: } else {
                     18: 	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.");
                     19: 	_editor_url = '';
                     20: }
                     21: 
                     22: // make sure we have a language
                     23: if (typeof _editor_lang == "string") {
                     24: 	_editor_lang = _editor_lang.toLowerCase();
                     25: } else {
                     26: 	_editor_lang = "en";
                     27: }
1.1       www        28: 
                     29: // Creates a new HTMLArea object.  Tries to replace the textarea with the given
                     30: // ID with it.
                     31: function HTMLArea(textarea, config) {
                     32: 	if (HTMLArea.checkSupportedBrowser()) {
                     33: 		if (typeof config == "undefined") {
                     34: 			this.config = new HTMLArea.Config();
                     35: 		} else {
                     36: 			this.config = config;
                     37: 		}
                     38: 		this._htmlArea = null;
                     39: 		this._textArea = textarea;
                     40: 		this._editMode = "wysiwyg";
                     41: 		this.plugins = {};
                     42: 		this._timerToolbar = null;
1.3       www        43: 		this._timerUndo = null;
                     44: 		this._undoQueue = new Array(this.config.undoSteps);
                     45: 		this._undoPos = -1;
                     46: 		this._customUndo = false;
1.1       www        47: 		this._mdoc = document; // cache the document, we need it in plugins
1.3       www        48: 		this.doctype = '';
1.1       www        49: 	}
                     50: };
                     51: 
1.3       www        52: // load some scripts
                     53: (function() {
                     54: 	var scripts = HTMLArea._scripts = [ _editor_url + "htmlarea.js",
                     55: 					    _editor_url + "dialog.js",
                     56: 					    _editor_url + "popupwin.js",
                     57: 					    _editor_url + "lang/" + _editor_lang + ".js" ];
                     58: 	var head = document.getElementsByTagName("head")[0];
                     59: 	// start from 1, htmlarea.js is already loaded
                     60: 	for (var i = 1; i < scripts.length; ++i) {
                     61: 		var script = document.createElement("script");
                     62: 		script.src = scripts[i];
                     63: 		head.appendChild(script);
                     64: 	}
                     65: })();
                     66: 
                     67: // cache some regexps
                     68: HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig;
                     69: HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i;
                     70: HTMLArea.RE_head    = /<head>((.|\n)*?)<\/head>/i;
                     71: HTMLArea.RE_body    = /<body>((.|\n)*?)<\/body>/i;
                     72: 
1.1       www        73: HTMLArea.Config = function () {
                     74: 	this.version = "3.0";
                     75: 
                     76: 	this.width = "auto";
                     77: 	this.height = "auto";
                     78: 
                     79: 	// enable creation of a status bar?
                     80: 	this.statusBar = true;
                     81: 
1.3       www        82: 	// maximum size of the undo queue
                     83: 	this.undoSteps = 20;
                     84: 
                     85: 	// the time interval at which undo samples are taken
                     86: 	this.undoTimeout = 500;	// 1/2 sec.
                     87: 
1.1       www        88: 	// the next parameter specifies whether the toolbar should be included
                     89: 	// in the size or not.
                     90: 	this.sizeIncludesToolbar = true;
                     91: 
1.3       www        92: 	// if true then HTMLArea will retrieve the full HTML, starting with the
                     93: 	// <HTML> tag.
                     94: 	this.fullPage = false;
                     95: 
1.1       www        96: 	// style included in the iframe document
1.3       www        97: 	this.pageStyle = "";
                     98: 
                     99: 	// set to true if you want Word code to be cleaned upon Paste
                    100: 	this.killWordOnPaste = false;
                    101: 
                    102: 	// BaseURL included in the iframe document
                    103: 	this.baseURL = document.baseURI || document.URL;
                    104: 	if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/))
                    105: 		this.baseURL = RegExp.$1 + "/";
1.1       www       106: 
                    107: 	// URL-s
                    108: 	this.imgURL = "images/";
                    109: 	this.popupURL = "popups/";
                    110: 
                    111: 	/** CUSTOMIZING THE TOOLBAR
                    112: 	 * -------------------------
                    113: 	 *
                    114: 	 * It is recommended that you customize the toolbar contents in an
                    115: 	 * external file (i.e. the one calling HTMLArea) and leave this one
                    116: 	 * unchanged.  That's because when we (InteractiveTools.com) release a
                    117: 	 * new official version, it's less likely that you will have problems
                    118: 	 * upgrading HTMLArea.
                    119: 	 */
                    120: 	this.toolbar = [
                    121: 		[ "fontname", "space",
                    122: 		  "fontsize", "space",
                    123: 		  "formatblock", "space",
1.3       www       124: 		  "bold", "italic", "underline", "strikethrough", "separator",
                    125: 		  "subscript", "superscript", "separator",
1.1       www       126: 		  "copy", "cut", "paste", "space", "undo", "redo" ],
1.3       www       127: 
1.1       www       128: 		[ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
1.3       www       129: 		  "lefttoright", "righttoleft", "separator",
1.1       www       130: 		  "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
1.3       www       131: 		  "forecolor", "hilitecolor", "separator",
1.1       www       132: 		  "inserthorizontalrule", "createlink", "insertimage", "inserttable", "htmlmode", "separator",
                    133: 		  "popupeditor", "separator", "showhelp", "about" ]
1.3       www       134: 	];
1.1       www       135: 
                    136: 	this.fontname = {
                    137: 		"Arial":	   'arial,helvetica,sans-serif',
                    138: 		"Courier New":	   'courier new,courier,monospace',
                    139: 		"Georgia":	   'georgia,times new roman,times,serif',
                    140: 		"Tahoma":	   'tahoma,arial,helvetica,sans-serif',
                    141: 		"Times New Roman": 'times new roman,times,serif',
                    142: 		"Verdana":	   'verdana,arial,helvetica,sans-serif',
                    143: 		"impact":	   'impact',
                    144: 		"WingDings":	   'wingdings'
                    145: 	};
                    146: 
                    147: 	this.fontsize = {
                    148: 		"1 (8 pt)":  "1",
                    149: 		"2 (10 pt)": "2",
                    150: 		"3 (12 pt)": "3",
                    151: 		"4 (14 pt)": "4",
                    152: 		"5 (18 pt)": "5",
                    153: 		"6 (24 pt)": "6",
                    154: 		"7 (36 pt)": "7"
                    155: 	};
                    156: 
                    157: 	this.formatblock = {
                    158: 		"Heading 1": "h1",
                    159: 		"Heading 2": "h2",
                    160: 		"Heading 3": "h3",
                    161: 		"Heading 4": "h4",
                    162: 		"Heading 5": "h5",
                    163: 		"Heading 6": "h6",
                    164: 		"Normal": "p",
                    165: 		"Address": "address",
                    166: 		"Formatted": "pre"
                    167: 	};
                    168: 
                    169: 	this.customSelects = {};
                    170: 
                    171: 	function cut_copy_paste(e, cmd, obj) {
1.3       www       172: 		e.execCommand(cmd);
1.1       www       173: 	};
                    174: 
                    175: 	// ADDING CUSTOM BUTTONS: please read below!
                    176: 	// format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]"
                    177: 	//    - ID: unique ID for the button.  If the button calls document.execCommand
                    178: 	//	    it's wise to give it the same name as the called command.
                    179: 	//    - ACTION: function that gets called when the button is clicked.
                    180: 	//              it has the following prototype:
                    181: 	//                 function(editor, buttonName)
                    182: 	//              - editor is the HTMLArea object that triggered the call
                    183: 	//              - buttonName is the ID of the clicked button
                    184: 	//              These 2 parameters makes it possible for you to use the same
                    185: 	//              handler for more HTMLArea objects or for more different buttons.
                    186: 	//    - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N)
                    187: 	//    - Icon: path to an icon image file for the button (TODO: use one image for all buttons!)
                    188: 	//    - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.
                    189: 	this.btnList = {
1.3       www       190: 		bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ],
                    191: 		italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ],
                    192: 		underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ],
                    193: 		strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ],
                    194: 		subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ],
                    195: 		superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ],
                    196: 		justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ],
                    197: 		justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ],
                    198: 		justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ],
                    199: 		justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ],
                    200: 		insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ],
                    201: 		insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ],
                    202: 		outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ],
                    203: 		indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ],
                    204: 		forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ],
                    205: 		hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ],
                    206: 		inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ],
                    207: 		createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ],
                    208: 		insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ],
                    209: 		inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ],
                    210: 		htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ],
                    211: 		popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ],
                    212: 		about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ],
                    213: 		showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ],
                    214: 		undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ],
                    215: 		redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ],
                    216: 		cut: [ "Cut selection", "ed_cut.gif", false, cut_copy_paste ],
                    217: 		copy: [ "Copy selection", "ed_copy.gif", false, cut_copy_paste ],
                    218: 		paste: [ "Paste from clipboard", "ed_paste.gif", false, cut_copy_paste ],
                    219: 		lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ],
                    220: 		righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ]
1.1       www       221: 	};
                    222: 	/* ADDING CUSTOM BUTTONS
                    223: 	 * ---------------------
                    224: 	 *
                    225: 	 * It is recommended that you add the custom buttons in an external
                    226: 	 * file and leave this one unchanged.  That's because when we
                    227: 	 * (InteractiveTools.com) release a new official version, it's less
                    228: 	 * likely that you will have problems upgrading HTMLArea.
                    229: 	 *
                    230: 	 * Example on how to add a custom button when you construct the HTMLArea:
                    231: 	 *
                    232: 	 *   var editor = new HTMLArea("your_text_area_id");
                    233: 	 *   var cfg = editor.config; // this is the default configuration
                    234: 	 *   cfg.btnList["my-hilite"] =
                    235: 	 *	[ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action
                    236: 	 *	  "Highlight selection", // tooltip
                    237: 	 *	  "my_hilite.gif", // image
                    238: 	 *	  false // disabled in text mode
                    239: 	 *	];
                    240: 	 *   cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar
                    241: 	 *
                    242: 	 * An alternate (also more convenient and recommended) way to
                    243: 	 * accomplish this is to use the registerButton function below.
                    244: 	 */
1.3       www       245: 	// initialize tooltips from the I18N module and generate correct image path
1.1       www       246: 	for (var i in this.btnList) {
                    247: 		var btn = this.btnList[i];
1.3       www       248: 		btn[1] = _editor_url + this.imgURL + btn[1];
1.1       www       249: 		if (typeof HTMLArea.I18N.tooltips[i] != "undefined") {
                    250: 			btn[0] = HTMLArea.I18N.tooltips[i];
                    251: 		}
                    252: 	}
                    253: };
                    254: 
                    255: /** Helper function: register a new button with the configuration.  It can be
                    256:  * called with all 5 arguments, or with only one (first one).  When called with
                    257:  * only one argument it must be an object with the following properties: id,
                    258:  * tooltip, image, textMode, action.  Examples:
                    259:  *
                    260:  * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...});
                    261:  * 2. config.registerButton({
                    262:  *      id       : "my-hilite",      // the ID of your button
                    263:  *      tooltip  : "Hilite text",    // the tooltip
                    264:  *      image    : "my-hilite.gif",  // image to be displayed in the toolbar
                    265:  *      textMode : false,            // disabled in text mode
                    266:  *      action   : function(editor) { // called when the button is clicked
                    267:  *                   editor.surroundHTML('<span class="hilite">', '</span>');
                    268:  *                 },
                    269:  *      context  : "p"               // will be disabled if outside a <p> element
                    270:  *    });
                    271:  */
                    272: HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
                    273: 	var the_id;
                    274: 	if (typeof id == "string") {
                    275: 		the_id = id;
                    276: 	} else if (typeof id == "object") {
                    277: 		the_id = id.id;
                    278: 	} else {
                    279: 		alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
                    280: 		return false;
                    281: 	}
                    282: 	// check for existing id
                    283: 	if (typeof this.customSelects[the_id] != "undefined") {
1.3       www       284: 		// alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
1.1       www       285: 	}
                    286: 	if (typeof this.btnList[the_id] != "undefined") {
1.3       www       287: 		// alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
1.1       www       288: 	}
                    289: 	switch (typeof id) {
                    290: 	    case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
                    291: 	    case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
                    292: 	}
                    293: };
                    294: 
                    295: /** The following helper function registers a dropdown box with the editor
                    296:  * configuration.  You still have to add it to the toolbar, same as with the
                    297:  * buttons.  Call it like this:
                    298:  *
                    299:  * FIXME: add example
                    300:  */
                    301: HTMLArea.Config.prototype.registerDropdown = function(object) {
                    302: 	// check for existing id
                    303: 	if (typeof this.customSelects[object.id] != "undefined") {
1.3       www       304: 		// alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
1.1       www       305: 	}
                    306: 	if (typeof this.btnList[object.id] != "undefined") {
1.3       www       307: 		// alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
1.1       www       308: 	}
                    309: 	this.customSelects[object.id] = object;
                    310: };
                    311: 
1.3       www       312: /** Call this function to remove some buttons/drop-down boxes from the toolbar.
                    313:  * Pass as the only parameter a string containing button/drop-down names
                    314:  * delimited by spaces.  Note that the string should also begin with a space
                    315:  * and end with a space.  Example:
                    316:  *
                    317:  *   config.hideSomeButtons(" fontname fontsize textindicator ");
                    318:  *
                    319:  * It's useful because it's easier to remove stuff from the defaul toolbar than
                    320:  * create a brand new toolbar ;-)
                    321:  */
                    322: HTMLArea.Config.prototype.hideSomeButtons = function(remove) {
                    323: 	var toolbar = this.toolbar;
                    324: 	for (var i in toolbar) {
                    325: 		var line = toolbar[i];
                    326: 		for (var j = line.length; --j >= 0; ) {
                    327: 			if (remove.indexOf(" " + line[j] + " ") >= 0) {
                    328: 				var len = 1;
                    329: 				if (/separator|space/.test(line[j + 1])) {
                    330: 					len = 2;
                    331: 				}
                    332: 				line.splice(j, len);
                    333: 			}
                    334: 		}
                    335: 	}
                    336: };
                    337: 
1.1       www       338: /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
                    339: HTMLArea.replaceAll = function(config) {
                    340: 	var tas = document.getElementsByTagName("textarea");
                    341: 	for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
                    342: };
                    343: 
                    344: /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
                    345: HTMLArea.replace = function(id, config) {
1.3       www       346: 	var ta = HTMLArea.getElementById("textarea", id);
1.1       www       347: 	return ta ? (new HTMLArea(ta, config)).generate() : null;
                    348: };
                    349: 
                    350: // Creates the toolbar and appends it to the _htmlarea
                    351: HTMLArea.prototype._createToolbar = function () {
                    352: 	var editor = this;	// to access this in nested functions
                    353: 
                    354: 	var toolbar = document.createElement("div");
                    355: 	this._toolbar = toolbar;
                    356: 	toolbar.className = "toolbar";
                    357: 	toolbar.unselectable = "1";
                    358: 	var tb_row = null;
                    359: 	var tb_objects = new Object();
                    360: 	this._toolbarObjects = tb_objects;
                    361: 
                    362: 	// creates a new line in the toolbar
                    363: 	function newLine() {
                    364: 		var table = document.createElement("table");
                    365: 		table.border = "0px";
                    366: 		table.cellSpacing = "0px";
                    367: 		table.cellPadding = "0px";
                    368: 		toolbar.appendChild(table);
                    369: 		// TBODY is required for IE, otherwise you don't see anything
                    370: 		// in the TABLE.
                    371: 		var tb_body = document.createElement("tbody");
                    372: 		table.appendChild(tb_body);
                    373: 		tb_row = document.createElement("tr");
                    374: 		tb_body.appendChild(tb_row);
                    375: 	}; // END of function: newLine
                    376: 	// init first line
                    377: 	newLine();
                    378: 
                    379: 	// updates the state of a toolbar element.  This function is member of
                    380: 	// a toolbar element object (unnamed objects created by createButton or
                    381: 	// createSelect functions below).
                    382: 	function setButtonStatus(id, newval) {
                    383: 		var oldval = this[id];
                    384: 		var el = this.element;
                    385: 		if (oldval != newval) {
                    386: 			switch (id) {
                    387: 			    case "enabled":
                    388: 				if (newval) {
                    389: 					HTMLArea._removeClass(el, "buttonDisabled");
                    390: 					el.disabled = false;
                    391: 				} else {
                    392: 					HTMLArea._addClass(el, "buttonDisabled");
                    393: 					el.disabled = true;
                    394: 				}
                    395: 				break;
                    396: 			    case "active":
                    397: 				if (newval) {
                    398: 					HTMLArea._addClass(el, "buttonPressed");
                    399: 				} else {
                    400: 					HTMLArea._removeClass(el, "buttonPressed");
                    401: 				}
                    402: 				break;
                    403: 			}
                    404: 			this[id] = newval;
                    405: 		}
                    406: 	}; // END of function: setButtonStatus
                    407: 
                    408: 	// this function will handle creation of combo boxes.  Receives as
                    409: 	// parameter the name of a button as defined in the toolBar config.
                    410: 	// This function is called from createButton, above, if the given "txt"
                    411: 	// doesn't match a button.
                    412: 	function createSelect(txt) {
                    413: 		var options = null;
                    414: 		var el = null;
                    415: 		var cmd = null;
                    416: 		var customSelects = editor.config.customSelects;
                    417: 		var context = null;
                    418: 		switch (txt) {
                    419: 		    case "fontsize":
                    420: 		    case "fontname":
                    421: 		    case "formatblock":
                    422: 			// the following line retrieves the correct
                    423: 			// configuration option because the variable name
                    424: 			// inside the Config object is named the same as the
                    425: 			// button/select in the toolbar.  For instance, if txt
                    426: 			// == "formatblock" we retrieve config.formatblock (or
                    427: 			// a different way to write it in JS is
                    428: 			// config["formatblock"].
                    429: 			options = editor.config[txt];
                    430: 			cmd = txt;
                    431: 			break;
                    432: 		    default:
                    433: 			// try to fetch it from the list of registered selects
                    434: 			cmd = txt;
                    435: 			var dropdown = customSelects[cmd];
                    436: 			if (typeof dropdown != "undefined") {
                    437: 				options = dropdown.options;
                    438: 				context = dropdown.context;
                    439: 			} else {
                    440: 				alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
                    441: 			}
                    442: 			break;
                    443: 		}
                    444: 		if (options) {
                    445: 			el = document.createElement("select");
                    446: 			var obj = {
                    447: 				name	: txt, // field name
                    448: 				element : el,	// the UI element (SELECT)
                    449: 				enabled : true, // is it enabled?
                    450: 				text	: false, // enabled in text mode?
                    451: 				cmd	: cmd, // command ID
                    452: 				state	: setButtonStatus, // for changing state
                    453: 				context : context
                    454: 			};
                    455: 			tb_objects[txt] = obj;
                    456: 			for (var i in options) {
                    457: 				var op = document.createElement("option");
                    458: 				op.appendChild(document.createTextNode(i));
                    459: 				op.value = options[i];
                    460: 				el.appendChild(op);
                    461: 			}
                    462: 			HTMLArea._addEvent(el, "change", function () {
                    463: 				editor._comboSelected(el, txt);
                    464: 			});
                    465: 		}
                    466: 		return el;
                    467: 	}; // END of function: createSelect
                    468: 
                    469: 	// appends a new button to toolbar
                    470: 	function createButton(txt) {
                    471: 		// the element that will be created
                    472: 		var el = null;
                    473: 		var btn = null;
                    474: 		switch (txt) {
                    475: 		    case "separator":
                    476: 			el = document.createElement("div");
                    477: 			el.className = "separator";
                    478: 			break;
                    479: 		    case "space":
                    480: 			el = document.createElement("div");
                    481: 			el.className = "space";
                    482: 			break;
                    483: 		    case "linebreak":
                    484: 			newLine();
                    485: 			return false;
                    486: 		    case "textindicator":
                    487: 			el = document.createElement("div");
                    488: 			el.appendChild(document.createTextNode("A"));
                    489: 			el.className = "indicator";
                    490: 			el.title = HTMLArea.I18N.tooltips.textindicator;
                    491: 			var obj = {
                    492: 				name	: txt, // the button name (i.e. 'bold')
                    493: 				element : el, // the UI element (DIV)
                    494: 				enabled : true, // is it enabled?
                    495: 				active	: false, // is it pressed?
                    496: 				text	: false, // enabled in text mode?
                    497: 				cmd	: "textindicator", // the command ID
                    498: 				state	: setButtonStatus // for changing state
                    499: 			};
                    500: 			tb_objects[txt] = obj;
                    501: 			break;
                    502: 		    default:
                    503: 			btn = editor.config.btnList[txt];
                    504: 		}
                    505: 		if (!el && btn) {
                    506: 			el = document.createElement("div");
                    507: 			el.title = btn[0];
                    508: 			el.className = "button";
                    509: 			// let's just pretend we have a button object, and
                    510: 			// assign all the needed information to it.
                    511: 			var obj = {
                    512: 				name	: txt, // the button name (i.e. 'bold')
                    513: 				element : el, // the UI element (DIV)
                    514: 				enabled : true, // is it enabled?
                    515: 				active	: false, // is it pressed?
                    516: 				text	: btn[2], // enabled in text mode?
                    517: 				cmd	: btn[3], // the command ID
                    518: 				state	: setButtonStatus, // for changing state
                    519: 				context : btn[4] || null // enabled in a certain context?
                    520: 			};
                    521: 			tb_objects[txt] = obj;
                    522: 			// handlers to emulate nice flat toolbar buttons
                    523: 			HTMLArea._addEvent(el, "mouseover", function () {
                    524: 				if (obj.enabled) {
                    525: 					HTMLArea._addClass(el, "buttonHover");
                    526: 				}
                    527: 			});
                    528: 			HTMLArea._addEvent(el, "mouseout", function () {
                    529: 				if (obj.enabled) with (HTMLArea) {
                    530: 					_removeClass(el, "buttonHover");
                    531: 					_removeClass(el, "buttonActive");
                    532: 					(obj.active) && _addClass(el, "buttonPressed");
                    533: 				}
                    534: 			});
                    535: 			HTMLArea._addEvent(el, "mousedown", function (ev) {
                    536: 				if (obj.enabled) with (HTMLArea) {
                    537: 					_addClass(el, "buttonActive");
                    538: 					_removeClass(el, "buttonPressed");
                    539: 					_stopEvent(is_ie ? window.event : ev);
                    540: 				}
                    541: 			});
                    542: 			// when clicked, do the following:
                    543: 			HTMLArea._addEvent(el, "click", function (ev) {
                    544: 				if (obj.enabled) with (HTMLArea) {
                    545: 					_removeClass(el, "buttonActive");
                    546: 					_removeClass(el, "buttonHover");
                    547: 					obj.cmd(editor, obj.name, obj);
                    548: 					_stopEvent(is_ie ? window.event : ev);
                    549: 				}
                    550: 			});
                    551: 			var img = document.createElement("img");
1.3       www       552: 			img.src = btn[1];
1.1       www       553: 			img.style.width = "18px";
                    554: 			img.style.height = "18px";
                    555: 			el.appendChild(img);
                    556: 		} else if (!el) {
                    557: 			el = createSelect(txt);
                    558: 		}
                    559: 		if (el) {
                    560: 			var tb_cell = document.createElement("td");
                    561: 			tb_row.appendChild(tb_cell);
                    562: 			tb_cell.appendChild(el);
                    563: 		} else {
                    564: 			alert("FIXME: Unknown toolbar item: " + txt);
                    565: 		}
                    566: 		return el;
                    567: 	};
                    568: 
                    569: 	var first = true;
                    570: 	for (var i in this.config.toolbar) {
                    571: 		if (!first) {
                    572: 			createButton("linebreak");
                    573: 		} else {
                    574: 			first = false;
                    575: 		}
                    576: 		var group = this.config.toolbar[i];
                    577: 		for (var j in group) {
                    578: 			var code = group[j];
                    579: 			if (/^([IT])\[(.*?)\]/.test(code)) {
                    580: 				// special case, create text label
                    581: 				var l7ed = RegExp.$1 == "I"; // localized?
                    582: 				var label = RegExp.$2;
                    583: 				if (l7ed) {
                    584: 					label = HTMLArea.I18N.custom[label];
                    585: 				}
                    586: 				var tb_cell = document.createElement("td");
                    587: 				tb_row.appendChild(tb_cell);
                    588: 				tb_cell.className = "label";
                    589: 				tb_cell.innerHTML = label;
                    590: 			} else {
                    591: 				createButton(code);
                    592: 			}
                    593: 		}
                    594: 	}
                    595: 
                    596: 	this._htmlArea.appendChild(toolbar);
                    597: };
                    598: 
                    599: HTMLArea.prototype._createStatusBar = function() {
1.3       www       600: 	var statusbar = document.createElement("div");
                    601: 	statusbar.className = "statusBar";
                    602: 	this._htmlArea.appendChild(statusbar);
                    603: 	this._statusBar = statusbar;
                    604: 	// statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
1.1       www       605: 	// creates a holder for the path view
                    606: 	div = document.createElement("span");
                    607: 	div.className = "statusBarTree";
1.3       www       608: 	div.innerHTML = HTMLArea.I18N.msg["Path"] + ": ";
1.1       www       609: 	this._statusBarTree = div;
                    610: 	this._statusBar.appendChild(div);
                    611: 	if (!this.config.statusBar) {
                    612: 		// disable it...
1.3       www       613: 		statusbar.style.display = "none";
1.1       www       614: 	}
                    615: };
                    616: 
                    617: // Creates the HTMLArea object and replaces the textarea with it.
                    618: HTMLArea.prototype.generate = function () {
                    619: 	var editor = this;	// we'll need "this" in some nested functions
                    620: 	// get the textarea
                    621: 	var textarea = this._textArea;
                    622: 	if (typeof textarea == "string") {
                    623: 		// it's not element but ID
1.3       www       624: 		this._textArea = textarea = HTMLArea.getElementById("textarea", textarea);
1.1       www       625: 	}
                    626: 	this._ta_size = {
                    627: 		w: textarea.offsetWidth,
                    628: 		h: textarea.offsetHeight
                    629: 	};
                    630: 	textarea.style.display = "none";
                    631: 
                    632: 	// create the editor framework
                    633: 	var htmlarea = document.createElement("div");
                    634: 	htmlarea.className = "htmlarea";
                    635: 	this._htmlArea = htmlarea;
                    636: 
                    637: 	// insert the editor before the textarea.
                    638: 	textarea.parentNode.insertBefore(htmlarea, textarea);
                    639: 
                    640: 	if (textarea.form) {
                    641: 		// we have a form, on submit get the HTMLArea content and
                    642: 		// update original textarea.
1.3       www       643: 		var f = textarea.form;
                    644: 		if (typeof f.onsubmit == "function") {
                    645: 			var funcref = f.onsubmit;
                    646: 			if (typeof f.__msh_prevOnSubmit == "undefined") {
                    647: 				f.__msh_prevOnSubmit = [];
                    648: 			}
                    649: 			f.__msh_prevOnSubmit.push(funcref);
                    650: 		}
                    651: 		f.onsubmit = function() {
1.1       www       652: 			editor._textArea.value = editor.getHTML();
1.3       www       653: 			var a = this.__msh_prevOnSubmit;
                    654: 			// call previous submit methods if they were there.
                    655: 			if (typeof a != "undefined") {
                    656: 				for (var i in a) {
                    657: 					a[i]();
                    658: 				}
                    659: 			}
1.1       www       660: 		};
                    661: 	}
                    662: 
                    663: 	// add a handler for the "back/forward" case -- on body.unload we save
                    664: 	// the HTML content into the original textarea.
                    665: 	window.onunload = function() {
                    666: 		editor._textArea.value = editor.getHTML();
                    667: 	};
                    668: 
                    669: 	// creates & appends the toolbar
                    670: 	this._createToolbar();
                    671: 
                    672: 	// create the IFRAME
                    673: 	var iframe = document.createElement("iframe");
                    674: 	htmlarea.appendChild(iframe);
                    675: 
                    676: 	this._iframe = iframe;
                    677: 
                    678: 	// creates & appends the status bar, if the case
                    679: 	this._createStatusBar();
                    680: 
                    681: 	// remove the default border as it keeps us from computing correctly
                    682: 	// the sizes.  (somebody tell me why doesn't this work in IE)
                    683: 
                    684: 	if (!HTMLArea.is_ie) {
                    685: 		iframe.style.borderWidth = "1px";
                    686: 	// iframe.frameBorder = "1";
                    687: 	// iframe.marginHeight = "0";
                    688: 	// iframe.marginWidth = "0";
                    689: 	}
                    690: 
                    691: 	// size the IFRAME according to user's prefs or initial textarea
                    692: 	var height = (this.config.height == "auto" ? (this._ta_size.h + "px") : this.config.height);
                    693: 	height = parseInt(height);
                    694: 	var width = (this.config.width == "auto" ? (this._ta_size.w + "px") : this.config.width);
                    695: 	width = parseInt(width);
                    696: 
                    697: 	if (!HTMLArea.is_ie) {
                    698: 		height -= 2;
                    699: 		width -= 2;
                    700: 	}
                    701: 
                    702: 	iframe.style.width = width + "px";
                    703: 	if (this.config.sizeIncludesToolbar) {
                    704: 		// substract toolbar height
                    705: 		height -= this._toolbar.offsetHeight;
                    706: 		height -= this._statusBar.offsetHeight;
                    707: 	}
                    708: 	if (height < 0) {
                    709: 		height = 0;
                    710: 	}
                    711: 	iframe.style.height = height + "px";
                    712: 
                    713: 	// the editor including the toolbar now have the same size as the
                    714: 	// original textarea.. which means that we need to reduce that a bit.
                    715: 	textarea.style.width = iframe.style.width;
                    716:  	textarea.style.height = iframe.style.height;
                    717: 
                    718: 	// IMPORTANT: we have to allow Mozilla a short time to recognize the
                    719: 	// new frame.  Otherwise we get a stupid exception.
                    720: 	function initIframe() {
                    721: 		var doc = editor._iframe.contentWindow.document;
                    722: 		if (!doc) {
                    723: 			// Try again..
                    724: 			// FIXME: don't know what else to do here.  Normally
                    725: 			// we'll never reach this point.
                    726: 			if (HTMLArea.is_gecko) {
1.3       www       727: 				setTimeout(initIframe, 100);
1.1       www       728: 				return false;
                    729: 			} else {
                    730: 				alert("ERROR: IFRAME can't be initialized.");
                    731: 			}
                    732: 		}
                    733: 		if (HTMLArea.is_gecko) {
                    734: 			// enable editable mode for Mozilla
                    735: 			doc.designMode = "on";
                    736: 		}
                    737: 		editor._doc = doc;
1.3       www       738: 		if (!editor.config.fullPage) {
                    739: 			doc.open();
                    740: 			var html = "<html>\n";
                    741: 			html += "<head>\n";
                    742: 			if (editor.config.baseURL)
                    743: 				html += '<base href="' + editor.config.baseURL + '" />';
                    744: 			html += "<style> html,body { border: 0px; } " +
                    745: 				editor.config.pageStyle + "</style>\n";
                    746: 			html += "</head>\n";
                    747: 			html += "<body>\n";
                    748: 			html += editor._textArea.value;
                    749: 			html += "</body>\n";
                    750: 			html += "</html>";
                    751: 			doc.write(html);
                    752: 			doc.close();
                    753: 		} else {
                    754: 			var html = editor._textArea.value;
                    755: 			if (html.match(HTMLArea.RE_doctype)) {
                    756: 				editor.setDoctype(RegExp.$1);
                    757: 				html = html.replace(HTMLArea.RE_doctype, "");
                    758: 			}
                    759: 			doc.open();
                    760: 			doc.write(html);
                    761: 			doc.close();
                    762: 		}
1.1       www       763: 
                    764: 		if (HTMLArea.is_ie) {
                    765: 			// enable editable mode for IE.	 For some reason this
                    766: 			// doesn't work if done in the same place as for Gecko
                    767: 			// (above).
                    768: 			doc.body.contentEditable = true;
                    769: 		}
                    770: 
                    771: 		editor.focusEditor();
                    772: 		// intercept some events; for updating the toolbar & keyboard handlers
                    773: 		HTMLArea._addEvents
                    774: 			(doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
                    775: 			 function (event) {
                    776: 				 return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
                    777: 			 });
1.3       www       778: 
                    779: 		// check if any plugins have registered refresh handlers
                    780: 		for (var i in editor.plugins) {
                    781: 			var plugin = editor.plugins[i].instance;
                    782: 			if (typeof plugin.onGenerate == "function")
                    783: 				plugin.onGenerate();
                    784: 		}
                    785: 
                    786: 		setTimeout(function() {
                    787: 			editor.updateToolbar();
                    788: 		}, 250);
                    789: 
                    790: 		if (typeof editor.onGenerate == "function")
                    791: 			editor.onGenerate();
1.1       www       792: 	};
1.3       www       793: 	setTimeout(initIframe, 100);
1.1       www       794: };
                    795: 
                    796: // Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no
                    797: // parameter was passed this function toggles between modes.
                    798: HTMLArea.prototype.setMode = function(mode) {
                    799: 	if (typeof mode == "undefined") {
                    800: 		mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
                    801: 	}
                    802: 	switch (mode) {
                    803: 	    case "textmode":
                    804: 		this._textArea.value = this.getHTML();
                    805: 		this._iframe.style.display = "none";
                    806: 		this._textArea.style.display = "block";
                    807: 		if (this.config.statusBar) {
                    808: 			this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"];
                    809: 		}
                    810: 		break;
                    811: 	    case "wysiwyg":
                    812: 		if (HTMLArea.is_gecko) {
                    813: 			// disable design mode before changing innerHTML
1.3       www       814: 			try {
                    815: 				this._doc.designMode = "off";
                    816: 			} catch(e) {};
1.1       www       817: 		}
1.3       www       818: 		if (!this.config.fullPage)
                    819: 			this._doc.body.innerHTML = this.getHTML();
                    820: 		else
                    821: 			this.setFullHTML(this.getHTML());
1.1       www       822: 		this._iframe.style.display = "block";
                    823: 		this._textArea.style.display = "none";
                    824: 		if (HTMLArea.is_gecko) {
                    825: 			// we need to refresh that info for Moz-1.3a
1.3       www       826: 			try {
                    827: 				this._doc.designMode = "on";
                    828: 			} catch(e) {};
1.1       www       829: 		}
                    830: 		if (this.config.statusBar) {
                    831: 			this._statusBar.innerHTML = '';
                    832: 			this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
                    833: 			this._statusBar.appendChild(this._statusBarTree);
                    834: 		}
                    835: 		break;
                    836: 	    default:
                    837: 		alert("Mode <" + mode + "> not defined!");
                    838: 		return false;
                    839: 	}
                    840: 	this._editMode = mode;
                    841: 	this.focusEditor();
                    842: };
                    843: 
1.3       www       844: HTMLArea.prototype.setFullHTML = function(html) {
                    845: 	var save_multiline = RegExp.multiline;
                    846: 	RegExp.multiline = true;
                    847: 	if (html.match(HTMLArea.RE_doctype)) {
                    848: 		this.setDoctype(RegExp.$1);
                    849: 		html = html.replace(HTMLArea.RE_doctype, "");
                    850: 	}
                    851: 	RegExp.multiline = save_multiline;
                    852: 	if (!HTMLArea.is_ie) {
                    853: 		if (html.match(HTMLArea.RE_head))
                    854: 			this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
                    855: 		if (html.match(HTMLArea.RE_body))
                    856: 			this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
                    857: 	} else {
                    858: 		var html_re = /<html>((.|\n)*?)<\/html>/i;
                    859: 		html = html.replace(html_re, "$1");
                    860: 		this._doc.open();
                    861: 		this._doc.write(html);
                    862: 		this._doc.close();
                    863: 		this._doc.body.contentEditable = true;
                    864: 		return true;
                    865: 	}
                    866: };
                    867: 
1.1       www       868: /***************************************************
                    869:  *  Category: PLUGINS
                    870:  ***************************************************/
                    871: 
1.3       www       872: // this is the variant of the function above where the plugin arguments are
                    873: // already packed in an array.  Externally, it should be only used in the
                    874: // full-screen editor code, in order to initialize plugins with the same
                    875: // parameters as in the opener window.
                    876: HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
                    877: 	if (typeof plugin == "string")
                    878: 		plugin = eval(plugin);
                    879: 	var obj = new plugin(this, args);
                    880: 	if (obj) {
                    881: 		var clone = {};
                    882: 		var info = plugin._pluginInfo;
                    883: 		for (var i in info)
                    884: 			clone[i] = info[i];
                    885: 		clone.instance = obj;
                    886: 		clone.args = args;
                    887: 		this.plugins[plugin._pluginInfo.name] = clone;
                    888: 	} else
                    889: 		alert("Can't register plugin " + plugin.toString() + ".");
                    890: };
                    891: 
1.1       www       892: // Create the specified plugin and register it with this HTMLArea
1.3       www       893: HTMLArea.prototype.registerPlugin = function() {
                    894: 	var plugin = arguments[0];
                    895: 	var args = [];
                    896: 	for (var i = 1; i < arguments.length; ++i)
                    897: 		args.push(arguments[i]);
                    898: 	this.registerPlugin2(plugin, args);
1.1       www       899: };
                    900: 
                    901: // static function that loads the required plugin and lang file, based on the
                    902: // language loaded already for HTMLArea.  You better make sure that the plugin
                    903: // _has_ that language, otherwise shit might happen ;-)
                    904: HTMLArea.loadPlugin = function(pluginName) {
1.3       www       905: 	var dir = _editor_url + "plugins/" + pluginName;
1.1       www       906: 	var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
                    907: 					function (str, l1, l2, l3) {
                    908: 						return l1 + "-" + l2.toLowerCase() + l3;
                    909: 					}).toLowerCase() + ".js";
1.3       www       910: 	var plugin_file = dir + "/" + plugin;
                    911: 	var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js";
                    912: 	HTMLArea._scripts.push(plugin_file, plugin_lang);
                    913: 	document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
                    914: 	document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>");
                    915: };
                    916: 
                    917: HTMLArea.loadStyle = function(style, plugin) {
                    918: 	var url = _editor_url || '';
                    919: 	if (typeof plugin != "undefined") {
                    920: 		url += "plugins/" + plugin + "/";
                    921: 	}
                    922: 	url += style;
                    923: 	document.write("<style type='text/css'>@import url(" + url + ");</style>");
1.1       www       924: };
1.3       www       925: HTMLArea.loadStyle("htmlarea.css");
1.1       www       926: 
                    927: /***************************************************
                    928:  *  Category: EDITOR UTILITIES
                    929:  ***************************************************/
                    930: 
1.3       www       931: // The following function is a slight variation of the word cleaner code posted
                    932: // by Weeezl (user @ InteractiveTools forums).
                    933: HTMLArea.prototype._wordClean = function() {
                    934: 	var D = this.getInnerHTML();
                    935: 	if (D.indexOf('class=Mso') >= 0) {
                    936: 
                    937: 		// make one line
                    938: 		D = D.replace(/\r\n/g, ' ').
                    939: 			replace(/\n/g, ' ').
                    940: 			replace(/\r/g, ' ').
                    941: 			replace(/\&nbsp\;/g,' ');
                    942: 
                    943: 		// keep tags, strip attributes
                    944: 		D = D.replace(/ class=[^\s|>]*/gi,'').
                    945: 			//replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').
                    946: 			replace(/ style=\"[^>]*\"/gi,'').
                    947: 			replace(/ align=[^\s|>]*/gi,'');
                    948: 
                    949: 		//clean up tags
                    950: 		D = D.replace(/<b [^>]*>/gi,'<b>').
                    951: 			replace(/<i [^>]*>/gi,'<i>').
                    952: 			replace(/<li [^>]*>/gi,'<li>').
                    953: 			replace(/<ul [^>]*>/gi,'<ul>');
                    954: 
                    955: 		// replace outdated tags
                    956: 		D = D.replace(/<b>/gi,'<strong>').
                    957: 			replace(/<\/b>/gi,'</strong>');
                    958: 
                    959: 		// mozilla doesn't like <em> tags
                    960: 		D = D.replace(/<em>/gi,'<i>').
                    961: 			replace(/<\/em>/gi,'</i>');
                    962: 
                    963: 		// kill unwanted tags
                    964: 		D = D.replace(/<\?xml:[^>]*>/g, '').       // Word xml
                    965: 			replace(/<\/?st1:[^>]*>/g,'').     // Word SmartTags
                    966: 			replace(/<\/?[a-z]\:[^>]*>/g,'').  // All other funny Word non-HTML stuff
                    967: 			replace(/<\/?font[^>]*>/gi,'').    // Disable if you want to keep font formatting
                    968: 			replace(/<\/?span[^>]*>/gi,' ').
                    969: 			replace(/<\/?div[^>]*>/gi,' ').
                    970: 			replace(/<\/?pre[^>]*>/gi,' ').
                    971: 			replace(/<\/?h[1-6][^>]*>/gi,' ');
                    972: 
                    973: 		//remove empty tags
                    974: 		//D = D.replace(/<strong><\/strong>/gi,'').
                    975: 		//replace(/<i><\/i>/gi,'').
                    976: 		//replace(/<P[^>]*><\/P>/gi,'');
                    977: 
                    978: 		// nuke double tags
                    979: 		oldlen = D.length + 1;
                    980: 		while(oldlen > D.length) {
                    981: 			oldlen = D.length;
                    982: 			// join us now and free the tags, we'll be free hackers, we'll be free... ;-)
                    983: 			D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' ').
                    984: 				replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>');
                    985: 		}
                    986: 		D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>').
                    987: 			replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>');
                    988: 
                    989: 		// nuke double spaces
                    990: 		D = D.replace(/  */gi,' ');
                    991: 
                    992: 		this.setHTML(D);
                    993: 		this.updateToolbar();
                    994: 	}
                    995: };
                    996: 
1.1       www       997: HTMLArea.prototype.forceRedraw = function() {
                    998: 	this._doc.body.style.visibility = "hidden";
                    999: 	this._doc.body.style.visibility = "visible";
                   1000: 	// this._doc.body.innerHTML = this.getInnerHTML();
                   1001: };
                   1002: 
                   1003: // focuses the iframe window.  returns a reference to the editor document.
                   1004: HTMLArea.prototype.focusEditor = function() {
                   1005: 	switch (this._editMode) {
                   1006: 	    case "wysiwyg" : this._iframe.contentWindow.focus(); break;
                   1007: 	    case "textmode": this._textArea.focus(); break;
                   1008: 	    default	   : alert("ERROR: mode " + this._editMode + " is not defined");
                   1009: 	}
                   1010: 	return this._doc;
                   1011: };
                   1012: 
1.3       www      1013: // takes a snapshot of the current text (for undo)
                   1014: HTMLArea.prototype._undoTakeSnapshot = function() {
                   1015: 	++this._undoPos;
                   1016: 	if (this._undoPos >= this.config.undoSteps) {
                   1017: 		// remove the first element
                   1018: 		this._undoQueue.shift();
                   1019: 		--this._undoPos;
                   1020: 	}
                   1021: 	// use the fasted method (getInnerHTML);
                   1022: 	var take = true;
                   1023: 	var txt = this.getInnerHTML();
                   1024: 	if (this._undoPos > 0)
                   1025: 		take = (this._undoQueue[this._undoPos - 1] != txt);
                   1026: 	if (take) {
                   1027: 		this._undoQueue[this._undoPos] = txt;
                   1028: 	} else {
                   1029: 		this._undoPos--;
                   1030: 	}
                   1031: };
                   1032: 
                   1033: HTMLArea.prototype.undo = function() {
                   1034: 	if (this._undoPos > 0) {
                   1035: 		var txt = this._undoQueue[--this._undoPos];
                   1036: 		if (txt) this.setHTML(txt);
                   1037: 		else ++this._undoPos;
                   1038: 	}
                   1039: };
                   1040: 
                   1041: HTMLArea.prototype.redo = function() {
                   1042: 	if (this._undoPos < this._undoQueue.length - 1) {
                   1043: 		var txt = this._undoQueue[++this._undoPos];
                   1044: 		if (txt) this.setHTML(txt);
                   1045: 		else --this._undoPos;
                   1046: 	}
                   1047: };
                   1048: 
1.1       www      1049: // updates enabled/disable/active state of the toolbar elements
                   1050: HTMLArea.prototype.updateToolbar = function(noStatus) {
                   1051: 	var doc = this._doc;
                   1052: 	var text = (this._editMode == "textmode");
                   1053: 	var ancestors = null;
                   1054: 	if (!text) {
                   1055: 		ancestors = this.getAllAncestors();
                   1056: 		if (this.config.statusBar && !noStatus) {
1.3       www      1057: 			this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear
1.1       www      1058: 			for (var i = ancestors.length; --i >= 0;) {
                   1059: 				var el = ancestors[i];
                   1060: 				if (!el) {
                   1061: 					// hell knows why we get here; this
                   1062: 					// could be a classic example of why
                   1063: 					// it's good to check for conditions
                   1064: 					// that are impossible to happen ;-)
                   1065: 					continue;
                   1066: 				}
                   1067: 				var a = document.createElement("a");
                   1068: 				a.href = "#";
                   1069: 				a.el = el;
                   1070: 				a.editor = this;
                   1071: 				a.onclick = function() {
                   1072: 					this.blur();
                   1073: 					this.editor.selectNodeContents(this.el);
                   1074: 					this.editor.updateToolbar(true);
                   1075: 					return false;
                   1076: 				};
                   1077: 				a.oncontextmenu = function() {
                   1078: 					// TODO: add context menu here
                   1079: 					this.blur();
                   1080: 					var info = "Inline style:\n\n";
                   1081: 					info += this.el.style.cssText.split(/;\s*/).join(";\n");
                   1082: 					alert(info);
                   1083: 					return false;
                   1084: 				};
                   1085: 				var txt = el.tagName.toLowerCase();
                   1086: 				a.title = el.style.cssText;
                   1087: 				if (el.id) {
                   1088: 					txt += "#" + el.id;
                   1089: 				}
                   1090: 				if (el.className) {
                   1091: 					txt += "." + el.className;
                   1092: 				}
                   1093: 				a.appendChild(document.createTextNode(txt));
                   1094: 				this._statusBarTree.appendChild(a);
                   1095: 				if (i != 0) {
                   1096: 					this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
                   1097: 				}
                   1098: 			}
                   1099: 		}
                   1100: 	}
                   1101: 	for (var i in this._toolbarObjects) {
                   1102: 		var btn = this._toolbarObjects[i];
                   1103: 		var cmd = i;
                   1104: 		var inContext = true;
                   1105: 		if (btn.context && !text) {
                   1106: 			inContext = false;
                   1107: 			var context = btn.context;
                   1108: 			var attrs = [];
                   1109: 			if (/(.*)\[(.*?)\]/.test(context)) {
                   1110: 				context = RegExp.$1;
                   1111: 				attrs = RegExp.$2.split(",");
                   1112: 			}
                   1113: 			context = context.toLowerCase();
                   1114: 			var match = (context == "*");
                   1115: 			for (var k in ancestors) {
                   1116: 				if (!ancestors[k]) {
                   1117: 					// the impossible really happens.
                   1118: 					continue;
                   1119: 				}
                   1120: 				if (match || (ancestors[k].tagName.toLowerCase() == context)) {
                   1121: 					inContext = true;
                   1122: 					for (var ka in attrs) {
                   1123: 						if (!eval("ancestors[k]." + attrs[ka])) {
                   1124: 							inContext = false;
                   1125: 							break;
                   1126: 						}
                   1127: 					}
                   1128: 					if (inContext) {
                   1129: 						break;
                   1130: 					}
                   1131: 				}
                   1132: 			}
                   1133: 		}
                   1134: 		btn.state("enabled", (!text || btn.text) && inContext);
                   1135: 		if (typeof cmd == "function") {
                   1136: 			continue;
                   1137: 		}
                   1138: 		// look-it-up in the custom dropdown boxes
                   1139: 		var dropdown = this.config.customSelects[cmd];
                   1140: 		if ((!text || btn.text) && (typeof dropdown != "undefined")) {
                   1141: 			dropdown.refresh(this);
                   1142: 			continue;
                   1143: 		}
                   1144: 		switch (cmd) {
                   1145: 		    case "fontname":
                   1146: 		    case "fontsize":
                   1147: 		    case "formatblock":
1.3       www      1148: 			if (!text) try {
1.1       www      1149: 				var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
                   1150: 				if (!value) {
                   1151: 					// FIXME: what do we do here?
                   1152: 					break;
                   1153: 				}
                   1154: 				// HACK -- retrieve the config option for this
                   1155: 				// combo box.  We rely on the fact that the
                   1156: 				// variable in config has the same name as
                   1157: 				// button name in the toolbar.
                   1158: 				var options = this.config[cmd];
                   1159: 				var k = 0;
                   1160: 				// btn.element.selectedIndex = 0;
                   1161: 				for (var j in options) {
                   1162: 					// FIXME: the following line is scary.
                   1163: 					if ((j.toLowerCase() == value) ||
                   1164: 					    (options[j].substr(0, value.length).toLowerCase() == value)) {
                   1165: 						btn.element.selectedIndex = k;
                   1166: 						break;
                   1167: 					}
                   1168: 					++k;
                   1169: 				}
1.3       www      1170: 			} catch(e) {};
1.1       www      1171: 			break;
                   1172: 		    case "textindicator":
                   1173: 			if (!text) {
                   1174: 				try {with (btn.element.style) {
                   1175: 					backgroundColor = HTMLArea._makeColor(
                   1176: 						doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
                   1177: 					if (/transparent/i.test(backgroundColor)) {
                   1178: 						// Mozilla
                   1179: 						backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
                   1180: 					}
                   1181: 					color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
                   1182: 					fontFamily = doc.queryCommandValue("fontname");
                   1183: 					fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
                   1184: 					fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
                   1185: 				}} catch (e) {
                   1186: 					// alert(e + "\n\n" + cmd);
                   1187: 				}
                   1188: 			}
                   1189: 			break;
                   1190: 		    case "htmlmode": btn.state("active", text); break;
1.3       www      1191: 		    case "lefttoright":
                   1192: 		    case "righttoleft":
                   1193: 			var el = this.getParentElement();
                   1194: 			while (el && !HTMLArea.isBlockElement(el))
                   1195: 				el = el.parentNode;
                   1196: 			if (el)
                   1197: 				btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
                   1198: 			break;
1.1       www      1199: 		    default:
                   1200: 			try {
                   1201: 				btn.state("active", (!text && doc.queryCommandState(cmd)));
                   1202: 			} catch (e) {}
                   1203: 		}
                   1204: 	}
1.3       www      1205: 	// take undo snapshots
                   1206: 	if (this._customUndo && !this._timerUndo) {
                   1207: 		this._undoTakeSnapshot();
                   1208: 		var editor = this;
                   1209: 		this._timerUndo = setTimeout(function() {
                   1210: 			editor._timerUndo = null;
                   1211: 		}, this.config.undoTimeout);
                   1212: 	}
                   1213: 	// check if any plugins have registered refresh handlers
                   1214: 	for (var i in this.plugins) {
                   1215: 		var plugin = this.plugins[i].instance;
                   1216: 		if (typeof plugin.onUpdateToolbar == "function")
                   1217: 			plugin.onUpdateToolbar();
                   1218: 	}
1.1       www      1219: };
                   1220: 
                   1221: /** Returns a node after which we can insert other nodes, in the current
                   1222:  * selection.  The selection is removed.  It splits a text node, if needed.
                   1223:  */
                   1224: HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
                   1225: 	if (!HTMLArea.is_ie) {
                   1226: 		var sel = this._getSelection();
                   1227: 		var range = this._createRange(sel);
                   1228: 		// remove the current selection
                   1229: 		sel.removeAllRanges();
                   1230: 		range.deleteContents();
                   1231: 		var node = range.startContainer;
                   1232: 		var pos = range.startOffset;
                   1233: 		switch (node.nodeType) {
                   1234: 		    case 3: // Node.TEXT_NODE
                   1235: 			// we have to split it at the caret position.
                   1236: 			if (toBeInserted.nodeType == 3) {
                   1237: 				// do optimized insertion
                   1238: 				node.insertData(pos, toBeInserted.data);
                   1239: 				range = this._createRange();
                   1240: 				range.setEnd(node, pos + toBeInserted.length);
                   1241: 				range.setStart(node, pos + toBeInserted.length);
                   1242: 				sel.addRange(range);
                   1243: 			} else {
                   1244: 				node = node.splitText(pos);
                   1245: 				var selnode = toBeInserted;
                   1246: 				if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
                   1247: 					selnode = selnode.firstChild;
                   1248: 				}
                   1249: 				node.parentNode.insertBefore(toBeInserted, node);
                   1250: 				this.selectNodeContents(selnode);
                   1251: 				this.updateToolbar();
                   1252: 			}
                   1253: 			break;
                   1254: 		    case 1: // Node.ELEMENT_NODE
                   1255: 			var selnode = toBeInserted;
                   1256: 			if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
                   1257: 				selnode = selnode.firstChild;
                   1258: 			}
                   1259: 			node.insertBefore(toBeInserted, node.childNodes[pos]);
                   1260: 			this.selectNodeContents(selnode);
                   1261: 			this.updateToolbar();
                   1262: 			break;
                   1263: 		}
                   1264: 	} else {
                   1265: 		return null;	// this function not yet used for IE <FIXME>
                   1266: 	}
                   1267: };
                   1268: 
                   1269: // Returns the deepest node that contains both endpoints of the selection.
                   1270: HTMLArea.prototype.getParentElement = function() {
                   1271: 	var sel = this._getSelection();
                   1272: 	var range = this._createRange(sel);
                   1273: 	if (HTMLArea.is_ie) {
1.3       www      1274: 		switch (sel.type) {
                   1275: 		    case "Text":
                   1276: 		    case "None":
                   1277: 			// It seems that even for selection of type "None",
                   1278: 			// there _is_ a parent element and it's value is not
                   1279: 			// only correct, but very important to us.  MSIE is
                   1280: 			// certainly the buggiest browser in the world and I
                   1281: 			// wonder, God, how can Earth stand it?
                   1282: 			return range.parentElement();
                   1283: 		    case "Control":
                   1284: 			return range.item(0);
                   1285: 		    default:
                   1286: 			return this._doc.body;
                   1287: 		}
                   1288: 	} else try {
1.1       www      1289: 		var p = range.commonAncestorContainer;
1.3       www      1290: 		if (!range.collapsed && range.startContainer == range.endContainer &&
                   1291: 		    range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
                   1292: 			p = range.startContainer.childNodes[range.startOffset];
                   1293: 		/*
                   1294: 		alert(range.startContainer + ":" + range.startOffset + "\n" +
                   1295: 		      range.endContainer + ":" + range.endOffset);
                   1296: 		*/
1.1       www      1297: 		while (p.nodeType == 3) {
                   1298: 			p = p.parentNode;
                   1299: 		}
                   1300: 		return p;
1.3       www      1301: 	} catch (e) {
                   1302: 		return null;
1.1       www      1303: 	}
                   1304: };
                   1305: 
                   1306: // Returns an array with all the ancestor nodes of the selection.
                   1307: HTMLArea.prototype.getAllAncestors = function() {
                   1308: 	var p = this.getParentElement();
                   1309: 	var a = [];
                   1310: 	while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
                   1311: 		a.push(p);
                   1312: 		p = p.parentNode;
                   1313: 	}
                   1314: 	a.push(this._doc.body);
                   1315: 	return a;
                   1316: };
                   1317: 
                   1318: // Selects the contents inside the given node
                   1319: HTMLArea.prototype.selectNodeContents = function(node, pos) {
                   1320: 	this.focusEditor();
                   1321: 	this.forceRedraw();
                   1322: 	var range;
                   1323: 	var collapsed = (typeof pos != "undefined");
                   1324: 	if (HTMLArea.is_ie) {
                   1325: 		range = this._doc.body.createTextRange();
                   1326: 		range.moveToElementText(node);
                   1327: 		(collapsed) && range.collapse(pos);
                   1328: 		range.select();
                   1329: 	} else {
                   1330: 		var sel = this._getSelection();
                   1331: 		range = this._doc.createRange();
                   1332: 		range.selectNodeContents(node);
                   1333: 		(collapsed) && range.collapse(pos);
                   1334: 		sel.removeAllRanges();
                   1335: 		sel.addRange(range);
                   1336: 	}
                   1337: };
                   1338: 
                   1339: /** Call this function to insert HTML code at the current position.  It deletes
                   1340:  * the selection, if any.
                   1341:  */
                   1342: HTMLArea.prototype.insertHTML = function(html) {
                   1343: 	var sel = this._getSelection();
                   1344: 	var range = this._createRange(sel);
                   1345: 	if (HTMLArea.is_ie) {
                   1346: 		range.pasteHTML(html);
                   1347: 	} else {
                   1348: 		// construct a new document fragment with the given HTML
                   1349: 		var fragment = this._doc.createDocumentFragment();
                   1350: 		var div = this._doc.createElement("div");
                   1351: 		div.innerHTML = html;
                   1352: 		while (div.firstChild) {
                   1353: 			// the following call also removes the node from div
                   1354: 			fragment.appendChild(div.firstChild);
                   1355: 		}
                   1356: 		// this also removes the selection
                   1357: 		var node = this.insertNodeAtSelection(fragment);
                   1358: 	}
                   1359: };
                   1360: 
                   1361: /**
                   1362:  *  Call this function to surround the existing HTML code in the selection with
                   1363:  *  your tags.  FIXME: buggy!  This function will be deprecated "soon".
                   1364:  */
                   1365: HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
                   1366: 	var html = this.getSelectedHTML();
                   1367: 	// the following also deletes the selection
                   1368: 	this.insertHTML(startTag + html + endTag);
                   1369: };
                   1370: 
                   1371: /// Retrieve the selected block
                   1372: HTMLArea.prototype.getSelectedHTML = function() {
                   1373: 	var sel = this._getSelection();
                   1374: 	var range = this._createRange(sel);
                   1375: 	var existing = null;
                   1376: 	if (HTMLArea.is_ie) {
                   1377: 		existing = range.htmlText;
                   1378: 	} else {
1.3       www      1379: 		existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1.1       www      1380: 	}
                   1381: 	return existing;
                   1382: };
                   1383: 
1.3       www      1384: /// Return true if we have some selection
                   1385: HTMLArea.prototype.hasSelectedText = function() {
                   1386: 	// FIXME: come _on_ mishoo, you can do better than this ;-)
                   1387: 	return this.getSelectedHTML() != '';
                   1388: };
                   1389: 
                   1390: HTMLArea.prototype._createLink = function(link) {
                   1391: 	var editor = this;
                   1392: 	var outparam = null;
                   1393: 	if (typeof link == "undefined") {
                   1394: 		link = this.getParentElement();
                   1395: 		if (link && !/^a$/i.test(link.tagName))
                   1396: 			link = null;
                   1397: 	}
                   1398: 	if (link) outparam = {
                   1399: 		f_href   : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
                   1400: 		f_title  : link.title,
                   1401: 		f_target : link.target
                   1402: 	};
                   1403: 	this._popupDialog("link.html", function(param) {
                   1404: 		if (!param)
                   1405: 			return false;
                   1406: 		var a = link;
                   1407: 		if (!a) {
                   1408: 			editor._doc.execCommand("createlink", false, param.f_href);
                   1409: 			a = editor.getParentElement();
                   1410: 			var sel = editor._getSelection();
                   1411: 			var range = editor._createRange(sel);
                   1412: 			if (!HTMLArea.is_ie) {
                   1413: 				a = range.startContainer;
                   1414: 				if (!/^a$/i.test(a.tagName))
                   1415: 					a = a.nextSibling;
                   1416: 			}
                   1417: 		} else a.href = param.f_href.trim();
                   1418: 		if (!/^a$/i.test(a.tagName))
                   1419: 			return false;
                   1420: 		a.target = param.f_target.trim();
                   1421: 		a.title = param.f_title.trim();
                   1422: 		editor.selectNodeContents(a);
                   1423: 		editor.updateToolbar();
                   1424: 	}, outparam);
                   1425: };
                   1426: 
                   1427: // Called when the user clicks on "InsertImage" button.  If an image is already
                   1428: // there, it will just modify it's properties.
                   1429: HTMLArea.prototype._insertImage = function(image) {
1.1       www      1430: 	var editor = this;	// for nested functions
1.3       www      1431: 	var outparam = null;
                   1432: 	if (typeof image == "undefined") {
                   1433: 		image = this.getParentElement();
                   1434: 		if (image && !/^img$/i.test(image.tagName))
                   1435: 			image = null;
                   1436: 	}
                   1437: 	if (image) outparam = {
                   1438: 		f_url    : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
                   1439: 		f_alt    : image.alt,
                   1440: 		f_border : image.border,
                   1441: 		f_align  : image.align,
                   1442: 		f_vert   : image.vspace,
                   1443: 		f_horiz  : image.hspace
                   1444: 	};
1.1       www      1445: 	this._popupDialog("insert_image.html", function(param) {
                   1446: 		if (!param) {	// user must have pressed Cancel
                   1447: 			return false;
                   1448: 		}
1.3       www      1449: 		var img = image;
                   1450: 		if (!img) {
                   1451: 			var sel = editor._getSelection();
                   1452: 			var range = editor._createRange(sel);
                   1453: 			editor._doc.execCommand("insertimage", false, param.f_url);
                   1454: 			if (HTMLArea.is_ie) {
                   1455: 				img = range.parentElement();
                   1456: 				// wonder if this works...
                   1457: 				if (img.tagName.toLowerCase() != "img") {
                   1458: 					img = img.previousSibling;
                   1459: 				}
                   1460: 			} else {
                   1461: 				img = range.startContainer.previousSibling;
1.1       www      1462: 			}
                   1463: 		} else {
1.3       www      1464: 			img.src = param.f_url;
1.1       www      1465: 		}
                   1466: 		for (field in param) {
                   1467: 			var value = param[field];
                   1468: 			switch (field) {
                   1469: 			    case "f_alt"    : img.alt	 = value; break;
1.3       www      1470: 			    case "f_border" : img.border = parseInt(value || "0"); break;
1.1       www      1471: 			    case "f_align"  : img.align	 = value; break;
1.3       www      1472: 			    case "f_vert"   : img.vspace = parseInt(value || "0"); break;
                   1473: 			    case "f_horiz"  : img.hspace = parseInt(value || "0"); break;
1.1       www      1474: 			}
                   1475: 		}
1.3       www      1476: 	}, outparam);
1.1       www      1477: };
                   1478: 
                   1479: // Called when the user clicks the Insert Table button
                   1480: HTMLArea.prototype._insertTable = function() {
                   1481: 	var sel = this._getSelection();
                   1482: 	var range = this._createRange(sel);
                   1483: 	var editor = this;	// for nested functions
                   1484: 	this._popupDialog("insert_table.html", function(param) {
                   1485: 		if (!param) {	// user must have pressed Cancel
                   1486: 			return false;
                   1487: 		}
                   1488: 		var doc = editor._doc;
                   1489: 		// create the table element
                   1490: 		var table = doc.createElement("table");
                   1491: 		// assign the given arguments
                   1492: 		for (var field in param) {
                   1493: 			var value = param[field];
                   1494: 			if (!value) {
                   1495: 				continue;
                   1496: 			}
                   1497: 			switch (field) {
                   1498: 			    case "f_width"   : table.style.width = value + param["f_unit"]; break;
                   1499: 			    case "f_align"   : table.align	 = value; break;
                   1500: 			    case "f_border"  : table.border	 = parseInt(value); break;
                   1501: 			    case "f_spacing" : table.cellspacing = parseInt(value); break;
                   1502: 			    case "f_padding" : table.cellpadding = parseInt(value); break;
                   1503: 			}
                   1504: 		}
                   1505: 		var tbody = doc.createElement("tbody");
                   1506: 		table.appendChild(tbody);
                   1507: 		for (var i = 0; i < param["f_rows"]; ++i) {
                   1508: 			var tr = doc.createElement("tr");
                   1509: 			tbody.appendChild(tr);
                   1510: 			for (var j = 0; j < param["f_cols"]; ++j) {
                   1511: 				var td = doc.createElement("td");
                   1512: 				tr.appendChild(td);
                   1513: 				// Mozilla likes to see something inside the cell.
                   1514: 				(HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
                   1515: 			}
                   1516: 		}
                   1517: 		if (HTMLArea.is_ie) {
                   1518: 			range.pasteHTML(table.outerHTML);
                   1519: 		} else {
                   1520: 			// insert the table
                   1521: 			editor.insertNodeAtSelection(table);
                   1522: 		}
                   1523: 		return true;
                   1524: 	}, null);
                   1525: };
                   1526: 
                   1527: /***************************************************
                   1528:  *  Category: EVENT HANDLERS
                   1529:  ***************************************************/
                   1530: 
                   1531: // el is reference to the SELECT object
                   1532: // txt is the name of the select field, as in config.toolbar
                   1533: HTMLArea.prototype._comboSelected = function(el, txt) {
                   1534: 	this.focusEditor();
                   1535: 	var value = el.options[el.selectedIndex].value;
                   1536: 	switch (txt) {
                   1537: 	    case "fontname":
                   1538: 	    case "fontsize": this.execCommand(txt, false, value); break;
                   1539: 	    case "formatblock":
                   1540: 		(HTMLArea.is_ie) && (value = "<" + value + ">");
                   1541: 		this.execCommand(txt, false, value);
                   1542: 		break;
                   1543: 	    default:
                   1544: 		// try to look it up in the registered dropdowns
                   1545: 		var dropdown = this.config.customSelects[txt];
                   1546: 		if (typeof dropdown != "undefined") {
                   1547: 			dropdown.action(this);
                   1548: 		} else {
                   1549: 			alert("FIXME: combo box " + txt + " not implemented");
                   1550: 		}
                   1551: 	}
                   1552: };
                   1553: 
                   1554: // the execCommand function (intercepts some commands and replaces them with
                   1555: // our own implementation)
                   1556: HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
                   1557: 	var editor = this;	// for nested functions
                   1558: 	this.focusEditor();
1.3       www      1559: 	cmdID = cmdID.toLowerCase();
                   1560: 	switch (cmdID) {
1.1       www      1561: 	    case "htmlmode" : this.setMode(); break;
                   1562: 	    case "hilitecolor":
                   1563: 		(HTMLArea.is_ie) && (cmdID = "backcolor");
                   1564: 	    case "forecolor":
                   1565: 		this._popupDialog("select_color.html", function(color) {
                   1566: 			if (color) { // selection not canceled
                   1567: 				editor._doc.execCommand(cmdID, false, "#" + color);
                   1568: 			}
                   1569: 		}, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
                   1570: 		break;
                   1571: 	    case "createlink":
1.3       www      1572: 		this._createLink();
1.1       www      1573: 		break;
                   1574: 	    case "popupeditor":
1.3       www      1575: 		// this object will be passed to the newly opened window
                   1576: 		HTMLArea._object = this;
1.1       www      1577: 		if (HTMLArea.is_ie) {
1.3       www      1578: 			//if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"]))
                   1579: 			{
                   1580: 				window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
                   1581: 					    "toolbar=no,location=no,directories=no,status=no,menubar=no," +
                   1582: 					    "scrollbars=no,resizable=yes,width=640,height=480");
                   1583: 			}
1.1       www      1584: 		} else {
                   1585: 			window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
                   1586: 				    "toolbar=no,menubar=no,personalbar=no,width=640,height=480," +
                   1587: 				    "scrollbars=no,resizable=yes");
                   1588: 		}
1.3       www      1589: 		break;
                   1590: 	    case "undo":
                   1591: 	    case "redo":
                   1592: 		if (this._customUndo)
                   1593: 			this[cmdID]();
                   1594: 		else
                   1595: 			this._doc.execCommand(cmdID, UI, param);
1.1       www      1596: 		break;
                   1597: 	    case "inserttable": this._insertTable(); break;
                   1598: 	    case "insertimage": this._insertImage(); break;
1.3       www      1599: 	    case "about"    : this._popupDialog("about.html", null, this); break;
                   1600: 	    case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break;
                   1601: 
                   1602: 	    case "killword": this._wordClean(); break;
                   1603: 
                   1604: 	    case "cut":
                   1605: 	    case "copy":
                   1606: 	    case "paste":
                   1607: 		try {
                   1608: 			if (this.config.killWordOnPaste)
                   1609: 				this._wordClean();
                   1610: 			this._doc.execCommand(cmdID, UI, param);
                   1611: 		} catch (e) {
                   1612: 			if (HTMLArea.is_gecko) {
                   1613: 				if (confirm("Unprivileged scripts cannot access Cut/Copy/Paste programatically " +
                   1614: 					    "for security reasons.  Click OK to see a technical note at mozilla.org " +
                   1615: 					    "which shows you how to allow a script to access the clipboard."))
                   1616: 					window.open("http://mozilla.org/editor/midasdemo/securityprefs.html");
                   1617: 			}
                   1618: 		}
                   1619: 		break;
                   1620: 	    case "lefttoright":
                   1621: 	    case "righttoleft":
                   1622: 		var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
                   1623: 		var el = this.getParentElement();
                   1624: 		while (el && !HTMLArea.isBlockElement(el))
                   1625: 			el = el.parentNode;
                   1626: 		if (el) {
                   1627: 			if (el.style.direction == dir)
                   1628: 				el.style.direction = "";
                   1629: 			else
                   1630: 				el.style.direction = dir;
                   1631: 		}
                   1632: 		break;
1.1       www      1633: 	    default: this._doc.execCommand(cmdID, UI, param);
                   1634: 	}
                   1635: 	this.updateToolbar();
                   1636: 	return false;
                   1637: };
                   1638: 
                   1639: /** A generic event handler for things that happen in the IFRAME's document.
                   1640:  * This function also handles key bindings. */
                   1641: HTMLArea.prototype._editorEvent = function(ev) {
                   1642: 	var editor = this;
                   1643: 	var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress");
1.3       www      1644: 	if (keyEvent) {
                   1645: 		for (var i in editor.plugins) {
                   1646: 			var plugin = editor.plugins[i].instance;
                   1647: 			if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev);
                   1648: 		}
                   1649: 	}
1.1       www      1650: 	if (keyEvent && ev.ctrlKey) {
                   1651: 		var sel = null;
                   1652: 		var range = null;
                   1653: 		var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
                   1654: 		var cmd = null;
                   1655: 		var value = null;
                   1656: 		switch (key) {
                   1657: 		    case 'a':
                   1658: 			if (!HTMLArea.is_ie) {
                   1659: 				// KEY select all
                   1660: 				sel = this._getSelection();
                   1661: 				sel.removeAllRanges();
                   1662: 				range = this._createRange();
                   1663: 				range.selectNodeContents(this._doc.body);
                   1664: 				sel.addRange(range);
                   1665: 				HTMLArea._stopEvent(ev);
                   1666: 			}
                   1667: 			break;
                   1668: 
                   1669: 			// simple key commands follow
                   1670: 
                   1671: 		    case 'b': cmd = "bold"; break;
                   1672: 		    case 'i': cmd = "italic"; break;
                   1673: 		    case 'u': cmd = "underline"; break;
                   1674: 		    case 's': cmd = "strikethrough"; break;
                   1675: 		    case 'l': cmd = "justifyleft"; break;
                   1676: 		    case 'e': cmd = "justifycenter"; break;
                   1677: 		    case 'r': cmd = "justifyright"; break;
                   1678: 		    case 'j': cmd = "justifyfull"; break;
1.3       www      1679: 		    case 'z': cmd = "undo"; break;
                   1680: 		    case 'y': cmd = "redo"; break;
                   1681: 		    case 'v': cmd = "paste"; break;
                   1682: 
                   1683: 		    case '0': cmd = "killword"; break;
1.1       www      1684: 
                   1685: 			// headings
                   1686: 		    case '1':
                   1687: 		    case '2':
                   1688: 		    case '3':
                   1689: 		    case '4':
                   1690: 		    case '5':
                   1691: 		    case '6':
                   1692: 			cmd = "formatblock";
                   1693: 			value = "h" + key;
                   1694: 			if (HTMLArea.is_ie) {
                   1695: 				value = "<" + value + ">";
                   1696: 			}
                   1697: 			break;
                   1698: 		}
                   1699: 		if (cmd) {
                   1700: 			// execute simple command
                   1701: 			this.execCommand(cmd, false, value);
                   1702: 			HTMLArea._stopEvent(ev);
                   1703: 		}
                   1704: 	}
                   1705: 	/*
                   1706: 	else if (keyEvent) {
                   1707: 		// other keys here
                   1708: 		switch (ev.keyCode) {
                   1709: 		    case 13: // KEY enter
                   1710: 			// if (HTMLArea.is_ie) {
                   1711: 			this.insertHTML("<br />");
                   1712: 			HTMLArea._stopEvent(ev);
                   1713: 			// }
                   1714: 			break;
                   1715: 		}
                   1716: 	}
                   1717: 	*/
                   1718: 	// update the toolbar state after some time
                   1719: 	if (editor._timerToolbar) {
                   1720: 		clearTimeout(editor._timerToolbar);
                   1721: 	}
                   1722: 	editor._timerToolbar = setTimeout(function() {
                   1723: 		editor.updateToolbar();
                   1724: 		editor._timerToolbar = null;
                   1725: 	}, 50);
                   1726: };
                   1727: 
                   1728: // retrieve the HTML
                   1729: HTMLArea.prototype.getHTML = function() {
                   1730: 	switch (this._editMode) {
1.3       www      1731: 	    case "wysiwyg"  :
                   1732: 		if (!this.config.fullPage) {
                   1733: 			return HTMLArea.getHTML(this._doc.body, false, this);
                   1734: 		} else
                   1735: 			return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
1.1       www      1736: 	    case "textmode" : return this._textArea.value;
                   1737: 	    default	    : alert("Mode <" + mode + "> not defined!");
                   1738: 	}
                   1739: 	return false;
                   1740: };
                   1741: 
                   1742: // retrieve the HTML (fastest version, but uses innerHTML)
                   1743: HTMLArea.prototype.getInnerHTML = function() {
                   1744: 	switch (this._editMode) {
1.3       www      1745: 	    case "wysiwyg"  :
                   1746: 		if (!this.config.fullPage)
                   1747: 			return this._doc.body.innerHTML;
                   1748: 		else
                   1749: 			return this.doctype + "\n" + this._doc.documentElement.innerHTML;
1.1       www      1750: 	    case "textmode" : return this._textArea.value;
                   1751: 	    default	    : alert("Mode <" + mode + "> not defined!");
                   1752: 	}
                   1753: 	return false;
                   1754: };
                   1755: 
                   1756: // completely change the HTML inside
                   1757: HTMLArea.prototype.setHTML = function(html) {
                   1758: 	switch (this._editMode) {
1.3       www      1759: 	    case "wysiwyg"  :
                   1760: 		if (!this.config.fullPage)
                   1761: 			this._doc.body.innerHTML = html;
                   1762: 		else
                   1763: 			// this._doc.documentElement.innerHTML = html;
                   1764: 			this._doc.body.innerHTML = html;
                   1765: 		break;
1.1       www      1766: 	    case "textmode" : this._textArea.value = html; break;
                   1767: 	    default	    : alert("Mode <" + mode + "> not defined!");
                   1768: 	}
                   1769: 	return false;
                   1770: };
                   1771: 
1.3       www      1772: // sets the given doctype (useful when config.fullPage is true)
                   1773: HTMLArea.prototype.setDoctype = function(doctype) {
                   1774: 	this.doctype = doctype;
                   1775: };
                   1776: 
1.1       www      1777: /***************************************************
                   1778:  *  Category: UTILITY FUNCTIONS
                   1779:  ***************************************************/
                   1780: 
                   1781: // browser identification
                   1782: 
                   1783: HTMLArea.agt = navigator.userAgent.toLowerCase();
                   1784: HTMLArea.is_ie	   = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
                   1785: HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
                   1786: HTMLArea.is_mac	   = (HTMLArea.agt.indexOf("mac") != -1);
                   1787: HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
                   1788: HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
                   1789: HTMLArea.is_gecko  = (navigator.product == "Gecko");
                   1790: 
                   1791: // variable used to pass the object to the popup editor window.
                   1792: HTMLArea._object = null;
                   1793: 
1.3       www      1794: // function that returns a clone of the given object
                   1795: HTMLArea.cloneObject = function(obj) {
                   1796: 	var newObj = new Object;
                   1797: 
                   1798: 	// check for array objects
                   1799: 	if (obj.constructor.toString().indexOf("function Array(") == 1) {
                   1800: 		newObj = obj.constructor();
                   1801: 	}
                   1802: 
                   1803: 	// check for function objects (as usual, IE is fucked up)
                   1804: 	if (obj.constructor.toString().indexOf("function Function(") == 1) {
                   1805: 		newObj = obj; // just copy reference to it
                   1806: 	} else for (var n in obj) {
                   1807: 		var node = obj[n];
                   1808: 		if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
                   1809: 		else                         { newObj[n] = node; }
                   1810: 	}
                   1811: 
                   1812: 	return newObj;
                   1813: };
                   1814: 
1.1       www      1815: // FIXME!!! this should return false for IE < 5.5
                   1816: HTMLArea.checkSupportedBrowser = function() {
                   1817: 	if (HTMLArea.is_gecko) {
                   1818: 		if (navigator.productSub < 20021201) {
1.4       www      1819: 			window.status="WYSIWYG Editor: You need at least Mozilla-1.3 Alpha. " +
                   1820: 			      "Sorry, your Gecko is not supported.";
1.1       www      1821: 			return false;
                   1822: 		}
                   1823: 		if (navigator.productSub < 20030210) {
1.4       www      1824: 			window.status="WYSIWYG Editor: Mozilla < 1.3 Beta is not supported! " +
                   1825: 			      "I'll try, though, but it might not work.";
1.1       www      1826: 		}
                   1827: 	}
                   1828: 	return HTMLArea.is_gecko || HTMLArea.is_ie;
                   1829: };
                   1830: 
                   1831: // selection & ranges
                   1832: 
                   1833: // returns the current selection object
                   1834: HTMLArea.prototype._getSelection = function() {
                   1835: 	if (HTMLArea.is_ie) {
                   1836: 		return this._doc.selection;
                   1837: 	} else {
                   1838: 		return this._iframe.contentWindow.getSelection();
                   1839: 	}
                   1840: };
                   1841: 
                   1842: // returns a range for the current selection
                   1843: HTMLArea.prototype._createRange = function(sel) {
                   1844: 	if (HTMLArea.is_ie) {
                   1845: 		return sel.createRange();
                   1846: 	} else {
                   1847: 		this.focusEditor();
                   1848: 		if (typeof sel != "undefined") {
1.3       www      1849: 			try {
                   1850: 				return sel.getRangeAt(0);
                   1851: 			} catch(e) {
                   1852: 				return this._doc.createRange();
                   1853: 			}
1.1       www      1854: 		} else {
                   1855: 			return this._doc.createRange();
                   1856: 		}
                   1857: 	}
                   1858: };
                   1859: 
                   1860: // event handling
                   1861: 
                   1862: HTMLArea._addEvent = function(el, evname, func) {
                   1863: 	if (HTMLArea.is_ie) {
                   1864: 		el.attachEvent("on" + evname, func);
                   1865: 	} else {
                   1866: 		el.addEventListener(evname, func, true);
                   1867: 	}
                   1868: };
                   1869: 
                   1870: HTMLArea._addEvents = function(el, evs, func) {
                   1871: 	for (var i in evs) {
                   1872: 		HTMLArea._addEvent(el, evs[i], func);
                   1873: 	}
                   1874: };
                   1875: 
                   1876: HTMLArea._removeEvent = function(el, evname, func) {
                   1877: 	if (HTMLArea.is_ie) {
                   1878: 		el.detachEvent("on" + evname, func);
                   1879: 	} else {
                   1880: 		el.removeEventListener(evname, func, true);
                   1881: 	}
                   1882: };
                   1883: 
                   1884: HTMLArea._removeEvents = function(el, evs, func) {
                   1885: 	for (var i in evs) {
                   1886: 		HTMLArea._removeEvent(el, evs[i], func);
                   1887: 	}
                   1888: };
                   1889: 
                   1890: HTMLArea._stopEvent = function(ev) {
                   1891: 	if (HTMLArea.is_ie) {
                   1892: 		ev.cancelBubble = true;
                   1893: 		ev.returnValue = false;
                   1894: 	} else {
                   1895: 		ev.preventDefault();
                   1896: 		ev.stopPropagation();
                   1897: 	}
                   1898: };
                   1899: 
                   1900: HTMLArea._removeClass = function(el, className) {
                   1901: 	if (!(el && el.className)) {
                   1902: 		return;
                   1903: 	}
                   1904: 	var cls = el.className.split(" ");
                   1905: 	var ar = new Array();
                   1906: 	for (var i = cls.length; i > 0;) {
                   1907: 		if (cls[--i] != className) {
                   1908: 			ar[ar.length] = cls[i];
                   1909: 		}
                   1910: 	}
                   1911: 	el.className = ar.join(" ");
                   1912: };
                   1913: 
                   1914: HTMLArea._addClass = function(el, className) {
                   1915: 	// remove the class first, if already there
                   1916: 	HTMLArea._removeClass(el, className);
                   1917: 	el.className += " " + className;
                   1918: };
                   1919: 
                   1920: HTMLArea._hasClass = function(el, className) {
                   1921: 	if (!(el && el.className)) {
                   1922: 		return false;
                   1923: 	}
                   1924: 	var cls = el.className.split(" ");
                   1925: 	for (var i = cls.length; i > 0;) {
                   1926: 		if (cls[--i] == className) {
                   1927: 			return true;
                   1928: 		}
                   1929: 	}
                   1930: 	return false;
                   1931: };
                   1932: 
                   1933: HTMLArea.isBlockElement = function(el) {
                   1934: 	var blockTags = " body form textarea fieldset ul ol dl li div " +
                   1935: 		"p h1 h2 h3 h4 h5 h6 quote pre table thead " +
                   1936: 		"tbody tfoot tr td iframe address ";
                   1937: 	return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
                   1938: };
                   1939: 
                   1940: HTMLArea.needsClosingTag = function(el) {
1.3       www      1941: 	var closingTags = " head script style div span tr td tbody table em strong font a title ";
1.1       www      1942: 	return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
                   1943: };
                   1944: 
                   1945: // performs HTML encoding of some given string
                   1946: HTMLArea.htmlEncode = function(str) {
                   1947: 	// we don't need regexp for that, but.. so be it for now.
                   1948: 	str = str.replace(/&/ig, "&amp;");
                   1949: 	str = str.replace(/</ig, "&lt;");
                   1950: 	str = str.replace(/>/ig, "&gt;");
                   1951: 	str = str.replace(/\x22/ig, "&quot;");
                   1952: 	// \x22 means '"' -- we use hex reprezentation so that we don't disturb
                   1953: 	// JS compressors (well, at least mine fails.. ;)
                   1954: 	return str;
                   1955: };
                   1956: 
                   1957: // Retrieves the HTML code from the given node.	 This is a replacement for
                   1958: // getting innerHTML, using standard DOM calls.
1.3       www      1959: HTMLArea.getHTML = function(root, outputRoot, editor) {
1.1       www      1960: 	var html = "";
                   1961: 	switch (root.nodeType) {
                   1962: 	    case 1: // Node.ELEMENT_NODE
                   1963: 	    case 11: // Node.DOCUMENT_FRAGMENT_NODE
                   1964: 		var closed;
                   1965: 		var i;
1.3       www      1966: 		var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
                   1967: 		if (HTMLArea.is_ie && root_tag == "head") {
                   1968: 			if (outputRoot)
                   1969: 				html += "<head>";
                   1970: 			// lowercasize
                   1971: 			var save_multiline = RegExp.multiline;
                   1972: 			RegExp.multiline = true;
                   1973: 			var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
                   1974: 				return p1 + p2.toLowerCase();
                   1975: 			});
                   1976: 			RegExp.multiline = save_multiline;
                   1977: 			html += txt;
                   1978: 			if (outputRoot)
                   1979: 				html += "</head>";
                   1980: 			break;
                   1981: 		} else if (outputRoot) {
1.1       www      1982: 			closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root)));
                   1983: 			html = "<" + root.tagName.toLowerCase();
                   1984: 			var attrs = root.attributes;
                   1985: 			for (i = 0; i < attrs.length; ++i) {
                   1986: 				var a = attrs.item(i);
                   1987: 				if (!a.specified) {
                   1988: 					continue;
                   1989: 				}
                   1990: 				var name = a.nodeName.toLowerCase();
1.3       www      1991: 				if (/_moz|contenteditable|_msh/.test(name)) {
                   1992: 					// avoid certain attributes
1.1       www      1993: 					continue;
                   1994: 				}
                   1995: 				var value;
                   1996: 				if (name != "style") {
                   1997: 					// IE5.5 reports 25 when cellSpacing is
                   1998: 					// 1; other values might be doomed too.
                   1999: 					// For this reason we extract the
                   2000: 					// values directly from the root node.
                   2001: 					// I'm starting to HATE JavaScript
                   2002: 					// development.  Browser differences
                   2003: 					// suck.
1.3       www      2004: 					//
                   2005: 					// Using Gecko the values of href and src are converted to absolute links
                   2006: 					// unless we get them using nodeValue()
                   2007: 					if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
1.1       www      2008: 						value = root[a.nodeName];
                   2009: 					} else {
                   2010: 						value = a.nodeValue;
1.3       www      2011: 						// IE seems not willing to return the original values - it converts to absolute
                   2012: 						// links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href")
                   2013: 						// So we have to strip the baseurl manually -/
                   2014: 						if (HTMLArea.is_ie && (name == "href" || name == "src")) {
                   2015: 							value = editor.stripBaseURL(value);
                   2016: 						}
1.1       www      2017: 					}
                   2018: 				} else { // IE fails to put style in attributes list
                   2019: 					// FIXME: cssText reported by IE is UPPERCASE
                   2020: 					value = root.style.cssText;
                   2021: 				}
1.3       www      2022: 				if (/(_moz|^$)/.test(value)) {
1.1       www      2023: 					// Mozilla reports some special tags
                   2024: 					// here; we don't need them.
                   2025: 					continue;
                   2026: 				}
                   2027: 				html += " " + name + '="' + value + '"';
                   2028: 			}
                   2029: 			html += closed ? " />" : ">";
                   2030: 		}
                   2031: 		for (i = root.firstChild; i; i = i.nextSibling) {
1.3       www      2032: 			html += HTMLArea.getHTML(i, true, editor);
1.1       www      2033: 		}
                   2034: 		if (outputRoot && !closed) {
                   2035: 			html += "</" + root.tagName.toLowerCase() + ">";
                   2036: 		}
                   2037: 		break;
                   2038: 	    case 3: // Node.TEXT_NODE
1.3       www      2039: 		// If a text node is alone in an element and all spaces, replace it with an non breaking one
                   2040: 		// This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element
1.5     ! albertel 2041: 		// this was &nbsp; but since are document encodings are all over the place it causes random havoc
        !          2042: 		if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = ' ';
1.3       www      2043: 		else html = HTMLArea.htmlEncode(root.data);
1.1       www      2044: 		break;
                   2045: 	    case 8: // Node.COMMENT_NODE
                   2046: 		html = "<!--" + root.data + "-->";
                   2047: 		break;		// skip comments, for now.
                   2048: 	}
                   2049: 	return html;
                   2050: };
                   2051: 
1.3       www      2052: HTMLArea.prototype.stripBaseURL = function(string) {
                   2053: 	var baseurl = this.config.baseURL;
                   2054: 
                   2055: 	// strip to last directory in case baseurl points to a file
                   2056: 	baseurl = baseurl.replace(/[^\/]+$/, '');
                   2057: 	var basere = new RegExp(baseurl);
                   2058: 	string = string.replace(basere, "");
                   2059: 
                   2060: 	// strip host-part of URL which is added by MSIE to links relative to server root
                   2061: 	baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
                   2062: 	basere = new RegExp(baseurl);
                   2063: 	return string.replace(basere, "");
                   2064: };
                   2065: 
                   2066: String.prototype.trim = function() {
                   2067: 	a = this.replace(/^\s+/, '');
                   2068: 	return a.replace(/\s+$/, '');
                   2069: };
                   2070: 
1.1       www      2071: // creates a rgb-style color from a number
                   2072: HTMLArea._makeColor = function(v) {
                   2073: 	if (typeof v != "number") {
                   2074: 		// already in rgb (hopefully); IE doesn't get here.
                   2075: 		return v;
                   2076: 	}
                   2077: 	// IE sends number; convert to rgb.
                   2078: 	var r = v & 0xFF;
                   2079: 	var g = (v >> 8) & 0xFF;
                   2080: 	var b = (v >> 16) & 0xFF;
                   2081: 	return "rgb(" + r + "," + g + "," + b + ")";
                   2082: };
                   2083: 
                   2084: // returns hexadecimal color representation from a number or a rgb-style color.
                   2085: HTMLArea._colorToRgb = function(v) {
1.3       www      2086: 	if (!v)
                   2087: 		return '';
                   2088: 
1.1       www      2089: 	// returns the hex representation of one byte (2 digits)
                   2090: 	function hex(d) {
                   2091: 		return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
                   2092: 	};
                   2093: 
                   2094: 	if (typeof v == "number") {
                   2095: 		// we're talking to IE here
                   2096: 		var r = v & 0xFF;
                   2097: 		var g = (v >> 8) & 0xFF;
                   2098: 		var b = (v >> 16) & 0xFF;
                   2099: 		return "#" + hex(r) + hex(g) + hex(b);
                   2100: 	}
                   2101: 
                   2102: 	if (v.substr(0, 3) == "rgb") {
                   2103: 		// in rgb(...) form -- Mozilla
                   2104: 		var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
                   2105: 		if (v.match(re)) {
                   2106: 			var r = parseInt(RegExp.$1);
                   2107: 			var g = parseInt(RegExp.$2);
                   2108: 			var b = parseInt(RegExp.$3);
                   2109: 			return "#" + hex(r) + hex(g) + hex(b);
                   2110: 		}
                   2111: 		// doesn't match RE?!  maybe uses percentages or float numbers
                   2112: 		// -- FIXME: not yet implemented.
                   2113: 		return null;
                   2114: 	}
                   2115: 
1.3       www      2116: 	if (v.substr(0, 1) == "#") {
1.1       www      2117: 		// already hex rgb (hopefully :D )
                   2118: 		return v;
                   2119: 	}
                   2120: 
                   2121: 	// if everything else fails ;)
                   2122: 	return null;
                   2123: };
                   2124: 
                   2125: // modal dialogs for Mozilla (for IE we're using the showModalDialog() call).
                   2126: 
                   2127: // receives an URL to the popup dialog and a function that receives one value;
                   2128: // this function will get called after the dialog is closed, with the return
                   2129: // value of the dialog.
                   2130: HTMLArea.prototype._popupDialog = function(url, action, init) {
                   2131: 	Dialog(this.popupURL(url), action, init);
                   2132: };
                   2133: 
                   2134: // paths
                   2135: 
                   2136: HTMLArea.prototype.imgURL = function(file, plugin) {
1.3       www      2137: 	if (typeof plugin == "undefined")
                   2138: 		return _editor_url + file;
                   2139: 	else
                   2140: 		return _editor_url + "plugins/" + plugin + "/img/" + file;
1.1       www      2141: };
                   2142: 
                   2143: HTMLArea.prototype.popupURL = function(file) {
1.3       www      2144: 	var url = "";
                   2145: 	if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
                   2146: 		var plugin = RegExp.$1;
                   2147: 		var popup = RegExp.$2;
                   2148: 		if (!/\.html$/.test(popup))
                   2149: 			popup += ".html";
                   2150: 		url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
                   2151: 	} else
                   2152: 		url = _editor_url + this.config.popupURL + file;
                   2153: 	return url;
1.1       www      2154: };
                   2155: 
1.3       www      2156: /**
                   2157:  * FIX: Internet Explorer returns an item having the _name_ equal to the given
                   2158:  * id, even if it's not having any id.  This way it can return a different form
                   2159:  * field even if it's not a textarea.  This workarounds the problem by
                   2160:  * specifically looking to search only elements having a certain tag name.
                   2161:  */
                   2162: HTMLArea.getElementById = function(tag, id) {
                   2163: 	var el, i, objs = document.getElementsByTagName(tag);
                   2164: 	for (i = objs.length; --i >= 0 && (el = objs[i]);)
                   2165: 		if (el.id == id)
                   2166: 			return el;
                   2167: 	return null;
                   2168: };
                   2169: 
                   2170: 
                   2171: 
1.1       www      2172: // EOF
                   2173: // Local variables: //
                   2174: // c-basic-offset:8 //
                   2175: // indent-tabs-mode:t //
                   2176: // End: //

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>