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