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 (Aloha, 39 Selection, 40 jQuery, 41 console) { 42 'use strict'; 43 44 var XMLSerializer = window.XMLSerializer; 45 46 /** 47 * jQuery between Extension 48 * 49 * insert either html code, a dom object OR a jQuery object inside of an existing text node. 50 * if the chained jQuery object is not a text node, nothing will happen. 51 * 52 * @param content HTML Code, DOM object or jQuery object to be inserted 53 54 * @param offset character offset from the start where the content should be inserted 55 */ 56 jQuery.fn.between = function (content, offset) { 57 var 58 offSize, 59 fullText; 60 61 if (this[0].nodeType !== 3) { 62 // we are not in a text node, just insert the element at the corresponding position 63 offSize = this.children().size(); 64 if (offset > offSize) { 65 offset = offSize; 66 } 67 if (offset <= 0) { 68 this.prepend(content); 69 } else { 70 this.children().eq(offset -1).after(content); 71 } 72 } else { 73 // we are in a text node so we have to split it at the correct position 74 if (offset <= 0) { 75 this.before(content); 76 } else if (offset >= this[0].length) { 77 this.after(content); 78 } else { 79 fullText = this[0].data; 80 this[0].data = fullText.substring(0, offset); 81 this.after(fullText.substring(offset, fullText.length)); 82 this.after(content); 83 } 84 } 85 }; 86 87 /** 88 * Make the object contenteditable. Care about browser version (name of contenteditable attribute depends on it) 89 */ 90 jQuery.fn.contentEditable = function ( b ) { 91 // ie does not understand contenteditable but contentEditable 92 // contentEditable is not xhtml compatible. 93 var $el = jQuery(this); 94 var ce = 'contenteditable'; 95 96 // Check 97 if (jQuery.browser.msie && parseInt(jQuery.browser.version,10) == 7 ) { 98 ce = 'contentEditable'; 99 } 100 101 if (typeof b === 'undefined' ) { 102 103 // For chrome use this specific attribute. The old ce will only 104 // return 'inherit' for nested elements of a contenteditable. 105 // The isContentEditable is a w3c standard compliant property which works in IE7,8,FF36+, Chrome 12+ 106 if (typeof $el[0] === 'undefined' ) { 107 console.warn('The jquery object did not contain any valid elements.'); // die silent 108 return undefined; 109 } 110 if (typeof $el[0].isContentEditable === 'undefined') { 111 console.warn('Could not determine whether the is editable or not. I assume it is.'); 112 return true; 113 } else { 114 return $el[0].isContentEditable; 115 } 116 } else if (b === '') { 117 $el.removeAttr(ce); 118 } else { 119 if (b && b !== 'false') { 120 b = 'true'; 121 } else { 122 b = 'false'; 123 } 124 $el.attr(ce, b); 125 } 126 127 return $el; 128 }; 129 130 /** 131 * jQuery Aloha Plugin 132 * 133 * turn all dom elements to continous text 134 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 ) ); 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 218 $el = jQuery(this), 219 el = $el.get(0); 220 if (typeof el.outerHTML != 'undefined') { 221 return el.outerHTML; 222 } else { 223 try { 224 // Gecko-based browsers, Safari, Opera. 225 return (new XMLSerializer()).serializeToString(el); 226 } catch (e) { 227 228 try { 229 // Internet Explorer. 230 return el.xml; 231 } catch (e) {} 232 } 233 } 234 235 }; 236 237 jQuery.fn.zap = function () { 238 return this.each(function (){ jQuery(this.childNodes).insertBefore(this); }).remove(); 239 }; 240 241 jQuery.fn.textNodes = function (excludeBreaks, includeEmptyTextNodes) { 242 var 243 ret = [], 244 doSomething = function (el) { 245 if ( 246 (el.nodeType === 3 && jQuery.trim(el.data) && !includeEmptyTextNodes) || 247 (el.nodeType === 3 && includeEmptyTextNodes) || 248 (el.nodeName =="BR" && !excludeBreaks)) { 249 ret.push(el); 250 } else { 251 for (var i=0, childLength = el.childNodes.length; i < childLength; ++i) { 252 doSomething(el.childNodes[i]); 253 } 254 } 255 }; 256 257 doSomething(this[0]); 258 259 return jQuery(ret); 260 }; 261 262 /** 263 * extendObjects is like jQuery.extend, but it does not extend arrays 264 */ 265 jQuery.extendObjects = jQuery.fn.extendObjects = function () { 266 var options, name, src, copy, copyIsArray, clone, 267 target = arguments[0] || {}, 268 i = 1, 269 length = arguments.length, 270 deep = false; 271 272 // Handle a deep copy situation 273 if ( typeof target === "boolean" ) { 274 deep = target; 275 target = arguments[1] || {}; 276 // skip the boolean and the target 277 i = 2; 278 } 279 280 // Handle case when target is a string or something (possible in deep copy) 281 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 282 target = {}; 283 } 284 285 // extend jQuery itself if only one argument is passed 286 if ( length === i ) { 287 target = this; 288 --i; 289 } 290 291 for ( ; i < length; i++ ) { 292 // Only deal with non-null/undefined values 293 if ( (options = arguments[ i ]) != null ) { 294 // Extend the base object 295 for ( name in options ) { 296 src = target[ name ]; 297 copy = options[ name ]; 298 299 // Prevent never-ending loop 300 if ( target === copy ) { 301 continue; 302 } 303 304 // Recurse if we're merging plain objects or arrays 305 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 306 if ( copyIsArray ) { 307 copyIsArray = false; 308 clone = src && jQuery.isArray(src) ? src : []; 309 310 } else { 311 clone = src && jQuery.isPlainObject(src) ? src : {}; 312 } 313 314 // Never move original objects, clone them 315 if (jQuery.isArray(copy)) { 316 // don't extend arrays 317 target[ name ] = copy; 318 } else { 319 target[ name ] = jQuery.extendObjects( deep, clone, copy ); 320 } 321 322 // Don't bring in undefined values 323 } else if ( copy !== undefined ) { 324 target[ name ] = copy; 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", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 351 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 352 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 353 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 354 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 355 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 356 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" 357 }, 358 359 shiftNums: { 360 "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 361 "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 362 ".": ">", "/": "?", "\\": "|" 363 } 364 }; 365 366 function applyKeyHandler(handler, context, args, event) { 367 // Don't fire in text-accepting inputs that we didn't directly bind to 368 if ( context !== event.target 369 && (/textarea|input|select/i.test( event.target.nodeName ) 370 || event.target.type === "text") ) { 371 return; 372 } 373 return handler.apply(context, args); 374 } 375 376 function keyHandler( handleObj ) { 377 // Only care when a possible input has been specified 378 if ( typeof handleObj.data !== "string" ) { 379 return; 380 } 381 382 var origHandler = handleObj.handler, 383 keys = handleObj.data.toLowerCase().split(" "), 384 handle = {}; 385 386 for (var i = 0; i < keys.length; i++) { 387 handle[keys[i]] = true; 388 } 389 390 handleObj.handler = function(event) { 391 // The original comment that was added with this condition says: 392 // "Don't fire in contentEditable true elements" 393 // But this is incorrect. 394 // What this condition does is it skips hotkey events for 395 // any target unless it is directly bound. 396 397 // The condition event.target.contentEditable !== true will 398 // always be true, because contentEditable is a string 399 // attribute that is never strictly equal true. 400 //if (this !== event.target && event.target.contentEditable !== true) { 401 //return; 402 //} 403 // Below is what this condition really does. Ideally, I'd 404 // like to remove this condition since it was not there in 405 // the original implementation by John Resig and it could 406 // interfere with other plugins, but when I removed it, I 407 // was unable to input any space characters into an 408 // editable. 409 // TODO figure out a way to safely remove this 410 if (this !== event.target) { 411 return; 412 } 413 414 // Keypress represents characters, not special keys 415 var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], 416 modif = "", 417 character; 418 419 // check combinations (alt|ctrl|shift+anything) 420 if ( event.altKey && special !== "alt" ) { 421 modif += "alt+"; 422 } 423 424 if ( event.ctrlKey && special !== "ctrl" ) { 425 modif += "ctrl+"; 426 } 427 428 // TODO: Need to make sure this works consistently across platforms 429 if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { 430 modif += "meta+"; 431 } 432 433 if ( event.shiftKey && special !== "shift" ) { 434 modif += "shift+"; 435 } 436 437 if ( special ) { 438 if (handle[modif + special]) { 439 return applyKeyHandler(origHandler, this, arguments, event); 440 } 441 } else { 442 character = String.fromCharCode(event.which).toLowerCase(); 443 444 if (handle[modif + character]) { 445 return applyKeyHandler(origHandler, this, arguments, event); 446 } 447 448 if (handle[modif + jQuery.hotkeys.shiftNums[character]]) { 449 return applyKeyHandler(origHandler, this, arguments, event); 450 } 451 452 // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 453 if (modif === "shift+") { 454 if (handle[jQuery.hotkeys.shiftNums[character]]) { 455 return applyKeyHandler(origHandler, this, arguments, event); 456 } 457 } 458 } 459 }; 460 } 461 462 jQuery.each(['keydown', 'keyup', 'keypress'], function () { 463 jQuery.event.special[this] = {add: keyHandler}; 464 }); 465 466 }); 467