From b3d3ca5b79233687bbd20fa31563b66dc8ef9860 Mon Sep 17 00:00:00 2001 From: julmis <julmis> Date: Sun, 15 Aug 2004 11:56:45 +0000 Subject: [PATCH] Big update for the editor! Code is closer to HTMLArea 3.0 RC1. - Updated dialog behavior (no more showModalDialogs) - Cleaner XHTML coding in dialogs - Unnecessary comment stripped off from htmlarea.php - New anchor feature. --- lib/editor/dialog.js | 41 +- lib/editor/htmlarea.php | 221 +- lib/editor/htmlarea_bak.php | 4315 +++++++++++++----------- lib/editor/popups/createanchor.php | 68 + lib/editor/popups/dlg_ins_char.php | 52 +- lib/editor/popups/dlg_ins_smile.php | 27 +- lib/editor/popups/insert_image.php | 27 +- lib/editor/popups/insert_image_std.php | 14 +- lib/editor/popups/insert_table.php | 16 +- lib/editor/popups/link.php | 46 +- lib/editor/popups/link_std.php | 44 +- lib/editor/popups/popup.js | 103 +- lib/editor/popups/preview.php | 3 +- 13 files changed, 2620 insertions(+), 2357 deletions(-) create mode 100644 lib/editor/popups/createanchor.php diff --git a/lib/editor/dialog.js b/lib/editor/dialog.js index 2685fc81cf..33f86013cf 100644 --- a/lib/editor/dialog.js +++ b/lib/editor/dialog.js @@ -6,25 +6,13 @@ function Dialog(url, action, init) { if (typeof init == "undefined") { init = window; // pass this window object by default } - if (document.all) { // here we hope that Mozilla will never support document.all - var value = - showModalDialog(url, init, - //window.open(url, '_blank', - "resizable: no; help: no; status: no; scroll: no"); - if (action) { - action(value); - } - } else { - return Dialog._geckoOpenModal(url, action, init); - } + Dialog._geckoOpenModal(url, action, init); }; Dialog._parentEvent = function(ev) { if (Dialog._modal && !Dialog._modal.closed) { - // we get here in Mozilla only, anyway, so we can safely use - // the DOM version. - ev.preventDefault(); - ev.stopPropagation(); + Dialog._modal.focus(); + HTMLArea._stopEvent(ev); } }; @@ -44,12 +32,13 @@ Dialog._geckoOpenModal = function(url, action, init) { switch(file) { case "insert_image": x = 730; y = 540; break; case "link_std": x = 400; y = 180; break; - case "dlg_ins_smile": x = 330; y = 360; break; - case "dlg_ins_char": x = 450; y = 270; break; + case "dlg_ins_smile": x = 330; y = 300; break; + case "dlg_ins_char": x = 480; y = 270; break; case "select_color": x = 238; y = 188; break; - case "insert_table": x = 410; y = 240; break; + case "insert_table": x = 420; y = 240; break; case "link_std": x = 420; y = 210; break; case "insert_image_std": x = 450; y = 230; break; + case "createanchor": x = 300; y = 130; break; default: x = 50; y = 50; } @@ -61,19 +50,19 @@ Dialog._geckoOpenModal = function(url, action, init) { // capture some window's events function capwin(w) { - w.addEventListener("click", Dialog._parentEvent, true); - w.addEventListener("mousedown", Dialog._parentEvent, true); - w.addEventListener("focus", Dialog._parentEvent, true); + HTMLArea._addEvent(w, "click", Dialog._parentEvent); + HTMLArea._addEvent(w, "mousedown", Dialog._parentEvent); + HTMLArea._addEvent(w, "focus", Dialog._parentEvent); }; // release the captured events function relwin(w) { - w.removeEventListener("focus", Dialog._parentEvent, true); - w.removeEventListener("mousedown", Dialog._parentEvent, true); - w.removeEventListener("click", Dialog._parentEvent, true); + HTMLArea._removeEvent(w, "click", Dialog._parentEvent); + HTMLArea._removeEvent(w, "mousedown", Dialog._parentEvent); + HTMLArea._removeEvent(w, "focus", Dialog._parentEvent); }; capwin(window); // capture other frames - //for (var i = 0; i < window.frames.length; capwin(window.frames[i++])); + for (var i = 0; i < window.frames.length; capwin(window.frames[i++])); // make up a function to be called when the Dialog ends. Dialog._return = function (val) { if (val && action) { @@ -81,7 +70,7 @@ Dialog._geckoOpenModal = function(url, action, init) { } relwin(window); // capture other frames - //for (var i = 0; i < window.frames.length; relwin(window.frames[i++])); + for (var i = 0; i < window.frames.length; relwin(window.frames[i++])); Dialog._modal = null; }; }; diff --git a/lib/editor/htmlarea.php b/lib/editor/htmlarea.php index cf167e6b1b..98e2dd27c1 100644 --- a/lib/editor/htmlarea.php +++ b/lib/editor/htmlarea.php @@ -132,15 +132,6 @@ HTMLArea.Config = function () { this.imgURL = "images/"; this.popupURL = "popups/"; - /** CUSTOMIZING THE TOOLBAR - * ------------------------- - * - * It is recommended that you customize the toolbar contents in an - * external file (i.e. the one calling HTMLArea) and leave this one - * unchanged. That's because when we (InteractiveTools.com) release a - * new official version, it's less likely that you will have problems - * upgrading HTMLArea. - */ this.toolbar = [ [ "fontname", "space", "fontsize", "space", @@ -153,7 +144,7 @@ HTMLArea.Config = function () { "lefttoright", "righttoleft", "separator", "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator", "forecolor", "hilitecolor", "separator", - "inserthorizontalrule", "createlink", "unlink", "insertimage", "inserttable", + "inserthorizontalrule", "createanchor", "createlink", "unlink", "insertimage", "inserttable", "insertsmile", "insertchar", "separator", "htmlmode", "separator", "popupeditor" ] ]; @@ -164,7 +155,7 @@ HTMLArea.Config = function () { "Tahoma": 'tahoma,arial,helvetica,sans-serif', "Times New Roman": 'times new roman,times,serif', "Verdana": 'verdana,arial,helvetica,sans-serif', - "impact": 'impact', + "Impact": 'impact', "WingDings": 'wingdings' }; @@ -196,20 +187,6 @@ HTMLArea.Config = function () { e.execCommand(cmd); }; - // ADDING CUSTOM BUTTONS: please read below! - // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]" - // - ID: unique ID for the button. If the button calls document.execCommand - // it's wise to give it the same name as the called command. - // - ACTION: function that gets called when the button is clicked. - // it has the following prototype: - // function(editor, buttonName) - // - editor is the HTMLArea object that triggered the call - // - buttonName is the ID of the clicked button - // These 2 parameters makes it possible for you to use the same - // handler for more HTMLArea objects or for more different buttons. - // - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N) - // - Icon: path to an icon image file for the button (TODO: use one image for all buttons!) - // - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time. this.btnList = { bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ], italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ], @@ -228,6 +205,7 @@ HTMLArea.Config = function () { forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ], hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ], inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ], + createanchor: [ "Create anchor", "ed_anchor.gif", false, function(e) {e.execCommand("createanchor", true);} ], createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ], unlink: [ "Remove Link", "ed_unlink.gif", false, function(e) {e.execCommand("unlink");} ], insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ], @@ -247,29 +225,7 @@ HTMLArea.Config = function () { insertsmile: ["Insert Smiley", "em.icon.smile.gif", false, function(e) {e.execCommand("insertsmile");} ], insertchar: [ "Insert Char", "icon_ins_char.gif", false, function(e) {e.execCommand("insertchar");} ] }; - /* ADDING CUSTOM BUTTONS - * --------------------- - * - * It is recommended that you add the custom buttons in an external - * file and leave this one unchanged. That's because when we - * (InteractiveTools.com) release a new official version, it's less - * likely that you will have problems upgrading HTMLArea. - * - * Example on how to add a custom button when you construct the HTMLArea: - * - * var editor = new HTMLArea("your_text_area_id"); - * var cfg = editor.config; // this is the default configuration - * cfg.btnList["my-hilite"] = - * [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action - * "Highlight selection", // tooltip - * "my_hilite.gif", // image - * false // disabled in text mode - * ]; - * cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar - * - * An alternate (also more convenient and recommended) way to - * accomplish this is to use the registerButton function below. - */ + // initialize tooltips from the I18N module and generate correct image path for (var i in this.btnList) { var btn = this.btnList[i]; @@ -280,23 +236,6 @@ HTMLArea.Config = function () { } }; -/** Helper function: register a new button with the configuration. It can be - * called with all 5 arguments, or with only one (first one). When called with - * only one argument it must be an object with the following properties: id, - * tooltip, image, textMode, action. Examples: - * - * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}); - * 2. config.registerButton({ - * id : "my-hilite", // the ID of your button - * tooltip : "Hilite text", // the tooltip - * image : "my-hilite.gif", // image to be displayed in the toolbar - * textMode : false, // disabled in text mode - * action : function(editor) { // called when the button is clicked - * editor.surroundHTML('<span class="hilite">', '</span>'); - * }, - * context : "p" // will be disabled if outside a <p> element - * }); - */ HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) { var the_id; if (typeof id == "string") { @@ -320,12 +259,6 @@ HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode } }; -/** The following helper function registers a dropdown box with the editor - * configuration. You still have to add it to the toolbar, same as with the - * buttons. Call it like this: - * - * FIXME: add example - */ HTMLArea.Config.prototype.registerDropdown = function(object) { // check for existing id if (typeof this.customSelects[object.id] != "undefined") { @@ -337,16 +270,6 @@ HTMLArea.Config.prototype.registerDropdown = function(object) { this.customSelects[object.id] = object; }; -/** Call this function to remove some buttons/drop-down boxes from the toolbar. - * Pass as the only parameter a string containing button/drop-down names - * delimited by spaces. Note that the string should also begin with a space - * and end with a space. Example: - * - * config.hideSomeButtons(" fontname fontsize textindicator "); - * - * It's useful because it's easier to remove stuff from the defaul toolbar than - * create a brand new toolbar ;-) - */ HTMLArea.Config.prototype.hideSomeButtons = function(remove) { var toolbar = this.toolbar; for (var i in toolbar) { @@ -404,9 +327,6 @@ HTMLArea.prototype._createToolbar = function () { // init first line newLine(); - // updates the state of a toolbar element. This function is member of - // a toolbar element object (unnamed objects created by createButton or - // createSelect functions below). function setButtonStatus(id, newval) { var oldval = this[id]; var el = this.element; @@ -433,10 +353,6 @@ HTMLArea.prototype._createToolbar = function () { } }; // END of function: setButtonStatus - // this function will handle creation of combo boxes. Receives as - // parameter the name of a button as defined in the toolBar config. - // This function is called from createButton, above, if the given "txt" - // doesn't match a button. function createSelect(txt) { var options = null; var el = null; @@ -447,13 +363,6 @@ HTMLArea.prototype._createToolbar = function () { case "fontsize": case "fontname": case "formatblock": - // the following line retrieves the correct - // configuration option because the variable name - // inside the Config object is named the same as the - // button/select in the toolbar. For instance, if txt - // == "formatblock" we retrieve config.formatblock (or - // a different way to write it in JS is - // config["formatblock"]. options = editor.config[txt]; cmd = txt; break; @@ -717,9 +626,6 @@ HTMLArea.prototype.generate = function () { if (!HTMLArea.is_ie) { iframe.style.borderWidth = "1px"; - // iframe.frameBorder = "1"; - // iframe.marginHeight = "0"; - // iframe.marginWidth = "0"; } // size the IFRAME according to user's prefs or initial textarea @@ -795,9 +701,6 @@ HTMLArea.prototype.generate = function () { } if (HTMLArea.is_ie) { - // enable editable mode for IE. For some reason this - // doesn't work if done in the same place as for Gecko - // (above). doc.body.contentEditable = true; } @@ -899,14 +802,8 @@ HTMLArea.prototype.setFullHTML = function(html) { } }; -/*************************************************** - * Category: PLUGINS - ***************************************************/ +// Category: PLUGINS -// this is the variant of the function above where the plugin arguments are -// already packed in an array. Externally, it should be only used in the -// full-screen editor code, in order to initialize plugins with the same -// parameters as in the opener window. HTMLArea.prototype.registerPlugin2 = function(plugin, args) { if (typeof plugin == "string") plugin = eval(plugin); @@ -932,9 +829,6 @@ HTMLArea.prototype.registerPlugin = function() { this.registerPlugin2(plugin, args); }; -// static function that loads the required plugin and lang file, based on the -// language loaded already for HTMLArea. You better make sure that the plugin -// _has_ that language, otherwise shit might happen ;-) HTMLArea.loadPlugin = function(pluginName) { var dir = _editor_url + "plugins/" + pluginName; var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, @@ -958,9 +852,7 @@ HTMLArea.loadStyle = function(style, plugin) { }; HTMLArea.loadStyle("htmlarea.css"); -/*************************************************** - * Category: EDITOR UTILITIES - ***************************************************/ +// Category: EDITOR UTILITIES // The following function is a slight variation of the word cleaner code posted // by Weeezl (user @ InteractiveTools forums). @@ -1185,10 +1077,6 @@ HTMLArea.prototype.updateToolbar = function(noStatus) { // FIXME: what do we do here? break; } - // HACK -- retrieve the config option for this - // combo box. We rely on the fact that the - // variable in config has the same name as - // button name in the toolbar. var options = this.config[cmd]; var k = 0; // btn.element.selectedIndex = 0; @@ -1308,11 +1196,6 @@ HTMLArea.prototype.getParentElement = function() { switch (sel.type) { case "Text": case "None": - // It seems that even for selection of type "None", - // there _is_ a parent element and it's value is not - // only correct, but very important to us. MSIE is - // certainly the buggiest browser in the world and I - // wonder, God, how can Earth stand it? return range.parentElement(); case "Control": return range.item(0); @@ -1370,9 +1253,8 @@ HTMLArea.prototype.selectNodeContents = function(node, pos) { } }; -/** Call this function to insert HTML code at the current position. It deletes - * the selection, if any. - */ +// Call this function to insert HTML code at the current position. It deletes +// the selection, if any. HTMLArea.prototype.insertHTML = function(html) { var sel = this._getSelection(); var range = this._createRange(sel); @@ -1392,10 +1274,8 @@ HTMLArea.prototype.insertHTML = function(html) { } }; -/** - * Call this function to surround the existing HTML code in the selection with - * your tags. FIXME: buggy! This function will be deprecated "soon". - */ +// Call this function to surround the existing HTML code in the selection with +// your tags. FIXME: buggy! This function will be deprecated "soon". HTMLArea.prototype.surroundHTML = function(startTag, endTag) { var html = this.getSelectedHTML(); // the following also deletes the selection @@ -1423,17 +1303,31 @@ HTMLArea.prototype.hasSelectedText = function() { HTMLArea.prototype._createLink = function(link) { var editor = this; + var allinks = editor._doc.getElementsByTagName('A'); + var anchors = new Array(); + for(var i = 0; i < allinks.length; i++) { + var attrname = allinks[i].getAttribute('name'); + if((HTMLArea.is_ie ? attrname.length > 0 : attrname != null)) { + anchors[i] = allinks[i].getAttribute('name'); + } + } var outparam = null; if (typeof link == "undefined") { link = this.getParentElement(); if (link && !/^a$/i.test(link.tagName)) link = null; } - if (link) outparam = { + if (link) { + outparam = { f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"), f_title : link.title, - f_target : link.target + f_target : link.target, + f_anchors: anchors }; + } else { + outparam = { + f_anchors:anchors }; + } this._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param) { if (!param) return false; @@ -1587,11 +1481,8 @@ HTMLArea.prototype._insertTable = function() { return true; }, null); }; -/****************************************************************** -* Moodle hack - insertSmile -******************************************************************/ -/// since method insertimage doesn't work the same way in mozilla -/// as it does in IE, let's go around this for both browsers. + +/// Moodle hack - insertSmile HTMLArea.prototype._insertSmile = function() { var sel = this._getSelection(); var range = this._createRange(sel); @@ -1603,7 +1494,6 @@ HTMLArea.prototype._insertSmile = function() { if (HTMLArea.is_ie) { range.pasteHTML(imgString); } else { - // insert the table editor.insertHTML(imgString); } return true; @@ -1636,12 +1526,30 @@ HTMLArea.prototype._removelink = function() { this._doc.execCommand("unlink", false, null); this.focusEditor(); }; -/************************************************************************ -* Moodle hack's ends -************************************************************************/ -/*************************************************** - * Category: EVENT HANDLERS - ***************************************************/ + +HTMLArea.prototype._createanchor = function () { + var editor = this; + var sel = this._getSelection(); + var rng = this._createRange(sel); + var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length; + if(len < 1) { + alert("You must select text first!"); + return false; + } + this._popupDialog("createanchor.php", function(objAn) { + if(!objAn) { + return false; + } + var str = '<a name="'+ objAn.anchor+'">'; + str += HTMLArea.is_ie ? rng.text : sel ; + str += '</a>'; + editor.insertHTML(str); + },null); +}; + +/// Moodle hack's ends +// +// Category: EVENT HANDLERS // el is reference to the SELECT object // txt is the name of the select field, as in config.toolbar @@ -1683,6 +1591,7 @@ HTMLArea.prototype.execCommand = function(cmdID, UI, param) { } }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID))); break; + case "createanchor": this._createanchor(); break; case "createlink": this._createLink(); break; @@ -1691,7 +1600,6 @@ HTMLArea.prototype.execCommand = function(cmdID, UI, param) { // this object will be passed to the newly opened window HTMLArea._object = this; if (HTMLArea.is_ie) { - //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"])) { window.open(this.popupURL("fullscreen.php?id=<?php print($id);?>"), "ha_fullscreen", "toolbar=no,location=no,directories=no,status=no,menubar=no," + @@ -2114,13 +2022,6 @@ HTMLArea.getHTML = function(root, outputRoot, editor) { } var value; if (name != "style") { - // IE5.5 reports 25 when cellSpacing is - // 1; other values might be doomed too. - // For this reason we extract the - // values directly from the root node. - // I'm starting to HATE JavaScript - // development. Browser differences - // suck. // // Using Gecko the values of href and src are converted to absolute links // unless we get them using nodeValue() @@ -2128,9 +2029,6 @@ HTMLArea.getHTML = function(root, outputRoot, editor) { value = root[a.nodeName]; } else { value = a.nodeValue; - // IE seems not willing to return the original values - it converts to absolute - // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href") - // So we have to strip the baseurl manually -/ if (HTMLArea.is_ie && (name == "href" || name == "src")) { value = editor.stripBaseURL(value); } @@ -2250,11 +2148,6 @@ HTMLArea._colorToRgb = function(v) { return null; }; -// modal dialogs for Mozilla (for IE we're using the showModalDialog() call). - -// receives an URL to the popup dialog and a function that receives one value; -// this function will get called after the dialog is closed, with the return -// value of the dialog. HTMLArea.prototype._popupDialog = function(url, action, init) { Dialog(this.popupURL(url), action, init); }; @@ -2294,11 +2187,3 @@ HTMLArea.getElementById = function(tag, id) { return el; return null; }; - - - -// EOF -// Local variables: // -// c-basic-offset:8 // -// indent-tabs-mode:t // -// End: // diff --git a/lib/editor/htmlarea_bak.php b/lib/editor/htmlarea_bak.php index f3e2350b9d..cf167e6b1b 100644 --- a/lib/editor/htmlarea_bak.php +++ b/lib/editor/htmlarea_bak.php @@ -1,2011 +1,2304 @@ -<?php - include("../../config.php"); - - $lastmodified = filemtime("htmlarea.php"); - $lifetime = 1800; - - header("Content-type: application/x-javascript"); // Correct MIME type - header("Last-Modified: " . gmdate("D, d M Y H:i:s", lastmodified) . " GMT"); - header("Expires: " . gmdate("D, d M Y H:i:s", time() + $lifetime) . " GMT"); - header("Cache-control: max_age = $lifetime"); - header("Pragma: "); - - $lang = current_language(); - - if (empty($lang)) { - $lang = "en"; - } - - $strheading = get_string("heading", "editor"); - $strnormal = get_string("normal", "editor"); - $straddress = get_string("address", "editor"); - $strpreformatted = get_string("preformatted", "editor"); -?> - -// -// htmlArea v3.0 - Copyright (c) 2002 interactivetools.com, inc. -// This copyright notice MUST stay intact for use (see license.txt). -// -// A free WYSIWYG editor replacement for <textarea> fields. -// For full source code and docs, visit http://www.interactivetools.com/ -// -// Version 3.0 developed by Mihai Bazon for InteractiveTools. -// http://dynarch.com/mishoo -// -// -// Modified for Moodle by Janne Mikkonen -// $Id$ - -// Creates a new HTMLArea object. Tries to replace the textarea with the given -// ID with it. -function HTMLArea(textarea, config) { - if (HTMLArea.checkSupportedBrowser()) { - if (typeof config == "undefined") { - this.config = new HTMLArea.Config(); - } else { - this.config = config; - } - this._htmlArea = null; - this._textArea = textarea; - this._editMode = "wysiwyg"; - this.plugins = {}; - this._timerToolbar = null; - this._timerUndo = null; - this._undoQueue = new Array(this.config.undoSteps); - this._undoPos = -1; - this._mdoc = document; // cache the document, we need it in plugins - this.doctype = ''; - } - // Hide cut, copy and paste buttons from gecko browsers - if (HTMLArea.is_gecko) { - this.config.hideSomeButtons(" cut copy paste "); - } -}; - -// cache some regexps -HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig; -HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i; -HTMLArea.RE_head = /<head>((.|\n)*?)<\/head>/i; -HTMLArea.RE_body = /<body>((.|\n)*?)<\/body>/i; - -HTMLArea.Config = function () { - this.version = "3.0"; - - this.width = "auto"; - this.height = "auto"; - - // enable creation of a status bar? - this.statusBar = true; - - // maximum size of the undo queue - this.undoSteps = 20; - - // the time interval at which undo samples are taken - this.undoTimeout = 500; // 1/2 sec. - - // the next parameter specifies whether the toolbar should be included - // in the size or not. - this.sizeIncludesToolbar = true; - - // if true then HTMLArea will retrieve the full HTML, starting with the - // <HTML> tag. - this.fullPage = false; - - // style included in the iframe document - this.pageStyle = "body { background-color: #fff; font-family: 'Times New Roman', Times; }"; - if (typeof _editor_url != "undefined") { - this.editorURL = _editor_url; - } else { - this.editorURL = "<?php echo $CFG->wwwroot ?>/lib/editor/"; - } - - // URL-s - this.imgURL = "images/"; - this.popupURL = "popups/"; - - /** CUSTOMIZING THE TOOLBAR - * ------------------------- - * - * It is recommended that you customize the toolbar contents in an - * external file (i.e. the one calling HTMLArea) and leave this one - * unchanged. That's because when we (InteractiveTools.com) release a - * new official version, it's less likely that you will have problems - * upgrading HTMLArea. - */ - this.toolbar = [ - [ "fontname", "space", - "fontsize", "space", - "formatblock", "space", - "bold", "italic", "underline", "strikethrough", "separator", - "subscript", "superscript", "separator", - "copy", "cut", "paste", "space", "undo", "redo" ], - - [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator", - "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator", - "forecolor", "hilitecolor", "separator", - "inserthorizontalrule", "createlink", "insertimage", "inserttable", - "insertsmile", "insertchar", "separator", "htmlmode", "separator", "popupeditor" ] - ]; - - this.fontname = { - "Arial": 'arial,helvetica,sans-serif', - "Courier New": 'courier new,courier,monospace', - "Georgia": 'georgia,times new roman,times,serif', - "Impact": 'impact', - "Tahoma": 'tahoma,arial,helvetica,sans-serif', - "Times New Roman": 'times new roman,times,serif', - "Verdana": 'verdana,arial,helvetica,sans-serif', - "WingDings": 'wingdings' - }; - - this.fontsize = { - "1 (8 pt)": "1", - "2 (10 pt)": "2", - "3 (12 pt)": "3", - "4 (14 pt)": "4", - "5 (18 pt)": "5", - "6 (24 pt)": "6", - "7 (36 pt)": "7" - }; - - this.formatblock = { - "<?php echo $strheading ?> 1": "h1", - "<?php echo $strheading ?> 2": "h2", - "<?php echo $strheading ?> 3": "h3", - "<?php echo $strheading ?> 4": "h4", - "<?php echo $strheading ?> 5": "h5", - "<?php echo $strheading ?> 6": "h6", - "<?php echo $strnormal ?>": "p", - "<?php echo $straddress ?>": "address", - "<?php echo $strpreformatted ?>": "pre" - }; - - this.customSelects = {}; - - function cut_copy_paste(e, cmd, obj) { - try { - e.execCommand(cmd); - } catch (e) { - if (HTMLArea.is_gecko) { - alert("Some revisions of Mozilla/Gecko do not support programatic " + - "access to cut/copy/paste functions, for security reasons. " + - "Your browser is one of them. Please use the standard key combinations:\n" + - "CTRL-X for cut, CTRL-C for copy, CTRL-V for paste."); - obj.element.style.display = "none"; - } - } - }; - - // ADDING CUSTOM BUTTONS: please read below! - // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]" - // - ID: unique ID for the button. If the button calls document.execCommand - // it's wise to give it the same name as the called command. - // - ACTION: function that gets called when the button is clicked. - // it has the following prototype: - // function(editor, buttonName) - // - editor is the HTMLArea object that triggered the call - // - buttonName is the ID of the clicked button - // These 2 parameters makes it possible for you to use the same - // handler for more HTMLArea objects or for more different buttons. - // - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N) - // - Icon: path to an icon image file for the button (TODO: use one image for all buttons!) - // - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time. - this.btnList = { - bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ], - italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ], - underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ], - strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ], - subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ], - superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ], - justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ], - justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ], - justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ], - justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ], - insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ], - insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ], - outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ], - indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ], - forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ], - hilitecolor: [ "Background Colorxxx", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ], - inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ], - createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ], - insertimage: [ "Insert Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ], - inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ], - htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ], - popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ], - about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ], - showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ], - undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ], - redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ], - cut: [ "Cut selection", "ed_cut.gif", false, cut_copy_paste ], - copy: [ "Copy selection", "ed_copy.gif", false, cut_copy_paste ], - paste: [ "Paste from clipboard", "ed_paste.gif", false, cut_copy_paste ], - insertsmile: ["Insert Smiley", "em.icon.smile.gif", false, function(e) {e.execCommand("insertsmile");} ], - insertchar: [ "Insert Char", "icon_ins_char.gif", false, function(e) {e.execCommand("insertchar");} ] - }; - /* ADDING CUSTOM BUTTONS - * --------------------- - * - * It is recommended that you add the custom buttons in an external - * file and leave this one unchanged. That's because when we - * (InteractiveTools.com) release a new official version, it's less - * likely that you will have problems upgrading HTMLArea. - * - * Example on how to add a custom button when you construct the HTMLArea: - * - * var editor = new HTMLArea("your_text_area_id"); - * var cfg = editor.config; // this is the default configuration - * cfg.btnList["my-hilite"] = - * [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action - * "Highlight selection", // tooltip - * "my_hilite.gif", // image - * false // disabled in text mode - * ]; - * cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar - * - * An alternate (also more convenient and recommended) way to - * accomplish this is to use the registerButton function below. - */ - // initialize tooltips from the I18N module and generate correct image path - for (var i in this.btnList) { - var btn = this.btnList[i]; - btn[1] = this.editorURL + this.imgURL + btn[1]; - if (typeof HTMLArea.I18N.tooltips[i] != "undefined") { - btn[0] = HTMLArea.I18N.tooltips[i]; - } - } -}; - -/** Helper function: register a new button with the configuration. It can be - * called with all 5 arguments, or with only one (first one). When called with - * only one argument it must be an object with the following properties: id, - * tooltip, image, textMode, action. Examples: - * - * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}); - * 2. config.registerButton({ - * id : "my-hilite", // the ID of your button - * tooltip : "Hilite text", // the tooltip - * image : "my-hilite.gif", // image to be displayed in the toolbar - * textMode : false, // disabled in text mode - * action : function(editor) { // called when the button is clicked - * editor.surroundHTML('<span class="hilite">', '</span>'); - * }, - * context : "p" // will be disabled if outside a <p> element - * }); - */ -HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) { - var the_id; - if (typeof id == "string") { - the_id = id; - } else if (typeof id == "object") { - the_id = id.id; - } else { - alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments"); - return false; - } - // check for existing id - if (typeof this.customSelects[the_id] != "undefined") { - alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); - } - if (typeof this.btnList[the_id] != "undefined") { - alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); - } - switch (typeof id) { - case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break; - case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break; - } -}; - -/** The following helper function registers a dropdown box with the editor - * configuration. You still have to add it to the toolbar, same as with the - * buttons. Call it like this: - * - * FIXME: add example - */ -HTMLArea.Config.prototype.registerDropdown = function(object) { - // check for existing id - if (typeof this.customSelects[object.id] != "undefined") { - alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); - } - if (typeof this.btnList[object.id] != "undefined") { - alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); - } - this.customSelects[object.id] = object; -}; - -/** Call this function to remove some buttons/drop-down boxes from the toolbar. - * Pass as the only parameter a string containing button/drop-down names - * delimited by spaces. Note that the string should also begin with a space - * and end with a space. Example: - * - * config.hideSomeButtons(" fontname fontsize textindicator "); - * - * It's useful because it's easier to remove stuff from the defaul toolbar than - * create a brand new toolbar ;-) - */ -HTMLArea.Config.prototype.hideSomeButtons = function(remove) { - var toolbar = this.toolbar; - for (var i in toolbar) { - var line = toolbar[i]; - for (var j = line.length; --j >= 0; ) { - if (remove.indexOf(" " + line[j] + " ") >= 0) { - var len = 1; - if (/separator|space/.test(line[j + 1])) { - len = 2; - } - line.splice(j, len); - } - } - } -}; - -/** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */ -HTMLArea.replaceAll = function(config) { - var tas = document.getElementsByTagName("textarea"); - for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate()); -}; - -/** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */ -HTMLArea.replace = function(id, config) { - var ta = document.getElementById(id); - return ta ? (new HTMLArea(ta, config)).generate() : null; -}; - -// Creates the toolbar and appends it to the _htmlarea -HTMLArea.prototype._createToolbar = function () { - var editor = this; // to access this in nested functions - - var toolbar = document.createElement("div"); - this._toolbar = toolbar; - toolbar.className = "toolbar"; - toolbar.unselectable = "1"; - var tb_row = null; - var tb_objects = new Object(); - this._toolbarObjects = tb_objects; - - // creates a new line in the toolbar - function newLine() { - var table = document.createElement("table"); - table.border = "0px"; - table.cellSpacing = "0px"; - table.cellPadding = "0px"; - toolbar.appendChild(table); - // TBODY is required for IE, otherwise you don't see anything - // in the TABLE. - var tb_body = document.createElement("tbody"); - table.appendChild(tb_body); - tb_row = document.createElement("tr"); - tb_body.appendChild(tb_row); - }; // END of function: newLine - // init first line - newLine(); - - // updates the state of a toolbar element. This function is member of - // a toolbar element object (unnamed objects created by createButton or - // createSelect functions below). - function setButtonStatus(id, newval) { - var oldval = this[id]; - var el = this.element; - if (oldval != newval) { - switch (id) { - case "enabled": - if (newval) { - HTMLArea._removeClass(el, "buttonDisabled"); - el.disabled = false; - } else { - HTMLArea._addClass(el, "buttonDisabled"); - el.disabled = true; - } - break; - case "active": - if (newval) { - HTMLArea._addClass(el, "buttonPressed"); - } else { - HTMLArea._removeClass(el, "buttonPressed"); - } - break; - } - this[id] = newval; - } - }; // END of function: setButtonStatus - - // this function will handle creation of combo boxes. Receives as - // parameter the name of a button as defined in the toolBar config. - // This function is called from createButton, above, if the given "txt" - // doesn't match a button. - function createSelect(txt) { - var options = null; - var el = null; - var cmd = null; - var customSelects = editor.config.customSelects; - var context = null; - switch (txt) { - case "fontsize": - case "fontname": - case "formatblock": - // the following line retrieves the correct - // configuration option because the variable name - // inside the Config object is named the same as the - // button/select in the toolbar. For instance, if txt - // == "formatblock" we retrieve config.formatblock (or - // a different way to write it in JS is - // config["formatblock"]. - options = editor.config[txt]; - cmd = txt; - break; - default: - // try to fetch it from the list of registered selects - cmd = txt; - var dropdown = customSelects[cmd]; - if (typeof dropdown != "undefined") { - options = dropdown.options; - context = dropdown.context; - } else { - alert("ERROR [createSelect]:\nCan't find the requested dropdown definition"); - } - break; - } - if (options) { - el = document.createElement("select"); - var obj = { - name : txt, // field name - element : el, // the UI element (SELECT) - enabled : true, // is it enabled? - text : false, // enabled in text mode? - cmd : cmd, // command ID - state : setButtonStatus, // for changing state - context : context - }; - tb_objects[txt] = obj; - for (var i in options) { - var op = document.createElement("option"); - op.appendChild(document.createTextNode(i)); - op.value = options[i]; - el.appendChild(op); - } - HTMLArea._addEvent(el, "change", function () { - editor._comboSelected(el, txt); - }); - } - return el; - }; // END of function: createSelect - - // appends a new button to toolbar - function createButton(txt) { - // the element that will be created - var el = null; - var btn = null; - switch (txt) { - case "separator": - el = document.createElement("div"); - el.className = "separator"; - break; - case "space": - el = document.createElement("div"); - el.className = "space"; - break; - case "linebreak": - newLine(); - return false; - case "textindicator": - el = document.createElement("div"); - el.appendChild(document.createTextNode("A")); - el.className = "indicator"; - el.title = HTMLArea.I18N.tooltips.textindicator; - var obj = { - name : txt, // the button name (i.e. 'bold') - element : el, // the UI element (DIV) - enabled : true, // is it enabled? - active : false, // is it pressed? - text : false, // enabled in text mode? - cmd : "textindicator", // the command ID - state : setButtonStatus // for changing state - }; - tb_objects[txt] = obj; - break; - default: - btn = editor.config.btnList[txt]; - } - if (!el && btn) { - el = document.createElement("div"); - el.title = btn[0]; - el.className = "button"; - // let's just pretend we have a button object, and - // assign all the needed information to it. - var obj = { - name : txt, // the button name (i.e. 'bold') - element : el, // the UI element (DIV) - enabled : true, // is it enabled? - active : false, // is it pressed? - text : btn[2], // enabled in text mode? - cmd : btn[3], // the command ID - state : setButtonStatus, // for changing state - context : btn[4] || null // enabled in a certain context? - }; - tb_objects[txt] = obj; - // handlers to emulate nice flat toolbar buttons - HTMLArea._addEvent(el, "mouseover", function () { - if (obj.enabled) { - HTMLArea._addClass(el, "buttonHover"); - } - }); - HTMLArea._addEvent(el, "mouseout", function () { - if (obj.enabled) with (HTMLArea) { - _removeClass(el, "buttonHover"); - _removeClass(el, "buttonActive"); - (obj.active) && _addClass(el, "buttonPressed"); - } - }); - HTMLArea._addEvent(el, "mousedown", function (ev) { - if (obj.enabled) with (HTMLArea) { - _addClass(el, "buttonActive"); - _removeClass(el, "buttonPressed"); - _stopEvent(is_ie ? window.event : ev); - } - }); - // when clicked, do the following: - HTMLArea._addEvent(el, "click", function (ev) { - if (obj.enabled) with (HTMLArea) { - _removeClass(el, "buttonActive"); - _removeClass(el, "buttonHover"); - obj.cmd(editor, obj.name, obj); - _stopEvent(is_ie ? window.event : ev); - } - }); - var img = document.createElement("img"); - img.src = btn[1]; - img.style.width = "18px"; - img.style.height = "18px"; - el.appendChild(img); - } else if (!el) { - el = createSelect(txt); - } - if (el) { - var tb_cell = document.createElement("td"); - tb_row.appendChild(tb_cell); - tb_cell.appendChild(el); - } else { - alert("FIXME: Unknown toolbar item: " + txt); - } - return el; - }; - - var first = true; - for (var i in this.config.toolbar) { - if (!first) { - createButton("linebreak"); - } else { - first = false; - } - var group = this.config.toolbar[i]; - for (var j in group) { - var code = group[j]; - if (/^([IT])\[(.*?)\]/.test(code)) { - // special case, create text label - var l7ed = RegExp.$1 == "I"; // localized? - var label = RegExp.$2; - if (l7ed) { - label = HTMLArea.I18N.custom[label]; - } - var tb_cell = document.createElement("td"); - tb_row.appendChild(tb_cell); - tb_cell.className = "label"; - tb_cell.innerHTML = label; - } else { - createButton(code); - } - } - } - - this._htmlArea.appendChild(toolbar); -}; - -HTMLArea.prototype._createStatusBar = function() { - var statusbar = document.createElement("div"); - statusbar.className = "statusBar"; - this._htmlArea.appendChild(statusbar); - this._statusBar = statusbar; - statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); - // creates a holder for the path view - div = document.createElement("span"); - div.className = "statusBarTree"; - this._statusBarTree = div; - this._statusBar.appendChild(div); - if (!this.config.statusBar) { - // disable it... - statusbar.style.display = "none"; - } -}; - -// Creates the HTMLArea object and replaces the textarea with it. -HTMLArea.prototype.generate = function () { - var editor = this; // we'll need "this" in some nested functions - // get the textarea - var textarea = this._textArea; - if (typeof textarea == "string") { - // it's not element but ID - this._textArea = textarea = document.getElementById(textarea); - } - this._ta_size = { - w: textarea.offsetWidth, - h: textarea.offsetHeight - }; - textarea.style.display = "none"; - - // create the editor framework - var htmlarea = document.createElement("div"); - htmlarea.className = "htmlarea"; - this._htmlArea = htmlarea; - - // insert the editor before the textarea. - textarea.parentNode.insertBefore(htmlarea, textarea); - - if (textarea.form) { - // we have a form, on submit get the HTMLArea content and - // update original textarea. - var f = textarea.form; - if (typeof f.onsubmit == "function") { - var funcref = f.onsubmit; - if (typeof f.__msh_prevOnSubmit == "undefined") { - f.__msh_prevOnSubmit = []; - } - f.__msh_prevOnSubmit.push(funcref); - } - f.onsubmit = function() { - editor._textArea.value = editor.getHTML(); - var a = this.__msh_prevOnSubmit; - // call previous submit methods if they were there. - if (typeof a != "undefined") { - for (var i in a) { - a[i](); - } - } - }; - } - - // add a handler for the "back/forward" case -- on body.unload we save - // the HTML content into the original textarea. - window.onunload = function() { - editor._textArea.value = editor.getHTML(); - }; - - // creates & appends the toolbar - this._createToolbar(); - - // create the IFRAME - var iframe = document.createElement("iframe"); - htmlarea.appendChild(iframe); - - this._iframe = iframe; - - // creates & appends the status bar, if the case - this._createStatusBar(); - - // remove the default border as it keeps us from computing correctly - // the sizes. (somebody tell me why doesn't this work in IE) - - if (!HTMLArea.is_ie) { - iframe.style.borderWidth = "1px"; - // iframe.frameBorder = "1"; - // iframe.marginHeight = "0"; - // iframe.marginWidth = "0"; - } - - // size the IFRAME according to user's prefs or initial textarea - var height = (this.config.height == "auto" ? (this._ta_size.h + "px") : this.config.height); - height = parseInt(height); - var width = (this.config.width == "auto" ? (this._ta_size.w + 50 + "px") : this.config.width); - width = parseInt(width); - - if (!HTMLArea.is_ie) { - height -= 2; - width -= 2; - } - - iframe.style.width = width + "px"; - if (this.config.sizeIncludesToolbar) { - // substract toolbar height - height -= this._toolbar.offsetHeight; - height -= this._statusBar.offsetHeight; - } - if (height < 0) { - height = 0; - } - iframe.style.height = height + "px"; - - // the editor including the toolbar now have the same size as the - // original textarea.. which means that we need to reduce that a bit. - textarea.style.width = iframe.style.width; - textarea.style.height = iframe.style.height; - - // IMPORTANT: we have to allow Mozilla a short time to recognize the - // new frame. Otherwise we get a stupid exception. - function initIframe() { - var doc = editor._iframe.contentWindow.document; - if (!doc) { - // Try again.. - // FIXME: don't know what else to do here. Normally - // we'll never reach this point. - if (HTMLArea.is_gecko) { - setTimeout(initIframe, 10); - return false; - } else { - alert("ERROR: IFRAME can't be initialized."); - } - } - if (HTMLArea.is_gecko) { - // enable editable mode for Mozilla - doc.designMode = "on"; - } - editor._doc = doc; - if (!editor.config.fullPage) { - doc.open(); - var html = "<html>\n"; - html += "<head>\n"; - html += "<style>" + editor.config.pageStyle + " td { border: 1px dotted gray; }</style>\n"; - html += "</head>\n"; - html += "<body>\n"; - html += editor._textArea.value; - html += "</body>\n"; - html += "</html>"; - doc.write(html); - doc.close(); - } else { - var html = editor._textArea.value; - if (html.match(HTMLArea.RE_doctype)) { - editor.setDoctype(RegExp.$1); - html = html.replace(HTMLArea.RE_doctype, ""); - } - doc.open(); - doc.write(html); - doc.close(); - } - - if (HTMLArea.is_ie) { - // enable editable mode for IE. For some reason this - // doesn't work if done in the same place as for Gecko - // (above). - doc.body.contentEditable = true; - } - - editor.focusEditor(); - // intercept some events; for updating the toolbar & keyboard handlers - HTMLArea._addEvents - (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"], - function (event) { - return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event); - }); - editor.updateToolbar(); - }; - setTimeout(initIframe, HTMLArea.is_gecko ? 10 : 0); -}; - -// Switches editor mode; parameter can be "textmode" or "wysiwyg". If no -// parameter was passed this function toggles between modes. -HTMLArea.prototype.setMode = function(mode) { - if (typeof mode == "undefined") { - mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode"); - } - switch (mode) { - case "textmode": - this._textArea.value = this.getHTML(); - this._iframe.style.display = "none"; - this._textArea.style.display = "block"; - if (this.config.statusBar) { - this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"]; - } - break; - case "wysiwyg": - if (HTMLArea.is_gecko) { - // disable design mode before changing innerHTML - this._doc.designMode = "off"; - } - if (!this.config.fullPage) - this._doc.body.innerHTML = this.getHTML(); - else - this.setFullHTML(this.getHTML()); - this._iframe.style.display = "block"; - this._textArea.style.display = "none"; - if (HTMLArea.is_gecko) { - // we need to refresh that info for Moz-1.3a - this._doc.designMode = "on"; - } - if (this.config.statusBar) { - this._statusBar.innerHTML = ''; - this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); - this._statusBar.appendChild(this._statusBarTree); - } - break; - default: - alert("Mode <" + mode + "> not defined!"); - return false; - } - this._editMode = mode; - this.focusEditor(); -}; - -HTMLArea.prototype.setFullHTML = function(html) { - var save_multiline = RegExp.multiline; - RegExp.multiline = true; - if (html.match(HTMLArea.RE_doctype)) { - this.setDoctype(RegExp.$1); - html = html.replace(HTMLArea.RE_doctype, ""); - } - RegExp.multiline = save_multiline; - if (!HTMLArea.is_ie) { - if (html.match(HTMLArea.RE_head)) - this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1; - if (html.match(HTMLArea.RE_body)) - this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1; - } else { - var html_re = /<html>((.|\n)*?)<\/html>/i; - html = html.replace(html_re, "$1"); - this._doc.open(); - this._doc.write(html); - this._doc.close(); - this._doc.body.contentEditable = true; - return true; - } -}; - -/*************************************************** - * Category: PLUGINS - ***************************************************/ - -// Create the specified plugin and register it with this HTMLArea -HTMLArea.prototype.registerPlugin = function() { - var plugin = arguments[0]; - if (typeof plugin == "string") - plugin = eval(plugin); - var args = []; - for (var i = 1; i < arguments.length; ++i) - args.push(arguments[i]); - var obj = new plugin(this, args); - if (obj) { - var clone = {}; - var info = plugin._pluginInfo; - for (var i in info) - clone[i] = info[i]; - clone.instance = obj; - this.plugins[plugin._pluginInfo.name] = clone; - } else - alert("Can't register plugin " + plugin.toString() + "."); -}; - -// static function that loads the required plugin and lang file, based on the -// language loaded already for HTMLArea. You better make sure that the plugin -// _has_ that language, otherwise shit might happen ;-) -HTMLArea.loadPlugin = function(pluginName) { - var editorurl = ''; - if (typeof _editor_url != "undefined") { - editorurl = _editor_url + "/"; - } - var dir = editorurl + "plugins/" + pluginName; - var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, - function (str, l1, l2, l3) { - return l1 + "-" + l2.toLowerCase() + l3; - }).toLowerCase() + ".js"; - document.write("<script type='text/javascript' src='" + dir + "/" + plugin + "'></script>"); - document.write("<script type='text/javascript' src='" + dir + "/lang/" + HTMLArea.I18N.lang + ".js'></script>"); -}; - -/*************************************************** - * Category: EDITOR UTILITIES - ***************************************************/ - -HTMLArea.prototype.forceRedraw = function() { - this._doc.body.style.visibility = "hidden"; - this._doc.body.style.visibility = "visible"; - // this._doc.body.innerHTML = this.getInnerHTML(); -}; - -// focuses the iframe window. returns a reference to the editor document. -HTMLArea.prototype.focusEditor = function() { - switch (this._editMode) { - case "wysiwyg" : this._iframe.contentWindow.focus(); break; - case "textmode": this._textArea.focus(); break; - default : alert("ERROR: mode " + this._editMode + " is not defined"); - } - return this._doc; -}; - -// takes a snapshot of the current text (for undo) -HTMLArea.prototype._undoTakeSnapshot = function() { - ++this._undoPos; - if (this._undoPos >= this.config.undoSteps) { - // remove the first element - this._undoQueue.shift(); - --this._undoPos; - } - // use the fasted method (getInnerHTML); - var take = true; - var txt = this.getInnerHTML(); - if (this._undoPos > 0) - take = (this._undoQueue[this._undoPos - 1] != txt); - if (take) { - this._undoQueue[this._undoPos] = txt; - } else { - this._undoPos--; - } -}; - -HTMLArea.prototype.undo = function() { - if (this._undoPos > 0) { - var txt = this._undoQueue[--this._undoPos]; - if (txt) this.setHTML(txt); - else ++this._undoPos; - } -}; - -HTMLArea.prototype.redo = function() { - if (this._undoPos < this._undoQueue.length - 1) { - var txt = this._undoQueue[++this._undoPos]; - if (txt) this.setHTML(txt); - else --this._undoPos; - } -}; - -// updates enabled/disable/active state of the toolbar elements -HTMLArea.prototype.updateToolbar = function(noStatus) { - var doc = this._doc; - var text = (this._editMode == "textmode"); - var ancestors = null; - if (!text) { - ancestors = this.getAllAncestors(); - if (this.config.statusBar && !noStatus) { - this._statusBarTree.innerHTML = ''; // clear - for (var i = ancestors.length; --i >= 0;) { - var el = ancestors[i]; - if (!el) { - // hell knows why we get here; this - // could be a classic example of why - // it's good to check for conditions - // that are impossible to happen ;-) - continue; - } - var a = document.createElement("a"); - a.href = "#"; - a.el = el; - a.editor = this; - a.onclick = function() { - this.blur(); - this.editor.selectNodeContents(this.el); - this.editor.updateToolbar(true); - return false; - }; - a.oncontextmenu = function() { - // TODO: add context menu here - this.blur(); - var info = "Inline style:\n\n"; - info += this.el.style.cssText.split(/;\s*/).join(";\n"); - alert(info); - return false; - }; - var txt = el.tagName.toLowerCase(); - a.title = el.style.cssText; - if (el.id) { - txt += "#" + el.id; - } - if (el.className) { - txt += "." + el.className; - } - a.appendChild(document.createTextNode(txt)); - this._statusBarTree.appendChild(a); - if (i != 0) { - this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb))); - } - } - } - } - for (var i in this._toolbarObjects) { - var btn = this._toolbarObjects[i]; - var cmd = i; - var inContext = true; - if (btn.context && !text) { - inContext = false; - var context = btn.context; - var attrs = []; - if (/(.*)\[(.*?)\]/.test(context)) { - context = RegExp.$1; - attrs = RegExp.$2.split(","); - } - context = context.toLowerCase(); - var match = (context == "*"); - for (var k in ancestors) { - if (!ancestors[k]) { - // the impossible really happens. - continue; - } - if (match || (ancestors[k].tagName.toLowerCase() == context)) { - inContext = true; - for (var ka in attrs) { - if (!eval("ancestors[k]." + attrs[ka])) { - inContext = false; - break; - } - } - if (inContext) { - break; - } - } - } - } - btn.state("enabled", (!text || btn.text) && inContext); - if (typeof cmd == "function") { - continue; - } - // look-it-up in the custom dropdown boxes - var dropdown = this.config.customSelects[cmd]; - if ((!text || btn.text) && (typeof dropdown != "undefined")) { - dropdown.refresh(this); - continue; - } - switch (cmd) { - case "fontname": - case "fontsize": - case "formatblock": - if (!text) { - var value = ("" + doc.queryCommandValue(cmd)).toLowerCase(); - if (!value) { - // FIXME: what do we do here? - break; - } - // HACK -- retrieve the config option for this - // combo box. We rely on the fact that the - // variable in config has the same name as - // button name in the toolbar. - var options = this.config[cmd]; - var k = 0; - // btn.element.selectedIndex = 0; - for (var j in options) { - // FIXME: the following line is scary. - if ((j.toLowerCase() == value) || - (options[j].substr(0, value.length).toLowerCase() == value)) { - btn.element.selectedIndex = k; - break; - } - ++k; - } - } - break; - case "textindicator": - if (!text) { - try {with (btn.element.style) { - backgroundColor = HTMLArea._makeColor( - doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor")); - if (/transparent/i.test(backgroundColor)) { - // Mozilla - backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor")); - } - color = HTMLArea._makeColor(doc.queryCommandValue("forecolor")); - fontFamily = doc.queryCommandValue("fontname"); - fontWeight = doc.queryCommandState("bold") ? "bold" : "normal"; - fontStyle = doc.queryCommandState("italic") ? "italic" : "normal"; - }} catch (e) { - // alert(e + "\n\n" + cmd); - } - } - break; - case "htmlmode": btn.state("active", text); break; - default: - try { - btn.state("active", (!text && doc.queryCommandState(cmd))); - } catch (e) {} - } - } - // take undo snapshots - if (!this._timerUndo) { - this._undoTakeSnapshot(); - var editor = this; - this._timerUndo = setTimeout(function() { - editor._timerUndo = null; - }, this.config.undoTimeout); - } - // check if any plugins have registered refresh handlers - for (var i in this.plugins) { - var plugin = this.plugins[i].instance; - if (typeof plugin.onUpdateToolbar == "function") - plugin.onUpdateToolbar(); - } -}; - -/** Returns a node after which we can insert other nodes, in the current - * selection. The selection is removed. It splits a text node, if needed. - */ -HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) { - if (!HTMLArea.is_ie) { - var sel = this._getSelection(); - var range = this._createRange(sel); - // remove the current selection - sel.removeAllRanges(); - range.deleteContents(); - var node = range.startContainer; - var pos = range.startOffset; - switch (node.nodeType) { - case 3: // Node.TEXT_NODE - // we have to split it at the caret position. - if (toBeInserted.nodeType == 3) { - // do optimized insertion - node.insertData(pos, toBeInserted.data); - range = this._createRange(); - range.setEnd(node, pos + toBeInserted.length); - range.setStart(node, pos + toBeInserted.length); - sel.addRange(range); - } else { - node = node.splitText(pos); - var selnode = toBeInserted; - if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { - selnode = selnode.firstChild; - } - node.parentNode.insertBefore(toBeInserted, node); - this.selectNodeContents(selnode); - this.updateToolbar(); - } - break; - case 1: // Node.ELEMENT_NODE - var selnode = toBeInserted; - if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { - selnode = selnode.firstChild; - } - node.insertBefore(toBeInserted, node.childNodes[pos]); - this.selectNodeContents(selnode); - this.updateToolbar(); - break; - } - } else { - return null; // this function not yet used for IE <FIXME> - } -}; - -// Returns the deepest node that contains both endpoints of the selection. -HTMLArea.prototype.getParentElement = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - if (HTMLArea.is_ie) { - return range.parentElement ? range.parentElement() : this._doc.body; - } else { - var p = range.commonAncestorContainer; - while (p.nodeType == 3) { - p = p.parentNode; - } - return p; - } -}; - -// Returns an array with all the ancestor nodes of the selection. -HTMLArea.prototype.getAllAncestors = function() { - var p = this.getParentElement(); - var a = []; - while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) { - a.push(p); - p = p.parentNode; - } - a.push(this._doc.body); - return a; -}; - -// Selects the contents inside the given node -HTMLArea.prototype.selectNodeContents = function(node, pos) { - this.focusEditor(); - this.forceRedraw(); - var range; - var collapsed = (typeof pos != "undefined"); - if (HTMLArea.is_ie) { - range = this._doc.body.createTextRange(); - range.moveToElementText(node); - (collapsed) && range.collapse(pos); - range.select(); - } else { - var sel = this._getSelection(); - range = this._doc.createRange(); - range.selectNodeContents(node); - (collapsed) && range.collapse(pos); - sel.removeAllRanges(); - sel.addRange(range); - } -}; - -/** Call this function to insert HTML code at the current position. It deletes - * the selection, if any. - */ -HTMLArea.prototype.insertHTML = function(html) { - var sel = this._getSelection(); - var range = this._createRange(sel); - if (HTMLArea.is_ie) { - range.pasteHTML(html); - } else { - // construct a new document fragment with the given HTML - var fragment = this._doc.createDocumentFragment(); - var div = this._doc.createElement("div"); - div.innerHTML = html; - while (div.firstChild) { - // the following call also removes the node from div - fragment.appendChild(div.firstChild); - } - // this also removes the selection - var node = this.insertNodeAtSelection(fragment); - } -}; - -/** - * Call this function to surround the existing HTML code in the selection with - * your tags. FIXME: buggy! This function will be deprecated "soon". - */ -HTMLArea.prototype.surroundHTML = function(startTag, endTag) { - var html = this.getSelectedHTML(); - // the following also deletes the selection - this.insertHTML(startTag + html + endTag); -}; - -/// Retrieve the selected block -HTMLArea.prototype.getSelectedHTML = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - var existing = null; - if (HTMLArea.is_ie) { - existing = range.htmlText; - } else { - existing = HTMLArea.getHTML(range.cloneContents(), false); - } - return existing; -}; - -// Called when the user clicks on "InsertImage" button -HTMLArea.prototype._insertImage = function() { - var editor = this; // for nested functions - this._popupDialog("insert_image.php?id=<?php echo $id ?>", function(param) { - if (!param) { // user must have pressed Cancel - return false; - } - var sel = editor._getSelection(); - var range = editor._createRange(sel); - editor._doc.execCommand("insertimage", false, param["f_url"]); - var img = null; - if (HTMLArea.is_ie) { - img = range.parentElement(); - // wonder if this works... - if (img.tagName.toLowerCase() != "img") { - img = img.previousSibling; - } - } else { - img = range.startContainer.previousSibling; - } - for (field in param) { - var value = param[field]; - if (!value) { - continue; - } - switch (field) { - case "f_alt" : img.alt = value; break; - case "f_border" : img.border = parseInt(value); break; - case "f_align" : img.align = value; break; - case "f_vert" : img.vspace = parseInt(value); break; - case "f_horiz" : img.hspace = parseInt(value); break; - } - } - }, null); -}; - -// Called when the user clicks the Insert Table button -HTMLArea.prototype._insertTable = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - var editor = this; // for nested functions - this._popupDialog("insert_table.php", function(param) { - if (!param) { // user must have pressed Cancel - return false; - } - var doc = editor._doc; - // create the table element - var table = doc.createElement("table"); - // assign the given arguments - for (var field in param) { - var value = param[field]; - if (!value) { - continue; - } - switch (field) { - case "f_width" : table.style.width = value + param["f_unit"]; break; - case "f_align" : table.align = value; break; - case "f_border" : table.border = parseInt(value); break; - case "f_spacing" : table.cellspacing = parseInt(value); break; - case "f_padding" : table.cellpadding = parseInt(value); break; - } - } - var tbody = doc.createElement("tbody"); - table.appendChild(tbody); - for (var i = 0; i < param["f_rows"]; ++i) { - var tr = doc.createElement("tr"); - tbody.appendChild(tr); - for (var j = 0; j < param["f_cols"]; ++j) { - var td = doc.createElement("td"); - /// Moodle hack - if(param["f_unit"] == "px") { - tdwidth = Math.round(table.width / param["f_cols"]); - } else { - tdwidth = Math.round(100 / param["f_cols"]); - } - td.setAttribute("width",tdwidth + param["f_unit"]); - td.setAttribute("valign","top"); - /// Moodle hack -ends - tr.appendChild(td); - // Mozilla likes to see something inside the cell. - (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br")); - } - } - if (HTMLArea.is_ie) { - range.pasteHTML(table.outerHTML); - } else { - // insert the table - editor.insertNodeAtSelection(table); - } - return true; - }, null); -}; - -/****************************************************************** -* Moodle hack - insertSmile -******************************************************************/ -/// since method insertimage doesn't work the same way in mozilla -/// as it does in IE, let's go around this for both browsers. -HTMLArea.prototype._insertSmile = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - var editor = this; // for nested functions - this._popupDialog("dlg_ins_smile.php", function(imgString) { - if(!imgString) { - return false; - } - if (HTMLArea.is_ie) { - range.pasteHTML(imgString); - } else { - // insert the table - editor.insertHTML(imgString); - } - return true; - }, null); -}; - -HTMLArea.prototype._insertChar = function() { - var sel = this._getSelection(); - var range = this._createRange(sel); - var editor = this; // for nested functions - this._popupDialog("dlg_ins_char.php", function(sChar) { - if(!sChar) { - return false; - } - if (HTMLArea.is_ie) { - range.pasteHTML(sChar); - } else { - // insert the table - editor.insertHTML(sChar); - } - return true; - }, null); -}; -/************************************************************************ -* Moodle hack's ends -************************************************************************/ -/// Called when the user Pastes from Ctrl-V; -HTMLArea.prototype._pasteSpecial = function() { - var editor = this; // for nested functions - editor.unPasteSpecial = function () {editor._unPasteSpecial()}; - HTMLArea._addEvent(editor._doc, "keyup",editor.unPasteSpecial); -}; - -/// Called on Ctrl-V keyup; - -HTMLArea.prototype._unPasteSpecial = function() { - var editor = this; - HTMLArea._removeEvent(editor._doc, "keyup",editor.unPasteSpecial); - editor._wordClean(); -}; - -// Word Clean Function; - -HTMLArea.prototype._wordClean = function() { - var D = this.getInnerHTML(); - if (D.indexOf('class=Mso') >= 0 || D.indexOf('mso') >= 0) { - - // make one line - D = D.replace(/\r\n/g, ' '). - replace(/\n/g, ' '). - replace(/\r/g, ' '). - replace(/\ \;/g,' '); - - // keep tags, strip attributes - D = D.replace(/ class=[^\s|>]*/gi,''). - //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">'). - replace(/ style=\"[^>]*\"/gi,''). - replace(/ align=[^\s|>]*/gi,''); - - //clean up tags - D = D.replace(/<b [^>]*>/gi,'<b>'). - replace(/<i [^>]*>/gi,'<i>'). - replace(/<li [^>]*>/gi,'<li>'). - replace(/<ul [^>]*>/gi,'<ul>'); - - // replace outdated tags - D = D.replace(/<b>/gi,'<strong>'). - replace(/<\/b>/gi,'</strong>'); - - // mozilla doesn't like <em> tags - D = D.replace(/<em>/gi,'<i>'). - replace(/<\/em>/gi,'</i>'); - - // kill unwanted tags - D = D.replace(/<\?xml:[^>]*>/g, ''). // Word xml - replace(/<\/?st1:[^>]*>/g,''). // Word SmartTags - replace(/<\/?[a-z]\:[^>]*>/g,''). // All other funny Word non-HTML stuff - replace(/<\/?font[^>]*>/gi,''). // Disable if you want to keep font formatting - replace(/<\/?span[^>]*>/gi,' '). - replace(/<\/?div[^>]*>/gi,' '). - replace(/<\/?pre[^>]*>/gi,' '). - //replace(/<\/?h[1-6][^>]*>/gi,' '). - /// Try to remove <!--[endif]--> and stuff - replace(/<!--[^>]*>/gi,''); /// MOODLE HACK - not so sure does this work right? - - //remove empty tags - //D = D.replace(/<strong><\/strong>/gi,''). - //replace(/<i><\/i>/gi,''). - //replace(/<P[^>]*><\/P>/gi,''); - - // nuke double tags - oldlen = D.length + 1; - while(oldlen > D.length) { - oldlen = D.length; - // join us now and free the tags, we'll be free hackers, we'll be free... ;-) - D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' '). - replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>'); - } - D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>'). - replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>'); - - // nuke double spaces - D = D.replace(/ */gi,' '); - - this.setHTML(D); - this.updateToolbar(); - } -}; -/*************************************************** - * Category: EVENT HANDLERS - ***************************************************/ - -// el is reference to the SELECT object -// txt is the name of the select field, as in config.toolbar -HTMLArea.prototype._comboSelected = function(el, txt) { - this.focusEditor(); - var value = el.options[el.selectedIndex].value; - switch (txt) { - case "fontname": - case "fontsize": this.execCommand(txt, false, value); break; - case "formatblock": - (HTMLArea.is_ie) && (value = "<" + value + ">"); - this.execCommand(txt, false, value); - break; - default: - // try to look it up in the registered dropdowns - var dropdown = this.config.customSelects[txt]; - if (typeof dropdown != "undefined") { - dropdown.action(this); - } else { - alert("FIXME: combo box " + txt + " not implemented"); - } - } -}; - -// the execCommand function (intercepts some commands and replaces them with -// our own implementation) -HTMLArea.prototype.execCommand = function(cmdID, UI, param) { - var editor = this; // for nested functions - this.focusEditor(); - switch (cmdID.toLowerCase()) { - case "htmlmode" : this.setMode(); break; - case "hilitecolor": - (HTMLArea.is_ie) && (cmdID = "backcolor"); - case "forecolor": - this._popupDialog("select_color.html", function(color) { - if (color) { // selection not canceled - editor._doc.execCommand(cmdID, false, "#" + color); - } - }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID))); - break; - case "createlink": - if (HTMLArea.is_ie || !UI) { - this._doc.execCommand(cmdID, UI, param); - } else { - // browser is Mozilla & wants UI - var param; - if ((param = prompt("Enter URL"))) { - this._doc.execCommand(cmdID, false, param); - } - } - break; - case "popupeditor": - if (HTMLArea.is_ie) { - window.open(this.popupURL("fullscreen.php?id=<?php echo $id ?>"), "ha_fullscreen", - "toolbar=no,location=no,directories=no,status=no,menubar=no," + - "scrollbars=no,resizable=yes,width=640,height=480"); - } else { - window.open(this.popupURL("fullscreen.php?id=<?php echo $id ?>"), "ha_fullscreen", - "toolbar=no,menubar=no,personalbar=no,width=640,height=480," + - "scrollbars=no,resizable=yes"); - } - // pass this object to the newly opened window - HTMLArea._object = this; - break; - case "undo": this.undo(); break; - case "redo": this.redo(); break; - case "inserttable": this._insertTable(); break; - case "insertimage": this._insertImage(); break; - case "insertsmile": this._insertSmile(); break; - case "insertchar": this._insertChar(); break; - case "about" : this._popupDialog("about.html", null, this); break; - case "showhelp" : window.open(this.config.editorURL + "reference.html", "ha_help"); break; - /// Moodle hack - case "pastespecial" : this._pasteSpecial(); break; - /// Moodle hack - default: this._doc.execCommand(cmdID, UI, param); - } - this.updateToolbar(); - return false; -}; - -/** A generic event handler for things that happen in the IFRAME's document. - * This function also handles key bindings. */ -HTMLArea.prototype._editorEvent = function(ev) { - var editor = this; - var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress"); - if (keyEvent && ev.ctrlKey && ! ev.altKey) { - var sel = null; - var range = null; - var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); - var cmd = null; - var value = null; - switch (key) { - case 'a': - if (!HTMLArea.is_ie) { - // KEY select all - sel = this._getSelection(); - sel.removeAllRanges(); - range = this._createRange(); - range.selectNodeContents(this._doc.body); - sel.addRange(range); - HTMLArea._stopEvent(ev); - } - break; - - // simple key commands follow - - case 'b': cmd = "bold"; break; - case 'i': cmd = "italic"; break; - case 'u': cmd = "underline"; break; - case 's': cmd = "strikethrough"; break; - case 'l': cmd = "justifyleft"; break; - case 'e': cmd = "justifycenter"; break; - case 'r': cmd = "justifyright"; break; - case 'j': cmd = "justifyfull"; break; - case 'v': this.execCommand("pasteSpecial"); break; - case 'z': cmd = "undo"; break; - case 'y': cmd = "redo"; break; - - // headings - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - cmd = "formatblock"; - value = "h" + key; - if (HTMLArea.is_ie) { - value = "<" + value + ">"; - } - break; - } - if (cmd) { - // execute simple command - this.execCommand(cmd, false, value); - HTMLArea._stopEvent(ev); - } - } - /* - else if (keyEvent) { - // other keys here - switch (ev.keyCode) { - case 13: // KEY enter - // if (HTMLArea.is_ie) { - this.insertHTML("<br />"); - HTMLArea._stopEvent(ev); - // } - break; - } - } - */ - // update the toolbar state after some time - if (editor._timerToolbar) { - clearTimeout(editor._timerToolbar); - } - editor._timerToolbar = setTimeout(function() { - editor.updateToolbar(); - editor._timerToolbar = null; - }, 50); -}; - -// retrieve the HTML -HTMLArea.prototype.getHTML = function() { - switch (this._editMode) { - case "wysiwyg" : - if (!this.config.fullPage) - return HTMLArea.getHTML(this._doc.body, false); - else - return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true); - case "textmode" : return this._textArea.value; - default : alert("Mode <" + mode + "> not defined!"); - } - return false; -}; - -// retrieve the HTML (fastest version, but uses innerHTML) -HTMLArea.prototype.getInnerHTML = function() { - switch (this._editMode) { - case "wysiwyg" : - if (!this.config.fullPage) - return this._doc.body.innerHTML; - else - return this.doctype + "\n" + this._doc.documentElement.innerHTML; - case "textmode" : return this._textArea.value; - default : alert("Mode <" + mode + "> not defined!"); - } - return false; -}; - -// completely change the HTML inside -HTMLArea.prototype.setHTML = function(html) { - switch (this._editMode) { - case "wysiwyg" : - if (!this.config.fullPage) - this._doc.body.innerHTML = html; - else - // this._doc.documentElement.innerHTML = html; - this._doc.body.innerHTML = html; - break; - case "textmode" : this._textArea.value = html; break; - default : alert("Mode <" + mode + "> not defined!"); - } - return false; -}; - -// sets the given doctype (useful when config.fullPage is true) -HTMLArea.prototype.setDoctype = function(doctype) { - this.doctype = doctype; -}; - -/*************************************************** - * Category: UTILITY FUNCTIONS - ***************************************************/ - -// browser identification - -HTMLArea.agt = navigator.userAgent.toLowerCase(); -HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1)); -HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1); -HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1); -HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac); -HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac); -HTMLArea.is_gecko = (navigator.product == "Gecko"); - -// variable used to pass the object to the popup editor window. -HTMLArea._object = null; - -// FIXME!!! this should return false for IE < 5.5 -HTMLArea.checkSupportedBrowser = function() { - if (HTMLArea.is_gecko) { - if (navigator.productSub < 20021201) { - alert("You need at least Mozilla-1.3 Alpha.\n" + - "Sorry, your Gecko is not supported."); - return false; - } - if (navigator.productSub < 20030210) { - alert("Mozilla < 1.3 Beta is not supported!\n" + - "I'll try, though, but it might not work."); - } - } - return HTMLArea.is_gecko || HTMLArea.is_ie; -}; - -// selection & ranges - -// returns the current selection object -HTMLArea.prototype._getSelection = function() { - if (HTMLArea.is_ie) { - return this._doc.selection; - } else { - return this._iframe.contentWindow.getSelection(); - } -}; - -// returns a range for the current selection -HTMLArea.prototype._createRange = function(sel) { - if (HTMLArea.is_ie) { - return sel.createRange(); - } else { - this.focusEditor(); - if (typeof sel != "undefined") { - return sel.getRangeAt(0); - } else { - return this._doc.createRange(); - } - } -}; - -// event handling - -HTMLArea._addEvent = function(el, evname, func) { - if (HTMLArea.is_ie) { - el.attachEvent("on" + evname, func); - } else { - el.addEventListener(evname, func, true); - } -}; - -HTMLArea._addEvents = function(el, evs, func) { - for (var i in evs) { - HTMLArea._addEvent(el, evs[i], func); - } -}; - -HTMLArea._removeEvent = function(el, evname, func) { - if (HTMLArea.is_ie) { - el.detachEvent("on" + evname, func); - } else { - el.removeEventListener(evname, func, true); - } -}; - -HTMLArea._removeEvents = function(el, evs, func) { - for (var i in evs) { - HTMLArea._removeEvent(el, evs[i], func); - } -}; - -HTMLArea._stopEvent = function(ev) { - if (HTMLArea.is_ie) { - ev.cancelBubble = true; - ev.returnValue = false; - } else { - ev.preventDefault(); - ev.stopPropagation(); - } -}; - -HTMLArea._removeClass = function(el, className) { - if (!(el && el.className)) { - return; - } - var cls = el.className.split(" "); - var ar = new Array(); - for (var i = cls.length; i > 0;) { - if (cls[--i] != className) { - ar[ar.length] = cls[i]; - } - } - el.className = ar.join(" "); -}; - -HTMLArea._addClass = function(el, className) { - // remove the class first, if already there - HTMLArea._removeClass(el, className); - el.className += " " + className; -}; - -HTMLArea._hasClass = function(el, className) { - if (!(el && el.className)) { - return false; - } - var cls = el.className.split(" "); - for (var i = cls.length; i > 0;) { - if (cls[--i] == className) { - return true; - } - } - return false; -}; - -HTMLArea.isBlockElement = function(el) { - var blockTags = " body form textarea fieldset ul ol dl li div " + - "p h1 h2 h3 h4 h5 h6 quote pre table thead " + - "tbody tfoot tr td iframe address "; - return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); -}; - -HTMLArea.needsClosingTag = function(el) { - var closingTags = " head script style div span tr td tbody table em strong font a title "; - return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); -}; - -// performs HTML encoding of some given string -HTMLArea.htmlEncode = function(str) { - // we don't need regexp for that, but.. so be it for now. - str = str.replace(/&/ig, "&"); - str = str.replace(/</ig, "<"); - str = str.replace(/>/ig, ">"); - str = str.replace(/\x22/ig, """); - // \x22 means '"' -- we use hex reprezentation so that we don't disturb - // JS compressors (well, at least mine fails.. ;) - return str; -}; - -// Retrieves the HTML code from the given node. This is a replacement for -// getting innerHTML, using standard DOM calls. -HTMLArea.getHTML = function(root, outputRoot) { - var html = ""; - switch (root.nodeType) { - case 1: // Node.ELEMENT_NODE - case 11: // Node.DOCUMENT_FRAGMENT_NODE - var closed; - var i; - var root_tag = root.tagName.toLowerCase(); - if (HTMLArea.is_ie && root_tag == "head") { - if (outputRoot) - html += "<head>"; - // lowercasize - var save_multiline = RegExp.multiline; - RegExp.multiline = true; - var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) { - return p1 + p2.toLowerCase(); - }); - RegExp.multiline = save_multiline; - html += txt; - if (outputRoot) - html += "</head>"; - break; - } else if (outputRoot) { - closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root))); - html = "<" + root.tagName.toLowerCase(); - var attrs = root.attributes; - for (i = 0; i < attrs.length; ++i) { - var a = attrs.item(i); - if (!a.specified) { - continue; - } - var name = a.nodeName.toLowerCase(); - if (/_moz|contenteditable/.test(name)) { - // avoid certain attributes - continue; - } - var value; - if (name != "style") { - // IE5.5 reports 25 when cellSpacing is - // 1; other values might be doomed too. - // For this reason we extract the - // values directly from the root node. - // I'm starting to HATE JavaScript - // development. Browser differences - // suck. - if (typeof root[a.nodeName] != "undefined") { - value = root[a.nodeName]; - } else { - value = a.nodeValue; - } - } else { // IE fails to put style in attributes list - // FIXME: cssText reported by IE is UPPERCASE - value = root.style.cssText; - } - if (/_moz/.test(value)) { - // Mozilla reports some special tags - // here; we don't need them. - continue; - } - html += " " + name + '="' + value + '"'; - } - html += closed ? " />" : ">"; - } - for (i = root.firstChild; i; i = i.nextSibling) { - html += HTMLArea.getHTML(i, true); - } - if (outputRoot && !closed) { - html += "</" + root.tagName.toLowerCase() + ">"; - } - break; - case 3: // Node.TEXT_NODE - html = HTMLArea.htmlEncode(root.data); - break; - case 8: // Node.COMMENT_NODE - html = "<!--" + root.data + "-->"; - break; // skip comments, for now. - } - return html; -}; - -// creates a rgb-style color from a number -HTMLArea._makeColor = function(v) { - if (typeof v != "number") { - // already in rgb (hopefully); IE doesn't get here. - return v; - } - // IE sends number; convert to rgb. - var r = v & 0xFF; - var g = (v >> 8) & 0xFF; - var b = (v >> 16) & 0xFF; - return "rgb(" + r + "," + g + "," + b + ")"; -}; - -// returns hexadecimal color representation from a number or a rgb-style color. -HTMLArea._colorToRgb = function(v) { - if (!v) - return ''; - - // returns the hex representation of one byte (2 digits) - function hex(d) { - return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); - }; - - if (typeof v == "number") { - // we're talking to IE here - var r = v & 0xFF; - var g = (v >> 8) & 0xFF; - var b = (v >> 16) & 0xFF; - return "#" + hex(r) + hex(g) + hex(b); - } - - if (v.substr(0, 3) == "rgb") { - // in rgb(...) form -- Mozilla - var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/; - if (v.match(re)) { - var r = parseInt(RegExp.$1); - var g = parseInt(RegExp.$2); - var b = parseInt(RegExp.$3); - return "#" + hex(r) + hex(g) + hex(b); - } - // doesn't match RE?! maybe uses percentages or float numbers - // -- FIXME: not yet implemented. - return null; - } - - if (v.substr(0, 1) == "#") { - // already hex rgb (hopefully :D ) - return v; - } - - // if everything else fails ;) - return null; -}; - -// modal dialogs for Mozilla (for IE we're using the showModalDialog() call). - -// receives an URL to the popup dialog and a function that receives one value; -// this function will get called after the dialog is closed, with the return -// value of the dialog. -HTMLArea.prototype._popupDialog = function(url, action, init) { - Dialog(this.popupURL(url), action, init); -}; - -// paths - -HTMLArea.prototype.imgURL = function(file, plugin) { - if (typeof plugin == "undefined") - return this.config.editorURL + file; - else - return this.config.editorURL + "plugins/" + plugin + "/img/" + file; -}; - -HTMLArea.prototype.popupURL = function(file) { - var url = ""; - if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) { - var plugin = RegExp.$1; - var popup = RegExp.$2; - if (!/\.html$/.test(popup)) - popup += ".html"; - url = this.config.editorURL + "plugins/" + plugin + "/popups/" + popup; - } else - url = this.config.editorURL + this.config.popupURL + file; - return url; -}; - -// EOF -// Local variables: // -// c-basic-offset:8 // -// indent-tabs-mode:t // -// End: // +<?php + include("../../config.php"); + + $lastmodified = filemtime("htmlarea.php"); + $lifetime = 1800; + + header("Content-type: application/x-javascript"); // Correct MIME type + header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastmodified) . " GMT"); + header("Expires: " . gmdate("D, d M Y H:i:s", time() + $lifetime) . " GMT"); + header("Cache-control: max_age = $lifetime"); + header("Pragma: "); + + $lang = current_language(); + + if (empty($lang)) { + $lang = "en"; + } + + $strheading = get_string("heading", "editor"); + $strnormal = get_string("normal", "editor"); + $straddress = get_string("address", "editor"); + $strpreformatted = get_string("preformatted", "editor"); +?> + +// htmlArea v3.0 - Copyright (c) 2002, 2003 interactivetools.com, inc. +// This copyright notice MUST stay intact for use (see license.txt). +// +// Portions (c) dynarch.com, 2003-2004 +// +// A free WYSIWYG editor replacement for <textarea> fields. +// For full source code and docs, visit http://www.interactivetools.com/ +// +// Version 3.0 developed by Mihai Bazon. +// http://dynarch.com/mishoo +// +// $Id$ + +if (typeof _editor_url == "string") { + // Leave exactly one backslash at the end of _editor_url + _editor_url = _editor_url.replace(/\x2f*$/, '/'); +} else { + //alert("WARNING: _editor_url is not set! You should set this variable to the editor files path; it should preferably be an absolute path, like in '/htmlarea', but it can be relative if you prefer. Further we will try to load the editor files correctly but we'll probably fail."); + _editor_url = '<?php print ($CFG->wwwroot); ?>/lib/editor/'; +} + +// make sure we have a language +if (typeof _editor_lang == "string") { + _editor_lang = "en"; // should always be english in moodle. +} else { + _editor_lang = "en"; +} + +// Creates a new HTMLArea object. Tries to replace the textarea with the given +// ID with it. +function HTMLArea(textarea, config) { + if (HTMLArea.checkSupportedBrowser()) { + if (typeof config == "undefined") { + this.config = new HTMLArea.Config(); + } else { + this.config = config; + } + this._htmlArea = null; + this._textArea = textarea; + this._editMode = "wysiwyg"; + this.plugins = {}; + this._timerToolbar = null; + this._timerUndo = null; + this._undoQueue = new Array(this.config.undoSteps); + this._undoPos = -1; + this._customUndo = true; + this._mdoc = document; // cache the document, we need it in plugins + this.doctype = ''; + } +}; + +// load some scripts +(function() { + var scripts = HTMLArea._scripts = [ _editor_url + "htmlarea.js", + _editor_url + "dialog.js", + _editor_url + "popupwin.js", + _editor_url + "lang/" + _editor_lang + ".js" ]; + var head = document.getElementsByTagName("head")[0]; + // start from 1, htmlarea.js is already loaded + for (var i = 1; i < scripts.length; ++i) { + var script = document.createElement("script"); + script.src = scripts[i]; + head.appendChild(script); + } +})(); + +// cache some regexps +HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig; +HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i; +HTMLArea.RE_head = /<head>((.|\n)*?)<\/head>/i; +HTMLArea.RE_body = /<body>((.|\n)*?)<\/body>/i; + +HTMLArea.Config = function () { + this.version = "3.0"; + + this.width = "auto"; + this.height = "auto"; + + // enable creation of a status bar? + this.statusBar = true; + + // maximum size of the undo queue + this.undoSteps = 20; + + // the time interval at which undo samples are taken + this.undoTimeout = 500; // 1/2 sec. + + // the next parameter specifies whether the toolbar should be included + // in the size or not. + this.sizeIncludesToolbar = true; + + // if true then HTMLArea will retrieve the full HTML, starting with the + // <HTML> tag. + this.fullPage = false; + + // style included in the iframe document + this.pageStyle = "body { background-color: #fff; font-family: 'Times New Roman', Times; }"; + + // set to true if you want Word code to be cleaned upon Paste + this.killWordOnPaste = true; + + // BaseURL included in the iframe document + this.baseURL = document.baseURI || document.URL; + if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/)) + this.baseURL = RegExp.$1 + "/"; + + // URL-s + this.imgURL = "images/"; + this.popupURL = "popups/"; + + /** CUSTOMIZING THE TOOLBAR + * ------------------------- + * + * It is recommended that you customize the toolbar contents in an + * external file (i.e. the one calling HTMLArea) and leave this one + * unchanged. That's because when we (InteractiveTools.com) release a + * new official version, it's less likely that you will have problems + * upgrading HTMLArea. + */ + this.toolbar = [ + [ "fontname", "space", + "fontsize", "space", + "formatblock", "space", + "bold", "italic", "underline", "strikethrough", "separator", + "subscript", "superscript", "separator", + "copy", "cut", "paste","clean", "separator", "undo", "redo" ], + + [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator", + "lefttoright", "righttoleft", "separator", + "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator", + "forecolor", "hilitecolor", "separator", + "inserthorizontalrule", "createlink", "unlink", "insertimage", "inserttable", + "insertsmile", "insertchar", "separator", "htmlmode", "separator", "popupeditor" ] + ]; + + this.fontname = { + "Arial": 'arial,helvetica,sans-serif', + "Courier New": 'courier new,courier,monospace', + "Georgia": 'georgia,times new roman,times,serif', + "Tahoma": 'tahoma,arial,helvetica,sans-serif', + "Times New Roman": 'times new roman,times,serif', + "Verdana": 'verdana,arial,helvetica,sans-serif', + "impact": 'impact', + "WingDings": 'wingdings' + }; + + this.fontsize = { + "1 (8 pt)": "1", + "2 (10 pt)": "2", + "3 (12 pt)": "3", + "4 (14 pt)": "4", + "5 (18 pt)": "5", + "6 (24 pt)": "6", + "7 (36 pt)": "7" + }; + + this.formatblock = { + "<?php echo $strheading ?> 1": "h1", + "<?php echo $strheading ?> 2": "h2", + "<?php echo $strheading ?> 3": "h3", + "<?php echo $strheading ?> 4": "h4", + "<?php echo $strheading ?> 5": "h5", + "<?php echo $strheading ?> 6": "h6", + "<?php echo $strnormal ?>": "p", + "<?php echo $straddress ?>": "address", + "<?php echo $strpreformatted ?>": "pre" + }; + + this.customSelects = {}; + + function cut_copy_paste(e, cmd, obj) { + e.execCommand(cmd); + }; + + // ADDING CUSTOM BUTTONS: please read below! + // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]" + // - ID: unique ID for the button. If the button calls document.execCommand + // it's wise to give it the same name as the called command. + // - ACTION: function that gets called when the button is clicked. + // it has the following prototype: + // function(editor, buttonName) + // - editor is the HTMLArea object that triggered the call + // - buttonName is the ID of the clicked button + // These 2 parameters makes it possible for you to use the same + // handler for more HTMLArea objects or for more different buttons. + // - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N) + // - Icon: path to an icon image file for the button (TODO: use one image for all buttons!) + // - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time. + this.btnList = { + bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ], + italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ], + underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ], + strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ], + subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ], + superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ], + justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ], + justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ], + justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ], + justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ], + insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ], + insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ], + outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ], + indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ], + forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ], + hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ], + inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ], + createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ], + unlink: [ "Remove Link", "ed_unlink.gif", false, function(e) {e.execCommand("unlink");} ], + insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ], + inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ], + htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ], + popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ], + about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ], + showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ], + undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ], + redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ], + cut: [ "Cut selection", "ed_cut.gif", false, cut_copy_paste ], + copy: [ "Copy selection", "ed_copy.gif", false, cut_copy_paste ], + paste: [ "Paste from clipboard", "ed_paste.gif", false, cut_copy_paste ], + clean: [ "Clean Word HTML", "ed_wordclean.gif", false, function(e) {e.execCommand("killword"); }], + lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ], + righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ], + insertsmile: ["Insert Smiley", "em.icon.smile.gif", false, function(e) {e.execCommand("insertsmile");} ], + insertchar: [ "Insert Char", "icon_ins_char.gif", false, function(e) {e.execCommand("insertchar");} ] + }; + /* ADDING CUSTOM BUTTONS + * --------------------- + * + * It is recommended that you add the custom buttons in an external + * file and leave this one unchanged. That's because when we + * (InteractiveTools.com) release a new official version, it's less + * likely that you will have problems upgrading HTMLArea. + * + * Example on how to add a custom button when you construct the HTMLArea: + * + * var editor = new HTMLArea("your_text_area_id"); + * var cfg = editor.config; // this is the default configuration + * cfg.btnList["my-hilite"] = + * [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action + * "Highlight selection", // tooltip + * "my_hilite.gif", // image + * false // disabled in text mode + * ]; + * cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar + * + * An alternate (also more convenient and recommended) way to + * accomplish this is to use the registerButton function below. + */ + // initialize tooltips from the I18N module and generate correct image path + for (var i in this.btnList) { + var btn = this.btnList[i]; + btn[1] = _editor_url + this.imgURL + btn[1]; + if (typeof HTMLArea.I18N.tooltips[i] != "undefined") { + btn[0] = HTMLArea.I18N.tooltips[i]; + } + } +}; + +/** Helper function: register a new button with the configuration. It can be + * called with all 5 arguments, or with only one (first one). When called with + * only one argument it must be an object with the following properties: id, + * tooltip, image, textMode, action. Examples: + * + * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}); + * 2. config.registerButton({ + * id : "my-hilite", // the ID of your button + * tooltip : "Hilite text", // the tooltip + * image : "my-hilite.gif", // image to be displayed in the toolbar + * textMode : false, // disabled in text mode + * action : function(editor) { // called when the button is clicked + * editor.surroundHTML('<span class="hilite">', '</span>'); + * }, + * context : "p" // will be disabled if outside a <p> element + * }); + */ +HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) { + var the_id; + if (typeof id == "string") { + the_id = id; + } else if (typeof id == "object") { + the_id = id.id; + } else { + alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments"); + return false; + } + // check for existing id + if (typeof this.customSelects[the_id] != "undefined") { + // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); + } + if (typeof this.btnList[the_id] != "undefined") { + // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); + } + switch (typeof id) { + case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break; + case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break; + } +}; + +/** The following helper function registers a dropdown box with the editor + * configuration. You still have to add it to the toolbar, same as with the + * buttons. Call it like this: + * + * FIXME: add example + */ +HTMLArea.Config.prototype.registerDropdown = function(object) { + // check for existing id + if (typeof this.customSelects[object.id] != "undefined") { + // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); + } + if (typeof this.btnList[object.id] != "undefined") { + // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); + } + this.customSelects[object.id] = object; +}; + +/** Call this function to remove some buttons/drop-down boxes from the toolbar. + * Pass as the only parameter a string containing button/drop-down names + * delimited by spaces. Note that the string should also begin with a space + * and end with a space. Example: + * + * config.hideSomeButtons(" fontname fontsize textindicator "); + * + * It's useful because it's easier to remove stuff from the defaul toolbar than + * create a brand new toolbar ;-) + */ +HTMLArea.Config.prototype.hideSomeButtons = function(remove) { + var toolbar = this.toolbar; + for (var i in toolbar) { + var line = toolbar[i]; + for (var j = line.length; --j >= 0; ) { + if (remove.indexOf(" " + line[j] + " ") >= 0) { + var len = 1; + if (/separator|space/.test(line[j + 1])) { + len = 2; + } + line.splice(j, len); + } + } + } +}; + +/** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */ +HTMLArea.replaceAll = function(config) { + var tas = document.getElementsByTagName("textarea"); + for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate()); +}; + +/** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */ +HTMLArea.replace = function(id, config) { + var ta = HTMLArea.getElementById("textarea", id); + return ta ? (new HTMLArea(ta, config)).generate() : null; +}; + +// Creates the toolbar and appends it to the _htmlarea +HTMLArea.prototype._createToolbar = function () { + var editor = this; // to access this in nested functions + + var toolbar = document.createElement("div"); + this._toolbar = toolbar; + toolbar.className = "toolbar"; + toolbar.unselectable = "1"; + var tb_row = null; + var tb_objects = new Object(); + this._toolbarObjects = tb_objects; + + // creates a new line in the toolbar + function newLine() { + var table = document.createElement("table"); + table.border = "0px"; + table.cellSpacing = "0px"; + table.cellPadding = "0px"; + toolbar.appendChild(table); + // TBODY is required for IE, otherwise you don't see anything + // in the TABLE. + var tb_body = document.createElement("tbody"); + table.appendChild(tb_body); + tb_row = document.createElement("tr"); + tb_body.appendChild(tb_row); + }; // END of function: newLine + // init first line + newLine(); + + // updates the state of a toolbar element. This function is member of + // a toolbar element object (unnamed objects created by createButton or + // createSelect functions below). + function setButtonStatus(id, newval) { + var oldval = this[id]; + var el = this.element; + if (oldval != newval) { + switch (id) { + case "enabled": + if (newval) { + HTMLArea._removeClass(el, "buttonDisabled"); + el.disabled = false; + } else { + HTMLArea._addClass(el, "buttonDisabled"); + el.disabled = true; + } + break; + case "active": + if (newval) { + HTMLArea._addClass(el, "buttonPressed"); + } else { + HTMLArea._removeClass(el, "buttonPressed"); + } + break; + } + this[id] = newval; + } + }; // END of function: setButtonStatus + + // this function will handle creation of combo boxes. Receives as + // parameter the name of a button as defined in the toolBar config. + // This function is called from createButton, above, if the given "txt" + // doesn't match a button. + function createSelect(txt) { + var options = null; + var el = null; + var cmd = null; + var customSelects = editor.config.customSelects; + var context = null; + switch (txt) { + case "fontsize": + case "fontname": + case "formatblock": + // the following line retrieves the correct + // configuration option because the variable name + // inside the Config object is named the same as the + // button/select in the toolbar. For instance, if txt + // == "formatblock" we retrieve config.formatblock (or + // a different way to write it in JS is + // config["formatblock"]. + options = editor.config[txt]; + cmd = txt; + break; + default: + // try to fetch it from the list of registered selects + cmd = txt; + var dropdown = customSelects[cmd]; + if (typeof dropdown != "undefined") { + options = dropdown.options; + context = dropdown.context; + } else { + alert("ERROR [createSelect]:\nCan't find the requested dropdown definition"); + } + break; + } + if (options) { + el = document.createElement("select"); + var obj = { + name : txt, // field name + element : el, // the UI element (SELECT) + enabled : true, // is it enabled? + text : false, // enabled in text mode? + cmd : cmd, // command ID + state : setButtonStatus, // for changing state + context : context + }; + tb_objects[txt] = obj; + for (var i in options) { + var op = document.createElement("option"); + op.appendChild(document.createTextNode(i)); + op.value = options[i]; + el.appendChild(op); + } + HTMLArea._addEvent(el, "change", function () { + editor._comboSelected(el, txt); + }); + } + return el; + }; // END of function: createSelect + + // appends a new button to toolbar + function createButton(txt) { + // the element that will be created + var el = null; + var btn = null; + switch (txt) { + case "separator": + el = document.createElement("div"); + el.className = "separator"; + break; + case "space": + el = document.createElement("div"); + el.className = "space"; + break; + case "linebreak": + newLine(); + return false; + case "textindicator": + el = document.createElement("div"); + el.appendChild(document.createTextNode("A")); + el.className = "indicator"; + el.title = HTMLArea.I18N.tooltips.textindicator; + var obj = { + name : txt, // the button name (i.e. 'bold') + element : el, // the UI element (DIV) + enabled : true, // is it enabled? + active : false, // is it pressed? + text : false, // enabled in text mode? + cmd : "textindicator", // the command ID + state : setButtonStatus // for changing state + }; + tb_objects[txt] = obj; + break; + default: + btn = editor.config.btnList[txt]; + } + if (!el && btn) { + el = document.createElement("div"); + el.title = btn[0]; + el.className = "button"; + // let's just pretend we have a button object, and + // assign all the needed information to it. + var obj = { + name : txt, // the button name (i.e. 'bold') + element : el, // the UI element (DIV) + enabled : true, // is it enabled? + active : false, // is it pressed? + text : btn[2], // enabled in text mode? + cmd : btn[3], // the command ID + state : setButtonStatus, // for changing state + context : btn[4] || null // enabled in a certain context? + }; + tb_objects[txt] = obj; + // handlers to emulate nice flat toolbar buttons + HTMLArea._addEvent(el, "mouseover", function () { + if (obj.enabled) { + HTMLArea._addClass(el, "buttonHover"); + } + }); + HTMLArea._addEvent(el, "mouseout", function () { + if (obj.enabled) with (HTMLArea) { + _removeClass(el, "buttonHover"); + _removeClass(el, "buttonActive"); + (obj.active) && _addClass(el, "buttonPressed"); + } + }); + HTMLArea._addEvent(el, "mousedown", function (ev) { + if (obj.enabled) with (HTMLArea) { + _addClass(el, "buttonActive"); + _removeClass(el, "buttonPressed"); + _stopEvent(is_ie ? window.event : ev); + } + }); + // when clicked, do the following: + HTMLArea._addEvent(el, "click", function (ev) { + if (obj.enabled) with (HTMLArea) { + _removeClass(el, "buttonActive"); + _removeClass(el, "buttonHover"); + obj.cmd(editor, obj.name, obj); + _stopEvent(is_ie ? window.event : ev); + } + }); + var img = document.createElement("img"); + img.src = btn[1]; + img.style.width = "18px"; + img.style.height = "18px"; + el.appendChild(img); + } else if (!el) { + el = createSelect(txt); + } + if (el) { + var tb_cell = document.createElement("td"); + tb_row.appendChild(tb_cell); + tb_cell.appendChild(el); + } else { + alert("FIXME: Unknown toolbar item: " + txt); + } + return el; + }; + + var first = true; + for (var i in this.config.toolbar) { + if (!first) { + createButton("linebreak"); + } else { + first = false; + } + var group = this.config.toolbar[i]; + for (var j in group) { + var code = group[j]; + if (/^([IT])\[(.*?)\]/.test(code)) { + // special case, create text label + var l7ed = RegExp.$1 == "I"; // localized? + var label = RegExp.$2; + if (l7ed) { + label = HTMLArea.I18N.custom[label]; + } + var tb_cell = document.createElement("td"); + tb_row.appendChild(tb_cell); + tb_cell.className = "label"; + tb_cell.innerHTML = label; + } else { + createButton(code); + } + } + } + + this._htmlArea.appendChild(toolbar); +}; + +HTMLArea.prototype._createStatusBar = function() { + var statusbar = document.createElement("div"); + statusbar.className = "statusBar"; + this._htmlArea.appendChild(statusbar); + this._statusBar = statusbar; + // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); + // creates a holder for the path view + div = document.createElement("span"); + div.className = "statusBarTree"; + div.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; + this._statusBarTree = div; + this._statusBar.appendChild(div); + if (!this.config.statusBar) { + // disable it... + statusbar.style.display = "none"; + } +}; + +// Creates the HTMLArea object and replaces the textarea with it. +HTMLArea.prototype.generate = function () { + var editor = this; // we'll need "this" in some nested functions + // get the textarea + var textarea = this._textArea; + if (typeof textarea == "string") { + // it's not element but ID + this._textArea = textarea = HTMLArea.getElementById("textarea", textarea); + } + this._ta_size = { + w: textarea.offsetWidth, + h: textarea.offsetHeight + }; + textarea.style.display = "none"; + + // create the editor framework + var htmlarea = document.createElement("div"); + htmlarea.className = "htmlarea"; + this._htmlArea = htmlarea; + + // insert the editor before the textarea. + textarea.parentNode.insertBefore(htmlarea, textarea); + + if (textarea.form) { + // we have a form, on submit get the HTMLArea content and + // update original textarea. + var f = textarea.form; + if (typeof f.onsubmit == "function") { + var funcref = f.onsubmit; + if (typeof f.__msh_prevOnSubmit == "undefined") { + f.__msh_prevOnSubmit = []; + } + f.__msh_prevOnSubmit.push(funcref); + } + f.onsubmit = function() { + editor._textArea.value = editor.getHTML(); + var a = this.__msh_prevOnSubmit; + // call previous submit methods if they were there. + if (typeof a != "undefined") { + for (var i in a) { + a[i](); + } + } + }; + } + + // add a handler for the "back/forward" case -- on body.unload we save + // the HTML content into the original textarea. + window.onunload = function() { + editor._textArea.value = editor.getHTML(); + }; + + // creates & appends the toolbar + this._createToolbar(); + + // create the IFRAME + var iframe = document.createElement("iframe"); + + if (HTMLArea.is_ie) { // http://moodle.org/mod/forum/discuss.php?d=8555 + // tricky! set src to local url to turn off SSL security alert + iframe.src = _editor_url + this.config.popupURL+"blank.html"; + } + + htmlarea.appendChild(iframe); + + this._iframe = iframe; + + // creates & appends the status bar, if the case + this._createStatusBar(); + + // remove the default border as it keeps us from computing correctly + // the sizes. (somebody tell me why doesn't this work in IE) + + if (!HTMLArea.is_ie) { + iframe.style.borderWidth = "1px"; + // iframe.frameBorder = "1"; + // iframe.marginHeight = "0"; + // iframe.marginWidth = "0"; + } + + // size the IFRAME according to user's prefs or initial textarea + var height = (this.config.height == "auto" ? (this._ta_size.h + "px") : this.config.height); + height = parseInt(height); + var width = (this.config.width == "auto" ? (this._ta_size.w + 50 + "px") : this.config.width); + width = parseInt(width); + + if (!HTMLArea.is_ie) { + height -= 2; + width -= 2; + } + + iframe.style.width = width + "px"; + if (this.config.sizeIncludesToolbar) { + // substract toolbar height + height -= this._toolbar.offsetHeight; + height -= this._statusBar.offsetHeight; + } + if (height < 0) { + height = 0; + } + iframe.style.height = height + "px"; + + // the editor including the toolbar now have the same size as the + // original textarea.. which means that we need to reduce that a bit. + textarea.style.width = iframe.style.width; + textarea.style.height = iframe.style.height; + + // IMPORTANT: we have to allow Mozilla a short time to recognize the + // new frame. Otherwise we get a stupid exception. + function initIframe() { + var doc = editor._iframe.contentWindow.document; + if (!doc) { + // Try again.. + // FIXME: don't know what else to do here. Normally + // we'll never reach this point. + if (HTMLArea.is_gecko) { + setTimeout(initIframe, 100); + return false; + } else { + alert("ERROR: IFRAME can't be initialized."); + } + } + if (HTMLArea.is_gecko) { + // enable editable mode for Mozilla + doc.designMode = "on"; + } + editor._doc = doc; + if (!editor.config.fullPage) { + doc.open(); + var html = "<html>\n"; + html += "<head>\n"; + if (editor.config.baseURL) + html += '<base href="' + editor.config.baseURL + '" />'; + html += "<style>" + editor.config.pageStyle + " td { border: 1px dotted gray; }</style>\n"; + html += "</head>\n"; + html += "<body>\n"; + html += editor._textArea.value; + html += "</body>\n"; + html += "</html>"; + doc.write(html); + doc.close(); + } else { + var html = editor._textArea.value; + if (html.match(HTMLArea.RE_doctype)) { + editor.setDoctype(RegExp.$1); + html = html.replace(HTMLArea.RE_doctype, ""); + } + doc.open(); + doc.write(html); + doc.close(); + } + + if (HTMLArea.is_ie) { + // enable editable mode for IE. For some reason this + // doesn't work if done in the same place as for Gecko + // (above). + doc.body.contentEditable = true; + } + + editor.focusEditor(); + // intercept some events; for updating the toolbar & keyboard handlers + HTMLArea._addEvents + (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"], + function (event) { + return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event); + }); + + // check if any plugins have registered refresh handlers + for (var i in editor.plugins) { + var plugin = editor.plugins[i].instance; + if (typeof plugin.onGenerate == "function") + plugin.onGenerate(); + } + + setTimeout(function() { + editor.updateToolbar(); + }, 250); + + if (typeof editor.onGenerate == "function") + editor.onGenerate(); + }; + setTimeout(initIframe, 100); +}; + +// Switches editor mode; parameter can be "textmode" or "wysiwyg". If no +// parameter was passed this function toggles between modes. +HTMLArea.prototype.setMode = function(mode) { + if (typeof mode == "undefined") { + mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode"); + } + switch (mode) { + case "textmode": + this._textArea.value = this.getHTML(); + this._iframe.style.display = "none"; + this._textArea.style.display = "block"; + if (this.config.statusBar) { + this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"]; + } + break; + case "wysiwyg": + if (HTMLArea.is_gecko) { + // disable design mode before changing innerHTML + try { + this._doc.designMode = "off"; + } catch(e) {}; + } + if (!this.config.fullPage) + this._doc.body.innerHTML = this.getHTML(); + else + this.setFullHTML(this.getHTML()); + this._iframe.style.display = "block"; + this._textArea.style.display = "none"; + if (HTMLArea.is_gecko) { + // we need to refresh that info for Moz-1.3a + try { + this._doc.designMode = "on"; + //this._doc.focus(); + } catch(e) {}; + } + if (this.config.statusBar) { + this._statusBar.innerHTML = ''; + this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); + this._statusBar.appendChild(this._statusBarTree); + } + break; + default: + alert("Mode <" + mode + "> not defined!"); + return false; + } + this._editMode = mode; + this.focusEditor(); +}; + +HTMLArea.prototype.setFullHTML = function(html) { + var save_multiline = RegExp.multiline; + RegExp.multiline = true; + if (html.match(HTMLArea.RE_doctype)) { + this.setDoctype(RegExp.$1); + html = html.replace(HTMLArea.RE_doctype, ""); + } + RegExp.multiline = save_multiline; + if (!HTMLArea.is_ie) { + if (html.match(HTMLArea.RE_head)) + this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1; + if (html.match(HTMLArea.RE_body)) + this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1; + } else { + var html_re = /<html>((.|\n)*?)<\/html>/i; + html = html.replace(html_re, "$1"); + this._doc.open(); + this._doc.write(html); + this._doc.close(); + this._doc.body.contentEditable = true; + return true; + } +}; + +/*************************************************** + * Category: PLUGINS + ***************************************************/ + +// this is the variant of the function above where the plugin arguments are +// already packed in an array. Externally, it should be only used in the +// full-screen editor code, in order to initialize plugins with the same +// parameters as in the opener window. +HTMLArea.prototype.registerPlugin2 = function(plugin, args) { + if (typeof plugin == "string") + plugin = eval(plugin); + var obj = new plugin(this, args); + if (obj) { + var clone = {}; + var info = plugin._pluginInfo; + for (var i in info) + clone[i] = info[i]; + clone.instance = obj; + clone.args = args; + this.plugins[plugin._pluginInfo.name] = clone; + } else + alert("Can't register plugin " + plugin.toString() + "."); +}; + +// Create the specified plugin and register it with this HTMLArea +HTMLArea.prototype.registerPlugin = function() { + var plugin = arguments[0]; + var args = []; + for (var i = 1; i < arguments.length; ++i) + args.push(arguments[i]); + this.registerPlugin2(plugin, args); +}; + +// static function that loads the required plugin and lang file, based on the +// language loaded already for HTMLArea. You better make sure that the plugin +// _has_ that language, otherwise shit might happen ;-) +HTMLArea.loadPlugin = function(pluginName) { + var dir = _editor_url + "plugins/" + pluginName; + var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, + function (str, l1, l2, l3) { + return l1 + "-" + l2.toLowerCase() + l3; + }).toLowerCase() + ".js"; + var plugin_file = dir + "/" + plugin; + var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js"; + HTMLArea._scripts.push(plugin_file, plugin_lang); + document.write("<script type='text/javascript' src='" + plugin_file + "'></script>"); + document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>"); +}; + +HTMLArea.loadStyle = function(style, plugin) { + var url = _editor_url || ''; + if (typeof plugin != "undefined") { + url += "plugins/" + plugin + "/"; + } + url += style; + document.write("<style type='text/css'>@import url(" + url + ");</style>"); +}; +HTMLArea.loadStyle("htmlarea.css"); + +/*************************************************** + * Category: EDITOR UTILITIES + ***************************************************/ + +// The following function is a slight variation of the word cleaner code posted +// by Weeezl (user @ InteractiveTools forums). +HTMLArea.prototype._wordClean = function() { + var D = this.getInnerHTML(); + if (D.indexOf("class=Mso") >= 0 || D.indexOf("mso") >= 0 || D.indexOf("Mso") >= 0) { + + // make one line + D = D.replace(/\r\n/g, ' '). + replace(/\n/g, ' '). + replace(/\r/g, ' '). + replace(/\ \;/g,' '); + + // keep tags, strip attributes + D = D.replace(/ class=[^\s|>]*/gi,''). + //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">'). + replace(/ style=\"[^>]*\"/gi,''). + replace(/ align=[^\s|>]*/gi,''); + + //clean up tags + D = D.replace(/<b [^>]*>/gi,'<b>'). + replace(/<i [^>]*>/gi,'<i>'). + replace(/<li [^>]*>/gi,'<li>'). + replace(/<ul [^>]*>/gi,'<ul>'); + + // replace outdated tags + D = D.replace(/<b>/gi,'<strong>'). + replace(/<\/b>/gi,'</strong>'); + + // mozilla doesn't like <em> tags + D = D.replace(/<em>/gi,'<i>'). + replace(/<\/em>/gi,'</i>'); + + // kill unwanted tags + D = D.replace(/<\?xml:[^>]*>/g, ''). // Word xml + replace(/<\/?st1:[^>]*>/g,''). // Word SmartTags + replace(/<\/?[a-z]\:[^>]*>/g,''). // All other funny Word non-HTML stuff + replace(/<\/?font[^>]*>/gi,''). // Disable if you want to keep font formatting + replace(/<\/?span[^>]*>/gi,' '). + replace(/<\/?div[^>]*>/gi,' '). + replace(/<\/?pre[^>]*>/gi,' '). + replace(/<(\/?)(h[1-6]+)[^>]*>/gi,'<$1$2>'); + + //remove empty tags + //D = D.replace(/<strong><\/strong>/gi,''). + //replace(/<i><\/i>/gi,''). + //replace(/<P[^>]*><\/P>/gi,''); + + // nuke double tags + oldlen = D.length + 1; + while(oldlen > D.length) { + oldlen = D.length; + // join us now and free the tags, we'll be free hackers, we'll be free... ;-) + D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' '). + replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>'); + } + D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>'). + replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>'); + + // nuke double spaces + D = D.replace(/ */gi,' '); + + this.setHTML(D); + this.updateToolbar(); + } +}; + +HTMLArea.prototype.forceRedraw = function() { + this._doc.body.style.visibility = "hidden"; + this._doc.body.style.visibility = "visible"; + // this._doc.body.innerHTML = this.getInnerHTML(); +}; + +// focuses the iframe window. returns a reference to the editor document. +HTMLArea.prototype.focusEditor = function() { + switch (this._editMode) { + case "wysiwyg" : this._iframe.contentWindow.focus(); break; + case "textmode": this._textArea.focus(); break; + default : alert("ERROR: mode " + this._editMode + " is not defined"); + } + return this._doc; +}; + +// takes a snapshot of the current text (for undo) +HTMLArea.prototype._undoTakeSnapshot = function() { + ++this._undoPos; + if (this._undoPos >= this.config.undoSteps) { + // remove the first element + this._undoQueue.shift(); + --this._undoPos; + } + // use the fasted method (getInnerHTML); + var take = true; + var txt = this.getInnerHTML(); + if (this._undoPos > 0) + take = (this._undoQueue[this._undoPos - 1] != txt); + if (take) { + this._undoQueue[this._undoPos] = txt; + } else { + this._undoPos--; + } +}; + +HTMLArea.prototype.undo = function() { + if (this._undoPos > 0) { + var txt = this._undoQueue[--this._undoPos]; + if (txt) this.setHTML(txt); + else ++this._undoPos; + } +}; + +HTMLArea.prototype.redo = function() { + if (this._undoPos < this._undoQueue.length - 1) { + var txt = this._undoQueue[++this._undoPos]; + if (txt) this.setHTML(txt); + else --this._undoPos; + } +}; + +// updates enabled/disable/active state of the toolbar elements +HTMLArea.prototype.updateToolbar = function(noStatus) { + var doc = this._doc; + var text = (this._editMode == "textmode"); + var ancestors = null; + if (!text) { + ancestors = this.getAllAncestors(); + if (this.config.statusBar && !noStatus) { + this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear + for (var i = ancestors.length; --i >= 0;) { + var el = ancestors[i]; + if (!el) { + // hell knows why we get here; this + // could be a classic example of why + // it's good to check for conditions + // that are impossible to happen ;-) + continue; + } + var a = document.createElement("a"); + a.href = "#"; + a.el = el; + a.editor = this; + a.onclick = function() { + this.blur(); + this.editor.selectNodeContents(this.el); + this.editor.updateToolbar(true); + return false; + }; + a.oncontextmenu = function() { + // TODO: add context menu here + this.blur(); + var info = "Inline style:\n\n"; + info += this.el.style.cssText.split(/;\s*/).join(";\n"); + alert(info); + return false; + }; + var txt = el.tagName.toLowerCase(); + a.title = el.style.cssText; + if (el.id) { + txt += "#" + el.id; + } + if (el.className) { + txt += "." + el.className; + } + a.appendChild(document.createTextNode(txt)); + this._statusBarTree.appendChild(a); + if (i != 0) { + this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb))); + } + } + } + } + for (var i in this._toolbarObjects) { + var btn = this._toolbarObjects[i]; + var cmd = i; + var inContext = true; + if (btn.context && !text) { + inContext = false; + var context = btn.context; + var attrs = []; + if (/(.*)\[(.*?)\]/.test(context)) { + context = RegExp.$1; + attrs = RegExp.$2.split(","); + } + context = context.toLowerCase(); + var match = (context == "*"); + for (var k in ancestors) { + if (!ancestors[k]) { + // the impossible really happens. + continue; + } + if (match || (ancestors[k].tagName.toLowerCase() == context)) { + inContext = true; + for (var ka in attrs) { + if (!eval("ancestors[k]." + attrs[ka])) { + inContext = false; + break; + } + } + if (inContext) { + break; + } + } + } + } + btn.state("enabled", (!text || btn.text) && inContext); + if (typeof cmd == "function") { + continue; + } + // look-it-up in the custom dropdown boxes + var dropdown = this.config.customSelects[cmd]; + if ((!text || btn.text) && (typeof dropdown != "undefined")) { + dropdown.refresh(this); + continue; + } + switch (cmd) { + case "fontname": + case "fontsize": + case "formatblock": + if (!text) try { + var value = ("" + doc.queryCommandValue(cmd)).toLowerCase(); + if (!value) { + // FIXME: what do we do here? + break; + } + // HACK -- retrieve the config option for this + // combo box. We rely on the fact that the + // variable in config has the same name as + // button name in the toolbar. + var options = this.config[cmd]; + var k = 0; + // btn.element.selectedIndex = 0; + for (var j in options) { + // FIXME: the following line is scary. + if ((j.toLowerCase() == value) || + (options[j].substr(0, value.length).toLowerCase() == value)) { + btn.element.selectedIndex = k; + break; + } + ++k; + } + } catch(e) {}; + break; + case "textindicator": + if (!text) { + try {with (btn.element.style) { + backgroundColor = HTMLArea._makeColor( + doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor")); + if (/transparent/i.test(backgroundColor)) { + // Mozilla + backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor")); + } + color = HTMLArea._makeColor(doc.queryCommandValue("forecolor")); + fontFamily = doc.queryCommandValue("fontname"); + fontWeight = doc.queryCommandState("bold") ? "bold" : "normal"; + fontStyle = doc.queryCommandState("italic") ? "italic" : "normal"; + }} catch (e) { + // alert(e + "\n\n" + cmd); + } + } + break; + case "htmlmode": btn.state("active", text); break; + case "lefttoright": + case "righttoleft": + var el = this.getParentElement(); + while (el && !HTMLArea.isBlockElement(el)) + el = el.parentNode; + if (el) + btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr"))); + break; + default: + try { + btn.state("active", (!text && doc.queryCommandState(cmd))); + } catch (e) {} + } + } + // take undo snapshots + if (this._customUndo && !this._timerUndo) { + this._undoTakeSnapshot(); + var editor = this; + this._timerUndo = setTimeout(function() { + editor._timerUndo = null; + }, this.config.undoTimeout); + } + // check if any plugins have registered refresh handlers + for (var i in this.plugins) { + var plugin = this.plugins[i].instance; + if (typeof plugin.onUpdateToolbar == "function") + plugin.onUpdateToolbar(); + } +}; + +/** Returns a node after which we can insert other nodes, in the current + * selection. The selection is removed. It splits a text node, if needed. + */ +HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) { + if (!HTMLArea.is_ie) { + var sel = this._getSelection(); + var range = this._createRange(sel); + // remove the current selection + sel.removeAllRanges(); + range.deleteContents(); + var node = range.startContainer; + var pos = range.startOffset; + switch (node.nodeType) { + case 3: // Node.TEXT_NODE + // we have to split it at the caret position. + if (toBeInserted.nodeType == 3) { + // do optimized insertion + node.insertData(pos, toBeInserted.data); + range = this._createRange(); + range.setEnd(node, pos + toBeInserted.length); + range.setStart(node, pos + toBeInserted.length); + sel.addRange(range); + } else { + node = node.splitText(pos); + var selnode = toBeInserted; + if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { + selnode = selnode.firstChild; + } + node.parentNode.insertBefore(toBeInserted, node); + this.selectNodeContents(selnode); + this.updateToolbar(); + } + break; + case 1: // Node.ELEMENT_NODE + var selnode = toBeInserted; + if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { + selnode = selnode.firstChild; + } + node.insertBefore(toBeInserted, node.childNodes[pos]); + this.selectNodeContents(selnode); + this.updateToolbar(); + break; + } + } else { + return null; // this function not yet used for IE <FIXME> + } +}; + +// Returns the deepest node that contains both endpoints of the selection. +HTMLArea.prototype.getParentElement = function() { + var sel = this._getSelection(); + var range = this._createRange(sel); + if (HTMLArea.is_ie) { + switch (sel.type) { + case "Text": + case "None": + // It seems that even for selection of type "None", + // there _is_ a parent element and it's value is not + // only correct, but very important to us. MSIE is + // certainly the buggiest browser in the world and I + // wonder, God, how can Earth stand it? + return range.parentElement(); + case "Control": + return range.item(0); + default: + return this._doc.body; + } + } else try { + var p = range.commonAncestorContainer; + if (!range.collapsed && range.startContainer == range.endContainer && + range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes()) + p = range.startContainer.childNodes[range.startOffset]; + /* + alert(range.startContainer + ":" + range.startOffset + "\n" + + range.endContainer + ":" + range.endOffset); + */ + while (p.nodeType == 3) { + p = p.parentNode; + } + return p; + } catch (e) { + return null; + } +}; + +// Returns an array with all the ancestor nodes of the selection. +HTMLArea.prototype.getAllAncestors = function() { + var p = this.getParentElement(); + var a = []; + while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) { + a.push(p); + p = p.parentNode; + } + a.push(this._doc.body); + return a; +}; + +// Selects the contents inside the given node +HTMLArea.prototype.selectNodeContents = function(node, pos) { + this.focusEditor(); + this.forceRedraw(); + var range; + var collapsed = (typeof pos != "undefined"); + if (HTMLArea.is_ie) { + range = this._doc.body.createTextRange(); + range.moveToElementText(node); + (collapsed) && range.collapse(pos); + range.select(); + } else { + var sel = this._getSelection(); + range = this._doc.createRange(); + range.selectNodeContents(node); + (collapsed) && range.collapse(pos); + sel.removeAllRanges(); + sel.addRange(range); + } +}; + +/** Call this function to insert HTML code at the current position. It deletes + * the selection, if any. + */ +HTMLArea.prototype.insertHTML = function(html) { + var sel = this._getSelection(); + var range = this._createRange(sel); + if (HTMLArea.is_ie) { + range.pasteHTML(html); + } else { + // construct a new document fragment with the given HTML + var fragment = this._doc.createDocumentFragment(); + var div = this._doc.createElement("div"); + div.innerHTML = html; + while (div.firstChild) { + // the following call also removes the node from div + fragment.appendChild(div.firstChild); + } + // this also removes the selection + var node = this.insertNodeAtSelection(fragment); + } +}; + +/** + * Call this function to surround the existing HTML code in the selection with + * your tags. FIXME: buggy! This function will be deprecated "soon". + */ +HTMLArea.prototype.surroundHTML = function(startTag, endTag) { + var html = this.getSelectedHTML(); + // the following also deletes the selection + this.insertHTML(startTag + html + endTag); +}; + +/// Retrieve the selected block +HTMLArea.prototype.getSelectedHTML = function() { + var sel = this._getSelection(); + var range = this._createRange(sel); + var existing = null; + if (HTMLArea.is_ie) { + existing = range.htmlText; + } else { + existing = HTMLArea.getHTML(range.cloneContents(), false, this); + } + return existing; +}; + +/// Return true if we have some selection +HTMLArea.prototype.hasSelectedText = function() { + // FIXME: come _on_ mishoo, you can do better than this ;-) + return this.getSelectedHTML() != ''; +}; + +HTMLArea.prototype._createLink = function(link) { + var editor = this; + var outparam = null; + if (typeof link == "undefined") { + link = this.getParentElement(); + if (link && !/^a$/i.test(link.tagName)) + link = null; + } + if (link) outparam = { + f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"), + f_title : link.title, + f_target : link.target + }; + this._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param) { + if (!param) + return false; + var a = link; + if (!a) { + editor._doc.execCommand("createlink", false, param.f_href); + a = editor.getParentElement(); + var sel = editor._getSelection(); + var range = editor._createRange(sel); + // if (!HTMLArea.is_ie) { /// Removed by PJ and Martin, Moodle bug #1455 + // a = range.startContainer; + // if (!/^a$/i.test(a.tagName)) + // a = a.nextSibling; + // } + } else a.href = param.f_href.trim(); + if (!/^a$/i.test(a.tagName)) + return false; + a.target = param.f_target.trim(); + a.title = param.f_title.trim(); + editor.selectNodeContents(a); + editor.updateToolbar(); + }, outparam); +}; + +// Called when the user clicks on "InsertImage" button. If an image is already +// there, it will just modify it's properties. +HTMLArea.prototype._insertImage = function(image) { + var editor = this; // for nested functions + var outparam = null; + if (typeof image == "undefined") { + image = this.getParentElement(); + if (image && !/^img$/i.test(image.tagName)) + image = null; + } + if (image) outparam = { + f_url : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"), + f_alt : image.alt, + f_border : image.border, + f_align : image.align, + f_vert : image.vspace, + f_horiz : image.hspace, + f_width : image.width, + f_height : image.height + }; + this._popupDialog("<?php + if(isteacher($id)) { + echo "insert_image.php?id=$id"; + } else { + echo "insert_image_std.php?id=$id"; + }?>", function(param) { + if (!param) { // user must have pressed Cancel + return false; + } + var img = image; + if (!img) { + var sel = editor._getSelection(); + var range = editor._createRange(sel); + editor._doc.execCommand("insertimage", false, param.f_url); + if (HTMLArea.is_ie) { + img = range.parentElement(); + // wonder if this works... + if (img.tagName.toLowerCase() != "img") { + img = img.previousSibling; + } + } else { + img = range.startContainer.previousSibling; + } + } else { + img.src = param.f_url; + } + for (field in param) { + var value = param[field]; + switch (field) { + case "f_alt" : img.alt = value; break; + case "f_border" : img.border = parseInt(value || "0"); break; + case "f_align" : img.align = value; break; + case "f_vert" : img.vspace = parseInt(value || "0"); break; + case "f_horiz" : img.hspace = parseInt(value || "0"); break; + case "f_width" : + if(value != 0) { + img.width = parseInt(value); + } else { + break; + } + break; + case "f_height" : + if(value != 0) { + img.height = parseInt(value); + } else { + break; + } + break; + } + } + }, outparam); +}; + +// Called when the user clicks the Insert Table button +HTMLArea.prototype._insertTable = function() { + var sel = this._getSelection(); + var range = this._createRange(sel); + var editor = this; // for nested functions + this._popupDialog("insert_table.php", function(param) { + if (!param) { // user must have pressed Cancel + return false; + } + var doc = editor._doc; + // create the table element + var table = doc.createElement("table"); + // assign the given arguments + for (var field in param) { + var value = param[field]; + if (!value) { + continue; + } + switch (field) { + case "f_width" : table.width = value + param["f_unit"]; break; + case "f_align" : table.align = value; break; + case "f_border" : table.border = parseInt(value); break; + case "f_spacing" : table.cellspacing = parseInt(value); break; + case "f_padding" : table.cellpadding = parseInt(value); break; + } + } + var tbody = doc.createElement("tbody"); + table.appendChild(tbody); + for (var i = 0; i < param["f_rows"]; ++i) { + var tr = doc.createElement("tr"); + tbody.appendChild(tr); + for (var j = 0; j < param["f_cols"]; ++j) { + var td = doc.createElement("td"); + /// Moodle hack + if(param["f_unit"] == "px") { + tdwidth = Math.round(table.width / param["f_cols"]); + } else { + tdwidth = Math.round(100 / param["f_cols"]); + } + td.setAttribute("width",tdwidth + param["f_unit"]); + td.setAttribute("valign","top"); + /// Moodle hack -ends + tr.appendChild(td); + // Mozilla likes to see something inside the cell. + (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br")); + } + } + if (HTMLArea.is_ie) { + range.pasteHTML(table.outerHTML); + } else { + // insert the table + editor.insertNodeAtSelection(table); + } + return true; + }, null); +}; +/****************************************************************** +* Moodle hack - insertSmile +******************************************************************/ +/// since method insertimage doesn't work the same way in mozilla +/// as it does in IE, let's go around this for both browsers. +HTMLArea.prototype._insertSmile = function() { + var sel = this._getSelection(); + var range = this._createRange(sel); + var editor = this; // for nested functions + this._popupDialog("dlg_ins_smile.php", function(imgString) { + if(!imgString) { + return false; + } + if (HTMLArea.is_ie) { + range.pasteHTML(imgString); + } else { + // insert the table + editor.insertHTML(imgString); + } + return true; + }, null); +}; + +HTMLArea.prototype._insertChar = function() { + var sel = this._getSelection(); + var range = this._createRange(sel); + var editor = this; // for nested functions + this._popupDialog("dlg_ins_char.php", function(sChar) { + if(!sChar) { + return false; + } + if (HTMLArea.is_ie) { + range.pasteHTML(sChar); + } else { + // insert the table + editor.insertHTML(sChar); + } + return true; + }, null); +}; + +HTMLArea.prototype._removelink = function() { + var editor = this; + link = this.getParentElement(); + editor.selectNodeContents(link); + + this._doc.execCommand("unlink", false, null); + this.focusEditor(); +}; +/************************************************************************ +* Moodle hack's ends +************************************************************************/ +/*************************************************** + * Category: EVENT HANDLERS + ***************************************************/ + +// el is reference to the SELECT object +// txt is the name of the select field, as in config.toolbar +HTMLArea.prototype._comboSelected = function(el, txt) { + this.focusEditor(); + var value = el.options[el.selectedIndex].value; + switch (txt) { + case "fontname": + case "fontsize": this.execCommand(txt, false, value); break; + case "formatblock": + (HTMLArea.is_ie) && (value = "<" + value + ">"); + this.execCommand(txt, false, value); + break; + default: + // try to look it up in the registered dropdowns + var dropdown = this.config.customSelects[txt]; + if (typeof dropdown != "undefined") { + dropdown.action(this); + } else { + alert("FIXME: combo box " + txt + " not implemented"); + } + } +}; + +// the execCommand function (intercepts some commands and replaces them with +// our own implementation) +HTMLArea.prototype.execCommand = function(cmdID, UI, param) { + var editor = this; // for nested functions + this.focusEditor(); + cmdID = cmdID.toLowerCase(); + switch (cmdID) { + case "htmlmode" : this.setMode(); break; + case "hilitecolor": + (HTMLArea.is_ie) && (cmdID = "backcolor"); + case "forecolor": + this._popupDialog("select_color.php", function(color) { + if (color) { // selection not canceled + editor._doc.execCommand(cmdID, false, "#" + color); + } + }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID))); + break; + case "createlink": + this._createLink(); + break; + case "unlink": this._removelink(); break; + case "popupeditor": + // this object will be passed to the newly opened window + HTMLArea._object = this; + if (HTMLArea.is_ie) { + //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"])) + { + window.open(this.popupURL("fullscreen.php?id=<?php print($id);?>"), "ha_fullscreen", + "toolbar=no,location=no,directories=no,status=no,menubar=no," + + "scrollbars=no,resizable=yes,width=800,height=600"); + } + } else { + window.open(this.popupURL("fullscreen.php?id=<?php print($id);?>"), "ha_fullscreen", + "toolbar=no,menubar=no,personalbar=no,width=800,height=600," + + "scrollbars=no,resizable=yes"); + } + break; + case "undo": + case "redo": + if (this._customUndo) + this[cmdID](); + else + this._doc.execCommand(cmdID, UI, param); + break; + case "inserttable": this._insertTable(); break; + case "insertimage": this._insertImage(); break; + case "insertsmile": this._insertSmile(); break; + case "insertchar": this._insertChar(); break; + case "about" : this._popupDialog("about.html", null, this); break; + case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break; + + case "killword": this._wordClean(); break; + + case "cut": + case "copy": + case "paste": + try { + if (this.config.killWordOnPaste) + this._wordClean(); + this._doc.execCommand(cmdID, UI, param); + } catch (e) { + if (HTMLArea.is_gecko) { + if (confirm("Unprivileged scripts cannot access Cut/Copy/Paste programatically " + + "for security reasons. Click OK to see a technical note at mozilla.org " + + "which shows you how to allow a script to access the clipboard." + + "\n\nFor more information HOW TO enable Cut/Copy/Paste see moodle -discussion: " + + "\nhttp://moodle.org/mod/forum/discuss.php?d=5880")) + window.open("http://mozilla.org/editor/midasdemo/securityprefs.html"); + } + } + break; + case "lefttoright": + case "righttoleft": + var dir = (cmdID == "righttoleft") ? "rtl" : "ltr"; + var el = this.getParentElement(); + while (el && !HTMLArea.isBlockElement(el)) + el = el.parentNode; + if (el) { + if (el.style.direction == dir) + el.style.direction = ""; + else + el.style.direction = dir; + } + break; + default: this._doc.execCommand(cmdID, UI, param); + } + this.updateToolbar(); + return false; +}; + +/** A generic event handler for things that happen in the IFRAME's document. + * This function also handles key bindings. */ +HTMLArea.prototype._editorEvent = function(ev) { + var editor = this; + var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress"); + if (keyEvent) { + for (var i in editor.plugins) { + var plugin = editor.plugins[i].instance; + if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev); + } + } + if (keyEvent && ev.ctrlKey && ! ev.altKey) { + var sel = null; + var range = null; + var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); + var cmd = null; + var value = null; + switch (key) { + case 'a': + if (!HTMLArea.is_ie) { + // KEY select all + sel = this._getSelection(); + sel.removeAllRanges(); + range = this._createRange(); + range.selectNodeContents(this._doc.body); + sel.addRange(range); + HTMLArea._stopEvent(ev); + } + break; + + // simple key commands follow + + case 'b': cmd = "bold"; break; + case 'i': cmd = "italic"; break; + case 'u': cmd = "underline"; break; + case 's': cmd = "strikethrough"; break; + case 'l': cmd = "justifyleft"; break; + case 'e': cmd = "justifycenter"; break; + case 'r': cmd = "justifyright"; break; + case 'j': cmd = "justifyfull"; break; + case 'z': cmd = "undo"; break; + case 'y': cmd = "redo"; break; + case 'v': cmd = "paste"; break; + + case '0': cmd = "killword"; break; + + // headings + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + cmd = "formatblock"; + value = "h" + key; + if (HTMLArea.is_ie) { + value = "<" + value + ">"; + } + break; + } + if (cmd) { + // execute simple command + this.execCommand(cmd, false, value); + HTMLArea._stopEvent(ev); + } + } + /* + else if (keyEvent) { + // other keys here + switch (ev.keyCode) { + case 13: // KEY enter + // if (HTMLArea.is_ie) { + this.insertHTML("<br />"); + HTMLArea._stopEvent(ev); + // } + break; + } + } + */ + // update the toolbar state after some time + if (editor._timerToolbar) { + clearTimeout(editor._timerToolbar); + } + editor._timerToolbar = setTimeout(function() { + editor.updateToolbar(); + editor._timerToolbar = null; + }, 50); +}; + +// retrieve the HTML +HTMLArea.prototype.getHTML = function() { + switch (this._editMode) { + case "wysiwyg" : + if (!this.config.fullPage) { + return HTMLArea.getHTML(this._doc.body, false, this); + } else + return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this); + case "textmode" : return this._textArea.value; + default : alert("Mode <" + mode + "> not defined!"); + } + return false; +}; + +// retrieve the HTML (fastest version, but uses innerHTML) +HTMLArea.prototype.getInnerHTML = function() { + switch (this._editMode) { + case "wysiwyg" : + if (!this.config.fullPage) + return this._doc.body.innerHTML; + else + return this.doctype + "\n" + this._doc.documentElement.innerHTML; + case "textmode" : return this._textArea.value; + default : alert("Mode <" + mode + "> not defined!"); + } + return false; +}; + +// completely change the HTML inside +HTMLArea.prototype.setHTML = function(html) { + switch (this._editMode) { + case "wysiwyg" : + if (!this.config.fullPage) + this._doc.body.innerHTML = html; + else + // this._doc.documentElement.innerHTML = html; + this._doc.body.innerHTML = html; + break; + case "textmode" : this._textArea.value = html; break; + default : alert("Mode <" + mode + "> not defined!"); + } + return false; +}; + +// sets the given doctype (useful when config.fullPage is true) +HTMLArea.prototype.setDoctype = function(doctype) { + this.doctype = doctype; +}; + +/*************************************************** + * Category: UTILITY FUNCTIONS + ***************************************************/ + +// browser identification + +HTMLArea.agt = navigator.userAgent.toLowerCase(); +HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1)); +HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1); +HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1); +HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac); +HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac); +HTMLArea.is_gecko = (navigator.product == "Gecko"); + +// variable used to pass the object to the popup editor window. +HTMLArea._object = null; + +// function that returns a clone of the given object +HTMLArea.cloneObject = function(obj) { + var newObj = new Object; + + // check for array objects + if (obj.constructor.toString().indexOf("function Array(") == 1) { + newObj = obj.constructor(); + } + + // check for function objects (as usual, IE is fucked up) + if (obj.constructor.toString().indexOf("function Function(") == 1) { + newObj = obj; // just copy reference to it + } else for (var n in obj) { + var node = obj[n]; + if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); } + else { newObj[n] = node; } + } + + return newObj; +}; + +// FIXME!!! this should return false for IE < 5.5 +HTMLArea.checkSupportedBrowser = function() { + if (HTMLArea.is_gecko) { + if (navigator.productSub < 20021201) { + alert("You need at least Mozilla-1.3 Alpha.\n" + + "Sorry, your Gecko is not supported."); + return false; + } + if (navigator.productSub < 20030210) { + alert("Mozilla < 1.3 Beta is not supported!\n" + + "I'll try, though, but it might not work."); + } + } + return HTMLArea.is_gecko || HTMLArea.is_ie; +}; + +// selection & ranges + +// returns the current selection object +HTMLArea.prototype._getSelection = function() { + if (HTMLArea.is_ie) { + return this._doc.selection; + } else { + return this._iframe.contentWindow.getSelection(); + } +}; + +// returns a range for the current selection +HTMLArea.prototype._createRange = function(sel) { + if (HTMLArea.is_ie) { + return sel.createRange(); + } else { + this.focusEditor(); + if (typeof sel != "undefined") { + try { + return sel.getRangeAt(0); + } catch(e) { + return this._doc.createRange(); + } + } else { + return this._doc.createRange(); + } + } +}; + +// event handling + +HTMLArea._addEvent = function(el, evname, func) { + if (HTMLArea.is_ie) { + el.attachEvent("on" + evname, func); + } else { + el.addEventListener(evname, func, true); + } +}; + +HTMLArea._addEvents = function(el, evs, func) { + for (var i in evs) { + HTMLArea._addEvent(el, evs[i], func); + } +}; + +HTMLArea._removeEvent = function(el, evname, func) { + if (HTMLArea.is_ie) { + el.detachEvent("on" + evname, func); + } else { + el.removeEventListener(evname, func, true); + } +}; + +HTMLArea._removeEvents = function(el, evs, func) { + for (var i in evs) { + HTMLArea._removeEvent(el, evs[i], func); + } +}; + +HTMLArea._stopEvent = function(ev) { + if (HTMLArea.is_ie) { + ev.cancelBubble = true; + ev.returnValue = false; + } else { + ev.preventDefault(); + ev.stopPropagation(); + } +}; + +HTMLArea._removeClass = function(el, className) { + if (!(el && el.className)) { + return; + } + var cls = el.className.split(" "); + var ar = new Array(); + for (var i = cls.length; i > 0;) { + if (cls[--i] != className) { + ar[ar.length] = cls[i]; + } + } + el.className = ar.join(" "); +}; + +HTMLArea._addClass = function(el, className) { + // remove the class first, if already there + HTMLArea._removeClass(el, className); + el.className += " " + className; +}; + +HTMLArea._hasClass = function(el, className) { + if (!(el && el.className)) { + return false; + } + var cls = el.className.split(" "); + for (var i = cls.length; i > 0;) { + if (cls[--i] == className) { + return true; + } + } + return false; +}; + +HTMLArea.isBlockElement = function(el) { + var blockTags = " body form textarea fieldset ul ol dl li div " + + "p h1 h2 h3 h4 h5 h6 quote pre table thead " + + "tbody tfoot tr td iframe address "; + return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); +}; + +HTMLArea.needsClosingTag = function(el) { + var closingTags = " head script style div span tr td tbody table em strong font a title "; + return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); +}; + +// performs HTML encoding of some given string +HTMLArea.htmlEncode = function(str) { + // we don't need regexp for that, but.. so be it for now. + str = str.replace(/&/ig, "&"); + str = str.replace(/</ig, "<"); + str = str.replace(/>/ig, ">"); + str = str.replace(/\x22/ig, """); + // \x22 means '"' -- we use hex reprezentation so that we don't disturb + // JS compressors (well, at least mine fails.. ;) + return str; +}; + +// Retrieves the HTML code from the given node. This is a replacement for +// getting innerHTML, using standard DOM calls. +HTMLArea.getHTML = function(root, outputRoot, editor) { + var html = ""; + switch (root.nodeType) { + case 1: // Node.ELEMENT_NODE + case 11: // Node.DOCUMENT_FRAGMENT_NODE + var closed; + var i; + var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : ''; + if (HTMLArea.is_ie && root_tag == "head") { + if (outputRoot) + html += "<head>"; + // lowercasize + var save_multiline = RegExp.multiline; + RegExp.multiline = true; + var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) { + return p1 + p2.toLowerCase(); + }); + RegExp.multiline = save_multiline; + html += txt; + if (outputRoot) + html += "</head>"; + break; + } else if (outputRoot) { + closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root))); + html = "<" + root.tagName.toLowerCase(); + var attrs = root.attributes; + for (i = 0; i < attrs.length; ++i) { + var a = attrs.item(i); + if (!a.specified) { + continue; + } + var name = a.nodeName.toLowerCase(); + if (/_moz|contenteditable|_msh/.test(name)) { + // avoid certain attributes + continue; + } + var value; + if (name != "style") { + // IE5.5 reports 25 when cellSpacing is + // 1; other values might be doomed too. + // For this reason we extract the + // values directly from the root node. + // I'm starting to HATE JavaScript + // development. Browser differences + // suck. + // + // Using Gecko the values of href and src are converted to absolute links + // unless we get them using nodeValue() + if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") { + value = root[a.nodeName]; + } else { + value = a.nodeValue; + // IE seems not willing to return the original values - it converts to absolute + // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href") + // So we have to strip the baseurl manually -/ + if (HTMLArea.is_ie && (name == "href" || name == "src")) { + value = editor.stripBaseURL(value); + } + } + } else { // IE fails to put style in attributes list + // FIXME: cssText reported by IE is UPPERCASE + value = root.style.cssText; + } + if (/(_moz|^$)/.test(value)) { + // Mozilla reports some special tags + // here; we don't need them. + continue; + } + html += " " + name + '="' + value + '"'; + } + html += closed ? " />" : ">"; + } + for (i = root.firstChild; i; i = i.nextSibling) { + html += HTMLArea.getHTML(i, true, editor); + } + if (outputRoot && !closed) { + html += "</" + root.tagName.toLowerCase() + ">"; + } + break; + case 3: // Node.TEXT_NODE + // If a text node is alone in an element and all spaces, replace it with an non breaking one + // This partially undoes the damage done by moz, which translates ' 's into spaces in the data element + if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = ' '; + else html = HTMLArea.htmlEncode(root.data); + break; + case 8: // Node.COMMENT_NODE + html = "<!--" + root.data + "-->"; + break; // skip comments, for now. + } + return html; +}; + +HTMLArea.prototype.stripBaseURL = function(string) { + var baseurl = this.config.baseURL; + + // IE adds the path to an anchor, converting #anchor + // to path/#anchor which of course needs to be fixed + var index = string.indexOf("/#")+1; + if ((index > 0) && (string.indexOf(baseurl) > -1)) { + return string.substr(index); + } + return string; // Moodle doesn't use the code below because + // Moodle likes to keep absolute links + + // strip to last directory in case baseurl points to a file + baseurl = baseurl.replace(/[^\/]+$/, ''); + var basere = new RegExp(baseurl); + string = string.replace(basere, ""); + + // strip host-part of URL which is added by MSIE to links relative to server root + baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1'); + basere = new RegExp(baseurl); + return string.replace(basere, ""); +}; + +String.prototype.trim = function() { + a = this.replace(/^\s+/, ''); + return a.replace(/\s+$/, ''); +}; + +// creates a rgb-style color from a number +HTMLArea._makeColor = function(v) { + if (typeof v != "number") { + // already in rgb (hopefully); IE doesn't get here. + return v; + } + // IE sends number; convert to rgb. + var r = v & 0xFF; + var g = (v >> 8) & 0xFF; + var b = (v >> 16) & 0xFF; + return "rgb(" + r + "," + g + "," + b + ")"; +}; + +// returns hexadecimal color representation from a number or a rgb-style color. +HTMLArea._colorToRgb = function(v) { + if (!v) + return ''; + + // returns the hex representation of one byte (2 digits) + function hex(d) { + return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); + }; + + if (typeof v == "number") { + // we're talking to IE here + var r = v & 0xFF; + var g = (v >> 8) & 0xFF; + var b = (v >> 16) & 0xFF; + return "#" + hex(r) + hex(g) + hex(b); + } + + if (v.substr(0, 3) == "rgb") { + // in rgb(...) form -- Mozilla + var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/; + if (v.match(re)) { + var r = parseInt(RegExp.$1); + var g = parseInt(RegExp.$2); + var b = parseInt(RegExp.$3); + return "#" + hex(r) + hex(g) + hex(b); + } + // doesn't match RE?! maybe uses percentages or float numbers + // -- FIXME: not yet implemented. + return null; + } + + if (v.substr(0, 1) == "#") { + // already hex rgb (hopefully :D ) + return v; + } + + // if everything else fails ;) + return null; +}; + +// modal dialogs for Mozilla (for IE we're using the showModalDialog() call). + +// receives an URL to the popup dialog and a function that receives one value; +// this function will get called after the dialog is closed, with the return +// value of the dialog. +HTMLArea.prototype._popupDialog = function(url, action, init) { + Dialog(this.popupURL(url), action, init); +}; + +// paths + +HTMLArea.prototype.imgURL = function(file, plugin) { + if (typeof plugin == "undefined") + return _editor_url + file; + else + return _editor_url + "plugins/" + plugin + "/img/" + file; +}; + +HTMLArea.prototype.popupURL = function(file) { + var url = ""; + if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) { + var plugin = RegExp.$1; + var popup = RegExp.$2; + if (!/\.html$/.test(popup)) + popup += ".html"; + url = _editor_url + "plugins/" + plugin + "/popups/" + popup; + } else + url = _editor_url + this.config.popupURL + file; + return url; +}; + +/** + * FIX: Internet Explorer returns an item having the _name_ equal to the given + * id, even if it's not having any id. This way it can return a different form + * field even if it's not a textarea. This workarounds the problem by + * specifically looking to search only elements having a certain tag name. + */ +HTMLArea.getElementById = function(tag, id) { + var el, i, objs = document.getElementsByTagName(tag); + for (i = objs.length; --i >= 0 && (el = objs[i]);) + if (el.id == id) + return el; + return null; +}; + + + +// EOF +// Local variables: // +// c-basic-offset:8 // +// indent-tabs-mode:t // +// End: // diff --git a/lib/editor/popups/createanchor.php b/lib/editor/popups/createanchor.php new file mode 100644 index 0000000000..b60d7424ee --- /dev/null +++ b/lib/editor/popups/createanchor.php @@ -0,0 +1,68 @@ +<?php // $Id$ + include("../../../config.php"); +?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=<?php print_string("thischarset");?>" /> +<title>Create anchor</title> +<script language="javascript" type="text/javascript" src="popup.js"></script> +<script language="javascript" type="text/javascript"> +<!-- +function Init() { + __dlg_init(); +} + +function onOK() { + var required = { + "f_anc": "You must enter the URL where this link points to" + }; + var txt = document.forms[0].anc.value; + if (!txt) { + alert(required[f_anc]); + el.focus(); + return false; + } + // pass data back to the calling window + var param = new Object(); + param.anchor = txt; + __dlg_close(param); + return false; +}; + +function onCancel() { + __dlg_close(null); + return false; +}; +// --> +</script> +<style type="text/css"> +<!-- +body { background: ButtonFace; font-family: Tahoma, sans-serif; } +td, button, input { font-family: Tahoma, verdana, sans-serif; font-size: 8pt; } +button { width: 70px; } +.title { background: #ddf; color: #000; font-weight: bold; font-size: 10pt; padding: 3px 10px; margin-bottom: 10px; +border-bottom: 1px solid black; letter-spacing: 2px; +} +.note { font-size: 8pt; } +// --> +</style> +</head> +<body> +<div class="title"><?php print_string("createanchor","editor");?></div> +<form name="fie"> +<table border="0" cellpadding="2" cellspacing="0"> +<tr> + <td><?php print_string("anchorname","editor");?>: <input id="f_anc" name="anc" type="text" size="30" /></td> +</tr> +<tr> + <td align="right"> + <br /> + <button onclick="return onOK();" type="button"><?php print_string("ok","editor");?></button> <button onclick="return onCancel();" type="button"><?php print_string("cancel","editor");?></button> + <button type="button" onclick="javascript: void(0); alert('This only creates an anchor.\nYou still need to create a link manually.');">Help</button></td> +</tr> +</table> +</form> +</body> +</html> \ No newline at end of file diff --git a/lib/editor/popups/dlg_ins_char.php b/lib/editor/popups/dlg_ins_char.php index 83d207a793..4ae9020205 100644 --- a/lib/editor/popups/dlg_ins_char.php +++ b/lib/editor/popups/dlg_ins_char.php @@ -47,44 +47,28 @@ <?php include("../../../config.php"); ?> -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> -<html style="height: 270px;"> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html> <head> -<meta http-equiv="content-type" content="text/html; charset=<?php print_string("thischarset");?>"> +<meta http-equiv="content-type" content="text/html; charset=<?php print_string("thischarset");?>" /> <style type="text/css"> -body { - background: ButtonFace; - color: ButtonText; - font: 11px Tahoma,Verdana,sans-serif; - margin: 0px; - padding: 0px; -} -body { padding: 5px; } -table { - font: 11px Tahoma,Verdana,sans-serif; -} -form p { - margin-top: 5px; - margin-bottom: 5px; -} - +<!-- +body { background: ButtonFace; color: ButtonText; font: 11px Tahoma,Verdana,sans-serif; +margin: 0px; padding: 0px; } +form p { margin-top: 5px; margin-bottom: 5px; } select, input, button { font: 11px Tahoma,Verdana,sans-serif; } button { width: 70px; } .space { padding: 2px; } - -.title { background: #ddf; color: #000; font-weight: bold; font-size: 120%; padding: 3px 10px; margin-bottom: 10px; -border-bottom: 1px solid black; letter-spacing: 2px; -} +.title { background: #ddf; color: #000; font-weight: bold; font-size: 14px; padding: 3px 10px; margin-bottom: 10px; +border-bottom: 1px solid black; letter-spacing: 2px; } form { padding: 0px; margin: 0px; } -.chr { -background-color: transparent; -border: 1px solid #dcdcdc; -font-family: "Times New Roman", times; -font-size: small; -} +.chr { background-color: transparent; border: 1px solid #dcdcdc; font-family: "Times New Roman", times; +font-size: small; } +// --> </style> -<script type="text/javascript" src="popup.js"></script> -<SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript"> +<script language="javascript" type="text/javascript" src="popup.js"></script> +<script language="javascript" type="text/javascript"> <!-- function Init() { __dlg_init(); @@ -126,7 +110,7 @@ function cancel() { return false; } //--> -</SCRIPT> +</script> <title><?php print_string("choosechar","editor");?></title> </head> <body onload="Init()"> @@ -138,11 +122,11 @@ function cancel() { <tr valign="top"> <td> - <SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript"> + <script language="javascript" type="text/javascript"> <!-- document.write(tab(7,32)) //--> - </SCRIPT> + </script> </td> </tr> diff --git a/lib/editor/popups/dlg_ins_smile.php b/lib/editor/popups/dlg_ins_smile.php index 9117e00c25..6bfa44084b 100644 --- a/lib/editor/popups/dlg_ins_smile.php +++ b/lib/editor/popups/dlg_ins_smile.php @@ -33,16 +33,16 @@ 'evil' => '}-]' ); ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=<?php print_string("thischarset");?>" /> -<meta name=vs_targetSchema content="HTML 4.0"> -<meta name="GENERATOR" content="Microsoft Visual Studio 7.0"> -<LINK rel="stylesheet" type="text/css" href="dialog.css"> -<title><?php print_string('insertsmile', 'editor') ?> </title> -<script type="text/javascript" src="popup.js"></script> - -<script language="JavaScript" type="text/javascript"> +<title><?php print_string('insertsmile', 'editor') ?></title> +<link rel="stylesheet" href="dialog.css" type="text/css" /> +<script language="javascript" type="text/javascript" src="popup.js"></script> +<script language="javascript" type="text/javascript"> +<!-- function Init() { __dlg_init(); } @@ -65,23 +65,18 @@ function cancel() { __dlg_close(null); return false; }; +// --> </script> -<style type="text/css"> -body { - width: 330; - height: 360; -} -</style> </head> <body onload="Init()"> -<table class="dlg" cellpadding="0" cellspacing="2" width="100%" height="100%"> +<table class="dlg" cellpadding="0" cellspacing="2" width="100%"> <tr><td><table width="100%"><tr><td class="title" nowrap><?php print_string("chooseicon","editor") ?></td></tr></table></td></tr> <tr> <td> <table border="0" align="center" cellpadding="5"> <tr valign="top"> <td> - <table border="0" align="center"> + <table border="0"> <?php $list = array('smiley', 'biggrin', 'wink', 'mixed', 'thoughtful', 'tongueout', 'cool', 'approve', 'wideeyes', 'surprise'); @@ -124,7 +119,7 @@ body { </tr> <tr><td><table width="100%"><tr><td valign="middle" width="90%"><hr width="100%"></td></tr></table></td></tr> <tr><td align="right"> - <button type="button" onclick="cancel()"><?php print_string("close","editor") ?></button></td></tr> + <button type="button" onclick="return cancel();"><?php print_string("close","editor") ?></button></td></tr> </table> </body> </html> diff --git a/lib/editor/popups/insert_image.php b/lib/editor/popups/insert_image.php index 7570efc0a1..44ba2e6dcc 100644 --- a/lib/editor/popups/insert_image.php +++ b/lib/editor/popups/insert_image.php @@ -9,24 +9,27 @@ } ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=<?php print_string("thischarset");?>" /> <title><?php print_string("insertimage","editor");?></title> -<script type="text/javascript" src="popup.js"></script> -<script type="text/javascript"> +<script language="javascript" type="text/javascript" src="popup.js"></script> +<script language="javascript" type="text/javascript"> var preview_window = null; function Init() { __dlg_init(); var param = window.dialogArguments; if (param) { + var alt = param["f_url"].substring(param["f_url"].lastIndexOf('/') + 1); document.getElementById("f_url").value = param["f_url"]; - document.getElementById("f_alt").value = param["f_alt"]; - document.getElementById("f_border").value = param["f_border"]; + document.getElementById("f_alt").value = param["f_alt"] ? param["f_alt"] : alt; + document.getElementById("f_border").value = parseInt(param["f_border"] || 0); document.getElementById("f_align").value = param["f_align"]; - document.getElementById("f_vert").value = param["f_vert"]; - document.getElementById("f_horiz").value = param["f_horiz"]; + document.getElementById("f_vert").value = param["f_vert"] != -1 ? param["f_vert"] : 0; + document.getElementById("f_horiz").value = param["f_horiz"] != -1 ? param["f_horiz"] : 0; document.getElementById("f_width").value = param["f_width"]; document.getElementById("f_height").value = param["f_height"]; window.ipreview.location.replace('preview.php?id='+ <?php print($course->id);?> +'&imageurl='+ param.f_url); @@ -144,8 +147,6 @@ function submit_form(dothis) { </script> <style type="text/css"> html, body { -width: 730; -height: 540; margin: 2px; background-color: rgb(212,208,200); font-family: Tahoma, Verdana, sans-serif; @@ -168,9 +169,7 @@ button { width: 70px; } .space { padding: 2px; } form { margin-bottom: 0px; margin-top: 0px; } </style> -<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> </head> - <body onload="Init()"> <div class="title"><?php print_string("insertimage","editor");?></div> <div class="space"></div> @@ -264,12 +263,12 @@ form { margin-bottom: 0px; margin-top: 0px; } print_string("filebrowser","editor"); } else { print ""; - }?><br> + }?><br /> <?php print(isteacher($id))? "<iframe id=\"ibrowser\" name=\"ibrowser\" src=\"".$CFG->wwwroot."/lib/editor/coursefiles.php?usecheckboxes=true&id=".$course->id."\" style=\"width: 100%; height: 200px;\"></iframe>": "";?> </td> - <td width="45%" valign="top"><?php print_string("preview","editor");?>:<br> + <td width="45%" valign="top"><?php print_string("preview","editor");?>:<br /> <iframe id="ipreview" name="ipreview" src="about:blank" style="width: 100%; height: 200px;"></iframe> </td> </tr> @@ -293,7 +292,7 @@ form { margin-bottom: 0px; margin-top: 0px; } <input type="hidden" name="action" value="rename" /> <input name="btnRename" type="submit" id="btnRename" value="<?php print_string("rename","editor");?>" /></form></td> <tr></table> - <br> + <br /> <?php } else { print ""; @@ -336,6 +335,6 @@ form { margin-bottom: 0px; margin-top: 0px; } </td> </tr> </table> - <p> </p> + <p> </p> </body> </html> diff --git a/lib/editor/popups/insert_image_std.php b/lib/editor/popups/insert_image_std.php index 8bf2038ce5..8e3ad1c2cd 100644 --- a/lib/editor/popups/insert_image_std.php +++ b/lib/editor/popups/insert_image_std.php @@ -9,14 +9,16 @@ } ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=<?php print_string("thischarset");?>" /> <title><?php print_string("insertimage","editor");?></title> -<script type="text/javascript" src="popup.js"></script> +<script language="javascript" type="text/javascript" src="popup.js"></script> -<script type="text/javascript"> +<script language="javascript" type="text/javascript"> var preview_window = null; function Init() { @@ -109,8 +111,6 @@ function onPreview() { <style type="text/css"> html, body { - width: 450; - height: 230; background: ButtonFace; color: ButtonText; font: 11px Tahoma,Verdana,sans-serif; @@ -137,9 +137,7 @@ border-bottom: 1px solid black; letter-spacing: 2px; } form { padding: 0px; margin: 0px; } </style> - </head> - <body onload="Init()"> <div class="title"><?php print_string("insertimage","editor");?></div> @@ -161,12 +159,10 @@ form { padding: 0px; margin: 0px; } <td><input type="text" name="alt" id="f_alt" style="width:100%" title="For browsers that don't support images" /></td> </tr> - </tbody> </table> <p /> - <fieldset style="float: left; margin-left: 5px;"> <legend><?php print_string("layout","editor");?></legend> @@ -221,8 +217,6 @@ title="Vertical padding" /> <button type="button" name="ok" onclick="return onOK();"><?php print_string("ok","editor");?></button> <button type="button" name="cancel" onclick="return onCancel();"><?php print_string("cancel","editor");?></button> </div> - </form> - </body> </html> diff --git a/lib/editor/popups/insert_table.php b/lib/editor/popups/insert_table.php index 522c34371b..6718eca14b 100644 --- a/lib/editor/popups/insert_table.php +++ b/lib/editor/popups/insert_table.php @@ -1,12 +1,14 @@ <?php include("../../../config.php"); ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=<?php print_string("thischarset");?>" /> <title><?php print_string("inserttable","editor");?></title> -<script type="text/javascript" src="popup.js"></script> -<script type="text/javascript"> +<script language="javascript" type="text/javascript" src="popup.js"></script> +<script language="javascript" type="text/javascript"> function Init() { __dlg_init(); @@ -47,8 +49,6 @@ function onCancel() { <style type="text/css"> html, body { - width: 410; - height: 240; background: ButtonFace; color: ButtonText; font: 11px Tahoma,Verdana,sans-serif; @@ -75,9 +75,7 @@ border-bottom: 1px solid black; letter-spacing: 2px; } form { padding: 0px; margin: 0px; } </style> - </head> - <body onload="Init()"> <div class="title"><?php print_string("inserttable","editor") ?></div> @@ -99,7 +97,7 @@ form { padding: 0px; margin: 0px; } <td style="width: 4em; text-align: right"><?php print_string("width","editor") ?>:</td> <td><input type="text" name="f_width" id="f_width" size="5" title="Width of the table" value="100" /></td> <td><select size="1" name="f_unit" id="f_unit" title="Width unit"> - <option value="%" selected="1" ><?php print_string("percent","editor") ?></option> + <option value="%" selected="selected" ><?php print_string("percent","editor") ?></option> <option value="px" ><?php print_string("pixels","editor") ?></option> <option value="em" >Em</option> </select></td> @@ -118,7 +116,7 @@ form { padding: 0px; margin: 0px; } <div class="fl"><?php print_string("alignment","editor") ?>:</div> <select size="1" name="f_align" id="f_align" title="Positioning of this image"> - <option value="" selected="1" ><?php print_string("notset","editor") ?></option> + <option value="" selected="selected" ><?php print_string("notset","editor") ?></option> <option value="left" ><?php print_string("left","editor") ?></option> <option value="right" ><?php print_string("right","editor") ?></option> <option value="texttop" ><?php print_string("texttop","editor") ?></option> @@ -169,8 +167,6 @@ title="Space between content and border in cell" /> <button type="button" name="ok" onclick="return onOK();"><?php print_string("ok","editor") ?></button> <button type="button" name="cancel" onclick="return onCancel();"><?php print_string("cancel","editor") ?></button> </div> - </form> - </body> </html> diff --git a/lib/editor/popups/link.php b/lib/editor/popups/link.php index 255f47ca39..f38e1b8057 100644 --- a/lib/editor/popups/link.php +++ b/lib/editor/popups/link.php @@ -9,12 +9,13 @@ $course->fullname = ""; // Just to keep display happy, though browsing may fail } ?> -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title><?php print_string("insertlink","editor");?></title> -<meta http-equiv="Content-Type" content="text/html; <?php print_string("thischarset");?>"> -<script language="JavaScript" type="text/javascript"> +<meta http-equiv="Content-Type" content="text/html; <?php print_string("thischarset");?>" /> +<script language="javascript" type="text/javascript"> function onCancel() { window.close(); @@ -74,7 +75,6 @@ color: black; form { margin-bottom: 1px; margin-top: 1px; } </style> </head> - <body> <div class="title"><?php print_string("insertlink","editor");?></div> <table width="450" border="0" cellspacing="0" cellpadding="2"> @@ -103,17 +103,17 @@ form { margin-bottom: 1px; margin-top: 1px; } <table border="0" cellpadding="2" cellspacing="0"> <tr><td><?php print_string("selection","editor");?>: </td> <td><form name="idelete" id="idelete"> - <input name="btnDelete" type="submit" id="btnDelete" value="<?php print_string("delete","editor");?>" onclick="return submit_form('delete');"></form></td> + <input name="btnDelete" type="submit" id="btnDelete" value="<?php print_string("delete","editor");?>" onclick="return submit_form('delete');" /></form></td> <td><form name="imove" id="imove"> - <input name="btnMove" type="submit" id="btnMove" value="<?php print_string("move","editor");?>" onclick="return submit_form('move');"></form></td> + <input name="btnMove" type="submit" id="btnMove" value="<?php print_string("move","editor");?>" onclick="return submit_form('move');" /></form></td> <td><form name="izip" id="izip"> - <input name="btnZip" type="submit" id="btnZip" value="<?php print_string("zip","editor");?>" onclick="return submit_form('zip');"></form></td> + <input name="btnZip" type="submit" id="btnZip" value="<?php print_string("zip","editor");?>" onclick="return submit_form('zip');" /></form></td> <td><form name="irename" id="irename" method="post" action="../coursefiles.php" target="fbrowser"> - <input type="hidden" name="id" value="<?php print($course->id);?>"> - <input type="hidden" name="wdir" value=""> - <input type="hidden" name="file" value=""> - <input type="hidden" name="action" value="rename"> - <input name="btnRename" type="submit" id="btnRename" value="<?php print_string("rename","editor");?>"></form></td> + <input type="hidden" name="id" value="<?php print($course->id);?>" /> + <input type="hidden" name="wdir" value="" /> + <input type="hidden" name="file" value="" /> + <input type="hidden" name="action" value="rename" /> + <input name="btnRename" type="submit" id="btnRename" value="<?php print_string("rename","editor");?>" /></form></td> </tr> </table> </td> @@ -127,19 +127,19 @@ form { margin-bottom: 1px; margin-top: 1px; } <td height="22"><?php if(isteacher($id)) { ?> <form name="cfolder" id="cfolder" action="../coursefiles.php" method="post" target="fbrowser"> - <input type="hidden" name="id" value="<?php print($course->id);?>"> - <input type="hidden" name="wdir" value=""> - <input type="hidden" name="action" value="mkdir"> - <input name="name" type="text" id="foldername" size="35"> - <input name="btnCfolder" type="submit" id="btnCfolder" value="<?php print_string("createfolder","editor");?>" onclick="return checkvalue('foldername','cfolder');"> + <input type="hidden" name="id" value="<?php print($course->id);?>" /> + <input type="hidden" name="wdir" value="" /> + <input type="hidden" name="action" value="mkdir" /> + <input name="name" type="text" id="foldername" size="35" /> + <input name="btnCfolder" type="submit" id="btnCfolder" value="<?php print_string("createfolder","editor");?>" onclick="return checkvalue('foldername','cfolder');" /> </form> <form action="../coursefiles.php?id=<?php print($course->id);?>" method="post" enctype="multipart/form-data" name="uploader" target="fbrowser" id="uploader"> - <input type="hidden" name="MAX_FILE_SIZE" value="<?php print($upload_max_filesize);?>"> - <input type="hidden" name="id" VALUE="<?php print($course->id);?>"> - <input type="hidden" name="wdir" value=""> - <input type="hidden" name="action" value="upload"> - <input type="file" name="userfile" id="userfile" size="35"> - <input name="save" type="submit" id="save" onclick="return checkvalue('userfile','uploader');" value="<?php print_string("upload","editor");?>"> + <input type="hidden" name="MAX_FILE_SIZE" value="<?php print($upload_max_filesize);?>" /> + <input type="hidden" name="id" VALUE="<?php print($course->id);?>" /> + <input type="hidden" name="wdir" value="" /> + <input type="hidden" name="action" value="upload" /> + <input type="file" name="userfile" id="userfile" size="35" /> + <input name="save" type="submit" id="save" onclick="return checkvalue('userfile','uploader');" value="<?php print_string("upload","editor");?>" /> </form> <?php } else { diff --git a/lib/editor/popups/link_std.php b/lib/editor/popups/link_std.php index e95e07563a..4d5ccae3de 100644 --- a/lib/editor/popups/link_std.php +++ b/lib/editor/popups/link_std.php @@ -9,14 +9,14 @@ $course->fullname = ""; // Just to keep display happy, though browsing may fail } ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> - <head> -<meta http-equiv="Content-Type" content="text/html; <?php print_string("thischarset");?>"> +<meta http-equiv="Content-Type" content="text/html; <?php print_string("thischarset");?>" /> <title><?php print_string("insertlink","editor");?></title> - <script type="text/javascript" src="popup.js"></script> - <script type="text/javascript"> - + <script language="javascript" type="text/javascript" src="popup.js"></script> + <script language="javascript" type="text/javascript"> function onTargetChanged() { var f = document.getElementById("f_other_target"); if (this.value == "_other") { @@ -25,13 +25,23 @@ function onTargetChanged() { f.focus(); } else f.style.visibility = "hidden"; }; - function Init() { //__dlg_translate(I18N); __dlg_init(); + var param = window.dialogArguments; + if(param.f_anchors) { + //anchors = param.f_anchors; + var anchor = document.getElementById('f_anchors'); + for(var a in param.f_anchors) { + var opti = document.createElement('option'); + opti.value = '#' + param.f_anchors[a]; + opti.innerHTML = opti.value; + anchor.appendChild(opti); + } + } var target_select = document.getElementById("f_target"); - if (param) { + if (param.f_href) { document.getElementById("f_href").value = param["f_href"]; document.getElementById("f_title").value = param["f_title"]; //comboSelectValue(target_select, param["f_target"]); @@ -103,12 +113,17 @@ function onBrowse() { var newwin = window.open("link.php?id=<?php echo $id; ?>","",""+ settings +" left="+ lx +", top="+ tx +""); return false; } +function seturl() { + var sel = document.getElementById('f_anchors'); + var txt = sel.options[sel.selectedIndex].text; + if(txt != '----') { + var f_url = document.getElementById('f_href'); + f_url.value = txt; + } +} </script> - <style type="text/css"> html, body { - width: 400px; - height: 180px; background: ButtonFace; color: ButtonText; font: 11px Tahoma,Verdana,sans-serif; @@ -130,13 +145,16 @@ border-bottom: 1px solid black; letter-spacing: 2px; padding: 2px; text-align: right; } </style> - </head> - <body onload="Init()"> <div class="title"><?php print_string("insertlink","editor");?></div> <table border="0" style="width: 100%;"> + <tr> + <td class="label"><?php print_string("anchors","editor");?>:</td> + <td><select id="f_anchors" onchange="seturl()"> + <option value="">----</option></select></td> + </tr> <tr> <td class="label"><?php print_string("linkurl","editor");?>:</td> <td><input type="text" id="f_href" style="width: 100%" /></td> @@ -159,7 +177,7 @@ border-bottom: 1px solid black; letter-spacing: 2px; </table> <div id="buttons"> - <?php print(isteacher($id))? "<button type=\"button\" name=\"browse\" onclick=\"return onBrowse();\">".get_string("browse", "editor")."...</button>" : ""; ?> + <?php print(isteacher($id))? "<button type=\"button\" name=\"browse\" onclick=\"return onBrowse();\">".get_string("browse","editor")."</button>" : ""; ?> <button type="button" name="ok" onclick="return onOK();"><?php print_string("ok","editor");?></button> <button type="button" name="cancel" onclick="return onCancel();"><?php print_string("cancel","editor");?></button> </div> diff --git a/lib/editor/popups/popup.js b/lib/editor/popups/popup.js index 991da23e78..8c3afe45aa 100644 --- a/lib/editor/popups/popup.js +++ b/lib/editor/popups/popup.js @@ -1,45 +1,86 @@ +// htmlArea v3.0 - Copyright (c) 2002, 2003 interactivetools.com, inc. +// This copyright notice MUST stay intact for use (see license.txt). +// +// Portions (c) dynarch.com, 2003 +// +// A free WYSIWYG editor replacement for <textarea> fields. +// For full source code and docs, visit http://www.interactivetools.com/ +// +// Version 3.0 developed by Mihai Bazon. +// http://dynarch.com/mishoo +// +// $Id$ + +function getAbsolutePos(el) { + var r = { x: el.offsetLeft, y: el.offsetTop }; + if (el.offsetParent) { + var tmp = getAbsolutePos(el.offsetParent); + r.x += tmp.x; + r.y += tmp.y; + } + return r; +}; + +function comboSelectValue(c, val) { + var ops = c.getElementsByTagName("option"); + for (var i = ops.length; --i >= 0;) { + var op = ops[i]; + op.selected = (op.value == val); + } + c.value = val; +}; + function __dlg_onclose() { - if (!document.all) { opener.Dialog._return(null); - } }; -function __dlg_init() { - if (!document.all) { - // init dialogArguments, as IE gets it - window.dialogArguments = opener.Dialog._arguments; - window.sizeToContent(); - window.sizeToContent(); // for reasons beyond understanding, - // only if we call it twice we get the - // correct size. - window.addEventListener("unload", __dlg_onclose, true); - // center on parent - var px1 = opener.screenX; - var px2 = opener.screenX + opener.outerWidth; - var py1 = opener.screenY; - var py2 = opener.screenY + opener.outerHeight; - var x = (px2 - px1 - window.outerWidth) / 2; - var y = (py2 - py1 - window.outerHeight) / 2; +function __dlg_init(bottom) { var body = document.body; - window.innerHeight = body.offsetHeight + 10; - window.innerWidth = body.offsetWidth + 10; - //window.moveTo(((screen.width - window.innerWidth) / 2), ((screen.height - window.innerHeight)/2)); - //window.resizeTo(window.innerWidth, window.innerHeight); - window.focus(); + var body_height = 0; + if (typeof bottom == "undefined") { + var div = document.createElement("div"); + body.appendChild(div); + var pos = getAbsolutePos(div); + body_height = pos.y; } else { - var body = document.body; - window.dialogWidth = body.offsetWidth + "px"; - window.dialogHeight = body.offsetHeight + 50 + "px"; - window.resizeTo(window.dialogWidth, window.dialogHeight); + var pos = getAbsolutePos(bottom); + body_height = pos.y + bottom.offsetHeight; } + window.dialogArguments = opener.Dialog._arguments; + + document.body.onkeypress = __dlg_close_on_esc; + window.focus(); +}; + +function __dlg_translate(i18n) { + var types = ["span", "option", "td", "button", "div"]; + for (var type in types) { + var spans = document.getElementsByTagName(types[type]); + for (var i = spans.length; --i >= 0;) { + var span = spans[i]; + if (span.firstChild && span.firstChild.data) { + var txt = i18n[span.firstChild.data]; + if (txt) + span.firstChild.data = txt; + } + } + } + var txt = i18n[document.title]; + if (txt) + document.title = txt; }; // closes the dialog and passes the return info upper. function __dlg_close(val) { - if (document.all) { // IE - window.returnValue = val; - } else { opener.Dialog._return(val); - } window.close(); }; + +function __dlg_close_on_esc(ev) { + ev || (ev = window.event); + if (ev.keyCode == 27) { + window.close(); + return false; + } + return true; +}; diff --git a/lib/editor/popups/preview.php b/lib/editor/popups/preview.php index f2db186ada..e39764929c 100644 --- a/lib/editor/popups/preview.php +++ b/lib/editor/popups/preview.php @@ -34,7 +34,8 @@ $width = round($width / $division); $height = 190; } - + echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"; + echo "\t\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; echo "<html>\n"; echo "<head>\n"; echo "<title>Preview</title>\n"; -- 2.39.5