You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

jquery.cleditor.js 34KB

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