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 		 *
238 		 * @return array
239 		 * @internal
240 		 */
241 		getPluginsToBeLoaded: function() {
242 			// look for data-aloha-plugins attributes and load values
243 			var
244 				plugins = jQuery('[data-aloha-plugins]').data('aloha-plugins');
245 
246 			// Determine Plugins
247 			if ( typeof plugins === 'string' && plugins !== "") {
248 				return plugins.replace(/\s+/g, '').split(',');
249 			}
250 			// Return
251 			return [];
252 			
253 		},
254 
255 		/**
256 		 * Returns list of loaded plugins (without Bundle name)
257 		 *
258 		 * @return array
259 		 */
260 		getLoadedPlugins: function() {
261 			return this.loadedPlugins;
262 		},
263 
264 		/**
265 		 * Returns true if a certain plugin is loaded, false otherwise.
266 		 */
267 		isPluginLoaded: function(pluginName) {
268 			var found = false;
269 			jQuery.each(this.loadedPlugins, function() {
270 				if (pluginName.toString() === this.toString()) {
271 					found = true;
272 				}
273 			});
274 			return found;
275 		},
276 
277 		/**
278 		 * Initialise Aloha
279 		 */
280 		initAloha: function(next){
281 			// check browser version on init
282 			// this has to be revamped, as
283 			if (jQuery.browser.webkit && parseFloat(jQuery.browser.version) < 532.5 || // Chrome/Safari 4
284 				jQuery.browser.mozilla && parseFloat(jQuery.browser.version) < 1.9 || // FF 3.5
285 				jQuery.browser.msie && jQuery.browser.version < 7 || // IE 7
286 				jQuery.browser.opera && jQuery.browser.version < 11 ) { // right now, Opera needs some work
287 				if (window.console && window.console.log) {
288 					window.console.log( 'Your browser is not supported.' );
289 				}
290 			}
291 
292 			// register the body click event to blur editables
293 			jQuery('html').mousedown(function(e) {
294 				// if an Ext JS modal is visible, we don't want to loose the focus on
295 				// the editable as we assume that the user must have clicked somewhere
296 				// in the modal... where else could he click?
297 				// loosing the editable focus in this case hinders correct table
298 				// column/row deletion, as the table module will clean it's selection
299 				// as soon as the editable is deactivated. Fusubscriberthermore you'd have to
300 				// refocus the editable again, which is just strange UX
301 				if (Aloha.activeEditable && !Aloha.isMessageVisible() && !Aloha.eventHandled) {
302 					Aloha.activeEditable.blur();
303 					Aloha.activeEditable = null;
304 				}
305 			}).mouseup(function(e) {
306 				Aloha.eventHandled = false;
307 			});
308 			
309 			// Initialise the base path to the aloha files
310 			Aloha.settings.base = Aloha.getAlohaUrl();
311 
312 			// initialize the Log
313 			Aloha.Log.init();
314 
315 			// initialize the error handler for general javascript errors
316 			if ( Aloha.settings.errorhandling ) {
317 				window.onerror = function (msg, url, linenumber) {
318 					Aloha.Log.error(Aloha, 'Error message: ' + msg + '\nURL: ' + url + '\nLine Number: ' + linenumber);
319 					// TODO eventually add a message to the message line?
320 					return true;
321 				};
322 			}
323 
324 			// OS detection
325 			if (navigator.appVersion.indexOf('Win') != -1) {
326 				Aloha.OSName = 'Win';
327 			}
328 			if (navigator.appVersion.indexOf('Mac') != -1) {
329 				Aloha.OSName = 'Mac';
330 			}
331 			if (navigator.appVersion.indexOf('X11') != -1) {
332 				Aloha.OSName = 'Unix';
333 			}
334 			if (navigator.appVersion.indexOf('Linux') != -1) {
335 				Aloha.OSName = 'Linux';
336 			}
337 
338 			try {
339 				// this will disable browsers image resizing facilities
340 				// disable resize handles
341 				var supported;
342 				try {
343 					supported = document.queryCommandSupported( 'enableObjectResizing' );
344 				} catch ( e ) {
345 					supported = false;
346 					Aloha.Log.log( 'enableObjectResizing is not supported.' );
347 				}
348 				
349 				if ( supported ) {
350 					document.execCommand( 'enableObjectResizing', false, false);
351 					Aloha.Log.log( 'enableObjectResizing disabled.' );
352 				}
353 			} catch (e) {
354 				Aloha.Log.error( e, 'Could not disable enableObjectResizing' );
355 				// this is just for others, who will not support disabling enableObjectResizing
356 			}
357 			// Forward
358 			next();
359 		},
360 
361 		/**
362 		 * Loads plugins Aloha
363 		 * @return void
364 		 */
365 		initPlugins: function (next) {
366 			PluginManager.init(function(){
367 				next();
368 			}, this.getLoadedPlugins() );
369 		},
370 
371 		/**
372 		 * Loads GUI components
373 		 * @return void
374 		 */
375 		initGui: function (next) {
376 			
377 			Aloha.RepositoryManager.init();
378 
379 			// activate registered editables
380 			for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
381 				if ( !Aloha.editables[i].ready ) {
382 					Aloha.editables[i].init();
383 				}
384 			}
385 
386 			// Forward
387 			next();
388 		},
389 
390 		/**
391 		 * Activates editable and deactivates all other Editables
392 		 * @param {Editable} editable the Editable to be activated
393 		 * @return void
394 		 */
395 		activateEditable: function (editable) {
396 
397 			// blur all editables, which are currently active
398 			for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
399 				if (Aloha.editables[i] != editable && Aloha.editables[i].isActive) {
400 					Aloha.editables[i].blur();
401 				}
402 			}
403 
404 			Aloha.activeEditable = editable;
405 		},
406 
407 		/**
408 		 * Returns the current Editable
409 		 * @return {Editable} returns the active Editable
410 		 */
411 		getActiveEditable: function() {
412 			return Aloha.activeEditable;
413 		},
414 
415 		/**
416 		 * deactivated the current Editable
417 		 * @return void
418 		 */
419 		deactivateEditable: function () {
420 
421 			if ( typeof Aloha.activeEditable === 'undefined' || Aloha.activeEditable === null ) {
422 				return;
423 			}
424 
425 			// blur the editable
426 			Aloha.activeEditable.blur();
427 			Aloha.activeEditable = null;
428 		},
429 
430 		/**
431 		 * Gets an editable by an ID or null if no Editable with that ID registered.
432 		 * @param {string} id the element id to look for.
433 		 * @return {Aloha.Editable} editable
434 		 */
435 		getEditableById: function (id) {
436 
437 			// if the element is a textarea than route to the editable div
438 			if (jQuery('#'+id).get(0).nodeName.toLowerCase() === 'textarea' ) {
439 				id = id + '-aloha';
440 			}
441 
442 			// serach all editables for id
443 			for (var i = 0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
444 				if (Aloha.editables[i].getId() == id) {
445 					return Aloha.editables[i];
446 				}
447 			}
448 
449 			return null;
450 		},
451 
452 		/**
453 		 * Checks wheater an object is a registered Aloha Editable.
454 		 * @param {jQuery} obj the jQuery object to be checked.
455 		 * @return {boolean}
456 		 */
457 		isEditable: function (obj) {
458 			for (var i=0, editablesLength = Aloha.editables.length; i < editablesLength; i++) {
459 				if ( Aloha.editables[i].originalObj.get(0) === obj ) {
460 					return true;
461 				}
462 			}
463 			return false;
464 		},
465 
466 		/**
467 		 * Logs a message to the console
468 		 * @param level Level of the log ("error", "warn" or "info", "debug")
469 		 * @param component Component that calls the log
470 		 * @param message log message
471 		 * @return void
472 		 * @hide
473 		 */
474 		log: function(level, component, message) {
475 			if (typeof Aloha.Log !== "undefined")
476 				Aloha.Log.log(level, component, message);
477 		},
478 		
479 		/**
480 		 * Register the given editable
481 		 * @param editable editable to register
482 		 * @return void
483 		 * @hide
484 		 */
485 		registerEditable: function (editable) {
486 			Aloha.editables.push(editable);
487 		},
488 
489 		/**
490 		 * Unregister the given editable. It will be deactivated and removed from editables.
491 		 * @param editable editable to unregister
492 		 * @return void
493 		 * @hide
494 		 */
495 496 		unregisterEditable: function (editable) {
497 
498 			// Find the index
499 			var id = Aloha.editables.indexOf( editable );
500 			// Remove it if really found!
501 			if (id != -1) {
502 				Aloha.editables.splice(id, 1);
503 			}
504 		},
505 
506 		/**
507 		 * String representation
508 		 * @hide
509 		 */
510 		toString: function () {
511 			return 'Aloha';
512 		},
513 
514 		/**
515 		 * Check whether at least one editable was modified
516 		 * @method
517 		 * @return {boolean} true when at least one editable was modified, false if not
518 		 */
519 		isModified: function () {
520 			// check if something needs top be saved
521 			for (var i = 0; i < Aloha.editables.length; i++) {
522 				if (Aloha.editables[i].isModified && Aloha.editables[i].isModified()) {
523 					return true;
524 				}
525 			}
526 
527 			return false;
528 		},
529 
530 		/**
531 		 * Determines the Aloha Url
532 		 * @method
533 		 * @return {String} alohaUrl
534 		 */
535 		getAlohaUrl: function( suffix ) {
536 			// aloha base path is defined by a script tag with 2 data attributes
537 			var requireJs = jQuery('[data-aloha-plugins]'),
538 				baseUrl = ( requireJs.length ) ? requireJs[0].src.replace( /\/?aloha.js$/ , '' ) : '';
539 				
540 			return baseUrl;
541 		},
542 
543 		/**
544 		 * Gets the Plugin Url
545 		 * @method
546 		 * @param {String} name
547 		 * @return {String} url
548 		 */
549 		getPluginUrl: function (name) {
550 			var url;
551 
552 			if (name) {
553 				url = pluginPaths[name];
554 				if(url) {
555 					//Check if url is absolute and attach base url if it is not
556 					if(!url.match("^(\/|http[s]?:).*")) {
557 						url = Aloha.getAlohaUrl() + '/' + url;
558 					}
559 				}
560 			}
561 
562 			return url;
563 		}
564 
565 	});
566 
567 	return Aloha;
568 });
569