Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

cleditor for content

  • Loading branch information...
commit dfa6988c8ffa80ee286bb3a0744e2b8920f347be 1 parent 9bcbb00
pory authored April 15, 2012
2  helpers/cms_helper.rb
@@ -30,7 +30,7 @@ def list options = {}
30 30
     document_id = options[:object]['_id']
31 31
     method = options[:object][item].blank? ? 'post' : 'put'    
32 32
     if authorized?
33  
-      "<img src='/images/icon_pencil.gif' class='edit-icon'><span id='text_item_#{item}' class='editable' data-key='#{item}' data-document_id='#{document_id}' data-method='#{method}'>#{value}</span>"
  33
+      "<img src='/images/icon_pencil.gif' class='edit-icon'><div id='text_item_#{item}' class='editable' data-key='#{item}' data-document_id='#{document_id}' data-method='#{method}'>#{value}</div>"
34 34
     else
35 35
       options[:object][item] unless options[:object][item].blank?
36 36
     end
BIN  public/cleditor/images/buttons.gif
BIN  public/cleditor/images/toolbar.gif
24  public/cleditor/jquery.cleditor.css
... ...
@@ -0,0 +1,24 @@
  1
+.cleditorMain {border:1px solid #999; padding:0 1px 1px; background-color:white}
  2
+.cleditorMain iframe {border:none; margin:0; padding:0}
  3
+.cleditorMain textarea {border:none; margin:0; padding:0; overflow-y:scroll; font:10pt Arial,Verdana; resize:none; outline:none /* webkit grip focus */}
  4
+.cleditorToolbar {background: url('images/toolbar.gif') repeat}
  5
+.cleditorGroup {float:left; height:26px}
  6
+.cleditorButton {float:left; width:24px; height:24px; margin:1px 0 1px 0; background: url('images/buttons.gif')}
  7
+.cleditorDisabled {opacity:0.3; filter:alpha(opacity=30)}
  8
+.cleditorDivider {float:left; width:1px; height:23px; margin:1px 0 1px 0; background:#CCC}
  9
+.cleditorPopup {border:solid 1px #999; background-color:white; position:absolute; font:10pt Arial,Verdana; cursor:default; z-index:10000}
  10
+.cleditorList div {padding:2px 4px 2px 4px}
  11
+.cleditorList p,
  12
+.cleditorList h1,
  13
+.cleditorList h2,
  14
+.cleditorList h3,
  15
+.cleditorList h4,
  16
+.cleditorList h5,
  17
+.cleditorList h6,
  18
+.cleditorList font {padding:0; margin:0; background-color:Transparent}
  19
+.cleditorColor {width:150px; padding:1px 0 0 1px}
  20
+.cleditorColor div {float:left; width:14px; height:14px; margin:0 1px 1px 0}
  21
+.cleditorPrompt {background-color:#F6F7F9; padding:4px; font-size:8.5pt}
  22
+.cleditorPrompt input,
  23
+.cleditorPrompt textarea {font:8.5pt Arial,Verdana;}
  24
+.cleditorMsg {background-color:#FDFCEE; width:150px; padding:4px; font-size:8.5pt}
1,132  public/cleditor/jquery.cleditor.js
... ...
@@ -0,0 +1,1132 @@
  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
+    }