1 /* jquery.aloha.js is part of Aloha Editor project http://aloha-editor.org 2 * 3 * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 4 * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria. 5 * Contributors http://aloha-editor.org/contribution.php 6 * 7 * Aloha Editor is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or any later version. 11 * 12 * Aloha Editor is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 * 21 * As an additional permission to the GNU GPL version 2, you may distribute 22 * non-source (e.g., minimized or compacted) forms of the Aloha-Editor 23 * source code without the copy of the GNU GPL normally required, 24 * provided you include this license notice and a URL through which 25 * recipients can access the Corresponding Source. 26 */ 27 /** 28 * IMPORTANT! 29 * Don't add any more custom jquery extensions here. 30 * Instead use the define(...) mechanism to define a module and to 31 * import it where you need it. 32 */ 33 define([ 34 'aloha/core', 35 'aloha/selection', 36 'jquery', 37 'aloha/console' 38 ], function ( 39 Aloha, 40 Selection, 41 jQuery, 42 console 43 ) { 44 'use strict'; 45 46 var XMLSerializer = window.XMLSerializer; 47 48 /** 49 * jQuery between Extension 50 * 51 * insert either html code, a dom object OR a jQuery object inside of an existing text node. 52 * if the chained jQuery object is not a text node, nothing will happen. 53 * 54 * @param content HTML Code, DOM object or jQuery object to be inserted 55 * @param offset character offset from the start where the content should be inserted 56 */ 57 jQuery.fn.between = function (content, offset) { 58 var offSize, fullText; 59 60 if (this[0].nodeType !== 3) { 61 // we are not in a text node, just insert the element at the corresponding position 62 offSize = this.children().size(); 63 if (offset > offSize) { 64 offset = offSize; 65 } 66 if (offset <= 0) { 67 this.prepend(content); 68 } else { 69 this.children().eq(offset - 1).after(content); 70 } 71 } else { 72 // we are in a text node so we have to split it at the correct position 73 if (offset <= 0) { 74 this.before(content); 75 } else if (offset >= this[0].length) { 76 this.after(content); 77 } else { 78 fullText = this[0].data; 79 this[0].data = fullText.substring(0, offset); 80 this.after(fullText.substring(offset, fullText.length)); 81 this.after(content); 82 } 83 } 84 }; 85 86 /** 87 * Make the object contenteditable. Care about browser version (name of contenteditable attribute depends on it) 88 */ 89 jQuery.fn.contentEditable = function (b) { 90 // ie does not understand contenteditable but contentEditable 91 // contentEditable is not xhtml compatible. 92 var $el = jQuery(this); 93 var ce = 'contenteditable'; 94 95 // Check 96 if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) == 7) { 97 ce = 'contentEditable'; 98 } 99 100 if (typeof b === 'undefined') { 101 102 // For chrome use this specific attribute. The old ce will only 103 // return 'inherit' for nested elements of a contenteditable. 104 // The isContentEditable is a w3c standard compliant property which works in IE7,8,FF36+, Chrome 12+ 105 if (typeof $el[0] === 'undefined') { 106 console.warn('The jquery object did not contain any valid elements.'); // die silent 107 return undefined; 108 } 109 if (typeof $el[0].isContentEditable === 'undefined') { 110 console.warn('Could not determine whether the is editable or not. I assume it is.'); 111 return true; 112 } 113 114 return $el[0].isContentEditable; 115 } 116 117 if (b === '') { 118 $el.removeAttr(ce); 119 } else { 120 if (b && b !== 'false') { 121 b = 'true'; 122 } else { 123 b = 'false'; 124 } 125 $el.attr(ce, b); 126 } 127 128 return $el; 129 }; 130 131 /** 132 * jQuery Aloha Plugin 133 * 134 * turn all dom elements to continous text 135 * @return jQuery object for the matched elements 136 * @api 137 */ 138 jQuery.fn.aloha = function () { 139 var $this = jQuery(this); 140 141 Aloha.bind('aloha-ready', function () { 142 $this.each(function () { 143 // create a new aloha editable object for each passed object 144 if (!Aloha.isEditable(this)) { 145 new Aloha.Editable(jQuery(this)).init(); 146 } 147 }); 148 }); 149 150 // Chain 151 return $this; 152 }; 153 154 /** 155 * jQuery destroy elements as editable 156 * 157 * destroy all mached elements editable capabilities 158 * @return jQuery object for the matched elements 159 * @api 160 */ 161 jQuery.fn.mahalo = function () { 162 return this.each(function () { 163 if (Aloha.isEditable(this)) { 164 Aloha.getEditableById(jQuery(this).attr('id')).destroy(); 165 } 166 }); 167 }; 168 169 /** 170 * jQuery Extension 171 * new Event which is triggered whenever a selection (length >= 0) is made in 172 * an Aloha Editable element 173 */ 174 jQuery.fn.contentEditableSelectionChange = function (callback) { 175 var that = this; 176 177 // update selection when keys are pressed 178 this.keyup(function (event) { 179 var rangeObject = Selection.getRangeObject(); 180 callback(event); 181 }); 182 183 // update selection on doubleclick (especially important for the first automatic selection, when the Editable is not active yet, but is at the same time activated as the selection occurs 184 this.dblclick(function (event) { 185 callback(event); 186 }); 187 188 // update selection when text is selected 189 this.mousedown(function (event) { 190 // remember that a selection was started 191 that.selectionStarted = true; 192 }); 193 194 jQuery(document).mouseup(function (event) { 195 Selection.eventOriginalTarget = that; 196 if (that.selectionStarted) { 197 callback(event); 198 } 199 Selection.eventOriginalTarget = false; 200 that.selectionStarted = false; 201 }); 202 203 return this; 204 }; 205 206 /** 207 * Fetch the outerHTML of an Element 208 * @version 1.0.0 209 * @date February 01, 2011 210 * @package jquery-sparkle {@link http://www.balupton/projects/jquery-sparkle} 211 * @author Benjamin Arthur Lupton {@link http://balupton.com} 212 * @copyright 2011 Benjamin Arthur Lupton {@link http://balupton.com} 213 * @license MIT License {@link http://creativecommons.org/licenses/MIT/} 214 * @return {String} outerHtml 215 */ 216 jQuery.fn.outerHtml = jQuery.fn.outerHtml || function () { 217 var $el = jQuery(this), 218 el = $el.get(0); 219 if (typeof el.outerHTML != 'undefined') { 220 return el.outerHTML; 221 } 222 try { 223 // Gecko-based browsers, Safari, Opera. 224 return (new XMLSerializer()).serializeToString(el); 225 } catch (e) { 226 try { 227 // Internet Explorer. 228 return el.xml; 229 } catch (e2) {} 230 } 231 }; 232 233 jQuery.fn.zap = function () { 234 return this.each(function () { 235 jQuery(this.childNodes).insertBefore(this); 236 }).remove(); 237 }; 238 239 jQuery.fn.textNodes = function (excludeBreaks, includeEmptyTextNodes) { 240 var ret = [], 241 doSomething = function (el) { 242 var i, childLength; 243 if ((el.nodeType === 3 && jQuery.trim(el.data) && !includeEmptyTextNodes) || (el.nodeType === 3 && includeEmptyTextNodes) || (el.nodeName == "BR" && !excludeBreaks)) { 244 ret.push(el); 245 } else { 246 for (i = 0, childLength = el.childNodes.length; i < childLength; ++i) { 247 doSomething(el.childNodes[i]); 248 } 249 } 250 }; 251 252 doSomething(this[0]); 253 254 return jQuery(ret); 255 }; 256 257 /** 258 * extendObjects is like jQuery.extend, but it does not extend arrays 259 */ 260 jQuery.extendObjects = jQuery.fn.extendObjects = function (arg1, arg2) { 261 var options, name, src, copy, copyIsArray, clone, 262 start = 1, 263 target = arg1 || {}, 264 length = arguments.length, 265 deep = false, 266 i; 267 268 269 // Handle a deep copy situation 270 if (typeof target === "boolean") { 271 deep = target; 272 target = arg2 || {}; 273 // skip the boolean and the target 274 start = 2; 275 } 276 277 // Handle case when target is a string or something (possible in deep copy) 278 if (typeof target !== "object" && !jQuery.isFunction(target)) { 279 target = {}; 280 } 281 282 // extend jQuery itself if only one argument is passed 283 if (length === start) { 284 target = this; 285 --start; 286 } 287 288 for (i = start; i < length; i++) { 289 // Only deal with non-null/undefined values 290 if ((options = arguments[i]) != null) { 291 // Extend the base object 292 for (name in options) { 293 if (options.hasOwnProperty(name)) { 294 295 src = target[name]; 296 copy = options[name]; 297 298 // Prevent never-ending loop 299 if (target === copy) { 300 continue; 301 } 302 303 // Recurse if we're merging plain objects or arrays 304 if (deep && copy && (jQuery.isPlainObject(copy) || true === (copyIsArray = jQuery.isArray(copy)))) { 305 if (copyIsArray) { 306 copyIsArray = false; 307 clone = src && jQuery.isArray(src) ? src : []; 308 309 } else { 310 clone = src && jQuery.isPlainObject(src) ? src : {}; 311 } 312 313 // Never move original objects, clone them 314 if (jQuery.isArray(copy)) { 315 // don't extend arrays 316 target[name] = copy; 317 } else { 318 target[name] = jQuery.extendObjects(deep, clone, copy); 319 } 320 321 // Don't bring in undefined values 322 } else if (copy !== undefined) { 323 target[name] = copy; 324 } 325 } 326 } 327 } 328 } 329 330 // Return the modified object 331 return target; 332 }; 333 334 /* 335 * jQuery Hotkeys Plugin 336 * Copyright 2010, John Resig 337 * Dual licensed under the MIT or GPL Version 2 licenses. 338 * 339 * Based upon the plugin by Tzury Bar Yochay: 340 * http://github.com/tzuryby/hotkeys 341 * 342 * Original idea by: 343 * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ 344 */ 345 346 jQuery.hotkeys = { 347 version: "0.8", 348 349 specialKeys: { 350 8: "backspace", 351 9: "tab", 352 13: "return", 353 16: "shift", 354 17: "ctrl", 355 18: "alt", 356 19: "pause", 357 20: "capslock", 358 27: "esc", 359 32: "space", 360 33: "pageup", 361 34: "pagedown", 362 35: "end", 363 36: "home", 364 37: "left", 365 38: "up", 366 39: "right", 367 40: "down", 368 45: "insert", 369 46: "del", 370 96: "0", 371 97: "1", 372 98: "2", 373 99: "3", 374 100: "4", 375 101: "5", 376 102: "6", 377 103: "7", 378 104: "8", 379 105: "9", 380 106: "*", 381 107: "+", 382 109: "-", 383 110: ".", 384 111: "/", 385 112: "f1", 386 113: "f2", 387 114: "f3", 388 115: "f4", 389 116: "f5", 390 117: "f6", 391 118: "f7", 392 119: "f8", 393 120: "f9", 394 121: "f10", 395 122: "f11", 396 123: "f12", 397 144: "numlock", 398 145: "scroll", 399 191: "/", 400 224: "meta" 401 }, 402 403 shiftNums: { 404 "`": "~", 405 "1": "!", 406 "2": "@", 407 "3": "#", 408 "4": "$", 409 "5": "%", 410 "6": "^", 411 "7": "&", 412 "8": "*", 413 "9": "(", 414 "0": ")", 415 "-": "_", 416 "=": "+", 417 ";": ": ", 418 "'": "\"", 419 ",": "<", 420 ".": ">", 421 "/": "?", 422 "\\": "|" 423 } 424 }; 425 426 function applyKeyHandler(handler, context, args, event) { 427 // Don't fire in text-accepting inputs that we didn't directly bind to 428 if (context !== event.target && (/textarea|input|select/i.test(event.target.nodeName) || event.target.type === "text")) { 429 return; 430 } 431 return handler.apply(context, args); 432 } 433 434 function keyHandler(handleObj) { 435 var origHandler, keys, handle, i; 436 437 // Only care when a possible input has been specified 438 if (typeof handleObj.data !== "string") { 439 return; 440 } 441 442 origHandler = handleObj.handler; 443 keys = handleObj.data.toLowerCase().split(" "); 444 handle = {}; 445 446 for (i = 0; i < keys.length; i++) { 447 handle[keys[i]] = true; 448 } 449 450 handleObj.handler = function (event) { 451 // The original comment that was added with this condition says: 452 // "Don't fire in contentEditable true elements" 453 // But this is incorrect. 454 // What this condition does is it skips hotkey events for 455 // any target unless it is directly bound. 456 // The condition event.target.contentEditable !== true will 457 // always be true, because contentEditable is a string 458 // attribute that is never strictly equal true. 459 //if (this !== event.target && event.target.contentEditable !== true) { 460 //return; 461 //} 462 // Below is what this condition really does. Ideally, I'd 463 // like to remove this condition since it was not there in 464 // the original implementation by John Resig and it could 465 // interfere with other plugins, but when I removed it, I 466 // was unable to input any space characters into an 467 // editable. 468 // TODO figure out a way to safely remove this 469 if (this !== event.target) { 470 return; 471 } 472 473 // Keypress represents characters, not special keys 474 var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which], 475 modif = "", 476 character; 477 478 // check combinations (alt|ctrl|shift+anything) 479 if (event.altKey && special !== "alt") { 480 modif += "alt+"; 481 } 482 483 if (event.ctrlKey && special !== "ctrl") { 484 modif += "ctrl+"; 485 } 486 487 // TODO: Need to make sure this works consistently across platforms 488 if (event.metaKey && !event.ctrlKey && special !== "meta") { 489 modif += "meta+"; 490 } 491 492 if (event.shiftKey && special !== "shift") { 493 modif += "shift+"; 494 } 495 496 if (special) { 497 if (handle[modif + special]) { 498 return applyKeyHandler(origHandler, this, arguments, event); 499 } 500 } else { 501 character = String.fromCharCode(event.which).toLowerCase(); 502 503 if (handle[modif + character]) { 504 return applyKeyHandler(origHandler, this, arguments, event); 505 } 506 507 if (handle[modif + jQuery.hotkeys.shiftNums[character]]) { 508 return applyKeyHandler(origHandler, this, arguments, event); 509 } 510 511 // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 512 if (modif === "shift+") { 513 if (handle[jQuery.hotkeys.shiftNums[character]]) { 514 return applyKeyHandler(origHandler, this, arguments, event); 515 } 516 } 517 } 518 }; 519 } 520 521 jQuery.each(['keydown', 'keyup', 'keypress'], function () { 522 jQuery.event.special[this] = { 523 add: keyHandler 524 }; 525 }); 526 527 }); 528