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