Enford 5 年之前
父節點
當前提交
36f6bb3d3a
共有 100 個文件被更改,包括 78552 次插入0 次删除
  1. 2601 0
      src/main/webapp/static/ace/assets/js/ace-elements.js
  2. 489 0
      src/main/webapp/static/ace/assets/js/ace-extra.js
  3. 3336 0
      src/main/webapp/static/ace/assets/js/ace.js
  4. 435 0
      src/main/webapp/static/ace/assets/js/ace/ace.ajax-content.js
  5. 19 0
      src/main/webapp/static/ace/assets/js/ace/ace.auto-container.js
  6. 81 0
      src/main/webapp/static/ace/assets/js/ace/ace.auto-padding.js
  7. 534 0
      src/main/webapp/static/ace/assets/js/ace/ace.js
  8. 156 0
      src/main/webapp/static/ace/assets/js/ace/ace.onpage-help.js
  9. 19 0
      src/main/webapp/static/ace/assets/js/ace/ace.searchbox-autocomplete.js
  10. 155 0
      src/main/webapp/static/ace/assets/js/ace/ace.settings-rtl.js
  11. 142 0
      src/main/webapp/static/ace/assets/js/ace/ace.settings-skin.js
  12. 88 0
      src/main/webapp/static/ace/assets/js/ace/ace.settings.js
  13. 506 0
      src/main/webapp/static/ace/assets/js/ace/ace.sidebar-scroll-1.js
  14. 366 0
      src/main/webapp/static/ace/assets/js/ace/ace.sidebar-scroll-2.js
  15. 541 0
      src/main/webapp/static/ace/assets/js/ace/ace.sidebar.js
  16. 513 0
      src/main/webapp/static/ace/assets/js/ace/ace.submenu-hover.js
  17. 118 0
      src/main/webapp/static/ace/assets/js/ace/ace.touch-drag.js
  18. 258 0
      src/main/webapp/static/ace/assets/js/ace/ace.widget-box.js
  19. 28 0
      src/main/webapp/static/ace/assets/js/ace/ace.widget-on-reload.js
  20. 284 0
      src/main/webapp/static/ace/assets/js/ace/elements.aside.js
  21. 112 0
      src/main/webapp/static/ace/assets/js/ace/elements.colorpicker.js
  22. 647 0
      src/main/webapp/static/ace/assets/js/ace/elements.fileinput.js
  23. 708 0
      src/main/webapp/static/ace/assets/js/ace/elements.onpage-help.js
  24. 687 0
      src/main/webapp/static/ace/assets/js/ace/elements.scroller.js
  25. 115 0
      src/main/webapp/static/ace/assets/js/ace/elements.spinner.js
  26. 54 0
      src/main/webapp/static/ace/assets/js/ace/elements.treeview.js
  27. 340 0
      src/main/webapp/static/ace/assets/js/ace/elements.typeahead.js
  28. 37 0
      src/main/webapp/static/ace/assets/js/ace/elements.wizard.js
  29. 322 0
      src/main/webapp/static/ace/assets/js/ace/elements.wysiwyg.js
  30. 1 0
      src/main/webapp/static/ace/assets/js/ace/readme
  31. 31 0
      src/main/webapp/static/ace/assets/js/ace/scripts.json
  32. 998 0
      src/main/webapp/static/ace/assets/js/additional-methods.js
  33. 242 0
      src/main/webapp/static/ace/assets/js/autosize.js
  34. 985 0
      src/main/webapp/static/ace/assets/js/bootbox.js
  35. 540 0
      src/main/webapp/static/ace/assets/js/bootstrap-colorpicker.js
  36. 1417 0
      src/main/webapp/static/ace/assets/js/bootstrap-multiselect.js
  37. 206 0
      src/main/webapp/static/ace/assets/js/bootstrap-tag.js
  38. 217 0
      src/main/webapp/static/ace/assets/js/bootstrap-wysiwyg.js
  39. 2363 0
      src/main/webapp/static/ace/assets/js/bootstrap.js
  40. 1257 0
      src/main/webapp/static/ace/assets/js/chosen.jquery.js
  41. 149 0
      src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.colVis.js
  42. 783 0
      src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.flash.js
  43. 751 0
      src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.html5.js
  44. 128 0
      src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.print.js
  45. 1506 0
      src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/dataTables.buttons.js
  46. 二進制
      src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/swf/flashExport.swf
  47. 1023 0
      src/main/webapp/static/ace/assets/js/dataTables/extensions/select/dataTables.select.js
  48. 261 0
      src/main/webapp/static/ace/assets/js/dataTables/jquery.dataTables.bootstrap.js
  49. 15129 0
      src/main/webapp/static/ace/assets/js/dataTables/jquery.dataTables.js
  50. 1782 0
      src/main/webapp/static/ace/assets/js/date-time/bootstrap-datepicker.js
  51. 2552 0
      src/main/webapp/static/ace/assets/js/date-time/bootstrap-datetimepicker.js
  52. 1180 0
      src/main/webapp/static/ace/assets/js/date-time/bootstrap-timepicker.js
  53. 1492 0
      src/main/webapp/static/ace/assets/js/date-time/daterangepicker.js
  54. 3195 0
      src/main/webapp/static/ace/assets/js/date-time/moment.js
  55. 1728 0
      src/main/webapp/static/ace/assets/js/dropzone.js
  56. 1428 0
      src/main/webapp/static/ace/assets/js/excanvas.js
  57. 3168 0
      src/main/webapp/static/ace/assets/js/flot/jquery.flot.js
  58. 820 0
      src/main/webapp/static/ace/assets/js/flot/jquery.flot.pie.js
  59. 59 0
      src/main/webapp/static/ace/assets/js/flot/jquery.flot.resize.js
  60. 604 0
      src/main/webapp/static/ace/assets/js/fuelux/fuelux.spinner.js
  61. 604 0
      src/main/webapp/static/ace/assets/js/fuelux/fuelux.tree.js
  62. 463 0
      src/main/webapp/static/ace/assets/js/fuelux/fuelux.wizard.js
  63. 11170 0
      src/main/webapp/static/ace/assets/js/fullcalendar.js
  64. 524 0
      src/main/webapp/static/ace/assets/js/html5shiv-printshiv.js
  65. 326 0
      src/main/webapp/static/ace/assets/js/html5shiv.js
  66. 166 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ar.js
  67. 172 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-bg.js
  68. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ca.js
  69. 207 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-cn.js
  70. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-cs.js
  71. 215 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-de.js
  72. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-dk.js
  73. 166 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-el.js
  74. 208 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-en.js
  75. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-es.js
  76. 185 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-fa.js
  77. 169 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-fi.js
  78. 166 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-fr.js
  79. 166 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-gl.js
  80. 167 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-he.js
  81. 202 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-hr.js
  82. 169 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-hu.js
  83. 208 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-id.js
  84. 166 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-is.js
  85. 47 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-it.js
  86. 196 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ja.js
  87. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-kr.js
  88. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-lt.js
  89. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-me.js
  90. 189 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-nl.js
  91. 75 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-no.js
  92. 172 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-pl.js
  93. 176 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-pt-br.js
  94. 165 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-pt.js
  95. 179 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ro.js
  96. 169 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ru.js
  97. 167 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sk.js
  98. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sr-latin.js
  99. 168 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sr.js
  100. 0 0
      src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sv.js

文件差異過大導致無法顯示
+ 2601 - 0
src/main/webapp/static/ace/assets/js/ace-elements.js


+ 489 - 0
src/main/webapp/static/ace/assets/js/ace-extra.js

@@ -0,0 +1,489 @@
+if( !('ace' in window) ) window['ace'] = {}
+
+ace.config = {
+ cookie_expiry : 604800, //1 week duration for saved settings
+ cookie_path: '',
+ storage_method: 2 //2 means use cookies, 1 means localStorage, 0 means localStorage if available otherwise cookies
+}
+if( !('vars' in window['ace']) ) window['ace'].vars = {}
+ace.vars['very_old_ie']	= !('querySelector' in document.documentElement);
+
+
+ace.settings = {
+	is : function(item, status) {
+		//such as ace.settings.is('navbar', 'fixed')
+		return (ace.data.get('settings', item+'-'+status) == 1)
+	},
+	exists : function(item, status) {
+		return (ace.data.get('settings', item+'-'+status) !== null)
+	},
+	set : function(item, status) {
+		ace.data.set('settings', item+'-'+status, 1)
+	},
+	unset : function(item, status) {
+		ace.data.set('settings', item+'-'+status, -1)
+	},
+	remove : function(item, status) {
+		ace.data.remove('settings', item+'-'+status)
+	},
+
+	navbar_fixed : function(navbar, fix , save, chain) {
+		if(ace.vars['very_old_ie']) return false;
+		
+		var navbar = navbar || '#navbar';
+		if(typeof navbar === 'string') navbar = document.querySelector(navbar);
+		if(!navbar) return false;
+	
+		fix = fix || false;
+		save = save && true;
+	
+		if(!fix && chain !== false) {
+			//unfix sidebar as well
+			var sidebar = null;
+			if(
+				ace.settings.is('sidebar', 'fixed')
+				||
+				((sidebar = document.getElementById('sidebar')) && ace.hasClass(sidebar , 'sidebar-fixed'))
+			 )
+			{
+				ace.settings.sidebar_fixed(sidebar, false, save);
+			}
+		}
+		
+		if(fix) {
+			if(!ace.hasClass(navbar , 'navbar-fixed-top'))  ace.addClass(navbar , 'navbar-fixed-top');
+			
+			if(save !== false) ace.settings.set('navbar', 'fixed');
+		} else {
+			ace.removeClass(navbar , 'navbar-fixed-top');
+			
+			if(save !== false) ace.settings.unset('navbar', 'fixed');
+		}
+		try {
+			document.getElementById('ace-settings-navbar').checked = fix;
+		} catch(e) {}
+		
+		if(window.jQuery) jQuery(document).trigger('settings.ace', ['navbar_fixed' , fix , navbar]);
+	},
+
+
+	sidebar_fixed : function(sidebar, fix , save, chain) {
+		if(ace.vars['very_old_ie']) return false;
+		
+		var sidebar = sidebar || '#sidebar';
+		if(typeof sidebar === 'string') sidebar = document.querySelector(sidebar);
+		if(!sidebar) return false;
+	
+		fix = fix || false;
+		save = save && true;
+		
+		if(!fix && chain !== false) {
+			//unfix breadcrumbs as well
+			var breadcrumbs = null;
+			if(
+				ace.settings.is('breadcrumbs', 'fixed')
+				||
+				((breadcrumbs = document.getElementById('breadcrumbs')) && ace.hasClass(breadcrumbs , 'breadcrumbs-fixed'))
+			 )
+			{
+				ace.settings.breadcrumbs_fixed(breadcrumbs, false, save);
+			}
+		}
+
+		if( fix && chain !== false && !ace.settings.is('navbar', 'fixed') ) {
+			ace.settings.navbar_fixed(null, true, save);
+		}
+
+		if(fix) {
+			if( !ace.hasClass(sidebar , 'sidebar-fixed') )  {
+				ace.addClass(sidebar , 'sidebar-fixed');
+				var toggler = document.getElementById('menu-toggler');
+
+				if(toggler) ace.addClass(toggler , 'fixed');
+			}
+			if(save !== false) ace.settings.set('sidebar', 'fixed');
+		} else {
+			ace.removeClass(sidebar , 'sidebar-fixed');
+			var toggler = document.getElementById('menu-toggler');
+			if(toggler) ace.removeClass(toggler , 'fixed');
+
+			if(save !== false) ace.settings.unset('sidebar', 'fixed');
+		}
+		try {
+			document.getElementById('ace-settings-sidebar').checked = fix;
+		} catch(e) {}
+		
+		if(window.jQuery) jQuery(document).trigger('settings.ace', ['sidebar_fixed' , fix , sidebar]);
+	},
+	
+	//fixed position
+	breadcrumbs_fixed : function(breadcrumbs, fix , save, chain) {
+		if(ace.vars['very_old_ie']) return false;
+		
+		var breadcrumbs = breadcrumbs || '#breadcrumbs';
+		if(typeof breadcrumbs === 'string') breadcrumbs = document.querySelector(breadcrumbs);
+		if(!breadcrumbs) return false;
+	
+		fix = fix || false;
+		save = save && true;
+		
+		if(fix && chain !== false && !ace.settings.is('sidebar', 'fixed')) {
+			ace.settings.sidebar_fixed(null, true, save);
+		}
+
+		if(fix) {
+			if(!ace.hasClass(breadcrumbs , 'breadcrumbs-fixed'))  ace.addClass(breadcrumbs , 'breadcrumbs-fixed');
+			if(save !== false) ace.settings.set('breadcrumbs', 'fixed');
+		} else {
+			ace.removeClass(breadcrumbs , 'breadcrumbs-fixed');
+			if(save !== false) ace.settings.unset('breadcrumbs', 'fixed');
+		}
+		try {
+			document.getElementById('ace-settings-breadcrumbs').checked = fix;
+		} catch(e) {}
+		
+		if(window.jQuery) jQuery(document).trigger('settings.ace', ['breadcrumbs_fixed' , fix , breadcrumbs]);
+	},
+
+	//fixed size
+	main_container_fixed : function(main_container, inside , save) {
+		if(ace.vars['very_old_ie']) return false;
+		
+		inside = inside || false;
+		save = save && true;
+		
+		var main_container = main_container || '#main-container';
+		if(typeof main_container === 'string') main_container = document.querySelector(main_container);
+		if(!main_container) return false;
+		
+		var navbar_container = document.getElementById('navbar-container');
+		if(inside) {
+			if( !ace.hasClass(main_container , 'container') )  ace.addClass(main_container , 'container');
+			if( navbar_container && !ace.hasClass(navbar_container , 'container') )  ace.addClass(navbar_container , 'container');
+			if( save !== false ) ace.settings.set('main-container', 'fixed');
+		} else {
+			ace.removeClass(main_container , 'container');
+			if(navbar_container) ace.removeClass(navbar_container , 'container');
+			if(save !== false) ace.settings.unset('main-container', 'fixed');
+		}
+		try {
+			document.getElementById('ace-settings-add-container').checked = inside;
+		} catch(e) {}
+
+		
+		if(navigator.userAgent.match(/webkit/i)) {
+			//webkit has a problem redrawing and moving around the sidebar background in realtime
+			//so we do this, to force redraw
+			//there will be no problems with webkit if the ".container" class is statically put inside HTML code.
+			var sidebar = document.getElementById('sidebar')
+			ace.toggleClass(sidebar , 'menu-min')
+			setTimeout(function() {	ace.toggleClass(sidebar , 'menu-min') } , 0)
+		}
+		
+		if(window.jQuery) jQuery(document).trigger('settings.ace', ['main_container_fixed', inside, main_container]);
+	},
+
+	sidebar_collapsed : function(sidebar, collapse , save) {
+		if(ace.vars['very_old_ie']) return false;
+
+		var sidebar = sidebar || '#sidebar';
+		if(typeof sidebar === 'string') sidebar = document.querySelector(sidebar);
+		if(!sidebar) return false;
+
+		collapse = collapse || false;
+
+		if(collapse) {
+			ace.addClass(sidebar , 'menu-min');
+			if(save !== false) ace.settings.set('sidebar', 'collapsed');
+		} else {
+			ace.removeClass(sidebar , 'menu-min');
+			if(save !== false) ace.settings.unset('sidebar', 'collapsed');
+		}
+		
+		if(window.jQuery) jQuery(document).trigger('settings.ace', ['sidebar_collapsed' , collapse, sidebar]);
+		
+		if(!window.jQuery) {
+			var toggle_btn = document.querySelector('.sidebar-collapse[data-target="#'+(sidebar.getAttribute('id')||'')+'"]');
+			if(!toggle_btn) toggle_btn = sidebar.querySelector('.sidebar-collapse');
+			if(!toggle_btn) return;
+
+	
+			var icon = toggle_btn.querySelector('[data-icon1][data-icon2]'), icon1, icon2;	
+			if(!icon) return;
+
+			icon1 = icon.getAttribute('data-icon1');//the icon for expanded state
+			icon2 = icon.getAttribute('data-icon2');//the icon for collapsed state
+
+			if(collapse) {
+				ace.removeClass(icon, icon1);
+				ace.addClass(icon, icon2);
+			}
+			else {
+				ace.removeClass(icon, icon2);
+				ace.addClass(icon, icon1);
+			}
+		}		
+	}
+	/**
+	,
+	select_skin : function(skin) {
+	}
+	*/
+}
+
+
+//check the status of something
+ace.settings.check = function(item, val) {
+	if(! ace.settings.exists(item, val) ) return;//no such setting specified
+	var status = ace.settings.is(item, val);//is breadcrumbs-fixed? or is sidebar-collapsed? etc
+
+	var mustHaveClass = {
+		'navbar-fixed' : 'navbar-fixed-top',
+		'sidebar-fixed' : 'sidebar-fixed',
+		'breadcrumbs-fixed' : 'breadcrumbs-fixed',
+		'sidebar-collapsed' : 'menu-min',
+		'main-container-fixed' : 'container'
+	}
+
+
+	//if an element doesn't have a specified class, but saved settings say it should, then add it
+	//for example, sidebar isn't .fixed, but user fixed it on a previous page
+	//or if an element has a specified class, but saved settings say it shouldn't, then remove it
+	//for example, sidebar by default is minimized (.menu-min hard coded), but user expanded it and now shouldn't have 'menu-min' class
+	
+	var target = document.getElementById(item);//#navbar, #sidebar, #breadcrumbs
+	if(status != ace.hasClass(target , mustHaveClass[item+'-'+val])) {
+		ace.settings[item.replace('-','_')+'_'+val](null, status);//call the relevant function to make the changes
+	}
+}
+
+
+
+
+
+
+//save/retrieve data using localStorage or cookie
+//method == 1, use localStorage
+//method == 2, use cookies
+//method not specified, use localStorage if available, otherwise cookies
+ace.data_storage = function(method, undefined) {
+	var prefix = 'ace_';
+
+	var storage = null;
+	var type = 0;
+	
+	if((method == 1 || method === undefined) && 'localStorage' in window && window['localStorage'] !== null) {
+		storage = ace.storage;
+		type = 1;
+	}
+	else if(storage == null && (method == 2 || method === undefined) && 'cookie' in document && document['cookie'] !== null) {
+		storage = ace.cookie;
+		type = 2;
+	}
+
+	//var data = {}
+	this.set = function(namespace, key, value, path, undefined) {
+		if(!storage) return;
+		
+		if(value === undefined) {//no namespace here?
+			value = key;
+			key = namespace;
+
+			if(value == null) storage.remove(prefix+key)
+			else {
+				if(type == 1)
+					storage.set(prefix+key, value)
+				else if(type == 2)
+					storage.set(prefix+key, value, ace.config.cookie_expiry, path || ace.config.cookie_path)
+			}
+		}
+		else {
+			if(type == 1) {//localStorage
+				if(value == null) storage.remove(prefix+namespace+'_'+key)
+				else storage.set(prefix+namespace+'_'+key, value);
+			}
+			else if(type == 2) {//cookie
+				var val = storage.get(prefix+namespace);
+				var tmp = val ? JSON.parse(val) : {};
+
+				if(value == null) {
+					delete tmp[key];//remove
+					if(ace.sizeof(tmp) == 0) {//no other elements in this cookie, so delete it
+						storage.remove(prefix+namespace);
+						return;
+					}
+				}
+				
+				else {
+					tmp[key] = value;
+				}
+
+				storage.set(prefix+namespace , JSON.stringify(tmp), ace.config.cookie_expiry, path || ace.config.cookie_path)
+			}
+		}
+	}
+
+	this.get = function(namespace, key, undefined) {
+		if(!storage) return null;
+		
+		if(key === undefined) {//no namespace here?
+			key = namespace;
+			return storage.get(prefix+key);
+		}
+		else {
+			if(type == 1) {//localStorage
+				return storage.get(prefix+namespace+'_'+key);
+			}
+			else if(type == 2) {//cookie
+				var val = storage.get(prefix+namespace);
+				var tmp = val ? JSON.parse(val) : {};
+				return key in tmp ? tmp[key] : null;
+			}
+		}
+	}
+
+	
+	this.remove = function(namespace, key, undefined) {
+		if(!storage) return;
+		
+		if(key === undefined) {
+			key = namespace
+			this.set(key, null);
+		}
+		else {
+			this.set(namespace, key, null);
+		}
+	}
+}
+
+
+
+
+
+//cookie storage
+ace.cookie = {
+	// The following functions are from Cookie.js class in TinyMCE, Moxiecode, used under LGPL.
+
+	/**
+	 * Get a cookie.
+	 */
+	get : function(name) {
+		var cookie = document.cookie, e, p = name + "=", b;
+
+		if ( !cookie )
+			return;
+
+		b = cookie.indexOf("; " + p);
+
+		if ( b == -1 ) {
+			b = cookie.indexOf(p);
+
+			if ( b != 0 )
+				return null;
+
+		} else {
+			b += 2;
+		}
+
+		e = cookie.indexOf(";", b);
+
+		if ( e == -1 )
+			e = cookie.length;
+
+		return decodeURIComponent( cookie.substring(b + p.length, e) );
+	},
+
+	/**
+	 * Set a cookie.
+	 *
+	 * The 'expires' arg can be either a JS Date() object set to the expiration date (back-compat)
+	 * or the number of seconds until expiration
+	 */
+	set : function(name, value, expires, path, domain, secure) {
+		var d = new Date();
+
+		if ( typeof(expires) == 'object' && expires.toGMTString ) {
+			expires = expires.toGMTString();
+		} else if ( parseInt(expires, 10) ) {
+			d.setTime( d.getTime() + ( parseInt(expires, 10) * 1000 ) ); // time must be in miliseconds
+			expires = d.toGMTString();
+		} else {
+			expires = '';
+		}
+
+		document.cookie = name + "=" + encodeURIComponent(value) +
+			((expires) ? "; expires=" + expires : "") +
+			((path) ? "; path=" + path : "") +
+			((domain) ? "; domain=" + domain : "") +
+			((secure) ? "; secure" : "");
+	},
+
+	/**
+	 * Remove a cookie.
+	 *
+	 * This is done by setting it to an empty value and setting the expiration time in the past.
+	 */
+	remove : function(name, path) {
+		this.set(name, '', -1000, path);
+	}
+};
+
+
+//local storage
+ace.storage = {
+	get: function(key) {
+		return window['localStorage'].getItem(key);
+	},
+	set: function(key, value) {
+		window['localStorage'].setItem(key , value);
+	},
+	remove: function(key) {
+		window['localStorage'].removeItem(key);
+	}
+};
+
+
+
+
+
+
+//count the number of properties in an object
+//useful for getting the number of elements in an associative array
+ace.sizeof = function(obj) {
+	var size = 0;
+	for(var key in obj) if(obj.hasOwnProperty(key)) size++;
+	return size;
+}
+
+//because jQuery may not be loaded at this stage, we use our own toggleClass
+ace.hasClass = function(elem, className) {	return (" " + elem.className + " ").indexOf(" " + className + " ") > -1; }
+
+ace.addClass = function(elem, className) {
+ if (!ace.hasClass(elem, className)) {
+	var currentClass = elem.className;
+	elem.className = currentClass + (currentClass.length? " " : "") + className;
+ }
+}
+
+ace.removeClass = function(elem, className) {ace.replaceClass(elem, className);}
+
+ace.replaceClass = function(elem, className, newClass) {
+	var classToRemove = new RegExp(("(^|\\s)" + className + "(\\s|$)"), "i");
+	elem.className = elem.className.replace(classToRemove, function (match, p1, p2) {
+		return newClass? (p1 + newClass + p2) : " ";
+	}).replace(/^\s+|\s+$/g, "");
+}
+
+ace.toggleClass = function(elem, className) {
+	if(ace.hasClass(elem, className))
+		ace.removeClass(elem, className);
+	else ace.addClass(elem, className);
+}
+
+ace.isHTTMlElement = function(elem) {
+ return window.HTMLElement ? elem instanceof HTMLElement : ('nodeType' in elem ? elem.nodeType == 1 : false);
+}
+
+
+//data_storage instance used inside ace.settings etc
+ace.data = new ace.data_storage(ace.config.storage_method);

文件差異過大導致無法顯示
+ 3336 - 0
src/main/webapp/static/ace/assets/js/ace.js


+ 435 - 0
src/main/webapp/static/ace/assets/js/ace/ace.ajax-content.js

@@ -0,0 +1,435 @@
+/**
+ <b>Load content via Ajax </b>. For more information please refer to documentation #basics/ajax
+*/
+
+(function($ , undefined) {
+	var ajax_loaded_scripts = {}
+
+	function AceAjax(contentArea, settings) {
+		var $contentArea = $(contentArea);
+		var self = this;
+		$contentArea.attr('data-ajax-content', 'true');
+		
+		//get a list of 'data-*' attributes that override 'defaults' and 'settings'
+		var attrib_values = ace.helper.getAttrSettings(contentArea, $.fn.ace_ajax.defaults);
+		this.settings = $.extend({}, $.fn.ace_ajax.defaults, settings, attrib_values);
+
+
+		var working = false;
+		var $overlay = $();//empty set
+
+		this.force_reload = false;//set jQuery ajax's cache option to 'false' to reload content
+		this.loadUrl = function(hash, cache, manual_trigger) {
+			var url = false;
+			hash = hash.replace(/^(\#\!)?\#/, '');
+			
+			this.force_reload = (cache === false)
+			
+			if(typeof this.settings.content_url === 'function') url = this.settings.content_url(hash);
+			if(typeof url === 'string') this.getUrl(url, hash, manual_trigger);
+		}
+		
+		this.loadAddr = function(url, hash, cache) {
+			this.force_reload = (cache === false);
+			this.getUrl(url, hash, false);
+		}
+
+
+		this.reload = function() {
+			var hash = $.trim(window.location.hash);
+			if(!hash && this.settings.default_url) hash = this.settings.default_url;
+			
+			this.loadUrl(hash, false);
+		}
+		this.post = function(url, data, updateView, extraParams) {
+			var url = url || $.trim(location.href.replace(location.hash,''));
+			if(!url) return;
+			var data = data || {}
+			var updateView = updateView || false;
+			this.getUrl(url, null, false, 'POST', data, updateView, extraParams);
+		}
+		
+		
+		this.getUrl = function(url, hash, manual_trigger, method, data, updateView, extraParams) {
+			if(working) {
+				return;
+			}
+			
+			var method = method || 'GET';
+			var updateView = (method == 'GET') || (method == 'POST' && updateView == true)
+			var data = data || null;
+		
+			var event
+			$contentArea.trigger(event = $.Event('ajaxloadstart'), {url: url, hash: hash, method: method, data: data})
+			if (event.isDefaultPrevented()) return;
+			
+			self.startLoading();
+			
+			
+			var ajax_params = method == 'GET' ? {'url': url, 'cache': !this.force_reload} : {'url': url, 'method' : 'POST', 'data': data}
+			if(method == 'POST' && typeof extraParams == 'object') ajax_params = $.extend({}, ajax_params, extraParams);
+
+			$.ajax(ajax_params)
+			.error(function() {
+				$contentArea.trigger('ajaxloaderror', {url: url, hash: hash, method: method, data: data});
+				
+				self.stopLoading(true);
+			})
+			.done(function(result) {
+				$contentArea.trigger('ajaxloaddone', {url: url, hash: hash, method: method, data: data});
+				if(method == 'POST') {
+					var event
+					$contentArea.trigger(event = $.Event('ajaxpostdone', {url: url, data: data, result: result}))
+					if( event.isDefaultPrevented() ) updateView = false;
+				}
+				
+				
+				var link_element = null, link_text = '';
+				if(typeof self.settings.update_active === 'function') {
+					link_element = self.settings.update_active.call(null, hash, url, method, updateView);
+				}
+				else if(self.settings.update_active === true && hash) {
+					link_element = $('a[data-url="'+hash+'"]');
+					if(link_element.length > 0) {
+						var nav = link_element.closest('.nav');
+						if(nav.length > 0) {
+							nav.find('.active').each(function(){
+								var $class = 'active';
+								if( $(this).hasClass('hover') || self.settings.close_active ) $class += ' open';
+								
+								$(this).removeClass($class);							
+								if(self.settings.close_active) {
+									$(this).find(' > .submenu').css('display', '');
+								}
+							})
+							
+							var active_li = link_element.closest('li').addClass('active').parents('.nav li').addClass('active open');
+							nav.closest('.sidebar[data-sidebar-scroll=true]').each(function() {
+								var $this = $(this);
+								$this.ace_sidebar_scroll('reset');
+								if(manual_trigger == true) $this.ace_sidebar_scroll('scroll_to_active');//first time only
+							})
+						}
+					}
+				}
+
+				/////////
+				if(typeof self.settings.update_breadcrumbs === 'function') {
+					link_text = self.settings.update_breadcrumbs.call(null, hash, url, link_element, method, updateView);
+				}
+				else if(self.settings.update_breadcrumbs === true && link_element != null && link_element.length > 0) {
+					link_text = updateBreadcrumbs(link_element);
+				}
+				/////////
+				
+				$overlay.addClass('content-loaded').detach();
+				if(updateView) {
+					//convert "title" and "link" tags to "div" tags for later processing
+					result = String(result)
+						.replace(/<(title|link)([\s\>])/gi,'<div class="hidden ajax-append-$1"$2')
+						.replace(/<\/(title|link)\>/gi,'</div>')
+					$contentArea.empty().html(result);
+				}
+				
+				$(self.settings.loading_overlay || $contentArea).append($overlay);
+
+
+
+				//remove previous stylesheets inserted via ajax
+				if(updateView) setTimeout(function() {
+					$('head').find('link.ace-ajax-stylesheet').remove();
+
+					var main_selectors = ['link.ace-main-stylesheet', 'link#main-ace-style', 'link[href*="/ace.min.css"]', 'link[href*="/ace.css"]']
+					var ace_style = [];
+					for(var m = 0; m < main_selectors.length; m++) {
+						ace_style = $('head').find(main_selectors[m]).first();
+						if(ace_style.length > 0) break;
+					}
+					
+					$contentArea.find('.ajax-append-link').each(function(e) {
+						var $link = $(this);
+						if ( $link.attr('href') ) {
+							var new_link = jQuery('<link />', {type : 'text/css', rel: 'stylesheet', 'class': 'ace-ajax-stylesheet'})
+							if( ace_style.length > 0 ) new_link.insertBefore(ace_style);
+							else new_link.appendTo('head');
+							new_link.attr('href', $link.attr('href'));//we set "href" after insertion, for IE to work
+						}
+						$link.remove();
+					})
+				}, 10);
+
+				//////////////////////
+
+				if(typeof self.settings.update_title === 'function') {
+					self.settings.update_title.call(null, hash, url, link_text, method, updateView);
+				}
+				else if(self.settings.update_title === true && method == 'GET') {
+					updateTitle(link_text);
+				}
+
+				if( !manual_trigger && updateView ) {
+					$('html,body').animate({scrollTop: 0}, 250);
+				}
+
+				//////////////////////
+				$contentArea.trigger('ajaxloadcomplete', {url: url, hash: hash, method: method, data:data});
+				//////////////////////
+				
+				
+				//if result contains call to "loadScripts" then don't stopLoading now
+				var re = /\.(?:\s*)ace(?:_a|A)jax(?:\s*)\((?:\s*)(?:\'|\")loadScripts(?:\'|\")/;
+				if(result.match(re)) self.stopLoading();
+				else self.stopLoading(true);
+			})
+		}
+		
+		
+		///////////////////////
+		var fixPos = false;
+		var loadTimer = null;
+		this.startLoading = function() {
+			if(working) return;
+			working = true;
+			
+			if(!this.settings.loading_overlay && $contentArea.css('position') == 'static') {
+				$contentArea.css('position', 'relative');//for correct icon positioning
+				fixPos = true;
+			}
+				
+			$overlay.remove();
+			$overlay = $('<div class="ajax-loading-overlay"><i class="ajax-loading-icon '+(this.settings.loading_icon || '')+'"></i> '+this.settings.loading_text+'</div>')
+
+			if(this.settings.loading_overlay == 'body') $('body').append($overlay.addClass('ajax-overlay-body'));
+			else if(this.settings.loading_overlay) $(this.settings.loading_overlay).append($overlay);
+			else $contentArea.append($overlay);
+
+			
+			if(this.settings.max_load_wait !== false) 
+			 loadTimer = setTimeout(function() {
+				loadTimer = null;
+				if(!working) return;
+				
+				var event
+				$contentArea.trigger(event = $.Event('ajaxloadlong'))
+				if (event.isDefaultPrevented()) return;
+				
+				self.stopLoading(true);
+			 }, this.settings.max_load_wait * 1000);
+		}
+		
+		this.stopLoading = function(stopNow) {
+			if(stopNow === true) {
+				working = false;
+				
+				$overlay.remove();
+				if(fixPos) {
+					$contentArea.css('position', '');//restore previous 'position' value
+					fixPos = false;
+				}
+				
+				if(loadTimer != null) {
+					clearTimeout(loadTimer);
+					loadTimer = null;
+				}
+			}
+			else {
+				$overlay.addClass('almost-loaded');
+				
+				$contentArea.one('ajaxscriptsloaded.inner_call', function() {
+					self.stopLoading(true);
+					/**
+					if(window.Pace && Pace.running == true) {
+						Pace.off('done');
+						Pace.once('done', function() { self.stopLoading(true) })
+					}
+					else self.stopLoading(true);
+					*/
+				})
+			}
+		}
+		
+		this.working = function() {
+			return working;
+		}
+		///////////////////////
+		
+		
+		
+		function updateBreadcrumbs(link_element) {
+			var link_text = '';
+		 
+			//update breadcrumbs
+			var breadcrumbs = $('.breadcrumb');
+			if(breadcrumbs.length > 0 && breadcrumbs.is(':visible')) {
+				breadcrumbs.find('> li:not(:first-child)').remove();
+
+				var i = 0;		
+				link_element.parents('.nav li').each(function() {
+					var link = $(this).find('> a');
+					
+					var link_clone = link.clone();
+					link_clone.find('i,.fa,.glyphicon,.ace-icon,.menu-icon,.badge,.label').remove();
+					var text = link_clone.text();
+					link_clone.remove();
+					
+					var href = link.attr('href');
+
+					if(i == 0) {
+						var li = $('<li class="active"></li>').appendTo(breadcrumbs);
+						li.text(text);
+						link_text = text;
+					}
+					else {
+						var li = $('<li><a /></li>').insertAfter(breadcrumbs.find('> li:first-child'));
+						li.find('a').attr('href', href).text(text);
+					}
+					i++;
+				})
+			}
+			
+			return link_text;
+		 }
+		 
+		 function updateTitle(link_text) {
+			var $title = $contentArea.find('.ajax-append-title');
+			if($title.length > 0) {
+				document.title = $title.text();
+				$title.remove();
+			}
+			else if(link_text.length > 0) {
+				var extra = $.trim(String(document.title).replace(/^(.*)[\-]/, ''));//for example like " - Ace Admin"
+				if(extra) extra = ' - ' + extra;
+				link_text = $.trim(link_text) + extra;
+			}
+		 }
+		 
+		 
+		 this.loadScripts = function(scripts, callback) {
+			var scripts = scripts || [];
+			$.ajaxPrefilter('script', function(opts) {opts.cache = true});
+			setTimeout(function() {
+				//let's keep a list of loaded scripts so that we don't load them more than once!
+				
+				function finishLoading() {
+					if(typeof callback === 'function') callback();
+					$('.btn-group[data-toggle="buttons"] > .btn').button();
+					
+					$contentArea.trigger('ajaxscriptsloaded');
+				}
+				
+				//var deferreds = [];
+				var deferred_count = 0;//deferreds count
+				var resolved = 0;
+				for(var i = 0; i < scripts.length; i++) if(scripts[i]) {
+					(function() {
+						var script_name = "js-"+scripts[i].replace(/[^\w\d\-]/g, '-').replace(/\-\-/g, '-');
+						if( ajax_loaded_scripts[script_name] !== true )	deferred_count++;
+					})()
+				}
+				
+
+				function nextScript(index) {
+					index += 1;
+					if(index < scripts.length) loadScript(index);
+					else {
+						finishLoading();
+					}
+				}
+				
+				function loadScript(index) {
+					index = index || 0;
+					if(!scripts[index]) {//could be null sometimes
+						return nextScript(index);
+					}
+				
+					var script_name = "js-"+scripts[index].replace(/[^\w\d\-]/g, '-').replace(/\-\-/g, '-');
+					//only load scripts that are not loaded yet!
+					if( ajax_loaded_scripts[script_name] !== true ) {
+						$.getScript(scripts[index])
+						.done(function() {
+							ajax_loaded_scripts[script_name] = true;
+						})
+						//.fail(function() {
+						//})
+						.complete(function() {
+							resolved++;
+							if(resolved >= deferred_count && working) {
+								finishLoading();
+							}
+							else {
+								nextScript(index);
+							}
+						})
+					}
+					else {//script previoisly loaded
+						nextScript(index);
+					}
+				}
+				
+				
+				if (deferred_count > 0) {
+					loadScript();
+				}
+				else {
+					finishLoading();
+				}
+
+			}, 10)
+		}
+		
+		
+		
+		/////////////////
+		$(window)
+		.off('hashchange.ace_ajax')
+		.on('hashchange.ace_ajax', function(e, manual_trigger) {
+			var hash = $.trim(window.location.hash);
+			if(!hash || hash.length == 0) return;
+			
+			self.loadUrl(hash, null, manual_trigger);
+		}).trigger('hashchange.ace_ajax', [true]);
+		
+		var hash = $.trim(window.location.hash);
+		if(!hash && this.settings.default_url) window.location.hash = this.settings.default_url;
+
+	}//AceAjax
+
+
+
+	$.fn.aceAjax = $.fn.ace_ajax = function (option, value, value2, value3, value4) {
+		var method_call;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_ajax');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_ajax', (data = new AceAjax(this, options)));
+			if (typeof option === 'string' && typeof data[option] === 'function') {
+				if(value4 !== undefined) method_call = data[option](value, value2, value3, value4);
+				else if(value3 !== undefined) method_call = data[option](value, value2, value3);
+				else if(value2 !== undefined) method_call = data[option](value, value2);
+				else method_call == data[option](value);
+			}
+		});
+
+		return (method_call === undefined) ? $set : method_call;
+	}
+	
+	
+	
+	$.fn.aceAjax.defaults = $.fn.ace_ajax.defaults = {
+		content_url: false,
+		default_url: false,
+		loading_icon: 'fa fa-spin fa-spinner fa-2x orange',
+		loading_text: '',
+		loading_overlay: null,
+		update_breadcrumbs: true,
+		update_title: true,
+		update_active: true,
+		close_active: false,
+		max_load_wait: false
+     }
+
+})(window.jQuery);
+

+ 19 - 0
src/main/webapp/static/ace/assets/js/ace/ace.auto-container.js

@@ -0,0 +1,19 @@
+/**
+ <b>Auto Container</b> Adds .container when window size is above 1140px.
+ In Bootstrap you should stick with fixed width breakpoints.
+ You can use this feature to enable fixed container only when window size is above 1140px
+*/
+(function($ , undefined) {
+
+ $(window).on('resize.auto_container', function() {
+	var enable = $(window).width() > 1140;
+	try {
+		ace.settings.main_container_fixed(enable, false, false);
+	} catch(e) {
+		if(enable) $('.main-container,.navbar-container').addClass('container');
+		else $('.main-container,.navbar-container').removeClass('container');
+		$(document).trigger('settings.ace', ['main_container_fixed' , enable]);
+	}
+ }).triggerHandler('resize.auto_container');
+
+})(window.jQuery);

+ 81 - 0
src/main/webapp/static/ace/assets/js/ace/ace.auto-padding.js

@@ -0,0 +1,81 @@
+/**
+ <b>Auto content padding on fixed navbar &amp; breadcrumbs</b>.
+ Navbar's content and height is often predictable and when navbar is fixed we can add appropriate padding to content area using CSS.
+ 
+ But when navbar is fixed and its content size and height is not predictable we may need to add the necessary padding to content area dynamically using Javascript.
+
+ It's not often needed and you can have good results using CSS3 media queries to add necessary paddings based on window size.
+*/
+(function($ , undefined) {
+
+	var navbar = $('.navbar').eq(0);
+	var navbar_container = $('.navbar-container').eq(0);
+	
+	var sidebar = $('.sidebar').eq(0);
+	
+	var main_container = $('.main-container').get(0);
+	
+	var breadcrumbs = $('.breadcrumbs').eq(0);
+	var page_content = $('.page-content').get(0);
+	
+	var default_padding = 8
+
+	if(navbar.length > 0) {
+	  $(window).on('resize.auto_padding', function() {
+		if( navbar.css('position') == 'fixed' ) {
+			var padding1 = !ace.vars['nav_collapse'] ? navbar.outerHeight() : navbar_container.outerHeight();
+			padding1 = parseInt(padding1);
+			main_container.style.paddingTop = padding1 + 'px';
+			
+			if(ace.vars['non_auto_fixed'] && sidebar.length > 0) {
+				if(sidebar.css('position') == 'fixed') {
+					sidebar.get(0).style.top = padding1 + 'px';
+				}
+				else sidebar.get(0).style.top = '';
+			}
+
+			if( breadcrumbs.length > 0 ) {
+				if(breadcrumbs.css('position') == 'fixed') {
+					var padding2 = default_padding + breadcrumbs.outerHeight() + parseInt(breadcrumbs.css('margin-top'));
+					padding2 = parseInt(padding2);
+					page_content.style['paddingTop'] = padding2 + 'px';
+
+					if(ace.vars['non_auto_fixed']) breadcrumbs.get(0).style.top = padding1 + 'px';
+				} else {
+					page_content.style.paddingTop = '';
+					if(ace.vars['non_auto_fixed']) breadcrumbs.get(0).style.top = '';
+				}
+			}
+		}
+		else {
+			main_container.style.paddingTop = '';
+			page_content.style.paddingTop = '';
+			
+			if(ace.vars['non_auto_fixed']) {
+				if(sidebar.length > 0) {
+					sidebar.get(0).style.top = '';
+				}
+				if(breadcrumbs.length > 0) {
+					breadcrumbs.get(0).style.top = '';
+				}
+			}
+		}
+	  }).triggerHandler('resize.auto_padding');
+
+	  $(document).on('settings.ace.auto_padding', function(ev, event_name, event_val) {
+		if(event_name == 'navbar_fixed' || event_name == 'breadcrumbs_fixed') {
+			if(ace.vars['webkit']) {
+				//force new 'css position' values to kick in
+				navbar.get(0).offsetHeight;
+				if(breadcrumbs.length > 0) breadcrumbs.get(0).offsetHeight;
+			}
+			$(window).triggerHandler('resize.auto_padding');
+		}
+	  });
+	  
+	  /**$('#skin-colorpicker').on('change', function() {
+		$(window).triggerHandler('resize.auto_padding');
+	  });*/
+	}
+
+})(window.jQuery);

+ 534 - 0
src/main/webapp/static/ace/assets/js/ace/ace.js

@@ -0,0 +1,534 @@
+/**
+ Required. Ace's Basic File to Initiliaze Different Parts and Some Variables.
+*/
+
+
+//some basic variables
+(function(undefined) {
+	if( !('ace' in window) ) window['ace'] = {}
+	if( !('helper' in window['ace']) ) window['ace'].helper = {}
+	if( !('vars' in window['ace']) ) window['ace'].vars = {}
+	window['ace'].vars['icon'] = ' ace-icon ';
+	window['ace'].vars['.icon'] = '.ace-icon';
+
+	ace.vars['touch']	= ('ontouchstart' in window);//(('ontouchstart' in document.documentElement) || (window.DocumentTouch && document instanceof DocumentTouch));
+	
+	//sometimes the only good way to work around browser's pecularities is to detect them using user-agents
+	//though it's not accurate
+	var agent = navigator.userAgent
+	ace.vars['webkit'] = !!agent.match(/AppleWebKit/i)
+	ace.vars['safari'] = !!agent.match(/Safari/i) && !agent.match(/Chrome/i);
+	ace.vars['android'] = ace.vars['safari'] && !!agent.match(/Android/i)
+	ace.vars['ios_safari'] = !!agent.match(/OS ([4-9])(_\d)+ like Mac OS X/i) && !agent.match(/CriOS/i)
+	
+	ace.vars['ie'] = window.navigator.msPointerEnabled || (document.all && document.querySelector);//8-11
+	ace.vars['old_ie'] = document.all && !document.addEventListener;//8 and below
+	ace.vars['very_old_ie']	= document.all && !document.querySelector;//7 and below
+	ace.vars['firefox'] = 'MozAppearance' in document.documentElement.style;
+	
+	ace.vars['non_auto_fixed'] = ace.vars['android'] || ace.vars['ios_safari'];
+})();
+
+
+
+(function($ , undefined) {
+	//sometimes we try to use 'tap' event instead of 'click' if jquery mobile plugin is available
+	ace['click_event'] = ace.vars['touch'] && $.fn.tap ? 'tap' : 'click';
+})(jQuery);
+
+
+
+
+//document ready function
+jQuery(function($) {
+	basics();
+	enableSidebar();
+	
+	enableDemoAjax();
+	handleScrollbars();
+	
+	dropdownAutoPos();
+	
+	navbarHelpers();
+	sidebarTooltips();
+	
+	scrollTopBtn();
+	
+	someBrowserFix();
+	
+	bsCollapseToggle();
+	smallDeviceDropdowns();
+	
+	////////////////////////////
+
+	function basics() {
+		// for android and ios we don't use "top:auto" when breadcrumbs is fixed
+		if(ace.vars['non_auto_fixed']) {
+			$('body').addClass('mob-safari');
+		}
+
+		ace.vars['transition'] = !!$.support.transition.end
+	}
+	
+	function enableSidebar() {
+		//initiate sidebar function
+		var $sidebar = $('.sidebar');
+		if($.fn.ace_sidebar) $sidebar.ace_sidebar();
+		if($.fn.ace_sidebar_scroll) $sidebar.ace_sidebar_scroll({
+			//for other options please see documentation
+			'include_toggle': false || ace.vars['safari'] || ace.vars['ios_safari'] //true = include toggle button in the scrollbars
+		});
+		if($.fn.ace_sidebar_hover)	$sidebar.ace_sidebar_hover({
+			'sub_hover_delay': 750,
+			'sub_scroll_style': 'no-track scroll-thin scroll-margin scroll-visible'
+		});
+	}
+
+	
+	//Load content via ajax
+	function enableDemoAjax() {		
+		if(!$.fn.ace_ajax) return;
+ 
+		if(window.Pace) {
+			window.paceOptions = {
+				ajax: true,
+				document: true,
+				eventLag: false // disabled
+				//elements: {selectors: ['.page-content-area']}
+			}
+		}
+
+		var demo_ajax_options = {
+			 'close_active': true,
+			 
+			 'default_url': 'page/index',//default hash
+			 'content_url': function(hash) {
+				//***NOTE***
+				//this is for Ace demo only, you should change it to return a valid URL
+				//please refer to documentation for more info
+
+				if( !hash.match(/^page\//) ) return false;
+				var path = document.location.pathname;
+
+				//for example in Ace HTML demo version we convert /ajax/index.html#page/gallery to > /ajax/content/gallery.html and load it
+				if(path.match(/(\/ajax\/)(index\.html)?/))
+					return path.replace(/(\/ajax\/)(index\.html)?/, '/ajax/content/'+hash.replace(/^page\//, '')+'.html') ;
+
+				//for example in Ace PHP demo version we convert "ajax.php#page/dashboard" to "ajax.php?page=dashboard" and load it
+				return path + "?" + hash.replace(/\//, "=");
+			  }			  
+		}
+		   
+		//for IE9 and below we exclude PACE loader (using conditional IE comments)
+		//for other browsers we use the following extra ajax loader options
+		if(window.Pace) {
+			demo_ajax_options['loading_overlay'] = 'body';//the opaque overlay is applied to 'body'
+		}
+
+		//initiate ajax loading on this element( which is .page-content-area[data-ajax-content=true] in Ace's demo)
+		$('[data-ajax-content=true]').ace_ajax(demo_ajax_options)
+
+		//if general error happens and ajax is working, let's stop loading icon & PACE
+		$(window).on('error.ace_ajax', function() {
+			$('[data-ajax-content=true]').each(function() {
+				var $this = $(this);
+				if( $this.ace_ajax('working') ) {
+					if(window.Pace && Pace.running) Pace.stop();
+					$this.ace_ajax('stopLoading', true);
+				}
+			})
+		})
+	}
+
+	/////////////////////////////
+
+	function handleScrollbars() {
+		//add scrollbars for navbar dropdowns
+		var has_scroll = !!$.fn.ace_scroll;
+		if(has_scroll) $('.dropdown-content').ace_scroll({reset: false, mouseWheelLock: true})
+
+		//reset scrolls bars on window resize
+		if(has_scroll && !ace.vars['old_ie']) {//IE has an issue with widget fullscreen on ajax?!!!
+			$(window).on('resize.reset_scroll', function() {
+				$('.ace-scroll:not(.scroll-disabled)').not(':hidden').ace_scroll('reset');
+			});
+			if(has_scroll) $(document).on('settings.ace.reset_scroll', function(e, name) {
+				if(name == 'sidebar_collapsed') $('.ace-scroll:not(.scroll-disabled)').not(':hidden').ace_scroll('reset');
+			});
+		}
+	}
+
+
+	function dropdownAutoPos() {
+		//change a dropdown to "dropup" depending on its position
+		$(document).on('click.dropdown.pos', '.dropdown-toggle[data-position="auto"]', function() {
+			var offset = $(this).offset();
+			var parent = $(this.parentNode);
+
+			if ( parseInt(offset.top + $(this).height()) + 50 
+					>
+				(ace.helper.scrollTop() + ace.helper.winHeight() - parent.find('.dropdown-menu').eq(0).height()) 
+				) parent.addClass('dropup');
+			else parent.removeClass('dropup');
+		});
+	}
+
+	
+	function navbarHelpers() {
+		//prevent dropdowns from hiding when a from is clicked
+		/**$(document).on('click', '.dropdown-navbar form', function(e){
+			e.stopPropagation();
+		});*/
+
+
+		//disable navbar icon animation upon click
+		$('.ace-nav [class*="icon-animated-"]').closest('a').one('click', function(){
+			var icon = $(this).find('[class*="icon-animated-"]').eq(0);
+			var $match = icon.attr('class').match(/icon\-animated\-([\d\w]+)/);
+			icon.removeClass($match[0]);
+		});
+
+
+		//prevent dropdowns from hiding when a tab is selected
+		$(document).on('click', '.dropdown-navbar .nav-tabs', function(e){
+			e.stopPropagation();
+			var $this , href
+			var that = e.target
+			if( ($this = $(e.target).closest('[data-toggle=tab]')) && $this.length > 0) {
+				$this.tab('show');
+				e.preventDefault();
+				$(window).triggerHandler('resize.navbar.dropdown')
+			}
+		});
+	}
+
+	
+	function sidebarTooltips() {
+		//tooltip in sidebar items
+		$('.sidebar .nav-list .badge[title],.sidebar .nav-list .badge[title]').each(function() {
+			var tooltip_class = $(this).attr('class').match(/tooltip\-(?:\w+)/);
+			tooltip_class = tooltip_class ? tooltip_class[0] : 'tooltip-error';
+			$(this).tooltip({
+				'placement': function (context, source) {
+					var offset = $(source).offset();
+
+					if( parseInt(offset.left) < parseInt(document.body.scrollWidth / 2) ) return 'right';
+					return 'left';
+				},
+				container: 'body',
+				template: '<div class="tooltip '+tooltip_class+'"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+			});
+		});
+		
+		//or something like this if items are dynamically inserted
+		/**
+		$('.sidebar').tooltip({
+			'placement': function (context, source) {
+				var offset = $(source).offset();
+
+				if( parseInt(offset.left) < parseInt(document.body.scrollWidth / 2) ) return 'right';
+				return 'left';
+			},
+			selector: '.nav-list .badge[title],.nav-list .label[title]',
+			container: 'body',
+			template: '<div class="tooltip tooltip-error"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+		});
+		*/
+	}
+	
+	
+
+	function scrollTopBtn() {
+		//the scroll to top button
+		var scroll_btn = $('.btn-scroll-up');
+		if(scroll_btn.length > 0) {
+			var is_visible = false;
+			$(window).on('scroll.scroll_btn', function() {
+				var scroll = ace.helper.scrollTop();
+				var h = ace.helper.winHeight();
+				var body_sH = document.body.scrollHeight;
+				if(scroll > parseInt(h / 4) || (scroll > 0 && body_sH >= h && h + scroll >= body_sH - 1)) {//|| for smaller pages, when reached end of page
+					if(!is_visible) {
+						scroll_btn.addClass('display');
+						is_visible = true;
+					}
+				} else {
+					if(is_visible) {
+						scroll_btn.removeClass('display');
+						is_visible = false;
+					}
+				}
+			}).triggerHandler('scroll.scroll_btn');
+
+			scroll_btn.on(ace.click_event, function(){
+				var duration = Math.min(500, Math.max(100, parseInt(ace.helper.scrollTop() / 3)));
+				$('html,body').animate({scrollTop: 0}, duration);
+				return false;
+			});
+		}
+	}
+
+
+	
+	function someBrowserFix() {
+		//chrome and webkit have a problem here when resizing from 479px to more
+		//we should force them redraw the navbar!
+		if( ace.vars['webkit'] ) {
+			var ace_nav = $('.ace-nav').get(0);
+			if( ace_nav ) $(window).on('resize.webkit_fix' , function(){
+				ace.helper.redraw(ace_nav);
+			});
+		}
+		
+		
+		//fix an issue with ios safari, when an element is fixed and an input receives focus
+		if(ace.vars['ios_safari']) {
+		  $(document).on('ace.settings.ios_fix', function(e, event_name, event_val) {
+			if(event_name != 'navbar_fixed') return;
+
+			$(document).off('focus.ios_fix blur.ios_fix', 'input,textarea,.wysiwyg-editor');
+			if(event_val == true) {
+			  $(document).on('focus.ios_fix', 'input,textarea,.wysiwyg-editor', function() {
+				$(window).on('scroll.ios_fix', function() {
+					var navbar = $('#navbar').get(0);
+					if(navbar) ace.helper.redraw(navbar);
+				});
+			  }).on('blur.ios_fix', 'input,textarea,.wysiwyg-editor', function() {
+				$(window).off('scroll.ios_fix');
+			  })
+			}
+		  }).triggerHandler('ace.settings.ios_fix', ['navbar_fixed', $('#navbar').css('position') == 'fixed']);
+		}
+	}
+
+	
+	
+	function bsCollapseToggle() {
+		//bootstrap collapse component icon toggle
+		$(document).on('hide.bs.collapse show.bs.collapse', function (ev) {
+			var panel_id = ev.target.getAttribute('id')
+			var panel = $('a[href*="#'+ panel_id+'"]');
+			if(panel.length == 0) panel = $('a[data-target*="#'+ panel_id+'"]');
+			if(panel.length == 0) return;
+
+			panel.find(ace.vars['.icon']).each(function(){
+				var $icon = $(this)
+
+				var $match
+				var $icon_down = null
+				var $icon_up = null
+				if( ($icon_down = $icon.attr('data-icon-show')) ) {
+					$icon_up = $icon.attr('data-icon-hide')
+				}
+				else if( $match = $icon.attr('class').match(/fa\-(.*)\-(up|down)/) ) {
+					$icon_down = 'fa-'+$match[1]+'-down'
+					$icon_up = 'fa-'+$match[1]+'-up'
+				}
+
+				if($icon_down) {
+					if(ev.type == 'show') $icon.removeClass($icon_down).addClass($icon_up)
+						else $icon.removeClass($icon_up).addClass($icon_down)
+						
+					return false;//ignore other icons that match, one is enough
+				}
+
+			});
+		})
+	}
+	
+
+	
+	//in small devices display navbar dropdowns like modal boxes
+	function smallDeviceDropdowns() {
+	  if(ace.vars['old_ie']) return;
+	  
+	  $(document)
+	  .on('shown.bs.dropdown.navbar', '.ace-nav > li.dropdown-modal', function(e) {
+		adjustNavbarDropdown.call(this);
+		var self = this;
+		$(window).on('resize.navbar.dropdown', function() {
+			adjustNavbarDropdown.call(self);
+		})
+	  })
+	  .on('hidden.bs.dropdown.navbar', '.ace-nav > li.dropdown-modal', function(e) {
+		$(window).off('resize.navbar.dropdown');
+		resetNavbarDropdown.call(this);
+	  })
+	 
+	  function adjustNavbarDropdown() {
+		var $sub = $(this).find('> .dropdown-menu');
+
+		if( $sub.css('position') == 'fixed' ) {
+			var win_width = parseInt($(window).width());
+			var offset_w = win_width > 320 ? 60 : (win_width > 240 ? 40 : 30);
+			var avail_width = parseInt(win_width) - offset_w;
+			var avail_height = parseInt($(window).height()) - 30;
+			
+			var width = parseInt(Math.min(avail_width , 320));
+			//we set 'width' here for text wrappings and spacings to take effect before calculating scrollHeight
+			$sub.css('width', width);
+
+			var tabbed = false;
+			var extra_parts = 0;
+			var dropdown_content = $sub.find('.tab-pane.active .dropdown-content.ace-scroll');
+			if(dropdown_content.length == 0) dropdown_content = $sub.find('.dropdown-content.ace-scroll');
+			else tabbed = true;
+
+			var parent_menu = dropdown_content.closest('.dropdown-menu');
+			var scrollHeight = $sub[0].scrollHeight;
+			if(dropdown_content.length == 1) {
+				//sometimes there's no scroll-content, for example in detached scrollbars
+				var content =  dropdown_content.find('.scroll-content')[0];
+				if(content) {
+					scrollHeight = content.scrollHeight;
+				}
+			
+				extra_parts += parent_menu.find('.dropdown-header').outerHeight();
+				extra_parts += parent_menu.find('.dropdown-footer').outerHeight();
+				
+				var tab_content = parent_menu.closest('.tab-content');
+				if( tab_content.length != 0 ) {
+					extra_parts += tab_content.siblings('.nav-tabs').eq(0).height();
+				}
+			}
+			
+
+			
+			var height = parseInt(Math.min(avail_height , 480, scrollHeight + extra_parts));
+			var left = parseInt(Math.abs((avail_width + offset_w - width)/2));
+			var top = parseInt(Math.abs((avail_height + 30 - height)/2));
+
+			
+			var zindex = parseInt($sub.css('z-index')) || 0;
+
+			$sub.css({'height': height, 'left': left, 'right': 'auto', 'top': top - (!tabbed ? 1 : 3)});
+			if(dropdown_content.length == 1) {
+				if(!ace.vars['touch']) {
+					dropdown_content.ace_scroll('update', {size: height - extra_parts}).ace_scroll('enable').ace_scroll('reset');
+				}
+				else {
+					dropdown_content
+					.ace_scroll('disable').css('max-height', height - extra_parts).addClass('overflow-scroll');
+				}
+			}
+			$sub.css('height', height + (!tabbed ? 2 : 7));//for bottom border adjustment and tab content paddings
+			
+			
+			if($sub.hasClass('user-menu')) {
+				$sub.css('height', '');//because of user-info hiding/showing at different widths, which changes above 'scrollHeight', so we remove it!
+				
+				//user menu is re-positioned in small widths
+				//but we need to re-position again in small heights as well (modal mode)
+				var user_info = $(this).find('.user-info');
+				if(user_info.length == 1 && user_info.css('position') == 'fixed') {
+					user_info.css({'left': left, 'right': 'auto', 'top': top, 'width': width - 2, 'max-width': width - 2, 'z-index': zindex + 1});
+				}
+				else user_info.css({'left': '', 'right': '', 'top': '', 'width': '', 'max-width': '', 'z-index': ''});
+			}
+			
+			//dropdown's z-index is limited by parent .navbar's z-index (which doesn't make sense because dropdowns are fixed!)
+			//so for example when in 'content-slider' page, fixed modal toggle buttons go above are dropdowns
+			//so we increase navbar's z-index to fix this!
+			$(this).closest('.navbar.navbar-fixed-top').css('z-index', zindex);
+		}
+		else {
+			if($sub.length != 0) resetNavbarDropdown.call(this, $sub);
+		}
+	  }
+
+	  //reset scrollbars and user menu
+	  function resetNavbarDropdown($sub) {
+		$sub = $sub || $(this).find('> .dropdown-menu');
+	  
+	    if($sub.length > 0) {
+			$sub
+			.css({'width': '', 'height': '', 'left': '', 'right': '', 'top': ''})
+			.find('.dropdown-content').each(function() {
+				if(ace.vars['touch']) {
+					$(this).css('max-height', '').removeClass('overflow-scroll');
+				}
+
+				var size = parseInt($(this).attr('data-size') || 0) || $.fn.ace_scroll.defaults.size;
+				$(this).ace_scroll('update', {size: size}).ace_scroll('enable').ace_scroll('reset');
+			})
+			
+			if( $sub.hasClass('user-menu') ) {
+				var user_info = 
+				$(this).find('.user-info')
+				.css({'left': '', 'right': '', 'top': '', 'width': '', 'max-width': '', 'z-index': ''});
+			}
+		}
+		
+		$(this).closest('.navbar').css('z-index', '');
+	  }
+	}
+
+});//jQuery document ready
+
+
+
+
+
+//some ace helper functions
+(function($$ , undefined) {//$$ is ace.helper
+ $$.unCamelCase = function(str) {
+	return str.replace(/([a-z])([A-Z])/g, function(match, c1, c2){ return c1+'-'+c2.toLowerCase() })
+ }
+ $$.strToVal = function(str) {
+	var res = str.match(/^(?:(true)|(false)|(null)|(\-?[\d]+(?:\.[\d]+)?)|(\[.*\]|\{.*\}))$/i);
+
+	var val = str;
+	if(res) {
+		if(res[1]) val = true;
+		else if(res[2]) val = false;
+		else if(res[3]) val = null;	
+		else if(res[4]) val = parseFloat(str);
+		else if(res[5]) {
+			try { val = JSON.parse(str) }
+			catch (err) {}
+		}
+	}
+
+	return val;
+ }
+ $$.getAttrSettings = function(elem, attr_list, prefix) {
+	if(!elem) return;
+	var list_type = attr_list instanceof Array ? 1 : 2;
+	//attr_list can be Array or Object(key/value)
+	var prefix = prefix ? prefix.replace(/([^\-])$/ , '$1-') : '';
+	prefix = 'data-' + prefix;
+
+	var settings = {}
+	for(var li in attr_list) if(attr_list.hasOwnProperty(li)) {
+		var name = list_type == 1 ? attr_list[li] : li;
+		var attr_val, attr_name = $$.unCamelCase(name.replace(/[^A-Za-z0-9]{1,}/g , '-')).toLowerCase()
+
+		if( ! ((attr_val = elem.getAttribute(prefix + attr_name))  ) ) continue;
+		settings[name] = $$.strToVal(attr_val);
+	}
+
+	return settings;
+ }
+
+ $$.scrollTop = function() {
+	return document.scrollTop || document.documentElement.scrollTop || document.body.scrollTop
+ }
+ $$.winHeight = function() {
+	return window.innerHeight || document.documentElement.clientHeight;
+ }
+ $$.redraw = function(elem, force) {
+	if(!elem) return;
+	var saved_val = elem.style['display'];
+	elem.style.display = 'none';
+	elem.offsetHeight;
+	if(force !== true) {
+		elem.style.display = saved_val;
+	}
+	else {
+		//force redraw for example in old IE
+		setTimeout(function() {
+			elem.style.display = saved_val;
+		}, 10);
+	}
+ }
+})(ace.helper);

+ 156 - 0
src/main/webapp/static/ace/assets/js/ace/ace.onpage-help.js

@@ -0,0 +1,156 @@
+jQuery(function($) {
+	var help = null;//Onpage_Help instance
+
+	//for ace demo pages, we temporarily disable fixed navbar, etc ... when help is enabled
+	//because when an element is fixed, its highlighted help section should also become fixed!
+	var page_settings = {}
+	var before_enable_help = function() {
+		$('#btn-scroll-up').css('z-index', 1000000);//bring btn-scroll-up  higher , to be over our help area
+
+		//save our current state of navbar, sidebar and breadcrumbs before enabling help
+		page_settings['navbar'] = ace.settings.is('navbar', 'fixed')
+		page_settings['sidebar'] = ace.settings.is('sidebar', 'fixed')
+		page_settings['breadcrumbs'] = ace.settings.is('breadcrumbs', 'fixed')
+		
+		ace.settings.navbar_fixed(null, false , false);//now disable fixed navbar, which automatically disabled fixed sidebar and breadcrumbs
+	}
+	var after_disable_help = function() {
+		$('#btn-scroll-up').css('z-index', '');
+
+		//restore fixed state of navbar, sidebar, etc
+		if( page_settings['breadcrumbs'] ) ace.settings.breadcrumbs_fixed(null, true, false);
+		if( page_settings['sidebar'] ) ace.settings.sidebar_fixed(null, true, false);
+		if( page_settings['navbar'] ) ace.settings.navbar_fixed(null, true, false);
+	}
+	
+	var get_file_url = function(url, language) {
+		//function that return the real path to a file which is being loaded
+		return this.settings.base + '/' + url;
+	}
+	var get_section_url = function(section_name) {
+		//according to a section_name such as `basics/navbar.toggle` return the file url which contains help content
+		section_name = section_name || '';
+
+		//for example convert `basic/navbar.layout.brand` to `basic/navbar`
+		//because 'layout.brand' section is inside `basic/navbar.html` file
+		var url = section_name.replace(/\..*$/g, '');
+
+		var parts = url.split('/');
+		if(parts.length == 1) {
+			//for example convert `changes` to `changes/index.html`
+			if(url.length == 0) url = 'intro';//or convert `empty string` to `intro/index.html`
+			url = url + '/index.html';
+		}
+		else if(parts.length > 1) {
+			//for example convert `basics/navbar.layout` to `basics/navbar.html`
+			url = url + '.html';
+		}
+		return this.settings.base + '/docs/sections/' + url;
+	}
+	var get_img_url = function(src) {
+		return this.settings.base + '/docs/' +src;
+	}
+
+	/**
+	var code_highlight = function(e, language) {
+		//'this' refers to 'Onpage_Help' object invoking this function
+		if(typeof e === 'string') {
+			if(typeof language === 'string') {
+				//called when a file (html,css,etc) is loaded
+				//'e' is a piece of code
+				//maybe highlight the syntax it according to `language` and return result
+			}
+			else {
+				//called before new help content is being displayed
+				//'e' is a string which may contain <pre>...</pre> code sections
+				//which you may want to highlight the code, or for example convert them < to &lt; and > to &gt;
+				//and return the result
+			}
+		}
+		else if(typeof e === 'object') {
+			//called when new help content is displayed
+			//'e' is an html element which may have "pre" children that you can syntax-highlight
+		}
+	}
+	*/
+
+
+	function startHelp() {
+		if(help !== null) return;//already created?
+
+		help = new Onpage_Help({
+			'include_all': false,
+			'base': ace.vars['base'] || '../..',
+			'file_url': get_file_url,
+			'section_url': get_section_url,
+			
+			'img_url': get_img_url,
+			
+			'before_enable': before_enable_help,
+			'after_disable': after_disable_help
+			
+			//,'code_highlight': code_highlight
+		})
+
+		
+		
+		var help_container = $('#onpage-help-container');
+		//add a custom button to enable/disable help
+		help_container.append('<div class="ace-settings-container onpage-help-toggle-container">\
+			<div id="onpage-help-toggle-btn" class="btn btn-app btn-xs btn-info ace-settings-btn onpage-help-toggle-btn">\
+				<i class="onpage-help-toggle-text ace-icon fa fa-question bigger-150"></i>\
+			</div>\
+		</div>');
+
+		$('#onpage-help-toggle-btn').on('click', function(e) {
+			e.preventDefault();
+			toggleHelp();
+		})
+		
+		//add .container class to help container div when our content is put inside a ".container"
+		$(document).on('settings.ace.help', function(ev, event_name, fixed) {
+		   if(event_name == 'main_container_fixed') {
+			  if(fixed) help_container.addClass('container');
+			  else help_container.removeClass('container');
+		   }
+		}).triggerHandler('settings.ace.help', ['main_container_fixed', $('.main-container').hasClass('container')])
+		
+		//in ajax mode when a content is loaded via ajax, we may want to update help sections
+		$(document).on('ajaxloadcomplete.ace.help', function() {
+			help.update_sections();
+		});
+	}
+	
+	function toggleHelp() {
+		help.toggle();
+		
+		var toggle_btn = $('#onpage-help-toggle-btn');
+		toggle_btn.find('.onpage-help-toggle-text').removeClass('onpage-help-toggle-text');
+		toggle_btn.toggleClass('btn-grey btn-info').parent().toggleClass('active');
+	}
+
+
+	$(window).on('hashchange.start_help', function(e) {
+		if(help == null && window.location.hash == '#help') {
+			startHelp();
+
+			//add #help tag to sidebar links to enable help when navigating to the page
+			$(document).on('click.start_help', '.sidebar .nav-list a', function() {
+				var href = $(this).attr('href');
+				if( !href.match(/\#help$/) ) $(this).attr('href', href+'#help');
+			});
+		}
+	}).triggerHandler('hashchange.start_help');
+
+
+	//some buttons inside demo pages that launch a help section
+	$(document).on('click', '.btn-display-help', function(e) {
+		e.preventDefault();
+
+		startHelp();
+		if( !help.is_active() ) toggleHelp();
+
+		var section = $(this).attr('href');
+		help.show_section_help(section);
+	});
+});

文件差異過大導致無法顯示
+ 19 - 0
src/main/webapp/static/ace/assets/js/ace/ace.searchbox-autocomplete.js


+ 155 - 0
src/main/webapp/static/ace/assets/js/ace/ace.settings-rtl.js

@@ -0,0 +1,155 @@
+/**
+<b>RTL</b> (right-to-left direction for Arabic, Hebrew, Persian languages).
+It's good for demo only.
+You should hard code RTL-specific changes inside your HTML/server-side code.
+Dynamically switching to RTL using Javascript is not a good idea.
+Please refer to documentation for more info.
+*/
+
+
+(function($ , undefined) {
+ //Switching to RTL (right to left) Mode
+ $('#ace-settings-rtl').removeAttr('checked').on('click', function(){
+	switch_direction();
+ });
+ 
+ 
+ //>>> you should hard code changes inside HTML for RTL direction
+ //you shouldn't use this function to switch direction
+ //this is only for dynamically switching for demonstration
+ //take a look at this function to see what changes should be made
+ //also take a look at docs for some tips
+ var switch_direction = function() {
+	if($('#ace-rtl-stylesheet').length == 0) {
+		//let's load RTL stylesheet only when needed!
+		var ace_style = $('head').find('link.ace-main-stylesheet');
+		if(ace_style.length == 0) {
+			ace_style = $('head').find('link[href*="/ace.min.css"],link[href*="/ace-part2.min.css"]');
+			if(ace_style.length == 0) {
+				ace_style = $('head').find('link[href*="/ace.css"],link[href*="/ace-part2.css"]');
+			}
+		}
+		
+		var ace_skins = $('head').find('link#ace-skins-stylesheet');
+		var stylesheet_url = ace_style.first().attr('href').replace(/(\.min)?\.css$/i , '-rtl$1.css');
+		$.ajax({
+			'url': stylesheet_url
+		}).done(function() {
+			var new_link = jQuery('<link />', {type : 'text/css', rel: 'stylesheet', 'id': 'ace-rtl-stylesheet'})
+			if(ace_skins.length > 0) {
+				new_link.insertAfter(ace_skins);
+			}
+			else if(ace_style.length > 0){
+				new_link.insertAfter(ace_style.last());
+			}
+			else new_link.appendTo('head');
+		
+			new_link.attr('href', stylesheet_url);
+			//we set "href" after insertion, for IE to work
+			
+			applyChanges();
+			if(window.Pace && Pace.running)	Pace.stop();
+		})		
+	}
+	else {
+		applyChanges();
+	}
+	
+	//in ajax when new content is loaded, we dynamically apply RTL changes again
+	//please note that this is only for Ace demo
+	//for info about RTL see Ace's docs
+	$('.page-content-area[data-ajax-content=true]').on('ajaxscriptsloaded.rtl', function() {
+		if( $('body').hasClass('rtl') ) {
+			applyChanges(this);
+		}
+	});
+
+	/////////////////////////
+	function applyChanges(el) {
+		var $body = $(document.body);
+		if(!el) $body.toggleClass('rtl');//el is 'body'
+
+		el = el || document.body;		
+		var $container = $(el);
+		$container
+		//toggle pull-right class on dropdown-menu
+		.find('.dropdown-menu:not(.datepicker-dropdown,.colorpicker)').toggleClass('dropdown-menu-right')
+		.end()
+		//swap pull-left & pull-right
+		.find('.pull-right:not(.dropdown-menu,blockquote,.profile-skills .pull-right)').removeClass('pull-right').addClass('tmp-rtl-pull-right')
+		.end()
+		.find('.pull-left:not(.dropdown-submenu,.profile-skills .pull-left)').removeClass('pull-left').addClass('pull-right')
+		.end()
+		.find('.tmp-rtl-pull-right').removeClass('tmp-rtl-pull-right').addClass('pull-left')
+		.end()
+		
+		.find('.chosen-select').toggleClass('chosen-rtl').next().toggleClass('chosen-rtl');
+		
+
+		function swap_classes(class1, class2) {
+			$container
+			 .find('.'+class1).removeClass(class1).addClass('tmp-rtl-'+class1)
+			 .end()
+			 .find('.'+class2).removeClass(class2).addClass(class1)
+			 .end()
+			 .find('.tmp-rtl-'+class1).removeClass('tmp-rtl-'+class1).addClass(class2)
+		}
+
+		swap_classes('align-left', 'align-right');
+		swap_classes('no-padding-left', 'no-padding-right');
+		swap_classes('arrowed', 'arrowed-right');
+		swap_classes('arrowed-in', 'arrowed-in-right');
+		swap_classes('tabs-left', 'tabs-right');
+		swap_classes('messagebar-item-left', 'messagebar-item-right');//for inbox page
+		
+		$('.modal.aside-vc').ace_aside('flip').ace_aside('insideContainer');
+		
+		
+		//mirror all icons and attributes that have a "fa-*-right|left" attrobute
+		$container.find('.fa').each(function() {
+			if(this.className.match(/ui-icon/) || $(this).closest('.fc-button').length > 0) return;
+			//skip mirroring icons of plugins that have built in RTL support
+
+			var l = this.attributes.length;
+			for(var i = 0 ; i < l ; i++) {
+				var val = this.attributes[i].value;
+				if(val.match(/fa\-(?:[\w\-]+)\-left/)) 
+					this.attributes[i].value = val.replace(/fa\-([\w\-]+)\-(left)/i , 'fa-$1-right')
+				 else if(val.match(/fa\-(?:[\w\-]+)\-right/)) 
+					this.attributes[i].value = val.replace(/fa\-([\w\-]+)\-(right)/i , 'fa-$1-left')
+			}
+		});
+		
+		//browsers are incosistent with horizontal scroll and RTL
+		//so let's make our scrollbars LTR and wrap the content inside RTL
+		var rtl = $body.hasClass('rtl');
+		if(rtl)	{
+			$container.find('.scroll-hz').addClass('make-ltr')
+			.find('.scroll-content')
+			.wrapInner('<div class="make-rtl" />');
+			$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('changeDir', 'right');
+		}
+		else {
+			//remove the wrap
+			$container.find('.scroll-hz').removeClass('make-ltr')
+			.find('.make-rtl').children().unwrap();
+			$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('changeDir', 'left');
+		}
+		if($.fn.ace_scroll) $container.find('.scroll-hz').ace_scroll('reset') //to reset scrollLeft
+
+		//redraw the traffic pie chart on homepage with a different parameter
+		try {
+			var placeholder = $('#piechart-placeholder');
+			if(placeholder.length > 0) {
+				var pos = $body.hasClass('rtl') ? 'nw' : 'ne';//draw on north-west or north-east?
+				placeholder.data('draw').call(placeholder.get(0) , placeholder, placeholder.data('chart'), pos);
+			}
+		}catch(e) {}
+		
+		
+		ace.helper.redraw(el, true);
+	}
+ }
+})(jQuery);
+
+

+ 142 - 0
src/main/webapp/static/ace/assets/js/ace/ace.settings-skin.js

@@ -0,0 +1,142 @@
+/**
+ <b>Select a different skin</b>. It's good for demo only.
+ You should hard code skin-specific changes inside your HTML/server-side code.
+ Please refer to documentation for more info.
+*/
+
+(function($ , undefined) {
+  try {
+	$('#skin-colorpicker').ace_colorpicker({'auto_pos': false});
+  } catch(e) {}
+
+  $('#skin-colorpicker').on('change', function(){
+	var skin_class = $(this).find('option:selected').data('skin');
+
+	if($('#ace-skins-stylesheet').length == 0) {
+		//let's load skins stylesheet only when needed!
+		var ace_style = $('head').find('link.ace-main-stylesheet');
+		if(ace_style.length == 0) {
+			ace_style = $('head').find('link[href*="/ace.min.css"],link[href*="/ace-part2.min.css"]');
+			if(ace_style.length == 0) {
+				ace_style = $('head').find('link[href*="/ace.css"],link[href*="/ace-part2.css"]');
+			}
+		}
+		
+		var stylesheet_url = ace_style.first().attr('href').replace(/(\.min)?\.css$/i , '-skins$1.css');
+		$.ajax({
+			'url': stylesheet_url
+		}).done(function() {
+			var new_link = jQuery('<link />', {type : 'text/css', rel: 'stylesheet', 'id': 'ace-skins-stylesheet'})
+			if(ace_style.length > 0){
+				new_link.insertAfter(ace_style.last());
+			}
+			else new_link.appendTo('head');
+	
+			new_link.attr('href', stylesheet_url);
+			//we set "href" after insertion, for IE to work
+			
+			applyChanges(skin_class);
+			if(window.Pace && Pace.running)	Pace.stop();
+		})
+	}
+	else {
+		applyChanges(skin_class);
+	}
+
+
+	function applyChanges(skin_class) {
+		//skin cookie tip
+		var body = $(document.body);
+		body.removeClass('no-skin skin-1 skin-2 skin-3');
+		//if(skin_class != 'skin-0') {
+			body.addClass(skin_class);
+			ace.data.set('skin', skin_class);
+			//save the selected skin to cookies
+			//which can later be used by your server side app to set the skin
+			//for example: <body class="<?php echo $_COOKIE['ace_skin']; ?>"
+		//} else ace.data.remove('skin');
+		
+		var skin3_colors = ['red', 'blue', 'green', ''];
+
+		
+			//undo skin-1
+			$('.ace-nav > li.grey').removeClass('dark');
+			
+			//undo skin-2
+			$('.ace-nav > li').removeClass('no-border margin-1');
+			$('.ace-nav > li:not(:last-child)').removeClass('light-pink').find('> a > '+ace.vars['.icon']).removeClass('pink').end().eq(0).find('.badge').removeClass('badge-warning');
+			$('.sidebar-shortcuts .btn')
+			.removeClass('btn-pink btn-white')
+			.find(ace.vars['.icon']).removeClass('white');
+			
+			//undo skin-3
+			$('.ace-nav > li.grey').removeClass('red').find('.badge').removeClass('badge-yellow');
+			$('.sidebar-shortcuts .btn').removeClass('btn-primary btn-white')
+			var i = 0;
+			$('.sidebar-shortcuts .btn').each(function() {
+				$(this).find(ace.vars['.icon']).removeClass(skin3_colors[i++]);
+			})
+		
+		
+
+		
+		var skin0_buttons = ['btn-success', 'btn-info', 'btn-warning', 'btn-danger'];
+		if(skin_class == 'no-skin') {
+			var i = 0;
+			$('.sidebar-shortcuts .btn').each(function() {
+				$(this).attr('class', 'btn ' + skin0_buttons[i++%4]);
+			})
+			
+			$('.sidebar[data-sidebar-scroll=true]').ace_sidebar_scroll('updateStyle', '');
+			$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('updateStyle', 'no-track scroll-thin');
+		}
+
+		else if(skin_class == 'skin-1') {
+			$('.ace-nav > li.grey').addClass('dark');
+			var i = 0;
+			$('.sidebar-shortcuts')
+			.find('.btn').each(function() {
+				$(this).attr('class', 'btn ' + skin0_buttons[i++%4]);
+			})
+			
+			$('.sidebar[data-sidebar-scroll=true]').ace_sidebar_scroll('updateStyle', 'scroll-white no-track');
+			$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('updateStyle', 'no-track scroll-thin scroll-white');
+		}
+
+		else if(skin_class == 'skin-2') {
+			$('.ace-nav > li').addClass('no-border margin-1');
+			$('.ace-nav > li:not(:last-child)').addClass('light-pink').find('> a > '+ace.vars['.icon']).addClass('pink').end().eq(0).find('.badge').addClass('badge-warning');
+			
+			$('.sidebar-shortcuts .btn').attr('class', 'btn btn-white btn-pink')
+			.find(ace.vars['.icon']).addClass('white');
+			
+			$('.sidebar[data-sidebar-scroll=true]').ace_sidebar_scroll('updateStyle', 'scroll-white no-track');
+			$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('updateStyle', 'no-track scroll-thin scroll-white');
+		}
+
+		//skin-3
+		//change shortcut buttons classes, this should be hard-coded if you want to choose this skin
+		else if(skin_class == 'skin-3') {
+			body.addClass('no-skin');//because skin-3 has many parts of no-skin as well
+			
+			$('.ace-nav > li.grey').addClass('red').find('.badge').addClass('badge-yellow');
+			
+			var i = 0;
+			$('.sidebar-shortcuts .btn').each(function() {
+				$(this).attr('class', 'btn btn-primary btn-white');
+				$(this).find(ace.vars['.icon']).addClass(skin3_colors[i++]);
+			})
+			
+			$('.sidebar[data-sidebar-scroll=true]').ace_sidebar_scroll('updateStyle', 'scroll-dark no-track');
+			$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('updateStyle', 'no-track scroll-thin');
+		}
+
+		//some sizing differences may be there in skins, so reset scrollbar size
+		$('.sidebar[data-sidebar-scroll=true]').ace_sidebar_scroll('reset')
+		//$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('reset')
+		
+		if(ace.vars['old_ie']) ace.helper.redraw(document.body, true);
+	}
+
+ })
+})(jQuery);

+ 88 - 0
src/main/webapp/static/ace/assets/js/ace/ace.settings.js

@@ -0,0 +1,88 @@
+/**
+ <b>Settings box</b>. It's good for demo only. You don't need this.
+*/
+(function($ , undefined) {
+
+ $('#ace-settings-btn').on(ace.click_event, function(e){
+	e.preventDefault();
+
+	$(this).toggleClass('open');
+	$('#ace-settings-box').toggleClass('open');
+ })
+
+ $('#ace-settings-navbar').on('click', function(){
+	ace.settings.navbar_fixed(null, this.checked);//@ ace-extra.js
+	//$(window).triggerHandler('resize.navbar');
+
+	//force redraw?
+	//if(ace.vars['webkit']) ace.helper.redraw(document.body);
+ }).each(function(){this.checked = ace.settings.is('navbar', 'fixed')})
+
+ $('#ace-settings-sidebar').on('click', function(){
+	ace.settings.sidebar_fixed(null, this.checked);//@ ace-extra.js
+
+	//if(ace.vars['webkit']) ace.helper.redraw(document.body);
+ }).each(function(){this.checked = ace.settings.is('sidebar', 'fixed')})
+
+ $('#ace-settings-breadcrumbs').on('click', function(){
+	ace.settings.breadcrumbs_fixed(null, this.checked);//@ ace-extra.js
+
+	//if(ace.vars['webkit']) ace.helper.redraw(document.body);
+ }).each(function(){this.checked = ace.settings.is('breadcrumbs', 'fixed')})
+
+ $('#ace-settings-add-container').on('click', function(){
+	ace.settings.main_container_fixed(null, this.checked);//@ ace-extra.js
+
+	//if(ace.vars['webkit']) ace.helper.redraw(document.body);
+ }).each(function(){this.checked = ace.settings.is('main-container', 'fixed')})
+
+
+
+ $('#ace-settings-compact').on('click', function(){
+	if(this.checked) {
+		$('#sidebar').addClass('compact');
+		var hover = $('#ace-settings-hover');
+		if( hover.length > 0 ) {
+			hover.removeAttr('checked').trigger('click');
+		}
+	}
+	else {
+		$('#sidebar').removeClass('compact');
+		$('#sidebar[data-sidebar-scroll=true]').ace_sidebar_scroll('reset')
+	}
+	
+	if(ace.vars['old_ie']) ace.helper.redraw($('#sidebar')[0], true);
+ })/*.removeAttr('checked')*/
+
+
+ $('#ace-settings-highlight').on('click', function(){
+	if(this.checked) $('#sidebar .nav-list > li').addClass('highlight');
+	else $('#sidebar .nav-list > li').removeClass('highlight');
+	
+	if(ace.vars['old_ie']) ace.helper.redraw($('#sidebar')[0]);
+ })/*.removeAttr('checked')*/
+
+
+ $('#ace-settings-hover').on('click', function(){
+	if($('#sidebar').hasClass('h-sidebar')) return;
+	if(this.checked) {
+		$('#sidebar li').addClass('hover')
+		.filter('.open').removeClass('open').find('> .submenu').css('display', 'none');
+		//and remove .open items
+	}
+	else {
+		$('#sidebar li.hover').removeClass('hover');
+
+		var compact = $('#ace-settings-compact');
+		if( compact.length > 0 && compact.get(0).checked ) {
+			compact.trigger('click');
+		}
+	}
+	
+	$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('reset')
+	$('.sidebar[data-sidebar-scroll=true]').ace_sidebar_scroll('reset')
+	
+	if(ace.vars['old_ie']) ace.helper.redraw($('#sidebar')[0]);
+ })/*.removeAttr('checked')*/
+
+})(jQuery);

+ 506 - 0
src/main/webapp/static/ace/assets/js/ace/ace.sidebar-scroll-1.js

@@ -0,0 +1,506 @@
+/**
+ <b>Scrollbars for sidebar</b>. This approach can <span class="text-danger">only</span> be used on <u>fixed</u> sidebar.
+ It doesn't use <u>"overflow:hidden"</u> CSS property and therefore can be used with <u>.hover</u> submenus and minimized sidebar.
+ Except when in mobile view and menu toggle button is not in the navbar.
+*/
+
+(function($ , undefined) {
+	//if( !$.fn.ace_scroll ) return;
+
+	var old_safari = ace.vars['safari'] && navigator.userAgent.match(/version\/[1-5]/i)
+	//NOTE
+	//Safari on windows has not been updated for a long time.
+	//And it has a problem when sidebar is fixed & scrollable and there is a CSS3 animation inside page content.
+	//Very probably windows users of safari have migrated to another browser by now!
+
+	var is_element_pos =
+	'getComputedStyle' in window ?
+	//el.offsetHeight is used to force redraw and recalculate 'el.style.position' esp. for webkit!
+	function(el, pos) { el.offsetHeight; return window.getComputedStyle(el).position == pos }
+	:
+	function(el, pos) { el.offsetHeight; return $(el).css('position') == pos }
+	
+		
+	function Sidebar_Scroll(sidebar , settings) {
+		var self = this;
+
+		var $window = $(window);
+		var $sidebar = $(sidebar),
+			$nav = $sidebar.find('.nav-list'),
+			$toggle = $sidebar.find('.sidebar-toggle').eq(0),
+			$shortcuts = $sidebar.find('.sidebar-shortcuts').eq(0);
+			
+		var nav = $nav.get(0);
+		if(!nav) return;
+		
+		
+		var attrib_values = ace.helper.getAttrSettings(sidebar, $.fn.ace_sidebar_scroll.defaults);
+		this.settings = $.extend({}, $.fn.ace_sidebar_scroll.defaults, settings, attrib_values);
+		var scroll_to_active = self.settings.scroll_to_active;
+	
+	
+		var ace_sidebar = $sidebar.ace_sidebar('ref');
+		$sidebar.attr('data-sidebar-scroll', 'true');
+			
+		
+		var scroll_div = null,
+			scroll_content = null,
+			scroll_content_div = null,
+			bar = null,
+			track = null,
+			ace_scroll = null;
+
+
+		this.is_scrolling = false;
+		var _initiated = false;
+		this.sidebar_fixed = is_element_pos(sidebar, 'fixed');
+		
+		var $avail_height, $content_height;
+
+		
+		var available_height = function() {
+			//available window space
+			var offset = $nav.parent().offset();//because `$nav.offset()` considers the "scrolled top" amount as well
+			if(self.sidebar_fixed) offset.top -= ace.helper.scrollTop();
+
+			return $window.innerHeight() - offset.top - ( self.settings.include_toggle ? 0 : $toggle.outerHeight() );
+		}
+		var content_height = function() {
+			return nav.clientHeight;//we don't use nav.scrollHeight here, because hover submenus are considered in calculating scrollHeight despite position=absolute!
+		}
+
+		
+		
+		var initiate = function(on_page_load) {
+			if( _initiated ) return;
+			if( !self.sidebar_fixed ) return;//eligible??
+			//return if we want scrollbars only on "fixed" sidebar and sidebar is not "fixed" yet!
+
+			//initiate once
+			$nav.wrap('<div class="nav-wrap-up pos-rel" />');
+			$nav.after('<div><div></div></div>');
+
+			$nav.wrap('<div class="nav-wrap" />');
+			if(!self.settings.include_toggle) $toggle.css({'z-index': 1});
+			if(!self.settings.include_shortcuts) $shortcuts.css({'z-index': 99});
+
+			scroll_div = $nav.parent().next()
+			.ace_scroll({
+				size: available_height(),
+				//reset: true,
+				mouseWheelLock: true,
+				hoverReset: false,
+				dragEvent: true,
+				styleClass: self.settings.scroll_style,
+				touchDrag: false//disable touch drag event on scrollbars, we'll add a custom one later
+			})
+			.closest('.ace-scroll').addClass('nav-scroll');
+			
+			ace_scroll = scroll_div.data('ace_scroll');
+
+			scroll_content = scroll_div.find('.scroll-content').eq(0);
+			scroll_content_div = scroll_content.find(' > div').eq(0);
+			
+			track = $(ace_scroll.get_track());
+			bar = track.find('.scroll-bar').eq(0);
+
+			if(self.settings.include_shortcuts && $shortcuts.length != 0) {
+				$nav.parent().prepend($shortcuts).wrapInner('<div />');
+				$nav = $nav.parent();
+			}
+			if(self.settings.include_toggle && $toggle.length != 0) {
+				$nav.append($toggle);
+				$nav.closest('.nav-wrap').addClass('nav-wrap-t');//it just helps to remove toggle button's top border and restore li:last-child's bottom border
+			}
+
+			$nav.css({position: 'relative'});
+			if( self.settings.scroll_outside == true ) scroll_div.addClass('scrollout');
+			
+			nav = $nav.get(0);
+			nav.style.top = 0;
+			scroll_content.on('scroll.nav', function() {
+				nav.style.top = (-1 * this.scrollTop) + 'px';
+			});
+			
+			//mousewheel library available?
+			$nav.on(!!$.event.special.mousewheel ? 'mousewheel.ace_scroll' : 'mousewheel.ace_scroll DOMMouseScroll.ace_scroll', function(event){
+				if( !self.is_scrolling || !ace_scroll.is_active() ) {
+					return !self.settings.lock_anyway;
+				}
+				//transfer $nav's mousewheel event to scrollbars
+				return scroll_div.trigger(event);
+			});
+			
+			$nav.on('mouseenter.ace_scroll', function() {
+				track.addClass('scroll-hover');
+			}).on('mouseleave.ace_scroll', function() {
+				track.removeClass('scroll-hover');
+			});
+
+
+			/**
+			$(document.body).on('touchmove.nav', function(event) {
+				if( self.is_scrolling && $.contains(sidebar, event.target) ) {
+					event.preventDefault();
+					return false;
+				}
+			})
+			*/
+
+			//you can also use swipe event in a similar way //swipe.nav
+			var content = scroll_content.get(0);
+			$nav.on('ace_drag.nav', function(event) {
+				if( !self.is_scrolling || !ace_scroll.is_active() ) {
+					event.retval.cancel = true;
+					return;
+				}
+				
+				//if submenu hover is being scrolled let's cancel sidebar scroll!
+				if( $(event.target).closest('.can-scroll').length != 0 ) {
+					event.retval.cancel = true;
+					return;
+				}
+
+				if(event.direction == 'up' || event.direction == 'down') {
+					
+					ace_scroll.move_bar(true);
+					
+					var distance = event.dy;
+					
+					distance = parseInt(Math.min($avail_height, distance))
+					if(Math.abs(distance) > 2) distance = distance * 2;
+					
+					if(distance != 0) {
+						content.scrollTop = content.scrollTop + distance;
+						nav.style.top = (-1 * content.scrollTop) + 'px';
+					}
+				}
+			});
+			
+
+			//for drag only
+			if(self.settings.smooth_scroll) {
+				$nav
+				.on('touchstart.nav MSPointerDown.nav pointerdown.nav', function(event) {
+					$nav.css('transition-property', 'none');
+					bar.css('transition-property', 'none');
+				})
+				.on('touchend.nav touchcancel.nav MSPointerUp.nav MSPointerCancel.nav pointerup.nav pointercancel.nav', function(event) {
+					$nav.css('transition-property', 'top');
+					bar.css('transition-property', 'top');
+				});
+			}
+			
+			
+
+			if(old_safari && !self.settings.include_toggle) {
+				var toggle = $toggle.get(0);
+				if(toggle) scroll_content.on('scroll.safari', function() {
+					ace.helper.redraw(toggle);
+				});
+			}
+
+			_initiated = true;
+
+			//if the active item is not visible, scroll down so that it becomes visible
+			//only the first time, on page load
+			if(on_page_load == true) {
+				self.reset();//try resetting at first
+
+				if( scroll_to_active ) {
+					self.scroll_to_active();
+				}
+				scroll_to_active = false;
+			}
+			
+			
+			
+			if( typeof self.settings.smooth_scroll === 'number' && self.settings.smooth_scroll > 0) {
+				$nav.css({'transition-property': 'top', 'transition-duration': (self.settings.smooth_scroll / 1000).toFixed(2)+'s'})
+				bar.css({'transition-property': 'top', 'transition-duration': (self.settings.smooth_scroll / 1500).toFixed(2)+'s'})
+				
+				scroll_div
+				.on('drag.start', function(e) {
+					e.stopPropagation();
+					$nav.css('transition-property', 'none')
+				})
+				.on('drag.end', function(e) {
+					e.stopPropagation();
+					$nav.css('transition-property', 'top')
+				});
+			}
+			
+			if(ace.vars['android']) {
+				//force hide address bar, because its changes don't trigger window resize and become kinda ugly
+				var val = ace.helper.scrollTop();
+				if(val < 2) {
+					window.scrollTo( val, 0 );
+					setTimeout( function() {
+						self.reset();
+					}, 20 );
+				}
+				
+				var last_height = ace.helper.winHeight() , new_height;
+				$(window).on('scroll.ace_scroll', function() {
+					if(self.is_scrolling && ace_scroll.is_active()) {
+						new_height = ace.helper.winHeight();
+						if(new_height != last_height) {
+							last_height = new_height;
+							self.reset();
+						}
+					}
+				});
+			}
+		}
+		
+		
+		
+		
+		this.scroll_to_active = function() {
+			if( !ace_scroll || !ace_scroll.is_active() ) return;
+			try {
+				//sometimes there's no active item or not 'offsetTop' property
+				var $active;
+				
+				var vars = ace_sidebar['vars']()
+
+				var nav_list = $sidebar.find('.nav-list')
+				if(vars['minimized'] && !vars['collapsible']) {
+					$active = nav_list.find('> .active')
+				}
+				else {
+					$active = $nav.find('> .active.hover')
+					if($active.length == 0)	$active = $nav.find('.active:not(.open)')
+				}
+
+			
+				var top = $active.outerHeight();
+				nav_list = nav_list.get(0);
+				var active = $active.get(0);
+				while(active != nav_list) {
+					top += active.offsetTop;
+					active = active.parentNode;
+				}
+
+				var scroll_amount = top - scroll_div.height();
+				if(scroll_amount > 0) {
+					nav.style.top = -scroll_amount + 'px';
+					scroll_content.scrollTop(scroll_amount);
+				}
+			}catch(e){}
+		}
+		
+		
+		
+		this.reset = function(recalc) {
+			if(recalc === true) {
+				this.sidebar_fixed = is_element_pos(sidebar, 'fixed');
+			}
+			
+			if( !this.sidebar_fixed ) {
+				this.disable();
+				return;//eligible??
+			}
+
+			//return if we want scrollbars only on "fixed" sidebar and sidebar is not "fixed" yet!
+
+			if( !_initiated ) initiate();
+			//initiate scrollbars if not yet
+			
+			var vars = ace_sidebar['vars']();
+			
+
+			//enable if:
+			//menu is not collapsible mode (responsive navbar-collapse mode which has default browser scrollbar)
+			//menu is not horizontal or horizontal but mobile view (which is not navbar-collapse)
+			//and available height is less than nav's height
+			
+
+			var enable_scroll = !vars['collapsible'] && !vars['horizontal']
+								&& ($avail_height = available_height()) < ($content_height = nav.clientHeight);
+								//we don't use nav.scrollHeight here, because hover submenus are considered in calculating scrollHeight despite position=absolute!
+
+								
+			this.is_scrolling = true;
+			if( enable_scroll ) {
+				scroll_content_div.css({height: $content_height, width: 8});
+				scroll_div.prev().css({'max-height' : $avail_height})
+				ace_scroll.update({size: $avail_height})
+				ace_scroll.enable();
+				ace_scroll.reset();
+			}
+			if( !enable_scroll || !ace_scroll.is_active() ) {
+				if(this.is_scrolling) this.disable();
+			}
+			else {
+				$sidebar.addClass('sidebar-scroll');
+			}
+			
+			//return this.is_scrolling;
+		}
+		
+		
+		
+		this.disable = function() {
+			this.is_scrolling = false;
+			if(scroll_div) {
+				scroll_div.css({'height' : '', 'max-height' : ''});
+				scroll_content_div.css({height: '', width: ''});//otherwise it will have height and takes up some space even when invisible
+				scroll_div.prev().css({'max-height' : ''})
+				ace_scroll.disable();
+			}
+
+			if(parseInt(nav.style.top) < 0 && self.settings.smooth_scroll && $.support.transition.end) {
+				$nav.one($.support.transition.end, function() {
+					$sidebar.removeClass('sidebar-scroll');
+					$nav.off('.trans');
+				});
+			} else {
+				$sidebar.removeClass('sidebar-scroll');
+			}
+
+			nav.style.top = 0;
+		}
+		
+		this.prehide = function(height_change) {
+			if(!this.is_scrolling || ace_sidebar.get('minimized')) return;//when minimized submenu's toggle should have no effect
+			
+			if(content_height() + height_change < available_height()) {
+				this.disable();
+			}
+			else if(height_change < 0) {
+				//if content height is decreasing
+				//let's move nav down while a submenu is being hidden
+				var scroll_top = scroll_content.scrollTop() + height_change
+				if(scroll_top < 0) return;
+
+				nav.style.top = (-1 * scroll_top) + 'px';
+			}
+		}
+		
+		
+		this._reset = function(recalc) {
+			if(recalc === true) {
+				this.sidebar_fixed = is_element_pos(sidebar, 'fixed');
+			}
+			
+			if(ace.vars['webkit']) 
+				setTimeout(function() { self.reset() } , 0);
+			else this.reset();
+		}
+		
+		
+		this.set_hover = function() {
+			if(track) track.addClass('scroll-hover');
+		}
+		
+		this.get = function(name) {
+			if(this.hasOwnProperty(name)) return this[name];
+		}
+		this.set = function(name, value) {
+			if(this.hasOwnProperty(name)) this[name] = value;
+		}
+		this.ref = function() {
+			//return a reference to self
+			return this;
+		}
+		
+		this.updateStyle = function(styleClass) {
+			if(ace_scroll == null) return;
+			ace_scroll.update({styleClass: styleClass});
+		}
+
+		
+		//change scrollbar size after a submenu is hidden/shown
+		//but don't change if sidebar is minimized
+		$sidebar.on('hidden.ace.submenu.sidebar_scroll shown.ace.submenu.sidebar_scroll', '.submenu', function(e) {
+			e.stopPropagation();
+
+			if( !ace_sidebar.get('minimized') ) {
+				//webkit has a little bit of a glitch!!!
+				self._reset();
+				if( e.type == 'shown' ) self.set_hover();
+			}
+		});
+
+		
+		initiate(true);//true = on_page_load
+	}
+	
+
+	
+	//reset on document and window changes
+	$(document).on('settings.ace.sidebar_scroll', function(ev, event_name, event_val){
+		$('.sidebar[data-sidebar-scroll=true]').each(function() {
+			var $this = $(this);
+			var sidebar_scroll = $this.ace_sidebar_scroll('ref');
+
+			if( event_name == 'sidebar_collapsed' && is_element_pos(this, 'fixed') ) {
+				if( $this.attr('data-sidebar-hover') == 'true' ) $this.ace_sidebar_hover('reset');
+				sidebar_scroll._reset();
+			}
+			else if( event_name === 'sidebar_fixed' || event_name === 'navbar_fixed' ) {
+				var is_scrolling = sidebar_scroll.get('is_scrolling');
+				var sidebar_fixed = is_element_pos(this, 'fixed')
+				sidebar_scroll.set('sidebar_fixed', sidebar_fixed);
+
+				if(sidebar_fixed && !is_scrolling) {
+					sidebar_scroll._reset();
+				}
+				else if( !sidebar_fixed ) {
+					sidebar_scroll.disable();
+				}
+			}
+		
+		});
+	});
+	
+	$(window).on('resize.ace.sidebar_scroll', function(){
+		$('.sidebar[data-sidebar-scroll=true]').each(function() {
+			var $this = $(this);
+			if( $this.attr('data-sidebar-hover') == 'true' ) $this.ace_sidebar_hover('reset');
+			/////////////
+			var sidebar_scroll = $(this).ace_sidebar_scroll('ref');
+			
+			var sidebar_fixed = is_element_pos(this, 'fixed')
+			sidebar_scroll.set('sidebar_fixed', sidebar_fixed);
+			sidebar_scroll._reset();
+		});
+	})
+	
+
+	
+	
+	 /////////////////////////////////////////////
+	 if(!$.fn.ace_sidebar_scroll) {
+	  $.fn.ace_sidebar_scroll = function (option, value) {
+		var method_call;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_sidebar_scroll');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_sidebar_scroll', (data = new Sidebar_Scroll(this, options)));
+			if (typeof option === 'string' && typeof data[option] === 'function') {
+				method_call = data[option](value);
+			}
+		});
+
+		return (method_call === undefined) ? $set : method_call;
+	 }
+	 
+	 
+	 $.fn.ace_sidebar_scroll.defaults = {
+		'scroll_to_active': true,
+		'include_shortcuts': true,
+		'include_toggle': false,
+		'smooth_scroll': 150,
+		'scroll_outside': false,
+		'scroll_style': '',
+		'lock_anyway': false
+     }
+	 
+	}
+
+})(window.jQuery);

+ 366 - 0
src/main/webapp/static/ace/assets/js/ace/ace.sidebar-scroll-2.js

@@ -0,0 +1,366 @@
+/**
+ <b>Scrollbars for sidebar <i>(second style)</i></b>. This approach can be used on fixed or normal sidebar.
+ It uses <u>"overflow:hidden"</u> so you can't use <u>.hover</u> submenus and it will be disabled when sidebar is minimized.
+ It may also be slightly faster especially when resizing browser window.
+ <i class="glyphicon glyphicon-flash"></i> <u class="text-primary">Native browser scrollbars</u> are used in touch devices.
+*/
+
+(function($ , undefined) {
+	//if( !$.fn.ace_scroll ) return;
+	
+	var hasTouch = ace.vars['touch'];
+	var nativeScroll = /**ace.vars['old_ie'] ||*/ hasTouch;
+
+	var old_safari = ace.vars['safari'] && navigator.userAgent.match(/version\/[1-5]/i)
+	//NOTE
+	//Safari on windows has not been updated for a long time.
+	//And it has a problem when sidebar is fixed&scrollable and there is a CSS3 animation inside page content.
+	//Very probably windows users of safari have migrated to another browser by now!
+
+
+
+	var is_element_pos =
+	'getComputedStyle' in window ?
+	//el.offsetHeight is used to force redraw and recalculate 'el.style.position' esp. for webkit!
+	function(el, pos) { el.offsetHeight; return window.getComputedStyle(el).position == pos }
+	:
+	function(el, pos) { el.offsetHeight; return $(el).css('position') == pos }
+
+	
+	function Sidebar_Scroll(sidebar , settings) {
+		var self = this;
+		
+		var $window = $(window);
+		
+		var $sidebar = $(sidebar),
+			$nav = $sidebar.find('.nav-list'),
+			$toggle = $sidebar.find('.sidebar-toggle').eq(0),
+			$shortcuts = $sidebar.find('.sidebar-shortcuts').eq(0),
+
+			nav = $nav.get(0);
+
+		if(!nav) return;
+
+		
+		var attrib_values = ace.helper.getAttrSettings(sidebar, $.fn.ace_sidebar_scroll.defaults);
+		this.settings = $.extend({}, $.fn.ace_sidebar_scroll.defaults, settings, attrib_values);
+				
+		var scroll_to_active = self.settings.scroll_to_active;
+		this.only_if_fixed = self.settings.only_if_fixed;
+		
+
+			
+		var ace_sidebar = $sidebar.ace_sidebar('ref');
+		$sidebar.attr('data-sidebar-scroll', 'true');
+
+		var submenu_hover = function() {
+			return $sidebar.first('li.hover > .submenu').css('position') == 'absolute'
+		}
+		
+		
+		var scroll_div = null,
+			scroll_content = null,
+			scroll_content_div = null,
+			bar = null,
+			ace_scroll = null;
+
+		this.is_scrolling = false;
+		var _initiated = false;
+		this.sidebar_fixed = is_element_pos(sidebar, 'fixed');
+
+		var $avail_height, $content_height;
+
+		var available_height = function() {
+			//available window space
+			var offset = $nav.parent().offset();//because `$nav.offset()` considers the "scrolled top" amount as well
+			if(self.sidebar_fixed) offset.top -= ace.helper.scrollTop();
+
+			return $window.innerHeight() - offset.top - ( self.settings.include_toggle ? 0 : $toggle.outerHeight() );
+		}
+		var content_height = function() {
+			return nav.scrollHeight;
+		}
+		
+		var initiate = function(on_page_load) {
+			if( _initiated ) return;
+			if( (self.only_if_fixed && !self.sidebar_fixed) || submenu_hover() ) return;//eligible??
+			//return if we want scrollbars only on "fixed" sidebar and sidebar is not "fixed" yet!
+
+			//initiate once
+			$nav.wrap('<div class="nav-wrap-up" />');
+			if(self.settings.include_shortcuts && $shortcuts.length != 0) $nav.parent().prepend($shortcuts);
+			if(self.settings.include_toggle && $toggle.length != 0) $nav.parent().append($toggle);
+
+			if(!nativeScroll) {
+				scroll_div = $nav.parent()
+				.ace_scroll({
+					size: available_height(),
+					reset: true,
+					mouseWheelLock: true,
+					lockAnyway: self.settings.lock_anyway,
+					styleClass: self.settings.scroll_style,
+					hoverReset: false
+				})
+				.closest('.ace-scroll').addClass('nav-scroll');
+				
+				ace_scroll = scroll_div.data('ace_scroll');
+
+				scroll_content = scroll_div.find('.scroll-content').eq(0);
+
+				if(old_safari && !self.settings.include_toggle) {
+					var toggle = $toggle.get(0);
+					if(toggle) scroll_content.on('scroll.safari', function() {
+						ace.helper.redraw(toggle);
+					});
+				}
+			}
+			else {
+				$nav.parent().addClass('sidebar-scroll-native').css('max-height', available_height());
+				ace_scroll = true;
+				scroll_div = scroll_content = $nav.parent();
+			}
+
+			_initiated = true;
+
+			//if the active item is not visible, scroll down so that it becomes visible
+			//only the first time, on page load
+			if(on_page_load == true) {
+				self.reset();//try resetting at first
+
+				if( scroll_to_active ) {
+					self.scroll_to_active();
+				}
+				scroll_to_active = false;
+			}
+		}
+		
+		
+		this.scroll_to_active = function() {
+			if( !nativeScroll && (!ace_scroll || !ace_scroll.is_active()) ) return;
+			try {
+				//sometimes there's no active item or not 'offsetTop' property
+				var $active;
+				
+				var vars = ace_sidebar['vars']();
+
+				var nav_list = $sidebar.find('.nav-list')
+				if(vars['minimized'] && !vars['collapsible']) {
+					$active = nav_list.find('> .active')
+				}
+				else {
+					$active = $nav.find('> .active.hover')
+					if($active.length == 0)	$active = $nav.find('.active:not(.open)')
+				}
+
+				var top = $active.outerHeight();
+
+				nav_list = nav_list.get(0);
+				var active = $active.get(0);
+				while(active != nav_list) {
+					top += active.offsetTop;
+					active = active.parentNode;
+				}
+
+				var scroll_amount = top - scroll_div.height();
+				if(scroll_amount > 0) {
+					scroll_content.scrollTop(scroll_amount);
+				}
+			}catch(e){}
+			
+			
+			
+		}
+		
+		
+		this.reset = function(recalc) {
+			if(recalc === true) {
+				this.sidebar_fixed = is_element_pos(sidebar, 'fixed');
+			}
+		
+			if( (this.only_if_fixed && !this.sidebar_fixed) || submenu_hover() ) {
+				this.disable();
+				return;//eligible??
+			}
+			//return if we want scrollbars only on "fixed" sidebar and sidebar is not "fixed" yet!
+
+			if( !_initiated ) initiate();
+			//initiate scrollbars if not yet
+			
+			$sidebar.addClass('sidebar-scroll');
+			
+			var vars = ace_sidebar['vars']();
+			
+
+			//enable if:
+			//menu is not minimized
+			//menu is not collapsible mode (responsive navbar-collapse mode which has default browser scroller)
+			//menu is not horizontal or horizontal but mobile view (which is not navbar-collapse)
+			//and available height is less than nav's height			
+			var enable_scroll = !vars['minimized'] && !vars['collapsible'] && !vars['horizontal']
+								&& ($avail_height = available_height()) < ($content_height = nav.parentNode.scrollHeight);
+
+			this.is_scrolling = true;
+			if( enable_scroll && ace_scroll ) {
+				//scroll_content_div.css({height: $content_height, width: 8});
+				//scroll_div.prev().css({'max-height' : $avail_height})
+				if(!nativeScroll) {
+					ace_scroll.update({size: $avail_height});
+					ace_scroll.enable();
+					ace_scroll.reset();
+				}
+				else {
+					$nav.parent().addClass('sidebar-scroll-native').css('max-height', $avail_height);
+				}
+			}
+			
+			if(!nativeScroll) {
+				if( !enable_scroll || !ace_scroll.is_active() ) {
+					if(this.is_scrolling) this.disable();
+				}
+			}
+			else {
+				if( !enable_scroll && this.is_scrolling ) this.disable();
+			}
+			
+			//return is_scrolling;
+		}
+		
+		this.disable = function() {
+			this.is_scrolling = false;
+			if(ace_scroll) {
+				if(!nativeScroll) ace_scroll.disable();
+				else $nav.parent().removeClass('sidebar-scroll-native').css('max-height', '');
+			}
+			
+			$sidebar.removeClass('sidebar-scroll');
+		}
+		
+		this.prehide = function(height_change) {
+			if(!this.is_scrolling || ace_sidebar.get('minimized')) return;//when minimized submenu's toggle should have no effect
+
+			if(content_height() + height_change < available_height()) {
+				this.disable();
+			}
+			else if(height_change < 0) {
+				//if content height is decreasing
+				//let's move nav down while a submenu is being hidden
+				var scroll_top = scroll_content.scrollTop() + height_change
+				if(scroll_top < 0) return;
+
+				scroll_content.scrollTop(scroll_top);
+			}
+		}
+		
+		this._reset = function(recalc) {
+			if(recalc === true) {
+				this.sidebar_fixed = is_element_pos(sidebar, 'fixed');
+			}
+			
+			if(ace.vars['webkit']) 
+				setTimeout(function() { self.reset() } , 0);
+			else this.reset();
+		}
+		
+		this.get = function(name) {
+			if(this.hasOwnProperty(name)) return this[name];
+		}
+		this.set = function(name, value) {
+			if(this.hasOwnProperty(name)) this[name] = value;
+		}
+		this.ref = function() {
+			//return a reference to self
+			return this;
+		}
+		
+		this.updateStyle = function(styleClass) {
+			if(!ace_scroll || nativeScroll) return;
+			ace_scroll.update({styleClass: styleClass});
+		}
+		
+		
+		//change scrollbar size after a submenu is hidden/shown
+		//but don't change if sidebar is minimized
+		$sidebar.on('hidden.ace.submenu.sidebar_scroll shown.ace.submenu.sidebar_scroll', '.submenu', function(e) {
+			e.stopPropagation();
+
+			if( !ace_sidebar.get('minimized') ) {
+				//webkit has a little bit of a glitch!!!
+				self._reset();
+			}
+		})
+
+		initiate(true);//true = on_page_load
+	}
+		
+
+
+	//reset on document and window changes
+	$(document).on('settings.ace.sidebar_scroll', function(ev, event_name, event_val){
+		$('.sidebar[data-sidebar-scroll=true]').each(function() {
+			var $this = $(this);
+			var sidebar_scroll = $this.ace_sidebar_scroll('ref');
+
+			if( event_name == 'sidebar_collapsed' ) {
+				if( $this.attr('data-sidebar-hover') == 'true' ) $this.ace_sidebar_hover('reset');
+			
+				if(event_val == true) sidebar_scroll.disable();//disable scroll if collapsed
+				else sidebar_scroll.reset();
+			}
+			else if( event_name === 'sidebar_fixed' || event_name === 'navbar_fixed' ) {
+				var is_scrolling = sidebar_scroll.get('is_scrolling');
+				var sidebar_fixed = is_element_pos(this, 'fixed')
+				sidebar_scroll.set('sidebar_fixed', sidebar_fixed);
+
+				if(sidebar_fixed && !is_scrolling) {
+					sidebar_scroll.reset();
+				}
+				else if( !sidebar_fixed  && sidebar_scroll.get('only_if_fixed') ) {
+					sidebar_scroll.disable();
+				}
+			}		
+		})
+	})
+
+	$(window).on('resize.ace.sidebar_scroll', function(){
+		$('.sidebar[data-sidebar-scroll=true]').each(function() {
+			var sidebar_scroll = $(this).ace_sidebar_scroll('ref');
+			
+			var sidebar_fixed = is_element_pos(this, 'fixed')
+			sidebar_scroll.set('sidebar_fixed', sidebar_fixed);
+			sidebar_scroll.reset();
+		});
+	})
+	
+	
+	
+	/////////////////////////////////////////////
+	if(!$.fn.ace_sidebar_scroll) {
+	 $.fn.ace_sidebar_scroll = function (option, value) {
+		var method_call;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_sidebar_scroll');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_sidebar_scroll', (data = new Sidebar_Scroll(this, options)));
+			if (typeof option === 'string' && typeof data[option] === 'function') {
+				method_call = data[option](value);
+			}
+		});
+
+		return (method_call === undefined) ? $set : method_call;
+	 }
+	 
+	  $.fn.ace_sidebar_scroll.defaults = {
+		'scroll_to_active': true,
+		'include_shortcuts': true,
+		'include_toggle': false,
+		'scroll_style': '',
+		'lock_anyway': false,
+		'only_if_fixed': true
+     }
+	 
+	}
+
+})(window.jQuery);

+ 541 - 0
src/main/webapp/static/ace/assets/js/ace/ace.sidebar.js

@@ -0,0 +1,541 @@
+/**
+ <b>Sidebar functions</b>. Collapsing/expanding, toggling mobile view menu and other sidebar functions.
+*/
+
+(function($ , undefined) {
+	var sidebar_count = 0;
+
+	function Sidebar(sidebar, settings) {
+		var self = this;
+		this.$sidebar = $(sidebar);
+		this.$sidebar.attr('data-sidebar', 'true');
+		if( !this.$sidebar.attr('id') ) this.$sidebar.attr( 'id' , 'id-sidebar-'+(++sidebar_count) )
+
+		
+		//get a list of 'data-*' attributes that override 'defaults' and 'settings'
+		var attrib_values = ace.helper.getAttrSettings(sidebar, $.fn.ace_sidebar.defaults, 'sidebar-');
+		this.settings = $.extend({}, $.fn.ace_sidebar.defaults, settings, attrib_values);
+
+
+		//some vars
+		this.minimized = false;//will be initiated later
+		this.collapsible = false;//...
+		this.horizontal = false;//...
+		this.mobile_view = false;//
+
+
+		this.vars = function() {
+			return {'minimized': this.minimized, 'collapsible': this.collapsible, 'horizontal': this.horizontal, 'mobile_view': this.mobile_view}
+		}
+		this.get = function(name) {
+			if(this.hasOwnProperty(name)) return this[name];
+		}
+		this.set = function(name, value) {
+			if(this.hasOwnProperty(name)) this[name] = value;
+		}
+		
+
+		this.ref = function() {
+			//return a reference to self
+			return this;
+		}
+
+		var toggleIcon = function(minimized) {
+			var icon = $(this).find(ace.vars['.icon']), icon1, icon2;
+			if(icon.length > 0) {
+				icon1 = icon.attr('data-icon1');//the icon for expanded state
+				icon2 = icon.attr('data-icon2');//the icon for collapsed state
+
+				if(minimized !== undefined) {
+					if(minimized) icon.removeClass(icon1).addClass(icon2);
+					else icon.removeClass(icon2).addClass(icon1);
+				}
+				else {
+					icon.toggleClass(icon1).toggleClass(icon2);
+				}
+			}
+		}		
+		
+		var findToggleBtn = function() {
+			var toggle_btn = self.$sidebar.find('.sidebar-collapse');
+			if(toggle_btn.length == 0) toggle_btn = $('.sidebar-collapse[data-target="#'+(self.$sidebar.attr('id')||'')+'"]');
+			if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
+			else toggle_btn = null;
+			
+			return toggle_btn;
+		}
+		
+		//collapse/expand button
+		this.toggleMenu = function(toggle_btn, save) {
+			if(this.collapsible) return;
+
+			//var minimized = this.$sidebar.hasClass('menu-min');
+			this.minimized = !this.minimized;
+			
+			try {
+				//toggle_btn can also be a param to indicate saving to cookie or not?! if toggle_btn === false, it won't be saved
+				ace.settings.sidebar_collapsed(sidebar, this.minimized, !(toggle_btn === false || save === false));//@ ace-extra.js
+			} catch(e) {
+				if(this.minimized)
+					this.$sidebar.addClass('menu-min');
+				else this.$sidebar.removeClass('menu-min');
+			}
+	
+			if( !toggle_btn ) {
+				toggle_btn = findToggleBtn();
+			}
+			if(toggle_btn) {
+				toggleIcon.call(toggle_btn, this.minimized);
+			}
+
+			//force redraw for ie8
+			if(ace.vars['old_ie']) ace.helper.redraw(sidebar);
+		}
+		this.collapse = function(toggle_btn, save) {
+			if(this.collapsible) return;
+			this.minimized = false;
+			
+			this.toggleMenu(toggle_btn, save);
+		}
+		this.expand = function(toggle_btn, save) {
+			if(this.collapsible) return;
+			this.minimized = true;
+			
+			this.toggleMenu(toggle_btn, save);
+		}
+		
+
+		
+		//collapse/expand in 2nd mobile style
+		this.toggleResponsive = function(toggle_btn) {
+			if(!this.mobile_view || this.mobile_style != 3) return;
+		
+			if( this.$sidebar.hasClass('menu-min') ) {
+				//remove menu-min because it interferes with responsive-max
+				this.$sidebar.removeClass('menu-min');
+				var btn = findToggleBtn();
+				if(btn) toggleIcon.call(btn);
+			}
+
+
+			this.minimized = !this.$sidebar.hasClass('responsive-min');
+			this.$sidebar.toggleClass('responsive-min responsive-max');
+
+
+			if( !toggle_btn ) {
+				toggle_btn = this.$sidebar.find('.sidebar-expand');
+				if(toggle_btn.length == 0) toggle_btn = $('.sidebar-expand[data-target="#'+(this.$sidebar.attr('id')||'')+'"]');
+				if(toggle_btn.length != 0) toggle_btn = toggle_btn[0];
+				else toggle_btn = null;
+			}
+			
+			if(toggle_btn) {
+				var icon = $(toggle_btn).find(ace.vars['.icon']), icon1, icon2;
+				if(icon.length > 0) {
+					icon1 = icon.attr('data-icon1');//the icon for expanded state
+					icon2 = icon.attr('data-icon2');//the icon for collapsed state
+
+					icon.toggleClass(icon1).toggleClass(icon2);
+				}
+			}
+
+			$(document).triggerHandler('settings.ace', ['sidebar_collapsed' , this.minimized]);
+		}
+		
+		//some helper functions
+		this.is_collapsible = function() {
+			var toggle
+			return (this.$sidebar.hasClass('navbar-collapse'))
+			&& ((toggle = $('.navbar-toggle[data-target="#'+(this.$sidebar.attr('id')||'')+'"]').get(0)) != null)
+			&&  toggle.scrollHeight > 0
+			//sidebar is collapsible and collapse button is visible?
+		}
+		this.is_mobile_view = function() {
+			var toggle
+			return ((toggle = $('.menu-toggler[data-target="#'+(this.$sidebar.attr('id')||'')+'"]').get(0)) != null)
+			&&  toggle.scrollHeight > 0
+		}
+
+
+		//toggling submenu
+		this.$sidebar.on(ace.click_event+'.ace.submenu', '.nav-list', function (ev) {
+			var nav_list = this;
+
+			//check to see if we have clicked on an element which is inside a .dropdown-toggle element?!
+			//if so, it means we should toggle a submenu
+			var link_element = $(ev.target).closest('a');
+			if(!link_element || link_element.length == 0) return;//return if not clicked inside a link element
+
+			var minimized  = self.minimized && !self.collapsible;
+			//if .sidebar is .navbar-collapse and in small device mode, then let minimized be uneffective
+	
+			if( !link_element.hasClass('dropdown-toggle') ) {//it doesn't have a submenu return
+				//just one thing before we return
+				//if sidebar is collapsed(minimized) and we click on a first level menu item
+				//and the click is on the icon, not on the menu text then let's cancel event and cancel navigation
+				//Good for touch devices, that when the icon is tapped to see the menu text, navigation is cancelled
+				//navigation is only done when menu text is tapped
+
+				if( ace.click_event == 'tap'
+					&&
+					minimized
+					&&
+					link_element.get(0).parentNode.parentNode == nav_list )//only level-1 links
+				{
+					var text = link_element.find('.menu-text').get(0);
+					if( text != null && ev.target != text && !$.contains(text , ev.target) ) {//not clicking on the text or its children
+						ev.preventDefault();
+						return false;
+					}
+				}
+
+
+				//ios safari only has a bit of a problem not navigating to link address when scrolling down
+				//specify data-link attribute to ignore this
+				if(ace.vars['ios_safari'] && link_element.attr('data-link') !== 'false') {
+					//only ios safari has a bit of a problem not navigating to link address when scrolling down
+					//please see issues section in documentation
+					document.location = link_element.attr('href');
+					ev.preventDefault();
+					return false;
+				}
+
+				return;
+			}
+			
+			ev.preventDefault();
+			
+			
+
+
+			var sub = link_element.siblings('.submenu').get(0);
+			if(!sub) return false;
+			var $sub = $(sub);
+
+			var height_change = 0;//the amount of height change in .nav-list
+
+			var parent_ul = sub.parentNode.parentNode;
+			if
+			(
+				( minimized && parent_ul == nav_list )
+				 || 
+				( ( $sub.parent().hasClass('hover') && $sub.css('position') == 'absolute' ) && !self.collapsible )
+			)
+			{
+				return false;
+			}
+
+			
+			var sub_hidden = (sub.scrollHeight == 0)
+
+			//if not open and visible, let's open it and make it visible
+			if( sub_hidden ) {//being shown now
+			  $(parent_ul).find('> .open > .submenu').each(function() {
+				//close all other open submenus except for the active one
+				if(this != sub && !$(this.parentNode).hasClass('active')) {
+					height_change -= this.scrollHeight;
+					self.hide(this, self.settings.duration, false);
+				}
+			  })
+			}
+
+			if( sub_hidden ) {//being shown now
+				self.show(sub, self.settings.duration);
+				//if a submenu is being shown and another one previously started to hide, then we may need to update/hide scrollbars
+				//but if no previous submenu is being hidden, then no need to check if we need to hide the scrollbars in advance
+				if(height_change != 0) height_change += sub.scrollHeight;//we need new updated 'scrollHeight' here
+			} else {
+				self.hide(sub, self.settings.duration);
+				height_change -= sub.scrollHeight;
+				//== -1 means submenu is being hidden
+			}
+
+			//hide scrollbars if content is going to be small enough that scrollbars is not needed anymore
+			//do this almost before submenu hiding begins
+			//but when minimized submenu's toggle should have no effect
+			if (height_change != 0) {
+				if(self.$sidebar.attr('data-sidebar-scroll') == 'true' && !self.minimized) 
+					self.$sidebar.ace_sidebar_scroll('prehide', height_change)
+			}
+
+			return false;
+		})
+
+		var submenu_working = false;
+		this.show = function(sub, $duration, shouldWait) {
+			//'shouldWait' indicates whether to wait for previous transition (submenu toggle) to be complete or not?
+			shouldWait = (shouldWait !== false);
+			if(shouldWait && submenu_working) return false;
+					
+			var $sub = $(sub);
+			var event;
+			$sub.trigger(event = $.Event('show.ace.submenu'))
+			if (event.isDefaultPrevented()) {
+				return false;
+			}
+			
+			if(shouldWait) submenu_working = true;
+
+
+			$duration = $duration || this.settings.duration;
+			
+			$sub.css({
+				height: 0,
+				overflow: 'hidden',
+				display: 'block'
+			})
+			.removeClass('nav-hide').addClass('nav-show')//only for window < @grid-float-breakpoint and .navbar-collapse.menu-min
+			.parent().addClass('open');
+			
+			sub.scrollTop = 0;//this is for submenu_hover when sidebar is minimized and a submenu is scrollTop'ed using scrollbars ...
+
+			if( $duration > 0 ) {
+			  $sub.css({height: sub.scrollHeight,
+				'transition-property': 'height',
+				'transition-duration': ($duration/1000)+'s'})
+			}
+
+			var complete = function(ev, trigger) {
+				ev && ev.stopPropagation();
+				$sub
+				.css({'transition-property': '', 'transition-duration': '', overflow:'', height: ''})
+				//if(ace.vars['webkit']) ace.helper.redraw(sub);//little Chrome issue, force redraw ;)
+
+				if(trigger !== false) $sub.trigger($.Event('shown.ace.submenu'))
+				
+				if(shouldWait) submenu_working = false;
+			}
+			
+			if( $duration > 0 && !!$.support.transition.end ) {
+			  $sub.one($.support.transition.end, complete);
+			}
+			else complete();
+			
+			//there is sometimes a glitch, so maybe retry
+			if(ace.vars['android']) {
+				setTimeout(function() {
+					complete(null, false);
+					ace.helper.redraw(sub);
+				}, $duration + 20);
+			}
+
+			return true;
+		 }
+		 
+		 
+		 this.hide = function(sub, $duration, shouldWait) {
+			//'shouldWait' indicates whether to wait for previous transition (submenu toggle) to be complete or not?
+			shouldWait = (shouldWait !== false);
+			if(shouldWait && submenu_working) return false;
+		 
+			
+			var $sub = $(sub);
+			var event;
+			$sub.trigger(event = $.Event('hide.ace.submenu'))
+			if (event.isDefaultPrevented()) {
+				return false;
+			}
+			
+			if(shouldWait) submenu_working = true;
+			
+
+			$duration = $duration || this.settings.duration;
+			
+			$sub.css({
+				height: sub.scrollHeight,
+				overflow: 'hidden',
+				display: 'block'
+			})
+			.parent().removeClass('open');
+
+			sub.offsetHeight;
+			//forces the "sub" to re-consider the new 'height' before transition
+
+			if( $duration > 0 ) {
+			  $sub.css({'height': 0,
+				'transition-property': 'height',
+				'transition-duration': ($duration/1000)+'s'});
+			}
+
+
+			var complete = function(ev, trigger) {
+				ev && ev.stopPropagation();
+				$sub
+				.css({display: 'none', overflow:'', height: '', 'transition-property': '', 'transition-duration': ''})
+				.removeClass('nav-show').addClass('nav-hide')//only for window < @grid-float-breakpoint and .navbar-collapse.menu-min
+
+				if(trigger !== false) $sub.trigger($.Event('hidden.ace.submenu'))
+				
+				if(shouldWait) submenu_working = false;
+			}
+
+			if( $duration > 0 && !!$.support.transition.end ) {
+			   $sub.one($.support.transition.end, complete);
+			}
+			else complete();
+
+
+			//there is sometimes a glitch, so maybe retry
+			if(ace.vars['android']) {
+				setTimeout(function() {
+					complete(null, false);
+					ace.helper.redraw(sub);
+				}, $duration + 20);
+			}
+
+			return true;
+		 }
+
+		 this.toggle = function(sub, $duration) {
+			$duration = $duration || self.settings.duration;
+		 
+			if( sub.scrollHeight == 0 ) {//if an element is hidden scrollHeight becomes 0
+				if( this.show(sub, $duration) ) return 1;
+			} else {
+				if( this.hide(sub, $duration) ) return -1;
+			}
+			return 0;
+		 }
+
+
+		//sidebar vars
+		var minimized_menu_class  = 'menu-min';
+		var responsive_min_class  = 'responsive-min';
+		var horizontal_menu_class = 'h-sidebar';
+
+		var sidebar_mobile_style = function() {
+			//differnet mobile menu styles
+			this.mobile_style = 1;//default responsive mode with toggle button inside navbar
+			if(this.$sidebar.hasClass('responsive') && !$('.menu-toggler[data-target="#'+this.$sidebar.attr('id')+'"]').hasClass('navbar-toggle')) this.mobile_style = 2;//toggle button behind sidebar
+			 else if(this.$sidebar.hasClass(responsive_min_class)) this.mobile_style = 3;//minimized menu
+			  else if(this.$sidebar.hasClass('navbar-collapse')) this.mobile_style = 4;//collapsible (bootstrap style)
+		}
+		sidebar_mobile_style.call(self);
+		  
+		function update_vars() {
+			this.mobile_view = this.mobile_style < 4 && this.is_mobile_view();
+			this.collapsible = !this.mobile_view && this.is_collapsible();
+
+			this.minimized = 
+			(!this.collapsible && this.$sidebar.hasClass(minimized_menu_class))
+			 ||
+			(this.mobile_style == 3 && this.mobile_view && this.$sidebar.hasClass(responsive_min_class))
+
+			this.horizontal = !(this.mobile_view || this.collapsible) && this.$sidebar.hasClass(horizontal_menu_class)
+		}
+
+		//update some basic variables
+		$(window).on('resize.sidebar.vars' , function(){
+			update_vars.call(self);
+		}).triggerHandler('resize.sidebar.vars')
+
+	}//end of Sidebar
+	
+
+	//sidebar events
+	
+	//menu-toggler
+	$(document)
+	.on(ace.click_event+'.ace.menu', '.menu-toggler', function(e){
+		var btn = $(this);
+		var sidebar = $(btn.attr('data-target'));
+		if(sidebar.length == 0) return;
+		
+		e.preventDefault();
+				
+		sidebar.toggleClass('display');
+		btn.toggleClass('display');
+		
+		var click_event = ace.click_event+'.ace.autohide';
+		var auto_hide = sidebar.attr('data-auto-hide') === 'true';
+
+		if( btn.hasClass('display') ) {
+			//hide menu if clicked outside of it!
+			if(auto_hide) {
+				$(document).on(click_event, function(ev) {
+					if( sidebar.get(0) == ev.target || $.contains(sidebar.get(0), ev.target) ) {
+						ev.stopPropagation();
+						return;
+					}
+
+					sidebar.removeClass('display');
+					btn.removeClass('display');
+					$(document).off(click_event);
+				})
+			}
+
+			if(sidebar.attr('data-sidebar-scroll') == 'true') sidebar.ace_sidebar_scroll('reset');
+		}
+		else {
+			if(auto_hide) $(document).off(click_event);
+		}
+
+		return false;
+	})
+	//sidebar collapse/expand button
+	.on(ace.click_event+'.ace.menu', '.sidebar-collapse', function(e){
+		
+		var target = $(this).attr('data-target'), $sidebar = null;
+		if(target) $sidebar = $(target);
+		if($sidebar == null || $sidebar.length == 0) $sidebar = $(this).closest('.sidebar');
+		if($sidebar.length == 0) return;
+
+		e.preventDefault();
+		$sidebar.ace_sidebar('toggleMenu', this);
+	})
+	//this button is used in `mobile_style = 3` responsive menu style to expand minimized sidebar
+	.on(ace.click_event+'.ace.menu', '.sidebar-expand', function(e){
+		var target = $(this).attr('data-target'), $sidebar = null;
+		if(target) $sidebar = $(target);
+		if($sidebar == null || $sidebar.length == 0) $sidebar = $(this).closest('.sidebar');
+		if($sidebar.length == 0) return;	
+	
+		var btn = this;
+		e.preventDefault();
+		$sidebar.ace_sidebar('toggleResponsive', this);
+		
+		var click_event = ace.click_event+'.ace.autohide';
+		if($sidebar.attr('data-auto-hide') === 'true') {
+			if( $sidebar.hasClass('responsive-max') ) {
+				$(document).on(click_event, function(ev) {
+					if( $sidebar.get(0) == ev.target || $.contains($sidebar.get(0), ev.target) ) {
+						ev.stopPropagation();
+						return;
+					}
+
+					$sidebar.ace_sidebar('toggleResponsive', btn);
+					$(document).off(click_event);
+				})
+			}
+			else {
+				$(document).off(click_event);
+			}
+		}
+	})
+
+	
+	$.fn.ace_sidebar = function (option, value) {
+		var method_call;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_sidebar');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_sidebar', (data = new Sidebar(this, options)));
+			if (typeof option === 'string' && typeof data[option] === 'function') {
+				if(value instanceof Array) method_call = data[option].apply(data, value);
+				else method_call = data[option](value);
+			}
+		});
+
+		return (method_call === undefined) ? $set : method_call;
+	};
+	
+	
+	$.fn.ace_sidebar.defaults = {
+		'duration': 300
+    }
+
+
+})(window.jQuery);

+ 513 - 0
src/main/webapp/static/ace/assets/js/ace/ace.submenu-hover.js

@@ -0,0 +1,513 @@
+/**
+ <b>Submenu hover adjustment</b>. Automatically move up a submenu to fit into screen when some part of it goes beneath window.
+ Pass a "true" value as an argument and submenu will have native browser scrollbars when necessary.
+*/
+
+(function($ , undefined) {
+
+ if( ace.vars['very_old_ie'] ) return;
+ //ignore IE7 & below
+
+ var hasTouch = ace.vars['touch'];
+ var nativeScroll = ace.vars['old_ie'] || hasTouch;
+ 
+
+ var is_element_pos =
+	'getComputedStyle' in window ?
+	//el.offsetHeight is used to force redraw and recalculate 'el.style.position' esp. for webkit!
+	function(el, pos) { el.offsetHeight; return window.getComputedStyle(el).position == pos }
+	:
+	function(el, pos) { el.offsetHeight; return $(el).css('position') == pos }
+
+
+
+ $(window).on('resize.sidebar.ace_hover', function() {
+	$('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('update_vars').ace_sidebar_hover('reset');
+ })
+
+ $(document).on('settings.ace.ace_hover', function(e, event_name, event_val) {
+	if(event_name == 'sidebar_collapsed') $('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('reset');
+	else if(event_name == 'navbar_fixed') $('.sidebar[data-sidebar-hover=true]').ace_sidebar_hover('update_vars');
+ })
+ 
+ var sidebars = [];
+
+ function Sidebar_Hover(sidebar , settings) {
+	var self = this, that = this;
+	
+	var attrib_values = ace.helper.getAttrSettings(sidebar, $.fn.ace_sidebar_hover.defaults);
+	this.settings = $.extend({}, $.fn.ace_sidebar_hover.defaults, settings, attrib_values);
+	
+
+	var $sidebar = $(sidebar), nav_list = $sidebar.find('.nav-list').get(0);
+	$sidebar.attr('data-sidebar-hover', 'true');
+	
+	sidebars.push($sidebar);
+
+	var sidebar_vars = {};
+	var old_ie = ace.vars['old_ie'];
+
+	
+	
+	var scroll_right = false;
+	//scroll style class
+	var hasHoverDelay = self.settings.sub_hover_delay || false;
+	
+	if(hasTouch && hasHoverDelay) self.settings.sub_hover_delay = parseInt(Math.max(self.settings.sub_hover_delay, 2500));//for touch device, delay is at least 2.5sec
+
+	var $window = $(window);
+	//navbar used for adding extra offset from top when adjusting submenu
+	var $navbar = $('.navbar').eq(0);
+	var navbar_fixed = $navbar.css('position') == 'fixed';
+	this.update_vars = function() {
+		navbar_fixed = $navbar.css('position') == 'fixed';
+	}
+
+	self.dirty = false;
+	//on window resize or sidebar expand/collapse a previously "pulled up" submenu should be reset back to its default position
+	//for example if "pulled up" in "responsive-min" mode, in "fullmode" should not remain "pulled up"
+	this.reset = function() {
+		if( self.dirty == false ) return;
+		self.dirty = false;//so don't reset is not called multiple times in a row!
+	
+		$sidebar.find('.submenu').each(function() {
+			var $sub = $(this), li = $sub.parent();
+			$sub.css({'top': '', 'bottom': '', 'max-height': ''});
+			
+			if($sub.hasClass('ace-scroll')) {
+				$sub.ace_scroll('disable');
+			}
+			else {
+				$sub.removeClass('sub-scroll');
+			}
+			 
+			if( is_element_pos(this, 'absolute') ) $sub.addClass('can-scroll');
+			else $sub.removeClass('can-scroll');
+
+			li.removeClass('pull_up').find('.menu-text:first').css('margin-top', '');
+		})
+
+		$sidebar.find('.hover-show').removeClass('hover-show hover-shown hover-flip');
+	}
+	
+	this.updateStyle = function(newStyle) {
+		sub_scroll_style = newStyle;
+		$sidebar.find('.submenu.ace-scroll').ace_scroll('update', {styleClass: newStyle});
+	}
+	this.changeDir = function(dir) {
+		scroll_right = (dir === 'right');
+	}
+	
+	
+	//update submenu scrollbars on submenu hide & show
+
+	var lastScrollHeight = -1;
+	//hide scrollbars if it's going to be not needed anymore!
+	if(!nativeScroll)
+	$sidebar.on('hide.ace.submenu.sidebar_hover', '.submenu', function(e) {
+		if(lastScrollHeight < 1) return;
+
+		e.stopPropagation();
+		var $sub = $(this).closest('.ace-scroll.can-scroll');
+		if($sub.length == 0 || !is_element_pos($sub[0], 'absolute')) return;
+
+		if($sub[0].scrollHeight - this.scrollHeight < lastScrollHeight) {
+			$sub.ace_scroll('disable');
+		}
+	});
+
+	
+	
+	
+	//reset scrollbars 
+	if(!nativeScroll)
+	$sidebar.on('shown.ace.submenu.sidebar_hover hidden.ace.submenu.sidebar_hover', '.submenu', function(e) {
+		if(lastScrollHeight < 1) return;
+	
+		var $sub = $(this).closest('.ace-scroll.can-scroll');
+		if($sub.length == 0 || !is_element_pos($sub[0], 'absolute') ) return;
+		
+		var sub_h = $sub[0].scrollHeight;
+		
+		if(lastScrollHeight > 14 && sub_h - lastScrollHeight > 4) {
+			$sub.ace_scroll('enable').ace_scroll('reset');//don't update track position
+		}
+		else {
+			$sub.ace_scroll('disable');
+		}
+	});
+
+
+	///////////////////////
+
+
+	var currentScroll = -1;
+
+	//some mobile browsers don't have mouseenter
+	var event_1 = !hasTouch ? 'mouseenter.sub_hover' : 'touchstart.sub_hover';// pointerdown.sub_hover';
+	var event_2 = !hasTouch ? 'mouseleave.sub_hover' : 'touchend.sub_hover touchcancel.sub_hover';// pointerup.sub_hover pointercancel.sub_hover';
+	
+	$sidebar.on(event_1, '.nav-list li, .sidebar-shortcuts', function (e) {
+		sidebar_vars = $sidebar.ace_sidebar('vars');
+		
+	
+		//ignore if collapsible mode (mobile view .navbar-collapse) so it doesn't trigger submenu movements
+		//or return if horizontal but not mobile_view (style 1&3)
+		if( sidebar_vars['collapsible'] /**|| sidebar_vars['horizontal']*/ ) return;
+		
+		var $this = $(this);
+
+		var shortcuts = false;
+		var has_hover = $this.hasClass('hover');
+		
+		var sub = $this.find('> .submenu').get(0);
+		if( !(sub || ((this.parentNode == nav_list || has_hover || (shortcuts = $this.hasClass('sidebar-shortcuts'))) /**&& sidebar_vars['minimized']*/)) ) {
+			if(sub) $(sub).removeClass('can-scroll');
+			return;//include .compact and .hover state as well?
+		}
+		
+		var target_element = sub, is_abs = false;
+		if( !target_element && this.parentNode == nav_list ) target_element = $this.find('> a > .menu-text').get(0);
+		if( !target_element && shortcuts ) target_element = $this.find('.sidebar-shortcuts-large').get(0);
+		if( (!target_element || !(is_abs = is_element_pos(target_element, 'absolute'))) && !has_hover ) {
+			if(sub) $(sub).removeClass('can-scroll');
+			return;
+		}
+		
+		
+		var sub_hide = hasHoverDelay ? getSubHide(this) : null;
+		//var show_sub = false;
+
+		if(sub) {
+		 if(is_abs) {
+			self.dirty = true;
+			
+			var newScroll = ace.helper.scrollTop();
+			//if submenu is becoming visible for first time or document has been scrolled, then adjust menu
+			if( (hasHoverDelay && !sub_hide.is_visible()) || (!hasTouch && newScroll != currentScroll) || old_ie ) {
+				//try to move/adjust submenu if the parent is a li.hover or if submenu is minimized
+				//if( is_element_pos(sub, 'absolute') ) {//for example in small device .hover > .submenu may not be absolute anymore!
+					$(sub).addClass('can-scroll');
+					//show_sub = true;
+					if(!old_ie && !hasTouch) adjust_submenu.call(this, sub);
+					else {
+						//because ie8 needs some time for submenu to be displayed and real value of sub.scrollHeight be kicked in
+						var that = this;
+						setTimeout(function() {	adjust_submenu.call(that, sub) }, 0)
+					}
+				//}
+				//else $(sub).removeClass('can-scroll');
+			}
+			currentScroll = newScroll;
+		 }
+		 else {
+			$(sub).removeClass('can-scroll');
+		 }
+		}
+		//if(show_sub) 
+		hasHoverDelay && sub_hide.show();
+		
+	 }).on(event_2, '.nav-list li, .sidebar-shortcuts', function (e) {
+		sidebar_vars = $sidebar.ace_sidebar('vars');
+		
+		if( sidebar_vars['collapsible'] /**|| sidebar_vars['horizontal']*/ ) return;
+
+		if( !$(this).hasClass('hover-show') ) return;
+
+		hasHoverDelay && getSubHide(this).hideDelay();
+	 });
+	 
+	
+	function subHide(li_sub) {
+		var self = li_sub, $self = $(self);
+		var timer = null;
+		var visible = false;
+		
+		this.show = function() {
+			if(timer != null) clearTimeout(timer);
+			timer = null;		
+
+			$self.addClass('hover-show hover-shown');
+			visible = true;
+
+			//let's hide .hover-show elements that are not .hover-shown anymore (i.e. marked for hiding in hideDelay)
+			for(var i = 0; i < sidebars.length ; i++)
+			{
+			  sidebars[i].find('.hover-show').not('.hover-shown').each(function() {
+				getSubHide(this).hide();
+			  })
+			}
+		}
+		
+		this.hide = function() {
+			visible = false;
+			
+			$self.removeClass('hover-show hover-shown hover-flip');
+			
+			if(timer != null) clearTimeout(timer);
+			timer = null;
+			
+			var sub = $self.find('> .submenu').get(0);
+			if(sub) getSubScroll(sub, 'hide');
+		}
+		
+		this.hideDelay = function(callback) {
+			if(timer != null) clearTimeout(timer);
+			
+			$self.removeClass('hover-shown');//somehow marked for hiding
+			
+			timer = setTimeout(function() {
+				visible = false;
+				$self.removeClass('hover-show hover-flip');
+				timer = null;
+				
+				var sub = $self.find('> .submenu').get(0);
+				if(sub) getSubScroll(sub, 'hide');
+				
+				if(typeof callback === 'function') callback.call(this);
+			}, that.settings.sub_hover_delay);
+		}
+		
+		this.is_visible = function() {
+			return visible;
+		}
+	}
+	function getSubHide(el) {
+		var sub_hide = $(el).data('subHide');
+		if(!sub_hide) $(el).data('subHide', (sub_hide = new subHide(el)));
+		return sub_hide;
+	}
+	
+	
+	function getSubScroll(el, func) {
+		var sub_scroll = $(el).data('ace_scroll');
+		if(!sub_scroll) return false;
+		if(typeof func === 'string') {
+			sub_scroll[func]();
+			return true;
+		}
+		return sub_scroll;
+	}	
+	
+	function adjust_submenu(sub) {
+		var $li = $(this);
+		var $sub = $(sub);
+		sub.style.top = '';
+		sub.style.bottom = '';
+
+
+		var menu_text = null
+		if( sidebar_vars['minimized'] && (menu_text = $li.find('.menu-text').get(0)) ) {
+			//2nd level items don't have .menu-text
+			menu_text.style.marginTop = '';
+		}
+
+		var scroll = ace.helper.scrollTop();
+		var navbar_height = 0;
+
+		var $scroll = scroll;
+		
+		if( navbar_fixed ) {
+			navbar_height = sidebar.offsetTop;//$navbar.height();
+			$scroll += navbar_height + 1;
+			//let's avoid our submenu from going below navbar
+			//because of chrome z-index stacking issue and firefox's normal .submenu over fixed .navbar flicker issue
+		}
+
+
+
+
+		var off = $li.offset();
+		off.top = parseInt(off.top);
+		
+		var extra = 0, parent_height;
+		
+		sub.style.maxHeight = '';//otherwise scrollHeight won't be consistent in consecutive calls!?
+		var sub_h = sub.scrollHeight;
+		var parent_height = $li.height();
+		if(menu_text) {
+			extra = parent_height;
+			off.top += extra;
+		}
+		var sub_bottom = parseInt(off.top + sub_h)
+
+		var move_up = 0;
+		var winh = $window.height();
+
+
+		//if the bottom of menu is going to go below visible window
+
+		var top_space = parseInt(off.top - $scroll - extra);//available space on top
+		var win_space = winh;//available window space
+		
+		var horizontal = sidebar_vars['horizontal'], horizontal_sub = false;
+		if(horizontal && this.parentNode == nav_list) {
+			move_up = 0;//don't move up first level submenu in horizontal mode
+			off.top += $li.height();
+			horizontal_sub = true;//first level submenu
+		}
+
+		if(!horizontal_sub && (move_up = (sub_bottom - (winh + scroll))) >= 0 ) {
+			//don't move up more than available space
+			move_up = move_up < top_space ? move_up : top_space;
+
+			//move it up a bit more if there's empty space
+			if(move_up == 0) move_up = 20;
+			if(top_space - move_up > 10) {
+				move_up += parseInt(Math.min(25, top_space - move_up));
+			}
+
+
+			//move it down if submenu's bottom is going above parent LI
+			if(off.top + (parent_height - extra) > (sub_bottom - move_up)) {
+				move_up -= (off.top + (parent_height - extra) - (sub_bottom - move_up));
+			}
+
+			if(move_up > 0) {
+				sub.style.top = -(move_up) + 'px';
+				if( menu_text ) {
+					menu_text.style.marginTop = -(move_up) + 'px';
+				}
+			}
+		}
+		if(move_up < 0) move_up = 0;//when it goes below
+		
+		var pull_up = move_up > 0 && move_up > parent_height - 20;
+		if(pull_up) {
+			$li.addClass('pull_up');
+		}
+		else $li.removeClass('pull_up');
+		
+		
+		//flip submenu if out of window width
+		if(horizontal) {
+			if($li.parent().parent().hasClass('hover-flip')) $li.addClass('hover-flip');//if a parent is already flipped, flip it then!
+			else {
+				var sub_off = $sub.offset();
+				var sub_w = $sub.width();
+				var win_w = $window.width();
+				if(sub_off.left + sub_w > win_w) {
+					$li.addClass('hover-flip');
+				}
+			}
+		}
+
+
+		//don't add scrollbars if it contains .hover menus
+		var has_hover = $li.hasClass('hover') && !sidebar_vars['mobile_view'];
+		if(has_hover && $sub.find('> li > .submenu').length > 0) return;
+
+	
+		//if(  ) {
+			var scroll_height = (win_space - (off.top - scroll)) + (move_up);
+			//if after scroll, the submenu is above parent LI, then move it down
+			var tmp = move_up - scroll_height;
+			if(tmp > 0 && tmp < parent_height) scroll_height += parseInt(Math.max(parent_height, parent_height - tmp));
+
+			scroll_height -= 5;
+			
+			if(scroll_height < 90) {
+				return;
+			}
+			
+			var ace_scroll = false;
+			if(!nativeScroll) {
+				ace_scroll = getSubScroll(sub);
+				if(ace_scroll == false) {
+					$sub.ace_scroll({
+						//hideOnIdle: true,
+						observeContent: true,
+						detached: true,
+						updatePos: false,
+						reset: true,
+						mouseWheelLock: true,
+						styleClass: self.settings.sub_scroll_style
+					});
+					ace_scroll = getSubScroll(sub);
+					
+					var track = ace_scroll.get_track();
+					if(track) {
+						//detach it from body and insert it after submenu for better and cosistent positioning
+						$sub.after(track);
+					}
+				}
+				
+				ace_scroll.update({size: scroll_height});
+			}
+			else {
+				$sub
+				.addClass('sub-scroll')
+				.css('max-height', (scroll_height)+'px')
+			}
+
+
+			lastScrollHeight = scroll_height;
+			if(!nativeScroll && ace_scroll) {
+				if(scroll_height > 14 && sub_h - scroll_height > 4) {
+					ace_scroll.enable()
+					ace_scroll.reset();
+				}			
+				else {
+					ace_scroll.disable();
+				}
+
+				//////////////////////////////////
+				var track = ace_scroll.get_track();
+				if(track) {
+					track.style.top = -(move_up - extra - 1) + 'px';
+					
+					var off = $sub.position();
+					var left = off.left 
+					if( !scroll_right ) {
+						left += ($sub.outerWidth() - ace_scroll.track_size());
+					}
+					else {
+						left += 2;
+					}
+					track.style.left = parseInt(left) + 'px';
+					
+					if(horizontal_sub) {//first level submenu
+						track.style.left = parseInt(left - 2) + 'px';
+						track.style.top = parseInt(off.top) + (menu_text ? extra - 2 : 0) + 'px';
+					}
+				}
+			}
+		//}
+
+
+		//again force redraw for safari!
+		if( ace.vars['safari'] ) {
+			ace.helper.redraw(sub)
+		}
+   }
+
+}
+ 
+ 
+ 
+ /////////////////////////////////////////////
+ $.fn.ace_sidebar_hover = function (option, value) {
+	var method_call;
+
+	var $set = this.each(function () {
+		var $this = $(this);
+		var data = $this.data('ace_sidebar_hover');
+		var options = typeof option === 'object' && option;
+
+		if (!data) $this.data('ace_sidebar_hover', (data = new Sidebar_Hover(this, options)));
+		if (typeof option === 'string' && typeof data[option] === 'function') {
+			method_call = data[option](value);
+		}
+	});
+
+	return (method_call === undefined) ? $set : method_call;
+ }
+ 
+  $.fn.ace_sidebar_hover.defaults = {
+	'sub_sub_hover_delay': 750,
+	'sub_scroll_style': 'no-track scroll-thin'
+ }
+ 
+
+})(window.jQuery);
+

+ 118 - 0
src/main/webapp/static/ace/assets/js/ace/ace.touch-drag.js

@@ -0,0 +1,118 @@
+/**
+ <b>Custom drag event for touch devices</b> used in scrollbars.
+ For better touch event handling and extra options a more advanced solution such as <u>Hammer.js</u> is recommended.
+*/
+
+//based on but not dependent on jQuery mobile
+/*
+* jQuery Mobile v1.3.2
+* http://jquerymobile.com
+*
+* Copyright 2010, 2013 jQuery Foundation, Inc. and other contributors
+* Released under the MIT license.
+* http://jquery.org/license
+*
+*/
+(function($ , undefined) {
+
+	if(!ace.vars['touch']) return;
+
+	var touchStartEvent = "touchstart MSPointerDown pointerdown",// : "mousedown",
+			touchStopEvent  =  "touchend touchcancel MSPointerUp MSPointerCancel pointerup pointercancel",// : "mouseup",
+			touchMoveEvent  =  "touchmove MSPointerMove MSPointerHover pointermove";// : "mousemove";
+
+
+	$.event.special.ace_drag = {
+		setup: function() {
+			var min_threshold = 0;
+		
+			var $this = $(this);
+			$this.on(touchStartEvent, function(event) {		
+				var data = event.originalEvent.touches ?
+					event.originalEvent.touches[ 0 ] :
+					event,
+					start = {
+						//time: Date.now(),
+						coords: [ data.pageX, data.pageY ],
+						origin: $(event.target)
+					},
+					stop;
+					//start.origin.trigger({'type' : 'ace_dragStart', 'start':(start || [-1,-1])});
+					
+					var direction = false, dx = 0, dy = 0;
+
+				function moveHandler(event) {
+					if (!start) {
+						return;
+					}
+					var data = event.originalEvent.touches ?
+							event.originalEvent.touches[ 0 ] :
+							event;
+					stop = {
+						coords: [ data.pageX, data.pageY ]
+					};
+					
+					// prevent scrolling
+					//if ( Math.abs(start.coords[1] - stop.coords[1]) > 0 || Math.abs(start.coords[0] - stop.coords[01]) > 0 ) {
+						//event.preventDefault();
+					//}
+
+
+					if (start && stop) {
+						dx = 0;
+						dy = 0;
+
+						direction = 
+							(
+							 Math.abs(dy = start.coords[ 1 ] - stop.coords[ 1 ]) > min_threshold
+								&& 
+							 Math.abs(dx = start.coords[ 0 ] - stop.coords[ 0 ]) <= Math.abs(dy)
+							)
+							? 
+							(dy > 0 ? 'up' : 'down')
+							:
+							(
+							 Math.abs(dx = start.coords[ 0 ] - stop.coords[ 0 ]) > min_threshold
+								&& 
+							 Math.abs( dy ) <= Math.abs(dx)
+							)
+							?
+							(dx > 0 ? 'left' : 'right')
+							:
+							false;
+							
+
+							if( direction !== false ) {
+							 var retval = {cancel: false}
+							 start.origin.trigger({
+								'type': 'ace_drag',
+								//'start': start.coords,
+								//'stop': stop.coords,
+								'direction': direction,
+								'dx': dx,
+								'dy': dy,
+								'retval': retval
+							 })
+
+		 					  // prevent document scrolling unless retval.cancel == true
+							  if( retval.cancel == false ) event.preventDefault();
+							}
+					}
+					start.coords[0] = stop.coords[0];
+					start.coords[1] = stop.coords[1];
+				}
+
+				$this
+				.on(touchMoveEvent, moveHandler)
+				.one(touchStopEvent, function(event) {
+					$this.off(touchMoveEvent, moveHandler);
+					//start.origin.trigger({'type' : 'ace_dragEnd', 'stop':(stop || [-1,-1])});
+					
+					start = stop = undefined;
+				
+				});
+			});
+		}
+	}
+
+})(window.jQuery);

+ 258 - 0
src/main/webapp/static/ace/assets/js/ace/ace.widget-box.js

@@ -0,0 +1,258 @@
+/**
+ <b>Widget boxes</b>
+*/
+(function($ , undefined) {
+
+	var Widget_Box = function(box, options) {
+		this.$box = $(box);
+		var that = this;
+		//this.options = $.extend({}, $.fn.widget_box.defaults, options);
+
+		this.reload = function() {
+			var $box = this.$box;
+			var $remove_position = false;
+			if($box.css('position') == 'static') {
+				$remove_position = true;
+				$box.addClass('position-relative');
+			}
+			$box.append('<div class="widget-box-overlay"><i class="'+ ace.vars['icon'] + 'loading-icon fa fa-spinner fa-spin fa-2x white"></i></div>');
+
+			$box.one('reloaded.ace.widget', function() {
+				$box.find('.widget-box-overlay').remove();
+				if($remove_position) $box.removeClass('position-relative');
+			});
+		}
+
+		this.close = function() {
+			var $box = this.$box;
+			var closeSpeed = 300;
+			$box.fadeOut(closeSpeed , function(){
+					$box.trigger('closed.ace.widget');
+					$box.remove();
+				}
+			)
+		}
+		
+		this.toggle = function(type, button) {
+			var $box = this.$box;
+			var $body = $box.find('.widget-body').eq(0);
+			var $icon = null;
+			
+			var event_name = typeof type !== 'undefined' ? type : ($box.hasClass('collapsed') ? 'show' : 'hide');
+			var event_complete_name = event_name == 'show' ? 'shown' : 'hidden';
+
+			if(typeof button === 'undefined') {
+				button = $box.find('> .widget-header a[data-action=collapse]').eq(0);
+				if(button.length == 0) button = null;
+			}
+
+			if(button) {
+				$icon = button.find(ace.vars['.icon']).eq(0);
+
+				var $match
+				var $icon_down = null
+				var $icon_up = null
+				if( ($icon_down = $icon.attr('data-icon-show')) ) {
+					$icon_up = $icon.attr('data-icon-hide')
+				}
+				else if( $match = $icon.attr('class').match(/fa\-(.*)\-(up|down)/) ) {
+					$icon_down = 'fa-'+$match[1]+'-down'
+					$icon_up = 'fa-'+$match[1]+'-up'
+				}
+			}
+
+			var expandSpeed   = 250;
+			var collapseSpeed = 200;
+
+			if( event_name == 'show' ) {
+				if($icon) $icon.removeClass($icon_down).addClass($icon_up);
+
+				$body.hide();
+				$box.removeClass('collapsed');
+				$body.slideDown(expandSpeed, function(){
+					$box.trigger(event_complete_name+'.ace.widget')
+				})
+			}
+			else {
+				if($icon) $icon.removeClass($icon_up).addClass($icon_down);
+				$body.slideUp(collapseSpeed, function(){
+						$box.addClass('collapsed')
+						$box.trigger(event_complete_name+'.ace.widget')
+					}
+				);
+			}
+		}
+		
+		this.hide = function() {
+			this.toggle('hide');
+		}
+		this.show = function() {
+			this.toggle('show');
+		}
+		
+		
+		this.fullscreen = function() {
+			var $icon = this.$box.find('> .widget-header a[data-action=fullscreen]').find(ace.vars['.icon']).eq(0);
+			var $icon_expand = null
+			var $icon_compress = null
+			if( ($icon_expand = $icon.attr('data-icon1')) ) {
+				$icon_compress = $icon.attr('data-icon2')
+			}
+			else {
+				$icon_expand = 'fa-expand';
+				$icon_compress = 'fa-compress';
+			}
+			
+			
+			if(!this.$box.hasClass('fullscreen')) {
+				$icon.removeClass($icon_expand).addClass($icon_compress);
+				this.$box.addClass('fullscreen');
+				
+				applyScrollbars(this.$box, true);
+			}
+			else {
+				$icon.addClass($icon_expand).removeClass($icon_compress);
+				this.$box.removeClass('fullscreen');
+				
+				applyScrollbars(this.$box, false);
+			}
+			
+			this.$box.trigger('fullscreened.ace.widget')
+		}
+
+	}
+	
+	$.fn.widget_box = function (option, value) {
+		var method_call;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('widget_box');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('widget_box', (data = new Widget_Box(this, options)));
+			if (typeof option === 'string') method_call = data[option](value);
+		});
+
+		return (method_call === undefined) ? $set : method_call;
+	};
+
+
+	$(document).on('click.ace.widget', '.widget-header a[data-action]', function (ev) {
+		ev.preventDefault();
+
+		var $this = $(this);
+		var $box = $this.closest('.widget-box');
+		if( $box.length == 0 || $box.hasClass('ui-sortable-helper') ) return;
+
+		var $widget_box = $box.data('widget_box');
+		if (!$widget_box) {
+			$box.data('widget_box', ($widget_box = new Widget_Box($box.get(0))));
+		}
+
+		var $action = $this.data('action');
+		if($action == 'collapse') {
+			var event_name = $box.hasClass('collapsed') ? 'show' : 'hide';
+
+			var event
+			$box.trigger(event = $.Event(event_name+'.ace.widget'))
+			if (event.isDefaultPrevented()) return
+
+			$widget_box.toggle(event_name, $this);
+		}
+		else if($action == 'close') {
+			var event
+			$box.trigger(event = $.Event('close.ace.widget'))
+			if (event.isDefaultPrevented()) return
+
+			$widget_box.close();
+		}
+		else if($action == 'reload') {
+			$this.blur();
+			var event
+			$box.trigger(event = $.Event('reload.ace.widget'))
+			if (event.isDefaultPrevented()) return
+
+			$widget_box.reload();
+		}
+		else if($action == 'fullscreen') {
+			var event
+			$box.trigger(event = $.Event('fullscreen.ace.widget'))
+			if (event.isDefaultPrevented()) return
+		
+			$widget_box.fullscreen();
+		}
+		else if($action == 'settings') {
+			$box.trigger('setting.ace.widget')
+		}
+
+	});
+	
+		
+	function applyScrollbars($widget, enable) {
+		var $main = $widget.find('.widget-main').eq(0);
+		$(window).off('resize.widget.scroll');
+		
+		//IE8 has an unresolvable issue!!! re-scrollbaring with unknown values?!
+		var nativeScrollbars = ace.vars['old_ie'] || ace.vars['touch'];
+		
+		if(enable) {
+			var ace_scroll = $main.data('ace_scroll');
+			if( ace_scroll ) {
+				$main.data('save_scroll', {size: ace_scroll['size'], lock: ace_scroll['lock'], lock_anyway: ace_scroll['lock_anyway']});
+			}
+			
+			var size = $widget.height() - $widget.find('.widget-header').height() - 10;//extra paddings
+			size = parseInt(size);
+			
+			$main.css('min-height', size);
+			if( !nativeScrollbars ) {
+				if( ace_scroll ) {
+					$main.ace_scroll('update', {'size': size, 'mouseWheelLock': true, 'lockAnyway': true});
+				}
+				else {
+					$main.ace_scroll({'size': size, 'mouseWheelLock': true, 'lockAnyway': true});
+				}
+				$main.ace_scroll('enable').ace_scroll('reset');
+			}
+			else {
+				if( ace_scroll ) $main.ace_scroll('disable');
+				$main.css('max-height', size).addClass('overflow-scroll');
+			}
+			
+			
+			$(window)
+			.on('resize.widget.scroll', function() {
+				var size = $widget.height() - $widget.find('.widget-header').height() - 10;//extra paddings
+				size = parseInt(size);
+				
+				$main.css('min-height', size);
+				if( !nativeScrollbars ) {
+					$main.ace_scroll('update', {'size': size}).ace_scroll('reset');
+				}
+				else {
+					$main.css('max-height', size).addClass('overflow-scroll');
+				}
+			});
+		}
+		
+		else  {
+			$main.css('min-height', '');
+			var saved_scroll = $main.data('save_scroll');
+			if(saved_scroll) {
+				$main
+				.ace_scroll('update', {'size': saved_scroll['size'], 'mouseWheelLock': saved_scroll['lock'], 'lockAnyway': saved_scroll['lock_anyway']})
+				.ace_scroll('enable')
+				.ace_scroll('reset');
+			}
+			
+			if( !nativeScrollbars ) {				
+				if(!saved_scroll) $main.ace_scroll('disable');				
+			}
+			else {
+				$main.css('max-height', '').removeClass('overflow-scroll');
+			}
+		}
+	}
+
+})(window.jQuery);

+ 28 - 0
src/main/webapp/static/ace/assets/js/ace/ace.widget-on-reload.js

@@ -0,0 +1,28 @@
+/**
+ The widget box reload button/event handler. You should use your own handler. An example is available at <i class="text-info">examples/widgets.html</i>.
+ <u><i class="glyphicon glyphicon-flash"></i> You don't need this. Used for demo only</u>
+*/
+
+(function($ , undefined) {
+
+	//***default action for reload in this demo
+	//you should remove this and add your own handler for each specific .widget-box
+	//when data is finished loading or processing is done you can call $box.trigger('reloaded.ace.widget')
+	$(document).on('reload.ace.widget', '.widget-box', function (ev) {
+		var $box = $(this);
+		
+		//trigger the reloaded event to remove the spinner icon after 1-2 seconds
+		setTimeout(function() {
+			$box.trigger('reloaded.ace.widget');
+		}, parseInt(Math.random() * 1000 + 1000));
+	});
+
+	//you may want to do something like this:
+	/**
+	$('#my-widget-box').on('reload.ace.widget', function(){
+		//load new data here
+		//and when finished trigger "reloaded" event
+		$(this).trigger('reloaded.ace.widget');
+	});
+	*/
+})(window.jQuery);

+ 284 - 0
src/main/webapp/static/ace/assets/js/ace/elements.aside.js

@@ -0,0 +1,284 @@
+/**
+ <b>Content Slider</b>. with custom content and elements based on Bootstrap modals.
+*/
+(function($ , undefined) {
+	var $window = $(window);
+
+	function Aside(modal, settings) {
+		var self = this;
+	
+		var $modal = $(modal);
+		var placement = 'right', vertical = false;
+		var hasFade = $modal.hasClass('fade');//Bootstrap enables transition only when modal is ".fade"
+
+		var attrib_values = ace.helper.getAttrSettings(modal, $.fn.ace_aside.defaults);
+		this.settings = $.extend({}, $.fn.ace_aside.defaults, settings, attrib_values);
+		
+		//if no scroll style specified and modal has dark background, let's make scrollbars 'white'
+		if(this.settings.background && !settings.scroll_style && !attrib_values.scroll_style) { 
+			this.settings.scroll_style = 'scroll-white no-track';
+		}
+
+		
+		this.container = this.settings.container;
+		if(this.container) {
+			try {
+				if( $(this.container).get(0) == document.body ) this.container = null;
+			} catch(e) {}
+		}
+		if(this.container) {
+			this.settings.backdrop = false;//no backdrop when inside another element?
+			$modal.addClass('aside-contained');
+		}
+
+		
+		var dialog = $modal.find('.modal-dialog');
+		var content = $modal.find('.modal-content');
+		var delay = 300;
+		
+		this.initiate = function() {
+			modal.className = modal.className.replace(/(\s|^)aside\-(right|top|left|bottom)(\s|$)/ig , '$1$3');
+
+			placement = this.settings.placement;
+			if(placement) placement = $.trim(placement.toLowerCase());
+			if(!placement || !(/right|top|left|bottom/.test(placement))) placement = 'right';
+
+			$modal.attr('data-placement', placement);
+			$modal.addClass('aside-' + placement);
+			
+			if( /right|left/.test(placement) ) {
+				vertical = true;
+				$modal.addClass('aside-vc');//vertical
+			}
+			else $modal.addClass('aside-hz');//horizontal
+			
+			if( this.settings.fixed ) $modal.addClass('aside-fixed');
+			if( this.settings.background ) $modal.addClass('aside-dark');
+			if( this.settings.offset ) $modal.addClass('navbar-offset');
+			
+			if( !this.settings.transition ) $modal.addClass('transition-off');
+			
+			$modal.addClass('aside-hidden');
+
+			this.insideContainer();
+			
+			/////////////////////////////
+			
+			dialog = $modal.find('.modal-dialog');
+			content = $modal.find('.modal-content');
+			
+			if(!this.settings.body_scroll) {
+				//don't allow body scroll when modal is open
+				$modal.on('mousewheel.aside DOMMouseScroll.aside touchmove.aside pointermove.aside', function(e) {
+					if( !$.contains(content[0], e.target) ) {
+						e.preventDefault();
+						return false;
+					}
+				})
+			}
+			
+			if( this.settings.backdrop == false ) {
+				$modal.addClass('no-backdrop');
+			}
+		}
+		
+		
+		this.show = function() {
+			if(this.settings.backdrop == false) {
+			  try {
+				$modal.data('bs.modal').$backdrop.remove();
+			  } catch(e){}
+			}
+	
+			if(this.container) $(this.container).addClass('overflow-hidden');
+			else $modal.css('position', 'fixed')
+			
+			$modal.removeClass('aside-hidden');
+		}
+		
+		this.hide = function() {
+			if(this.container) {
+				this.container.addClass('overflow-hidden');
+				
+				if(ace.vars['firefox']) {
+					//firefox needs a bit of forcing re-calculation
+					modal.offsetHeight;
+				}
+			}
+		
+			toggleButton();
+			
+			if(ace.vars['transition'] && !hasFade) {
+				$modal.one('bsTransitionEnd', function() {
+					$modal.addClass('aside-hidden');
+					$modal.css('position', '');
+					
+					if(self.container) self.container.removeClass('overflow-hidden');
+				}).emulateTransitionEnd(delay);
+			}
+		}
+		
+		this.shown = function() {
+			toggleButton();
+			$('body').removeClass('modal-open').css('padding-right', '');
+			
+			if( this.settings.backdrop == 'invisible' ) {
+			  try {
+				$modal.data('bs.modal').$backdrop.css('opacity', 0);
+			  } catch(e){}
+			}
+
+			var size = !vertical ? dialog.height() : content.height();
+			if(!ace.vars['touch']) {
+				if(!content.hasClass('ace-scroll')) {
+					content.ace_scroll({
+							size: size,
+							reset: true,
+							mouseWheelLock: true,
+							lockAnyway: !this.settings.body_scroll,
+							styleClass: this.settings.scroll_style,
+							'observeContent': true,
+							'hideOnIdle': !ace.vars['old_ie'],
+							'hideDelay': 1500
+					})
+				}
+			}
+			else {
+				content.addClass('overflow-scroll').css('max-height', size+'px');
+			}
+
+			$window
+			.off('resize.modal.aside')
+			.on('resize.modal.aside', function() {
+				if(!ace.vars['touch']) {
+				  content.ace_scroll('disable');//to get correct size when going from small window size to large size
+					var size = !vertical ? dialog.height() : content.height();
+					content
+					.ace_scroll('update', {'size': size})
+					.ace_scroll('enable')
+					.ace_scroll('reset');
+				}
+				else content.css('max-height', (!vertical ? dialog.height() : content.height())+'px');
+			}).triggerHandler('resize.modal.aside');
+			
+			
+			///////////////////////////////////////////////////////////////////////////
+			if(self.container && ace.vars['transition'] && !hasFade) {
+				$modal.one('bsTransitionEnd', function() {
+					self.container.removeClass('overflow-hidden')
+				}).emulateTransitionEnd(delay);
+			}
+		}
+		
+		
+		this.hidden = function() {
+			$window.off('.aside')
+			//$modal.off('.aside')
+			//			
+			if( !ace.vars['transition'] || hasFade ) {
+				$modal.addClass('aside-hidden');
+				$modal.css('position', '');
+			}
+		}
+		
+		
+		this.insideContainer = function() {
+			var container = $('.main-container');
+
+			var dialog = $modal.find('.modal-dialog');
+			dialog.css({'right': '', 'left': ''});
+			if( container.hasClass('container') ) {
+				var flag = false;
+				if(vertical == true) {
+					dialog.css( placement, parseInt(($window.width() - container.width()) / 2) );
+					flag = true;
+				}
+
+				//strange firefox issue, not redrawing properly on window resize (zoom in/out)!!!!
+				//--- firefix is still having issue!
+				if(flag && ace.vars['firefox']) {
+					ace.helper.redraw(container[0]);
+				}
+			}
+		}
+		
+		this.flip = function() {
+			var flipSides = {right : 'left', left : 'right', top: 'bottom', bottom: 'top'};
+			$modal.removeClass('aside-'+placement).addClass('aside-'+flipSides[placement]);
+			placement = flipSides[placement];
+		}
+
+		var toggleButton = function() {
+			var btn = $modal.find('.aside-trigger');
+			if(btn.length == 0) return;
+			btn.toggleClass('open');
+			
+			var icon = btn.find(ace.vars['.icon']);
+			if(icon.length == 0) return;
+			icon.toggleClass(icon.attr('data-icon1') + " " + icon.attr('data-icon2'));
+		}
+		
+
+		this.initiate();
+		
+		if(this.container) this.container = $(this.container);
+		$modal.appendTo(this.container || 'body'); 
+	}
+
+
+	$(document)
+	.on('show.bs.modal', '.modal.aside', function(e) {
+		$('.aside.in').modal('hide');//??? hide previous open ones?
+		$(this).ace_aside('show');
+	})
+	.on('hide.bs.modal', '.modal.aside', function(e) {
+		$(this).ace_aside('hide');
+	})
+	.on('shown.bs.modal', '.modal.aside', function(e) {
+		$(this).ace_aside('shown');
+	})
+	.on('hidden.bs.modal', '.modal.aside', function(e) {
+		$(this).ace_aside('hidden');
+	})
+	
+	
+
+	
+	$(window).on('resize.aside_container', function() {
+		$('.modal.aside').ace_aside('insideContainer');
+	});
+	$(document).on('settings.ace.aside', function(e, event_name) {
+		if(event_name == 'main_container_fixed') $('.modal.aside').ace_aside('insideContainer');
+	});
+
+	$.fn.aceAside = $.fn.ace_aside = function (option, value) {
+		var method_call;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_aside');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_aside', (data = new Aside(this, options)));
+			if (typeof option === 'string' && typeof data[option] === 'function') {
+				if(value instanceof Array) method_call = data[option].apply(data, value);
+				else method_call = data[option](value);
+			}
+		});
+
+		return (method_call === undefined) ? $set : method_call;
+	}
+	
+	$.fn.ace_aside.defaults = {
+		fixed: false,
+		background: false,
+		offset: false,
+		body_scroll: false,
+		transition: true,
+		scroll_style: 'scroll-dark no-track',
+		container: null,
+		backdrop: false,
+		placement: 'right'
+     }
+
+})(window.jQuery);

+ 112 - 0
src/main/webapp/static/ace/assets/js/ace/elements.colorpicker.js

@@ -0,0 +1,112 @@
+/**
+ <b>Custom color picker element</b>. Converts html select elements to a dropdown color picker.
+*/
+(function($ , undefined) {
+	var Ace_Colorpicker = function(element, _options) {
+
+		var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_colorpicker.defaults);
+		var options = $.extend({}, $.fn.ace_colorpicker.defaults, _options, attrib_values);
+
+
+		var $element = $(element);
+		var color_list = '';
+		var color_selected = '';
+		var selection = null;
+		var color_array = [];
+		
+		$element.addClass('hide').find('option').each(function() {
+			var $class = 'colorpick-btn';
+			var color = this.value.replace(/[^\w\s,#\(\)\.]/g, '');
+			if(this.value != color) this.value = color;
+			if(this.selected) {
+				$class += ' selected';
+				color_selected = color;
+			}
+			color_array.push(color)
+			color_list += '<li><a class="'+$class+'" href="#" style="background-color:'+color+';" data-color="'+color+'"></a></li>';
+		}).
+		end()
+		.on('change.color', function(){
+			$element.next().find('.btn-colorpicker').css('background-color', this.value);
+		})
+		.after('<div class="dropdown dropdown-colorpicker">\
+		<a data-toggle="dropdown" class="dropdown-toggle" '+(options.auto_pos ? 'data-position="auto"' : '')+' href="#"><span class="btn-colorpicker" style="background-color:'+color_selected+'"></span></a><ul class="dropdown-menu'+(options.caret? ' dropdown-caret' : '')+(options.pull_right ? ' dropdown-menu-right' : '')+'">'+color_list+'</ul></div>')
+
+		
+		var dropdown = $element.next().find('.dropdown-menu')
+		dropdown.on(ace.click_event, function(e) {
+			var a = $(e.target);
+			if(!a.is('.colorpick-btn')) return false;
+
+			if(selection) selection.removeClass('selected');
+			selection = a;
+			selection.addClass('selected');
+			var color = selection.data('color');
+
+			$element.val(color).trigger('change');
+
+			e.preventDefault();
+			return true;//to hide dropdown
+		})
+		selection = $element.next().find('a.selected');
+
+		this.pick = function(index, insert) {
+			if(typeof index === 'number') {
+				if(index >= color_array.length) return;
+				element.selectedIndex = index;
+				dropdown.find('a:eq('+index+')').trigger(ace.click_event);
+			}
+			else if(typeof index === 'string') {
+				var color = index.replace(/[^\w\s,#\(\)\.]/g, '');
+				index = color_array.indexOf(color);
+				//add this color if it doesn't exist
+				if(index == -1 && insert === true) {
+					color_array.push(color);
+					
+					$('<option />')
+					.appendTo($element)
+					.val(color);
+					
+					$('<li><a class="colorpick-btn" href="#"></a></li>')
+					.appendTo(dropdown)
+					.find('a')
+					.css('background-color', color)
+					.data('color', color);
+					
+					index = color_array.length - 1;
+				}
+				if(index == -1) return;
+				dropdown.find('a:eq('+index+')').trigger(ace.click_event);
+			}
+		}
+
+		this.destroy = function() {
+			$element.removeClass('hide').off('change.color')
+			.next().remove();
+			color_array = [];
+		}
+	}
+
+
+	$.fn.ace_colorpicker = function(option, value) {
+		var retval;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_colorpicker');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_colorpicker', (data = new Ace_Colorpicker(this, options)));
+			if (typeof option === 'string') retval = data[option](value);
+		});
+
+		return (retval === undefined) ? $set : retval;
+	}
+	
+	$.fn.ace_colorpicker.defaults = {
+		'pull_right' : false,
+		'caret': true,
+		'auto_pos': true
+	}
+	
+})(window.jQuery);

+ 647 - 0
src/main/webapp/static/ace/assets/js/ace/elements.fileinput.js

@@ -0,0 +1,647 @@
+/**
+ <b>Ace file input element</b>. Custom, simple file input element to style browser's default file input.
+*/
+(function($ , undefined) {
+	var multiplible = 'multiple' in document.createElement('INPUT');
+	var hasFileList = 'FileList' in window;//file list enabled in modern browsers
+	var hasFileReader = 'FileReader' in window;
+	var hasFile = 'File' in window;
+
+	var Ace_File_Input = function(element , settings) {
+		var self = this;
+		
+		var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_file_input.defaults);
+		this.settings = $.extend({}, $.fn.ace_file_input.defaults, settings, attrib_values);
+
+		this.$element = $(element);
+		this.element = element;
+		this.disabled = false;
+		this.can_reset = true;
+		
+
+		this.$element
+		.off('change.ace_inner_call')
+		.on('change.ace_inner_call', function(e , ace_inner_call){
+			if(self.disabled) return;
+		
+			if(ace_inner_call === true) return;//this change event is called from above drop event and extra checkings are taken care of there
+			return handle_on_change.call(self);
+			//if(ret === false) e.preventDefault();
+		});
+
+		var parent_label = this.$element.closest('label').css({'display':'block'})
+		var tagName = parent_label.length == 0 ? 'label' : 'span';//if not inside a "LABEL" tag, use "LABEL" tag, otherwise use "SPAN"
+		this.$element.wrap('<'+tagName+' class="ace-file-input" />');
+
+		this.apply_settings();
+		this.reset_input_field();//for firefox as it keeps selected file after refresh
+	}
+	Ace_File_Input.error = {
+		'FILE_LOAD_FAILED' : 1,
+		'IMAGE_LOAD_FAILED' : 2,
+		'THUMBNAIL_FAILED' : 3
+	};
+
+
+	Ace_File_Input.prototype.apply_settings = function() {
+		var self = this;
+
+		this.multi = this.$element.attr('multiple') && multiplible;
+		this.well_style = this.settings.style == 'well';
+
+		if(this.well_style) this.$element.parent().addClass('ace-file-multiple');
+		 else this.$element.parent().removeClass('ace-file-multiple');
+
+
+		this.$element.parent().find(':not(input[type=file])').remove();//remove all except our input, good for when changing settings
+		this.$element.after('<span class="ace-file-container" data-title="'+this.settings.btn_choose+'"><span class="ace-file-name" data-title="'+this.settings.no_file+'">'+(this.settings.no_icon ? '<i class="'+ ace.vars['icon'] + this.settings.no_icon+'"></i>' : '')+'</span></span>');
+		this.$label = this.$element.next();
+		this.$container = this.$element.closest('.ace-file-input');
+
+		var remove_btn = !!this.settings.icon_remove;
+		if(remove_btn) {
+			var btn = 
+			$('<a class="remove" href="#"><i class="'+ ace.vars['icon'] + this.settings.icon_remove+'"></i></a>')
+			.appendTo(this.$element.parent());
+
+			btn.on(ace.click_event, function(e){
+				e.preventDefault();
+				if( !self.can_reset ) return false;
+				
+				var ret = true;
+				if(self.settings.before_remove) ret = self.settings.before_remove.call(self.element);
+				if(!ret) return false;
+
+				var r = self.reset_input();
+				return false;
+			});
+		}
+
+
+		if(this.settings.droppable && hasFileList) {
+			enable_drop_functionality.call(this);
+		}
+	}
+
+	Ace_File_Input.prototype.show_file_list = function($files , inner_call) {
+		var files = typeof $files === "undefined" ? this.$element.data('ace_input_files') : $files;
+		if(!files || files.length == 0) return;
+		
+		//////////////////////////////////////////////////////////////////
+		
+		if(this.well_style) {
+			this.$label.find('.ace-file-name').remove();
+			if(!this.settings.btn_change) this.$label.addClass('hide-placeholder');
+		}
+		this.$label.attr('data-title', this.settings.btn_change).addClass('selected');
+		
+		for (var i = 0; i < files.length; i++) {
+			var filename = '', format = false;
+			if(typeof files[i] === "string") filename = files[i];
+			else if(hasFile && files[i] instanceof File) filename = $.trim( files[i].name );
+			else if(files[i] instanceof Object && files[i].hasOwnProperty('name')) {
+				//format & name specified by user (pre-displaying name, etc)
+				filename = files[i].name;
+				if(files[i].hasOwnProperty('type')) format = files[i].type;
+				if(!files[i].hasOwnProperty('path')) files[i].path = files[i].name;
+			}
+			else continue;
+			
+			var index = filename.lastIndexOf("\\") + 1;
+			if(index == 0)index = filename.lastIndexOf("/") + 1;
+			filename = filename.substr(index);
+			
+			if(format == false) {
+				if((/\.(jpe?g|png|gif|svg|bmp|tiff?)$/i).test(filename)) {				
+					format = 'image';
+				}
+				else if((/\.(mpe?g|flv|mov|avi|swf|mp4|mkv|webm|wmv|3gp)$/i).test(filename)) {
+					format = 'video';
+				}
+				else if((/\.(mp3|ogg|wav|wma|amr|aac)$/i).test(filename)) {
+					format = 'audio';
+				}
+				else format = 'file';
+			}
+			
+			var fileIcons = {
+				'file' : 'fa fa-file',
+				'image' : 'fa fa-picture-o file-image',
+				'video' : 'fa fa-film file-video',
+				'audio' : 'fa fa-music file-audio'
+			};
+			var fileIcon = fileIcons[format];
+			
+			
+			if(!this.well_style) this.$label.find('.ace-file-name').attr({'data-title':filename}).find(ace.vars['.icon']).attr('class', ace.vars['icon'] + fileIcon);
+			else {
+				this.$label.append('<span class="ace-file-name" data-title="'+filename+'"><i class="'+ ace.vars['icon'] + fileIcon+'"></i></span>');
+				var type = (inner_call === true && hasFile && files[i] instanceof File) ? $.trim(files[i].type) : '';
+				var can_preview = hasFileReader && this.settings.thumbnail 
+						&&
+						( (type.length > 0 && type.match('image')) || (type.length == 0 && format == 'image') )//the second one is for older Android's default browser which gives an empty text for file.type
+				if(can_preview) {
+					var self = this;
+					$.when(preview_image.call(this, files[i])).fail(function(result){
+						//called on failure to load preview
+						if(self.settings.preview_error) self.settings.preview_error.call(self, filename, result.code);
+					})
+				}
+			}
+		}
+		
+		return true;
+	}
+	
+	Ace_File_Input.prototype.reset_input = function() {
+	    this.reset_input_ui();
+		this.reset_input_field();
+	}
+	
+	Ace_File_Input.prototype.reset_input_ui = function() {
+		 this.$label.attr({'data-title':this.settings.btn_choose, 'class':'ace-file-container'})
+			.find('.ace-file-name:first').attr({'data-title':this.settings.no_file , 'class':'ace-file-name'})
+			.find(ace.vars['.icon']).attr('class', ace.vars['icon'] + this.settings.no_icon)
+			.prev('img').remove();
+			if(!this.settings.no_icon) this.$label.find(ace.vars['.icon']).remove();
+		
+		this.$label.find('.ace-file-name').not(':first').remove();
+		
+		this.reset_input_data();
+		
+		//if(ace.vars['old_ie']) ace.helper.redraw(this.$container[0]);
+	}
+	Ace_File_Input.prototype.reset_input_field = function() {
+		//http://stackoverflow.com/questions/1043957/clearing-input-type-file-using-jquery/13351234#13351234
+		this.$element.wrap('<form>').parent().get(0).reset();
+		this.$element.unwrap();
+		
+		//strangely when reset is called on this temporary inner form
+		//only **IE9/10** trigger 'reset' on the outer form as well
+		//and as we have mentioned to reset input on outer form reset
+		//it causes infinite recusrsion by coming back to reset_input_field
+		//thus calling reset again and again and again
+		//so because when "reset" button of outer form is hit, file input is automatically reset
+		//we just reset_input_ui to avoid recursion
+	}
+	Ace_File_Input.prototype.reset_input_data = function() {
+		if(this.$element.data('ace_input_files')) {
+			this.$element.removeData('ace_input_files');
+			this.$element.removeData('ace_input_method');
+		}
+	}
+
+	Ace_File_Input.prototype.enable_reset = function(can_reset) {
+		this.can_reset = can_reset;
+	}
+
+	Ace_File_Input.prototype.disable = function() {
+		this.disabled = true;
+		this.$element.attr('disabled', 'disabled').addClass('disabled');
+	}
+	Ace_File_Input.prototype.enable = function() {
+		this.disabled = false;
+		this.$element.removeAttr('disabled').removeClass('disabled');
+	}
+
+	Ace_File_Input.prototype.files = function() {
+		return $(this).data('ace_input_files') || null;
+	}
+	Ace_File_Input.prototype.method = function() {
+		return $(this).data('ace_input_method') || '';
+	}
+	
+	Ace_File_Input.prototype.update_settings = function(new_settings) {
+		this.settings = $.extend({}, this.settings, new_settings);
+		this.apply_settings();
+	}
+	
+	Ace_File_Input.prototype.loading = function(is_loading) {
+		if(is_loading === false) {
+			this.$container.find('.ace-file-overlay').remove();
+			this.element.removeAttribute('readonly');
+		}
+		else {
+			var inside = typeof is_loading === 'string' ? is_loading : '<i class="overlay-content fa fa-spin fa-spinner orange2 fa-2x"></i>';
+			var loader = this.$container.find('.ace-file-overlay');
+			if(loader.length == 0) {
+				loader = $('<div class="ace-file-overlay"></div>').appendTo(this.$container);
+				loader.on('click tap', function(e) {
+					e.stopImmediatePropagation();
+					e.preventDefault();
+					return false;
+				});
+				
+				this.element.setAttribute('readonly' , 'true');//for IE
+			}
+			loader.empty().append(inside);
+		}
+	}
+
+
+
+	var enable_drop_functionality = function() {
+		var self = this;
+		
+		var dropbox = this.$element.parent();
+		dropbox
+		.off('dragenter')
+		.on('dragenter', function(e){
+			e.preventDefault();
+			e.stopPropagation();
+		})
+		.off('dragover')
+		.on('dragover', function(e){
+			e.preventDefault();
+			e.stopPropagation();
+		})
+		.off('drop')
+		.on('drop', function(e){
+			e.preventDefault();
+			e.stopPropagation();
+
+			if(self.disabled) return;
+		
+			var dt = e.originalEvent.dataTransfer;
+			var file_list = dt.files;
+			if(!self.multi && file_list.length > 1) {//single file upload, but dragged multiple files
+				var tmpfiles = [];
+				tmpfiles.push(file_list[0]);
+				file_list = tmpfiles;//keep only first file
+			}
+			
+			
+			file_list = processFiles.call(self, file_list, true);//true means files have been selected, not dropped
+			if(file_list === false) return false;
+
+			self.$element.data('ace_input_method', 'drop');
+			self.$element.data('ace_input_files', file_list);//save files data to be used later by user
+
+			self.show_file_list(file_list , true);
+			
+			self.$element.triggerHandler('change' , [true]);//true means ace_inner_call
+			return true;
+		});
+	}
+	
+	
+	var handle_on_change = function() {
+		var file_list = this.element.files || [this.element.value];/** make it an array */
+		
+		file_list = processFiles.call(this, file_list, false);//false means files have been selected, not dropped
+		if(file_list === false) return false;
+		
+		this.$element.data('ace_input_method', 'select');
+		this.$element.data('ace_input_files', file_list);
+		
+		this.show_file_list(file_list , true);
+		
+		return true;
+	}
+
+
+
+	var preview_image = function(file) {
+		var self = this;
+		var $span = self.$label.find('.ace-file-name:last');//it should be out of onload, otherwise all onloads may target the same span because of delays
+		
+		var deferred = new $.Deferred;
+		
+		var getImage = function(src, $file) {
+			$span.prepend("<img class='middle' style='display:none;' />");
+			var img = $span.find('img:last').get(0);
+		
+			$(img).one('load', function() {
+				imgLoaded.call(null, img, $file);
+			}).one('error', function() {
+				imgFailed.call(null, img);
+			});
+
+			img.src = src;
+		}
+		var imgLoaded = function(img, $file) {
+			//if image loaded successfully
+			
+			var size = self.settings['previewSize'];
+			
+			if(!size) {
+				if(self.settings['previewWidth'] || self.settings['previewHeight']) {
+					size = {previewWidth: self.settings['previewWidth'], previewHeight: self.settings['previewHeight']}
+				}
+				else {
+					size = 50;
+					if(self.settings.thumbnail == 'large') size = 150;
+				}
+			}
+			if(self.settings.thumbnail == 'fit') size = $span.width();
+			else if(typeof size == 'number') size = parseInt(Math.min(size, $span.width()));
+
+
+			var thumb = get_thumbnail(img, size/**, file.type*/);
+			if(thumb == null) {
+				//if making thumbnail fails
+				$(this).remove();
+				deferred.reject({code:Ace_File_Input.error['THUMBNAIL_FAILED']});
+				return;
+			}
+			
+			
+			var showPreview = true;
+			//add width/height info to "file" and trigger preview finished event for each image!
+			if($file && $file instanceof File) {
+				$file.width = thumb.width;
+				$file.height = thumb.height;
+				self.$element.trigger('file.preview.ace', {'file': $file});
+				
+				var event
+				self.$element.trigger( event = new $.Event('file.preview.ace'), {'file': $file} );
+				if ( event.isDefaultPrevented() ) showPreview = false;
+			}
+			
+
+			if(showPreview) {
+				var w = thumb.previewWidth, h = thumb.previewHeight;
+				if(self.settings.thumbnail == 'small') {w=h=parseInt(Math.max(w,h))}
+				else $span.addClass('large');
+
+				$(img).css({'background-image':'url('+thumb.src+')' , width:w, height:h})
+						.data('thumb', thumb.src)
+						.attr({src:'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=='})
+						.show()
+			}
+
+			///////////////////
+			deferred.resolve();
+		}
+		var imgFailed = function(img) {
+			//for example when a file has image extenstion, but format is something else
+			$span.find('img').remove();
+			deferred.reject({code:Ace_File_Input.error['IMAGE_LOAD_FAILED']});
+		}
+		
+		if(hasFile && file instanceof File) {
+			var reader = new FileReader();
+			reader.onload = function (e) {
+				getImage(e.target.result, file);
+			}
+			reader.onerror = function (e) {
+				deferred.reject({code:Ace_File_Input.error['FILE_LOAD_FAILED']});
+			}
+			reader.readAsDataURL(file);
+		}
+		else {
+			if(file instanceof Object && file.hasOwnProperty('path')) {
+				getImage(file.path, null);//file is a file name (path) --- this is used to pre-show user-selected image
+			}
+		}
+		
+		return deferred.promise();
+	}
+
+	var get_thumbnail = function(img, size, type) {
+		var imgWidth = img.width, imgHeight = img.height;
+		
+		//**IE10** is not giving correct width using img.width so we use $(img).width()
+		imgWidth = imgWidth > 0 ? imgWidth : $(img).width()
+		imgHeight = imgHeight > 0 ? imgHeight : $(img).height()
+
+		var previewSize = false, previewHeight = false, previewWidth = false;
+		if(typeof size == 'number') previewSize = size;
+		else if(size instanceof Object) {
+			if(size['previewWidth'] && !size['previewHeight']) previewWidth = size['previewWidth'];
+			else if(size['previewHeight'] && !size['previewWidth']) previewHeight = size['previewHeight'];
+			else if(size['previewWidth'] && size['previewHeight']) {
+				previewWidth = size['previewWidth'];
+				previewHeight = size['previewHeight'];
+			}
+		}
+		
+		if(previewSize) {
+			if(imgWidth > imgHeight) {
+				previewWidth = previewSize;
+				previewHeight = parseInt(imgHeight/imgWidth * previewWidth);
+			} else {
+				previewHeight = previewSize;
+				previewWidth = parseInt(imgWidth/imgHeight * previewHeight);
+			}
+		}
+		else {
+			if(!previewHeight && previewWidth) {
+				previewHeight = parseInt(imgHeight/imgWidth * previewWidth);
+			}
+			else if(previewHeight && !previewWidth) {
+				previewWidth = parseInt(imgWidth/imgHeight * previewHeight);
+			}
+		}
+		
+		
+	
+		var dataURL
+		try {
+			var canvas = document.createElement('canvas');
+			canvas.width = previewWidth; canvas.height = previewHeight;
+			var context = canvas.getContext('2d');
+			context.drawImage(img, 0, 0, imgWidth, imgHeight, 0, 0, previewWidth, previewHeight);
+			dataURL = canvas.toDataURL(/*type == 'image/jpeg' ? type : 'image/png', 10*/)
+		} catch(e) {
+			dataURL = null;
+		}
+		if(! dataURL) return null;
+		
+
+		//there was only one image that failed in firefox completely randomly! so let's double check things
+		if( !( /^data\:image\/(png|jpe?g|gif);base64,[0-9A-Za-z\+\/\=]+$/.test(dataURL)) ) dataURL = null;
+		if(! dataURL) return null;
+		
+
+		return {src: dataURL, previewWidth: previewWidth, previewHeight: previewHeight, width: imgWidth, height: imgHeight};
+	}
+	
+
+	
+	var processFiles = function(file_list, dropped) {
+		var ret = checkFileList.call(this, file_list, dropped);
+		if(ret === -1) {
+			this.reset_input();
+			return false;
+		}
+		if( !ret || ret.length == 0 ) {
+			if( !this.$element.data('ace_input_files') ) this.reset_input();
+			//if nothing selected before, reset because of the newly unacceptable (ret=false||length=0) selection
+			//otherwise leave the previous selection intact?!!!
+			return false;
+		}
+		if (ret instanceof Array || (hasFileList && ret instanceof FileList)) file_list = ret;
+		
+		
+		ret = true;
+		if(this.settings.before_change) ret = this.settings.before_change.call(this.element, file_list, dropped);
+		if(ret === -1) {
+			this.reset_input();
+			return false;
+		}
+		if(!ret || ret.length == 0) {
+			if( !this.$element.data('ace_input_files') ) this.reset_input();
+			return false;
+		}
+		
+		//inside before_change you can return a modified File Array as result
+		if (ret instanceof Array || (hasFileList && ret instanceof FileList)) file_list = ret;
+		
+		return file_list;
+	}
+	
+	
+	var getExtRegex = function(ext) {
+		if(!ext) return null;
+		if(typeof ext === 'string') ext = [ext];
+		if(ext.length == 0) return null;
+		return new RegExp("\.(?:"+ext.join('|')+")$", "i");
+	}
+	var getMimeRegex = function(mime) {
+		if(!mime) return null;
+		if(typeof mime === 'string') mime = [mime];
+		if(mime.length == 0) return null;
+		return new RegExp("^(?:"+mime.join('|').replace(/\//g, "\\/")+")$", "i");
+	}
+	var checkFileList = function(files, dropped) {
+		var allowExt   = getExtRegex(this.settings.allowExt);
+
+		var denyExt    = getExtRegex(this.settings.denyExt);
+		
+		var allowMime  = getMimeRegex(this.settings.allowMime);
+
+		var denyMime   = getMimeRegex(this.settings.denyMime);
+
+		var maxSize    = this.settings.maxSize || false;
+		
+		if( !(allowExt || denyExt || allowMime || denyMime || maxSize) ) return true;//no checking required
+
+
+		var safe_files = [];
+		var error_list = {}
+		for(var f = 0; f < files.length; f++) {
+			var file = files[f];
+			
+			//file is either a string(file name) or a File object
+			var filename = !hasFile ? file : file.name;
+			if( allowExt && !allowExt.test(filename) ) {
+				//extension not matching whitelist, so drop it
+				if(!('ext' in error_list)) error_list['ext'] = [];
+				 error_list['ext'].push(filename);
+				
+				continue;
+			} else if( denyExt && denyExt.test(filename) ) {
+				//extension is matching blacklist, so drop it
+				if(!('ext' in error_list)) error_list['ext'] = [];
+				 error_list['ext'].push(filename);
+				
+				continue;
+			}
+
+			var type;
+			if( !hasFile ) {
+				//in browsers that don't support FileReader API
+				safe_files.push(file);
+				continue;
+			}
+			else if((type = $.trim(file.type)).length > 0) {
+				//there is a mimetype for file so let's check against are rules
+				if( allowMime && !allowMime.test(type) ) {
+					//mimeType is not matching whitelist, so drop it
+					if(!('mime' in error_list)) error_list['mime'] = [];
+					 error_list['mime'].push(filename);
+					continue;
+				}
+				else if( denyMime && denyMime.test(type) ) {
+					//mimeType is matching blacklist, so drop it
+					if(!('mime' in error_list)) error_list['mime'] = [];
+					 error_list['mime'].push(filename);
+					continue;
+				}
+			}
+
+			if( maxSize && file.size > maxSize ) {
+				//file size is not acceptable
+				if(!('size' in error_list)) error_list['size'] = [];
+				 error_list['size'].push(filename);
+				continue;
+			}
+
+			safe_files.push(file)
+		}
+		
+	
+		
+		if(safe_files.length == files.length) return files;//return original file list if all are valid
+
+		/////////
+		var error_count = {'ext': 0, 'mime': 0, 'size': 0}
+		if( 'ext' in error_list ) error_count['ext'] = error_list['ext'].length;
+		if( 'mime' in error_list ) error_count['mime'] = error_list['mime'].length;
+		if( 'size' in error_list ) error_count['size'] = error_list['size'].length;
+		
+		var event
+		this.$element.trigger(
+			event = new $.Event('file.error.ace'), 
+			{
+				'file_count': files.length,
+				'invalid_count' : files.length - safe_files.length,
+				'error_list' : error_list,
+				'error_count' : error_count,
+				'dropped': dropped
+			}
+		);
+		if ( event.isDefaultPrevented() ) return -1;//it will reset input
+		//////////
+
+		return safe_files;//return safe_files
+	}
+
+
+
+	///////////////////////////////////////////
+	$.fn.aceFileInput = $.fn.ace_file_input = function (option,value) {
+		var retval;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_file_input');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_file_input', (data = new Ace_File_Input(this, options)));
+			if (typeof option === 'string') retval = data[option](value);
+		});
+
+		return (retval === undefined) ? $set : retval;
+	};
+
+
+	$.fn.ace_file_input.defaults = {
+		style: false,
+		no_file: 'No File ...',
+		no_icon: 'fa fa-upload',
+		btn_choose: 'Choose',
+		btn_change: 'Change',
+		icon_remove: 'fa fa-times',
+		droppable: false,
+		thumbnail: false,//large, fit, small
+		
+		allowExt: null,
+		denyExt: null,
+		allowMime: null,
+		denyMime: null,
+		maxSize: false,
+		
+		previewSize: false,
+		previewWidth: false,
+		previewHeight: false,
+		
+		//callbacks
+		before_change: null,
+		before_remove: null,
+		preview_error: null
+     }
+
+
+})(window.jQuery);

+ 708 - 0
src/main/webapp/static/ace/assets/js/ace/elements.onpage-help.js

@@ -0,0 +1,708 @@
+window.Onpage_Help = function(options) {
+	var $ = window.jQuery || null;
+	if($ == null) return;
+
+	options = options || {}
+	var defaults = {
+		include_all: true,
+		icon_1: 'fa fa-question',
+		icon_2: 'fa fa-lightbulb-o',
+		base: '',
+		code_highlight: (!!window.Rainbow ? 'rainbow' : (!!window.Prism ? 'prism' : null)),
+
+		add_panels: true,
+		panel_content_selector: '.info-section',
+		panel_content_title: '.info-title'
+	}
+	this.settings = $.extend({}, defaults, options);
+	
+
+	var $base = this.settings['base'];
+	var ie_fix = document.all && !window.atob;//ie9 and below need a little fix
+
+	var section_start = {};
+	var section_end = {};
+	var section_rect = {};
+	var section_count = 0;
+	
+	var created = false;
+	var active = false;
+	
+	var self = this, _ = this;
+	var ovfx = '';
+	var help_container = null;
+	
+	var body_h, body_w;
+	
+	var captureFocus = function() {
+		if(!help_container) return;
+		var scroll = -1;
+		//like bootstrap modal
+		$(document)
+		.off('focusin.ace.help') //remove any previously attached handler
+		.on('focusin.ace.help', function (e) {
+			if (!( help_container[0] == e.target || $.contains(help_container[0], e.target) )) {
+			  help_container.focus();
+			}
+
+			if(e.target == document && scroll > -1) {
+				//when window regains focus and container is focused, it scrolls to bottom
+				//so we put it back to its place
+				$('body,html').scrollTop(scroll);
+				scroll = -1;
+			}
+		})
+
+		$(window).on('blur.ace.help', function(){
+			scroll = $(window).scrollTop();
+		});
+	}
+	var releaseFocus = function() {
+		$(document).off('focusin.ace.help');
+		$(window).off('blur.ace.help');
+	}
+
+
+	this.toggle = function() {
+		if(active) {
+			self.disable();
+		}
+		else {
+			self.enable();
+		}
+	}
+
+	this.enable = function() {
+		if(active) return;
+		if(typeof _.settings.before_enable === 'function' && _.settings.before_enable.call(self) === false) return;
+		////
+
+		//if( !created ) this.init();
+		active = true;
+		
+		$('.onpage-help-backdrop, .onpage-help-section').removeClass('hidden');
+		
+		ovfx = document.body.style.overflowX;
+		document.body.style.overflowX = 'hidden';//hide body:overflow-x
+		
+		display_help_sections();
+		captureFocus();
+
+		////
+		if(typeof _.settings.after_enable === 'function') _.settings.after_enable.call(self);
+	}
+	
+	this.disable = function() {
+		if(!active) return;
+		if(typeof _.settings.before_disable === 'function' && _.settings.before_disable.call(self)) return;
+		////
+				
+		active = false;
+		$('.onpage-help-backdrop, .onpage-help-section').addClass('hidden');
+
+		document.body.style.overflowX = ovfx;//restore body:overflow-x
+		releaseFocus();
+		
+		////
+		if(typeof _.settings.after_disable === 'function') _.settings.after_disable.call(self);
+	}
+	
+	this.is_active = function() {
+		return active;
+	}
+	this.show_section_help = function(section) {
+		launch_help_modal(section, true);
+	}
+	
+	
+	this.init = function() {
+		if( created ) return;
+	
+		help_container = 
+		$('<div class="onpage-help-container" id="onpage-help-container" tabindex="-1" />')
+		.appendTo('body');
+
+		help_container.append('<div class="onpage-help-backdrop hidden" />')
+
+		//update to correct position and size
+		$(window).on('resize.onpage_help', function() {
+			if(!active) return;
+			display_help_sections();
+			
+			if( help_modal != null && help_modal.hasClass('in') ) {
+				setBodyHeight();				
+				disableBodyScroll();
+			}
+		})
+
+		created = true;
+	}
+	this.init();//create once at first
+	
+
+	///////////////////////////
+	this.update_sections = function() {
+		save_sections(true);//reset sections, maybe because of new elements and comments inserted into DOM
+	}
+
+
+	function display_help_sections() {
+		if(!active) return;
+
+		save_sections();//finds comments and relevant help sections
+
+		body_h = document.body.scrollHeight - 2;
+		body_w = document.body.scrollWidth - 2;
+
+		//we first calculate all positions
+		//because if we calculate one position and then make changes to DOM,
+		//next position calculation will become slow on Webkit, because it tries to re-calculate layout changes and things
+		//i.e. we batch call all and save offsets and scrollWidth, etc and then use them later in highlight_section
+		//Firefox doesn't have such issue
+		for(var name in section_start) {
+			if(section_start.hasOwnProperty(name)) {
+				save_section_offset(name);
+			}
+		}
+		for(var name in section_start) {
+			if(section_start.hasOwnProperty(name)) {
+				highlight_section(name);
+			}
+		}
+	}
+
+	
+	//finds comments and relevant help sections
+	function save_sections(reset) {
+		if( !(reset === true || section_count == 0) ) return;//no need to re-calculate sections, then return
+		if(reset === true) help_container.find('.onpage-help-section').remove();
+
+		section_start = {};
+		section_end = {};
+		section_count = 0;
+		
+		var count1 = 0, count2 = 0;
+		
+		//find all relevant comments
+		var comments = $('*').contents().filter(function(){ return this.nodeType == 8/**Node.COMMENT_NODE;*/ })
+		$(comments).each(function() {
+			var match
+			if( (match = $.trim(this.data).match(/#section\s*:\s*([\w\d\-\.\/]+)/i)) ) {
+				var section_name = match[1];
+				if( !(section_name in section_start) ) 	section_start[ section_name ] = this;
+			}
+			if( (match = $.trim(this.data).match(/\/section\s*:\s*([\w\d\-\.\/]+)/i)) ) {
+				var section_name = match[1];
+				if( !(section_name in section_end) && (section_name in section_start) ) {
+					section_end[ section_name ] = this;
+					section_count++;
+				}
+			}
+		})
+	}
+
+
+	
+	function save_section_offset(name) {
+		if( !(name in section_start) || !(name in section_end) ) return;
+		
+		var x1 = 1000000, y1 = 1000000, x2 = -1000000, y2 = -1000000;
+		var visible = false;
+
+		
+		var elements = [];
+		
+		var start = section_start[name];
+		var end = section_end[name];
+		while(start != end) {
+			start = start.nextSibling;
+			if(start == null) break;
+			else if(start.nodeType == 1 /**Node.ELEMENT_NODE*/) elements.push(start);
+		}
+
+		var elen = elements.length;
+		if(elen > 0 && !_.settings['include_all']) {
+			//calculate dimension of only first and last element
+			elements = elen == 1 ? [elements[0]] : [elements[0], elements[elen - 1]]
+		}
+
+		$(elements).each(function() {
+			var $this = $(this);
+			if( $this.is(':hidden') ) return;
+			
+			var off = $this.offset();
+			var w = $this.outerWidth();
+			var h = $this.outerHeight();
+			
+			if( !off || !w || !h ) return;
+			
+			visible = true;
+			if(off.left < x1) x1 = off.left;
+			if(off.left + w > x2) x2 = off.left + w;
+			
+			if(off.top < y1) y1 = off.top;
+			if(off.top + h > y2) y2 = off.top + h;
+		});
+		
+					
+		if( !visible ) {
+			section_rect[name] = {is_hidden: true}
+			return;
+		}
+		
+		x1 -= 1;
+		y1 -= 1;
+		x2 += 1;
+		y2 += 1;
+		
+		
+		var width = x2 - x1, height = y2 - y1;
+		//section_rect is out of window ???
+		if(x1 + width < 2 || x1 > body_w || y1 + height < 2 || y1 > body_h ) {
+			section_rect[name] = {is_hidden: true}
+			return;
+		}
+
+		section_rect[name] = {
+			'left': parseInt(x1),
+			'top': parseInt(y1),
+			'width': parseInt(width),
+			'height': parseInt(height)
+		}
+	}
+
+
+	function highlight_section(name) {
+		if( !(name in section_rect) || !help_container ) return;
+
+		//div is the highlighted box above each section
+		var div = help_container.find('.onpage-help-section[data-section="'+name+'"]').eq(0);
+		if(div.length == 0)	{
+			div = $('<a class="onpage-help-section" href="#" />').appendTo(help_container);
+			if(ie_fix) div.append('<span class="ie-hover-fix" />');
+			
+			if(_.settings.icon_1) div.append('<i class="help-icon-1 '+_.settings.icon_1+'"></i>');
+			if(_.settings.icon_2) div.append('<i class="help-icon-2 '+_.settings.icon_2+'"></i>');
+			
+			div.attr('data-section', name);
+
+			div.on('click', function(e) {
+				e.preventDefault();
+				launch_help_modal(name);
+			});
+		}
+
+		var rect = section_rect[name];
+		if(rect['is_hidden'] === true) {
+			div.addClass('hidden');
+			return;
+		}
+		
+		div.css({
+			left: rect.left,
+			top: rect.top,
+			width: rect.width,
+			height: rect.height
+		});
+		
+
+		div.removeClass('hidden');
+		div.removeClass('help-section-small help-section-smaller');
+		if(rect.height < 55 || rect.width < 55) {
+			div.addClass('help-section-smaller');
+		}
+		else if(rect.height < 75 || rect.width < 75) {
+			div.addClass('help-section-small');
+		}
+	}
+	
+
+	var nav_list = [];
+	var nav_pos = -1;
+	var mbody = null;
+	var maxh = 0;
+	var help_modal = null;
+
+	//disable body scroll, when modal content has no scrollbars or reached end of scrolling
+	function disableBodyScroll() {
+		if (!mbody) return;
+		
+		var body = mbody[0];
+		var disableScroll = body.scrollHeight <= body.clientHeight;
+		
+		//mousewheel library available?
+		var mousewheel_event = !!$.event.special.mousewheel ? 'mousewheel.ace.help' : 'mousewheel.ace.help DOMMouseScroll.ace.help';
+
+		mbody.parent()
+		.off(mousewheel_event)
+		.on(mousewheel_event, function(event) {
+			if(disableScroll) event.preventDefault();
+			else {
+				event.deltaY = event.deltaY || 0;
+				var delta = (event.deltaY > 0 || event.originalEvent.detail < 0 || event.originalEvent.wheelDelta > 0) ? 1 : -1
+
+				if(delta == -1 && body.scrollTop + body.clientHeight >= body.scrollHeight) event.preventDefault();
+				else if(delta == 1 && body.scrollTop <= 0) event.preventDefault();
+			}
+		});
+	}
+	
+	function setBodyHeight() {
+		if (!mbody) return;
+		
+		var diff = parseInt(help_modal.find('.modal-dialog').css('margin-top'));
+		diff = diff + 110 + parseInt(diff / 2);
+		maxh = parseInt( $(window).innerHeight() - diff + 40 );
+		mbody.css({'max-height': maxh});
+	}
+
+
+	function launch_help_modal(section_name, save_to_list) {
+		if(help_modal == null) {
+			help_modal = $('<div id="onpage-help-modal" class="modal onpage-help-modal" tabindex="-1" role="dialog" aria-labelledby="HelpModalDialog" aria-hidden="true">\
+			  <div class="modal-dialog modal-lg">\
+				<div class="modal-content">\
+					<div class="modal-header">\
+					  <div class="pull-right onpage-help-modal-buttons">\
+						<button aria-hidden="true" data-navdir="up" type="button" class="disabled btn btn-white btn-success btn-sm"><i class="ace-icon fa fa-level-up fa-flip-horizontal bigger-125 icon-only"></i></button>\
+						&nbsp;\
+						<button aria-hidden="true" data-navdir="back" type="button" class="disabled btn btn-white btn-info btn-sm"><i class="ace-icon fa fa-arrow-left icon-only"></i></button>\
+						<button aria-hidden="true" data-navdir="forward" type="button" class="disabled btn btn-white btn-info  btn-sm"><i class="ace-icon fa fa-arrow-right icon-only"></i></button>\
+						&nbsp;\
+						<button aria-hidden="true" data-dismiss="modal" class="btn btn-white btn-danger btn-sm" type="button"><i class="ace-icon fa fa-times icon-only"></i></button>\
+					  </div>\
+					  <h4 class="modal-title">Help Dialog</h4>\
+					</div>\
+					<div class="modal-body"><div class="onpage-help-content"></div></div>\
+				</div>\
+			  </div>\
+			</div>').appendTo('body');
+		
+			mbody = help_modal.find('.modal-body');
+			mbody.css({'overflow-y': 'auto', 'overflow-x': 'hidden'});
+			
+			help_modal.css({'overflow' : 'hidden'})
+			.on('show.bs.modal', function() {
+				releaseFocus();
+			})
+			.on('hidden.bs.modal', function() {
+				captureFocus();
+			})
+			
+			help_modal.find('.onpage-help-modal-buttons').on('click', 'button[data-navdir]', function() {
+				var dir = $(this).attr('data-navdir');
+				if(dir == 'back') {
+					if(nav_pos > 0) {
+						nav_pos--;
+						launch_help_modal(nav_list[nav_pos], false);
+					}
+				}
+				else if(dir == 'forward') {
+					if(nav_pos < nav_list.length - 1) {
+						nav_pos++;
+						launch_help_modal(nav_list[nav_pos], false);//don't save to history list, already in the list
+					}
+				}
+				else if(dir == 'up') {
+					var $this = $(this), url;
+					if( $this.hasClass('disabled') || !(url = $this.attr('data-url')) ) return;
+					
+					launch_help_modal(url , true);//add to history list
+				}
+			});
+		}
+
+
+		if( !help_modal.hasClass('in') ) {
+			if( document.body.lastChild != help_modal[0] ) $(document.body).append(help_modal);//move it to become the last child of body
+			help_modal.modal('show');
+			
+			setBodyHeight();
+		}
+
+		help_modal.find('.modal-title').wrapInner("<span class='hidden' />").append('<i class="fa fa-spinner fa-spin blue bigger-125"></i>');
+		var content = $('.onpage-help-content');
+		content.addClass('hidden')
+		
+		$(document.body).removeClass('modal-open');//modal by default hides body scrollbars, but we don't want to do so, because on modal hide, a winow resize is triggered
+		
+		var parts = section_name.match(/file\:(.*?)\:(.+)/i);
+		if(parts && parts.length == 3) {
+			display_codeview(parts[2], parts[1], false);
+			return;
+		}
+
+		section_name = section_name.replace(/^#/g, '');
+		if(typeof _.settings.section_url === 'function') url = _.settings.section_url.call(self, section_name);
+
+		$.ajax({url: url, dataType: 'text'})
+		.done(function(result) {
+			//find the title for this dialog by looking for a tag that has data-id attribute
+			var title = '', excerpt = '';
+			
+			if(typeof _.settings.section_title === 'function') title = _.settings.section_title.call(self, result, section_name, url);
+			else {
+				var escapeSpecialChars = function(name) {
+					return name.replace(/[\-\.\(\)\=\"\'\\\/]/g, function(a,b){return "\\"+a;})
+				}
+				var tname = section_name;
+				while(title.length == 0) {	
+					var reg_str = '\\<([a-z][a-z0-9]*)(?:\\s+)(?:[^\\<\\>]+?)data\\-id\\=\\"\\#'+escapeSpecialChars(tname)+'\\"(?:[^\\>]*)\\>([\\s\\S]*?)</\\1>';
+					
+					var regexp = new RegExp(reg_str , "im");
+					var arr = result.match(reg_str);
+					if(arr && arr[2]) {
+						title = arr[2];
+						break;
+					}
+
+					//if no "#something.part" was not found try looking for "#something" instead
+					var tpos
+					if((tpos = tname.lastIndexOf('.')) > -1) {
+						tname = tname.substr(0, tpos);
+					} else break;
+				}
+			}
+
+			help_modal.find('.modal-title').html( $.trim(title) || '&nbsp;' );
+
+			if(typeof _.settings.section_content === 'function') excerpt = _.settings.section_content.call(self, result, section_name, url);
+			else {
+				var find1 = '<!-- #section:'+section_name+' -->';
+				var pos1 = result.indexOf(find1);
+				var pos2 = result.indexOf('<!-- /section:'+section_name+' -->', pos1);
+
+				if(pos1 == -1 || pos2 == -1) {
+					help_modal.find('.modal-title').html( '&nbsp;' );
+					return;
+				}
+
+				excerpt = result.substring(pos1 + find1.length + 1, pos2);
+			}
+
+			
+			//convert `<` and `>` to `&lt;` and `&gt;` inside code snippets
+			if(typeof _.settings.code_highlight === 'function') {
+				excerpt = _.settings.code_highlight.call(self, excerpt);
+			}
+			else {
+				//find prism & rainbow style pre tags and replace < > characters with &lt; &gt;
+				excerpt =
+				excerpt.replace(/\<pre((?:(?:.*?)(?:data\-language=["'](?:[\w\d]+)["'])(?:.*?))|(?:(?:.*?)(?:class=["'](?:.*?)language\-(?:[\w\d]+)(?:.*?)["'])(?:.*?)))\>([\s\S]+?)\<\/pre\>/ig, function(a, b, c){
+					return '<pre'+(b)+'>'+c.replace(/\</g , '&lt;').replace(/\>/g , '&gt;')+'</pre>';
+				});
+			}
+
+
+			//modify image paths if needed!
+			if(typeof _.settings.img_url === 'function') {
+				excerpt = excerpt.replace(/\<img(?:(?:.*?)src=["']([^"']+)["'])/ig, function(img, src) {
+					var new_src = _.settings.img_url.call(self, src);
+					return img.replace(src, new_src)
+				});
+			}
+
+
+			//now update content area
+			content.empty().append(excerpt);
+			if(typeof _.settings.code_highlight === 'function') {
+				_.settings.code_highlight.call(self, content);
+			}
+			else if(_.settings.code_highlight === 'rainbow') {
+				try {
+					Rainbow.color(content[0]);
+				} catch(e) {}
+			}
+			else if(_.settings.code_highlight === 'prism') {
+				try {
+					content.find('pre[class*="language-"],code[class*="language-"]').each(function() {
+						Prism.highlightElement(this);
+					})
+				} catch(e) {}
+			}
+
+
+			//wrap titles and contents inside panels
+			if(_.settings.add_panels) {
+				content
+				.find(_.settings.panel_content_selector).each(function() {
+					var header = $(this).prevAll(_.settings.panel_content_title);
+					if(header.length == 0) return false;
+					
+					header =
+					header.attr('class', 'panel-title')
+					.wrapInner('<a class="help-panel-toggle" href="#" data-parent="#" data-toggle="collapse" />')
+					.wrap('<div class="panel-heading" />')
+					.closest('.panel-heading');
+
+					$(this).wrap('<div class="panel panel-default panel-help"><div class="panel-collapse collapse"><div class="panel-body"></div></div></div>');
+					$(this).closest('.panel').prepend(header);
+				})
+							
+				var group_count = $('.panel-group').length;
+				content.find('.panel').each(function() {
+					if( $(this).parent().hasClass('panel-group') ) return;
+
+					var group_id = 'panel-group-help-'+ (++group_count);
+					var group = $('<div class="panel-group" />').insertBefore(this);
+					group.attr('id', group_id);
+					
+					var panel_id = 0;
+					group.siblings('.panel').appendTo(group);
+					group.find('.help-panel-toggle')
+					.append('<i class="pull-right ace-icon fa fa-plus" data-icon-show="ace-icon fa fa-plus" data-icon-hide="ace-icon fa fa-minus"></i>')
+					.attr('data-parent', '#'+group_id)
+					.each(function() {
+						panel_id++;
+						$(this).attr('data-target', '#'+group_id+'-'+panel_id);
+						$(this).closest('.panel-heading').siblings('.panel-collapse').attr('id', group_id+'-'+panel_id);
+					});
+				});
+				$(document).off('click.help-panel-toggle', '.help-panel-toggle').on('click.help-panel-toggle', '.help-panel-toggle', function(e) {
+					e.preventDefault();
+				});
+			}
+
+
+			///////////////////////////////////////////
+
+			content.removeClass('hidden')
+
+			var images = content.find('img:visible');
+			if(images.length > 0) {
+				//handle scrollbars when all images are loaded
+				var ev_count = 0;
+				images.off('.help_body_scroll').on('load.help_body_scroll error.help_body_scroll', function() {
+					$(this).off('.help_body_scroll');
+					ev_count++;
+					if(ev_count >= images.length) disableBodyScroll();
+				});
+			}
+
+			disableBodyScroll();
+			content.find('.panel > .panel-collapse').on('shown.bs.collapse hidden.bs.collapse', function() {
+				disableBodyScroll();
+			});
+
+
+			//save history list
+			add_to_nav_list(section_name, save_to_list);
+		
+			var pos = -1;
+			if((pos = section_name.lastIndexOf('.')) > -1) {
+				section_name = section_name.substr(0, pos);
+				help_modal.find('button[data-navdir=up]').removeClass('disabled').attr('data-url', section_name);
+			}
+			else {
+				help_modal.find('button[data-navdir=up]').addClass('disabled').removeAttr('data-url').blur();
+			}
+		})
+		.fail(function() {
+			help_modal.find('.modal-title').find('.fa-spin').remove().end().find('.hidden').children().unwrap();
+		});
+	}//launch_help_modal
+
+
+	$(document).on('click', '.onpage-help-modal a[href^="http"]', function() {
+		$(this).attr('target', '_blank');
+	});
+	
+	$(document).on('click', '.help-more', function(e) {
+		e.preventDefault();
+		var href = $(this).attr('href');
+		launch_help_modal(href);
+	});
+	
+
+	
+	function add_to_nav_list(section_name, save_to_list) {
+		if(save_to_list !== false) {
+			if(nav_list.length > 0) {
+				nav_list = nav_list.slice(0, nav_pos + 1);
+			}
+			if(nav_list[nav_list.length - 1] != section_name) {
+				nav_list.push(section_name);
+				nav_pos = nav_list.length - 1;
+			}
+		}
+		
+		if(nav_pos == 0){
+			help_modal.find('button[data-navdir=back]').addClass('disabled').blur();
+		}
+		else {
+			help_modal.find('button[data-navdir=back]').removeClass('disabled');
+		}
+		
+		if(nav_pos == nav_list.length - 1){
+			help_modal.find('button[data-navdir=forward]').addClass('disabled').blur();
+		}
+		else {
+			help_modal.find('button[data-navdir=forward]').removeClass('disabled');
+		}
+	}
+
+	
+	$(document).on('click', '.open-file[data-open-file]', function() {
+		help_modal.find('.modal-title').wrapInner("<span class='hidden' />").append('<i class="fa fa-spinner fa-spin blue bigger-125"></i>');
+		$('.onpage-help-content').addClass('hidden')
+
+		var url = $(this).attr('data-path') || $(this).text();
+		var language = $(this).attr('data-open-file');
+		display_codeview(url, language, true);
+	});
+	
+	
+	function display_codeview(url, language, save_to_list) {
+		var $url = url;
+	
+		if(typeof _.settings.file_url === 'function') url = _.settings.file_url.call(self, url, language);
+		$.ajax({url: url, dataType:'text'})
+		.done(function(result) {
+
+			add_to_nav_list('file:'+language+':'+$url, save_to_list);
+			
+			help_modal.find('button[data-navdir=up]').addClass('disabled').blur();
+			help_modal.find('.modal-title').html($url).wrapInner('<code />');
+
+			if(language != 'json') {
+				if(language != 'css') {
+					//replace each tab character with two spaces (only those that start at a new line)
+					result = result.replace(/\n[\t]{1,}/g, function(p, q) {
+						return p.replace(/\t/g, "  ");
+					});
+				} else {
+					result = result.replace(/\t/g , "  ")
+				}
+			}
+			else {
+				language = 'javascript';
+				result = JSON.stringify(JSON.parse(result), null, 2);//add spacing and somehow beautification
+			}
+			
+			result = result.replace(/\>/g, '&gt;').replace(/\</g, '&lt;');
+
+			var content = $('.onpage-help-content');
+			content.removeClass('hidden').empty();
+
+			if(typeof _.settings.code_highlight === 'function') {
+				result = _.settings.code_highlight.call(self, result, language);
+				content.html(result);
+			}
+			else if(_.settings.code_highlight === 'rainbow') {
+				try {
+					Rainbow.color(result, language, function(highlighted_code) {
+						content.html(highlighted_code).wrapInner('<pre data-language="'+language+'" />');
+					});
+				} catch(e){}
+			}
+			else if(_.settings.code_highlight === 'prism') {
+				try {
+					result = Prism.highlight(result, Prism.languages[language] , language);
+					content.html(result).wrapInner('<pre class="language-'+language+'" />');
+				} catch(e){}
+			}
+
+		});
+	}
+
+}

+ 687 - 0
src/main/webapp/static/ace/assets/js/ace/elements.scroller.js

@@ -0,0 +1,687 @@
+/**
+ <b>Ace custom scroller</b>. It is not as feature-rich as plugins such as NiceScroll but it's good enough for most cases.
+*/
+(function($ , undefined) {
+	var Ace_Scroll = function(element , _settings) {
+		var self = this;
+		
+		var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_scroll.defaults);
+		var settings = $.extend({}, $.fn.ace_scroll.defaults, _settings, attrib_values);
+	
+		this.size = 0;
+		this.lock = false;
+		this.lock_anyway = false;
+		
+		this.$element = $(element);
+		this.element = element;
+		
+		var vertical = true;
+
+		var disabled = false;
+		var active = false;
+		var created = false;
+
+		
+		var $content_wrap = null, content_wrap = null;
+		var $track = null, $bar = null, track = null, bar = null;
+		var bar_style = null;
+		
+		var bar_size = 0, bar_pos = 0, bar_max_pos = 0, bar_size_2 = 0, move_bar = true;
+		var reset_once = false;
+		
+		var styleClass = '';
+		var trackFlip = false;//vertical on left or horizontal on top
+		var trackSize = 0;
+
+		var css_pos,
+			css_size,
+			max_css_size,
+			client_size,
+			scroll_direction,
+			scroll_size;
+
+		var ratio = 1;
+		var inline_style = false;
+		var mouse_track = false;
+		var mouse_release_target = 'onmouseup' in window ? window : 'html';
+		var dragEvent = settings.dragEvent || false;
+		
+		var trigger_scroll = _settings.scrollEvent || false;
+		
+		
+		var detached = settings.detached || false;//when detached, hideOnIdle as well?
+		var updatePos = settings.updatePos || false;//default is true
+		
+		var hideOnIdle = settings.hideOnIdle || false;
+		var hideDelay = settings.hideDelay || 1500;
+		var insideTrack = false;//used to hide scroll track when mouse is up and outside of track
+		var observeContent = settings.observeContent || false;
+		var prevContentSize = 0;
+		
+		var is_dirty = true;//to prevent consecutive 'reset' calls
+		
+		this.ref = function() {
+			return this;
+		}
+		
+		this.create = function(_settings) {
+			if(created) return;
+
+			if(_settings) settings = $.extend({}, $.fn.ace_scroll.defaults, _settings);
+
+			this.size = parseInt(this.$element.attr('data-size')) || settings.size || 200;
+			vertical = !settings['horizontal'];
+
+			css_pos = vertical ? 'top' : 'left';//'left' for horizontal
+			css_size = vertical ? 'height' : 'width';//'width' for horizontal
+			max_css_size = vertical ? 'maxHeight' : 'maxWidth';
+
+			client_size = vertical ? 'clientHeight' : 'clientWidth';
+			scroll_direction = vertical ? 'scrollTop' : 'scrollLeft';
+			scroll_size = vertical ? 'scrollHeight' : 'scrollWidth';
+
+
+
+			this.$element.addClass('ace-scroll');
+			if(this.$element.css('position') == 'static') {
+				inline_style = this.element.style.position;
+				this.element.style.position = 'relative';
+			} else inline_style = false;
+
+			var scroll_bar = null;
+			if(!detached) {
+				this.$element.wrapInner('<div class="scroll-content" />');
+				this.$element.prepend('<div class="scroll-track"><div class="scroll-bar"></div></div>');
+			}
+			else {
+				scroll_bar = $('<div class="scroll-track scroll-detached"><div class="scroll-bar"></div></div>').appendTo('body');
+			}
+
+
+			$content_wrap = this.$element;
+			if(!detached) $content_wrap = this.$element.find('.scroll-content').eq(0);
+			
+			if(!vertical) $content_wrap.wrapInner('<div />');
+			
+			content_wrap = $content_wrap.get(0);
+			if(detached) {
+				//set position for detached scrollbar
+				$track = scroll_bar;
+				setTrackPos();
+			}
+			else $track = this.$element.find('.scroll-track').eq(0);
+			
+			$bar = $track.find('.scroll-bar').eq(0);
+			track = $track.get(0);
+			bar = $bar.get(0);
+			bar_style = bar.style;
+
+			//add styling classes and horizontalness
+			if(!vertical) $track.addClass('scroll-hz');
+			if(settings.styleClass) {
+				styleClass = settings.styleClass;
+				$track.addClass(styleClass);
+				trackFlip = !!styleClass.match(/scroll\-left|scroll\-top/);
+			}
+			
+			//calculate size of track!
+			if(trackSize == 0) {
+				$track.show();
+				getTrackSize();
+			}
+			
+			$track.hide();
+			
+
+			//if(!touchDrag) {
+			$track.on('mousedown', mouse_down_track);
+			$bar.on('mousedown', mouse_down_bar);
+			//}
+
+			$content_wrap.on('scroll', function() {
+				if(move_bar) {
+					bar_pos = parseInt(Math.round(this[scroll_direction] * ratio));
+					bar_style[css_pos] = bar_pos + 'px';
+				}
+				move_bar = false;
+				if(trigger_scroll) this.$element.trigger('scroll', [content_wrap]);
+			})
+
+
+			if(settings.mouseWheel) {
+				this.lock = settings.mouseWheelLock;
+				this.lock_anyway = settings.lockAnyway;
+
+				//mousewheel library available?
+				this.$element.on(!!$.event.special.mousewheel ? 'mousewheel.ace_scroll' : 'mousewheel.ace_scroll DOMMouseScroll.ace_scroll', function(event) {
+					if(disabled) return;
+					checkContentChanges(true);
+
+					if(!active) return !self.lock_anyway;
+
+					if(mouse_track) {
+						mouse_track = false;
+						$('html').off('.ace_scroll')
+						$(mouse_release_target).off('.ace_scroll');
+						if(dragEvent) self.$element.trigger('drag.end');
+					}
+					
+
+					event.deltaY = event.deltaY || 0;
+					var delta = (event.deltaY > 0 || event.originalEvent.detail < 0 || event.originalEvent.wheelDelta > 0) ? 1 : -1
+					var scrollEnd = false//have we reached the end of scrolling?
+					
+					var clientSize = content_wrap[client_size], scrollAmount = content_wrap[scroll_direction];
+					if( !self.lock ) {
+						if(delta == -1)	scrollEnd = (content_wrap[scroll_size] <= scrollAmount + clientSize);
+						else scrollEnd = (scrollAmount == 0);
+					}
+
+					self.move_bar(true);
+
+					//var step = parseInt( Math.min(Math.max(parseInt(clientSize / 8) , 80) , self.size) ) + 1;
+					var step = parseInt(clientSize / 8);
+					if(step < 80) step = 80;
+					if(step > self.size) step = self.size;
+					step += 1;
+					
+					content_wrap[scroll_direction] = scrollAmount - (delta * step);
+
+
+					return scrollEnd && !self.lock_anyway;
+				})
+			}
+			
+			
+			//swipe not available yet
+			var touchDrag = ace.vars['touch'] && 'ace_drag' in $.event.special && settings.touchDrag //&& !settings.touchSwipe;
+			//add drag event for touch devices to scroll
+			if(touchDrag/** || ($.fn.swipe && settings.touchSwipe)*/) {
+				var dir = '', event_name = touchDrag ? 'ace_drag' : 'swipe';
+				this.$element.on(event_name + '.ace_scroll', function(event) {
+					if(disabled) {
+						event.retval.cancel = true;
+						return;
+					}
+					checkContentChanges(true);
+					
+					if(!active) {
+						event.retval.cancel = this.lock_anyway;
+						return;
+					}
+
+					dir = event.direction;
+					if( (vertical && (dir == 'up' || dir == 'down'))
+						||
+						(!vertical && (dir == 'left' || dir == 'right'))
+					   )
+					{
+						var distance = vertical ? event.dy : event.dx;
+
+						if(distance != 0) {
+							if(Math.abs(distance) > 20 && touchDrag) distance = distance * 2;
+
+							self.move_bar(true);
+							content_wrap[scroll_direction] = content_wrap[scroll_direction] + distance;
+						}
+					}
+					
+				})
+			}
+			
+			
+			/////////////////////////////////
+			
+			if(hideOnIdle) {
+				$track.addClass('idle-hide');
+			}
+			if(observeContent) {
+				$track.on('mouseenter.ace_scroll', function() {
+					insideTrack = true;
+					checkContentChanges(false);
+				}).on('mouseleave.ace_scroll', function() {
+					insideTrack = false;
+					if(mouse_track == false) hideScrollbars();
+				});
+			}
+
+
+			
+			//some mobile browsers don't have mouseenter
+			this.$element.on('mouseenter.ace_scroll touchstart.ace_scroll', function(e) {
+				is_dirty = true;
+				if(observeContent) checkContentChanges(true);
+				else if(settings.hoverReset) self.reset(true);
+				
+				$track.addClass('scroll-hover');
+			}).on('mouseleave.ace_scroll touchend.ace_scroll', function() {
+				$track.removeClass('scroll-hover');
+			});
+			//
+
+			if(!vertical) $content_wrap.children(0).css(css_size, this.size);//the extra wrapper
+			$content_wrap.css(max_css_size , this.size);
+			
+			disabled = false;
+			created = true;
+		}
+		this.is_active = function() {
+			return active;
+		}
+		this.is_enabled = function() {
+			return !disabled;
+		}
+		this.move_bar = function($move) {
+			move_bar = $move;
+		}
+		
+		this.get_track = function() {
+			return track;
+		}
+
+		this.reset = function(innert_call) {
+			if(disabled) return;// this;
+			if(!created) this.create();
+			/////////////////////
+			var size = this.size;
+			
+			if(innert_call && !is_dirty) {
+				return;
+			}
+			is_dirty = false;
+
+			if(detached) {
+				var border_size = parseInt(Math.round( (parseInt($content_wrap.css('border-top-width')) + parseInt($content_wrap.css('border-bottom-width'))) / 2.5 ));//(2.5 from trial?!)
+				size -= border_size;//only if detached
+			}
+	
+			var content_size   = vertical ? content_wrap[scroll_size] : size;
+			if( (vertical && content_size == 0) || (!vertical && this.element.scrollWidth == 0) ) {
+				//element is hidden
+				//this.$element.addClass('scroll-hidden');
+				$track.removeClass('scroll-active')
+				return;// this;
+			}
+
+			var available_space = vertical ? size : content_wrap.clientWidth;
+
+			if(!vertical) $content_wrap.children(0).css(css_size, size);//the extra wrapper
+			$content_wrap.css(max_css_size , this.size);
+			
+
+			if(content_size > available_space) {
+				active = true;
+				$track.css(css_size, available_space).show();
+
+				ratio = parseFloat((available_space / content_size).toFixed(5))
+				
+				bar_size = parseInt(Math.round(available_space * ratio));
+				bar_size_2 = parseInt(Math.round(bar_size / 2));
+
+				bar_max_pos = available_space - bar_size;
+				bar_pos = parseInt(Math.round(content_wrap[scroll_direction] * ratio));
+
+				bar_style[css_size] = bar_size + 'px';
+				bar_style[css_pos] = bar_pos + 'px';
+				
+				$track.addClass('scroll-active');
+				
+				if(trackSize == 0) {
+					getTrackSize();
+				}
+
+				if(!reset_once) {
+					//this.$element.removeClass('scroll-hidden');
+					if(settings.reset) {
+						//reset scrollbar to zero position at first							
+						content_wrap[scroll_direction] = 0;
+						bar_style[css_pos] = 0;
+					}
+					reset_once = true;
+				}
+				
+				if(detached) setTrackPos();
+			} else {
+				active = false;
+				$track.hide();
+				$track.removeClass('scroll-active');
+				$content_wrap.css(max_css_size , '');
+			}
+
+			return;// this;
+		}
+		this.disable = function() {
+			content_wrap[scroll_direction] = 0;
+			bar_style[css_pos] = 0;
+
+			disabled = true;
+			active = false;
+			$track.hide();
+			
+			this.$element.addClass('scroll-disabled');
+			
+			$track.removeClass('scroll-active');
+			$content_wrap.css(max_css_size , '');
+		}
+		this.enable = function() {
+			disabled = false;
+			this.$element.removeClass('scroll-disabled');
+		}
+		this.destroy = function() {
+			active = false;
+			disabled = false;
+			created = false;
+			
+			this.$element.removeClass('ace-scroll scroll-disabled scroll-active');
+			this.$element.off('.ace_scroll')
+
+			if(!detached) {
+				if(!vertical) {
+					//remove the extra wrapping div
+					$content_wrap.find('> div').children().unwrap();
+				}
+				$content_wrap.children().unwrap();
+				$content_wrap.remove();
+			}
+			
+			$track.remove();
+			
+			if(inline_style !== false) this.element.style.position = inline_style;
+			
+			if(idleTimer != null) {
+				clearTimeout(idleTimer);
+				idleTimer = null;
+			}
+		}
+		this.modify = function(_settings) {
+			if(_settings) settings = $.extend({}, settings, _settings);
+			
+			this.destroy();
+			this.create();
+			is_dirty = true;
+			this.reset(true);
+		}
+		this.update = function(_settings) {
+			if(_settings) settings = $.extend({}, settings, _settings);
+			else _settings = {}
+		
+			this.size = _settings.size || this.size;
+			
+			this.lock = _settings.mouseWheelLock || this.lock;
+			this.lock_anyway = _settings.lockAnyway || this.lock_anyway;
+			
+			hideOnIdle = _settings.hideOnIdle || hideOnIdle;
+			hideDelay = _settings.hideDelay || hideDelay;
+			observeContent = _settings.observeContent || false;
+			
+			dragEvent = _settings.dragEvent || false;
+			
+			if(_settings.styleClass != undefined) {
+				if(styleClass) $track.removeClass(styleClass);
+				styleClass = _settings.styleClass;
+				if(styleClass) $track.addClass(styleClass);
+				trackFlip = !!styleClass.match(/scroll\-left|scroll\-top/);
+			}
+		}
+		
+		this.start = function() {
+			content_wrap[scroll_direction] = 0;
+		}
+		this.end = function() {
+			content_wrap[scroll_direction] = content_wrap[scroll_size];
+		}
+		
+		this.hide = function() {
+			$track.hide();
+		}
+		this.show = function() {
+			$track.show();
+		}
+
+		
+		this.update_scroll = function() {
+			move_bar = false;
+			bar_style[css_pos] = bar_pos + 'px';
+			content_wrap[scroll_direction] = parseInt(Math.round(bar_pos / ratio));
+		}
+
+		function mouse_down_track(e) {
+			e.preventDefault();
+			e.stopPropagation();
+				
+			var track_offset = $track.offset();
+			var track_pos = track_offset[css_pos];//top for vertical, left for horizontal
+			var mouse_pos = vertical ? e.pageY : e.pageX;
+			
+			if(mouse_pos > track_pos + bar_pos) {
+				bar_pos = mouse_pos - track_pos - bar_size + bar_size_2;
+				if(bar_pos > bar_max_pos) {						
+					bar_pos = bar_max_pos;
+				}
+			}
+			else {
+				bar_pos = mouse_pos - track_pos - bar_size_2;
+				if(bar_pos < 0) bar_pos = 0;
+			}
+
+			self.update_scroll()
+		}
+
+		var mouse_pos1 = -1, mouse_pos2 = -1;
+		function mouse_down_bar(e) {
+			e.preventDefault();
+			e.stopPropagation();
+
+			if(vertical) {
+				mouse_pos2 = mouse_pos1 = e.pageY;
+			} else {
+				mouse_pos2 = mouse_pos1 = e.pageX;
+			}
+
+			mouse_track = true;
+			$('html').off('mousemove.ace_scroll').on('mousemove.ace_scroll', mouse_move_bar)
+			$(mouse_release_target).off('mouseup.ace_scroll').on('mouseup.ace_scroll', mouse_up_bar);
+			
+			$track.addClass('active');
+			if(dragEvent) self.$element.trigger('drag.start');
+		}
+		function mouse_move_bar(e) {
+			e.preventDefault();
+			e.stopPropagation();
+
+			if(vertical) {
+				mouse_pos2 = e.pageY;
+			} else {
+				mouse_pos2 = e.pageX;
+			}
+			
+
+			if(mouse_pos2 - mouse_pos1 + bar_pos > bar_max_pos) {
+				mouse_pos2 = mouse_pos1 + bar_max_pos - bar_pos;
+			} else if(mouse_pos2 - mouse_pos1 + bar_pos < 0) {
+				mouse_pos2 = mouse_pos1 - bar_pos;
+			}
+			bar_pos = bar_pos + (mouse_pos2 - mouse_pos1);
+
+			mouse_pos1 = mouse_pos2;
+
+			if(bar_pos < 0) {
+				bar_pos = 0;
+			}
+			else if(bar_pos > bar_max_pos) {
+				bar_pos = bar_max_pos;
+			}
+			
+			self.update_scroll()
+		}
+		function mouse_up_bar(e) {
+			e.preventDefault();
+			e.stopPropagation();
+			
+			mouse_track = false;
+			$('html').off('.ace_scroll')
+			$(mouse_release_target).off('.ace_scroll');
+
+			$track.removeClass('active');
+			if(dragEvent) self.$element.trigger('drag.end');
+			
+			if(active && hideOnIdle && !insideTrack) hideScrollbars();
+		}
+		
+		
+		var idleTimer = null;
+		var prevCheckTime = 0;
+		function checkContentChanges(hideSoon) {
+			//check if content size has been modified since last time?
+			//and with at least 1s delay
+			var newCheck = +new Date();
+			if(observeContent && newCheck - prevCheckTime > 1000) {
+				var newSize = content_wrap[scroll_size];
+				if(prevContentSize != newSize) {
+					prevContentSize = newSize;
+					is_dirty = true;
+					self.reset(true);
+				}
+				prevCheckTime = newCheck;
+			}
+			
+			//show scrollbars when not idle anymore i.e. triggered by mousewheel, dragging, etc
+			if(active && hideOnIdle) {
+				if(idleTimer != null) {
+					clearTimeout(idleTimer);
+					idleTimer = null;
+				}
+				$track.addClass('not-idle');
+			
+				if(!insideTrack && hideSoon == true) {
+					//hideSoon is false when mouse enters track
+					hideScrollbars();
+				}
+			}
+		}
+
+		function hideScrollbars() {
+			if(idleTimer != null) {
+				clearTimeout(idleTimer);
+				idleTimer = null;
+			}
+			idleTimer = setTimeout(function() {
+				idleTimer = null;
+				$track.removeClass('not-idle');
+			} , hideDelay);
+		}
+		
+		//for detached scrollbars
+		function getTrackSize() {
+			$track.css('visibility', 'hidden').addClass('scroll-hover');
+			if(vertical) trackSize = parseInt($track.outerWidth()) || 0;
+			 else trackSize = parseInt($track.outerHeight()) || 0;
+			$track.css('visibility', '').removeClass('scroll-hover');
+		}
+		this.track_size = function() {
+			if(trackSize == 0) getTrackSize();
+			return trackSize;
+		}
+		
+		//for detached scrollbars
+		function setTrackPos() {
+			if(updatePos === false) return;
+		
+			var off = $content_wrap.offset();//because we want it relative to parent not document
+			var left = off.left;
+			var top = off.top;
+
+			if(vertical) {
+				if(!trackFlip) {
+					left += ($content_wrap.outerWidth() - trackSize)
+				}
+			}
+			else {
+				if(!trackFlip) {
+					top += ($content_wrap.outerHeight() - trackSize)
+				}
+			}
+			
+			if(updatePos === true) $track.css({top: parseInt(top), left: parseInt(left)});
+			else if(updatePos === 'left') $track.css('left', parseInt(left));
+			else if(updatePos === 'top') $track.css('top', parseInt(top));
+		}
+		
+
+
+		this.create();
+		is_dirty = true;
+		this.reset(true);
+		prevContentSize = content_wrap[scroll_size];
+
+		return this;
+	}
+
+	
+	$.fn.ace_scroll = function (option,value) {
+		var retval;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('ace_scroll');
+			var options = typeof option === 'object' && option;
+
+			if (!data) $this.data('ace_scroll', (data = new Ace_Scroll(this, options)));
+			 //else if(typeof options == 'object') data['modify'](options);
+			if (typeof option === 'string') retval = data[option](value);
+		});
+
+		return (retval === undefined) ? $set : retval;
+	};
+
+
+	$.fn.ace_scroll.defaults = {
+		'size' : 200,
+		'horizontal': false,
+		'mouseWheel': true,
+		'mouseWheelLock': false,
+		'lockAnyway': false,
+		'styleClass' : false,
+		
+		'observeContent': false,
+		'hideOnIdle': false,
+		'hideDelay': 1500,
+		
+		'hoverReset': true //reset scrollbar sizes on mouse hover because of possible sizing changes
+		,
+		'reset': false //true= set scrollTop = 0
+		,
+		'dragEvent': false
+		,
+		'touchDrag': true
+		,
+		'touchSwipe': false
+		,
+		'scrollEvent': false //trigger scroll event
+
+		,
+		'detached': false
+		,
+		'updatePos': true
+		/**
+		,		
+		'track' : true,
+		'show' : false,
+		'dark': false,
+		'alwaysVisible': false,
+		'margin': false,
+		'thin': false,
+		'position': 'right'
+		*/
+     }
+
+	/**
+	$(document).on('ace.settings.ace_scroll', function(e, name) {
+		if(name == 'sidebar_collapsed') $('.ace-scroll').scroller('reset');
+	});
+	$(window).on('resize.ace_scroll', function() {
+		$('.ace-scroll').scroller('reset');
+	});
+	*/
+
+})(window.jQuery);

+ 115 - 0
src/main/webapp/static/ace/assets/js/ace/elements.spinner.js

@@ -0,0 +1,115 @@
+/**
+ <b>Spinner</b>. A wrapper for FuelUX spinner element.
+ It's just a wrapper so you still need to include FuelUX spinner script first.
+*/
+(function($ , undefined) {
+	//a wrapper for fuelux spinner
+	function Ace_Spinner(element , _options) {
+		var attrib_values = ace.helper.getAttrSettings(element, $.fn.ace_spinner.defaults);
+		var options = $.extend({}, $.fn.ace_spinner.defaults, _options, attrib_values);
+	
+		var max = options.max
+		max = (''+max).length
+		var width = parseInt(Math.max((max * 20 + 40) , 90))
+
+		var $element = $(element);
+		
+		var btn_class = 'btn-sm';//default
+		var sizing = 2;
+		if($element.hasClass('input-sm')) {
+			btn_class = 'btn-xs';
+			sizing = 1;
+		}
+		else if($element.hasClass('input-lg')) {
+			btn_class = 'btn-lg';
+			sizing = 3;
+		}
+		
+		if(sizing == 2) width += 25;
+		else if(sizing == 3) width += 50;
+		
+		$element.addClass('spinbox-input form-control text-center').wrap('<div class="ace-spinner middle">')
+
+		var $parent_div = $element.closest('.ace-spinner').spinbox(options).wrapInner("<div class='input-group'></div>")
+		var $spinner = $parent_div.data('fu.spinbox');
+		
+		if(options.on_sides)
+		{
+			$element
+			.before('<div class="spinbox-buttons input-group-btn">\
+					<button type="button" class="btn spinbox-down '+btn_class+' '+options.btn_down_class+'">\
+						<i class="icon-only '+ ace.vars['icon'] + options.icon_down+'"></i>\
+					</button>\
+				</div>')
+			.after('<div class="spinbox-buttons input-group-btn">\
+					<button type="button" class="btn spinbox-up '+btn_class+' '+options.btn_up_class+'">\
+						<i class="icon-only '+ ace.vars['icon'] + options.icon_up+'"></i>\
+					</button>\
+				</div>');
+
+			$parent_div.addClass('touch-spinner')
+			$parent_div.css('width' , width+'px')
+		}
+		else {
+			 $element
+			 .after('<div class="spinbox-buttons input-group-btn">\
+					<button type="button" class="btn spinbox-up '+btn_class+' '+options.btn_up_class+'">\
+						<i class="icon-only '+ ace.vars['icon'] + options.icon_up+'"></i>\
+					</button>\
+					<button type="button" class="btn spinbox-down '+btn_class+' '+options.btn_down_class+'">\
+						<i class="icon-only '+ ace.vars['icon'] + options.icon_down+'"></i>\
+					</button>\
+				</div>')
+
+			if(ace.vars['touch'] || options.touch_spinner) {
+				$parent_div.addClass('touch-spinner')
+				$parent_div.css('width' , width+'px')
+			}
+			else {
+				$element.next().addClass('btn-group-vertical');
+				$parent_div.css('width' , width+'px')
+			}
+		}
+
+		$parent_div.on('changed', function(){
+			$element.trigger('change')//trigger the input's change event
+		});
+
+		this._call = function(name, arg) {
+			$spinner[name](arg);
+		}
+	}
+
+
+	$.fn.ace_spinner = function(option, value) {
+		var retval;
+
+		var $set = this.each(function() {
+			var $this = $(this);
+			var data = $this.data('ace_spinner');
+			var options = typeof option === 'object' && option;
+
+			if (!data) {
+				options = $.extend({}, $.fn.ace_spinner.defaults, option);
+				$this.data('ace_spinner', (data = new Ace_Spinner(this, options)));
+			}
+			if (typeof option === 'string') retval = data._call(option, value);
+		});
+
+		return (retval === undefined) ? $set : retval;
+	}
+	
+	$.fn.ace_spinner.defaults = {
+		'icon_up' : 'fa fa-chevron-up',
+		'icon_down': 'fa fa-chevron-down',
+		
+		'on_sides': false,		
+		'btn_up_class': '',
+		'btn_down_class' : '',
+		
+		'max' : 999,
+		'touch_spinner': false
+     }
+
+
+})(window.jQuery);

+ 54 - 0
src/main/webapp/static/ace/assets/js/ace/elements.treeview.js

@@ -0,0 +1,54 @@
+/**
+ <b>Treeview</b>. A wrapper for FuelUX treeview element.
+ It's just a wrapper so you still need to include FuelUX treeview script first.
+*/
+(function($ , undefined) {
+
+	$.fn.aceTree = $.fn.ace_tree = function(options) {
+		var $defaults = {
+			'open-icon' : ace.vars['icon'] + 'fa fa-folder-open',
+			'close-icon' : ace.vars['icon'] + 'fa fa-folder',
+			'toggle-icon': ace.vars['icon'] + 'fa fa-play',
+			'selected-icon' : ace.vars['icon'] + 'fa fa-check',
+			'unselected-icon' : ace.vars['icon'] + 'fa fa-times',
+			'base-icon' : ace.vars['icon'] + 'fa',
+			'folder-open-icon' : 'fa fa-plus-square-o',
+			'folder-close-icon' : 'fa fa-plus-minus-o',
+			'loadingHTML': 'Loading...'
+		}
+
+		this.each(function() {
+		
+			var attrib_values = ace.helper.getAttrSettings(this, $defaults);
+			var $options = $.extend({}, $defaults, options, attrib_values);
+
+			var $this = $(this);
+			$this.addClass('tree').attr('role', 'tree');
+			$this.html(
+			'<li class="tree-branch hide" data-template="treebranch" role="treeitem" aria-expanded="false">\
+				'+($options['folderSelect'] ? '<i class="icon-caret '+$options['folder-open-icon']+'"></i>&nbsp;' : '')+'\
+				<div class="tree-branch-header">\
+					<span class="tree-branch-name">\
+						<i class="icon-folder '+$options['close-icon']+'"></i>\
+						<span class="tree-label"></span>\
+					</span>\
+				</div>\
+				<ul class="tree-branch-children" role="group"></ul>\
+				<div class="tree-loader" role="alert">'+$options['loadingHTML']+'</div>\
+			</li>\
+			<li class="tree-item hide" data-template="treeitem" role="treeitem">\
+				<span class="tree-item-name">\
+				  '+($options['unselected-icon'] == null ? '' : '<i class="icon-item '+$options['unselected-icon']+'"></i>')+'\
+				  <span class="tree-label"></span>\
+				</span>\
+			</li>');
+			
+			$this.addClass($options['selectable'] == true ? 'tree-selectable' : 'tree-unselectable');
+			
+			$this.tree($options);
+		});
+
+		return this;
+	}
+
+})(window.jQuery);

+ 340 - 0
src/main/webapp/static/ace/assets/js/ace/elements.typeahead.js

@@ -0,0 +1,340 @@
+/**
+  <b>Bootstrap 2 typeahead plugin.</b> With Bootstrap <u>3</u> it's been dropped in favor of a more advanced separate plugin.
+  Pretty good for simple cases such as autocomplete feature of the search box and required for <u class="text-danger">Tag input</u> plugin.
+*/
+
+/* =============================================================
+ * bootstrap-typeahead.js v2.3.2
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+  "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+  * ================================= */
+
+  var Typeahead = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.bs_typeahead.defaults, options)
+    this.matcher = this.options.matcher || this.matcher
+    this.sorter = this.options.sorter || this.sorter
+    this.highlighter = this.options.highlighter || this.highlighter
+    this.updater = this.options.updater || this.updater
+    this.source = this.options.source
+    this.$menu = $(this.options.menu)
+    this.shown = false
+    this.listen()
+  }
+
+  Typeahead.prototype = {
+
+    constructor: Typeahead
+
+  , select: function () {
+      var val = this.$menu.find('.active').attr('data-value')
+      this.$element
+        .val(this.updater(val))
+        .change()
+      return this.hide()
+    }
+
+  , updater: function (item) {
+      return item
+    }
+
+  , show: function () {
+      var pos = $.extend({}, this.$element.position(), {
+        height: this.$element[0].offsetHeight
+      })
+
+      this.$menu
+        .insertAfter(this.$element)
+        .css({
+          top: pos.top + pos.height
+        , left: pos.left
+        })
+        .show()
+
+      this.shown = true
+      return this
+    }
+
+  , hide: function () {
+      this.$menu.hide()
+      this.shown = false
+      return this
+    }
+
+  , lookup: function (event) {
+      var items
+
+      this.query = this.$element.val()
+
+      if (!this.query || this.query.length < this.options.minLength) {
+        return this.shown ? this.hide() : this
+      }
+
+      items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+      return items ? this.process(items) : this
+    }
+
+  , process: function (items) {
+      var that = this
+
+      items = $.grep(items, function (item) {
+        return that.matcher(item)
+      })
+
+      items = this.sorter(items)
+
+      if (!items.length) {
+        return this.shown ? this.hide() : this
+      }
+
+      return this.render(items.slice(0, this.options.items)).show()
+    }
+
+  , matcher: function (item) {
+      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+    }
+
+  , sorter: function (items) {
+      var beginswith = []
+        , caseSensitive = []
+        , caseInsensitive = []
+        , item
+
+      while (item = items.shift()) {
+        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+        else if (~item.indexOf(this.query)) caseSensitive.push(item)
+        else caseInsensitive.push(item)
+      }
+
+      return beginswith.concat(caseSensitive, caseInsensitive)
+    }
+
+  , highlighter: function (item) {
+      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+        return '<strong>' + match + '</strong>'
+      })
+    }
+
+  , render: function (items) {
+      var that = this
+
+      items = $(items).map(function (i, item) {
+        i = $(that.options.item).attr('data-value', item)
+        i.find('a').html(that.highlighter(item))
+        return i[0]
+      })
+
+      items.first().addClass('active')
+      this.$menu.html(items)
+      return this
+    }
+
+  , next: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , next = active.next()
+
+      if (!next.length) {
+        next = $(this.$menu.find('li')[0])
+      }
+
+      next.addClass('active')
+    }
+
+  , prev: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , prev = active.prev()
+
+      if (!prev.length) {
+        prev = this.$menu.find('li').last()
+      }
+
+      prev.addClass('active')
+    }
+
+  , listen: function () {
+      this.$element
+        .on('focus',    $.proxy(this.focus, this))
+        .on('blur',     $.proxy(this.blur, this))
+        .on('keypress', $.proxy(this.keypress, this))
+        .on('keyup',    $.proxy(this.keyup, this))
+
+      if (this.eventSupported('keydown')) {
+        this.$element.on('keydown', $.proxy(this.keydown, this))
+      }
+
+      this.$menu
+        .on('click', $.proxy(this.click, this))
+        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+        .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
+    }
+
+  , eventSupported: function(eventName) {
+      var isSupported = eventName in this.$element
+      if (!isSupported) {
+        this.$element.setAttribute(eventName, 'return;')
+        isSupported = typeof this.$element[eventName] === 'function'
+      }
+      return isSupported
+    }
+
+  , move: function (e) {
+      if (!this.shown) return
+
+      switch(e.keyCode) {
+        case 9: // tab
+        case 13: // enter
+        case 27: // escape
+          e.preventDefault()
+          break
+
+        case 38: // up arrow
+          e.preventDefault()
+          this.prev()
+          break
+
+        case 40: // down arrow
+          e.preventDefault()
+          this.next()
+          break
+      }
+
+      e.stopPropagation()
+    }
+
+  , keydown: function (e) {
+      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
+      this.move(e)
+    }
+
+  , keypress: function (e) {
+      if (this.suppressKeyPressRepeat) return
+      this.move(e)
+    }
+
+  , keyup: function (e) {
+      switch(e.keyCode) {
+        case 40: // down arrow
+        case 38: // up arrow
+        case 16: // shift
+        case 17: // ctrl
+        case 18: // alt
+          break
+
+        case 9: // tab
+        case 13: // enter
+          if (!this.shown) return
+          this.select()
+          break
+
+        case 27: // escape
+          if (!this.shown) return
+          this.hide()
+          break
+
+        default:
+          this.lookup()
+      }
+
+      e.stopPropagation()
+      e.preventDefault()
+  }
+
+  , focus: function (e) {
+      this.focused = true
+    }
+
+  , blur: function (e) {
+      this.focused = false
+      if (!this.mousedover && this.shown) this.hide()
+    }
+
+  , click: function (e) {
+      e.stopPropagation()
+      e.preventDefault()
+      this.select()
+      this.$element.focus()
+    }
+
+  , mouseenter: function (e) {
+      this.mousedover = true
+      this.$menu.find('.active').removeClass('active')
+      $(e.currentTarget).addClass('active')
+    }
+
+  , mouseleave: function (e) {
+      this.mousedover = false
+      if (!this.focused && this.shown) this.hide()
+    }
+
+  }
+
+
+  /* TYPEAHEAD PLUGIN DEFINITION
+   * =========================== */
+
+  var old = $.fn.bs_typeahead
+
+  $.fn.bs_typeahead = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('bs_typeahead')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('bs_typeahead', (data = new Typeahead(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.bs_typeahead.defaults = {
+    source: []
+  , items: 8
+  , menu: '<ul class="typeahead dropdown-menu"></ul>'
+  , item: '<li><a href="#"></a></li>'
+  , minLength: 1
+  }
+
+  $.fn.bs_typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD NO CONFLICT
+  * =================== */
+
+  $.fn.bs_typeahead.noConflict = function () {
+    $.fn.bs_typeahead = old
+    return this
+  }
+
+
+ /* TYPEAHEAD DATA-API
+  * ================== */
+
+  $(document).on('focus.bs_typeahead.data-api', '[data-provide="bs_typeahead"]', function (e) {
+    var $this = $(this)
+    if ($this.data('bs_typeahead')) return
+    $this.bs_typeahead($this.data())
+  })
+
+}(window.jQuery);

+ 37 - 0
src/main/webapp/static/ace/assets/js/ace/elements.wizard.js

@@ -0,0 +1,37 @@
+/**
+ <b>Wizard</b>. A wrapper for FuelUX wizard element.
+ It's just a wrapper so you still need to include FuelUX wizard script first.
+*/
+(function($ , undefined) {
+	$.fn.aceWizard = $.fn.ace_wizard = function(options) {
+
+		this.each(function() {
+			var $this = $(this);
+			$this.wizard();
+			
+			if(ace.vars['old_ie']) $this.find('ul.steps > li').last().addClass('last-child');
+
+			var buttons = (options && options['buttons']) ? $(options['buttons']) : $this.siblings('.wizard-actions').eq(0);
+			var $wizard = $this.data('fu.wizard');
+			$wizard.$prevBtn.remove();
+			$wizard.$nextBtn.remove();
+			
+			$wizard.$prevBtn = buttons.find('.btn-prev').eq(0).on(ace.click_event,  function(){
+				$wizard.previous();
+			}).attr('disabled', 'disabled');
+			$wizard.$nextBtn = buttons.find('.btn-next').eq(0).on(ace.click_event,  function(){
+				$wizard.next();
+			}).removeAttr('disabled');
+			$wizard.nextText = $wizard.$nextBtn.text();
+			
+			var step = options && ((options.selectedItem && options.selectedItem.step) || options.step);
+			if(step) {
+				$wizard.currentStep = step;
+				$wizard.setState();
+			}
+		});
+
+		return this;
+	}
+
+})(window.jQuery);

+ 322 - 0
src/main/webapp/static/ace/assets/js/ace/elements.wysiwyg.js

@@ -0,0 +1,322 @@
+/**
+ <b>Wysiwyg</b>. A wrapper for Bootstrap wyswiwyg plugin.
+ It's just a wrapper so you still need to include Bootstrap wysiwyg script first.
+*/
+(function($ , undefined) {
+	$.fn.ace_wysiwyg = function($options , undefined) {
+		var options = $.extend( {
+			speech_button:true,
+			wysiwyg:{}
+        }, $options);
+
+		var color_values = [
+			'#ac725e','#d06b64','#f83a22','#fa573c','#ff7537','#ffad46',
+			'#42d692','#16a765','#7bd148','#b3dc6c','#fbe983','#fad165',
+			'#92e1c0','#9fe1e7','#9fc6e7','#4986e7','#9a9cff','#b99aff',
+			'#c2c2c2','#cabdbf','#cca6ac','#f691b2','#cd74e6','#a47ae2',
+			'#444444'
+		]
+
+		var button_defaults =
+		{
+			'font' : {
+				values:['Arial', 'Courier', 'Comic Sans MS', 'Helvetica', 'Open Sans', 'Tahoma', 'Verdana'],
+				icon:'fa fa-font',
+				title:'Font'
+			},
+			'fontSize' : {
+				values:{5:'Huge', 3:'Normal', 1:'Small'},
+				icon:'fa fa-text-height',
+				title:'Font Size'
+			},
+			'bold' : {
+				icon : 'fa fa-bold',
+				title : 'Bold (Ctrl/Cmd+B)'
+			},
+			'italic' : {
+				icon : 'fa fa-italic',
+				title : 'Italic (Ctrl/Cmd+I)'
+			},
+			'strikethrough' : {
+				icon : 'fa fa-strikethrough',
+				title : 'Strikethrough'
+			},
+			'underline' : {
+				icon : 'fa fa-underline',
+				title : 'Underline'
+			},
+			'insertunorderedlist' : {
+				icon : 'fa fa-list-ul',
+				title : 'Bullet list'
+			},
+			'insertorderedlist' : {
+				icon : 'fa fa-list-ol',
+				title : 'Number list'
+			},
+			'outdent' : {
+				icon : 'fa fa-outdent',
+				title : 'Reduce indent (Shift+Tab)'
+			},
+			'indent' : {
+				icon : 'fa fa-indent',
+				title : 'Indent (Tab)'
+			},
+			'justifyleft' : {
+				icon : 'fa fa-align-left',
+				title : 'Align Left (Ctrl/Cmd+L)'
+			},
+			'justifycenter' : {
+				icon : 'fa fa-align-center',
+				title : 'Center (Ctrl/Cmd+E)'
+			},
+			'justifyright' : {
+				icon : 'fa fa-align-right',
+				title : 'Align Right (Ctrl/Cmd+R)'
+			},
+			'justifyfull' : {
+				icon : 'fa fa-align-justify',
+				title : 'Justify (Ctrl/Cmd+J)'
+			},
+			'createLink' : {
+				icon : 'fa fa-link',
+				title : 'Hyperlink',
+				button_text : 'Add',
+				placeholder : 'URL',
+				button_class : 'btn-primary'
+			},
+			'unlink' : {
+				icon : 'fa fa-chain-broken',
+				title : 'Remove Hyperlink'
+			},
+			'insertImage' : {
+				icon : 'fa fa-picture-o',
+				title : 'Insert picture',
+				button_text : '<i class="'+ ace.vars['icon'] + 'fa fa-file"></i> Choose Image &hellip;',
+				placeholder : 'Image URL',
+				button_insert : 'Insert',
+				button_class : 'btn-success',
+				button_insert_class : 'btn-primary',
+				choose_file: true //show the choose file button?
+			},
+			'foreColor' : {
+				values : color_values,
+				title : 'Change Color'
+			},
+			'backColor' : {
+				values : color_values,
+				title : 'Change Background Color'
+			},
+			'undo' : {
+				icon : 'fa fa-undo',
+				title : 'Undo (Ctrl/Cmd+Z)'
+			},
+			'redo' : {
+				icon : 'fa fa-repeat',
+				title : 'Redo (Ctrl/Cmd+Y)'
+			},
+			'viewSource' : {
+				icon : 'fa fa-code',
+				title : 'View Source'
+			}
+		}
+		
+		var toolbar_buttons =
+		options.toolbar ||
+		[
+			'font',
+			null,
+			'fontSize',
+			null,
+			'bold',
+			'italic',
+			'strikethrough',
+			'underline',
+			null,
+			'insertunorderedlist',
+			'insertorderedlist',
+			'outdent',
+			'indent',
+			null,
+			'justifyleft',
+			'justifycenter',
+			'justifyright',
+			'justifyfull',
+			null,
+			'createLink',
+			'unlink',
+			null,
+			'insertImage',
+			null,
+			'foreColor',
+			null,
+			'undo',
+			'redo',
+			null,
+			'viewSource'
+		]
+
+
+		this.each(function() {
+			var toolbar = ' <div class="wysiwyg-toolbar btn-toolbar center"> <div class="btn-group"> ';
+
+			for(var tb in toolbar_buttons) if(toolbar_buttons.hasOwnProperty(tb)) {
+				var button = toolbar_buttons[tb];
+				if(button === null){
+					toolbar += ' </div> <div class="btn-group"> ';
+					continue;
+				}
+				
+				if(typeof button == "string" && button in button_defaults) {
+					button = button_defaults[button];
+					button.name = toolbar_buttons[tb];
+				} else if(typeof button == "object" && button.name in button_defaults) {
+					button = $.extend(button_defaults[button.name] , button);
+				}
+				else continue;
+				
+				var className = "className" in button ? button.className : 'btn-default';
+				switch(button.name) {
+					case 'font':
+						toolbar += ' <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i><i class="' + ace.vars['icon'] + 'fa fa-angle-down icon-on-right"></i></a> ';
+						toolbar += ' <ul class="dropdown-menu dropdown-light dropdown-caret">';
+						for(var font in button.values)
+							if(button.values.hasOwnProperty(font))
+								toolbar += ' <li><a data-edit="fontName ' + button.values[font] +'" style="font-family:\''+ button.values[font]  +'\'">'+button.values[font]  + '</a></li> '
+						toolbar += ' </ul>';
+					break;
+
+					case 'fontSize':
+						toolbar += ' <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i>&nbsp;<i class="'+ ace.vars['icon'] + 'fa fa-angle-down icon-on-right"></i></a> ';
+						toolbar += ' <ul class="dropdown-menu dropdown-light dropdown-caret"> ';
+						for(var size in button.values)
+							if(button.values.hasOwnProperty(size))
+								toolbar += ' <li><a data-edit="fontSize '+size+'"><font size="'+size+'">'+ button.values[size] +'</font></a></li> '
+						toolbar += ' </ul> ';
+					break;
+
+					case 'createLink':
+						toolbar += ' <div class="btn-group"> <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
+						toolbar += ' <div class="dropdown-menu dropdown-caret dropdown-menu-right">\
+							 <div class="input-group">\
+								<input class="form-control" placeholder="'+button.placeholder+'" type="text" data-edit="'+button.name+'" />\
+								<span class="input-group-btn">\
+									<button class="btn btn-sm '+button.button_class+'" type="button">'+button.button_text+'</button>\
+								</span>\
+							 </div>\
+						</div> </div>';
+					break;
+
+					case 'insertImage':
+						toolbar += ' <div class="btn-group"> <a class="btn btn-sm '+className+' dropdown-toggle" data-toggle="dropdown" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
+						toolbar += ' <div class="dropdown-menu dropdown-caret dropdown-menu-right">\
+							 <div class="input-group">\
+								<input class="form-control" placeholder="'+button.placeholder+'" type="text" data-edit="'+button.name+'" />\
+								<span class="input-group-btn">\
+									<button class="btn btn-sm '+button.button_insert_class+'" type="button">'+button.button_insert+'</button>\
+								</span>\
+							 </div>';
+							if( button.choose_file && 'FileReader' in window ) toolbar +=
+							 '<div class="space-2"></div>\
+							 <label class="center block no-margin-bottom">\
+								<button class="btn btn-sm '+button.button_class+' wysiwyg-choose-file" type="button">'+button.button_text+'</button>\
+								<input type="file" data-edit="'+button.name+'" />\
+							  </label>'
+						toolbar += ' </div> </div>';
+					break;
+
+					case 'foreColor':
+					case 'backColor':
+						toolbar += ' <select class="hide wysiwyg_colorpicker" title="'+button.title+'"> ';
+						$.each(button.values, function (_, color) {
+                            toolbar += ' <option value="' + color + '">' + color + '</option> ';
+                        });
+						toolbar += ' </select> ';
+						toolbar += ' <input style="display:none;" disabled class="hide" type="text" data-edit="'+button.name+'" /> ';
+					break;
+
+					case 'viewSource':
+						toolbar += ' <a class="btn btn-sm '+className+'" data-view="source" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
+					break;
+					default:
+						toolbar += ' <a class="btn btn-sm '+className+'" data-edit="'+button.name+'" title="'+button.title+'"><i class="'+ ace.vars['icon'] + button.icon+'"></i></a> ';
+					break;
+				}
+			}
+			toolbar += ' </div> ';
+			////////////
+			var speech_input;
+			if (options.speech_button && 'onwebkitspeechchange' in (speech_input = document.createElement('input'))) {
+				toolbar += ' <input class="wysiwyg-speech-input" type="text" data-edit="inserttext" x-webkit-speech />';
+			}
+			speech_input = null;
+			////////////
+			toolbar += ' </div> ';
+
+
+			//if we have a function to decide where to put the toolbar, then call that
+			if(options.toolbar_place) toolbar = options.toolbar_place.call(this, toolbar);
+			//otherwise put it just before our DIV
+			else toolbar = $(this).before(toolbar).prev();
+
+			toolbar.find('a[title]').tooltip({animation:false, container:'body'});
+			toolbar.find('.dropdown-menu input[type=text]').on('click', function() {return false})
+		    .on('change', function() {$(this).closest('.dropdown-menu').siblings('.dropdown-toggle').dropdown('toggle')})
+			.on('keydown', function (e) {
+				if(e.which == 27) {
+					this.value = '';
+					$(this).change();
+				}
+				else if(e.which == 13) {
+					e.preventDefault();
+					e.stopPropagation();
+					$(this).change();
+				}
+			});
+			
+			toolbar.find('input[type=file]').prev().on(ace.click_event, function (e) { 
+				$(this).next().click();
+			});
+			toolbar.find('.wysiwyg_colorpicker').each(function() {
+				$(this).ace_colorpicker({pull_right:true}).change(function(){
+					$(this).nextAll('input').eq(0).val(this.value).change();
+				}).next().find('.btn-colorpicker').tooltip({title: this.title, animation:false, container:'body'})
+			});
+			
+			
+			var self = $(this);
+			//view source
+			var view_source = false;
+			toolbar.find('a[data-view=source]').on('click', function(e){
+				e.preventDefault();
+				
+				if(!view_source) {
+					$('<textarea />')
+					.css({'width':self.outerWidth(), 'height':self.outerHeight()})
+					.val(self.html())
+					.insertAfter(self)
+					self.hide();
+					
+					$(this).addClass('active');
+				}
+				else {
+					var textarea = self.next();
+					self.html(textarea.val()).show();
+					textarea.remove();
+					
+					$(this).removeClass('active');
+				}
+				
+				view_source = !view_source;
+			});
+
+
+			var $options = $.extend({}, { activeToolbarClass: 'active' , toolbarSelector : toolbar }, options.wysiwyg || {})
+			$(this).wysiwyg( $options );
+		});
+
+		return this;
+	}
+
+
+})(window.jQuery);
+

+ 1 - 0
src/main/webapp/static/ace/assets/js/ace/readme

@@ -0,0 +1 @@
+To build a custom JS file please open path/to/ace/build/js.html in your browser.

+ 31 - 0
src/main/webapp/static/ace/assets/js/ace/scripts.json

@@ -0,0 +1,31 @@
+{
+ "elements.scroller.js" : true,
+ "elements.colorpicker.js" : true,
+ "elements.fileinput.js" : true,
+ "elements.typeahead.js" : true,
+ "elements.wysiwyg.js" : true,
+ "elements.spinner.js" : true,
+ "elements.treeview.js" : true,
+ "elements.wizard.js" : true,
+ "elements.aside.js" : true,
+
+ "ace.js" : true,
+ "ace.ajax-content.js" : true,
+ "ace.touch-drag.js" : true,
+ 
+ "ace.sidebar.js" : true,
+ "ace.sidebar-scroll-1.js" : true,
+ "ace.submenu-hover.js" : true,
+
+ "ace.widget-box.js" : true,
+ 
+ "ace.settings.js" : true,
+ "ace.settings-rtl.js" : true,
+ "ace.settings-skin.js" : true,
+
+ "ace.widget-on-reload.js" : true,
+ "ace.searchbox-autocomplete.js" : true,
+
+ "ace.auto-padding.js" : false,
+ "ace.auto-container.js" : false
+}

文件差異過大導致無法顯示
+ 998 - 0
src/main/webapp/static/ace/assets/js/additional-methods.js


+ 242 - 0
src/main/webapp/static/ace/assets/js/autosize.js

@@ -0,0 +1,242 @@
+/*!
+	Autosize 3.0.13
+	license: MIT
+	http://www.jacklmoore.com/autosize
+*/
+(function (global, factory) {
+	if (typeof define === 'function' && define.amd) {
+		define(['exports', 'module'], factory);
+	} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
+		factory(exports, module);
+	} else {
+		var mod = {
+			exports: {}
+		};
+		factory(mod.exports, mod);
+		global.autosize = mod.exports;
+	}
+})(this, function (exports, module) {
+	'use strict';
+
+	var set = typeof Set === 'function' ? new Set() : (function () {
+		var list = [];
+
+		return {
+			has: function has(key) {
+				return Boolean(list.indexOf(key) > -1);
+			},
+			add: function add(key) {
+				list.push(key);
+			},
+			'delete': function _delete(key) {
+				list.splice(list.indexOf(key), 1);
+			} };
+	})();
+
+	function assign(ta) {
+		var _ref = arguments[1] === undefined ? {} : arguments[1];
+
+		var _ref$setOverflowX = _ref.setOverflowX;
+		var setOverflowX = _ref$setOverflowX === undefined ? true : _ref$setOverflowX;
+		var _ref$setOverflowY = _ref.setOverflowY;
+		var setOverflowY = _ref$setOverflowY === undefined ? true : _ref$setOverflowY;
+
+		if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || set.has(ta)) return;
+
+		var heightOffset = null;
+		var overflowY = null;
+		var clientWidth = ta.clientWidth;
+
+		function init() {
+			var style = window.getComputedStyle(ta, null);
+
+			overflowY = style.overflowY;
+
+			if (style.resize === 'vertical') {
+				ta.style.resize = 'none';
+			} else if (style.resize === 'both') {
+				ta.style.resize = 'horizontal';
+			}
+
+			if (style.boxSizing === 'content-box') {
+				heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
+			} else {
+				heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
+			}
+			// Fix when a textarea is not on document body and heightOffset is Not a Number
+			if (isNaN(heightOffset)) {
+				heightOffset = 0;
+			}
+
+			update();
+		}
+
+		function changeOverflow(value) {
+			{
+				// Chrome/Safari-specific fix:
+				// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
+				// made available by removing the scrollbar. The following forces the necessary text reflow.
+				var width = ta.style.width;
+				ta.style.width = '0px';
+				// Force reflow:
+				/* jshint ignore:start */
+				ta.offsetWidth;
+				/* jshint ignore:end */
+				ta.style.width = width;
+			}
+
+			overflowY = value;
+
+			if (setOverflowY) {
+				ta.style.overflowY = value;
+			}
+
+			resize();
+		}
+
+		function resize() {
+			var htmlTop = window.pageYOffset;
+			var bodyTop = document.body.scrollTop;
+			var originalHeight = ta.style.height;
+
+			ta.style.height = 'auto';
+
+			var endHeight = ta.scrollHeight + heightOffset;
+
+			if (ta.scrollHeight === 0) {
+				// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
+				ta.style.height = originalHeight;
+				return;
+			}
+
+			ta.style.height = endHeight + 'px';
+
+			// used to check if an update is actually necessary on window.resize
+			clientWidth = ta.clientWidth;
+
+			// prevents scroll-position jumping
+			document.documentElement.scrollTop = htmlTop;
+			document.body.scrollTop = bodyTop;
+		}
+
+		function update() {
+			var startHeight = ta.style.height;
+
+			resize();
+
+			var style = window.getComputedStyle(ta, null);
+
+			if (style.height !== ta.style.height) {
+				if (overflowY !== 'visible') {
+					changeOverflow('visible');
+				}
+			} else {
+				if (overflowY !== 'hidden') {
+					changeOverflow('hidden');
+				}
+			}
+
+			if (startHeight !== ta.style.height) {
+				var evt = document.createEvent('Event');
+				evt.initEvent('autosize:resized', true, false);
+				ta.dispatchEvent(evt);
+			}
+		}
+
+		var pageResize = function pageResize() {
+			if (ta.clientWidth !== clientWidth) {
+				update();
+			}
+		};
+
+		var destroy = (function (style) {
+			window.removeEventListener('resize', pageResize);
+			ta.removeEventListener('input', update);
+			ta.removeEventListener('keyup', update);
+			ta.removeEventListener('autosize:destroy', destroy);
+			set['delete'](ta);
+
+			Object.keys(style).forEach(function (key) {
+				ta.style[key] = style[key];
+			});
+		}).bind(ta, {
+			height: ta.style.height,
+			resize: ta.style.resize,
+			overflowY: ta.style.overflowY,
+			overflowX: ta.style.overflowX,
+			wordWrap: ta.style.wordWrap });
+
+		ta.addEventListener('autosize:destroy', destroy);
+
+		// IE9 does not fire onpropertychange or oninput for deletions,
+		// so binding to onkeyup to catch most of those events.
+		// There is no way that I know of to detect something like 'cut' in IE9.
+		if ('onpropertychange' in ta && 'oninput' in ta) {
+			ta.addEventListener('keyup', update);
+		}
+
+		window.addEventListener('resize', pageResize);
+		ta.addEventListener('input', update);
+		ta.addEventListener('autosize:update', update);
+		set.add(ta);
+
+		if (setOverflowX) {
+			ta.style.overflowX = 'hidden';
+			ta.style.wordWrap = 'break-word';
+		}
+
+		init();
+	}
+
+	function destroy(ta) {
+		if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
+		var evt = document.createEvent('Event');
+		evt.initEvent('autosize:destroy', true, false);
+		ta.dispatchEvent(evt);
+	}
+
+	function update(ta) {
+		if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
+		var evt = document.createEvent('Event');
+		evt.initEvent('autosize:update', true, false);
+		ta.dispatchEvent(evt);
+	}
+
+	var autosize = null;
+
+	// Do nothing in Node.js environment and IE8 (or lower)
+	if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
+		autosize = function (el) {
+			return el;
+		};
+		autosize.destroy = function (el) {
+			return el;
+		};
+		autosize.update = function (el) {
+			return el;
+		};
+	} else {
+		autosize = function (el, options) {
+			if (el) {
+				Array.prototype.forEach.call(el.length ? el : [el], function (x) {
+					return assign(x, options);
+				});
+			}
+			return el;
+		};
+		autosize.destroy = function (el) {
+			if (el) {
+				Array.prototype.forEach.call(el.length ? el : [el], destroy);
+			}
+			return el;
+		};
+		autosize.update = function (el) {
+			if (el) {
+				Array.prototype.forEach.call(el.length ? el : [el], update);
+			}
+			return el;
+		};
+	}
+
+	module.exports = autosize;
+});

+ 985 - 0
src/main/webapp/static/ace/assets/js/bootbox.js

@@ -0,0 +1,985 @@
+/**
+ * bootbox.js [v4.4.0]
+ *
+ * http://bootboxjs.com/license.txt
+ */
+
+// @see https://github.com/makeusabrew/bootbox/issues/180
+// @see https://github.com/makeusabrew/bootbox/issues/186
+(function (root, factory) {
+
+  "use strict";
+  if (typeof define === "function" && define.amd) {
+    // AMD. Register as an anonymous module.
+    define(["jquery"], factory);
+  } else if (typeof exports === "object") {
+    // Node. Does not work with strict CommonJS, but
+    // only CommonJS-like environments that support module.exports,
+    // like Node.
+    module.exports = factory(require("jquery"));
+  } else {
+    // Browser globals (root is window)
+    root.bootbox = factory(root.jQuery);
+  }
+
+}(this, function init($, undefined) {
+
+  "use strict";
+
+  // the base DOM structure needed to create a modal
+  var templates = {
+    dialog:
+      "<div class='bootbox modal' tabindex='-1' role='dialog'>" +
+        "<div class='modal-dialog'>" +
+          "<div class='modal-content'>" +
+            "<div class='modal-body'><div class='bootbox-body'></div></div>" +
+          "</div>" +
+        "</div>" +
+      "</div>",
+    header:
+      "<div class='modal-header'>" +
+        "<h4 class='modal-title'></h4>" +
+      "</div>",
+    footer:
+      "<div class='modal-footer'></div>",
+    closeButton:
+      "<button type='button' class='bootbox-close-button close' data-dismiss='modal' aria-hidden='true'>&times;</button>",
+    form:
+      "<form class='bootbox-form'></form>",
+    inputs: {
+      text:
+        "<input class='bootbox-input bootbox-input-text form-control' autocomplete=off type=text />",
+      textarea:
+        "<textarea class='bootbox-input bootbox-input-textarea form-control'></textarea>",
+      email:
+        "<input class='bootbox-input bootbox-input-email form-control' autocomplete='off' type='email' />",
+      select:
+        "<select class='bootbox-input bootbox-input-select form-control'></select>",
+      checkbox:
+        "<div class='checkbox'><label><input class='bootbox-input bootbox-input-checkbox' type='checkbox' /></label></div>",
+      date:
+        "<input class='bootbox-input bootbox-input-date form-control' autocomplete=off type='date' />",
+      time:
+        "<input class='bootbox-input bootbox-input-time form-control' autocomplete=off type='time' />",
+      number:
+        "<input class='bootbox-input bootbox-input-number form-control' autocomplete=off type='number' />",
+      password:
+        "<input class='bootbox-input bootbox-input-password form-control' autocomplete='off' type='password' />"
+    }
+  };
+
+  var defaults = {
+    // default language
+    locale: "en",
+    // show backdrop or not. Default to static so user has to interact with dialog
+    backdrop: "static",
+    // animate the modal in/out
+    animate: true,
+    // additional class string applied to the top level dialog
+    className: null,
+    // whether or not to include a close button
+    closeButton: true,
+    // show the dialog immediately by default
+    show: true,
+    // dialog container
+    container: "body"
+  };
+
+  // our public object; augmented after our private API
+  var exports = {};
+
+  /**
+   * @private
+   */
+  function _t(key) {
+    var locale = locales[defaults.locale];
+    return locale ? locale[key] : locales.en[key];
+  }
+
+  function processCallback(e, dialog, callback) {
+    e.stopPropagation();
+    e.preventDefault();
+
+    // by default we assume a callback will get rid of the dialog,
+    // although it is given the opportunity to override this
+
+    // so, if the callback can be invoked and it *explicitly returns false*
+    // then we'll set a flag to keep the dialog active...
+    var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
+
+    // ... otherwise we'll bin it
+    if (!preserveDialog) {
+      dialog.modal("hide");
+    }
+  }
+
+  function getKeyLength(obj) {
+    // @TODO defer to Object.keys(x).length if available?
+    var k, t = 0;
+    for (k in obj) {
+      t ++;
+    }
+    return t;
+  }
+
+  function each(collection, iterator) {
+    var index = 0;
+    $.each(collection, function(key, value) {
+      iterator(key, value, index++);
+    });
+  }
+
+  function sanitize(options) {
+    var buttons;
+    var total;
+
+    if (typeof options !== "object") {
+      throw new Error("Please supply an object of options");
+    }
+
+    if (!options.message) {
+      throw new Error("Please specify a message");
+    }
+
+    // make sure any supplied options take precedence over defaults
+    options = $.extend({}, defaults, options);
+
+    if (!options.buttons) {
+      options.buttons = {};
+    }
+
+    buttons = options.buttons;
+
+    total = getKeyLength(buttons);
+
+    each(buttons, function(key, button, index) {
+
+      if ($.isFunction(button)) {
+        // short form, assume value is our callback. Since button
+        // isn't an object it isn't a reference either so re-assign it
+        button = buttons[key] = {
+          callback: button
+        };
+      }
+
+      // before any further checks make sure by now button is the correct type
+      if ($.type(button) !== "object") {
+        throw new Error("button with key " + key + " must be an object");
+      }
+
+      if (!button.label) {
+        // the lack of an explicit label means we'll assume the key is good enough
+        button.label = key;
+      }
+
+      if (!button.className) {
+        if (total <= 2 && index === total-1) {
+          // always add a primary to the main option in a two-button dialog
+          button.className = "btn-primary";
+        } else {
+          button.className = "btn-default";
+        }
+      }
+    });
+
+    return options;
+  }
+
+  /**
+   * map a flexible set of arguments into a single returned object
+   * if args.length is already one just return it, otherwise
+   * use the properties argument to map the unnamed args to
+   * object properties
+   * so in the latter case:
+   * mapArguments(["foo", $.noop], ["message", "callback"])
+   * -> { message: "foo", callback: $.noop }
+   */
+  function mapArguments(args, properties) {
+    var argn = args.length;
+    var options = {};
+
+    if (argn < 1 || argn > 2) {
+      throw new Error("Invalid argument length");
+    }
+
+    if (argn === 2 || typeof args[0] === "string") {
+      options[properties[0]] = args[0];
+      options[properties[1]] = args[1];
+    } else {
+      options = args[0];
+    }
+
+    return options;
+  }
+
+  /**
+   * merge a set of default dialog options with user supplied arguments
+   */
+  function mergeArguments(defaults, args, properties) {
+    return $.extend(
+      // deep merge
+      true,
+      // ensure the target is an empty, unreferenced object
+      {},
+      // the base options object for this type of dialog (often just buttons)
+      defaults,
+      // args could be an object or array; if it's an array properties will
+      // map it to a proper options object
+      mapArguments(
+        args,
+        properties
+      )
+    );
+  }
+
+  /**
+   * this entry-level method makes heavy use of composition to take a simple
+   * range of inputs and return valid options suitable for passing to bootbox.dialog
+   */
+  function mergeDialogOptions(className, labels, properties, args) {
+    //  build up a base set of dialog properties
+    var baseOptions = {
+      className: "bootbox-" + className,
+      buttons: createLabels.apply(null, labels)
+    };
+
+    // ensure the buttons properties generated, *after* merging
+    // with user args are still valid against the supplied labels
+    return validateButtons(
+      // merge the generated base properties with user supplied arguments
+      mergeArguments(
+        baseOptions,
+        args,
+        // if args.length > 1, properties specify how each arg maps to an object key
+        properties
+      ),
+      labels
+    );
+  }
+
+  /**
+   * from a given list of arguments return a suitable object of button labels
+   * all this does is normalise the given labels and translate them where possible
+   * e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" }
+   */
+  function createLabels() {
+    var buttons = {};
+
+    for (var i = 0, j = arguments.length; i < j; i++) {
+      var argument = arguments[i];
+      var key = argument.toLowerCase();
+      var value = argument.toUpperCase();
+
+      buttons[key] = {
+        label: _t(value)
+      };
+    }
+
+    return buttons;
+  }
+
+  function validateButtons(options, buttons) {
+    var allowedButtons = {};
+    each(buttons, function(key, value) {
+      allowedButtons[value] = true;
+    });
+
+    each(options.buttons, function(key) {
+      if (allowedButtons[key] === undefined) {
+        throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")");
+      }
+    });
+
+    return options;
+  }
+
+  exports.alert = function() {
+    var options;
+
+    options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments);
+
+    if (options.callback && !$.isFunction(options.callback)) {
+      throw new Error("alert requires callback property to be a function when provided");
+    }
+
+    /**
+     * overrides
+     */
+    options.buttons.ok.callback = options.onEscape = function() {
+      if ($.isFunction(options.callback)) {
+        return options.callback.call(this);
+      }
+      return true;
+    };
+
+    return exports.dialog(options);
+  };
+
+  exports.confirm = function() {
+    var options;
+
+    options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments);
+
+    /**
+     * overrides; undo anything the user tried to set they shouldn't have
+     */
+    options.buttons.cancel.callback = options.onEscape = function() {
+      return options.callback.call(this, false);
+    };
+
+    options.buttons.confirm.callback = function() {
+      return options.callback.call(this, true);
+    };
+
+    // confirm specific validation
+    if (!$.isFunction(options.callback)) {
+      throw new Error("confirm requires a callback");
+    }
+
+    return exports.dialog(options);
+  };
+
+  exports.prompt = function() {
+    var options;
+    var defaults;
+    var dialog;
+    var form;
+    var input;
+    var shouldShow;
+    var inputOptions;
+
+    // we have to create our form first otherwise
+    // its value is undefined when gearing up our options
+    // @TODO this could be solved by allowing message to
+    // be a function instead...
+    form = $(templates.form);
+
+    // prompt defaults are more complex than others in that
+    // users can override more defaults
+    // @TODO I don't like that prompt has to do a lot of heavy
+    // lifting which mergeDialogOptions can *almost* support already
+    // just because of 'value' and 'inputType' - can we refactor?
+    defaults = {
+      className: "bootbox-prompt",
+      buttons: createLabels("cancel", "confirm"),
+      value: "",
+      inputType: "text"
+    };
+
+    options = validateButtons(
+      mergeArguments(defaults, arguments, ["title", "callback"]),
+      ["cancel", "confirm"]
+    );
+
+    // capture the user's show value; we always set this to false before
+    // spawning the dialog to give us a chance to attach some handlers to
+    // it, but we need to make sure we respect a preference not to show it
+    shouldShow = (options.show === undefined) ? true : options.show;
+
+    /**
+     * overrides; undo anything the user tried to set they shouldn't have
+     */
+    options.message = form;
+
+    options.buttons.cancel.callback = options.onEscape = function() {
+      return options.callback.call(this, null);
+    };
+
+    options.buttons.confirm.callback = function() {
+      var value;
+
+      switch (options.inputType) {
+        case "text":
+        case "textarea":
+        case "email":
+        case "select":
+        case "date":
+        case "time":
+        case "number":
+        case "password":
+          value = input.val();
+          break;
+
+        case "checkbox":
+          var checkedItems = input.find("input:checked");
+
+          // we assume that checkboxes are always multiple,
+          // hence we default to an empty array
+          value = [];
+
+          each(checkedItems, function(_, item) {
+            value.push($(item).val());
+          });
+          break;
+      }
+
+      return options.callback.call(this, value);
+    };
+
+    options.show = false;
+
+    // prompt specific validation
+    if (!options.title) {
+      throw new Error("prompt requires a title");
+    }
+
+    if (!$.isFunction(options.callback)) {
+      throw new Error("prompt requires a callback");
+    }
+
+    if (!templates.inputs[options.inputType]) {
+      throw new Error("invalid prompt type");
+    }
+
+    // create the input based on the supplied type
+    input = $(templates.inputs[options.inputType]);
+
+    switch (options.inputType) {
+      case "text":
+      case "textarea":
+      case "email":
+      case "date":
+      case "time":
+      case "number":
+      case "password":
+        input.val(options.value);
+        break;
+
+      case "select":
+        var groups = {};
+        inputOptions = options.inputOptions || [];
+
+        if (!$.isArray(inputOptions)) {
+          throw new Error("Please pass an array of input options");
+        }
+
+        if (!inputOptions.length) {
+          throw new Error("prompt with select requires options");
+        }
+
+        each(inputOptions, function(_, option) {
+
+          // assume the element to attach to is the input...
+          var elem = input;
+
+          if (option.value === undefined || option.text === undefined) {
+            throw new Error("given options in wrong format");
+          }
+
+          // ... but override that element if this option sits in a group
+
+          if (option.group) {
+            // initialise group if necessary
+            if (!groups[option.group]) {
+              groups[option.group] = $("<optgroup/>").attr("label", option.group);
+            }
+
+            elem = groups[option.group];
+          }
+
+          elem.append("<option value='" + option.value + "'>" + option.text + "</option>");
+        });
+
+        each(groups, function(_, group) {
+          input.append(group);
+        });
+
+        // safe to set a select's value as per a normal input
+        input.val(options.value);
+        break;
+
+      case "checkbox":
+        var values   = $.isArray(options.value) ? options.value : [options.value];
+        inputOptions = options.inputOptions || [];
+
+        if (!inputOptions.length) {
+          throw new Error("prompt with checkbox requires options");
+        }
+
+        if (!inputOptions[0].value || !inputOptions[0].text) {
+          throw new Error("given options in wrong format");
+        }
+
+        // checkboxes have to nest within a containing element, so
+        // they break the rules a bit and we end up re-assigning
+        // our 'input' element to this container instead
+        input = $("<div/>");
+
+        each(inputOptions, function(_, option) {
+          var checkbox = $(templates.inputs[options.inputType]);
+
+          checkbox.find("input").attr("value", option.value);
+          checkbox.find("label").append(option.text);
+
+          // we've ensured values is an array so we can always iterate over it
+          each(values, function(_, value) {
+            if (value === option.value) {
+              checkbox.find("input").prop("checked", true);
+            }
+          });
+
+          input.append(checkbox);
+        });
+        break;
+    }
+
+    // @TODO provide an attributes option instead
+    // and simply map that as keys: vals
+    if (options.placeholder) {
+      input.attr("placeholder", options.placeholder);
+    }
+
+    if (options.pattern) {
+      input.attr("pattern", options.pattern);
+    }
+
+    if (options.maxlength) {
+      input.attr("maxlength", options.maxlength);
+    }
+
+    // now place it in our form
+    form.append(input);
+
+    form.on("submit", function(e) {
+      e.preventDefault();
+      // Fix for SammyJS (or similar JS routing library) hijacking the form post.
+      e.stopPropagation();
+      // @TODO can we actually click *the* button object instead?
+      // e.g. buttons.confirm.click() or similar
+      dialog.find(".btn-primary").click();
+    });
+
+    dialog = exports.dialog(options);
+
+    // clear the existing handler focusing the submit button...
+    dialog.off("shown.bs.modal");
+
+    // ...and replace it with one focusing our input, if possible
+    dialog.on("shown.bs.modal", function() {
+      // need the closure here since input isn't
+      // an object otherwise
+      input.focus();
+    });
+
+    if (shouldShow === true) {
+      dialog.modal("show");
+    }
+
+    return dialog;
+  };
+
+  exports.dialog = function(options) {
+    options = sanitize(options);
+
+    var dialog = $(templates.dialog);
+    var innerDialog = dialog.find(".modal-dialog");
+    var body = dialog.find(".modal-body");
+    var buttons = options.buttons;
+    var buttonStr = "";
+    var callbacks = {
+      onEscape: options.onEscape
+    };
+
+    if ($.fn.modal === undefined) {
+      throw new Error(
+        "$.fn.modal is not defined; please double check you have included " +
+        "the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " +
+        "for more details."
+      );
+    }
+
+    each(buttons, function(key, button) {
+
+      // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
+      // can we just build up button elements instead? slower but neater. Then button
+      // can just become a template too
+      buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
+      callbacks[key] = button.callback;
+    });
+
+    body.find(".bootbox-body").html(options.message);
+
+    if (options.animate === true) {
+      dialog.addClass("fade");
+    }
+
+    if (options.className) {
+      dialog.addClass(options.className);
+    }
+
+    if (options.size === "large") {
+      innerDialog.addClass("modal-lg");
+    } else if (options.size === "small") {
+      innerDialog.addClass("modal-sm");
+    }
+
+    if (options.title) {
+      body.before(templates.header);
+    }
+
+    if (options.closeButton) {
+      var closeButton = $(templates.closeButton);
+
+      if (options.title) {
+        dialog.find(".modal-header").prepend(closeButton);
+      } else {
+        closeButton.css("margin-top", "-10px").prependTo(body);
+      }
+    }
+
+    if (options.title) {
+      dialog.find(".modal-title").html(options.title);
+    }
+
+    if (buttonStr.length) {
+      body.after(templates.footer);
+      dialog.find(".modal-footer").html(buttonStr);
+    }
+
+
+    /**
+     * Bootstrap event listeners; used handle extra
+     * setup & teardown required after the underlying
+     * modal has performed certain actions
+     */
+
+    dialog.on("hidden.bs.modal", function(e) {
+      // ensure we don't accidentally intercept hidden events triggered
+      // by children of the current dialog. We shouldn't anymore now BS
+      // namespaces its events; but still worth doing
+      if (e.target === this) {
+        dialog.remove();
+      }
+    });
+
+    /*
+    dialog.on("show.bs.modal", function() {
+      // sadly this doesn't work; show is called *just* before
+      // the backdrop is added so we'd need a setTimeout hack or
+      // otherwise... leaving in as would be nice
+      if (options.backdrop) {
+        dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
+      }
+    });
+    */
+
+    dialog.on("shown.bs.modal", function() {
+      dialog.find(".btn-primary:first").focus();
+    });
+
+    /**
+     * Bootbox event listeners; experimental and may not last
+     * just an attempt to decouple some behaviours from their
+     * respective triggers
+     */
+
+    if (options.backdrop !== "static") {
+      // A boolean true/false according to the Bootstrap docs
+      // should show a dialog the user can dismiss by clicking on
+      // the background.
+      // We always only ever pass static/false to the actual
+      // $.modal function because with `true` we can't trap
+      // this event (the .modal-backdrop swallows it)
+      // However, we still want to sort of respect true
+      // and invoke the escape mechanism instead
+      dialog.on("click.dismiss.bs.modal", function(e) {
+        // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
+        // moved *inside* the outer dialog rather than *alongside* it
+        if (dialog.children(".modal-backdrop").length) {
+          e.currentTarget = dialog.children(".modal-backdrop").get(0);
+        }
+
+        if (e.target !== e.currentTarget) {
+          return;
+        }
+
+        dialog.trigger("escape.close.bb");
+      });
+    }
+
+    dialog.on("escape.close.bb", function(e) {
+      if (callbacks.onEscape) {
+        processCallback(e, dialog, callbacks.onEscape);
+      }
+    });
+
+    /**
+     * Standard jQuery event listeners; used to handle user
+     * interaction with our dialog
+     */
+
+    dialog.on("click", ".modal-footer button", function(e) {
+      var callbackKey = $(this).data("bb-handler");
+
+      processCallback(e, dialog, callbacks[callbackKey]);
+    });
+
+    dialog.on("click", ".bootbox-close-button", function(e) {
+      // onEscape might be falsy but that's fine; the fact is
+      // if the user has managed to click the close button we
+      // have to close the dialog, callback or not
+      processCallback(e, dialog, callbacks.onEscape);
+    });
+
+    dialog.on("keyup", function(e) {
+      if (e.which === 27) {
+        dialog.trigger("escape.close.bb");
+      }
+    });
+
+    // the remainder of this method simply deals with adding our
+    // dialogent to the DOM, augmenting it with Bootstrap's modal
+    // functionality and then giving the resulting object back
+    // to our caller
+
+    $(options.container).append(dialog);
+
+    dialog.modal({
+      backdrop: options.backdrop ? "static": false,
+      keyboard: false,
+      show: false
+    });
+
+    if (options.show) {
+      dialog.modal("show");
+    }
+
+    // @TODO should we return the raw element here or should
+    // we wrap it in an object on which we can expose some neater
+    // methods, e.g. var d = bootbox.alert(); d.hide(); instead
+    // of d.modal("hide");
+
+   /*
+    function BBDialog(elem) {
+      this.elem = elem;
+    }
+
+    BBDialog.prototype = {
+      hide: function() {
+        return this.elem.modal("hide");
+      },
+      show: function() {
+        return this.elem.modal("show");
+      }
+    };
+    */
+
+    return dialog;
+
+  };
+
+  exports.setDefaults = function() {
+    var values = {};
+
+    if (arguments.length === 2) {
+      // allow passing of single key/value...
+      values[arguments[0]] = arguments[1];
+    } else {
+      // ... and as an object too
+      values = arguments[0];
+    }
+
+    $.extend(defaults, values);
+  };
+
+  exports.hideAll = function() {
+    $(".bootbox").modal("hide");
+
+    return exports;
+  };
+
+
+  /**
+   * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
+   * unlikely to be required. If this gets too large it can be split out into separate JS files.
+   */
+  var locales = {
+    bg_BG : {
+      OK      : "Ок",
+      CANCEL  : "Отказ",
+      CONFIRM : "Потвърждавам"
+    },
+    br : {
+      OK      : "OK",
+      CANCEL  : "Cancelar",
+      CONFIRM : "Sim"
+    },
+    cs : {
+      OK      : "OK",
+      CANCEL  : "Zrušit",
+      CONFIRM : "Potvrdit"
+    },
+    da : {
+      OK      : "OK",
+      CANCEL  : "Annuller",
+      CONFIRM : "Accepter"
+    },
+    de : {
+      OK      : "OK",
+      CANCEL  : "Abbrechen",
+      CONFIRM : "Akzeptieren"
+    },
+    el : {
+      OK      : "Εντάξει",
+      CANCEL  : "Ακύρωση",
+      CONFIRM : "Επιβεβαίωση"
+    },
+    en : {
+      OK      : "OK",
+      CANCEL  : "Cancel",
+      CONFIRM : "OK"
+    },
+    es : {
+      OK      : "OK",
+      CANCEL  : "Cancelar",
+      CONFIRM : "Aceptar"
+    },
+    et : {
+      OK      : "OK",
+      CANCEL  : "Katkesta",
+      CONFIRM : "OK"
+    },
+    fa : {
+      OK      : "قبول",
+      CANCEL  : "لغو",
+      CONFIRM : "تایید"
+    },
+    fi : {
+      OK      : "OK",
+      CANCEL  : "Peruuta",
+      CONFIRM : "OK"
+    },
+    fr : {
+      OK      : "OK",
+      CANCEL  : "Annuler",
+      CONFIRM : "D'accord"
+    },
+    he : {
+      OK      : "אישור",
+      CANCEL  : "ביטול",
+      CONFIRM : "אישור"
+    },
+    hu : {
+      OK      : "OK",
+      CANCEL  : "Mégsem",
+      CONFIRM : "Megerősít"
+    },
+    hr : {
+      OK      : "OK",
+      CANCEL  : "Odustani",
+      CONFIRM : "Potvrdi"
+    },
+    id : {
+      OK      : "OK",
+      CANCEL  : "Batal",
+      CONFIRM : "OK"
+    },
+    it : {
+      OK      : "OK",
+      CANCEL  : "Annulla",
+      CONFIRM : "Conferma"
+    },
+    ja : {
+      OK      : "OK",
+      CANCEL  : "キャンセル",
+      CONFIRM : "確認"
+    },
+    lt : {
+      OK      : "Gerai",
+      CANCEL  : "Atšaukti",
+      CONFIRM : "Patvirtinti"
+    },
+    lv : {
+      OK      : "Labi",
+      CANCEL  : "Atcelt",
+      CONFIRM : "Apstiprināt"
+    },
+    nl : {
+      OK      : "OK",
+      CANCEL  : "Annuleren",
+      CONFIRM : "Accepteren"
+    },
+    no : {
+      OK      : "OK",
+      CANCEL  : "Avbryt",
+      CONFIRM : "OK"
+    },
+    pl : {
+      OK      : "OK",
+      CANCEL  : "Anuluj",
+      CONFIRM : "Potwierdź"
+    },
+    pt : {
+      OK      : "OK",
+      CANCEL  : "Cancelar",
+      CONFIRM : "Confirmar"
+    },
+    ru : {
+      OK      : "OK",
+      CANCEL  : "Отмена",
+      CONFIRM : "Применить"
+    },
+    sq : {
+      OK : "OK",
+      CANCEL : "Anulo",
+      CONFIRM : "Prano"
+    },
+    sv : {
+      OK      : "OK",
+      CANCEL  : "Avbryt",
+      CONFIRM : "OK"
+    },
+    th : {
+      OK      : "ตกลง",
+      CANCEL  : "ยกเลิก",
+      CONFIRM : "ยืนยัน"
+    },
+    tr : {
+      OK      : "Tamam",
+      CANCEL  : "İptal",
+      CONFIRM : "Onayla"
+    },
+    zh_CN : {
+      OK      : "OK",
+      CANCEL  : "取消",
+      CONFIRM : "确认"
+    },
+    zh_TW : {
+      OK      : "OK",
+      CANCEL  : "取消",
+      CONFIRM : "確認"
+    }
+  };
+
+  exports.addLocale = function(name, values) {
+    $.each(["OK", "CANCEL", "CONFIRM"], function(_, v) {
+      if (!values[v]) {
+        throw new Error("Please supply a translation for '" + v + "'");
+      }
+    });
+
+    locales[name] = {
+      OK: values.OK,
+      CANCEL: values.CANCEL,
+      CONFIRM: values.CONFIRM
+    };
+
+    return exports;
+  };
+
+  exports.removeLocale = function(name) {
+    delete locales[name];
+
+    return exports;
+  };
+
+  exports.setLocale = function(name) {
+    return exports.setDefaults("locale", name);
+  };
+
+  exports.init = function(_$) {
+    return init(_$ || $);
+  };
+
+  return exports;
+}));

+ 540 - 0
src/main/webapp/static/ace/assets/js/bootstrap-colorpicker.js

@@ -0,0 +1,540 @@
+/* =========================================================
+ * bootstrap-colorpicker.js 
+ * http://www.eyecon.ro/bootstrap-colorpicker
+ * =========================================================
+ * Copyright 2012 Stefan Petre
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+ 
+!function( $ ) {
+	
+	// Color object
+	
+	var Color = function(val) {
+		this.value = {
+			h: 1,
+			s: 1,
+			b: 1,
+			a: 1
+		};
+		this.setColor(val);
+	};
+	
+	Color.prototype = {
+		constructor: Color,
+		
+		//parse a string to HSB
+		setColor: function(val){
+			val = val.toLowerCase();
+			var that = this;
+			$.each( CPGlobal.stringParsers, function( i, parser ) {
+				var match = parser.re.exec( val ),
+					values = match && parser.parse( match ),
+					space = parser.space||'rgba';
+				if ( values ) {
+					if (space === 'hsla') {
+						that.value = CPGlobal.RGBtoHSB.apply(null, CPGlobal.HSLtoRGB.apply(null, values));
+					} else {
+						that.value = CPGlobal.RGBtoHSB.apply(null, values);
+					}
+					return false;
+				}
+			});
+		},
+		
+		setHue: function(h) {
+			this.value.h = 1- h;
+		},
+		
+		setSaturation: function(s) {
+			this.value.s = s;
+		},
+		
+		setLightness: function(b) {
+			this.value.b = 1- b;
+		},
+		
+		setAlpha: function(a) {
+			this.value.a = parseInt((1 - a)*100, 10)/100;
+		},
+		
+		// HSBtoRGB from RaphaelJS
+		// https://github.com/DmitryBaranovskiy/raphael/
+		toRGB: function(h, s, b, a) {
+			if (!h) {
+				h = this.value.h;
+				s = this.value.s;
+				b = this.value.b;
+			}
+			h *= 360;
+			var R, G, B, X, C;
+			h = (h % 360) / 60;
+			C = b * s;
+			X = C * (1 - Math.abs(h % 2 - 1));
+			R = G = B = b - C;
+
+			h = ~~h;
+			R += [C, X, 0, 0, X, C][h];
+			G += [X, C, C, X, 0, 0][h];
+			B += [0, 0, X, C, C, X][h];
+			return {
+				r: Math.round(R*255),
+				g: Math.round(G*255),
+				b: Math.round(B*255),
+				a: a||this.value.a
+			};
+		},
+		
+		toHex: function(h, s, b, a){
+			var rgb = this.toRGB(h, s, b, a);
+			return '#'+((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1);
+		},
+		
+		toHSL: function(h, s, b, a){
+			if (!h) {
+				h = this.value.h;
+				s = this.value.s;
+				b = this.value.b;
+			}
+			var H = h,
+				L = (2 - s) * b,
+				S = s * b;
+			if (L > 0 && L <= 1) {
+				S /= L;
+			} else {
+				S /= 2 - L;
+			}
+			L /= 2;
+			if (S > 1) {
+				S = 1;
+			}
+			return {
+				h: H,
+				s: S,
+				l: L,
+				a: a||this.value.a
+			};
+		}
+	};
+	
+	// Picker object
+	
+	var Colorpicker = function(element, options){
+		this.element = $(element);
+		var format = options.format||this.element.data('color-format')||'hex';
+		this.format = CPGlobal.translateFormats[format];
+		this.isInput = this.element.is('input');
+		this.component = this.element.is('.color') ? this.element.find('.add-on') : false;
+		
+		this.picker = $(CPGlobal.template)
+							.appendTo('body')
+							.on('mousedown', $.proxy(this.mousedown, this));
+		
+		if (this.isInput) {
+			this.element.on({
+				'focus': $.proxy(this.show, this),
+				'keyup': $.proxy(this.update, this)
+			});
+		} else if (this.component){
+			this.component.on({
+				'click': $.proxy(this.show, this)
+			});
+		} else {
+			this.element.on({
+				'click': $.proxy(this.show, this)
+			});
+		}
+		if (format === 'rgba' || format === 'hsla') {
+			this.picker.addClass('alpha');
+			this.alpha = this.picker.find('.colorpicker-alpha')[0].style;
+		}
+		
+		if (this.component){
+			this.picker.find('.colorpicker-color').hide();
+			this.preview = this.element.find('i')[0].style;
+		} else {
+			this.preview = this.picker.find('div:last')[0].style;
+		}
+		
+		this.base = this.picker.find('div:first')[0].style;
+		this.update();
+	};
+	
+	Colorpicker.prototype = {
+		constructor: Colorpicker,
+		
+		show: function(e) {
+			this.picker.show();
+			this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
+			this.place();
+			$(window).on('resize', $.proxy(this.place, this));
+			if (!this.isInput) {
+				if (e) {
+					e.stopPropagation();
+					e.preventDefault();
+				}
+			}
+			$(document).on({
+				'mousedown': $.proxy(this.hide, this)
+			});
+			this.element.trigger({
+				type: 'show',
+				color: this.color
+			});
+		},
+		
+		update: function(){
+			this.color = new Color(this.isInput ? this.element.prop('value') : this.element.data('color'));
+			this.picker.find('i')
+				.eq(0).css({left: this.color.value.s*100, top: 100 - this.color.value.b*100}).end()
+				.eq(1).css('top', 100 * (1 - this.color.value.h)).end()
+				.eq(2).css('top', 100 * (1 - this.color.value.a));
+			this.previewColor();
+		},
+		
+		setValue: function(newColor) {
+			this.color = new Color(newColor);
+			this.picker.find('i')
+				.eq(0).css({left: this.color.value.s*100, top: 100 - this.color.value.b*100}).end()
+				.eq(1).css('top', 100 * (1 - this.color.value.h)).end()
+				.eq(2).css('top', 100 * (1 - this.color.value.a));
+			this.previewColor();
+			this.element.trigger({
+				type: 'changeColor',
+				color: this.color
+			});
+		},
+		
+		hide: function(){
+			this.picker.hide();
+			$(window).off('resize', this.place);
+			if (!this.isInput) {
+				$(document).off({
+					'mousedown': this.hide
+				});
+				if (this.component){
+					this.element.find('input').prop('value', this.format.call(this));
+				}
+				this.element.data('color', this.format.call(this));
+			} else {
+				this.element.prop('value', this.format.call(this));
+			}
+			this.element.trigger({
+				type: 'hide',
+				color: this.color
+			});
+		},
+		
+		place: function(){
+			var offset = this.component ? this.component.offset() : this.element.offset();
+			this.picker.css({
+				top: offset.top + this.height,
+				left: offset.left
+			});
+		},
+		
+		//preview color change
+		previewColor: function(){
+			try {
+				this.preview.backgroundColor = this.format.call(this);
+			} catch(e) {
+				this.preview.backgroundColor = this.color.toHex();
+			}
+			//set the color for brightness/saturation slider
+			this.base.backgroundColor = this.color.toHex(this.color.value.h, 1, 1, 1);
+			//set te color for alpha slider
+			if (this.alpha) {
+				this.alpha.backgroundColor = this.color.toHex();
+			}
+		},
+		
+		pointer: null,
+		
+		slider: null,
+		
+		mousedown: function(e){
+			e.stopPropagation();
+			e.preventDefault();
+			
+			var target = $(e.target);
+			
+			//detect the slider and set the limits and callbacks
+			var zone = target.closest('div');
+			if (!zone.is('.colorpicker')) {
+				if (zone.is('.colorpicker-saturation')) {
+					this.slider = $.extend({}, CPGlobal.sliders.saturation);
+				} 
+				else if (zone.is('.colorpicker-hue')) {
+					this.slider = $.extend({}, CPGlobal.sliders.hue);
+				}
+				else if (zone.is('.colorpicker-alpha')) {
+					this.slider = $.extend({}, CPGlobal.sliders.alpha);
+				} else {
+					return false;
+				}
+				var offset = zone.offset();
+				//reference to knob's style
+				this.slider.knob = zone.find('i')[0].style;
+				this.slider.left = e.pageX - offset.left;
+				this.slider.top = e.pageY - offset.top;
+				this.pointer = {
+					left: e.pageX,
+					top: e.pageY
+				};
+				//trigger mousemove to move the knob to the current position
+				$(document).on({
+					mousemove: $.proxy(this.mousemove, this),
+					mouseup: $.proxy(this.mouseup, this)
+				}).trigger('mousemove');
+			}
+			return false;
+		},
+		
+		mousemove: function(e){
+			e.stopPropagation();
+			e.preventDefault();
+			var left = Math.max(
+				0,
+				Math.min(
+					this.slider.maxLeft,
+					this.slider.left + ((e.pageX||this.pointer.left) - this.pointer.left)
+				)
+			);
+			var top = Math.max(
+				0,
+				Math.min(
+					this.slider.maxTop,
+					this.slider.top + ((e.pageY||this.pointer.top) - this.pointer.top)
+				)
+			);
+			this.slider.knob.left = left + 'px';
+			this.slider.knob.top = top + 'px';
+			if (this.slider.callLeft) {
+				this.color[this.slider.callLeft].call(this.color, left/100);
+			}
+			if (this.slider.callTop) {
+				this.color[this.slider.callTop].call(this.color, top/100);
+			}
+			this.previewColor();
+			this.element.trigger({
+				type: 'changeColor',
+				color: this.color
+			});
+			return false;
+		},
+		
+		mouseup: function(e){
+			e.stopPropagation();
+			e.preventDefault();
+			$(document).off({
+				mousemove: this.mousemove,
+				mouseup: this.mouseup
+			});
+			return false;
+		}
+	}
+
+	$.fn.colorpicker = function ( option, val ) {
+		return this.each(function () {
+			var $this = $(this),
+				data = $this.data('colorpicker'),
+				options = typeof option === 'object' && option;
+			if (!data) {
+				$this.data('colorpicker', (data = new Colorpicker(this, $.extend({}, $.fn.colorpicker.defaults,options))));
+			}
+			if (typeof option === 'string') data[option](val);
+		});
+	};
+
+	$.fn.colorpicker.defaults = {
+	};
+	
+	$.fn.colorpicker.Constructor = Colorpicker;
+	
+	var CPGlobal = {
+	
+		// translate a format from Color object to a string
+		translateFormats: {
+			'rgb': function(){
+				var rgb = this.color.toRGB();
+				return 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')';
+			},
+			
+			'rgba': function(){
+				var rgb = this.color.toRGB();
+				return 'rgba('+rgb.r+','+rgb.g+','+rgb.b+','+rgb.a+')';
+			},
+			
+			'hsl': function(){
+				var hsl = this.color.toHSL();
+				return 'hsl('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%)';
+			},
+			
+			'hsla': function(){
+				var hsl = this.color.toHSL();
+				return 'hsla('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%,'+hsl.a+')';
+			},
+			
+			'hex': function(){
+				return  this.color.toHex();
+			}
+		},
+		
+		sliders: {
+			saturation: {
+				maxLeft: 100,
+				maxTop: 100,
+				callLeft: 'setSaturation',
+				callTop: 'setLightness'
+			},
+			
+			hue: {
+				maxLeft: 0,
+				maxTop: 100,
+				callLeft: false,
+				callTop: 'setHue'
+			},
+			
+			alpha: {
+				maxLeft: 0,
+				maxTop: 100,
+				callLeft: false,
+				callTop: 'setAlpha'
+			}
+		},
+		
+		// HSBtoRGB from RaphaelJS
+		// https://github.com/DmitryBaranovskiy/raphael/
+		RGBtoHSB: function (r, g, b, a){
+			r /= 255;
+			g /= 255;
+			b /= 255;
+
+			var H, S, V, C;
+			V = Math.max(r, g, b);
+			C = V - Math.min(r, g, b);
+			H = (C === 0 ? null :
+				V == r ? (g - b) / C :
+				V == g ? (b - r) / C + 2 :
+					(r - g) / C + 4
+				);
+			H = ((H + 360) % 6) * 60 / 360;
+			S = C === 0 ? 0 : C / V;
+			return {h: H||1, s: S, b: V, a: a||1};
+		},
+		
+		HueToRGB: function (p, q, h) {
+			if (h < 0)
+				h += 1;
+			else if (h > 1)
+				h -= 1;
+
+			if ((h * 6) < 1)
+				return p + (q - p) * h * 6;
+			else if ((h * 2) < 1)
+				return q;
+			else if ((h * 3) < 2)
+				return p + (q - p) * ((2 / 3) - h) * 6;
+			else
+				return p;
+		},
+	
+		HSLtoRGB: function (h, s, l, a)
+		{
+			if (s < 0) {
+				s = 0;
+			}
+			var q;
+			if (l <= 0.5) {
+				q = l * (1 + s);
+			} else {
+				q = l + s - (l * s);
+			}
+			
+			var p = 2 * l - q;
+
+			var tr = h + (1 / 3);
+			var tg = h;
+			var tb = h - (1 / 3);
+
+			var r = Math.round(CPGlobal.HueToRGB(p, q, tr) * 255);
+			var g = Math.round(CPGlobal.HueToRGB(p, q, tg) * 255);
+			var b = Math.round(CPGlobal.HueToRGB(p, q, tb) * 255);
+			return [r, g, b, a||1];
+		},
+		
+		// a set of RE's that can match strings and generate color tuples.
+		// from John Resig color plugin
+		// https://github.com/jquery/jquery-color/
+		stringParsers: [
+			{
+				re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
+				parse: function( execResult ) {
+					return [
+						execResult[ 1 ],
+						execResult[ 2 ],
+						execResult[ 3 ],
+						execResult[ 4 ]
+					];
+				}
+			}, {
+				re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
+				parse: function( execResult ) {
+					return [
+						2.55 * execResult[1],
+						2.55 * execResult[2],
+						2.55 * execResult[3],
+						execResult[ 4 ]
+					];
+				}
+			}, {
+				re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
+				parse: function( execResult ) {
+					return [
+						parseInt( execResult[ 1 ], 16 ),
+						parseInt( execResult[ 2 ], 16 ),
+						parseInt( execResult[ 3 ], 16 )
+					];
+				}
+			}, {
+				re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
+				parse: function( execResult ) {
+					return [
+						parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
+						parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
+						parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
+					];
+				}
+			}, {
+				re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
+				space: 'hsla',
+				parse: function( execResult ) {
+					return [
+						execResult[1]/360,
+						execResult[2] / 100,
+						execResult[3] / 100,
+						execResult[4]
+					];
+				}
+			}
+		],
+		template: '<div class="colorpicker dropdown-menu">'+
+							'<div class="colorpicker-saturation"><i><b></b></i></div>'+
+							'<div class="colorpicker-hue"><i></i></div>'+
+							'<div class="colorpicker-alpha"><i></i></div>'+
+							'<div class="colorpicker-color"><div /></div>'+
+						'</div>'
+	};
+
+}( window.jQuery )

文件差異過大導致無法顯示
+ 1417 - 0
src/main/webapp/static/ace/assets/js/bootstrap-multiselect.js


+ 206 - 0
src/main/webapp/static/ace/assets/js/bootstrap-tag.js

@@ -0,0 +1,206 @@
+/* ==========================================================
+ * bootstrap-tag.js v2.2.5
+ * https://github.com/fdeschenes/bootstrap-tag
+ * ==========================================================
+ * Copyright 2012 Francois Deschenes.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+!function ( $ ) {
+
+  'use strict' // jshint ;_;
+
+  var Tag = function ( element, options ) {
+    this.element = $(element)
+    this.options = $.extend(true, {}, $.fn.tag.defaults, options)
+    this.values = $.grep($.map(this.element.val().split(','), $.trim), function ( value ) { return value.length > 0 })
+    this.show()
+  }
+
+  Tag.prototype = {
+    constructor: Tag
+
+  , show: function () {
+      var that = this
+
+      that.element.parent().prepend(that.element.detach().hide())
+      that.element
+        .wrap($('<div class="tags">'))
+        .parent()
+        .on('click', function () {
+          that.input.focus()
+        })
+
+      if (that.values.length) {
+        $.each(that.values, function () {
+          that.createBadge(this)
+        })
+      }
+
+      that.input = $('<input type="text">')
+        .attr('placeholder', that.options.placeholder)
+        .insertAfter(that.element)
+        .on('focus', function () {
+          that.element.parent().addClass('tags-hover')
+        })
+        .on('blur', function () {
+          if (!that.skip) {
+            that.process()
+            that.element.parent().removeClass('tags-hover')
+            that.element.siblings('.tag').removeClass('tag-important')
+          }
+          that.skip = false
+        })
+        .on('keydown', function ( event ) {
+          if ( event.keyCode == 188 || event.keyCode == 13 || event.keyCode == 9 ) {
+            if ( $.trim($(this).val()) && ( !that.element.siblings('.typeahead').length || that.element.siblings('.typeahead').is(':hidden') ) ) {
+              if ( event.keyCode != 9 ) event.preventDefault()
+              that.process()
+            } else if ( event.keyCode == 188 ) {
+              if ( !that.element.siblings('.typeahead').length || that.element.siblings('.typeahead').is(':hidden') ) {
+                event.preventDefault()
+              } else {
+                that.input.data('typeahead').select()
+                event.stopPropagation()
+                event.preventDefault()
+              }
+            }
+          } else if ( !$.trim($(this).val()) && event.keyCode == 8 ) {
+            var count = that.element.siblings('.tag').length
+            if (count) {
+              var tag = that.element.siblings('.tag:eq(' + (count - 1) + ')')
+              if (tag.hasClass('tag-important')) that.remove(count - 1)
+              else tag.addClass('tag-important')
+            }
+          } else {
+            that.element.siblings('.tag').removeClass('tag-important')
+          }
+        })
+        .bs_typeahead({
+          source: that.options.source
+        , matcher: function ( value ) {
+            return ~value.toLowerCase().indexOf(this.query.toLowerCase()) && (that.inValues(value) == -1 || that.options.allowDuplicates)
+          }
+        , updater: $.proxy(that.add, that)
+        })
+
+      $(that.input.data('bs_typeahead').$menu).on('mousedown', function() {
+        that.skip = true
+      })
+
+      this.element.trigger('shown')
+    }
+  , inValues: function ( value ) {
+      if (this.options.caseInsensitive) {
+        var index = -1
+        $.each(this.values, function (indexInArray, valueOfElement) {
+          if ( valueOfElement.toLowerCase() == value.toLowerCase() ) {
+            index = indexInArray
+            return false
+          }
+        })
+        return index
+      } else {
+        return $.inArray(value, this.values)
+      }
+    }
+  , createBadge: function ( value ) {
+    var that = this
+
+      $('<span/>', {
+        'class' : "tag"
+      })
+      .text(value)
+      .append($('<button type="button" class="close">&times;</button>')
+        .on('click', function () {
+          that.remove(that.element.siblings('.tag').index($(this).closest('.tag')))
+        })
+      )
+      .insertBefore(that.element)
+  }
+  , add: function ( value ) {
+      var that = this
+
+      if ( !that.options.allowDuplicates ) {
+        var index = that.inValues(value)
+        if ( index != -1 ) {
+          var badge = that.element.siblings('.tag:eq(' + index + ')')
+          badge.addClass('tag-warning')
+          setTimeout(function () {
+            $(badge).removeClass('tag-warning')
+          }, 500)
+          return
+        }
+      }
+
+      this.values.push(value)
+      this.createBadge(value)
+
+      this.element.val(this.values.join(', '))
+      this.element.trigger('added', [value])
+    }
+  , remove: function ( index ) {
+      if ( index >= 0 ) {
+        var value = this.values.splice(index, 1)
+        this.element.siblings('.tag:eq(' + index + ')').remove()
+        this.element.val(this.values.join(', '))
+
+        this.element.trigger('removed', [value])
+      }
+    }
+  , process: function () {
+      var values = $.grep($.map(this.input.val().split(','), $.trim), function ( value ) { return value.length > 0 }),
+          that = this
+      $.each(values, function() {
+        that.add(this)
+      })
+      this.input.val('')
+    }
+  , skip: false
+  }
+
+  var old = $.fn.tag
+
+  $.fn.tag = function ( option ) {
+    return this.each(function () {
+      var that = $(this)
+        , data = that.data('tag')
+        , options = typeof option == 'object' && option
+      if (!data) that.data('tag', (data = new Tag(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tag.defaults = {
+    allowDuplicates: false
+  , caseInsensitive: true
+  , placeholder: ''
+  , source: []
+  }
+
+  $.fn.tag.Constructor = Tag
+
+  $.fn.tag.noConflict = function () {
+    $.fn.tag = old
+    return this
+  }
+
+  $(window).on('load', function () {
+    $('[data-provide="tag"]').each(function () {
+      var that = $(this)
+      if (that.data('tag')) return
+      that.tag(that.data())
+    })
+  })
+}(window.jQuery);

+ 217 - 0
src/main/webapp/static/ace/assets/js/bootstrap-wysiwyg.js

@@ -0,0 +1,217 @@
+/* http://github.com/mindmup/bootstrap-wysiwyg */
+/*global jQuery, $, FileReader*/
+/*jslint browser:true*/
+(function ($) {
+	'use strict';
+	var readFileIntoDataUrl = function (fileInfo) {
+		var loader = $.Deferred(),
+			fReader = new FileReader();
+		fReader.onload = function (e) {
+			loader.resolve(e.target.result);
+		};
+		fReader.onerror = loader.reject;
+		fReader.onprogress = loader.notify;
+		fReader.readAsDataURL(fileInfo);
+		return loader.promise();
+	};
+	$.fn.cleanHtml = function () {
+		var html = $(this).html();
+		return html && html.replace(/(<br>|\s|<div><br><\/div>|&nbsp;)*$/, '');
+	};
+	$.fn.wysiwyg = function (userOptions) {
+		var editor = this,
+			selectedRange,
+			options,
+			toolbarBtnSelector,
+			updateToolbar = function () {
+				if (options.activeToolbarClass) {
+					$(options.toolbarSelector).find(toolbarBtnSelector).each(function () {
+						try {
+							var command = $(this).data(options.commandRole);
+							if (document.queryCommandState(command)) {
+								$(this).addClass(options.activeToolbarClass);
+							} else {
+								$(this).removeClass(options.activeToolbarClass);
+							}
+						} catch(e){}
+					});
+				}
+			},
+			execCommand = function (commandWithArgs, valueArg) {
+				var commandArr = commandWithArgs.split(' '),
+					command = commandArr.shift(),
+					args = commandArr.join(' ') + (valueArg || '');
+				document.execCommand(command, 0, args);
+				updateToolbar();
+			},
+			bindHotkeys = function (hotKeys) {
+				$.each(hotKeys, function (hotkey, command) {
+					editor.keydown(hotkey, function (e) {
+						if (editor.attr('contenteditable') && editor.is(':visible')) {
+							e.preventDefault();
+							e.stopPropagation();
+							execCommand(command);
+						}
+					}).keyup(hotkey, function (e) {
+						if (editor.attr('contenteditable') && editor.is(':visible')) {
+							e.preventDefault();
+							e.stopPropagation();
+						}
+					});
+				});
+			},
+			getCurrentRange = function () {
+				try {
+					var sel = window.getSelection();
+					if (sel.getRangeAt && sel.rangeCount) {
+						return sel.getRangeAt(0);
+					}
+				} catch(e){}
+			},
+			saveSelection = function () {
+				selectedRange = getCurrentRange();
+			},
+			restoreSelection = function () {
+				try {
+					var selection = window.getSelection();
+					if (selectedRange) {
+						try {
+							selection.removeAllRanges();
+						} catch (ex) {
+							document.body.createTextRange().select();
+							document.selection.empty();
+						}
+
+						selection.addRange(selectedRange);
+					}
+				} catch(e){}
+			},
+			insertFiles = function (files) {
+				editor.focus();
+				$.each(files, function (idx, fileInfo) {
+					if (/^image\//.test(fileInfo.type)) {
+						$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
+							execCommand('insertimage', dataUrl);
+						}).fail(function (e) {
+							options.fileUploadError("file-reader", e);
+						});
+					} else {
+						options.fileUploadError("unsupported-file-type", fileInfo.type);
+					}
+				});
+			},
+			markSelection = function (input, color) {
+				restoreSelection();
+				if (document.queryCommandSupported('hiliteColor')) {
+					document.execCommand('hiliteColor', 0, color || 'transparent');
+				}
+				saveSelection();
+				input.data(options.selectionMarker, color);
+			},
+			bindToolbar = function (toolbar, options) {
+				toolbar.find(toolbarBtnSelector).click(function () {
+					restoreSelection();
+					editor.focus();
+					execCommand($(this).data(options.commandRole));
+					saveSelection();
+				});
+				toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
+
+				
+				//ACE
+				//ie 9-11
+				var is_ie = !!window.navigator.msPointerEnabled || (!!document.all && !!document.addEventListener);
+
+				toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
+					var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
+					this.value = '';
+					restoreSelection();
+					if (newValue) {
+						editor.focus();
+						execCommand($(this).data(options.commandRole), newValue);
+					}
+					saveSelection();
+				}).on('focus', function () {
+					//if IE return, not needed
+					if(is_ie) return;//ACE
+				
+					var input = $(this);
+					if (!input.data(options.selectionMarker)) {
+						markSelection(input, options.selectionColor);
+						input.focus();
+					}
+				}).on('blur', function () {
+					//if IE return, not needed
+					if(is_ie) return;//ACE
+					
+					var input = $(this);
+					if (input.data(options.selectionMarker)) {
+						markSelection(input, false);
+					}
+				});
+				toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
+					restoreSelection();
+					if (this.type === 'file' && this.files && this.files.length > 0) {
+						insertFiles(this.files);
+					}
+					saveSelection();
+					this.value = '';
+				});
+			},
+			initFileDrops = function () {
+				editor.on('dragenter dragover', false)
+					.on('drop', function (e) {
+						var dataTransfer = e.originalEvent.dataTransfer;
+						e.stopPropagation();
+						e.preventDefault();
+						if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
+							insertFiles(dataTransfer.files);
+						}
+					});
+			};
+		options = $.extend({}, $.fn.wysiwyg.defaults, userOptions);
+		toolbarBtnSelector = 'a[data-' + options.commandRole + '],button[data-' + options.commandRole + '],input[type=button][data-' + options.commandRole + ']';
+		bindHotkeys(options.hotKeys);
+		if (options.dragAndDropImages) {
+			initFileDrops();
+		}
+		bindToolbar($(options.toolbarSelector), options);
+		editor.attr('contenteditable', true)
+			.on('mouseup keyup mouseout', function () {
+				saveSelection();
+				updateToolbar();
+			});
+		$(window).bind('touchend', function (e) {
+			var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
+				currentRange = getCurrentRange(),
+				clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
+			if (!clear || isInside) {
+				saveSelection();
+				updateToolbar();
+			}
+		});
+		return this;
+	};
+	$.fn.wysiwyg.defaults = {
+		hotKeys: {
+			'ctrl+b meta+b': 'bold',
+			'ctrl+i meta+i': 'italic',
+			'ctrl+u meta+u': 'underline',
+			'ctrl+z meta+z': 'undo',
+			'ctrl+y meta+y meta+shift+z': 'redo',
+			'ctrl+l meta+l': 'justifyleft',
+			'ctrl+r meta+r': 'justifyright',
+			'ctrl+e meta+e': 'justifycenter',
+			'ctrl+j meta+j': 'justifyfull',
+			'shift+tab': 'outdent',
+			'tab': 'indent'
+		},
+		toolbarSelector: '[data-role=editor-toolbar]',
+		commandRole: 'edit',
+		activeToolbarClass: 'btn-info',
+		selectionMarker: 'edit-focus-marker',
+		selectionColor: 'darkgrey',
+		dragAndDropImages: true,
+		fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); }
+	};
+}(window.jQuery));

文件差異過大導致無法顯示
+ 2363 - 0
src/main/webapp/static/ace/assets/js/bootstrap.js


文件差異過大導致無法顯示
+ 1257 - 0
src/main/webapp/static/ace/assets/js/chosen.jquery.js


+ 149 - 0
src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.colVis.js

@@ -0,0 +1,149 @@
+/*!
+ * Column visibility buttons for Buttons and DataTables.
+ * 2015 SpryMedia Ltd - datatables.net/license
+ */
+
+(function($, DataTable) {
+"use strict";
+
+
+$.extend( DataTable.ext.buttons, {
+	// A collection of column visibility buttons
+	colvis: function ( dt, conf ) {
+		return {
+			extend: 'collection',
+			text: function ( dt ) {
+				return dt.i18n( 'buttons.colvis', 'Column visibility' );
+			},
+			className: 'buttons-colvis',
+			buttons: [ {
+				extend: 'columnsToggle',
+				columns: conf.columns
+			} ]
+		};
+	},
+
+	// Selected columns with individual buttons - toggle column visibility
+	columnsToggle: function ( dt, conf ) {
+		var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) {
+			return {
+				extend: 'columnToggle',
+				columns: idx
+			};
+		} ).toArray();
+
+		return columns;
+	},
+
+	// Single button to toggle column visibility
+	columnToggle: function ( dt, conf ) {
+		return {
+			extend: 'columnVisibility',
+			columns: conf.columns
+		};
+	},
+
+	// Selected columns with individual buttons - set column visibility
+	columnsVisibility: function ( dt, conf ) {
+		var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) {
+			return {
+				extend: 'columnVisibility',
+				columns: idx,
+				visibility: conf.visibility
+			};
+		} ).toArray();
+
+		return columns;
+	},
+
+	// Single button to set column visibility
+	columnVisibility: {
+		columns: null, // column selector
+		text: function ( dt, button, conf ) {
+			return conf._columnText( dt, conf.columns );
+		},
+		className: 'buttons-columnVisibility',
+		action: function ( e, dt, button, conf ) {
+			var col = dt.column( conf.columns );
+
+			col.visible( conf.visibility !== undefined ?
+				conf.visibility :
+				! col.visible()
+			);
+		},
+		init: function ( dt, button, conf ) {
+			var that = this;
+			var col = dt.column( conf.columns );
+
+			dt
+				.on( 'column-visibility.dt'+conf.namespace, function (e, settings, column, state) {
+					if ( column === conf.columns ) {
+						that.active( state );
+					}
+				} )
+				.on( 'column-reorder.dt'+conf.namespace, function (e, settings, details) {
+					var col = dt.column( conf.columns );
+
+					button.text( conf._columnText( dt, conf.columns ) );
+					that.active( col.visible() );
+				} );
+
+			this.active( col.visible() );
+		},
+		destroy: function ( dt, button, conf ) {
+			dt
+				.off( 'column-visibility.dt'+conf.namespace )
+				.off( 'column-reorder.dt'+conf.namespace );
+		},
+
+		_columnText: function ( dt, col ) {
+			// Use DataTables' internal data structure until this is presented
+			// is a public API. The other option is to use
+			// `$( column(col).node() ).text()` but the node might not have been
+			// populated when Buttons is constructed.
+			var idx = dt.column( col ).index();
+			return dt.settings()[0].aoColumns[ idx ].sTitle
+				.replace(/\n/g," ")        // remove new lines
+				.replace( /<.*?>/g, "" )   // strip HTML
+				.replace(/^\s+|\s+$/g,""); // trim
+		}
+	},
+
+
+	colvisRestore: {
+		className: 'buttons-colvisRestore',
+
+		text: function ( dt ) {
+			return dt.i18n( 'buttons.colvisRestore', 'Restore visibility' );
+		},
+
+		init: function ( dt, button, conf ) {
+			conf._visOriginal = dt.columns().indexes().map( function ( idx ) {
+				return dt.column( idx ).visible();
+			} ).toArray();
+		},
+
+		action: function ( e, dt, button, conf ) {
+			dt.columns().every( function ( i ) {
+				this.visible( conf._visOriginal[ i ] );
+			} );
+		}
+	},
+
+
+	colvisGroup: {
+		className: 'buttons-colvisGroup',
+
+		action: function ( e, dt, button, conf ) {
+			dt.columns( conf.show ).visible( true );
+			dt.columns( conf.hide ).visible( false );
+		},
+
+		show: [],
+
+		hide: []
+	}
+} );
+
+
+})(jQuery, jQuery.fn.dataTable);

文件差異過大導致無法顯示
+ 783 - 0
src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.flash.js


+ 751 - 0
src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.html5.js

@@ -0,0 +1,751 @@
+/*!
+ * HTML5 export buttons for Buttons and DataTables.
+ * 2015 SpryMedia Ltd - datatables.net/license
+ *
+ * FileSaver.js (2015-05-07.2) - MIT license
+ * Copyright © 2015 Eli Grey - http://eligrey.com
+ */
+
+(function($, DataTable) {
+"use strict";
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * FileSaver.js dependency
+ */
+
+/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
+
+var _saveAs = (function(view) {
+	// IE <10 is explicitly unsupported
+	if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
+		return;
+	}
+	var
+		  doc = view.document
+		  // only get URL when necessary in case Blob.js hasn't overridden it yet
+		, get_URL = function() {
+			return view.URL || view.webkitURL || view;
+		}
+		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+		, can_use_save_link = "download" in save_link
+		, click = function(node) {
+			var event = doc.createEvent("MouseEvents");
+			event.initMouseEvent(
+				"click", true, false, view, 0, 0, 0, 0, 0
+				, false, false, false, false, 0, null
+			);
+			node.dispatchEvent(event);
+		}
+		, webkit_req_fs = view.webkitRequestFileSystem
+		, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
+		, throw_outside = function(ex) {
+			(view.setImmediate || view.setTimeout)(function() {
+				throw ex;
+			}, 0);
+		}
+		, force_saveable_type = "application/octet-stream"
+		, fs_min_size = 0
+		// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
+		// https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
+		// for the reasoning behind the timeout and revocation flow
+		, arbitrary_revoke_timeout = 500 // in ms
+		, revoke = function(file) {
+			var revoker = function() {
+				if (typeof file === "string") { // file is an object URL
+					get_URL().revokeObjectURL(file);
+				} else { // file is a File
+					file.remove();
+				}
+			};
+			if (view.chrome) {
+				revoker();
+			} else {
+				setTimeout(revoker, arbitrary_revoke_timeout);
+			}
+		}
+		, dispatch = function(filesaver, event_types, event) {
+			event_types = [].concat(event_types);
+			var i = event_types.length;
+			while (i--) {
+				var listener = filesaver["on" + event_types[i]];
+				if (typeof listener === "function") {
+					try {
+						listener.call(filesaver, event || filesaver);
+					} catch (ex) {
+						throw_outside(ex);
+					}
+				}
+			}
+		}
+		, auto_bom = function(blob) {
+			// prepend BOM for UTF-8 XML and text/* types (including HTML)
+			if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
+				return new Blob(["\ufeff", blob], {type: blob.type});
+			}
+			return blob;
+		}
+		, FileSaver = function(blob, name) {
+			blob = auto_bom(blob);
+			// First try a.download, then web filesystem, then object URLs
+			var
+				  filesaver = this
+				, type = blob.type
+				, blob_changed = false
+				, object_url
+				, target_view
+				, dispatch_all = function() {
+					dispatch(filesaver, "writestart progress write writeend".split(" "));
+				}
+				// on any filesys errors revert to saving with object URLs
+				, fs_error = function() {
+					// don't create more object URLs than needed
+					if (blob_changed || !object_url) {
+						object_url = get_URL().createObjectURL(blob);
+					}
+					if (target_view) {
+						target_view.location.href = object_url;
+					} else {
+						var new_tab = view.open(object_url, "_blank");
+						if (new_tab === undefined && typeof safari !== "undefined") {
+							//Apple do not allow window.open, see http://bit.ly/1kZffRI
+							view.location.href = object_url;
+						}
+					}
+					filesaver.readyState = filesaver.DONE;
+					dispatch_all();
+					revoke(object_url);
+				}
+				, abortable = function(func) {
+					return function() {
+						if (filesaver.readyState !== filesaver.DONE) {
+							return func.apply(this, arguments);
+						}
+					};
+				}
+				, create_if_not_found = {create: true, exclusive: false}
+				, slice
+			;
+			filesaver.readyState = filesaver.INIT;
+			if (!name) {
+				name = "download";
+			}
+			if (can_use_save_link) {
+				object_url = get_URL().createObjectURL(blob);
+				save_link.href = object_url;
+				save_link.download = name;
+				click(save_link);
+				filesaver.readyState = filesaver.DONE;
+				dispatch_all();
+				revoke(object_url);
+				return;
+			}
+			// Object and web filesystem URLs have a problem saving in Google Chrome when
+			// viewed in a tab, so I force save with application/octet-stream
+			// http://code.google.com/p/chromium/issues/detail?id=91158
+			// Update: Google errantly closed 91158, I submitted it again:
+			// https://code.google.com/p/chromium/issues/detail?id=389642
+			if (view.chrome && type && type !== force_saveable_type) {
+				slice = blob.slice || blob.webkitSlice;
+				blob = slice.call(blob, 0, blob.size, force_saveable_type);
+				blob_changed = true;
+			}
+			// Since I can't be sure that the guessed media type will trigger a download
+			// in WebKit, I append .download to the filename.
+			// https://bugs.webkit.org/show_bug.cgi?id=65440
+			if (webkit_req_fs && name !== "download") {
+				name += ".download";
+			}
+			if (type === force_saveable_type || webkit_req_fs) {
+				target_view = view;
+			}
+			if (!req_fs) {
+				fs_error();
+				return;
+			}
+			fs_min_size += blob.size;
+			req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
+				fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
+					var save = function() {
+						dir.getFile(name, create_if_not_found, abortable(function(file) {
+							file.createWriter(abortable(function(writer) {
+								writer.onwriteend = function(event) {
+									target_view.location.href = file.toURL();
+									filesaver.readyState = filesaver.DONE;
+									dispatch(filesaver, "writeend", event);
+									revoke(file);
+								};
+								writer.onerror = function() {
+									var error = writer.error;
+									if (error.code !== error.ABORT_ERR) {
+										fs_error();
+									}
+								};
+								"writestart progress write abort".split(" ").forEach(function(event) {
+									writer["on" + event] = filesaver["on" + event];
+								});
+								writer.write(blob);
+								filesaver.abort = function() {
+									writer.abort();
+									filesaver.readyState = filesaver.DONE;
+								};
+								filesaver.readyState = filesaver.WRITING;
+							}), fs_error);
+						}), fs_error);
+					};
+					dir.getFile(name, {create: false}, abortable(function(file) {
+						// delete file if it already exists
+						file.remove();
+						save();
+					}), abortable(function(ex) {
+						if (ex.code === ex.NOT_FOUND_ERR) {
+							save();
+						} else {
+							fs_error();
+						}
+					}));
+				}), fs_error);
+			}), fs_error);
+		}
+		, FS_proto = FileSaver.prototype
+		, saveAs = function(blob, name) {
+			return new FileSaver(blob, name);
+		}
+	;
+	// IE 10+ (native saveAs)
+	if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
+		return function(blob, name) {
+			return navigator.msSaveOrOpenBlob(auto_bom(blob), name);
+		};
+	}
+
+	FS_proto.abort = function() {
+		var filesaver = this;
+		filesaver.readyState = filesaver.DONE;
+		dispatch(filesaver, "abort");
+	};
+	FS_proto.readyState = FS_proto.INIT = 0;
+	FS_proto.WRITING = 1;
+	FS_proto.DONE = 2;
+
+	FS_proto.error =
+	FS_proto.onwritestart =
+	FS_proto.onprogress =
+	FS_proto.onwrite =
+	FS_proto.onabort =
+	FS_proto.onerror =
+	FS_proto.onwriteend =
+		null;
+
+	return saveAs;
+}(window));
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Local (private) functions
+ */
+
+/**
+ * Get the title / file name for an exported file.
+ *
+ * @param {object}  config       Button configuration
+ * @param {boolean} incExtension Include the file name extension
+ */
+var _filename = function ( config, incExtension )
+{
+	// Backwards compatibility
+	var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined ?
+		config.title :
+		config.filename;
+
+	if ( filename.indexOf( '*' ) !== -1 ) {
+		filename = filename.replace( '*', $('title').text() );
+	}
+
+	// Strip characters which the OS will object to
+	filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");
+
+	return incExtension === undefined || incExtension === true ?
+		filename+config.extension :
+		filename;
+};
+
+/**
+ * Get the newline character(s)
+ *
+ * @param {object}  config Button configuration
+ * @return {string}        Newline character
+ */
+var _newLine = function ( config )
+{
+	return config.newline ?
+		config.newline :
+		navigator.userAgent.match(/Windows/) ?
+			'\r\n' :
+			'\n';
+};
+
+/**
+ * Combine the data from the `buttons.exportData` method into a string that
+ * will be used in the export file.
+ *
+ * @param  {DataTable.Api} dt     DataTables API instance
+ * @param  {object}        config Button configuration
+ * @return {object}               The data to export
+ */
+var _exportData = function ( dt, config )
+{
+	var newLine = _newLine( config );
+	var data = dt.buttons.exportData( config.exportOptions );
+	var boundary = config.fieldBoundary;
+	var separator = config.fieldSeparator;
+	var reBoundary = new RegExp( boundary, 'g' );
+	var escapeChar = config.escapeChar !== undefined ?
+		config.escapeChar :
+		'\\';
+	var join = function ( a ) {
+		var s = '';
+
+		// If there is a field boundary, then we might need to escape it in
+		// the source data
+		for ( var i=0, ien=a.length ; i<ien ; i++ ) {
+			if ( i > 0 ) {
+				s += separator;
+			}
+
+			s += boundary ?
+				boundary + ('' + a[i]).replace( reBoundary, escapeChar+boundary ) + boundary :
+				a[i];
+		}
+
+		return s;
+	};
+
+	var header = config.header ? join( data.header )+newLine : '';
+	var footer = config.footer ? newLine+join( data.footer ) : '';
+	var body = [];
+
+	for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
+		body.push( join( data.body[i] ) );
+	}
+
+	return {
+		str: header + body.join( newLine ) + footer,
+		rows: body.length
+	};
+};
+
+/**
+ * Safari's data: support for creating and downloading files is really poor, so
+ * various options need to be disabled in it. See
+ * https://bugs.webkit.org/show_bug.cgi?id=102914
+ * 
+ * @return {Boolean} `true` if Safari
+ */
+var _isSafari = function ()
+{
+	return navigator.userAgent.indexOf('Safari') !== -1 &&
+		navigator.userAgent.indexOf('Chrome') === -1 &&
+		navigator.userAgent.indexOf('Opera') === -1;
+};
+
+
+// Excel - Pre-defined strings to build a minimal XLSX file
+var excelStrings = {
+	"_rels/.rels": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\
+	<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>\
+</Relationships>',
+
+	"xl/_rels/workbook.xml.rels": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">\
+	<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>\
+</Relationships>',
+
+	"[Content_Types].xml": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\
+<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">\
+	<Default Extension="xml" ContentType="application/xml"/>\
+	<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>\
+	<Default Extension="jpeg" ContentType="image/jpeg"/>\
+	<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>\
+	<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>\
+</Types>',
+
+	"xl/workbook.xml": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\
+<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">\
+	<fileVersion appName="xl" lastEdited="5" lowestEdited="5" rupBuild="24816"/>\
+	<workbookPr showInkAnnotation="0" autoCompressPictures="0"/>\
+	<bookViews>\
+		<workbookView xWindow="0" yWindow="0" windowWidth="25600" windowHeight="19020" tabRatio="500"/>\
+	</bookViews>\
+	<sheets>\
+		<sheet name="Sheet1" sheetId="1" r:id="rId1"/>\
+	</sheets>\
+</workbook>',
+
+	"xl/worksheets/sheet1.xml": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\
+<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">\
+	<sheetData>\
+		__DATA__\
+	</sheetData>\
+</worksheet>'
+};
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Buttons
+ */
+
+//
+// Copy to clipboard
+//
+DataTable.ext.buttons.copyHtml5 = {
+	className: 'buttons-copy buttons-html5',
+
+	text: function ( dt ) {
+		return dt.i18n( 'buttons.copy', 'Copy' );
+	},
+
+	action: function ( e, dt, button, config ) {
+		// This button is slightly sneaky as there is no HTML API to copy text
+		// to a clipboard, so what it does is use the buttons information
+		// element with an also completely hidden textarea that contains the
+		// data to be copied. That is pre-selected so the user just needs to
+		// activate their system clipboard.
+		var newLine = _newLine( config );
+		var output = _exportData( dt, config ).str;
+		var message = $('<span>'+dt.i18n( 'buttons.copyKeys',
+				'Press <i>ctrl</i> or <i>\u2318</i> + <i>C</i> to copy the table data<br>to your system clipboard.<br><br>'+
+				'To cancel, click this message or press escape.' )+'</span>'
+			)
+			.append( $('<div/>')
+				.css( {
+					height: 1,
+					width: 1,
+					overflow: 'hidden'
+				} )
+				.append(
+					$('<textarea readonly/>').val( output )
+				)
+		);
+
+		dt.buttons.info( dt.i18n( 'buttons.copyTitle', 'Copy to clipboard' ), message, 0 );
+
+		// Select the text so when the user activates their system clipboard
+		// it will copy that text
+		message.find('textarea')[0].focus();
+		message.find('textarea')[0].select();
+
+		// Event to hide the message when the user is done
+		var container = $(message).closest('.dt-button-info');
+		var close = function () {
+			container.off( 'click.buttons-copy' );
+			$(document).off( '.buttons-copy' );
+			dt.buttons.info( false );
+		};
+
+		container.on( 'click.buttons-copy', close );
+		$(document)
+			.on( 'keydown.buttons-copy', function (e) {
+				if ( e.keyCode === 27 ) { // esc
+					close();
+				}
+			} )
+			.on( 'copy.buttons-copy cut.buttons-copy', function () {
+				close();
+			} );
+	},
+
+	exportOptions: {},
+
+	fieldSeparator: '\t',
+
+	fieldBoundary: '',
+
+	header: true,
+
+	footer: false
+};
+
+//
+// CSV export
+//
+DataTable.ext.buttons.csvHtml5 = {
+	className: 'buttons-csv buttons-html5',
+
+	available: function () {
+		return window.FileReader !== undefined && window.Blob;
+	},
+
+	text: function ( dt ) {
+		return dt.i18n( 'buttons.csv', 'CSV' );
+	},
+
+	action: function ( e, dt, button, config ) {
+		// Set the text
+		var newLine = _newLine( config );
+		var output = _exportData( dt, config ).str;
+		var charset = config.charset;
+
+		if ( charset !== false ) {
+			if ( ! charset ) {
+				charset = document.characterSet || document.charset;
+			}
+
+			if ( charset ) {
+				charset = ';charset='+charset;
+			}
+		}
+		else {
+			charset = '';
+		}
+
+		_saveAs(
+			new Blob( [output], {type: 'text/csv'+charset} ),
+			_filename( config )
+		);
+	},
+
+	filename: '*',
+
+	extension: '.csv',
+
+	exportOptions: {},
+
+	fieldSeparator: ',',
+
+	fieldBoundary: '"',
+
+	escapeChar: '"',
+
+	charset: null,
+
+	header: true,
+
+	footer: false
+};
+
+//
+// Excel (xlsx) export
+//
+DataTable.ext.buttons.excelHtml5 = {
+	className: 'buttons-excel buttons-html5',
+
+	available: function () {
+		return window.FileReader !== undefined && window.JSZip !== undefined && ! _isSafari();
+	},
+
+	text: function ( dt ) {
+		return dt.i18n( 'buttons.excel', 'Excel' );
+	},
+
+	action: function ( e, dt, button, config ) {
+		// Set the text
+		var xml = '';
+		var data = dt.buttons.exportData( config.exportOptions );
+		var addRow = function ( row ) {
+			var cells = [];
+
+			for ( var i=0, ien=row.length ; i<ien ; i++ ) {
+				cells.push( ! row[i].match(/[^0-9\-\.]/) ?
+					'<c t="n"><v>'+row[i]+'</v></c>' :
+					'<c t="inlineStr"><is><t>'+
+						row[i]
+							.replace(/&(?!amp;)/g, '&amp;')
+							.replace(/[\x00-\x1F\x7F-\x9F]/g, '')+ // remove control characters
+					'</t></is></c>'                                // they are not valid in XML
+				);
+			}
+
+			return '<row>'+cells.join('')+'</row>';
+		};
+
+		if ( config.header ) {
+			xml += addRow( data.header );
+		}
+
+		for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
+			xml += addRow( data.body[i] );
+		}
+
+		if ( config.footer ) {
+			xml += addRow( data.footer );
+		}
+
+		var zip           = new window.JSZip();
+		var _rels         = zip.folder("_rels");
+		var xl            = zip.folder("xl");
+		var xl_rels       = zip.folder("xl/_rels");
+		var xl_worksheets = zip.folder("xl/worksheets");
+
+		zip.file(           '[Content_Types].xml', excelStrings['[Content_Types].xml'] );
+		_rels.file(         '.rels',               excelStrings['_rels/.rels'] );
+		xl.file(            'workbook.xml',        excelStrings['xl/workbook.xml'] );
+		xl_rels.file(       'workbook.xml.rels',   excelStrings['xl/_rels/workbook.xml.rels'] );
+		xl_worksheets.file( 'sheet1.xml',          excelStrings['xl/worksheets/sheet1.xml'].replace( '__DATA__', xml ) );
+
+		_saveAs(
+			zip.generate( {type:"blob"} ),
+			_filename( config )
+		);
+	},
+
+	filename: '*',
+
+	extension: '.xlsx',
+
+	exportOptions: {},
+
+	header: true,
+
+	footer: false
+};
+
+//
+// PDF export - using pdfMake - http://pdfmake.org
+//
+DataTable.ext.buttons.pdfHtml5 = {
+	className: 'buttons-pdf buttons-html5',
+
+	available: function () {
+		return window.FileReader !== undefined && window.pdfMake;
+	},
+
+	text: function ( dt ) {
+		return dt.i18n( 'buttons.pdf', 'PDF' );
+	},
+
+	action: function ( e, dt, button, config ) {
+		var newLine = _newLine( config );
+		var data = dt.buttons.exportData( config.exportOptions );
+		var rows = [];
+
+		if ( config.header ) {
+			rows.push( $.map( data.header, function ( d ) {
+				return {
+					text: d,
+					style: 'tableHeader'
+				};
+			} ) );
+		}
+
+		for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
+			rows.push( $.map( data.body[i], function ( d ) {
+				return {
+					text: d,
+					style: i % 2 ? 'tableBodyEven' : 'tableBodyOdd'
+				};
+			} ) );
+		}
+
+		if ( config.footer ) {
+			rows.push( $.map( data.footer, function ( d ) {
+				return {
+					text: d,
+					style: 'tableFooter'
+				};
+			} ) );
+		}
+
+		var doc = {
+			pageSize: config.pageSize,
+			pageOrientation: config.orientation,
+			content: [
+				{
+					table: {
+						headerRows: 1,
+						body: rows
+					},
+					layout: 'noBorders'
+				}
+			],
+			styles: {
+				tableHeader: {
+					bold: true,
+					fontSize: 11,
+					color: 'white',
+					fillColor: '#2d4154',
+					alignment: 'center'
+				},
+				tableBodyEven: {},
+				tableBodyOdd: {
+					fillColor: '#f3f3f3'
+				},
+				tableFooter: {
+					bold: true,
+					fontSize: 11,
+					color: 'white',
+					fillColor: '#2d4154'
+				},
+				title: {
+					alignment: 'center',
+					fontSize: 15
+				},
+				message: {}
+			},
+			defaultStyle: {
+				fontSize: 10
+			}
+		};
+
+		if ( config.message ) {
+			doc.content.unshift( {
+				text: config.message,
+				style: 'message',
+				margin: [ 0, 0, 0, 12 ]
+			} );
+		}
+
+		if ( config.title ) {
+			doc.content.unshift( {
+				text: _filename( config, false ),
+				style: 'title',
+				margin: [ 0, 0, 0, 12 ]
+			} );
+		}
+
+		if ( config.customize ) {
+			config.customize( doc );
+		}
+
+		var pdf = window.pdfMake.createPdf( doc );
+
+		if ( config.download === 'open' && ! _isSafari() ) {
+			pdf.open();
+		}
+		else {
+			pdf.getBuffer( function (buffer) {
+				var blob = new Blob( [buffer], {type:'application/pdf'} );
+
+				_saveAs( blob, _filename( config ) );
+			} );
+		}
+	},
+
+	title: '*',
+
+	filename: '*',
+
+	extension: '.pdf',
+
+	exportOptions: {},
+
+	orientation: 'portrait',
+
+	pageSize: 'A4',
+
+	header: true,
+
+	footer: false,
+
+	message: null,
+
+	customize: null,
+
+	download: 'download'
+};
+
+
+})(jQuery, jQuery.fn.dataTable);

+ 128 - 0
src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/buttons.print.js

@@ -0,0 +1,128 @@
+/*!
+ * Print button for Buttons and DataTables.
+ * 2015 SpryMedia Ltd - datatables.net/license
+ */
+
+(function($, DataTable) {
+"use strict";
+
+
+var _link = document.createElement( 'a' );
+
+/**
+ * Convert a `link` tag's URL from a relative to an absolute address so it will
+ * work correctly in the popup window which has no base URL.
+ *
+ * @param  {node}     el Element to convert
+ */
+var _relToAbs = function( el ) {
+	var url;
+	var clone = $(el).clone()[0];
+	var linkHost;
+
+	if ( clone.nodeName.toLowerCase() === 'link' ) {
+		_link.href = clone.href;
+		linkHost = _link.host;
+
+		// IE doesn't have a trailing slash on the host
+		// Chrome has it on the pathname
+		if ( linkHost.indexOf('/') === -1 && _link.pathname.indexOf('/') !== 0) {
+			linkHost += '/';
+		}
+
+		clone.href = _link.protocol+"//"+linkHost+_link.pathname+_link.search;
+	}
+
+	return clone.outerHTML;
+};
+
+
+DataTable.ext.buttons.print = {
+	className: 'buttons-print',
+
+	text: function ( dt ) {
+		return dt.i18n( 'buttons.print', 'Print' );
+	},
+
+	action: function ( e, dt, button, config ) {
+		var data = dt.buttons.exportData( config.exportOptions );
+		var addRow = function ( d, tag ) {
+			var str = '<tr>';
+
+			for ( var i=0, ien=d.length ; i<ien ; i++ ) {
+				str += '<'+tag+'>'+d[i]+'</'+tag+'>';
+			}
+
+			return str + '</tr>';
+		};
+
+		// Construct a table for printing
+		var html = '<table class="'+dt.table().node().className+'">';
+
+		if ( config.header ) {
+			html += '<thead>'+ addRow( data.header, 'th' ) +'</thead>';
+		}
+
+		html += '<tbody>';
+		for ( var i=0, ien=data.body.length ; i<ien ; i++ ) {
+			html += addRow( data.body[i], 'td' );
+		}
+		html += '</tbody>';
+
+		if ( config.footer ) {
+			html += '<thead>'+ addRow( data.footer, 'th' ) +'</thead>';
+		}
+
+		// Open a new window for the printable table
+		var win = window.open( '', '' );
+		var title = config.title.replace( '*', $('title').text() );
+
+		win.document.close();
+
+		// Inject the title and also a copy of the style and link tags from this
+		// document so the table can retain its base styling. Note that we have
+		// to use string manipulation as IE won't allow elements to be created
+		// in the host document and then appended to the new window.
+		var head = '<title>'+title+'</title>';
+		$('style, link').each( function () {
+			head += _relToAbs( this );
+		} );
+
+		$(win.document.head).html( head );
+
+		// Inject the table and other surrounding information
+		$(win.document.body).html(
+			'<h1>'+title+'</h1>'+
+			'<div>'+config.message+'</div>'+
+			html
+		);
+
+		if ( config.customize ) {
+			config.customize( win );
+		}
+
+		setTimeout( function () {
+			if ( config.autoPrint ) {
+				win.print(); // blocking - so close will not
+				win.close(); // execute until this is done
+			}
+		}, 250 );
+	},
+
+	title: '*',
+
+	message: '',
+
+	exportOptions: {},
+
+	header: true,
+
+	footer: false,
+
+	autoPrint: true,
+
+	customize: null
+};
+
+
+})(jQuery, jQuery.fn.dataTable);

文件差異過大導致無法顯示
+ 1506 - 0
src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/dataTables.buttons.js


二進制
src/main/webapp/static/ace/assets/js/dataTables/extensions/buttons/swf/flashExport.swf


文件差異過大導致無法顯示
+ 1023 - 0
src/main/webapp/static/ace/assets/js/dataTables/extensions/select/dataTables.select.js


+ 261 - 0
src/main/webapp/static/ace/assets/js/dataTables/jquery.dataTables.bootstrap.js

@@ -0,0 +1,261 @@
+/* Set the defaults for DataTables initialisation */
+$.extend( true, $.fn.dataTable.defaults, {
+	"sDom":
+		"<'row'<'col-xs-6'l><'col-xs-6'f>r>"+
+		"t"+
+		"<'row'<'col-xs-6'i><'col-xs-6'p>>",
+	"oLanguage": {
+		"sLengthMenu": "Display _MENU_ records"
+	}
+} );
+
+
+/* Default class modification */
+$.extend( $.fn.dataTableExt.oStdClasses, {
+	"sWrapper": "dataTables_wrapper form-inline",
+	"sFilterInput": "form-control input-sm",
+	"sLengthSelect": "form-control input-sm"
+} );
+
+// In 1.10 we use the pagination renderers to draw the Bootstrap paging,
+// rather than  custom plug-in
+if ( $.fn.dataTable.Api ) {
+	$.fn.dataTable.defaults.renderer = 'bootstrap';
+	$.fn.dataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, buttons, page, pages ) {
+		var api = new $.fn.dataTable.Api( settings );
+		var classes = settings.oClasses;
+		var lang = settings.oLanguage.oPaginate;
+		var btnDisplay, btnClass;
+
+		var attach = function( container, buttons ) {
+			var i, ien, node, button;
+			var clickHandler = function ( e ) {
+				e.preventDefault();
+				//return if target is disabled
+				if($(e.target).parent().hasClass('disabled')) return false;//ACE
+				if ( e.data.action !== 'ellipsis' ) {
+					api.page( e.data.action ).draw( false );
+				}
+			};
+
+			for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
+				button = buttons[i];
+
+				if ( $.isArray( button ) ) {
+					attach( container, button );
+				}
+				else {
+					btnDisplay = '';
+					btnClass = '';
+
+					switch ( button ) {
+						case 'ellipsis':
+							btnDisplay = '&hellip;';
+							btnClass = 'disabled';
+							break;
+
+						case 'first':
+							btnDisplay = lang.sFirst;
+							btnClass = button + (page > 0 ?
+								'' : ' disabled');
+							break;
+
+						case 'previous':
+							btnDisplay = lang.sPrevious;
+							btnClass = button + (page > 0 ?
+								'' : ' disabled');
+							break;
+
+						case 'next':
+							btnDisplay = lang.sNext;
+							btnClass = button + (page < pages-1 ?
+								'' : ' disabled');
+							break;
+
+						case 'last':
+							btnDisplay = lang.sLast;
+							btnClass = button + (page < pages-1 ?
+								'' : ' disabled');
+							break;
+
+						default:
+							btnDisplay = button + 1;
+							btnClass = page === button ?
+								'active' : '';
+							break;
+					}
+
+					if ( btnDisplay ) {
+						node = $('<li>', {
+								'class': classes.sPageButton+' '+btnClass,
+								'aria-controls': settings.sTableId,
+								'tabindex': settings.iTabIndex,
+								'id': idx === 0 && typeof button === 'string' ?
+									settings.sTableId +'_'+ button :
+									null
+							} )
+							.append( $('<a>', {
+									'href': '#'
+								} )
+								.html( btnDisplay )
+							)
+							.appendTo( container );
+
+						settings.oApi._fnBindAction(
+							node, {action: button}, clickHandler
+						);
+					}
+				}
+			}
+		};
+
+		attach(
+			$(host).empty().html('<ul class="pagination"/>').children('ul'),
+			buttons
+		);
+	}
+}
+else {
+	// Integration for 1.9-
+	$.fn.dataTable.defaults.sPaginationType = 'bootstrap';
+
+	/* API method to get paging information */
+	$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
+	{
+		return {
+			"iStart":         oSettings._iDisplayStart,
+			"iEnd":           oSettings.fnDisplayEnd(),
+			"iLength":        oSettings._iDisplayLength,
+			"iTotal":         oSettings.fnRecordsTotal(),
+			"iFilteredTotal": oSettings.fnRecordsDisplay(),
+			"iPage":          oSettings._iDisplayLength === -1 ?
+				0 : Math.ceil( oSettings._iDisplayStart / oSettings._iDisplayLength ),
+			"iTotalPages":    oSettings._iDisplayLength === -1 ?
+				0 : Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
+		};
+	};
+
+	/* Bootstrap style pagination control */
+	$.extend( $.fn.dataTableExt.oPagination, {
+		"bootstrap": {
+			"fnInit": function( oSettings, nPaging, fnDraw ) {
+				var oLang = oSettings.oLanguage.oPaginate;
+				var fnClickHandler = function ( e ) {
+					alert(1);
+					e.preventDefault();
+					if ( oSettings.oApi._fnPageChange(oSettings, e.data.action) ) {
+						fnDraw( oSettings );
+					}
+				};
+
+				//Pagination Buttons
+				$(nPaging).append(
+					'<ul class="pagination">'+
+						'<li class="prev disabled"><a href="#"><i class="fa fa-angle-double-left"></i></a></li>'+//first
+						'<li class="prev disabled"><a href="#"><i class="fa fa-angle-left"></i></a></li>'+//next
+						'<li class="next disabled"><a href="#"><i class="fa fa-angle-right"></i></a></li>'+//prev
+						'<li class="next disabled"><a href="#"><i class="fa fa-angle-double-right"></i></a></li>'+//last
+					'</ul>'
+				);
+				var els = $('a', nPaging);
+				$(els[0]).bind( 'click.DT', { action: "first" }, fnClickHandler );//first
+				$(els[1]).bind( 'click.DT', { action: "previous" }, fnClickHandler );//next
+				$(els[2]).bind( 'click.DT', { action: "next" }, fnClickHandler );//prev
+				$(els[3]).bind( 'click.DT', { action: "last" }, fnClickHandler );//last
+				//if you don't want the "first & last" buttons, you can remove the relevant HTML line and event handlers
+			},
+
+			"fnUpdate": function ( oSettings, fnDraw ) {
+				var iListLength = 5;
+				var oPaging = oSettings.oInstance.fnPagingInfo();
+				var an = oSettings.aanFeatures.p;
+				var i, ien, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);
+
+				if ( oPaging.iTotalPages < iListLength) {
+					iStart = 1;
+					iEnd = oPaging.iTotalPages;
+				}
+				else if ( oPaging.iPage <= iHalf ) {
+					iStart = 1;
+					iEnd = iListLength;
+				} else if ( oPaging.iPage >= (oPaging.iTotalPages-iHalf) ) {
+					iStart = oPaging.iTotalPages - iListLength + 1;
+					iEnd = oPaging.iTotalPages;
+				} else {
+					iStart = oPaging.iPage - iHalf + 1;
+					iEnd = iStart + iListLength - 1;
+				}
+
+				for ( i=0, ien=an.length ; i<ien ; i++ ) {
+					// Remove the middle elements
+					$('li:gt(0)', an[i]).filter(':not(.next,.prev)').remove();//ACE
+
+					// Add the new list items and their event handlers
+					for ( j=iStart ; j<=iEnd ; j++ ) {
+						sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
+						$('<li '+sClass+'><a href="#">'+j+'</a></li>')
+							.insertBefore( $('li.next:eq(0)', an[i])[0] )//ACE
+							.bind('click', function (e) {
+								e.preventDefault();
+								oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
+								fnDraw( oSettings );
+							} );
+					}
+
+					// Add / remove disabled classes from the static elements
+					//ACE
+					if ( oPaging.iPage === 0 ) {
+						$('li.prev', an[i]).addClass('disabled');
+					} else {
+						$('li.prev', an[i]).removeClass('disabled');
+					}
+	 
+					if ( oPaging.iPage === oPaging.iTotalPages-1 || oPaging.iTotalPages === 0 ) {
+						$('li.next', an[i]).addClass('disabled');
+					} else {
+						$('li.next', an[i]).removeClass('disabled');
+					}
+				}
+			}
+		}
+	} );
+}
+
+
+/*
+ * TableTools Bootstrap compatibility
+ * Required TableTools 2.1+
+ */
+if ( $.fn.DataTable.TableTools ) {
+	// Set the classes that TableTools uses to something suitable for Bootstrap
+	$.extend( true, $.fn.DataTable.TableTools.classes, {
+		"container": "DTTT btn-group",
+		"buttons": {
+			"normal": "btn btn-default",
+			"disabled": "disabled"
+		},
+		"collection": {
+			"container": "DTTT_dropdown dropdown-menu",
+			"buttons": {
+				"normal": "",
+				"disabled": "disabled"
+			}
+		},
+		"print": {
+			"info": "DTTT_print_info modal"
+		},
+		"select": {
+			"row": "active"
+		}
+	} );
+
+	// Have the collection use a bootstrap compatible dropdown
+	$.extend( true, $.fn.DataTable.TableTools.DEFAULTS.oTags, {
+		"collection": {
+			"container": "ul",
+			"button": "li",
+			"liner": "a"
+		}
+	} );
+}
+

文件差異過大導致無法顯示
+ 15129 - 0
src/main/webapp/static/ace/assets/js/dataTables/jquery.dataTables.js


文件差異過大導致無法顯示
+ 1782 - 0
src/main/webapp/static/ace/assets/js/date-time/bootstrap-datepicker.js


文件差異過大導致無法顯示
+ 2552 - 0
src/main/webapp/static/ace/assets/js/date-time/bootstrap-datetimepicker.js


文件差異過大導致無法顯示
+ 1180 - 0
src/main/webapp/static/ace/assets/js/date-time/bootstrap-timepicker.js


文件差異過大導致無法顯示
+ 1492 - 0
src/main/webapp/static/ace/assets/js/date-time/daterangepicker.js


文件差異過大導致無法顯示
+ 3195 - 0
src/main/webapp/static/ace/assets/js/date-time/moment.js


文件差異過大導致無法顯示
+ 1728 - 0
src/main/webapp/static/ace/assets/js/dropzone.js


文件差異過大導致無法顯示
+ 1428 - 0
src/main/webapp/static/ace/assets/js/excanvas.js


文件差異過大導致無法顯示
+ 3168 - 0
src/main/webapp/static/ace/assets/js/flot/jquery.flot.js


+ 820 - 0
src/main/webapp/static/ace/assets/js/flot/jquery.flot.pie.js

@@ -0,0 +1,820 @@
+/* Flot plugin for rendering pie charts.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin assumes that each series has a single data value, and that each
+value is a positive integer or zero.  Negative numbers don't make sense for a
+pie chart, and have unpredictable results.  The values do NOT need to be
+passed in as percentages; the plugin will calculate the total and per-slice
+percentages internally.
+
+* Created by Brian Medendorp
+
+* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
+
+The plugin supports these options:
+
+	series: {
+		pie: {
+			show: true/false
+			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
+			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
+			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
+			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
+			offset: {
+				top: integer value to move the pie up or down
+				left: integer value to move the pie left or right, or 'auto'
+			},
+			stroke: {
+				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
+				width: integer pixel width of the stroke
+			},
+			label: {
+				show: true/false, or 'auto'
+				formatter:  a user-defined function that modifies the text/style of the label text
+				radius: 0-1 for percentage of fullsize, or a specified pixel length
+				background: {
+					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
+					opacity: 0-1
+				},
+				threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
+			},
+			combine: {
+				threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
+				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
+				label: any text value of what the combined slice should be labeled
+			}
+			highlight: {
+				opacity: 0-1
+			}
+		}
+	}
+
+More detail and specific examples can be found in the included HTML file.
+
+*/
+
+(function($) {
+
+	// Maximum redraw attempts when fitting labels within the plot
+
+	var REDRAW_ATTEMPTS = 10;
+
+	// Factor by which to shrink the pie when fitting labels within the plot
+
+	var REDRAW_SHRINK = 0.95;
+
+	function init(plot) {
+
+		var canvas = null,
+			target = null,
+			options = null,
+			maxRadius = null,
+			centerLeft = null,
+			centerTop = null,
+			processed = false,
+			ctx = null;
+
+		// interactive variables
+
+		var highlights = [];
+
+		// add hook to determine if pie plugin in enabled, and then perform necessary operations
+
+		plot.hooks.processOptions.push(function(plot, options) {
+			if (options.series.pie.show) {
+
+				options.grid.show = false;
+
+				// set labels.show
+
+				if (options.series.pie.label.show == "auto") {
+					if (options.legend.show) {
+						options.series.pie.label.show = false;
+					} else {
+						options.series.pie.label.show = true;
+					}
+				}
+
+				// set radius
+
+				if (options.series.pie.radius == "auto") {
+					if (options.series.pie.label.show) {
+						options.series.pie.radius = 3/4;
+					} else {
+						options.series.pie.radius = 1;
+					}
+				}
+
+				// ensure sane tilt
+
+				if (options.series.pie.tilt > 1) {
+					options.series.pie.tilt = 1;
+				} else if (options.series.pie.tilt < 0) {
+					options.series.pie.tilt = 0;
+				}
+			}
+		});
+
+		plot.hooks.bindEvents.push(function(plot, eventHolder) {
+			var options = plot.getOptions();
+			if (options.series.pie.show) {
+				if (options.grid.hoverable) {
+					eventHolder.unbind("mousemove").mousemove(onMouseMove);
+				}
+				if (options.grid.clickable) {
+					eventHolder.unbind("click").click(onClick);
+				}
+			}
+		});
+
+		plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
+			var options = plot.getOptions();
+			if (options.series.pie.show) {
+				processDatapoints(plot, series, data, datapoints);
+			}
+		});
+
+		plot.hooks.drawOverlay.push(function(plot, octx) {
+			var options = plot.getOptions();
+			if (options.series.pie.show) {
+				drawOverlay(plot, octx);
+			}
+		});
+
+		plot.hooks.draw.push(function(plot, newCtx) {
+			var options = plot.getOptions();
+			if (options.series.pie.show) {
+				draw(plot, newCtx);
+			}
+		});
+
+		function processDatapoints(plot, series, datapoints) {
+			if (!processed)	{
+				processed = true;
+				canvas = plot.getCanvas();
+				target = $(canvas).parent();
+				options = plot.getOptions();
+				plot.setData(combine(plot.getData()));
+			}
+		}
+
+		function combine(data) {
+
+			var total = 0,
+				combined = 0,
+				numCombined = 0,
+				color = options.series.pie.combine.color,
+				newdata = [];
+
+			// Fix up the raw data from Flot, ensuring the data is numeric
+
+			for (var i = 0; i < data.length; ++i) {
+
+				var value = data[i].data;
+
+				// If the data is an array, we'll assume that it's a standard
+				// Flot x-y pair, and are concerned only with the second value.
+
+				// Note how we use the original array, rather than creating a
+				// new one; this is more efficient and preserves any extra data
+				// that the user may have stored in higher indexes.
+
+				if ($.isArray(value) && value.length == 1) {
+    				value = value[0];
+				}
+
+				if ($.isArray(value)) {
+					// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
+					if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
+						value[1] = +value[1];
+					} else {
+						value[1] = 0;
+					}
+				} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
+					value = [1, +value];
+				} else {
+					value = [1, 0];
+				}
+
+				data[i].data = [value];
+			}
+
+			// Sum up all the slices, so we can calculate percentages for each
+
+			for (var i = 0; i < data.length; ++i) {
+				total += data[i].data[0][1];
+			}
+
+			// Count the number of slices with percentages below the combine
+			// threshold; if it turns out to be just one, we won't combine.
+
+			for (var i = 0; i < data.length; ++i) {
+				var value = data[i].data[0][1];
+				if (value / total <= options.series.pie.combine.threshold) {
+					combined += value;
+					numCombined++;
+					if (!color) {
+						color = data[i].color;
+					}
+				}
+			}
+
+			for (var i = 0; i < data.length; ++i) {
+				var value = data[i].data[0][1];
+				if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
+					newdata.push(
+						$.extend(data[i], {     /* extend to allow keeping all other original data values
+						                           and using them e.g. in labelFormatter. */
+							data: [[1, value]],
+							color: data[i].color,
+							label: data[i].label,
+							angle: value * Math.PI * 2 / total,
+							percent: value / (total / 100)
+						})
+					);
+				}
+			}
+
+			if (numCombined > 1) {
+				newdata.push({
+					data: [[1, combined]],
+					color: color,
+					label: options.series.pie.combine.label,
+					angle: combined * Math.PI * 2 / total,
+					percent: combined / (total / 100)
+				});
+			}
+
+			return newdata;
+		}
+
+		function draw(plot, newCtx) {
+
+			if (!target) {
+				return; // if no series were passed
+			}
+
+			var canvasWidth = plot.getPlaceholder().width(),
+				canvasHeight = plot.getPlaceholder().height(),
+				legendWidth = target.children().filter(".legend").children().width() || 0;
+
+			ctx = newCtx;
+
+			// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
+
+			// When combining smaller slices into an 'other' slice, we need to
+			// add a new series.  Since Flot gives plugins no way to modify the
+			// list of series, the pie plugin uses a hack where the first call
+			// to processDatapoints results in a call to setData with the new
+			// list of series, then subsequent processDatapoints do nothing.
+
+			// The plugin-global 'processed' flag is used to control this hack;
+			// it starts out false, and is set to true after the first call to
+			// processDatapoints.
+
+			// Unfortunately this turns future setData calls into no-ops; they
+			// call processDatapoints, the flag is true, and nothing happens.
+
+			// To fix this we'll set the flag back to false here in draw, when
+			// all series have been processed, so the next sequence of calls to
+			// processDatapoints once again starts out with a slice-combine.
+			// This is really a hack; in 0.9 we need to give plugins a proper
+			// way to modify series before any processing begins.
+
+			processed = false;
+
+			// calculate maximum radius and center point
+
+			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
+			centerTop = canvasHeight / 2 + options.series.pie.offset.top;
+			centerLeft = canvasWidth / 2;
+
+			if (options.series.pie.offset.left == "auto") {
+				if (options.legend.position.match("w")) {
+					centerLeft += legendWidth / 2;
+				} else {
+					centerLeft -= legendWidth / 2;
+				}
+				if (centerLeft < maxRadius) {
+					centerLeft = maxRadius;
+				} else if (centerLeft > canvasWidth - maxRadius) {
+					centerLeft = canvasWidth - maxRadius;
+				}
+			} else {
+				centerLeft += options.series.pie.offset.left;
+			}
+
+			var slices = plot.getData(),
+				attempts = 0;
+
+			// Keep shrinking the pie's radius until drawPie returns true,
+			// indicating that all the labels fit, or we try too many times.
+
+			do {
+				if (attempts > 0) {
+					maxRadius *= REDRAW_SHRINK;
+				}
+				attempts += 1;
+				clear();
+				if (options.series.pie.tilt <= 0.8) {
+					drawShadow();
+				}
+			} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
+
+			if (attempts >= REDRAW_ATTEMPTS) {
+				clear();
+				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
+			}
+
+			if (plot.setSeries && plot.insertLegend) {
+				plot.setSeries(slices);
+				plot.insertLegend();
+			}
+
+			// we're actually done at this point, just defining internal functions at this point
+
+			function clear() {
+				ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+				target.children().filter(".pieLabel, .pieLabelBackground").remove();
+			}
+
+			function drawShadow() {
+
+				var shadowLeft = options.series.pie.shadow.left;
+				var shadowTop = options.series.pie.shadow.top;
+				var edge = 10;
+				var alpha = options.series.pie.shadow.alpha;
+				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
+					return;	// shadow would be outside canvas, so don't draw it
+				}
+
+				ctx.save();
+				ctx.translate(shadowLeft,shadowTop);
+				ctx.globalAlpha = alpha;
+				ctx.fillStyle = "#000";
+
+				// center and rotate to starting position
+
+				ctx.translate(centerLeft,centerTop);
+				ctx.scale(1, options.series.pie.tilt);
+
+				//radius -= edge;
+
+				for (var i = 1; i <= edge; i++) {
+					ctx.beginPath();
+					ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
+					ctx.fill();
+					radius -= i;
+				}
+
+				ctx.restore();
+			}
+
+			function drawPie() {
+
+				var startAngle = Math.PI * options.series.pie.startAngle;
+				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+				// center and rotate to starting position
+
+				ctx.save();
+				ctx.translate(centerLeft,centerTop);
+				ctx.scale(1, options.series.pie.tilt);
+				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
+
+				// draw slices
+
+				ctx.save();
+				var currentAngle = startAngle;
+				for (var i = 0; i < slices.length; ++i) {
+					slices[i].startAngle = currentAngle;
+					drawSlice(slices[i].angle, slices[i].color, true);
+				}
+				ctx.restore();
+
+				// draw slice outlines
+
+				if (options.series.pie.stroke.width > 0) {
+					ctx.save();
+					ctx.lineWidth = options.series.pie.stroke.width;
+					currentAngle = startAngle;
+					for (var i = 0; i < slices.length; ++i) {
+						drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
+					}
+					ctx.restore();
+				}
+
+				// draw donut hole
+
+				drawDonutHole(ctx);
+
+				ctx.restore();
+
+				// Draw the labels, returning true if they fit within the plot
+
+				if (options.series.pie.label.show) {
+					return drawLabels();
+				} else return true;
+
+				function drawSlice(angle, color, fill) {
+
+					if (angle <= 0 || isNaN(angle)) {
+						return;
+					}
+
+					if (fill) {
+						ctx.fillStyle = color;
+					} else {
+						ctx.strokeStyle = color;
+						ctx.lineJoin = "round";
+					}
+
+					ctx.beginPath();
+					if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
+						ctx.moveTo(0, 0); // Center of the pie
+					}
+
+					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
+					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
+					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
+					ctx.closePath();
+					//ctx.rotate(angle); // This doesn't work properly in Opera
+					currentAngle += angle;
+
+					if (fill) {
+						ctx.fill();
+					} else {
+						ctx.stroke();
+					}
+				}
+
+				function drawLabels() {
+
+					var currentAngle = startAngle;
+					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
+
+					for (var i = 0; i < slices.length; ++i) {
+						if (slices[i].percent >= options.series.pie.label.threshold * 100) {
+							if (!drawLabel(slices[i], currentAngle, i)) {
+								return false;
+							}
+						}
+						currentAngle += slices[i].angle;
+					}
+
+					return true;
+
+					function drawLabel(slice, startAngle, index) {
+
+						if (slice.data[0][1] == 0) {
+							return true;
+						}
+
+						// format label text
+
+						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
+
+						if (lf) {
+							text = lf(slice.label, slice);
+						} else {
+							text = slice.label;
+						}
+
+						if (plf) {
+							text = plf(text, slice);
+						}
+
+						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
+						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
+						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
+
+						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
+						target.append(html);
+
+						var label = target.children("#pieLabel" + index);
+						var labelTop = (y - label.height() / 2);
+						var labelLeft = (x - label.width() / 2);
+
+						label.css("top", labelTop);
+						label.css("left", labelLeft);
+
+						// check to make sure that the label is not outside the canvas
+
+						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
+							return false;
+						}
+
+						if (options.series.pie.label.background.opacity != 0) {
+
+							// put in the transparent background separately to avoid blended labels and label boxes
+
+							var c = options.series.pie.label.background.color;
+
+							if (c == null) {
+								c = slice.color;
+							}
+
+							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
+							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
+								.css("opacity", options.series.pie.label.background.opacity)
+								.insertBefore(label);
+						}
+
+						return true;
+					} // end individual label function
+				} // end drawLabels function
+			} // end drawPie function
+		} // end draw function
+
+		// Placed here because it needs to be accessed from multiple locations
+
+		function drawDonutHole(layer) {
+			if (options.series.pie.innerRadius > 0) {
+
+				// subtract the center
+
+				layer.save();
+				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
+				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
+				layer.beginPath();
+				layer.fillStyle = options.series.pie.stroke.color;
+				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+				layer.fill();
+				layer.closePath();
+				layer.restore();
+
+				// add inner stroke
+
+				layer.save();
+				layer.beginPath();
+				layer.strokeStyle = options.series.pie.stroke.color;
+				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+				layer.stroke();
+				layer.closePath();
+				layer.restore();
+
+				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
+			}
+		}
+
+		//-- Additional Interactive related functions --
+
+		function isPointInPoly(poly, pt) {
+			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
+				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
+				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
+				&& (c = !c);
+			return c;
+		}
+
+		function findNearbySlice(mouseX, mouseY) {
+
+			var slices = plot.getData(),
+				options = plot.getOptions(),
+				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
+				x, y;
+
+			for (var i = 0; i < slices.length; ++i) {
+
+				var s = slices[i];
+
+				if (s.pie.show) {
+
+					ctx.save();
+					ctx.beginPath();
+					ctx.moveTo(0, 0); // Center of the pie
+					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
+					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
+					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
+					ctx.closePath();
+					x = mouseX - centerLeft;
+					y = mouseY - centerTop;
+
+					if (ctx.isPointInPath) {
+						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
+							ctx.restore();
+							return {
+								datapoint: [s.percent, s.data],
+								dataIndex: 0,
+								series: s,
+								seriesIndex: i
+							};
+						}
+					} else {
+
+						// excanvas for IE doesn;t support isPointInPath, this is a workaround.
+
+						var p1X = radius * Math.cos(s.startAngle),
+							p1Y = radius * Math.sin(s.startAngle),
+							p2X = radius * Math.cos(s.startAngle + s.angle / 4),
+							p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
+							p3X = radius * Math.cos(s.startAngle + s.angle / 2),
+							p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
+							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
+							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
+							p5X = radius * Math.cos(s.startAngle + s.angle),
+							p5Y = radius * Math.sin(s.startAngle + s.angle),
+							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
+							arrPoint = [x, y];
+
+						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
+
+						if (isPointInPoly(arrPoly, arrPoint)) {
+							ctx.restore();
+							return {
+								datapoint: [s.percent, s.data],
+								dataIndex: 0,
+								series: s,
+								seriesIndex: i
+							};
+						}
+					}
+
+					ctx.restore();
+				}
+			}
+
+			return null;
+		}
+
+		function onMouseMove(e) {
+			triggerClickHoverEvent("plothover", e);
+		}
+
+		function onClick(e) {
+			triggerClickHoverEvent("plotclick", e);
+		}
+
+		// trigger click or hover event (they send the same parameters so we share their code)
+
+		function triggerClickHoverEvent(eventname, e) {
+
+			var offset = plot.offset();
+			var canvasX = parseInt(e.pageX - offset.left);
+			var canvasY =  parseInt(e.pageY - offset.top);
+			var item = findNearbySlice(canvasX, canvasY);
+
+			if (options.grid.autoHighlight) {
+
+				// clear auto-highlights
+
+				for (var i = 0; i < highlights.length; ++i) {
+					var h = highlights[i];
+					if (h.auto == eventname && !(item && h.series == item.series)) {
+						unhighlight(h.series);
+					}
+				}
+			}
+
+			// highlight the slice
+
+			if (item) {
+				highlight(item.series, eventname);
+			}
+
+			// trigger any hover bind events
+
+			var pos = { pageX: e.pageX, pageY: e.pageY };
+			target.trigger(eventname, [pos, item]);
+		}
+
+		function highlight(s, auto) {
+			//if (typeof s == "number") {
+			//	s = series[s];
+			//}
+
+			var i = indexOfHighlight(s);
+
+			if (i == -1) {
+				highlights.push({ series: s, auto: auto });
+				plot.triggerRedrawOverlay();
+			} else if (!auto) {
+				highlights[i].auto = false;
+			}
+		}
+
+		function unhighlight(s) {
+			if (s == null) {
+				highlights = [];
+				plot.triggerRedrawOverlay();
+			}
+
+			//if (typeof s == "number") {
+			//	s = series[s];
+			//}
+
+			var i = indexOfHighlight(s);
+
+			if (i != -1) {
+				highlights.splice(i, 1);
+				plot.triggerRedrawOverlay();
+			}
+		}
+
+		function indexOfHighlight(s) {
+			for (var i = 0; i < highlights.length; ++i) {
+				var h = highlights[i];
+				if (h.series == s)
+					return i;
+			}
+			return -1;
+		}
+
+		function drawOverlay(plot, octx) {
+
+			var options = plot.getOptions();
+
+			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+			octx.save();
+			octx.translate(centerLeft, centerTop);
+			octx.scale(1, options.series.pie.tilt);
+
+			for (var i = 0; i < highlights.length; ++i) {
+				drawHighlight(highlights[i].series);
+			}
+
+			drawDonutHole(octx);
+
+			octx.restore();
+
+			function drawHighlight(series) {
+
+				if (series.angle <= 0 || isNaN(series.angle)) {
+					return;
+				}
+
+				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
+				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
+				octx.beginPath();
+				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
+					octx.moveTo(0, 0); // Center of the pie
+				}
+				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
+				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
+				octx.closePath();
+				octx.fill();
+			}
+		}
+	} // end init (plugin body)
+
+	// define pie specific options and their default values
+
+	var options = {
+		series: {
+			pie: {
+				show: false,
+				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
+				innerRadius: 0, /* for donut */
+				startAngle: 3/2,
+				tilt: 1,
+				shadow: {
+					left: 5,	// shadow left offset
+					top: 15,	// shadow top offset
+					alpha: 0.02	// shadow alpha
+				},
+				offset: {
+					top: 0,
+					left: "auto"
+				},
+				stroke: {
+					color: "#fff",
+					width: 1
+				},
+				label: {
+					show: "auto",
+					formatter: function(label, slice) {
+						return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
+					},	// formatter function
+					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
+					background: {
+						color: null,
+						opacity: 0
+					},
+					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
+				},
+				combine: {
+					threshold: -1,	// percentage at which to combine little slices into one larger slice
+					color: null,	// color to give the new slice (auto-generated if null)
+					label: "Other"	// label to give the new slice
+				},
+				highlight: {
+					//color: "#fff",		// will add this functionality once parseColor is available
+					opacity: 0.5
+				}
+			}
+		}
+	};
+
+	$.plot.plugins.push({
+		init: init,
+		options: options,
+		name: "pie",
+		version: "1.1"
+	});
+
+})(jQuery);

文件差異過大導致無法顯示
+ 59 - 0
src/main/webapp/static/ace/assets/js/flot/jquery.flot.resize.js


+ 604 - 0
src/main/webapp/static/ace/assets/js/fuelux/fuelux.spinner.js

@@ -0,0 +1,604 @@
+/*
+ * Fuel UX Spinbox
+ * https://github.com/ExactTarget/fuelux
+ *
+ * Copyright (c) 2014 ExactTarget
+ * Licensed under the BSD New license.
+ */
+
+// -- BEGIN UMD WRAPPER PREFACE --
+
+// For more information on UMD visit:
+// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
+
+(function (factory) {
+	if (typeof define === 'function' && define.amd) {
+		// if AMD loader is available, register as an anonymous module.
+		define(['jquery'], factory);
+	} else if (typeof exports === 'object') {
+		// Node/CommonJS
+		module.exports = factory(require('jquery'));
+	} else {
+		// OR use browser globals if AMD is not present
+		factory(jQuery);
+	}
+}(function ($) {
+	// -- END UMD WRAPPER PREFACE --
+
+	// -- BEGIN MODULE CODE HERE --
+
+	var old = $.fn.spinbox;
+
+	// SPINBOX CONSTRUCTOR AND PROTOTYPE
+
+	var Spinbox = function Spinbox(element, options) {
+		this.$element = $(element);
+		this.$element.find('.btn').on('click', function (e) {
+			//keep spinbox from submitting if they forgot to say type="button" on their spinner buttons
+			e.preventDefault();
+		});
+		this.options = $.extend({}, $.fn.spinbox.defaults, options);
+		this.options.step = this.$element.data('step') || this.options.step;
+
+		if (this.options.value < this.options.min) {
+			this.options.value = this.options.min;
+		} else if (this.options.max < this.options.value) {
+			this.options.value = this.options.max;
+		}
+
+		this.$input = this.$element.find('.spinbox-input');
+
+		this.$input.on('focusout.fu.spinbox', this.$input, $.proxy(this.change, this));
+		this.$element.on('keydown.fu.spinbox', this.$input, $.proxy(this.keydown, this));
+		this.$element.on('keyup.fu.spinbox', this.$input, $.proxy(this.keyup, this));
+
+		this.bindMousewheelListeners();
+		this.mousewheelTimeout = {};
+
+
+		if (this.options.hold) {
+			this.$element.on('mousedown.fu.spinbox', '.spinbox-up', $.proxy(function () {
+				this.startSpin(true);
+			}, this));
+			this.$element.on('mouseup.fu.spinbox', '.spinbox-up, .spinbox-down', $.proxy(this.stopSpin, this));
+			this.$element.on('mouseout.fu.spinbox', '.spinbox-up, .spinbox-down', $.proxy(this.stopSpin, this));
+			this.$element.on('mousedown.fu.spinbox', '.spinbox-down', $.proxy(function () {
+				this.startSpin(false);
+			}, this));
+		} else {
+			this.$element.on('click.fu.spinbox', '.spinbox-up', $.proxy(function () {
+				this.step(true);
+			}, this));
+			this.$element.on('click.fu.spinbox', '.spinbox-down', $.proxy(function () {
+				this.step(false);
+			}, this));
+		}
+
+		this.switches = {
+			count: 1,
+			enabled: true
+		};
+
+		if (this.options.speed === 'medium') {
+			this.switches.speed = 300;
+		} else if (this.options.speed === 'fast') {
+			this.switches.speed = 100;
+		} else {
+			this.switches.speed = 500;
+		}
+
+		this.options.defaultUnit = _isUnitLegal(this.options.defaultUnit, this.options.units) ? this.options.defaultUnit : '';
+		this.unit = this.options.defaultUnit;
+
+		this.lastValue = this.options.value;
+
+		this.render();
+
+		if (this.options.disabled) {
+			this.disable();
+		}
+	};
+
+	// Truly private methods
+	var _limitToStep = function _limitToStep(number, step) {
+		return Math.round(number / step) * step;
+	};
+
+	var _isUnitLegal = function _isUnitLegal(unit, validUnits) {
+		var legalUnit = false;
+		var suspectUnit = unit.toLowerCase();
+
+		$.each(validUnits, function (i, validUnit) {
+			validUnit = validUnit.toLowerCase();
+			if (suspectUnit === validUnit) {
+				legalUnit = true;
+				return false;//break out of the loop
+			}
+		});
+
+		return legalUnit;
+	};
+
+	var _applyLimits = function _applyLimits(value) {
+		// if unreadable
+		if (isNaN(parseFloat(value))) {
+			return value;
+		}
+
+		// if not within range return the limit
+		if (value > this.options.max) {
+			if (this.options.cycle) {
+				value = this.options.min;
+			} else {
+				value = this.options.max;
+			}
+		} else if (value < this.options.min) {
+			if (this.options.cycle) {
+				value = this.options.max;
+			} else {
+				value = this.options.min;
+			}
+		}
+
+		if (this.options.limitToStep && this.options.step) {
+			value = _limitToStep(value, this.options.step);
+
+			//force round direction so that it stays within bounds
+			if(value > this.options.max){
+				value = value - this.options.step;
+			} else if(value < this.options.min) {
+				value = value + this.options.step;
+			}
+		}
+
+		return value;
+	};
+
+	Spinbox.prototype = {
+		constructor: Spinbox,
+
+		destroy: function destroy() {
+			this.$element.remove();
+			// any external bindings
+			// [none]
+			// set input value attrbute
+			this.$element.find('input').each(function () {
+				$(this).attr('value', $(this).val());
+			});
+			// empty elements to return to original markup
+			// [none]
+			// returns string of markup
+			return this.$element[0].outerHTML;
+		},
+
+		render: function render() {
+			this.setValue(this.getDisplayValue());
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+		},
+
+		change: function change() {
+			this.setValue(this.getDisplayValue());
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+			this.triggerChangedEvent();
+		},
+
+		stopSpin: function stopSpin() {
+
+
+
+
+
+			if (this.switches.timeout !== undefined) {
+				clearTimeout(this.switches.timeout);
+				this.switches.count = 1;
+				this.triggerChangedEvent();
+			}
+		},
+
+		triggerChangedEvent: function triggerChangedEvent() {
+			var currentValue = this.getValue();
+			if (currentValue === this.lastValue) return;
+			this.lastValue = currentValue;
+
+			// Primary changed event
+			this.$element.trigger('changed.fu.spinbox', currentValue);
+		},
+
+		startSpin: function startSpin(type) {
+			if (!this.options.disabled) {
+				var divisor = this.switches.count;
+
+				if (divisor === 1) {
+					this.step(type);
+					divisor = 1;
+				} else if (divisor < 3) {
+					divisor = 1.5;
+				} else if (divisor < 8) {
+					divisor = 2.5;
+				} else {
+					divisor = 4;
+				}
+
+				this.switches.timeout = setTimeout($.proxy(function () {
+					this.iterate(type);
+				}, this), this.switches.speed / divisor);
+				this.switches.count++;
+			}
+		},
+
+		iterate: function iterate(type) {
+			this.step(type);
+			this.startSpin(type);
+		},
+
+		step: function step(isIncrease) {
+			//refresh value from display before trying to increment in case they have just been typing before clicking the nubbins
+			this.setValue(this.getDisplayValue());
+			var newVal;
+
+
+			if (isIncrease) {
+				newVal = this.options.value + this.options.step;
+			} else {
+				newVal = this.options.value - this.options.step;
+
+
+
+
+
+			}
+
+			newVal = newVal.toFixed(5);
+
+
+
+
+			this.setValue(newVal + this.unit);
+		},
+
+
+
+		getDisplayValue: function getDisplayValue() {
+			var inputValue = this.parseInput(this.$input.val());
+			var value = (!!inputValue) ? inputValue : this.options.value;
+			return value;
+		},
+
+
+
+
+
+
+
+		setDisplayValue: function setDisplayValue(value) {
+			this.$input.val(value);
+		},
+
+
+
+
+
+
+
+		getValue: function getValue() {
+			var val = this.options.value;
+			if (this.options.decimalMark !== '.'){
+				val = (val + '').split('.').join(this.options.decimalMark);
+
+
+			}
+			return val + this.unit;
+		},
+
+		setValue: function setValue(val) {
+			//remove any i18n on the number
+			if (this.options.decimalMark !== '.') {
+				val = this.parseInput(val);
+			}
+
+			//are we dealing with united numbers?
+			if(typeof val !== "number"){
+				var potentialUnit = val.replace(/[0-9.-]/g, '');
+				//make sure unit is valid, or else drop it in favor of current unit, or default unit (potentially nothing)
+				this.unit = _isUnitLegal(potentialUnit, this.options.units) ? potentialUnit : this.options.defaultUnit;
+			}
+
+
+
+
+			var intVal = this.getIntValue(val);
+
+
+
+
+
+
+			//make sure we are dealing with a number
+			if (isNaN(intVal) && !isFinite(intVal)) {
+				return this.setValue(this.options.value);
+			}
+
+
+
+
+			//conform
+			intVal = _applyLimits.call(this, intVal);
+
+
+			//cache the pure int value
+			this.options.value = intVal;
+
+
+
+
+
+			//prepare number for display
+
+			val = intVal + this.unit;
+
+
+
+
+			if (this.options.decimalMark !== '.'){
+				val = (val + '').split('.').join(this.options.decimalMark);
+			}
+
+			//display number
+			this.setDisplayValue(val);
+
+			return this;
+		},
+
+		value: function value(val) {
+			if (val || val === 0) {
+				return this.setValue(val);
+			} else {
+				return this.getValue();
+			}
+		},
+
+
+
+		parseInput: function parseInput(value) {
+			value = (value + '').split(this.options.decimalMark).join('.');
+
+
+
+
+
+
+			return value;
+		},
+
+		getIntValue: function getIntValue(value) {
+			//if they didn't pass in a number, try and get the number
+			value = (typeof value === "undefined") ? this.getValue() : value;
+			// if there still isn't a number, abort
+			if(typeof value === "undefined"){return;}
+
+
+
+
+
+
+
+
+
+			if (typeof value === 'string'){
+				value = this.parseInput(value);
+
+
+
+
+
+
+
+
+
+			}
+
+			value = parseFloat(value, 10);
+
+
+
+
+
+			return value;
+		},
+
+		disable: function disable() {
+			this.options.disabled = true;
+			this.$element.addClass('disabled');
+			this.$input.attr('disabled', '');
+			this.$element.find('button').addClass('disabled');
+		},
+
+		enable: function enable() {
+			this.options.disabled = false;
+			this.$element.removeClass('disabled');
+			this.$input.removeAttr('disabled');
+			this.$element.find('button').removeClass('disabled');
+		},
+
+		keydown: function keydown(event) {
+			var keyCode = event.keyCode;
+			if (keyCode === 38) {
+				this.step(true);
+			} else if (keyCode === 40) {
+				this.step(false);
+			} else if (keyCode === 13) {
+				this.change();
+			}
+		},
+
+		keyup: function keyup(event) {
+			var keyCode = event.keyCode;
+
+			if (keyCode === 38 || keyCode === 40) {
+				this.triggerChangedEvent();
+			}
+		},
+
+		bindMousewheelListeners: function bindMousewheelListeners() {
+			var inputEl = this.$input.get(0);
+			if (inputEl.addEventListener) {
+				//IE 9, Chrome, Safari, Opera
+				inputEl.addEventListener('mousewheel', $.proxy(this.mousewheelHandler, this), false);
+				// Firefox
+				inputEl.addEventListener('DOMMouseScroll', $.proxy(this.mousewheelHandler, this), false);
+			} else {
+				// IE <9
+				inputEl.attachEvent('onmousewheel', $.proxy(this.mousewheelHandler, this));
+			}
+		},
+
+		mousewheelHandler: function mousewheelHandler(event) {
+			if (!this.options.disabled) {
+				var e = window.event || event;// old IE support
+				var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
+				var self = this;
+
+				clearTimeout(this.mousewheelTimeout);
+				this.mousewheelTimeout = setTimeout(function () {
+					self.triggerChangedEvent();
+				}, 300);
+
+				if (delta > 0) {//ACE
+					this.step(true);
+				} else {
+					this.step(false);
+				}
+
+				if (e.preventDefault) {
+					e.preventDefault();
+				} else {
+					e.returnValue = false;
+				}
+
+				return false;
+			}
+		}
+	};
+
+
+	// SPINBOX PLUGIN DEFINITION
+
+	$.fn.spinbox = function spinbox(option) {
+		var args = Array.prototype.slice.call(arguments, 1);
+		var methodReturn;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('fu.spinbox');
+			var options = typeof option === 'object' && option;
+
+			if (!data) {
+				$this.data('fu.spinbox', (data = new Spinbox(this, options)));
+			}
+
+			if (typeof option === 'string') {
+				methodReturn = data[option].apply(data, args);
+			}
+		});
+
+		return (methodReturn === undefined) ? $set : methodReturn;
+	};
+
+	// value needs to be 0 for this.render();
+	$.fn.spinbox.defaults = {
+		value: 0,
+		min: 0,
+		max: 999,
+		step: 1,
+		hold: true,
+		speed: 'medium',
+		disabled: false,
+		cycle: false,
+		units: [],
+		decimalMark: '.',
+		defaultUnit: '',
+		limitToStep: false
+	};
+
+	$.fn.spinbox.Constructor = Spinbox;
+
+	$.fn.spinbox.noConflict = function noConflict() {
+		$.fn.spinbox = old;
+		return this;
+	};
+
+
+	// DATA-API
+
+	$(document).on('mousedown.fu.spinbox.data-api', '[data-initialize=spinbox]', function (e) {
+		var $control = $(e.target).closest('.spinbox');
+		if (!$control.data('fu.spinbox')) {
+			$control.spinbox($control.data());
+		}
+	});
+
+	// Must be domReady for AMD compatibility
+	$(function () {
+		$('[data-initialize=spinbox]').each(function () {
+			var $this = $(this);
+			if (!$this.data('fu.spinbox')) {
+				$this.spinbox($this.data());
+			}
+		});
+	});
+
+	// -- BEGIN UMD WRAPPER AFTERWORD --
+}));
+// -- END UMD WRAPPER AFTERWORD --

+ 604 - 0
src/main/webapp/static/ace/assets/js/fuelux/fuelux.tree.js

@@ -0,0 +1,604 @@
+/*
+ * Fuel UX Tree
+ * https://github.com/ExactTarget/fuelux
+ *
+ * Copyright (c) 2014 ExactTarget
+ * Licensed under the BSD New license.
+ */
+
+// -- BEGIN UMD WRAPPER PREFACE --
+
+// For more information on UMD visit:
+// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
+
+(function (factory) {
+	if (typeof define === 'function' && define.amd) {
+		// if AMD loader is available, register as an anonymous module.
+		define(['jquery'], factory);
+	} else if (typeof exports === 'object') {
+		// Node/CommonJS
+		module.exports = factory(require('jquery'));
+	} else {
+		// OR use browser globals if AMD is not present
+		factory(jQuery);
+	}
+}(function ($) {
+	// -- END UMD WRAPPER PREFACE --
+
+	// -- BEGIN MODULE CODE HERE --
+
+	var old = $.fn.tree;
+
+	// TREE CONSTRUCTOR AND PROTOTYPE
+
+	var Tree = function Tree(element, options) {
+		this.$element = $(element);
+		this.options = $.extend({}, $.fn.tree.defaults, options);
+
+		if (this.options.itemSelect) {
+			this.$element.on('click.fu.tree', '.tree-item', $.proxy(function (ev) {
+				this.selectItem(ev.currentTarget);
+			}, this));
+		}
+
+		//ACE
+		this.$element.on('click.fu.tree', '.tree-branch-header', $.proxy(function (ev) {
+			this.toggleFolder(ev.currentTarget);
+		}, this));
+
+		//ACE
+		// folderSelect default is true
+		if (this.options.folderSelect) {
+			this.$element.addClass('tree-folder-select');
+			this.$element.off('click.fu.tree', '.tree-branch-header');
+			this.$element.on('click.fu.tree', '.icon-caret', $.proxy(function (ev) {
+				this.toggleFolder($(ev.currentTarget).next());
+			}, this));
+			this.$element.on('click.fu.tree', '.tree-branch-header', $.proxy(function (ev) {
+				this.selectFolder($(ev.currentTarget));
+			}, this));
+		}
+
+		this.render();
+	};
+
+	Tree.prototype = {
+		constructor: Tree,
+
+		deselectAll: function deselectAll(nodes) {
+			// clear all child tree nodes and style as deselected
+			nodes = nodes || this.$element;
+			var $selectedElements = $(nodes).find('.tree-selected');
+			$selectedElements.each(function (index, element) {
+				//styleNodeDeselected( $(element), $(element).find( '.glyphicon' ) );
+				styleNodeDeselected( this, $(element), $(element).find('.'+$.trim(this.options['base-icon']).replace(/(\s+)/g, '.')) );//ACE
+			});
+			return $selectedElements;
+		},
+
+		destroy: function destroy() {
+			// any external bindings [none]
+			// empty elements to return to original markup
+			this.$element.find("li:not([data-template])").remove();
+
+			this.$element.remove();
+			// returns string of markup
+			return this.$element[0].outerHTML;
+		},
+
+		render: function render() {
+			this.populate(this.$element);
+		},
+
+		populate: function populate($el) {
+			var self = this;
+			var $parent = ($el.hasClass('tree')) ? $el : $el.parent();
+			var loader = $parent.find('.tree-loader:eq(0)');
+			var treeData = $parent.data();
+
+			loader.removeClass('hide hidden'); // hide is deprecated
+			this.options.dataSource(treeData ? treeData : {}, function (items) {
+				loader.addClass('hidden');
+
+				$.each(items.data, function (index, value) {
+					var $entity;
+
+					if (value.type === 'folder') {
+						$entity = self.$element.find('[data-template=treebranch]:eq(0)').clone().removeClass('hide hidden').removeData('template'); // hide is deprecated
+						$entity.data(value);
+						$entity.find('.tree-branch-name > .tree-label').html(value.text || value.name);
+						
+						//ACE
+						var header = $entity.find('.tree-branch-header');
+
+						if('icon-class' in value)
+							header.find('i').addClass(value['icon-class']);
+						
+						if('additionalParameters' in value
+							&& 'item-selected' in value.additionalParameters 
+								&& value.additionalParameters['item-selected'] == true) {
+								setTimeout(function(){header.trigger('click')}, 0);
+							}
+							
+					} else if (value.type === 'item') {
+						$entity = self.$element.find('[data-template=treeitem]:eq(0)').clone().removeClass('hide hidden').removeData('template'); // hide is deprecated
+						$entity.find('.tree-item-name > .tree-label').html(value.text || value.name);
+						$entity.data(value);
+						
+						//ACE
+						if('additionalParameters' in value
+							&& 'item-selected' in value.additionalParameters 
+								&& value.additionalParameters['item-selected'] == true) {
+								$entity.addClass ('tree-selected');
+								$entity.find('i').removeClass(self.options['unselected-icon']).addClass(self.options['selected-icon']);
+								//$entity.closest('.tree-folder-content').show();
+						}
+					}
+
+					// Decorate $entity with data or other attributes making the
+					// element easily accessable with libraries like jQuery.
+					//
+					// Values are contained within the object returned
+					// for folders and items as attr:
+					//
+					// {
+					//     text: "An Item",
+					//     type: 'item',
+					//     attr = {
+					//         'classes': 'required-item red-text',
+					//         'data-parent': parentId,
+					//         'guid': guid,
+					//         'id': guid
+					//     }
+					// };
+					//
+					// the "name" attribute is also supported but is deprecated for "text".
+
+					// add attributes to tree-branch or tree-item
+					var attr = value.attr || value.dataAttributes || [];
+					$.each(attr, function (key, value) {
+						switch (key) {
+							case 'cssClass':
+							case 'class':
+							case 'className':
+								$entity.addClass(value);
+								break;
+
+							// allow custom icons
+							case 'data-icon':
+								$entity.find('.icon-item').removeClass().addClass('icon-item ' + value);
+								$entity.attr(key, value);
+								break;
+
+							// ARIA support
+							case 'id':
+								$entity.attr(key, value);
+								$entity.attr('aria-labelledby', value + '-label');
+								$entity.find('.tree-branch-name > .tree-label').attr('id', value + '-label');
+								break;
+
+							// style, data-*
+							default:
+								$entity.attr(key, value);
+								break;
+						}
+					});
+
+					// add child nodes
+					if ($el.hasClass('tree-branch-header')) {
+						$parent.find('.tree-branch-children:eq(0)').append($entity);
+					} else {
+						$el.append($entity);
+					}
+				});
+
+				// return newly populated folder
+				self.$element.trigger('loaded.fu.tree', $parent);
+			});
+		},
+
+		selectTreeNode: function selectItem(clickedElement, nodeType) {
+			var clicked = {};	// object for clicked element
+			clicked.$element = $(clickedElement);
+
+			var selected = {}; // object for selected elements
+			selected.$elements = this.$element.find('.tree-selected');
+			selected.dataForEvent = [];
+
+			// determine clicked element and it's icon
+			if (nodeType === 'folder') {
+				// make the clicked.$element the container branch
+				clicked.$element = clicked.$element.closest('.tree-branch');
+				clicked.$icon = clicked.$element.find('.icon-folder');
+			}
+			else {
+				clicked.$icon = clicked.$element.find('.icon-item');
+			}
+			clicked.elementData = clicked.$element.data();
+
+			// the below functions pass objects by copy/reference and use modified object in this function
+			if ( this.options.multiSelect ) {
+				multiSelectSyncNodes(this, clicked, selected);
+			}
+			else {
+				singleSelectSyncNodes(this, clicked, selected);
+			}
+
+			// all done with the DOM, now fire events
+			this.$element.trigger(selected.eventType + '.fu.tree', {
+				target: clicked.elementData,
+				selected: selected.dataForEvent
+			});
+
+			clicked.$element.trigger('updated.fu.tree', {
+				selected: selected.dataForEvent,
+				item: clicked.$element,
+				eventType: selected.eventType
+			});
+		},
+
+		discloseFolder: function discloseFolder(el) {
+			var $el = $(el);
+
+			var $branch = $el.closest('.tree-branch');
+			var $treeFolderContent = $branch.find('.tree-branch-children');
+			var $treeFolderContentFirstChild = $treeFolderContent.eq(0);
+
+			//take care of the styles
+			$branch.addClass('tree-open');
+			$branch.attr('aria-expanded', 'true');
+			$treeFolderContentFirstChild.removeClass('hide hidden'); // hide is deprecated
+			$branch.find('> .tree-branch-header .icon-folder').eq(0)
+				//.removeClass('glyphicon-folder-close')
+				//.addClass('glyphicon-folder-open');
+				.removeClass(this.options['close-icon']).addClass(this.options['open-icon']);//ACE
+				
+			$branch.find('> .icon-caret').eq(0)
+				.removeClass(this.options['folder-open-icon']).addClass(this.options['folder-close-icon']);//ACE
+
+			//add the children to the folder
+			if (!$treeFolderContent.children().length) {
+				this.populate($treeFolderContent);
+			}
+
+			this.$element.trigger('disclosedFolder.fu.tree', $branch.data());
+		},
+
+		closeFolder: function closeFolder(el) {
+			var $el = $(el);
+			var $branch = $el.closest('.tree-branch');
+			var $treeFolderContent = $branch.find('.tree-branch-children');
+			var $treeFolderContentFirstChild = $treeFolderContent.eq(0);
+
+			//take care of the styles
+			$branch.removeClass('tree-open');
+			$branch.attr('aria-expanded', 'false');
+			$treeFolderContentFirstChild.addClass('hidden');
+			$branch.find('> .tree-branch-header .icon-folder').eq(0)
+				//.removeClass('glyphicon-folder-open')
+				//.addClass('glyphicon-folder-close');
+				.removeClass(this.options['open-icon']).addClass(this.options['close-icon']);//ACE
+				
+			$branch.find('> .icon-caret').eq(0)
+				.removeClass(this.options['folder-close-icon']).addClass(this.options['folder-open-icon']);//ACE
+
+			// remove chidren if no cache
+			if (!this.options.cacheItems) {
+				$treeFolderContentFirstChild.empty();
+			}
+
+			this.$element.trigger('closed.fu.tree', $branch.data());
+		},
+
+		toggleFolder: function toggleFolder(el) {
+			var $el = $(el);
+
+			/**
+			if ($el.find('.glyphicon-folder-close').length) {
+				this.discloseFolder(el);
+			} else if ($el.find('.glyphicon-folder-open').length) {
+				this.closeFolder(el);
+			}
+			*/
+			if ($el.find('.'+$.trim(this.options['close-icon']).replace(/(\s+)/g, '.')).length) {//ACE
+				this.discloseFolder(el);
+			} else if($el.find('.'+$.trim(this.options['open-icon']).replace(/(\s+)/g, '.')).length) {//ACE
+				this.closeFolder(el);
+			}
+		},
+
+		selectFolder: function selectFolder(el) {
+			if (this.options.folderSelect) {
+				this.selectTreeNode(el, 'folder');
+			}
+		},
+
+		selectItem: function selectItem(el) {
+			if (this.options.itemSelect) {
+				this.selectTreeNode(el, 'item');
+			}
+		},
+
+		selectedItems: function selectedItems() {
+			var $sel = this.$element.find('.tree-selected');
+			var data = [];
+
+			$.each($sel, function (index, value) {
+				data.push($(value).data());
+			});
+			return data;
+		},
+
+		// collapses open folders
+		collapse: function collapse() {
+			var self = this;
+			var reportedClosed = [];
+
+			var closedReported = function closedReported(event, closed) {
+				reportedClosed.push(closed);
+
+				// hide is deprecated
+				if (self.$element.find(".tree-branch.tree-open:not('.hidden, .hide')").length === 0) {
+					self.$element.trigger('closedAll.fu.tree', {
+						tree: self.$element,
+						reportedClosed: reportedClosed
+					});
+					self.$element.off('loaded.fu.tree', self.$element, closedReported);
+				}
+			};
+
+			//trigger callback when all folders have reported closed
+			self.$element.on('closed.fu.tree', closedReported);
+
+			self.$element.find(".tree-branch.tree-open:not('.hidden, .hide')").each(function () {
+				self.closeFolder(this);
+			});
+		},
+
+		//disclose visible will only disclose visible tree folders
+		discloseVisible: function discloseVisible() {
+			var self = this;
+
+			var $openableFolders = self.$element.find(".tree-branch:not('.tree-open, .hidden, .hide')");
+			var reportedOpened = [];
+
+			var openReported = function openReported(event, opened) {
+				reportedOpened.push(opened);
+
+				if (reportedOpened.length === $openableFolders.length) {
+					self.$element.trigger('disclosedVisible.fu.tree', {
+						tree: self.$element,
+						reportedOpened: reportedOpened
+					});
+					/*
+					* Unbind the `openReported` event. `discloseAll` may be running and we want to reset this
+					* method for the next iteration.
+					*/
+					self.$element.off('loaded.fu.tree', self.$element, openReported);
+				}
+			};
+
+			//trigger callback when all folders have reported opened
+			self.$element.on('loaded.fu.tree', openReported);
+
+			// open all visible folders
+			self.$element.find(".tree-branch:not('.tree-open, .hidden, .hide')").each(function triggerOpen() {
+				self.discloseFolder($(this).find('.tree-branch-header'));
+			});
+		},
+
+		/**
+		* Disclose all will keep listening for `loaded.fu.tree` and if `$(tree-el).data('ignore-disclosures-limit')`
+		* is `true` (defaults to `true`) it will attempt to disclose any new closed folders than were
+		* loaded in during the last disclosure.
+		*/
+		discloseAll: function discloseAll() {
+			var self = this;
+
+			//first time
+			if (typeof self.$element.data('disclosures') === 'undefined') {
+				self.$element.data('disclosures', 0);
+			}
+
+			var isExceededLimit = (self.options.disclosuresUpperLimit >= 1 && self.$element.data('disclosures') >= self.options.disclosuresUpperLimit);
+			var isAllDisclosed = self.$element.find(".tree-branch:not('.tree-open, .hidden, .hide')").length === 0;
+
+
+			if (!isAllDisclosed) {
+				if (isExceededLimit) {
+					self.$element.trigger('exceededDisclosuresLimit.fu.tree', {
+						tree: self.$element,
+						disclosures: self.$element.data('disclosures')
+					});
+
+					/*
+					* If you've exceeded the limit, the loop will be killed unless you
+					* explicitly ignore the limit and start the loop again:
+					*
+					*    $tree.one('exceededDisclosuresLimit.fu.tree', function () {
+					*        $tree.data('ignore-disclosures-limit', true);
+					*        $tree.tree('discloseAll');
+					*    });
+					*/
+					if (!self.$element.data('ignore-disclosures-limit')) {
+						return;
+					}
+
+				}
+
+				self.$element.data('disclosures', self.$element.data('disclosures') + 1);
+
+				/*
+				* A new branch that is closed might be loaded in, make sure those get handled too.
+				* This attachment needs to occur before calling `discloseVisible` to make sure that
+				* if the execution of `discloseVisible` happens _super fast_ (as it does in our QUnit tests
+				* this will still be called. However, make sure this only gets called _once_, because
+				* otherwise, every single time we go through this loop, _another_ event will be bound
+				* and then when the trigger happens, this will fire N times, where N equals the number
+				* of recursive `discloseAll` executions (instead of just one)
+				*/
+				self.$element.one('disclosedVisible.fu.tree', function () {
+					self.discloseAll();
+				});
+
+				/*
+				* If the page is very fast, calling this first will cause `disclosedVisible.fu.tree` to not
+				* be bound in time to be called, so, we need to call this last so that the things bound
+				* and triggered above can have time to take place before the next execution of the
+				* `discloseAll` method.
+				*/
+				self.discloseVisible();
+			} else {
+				self.$element.trigger('disclosedAll.fu.tree', {
+					tree: self.$element,
+					disclosures: self.$element.data('disclosures')
+				});
+
+				//if `cacheItems` is false, and they call closeAll, the data is trashed and therefore
+				//disclosures needs to accurately reflect that
+				if (!self.options.cacheItems) {
+					self.$element.one('closeAll.fu.tree', function () {
+						self.$element.data('disclosures', 0);
+					});
+				}
+
+			}
+		}
+	};
+
+
+	// ALIASES
+
+	//alias for collapse for consistency. "Collapse" is an ambiguous term (collapse what? All? One specific branch?)
+	Tree.prototype.closeAll = Tree.prototype.collapse;
+	//alias for backwards compatibility because there's no reason not to.
+	Tree.prototype.openFolder = Tree.prototype.discloseFolder;
+	//For library consistency
+	Tree.prototype.getValue = Tree.prototype.selectedItems;
+
+	// PRIVATE FUNCTIONS
+
+	function styleNodeSelected (self, $element, $icon) {
+		$element.addClass('tree-selected');
+		if ( $element.data('type') === 'item' && $icon.hasClass(self.options['unselected-icon']) ) {
+			//$icon.removeClass('fueluxicon-bullet').addClass('glyphicon-ok'); // make checkmark
+			$icon.removeClass(self.options['unselected-icon']).addClass(self.options['selected-icon']); //ACE
+		}
+	}
+
+	function styleNodeDeselected (self, $element, $icon) {
+		$element.removeClass('tree-selected');
+		//if ( $element.data('type') === 'item' && $icon.hasClass('glyphicon-ok') ) {
+			//$icon.removeClass('glyphicon-ok').addClass('fueluxicon-bullet'); // make bullet
+		//}
+		//ACE
+		if ( $element.data('type') === 'item' && $icon.hasClass(self.options['selected-icon']) ) {
+			$icon.removeClass(self.options['selected-icon']).addClass(self.options['unselected-icon']); // make bullet
+		}
+	}
+
+	function multiSelectSyncNodes (self, clicked, selected) {
+		// search for currently selected and add to selected data list if needed
+		$.each(selected.$elements, function (index, element) {
+			var $element = $(element);
+			if ($element[0] !== clicked.$element[0]) {
+				selected.dataForEvent.push( $($element).data() );
+			}
+		});
+
+		if (clicked.$element.hasClass('tree-selected')) {
+			styleNodeDeselected (self, clicked.$element, clicked.$icon);//ACE
+			// set event data
+			selected.eventType = 'deselected';
+		}
+		else {
+			styleNodeSelected(self, clicked.$element, clicked.$icon);//ACE
+			// set event data
+			selected.eventType = 'selected';
+			selected.dataForEvent.push(clicked.elementData);
+		}
+	}
+
+	function singleSelectSyncNodes(self, clicked, selected) {
+		// element is not currently selected
+		if (selected.$elements[0] !== clicked.$element[0]) {
+			var clearedElements = self.deselectAll(self.$element);
+			styleNodeSelected(self, clicked.$element, clicked.$icon);//ACE
+			// set event data
+			selected.eventType = 'selected';
+			selected.dataForEvent = [clicked.elementData];
+		}
+		else {
+			styleNodeDeselected(self, clicked.$element, clicked.$icon);//ACE
+			// set event data
+			selected.eventType = 'deselected';
+			selected.dataForEvent = [];
+		}
+	}
+
+
+	// TREE PLUGIN DEFINITION
+
+	$.fn.tree = function tree(option) {
+		var args = Array.prototype.slice.call(arguments, 1);
+		var methodReturn;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('fu.tree');
+			var options = typeof option === 'object' && option;
+
+			if (!data) {
+				$this.data('fu.tree', (data = new Tree(this, options)));
+			}
+
+			if (typeof option === 'string') {
+				methodReturn = data[option].apply(data, args);
+			}
+		});
+
+		return (methodReturn === undefined) ? $set : methodReturn;
+	};
+
+	$.fn.tree.defaults = {
+		dataSource: function dataSource(options, callback) {},
+		multiSelect: false,
+		cacheItems: true,
+		folderSelect: true,
+		itemSelect: true,
+		/*
+		* How many times `discloseAll` should be called before a stopping and firing
+		* an `exceededDisclosuresLimit` event. You can force it to continue by
+		* listening for this event, setting `ignore-disclosures-limit` to `true` and
+		* starting `discloseAll` back up again. This lets you make more decisions
+		* about if/when/how/why/how many times `discloseAll` will be started back
+		* up after it exceeds the limit.
+		*
+		*    $tree.one('exceededDisclosuresLimit.fu.tree', function () {
+		*        $tree.data('ignore-disclosures-limit', true);
+		*        $tree.tree('discloseAll');
+		*    });
+		*
+		* `disclusuresUpperLimit` defaults to `0`, so by default this trigger
+		* will never fire. The true hard the upper limit is the browser's
+		* ability to load new items (i.e. it will keep loading until the browser
+		* falls over and dies). On the Fuel UX `index.html` page, the point at
+		* which the page became super slow (enough to seem almost unresponsive)
+		* was `4`, meaning 256 folders had been opened, and 1024 were attempting to open.
+		*/
+		disclosuresUpperLimit: 0
+	};
+
+	$.fn.tree.Constructor = Tree;
+
+	$.fn.tree.noConflict = function () {
+		$.fn.tree = old;
+		return this;
+	};
+
+
+	// NO DATA-API DUE TO NEED OF DATA-SOURCE
+
+	// -- BEGIN UMD WRAPPER AFTERWORD --
+}));
+// -- END UMD WRAPPER AFTERWORD --

+ 463 - 0
src/main/webapp/static/ace/assets/js/fuelux/fuelux.wizard.js

@@ -0,0 +1,463 @@
+/*
+ * Fuel UX Wizard
+ * https://github.com/ExactTarget/fuelux
+ *
+ * Copyright (c) 2014 ExactTarget
+ * Licensed under the BSD New license.
+ */
+
+// -- BEGIN UMD WRAPPER PREFACE --
+
+// For more information on UMD visit:
+// https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
+
+(function (factory) {
+	if (typeof define === 'function' && define.amd) {
+		// if AMD loader is available, register as an anonymous module.
+		define(['jquery'], factory);
+	} else if (typeof exports === 'object') {
+		// Node/CommonJS
+		module.exports = factory(require('jquery'));
+	} else {
+		// OR use browser globals if AMD is not present
+		factory(jQuery);
+	}
+}(function ($) {
+	// -- END UMD WRAPPER PREFACE --
+
+	// -- BEGIN MODULE CODE HERE --
+
+	var old = $.fn.wizard;
+
+	// WIZARD CONSTRUCTOR AND PROTOTYPE
+
+	var Wizard = function (element, options) {
+		var kids;
+
+		this.$element = $(element);
+		this.options = $.extend({}, $.fn.wizard.defaults, options);
+		this.options.disablePreviousStep = (this.$element.attr('data-restrict') === 'previous') ? true : this.options.disablePreviousStep;
+		this.currentStep = this.options.selectedItem.step;
+		this.numSteps = this.$element.find('.steps li').length;
+		this.$prevBtn = this.$element.find('button.btn-prev');
+		this.$nextBtn = this.$element.find('button.btn-next');
+
+		// maintains backwards compatibility with < 3.8, will be removed in the future
+		if (this.$element.children('.steps-container').length === 0) {
+			this.$element.addClass('no-steps-container');
+			if (window && window.console && window.console.warn) {
+				window.console.warn('please update your wizard markup to include ".steps-container" as seen in http://getfuelux.com/javascript.html#wizard-usage-markup');
+			}
+		}
+
+		kids = this.$nextBtn.children().detach();
+		this.nextText = $.trim(this.$nextBtn.text());
+		this.$nextBtn.append(kids);
+
+		// handle events
+		this.$prevBtn.on('click.fu.wizard', $.proxy(this.previous, this));
+		this.$nextBtn.on('click.fu.wizard', $.proxy(this.next, this));
+		this.$element.on('click.fu.wizard', 'li.complete', $.proxy(this.stepclicked, this));
+
+		this.selectedItem(this.options.selectedItem);
+
+		if (this.options.disablePreviousStep) {
+			this.$prevBtn.attr('disabled', true);
+			this.$element.find('.steps').addClass('previous-disabled');
+		}
+	};
+
+	Wizard.prototype = {
+
+		constructor: Wizard,
+
+		destroy: function () {
+			this.$element.remove();
+			// any external bindings [none]
+			// empty elements to return to original markup [none]
+			// returns string of markup
+			return this.$element[0].outerHTML;
+		},
+
+		//index is 1 based
+		//second parameter can be array of objects [{ ... }, { ... }] or you can pass n additional objects as args
+		//object structure is as follows (all params are optional): { badge: '', label: '', pane: '' }
+		addSteps: function (index) {
+			var items = [].slice.call(arguments).slice(1);
+			var $steps = this.$element.find('.steps');
+			var $stepContent = this.$element.find('.step-content');
+			var i, l, $pane, $startPane, $startStep, $step;
+
+			index = (index === -1 || (index > (this.numSteps + 1))) ? this.numSteps + 1 : index;
+			if (items[0] instanceof Array) {
+				items = items[0];
+			}
+
+			$startStep = $steps.find('li:nth-child(' + index + ')');
+			$startPane = $stepContent.find('.step-pane:nth-child(' + index + ')');
+			if ($startStep.length < 1) {
+				$startStep = null;
+			}
+
+			for (i = 0, l = items.length; i < l; i++) {
+				$step = $('<li data-step="' + index + '"><span class="badge badge-info"></span></li>');
+				$step.append(items[i].label || '').append('<span class="chevron"></span>');
+				$step.find('.badge').append(items[i].badge || index);
+
+				$pane = $('<div class="step-pane" data-step="' + index + '"></div>');
+				$pane.append(items[i].pane || '');
+
+				if (!$startStep) {
+					$steps.append($step);
+					$stepContent.append($pane);
+				} else {
+					$startStep.before($step);
+					$startPane.before($pane);
+				}
+
+				index++;
+			}
+
+			this.syncSteps();
+			this.numSteps = $steps.find('li').length;
+			this.setState();
+		},
+
+		//index is 1 based, howMany is number to remove
+		removeSteps: function (index, howMany) {
+			var action = 'nextAll';
+			var i = 0;
+			var $steps = this.$element.find('.steps');
+			var $stepContent = this.$element.find('.step-content');
+			var $start;
+
+			howMany = (howMany !== undefined) ? howMany : 1;
+
+			if (index > $steps.find('li').length) {
+				$start = $steps.find('li:last');
+			} else {
+				$start = $steps.find('li:nth-child(' + index + ')').prev();
+				if ($start.length < 1) {
+					action = 'children';
+					$start = $steps;
+				}
+
+			}
+
+			$start[action]().each(function () {
+				var item = $(this);
+				var step = item.attr('data-step');
+				if (i < howMany) {
+					item.remove();
+					$stepContent.find('.step-pane[data-step="' + step + '"]:first').remove();
+				} else {
+					return false;
+				}
+
+				i++;
+			});
+
+			this.syncSteps();
+			this.numSteps = $steps.find('li').length;
+			this.setState();
+		},
+
+		setState: function () {
+			var canMovePrev = (this.currentStep > 1);//remember, steps index is 1 based...
+			var isFirstStep = (this.currentStep === 1);
+			var isLastStep = (this.currentStep === this.numSteps);
+
+			// disable buttons based on current step
+			if (!this.options.disablePreviousStep) {
+				this.$prevBtn.attr('disabled', (isFirstStep === true || canMovePrev === false));
+			}
+
+			// change button text of last step, if specified
+			var last = this.$nextBtn.attr('data-last');
+			if (last) {
+				this.lastText = last;
+				// replace text
+				var text = this.nextText;
+				if (isLastStep === true) {
+					text = this.lastText;
+					// add status class to wizard
+					this.$element.addClass('complete');
+				} else {
+					this.$element.removeClass('complete');
+				}
+
+				var kids = this.$nextBtn.children().detach();
+				this.$nextBtn.text(text).append(kids);
+			}
+
+			// reset classes for all steps
+			var $steps = this.$element.find('.steps li');
+			$steps.removeClass('active').removeClass('complete');
+			$steps.find('span.badge').removeClass('badge-info').removeClass('badge-success');
+
+			// set class for all previous steps
+			var prevSelector = '.steps li:lt(' + (this.currentStep - 1) + ')';
+			var $prevSteps = this.$element.find(prevSelector);
+			$prevSteps.addClass('complete');
+			$prevSteps.find('span.badge').addClass('badge-success');
+
+			// set class for current step
+			var currentSelector = '.steps li:eq(' + (this.currentStep - 1) + ')';
+			var $currentStep = this.$element.find(currentSelector);
+			$currentStep.addClass('active');
+			$currentStep.find('span.badge').addClass('badge-info');
+
+			// set display of target element
+			var $stepContent = this.$element.find('.step-content');
+			var target = $currentStep.attr('data-step');
+			$stepContent.find('.step-pane').removeClass('active');
+			$stepContent.find('.step-pane[data-step="' + target + '"]:first').addClass('active');
+
+			
+			//ACE
+			/**
+			// reset the wizard position to the left
+			this.$element.find('.steps').first().attr('style', 'margin-left: 0');
+
+			// check if the steps are wider than the container div
+			var totalWidth = 0;
+			this.$element.find('.steps > li').each(function () {
+				totalWidth += $(this).outerWidth();
+			});
+			var containerWidth = 0;
+			if (this.$element.find('.actions').length) {
+				containerWidth = this.$element.width() - this.$element.find('.actions').first().outerWidth();
+			} else {
+				containerWidth = this.$element.width();
+			}
+
+			if (totalWidth > containerWidth) {
+				// set the position so that the last step is on the right
+				var newMargin = totalWidth - containerWidth;
+				this.$element.find('.steps').first().attr('style', 'margin-left: -' + newMargin + 'px');
+
+				// set the position so that the active step is in a good
+				// position if it has been moved out of view
+				if (this.$element.find('li.active').first().position().left < 200) {
+					newMargin += this.$element.find('li.active').first().position().left - 200;
+					if (newMargin < 1) {
+						this.$element.find('.steps').first().attr('style', 'margin-left: 0');
+					} else {
+						this.$element.find('.steps').first().attr('style', 'margin-left: -' + newMargin + 'px');
+					}
+
+				}
+
+			}
+			*/
+
+			// only fire changed event after initializing
+			if (typeof (this.initialized) !== 'undefined') {
+				var e = $.Event('changed.fu.wizard');
+				this.$element.trigger(e, {
+					step: this.currentStep
+				});
+			}
+
+			this.initialized = true;
+		},
+
+		stepclicked: function (e) {
+			var li = $(e.currentTarget);
+			var index = this.$element.find('.steps li').index(li);
+
+			if (index < this.currentStep && this.options.disablePreviousStep) {//enforce restrictions
+				return;
+			} else {
+				var evt = $.Event('stepclicked.fu.wizard');
+				this.$element.trigger(evt, {
+					step: index + 1
+				});
+				if (evt.isDefaultPrevented()) {
+					return;
+				}
+
+				this.currentStep = (index + 1);
+				this.setState();
+			}
+		},
+
+		syncSteps: function () {
+			var i = 1;
+			var $steps = this.$element.find('.steps');
+			var $stepContent = this.$element.find('.step-content');
+
+			$steps.children().each(function () {
+				var item = $(this);
+				var badge = item.find('.badge');
+				var step = item.attr('data-step');
+
+				if (!isNaN(parseInt(badge.html(), 10))) {
+					badge.html(i);
+				}
+
+				item.attr('data-step', i);
+				$stepContent.find('.step-pane[data-step="' + step + '"]:last').attr('data-step', i);
+				i++;
+			});
+		},
+
+		previous: function () {
+			if (this.options.disablePreviousStep || this.currentStep === 1) {
+				return;
+			}
+
+			var e = $.Event('actionclicked.fu.wizard');
+			this.$element.trigger(e, {
+				step: this.currentStep,
+				direction: 'previous'
+			});
+			if (e.isDefaultPrevented()) {
+				return;
+			}// don't increment ...what? Why?
+
+			this.currentStep -= 1;
+			this.setState();
+
+			// only set focus if focus is still on the $nextBtn (avoid stomping on a focus set programmatically in actionclicked callback)
+			if (this.$prevBtn.is(':focus')) {
+				var firstFormField = this.$element.find('.active').find('input, select, textarea')[0];
+
+				if (typeof firstFormField !== 'undefined') {
+					// allow user to start typing immediately instead of having to click on the form field.
+					$(firstFormField).focus();
+				} else if (this.$element.find('.active input:first').length === 0 && this.$prevBtn.is(':disabled')) {
+					//only set focus on a button as the last resort if no form fields exist and the just clicked button is now disabled
+					this.$nextBtn.focus();
+				}
+
+			}
+		},
+
+		next: function () {
+			var e = $.Event('actionclicked.fu.wizard');
+			this.$element.trigger(e, {
+				step: this.currentStep,
+				direction: 'next'
+			});
+			if (e.isDefaultPrevented()) {
+				return;
+			}// respect preventDefault in case dev has attached validation to step and wants to stop propagation based on it.
+
+			if (this.currentStep < this.numSteps) {
+				this.currentStep += 1;
+				this.setState();
+			} else {//is last step
+				this.$element.trigger('finished.fu.wizard');
+			}
+
+			// only set focus if focus is still on the $nextBtn (avoid stomping on a focus set programmatically in actionclicked callback)
+			if (this.$nextBtn.is(':focus')) {
+				var firstFormField = this.$element.find('.active').find('input, select, textarea')[0];
+
+				if (typeof firstFormField !== 'undefined') {
+					// allow user to start typing immediately instead of having to click on the form field.
+					$(firstFormField).focus();
+				} else if (this.$element.find('.active input:first').length === 0 && this.$nextBtn.is(':disabled')) {
+					//only set focus on a button as the last resort if no form fields exist and the just clicked button is now disabled
+					this.$prevBtn.focus();
+				}
+
+			}
+		},
+
+		selectedItem: function (selectedItem) {
+			var retVal, step;
+
+			if (selectedItem) {
+				step = selectedItem.step || -1;
+				//allow selection of step by data-name
+				step = Number(this.$element.find('.steps li[data-name="' + step + '"]').first().attr('data-step')) || Number(step);
+
+				if (1 <= step && step <= this.numSteps) {
+					this.currentStep = step;
+					this.setState();
+				} else {
+					step = this.$element.find('.steps li.active:first').attr('data-step');
+					if (!isNaN(step)) {
+						this.currentStep = parseInt(step, 10);
+						this.setState();
+					}
+
+				}
+
+				retVal = this;
+			} else {
+				retVal = {
+					step: this.currentStep
+				};
+				if (this.$element.find('.steps li.active:first[data-name]').length) {
+					retVal.stepname = this.$element.find('.steps li.active:first').attr('data-name');
+				}
+
+			}
+
+			return retVal;
+		}
+	};
+
+
+	// WIZARD PLUGIN DEFINITION
+
+	$.fn.wizard = function (option) {
+		var args = Array.prototype.slice.call(arguments, 1);
+		var methodReturn;
+
+		var $set = this.each(function () {
+			var $this = $(this);
+			var data = $this.data('fu.wizard');
+			var options = typeof option === 'object' && option;
+
+			if (!data) {
+				$this.data('fu.wizard', (data = new Wizard(this, options)));
+			}
+
+			if (typeof option === 'string') {
+				methodReturn = data[option].apply(data, args);
+			}
+		});
+
+		return (methodReturn === undefined) ? $set : methodReturn;
+	};
+
+	$.fn.wizard.defaults = {
+		disablePreviousStep: false,
+		selectedItem: {
+			step: -1
+		}//-1 means it will attempt to look for "active" class in order to set the step
+	};
+
+	$.fn.wizard.Constructor = Wizard;
+
+	$.fn.wizard.noConflict = function () {
+		$.fn.wizard = old;
+		return this;
+	};
+
+
+	// DATA-API
+
+	$(document).on('mouseover.fu.wizard.data-api', '[data-initialize=wizard]', function (e) {
+		var $control = $(e.target).closest('.wizard');
+		if (!$control.data('fu.wizard')) {
+			$control.wizard($control.data());
+		}
+	});
+
+	// Must be domReady for AMD compatibility
+	$(function () {
+		$('[data-initialize=wizard]').each(function () {
+			var $this = $(this);
+			if ($this.data('fu.wizard')) return;
+			$this.wizard($this.data());
+		});
+	});
+
+	// -- BEGIN UMD WRAPPER AFTERWORD --
+}));
+// -- END UMD WRAPPER AFTERWORD --

文件差異過大導致無法顯示
+ 11170 - 0
src/main/webapp/static/ace/assets/js/fullcalendar.js


+ 524 - 0
src/main/webapp/static/ace/assets/js/html5shiv-printshiv.js

@@ -0,0 +1,524 @@
+/**
+* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+*/
+;(function(window, document) {
+/*jshint evil:true */
+  /** version */
+  var version = '3.7.3';
+
+  /** Preset options */
+  var options = window.html5 || {};
+
+  /** Used to skip problem elements */
+  var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
+
+  /** Not all elements can be cloned in IE **/
+  var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
+
+  /** Detect whether the browser supports default html5 styles */
+  var supportsHtml5Styles;
+
+  /** Name of the expando, to work with multiple documents or to re-shiv one document */
+  var expando = '_html5shiv';
+
+  /** The id for the the documents expando */
+  var expanID = 0;
+
+  /** Cached data for each document */
+  var expandoData = {};
+
+  /** Detect whether the browser supports unknown elements */
+  var supportsUnknownElements;
+
+  (function() {
+    try {
+        var a = document.createElement('a');
+        a.innerHTML = '<xyz></xyz>';
+        //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
+        supportsHtml5Styles = ('hidden' in a);
+
+        supportsUnknownElements = a.childNodes.length == 1 || (function() {
+          // assign a false positive if unable to shiv
+          (document.createElement)('a');
+          var frag = document.createDocumentFragment();
+          return (
+            typeof frag.cloneNode == 'undefined' ||
+            typeof frag.createDocumentFragment == 'undefined' ||
+            typeof frag.createElement == 'undefined'
+          );
+        }());
+    } catch(e) {
+      // assign a false positive if detection fails => unable to shiv
+      supportsHtml5Styles = true;
+      supportsUnknownElements = true;
+    }
+
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a style sheet with the given CSS text and adds it to the document.
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @param {String} cssText The CSS text.
+   * @returns {StyleSheet} The style element.
+   */
+  function addStyleSheet(ownerDocument, cssText) {
+    var p = ownerDocument.createElement('p'),
+        parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+    p.innerHTML = 'x<style>' + cssText + '</style>';
+    return parent.insertBefore(p.lastChild, parent.firstChild);
+  }
+
+  /**
+   * Returns the value of `html5.elements` as an array.
+   * @private
+   * @returns {Array} An array of shived element node names.
+   */
+  function getElements() {
+    var elements = html5.elements;
+    return typeof elements == 'string' ? elements.split(' ') : elements;
+  }
+
+  /**
+   * Extends the built-in list of html5 elements
+   * @memberOf html5
+   * @param {String|Array} newElements whitespace separated list or array of new element names to shiv
+   * @param {Document} ownerDocument The context document.
+   */
+  function addElements(newElements, ownerDocument) {
+    var elements = html5.elements;
+    if(typeof elements != 'string'){
+      elements = elements.join(' ');
+    }
+    if(typeof newElements != 'string'){
+      newElements = newElements.join(' ');
+    }
+    html5.elements = elements +' '+ newElements;
+    shivDocument(ownerDocument);
+  }
+
+    /**
+   * Returns the data associated to the given document
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @returns {Object} An object of data.
+   */
+  function getExpandoData(ownerDocument) {
+    var data = expandoData[ownerDocument[expando]];
+    if (!data) {
+        data = {};
+        expanID++;
+        ownerDocument[expando] = expanID;
+        expandoData[expanID] = data;
+    }
+    return data;
+  }
+
+  /**
+   * returns a shived element for the given nodeName and document
+   * @memberOf html5
+   * @param {String} nodeName name of the element
+   * @param {Document} ownerDocument The context document.
+   * @returns {Object} The shived element.
+   */
+  function createElement(nodeName, ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createElement(nodeName);
+    }
+    if (!data) {
+        data = getExpandoData(ownerDocument);
+    }
+    var node;
+
+    if (data.cache[nodeName]) {
+        node = data.cache[nodeName].cloneNode();
+    } else if (saveClones.test(nodeName)) {
+        node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
+    } else {
+        node = data.createElem(nodeName);
+    }
+
+    // Avoid adding some elements to fragments in IE < 9 because
+    // * Attributes like `name` or `type` cannot be set/changed once an element
+    //   is inserted into a document/fragment
+    // * Link elements with `src` attributes that are inaccessible, as with
+    //   a 403 response, will cause the tab/window to crash
+    // * Script elements appended to fragments will execute when their `src`
+    //   or `text` property is set
+    return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
+  }
+
+  /**
+   * returns a shived DocumentFragment for the given document
+   * @memberOf html5
+   * @param {Document} ownerDocument The context document.
+   * @returns {Object} The shived DocumentFragment.
+   */
+  function createDocumentFragment(ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createDocumentFragment();
+    }
+    data = data || getExpandoData(ownerDocument);
+    var clone = data.frag.cloneNode(),
+        i = 0,
+        elems = getElements(),
+        l = elems.length;
+    for(;i<l;i++){
+        clone.createElement(elems[i]);
+    }
+    return clone;
+  }
+
+  /**
+   * Shivs the `createElement` and `createDocumentFragment` methods of the document.
+   * @private
+   * @param {Document|DocumentFragment} ownerDocument The document.
+   * @param {Object} data of the document.
+   */
+  function shivMethods(ownerDocument, data) {
+    if (!data.cache) {
+        data.cache = {};
+        data.createElem = ownerDocument.createElement;
+        data.createFrag = ownerDocument.createDocumentFragment;
+        data.frag = data.createFrag();
+    }
+
+
+    ownerDocument.createElement = function(nodeName) {
+      //abort shiv
+      if (!html5.shivMethods) {
+          return data.createElem(nodeName);
+      }
+      return createElement(nodeName, ownerDocument, data);
+    };
+
+    ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+      'var n=f.cloneNode(),c=n.createElement;' +
+      'h.shivMethods&&(' +
+        // unroll the `createElement` calls
+        getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
+          data.createElem(nodeName);
+          data.frag.createElement(nodeName);
+          return 'c("' + nodeName + '")';
+        }) +
+      ');return n}'
+    )(html5, data.frag);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Shivs the given document.
+   * @memberOf html5
+   * @param {Document} ownerDocument The document to shiv.
+   * @returns {Document} The shived document.
+   */
+  function shivDocument(ownerDocument) {
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    var data = getExpandoData(ownerDocument);
+
+    if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
+      data.hasCSS = !!addStyleSheet(ownerDocument,
+        // corrects block display not defined in IE6/7/8/9
+        'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
+        // adds styling not present in IE6/7/8/9
+        'mark{background:#FF0;color:#000}' +
+        // hides non-rendered elements
+        'template{display:none}'
+      );
+    }
+    if (!supportsUnknownElements) {
+      shivMethods(ownerDocument, data);
+    }
+    return ownerDocument;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The `html5` object is exposed so that more elements can be shived and
+   * existing shiving can be detected on iframes.
+   * @type Object
+   * @example
+   *
+   * // options can be changed before the script is included
+   * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
+   */
+  var html5 = {
+
+    /**
+     * An array or space separated string of node names of the elements to shiv.
+     * @memberOf html5
+     * @type Array|String
+     */
+    'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
+
+    /**
+     * current version of html5shiv
+     */
+    'version': version,
+
+    /**
+     * A flag to indicate that the HTML5 style sheet should be inserted.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivCSS': (options.shivCSS !== false),
+
+    /**
+     * Is equal to true if a browser supports creating unknown/HTML5 elements
+     * @memberOf html5
+     * @type boolean
+     */
+    'supportsUnknownElements': supportsUnknownElements,
+
+    /**
+     * A flag to indicate that the document's `createElement` and `createDocumentFragment`
+     * methods should be overwritten.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivMethods': (options.shivMethods !== false),
+
+    /**
+     * A string to describe the type of `html5` object ("default" or "default print").
+     * @memberOf html5
+     * @type String
+     */
+    'type': 'default',
+
+    // shivs the document according to the specified `html5` object options
+    'shivDocument': shivDocument,
+
+    //creates a shived element
+    createElement: createElement,
+
+    //creates a shived documentFragment
+    createDocumentFragment: createDocumentFragment,
+
+    //extends list of elements
+    addElements: addElements
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose html5
+  window.html5 = html5;
+
+  // shiv the document
+  shivDocument(document);
+
+  /*------------------------------- Print Shiv -------------------------------*/
+
+  /** Used to filter media types */
+  var reMedia = /^$|\b(?:all|print)\b/;
+
+  /** Used to namespace printable elements */
+  var shivNamespace = 'html5shiv';
+
+  /** Detect whether the browser supports shivable style sheets */
+  var supportsShivableSheets = !supportsUnknownElements && (function() {
+    // assign a false negative if unable to shiv
+    var docEl = document.documentElement;
+    return !(
+      typeof document.namespaces == 'undefined' ||
+      typeof document.parentWindow == 'undefined' ||
+      typeof docEl.applyElement == 'undefined' ||
+      typeof docEl.removeNode == 'undefined' ||
+      typeof window.attachEvent == 'undefined'
+    );
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Wraps all HTML5 elements in the given document with printable elements.
+   * (eg. the "header" element is wrapped with the "html5shiv:header" element)
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @returns {Array} An array wrappers added.
+   */
+  function addWrappers(ownerDocument) {
+    var node,
+        nodes = ownerDocument.getElementsByTagName('*'),
+        index = nodes.length,
+        reElements = RegExp('^(?:' + getElements().join('|') + ')$', 'i'),
+        result = [];
+
+    while (index--) {
+      node = nodes[index];
+      if (reElements.test(node.nodeName)) {
+        result.push(node.applyElement(createWrapper(node)));
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates a printable wrapper for the given element.
+   * @private
+   * @param {Element} element The element.
+   * @returns {Element} The wrapper.
+   */
+  function createWrapper(element) {
+    var node,
+        nodes = element.attributes,
+        index = nodes.length,
+        wrapper = element.ownerDocument.createElement(shivNamespace + ':' + element.nodeName);
+
+    // copy element attributes to the wrapper
+    while (index--) {
+      node = nodes[index];
+      node.specified && wrapper.setAttribute(node.nodeName, node.nodeValue);
+    }
+    // copy element styles to the wrapper
+    wrapper.style.cssText = element.style.cssText;
+    return wrapper;
+  }
+
+  /**
+   * Shivs the given CSS text.
+   * (eg. header{} becomes html5shiv\:header{})
+   * @private
+   * @param {String} cssText The CSS text to shiv.
+   * @returns {String} The shived CSS text.
+   */
+  function shivCssText(cssText) {
+    var pair,
+        parts = cssText.split('{'),
+        index = parts.length,
+        reElements = RegExp('(^|[\\s,>+~])(' + getElements().join('|') + ')(?=[[\\s,>+~#.:]|$)', 'gi'),
+        replacement = '$1' + shivNamespace + '\\:$2';
+
+    while (index--) {
+      pair = parts[index] = parts[index].split('}');
+      pair[pair.length - 1] = pair[pair.length - 1].replace(reElements, replacement);
+      parts[index] = pair.join('}');
+    }
+    return parts.join('{');
+  }
+
+  /**
+   * Removes the given wrappers, leaving the original elements.
+   * @private
+   * @params {Array} wrappers An array of printable wrappers.
+   */
+  function removeWrappers(wrappers) {
+    var index = wrappers.length;
+    while (index--) {
+      wrappers[index].removeNode();
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Shivs the given document for print.
+   * @memberOf html5
+   * @param {Document} ownerDocument The document to shiv.
+   * @returns {Document} The shived document.
+   */
+  function shivPrint(ownerDocument) {
+    var shivedSheet,
+        wrappers,
+        data = getExpandoData(ownerDocument),
+        namespaces = ownerDocument.namespaces,
+        ownerWindow = ownerDocument.parentWindow;
+
+    if (!supportsShivableSheets || ownerDocument.printShived) {
+      return ownerDocument;
+    }
+    if (typeof namespaces[shivNamespace] == 'undefined') {
+      namespaces.add(shivNamespace);
+    }
+
+    function removeSheet() {
+      clearTimeout(data._removeSheetTimer);
+      if (shivedSheet) {
+          shivedSheet.removeNode(true);
+      }
+      shivedSheet= null;
+    }
+
+    ownerWindow.attachEvent('onbeforeprint', function() {
+
+      removeSheet();
+
+      var imports,
+          length,
+          sheet,
+          collection = ownerDocument.styleSheets,
+          cssText = [],
+          index = collection.length,
+          sheets = Array(index);
+
+      // convert styleSheets collection to an array
+      while (index--) {
+        sheets[index] = collection[index];
+      }
+      // concat all style sheet CSS text
+      while ((sheet = sheets.pop())) {
+        // IE does not enforce a same origin policy for external style sheets...
+        // but has trouble with some dynamically created stylesheets
+        if (!sheet.disabled && reMedia.test(sheet.media)) {
+
+          try {
+            imports = sheet.imports;
+            length = imports.length;
+          } catch(er){
+            length = 0;
+          }
+
+          for (index = 0; index < length; index++) {
+            sheets.push(imports[index]);
+          }
+
+          try {
+            cssText.push(sheet.cssText);
+          } catch(er){}
+        }
+      }
+
+      // wrap all HTML5 elements with printable elements and add the shived style sheet
+      cssText = shivCssText(cssText.reverse().join(''));
+      wrappers = addWrappers(ownerDocument);
+      shivedSheet = addStyleSheet(ownerDocument, cssText);
+
+    });
+
+    ownerWindow.attachEvent('onafterprint', function() {
+      // remove wrappers, leaving the original elements, and remove the shived style sheet
+      removeWrappers(wrappers);
+      clearTimeout(data._removeSheetTimer);
+      data._removeSheetTimer = setTimeout(removeSheet, 500);
+    });
+
+    ownerDocument.printShived = true;
+    return ownerDocument;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose API
+  html5.type += ' print';
+  html5.shivPrint = shivPrint;
+
+  // shiv for print
+  shivPrint(document);
+
+  if(typeof module == 'object' && module.exports){
+    module.exports = html5;
+  }
+
+}(typeof window !== "undefined" ? window : this, document));

+ 326 - 0
src/main/webapp/static/ace/assets/js/html5shiv.js

@@ -0,0 +1,326 @@
+/**
+* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
+*/
+;(function(window, document) {
+/*jshint evil:true */
+  /** version */
+  var version = '3.7.3';
+
+  /** Preset options */
+  var options = window.html5 || {};
+
+  /** Used to skip problem elements */
+  var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i;
+
+  /** Not all elements can be cloned in IE **/
+  var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i;
+
+  /** Detect whether the browser supports default html5 styles */
+  var supportsHtml5Styles;
+
+  /** Name of the expando, to work with multiple documents or to re-shiv one document */
+  var expando = '_html5shiv';
+
+  /** The id for the the documents expando */
+  var expanID = 0;
+
+  /** Cached data for each document */
+  var expandoData = {};
+
+  /** Detect whether the browser supports unknown elements */
+  var supportsUnknownElements;
+
+  (function() {
+    try {
+        var a = document.createElement('a');
+        a.innerHTML = '<xyz></xyz>';
+        //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles
+        supportsHtml5Styles = ('hidden' in a);
+
+        supportsUnknownElements = a.childNodes.length == 1 || (function() {
+          // assign a false positive if unable to shiv
+          (document.createElement)('a');
+          var frag = document.createDocumentFragment();
+          return (
+            typeof frag.cloneNode == 'undefined' ||
+            typeof frag.createDocumentFragment == 'undefined' ||
+            typeof frag.createElement == 'undefined'
+          );
+        }());
+    } catch(e) {
+      // assign a false positive if detection fails => unable to shiv
+      supportsHtml5Styles = true;
+      supportsUnknownElements = true;
+    }
+
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a style sheet with the given CSS text and adds it to the document.
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @param {String} cssText The CSS text.
+   * @returns {StyleSheet} The style element.
+   */
+  function addStyleSheet(ownerDocument, cssText) {
+    var p = ownerDocument.createElement('p'),
+        parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+    p.innerHTML = 'x<style>' + cssText + '</style>';
+    return parent.insertBefore(p.lastChild, parent.firstChild);
+  }
+
+  /**
+   * Returns the value of `html5.elements` as an array.
+   * @private
+   * @returns {Array} An array of shived element node names.
+   */
+  function getElements() {
+    var elements = html5.elements;
+    return typeof elements == 'string' ? elements.split(' ') : elements;
+  }
+
+  /**
+   * Extends the built-in list of html5 elements
+   * @memberOf html5
+   * @param {String|Array} newElements whitespace separated list or array of new element names to shiv
+   * @param {Document} ownerDocument The context document.
+   */
+  function addElements(newElements, ownerDocument) {
+    var elements = html5.elements;
+    if(typeof elements != 'string'){
+      elements = elements.join(' ');
+    }
+    if(typeof newElements != 'string'){
+      newElements = newElements.join(' ');
+    }
+    html5.elements = elements +' '+ newElements;
+    shivDocument(ownerDocument);
+  }
+
+   /**
+   * Returns the data associated to the given document
+   * @private
+   * @param {Document} ownerDocument The document.
+   * @returns {Object} An object of data.
+   */
+  function getExpandoData(ownerDocument) {
+    var data = expandoData[ownerDocument[expando]];
+    if (!data) {
+        data = {};
+        expanID++;
+        ownerDocument[expando] = expanID;
+        expandoData[expanID] = data;
+    }
+    return data;
+  }
+
+  /**
+   * returns a shived element for the given nodeName and document
+   * @memberOf html5
+   * @param {String} nodeName name of the element
+   * @param {Document|DocumentFragment} ownerDocument The context document.
+   * @returns {Object} The shived element.
+   */
+  function createElement(nodeName, ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createElement(nodeName);
+    }
+    if (!data) {
+        data = getExpandoData(ownerDocument);
+    }
+    var node;
+
+    if (data.cache[nodeName]) {
+        node = data.cache[nodeName].cloneNode();
+    } else if (saveClones.test(nodeName)) {
+        node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode();
+    } else {
+        node = data.createElem(nodeName);
+    }
+
+    // Avoid adding some elements to fragments in IE < 9 because
+    // * Attributes like `name` or `type` cannot be set/changed once an element
+    //   is inserted into a document/fragment
+    // * Link elements with `src` attributes that are inaccessible, as with
+    //   a 403 response, will cause the tab/window to crash
+    // * Script elements appended to fragments will execute when their `src`
+    //   or `text` property is set
+    return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node;
+  }
+
+  /**
+   * returns a shived DocumentFragment for the given document
+   * @memberOf html5
+   * @param {Document} ownerDocument The context document.
+   * @returns {Object} The shived DocumentFragment.
+   */
+  function createDocumentFragment(ownerDocument, data){
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    if(supportsUnknownElements){
+        return ownerDocument.createDocumentFragment();
+    }
+    data = data || getExpandoData(ownerDocument);
+    var clone = data.frag.cloneNode(),
+        i = 0,
+        elems = getElements(),
+        l = elems.length;
+    for(;i<l;i++){
+        clone.createElement(elems[i]);
+    }
+    return clone;
+  }
+
+  /**
+   * Shivs the `createElement` and `createDocumentFragment` methods of the document.
+   * @private
+   * @param {Document|DocumentFragment} ownerDocument The document.
+   * @param {Object} data of the document.
+   */
+  function shivMethods(ownerDocument, data) {
+    if (!data.cache) {
+        data.cache = {};
+        data.createElem = ownerDocument.createElement;
+        data.createFrag = ownerDocument.createDocumentFragment;
+        data.frag = data.createFrag();
+    }
+
+
+    ownerDocument.createElement = function(nodeName) {
+      //abort shiv
+      if (!html5.shivMethods) {
+          return data.createElem(nodeName);
+      }
+      return createElement(nodeName, ownerDocument, data);
+    };
+
+    ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+      'var n=f.cloneNode(),c=n.createElement;' +
+      'h.shivMethods&&(' +
+        // unroll the `createElement` calls
+        getElements().join().replace(/[\w\-:]+/g, function(nodeName) {
+          data.createElem(nodeName);
+          data.frag.createElement(nodeName);
+          return 'c("' + nodeName + '")';
+        }) +
+      ');return n}'
+    )(html5, data.frag);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Shivs the given document.
+   * @memberOf html5
+   * @param {Document} ownerDocument The document to shiv.
+   * @returns {Document} The shived document.
+   */
+  function shivDocument(ownerDocument) {
+    if (!ownerDocument) {
+        ownerDocument = document;
+    }
+    var data = getExpandoData(ownerDocument);
+
+    if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) {
+      data.hasCSS = !!addStyleSheet(ownerDocument,
+        // corrects block display not defined in IE6/7/8/9
+        'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' +
+        // adds styling not present in IE6/7/8/9
+        'mark{background:#FF0;color:#000}' +
+        // hides non-rendered elements
+        'template{display:none}'
+      );
+    }
+    if (!supportsUnknownElements) {
+      shivMethods(ownerDocument, data);
+    }
+    return ownerDocument;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The `html5` object is exposed so that more elements can be shived and
+   * existing shiving can be detected on iframes.
+   * @type Object
+   * @example
+   *
+   * // options can be changed before the script is included
+   * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
+   */
+  var html5 = {
+
+    /**
+     * An array or space separated string of node names of the elements to shiv.
+     * @memberOf html5
+     * @type Array|String
+     */
+    'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video',
+
+    /**
+     * current version of html5shiv
+     */
+    'version': version,
+
+    /**
+     * A flag to indicate that the HTML5 style sheet should be inserted.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivCSS': (options.shivCSS !== false),
+
+    /**
+     * Is equal to true if a browser supports creating unknown/HTML5 elements
+     * @memberOf html5
+     * @type boolean
+     */
+    'supportsUnknownElements': supportsUnknownElements,
+
+    /**
+     * A flag to indicate that the document's `createElement` and `createDocumentFragment`
+     * methods should be overwritten.
+     * @memberOf html5
+     * @type Boolean
+     */
+    'shivMethods': (options.shivMethods !== false),
+
+    /**
+     * A string to describe the type of `html5` object ("default" or "default print").
+     * @memberOf html5
+     * @type String
+     */
+    'type': 'default',
+
+    // shivs the document according to the specified `html5` object options
+    'shivDocument': shivDocument,
+
+    //creates a shived element
+    createElement: createElement,
+
+    //creates a shived documentFragment
+    createDocumentFragment: createDocumentFragment,
+
+    //extends list of elements
+    addElements: addElements
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose html5
+  window.html5 = html5;
+
+  // shiv the document
+  shivDocument(document);
+
+  if(typeof module == 'object' && module.exports){
+    module.exports = html5;
+  }
+
+}(typeof window !== "undefined" ? window : this, document));

文件差異過大導致無法顯示
+ 166 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ar.js


文件差異過大導致無法顯示
+ 172 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-bg.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ca.js


文件差異過大導致無法顯示
+ 207 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-cn.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-cs.js


文件差異過大導致無法顯示
+ 215 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-de.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-dk.js


文件差異過大導致無法顯示
+ 166 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-el.js


文件差異過大導致無法顯示
+ 208 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-en.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-es.js


文件差異過大導致無法顯示
+ 185 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-fa.js


文件差異過大導致無法顯示
+ 169 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-fi.js


文件差異過大導致無法顯示
+ 166 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-fr.js


文件差異過大導致無法顯示
+ 166 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-gl.js


文件差異過大導致無法顯示
+ 167 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-he.js


文件差異過大導致無法顯示
+ 202 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-hr.js


文件差異過大導致無法顯示
+ 169 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-hu.js


文件差異過大導致無法顯示
+ 208 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-id.js


文件差異過大導致無法顯示
+ 166 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-is.js


文件差異過大導致無法顯示
+ 47 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-it.js


+ 196 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ja.js

@@ -0,0 +1,196 @@
+/**
+ * jqGrid Japanese Translation
+ * OKADA Yoshitada okada.dev@sth.jp
+ * http://trirand.com/blog/ 
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+**/
+/*global jQuery, define */
+(function( factory ) {
+	"use strict";
+	if ( typeof define === "function" && define.amd ) {
+		// AMD. Register as an anonymous module.
+		define([
+			"jquery",
+			"../grid.base"
+		], factory );
+	} else {
+		// Browser globals
+		factory( jQuery );
+	}
+}(function( $ ) {
+
+$.jgrid = $.jgrid || {};
+if(!$.jgrid.hasOwnProperty("regional")) {
+	$.jgrid.regional = [];
+}
+$.jgrid.regional["ja"] = {
+	defaults : {
+		recordtext: "{2} \u4EF6\u4E2D {0} - {1} \u3092\u8868\u793A ",
+	    emptyrecords: "\u8868\u793A\u3059\u308B\u30EC\u30B3\u30FC\u30C9\u304C\u3042\u308A\u307E\u305B\u3093",
+		loadtext: "\u8aad\u307f\u8fbc\u307f\u4e2d...",
+		pgtext : "{1} \u30DA\u30FC\u30B8\u4E2D {0} \u30DA\u30FC\u30B8\u76EE ",
+		savetext: "Saving...",
+		pgfirst : "First Page",
+		pglast : "Last Page",
+		pgnext : "Next Page",
+		pgprev : "Previous Page",
+		pgrecs : "Records per Page",
+		showhide: "Toggle Expand Collapse Grid",
+		// mobile
+		pagerCaption : "Grid::Page Settings",
+		pageText : "Page:",
+		recordPage : "Records per Page",
+		nomorerecs : "No more records...",
+		scrollPullup: "Pull up to load more...",
+		scrollPulldown : "Pull down to refresh...",
+		scrollRefresh : "Release to refresh..."
+	},
+	search : {
+	    caption: "\u691c\u7d22...",
+	    Find: "\u691c\u7d22",
+	    Reset: "\u30ea\u30bb\u30c3\u30c8",
+	    odata: [{ oper:'eq', text:"\u6B21\u306B\u7B49\u3057\u3044"}, { oper:'ne', text:"\u6B21\u306B\u7B49\u3057\u304F\u306A\u3044"},
+            { oper:'lt', text:"\u6B21\u3088\u308A\u5C0F\u3055\u3044"}, { oper:'le', text:"\u6B21\u306B\u7B49\u3057\u3044\u304B\u5C0F\u3055\u3044"},
+            { oper:'gt', text:"\u6B21\u3088\u308A\u5927\u304D\u3044"}, { oper:'ge', text:"\u6B21\u306B\u7B49\u3057\u3044\u304B\u5927\u304D\u3044"},
+            { oper:'bw', text:"\u6B21\u3067\u59CB\u307E\u308B"}, { oper:'bn', text:"\u6B21\u3067\u59CB\u307E\u3089\u306A\u3044"},
+            { oper:'in', text:"\u6B21\u306B\u542B\u307E\u308C\u308B"}, { oper:'ni', text:"\u6B21\u306B\u542B\u307E\u308C\u306A\u3044"},
+            { oper:'ew', text:"\u6B21\u3067\u7D42\u308F\u308B"}, { oper:'en', text:"\u6B21\u3067\u7D42\u308F\u3089\u306A\u3044"},
+            { oper:'cn', text:"\u6B21\u3092\u542B\u3080"}, { oper:'nc', text:"\u6B21\u3092\u542B\u307E\u306A\u3044"},
+			{ oper:'nu', text:'is null'},{ oper:'nn', text:'is not null'}],
+	    groupOps: [{
+                op: "AND",
+                text: "\u3059\u3079\u3066\u306E"
+            },
+            {
+                op: "OR",
+                text: "\u3044\u305A\u308C\u304B\u306E"
+            }],
+		operandTitle : "Click to select search operation.",
+		resetTitle : "Reset Search Value"
+	},
+	edit : {
+	    addCaption: "\u30ec\u30b3\u30fc\u30c9\u8ffd\u52a0",
+	    editCaption: "\u30ec\u30b3\u30fc\u30c9\u7de8\u96c6",
+	    bSubmit: "\u9001\u4fe1",
+	    bCancel: "\u30ad\u30e3\u30f3\u30bb\u30eb",
+  		bClose: "\u9589\u3058\u308b",
+      saveData: "\u30C7\u30FC\u30BF\u304C\u5909\u66F4\u3055\u308C\u3066\u3044\u307E\u3059\u3002\u4FDD\u5B58\u3057\u307E\u3059\u304B\uFF1F",
+      bYes: "\u306F\u3044",
+      bNo: "\u3044\u3044\u3048",
+      bExit: "\u30AD\u30E3\u30F3\u30BB\u30EB",
+	    msg: {
+	        required:"\u3053\u306e\u9805\u76ee\u306f\u5fc5\u9808\u3067\u3059\u3002",
+	        number:"\u6b63\u3057\u3044\u6570\u5024\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002",
+	        minValue:"\u6b21\u306e\u5024\u4ee5\u4e0a\u3067\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002",
+	        maxValue:"\u6b21\u306e\u5024\u4ee5\u4e0b\u3067\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002",
+	        email: "e-mail\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002",
+	        integer: "\u6b63\u3057\u3044\u6574\u6570\u5024\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002",
+    			date: "\u6b63\u3057\u3044\u5024\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002",
+          url: "\u306F\u6709\u52B9\u306AURL\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u3002\20\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u304C\u5FC5\u8981\u3067\u3059\u3002 ('http://' \u307E\u305F\u306F 'https://')",
+          nodefined: " \u304C\u5B9A\u7FA9\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
+          novalue: " \u623B\u308A\u5024\u304C\u5FC5\u8981\u3067\u3059",
+          customarray: "\u30AB\u30B9\u30BF\u30E0\u95A2\u6570\u306F\u914D\u5217\u3092\u8FD4\u3059\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059",
+          customfcheck: "\u30AB\u30B9\u30BF\u30E0\u691C\u8A3C\u306B\u306F\u30AB\u30B9\u30BF\u30E0\u95A2\u6570\u304C\u5FC5\u8981\u3067\u3059"
+		}
+	},
+	view : {
+      caption: "\u30EC\u30B3\u30FC\u30C9\u3092\u8868\u793A",
+      bClose: "\u9589\u3058\u308B"
+	},
+	del : {
+	    caption: "\u524a\u9664",
+	    msg: "\u9078\u629e\u3057\u305f\u30ec\u30b3\u30fc\u30c9\u3092\u524a\u9664\u3057\u307e\u3059\u304b\uff1f",
+	    bSubmit: "\u524a\u9664",
+	    bCancel: "\u30ad\u30e3\u30f3\u30bb\u30eb"
+	},
+	nav : {
+    	edittext: " ",
+	    edittitle: "\u9078\u629e\u3057\u305f\u884c\u3092\u7de8\u96c6",
+      addtext:" ",
+	    addtitle: "\u884c\u3092\u65b0\u898f\u8ffd\u52a0",
+	    deltext: " ",
+	    deltitle: "\u9078\u629e\u3057\u305f\u884c\u3092\u524a\u9664",
+	    searchtext: " ",
+	    searchtitle: "\u30ec\u30b3\u30fc\u30c9\u691c\u7d22",
+	    refreshtext: "",
+	    refreshtitle: "\u30b0\u30ea\u30c3\u30c9\u3092\u30ea\u30ed\u30fc\u30c9",
+	    alertcap: "\u8b66\u544a",
+	    alerttext: "\u884c\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044\u3002",
+      viewtext: "",
+      viewtitle: "\u9078\u629E\u3057\u305F\u884C\u3092\u8868\u793A",
+		savetext: "",
+		savetitle: "Save row",
+		canceltext: "",
+		canceltitle : "Cancel row editing",
+		selectcaption : "Actions..."
+	},
+	col : {
+	    caption: "\u5217\u3092\u8868\u793a\uff0f\u96a0\u3059",
+	    bSubmit: "\u9001\u4fe1",
+	    bCancel: "\u30ad\u30e3\u30f3\u30bb\u30eb"	
+	},
+	errors : {
+		errcap : "\u30a8\u30e9\u30fc",
+		nourl : "URL\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002",
+		norecords: "\u51e6\u7406\u5bfe\u8c61\u306e\u30ec\u30b3\u30fc\u30c9\u304c\u3042\u308a\u307e\u305b\u3093\u3002",
+	    model : "colNames\u306e\u9577\u3055\u304ccolModel\u3068\u4e00\u81f4\u3057\u307e\u305b\u3093\u3002"
+	},
+	formatter : {
+            integer: {
+                thousandsSeparator: ",",
+                defaultValue: '0'
+            },
+            number: {
+                decimalSeparator: ".",
+                thousandsSeparator: ",",
+                decimalPlaces: 2,
+                defaultValue: '0.00'
+            },
+            currency: {
+                decimalSeparator: ".",
+                thousandsSeparator: ",",
+                decimalPlaces: 0,
+                prefix: "",
+                suffix: "",
+                defaultValue: '0'
+            },
+		date : {
+			dayNames:   [
+				"\u65e5", "\u6708", "\u706b", "\u6c34", "\u6728", "\u91d1", "\u571f",
+				"\u65e5", "\u6708", "\u706b", "\u6c34", "\u6728", "\u91d1", "\u571f"
+			],
+			monthNames: [
+				"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",
+				"1\u6708", "2\u6708", "3\u6708", "4\u6708", "5\u6708", "6\u6708", "7\u6708", "8\u6708", "9\u6708", "10\u6708", "11\u6708", "12\u6708"
+			],
+			AmPm : ["am","pm","AM","PM"],
+			S: function (j) { return "\u756a\u76ee"; },
+			srcformat: 'Y-m-d',
+			newformat: 'd/m/Y',
+			parseRe : /[#%\\\/:_;.,\t\s-]/,
+			masks : {
+	            ISO8601Long:"Y-m-d H:i:s",
+	            ISO8601Short:"Y-m-d",
+	            ShortDate: "n/j/Y",
+	            LongDate: "l, F d, Y",
+	            FullDateTime: "l, F d, Y g:i:s A",
+	            MonthDay: "F d",
+	            ShortTime: "g:i A",
+	            LongTime: "g:i:s A",
+	            SortableDateTime: "Y-m-d\\TH:i:s",
+	            UniversalSortableDateTime: "Y-m-d H:i:sO",
+	            YearMonth: "F, Y"
+	        },
+	        reformatAfterEdit : false,
+			userLocalTime : false
+		},
+		baseLinkUrl: '',
+		showAction: '',
+	    target: '',
+	    checkbox : {disabled:true},
+		idName : 'id'
+	}
+};
+}));

文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-kr.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-lt.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-me.js


文件差異過大導致無法顯示
+ 189 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-nl.js


文件差異過大導致無法顯示
+ 75 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-no.js


文件差異過大導致無法顯示
+ 172 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-pl.js


文件差異過大導致無法顯示
+ 176 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-pt-br.js


文件差異過大導致無法顯示
+ 165 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-pt.js


文件差異過大導致無法顯示
+ 179 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ro.js


文件差異過大導致無法顯示
+ 169 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-ru.js


文件差異過大導致無法顯示
+ 167 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sk.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sr-latin.js


文件差異過大導致無法顯示
+ 168 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sr.js


+ 0 - 0
src/main/webapp/static/ace/assets/js/jqGrid/i18n/grid.locale-sv.js


部分文件因文件數量過多而無法顯示