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