integration/lib/CLEditor/jquery.cleditor.js
changeset 2 78f71aa0a477
parent 1 b95aebb070b5
equal deleted inserted replaced
1:b95aebb070b5 2:78f71aa0a477
       
     1 /**
       
     2  @preserve CLEditor WYSIWYG HTML Editor v1.3.0
       
     3  http://premiumsoftware.net/cleditor
       
     4  requires jQuery v1.4.2 or later
       
     5 
       
     6  Copyright 2010, Chris Landowski, Premium Software, LLC
       
     7  Dual licensed under the MIT or GPL Version 2 licenses.
       
     8 */
       
     9 
       
    10 // ==ClosureCompiler==
       
    11 // @compilation_level SIMPLE_OPTIMIZATIONS
       
    12 // @output_file_name jquery.cleditor.min.js
       
    13 // ==/ClosureCompiler==
       
    14 
       
    15 (function($) {
       
    16 
       
    17   //==============
       
    18   // jQuery Plugin
       
    19   //==============
       
    20 
       
    21   $.cleditor = {
       
    22 
       
    23     // Define the defaults used for all new cleditor instances
       
    24     defaultOptions: {
       
    25       width:        500, // width not including margins, borders or padding
       
    26       height:       250, // height not including margins, borders or padding
       
    27       controls:     // controls to add to the toolbar
       
    28                     "bold italic underline strikethrough subscript superscript | font size " +
       
    29                     "style | color highlight removeformat | bullets numbering | outdent " +
       
    30                     "indent | alignleft center alignright justify | undo redo | " +
       
    31                     "rule image link unlink | cut copy paste pastetext | print source",
       
    32       colors:       // colors in the color popup
       
    33                     "FFF FCC FC9 FF9 FFC 9F9 9FF CFF CCF FCF " +
       
    34                     "CCC F66 F96 FF6 FF3 6F9 3FF 6FF 99F F9F " +
       
    35                     "BBB F00 F90 FC6 FF0 3F3 6CC 3CF 66C C6C " +
       
    36                     "999 C00 F60 FC3 FC0 3C0 0CC 36F 63F C3C " +
       
    37                     "666 900 C60 C93 990 090 399 33F 60C 939 " +
       
    38                     "333 600 930 963 660 060 366 009 339 636 " +
       
    39                     "000 300 630 633 330 030 033 006 309 303",    
       
    40       fonts:        // font names in the font popup
       
    41                     "Arial,Arial Black,Comic Sans MS,Courier New,Narrow,Garamond," +
       
    42                     "Georgia,Impact,Sans Serif,Serif,Tahoma,Trebuchet MS,Verdana",
       
    43       sizes:        // sizes in the font size popup
       
    44                     "1,2,3,4,5,6,7",
       
    45       styles:       // styles in the style popup
       
    46                     [["Paragraph", "<p>"], ["Header 1", "<h1>"], ["Header 2", "<h2>"],
       
    47                     ["Header 3", "<h3>"],  ["Header 4","<h4>"],  ["Header 5","<h5>"],
       
    48                     ["Header 6","<h6>"]],
       
    49       useCSS:       false, // use CSS to style HTML when possible (not supported in ie)
       
    50       docType:      // Document type contained within the editor
       
    51                     '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
       
    52       docCSSFile:   // CSS file used to style the document contained within the editor
       
    53                     "", 
       
    54       bodyStyle:    // style to assign to document body contained within the editor
       
    55                     "margin:4px; font:10pt Arial,Verdana; cursor:text"
       
    56     },
       
    57 
       
    58     // Define all usable toolbar buttons - the init string property is 
       
    59     //   expanded during initialization back into the buttons object and 
       
    60     //   seperate object properties are created for each button.
       
    61     //   e.g. buttons.size.title = "Font Size"
       
    62     buttons: {
       
    63       // name,title,command,popupName (""=use name)
       
    64       init:
       
    65       "bold,,|" +
       
    66       "italic,,|" +
       
    67       "underline,,|" +
       
    68       "strikethrough,,|" +
       
    69       "subscript,,|" +
       
    70       "superscript,,|" +
       
    71       "font,,fontname,|" +
       
    72       "size,Font Size,fontsize,|" +
       
    73       "style,,formatblock,|" +
       
    74       "color,Font Color,forecolor,|" +
       
    75       "highlight,Text Highlight Color,hilitecolor,color|" +
       
    76       "removeformat,Remove Formatting,|" +
       
    77       "bullets,,insertunorderedlist|" +
       
    78       "numbering,,insertorderedlist|" +
       
    79       "outdent,,|" +
       
    80       "indent,,|" +
       
    81       "alignleft,Align Text Left,justifyleft|" +
       
    82       "center,,justifycenter|" +
       
    83       "alignright,Align Text Right,justifyright|" +
       
    84       "justify,,justifyfull|" +
       
    85       "undo,,|" +
       
    86       "redo,,|" +
       
    87       "rule,Insert Horizontal Rule,inserthorizontalrule|" +
       
    88       "image,Insert Image,insertimage,url|" +
       
    89       "link,Insert Hyperlink,createlink,url|" +
       
    90       "unlink,Remove Hyperlink,|" +
       
    91       "cut,,|" +
       
    92       "copy,,|" +
       
    93       "paste,,|" +
       
    94       "pastetext,Paste as Text,inserthtml,|" +
       
    95       "print,,|" +
       
    96       "source,Show Source"
       
    97     },
       
    98 
       
    99     // imagesPath - returns the path to the images folder
       
   100     imagesPath: function() { return imagesPath(); }
       
   101 
       
   102   };
       
   103 
       
   104   // cleditor - creates a new editor for each of the matched textareas
       
   105   $.fn.cleditor = function(options) {
       
   106 
       
   107     // Create a new jQuery object to hold the results
       
   108     var $result = $([]);
       
   109 
       
   110     // Loop through all matching textareas and create the editors
       
   111     this.each(function(idx, elem) {
       
   112       if (elem.tagName == "TEXTAREA") {
       
   113         var data = $.data(elem, CLEDITOR);
       
   114         if (!data) data = new cleditor(elem, options);
       
   115         $result = $result.add(data);
       
   116       }
       
   117     });
       
   118 
       
   119     // return the new jQuery object
       
   120     return $result;
       
   121 
       
   122   };
       
   123     
       
   124   //==================
       
   125   // Private Variables
       
   126   //==================
       
   127 
       
   128   var
       
   129 
       
   130   // Misc constants
       
   131   BACKGROUND_COLOR = "backgroundColor",
       
   132   BUTTON           = "button",
       
   133   BUTTON_NAME      = "buttonName",
       
   134   CHANGE           = "change",
       
   135   CLEDITOR         = "cleditor",
       
   136   CLICK            = "click",
       
   137   DISABLED         = "disabled",
       
   138   DIV_TAG          = "<div>",
       
   139   TRANSPARENT      = "transparent",
       
   140   UNSELECTABLE     = "unselectable",
       
   141 
       
   142   // Class name constants
       
   143   MAIN_CLASS       = "cleditorMain",    // main containing div
       
   144   TOOLBAR_CLASS    = "cleditorToolbar", // toolbar div inside main div
       
   145   GROUP_CLASS      = "cleditorGroup",   // group divs inside the toolbar div
       
   146   BUTTON_CLASS     = "cleditorButton",  // button divs inside group div
       
   147   DISABLED_CLASS   = "cleditorDisabled",// disabled button divs
       
   148   DIVIDER_CLASS    = "cleditorDivider", // divider divs inside group div
       
   149   POPUP_CLASS      = "cleditorPopup",   // popup divs inside body
       
   150   LIST_CLASS       = "cleditorList",    // list popup divs inside body
       
   151   COLOR_CLASS      = "cleditorColor",   // color popup div inside body
       
   152   PROMPT_CLASS     = "cleditorPrompt",  // prompt popup divs inside body
       
   153   MSG_CLASS        = "cleditorMsg",     // message popup div inside body
       
   154 
       
   155   // Test for ie
       
   156   ie = $.browser.msie,
       
   157   ie6 = /msie\s6/i.test(navigator.userAgent),
       
   158 
       
   159   // Test for iPhone/iTouch/iPad
       
   160   iOS = /iphone|ipad|ipod/i.test(navigator.userAgent),
       
   161 
       
   162   // Popups are created once as needed and shared by all editor instances
       
   163   popups = {},
       
   164 
       
   165   // Used to prevent the document click event from being bound more than once
       
   166   documentClickAssigned,
       
   167 
       
   168   // Local copy of the buttons object
       
   169   buttons = $.cleditor.buttons;
       
   170 
       
   171   //===============
       
   172   // Initialization
       
   173   //===============
       
   174 
       
   175   // Expand the buttons.init string back into the buttons object
       
   176   //   and create seperate object properties for each button.
       
   177   //   e.g. buttons.size.title = "Font Size"
       
   178   $.each(buttons.init.split("|"), function(idx, button) {
       
   179     var items = button.split(","), name = items[0];
       
   180     buttons[name] = {
       
   181       stripIndex: idx,
       
   182       name: name,
       
   183       title: items[1] === "" ? name.charAt(0).toUpperCase() + name.substr(1) : items[1],
       
   184       command: items[2] === "" ? name : items[2],
       
   185       popupName: items[3] === "" ? name : items[3]
       
   186     };
       
   187   });
       
   188   delete buttons.init;
       
   189 
       
   190   //============
       
   191   // Constructor
       
   192   //============
       
   193 
       
   194   // cleditor - creates a new editor for the passed in textarea element
       
   195   cleditor = function(area, options) {
       
   196 
       
   197     var editor = this;
       
   198 
       
   199     // Get the defaults and override with options
       
   200     editor.options = options = $.extend({}, $.cleditor.defaultOptions, options);
       
   201 
       
   202     // Hide the textarea and associate it with this editor
       
   203     var $area = editor.$area = $(area)
       
   204       .hide()
       
   205       .data(CLEDITOR, editor)
       
   206       .blur(function() {
       
   207         // Update the iframe when the textarea loses focus
       
   208         updateFrame(editor, true);
       
   209       });
       
   210 
       
   211     // Create the main container and append the textarea
       
   212     var $main = editor.$main = $(DIV_TAG)
       
   213       .addClass(MAIN_CLASS)
       
   214       .width(options.width)
       
   215       .height(options.height);
       
   216 
       
   217     // Create the toolbar
       
   218     var $toolbar = editor.$toolbar = $(DIV_TAG)
       
   219       .addClass(TOOLBAR_CLASS)
       
   220       .appendTo($main);
       
   221 
       
   222     // Add the first group to the toolbar
       
   223     var $group = $(DIV_TAG)
       
   224       .addClass(GROUP_CLASS)
       
   225       .appendTo($toolbar);
       
   226     
       
   227     // Add the buttons to the toolbar
       
   228     $.each(options.controls.split(" "), function(idx, buttonName) {
       
   229       if (buttonName === "") return true;
       
   230 
       
   231       // Divider
       
   232       if (buttonName == "|") {
       
   233 
       
   234         // Add a new divider to the group
       
   235         var $div = $(DIV_TAG)
       
   236           .addClass(DIVIDER_CLASS)
       
   237           .appendTo($group);
       
   238 
       
   239         // Create a new group
       
   240         $group = $(DIV_TAG)
       
   241           .addClass(GROUP_CLASS)
       
   242           .appendTo($toolbar);
       
   243 
       
   244       }
       
   245 
       
   246       // Button
       
   247       else {
       
   248         
       
   249         // Get the button definition
       
   250         var button = buttons[buttonName];
       
   251 
       
   252         // Add a new button to the group
       
   253         var $buttonDiv = $(DIV_TAG)
       
   254           .data(BUTTON_NAME, button.name)
       
   255           .addClass(BUTTON_CLASS)
       
   256           .attr("title", button.title)
       
   257           .bind(CLICK, $.proxy(buttonClick, editor))
       
   258           .appendTo($group)
       
   259           .hover(hoverEnter, hoverLeave);
       
   260 
       
   261         // Prepare the button image
       
   262         var map = {};
       
   263         if (button.css) map = button.css;
       
   264         else if (button.image) map.backgroundImage = imageUrl(button.image);
       
   265         if (button.stripIndex) map.backgroundPosition = button.stripIndex * -24;
       
   266         $buttonDiv.css(map);
       
   267 
       
   268         // Add the unselectable attribute for ie
       
   269         if (ie)
       
   270           $buttonDiv.attr(UNSELECTABLE, "on");
       
   271 
       
   272         // Create the popup
       
   273         if (button.popupName)
       
   274           createPopup(button.popupName, options, button.popupClass,
       
   275             button.popupContent, button.popupHover);
       
   276         
       
   277       }
       
   278 
       
   279     });
       
   280 
       
   281     // Add the main div to the DOM and append the textarea
       
   282     $main.insertBefore($area)
       
   283       .append($area);
       
   284 
       
   285     // Bind the document click event handler
       
   286     if (!documentClickAssigned) {
       
   287       $(document).click(function(e) {
       
   288         // Dismiss all non-prompt popups
       
   289         var $target = $(e.target);
       
   290         if (!$target.add($target.parents()).is("." + PROMPT_CLASS))
       
   291           hidePopups();
       
   292       });
       
   293       documentClickAssigned = true;
       
   294     }
       
   295 
       
   296     // Bind the window resize event when the width or height is auto or %
       
   297     if (/auto|%/.test("" + options.width + options.height))
       
   298       $(window).resize(function() {refresh(editor);});
       
   299 
       
   300     // Create the iframe and resize the controls
       
   301     refresh(editor);
       
   302 
       
   303   };
       
   304 
       
   305   //===============
       
   306   // Public Methods
       
   307   //===============
       
   308 
       
   309   var fn = cleditor.prototype,
       
   310 
       
   311   // Expose the following private functions as methods on the cleditor object.
       
   312   // The closure compiler will rename the private functions. However, the
       
   313   // exposed method names on the cleditor object will remain fixed.
       
   314   methods = [
       
   315     ["clear", clear],
       
   316     ["disable", disable],
       
   317     ["execCommand", execCommand],
       
   318     ["focus", focus],
       
   319     ["hidePopups", hidePopups],
       
   320     ["sourceMode", sourceMode, true],
       
   321     ["refresh", refresh],
       
   322     ["select", select],
       
   323     ["selectedHTML", selectedHTML, true],
       
   324     ["selectedText", selectedText, true],
       
   325     ["showMessage", showMessage],
       
   326     ["updateFrame", updateFrame],
       
   327     ["updateTextArea", updateTextArea]
       
   328   ];
       
   329 
       
   330   $.each(methods, function(idx, method) {
       
   331     fn[method[0]] = function() {
       
   332       var editor = this, args = [editor];
       
   333       // using each here would cast booleans into objects!
       
   334       for(var x = 0; x < arguments.length; x++) {args.push(arguments[x]);}
       
   335       var result = method[1].apply(editor, args);
       
   336       if (method[2]) return result;
       
   337       return editor;
       
   338     };
       
   339   });
       
   340 
       
   341   // change - shortcut for .bind("change", handler) or .trigger("change")
       
   342   fn.change = function(handler) {
       
   343     var $this = $(this);
       
   344     return handler ? $this.bind(CHANGE, handler) : $this.trigger(CHANGE);
       
   345   };
       
   346 
       
   347   //===============
       
   348   // Event Handlers
       
   349   //===============
       
   350 
       
   351   // buttonClick - click event handler for toolbar buttons
       
   352   function buttonClick(e) {
       
   353 
       
   354     var editor = this,
       
   355         buttonDiv = e.target,
       
   356         buttonName = $.data(buttonDiv, BUTTON_NAME),
       
   357         button = buttons[buttonName],
       
   358         popupName = button.popupName,
       
   359         popup = popups[popupName];
       
   360 
       
   361     // Check if disabled
       
   362     if (editor.disabled || $(buttonDiv).attr(DISABLED) == DISABLED)
       
   363       return;
       
   364 
       
   365     // Fire the buttonClick event
       
   366     var data = {
       
   367       editor: editor,
       
   368       button: buttonDiv,
       
   369       buttonName: buttonName,
       
   370       popup: popup,
       
   371       popupName: popupName,
       
   372       command: button.command,
       
   373       useCSS: editor.options.useCSS
       
   374     };
       
   375 
       
   376     if (button.buttonClick && button.buttonClick(e, data) === false)
       
   377       return false;
       
   378 
       
   379     // Toggle source
       
   380     if (buttonName == "source") {
       
   381 
       
   382       // Show the iframe
       
   383       if (sourceMode(editor)) {
       
   384         delete editor.range;
       
   385         editor.$area.hide();
       
   386         editor.$frame.show();
       
   387         buttonDiv.title = button.title;
       
   388       }
       
   389 
       
   390       // Show the textarea
       
   391       else {
       
   392         editor.$frame.hide();
       
   393         editor.$area.show();
       
   394         buttonDiv.title = "Show Rich Text";
       
   395       }
       
   396 
       
   397       // Enable or disable the toolbar buttons
       
   398       // IE requires the timeout
       
   399       setTimeout(function() {refreshButtons(editor);}, 100);
       
   400 
       
   401     }
       
   402 
       
   403     // Check for rich text mode
       
   404     else if (!sourceMode(editor)) {
       
   405 
       
   406       // Handle popups
       
   407       if (popupName) {
       
   408         var $popup = $(popup);
       
   409 
       
   410         // URL
       
   411         if (popupName == "url") {
       
   412 
       
   413           // Check for selection before showing the link url popup
       
   414           if (buttonName == "link" && selectedText(editor) === "") {
       
   415             showMessage(editor, "A selection is required when inserting a link.", buttonDiv);
       
   416             return false;
       
   417           }
       
   418 
       
   419           // Wire up the submit button click event handler
       
   420           $popup.children(":button")
       
   421             .unbind(CLICK)
       
   422             .bind(CLICK, function() {
       
   423 
       
   424               // Insert the image or link if a url was entered
       
   425               var $text = $popup.find(":text"),
       
   426                 url = $.trim($text.val());
       
   427               if (url !== "")
       
   428                 execCommand(editor, data.command, url, null, data.button);
       
   429 
       
   430               // Reset the text, hide the popup and set focus
       
   431               $text.val("http://");
       
   432               hidePopups();
       
   433               focus(editor);
       
   434 
       
   435             });
       
   436 
       
   437         }
       
   438 
       
   439         // Paste as Text
       
   440         else if (popupName == "pastetext") {
       
   441 
       
   442           // Wire up the submit button click event handler
       
   443           $popup.children(":button")
       
   444             .unbind(CLICK)
       
   445             .bind(CLICK, function() {
       
   446 
       
   447               // Insert the unformatted text replacing new lines with break tags
       
   448               var $textarea = $popup.find("textarea"),
       
   449                 text = $textarea.val().replace(/\n/g, "<br />");
       
   450               if (text !== "")
       
   451                 execCommand(editor, data.command, text, null, data.button);
       
   452 
       
   453               // Reset the text, hide the popup and set focus
       
   454               $textarea.val("");
       
   455               hidePopups();
       
   456               focus(editor);
       
   457 
       
   458             });
       
   459 
       
   460         }
       
   461 
       
   462         // Show the popup if not already showing for this button
       
   463         if (buttonDiv !== $.data(popup, BUTTON)) {
       
   464           showPopup(editor, popup, buttonDiv);
       
   465           return false; // stop propagination to document click
       
   466         }
       
   467 
       
   468         // propaginate to documnt click
       
   469         return;
       
   470 
       
   471       }
       
   472 
       
   473       // Print
       
   474       else if (buttonName == "print")
       
   475         editor.$frame[0].contentWindow.print();
       
   476 
       
   477       // All other buttons
       
   478       else if (!execCommand(editor, data.command, data.value, data.useCSS, buttonDiv))
       
   479         return false;
       
   480 
       
   481     }
       
   482 
       
   483     // Focus the editor
       
   484     focus(editor);
       
   485 
       
   486   }
       
   487 
       
   488   // hoverEnter - mouseenter event handler for buttons and popup items
       
   489   function hoverEnter(e) {
       
   490     var $div = $(e.target).closest("div");
       
   491     $div.css(BACKGROUND_COLOR, $div.data(BUTTON_NAME) ? "#FFF" : "#FFC");
       
   492   }
       
   493 
       
   494   // hoverLeave - mouseleave event handler for buttons and popup items
       
   495   function hoverLeave(e) {
       
   496     $(e.target).closest("div").css(BACKGROUND_COLOR, "transparent");
       
   497   }
       
   498 
       
   499   // popupClick - click event handler for popup items
       
   500   function popupClick(e) {
       
   501 
       
   502     var editor = this,
       
   503         popup = e.data.popup,
       
   504         target = e.target;
       
   505 
       
   506     // Check for message and prompt popups
       
   507     if (popup === popups.msg || $(popup).hasClass(PROMPT_CLASS))
       
   508       return;
       
   509 
       
   510     // Get the button info
       
   511     var buttonDiv = $.data(popup, BUTTON),
       
   512         buttonName = $.data(buttonDiv, BUTTON_NAME),
       
   513         button = buttons[buttonName],
       
   514         command = button.command,
       
   515         value,
       
   516         useCSS = editor.options.useCSS;
       
   517 
       
   518     // Get the command value
       
   519     if (buttonName == "font")
       
   520       // Opera returns the fontfamily wrapped in quotes
       
   521       value = target.style.fontFamily.replace(/"/g, "");
       
   522     else if (buttonName == "size") {
       
   523       if (target.tagName == "DIV")
       
   524         target = target.children[0];
       
   525       value = target.innerHTML;
       
   526     }
       
   527     else if (buttonName == "style")
       
   528       value = "<" + target.tagName + ">";
       
   529     else if (buttonName == "color")
       
   530       value = hex(target.style.backgroundColor);
       
   531     else if (buttonName == "highlight") {
       
   532       value = hex(target.style.backgroundColor);
       
   533       if (ie) command = 'backcolor';
       
   534       else useCSS = true;
       
   535     }
       
   536 
       
   537     // Fire the popupClick event
       
   538     var data = {
       
   539       editor: editor,
       
   540       button: buttonDiv,
       
   541       buttonName: buttonName,
       
   542       popup: popup,
       
   543       popupName: button.popupName,
       
   544       command: command,
       
   545       value: value,
       
   546       useCSS: useCSS
       
   547     };
       
   548 
       
   549     if (button.popupClick && button.popupClick(e, data) === false)
       
   550       return;
       
   551 
       
   552     // Execute the command
       
   553     if (data.command && !execCommand(editor, data.command, data.value, data.useCSS, buttonDiv))
       
   554       return false;
       
   555 
       
   556     // Hide the popup and focus the editor
       
   557     hidePopups();
       
   558     focus(editor);
       
   559 
       
   560   }
       
   561 
       
   562   //==================
       
   563   // Private Functions
       
   564   //==================
       
   565 
       
   566   // checksum - returns a checksum using the Adler-32 method
       
   567   function checksum(text)
       
   568   {
       
   569     var a = 1, b = 0;
       
   570     for (var index = 0; index < text.length; ++index) {
       
   571       a = (a + text.charCodeAt(index)) % 65521;
       
   572       b = (b + a) % 65521;
       
   573     }
       
   574     return (b << 16) | a;
       
   575   }
       
   576 
       
   577   // clear - clears the contents of the editor
       
   578   function clear(editor) {
       
   579     editor.$area.val("");
       
   580     updateFrame(editor);
       
   581   }
       
   582 
       
   583   // createPopup - creates a popup and adds it to the body
       
   584   function createPopup(popupName, options, popupTypeClass, popupContent, popupHover) {
       
   585 
       
   586     // Check if popup already exists
       
   587     if (popups[popupName])
       
   588       return popups[popupName];
       
   589 
       
   590     // Create the popup
       
   591     var $popup = $(DIV_TAG)
       
   592       .hide()
       
   593       .addClass(POPUP_CLASS)
       
   594       .appendTo("body");
       
   595 
       
   596     // Add the content
       
   597 
       
   598     // Custom popup
       
   599     if (popupContent)
       
   600       $popup.html(popupContent);
       
   601 
       
   602     // Color
       
   603     else if (popupName == "color") {
       
   604       var colors = options.colors.split(" ");
       
   605       if (colors.length < 10)
       
   606         $popup.width("auto");
       
   607       $.each(colors, function(idx, color) {
       
   608         $(DIV_TAG).appendTo($popup)
       
   609           .css(BACKGROUND_COLOR, "#" + color);
       
   610       });
       
   611       popupTypeClass = COLOR_CLASS;
       
   612     }
       
   613 
       
   614     // Font
       
   615     else if (popupName == "font")
       
   616       $.each(options.fonts.split(","), function(idx, font) {
       
   617         $(DIV_TAG).appendTo($popup)
       
   618           .css("fontFamily", font)
       
   619           .html(font);
       
   620       });
       
   621 
       
   622     // Size
       
   623     else if (popupName == "size")
       
   624       $.each(options.sizes.split(","), function(idx, size) {
       
   625         $(DIV_TAG).appendTo($popup)
       
   626           .html("<font size=" + size + ">" + size + "</font>");
       
   627       });
       
   628 
       
   629     // Style
       
   630     else if (popupName == "style")
       
   631       $.each(options.styles, function(idx, style) {
       
   632         $(DIV_TAG).appendTo($popup)
       
   633           .html(style[1] + style[0] + style[1].replace("<", "</"));
       
   634       });
       
   635 
       
   636     // URL
       
   637     else if (popupName == "url") {
       
   638       $popup.html('Enter URL:<br><input type=text value="http://" size=35><br><input type=button value="Submit">');
       
   639       popupTypeClass = PROMPT_CLASS;
       
   640     }
       
   641 
       
   642     // Paste as Text
       
   643     else if (popupName == "pastetext") {
       
   644       $popup.html('Paste your content here and click submit.<br /><textarea cols=40 rows=3></textarea><br /><input type=button value=Submit>');
       
   645       popupTypeClass = PROMPT_CLASS;
       
   646     }
       
   647 
       
   648     // Add the popup type class name
       
   649     if (!popupTypeClass && !popupContent)
       
   650       popupTypeClass = LIST_CLASS;
       
   651     $popup.addClass(popupTypeClass);
       
   652 
       
   653     // Add the unselectable attribute to all items
       
   654     if (ie) {
       
   655       $popup.attr(UNSELECTABLE, "on")
       
   656         .find("div,font,p,h1,h2,h3,h4,h5,h6")
       
   657         .attr(UNSELECTABLE, "on");
       
   658     }
       
   659 
       
   660     // Add the hover effect to all items
       
   661     if ($popup.hasClass(LIST_CLASS) || popupHover === true)
       
   662       $popup.children().hover(hoverEnter, hoverLeave);
       
   663 
       
   664     // Add the popup to the array and return it
       
   665     popups[popupName] = $popup[0];
       
   666     return $popup[0];
       
   667 
       
   668   }
       
   669 
       
   670   // disable - enables or disables the editor
       
   671   function disable(editor, disabled) {
       
   672 
       
   673     // Update the textarea and save the state
       
   674     if (disabled) {
       
   675       editor.$area.attr(DISABLED, DISABLED);
       
   676       editor.disabled = true;
       
   677     }
       
   678     else {
       
   679       editor.$area.removeAttr(DISABLED);
       
   680       delete editor.disabled;
       
   681     }
       
   682 
       
   683     // Switch the iframe into design mode.
       
   684     // ie6 does not support designMode.
       
   685     // ie7 & ie8 do not properly support designMode="off".
       
   686     try {
       
   687       if (ie) editor.doc.body.contentEditable = !disabled;
       
   688       else editor.doc.designMode = !disabled ? "on" : "off";
       
   689     }
       
   690     // Firefox 1.5 throws an exception that can be ignored
       
   691     // when toggling designMode from off to on.
       
   692     catch (err) {}
       
   693 
       
   694     // Enable or disable the toolbar buttons
       
   695     refreshButtons(editor);
       
   696 
       
   697   }
       
   698 
       
   699   // execCommand - executes a designMode command
       
   700   function execCommand(editor, command, value, useCSS, button) {
       
   701 
       
   702     // Restore the current ie selection
       
   703     restoreRange(editor);
       
   704 
       
   705     // Set the styling method
       
   706     if (!ie) {
       
   707       if (useCSS === undefined || useCSS === null)
       
   708         useCSS = editor.options.useCSS;
       
   709       editor.doc.execCommand("styleWithCSS", 0, useCSS.toString());
       
   710     }
       
   711 
       
   712     // Execute the command and check for error
       
   713     var success = true, description;
       
   714     if (ie && command.toLowerCase() == "inserthtml")
       
   715       getRange(editor).pasteHTML(value);
       
   716     else {
       
   717       try { success = editor.doc.execCommand(command, 0, value || null); }
       
   718       catch (err) { description = err.description; success = false; }
       
   719       if (!success) {
       
   720         if ("cutcopypaste".indexOf(command) > -1)
       
   721           showMessage(editor, "For security reasons, your browser does not support the " +
       
   722             command + " command. Try using the keyboard shortcut or context menu instead.",
       
   723             button);
       
   724         else
       
   725           showMessage(editor,
       
   726             (description ? description : "Error executing the " + command + " command."),
       
   727             button);
       
   728       }
       
   729     }
       
   730 
       
   731     // Enable the buttons
       
   732     refreshButtons(editor);
       
   733     return success;
       
   734 
       
   735   }
       
   736 
       
   737   // focus - sets focus to either the textarea or iframe
       
   738   function focus(editor) {
       
   739     setTimeout(function() {
       
   740       if (sourceMode(editor)) editor.$area.focus();
       
   741       else editor.$frame[0].contentWindow.focus();
       
   742       refreshButtons(editor);
       
   743     }, 0);
       
   744   }
       
   745 
       
   746   // getRange - gets the current text range object
       
   747   function getRange(editor) {
       
   748     if (ie) return getSelection(editor).createRange();
       
   749     return getSelection(editor).getRangeAt(0);
       
   750   }
       
   751 
       
   752   // getSelection - gets the current text range object
       
   753   function getSelection(editor) {
       
   754     if (ie) return editor.doc.selection;
       
   755     return editor.$frame[0].contentWindow.getSelection();
       
   756   }
       
   757 
       
   758   // Returns the hex value for the passed in string.
       
   759   //   hex("rgb(255, 0, 0)"); // #FF0000
       
   760   //   hex("#FF0000"); // #FF0000
       
   761   //   hex("#F00"); // #FF0000
       
   762   function hex(s) {
       
   763     var m = /rgba?\((\d+), (\d+), (\d+)/.exec(s),
       
   764       c = s.split("");
       
   765     if (m) {
       
   766       s = ( m[1] << 16 | m[2] << 8 | m[3] ).toString(16);
       
   767       while (s.length < 6)
       
   768         s = "0" + s;
       
   769     }
       
   770     return "#" + (s.length == 6 ? s : c[1] + c[1] + c[2] + c[2] + c[3] + c[3]);
       
   771   }
       
   772 
       
   773   // hidePopups - hides all popups
       
   774   function hidePopups() {
       
   775     $.each(popups, function(idx, popup) {
       
   776       $(popup)
       
   777         .hide()
       
   778         .unbind(CLICK)
       
   779         .removeData(BUTTON);
       
   780     });
       
   781   }
       
   782 
       
   783   // imagesPath - returns the path to the images folder
       
   784   function imagesPath() {
       
   785     var cssFile = "jquery.cleditor.css",
       
   786         href = $("link[href$='" + cssFile +"']").attr("href");
       
   787     return href.substr(0, href.length - cssFile.length) + "images/";
       
   788   }
       
   789 
       
   790   // imageUrl - Returns the css url string for a filemane
       
   791   function imageUrl(filename) {
       
   792     return "url(" + imagesPath() + filename + ")";
       
   793   }
       
   794 
       
   795   // refresh - creates the iframe and resizes the controls
       
   796   function refresh(editor) {
       
   797 
       
   798     var $main = editor.$main,
       
   799       options = editor.options;
       
   800 
       
   801     // Remove the old iframe
       
   802     if (editor.$frame) 
       
   803       editor.$frame.remove();
       
   804 
       
   805     // Create a new iframe
       
   806     var $frame = editor.$frame = $('<iframe frameborder="0" src="javascript:true;">')
       
   807       .hide()
       
   808       .appendTo($main);
       
   809 
       
   810     // Load the iframe document content
       
   811     var contentWindow = $frame[0].contentWindow,
       
   812       doc = editor.doc = contentWindow.document,
       
   813       $doc = $(doc);
       
   814 
       
   815     doc.open();
       
   816     doc.write(
       
   817       options.docType +
       
   818       '<html>' +
       
   819       ((options.docCSSFile === '') ? '' : '<head><link rel="stylesheet" type="text/css" href="' + options.docCSSFile + '" /></head>') +
       
   820       '<body style="' + options.bodyStyle + '"></body></html>'
       
   821     );
       
   822     doc.close();
       
   823 
       
   824     // Work around for bug in IE which causes the editor to lose
       
   825     // focus when clicking below the end of the document.
       
   826     if (ie)
       
   827       $doc.click(function() {focus(editor);});
       
   828 
       
   829     // Load the content
       
   830     updateFrame(editor);
       
   831 
       
   832     // Bind the ie specific iframe event handlers
       
   833     if (ie) {
       
   834 
       
   835       // Save the current user selection. This code is needed since IE will
       
   836       // reset the selection just after the beforedeactivate event and just
       
   837       // before the beforeactivate event.
       
   838       $doc.bind("beforedeactivate beforeactivate selectionchange keypress", function(e) {
       
   839         
       
   840         // Flag the editor as inactive
       
   841         if (e.type == "beforedeactivate")
       
   842           editor.inactive = true;
       
   843         
       
   844         // Get rid of the bogus selection and flag the editor as active
       
   845         else if (e.type == "beforeactivate") {
       
   846           if (!editor.inactive && editor.range && editor.range.length > 1)
       
   847             editor.range.shift();
       
   848           delete editor.inactive;
       
   849         }
       
   850 
       
   851         // Save the selection when the editor is active
       
   852         else if (!editor.inactive) {
       
   853           if (!editor.range) 
       
   854             editor.range = [];
       
   855           editor.range.unshift(getRange(editor));
       
   856 
       
   857           // We only need the last 2 selections
       
   858           while (editor.range.length > 2)
       
   859             editor.range.pop();
       
   860         }
       
   861 
       
   862       });
       
   863 
       
   864       // Restore the text range when the iframe gains focus
       
   865       $frame.focus(function() {
       
   866         restoreRange(editor);
       
   867       });
       
   868 
       
   869     }
       
   870 
       
   871     // Update the textarea when the iframe loses focus
       
   872     ($.browser.mozilla ? $doc : $(contentWindow)).blur(function() {
       
   873       updateTextArea(editor, true);
       
   874     });
       
   875 
       
   876     // Enable the toolbar buttons as the user types or clicks
       
   877     $doc.click(hidePopups)
       
   878       .bind("keyup mouseup", function() {
       
   879         refreshButtons(editor);
       
   880       });
       
   881 
       
   882     // Show the textarea for iPhone/iTouch/iPad or
       
   883     // the iframe when design mode is supported.
       
   884     if (iOS) editor.$area.show();
       
   885     else $frame.show();
       
   886 
       
   887     // Wait for the layout to finish - shortcut for $(document).ready()
       
   888     $(function() {
       
   889 
       
   890       var $toolbar = editor.$toolbar,
       
   891           $group = $toolbar.children("div:last"),
       
   892           wid = $main.width();
       
   893 
       
   894       // Resize the toolbar
       
   895       var hgt = $group.offset().top + $group.outerHeight() - $toolbar.offset().top + 1;
       
   896       $toolbar.height(hgt);
       
   897 
       
   898       // Resize the iframe
       
   899       hgt = (/%/.test("" + options.height) ? $main.height() : parseInt(options.height)) - hgt;
       
   900       $frame.width(wid).height(hgt);
       
   901 
       
   902       // Resize the textarea. IE6 textareas have a 1px top
       
   903       // & bottom margin that cannot be removed using css.
       
   904       editor.$area.width(wid).height(ie6 ? hgt - 2 : hgt);
       
   905 
       
   906       // Switch the iframe into design mode if enabled
       
   907       disable(editor, editor.disabled);
       
   908 
       
   909       // Enable or disable the toolbar buttons
       
   910       refreshButtons(editor);
       
   911 
       
   912     });
       
   913 
       
   914   }
       
   915 
       
   916   // refreshButtons - enables or disables buttons based on availability
       
   917   function refreshButtons(editor) {
       
   918 
       
   919     // Webkit requires focus before queryCommandEnabled will return anything but false
       
   920     if (!iOS && $.browser.webkit && !editor.focused) {
       
   921       editor.$frame[0].contentWindow.focus();
       
   922       window.focus();
       
   923       editor.focused = true;
       
   924     }
       
   925 
       
   926     // Get the object used for checking queryCommandEnabled
       
   927     var queryObj = editor.doc;
       
   928     if (ie) queryObj = getRange(editor);
       
   929 
       
   930     // Loop through each button
       
   931     var inSourceMode = sourceMode(editor);
       
   932     $.each(editor.$toolbar.find("." + BUTTON_CLASS), function(idx, elem) {
       
   933 
       
   934       var $elem = $(elem),
       
   935         button = $.cleditor.buttons[$.data(elem, BUTTON_NAME)],
       
   936         command = button.command,
       
   937         enabled = true;
       
   938 
       
   939       // Determine the state
       
   940       if (editor.disabled)
       
   941         enabled = false;
       
   942       else if (button.getEnabled) {
       
   943         var data = {
       
   944           editor: editor,
       
   945           button: elem,
       
   946           buttonName: button.name,
       
   947           popup: popups[button.popupName],
       
   948           popupName: button.popupName,
       
   949           command: button.command,
       
   950           useCSS: editor.options.useCSS
       
   951         };
       
   952         enabled = button.getEnabled(data);
       
   953         if (enabled === undefined)
       
   954           enabled = true;
       
   955       }
       
   956       else if (((inSourceMode || iOS) && button.name != "source") ||
       
   957       (ie && (command == "undo" || command == "redo")))
       
   958         enabled = false;
       
   959       else if (command && command != "print") {
       
   960         if (ie && command == "hilitecolor")
       
   961           command = "backcolor";
       
   962         // IE does not support inserthtml, so it's always enabled
       
   963         if (!ie || command != "inserthtml") {
       
   964           try {enabled = queryObj.queryCommandEnabled(command);}
       
   965           catch (err) {enabled = false;}
       
   966         }
       
   967       }
       
   968 
       
   969       // Enable or disable the button
       
   970       if (enabled) {
       
   971         $elem.removeClass(DISABLED_CLASS);
       
   972         $elem.removeAttr(DISABLED);
       
   973       }
       
   974       else {
       
   975         $elem.addClass(DISABLED_CLASS);
       
   976         $elem.attr(DISABLED, DISABLED);
       
   977       }
       
   978 
       
   979     });
       
   980   }
       
   981 
       
   982   // restoreRange - restores the current ie selection
       
   983   function restoreRange(editor) {
       
   984     if (ie && editor.range)
       
   985       editor.range[0].select();
       
   986   }
       
   987 
       
   988   // select - selects all the text in either the textarea or iframe
       
   989   function select(editor) {
       
   990     setTimeout(function() {
       
   991       if (sourceMode(editor)) editor.$area.select();
       
   992       else execCommand(editor, "selectall");
       
   993     }, 0);
       
   994   }
       
   995 
       
   996   // selectedHTML - returns the current HTML selection or and empty string
       
   997   function selectedHTML(editor) {
       
   998     restoreRange(editor);
       
   999     var range = getRange(editor);
       
  1000     if (ie)
       
  1001       return range.htmlText;
       
  1002     var layer = $("<layer>")[0];
       
  1003     layer.appendChild(range.cloneContents());
       
  1004     var html = layer.innerHTML;
       
  1005     layer = null;
       
  1006     return html;
       
  1007   }
       
  1008 
       
  1009   // selectedText - returns the current text selection or and empty string
       
  1010   function selectedText(editor) {
       
  1011     restoreRange(editor);
       
  1012     if (ie) return getRange(editor).text;
       
  1013     return getSelection(editor).toString();
       
  1014   }
       
  1015 
       
  1016   // showMessage - alert replacement
       
  1017   function showMessage(editor, message, button) {
       
  1018     var popup = createPopup("msg", editor.options, MSG_CLASS);
       
  1019     popup.innerHTML = message;
       
  1020     showPopup(editor, popup, button);
       
  1021   }
       
  1022 
       
  1023   // showPopup - shows a popup
       
  1024   function showPopup(editor, popup, button) {
       
  1025 
       
  1026     var offset, left, top, $popup = $(popup);
       
  1027 
       
  1028     // Determine the popup location
       
  1029     if (button) {
       
  1030       var $button = $(button);
       
  1031       offset = $button.offset();
       
  1032       left = --offset.left;
       
  1033       top = offset.top + $button.height();
       
  1034     }
       
  1035     else {
       
  1036       var $toolbar = editor.$toolbar;
       
  1037       offset = $toolbar.offset();
       
  1038       left = Math.floor(($toolbar.width() - $popup.width()) / 2) + offset.left;
       
  1039       top = offset.top + $toolbar.height() - 2;
       
  1040     }
       
  1041 
       
  1042     // Position and show the popup
       
  1043     hidePopups();
       
  1044     $popup.css({left: left, top: top})
       
  1045       .show();
       
  1046 
       
  1047     // Assign the popup button and click event handler
       
  1048     if (button) {
       
  1049       $.data(popup, BUTTON, button);
       
  1050       $popup.bind(CLICK, {popup: popup}, $.proxy(popupClick, editor));
       
  1051     }
       
  1052 
       
  1053     // Focus the first input element if any
       
  1054     setTimeout(function() {
       
  1055       $popup.find(":text,textarea").eq(0).focus().select();
       
  1056     }, 100);
       
  1057 
       
  1058   }
       
  1059 
       
  1060   // sourceMode - returns true if the textarea is showing
       
  1061   function sourceMode(editor) {
       
  1062     return editor.$area.is(":visible");
       
  1063   }
       
  1064 
       
  1065   // updateFrame - updates the iframe with the textarea contents
       
  1066   function updateFrame(editor, checkForChange) {
       
  1067     
       
  1068     var code = editor.$area.val(),
       
  1069       options = editor.options,
       
  1070       updateFrameCallback = options.updateFrame,
       
  1071       $body = $(editor.doc.body);
       
  1072 
       
  1073     // Check for textarea change to avoid unnecessary firing
       
  1074     // of potentially heavy updateFrame callbacks.
       
  1075     if (updateFrameCallback) {
       
  1076       var sum = checksum(code);
       
  1077       if (checkForChange && editor.areaChecksum == sum)
       
  1078         return;
       
  1079       editor.areaChecksum = sum;
       
  1080     }
       
  1081 
       
  1082     // Convert the textarea source code into iframe html
       
  1083     var html = updateFrameCallback ? updateFrameCallback(code) : code;
       
  1084 
       
  1085     // Prevent script injection attacks by html encoding script tags
       
  1086     html = html.replace(/<(?=\/?script)/ig, "&lt;");
       
  1087 
       
  1088     // Update the iframe checksum
       
  1089     if (options.updateTextArea)
       
  1090       editor.frameChecksum = checksum(html);
       
  1091 
       
  1092     // Update the iframe and trigger the change event
       
  1093     if (html != $body.html()) {
       
  1094       $body.html(html);
       
  1095       $(editor).triggerHandler(CHANGE);
       
  1096     }
       
  1097 
       
  1098   }
       
  1099 
       
  1100   // updateTextArea - updates the textarea with the iframe contents
       
  1101   function updateTextArea(editor, checkForChange) {
       
  1102 
       
  1103     var html = $(editor.doc.body).html(),
       
  1104       options = editor.options,
       
  1105       updateTextAreaCallback = options.updateTextArea,
       
  1106       $area = editor.$area;
       
  1107 
       
  1108     // Check for iframe change to avoid unnecessary firing
       
  1109     // of potentially heavy updateTextArea callbacks.
       
  1110     if (updateTextAreaCallback) {
       
  1111       var sum = checksum(html);
       
  1112       if (checkForChange && editor.frameChecksum == sum)
       
  1113         return;
       
  1114       editor.frameChecksum = sum;
       
  1115     }
       
  1116 
       
  1117     // Convert the iframe html into textarea source code
       
  1118     var code = updateTextAreaCallback ? updateTextAreaCallback(html) : html;
       
  1119 
       
  1120     // Update the textarea checksum
       
  1121     if (options.updateFrame)
       
  1122       editor.areaChecksum = checksum(code);
       
  1123 
       
  1124     // Update the textarea and trigger the change event
       
  1125     if (code != $area.val()) {
       
  1126       $area.val(code);
       
  1127       $(editor).triggerHandler(CHANGE);
       
  1128     }
       
  1129 
       
  1130   }
       
  1131 
       
  1132 })(jQuery);