You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2006/10/08 21:54:16 UTC
svn commit: r454197 [3/29] - in /incubator/roller/trunk/web:
WEB-INF/classes/ roller-ui/authoring/editors/
roller-ui/authoring/editors/xinha/ roller-ui/authoring/editors/xinha/conf/
roller-ui/authoring/editors/xinha/contrib/ roller-ui/authoring/editors...
Added: incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js?view=auto&rev=454197
==============================================================================
--- incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js (added)
+++ incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js Sun Oct 8 12:53:13 2006
@@ -0,0 +1,6792 @@
+
+ /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:--
+ -- Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
+ --
+ -- Use of Xinha is granted by the terms of the htmlArea License (based on
+ -- BSD license) please read license.txt in this package for details.
+ --
+ -- Xinha was originally based on work by Mihai Bazon which is:
+ -- Copyright (c) 2003-2004 dynarch.com.
+ -- Copyright (c) 2002-2003 interactivetools.com, inc.
+ -- This copyright notice MUST stay intact for use.
+ --
+ -- Developers - Coding Style:
+ -- For the sake of not committing needlessly conflicting changes,
+ --
+ -- * New code to be indented with 2 spaces ("soft tab").
+ -- * New code preferably uses BSD-Style Bracing
+ -- if ( foo )
+ -- {
+ -- bar();
+ -- }
+ -- * Don't change brace styles unless you're working on the non BSD-Style
+ -- area (so we don't get spurious changes in line numbering).
+ -- * Don't change indentation unless you're working on the badly indented
+ -- area (so we don't get spurious changes of large blocks of code).
+ -- * Jedit is the recommended editor, a comment of this format should be
+ -- included in the top 10 lines of the file (see the embedded edit mode)
+ --
+ -- $HeadURL: http://svn.xinha.python-hosting.com/trunk/htmlarea.js $
+ -- $LastChangedDate: 2006-10-05 02:14:50 +1300 (Thu, 05 Oct 2006) $
+ -- $LastChangedRevision: 595 $
+ -- $LastChangedBy: ray $
+ --------------------------------------------------------------------------*/
+
+HTMLArea.version =
+{
+ 'Release' : 'Trunk',
+ 'Head' : '$HeadURL: http://svn.xinha.python-hosting.com/trunk/htmlarea.js $'.replace(/^[^:]*: (.*) \$$/, '$1'),
+ 'Date' : '$LastChangedDate: 2006-10-05 02:14:50 +1300 (Thu, 05 Oct 2006) $'.replace(/^[^:]*: ([0-9-]*) ([0-9:]*) ([+0-9]*) \((.*)\) \$/, '$4 $2 $3'),
+ 'Revision' : '$LastChangedRevision: 595 $'.replace(/^[^:]*: (.*) \$$/, '$1'),
+ 'RevisionBy': '$LastChangedBy: ray $'.replace(/^[^:]*: (.*) \$$/, '$1')
+};
+
+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 = '';
+}
+
+// make sure we have a language
+if ( typeof _editor_lang == "string" )
+{
+ _editor_lang = _editor_lang.toLowerCase();
+}
+else
+{
+ _editor_lang = "en";
+}
+
+// skin stylesheet to load
+if ( typeof _editor_skin !== "string" )
+{
+ _editor_skin = "";
+}
+
+var __htmlareas = [];
+
+// 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");
+HTMLArea.isRunLocally = document.URL.toLowerCase().search(/^file:/) != -1;
+if ( HTMLArea.isRunLocally )
+{
+ alert('Xinha *must* be installed on a web server. Locally opened files (those that use the "file://" protocol) cannot properly function. Xinha will try to initialize but may not be correctly loaded.');
+}
+
+// Creates a new HTMLArea object. Tries to replace the textarea with the given
+// ID with it.
+function HTMLArea(textarea, config)
+{
+ if ( !textarea )
+ {
+ throw("Tried to create HTMLArea without textarea specified.");
+ }
+
+ if ( HTMLArea.checkSupportedBrowser() )
+ {
+ if ( typeof config == "undefined" )
+ {
+ this.config = new HTMLArea.Config();
+ }
+ else
+ {
+ this.config = config;
+ }
+ this._htmlArea = null;
+
+ if ( typeof textarea != 'object' )
+ {
+ textarea = HTMLArea.getElementById('textarea', textarea);
+ }
+ this._textArea = textarea;
+
+ // Before we modify anything, get the initial textarea size
+ this._initial_ta_size =
+ {
+ w: textarea.style.width ? textarea.style.width : ( textarea.offsetWidth ? ( textarea.offsetWidth + 'px' ) : ( textarea.cols + 'em') ),
+ h: textarea.style.height ? textarea.style.height : ( textarea.offsetHeight ? ( textarea.offsetHeight + 'px' ) : ( textarea.rows + 'em') )
+ };
+ // Create the loading message elements
+ if ( this.config.showLoading )
+ {
+ // Create and show the main loading message and the sub loading message for details of loading actions
+ // global element
+ var loading_message = document.createElement("div");
+ loading_message.id = "loading_" + textarea.name;
+ loading_message.className = "loading";
+ try
+ {
+ // how can i find the real width in pixels without % or em *and* with no visual errors ?
+ // for instance, a textarea with a style="width:100%" and the body padding > 0 result in a horizontal scrollingbar while loading
+ // A few lines above seems to indicate offsetWidth is not always set
+ loading_message.style.width = textarea.offsetWidth + 'px';
+ }
+ catch (ex)
+ {
+ // offsetWidth seems not set, so let's use this._initial_ta_size.w, but sometimes it may be too huge width
+ loading_message.style.width = this._initial_ta_size.w;
+ }
+ loading_message.style.left = HTMLArea.findPosX(textarea) + 'px';
+ loading_message.style.top = (HTMLArea.findPosY(textarea) + parseInt(this._initial_ta_size.h, 10) / 2) + 'px';
+ // main static message
+ var loading_main = document.createElement("div");
+ loading_main.className = "loading_main";
+ loading_main.id = "loading_main_" + textarea.name;
+ loading_main.appendChild(document.createTextNode(HTMLArea._lc("Loading in progress. Please wait !")));
+ // sub dynamic message
+ var loading_sub = document.createElement("div");
+ loading_sub.className = "loading_sub";
+ loading_sub.id = "loading_sub_" + textarea.name;
+ loading_sub.appendChild(document.createTextNode(HTMLArea._lc("Constructing main object")));
+ loading_message.appendChild(loading_main);
+ loading_message.appendChild(loading_sub);
+ document.body.appendChild(loading_message);
+ this.setLoadingMessage("Constructing object");
+ }
+
+ this._editMode = "wysiwyg";
+ this.plugins = {};
+ this._timerToolbar = null;
+ this._timerUndo = null;
+ this._undoQueue = [this.config.undoSteps];
+ this._undoPos = -1;
+ this._customUndo = true;
+ this._mdoc = document; // cache the document, we need it in plugins
+ this.doctype = '';
+ this.__htmlarea_id_num = __htmlareas.length;
+ __htmlareas[this.__htmlarea_id_num] = this;
+
+ this._notifyListeners = {};
+
+ // Panels
+ var panels =
+ {
+ right:
+ {
+ on: true,
+ container: document.createElement('td'),
+ panels: []
+ },
+ left:
+ {
+ on: true,
+ container: document.createElement('td'),
+ panels: []
+ },
+ top:
+ {
+ on: true,
+ container: document.createElement('td'),
+ panels: []
+ },
+ bottom:
+ {
+ on: true,
+ container: document.createElement('td'),
+ panels: []
+ }
+ };
+
+ for ( var i in panels )
+ {
+ if(!panels[i].container) { continue; } // prevent iterating over wrong type
+ panels[i].div = panels[i].container; // legacy
+ panels[i].container.className = 'panels ' + i;
+ HTMLArea.freeLater(panels[i], 'container');
+ HTMLArea.freeLater(panels[i], 'div');
+ }
+ // finally store the variable
+ this._panels = panels;
+
+ HTMLArea.freeLater(this, '_textArea');
+ }
+}
+
+HTMLArea.onload = function() { };
+HTMLArea.init = function() { HTMLArea.onload(); };
+
+// 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|\r|\t)*?)<\/body>/i;
+HTMLArea.RE_Specials = /([\/\^$*+?.()|{}[\]])/g;
+HTMLArea.RE_email = /[_a-zA-Z\d\-\.]{3,}@[_a-zA-Z\d\-]{2,}(\.[_a-zA-Z\d\-]{2,})+/i;
+HTMLArea.RE_url = /(https?:\/\/)?(([a-z0-9_]+:[a-z0-9_]+@)?[a-z0-9_-]{2,}(\.[a-z0-9_-]{2,}){2,}(:[0-9]+)?(\/\S+)*)/i;
+
+HTMLArea.Config = function()
+{
+ var cfg = this;
+ this.version = HTMLArea.version.Revision;
+
+ // Width and Height
+ // you may set these as follows
+ // width = 'auto' -- the width of the original textarea will be used
+ // width = 'toolbar' -- the width of the toolbar will be used
+ // width = '<css measure>' -- use any css measurement, eg width = '75%'
+ //
+ // height = 'auto' -- the height of the original textarea
+ // height = '<css measure>' -- any css measurement, eg height = '480px'
+ this.width = "auto";
+ this.height = "auto";
+
+ // the next parameter specifies whether the toolbar should be included
+ // in the size above, or are extra to it. If false then it's recommended
+ // to have explicit pixel sizes above (or on your textarea and have auto above)
+ this.sizeIncludesBars = true;
+
+ // the next parameter specifies whether the panels should be included
+ // in the size above, or are extra to it. If false then it's recommended
+ // to have explicit pixel sizes above (or on your textarea and have auto above)
+ this.sizeIncludesPanels = true;
+
+ // each of the panels has a dimension, for the left/right it's the width
+ // for the top/bottom it's the height.
+ //
+ // WARNING: PANEL DIMENSIONS MUST BE SPECIFIED AS PIXEL WIDTHS
+ this.panel_dimensions =
+ {
+ left: '200px', // Width
+ right: '200px',
+ top: '100px', // Height
+ bottom: '100px'
+ };
+
+ // enable creation of a status bar?
+ this.statusBar = true;
+
+ // intercept ^V and use the HTMLArea paste command
+ // If false, then passes ^V through to browser editor widget
+ this.htmlareaPaste = false;
+
+ this.mozParaHandler = 'best'; // set to 'built-in', 'dirty' or 'best'
+ // built-in: will (may) use 'br' instead of 'p' tags
+ // dirty : will use p and work good enough for the majority of cases,
+ // best : works the best, but it's about 12kb worth of javascript
+ // and will probably be slower than 'dirty'. This is the "EnterParagraphs"
+ // plugin from "hipikat", rolled in to be part of the core code
+
+ // maximum size of the undo queue
+ this.undoSteps = 20;
+
+ // the time interval at which undo samples are taken
+ this.undoTimeout = 500; // 1/2 sec.
+
+ // set this to true if you want to explicitly right-justify when
+ // setting the text direction to right-to-left
+ this.changeJustifyWithDirection = false;
+
+ // 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 = "";
+
+ // external stylesheets to load (REFERENCE THESE ABSOLUTELY)
+ this.pageStyleSheets = [];
+
+ // specify a base href for relative links
+ this.baseHref = null;
+
+ // we can strip the base href out of relative links to leave them relative, reason for this
+ // especially if you don't specify a baseHref is that mozilla at least (& IE ?) will prefix
+ // the baseHref to any relative links to make them absolute, which isn't what you want most the time.
+ this.stripBaseHref = true;
+
+ // and we can strip the url of the editor page from named links (eg <a href="#top">...</a>)
+ // reason for this is that mozilla at least (and IE ?) prefixes location.href to any
+ // that don't have a url prefixing them
+ this.stripSelfNamedAnchors = true;
+
+ // sometimes high-ascii in links can cause problems for servers (basically they don't recognise them)
+ // so you can use this flag to ensure that all characters other than the normal ascii set (actually
+ // only ! through ~) are escaped in URLs to % codes
+ this.only7BitPrintablesInURLs = true;
+
+ // if you are putting the HTML written in Xinha into an email you might want it to be 7-bit
+ // characters only. This config option (off by default) will convert all characters consuming
+ // more than 7bits into UNICODE decimal entity references (actually it will convert anything
+ // below <space> (chr 20) except cr, lf and tab and above <tilde> (~, chr 7E))
+ this.sevenBitClean = false;
+
+ // sometimes we want to be able to replace some string in the html comng in and going out
+ // so that in the editor we use the "internal" string, and outside and in the source view
+ // we use the "external" string this is useful for say making special codes for
+ // your absolute links, your external string might be some special code, say "{server_url}"
+ // an you say that the internal represenattion of that should be http://your.server/
+ this.specialReplacements = {}; // { 'external_string' : 'internal_string' }
+
+ // set to true if you want Word code to be cleaned upon Paste
+ this.killWordOnPaste = true;
+
+ // enable the 'Target' field in the Make Link dialog
+ this.makeLinkShowsTarget = true;
+
+ // CharSet of the iframe, default is the charset of the document
+ this.charSet = HTMLArea.is_gecko ? document.characterSet : document.charset;
+
+ // URL-s
+ this.imgURL = "images/";
+ this.popupURL = "popups/";
+
+ // remove tags (these have to be a regexp, or null if this functionality is not desired)
+ this.htmlRemoveTags = null;
+
+ // Turning this on will turn all "linebreak" and "separator" items in your toolbar into soft-breaks,
+ // this means that if the items between that item and the next linebreak/separator can
+ // fit on the same line as that which came before then they will, otherwise they will
+ // float down to the next line.
+
+ // If you put a linebreak and separator next to each other, only the separator will
+ // take effect, this allows you to have one toolbar that works for both flowToolbars = true and false
+ // infact the toolbar below has been designed in this way, if flowToolbars is false then it will
+ // create explictly two lines (plus any others made by plugins) breaking at justifyleft, however if
+ // flowToolbars is false and your window is narrow enough then it will create more than one line
+ // even neater, if you resize the window the toolbars will reflow. Niiiice.
+
+ this.flowToolbars = true;
+
+ // set to true if you want the loading panel to show at startup
+ this.showLoading = false;
+
+ // size of color picker cells
+ this.colorPickerCellSize = '6px';
+ // granularity of color picker cells (number per column/row)
+ this.colorPickerGranularity = 18;
+ // position of color picker from toolbar button
+ this.colorPickerPosition = 'bottom,right';
+
+ /** 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 =
+ [
+ ["popupeditor"],
+ ["separator","formatblock","fontname","fontsize","bold","italic","underline","strikethrough"],
+ ["separator","forecolor","hilitecolor","textindicator"],
+ ["separator","subscript","superscript"],
+ ["linebreak","separator","justifyleft","justifycenter","justifyright","justifyfull"],
+ ["separator","insertorderedlist","insertunorderedlist","outdent","indent"],
+ ["separator","inserthorizontalrule","createlink","insertimage","inserttable"],
+ ["linebreak","separator","undo","redo","selectall","print"], (HTMLArea.is_gecko ? [] : ["cut","copy","paste","overwrite","saveas"]),
+ ["separator","killword","clearfonts","removeformat","toggleborders","splitblock","lefttoright", "righttoleft"],
+ ["separator","htmlmode","showhelp","about"]
+ ];
+
+
+ this.fontname =
+ {
+ "— font —": '',
+ "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 =
+ {
+ "— size —": "",
+ "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 =
+ {
+ "— format —": "",
+ "Heading 1": "h1",
+ "Heading 2": "h2",
+ "Heading 3": "h3",
+ "Heading 4": "h4",
+ "Heading 5": "h5",
+ "Heading 6": "h6",
+ "Normal" : "p",
+ "Address" : "address",
+ "Formatted": "pre"
+ };
+
+ this.customSelects = {};
+
+ function cut_copy_paste(e, cmd, obj) { e.execCommand(cmd); }
+
+ this.debug = true;
+
+ this.URIs =
+ {
+ "blank": "popups/blank.html",
+ "link": "link.html",
+ "insert_image": "insert_image.html",
+ "insert_table": "insert_table.html",
+ "select_color": "select_color.html",
+ "about": "about.html",
+ "help": "editor_help.html"
+ };
+
+
+ // 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: tooltip, will be translated below
+ // - Icon: path to an icon image file for the button
+ // OR; you can use an 18x18 block of a larger image by supllying an array
+ // that has three elemtents, the first is the larger image, the second is the column
+ // the third is the row. The ros and columns numbering starts at 0 but there is
+ // a header row and header column which have numbering to make life easier.
+ // See images/buttons_main.gif to see how it's done.
+ // - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.
+ this.btnList =
+ {
+ bold: [ "Bold", HTMLArea._lc({key: 'button_bold', string: ["ed_buttons_main.gif",3,2]}, 'HTMLArea'), false, function(e) { e.execCommand("bold"); } ],
+ italic: [ "Italic", HTMLArea._lc({key: 'button_italic', string: ["ed_buttons_main.gif",2,2]}, 'HTMLArea'), false, function(e) { e.execCommand("italic"); } ],
+ underline: [ "Underline", HTMLArea._lc({key: 'button_underline', string: ["ed_buttons_main.gif",2,0]}, 'HTMLArea'), false, function(e) { e.execCommand("underline"); } ],
+ strikethrough: [ "Strikethrough", HTMLArea._lc({key: 'button_strikethrough', string: ["ed_buttons_main.gif",3,0]}, 'HTMLArea'), false, function(e) { e.execCommand("strikethrough"); } ],
+ subscript: [ "Subscript", HTMLArea._lc({key: 'button_subscript', string: ["ed_buttons_main.gif",3,1]}, 'HTMLArea'), false, function(e) { e.execCommand("subscript"); } ],
+ superscript: [ "Superscript", HTMLArea._lc({key: 'button_superscript', string: ["ed_buttons_main.gif",2,1]}, 'HTMLArea'), false, function(e) { e.execCommand("superscript"); } ],
+
+ justifyleft: [ "Justify Left", ["ed_buttons_main.gif",0,0], false, function(e) { e.execCommand("justifyleft"); } ],
+ justifycenter: [ "Justify Center", ["ed_buttons_main.gif",1,1], false, function(e){ e.execCommand("justifycenter"); } ],
+ justifyright: [ "Justify Right", ["ed_buttons_main.gif",1,0], false, function(e) { e.execCommand("justifyright"); } ],
+ justifyfull: [ "Justify Full", ["ed_buttons_main.gif",0,1], false, function(e) { e.execCommand("justifyfull"); } ],
+
+ orderedlist: [ "Ordered List", ["ed_buttons_main.gif",0,3], false, function(e) { e.execCommand("insertorderedlist"); } ],
+ unorderedlist: [ "Bulleted List", ["ed_buttons_main.gif",1,3], false, function(e) { e.execCommand("insertunorderedlist"); } ],
+ insertorderedlist: [ "Ordered List", ["ed_buttons_main.gif",0,3], false, function(e) { e.execCommand("insertorderedlist"); } ],
+ insertunorderedlist: [ "Bulleted List", ["ed_buttons_main.gif",1,3], false, function(e) { e.execCommand("insertunorderedlist"); } ],
+
+ outdent: [ "Decrease Indent", ["ed_buttons_main.gif",1,2], false, function(e) { e.execCommand("outdent"); } ],
+ indent: [ "Increase Indent",["ed_buttons_main.gif",0,2], false, function(e) { e.execCommand("indent"); } ],
+ forecolor: [ "Font Color", ["ed_buttons_main.gif",3,3], false, function(e) { e.execCommand("forecolor"); } ],
+ hilitecolor: [ "Background Color", ["ed_buttons_main.gif",2,3], false, function(e) { e.execCommand("hilitecolor"); } ],
+
+ undo: [ "Undoes your last action", ["ed_buttons_main.gif",4,2], false, function(e) { e.execCommand("undo"); } ],
+ redo: [ "Redoes your last action", ["ed_buttons_main.gif",5,2], false, function(e) { e.execCommand("redo"); } ],
+ cut: [ "Cut selection", ["ed_buttons_main.gif",5,0], false, cut_copy_paste ],
+ copy: [ "Copy selection", ["ed_buttons_main.gif",4,0], false, cut_copy_paste ],
+ paste: [ "Paste from clipboard", ["ed_buttons_main.gif",4,1], false, cut_copy_paste ],
+ selectall: [ "Select all", "ed_selectall.gif", false, function(e) {e.execCommand("selectall");} ],
+
+ inserthorizontalrule: [ "Horizontal Rule", ["ed_buttons_main.gif",6,0], false, function(e) { e.execCommand("inserthorizontalrule"); } ],
+ createlink: [ "Insert Web Link", ["ed_buttons_main.gif",6,1], false, function(e) { e._createLink(); } ],
+ insertimage: [ "Insert/Modify Image", ["ed_buttons_main.gif",6,3], false, function(e) { e.execCommand("insertimage"); } ],
+ inserttable: [ "Insert Table", ["ed_buttons_main.gif",6,2], false, function(e) { e.execCommand("inserttable"); } ],
+
+ htmlmode: [ "Toggle HTML Source", ["ed_buttons_main.gif",7,0], true, function(e) { e.execCommand("htmlmode"); } ],
+ toggleborders: [ "Toggle Borders", ["ed_buttons_main.gif",7,2], false, function(e) { e._toggleBorders(); } ],
+ print: [ "Print document", ["ed_buttons_main.gif",8,1], false, function(e) { if(HTMLArea.is_gecko) {e._iframe.contentWindow.print(); } else { e.focusEditor(); print(); } } ],
+ saveas: [ "Save as", "ed_saveas.gif", false, function(e) { e.execCommand("saveas",false,"noname.htm"); } ],
+ about: [ "About this editor", ["ed_buttons_main.gif",8,2], true, function(e) { e.execCommand("about"); } ],
+ showhelp: [ "Help using editor", ["ed_buttons_main.gif",9,2], true, function(e) { e.execCommand("showhelp"); } ],
+
+ splitblock: [ "Split Block", "ed_splitblock.gif", false, function(e) { e._splitBlock(); } ],
+ lefttoright: [ "Direction left to right", ["ed_buttons_main.gif",0,4], false, function(e) { e.execCommand("lefttoright"); } ],
+ righttoleft: [ "Direction right to left", ["ed_buttons_main.gif",1,4], false, function(e) { e.execCommand("righttoleft"); } ],
+ overwrite: [ "Insert/Overwrite", "ed_overwrite.gif", false, function(e) { e.execCommand("overwrite"); } ],
+
+ wordclean: [ "MS Word Cleaner", ["ed_buttons_main.gif",5,3], false, function(e) { e._wordClean(); } ],
+ clearfonts: [ "Clear Inline Font Specifications", ["ed_buttons_main.gif",5,4], true, function(e) { e._clearFonts(); } ],
+ removeformat: [ "Remove formatting", ["ed_buttons_main.gif",4,4], false, function(e) { e.execCommand("removeformat"); } ],
+ killword: [ "Clear MSOffice tags", ["ed_buttons_main.gif",4,3], false, function(e) { e.execCommand("killword"); } ]
+ };
+
+ /* 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];
+ // prevent iterating over wrong type
+ if ( typeof btn != 'object' )
+ {
+ continue;
+ }
+ if ( typeof btn[1] != 'string' )
+ {
+ btn[1][0] = _editor_url + this.imgURL + btn[1][0];
+ }
+ else
+ {
+ btn[1] = _editor_url + this.imgURL + btn[1];
+ }
+ btn[0] = HTMLArea._lc(btn[0]); //initialize tooltip
+ }
+
+};
+
+/** 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;
+ }
+};
+
+HTMLArea.prototype.registerPanel = function(side, object)
+{
+ if ( !side )
+ {
+ side = 'right';
+ }
+ this.setLoadingMessage('Register panel ' + side);
+ var panel = this.addPanel(side);
+ if ( object )
+ {
+ object.drawPanelIn(panel);
+ }
+};
+
+/** 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 = toolbar.length; --i >= 0; )
+ {
+ 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: add buttons/drop-downs boxes with title or separator to the toolbar
+ * if the buttons/drop-downs boxes doesn't allready exists.
+ * id: button or selectbox (as array with separator or title)
+ * where: button or selectbox (as array if the first is not found take the second and so on)
+ * position:
+ * -1 = insert button (id) one position before the button (where)
+ * 0 = replace button (where) by button (id)
+ * +1 = insert button (id) one position after button (where)
+ *
+ * cfg.addToolbarElement(["T[title]", "button_id", "separator"] , ["first_id","second_id"], -1);
+*/
+
+HTMLArea.Config.prototype.addToolbarElement = function(id, where, position)
+{
+ var toolbar = this.toolbar;
+ var a, i, j, o, sid;
+ var idIsArray = false;
+ var whereIsArray = false;
+ var whereLength = 0;
+ var whereJ = 0;
+ var whereI = 0;
+ var exists = false;
+ var found = false;
+ // check if id and where are arrys
+ if ( ( id && typeof id == "object" ) && ( id.constructor == Array ) )
+ {
+ idIsArray = true;
+ }
+ if ( ( where && typeof where == "object" ) && ( where.constructor == Array ) )
+ {
+ whereIsArray = true;
+ whereLength = where.length;
+ }
+
+ if ( idIsArray ) //find the button/select box in input array
+ {
+ for ( i = 0; i < id.length; ++i )
+ {
+ if ( ( id[i] != "separator" ) && ( id[i].indexOf("T[") !== 0) )
+ {
+ sid = id[i];
+ }
+ }
+ }
+ else
+ {
+ sid = id;
+ }
+
+ for ( i = 0; !exists && !found && i < toolbar.length; ++i )
+ {
+ a = toolbar[i];
+ for ( j = 0; !found && j < a.length; ++j )
+ {
+ // check if button/select box exists
+ if ( a[i] == sid )
+ {
+ exists = true;
+ break;
+ }
+ if ( whereIsArray )
+ {
+ for ( o = 0; o < whereLength; ++o )
+ {
+ if ( a[j] == where[o] )
+ {
+ if ( o === 0 )
+ {
+ found = true;
+ j--;
+ break;
+ }
+ else
+ {
+ whereI = i;
+ whereJ = j;
+ whereLength = o;
+ }
+ }
+ }
+ }
+ else
+ {
+ // find the position to insert
+ if ( a[j] == where )
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !exists )
+ {
+ //if check found any other as the first button
+ if ( !found && whereIsArray )
+ {
+ if ( where.length != whereLength )
+ {
+ j = whereJ;
+ a = toolbar[whereI];
+ found = true;
+ }
+ }
+ if ( found )
+ {
+ // replace the found button
+ if ( position === 0 )
+ {
+ if ( idIsArray)
+ {
+ a[j] = id[id.length-1];
+ for ( i = id.length-1; --i >= 0; )
+ {
+ a.splice(j, 0, id[i]);
+ }
+ }
+ else
+ {
+ a[j] = id;
+ }
+ }
+ else
+ {
+ // insert before/after the found button
+ if ( position < 0 )
+ {
+ j = j + position + 1; //correct position before
+ }
+ else if ( position > 0 )
+ {
+ j = j + position; //correct posion after
+ }
+ if ( idIsArray )
+ {
+ for ( i = id.length; --i >= 0; )
+ {
+ a.splice(j, 0, id[i]);
+ }
+ }
+ else
+ {
+ a.splice(j, 0, id);
+ }
+ }
+ }
+ else
+ {
+ // no button found
+ toolbar[0].splice(0, 0, "separator");
+ if ( idIsArray)
+ {
+ for ( i = id.length; --i >= 0; )
+ {
+ toolbar[0].splice(0, 0, id[i]);
+ }
+ }
+ else
+ {
+ toolbar[0].splice(0, 0, id);
+ }
+ }
+ }
+};
+
+HTMLArea.Config.prototype.removeToolbarElement = HTMLArea.Config.prototype.hideSomeButtons;
+
+/** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
+HTMLArea.replaceAll = function(config)
+{
+ var tas = document.getElementsByTagName("textarea");
+ // @todo: weird syntax, doesnt help to read the code, doesnt obfuscate it and doesnt make it quicker, better rewrite this part
+ for ( var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate() )
+ {
+ // NOP
+ }
+};
+
+/** 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 ()
+{
+ this.setLoadingMessage('Create Toolbar');
+ var editor = this; // to access this in nested functions
+
+ var toolbar = document.createElement("div");
+ // ._toolbar is for legacy, ._toolBar is better thanks.
+ this._toolBar = this._toolbar = toolbar;
+ toolbar.className = "toolbar";
+ toolbar.unselectable = "1";
+
+ HTMLArea.freeLater(this, '_toolBar');
+ HTMLArea.freeLater(this, '_toolbar');
+
+ var tb_row = null;
+ var tb_objects = {};
+ this._toolbarObjects = tb_objects;
+
+ this._createToolbar1(editor, toolbar, tb_objects);
+ this._htmlArea.appendChild(toolbar);
+
+ return toolbar;
+};
+
+// FIXME : function never used, can probably be removed from source
+HTMLArea.prototype._setConfig = function(config)
+{
+ this.config = config;
+};
+
+HTMLArea.prototype._addToolbar = function()
+{
+ this._createToolbar1(this, this._toolbar, this._toolbarObjects);
+};
+
+/**
+ * Create a break element to add in the toolbar
+ *
+ * @return {Object} HTML element to add
+ * @private
+ */
+HTMLArea._createToolbarBreakingElement = function()
+{
+ var brk = document.createElement('div');
+ brk.style.height = '1px';
+ brk.style.width = '1px';
+ brk.style.lineHeight = '1px';
+ brk.style.fontSize = '1px';
+ brk.style.clear = 'both';
+ return brk;
+};
+
+// separate from previous createToolBar to allow dynamic change of toolbar
+HTMLArea.prototype._createToolbar1 = function (editor, toolbar, tb_objects)
+{
+ var tb_row;
+ // This shouldn't be necessary, but IE seems to float outside of the container
+ // when we float toolbar sections, so we have to clear:both here as well
+ // as at the end (which we do have to do).
+ if ( editor.config.flowToolbars )
+ {
+ toolbar.appendChild(HTMLArea._createToolbarBreakingElement());
+ }
+
+ // creates a new line in the toolbar
+ function newLine()
+ {
+ if ( typeof tb_row != 'undefined' && tb_row.childNodes.length === 0)
+ {
+ return;
+ }
+
+ var table = document.createElement("table");
+ table.border = "0px";
+ table.cellSpacing = "0px";
+ table.cellPadding = "0px";
+ if ( editor.config.flowToolbars )
+ {
+ if ( HTMLArea.is_ie )
+ {
+ table.style.styleFloat = "left";
+ }
+ else
+ {
+ table.style.cssFloat = "left";
+ }
+ }
+
+ 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);
+
+ table.className = 'toolbarRow'; // meh, kinda.
+ } // 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;
+ var tooltip = "";
+ 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;
+ if ( typeof dropdown.tooltip != "undefined" )
+ {
+ tooltip = dropdown.tooltip;
+ }
+ }
+ else
+ {
+ alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
+ }
+ break;
+ }
+ if ( options )
+ {
+ el = document.createElement("select");
+ el.title = tooltip;
+ 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
+ };
+
+ HTMLArea.freeLater(obj);
+
+ tb_objects[txt] = obj;
+
+ for ( var i in options )
+ {
+ // prevent iterating over wrong type
+ if ( typeof(options[i]) != 'string' )
+ {
+ continue;
+ }
+ var op = document.createElement("option");
+ op.innerHTML = HTMLArea._lc(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, btn, obj = null;
+ switch (txt)
+ {
+ case "separator":
+ if ( editor.config.flowToolbars )
+ {
+ newLine();
+ }
+ 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._lc("Current style");
+ 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
+ };
+
+ HTMLArea.freeLater(obj);
+
+ tb_objects[txt] = obj;
+ break;
+ default:
+ btn = editor.config.btnList[txt];
+ }
+ if ( !el && btn )
+ {
+ el = document.createElement("a");
+ el.style.display = 'block';
+ el.href = 'javascript:void(0)';
+ el.style.textDecoration = 'none';
+ el.title = btn[0];
+ el.className = "button";
+ el.style.direction = "ltr";
+ // let's just pretend we have a button object, and
+ // assign all the needed information to it.
+ 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?
+ };
+ HTMLArea.freeLater(el);
+ HTMLArea.freeLater(obj);
+
+ tb_objects[txt] = obj;
+
+ // prevent drag&drop of the icon to content area
+ el.ondrag = function() { return false; };
+
+ // handlers to emulate nice flat toolbar buttons
+ HTMLArea._addEvent(
+ el,
+ "mouseout",
+ function(ev)
+ {
+ if ( obj.enabled )
+ {
+ //HTMLArea._removeClass(el, "buttonHover");
+ HTMLArea._removeClass(el, "buttonActive");
+ if ( obj.active )
+ {
+ HTMLArea._addClass(el, "buttonPressed");
+ }
+ }
+ }
+ );
+
+ HTMLArea._addEvent(
+ el,
+ "mousedown",
+ function(ev)
+ {
+ if ( obj.enabled )
+ {
+ HTMLArea._addClass(el, "buttonActive");
+ HTMLArea._removeClass(el, "buttonPressed");
+ HTMLArea._stopEvent(HTMLArea.is_ie ? window.event : ev);
+ }
+ }
+ );
+
+ // when clicked, do the following:
+ HTMLArea._addEvent(
+ el,
+ "click",
+ function(ev)
+ {
+ if ( obj.enabled )
+ {
+ HTMLArea._removeClass(el, "buttonActive");
+ //HTMLArea._removeClass(el, "buttonHover");
+ if ( HTMLArea.is_gecko )
+ {
+ editor.activateEditor();
+ }
+ obj.cmd(editor, obj.name, obj);
+ HTMLArea._stopEvent(HTMLArea.is_ie ? window.event : ev);
+ }
+ }
+ );
+
+ var i_contain = HTMLArea.makeBtnImg(btn[1]);
+ var img = i_contain.firstChild;
+ el.appendChild(i_contain);
+
+ obj.imgel = img;
+ obj.swapImage = function(newimg)
+ {
+ if ( typeof newimg != 'string' )
+ {
+ img.src = newimg[0];
+ img.style.position = 'relative';
+ img.style.top = newimg[2] ? ('-' + (18 * (newimg[2] + 1)) + 'px') : '-18px';
+ img.style.left = newimg[1] ? ('-' + (18 * (newimg[1] + 1)) + 'px') : '-18px';
+ }
+ else
+ {
+ obj.imgel.src = newimg;
+ img.style.top = '0px';
+ img.style.left = '0px';
+ }
+ };
+
+ }
+ else if( !el )
+ {
+ el = createSelect(txt);
+ }
+
+ return el;
+ }
+
+ var first = true;
+ for ( var i = 0; i < this.config.toolbar.length; ++i )
+ {
+ if ( !first )
+ {
+ // createButton("linebreak");
+ }
+ else
+ {
+ first = false;
+ }
+ if ( this.config.toolbar[i] === null )
+ {
+ this.config.toolbar[i] = ['separator'];
+ }
+ var group = this.config.toolbar[i];
+
+ for ( var j = 0; j < group.length; ++j )
+ {
+ var code = group[j];
+ var tb_cell;
+ if ( /^([IT])\[(.*?)\]/.test(code) )
+ {
+ // special case, create text label
+ var l7ed = RegExp.$1 == "I"; // localized?
+ var label = RegExp.$2;
+ if ( l7ed )
+ {
+ label = HTMLArea._lc(label);
+ }
+ tb_cell = document.createElement("td");
+ tb_row.appendChild(tb_cell);
+ tb_cell.className = "label";
+ tb_cell.innerHTML = label;
+ }
+ else if ( typeof code != 'function' )
+ {
+ var tb_element = createButton(code);
+ if ( tb_element )
+ {
+ tb_cell = document.createElement("td");
+ tb_cell.className = 'toolbarElement';
+ tb_row.appendChild(tb_cell);
+ tb_cell.appendChild(tb_element);
+ }
+ else if ( tb_element === null )
+ {
+ alert("FIXME: Unknown toolbar item: " + code);
+ }
+ }
+ }
+ }
+
+ if ( editor.config.flowToolbars )
+ {
+ toolbar.appendChild(HTMLArea._createToolbarBreakingElement());
+ }
+
+ return toolbar;
+};
+
+// @todo : is this some kind of test not finished ?
+// Why the hell this is not in the config object ?
+var use_clone_img = false;
+HTMLArea.makeBtnImg = function(imgDef, doc)
+{
+ if ( !doc )
+ {
+ doc = document;
+ }
+
+ if ( !doc._htmlareaImgCache )
+ {
+ doc._htmlareaImgCache = {};
+ HTMLArea.freeLater(doc._htmlareaImgCache);
+ }
+
+ var i_contain = null;
+ if ( HTMLArea.is_ie && ( ( !doc.compatMode ) || ( doc.compatMode && doc.compatMode == "BackCompat" ) ) )
+ {
+ i_contain = doc.createElement('span');
+ }
+ else
+ {
+ i_contain = doc.createElement('div');
+ i_contain.style.position = 'relative';
+ }
+
+ i_contain.style.overflow = 'hidden';
+ i_contain.style.width = "18px";
+ i_contain.style.height = "18px";
+ i_contain.className = 'buttonImageContainer';
+
+ var img = null;
+ if ( typeof imgDef == 'string' )
+ {
+ if ( doc._htmlareaImgCache[imgDef] )
+ {
+ img = doc._htmlareaImgCache[imgDef].cloneNode();
+ }
+ else
+ {
+ img = doc.createElement("img");
+ img.src = imgDef;
+ img.style.width = "18px";
+ img.style.height = "18px";
+ if ( use_clone_img )
+ {
+ doc._htmlareaImgCache[imgDef] = img.cloneNode();
+ }
+ }
+ }
+ else
+ {
+ if ( doc._htmlareaImgCache[imgDef[0]] )
+ {
+ img = doc._htmlareaImgCache[imgDef[0]].cloneNode();
+ }
+ else
+ {
+ img = doc.createElement("img");
+ img.src = imgDef[0];
+ img.style.position = 'relative';
+ if ( use_clone_img )
+ {
+ doc._htmlareaImgCache[imgDef[0]] = img.cloneNode();
+ }
+ }
+ // @todo: Using 18 dont let us use a theme with its own icon toolbar height
+ // and width. Probably better to calculate this value 18
+ // var sizeIcon = img.width / nb_elements_per_image;
+ img.style.top = imgDef[2] ? ('-' + (18 * (imgDef[2] + 1)) + 'px') : '-18px';
+ img.style.left = imgDef[1] ? ('-' + (18 * (imgDef[1] + 1)) + 'px') : '-18px';
+ }
+ i_contain.appendChild(img);
+ return i_contain;
+};
+
+HTMLArea.prototype._createStatusBar = function()
+{
+ this.setLoadingMessage('Create StatusBar');
+ var statusbar = document.createElement("div");
+ statusbar.className = "statusBar";
+ this._statusBar = statusbar;
+ HTMLArea.freeLater(this, '_statusBar');
+
+ // statusbar.appendChild(document.createTextNode(HTMLArea._lc("Path") + ": "));
+ // creates a holder for the path view
+ var div = document.createElement("span");
+ div.className = "statusBarTree";
+ div.innerHTML = HTMLArea._lc("Path") + ": ";
+ this._statusBarTree = div;
+ HTMLArea.freeLater(this, '_statusBarTree');
+ this._statusBar.appendChild(div);
+
+ div = document.createElement("span");
+ div.innerHTML = HTMLArea._lc("You are in TEXT MODE. Use the [<>] button to switch back to WYSIWYG.");
+ div.style.display = "none";
+ this._statusBarTextMode = div;
+ HTMLArea.freeLater(this, '_statusBarTextMode');
+ this._statusBar.appendChild(div);
+
+ if ( !this.config.statusBar )
+ {
+ // disable it...
+ statusbar.style.display = "none";
+ }
+
+ return statusbar;
+};
+
+// Creates the HTMLArea object and replaces the textarea with it.
+HTMLArea.prototype.generate = function ()
+{
+ var i;
+ var editor = this; // we'll need "this" in some nested functions
+ this.setLoadingMessage('Generate Xinha object');
+
+ if ( typeof Dialog == 'undefined' )
+ {
+ HTMLArea._loadback(_editor_url + 'dialog.js', this.generate, this );
+ return false;
+ }
+
+ if ( typeof HTMLArea.Dialog == 'undefined' )
+ {
+ HTMLArea._loadback(_editor_url + 'inline-dialog.js', this.generate, this );
+ return false;
+ }
+
+ if ( typeof PopupWin == 'undefined' )
+ {
+ HTMLArea._loadback(_editor_url + 'popupwin.js', this.generate, this );
+ return false;
+ }
+
+ if ( typeof colorPicker == 'undefined' )
+ {
+ HTMLArea._loadback(_editor_url + 'popups/color_picker.js', this.generate, this );
+ return false;
+ }
+
+ if ( _editor_skin !== "" )
+ {
+ var found = false;
+ var head = document.getElementsByTagName("head")[0];
+ var links = document.getElementsByTagName("link");
+ for(i = 0; i<links.length; i++)
+ {
+ if ( ( links[i].rel == "stylesheet" ) && ( links[i].href == _editor_url + 'skins/' + _editor_skin + '/skin.css' ) )
+ {
+ found = true;
+ }
+ }
+ if ( !found )
+ {
+ var link = document.createElement("link");
+ link.type = "text/css";
+ link.href = _editor_url + 'skins/' + _editor_skin + '/skin.css';
+ link.rel = "stylesheet";
+ head.appendChild(link);
+ }
+ }
+
+ //backwards-compatibility: load FullScreen-Plugin if we find a "popupeditor"-button in the toolbar
+ // @todo: remove the backward compatibility in release 2.0
+ var toolbar = editor.config.toolbar;
+ for ( i = toolbar.length; --i >= 0; )
+ {
+ for ( var j = toolbar[i].length; --j >= 0; )
+ {
+ if ( toolbar[i][j]=="popupeditor" )
+ {
+ if ( typeof FullScreen == "undefined" )
+ {
+ // why can't we use the following line instead ?
+// HTMLArea.loadPlugin("FullScreen", this.generate );
+ HTMLArea.loadPlugin("FullScreen", function() { editor.generate(); } );
+ return false;
+ }
+ editor.registerPlugin('FullScreen');
+ }
+ }
+ }
+
+ // If this is gecko, set up the paragraph handling now
+ if ( HTMLArea.is_gecko && editor.config.mozParaHandler == 'best' )
+ {
+ if ( typeof EnterParagraphs == 'undefined' )
+ {
+ // why can't we use the following line instead ?
+// HTMLArea.loadPlugin("EnterParagraphs", this.generate );
+ HTMLArea.loadPlugin("EnterParagraphs", function() { editor.generate(); } );
+ return false;
+ }
+ editor.registerPlugin('EnterParagraphs');
+ }
+
+ // create the editor framework, yah, table layout I know, but much easier
+ // to get it working correctly this way, sorry about that, patches welcome.
+
+ this._framework =
+ {
+ 'table': document.createElement('table'),
+ 'tbody': document.createElement('tbody'), // IE will not show the table if it doesn't have a tbody!
+ 'tb_row': document.createElement('tr'),
+ 'tb_cell': document.createElement('td'), // Toolbar
+
+ 'tp_row': document.createElement('tr'),
+ 'tp_cell': this._panels.top.container, // top panel
+
+ 'ler_row': document.createElement('tr'),
+ 'lp_cell': this._panels.left.container, // left panel
+ 'ed_cell': document.createElement('td'), // editor
+ 'rp_cell': this._panels.right.container, // right panel
+
+ 'bp_row': document.createElement('tr'),
+ 'bp_cell': this._panels.bottom.container,// bottom panel
+
+ 'sb_row': document.createElement('tr'),
+ 'sb_cell': document.createElement('td') // status bar
+
+ };
+ HTMLArea.freeLater(this._framework);
+
+ var fw = this._framework;
+ fw.table.border = "0";
+ fw.table.cellPadding = "0";
+ fw.table.cellSpacing = "0";
+
+ fw.tb_row.style.verticalAlign = 'top';
+ fw.tp_row.style.verticalAlign = 'top';
+ fw.ler_row.style.verticalAlign= 'top';
+ fw.bp_row.style.verticalAlign = 'top';
+ fw.sb_row.style.verticalAlign = 'top';
+ fw.ed_cell.style.position = 'relative';
+
+ // Put the cells in the rows set col & rowspans
+ // note that I've set all these so that all panels are showing
+ // but they will be redone in sizeEditor() depending on which
+ // panels are shown. It's just here to clarify how the thing
+ // is put togethor.
+ fw.tb_row.appendChild(fw.tb_cell);
+ fw.tb_cell.colSpan = 3;
+
+ fw.tp_row.appendChild(fw.tp_cell);
+ fw.tp_cell.colSpan = 3;
+
+ fw.ler_row.appendChild(fw.lp_cell);
+ fw.ler_row.appendChild(fw.ed_cell);
+ fw.ler_row.appendChild(fw.rp_cell);
+
+ fw.bp_row.appendChild(fw.bp_cell);
+ fw.bp_cell.colSpan = 3;
+
+ fw.sb_row.appendChild(fw.sb_cell);
+ fw.sb_cell.colSpan = 3;
+
+ // Put the rows in the table body
+ fw.tbody.appendChild(fw.tb_row); // Toolbar
+ fw.tbody.appendChild(fw.tp_row); // Left, Top, Right panels
+ fw.tbody.appendChild(fw.ler_row); // Editor/Textarea
+ fw.tbody.appendChild(fw.bp_row); // Bottom panel
+ fw.tbody.appendChild(fw.sb_row); // Statusbar
+
+ // and body in the table
+ fw.table.appendChild(fw.tbody);
+
+ var htmlarea = this._framework.table;
+ this._htmlArea = htmlarea;
+ HTMLArea.freeLater(this, '_htmlArea');
+ htmlarea.className = "htmlarea";
+
+ // create the toolbar and put in the area
+ this._framework.tb_cell.appendChild( this._createToolbar() );
+
+ // create the IFRAME & add to container
+ var iframe = document.createElement("iframe");
+ iframe.src = _editor_url + editor.config.URIs.blank;
+ this._framework.ed_cell.appendChild(iframe);
+ this._iframe = iframe;
+ this._iframe.className = 'xinha_iframe';
+ HTMLArea.freeLater(this, '_iframe');
+
+ // creates & appends the status bar
+ var statusbar = this._createStatusBar();
+ this._framework.sb_cell.appendChild(statusbar);
+
+ // insert Xinha before the textarea.
+ var textarea = this._textArea;
+ textarea.parentNode.insertBefore(htmlarea, textarea);
+ textarea.className = 'xinha_textarea';
+
+ // extract the textarea and insert it into the htmlarea
+ HTMLArea.removeFromParent(textarea);
+ this._framework.ed_cell.appendChild(textarea);
+
+
+ // Set up event listeners for saving the iframe content to the textarea
+ if ( textarea.form )
+ {
+ // onsubmit get the HTMLArea content and update original textarea.
+ HTMLArea.prependDom0Event(
+ this._textArea.form,
+ 'submit',
+ function()
+ {
+ editor._textArea.value = editor.outwardHtml(editor.getHTML());
+ return true;
+ }
+ );
+
+ var initialTAContent = textarea.value;
+
+ // onreset revert the HTMLArea content to the textarea content
+ HTMLArea.prependDom0Event(
+ this._textArea.form,
+ 'reset',
+ function()
+ {
+ editor.setHTML(editor.inwardHtml(initialTAContent));
+ editor.updateToolbar();
+ return true;
+ }
+ );
+ }
+
+ // add a handler for the "back/forward" case -- on body.unload we save
+ // the HTML content into the original textarea.
+ HTMLArea.prependDom0Event(
+ window,
+ 'unload',
+ function()
+ {
+ textarea.value = editor.outwardHtml(editor.getHTML());
+ return true;
+ }
+ );
+
+ // Hide textarea
+ textarea.style.display = "none";
+
+ // Initalize size
+ editor.initSize();
+
+ // Add an event to initialize the iframe once loaded.
+ editor._iframeLoadDone = false;
+ HTMLArea._addEvent(
+ this._iframe,
+ 'load',
+ function(e)
+ {
+ if ( !editor._iframeLoadDone )
+ {
+ editor._iframeLoadDone = true;
+ editor.initIframe();
+ }
+ return true;
+ }
+ );
+
+};
+
+/**
+ * Size the editor according to the INITIAL sizing information.
+ * config.width
+ * The width may be set via three ways
+ * auto = the width is inherited from the original textarea
+ * toolbar = the width is set to be the same size as the toolbar
+ * <set size> = the width is an explicit size (any CSS measurement, eg 100em should be fine)
+ *
+ * config.height
+ * auto = the height is inherited from the original textarea
+ * <set size> = an explicit size measurement (again, CSS measurements)
+ *
+ * config.sizeIncludesBars
+ * true = the tool & status bars will appear inside the width & height confines
+ * false = the tool & status bars will appear outside the width & height confines
+ *
+ */
+
+HTMLArea.prototype.initSize = function()
+{
+ this.setLoadingMessage('Init editor size');
+ var editor = this;
+ var width = null;
+ var height = null;
+
+ switch ( this.config.width )
+ {
+ case 'auto':
+ width = this._initial_ta_size.w;
+ break;
+
+ case 'toolbar':
+ width = this._toolBar.offsetWidth + 'px';
+ break;
+
+ default :
+ // @todo: check if this is better :
+ // width = (parseInt(this.config.width, 10) == this.config.width)? this.config.width + 'px' : this.config.width;
+ width = /[^0-9]/.test(this.config.width) ? this.config.width : this.config.width + 'px';
+ break;
+ }
+
+ switch ( this.config.height )
+ {
+ case 'auto':
+ height = this._initial_ta_size.h;
+ break;
+
+ default :
+ // @todo: check if this is better :
+ // height = (parseInt(this.config.height, 10) == this.config.height)? this.config.height + 'px' : this.config.height;
+ height = /[^0-9]/.test(this.config.height) ? this.config.height : this.config.height + 'px';
+ break;
+ }
+
+ this.sizeEditor(width, height, this.config.sizeIncludesBars, this.config.sizeIncludesPanels);
+
+ // why can't we use the following line instead ?
+// this.notifyOn('panel_change',this.sizeEditor);
+ this.notifyOn('panel_change',function() { editor.sizeEditor(); });
+};
+
+/**
+ * Size the editor to a specific size, or just refresh the size (when window resizes for example)
+ * @param width optional width (CSS specification)
+ * @param height optional height (CSS specification)
+ * @param includingBars optional boolean to indicate if the size should include or exclude tool & status bars
+ */
+HTMLArea.prototype.sizeEditor = function(width, height, includingBars, includingPanels)
+{
+
+ // We need to set the iframe & textarea to 100% height so that the htmlarea
+ // isn't "pushed out" when we get it's height, so we can change them later.
+ this._iframe.style.height = '100%';
+ this._textArea.style.height = '100%';
+ this._iframe.style.width = '';
+ this._textArea.style.width = '';
+
+ if ( includingBars !== null )
+ {
+ this._htmlArea.sizeIncludesToolbars = includingBars;
+ }
+ if ( includingPanels !== null )
+ {
+ this._htmlArea.sizeIncludesPanels = includingPanels;
+ }
+
+ if ( width )
+ {
+ this._htmlArea.style.width = width;
+ if ( !this._htmlArea.sizeIncludesPanels )
+ {
+ // Need to add some for l & r panels
+ var rPanel = this._panels.right;
+ if ( rPanel.on && rPanel.panels.length && HTMLArea.hasDisplayedChildren(rPanel.div) )
+ {
+ this._htmlArea.style.width = (this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.right, 10)) + 'px';
+ }
+
+ var lPanel = this._panels.left;
+ if ( lPanel.on && lPanel.panels.length && HTMLArea.hasDisplayedChildren(lPanel.div) )
+ {
+ this._htmlArea.style.width = (this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.left, 10)) + 'px';
+ }
+ }
+ }
+
+ if ( height )
+ {
+ this._htmlArea.style.height = height;
+ if ( !this._htmlArea.sizeIncludesToolbars )
+ {
+ // Need to add some for toolbars
+ this._htmlArea.style.height = (this._htmlArea.offsetHeight + this._toolbar.offsetHeight + this._statusBar.offsetHeight) + 'px';
+ }
+
+ if ( !this._htmlArea.sizeIncludesPanels )
+ {
+ // Need to add some for t & b panels
+ var tPanel = this._panels.top;
+ if ( tPanel.on && tPanel.panels.length && HTMLArea.hasDisplayedChildren(tPanel.div) )
+ {
+ this._htmlArea.style.height = (this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.top, 10)) + 'px';
+ }
+
+ var bPanel = this._panels.bottom;
+ if ( bPanel.on && bPanel.panels.length && HTMLArea.hasDisplayedChildren(bPanel.div) )
+ {
+ this._htmlArea.style.height = (this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.bottom, 10)) + 'px';
+ }
+ }
+ }
+
+ // At this point we have this._htmlArea.style.width & this._htmlArea.style.height
+ // which are the size for the OUTER editor area, including toolbars and panels
+ // now we size the INNER area and position stuff in the right places.
+ width = this._htmlArea.offsetWidth;
+ height = this._htmlArea.offsetHeight;
+
+ // Set colspan for toolbar, and statusbar, rowspan for left & right panels, and insert panels to be displayed
+ // into thier rows
+ var panels = this._panels;
+ var editor = this;
+ var col_span = 1;
+
+ function panel_is_alive(pan)
+ {
+ if ( panels[pan].on && panels[pan].panels.length && HTMLArea.hasDisplayedChildren(panels[pan].container) )
+ {
+ panels[pan].container.style.display = '';
+ return true;
+ }
+ // Otherwise make sure it's been removed from the framework
+ else
+ {
+ panels[pan].container.style.display='none';
+ return false;
+ }
+ }
+
+ if ( panel_is_alive('left') )
+ {
+ col_span += 1;
+ }
+
+// if ( panel_is_alive('top') )
+// {
+ // NOP
+// }
+
+ if ( panel_is_alive('right') )
+ {
+ col_span += 1;
+ }
+
+// if ( panel_is_alive('bottom') )
+// {
+ // NOP
+// }
+
+ this._framework.tb_cell.colSpan = col_span;
+ this._framework.tp_cell.colSpan = col_span;
+ this._framework.bp_cell.colSpan = col_span;
+ this._framework.sb_cell.colSpan = col_span;
+
+ // Put in the panel rows, top panel goes above editor row
+ if ( !this._framework.tp_row.childNodes.length )
+ {
+ HTMLArea.removeFromParent(this._framework.tp_row);
+ }
+ else
+ {
+ if ( !HTMLArea.hasParentNode(this._framework.tp_row) )
+ {
+ this._framework.tbody.insertBefore(this._framework.tp_row, this._framework.ler_row);
+ }
+ }
+
+ // bp goes after the editor
+ if ( !this._framework.bp_row.childNodes.length )
+ {
+ HTMLArea.removeFromParent(this._framework.bp_row);
+ }
+ else
+ {
+ if ( !HTMLArea.hasParentNode(this._framework.bp_row) )
+ {
+ this._framework.tbody.insertBefore(this._framework.bp_row, this._framework.ler_row.nextSibling);
+ }
+ }
+
+ // finally if the statusbar is on, insert it
+ if ( !this.config.statusBar )
+ {
+ HTMLArea.removeFromParent(this._framework.sb_row);
+ }
+ else
+ {
+ if ( !HTMLArea.hasParentNode(this._framework.sb_row) )
+ {
+ this._framework.table.appendChild(this._framework.sb_row);
+ }
+ }
+
+ // Size and set colspans, link up the framework
+ this._framework.lp_cell.style.width = this.config.panel_dimensions.left;
+ this._framework.rp_cell.style.width = this.config.panel_dimensions.right;
+ this._framework.tp_cell.style.height = this.config.panel_dimensions.top;
+ this._framework.bp_cell.style.height = this.config.panel_dimensions.bottom;
+ this._framework.tb_cell.style.height = this._toolBar.offsetHeight + 'px';
+ this._framework.sb_cell.style.height = this._statusBar.offsetHeight + 'px';
+
+ var edcellheight = height - this._toolBar.offsetHeight - this._statusBar.offsetHeight;
+ if ( panel_is_alive('top') )
+ {
+ edcellheight -= parseInt(this.config.panel_dimensions.top, 10);
+ }
+ if ( panel_is_alive('bottom') )
+ {
+ edcellheight -= parseInt(this.config.panel_dimensions.bottom, 10);
+ }
+ this._iframe.style.height = edcellheight + 'px';
+// this._framework.rp_cell.style.height = edcellheight + 'px';
+// this._framework.lp_cell.style.height = edcellheight + 'px';
+
+ // (re)size the left and right panels so they are equal the editor height
+// for(var i = 0; i < this._panels.left.panels.length; i++)
+// {
+// this._panels.left.panels[i].style.height = this._iframe.style.height;
+// }
+
+// for(var i = 0; i < this._panels.right.panels.length; i++)
+// {
+// this._panels.right.panels[i].style.height = this._iframe.style.height;
+// }
+
+ var edcellwidth = width;
+ if ( panel_is_alive('left') )
+ {
+ edcellwidth -= parseInt(this.config.panel_dimensions.left, 10);
+ }
+ if ( panel_is_alive('right') )
+ {
+ edcellwidth -= parseInt(this.config.panel_dimensions.right, 10);
+ }
+ this._iframe.style.width = edcellwidth + 'px';
+
+ this._textArea.style.height = this._iframe.style.height;
+ this._textArea.style.width = this._iframe.style.width;
+
+ this.notifyOf('resize', {width:this._htmlArea.offsetWidth, height:this._htmlArea.offsetHeight});
+};
+
+HTMLArea.prototype.addPanel = function(side)
+{
+ var div = document.createElement('div');
+ div.side = side;
+ if ( side == 'left' || side == 'right' )
+ {
+ div.style.width = this.config.panel_dimensions[side];
+ if(this._iframe) div.style.height = this._iframe.style.height;
+ }
+ HTMLArea.addClasses(div, 'panel');
+ this._panels[side].panels.push(div);
+ this._panels[side].div.appendChild(div);
+
+ this.notifyOf('panel_change', {'action':'add','panel':div});
+
+ return div;
+};
+
+
+HTMLArea.prototype.removePanel = function(panel)
+{
+ this._panels[panel.side].div.removeChild(panel);
+ var clean = [];
+ for ( var i = 0; i < this._panels[panel.side].panels.length; i++ )
+ {
+ if ( this._panels[panel.side].panels[i] != panel )
+ {
+ clean.push(this._panels[panel.side].panels[i]);
+ }
+ }
+ this._panels[panel.side].panels = clean;
+ this.notifyOf('panel_change', {'action':'remove','panel':panel});
+};
+
+HTMLArea.prototype.hidePanel = function(panel)
+{
+ if ( panel && panel.style.display != 'none' )
+ {
+ panel.style.display = 'none';
+ this.notifyOf('panel_change', {'action':'hide','panel':panel});
+ }
+};
+
+HTMLArea.prototype.showPanel = function(panel)
+{
+ if ( panel && panel.style.display == 'none' )
+ {
+ panel.style.display = '';
+ this.notifyOf('panel_change', {'action':'show','panel':panel});
+ }
+};
+
+HTMLArea.prototype.hidePanels = function(sides)
+{
+ if ( typeof sides == 'undefined' )
+ {
+ sides = ['left','right','top','bottom'];
+ }
+
+ var reShow = [];
+ for ( var i = 0; i < sides.length;i++ )
+ {
+ if ( this._panels[sides[i]].on )
+ {
+ reShow.push(sides[i]);
+ this._panels[sides[i]].on = false;
+ }
+ }
+ this.notifyOf('panel_change', {'action':'multi_hide','sides':sides});
+};
+
+HTMLArea.prototype.showPanels = function(sides)
+{
+ if ( typeof sides == 'undefined' )
+ {
+ sides = ['left','right','top','bottom'];
+ }
+
+ var reHide = [];
+ for ( var i = 0; i < sides.length; i++ )
+ {
+ if ( !this._panels[sides[i]].on )
+ {
+ reHide.push(sides[i]);
+ this._panels[sides[i]].on = true;
+ }
+ }
+ this.notifyOf('panel_change', {'action':'multi_show','sides':sides});
+};
+
+HTMLArea.objectProperties = function(obj)
+{
+ var props = [];
+ for ( var x in obj )
+ {
+ props[props.length] = x;
+ }
+ return props;
+};
+
+/*
+ * EDITOR ACTIVATION NOTES:
+ * when a page has multiple Xinha editors, ONLY ONE should be activated at any time (this is mostly to
+ * work around a bug in Mozilla, but also makes some sense). No editor should be activated or focused
+ * automatically until at least one editor has been activated through user action (by mouse-clicking in
+ * the editor).
+ */
+HTMLArea.prototype.editorIsActivated = function()
+{
+ try
+ {
+ return HTMLArea.is_gecko? this._doc.designMode == 'on' : this._doc.body.contentEditable;
+ }
+ catch (ex)
+ {
+ return false;
+ }
+};
+
+HTMLArea._someEditorHasBeenActivated = false;
+HTMLArea._currentlyActiveEditor = false;
+HTMLArea.prototype.activateEditor = function()
+{
+ // We only want ONE editor at a time to be active
+ if ( HTMLArea._currentlyActiveEditor )
+ {
+ if ( HTMLArea._currentlyActiveEditor == this )
+ {
+ return true;
+ }
+ HTMLArea._currentlyActiveEditor.deactivateEditor();
+ }
+
+ if ( HTMLArea.is_gecko && this._doc.designMode != 'on' )
+ {
+ try
+ {
+ // cannot set design mode if no display
+ if ( this._iframe.style.display == 'none' )
+ {
+ this._iframe.style.display = '';
+ this._doc.designMode = 'on';
+ this._iframe.style.display = 'none';
+ }
+ else
+ {
+ this._doc.designMode = 'on';
+ }
+ } catch (ex) {}
+ }
+ else if ( !HTMLArea.is_gecko && this._doc.body.contentEditable !== true )
+ {
+ this._doc.body.contentEditable = true;
+ }
+
+ // We need to know that at least one editor on the page has been activated
+ // this is because we will not focus any editor until an editor has been activated
+ HTMLArea._someEditorHasBeenActivated = true;
+ HTMLArea._currentlyActiveEditor = this;
+
+ var editor = this;
+ this.enableToolbar();
+};
+
+HTMLArea.prototype.deactivateEditor = function()
+{
+ // If the editor isn't active then the user shouldn't use the toolbar
+ this.disableToolbar();
+
+ if ( HTMLArea.is_gecko && this._doc.designMode != 'off' )
+ {
+ try
+ {
+ this._doc.designMode = 'off';
+ } catch (ex) {}
+ }
+ else if ( !HTMLArea.is_gecko && this._doc.body.contentEditable !== false )
+ {
+ this._doc.body.contentEditable = false;
+ }
+
+ if ( HTMLArea._currentlyActiveEditor != this )
+ {
+ // We just deactivated an editor that wasn't marked as the currentlyActiveEditor
+
+ return; // I think this should really be an error, there shouldn't be a situation where
+ // an editor is deactivated without first being activated. but it probably won't
+ // hurt anything.
+ }
+
+ HTMLArea._currentlyActiveEditor = false;
+};
+
+HTMLArea.prototype.initIframe = function()
+{
+ this.setLoadingMessage('Init IFrame');
+ this.disableToolbar();
+ var doc = null;
+ var editor = this;
+ try
+ {
+ if ( editor._iframe.contentDocument )
+ {
+ this._doc = editor._iframe.contentDocument;
+ }
+ else
+ {
+ this._doc = editor._iframe.contentWindow.document;
+ }
+ doc = this._doc;
+ // try later
+ if ( !doc )
+ {
+ if ( HTMLArea.is_gecko )
+ {
+ setTimeout(function() { editor.initIframe(); }, 50);
+ return false;
+ }
+ else
+ {
+ alert("ERROR: IFRAME can't be initialized.");
+ }
+ }
+ }
+ catch(ex)
+ { // try later
+ setTimeout(function() { editor.initIframe(); }, 50);
+ }
+
+ HTMLArea.freeLater(this, '_doc');
+
+ doc.open("text/html","replace");
+ var html = '';
+ if ( !editor.config.fullPage )
+ {
+ html = "<html>\n";
+ html += "<head>\n";
+ html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + editor.config.charSet + "\">\n";
+ if ( typeof editor.config.baseHref != 'undefined' && editor.config.baseHref !== null )
+ {
+ html += "<base href=\"" + editor.config.baseHref + "\"/>\n";
+ }
+ html += "<style title=\"table borders\">";
+ html += ".htmtableborders, .htmtableborders td, .htmtableborders th {border : 1px dashed lightgrey ! important;} \n";
+ html += "</style>\n";
+ html += "<style type=\"text/css\">";
+ html += "html, body { border: 0px; background-color: #ffffff; } \n";
+ html += "span.macro, span.macro ul, span.macro div, span.macro p {background : #CCCCCC;}\n";
+ html += "</style>\n";
+
+ if ( editor.config.pageStyle )
+ {
+ html += "<style type=\"text/css\">\n" + editor.config.pageStyle + "\n</style>";
+ }
+
+ if ( typeof editor.config.pageStyleSheets !== 'undefined' )
+ {
+ for ( var i = 0; i < editor.config.pageStyleSheets.length; i++ )
+ {
+ if ( editor.config.pageStyleSheets[i].length > 0 )
+ {
+ html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + editor.config.pageStyleSheets[i] + "\">";
+ //html += "<style> @import url('" + editor.config.pageStyleSheets[i] + "'); </style>\n";
+ }
+ }
+ }
+ html += "</head>\n";
+ html += "<body>\n";
+ html += editor.inwardHtml(editor._textArea.value);
+ html += "</body>\n";
+ html += "</html>";
+ }
+ else
+ {
+ html = editor.inwardHtml(editor._textArea.value);
+ if ( html.match(HTMLArea.RE_doctype) )
+ {
+ editor.setDoctype(RegExp.$1);
+ html = html.replace(HTMLArea.RE_doctype, "");
+ }
+ }
+ doc.write(html);
+ doc.close();
+
+ this.setEditorEvents();
+};
+
+/**
+ * Delay a function until the document is ready for operations.
+ * See ticket:547
+ * @param {object} F (Function) The function to call once the document is ready
+ * @public
+ */
+HTMLArea.prototype.whenDocReady = function(F)
+{
+ var E = this;
+ if ( this._doc && this._doc.body )
+ {
+ F();
+ }
+ else
+ {
+ setTimeout(function() { E.whenDocReady(F); }, 50);
+ }
+};
+
+// Switches editor mode; parameter can be "textmode" or "wysiwyg". If no
+// parameter was passed this function toggles between modes.
+HTMLArea.prototype.setMode = function(mode)
+{
+ var html;
+ if ( typeof mode == "undefined" )
+ {
+ mode = this._editMode == "textmode" ? "wysiwyg" : "textmode";
+ }
+ switch ( mode )
+ {
+ case "textmode":
+ html = this.outwardHtml(this.getHTML());
+ this.setHTML(html);
+
+ // Hide the iframe
+ this.deactivateEditor();
+ this._iframe.style.display = 'none';
+ this._textArea.style.display = '';
+
+ if ( this.config.statusBar )
+ {
+ this._statusBarTree.style.display = "none";
+ this._statusBarTextMode.style.display = "";
+ }
+
+ this.notifyOf('modechange', {'mode':'text'});
+ break;
+
+ case "wysiwyg":
+ html = this.inwardHtml(this.getHTML());
+ this.deactivateEditor();
+ this.setHTML(html);
+ this._iframe.style.display = '';
+ this._textArea.style.display = "none";
+ this.activateEditor();
+ if ( this.config.statusBar )
+ {
+ this._statusBarTree.style.display = "";
+ this._statusBarTextMode.style.display = "none";
+ }
+
+ this.notifyOf('modechange', {'mode':'wysiwyg'});
+ break;
+
+ default:
+ alert("Mode <" + mode + "> not defined!");
+ return false;
+ }
+ this._editMode = mode;
+
+ for ( var i in this.plugins )
+ {
+ var plugin = this.plugins[i].instance;
+ if ( plugin && typeof plugin.onMode == "function" )
+ {
+ plugin.onMode(mode);
+ }
+ }
+};
+
+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 reac = this.editorIsActivated();
+ if ( reac )
+ {
+ this.deactivateEditor();
+ }
+ var html_re = /<html>((.|\n)*?)<\/html>/i;
+ html = html.replace(html_re, "$1");
+ this._doc.open("text/html","replace");
+ this._doc.write(html);
+ this._doc.close();
+ if ( reac )
+ {
+ this.activateEditor();
+ }
+ this.setEditorEvents();
+ return true;
+ }
+};
+
+HTMLArea.prototype.setEditorEvents = function()
+{
+ var editor=this;
+ var doc=this._doc;
+ editor.whenDocReady(
+ function()
+ {
+ // if we have multiple editors some bug in Mozilla makes some lose editing ability
+ HTMLArea._addEvents(
+ doc,
+ ["mousedown"],
+ function()
+ {
+ editor.activateEditor();
+ return true;
+ }
+ );
+
+ // 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;
+ HTMLArea.refreshPlugin(plugin);
+ }
+
+ // specific editor initialization
+ if ( typeof editor._onGenerate == "function" )
+ {
+ editor._onGenerate();
+ }
+
+ HTMLArea.addDom0Event(window, 'resize', function(e) { editor.sizeEditor(); });
+ editor.removeLoadingMessage();
+ }
+ );
+};
+
+/***************************************************
+ * Category: PLUGINS
+ ***************************************************/
+
+// Create the specified plugin and register it with this HTMLArea
+// return the plugin created to allow refresh when necessary
+HTMLArea.prototype.registerPlugin = function()
+{
+ var plugin = arguments[0];
+
+ // @todo : try to avoid the use of eval()
+ // We can only register plugins that have been succesfully loaded
+ if ( plugin === null || typeof plugin == 'undefined' || (typeof plugin == 'string' && eval('typeof ' + plugin) == 'undefined') )
+ {
+ return false;
+ }
+
+ var args = [];
+ for ( var i = 1; i < arguments.length; ++i )
+ {
+ args.push(arguments[i]);
+ }
+ return this.registerPlugin2(plugin, args);
+};
+
+// 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)
+{
+ // @todo : try to avoid the use of eval()
+ if ( typeof plugin == "string" )
+ {
+ plugin = eval(plugin);
+ }
+ if ( typeof plugin == "undefined" )
+ {
+ /* FIXME: This should never happen. But why does it do? */
+ return false;
+ }
+ 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;
+ return obj;
+ }
+ 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.getPluginDir = function(pluginName)
+{
+ return _editor_url + "plugins/" + pluginName;
+};
+
+HTMLArea.loadPlugin = function(pluginName, callback)
+{
+ // @todo : try to avoid the use of eval()
+ // Might already be loaded
+ if ( eval('typeof ' + pluginName) != 'undefined' )
+ {
+ if ( callback )
+ {
+ callback(pluginName);
+ }
+ return true;
+ }
+
+ var dir = this.getPluginDir(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;
+
+ HTMLArea._loadback(plugin_file, callback ? function() { callback(pluginName); } : null);
+ return false;
+};
+
+HTMLArea._pluginLoadStatus = {};
+HTMLArea.loadPlugins = function(plugins, callbackIfNotReady)
+{
+ // Rip the ones that are loaded and look for ones that have failed
+ var retVal = true;
+ var nuPlugins = HTMLArea.cloneObject(plugins);
+ while ( nuPlugins.length )
+ {
+ var p = nuPlugins.pop();
+ if ( typeof HTMLArea._pluginLoadStatus[p] == 'undefined' )
+ {
+ // Load it
+ HTMLArea._pluginLoadStatus[p] = 'loading';
+ HTMLArea.loadPlugin(p,
+ function(plugin)
+ {
+ // @todo : try to avoid the use of eval()
+ if ( eval('typeof ' + plugin) != 'undefined' )
+ {
+ HTMLArea._pluginLoadStatus[plugin] = 'ready';
+ }
+ else
+ {
+ // Actually, this won't happen, because if the script fails
+ // it will throw an exception preventing the callback from
+ // running. This will leave it always in the "loading" state
+ // unfortunatly that means we can't fail plugins gracefully
+ // by just skipping them.
+ HTMLArea._pluginLoadStatus[plugin] = 'failed';
+ }
+ }
+ );
+ retVal = false;
+ }
+ else
+ {
+ // @todo: a simple (if) would not be better than this tortuous (switch) structure ?
+ // if ( HTMLArea._pluginLoadStatus[p] !== 'failed' && HTMLArea._pluginLoadStatus[p] !== 'ready' )
+ // {
+ // retVal = false;
+ // }
+ switch ( HTMLArea._pluginLoadStatus[p] )
+ {
+ case 'failed':
+ case 'ready' :
+ break;
+
+ //case 'loading':
+ default :
+ retVal = false;
+ break;
+ }
+ }
+ }
+
+ // All done, just return
+ if ( retVal )
+ {
+ return true;
+ }
+
+ // Waiting on plugins to load, return false now and come back a bit later
+ // if we have to callback
+ if ( callbackIfNotReady )
+ {
+ setTimeout(function() { if ( HTMLArea.loadPlugins(plugins, callbackIfNotReady) ) { callbackIfNotReady(); } }, 150);
+ }
+ return retVal;
+};
+
+// refresh plugin by calling onGenerate or onGenerateOnce method.
+HTMLArea.refreshPlugin = function(plugin)
+{
+ if ( plugin && typeof plugin.onGenerate == "function" )
+ {
+ plugin.onGenerate();
+ }
+ if ( plugin && typeof plugin.onGenerateOnce == "function" )
+ {
+ plugin.onGenerateOnce();
+ plugin.onGenerateOnce = null;
+ }
+};
+
+HTMLArea.loadStyle = function(style, plugin)
+{
+ var url = _editor_url || '';
+ if ( typeof plugin != "undefined" )
+ {
+ url += "plugins/" + plugin + "/";
+ }
+ url += style;
+ // @todo: would not it be better to check the first character instead of a regex ?
+ // if ( typeof style == 'string' && style.charAt(0) == '/' )
+ // {
+ // url = style;
+ // }
+ if ( /^\//.test(style) )
+ {
+ url = style;
+ }
+ var head = document.getElementsByTagName("head")[0];
+ var link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.href = url;
+ head.appendChild(link);
+ //document.write("<style type='text/css'>@import url(" + url + ");</style>");
+};
+HTMLArea.loadStyle(typeof _editor_css == "string" ? _editor_css : "htmlarea.css");
+
+/***************************************************
+ * Category: EDITOR UTILITIES
+ ***************************************************/
+
+HTMLArea.prototype.debugTree = function()
+{
+ var ta = document.createElement("textarea");
+ ta.style.width = "100%";
+ ta.style.height = "20em";
+ ta.value = "";
+ function debug(indent, str)
+ {
+ for ( ; --indent >= 0; )
+ {
+ ta.value += " ";
+ }
+ ta.value += str + "\n";
+ }
+ function _dt(root, level)
+ {
+ var tag = root.tagName.toLowerCase(), i;
+ var ns = HTMLArea.is_ie ? root.scopeName : root.prefix;
+ debug(level, "- " + tag + " [" + ns + "]");
+ for ( i = root.firstChild; i; i = i.nextSibling )
+ {
+ if ( i.nodeType == 1 )
+ {
+ _dt(i, level + 2);
+ }
+ }
+ }
+ _dt(this._doc.body, 0);
+ document.body.appendChild(ta);
+};
+
+HTMLArea.getInnerText = function(el)
+{
+ var txt = '', i;
+ for ( i = el.firstChild; i; i = i.nextSibling )
+ {
+ if ( i.nodeType == 3 )
+ {
+ txt += i.data;
+ }
+ else if ( i.nodeType == 1 )
+ {
+ txt += HTMLArea.getInnerText(i);
+ }
+ }
+ return txt;
+};
+
+HTMLArea.prototype._wordClean = function()
+{
+ var editor = this;
+ var stats =
+ {
+ empty_tags : 0,
+ mso_class : 0,
+ mso_style : 0,
+ mso_xmlel : 0,
+ orig_len : this._doc.body.innerHTML.length,
+ T : (new Date()).getTime()
+ };
+ var stats_txt =
+ {
+ empty_tags : "Empty tags removed: ",
+ mso_class : "MSO class names removed: ",
+ mso_style : "MSO inline style removed: ",
+ mso_xmlel : "MSO XML elements stripped: "
+ };
+
+ function showStats()
+ {
+ var txt = "HTMLArea word cleaner stats: \n\n";
+ for ( var i in stats )
+ {
+ if ( stats_txt[i] )
+ {
+ txt += stats_txt[i] + stats[i] + "\n";
+ }
+ }
+ txt += "\nInitial document length: " + stats.orig_len + "\n";
+ txt += "Final document length: " + editor._doc.body.innerHTML.length + "\n";
+ txt += "Clean-up took " + (((new Date()).getTime() - stats.T) / 1000) + " seconds";
+ alert(txt);
+ }
+
+ function clearClass(node)
+ {
+ var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig, ' ');
+ if ( newc != node.className )
+ {
+ node.className = newc;
+ if ( ! ( /\S/.test(node.className) ) )
+ {
+ node.removeAttribute("className");
+ ++stats.mso_class;
+ }
+ }
+ }
+
+ function clearStyle(node)
+ {
+ var declarations = node.style.cssText.split(/\s*;\s*/);
+ for ( var i = declarations.length; --i >= 0; )
+ {
+ if ( ( /^mso|^tab-stops/i.test(declarations[i]) ) || ( /^margin\s*:\s*0..\s+0..\s+0../i.test(declarations[i]) ) )
+ {
+ ++stats.mso_style;
+ declarations.splice(i, 1);
+ }
+ }
+ node.style.cssText = declarations.join("; ");
+ }
+
+ var stripTag = null;
+ if ( HTMLArea.is_ie )
+ {
+ stripTag = function(el)
+ {
+ el.outerHTML = HTMLArea.htmlEncode(el.innerText);
+ ++stats.mso_xmlel;
+ };
+ }
+ else
+ {
+ stripTag = function(el)
+ {
+ var txt = document.createTextNode(HTMLArea.getInnerText(el));
+ el.parentNode.insertBefore(txt, el);
+ HTMLArea.removeFromParent(el);
+ ++stats.mso_xmlel;
+ };
+ }
+
+ function checkEmpty(el)
+ {
+ // @todo : check if this is quicker
+ // if (!['A','SPAN','B','STRONG','I','EM','FONT'].contains(el.tagName) && !el.firstChild)
+ if ( /^(a|span|b|strong|i|em|font)$/i.test(el.tagName) && !el.firstChild)
+ {
+ HTMLArea.removeFromParent(el);
+ ++stats.empty_tags;
+ }
+ }
+
+ function parseTree(root)
+ {
+ var tag = root.tagName.toLowerCase(), i, next;
+ // @todo : probably better to use String.indexOf() instead of this ugly regex
+ // if ((HTMLArea.is_ie && root.scopeName != 'HTML') || (!HTMLArea.is_ie && tag.indexOf(':') !== -1)) {
+ if ( ( HTMLArea.is_ie && root.scopeName != 'HTML' ) || ( !HTMLArea.is_ie && ( /:/.test(tag) ) ) )
+ {
+ stripTag(root);
+ return false;
+ }
+ else
+ {
+ clearClass(root);
+ clearStyle(root);
+ for ( i = root.firstChild; i; i = next )
+ {
+ next = i.nextSibling;
+ if ( i.nodeType == 1 && parseTree(i) )
+ {
[... 4023 lines stripped ...]