1 /*!
  2 * This file is part of Aloha Editor Project http://aloha-editor.org
  3 * Copyright © 2010-2011 Gentics Software GmbH, aloha@gentics.com
  4 * Contributors http://aloha-editor.org/contribution.php 
  5 * Licensed unter the terms of http://www.aloha-editor.org/license.html
  6 *//*
  7 * Aloha Editor is free software: you can redistribute it and/or modify
  8 * it under the terms of the GNU Affero General Public License as published by
  9 * the Free Software Foundation, either version 3 of the License, or
 10 * (at your option) 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 Affero General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU Affero General Public License
 18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20 
 21 define(
 22 
 23 [
 24 	'aloha/jquery',
 25 	'aloha/pluginmanager'
 26 ],
 27 
 28 function ( jQuery, PluginManager ) {
 29 	"use strict";
 30 
 31 	// Aloha Editor does not support Internet Explorer 6.  ExtJS style fixes for
 32 	// IE6 which are applied through the "ext-ie6" class cause visual bugs in
 33 	// IE9, and so we remove it so that IE6 fixes are not applied.
 34 	Aloha.ready(function() {
 35 		jQuery('.ext-ie6').removeClass('ext-ie6');
 36 	});
 37 
 38 	//----------------------------------------
 39 	// Private variables
 40 	//----------------------------------------
 41 	
 42 	/**
 43 	 * Hash table that will be populated through the loadPlugins method.
 44 	 * Maps the names of plugins with their urls for easy assess in the getPluginsUrl method
 45 	 */
 46 	var pluginPaths = {};
 47 
 48 	/**
 49 	 * Base Aloha Object
 50 	 * @namespace Aloha
 51 	 * @class Aloha The Aloha base object, which contains all the core functionality
 52 	 * @singleton
 53 	 */
 54 	jQuery.extend(true, Aloha, {
 55 
 56 		/**
 57 		 * The Aloha Editor Version we are using
 58 		 * It should be set by us and updated for the particular branch
 59 		 * @property
 60 		 */
 61 		version: '0.10.0',
 62 
 63 		/**
 64 		 * Array of editables that are managed by Aloha
 65 		 * @property
 66 		 * @type Array
 67 		 */
 68 		editables: [],
 69 
 70 		/**
 71 		 * The currently active editable is referenced here
 72 		 * @property
 73 		 * @type Aloha.Editable
 74 		 */
 75 		activeEditable: null,
 76 
 77 		/**
 78 		 * settings object, which will contain all Aloha settings
 79 		 * @cfg {Object} object Aloha's settings
 80 		 */
 81 		settings: {},
 82 		
 83 		/**
 84 		 * defaults object, which will contain all Aloha defaults
 85 		 * @cfg {Object} object Aloha's settings
 86 		 */
 87 		defaults: {},
 88 		
 89 		/**
 90 		 * Namespace for ui components
 91 		 */
 92 		ui: {},
 93 		
 94 		/**
 95 		 * This represents the name of the users OS. Could be:
 96 		 * 'Mac', 'Linux', 'Win', 'Unix', 'Unknown'
 97 		 * @property
 98 		 * @type string
 99 		 */
100 		OSName: 'Unknown',
101 
102 		/**
103 		 * Which stage is the aloha init process at?
104 		 * @property
105 		 * @type string
106 		 */
107 		stage: 'loadingAloha',
108 
109 		/**
110 		 * A list of loaded plugin names. Available after the
111 		 * "loadPlugins" stage.
112 		 *
113 		 * @property
114 		 * @type array
115 		 * @internal
116 		 */
117 		loadedPlugins: [],
118 
119 		requirePaths: [],
120 		/**
121 		 * Initialize the initialization process
122 		 */
123 		init: function () {
124 				
125 			// merge defaults and settings and provide all in settings
126 			Aloha.settings = jQuery.extendObjects( true, {}, Aloha.defaults, Aloha.settings );
127 
128 			// initialize rangy. This is probably necessary here,
129 			// because due to the current loading mechanism, rangy
130 			// doesn't initialize itself in all browsers
131 			if (window.rangy) {
132 				window.rangy.init();
133 			}
134 			
135 			// Load & Initialise
136 			Aloha.stage = 'loadPlugins';
137 			Aloha.loadPlugins(function(){
138 				Aloha.stage = 'initAloha';
139 				Aloha.initAloha(function(){
140 					Aloha.stage = 'initPlugins';
141 					Aloha.initPlugins(function(){
142 						Aloha.stage = 'initGui';
143 						Aloha.initGui(function(){
144 							Aloha.stage = 'alohaReady';
145 							Aloha.trigger('aloha-ready');
146 						});
147 					});
148 				});
149 			});
150 		},
151 
152 		/**
153 		 * Load Plugins
154 		 */
155 		loadPlugins: function (next) {
156 			// contains an array like [common/format, common/block]
157 			var configuredPluginsWithBundle = this.getPluginsToBeLoaded();
158 
159 			if (configuredPluginsWithBundle.length) {
160 				var paths = {},
161 				    pluginNames = [],
162 				    requiredInitializers = [],
163 				    pathsToPlugins = {};
164 
165 				// Background: We do not use CommonJS packages for our Plugins
166 				// as this breaks the loading order when these modules have
167 				// other dependencies.
168 				// We "emulate" the commonjs modules with the path mapping.
169 				/* require(
170 				 *  { paths: {
171 				 *      'format': 'plugins/common/format/lib',
172 				 *      'format/nls': 'plugins/common/format/nls',
173 174 				 *      ... for every plugin ...
175 				 *    }
176 				 *  },
177 				 *  ['format/format-plugin', ... for every plugin ...],
178 				 *  next <-- when everything is loaded, we continue
179 				 */
180 				jQuery.each(configuredPluginsWithBundle, function (i, configuredPluginWithBundle) {
181 					var tmp, bundleName, pluginName, bundlePath = '';
182 
183 					tmp = configuredPluginWithBundle.split('/');
184 					bundleName = tmp[0];
185 					pluginName = tmp[1];
186 
187 					// TODO assertion if pluginName or bundleName NULL _-> ERROR!!
188 
189 					if (Aloha.settings.basePath) {
190 						bundlePath = Aloha.settings.basePath;
191 					}
192 
193 					if (Aloha.settings.bundles && Aloha.settings.bundles[bundleName]) {
194 						bundlePath += Aloha.settings.bundles[bundleName];
195 					} else {
196 						bundlePath += '../plugins/' + bundleName;
197 					}
198 
199 					pluginNames.push(pluginName);
200 					paths[pluginName] = bundlePath + '/' + pluginName + '/lib';
201 
202 					pathsToPlugins[pluginName] = bundlePath + '/' + pluginName;
203 
204 					// As the "nls" path lies NOT inside /lib/, but is a sibling to /lib/, we need
205 					// to register it explicitely. The same goes for the "css" folder.
206 					jQuery.each(['nls', 'css', 'vendor', 'res'], function() {
207 						paths[pluginName + '/' + this] = bundlePath + '/' + pluginName + '/' + this;
208 					});
209 
210 					requiredInitializers.push(pluginName + '/' + pluginName + '-plugin');
211 				});
212 
213 				this.loadedPlugins = pluginNames;
214 				this.requirePaths = paths;
215 				
216 				// Main Require.js loading call, which fetches all the plugins.
217 				require(
218 					{
219 						context: 'aloha',
220 						paths: paths,
221 						locale: this.settings.locale || this.defaults.locale || 'en'
222 					},
223 					requiredInitializers,
224 					next
225 				);
226 
227 				pluginPaths = pathsToPlugins;
228 			} else {
229 				next();
230 			}
231 		},
232 
233 		/**
234 		 * Fetches plugins the user wants to have loaded. Returns all plugins the user
235 		 * has specified with the data-plugins property as array, with the bundle
236 		 * name in front.
237 		 * It's also possible to use 'Aloha.settings.plugins.load' to define the plugins
238 		 * to load.
239 		 *
240 		 * @return array
241 		 * @internal
242 		 */
243 		getPluginsToBeLoaded: function() {
244 			// look for data-aloha-plugins attributes and load values
245 			var
246 				plugins = jQuery('[data-aloha-plugins]').data('aloha-plugins');
247 
248 			// load aloha plugins from config
249 			if ( typeof Aloha.settings.plugins != 'undefined' && typeof Aloha.settings.plugins.load != 'undefined' ) {
250 				plugins = Aloha.settings.plugins.load;
251 				if (plugins instanceof Array) {
252 					return plugins;
253 				}
254 			}
255 
256 			// Determine Plugins
257 			if ( typeof plugins === 'string' && plugins !== "") {
258 				return plugins.replace(/\s+/g, '').split(',');
259 			}
260 			// Return
261 			return [];
262 		},
263 
264 		/**
265 		 * Returns list of loaded plugins (without Bundle name)
266 		 *
267 		 * @return array
268 		 */
269 		getLoadedPlugins: function() {
270 			return this.loadedPlugins;
271 		},
272 
273 		/**
274 		 * Returns true if a certain plugin is loaded, false otherwise.
275 		 */
276 		isPluginLoaded: function(pluginName) {
277 			var found = false;
278 			jQuery.each(this.loadedPlugins, function() {
279 				if (pluginName.toString() === this.toString()) {
280 					found = true;
281 				}
282 			});
283 			return found;
284 		},
285 
286 		/**
287 		 * Initialise Aloha
288 		 */
289 		initAloha: function(next){
290 			// check browser version on init
291 			// this has to be revamped, as
292 			if (jQuery.browser.webkit && parseFloat(jQuery.browser.version) < 532.5 || // Chrome/Safari 4
293 				jQuery.browser.mozilla && parseFloat(jQuery.browser.version) < 1.9 || // FF 3.5
294 				jQuery.browser.msie && jQuery.browser.version < 7 || // IE 7
295 				jQuery.browser.opera && jQuery.browser.version < 11 ) { // right now, Opera needs some work
296 				if (window.console && window.console.log) {
297 					window.console.log( 'Your browser is not supported.' );
298 				}
299 			}
300 
301 			// register the body click event to blur editables
302 			jQuery('html').mousedown(function(e) {
303 				// if an Ext JS modal is visible, we don't want to loose the focus on
304 				// the editable as we assume that the user must have clicked somewhere
305 				// in the modal... where else could he click?
306 				// loosing the editable focus in this case hinders correct table
307 				// column/row deletion, as the table module will clean it's selection
308 				// as soon as the editable is deactivated. Fusubscriberthermore you'd have to
309 				// refocus the editable again, which is just strange UX
310 				if (Aloha.activeEditable && !Aloha.isMessageVisible() && !Aloha.eventHandled) {
311 					Aloha.activeEditable.blur();
312 					Aloha.activeEditable = null;
313 				}
314 			}).mouseup(function(e) {
315 				Aloha.eventHandled = false;
316 			});
317 			
318 			// Initialise the base path to the aloha files
319 			Aloha.settings.base = Aloha.getAlohaUrl();
320 
321 			// initialize the Log
322 			Aloha.Log.init();
323 
324 			// initialize the error handler for general javascript errors
325 			if ( Aloha.settings.errorhandling ) {
326 				window.onerror = function (msg, url, linenumber) {
327 					Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' + url + '\nLine Number: ' + linenumber);
328 					// TODO eventually add a message to the message line?
329 					return true;
330 				};
331 			}
332 
333 			// OS detection
334 			if (navigator.appVersion.indexOf('Win') != -1) {
335 				Aloha.OSName = 'Win';
336 			}
337 			if (navigator.appVersion.indexOf('Mac') != -1) {
338 				Aloha.OSName = 'Mac';
339 			}
340 			if (navigator.appVersion.indexOf('X11') != -1) {
341 				Aloha.OSName = 'Unix';
342 			}
343 			if (navigator.appVersion.indexOf('Linux') != -1) {
344 				Aloha.OSName = 'Linux';
345 			}
346 
347 			try {
348 				// this will disable browsers image resizing facilities
349 				// disable resize handles
350 				var supported;
351 				try {
352 					supported = document.queryCommandSupported( 'enableObjectResizing' );
353 				} catch ( e ) {
354 					supported = false;
355 					Aloha.Log.log( 'enableObjectResizing is not supported.' );
356 				}
357 				
358 				if ( supported ) {
359 					document.execCommand( 'enableObjectResizing', false, false);
360 					Aloha.Log.log( 'enableObjectResizing disabled.' );
361 				}
362 			} catch (e) {
363 				Aloha.Log.error( e, 'Could not disable enableObjectResizing' );
364 				// this is just for others, who will not support disabling enableObjectResizing
365 			}
366 			// Forward
367 			next();
368 		},
369 
370 		/**
371 		 * Loads plugins Aloha
372 		 * @return void
373 		 */
374 		initPlugins: function (next) {
375 			PluginManager.init(function(){
376 				next();
377 			}, this.getLoadedPlugins() );
378 		},
379 
380 		/**
381 		 * Loads GUI components
382 		 * @return void
383 		 */
384 		initGui: function (next) {
385 			
386 			Aloha.RepositoryManager.init();
387 
388 			// activate registered editables
389 			for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
390 				if ( !Aloha.editables[i].ready ) {
391 					Aloha.editables[i].init();
392 				}
393 			}
394 
395 			// Forward
396 			next();
397 		},
398 
399 		/**
400 		 * Activates editable and deactivates all other Editables
401 		 * @param {Editable} editable the Editable to be activated
402 		 * @return void
403 		 */
404 		activateEditable: function (editable) {
405 
406 			// blur all editables, which are currently active
407 			for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
408 				if (Aloha.editables[i] != editable && Aloha.editables[i].isActive) {
409 					Aloha.editables[i].blur();
410 				}
411 			}
412 
413 			Aloha.activeEditable = editable;
414 		},
415 
416 		/**
417 		 * Returns the current Editable
418 		 * @return {Editable} returns the active Editable
419 		 */
420 		getActiveEditable: function() {
421 			return Aloha.activeEditable;
422 		},
423 
424 		/**
425 		 * deactivated the current Editable
426 		 * @return void
427 		 */
428 		deactivateEditable: function () {
429 
430 			if ( typeof Aloha.activeEditable === 'undefined' || Aloha.activeEditable === null ) {
431 				return;
432 			}
433 
434 			// blur the editable
435 			Aloha.activeEditable.blur();
436 			Aloha.activeEditable = null;
437 		},
438 
439 		/**
440 		 * Gets an editable by an ID or null if no Editable with that ID registered.
441 		 * @param {string} id the element id to look for.
442 		 * @return {Aloha.Editable} editable
443 		 */
444 		getEditableById: function (id) {
445 
446 			// if the element is a textarea than route to the editable div
447 			if (jQuery('#'+id).get(0).nodeName.toLowerCase() === 'textarea' ) {
448 				id = id + '-aloha';
449 			}
450 
451 			// serach all editables for id
452 			for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
453 				if (Aloha.editables[i].getId() == id) {
454 					return Aloha.editables[i];
455 				}
456 			}
457 
458 			return null;
459 		},
460 
461 		/**
462 		 * Checks wheater an object is a registered Aloha Editable.
463 		 * @param {jQuery} obj the jQuery object to be checked.
464 		 * @return {boolean}
465 		 */
466 		isEditable: function (obj) {
467 			for (var i=0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
468 				if ( Aloha.editables[i].originalObj.get(0) === obj ) {
469 					return true;
470 				}
471 			}
472 			return false;
473 		},
474 
475 		/**
476 		 * Logs a message to the console
477 		 * @param level Level of the log ("error", "warn" or "info", "debug")
478 		 * @param component Component that calls the log
479 		 * @param message log message
480 		 * @return void
481 		 * @hide
482 		 */
483 		log: function(level, component, message) {
484 			if (typeof Aloha.Log !== "undefined")
485 				Aloha.Log.log(level, component, message);
486 		},
487 		
488 		/**
489 		 * Register the given editable
490 		 * @param editable editable to register
491 		 * @return void
492 		 * @hide
493 		 */
494 		registerEditable: function (editable) {
495 			Aloha.editables.push(editable);
496 		},
497 
498 		/**
499 		 * Unregister the given editable. It will be deactivated and removed from editables.
500 		 * @param editable editable to unregister
501 		 * @return void
502 		 * @hide
503 		 */
504 		unregisterEditable: function (editable) {
505 
506 			// Find the index
507 			var id = Aloha.editables.indexOf( editable );
508 			// Remove it if really found!
509 			if (id != -1) {
510 				Aloha.editables.splice(id, 1);
511 			}
512 		},
513 
514 		/**
515 		 * String representation
516 		 * @hide
517 		 */
518 		toString: function () {
519 			return 'Aloha';
520 		},
521 
522 		/**
523 		 * Check whether at least one editable was modified
524 		 * @method
525 		 * @return {boolean} true when at least one editable was modified, false if not
526 		 */
527 		isModified: function () {
528 			// check if something needs top be saved
529 			for (var i = 0; i < Aloha.editables.length; i++) {
530 				if (Aloha.editables[i].isModified && Aloha.editables[i].isModified()) {
531 					return true;
532 				}
533 			}
534 
535 			return false;
536 		},
537 
538 		/**
539 		 * Determines the Aloha Url
540 		 * Uses Aloha.settings.baseUrl if set.
541 		 * @method
542 		 * @return {String} alohaUrl
543 		 */
544 		getAlohaUrl: function( suffix ) {
545 			// aloha base path is defined by a script tag with 2 data attributes
546 			var requireJs = jQuery('[data-aloha-plugins]'),
547 				baseUrl = ( requireJs.length ) ? requireJs[0].src.replace( /\/?aloha.js$/ , '' ) : '';
548 			
549 			if ( typeof Aloha.settings.baseUrl === "string" ) {
550 				baseUrl = Aloha.settings.baseUrl;
551 			}
552 			
553 			return baseUrl;
554 		},
555 
556 		/**
557 		 * Gets the Plugin Url
558 		 * @method
559 		 * @param {String} name
560 		 * @return {String} url
561 		 */
562 		getPluginUrl: function (name) {
563 			var url;
564 
565 			if (name) {
566 				url = pluginPaths[name];
567 				if(url) {
568 					//Check if url is absolute and attach base url if it is not
569 					if(!url.match("^(\/|http[s]?:).*")) {
570 						url = Aloha.getAlohaUrl() + '/' + url;
571 					}
572 				}
573 			}
574 
575 			return url;
576 		}
577 
578 	});
579 
580 	return Aloha;
581 });